[
  {
    "path": ".browserslistrc",
    "content": "# `npx browserslist --mobile-to-desktop \"baseline widely available\"` when the last major is released.\n# On update, sync references where \"#stable-snapshot\" is mentioned in the codebase.\n[stable]\nand_chr 141\nand_chr 140\nand_chr 139\nand_chr 138\nand_chr 137\nand_chr 136\nand_chr 135\nand_chr 134\nand_chr 133\nand_chr 132\nand_chr 131\nand_chr 130\nand_chr 129\nand_chr 128\nand_chr 127\nand_chr 126\nand_chr 125\nand_chr 124\nand_chr 123\nand_chr 122\nand_chr 121\nand_chr 120\nand_chr 119\nand_chr 118\nand_chr 117\nand_chr 116\nand_chr 115\nand_chr 114\nand_chr 113\nand_chr 112\nand_chr 111\nand_ff 144\nand_ff 143\nand_ff 142\nand_ff 141\nand_ff 140\nand_ff 139\nand_ff 138\nand_ff 137\nand_ff 136\nand_ff 135\nand_ff 134\nand_ff 133\nand_ff 132\nand_ff 131\nand_ff 130\nand_ff 129\nand_ff 128\nand_ff 127\nand_ff 126\nand_ff 125\nand_ff 124\nand_ff 123\nand_ff 122\nand_ff 121\nand_ff 120\nand_ff 119\nand_ff 118\nand_ff 117\nand_ff 116\nand_ff 115\nand_ff 114\nand_ff 113\nchrome 141\nchrome 140\nchrome 139\nchrome 138\nchrome 137\nchrome 136\nchrome 135\nchrome 134\nchrome 133\nchrome 132\nchrome 131\nchrome 130\nchrome 129\nchrome 128\nchrome 127\nchrome 126\nchrome 125\nchrome 124\nchrome 123\nchrome 122\nchrome 121\nchrome 120\nchrome 119\nchrome 118\nchrome 117\nchrome 116\nchrome 115\nchrome 114\nchrome 113\nchrome 112\nchrome 111\nedge 141\nedge 140\nedge 139\nedge 138\nedge 137\nedge 136\nedge 135\nedge 134\nedge 133\nedge 132\nedge 131\nedge 130\nedge 129\nedge 128\nedge 127\nedge 126\nedge 125\nedge 124\nedge 123\nedge 122\nedge 121\nedge 120\nedge 119\nedge 118\nedge 117\nedge 116\nedge 115\nedge 114\nedge 113\nedge 112\nedge 111\nfirefox 144\nfirefox 143\nfirefox 142\nfirefox 141\nfirefox 140\nfirefox 139\nfirefox 138\nfirefox 137\nfirefox 136\nfirefox 135\nfirefox 134\nfirefox 133\nfirefox 132\nfirefox 131\nfirefox 130\nfirefox 129\nfirefox 128\nfirefox 127\nfirefox 126\nfirefox 125\nfirefox 124\nfirefox 123\nfirefox 122\nfirefox 121\nfirefox 120\nfirefox 119\nfirefox 118\nfirefox 117\nfirefox 116\nfirefox 115\nfirefox 114\nfirefox 113\nios_saf 26.0\nios_saf 18.5-18.6\nios_saf 18.4\nios_saf 18.3\nios_saf 18.2\nios_saf 18.1\nios_saf 18.0\nios_saf 17.6-17.7\nios_saf 17.5\nios_saf 17.4\nios_saf 17.3\nios_saf 17.2\nios_saf 17.1\nios_saf 17.0\nios_saf 16.6-16.7\nios_saf 16.5\nios_saf 16.4\nsafari 26.0\nsafari 18.5-18.6\nsafari 18.4\nsafari 18.3\nsafari 18.2\nsafari 18.1\nsafari 18.0\nsafari 17.6\nsafari 17.5\nsafari 17.4\nsafari 17.3\nsafari 17.2\nsafari 17.1\nsafari 17.0\nsafari 16.6\nsafari 16.5\nsafari 16.4\n\n# snapshot of `npx browserslist \"maintained node versions\"`\n# On update check all #stable-snapshot markers\n[node]\nnode 25.0.0\nnode 24.10.0\nnode 22.21.0\nnode 20.19.0\n\n# same as `node`\n[coverage]\nnode 25.0.0\nnode 24.10.0\nnode 22.21.0\nnode 20.19.0\n\n# same as `node`\n[development]\nnode 25.0.0\nnode 24.10.0\nnode 22.21.0\nnode 20.19.0\n\n# same as `node`\n[test]\nnode 25.0.0\nnode 24.10.0\nnode 22.21.0\nnode 20.19.0\n"
  },
  {
    "path": ".circleci/config.yml",
    "content": "version: 2.1\n\norbs:\n  code-infra: https://raw.githubusercontent.com/mui/mui-public/f544b55fdbbb922b7293365705cf62895b4f5186/.circleci/orbs/code-infra.yml\n\nparameters:\n  browserstack-force:\n    description: Whether to force browserstack usage. We have limited resources on browserstack so the pipeline might decide to skip browserstack if this parameter isn't set to true.\n    type: boolean\n    default: false\n  react-version:\n    description: The version of react to be used\n    type: string\n    default: stable\n  workflow:\n    description: The name of the workflow to run\n    type: string\n    default: pipeline\n\ndefault-job: &default-job\n  parameters:\n    react-version:\n      description: The version of react to be used\n      type: string\n      default: << pipeline.parameters.react-version >>\n  resource_class: medium\n  environment:\n    # expose it globally otherwise we have to thread it from each job to the install command\n    BROWSERSTACK_FORCE: << pipeline.parameters.browserstack-force >>\n    COREPACK_ENABLE_DOWNLOAD_PROMPT: '0'\n  working_directory: /tmp/base-ui\n  executor: code-infra/mui-node\n\ndefault-context: &default-context\n  context:\n    - org-global\n\n# CircleCI has disabled the cache across forks for security reasons.\n# Following their official statement, it was a quick solution, they\n# are working on providing this feature back with appropriate security measures.\n# https://discuss.circleci.com/t/saving-cache-stopped-working-warning-skipping-this-step-disabled-in-configuration/24423/21\n#\n# restore_repo: &restore_repo\n#   restore_cache:\n#     key: v1-repo-{{ .Branch }}-{{ .Revision }}\n\ncommands:\n  build-with-compiler:\n    description: 'Build packages with React Compiler'\n    steps:\n      - run:\n          name: Add compiler package\n          command: pnpm -F \"./packages/*\" add react-compiler-runtime\n      - run:\n          name: Build with compiler\n          command: pnpm -F \"./packages/*\" build --enableReactCompiler\n          environment:\n            MUI_REACT_COMPILER_MODE: 'infer'\n\njobs:\n  test_unit:\n    <<: *default-job\n    resource_class: large\n    steps:\n      - checkout\n      - code-infra/install-deps:\n          package-overrides: react@<< parameters.react-version >>\n      - run:\n          name: Run tests on JSDOM\n          command: pnpm test:jsdom\n      - store_test_results:\n          path: test-results\n  docs_validate:\n    <<: *default-job\n    steps:\n      - checkout\n      - code-infra/install-deps\n      - run:\n          name: Validate the documentation files\n          command: pnpm docs:validate\n  test_lint:\n    <<: *default-job\n    steps:\n      - checkout\n      - code-infra/install-deps\n      - code-infra/run-linters\n  test_static:\n    <<: *default-job\n    steps:\n      - checkout\n      - code-infra/install-deps\n      - code-infra/check-static-changes\n      - run:\n          name: Generate the documentation\n          command: pnpm docs:api\n      - run:\n          name: '`pnpm docs:api` changes committed?'\n          command: git add -A && git diff --exit-code --staged\n      - run:\n          name: '`pnpm extract-error-codes` changes committed?'\n          command: |\n            pnpm extract-error-codes\n            git add -A && git diff --exit-code --staged\n      - run:\n          name: '`pnpm inline-scripts` changes committed?'\n          command: |\n            pnpm inline-scripts\n            git add -A && git diff --exit-code --staged\n  test_types:\n    <<: *default-job\n    resource_class: 'medium+'\n    steps:\n      - checkout\n      - code-infra/install-deps:\n          package-overrides: react@<< parameters.react-version >>\n      - run:\n          name: Tests TypeScript definitions\n          command: pnpm typescript\n          environment:\n            NODE_OPTIONS: --max-old-space-size=3072\n      - run:\n          name: Any defect declaration files?\n          command: pnpm code-infra validate-built-types\n      - save_cache:\n          name: Save generated declaration files\n          key: typescript-declaration-files-{{ .Branch }}-{{ .Revision }}\n          paths:\n            # packages with generated declaration files\n            - packages/react/build\n  test_types_next:\n    <<: *default-job\n    resource_class: 'medium+'\n    steps:\n      - checkout\n      - code-infra/install-deps:\n          package-overrides: typescript@next\n      - run:\n          name: Tests TypeScript definitions\n          command: pnpm typescript\n      - restore_cache:\n          name: Restore generated declaration files\n          keys:\n            # We assume that the target branch is `master` and that declaration files are persisted in commit order.\n            # \"If there are multiple matches, the most recently generated cache will be used.\"\n            - typescript-declaration-files-master\n      - run:\n          name: Log defect declaration files\n          command: |\n            # ignore build failures\n            # Fixing these takes some effort that isn't viable to merge in a single PR.\n            # We'll simply monitor them for now.\n            set +e\n            pnpm code-infra validate-built-types\n            exit 0\n  test_browser:\n    <<: *default-job\n    resource_class: large\n    executor:\n      name: code-infra/mui-node-browser\n      playwright-img-version: v1.58.2-noble\n    steps:\n      - checkout\n      - code-infra/install-deps:\n          package-overrides: react@<< parameters.react-version >>\n      - run:\n          name: Run tests on headless Chromium\n          command: pnpm test:chromium\n      - store_test_results:\n          path: test-results\n  test_regressions:\n    <<: *default-job\n    resource_class: 'medium+'\n    executor:\n      name: code-infra/mui-node-browser\n      playwright-img-version: v1.58.2-noble\n    steps:\n      - checkout\n      - code-infra/install-deps\n      - run:\n          name: Run visual regression tests\n          command: xvfb-run pnpm test:regressions\n      - store_test_results:\n          path: test-results\n      - run:\n          name: Upload screenshots to Argos CI\n          command: pnpm test:argos\n  test_e2e:\n    <<: *default-job\n    executor:\n      name: code-infra/mui-node-browser\n      playwright-img-version: v1.58.2-noble\n    steps:\n      - checkout\n      - code-infra/install-deps\n      - run:\n          name: pnpm test:e2e\n          command: pnpm test:e2e\n      - store_test_results:\n          path: test-results\n\n  test_package:\n    <<: *default-job\n    resource_class: 'medium+'\n    steps:\n      - checkout\n      - code-infra/install-deps\n      - run:\n          name: Build packages\n          command: pnpm release:build\n      - run:\n          name: Validate type declarations\n          command: pnpm code-infra validate-built-types\n      - run:\n          name: Check public types\n          command: pnpm -r run release:test\n      - run:\n          name: Test Node.js module resolution\n          command: pnpm -F @base-ui/test-node-resolution test\n      - run:\n          name: Verify built packages\n          command: pnpm -r test:package\n      - code-infra/upload-size-snapshot\n\n  test_unit_compiler:\n    <<: *default-job\n    resource_class: large\n    steps:\n      - checkout\n      - code-infra/install-deps\n      - build-with-compiler\n      - run:\n          name: Run tests on JSDOM\n          command: pnpm test:jsdom\n          environment:\n            MUI_DISABLE_WORKSPACE_ALIASES: 1\n      - store_test_results:\n          path: test-results\n\n  test_browser_compiler:\n    <<: *default-job\n    resource_class: large\n    executor:\n      name: code-infra/mui-node-browser\n      playwright-img-version: v1.58.2-noble\n    steps:\n      - checkout\n      - code-infra/install-deps\n      - build-with-compiler\n      - run:\n          name: Run tests on headless Chromium\n          command: pnpm test:chromium\n          environment:\n            MUI_DISABLE_WORKSPACE_ALIASES: 1\n      - store_test_results:\n          path: test-results\n\nworkflows:\n  pipeline:\n    when:\n      equal: [pipeline, << pipeline.parameters.workflow >>]\n    jobs:\n      - test_unit:\n          <<: *default-context\n          name: 'JSDOM tests'\n      - test_lint:\n          <<: *default-context\n          name: 'Linting'\n      - test_static:\n          <<: *default-context\n          name: 'Generated files verification'\n      - test_types:\n          <<: *default-context\n          name: 'Typechecking'\n      - test_browser:\n          <<: *default-context\n          name: 'Browser tests'\n      - test_regressions:\n          <<: *default-context\n          name: 'Regression tests'\n      - test_e2e:\n          <<: *default-context\n          name: 'E2E tests'\n      - test_package:\n          <<: *default-context\n          name: 'Package verification'\n      - docs_validate:\n          <<: *default-context\n          name: 'Validating Docs'\n      - test_unit_compiler:\n          <<: *default-context\n          name: 'JSDOM tests (React Compiler)'\n      - test_browser_compiler:\n          <<: *default-context\n          name: 'Browser tests (React Compiler)'\n  react-18:\n    when:\n      equal: [pipeline, << pipeline.parameters.workflow >>]\n    jobs:\n      - test_unit:\n          <<: *default-context\n          name: 'JSDOM tests (React 18)'\n          react-version: '^18'\n      - test_browser:\n          <<: *default-context\n          name: 'Browser tests (React 18)'\n          react-version: '^18'\n      - test_types:\n          <<: *default-context\n          name: 'Typechecking (React 18)'\n          react-version: '^18'\n  typescript-next:\n    triggers:\n      - schedule:\n          cron: '0 0 * * *'\n          filters:\n            branches:\n              only:\n                - master\n    jobs:\n      - test_types_next:\n          <<: *default-context\n          name: 'Typechecking (typescript@next)'\n"
  },
  {
    "path": ".editorconfig",
    "content": "# EditorConfig is awesome: https://editorconfig.org/\n\n# top-most EditorConfig file\nroot = true\n\n# Unix-style newlines with a newline ending every file\n[*]\ntrim_trailing_whitespace = true\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ninsert_final_newline = true\nmax_line_length = 100\n"
  },
  {
    "path": ".gitattributes",
    "content": "# Set the default behavior, in case people don't have core.autocrlf set.\n* text=auto eol=lf\n\n# Undo the GitHub's default\n# https://github.com/github/linguist/blob/96ad1185828f44bb9b774328a584551ee57ed264/lib/linguist/vendor.yml#L177\npackages/**/*.d.ts -linguist-vendored\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "* @atomiks @michaldudak @flaviendelangle\n/docs/src/ @atomiks @colmtuite @michaldudak @flaviendelangle\n/packages/react/ @atomiks @colmtuite @michaldudak @flaviendelangle @LukasTy \n/scripts/ @michaldudak\n/packages/utils/ @romgrk \n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/1.bug.md",
    "content": "---\nname: 'Bug report'\nabout: 'File a bug report.'\nlabels: ['status: waiting for maintainer']\n---\n\n# Bug report\n\n## Current behavior\n\nProvide a clear and concise description of the current behavior you're experiencing. If applicable, provide screenshots and/or videos.\n\n## Expected behavior\n\nProvide a clear and concise description of the expected behavior.\n\n## Reproducible example\n\nLink to a CodeSandbox, StackBlitz, or other IDE.\n\n## Base UI version\n\nFor example v1.0.2\n\n## Which browser are you using?\n\nChrome/Safari/Firefox/Safari iOS etc.\n\n## Which OS are you using?\n\nMac OS/Windows/Other etc.\n\n## Which assistive tech are you using (if applicable)?\n\nVoiceover/JAWS etc.\n\n## Additional context\n\nProvide any additional context that might help us identify the problem and find a solution.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/2.feature-request.md",
    "content": "---\nname: 'Feature request'\nabout: 'Suggest a new component or an enhancement for an existing component.'\nlabels: ['status: waiting for maintainer']\n---\n\n# Feature request\n\n## Summary\n\n<!-- Provide a clear and concise description of the component or enhancement that you want. -->\n\n## Examples in other libraries\n\n<!-- Provide a link to examples of this component or feature implemented in other libraries (if applicable).  -->\n\n## Motivation\n\n<!-- What are you trying to accomplish? Which kind of user is this for? Providing context helps us come up with a solution that is more useful in the real world -->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/3.get-help.md",
    "content": "---\nname: 'Get help'\nabout: 'Ask a question.'\nlabels: ['status: waiting for maintainer']\n---\n\n# Get help\n\n## Ask a question\n\nBefore opening an issue, please consider starting a [GitHub Discussion](https://github.com/mui/base-ui/discussions) or asking the community for help in our [Discord](https://base-ui.com/r/discord).\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/4.docs-feedback.md",
    "content": "---\nname: 'Docs feedback'\nabout: 'Help us improve Base UI documentation.'\nlabels: ['status: waiting for maintainer']\n---\n\n# Docs feedback\n\n## How can we improve Base UI documentation?\n\nProvide a clear and concise description of the issue you're experiencing. If applicable, provide screenshots and/or videos.\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!-- Thanks so much for your PR, your contribution is appreciated! ❤️ -->\n\n- [ ] I have followed (at least) the [PR section of the contributing guide](https://github.com/mui/base-ui/blob/HEAD/CONTRIBUTING.md#sending-a-pull-request).\n"
  },
  {
    "path": ".github/codeql/codeql-config.yml",
    "content": "name: CodeQL configuration\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  # Enable version updates for npm\n  - package-ecosystem: npm\n    # Look for `package.json` and `lock` files in the `root` directory\n    directory: /\n    schedule:\n      interval: yearly\n    # https://stackoverflow.com/questions/64047526/how-to-get-dependabot-to-trigger-for-security-updates-only\n    open-pull-requests-limit: 0\n    labels:\n      - dependencies\n      - security\n"
  },
  {
    "path": ".github/workflows/check-if-pr-has-label.yml",
    "content": "name: Check if PR has label\n\non:\n  pull_request:\n    types: [opened, labeled, unlabeled, synchronize]\n\npermissions: {}\n\njobs:\n  test-label-applied:\n    # Tests that label is added on the PR\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n    steps:\n      - uses: mnajdova/github-action-required-labels@ca0df9249827e43aa4b4a0d25d9fe3e9b19b0705 # v2.1.0\n        with:\n          mode: minimum\n          count: 1\n          labels: ''\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n    branches-ignore:\n      # Renovate branches are always Pull Requests.\n      # We don't need to run CI twice (push+pull_request)\n      - 'renovate/**'\n      - 'dependabot/**'\n  pull_request:\n\npermissions: {}\n\njobs:\n  # Tests dev-only scripts across all supported dev environments\n  test-dev:\n    # l10nbot does not affect dev scripts.\n    if: ${{ github.actor != 'l10nbot' }}\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        os: [macos-latest, windows-latest, ubuntu-latest]\n    steps:\n      - run: echo \"${{ github.actor }}\"\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          # fetch all tags which are required for `pnpm release:changelog`\n          fetch-depth: 0\n      - name: Set up pnpm\n        uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0\n      - name: Use Node.js 22.x\n        uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0\n        with:\n          node-version: '22.18'\n          cache: 'pnpm' # https://github.com/actions/setup-node/blob/main/docs/advanced-usage.md#caching-packages-dependencies\n      - run: pnpm install\n      - run: pnpm release:build\n      - name: Publish packages\n        if: matrix.os == 'ubuntu-latest'\n        uses: mui/mui-public/.github/actions/ci-publish@f544b55fdbbb922b7293365705cf62895b4f5186\n        with:\n          pr-comment: true\n"
  },
  {
    "path": ".github/workflows/codeql.yml",
    "content": "name: CodeQL\n\non:\n  schedule:\n    - cron: '0 2 * * *'\n\npermissions: {}\n\njobs:\n  analyze:\n    name: Analyze\n    runs-on: ubuntu-latest\n    permissions:\n      actions: read\n      contents: read\n      security-events: write\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      # Initializes the CodeQL tools for scanning.\n      - name: Initialize CodeQL\n        uses: github/codeql-action/init@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6\n        with:\n          languages: typescript\n          config-file: ./.github/codeql/codeql-config.yml\n          # If you wish to specify custom queries, you can do so here or in a config file.\n          # By default, queries listed here will override any specified in a config file.\n          # Prefix the list here with \"+\" to use these queries and those in the config file.\n\n          # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs\n          # queries: security-extended,security-and-quality\n      - name: Perform CodeQL Analysis\n        uses: github/codeql-action/analyze@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6\n"
  },
  {
    "path": ".github/workflows/ensure-triage-label.yml",
    "content": "name: Ensure triage label is present\n\non:\n  label:\n    types:\n      - deleted\n  issues:\n    types:\n      - opened\n\npermissions: {}\n\njobs:\n  label_issues:\n    runs-on: ubuntu-latest\n    permissions:\n      issues: write\n    steps:\n      - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8\n        with:\n          script: |\n            const { data: labels } = await github.rest.issues.listLabelsOnIssue({\n              issue_number: context.issue.number,\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n            });\n\n            if (labels.length <= 0) {\n              await github.rest.issues.addLabels({\n                issue_number: context.issue.number,\n                owner: context.repo.owner,\n                repo: context.repo.repo,\n                labels: ['status: waiting for maintainer']\n              })\n            }\n"
  },
  {
    "path": ".github/workflows/fixed-issue.yml",
    "content": "name: Comment on fixed issues\n\non:\n  issues:\n    types: [closed]\n\npermissions: {}\n\njobs:\n  add-comment:\n    runs-on: ubuntu-latest\n    permissions:\n      issues: write\n      pull-requests: read\n    steps:\n      - name: Get associated PRs\n        id: get-prs\n        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8\n        with:\n          script: |\n            const issue_number = context.payload.issue.number;\n\n            console.log(`Processing issue #${issue_number}`);\n\n            // Use GraphQL to find PRs that close this issue\n            const query = `\n              query($owner:String!, $repo:String!, $issueNumber:Int!) {\n                repository(owner: $owner, name: $repo) {\n                  issue(number: $issueNumber) {\n                    timelineItems(last: 100, itemTypes: [CONNECTED_EVENT, CROSS_REFERENCED_EVENT]) {\n                      nodes {\n                        __typename\n                        ... on ConnectedEvent {\n                          subject {\n                            ... on PullRequest {\n                              number\n                              merged\n                              mergedAt\n                            }\n                          }\n                        }\n                        ... on CrossReferencedEvent {\n                          source {\n                            ... on PullRequest {\n                              number\n                              merged\n                              mergedAt\n                            }\n                          }\n                        }\n                      }\n                    }\n                  }\n                }\n              }\n            `;\n\n            const variables = {\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n              issueNumber: parseInt(issue_number)\n            };\n\n            try {\n              const result = await github.graphql(query, variables);\n              console.log('GraphQL query completed successfully');\n\n              // Extract all merged PRs that are linked to this issue\n              const relatedPRs = [];\n              if (result.repository.issue) {\n                const timelineItems = result.repository.issue.timelineItems.nodes;\n\n                for (const item of timelineItems) {\n                  let pr = null;\n\n                  if (item.__typename === 'ConnectedEvent' && item.subject) {\n                    pr = item.subject;\n                  } else if (item.__typename === 'CrossReferencedEvent' && item.source) {\n                    pr = item.source;\n                  }\n\n                  if (pr && pr.merged) {\n                    relatedPRs.push({\n                      number: pr.number,\n                      mergedAt: pr.mergedAt\n                    });\n                  }\n                }\n              }\n\n              // Sort by merge date (newest first) and take the first one\n              relatedPRs.sort((a, b) => new Date(b.mergedAt) - new Date(a.mergedAt));\n\n              if (relatedPRs.length === 0) {\n                console.log('No merged PRs found for this issue');\n                return;\n              }\n\n              const prNumber = relatedPRs[0].number;\n              console.log(`Found merged PR #${prNumber} associated with issue #${issue_number}`);\n\n              // Set output for the next step\n              core.setOutput('pr_number', prNumber);\n              core.setOutput('has_pr', 'true');\n\n            } catch (error) {\n              console.error('Error fetching related PRs:', error);\n            }\n\n      - name: Add comment to issue\n        if: steps.get-prs.outputs.has_pr == 'true'\n        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8\n        with:\n          script: |\n            // Get PR number from the previous step's output\n            const prNumber = '${{ steps.get-prs.outputs.pr_number }}';\n            const issueNumber = context.payload.issue.number;\n\n            // Get issue labels\n            const issue = context.payload.issue;\n            const labels = issue.labels.map(label => label.name);\n\n            // Check if this is a bug/regression or a feature\n            const isNewFeature = labels.some(label => label.includes('new feature'));\n\n            const typeText = isNewFeature ? 'feature' : 'fix';\n\n            const commentBody = `This ${typeText} will be available in the next npm release of Base UI.\n\n            In the meantime, you can try it out on our Canary release channel:\n\n            \\`\\`\\`sh\n            npm i https://pkg.pr.new/@base-ui/react@${prNumber}\n            \\`\\`\\``;\n\n            await github.rest.issues.createComment({\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n              issue_number: issueNumber,\n              body: commentBody\n            });\n\n            console.log(`Added comment to issue #${issueNumber}`);\n"
  },
  {
    "path": ".github/workflows/maintenance.yml",
    "content": "name: Maintenance\n\non:\n  # So that PRs touching the same files as the push are updated\n  push:\n    branches:\n      - master\n      - next\n  # So that the `dirtyLabel` is removed if conflicts are resolved\n  # Could put too much strain on rate limit\n  # If we hit the rate limit too often remove this event\n  pull_request_target:\n    branches:\n      - master\n      - next\n    types: [synchronize]\n\npermissions: {}\n\njobs:\n  main:\n    # l10nbot creates a lot of commits at once which starves CI.\n    # We rely on other pushes to mark these branches as outdated.\n    if: ${{ github.actor != 'l10nbot' }}\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      pull-requests: write\n    steps:\n      - run: echo \"${{ github.actor }}\"\n      - name: Check if prs are dirty\n        uses: eps1lon/actions-label-merge-conflict@1df065ebe6e3310545d4f4c4e862e43bdca146f0 # v3.0.3\n        with:\n          dirtyLabel: 'PR: out-of-date'\n          removeOnDirtyLabel: 'PR: ready to ship'\n          repoToken: '${{ secrets.GITHUB_TOKEN }}'\n          retryAfter: 130\n          retryMax: 10\n"
  },
  {
    "path": ".github/workflows/mark-duplicate.yml",
    "content": "name: Mark duplicate\n\non:\n  issue_comment:\n    types: [created]\n\npermissions: {}\n\njobs:\n  mark-duplicate:\n    runs-on: ubuntu-latest\n    if: ${{ !github.event.issue.pull_request }}\n    permissions:\n      contents: read\n      issues: write\n    steps:\n      - name: Mark duplicate\n        uses: actions-cool/issues-helper@71b62d7da76e59ff7b193904feb6e77d4dbb2777 # v3.7.6\n        with:\n          actions: 'mark-duplicate'\n          token: ${{ secrets.GITHUB_TOKEN }}\n          duplicate-labels: 'duplicate'\n          remove-labels: 'status: incomplete,status: waiting for maintainer'\n          close-issue: true\n"
  },
  {
    "path": ".github/workflows/new-issue-triage.yml",
    "content": "name: New issue triage\non:\n  issues:\n    types:\n      - opened\n\npermissions: {}\n\njobs:\n  issue_cleanup:\n    name: Clean issue body\n    uses: mui/mui-public/.github/workflows/issues_body-cleanup.yml@f544b55fdbbb922b7293365705cf62895b4f5186\n    permissions:\n      contents: read\n      issues: write\n"
  },
  {
    "path": ".github/workflows/no-response.yml",
    "content": "name: No response\n\n# `issues`.`closed`, `issue_comment`.`created`, and `scheduled` event types are required for this Action\n# to work properly.\non:\n  issues:\n    types: [closed]\n  issue_comment:\n    types: [created]\n  schedule:\n    # These runs in our repos are spread evenly throughout the day to avoid hitting rate limits.\n    # If you change this schedule, consider changing the remaining repositories as well.\n    # Runs at 9 am, 9 pm\n    - cron: '0 9,21 * * *'\n\npermissions: {}\n\njobs:\n  noResponse:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      issues: write\n    steps:\n      - uses: MBilalShafi/no-response-add-label@8336c12292902f27b931154c34ba4670cb9899a2\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n          # Number of days of inactivity before an Issue is closed for lack of response\n          daysUntilClose: 7\n          # Label requiring a response\n          responseRequiredLabel: 'status: waiting for author'\n          # Label to add back when required label is removed\n          optionalFollowupLabel: 'status: waiting for maintainer'\n          # Comment to post when closing an Issue for lack of response. Set to `false` to disable\n          closeComment: >\n            Since the issue is missing key information and has been inactive for 7 days, it has been automatically closed.\n            If you wish to see the issue reopened, please provide the missing information.\n"
  },
  {
    "path": ".github/workflows/publish.yml",
    "content": "name: Publish packages\n\non:\n  workflow_dispatch:\n    inputs:\n      sha:\n        description: 'Commit SHA to release from'\n        required: true\n        type: string\n      dry-run:\n        description: 'Run in dry-run mode without actually publishing packages'\n        required: false\n        type: boolean\n        default: false\n      github-release:\n        description: 'Create a GitHub release after publishing'\n        required: false\n        type: boolean\n        default: true\n      dist-tag:\n        description: 'npm dist tag to publish to'\n        required: false\n        type: string\n        default: 'latest'\n\npermissions: {}\n\njobs:\n  publish:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write # Required for pushing tags and creating releases\n      id-token: write # Required for provenance\n    environment:\n      name: npm-publish\n    steps:\n      - name: Checkout\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          ref: ${{ inputs.sha }}\n          fetch-depth: 0 # Fetch full history for proper git operations\n      - name: Prepare for publishing\n        uses: mui/mui-public/.github/actions/publish-prepare@f544b55fdbbb922b7293365705cf62895b4f5186\n      - name: Publish packages\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          # Build common flags\n          ARGS=\"\"\n          if [ \"${{ inputs.dry-run }}\" = \"true\" ]; then\n            ARGS=\"$ARGS --dry-run\"\n          fi\n          if [ \"${{ inputs.github-release }}\" = \"true\" ]; then\n            ARGS=\"$ARGS --github-release\"\n          fi\n          if [ -n \"${{ inputs.dist-tag }}\" ]; then\n            ARGS=\"$ARGS --tag ${{ inputs.dist-tag }}\"\n          fi\n\n          pnpm code-infra publish --ci $ARGS\n"
  },
  {
    "path": ".github/workflows/scorecards.yml",
    "content": "name: Scorecards supply-chain security\n\non:\n  # Only the default branch is supported.\n  branch_protection_rule:\n  schedule:\n    - cron: '0 2 * * *'\n\npermissions: {}\n\njobs:\n  analysis:\n    name: Scorecards analysis\n    runs-on: ubuntu-latest\n    permissions:\n      # Needed to upload the results to code-scanning dashboard.\n      security-events: write\n      # Used to receive a badge.\n      id-token: write\n      # Needs for private repositories.\n      contents: read\n      actions: read\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          persist-credentials: false\n      - name: Run analysis\n        uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3\n        with:\n          results_file: results.sarif\n          results_format: sarif\n          # (Optional) Read-only PAT token. Uncomment the `repo_token` line below if:\n          # - you want to enable the Branch-Protection check on a *public* repository, or\n          # - you are installing Scorecards on a *private* repository\n          # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.\n          repo_token: ${{ secrets.SCORECARD_READ_TOKEN }}\n          # Publish the results for public repositories to enable scorecard badges. For more details, see\n          # https://github.com/ossf/scorecard-action#publishing-results.\n          publish_results: true\n      # Upload the results to GitHub's code scanning dashboard.\n      - name: Upload to code-scanning\n        uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6\n        with:\n          sarif_file: results.sarif\n"
  },
  {
    "path": ".github/workflows/support-stackoverflow.yml",
    "content": "# Configuration for support-requests - https://github.com/dessant/support-requests\nname: Support Stack Overflow\n\non:\n  issues:\n    types: [labeled, unlabeled, reopened]\n\npermissions: {}\n\njobs:\n  mark-support:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      issues: write\n    steps:\n      - uses: dessant/support-requests@47d5ea12f6c9e4a081637de9626b7319b415a3bf # v4.0.0\n        with:\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n          # Label used to mark issues as support requests\n          support-label: 'support: Stack Overflow'\n          # Comment to post on issues marked as support requests. Add a link\n          # to a support page, or set to `false` to disable\n          issue-comment: |\n            👋 Thanks for using this project!\n\n            We use GitHub issues exclusively as a bug and feature requests tracker, however, this issue appears to be a support request.\n\n            For support with Base UI please check out https://base-ui.com/react/overview/about#community.\n\n            If you have a question on Stack Overflow, you are welcome to link to it here, it might help others.\n            If your issue is subsequently confirmed as a bug, and the report follows the issue template, it can be reopened.\n          close-issue: true\n          lock-issue: false\n"
  },
  {
    "path": ".github/workflows/vale-action.yml",
    "content": "name: Vale action\n\non: [pull_request]\n\npermissions: {}\n\njobs:\n  vale:\n    name: runner / vale\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      pull-requests: write\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - name: Extract Vale version from pnpm-lock.yaml\n        id: vale-version\n        run: |\n          # Extract version from lock file\n          VERSION=$(awk -F\"@|'\" '/@vvago\\/vale@/ {print $4}' pnpm-lock.yaml | head -n1)\n          echo \"Extracted Vale version: $VERSION\"\n          echo \"vale_version=$VERSION\" >> $GITHUB_OUTPUT\n      - uses: errata-ai/vale-action@d89dee975228ae261d22c15adcd03578634d429c # v2.1.1\n        continue-on-error: true # GitHub Action flag needed until https://github.com/errata-ai/vale-action/issues/89 is fixed\n        with:\n          version: ${{ steps.vale-version.outputs.vale_version }}\n          # Errors should be more visible\n          fail_on_error: true\n          # The other reports don't work, not really https://github.com/reviewdog/reviewdog#reporters\n          reporter: github-pr-check\n          # Required, set by GitHub actions automatically:\n          # https://docs.github.com/en/actions/security-guides/automatic-token-authentication#about-the-github_token-secret\n          token: ${{secrets.GITHUB_TOKEN}}\n"
  },
  {
    "path": ".gitignore",
    "content": "# It is best to ignoring editor and system files in a local .gitignore configuration file.\n# However, in order to prevent issues, they are ignored here.\n.DS_STORE\n.idea\n# IntelliJ IDEA module file\n*.iml\n*.log\n/.eslintcache\n/coverage\n/docs/.env.local\n/docs/.next\n/docs/export\n/docs/src/app/(private)/playground/*\n!/docs/src/app/(private)/playground/\\[slug\\]\n/docs/public/feed/\n/docs/reference/temp/*\n/test/regressions/screenshots\n# created by netlify dev (to perform local debug)\n.netlify\nbuild\nbuild-tests\nnode_modules\npackage-lock.json\nsize-snapshot.json\n# vale downloaded config\n.github/styles/\n.nx/cache\n.nx/workspace-data\n\n# Remove if you want to pregenerate the docs md files\ndocs/public/llms.txt\ndocs/public/llms-full.txt\ndocs/public/react\n.cursor/rules/nx-rules.mdc\n.github/instructions/nx.instructions.md\n\n# typescript\n*.tsbuildinfo\nnext-env.d.ts\n\n# Claude\n.claude/settings.local.json\n\n# Testing\ntest-results/\n"
  },
  {
    "path": ".lintignore",
    "content": ".git\n/coverage\n/docs/next-env.d.ts\n/docs/.next\n/docs/export\n/docs/src/app/(private)/playground/\n/packages/react/**/*.min.*\nbuild\nbuild-tests\nnode_modules\npnpm-lock.yaml\nrouteTree.gen.ts"
  },
  {
    "path": ".markdownlint-cli2.mjs",
    "content": "import { createBaseConfig } from '@mui/internal-code-infra/markdownlint';\n\nconst baseline = createBaseConfig();\n\nexport default {\n  ...baseline,\n  ignores: [...(baseline.ignores ?? []), 'IMPLEMENTATION_SUMMARY.md'],\n  config: {\n    ...baseline.config,\n    MD038: false, // false positives in MDX\n    MD041: false, // false positives in MDX\n  },\n};\n"
  },
  {
    "path": ".npmrc",
    "content": "enable-pre-post-scripts = true\n"
  },
  {
    "path": ".vale.ini",
    "content": "# Vale config. More information at https://vale.sh/docs/topics/config/\nStylesPath = .github/styles\nMinAlertLevel = warning\n\n# To update mui-vale package:\n# 1. Go to the docs folder in the material-ui repo\n# 2. Update/create YAML files\n# 3. Run `pnpm docs:zipRules` to generate the zip files\n# 4. You can test locally by replacing the url with the file path of the generated zip\nPackages = Google, https://github.com/mui/material-ui/raw/HEAD/docs/mui-vale.zip\n\n[formats]\nmdx = md\n\n[*.{md,mdx}]\n# Ignore React component calls\nTokenIgnores = (<\\/?[A-Z].+>)\n\n# Ignore code injections that start with {{...\nBlockIgnores = {{.*\n\nBasedOnStyles = MUI\n\n# Google errors:\nGoogle.GenderBias = YES # No Gender bias\n# Google warings:\nGoogle.FirstPerson = YES # Avoid first-person\nGoogle.We = YES # Avoid first-person plural\nGoogle.Will = YES # Avoid future tense\nGoogle.OxfordComma = YES # Prefer Oxford comma\n\n[*.mdx]\n# MDX doesn't support HTML comments, see https://vale.sh/docs/keys/commentdelimiters.\nCommentDelimiters = {/*, */}\n\n[CHANGELOG*.md]\nMUI.CorrectReferenceAllCases = NO\n\n[IMPLEMENTATION_SUMMARY.md]\nBasedOnStyles =\n"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n  \"recommendations\": [\n    // Formating\n    \"esbenp.prettier-vscode\", // Prettier\n    \"editorconfig.editorconfig\", // EditorConfig\n\n    // Highlighting\n    \"bradlc.vscode-tailwindcss\", // Tailwind CSS\n    \"unifiedjs.vscode-mdx\", // MDX\n    \"styled-components.vscode-styled-components\", // styled()\n\n    // Lint\n    \"dbaeumer.vscode-eslint\", // ESLint\n    \"yoavbls.pretty-ts-errors\", // TypeScript\n    \"stylelint.vscode-stylelint\", // Stylelint\n    \"davidanson.vscode-markdownlint\" // markdownlint\n  ]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"editor.defaultFormatter\": \"esbenp.prettier-vscode\",\n  \"files.trimTrailingWhitespace\": true,\n\n  // Root workspace only\n  \"grammarly.selectors\": [\n    {\n      \"language\": \"markdown\",\n      \"scheme\": \"file\"\n    }\n  ]\n}\n"
  },
  {
    "path": "AGENTS.md",
    "content": "<!-- markdownlint-disable MD038 -->\n\n# Repository Guidelines\n\nThis repository contains the source code and documentation for Base UI: a headless, unstyled React component library.\n\n## Project structure\n\n- Source code for components and private utils is in `packages/react/`.\n- Source code for public shared utils is in `packages/utils/`.\n- Experiments are located at `docs/src/app/(private)/experiments/`. Use for creating demos that require manual testing in the browser.\n- Public documentation is located at `docs/src/app/(docs)/react/`. Alter the docs where necessary when changes must be visible to library users.\n- When creating public demos on the docs, refer to the `hero` demo for the given component and largely follow its styles (both CSS Modules and Tailwind CSS versions). Other demos may also contain relevant styling. Do not add custom styling beyond the critical layout styles necessary for new demos.\n\n## Code guidelines\n\n- Always use the `useTimeout` utility from `@base-ui/utils/useTimeout` instead of `window.setTimeout`, and `useAnimationFrame` from `@base-ui/utils/useAnimationFrame` instead of `requestAnimationFrame`. Search for other example usage in the codebase if unsure how to use them.\n- Use the `useStableCallback` utility from `@base-ui/utils/useStableCallback` instead of `React.useCallback` if the function is called within an effect or event handler. The utility cannot be used to memoize functions that are called directly in the body of a component (during render), so continue with `React.useCallback` in those scenarios.\n- Always use the `useIsoLayoutEffect` utility from `@base-ui/utils/useIsoLayoutEffect` instead of `React.useLayoutEffect`.\n- Avoid duplicating logic where necessary. If two components can share logic (such as event handlers), define the logic/handlers in the parent and share it through a context to the child; use the existing context if it exists.\n\n## Linting, typechecking, and formatting\n\n- Do not randomly cast (for example `as any`) if there are no type errors without doing so. Run `pnpm typescript` to verify types.\n- Ensure your changes pass linting - run `pnpm eslint`.\n- Ensure your styles pass stylelint - run `pnpm stylelint`.\n- Ensure your markdown passes markdownlint - run `pnpm markdownlint`.\n- Ensure your changes are formatted correctly - run `pnpm prettier`.\n- When you change a public component API (props or JSDoc), run `pnpm docs:api`.\n\n## Testing\n\n- If a repository command fails because dependencies are unavailable, run `pnpm i` first and then retry the command.\n- Run tests in JSDOM env with `pnpm test:jsdom {name} --no-watch` such as `pnpm test:jsdom NumberField --no-watch` or `pnpm test:jsdom parse --no-watch`.\n- Run tests in Chromium env with `pnpm test:chromium {name} --no-watch` such as `pnpm test:chromium NumberField --no-watch` or `pnpm test:jsdom parse --no-watch`.\n- Do not call `await flushMicrotasks()` directly after `await render(...)` when there are no interactions or state changes between them; `render` is already awaited, so that immediate flush is unnecessary.\n- If you made changes to the source code, ensure you verify your changes by running tests (see above), and writing new tests where applicable. If tests require the browser because, for example, they require layout measurements, restrict it to the Chromium env by using `it.skipIf(isJSDOM)` or `describe.skipIf(isJSDOM)` (search other tests for example usage if unsure).\n- Follow the established conventions in existing tests. Each file/component is tested with the filename `name.test.tsx`. For example, `PopoverRoot.test.tsx` is next to its source file `PopoverRoot.tsx`.\n- Tests use Vitest APIs only: `expect()`, `vi.fn()`, and `@testing-library/jest-dom` DOM matchers. Do not use Chai- or Sinon-style matcher chains or spies.\n\n## Commit guidelines\n\n- Commit messages follow the format `[scope] Imperative summary` (for example `[popover] Fix focus trap`). Choose scopes that mirror package or component names that were changed.\n- Use `[all components]` scope for changes that broadly affect most components.\n\n## Errors\n\nThese guidelines apply only to errors thrown by public packages.\n\nEvery error message must:\n\n1. **Say what happened** - Describe the problem clearly\n2. **Say why it's a problem** - Explain the consequence\n3. **Point toward how to solve it** - Give actionable guidance\n\nFormat:\n\n- Prefix with `Base UI: `\n- Use string concatenation for readability\n- Include a documentation link when applicable (`https://base-ui.com/...`)\n\n### Error Minifier\n\nYou MUST run `pnpm extract-error-codes` to update `docs/src/error-codes.json` every time you add or update an error message in an `Error` constructor.\n\n**Important:** If the update created a new error code, but the new and original message have the same number of arguments and semantics haven't changed, update the original error in `error-codes.json` instead of creating a new code.\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Versions\n\n## v1.3.0\n\n_Mar 12, 2026_\n\n### General Changes\n\n- Warn when a component function is rendered directly (#4077) by @atomiks\n- Reset `openMethod` after close transition (#4128) by @atomiks\n- Fire <kbd>Space</kbd> activation on `keydown` in composite widgets (#4053) by @atomiks\n- Skip CSS-hidden items during keyboard navigation in composite widgets (#4195) by @atomiks\n- Optimize hot paths in `useHover` hooks and `safePolygon` (#4199) by @atomiks\n- Snap `--anchor-width` and `--anchor-height` to device pixel grid (#4082) by @flaviendelangle\n- Fix outside press dismissal when a component is portaled into a shadow DOM (#4230) by @dmitri-gb\n- Fix nested hoverable popups (#4206) by @dmitri-gb\n- Apply `data-base-ui-inert` to highest-level node (#3955) by @atomiks\n- Fix portable types (#4058) by @cgatian\n\n### Accordion\n\n- Add generic `Value` typing (#4138) by @atomiks\n\n### Autocomplete\n\n- Respect a `null` `filter` prop (#4117) by @atomiks\n- Add `InputGroup` part (#3745) by @atomiks\n\n### Avatar\n\n- Remove fallback transition logic and prevent premature image display (#4110) by @atomiks\n\n### Button\n\n- Avoid checking `disabled` twice in `onKeyDown` and `onKeyUp` (#4132) by @flaviendelangle\n\n### Checkbox\n\n- Add automatic `aria-labelledby` support (#4142) by @atomiks\n\n### Combobox\n\n- Avoid applying field attributes to input when it is inside popup (#4154) by @atomiks\n- Preserve inline input on `Enter` when nothing is highlighted (#4235) by @atomiks\n- Fix ARIA attributes during SSR (#4179) by @atomiks\n- Fix wrapping in virtualized grid arrow-key navigation (#4164) by @atomiks\n- Add `InputGroup` part (#3745) by @atomiks\n- Add support for a visually hidden close button and improve modal focus trapping (#4084) by @atomiks\n- Add `Label` part (#4167) by @atomiks\n\n### Context Menu\n\n- Ignore `mouseup` on non-Mac platforms (#3944) by @atomiks\n\n### Drawer\n\n- **Breaking change:** `Drawer` is no longer marked as preview<br />\n  `Drawer` is now stable and should be imported as `{ Drawer } from '@base-ui/react/drawer'` (#4293) by @atomiks\n- Fix React 17 support (#4178) by @atomiks\n- Include border in frontmost height variable (#4202) by @atomiks\n- Improve touch selection (#4104) by @atomiks\n- Preserve cross-axis scrolling during touch gestures (#4187) by @atomiks\n- Prevent swipe dismissal when component is controlled (#4133) by @flaviendelangle\n- Add `SwipeArea` part (#4102) by @atomiks\n- Make `data-base-ui-swipe-ignore` explicit for touch interactions (#4295) by @atomiks\n- Disable inheritance for swipe CSS variables (#4099) by @atomiks\n\n### Field\n\n- Fix field validation when `Form` errors or `invalid` prop are present at same time (#4112) by @mj12albert\n\n### Menu\n\n- Prevent `pointerleave` from stealing focus from dialogs (#4125) by @atomiks\n- Optimize `pointer-events` for submenu hover interactions (#4231) by @atomiks\n- Fix `closeDelay` not being applied to `Menu.SubmenuTrigger` (#4134) by @flaviendelangle\n- Implement content transitions with `Viewport` (#4060) by @michaldudak\n\n### Meter\n\n- Fix label announcements in NVDA (#4200) by @mj12albert\n\n### Navigation Menu\n\n- Fix support for nested inline menus (#4198) by @atomiks\n- Fix close propagation in nested hover menus (#4285) by @atomiks\n- Close parent menus when nested link with `closeOnClick` is clicked (#4276) by @CiscoFran10\n- Fix duplicate `aria-orientation` (#4309) by @atomiks\n- Fix delayed trigger switches in Safari (#4310) by @atomiks\n\n### Number Field\n\n- Fix increment/decrement press `reason` values in `onValueCommitted` (#4259) by @jijiseong\n\n### Popover\n\n- Trap focus when `<Popover.Close>` is rendered inside and `modal` is `true`, and add support for a visually hidden close button (#4084) by @atomiks\n- Fix nested hoverable popups (#3798) by @flaviendelangle\n\n### Preview Card\n\n- Fix nested hoverable popups (#3798) by @flaviendelangle\n\n### Progress\n\n- Fix label announcements in NVDA (#4200) by @mj12albert\n\n### Radio Group\n\n- Add automatic `aria-labelledby` support (#4142) by @atomiks\n\n### Scroll Area\n\n- Fix focus trapping with a non-scrollable viewport (#4220) by @atomiks\n- Fix thumb size after remounting (#4107) by @atomiks\n\n### Select\n\n- Fix hidden input `id` fallback (#4135) by @atomiks\n- Fix `Value` placeholder not rendering with `Record` items (#4137) by @vcode-sh\n- Fire `onClick` during drag-to-select (#3969) by @obeattie\n- Fix `items` type definition for groups (#3884) by @aarongarciah\n- Fix `alignItemWithTrigger` fallback with browser zoom (#4292) by @atomiks\n- Add `Label` part (#4167) by @atomiks\n\n### Slider\n\n- Add `Label` part (#4167) by @atomiks\n- Stop committing validation on `touchend` (#4091) by @jijiseong\n\n### Switch\n\n- Add automatic `aria-labelledby` support (#4142) by @atomiks\n\n### Tabs\n\n- Re-render indicator position on tab resize (#4165) by @atomiks\n\n### Toast\n\n- Enable closing all toasts (#3979) by @chuganzy\n- Prevent duplicate `onClose` calls for ending toasts (#4280) by @chuganzy\n\n### Tooltip\n\n- Add `closeOnClick` prop (#4140) by @atomiks\n\nAll contributors of this release in alphabetical order : @aarongarciah, @atomiks, @cgatian, @chuganzy, @CiscoFran10, @dmitri-gb, @flaviendelangle, @jijiseong, @michaldudak, @mj12albert, @obeattie, @vcode-sh\n\n## v1.2.0\n\n_Feb 12, 2026_\n\n### General changes\n\n- Do not memoize `state` when not needed (#3812) by @flaviendelangle\n- Support lazy element in `render` prop (#3856) by @oliviertassinari\n- Replace Firefox deprecated mozInputSource check for virtual click detection (#3942) by @CiscoFran10\n- Use `WeakRef` for previously focused elements (#3916) by @atomiks\n- Fix page scroll jump when input has focus on unmount in Safari (#3925) by @atomiks\n- Fix flash at origin before positioning completes in Preact (#3975) by @OliverSpeir\n- Reduce style recalculation with classic scrollbars (#3854) by @mdm317\n- Fix event handling in useEnhancedClickHandler (#3981) by @sai6855\n\n### Autocomplete\n\n- Fix filter method's `useMemo` dependency (#3862) by @ZeeshanTamboli\n- Fix Autocomplete not using its internal filter method when `mode` is `list` (#3936) by @ZeeshanTamboli\n- Remove unnecessary double stringification of item in filtering logic (#3945) by @ZeeshanTamboli\n- Add `useFilteredItems` hook (#3732) by @guisehn\n- Fix popup closing on iOS VoiceOver (#3859) by @atomiks\n- Remove `aria-readonly` prop from `Clear` and `Popup` components when `readOnly` (#3907) by @markocupic024\n\n### Avatar\n\n- Add transition attributes (#3939) by @atomiks\n\n### Button\n\n- Capture component stack for `nativeButton` error message (#3861) by @atomiks\n\n### Checkbox\n\n- Cleanup disabled state tracking (#3913) by @atomiks\n- Preserve modifier key properties in the change event (#3935) by @mj12albert\n- Allow exit animations on `<Checkbox.Indicator>` when `keepMounted={false}` (#3939) by @atomiks\n\n### Combobox\n\n- Fix the type of the ref of the `Icon` part (#3796) by @flaviendelangle\n- Avoid clearing selected value if item is not present in items array (#3824) by @atomiks\n- Fix highlight change reason in `ChipRemove` (#3980) by @sai6855\n- Keep highlight on last deselect (#3923) by @atomiks\n- Fix inline filtering after selection in single mode (#3978) by @atomiks\n- Clear highlight on inline blur when inline (#3973) by @atomiks\n- Prevent opening popup on autofill change (#3924) by @atomiks\n- Distinguish `input-press` from `trigger-press` in `onOpenChange` reason (#4015) by @jijiseong\n- Fix async items while popup is open (#4034) by @atomiks\n- Prevent `Chip` from receiving focus when `disabled` (#4044) by @jijiseong\n- Add `useFilteredItems` hook (#3732) by @guisehn\n- Fix popup closing on iOS VoiceOver (#3859) by @atomiks\n- Remove `aria-readonly` prop from `Clear` and `Popup` components when `readOnly` (#3907) by @markocupic024\n- Fix `onClick` `Item` type (#3964) by @atomiks\n- Use reactive `domReferenceElement` subscriptions (#4017) by @atomiks\n- Add `autoComplete` prop for explicit browser autofill support (#4005) by @mattrothenberg\n- Fix inconsistent isItemEqualToValue argument order (#4056) by @atomiks\n\n### Context Menu\n\n- Fix `disabled` prop not working (#3806) by @arturbien\n- Fix explicit `collisionAvoidance` with `side: 'flip'` not working (#3877) by @obeattie\n\n### Drawer\n\n- Create new Drawer / Sheet component (#3680) by @atomiks\n\n### Field\n\n- Prevent re-renders when `Field.Control` is uncontrolled (#3820) by @atomiks\n- Fix autofocus in SSR environments (#3871) by @mj12albert\n- Fix max update depth loop when using `<React.Activity>` (#3931) by @atomiks\n- Add transition attributes (#3939) by @atomiks\n\n### Input\n\n- Fix autofocus in SSR environments (#3871) by @mj12albert\n- Update ref type to `HTMLElement` (#3866) by @mj12albert\n\n### Menu\n\n- Fix `onClick` `Item` type (#3964) by @atomiks\n- Fix submenu stuck glitch (#3783) by @atomiks\n- Fix race conditions (#3821) by @atomiks\n- Add `<Menu.LinkItem>` part (#3400) by @mj12albert\n\n### Navigation Menu\n\n- Fix forwarded ref types (#3775) by @CrawlerCode\n- Add `keepMounted` prop to `Content` part (#3794) by @atomiks\n\n### Number Field\n\n- Fix click handlers on ScrubArea (#3827) by @mj12albert\n- Remove `event.isTrusted` (#3920) by @atomiks\n- Stop repeat change at bounds (#3915) by @atomiks\n- Add `allowOutOfRange` prop (#3919) by @atomiks\n- Fix pen pointer handling (#3917) by @atomiks\n- Fix missing field state data attributes (#3909) by @mj12albert\n\n### Popover\n\n- Fix missing `aria-owns` element (#3959) by @atomiks\n- Use reactive `domReferenceElement` subscriptions (#4017) by @atomiks\n- Fix broken scale transition with detached triggers (#3810) by @michaldudak\n\n### Preview Card\n\n- Fix broken scale transition with detached triggers (#3810) by @michaldudak\n\n### Progress\n\n- De-duplicate `formatValue` function (#3805) by @sai6855\n\n### Radio Group\n\n- Preserve modifier key properties in the change event (#3935) by @mj12albert\n- Allow exit animations on `<Radio.Indicator>` when `keepMounted={false}` (#3939) by @atomiks\n- Rely on individual radio hidden inputs (#3826) by @atomiks\n- Add generic `Value` typing to `Radio` (#4033) by @atomiks\n\n### Scroll Area\n\n- Add `data-scrolling` state attribute to `Root` and `Viewport` parts (#3823) by @arturbien\n- Fix overflow edge rounding (#3888) by @atomiks\n\n### Select\n\n- Add `finalFocus` prop (#3785) by @markocupic024\n- Fix `alignItemWithTrigger` transform with CSS animations (#3831) by @atomiks\n- Fix `highlightItemOnHover` not being respected (#3868) by @sarthakmalik0810\n- Reset typeahead on external blur (#2618) by @antonfrolovsky\n- Fix scroll height loop (#3795) by @atomiks\n- Add `autoComplete` prop for explicit browser autofill support (#4005) by @mattrothenberg\n- Fix inconsistent isItemEqualToValue argument order (#4056) by @atomiks\n\n### Slider\n\n- Fix missing field state data attributes (#3909) by @mj12albert\n- Fix change event cloning (#3960) by @atomiks\n\n### Switch\n\n- Preserve modifier key properties in the change event (#3935) by @mj12albert\n\n### Tabs\n\n- Add transition attributes to `<Tabs.Panel>` part (#3880) by @atomiks\n\n### Toast\n\n- Make `useToastManager` and `createToastManager` generic functions (#3882) by @solastley\n- Prevent dismissed promise toast from reopening on updates (#4040) by @atomiks\n- Introduce a store (#3464) by @flaviendelangle\n\n### Toggle\n\n- Improve type safety and inference (#3173) by @michaelhazan\n\n### Toggle Group\n\n- Type value as string to match Toggle (#3770) by @markocupic024\n- Enable `Home`/`End` key navigation (#3971) by @jijiseong\n- Improve type safety and inference (#3173) by @michaelhazan\n\n### Tooltip\n\n- Prevent opening when focusing a disabled Trigger (#3902) by @michaldudak\n- Fix broken scale transition with detached triggers (#3810) by @michaldudak\n- Fix disabled prop on Triggers (#4049) by @michaldudak\n\nAll contributors of this release in alphabetical order : @antonfrolovsky, @arturbien, @atomiks, @CiscoFran10, @CrawlerCode, @flaviendelangle, @guisehn, @jijiseong, @LukasTy, @markocupic024, @mattrothenberg, @mdm317, @michaelhazan, @michaldudak, @mj12albert, @obeattie, @OliverSpeir, @oliviertassinari, @sai6855, @sarthakmalik0810, @solastley, @ZeeshanTamboli\n\n## v1.1.0\n\n_Jan 15, 2026_\n\n### General changes\n\n- Fix `onOpenChangeComplete(true)` timing (#3558) by @atomiks\n- Fix touch `openMethod` when tapping outside element bounds on Safari (#3541) by @atomiks\n- Fix visually hidden input styles across form components (#3606) by @atomiks\n- Fix click and drags outside a nested popup component from closing its parents (#3571) by @atomiks\n- Fix forwarded ref types (#3638) by @atomiks\n- Fix detached trigger remounting (#3724) by @atomiks\n- Include `ref` in `BaseUIComponentProps` (#2813) by @atomiks\n- Remove duplicated `disabled` prop (#3650) by @seongminn\n- Allow `actionsRef` to be `null` (#3682) by @mj12albert\n\n### Accordion\n\n- Fix keyboard navigation with non-interactive trigger elements (#3684) by @ZeeshanTamboli\n\n### Autocomplete\n\n- Add `data-popup-side` and `data-list-empty` state attributes to `<Autocomplete.Trigger>` (#3491) by @atomiks\n- Add `loopFocus` prop (#3592) by @atomiks\n- Fix hidden input `id` and `required` props (#3640) by @atomiks\n\n### Button\n\n- Remove discriminated props union (#3643) by @atomiks\n\n### Checkbox\n\n- Fix hidden input `id` and `required` props (#3640) by @atomiks\n\n### Combobox\n\n- Add `data-popup-side` and `data-list-empty` state attributes to `<Combobox.Trigger>` (#3491) by @atomiks\n- Add `loopFocus` prop (#3592) by @atomiks\n- Add `toolbar` role to `<Combobox.Chips>` to prevent NVDA from entering browse mode (#3647) by @atomiks\n- Add `placeholder` prop to `<Combobox.Value>` (#3604) by @atomiks\n- Fix controlled `value` prop when `items` change (#3607) by @atomiks\n- Fix `multiple` values label resolution in `<Combobox.Value>` (#3314) by @atomiks\n- Forward root `id` to visible form element (#3722) by @atomiks\n- Do not trigger Field `onBlur` handlers when opening popup (#3609) by @atomiks\n\n### Context Menu\n\n- Avoid creating sibling elements next to trigger (#3645) by @atomiks\n\n### CSP Provider\n\n- Add `CSPProvider` (#3553) by @atomiks\n\n### Dialog\n\n- Fix `Maximum update depth exceeded` error with Suspense (#3700) by @michaldudak\n- Fix `<Dialog.Title>` forwardedRef type (#3736) by @ZeeshanTamboli\n\n### Field\n\n- Add `actionsRef` prop (#3395) by @mj12albert\n- Add `nativeLabel` prop to `<Field.Label>` (#3723) by @atomiks\n- Add missing type export (#3702) by @DiegoAndai\n\n### Form\n\n- Add `actionsRef` prop (#3395) by @mj12albert\n\n### Menu\n\n- Fix focus guard handling (#3654) by @atomiks\n- Avoid disabling modality on click after hover-open (#3455) by @atomiks\n\n### Menubar\n\n- Fix submenu outside-press dismiss on touch (#3556) by @atomiks\n\n### Number Field\n\n- Fix Field `data-focused` state (#3563) by @atomiks\n- Fix hidden input focus on submit (#3581) by @atomiks\n\n### Popover\n\n- Fix popup auto resize glitches (#3591) by @atomiks\n- Fix focus guard handling (#3654) by @atomiks\n- Prevent disabling focus management when clicking trigger before hover delay completes (#3572) by @atomiks\n- Refactor popup auto resize logic. It is no longer necessary to specify `--positioner-width`/`--positioner-height` CSS variables on `<Popover.Positioner>` when using detached triggers unless the `Viewport` part has been added to the JSX. (#3652) by @atomiks\n\n### Preview Card\n\n- Support detached triggers (#3566) by @michaldudak and @atomiks\n\n### Radio Group\n\n- Fix `value` type (#3582) by @atomiks\n- Fix hidden input `id` and `required` props (#3640) by @atomiks\n\n### Scroll Area\n\n- Perf improvements (#3536) by @atomiks\n\n### Select\n\n- Add `placeholder` prop to `<Select.Value>` (#3604) by @atomiks\n- Fix support for transform animations when `alignItemWithTrigger` is active (#3532) by @atomiks\n- Fix support for `max-height` popup style when `alignItemWithTrigger` is active (#3573) by @atomiks\n- Fix `data-filled` state in `multiple` mode (#3608) by @atomiks\n- Fix highlight being removed on popup mouseout when `highlightItemOnHover` is disabled (#3492) by @atomiks\n- Fix support for individual transform animations when `alignItemWithTrigger` is active (#3637) by @atomiks\n- Fix `multiple` values label resolution in `<Select.Value>` (#3314) by @atomiks\n- Forward root `id` to visible form element (#3722) by @atomiks\n- Do not trigger Field `onBlur` handlers when opening popup (#3609) by @atomiks\n\n### Slider\n\n- Fix `onValueCommitted` not called for range sliders (#3600) by @mj12albert\n\n### Switch\n\n- Add `value` prop (#3676) by @Grafikart\n- Fix hidden input `id` and `required` props (#3640) by @atomiks\n\n### Toast\n\n- Fix timers not being rescheduled when updated (#3564) by @atomiks\n\n### Tooltip\n\n- Fix popup auto resize glitches (#3591) by @atomiks\n- Fix `trackCursorAxis` handling (#3679) by @atomiks\n- Refactor popup auto resize logic. It is no longer necessary to specify `--positioner-width`/`--positioner-height` CSS variables on `<Tooltip.Positioner>` when using detached triggers unless the `Viewport` part has been added to the JSX. (#3652) by @atomiks\n\n### mergeProps\n\n- Make `mergeProps` public (#3642) by @michaldudak and @LukasTy\n\n### useRender\n\n- Export missing types (#3565) by @michaldudak\n\nAll contributors of this release in alphabetical order: @albertdugba, @atomiks, @brijeshb42, @chuganzy, @colmtuite, @dav-is, @DiegoAndai, @Grafikart, @Janpot, @LukasTy, @michaldudak, @mj12albert, @oliviertassinari, @seongminn, @updbqn, @ZeeshanTamboli\n\n## v1.0.0\n\n_Dec 11, 2025_\n\n### General changes\n\n- **Breaking change:** Rename packages to use the `@base-ui` org.<br />\n  The package name has changed from `@base-ui-components/react` to `@base-ui/react`.\n  (#3462) by @mnajdova\n\n### Combobox\n\n- Respect `itemToStringValue` for `onFormSubmit` (#3441) by @atomiks\n- Add `null` as an option for the value prop (#3488) by @mnajdova\n\n### Menu\n\n- Fix submenu opens with 0 delay (#3459) by @atomiks\n- Fix focus not returning to trigger on <kbd>Esc</kbd> while pointer rests on popup (#3482) by @atomiks\n- Fix always `null` open method (#3486) by @atomiks\n- Allow side axis fallback for submenus by default (#3470) by @atomiks\n\n### Navigation Menu\n\n- Fix mount transitions on `Positioner` in Firefox (#3424) by @atomiks\n\n### Number Field\n\n- Fix multiple scrub area support (#3471) by @atomiks\n\n### Popover\n\n- Fix mount transitions on `Positioner` in Firefox (#3424) by @atomiks\n- Fix skipped viewport transitions (#3453) by @atomiks\n\n### Select\n\n- Respect `itemToStringValue` for `onFormSubmit` (#3441) by @atomiks\n- Add `null` as an option for the value prop (#3488) by @mnajdova\n\n### Tabs\n\n- Fix indicator positioning in transformed containers (#3439) by @atomiks\n- Do not initially select a disabled tab (#3475) by @michaldudak\n\n### Toast\n\n- Fix `flushSync` dev error when toast is added (#3443) by @atomiks\n- Fix `<Toast.Close>;` emitting `aria-hidden` warning on click (#3469) by @atomiks\n\n### Toggle Group\n\n- More permissive towards falsy toggle values (#3477) by @mj12albert\n\n### Tooltip\n\n- Fix mount transitions on `Positioner` in Firefox (#3424) by @atomiks\n- Fix ignored \"modal\" setting in Popovers experiment (#3474) by @michaldudak\n- Fix shared tooltip closing with trigger gaps (#3452) by @atomiks\n- Fix skipped viewport transitions (#3453) by @atomiks\n\nAll contributors of this release in alphabetical order: @atomiks, @LukasTy, @michaldudak, @mj12albert, @mnajdova, @oliviertassinari, @pondorasti, @romgrk, @ZeeshanTamboli\n\n## v1.0.0-rc.2\n\n_Dec 11, 2025_\n\nThis release contains the same code as v1.0.0.\nPlease refer to that version to see the changes.\n\n## v1.0.0-rc.1\n\n_Dec 11, 2025_\n\nThis release contains the same code as v1.0.0.\nPlease refer to that version to see the changes.\n\n## v1.0.0-rc.0\n\n_Dec 4, 2025_\n\n### General changes\n\n- Fix missing `'use client'` directives (#3408) by @atomiks\n\n### Autocomplete\n\n- Fix `keepHighlight` focus sync (#3399) by @atomiks\n\n### Checkbox\n\n- **Breaking change:** Match native unchecked state in form submission.<br />\n  The Checkbox will not submit the `\"off\"` value with a form when unchecked anymore, unless the new `uncheckedValue` prop is set.\n  (#3406) by @atomiks\n\n### Collapsible\n\n- Remove `render={null}` (#3407) by @mj12albert\n\n### Combobox\n\n- **Breaking change:** Removed the `keepHighlight` prop (#3377) by @atomiks\n\n### Dialog\n\n- Close when pressing focusable element outside (#3380) by @atomiks\n- Fix closing after pointer lock exit in Firefox (#3379) by @atomiks\n\n### Menu\n\n- Add `highlightItemOnHover` prop (#3377) by @atomiks\n- Do not import client components from MenuStore (#3409) by @michaldudak\n\n### Number Field\n\n- Ensure hidden input participates in form validation (#3374) by @atomiks\n- Improve symbol replacement logic (#3376) by @atomiks\n- Fix fractional step snapping (#3375) by @atomiks\n- Fix parsing numbers with Swiss locale (#3361) by @michaldudak\n- Fix pointer lock release when soft clicking in Firefox (#3378) by @atomiks\n\n### Popover\n\n- Close when pressing focusable element outside (#3380) by @atomiks\n- Fix modal backdrop on touch (#3383) by @atomiks\n- Fix popover glitching when flipped (#3364) by @michaldudak\n\n### Select\n\n- Add `highlightItemOnHover` prop (#3377) by @atomiks\n\n### Switch\n\n- **Breaking change:** Match native off state in form submission.<br />\n  The Switch will not submit the `\"off\"` value with a form when unchecked anymore, unless the new `uncheckedValue` prop is set.\n  (#3406) by @atomiks\n\n### Tabs\n\n- **Breaking change:** Fix Panel `keepMounted` behavior.<br />\n  The `value` prop is now required on `<Tabs.Tab>` and `<Tabs.Panel>` parts.\n  (#3372) by @atomiks\n\n### Toast\n\n- Recalculate content height when layout size is fixed (#3359) by @atomiks\n- Fix multiple swipe directions on same axis (#3392) by @mj12albert\n\n### Tooltip\n\n- Improve contained triggers performance (#3385) by @michaldudak\n\nAll contributors of this release in alphabetical order: @atomiks, @michaldudak, @mj12albert, @oliviertassinari, @pondorasti, @romgrk\n\n## v1.0.0-beta.7\n\n_Nov 27, 2025_\n\n### General changes\n\n- Fix error about `props.ref` access in React &lt;=18 (#3257) by @atomiks\n- Prefer non-adaptive anchoring position in `<Positioner>` components and fix `autoFocus` scroll jumps (#3250) by @atomiks\n- Make popups' `data-anchor-hidden` state attribute check for anchor presence in layout (#3267) by @atomiks\n- Prevent popups from sticking after hover when pressing `&lt;a&gt;` tags inside them (#3318) by @atomiks\n- Improve performance when detached triggers are used (#3277)\n- Fix iOS VoiceOver voice control accessibility in non-modal popups (#3340)\n\n### Alert Dialog\n\n- Fix trigger registration loop (#3249) by @atomiks\n- Fix focus restoration when focused element is hidden with CSS (#3313)\n\n### Checkbox Group\n\n- Fix `aria-describedby` on checkbox group (#3269) by @mj12albert\n\n### Combobox\n\n- Revert overload types to ensure typed wrappers work correctly (#3254) by @atomiks\n- Fix ignored `filteredItems` instances (#3272) by @atomiks\n- Fix loop when passing `undefined` to `items` prop (#3348)\n\n### Context Menu\n\n- Block mouseup at initial cursor point (#3274) by @atomiks\n\n### Dialog\n\n- Fix trigger registration loop (#3249) by @atomiks\n- Fix focus restoration when focused element is hidden with CSS (#3313)\n\n### Form\n\n- Fix cast `ref` type (#3324) by @mj12albert\n\n### Menu\n\n- Fix trigger registration loop (#3249) by @atomiks\n- Do not pass `key` to the rendered element (#3255) by @michaldudak\n- Fix nested dialog from closing on <kbd>Shift+Tab</kbd> (#3346)\n\n### Navigation Menu\n\n- Fix Safari 18 issue where `<Positioner>` width may be set to 0 on hover (#3309) by @EmilNordling\n- Ensure submenu triggers participate in composite list (#3344) by @atomiks\n\n### Number Field\n\n- Fix literal space handling with symbols (#3334) by @atomiks\n\n### Popover\n\n- Fix trigger registration loop (#3249) by @atomiks\n- Do not pass `key` to the rendered element (#3255) by @michaldudak\n- Fix focus restoration when focused element is hidden with CSS (#3313)\n\n### Select\n\n- Revert overload types to ensure typed wrappers work correctly (#3254) by @atomiks\n\n### Slider\n\n- Fix extra `onValueCommitted` calls (#3312) by @mj12albert\n- Fix cast `ref` type (#3324) by @mj12albert\n\n### Tooltip\n\n- Fix trigger registration loop (#3249) by @atomiks\n\nAll contributors of this release in alphabetical order: @atomiks, @brijeshb42, @Copilot, @EmilNordling, @michaldudak, @mj12albert, @oliviertassinari, @ZeeshanTamboli\n\n## v1.0.0-beta.6\n\n_Nov 17, 2025_\n\nThis is a hotfix release with the following changes:\n\n- Fix for rendering of Alert Dialog, Dialog, Menu, Popover, and Tooltip in React Server Components (#3241) by @michaldudak\n- Fix of the types of the refs in the Checkbox, Switch and Radio components (#3246) by @mnajdova\n- Fix of the value type error with mergeProps (#3247) by @atomiks\n\n## v1.0.0-beta.5\n\n_Nov 17, 2025_\n\n### General changes\n\n- **Breaking change:** Replace `trackAnchor` with `disableAnchorTracking`.<br />\n  If you were using `trackAnchor={false}`, be sure to update your usage to `disableAnchorTracking` instead.\n  (#3188) by @mnajdova\n- **Breaking change:** Rename `loop` to `loopFocus` (#3186) by @mnajdova\n- Fix type portability (#2912) by @atomiks\n- Accept a function for the `style` prop (#3038) by @mnajdova\n- Create portal elements inside React (#2889) by @atomiks\n- Avoid applying `hidden` attribute to indicator elements when they specify `keepMounted` and are invisible (#3228) by @atomiks\n- Fix crash in Next.js 16 when accessing `render.props.ref` (#3231) by @atomiks\n\n### Accordion\n\n- **Breaking change:** Change `multiple` prop to be false by default and add a demo (#3141) by @mnajdova\n- Fix flaky exit transition (#3101) by @atomiks\n\n### Alert Dialog\n\n- Fix `initialFocus` as function being called on close (#2949) by @atomiks\n- Support detached triggers (#2974) by @michaldudak\n- Place `overflow: hidden` on `<body>` for overlay scrollbars by default. Avoids sticky elements shifting if `<body>` has an `overflow` style specified. (#3083) by @atomiks\n- Add `<AlertDialog.Viewport>` part (#2808) by @atomiks\n\n### Autocomplete\n\n- **Breaking change:** Refactor `alwaysSubmitOnEnter` to `submitOnItemClick` prop.<br />\n  If you were using `alwaysSubmitOnEnter`, be sure to update your usage to `submitOnItemClick` instead.\n  (#3018) by @atomiks\n- Prevent blocking filtering while composing text on Android (#2944) by @atomiks\n- Add empty state to `List.State` (#2934) by @atomiks\n- Fix `initialFocus` as function being called on close (#2949) by @atomiks\n- Add `role=\"combobox\"` to `<Autocomplete.Trigger>` if `<Autocomplete.Input>` is inside Popup (#2973) by @atomiks\n- Fix stale `onItemHighlighted` data when filtering with `autoHighlight` (#2829) by @atomiks\n- Add empty and side styling attributes on `<Autocomplete.Input>` (#2926) by @atomiks\n- Fix `<Autocomplete.Value>` component return type for React 17 (#3050) by @atomiks\n- Support `autoHighlight: \"always\"`, and add `keepHighlight`, `highlightItemOnHover` props (#2976) by @atomiks\n- Keep focus on input when pressing list element (#3092) by @atomiks\n- Allow <kbd>Esc</kbd> to bubble if `<Autocomplete.Empty>` is not used (#2935) by @atomiks\n- Add `dialog` role to popup when input is inside (#3213) by @atomiks\n\n### Button\n\n- New `<Button>` component (#2363) by @atomiks\n\n### Checkbox\n\n- **Breaking change:** Render root as `<span>` instead of `<button>`\n  (#3205) by @mj12albert\n\n### Collapsible\n\n- Fix `starting-style` state (#2985) by @atomiks\n\n### Combobox\n\n- Take into account `isItemEqualToValue` when selecting an option in multiple mode (#2893) by @epr3\n- Move `CompositeList` to `List` component to make `Input` work with composites (#2883) by @chuganzy\n- Fix `onValueChange` type inference when `value` is unspecified (#2897) by @atomiks\n- Fix `required` form submission with multiple values (#2925) by @atomiks\n- Fix <kbd>Home</kbd>/<kbd>End</kbd> Input scroll in Chrome/Safari (#2928) by @atomiks\n- Prevent blocking filtering while composing text on Android (#2944) by @atomiks\n- Add empty state to `List.State` (#2934) by @atomiks\n- Fix `initialFocus` as function being called on close (#2949) by @atomiks\n- Add `role=\"combobox\"` to `<Combobox.Trigger>` if `<Combobox.Input>` is inside Popup (#2973) by @atomiks\n- Fix `Field` control ref when input is inside popup (#2971) by @atomiks\n- Fix stale `onItemHighlighted` data when filtering with `autoHighlight` (#2829) by @atomiks\n- Add empty and side styling attributes on `<Combobox.Input>` (#2926) by @atomiks\n- Fix `<Combobox.Value>` component return type for React 17 (#3050) by @atomiks\n- Fix input value derivation on `value` and `items` prop updates (#3067) by @atomiks\n- Support `autoHighlight: \"always\"`, and add `keepHighlight`, `highlightItemOnHover` props (#2976) by @atomiks\n- Keep focus on input when pressing list element (#3092) by @atomiks\n- Fix support of dialog + combobox pattern (#3049) by @atomiks\n- Support drag-to-select (#3167) by @atomiks\n- Allow <kbd>Esc</kbd> to bubble if `<Combobox.Empty>` is not used (#2935) by @atomiks\n- Fix stuck filtering with differing stringifiers (#3201) by @atomiks\n- Add `dialog` role to popup when input is inside (#3213) by @atomiks\n\n### Context Menu\n\n- Add `open` state to `<ContextMenu.Trigger>` (#3195) by @atomiks\n- Fix ignored `anchor` prop on `<ContextMenu.Positioner>` (#3202) by @atomiks\n\n### Dialog\n\n- **Breaking change:** Replace `dismissible` with `disablePointerDismissal`.<br />\n  If you were using `dismissible={false}`, replace it with `disablePointerDismissal`.\n  (#3190) by @mnajdova\n- Fix `initialFocus` as function being called on close (#2949) by @atomiks\n- Support detached triggers (#2974) by @michaldudak\n- Place `overflow: hidden` on `<body>` for overlay scrollbars by default. Avoids sticky elements shifting if `<body>` has an `overflow` style specified. (#3083) by @atomiks\n- Add `<Dialog.Viewport>` part and scrollable demos on docs (#2808) by @atomiks\n\n### Field\n\n- **Breaking change:** Add `onSubmit` validation mode and make it the default over `onBlur`.<br />\n  Fields that use non-`required` attribute validation no longer validate the control on blur. Instead, validation first occurs `onSubmit`, and afterwards revalidation occurs `onChange`.\n  (#3013) by @mj12albert\n- Add `dirty` and `touched` props (#2950) by @mj12albert\n- New `<Field.Item>` part (#2810) by @mj12albert\n- Fix `validationMode=\"onChange\"` not clearing custom error state (#3048) by @mj12albert\n- Fix external `onChange` validation mode errors (#3137) by @atomiks\n\n### Form\n\n- **Breaking change:** The `onClearErrors` prop has been removed.<br />\n  Errors from the `errors` prop are always cleared when the value changes.\n  (#3136) by @mj12albert\n- Add `onSubmit` validation mode.<br />\n  Additionally, `validationMode` can be set on `<Form>`.\n  (#3013) by @mj12albert\n- Add `onFormSubmit` callback (#3131) by @mj12albert\n\n### Menu\n\n- **Breaking change:** Support detached triggers.<br />\n  `openOnHover`, `delay`, and `closeDelay` props have been moved from `<Menu.Root>` to `<Menu.Trigger>`.<br />\n  Additionally, menus now must have at least one `<Menu.Trigger>` element.\n  (#3170) by @michaldudak\n- Ignore disabled item on initial focusing (#2604) by @mnajdova\n- Fix stealing focus from dialogs on close (#2920) by @atomiks\n- Place `overflow: hidden` on `<body>` for overlay scrollbars by default. Avoids sticky elements shifting if `<body>` has an `overflow` style specified. (#3083) by @atomiks\n\n### Navigation Menu\n\n- Fix nested popup dismiss actions (#2978) by @atomiks\n- Fix error on React 17 (#3204) by @atomiks\n\n### Number Field\n\n- Granular change reasons (#3132) by @atomiks\n\n### Popover\n\n- **Breaking change:** Support detached triggers and multiple triggers per popover.<br />\n  `openOnHover`, `delay`, and `closeDelay` props have been moved from `<Popover.Root>` to `<Popover.Trigger>`.\n  (#2336) by @michaldudak\n- Fix `initialFocus` as function being called on close (#2949) by @atomiks\n- Fix swiping or scrolling on nested popup dismissing popover on touch (#3011) by @atomiks\n- Place `overflow: hidden` on `<body>` for overlay scrollbars by default. Avoids sticky elements shifting if `<body>` has an `overflow` style specified. (#3083) by @atomiks\n\n### Preview Card\n\n- **Breaking change:** Move delay props to trigger.<br />\n  If you were using `delay` or `closeDelay` props, be sure to move them to from `<PreviewCard.Root>` to the `<PreviewCard.Trigger>` component.\n  (#3182) by @atomiks\n\n### Radio Group\n\n- **Breaking change:** Render root as `<span>` instead of `<button>`\n  (#3205) by @mj12albert\n\n### Scroll Area\n\n- **Breaking change:** Improve CSS vars performance.<br />\n  The CSS variables are now on the `<ScrollArea.Viewport>` part, not `<ScrollArea.Root>`, and inheritance is disabled for all child elements (or pseudo-elements). Children must manually opt in using `--scroll-area-[variable-name]: inherit`.\n  (#3156) by @atomiks\n\n### Select\n\n- **Breaking change:** Make the trigger native button by default.<br />\n  The trigger now renders a `<button>` element, be sure to adjust your code if necessary.\n  (#3177) by @mnajdova\n- Add `open` state type on `Select.Icon` interface (#2919) by @komkanit\n- Fix `onValueChange` type inference when `value` is unspecified (#2897) by @atomiks\n- Fix `required` form submission with multiple values (#2925) by @atomiks\n- Avoid re-rendering on popup height expansion (#2972) by @atomiks\n- Place `overflow: hidden` on `<body>` for overlay scrollbars by default. Avoids sticky elements shifting if `<body>` has an `overflow` style specified. (#3083) by @atomiks\n- Add `data-placeholder` attribute (#2737) by @seongminn\n\n### Slider\n\n- **Breaking change:** Add `thumbCollisionBehavior` prop.<br />\n  In range sliders, moving a thumb with a pointer will now push other thumbs it collides with to avoid blocking drag movements by default (the default value is `push`).<br />\n  The value `swap` was also added, which allows thumbs to be dragged past each other when they collide.<br />\n  Lastly, the value `none` is the same as the previous behavior, where thumbs can't be dragged past one another.<br />\n  Keyboard interactions always use `none` behavior.\n  (#2856) by @atomiks\n- Granular change reasons (#3132) by @atomiks\n\n### Switch\n\n- **Breaking change:** Render root as `<span>` instead of `<button>`\n  (#3205) by @mj12albert\n\n### Tabs\n\n- **Breaking change:** Fix selected/active state naming consistency.<br />\n  - Renamed `[data-selected]` to `[data-active]` in `<Tabs.Tab>`\n  - Removed `[data-highlighted]` (`:focus-visible` was already the recommendation in styles)\n  - `selectedTabPosition`/`selectedTabSize` are now `activeTabPosition`/`activeTabSize` in `Tabs.Indicator.State`\n    (#3024) by @atomiks\n- **Breaking change:** Change `activateOnFocus` to false.<br />\n  If you need your Tabs to activate on focus, be sure to add `activateOnFocus` prop.\n  (#3176) by @mnajdova\n- Fix Next.js 16 error from `Math.random` id generation (#3051) by @atomiks\n- Fix indicator sizing and offsets (#3214) by @atomiks\n\n### Toast\n\n- Allow `React.ReactNode` for `title`/`description` properties (#2929) by @atomiks\n- Add ability to anchor to an element (#3096) by @atomiks\n\n### Toolbar\n\n- **Breaking change:** The `cols` prop has been removed.<br />\n  This prop was not supposed to be exposed.\n  (#3133) by @mj12albert\n\n### Tooltip\n\n- **Breaking change:** Support detached triggers.<br />\n  `delay` and `closeDelay` props have been moved from `<Tooltip.Root>` to `<Tooltip.Trigger>`.\n  (#3071) by @michaldudak\n- **Breaking change:** Change `hoverable` to `disableHoverablePopup`.<br />\n  In case you need to disable the hoverable popup behavior, be sure to add the `disableHoverablePopup` prop.\n  (#3178) by @mnajdova\n- Fix `data-instant` ending transition of same tooltip (#2962) by @atomiks\n\nAll contributors of this release in alphabetical order: @atomiks, @brianle1301, @brijeshb42, @chuganzy, @dav-is, @epr3, @fredericoo, @Janpot, @komkanit, @LukasTy, @michaldudak, @mj12albert, @mnajdova, @oliviertassinari, @romgrk, @seongminn, @sukvvon, @ZeeshanTamboli\n\n## v1.0.0-beta.4\n\n_Oct 1, 2025_\n\n### General changes\n\n- **Breaking change:** Generic event details.\n  The main exported type is now `BaseUIChangeEventDetails` (with a paired `BaseUIGenericEventDetails`), not `BaseUIEventDetails`.\n  (#2796) by @atomiks\n- Update `disabled` prop of buttons when ref changes (#2756) by @chuganzy\n- Refine event details (#2698) by @atomiks\n\n### Accordion\n\n- **Breaking change:** Use `useId` instead of composite index as fallback value.\n  Accordion items must have an explicit `value` set in order to be initially open. Inferring the value by their DOM index is no longer supported.\n  (#2664) by @mj12albert\n- **Breaking change:** Rename `openMultiple` prop to `multiple`\n  (#2764) by @LukasTy\n\n### Autocomplete\n\n- **Breaking change:** Rename `cols` to `grid` prop.\n  Specify `grid={true}` instead of `cols={number}` - the columns are automatically inferred from `Autocomplete.Row`\n  (#2683) by @atomiks\n- Fix duplicate `onOpenChange` calls and pass correct DOM `event`.\n  (#2682) by @atomiks\n- Fix controlled input value updates (#2707) by @atomiks\n- Fix input focus on close when clicking trigger (#2723) by @atomiks\n- Add `alwaysSubmitOnEnter` prop and allow form submission on <kbd>Enter</kbd> if no item is highlighted by default (#2700) by @atomiks\n- Use `ReadonlyArray` type for `items` (#2819) by @atomiks\n\n### Collapsible\n\n- Fix CollapsiblePanel type to use its own state (#2697) by @chuganzy\n- Respect user's CSS `display` property on panel (#2772) by @mj12albert\n\n### Combobox\n\n- **Breaking change**: `onItemHighlighted` now has a `reason` property instead of `type` to be consistent with the `eventDetails` API. (#2796) by @atomiks\n- **Breaking change:** Rename `cols` to `grid` prop.\n  Specify `grid={true}` instead of `cols={number}` - the columns are automatically inferred from `Combobox.Row`\n  (#2683) by @atomiks\n- Fix duplicate `onOpenChange` calls and pass correct DOM `event`.\n  (#2682) by @atomiks\n- Fix initial closed typeahead (#2665) by @atomiks\n- Support `autoHighlight` prop (#2668) by @atomiks\n- Set default input value based on `value` prop (#2680) by @atomiks\n- Fix controlled input value updates (#2707) by @atomiks\n- Fix input focus on close when clicking trigger. Fixes a jump to the bottom of the page in Safari (#2723) by @atomiks\n- Fix unexpected close with multiple selection and input inside popup (#2771) by @atomiks\n- Allow form submission on <kbd>Enter</kbd> if no item is highlighted by default (#2700) by @atomiks\n- Avoid refiltering with ending transition in multiple selection mode (#2681) by @atomiks\n- Support object values with `isItemEqualToValue` prop (#2704) by @atomiks\n- Use `ReadonlyArray` type for `items` (#2819) by @atomiks\n- Fix misleading `item-press` reason in `onInputValueChange` (#2830) by @atomiks\n- Clear single-select value on input clear (#2860) by @atomiks\n- Fix `focusout` of input not closing popup under certain conditions (#2864)\n\n### Context Menu\n\n- Ensure submenus close when parents close (#2768) by @atomiks\n- Fix `onClick` firing twice on first click of item (#2849) by @atomiks\n\n### Menu\n\n- Ensure submenus close when parents close (#2768) by @atomiks\n- Allow non-nested portals across differing popup trees (#2818) by @atomiks\n\n### Menubar\n\n- Fix Menubar not disabling child Menus (#2736) by @aarongarciah\n- Ensure submenus close when parents close (#2768) by @atomiks\n- Fix `CompositeList` not updating item order on reordering (#2675) by @chuganzy\n\n### Navigation Menu\n\n- Make link close on click configurable (#2740) by @atomiks\n- Fix focus returning to trigger without animations (#2779) by @atomiks\n- Fix `CompositeList` not updating item order on reordering (#2675) by @chuganzy\n\n### Number Field\n\n- Fix stuck virtual cursor after mouse tap (#2720) by @atomiks\n- Improve parsing logic (#2725) by @atomiks\n- Align value changes with `Slider`. An `onValueCommitted` callback has been added. (#2726) by @atomiks\n\n### Popover\n\n- Allow non-nested portals across differing popup trees (#2818) by @atomiks\n\n### Scroll Area\n\n- Add overflow presence state attributes and CSS variables (#2478) by @atomiks\n- Fix RTL horizontal scrollbar on Safari (#2776) by @atomiks\n- Fix thumb size flicker (#2778) by @atomiks\n\n### Select\n\n- **Breaking change:** Add `Select.List` component. It is now possible for `Select.ScrollArrow` to show when in fallback (`alignItemWithTrigger` deactivated). As a result, if you want the scroll arrows to be hidden in this mode like before, change the styles to default to `display: none` on `.ScrollArrow`, and `display: block` when `[data-side=\"none\"]`. (#2596) by @atomiks\n- Block opening the popup when provided `readOnly` (#2717) by @seongminn\n- Add `open` state for `Select.Icon` and fix `ref` type (#2714) by @seongminn\n- Support object values with `isItemEqualToValue` prop (#2704) by @atomiks\n- Use `ReadonlyArray` type for `items` (#2819) by @atomiks\n\n### Slider\n\n- **Breaking change:** `onValueChange` has `activeThumbIndex` as part of the `eventDetails` object as a second parameter, not third. (#2796) by @atomiks\n- **Breaking change:** Remove redundant hidden inputs.\n  The `inputRef` prop is moved from `Root` to `Thumb`.\n  (#2631) by @mj12albert\n- Fix pointer tracking bugs (#2688) by @mj12albert\n- Fix input attributes (#2728) by @mj12albert\n- Add `thumbAlignment` prop (#2540) by @mj12albert\n\n### Switch\n\n- Fix duplicate `name` attribute (#2763) by @mj12albert\n\n### Toast\n\n- **Breaking change:** Support variable height stacking.\n  Toasts that have varying heights no longer force a `data-expanded` expanded state on the viewport. CSS should be amended to ensure larger toasts don't overflow a small toast stacked at the front. See this [diff](https://github.com/mui/base-ui/pull/2742/files#diff-e378460dafb74fe0c90ef960ad0ef1c38d68d74b63815520bb437f9041361917) for new styles, along with general improvements to stacking styles.\n  (#2742) by @atomiks\n- Reduce stickiness of expanded state (#2770) by @atomiks\n- Ensure toast is frozen at its current visual transform while swiping (#2769) by @atomiks\n\n### Toggle Group\n\n- **Breaking change:** Rename `toggleMultiple` prop to `multiple`.\n  (#2764) by @LukasTy\n\n### Toolbar\n\n- Fix `CompositeList` not updating item order on reordering (#2675) by @chuganzy\n\n### useRender\n\n- Add div as a `defaultTagName` (#2692) by @mnajdova\n\nAll contributors of this release in alphabetical order: @aarongarciah, @atomiks, @brijeshb42, @chuganzy, @Copilot, @Janpot, @LukasTy, @martenbjork, @michaldudak, @mj12albert, @mnajdova, @oliviertassinari, @seongminn, @sukvvon, @vladmoroz\n\n## v1.0.0-beta.3\n\n_Sep 3, 2025_\n\n### General changes\n\n- **Breaking change:** Base UI event details.\n  Custom event callbacks provide BaseUIEventDetails object as their second parameter.\n  This object contains the source event, reason and methods to customize the behavior (where applicable).\n  For example, `onOpenChange(open, event, reason)` becomes `onOpenChange(open, eventDetails)`, where `eventDetails` contains `event` and `reason` properties.\n  ```diff\n  -onOpenChange: (open, event, reason) => {\n  +onOpenChange: (open, eventDetails) => {\n  - if (reason === 'escape-key') {\n  +  if (eventDetails.reason === 'escape-key') {\n       // ...\n     }\n   }\n  ```\n  (#2382) by @atomiks\n\n### Alert Dialog\n\n- **Breaking change:** Support `initialFocus` and `finalFocus` functions.\n  The `initialFocus` and `finalFocus` props can be functions that return DOM elements to focus.\n  This is a new feature for `finalFocus` and a breaking change for `initialFocus` as the element must be returned directly (not as a ref).\n  (#2536) by @atomiks\n\n### Autocomplete\n\n- New Autocomplete component (#2105) by @atomiks\n\n### Checkbox\n\n- Fix missing validity attributes when wrapped in `Field` (#2572) by @Copilot\n\n### Combobox\n\n- New Combobox component (#2105) by @atomiks\n\n### Context Menu\n\n- Fix default offsets when `align=\"center\"` or `side` differs (#2601) by @atomiks\n\n### Dialog\n\n- **Breaking change:** Support `initialFocus` and `finalFocus` functions.\n  The `initialFocus` and `finalFocus` props can be functions that return DOM elements to focus.\n  This is a new feature for `finalFocus` and a breaking change for `initialFocus` as the element must be returned directly (not as a ref).\n  (#2536) by @atomiks\n- Restore focus to popup when focused element is removed (#2479) by @atomiks\n\n### Field\n\n- Prevent defaultValue reset on focus for uncontrolled inputs (#2543) by @ingokpp\n- Allow `onValueChange` to fire when `defaultValue`/`value` are not set (#2600) by @atomiks\n\n### Input\n\n- Allow `onValueChange` to fire when `defaultValue`/`value` are not set (#2600) by @atomiks\n\n### Menu\n\n- **Breaking change:** Fix `closeParentOnEsc` default value.\n  The default value of `closeParentOnEsc` in Menu.SubmenuRoot is now false.\n  When the <kbd>Esc</kbd> key is pressed in a Submenu, the Submenu closes, and the focus correctly moves to the SubmenuTrigger.\n  (#2493) by @seongminn\n- **Breaking change:** Support `initialFocus` and `finalFocus` functions.\n  The `initialFocus` and `finalFocus` props can be functions that return DOM elements to focus.\n  This is a new feature for `finalFocus` and a breaking change for `initialFocus` as the element must be returned directly (not as a ref).\n  (#2536) by @atomiks\n- Fix menu not opening when inside context menu trigger (#2506) by @baptisteArno\n- Fix `transform-origin` variable calculation when Positioner `sideOffset` is a function (#2511) by @atomiks\n- Fix submenu events (#2483) by @atomiks\n- Fix `limitShift` offset based on arrow size (#2571) by @atomiks\n\n### Navigation Menu\n\n- **Breaking change:** Semantic element structure and `active` page prop.\n  `NavigationMenu.List` renders `<ul>` and `NavigationMenu.Item` renders `<li>` by default.\n  (#2526) by @atomiks\n- Unshare `AbortController` instance (#2441) by @tomokinat\n- Close on link click by default (#2535) by @atomiks\n\n### Number Field\n\n- Fix duplicate `onValueChange` calls (#2591) by @atomiks\n\n### Popover\n\n- **Breaking change:** Support `initialFocus` and `finalFocus` functions.\n  The `initialFocus` and `finalFocus` props can be functions that return DOM elements to focus.\n  This is a new feature for `finalFocus` and a breaking change for `initialFocus` as the element must be returned directly (not as a ref).\n  (#2536) by @atomiks\n- Fix outside click after right clicking in popup (#2508) by @baptisteArno\n- Fix unexpected close when nested inside two popovers (#2481) by @atomiks\n- Fix `transform-origin` variable calculation when Positioner `sideOffset` is a function (#2511) by @atomiks\n- Restore focus to popup when focused element is removed (#2479) by @atomiks\n- Fix `limitShift` offset based on arrow size (#2571) by @atomiks\n\n### Preview Card\n\n- Fix `transform-origin` variable calculation when Positioner `sideOffset` is a function (#2511) by @atomiks\n- Fix `limitShift` offset based on arrow size (#2571) by @atomiks\n\n### Radio Group\n\n- Return null in form data when no option selected (#2473) by @ingokpp\n\n### Scroll Area\n\n- Prevent pointer events from sibling portals triggering hover (#2542) by @KenanYusuf\n\n### Select\n\n- Fix stale `items` prop (#2397) by @atomiks\n- Fix unexpected close when nested inside two popovers (#2481) by @atomiks\n- Fix `onValueChange` type inference (#2372) by @atomiks\n- Fix `transform-origin` variable calculation when Positioner `sideOffset` is a function (#2511) by @atomiks\n- Reset state when selected item is removed (#2577) by @atomiks\n- Fix `data-highlighted` and DOM focus item desync (#2569) by @atomiks\n- Fix item click with `defaultOpen` prop (#2570) by @atomiks\n- Fix scroll arrows not propagating scroll fully to start/end of list (#2523) by @atomiks\n- Fix `limitShift` offset based on arrow size (#2571) by @atomiks\n\n### Slider\n\n- **Breaking change:** Instead of the thumb div, the `input type=\"range\"` element receives focus. Focus styles that were targeting the thumb, should be updated.\n  For example `.Thumb:focus-visible` should be replaced with `.Thumb:has(:focus-visible)`.\n  The `tabIndex` prop is moved from Root to Thumb where it gets forwarded to the input.\n  The thumb's `render` prop no longer contains the third `inputProps` argument; the input element is instead merged with children.\n  (#2578) by @mj12albert\n- Reduce bundle size (#2551) by @oliviertassinari\n- Fix thumb `:focus-visible` with mixed keyboard and pointer modality (#2584) by @mj12albert\n- Add `index` prop to `Slider.Thumb` (#2593) by @mj12albert\n\n### Tabs\n\n- Fix tab size rounding (#2488) by @atomiks\n- Fix highlight sync when focus is inside list (#2487) by @atomiks\n\n### Tooltip\n\n- Fix `transform-origin` variable calculation when Positioner `sideOffset` is a function (#2511) by @atomiks\n- Fix `limitShift` offset based on arrow size (#2571) by @atomiks\n\n### useRender\n\n- Add support for data-\\* attributes (#2524) by @Raghuboi\n- Add `defaultTagName` parameter (#2527) by @atomiks\n\nAll contributors of this release in alphabetical order: @atomiks, @baptisteArno, @brijeshb42, @Copilot, @ingokpp, @Janpot, @KenanYusuf, @LukasTy, @michaldudak, @mirka, @mj12albert, @mnajdova, @oliviertassinari, @Powerplex, @Raghuboi, @seongminn, @tomokinat\n\n## v1.0.0-beta.2\n\n_Jul 30, 2025_\n\n### General changes\n\n- Fix navigator checks and ensure safe platform retrieval (#2273) by @mo36924\n- Prevent `Space` key default on keydown (#2295) by @atomiks\n- Check for `performance` existence on server (#2316) by @atomiks\n\n### Accordion\n\n- Destructure `render` prop (#2280) by @atomiks\n- Fix keyboard interactions with elements in the panel (#2321) by @mj12albert\n- Fix open transitions in Safari/Firefox (#2327) by @atomiks\n\n### Alert Dialog\n\n- Support `ShadowRoot` containers (#2236) by @atomiks\n- Add `forceRender` prop to `Backdrop` part (#2037) by @atomiks\n- Improve outside press behavior with touch input (#2334) by @atomiks\n\n### Checkbox\n\n- Fix focusing form controls with `inputRef` (#2252) by @mj12albert\n\n### Collapsible\n\n- Destructure render prop (#2323) by @atomiks\n- Fix open transitions in Safari/Firefox (#2327) by @atomiks\n\n### Dialog\n\n- Support `ShadowRoot` containers (#2236) by @atomiks\n- Add `forceRender` prop to `Backdrop` part (#2037) by @atomiks\n- Improve outside press behavior with touch input (#2334) by @atomiks\n- Use `click` event for outside press dismissal (#2275) by @atomiks\n\n### Field\n\n- Deregister fields from `Form` when unmounting (#2231) by @mj12albert\n\n### Form\n\n- Deregister fields from `Form` when unmounting (#2231) by @mj12albert\n\n### Menu\n\n- Support `ShadowRoot` containers (#2236) by @atomiks\n- Avoid double `useRenderElement` passes (#2256) by @atomiks\n- Improve outside press behavior with touch input (#2334) by @atomiks\n- Close submenus when focus is lost by shift-tabbing (#2290) by @michaldudak\n\n### Menubar\n\n- Fix triggers role (#2317) by @atomiks\n\n### Meter\n\n- Fix ARIA attributes and update docs (#2267) by @mj12albert\n\n### Navigation Menu\n\n- **Breaking change:** Support inlined nesting.\n  Ensure the popup's `width` is set to `var(--popup-width)` unconditionally (without the media query) on the `.Popup` class.\n  (#2269) by @atomiks\n- Avoid double `useRenderElement` passes (#2256) by @atomiks\n- Add `useButton` integration to `Trigger` (#2296) by @atomiks\n- Fix popup size transitions on iOS (#2387) by @atomiks\n\n### Number Field\n\n- Remove `invalid` prop (#2315) by @atomiks\n- Fix button disabled state only including root disabled state (#2268) by @mj12albert\n\n### Popover\n\n- Support `ShadowRoot` containers (#2236) by @atomiks\n- Remove ancestor nodes from inside elements for outside press detection (#2339) by @atomiks\n- Improve outside press behavior with touch input (#2334) by @atomiks\n- Use `click` event for outside press dismissal (#2275) by @atomiks\n\n### Preview Card\n\n- Support `ShadowRoot` containers (#2236) by @atomiks\n\n### Progress\n\n- Fix ARIA attributes and update docs (#2267) by @mj12albert\n\n### Radio Group\n\n- Add aria-required attribute (#2227) by @cgatian\n- Extend state with `FieldRoot.State` (#2251) by @mj12albert\n- Fix focusing form controls with `inputRef` (#2252) by @mj12albert\n- Avoid double `useRenderElement` passes (#2256) by @atomiks\n\n### Scroll Area\n\n- Disable `user-select` on scrollbar and non-main button interactions (#2338) by @atomiks\n\n### Select\n\n- Support `ShadowRoot` containers (#2236) by @atomiks\n- Add `value` and `readOnly` to `Select.Trigger` state (#2237) by @atomiks\n- Add `multiple` prop (#2173) by @atomiks\n- Allow typeahead while open for `multiple` mode (#2274) by @atomiks\n- Ensure positionerElement is available in document mouseup (#2276) by @atomiks\n- Fix `alignItemWithTrigger` fallback scroll jump (#2241) by @atomiks\n- Support conditional `multiple` prop in types (#2369) by @atomiks\n- Fix multiple ARIA behavior on touch (#2333) by @atomiks\n- Improve outside press behavior with touch input (#2334) by @atomiks\n\n### Slider\n\n- Fix focusing form controls with `inputRef` (#2252) by @mj12albert\n\n### Toast\n\n- Fix `promise` method timeout option handling (#2294) by @atomiks\n- Make `Toast.Viewport` an announce container (#2246) by @atomiks\n\n### Toggle\n\n- Avoid double `useRenderElement` passes (#2256) by @atomiks\n\n### Toggle Group\n\n- Avoid double `useRenderElement` passes (#2256) by @atomiks\n\n### Toolbar\n\n- Avoid double `useRenderElement` passes (#2256) by @atomiks\n\n### Tooltip\n\n- Support `ShadowRoot` containers (#2236) by @atomiks\n- Memoize leftover object in tooltip (#2250) by @sai6855\n- Fix error when combining `defaultOpen` and `disabled` (#2374) by @atomiks\n\nAll contributors of this release in alphabetical order: @aelfannir, @atomiks, @brijeshb42, @cgatian, @Janpot, @michaldudak, @mj12albert, @mo36924, @romgrk, @sai6855\n\n## v1.0.0-beta.1\n\n_Jul 1, 2025_\n\n### General changes\n\n- Make error messages consistent (#2049) by @michaldudak\n- Do not overwrite event handler when `undefined` is passed explicitly (#2151) by @michaldudak\n\n### Accordion\n\n- Allow content to resize naturally (#2043) by @atomiks\n- Fix transition status mapping (#2169) by @atomiks\n- Fix `aria-controls` reference (#2170) by @atomiks\n- Fix test warning about mixed animation types (#2180) by @atomiks\n\n### Checkbox\n\n- **Breaking change:** Support implicit `Field.Label`.\n  If `Field.Label` encloses Switch/Checkbox/Radio, the `htmlFor`/`id` attributes are no longer explicitly set to associate them.\n  (#2036) by @mj12albert\n- Refactor to `useRenderElement` (#2053) by @mj12albert\n- Always set `id` on the `<input>` element (#2115) by @mj12albert\n\n### Checkbox Group\n\n- Fix `onCheckedChange` not running when parent checkbox is present (#2155) by @mj12albert\n\n### Collapsible\n\n- Allow content to resize naturally (#2043) by @atomiks\n- Fix `aria-controls` reference (#2170) by @atomiks\n- Fix test warning about mixed animation types (#2180) by @atomiks\n\n### Context Menu\n\n- **Breaking change:** Add `SubmenuRoot` part.\n  Nested menus should be defined with `Menu.SubmenuRoot` instead of `Menu.Root` to to avoid ambiguity.\n  (#2042) by @atomiks\n- Fix CheckboxItemIndicator export (#2009) by @aarongarciah\n\n### Dialog\n\n- Fix popup prop merging (#2119) by @atomiks\n\n### Field\n\n- **Breaking change:** Support implicit `Field.Label`.\n  If `Field.Label` encloses Switch/Checkbox/Radio, the `htmlFor`/`id` attributes are no longer explicitly set to associate them.\n  (#2036) by @mj12albert\n- Enable custom validation based on other form values (#1941) by @mj12albert\n- Fix `onValueChange` `value` type (#2112) by @atomiks\n- Fix `Field.Label` focusing trigger (#2118) by @atomiks\n- Fix slider field label (#2154) by @mj12albert\n\n### Fieldset\n\n- Refactor to `useRenderElement` (#2053) by @mj12albert\n\n### Form\n\n- Enable custom validation based on other form values (#1941) by @mj12albert\n\n### Input\n\n- Fix `onValueChange` `value` type (#2112) by @atomiks\n\n### Menu\n\n- **Breaking change:** Add `SubmenuRoot` part.\n  Nested menus should be defined with `Menu.SubmenuRoot` instead of `Menu.Root` to to avoid ambiguity.\n  (#2042) by @atomiks\n- Unset `role` from Trigger (#2047) by @atomiks\n- Emit `close` event on `cancel-open` (#2067) by @atomiks\n- Fix close toggle when rendering non-native button (#2071) by @atomiks\n- Add `highlighted` to item `State` (#2079) by @atomiks\n- Remove highlighted effect (#2162) by @atomiks\n- Cut out internal backdrop to allow interacting with triggers (#2141) by @michaldudak\n- Fix active index sync on hover (#2163) by @atomiks\n- Fix focus returning to root when submenus have exit transitions (#2163) by @atomiks\n\n### Menubar\n\n- Fix `closeOnClick: false` not working in nested menus (#2094) by @michaldudak\n\n### Navigation Menu\n\n- Handle layout resize while open (#2070) by @atomiks\n- Fix positioner height when opening menu using the keyboard arrows (#2060) by @juliomerisio\n\n### Number Field\n\n- Ensure `onValueChange` is called with already-formatted parsed value (#1905) by @atomiks\n- Fix revalidation on change (#2174) by @atomiks\n\n### Popover\n\n- Fix close toggle when rendering non-native button (#2071) by @atomiks\n- Cut out internal backdrop to allow interacting with triggers (#2141) by @michaldudak\n\n### Radio Group\n\n- **Breaking change:** Support implicit `Field.Label`.\n  If `Field.Label` encloses Radio, the `htmlFor`/`id` attributes are no longer explicitly set to associate them.\n  (#2036) by @mj12albert\n- Refactor to `useRenderElement` (#2053) by @mj12albert\n\n### Scroll Area\n\n- Ignore `data-scrolling` during programmatic scroll (#1908) by @atomiks\n\n### Select\n\n- **Breaking change:** Print raw value in `Select.Value`.\n  `<Select.Value>` now prints the raw value by default unless an `items` prop is specified on `Select.Root`.\n  See https://base-ui.com/react/components/select#formatting-the-value for more information.\n  (#2087) by @atomiks\n- Performance: avoid re-renders (#1961) by @romgrk\n- Fix close toggle when rendering non-native button (#2071) by @atomiks\n- Fix `Field.Label` focusing trigger (#2118) by @atomiks\n- Fix programmatic value changes and autofill handling (#2084) by @atomiks\n- Add `highlighted` to item `State` (#2079) by @atomiks\n- Cut out internal backdrop to allow interacting with triggers (#2141) by @michaldudak\n- Pass `value` as state (#2153) by @atomiks\n- Extend `FieldRoot.State` type (#2192) by @atomiks\n\n### Slider\n\n- Use pointer capture when dragging (#2059) by @mj12albert\n- Fix slider field label (#2154) by @mj12albert\n\n### Switch\n\n- **Breaking change:** Support implicit `Field.Label`.\n  If `Field.Label` encloses Switch, the `htmlFor`/`id` attributes are no longer explicitly set to associate them.\n  (#2036) by @mj12albert\n\n### Tabs\n\n- Fix indicator positioning when TabsList overflows (#2093) by @mj12albert\n- Fix focus going out of sync when selected value is changed externally (#2107) by @atomiks\n- Remove highlighted state (#2164) by @atomiks\n\n### Toolbar\n\n- Set `disabled` attr on toolbar button when `focusableWhenDisabled={false}` (#2176) by @mj12albert\n\n### useRender\n\n- Make useRender RSC-friendly (#2134) by @michaldudak\n\nAll contributors of this release in alphabetical order: @aarongarciah, @atomiks, @bernardobelchior, @brijeshb42, @Janpot, @juliomerisio, @lesha1201, @michaldudak, @mj12albert, @oliviertassinari, @romgrk\n\n## v1.0.0-beta.0\n\n_May 29, 2025_\n\n### General changes\n\n- Remove proptypes (#1760) by @michaldudak\n- Unify component export patterns (#1478) by @michaldudak\n- Default `tabIndex` to `0` on `<button>` parts (#1939) by @atomiks\n\n### Accordion\n\n- Stop event propagation to allow composite components to be used within popups (#1871) by @atomiks\n\n### Alert Dialog\n\n- **Breaking change:** Refine `OpenChangeReason`. `hover` is now `trigger-hover`; `click` is now `trigger-press`; `focus` is now `trigger-focus`.\n  (#1782) by @atomiks\n- Use basic scroll lock on iOS\n  (#1890) by @atomiks\n\n### Checkbox\n\n- Set `aria-required`, use `useButton` (#1777) by @mj12albert\n\n### Checkbox Group\n\n- **Breaking change:** Enable submitting checkbox group value as one field.\n  For parent checkboxes, use `value` instead of `name` on each `Checkbox.Root` part to link as the values.\n  (#1948) by @mj12albert\n- Fix `validate` fn incorrectly running twice (#1959) by @mj12albert\n\n### Context Menu\n\n- New `ContextMenu` component (#1665) by @atomiks\n\n### Dialog\n\n- **Breaking change:** Refine `OpenChangeReason`. `hover` is now `trigger-hover`; `click` is now `trigger-press`; `focus` is now `trigger-focus`.\n  (#1782) by @atomiks\n- Use basic scroll lock on iOS\n  (#1890) by @atomiks\n\n### Field\n\n- **Breaking change:** Consolidate `Field.Error` `forceShow` into `match` prop.\n  Use `match={true}` (or implicit boolean) instead of `forceShow`.\n  (#1919) by @atomiks\n- Improve `Label` logic that prevents text selection on double click (#1784) by @atomiks\n- Fix validation inconsistency (#1779) by @atomiks\n- Fix integration of Base UI components (#1755) by @atomiks\n- Set `valueMissing` to false if only error and not dirtied (#1810) by @atomiks\n- `validate` with latest value on blur (#1850) by @atomiks\n- Revalidate only `required` on change (#1840) by @atomiks\n- Run validate function after native validations (#1926) by @mj12albert\n- Fix `validate` fn incorrectly running twice (#1959) by @mj12albert\n- Integrate range sliders with Form and Field (#1929) by @mj12albert\n\n### Form\n\n- Fix integration of Base UI components (#1755) by @atomiks\n- Select inputs on focus (#1858) by @atomiks\n- Exclude number formatting from form value (#1957) by @mj12albert\n- Integrate range sliders with Form and Field (#1929) by @mj12albert\n\n### Input\n\n- Fix `Input.Props` type (#1915) by @mj12albert\n- Extend `Field.Control.State` (#1954) by @atomiks\n\n### Menu\n\n- **Breaking change:** Refine `OpenChangeReason`. `hover` is now `trigger-hover`; `click` is now `trigger-press`; `focus` is now `trigger-focus`.\n  (#1782) by @atomiks\n- Fix function dependency handling (#1787) by @atomiks\n- Add missing `'use client'` to `RadioGroup` part (#1851) by @atomiks\n- Ensure `null` items are removed from composite lists (#1847) by @atomiks\n- Avoid `:focus-visible` style appearing (#1846) by @atomiks\n- Better handle dynamic and non-string items (#1861) by @atomiks\n- Add `collisionAvoidance` prop (#1849) by @atomiks\n- Add `finalFocus` and `closeDelay` props (#1918) by @atomiks\n- Use basic scroll lock on iOS\n  (#1890) by @atomiks\n\n### Menubar\n\n- New `Menubar` component (#1684) by @michaldudak\n\n### Navigation Menu\n\n- New `NavigationMenu` component (#1741) by @atomiks\n\n### Number Field\n\n- `validate` with latest value on blur (#1850) by @atomiks\n- Move scrubbing logic to `ScrubArea` component (#1859) by @atomiks\n- Remove floating point errors when `snapOnStep` is disabled (#1857) by @atomiks\n- Stop event propagation to allow composite components to be used within popups (#1871) by @atomiks\n- Exclude number formatting from form value (#1957) by @mj12albert\n\n### Popover\n\n- **Breaking change:** Refine `OpenChangeReason`. `hover` is now `trigger-hover`; `click` is now `trigger-press`; `focus` is now `trigger-focus`.\n  (#1782) by @atomiks\n- Fix function dependency handling (#1787) by @atomiks\n- Avoid prop getters when merging props (#1852) by @atomiks\n- Add `collisionAvoidance` prop (#1849) by @atomiks\n- Fix nested `openOnHover` (#1938) by @atomiks\n- Use basic scroll lock on iOS\n  (#1890) by @atomiks\n\n### Preview Card\n\n- **Breaking change:** Refine `OpenChangeReason`. `hover` is now `trigger-hover`; `click` is now `trigger-press`; `focus` is now `trigger-focus`.\n  (#1782) by @atomiks\n- Fix function dependency handling (#1787) by @atomiks\n- Add `collisionAvoidance` prop (#1849) by @atomiks\n\n### Radio Group\n\n- Fix composite focus of initially selected radio item (#1753) by @atomiks\n- Add `inputRef` props (#1683) by @atomiks\n- Stop event propagation to allow composite components to be used within popups (#1871) by @atomiks\n\n### Select\n\n- **Breaking change:** Move item anchoring prop to `Positioner`.\n  Use `<Select.Positioner alignItemWithTrigger={false}>` instead of `<Select.Root alignItemToTrigger={false}>` (note the `With` instead of `To`).\n  (#1713) by @atomiks\n- **Breaking change:** Defer mounting until typeahead is needed.\n  The `placeholder` prop is now required. Previously, only SSR needed it to prevent a hydration flash, but client-side rendering now also requires it.\n  (#1906) by @atomiks\n- **Breaking change:** Refine `OpenChangeReason`. `hover` is now `trigger-hover`; `click` is now `trigger-press`; `focus` is now `trigger-focus`.\n  (#1782) by @atomiks\n- Fix function dependency handling (#1787) by @atomiks\n- Add `inputRef` props (#1683) by @atomiks\n- Refactor to `useRenderElement` (#1797) by @atomiks\n- Ensure `null` items are removed from composite lists (#1847) by @atomiks\n- Fix `id` prop forwarding to hidden input (#1862) by @atomiks\n- Avoid `:focus-visible` style appearing (#1846) by @atomiks\n- Fix `transitionStatus` mapping on `ItemIndicator` (#1925) by @atomiks\n- Better handle dynamic and non-string items (#1861) by @atomiks\n- Use `Select.ItemText` ref to grab default text content (#1943) by @atomiks\n- Add `collisionAvoidance` prop (#1849) by @atomiks\n- Use basic scroll lock on iOS\n  (#1890) by @atomiks\n\n### Slider\n\n- **Breaking change:** Drop `inputId` prop from Thumb.\n  (#1914) by @mj12albert\n- Position thumb based on value instead of pointer location when dragging (#1750) by @DarthSim\n- Use `useRenderElement` (#1772) by @mj12albert\n- Add `inputRef` props (#1683) by @atomiks\n- Add `locale` prop (#1796) by @mj12albert\n- Stop event propagation to allow composite components to be used within popups (#1871) by @atomiks\n- set `data-dragging` on touchstart and pointerdown (#1874) by @mj12albert\n- Integrate range sliders with Form and Field (#1929) by @mj12albert\n\n### Toast\n\n- **Breaking change:** Add `Portal` part.\n  Place `<Toast.Viewport>` inside of `<Toast.Portal>`.\n  (#1962) by @atomiks\n- **Breaking change:** Avoid removing limited toasts from the DOM.\n  The `[data-limited]` styles in the demos were updated to handle limited toasts remaining in the DOM. They should now be a standalone style as `&[data-limited] { opacity: 0 }`.\n  (#1953) by @atomiks\n- Fix swipe jump on iOS (#1785) by @atomiks\n\n### Toggle\n\n- Stop event propagation to allow composite components to be used within popups (#1871) by @atomiks\n\n### Toolbar\n\n- Stop event propagation to allow composite components to be used within popups (#1871) by @atomiks\n\n### Tooltip\n\n- **Breaking change:** Refine `OpenChangeReason`. `hover` is now `trigger-hover`; `click` is now `trigger-press`; `focus` is now `trigger-focus`.\n  (#1782) by @atomiks\n- Fix function dependency handling (#1787) by @atomiks\n- Avoid prop getters when merging props (#1852) by @atomiks\n- Remove `trackCursorAxis` type from `Positioner` (#1895) by @atomiks\n- Apply `pointer-events: none` to `Positioner` when not hoverable (#1917) by @atomiks\n- Add `collisionAvoidance` prop (#1849) by @atomiks\n\n### useRender\n\n- **Breaking change:** Performance/refactor: `useRender`. An object with a `renderElement` property is no longer returned; instead, the hook returns the element directly (`const element = useRender(...)`). The `refs` option was also renamed to `ref`.\n  (#1934) by @romgrk\n- Skip most of useRenderElement logic when unnecessary (#1967) by @michaldudak\n\nAll contributors of this release in alphabetical order: @aarongarciah, @atomiks, @brijeshb42, @DarthSim, @flaviendelangle, @Janpot, @JCQuintas, @michaldudak, @mj12albert, @oliviertassinari, @romgrk, @Yonava, @ZeeshanTamboli\n\n## v1.0.0-alpha.8\n\n_Apr 17, 2025_\n\n### Accordion\n\n- Recalculate panel dimensions on layout resize (#1704) @atomiks\n- Rework animations and transitions (#1601) @mj12albert\n\n### AlertDialog\n\n- **Breaking change:** Rename `data-has-nested-dialogs` to `data-nested-dialog-open` (#1686) @mj12albert\n- Fix `onOpenChange` types for `event`/`reason` passing (#1721) @atomiks\n- Use consistent `inert` attr and map `[data-popup-open]` back to `open` (#1650) @atomiks\n- Fix text selection & right-clicks (#1702) @mj12albert\n\n### CheckboxGroup\n\n- Parent checkbox/nested demos (#1610) @atomiks\n\n### Collapsible\n\n- Fix ForwardedRef type of CollapsiblePanel (#1595) @megos\n- Recalculate panel dimensions on layout resize (#1704) @atomiks\n- Rework animations and transitions (#1601) @mj12albert\n\n### Dialog\n\n- **Breaking change:** Rename `data-has-nested-dialogs` to `data-nested-dialog-open` (#1686) @mj12albert\n- **Breaking change:** Add new `trap-focus` value to `modal` prop.\n  Dialogs with `modal=false` no longer trap focus.\n  (#1571) @atomiks\n- Fix `onOpenChange` types for `event`/`reason` passing (#1721) @atomiks\n- Use consistent `inert` attr and map `[data-popup-open]` back to `open` (#1650) @atomiks\n- Fix text selection & right-clicks (#1702) @mj12albert\n- Allow document to slide input into view on iOS when keyboard opens (#1735) @atomiks\n\n### Field\n\n- Fix forwarding of `name` and `disabled` props (#1616) @atomiks\n\n### Menu\n\n- Add missing item data attributes docs (#1691) @atomiks\n- Fix `inert` prop compatibility in React <19 (#1618) @sebinsua\n- Fix stuck highlight on submenu trigger when submenu opens with keyboard (#1698) @atomiks\n- Fix `onOpenChange` types for `event`/`reason` passing (#1721) @atomiks\n- Use consistent `inert` attr and map `[data-popup-open]` back to `open` (#1650) @atomiks\n- Fix text selection & right-clicks (#1702) @mj12albert\n\n### Meter\n\n- New Meter component (#1435) @mj12albert\n\n### NumberField\n\n- Correct percentage parse handling (#1676) @atomiks\n- New `snapOnStep` prop (#1560) @atomiks\n\n### Popover\n\n- **Breaking change:** Add new `trap-focus` value to `modal` prop (#1571) @atomiks\n- Fix `inert` prop compatibility in React <19 (#1618) @sebinsua\n- Fix `onOpenChange` types for `event`/`reason` passing (#1721) @atomiks\n- Use consistent `inert` attr and map `[data-popup-open]` back to `open` (#1650) @atomiks\n- Fix text selection & right-clicks (#1702) @mj12albert\n\n### Progress\n\n- **Breaking change:** Add `Progress.Label` and `locale` prop.\n  The `getAriaLabel` prop was removed as `Progress.Label` should be used to provide an accessible name.\n  (#1666) @mj12albert\n\n### Radio\n\n- Fix value forwarding and null handling (#1697) @atomiks\n\n### ScrollArea\n\n- **Breaking change:** Add `Content` part.\n  It is now required to include the `ScrollArea.Content` within `ScrollArea.Viewport` part when the content is horizontally scrollable.\n  (#1607) @atomiks\n- Handle visibility change and nesting (#1598) @atomiks\n- Correct thumb sizing with scrollbar margins (#1606) @atomiks\n\n### Select\n\n- **Breaking change:** Improve item highlight performance.\n  The highlighted state is now removed. It's not possible to customize the `data-highlighted` attribute anymore.\n  (#1570) @atomiks\n- Avoid double commit on value change (#1597) @atomiks\n- Reset `selectedIndex` when set to `null` (#1596) @atomiks\n- Add missing item data attributes docs (#1691) @atomiks\n- Fix `onOpenChange` types for `event`/`reason` passing (#1721) @atomiks\n- Use consistent `inert` attr and map `[data-popup-open]` back to `open` (#1650) @atomiks\n- Fix text selection & right-clicks (#1702) @mj12albert\n\n### Slider\n\n- Correct thumb positioning when control has padding (#1661) @mj12albert\n- Prevent range slider thumbs from being dragged past each other (#1612) @mj12albert\n- Fix incorrect CSS position on vertical slider indicator (#1599) @ZeeshanTamboli\n- Fix overlapping slider thumbs stuck at min or max (#1732) @mj12albert\n\n### Toast\n\n- New Toast component (#1467) @atomiks\n\n### Tooltip\n\n- Avoid re-rendering unrelated consumers (#1677) @atomiks\n- Add `disabled` prop (#1682) @atomiks\n- Fix `onOpenChange` types for `event`/`reason` passing (#1721) @atomiks\n- Use consistent `inert` attr and map `[data-popup-open]` back to `open` (#1650) @atomiks\n- Fix text selection & right-clicks (#1702) @mj12albert\n\nAll contributors of this release in alphabetical order: @atomiks, @megos, @michaldudak, @mj12albert, @oliviertassinari, @sebinsua, @ZeeshanTamboli\n\n## v1.0.0-alpha.7\n\n_Mar 20, 2025_\n\n### Accordion\n\n- Fix `aria-labelledby` on accordion panel (#1544) @mj12albert\n\n### AlertDialog\n\n- Fix selection on outside press on Firefox with modal prop (#1573) @atomiks\n- Fix non-interactive button disabled state (#1473) @mj12albert\n- `actionsRef` prop (#1236) @atomiks\n\n### Avatar\n\n- Support cross origin in useImageLoadingStatus (#1433) @ISnackable\n- Add missing Avatar export (#1428) @Gomah\n\n### Collapsible\n\n- Update props destructuring to fix Trigger disabled state (#1469) @huijiewei\n\n### Dialog\n\n- Fix selection on outside press on Firefox with modal prop (#1573) @atomiks\n- Fix non-interactive button disabled state (#1473) @mj12albert\n- `actionsRef` prop (#1236) @atomiks\n\n### Field\n\n- Fix `FieldControl` [data-filled] not reacting to external value changes (#1565) @atomiks\n\n### Menu\n\n- Ensure submenu triggers respond to clicks when `openOnHover=false` (#1583) @atomiks\n- Ensure `stickIfOpen` is reset to `true` correctly (#1548) @atomiks\n- Fix selection on outside press on Firefox with modal prop (#1573) @atomiks\n- Reset `hoverEnabled` state on close (#1461) @atomiks\n- Fix prop merging issues (#1445) @michaldudak\n- Set `pointer-events: none` style on backdrops when hoverable (#1351) @atomiks\n- `actionsRef` prop (#1236) @atomiks\n\n### NumberField\n\n- Fix ScrubArea on Safari (#1584) @atomiks\n- Fix `large/smallStep` getting stuck (#1578) @atomiks\n- Fix parse of numbers with spaces as thousands separators (#1577) @michaldudak\n- Prevent virtual cursor overlapping native one (#1491) @atomiks\n- Fix disabled state on increment/decrement buttons (#1462) @mj12albert\n- Correct virtual cursor rendering (#1484) @atomiks\n- Add `locale` prop (#1488) @atomiks\n- Improve virtual cursor perf (#1485) @atomiks\n\n### Popover\n\n- Ensure `stickIfOpen` is reset to `true` correctly (#1548) @atomiks\n- Fix selection on outside press on Firefox with modal prop (#1573) @atomiks\n- Set `pointer-events: none` style on backdrops when hoverable (#1351) @atomiks\n- Fix non-interactive button disabled state (#1473) @mj12albert\n- `modal` prop (#1459) @atomiks\n- `actionsRef` prop (#1236) @atomiks\n\n### PreviewCard\n\n- Set `pointer-events: none` style on backdrops when hoverable (#1351) @atomiks\n- `actionsRef` prop (#1236) @atomiks\n\n### RadioGroup\n\n- Fix `Form`/`Field` validation integration (#1448) @atomiks\n- Handle modifier keys (#1529) @mj12albert\n\n### Select\n\n- Fix selection on outside press on Firefox with modal prop (#1573) @atomiks\n- Improve `ScrollArrow` behavior (#1564) @atomiks\n- Ensure switching controlled value to `null` updates `Select.Value` label (#1561) @atomiks\n- Pass `value` as second argument to function children `Select.Value` (#1562) @atomiks\n- Fix focus jump while hovering while navigating with keyboard (#1563) @atomiks\n- Fix disabled state changing (#1526) @mj12albert\n- `actionsRef` prop (#1236) @atomiks\n\n### Slider\n\n- Fix thumb positioning when controlled value violates min/max/step (#1541) @mj12albert\n- Warn when `min` is not less than `max` (#1475) @mj12albert\n- Narrow the type of `value` in callbacks (#1241) @seloner\n\n### Tabs\n\n- Fix keyboard navigation involving disabled Tabs (#1449) @mj12albert\n- Handle modifier keys (#1529) @mj12albert\n\n### Toolbar\n\n- Add Toolbar components (#1349) @mj12albert\n\n### Tooltip\n\n- `actionsRef` prop (#1236) @atomiks\n- Fix `Provider` `delay=0` not being respected (#1416) @atomiks\n\n### useRender\n\n- Add public hook (#1418) @mnajdova\n- Refine docs and APIs (#1551) @atomiks\n\n### Docs\n\n- Fix CSS issues (#1585) @atomiks\n- Clean up old experiments (#1572) @mj12albert\n- Fix SEO site name description (#1520) @oliviertassinari\n- Fix `actionsRef` propTypes (#1460) @atomiks\n- Tooltip guidelines (#1356) @atomiks\n- Update the release instructions (#1444) @michaldudak\n- Mention Progress.Value in API reference (#1429) @aarongarciah\n- Update release instructions (#1417) @michaldudak\n\n### Core\n\n- [code-infra] Polish VS Code DX (#1238) @oliviertassinari\n- [code-infra] Fix build:types not copying on some setups (#1482) @Janpot\n- [Composite] Derive sorted map state (#1489) @atomiks\n- Update release docs and scripts (#1245) @oliviertassinari\n- Export namespaces consistently (#1472) @michaldudak\n- Make `mergeReactProps` work with non-native event handlers (#1440) @michaldudak\n- Remove babel-plugin-istanbul (#1409) @michaldudak\n- Fix stylelint violations (#1422) @michaldudak\n- Misc cleaning (#1579) @atomiks\n- [mergeProps] Convert as a top level import and export publicly (#1535) @mnajdova\n- [test] Fix wrong env skip (#1490) @atomiks\n- [test] Fix PreviewCard test flake (#1487) @atomiks\n- [test] Extract common popup tests (#1358) @michaldudak\n- [test] Verify root exports (#1431) @michaldudak\n- [test] Fix flaky browser tests (#1371) @atomiks\n- [test] Update vitest to ^3 (#1453) @michaldudak\n- [test] Skip flaky FieldRoot tests in real browsers (#1446) @michaldudak\n- [useMergedRefs] Support ref cleanup functions (#1553) @atomiks\n- [utils] Change order of args in `mergeReactProps` (#1533) @mnajdova\n\n## v1.0.0-alpha.6\n\n_Feb 6, 2025_\n\n### AlertDialog\n\n- `onOpenChangeComplete` prop (#1305) @atomiks\n- Fix jump with `scroll-behavior` style (#1343) @atomiks\n\n### Avatar\n\n- Add Avatar component (#1210) @acomanescu\n\n### Checkbox\n\n- Avoid applying `hidden` attr when `keepMounted=true` for indicators (#1329) @onehanddev\n\n### Dialog\n\n- Remove `modal={open}` state (#1352) @atomiks\n- Support multiple non-nested modal backdrops (#1327) @atomiks\n- Fix missing `id`s on Title and Description (#1326) @mj12albert\n- `onOpenChangeComplete` prop (#1305) @atomiks\n- Fix jump with `scroll-behavior` style (#1343) @atomiks\n\n### Field\n\n- Respect `validationMode` (#1053) @atomiks\n- Add `filled` and `focused` style hooks (#1341) @atomiks\n\n### Form\n\n- Fix focusing of invalid field controls on errors prop change (#1364) @atomiks\n\n### Menu\n\n- Avoid applying `hidden` attr when `keepMounted=true` for indicators (#1329) @onehanddev\n- Support submenus with `openOnHover` prop (#1338) @atomiks\n- Fix iPad detection when applying scroll lock (#1342) @mj12albert\n- `onOpenChangeComplete` prop (#1305) @atomiks\n- Fix jump with `scroll-behavior` style (#1343) @atomiks\n- Add `OffsetFunction` for `sideOffset` and `alignOffset` (#1223) @atomiks\n- Ensure `keepMounted` is a private param on `Positioner` (#1410) @atomiks\n\n### Popover\n\n- `onOpenChangeComplete` prop (#1305) @atomiks\n- Add `OffsetFunction` for `sideOffset` and `alignOffset` (#1223) @atomiks\n- Ensure `keepMounted` is a private param on `Positioner` (#1410) @atomiks\n\n### PreviewCard\n\n- `onOpenChangeComplete` prop (#1305) @atomiks\n- Add `OffsetFunction` for `sideOffset` and `alignOffset` (#1223) @atomiks\n- Ensure `keepMounted` is a private param on `Positioner` (#1410) @atomiks\n\n### Progress\n\n- Add `format` prop and `Value` component (#1355) @mj12albert\n\n### Radio\n\n- Avoid applying `hidden` attr when `keepMounted=true` for indicators (#1329) @onehanddev\n\n### Select\n\n- `onOpenChangeComplete` prop (#1305) @atomiks\n- Fix jump with `scroll-behavior` style (#1343) @atomiks\n- Add `OffsetFunction` for `sideOffset` and `alignOffset` (#1223) @atomiks\n- Ensure `keepMounted` is a private param on `Positioner` (#1410) @atomiks\n\n### Slider\n\n- Fix thumb positioning (#1411) @mj12albert\n\n### Tabs\n\n- Fix being able to activate a disabled tab (#1359) @michaldudak\n- Fix tabs activating incorrectly on non-primary button clicks (#1318) @mj12albert\n\n### Tooltip\n\n- `onOpenChangeComplete` prop (#1305) @atomiks\n- Add `OffsetFunction` for `sideOffset` and `alignOffset` (#1223) @atomiks\n- Ensure `keepMounted` is a private param on `Positioner` (#1410) @atomiks\n\n## v1.0.0-alpha.5\n\n_Jan 10, 2025_\n\n### AlertDialog\n\n- **Breaking change:** Require `Portal` part.\n  The AlertDialog must explicitly include the Portal part wrapping the Popup.\n  The `keepMounted` prop was removed from the Popup.\n  It's only present on the Portal part.\n  [#1222](https://github.com/mui/base-ui/pull/1222) @atomiks\n- Don't call `onNestedDialogOpen` when unmounting a closed nested dialog [#1280](https://github.com/mui/base-ui/pull/1280) @mj12albert\n- Fix the nesting of different dialogs [#1167](https://github.com/mui/base-ui/pull/1167) @mnajdova\n- Remove `useFloating` call from the Popup [#1300](https://github.com/mui/base-ui/pull/1300) @michaldudak\n- Set `pointer-events` on `InternalBackdrop` based on `open` state [#1221](https://github.com/mui/base-ui/pull/1221) @atomiks\n- Use internal backdrop for pointer modality [#1161](https://github.com/mui/base-ui/pull/1161) @atomiks\n\n### Dialog\n\n- **Breaking change:** Require `Portal` part.\n  The Dialog must explicitly include the Portal part wrapping the Popup.\n  The `keepMounted` prop was removed from the Popup.\n  It's only present on the Portal part.\n  [#1222](https://github.com/mui/base-ui/pull/1222) @atomiks\n- Don't call `onNestedDialogOpen` when unmounting a closed nested dialog [#1280](https://github.com/mui/base-ui/pull/1280) @mj12albert\n- Fix the nesting of different dialogs [#1167](https://github.com/mui/base-ui/pull/1167) @mnajdova\n- Remove `useFloating` call from the Popup [#1300](https://github.com/mui/base-ui/pull/1300) @michaldudak\n- Set `pointer-events` on `InternalBackdrop` based on `open` state [#1221](https://github.com/mui/base-ui/pull/1221) @atomiks\n- Use internal backdrop for pointer modality [#1161](https://github.com/mui/base-ui/pull/1161) @atomiks\n\n### Menu\n\n- **Breaking change:** Require `Portal` part.\n  The Menu must explicitly include the Portal part wrapping the Positioner.\n  The `keepMounted` prop was removed from the Positioner.\n  It's only present on the Portal part.\n  [#1222](https://github.com/mui/base-ui/pull/1222) @atomiks\n- Apply `aria-hidden` to `Arrow` parts [#1196](https://github.com/mui/base-ui/pull/1196) @atomiks\n- Fix `focusableWhenDisabled` components [#1313](https://github.com/mui/base-ui/pull/1313) @mj12albert\n- Fix `openOnHover` issues [#1191](https://github.com/mui/base-ui/pull/1191) @atomiks\n- Fix closing the menu when clicking on checkboxitem/radioitem [#1301](https://github.com/mui/base-ui/pull/1301) @michaldudak\n- Fix Enter key preventDefault when rendering links [#1251](https://github.com/mui/base-ui/pull/1251) @mj12albert\n- Handle pseudo-element bounds in mouseup detection [#1250](https://github.com/mui/base-ui/pull/1250) @atomiks\n- Set `pointer-events` on `InternalBackdrop` based on `open` state [#1221](https://github.com/mui/base-ui/pull/1221) @atomiks\n- Use internal backdrop for pointer modality [#1161](https://github.com/mui/base-ui/pull/1161) @atomiks\n\n### NumberField\n\n- Correctly handle quick touches [#1294](https://github.com/mui/base-ui/pull/1294) @atomiks\n\n### Popover\n\n- **Breaking change:** Require `Portal` part.\n  The Popover must explicitly include the Portal part wrapping the Positioner.\n  The `keepMounted` prop was removed from the Positioner.\n  It's only present on the Portal part.\n  [#1222](https://github.com/mui/base-ui/pull/1222) @atomiks\n- Apply `aria-hidden` to `Arrow` parts [#1196](https://github.com/mui/base-ui/pull/1196) @atomiks\n- Fix PopoverTrigger and TooltipTrigger prop types [#1209](https://github.com/mui/base-ui/pull/1209) @mnajdova\n\n### PreviewCard\n\n- **Breaking change:** Require `Portal` part.\n  The PreviewCard must explicitly include the Portal part wrapping the Positioner.\n  The `keepMounted` prop was removed from the Positioner.\n  It's only present on the Portal part.\n  [#1222](https://github.com/mui/base-ui/pull/1222) @atomiks\n- Apply `aria-hidden` to `Arrow` parts [#1196](https://github.com/mui/base-ui/pull/1196) @atomiks\n- Use `FloatingPortalLite` [#1278](https://github.com/mui/base-ui/pull/1278) @atomiks\n\n### Progress\n\n- Set zero width when value is zero [#1204](https://github.com/mui/base-ui/pull/1204) @mj12albert\n\n### ScrollArea\n\n- Differentiate `x`/`y` orientation `data-scrolling` [#1188](https://github.com/mui/base-ui/pull/1188) @atomiks\n- Read `DirectionProvider` and use logical positioning CSS props [#1194](https://github.com/mui/base-ui/pull/1194) @mj12albert\n\n### Select\n\n- **Breaking change:** Require `Portal` part.\n  The Select must explicitly include the Portal part wrapping the Positioner.\n  The `keepMounted` prop was removed from the Positioner.\n  It's only present on the Portal part.\n  [#1222](https://github.com/mui/base-ui/pull/1222) @atomiks\n- Allow `id` to be passed to trigger [#1174](https://github.com/mui/base-ui/pull/1174) @atomiks\n- Fallback to standard positioning when pinch-zoomed in Safari [#1139](https://github.com/mui/base-ui/pull/1139) @atomiks\n- Fix `focusableWhenDisabled` components [#1313](https://github.com/mui/base-ui/pull/1313) @mj12albert\n- Fix highlight flash on Safari [#1233](https://github.com/mui/base-ui/pull/1233) @atomiks\n- Handle pseudo-element bounds in mouseup detection [#1250](https://github.com/mui/base-ui/pull/1250) @atomiks\n- Use internal backdrop for pointer modality [#1161](https://github.com/mui/base-ui/pull/1161) @atomiks\n\n### Separator\n\n- Support vertical orientation [#1304](https://github.com/mui/base-ui/pull/1304) @mj12albert\n\n### Slider\n\n- Ensure `onValueCommitted` is called with the same value as latest `onValueChange` [#1296](https://github.com/mui/base-ui/pull/1296) @mj12albert\n- Replace internal map with `Composite` metadata [#1082](https://github.com/mui/base-ui/pull/1082) @mj12albert\n- Set `position: relative` on range slider indicator [#1175](https://github.com/mui/base-ui/pull/1175) @mj12albert\n- Use un-rounded values to position thumbs [#1219](https://github.com/mui/base-ui/pull/1219) @mj12albert\n\n### Tabs\n\n- Expose width/height state in tabs indicator [#1288](https://github.com/mui/base-ui/pull/1288) @aarongarciah\n\n### Tooltip\n\n- **Breaking change:** Require `Portal` part.\n  The Tooltip must explicitly include the Portal part wrapping the Positioner.\n  The `keepMounted` prop was removed from the Positioner.\n  It's only present on the Portal part.\n  [#1222](https://github.com/mui/base-ui/pull/1222) @atomiks\n- Apply `aria-hidden` to `Arrow` parts [#1196](https://github.com/mui/base-ui/pull/1196) @atomiks\n- Fix PopoverTrigger and TooltipTrigger prop types [#1209](https://github.com/mui/base-ui/pull/1209) @mnajdova\n- Use `FloatingPortalLite` [#1278](https://github.com/mui/base-ui/pull/1278) @atomiks\n\n## v1.0.0-alpha.4\n\n_Dec 17, 2024_\n\nPublic alpha launch 🐣 Merry Xmas! 🎁\n"
  },
  {
    "path": "CHANGELOG.old.md",
    "content": "# Non-public versions\n\n## v1.0.0-alpha.3\n\n_Oct 7, 2024_\n\nA big thanks to the 7 contributors who made this release possible. Here are some highlights ✨:\n\n- ⭐ We added several new components: CheckboxGroup, RadioGroup, Form, Separator\n- ⭐ Menu has new parts: CheckboxItem, RadioItem, and Group\n\n### `@base_ui/react@1.0.0-alpha.3`\n\n- [AlertDialog] Move types to namespaces (#591) @michaldudak\n- [Checkbox] Modernize implementation (#594) @atomiks\n- [CheckboxGroup] Implement components and hooks (#458) @atomiks\n- [Collapsible] Add Collapsible components and hooks (#481) @mj12albert\n- [Dialog] Move types to namespaces (#697) @michaldudak\n- [Dialog][Collapsible] Fix style prop merging (#641) @michaldudak\n- [Form] Create new `Form` component (#589) @atomiks\n- [Menu] CheckboxItem component (#533) @michaldudak\n- [Menu] Do not select an item when space is pressed during typeahead (#542) @michaldudak\n- [Menu] Fix custom anchor positioning (#609) @michaldudak\n- [Menu] Group and Separator components (#535) @michaldudak\n- [Menu] Increase test timeout (#592) @michaldudak\n- [Menu] RadioItem component (#534) @michaldudak\n- [Menu] Remove the unused prop (#647) @michaldudak\n- [Menu] Remove wrong default value from docs (#549) @sai6855\n- [Menu][Popover][PreviewCard][Tooltip] Add default value as `clippingAncestors` to collisionBoundary prop (#580) @sai6855\n- [NumberField] Modernize implementation (#590) @atomiks\n- [Popover] Modernize implementation (#607) @atomiks\n- [PreviewCard] Modernize implementation (#626) @atomiks\n- [RadioGroup] Create new `RadioGroup` component (#487) @atomiks\n- [Slider] Fix Home / End regression (#526) @sai6855\n- [Tooltip] Modernize implementation (#606) @atomiks\n- [useButton] Modernize implementation (#643) @michaldudak\n- [useScrollLock] Avoid scrollbar layout shift issues (#604) @atomiks\n\n### Docs\n\n- [docs] Fix 301 redirections in docs @oliviertassinari\n- [docs] Make the readme specific to @base_ui/react (#633) @michaldudak\n- [docs] Copy vale-action.yml from main repo @oliviertassinari\n- [docs] Fix 301 to chromium (#636) @oliviertassinari\n- [docs] Avoid dead links in demos (#610) @oliviertassinari\n- [docs] Fix rel attribute on edit GitHub links (#614) @oliviertassinari\n- [docs] Fix pnpm docs:link-check script (#552) @oliviertassinari\n- [docs] Fix Stack Overflow issue canned response @oliviertassinari\n- [docs] Fix outdated link to support page @oliviertassinari\n- [docs] Clarify contribution guide references @oliviertassinari\n\n### Core\n\n- [code-infra] Remove custom playwright installation steps (#646) @Janpot\n- [core] Fix 301 link to Next.js and git diff @oliviertassinari\n- [core] Fix package.json repository rule @oliviertassinari\n- [core] MUI X repository moved to a new location @oliviertassinari\n- [core] React 19 compatibility (#605) @michaldudak\n- [core] Reference `ownerDocument` (#660) @atomiks\n- [core] Remove 'use client' from index files (#331) @michaldudak\n- [core] Remove /.yarn (#596) @oliviertassinari\n- [core] Remove Material UI dependency (#585) @michaldudak\n- [core] Remove the legacy components from the repo (#584) @michaldudak\n- [core] Rename positionStrategy to positionMethod (#704) @michaldudak\n- [docs-infra] Fix double // (#613) @oliviertassinari\n- [docs-infra] Strengthen CSP (#595) @oliviertassinari\n- [infra] Adds reusable workflow for closing message on issues (#598) @michelengelen\n- [infra] Adds reusable workflow for issue cleanup (#597) @michelengelen\n- [infra] Fix line break in Stack Overflow message @oliviertassinari\n- [test] Fix tests on Safari (#546) @michaldudak\n- [test] Fix the test_types_next CI job (#703) @michaldudak\n- [test] Improve visual screenshot canva (#708)\n- [test] Point Istanbul to correct URL (#657) @sai6855\n- [test] Run Browserstack tests on master only (#578) @michaldudak\n- [test] Use `waitFor` instead of fixed timeout in tests (#632) @michaldudak\n- [website] Improve utm_source strategy @oliviertassinari\n- [website] Modernize the Base UI website (#538) @michaldudak\n\nAll contributors of this release in alphabetical order: @atomiks, @Janpot, @michaldudak, @michelengelen, @mj12albert, @oliviertassinari, @sai6855\n\n## v1.0.0-alpha.2\n\n_Aug 19, 2024_\n\nA big thanks to the 10 contributors who made this release possible. Here are some highlights ✨:\n\n⭐ We added many new components: AlertDialog, Dialog, Field, Menu, Popover, PreviewCard, Progress, Slider, and Tooltip.\n\n### `@base_ui/react@1.0.0-alpha.2`\n\n- [Checkbox] Fix checked change when clicking button with wrapping label (#467) @atomiks\n- [Dialog] Create new component and hook (#372) @michaldudak\n- [Field] Create new Field components (#477) @atomiks\n- [Menu] Overhaul the component API (#468) @michaldudak\n- [NumberField] Fix tests on non-English locale machines (#524) @michaldudak\n- [NumberField] Rename `onChange` prop to `onValueChange` (#464) @atomiks\n- [Popover] Component and Hook (#381) @atomiks\n- [Popover] Fix `keepMounted` focus management (#489) @atomiks\n- [Popover] Wait for focus to settle in tests (#491) @michaldudak\n- [PreviewCard] Create new component (#469) @atomiks\n- [PreviewCard] Fix Firefox browser hang (#490) @atomiks\n- [Progress] New `Progress` components (#470) @mj12albert\n- [Slider] improve `disabled` prop description (#527) @sai6855\n- [Slider] New Slider components and hook (#373) @mj12albert\n- [Switch/Checkbox] Rename `onChange` prop to `onCheckedChange` (#465) @atomiks\n- [Tabs] Fix indicator tests (#379) @michaldudak\n- [Tooltip] Component and Hook (#264) @atomiks\n- [Tooltip] Fix animations (#426) @atomiks\n- [useCompoundParent] Display `displayName` only in dev (#525) @sai6855\n\n### Docs\n\n- [docs] Add badges like in Material UI @oliviertassinari\n- [docs] Add the logo to the README (#448) @danilo-leal\n- [docs] Convert alpha component docs to new docs template (#392) @colmtuite\n- [docs] Correct Bundlephobia links (#419) @michaldudak\n- [docs] Fix page description line break @oliviertassinari\n- [docs] Fix the X link (#450) @michaldudak\n- [docs] Fix Vale errors (#492) @oliviertassinari\n- [docs] Prepare security table for once it has its first release (#536) @oliviertassinari\n- [docs] Update twitter.com to x.com @oliviertassinari\n- [docs][Tooltip] Use the correct version of ComponentLinkHeader (#425) @michaldudak\n\n### Core\n\n- [code-infra] Fix pnpm version in package.json engines (#409) @Janpot\n- [code-infra] Propagate API docs builder package interface changes (#478) @LukasTy\n- [code-infra] Remove raw-loader (#404) @michaldudak\n- [code-infra] Use shared .stylelintrc.js config (#415) @oliviertassinari\n- [core] Add `trackAnchor` prop for anchor positioning (#519) @atomiks\n- [core] Add `useAnchorPositioning` Hook (#461) @atomiks\n- [core] Add `useTransitionStatus` and `useExecuteIfNotAnimated` Hooks (#396) @atomiks\n- [core] Add codeowners file (#447) @michaldudak\n- [core] Allow Renovate to update pnpm (#446) @michaldudak\n- [core] Encapsulate the common rendering logic in `useComponentRenderer` (#408) @michaldudak\n- [core] Fix event naming convention @oliviertassinari\n- [core] Improve performance of `mergeProps` (#456) @marcpachecog\n- [core] Improve Tooltip and Popover consistency (#463) @atomiks\n- [core] Link GH issue for import/prefer-default-export @oliviertassinari\n- [core] Make pnpm version permissive (#529) @atomiks\n- [core] Move hooks under component directories (#405) @michaldudak\n- [core] Move legacy components to a subdirectory (#410) @michaldudak\n- [core] Normalize rest / other to match the most common used @oliviertassinari\n- [core] Refactor animation hooks (#417) @atomiks\n- [core] Remove sources of old components we don't intend to support (#474) @michaldudak\n- [core] Simpler pnpm dedupe error message to act on @oliviertassinari\n- [core] Upgrade to core-js v3 (#418) @atomiks\n- [core] Verify types in test code (#457) @michaldudak\n- [dependencies] Do not try to update ESLint (#515) @michaldudak\n- [docs-infra] Fix a stylelint issue (#421) @oliviertassinari\n- [docs-infra] Integrate the latest @mui/docs (#378) @michaldudak\n- [test] Clean up and unify test code (#532) @michaldudak\n- [test] Update test-utils and remove enzyme (#473) @michaldudak\n- [test] Use internal-test-utils from npm (#424) @michaldudak\n- [typescript] Add `type` to export statements (#544) @michaldudak\n- [website] Fix /base-ui/ code duplication (#416) @oliviertassinari- [infra] Add support donation button @oliviertassinari\n- [website] Redirect to an existing page on dev (#445) @michaldudak\n\nAll contributors of this release in alphabetical order: @atomiks, @colmtuite, @danilo-leal, @Janpot, @LukasTy, @marcpachecog, @michaldudak, @mj12albert, @oliviertassinari, @sai6855\n\n## v1.0.0-alpha.1\n\n<!-- generated comparing v1.0.0-alpha.0..master -->\n\n_May 14, 2024_\n\nA big thanks to the 4 contributors who made this release possible. Here are some highlights ✨:\n\n⭐ We overhauled the Tabs components' API and added a few exciting new features to them (#245).\n\n### `@base_ui/react@1.0.0-alpha.1`\n\n- [NumberField] Fix failing browser tests (#317) @atomiks\n- [Tabs] Overhaul Tabs API (#245) @michaldudak\n\n## Docs\n\n- Consistent arrow style (#397) @oliviertassinari\n- Link to docs on PRs (#394) @oliviertassinari\n- Fix Netlify preview 301 JS assets @oliviertassinari\n- Reference shared code from the Core monorepo (#326) @michaldudak\n- Remove or hide pages describing the old API (#323) @michaldudak\n- Prepare React 19 (#393) @oliviertassinari\n- Fix incorrect Tabs import instructions (#401) @michaldudak\n\n## Core\n\n- Rely on @mui/monorepo/.eslintrc (#352) @oliviertassinari\n- Use Base UI repo in the release changelog script (#355) @michaldudak\n- Export NumberField components and hooks (#400) @atomiks\n- Config cleanup (#377) @michaldudak\n- Make dependency updates less frequent (#375) @michaldudak\n- Update @mui deps to next (#354) @atomiks\n- Remove legacy dependencies and settings (#332) @michaldudak\n- Remove redundant Next.js config (#333) @oliviertassinari\n- Use the root dependency (#334) @oliviertassinari\n- Clean up unnecessary files (#324) @michaldudak\n- Describe how to publish the docs (#320) @michaldudak\n- Change the references to the Material UI repo in the releaseTag script (#319) @michaldudak\n- Port e2e infra back to Base UI (#395) @oliviertassinari\n- Fix Firefox browser version in karma profile config and resolve user-event test TODOs (#353) @ZeeshanTamboli\n- Update Karma config (#322) @michaldudak\n- Use absolute URLs for non-Base pages (#321) @michaldudak\n\nAll contributors of this release in alphabetical order: @atomiks, @michaldudak, @oliviertassinari, @ZeeshanTamboli\n\n## v1.0.0-alpha.0\n\n_Apr 15, 2024_\n\nThis is an initial release of Base UI as the @base_ui/react package.\nIt features the Checkbox, Number Field, and Switch as the first components to be rewritten with a fresh new API. 🚀\n\n### `@base_ui/react@1.0.0-alpha.0`\n\n- [Checkbox] Component and Hook (#159) @atomiks\n- [NumberField] Component and Hook (#186) @atomiks\n- [Switch] Implement the component-per-node API (#135) @michaldudak\n- [core] Rename package to @base_ui/react (#287) @michaldudak\n- [core] Exclude legacy components from the package (#288) @michaldudak\n"
  },
  {
    "path": "CLAUDE.md",
    "content": "@AGENTS.md\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to Base UI\n\n## Base UI vs. MUI organization\n\nBase UI is an open-source project of the MUI organization. The repositories are part of the same codebase.\n`mui/base-ui` imports the code infrastructure from [`mui/material-ui`](https://github.com/mui/material-ui).\nYou can find the \"contributing\" guide for the main repository in [mui/material-ui/CONTRIBUTING.md](https://github.com/mui/material-ui/blob/HEAD/CONTRIBUTING.md).\n\nMost of the content in the main repository \"contributing\" guide applies to this repository.\nThere are, however, a few differences:\n\n- The default GitHub branch might be different.\n- The local documentation site opens on a different port: 3005 instead of 3000.\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2019 Material-UI SAS\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Base UI\n\nFrom the creators of Radix, Floating UI, and Material UI, Base UI is an unstyled UI component library for building accessible user interfaces.\n\n---\n\n## Documentation\n\nTo get started, check out the [Base UI documentation](https://base-ui.com/react/overview/quick-start).\n\n## Contributing\n\nRead our [contributing guide](/CONTRIBUTING.md) to learn about our development process, how to propose bug fixes and improvements, and how to build and test your changes.\n\n## Releases\n\nTo see the latest updates, check out the [releases](https://base-ui.com/react/overview/releases).\n\n## Community\n\n- **Discord** For community support, questions, and tips, join our [Discord](https://discord.gg/g6C3hUtuxz).\n- **X** To stay up-to-date on new releases and announcements follow [Base UI on X](https://x.com/base_ui).\n- **Bluesky** We're also on [Bluesky](https://bsky.app/profile/base-ui.com).\n\n## Team\n\n- **Colm Tuite** (Radix) [@colmtuite](https://x.com/colmtuite)\n- **James Nelson** (Floating UI) [@atomiksdev](https://x.com/atomiksdev)\n- **Michał Dudak** (Material UI) [@michaldudak](https://x.com/michaldudak)\n- **Marija Najdova** (Material UI + Fluent UI) [@marijanajdova](https://x.com/marijanajdova)\n- **Albert Yu** (Material UI) [@mj12albert](https://github.com/mj12albert)\n- **Lukas Tyla** (Material UI) [@LukasTy](https://github.com/LukasTy)\n\n## License\n\nThis project is licensed under the terms of the [MIT license](/LICENSE).\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security policy\n\n## Supported versions\n\nThe versions of the project that are currently supported with security updates.\n\n| Base UI version | Release    | Supported                       |\n| --------------: | :--------- | :------------------------------ |\n|           1.0.0 | 2025-12-11 | :white_check_mark: Stable major |\n|          <1.0.0 | 2024-12-17 | ❌                              |\n\n## Reporting a vulnerability\n\nYou can report a vulnerability by contacting us via email at [security@mui.com](mailto:security@mui.com).\n"
  },
  {
    "path": "babel.config.mjs",
    "content": "import getBaseConfig from '@mui/internal-code-infra/babel-config';\nimport * as path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst dirname = path.dirname(fileURLToPath(import.meta.url));\n\nconst errorCodesPath = path.join(dirname, 'docs/src/error-codes.json');\n\nexport default function getBabelConfig(api) {\n  const baseConfig = getBaseConfig(api);\n\n  const plugins = [\n    [\n      '@mui/internal-babel-plugin-minify-errors',\n      {\n        missingError: 'annotate',\n        runtimeModule: '#formatErrorMessage',\n        detection: 'opt-out',\n        errorCodesPath,\n      },\n    ],\n  ];\n\n  const displayNamePlugin = baseConfig.plugins.find(\n    (p) => p[2] === '@mui/internal-babel-plugin-display-name',\n  );\n  displayNamePlugin[1].allowedCallees ??= {};\n  displayNamePlugin[1].allowedCallees['@base-ui/utils/fastHooks'] = [\n    'fastComponent',\n    'fastComponentRef',\n  ];\n\n  return {\n    ...baseConfig,\n    plugins: [...baseConfig.plugins, ...plugins],\n    overrides: [\n      {\n        exclude: /\\.test\\.(js|ts|tsx)$/,\n        plugins: ['@babel/plugin-transform-react-constant-elements'],\n      },\n    ],\n    env: {\n      test: {\n        sourceMaps: 'both',\n      },\n    },\n  };\n}\n"
  },
  {
    "path": "docs/README.md",
    "content": "# Docs\n\nThis is the documentation of Base UI.\n\nTo start the docs site in development mode, from the project root, run:\n\n```bash\npnpm start\n```\n\nIf you do not have pnpm installed, select your OS and follow the instructions on the [pnpm website](https://pnpm.io/installation).\n\nPackage managers other than pnpm (like npm or Yarn) are not supported and don't work.\n\n## How can I add a new demo to the documentation?\n\n[You can follow this guide](../CONTRIBUTING.md)\non how to get started contributing to Base UI.\n\n## Error code extraction\n\nErrors in production are minified. They are extracted out of the source code by running the command\n\n```bash\npnpm extract-error-codes\n```\n\nThis updates the `./src/error-codes.json` file with the newly extracted errors.\n\nImportant: If you just altered the text of an error, you are allowed to update the existing error code with the new text in `./src/error-codes.json`, but only under the following conditions:\n\n1. There hasn't been an update to the semantic meaning of the error message. Error codes need to outlive Base UI versions, so the same code must mean the same thing across versions.\n2. There hasn't been a change in parameters, no added and no removed.\n\nIn both of those cases, always create a new error code lline in `./src/error-codes.json`.\n"
  },
  {
    "path": "docs/next.config.mjs",
    "content": "// @ts-check\nimport * as path from 'path';\nimport * as url from 'url';\nimport * as fs from 'fs';\nimport { withDeploymentConfig } from '@mui/internal-docs-infra/withDocsInfra';\nimport transformMarkdownMetadata from '@mui/internal-docs-infra/pipeline/transformMarkdownMetadata';\nimport transformMarkdownRelativePaths from '@mui/internal-docs-infra/pipeline/transformMarkdownRelativePaths';\nimport nextMdx from '@next/mdx';\nimport rehypeExtractToc from '@stefanprobst/rehype-extract-toc';\nimport remarkGfm from 'remark-gfm';\nimport remarkTypography from 'remark-typography';\nimport { rehypeQuickNav } from 'docs/src/components/QuickNav/rehypeQuickNav.mjs';\nimport { rehypeConcatHeadings } from 'docs/src/components/QuickNav/rehypeConcatHeadings.mjs';\nimport { rehypeKbd } from 'docs/src/components/Kbd/rehypeKbd.mjs';\nimport { rehypeReference } from 'docs/src/components/ReferenceTable/rehypeReference.mjs';\nimport { rehypeSyntaxHighlighting } from 'docs/src/syntax-highlighting/index.mjs';\nimport { rehypeSlug } from 'docs/src/components/QuickNav/rehypeSlug.mjs';\nimport { rehypeSubtitle } from 'docs/src/components/Subtitle/rehypeSubtitle.mjs';\n\nconst currentDirectory = url.fileURLToPath(new URL('.', import.meta.url));\nconst workspaceRoot = path.resolve(currentDirectory, '../');\n\nconst withMdx = nextMdx({\n  options: {\n    remarkPlugins: [\n      remarkGfm,\n      [\n        transformMarkdownMetadata,\n        {\n          titleSuffix: ' · Base UI',\n          extractToIndex: {\n            include: ['src/app/react'],\n            exclude: [\n              'src/app/careers',\n              'src/app/production-error',\n              'src/app/test',\n              'src/app/experiments',\n              'src/app/playground',\n            ],\n            baseDir: path.dirname(url.fileURLToPath(import.meta.url)),\n            useVisibleDescription: true,\n          },\n        },\n      ],\n      remarkTypography,\n      transformMarkdownRelativePaths,\n    ],\n    rehypePlugins: [\n      rehypeReference,\n      ...rehypeSyntaxHighlighting,\n      rehypeSlug,\n      rehypeConcatHeadings,\n      rehypeExtractToc,\n      rehypeQuickNav,\n      rehypeSubtitle,\n      rehypeKbd,\n    ],\n  },\n});\n\n/**\n * @returns {{version: string}}\n */\nfunction loadPackageJson() {\n  const pkgContent = fs.readFileSync(path.resolve(workspaceRoot, 'package.json'), 'utf8');\n  return JSON.parse(pkgContent);\n}\n\nconst rootPackage = loadPackageJson();\n\n/** @type {import('next').NextConfig} */\nconst nextConfig = {\n  trailingSlash: false,\n  pageExtensions: ['mdx', 'tsx'],\n  turbopack: {\n    rules: {\n      './src/app/sitemap/index.ts': {\n        as: '*.ts',\n        loaders: ['@mui/internal-docs-infra/pipeline/loadPrecomputedSitemap'],\n      },\n      './src/app/**/demos/*/index.ts': {\n        as: '*.ts',\n        loaders: ['@mui/internal-docs-infra/pipeline/loadPrecomputedCodeHighlighter'],\n      },\n      './src/demo-data/*/index.ts': {\n        as: '*.ts',\n        loaders: ['@mui/internal-docs-infra/pipeline/loadPrecomputedCodeHighlighter'],\n      },\n    },\n  },\n  webpack: (config, { defaultLoaders }) => {\n    // for production builds\n    config.module.rules.push({\n      test: /[/\\\\\\\\]sitemap[/\\\\\\\\]index\\.ts$/,\n      use: [defaultLoaders.babel, '@mui/internal-docs-infra/pipeline/loadPrecomputedSitemap'],\n    });\n    config.module.rules.push({\n      test: /[/\\\\\\\\]demos[/\\\\\\\\][^/\\\\\\\\]+[/\\\\\\\\]index\\.ts$/,\n      use: [\n        defaultLoaders.babel,\n        '@mui/internal-docs-infra/pipeline/loadPrecomputedCodeHighlighter',\n      ],\n    });\n    config.module.rules.push({\n      test: /[/\\\\\\\\]src[/\\\\\\\\]demo-data[/\\\\\\\\][^/\\\\\\\\]+[/\\\\\\\\]index\\.ts$/,\n      use: [\n        defaultLoaders.babel,\n        '@mui/internal-docs-infra/pipeline/loadPrecomputedCodeHighlighter',\n      ],\n    });\n\n    return config;\n  },\n  env: {\n    // docs-infra\n    LIB_VERSION: rootPackage.version,\n    SOURCE_CODE_REPO: 'https://github.com/mui/base-ui',\n  },\n  ...(process.env.NODE_ENV === 'production' && { distDir: 'export', output: 'export' }),\n  devIndicators: false,\n  experimental: {\n    globalNotFound: true,\n  },\n};\n\nconst mergedConfig = withMdx(withDeploymentConfig(nextConfig));\n\nif (!process.env.CI) {\n  delete mergedConfig.experimental?.cpus;\n}\n\nexport default mergedConfig;\n"
  },
  {
    "path": "docs/package.json",
    "content": "{\n  \"name\": \"docs\",\n  \"private\": true,\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"build\": \"rimraf ./export && cross-env NODE_ENV=production NODE_OPTIONS=--max_old_space_size=8192 next build --webpack && pnpm link-check\",\n    \"build:clean\": \"rimraf .next && pnpm build\",\n    \"dev\": \"next dev --webpack --port 3005\",\n    \"deploy\": \"git fetch upstream master && git push -f upstream FETCH_HEAD:docs-v1\",\n    \"serve\": \"serve ./export -l 3010\",\n    \"docs-infra\": \"docs-infra\",\n    \"validate\": \"pnpm internal-validate --command 'pnpm validate'\",\n    \"internal-validate\": \"docs-infra validate --useVisibleDescription\",\n    \"typescript\": \"tsc -b tsconfig.json\",\n    \"link-check\": \"tsx ./scripts/reportBrokenLinks.mts\",\n    \"generate-llms\": \"node --experimental-strip-types ./scripts/generateLlmTxt/index.mjs\"\n  },\n  \"dependencies\": {\n    \"@base-ui/react\": \"workspace:*\",\n    \"@base-ui/utils\": \"workspace:*\",\n    \"@date-fns/tz\": \"^1.4.1\",\n    \"@mdx-js/loader\": \"^3.1.1\",\n    \"@mdx-js/react\": \"^3.1.1\",\n    \"@mui/internal-docs-infra\": \"0.6.1-canary.3\",\n    \"@next/mdx\": \"^16.1.6\",\n    \"@react-spring/web\": \"^10.0.3\",\n    \"@stefanprobst/rehype-extract-toc\": \"^3.0.0\",\n    \"@tanstack/react-form\": \"^1.28.4\",\n    \"@tanstack/react-virtual\": \"^3.13.21\",\n    \"@types/mdx\": \"^2.0.13\",\n    \"clipboard-copy\": \"^4.0.1\",\n    \"clsx\": \"^2.1.1\",\n    \"date-fns\": \"^4.1.0\",\n    \"es-toolkit\": \"^1.45.1\",\n    \"estree-util-value-to-estree\": \"^3.5.0\",\n    \"globby\": \"^16.1.1\",\n    \"hast\": \"^1.0.0\",\n    \"hast-util-heading-rank\": \"^3.0.0\",\n    \"hast-util-to-string\": \"^3.0.1\",\n    \"lucide-react\": \"^0.577.0\",\n    \"lz-string\": \"^1.5.0\",\n    \"match-sorter\": \"^8.2.0\",\n    \"next\": \"16.1.7\",\n    \"postcss\": \"^8.5.8\",\n    \"postcss-custom-media\": \"^12.0.1\",\n    \"postcss-import\": \"^16.1.1\",\n    \"radix-ui\": \"^1.4.3\",\n    \"react\": \"^19.2.4\",\n    \"react-dom\": \"^19.2.4\",\n    \"react-error-boundary\": \"6.1.1\",\n    \"react-hook-form\": \"^7.71.2\",\n    \"react-is\": \"^19.2.4\",\n    \"rehype-pretty-code\": \"^0.14.3\",\n    \"remark\": \"^15.0.1\",\n    \"remark-frontmatter\": \"^5.0.0\",\n    \"remark-gfm\": \"^4.0.1\",\n    \"remark-mdx-frontmatter\": \"^5.2.0\",\n    \"remark-parse\": \"^11.0.0\",\n    \"remark-rehype\": \"^11.1.2\",\n    \"remark-typography\": \"0.7.3\",\n    \"scroll-into-view-if-needed\": \"3.1.0\",\n    \"server-only\": \"^0.0.1\",\n    \"shiki\": \"^4.0.2\",\n    \"to-vfile\": \"^8.0.0\",\n    \"unist-util-visit-parents\": \"^6.0.2\",\n    \"vfile-matter\": \"^5.0.1\"\n  },\n  \"devDependencies\": {\n    \"@mdx-js/mdx\": \"3.1.1\",\n    \"@tailwindcss/postcss\": \"4.2.1\",\n    \"@types/gtag.js\": \"0.0.20\",\n    \"@types/hast\": \"3.0.4\",\n    \"@types/node\": \"22.18.13\",\n    \"@types/react\": \"19.2.14\",\n    \"@types/react-dom\": \"19.2.3\",\n    \"@types/unist\": \"3.0.3\",\n    \"@wooorm/starry-night\": \"3.9.0\",\n    \"baseline-browser-mapping\": \"2.10.0\",\n    \"cross-env\": \"10.1.0\",\n    \"mdast-util-mdx-jsx\": \"3.2.0\",\n    \"motion\": \"12.35.2\",\n    \"prettier\": \"3.8.1\",\n    \"react-reconciler\": \"0.33.0\",\n    \"remark-parse\": \"11.0.0\",\n    \"remark-stringify\": \"11.0.0\",\n    \"rimraf\": \"6.1.3\",\n    \"serve\": \"14.2.6\",\n    \"tailwindcss\": \"4.2.1\",\n    \"unified\": \"11.0.5\",\n    \"unist-util-visit\": \"5.1.0\",\n    \"yargs\": \"18.0.0\",\n    \"zod\": \"4.3.6\"\n  }\n}\n"
  },
  {
    "path": "docs/postcss.config.js",
    "content": "module.exports = {\n  plugins: {\n    'postcss-import': {},\n    '@tailwindcss/postcss': {},\n    'postcss-custom-media': {},\n  },\n};\n"
  },
  {
    "path": "docs/public/_headers",
    "content": "/_next/*.js\n  Cache-Control: public, max-age=31536000, immutable\n\n/static/favicon.ico\n  Content-Type: image/x-icon\n\n/performance/*\n  X-Robots-Tag: noindex\n\n/experiments/*\n  X-Robots-Tag: noindex\n\n/*\n  Strict-Transport-Security: max-age=31536000; includeSubDomains; preload\n  # Block usage in iframes.\n  X-Frame-Options: SAMEORIGIN\n  # Force the browser to trust the Content-Type header\n  # https://stackoverflow.com/questions/18337630/what-is-x-content-type-options-nosniff\n  X-Content-Type-Options: nosniff\n  X-XSS-Protection: 1; mode=block\n  Referrer-Policy: strict-origin-when-cross-origin\n  # TODO: progressively reduce the CSP scopes\n  # Start with a wildcard, using https://github.com/mui/toolpad/blob/f4c4eb046b352e4fc00729c3bed605e671b040c4/packages/toolpad-studio/src/server/index.ts#L241\n  Content-Security-Policy: default-src * data: mediastream: blob: filesystem: about: ws: wss: 'unsafe-eval' 'wasm-unsafe-eval' 'unsafe-inline'; script-src * data: blob: 'unsafe-inline' 'unsafe-eval'; script-src-elem * data: blob: 'unsafe-inline'; connect-src * data: blob: 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; media-src * data: blob: 'unsafe-inline'; frame-src * data: blob: ; style-src * data: blob: 'unsafe-inline'; font-src * data: blob: 'unsafe-inline'; frame-ancestors *;\n"
  },
  {
    "path": "docs/public/_redirects",
    "content": "# Link used in the demos, avoid 404\n/drafts / 301\n/inbox* / 301\n/trash / 301\n\n# Playground SPA redirects\n/vite-playground/* /vite-playground/index.html 200\n\n# For links that we can't edit later on, for example hosted in the code published on npm\n/r/discord https://discord.com/invite/g6C3hUtuxz 302\n/r/invalid-render-prop /react/handbook/composition 302\n\n# Legacy redirection\n# Added in chronological order (the last line is the most recent one)\n# To be removed 3+ years after being introduced\n\n# 2025\n\n# Handles error code path redirect to arg based path\n\n# At the time of adding this rule, there was requirement of at max only a single args[] param\n/production-error/:code args\\[\\]=:args /production-error?code=:code&args[]=:args 301\n/production-error/:code /production-error?code=:code 301\n"
  },
  {
    "path": "docs/public/robots.txt",
    "content": "# Algolia-Crawler-Verif: 98C49CAFF7AEED76\n\nUser-agent: *\nAllow: /\n"
  },
  {
    "path": "docs/reference/generated/accordion-header.json",
    "content": "{\n  \"name\": \"AccordionHeader\",\n  \"description\": \"A heading that labels the corresponding panel.\\nRenders an `<h3>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Accordion.Header.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Accordion.Header.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Accordion.Header.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Accordion.Header.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Accordion.Header.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Accordion.Header.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the accordion item is open.\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the accordion item is disabled.\"\n    },\n    \"data-index\": {\n      \"description\": \"Indicates the index of the accordion item.\",\n      \"type\": \"number\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/accordion-item.json",
    "content": "{\n  \"name\": \"AccordionItem\",\n  \"description\": \"Groups an accordion header with the corresponding panel.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"value\": {\n      \"type\": \"any\",\n      \"description\": \"A unique value that identifies this accordion item.\\nIf no value is provided, a unique ID will be generated automatically.\\nUse when controlling the accordion programmatically, or to set an initial\\nopen state.\",\n      \"example\": \"```tsx\\n<Accordion.Root value={['a']}>\\n  <Accordion.Item value=\\\"a\\\" /> // initially open\\n  <Accordion.Item value=\\\"b\\\" /> // initially closed\\n</Accordion.Root>\\n```\"\n    },\n    \"onOpenChange\": {\n      \"type\": \"((open: boolean, eventDetails: Accordion.Item.ChangeEventDetails) => void)\",\n      \"description\": \"Event handler called when the panel is opened or closed.\",\n      \"detailedType\": \"| ((\\n    open: boolean,\\n    eventDetails: Accordion.Item.ChangeEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component should ignore user interaction.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Accordion.Item.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Accordion.Item.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Accordion.Item.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Accordion.Item.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Accordion.Item.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Accordion.Item.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the accordion item is open.\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the accordion item is disabled.\"\n    },\n    \"data-index\": {\n      \"description\": \"Indicates the index of the accordion item.\",\n      \"type\": \"number\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/accordion-panel.json",
    "content": "{\n  \"name\": \"AccordionPanel\",\n  \"description\": \"A collapsible panel with the accordion item contents.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"hiddenUntilFound\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Allows the browser’s built-in page search to find and expand the panel contents.\\n\\nOverrides the `keepMounted` prop and uses `hidden=\\\"until-found\\\"`\\nto hide the element without removing it from the DOM.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Accordion.Panel.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Accordion.Panel.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Accordion.Panel.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Accordion.Panel.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"keepMounted\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to keep the element in the DOM while the panel is closed.\\nThis prop is ignored when `hiddenUntilFound` is used.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Accordion.Panel.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Accordion.Panel.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the accordion panel is open.\"\n    },\n    \"data-orientation\": {\n      \"description\": \"Indicates the orientation of the accordion.\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the accordion item is disabled.\"\n    },\n    \"data-index\": {\n      \"description\": \"Indicates the index of the accordion item.\",\n      \"type\": \"number\"\n    },\n    \"data-starting-style\": {\n      \"description\": \"Present when the panel is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the panel is animating out.\"\n    }\n  },\n  \"cssVariables\": {\n    \"--accordion-panel-height\": {\n      \"description\": \"The accordion panel's height.\",\n      \"type\": \"number\"\n    },\n    \"--accordion-panel-width\": {\n      \"description\": \"The accordion panel's width.\",\n      \"type\": \"number\"\n    }\n  }\n}\n"
  },
  {
    "path": "docs/reference/generated/accordion-root.json",
    "content": "{\n  \"name\": \"AccordionRoot\",\n  \"description\": \"Groups all parts of the accordion.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"defaultValue\": {\n      \"type\": \"Value[]\",\n      \"description\": \"The uncontrolled value of the item(s) that should be initially expanded.\\n\\nTo render a controlled accordion, use the `value` prop instead.\",\n      \"detailedType\": \"Value[] | undefined\"\n    },\n    \"value\": {\n      \"type\": \"Value[]\",\n      \"description\": \"The controlled value of the item(s) that should be expanded.\\n\\nTo render an uncontrolled accordion, use the `defaultValue` prop instead.\",\n      \"detailedType\": \"Value[] | undefined\"\n    },\n    \"onValueChange\": {\n      \"type\": \"((value: Value[], eventDetails: Accordion.Root.ChangeEventDetails) => void)\",\n      \"description\": \"Event handler called when an accordion item is expanded or collapsed.\\nProvides the new value as an argument.\",\n      \"detailedType\": \"| ((\\n    value: Value[],\\n    eventDetails: Accordion.Root.ChangeEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"hiddenUntilFound\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Allows the browser’s built-in page search to find and expand the panel contents.\\n\\nOverrides the `keepMounted` prop and uses `hidden=\\\"until-found\\\"`\\nto hide the element without removing it from the DOM.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"loopFocus\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether to loop keyboard focus back to the first item\\nwhen the end of the list is reached while using the arrow keys.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"multiple\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether multiple items can be open at the same time.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component should ignore user interaction.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"orientation\": {\n      \"type\": \"Orientation\",\n      \"default\": \"'vertical'\",\n      \"description\": \"The visual orientation of the accordion.\\nControls whether roving focus uses left/right or up/down arrow keys.\",\n      \"detailedType\": \"'horizontal' | 'vertical' | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Accordion.Root.State<Value>) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((\\n    state: Accordion.Root.State<Value>,\\n  ) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Accordion.Root.State<Value>) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Accordion.Root.State<Value>,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"keepMounted\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to keep the element in the DOM while the panel is closed.\\nThis prop is ignored when `hiddenUntilFound` is used.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Accordion.Root.State<Value>) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Accordion.Root.State<Value>,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-orientation\": {\n      \"description\": \"Indicates the orientation of the accordion.\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the accordion is disabled.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/accordion-trigger.json",
    "content": "{\n  \"name\": \"AccordionTrigger\",\n  \"description\": \"A button that opens and closes the corresponding panel.\\nRenders a `<button>` element.\",\n  \"props\": {\n    \"nativeButton\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether the component renders a native `<button>` element when replacing it\\nvia the `render` prop.\\nSet to `false` if the rendered element is not a button (e.g. `<div>`).\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Accordion.Trigger.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Accordion.Trigger.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Accordion.Trigger.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Accordion.Trigger.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Accordion.Trigger.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Accordion.Trigger.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-panel-open\": {\n      \"description\": \"Present when the accordion panel is open.\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the accordion item is disabled.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/alert-dialog-backdrop.json",
    "content": "{\n  \"name\": \"AlertDialogBackdrop\",\n  \"description\": \"An overlay displayed beneath the popup.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"forceRender\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the backdrop is forced to render even when nested.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: AlertDialog.Backdrop.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((\\n    state: AlertDialog.Backdrop.State,\\n  ) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: AlertDialog.Backdrop.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: AlertDialog.Backdrop.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: AlertDialog.Backdrop.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: AlertDialog.Backdrop.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the dialog is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the dialog is closed.\"\n    },\n    \"data-starting-style\": {\n      \"description\": \"Present when the dialog is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the dialog is animating out.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/alert-dialog-close.json",
    "content": "{\n  \"name\": \"AlertDialogClose\",\n  \"description\": \"A button that closes the alert dialog.\\nRenders a `<button>` element.\",\n  \"props\": {\n    \"nativeButton\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether the component renders a native `<button>` element when replacing it\\nvia the `render` prop.\\nSet to `false` if the rendered element is not a button (e.g. `<div>`).\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: AlertDialog.Close.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: AlertDialog.Close.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: AlertDialog.Close.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: AlertDialog.Close.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: AlertDialog.Close.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: AlertDialog.Close.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-disabled\": {\n      \"description\": \"Present when the button is disabled.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/alert-dialog-description.json",
    "content": "{\n  \"name\": \"AlertDialogDescription\",\n  \"description\": \"A paragraph with additional information about the alert dialog.\\nRenders a `<p>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: AlertDialog.Description.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((\\n    state: AlertDialog.Description.State,\\n  ) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: AlertDialog.Description.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: AlertDialog.Description.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: AlertDialog.Description.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: AlertDialog.Description.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/alert-dialog-popup.json",
    "content": "{\n  \"name\": \"AlertDialogPopup\",\n  \"description\": \"A container for the alert dialog contents.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"initialFocus\": {\n      \"type\": \"boolean | RefObject<HTMLElement | null> | ((openType: InteractionType) => boolean | void | HTMLElement | null)\",\n      \"description\": \"Determines the element to focus when the dialog is opened.\\n\\n- `false`: Do not move focus.\\n- `true`: Move focus based on the default behavior (first tabbable element or popup).\\n- `RefObject`: Move focus to the ref element.\\n- `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`).\\n  Return an element to focus, `true` to use the default behavior, or `false`/`undefined` to do nothing.\",\n      \"detailedType\": \"| boolean\\n| React.RefObject<HTMLElement | null>\\n| ((\\n    openType: InteractionType,\\n  ) => boolean | void | HTMLElement | null)\\n| undefined\"\n    },\n    \"finalFocus\": {\n      \"type\": \"boolean | RefObject<HTMLElement | null> | ((closeType: InteractionType) => boolean | void | HTMLElement | null)\",\n      \"description\": \"Determines the element to focus when the dialog is closed.\\n\\n- `false`: Do not move focus.\\n- `true`: Move focus based on the default behavior (trigger or previously focused element).\\n- `RefObject`: Move focus to the ref element.\\n- `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`).\\n  Return an element to focus, `true` to use the default behavior, or `false`/`undefined` to do nothing.\",\n      \"detailedType\": \"| boolean\\n| React.RefObject<HTMLElement | null>\\n| ((\\n    closeType: InteractionType,\\n  ) => boolean | void | HTMLElement | null)\\n| undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: AlertDialog.Popup.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: AlertDialog.Popup.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: AlertDialog.Popup.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: AlertDialog.Popup.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: AlertDialog.Popup.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: AlertDialog.Popup.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the dialog is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the dialog is closed.\"\n    },\n    \"data-nested\": {\n      \"description\": \"Present when the dialog is nested within another dialog.\"\n    },\n    \"data-nested-dialog-open\": {\n      \"description\": \"Present when the dialog has other open dialogs nested within it.\"\n    },\n    \"data-starting-style\": {\n      \"description\": \"Present when the dialog is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the dialog is animating out.\"\n    }\n  },\n  \"cssVariables\": {\n    \"--nested-dialogs\": {\n      \"description\": \"Indicates how many dialogs are nested within.\",\n      \"type\": \"number\"\n    }\n  }\n}\n"
  },
  {
    "path": "docs/reference/generated/alert-dialog-portal.json",
    "content": "{\n  \"name\": \"AlertDialogPortal\",\n  \"description\": \"A portal element that moves the popup to a different part of the DOM.\\nBy default, the portal element is appended to `<body>`.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"container\": {\n      \"type\": \"HTMLElement | ShadowRoot | RefObject<HTMLElement | ShadowRoot | null> | null\",\n      \"description\": \"A parent element to render the portal element into.\",\n      \"detailedType\": \"| HTMLElement\\n| ShadowRoot\\n| React.RefObject<HTMLElement | ShadowRoot | null>\\n| null\\n| undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: AlertDialog.Portal.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((\\n    state: AlertDialog.Portal.State,\\n  ) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: AlertDialog.Portal.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: AlertDialog.Portal.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"keepMounted\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to keep the portal mounted in the DOM while the popup is hidden.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: AlertDialog.Portal.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: AlertDialog.Portal.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/alert-dialog-root.json",
    "content": "{\n  \"name\": \"AlertDialogRoot\",\n  \"description\": \"Groups all parts of the alert dialog.\\nDoesn’t render its own HTML element.\",\n  \"props\": {\n    \"defaultOpen\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the dialog is initially open.\\n\\nTo render a controlled dialog, use the `open` prop instead.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"open\": {\n      \"type\": \"boolean\",\n      \"description\": \"Whether the dialog is currently open.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"onOpenChange\": {\n      \"type\": \"((open: boolean, eventDetails: AlertDialog.Root.ChangeEventDetails) => void)\",\n      \"description\": \"Event handler called when the dialog is opened or closed.\",\n      \"detailedType\": \"| ((\\n    open: boolean,\\n    eventDetails: AlertDialog.Root.ChangeEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"actionsRef\": {\n      \"type\": \"RefObject<AlertDialog.Root.Actions | null>\",\n      \"description\": \"A ref to imperative actions.\\n- `unmount`: When specified, the dialog will not be unmounted when closed.\\nInstead, the `unmount` function must be called to unmount the dialog manually.\\nUseful when the dialog's animation is controlled by an external library.\\n- `close`: Closes the dialog imperatively when called.\",\n      \"detailedType\": \"| React.RefObject<AlertDialog.Root.Actions | null>\\n| undefined\"\n    },\n    \"defaultTriggerId\": {\n      \"type\": \"string | null\",\n      \"description\": \"ID of the trigger that the dialog is associated with.\\nThis is useful in conjunction with the `defaultOpen` prop to create an initially open dialog.\",\n      \"detailedType\": \"string | null | undefined\"\n    },\n    \"handle\": {\n      \"type\": \"AlertDialog.Handle<Payload>\",\n      \"description\": \"A handle to associate the alert dialog with a trigger.\\nIf specified, allows external triggers to control the alert dialog's open state.\\nCan be created with the AlertDialog.createHandle() method.\",\n      \"detailedType\": \"{} | undefined\"\n    },\n    \"onOpenChangeComplete\": {\n      \"type\": \"((open: boolean) => void)\",\n      \"description\": \"Event handler called after any animations complete when the dialog is opened or closed.\",\n      \"detailedType\": \"((open: boolean) => void) | undefined\"\n    },\n    \"triggerId\": {\n      \"type\": \"string | null\",\n      \"description\": \"ID of the trigger that the dialog is associated with.\\nThis is useful in conjunction with the `open` prop to create a controlled dialog.\\nThere's no need to specify this prop when the popover is uncontrolled (i.e. when the `open` prop is not set).\",\n      \"detailedType\": \"string | null | undefined\"\n    },\n    \"children\": {\n      \"type\": \"ReactNode | PayloadChildRenderFunction<Payload>\",\n      \"description\": \"The content of the dialog.\\nThis can be a regular React node or a render function that receives the `payload` of the active trigger.\",\n      \"detailedType\": \"| React.ReactNode\\n| ((arg: { payload: Payload | undefined }) => ReactNode)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/alert-dialog-title.json",
    "content": "{\n  \"name\": \"AlertDialogTitle\",\n  \"description\": \"A heading that labels the dialog.\\nRenders an `<h2>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: AlertDialog.Title.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: AlertDialog.Title.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: AlertDialog.Title.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: AlertDialog.Title.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: AlertDialog.Title.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: AlertDialog.Title.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/alert-dialog-trigger.json",
    "content": "{\n  \"name\": \"AlertDialogTrigger\",\n  \"description\": \"A button that opens the alert dialog.\\nRenders a `<button>` element.\",\n  \"props\": {\n    \"handle\": {\n      \"type\": \"DialogHandle<Payload>\",\n      \"description\": \"A handle to associate the trigger with a dialog.\\nCan be created with the AlertDialog.createHandle() method.\",\n      \"detailedType\": \"{} | undefined\"\n    },\n    \"nativeButton\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether the component renders a native `<button>` element when replacing it\\nvia the `render` prop.\\nSet to `false` if the rendered element is not a button (e.g. `<div>`).\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"payload\": {\n      \"type\": \"Payload\",\n      \"description\": \"A payload to pass to the dialog when it is opened.\",\n      \"detailedType\": \"Payload | undefined\"\n    },\n    \"id\": {\n      \"type\": \"string\",\n      \"description\": \"ID of the trigger. In addition to being forwarded to the rendered element,\\nit is also used to specify the active trigger for the dialogs in controlled mode (with the Dialog.Root `triggerId` prop).\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: AlertDialog.Trigger.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: AlertDialog.Trigger.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: AlertDialog.Trigger.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: AlertDialog.Trigger.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: AlertDialog.Trigger.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: AlertDialog.Trigger.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-popup-open\": {\n      \"description\": \"Present when the corresponding dialog is open.\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the trigger is disabled.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/alert-dialog-viewport.json",
    "content": "{\n  \"name\": \"AlertDialogViewport\",\n  \"description\": \"A positioning container for the dialog popup that can be made scrollable.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: AlertDialog.Viewport.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((\\n    state: AlertDialog.Viewport.State,\\n  ) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: AlertDialog.Viewport.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: AlertDialog.Viewport.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: AlertDialog.Viewport.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: AlertDialog.Viewport.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the dialog is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the dialog is closed.\"\n    },\n    \"data-nested\": {\n      \"description\": \"Present when the dialog is nested within another dialog.\"\n    },\n    \"data-nested-dialog-open\": {\n      \"description\": \"Present when the dialog has other open dialogs nested within it.\"\n    },\n    \"data-starting-style\": {\n      \"description\": \"Present when the dialog is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the dialog is animating out.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/autocomplete-root.json",
    "content": "{\n  \"name\": \"AutocompleteRoot\",\n  \"description\": \"Groups all parts of the autocomplete.\\nDoesn't render its own HTML element.\",\n  \"props\": {\n    \"name\": {\n      \"type\": \"string\",\n      \"description\": \"Identifies the field when a form is submitted.\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"defaultValue\": {\n      \"type\": \"string | number | string[]\",\n      \"description\": \"The uncontrolled input value of the autocomplete when it's initially rendered.\\n\\nTo render a controlled autocomplete, use the `value` prop instead.\",\n      \"detailedType\": \"string | number | string[] | undefined\"\n    },\n    \"value\": {\n      \"type\": \"string | number | string[]\",\n      \"description\": \"The input value of the autocomplete. Use when controlled.\",\n      \"detailedType\": \"string | number | string[] | undefined\"\n    },\n    \"onValueChange\": {\n      \"type\": \"((value: string, eventDetails: Autocomplete.Root.ChangeEventDetails) => void)\",\n      \"description\": \"Event handler called when the input value of the autocomplete changes.\",\n      \"detailedType\": \"| ((\\n    value: string,\\n    eventDetails: Autocomplete.Root.ChangeEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"defaultOpen\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the popup is initially open.\\n\\nTo render a controlled popup, use the `open` prop instead.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"open\": {\n      \"type\": \"boolean\",\n      \"description\": \"Whether the popup is currently open. Use when controlled.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"onOpenChange\": {\n      \"type\": \"((open: boolean, eventDetails: Autocomplete.Root.ChangeEventDetails) => void)\",\n      \"description\": \"Event handler called when the popup is opened or closed.\",\n      \"detailedType\": \"| ((\\n    open: boolean,\\n    eventDetails: Autocomplete.Root.ChangeEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"autoHighlight\": {\n      \"type\": \"boolean | 'always'\",\n      \"default\": \"false\",\n      \"description\": \"Whether the first matching item is highlighted automatically.\\n- `true`: highlight after the user types and keep the highlight while the query changes.\\n- `'always'`: always highlight the first item.\",\n      \"detailedType\": \"boolean | 'always' | undefined\"\n    },\n    \"keepHighlight\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the highlighted item should be preserved when the pointer leaves the list.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"highlightItemOnHover\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether moving the pointer over items should highlight them.\\nDisabling this prop allows CSS `:hover` to be differentiated from the `:focus` (`data-highlighted`) state.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"actionsRef\": {\n      \"type\": \"RefObject<Autocomplete.Root.Actions | null>\",\n      \"description\": \"A ref to imperative actions.\\n- `unmount`: When specified, the autocomplete will not be unmounted when closed.\\nInstead, the `unmount` function must be called to unmount the autocomplete manually.\\nUseful when the autocomplete's animation is controlled by an external library.\",\n      \"detailedType\": \"| React.RefObject<Autocomplete.Root.Actions | null>\\n| undefined\"\n    },\n    \"filter\": {\n      \"type\": \"((itemValue: ItemValue, query: string, itemToString: ((itemValue: ItemValue) => string) | undefined) => boolean) | null\",\n      \"description\": \"Filter function used to match items vs input query.\",\n      \"detailedType\": \"| ((\\n    itemValue: ItemValue,\\n    query: string,\\n    itemToString:\\n      | ((itemValue: ItemValue) => string)\\n      | undefined,\\n  ) => boolean)\\n| null\\n| undefined\"\n    },\n    \"filteredItems\": {\n      \"type\": \"any[] | Group[]\",\n      \"description\": \"Filtered items to display in the list.\\nWhen provided, the list will use these items instead of filtering the `items` prop internally.\\nUse when you want to control filtering logic externally with the `useFilter()` hook.\",\n      \"detailedType\": \"any[] | Group[] | undefined\"\n    },\n    \"grid\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether list items are presented in a grid layout.\\nWhen enabled, arrow keys navigate across rows and columns inferred from DOM rows.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"inline\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the list is rendered inline without using the popup.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"itemToStringValue\": {\n      \"type\": \"((itemValue: ItemValue) => string)\",\n      \"description\": \"When the item values are objects (`<Autocomplete.Item value={object}>`), this function converts the object value to a string representation for both display in the input and form submission.\\nIf the shape of the object is `{ value, label }`, the label will be used automatically without needing to specify this prop.\",\n      \"detailedType\": \"((itemValue: ItemValue) => string) | undefined\"\n    },\n    \"items\": {\n      \"type\": \"({ items: any[] })[] | ItemValue[]\",\n      \"description\": \"The items to be displayed in the list.\\nCan be either a flat array of items or an array of groups with items.\",\n      \"detailedType\": \"{ items: any[] }[] | ItemValue[] | undefined\"\n    },\n    \"limit\": {\n      \"type\": \"number\",\n      \"default\": \"-1\",\n      \"description\": \"The maximum number of items to display in the list.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"locale\": {\n      \"type\": \"Intl.LocalesArgument\",\n      \"description\": \"The locale to use for string comparison.\\nDefaults to the user's runtime locale.\",\n      \"detailedType\": \"Intl.LocalesArgument | undefined\"\n    },\n    \"loopFocus\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether to loop keyboard focus back to the input when the end of the list is reached while using the arrow keys. The first item can then be reached by pressing <kbd>ArrowDown</kbd> again from the input, or the last item can be reached by pressing <kbd>ArrowUp</kbd> from the input.\\nThe input is always included in the focus loop per [ARIA Authoring Practices](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/).\\nWhen disabled, focus does not move when on the last element and the user presses <kbd>ArrowDown</kbd>, or when on the first element and the user presses <kbd>ArrowUp</kbd>.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"modal\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Determines if the popup enters a modal state when open.\\n- `true`: user interaction is limited to the popup: document page scroll is locked and pointer interactions on outside elements are disabled.\\n- `false`: user interaction with the rest of the document is allowed.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"mode\": {\n      \"type\": \"'list' | 'both' | 'inline' | 'none'\",\n      \"default\": \"'list'\",\n      \"description\": \"Controls how the autocomplete behaves with respect to list filtering and inline autocompletion.\\n- `list` (default): items are dynamically filtered based on the input value. The input value does not change based on the active item.\\n- `both`: items are dynamically filtered based on the input value, which will temporarily change based on the active item (inline autocompletion).\\n- `inline`: items are static (not filtered), and the input value will temporarily change based on the active item (inline autocompletion).\\n- `none`: items are static (not filtered), and the input value will not change based on the active item.\",\n      \"detailedType\": \"'list' | 'both' | 'inline' | 'none' | undefined\"\n    },\n    \"onItemHighlighted\": {\n      \"type\": \"((highlightedValue: ItemValue | undefined, eventDetails: Autocomplete.Root.HighlightEventDetails) => void)\",\n      \"description\": \"Callback fired when an item is highlighted or unhighlighted.\\nReceives the highlighted item value (or `undefined` if no item is highlighted) and event details with a `reason` property describing why the highlight changed.\\nThe `reason` can be:\\n- `'keyboard'`: the highlight changed due to keyboard navigation.\\n- `'pointer'`: the highlight changed due to pointer hovering.\\n- `'none'`: the highlight changed programmatically.\",\n      \"detailedType\": \"| ((\\n    highlightedValue: ItemValue | undefined,\\n    eventDetails: Autocomplete.Root.HighlightEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"onOpenChangeComplete\": {\n      \"type\": \"((open: boolean) => void)\",\n      \"description\": \"Event handler called after any animations complete when the popup is opened or closed.\",\n      \"detailedType\": \"((open: boolean) => void) | undefined\"\n    },\n    \"openOnInputClick\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the popup opens when clicking the input.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"submitOnItemClick\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether clicking an item should submit the autocomplete's owning form.\\nBy default, clicking an item via a pointer or <kbd>Enter</kbd> key does not submit the owning form.\\nUseful when the autocomplete is used as a single-field form search input.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"virtualized\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the items are being externally virtualized.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component should ignore user interaction.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"readOnly\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the user should be unable to choose a different option from the popup.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"required\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the user must choose a value before submitting a form.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"inputRef\": {\n      \"type\": \"Ref<HTMLInputElement>\",\n      \"description\": \"A ref to the hidden input element.\",\n      \"detailedType\": \"React.Ref<HTMLInputElement> | undefined\"\n    },\n    \"id\": {\n      \"type\": \"string\",\n      \"description\": \"The id of the component.\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"children\": {\n      \"type\": \"ReactNode\",\n      \"detailedType\": \"React.ReactNode | undefined\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/autocomplete-value.json",
    "content": "{\n  \"name\": \"AutocompleteValue\",\n  \"description\": \"The current value of the autocomplete.\\nDoesn't render its own HTML element.\",\n  \"props\": {\n    \"children\": {\n      \"type\": \"ReactNode | ((value: string) => ReactNode)\",\n      \"detailedType\": \"React.ReactNode | ((value: string) => ReactNode)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/avatar-fallback.json",
    "content": "{\n  \"name\": \"AvatarFallback\",\n  \"description\": \"Rendered when the image fails to load or when no image is provided.\\nRenders a `<span>` element.\",\n  \"props\": {\n    \"delay\": {\n      \"type\": \"number\",\n      \"description\": \"How long to wait before showing the fallback. Specified in milliseconds.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Avatar.Fallback.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Avatar.Fallback.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Avatar.Fallback.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Avatar.Fallback.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Avatar.Fallback.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Avatar.Fallback.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/avatar-image.json",
    "content": "{\n  \"name\": \"AvatarImage\",\n  \"description\": \"The image to be displayed in the avatar.\\nRenders an `<img>` element.\",\n  \"props\": {\n    \"onLoadingStatusChange\": {\n      \"type\": \"((status: ImageLoadingStatus) => void)\",\n      \"description\": \"Callback fired when the loading status changes.\",\n      \"detailedType\": \"((status: ImageLoadingStatus) => void) | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Avatar.Image.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Avatar.Image.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Avatar.Image.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Avatar.Image.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Avatar.Image.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Avatar.Image.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-starting-style\": {\n      \"description\": \"Present when the image is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the image is animating out.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/avatar-root.json",
    "content": "{\n  \"name\": \"AvatarRoot\",\n  \"description\": \"Displays a user's profile picture, initials, or fallback icon.\\nRenders a `<span>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Avatar.Root.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Avatar.Root.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Avatar.Root.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Avatar.Root.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Avatar.Root.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Avatar.Root.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/button.json",
    "content": "{\n  \"name\": \"Button\",\n  \"description\": \"A button component that can be used to trigger actions.\\nRenders a `<button>` element.\",\n  \"props\": {\n    \"focusableWhenDisabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the button should be focusable when disabled.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"nativeButton\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether the component renders a native `<button>` element when replacing it\\nvia the `render` prop.\\nSet to `false` if the rendered element is not a button (e.g. `<div>`).\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Button.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Button.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Button.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Button.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Button.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((props: HTMLProps, state: Button.State) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-disabled\": {\n      \"description\": \"Present when the button is disabled.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/calendar-day-button.json",
    "content": "{\n  \"name\": \"CalendarDayButton\",\n  \"description\": \"An individual interactive day button in the calendar.\\nRenders a `<button>` element.\",\n  \"props\": {\n    \"focusableWhenDisabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"When `true` the item remains focusable when disabled.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"nativeButton\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether the component renders a native `<button>` element when replacing it\\nvia the `render` prop.\\nSet to `false` if the rendered element is not a button (e.g. `<div>`).\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"format\": {\n      \"type\": \"string\",\n      \"default\": \"adapter.formats.dayOfMonth\",\n      \"description\": \"The format used to display the day.\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Calendar.DayButton.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Calendar.DayButton.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Calendar.DayButton.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Calendar.DayButton.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Calendar.DayButton.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Calendar.DayButton.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-selected\": {\n      \"description\": \"Present when the day is selected.\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the day is disabled.\"\n    },\n    \"data-current\": {\n      \"description\": \"Present when the day is the current date.\"\n    },\n    \"data-outside-month\": {\n      \"description\": \"Present when the day is outside the month rendered by the day grid wrapping it.\"\n    },\n    \"data-unavailable\": {\n      \"description\": \"Present when the day is unavailable.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/calendar-day-grid-body.json",
    "content": "{\n  \"name\": \"CalendarDayGridBody\",\n  \"description\": \"Groups all parts of the calendar's day grid.\\nRenders a `<tbody>` element.\",\n  \"props\": {\n    \"fixedWeekNumber\": {\n      \"type\": \"number\",\n      \"description\": \"Will render the requested amount of weeks by adding weeks of the next month if needed.\\nSet it to 6 to create a Gregorian calendar where all months have the same number of weeks.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"offset\": {\n      \"type\": \"number\",\n      \"default\": \"0\",\n      \"description\": \"The offset to apply to the rendered month compared to the current month.\\nThis is mostly useful when displaying multiple day grids.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"children\": {\n      \"type\": \"ReactNode | ((week: Date, index: number, weeks: Date[]) => ReactNode)\",\n      \"description\": \"The children of the component.\\nIf a function is provided, it will be called for each week to render as its parameter.\",\n      \"detailedType\": \"| React.ReactNode\\n| ((\\n    week: Date,\\n    index: number,\\n    weeks: Date[],\\n  ) => ReactNode)\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Calendar.DayGridBody.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((\\n    state: Calendar.DayGridBody.State,\\n  ) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Calendar.DayGridBody.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Calendar.DayGridBody.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Calendar.DayGridBody.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Calendar.DayGridBody.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/calendar-day-grid-cell.json",
    "content": "{\n  \"name\": \"CalendarDayGridCell\",\n  \"description\": \"An individual day cell in the calendar.\\nRenders a `<td>` element.\",\n  \"props\": {\n    \"value\": {\n      \"type\": \"Date\",\n      \"required\": true,\n      \"description\": \"The value to select when this cell is clicked.\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Calendar.DayGridCell.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((\\n    state: Calendar.DayGridCell.State,\\n  ) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Calendar.DayGridCell.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Calendar.DayGridCell.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Calendar.DayGridCell.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Calendar.DayGridCell.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/calendar-day-grid-header-cell.json",
    "content": "{\n  \"name\": \"CalendarDayGridHeaderCell\",\n  \"description\": \"An individual day header cell in the calendar.\\nRenders a `<th>` element.\",\n  \"props\": {\n    \"value\": {\n      \"type\": \"Date\",\n      \"required\": true\n    },\n    \"formatter\": {\n      \"type\": \"((date: Date) => string)\",\n      \"default\": \"(date) => adapter.format(date, 'EEE').charAt(0).toUpperCase()\",\n      \"description\": \"The formatter function used to display the day of the week.\",\n      \"detailedType\": \"((date: Date) => string) | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Calendar.DayGridHeaderCell.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((\\n    state: Calendar.DayGridHeaderCell.State,\\n  ) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Calendar.DayGridHeaderCell.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Calendar.DayGridHeaderCell.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Calendar.DayGridHeaderCell.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Calendar.DayGridHeaderCell.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/calendar-day-grid-header-row.json",
    "content": "{\n  \"name\": \"CalendarDayGridHeaderRow\",\n  \"description\": \"Groups all cells of the calendar's day grid header row.\\nRenders a `<tr>` element.\",\n  \"props\": {\n    \"children\": {\n      \"type\": \"ReactNode | ((day: Date, index: number, days: Date[]) => ReactNode)\",\n      \"description\": \"The children of the component.\\nIf a function is provided, it will be called for each day of the week as its parameter.\",\n      \"detailedType\": \"| React.ReactNode\\n| ((day: Date, index: number, days: Date[]) => ReactNode)\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Calendar.DayGridHeaderRow.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((\\n    state: Calendar.DayGridHeaderRow.State,\\n  ) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Calendar.DayGridHeaderRow.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Calendar.DayGridHeaderRow.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Calendar.DayGridHeaderRow.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Calendar.DayGridHeaderRow.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/calendar-day-grid-header.json",
    "content": "{\n  \"name\": \"CalendarDayGridHeader\",\n  \"description\": \"Groups all parts of the calendar's day grid header.\\nRenders a `<thead>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Calendar.DayGridHeader.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((\\n    state: Calendar.DayGridHeader.State,\\n  ) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Calendar.DayGridHeader.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Calendar.DayGridHeader.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Calendar.DayGridHeader.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Calendar.DayGridHeader.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/calendar-day-grid-row.json",
    "content": "{\n  \"name\": \"CalendarDayGridRow\",\n  \"description\": \"Groups all cells of a given calendar's day grid row.\\nRenders a `<tr>` element.\",\n  \"props\": {\n    \"value\": {\n      \"type\": \"Date\",\n      \"required\": true,\n      \"description\": \"The date object representing the week.\"\n    },\n    \"children\": {\n      \"type\": \"ReactNode | ((day: Date, index: number, days: Date[]) => ReactNode)\",\n      \"description\": \"The children of the component.\\nIf a function is provided, it will be called for each day of the week as its parameter.\",\n      \"detailedType\": \"| React.ReactNode\\n| ((day: Date, index: number, days: Date[]) => ReactNode)\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Calendar.DayGridRow.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Calendar.DayGridRow.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Calendar.DayGridRow.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Calendar.DayGridRow.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Calendar.DayGridRow.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Calendar.DayGridRow.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/calendar-day-grid.json",
    "content": "{\n  \"name\": \"CalendarDayGrid\",\n  \"description\": \"Groups all the parts of the calendar's day grid.\\nRenders a `<table>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Calendar.DayGrid.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Calendar.DayGrid.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Calendar.DayGrid.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Calendar.DayGrid.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Calendar.DayGrid.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Calendar.DayGrid.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/calendar-decrement-month.json",
    "content": "{\n  \"name\": \"CalendarDecrementMonth\",\n  \"description\": \"Displays an element to navigate to the previous month in the calendar.\\nRenders a `<button>` element.\",\n  \"props\": {\n    \"nativeButton\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether the component renders a native `<button>` element when replacing it\\nvia the `render` prop.\\nSet to `false` if the rendered element is not a button (e.g. `<div>`).\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Calendar.DecrementMonth.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((\\n    state: Calendar.DecrementMonth.State,\\n  ) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Calendar.DecrementMonth.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Calendar.DecrementMonth.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Calendar.DecrementMonth.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Calendar.DecrementMonth.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-disabled\": {\n      \"description\": \"Present when the button is disabled.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/calendar-increment-month.json",
    "content": "{\n  \"name\": \"CalendarIncrementMonth\",\n  \"description\": \"Displays an element to navigate to the next month in the calendar.\\nRenders a `<button>` element.\",\n  \"props\": {\n    \"nativeButton\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether the component renders a native `<button>` element when replacing it\\nvia the `render` prop.\\nSet to `false` if the rendered element is not a button (e.g. `<div>`).\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Calendar.IncrementMonth.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((\\n    state: Calendar.IncrementMonth.State,\\n  ) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Calendar.IncrementMonth.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Calendar.IncrementMonth.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Calendar.IncrementMonth.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Calendar.IncrementMonth.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-disabled\": {\n      \"description\": \"Present when the button is disabled.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/calendar-root.json",
    "content": "{\n  \"name\": \"CalendarRoot\",\n  \"description\": \"Groups all parts of the calendar.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"defaultValue\": {\n      \"type\": \"TemporalValue\",\n      \"description\": \"The uncontrolled value that should be initially selected.\\nTo render a controlled (Range)Calendar, use the `value` prop instead.\",\n      \"detailedType\": \"Date | null | undefined\"\n    },\n    \"value\": {\n      \"type\": \"TemporalValue\",\n      \"description\": \"The controlled value that should be selected.\\nTo render an uncontrolled (Range)Calendar, use the `defaultValue` prop instead.\",\n      \"detailedType\": \"Date | null | undefined\"\n    },\n    \"onValueChange\": {\n      \"type\": \"((value: TemporalValue, eventDetails: CalendarValueChangeEventDetails<ValidateDateReturnValue>) => void)\",\n      \"description\": \"Event handler called when the selected value changes.\\nProvides the new value as an argument.\\nHas `getValidationError()` in the `eventDetails` to retrieve the validation error associated to the new value.\",\n      \"detailedType\": \"| ((\\n    value: TemporalValue,\\n    eventDetails: CalendarValueChangeEventDetails<ValidateDateReturnValue>,\\n  ) => void)\\n| undefined\"\n    },\n    \"defaultVisibleDate\": {\n      \"type\": \"Date\",\n      \"description\": \"The date used to decide which month should be initially displayed in the Day Grid.\\nTo render a controlled Calendar, use the `visibleDate` prop instead.\",\n      \"detailedType\": \"Date | undefined\"\n    },\n    \"visibleDate\": {\n      \"type\": \"Date\",\n      \"description\": \"The date used to decide which month should be displayed in the Day Grid.\\nTo render an uncontrolled Calendar, use the `defaultVisibleDate` prop instead.\",\n      \"detailedType\": \"Date | undefined\"\n    },\n    \"onVisibleDateChange\": {\n      \"type\": \"((visibleDate: Date, eventDetails: CalendarVisibleDateChangeEventDetails) => void)\",\n      \"description\": \"Event handler called when the visible date changes.\\nProvides the new date as an argument.\\nHas the change reason in the `eventDetails`.\",\n      \"detailedType\": \"| ((\\n    visibleDate: Date,\\n    eventDetails: CalendarVisibleDateChangeEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"isDateUnavailable\": {\n      \"type\": \"((day: Date) => boolean)\",\n      \"description\": \"Mark specific dates as unavailable.\\nThose dates will not be selectable but they will still be focusable with the keyboard.\",\n      \"detailedType\": \"((day: Date) => boolean) | undefined\"\n    },\n    \"maxDate\": {\n      \"type\": \"Date\",\n      \"description\": \"Maximal selectable date.\",\n      \"detailedType\": \"Date | undefined\"\n    },\n    \"minDate\": {\n      \"type\": \"Date\",\n      \"description\": \"Minimal selectable date.\",\n      \"detailedType\": \"Date | undefined\"\n    },\n    \"monthPageSize\": {\n      \"type\": \"number\",\n      \"default\": \"1\",\n      \"description\": \"The amount of months to move by when navigating.\\nThis is mostly useful when displaying multiple day grids.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"referenceDate\": {\n      \"type\": \"Date\",\n      \"default\": \"'The closest valid date using the validation props.'\",\n      \"description\": \"The date used to generate the new value when both `value` and `defaultValue` are empty.\\nIt can be used to:\\n- set a desired time on the selected date;\\n- set a desired default year or month;\",\n      \"detailedType\": \"Date | undefined\"\n    },\n    \"timezone\": {\n      \"type\": \"string\",\n      \"default\": \"'The timezone of the \\\"value\\\" or \\\"defaultValue\\\" prop if defined, \\\"default\\\" otherwise.'\",\n      \"description\": \"Choose which timezone to use for the value.\\nExample: \\\"default\\\", \\\"system\\\", \\\"UTC\\\", \\\"America/New_York\\\".\\nIf you pass values from other timezones to some props, they will be converted to this timezone before being used.\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component should ignore user interaction.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"readOnly\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the user should be unable to select a date in the calendar.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"invalid\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the calendar is forcefully marked as invalid.\\nA calendar can be invalid when the selected date fails validation (i.e., is outside of the allowed `minDate` and `maxDate` range).\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"children\": {\n      \"type\": \"ReactNode | ((parameters: CalendarContext) => ReactNode)\",\n      \"description\": \"The children of the component.\\nIf a function is provided, it will be called with the public context as its parameter.\",\n      \"detailedType\": \"| React.ReactNode\\n| ((parameters: CalendarContext) => ReactNode)\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Calendar.Root.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Calendar.Root.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Calendar.Root.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Calendar.Root.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Calendar.Root.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Calendar.Root.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-disabled\": {\n      \"description\": \"Present when the calendar is disabled.\"\n    },\n    \"data-readonly\": {\n      \"description\": \"Present when the calendar is readonly.\"\n    },\n    \"data-invalid\": {\n      \"description\": \"Present when the current value is invalid (fails validation).\"\n    },\n    \"data-empty\": {\n      \"description\": \"Present when the current value is empty.\"\n    },\n    \"data-navigation-direction\": {\n      \"description\": \"Indicates the direction of the navigation (based on the month navigating to).\",\n      \"type\": \"'previous' | 'next' | 'none'\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/calendar-viewport.json",
    "content": "{\n  \"name\": \"CalendarViewport\",\n  \"description\": \"A viewport for displaying calendar month transitions.\\nThis component is only required if you want to animate certain part of a calendar when navigating between months.\\nThe first rendered child element has to handle a ref.\\nPasses `data-current` to the currently visible content and `data-previous` to the previous content when animating between two.\\nDoesn't render its own HTML element.\",\n  \"props\": {\n    \"children\": {\n      \"type\": \"React.JSX.Element\",\n      \"required\": true,\n      \"description\": \"The content to render inside the transition container.\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-current\": {\n      \"description\": \"Applied to the direct child of the viewport when no transitions are present or the new content when it's entering.\"\n    },\n    \"data-navigation-direction\": {\n      \"description\": \"Indicates the direction of the navigation (based on the month navigating to).\",\n      \"type\": \"'previous' | 'next' | 'none'\"\n    },\n    \"data-previous\": {\n      \"description\": \"Applied to the direct child of the viewport that contains the exiting content when transitions are present.\"\n    },\n    \"data-starting-style\": {\n      \"description\": \"Present when the day grid body is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the day grid body is animating out.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/checkbox-group.json",
    "content": "{\n  \"name\": \"CheckboxGroup\",\n  \"description\": \"Provides a shared state to a series of checkboxes.\",\n  \"props\": {\n    \"defaultValue\": {\n      \"type\": \"string[]\",\n      \"description\": \"Names of the checkboxes in the group that should be initially ticked.\\n\\nTo render a controlled checkbox group, use the `value` prop instead.\",\n      \"detailedType\": \"string[] | undefined\"\n    },\n    \"value\": {\n      \"type\": \"string[]\",\n      \"description\": \"Names of the checkboxes in the group that should be ticked.\\n\\nTo render an uncontrolled checkbox group, use the `defaultValue` prop instead.\",\n      \"detailedType\": \"string[] | undefined\"\n    },\n    \"onValueChange\": {\n      \"type\": \"((value: string[], eventDetails: CheckboxGroupChangeEventDetails) => void)\",\n      \"description\": \"Event handler called when a checkbox in the group is ticked or unticked.\\nProvides the new value as an argument.\",\n      \"detailedType\": \"| ((\\n    value: string[],\\n    eventDetails: CheckboxGroupChangeEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"allValues\": {\n      \"type\": \"string[]\",\n      \"description\": \"Names of all checkboxes in the group. Use this when creating a parent checkbox.\",\n      \"detailedType\": \"string[] | undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component should ignore user interaction.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: CheckboxGroup.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: CheckboxGroup.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: CheckboxGroup.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: CheckboxGroup.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: CheckboxGroup.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: CheckboxGroup.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-disabled\": {\n      \"description\": \"Present when the checkbox group is disabled.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/checkbox-indicator.json",
    "content": "{\n  \"name\": \"CheckboxIndicator\",\n  \"description\": \"Indicates whether the checkbox is ticked.\\nRenders a `<span>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Checkbox.Indicator.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Checkbox.Indicator.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Checkbox.Indicator.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Checkbox.Indicator.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"keepMounted\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to keep the element in the DOM when the checkbox is not checked.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Checkbox.Indicator.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Checkbox.Indicator.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-checked\": {\n      \"description\": \"Present when the checkbox is checked.\"\n    },\n    \"data-unchecked\": {\n      \"description\": \"Present when the checkbox is not checked.\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the checkbox is disabled.\"\n    },\n    \"data-readonly\": {\n      \"description\": \"Present when the checkbox is readonly.\"\n    },\n    \"data-required\": {\n      \"description\": \"Present when the checkbox is required.\"\n    },\n    \"data-valid\": {\n      \"description\": \"Present when the checkbox is in valid state (when wrapped in Field.Root).\"\n    },\n    \"data-invalid\": {\n      \"description\": \"Present when the checkbox is in invalid state (when wrapped in Field.Root).\"\n    },\n    \"data-dirty\": {\n      \"description\": \"Present when the checkbox's value has changed (when wrapped in Field.Root).\"\n    },\n    \"data-touched\": {\n      \"description\": \"Present when the checkbox has been touched (when wrapped in Field.Root).\"\n    },\n    \"data-filled\": {\n      \"description\": \"Present when the checkbox is checked (when wrapped in Field.Root).\"\n    },\n    \"data-focused\": {\n      \"description\": \"Present when the checkbox is focused (when wrapped in Field.Root).\"\n    },\n    \"data-indeterminate\": {\n      \"description\": \"Present when the checkbox is in an indeterminate state.\"\n    },\n    \"data-starting-style\": {\n      \"description\": \"Present when the checkbox indicator is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the checkbox indicator is animating out.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/checkbox-root.json",
    "content": "{\n  \"name\": \"CheckboxRoot\",\n  \"description\": \"Represents the checkbox itself.\\nRenders a `<span>` element and a hidden `<input>` beside.\",\n  \"props\": {\n    \"name\": {\n      \"type\": \"string\",\n      \"default\": \"undefined\",\n      \"description\": \"Identifies the field when a form is submitted.\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"defaultChecked\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the checkbox is initially ticked.\\n\\nTo render a controlled checkbox, use the `checked` prop instead.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"checked\": {\n      \"type\": \"boolean\",\n      \"default\": \"undefined\",\n      \"description\": \"Whether the checkbox is currently ticked.\\n\\nTo render an uncontrolled checkbox, use the `defaultChecked` prop instead.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"onCheckedChange\": {\n      \"type\": \"((checked: boolean, eventDetails: Checkbox.Root.ChangeEventDetails) => void)\",\n      \"description\": \"Event handler called when the checkbox is ticked or unticked.\",\n      \"detailedType\": \"| ((\\n    checked: boolean,\\n    eventDetails: Checkbox.Root.ChangeEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"indeterminate\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the checkbox is in a mixed state: neither ticked, nor unticked.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"value\": {\n      \"type\": \"string\",\n      \"description\": \"The value of the selected checkbox.\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"nativeButton\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component renders a native `<button>` element when replacing it\\nvia the `render` prop.\\nSet to `true` if the rendered element is a native button.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"parent\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the checkbox controls a group of child checkboxes.\\n\\nMust be used in a [Checkbox Group](https://base-ui.com/react/components/checkbox-group).\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"uncheckedValue\": {\n      \"type\": \"string\",\n      \"description\": \"The value submitted with the form when the checkbox is unchecked.\\nBy default, unchecked checkboxes do not submit any value, matching native checkbox behavior.\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component should ignore user interaction.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"readOnly\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the user should be unable to tick or untick the checkbox.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"required\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the user must tick the checkbox before submitting a form.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"inputRef\": {\n      \"type\": \"Ref<HTMLInputElement>\",\n      \"description\": \"A ref to access the hidden `<input>` element.\",\n      \"detailedType\": \"React.Ref<HTMLInputElement> | undefined\"\n    },\n    \"id\": {\n      \"type\": \"string\",\n      \"description\": \"The id of the input element.\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Checkbox.Root.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Checkbox.Root.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Checkbox.Root.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Checkbox.Root.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Checkbox.Root.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Checkbox.Root.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-checked\": {\n      \"description\": \"Present when the checkbox is checked.\"\n    },\n    \"data-unchecked\": {\n      \"description\": \"Present when the checkbox is not checked.\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the checkbox is disabled.\"\n    },\n    \"data-readonly\": {\n      \"description\": \"Present when the checkbox is readonly.\"\n    },\n    \"data-required\": {\n      \"description\": \"Present when the checkbox is required.\"\n    },\n    \"data-valid\": {\n      \"description\": \"Present when the checkbox is in valid state (when wrapped in Field.Root).\"\n    },\n    \"data-invalid\": {\n      \"description\": \"Present when the checkbox is in invalid state (when wrapped in Field.Root).\"\n    },\n    \"data-dirty\": {\n      \"description\": \"Present when the checkbox's value has changed (when wrapped in Field.Root).\"\n    },\n    \"data-touched\": {\n      \"description\": \"Present when the checkbox has been touched (when wrapped in Field.Root).\"\n    },\n    \"data-filled\": {\n      \"description\": \"Present when the checkbox is checked (when wrapped in Field.Root).\"\n    },\n    \"data-focused\": {\n      \"description\": \"Present when the checkbox is focused (when wrapped in Field.Root).\"\n    },\n    \"data-indeterminate\": {\n      \"description\": \"Present when the checkbox is in an indeterminate state.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/collapsible-panel.json",
    "content": "{\n  \"name\": \"CollapsiblePanel\",\n  \"description\": \"A panel with the collapsible contents.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"hiddenUntilFound\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Allows the browser’s built-in page search to find and expand the panel contents.\\n\\nOverrides the `keepMounted` prop and uses `hidden=\\\"until-found\\\"`\\nto hide the element without removing it from the DOM.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Collapsible.Panel.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Collapsible.Panel.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Collapsible.Panel.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Collapsible.Panel.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"keepMounted\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to keep the element in the DOM while the panel is hidden.\\nThis prop is ignored when `hiddenUntilFound` is used.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Collapsible.Panel.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Collapsible.Panel.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the collapsible panel is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the collapsible panel is closed.\"\n    },\n    \"data-starting-style\": {\n      \"description\": \"Present when the panel is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the panel is animating out.\"\n    }\n  },\n  \"cssVariables\": {\n    \"--collapsible-panel-height\": {\n      \"description\": \"The collapsible panel's height.\",\n      \"type\": \"number\"\n    },\n    \"--collapsible-panel-width\": {\n      \"description\": \"The collapsible panel's width.\",\n      \"type\": \"number\"\n    }\n  }\n}\n"
  },
  {
    "path": "docs/reference/generated/collapsible-root.json",
    "content": "{\n  \"name\": \"CollapsibleRoot\",\n  \"description\": \"Groups all parts of the collapsible.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"defaultOpen\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the collapsible panel is initially open.\\n\\nTo render a controlled collapsible, use the `open` prop instead.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"open\": {\n      \"type\": \"boolean\",\n      \"description\": \"Whether the collapsible panel is currently open.\\n\\nTo render an uncontrolled collapsible, use the `defaultOpen` prop instead.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"onOpenChange\": {\n      \"type\": \"((open: boolean, eventDetails: Collapsible.Root.ChangeEventDetails) => void)\",\n      \"description\": \"Event handler called when the panel is opened or closed.\",\n      \"detailedType\": \"| ((\\n    open: boolean,\\n    eventDetails: Collapsible.Root.ChangeEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component should ignore user interaction.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Collapsible.Root.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Collapsible.Root.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Collapsible.Root.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Collapsible.Root.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Collapsible.Root.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Collapsible.Root.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/collapsible-trigger.json",
    "content": "{\n  \"name\": \"CollapsibleTrigger\",\n  \"description\": \"A button that opens and closes the collapsible panel.\\nRenders a `<button>` element.\",\n  \"props\": {\n    \"nativeButton\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether the component renders a native `<button>` element when replacing it\\nvia the `render` prop.\\nSet to `false` if the rendered element is not a button (e.g. `<div>`).\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Collapsible.Trigger.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Collapsible.Trigger.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Collapsible.Trigger.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Collapsible.Trigger.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Collapsible.Trigger.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Collapsible.Trigger.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-panel-open\": {\n      \"description\": \"Present when the collapsible panel is open.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/combobox-arrow.json",
    "content": "{\n  \"name\": \"ComboboxArrow\",\n  \"description\": \"Displays an element positioned against the anchor.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Combobox.Arrow.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Combobox.Arrow.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Combobox.Arrow.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Combobox.Arrow.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Combobox.Arrow.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Combobox.Arrow.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the popup is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the popup is closed.\"\n    },\n    \"data-uncentered\": {\n      \"description\": \"Present when the arrow is uncentered.\"\n    },\n    \"data-align\": {\n      \"description\": \"Indicates how the popup is aligned relative to specified side.\",\n      \"type\": \"'start' | 'center' | 'end'\"\n    },\n    \"data-side\": {\n      \"description\": \"Indicates which side the popup is positioned relative to the trigger.\",\n      \"type\": \"'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/combobox-backdrop.json",
    "content": "{\n  \"name\": \"ComboboxBackdrop\",\n  \"description\": \"An overlay displayed beneath the popup.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Combobox.Backdrop.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Combobox.Backdrop.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Combobox.Backdrop.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Combobox.Backdrop.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Combobox.Backdrop.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Combobox.Backdrop.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the popup is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the popup is closed.\"\n    },\n    \"data-starting-style\": {\n      \"description\": \"Present when the popup is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the popup is animating out.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/combobox-chip-remove.json",
    "content": "{\n  \"name\": \"ComboboxChipRemove\",\n  \"description\": \"A button to remove a chip.\\nRenders a `<button>` element.\",\n  \"props\": {\n    \"nativeButton\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether the component renders a native `<button>` element when replacing it\\nvia the `render` prop.\\nSet to `false` if the rendered element is not a button (e.g. `<div>`).\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Combobox.ChipRemove.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Combobox.ChipRemove.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Combobox.ChipRemove.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Combobox.ChipRemove.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Combobox.ChipRemove.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Combobox.ChipRemove.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/combobox-chip.json",
    "content": "{\n  \"name\": \"ComboboxChip\",\n  \"description\": \"An individual chip that represents a value in a multiselectable input.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Combobox.Chip.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Combobox.Chip.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Combobox.Chip.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Combobox.Chip.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Combobox.Chip.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Combobox.Chip.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/combobox-chips.json",
    "content": "{\n  \"name\": \"ComboboxChips\",\n  \"description\": \"A container for the chips in a multiselectable input.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Combobox.Chips.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Combobox.Chips.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Combobox.Chips.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Combobox.Chips.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Combobox.Chips.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Combobox.Chips.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/combobox-clear.json",
    "content": "{\n  \"name\": \"ComboboxClear\",\n  \"description\": \"Clears the value when clicked.\\nRenders a `<button>` element.\",\n  \"props\": {\n    \"nativeButton\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether the component renders a native `<button>` element when replacing it\\nvia the `render` prop.\\nSet to `false` if the rendered element is not a button (e.g. `<div>`).\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component should ignore user interaction.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Combobox.Clear.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Combobox.Clear.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Combobox.Clear.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Combobox.Clear.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"keepMounted\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component should remain mounted in the DOM when not visible.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Combobox.Clear.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Combobox.Clear.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-popup-open\": {\n      \"description\": \"Present when the corresponding popup is open.\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the button is disabled.\"\n    },\n    \"data-starting-style\": {\n      \"description\": \"Present when the button is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the button is animating out.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/combobox-collection.json",
    "content": "{\n  \"name\": \"ComboboxCollection\",\n  \"description\": \"Renders filtered list items.\\nDoesn't render its own HTML element.\\n\\nIf rendering a flat list, pass a function child to the `List` component instead, which implicitly wraps it.\",\n  \"props\": {\n    \"children\": {\n      \"type\": \"((item: any, index: number) => ReactNode)\",\n      \"required\": true,\n      \"detailedType\": \"(item: any, index: number) => ReactNode\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/combobox-empty.json",
    "content": "{\n  \"name\": \"ComboboxEmpty\",\n  \"description\": \"Renders its children only when the list is empty.\\nRequires the `items` prop on the root component.\\nAnnounces changes politely to screen readers.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Combobox.Empty.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Combobox.Empty.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Combobox.Empty.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Combobox.Empty.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Combobox.Empty.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Combobox.Empty.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/combobox-group-label.json",
    "content": "{\n  \"name\": \"ComboboxGroupLabel\",\n  \"description\": \"An accessible label that is automatically associated with its parent group.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Combobox.GroupLabel.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Combobox.GroupLabel.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Combobox.GroupLabel.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Combobox.GroupLabel.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Combobox.GroupLabel.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Combobox.GroupLabel.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/combobox-group.json",
    "content": "{\n  \"name\": \"ComboboxGroup\",\n  \"description\": \"Groups related items with the corresponding label.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"items\": {\n      \"type\": \"any[]\",\n      \"description\": \"Items to be rendered within this group.\\nWhen provided, child `Collection` components will use these items.\",\n      \"detailedType\": \"any[] | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Combobox.Group.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Combobox.Group.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Combobox.Group.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Combobox.Group.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Combobox.Group.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Combobox.Group.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/combobox-icon.json",
    "content": "{\n  \"name\": \"ComboboxIcon\",\n  \"description\": \"An icon that indicates that the trigger button opens the popup.\\nRenders a `<span>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Combobox.Icon.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Combobox.Icon.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Combobox.Icon.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Combobox.Icon.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Combobox.Icon.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Combobox.Icon.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/combobox-input-group.json",
    "content": "{\n  \"name\": \"ComboboxInputGroup\",\n  \"description\": \"A wrapper for the input and its associated controls.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Combobox.InputGroup.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Combobox.InputGroup.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Combobox.InputGroup.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Combobox.InputGroup.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Combobox.InputGroup.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Combobox.InputGroup.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-popup-open\": {\n      \"description\": \"Present when the corresponding popup is open.\"\n    },\n    \"data-popup-side\": {\n      \"description\": \"Indicates which side the corresponding popup is positioned relative to its anchor.\",\n      \"type\": \"'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start' | null\"\n    },\n    \"data-list-empty\": {\n      \"description\": \"Present when the corresponding items list is empty.\"\n    },\n    \"data-pressed\": {\n      \"description\": \"Present when the input group is pressed.\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the component is disabled.\"\n    },\n    \"data-readonly\": {\n      \"description\": \"Present when the component is readonly.\"\n    },\n    \"data-valid\": {\n      \"description\": \"Present when the component is in valid state (when wrapped in Field.Root).\"\n    },\n    \"data-invalid\": {\n      \"description\": \"Present when the component is in invalid state (when wrapped in Field.Root).\"\n    },\n    \"data-dirty\": {\n      \"description\": \"Present when the component's value has changed (when wrapped in Field.Root).\"\n    },\n    \"data-touched\": {\n      \"description\": \"Present when the component has been touched (when wrapped in Field.Root).\"\n    },\n    \"data-filled\": {\n      \"description\": \"Present when the component has a value (when wrapped in Field.Root).\"\n    },\n    \"data-focused\": {\n      \"description\": \"Present when the component is focused (when wrapped in Field.Root).\"\n    },\n    \"data-placeholder\": {\n      \"description\": \"Present when the combobox doesn't have a value.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/combobox-input.json",
    "content": "{\n  \"name\": \"ComboboxInput\",\n  \"description\": \"A text input to search for items in the list.\\nRenders an `<input>` element.\",\n  \"props\": {\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component should ignore user interaction.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Combobox.Input.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Combobox.Input.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Combobox.Input.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Combobox.Input.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Combobox.Input.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Combobox.Input.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-popup-open\": {\n      \"description\": \"Present when the corresponding popup is open.\"\n    },\n    \"data-popup-side\": {\n      \"description\": \"Indicates which side the corresponding popup is positioned relative to its anchor.\",\n      \"type\": \"'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start' | null\"\n    },\n    \"data-list-empty\": {\n      \"description\": \"Present when the corresponding items list is empty.\"\n    },\n    \"data-pressed\": {\n      \"description\": \"Present when the input is pressed.\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the component is disabled.\"\n    },\n    \"data-readonly\": {\n      \"description\": \"Present when the component is readonly.\"\n    },\n    \"data-required\": {\n      \"description\": \"Present when the component is required.\"\n    },\n    \"data-valid\": {\n      \"description\": \"Present when the component is in valid state (when wrapped in Field.Root).\"\n    },\n    \"data-invalid\": {\n      \"description\": \"Present when the component is in invalid state (when wrapped in Field.Root).\"\n    },\n    \"data-dirty\": {\n      \"description\": \"Present when the component's value has changed (when wrapped in Field.Root).\"\n    },\n    \"data-touched\": {\n      \"description\": \"Present when the component has been touched (when wrapped in Field.Root).\"\n    },\n    \"data-filled\": {\n      \"description\": \"Present when the component has a value (when wrapped in Field.Root).\"\n    },\n    \"data-focused\": {\n      \"description\": \"Present when the input is focused (when wrapped in Field.Root).\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/combobox-item-indicator.json",
    "content": "{\n  \"name\": \"ComboboxItemIndicator\",\n  \"description\": \"Indicates whether the item is selected.\\nRenders a `<span>` element.\",\n  \"props\": {\n    \"children\": {\n      \"type\": \"ReactNode\",\n      \"detailedType\": \"React.ReactNode\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Combobox.ItemIndicator.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((\\n    state: Combobox.ItemIndicator.State,\\n  ) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Combobox.ItemIndicator.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Combobox.ItemIndicator.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"keepMounted\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to keep the HTML element in the DOM when the item is not selected.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Combobox.ItemIndicator.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Combobox.ItemIndicator.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-starting-style\": {\n      \"description\": \"Present when the indicator is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the indicator is animating out.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/combobox-item.json",
    "content": "{\n  \"name\": \"ComboboxItem\",\n  \"description\": \"An individual item in the list.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"value\": {\n      \"type\": \"any\",\n      \"default\": \"null\",\n      \"description\": \"A unique value that identifies this item.\"\n    },\n    \"onClick\": {\n      \"type\": \"((event: BaseUIEvent<MouseEvent<HTMLDivElement, MouseEvent>>) => void)\",\n      \"description\": \"An optional click handler for the item when selected.\\nIt fires when clicking the item with the pointer, as well as when pressing `Enter` with the keyboard if the item is highlighted when the `Input` or `List` element has focus.\",\n      \"detailedType\": \"| ((\\n    event: BaseUIEvent<\\n      MouseEvent<HTMLDivElement, MouseEvent>\\n    >,\\n  ) => void)\\n| undefined\"\n    },\n    \"index\": {\n      \"type\": \"number\",\n      \"description\": \"The index of the item in the list. Improves performance when specified by avoiding the need to calculate the index automatically from the DOM.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"nativeButton\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component renders a native `<button>` element when replacing it\\nvia the `render` prop.\\nSet to `true` if the rendered element is a native button.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component should ignore user interaction.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"children\": {\n      \"type\": \"ReactNode\",\n      \"detailedType\": \"React.ReactNode\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Combobox.Item.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Combobox.Item.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Combobox.Item.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Combobox.Item.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Combobox.Item.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Combobox.Item.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-selected\": {\n      \"description\": \"Present when the item is selected.\"\n    },\n    \"data-highlighted\": {\n      \"description\": \"Present when the item is highlighted.\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the item is disabled.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/combobox-label.json",
    "content": "{\n  \"name\": \"ComboboxLabel\",\n  \"description\": \"An accessible label that is automatically associated with the combobox trigger.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Field.Root.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Field.Root.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Field.Root.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Field.Root.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Field.Root.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Field.Root.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/combobox-list.json",
    "content": "{\n  \"name\": \"ComboboxList\",\n  \"description\": \"A list container for the items.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"children\": {\n      \"type\": \"ReactNode | ((item: any, index: number) => ReactNode)\",\n      \"detailedType\": \"| React.ReactNode\\n| ((item: any, index: number) => ReactNode)\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Combobox.List.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Combobox.List.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Combobox.List.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Combobox.List.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Combobox.List.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Combobox.List.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/combobox-popup.json",
    "content": "{\n  \"name\": \"ComboboxPopup\",\n  \"description\": \"A container for the list.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"initialFocus\": {\n      \"type\": \"boolean | RefObject<HTMLElement | null> | ((openType: InteractionType) => boolean | void | HTMLElement | null)\",\n      \"description\": \"Determines the element to focus when the popup is opened.\\n\\n- `false`: Do not move focus.\\n- `true`: Move focus based on the default behavior (first tabbable element or popup).\\n- `RefObject`: Move focus to the ref element.\\n- `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`).\\n  Return an element to focus, `true` to use the default behavior, or `false`/`undefined` to do nothing.\",\n      \"detailedType\": \"| boolean\\n| React.RefObject<HTMLElement | null>\\n| ((\\n    openType: InteractionType,\\n  ) => boolean | void | HTMLElement | null)\\n| undefined\"\n    },\n    \"finalFocus\": {\n      \"type\": \"boolean | RefObject<HTMLElement | null> | ((closeType: InteractionType) => boolean | void | HTMLElement | null)\",\n      \"description\": \"Determines the element to focus when the popup is closed.\\n\\n- `false`: Do not move focus.\\n- `true`: Move focus based on the default behavior (trigger or previously focused element).\\n- `RefObject`: Move focus to the ref element.\\n- `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`).\\n  Return an element to focus, `true` to use the default behavior, or `false`/`undefined` to do nothing.\",\n      \"detailedType\": \"| boolean\\n| React.RefObject<HTMLElement | null>\\n| ((\\n    closeType: InteractionType,\\n  ) => boolean | void | HTMLElement | null)\\n| undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Combobox.Popup.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Combobox.Popup.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Combobox.Popup.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Combobox.Popup.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Combobox.Popup.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Combobox.Popup.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the popup is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the popup is closed.\"\n    },\n    \"data-align\": {\n      \"description\": \"Indicates how the popup is aligned relative to specified side.\",\n      \"type\": \"'start' | 'center' | 'end'\"\n    },\n    \"data-empty\": {\n      \"description\": \"Present when the items list is empty.\"\n    },\n    \"data-instant\": {\n      \"description\": \"Present if animations should be instant.\",\n      \"type\": \"'click' | 'dismiss'\"\n    },\n    \"data-side\": {\n      \"description\": \"Indicates which side the popup is positioned relative to the trigger.\",\n      \"type\": \"'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'\"\n    },\n    \"data-starting-style\": {\n      \"description\": \"Present when the popup is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the popup is animating out.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/combobox-portal.json",
    "content": "{\n  \"name\": \"ComboboxPortal\",\n  \"description\": \"A portal element that moves the popup to a different part of the DOM.\\nBy default, the portal element is appended to `<body>`.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"container\": {\n      \"type\": \"HTMLElement | ShadowRoot | RefObject<HTMLElement | ShadowRoot | null> | null\",\n      \"description\": \"A parent element to render the portal element into.\",\n      \"detailedType\": \"| HTMLElement\\n| ShadowRoot\\n| React.RefObject<HTMLElement | ShadowRoot | null>\\n| null\\n| undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Combobox.Portal.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Combobox.Portal.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Combobox.Portal.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Combobox.Portal.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"keepMounted\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to keep the portal mounted in the DOM while the popup is hidden.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Combobox.Portal.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Combobox.Portal.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/combobox-positioner.json",
    "content": "{\n  \"name\": \"ComboboxPositioner\",\n  \"description\": \"Positions the popup against the trigger.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"disableAnchorTracking\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to disable the popup from tracking any layout shift of its positioning anchor.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"align\": {\n      \"type\": \"Align\",\n      \"default\": \"'center'\",\n      \"description\": \"How to align the popup relative to the specified side.\",\n      \"detailedType\": \"'start' | 'center' | 'end' | undefined\"\n    },\n    \"alignOffset\": {\n      \"type\": \"number | OffsetFunction\",\n      \"default\": \"0\",\n      \"description\": \"Additional offset along the alignment axis in pixels.\\nAlso accepts a function that returns the offset to read the dimensions of the anchor\\nand positioner elements, along with its side and alignment.\\n\\nThe function takes a `data` object parameter with the following properties:\\n- `data.anchor`: the dimensions of the anchor element with properties `width` and `height`.\\n- `data.positioner`: the dimensions of the positioner element with properties `width` and `height`.\\n- `data.side`: which side of the anchor element the positioner is aligned against.\\n- `data.align`: how the positioner is aligned relative to the specified side.\",\n      \"example\": \"```jsx\\n<Positioner\\n  alignOffset={({ side, align, anchor, positioner }) => {\\n    return side === 'top' || side === 'bottom'\\n      ? anchor.width\\n      : anchor.height;\\n  }}\\n/>\\n```\",\n      \"detailedType\": \"| number\\n| ((data: {\\n    side: Side\\n    align: Align\\n    anchor: { width: number; height: number }\\n    positioner: { width: number; height: number }\\n  }) => number)\\n| undefined\"\n    },\n    \"side\": {\n      \"type\": \"Side\",\n      \"default\": \"'bottom'\",\n      \"description\": \"Which side of the anchor element to align the popup against.\\nMay automatically change to avoid collisions.\",\n      \"detailedType\": \"| 'top'\\n| 'bottom'\\n| 'left'\\n| 'right'\\n| 'inline-end'\\n| 'inline-start'\\n| undefined\"\n    },\n    \"sideOffset\": {\n      \"type\": \"number | OffsetFunction\",\n      \"default\": \"0\",\n      \"description\": \"Distance between the anchor and the popup in pixels.\\nAlso accepts a function that returns the distance to read the dimensions of the anchor\\nand positioner elements, along with its side and alignment.\\n\\nThe function takes a `data` object parameter with the following properties:\\n- `data.anchor`: the dimensions of the anchor element with properties `width` and `height`.\\n- `data.positioner`: the dimensions of the positioner element with properties `width` and `height`.\\n- `data.side`: which side of the anchor element the positioner is aligned against.\\n- `data.align`: how the positioner is aligned relative to the specified side.\",\n      \"example\": \"```jsx\\n<Positioner\\n  sideOffset={({ side, align, anchor, positioner }) => {\\n    return side === 'top' || side === 'bottom'\\n      ? anchor.height\\n      : anchor.width;\\n  }}\\n/>\\n```\",\n      \"detailedType\": \"| number\\n| ((data: {\\n    side: Side\\n    align: Align\\n    anchor: { width: number; height: number }\\n    positioner: { width: number; height: number }\\n  }) => number)\\n| undefined\"\n    },\n    \"arrowPadding\": {\n      \"type\": \"number\",\n      \"default\": \"5\",\n      \"description\": \"Minimum distance to maintain between the arrow and the edges of the popup.\\n\\nUse it to prevent the arrow element from hanging out of the rounded corners of a popup.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"anchor\": {\n      \"type\": \"Element | VirtualElement | RefObject<Element | null> | (() => Element | VirtualElement | null) | null\",\n      \"description\": \"An element to position the popup against.\\nBy default, the popup will be positioned against the trigger.\",\n      \"detailedType\": \"| Element\\n| VirtualElement\\n| React.RefObject<Element | null>\\n| (() => Element | VirtualElement | null)\\n| null\\n| undefined\"\n    },\n    \"collisionAvoidance\": {\n      \"type\": \"CollisionAvoidance\",\n      \"description\": \"Determines how to handle collisions when positioning the popup.\\n\\n`side` controls overflow on the preferred placement axis (`top`/`bottom` or `left`/`right`):\\n- `'flip'`: keep the requested side when it fits; otherwise try the opposite side\\n  (`top` and `bottom`, or `left` and `right`).\\n- `'shift'`: never change side; keep the requested side and move the popup within\\n  the clipping boundary so it stays visible.\\n- `'none'`: do not correct side-axis overflow.\\n\\n`align` controls overflow on the alignment axis (`start`/`center`/`end`):\\n- `'flip'`: keep side, but swap `start` and `end` when the requested alignment overflows.\\n- `'shift'`: keep side and requested alignment, then nudge the popup along the\\n  alignment axis to fit.\\n- `'none'`: do not correct alignment-axis overflow.\\n\\n`fallbackAxisSide` controls fallback behavior on the perpendicular axis when the\\npreferred axis cannot fit:\\n- `'start'`: allow perpendicular fallback and try the logical start side first\\n  (`top` before `bottom`, or `left` before `right` in LTR).\\n- `'end'`: allow perpendicular fallback and try the logical end side first\\n  (`bottom` before `top`, or `right` before `left` in LTR).\\n- `'none'`: do not fallback to the perpendicular axis.\\n\\nWhen `side` is `'shift'`, explicitly setting `align` only supports `'shift'` or `'none'`.\\nIf `align` is omitted, it defaults to `'flip'`.\",\n      \"example\": \"```jsx\\n<Positioner\\n  collisionAvoidance={{\\n    side: 'shift',\\n    align: 'shift',\\n    fallbackAxisSide: 'none',\\n  }}\\n/>\\n```\",\n      \"detailedType\": \"| {\\n    side?: 'flip' | 'none'\\n    align?: 'flip' | 'shift' | 'none'\\n    fallbackAxisSide?: 'start' | 'end' | 'none'\\n  }\\n| {\\n    side?: 'shift' | 'none'\\n    align?: 'shift' | 'none'\\n    fallbackAxisSide?: 'start' | 'end' | 'none'\\n  }\\n| undefined\"\n    },\n    \"collisionBoundary\": {\n      \"type\": \"Boundary\",\n      \"default\": \"'clipping-ancestors'\",\n      \"description\": \"An element or a rectangle that delimits the area that the popup is confined to.\",\n      \"detailedType\": \"| 'clipping-ancestors'\\n| Element\\n| Element[]\\n| Rect\\n| undefined\"\n    },\n    \"collisionPadding\": {\n      \"type\": \"Padding\",\n      \"default\": \"5\",\n      \"description\": \"Additional space to maintain from the edge of the collision boundary.\",\n      \"detailedType\": \"| {\\n    top?: number\\n    right?: number\\n    bottom?: number\\n    left?: number\\n  }\\n| number\\n| undefined\"\n    },\n    \"sticky\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to maintain the popup in the viewport after\\nthe anchor element was scrolled out of view.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"positionMethod\": {\n      \"type\": \"'absolute' | 'fixed'\",\n      \"default\": \"'absolute'\",\n      \"description\": \"Determines which CSS `position` property to use.\",\n      \"detailedType\": \"'absolute' | 'fixed' | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Combobox.Positioner.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Combobox.Positioner.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Combobox.Positioner.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Combobox.Positioner.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Combobox.Positioner.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Combobox.Positioner.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the popup is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the popup is closed.\"\n    },\n    \"data-anchor-hidden\": {\n      \"description\": \"Present when the anchor is hidden.\"\n    },\n    \"data-align\": {\n      \"description\": \"Indicates how the popup is aligned relative to specified side.\",\n      \"type\": \"'start' | 'center' | 'end'\"\n    },\n    \"data-empty\": {\n      \"description\": \"Present when the items list is empty.\"\n    },\n    \"data-side\": {\n      \"description\": \"Indicates which side the popup is positioned relative to the trigger.\",\n      \"type\": \"'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'\"\n    }\n  },\n  \"cssVariables\": {\n    \"--anchor-height\": {\n      \"description\": \"The anchor's height.\",\n      \"type\": \"number\"\n    },\n    \"--anchor-width\": {\n      \"description\": \"The anchor's width.\",\n      \"type\": \"number\"\n    },\n    \"--available-height\": {\n      \"description\": \"The available height between the trigger and the edge of the viewport.\",\n      \"type\": \"number\"\n    },\n    \"--available-width\": {\n      \"description\": \"The available width between the trigger and the edge of the viewport.\",\n      \"type\": \"number\"\n    },\n    \"--transform-origin\": {\n      \"description\": \"The coordinates that this element is anchored to. Used for animations and transitions.\",\n      \"type\": \"string\"\n    }\n  }\n}\n"
  },
  {
    "path": "docs/reference/generated/combobox-root.json",
    "content": "{\n  \"name\": \"ComboboxRoot\",\n  \"description\": \"Groups all parts of the combobox.\\nDoesn't render its own HTML element.\",\n  \"props\": {\n    \"name\": {\n      \"type\": \"string\",\n      \"description\": \"Identifies the field when a form is submitted.\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"defaultValue\": {\n      \"type\": \"Value[] | Value | null\",\n      \"description\": \"The uncontrolled selected value of the combobox when it's initially rendered.\\n\\nTo render a controlled combobox, use the `value` prop instead.\",\n      \"detailedType\": \"Value[] | Value | null | undefined\"\n    },\n    \"value\": {\n      \"type\": \"Value[] | Value | null\",\n      \"description\": \"The selected value of the combobox. Use when controlled.\",\n      \"detailedType\": \"Value[] | Value | null | undefined\"\n    },\n    \"onValueChange\": {\n      \"type\": \"((value: Value[] | Value | any | null, eventDetails: Combobox.Root.ChangeEventDetails) => void)\",\n      \"description\": \"Event handler called when the selected value of the combobox changes.\",\n      \"detailedType\": \"| ((\\n    value: Value[] | Value | any | null,\\n    eventDetails: Combobox.Root.ChangeEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"defaultInputValue\": {\n      \"type\": \"string | number | string[]\",\n      \"description\": \"The uncontrolled input value when initially rendered.\\n\\nTo render a controlled input, use the `inputValue` prop instead.\",\n      \"detailedType\": \"string | number | string[] | undefined\"\n    },\n    \"inputValue\": {\n      \"type\": \"string | number | string[]\",\n      \"description\": \"The input value of the combobox. Use when controlled.\",\n      \"detailedType\": \"string | number | string[] | undefined\"\n    },\n    \"onInputValueChange\": {\n      \"type\": \"((inputValue: string, eventDetails: Combobox.Root.ChangeEventDetails) => void)\",\n      \"description\": \"Event handler called when the input value changes.\",\n      \"detailedType\": \"| ((\\n    inputValue: string,\\n    eventDetails: Combobox.Root.ChangeEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"defaultOpen\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the popup is initially open.\\n\\nTo render a controlled popup, use the `open` prop instead.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"open\": {\n      \"type\": \"boolean\",\n      \"description\": \"Whether the popup is currently open. Use when controlled.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"onOpenChange\": {\n      \"type\": \"((open: boolean, eventDetails: Combobox.Root.ChangeEventDetails) => void)\",\n      \"description\": \"Event handler called when the popup is opened or closed.\",\n      \"detailedType\": \"| ((\\n    open: boolean,\\n    eventDetails: Combobox.Root.ChangeEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"autoHighlight\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the first matching item is highlighted automatically while filtering.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"highlightItemOnHover\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether moving the pointer over items should highlight them.\\nDisabling this prop allows CSS `:hover` to be differentiated from the `:focus` (`data-highlighted`) state.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"actionsRef\": {\n      \"type\": \"RefObject<Combobox.Root.Actions | null>\",\n      \"description\": \"A ref to imperative actions.\\n- `unmount`: When specified, the combobox will not be unmounted when closed.\\nInstead, the `unmount` function must be called to unmount the combobox manually.\\nUseful when the combobox's animation is controlled by an external library.\",\n      \"detailedType\": \"| React.RefObject<Combobox.Root.Actions | null>\\n| undefined\"\n    },\n    \"autoComplete\": {\n      \"type\": \"string\",\n      \"description\": \"Provides a hint to the browser for autofill.\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"filter\": {\n      \"type\": \"((itemValue: Value, query: string, itemToString: ((itemValue: Value) => string) | undefined) => boolean) | null\",\n      \"description\": \"Filter function used to match items vs input query.\",\n      \"detailedType\": \"| ((\\n    itemValue: Value,\\n    query: string,\\n    itemToString:\\n      | ((itemValue: Value) => string)\\n      | undefined,\\n  ) => boolean)\\n| null\\n| undefined\"\n    },\n    \"filteredItems\": {\n      \"type\": \"any[] | Group[]\",\n      \"description\": \"Filtered items to display in the list.\\nWhen provided, the list will use these items instead of filtering the `items` prop internally.\\nUse when you want to control filtering logic externally with the `useFilter()` hook.\",\n      \"detailedType\": \"any[] | Group[] | undefined\"\n    },\n    \"grid\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether list items are presented in a grid layout.\\nWhen enabled, arrow keys navigate across rows and columns inferred from DOM rows.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"inline\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the list is rendered inline without using the popup.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"isItemEqualToValue\": {\n      \"type\": \"((itemValue: Value, value: Value) => boolean)\",\n      \"description\": \"Custom comparison logic used to determine if a combobox item value matches the current selected value. Useful when item values are objects without matching referentially.\\nDefaults to `Object.is` comparison.\",\n      \"detailedType\": \"| ((itemValue: Value, value: Value) => boolean)\\n| undefined\"\n    },\n    \"itemToStringLabel\": {\n      \"type\": \"((itemValue: Value) => string)\",\n      \"description\": \"When the item values are objects (`<Combobox.Item value={object}>`), this function converts the object value to a string representation for display in the input.\\nIf the shape of the object is `{ value, label }`, the label will be used automatically without needing to specify this prop.\",\n      \"detailedType\": \"((itemValue: Value) => string) | undefined\"\n    },\n    \"itemToStringValue\": {\n      \"type\": \"((itemValue: Value) => string)\",\n      \"description\": \"When the item values are objects (`<Combobox.Item value={object}>`), this function converts the object value to a string representation for form submission.\\nIf the shape of the object is `{ value, label }`, the value will be used automatically without needing to specify this prop.\",\n      \"detailedType\": \"((itemValue: Value) => string) | undefined\"\n    },\n    \"items\": {\n      \"type\": \"any[] | Group[]\",\n      \"description\": \"The items to be displayed in the list.\\nCan be either a flat array of items or an array of groups with items.\",\n      \"detailedType\": \"any[] | Group[] | undefined\"\n    },\n    \"limit\": {\n      \"type\": \"number\",\n      \"default\": \"-1\",\n      \"description\": \"The maximum number of items to display in the list.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"locale\": {\n      \"type\": \"Intl.LocalesArgument\",\n      \"description\": \"The locale to use for string comparison.\\nDefaults to the user's runtime locale.\",\n      \"detailedType\": \"Intl.LocalesArgument | undefined\"\n    },\n    \"loopFocus\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether to loop keyboard focus back to the input when the end of the list is reached while using the arrow keys. The first item can then be reached by pressing <kbd>ArrowDown</kbd> again from the input, or the last item can be reached by pressing <kbd>ArrowUp</kbd> from the input.\\nThe input is always included in the focus loop per [ARIA Authoring Practices](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/).\\nWhen disabled, focus does not move when on the last element and the user presses <kbd>ArrowDown</kbd>, or when on the first element and the user presses <kbd>ArrowUp</kbd>.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"modal\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Determines if the popup enters a modal state when open.\\n- `true`: user interaction is limited to the popup: document page scroll is locked and pointer interactions on outside elements are disabled.\\n- `false`: user interaction with the rest of the document is allowed.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"multiple\": {\n      \"type\": \"boolean | undefined\",\n      \"default\": \"false\",\n      \"description\": \"Whether multiple items can be selected.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"onItemHighlighted\": {\n      \"type\": \"((highlightedValue: Value | undefined, eventDetails: Combobox.Root.HighlightEventDetails) => void)\",\n      \"description\": \"Callback fired when an item is highlighted or unhighlighted.\\nReceives the highlighted item value (or `undefined` if no item is highlighted) and event details with a `reason` property describing why the highlight changed.\\nThe `reason` can be:\\n- `'keyboard'`: the highlight changed due to keyboard navigation.\\n- `'pointer'`: the highlight changed due to pointer hovering.\\n- `'none'`: the highlight changed programmatically.\",\n      \"detailedType\": \"| ((\\n    highlightedValue: Value | undefined,\\n    eventDetails: Combobox.Root.HighlightEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"onOpenChangeComplete\": {\n      \"type\": \"((open: boolean) => void)\",\n      \"description\": \"Event handler called after any animations complete when the popup is opened or closed.\",\n      \"detailedType\": \"((open: boolean) => void) | undefined\"\n    },\n    \"openOnInputClick\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether the popup opens when clicking the input.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"virtualized\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the items are being externally virtualized.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component should ignore user interaction.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"readOnly\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the user should be unable to choose a different option from the popup.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"required\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the user must choose a value before submitting a form.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"inputRef\": {\n      \"type\": \"Ref<HTMLInputElement>\",\n      \"description\": \"A ref to the hidden input element.\",\n      \"detailedType\": \"React.Ref<HTMLInputElement> | undefined\"\n    },\n    \"id\": {\n      \"type\": \"string\",\n      \"description\": \"The id of the component.\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"children\": {\n      \"type\": \"ReactNode\",\n      \"detailedType\": \"React.ReactNode | undefined\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/combobox-row.json",
    "content": "{\n  \"name\": \"ComboboxRow\",\n  \"description\": \"Displays a single row of items in a grid list.\\nEnable `grid` on the root component to turn the listbox into a grid.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Combobox.Row.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Combobox.Row.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Combobox.Row.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Combobox.Row.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Combobox.Row.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Combobox.Row.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/combobox-status.json",
    "content": "{\n  \"name\": \"ComboboxStatus\",\n  \"description\": \"Displays a status message whose content changes are announced politely to screen readers.\\nUseful for conveying the status of an asynchronously loaded list.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Combobox.Status.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Combobox.Status.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Combobox.Status.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Combobox.Status.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Combobox.Status.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Combobox.Status.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/combobox-trigger.json",
    "content": "{\n  \"name\": \"ComboboxTrigger\",\n  \"description\": \"A button that opens the popup.\\nRenders a `<button>` element.\",\n  \"props\": {\n    \"nativeButton\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether the component renders a native `<button>` element when replacing it\\nvia the `render` prop.\\nSet to `false` if the rendered element is not a button (e.g. `<div>`).\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component should ignore user interaction.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Combobox.Trigger.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Combobox.Trigger.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Combobox.Trigger.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Combobox.Trigger.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Combobox.Trigger.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Combobox.Trigger.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-popup-open\": {\n      \"description\": \"Present when the corresponding popup is open.\"\n    },\n    \"data-popup-side\": {\n      \"description\": \"Indicates which side the corresponding popup is positioned relative to its anchor.\",\n      \"type\": \"'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start' | null\"\n    },\n    \"data-list-empty\": {\n      \"description\": \"Present when the corresponding items list is empty.\"\n    },\n    \"data-pressed\": {\n      \"description\": \"Present when the trigger is pressed.\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the component is disabled.\"\n    },\n    \"data-readonly\": {\n      \"description\": \"Present when the component is readonly.\"\n    },\n    \"data-required\": {\n      \"description\": \"Present when the component is required.\"\n    },\n    \"data-valid\": {\n      \"description\": \"Present when the component is in valid state (when wrapped in Field.Root).\"\n    },\n    \"data-invalid\": {\n      \"description\": \"Present when the component is in invalid state (when wrapped in Field.Root).\"\n    },\n    \"data-dirty\": {\n      \"description\": \"Present when the component's value has changed (when wrapped in Field.Root).\"\n    },\n    \"data-touched\": {\n      \"description\": \"Present when the component has been touched (when wrapped in Field.Root).\"\n    },\n    \"data-filled\": {\n      \"description\": \"Present when the component has a value (when wrapped in Field.Root).\"\n    },\n    \"data-focused\": {\n      \"description\": \"Present when the trigger is focused (when wrapped in Field.Root).\"\n    },\n    \"data-placeholder\": {\n      \"description\": \"Present when the combobox doesn't have a value.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/combobox-value.json",
    "content": "{\n  \"name\": \"ComboboxValue\",\n  \"description\": \"The current value of the combobox.\\nDoesn't render its own HTML element.\",\n  \"props\": {\n    \"placeholder\": {\n      \"type\": \"ReactNode\",\n      \"description\": \"The placeholder value to display when no value is selected.\\nThis is overridden by `children` if specified, or by a null item's label in `items`.\",\n      \"detailedType\": \"React.ReactNode\"\n    },\n    \"children\": {\n      \"type\": \"ReactNode | ((selectedValue: any) => ReactNode)\",\n      \"detailedType\": \"| React.ReactNode\\n| ((selectedValue: any) => ReactNode)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/context-menu-root.json",
    "content": "{\n  \"name\": \"ContextMenuRoot\",\n  \"description\": \"A component that creates a context menu activated by right clicking or long pressing.\\nDoesn’t render its own HTML element.\",\n  \"props\": {\n    \"defaultOpen\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the menu is initially open.\\n\\nTo render a controlled menu, use the `open` prop instead.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"open\": {\n      \"type\": \"boolean\",\n      \"description\": \"Whether the menu is currently open.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"onOpenChange\": {\n      \"type\": \"((open: boolean, eventDetails: ContextMenu.Root.ChangeEventDetails) => void)\",\n      \"description\": \"Event handler called when the menu is opened or closed.\",\n      \"detailedType\": \"| ((\\n    open: boolean,\\n    eventDetails: ContextMenu.Root.ChangeEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"highlightItemOnHover\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether moving the pointer over items should highlight them.\\nDisabling this prop allows CSS `:hover` to be differentiated from the `:focus` (`data-highlighted`) state.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"actionsRef\": {\n      \"type\": \"RefObject<Menu.Root.Actions | null>\",\n      \"description\": \"A ref to imperative actions.\\n- `unmount`: When specified, the menu will not be unmounted when closed.\\n   Instead, the `unmount` function must be called to unmount the menu manually.\\n  Useful when the menu's animation is controlled by an external library.\\n- `close`: When specified, the menu can be closed imperatively.\",\n      \"detailedType\": \"| React.RefObject<Menu.Root.Actions | null>\\n| undefined\"\n    },\n    \"closeParentOnEsc\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"When in a submenu, determines whether pressing the Escape key\\ncloses the entire menu, or only the current child menu.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"defaultTriggerId\": {\n      \"type\": \"string | null\",\n      \"description\": \"ID of the trigger that the popover is associated with.\\nThis is useful in conjunction with the `defaultOpen` prop to create an initially open popover.\",\n      \"detailedType\": \"string | null | undefined\"\n    },\n    \"handle\": {\n      \"type\": \"Menu.Handle<unknown>\",\n      \"description\": \"A handle to associate the menu with a trigger.\\nIf specified, allows external triggers to control the menu's open state.\",\n      \"detailedType\": \"{} | undefined\"\n    },\n    \"loopFocus\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether to loop keyboard focus back to the first item\\nwhen the end of the list is reached while using the arrow keys.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"onOpenChangeComplete\": {\n      \"type\": \"((open: boolean) => void)\",\n      \"description\": \"Event handler called after any animations complete when the menu is closed.\",\n      \"detailedType\": \"((open: boolean) => void) | undefined\"\n    },\n    \"triggerId\": {\n      \"type\": \"string | null\",\n      \"description\": \"ID of the trigger that the popover is associated with.\\nThis is useful in conjunction with the `open` prop to create a controlled popover.\\nThere's no need to specify this prop when the popover is uncontrolled (i.e. when the `open` prop is not set).\",\n      \"detailedType\": \"string | null | undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component should ignore user interaction.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"orientation\": {\n      \"type\": \"Menu.Root.Orientation\",\n      \"default\": \"'vertical'\",\n      \"description\": \"The visual orientation of the menu.\\nControls whether roving focus uses up/down or left/right arrow keys.\",\n      \"detailedType\": \"'horizontal' | 'vertical' | undefined\"\n    },\n    \"children\": {\n      \"type\": \"ReactNode | PayloadChildRenderFunction<unknown>\",\n      \"description\": \"The content of the popover.\\nThis can be a regular React node or a render function that receives the `payload` of the active trigger.\",\n      \"detailedType\": \"| React.ReactNode\\n| ((arg: { payload: unknown }) => ReactNode)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/context-menu-trigger.json",
    "content": "{\n  \"name\": \"ContextMenuTrigger\",\n  \"description\": \"An area that opens the menu on right click or long press.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: ContextMenu.Trigger.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: ContextMenu.Trigger.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: ContextMenu.Trigger.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: ContextMenu.Trigger.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: ContextMenu.Trigger.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: ContextMenu.Trigger.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-popup-open\": {\n      \"description\": \"Present when the corresponding context menu is open.\"\n    },\n    \"data-pressed\": {\n      \"description\": \"Present when the trigger is pressed.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/csp-provider.json",
    "content": "{\n  \"name\": \"CSPProvider\",\n  \"description\": \"Provides a default Content Security Policy (CSP) configuration for Base UI components that\\nrequire inline `<style>` or `<script>` tags.\",\n  \"props\": {\n    \"disableStyleElements\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether inline `<style>` elements created by Base UI components should not be rendered. Instead, components must specify the CSS styles via custom class names or other methods.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"nonce\": {\n      \"type\": \"string\",\n      \"description\": \"The nonce value to apply to inline `<style>` and `<script>` tags.\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"children\": {\n      \"type\": \"ReactNode\",\n      \"detailedType\": \"React.ReactNode\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/dialog-backdrop.json",
    "content": "{\n  \"name\": \"DialogBackdrop\",\n  \"description\": \"An overlay displayed beneath the popup.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"forceRender\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the backdrop is forced to render even when nested.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Dialog.Backdrop.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Dialog.Backdrop.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Dialog.Backdrop.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Dialog.Backdrop.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Dialog.Backdrop.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Dialog.Backdrop.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the dialog is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the dialog is closed.\"\n    },\n    \"data-starting-style\": {\n      \"description\": \"Present when the dialog is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the dialog is animating out.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/dialog-close.json",
    "content": "{\n  \"name\": \"DialogClose\",\n  \"description\": \"A button that closes the dialog.\\nRenders a `<button>` element.\",\n  \"props\": {\n    \"nativeButton\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether the component renders a native `<button>` element when replacing it\\nvia the `render` prop.\\nSet to `false` if the rendered element is not a button (e.g. `<div>`).\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Dialog.Close.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Dialog.Close.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Dialog.Close.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Dialog.Close.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Dialog.Close.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Dialog.Close.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-disabled\": {\n      \"description\": \"Present when the button is disabled.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/dialog-description.json",
    "content": "{\n  \"name\": \"DialogDescription\",\n  \"description\": \"A paragraph with additional information about the dialog.\\nRenders a `<p>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Dialog.Description.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Dialog.Description.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Dialog.Description.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Dialog.Description.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Dialog.Description.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Dialog.Description.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/dialog-popup.json",
    "content": "{\n  \"name\": \"DialogPopup\",\n  \"description\": \"A container for the dialog contents.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"initialFocus\": {\n      \"type\": \"boolean | RefObject<HTMLElement | null> | ((openType: InteractionType) => boolean | void | HTMLElement | null)\",\n      \"description\": \"Determines the element to focus when the dialog is opened.\\n\\n- `false`: Do not move focus.\\n- `true`: Move focus based on the default behavior (first tabbable element or popup).\\n- `RefObject`: Move focus to the ref element.\\n- `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`).\\n  Return an element to focus, `true` to use the default behavior, or `false`/`undefined` to do nothing.\",\n      \"detailedType\": \"| boolean\\n| React.RefObject<HTMLElement | null>\\n| ((\\n    openType: InteractionType,\\n  ) => boolean | void | HTMLElement | null)\\n| undefined\"\n    },\n    \"finalFocus\": {\n      \"type\": \"boolean | RefObject<HTMLElement | null> | ((closeType: InteractionType) => boolean | void | HTMLElement | null)\",\n      \"description\": \"Determines the element to focus when the dialog is closed.\\n\\n- `false`: Do not move focus.\\n- `true`: Move focus based on the default behavior (trigger or previously focused element).\\n- `RefObject`: Move focus to the ref element.\\n- `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`).\\n  Return an element to focus, `true` to use the default behavior, or `false`/`undefined` to do nothing.\",\n      \"detailedType\": \"| boolean\\n| React.RefObject<HTMLElement | null>\\n| ((\\n    closeType: InteractionType,\\n  ) => boolean | void | HTMLElement | null)\\n| undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Dialog.Popup.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Dialog.Popup.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Dialog.Popup.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Dialog.Popup.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Dialog.Popup.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Dialog.Popup.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the dialog is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the dialog is closed.\"\n    },\n    \"data-nested\": {\n      \"description\": \"Present when the dialog is nested within another dialog.\"\n    },\n    \"data-nested-dialog-open\": {\n      \"description\": \"Present when the dialog has other open dialogs nested within it.\"\n    },\n    \"data-starting-style\": {\n      \"description\": \"Present when the dialog is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the dialog is animating out.\"\n    }\n  },\n  \"cssVariables\": {\n    \"--nested-dialogs\": {\n      \"description\": \"Indicates how many dialogs are nested within.\",\n      \"type\": \"number\"\n    }\n  }\n}\n"
  },
  {
    "path": "docs/reference/generated/dialog-portal.json",
    "content": "{\n  \"name\": \"DialogPortal\",\n  \"description\": \"A portal element that moves the popup to a different part of the DOM.\\nBy default, the portal element is appended to `<body>`.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"container\": {\n      \"type\": \"HTMLElement | ShadowRoot | RefObject<HTMLElement | ShadowRoot | null> | null\",\n      \"description\": \"A parent element to render the portal element into.\",\n      \"detailedType\": \"| HTMLElement\\n| ShadowRoot\\n| React.RefObject<HTMLElement | ShadowRoot | null>\\n| null\\n| undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Dialog.Portal.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Dialog.Portal.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Dialog.Portal.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Dialog.Portal.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"keepMounted\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to keep the portal mounted in the DOM while the popup is hidden.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Dialog.Portal.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Dialog.Portal.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/dialog-root.json",
    "content": "{\n  \"name\": \"DialogRoot\",\n  \"description\": \"Groups all parts of the dialog.\\nDoesn’t render its own HTML element.\",\n  \"props\": {\n    \"defaultOpen\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the dialog is initially open.\\n\\nTo render a controlled dialog, use the `open` prop instead.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"open\": {\n      \"type\": \"boolean\",\n      \"description\": \"Whether the dialog is currently open.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"onOpenChange\": {\n      \"type\": \"((open: boolean, eventDetails: Dialog.Root.ChangeEventDetails) => void)\",\n      \"description\": \"Event handler called when the dialog is opened or closed.\",\n      \"detailedType\": \"| ((\\n    open: boolean,\\n    eventDetails: Dialog.Root.ChangeEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"actionsRef\": {\n      \"type\": \"RefObject<Dialog.Root.Actions | null>\",\n      \"description\": \"A ref to imperative actions.\\n- `unmount`: When specified, the dialog will not be unmounted when closed.\\nInstead, the `unmount` function must be called to unmount the dialog manually.\\nUseful when the dialog's animation is controlled by an external library.\\n- `close`: Closes the dialog imperatively when called.\",\n      \"detailedType\": \"| React.RefObject<Dialog.Root.Actions | null>\\n| undefined\"\n    },\n    \"defaultTriggerId\": {\n      \"type\": \"string | null\",\n      \"description\": \"ID of the trigger that the dialog is associated with.\\nThis is useful in conjunction with the `defaultOpen` prop to create an initially open dialog.\",\n      \"detailedType\": \"string | null | undefined\"\n    },\n    \"disablePointerDismissal\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Determines whether the dialog should close on outside clicks.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"handle\": {\n      \"type\": \"Dialog.Handle<Payload>\",\n      \"description\": \"A handle to associate the dialog with a trigger.\\nIf specified, allows external triggers to control the dialog's open state.\\nCan be created with the Dialog.createHandle() method.\",\n      \"detailedType\": \"{} | undefined\"\n    },\n    \"modal\": {\n      \"type\": \"boolean | 'trap-focus'\",\n      \"default\": \"true\",\n      \"description\": \"Determines if the dialog enters a modal state when open.\\n- `true`: user interaction is limited to just the dialog: focus is trapped, document page scroll is locked, and pointer interactions on outside elements are disabled.\\n- `false`: user interaction with the rest of the document is allowed.\\n- `'trap-focus'`: focus is trapped inside the dialog, but document page scroll is not locked and pointer interactions outside of it remain enabled.\\n\\nWhen `modal` is `true` or `'trap-focus'`, render `<Dialog.Close>` inside `<Dialog.Popup>` so\\ntouch screen readers can escape the popup.\",\n      \"detailedType\": \"boolean | 'trap-focus' | undefined\"\n    },\n    \"onOpenChangeComplete\": {\n      \"type\": \"((open: boolean) => void)\",\n      \"description\": \"Event handler called after any animations complete when the dialog is opened or closed.\",\n      \"detailedType\": \"((open: boolean) => void) | undefined\"\n    },\n    \"triggerId\": {\n      \"type\": \"string | null\",\n      \"description\": \"ID of the trigger that the dialog is associated with.\\nThis is useful in conjunction with the `open` prop to create a controlled dialog.\\nThere's no need to specify this prop when the popover is uncontrolled (i.e. when the `open` prop is not set).\",\n      \"detailedType\": \"string | null | undefined\"\n    },\n    \"children\": {\n      \"type\": \"ReactNode | PayloadChildRenderFunction<Payload>\",\n      \"description\": \"The content of the dialog.\\nThis can be a regular React node or a render function that receives the `payload` of the active trigger.\",\n      \"detailedType\": \"| React.ReactNode\\n| ((arg: { payload: Payload | undefined }) => ReactNode)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/dialog-title.json",
    "content": "{\n  \"name\": \"DialogTitle\",\n  \"description\": \"A heading that labels the dialog.\\nRenders an `<h2>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Dialog.Title.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Dialog.Title.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Dialog.Title.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Dialog.Title.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Dialog.Title.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Dialog.Title.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/dialog-trigger.json",
    "content": "{\n  \"name\": \"DialogTrigger\",\n  \"description\": \"A button that opens the dialog.\\nRenders a `<button>` element.\",\n  \"props\": {\n    \"handle\": {\n      \"type\": \"Dialog.Handle<Payload>\",\n      \"description\": \"A handle to associate the trigger with a dialog.\\nCan be created with the Dialog.createHandle() method.\",\n      \"detailedType\": \"{} | undefined\"\n    },\n    \"nativeButton\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether the component renders a native `<button>` element when replacing it\\nvia the `render` prop.\\nSet to `false` if the rendered element is not a button (e.g. `<div>`).\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"payload\": {\n      \"type\": \"Payload\",\n      \"description\": \"A payload to pass to the dialog when it is opened.\",\n      \"detailedType\": \"Payload | undefined\"\n    },\n    \"id\": {\n      \"type\": \"string\",\n      \"description\": \"ID of the trigger. In addition to being forwarded to the rendered element,\\nit is also used to specify the active trigger for the dialogs in controlled mode (with the Dialog.Root `triggerId` prop).\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Dialog.Trigger.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Dialog.Trigger.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Dialog.Trigger.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Dialog.Trigger.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Dialog.Trigger.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Dialog.Trigger.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-popup-open\": {\n      \"description\": \"Present when the corresponding dialog is open.\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the trigger is disabled.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/dialog-viewport.json",
    "content": "{\n  \"name\": \"DialogViewport\",\n  \"description\": \"A positioning container for the dialog popup that can be made scrollable.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Dialog.Viewport.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Dialog.Viewport.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Dialog.Viewport.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Dialog.Viewport.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Dialog.Viewport.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Dialog.Viewport.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the dialog is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the dialog is closed.\"\n    },\n    \"data-nested\": {\n      \"description\": \"Present when the dialog is nested within another dialog.\"\n    },\n    \"data-nested-dialog-open\": {\n      \"description\": \"Present when the dialog has other open dialogs nested within it.\"\n    },\n    \"data-starting-style\": {\n      \"description\": \"Present when the dialog is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the dialog is animating out.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/direction-provider.json",
    "content": "{\n  \"name\": \"DirectionProvider\",\n  \"description\": \"Enables RTL behavior for Base UI components.\",\n  \"props\": {\n    \"direction\": {\n      \"type\": \"TextDirection\",\n      \"default\": \"'ltr'\",\n      \"description\": \"The reading direction of the text\",\n      \"detailedType\": \"'ltr' | 'rtl' | undefined\"\n    },\n    \"children\": {\n      \"type\": \"ReactNode\",\n      \"detailedType\": \"React.ReactNode\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/drawer-backdrop.json",
    "content": "{\n  \"name\": \"DrawerBackdrop\",\n  \"description\": \"An overlay displayed beneath the popup.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"forceRender\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the backdrop is forced to render even when nested.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Drawer.Backdrop.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Drawer.Backdrop.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Drawer.Backdrop.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Drawer.Backdrop.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Drawer.Backdrop.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Drawer.Backdrop.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the drawer is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the drawer is closed.\"\n    },\n    \"data-starting-style\": {\n      \"description\": \"Present when the drawer is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the drawer is animating out.\"\n    }\n  },\n  \"cssVariables\": {\n    \"--drawer-swipe-progress\": {\n      \"description\": \"The swipe progress of the drawer gesture.\",\n      \"type\": \"number\"\n    }\n  }\n}\n"
  },
  {
    "path": "docs/reference/generated/drawer-close.json",
    "content": "{\n  \"name\": \"DrawerClose\",\n  \"description\": \"A button that closes the drawer.\\nRenders a `<button>` element.\",\n  \"props\": {\n    \"nativeButton\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether the component renders a native `<button>` element when replacing it\\nvia the `render` prop.\\nSet to `false` if the rendered element is not a button (e.g. `<div>`).\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Drawer.Close.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Drawer.Close.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Drawer.Close.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Drawer.Close.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Drawer.Close.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Drawer.Close.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/drawer-content.json",
    "content": "{\n  \"name\": \"DrawerContent\",\n  \"description\": \"A container for the drawer contents.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Drawer.Content.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Drawer.Content.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Drawer.Content.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Drawer.Content.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Drawer.Content.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Drawer.Content.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/drawer-description.json",
    "content": "{\n  \"name\": \"DrawerDescription\",\n  \"description\": \"A paragraph with additional information about the drawer.\\nRenders a `<p>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Drawer.Description.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Drawer.Description.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Drawer.Description.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Drawer.Description.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Drawer.Description.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Drawer.Description.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/drawer-indent-background.json",
    "content": "{\n  \"name\": \"DrawerIndentBackground\",\n  \"description\": \"An element placed before `<Drawer.Indent>` to render a background layer that can be styled based on whether any drawer is open.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Drawer.IndentBackground.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((\\n    state: Drawer.IndentBackground.State,\\n  ) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Drawer.IndentBackground.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Drawer.IndentBackground.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Drawer.IndentBackground.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Drawer.IndentBackground.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/drawer-indent.json",
    "content": "{\n  \"name\": \"DrawerIndent\",\n  \"description\": \"A wrapper element intended to contain your app's main UI.\\nApplies `data-active` when any drawer within the nearest `<Drawer.Provider>` is open.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Drawer.Indent.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Drawer.Indent.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Drawer.Indent.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Drawer.Indent.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Drawer.Indent.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Drawer.Indent.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/drawer-popup.json",
    "content": "{\n  \"name\": \"DrawerPopup\",\n  \"description\": \"A container for the drawer contents.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"initialFocus\": {\n      \"type\": \"boolean | RefObject<HTMLElement | null> | ((openType: InteractionType) => boolean | void | HTMLElement | null)\",\n      \"description\": \"Determines the element to focus when the drawer is opened.\\n\\n- `false`: Do not move focus.\\n- `true`: Move focus based on the default behavior (first tabbable element or popup).\\n- `RefObject`: Move focus to the ref element.\\n- `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`).\\n  Return an element to focus, `true` to use the default behavior, or `false`/`undefined` to do nothing.\",\n      \"detailedType\": \"| boolean\\n| React.RefObject<HTMLElement | null>\\n| ((\\n    openType: InteractionType,\\n  ) => boolean | void | HTMLElement | null)\\n| undefined\"\n    },\n    \"finalFocus\": {\n      \"type\": \"boolean | RefObject<HTMLElement | null> | ((closeType: InteractionType) => boolean | void | HTMLElement | null)\",\n      \"description\": \"Determines the element to focus when the drawer is closed.\\n\\n- `false`: Do not move focus.\\n- `true`: Move focus based on the default behavior (trigger or previously focused element).\\n- `RefObject`: Move focus to the ref element.\\n- `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`).\\n  Return an element to focus, `true` to use the default behavior, or `false`/`undefined` to do nothing.\",\n      \"detailedType\": \"| boolean\\n| React.RefObject<HTMLElement | null>\\n| ((\\n    closeType: InteractionType,\\n  ) => boolean | void | HTMLElement | null)\\n| undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Drawer.Popup.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Drawer.Popup.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Drawer.Popup.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Drawer.Popup.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Drawer.Popup.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Drawer.Popup.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the drawer is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the drawer is closed.\"\n    },\n    \"data-expanded\": {\n      \"description\": \"Present when the drawer is at the expanded (full-height) snap point.\"\n    },\n    \"data-nested-drawer-open\": {\n      \"description\": \"Present when a nested drawer is open.\"\n    },\n    \"data-nested-drawer-swiping\": {\n      \"description\": \"Present when a nested drawer is being swiped.\"\n    },\n    \"data-swipe-direction\": {\n      \"description\": \"Indicates the swipe direction.\",\n      \"type\": \"'up' | 'down' | 'left' | 'right'\"\n    },\n    \"data-swipe-dismiss\": {\n      \"description\": \"Present when the drawer is dismissed by swiping.\"\n    },\n    \"data-swiping\": {\n      \"description\": \"Present when the drawer is being swiped.\"\n    },\n    \"data-starting-style\": {\n      \"description\": \"Present when the drawer is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the drawer is animating out.\"\n    }\n  },\n  \"cssVariables\": {\n    \"--drawer-frontmost-height\": {\n      \"description\": \"The height of the frontmost open drawer in the current nested drawer stack.\",\n      \"type\": \"CSS\"\n    },\n    \"--drawer-height\": {\n      \"description\": \"The height of the drawer popup.\",\n      \"type\": \"CSS\"\n    },\n    \"--drawer-snap-point-offset\": {\n      \"description\": \"The snap point offset used for translating the drawer.\",\n      \"type\": \"CSS\"\n    },\n    \"--drawer-swipe-movement-x\": {\n      \"description\": \"The swipe movement on the X axis.\",\n      \"type\": \"CSS\"\n    },\n    \"--drawer-swipe-movement-y\": {\n      \"description\": \"The swipe movement on the Y axis.\",\n      \"type\": \"CSS\"\n    },\n    \"--drawer-swipe-strength\": {\n      \"description\": \"A scalar (0.1-1) used to scale the swipe release transition duration in CSS.\",\n      \"type\": \"number\"\n    },\n    \"--nested-drawers\": {\n      \"description\": \"The number of nested drawers that are currently open.\",\n      \"type\": \"number\"\n    }\n  }\n}\n"
  },
  {
    "path": "docs/reference/generated/drawer-portal.json",
    "content": "{\n  \"name\": \"DrawerPortal\",\n  \"description\": \"A portal element that moves the popup to a different part of the DOM.\\nBy default, the portal element is appended to `<body>`.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"container\": {\n      \"type\": \"HTMLElement | ShadowRoot | RefObject<HTMLElement | ShadowRoot | null> | null\",\n      \"description\": \"A parent element to render the portal element into.\",\n      \"detailedType\": \"| HTMLElement\\n| ShadowRoot\\n| React.RefObject<HTMLElement | ShadowRoot | null>\\n| null\\n| undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Drawer.Portal.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Drawer.Portal.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Drawer.Portal.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Drawer.Portal.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"keepMounted\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to keep the portal mounted in the DOM while the popup is hidden.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Drawer.Portal.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Drawer.Portal.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/drawer-provider.json",
    "content": "{\n  \"name\": \"DrawerProvider\",\n  \"description\": \"Provides a shared context for coordinating global Drawer UI, such as indent/background effects based on whether any Drawer is open.\\nDoesn't render its own HTML element.\",\n  \"props\": {\n    \"children\": {\n      \"type\": \"ReactNode\",\n      \"detailedType\": \"React.ReactNode\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/drawer-root.json",
    "content": "{\n  \"name\": \"DrawerRoot\",\n  \"description\": \"Groups all parts of the drawer.\\nDoesn't render its own HTML element.\",\n  \"props\": {\n    \"defaultOpen\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the drawer is initially open.\\n\\nTo render a controlled drawer, use the `open` prop instead.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"open\": {\n      \"type\": \"boolean\",\n      \"description\": \"Whether the drawer is currently open.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"onOpenChange\": {\n      \"type\": \"((open: boolean, eventDetails: Drawer.Root.ChangeEventDetails) => void)\",\n      \"description\": \"Event handler called when the drawer is opened or closed.\",\n      \"detailedType\": \"| ((\\n    open: boolean,\\n    eventDetails: Drawer.Root.ChangeEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"snapPoints\": {\n      \"type\": \"DrawerSnapPoint[]\",\n      \"description\": \"Snap points used to position the drawer.\\nUse numbers between 0 and 1 to represent fractions of the viewport height,\\nnumbers greater than 1 as pixel values, or strings in `px`/`rem` units\\n(for example, `'148px'` or `'30rem'`).\",\n      \"detailedType\": \"DrawerSnapPoint[] | undefined\"\n    },\n    \"defaultSnapPoint\": {\n      \"type\": \"DrawerSnapPoint | null\",\n      \"description\": \"The initial snap point value when uncontrolled.\",\n      \"detailedType\": \"number | string | null | undefined\"\n    },\n    \"snapPoint\": {\n      \"type\": \"DrawerSnapPoint | null\",\n      \"description\": \"The currently active snap point. Use with `onSnapPointChange` to control the snap point.\",\n      \"detailedType\": \"number | string | null | undefined\"\n    },\n    \"onSnapPointChange\": {\n      \"type\": \"((snapPoint: DrawerSnapPoint | null, eventDetails: Drawer.Root.SnapPointChangeEventDetails) => void)\",\n      \"description\": \"Callback fired when the snap point changes.\",\n      \"detailedType\": \"| ((\\n    snapPoint: DrawerSnapPoint | null,\\n    eventDetails: Drawer.Root.SnapPointChangeEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"actionsRef\": {\n      \"type\": \"RefObject<Drawer.Root.Actions | null>\",\n      \"description\": \"A ref to imperative actions.\\n- `unmount`: When specified, the drawer will not be unmounted when closed.\\nInstead, the `unmount` function must be called to unmount the drawer manually.\\nUseful when the drawer's animation is controlled by an external library.\\n- `close`: Closes the drawer imperatively when called.\",\n      \"detailedType\": \"| React.RefObject<Drawer.Root.Actions | null>\\n| undefined\"\n    },\n    \"defaultTriggerId\": {\n      \"type\": \"string | null\",\n      \"description\": \"ID of the trigger that the drawer is associated with.\\nThis is useful in conjunction with the `defaultOpen` prop to create an initially open drawer.\",\n      \"detailedType\": \"string | null | undefined\"\n    },\n    \"disablePointerDismissal\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Determines whether the drawer should close on outside clicks.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"handle\": {\n      \"type\": \"Drawer.Handle<Payload>\",\n      \"description\": \"A handle to associate the drawer with a trigger.\\nIf specified, allows detached triggers to control the drawer's open state.\\nCan be created with the Drawer.createHandle() method.\",\n      \"detailedType\": \"{} | undefined\"\n    },\n    \"modal\": {\n      \"type\": \"boolean | 'trap-focus'\",\n      \"default\": \"true\",\n      \"description\": \"Determines if the drawer enters a modal state when open.\\n- `true`: user interaction is limited to just the drawer: focus is trapped, document page scroll is locked, and pointer interactions on outside elements are disabled.\\n- `false`: user interaction with the rest of the document is allowed.\\n- `'trap-focus'`: focus is trapped inside the drawer, but document page scroll is not locked and pointer interactions outside of it remain enabled.\",\n      \"detailedType\": \"boolean | 'trap-focus' | undefined\"\n    },\n    \"onOpenChangeComplete\": {\n      \"type\": \"((open: boolean) => void)\",\n      \"description\": \"Event handler called after any animations complete when the drawer is opened or closed.\",\n      \"detailedType\": \"((open: boolean) => void) | undefined\"\n    },\n    \"snapToSequentialPoints\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Disables velocity-based snap skipping so drag distance determines the next snap point.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"swipeDirection\": {\n      \"type\": \"DrawerSwipeDirection\",\n      \"default\": \"'down'\",\n      \"description\": \"The swipe direction used to dismiss the drawer.\",\n      \"detailedType\": \"'up' | 'down' | 'left' | 'right' | undefined\"\n    },\n    \"triggerId\": {\n      \"type\": \"string | null\",\n      \"description\": \"ID of the trigger that the drawer is associated with.\\nThis is useful in conjunction with the `open` prop to create a controlled drawer.\\nThere's no need to specify this prop when the drawer is uncontrolled (i.e. when the `open` prop is not set).\",\n      \"detailedType\": \"string | null | undefined\"\n    },\n    \"children\": {\n      \"type\": \"ReactNode | PayloadChildRenderFunction<Payload>\",\n      \"description\": \"The content of the drawer.\",\n      \"detailedType\": \"| React.ReactNode\\n| ((arg: { payload: Payload | undefined }) => ReactNode)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/drawer-swipe-area.json",
    "content": "{\n  \"name\": \"DrawerSwipeArea\",\n  \"description\": \"An invisible area that listens for swipe gestures to open the drawer.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"swipeDirection\": {\n      \"type\": \"DrawerSwipeDirection\",\n      \"description\": \"The swipe direction that opens the drawer.\\nDefaults to the opposite of `Drawer.Root` `swipeDirection`.\",\n      \"detailedType\": \"'up' | 'down' | 'left' | 'right' | undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the swipe area is disabled.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Drawer.SwipeArea.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Drawer.SwipeArea.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Drawer.SwipeArea.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Drawer.SwipeArea.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Drawer.SwipeArea.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Drawer.SwipeArea.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the drawer is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the drawer is closed.\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the swipe area is disabled.\"\n    },\n    \"data-swipe-direction\": {\n      \"description\": \"Indicates the swipe direction.\",\n      \"type\": \"'up' | 'down' | 'left' | 'right'\"\n    },\n    \"data-swiping\": {\n      \"description\": \"Present when the drawer is being swiped.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/drawer-title.json",
    "content": "{\n  \"name\": \"DrawerTitle\",\n  \"description\": \"A heading that labels the drawer.\\nRenders an `<h2>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Drawer.Title.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Drawer.Title.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Drawer.Title.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Drawer.Title.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Drawer.Title.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Drawer.Title.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/drawer-trigger.json",
    "content": "{\n  \"name\": \"DrawerTrigger\",\n  \"description\": \"A button that opens the drawer.\\nRenders a `<button>` element.\",\n  \"props\": {\n    \"handle\": {\n      \"type\": \"DrawerHandle<Payload>\",\n      \"description\": \"A handle to associate the trigger with a drawer.\\nCan be created with the Drawer.createHandle() method.\",\n      \"detailedType\": \"{} | undefined\"\n    },\n    \"nativeButton\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether the component renders a native `<button>` element when replacing it\\nvia the `render` prop.\\nSet to `false` if the rendered element is not a button (e.g. `<div>`).\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"payload\": {\n      \"type\": \"Payload\",\n      \"description\": \"A payload to pass to the drawer when it is opened.\",\n      \"detailedType\": \"Payload | undefined\"\n    },\n    \"id\": {\n      \"type\": \"string\",\n      \"description\": \"ID of the trigger. In addition to being forwarded to the rendered element,\\nit is also used to specify the active trigger for drawers in controlled mode (with the Drawer.Root `triggerId` prop).\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Drawer.Trigger.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Drawer.Trigger.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Drawer.Trigger.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Drawer.Trigger.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Drawer.Trigger.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Drawer.Trigger.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/drawer-viewport.json",
    "content": "{\n  \"name\": \"DrawerViewport\",\n  \"description\": \"A positioning container for the drawer popup that can be made scrollable.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Drawer.Viewport.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Drawer.Viewport.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Drawer.Viewport.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Drawer.Viewport.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Drawer.Viewport.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Drawer.Viewport.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the drawer is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the drawer is closed.\"\n    },\n    \"data-nested\": {\n      \"description\": \"Present when the drawer is nested within another drawer.\"\n    },\n    \"data-starting-style\": {\n      \"description\": \"Present when the drawer is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the drawer is animating out.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/field-control.json",
    "content": "{\n  \"name\": \"FieldControl\",\n  \"description\": \"The form control to label and validate.\\nRenders an `<input>` element.\\n\\nYou can omit this part and use any Base UI input component instead. For example,\\n[Input](https://base-ui.com/react/components/input), [Checkbox](https://base-ui.com/react/components/checkbox),\\nor [Select](https://base-ui.com/react/components/select), among others, will work with Field out of the box.\",\n  \"props\": {\n    \"defaultValue\": {\n      \"type\": \"string | number | string[]\",\n      \"detailedType\": \"string | number | string[] | undefined\"\n    },\n    \"onValueChange\": {\n      \"type\": \"((value: string, eventDetails: Field.Control.ChangeEventDetails) => void)\",\n      \"description\": \"Callback fired when the `value` changes. Use when controlled.\",\n      \"detailedType\": \"| ((\\n    value: string,\\n    eventDetails: Field.Control.ChangeEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Field.Control.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Field.Control.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Field.Control.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Field.Control.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Field.Control.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Field.Control.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-disabled\": {\n      \"description\": \"Present when the field is disabled.\"\n    },\n    \"data-valid\": {\n      \"description\": \"Present when the field is in valid state.\"\n    },\n    \"data-invalid\": {\n      \"description\": \"Present when the field is in invalid state.\"\n    },\n    \"data-dirty\": {\n      \"description\": \"Present when the field's value has changed.\"\n    },\n    \"data-touched\": {\n      \"description\": \"Present when the field has been touched.\"\n    },\n    \"data-filled\": {\n      \"description\": \"Present when the field is filled.\"\n    },\n    \"data-focused\": {\n      \"description\": \"Present when the field control is focused.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/field-description.json",
    "content": "{\n  \"name\": \"FieldDescription\",\n  \"description\": \"A paragraph with additional information about the field.\\nRenders a `<p>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Field.Description.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Field.Description.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Field.Description.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Field.Description.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Field.Description.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Field.Description.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-disabled\": {\n      \"description\": \"Present when the field is disabled.\"\n    },\n    \"data-valid\": {\n      \"description\": \"Present when the field is in valid state.\"\n    },\n    \"data-invalid\": {\n      \"description\": \"Present when the field is in invalid state.\"\n    },\n    \"data-dirty\": {\n      \"description\": \"Present when the field's value has changed.\"\n    },\n    \"data-touched\": {\n      \"description\": \"Present when the field has been touched.\"\n    },\n    \"data-filled\": {\n      \"description\": \"Present when the field is filled.\"\n    },\n    \"data-focused\": {\n      \"description\": \"Present when the field control is focused.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/field-error.json",
    "content": "{\n  \"name\": \"FieldError\",\n  \"description\": \"An error message displayed if the field control fails validation.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"match\": {\n      \"type\": \"boolean | 'valid' | 'badInput' | 'customError' | 'patternMismatch' | 'rangeOverflow' | 'rangeUnderflow' | 'stepMismatch' | 'tooLong' | 'tooShort' | 'typeMismatch' | 'valueMissing'\",\n      \"description\": \"Determines whether to show the error message according to the field’s\\n[ValidityState](https://developer.mozilla.org/en-US/docs/Web/API/ValidityState).\\nSpecifying `true` will always show the error message, and lets external libraries\\ncontrol the visibility.\",\n      \"detailedType\": \"| boolean\\n| 'valid'\\n| 'badInput'\\n| 'customError'\\n| 'patternMismatch'\\n| 'rangeOverflow'\\n| 'rangeUnderflow'\\n| 'stepMismatch'\\n| 'tooLong'\\n| 'tooShort'\\n| 'typeMismatch'\\n| 'valueMissing'\\n| undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Field.Error.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Field.Error.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Field.Error.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Field.Error.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Field.Error.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Field.Error.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-disabled\": {\n      \"description\": \"Present when the field is disabled.\"\n    },\n    \"data-valid\": {\n      \"description\": \"Present when the field is in valid state.\"\n    },\n    \"data-invalid\": {\n      \"description\": \"Present when the field is in invalid state.\"\n    },\n    \"data-dirty\": {\n      \"description\": \"Present when the field's value has changed.\"\n    },\n    \"data-touched\": {\n      \"description\": \"Present when the field has been touched.\"\n    },\n    \"data-filled\": {\n      \"description\": \"Present when the field is filled.\"\n    },\n    \"data-focused\": {\n      \"description\": \"Present when the field control is focused.\"\n    },\n    \"data-starting-style\": {\n      \"description\": \"Present when the error message is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the error message is animating out.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/field-item.json",
    "content": "{\n  \"name\": \"FieldItem\",\n  \"description\": \"Groups individual items in a checkbox group or radio group with a label and description.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the wrapped control should ignore user interaction.\\nThe `disabled` prop on `<Field.Root>` takes precedence over this.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Field.Item.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Field.Item.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Field.Item.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Field.Item.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Field.Item.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Field.Item.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/field-label.json",
    "content": "{\n  \"name\": \"FieldLabel\",\n  \"description\": \"An accessible label that is automatically associated with the field control.\\nRenders a `<label>` element.\",\n  \"props\": {\n    \"nativeLabel\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether the component renders a native `<label>` element when replacing it via the `render` prop.\\nSet to `false` if the rendered element is not a label (e.g. `<div>`).\\n\\nThis is useful to avoid inheriting label behaviors on `<button>` controls (such as `<Select.Trigger>` and `<Combobox.Trigger>`), including avoiding `:hover` on the button when hovering the label, and preventing clicks on the label from firing on the button.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Field.Label.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Field.Label.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Field.Label.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Field.Label.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Field.Label.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Field.Label.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-disabled\": {\n      \"description\": \"Present when the field is disabled.\"\n    },\n    \"data-valid\": {\n      \"description\": \"Present when the field is in valid state.\"\n    },\n    \"data-invalid\": {\n      \"description\": \"Present when the field is in invalid state.\"\n    },\n    \"data-dirty\": {\n      \"description\": \"Present when the field's value has changed.\"\n    },\n    \"data-touched\": {\n      \"description\": \"Present when the field has been touched.\"\n    },\n    \"data-filled\": {\n      \"description\": \"Present when the field is filled.\"\n    },\n    \"data-focused\": {\n      \"description\": \"Present when the field control is focused.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/field-root.json",
    "content": "{\n  \"name\": \"FieldRoot\",\n  \"description\": \"Groups all parts of the field.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"name\": {\n      \"type\": \"string\",\n      \"description\": \"Identifies the field when a form is submitted.\\nTakes precedence over the `name` prop on the `<Field.Control>` component.\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"actionsRef\": {\n      \"type\": \"RefObject<Field.Root.Actions | null>\",\n      \"description\": \"A ref to imperative actions.\\n- `validate`: Validates the field when called.\",\n      \"detailedType\": \"| React.RefObject<Field.Root.Actions | null>\\n| undefined\"\n    },\n    \"dirty\": {\n      \"type\": \"boolean\",\n      \"description\": \"Whether the field's value has been changed from its initial value.\\nUseful when the field state is controlled by an external library.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"touched\": {\n      \"type\": \"boolean\",\n      \"description\": \"Whether the field has been touched.\\nUseful when the field state is controlled by an external library.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component should ignore user interaction.\\nTakes precedence over the `disabled` prop on the `<Field.Control>` component.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"invalid\": {\n      \"type\": \"boolean\",\n      \"description\": \"Whether the field is invalid.\\nUseful when the field state is controlled by an external library.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"validate\": {\n      \"type\": \"((value: unknown, formValues: Form.Values<string, any>) => string | string[] | Promise<string | string[] | null> | null)\",\n      \"description\": \"A function for custom validation. Return a string or an array of strings with\\nthe error message(s) if the value is invalid, or `null` if the value is valid.\\nAsynchronous functions are supported, but they do not prevent form submission\\nwhen using `validationMode=\\\"onSubmit\\\"`.\",\n      \"detailedType\": \"| ((\\n    value: unknown,\\n    formValues: Form.Values<string, any>,\\n  ) =>\\n    | string\\n    | string[]\\n    | Promise<string | string[] | null>\\n    | null)\\n| undefined\"\n    },\n    \"validationMode\": {\n      \"type\": \"Form.ValidationMode\",\n      \"default\": \"'onSubmit'\",\n      \"description\": \"Determines when the field should be validated.\\nThis takes precedence over the `validationMode` prop on `<Form>`.\\n\\n- `onSubmit`: triggers validation when the form is submitted, and re-validates on change after submission.\\n- `onBlur`: triggers validation when the control loses focus.\\n- `onChange`: triggers validation on every change to the control value.\",\n      \"detailedType\": \"'onSubmit' | 'onBlur' | 'onChange' | undefined\"\n    },\n    \"validationDebounceTime\": {\n      \"type\": \"number\",\n      \"default\": \"0\",\n      \"description\": \"How long to wait between `validate` callbacks if\\n`validationMode=\\\"onChange\\\"` is used. Specified in milliseconds.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Field.Root.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Field.Root.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Field.Root.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Field.Root.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Field.Root.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Field.Root.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-disabled\": {\n      \"description\": \"Present when the field is disabled.\"\n    },\n    \"data-valid\": {\n      \"description\": \"Present when the field is valid.\"\n    },\n    \"data-invalid\": {\n      \"description\": \"Present when the field is invalid.\"\n    },\n    \"data-dirty\": {\n      \"description\": \"Present when the field's value has changed.\"\n    },\n    \"data-touched\": {\n      \"description\": \"Present when the field has been touched.\"\n    },\n    \"data-filled\": {\n      \"description\": \"Present when the field is filled.\"\n    },\n    \"data-focused\": {\n      \"description\": \"Present when the field control is focused.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/field-validity.json",
    "content": "{\n  \"name\": \"FieldValidity\",\n  \"description\": \"Used to display a custom message based on the field’s validity.\\nRequires `children` to be a function that accepts field validity state as an argument.\",\n  \"props\": {\n    \"children\": {\n      \"type\": \"((state: Field.Validity.State) => ReactNode)\",\n      \"required\": true,\n      \"description\": \"A function that accepts the field validity state as an argument.\\n\\n```jsx\\n<Field.Validity>\\n  {(validity) => {\\n    return <div>...</div>\\n  }}\\n</Field.Validity>\\n```\",\n      \"detailedType\": \"(state: Field.Validity.State) => ReactNode\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/fieldset-legend.json",
    "content": "{\n  \"name\": \"FieldsetLegend\",\n  \"description\": \"An accessible label that is automatically associated with the fieldset.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Fieldset.Legend.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Fieldset.Legend.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Fieldset.Legend.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Fieldset.Legend.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Fieldset.Legend.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Fieldset.Legend.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/fieldset-root.json",
    "content": "{\n  \"name\": \"FieldsetRoot\",\n  \"description\": \"Groups a shared legend with related controls.\\nRenders a `<fieldset>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Fieldset.Root.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Fieldset.Root.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Fieldset.Root.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Fieldset.Root.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Fieldset.Root.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Fieldset.Root.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/form.json",
    "content": "{\n  \"name\": \"Form\",\n  \"description\": \"A native form element with consolidated error handling.\\nRenders a `<form>` element.\",\n  \"props\": {\n    \"errors\": {\n      \"type\": \"Errors\",\n      \"description\": \"Validation errors returned externally, typically after submission by a server or a form action.\\nThis should be an object where keys correspond to the `name` attribute on `<Field.Root>`,\\nand values correspond to error(s) related to that field.\",\n      \"detailedType\": \"{} | undefined\"\n    },\n    \"actionsRef\": {\n      \"type\": \"RefObject<Form.Actions | null>\",\n      \"description\": \"A ref to imperative actions.\\n- `validate`: Validates all fields when called. Optionally pass a field name to validate a single field.\",\n      \"example\": \"```tsx\\n// validate all fields\\nactionsRef.current.validate();\\n\\n// validate one field\\nactionsRef.current.validate('email');\\n```\",\n      \"detailedType\": \"React.RefObject<Form.Actions | null> | undefined\"\n    },\n    \"onFormSubmit\": {\n      \"type\": \"((formValues: Record<string, any>, eventDetails: Form.SubmitEventDetails) => void)\",\n      \"description\": \"Event handler called when the form is submitted.\\n`preventDefault()` is called on the native submit event when used.\",\n      \"detailedType\": \"| ((\\n    formValues: Record<string, any>,\\n    eventDetails: Form.SubmitEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"validationMode\": {\n      \"type\": \"FormValidationMode\",\n      \"default\": \"'onSubmit'\",\n      \"description\": \"Determines when the form should be validated.\\nThe `validationMode` prop on `<Field.Root>` takes precedence over this.\\n\\n- `onSubmit` (default): validates the field when the form is submitted, afterwards fields will re-validate on change.\\n- `onBlur`: validates a field when it loses focus.\\n- `onChange`: validates the field on every change to its value.\",\n      \"detailedType\": \"'onSubmit' | 'onBlur' | 'onChange' | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Form.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Form.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Form.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Form.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((props: HTMLProps, state: Form.State) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/group-collection-provider.json",
    "content": "{\n  \"name\": \"GroupCollectionProvider\",\n  \"props\": {\n    \"items\": {\n      \"type\": \"any[]\",\n      \"required\": true\n    },\n    \"children\": {\n      \"type\": \"ReactNode\",\n      \"required\": true,\n      \"detailedType\": \"React.ReactNode\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/input.json",
    "content": "{\n  \"name\": \"Input\",\n  \"description\": \"A native input element that automatically works with [Field](https://base-ui.com/react/components/field).\\nRenders an `<input>` element.\",\n  \"props\": {\n    \"defaultValue\": {\n      \"type\": \"string | number | string[]\",\n      \"description\": \"The default value of the input. Use when uncontrolled.\",\n      \"detailedType\": \"string | number | string[] | undefined\"\n    },\n    \"value\": {\n      \"type\": \"string | number | string[]\",\n      \"description\": \"The value of the input. Use when controlled.\",\n      \"detailedType\": \"string | number | string[] | undefined\"\n    },\n    \"onValueChange\": {\n      \"type\": \"((value: string, eventDetails: Field.Control.ChangeEventDetails) => void)\",\n      \"description\": \"Callback fired when the `value` changes. Use when controlled.\",\n      \"detailedType\": \"| ((\\n    value: string,\\n    eventDetails: Field.Control.ChangeEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Input.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Input.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Input.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Input.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Input.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((props: HTMLProps, state: Input.State) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-disabled\": {\n      \"description\": \"Present when the input is disabled.\"\n    },\n    \"data-valid\": {\n      \"description\": \"Present when the input is in valid state (when wrapped in Field.Root).\"\n    },\n    \"data-invalid\": {\n      \"description\": \"Present when the input is in invalid state (when wrapped in Field.Root).\"\n    },\n    \"data-dirty\": {\n      \"description\": \"Present when the input's value has changed (when wrapped in Field.Root).\"\n    },\n    \"data-touched\": {\n      \"description\": \"Present when the input has been touched (when wrapped in Field.Root).\"\n    },\n    \"data-filled\": {\n      \"description\": \"Present when the input is filled (when wrapped in Field.Root).\"\n    },\n    \"data-focused\": {\n      \"description\": \"Present when the input is focused (when wrapped in Field.Root).\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/localization-provider.json",
    "content": "{\n  \"name\": \"LocalizationProvider\",\n  \"description\": \"Defines the temporal locale provider for Base UI temporal components.\\n\\nDoesn't render its own HTML element.\",\n  \"props\": {\n    \"temporalLocale\": {\n      \"type\": \"Locale\",\n      \"default\": \"en-US\",\n      \"description\": \"The locale to use in temporal components.\",\n      \"detailedType\": \"Locale | undefined\"\n    },\n    \"children\": {\n      \"type\": \"ReactNode\",\n      \"detailedType\": \"React.ReactNode\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/menu-arrow.json",
    "content": "{\n  \"name\": \"MenuArrow\",\n  \"description\": \"Displays an element positioned against the menu anchor.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Menu.Arrow.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Menu.Arrow.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Menu.Arrow.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Menu.Arrow.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Menu.Arrow.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Menu.Arrow.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the menu popup is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the menu popup is closed.\"\n    },\n    \"data-uncentered\": {\n      \"description\": \"Present when the menu arrow is uncentered.\"\n    },\n    \"data-align\": {\n      \"description\": \"Indicates how the popup is aligned relative to specified side.\",\n      \"type\": \"'start' | 'center' | 'end'\"\n    },\n    \"data-side\": {\n      \"description\": \"Indicates which side the popup is positioned relative to the trigger.\",\n      \"type\": \"'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/menu-backdrop.json",
    "content": "{\n  \"name\": \"MenuBackdrop\",\n  \"description\": \"An overlay displayed beneath the menu popup.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Menu.Backdrop.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Menu.Backdrop.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Menu.Backdrop.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Menu.Backdrop.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Menu.Backdrop.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Menu.Backdrop.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the menu is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the menu is closed.\"\n    },\n    \"data-starting-style\": {\n      \"description\": \"Present when the menu is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the menu is animating out.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/menu-checkbox-item-indicator.json",
    "content": "{\n  \"name\": \"MenuCheckboxItemIndicator\",\n  \"description\": \"Indicates whether the checkbox item is ticked.\\nRenders a `<span>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Menu.CheckboxItemIndicator.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((\\n    state: Menu.CheckboxItemIndicator.State,\\n  ) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Menu.CheckboxItemIndicator.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Menu.CheckboxItemIndicator.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"keepMounted\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to keep the HTML element in the DOM when the checkbox item is not checked.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Menu.CheckboxItemIndicator.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Menu.CheckboxItemIndicator.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-checked\": {\n      \"description\": \"Present when the menu checkbox item is checked.\"\n    },\n    \"data-unchecked\": {\n      \"description\": \"Present when the menu checkbox item is not checked.\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the menu checkbox item is disabled.\"\n    },\n    \"data-starting-style\": {\n      \"description\": \"Present when the indicator is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the indicator is animating out.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/menu-checkbox-item.json",
    "content": "{\n  \"name\": \"MenuCheckboxItem\",\n  \"description\": \"A menu item that toggles a setting on or off.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"label\": {\n      \"type\": \"string\",\n      \"description\": \"Overrides the text label to use when the item is matched during keyboard text navigation.\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"defaultChecked\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the checkbox item is initially ticked.\\n\\nTo render a controlled checkbox item, use the `checked` prop instead.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"checked\": {\n      \"type\": \"boolean\",\n      \"description\": \"Whether the checkbox item is currently ticked.\\n\\nTo render an uncontrolled checkbox item, use the `defaultChecked` prop instead.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"onCheckedChange\": {\n      \"type\": \"((checked: boolean, eventDetails: Menu.CheckboxItem.ChangeEventDetails) => void)\",\n      \"description\": \"Event handler called when the checkbox item is ticked or unticked.\",\n      \"detailedType\": \"| ((\\n    checked: boolean,\\n    eventDetails: Menu.CheckboxItem.ChangeEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"onClick\": {\n      \"type\": \"((event: BaseUIEvent<MouseEvent<HTMLDivElement, MouseEvent>>) => void)\",\n      \"description\": \"The click handler for the menu item.\",\n      \"detailedType\": \"| ((\\n    event: BaseUIEvent<\\n      MouseEvent<HTMLDivElement, MouseEvent>\\n    >,\\n  ) => void)\\n| undefined\"\n    },\n    \"closeOnClick\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to close the menu when the item is clicked.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"nativeButton\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component renders a native `<button>` element when replacing it\\nvia the `render` prop.\\nSet to `true` if the rendered element is a native button.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component should ignore user interaction.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Menu.CheckboxItem.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Menu.CheckboxItem.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Menu.CheckboxItem.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Menu.CheckboxItem.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Menu.CheckboxItem.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Menu.CheckboxItem.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-checked\": {\n      \"description\": \"Present when the menu checkbox item is checked.\"\n    },\n    \"data-unchecked\": {\n      \"description\": \"Present when the menu checkbox item is not checked.\"\n    },\n    \"data-highlighted\": {\n      \"description\": \"Present when the menu checkbox item is highlighted.\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the menu checkbox item is disabled.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/menu-group-label.json",
    "content": "{\n  \"name\": \"MenuGroupLabel\",\n  \"description\": \"An accessible label that is automatically associated with its parent group.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Menu.GroupLabel.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Menu.GroupLabel.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Menu.GroupLabel.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Menu.GroupLabel.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Menu.GroupLabel.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Menu.GroupLabel.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/menu-group.json",
    "content": "{\n  \"name\": \"MenuGroup\",\n  \"description\": \"Groups related menu items with the corresponding label.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"children\": {\n      \"type\": \"ReactNode\",\n      \"description\": \"The content of the component.\",\n      \"detailedType\": \"React.ReactNode\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Menu.Group.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Menu.Group.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Menu.Group.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Menu.Group.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Menu.Group.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Menu.Group.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/menu-item.json",
    "content": "{\n  \"name\": \"MenuItem\",\n  \"description\": \"An individual interactive item in the menu.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"label\": {\n      \"type\": \"string\",\n      \"description\": \"Overrides the text label to use when the item is matched during keyboard text navigation.\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"onClick\": {\n      \"type\": \"((event: BaseUIEvent<MouseEvent<HTMLDivElement, MouseEvent>>) => void)\",\n      \"description\": \"The click handler for the menu item.\",\n      \"detailedType\": \"| ((\\n    event: BaseUIEvent<\\n      MouseEvent<HTMLDivElement, MouseEvent>\\n    >,\\n  ) => void)\\n| undefined\"\n    },\n    \"closeOnClick\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether to close the menu when the item is clicked.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"nativeButton\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component renders a native `<button>` element when replacing it\\nvia the `render` prop.\\nSet to `true` if the rendered element is a native button.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component should ignore user interaction.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Menu.Item.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Menu.Item.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Menu.Item.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Menu.Item.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Menu.Item.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Menu.Item.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-highlighted\": {\n      \"description\": \"Present when the menu item is highlighted.\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the menu item is disabled.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/menu-link-item.json",
    "content": "{\n  \"name\": \"MenuLinkItem\",\n  \"description\": \"A link in the menu that can be used to navigate to a different page or section.\\nRenders an `<a>` element.\",\n  \"props\": {\n    \"label\": {\n      \"type\": \"string\",\n      \"description\": \"Overrides the text label to use when the item is matched during keyboard text navigation.\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"closeOnClick\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to close the menu when the item is clicked.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Menu.LinkItem.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Menu.LinkItem.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Menu.LinkItem.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Menu.LinkItem.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Menu.LinkItem.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Menu.LinkItem.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-highlighted\": {\n      \"description\": \"Present when the link is highlighted.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/menu-popup.json",
    "content": "{\n  \"name\": \"MenuPopup\",\n  \"description\": \"A container for the menu items.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"finalFocus\": {\n      \"type\": \"boolean | RefObject<HTMLElement | null> | ((closeType: InteractionType) => boolean | void | HTMLElement | null)\",\n      \"description\": \"Determines the element to focus when the menu is closed.\\n\\n- `false`: Do not move focus.\\n- `true`: Move focus based on the default behavior (trigger or previously focused element).\\n- `RefObject`: Move focus to the ref element.\\n- `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`).\\n  Return an element to focus, `true` to use the default behavior, or `false`/`undefined` to do nothing.\",\n      \"detailedType\": \"| boolean\\n| React.RefObject<HTMLElement | null>\\n| ((\\n    closeType: InteractionType,\\n  ) => boolean | void | HTMLElement | null)\\n| undefined\"\n    },\n    \"children\": {\n      \"type\": \"ReactNode\",\n      \"detailedType\": \"React.ReactNode\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Menu.Popup.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Menu.Popup.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Menu.Popup.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Menu.Popup.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Menu.Popup.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Menu.Popup.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the menu is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the menu is closed.\"\n    },\n    \"data-align\": {\n      \"description\": \"Indicates how the popup is aligned relative to specified side.\",\n      \"type\": \"'start' | 'center' | 'end'\"\n    },\n    \"data-instant\": {\n      \"description\": \"Present if animations should be instant.\",\n      \"type\": \"'click' | 'dismiss' | 'group' | 'trigger-change'\"\n    },\n    \"data-side\": {\n      \"description\": \"Indicates which side the popup is positioned relative to the trigger.\",\n      \"type\": \"'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'\"\n    },\n    \"data-starting-style\": {\n      \"description\": \"Present when the menu is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the menu is animating out.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/menu-portal.json",
    "content": "{\n  \"name\": \"MenuPortal\",\n  \"description\": \"A portal element that moves the popup to a different part of the DOM.\\nBy default, the portal element is appended to `<body>`.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"container\": {\n      \"type\": \"HTMLElement | ShadowRoot | RefObject<HTMLElement | ShadowRoot | null> | null\",\n      \"description\": \"A parent element to render the portal element into.\",\n      \"detailedType\": \"| HTMLElement\\n| ShadowRoot\\n| React.RefObject<HTMLElement | ShadowRoot | null>\\n| null\\n| undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Menu.Portal.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Menu.Portal.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Menu.Portal.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Menu.Portal.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"keepMounted\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to keep the portal mounted in the DOM while the popup is hidden.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Menu.Portal.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Menu.Portal.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/menu-positioner.json",
    "content": "{\n  \"name\": \"MenuPositioner\",\n  \"description\": \"Positions the menu popup against the trigger.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"disableAnchorTracking\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to disable the popup from tracking any layout shift of its positioning anchor.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"align\": {\n      \"type\": \"Align\",\n      \"default\": \"'center'\",\n      \"description\": \"How to align the popup relative to the specified side.\",\n      \"detailedType\": \"'start' | 'center' | 'end' | undefined\"\n    },\n    \"alignOffset\": {\n      \"type\": \"number | OffsetFunction\",\n      \"default\": \"0\",\n      \"description\": \"Additional offset along the alignment axis in pixels.\\nAlso accepts a function that returns the offset to read the dimensions of the anchor\\nand positioner elements, along with its side and alignment.\\n\\nThe function takes a `data` object parameter with the following properties:\\n- `data.anchor`: the dimensions of the anchor element with properties `width` and `height`.\\n- `data.positioner`: the dimensions of the positioner element with properties `width` and `height`.\\n- `data.side`: which side of the anchor element the positioner is aligned against.\\n- `data.align`: how the positioner is aligned relative to the specified side.\",\n      \"example\": \"```jsx\\n<Positioner\\n  alignOffset={({ side, align, anchor, positioner }) => {\\n    return side === 'top' || side === 'bottom'\\n      ? anchor.width\\n      : anchor.height;\\n  }}\\n/>\\n```\",\n      \"detailedType\": \"| number\\n| ((data: {\\n    side: Side\\n    align: Align\\n    anchor: { width: number; height: number }\\n    positioner: { width: number; height: number }\\n  }) => number)\\n| undefined\"\n    },\n    \"side\": {\n      \"type\": \"Side\",\n      \"default\": \"'bottom'\",\n      \"description\": \"Which side of the anchor element to align the popup against.\\nMay automatically change to avoid collisions.\",\n      \"detailedType\": \"| 'top'\\n| 'bottom'\\n| 'left'\\n| 'right'\\n| 'inline-end'\\n| 'inline-start'\\n| undefined\"\n    },\n    \"sideOffset\": {\n      \"type\": \"number | OffsetFunction\",\n      \"default\": \"0\",\n      \"description\": \"Distance between the anchor and the popup in pixels.\\nAlso accepts a function that returns the distance to read the dimensions of the anchor\\nand positioner elements, along with its side and alignment.\\n\\nThe function takes a `data` object parameter with the following properties:\\n- `data.anchor`: the dimensions of the anchor element with properties `width` and `height`.\\n- `data.positioner`: the dimensions of the positioner element with properties `width` and `height`.\\n- `data.side`: which side of the anchor element the positioner is aligned against.\\n- `data.align`: how the positioner is aligned relative to the specified side.\",\n      \"example\": \"```jsx\\n<Positioner\\n  sideOffset={({ side, align, anchor, positioner }) => {\\n    return side === 'top' || side === 'bottom'\\n      ? anchor.height\\n      : anchor.width;\\n  }}\\n/>\\n```\",\n      \"detailedType\": \"| number\\n| ((data: {\\n    side: Side\\n    align: Align\\n    anchor: { width: number; height: number }\\n    positioner: { width: number; height: number }\\n  }) => number)\\n| undefined\"\n    },\n    \"arrowPadding\": {\n      \"type\": \"number\",\n      \"default\": \"5\",\n      \"description\": \"Minimum distance to maintain between the arrow and the edges of the popup.\\n\\nUse it to prevent the arrow element from hanging out of the rounded corners of a popup.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"anchor\": {\n      \"type\": \"Element | VirtualElement | RefObject<Element | null> | (() => Element | VirtualElement | null) | null\",\n      \"description\": \"An element to position the popup against.\\nBy default, the popup will be positioned against the trigger.\",\n      \"detailedType\": \"| Element\\n| VirtualElement\\n| React.RefObject<Element | null>\\n| (() => Element | VirtualElement | null)\\n| null\\n| undefined\"\n    },\n    \"collisionAvoidance\": {\n      \"type\": \"CollisionAvoidance\",\n      \"description\": \"Determines how to handle collisions when positioning the popup.\\n\\n`side` controls overflow on the preferred placement axis (`top`/`bottom` or `left`/`right`):\\n- `'flip'`: keep the requested side when it fits; otherwise try the opposite side\\n  (`top` and `bottom`, or `left` and `right`).\\n- `'shift'`: never change side; keep the requested side and move the popup within\\n  the clipping boundary so it stays visible.\\n- `'none'`: do not correct side-axis overflow.\\n\\n`align` controls overflow on the alignment axis (`start`/`center`/`end`):\\n- `'flip'`: keep side, but swap `start` and `end` when the requested alignment overflows.\\n- `'shift'`: keep side and requested alignment, then nudge the popup along the\\n  alignment axis to fit.\\n- `'none'`: do not correct alignment-axis overflow.\\n\\n`fallbackAxisSide` controls fallback behavior on the perpendicular axis when the\\npreferred axis cannot fit:\\n- `'start'`: allow perpendicular fallback and try the logical start side first\\n  (`top` before `bottom`, or `left` before `right` in LTR).\\n- `'end'`: allow perpendicular fallback and try the logical end side first\\n  (`bottom` before `top`, or `right` before `left` in LTR).\\n- `'none'`: do not fallback to the perpendicular axis.\\n\\nWhen `side` is `'shift'`, explicitly setting `align` only supports `'shift'` or `'none'`.\\nIf `align` is omitted, it defaults to `'flip'`.\",\n      \"example\": \"```jsx\\n<Positioner\\n  collisionAvoidance={{\\n    side: 'shift',\\n    align: 'shift',\\n    fallbackAxisSide: 'none',\\n  }}\\n/>\\n```\",\n      \"detailedType\": \"| {\\n    side?: 'flip' | 'none'\\n    align?: 'flip' | 'shift' | 'none'\\n    fallbackAxisSide?: 'start' | 'end' | 'none'\\n  }\\n| {\\n    side?: 'shift' | 'none'\\n    align?: 'shift' | 'none'\\n    fallbackAxisSide?: 'start' | 'end' | 'none'\\n  }\\n| undefined\"\n    },\n    \"collisionBoundary\": {\n      \"type\": \"Boundary\",\n      \"default\": \"'clipping-ancestors'\",\n      \"description\": \"An element or a rectangle that delimits the area that the popup is confined to.\",\n      \"detailedType\": \"| 'clipping-ancestors'\\n| Element\\n| Element[]\\n| Rect\\n| undefined\"\n    },\n    \"collisionPadding\": {\n      \"type\": \"Padding\",\n      \"default\": \"5\",\n      \"description\": \"Additional space to maintain from the edge of the collision boundary.\",\n      \"detailedType\": \"| {\\n    top?: number\\n    right?: number\\n    bottom?: number\\n    left?: number\\n  }\\n| number\\n| undefined\"\n    },\n    \"sticky\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to maintain the popup in the viewport after\\nthe anchor element was scrolled out of view.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"positionMethod\": {\n      \"type\": \"'absolute' | 'fixed'\",\n      \"default\": \"'absolute'\",\n      \"description\": \"Determines which CSS `position` property to use.\",\n      \"detailedType\": \"'absolute' | 'fixed' | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Menu.Positioner.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Menu.Positioner.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Menu.Positioner.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Menu.Positioner.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Menu.Positioner.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Menu.Positioner.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the menu popup is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the menu popup is closed.\"\n    },\n    \"data-anchor-hidden\": {\n      \"description\": \"Present when the anchor is hidden.\"\n    },\n    \"data-align\": {\n      \"description\": \"Indicates how the popup is aligned relative to specified side.\",\n      \"type\": \"'start' | 'center' | 'end'\"\n    },\n    \"data-side\": {\n      \"description\": \"Indicates which side the popup is positioned relative to the trigger.\",\n      \"type\": \"'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'\"\n    }\n  },\n  \"cssVariables\": {\n    \"--anchor-height\": {\n      \"description\": \"The anchor's height.\",\n      \"type\": \"number\"\n    },\n    \"--anchor-width\": {\n      \"description\": \"The anchor's width.\",\n      \"type\": \"number\"\n    },\n    \"--available-height\": {\n      \"description\": \"The available height between the trigger and the edge of the viewport.\",\n      \"type\": \"number\"\n    },\n    \"--available-width\": {\n      \"description\": \"The available width between the trigger and the edge of the viewport.\",\n      \"type\": \"number\"\n    },\n    \"--transform-origin\": {\n      \"description\": \"The coordinates that this element is anchored to. Used for animations and transitions.\",\n      \"type\": \"string\"\n    }\n  }\n}\n"
  },
  {
    "path": "docs/reference/generated/menu-radio-group.json",
    "content": "{\n  \"name\": \"MenuRadioGroup\",\n  \"description\": \"Groups related radio items.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"defaultValue\": {\n      \"type\": \"any\",\n      \"description\": \"The uncontrolled value of the radio item that should be initially selected.\\n\\nTo render a controlled radio group, use the `value` prop instead.\"\n    },\n    \"value\": {\n      \"type\": \"any\",\n      \"description\": \"The controlled value of the radio item that should be currently selected.\\n\\nTo render an uncontrolled radio group, use the `defaultValue` prop instead.\"\n    },\n    \"onValueChange\": {\n      \"type\": \"((value: any, eventDetails: Menu.RadioGroup.ChangeEventDetails) => void)\",\n      \"description\": \"Function called when the selected value changes.\",\n      \"detailedType\": \"| ((\\n    value: any,\\n    eventDetails: Menu.RadioGroup.ChangeEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component should ignore user interaction.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"children\": {\n      \"type\": \"ReactNode\",\n      \"description\": \"The content of the component.\",\n      \"detailedType\": \"React.ReactNode\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Menu.RadioGroup.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Menu.RadioGroup.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Menu.RadioGroup.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Menu.RadioGroup.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Menu.RadioGroup.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Menu.RadioGroup.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/menu-radio-item-indicator.json",
    "content": "{\n  \"name\": \"MenuRadioItemIndicator\",\n  \"description\": \"Indicates whether the radio item is selected.\\nRenders a `<span>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Menu.RadioItemIndicator.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((\\n    state: Menu.RadioItemIndicator.State,\\n  ) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Menu.RadioItemIndicator.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Menu.RadioItemIndicator.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"keepMounted\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to keep the HTML element in the DOM when the radio item is inactive.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Menu.RadioItemIndicator.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Menu.RadioItemIndicator.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-checked\": {\n      \"description\": \"Present when the menu radio item is selected.\"\n    },\n    \"data-unchecked\": {\n      \"description\": \"Present when the menu radio item is not selected.\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the menu radio item is disabled.\"\n    },\n    \"data-starting-style\": {\n      \"description\": \"Present when the radio indicator is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the radio indicator is animating out.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/menu-radio-item.json",
    "content": "{\n  \"name\": \"MenuRadioItem\",\n  \"description\": \"A menu item that works like a radio button in a given group.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"label\": {\n      \"type\": \"string\",\n      \"description\": \"Overrides the text label to use when the item is matched during keyboard text navigation.\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"value\": {\n      \"type\": \"any\",\n      \"required\": true,\n      \"description\": \"Value of the radio item.\\nThis is the value that will be set in the Menu.RadioGroup when the item is selected.\"\n    },\n    \"onClick\": {\n      \"type\": \"((event: BaseUIEvent<MouseEvent<HTMLDivElement, MouseEvent>>) => void)\",\n      \"description\": \"The click handler for the menu item.\",\n      \"detailedType\": \"| ((\\n    event: BaseUIEvent<\\n      MouseEvent<HTMLDivElement, MouseEvent>\\n    >,\\n  ) => void)\\n| undefined\"\n    },\n    \"closeOnClick\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to close the menu when the item is clicked.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"nativeButton\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component renders a native `<button>` element when replacing it\\nvia the `render` prop.\\nSet to `true` if the rendered element is a native button.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component should ignore user interaction.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Menu.RadioItem.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Menu.RadioItem.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Menu.RadioItem.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Menu.RadioItem.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Menu.RadioItem.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Menu.RadioItem.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-checked\": {\n      \"description\": \"Present when the menu radio item is selected.\"\n    },\n    \"data-unchecked\": {\n      \"description\": \"Present when the menu radio item is not selected.\"\n    },\n    \"data-highlighted\": {\n      \"description\": \"Present when the menu radio item is highlighted.\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the menu radio item is disabled.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/menu-root.json",
    "content": "{\n  \"name\": \"MenuRoot\",\n  \"description\": \"Groups all parts of the menu.\\nDoesn’t render its own HTML element.\",\n  \"props\": {\n    \"defaultOpen\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the menu is initially open.\\n\\nTo render a controlled menu, use the `open` prop instead.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"open\": {\n      \"type\": \"boolean\",\n      \"description\": \"Whether the menu is currently open.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"onOpenChange\": {\n      \"type\": \"((open: boolean, eventDetails: Menu.Root.ChangeEventDetails) => void)\",\n      \"description\": \"Event handler called when the menu is opened or closed.\",\n      \"detailedType\": \"| ((\\n    open: boolean,\\n    eventDetails: Menu.Root.ChangeEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"highlightItemOnHover\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether moving the pointer over items should highlight them.\\nDisabling this prop allows CSS `:hover` to be differentiated from the `:focus` (`data-highlighted`) state.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"actionsRef\": {\n      \"type\": \"RefObject<Menu.Root.Actions | null>\",\n      \"description\": \"A ref to imperative actions.\\n- `unmount`: When specified, the menu will not be unmounted when closed.\\n   Instead, the `unmount` function must be called to unmount the menu manually.\\n  Useful when the menu's animation is controlled by an external library.\\n- `close`: When specified, the menu can be closed imperatively.\",\n      \"detailedType\": \"| React.RefObject<Menu.Root.Actions | null>\\n| undefined\"\n    },\n    \"closeParentOnEsc\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"When in a submenu, determines whether pressing the Escape key\\ncloses the entire menu, or only the current child menu.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"defaultTriggerId\": {\n      \"type\": \"string | null\",\n      \"description\": \"ID of the trigger that the popover is associated with.\\nThis is useful in conjunction with the `defaultOpen` prop to create an initially open popover.\",\n      \"detailedType\": \"string | null | undefined\"\n    },\n    \"handle\": {\n      \"type\": \"Menu.Handle<Payload>\",\n      \"description\": \"A handle to associate the menu with a trigger.\\nIf specified, allows external triggers to control the menu's open state.\",\n      \"detailedType\": \"{} | undefined\"\n    },\n    \"loopFocus\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether to loop keyboard focus back to the first item\\nwhen the end of the list is reached while using the arrow keys.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"modal\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Determines if the menu enters a modal state when open.\\n- `true`: user interaction is limited to the menu: document page scroll is locked and pointer interactions on outside elements are disabled.\\n- `false`: user interaction with the rest of the document is allowed.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"onOpenChangeComplete\": {\n      \"type\": \"((open: boolean) => void)\",\n      \"description\": \"Event handler called after any animations complete when the menu is closed.\",\n      \"detailedType\": \"((open: boolean) => void) | undefined\"\n    },\n    \"triggerId\": {\n      \"type\": \"string | null\",\n      \"description\": \"ID of the trigger that the popover is associated with.\\nThis is useful in conjunction with the `open` prop to create a controlled popover.\\nThere's no need to specify this prop when the popover is uncontrolled (i.e. when the `open` prop is not set).\",\n      \"detailedType\": \"string | null | undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component should ignore user interaction.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"orientation\": {\n      \"type\": \"Menu.Root.Orientation\",\n      \"default\": \"'vertical'\",\n      \"description\": \"The visual orientation of the menu.\\nControls whether roving focus uses up/down or left/right arrow keys.\",\n      \"detailedType\": \"'horizontal' | 'vertical' | undefined\"\n    },\n    \"children\": {\n      \"type\": \"ReactNode | PayloadChildRenderFunction<Payload>\",\n      \"description\": \"The content of the popover.\\nThis can be a regular React node or a render function that receives the `payload` of the active trigger.\",\n      \"detailedType\": \"| React.ReactNode\\n| ((arg: { payload: Payload | undefined }) => ReactNode)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/menu-submenu-root.json",
    "content": "{\n  \"name\": \"MenuSubmenuRoot\",\n  \"description\": \"Groups all parts of a submenu.\\nDoesn’t render its own HTML element.\",\n  \"props\": {\n    \"defaultOpen\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the menu is initially open.\\n\\nTo render a controlled menu, use the `open` prop instead.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"open\": {\n      \"type\": \"boolean\",\n      \"description\": \"Whether the menu is currently open.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"onOpenChange\": {\n      \"type\": \"((open: boolean, eventDetails: Menu.SubmenuRoot.ChangeEventDetails) => void)\",\n      \"description\": \"Event handler called when the menu is opened or closed.\",\n      \"detailedType\": \"| ((\\n    open: boolean,\\n    eventDetails: Menu.SubmenuRoot.ChangeEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"highlightItemOnHover\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether moving the pointer over items should highlight them.\\nDisabling this prop allows CSS `:hover` to be differentiated from the `:focus` (`data-highlighted`) state.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"actionsRef\": {\n      \"type\": \"RefObject<Menu.Root.Actions | null>\",\n      \"description\": \"A ref to imperative actions.\\n- `unmount`: When specified, the menu will not be unmounted when closed.\\n   Instead, the `unmount` function must be called to unmount the menu manually.\\n  Useful when the menu's animation is controlled by an external library.\\n- `close`: When specified, the menu can be closed imperatively.\",\n      \"detailedType\": \"| React.RefObject<Menu.Root.Actions | null>\\n| undefined\"\n    },\n    \"closeParentOnEsc\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"When in a submenu, determines whether pressing the Escape key\\ncloses the entire menu, or only the current child menu.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"defaultTriggerId\": {\n      \"type\": \"string | null\",\n      \"description\": \"ID of the trigger that the popover is associated with.\\nThis is useful in conjunction with the `defaultOpen` prop to create an initially open popover.\",\n      \"detailedType\": \"string | null | undefined\"\n    },\n    \"handle\": {\n      \"type\": \"Menu.Handle<unknown>\",\n      \"description\": \"A handle to associate the menu with a trigger.\\nIf specified, allows external triggers to control the menu's open state.\",\n      \"detailedType\": \"{} | undefined\"\n    },\n    \"loopFocus\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether to loop keyboard focus back to the first item\\nwhen the end of the list is reached while using the arrow keys.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"onOpenChangeComplete\": {\n      \"type\": \"((open: boolean) => void)\",\n      \"description\": \"Event handler called after any animations complete when the menu is closed.\",\n      \"detailedType\": \"((open: boolean) => void) | undefined\"\n    },\n    \"triggerId\": {\n      \"type\": \"string | null\",\n      \"description\": \"ID of the trigger that the popover is associated with.\\nThis is useful in conjunction with the `open` prop to create a controlled popover.\\nThere's no need to specify this prop when the popover is uncontrolled (i.e. when the `open` prop is not set).\",\n      \"detailedType\": \"string | null | undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component should ignore user interaction.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"orientation\": {\n      \"type\": \"Menu.Root.Orientation\",\n      \"default\": \"'vertical'\",\n      \"description\": \"The visual orientation of the menu.\\nControls whether roving focus uses up/down or left/right arrow keys.\",\n      \"detailedType\": \"'horizontal' | 'vertical' | undefined\"\n    },\n    \"children\": {\n      \"type\": \"ReactNode | PayloadChildRenderFunction<unknown>\",\n      \"description\": \"The content of the popover.\\nThis can be a regular React node or a render function that receives the `payload` of the active trigger.\",\n      \"detailedType\": \"| React.ReactNode\\n| ((arg: { payload: unknown }) => ReactNode)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/menu-submenu-trigger.json",
    "content": "{\n  \"name\": \"MenuSubmenuTrigger\",\n  \"description\": \"A menu item that opens a submenu.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"label\": {\n      \"type\": \"string\",\n      \"description\": \"Overrides the text label to use when the item is matched during keyboard text navigation.\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"onClick\": {\n      \"type\": \"((event: BaseUIEvent<MouseEvent<HTMLDivElement, MouseEvent>>) => void)\",\n      \"detailedType\": \"| ((\\n    event: BaseUIEvent<\\n      MouseEvent<HTMLDivElement, MouseEvent>\\n    >,\\n  ) => void)\\n| undefined\"\n    },\n    \"nativeButton\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component renders a native `<button>` element when replacing it\\nvia the `render` prop.\\nSet to `true` if the rendered element is a native button.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component should ignore user interaction.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"openOnHover\": {\n      \"type\": \"boolean\",\n      \"description\": \"Whether the menu should also open when the trigger is hovered.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"delay\": {\n      \"type\": \"number\",\n      \"default\": \"100\",\n      \"description\": \"How long to wait before the menu may be opened on hover. Specified in milliseconds.\\n\\nRequires the `openOnHover` prop.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"closeDelay\": {\n      \"type\": \"number\",\n      \"default\": \"0\",\n      \"description\": \"How long to wait before closing the menu that was opened on hover.\\nSpecified in milliseconds.\\n\\nRequires the `openOnHover` prop.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Menu.SubmenuTrigger.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Menu.SubmenuTrigger.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Menu.SubmenuTrigger.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Menu.SubmenuTrigger.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Menu.SubmenuTrigger.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Menu.SubmenuTrigger.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-popup-open\": {\n      \"description\": \"Present when the corresponding submenu is open.\"\n    },\n    \"data-highlighted\": {\n      \"description\": \"Present when the submenu trigger is highlighted.\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the submenu trigger is disabled.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/menu-trigger.json",
    "content": "{\n  \"name\": \"MenuTrigger\",\n  \"description\": \"A button that opens the menu.\\nRenders a `<button>` element.\",\n  \"props\": {\n    \"handle\": {\n      \"type\": \"Menu.Handle<Payload>\",\n      \"description\": \"A handle to associate the trigger with a menu.\",\n      \"detailedType\": \"{} | undefined\"\n    },\n    \"nativeButton\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether the component renders a native `<button>` element when replacing it\\nvia the `render` prop.\\nSet to `false` if the rendered element is not a button (e.g. `<div>`).\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"payload\": {\n      \"type\": \"Payload\",\n      \"description\": \"A payload to pass to the menu when it is opened.\",\n      \"detailedType\": \"Payload | undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component should ignore user interaction.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"openOnHover\": {\n      \"type\": \"boolean\",\n      \"description\": \"Whether the menu should also open when the trigger is hovered.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"delay\": {\n      \"type\": \"number\",\n      \"default\": \"100\",\n      \"description\": \"How long to wait before the menu may be opened on hover. Specified in milliseconds.\\n\\nRequires the `openOnHover` prop.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"closeDelay\": {\n      \"type\": \"number\",\n      \"default\": \"0\",\n      \"description\": \"How long to wait before closing the menu that was opened on hover.\\nSpecified in milliseconds.\\n\\nRequires the `openOnHover` prop.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"children\": {\n      \"type\": \"ReactNode\",\n      \"detailedType\": \"React.ReactNode\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Menu.Trigger.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Menu.Trigger.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Menu.Trigger.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Menu.Trigger.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Menu.Trigger.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Menu.Trigger.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-popup-open\": {\n      \"description\": \"Present when the corresponding menu is open.\"\n    },\n    \"data-pressed\": {\n      \"description\": \"Present when the trigger is pressed.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/menu-viewport.json",
    "content": "{\n  \"name\": \"MenuViewport\",\n  \"description\": \"A viewport for displaying content transitions.\\nThis component is only required if one popup can be opened by multiple triggers, its content change based on the trigger\\nand switching between them is animated.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"children\": {\n      \"type\": \"ReactNode\",\n      \"description\": \"The content to render inside the transition container.\",\n      \"detailedType\": \"React.ReactNode\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Menu.Viewport.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Menu.Viewport.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Menu.Viewport.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Menu.Viewport.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Menu.Viewport.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Menu.Viewport.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-activation-direction\": {\n      \"description\": \"Indicates the direction from which the popup was activated.\\nThis can be used to create directional animations based on how the popup was triggered.\\nContains space-separated values for both horizontal and vertical axes.\",\n      \"type\": \"`${'left' | 'right'} {'down' | 'up'}`\"\n    },\n    \"data-current\": {\n      \"description\": \"Applied to the direct child of the viewport when no transitions are present or the new content when it's entering.\"\n    },\n    \"data-instant\": {\n      \"description\": \"Present if animations should be instant.\",\n      \"type\": \"'click' | 'dismiss' | 'group' | 'trigger-change'\"\n    },\n    \"data-previous\": {\n      \"description\": \"Applied to the direct child of the viewport that contains the exiting content when transitions are present.\"\n    },\n    \"data-transitioning\": {\n      \"description\": \"Indicates that the viewport is currently transitioning between old and new content.\"\n    }\n  },\n  \"cssVariables\": {\n    \"--popup-height\": {\n      \"description\": \"The height of the parent popup.\\nThis variable is placed on the 'previous' container and stores the height of the popup when the previous content was rendered.\\nIt can be used to freeze the dimensions of the popup when animating between different content.\"\n    },\n    \"--popup-width\": {\n      \"description\": \"The width of the parent popup.\\nThis variable is placed on the 'previous' container and stores the width of the popup when the previous content was rendered.\\nIt can be used to freeze the dimensions of the popup when animating between different content.\"\n    }\n  }\n}\n"
  },
  {
    "path": "docs/reference/generated/menubar.json",
    "content": "{\n  \"name\": \"Menubar\",\n  \"description\": \"The container for menus.\",\n  \"props\": {\n    \"loopFocus\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether to loop keyboard focus back to the first item\\nwhen the end of the list is reached while using the arrow keys.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"modal\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether the menubar is modal.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the whole menubar is disabled.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"orientation\": {\n      \"type\": \"Menu.Root.Orientation\",\n      \"default\": \"'horizontal'\",\n      \"description\": \"The orientation of the menubar.\",\n      \"detailedType\": \"'horizontal' | 'vertical' | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Menubar.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Menubar.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Menubar.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Menubar.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Menubar.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Menubar.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/merge-props-n.json",
    "content": "{\n  \"name\": \"mergePropsN\",\n  \"description\": \"Merges an arbitrary number of React props using the same logic as {@link mergeProps}.\\nThis function accepts an array of props instead of individual arguments.\\nThis has slightly lower performance than {@link mergeProps}due to accepting an array\\ninstead of a fixed number of arguments. Prefer {@link mergeProps}when merging 5 or\\nfewer prop sets for better performance.\",\n  \"parameters\": {\n    \"props\": {\n      \"type\": \"InputProps<ElementType>[]\",\n      \"description\": \"Array of props to merge.\"\n    }\n  },\n  \"returnValue\": {\n    \"type\": \"{}\",\n    \"description\": \"The merged props.\"\n  }\n}\n"
  },
  {
    "path": "docs/reference/generated/merge-props.json",
    "content": "{\n  \"name\": \"mergeProps\",\n  \"description\": \"Merges multiple sets of React props. It follows the Object.assign pattern where the rightmost object's fields overwrite\\nthe conflicting ones from others. This doesn't apply to event handlers, `className` and `style` props.\\n\\nEvent handlers are merged and called in right-to-left order (rightmost handler executes first, leftmost last).\\nFor React synthetic events, the rightmost handler can prevent prior (left-positioned) handlers from executing\\nby calling `event.preventBaseUIHandler()`. For non-synthetic events (custom events with primitive/object values),\\nall handlers always execute without prevention capability.\\n\\nThe `className` prop is merged by concatenating classes in right-to-left order (rightmost class appears first in the string).\\nThe `style` prop is merged with rightmost styles overwriting the prior ones.\\n\\nProps can either be provided as objects or as functions that take the previous props as an argument.\\nThe function will receive the merged props up to that point (going from left to right):\\nso in the case of `(obj1, obj2, fn, obj3)`, `fn` will receive the merged props of `obj1` and `obj2`.\\nThe function is responsible for chaining event handlers if needed (i.e. we don't run the merge logic).\\n\\nEvent handlers returned by the functions are not automatically prevented when `preventBaseUIHandler` is called.\\nThey must check `event.baseUIHandlerPrevented` themselves and bail out if it's true.\\n\\n**`ref` is not merged.**\",\n  \"parameters\": {\n    \"a\": {\n      \"type\": \"InputProps<ElementType>\",\n      \"description\": \"Props object to merge.\"\n    },\n    \"b\": {\n      \"type\": \"InputProps<ElementType>\",\n      \"description\": \"Props object to merge. The function will overwrite conflicting props from `a`.\"\n    },\n    \"c\": {\n      \"type\": \"InputProps<ElementType>\",\n      \"optional\": true,\n      \"description\": \"Props object to merge. The function will overwrite conflicting props from previous parameters.\"\n    },\n    \"d\": {\n      \"type\": \"InputProps<ElementType>\",\n      \"optional\": true,\n      \"description\": \"Props object to merge. The function will overwrite conflicting props from previous parameters.\"\n    },\n    \"e\": {\n      \"type\": \"InputProps<ElementType>\",\n      \"optional\": true,\n      \"description\": \"Props object to merge. The function will overwrite conflicting props from previous parameters.\"\n    }\n  },\n  \"returnValue\": {\n    \"type\": \"{}\",\n    \"description\": \"The merged props.\"\n  }\n}\n"
  },
  {
    "path": "docs/reference/generated/meter-indicator.json",
    "content": "{\n  \"name\": \"MeterIndicator\",\n  \"description\": \"Visualizes the position of the value along the range.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Meter.Indicator.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Meter.Indicator.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Meter.Indicator.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Meter.Indicator.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Meter.Indicator.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Meter.Indicator.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/meter-label.json",
    "content": "{\n  \"name\": \"MeterLabel\",\n  \"description\": \"An accessible label for the meter.\\nRenders a `<span>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Meter.Label.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Meter.Label.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Meter.Label.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Meter.Label.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Meter.Label.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Meter.Label.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/meter-root.json",
    "content": "{\n  \"name\": \"MeterRoot\",\n  \"description\": \"Groups all parts of the meter and provides the value for screen readers.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"value\": {\n      \"type\": \"number\",\n      \"required\": true,\n      \"description\": \"The current value.\"\n    },\n    \"aria-valuetext\": {\n      \"type\": \"string\",\n      \"description\": \"A string value that provides a user-friendly name for `aria-valuenow`, the current value of the meter.\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"getAriaValueText\": {\n      \"type\": \"((formattedValue: string, value: number) => string)\",\n      \"description\": \"A function that returns a string value that provides a human-readable text alternative for `aria-valuenow`, the current value of the meter.\",\n      \"detailedType\": \"| ((formattedValue: string, value: number) => string)\\n| undefined\"\n    },\n    \"locale\": {\n      \"type\": \"Intl.LocalesArgument\",\n      \"description\": \"The locale used by `Intl.NumberFormat` when formatting the value.\\nDefaults to the user's runtime locale.\"\n    },\n    \"min\": {\n      \"type\": \"number\",\n      \"default\": \"0\",\n      \"description\": \"The minimum value\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"max\": {\n      \"type\": \"number\",\n      \"default\": \"100\",\n      \"description\": \"The maximum value\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"format\": {\n      \"type\": \"Intl.NumberFormatOptions\",\n      \"description\": \"Options to format the value.\",\n      \"detailedType\": \"Intl.NumberFormatOptions | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Meter.Root.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Meter.Root.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Meter.Root.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Meter.Root.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Meter.Root.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Meter.Root.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/meter-track.json",
    "content": "{\n  \"name\": \"MeterTrack\",\n  \"description\": \"Contains the meter indicator and represents the entire range of the meter.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Meter.Track.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Meter.Track.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Meter.Track.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Meter.Track.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Meter.Track.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Meter.Track.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/meter-value.json",
    "content": "{\n  \"name\": \"MeterValue\",\n  \"description\": \"A text element displaying the current value.\\nRenders a `<span>` element.\",\n  \"props\": {\n    \"children\": {\n      \"type\": \"((formattedValue: string, value: number) => ReactNode) | null\",\n      \"detailedType\": \"| ((formattedValue: string, value: number) => ReactNode)\\n| null\\n| undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Meter.Value.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Meter.Value.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Meter.Value.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Meter.Value.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Meter.Value.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Meter.Value.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/navigation-menu-arrow.json",
    "content": "{\n  \"name\": \"NavigationMenuArrow\",\n  \"description\": \"Displays an element pointing toward the navigation menu's current anchor.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: NavigationMenu.Arrow.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((\\n    state: NavigationMenu.Arrow.State,\\n  ) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: NavigationMenu.Arrow.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: NavigationMenu.Arrow.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: NavigationMenu.Arrow.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: NavigationMenu.Arrow.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the popup is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the popup is closed.\"\n    },\n    \"data-uncentered\": {\n      \"description\": \"Present when the popup arrow is uncentered.\"\n    },\n    \"data-align\": {\n      \"description\": \"Indicates how the popup is aligned relative to specified side.\",\n      \"type\": \"'start' | 'center' | 'end'\"\n    },\n    \"data-side\": {\n      \"description\": \"Indicates which side the popup is positioned relative to the trigger.\",\n      \"type\": \"'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/navigation-menu-backdrop.json",
    "content": "{\n  \"name\": \"NavigationMenuBackdrop\",\n  \"description\": \"A backdrop for the navigation menu popup.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: NavigationMenu.Backdrop.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((\\n    state: NavigationMenu.Backdrop.State,\\n  ) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: NavigationMenu.Backdrop.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: NavigationMenu.Backdrop.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: NavigationMenu.Backdrop.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: NavigationMenu.Backdrop.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the popup is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the popup is closed.\"\n    },\n    \"data-starting-style\": {\n      \"description\": \"Present when the popup is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the popup is animating out.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/navigation-menu-content.json",
    "content": "{\n  \"name\": \"NavigationMenuContent\",\n  \"description\": \"A container for the content of the navigation menu item that is moved into the popup\\nwhen the item is active.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: NavigationMenu.Content.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((\\n    state: NavigationMenu.Content.State,\\n  ) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: NavigationMenu.Content.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: NavigationMenu.Content.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"keepMounted\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to keep the content mounted in the DOM while the popup is closed.\\nEnsures the content is present during server-side rendering for web crawlers.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: NavigationMenu.Content.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: NavigationMenu.Content.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the popup is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the popup is closed.\"\n    },\n    \"data-activation-direction\": {\n      \"description\": \"Which direction another trigger was activated from.\"\n    },\n    \"data-starting-style\": {\n      \"description\": \"Present when the content is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the content is animating out.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/navigation-menu-icon.json",
    "content": "{\n  \"name\": \"NavigationMenuIcon\",\n  \"description\": \"An icon that indicates that the trigger button opens a menu.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: NavigationMenu.Icon.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: NavigationMenu.Icon.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: NavigationMenu.Icon.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: NavigationMenu.Icon.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: NavigationMenu.Icon.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: NavigationMenu.Icon.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/navigation-menu-item.json",
    "content": "{\n  \"name\": \"NavigationMenuItem\",\n  \"description\": \"An individual navigation menu item.\\nRenders a `<li>` element.\",\n  \"props\": {\n    \"value\": {\n      \"type\": \"any\",\n      \"description\": \"A unique value that identifies this navigation menu item.\\nIf no value is provided, a unique ID will be generated automatically.\\nUse when controlling the navigation menu programmatically.\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: NavigationMenu.Item.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: NavigationMenu.Item.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: NavigationMenu.Item.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: NavigationMenu.Item.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: NavigationMenu.Item.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: NavigationMenu.Item.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/navigation-menu-link.json",
    "content": "{\n  \"name\": \"NavigationMenuLink\",\n  \"description\": \"A link in the navigation menu that can be used to navigate to a different page or section.\\nRenders an `<a>` element.\",\n  \"props\": {\n    \"closeOnClick\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to close the navigation menu when the link is clicked.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"active\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the link is the currently active page.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: NavigationMenu.Link.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: NavigationMenu.Link.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: NavigationMenu.Link.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: NavigationMenu.Link.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: NavigationMenu.Link.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: NavigationMenu.Link.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-active\": {\n      \"description\": \"Present when the link is the currently active page.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/navigation-menu-list.json",
    "content": "{\n  \"name\": \"NavigationMenuList\",\n  \"description\": \"Contains a list of navigation menu items.\\nRenders a `<ul>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: NavigationMenu.List.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: NavigationMenu.List.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: NavigationMenu.List.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: NavigationMenu.List.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: NavigationMenu.List.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: NavigationMenu.List.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/navigation-menu-popup.json",
    "content": "{\n  \"name\": \"NavigationMenuPopup\",\n  \"description\": \"A container for the navigation menu contents.\\nRenders a `<nav>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: NavigationMenu.Popup.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((\\n    state: NavigationMenu.Popup.State,\\n  ) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: NavigationMenu.Popup.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: NavigationMenu.Popup.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: NavigationMenu.Popup.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: NavigationMenu.Popup.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the popup is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the popup is closed.\"\n    },\n    \"data-align\": {\n      \"description\": \"Indicates how the popup is aligned relative to the specified side.\",\n      \"type\": \"'start' | 'center' | 'end'\"\n    },\n    \"data-side\": {\n      \"description\": \"Indicates which side the popup is positioned relative to the trigger.\",\n      \"type\": \"'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'\"\n    },\n    \"data-starting-style\": {\n      \"description\": \"Present when the popup is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the popup is animating out.\"\n    }\n  },\n  \"cssVariables\": {\n    \"--popup-height\": {\n      \"description\": \"The fixed height of the popup element.\",\n      \"type\": \"number\"\n    },\n    \"--popup-width\": {\n      \"description\": \"The fixed width of the popup element.\",\n      \"type\": \"number\"\n    }\n  }\n}\n"
  },
  {
    "path": "docs/reference/generated/navigation-menu-portal.json",
    "content": "{\n  \"name\": \"NavigationMenuPortal\",\n  \"description\": \"A portal element that moves the popup to a different part of the DOM.\\nBy default, the portal element is appended to `<body>`.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"container\": {\n      \"type\": \"HTMLElement | ShadowRoot | RefObject<HTMLElement | ShadowRoot | null> | null\",\n      \"description\": \"A parent element to render the portal element into.\",\n      \"detailedType\": \"| HTMLElement\\n| ShadowRoot\\n| React.RefObject<HTMLElement | ShadowRoot | null>\\n| null\\n| undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: NavigationMenu.Portal.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((\\n    state: NavigationMenu.Portal.State,\\n  ) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: NavigationMenu.Portal.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: NavigationMenu.Portal.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"keepMounted\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to keep the portal mounted in the DOM while the popup is hidden.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: NavigationMenu.Portal.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: NavigationMenu.Portal.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/navigation-menu-positioner.json",
    "content": "{\n  \"name\": \"NavigationMenuPositioner\",\n  \"description\": \"Positions the navigation menu against the currently active trigger.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"disableAnchorTracking\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to disable the popup from tracking any layout shift of its positioning anchor.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"align\": {\n      \"type\": \"Align\",\n      \"default\": \"'center'\",\n      \"description\": \"How to align the popup relative to the specified side.\",\n      \"detailedType\": \"'start' | 'center' | 'end' | undefined\"\n    },\n    \"alignOffset\": {\n      \"type\": \"number | OffsetFunction\",\n      \"default\": \"0\",\n      \"description\": \"Additional offset along the alignment axis in pixels.\\nAlso accepts a function that returns the offset to read the dimensions of the anchor\\nand positioner elements, along with its side and alignment.\\n\\nThe function takes a `data` object parameter with the following properties:\\n- `data.anchor`: the dimensions of the anchor element with properties `width` and `height`.\\n- `data.positioner`: the dimensions of the positioner element with properties `width` and `height`.\\n- `data.side`: which side of the anchor element the positioner is aligned against.\\n- `data.align`: how the positioner is aligned relative to the specified side.\",\n      \"example\": \"```jsx\\n<Positioner\\n  alignOffset={({ side, align, anchor, positioner }) => {\\n    return side === 'top' || side === 'bottom'\\n      ? anchor.width\\n      : anchor.height;\\n  }}\\n/>\\n```\",\n      \"detailedType\": \"| number\\n| ((data: {\\n    side: Side\\n    align: Align\\n    anchor: { width: number; height: number }\\n    positioner: { width: number; height: number }\\n  }) => number)\\n| undefined\"\n    },\n    \"side\": {\n      \"type\": \"Side\",\n      \"default\": \"'bottom'\",\n      \"description\": \"Which side of the anchor element to align the popup against.\\nMay automatically change to avoid collisions.\",\n      \"detailedType\": \"| 'top'\\n| 'bottom'\\n| 'left'\\n| 'right'\\n| 'inline-end'\\n| 'inline-start'\\n| undefined\"\n    },\n    \"sideOffset\": {\n      \"type\": \"number | OffsetFunction\",\n      \"default\": \"0\",\n      \"description\": \"Distance between the anchor and the popup in pixels.\\nAlso accepts a function that returns the distance to read the dimensions of the anchor\\nand positioner elements, along with its side and alignment.\\n\\nThe function takes a `data` object parameter with the following properties:\\n- `data.anchor`: the dimensions of the anchor element with properties `width` and `height`.\\n- `data.positioner`: the dimensions of the positioner element with properties `width` and `height`.\\n- `data.side`: which side of the anchor element the positioner is aligned against.\\n- `data.align`: how the positioner is aligned relative to the specified side.\",\n      \"example\": \"```jsx\\n<Positioner\\n  sideOffset={({ side, align, anchor, positioner }) => {\\n    return side === 'top' || side === 'bottom'\\n      ? anchor.height\\n      : anchor.width;\\n  }}\\n/>\\n```\",\n      \"detailedType\": \"| number\\n| ((data: {\\n    side: Side\\n    align: Align\\n    anchor: { width: number; height: number }\\n    positioner: { width: number; height: number }\\n  }) => number)\\n| undefined\"\n    },\n    \"arrowPadding\": {\n      \"type\": \"number\",\n      \"default\": \"5\",\n      \"description\": \"Minimum distance to maintain between the arrow and the edges of the popup.\\n\\nUse it to prevent the arrow element from hanging out of the rounded corners of a popup.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"anchor\": {\n      \"type\": \"Element | VirtualElement | RefObject<Element | null> | (() => Element | VirtualElement | null) | null\",\n      \"description\": \"An element to position the popup against.\\nBy default, the popup will be positioned against the trigger.\",\n      \"detailedType\": \"| Element\\n| VirtualElement\\n| React.RefObject<Element | null>\\n| (() => Element | VirtualElement | null)\\n| null\\n| undefined\"\n    },\n    \"collisionAvoidance\": {\n      \"type\": \"CollisionAvoidance\",\n      \"description\": \"Determines how to handle collisions when positioning the popup.\\n\\n`side` controls overflow on the preferred placement axis (`top`/`bottom` or `left`/`right`):\\n- `'flip'`: keep the requested side when it fits; otherwise try the opposite side\\n  (`top` and `bottom`, or `left` and `right`).\\n- `'shift'`: never change side; keep the requested side and move the popup within\\n  the clipping boundary so it stays visible.\\n- `'none'`: do not correct side-axis overflow.\\n\\n`align` controls overflow on the alignment axis (`start`/`center`/`end`):\\n- `'flip'`: keep side, but swap `start` and `end` when the requested alignment overflows.\\n- `'shift'`: keep side and requested alignment, then nudge the popup along the\\n  alignment axis to fit.\\n- `'none'`: do not correct alignment-axis overflow.\\n\\n`fallbackAxisSide` controls fallback behavior on the perpendicular axis when the\\npreferred axis cannot fit:\\n- `'start'`: allow perpendicular fallback and try the logical start side first\\n  (`top` before `bottom`, or `left` before `right` in LTR).\\n- `'end'`: allow perpendicular fallback and try the logical end side first\\n  (`bottom` before `top`, or `right` before `left` in LTR).\\n- `'none'`: do not fallback to the perpendicular axis.\\n\\nWhen `side` is `'shift'`, explicitly setting `align` only supports `'shift'` or `'none'`.\\nIf `align` is omitted, it defaults to `'flip'`.\",\n      \"example\": \"```jsx\\n<Positioner\\n  collisionAvoidance={{\\n    side: 'shift',\\n    align: 'shift',\\n    fallbackAxisSide: 'none',\\n  }}\\n/>\\n```\",\n      \"detailedType\": \"| {\\n    side?: 'flip' | 'none'\\n    align?: 'flip' | 'shift' | 'none'\\n    fallbackAxisSide?: 'start' | 'end' | 'none'\\n  }\\n| {\\n    side?: 'shift' | 'none'\\n    align?: 'shift' | 'none'\\n    fallbackAxisSide?: 'start' | 'end' | 'none'\\n  }\\n| undefined\"\n    },\n    \"collisionBoundary\": {\n      \"type\": \"Boundary\",\n      \"default\": \"'clipping-ancestors'\",\n      \"description\": \"An element or a rectangle that delimits the area that the popup is confined to.\",\n      \"detailedType\": \"| 'clipping-ancestors'\\n| Element\\n| Element[]\\n| Rect\\n| undefined\"\n    },\n    \"collisionPadding\": {\n      \"type\": \"Padding\",\n      \"default\": \"5\",\n      \"description\": \"Additional space to maintain from the edge of the collision boundary.\",\n      \"detailedType\": \"| {\\n    top?: number\\n    right?: number\\n    bottom?: number\\n    left?: number\\n  }\\n| number\\n| undefined\"\n    },\n    \"sticky\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to maintain the popup in the viewport after\\nthe anchor element was scrolled out of view.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"positionMethod\": {\n      \"type\": \"'absolute' | 'fixed'\",\n      \"default\": \"'absolute'\",\n      \"description\": \"Determines which CSS `position` property to use.\",\n      \"detailedType\": \"'absolute' | 'fixed' | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: NavigationMenu.Positioner.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((\\n    state: NavigationMenu.Positioner.State,\\n  ) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: NavigationMenu.Positioner.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: NavigationMenu.Positioner.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: NavigationMenu.Positioner.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: NavigationMenu.Positioner.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the popup is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the popup is closed.\"\n    },\n    \"data-anchor-hidden\": {\n      \"description\": \"Present when the anchor is hidden.\"\n    },\n    \"data-align\": {\n      \"description\": \"Indicates how the popup is aligned relative to the specified side.\",\n      \"type\": \"'start' | 'center' | 'end'\"\n    },\n    \"data-instant\": {\n      \"description\": \"Present if animations should be instant.\"\n    },\n    \"data-side\": {\n      \"description\": \"Indicates which side the popup is positioned relative to the trigger.\",\n      \"type\": \"'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'\"\n    }\n  },\n  \"cssVariables\": {\n    \"--anchor-height\": {\n      \"description\": \"The anchor's height.\",\n      \"type\": \"number\"\n    },\n    \"--anchor-width\": {\n      \"description\": \"The anchor's width.\",\n      \"type\": \"number\"\n    },\n    \"--available-height\": {\n      \"description\": \"The available height between the trigger and the edge of the viewport.\",\n      \"type\": \"number\"\n    },\n    \"--available-width\": {\n      \"description\": \"The available width between the trigger and the edge of the viewport.\",\n      \"type\": \"number\"\n    },\n    \"--positioner-height\": {\n      \"description\": \"The fixed height of the positioner element.\",\n      \"type\": \"number\"\n    },\n    \"--positioner-width\": {\n      \"description\": \"The fixed width of the positioner element.\",\n      \"type\": \"number\"\n    },\n    \"--transform-origin\": {\n      \"description\": \"The coordinates that this element is anchored to. Used for animations and transitions.\",\n      \"type\": \"string\"\n    }\n  }\n}\n"
  },
  {
    "path": "docs/reference/generated/navigation-menu-root.json",
    "content": "{\n  \"name\": \"NavigationMenuRoot\",\n  \"description\": \"Groups all parts of the navigation menu.\\nRenders a `<nav>` element at the root, or `<div>` element when nested.\",\n  \"props\": {\n    \"defaultValue\": {\n      \"type\": \"Value | null\",\n      \"default\": \"null\",\n      \"description\": \"The uncontrolled value of the item that should be initially selected.\\n\\nTo render a controlled navigation menu, use the `value` prop instead.\",\n      \"detailedType\": \"Value | null | undefined\"\n    },\n    \"value\": {\n      \"type\": \"Value | null\",\n      \"default\": \"null\",\n      \"description\": \"The controlled value of the navigation menu item that should be currently open.\\nWhen non-nullish, the menu will be open. When nullish, the menu will be closed.\\n\\nTo render an uncontrolled navigation menu, use the `defaultValue` prop instead.\",\n      \"detailedType\": \"Value | null | undefined\"\n    },\n    \"onValueChange\": {\n      \"type\": \"((value: Value | null, eventDetails: NavigationMenu.Root.ChangeEventDetails) => void)\",\n      \"description\": \"Callback fired when the value changes.\",\n      \"detailedType\": \"| ((\\n    value: Value | null,\\n    eventDetails: NavigationMenu.Root.ChangeEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"actionsRef\": {\n      \"type\": \"RefObject<NavigationMenu.Root.Actions | null>\",\n      \"description\": \"A ref to imperative actions.\",\n      \"detailedType\": \"| React.RefObject<NavigationMenu.Root.Actions | null>\\n| undefined\"\n    },\n    \"onOpenChangeComplete\": {\n      \"type\": \"((open: boolean) => void)\",\n      \"description\": \"Event handler called after any animations complete when the navigation menu is closed.\",\n      \"detailedType\": \"((open: boolean) => void) | undefined\"\n    },\n    \"delay\": {\n      \"type\": \"number\",\n      \"default\": \"50\",\n      \"description\": \"How long to wait before opening the navigation popup. Specified in milliseconds.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"closeDelay\": {\n      \"type\": \"number\",\n      \"default\": \"50\",\n      \"description\": \"How long to wait before closing the navigation popup. Specified in milliseconds.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"orientation\": {\n      \"type\": \"'horizontal' | 'vertical'\",\n      \"default\": \"'horizontal'\",\n      \"description\": \"The orientation of the navigation menu.\",\n      \"detailedType\": \"'horizontal' | 'vertical' | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: NavigationMenu.Root.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: NavigationMenu.Root.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: NavigationMenu.Root.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: NavigationMenu.Root.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: NavigationMenu.Root.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: NavigationMenu.Root.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/navigation-menu-trigger.json",
    "content": "{\n  \"name\": \"NavigationMenuTrigger\",\n  \"description\": \"Opens the navigation menu popup when hovered or clicked, revealing the\\nassociated content.\\nRenders a `<button>` element.\",\n  \"props\": {\n    \"nativeButton\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether the component renders a native `<button>` element when replacing it\\nvia the `render` prop.\\nSet to `false` if the rendered element is not a button (e.g. `<div>`).\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: NavigationMenu.Trigger.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((\\n    state: NavigationMenu.Trigger.State,\\n  ) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: NavigationMenu.Trigger.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: NavigationMenu.Trigger.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: NavigationMenu.Trigger.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: NavigationMenu.Trigger.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-popup-open\": {\n      \"description\": \"Present when the corresponding navigation menu is open.\"\n    },\n    \"data-pressed\": {\n      \"description\": \"Present when the trigger is pressed.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/navigation-menu-viewport.json",
    "content": "{\n  \"name\": \"NavigationMenuViewport\",\n  \"description\": \"The clipping viewport of the navigation menu's current content.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: NavigationMenu.Viewport.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((\\n    state: NavigationMenu.Viewport.State,\\n  ) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: NavigationMenu.Viewport.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: NavigationMenu.Viewport.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: NavigationMenu.Viewport.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: NavigationMenu.Viewport.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/number-field-decrement.json",
    "content": "{\n  \"name\": \"NumberFieldDecrement\",\n  \"description\": \"A stepper button that decreases the field value when clicked.\\nRenders an `<button>` element.\",\n  \"props\": {\n    \"nativeButton\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether the component renders a native `<button>` element when replacing it\\nvia the `render` prop.\\nSet to `false` if the rendered element is not a button (e.g. `<div>`).\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: NumberField.Decrement.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((\\n    state: NumberField.Decrement.State,\\n  ) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: NumberField.Decrement.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: NumberField.Decrement.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: NumberField.Decrement.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: NumberField.Decrement.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-disabled\": {\n      \"description\": \"Present when the number field is disabled.\"\n    },\n    \"data-readonly\": {\n      \"description\": \"Present when the number field is readonly.\"\n    },\n    \"data-required\": {\n      \"description\": \"Present when the number field is required.\"\n    },\n    \"data-valid\": {\n      \"description\": \"Present when the number field is in valid state (when wrapped in Field.Root).\"\n    },\n    \"data-invalid\": {\n      \"description\": \"Present when the number field is in invalid state (when wrapped in Field.Root).\"\n    },\n    \"data-dirty\": {\n      \"description\": \"Present when the number field's value has changed (when wrapped in Field.Root).\"\n    },\n    \"data-touched\": {\n      \"description\": \"Present when the number field has been touched (when wrapped in Field.Root).\"\n    },\n    \"data-filled\": {\n      \"description\": \"Present when the number field is filled (when wrapped in Field.Root).\"\n    },\n    \"data-focused\": {\n      \"description\": \"Present when the number field is focused (when wrapped in Field.Root).\"\n    },\n    \"data-scrubbing\": {\n      \"description\": \"Present while scrubbing.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/number-field-group.json",
    "content": "{\n  \"name\": \"NumberFieldGroup\",\n  \"description\": \"Groups the input with the increment and decrement buttons.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: NumberField.Group.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: NumberField.Group.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: NumberField.Group.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: NumberField.Group.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: NumberField.Group.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: NumberField.Group.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-disabled\": {\n      \"description\": \"Present when the number field is disabled.\"\n    },\n    \"data-readonly\": {\n      \"description\": \"Present when the number field is readonly.\"\n    },\n    \"data-required\": {\n      \"description\": \"Present when the number field is required.\"\n    },\n    \"data-valid\": {\n      \"description\": \"Present when the number field is in valid state (when wrapped in Field.Root).\"\n    },\n    \"data-invalid\": {\n      \"description\": \"Present when the number field is in invalid state (when wrapped in Field.Root).\"\n    },\n    \"data-dirty\": {\n      \"description\": \"Present when the number field's value has changed (when wrapped in Field.Root).\"\n    },\n    \"data-touched\": {\n      \"description\": \"Present when the number field has been touched (when wrapped in Field.Root).\"\n    },\n    \"data-filled\": {\n      \"description\": \"Present when the number field is filled (when wrapped in Field.Root).\"\n    },\n    \"data-focused\": {\n      \"description\": \"Present when the number field is focused (when wrapped in Field.Root).\"\n    },\n    \"data-scrubbing\": {\n      \"description\": \"Present while scrubbing.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/number-field-increment.json",
    "content": "{\n  \"name\": \"NumberFieldIncrement\",\n  \"description\": \"A stepper button that increases the field value when clicked.\\nRenders an `<button>` element.\",\n  \"props\": {\n    \"nativeButton\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether the component renders a native `<button>` element when replacing it\\nvia the `render` prop.\\nSet to `false` if the rendered element is not a button (e.g. `<div>`).\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: NumberField.Increment.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((\\n    state: NumberField.Increment.State,\\n  ) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: NumberField.Increment.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: NumberField.Increment.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: NumberField.Increment.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: NumberField.Increment.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-disabled\": {\n      \"description\": \"Present when the number field is disabled.\"\n    },\n    \"data-readonly\": {\n      \"description\": \"Present when the number field is readonly.\"\n    },\n    \"data-required\": {\n      \"description\": \"Present when the number field is required.\"\n    },\n    \"data-valid\": {\n      \"description\": \"Present when the number field is in valid state (when wrapped in Field.Root).\"\n    },\n    \"data-invalid\": {\n      \"description\": \"Present when the number field is in invalid state (when wrapped in Field.Root).\"\n    },\n    \"data-dirty\": {\n      \"description\": \"Present when the number field's value has changed (when wrapped in Field.Root).\"\n    },\n    \"data-touched\": {\n      \"description\": \"Present when the number field has been touched (when wrapped in Field.Root).\"\n    },\n    \"data-filled\": {\n      \"description\": \"Present when the number field is filled (when wrapped in Field.Root).\"\n    },\n    \"data-focused\": {\n      \"description\": \"Present when the number field is focused (when wrapped in Field.Root).\"\n    },\n    \"data-scrubbing\": {\n      \"description\": \"Present while scrubbing.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/number-field-input.json",
    "content": "{\n  \"name\": \"NumberFieldInput\",\n  \"description\": \"The native input control in the number field.\\nRenders an `<input>` element.\",\n  \"props\": {\n    \"aria-roledescription\": {\n      \"type\": \"string\",\n      \"default\": \"'Number field'\",\n      \"description\": \"A string value that provides a user-friendly name for the role of the input.\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: NumberField.Input.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: NumberField.Input.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: NumberField.Input.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: NumberField.Input.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: NumberField.Input.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: NumberField.Input.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-disabled\": {\n      \"description\": \"Present when the number field is disabled.\"\n    },\n    \"data-readonly\": {\n      \"description\": \"Present when the number field is readonly.\"\n    },\n    \"data-required\": {\n      \"description\": \"Present when the number field is required.\"\n    },\n    \"data-valid\": {\n      \"description\": \"Present when the number field is in valid state (when wrapped in Field.Root).\"\n    },\n    \"data-invalid\": {\n      \"description\": \"Present when the number field is in invalid state (when wrapped in Field.Root).\"\n    },\n    \"data-dirty\": {\n      \"description\": \"Present when the number field's value has changed (when wrapped in Field.Root).\"\n    },\n    \"data-touched\": {\n      \"description\": \"Present when the number field has been touched (when wrapped in Field.Root).\"\n    },\n    \"data-filled\": {\n      \"description\": \"Present when the number field is filled (when wrapped in Field.Root).\"\n    },\n    \"data-focused\": {\n      \"description\": \"Present when the number field is focused (when wrapped in Field.Root).\"\n    },\n    \"data-scrubbing\": {\n      \"description\": \"Present while scrubbing.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/number-field-root.json",
    "content": "{\n  \"name\": \"NumberFieldRoot\",\n  \"description\": \"Groups all parts of the number field and manages its state.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"name\": {\n      \"type\": \"string\",\n      \"description\": \"Identifies the field when a form is submitted.\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"defaultValue\": {\n      \"type\": \"number\",\n      \"description\": \"The uncontrolled value of the field when it’s initially rendered.\\n\\nTo render a controlled number field, use the `value` prop instead.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"value\": {\n      \"type\": \"number | null\",\n      \"description\": \"The raw numeric value of the field.\",\n      \"detailedType\": \"number | null | undefined\"\n    },\n    \"onValueChange\": {\n      \"type\": \"((value: number | null, eventDetails: NumberField.Root.ChangeEventDetails) => void)\",\n      \"description\": \"Callback fired when the number value changes.\\n\\nThe `eventDetails.reason` indicates what triggered the change:\\n- `'input-change'` for parseable typing or programmatic text updates\\n- `'input-clear'` when the field becomes empty\\n- `'input-blur'` when formatting (and clamping, if enabled) occurs on blur\\n- `'input-paste'` for paste interactions\\n- `'keyboard'` for keyboard input\\n- `'increment-press'` / `'decrement-press'` for button presses on the increment and decrement controls\\n- `'wheel'` for wheel-based scrubbing\\n- `'scrub'` for scrub area drags\",\n      \"detailedType\": \"| ((\\n    value: number | null,\\n    eventDetails: NumberField.Root.ChangeEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"onValueCommitted\": {\n      \"type\": \"((value: number | null, eventDetails: NumberField.Root.CommitEventDetails) => void)\",\n      \"description\": \"Callback function that is fired when the value is committed.\\nIt runs later than `onValueChange`, when:\\n- The input is blurred after typing a value.\\n- The pointer is released after scrubbing or pressing the increment/decrement buttons.\\n\\nIt runs simultaneously with `onValueChange` when interacting with the keyboard.\\n\\n**Warning**: This is a generic event not a change event.\",\n      \"detailedType\": \"| ((\\n    value: number | null,\\n    eventDetails: NumberField.Root.CommitEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"allowOutOfRange\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"When true, direct text entry may be outside the `min`/`max` range without clamping,\\nso native range underflow/overflow validation can occur.\\nStep-based interactions (keyboard arrows, buttons, wheel, scrub) still clamp.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"locale\": {\n      \"type\": \"Intl.LocalesArgument\",\n      \"description\": \"The locale of the input element.\\nDefaults to the user's runtime locale.\"\n    },\n    \"snapOnStep\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the value should snap to the nearest step when incrementing or decrementing.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"step\": {\n      \"type\": \"number | 'any'\",\n      \"default\": \"1\",\n      \"description\": \"Amount to increment and decrement with the buttons and arrow keys, or to scrub with pointer movement in the scrub area.\\nTo always enable step validation on form submission, specify the `min` prop explicitly in conjunction with this prop.\\nSpecify `step=\\\"any\\\"` to always disable step validation.\",\n      \"detailedType\": \"number | 'any' | undefined\"\n    },\n    \"smallStep\": {\n      \"type\": \"number\",\n      \"default\": \"0.1\",\n      \"description\": \"The small step value of the input element when incrementing while the meta key is held. Snaps\\nto multiples of this value.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"largeStep\": {\n      \"type\": \"number\",\n      \"default\": \"10\",\n      \"description\": \"The large step value of the input element when incrementing while the shift key is held. Snaps\\nto multiples of this value.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"min\": {\n      \"type\": \"number\",\n      \"description\": \"The minimum value of the input element.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"max\": {\n      \"type\": \"number\",\n      \"description\": \"The maximum value of the input element.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"allowWheelScrub\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to allow the user to scrub the input value with the mouse wheel while focused and\\nhovering over the input.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"format\": {\n      \"type\": \"Intl.NumberFormatOptions\",\n      \"description\": \"Options to format the input value.\",\n      \"detailedType\": \"Intl.NumberFormatOptions | undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component should ignore user interaction.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"readOnly\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the user should be unable to change the field value.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"required\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the user must enter a value before submitting a form.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"inputRef\": {\n      \"type\": \"Ref<HTMLInputElement>\",\n      \"description\": \"A ref to access the hidden input element.\",\n      \"detailedType\": \"React.Ref<HTMLInputElement> | undefined\"\n    },\n    \"id\": {\n      \"type\": \"string\",\n      \"description\": \"The id of the input element.\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: NumberField.Root.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: NumberField.Root.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: NumberField.Root.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: NumberField.Root.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: NumberField.Root.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: NumberField.Root.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-disabled\": {\n      \"description\": \"Present when the number field is disabled.\"\n    },\n    \"data-readonly\": {\n      \"description\": \"Present when the number field is readonly.\"\n    },\n    \"data-required\": {\n      \"description\": \"Present when the number field is required.\"\n    },\n    \"data-valid\": {\n      \"description\": \"Present when the number field is in valid state (when wrapped in Field.Root).\"\n    },\n    \"data-invalid\": {\n      \"description\": \"Present when the number field is in invalid state (when wrapped in Field.Root).\"\n    },\n    \"data-dirty\": {\n      \"description\": \"Present when the number field's value has changed (when wrapped in Field.Root).\"\n    },\n    \"data-touched\": {\n      \"description\": \"Present when the number field has been touched (when wrapped in Field.Root).\"\n    },\n    \"data-filled\": {\n      \"description\": \"Present when the number field is filled (when wrapped in Field.Root).\"\n    },\n    \"data-focused\": {\n      \"description\": \"Present when the number field is focused (when wrapped in Field.Root).\"\n    },\n    \"data-scrubbing\": {\n      \"description\": \"Present while scrubbing.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/number-field-scrub-area-cursor.json",
    "content": "{\n  \"name\": \"NumberFieldScrubAreaCursor\",\n  \"description\": \"A custom element to display instead of the native cursor while using the scrub area.\\nRenders a `<span>` element.\\n\\nThis component uses the [Pointer Lock API](https://developer.mozilla.org/en-US/docs/Web/API/Pointer_Lock_API), which may prompt the browser to display a related notification. It is disabled\\nin Safari to avoid a layout shift that this notification causes there.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: NumberField.ScrubAreaCursor.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((\\n    state: NumberField.ScrubAreaCursor.State,\\n  ) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: NumberField.ScrubAreaCursor.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: NumberField.ScrubAreaCursor.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: NumberField.ScrubAreaCursor.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: NumberField.ScrubAreaCursor.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-disabled\": {\n      \"description\": \"Present when the number field is disabled.\"\n    },\n    \"data-readonly\": {\n      \"description\": \"Present when the number field is readonly.\"\n    },\n    \"data-required\": {\n      \"description\": \"Present when the number field is required.\"\n    },\n    \"data-valid\": {\n      \"description\": \"Present when the number field is in valid state (when wrapped in Field.Root).\"\n    },\n    \"data-invalid\": {\n      \"description\": \"Present when the number field is in invalid state (when wrapped in Field.Root).\"\n    },\n    \"data-dirty\": {\n      \"description\": \"Present when the number field's value has changed (when wrapped in Field.Root).\"\n    },\n    \"data-touched\": {\n      \"description\": \"Present when the number field has been touched (when wrapped in Field.Root).\"\n    },\n    \"data-filled\": {\n      \"description\": \"Present when the number field is filled (when wrapped in Field.Root).\"\n    },\n    \"data-focused\": {\n      \"description\": \"Present when the number field is focused (when wrapped in Field.Root).\"\n    },\n    \"data-scrubbing\": {\n      \"description\": \"Present while scrubbing.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/number-field-scrub-area.json",
    "content": "{\n  \"name\": \"NumberFieldScrubArea\",\n  \"description\": \"An interactive area where the user can click and drag to change the field value.\\nRenders a `<span>` element.\",\n  \"props\": {\n    \"direction\": {\n      \"type\": \"'horizontal' | 'vertical'\",\n      \"default\": \"'horizontal'\",\n      \"description\": \"Cursor movement direction in the scrub area.\",\n      \"detailedType\": \"'horizontal' | 'vertical' | undefined\"\n    },\n    \"pixelSensitivity\": {\n      \"type\": \"number\",\n      \"default\": \"2\",\n      \"description\": \"Determines how many pixels the cursor must move before the value changes.\\nA higher value will make scrubbing less sensitive.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"teleportDistance\": {\n      \"type\": \"number\",\n      \"description\": \"If specified, determines the distance that the cursor may move from the center\\nof the scrub area before it will loop back around.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: NumberField.ScrubArea.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((\\n    state: NumberField.ScrubArea.State,\\n  ) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: NumberField.ScrubArea.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: NumberField.ScrubArea.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: NumberField.ScrubArea.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: NumberField.ScrubArea.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-disabled\": {\n      \"description\": \"Present when the number field is disabled.\"\n    },\n    \"data-readonly\": {\n      \"description\": \"Present when the number field is readonly.\"\n    },\n    \"data-required\": {\n      \"description\": \"Present when the number field is required.\"\n    },\n    \"data-valid\": {\n      \"description\": \"Present when the number field is in valid state (when wrapped in Field.Root).\"\n    },\n    \"data-invalid\": {\n      \"description\": \"Present when the number field is in invalid state (when wrapped in Field.Root).\"\n    },\n    \"data-dirty\": {\n      \"description\": \"Present when the number field's value has changed (when wrapped in Field.Root).\"\n    },\n    \"data-touched\": {\n      \"description\": \"Present when the number field has been touched (when wrapped in Field.Root).\"\n    },\n    \"data-filled\": {\n      \"description\": \"Present when the number field is filled (when wrapped in Field.Root).\"\n    },\n    \"data-focused\": {\n      \"description\": \"Present when the number field is focused (when wrapped in Field.Root).\"\n    },\n    \"data-scrubbing\": {\n      \"description\": \"Present while scrubbing.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/popover-arrow.json",
    "content": "{\n  \"name\": \"PopoverArrow\",\n  \"description\": \"Displays an element positioned against the popover anchor.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Popover.Arrow.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Popover.Arrow.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Popover.Arrow.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Popover.Arrow.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Popover.Arrow.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Popover.Arrow.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the popup is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the popup is closed.\"\n    },\n    \"data-uncentered\": {\n      \"description\": \"Present when the popover arrow is uncentered.\"\n    },\n    \"data-align\": {\n      \"description\": \"Indicates how the popup is aligned relative to specified side.\",\n      \"type\": \"'start' | 'center' | 'end'\"\n    },\n    \"data-side\": {\n      \"description\": \"Indicates which side the popup is positioned relative to the trigger.\",\n      \"type\": \"'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/popover-backdrop.json",
    "content": "{\n  \"name\": \"PopoverBackdrop\",\n  \"description\": \"An overlay displayed beneath the popover.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Popover.Backdrop.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Popover.Backdrop.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Popover.Backdrop.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Popover.Backdrop.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Popover.Backdrop.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Popover.Backdrop.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the popup is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the popup is closed.\"\n    },\n    \"data-starting-style\": {\n      \"description\": \"Present when the popup is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the popup is animating out.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/popover-close.json",
    "content": "{\n  \"name\": \"PopoverClose\",\n  \"description\": \"A button that closes the popover.\\nRenders a `<button>` element.\",\n  \"props\": {\n    \"nativeButton\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether the component renders a native `<button>` element when replacing it\\nvia the `render` prop.\\nSet to `false` if the rendered element is not a button (e.g. `<div>`).\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Popover.Close.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Popover.Close.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Popover.Close.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Popover.Close.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Popover.Close.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Popover.Close.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/popover-description.json",
    "content": "{\n  \"name\": \"PopoverDescription\",\n  \"description\": \"A paragraph with additional information about the popover.\\nRenders a `<p>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Popover.Description.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Popover.Description.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Popover.Description.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Popover.Description.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Popover.Description.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Popover.Description.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/popover-popup.json",
    "content": "{\n  \"name\": \"PopoverPopup\",\n  \"description\": \"A container for the popover contents.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"initialFocus\": {\n      \"type\": \"boolean | RefObject<HTMLElement | null> | ((openType: InteractionType) => boolean | void | HTMLElement | null)\",\n      \"description\": \"Determines the element to focus when the popover is opened.\\n\\n- `false`: Do not move focus.\\n- `true`: Move focus based on the default behavior (first tabbable element or popup).\\n- `RefObject`: Move focus to the ref element.\\n- `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`).\\n  Return an element to focus, `true` to use the default behavior, or `false`/`undefined` to do nothing.\",\n      \"detailedType\": \"| boolean\\n| React.RefObject<HTMLElement | null>\\n| ((\\n    openType: InteractionType,\\n  ) => boolean | void | HTMLElement | null)\\n| undefined\"\n    },\n    \"finalFocus\": {\n      \"type\": \"boolean | RefObject<HTMLElement | null> | ((closeType: InteractionType) => boolean | void | HTMLElement | null)\",\n      \"description\": \"Determines the element to focus when the popover is closed.\\n\\n- `false`: Do not move focus.\\n- `true`: Move focus based on the default behavior (trigger or previously focused element).\\n- `RefObject`: Move focus to the ref element.\\n- `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`).\\n  Return an element to focus, `true` to use the default behavior, or `false`/`undefined` to do nothing.\",\n      \"detailedType\": \"| boolean\\n| React.RefObject<HTMLElement | null>\\n| ((\\n    closeType: InteractionType,\\n  ) => boolean | void | HTMLElement | null)\\n| undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Popover.Popup.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Popover.Popup.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Popover.Popup.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Popover.Popup.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Popover.Popup.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Popover.Popup.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the popup is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the popup is closed.\"\n    },\n    \"data-align\": {\n      \"description\": \"Indicates how the popup is aligned relative to specified side.\",\n      \"type\": \"'start' | 'center' | 'end'\"\n    },\n    \"data-instant\": {\n      \"description\": \"Present if animations should be instant.\",\n      \"type\": \"'click' | 'dismiss'\"\n    },\n    \"data-side\": {\n      \"description\": \"Indicates which side the popup is positioned relative to the trigger.\",\n      \"type\": \"'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'\"\n    },\n    \"data-starting-style\": {\n      \"description\": \"Present when the popup is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the popup is animating out.\"\n    }\n  },\n  \"cssVariables\": {\n    \"--popup-height\": {\n      \"description\": \"The height of the popup.\"\n    },\n    \"--popup-width\": {\n      \"description\": \"The width of the popup.\"\n    }\n  }\n}\n"
  },
  {
    "path": "docs/reference/generated/popover-portal.json",
    "content": "{\n  \"name\": \"PopoverPortal\",\n  \"description\": \"A portal element that moves the popup to a different part of the DOM.\\nBy default, the portal element is appended to `<body>`.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"container\": {\n      \"type\": \"HTMLElement | ShadowRoot | RefObject<HTMLElement | ShadowRoot | null> | null\",\n      \"description\": \"A parent element to render the portal element into.\",\n      \"detailedType\": \"| HTMLElement\\n| ShadowRoot\\n| React.RefObject<HTMLElement | ShadowRoot | null>\\n| null\\n| undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Popover.Portal.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Popover.Portal.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Popover.Portal.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Popover.Portal.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"keepMounted\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to keep the portal mounted in the DOM while the popup is hidden.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Popover.Portal.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Popover.Portal.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/popover-positioner.json",
    "content": "{\n  \"name\": \"PopoverPositioner\",\n  \"description\": \"Positions the popover against the trigger.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"disableAnchorTracking\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to disable the popup from tracking any layout shift of its positioning anchor.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"align\": {\n      \"type\": \"Align\",\n      \"default\": \"'center'\",\n      \"description\": \"How to align the popup relative to the specified side.\",\n      \"detailedType\": \"'start' | 'center' | 'end' | undefined\"\n    },\n    \"alignOffset\": {\n      \"type\": \"number | OffsetFunction\",\n      \"default\": \"0\",\n      \"description\": \"Additional offset along the alignment axis in pixels.\\nAlso accepts a function that returns the offset to read the dimensions of the anchor\\nand positioner elements, along with its side and alignment.\\n\\nThe function takes a `data` object parameter with the following properties:\\n- `data.anchor`: the dimensions of the anchor element with properties `width` and `height`.\\n- `data.positioner`: the dimensions of the positioner element with properties `width` and `height`.\\n- `data.side`: which side of the anchor element the positioner is aligned against.\\n- `data.align`: how the positioner is aligned relative to the specified side.\",\n      \"example\": \"```jsx\\n<Positioner\\n  alignOffset={({ side, align, anchor, positioner }) => {\\n    return side === 'top' || side === 'bottom'\\n      ? anchor.width\\n      : anchor.height;\\n  }}\\n/>\\n```\",\n      \"detailedType\": \"| number\\n| ((data: {\\n    side: Side\\n    align: Align\\n    anchor: { width: number; height: number }\\n    positioner: { width: number; height: number }\\n  }) => number)\\n| undefined\"\n    },\n    \"side\": {\n      \"type\": \"Side\",\n      \"default\": \"'bottom'\",\n      \"description\": \"Which side of the anchor element to align the popup against.\\nMay automatically change to avoid collisions.\",\n      \"detailedType\": \"| 'top'\\n| 'bottom'\\n| 'left'\\n| 'right'\\n| 'inline-end'\\n| 'inline-start'\\n| undefined\"\n    },\n    \"sideOffset\": {\n      \"type\": \"number | OffsetFunction\",\n      \"default\": \"0\",\n      \"description\": \"Distance between the anchor and the popup in pixels.\\nAlso accepts a function that returns the distance to read the dimensions of the anchor\\nand positioner elements, along with its side and alignment.\\n\\nThe function takes a `data` object parameter with the following properties:\\n- `data.anchor`: the dimensions of the anchor element with properties `width` and `height`.\\n- `data.positioner`: the dimensions of the positioner element with properties `width` and `height`.\\n- `data.side`: which side of the anchor element the positioner is aligned against.\\n- `data.align`: how the positioner is aligned relative to the specified side.\",\n      \"example\": \"```jsx\\n<Positioner\\n  sideOffset={({ side, align, anchor, positioner }) => {\\n    return side === 'top' || side === 'bottom'\\n      ? anchor.height\\n      : anchor.width;\\n  }}\\n/>\\n```\",\n      \"detailedType\": \"| number\\n| ((data: {\\n    side: Side\\n    align: Align\\n    anchor: { width: number; height: number }\\n    positioner: { width: number; height: number }\\n  }) => number)\\n| undefined\"\n    },\n    \"arrowPadding\": {\n      \"type\": \"number\",\n      \"default\": \"5\",\n      \"description\": \"Minimum distance to maintain between the arrow and the edges of the popup.\\n\\nUse it to prevent the arrow element from hanging out of the rounded corners of a popup.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"anchor\": {\n      \"type\": \"Element | VirtualElement | RefObject<Element | null> | (() => Element | VirtualElement | null) | null\",\n      \"description\": \"An element to position the popup against.\\nBy default, the popup will be positioned against the trigger.\",\n      \"detailedType\": \"| Element\\n| VirtualElement\\n| React.RefObject<Element | null>\\n| (() => Element | VirtualElement | null)\\n| null\\n| undefined\"\n    },\n    \"collisionAvoidance\": {\n      \"type\": \"CollisionAvoidance\",\n      \"description\": \"Determines how to handle collisions when positioning the popup.\\n\\n`side` controls overflow on the preferred placement axis (`top`/`bottom` or `left`/`right`):\\n- `'flip'`: keep the requested side when it fits; otherwise try the opposite side\\n  (`top` and `bottom`, or `left` and `right`).\\n- `'shift'`: never change side; keep the requested side and move the popup within\\n  the clipping boundary so it stays visible.\\n- `'none'`: do not correct side-axis overflow.\\n\\n`align` controls overflow on the alignment axis (`start`/`center`/`end`):\\n- `'flip'`: keep side, but swap `start` and `end` when the requested alignment overflows.\\n- `'shift'`: keep side and requested alignment, then nudge the popup along the\\n  alignment axis to fit.\\n- `'none'`: do not correct alignment-axis overflow.\\n\\n`fallbackAxisSide` controls fallback behavior on the perpendicular axis when the\\npreferred axis cannot fit:\\n- `'start'`: allow perpendicular fallback and try the logical start side first\\n  (`top` before `bottom`, or `left` before `right` in LTR).\\n- `'end'`: allow perpendicular fallback and try the logical end side first\\n  (`bottom` before `top`, or `right` before `left` in LTR).\\n- `'none'`: do not fallback to the perpendicular axis.\\n\\nWhen `side` is `'shift'`, explicitly setting `align` only supports `'shift'` or `'none'`.\\nIf `align` is omitted, it defaults to `'flip'`.\",\n      \"example\": \"```jsx\\n<Positioner\\n  collisionAvoidance={{\\n    side: 'shift',\\n    align: 'shift',\\n    fallbackAxisSide: 'none',\\n  }}\\n/>\\n```\",\n      \"detailedType\": \"| {\\n    side?: 'flip' | 'none'\\n    align?: 'flip' | 'shift' | 'none'\\n    fallbackAxisSide?: 'start' | 'end' | 'none'\\n  }\\n| {\\n    side?: 'shift' | 'none'\\n    align?: 'shift' | 'none'\\n    fallbackAxisSide?: 'start' | 'end' | 'none'\\n  }\\n| undefined\"\n    },\n    \"collisionBoundary\": {\n      \"type\": \"Boundary\",\n      \"default\": \"'clipping-ancestors'\",\n      \"description\": \"An element or a rectangle that delimits the area that the popup is confined to.\",\n      \"detailedType\": \"| 'clipping-ancestors'\\n| Element\\n| Element[]\\n| Rect\\n| undefined\"\n    },\n    \"collisionPadding\": {\n      \"type\": \"Padding\",\n      \"default\": \"5\",\n      \"description\": \"Additional space to maintain from the edge of the collision boundary.\",\n      \"detailedType\": \"| {\\n    top?: number\\n    right?: number\\n    bottom?: number\\n    left?: number\\n  }\\n| number\\n| undefined\"\n    },\n    \"sticky\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to maintain the popup in the viewport after\\nthe anchor element was scrolled out of view.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"positionMethod\": {\n      \"type\": \"'absolute' | 'fixed'\",\n      \"default\": \"'absolute'\",\n      \"description\": \"Determines which CSS `position` property to use.\",\n      \"detailedType\": \"'absolute' | 'fixed' | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Popover.Positioner.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Popover.Positioner.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Popover.Positioner.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Popover.Positioner.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Popover.Positioner.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Popover.Positioner.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the popup is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the popup is closed.\"\n    },\n    \"data-anchor-hidden\": {\n      \"description\": \"Present when the anchor is hidden.\"\n    },\n    \"data-align\": {\n      \"description\": \"Indicates how the popup is aligned relative to specified side.\",\n      \"type\": \"'start' | 'center' | 'end'\"\n    },\n    \"data-side\": {\n      \"description\": \"Indicates which side the popup is positioned relative to the trigger.\",\n      \"type\": \"'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'\"\n    }\n  },\n  \"cssVariables\": {\n    \"--anchor-height\": {\n      \"description\": \"The anchor's height.\",\n      \"type\": \"number\"\n    },\n    \"--anchor-width\": {\n      \"description\": \"The anchor's width.\",\n      \"type\": \"number\"\n    },\n    \"--available-height\": {\n      \"description\": \"The available height between the trigger and the edge of the viewport.\",\n      \"type\": \"number\"\n    },\n    \"--available-width\": {\n      \"description\": \"The available width between the trigger and the edge of the viewport.\",\n      \"type\": \"number\"\n    },\n    \"--positioner-height\": {\n      \"description\": \"The height of the popover's positioner.\\nIt is important to set `height` to this value when using CSS to animate size changes.\"\n    },\n    \"--positioner-width\": {\n      \"description\": \"The width of the popover's positioner.\\nIt is important to set `width` to this value when using CSS to animate size changes.\"\n    },\n    \"--transform-origin\": {\n      \"description\": \"The coordinates that this element is anchored to. Used for animations and transitions.\",\n      \"type\": \"string\"\n    }\n  }\n}\n"
  },
  {
    "path": "docs/reference/generated/popover-root.json",
    "content": "{\n  \"name\": \"PopoverRoot\",\n  \"description\": \"Groups all parts of the popover.\\nDoesn’t render its own HTML element.\",\n  \"props\": {\n    \"defaultOpen\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the popover is initially open.\\n\\nTo render a controlled popover, use the `open` prop instead.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"open\": {\n      \"type\": \"boolean\",\n      \"description\": \"Whether the popover is currently open.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"onOpenChange\": {\n      \"type\": \"((open: boolean, eventDetails: Popover.Root.ChangeEventDetails) => void)\",\n      \"description\": \"Event handler called when the popover is opened or closed.\",\n      \"detailedType\": \"| ((\\n    open: boolean,\\n    eventDetails: Popover.Root.ChangeEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"actionsRef\": {\n      \"type\": \"RefObject<Popover.Root.Actions | null>\",\n      \"description\": \"A ref to imperative actions.\\n- `unmount`: When specified, the popover will not be unmounted when closed.\\nInstead, the `unmount` function must be called to unmount the popover manually.\\nUseful when the popover's animation is controlled by an external library.\\n- `close`: Closes the dialog imperatively when called.\",\n      \"detailedType\": \"| React.RefObject<Popover.Root.Actions | null>\\n| undefined\"\n    },\n    \"defaultTriggerId\": {\n      \"type\": \"string | null\",\n      \"description\": \"ID of the trigger that the popover is associated with.\\nThis is useful in conjunction with the `defaultOpen` prop to create an initially open popover.\",\n      \"detailedType\": \"string | null | undefined\"\n    },\n    \"handle\": {\n      \"type\": \"Popover.Handle<Payload>\",\n      \"description\": \"A handle to associate the popover with a trigger.\\nIf specified, allows external triggers to control the popover's open state.\",\n      \"detailedType\": \"{} | undefined\"\n    },\n    \"modal\": {\n      \"type\": \"boolean | 'trap-focus'\",\n      \"default\": \"false\",\n      \"description\": \"Determines if the popover enters a modal state when open.\\n- `true`: user interaction is limited to the popover: document page scroll is locked, and pointer interactions on outside elements are disabled.\\n- `false`: user interaction with the rest of the document is allowed.\\n- `'trap-focus'`: focus is trapped inside the popover, but document page scroll is not locked and pointer interactions outside of it remain enabled.\\n\\nWhen `modal` is `true`, focus trapping is enabled only if `<Popover.Close>` is rendered\\ninside `<Popover.Popup>`. It can be visually hidden with your own CSS if needed, such as\\nTailwind's `sr-only` utility.\\n\\nWhen `modal` is `'trap-focus'`, render `<Popover.Close>` inside `<Popover.Popup>` so touch\\nscreen readers can escape the popup.\",\n      \"detailedType\": \"boolean | 'trap-focus' | undefined\"\n    },\n    \"onOpenChangeComplete\": {\n      \"type\": \"((open: boolean) => void)\",\n      \"description\": \"Event handler called after any animations complete when the popover is opened or closed.\",\n      \"detailedType\": \"((open: boolean) => void) | undefined\"\n    },\n    \"triggerId\": {\n      \"type\": \"string | null\",\n      \"description\": \"ID of the trigger that the popover is associated with.\\nThis is useful in conjunction with the `open` prop to create a controlled popover.\\nThere's no need to specify this prop when the popover is uncontrolled (i.e. when the `open` prop is not set).\",\n      \"detailedType\": \"string | null | undefined\"\n    },\n    \"children\": {\n      \"type\": \"ReactNode | PayloadChildRenderFunction<Payload>\",\n      \"description\": \"The content of the popover.\\nThis can be a regular React node or a render function that receives the `payload` of the active trigger.\",\n      \"detailedType\": \"| React.ReactNode\\n| ((arg: { payload: Payload | undefined }) => ReactNode)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/popover-title.json",
    "content": "{\n  \"name\": \"PopoverTitle\",\n  \"description\": \"A heading that labels the popover.\\nRenders an `<h2>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Popover.Title.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Popover.Title.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Popover.Title.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Popover.Title.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Popover.Title.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Popover.Title.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/popover-trigger.json",
    "content": "{\n  \"name\": \"PopoverTrigger\",\n  \"description\": \"A button that opens the popover.\\nRenders a `<button>` element.\",\n  \"props\": {\n    \"handle\": {\n      \"type\": \"Popover.Handle<Payload>\",\n      \"description\": \"A handle to associate the trigger with a popover.\",\n      \"detailedType\": \"{} | undefined\"\n    },\n    \"nativeButton\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether the component renders a native `<button>` element when replacing it\\nvia the `render` prop.\\nSet to `false` if the rendered element is not a button (e.g. `<div>`).\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"payload\": {\n      \"type\": \"Payload\",\n      \"description\": \"A payload to pass to the popover when it is opened.\",\n      \"detailedType\": \"Payload | undefined\"\n    },\n    \"openOnHover\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the popover should also open when the trigger is hovered.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"delay\": {\n      \"type\": \"number\",\n      \"default\": \"300\",\n      \"description\": \"How long to wait before the popover may be opened on hover. Specified in milliseconds.\\n\\nRequires the `openOnHover` prop.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"closeDelay\": {\n      \"type\": \"number\",\n      \"default\": \"0\",\n      \"description\": \"How long to wait before closing the popover that was opened on hover.\\nSpecified in milliseconds.\\n\\nRequires the `openOnHover` prop.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"id\": {\n      \"type\": \"string\",\n      \"description\": \"ID of the trigger. In addition to being forwarded to the rendered element,\\nit is also used to specify the active trigger for the popover in controlled mode (with the Popover.Root `triggerId` prop).\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Popover.Trigger.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Popover.Trigger.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Popover.Trigger.State) => CSSProperties | undefined)\",\n      \"description\": \"Style applied to the element, or a function that\\nreturns a style object based on the component’s state.\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Popover.Trigger.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Popover.Trigger.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Popover.Trigger.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-popup-open\": {\n      \"description\": \"Present when the corresponding popover is open.\"\n    },\n    \"data-pressed\": {\n      \"description\": \"Present when the trigger is pressed.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/popover-viewport.json",
    "content": "{\n  \"name\": \"PopoverViewport\",\n  \"description\": \"A viewport for displaying content transitions.\\nThis component is only required if one popup can be opened by multiple triggers, its content change based on the trigger\\nand switching between them is animated.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"children\": {\n      \"type\": \"ReactNode\",\n      \"description\": \"The content to render inside the transition container.\",\n      \"detailedType\": \"React.ReactNode\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Popover.Viewport.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Popover.Viewport.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Popover.Viewport.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Popover.Viewport.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Popover.Viewport.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Popover.Viewport.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-activation-direction\": {\n      \"description\": \"Indicates the direction from which the popup was activated.\\nThis can be used to create directional animations based on how the popup was triggered.\\nContains space-separated values for both horizontal and vertical axes.\",\n      \"type\": \"`${'left' | 'right'} {'top' | 'bottom'}`\"\n    },\n    \"data-current\": {\n      \"description\": \"Applied to the direct child of the viewport when no transitions are present or the new content when it's entering.\"\n    },\n    \"data-instant\": {\n      \"description\": \"Present if animations should be instant.\",\n      \"type\": \"'dismiss' | 'click'\"\n    },\n    \"data-previous\": {\n      \"description\": \"Applied to the direct child of the viewport that contains the exiting content when transitions are present.\"\n    },\n    \"data-transitioning\": {\n      \"description\": \"Indicates that the viewport is currently transitioning between old and new content.\"\n    }\n  },\n  \"cssVariables\": {\n    \"--popup-height\": {\n      \"description\": \"The height of the parent popup.\\nThis variable is placed on the 'previous' container and stores the height of the popup when the previous content was rendered.\\nIt can be used to freeze the dimensions of the popup when animating between different content.\"\n    },\n    \"--popup-width\": {\n      \"description\": \"The width of the parent popup.\\nThis variable is placed on the 'previous' container and stores the width of the popup when the previous content was rendered.\\nIt can be used to freeze the dimensions of the popup when animating between different content.\"\n    }\n  }\n}\n"
  },
  {
    "path": "docs/reference/generated/preview-card-arrow.json",
    "content": "{\n  \"name\": \"PreviewCardArrow\",\n  \"description\": \"Displays an element positioned against the preview card anchor.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: PreviewCard.Arrow.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: PreviewCard.Arrow.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: PreviewCard.Arrow.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: PreviewCard.Arrow.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: PreviewCard.Arrow.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: PreviewCard.Arrow.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the preview card is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the preview card is closed.\"\n    },\n    \"data-uncentered\": {\n      \"description\": \"Present when the preview card arrow is uncentered.\"\n    },\n    \"data-align\": {\n      \"description\": \"Indicates how the popup is aligned relative to specified side.\",\n      \"type\": \"'start' | 'center' | 'end'\"\n    },\n    \"data-side\": {\n      \"description\": \"Indicates which side the popup is positioned relative to the trigger.\",\n      \"type\": \"'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/preview-card-backdrop.json",
    "content": "{\n  \"name\": \"PreviewCardBackdrop\",\n  \"description\": \"An overlay displayed beneath the popup.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: PreviewCard.Backdrop.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((\\n    state: PreviewCard.Backdrop.State,\\n  ) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: PreviewCard.Backdrop.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: PreviewCard.Backdrop.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: PreviewCard.Backdrop.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: PreviewCard.Backdrop.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the preview card is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the preview card is closed.\"\n    },\n    \"data-starting-style\": {\n      \"description\": \"Present when the preview card is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the preview card is animating out.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/preview-card-popup.json",
    "content": "{\n  \"name\": \"PreviewCardPopup\",\n  \"description\": \"A container for the preview card contents.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: PreviewCard.Popup.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: PreviewCard.Popup.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: PreviewCard.Popup.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: PreviewCard.Popup.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: PreviewCard.Popup.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: PreviewCard.Popup.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the preview card is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the preview card is closed.\"\n    },\n    \"data-align\": {\n      \"description\": \"Indicates how the popup is aligned relative to specified side.\",\n      \"type\": \"'start' | 'center' | 'end'\"\n    },\n    \"data-side\": {\n      \"description\": \"Indicates which side the popup is positioned relative to the trigger.\",\n      \"type\": \"'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'\"\n    },\n    \"data-starting-style\": {\n      \"description\": \"Present when the preview card is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the preview card is animating out.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/preview-card-portal.json",
    "content": "{\n  \"name\": \"PreviewCardPortal\",\n  \"description\": \"A portal element that moves the popup to a different part of the DOM.\\nBy default, the portal element is appended to `<body>`.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"container\": {\n      \"type\": \"HTMLElement | ShadowRoot | RefObject<HTMLElement | ShadowRoot | null> | null\",\n      \"description\": \"A parent element to render the portal element into.\",\n      \"detailedType\": \"| HTMLElement\\n| ShadowRoot\\n| React.RefObject<HTMLElement | ShadowRoot | null>\\n| null\\n| undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: PreviewCard.Portal.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: PreviewCard.Portal.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: PreviewCard.Portal.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: PreviewCard.Portal.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"keepMounted\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to keep the portal mounted in the DOM while the popup is hidden.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: PreviewCard.Portal.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: PreviewCard.Portal.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/preview-card-positioner.json",
    "content": "{\n  \"name\": \"PreviewCardPositioner\",\n  \"description\": \"Positions the popup against the trigger.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"disableAnchorTracking\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to disable the popup from tracking any layout shift of its positioning anchor.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"align\": {\n      \"type\": \"Align\",\n      \"default\": \"'center'\",\n      \"description\": \"How to align the popup relative to the specified side.\",\n      \"detailedType\": \"'start' | 'center' | 'end' | undefined\"\n    },\n    \"alignOffset\": {\n      \"type\": \"number | OffsetFunction\",\n      \"default\": \"0\",\n      \"description\": \"Additional offset along the alignment axis in pixels.\\nAlso accepts a function that returns the offset to read the dimensions of the anchor\\nand positioner elements, along with its side and alignment.\\n\\nThe function takes a `data` object parameter with the following properties:\\n- `data.anchor`: the dimensions of the anchor element with properties `width` and `height`.\\n- `data.positioner`: the dimensions of the positioner element with properties `width` and `height`.\\n- `data.side`: which side of the anchor element the positioner is aligned against.\\n- `data.align`: how the positioner is aligned relative to the specified side.\",\n      \"example\": \"```jsx\\n<Positioner\\n  alignOffset={({ side, align, anchor, positioner }) => {\\n    return side === 'top' || side === 'bottom'\\n      ? anchor.width\\n      : anchor.height;\\n  }}\\n/>\\n```\",\n      \"detailedType\": \"| number\\n| ((data: {\\n    side: Side\\n    align: Align\\n    anchor: { width: number; height: number }\\n    positioner: { width: number; height: number }\\n  }) => number)\\n| undefined\"\n    },\n    \"side\": {\n      \"type\": \"Side\",\n      \"default\": \"'bottom'\",\n      \"description\": \"Which side of the anchor element to align the popup against.\\nMay automatically change to avoid collisions.\",\n      \"detailedType\": \"| 'top'\\n| 'bottom'\\n| 'left'\\n| 'right'\\n| 'inline-end'\\n| 'inline-start'\\n| undefined\"\n    },\n    \"sideOffset\": {\n      \"type\": \"number | OffsetFunction\",\n      \"default\": \"0\",\n      \"description\": \"Distance between the anchor and the popup in pixels.\\nAlso accepts a function that returns the distance to read the dimensions of the anchor\\nand positioner elements, along with its side and alignment.\\n\\nThe function takes a `data` object parameter with the following properties:\\n- `data.anchor`: the dimensions of the anchor element with properties `width` and `height`.\\n- `data.positioner`: the dimensions of the positioner element with properties `width` and `height`.\\n- `data.side`: which side of the anchor element the positioner is aligned against.\\n- `data.align`: how the positioner is aligned relative to the specified side.\",\n      \"example\": \"```jsx\\n<Positioner\\n  sideOffset={({ side, align, anchor, positioner }) => {\\n    return side === 'top' || side === 'bottom'\\n      ? anchor.height\\n      : anchor.width;\\n  }}\\n/>\\n```\",\n      \"detailedType\": \"| number\\n| ((data: {\\n    side: Side\\n    align: Align\\n    anchor: { width: number; height: number }\\n    positioner: { width: number; height: number }\\n  }) => number)\\n| undefined\"\n    },\n    \"arrowPadding\": {\n      \"type\": \"number\",\n      \"default\": \"5\",\n      \"description\": \"Minimum distance to maintain between the arrow and the edges of the popup.\\n\\nUse it to prevent the arrow element from hanging out of the rounded corners of a popup.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"anchor\": {\n      \"type\": \"Element | VirtualElement | RefObject<Element | null> | (() => Element | VirtualElement | null) | null\",\n      \"description\": \"An element to position the popup against.\\nBy default, the popup will be positioned against the trigger.\",\n      \"detailedType\": \"| Element\\n| VirtualElement\\n| React.RefObject<Element | null>\\n| (() => Element | VirtualElement | null)\\n| null\\n| undefined\"\n    },\n    \"collisionAvoidance\": {\n      \"type\": \"CollisionAvoidance\",\n      \"description\": \"Determines how to handle collisions when positioning the popup.\\n\\n`side` controls overflow on the preferred placement axis (`top`/`bottom` or `left`/`right`):\\n- `'flip'`: keep the requested side when it fits; otherwise try the opposite side\\n  (`top` and `bottom`, or `left` and `right`).\\n- `'shift'`: never change side; keep the requested side and move the popup within\\n  the clipping boundary so it stays visible.\\n- `'none'`: do not correct side-axis overflow.\\n\\n`align` controls overflow on the alignment axis (`start`/`center`/`end`):\\n- `'flip'`: keep side, but swap `start` and `end` when the requested alignment overflows.\\n- `'shift'`: keep side and requested alignment, then nudge the popup along the\\n  alignment axis to fit.\\n- `'none'`: do not correct alignment-axis overflow.\\n\\n`fallbackAxisSide` controls fallback behavior on the perpendicular axis when the\\npreferred axis cannot fit:\\n- `'start'`: allow perpendicular fallback and try the logical start side first\\n  (`top` before `bottom`, or `left` before `right` in LTR).\\n- `'end'`: allow perpendicular fallback and try the logical end side first\\n  (`bottom` before `top`, or `right` before `left` in LTR).\\n- `'none'`: do not fallback to the perpendicular axis.\\n\\nWhen `side` is `'shift'`, explicitly setting `align` only supports `'shift'` or `'none'`.\\nIf `align` is omitted, it defaults to `'flip'`.\",\n      \"example\": \"```jsx\\n<Positioner\\n  collisionAvoidance={{\\n    side: 'shift',\\n    align: 'shift',\\n    fallbackAxisSide: 'none',\\n  }}\\n/>\\n```\",\n      \"detailedType\": \"| {\\n    side?: 'flip' | 'none'\\n    align?: 'flip' | 'shift' | 'none'\\n    fallbackAxisSide?: 'start' | 'end' | 'none'\\n  }\\n| {\\n    side?: 'shift' | 'none'\\n    align?: 'shift' | 'none'\\n    fallbackAxisSide?: 'start' | 'end' | 'none'\\n  }\\n| undefined\"\n    },\n    \"collisionBoundary\": {\n      \"type\": \"Boundary\",\n      \"default\": \"'clipping-ancestors'\",\n      \"description\": \"An element or a rectangle that delimits the area that the popup is confined to.\",\n      \"detailedType\": \"| 'clipping-ancestors'\\n| Element\\n| Element[]\\n| Rect\\n| undefined\"\n    },\n    \"collisionPadding\": {\n      \"type\": \"Padding\",\n      \"default\": \"5\",\n      \"description\": \"Additional space to maintain from the edge of the collision boundary.\",\n      \"detailedType\": \"| {\\n    top?: number\\n    right?: number\\n    bottom?: number\\n    left?: number\\n  }\\n| number\\n| undefined\"\n    },\n    \"sticky\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to maintain the popup in the viewport after\\nthe anchor element was scrolled out of view.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"positionMethod\": {\n      \"type\": \"'absolute' | 'fixed'\",\n      \"default\": \"'absolute'\",\n      \"description\": \"Determines which CSS `position` property to use.\",\n      \"detailedType\": \"'absolute' | 'fixed' | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: PreviewCard.Positioner.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((\\n    state: PreviewCard.Positioner.State,\\n  ) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: PreviewCard.Positioner.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: PreviewCard.Positioner.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: PreviewCard.Positioner.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: PreviewCard.Positioner.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the preview card is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the preview card is closed.\"\n    },\n    \"data-anchor-hidden\": {\n      \"description\": \"Present when the anchor is hidden.\"\n    },\n    \"data-align\": {\n      \"description\": \"Indicates how the popup is aligned relative to specified side.\",\n      \"type\": \"'start' | 'center' | 'end'\"\n    },\n    \"data-side\": {\n      \"description\": \"Indicates which side the popup is positioned relative to the trigger.\",\n      \"type\": \"'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'\"\n    }\n  },\n  \"cssVariables\": {\n    \"--anchor-height\": {\n      \"description\": \"The anchor's height.\",\n      \"type\": \"number\"\n    },\n    \"--anchor-width\": {\n      \"description\": \"The anchor's width.\",\n      \"type\": \"number\"\n    },\n    \"--available-height\": {\n      \"description\": \"The available height between the trigger and the edge of the viewport.\",\n      \"type\": \"number\"\n    },\n    \"--available-width\": {\n      \"description\": \"The available width between the trigger and the edge of the viewport.\",\n      \"type\": \"number\"\n    },\n    \"--transform-origin\": {\n      \"description\": \"The coordinates that this element is anchored to. Used for animations and transitions.\",\n      \"type\": \"string\"\n    }\n  }\n}\n"
  },
  {
    "path": "docs/reference/generated/preview-card-root.json",
    "content": "{\n  \"name\": \"PreviewCardRoot\",\n  \"description\": \"Groups all parts of the preview card.\\nDoesn’t render its own HTML element.\",\n  \"props\": {\n    \"defaultOpen\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the preview card is initially open.\\n\\nTo render a controlled preview card, use the `open` prop instead.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"open\": {\n      \"type\": \"boolean\",\n      \"description\": \"Whether the preview card is currently open.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"onOpenChange\": {\n      \"type\": \"((open: boolean, eventDetails: PreviewCard.Root.ChangeEventDetails) => void)\",\n      \"description\": \"Event handler called when the preview card is opened or closed.\",\n      \"detailedType\": \"| ((\\n    open: boolean,\\n    eventDetails: PreviewCard.Root.ChangeEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"actionsRef\": {\n      \"type\": \"RefObject<PreviewCard.Root.Actions | null>\",\n      \"description\": \"A ref to imperative actions.\\n- `unmount`: Unmounts the preview card popup.\\n- `close`: Closes the preview card imperatively when called.\",\n      \"detailedType\": \"| React.RefObject<PreviewCard.Root.Actions | null>\\n| undefined\"\n    },\n    \"defaultTriggerId\": {\n      \"type\": \"string | null\",\n      \"description\": \"ID of the trigger that the preview card is associated with.\\nThis is useful in conjunction with the `defaultOpen` prop to create an initially open preview card.\",\n      \"detailedType\": \"string | null | undefined\"\n    },\n    \"handle\": {\n      \"type\": \"PreviewCard.Handle<Payload>\",\n      \"description\": \"A handle to associate the preview card with a trigger.\\nIf specified, allows external triggers to control the card's open state.\\nCan be created with the PreviewCard.createHandle() method.\",\n      \"detailedType\": \"{} | undefined\"\n    },\n    \"onOpenChangeComplete\": {\n      \"type\": \"((open: boolean) => void)\",\n      \"description\": \"Event handler called after any animations complete when the preview card is opened or closed.\",\n      \"detailedType\": \"((open: boolean) => void) | undefined\"\n    },\n    \"triggerId\": {\n      \"type\": \"string | null\",\n      \"description\": \"ID of the trigger that the preview card is associated with.\\nThis is useful in conjunction with the `open` prop to create a controlled preview card.\\nThere's no need to specify this prop when the preview card is uncontrolled (i.e. when the `open` prop is not set).\",\n      \"detailedType\": \"string | null | undefined\"\n    },\n    \"children\": {\n      \"type\": \"ReactNode | PayloadChildRenderFunction<Payload>\",\n      \"description\": \"The content of the preview card.\\nThis can be a regular React node or a render function that receives the `payload` of the active trigger.\",\n      \"detailedType\": \"| React.ReactNode\\n| ((arg: { payload: Payload | undefined }) => ReactNode)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/preview-card-trigger.json",
    "content": "{\n  \"name\": \"PreviewCardTrigger\",\n  \"description\": \"A link that opens the preview card.\\nRenders an `<a>` element.\",\n  \"props\": {\n    \"handle\": {\n      \"type\": \"PreviewCard.Handle<Payload>\",\n      \"description\": \"A handle to associate the trigger with a preview card.\",\n      \"detailedType\": \"{} | undefined\"\n    },\n    \"payload\": {\n      \"type\": \"Payload\",\n      \"description\": \"A payload to pass to the preview card when it is opened.\",\n      \"detailedType\": \"Payload | undefined\"\n    },\n    \"delay\": {\n      \"type\": \"number\",\n      \"default\": \"600\",\n      \"description\": \"How long to wait before the preview card opens. Specified in milliseconds.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"closeDelay\": {\n      \"type\": \"number\",\n      \"default\": \"300\",\n      \"description\": \"How long to wait before closing the preview card. Specified in milliseconds.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: PreviewCard.Trigger.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: PreviewCard.Trigger.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: PreviewCard.Trigger.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: PreviewCard.Trigger.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: PreviewCard.Trigger.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: PreviewCard.Trigger.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-popup-open\": {\n      \"description\": \"Present when the corresponding preview card is open.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/preview-card-viewport.json",
    "content": "{\n  \"name\": \"PreviewCardViewport\",\n  \"description\": \"A viewport for displaying content transitions.\\nThis component is only required if one popup can be opened by multiple triggers, its content change based on the trigger\\nand switching between them is animated.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"children\": {\n      \"type\": \"ReactNode\",\n      \"description\": \"The content to render inside the transition container.\",\n      \"detailedType\": \"React.ReactNode\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: PreviewCard.Viewport.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((\\n    state: PreviewCard.Viewport.State,\\n  ) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: PreviewCard.Viewport.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: PreviewCard.Viewport.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: PreviewCard.Viewport.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: PreviewCard.Viewport.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-activation-direction\": {\n      \"description\": \"Indicates the direction from which the popup was activated.\\nThis can be used to create directional animations based on how the popup was triggered.\\nContains space-separated values for both horizontal and vertical axes.\",\n      \"type\": \"`${'left' | 'right'} {'top' | 'bottom'}`\"\n    },\n    \"data-current\": {\n      \"description\": \"Applied to the direct child of the viewport when no transitions are present or the new content when it's entering.\"\n    },\n    \"data-instant\": {\n      \"description\": \"Present if animations should be instant.\",\n      \"type\": \"'delay' | 'dismiss' | 'focus'\"\n    },\n    \"data-previous\": {\n      \"description\": \"Applied to the direct child of the viewport that contains the exiting content when transitions are present.\"\n    },\n    \"data-transitioning\": {\n      \"description\": \"Indicates that the viewport is currently transitioning between old and new content.\"\n    }\n  },\n  \"cssVariables\": {\n    \"--popup-height\": {\n      \"description\": \"The height of the parent popup.\\nThis variable is placed on the 'previous' container and stores the height of the popup when the previous content was rendered.\\nIt can be used to freeze the dimensions of the popup when animating between different content.\"\n    },\n    \"--popup-width\": {\n      \"description\": \"The width of the parent popup.\\nThis variable is placed on the 'previous' container and stores the width of the popup when the previous content was rendered.\\nIt can be used to freeze the dimensions of the popup when animating between different content.\"\n    }\n  }\n}\n"
  },
  {
    "path": "docs/reference/generated/progress-indicator.json",
    "content": "{\n  \"name\": \"ProgressIndicator\",\n  \"description\": \"Visualizes the completion status of the task.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Progress.Indicator.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Progress.Indicator.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Progress.Indicator.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Progress.Indicator.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Progress.Indicator.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Progress.Indicator.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-complete\": {\n      \"description\": \"Present when the progress has completed.\"\n    },\n    \"data-indeterminate\": {\n      \"description\": \"Present when the progress is in indeterminate state.\"\n    },\n    \"data-progressing\": {\n      \"description\": \"Present while the progress is progressing.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/progress-label.json",
    "content": "{\n  \"name\": \"ProgressLabel\",\n  \"description\": \"An accessible label for the progress bar.\\nRenders a `<span>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Progress.Label.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Progress.Label.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Progress.Label.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Progress.Label.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Progress.Label.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Progress.Label.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-complete\": {\n      \"description\": \"Present when the progress has completed.\"\n    },\n    \"data-indeterminate\": {\n      \"description\": \"Present when the progress is in indeterminate state.\"\n    },\n    \"data-progressing\": {\n      \"description\": \"Present while the progress is progressing.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/progress-root.json",
    "content": "{\n  \"name\": \"ProgressRoot\",\n  \"description\": \"Groups all parts of the progress bar and provides the task completion status to screen readers.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"value\": {\n      \"type\": \"number | null\",\n      \"default\": \"null\",\n      \"required\": true,\n      \"description\": \"The current value. The component is indeterminate when value is `null`.\"\n    },\n    \"aria-valuetext\": {\n      \"type\": \"string\",\n      \"description\": \"A string value that provides a user-friendly name for `aria-valuenow`, the current value of the meter.\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"getAriaValueText\": {\n      \"type\": \"((formattedValue: string | null, value: number | null) => string)\",\n      \"description\": \"Accepts a function which returns a string value that provides a human-readable text alternative for the current value of the progress bar.\",\n      \"detailedType\": \"| ((\\n    formattedValue: string | null,\\n    value: number | null,\\n  ) => string)\\n| undefined\"\n    },\n    \"locale\": {\n      \"type\": \"Intl.LocalesArgument\",\n      \"description\": \"The locale used by `Intl.NumberFormat` when formatting the value.\\nDefaults to the user's runtime locale.\"\n    },\n    \"min\": {\n      \"type\": \"number\",\n      \"default\": \"0\",\n      \"description\": \"The minimum value.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"max\": {\n      \"type\": \"number\",\n      \"default\": \"100\",\n      \"description\": \"The maximum value.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"format\": {\n      \"type\": \"Intl.NumberFormatOptions\",\n      \"description\": \"Options to format the value.\",\n      \"detailedType\": \"Intl.NumberFormatOptions | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Progress.Root.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Progress.Root.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Progress.Root.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Progress.Root.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Progress.Root.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Progress.Root.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-complete\": {\n      \"description\": \"Present when the progress has completed.\"\n    },\n    \"data-indeterminate\": {\n      \"description\": \"Present when the progress is in indeterminate state.\"\n    },\n    \"data-progressing\": {\n      \"description\": \"Present while the progress is progressing.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/progress-track.json",
    "content": "{\n  \"name\": \"ProgressTrack\",\n  \"description\": \"Contains the progress bar indicator.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Progress.Track.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Progress.Track.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Progress.Track.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Progress.Track.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Progress.Track.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Progress.Track.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-complete\": {\n      \"description\": \"Present when the progress has completed.\"\n    },\n    \"data-indeterminate\": {\n      \"description\": \"Present when the progress is in indeterminate state.\"\n    },\n    \"data-progressing\": {\n      \"description\": \"Present while the progress is progressing.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/progress-value.json",
    "content": "{\n  \"name\": \"ProgressValue\",\n  \"description\": \"A text label displaying the current value.\\nRenders a `<span>` element.\",\n  \"props\": {\n    \"children\": {\n      \"type\": \"((formattedValue: string | null, value: number | null) => ReactNode) | null\",\n      \"detailedType\": \"| ((\\n    formattedValue: string | null,\\n    value: number | null,\\n  ) => ReactNode)\\n| null\\n| undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Progress.Value.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Progress.Value.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Progress.Value.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Progress.Value.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Progress.Value.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Progress.Value.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-complete\": {\n      \"description\": \"Present when the progress has completed.\"\n    },\n    \"data-indeterminate\": {\n      \"description\": \"Present when the progress is in indeterminate state.\"\n    },\n    \"data-progressing\": {\n      \"description\": \"Present while the progress is progressing.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/radio-group.json",
    "content": "{\n  \"name\": \"RadioGroup\",\n  \"description\": \"Provides a shared state to a series of radio buttons.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"name\": {\n      \"type\": \"string\",\n      \"description\": \"Identifies the field when a form is submitted.\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"defaultValue\": {\n      \"type\": \"Value\",\n      \"description\": \"The uncontrolled value of the radio button that should be initially selected.\\n\\nTo render a controlled radio group, use the `value` prop instead.\",\n      \"detailedType\": \"Value | undefined\"\n    },\n    \"value\": {\n      \"type\": \"Value\",\n      \"description\": \"The controlled value of the radio item that should be currently selected.\\n\\nTo render an uncontrolled radio group, use the `defaultValue` prop instead.\",\n      \"detailedType\": \"Value | undefined\"\n    },\n    \"onValueChange\": {\n      \"type\": \"((value: Value, eventDetails: Radio.Group.ChangeEventDetails) => void)\",\n      \"description\": \"Callback fired when the value changes.\",\n      \"detailedType\": \"| ((\\n    value: Value,\\n    eventDetails: Radio.Group.ChangeEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component should ignore user interaction.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"readOnly\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the user should be unable to select a different radio button in the group.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"required\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the user must choose a value before submitting a form.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"inputRef\": {\n      \"type\": \"Ref<HTMLInputElement>\",\n      \"description\": \"A ref to access the hidden input element.\",\n      \"detailedType\": \"React.Ref<HTMLInputElement> | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: RadioGroup.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: RadioGroup.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: RadioGroup.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: RadioGroup.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: RadioGroup.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: RadioGroup.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-disabled\": {\n      \"description\": \"Present when the radio group is disabled.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/radio-indicator.json",
    "content": "{\n  \"name\": \"RadioIndicator\",\n  \"description\": \"Indicates whether the radio button is selected.\\nRenders a `<span>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Radio.Indicator.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Radio.Indicator.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Radio.Indicator.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Radio.Indicator.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"keepMounted\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to keep the HTML element in the DOM when the radio button is inactive.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Radio.Indicator.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Radio.Indicator.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-checked\": {\n      \"description\": \"Present when the radio is checked.\"\n    },\n    \"data-unchecked\": {\n      \"description\": \"Present when the radio is not checked.\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the radio is disabled.\"\n    },\n    \"data-readonly\": {\n      \"description\": \"Present when the radio is readonly.\"\n    },\n    \"data-required\": {\n      \"description\": \"Present when the radio is required.\"\n    },\n    \"data-valid\": {\n      \"description\": \"Present when the radio is in valid state (when wrapped in Field.Root).\"\n    },\n    \"data-invalid\": {\n      \"description\": \"Present when the radio is in invalid state (when wrapped in Field.Root).\"\n    },\n    \"data-dirty\": {\n      \"description\": \"Present when the radio's value has changed (when wrapped in Field.Root).\"\n    },\n    \"data-touched\": {\n      \"description\": \"Present when the radio has been touched (when wrapped in Field.Root).\"\n    },\n    \"data-filled\": {\n      \"description\": \"Present when the radio is checked (when wrapped in Field.Root).\"\n    },\n    \"data-focused\": {\n      \"description\": \"Present when the radio is focused (when wrapped in Field.Root).\"\n    },\n    \"data-starting-style\": {\n      \"description\": \"Present when the radio indicator is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the radio indicator is animating out.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/radio-root.json",
    "content": "{\n  \"name\": \"RadioRoot\",\n  \"description\": \"Represents the radio button itself.\\nRenders a `<span>` element and a hidden `<input>` beside.\",\n  \"props\": {\n    \"value\": {\n      \"type\": \"Value\",\n      \"required\": true,\n      \"description\": \"The unique identifying value of the radio in a group.\"\n    },\n    \"nativeButton\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component renders a native `<button>` element when replacing it\\nvia the `render` prop.\\nSet to `true` if the rendered element is a native button.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"description\": \"Whether the component should ignore user interaction.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"readOnly\": {\n      \"type\": \"boolean\",\n      \"description\": \"Whether the user should be unable to select the radio button.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"required\": {\n      \"type\": \"boolean\",\n      \"description\": \"Whether the user must choose a value before submitting a form.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"inputRef\": {\n      \"type\": \"Ref<HTMLInputElement>\",\n      \"description\": \"A ref to access the hidden input element.\",\n      \"detailedType\": \"React.Ref<HTMLInputElement> | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Radio.Root.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Radio.Root.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Radio.Root.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Radio.Root.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Radio.Root.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Radio.Root.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-checked\": {\n      \"description\": \"Present when the radio is checked.\"\n    },\n    \"data-unchecked\": {\n      \"description\": \"Present when the radio is not checked.\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the radio is disabled.\"\n    },\n    \"data-readonly\": {\n      \"description\": \"Present when the radio is readonly.\"\n    },\n    \"data-required\": {\n      \"description\": \"Present when the radio is required.\"\n    },\n    \"data-valid\": {\n      \"description\": \"Present when the radio is in valid state (when wrapped in Field.Root).\"\n    },\n    \"data-invalid\": {\n      \"description\": \"Present when the radio is in invalid state (when wrapped in Field.Root).\"\n    },\n    \"data-dirty\": {\n      \"description\": \"Present when the radio's value has changed (when wrapped in Field.Root).\"\n    },\n    \"data-touched\": {\n      \"description\": \"Present when the radio has been touched (when wrapped in Field.Root).\"\n    },\n    \"data-filled\": {\n      \"description\": \"Present when the radio is checked (when wrapped in Field.Root).\"\n    },\n    \"data-focused\": {\n      \"description\": \"Present when the radio is focused (when wrapped in Field.Root).\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/scroll-area-content.json",
    "content": "{\n  \"name\": \"ScrollAreaContent\",\n  \"description\": \"A container for the content of the scroll area.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: ScrollArea.Content.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: ScrollArea.Content.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: ScrollArea.Content.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: ScrollArea.Content.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: ScrollArea.Content.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: ScrollArea.Content.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/scroll-area-corner.json",
    "content": "{\n  \"name\": \"ScrollAreaCorner\",\n  \"description\": \"A small rectangular area that appears at the intersection of horizontal and vertical scrollbars.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: ScrollArea.Corner.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: ScrollArea.Corner.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: ScrollArea.Corner.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: ScrollArea.Corner.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: ScrollArea.Corner.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: ScrollArea.Corner.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/scroll-area-root.json",
    "content": "{\n  \"name\": \"ScrollAreaRoot\",\n  \"description\": \"Groups all parts of the scroll area.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"overflowEdgeThreshold\": {\n      \"type\": \"number | Partial<{ xStart: number, xEnd: number, yStart: number, yEnd: number }>\",\n      \"default\": \"0\",\n      \"description\": \"The threshold in pixels that must be passed before the overflow edge attributes are applied.\\nAccepts a single number for all edges or an object to configure them individually.\",\n      \"detailedType\": \"| number\\n| {\\n    xStart?: number\\n    xEnd?: number\\n    yStart?: number\\n    yEnd?: number\\n  }\\n| undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: ScrollArea.Root.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: ScrollArea.Root.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: ScrollArea.Root.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: ScrollArea.Root.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: ScrollArea.Root.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: ScrollArea.Root.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-has-overflow-x\": {\n      \"description\": \"Present when the scroll area content is wider than the viewport.\"\n    },\n    \"data-has-overflow-y\": {\n      \"description\": \"Present when the scroll area content is taller than the viewport.\"\n    },\n    \"data-overflow-x-end\": {\n      \"description\": \"Present when there is overflow on the horizontal end side.\"\n    },\n    \"data-overflow-x-start\": {\n      \"description\": \"Present when there is overflow on the horizontal start side.\"\n    },\n    \"data-overflow-y-end\": {\n      \"description\": \"Present when there is overflow on the vertical end side.\"\n    },\n    \"data-overflow-y-start\": {\n      \"description\": \"Present when there is overflow on the vertical start side.\"\n    },\n    \"data-scrolling\": {\n      \"description\": \"Present when the user scrolls inside the scroll area.\"\n    }\n  },\n  \"cssVariables\": {\n    \"--scroll-area-corner-height\": {\n      \"description\": \"The scroll area's corner height.\",\n      \"type\": \"number\"\n    },\n    \"--scroll-area-corner-width\": {\n      \"description\": \"The scroll area's corner width.\",\n      \"type\": \"number\"\n    }\n  }\n}\n"
  },
  {
    "path": "docs/reference/generated/scroll-area-scrollbar.json",
    "content": "{\n  \"name\": \"ScrollAreaScrollbar\",\n  \"description\": \"A vertical or horizontal scrollbar for the scroll area.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"orientation\": {\n      \"type\": \"'vertical' | 'horizontal'\",\n      \"default\": \"'vertical'\",\n      \"description\": \"Whether the scrollbar controls vertical or horizontal scroll.\",\n      \"detailedType\": \"'vertical' | 'horizontal' | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: ScrollArea.Scrollbar.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((\\n    state: ScrollArea.Scrollbar.State,\\n  ) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: ScrollArea.Scrollbar.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: ScrollArea.Scrollbar.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"keepMounted\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to keep the HTML element in the DOM when the viewport isn’t scrollable.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: ScrollArea.Scrollbar.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: ScrollArea.Scrollbar.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-orientation\": {\n      \"description\": \"Indicates the orientation of the scrollbar.\",\n      \"type\": \"'horizontal' | 'vertical'\"\n    },\n    \"data-has-overflow-x\": {\n      \"description\": \"Present when the scroll area content is wider than the viewport.\"\n    },\n    \"data-has-overflow-y\": {\n      \"description\": \"Present when the scroll area content is taller than the viewport.\"\n    },\n    \"data-hovering\": {\n      \"description\": \"Present when the pointer is over the scroll area.\"\n    },\n    \"data-overflow-x-end\": {\n      \"description\": \"Present when there is overflow on the horizontal end side.\"\n    },\n    \"data-overflow-x-start\": {\n      \"description\": \"Present when there is overflow on the horizontal start side.\"\n    },\n    \"data-overflow-y-end\": {\n      \"description\": \"Present when there is overflow on the vertical end side.\"\n    },\n    \"data-overflow-y-start\": {\n      \"description\": \"Present when there is overflow on the vertical start side.\"\n    },\n    \"data-scrolling\": {\n      \"description\": \"Present when the user scrolls inside the scroll area.\"\n    }\n  },\n  \"cssVariables\": {\n    \"--scroll-area-thumb-height\": {\n      \"description\": \"The scroll area thumb's height.\",\n      \"type\": \"number\"\n    },\n    \"--scroll-area-thumb-width\": {\n      \"description\": \"The scroll area thumb's width.\",\n      \"type\": \"number\"\n    }\n  }\n}\n"
  },
  {
    "path": "docs/reference/generated/scroll-area-thumb.json",
    "content": "{\n  \"name\": \"ScrollAreaThumb\",\n  \"description\": \"The draggable part of the scrollbar that indicates the current scroll position.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: ScrollArea.Thumb.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: ScrollArea.Thumb.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: ScrollArea.Thumb.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: ScrollArea.Thumb.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: ScrollArea.Thumb.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: ScrollArea.Thumb.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-orientation\": {\n      \"description\": \"Indicates the orientation of the scrollbar.\",\n      \"type\": \"'horizontal' | 'vertical'\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/scroll-area-viewport.json",
    "content": "{\n  \"name\": \"ScrollAreaViewport\",\n  \"description\": \"The actual scrollable container of the scroll area.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: ScrollArea.Viewport.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: ScrollArea.Viewport.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: ScrollArea.Viewport.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: ScrollArea.Viewport.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: ScrollArea.Viewport.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: ScrollArea.Viewport.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-has-overflow-x\": {\n      \"description\": \"Present when the scroll area content is wider than the viewport.\"\n    },\n    \"data-has-overflow-y\": {\n      \"description\": \"Present when the scroll area content is taller than the viewport.\"\n    },\n    \"data-overflow-x-end\": {\n      \"description\": \"Present when there is overflow on the horizontal end side.\"\n    },\n    \"data-overflow-x-start\": {\n      \"description\": \"Present when there is overflow on the horizontal start side.\"\n    },\n    \"data-overflow-y-end\": {\n      \"description\": \"Present when there is overflow on the vertical end side.\"\n    },\n    \"data-overflow-y-start\": {\n      \"description\": \"Present when there is overflow on the vertical start side.\"\n    },\n    \"data-scrolling\": {\n      \"description\": \"Present when the user scrolls inside the scroll area.\"\n    }\n  },\n  \"cssVariables\": {\n    \"--scroll-area-overflow-x-end\": {\n      \"description\": \"The distance from the horizontal end edge in pixels.\",\n      \"type\": \"number\"\n    },\n    \"--scroll-area-overflow-x-start\": {\n      \"description\": \"The distance from the horizontal start edge in pixels.\",\n      \"type\": \"number\"\n    },\n    \"--scroll-area-overflow-y-end\": {\n      \"description\": \"The distance from the vertical end edge in pixels.\",\n      \"type\": \"number\"\n    },\n    \"--scroll-area-overflow-y-start\": {\n      \"description\": \"The distance from the vertical start edge in pixels.\",\n      \"type\": \"number\"\n    }\n  }\n}\n"
  },
  {
    "path": "docs/reference/generated/select-arrow.json",
    "content": "{\n  \"name\": \"SelectArrow\",\n  \"description\": \"Displays an element positioned against the select popup anchor.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Select.Arrow.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Select.Arrow.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Select.Arrow.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Select.Arrow.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Select.Arrow.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Select.Arrow.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the select popup is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the select popup is closed.\"\n    },\n    \"data-uncentered\": {\n      \"description\": \"Present when the select arrow is uncentered.\"\n    },\n    \"data-align\": {\n      \"description\": \"Indicates how the popup is aligned relative to specified side.\",\n      \"type\": \"'start' | 'center' | 'end'\"\n    },\n    \"data-side\": {\n      \"description\": \"Indicates which side the popup is positioned relative to the trigger.\",\n      \"type\": \"'none' | 'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/select-backdrop.json",
    "content": "{\n  \"name\": \"SelectBackdrop\",\n  \"description\": \"An overlay displayed beneath the menu popup.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Select.Backdrop.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Select.Backdrop.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Select.Backdrop.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Select.Backdrop.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Select.Backdrop.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Select.Backdrop.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the select is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the select is closed.\"\n    },\n    \"data-starting-style\": {\n      \"description\": \"Present when the select is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the select is animating out.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/select-group-label.json",
    "content": "{\n  \"name\": \"SelectGroupLabel\",\n  \"description\": \"An accessible label that is automatically associated with its parent group.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Select.GroupLabel.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Select.GroupLabel.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Select.GroupLabel.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Select.GroupLabel.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Select.GroupLabel.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Select.GroupLabel.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/select-group.json",
    "content": "{\n  \"name\": \"SelectGroup\",\n  \"description\": \"Groups related select items with the corresponding label.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Select.Group.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Select.Group.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Select.Group.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Select.Group.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Select.Group.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Select.Group.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/select-icon.json",
    "content": "{\n  \"name\": \"SelectIcon\",\n  \"description\": \"An icon that indicates that the trigger button opens a select popup.\\nRenders a `<span>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Select.Icon.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Select.Icon.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Select.Icon.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Select.Icon.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Select.Icon.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Select.Icon.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-popup-open\": {\n      \"description\": \"Present when the corresponding popup is open.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/select-item-indicator.json",
    "content": "{\n  \"name\": \"SelectItemIndicator\",\n  \"description\": \"Indicates whether the select item is selected.\\nRenders a `<span>` element.\",\n  \"props\": {\n    \"children\": {\n      \"type\": \"ReactNode\",\n      \"detailedType\": \"React.ReactNode\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Select.ItemIndicator.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((\\n    state: Select.ItemIndicator.State,\\n  ) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Select.ItemIndicator.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Select.ItemIndicator.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"keepMounted\": {\n      \"type\": \"boolean\",\n      \"description\": \"Whether to keep the HTML element in the DOM when the item is not selected.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Select.ItemIndicator.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Select.ItemIndicator.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-starting-style\": {\n      \"description\": \"Present when the indicator is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the indicator is animating out.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/select-item-text.json",
    "content": "{\n  \"name\": \"SelectItemText\",\n  \"description\": \"A text label of the select item.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Select.ItemText.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Select.ItemText.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Select.ItemText.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Select.ItemText.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Select.ItemText.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Select.ItemText.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/select-item.json",
    "content": "{\n  \"name\": \"SelectItem\",\n  \"description\": \"An individual option in the select popup.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"label\": {\n      \"type\": \"string\",\n      \"description\": \"Specifies the text label to use when the item is matched during keyboard text navigation.\\n\\nDefaults to the item text content if not provided.\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"value\": {\n      \"type\": \"any\",\n      \"default\": \"null\",\n      \"description\": \"A unique value that identifies this select item.\"\n    },\n    \"nativeButton\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component renders a native `<button>` element when replacing it\\nvia the `render` prop.\\nSet to `true` if the rendered element is a native button.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component should ignore user interaction.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"children\": {\n      \"type\": \"ReactNode\",\n      \"detailedType\": \"React.ReactNode\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Select.Item.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Select.Item.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Select.Item.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Select.Item.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Select.Item.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Select.Item.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-selected\": {\n      \"description\": \"Present when the select item is selected.\"\n    },\n    \"data-highlighted\": {\n      \"description\": \"Present when the select item is highlighted.\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the select item is disabled.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/select-label.json",
    "content": "{\n  \"name\": \"SelectLabel\",\n  \"description\": \"An accessible label that is automatically associated with the select trigger.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Field.Root.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Field.Root.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Field.Root.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Field.Root.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Field.Root.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Field.Root.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/select-list.json",
    "content": "{\n  \"name\": \"SelectList\",\n  \"description\": \"A container for the select items.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Select.List.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Select.List.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Select.List.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Select.List.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Select.List.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Select.List.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/select-popup.json",
    "content": "{\n  \"name\": \"SelectPopup\",\n  \"description\": \"A container for the select list.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"finalFocus\": {\n      \"type\": \"boolean | RefObject<HTMLElement | null> | ((closeType: InteractionType) => boolean | void | HTMLElement | null)\",\n      \"description\": \"Determines the element to focus when the select popup is closed.\\n\\n- `false`: Do not move focus.\\n- `true`: Move focus based on the default behavior (trigger or previously focused element).\\n- `RefObject`: Move focus to the ref element.\\n- `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`).\\n  Return an element to focus, `true` to use the default behavior, or `false`/`undefined` to do nothing.\",\n      \"detailedType\": \"| boolean\\n| React.RefObject<HTMLElement | null>\\n| ((\\n    closeType: InteractionType,\\n  ) => boolean | void | HTMLElement | null)\\n| undefined\"\n    },\n    \"children\": {\n      \"type\": \"ReactNode\",\n      \"detailedType\": \"React.ReactNode\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Select.Popup.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Select.Popup.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Select.Popup.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Select.Popup.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Select.Popup.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Select.Popup.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the select is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the select is closed.\"\n    },\n    \"data-align\": {\n      \"description\": \"Indicates how the popup is aligned relative to specified side.\",\n      \"type\": \"'start' | 'center' | 'end'\"\n    },\n    \"data-side\": {\n      \"description\": \"Indicates which side the popup is positioned relative to the trigger.\",\n      \"type\": \"'none' | 'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'\"\n    },\n    \"data-starting-style\": {\n      \"description\": \"Present when the select is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the select is animating out.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/select-portal.json",
    "content": "{\n  \"name\": \"SelectPortal\",\n  \"description\": \"A portal element that moves the popup to a different part of the DOM.\\nBy default, the portal element is appended to `<body>`.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"container\": {\n      \"type\": \"HTMLElement | ShadowRoot | RefObject<HTMLElement | ShadowRoot | null> | null\",\n      \"description\": \"A parent element to render the portal element into.\",\n      \"detailedType\": \"| HTMLElement\\n| ShadowRoot\\n| React.RefObject<HTMLElement | ShadowRoot | null>\\n| null\\n| undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Select.Portal.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Select.Portal.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Select.Portal.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Select.Portal.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Select.Portal.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Select.Portal.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/select-positioner.json",
    "content": "{\n  \"name\": \"SelectPositioner\",\n  \"description\": \"Positions the select popup.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"alignItemWithTrigger\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether the positioner overlaps the trigger so the selected item's text is aligned with the trigger's value text. This only applies to mouse input and is automatically disabled if there is not enough space.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"disableAnchorTracking\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to disable the popup from tracking any layout shift of its positioning anchor.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"align\": {\n      \"type\": \"Align\",\n      \"default\": \"'center'\",\n      \"description\": \"How to align the popup relative to the specified side.\",\n      \"detailedType\": \"'start' | 'center' | 'end' | undefined\"\n    },\n    \"alignOffset\": {\n      \"type\": \"number | OffsetFunction\",\n      \"default\": \"0\",\n      \"description\": \"Additional offset along the alignment axis in pixels.\\nAlso accepts a function that returns the offset to read the dimensions of the anchor\\nand positioner elements, along with its side and alignment.\\n\\nThe function takes a `data` object parameter with the following properties:\\n- `data.anchor`: the dimensions of the anchor element with properties `width` and `height`.\\n- `data.positioner`: the dimensions of the positioner element with properties `width` and `height`.\\n- `data.side`: which side of the anchor element the positioner is aligned against.\\n- `data.align`: how the positioner is aligned relative to the specified side.\",\n      \"example\": \"```jsx\\n<Positioner\\n  alignOffset={({ side, align, anchor, positioner }) => {\\n    return side === 'top' || side === 'bottom'\\n      ? anchor.width\\n      : anchor.height;\\n  }}\\n/>\\n```\",\n      \"detailedType\": \"| number\\n| ((data: {\\n    side: Side\\n    align: Align\\n    anchor: { width: number; height: number }\\n    positioner: { width: number; height: number }\\n  }) => number)\\n| undefined\"\n    },\n    \"side\": {\n      \"type\": \"Side\",\n      \"default\": \"'bottom'\",\n      \"description\": \"Which side of the anchor element to align the popup against.\\nMay automatically change to avoid collisions.\",\n      \"detailedType\": \"| 'top'\\n| 'bottom'\\n| 'left'\\n| 'right'\\n| 'inline-end'\\n| 'inline-start'\\n| undefined\"\n    },\n    \"sideOffset\": {\n      \"type\": \"number | OffsetFunction\",\n      \"default\": \"0\",\n      \"description\": \"Distance between the anchor and the popup in pixels.\\nAlso accepts a function that returns the distance to read the dimensions of the anchor\\nand positioner elements, along with its side and alignment.\\n\\nThe function takes a `data` object parameter with the following properties:\\n- `data.anchor`: the dimensions of the anchor element with properties `width` and `height`.\\n- `data.positioner`: the dimensions of the positioner element with properties `width` and `height`.\\n- `data.side`: which side of the anchor element the positioner is aligned against.\\n- `data.align`: how the positioner is aligned relative to the specified side.\",\n      \"example\": \"```jsx\\n<Positioner\\n  sideOffset={({ side, align, anchor, positioner }) => {\\n    return side === 'top' || side === 'bottom'\\n      ? anchor.height\\n      : anchor.width;\\n  }}\\n/>\\n```\",\n      \"detailedType\": \"| number\\n| ((data: {\\n    side: Side\\n    align: Align\\n    anchor: { width: number; height: number }\\n    positioner: { width: number; height: number }\\n  }) => number)\\n| undefined\"\n    },\n    \"arrowPadding\": {\n      \"type\": \"number\",\n      \"default\": \"5\",\n      \"description\": \"Minimum distance to maintain between the arrow and the edges of the popup.\\n\\nUse it to prevent the arrow element from hanging out of the rounded corners of a popup.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"anchor\": {\n      \"type\": \"Element | VirtualElement | RefObject<Element | null> | (() => Element | VirtualElement | null) | null\",\n      \"description\": \"An element to position the popup against.\\nBy default, the popup will be positioned against the trigger.\",\n      \"detailedType\": \"| Element\\n| VirtualElement\\n| React.RefObject<Element | null>\\n| (() => Element | VirtualElement | null)\\n| null\\n| undefined\"\n    },\n    \"collisionAvoidance\": {\n      \"type\": \"CollisionAvoidance\",\n      \"description\": \"Determines how to handle collisions when positioning the popup.\\n\\n`side` controls overflow on the preferred placement axis (`top`/`bottom` or `left`/`right`):\\n- `'flip'`: keep the requested side when it fits; otherwise try the opposite side\\n  (`top` and `bottom`, or `left` and `right`).\\n- `'shift'`: never change side; keep the requested side and move the popup within\\n  the clipping boundary so it stays visible.\\n- `'none'`: do not correct side-axis overflow.\\n\\n`align` controls overflow on the alignment axis (`start`/`center`/`end`):\\n- `'flip'`: keep side, but swap `start` and `end` when the requested alignment overflows.\\n- `'shift'`: keep side and requested alignment, then nudge the popup along the\\n  alignment axis to fit.\\n- `'none'`: do not correct alignment-axis overflow.\\n\\n`fallbackAxisSide` controls fallback behavior on the perpendicular axis when the\\npreferred axis cannot fit:\\n- `'start'`: allow perpendicular fallback and try the logical start side first\\n  (`top` before `bottom`, or `left` before `right` in LTR).\\n- `'end'`: allow perpendicular fallback and try the logical end side first\\n  (`bottom` before `top`, or `right` before `left` in LTR).\\n- `'none'`: do not fallback to the perpendicular axis.\\n\\nWhen `side` is `'shift'`, explicitly setting `align` only supports `'shift'` or `'none'`.\\nIf `align` is omitted, it defaults to `'flip'`.\",\n      \"example\": \"```jsx\\n<Positioner\\n  collisionAvoidance={{\\n    side: 'shift',\\n    align: 'shift',\\n    fallbackAxisSide: 'none',\\n  }}\\n/>\\n```\",\n      \"detailedType\": \"| {\\n    side?: 'flip' | 'none'\\n    align?: 'flip' | 'shift' | 'none'\\n    fallbackAxisSide?: 'start' | 'end' | 'none'\\n  }\\n| {\\n    side?: 'shift' | 'none'\\n    align?: 'shift' | 'none'\\n    fallbackAxisSide?: 'start' | 'end' | 'none'\\n  }\\n| undefined\"\n    },\n    \"collisionBoundary\": {\n      \"type\": \"Boundary\",\n      \"default\": \"'clipping-ancestors'\",\n      \"description\": \"An element or a rectangle that delimits the area that the popup is confined to.\",\n      \"detailedType\": \"| 'clipping-ancestors'\\n| Element\\n| Element[]\\n| Rect\\n| undefined\"\n    },\n    \"collisionPadding\": {\n      \"type\": \"Padding\",\n      \"default\": \"5\",\n      \"description\": \"Additional space to maintain from the edge of the collision boundary.\",\n      \"detailedType\": \"| {\\n    top?: number\\n    right?: number\\n    bottom?: number\\n    left?: number\\n  }\\n| number\\n| undefined\"\n    },\n    \"sticky\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to maintain the popup in the viewport after\\nthe anchor element was scrolled out of view.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"positionMethod\": {\n      \"type\": \"'absolute' | 'fixed'\",\n      \"default\": \"'absolute'\",\n      \"description\": \"Determines which CSS `position` property to use.\",\n      \"detailedType\": \"'absolute' | 'fixed' | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Select.Positioner.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Select.Positioner.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Select.Positioner.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Select.Positioner.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Select.Positioner.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Select.Positioner.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the select popup is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the select popup is closed.\"\n    },\n    \"data-anchor-hidden\": {\n      \"description\": \"Present when the anchor is hidden.\"\n    },\n    \"data-align\": {\n      \"description\": \"Indicates how the popup is aligned relative to specified side.\",\n      \"type\": \"'start' | 'center' | 'end'\"\n    },\n    \"data-side\": {\n      \"description\": \"Indicates which side the popup is positioned relative to the trigger.\",\n      \"type\": \"'none' | 'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'\"\n    }\n  },\n  \"cssVariables\": {\n    \"--anchor-height\": {\n      \"description\": \"The anchor's height.\",\n      \"type\": \"number\"\n    },\n    \"--anchor-width\": {\n      \"description\": \"The anchor's width.\",\n      \"type\": \"number\"\n    },\n    \"--available-height\": {\n      \"description\": \"The available height between the trigger and the edge of the viewport.\",\n      \"type\": \"number\"\n    },\n    \"--available-width\": {\n      \"description\": \"The available width between the trigger and the edge of the viewport.\",\n      \"type\": \"number\"\n    },\n    \"--transform-origin\": {\n      \"description\": \"The coordinates that this element is anchored to. Used for animations and transitions.\",\n      \"type\": \"string\"\n    }\n  }\n}\n"
  },
  {
    "path": "docs/reference/generated/select-root.json",
    "content": "{\n  \"name\": \"SelectRoot\",\n  \"description\": \"Groups all parts of the select.\\nDoesn’t render its own HTML element.\",\n  \"props\": {\n    \"name\": {\n      \"type\": \"string\",\n      \"description\": \"Identifies the field when a form is submitted.\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"defaultValue\": {\n      \"type\": \"Value[] | Value | null\",\n      \"description\": \"The uncontrolled value of the select when it’s initially rendered.\\n\\nTo render a controlled select, use the `value` prop instead.\",\n      \"detailedType\": \"Value[] | Value | null | undefined\"\n    },\n    \"value\": {\n      \"type\": \"Value[] | Value | null\",\n      \"description\": \"The value of the select. Use when controlled.\",\n      \"detailedType\": \"Value[] | Value | null | undefined\"\n    },\n    \"onValueChange\": {\n      \"type\": \"((value: Value[] | Value | any | null, eventDetails: Select.Root.ChangeEventDetails) => void)\",\n      \"description\": \"Event handler called when the value of the select changes.\",\n      \"detailedType\": \"| ((\\n    value: Value[] | Value | any | null,\\n    eventDetails: Select.Root.ChangeEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"defaultOpen\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the select popup is initially open.\\n\\nTo render a controlled select popup, use the `open` prop instead.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"open\": {\n      \"type\": \"boolean\",\n      \"description\": \"Whether the select popup is currently open.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"onOpenChange\": {\n      \"type\": \"((open: boolean, eventDetails: Select.Root.ChangeEventDetails) => void)\",\n      \"description\": \"Event handler called when the select popup is opened or closed.\",\n      \"detailedType\": \"| ((\\n    open: boolean,\\n    eventDetails: Select.Root.ChangeEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"highlightItemOnHover\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether moving the pointer over items should highlight them.\\nDisabling this prop allows CSS `:hover` to be differentiated from the `:focus` (`data-highlighted`) state.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"actionsRef\": {\n      \"type\": \"RefObject<Select.Root.Actions | null>\",\n      \"description\": \"A ref to imperative actions.\\n- `unmount`: When specified, the select will not be unmounted when closed.\\nInstead, the `unmount` function must be called to unmount the select manually.\\nUseful when the select's animation is controlled by an external library.\",\n      \"detailedType\": \"| React.RefObject<Select.Root.Actions | null>\\n| undefined\"\n    },\n    \"autoComplete\": {\n      \"type\": \"string\",\n      \"description\": \"Provides a hint to the browser for autofill.\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"isItemEqualToValue\": {\n      \"type\": \"((itemValue: Value, value: Value) => boolean)\",\n      \"description\": \"Custom comparison logic used to determine if a select item value matches the current selected value. Useful when item values are objects without matching referentially.\\nDefaults to `Object.is` comparison.\",\n      \"detailedType\": \"| ((itemValue: Value, value: Value) => boolean)\\n| undefined\"\n    },\n    \"itemToStringLabel\": {\n      \"type\": \"((itemValue: Value) => string)\",\n      \"description\": \"When the item values are objects (`<Select.Item value={object}>`), this function converts the object value to a string representation for display in the trigger.\\nIf the shape of the object is `{ value, label }`, the label will be used automatically without needing to specify this prop.\",\n      \"detailedType\": \"((itemValue: Value) => string) | undefined\"\n    },\n    \"itemToStringValue\": {\n      \"type\": \"((itemValue: Value) => string)\",\n      \"description\": \"When the item values are objects (`<Select.Item value={object}>`), this function converts the object value to a string representation for form submission.\\nIf the shape of the object is `{ value, label }`, the value will be used automatically without needing to specify this prop.\",\n      \"detailedType\": \"((itemValue: Value) => string) | undefined\"\n    },\n    \"items\": {\n      \"type\": \"Record<string, ReactNode> | Group[] | ({ label: ReactNode, value: any })[]\",\n      \"description\": \"Data structure of the items rendered in the select popup.\\nWhen specified, `<Select.Value>` renders the label of the selected item instead of the raw value.\",\n      \"example\": \"```tsx\\nconst items = {\\n  sans: 'Sans-serif',\\n  serif: 'Serif',\\n  mono: 'Monospace',\\n  cursive: 'Cursive',\\n};\\n<Select.Root items={items} />\\n```\",\n      \"detailedType\": \"| Record<string, ReactNode>\\n| Group[]\\n| { label: ReactNode; value: any }[]\\n| undefined\"\n    },\n    \"modal\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Determines if the select enters a modal state when open.\\n- `true`: user interaction is limited to the select: document page scroll is locked and pointer interactions on outside elements are disabled.\\n- `false`: user interaction with the rest of the document is allowed.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"multiple\": {\n      \"type\": \"boolean | undefined\",\n      \"default\": \"false\",\n      \"description\": \"Whether multiple items can be selected.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"onOpenChangeComplete\": {\n      \"type\": \"((open: boolean) => void)\",\n      \"description\": \"Event handler called after any animations complete when the select popup is opened or closed.\",\n      \"detailedType\": \"((open: boolean) => void) | undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component should ignore user interaction.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"readOnly\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the user should be unable to choose a different option from the select popup.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"required\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the user must choose a value before submitting a form.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"inputRef\": {\n      \"type\": \"Ref<HTMLInputElement>\",\n      \"description\": \"A ref to access the hidden input element.\",\n      \"detailedType\": \"React.Ref<HTMLInputElement> | undefined\"\n    },\n    \"id\": {\n      \"type\": \"string\",\n      \"description\": \"The id of the Select.\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"children\": {\n      \"type\": \"ReactNode\",\n      \"detailedType\": \"React.ReactNode\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/select-scroll-down-arrow.json",
    "content": "{\n  \"name\": \"SelectScrollDownArrow\",\n  \"description\": \"An element that scrolls the select popup down when hovered. Does not render when using touch input.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Select.ScrollDownArrow.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((\\n    state: Select.ScrollDownArrow.State,\\n  ) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Select.ScrollDownArrow.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Select.ScrollDownArrow.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"keepMounted\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to keep the HTML element in the DOM while the select popup is not scrollable.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Select.ScrollDownArrow.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Select.ScrollDownArrow.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-direction\": {\n      \"description\": \"Indicates the direction of the scroll arrow.\",\n      \"type\": \"'down'\"\n    },\n    \"data-side\": {\n      \"description\": \"Indicates which side the popup is positioned relative to the trigger.\",\n      \"type\": \"'none' | 'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'\"\n    },\n    \"data-visible\": {\n      \"description\": \"Present when the scroll arrow is visible.\"\n    },\n    \"data-starting-style\": {\n      \"description\": \"Present when the scroll arrow is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the scroll arrow is animating out.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/select-scroll-up-arrow.json",
    "content": "{\n  \"name\": \"SelectScrollUpArrow\",\n  \"description\": \"An element that scrolls the select popup up when hovered. Does not render when using touch input.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Select.ScrollUpArrow.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((\\n    state: Select.ScrollUpArrow.State,\\n  ) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Select.ScrollUpArrow.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Select.ScrollUpArrow.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"keepMounted\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to keep the HTML element in the DOM while the select popup is not scrollable.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Select.ScrollUpArrow.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Select.ScrollUpArrow.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-direction\": {\n      \"description\": \"Indicates the direction of the scroll arrow.\",\n      \"type\": \"'up'\"\n    },\n    \"data-side\": {\n      \"description\": \"Indicates which side the popup is positioned relative to the trigger.\",\n      \"type\": \"'none' | 'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'\"\n    },\n    \"data-visible\": {\n      \"description\": \"Present when the scroll arrow is visible.\"\n    },\n    \"data-starting-style\": {\n      \"description\": \"Present when the scroll arrow is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the scroll arrow is animating out.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/select-trigger.json",
    "content": "{\n  \"name\": \"SelectTrigger\",\n  \"description\": \"A button that opens the select popup.\\nRenders a `<button>` element.\",\n  \"props\": {\n    \"nativeButton\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether the component renders a native `<button>` element when replacing it\\nvia the `render` prop.\\nSet to `false` if the rendered element is not a button (e.g. `<div>`).\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"description\": \"Whether the component should ignore user interaction.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"children\": {\n      \"type\": \"ReactNode\",\n      \"detailedType\": \"React.ReactNode\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Select.Trigger.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Select.Trigger.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Select.Trigger.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Select.Trigger.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Select.Trigger.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Select.Trigger.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-popup-open\": {\n      \"description\": \"Present when the corresponding select is open.\"\n    },\n    \"data-pressed\": {\n      \"description\": \"Present when the trigger is pressed.\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the select is disabled.\"\n    },\n    \"data-readonly\": {\n      \"description\": \"Present when the select is readonly.\"\n    },\n    \"data-required\": {\n      \"description\": \"Present when the select is required.\"\n    },\n    \"data-valid\": {\n      \"description\": \"Present when the select is in valid state (when wrapped in Field.Root).\"\n    },\n    \"data-invalid\": {\n      \"description\": \"Present when the select is in invalid state (when wrapped in Field.Root).\"\n    },\n    \"data-dirty\": {\n      \"description\": \"Present when the select's value has changed (when wrapped in Field.Root).\"\n    },\n    \"data-touched\": {\n      \"description\": \"Present when the select has been touched (when wrapped in Field.Root).\"\n    },\n    \"data-filled\": {\n      \"description\": \"Present when the select has a value (when wrapped in Field.Root).\"\n    },\n    \"data-focused\": {\n      \"description\": \"Present when the select trigger is focused (when wrapped in Field.Root).\"\n    },\n    \"data-placeholder\": {\n      \"description\": \"Present when the select doesn't have a value.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/select-value.json",
    "content": "{\n  \"name\": \"SelectValue\",\n  \"description\": \"A text label of the currently selected item.\\nRenders a `<span>` element.\",\n  \"props\": {\n    \"placeholder\": {\n      \"type\": \"ReactNode\",\n      \"description\": \"The placeholder value to display when no value is selected.\\nThis is overridden by `children` if specified, or by a null item's label in `items`.\",\n      \"detailedType\": \"React.ReactNode\"\n    },\n    \"children\": {\n      \"type\": \"ReactNode | ((value: any) => ReactNode)\",\n      \"description\": \"Accepts a function that returns a `ReactNode` to format the selected value.\",\n      \"example\": \"```tsx\\n<Select.Value>\\n  {(value: string | null) => value ? labels[value] : 'No value'}\\n</Select.Value>\\n```\",\n      \"detailedType\": \"React.ReactNode | ((value: any) => ReactNode)\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Select.Value.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Select.Value.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Select.Value.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Select.Value.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Select.Value.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Select.Value.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-placeholder\": {\n      \"description\": \"Present when the select doesn't have a value.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/separator.json",
    "content": "{\n  \"name\": \"Separator\",\n  \"description\": \"A separator element accessible to screen readers.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"orientation\": {\n      \"type\": \"Orientation\",\n      \"default\": \"'horizontal'\",\n      \"description\": \"The orientation of the separator.\",\n      \"detailedType\": \"'horizontal' | 'vertical' | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Separator.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Separator.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Separator.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Separator.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Separator.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Separator.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/slider-control.json",
    "content": "{\n  \"name\": \"SliderControl\",\n  \"description\": \"The clickable, interactive part of the slider.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Slider.Control.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Slider.Control.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Slider.Control.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Slider.Control.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Slider.Control.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Slider.Control.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-dragging\": {\n      \"description\": \"Present while the user is dragging.\"\n    },\n    \"data-orientation\": {\n      \"description\": \"Indicates the orientation of the slider.\",\n      \"type\": \"'horizontal' | 'vertical'\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the slider is disabled.\"\n    },\n    \"data-valid\": {\n      \"description\": \"Present when the slider is in valid state (when wrapped in Field.Root).\"\n    },\n    \"data-invalid\": {\n      \"description\": \"Present when the slider is in invalid state (when wrapped in Field.Root).\"\n    },\n    \"data-dirty\": {\n      \"description\": \"Present when the slider's value has changed (when wrapped in Field.Root).\"\n    },\n    \"data-touched\": {\n      \"description\": \"Present when the slider has been touched (when wrapped in Field.Root).\"\n    },\n    \"data-focused\": {\n      \"description\": \"Present when the slider is focused (when wrapped in Field.Root).\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/slider-indicator.json",
    "content": "{\n  \"name\": \"SliderIndicator\",\n  \"description\": \"Visualizes the current value of the slider.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Slider.Indicator.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Slider.Indicator.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Slider.Indicator.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Slider.Indicator.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Slider.Indicator.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Slider.Indicator.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-dragging\": {\n      \"description\": \"Present while the user is dragging.\"\n    },\n    \"data-orientation\": {\n      \"description\": \"Indicates the orientation of the slider.\",\n      \"type\": \"'horizontal' | 'vertical'\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the slider is disabled.\"\n    },\n    \"data-valid\": {\n      \"description\": \"Present when the slider is in valid state (when wrapped in Field.Root).\"\n    },\n    \"data-invalid\": {\n      \"description\": \"Present when the slider is in invalid state (when wrapped in Field.Root).\"\n    },\n    \"data-dirty\": {\n      \"description\": \"Present when the slider's value has changed (when wrapped in Field.Root).\"\n    },\n    \"data-touched\": {\n      \"description\": \"Present when the slider has been touched (when wrapped in Field.Root).\"\n    },\n    \"data-focused\": {\n      \"description\": \"Present when the slider is focused (when wrapped in Field.Root).\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/slider-label.json",
    "content": "{\n  \"name\": \"SliderLabel\",\n  \"description\": \"An accessible label that is automatically associated with the slider thumbs.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Slider.Root.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Slider.Root.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Slider.Root.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Slider.Root.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Slider.Root.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Slider.Root.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/slider-root.json",
    "content": "{\n  \"name\": \"SliderRoot\",\n  \"description\": \"Groups all parts of the slider.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"name\": {\n      \"type\": \"string\",\n      \"description\": \"Identifies the field when a form is submitted.\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"defaultValue\": {\n      \"type\": \"number | number[]\",\n      \"description\": \"The uncontrolled value of the slider when it’s initially rendered.\\n\\nTo render a controlled slider, use the `value` prop instead.\",\n      \"detailedType\": \"number | number[] | undefined\"\n    },\n    \"value\": {\n      \"type\": \"number | number[]\",\n      \"description\": \"The value of the slider.\\nFor ranged sliders, provide an array with two values.\",\n      \"detailedType\": \"number | number[] | undefined\"\n    },\n    \"onValueChange\": {\n      \"type\": \"((value: number | number[], eventDetails: Slider.Root.ChangeEventDetails) => void)\",\n      \"description\": \"Callback function that is fired when the slider's value changed.\\nYou can pull out the new value by accessing `event.target.value` (any).\\n\\nThe `eventDetails.reason` indicates what triggered the change:\\n\\n- `'input-change'` when the hidden range input emits a change event (for example, via form integration)\\n- `'track-press'` when the control track is pressed\\n- `'drag'` while dragging a thumb\\n- `'keyboard'` for keyboard input\\n- `'none'` when the change is triggered without a specific interaction\",\n      \"detailedType\": \"| ((\\n    value: number | number[],\\n    eventDetails: Slider.Root.ChangeEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"onValueCommitted\": {\n      \"type\": \"((value: number | number[], eventDetails: Slider.Root.CommitEventDetails) => void)\",\n      \"description\": \"Callback function that is fired when the `pointerup` is triggered.\\n**Warning**: This is a generic event not a change event.\\n\\nThe `eventDetails.reason` indicates what triggered the commit:\\n\\n- `'drag'` while dragging a thumb\\n- `'track-press'` when the control track is pressed\\n- `'keyboard'` for keyboard input\\n- `'input-change'` when the hidden range input emits a change event (for example, via form integration)\\n- `'none'` when the commit occurs without a specific interaction\",\n      \"detailedType\": \"| ((\\n    value: number | number[],\\n    eventDetails: Slider.Root.CommitEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"locale\": {\n      \"type\": \"Intl.LocalesArgument\",\n      \"description\": \"The locale used by `Intl.NumberFormat` when formatting the value.\\nDefaults to the user's runtime locale.\",\n      \"detailedType\": \"Intl.LocalesArgument | undefined\"\n    },\n    \"thumbAlignment\": {\n      \"type\": \"'center' | 'edge' | 'edge-client-only'\",\n      \"default\": \"'center'\",\n      \"description\": \"How the thumb(s) are aligned relative to `Slider.Control` when the value is at `min` or `max`:\\n- `center`: The center of the thumb is aligned with the control edge\\n- `edge`: The thumb is inset within the control such that its edge is aligned with the control edge\\n- `edge-client-only`: Same as `edge` but renders after React hydration on the client, reducing bundle size in return\",\n      \"detailedType\": \"'center' | 'edge' | 'edge-client-only' | undefined\"\n    },\n    \"thumbCollisionBehavior\": {\n      \"type\": \"'push' | 'swap' | 'none'\",\n      \"default\": \"'push'\",\n      \"description\": \"Controls how thumbs behave when they collide during pointer interactions.\\n\\n- `'push'` (default): Thumbs push each other without restoring their previous positions when dragged back.\\n- `'swap'`: Thumbs swap places when dragged past each other.\\n- `'none'`: Thumbs cannot move past each other; excess movement is ignored.\",\n      \"detailedType\": \"'push' | 'swap' | 'none' | undefined\"\n    },\n    \"step\": {\n      \"type\": \"number\",\n      \"default\": \"1\",\n      \"description\": \"The granularity with which the slider can step through values. (A \\\"discrete\\\" slider.)\\nThe `min` prop serves as the origin for the valid values.\\nWe recommend (max - min) to be evenly divisible by the step.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"largeStep\": {\n      \"type\": \"number\",\n      \"default\": \"10\",\n      \"description\": \"The granularity with which the slider can step through values when using Page Up/Page Down or Shift + Arrow Up/Arrow Down.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"minStepsBetweenValues\": {\n      \"type\": \"number\",\n      \"default\": \"0\",\n      \"description\": \"The minimum steps between values in a range slider.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"min\": {\n      \"type\": \"number\",\n      \"default\": \"0\",\n      \"description\": \"The minimum allowed value of the slider.\\nShould not be equal to max.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"max\": {\n      \"type\": \"number\",\n      \"default\": \"100\",\n      \"description\": \"The maximum allowed value of the slider.\\nShould not be equal to min.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"format\": {\n      \"type\": \"Intl.NumberFormatOptions\",\n      \"description\": \"Options to format the input value.\",\n      \"detailedType\": \"Intl.NumberFormatOptions | undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the slider should ignore user interaction.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"orientation\": {\n      \"type\": \"Orientation\",\n      \"default\": \"'horizontal'\",\n      \"description\": \"The component orientation.\",\n      \"detailedType\": \"'horizontal' | 'vertical' | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Slider.Root.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Slider.Root.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Slider.Root.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Slider.Root.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Slider.Root.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Slider.Root.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-dragging\": {\n      \"description\": \"Present while the user is dragging.\"\n    },\n    \"data-orientation\": {\n      \"description\": \"Indicates the orientation of the slider.\",\n      \"type\": \"'horizontal' | 'vertical'\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the slider is disabled.\"\n    },\n    \"data-valid\": {\n      \"description\": \"Present when the slider is in valid state (when wrapped in Field.Root).\"\n    },\n    \"data-invalid\": {\n      \"description\": \"Present when the slider is in invalid state (when wrapped in Field.Root).\"\n    },\n    \"data-dirty\": {\n      \"description\": \"Present when the slider's value has changed (when wrapped in Field.Root).\"\n    },\n    \"data-touched\": {\n      \"description\": \"Present when the slider has been touched (when wrapped in Field.Root).\"\n    },\n    \"data-focused\": {\n      \"description\": \"Present when the slider is focused (when wrapped in Field.Root).\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/slider-thumb.json",
    "content": "{\n  \"name\": \"SliderThumb\",\n  \"description\": \"The draggable part of the slider at the tip of the indicator.\\nRenders a `<div>` element and a nested `<input type=\\\"range\\\">`.\",\n  \"props\": {\n    \"getAriaLabel\": {\n      \"type\": \"((index: number) => string) | null\",\n      \"description\": \"A function which returns a string value for the [`aria-label`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-label) attribute of the `input`.\",\n      \"detailedType\": \"((index: number) => string) | null | undefined\"\n    },\n    \"getAriaValueText\": {\n      \"type\": \"((formattedValue: string, value: number, index: number) => string) | null\",\n      \"description\": \"A function which returns a string value for the [`aria-valuetext`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-valuetext) attribute of the `input`.\\nThis is important for screen reader users.\",\n      \"detailedType\": \"| ((\\n    formattedValue: string,\\n    value: number,\\n    index: number,\\n  ) => string)\\n| null\\n| undefined\"\n    },\n    \"index\": {\n      \"type\": \"number\",\n      \"description\": \"The index of the thumb which corresponds to the index of its value in the\\n`value` or `defaultValue` array.\\nThis prop is required to support server-side rendering for range sliders\\nwith multiple thumbs.\",\n      \"example\": \"```tsx\\n<Slider.Root value={[10, 20]}>\\n  <Slider.Thumb index={0} />\\n  <Slider.Thumb index={1} />\\n</Slider.Root>\\n```\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"onBlur\": {\n      \"type\": \"FocusEventHandler<HTMLInputElement>\",\n      \"description\": \"A blur handler forwarded to the `input`.\",\n      \"detailedType\": \"| React.FocusEventHandler<HTMLInputElement>\\n| undefined\"\n    },\n    \"onFocus\": {\n      \"type\": \"FocusEventHandler<HTMLInputElement>\",\n      \"description\": \"A focus handler forwarded to the `input`.\",\n      \"detailedType\": \"| React.FocusEventHandler<HTMLInputElement>\\n| undefined\"\n    },\n    \"tabIndex\": {\n      \"type\": \"number\",\n      \"description\": \"Optional tab index attribute forwarded to the `input`.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the thumb should ignore user interaction.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"inputRef\": {\n      \"type\": \"Ref<HTMLInputElement>\",\n      \"description\": \"A ref to access the nested input element.\",\n      \"detailedType\": \"React.Ref<HTMLInputElement> | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Slider.Thumb.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Slider.Thumb.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Slider.Thumb.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Slider.Thumb.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Slider.Thumb.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Slider.Thumb.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-dragging\": {\n      \"description\": \"Present while the user is dragging.\"\n    },\n    \"data-orientation\": {\n      \"description\": \"Indicates the orientation of the slider.\",\n      \"type\": \"'horizontal' | 'vertical'\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the slider is disabled.\"\n    },\n    \"data-valid\": {\n      \"description\": \"Present when the slider is in valid state (when wrapped in Field.Root).\"\n    },\n    \"data-invalid\": {\n      \"description\": \"Present when the slider is in invalid state (when wrapped in Field.Root).\"\n    },\n    \"data-dirty\": {\n      \"description\": \"Present when the slider's value has changed (when wrapped in Field.Root).\"\n    },\n    \"data-touched\": {\n      \"description\": \"Present when the slider has been touched (when wrapped in Field.Root).\"\n    },\n    \"data-focused\": {\n      \"description\": \"Present when the slider is focused (when wrapped in Field.Root).\"\n    },\n    \"data-index\": {\n      \"description\": \"Indicates the index of the thumb in range sliders.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/slider-track.json",
    "content": "{\n  \"name\": \"SliderTrack\",\n  \"description\": \"Contains the slider indicator and represents the entire range of the slider.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Slider.Track.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Slider.Track.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Slider.Track.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Slider.Track.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Slider.Track.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Slider.Track.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-dragging\": {\n      \"description\": \"Present while the user is dragging.\"\n    },\n    \"data-orientation\": {\n      \"description\": \"Indicates the orientation of the slider.\",\n      \"type\": \"'horizontal' | 'vertical'\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the slider is disabled.\"\n    },\n    \"data-valid\": {\n      \"description\": \"Present when the slider is in valid state (when wrapped in Field.Root).\"\n    },\n    \"data-invalid\": {\n      \"description\": \"Present when the slider is in invalid state (when wrapped in Field.Root).\"\n    },\n    \"data-dirty\": {\n      \"description\": \"Present when the slider's value has changed (when wrapped in Field.Root).\"\n    },\n    \"data-touched\": {\n      \"description\": \"Present when the slider has been touched (when wrapped in Field.Root).\"\n    },\n    \"data-focused\": {\n      \"description\": \"Present when the slider is focused (when wrapped in Field.Root).\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/slider-value.json",
    "content": "{\n  \"name\": \"SliderValue\",\n  \"description\": \"Displays the current value of the slider as text.\\nRenders an `<output>` element.\",\n  \"props\": {\n    \"children\": {\n      \"type\": \"((formattedValues: string[], values: number[]) => ReactNode) | null\",\n      \"detailedType\": \"| ((\\n    formattedValues: string[],\\n    values: number[],\\n  ) => ReactNode)\\n| null\\n| undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Slider.Value.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Slider.Value.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Slider.Value.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Slider.Value.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Slider.Value.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Slider.Value.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-dragging\": {\n      \"description\": \"Present while the user is dragging.\"\n    },\n    \"data-orientation\": {\n      \"description\": \"Indicates the orientation of the slider.\",\n      \"type\": \"'horizontal' | 'vertical'\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the slider is disabled.\"\n    },\n    \"data-valid\": {\n      \"description\": \"Present when the slider is in valid state (when wrapped in Field.Root).\"\n    },\n    \"data-invalid\": {\n      \"description\": \"Present when the slider is in invalid state (when wrapped in Field.Root).\"\n    },\n    \"data-dirty\": {\n      \"description\": \"Present when the slider's value has changed (when wrapped in Field.Root).\"\n    },\n    \"data-touched\": {\n      \"description\": \"Present when the slider has been touched (when wrapped in Field.Root).\"\n    },\n    \"data-focused\": {\n      \"description\": \"Present when the slider is focused (when wrapped in Field.Root).\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/switch-root.json",
    "content": "{\n  \"name\": \"SwitchRoot\",\n  \"description\": \"Represents the switch itself.\\nRenders a `<span>` element and a hidden `<input>` beside.\",\n  \"props\": {\n    \"name\": {\n      \"type\": \"string\",\n      \"description\": \"Identifies the field when a form is submitted.\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"defaultChecked\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the switch is initially active.\\n\\nTo render a controlled switch, use the `checked` prop instead.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"checked\": {\n      \"type\": \"boolean\",\n      \"description\": \"Whether the switch is currently active.\\n\\nTo render an uncontrolled switch, use the `defaultChecked` prop instead.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"onCheckedChange\": {\n      \"type\": \"((checked: boolean, eventDetails: Switch.Root.ChangeEventDetails) => void)\",\n      \"description\": \"Event handler called when the switch is activated or deactivated.\",\n      \"detailedType\": \"| ((\\n    checked: boolean,\\n    eventDetails: Switch.Root.ChangeEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"value\": {\n      \"type\": \"string\",\n      \"description\": \"The value submitted with the form when the switch is on.\\nBy default, switch submits the \\\"on\\\" value, matching native checkbox behavior.\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"nativeButton\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component renders a native `<button>` element when replacing it\\nvia the `render` prop.\\nSet to `true` if the rendered element is a native button.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"uncheckedValue\": {\n      \"type\": \"string\",\n      \"description\": \"The value submitted with the form when the switch is off.\\nBy default, unchecked switches do not submit any value, matching native checkbox behavior.\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component should ignore user interaction.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"readOnly\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the user should be unable to activate or deactivate the switch.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"required\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the user must activate the switch before submitting a form.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"inputRef\": {\n      \"type\": \"Ref<HTMLInputElement>\",\n      \"description\": \"A ref to access the hidden `<input>` element.\",\n      \"detailedType\": \"React.Ref<HTMLInputElement> | undefined\"\n    },\n    \"id\": {\n      \"type\": \"string\",\n      \"description\": \"The id of the switch element.\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Switch.Root.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Switch.Root.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Switch.Root.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Switch.Root.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Switch.Root.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Switch.Root.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-checked\": {\n      \"description\": \"Present when the switch is checked.\"\n    },\n    \"data-unchecked\": {\n      \"description\": \"Present when the switch is not checked.\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the switch is disabled.\"\n    },\n    \"data-readonly\": {\n      \"description\": \"Present when the switch is readonly.\"\n    },\n    \"data-required\": {\n      \"description\": \"Present when the switch is required.\"\n    },\n    \"data-valid\": {\n      \"description\": \"Present when the switch is in valid state (when wrapped in Field.Root).\"\n    },\n    \"data-invalid\": {\n      \"description\": \"Present when the switch is in invalid state (when wrapped in Field.Root).\"\n    },\n    \"data-dirty\": {\n      \"description\": \"Present when the switch's value has changed (when wrapped in Field.Root).\"\n    },\n    \"data-touched\": {\n      \"description\": \"Present when the switch has been touched (when wrapped in Field.Root).\"\n    },\n    \"data-filled\": {\n      \"description\": \"Present when the switch is active (when wrapped in Field.Root).\"\n    },\n    \"data-focused\": {\n      \"description\": \"Present when the switch is focused (when wrapped in Field.Root).\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/switch-thumb.json",
    "content": "{\n  \"name\": \"SwitchThumb\",\n  \"description\": \"The movable part of the switch that indicates whether the switch is on or off.\\nRenders a `<span>`.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Switch.Thumb.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Switch.Thumb.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Switch.Thumb.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Switch.Thumb.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Switch.Thumb.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Switch.Thumb.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-checked\": {\n      \"description\": \"Present when the switch is checked.\"\n    },\n    \"data-unchecked\": {\n      \"description\": \"Present when the switch is not checked.\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the switch is disabled.\"\n    },\n    \"data-readonly\": {\n      \"description\": \"Present when the switch is readonly.\"\n    },\n    \"data-required\": {\n      \"description\": \"Present when the switch is required.\"\n    },\n    \"data-valid\": {\n      \"description\": \"Present when the switch is in valid state (when wrapped in Field.Root).\"\n    },\n    \"data-invalid\": {\n      \"description\": \"Present when the switch is in invalid state (when wrapped in Field.Root).\"\n    },\n    \"data-dirty\": {\n      \"description\": \"Present when the switch's value has changed (when wrapped in Field.Root).\"\n    },\n    \"data-touched\": {\n      \"description\": \"Present when the switch has been touched (when wrapped in Field.Root).\"\n    },\n    \"data-filled\": {\n      \"description\": \"Present when the switch is active (when wrapped in Field.Root).\"\n    },\n    \"data-focused\": {\n      \"description\": \"Present when the switch is focused (when wrapped in Field.Root).\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/tabs-indicator.json",
    "content": "{\n  \"name\": \"TabsIndicator\",\n  \"description\": \"A visual indicator that can be styled to match the position of the currently active tab.\\nRenders a `<span>` element.\",\n  \"props\": {\n    \"renderBeforeHydration\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to render itself before React hydrates.\\nThis minimizes the time that the indicator isn’t visible after server-side rendering.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Tabs.Indicator.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Tabs.Indicator.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Tabs.Indicator.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Tabs.Indicator.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Tabs.Indicator.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Tabs.Indicator.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-orientation\": {\n      \"description\": \"Indicates the orientation of the tabs.\",\n      \"type\": \"'horizontal' | 'vertical'\"\n    },\n    \"data-activation-direction\": {\n      \"description\": \"Indicates the direction of the activation (based on the previous active tab).\",\n      \"type\": \"'left' | 'right' | 'up' | 'down' | 'none'\"\n    }\n  },\n  \"cssVariables\": {\n    \"--active-tab-bottom\": {\n      \"description\": \"Indicates the distance on the bottom side from the parent's container if the tab is active.\",\n      \"type\": \"number\"\n    },\n    \"--active-tab-height\": {\n      \"description\": \"Indicates the height of the tab if it is active.\",\n      \"type\": \"number\"\n    },\n    \"--active-tab-left\": {\n      \"description\": \"Indicates the distance on the left side from the parent's container if the tab is active.\",\n      \"type\": \"number\"\n    },\n    \"--active-tab-right\": {\n      \"description\": \"Indicates the distance on the right side from the parent's container if the tab is active.\",\n      \"type\": \"number\"\n    },\n    \"--active-tab-top\": {\n      \"description\": \"Indicates the distance on the top side from the parent's container if the tab is active.\",\n      \"type\": \"number\"\n    },\n    \"--active-tab-width\": {\n      \"description\": \"Indicates the width of the tab if it is active.\",\n      \"type\": \"number\"\n    }\n  }\n}\n"
  },
  {
    "path": "docs/reference/generated/tabs-list.json",
    "content": "{\n  \"name\": \"TabsList\",\n  \"description\": \"Groups the individual tab buttons.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"activateOnFocus\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to automatically change the active tab on arrow key focus.\\nOtherwise, tabs will be activated using <kbd>Enter</kbd> or <kbd>Space</kbd> key press.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"loopFocus\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether to loop keyboard focus back to the first item\\nwhen the end of the list is reached while using the arrow keys.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Tabs.List.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Tabs.List.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Tabs.List.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Tabs.List.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Tabs.List.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Tabs.List.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-orientation\": {\n      \"description\": \"Indicates the orientation of the tabs.\",\n      \"type\": \"'horizontal' | 'vertical'\"\n    },\n    \"data-activation-direction\": {\n      \"description\": \"Indicates the direction of the activation (based on the previous active tab).\",\n      \"type\": \"'left' | 'right' | 'up' | 'down' | 'none'\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/tabs-panel.json",
    "content": "{\n  \"name\": \"TabsPanel\",\n  \"description\": \"A panel displayed when the corresponding tab is active.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"value\": {\n      \"type\": \"Tabs.Tab.Value\",\n      \"required\": true,\n      \"description\": \"The value of the TabPanel. It will be shown when the Tab with the corresponding value is active.\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Tabs.Panel.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Tabs.Panel.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Tabs.Panel.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Tabs.Panel.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"keepMounted\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to keep the HTML element in the DOM while the panel is hidden.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Tabs.Panel.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Tabs.Panel.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-orientation\": {\n      \"description\": \"Indicates the orientation of the tabs.\",\n      \"type\": \"'horizontal' | 'vertical'\"\n    },\n    \"data-activation-direction\": {\n      \"description\": \"Indicates the direction of the activation (based on the previous active tab).\",\n      \"type\": \"'left' | 'right' | 'up' | 'down' | 'none'\"\n    },\n    \"data-hidden\": {\n      \"description\": \"Present when the panel is hidden.\"\n    },\n    \"data-index\": {\n      \"description\": \"Indicates the index of the tab panel.\"\n    },\n    \"data-starting-style\": {\n      \"description\": \"Present when the panel is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the panel is animating out.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/tabs-root.json",
    "content": "{\n  \"name\": \"TabsRoot\",\n  \"description\": \"Groups the tabs and the corresponding panels.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"defaultValue\": {\n      \"type\": \"Tabs.Tab.Value\",\n      \"default\": \"0\",\n      \"description\": \"The default value. Use when the component is not controlled.\\nWhen the value is `null`, no Tab will be active.\",\n      \"detailedType\": \"Tabs.Tab.Value | undefined\"\n    },\n    \"value\": {\n      \"type\": \"Tabs.Tab.Value\",\n      \"description\": \"The value of the currently active `Tab`. Use when the component is controlled.\\nWhen the value is `null`, no Tab will be active.\",\n      \"detailedType\": \"Tabs.Tab.Value | undefined\"\n    },\n    \"onValueChange\": {\n      \"type\": \"((value: Tabs.Tab.Value, eventDetails: Tabs.Root.ChangeEventDetails<'none', { activationDirection: Tabs.Tab.ActivationDirection }>) => void)\",\n      \"description\": \"Callback invoked when new value is being set.\",\n      \"detailedType\": \"| ((\\n    value: Tabs.Tab.Value,\\n    eventDetails: Tabs.Root.ChangeEventDetails<\\n      'none',\\n      {\\n        activationDirection: Tabs.Tab.ActivationDirection\\n      }\\n    >,\\n  ) => void)\\n| undefined\"\n    },\n    \"orientation\": {\n      \"type\": \"Tabs.Root.Orientation\",\n      \"default\": \"'horizontal'\",\n      \"description\": \"The component orientation (layout flow direction).\",\n      \"detailedType\": \"'horizontal' | 'vertical' | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Tabs.Root.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Tabs.Root.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Tabs.Root.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Tabs.Root.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Tabs.Root.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Tabs.Root.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-orientation\": {\n      \"description\": \"Indicates the orientation of the tabs.\",\n      \"type\": \"'horizontal' | 'vertical'\"\n    },\n    \"data-activation-direction\": {\n      \"description\": \"Indicates the direction of the activation (based on the previous active tab).\",\n      \"type\": \"'left' | 'right' | 'up' | 'down' | 'none'\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/tabs-tab.json",
    "content": "{\n  \"name\": \"TabsTab\",\n  \"description\": \"An individual interactive tab button that toggles the corresponding panel.\\nRenders a `<button>` element.\",\n  \"props\": {\n    \"value\": {\n      \"type\": \"Tabs.Tab.Value\",\n      \"required\": true,\n      \"description\": \"The value of the Tab.\"\n    },\n    \"nativeButton\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether the component renders a native `<button>` element when replacing it\\nvia the `render` prop.\\nSet to `false` if the rendered element is not a button (e.g. `<div>`).\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"description\": \"Whether the Tab is disabled.\\n\\nIf a first Tab on a `<Tabs.List>` is disabled, it won't initially be selected.\\nInstead, the next enabled Tab will be selected.\\nHowever, it does not work like this during server-side rendering, as it is not known\\nduring pre-rendering which Tabs are disabled.\\nTo work around it, ensure that `defaultValue` or `value` on `<Tabs.Root>` is set to an enabled Tab's value.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Tabs.Tab.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Tabs.Tab.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Tabs.Tab.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Tabs.Tab.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Tabs.Tab.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Tabs.Tab.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-orientation\": {\n      \"description\": \"Indicates the orientation of the tabs.\",\n      \"type\": \"'horizontal' | 'vertical'\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the tab is disabled.\"\n    },\n    \"data-activation-direction\": {\n      \"description\": \"Indicates the direction of the activation (based on the previous active tab).\",\n      \"type\": \"'left' | 'right' | 'up' | 'down' | 'none'\"\n    },\n    \"data-active\": {\n      \"description\": \"Present when the tab is active.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/temporal-adapter-provider.json",
    "content": "{\n  \"name\": \"TemporalAdapterProvider\",\n  \"description\": \"Defines the date library adapter for Base UI temporal components.\",\n  \"props\": {\n    \"adapter\": {\n      \"type\": \"TemporalAdapter\",\n      \"required\": true,\n      \"description\": \"The date library adapter.\",\n      \"detailedType\": \"{}\"\n    },\n    \"children\": {\n      \"type\": \"ReactNode\",\n      \"detailedType\": \"React.ReactNode\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/toast-action.json",
    "content": "{\n  \"name\": \"ToastAction\",\n  \"description\": \"Performs an action when clicked.\\nRenders a `<button>` element.\",\n  \"props\": {\n    \"nativeButton\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether the component renders a native `<button>` element when replacing it\\nvia the `render` prop.\\nSet to `false` if the rendered element is not a button (e.g. `<div>`).\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Toast.Action.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Toast.Action.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Toast.Action.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Toast.Action.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Toast.Action.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Toast.Action.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-type\": {\n      \"description\": \"The type of the toast.\",\n      \"type\": \"string\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/toast-arrow.json",
    "content": "{\n  \"name\": \"ToastArrow\",\n  \"description\": \"Displays an element positioned against the toast anchor.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Toast.Arrow.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Toast.Arrow.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Toast.Arrow.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Toast.Arrow.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Toast.Arrow.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Toast.Arrow.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-uncentered\": {\n      \"description\": \"Present when the toast arrow is uncentered.\"\n    },\n    \"data-align\": {\n      \"description\": \"Indicates how the toast is aligned relative to specified side.\",\n      \"type\": \"'start' | 'center' | 'end'\"\n    },\n    \"data-side\": {\n      \"description\": \"Indicates which side the toast is positioned relative to the anchor.\",\n      \"type\": \"'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/toast-close.json",
    "content": "{\n  \"name\": \"ToastClose\",\n  \"description\": \"Closes the toast when clicked.\\nRenders a `<button>` element.\",\n  \"props\": {\n    \"nativeButton\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether the component renders a native `<button>` element when replacing it\\nvia the `render` prop.\\nSet to `false` if the rendered element is not a button (e.g. `<div>`).\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Toast.Close.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Toast.Close.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Toast.Close.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Toast.Close.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Toast.Close.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Toast.Close.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-type\": {\n      \"description\": \"The type of the toast.\",\n      \"type\": \"string\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/toast-content.json",
    "content": "{\n  \"name\": \"ToastContent\",\n  \"description\": \"A container for the contents of a toast.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Toast.Content.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Toast.Content.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Toast.Content.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Toast.Content.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Toast.Content.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Toast.Content.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-behind\": {\n      \"description\": \"Present when the toast is behind the frontmost toast in the stack.\",\n      \"type\": \"boolean\"\n    },\n    \"data-expanded\": {\n      \"description\": \"Present when the toast viewport is expanded.\",\n      \"type\": \"boolean\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/toast-description.json",
    "content": "{\n  \"name\": \"ToastDescription\",\n  \"description\": \"A description that describes the toast.\\nCan be used as the default message for the toast when no title is provided.\\nRenders a `<p>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Toast.Description.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Toast.Description.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Toast.Description.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Toast.Description.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Toast.Description.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Toast.Description.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-type\": {\n      \"description\": \"The type of the toast.\",\n      \"type\": \"string\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/toast-portal.json",
    "content": "{\n  \"name\": \"ToastPortal\",\n  \"description\": \"A portal element that moves the viewport to a different part of the DOM.\\nBy default, the portal element is appended to `<body>`.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"container\": {\n      \"type\": \"HTMLElement | ShadowRoot | RefObject<HTMLElement | ShadowRoot | null> | null\",\n      \"description\": \"A parent element to render the portal element into.\",\n      \"detailedType\": \"| HTMLElement\\n| ShadowRoot\\n| React.RefObject<HTMLElement | ShadowRoot | null>\\n| null\\n| undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: any) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: any) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: any) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: any) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((props: HTMLProps, state: any) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/toast-positioner.json",
    "content": "{\n  \"name\": \"ToastPositioner\",\n  \"description\": \"Positions the toast against the anchor.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"disableAnchorTracking\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to disable the popup from tracking any layout shift of its positioning anchor.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"toast\": {\n      \"type\": \"ToastObject<any>\",\n      \"required\": true,\n      \"description\": \"The toast object associated with the positioner.\",\n      \"detailedType\": \"id: string\\n  ref?: RefObject<HTMLElement | null>\\n  title?: ReactNode\\n  type?: string\\n  description?: ReactNode\\n  timeout?: number\\n  priority?: 'low' | 'high'\\n  transitionStatus?: 'starting' | 'ending'\\n  limited?: boolean\\n  height?: number\\n  onClose?: () => void\\n  onRemove?: () => void\\n  actionProps?: Omit<\\n    DetailedHTMLProps<\\n      ButtonHTMLAttributes<HTMLButtonElement>,\\n      HTMLButtonElement\\n    >,\\n    'ref'\\n  >\\n  positionerProps?: ToastManagerPositionerProps\\n  data?: {}\\n}\"\n    },\n    \"align\": {\n      \"type\": \"Align\",\n      \"default\": \"'center'\",\n      \"description\": \"How to align the popup relative to the specified side.\",\n      \"detailedType\": \"'start' | 'center' | 'end' | undefined\"\n    },\n    \"alignOffset\": {\n      \"type\": \"number | OffsetFunction\",\n      \"default\": \"0\",\n      \"description\": \"Additional offset along the alignment axis in pixels.\\nAlso accepts a function that returns the offset to read the dimensions of the anchor\\nand positioner elements, along with its side and alignment.\\n\\nThe function takes a `data` object parameter with the following properties:\\n- `data.anchor`: the dimensions of the anchor element with properties `width` and `height`.\\n- `data.positioner`: the dimensions of the positioner element with properties `width` and `height`.\\n- `data.side`: which side of the anchor element the positioner is aligned against.\\n- `data.align`: how the positioner is aligned relative to the specified side.\",\n      \"example\": \"```jsx\\n<Positioner\\n  alignOffset={({ side, align, anchor, positioner }) => {\\n    return side === 'top' || side === 'bottom'\\n      ? anchor.width\\n      : anchor.height;\\n  }}\\n/>\\n```\",\n      \"detailedType\": \"| number\\n| ((data: {\\n    side: Side\\n    align: Align\\n    anchor: { width: number; height: number }\\n    positioner: { width: number; height: number }\\n  }) => number)\\n| undefined\"\n    },\n    \"side\": {\n      \"type\": \"Side\",\n      \"default\": \"'top'\",\n      \"description\": \"Which side of the anchor element to align the toast against.\\nMay automatically change to avoid collisions.\",\n      \"detailedType\": \"| 'top'\\n| 'bottom'\\n| 'left'\\n| 'right'\\n| 'inline-end'\\n| 'inline-start'\\n| undefined\"\n    },\n    \"sideOffset\": {\n      \"type\": \"number | OffsetFunction\",\n      \"default\": \"0\",\n      \"description\": \"Distance between the anchor and the popup in pixels.\\nAlso accepts a function that returns the distance to read the dimensions of the anchor\\nand positioner elements, along with its side and alignment.\\n\\nThe function takes a `data` object parameter with the following properties:\\n- `data.anchor`: the dimensions of the anchor element with properties `width` and `height`.\\n- `data.positioner`: the dimensions of the positioner element with properties `width` and `height`.\\n- `data.side`: which side of the anchor element the positioner is aligned against.\\n- `data.align`: how the positioner is aligned relative to the specified side.\",\n      \"example\": \"```jsx\\n<Positioner\\n  sideOffset={({ side, align, anchor, positioner }) => {\\n    return side === 'top' || side === 'bottom'\\n      ? anchor.height\\n      : anchor.width;\\n  }}\\n/>\\n```\",\n      \"detailedType\": \"| number\\n| ((data: {\\n    side: Side\\n    align: Align\\n    anchor: { width: number; height: number }\\n    positioner: { width: number; height: number }\\n  }) => number)\\n| undefined\"\n    },\n    \"arrowPadding\": {\n      \"type\": \"number\",\n      \"default\": \"5\",\n      \"description\": \"Minimum distance to maintain between the arrow and the edges of the popup.\\n\\nUse it to prevent the arrow element from hanging out of the rounded corners of a popup.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"anchor\": {\n      \"type\": \"Element | null\",\n      \"description\": \"An element to position the toast against.\",\n      \"detailedType\": \"Element | null | undefined\"\n    },\n    \"collisionAvoidance\": {\n      \"type\": \"CollisionAvoidance\",\n      \"description\": \"Determines how to handle collisions when positioning the popup.\\n\\n`side` controls overflow on the preferred placement axis (`top`/`bottom` or `left`/`right`):\\n- `'flip'`: keep the requested side when it fits; otherwise try the opposite side\\n  (`top` and `bottom`, or `left` and `right`).\\n- `'shift'`: never change side; keep the requested side and move the popup within\\n  the clipping boundary so it stays visible.\\n- `'none'`: do not correct side-axis overflow.\\n\\n`align` controls overflow on the alignment axis (`start`/`center`/`end`):\\n- `'flip'`: keep side, but swap `start` and `end` when the requested alignment overflows.\\n- `'shift'`: keep side and requested alignment, then nudge the popup along the\\n  alignment axis to fit.\\n- `'none'`: do not correct alignment-axis overflow.\\n\\n`fallbackAxisSide` controls fallback behavior on the perpendicular axis when the\\npreferred axis cannot fit:\\n- `'start'`: allow perpendicular fallback and try the logical start side first\\n  (`top` before `bottom`, or `left` before `right` in LTR).\\n- `'end'`: allow perpendicular fallback and try the logical end side first\\n  (`bottom` before `top`, or `right` before `left` in LTR).\\n- `'none'`: do not fallback to the perpendicular axis.\\n\\nWhen `side` is `'shift'`, explicitly setting `align` only supports `'shift'` or `'none'`.\\nIf `align` is omitted, it defaults to `'flip'`.\",\n      \"example\": \"```jsx\\n<Positioner\\n  collisionAvoidance={{\\n    side: 'shift',\\n    align: 'shift',\\n    fallbackAxisSide: 'none',\\n  }}\\n/>\\n```\",\n      \"detailedType\": \"| {\\n    side?: 'flip' | 'none'\\n    align?: 'flip' | 'shift' | 'none'\\n    fallbackAxisSide?: 'start' | 'end' | 'none'\\n  }\\n| {\\n    side?: 'shift' | 'none'\\n    align?: 'shift' | 'none'\\n    fallbackAxisSide?: 'start' | 'end' | 'none'\\n  }\\n| undefined\"\n    },\n    \"collisionBoundary\": {\n      \"type\": \"Boundary\",\n      \"default\": \"'clipping-ancestors'\",\n      \"description\": \"An element or a rectangle that delimits the area that the popup is confined to.\",\n      \"detailedType\": \"| 'clipping-ancestors'\\n| Element\\n| Element[]\\n| Rect\\n| undefined\"\n    },\n    \"collisionPadding\": {\n      \"type\": \"Padding\",\n      \"default\": \"5\",\n      \"description\": \"Additional space to maintain from the edge of the collision boundary.\",\n      \"detailedType\": \"| {\\n    top?: number\\n    right?: number\\n    bottom?: number\\n    left?: number\\n  }\\n| number\\n| undefined\"\n    },\n    \"sticky\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to maintain the popup in the viewport after\\nthe anchor element was scrolled out of view.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"positionMethod\": {\n      \"type\": \"'absolute' | 'fixed'\",\n      \"default\": \"'absolute'\",\n      \"description\": \"Determines which CSS `position` property to use.\",\n      \"detailedType\": \"'absolute' | 'fixed' | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Toast.Positioner.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Toast.Positioner.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Toast.Positioner.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Toast.Positioner.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Toast.Positioner.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Toast.Positioner.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-anchor-hidden\": {\n      \"description\": \"Present when the anchor is hidden.\"\n    },\n    \"data-align\": {\n      \"description\": \"Indicates how the toast is aligned relative to specified side.\",\n      \"type\": \"'start' | 'center' | 'end'\"\n    },\n    \"data-side\": {\n      \"description\": \"Indicates which side the toast is positioned relative to the trigger.\",\n      \"type\": \"'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'\"\n    }\n  },\n  \"cssVariables\": {\n    \"--anchor-height\": {\n      \"description\": \"The anchor's height.\",\n      \"type\": \"number\"\n    },\n    \"--anchor-width\": {\n      \"description\": \"The anchor's width.\",\n      \"type\": \"number\"\n    },\n    \"--available-height\": {\n      \"description\": \"The available height between the anchor and the edge of the viewport.\",\n      \"type\": \"number\"\n    },\n    \"--available-width\": {\n      \"description\": \"The available width between the anchor and the edge of the viewport.\",\n      \"type\": \"number\"\n    },\n    \"--transform-origin\": {\n      \"description\": \"The coordinates that this element is anchored to. Used for animations and transitions.\",\n      \"type\": \"string\"\n    }\n  }\n}\n"
  },
  {
    "path": "docs/reference/generated/toast-provider.json",
    "content": "{\n  \"name\": \"ToastProvider\",\n  \"description\": \"Provides a context for creating and managing toasts.\",\n  \"props\": {\n    \"limit\": {\n      \"type\": \"number\",\n      \"default\": \"3\",\n      \"description\": \"The maximum number of toasts that can be displayed at once.\\nWhen the limit is reached, the oldest toast will be removed to make room for the new one.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"toastManager\": {\n      \"type\": \"ToastManager\",\n      \"description\": \"A global manager for toasts to use outside of a React component.\",\n      \"detailedType\": \"| {\\n    subscribe: (\\n      listener: (data: ToastManagerEvent) => void,\\n    ) => () => void\\n    add: (options: ToastManagerAddOptions<any>) => string\\n    close: (id: string | undefined) => void\\n    update: (\\n      id: string,\\n      updates: ToastManagerUpdateOptions<any>,\\n    ) => void\\n    promise: (\\n      promiseValue: Promise<Value>,\\n      options: ToastManagerPromiseOptions<Value, any>,\\n    ) => Promise<Value>\\n  }\\n| undefined\"\n    },\n    \"timeout\": {\n      \"type\": \"number\",\n      \"default\": \"5000\",\n      \"description\": \"The default amount of time (in ms) before a toast is auto dismissed.\\nA value of `0` will prevent the toast from being dismissed automatically.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"children\": {\n      \"type\": \"ReactNode\",\n      \"detailedType\": \"React.ReactNode\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/toast-root.json",
    "content": "{\n  \"name\": \"ToastRoot\",\n  \"description\": \"Groups all parts of an individual toast.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"swipeDirection\": {\n      \"type\": \"'up' | 'down' | 'left' | 'right' | ('left' | 'right' | 'up' | 'down')[]\",\n      \"default\": \"['down', 'right']\",\n      \"description\": \"Direction(s) in which the toast can be swiped to dismiss.\",\n      \"detailedType\": \"| 'up'\\n| 'down'\\n| 'left'\\n| 'right'\\n| ('left' | 'right' | 'up' | 'down')[]\\n| undefined\"\n    },\n    \"toast\": {\n      \"type\": \"Toast.Root.ToastObject<any>\",\n      \"required\": true,\n      \"description\": \"The toast to render.\",\n      \"detailedType\": \"id: string\\n  ref?: RefObject<HTMLElement | null>\\n  title?: ReactNode\\n  type?: string\\n  description?: ReactNode\\n  timeout?: number\\n  priority?: 'low' | 'high'\\n  transitionStatus?: 'starting' | 'ending'\\n  limited?: boolean\\n  height?: number\\n  onClose?: () => void\\n  onRemove?: () => void\\n  actionProps?: Omit<\\n    DetailedHTMLProps<\\n      ButtonHTMLAttributes<HTMLButtonElement>,\\n      HTMLButtonElement\\n    >,\\n    'ref'\\n  >\\n  positionerProps?: ToastManagerPositionerProps\\n  data?: {}\\n}\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Toast.Root.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Toast.Root.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Toast.Root.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Toast.Root.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Toast.Root.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Toast.Root.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-expanded\": {\n      \"description\": \"Present when the toast is expanded in the viewport.\",\n      \"type\": \"boolean\"\n    },\n    \"data-limited\": {\n      \"description\": \"Present when the toast was removed due to exceeding the limit.\",\n      \"type\": \"boolean\"\n    },\n    \"data-swipe-direction\": {\n      \"description\": \"The direction the toast was swiped.\",\n      \"type\": \"'up' | 'down' | 'left' | 'right'\"\n    },\n    \"data-swiping\": {\n      \"description\": \"Present when the toast is being swiped.\",\n      \"type\": \"boolean\"\n    },\n    \"data-type\": {\n      \"description\": \"The type of the toast.\",\n      \"type\": \"string\"\n    },\n    \"data-starting-style\": {\n      \"description\": \"Present when the toast is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the toast is animating out.\"\n    }\n  },\n  \"cssVariables\": {\n    \"--toast-height\": {\n      \"description\": \"Indicates the measured natural height of the toast in pixels.\",\n      \"type\": \"number\"\n    },\n    \"--toast-index\": {\n      \"description\": \"Indicates the index of the toast in the list.\",\n      \"type\": \"number\"\n    },\n    \"--toast-offset-y\": {\n      \"description\": \"Indicates the vertical pixels offset of the toast in the list when expanded.\",\n      \"type\": \"number\"\n    },\n    \"--toast-swipe-movement-x\": {\n      \"description\": \"Indicates the horizontal swipe movement of the toast.\",\n      \"type\": \"number\"\n    },\n    \"--toast-swipe-movement-y\": {\n      \"description\": \"Indicates the vertical swipe movement of the toast.\",\n      \"type\": \"number\"\n    }\n  }\n}\n"
  },
  {
    "path": "docs/reference/generated/toast-title.json",
    "content": "{\n  \"name\": \"ToastTitle\",\n  \"description\": \"A title that labels the toast.\\nRenders an `<h2>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Toast.Title.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Toast.Title.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Toast.Title.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Toast.Title.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Toast.Title.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Toast.Title.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-type\": {\n      \"description\": \"The type of the toast.\",\n      \"type\": \"string\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/toast-viewport.json",
    "content": "{\n  \"name\": \"ToastViewport\",\n  \"description\": \"A container viewport for toasts.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Toast.Viewport.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Toast.Viewport.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Toast.Viewport.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Toast.Viewport.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Toast.Viewport.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Toast.Viewport.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-expanded\": {\n      \"description\": \"Indicates toasts are expanded in the viewport.\",\n      \"type\": \"boolean\"\n    }\n  },\n  \"cssVariables\": {\n    \"--toast-frontmost-height\": {\n      \"description\": \"Indicates the height of the frontmost toast.\",\n      \"type\": \"number\"\n    }\n  }\n}\n"
  },
  {
    "path": "docs/reference/generated/toggle-group.json",
    "content": "{\n  \"name\": \"ToggleGroup\",\n  \"description\": \"Provides a shared state to a series of toggle buttons.\",\n  \"props\": {\n    \"defaultValue\": {\n      \"type\": \"string[]\",\n      \"description\": \"The open state of the toggle group represented by an array of\\nthe values of all pressed toggle buttons.\\nThis is the uncontrolled counterpart of `value`.\",\n      \"detailedType\": \"string[] | undefined\"\n    },\n    \"value\": {\n      \"type\": \"string[]\",\n      \"description\": \"The open state of the toggle group represented by an array of\\nthe values of all pressed toggle buttons.\\nThis is the controlled counterpart of `defaultValue`.\",\n      \"detailedType\": \"string[] | undefined\"\n    },\n    \"onValueChange\": {\n      \"type\": \"((groupValue: string[], eventDetails: Toggle.Group.ChangeEventDetails) => void)\",\n      \"description\": \"Callback fired when the pressed states of the toggle group changes.\",\n      \"detailedType\": \"| ((\\n    groupValue: string[],\\n    eventDetails: Toggle.Group.ChangeEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"loopFocus\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether to loop keyboard focus back to the first item\\nwhen the end of the list is reached while using the arrow keys.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"multiple\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"When `false` only one item in the group can be pressed. If any item in\\nthe group becomes pressed, the others will become unpressed.\\nWhen `true` multiple items can be pressed.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the toggle group should ignore user interaction.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"orientation\": {\n      \"type\": \"Orientation\",\n      \"default\": \"'horizontal'\",\n      \"detailedType\": \"'horizontal' | 'vertical' | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: ToggleGroup.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: ToggleGroup.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: ToggleGroup.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: ToggleGroup.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: ToggleGroup.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: ToggleGroup.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-orientation\": {\n      \"description\": \"Indicates the orientation of the toggle group.\",\n      \"type\": \"'horizontal' | 'vertical'\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the toggle group is disabled.\"\n    },\n    \"data-multiple\": {\n      \"description\": \"Present when the toggle group allows multiple buttons to be in the pressed state at the same time.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/toggle.json",
    "content": "{\n  \"name\": \"Toggle\",\n  \"description\": \"A two-state button that can be on or off.\\nRenders a `<button>` element.\",\n  \"props\": {\n    \"value\": {\n      \"type\": \"string\",\n      \"description\": \"A unique string that identifies the toggle when used\\ninside a toggle group.\",\n      \"detailedType\": \"string | undefined\"\n    },\n    \"defaultPressed\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the toggle button is currently pressed.\\nThis is the uncontrolled counterpart of `pressed`.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"pressed\": {\n      \"type\": \"boolean\",\n      \"description\": \"Whether the toggle button is currently pressed.\\nThis is the controlled counterpart of `defaultPressed`.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"onPressedChange\": {\n      \"type\": \"((pressed: boolean, eventDetails: Toggle.ChangeEventDetails) => void)\",\n      \"description\": \"Callback fired when the pressed state is changed.\",\n      \"detailedType\": \"| ((\\n    pressed: boolean,\\n    eventDetails: Toggle.ChangeEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"nativeButton\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether the component renders a native `<button>` element when replacing it\\nvia the `render` prop.\\nSet to `false` if the rendered element is not a button (e.g. `<div>`).\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the component should ignore user interaction.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Toggle.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Toggle.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Toggle.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Toggle.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Toggle.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((props: HTMLProps, state: Toggle.State) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-pressed\": {\n      \"description\": \"Present when the toggle button is pressed.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/toolbar-button.json",
    "content": "{\n  \"name\": \"ToolbarButton\",\n  \"description\": \"A button that can be used as-is or as a trigger for other components.\\nRenders a `<button>` element.\",\n  \"props\": {\n    \"focusableWhenDisabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"When `true` the item remains focusable when disabled.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"nativeButton\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether the component renders a native `<button>` element when replacing it\\nvia the `render` prop.\\nSet to `false` if the rendered element is not a button (e.g. `<div>`).\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"When `true` the item is disabled.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Toolbar.Button.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Toolbar.Button.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Toolbar.Button.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Toolbar.Button.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Toolbar.Button.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Toolbar.Button.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-orientation\": {\n      \"description\": \"Indicates the orientation of the toolbar.\",\n      \"type\": \"'horizontal' | 'vertical'\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the button is disabled.\"\n    },\n    \"data-focusable\": {\n      \"description\": \"Present when the button remains focusable when disabled.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/toolbar-group.json",
    "content": "{\n  \"name\": \"ToolbarGroup\",\n  \"description\": \"Groups several toolbar items or toggles.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"When `true` all toolbar items in the group are disabled.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Toolbar.Group.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Toolbar.Group.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Toolbar.Group.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Toolbar.Group.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Toolbar.Group.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Toolbar.Group.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-orientation\": {\n      \"description\": \"Indicates the orientation of the toolbar.\",\n      \"type\": \"'horizontal' | 'vertical'\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the group is disabled.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/toolbar-input.json",
    "content": "{\n  \"name\": \"ToolbarInput\",\n  \"description\": \"A native input element that integrates with Toolbar keyboard navigation.\\nRenders an `<input>` element.\",\n  \"props\": {\n    \"defaultValue\": {\n      \"type\": \"string | number | string[]\",\n      \"detailedType\": \"string | number | string[] | undefined\"\n    },\n    \"focusableWhenDisabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"When `true` the item remains focusable when disabled.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"When `true` the item is disabled.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Toolbar.Input.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Toolbar.Input.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Toolbar.Input.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Toolbar.Input.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Toolbar.Input.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Toolbar.Input.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-orientation\": {\n      \"description\": \"Indicates the orientation of the toolbar.\",\n      \"type\": \"'horizontal' | 'vertical'\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the input is disabled.\"\n    },\n    \"data-focusable\": {\n      \"description\": \"Present when the input remains focusable when disabled.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/toolbar-link.json",
    "content": "{\n  \"name\": \"ToolbarLink\",\n  \"description\": \"A link component.\\nRenders an `<a>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Toolbar.Link.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Toolbar.Link.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Toolbar.Link.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Toolbar.Link.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Toolbar.Link.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Toolbar.Link.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-orientation\": {\n      \"description\": \"Indicates the orientation of the toolbar.\",\n      \"type\": \"'horizontal' | 'vertical'\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/toolbar-root.json",
    "content": "{\n  \"name\": \"ToolbarRoot\",\n  \"description\": \"A container for grouping a set of controls, such as buttons, toggle groups, or menus.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"loopFocus\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"If `true`, using keyboard navigation will wrap focus to the other end of the toolbar once the end is reached.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"orientation\": {\n      \"type\": \"Toolbar.Root.Orientation\",\n      \"default\": \"'horizontal'\",\n      \"description\": \"The orientation of the toolbar.\",\n      \"detailedType\": \"'horizontal' | 'vertical' | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Toolbar.Root.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Toolbar.Root.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Toolbar.Root.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((state: Toolbar.Root.State) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Toolbar.Root.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Toolbar.Root.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-orientation\": {\n      \"description\": \"Indicates the orientation of the toolbar.\",\n      \"type\": \"'horizontal' | 'vertical'\"\n    },\n    \"data-disabled\": {\n      \"description\": \"Present when the toolbar is disabled.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/toolbar-separator.json",
    "content": "{\n  \"name\": \"ToolbarSeparator\",\n  \"description\": \"A separator element accessible to screen readers.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"orientation\": {\n      \"type\": \"Orientation\",\n      \"default\": \"'horizontal'\",\n      \"description\": \"The orientation of the separator.\",\n      \"detailedType\": \"'horizontal' | 'vertical' | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Toolbar.Separator.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Toolbar.Separator.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Toolbar.Separator.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Toolbar.Separator.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Toolbar.Separator.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Toolbar.Separator.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-orientation\": {\n      \"description\": \"Indicates the orientation of the toolbar.\",\n      \"type\": \"'horizontal' | 'vertical'\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/tooltip-arrow.json",
    "content": "{\n  \"name\": \"TooltipArrow\",\n  \"description\": \"Displays an element positioned against the tooltip anchor.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Tooltip.Arrow.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Tooltip.Arrow.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Tooltip.Arrow.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Tooltip.Arrow.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Tooltip.Arrow.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Tooltip.Arrow.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the tooltip is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the tooltip is closed.\"\n    },\n    \"data-uncentered\": {\n      \"description\": \"Present when the tooltip arrow is uncentered.\"\n    },\n    \"data-align\": {\n      \"description\": \"Indicates how the popup is aligned relative to specified side.\",\n      \"type\": \"'start' | 'center' | 'end'\"\n    },\n    \"data-instant\": {\n      \"description\": \"Present if animations should be instant.\",\n      \"type\": \"'delay' | 'dismiss' | 'focus'\"\n    },\n    \"data-side\": {\n      \"description\": \"Indicates which side the popup is positioned relative to the trigger.\",\n      \"type\": \"'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/tooltip-popup.json",
    "content": "{\n  \"name\": \"TooltipPopup\",\n  \"description\": \"A container for the tooltip contents.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"className\": {\n      \"type\": \"string | ((state: Tooltip.Popup.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Tooltip.Popup.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Tooltip.Popup.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Tooltip.Popup.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Tooltip.Popup.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Tooltip.Popup.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the tooltip is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the tooltip is closed.\"\n    },\n    \"data-align\": {\n      \"description\": \"Indicates how the popup is aligned relative to specified side.\",\n      \"type\": \"'start' | 'center' | 'end'\"\n    },\n    \"data-instant\": {\n      \"description\": \"Present if animations should be instant.\",\n      \"type\": \"'delay' | 'dismiss' | 'focus'\"\n    },\n    \"data-side\": {\n      \"description\": \"Indicates which side the popup is positioned relative to the trigger.\",\n      \"type\": \"'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'\"\n    },\n    \"data-starting-style\": {\n      \"description\": \"Present when the tooltip is animating in.\"\n    },\n    \"data-ending-style\": {\n      \"description\": \"Present when the tooltip is animating out.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/tooltip-portal.json",
    "content": "{\n  \"name\": \"TooltipPortal\",\n  \"description\": \"A portal element that moves the popup to a different part of the DOM.\\nBy default, the portal element is appended to `<body>`.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"container\": {\n      \"type\": \"HTMLElement | ShadowRoot | RefObject<HTMLElement | ShadowRoot | null> | null\",\n      \"description\": \"A parent element to render the portal element into.\",\n      \"detailedType\": \"| HTMLElement\\n| ShadowRoot\\n| React.RefObject<HTMLElement | ShadowRoot | null>\\n| null\\n| undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Tooltip.Portal.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Tooltip.Portal.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Tooltip.Portal.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Tooltip.Portal.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"keepMounted\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to keep the portal mounted in the DOM while the popup is hidden.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Tooltip.Portal.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Tooltip.Portal.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/tooltip-positioner.json",
    "content": "{\n  \"name\": \"TooltipPositioner\",\n  \"description\": \"Positions the tooltip against the trigger.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"disableAnchorTracking\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to disable the popup from tracking any layout shift of its positioning anchor.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"align\": {\n      \"type\": \"Align\",\n      \"default\": \"'center'\",\n      \"description\": \"How to align the popup relative to the specified side.\",\n      \"detailedType\": \"'start' | 'center' | 'end' | undefined\"\n    },\n    \"alignOffset\": {\n      \"type\": \"number | OffsetFunction\",\n      \"default\": \"0\",\n      \"description\": \"Additional offset along the alignment axis in pixels.\\nAlso accepts a function that returns the offset to read the dimensions of the anchor\\nand positioner elements, along with its side and alignment.\\n\\nThe function takes a `data` object parameter with the following properties:\\n- `data.anchor`: the dimensions of the anchor element with properties `width` and `height`.\\n- `data.positioner`: the dimensions of the positioner element with properties `width` and `height`.\\n- `data.side`: which side of the anchor element the positioner is aligned against.\\n- `data.align`: how the positioner is aligned relative to the specified side.\",\n      \"example\": \"```jsx\\n<Positioner\\n  alignOffset={({ side, align, anchor, positioner }) => {\\n    return side === 'top' || side === 'bottom'\\n      ? anchor.width\\n      : anchor.height;\\n  }}\\n/>\\n```\",\n      \"detailedType\": \"| number\\n| ((data: {\\n    side: Side\\n    align: Align\\n    anchor: { width: number; height: number }\\n    positioner: { width: number; height: number }\\n  }) => number)\\n| undefined\"\n    },\n    \"side\": {\n      \"type\": \"Side\",\n      \"default\": \"'top'\",\n      \"description\": \"Which side of the anchor element to align the popup against.\\nMay automatically change to avoid collisions.\",\n      \"detailedType\": \"| 'top'\\n| 'bottom'\\n| 'left'\\n| 'right'\\n| 'inline-end'\\n| 'inline-start'\\n| undefined\"\n    },\n    \"sideOffset\": {\n      \"type\": \"number | OffsetFunction\",\n      \"default\": \"0\",\n      \"description\": \"Distance between the anchor and the popup in pixels.\\nAlso accepts a function that returns the distance to read the dimensions of the anchor\\nand positioner elements, along with its side and alignment.\\n\\nThe function takes a `data` object parameter with the following properties:\\n- `data.anchor`: the dimensions of the anchor element with properties `width` and `height`.\\n- `data.positioner`: the dimensions of the positioner element with properties `width` and `height`.\\n- `data.side`: which side of the anchor element the positioner is aligned against.\\n- `data.align`: how the positioner is aligned relative to the specified side.\",\n      \"example\": \"```jsx\\n<Positioner\\n  sideOffset={({ side, align, anchor, positioner }) => {\\n    return side === 'top' || side === 'bottom'\\n      ? anchor.height\\n      : anchor.width;\\n  }}\\n/>\\n```\",\n      \"detailedType\": \"| number\\n| ((data: {\\n    side: Side\\n    align: Align\\n    anchor: { width: number; height: number }\\n    positioner: { width: number; height: number }\\n  }) => number)\\n| undefined\"\n    },\n    \"arrowPadding\": {\n      \"type\": \"number\",\n      \"default\": \"5\",\n      \"description\": \"Minimum distance to maintain between the arrow and the edges of the popup.\\n\\nUse it to prevent the arrow element from hanging out of the rounded corners of a popup.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"anchor\": {\n      \"type\": \"Element | VirtualElement | RefObject<Element | null> | (() => Element | VirtualElement | null) | null\",\n      \"description\": \"An element to position the popup against.\\nBy default, the popup will be positioned against the trigger.\",\n      \"detailedType\": \"| Element\\n| VirtualElement\\n| React.RefObject<Element | null>\\n| (() => Element | VirtualElement | null)\\n| null\\n| undefined\"\n    },\n    \"collisionAvoidance\": {\n      \"type\": \"CollisionAvoidance\",\n      \"description\": \"Determines how to handle collisions when positioning the popup.\\n\\n`side` controls overflow on the preferred placement axis (`top`/`bottom` or `left`/`right`):\\n- `'flip'`: keep the requested side when it fits; otherwise try the opposite side\\n  (`top` and `bottom`, or `left` and `right`).\\n- `'shift'`: never change side; keep the requested side and move the popup within\\n  the clipping boundary so it stays visible.\\n- `'none'`: do not correct side-axis overflow.\\n\\n`align` controls overflow on the alignment axis (`start`/`center`/`end`):\\n- `'flip'`: keep side, but swap `start` and `end` when the requested alignment overflows.\\n- `'shift'`: keep side and requested alignment, then nudge the popup along the\\n  alignment axis to fit.\\n- `'none'`: do not correct alignment-axis overflow.\\n\\n`fallbackAxisSide` controls fallback behavior on the perpendicular axis when the\\npreferred axis cannot fit:\\n- `'start'`: allow perpendicular fallback and try the logical start side first\\n  (`top` before `bottom`, or `left` before `right` in LTR).\\n- `'end'`: allow perpendicular fallback and try the logical end side first\\n  (`bottom` before `top`, or `right` before `left` in LTR).\\n- `'none'`: do not fallback to the perpendicular axis.\\n\\nWhen `side` is `'shift'`, explicitly setting `align` only supports `'shift'` or `'none'`.\\nIf `align` is omitted, it defaults to `'flip'`.\",\n      \"example\": \"```jsx\\n<Positioner\\n  collisionAvoidance={{\\n    side: 'shift',\\n    align: 'shift',\\n    fallbackAxisSide: 'none',\\n  }}\\n/>\\n```\",\n      \"detailedType\": \"| {\\n    side?: 'flip' | 'none'\\n    align?: 'flip' | 'shift' | 'none'\\n    fallbackAxisSide?: 'start' | 'end' | 'none'\\n  }\\n| {\\n    side?: 'shift' | 'none'\\n    align?: 'shift' | 'none'\\n    fallbackAxisSide?: 'start' | 'end' | 'none'\\n  }\\n| undefined\"\n    },\n    \"collisionBoundary\": {\n      \"type\": \"Boundary\",\n      \"default\": \"'clipping-ancestors'\",\n      \"description\": \"An element or a rectangle that delimits the area that the popup is confined to.\",\n      \"detailedType\": \"| 'clipping-ancestors'\\n| Element\\n| Element[]\\n| Rect\\n| undefined\"\n    },\n    \"collisionPadding\": {\n      \"type\": \"Padding\",\n      \"default\": \"5\",\n      \"description\": \"Additional space to maintain from the edge of the collision boundary.\",\n      \"detailedType\": \"| {\\n    top?: number\\n    right?: number\\n    bottom?: number\\n    left?: number\\n  }\\n| number\\n| undefined\"\n    },\n    \"sticky\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether to maintain the popup in the viewport after\\nthe anchor element was scrolled out of view.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"positionMethod\": {\n      \"type\": \"'absolute' | 'fixed'\",\n      \"default\": \"'absolute'\",\n      \"description\": \"Determines which CSS `position` property to use.\",\n      \"detailedType\": \"'absolute' | 'fixed' | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Tooltip.Positioner.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Tooltip.Positioner.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Tooltip.Positioner.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Tooltip.Positioner.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Tooltip.Positioner.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Tooltip.Positioner.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-open\": {\n      \"description\": \"Present when the tooltip is open.\"\n    },\n    \"data-closed\": {\n      \"description\": \"Present when the tooltip is closed.\"\n    },\n    \"data-anchor-hidden\": {\n      \"description\": \"Present when the anchor is hidden.\"\n    },\n    \"data-align\": {\n      \"description\": \"Indicates how the popup is aligned relative to specified side.\",\n      \"type\": \"'start' | 'center' | 'end'\"\n    },\n    \"data-side\": {\n      \"description\": \"Indicates which side the popup is positioned relative to the trigger.\",\n      \"type\": \"'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'\"\n    }\n  },\n  \"cssVariables\": {\n    \"--anchor-height\": {\n      \"description\": \"The anchor's height.\",\n      \"type\": \"number\"\n    },\n    \"--anchor-width\": {\n      \"description\": \"The anchor's width.\",\n      \"type\": \"number\"\n    },\n    \"--available-height\": {\n      \"description\": \"The available height between the trigger and the edge of the viewport.\",\n      \"type\": \"number\"\n    },\n    \"--available-width\": {\n      \"description\": \"The available width between the trigger and the edge of the viewport.\",\n      \"type\": \"number\"\n    },\n    \"--transform-origin\": {\n      \"description\": \"The coordinates that this element is anchored to. Used for animations and transitions.\",\n      \"type\": \"string\"\n    }\n  }\n}\n"
  },
  {
    "path": "docs/reference/generated/tooltip-provider.json",
    "content": "{\n  \"name\": \"TooltipProvider\",\n  \"description\": \"Provides a shared delay for multiple tooltips. The grouping logic ensures that\\nonce a tooltip becomes visible, the adjacent tooltips will be shown instantly.\",\n  \"props\": {\n    \"delay\": {\n      \"type\": \"number\",\n      \"description\": \"How long to wait before opening a tooltip. Specified in milliseconds.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"closeDelay\": {\n      \"type\": \"number\",\n      \"description\": \"How long to wait before closing a tooltip. Specified in milliseconds.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"timeout\": {\n      \"type\": \"number\",\n      \"default\": \"400\",\n      \"description\": \"Another tooltip will open instantly if the previous tooltip\\nis closed within this timeout. Specified in milliseconds.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"children\": {\n      \"type\": \"ReactNode\",\n      \"detailedType\": \"React.ReactNode\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/tooltip-root.json",
    "content": "{\n  \"name\": \"TooltipRoot\",\n  \"description\": \"Groups all parts of the tooltip.\\nDoesn’t render its own HTML element.\",\n  \"props\": {\n    \"defaultOpen\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the tooltip is initially open.\\n\\nTo render a controlled tooltip, use the `open` prop instead.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"open\": {\n      \"type\": \"boolean\",\n      \"description\": \"Whether the tooltip is currently open.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"onOpenChange\": {\n      \"type\": \"((open: boolean, eventDetails: Tooltip.Root.ChangeEventDetails) => void)\",\n      \"description\": \"Event handler called when the tooltip is opened or closed.\",\n      \"detailedType\": \"| ((\\n    open: boolean,\\n    eventDetails: Tooltip.Root.ChangeEventDetails,\\n  ) => void)\\n| undefined\"\n    },\n    \"actionsRef\": {\n      \"type\": \"RefObject<Tooltip.Root.Actions | null>\",\n      \"description\": \"A ref to imperative actions.\\n- `unmount`: Unmounts the tooltip popup.\\n- `close`: Closes the tooltip imperatively when called.\",\n      \"detailedType\": \"| React.RefObject<Tooltip.Root.Actions | null>\\n| undefined\"\n    },\n    \"defaultTriggerId\": {\n      \"type\": \"string | null\",\n      \"description\": \"ID of the trigger that the tooltip is associated with.\\nThis is useful in conjunction with the `defaultOpen` prop to create an initially open tooltip.\",\n      \"detailedType\": \"string | null | undefined\"\n    },\n    \"handle\": {\n      \"type\": \"Tooltip.Handle<Payload>\",\n      \"description\": \"A handle to associate the tooltip with a trigger.\\nIf specified, allows external triggers to control the tooltip's open state.\\nCan be created with the Tooltip.createHandle() method.\",\n      \"detailedType\": \"{} | undefined\"\n    },\n    \"onOpenChangeComplete\": {\n      \"type\": \"((open: boolean) => void)\",\n      \"description\": \"Event handler called after any animations complete when the tooltip is opened or closed.\",\n      \"detailedType\": \"((open: boolean) => void) | undefined\"\n    },\n    \"triggerId\": {\n      \"type\": \"string | null\",\n      \"description\": \"ID of the trigger that the tooltip is associated with.\\nThis is useful in conjunction with the `open` prop to create a controlled tooltip.\\nThere's no need to specify this prop when the tooltip is uncontrolled (i.e. when the `open` prop is not set).\",\n      \"detailedType\": \"string | null | undefined\"\n    },\n    \"trackCursorAxis\": {\n      \"type\": \"'none' | 'x' | 'y' | 'both'\",\n      \"default\": \"'none'\",\n      \"description\": \"Determines which axis the tooltip should track the cursor on.\",\n      \"detailedType\": \"'none' | 'x' | 'y' | 'both' | undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the tooltip is disabled.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"disableHoverablePopup\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"Whether the tooltip contents can be hovered without closing the tooltip.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"children\": {\n      \"type\": \"ReactNode | PayloadChildRenderFunction<Payload>\",\n      \"description\": \"The content of the tooltip.\\nThis can be a regular React node or a render function that receives the `payload` of the active trigger.\",\n      \"detailedType\": \"| React.ReactNode\\n| ((arg: { payload: Payload | undefined }) => ReactNode)\"\n    }\n  },\n  \"dataAttributes\": {},\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/tooltip-trigger.json",
    "content": "{\n  \"name\": \"TooltipTrigger\",\n  \"description\": \"An element to attach the tooltip to.\\nRenders a `<button>` element.\",\n  \"props\": {\n    \"closeOnClick\": {\n      \"type\": \"boolean\",\n      \"default\": \"true\",\n      \"description\": \"Whether the tooltip should close when this trigger is clicked.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"handle\": {\n      \"type\": \"Tooltip.Handle<Payload>\",\n      \"description\": \"A handle to associate the trigger with a tooltip.\",\n      \"detailedType\": \"{} | undefined\"\n    },\n    \"payload\": {\n      \"type\": \"Payload\",\n      \"description\": \"A payload to pass to the tooltip when it is opened.\",\n      \"detailedType\": \"Payload | undefined\"\n    },\n    \"disabled\": {\n      \"type\": \"boolean\",\n      \"default\": \"false\",\n      \"description\": \"If `true`, the tooltip will not open when interacting with this trigger.\\nNote that this doesn't apply the `disabled` attribute to the trigger element.\\nIf you want to disable the trigger element itself, you can pass the `disabled` prop to the trigger element via the `render` prop.\",\n      \"detailedType\": \"boolean | undefined\"\n    },\n    \"delay\": {\n      \"type\": \"number\",\n      \"default\": \"600\",\n      \"description\": \"How long to wait before opening the tooltip. Specified in milliseconds.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"closeDelay\": {\n      \"type\": \"number\",\n      \"default\": \"0\",\n      \"description\": \"How long to wait before closing the tooltip. Specified in milliseconds.\",\n      \"detailedType\": \"number | undefined\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Tooltip.Trigger.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Tooltip.Trigger.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Tooltip.Trigger.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Tooltip.Trigger.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Tooltip.Trigger.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Tooltip.Trigger.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-popup-open\": {\n      \"description\": \"Present when the corresponding tooltip is open.\"\n    },\n    \"data-trigger-disabled\": {\n      \"description\": \"Present when the trigger is disabled, either by the `disabled` prop or by a parent `<Tooltip.Root>` component.\"\n    }\n  },\n  \"cssVariables\": {}\n}\n"
  },
  {
    "path": "docs/reference/generated/tooltip-viewport.json",
    "content": "{\n  \"name\": \"TooltipViewport\",\n  \"description\": \"A viewport for displaying content transitions.\\nThis component is only required if one popup can be opened by multiple triggers, its content change based on the trigger\\nand switching between them is animated.\\nRenders a `<div>` element.\",\n  \"props\": {\n    \"children\": {\n      \"type\": \"ReactNode\",\n      \"description\": \"The content to render inside the transition container.\",\n      \"detailedType\": \"React.ReactNode\"\n    },\n    \"className\": {\n      \"type\": \"string | ((state: Tooltip.Viewport.State) => string | undefined)\",\n      \"description\": \"CSS class applied to the element, or a function that\\nreturns a class based on the component’s state.\",\n      \"detailedType\": \"| string\\n| ((state: Tooltip.Viewport.State) => string | undefined)\"\n    },\n    \"style\": {\n      \"type\": \"CSSProperties | ((state: Tooltip.Viewport.State) => CSSProperties | undefined)\",\n      \"detailedType\": \"| React.CSSProperties\\n| ((\\n    state: Tooltip.Viewport.State,\\n  ) => CSSProperties | undefined)\\n| undefined\"\n    },\n    \"render\": {\n      \"type\": \"ReactElement | ((props: HTMLProps, state: Tooltip.Viewport.State) => ReactElement)\",\n      \"description\": \"Allows you to replace the component’s HTML element\\nwith a different tag, or compose it with another component.\\n\\nAccepts a `ReactElement` or a function that returns the element to render.\",\n      \"detailedType\": \"| ReactElement\\n| ((\\n    props: HTMLProps,\\n    state: Tooltip.Viewport.State,\\n  ) => ReactElement)\"\n    }\n  },\n  \"dataAttributes\": {\n    \"data-activation-direction\": {\n      \"description\": \"Indicates the direction from which the popup was activated.\\nThis can be used to create directional animations based on how the popup was triggered.\\nContains space-separated values for both horizontal and vertical axes.\",\n      \"type\": \"`${'left' | 'right'} {'top' | 'bottom'}`\"\n    },\n    \"data-current\": {\n      \"description\": \"Applied to the direct child of the viewport when no transitions are present or the new content when it's entering.\"\n    },\n    \"data-instant\": {\n      \"description\": \"Present if animations should be instant.\",\n      \"type\": \"'delay' | 'dismiss' | 'focus'\"\n    },\n    \"data-previous\": {\n      \"description\": \"Applied to the direct child of the viewport that contains the exiting content when transitions are present.\"\n    },\n    \"data-transitioning\": {\n      \"description\": \"Indicates that the viewport is currently transitioning between old and new content.\"\n    }\n  },\n  \"cssVariables\": {\n    \"--popup-height\": {\n      \"description\": \"The height of the parent popup.\\nThis variable is placed on the 'previous' container and stores the height of the popup when the previous content was rendered.\\nIt can be used to freeze the dimensions of the popup when animating between different content.\"\n    },\n    \"--popup-width\": {\n      \"description\": \"The width of the parent popup.\\nThis variable is placed on the 'previous' container and stores the width of the popup when the previous content was rendered.\\nIt can be used to freeze the dimensions of the popup when animating between different content.\"\n    }\n  }\n}\n"
  },
  {
    "path": "docs/reference/generated/use-render.json",
    "content": "{\n  \"name\": \"useRender\",\n  \"description\": \"Renders a Base UI element.\",\n  \"parameters\": {\n    \"render\": {\n      \"type\": \"UseRenderRenderProp<Record<string, unknown>>\",\n      \"description\": \"The React element or a function that returns one to override the default element.\"\n    },\n    \"ref\": {\n      \"type\": \"Ref<Element>[] | Ref<Element>\",\n      \"description\": \"The ref to apply to the rendered element.\",\n      \"detailedType\": \"Ref<Element>[] | React.Ref<Element> | undefined\"\n    },\n    \"state\": {\n      \"type\": \"Record<string, unknown>\",\n      \"description\": \"The state of the component, passed as the second argument to the `render` callback.\\nState properties are automatically converted to data-* attributes.\",\n      \"detailedType\": \"Record<string, unknown> | undefined\"\n    },\n    \"stateAttributesMapping\": {\n      \"type\": \"StateAttributesMapping<Record<string, unknown>>\",\n      \"description\": \"Custom mapping for converting state properties to data-* attributes.\",\n      \"example\": \"{ isActive: (value) => (value ? { 'data-is-active': '' } : null) }\",\n      \"detailedType\": \"{} | undefined\"\n    },\n    \"props\": {\n      \"type\": \"Record<string, unknown>\",\n      \"description\": \"Props to be spread on the rendered element.\\nThey are merged with the internal props of the component, so that event handlers\\nare merged, `className` strings and `style` properties are joined, while other external props overwrite the\\ninternal ones.\",\n      \"detailedType\": \"Record<string, unknown> | undefined\"\n    },\n    \"enabled\": {\n      \"type\": \"boolean | undefined\",\n      \"default\": \"true\",\n      \"description\": \"If `false`, the hook will skip most of its internal logic and return `null`.\\nThis is useful for rendering a component conditionally.\",\n      \"detailedType\": \"boolean | undefined | undefined\"\n    },\n    \"defaultTagName\": {\n      \"type\": \"'symbol' | 'object' | 'div' | 'a' | 'abbr' | 'address' | 'area' | 'article' | 'aside' | 'audio' | 'b' | 'base' | 'bdi' | 'bdo' | 'big' | 'blockquote' | 'body' | 'br' | 'button' | 'canvas' | 'caption' | 'center' | 'cite' | 'code' | 'col' | 'colgroup' | 'data' | 'datalist' | 'dd' | 'del' | 'details' | 'dfn' | 'dialog' | 'dl' | 'dt' | 'em' | 'embed' | 'fieldset' | 'figcaption' | 'figure' | 'footer' | 'form' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'head' | 'header' | 'hgroup' | 'hr' | 'html' | 'i' | 'iframe' | 'img' | 'input' | 'ins' | 'kbd' | 'keygen' | 'label' | 'legend' | 'li' | 'link' | 'main' | 'map' | 'mark' | 'menu' | 'menuitem' | 'meta' | 'meter' | 'nav' | 'noindex' | 'noscript' | 'ol' | 'optgroup' | 'option' | 'output' | 'p' | 'param' | 'picture' | 'pre' | 'progress' | 'q' | 'rp' | 'rt' | 'ruby' | 's' | 'samp' | 'search' | 'slot' | 'script' | 'section' | 'select' | 'small' | 'source' | 'span' | 'strong' | 'style' | 'sub' | 'summary' | 'sup' | 'table' | 'template' | 'tbody' | 'td' | 'textarea' | 'tfoot' | 'th' | 'thead' | 'time' | 'title' | 'tr' | 'track' | 'u' | 'ul' | 'var' | 'video' | 'wbr' | 'webview' | 'svg' | 'animate' | 'animateMotion' | 'animateTransform' | 'circle' | 'clipPath' | 'defs' | 'desc' | 'ellipse' | 'feBlend' | 'feColorMatrix' | 'feComponentTransfer' | 'feComposite' | 'feConvolveMatrix' | 'feDiffuseLighting' | 'feDisplacementMap' | 'feDistantLight' | 'feDropShadow' | 'feFlood' | 'feFuncA' | 'feFuncB' | 'feFuncG' | 'feFuncR' | 'feGaussianBlur' | 'feImage' | 'feMerge' | 'feMergeNode' | 'feMorphology' | 'feOffset' | 'fePointLight' | 'feSpecularLighting' | 'feSpotLight' | 'feTile' | 'feTurbulence' | 'filter' | 'foreignObject' | 'g' | 'image' | 'line' | 'linearGradient' | 'marker' | 'mask' | 'metadata' | 'mpath' | 'path' | 'pattern' | 'polygon' | 'polyline' | 'radialGradient' | 'rect' | 'set' | 'stop' | 'switch' | 'text' | 'textPath' | 'tspan' | 'use' | 'view'\",\n      \"default\": \"'div'\",\n      \"description\": \"The default tag name to use for the rendered element when `render` is not provided.\",\n      \"detailedType\": \"| 'symbol'\\n| 'object'\\n| 'div'\\n| 'a'\\n| 'abbr'\\n| 'address'\\n| 'area'\\n| 'article'\\n| 'aside'\\n| 'audio'\\n| 'b'\\n| 'base'\\n| 'bdi'\\n| 'bdo'\\n| 'big'\\n| 'blockquote'\\n| 'body'\\n| 'br'\\n| 'button'\\n| 'canvas'\\n| 'caption'\\n| 'center'\\n| 'cite'\\n| 'code'\\n| 'col'\\n| 'colgroup'\\n| 'data'\\n| 'datalist'\\n| 'dd'\\n| 'del'\\n| 'details'\\n| 'dfn'\\n| 'dialog'\\n| 'dl'\\n| 'dt'\\n| 'em'\\n| 'embed'\\n| 'fieldset'\\n| 'figcaption'\\n| 'figure'\\n| 'footer'\\n| 'form'\\n| 'h1'\\n| 'h2'\\n| 'h3'\\n| 'h4'\\n| 'h5'\\n| 'h6'\\n| 'head'\\n| 'header'\\n| 'hgroup'\\n| 'hr'\\n| 'html'\\n| 'i'\\n| 'iframe'\\n| 'img'\\n| 'input'\\n| 'ins'\\n| 'kbd'\\n| 'keygen'\\n| 'label'\\n| 'legend'\\n| 'li'\\n| 'link'\\n| 'main'\\n| 'map'\\n| 'mark'\\n| 'menu'\\n| 'menuitem'\\n| 'meta'\\n| 'meter'\\n| 'nav'\\n| 'noindex'\\n| 'noscript'\\n| 'ol'\\n| 'optgroup'\\n| 'option'\\n| 'output'\\n| 'p'\\n| 'param'\\n| 'picture'\\n| 'pre'\\n| 'progress'\\n| 'q'\\n| 'rp'\\n| 'rt'\\n| 'ruby'\\n| 's'\\n| 'samp'\\n| 'search'\\n| 'slot'\\n| 'script'\\n| 'section'\\n| 'select'\\n| 'small'\\n| 'source'\\n| 'span'\\n| 'strong'\\n| 'style'\\n| 'sub'\\n| 'summary'\\n| 'sup'\\n| 'table'\\n| 'template'\\n| 'tbody'\\n| 'td'\\n| 'textarea'\\n| 'tfoot'\\n| 'th'\\n| 'thead'\\n| 'time'\\n| 'title'\\n| 'tr'\\n| 'track'\\n| 'u'\\n| 'ul'\\n| 'var'\\n| 'video'\\n| 'wbr'\\n| 'webview'\\n| 'svg'\\n| 'animate'\\n| 'animateMotion'\\n| 'animateTransform'\\n| 'circle'\\n| 'clipPath'\\n| 'defs'\\n| 'desc'\\n| 'ellipse'\\n| 'feBlend'\\n| 'feColorMatrix'\\n| 'feComponentTransfer'\\n| 'feComposite'\\n| 'feConvolveMatrix'\\n| 'feDiffuseLighting'\\n| 'feDisplacementMap'\\n| 'feDistantLight'\\n| 'feDropShadow'\\n| 'feFlood'\\n| 'feFuncA'\\n| 'feFuncB'\\n| 'feFuncG'\\n| 'feFuncR'\\n| 'feGaussianBlur'\\n| 'feImage'\\n| 'feMerge'\\n| 'feMergeNode'\\n| 'feMorphology'\\n| 'feOffset'\\n| 'fePointLight'\\n| 'feSpecularLighting'\\n| 'feSpotLight'\\n| 'feTile'\\n| 'feTurbulence'\\n| 'filter'\\n| 'foreignObject'\\n| 'g'\\n| 'image'\\n| 'line'\\n| 'linearGradient'\\n| 'marker'\\n| 'mask'\\n| 'metadata'\\n| 'mpath'\\n| 'path'\\n| 'pattern'\\n| 'polygon'\\n| 'polyline'\\n| 'radialGradient'\\n| 'rect'\\n| 'set'\\n| 'stop'\\n| 'switch'\\n| 'text'\\n| 'textPath'\\n| 'tspan'\\n| 'use'\\n| 'view'\\n| undefined\"\n    }\n  },\n  \"returnValue\": \"ReactElement | null\"\n}\n"
  },
  {
    "path": "docs/scripts/generateLlmTxt/__snapshots__/mdxToMarkdown.test.mjs.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`mdxToMarkdown > should transform Accordion MDX content to markdown with metadata 1`] = `\n\"# Accordion\n\n\n\nA high-quality, unstyled React accordion component that displays a set of collapsible panels with headings.\n\n## Demo\n\n### Tailwind\n\nThis example shows how to implement the component using Tailwind CSS.\n\n\\`\\`\\`tsx\n/* index.tsx */\nimport * as React from 'react';\nimport { Accordion } from '@base-ui/react/accordion';\n\nexport default function ExampleAccordion() {\n  return (\n    <Accordion.Root className=\"flex w-96 max-w-[calc(100vw-8rem)] flex-col justify-center text-gray-900\">\n      <Accordion.Item className=\"border-b border-gray-200\">\n        <Accordion.Header>\n          <Accordion.Trigger className=\"group relative flex w-full items-baseline justify-between gap-4 bg-gray-50 py-2 pr-1 pl-3 text-left font-normal hover:bg-gray-100 focus-visible:z-1 focus-visible:outline-2 focus-visible:outline-blue-800\">\n            What is Base UI?\n            <PlusIcon className=\"mr-2 size-3 shrink-0 transition-all ease-out group-data-[panel-open]:scale-110 group-data-[panel-open]:rotate-45\" />\n          </Accordion.Trigger>\n        </Accordion.Header>\n        <Accordion.Panel className=\"h-[var(--accordion-panel-height)] overflow-hidden text-base text-gray-600 transition-[height] ease-out data-[ending-style]:h-0 data-[starting-style]:h-0\">\n          <div className=\"p-3\">\n            Base UI is a library of high-quality unstyled React components for design systems and\n            web apps.\n          </div>\n        </Accordion.Panel>\n      </Accordion.Item>\n\n      <Accordion.Item className=\"border-b border-gray-200\">\n        <Accordion.Header>\n          <Accordion.Trigger className=\"group relative flex w-full items-baseline justify-between gap-4 bg-gray-50 py-2 pr-1 pl-3 text-left font-normal hover:bg-gray-100 focus-visible:z-1 focus-visible:outline-2 focus-visible:outline-blue-800\">\n            How do I get started?\n            <PlusIcon className=\"mr-2 size-3 shrink-0 transition-all ease-out group-data-[panel-open]:scale-110 group-data-[panel-open]:rotate-45\" />\n          </Accordion.Trigger>\n        </Accordion.Header>\n        <Accordion.Panel className=\"h-[var(--accordion-panel-height)] overflow-hidden text-base text-gray-600 transition-[height] ease-out data-[ending-style]:h-0 data-[starting-style]:h-0\">\n          <div className=\"p-3\">\n            Head to the “Quick start” guide in the docs. If you’ve used unstyled libraries before,\n            you’ll feel at home.\n          </div>\n        </Accordion.Panel>\n      </Accordion.Item>\n\n      <Accordion.Item className=\"border-b border-gray-200\">\n        <Accordion.Header>\n          <Accordion.Trigger className=\"group relative flex w-full items-baseline justify-between gap-4 bg-gray-50 py-2 pr-1 pl-3 text-left font-normal hover:bg-gray-100 focus-visible:z-1 focus-visible:outline-2 focus-visible:outline-blue-800\">\n            Can I use it for my project?\n            <PlusIcon className=\"mr-2 size-3 shrink-0 transition-all ease-out group-data-[panel-open]:scale-110 group-data-[panel-open]:rotate-45\" />\n          </Accordion.Trigger>\n        </Accordion.Header>\n        <Accordion.Panel className=\"h-[var(--accordion-panel-height)] overflow-hidden text-base text-gray-600 transition-[height] ease-out data-[ending-style]:h-0 data-[starting-style]:h-0\">\n          <div className=\"p-3\">Of course! Base UI is free and open source.</div>\n        </Accordion.Panel>\n      </Accordion.Item>\n    </Accordion.Root>\n  );\n}\n\nfunction PlusIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg viewBox=\"0 0 12 12\" fill=\"currentcolor\" {...props}>\n      <path d=\"M6.75 0H5.25V5.25H0V6.75L5.25 6.75V12H6.75V6.75L12 6.75V5.25H6.75V0Z\" />\n    </svg>\n  );\n}\n\n\\`\\`\\`\n\n### CSS Modules\n\nThis example shows how to implement the component using CSS Modules.\n\n\\`\\`\\`css\n/* index.module.css */\n.Accordion {\n  box-sizing: border-box;\n  display: flex;\n  width: 24rem;\n  max-width: calc(100vw - 8rem);\n  flex-direction: column;\n  justify-content: center;\n  color: var(--color-gray-900);\n}\n\n.Item {\n  border-bottom: 1px solid var(--color-gray-200);\n}\n\n.Header {\n  margin: 0;\n}\n\n.Trigger {\n  box-sizing: border-box;\n  position: relative;\n  display: flex;\n  width: 100%;\n  gap: 1rem;\n  align-items: baseline;\n  justify-content: space-between;\n  padding-block: 0.5rem;\n  padding-inline: 0.75rem 0.25rem;\n  color: var(--color-gray-900);\n  font-family: inherit;\n  font-weight: 400;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  background: var(--color-gray-50);\n  border: none;\n  outline: none;\n  text-align: left;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    z-index: 1;\n  }\n}\n\n.TriggerIcon {\n  box-sizing: border-box;\n  flex-shrink: 0;\n  width: 0.75rem;\n  height: 0.75rem;\n  margin-right: 0.5rem;\n  transition: transform 150ms ease-out;\n\n  [data-panel-open] > & {\n    transform: rotate(45deg) scale(1.1);\n  }\n}\n\n.Panel {\n  box-sizing: border-box;\n  height: var(--accordion-panel-height);\n  overflow: hidden;\n  color: var(--color-gray-600);\n  font-size: 1rem;\n  line-height: 1.5rem;\n  transition: height 150ms ease-out;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    height: 0;\n  }\n}\n\n.Content {\n  padding: 0.75rem;\n}\n\n\\`\\`\\`\n\n\\`\\`\\`tsx\n/* index.tsx */\nimport * as React from 'react';\nimport { Accordion } from '@base-ui/react/accordion';\nimport styles from './index.module.css';\n\nexport default function ExampleAccordion() {\n  return (\n    <Accordion.Root className={styles.Accordion}>\n      <Accordion.Item className={styles.Item}>\n        <Accordion.Header className={styles.Header}>\n          <Accordion.Trigger className={styles.Trigger}>\n            What is Base UI?\n            <PlusIcon className={styles.TriggerIcon} />\n          </Accordion.Trigger>\n        </Accordion.Header>\n        <Accordion.Panel className={styles.Panel}>\n          <div className={styles.Content}>\n            Base UI is a library of high-quality unstyled React components for design systems and\n            web apps.\n          </div>\n        </Accordion.Panel>\n      </Accordion.Item>\n\n      <Accordion.Item className={styles.Item}>\n        <Accordion.Header className={styles.Header}>\n          <Accordion.Trigger className={styles.Trigger}>\n            How do I get started?\n            <PlusIcon className={styles.TriggerIcon} />\n          </Accordion.Trigger>\n        </Accordion.Header>\n        <Accordion.Panel className={styles.Panel}>\n          <div className={styles.Content}>\n            Head to the “Quick start” guide in the docs. If you’ve used unstyled libraries before,\n            you’ll feel at home.\n          </div>\n        </Accordion.Panel>\n      </Accordion.Item>\n\n      <Accordion.Item className={styles.Item}>\n        <Accordion.Header className={styles.Header}>\n          <Accordion.Trigger className={styles.Trigger}>\n            Can I use it for my project?\n            <PlusIcon className={styles.TriggerIcon} />\n          </Accordion.Trigger>\n        </Accordion.Header>\n        <Accordion.Panel className={styles.Panel}>\n          <div className={styles.Content}>Of course! Base UI is free and open source.</div>\n        </Accordion.Panel>\n      </Accordion.Item>\n    </Accordion.Root>\n  );\n}\n\nfunction PlusIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg viewBox=\"0 0 12 12\" fill=\"currentcolor\" {...props}>\n      <path d=\"M6.75 0H5.25V5.25H0V6.75L5.25 6.75V12H6.75V6.75L12 6.75V5.25H6.75V0Z\" />\n    </svg>\n  );\n}\n\n\\`\\`\\`\n\n## Anatomy\n\nImport the component and assemble its parts:\n\n\\`\\`\\`jsx title=\"Anatomy\"\nimport { Accordion } from '@base-ui/react/accordion';\n\n<Accordion.Root>\n  <Accordion.Item>\n    <Accordion.Header>\n      <Accordion.Trigger />\n    </Accordion.Header>\n    <Accordion.Panel />\n  </Accordion.Item>\n</Accordion.Root>;\n\\`\\`\\`\n\n## Examples\n\n### Open multiple panels\n\nYou can set up the accordion to allow multiple panels to be open at the same time using the \\`multiple\\` prop.\n\n## Demo\n\n### Tailwind\n\nThis example shows how to implement the component using Tailwind CSS.\n\n\\`\\`\\`tsx\n/* index.tsx */\nimport * as React from 'react';\nimport { Accordion } from '@base-ui/react/accordion';\n\nexport default function ExampleAccordion() {\n  return (\n    <Accordion.Root\n      multiple\n      className=\"flex w-96 max-w-[calc(100vw-8rem)] flex-col justify-center text-gray-900\"\n    >\n      <Accordion.Item className=\"border-b border-gray-200\">\n        <Accordion.Header>\n          <Accordion.Trigger className=\"group relative flex w-full items-baseline justify-between gap-4 bg-gray-50 py-2 pr-1 pl-3 text-left font-normal hover:bg-gray-100 focus-visible:z-1 focus-visible:outline-2 focus-visible:outline-blue-800\">\n            What is Base UI?\n            <PlusIcon className=\"mr-2 size-3 shrink-0 transition-all ease-out group-data-[panel-open]:scale-110 group-data-[panel-open]:rotate-45\" />\n          </Accordion.Trigger>\n        </Accordion.Header>\n        <Accordion.Panel className=\"h-[var(--accordion-panel-height)] overflow-hidden text-base text-gray-600 transition-[height] ease-out data-[ending-style]:h-0 data-[starting-style]:h-0\">\n          <div className=\"p-3\">\n            Base UI is a library of high-quality unstyled React components for design systems and\n            web apps.\n          </div>\n        </Accordion.Panel>\n      </Accordion.Item>\n\n      <Accordion.Item className=\"border-b border-gray-200\">\n        <Accordion.Header>\n          <Accordion.Trigger className=\"group relative flex w-full items-baseline justify-between gap-4 bg-gray-50 py-2 pr-1 pl-3 text-left font-normal hover:bg-gray-100 focus-visible:z-1 focus-visible:outline-2 focus-visible:outline-blue-800\">\n            How do I get started?\n            <PlusIcon className=\"mr-2 size-3 shrink-0 transition-all ease-out group-data-[panel-open]:scale-110 group-data-[panel-open]:rotate-45\" />\n          </Accordion.Trigger>\n        </Accordion.Header>\n        <Accordion.Panel className=\"h-[var(--accordion-panel-height)] overflow-hidden text-base text-gray-600 transition-[height] ease-out data-[ending-style]:h-0 data-[starting-style]:h-0\">\n          <div className=\"p-3\">\n            Head to the “Quick start” guide in the docs. If you’ve used unstyled libraries before,\n            you’ll feel at home.\n          </div>\n        </Accordion.Panel>\n      </Accordion.Item>\n\n      <Accordion.Item className=\"border-b border-gray-200\">\n        <Accordion.Header>\n          <Accordion.Trigger className=\"group relative flex w-full items-baseline justify-between gap-4 bg-gray-50 py-2 pr-1 pl-3 text-left font-normal hover:bg-gray-100 focus-visible:z-1 focus-visible:outline-2 focus-visible:outline-blue-800\">\n            Can I use it for my project?\n            <PlusIcon className=\"mr-2 size-3 shrink-0 transition-all ease-out group-data-[panel-open]:scale-110 group-data-[panel-open]:rotate-45\" />\n          </Accordion.Trigger>\n        </Accordion.Header>\n        <Accordion.Panel className=\"h-[var(--accordion-panel-height)] overflow-hidden text-base text-gray-600 transition-[height] ease-out data-[ending-style]:h-0 data-[starting-style]:h-0\">\n          <div className=\"p-3\">Of course! Base UI is free and open source.</div>\n        </Accordion.Panel>\n      </Accordion.Item>\n    </Accordion.Root>\n  );\n}\n\nfunction PlusIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg viewBox=\"0 0 12 12\" fill=\"currentcolor\" {...props}>\n      <path d=\"M6.75 0H5.25V5.25H0V6.75L5.25 6.75V12H6.75V6.75L12 6.75V5.25H6.75V0Z\" />\n    </svg>\n  );\n}\n\n\\`\\`\\`\n\n### CSS Modules\n\nThis example shows how to implement the component using CSS Modules.\n\n\\`\\`\\`css\n/* index.module.css */\n.Accordion {\n  box-sizing: border-box;\n  display: flex;\n  width: 24rem;\n  max-width: calc(100vw - 8rem);\n  flex-direction: column;\n  justify-content: center;\n  color: var(--color-gray-900);\n}\n\n.Item {\n  border-bottom: 1px solid var(--color-gray-200);\n}\n\n.Header {\n  margin: 0;\n}\n\n.Trigger {\n  box-sizing: border-box;\n  position: relative;\n  display: flex;\n  width: 100%;\n  gap: 1rem;\n  align-items: baseline;\n  justify-content: space-between;\n  padding-block: 0.5rem;\n  padding-inline: 0.75rem 0.25rem;\n  color: var(--color-gray-900);\n  font-family: inherit;\n  font-weight: 400;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  background: var(--color-gray-50);\n  border: none;\n  outline: none;\n  text-align: left;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    z-index: 1;\n  }\n}\n\n.TriggerIcon {\n  box-sizing: border-box;\n  flex-shrink: 0;\n  width: 0.75rem;\n  height: 0.75rem;\n  margin-right: 0.5rem;\n  transition: transform 150ms ease-out;\n\n  [data-panel-open] > & {\n    transform: rotate(45deg) scale(1.1);\n  }\n}\n\n.Panel {\n  box-sizing: border-box;\n  height: var(--accordion-panel-height);\n  overflow: hidden;\n  color: var(--color-gray-600);\n  font-size: 1rem;\n  line-height: 1.5rem;\n  transition: height 150ms ease-out;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    height: 0;\n  }\n}\n\n.Content {\n  padding: 0.75rem;\n}\n\n\\`\\`\\`\n\n\\`\\`\\`tsx\n/* index.tsx */\nimport * as React from 'react';\nimport { Accordion } from '@base-ui/react/accordion';\nimport styles from './index.module.css';\n\nexport default function ExampleAccordion() {\n  return (\n    <Accordion.Root className={styles.Accordion} multiple>\n      <Accordion.Item className={styles.Item}>\n        <Accordion.Header className={styles.Header}>\n          <Accordion.Trigger className={styles.Trigger}>\n            What is Base UI?\n            <PlusIcon className={styles.TriggerIcon} />\n          </Accordion.Trigger>\n        </Accordion.Header>\n        <Accordion.Panel className={styles.Panel}>\n          <div className={styles.Content}>\n            Base UI is a library of high-quality unstyled React components for design systems and\n            web apps.\n          </div>\n        </Accordion.Panel>\n      </Accordion.Item>\n\n      <Accordion.Item className={styles.Item}>\n        <Accordion.Header className={styles.Header}>\n          <Accordion.Trigger className={styles.Trigger}>\n            How do I get started?\n            <PlusIcon className={styles.TriggerIcon} />\n          </Accordion.Trigger>\n        </Accordion.Header>\n        <Accordion.Panel className={styles.Panel}>\n          <div className={styles.Content}>\n            Head to the “Quick start” guide in the docs. If you’ve used unstyled libraries before,\n            you’ll feel at home.\n          </div>\n        </Accordion.Panel>\n      </Accordion.Item>\n\n      <Accordion.Item className={styles.Item}>\n        <Accordion.Header className={styles.Header}>\n          <Accordion.Trigger className={styles.Trigger}>\n            Can I use it for my project?\n            <PlusIcon className={styles.TriggerIcon} />\n          </Accordion.Trigger>\n        </Accordion.Header>\n        <Accordion.Panel className={styles.Panel}>\n          <div className={styles.Content}>Of course! Base UI is free and open source.</div>\n        </Accordion.Panel>\n      </Accordion.Item>\n    </Accordion.Root>\n  );\n}\n\nfunction PlusIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg viewBox=\"0 0 12 12\" fill=\"currentcolor\" {...props}>\n      <path d=\"M6.75 0H5.25V5.25H0V6.75L5.25 6.75V12H6.75V6.75L12 6.75V5.25H6.75V0Z\" />\n    </svg>\n  );\n}\n\n\\`\\`\\`\n\n## API reference\n\n### Root\n\nGroups all parts of the accordion.\nRenders a \\`<div>\\` element.\n\n**Root Props:**\n\n| Prop             | Type                                                                                       | Default      | Description                                                                                                                                                                                                |\n| :--------------- | :----------------------------------------------------------------------------------------- | :----------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| defaultValue     | \\`Value[]\\`                                                                                  | -            | The uncontrolled value of the item(s) that should be initially expanded.To render a controlled accordion, use the \\`value\\` prop instead.                                                                    |\n| value            | \\`Value[]\\`                                                                                  | -            | The controlled value of the item(s) that should be expanded.To render an uncontrolled accordion, use the \\`defaultValue\\` prop instead.                                                                      |\n| onValueChange    | \\`((value: Value[], eventDetails: Accordion.Root.ChangeEventDetails) => void)\\`              | -            | Event handler called when an accordion item is expanded or collapsed.&#xA;Provides the new value as an argument.                                                                                           |\n| hiddenUntilFound | \\`boolean\\`                                                                                  | \\`false\\`      | Allows the browser’s built-in page search to find and expand the panel contents.Overrides the \\`keepMounted\\` prop and uses \\`hidden=\"until-found\"\\`&#xA;to hide the element without removing it from the DOM. |\n| loopFocus        | \\`boolean\\`                                                                                  | \\`true\\`       | Whether to loop keyboard focus back to the first item&#xA;when the end of the list is reached while using the arrow keys.                                                                                  |\n| multiple         | \\`boolean\\`                                                                                  | \\`false\\`      | Whether multiple items can be open at the same time.                                                                                                                                                       |\n| disabled         | \\`boolean\\`                                                                                  | \\`false\\`      | Whether the component should ignore user interaction.                                                                                                                                                      |\n| orientation      | \\`Orientation\\`                                                                              | \\`'vertical'\\` | The visual orientation of the accordion.&#xA;Controls whether roving focus uses left/right or up/down arrow keys.                                                                                          |\n| className        | \\`string \\\\| ((state: Accordion.Root.State<Value>) => string \\\\| undefined)\\`                  | -            | CSS class applied to the element, or a function that&#xA;returns a class based on the component’s state.                                                                                                   |\n| style            | \\`CSSProperties \\\\| ((state: Accordion.Root.State<Value>) => CSSProperties \\\\| undefined)\\`    | -            | -                                                                                                                                                                                                          |\n| keepMounted      | \\`boolean\\`                                                                                  | \\`false\\`      | Whether to keep the element in the DOM while the panel is closed.&#xA;This prop is ignored when \\`hiddenUntilFound\\` is used.                                                                                |\n| render           | \\`ReactElement \\\\| ((props: HTMLProps, state: Accordion.Root.State<Value>) => ReactElement)\\` | -            | Allows you to replace the component’s HTML element&#xA;with a different tag, or compose it with another component.Accepts a \\`ReactElement\\` or a function that returns the element to render.               |\n\n**Root Data Attributes:**\n\n| Attribute        | Type | Description                                 |\n| :--------------- | :--- | :------------------------------------------ |\n| data-orientation | -    | Indicates the orientation of the accordion. |\n| data-disabled    | -    | Present when the accordion is disabled.     |\n\n\n\n### Item\n\nGroups an accordion header with the corresponding panel.\nRenders a \\`<div>\\` element.\n\n**Item Props:**\n\n| Prop         | Type                                                                                | Default | Description                                                                                                                                                                                                                 |\n| :----------- | :---------------------------------------------------------------------------------- | :------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| value        | \\`any\\`                                                                               | -       | A unique value that identifies this accordion item.&#xA;If no value is provided, a unique ID will be generated automatically.&#xA;Use when controlling the accordion programmatically, or to set an initial&#xA;open state. |\n| onOpenChange | \\`((open: boolean, eventDetails: Accordion.Item.ChangeEventDetails) => void)\\`        | -       | Event handler called when the panel is opened or closed.                                                                                                                                                                    |\n| disabled     | \\`boolean\\`                                                                           | \\`false\\` | Whether the component should ignore user interaction.                                                                                                                                                                       |\n| className    | \\`string \\\\| ((state: Accordion.Item.State) => string \\\\| undefined)\\`                  | -       | CSS class applied to the element, or a function that&#xA;returns a class based on the component’s state.                                                                                                                    |\n| style        | \\`CSSProperties \\\\| ((state: Accordion.Item.State) => CSSProperties \\\\| undefined)\\`    | -       | -                                                                                                                                                                                                                           |\n| render       | \\`ReactElement \\\\| ((props: HTMLProps, state: Accordion.Item.State) => ReactElement)\\` | -       | Allows you to replace the component’s HTML element&#xA;with a different tag, or compose it with another component.Accepts a \\`ReactElement\\` or a function that returns the element to render.                                |\n\n**Item Data Attributes:**\n\n| Attribute     | Type     | Description                                  |\n| :------------ | :------- | :------------------------------------------- |\n| data-open     | -        | Present when the accordion item is open.     |\n| data-disabled | -        | Present when the accordion item is disabled. |\n| data-index    | \\`number\\` | Indicates the index of the accordion item.   |\n\n\n\n### Header\n\nA heading that labels the corresponding panel.\nRenders an \\`<h3>\\` element.\n\n**Header Props:**\n\n| Prop      | Type                                                                                  | Default | Description                                                                                                                                                                                  |\n| :-------- | :------------------------------------------------------------------------------------ | :------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| className | \\`string \\\\| ((state: Accordion.Header.State) => string \\\\| undefined)\\`                  | -       | CSS class applied to the element, or a function that&#xA;returns a class based on the component’s state.                                                                                     |\n| style     | \\`CSSProperties \\\\| ((state: Accordion.Header.State) => CSSProperties \\\\| undefined)\\`    | -       | -                                                                                                                                                                                            |\n| render    | \\`ReactElement \\\\| ((props: HTMLProps, state: Accordion.Header.State) => ReactElement)\\` | -       | Allows you to replace the component’s HTML element&#xA;with a different tag, or compose it with another component.Accepts a \\`ReactElement\\` or a function that returns the element to render. |\n\n**Header Data Attributes:**\n\n| Attribute     | Type     | Description                                  |\n| :------------ | :------- | :------------------------------------------- |\n| data-open     | -        | Present when the accordion item is open.     |\n| data-disabled | -        | Present when the accordion item is disabled. |\n| data-index    | \\`number\\` | Indicates the index of the accordion item.   |\n\n\n\n### Trigger\n\nA button that opens and closes the corresponding panel.\nRenders a \\`<button>\\` element.\n\n**Trigger Props:**\n\n| Prop         | Type                                                                                   | Default | Description                                                                                                                                                                                  |\n| :----------- | :------------------------------------------------------------------------------------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| nativeButton | \\`boolean\\`                                                                              | \\`true\\`  | Whether the component renders a native \\`<button>\\` element when replacing it&#xA;via the \\`render\\` prop.&#xA;Set to \\`false\\` if the rendered element is not a button (e.g. \\`<div>\\`).            |\n| className    | \\`string \\\\| ((state: Accordion.Trigger.State) => string \\\\| undefined)\\`                  | -       | CSS class applied to the element, or a function that&#xA;returns a class based on the component’s state.                                                                                     |\n| style        | \\`CSSProperties \\\\| ((state: Accordion.Trigger.State) => CSSProperties \\\\| undefined)\\`    | -       | -                                                                                                                                                                                            |\n| render       | \\`ReactElement \\\\| ((props: HTMLProps, state: Accordion.Trigger.State) => ReactElement)\\` | -       | Allows you to replace the component’s HTML element&#xA;with a different tag, or compose it with another component.Accepts a \\`ReactElement\\` or a function that returns the element to render. |\n\n**Trigger Data Attributes:**\n\n| Attribute       | Type | Description                                  |\n| :-------------- | :--- | :------------------------------------------- |\n| data-panel-open | -    | Present when the accordion panel is open.    |\n| data-disabled   | -    | Present when the accordion item is disabled. |\n\n\n\n### Panel\n\nA collapsible panel with the accordion item contents.\nRenders a \\`<div>\\` element.\n\n**Panel Props:**\n\n| Prop             | Type                                                                                 | Default | Description                                                                                                                                                                                                |\n| :--------------- | :----------------------------------------------------------------------------------- | :------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| hiddenUntilFound | \\`boolean\\`                                                                            | \\`false\\` | Allows the browser’s built-in page search to find and expand the panel contents.Overrides the \\`keepMounted\\` prop and uses \\`hidden=\"until-found\"\\`&#xA;to hide the element without removing it from the DOM. |\n| className        | \\`string \\\\| ((state: Accordion.Panel.State) => string \\\\| undefined)\\`                  | -       | CSS class applied to the element, or a function that&#xA;returns a class based on the component’s state.                                                                                                   |\n| style            | \\`CSSProperties \\\\| ((state: Accordion.Panel.State) => CSSProperties \\\\| undefined)\\`    | -       | -                                                                                                                                                                                                          |\n| keepMounted      | \\`boolean\\`                                                                            | \\`false\\` | Whether to keep the element in the DOM while the panel is closed.&#xA;This prop is ignored when \\`hiddenUntilFound\\` is used.                                                                                |\n| render           | \\`ReactElement \\\\| ((props: HTMLProps, state: Accordion.Panel.State) => ReactElement)\\` | -       | Allows you to replace the component’s HTML element&#xA;with a different tag, or compose it with another component.Accepts a \\`ReactElement\\` or a function that returns the element to render.               |\n\n**Panel Data Attributes:**\n\n| Attribute           | Type     | Description                                  |\n| :------------------ | :------- | :------------------------------------------- |\n| data-open           | -        | Present when the accordion panel is open.    |\n| data-orientation    | -        | Indicates the orientation of the accordion.  |\n| data-disabled       | -        | Present when the accordion item is disabled. |\n| data-index          | \\`number\\` | Indicates the index of the accordion item.   |\n| data-starting-style | -        | Present when the panel is animating in.      |\n| data-ending-style   | -        | Present when the panel is animating out.     |\n\n**Panel CSS Variables:**\n\n| Variable                 | Type     | Default | Description                   |\n| :----------------------- | :------- | :------ | :---------------------------- |\n| --accordion-panel-height | \\`number\\` | -       | The accordion panel's height. |\n| --accordion-panel-width  | \\`number\\` | -       | The accordion panel's width.  |\n\"\n`;\n\nexports[`mdxToMarkdown > should transform Direction Provider MDX content to markdown with metadata 1`] = `\n\"# Direction Provider\n\n\n\nA direction provider component that enables RTL behavior for Base UI components.\n\n## Demo\n\n### Tailwind\n\nThis example shows how to implement the component using Tailwind CSS.\n\n\\`\\`\\`tsx\n/* index.tsx */\nimport { Slider } from '@base-ui/react/slider';\nimport { DirectionProvider } from '@base-ui/react/direction-provider';\n\nexport default function ExampleDirectionProvider() {\n  return (\n    <div dir=\"rtl\">\n      <DirectionProvider direction=\"rtl\">\n        <Slider.Root defaultValue={25}>\n          <Slider.Control className=\"flex w-56 items-center py-3\">\n            <Slider.Track className=\"relative h-1 w-full rounded-sm bg-gray-200 shadow-[inset_0_0_0_1px] shadow-gray-200\">\n              <Slider.Indicator className=\"rounded-sm bg-gray-700\" />\n              <Slider.Thumb className=\"size-4 rounded-full bg-white outline-1 outline-gray-300 has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-blue-800\" />\n            </Slider.Track>\n          </Slider.Control>\n        </Slider.Root>\n      </DirectionProvider>\n    </div>\n  );\n}\n\n\\`\\`\\`\n\n### CSS Modules\n\nThis example shows how to implement the component using CSS Modules.\n\n\\`\\`\\`css\n/* index.module.css */\n.Control {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  width: 14rem;\n  padding-block: 0.75rem;\n}\n\n.Track {\n  width: 100%;\n  background-color: var(--color-gray-200);\n  box-shadow: inset 0 0 0 1px var(--color-gray-200);\n  height: 0.25rem;\n  border-radius: 0.25rem;\n  position: relative;\n}\n\n.Indicator {\n  border-radius: 0.25rem;\n  background-color: var(--color-gray-700);\n}\n\n.Thumb {\n  width: 1rem;\n  height: 1rem;\n  border-radius: 100%;\n  background-color: white;\n  outline: 1px solid var(--color-gray-300);\n\n  &:has(:focus-visible) {\n    outline: 2px solid var(--color-blue);\n  }\n}\n\n\\`\\`\\`\n\n\\`\\`\\`tsx\n/* index.tsx */\nimport { DirectionProvider } from '@base-ui/react/direction-provider';\nimport { Slider } from '@base-ui/react/slider';\nimport styles from './index.module.css';\n\nexport default function ExampleDirectionProvider() {\n  return (\n    <div dir=\"rtl\">\n      <DirectionProvider direction=\"rtl\">\n        <Slider.Root defaultValue={25}>\n          <Slider.Control className={styles.Control}>\n            <Slider.Track className={styles.Track}>\n              <Slider.Indicator className={styles.Indicator} />\n              <Slider.Thumb className={styles.Thumb} />\n            </Slider.Track>\n          </Slider.Control>\n        </Slider.Root>\n      </DirectionProvider>\n    </div>\n  );\n}\n\n\\`\\`\\`\n\n## Anatomy\n\nImport the component and wrap it around your app:\n\n\\`\\`\\`jsx title=\"Anatomy\"\nimport { DirectionProvider } from '@base-ui/react/direction-provider';\n\n// prettier-ignore\n<DirectionProvider>\n  {/* Your app or a group of components */}\n</DirectionProvider>\n\\`\\`\\`\n\n\\`<DirectionProvider>\\` enables child Base UI components to adjust behavior based on RTL text direction, but does not affect HTML and CSS. The \\`dir=\"rtl\"\\` HTML attribute or \\`direction: rtl\\` CSS style must be set additionally by your own application code.\n\n## API reference\n\nEnables RTL behavior for Base UI components.\n\n**DirectionProvider Props:**\n\n| Prop      | Type            | Default | Description                       |\n| :-------- | :-------------- | :------ | :-------------------------------- |\n| direction | \\`TextDirection\\` | \\`'ltr'\\` | The reading direction of the text |\n| children  | \\`ReactNode\\`     | -       | -                                 |\n\n## useDirection\n\nUse this hook to read the current text direction. This is useful for wrapping portaled components that may be rendered outside your application root and are unaffected by the \\`dir\\` attribute set within.\n\n### Return value\n\n**Return Value:**\n\n| Property  | Type            | Description                 |\n| :-------- | :-------------- | :-------------------------- |\n| direction | \\`TextDirection\\` | The current text direction. |\n\"\n`;\n"
  },
  {
    "path": "docs/scripts/generateLlmTxt/demoProcessor.mjs",
    "content": "/**\n * demoProcessor.mjs - Process demo component directories\n *\n * This module handles loading and converting demo code examples\n * into markdown code blocks for documentation.\n */\n\nimport path from 'path';\nimport { pathToFileURL } from 'url';\nimport {\n  loadServerCodeMeta,\n  resolveModulePathWithFs,\n} from '@mui/internal-docs-infra/pipeline/loadServerCodeMeta';\nimport {\n  loadCodeVariant,\n  flattenCodeVariant,\n} from '@mui/internal-docs-infra/pipeline/loadCodeVariant';\nimport { loadServerSource } from '@mui/internal-docs-infra/pipeline/loadServerSource';\nimport * as mdx from './mdxNodeHelpers.mjs';\n\n/**\n * Transforms a Demo component into markdown code blocks\n * @param {string} mdxFilePath - Path to the MDX file containing the Demo component\n * @param {string} demoPath - Path to the demo directory\n * @returns {Promise<Array>} Array of markdown nodes to replace the Demo component\n */\nexport async function processDemo(mdxFilePath, demoPath) {\n  // Resolve demo path relative to the MDX file\n  const mdxDir = path.dirname(mdxFilePath);\n  demoPath = pathToFileURL(path.resolve(mdxDir, demoPath)).href;\n  const demoModule = await resolveModulePathWithFs(demoPath).catch((err) => {\n    throw new Error(`Failed to resolve demo module at \"${demoPath}\": ${err.message}`);\n  });\n  const demoModulePath = typeof demoModule === 'string' ? demoModule : demoModule.import;\n  const codeMeta = await loadServerCodeMeta(demoModulePath).catch((err) => {\n    throw new Error(`Failed to load code meta for demo at \"${demoModulePath}\": ${err.message}`);\n  });\n\n  // Define implementation types and their configurations\n  const implementationTypes = [\n    {\n      id: 'Tailwind',\n      title: 'Tailwind',\n      description: 'This example shows how to implement the component using Tailwind CSS.',\n    },\n    {\n      id: 'CssModules',\n      title: 'CSS Modules',\n      description: 'This example shows how to implement the component using CSS Modules.',\n      firstFile: 'index.module.css',\n    },\n  ];\n  const implementationTypesMap = implementationTypes.reduce((acc, impl) => {\n    acc[impl.id] = impl;\n    return acc;\n  }, {});\n\n  const availableImplementations = Object.keys(codeMeta);\n\n  // Throw error if no implementation types are found\n  if (availableImplementations.length === 0) {\n    throw new Error(\n      `No implementation types found in \"${demoModulePath}\". Expected one of: ${implementationTypes.map((t) => t.id).join(', ')}`,\n    );\n  }\n\n  const result = [];\n\n  // Add main Demo heading\n  result.push(mdx.heading(2, 'Demo'));\n\n  /**\n   * Process a specific implementation type\n   * @param {string} variantName - Name of the variant (e.g., 'tailwind', 'css-modules')\n   * @param {string | { url?: string } | undefined} variantCodeOrUrl\n   * @param {string} title - Title for the section heading\n   * @param {string} description - Description text for the section\n   * @param {string | undefined} firstFile - Optional filename to display first\n   */\n  async function processImplementation(\n    variantName,\n    variantCodeOrUrl,\n    title,\n    description,\n    firstFile,\n  ) {\n    const implementationResult = [];\n\n    if (!variantCodeOrUrl) {\n      throw new Error(`No code variant found for \"${variantName}\" in demo at \"${demoModulePath}\"`);\n    }\n\n    implementationResult.push(mdx.heading(3, title));\n    implementationResult.push(mdx.paragraph(description));\n\n    /** @type {string | undefined} */\n    let url;\n    if (typeof variantCodeOrUrl === 'string') {\n      url = variantCodeOrUrl;\n    } else {\n      url = variantCodeOrUrl.url;\n    }\n\n    const { code: variantCode } = await loadCodeVariant(url, variantName, variantCodeOrUrl, {\n      loadSource: loadServerSource,\n      disableParsing: true,\n    });\n\n    const flattenedFiles = flattenCodeVariant(variantCode);\n\n    const allFiles = Object.entries(flattenedFiles).map(([filePath, fileData]) => ({\n      fileName: filePath,\n      content: fileData.source,\n      extension: path.extname(filePath).slice(1),\n    }));\n\n    // Reorder files if firstFile is specified\n    let files = allFiles;\n    if (firstFile) {\n      const firstFileIndex = allFiles.findIndex((file) => file.fileName === firstFile);\n      if (firstFileIndex > 0) {\n        const [firstFileEntry] = allFiles.splice(firstFileIndex, 1);\n        files = [firstFileEntry, ...allFiles];\n      }\n    }\n\n    files.forEach(({ fileName, content, extension }) => {\n      const commentedContent = `/* ${fileName} */\\n${content}`;\n      implementationResult.push(mdx.code(commentedContent, extension));\n    });\n\n    return implementationResult;\n  }\n\n  // Process each available implementation type\n  const implementationResults = await Promise.all(\n    availableImplementations.map(async (implName) => {\n      const implMeta = implementationTypesMap[implName];\n      if (!implMeta) {\n        throw new Error(`Unknown implementation type \"${implName}\" in demo at \"${demoModulePath}\"`);\n      }\n\n      return [\n        implName,\n        await processImplementation(\n          implName,\n          codeMeta[implName],\n          implMeta.title,\n          implMeta.description,\n          implMeta.firstFile,\n        ),\n      ];\n    }),\n  );\n\n  const implementations = implementationResults.reduce((acc, [implName, implementationResult]) => {\n    acc[implName] = implementationResult;\n    return acc;\n  }, {});\n  implementationTypes.forEach(({ id }) => {\n    if (implementations[id]) {\n      result.push(...implementations[id]);\n    }\n  });\n\n  return result;\n}\n"
  },
  {
    "path": "docs/scripts/generateLlmTxt/index.mjs",
    "content": "#!/usr/bin/env node\n/* eslint-disable no-await-in-loop */\n/* eslint-disable no-console */\n\nimport fs from 'fs/promises';\nimport path from 'path';\nimport { globby } from 'globby';\nimport * as prettier from 'prettier';\nimport { unified } from 'unified';\nimport remarkParse from 'remark-parse';\nimport remarkGfm from 'remark-gfm';\nimport remarkStringify from 'remark-stringify';\nimport { visit } from 'unist-util-visit';\nimport { mdxToMarkdown } from './mdxToMarkdown.mjs';\nimport { resolveUrl, isAbsoluteUrl } from './resolver.mjs';\n\nconst PROJECT_ROOT = path.resolve(import.meta.dirname, '../..');\nconst MDX_SOURCE_DIR = path.join(PROJECT_ROOT, 'src/app/(docs)/react');\nconst OUTPUT_BASE_DIR = path.join(PROJECT_ROOT, 'public');\nconst OUTPUT_REACT_DIR = path.join(OUTPUT_BASE_DIR, 'react');\n\nconst NETLIFY_DEPLOYMENT_URL =\n  process.env.PULL_REQUEST === 'true' ? process.env.DEPLOY_PRIME_URL : process.env.URL;\n// Use the deployment URL if available, just root relative otherwise\nconst BASE_URL = NETLIFY_DEPLOYMENT_URL || '/';\n\n/**\n * Remark plugin to increment heading levels by a specified amount\n * @param {number} increment - Amount to increment each heading level\n */\nfunction incrementHeaders(increment = 1) {\n  return (tree) => {\n    visit(tree, 'heading', (node) => {\n      node.depth = Math.min(node.depth + increment, 6); // Cap at h6\n    });\n  };\n}\n\nfunction githubSlugify(text) {\n  return text\n    .trim()\n    .toLowerCase()\n    .replace(/[^\\w\\s-]/g, '') // remove punctuation except - and space\n    .replace(/\\s+/g, '-') // spaces to hyphens\n    .replace(/-+/g, '-') // collapse multiple hyphens\n    .replace(/^-+|-+$/g, ''); // trim leading/trailing hyphens\n}\n\nfunction resolveRelativeLinks({ base, metadataByUrl = new Map() }) {\n  return (tree) => {\n    visit(tree, 'link', (node) => {\n      if (!node.url || isAbsoluteUrl(node.url)) {\n        return;\n      }\n\n      const urlPath = node.url.endsWith('.md') ? node.url.slice(0, -3) : node.url;\n      const metadata = metadataByUrl.get(urlPath);\n\n      if (metadata) {\n        const hash = githubSlugify(metadata.title);\n        node.url = `#${hash}`;\n      } else {\n        node.url = resolveUrl(node.url, base);\n      }\n    });\n  };\n}\n\n/**\n * Function to process markdown and increment headers\n * @param {string} markdown - Markdown string to process\n * @param {number} increment - Amount to increment headers by\n * @returns {Promise<string>} - Processed markdown\n */\nasync function prepareForInlineMarkdown(markdown, increment, metadataByUrl) {\n  const result = await unified()\n    .use(remarkParse)\n    .use(remarkGfm)\n    .use(incrementHeaders, increment)\n    .use(resolveRelativeLinks, { base: BASE_URL, metadataByUrl })\n    .use(remarkStringify)\n    .process(markdown);\n  return String(result.value);\n}\n\n/**\n * Generate llms.txt and markdown files from MDX content\n */\nasync function generateLlmsTxt() {\n  console.log('Generating llms.txt and markdown files...');\n\n  try {\n    // Create output directories if they don't exist\n    await fs.mkdir(OUTPUT_BASE_DIR, { recursive: true });\n    await fs.mkdir(OUTPUT_REACT_DIR, { recursive: true });\n\n    const metadataByUrl = new Map();\n    // Store metadata for each section as objects indexed by ID\n    const metadataBySection = {\n      overview: {},\n      handbook: {},\n      components: {},\n      utils: {},\n    };\n\n    // Counter for total files processed\n    let totalFiles = 0;\n\n    const mdxFiles = await globby('**/*/page.mdx', {\n      cwd: MDX_SOURCE_DIR,\n      absolute: true,\n    });\n\n    const mdxFilesInfo = mdxFiles.map((mdxFile) => {\n      const relativePath = path.relative(MDX_SOURCE_DIR, mdxFile);\n      const dirPath = path.dirname(relativePath);\n      const urlPath = `/${path.join('react', dirPath).replace(/\\\\/g, '/')}`;\n      const outputFilePath = path.join(OUTPUT_REACT_DIR, `${dirPath}.md`);\n      return { urlPath, mdxFile, outputFilePath };\n    });\n\n    const urlsWithMdVersion = new Set(mdxFilesInfo.map((info) => info.urlPath));\n\n    // Process files from a specific section\n    const processSection = async (sectionName) => {\n      console.log(`Processing ${sectionName} section...`);\n\n      for (const { urlPath, mdxFile, outputFilePath } of mdxFilesInfo) {\n        if (!urlPath.startsWith(`/react/${sectionName}/`)) {\n          continue;\n        }\n\n        const mdxContent = await fs.readFile(mdxFile, 'utf-8');\n\n        const { markdown, title, subtitle, description } = await mdxToMarkdown(\n          mdxContent,\n          mdxFile,\n          { urlPath, urlsWithMdVersion },\n        );\n\n        // Create directories for output if needed\n        await fs.mkdir(path.dirname(outputFilePath), { recursive: true });\n\n        const frontmatter = [\n          '---',\n          `title: ${title || 'Untitled'}`,\n          subtitle ? `subtitle: ${subtitle}` : null,\n          description ? `description: ${description}` : null,\n          '---',\n        ]\n          .filter(Boolean)\n          .join('\\n');\n\n        // Create markdown content with frontmatter\n        let content = [frontmatter, '', markdown].join('\\n');\n\n        // Format markdown with frontmatter using prettier\n        const prettierOptions = await prettier.resolveConfig(outputFilePath);\n\n        content = await prettier.format(content, {\n          ...prettierOptions,\n          filepath: outputFilePath,\n          parser: 'markdown',\n        });\n\n        // Write formatted markdown file\n        await fs.writeFile(outputFilePath, content, 'utf-8');\n\n        // Extract the filename without extension to use as id\n        const fileId = path.basename(outputFilePath, '.md');\n\n        const pageMeta = {\n          id: fileId,\n          title: title || 'Untitled',\n          subtitle: subtitle || '',\n          description: description || '',\n          urlPath,\n          mdUrlPath: `${urlPath}.md`,\n          fullMarkdown: markdown,\n        };\n\n        // Store metadata for this file in the appropriate section\n        metadataBySection[sectionName][fileId] = pageMeta;\n        metadataByUrl.set(urlPath, pageMeta);\n\n        // Increment the counter\n        totalFiles += 1;\n\n        console.log(`Processed: ${mdxFile}`);\n      }\n    };\n\n    // Process each section\n    await processSection('overview');\n    await processSection('handbook');\n    await processSection('components');\n    await processSection('utils');\n\n    // Build shared preamble for both files\n    const preamble = [\n      '# Base UI',\n      '',\n      'This is the documentation for the `@base-ui/react` package.',\n      'It contains a collection of components and utilities for building user interfaces in React.',\n      'The library is designed to be composable and styling agnostic.',\n      'The Tailwind CSS examples are written for Tailwind CSS v4. If `package.json` uses Tailwind CSS v3, automatically convert unsupported styles to v3-compatible equivalents.',\n      '',\n    ];\n\n    // Page rendering functions - focused only on their unique logic\n    const renderPageAsLink = (page) => {\n      const resolvedUrl = resolveUrl(page.mdUrlPath, BASE_URL);\n      return [`- [${page.title}](${resolvedUrl}): ${page.description}`];\n    };\n    const renderPageAsInline = async (page) => {\n      const content = await prepareForInlineMarkdown(page.fullMarkdown, 2, metadataByUrl);\n      return [content];\n    };\n\n    // Define specific orders for sections\n    const overviewOrder = ['quick-start', 'accessibility', 'releases', 'about'];\n    const handbookOrder = ['styling', 'animation', 'composition'];\n    const componentsOrder = Object.keys(metadataBySection.components).sort();\n    const utilsOrder = Object.keys(metadataBySection.utils).sort();\n\n    // Helper function to map ordered IDs to their metadata objects\n    const mapOrderToMetadata = (orderArray, metadataObject) => {\n      const metadataList = Object.values(metadataObject);\n      const orderMap = new Map(orderArray.map((id, index) => [id, index]));\n      return metadataList.sort((a, b) => {\n        return (orderMap.get(a.id) ?? Infinity) - (orderMap.get(b.id) ?? Infinity);\n      });\n    };\n\n    // Create the file structure with all sections and pages in correct order\n    const structure = {\n      sections: [\n        {\n          title: 'Overview',\n          pages: mapOrderToMetadata(overviewOrder, metadataBySection.overview),\n        },\n        {\n          title: 'Handbook',\n          pages: mapOrderToMetadata(handbookOrder, metadataBySection.handbook),\n        },\n        {\n          title: 'Components',\n          pages: mapOrderToMetadata(componentsOrder, metadataBySection.components),\n        },\n        {\n          title: 'Utilities',\n          pages: mapOrderToMetadata(utilsOrder, metadataBySection.utils),\n        },\n      ],\n    };\n\n    const createFile = async (filename, pageRenderer) => {\n      // Generate sections with shared logic\n      const sections = [];\n\n      for (const section of structure.sections) {\n        if (section.pages.length === 0) {\n          continue;\n        }\n\n        const sectionContent = [`## ${section.title}`, ''];\n\n        // Use the page renderer for each page (handle async renderers)\n        for (const page of section.pages) {\n          const renderedPage = await pageRenderer(page);\n          sectionContent.push(...renderedPage);\n        }\n\n        sectionContent.push(''); // Add empty line after section\n        sections.push(...sectionContent);\n      }\n\n      let content = [...preamble, ...sections].join('\\n');\n\n      // Apply prettier formatting\n      const filePath = path.join(OUTPUT_BASE_DIR, filename);\n      const prettierOptions = await prettier.resolveConfig(filePath);\n\n      content = await prettier.format(content, {\n        ...prettierOptions,\n        filepath: filePath,\n        parser: 'markdown',\n      });\n\n      await fs.writeFile(filePath, content, 'utf-8');\n    };\n\n    // Generate both files in parallel\n    await Promise.all([\n      createFile('llms.txt', renderPageAsLink),\n      createFile('llms-full.txt', renderPageAsInline),\n    ]);\n\n    console.log(`Successfully generated ${totalFiles} markdown files, llms.txt, and llms-full.txt`);\n  } catch (error) {\n    console.error('Error generating llms.txt:', error);\n    process.exit(1);\n  }\n}\n\n// Run the generator\ngenerateLlmsTxt();\n"
  },
  {
    "path": "docs/scripts/generateLlmTxt/mdxNodeHelpers.mjs",
    "content": "/**\n * mdxNodeHelpers.mjs - Helper functions for creating MDX AST nodes\n *\n * This module provides utility functions to create nodes for MDX/Markdown\n * abstract syntax trees, making transformer code more readable and maintainable.\n */\n\n/**\n * Create a text node\n * @param {string} value - The text content\n * @returns {Object} A text node\n */\nexport function text(value) {\n  return {\n    type: 'text',\n    value: value || '',\n  };\n}\n\n/**\n * Helper to normalize children (handles string, node, or array)\n * @param {string|Object|Array} children - Child content\n * @returns {Array} Normalized array of nodes\n */\nfunction normalizeChildren(children) {\n  // Handle empty or undefined\n  if (!children) {\n    return [];\n  }\n\n  // Convert to array if not already\n  const childArray = Array.isArray(children) ? children : [children];\n\n  // Convert strings to text nodes\n  return childArray.map((child) => (typeof child === 'string' ? text(child) : child));\n}\n\n/**\n * Create a paragraph node\n * @param {string|Object|Array} children - Child node, string, or array of nodes/strings\n * @returns {Object} A paragraph node\n */\nexport function paragraph(children) {\n  return {\n    type: 'paragraph',\n    children: normalizeChildren(children),\n  };\n}\n\n/**\n * Create an emphasis (italic) node\n * @param {string|Object|Array} children - Child node, string, or array of nodes/strings\n * @returns {Object} An emphasis node\n */\nexport function emphasis(children) {\n  return {\n    type: 'emphasis',\n    children: normalizeChildren(children),\n  };\n}\n\n/**\n * Create a strong (bold) node\n * @param {string|Object|Array} children - Child node, string, or array of nodes/strings\n * @returns {Object} A strong node\n */\nexport function strong(children) {\n  return {\n    type: 'strong',\n    children: normalizeChildren(children),\n  };\n}\n\n/**\n * Create a heading node\n * @param {number} depth - Heading level (1-6)\n * @param {string|Object|Array} children - Child node, string, or array of nodes/strings\n * @returns {Object} A heading node\n */\nexport function heading(depth, children) {\n  return {\n    type: 'heading',\n    depth: depth || 1,\n    children: normalizeChildren(children),\n  };\n}\n\n/**\n * Create a code block node\n * @param {string} value - Code content\n * @param {string} lang - Language for syntax highlighting\n * @returns {Object} A code node\n */\nexport function code(value, lang) {\n  return {\n    type: 'code',\n    lang: lang || null,\n    value: value || '',\n  };\n}\n\n/**\n * Create an inline code node\n * @param {string} value - Code content\n * @returns {Object} An inline code node\n */\nexport function inlineCode(value) {\n  return {\n    type: 'inlineCode',\n    value: value || '',\n  };\n}\n\n/**\n * Creates a table cell node\n * @param {string|Object} content - Cell content\n * @returns {Object} Table cell node\n */\nfunction tableCell(content) {\n  return {\n    type: 'tableCell',\n    children: normalizeChildren(content),\n  };\n}\n\n/**\n * Creates a table row node\n * @param {Array<string|Object>} cells - Array of cell contents\n * @returns {Object} Table row node\n */\nfunction tableRow(cells) {\n  return {\n    type: 'tableRow',\n    children: cells.map((cell) => tableCell(cell)),\n  };\n}\n\n/**\n * Creates a markdown table node (GFM)\n * @param {Array<string|Object>} headers - Array of header strings or nodes\n * @param {Array<Array<string|Object>>} rows - Array of row data, each row is an array of cell content\n * @param {Array<string>} [alignment] - Optional array of alignments ('left', 'center', 'right') for each column\n * @returns {Object} A table node\n */\nexport function table(headers, rows, alignment = null) {\n  // Convert alignment strings to AST format\n  const align = headers.map((_, index) => {\n    if (!alignment || !alignment[index]) {\n      return null;\n    }\n\n    switch (alignment[index]) {\n      case 'center':\n        return 'center';\n      case 'right':\n        return 'right';\n      default:\n        return 'left';\n    }\n  });\n\n  // Create header row\n  const headerRow = tableRow(headers);\n\n  // Create data rows\n  const dataRows = rows.map((row) => tableRow(row));\n\n  // Return table node\n  return {\n    type: 'table',\n    align,\n    children: [headerRow, ...dataRows],\n  };\n}\n\n/**\n * Function to extract all text from a node and its children recursively\n * @param {Object} node - AST node\n * @returns {string} Extracted text content\n */\nexport function textContent(node) {\n  if (!node) {\n    return '';\n  }\n\n  if (typeof node === 'string') {\n    return node;\n  }\n\n  if (node.type === 'text') {\n    return node.value || '';\n  }\n\n  if (node.children && Array.isArray(node.children)) {\n    return node.children.map(textContent).join('');\n  }\n\n  return '';\n}\n"
  },
  {
    "path": "docs/scripts/generateLlmTxt/mdxToMarkdown.mjs",
    "content": "/**\n * mdxToMarkdown.mjs - Converts MDX content to Markdown\n *\n * This module transforms MDX content to Markdown format\n * using remark and remark-mdx plugin.\n */\n\nimport { unified } from 'unified';\nimport remarkParse from 'remark-parse';\nimport remarkMdx from 'remark-mdx';\nimport remarkGfm from 'remark-gfm';\nimport remarkStringify from 'remark-stringify';\nimport { visit } from 'unist-util-visit';\nimport { processReference } from './referenceProcessor.mjs';\nimport { processDemo } from './demoProcessor.mjs';\nimport { processPropsReferenceTable } from './propsReferenceTableProcessor.mjs';\nimport { processReleaseTimeline } from './releaseTimelineProcessor.mjs';\nimport * as mdx from './mdxNodeHelpers.mjs';\nimport { resolveMdLinks } from './resolver.mjs';\n\n/**\n * Plugin to extract metadata from the MDX content\n */\nfunction extractMetadata() {\n  return (tree, file) => {\n    // Initialize metadata in file.data\n    file.data.metadata = {\n      title: '',\n      subtitle: '',\n      description: '',\n    };\n\n    // Extract title from first h1\n    visit(tree, 'heading', (node) => {\n      if (node.depth === 1 && node.children?.[0]?.value) {\n        file.data.metadata.title = mdx.textContent(node);\n      }\n    });\n\n    // Extract from MDX components\n    visit(tree, ['mdxJsxFlowElement', 'mdxFlowExpression', 'mdxJsxTextElement'], (node) => {\n      // Extract from Subtitle component\n      if (node.name === 'Subtitle') {\n        file.data.metadata.subtitle = mdx.textContent(node);\n      }\n      // Extract from Meta component\n      else if (node.name === 'Meta') {\n        const nameAttr = node.attributes?.find(\n          (attr) => attr.name === 'name' && attr.value === 'description',\n        );\n        const contentAttr = node.attributes?.find((attr) => attr.name === 'content');\n\n        if (nameAttr && contentAttr) {\n          file.data.metadata.description = contentAttr.value;\n        }\n      }\n    });\n\n    return tree;\n  };\n}\n\n/**\n * Plugin to transform JSX elements to markdown or remove them from the tree\n */\nfunction transformJsx() {\n  return async (tree, file) => {\n    // First pass: collect all demos to process and handle other components\n    const demosToProcess = [];\n\n    visit(\n      tree,\n      [\n        'mdxJsxFlowElement',\n        'mdxjsEsm',\n        'mdxFlowExpression',\n        'mdxTextExpression',\n        'mdxJsxTextElement',\n      ],\n      (node, index, parent) => {\n        if (node.type === 'mdxjsEsm') {\n          if (node.data.estree.type === 'Program') {\n            const estree = node.data.estree;\n            if (estree.body[0].type === 'ImportDeclaration') {\n              const importPath = estree.body[0].source.value;\n              // Only collect demo imports (those starting with ./demos/)\n              if (importPath.startsWith('./demos/')) {\n                demosToProcess.push({\n                  index,\n                  parent,\n                  importPath,\n                });\n              } else {\n                // Remove non-demo imports (e.g., component imports used only in JSX)\n                parent.children.splice(index, 1);\n                return [visit.SKIP, index];\n              }\n              return visit.CONTINUE;\n            }\n            if (estree.body[0].type === 'ExportNamedDeclaration') {\n              // Check if this is a metadata export\n              const declaration = estree.body[0].declaration;\n              if (\n                declaration?.type === 'VariableDeclaration' &&\n                declaration.declarations?.[0]?.id?.name === 'metadata'\n              ) {\n                // Remove metadata export statements\n                parent.children.splice(index, 1);\n                return [visit.SKIP, index];\n              }\n            }\n          }\n        }\n\n        if (node.name?.startsWith('Demo')) {\n          // Remove Demo components - they are handled by the import statement\n          parent.children.splice(index, 1);\n          return [visit.SKIP, index];\n        }\n\n        // Process different component types\n        switch (node.name) {\n          case 'Reference': {\n            // Process the reference component using our dedicated processor\n            const tables = processReference(node, parent, index);\n\n            // Replace the reference component with the generated tables\n            parent.children.splice(index, 1, ...tables);\n\n            return visit.CONTINUE;\n          }\n\n          case 'PropsReferenceTable': {\n            // Process the PropsReferenceTable component using our dedicated processor\n            const tables = processPropsReferenceTable(node);\n\n            // Replace the PropsReferenceTable component with the generated tables\n            parent.children.splice(index, 1, ...tables);\n\n            return visit.CONTINUE;\n          }\n\n          case 'Subtitle': {\n            parent.children.splice(index, 1);\n            return visit.CONTINUE;\n          }\n\n          case 'ReleaseTimeline': {\n            const releaseNodes = processReleaseTimeline();\n            parent.children.splice(index, 1, ...releaseNodes);\n            return visit.CONTINUE;\n          }\n\n          case 'Meta': {\n            // Check if it's a description meta tag\n            const nameAttr = node.attributes?.find(\n              (attr) => attr.name === 'name' && attr.value === 'description',\n            );\n            const contentAttr = node.attributes?.find((attr) => attr.name === 'content');\n\n            if (nameAttr && contentAttr && contentAttr.value) {\n              // Replace with a paragraph containing the description\n              parent.children.splice(index, 1, mdx.paragraph(contentAttr.value));\n              return visit.CONTINUE;\n            }\n\n            // Remove other Meta tags\n            parent.children.splice(index, 1);\n            return [visit.SKIP, index];\n          }\n\n          case 'a':\n          case 'abbr':\n          case 'b':\n          case 'br':\n          case 'code':\n          case 'del':\n          case 'em':\n          case 'i':\n          case 'img':\n          case 'kbd':\n          case 'mark':\n          case 's':\n          case 'span':\n          case 'strong':\n          case 'sub':\n          case 'sup':\n          case 'time': {\n            // Support some HTML elements from GitHub flavored markdown\n            return visit.CONTINUE;\n          }\n\n          case 'InstallationBlock': {\n            const pkg =\n              node.attributes?.find((attr) => attr.name === 'package')?.value || '@base-ui/react';\n\n            const nodes = [\n              mdx.heading(3, 'pnpm'),\n              mdx.code(`pnpm add ${pkg}`, 'bash'),\n\n              mdx.heading(3, 'npm'),\n              mdx.code(`npm install ${pkg}`, 'bash'),\n\n              mdx.heading(3, 'yarn'),\n              mdx.code(`yarn add ${pkg}`, 'bash'),\n\n              mdx.heading(3, 'bun'),\n              mdx.code(`bun add ${pkg}`, 'bash'),\n            ];\n\n            parent.children.splice(index, 1, ...nodes);\n            return visit.CONTINUE;\n          }\n\n          case 'link': {\n            // Ignore some hidden elements\n            parent.children.splice(index, 1);\n            return [visit.SKIP, index];\n          }\n\n          default: {\n            if (node.name) {\n              throw new Error(`Unknown component: ${node.name}`);\n            }\n            return visit.CONTINUE;\n          }\n        }\n      },\n    );\n\n    // Process all demos in parallel\n    const demoResults = await Promise.all(\n      demosToProcess.map(async ({ importPath }) => processDemo(file.path || '', importPath)),\n    );\n\n    // Replace demo imports with their processed content (in reverse order to avoid index shifting)\n    for (let i = demosToProcess.length - 1; i >= 0; i -= 1) {\n      const { index, parent } = demosToProcess[i];\n      parent.children.splice(index, 1, ...demoResults[i]);\n    }\n\n    return tree;\n  };\n}\n\n/**\n * Converts MDX content to markdown and extracts metadata\n * @param {string} mdxContent - The MDX content to convert\n * @param {string} mdxFilePath - Optional path to the MDX file for context\n * @returns {Promise<Object>} An object containing the markdown and metadata\n */\nexport async function mdxToMarkdown(mdxContent, mdxFilePath, { urlPath, urlsWithMdVersion } = {}) {\n  // Process the MDX content and include file path for context\n  const vfile = {\n    path: mdxFilePath,\n    value: mdxContent,\n  };\n\n  const file = await unified()\n    .use(remarkParse)\n    .use(remarkMdx)\n    .use(remarkGfm)\n    .use(extractMetadata)\n    .use(transformJsx)\n    .use(resolveMdLinks, { urlPath, urlsWithMdVersion })\n    .use(remarkStringify, {\n      bullet: '-',\n      emphasis: '*',\n      strong: '*',\n      fence: '`',\n      fences: true,\n      listItemIndent: 'one',\n      rule: '-',\n      commonmark: true,\n      gfm: true,\n    })\n    .process(vfile);\n\n  // Get markdown content as string\n  const markdown = String(file);\n\n  // Extract metadata from the file's data\n  const { title = '', subtitle = '', description = '' } = file.data.metadata || {};\n\n  return {\n    markdown,\n    title,\n    subtitle,\n    description,\n  };\n}\n"
  },
  {
    "path": "docs/scripts/generateLlmTxt/mdxToMarkdown.test.mjs",
    "content": "import { describe, it, expect } from 'vitest';\nimport fs from 'fs';\nimport path from 'path';\n// eslint-disable-next-line import/extensions\nimport { releases } from '../../src/data/releases.ts';\nimport { mdxToMarkdown } from './mdxToMarkdown.mjs';\n\ndescribe('mdxToMarkdown', () => {\n  // A snapshpot test for the Accordion MDX content.\n  // Serves as an integration test for the markdown generation of component docs.\n  it('should transform Accordion MDX content to markdown with metadata', async () => {\n    // Read the actual Accordion MDX file\n    const accordionMdxPath = path.resolve(\n      import.meta.dirname,\n      '../../src/app/(docs)/react/components/accordion/page.mdx',\n    );\n    const accordionMdxContent = fs.readFileSync(accordionMdxPath, 'utf-8');\n\n    // Transform the MDX content\n    const result = await mdxToMarkdown(accordionMdxContent, accordionMdxPath);\n\n    // Verify the result structure\n    expect(result).toBeTypeOf('object');\n    expect(result).toHaveProperty('markdown');\n    expect(result).toHaveProperty('title');\n    expect(result).toHaveProperty('subtitle');\n    expect(result).toHaveProperty('description');\n\n    // Verify extracted metadata\n    expect(result.title).toBe('Accordion');\n    expect(result.subtitle).toBe('A set of collapsible panels with headings.');\n    expect(result.description).toBe(\n      'A high-quality, unstyled React accordion component that displays a set of collapsible panels with headings.',\n    );\n\n    // Snapshot test the complete result\n    expect(result.markdown).toMatchSnapshot();\n  });\n\n  // A snapshpot test for the useRender MDX content.\n  // Serves as an integration test for the markdown generation of utils docs.\n  it('should transform Direction Provider MDX content to markdown with metadata', async () => {\n    // Read the actual Direcrion Provider MDX file\n    const directionProviderMdxPath = path.resolve(\n      import.meta.dirname,\n      '../../src/app/(docs)/react/utils/direction-provider/page.mdx',\n    );\n    const directionProviderMdxContent = fs.readFileSync(directionProviderMdxPath, 'utf-8');\n\n    // Transform the MDX content\n    const result = await mdxToMarkdown(directionProviderMdxContent, directionProviderMdxPath);\n\n    // Verify the result structure\n    expect(result).toBeTypeOf('object');\n    expect(result).toHaveProperty('markdown');\n    expect(result).toHaveProperty('title');\n    expect(result).toHaveProperty('subtitle');\n    expect(result).toHaveProperty('description');\n\n    // Verify extracted metadata\n    expect(result.title).toBe('Direction Provider');\n    expect(result.subtitle).toBe('Enables RTL behavior for Base UI components.');\n    expect(result.description).toBe(\n      'A direction provider component that enables RTL behavior for Base UI components.',\n    );\n\n    // Snapshot test the complete result\n    expect(result.markdown).toMatchSnapshot();\n  });\n\n  it('should include all releases from releases.ts', async () => {\n    const releasesMdxPath = path.resolve(\n      import.meta.dirname,\n      '../../src/app/(docs)/react/overview/releases/page.mdx',\n    );\n    const releasesMdxContent = fs.readFileSync(releasesMdxPath, 'utf-8');\n\n    const urlPath = '/react/overview/releases';\n    const urlsWithMdVersion = new Set([\n      urlPath,\n      ...releases.map((r) => `/react/overview/releases/${r.versionSlug}`),\n    ]);\n    const result = await mdxToMarkdown(releasesMdxContent, releasesMdxPath, {\n      urlPath,\n      urlsWithMdVersion,\n    });\n\n    // Strip markdown backslash escapes (e.g. \\<) for content comparison\n    const normalized = result.markdown.replace(/\\\\(.)/g, '$1');\n    // Split into per-release sections so duplicate highlights are verified per release\n    const sections = normalized.split(/(?=^### )/m);\n\n    for (const release of releases) {\n      const heading = `### [${release.version}](/react/overview/releases/${release.versionSlug}.md)`;\n      expect(result.markdown).toContain(heading);\n\n      const section = sections.find((s) => s.startsWith(heading));\n      expect(section).toBeDefined();\n      for (const highlight of release.highlights) {\n        expect(section).toContain(`- ${highlight}`);\n      }\n    }\n  });\n});\n"
  },
  {
    "path": "docs/scripts/generateLlmTxt/propsReferenceTableProcessor.mjs",
    "content": "/**\n * propsReferenceTableProcessor.mjs - Process inline PropsReferenceTable components\n *\n * This module handles converting inline props reference data from MDX PropsReferenceTable\n * components into markdown tables for documentation.\n */\n\nimport { unified } from 'unified';\nimport remarkParse from 'remark-parse';\nimport * as mdx from './mdxNodeHelpers.mjs';\n\n/**\n * Parse a markdown string into an AST\n * @param {string} markdown - Markdown string to parse\n * @returns {Object} The root content node of the parsed AST\n */\nfunction parseMarkdown(markdown) {\n  // Parse markdown into an AST\n  const processor = unified().use(remarkParse);\n  const result = processor.parse(markdown);\n  return result.children;\n}\n\n/**\n * Recursively convert an estree expression into a JavaScript object\n * @param {Object} estree - The estree node to convert\n * @returns {Object} The converted JavaScript object\n */\nfunction convertEstreeToObject(estree) {\n  // Get the main expression from the estree program body\n  if (!estree || !estree.body || !estree.body[0] || !estree.body[0].expression) {\n    throw new Error('Invalid estree structure - missing expression');\n  }\n\n  const expression = estree.body[0].expression;\n  return convertExpressionNode(expression);\n}\n\n/**\n * Convert an expression node to a JavaScript value\n * @param {Object} node - The expression node\n * @returns {any} The converted JavaScript value\n */\nfunction convertExpressionNode(node) {\n  if (!node || !node.type) {\n    throw new Error('Invalid expression node - missing type');\n  }\n\n  switch (node.type) {\n    case 'ObjectExpression': {\n      const obj = {};\n      // Convert each property in the object\n      for (const prop of node.properties) {\n        if (prop.type !== 'Property') {\n          throw new Error(`Unsupported property type: ${prop.type}`);\n        }\n\n        // Get the property key\n        let key;\n        if (prop.key.type === 'Identifier') {\n          key = prop.key.name;\n        } else if (prop.key.type === 'Literal') {\n          key = prop.key.value;\n        } else {\n          throw new Error(`Unsupported key type: ${prop.key.type}`);\n        }\n\n        // Get the property value\n        const value = convertExpressionNode(prop.value);\n\n        // Add to the object\n        obj[key] = value;\n      }\n      return obj;\n    }\n    case 'ArrayExpression':\n      // Convert each element in the array\n      return node.elements.map((element) => convertExpressionNode(element));\n\n    case 'Literal':\n      // Return literals directly\n      return node.value;\n\n    case 'TemplateLiteral':\n      // For simple template literals with no expressions\n      if (node.quasis.length === 1 && node.expressions.length === 0) {\n        return node.quasis[0].value.raw;\n      }\n      // For complex template literals, return a simplified representation\n      return node.quasis.map((q) => q.value.raw).join('…');\n\n    case 'Identifier':\n      // For identifiers like undefined, null, etc.\n      return node.name;\n\n    default:\n      throw new Error(`Unsupported expression type: ${node.type}`);\n  }\n}\n\n/**\n * Transforms a PropsReferenceTable component into a markdown table\n * @param {Object} node - The PropsReferenceTable JSX node from MDX\n * @returns {Array} Array of markdown nodes to replace the PropsReferenceTable component\n */\nexport function processPropsReferenceTable(node) {\n  // Extract the data attribute which contains props definitions\n  const dataAttr = node.attributes?.find((attr) => attr.name === 'data');\n  const typeAttr = node.attributes?.find((attr) => attr.name === 'type')?.value || 'props';\n\n  // If no data attribute is found, throw an error\n  if (!dataAttr) {\n    throw new Error('PropsReferenceTable: No data provided');\n  }\n\n  // Process the data object from the AST\n  let propsData = {};\n\n  if (dataAttr.type === 'mdxJsxAttribute' && dataAttr.value) {\n    try {\n      if (\n        dataAttr.value.type === 'mdxJsxAttributeValueExpression' &&\n        dataAttr.value.data &&\n        dataAttr.value.data.estree\n      ) {\n        // Convert the estree to a JavaScript object\n        propsData = convertEstreeToObject(dataAttr.value.data.estree);\n      } else {\n        throw new Error('PropsReferenceTable data must be a static JavaScript object');\n      }\n    } catch (err) {\n      throw new Error(`Error processing PropsReferenceTable data: ${err.message}`);\n    }\n  } else {\n    throw new Error('PropsReferenceTable data attribute must be a valid JSX attribute');\n  }\n\n  // Generate markdown tables\n  const tables = [];\n\n  // Add heading based on the type\n  const heading = typeAttr === 'return' ? 'Return Value' : 'Props';\n  tables.push(mdx.paragraph([mdx.strong(`${heading}:`)]));\n\n  // Convert props data to table rows\n  const propsRows = Object.entries(propsData).map(([propName, propDef]) => {\n    const row = [propName, propDef.type ? mdx.inlineCode(propDef.type) : '-'];\n\n    // Add default column for props type\n    if (typeAttr === 'props') {\n      row.push(propDef.default ? mdx.inlineCode(propDef.default) : '-');\n    }\n\n    // Add description\n    row.push(parseMarkdown(propDef.description || '-'));\n\n    return row;\n  });\n\n  // Define columns based on type\n  const headers =\n    typeAttr === 'props'\n      ? ['Prop', 'Type', 'Default', 'Description']\n      : ['Property', 'Type', 'Description'];\n\n  // Define column alignments\n  const alignments =\n    typeAttr === 'props' ? ['left', 'left', 'left', 'left'] : ['left', 'left', 'left'];\n\n  const tableNode = mdx.table(headers, propsRows, alignments);\n  tables.push(tableNode);\n\n  return tables;\n}\n"
  },
  {
    "path": "docs/scripts/generateLlmTxt/referenceProcessor.mjs",
    "content": "/**\n * referenceProcessor.mjs - Process component reference definitions\n *\n * This module handles loading and converting component reference data\n * from JSON files into markdown tables for documentation.\n */\n\nimport fs from 'fs';\nimport path from 'path';\nimport { unified } from 'unified';\nimport remarkParse from 'remark-parse';\n\nimport * as mdx from './mdxNodeHelpers.mjs';\nimport {\n  getAttributeValue,\n  isComponentDef,\n  isFunctionDef,\n  normalizeReturnValue,\n} from '../../src/components/ReferenceTable/referenceUtils.mjs';\n\n/**\n * Parse a markdown string into an AST\n * @param {string} markdown - Markdown string to parse\n * @returns {Object} The root content node of the parsed AST\n */\nfunction parseMarkdown(markdown) {\n  // Parse markdown into an AST\n  const processor = unified().use(remarkParse);\n  const result = processor.parse(markdown);\n  return result.children;\n}\n\n/**\n * Transforms a Reference component into markdown tables\n * @param {Object} node - The Reference JSX node from MDX\n * @param {Array} ancestors - The ancestry chain of the node\n * @returns {Array} Array of markdown nodes to replace the Reference component\n */\nexport function processReference(node) {\n  // Extract component name and parts from attributes\n  const componentAttr = getAttributeValue(node, 'component');\n  const nameAttr = getAttributeValue(node, 'name');\n  const partsAttr = getAttributeValue(node, 'parts');\n  const referenceName = componentAttr ?? nameAttr;\n\n  if (!referenceName) {\n    throw new Error('Missing \"component\" or \"name\" prop on the \"<Reference />\" component.');\n  }\n\n  const tables = [];\n\n  // Process each component part\n  const parts = partsAttr ? partsAttr.split(/,\\s*/).map((p) => p.trim()) : [referenceName];\n\n  // Load component definitions from JSON files\n  const componentDefs = [];\n  const kebabCase = (str) =>\n    str\n      .replace(/([a-z0-9])([A-Z])/g, '$1-$2')\n      .replace(/([A-Z])([A-Z][a-z])/g, '$1-$2')\n      .toLowerCase();\n  const projectRoot = path.resolve(import.meta.dirname, '../..');\n  let functionDef = null;\n\n  if (!partsAttr) {\n    const filename = `${kebabCase(referenceName)}.json`;\n    const filepath = path.join(projectRoot, 'reference/generated', filename);\n    const jsonContent = fs.readFileSync(filepath, 'utf-8');\n    const referenceDef = JSON.parse(jsonContent);\n\n    if (isFunctionDef(referenceDef) && !isComponentDef(referenceDef)) {\n      functionDef = referenceDef;\n    } else {\n      componentDefs.push(referenceDef);\n    }\n  } else {\n    for (const part of parts) {\n      // Construct file path for this component part\n      let filename = `${kebabCase(referenceName)}-${kebabCase(part)}.json`;\n      let filepath = path.join(projectRoot, 'reference/generated', filename);\n\n      // If file doesn't exist, try with just the part name\n      if (!fs.existsSync(filepath)) {\n        filename = `${kebabCase(part)}.json`;\n        filepath = path.join(projectRoot, 'reference/generated', filename);\n      }\n\n      // Read and parse JSON file\n      if (!fs.existsSync(filepath)) {\n        throw new Error(`Reference file not found for component ${referenceName}, part ${part}`);\n      }\n\n      const jsonContent = fs.readFileSync(filepath, 'utf-8');\n      const componentDef = JSON.parse(jsonContent);\n      componentDefs.push(componentDef);\n    }\n  }\n\n  if (functionDef) {\n    if (functionDef.description) {\n      const descriptionNode = parseMarkdown(functionDef.description);\n      tables.push(mdx.paragraph(descriptionNode));\n    }\n\n    if (functionDef.parameters && Object.keys(functionDef.parameters).length > 0) {\n      tables.push(mdx.paragraph([mdx.strong('Parameters:')]));\n\n      const parameterRows = Object.entries(functionDef.parameters).map(([paramName, paramDef]) => {\n        const displayName = paramDef.optional ? `${paramName}?` : paramName;\n        return [\n          displayName,\n          paramDef.type ? mdx.inlineCode(paramDef.type) : '-',\n          paramDef.default ? mdx.inlineCode(paramDef.default) : '-',\n          parseMarkdown(paramDef.description || '-'),\n        ];\n      });\n\n      const tableNode = mdx.table(['Parameter', 'Type', 'Default', 'Description'], parameterRows, [\n        'left',\n        'left',\n        'left',\n        'left',\n      ]);\n      tables.push(tableNode);\n    }\n\n    if (functionDef.returnValue) {\n      tables.push(mdx.paragraph([mdx.strong('Return Value:')]));\n\n      const returnData = normalizeReturnValue(functionDef.returnValue);\n\n      const includeName = Object.keys(returnData).length > 1;\n      const returnRows = Object.entries(returnData).map(([name, def]) => {\n        const description = def.description || '';\n        const namePrefix = includeName ? `${name}${description ? ': ' : ''}` : '';\n        const descriptionText = description ? `${namePrefix}${description}` : namePrefix;\n        return [def.type ? mdx.inlineCode(def.type) : '-', parseMarkdown(descriptionText || '-')];\n      });\n\n      const tableNode = mdx.table(['Type', 'Description'], returnRows, ['left', 'left']);\n      tables.push(tableNode);\n    }\n\n    return tables;\n  }\n\n  // Generate markdown tables for each component\n  componentDefs.forEach((def, idx) => {\n    const part = parts[idx];\n\n    // Add subheading for the part\n    if (parts.length > 1) {\n      tables.push(mdx.heading(3, part));\n    }\n\n    // Add description if available\n    if (def.description) {\n      // Parse the description as markdown\n      const descriptionNode = parseMarkdown(def.description);\n      tables.push(mdx.paragraph(descriptionNode));\n    }\n\n    // Props table\n    if (Object.keys(def.props || {}).length > 0) {\n      // Create a proper heading with strong node\n      tables.push(mdx.paragraph([mdx.strong(`${part} Props:`)]));\n\n      const propsRows = Object.entries(def.props).map(([propName, propDef]) => [\n        propName,\n        propDef.type ? mdx.inlineCode(propDef.type) : '-',\n        propDef.default ? mdx.inlineCode(propDef.default) : '-',\n        parseMarkdown(propDef.description || '-'),\n      ]);\n\n      // Define column alignments: prop name left-aligned, others left-aligned\n      const alignments = ['left', 'left', 'left', 'left'];\n\n      const tableNode = mdx.table(\n        ['Prop', 'Type', 'Default', 'Description'],\n        propsRows,\n        alignments,\n      );\n      tables.push(tableNode);\n    }\n\n    // Data attributes table\n    if (Object.keys(def.dataAttributes || {}).length > 0) {\n      tables.push(mdx.paragraph([mdx.strong(`${part} Data Attributes:`)]));\n\n      const attrRows = Object.entries(def.dataAttributes).map(([attrName, attrDef]) => [\n        attrName,\n        attrDef.type ? mdx.inlineCode(attrDef.type) : '-',\n        parseMarkdown(attrDef.description || '-'),\n      ]);\n\n      // Define column alignments\n      const alignments = ['left', 'left', 'left'];\n\n      const tableNode = mdx.table(['Attribute', 'Type', 'Description'], attrRows, alignments);\n      tables.push(tableNode);\n    }\n\n    // CSS variables table\n    if (Object.keys(def.cssVariables || {}).length > 0) {\n      tables.push(mdx.paragraph([mdx.strong(`${part} CSS Variables:`)]));\n\n      const cssRows = Object.entries(def.cssVariables).map(([varName, varDef]) => [\n        varName,\n        varDef.type ? mdx.inlineCode(varDef.type) : '-',\n        varDef.default ? mdx.inlineCode(varDef.default) : '-',\n        parseMarkdown(varDef.description || '-'),\n      ]);\n\n      // Define column alignments\n      const alignments = ['left', 'left', 'left', 'left'];\n\n      const tableNode = mdx.table(\n        ['Variable', 'Type', 'Default', 'Description'],\n        cssRows,\n        alignments,\n      );\n      tables.push(tableNode);\n    }\n\n    // Add separator between parts\n    if (parts.length > 1 && idx < parts.length - 1) {\n      tables.push(mdx.paragraph(''));\n    }\n  });\n\n  return tables;\n}\n"
  },
  {
    "path": "docs/scripts/generateLlmTxt/releaseTimelineProcessor.mjs",
    "content": "/**\n * releaseTimelineProcessor.mjs - Process release timeline data\n *\n * This module handles converting release data from releases.ts\n * into markdown AST nodes for LLM documentation output.\n */\n\nimport { unified } from 'unified';\nimport remarkParse from 'remark-parse';\n// eslint-disable-next-line import/extensions\nimport { releases } from '../../src/data/releases.ts';\nimport * as mdx from './mdxNodeHelpers.mjs';\n\nconst dateFormatter = new Intl.DateTimeFormat('en-US', {\n  month: 'long',\n  day: 'numeric',\n  year: 'numeric',\n  timeZone: 'UTC',\n});\n\n/**\n * Format an ISO date string (YYYY-MM-DD) to a human-readable format\n * @param {string} dateStr - ISO date string\n * @returns {string} Formatted date (e.g., \"February 12, 2026\")\n */\nfunction formatDate(dateStr) {\n  return dateFormatter.format(new Date(dateStr));\n}\n\n/**\n * Parse an inline markdown string into AST children of a paragraph.\n * @param {string} text - Markdown string (e.g., \"New `Drawer` component.\")\n * @returns {Array} Inline AST nodes\n */\nfunction parseInlineMarkdown(text) {\n  const tree = unified().use(remarkParse).parse(text);\n  // The parser wraps inline content in a paragraph node\n  if (tree.children.length === 1 && tree.children[0].type === 'paragraph') {\n    return tree.children[0].children;\n  }\n  return tree.children;\n}\n\n/**\n * Generates markdown AST nodes for the release timeline\n * @returns {Array} Array of markdown AST nodes\n */\nexport function processReleaseTimeline() {\n  const nodes = [];\n\n  nodes.push(mdx.heading(2, 'Timeline'));\n\n  for (const release of releases) {\n    // Heading with version linked to release page\n    const url = `/react/overview/releases/${release.versionSlug}`;\n    const headingChildren = [{ type: 'link', url, children: [mdx.text(release.version)] }];\n    if (release.latest) {\n      headingChildren.push(mdx.text(' (Latest)'));\n    }\n    nodes.push(mdx.heading(3, headingChildren));\n\n    // Date paragraph\n    const dateText = formatDate(release.date);\n    nodes.push(mdx.paragraph(mdx.emphasis(dateText)));\n\n    // Highlights as an unordered list\n    nodes.push({\n      type: 'list',\n      ordered: false,\n      spread: false,\n      children: release.highlights.map((highlight) => ({\n        type: 'listItem',\n        spread: false,\n        children: [{ type: 'paragraph', children: parseInlineMarkdown(highlight) }],\n      })),\n    });\n  }\n\n  return nodes;\n}\n"
  },
  {
    "path": "docs/scripts/generateLlmTxt/resolver.mjs",
    "content": "import { visit } from 'unist-util-visit';\n\nexport function isAbsoluteUrl(url) {\n  try {\n    return !!new URL(url);\n  } catch {\n    return false;\n  }\n}\n\nexport function resolveUrl(url, base) {\n  if (isAbsoluteUrl(base)) {\n    return new URL(url, base).href;\n  }\n  const baseUrl = new URL(base, 'https://example.com');\n  const absUrl = new URL(url, baseUrl).href;\n  return absUrl.slice(baseUrl.origin.length);\n}\n\nexport function resolveMdLink(link, { urlPath, urlsWithMdVersion }) {\n  if (isAbsoluteUrl(link)) {\n    return link;\n  }\n\n  const resolvedPath = new URL(link, new URL(urlPath, 'https://example.com')).pathname;\n\n  if (urlsWithMdVersion.has(resolvedPath)) {\n    return `${resolvedPath}.md`;\n  }\n\n  return resolvedPath;\n}\n\nexport function resolveMdLinks({ urlPath, urlsWithMdVersion }) {\n  return (tree) => {\n    visit(tree, 'link', (node) => {\n      if (!node.url || isAbsoluteUrl(node.url)) {\n        return;\n      }\n\n      node.url = resolveMdLink(node.url, { urlPath, urlsWithMdVersion });\n    });\n  };\n}\n"
  },
  {
    "path": "docs/scripts/playground.template.tsx",
    "content": "export default function Playground() {\n  return (\n    <div>A playground for a fast iteration development cycle in isolation outside of git.</div>\n  );\n}\n"
  },
  {
    "path": "docs/scripts/rehypeExtractLinkUrls.mts",
    "content": "import type { Element } from 'hast';\nimport type { Node } from 'unist';\nimport { visit, CONTINUE, SKIP } from 'unist-util-visit';\nimport type { MdxJsxFlowElement, MdxJsxAttribute } from 'mdast-util-mdx-jsx';\n\nexport interface RehypeExtractLinkUrlsOptions {\n  onCompleted?: (links: Set<string>) => void;\n}\n\n/**\n * A simple rehype plugin that extracts all link URLs from an MD(X) file.\n */\nexport function rehypeExtractLinkUrls(options: RehypeExtractLinkUrlsOptions) {\n  const foundLinks = new Set<string>();\n\n  const visitor = (node: Node) => {\n    if (node.type === 'element') {\n      const element = node as Element;\n      if (element.tagName === 'a' && element.properties.href != null) {\n        foundLinks.add(element.properties.href as string);\n        return SKIP;\n      }\n    }\n\n    if (node.type === 'mdxJsxFlowElement') {\n      const element = node as MdxJsxFlowElement;\n      if (element.name === 'a') {\n        const hrefAttribute = element.attributes.find(\n          (attr) => (attr as MdxJsxAttribute).name === 'href',\n        );\n        if (hrefAttribute != null && hrefAttribute.value != null) {\n          foundLinks.add(hrefAttribute.value as string);\n        }\n\n        return SKIP;\n      }\n    }\n\n    return CONTINUE;\n  };\n\n  return (tree: Node) => {\n    visit(tree, visitor);\n    options.onCompleted?.(foundLinks);\n  };\n}\n"
  },
  {
    "path": "docs/scripts/reportBrokenLinks.mts",
    "content": "import { crawl } from '@mui/internal-code-infra/brokenLinksChecker';\n\nasync function main() {\n  const { issues } = await crawl({\n    startCommand: 'pnpm serve --no-request-logging -p 3001',\n    host: 'http://localhost:3001/',\n    // Target paths to ignore during link checking\n    ignoredPaths: [],\n    // CSS selectors for content to ignore during link checking\n    ignoredContent: [],\n  });\n\n  process.exit(issues.length);\n}\n\nmain();\n"
  },
  {
    "path": "docs/src/app/(docs)/layout.css",
    "content": "@import 'docs/src/css/custom-media.css';\n\n@layer base {\n  html {\n    color-scheme: light dark;\n  }\n\n  /* Scope body styles to only apply when docs layout is present */\n  body:has(.RootLayout) {\n    position: relative; /* iOS 26 `position: absolute` backdrops */\n    min-width: 320px;\n    line-height: 1.5;\n    /* It's also the iOS footer overscroll background */\n    background-color: var(--color-background);\n    color: var(--color-foreground);\n  }\n\n  .RootLayout ::selection {\n    background-color: var(--color-selection);\n  }\n}\n\n@layer components {\n  .RootLayout {\n    z-index: 0;\n    position: relative;\n\n    --root-layout-padding-x: 0rem;\n    padding-inline: var(--root-layout-padding-x);\n\n    @media (--show-side-nav) {\n      --root-layout-padding-x: 3rem;\n\n      &::before,\n      &::after {\n        content: '';\n        position: absolute;\n        background-color: var(--color-gridline);\n        height: 1px;\n        right: 0;\n        left: 0;\n      }\n\n      &::before {\n        top: var(--header-height);\n        margin-top: -1px;\n      }\n\n      &::after {\n        bottom: var(--header-height);\n        margin-bottom: -1px;\n      }\n    }\n  }\n\n  .RootLayoutContainer {\n    position: relative;\n    display: flex;\n    flex-direction: column;\n    margin-inline: auto;\n    min-height: 100dvh;\n    max-width: calc(var(--breakpoint-max-layout-width) - var(--root-layout-padding-x) * 2);\n\n    @media (--show-side-nav) {\n      padding-block: var(--header-height);\n      padding-bottom: 0;\n\n      &::before,\n      &::after {\n        content: '';\n        position: absolute;\n        top: 0;\n        bottom: 0;\n        width: 1px;\n        background-color: var(--color-gridline);\n      }\n\n      &::before {\n        left: 0;\n        margin-left: -1px;\n      }\n\n      &::after {\n        right: 0;\n        margin-right: -1px;\n      }\n    }\n  }\n\n  .RootLayoutContent {\n    display: flex;\n    flex-grow: 1;\n    flex-direction: column;\n    background-color: var(--color-content);\n  }\n\n  .RootLayoutFooter {\n    @media (--lg) {\n      background-color: var(--color-background);\n      width: 100%;\n      height: var(--header-height);\n      position: absolute;\n      left: 0;\n      right: 0;\n      bottom: 0;\n    }\n  }\n\n  .ContentLayoutRoot {\n    --sidebar-width: 17.5rem;\n\n    display: grid;\n    align-items: start;\n    padding-top: var(--header-height);\n    padding-inline: 1.5rem;\n    grid-template-columns: 1fr;\n\n    @media (--sm) {\n      padding-inline: 2.5rem;\n    }\n\n    @media (--show-side-nav) {\n      padding-top: 0;\n      padding-inline: 0;\n      grid-template-columns: var(--sidebar-width) 1fr 3rem;\n    }\n\n    @media (--show-quick-nav) {\n      grid-template-columns: var(--sidebar-width) 1fr var(--sidebar-width);\n    }\n  }\n\n  .ContentLayoutMain {\n    min-width: 0;\n    max-width: 48rem;\n    width: 100%;\n\n    padding-top: 1.5rem;\n    padding-bottom: 5rem;\n    margin: 0 auto;\n\n    @media (--sm) {\n      padding-top: 2rem;\n    }\n\n    @media (--show-side-nav) {\n      margin: 0;\n      padding-bottom: 8rem;\n    }\n\n    @media (--show-quick-nav) {\n      margin: 0;\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/layout.tsx",
    "content": "import * as React from 'react';\nimport type { Metadata, Viewport } from 'next/types';\nimport { GoogleAnalytics } from 'docs/src/components/GoogleAnalytics';\nimport { DocsProviders } from 'docs/src/components/DocsProviders';\nimport * as SideNav from 'docs/src/components/SideNav';\nimport * as QuickNav from 'docs/src/components/QuickNav/QuickNav';\nimport { Header, titleMap } from 'docs/src/components/Header';\nimport { MAIN_CONTENT_ID } from 'docs/src/components/SkipNav';\nimport { sitemap } from 'docs/src/app/sitemap';\nimport 'docs/src/css/index.css';\nimport './layout.css';\n\nconst showPrivatePages = process.env.SHOW_PRIVATE_PAGES === 'true';\n\nexport default function Layout({ children }: React.PropsWithChildren) {\n  return (\n    // Use suppressHydrationWarning to avoid https://github.com/facebook/react/issues/24430\n    <html lang=\"en\">\n      <head>\n        <link\n          rel=\"preload\"\n          href=\"/fonts/die-grotesk-a-regular.woff2\"\n          as=\"font\"\n          type=\"font/woff2\"\n          crossOrigin=\"anonymous\"\n        />\n        <link\n          rel=\"preload\"\n          href=\"/fonts/die-grotesk-a-bold.woff2\"\n          as=\"font\"\n          type=\"font/woff2\"\n          crossOrigin=\"anonymous\"\n        />\n        <link\n          rel=\"preload\"\n          href=\"/fonts/die-grotesk-b-bold.woff2\"\n          as=\"font\"\n          type=\"font/woff2\"\n          crossOrigin=\"anonymous\"\n        />\n      </head>\n      <body suppressHydrationWarning>\n        <GoogleAnalytics>\n          <DocsProviders>\n            <div className=\"RootLayout\">\n              <div className=\"RootLayoutContainer\">\n                <div className=\"RootLayoutContent\">\n                  <div className=\"ContentLayoutRoot\">\n                    <Header />\n                    <SideNav.Root>\n                      {sitemap &&\n                        Object.entries(sitemap.data).map(([name, section]) => (\n                          <SideNav.Section key={name}>\n                            <SideNav.Heading>{name}</SideNav.Heading>\n                            <SideNav.List>\n                              {section.pages\n                                .filter((page) =>\n                                  page.audience === 'private' ? showPrivatePages : true,\n                                )\n                                .map((page) => {\n                                  const isNewPage = page.tags?.includes('New');\n                                  const isPreviewPage = page.tags?.includes('Preview');\n                                  const isPrivatePage = page.audience === 'private';\n\n                                  return (\n                                    <SideNav.Item\n                                      key={page.title}\n                                      href={\n                                        page.path.startsWith('./')\n                                          ? `${section.prefix}${page.path.replace(/^\\.\\//, '').replace(/\\/page\\.mdx$/, '')}`\n                                          : page.path\n                                      }\n                                      external={page.tags?.includes('External')}\n                                    >\n                                      {(page.title && titleMap[page.title]) || page.title}\n                                      {isPrivatePage && <SideNav.Badge>Private</SideNav.Badge>}\n                                      {isPreviewPage && <SideNav.Badge>Preview</SideNav.Badge>}\n                                      {isNewPage && !isPreviewPage && !isPrivatePage && (\n                                        <SideNav.Badge>New</SideNav.Badge>\n                                      )}\n                                    </SideNav.Item>\n                                  );\n                                })}\n                            </SideNav.List>\n                          </SideNav.Section>\n                        ))}\n                    </SideNav.Root>\n\n                    <main className=\"ContentLayoutMain\" id={MAIN_CONTENT_ID}>\n                      <QuickNav.Container>{children}</QuickNav.Container>\n                    </main>\n                  </div>\n                </div>\n                <span className=\"RootLayoutFooter\" />\n              </div>\n            </div>\n          </DocsProviders>\n        </GoogleAnalytics>\n      </body>\n    </html>\n  );\n}\n\nexport const metadata: Metadata = {\n  // Title and description are pulled from <h1> and <Subtitle> in the MDX.\n  title: null,\n  description: null,\n  twitter: {\n    site: '@base_ui',\n    card: 'summary_large_image',\n  },\n  openGraph: {\n    type: 'website',\n    locale: 'en_US',\n    title: {\n      template: '%s · Base UI',\n      default: 'Base UI',\n    },\n    ttl: 604800,\n  },\n  metadataBase: new URL('https://base-ui.com'),\n  alternates: {\n    canonical: './',\n  },\n  icons: {\n    icon: [\n      {\n        rel: 'icon',\n        url:\n          process.env.NODE_ENV !== 'production' ? '/static/favicon-dev.ico' : '/static/favicon.ico',\n        sizes: '32x32',\n      },\n      {\n        rel: 'icon',\n        type: 'image/svg+xml',\n        url:\n          process.env.NODE_ENV !== 'production' ? '/static/favicon-dev.svg' : '/static/favicon.svg',\n      },\n    ],\n    apple: [\n      {\n        rel: 'apple-touch-icon',\n        url: '/static/apple-touch-icon.png',\n      },\n    ],\n  },\n};\n\nexport const viewport: Viewport = {\n  initialScale: 1,\n  width: 'device-width',\n  themeColor: [\n    // Desktop Safari page background\n    {\n      media: '(prefers-color-scheme: light) and (min-width: 1024px)',\n      color: 'oklch(95% 0.25% 264)',\n    },\n    {\n      media: '(prefers-color-scheme: dark) and (min-width: 1024px)',\n      color: 'oklch(25% 1% 264)',\n    },\n\n    // Mobile Safari header background (match the site header)\n    {\n      media: '(prefers-color-scheme: light)',\n      color: 'oklch(98% 0.25% 264)',\n    },\n    {\n      media: '(prefers-color-scheme: dark)',\n      color: 'oklch(17% 1% 264)',\n    },\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/production-error/ErrorCode.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useSearchParams } from 'next/navigation';\n\nexport interface ErrorDisplayProps {\n  msg: string;\n}\n\nfunction ErrorCodeContent() {\n  return useSearchParams().get('code') ?? '';\n}\n\n/**\n * Client component that interpolates arguments in an error message. Must be\n * a client component because it reads the search params.\n */\nexport default function ErrorCode() {\n  return (\n    <React.Suspense fallback=\"…\">\n      <ErrorCodeContent />\n    </React.Suspense>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/production-error/ErrorDisplay.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useSearchParams } from 'next/navigation';\nimport codes from 'docs/src/error-codes.json';\n\nfunction ErrorMessageWithArgs() {\n  const searchParams = useSearchParams();\n\n  return React.useMemo(() => {\n    const code = searchParams.get('code');\n    const msg =\n      (code ? (codes as Partial<Record<string, string>>)[code ?? ''] : null) ??\n      `Unknown error code: ${code}`;\n    const args = searchParams.getAll('args[]');\n    let index = 0;\n    return msg.replace(/%s/g, () => {\n      const replacement = args[index];\n      index += 1;\n      return replacement === undefined ? '[missing argument]' : replacement;\n    });\n  }, [searchParams]);\n}\n\n/**\n * Client component that interpolates arguments in an error message. Must be\n * a client component because it reads the search params.\n */\nexport default function ErrorDisplay() {\n  return (\n    <code>\n      <React.Suspense fallback=\"…\">\n        <ErrorMessageWithArgs />\n      </React.Suspense>\n    </code>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/production-error/page.mdx",
    "content": "import ErrorCode from './ErrorCode';\nimport ErrorDisplay from './ErrorDisplay';\n\n# Production error #<ErrorCode />\n\n<Subtitle skipMarkdownLink>Explanation for minified production error message.</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"In the production build, error messages are minified to keep your application lightweight.\"\n/>\n\nA minified Base UI error occurred in the production build of React.\n\nThe full error message:\n\n<ErrorDisplay />\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/accordion/demos/_index.module.css",
    "content": ".Accordion {\n  box-sizing: border-box;\n  display: flex;\n  width: 24rem;\n  max-width: calc(100vw - 8rem);\n  flex-direction: column;\n  justify-content: center;\n  color: var(--color-gray-900);\n}\n\n.Item {\n  border-bottom: 1px solid var(--color-gray-200);\n}\n\n.Header {\n  margin: 0;\n}\n\n.Trigger {\n  box-sizing: border-box;\n  position: relative;\n  display: flex;\n  width: 100%;\n  gap: 1rem;\n  align-items: baseline;\n  justify-content: space-between;\n  padding-block: 0.5rem;\n  padding-inline: 0.75rem 0.25rem;\n  color: var(--color-gray-900);\n  font-family: inherit;\n  font-weight: 400;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  background: var(--color-gray-50);\n  border: none;\n  outline: none;\n  text-align: left;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    z-index: 1;\n  }\n}\n\n.TriggerIcon {\n  box-sizing: border-box;\n  flex-shrink: 0;\n  width: 0.75rem;\n  height: 0.75rem;\n  margin-right: 0.5rem;\n  transition: transform 150ms ease-out;\n\n  [data-panel-open] > & {\n    transform: rotate(45deg) scale(1.1);\n  }\n}\n\n.Panel {\n  box-sizing: border-box;\n  height: var(--accordion-panel-height);\n  overflow: hidden;\n  color: var(--color-gray-600);\n  font-size: 1rem;\n  line-height: 1.5rem;\n  transition: height 150ms ease-out;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    height: 0;\n  }\n}\n\n.Content {\n  padding: 0.75rem;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/accordion/demos/hero/css-modules/index.tsx",
    "content": "import * as React from 'react';\nimport { Accordion } from '@base-ui/react/accordion';\nimport styles from '../../_index.module.css';\n\nexport default function ExampleAccordion() {\n  return (\n    <Accordion.Root className={styles.Accordion}>\n      <Accordion.Item className={styles.Item}>\n        <Accordion.Header className={styles.Header}>\n          <Accordion.Trigger className={styles.Trigger}>\n            What is Base UI?\n            <PlusIcon className={styles.TriggerIcon} />\n          </Accordion.Trigger>\n        </Accordion.Header>\n        <Accordion.Panel className={styles.Panel}>\n          <div className={styles.Content}>\n            Base UI is a library of high-quality unstyled React components for design systems and\n            web apps.\n          </div>\n        </Accordion.Panel>\n      </Accordion.Item>\n\n      <Accordion.Item className={styles.Item}>\n        <Accordion.Header className={styles.Header}>\n          <Accordion.Trigger className={styles.Trigger}>\n            How do I get started?\n            <PlusIcon className={styles.TriggerIcon} />\n          </Accordion.Trigger>\n        </Accordion.Header>\n        <Accordion.Panel className={styles.Panel}>\n          <div className={styles.Content}>\n            Head to the “Quick start” guide in the docs. If you’ve used unstyled libraries before,\n            you’ll feel at home.\n          </div>\n        </Accordion.Panel>\n      </Accordion.Item>\n\n      <Accordion.Item className={styles.Item}>\n        <Accordion.Header className={styles.Header}>\n          <Accordion.Trigger className={styles.Trigger}>\n            Can I use it for my project?\n            <PlusIcon className={styles.TriggerIcon} />\n          </Accordion.Trigger>\n        </Accordion.Header>\n        <Accordion.Panel className={styles.Panel}>\n          <div className={styles.Content}>Of course! Base UI is free and open source.</div>\n        </Accordion.Panel>\n      </Accordion.Item>\n    </Accordion.Root>\n  );\n}\n\nfunction PlusIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg viewBox=\"0 0 12 12\" fill=\"currentcolor\" {...props}>\n      <path d=\"M6.75 0H5.25V5.25H0V6.75L5.25 6.75V12H6.75V6.75L12 6.75V5.25H6.75V0Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/accordion/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoAccordionHero = createDemoWithVariants(import.meta.url, { CssModules, Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/accordion/demos/hero/tailwind/index.tsx",
    "content": "import * as React from 'react';\nimport { Accordion } from '@base-ui/react/accordion';\n\nexport default function ExampleAccordion() {\n  return (\n    <Accordion.Root className=\"flex w-96 max-w-[calc(100vw-8rem)] flex-col justify-center text-gray-900\">\n      <Accordion.Item className=\"border-b border-gray-200\">\n        <Accordion.Header>\n          <Accordion.Trigger className=\"group relative flex w-full items-baseline justify-between gap-4 bg-gray-50 py-2 pr-1 pl-3 text-left font-normal hover:bg-gray-100 focus-visible:z-1 focus-visible:outline-2 focus-visible:outline-blue-800\">\n            What is Base UI?\n            <PlusIcon className=\"mr-2 size-3 shrink-0 transition-all ease-out group-data-[panel-open]:scale-110 group-data-[panel-open]:rotate-45\" />\n          </Accordion.Trigger>\n        </Accordion.Header>\n        <Accordion.Panel className=\"h-[var(--accordion-panel-height)] overflow-hidden text-base text-gray-600 transition-[height] ease-out data-[ending-style]:h-0 data-[starting-style]:h-0\">\n          <div className=\"p-3\">\n            Base UI is a library of high-quality unstyled React components for design systems and\n            web apps.\n          </div>\n        </Accordion.Panel>\n      </Accordion.Item>\n\n      <Accordion.Item className=\"border-b border-gray-200\">\n        <Accordion.Header>\n          <Accordion.Trigger className=\"group relative flex w-full items-baseline justify-between gap-4 bg-gray-50 py-2 pr-1 pl-3 text-left font-normal hover:bg-gray-100 focus-visible:z-1 focus-visible:outline-2 focus-visible:outline-blue-800\">\n            How do I get started?\n            <PlusIcon className=\"mr-2 size-3 shrink-0 transition-all ease-out group-data-[panel-open]:scale-110 group-data-[panel-open]:rotate-45\" />\n          </Accordion.Trigger>\n        </Accordion.Header>\n        <Accordion.Panel className=\"h-[var(--accordion-panel-height)] overflow-hidden text-base text-gray-600 transition-[height] ease-out data-[ending-style]:h-0 data-[starting-style]:h-0\">\n          <div className=\"p-3\">\n            Head to the “Quick start” guide in the docs. If you’ve used unstyled libraries before,\n            you’ll feel at home.\n          </div>\n        </Accordion.Panel>\n      </Accordion.Item>\n\n      <Accordion.Item className=\"border-b border-gray-200\">\n        <Accordion.Header>\n          <Accordion.Trigger className=\"group relative flex w-full items-baseline justify-between gap-4 bg-gray-50 py-2 pr-1 pl-3 text-left font-normal hover:bg-gray-100 focus-visible:z-1 focus-visible:outline-2 focus-visible:outline-blue-800\">\n            Can I use it for my project?\n            <PlusIcon className=\"mr-2 size-3 shrink-0 transition-all ease-out group-data-[panel-open]:scale-110 group-data-[panel-open]:rotate-45\" />\n          </Accordion.Trigger>\n        </Accordion.Header>\n        <Accordion.Panel className=\"h-[var(--accordion-panel-height)] overflow-hidden text-base text-gray-600 transition-[height] ease-out data-[ending-style]:h-0 data-[starting-style]:h-0\">\n          <div className=\"p-3\">Of course! Base UI is free and open source.</div>\n        </Accordion.Panel>\n      </Accordion.Item>\n    </Accordion.Root>\n  );\n}\n\nfunction PlusIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg viewBox=\"0 0 12 12\" fill=\"currentcolor\" {...props}>\n      <path d=\"M6.75 0H5.25V5.25H0V6.75L5.25 6.75V12H6.75V6.75L12 6.75V5.25H6.75V0Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/accordion/demos/multiple/css-modules/index.tsx",
    "content": "import * as React from 'react';\nimport { Accordion } from '@base-ui/react/accordion';\nimport styles from '../../_index.module.css';\n\nexport default function ExampleAccordion() {\n  return (\n    <Accordion.Root className={styles.Accordion} multiple>\n      <Accordion.Item className={styles.Item}>\n        <Accordion.Header className={styles.Header}>\n          <Accordion.Trigger className={styles.Trigger}>\n            What is Base UI?\n            <PlusIcon className={styles.TriggerIcon} />\n          </Accordion.Trigger>\n        </Accordion.Header>\n        <Accordion.Panel className={styles.Panel}>\n          <div className={styles.Content}>\n            Base UI is a library of high-quality unstyled React components for design systems and\n            web apps.\n          </div>\n        </Accordion.Panel>\n      </Accordion.Item>\n\n      <Accordion.Item className={styles.Item}>\n        <Accordion.Header className={styles.Header}>\n          <Accordion.Trigger className={styles.Trigger}>\n            How do I get started?\n            <PlusIcon className={styles.TriggerIcon} />\n          </Accordion.Trigger>\n        </Accordion.Header>\n        <Accordion.Panel className={styles.Panel}>\n          <div className={styles.Content}>\n            Head to the “Quick start” guide in the docs. If you’ve used unstyled libraries before,\n            you’ll feel at home.\n          </div>\n        </Accordion.Panel>\n      </Accordion.Item>\n\n      <Accordion.Item className={styles.Item}>\n        <Accordion.Header className={styles.Header}>\n          <Accordion.Trigger className={styles.Trigger}>\n            Can I use it for my project?\n            <PlusIcon className={styles.TriggerIcon} />\n          </Accordion.Trigger>\n        </Accordion.Header>\n        <Accordion.Panel className={styles.Panel}>\n          <div className={styles.Content}>Of course! Base UI is free and open source.</div>\n        </Accordion.Panel>\n      </Accordion.Item>\n    </Accordion.Root>\n  );\n}\n\nfunction PlusIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg viewBox=\"0 0 12 12\" fill=\"currentcolor\" {...props}>\n      <path d=\"M6.75 0H5.25V5.25H0V6.75L5.25 6.75V12H6.75V6.75L12 6.75V5.25H6.75V0Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/accordion/demos/multiple/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoAccordionMultiple = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/accordion/demos/multiple/tailwind/index.tsx",
    "content": "import * as React from 'react';\nimport { Accordion } from '@base-ui/react/accordion';\n\nexport default function ExampleAccordion() {\n  return (\n    <Accordion.Root\n      multiple\n      className=\"flex w-96 max-w-[calc(100vw-8rem)] flex-col justify-center text-gray-900\"\n    >\n      <Accordion.Item className=\"border-b border-gray-200\">\n        <Accordion.Header>\n          <Accordion.Trigger className=\"group relative flex w-full items-baseline justify-between gap-4 bg-gray-50 py-2 pr-1 pl-3 text-left font-normal hover:bg-gray-100 focus-visible:z-1 focus-visible:outline-2 focus-visible:outline-blue-800\">\n            What is Base UI?\n            <PlusIcon className=\"mr-2 size-3 shrink-0 transition-all ease-out group-data-[panel-open]:scale-110 group-data-[panel-open]:rotate-45\" />\n          </Accordion.Trigger>\n        </Accordion.Header>\n        <Accordion.Panel className=\"h-[var(--accordion-panel-height)] overflow-hidden text-base text-gray-600 transition-[height] ease-out data-[ending-style]:h-0 data-[starting-style]:h-0\">\n          <div className=\"p-3\">\n            Base UI is a library of high-quality unstyled React components for design systems and\n            web apps.\n          </div>\n        </Accordion.Panel>\n      </Accordion.Item>\n\n      <Accordion.Item className=\"border-b border-gray-200\">\n        <Accordion.Header>\n          <Accordion.Trigger className=\"group relative flex w-full items-baseline justify-between gap-4 bg-gray-50 py-2 pr-1 pl-3 text-left font-normal hover:bg-gray-100 focus-visible:z-1 focus-visible:outline-2 focus-visible:outline-blue-800\">\n            How do I get started?\n            <PlusIcon className=\"mr-2 size-3 shrink-0 transition-all ease-out group-data-[panel-open]:scale-110 group-data-[panel-open]:rotate-45\" />\n          </Accordion.Trigger>\n        </Accordion.Header>\n        <Accordion.Panel className=\"h-[var(--accordion-panel-height)] overflow-hidden text-base text-gray-600 transition-[height] ease-out data-[ending-style]:h-0 data-[starting-style]:h-0\">\n          <div className=\"p-3\">\n            Head to the “Quick start” guide in the docs. If you’ve used unstyled libraries before,\n            you’ll feel at home.\n          </div>\n        </Accordion.Panel>\n      </Accordion.Item>\n\n      <Accordion.Item className=\"border-b border-gray-200\">\n        <Accordion.Header>\n          <Accordion.Trigger className=\"group relative flex w-full items-baseline justify-between gap-4 bg-gray-50 py-2 pr-1 pl-3 text-left font-normal hover:bg-gray-100 focus-visible:z-1 focus-visible:outline-2 focus-visible:outline-blue-800\">\n            Can I use it for my project?\n            <PlusIcon className=\"mr-2 size-3 shrink-0 transition-all ease-out group-data-[panel-open]:scale-110 group-data-[panel-open]:rotate-45\" />\n          </Accordion.Trigger>\n        </Accordion.Header>\n        <Accordion.Panel className=\"h-[var(--accordion-panel-height)] overflow-hidden text-base text-gray-600 transition-[height] ease-out data-[ending-style]:h-0 data-[starting-style]:h-0\">\n          <div className=\"p-3\">Of course! Base UI is free and open source.</div>\n        </Accordion.Panel>\n      </Accordion.Item>\n    </Accordion.Root>\n  );\n}\n\nfunction PlusIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg viewBox=\"0 0 12 12\" fill=\"currentcolor\" {...props}>\n      <path d=\"M6.75 0H5.25V5.25H0V6.75L5.25 6.75V12H6.75V6.75L12 6.75V5.25H6.75V0Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/accordion/page.mdx",
    "content": "# Accordion\n\n<Subtitle>A set of collapsible panels with headings.</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"A high-quality, unstyled React accordion component that displays a set of collapsible panels with headings.\"\n/>\n\nimport { DemoAccordionHero } from './demos/hero';\n\n<DemoAccordionHero />\n\n## Anatomy\n\nImport the component and assemble its parts:\n\n```jsx title=\"Anatomy\"\nimport { Accordion } from '@base-ui/react/accordion';\n\n<Accordion.Root>\n  <Accordion.Item>\n    <Accordion.Header>\n      <Accordion.Trigger />\n    </Accordion.Header>\n    <Accordion.Panel />\n  </Accordion.Item>\n</Accordion.Root>;\n```\n\n## Examples\n\n### Open multiple panels\n\nYou can set up the accordion to allow multiple panels to be open at the same time using the `multiple` prop.\n\nimport { DemoAccordionMultiple } from './demos/multiple';\n\n<DemoAccordionMultiple compact />\n\n## API reference\n\n<Reference component=\"Accordion\" parts=\"Root, Item, Header, Trigger, Panel\" />\n\nexport const metadata = {\n  keywords: [\n    'React Accordion',\n    'Accordion Component',\n    'Collapsible Content',\n    'Expandable Panel',\n    'Accessible Accordion',\n    'Multi-Panel UI',\n    'Disclosure Widget',\n    'FAQ Component',\n    'Headless React Components',\n    'Base UI',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/alert-dialog/demos/_index.module.css",
    "content": ".Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.DangerButton {\n  color: var(--color-red);\n}\n\n.Backdrop {\n  position: fixed;\n  min-height: 100dvh;\n  inset: 0;\n  background-color: black;\n  opacity: 0.2;\n  transition: opacity 150ms cubic-bezier(0.45, 1.005, 0, 1.005);\n\n  /* iOS 26+: Ensure the backdrop covers the entire visible viewport. */\n  @supports (-webkit-touch-callout: none) {\n    position: absolute;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    opacity: 0.7;\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n  }\n}\n\n.Popup {\n  box-sizing: border-box;\n  position: fixed;\n  top: 50%;\n  left: 50%;\n  transform: translate(-50%, -50%);\n  width: 24rem;\n  max-width: calc(100vw - 3rem);\n  margin-top: -2rem;\n  padding: 1.5rem;\n  border-radius: 0.5rem;\n  outline: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-50);\n  color: var(--color-gray-900);\n  transition: all 150ms;\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: translate(-50%, -50%) scale(0.9);\n  }\n}\n\n.Title {\n  margin-top: -0.375rem;\n  margin-bottom: 0.25rem;\n  font-size: 1.125rem;\n  line-height: 1.75rem;\n  letter-spacing: -0.0025em;\n  font-weight: 700;\n}\n\n.Description {\n  margin: 0 0 1.5rem;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-600);\n}\n\n.Actions {\n  display: flex;\n  justify-content: end;\n  gap: 1rem;\n}\n\n.Container {\n  display: flex;\n  gap: 8px;\n  flex-wrap: wrap;\n  justify-content: center;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/alert-dialog/demos/detached-triggers-controlled/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { AlertDialog } from '@base-ui/react/alert-dialog';\nimport styles from '../../_index.module.css';\n\ntype AlertPayload = { message: string };\n\nconst demoAlertDialog = AlertDialog.createHandle<AlertPayload>();\n\nexport default function AlertDialogDetachedTriggersControlledDemo() {\n  const [open, setOpen] = React.useState(false);\n  const [triggerId, setTriggerId] = React.useState<string | null>(null);\n\n  const handleOpenChange = (isOpen: boolean, eventDetails: AlertDialog.Root.ChangeEventDetails) => {\n    setOpen(isOpen);\n    setTriggerId(eventDetails.trigger?.id ?? null);\n  };\n\n  return (\n    <React.Fragment>\n      <div className={styles.Container}>\n        <AlertDialog.Trigger\n          className={`${styles.Button} ${styles.DangerButton}`}\n          handle={demoAlertDialog}\n          id=\"alert-trigger-1\"\n          payload={{ message: 'Discard draft?' }}\n        >\n          Discard\n        </AlertDialog.Trigger>\n\n        <AlertDialog.Trigger\n          className={`${styles.Button} ${styles.DangerButton}`}\n          handle={demoAlertDialog}\n          id=\"alert-trigger-2\"\n          payload={{ message: 'Delete project?' }}\n        >\n          Delete\n        </AlertDialog.Trigger>\n\n        <AlertDialog.Trigger\n          className={styles.Button}\n          handle={demoAlertDialog}\n          id=\"alert-trigger-3\"\n          payload={{ message: 'Sign out?' }}\n        >\n          Sign out\n        </AlertDialog.Trigger>\n\n        <button\n          className={styles.Button}\n          type=\"button\"\n          onClick={() => {\n            setTriggerId('alert-trigger-2');\n            setOpen(true);\n          }}\n        >\n          Open programmatically\n        </button>\n      </div>\n\n      <AlertDialog.Root<AlertPayload>\n        handle={demoAlertDialog}\n        open={open}\n        onOpenChange={handleOpenChange}\n        triggerId={triggerId}\n      >\n        {({ payload }) => (\n          <AlertDialog.Portal>\n            <AlertDialog.Backdrop className={styles.Backdrop} />\n            <AlertDialog.Popup className={styles.Popup}>\n              <AlertDialog.Title className={styles.Title}>\n                {payload?.message ?? 'Are you sure?'}\n              </AlertDialog.Title>\n              <AlertDialog.Description className={styles.Description}>\n                This action cannot be undone.\n              </AlertDialog.Description>\n              <div className={styles.Actions}>\n                <AlertDialog.Close className={styles.Button}>Cancel</AlertDialog.Close>\n                <AlertDialog.Close className={`${styles.Button} ${styles.DangerButton}`}>\n                  Confirm\n                </AlertDialog.Close>\n              </div>\n            </AlertDialog.Popup>\n          </AlertDialog.Portal>\n        )}\n      </AlertDialog.Root>\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/alert-dialog/demos/detached-triggers-controlled/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoAlertDialogDetachedTriggersControlled = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/alert-dialog/demos/detached-triggers-controlled/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { AlertDialog } from '@base-ui/react/alert-dialog';\n\ntype AlertPayload = { message: string };\n\nconst demoAlertDialog = AlertDialog.createHandle<AlertPayload>();\n\nexport default function AlertDialogDetachedTriggersControlledDemo() {\n  const [open, setOpen] = React.useState(false);\n  const [triggerId, setTriggerId] = React.useState<string | null>(null);\n\n  const handleOpenChange = (isOpen: boolean, eventDetails: AlertDialog.Root.ChangeEventDetails) => {\n    setOpen(isOpen);\n    setTriggerId(eventDetails.trigger?.id ?? null);\n  };\n\n  return (\n    <React.Fragment>\n      <div className=\"flex flex-wrap gap-2 justify-center\">\n        <AlertDialog.Trigger\n          className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-red-800 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\"\n          handle={demoAlertDialog}\n          id=\"alert-trigger-1\"\n          payload={{ message: 'Discard draft?' }}\n        >\n          Discard\n        </AlertDialog.Trigger>\n\n        <AlertDialog.Trigger\n          className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-red-800 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\"\n          handle={demoAlertDialog}\n          id=\"alert-trigger-2\"\n          payload={{ message: 'Delete project?' }}\n        >\n          Delete\n        </AlertDialog.Trigger>\n\n        <AlertDialog.Trigger\n          className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\"\n          handle={demoAlertDialog}\n          id=\"alert-trigger-3\"\n          payload={{ message: 'Sign out?' }}\n        >\n          Sign out\n        </AlertDialog.Trigger>\n\n        <button\n          type=\"button\"\n          className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\"\n          onClick={() => {\n            setTriggerId('alert-trigger-2');\n            setOpen(true);\n          }}\n        >\n          Open programmatically\n        </button>\n      </div>\n\n      <AlertDialog.Root<AlertPayload>\n        handle={demoAlertDialog}\n        open={open}\n        onOpenChange={handleOpenChange}\n        triggerId={triggerId}\n      >\n        {({ payload }) => (\n          <AlertDialog.Portal>\n            <AlertDialog.Backdrop className=\"fixed inset-0 min-h-dvh bg-black opacity-20 transition-all duration-150 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0 dark:opacity-70 supports-[-webkit-touch-callout:none]:absolute\" />\n            <AlertDialog.Popup className=\"fixed top-1/2 left-1/2 -mt-8 w-96 max-w-[calc(100vw-3rem)] -translate-x-1/2 -translate-y-1/2 rounded-lg bg-gray-50 p-6 text-gray-900 outline-1 outline-gray-200 transition-all duration-150 data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 dark:outline-gray-300\">\n              <AlertDialog.Title className=\"-mt-1.5 mb-1 text-lg font-bold\">\n                {payload?.message ?? 'Are you sure?'}\n              </AlertDialog.Title>\n              <AlertDialog.Description className=\"mb-6 text-base text-gray-600\">\n                This action cannot be undone.\n              </AlertDialog.Description>\n              <div className=\"flex justify-end gap-4\">\n                <AlertDialog.Close className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n                  Cancel\n                </AlertDialog.Close>\n                <AlertDialog.Close className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-red-800 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n                  Confirm\n                </AlertDialog.Close>\n              </div>\n            </AlertDialog.Popup>\n          </AlertDialog.Portal>\n        )}\n      </AlertDialog.Root>\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/alert-dialog/demos/detached-triggers-simple/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { AlertDialog } from '@base-ui/react/alert-dialog';\nimport styles from '../../_index.module.css';\n\nconst demoAlertDialog = AlertDialog.createHandle();\n\nexport default function AlertDialogDetachedTriggersSimpleDemo() {\n  return (\n    <React.Fragment>\n      <AlertDialog.Trigger\n        className={`${styles.Button} ${styles.DangerButton}`}\n        handle={demoAlertDialog}\n      >\n        Discard draft\n      </AlertDialog.Trigger>\n\n      <AlertDialog.Root handle={demoAlertDialog}>\n        <AlertDialog.Portal>\n          <AlertDialog.Backdrop className={styles.Backdrop} />\n          <AlertDialog.Popup className={styles.Popup}>\n            <AlertDialog.Title className={styles.Title}>Discard draft?</AlertDialog.Title>\n            <AlertDialog.Description className={styles.Description}>\n              This action cannot be undone.\n            </AlertDialog.Description>\n            <div className={styles.Actions}>\n              <AlertDialog.Close className={styles.Button}>Cancel</AlertDialog.Close>\n              <AlertDialog.Close className={`${styles.Button} ${styles.DangerButton}`}>\n                Discard\n              </AlertDialog.Close>\n            </div>\n          </AlertDialog.Popup>\n        </AlertDialog.Portal>\n      </AlertDialog.Root>\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/alert-dialog/demos/detached-triggers-simple/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoAlertDialogDetachedTriggersSimple = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/alert-dialog/demos/detached-triggers-simple/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { AlertDialog } from '@base-ui/react/alert-dialog';\n\nconst demoAlertDialog = AlertDialog.createHandle();\n\nexport default function AlertDialogDetachedTriggersSimpleDemo() {\n  return (\n    <React.Fragment>\n      <AlertDialog.Trigger\n        className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-red-800 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\"\n        handle={demoAlertDialog}\n      >\n        Discard draft\n      </AlertDialog.Trigger>\n\n      <AlertDialog.Root handle={demoAlertDialog}>\n        <AlertDialog.Portal>\n          <AlertDialog.Backdrop className=\"fixed inset-0 min-h-dvh bg-black opacity-20 transition-all duration-150 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0 dark:opacity-70 supports-[-webkit-touch-callout:none]:absolute\" />\n          <AlertDialog.Popup className=\"fixed top-1/2 left-1/2 -mt-8 w-96 max-w-[calc(100vw-3rem)] -translate-x-1/2 -translate-y-1/2 rounded-lg bg-gray-50 p-6 text-gray-900 outline-1 outline-gray-200 transition-all duration-150 data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 dark:outline-gray-300\">\n            <AlertDialog.Title className=\"-mt-1.5 mb-1 text-lg font-bold\">\n              Discard draft?\n            </AlertDialog.Title>\n            <AlertDialog.Description className=\"mb-6 text-base text-gray-600\">\n              This action cannot be undone.\n            </AlertDialog.Description>\n            <div className=\"flex justify-end gap-4\">\n              <AlertDialog.Close className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n                Cancel\n              </AlertDialog.Close>\n              <AlertDialog.Close className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-red-800 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n                Discard\n              </AlertDialog.Close>\n            </div>\n          </AlertDialog.Popup>\n        </AlertDialog.Portal>\n      </AlertDialog.Root>\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/alert-dialog/demos/hero/css-modules/index.module.css",
    "content": ".Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  &[data-color='red'] {\n    color: var(--color-red);\n  }\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Backdrop {\n  position: fixed;\n  min-height: 100dvh;\n  inset: 0;\n  background-color: black;\n  opacity: 0.2;\n  transition: opacity 150ms;\n\n  /* iOS 26+: Ensure the backdrop covers the entire visible viewport. */\n  @supports (-webkit-touch-callout: none) {\n    position: absolute;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    opacity: 0.7;\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n  }\n}\n\n.Popup {\n  box-sizing: border-box;\n  position: fixed;\n  top: 50%;\n  left: 50%;\n  transform: translate(-50%, -50%);\n  width: 24rem;\n  max-width: calc(100vw - 3rem);\n  margin-top: -2rem;\n  padding: 1.5rem;\n  border-radius: 0.5rem;\n  outline: 1px solid var(--color-gray-300);\n  background-color: var(--color-gray-50);\n  color: var(--color-gray-900);\n  transition: all 150ms;\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: translate(-50%, -50%) scale(0.9);\n  }\n}\n\n.Title {\n  margin-top: -0.375rem;\n  margin-bottom: 0.25rem;\n  font-size: 1.125rem;\n  line-height: 1.75rem;\n  letter-spacing: -0.0025em;\n  font-weight: 700;\n}\n\n.Description {\n  margin: 0 0 1.5rem;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-600);\n}\n\n.Actions {\n  display: flex;\n  justify-content: end;\n  gap: 1rem;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/alert-dialog/demos/hero/css-modules/index.tsx",
    "content": "import { AlertDialog } from '@base-ui/react/alert-dialog';\nimport styles from './index.module.css';\n\nexport default function ExampleAlertDialog() {\n  return (\n    <AlertDialog.Root>\n      <AlertDialog.Trigger data-color=\"red\" className={styles.Button}>\n        Discard draft\n      </AlertDialog.Trigger>\n      <AlertDialog.Portal>\n        <AlertDialog.Backdrop className={styles.Backdrop} />\n        <AlertDialog.Popup className={styles.Popup}>\n          <AlertDialog.Title className={styles.Title}>Discard draft?</AlertDialog.Title>\n          <AlertDialog.Description className={styles.Description}>\n            You can't undo this action.\n          </AlertDialog.Description>\n          <div className={styles.Actions}>\n            <AlertDialog.Close className={styles.Button}>Cancel</AlertDialog.Close>\n            <AlertDialog.Close data-color=\"red\" className={styles.Button}>\n              Discard\n            </AlertDialog.Close>\n          </div>\n        </AlertDialog.Popup>\n      </AlertDialog.Portal>\n    </AlertDialog.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/alert-dialog/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoAlertDialogHero = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/alert-dialog/demos/hero/tailwind/index.tsx",
    "content": "import { AlertDialog } from '@base-ui/react/alert-dialog';\n\nexport default function ExampleAlertDialog() {\n  return (\n    <AlertDialog.Root>\n      <AlertDialog.Trigger className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-red-800 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n        Discard draft\n      </AlertDialog.Trigger>\n      <AlertDialog.Portal>\n        <AlertDialog.Backdrop className=\"fixed inset-0 min-h-dvh bg-black opacity-20 transition-all duration-150 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0 dark:opacity-70 supports-[-webkit-touch-callout:none]:absolute\" />\n        <AlertDialog.Popup className=\"fixed top-1/2 left-1/2 -mt-8 w-96 max-w-[calc(100vw-3rem)] -translate-x-1/2 -translate-y-1/2 rounded-lg bg-gray-50 p-6 text-gray-900 outline-1 outline-gray-200 transition-all duration-150 data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 dark:outline-gray-300\">\n          <AlertDialog.Title className=\"-mt-1.5 mb-1 text-lg font-bold\">\n            Discard draft?\n          </AlertDialog.Title>\n          <AlertDialog.Description className=\"mb-6 text-base text-gray-600\">\n            You can’t undo this action.\n          </AlertDialog.Description>\n          <div className=\"flex justify-end gap-4\">\n            <AlertDialog.Close className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n              Cancel\n            </AlertDialog.Close>\n            <AlertDialog.Close className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-red-800 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n              Discard\n            </AlertDialog.Close>\n          </div>\n        </AlertDialog.Popup>\n      </AlertDialog.Portal>\n    </AlertDialog.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/alert-dialog/page.mdx",
    "content": "# Alert Dialog\n\n<Subtitle>A dialog that requires a user response to proceed.</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"A high-quality, unstyled React alert dialog component that requires a user response to proceed.\"\n/>\n\nimport { DemoAlertDialogHero } from './demos/hero';\n\n<DemoAlertDialogHero />\n\n## Anatomy\n\nImport the component and assemble its parts:\n\n```jsx title=\"Anatomy\"\nimport { AlertDialog } from '@base-ui/react/alert-dialog';\n\n<AlertDialog.Root>\n  <AlertDialog.Trigger />\n  <AlertDialog.Portal>\n    <AlertDialog.Backdrop />\n    <AlertDialog.Viewport>\n      <AlertDialog.Popup>\n        <AlertDialog.Title />\n        <AlertDialog.Description />\n        <AlertDialog.Close />\n      </AlertDialog.Popup>\n    </AlertDialog.Viewport>\n  </AlertDialog.Portal>\n</AlertDialog.Root>;\n```\n\n## Examples\n\n### Open from a menu\n\nIn order to open a dialog using a menu, control the dialog state and open it imperatively using the `onClick` handler on the menu item.\n\n```tsx {12-13,17-18,24-25,28-29} title=\"Connecting a dialog to a menu\"\nimport * as React from 'react';\nimport { AlertDialog } from '@base-ui/react/alert-dialog';\nimport { Menu } from '@base-ui/react/menu';\n\nfunction ExampleMenu() {\n  const [dialogOpen, setDialogOpen] = React.useState(false);\n\n  return (\n    <React.Fragment>\n      <Menu.Root>\n        <Menu.Trigger>Open menu</Menu.Trigger>\n        <Menu.Portal>\n          <Menu.Positioner>\n            <Menu.Popup>\n              {/* Open the dialog when the menu item is clicked */}\n              <Menu.Item onClick={() => setDialogOpen(true)}>Open dialog</Menu.Item>\n            </Menu.Popup>\n          </Menu.Positioner>\n        </Menu.Portal>\n      </Menu.Root>\n\n      {/* Control the dialog state */}\n      <AlertDialog.Root open={dialogOpen} onOpenChange={setDialogOpen}>\n        <AlertDialog.Portal>\n          <AlertDialog.Backdrop />\n          <AlertDialog.Popup>\n            {/* prettier-ignore */}\n            {/* Rest of the dialog */}\n          </AlertDialog.Popup>\n        </AlertDialog.Portal>\n      </AlertDialog.Root>\n    </React.Fragment>\n  );\n}\n```\n\n### Close confirmation\n\nThis example shows a nested confirmation dialog that opens if the text entered in the parent dialog is going to be discarded.\n\nTo implement this, both dialogs should be controlled. The confirmation dialog may be opened when `onOpenChange` callback of the parent dialog receives a request to close. This way, the confirmation is automatically shown when the user clicks the backdrop, presses the Esc key, or clicks a close button.\n\nUse the `[data-nested-dialog-open]` selector and the `var(--nested-dialogs)` CSS variable to customize the styling of the parent dialog. Backdrops of the child dialogs won't be rendered so that you can present the parent dialog in a clean way behind the one on top of it.\n\nimport { DemoDialogCloseConfirmation } from '../dialog/demos/close-confirmation';\n\n<DemoDialogCloseConfirmation compact />\n\n### Detached triggers\n\nAn alert dialog can be controlled by a trigger located either inside or outside the `<AlertDialog.Root>` component.\nFor simple, one-off interactions, place the `<AlertDialog.Trigger>` inside `<AlertDialog.Root>`, as shown in the example at the top of this page.\n\nHowever, if defining the alert dialog's content next to its trigger is not practical, you can use a detached trigger.\nThis involves placing the `<AlertDialog.Trigger>` outside of `<AlertDialog.Root>` and linking them with a `handle` created by the `AlertDialog.createHandle()` function.\n\n```jsx title=\"Detached triggers\" {3,5} \"handle={demoAlertDialog}\"\nconst demoAlertDialog = AlertDialog.createHandle();\n\n<AlertDialog.Trigger handle={demoAlertDialog}>Open</AlertDialog.Trigger>\n\n<AlertDialog.Root handle={demoAlertDialog}>\n  ...\n</AlertDialog.Root>\n```\n\nimport { DemoAlertDialogDetachedTriggersSimple } from './demos/detached-triggers-simple';\n\n<DemoAlertDialogDetachedTriggersSimple />\n\n### Multiple triggers\n\nA single alert dialog can be opened by multiple trigger elements.\nYou can achieve this by using the same `handle` for several detached triggers, or by placing multiple `<AlertDialog.Trigger>` components inside a single `<AlertDialog.Root>`.\n\n```jsx title=\"Multiple triggers within the Root part\"\n<AlertDialog.Root>\n  <AlertDialog.Trigger>Trigger 1</AlertDialog.Trigger>\n  <AlertDialog.Trigger>Trigger 2</AlertDialog.Trigger>\n  ...\n</AlertDialog.Root>\n```\n\n```jsx title=\"Multiple detached triggers\"\nconst demoAlertDialog = AlertDialog.createHandle();\n\n<AlertDialog.Trigger handle={demoAlertDialog}>Trigger 1</AlertDialog.Trigger>\n<AlertDialog.Trigger handle={demoAlertDialog}>Trigger 2</AlertDialog.Trigger>\n<AlertDialog.Root handle={demoAlertDialog}>\n  ...\n</AlertDialog.Root>\n```\n\nThe alert dialog can render different content depending on which trigger opened it.\nThis is achieved by passing a `payload` to the `<AlertDialog.Trigger>` and using the function-as-a-child pattern in `<AlertDialog.Root>`.\n\nThe payload can be strongly typed by providing a type argument to the `createHandle()` function:\n\n```jsx title=\"Detached triggers with payload\" {1,3,7,12}\nconst demoAlertDialog = AlertDialog.createHandle<{ message: string }>();\n\n<AlertDialog.Trigger handle={demoAlertDialog} payload={{ message: 'Trigger 1' }}>\n  Trigger 1\n</AlertDialog.Trigger>\n\n<AlertDialog.Trigger handle={demoAlertDialog} payload={{ message: 'Trigger 2' }}>\n  Trigger 2\n</AlertDialog.Trigger>\n\n<AlertDialog.Root handle={demoAlertDialog}>\n  {({ payload }) => (\n    <AlertDialog.Portal>\n      <AlertDialog.Popup>\n        <AlertDialog.Title>Alert dialog</AlertDialog.Title>\n        {payload !== undefined && (\n          <AlertDialog.Description>\n            Confirming {payload.message}\n          </AlertDialog.Description>\n        )}\n      </AlertDialog.Popup>\n    </AlertDialog.Portal>\n  )}\n</AlertDialog.Root>\n```\n\n### Controlled mode with multiple triggers\n\nYou can control the alert dialog's open state externally using the `open` and `onOpenChange` props on `<AlertDialog.Root>`.\nThis allows you to manage the alert dialog's visibility based on your application's state.\nWhen using multiple triggers, you have to manage which trigger is active with the `triggerId` prop on `<AlertDialog.Root>` and the `id` prop on each `<AlertDialog.Trigger>`.\n\nNote that there is no separate `onTriggerIdChange` prop.\nInstead, the `onOpenChange` callback receives an additional argument, `eventDetails`, which contains the trigger element that initiated the state change.\n\nimport { DemoAlertDialogDetachedTriggersControlled } from './demos/detached-triggers-controlled';\n\n<DemoAlertDialogDetachedTriggersControlled />\n\n## API reference\n\n<Reference\n  component=\"AlertDialog\"\n  parts=\"Root, Trigger, Portal, Backdrop, Viewport, Popup, Title, Description, Close\"\n/>\n\nexport const metadata = {\n  keywords: [\n    'React Alert Dialog',\n    'Alert Dialog Component',\n    'Confirmation Modal',\n    'Warning Dialog',\n    'Blocking Modal',\n    'Interrupt Dialog',\n    'Destructive Action Confirm',\n    'Accessible Dialog',\n    'Headless React Components',\n    'React UI Overlay',\n    'Base UI',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/async/css-modules/index.module.css",
    "content": ".Input {\n  box-sizing: border-box;\n  padding-left: 0.875rem;\n  margin: 0;\n  border: 1px solid var(--color-gray-200);\n  width: 16rem;\n  height: 2.5rem;\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  outline: none;\n\n  &:focus {\n    border-color: var(--color-blue);\n    outline: 1px solid var(--color-blue);\n  }\n\n  @media (min-width: 500px) {\n    width: 20rem;\n  }\n}\n\n.Label {\n  display: flex;\n  flex-direction: column;\n  gap: 0.25rem;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 700;\n  color: var(--color-gray-900);\n}\n\n.Positioner {\n  outline: 0;\n}\n\n.Popup {\n  box-sizing: border-box;\n  padding-block: 0.5rem;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  width: var(--anchor-width);\n  max-height: min(var(--available-height), 23rem);\n  max-width: var(--available-width);\n  overflow-y: auto;\n  scroll-padding-block: 0.5rem;\n  overscroll-behavior: contain;\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Item {\n  box-sizing: border-box;\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  padding-block: 0.5rem;\n  padding-left: 1rem;\n  padding-right: 2rem;\n  display: flex;\n  font-size: 1rem;\n  line-height: 1rem;\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.5rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.MovieItem {\n  display: flex;\n  flex-direction: column;\n  gap: 0.25rem;\n  width: 100%;\n}\n\n.MovieName {\n  font-weight: 700;\n  line-height: 1.25rem;\n}\n\n.MovieYear {\n  font-size: 0.875rem;\n  line-height: 1rem;\n  opacity: 0.8;\n}\n\n.Status {\n  display: flex;\n  align-items: center;\n  gap: 0.5rem;\n  padding-block: 0.25rem;\n  padding-left: 1rem;\n  padding-right: 2rem;\n  font-size: 0.875rem;\n  text-align: center;\n  color: var(--color-gray-600);\n}\n\n.Spinner {\n  box-sizing: border-box;\n  width: 1rem;\n  height: 1rem;\n  border: 2px solid var(--color-gray-200);\n  border-top: 2px solid var(--color-gray-600);\n  border-radius: 50%;\n  animation: spin 1s linear infinite;\n}\n\n@keyframes spin {\n  0% {\n    transform: rotate(0deg);\n  }\n  100% {\n    transform: rotate(360deg);\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/async/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Autocomplete } from '@base-ui/react/autocomplete';\nimport styles from './index.module.css';\n\nexport default function ExampleAsyncAutocomplete() {\n  const [searchValue, setSearchValue] = React.useState('');\n  const [searchResults, setSearchResults] = React.useState<Movie[]>([]);\n  const [error, setError] = React.useState<string | null>(null);\n\n  const [isPending, startTransition] = React.useTransition();\n\n  const { contains } = Autocomplete.useFilter();\n\n  const abortControllerRef = React.useRef<AbortController | null>(null);\n\n  function getStatus(): React.ReactNode | null {\n    if (isPending) {\n      return (\n        <React.Fragment>\n          <div className={styles.Spinner} aria-hidden />\n          Searching…\n        </React.Fragment>\n      );\n    }\n\n    if (error) {\n      return error;\n    }\n\n    if (searchValue === '') {\n      return null;\n    }\n\n    if (searchResults.length === 0) {\n      return `Movie or year \"${searchValue}\" does not exist in the Top 100 IMDb movies`;\n    }\n\n    return `${searchResults.length} result${searchResults.length === 1 ? '' : 's'} found`;\n  }\n\n  const status = getStatus();\n\n  return (\n    <Autocomplete.Root\n      items={searchResults}\n      value={searchValue}\n      onValueChange={(nextSearchValue) => {\n        setSearchValue(nextSearchValue);\n\n        const controller = new AbortController();\n        abortControllerRef.current?.abort();\n        abortControllerRef.current = controller;\n\n        if (nextSearchValue === '') {\n          setSearchResults([]);\n          setError(null);\n          return;\n        }\n\n        startTransition(async () => {\n          setError(null);\n\n          const result = await searchMovies(nextSearchValue, contains);\n          if (controller.signal.aborted) {\n            return;\n          }\n\n          startTransition(() => {\n            setSearchResults(result.movies);\n            setError(result.error);\n          });\n        });\n      }}\n      itemToStringValue={(item) => item.title}\n      filter={null}\n    >\n      <label className={styles.Label}>\n        Search movies by name or year\n        <Autocomplete.Input placeholder=\"e.g. Pulp Fiction or 1994\" className={styles.Input} />\n      </label>\n\n      <Autocomplete.Portal hidden={!status}>\n        <Autocomplete.Positioner className={styles.Positioner} sideOffset={4} align=\"start\">\n          <Autocomplete.Popup className={styles.Popup} aria-busy={isPending || undefined}>\n            <Autocomplete.Status>\n              {status && <div className={styles.Status}>{status}</div>}\n            </Autocomplete.Status>\n            <Autocomplete.List>\n              {(movie: Movie) => (\n                <Autocomplete.Item key={movie.id} className={styles.Item} value={movie}>\n                  <div className={styles.MovieItem}>\n                    <div className={styles.MovieName}>{movie.title}</div>\n                    <div className={styles.MovieYear}>{movie.year}</div>\n                  </div>\n                </Autocomplete.Item>\n              )}\n            </Autocomplete.List>\n          </Autocomplete.Popup>\n        </Autocomplete.Positioner>\n      </Autocomplete.Portal>\n    </Autocomplete.Root>\n  );\n}\n\nasync function searchMovies(\n  query: string,\n  filter: (item: string, query: string) => boolean,\n): Promise<{ movies: Movie[]; error: string | null }> {\n  // Simulate network delay\n  await new Promise((resolve) => {\n    setTimeout(resolve, Math.random() * 500 + 100);\n  });\n\n  // Simulate occasional network errors (1% chance)\n  if (Math.random() < 0.01 || query === 'will_error') {\n    return {\n      movies: [],\n      error: 'Failed to fetch movies. Please try again.',\n    };\n  }\n\n  const movies = top100Movies.filter(\n    (movie) => filter(movie.title, query) || filter(movie.year.toString(), query),\n  );\n\n  return {\n    movies,\n    error: null,\n  };\n}\n\ninterface Movie {\n  id: string;\n  title: string;\n  year: number;\n}\n\nconst top100Movies: Movie[] = [\n  { id: '1', title: 'The Shawshank Redemption', year: 1994 },\n  { id: '2', title: 'The Godfather', year: 1972 },\n  { id: '3', title: 'The Dark Knight', year: 2008 },\n  { id: '4', title: 'The Godfather Part II', year: 1974 },\n  { id: '5', title: '12 Angry Men', year: 1957 },\n  { id: '6', title: 'The Lord of the Rings: The Return of the King', year: 2003 },\n  { id: '7', title: \"Schindler's List\", year: 1993 },\n  { id: '8', title: 'Pulp Fiction', year: 1994 },\n  { id: '9', title: 'The Lord of the Rings: The Fellowship of the Ring', year: 2001 },\n  { id: '10', title: 'The Good, the Bad and the Ugly', year: 1966 },\n  { id: '11', title: 'Forrest Gump', year: 1994 },\n  { id: '12', title: 'The Lord of the Rings: The Two Towers', year: 2002 },\n  { id: '13', title: 'Fight Club', year: 1999 },\n  { id: '14', title: 'Inception', year: 2010 },\n  { id: '15', title: 'Star Wars: Episode V – The Empire Strikes Back', year: 1980 },\n  { id: '16', title: 'The Matrix', year: 1999 },\n  { id: '17', title: 'Goodfellas', year: 1990 },\n  { id: '18', title: 'Interstellar', year: 2014 },\n  { id: '19', title: \"One Flew Over the Cuckoo's Nest\", year: 1975 },\n  { id: '20', title: 'Se7en', year: 1995 },\n  { id: '21', title: \"It's a Wonderful Life\", year: 1946 },\n  { id: '22', title: 'The Silence of the Lambs', year: 1991 },\n  { id: '23', title: 'Seven Samurai', year: 1954 },\n  { id: '24', title: 'Saving Private Ryan', year: 1998 },\n  { id: '25', title: 'City of God', year: 2002 },\n  { id: '26', title: 'Life Is Beautiful', year: 1997 },\n  { id: '27', title: 'The Green Mile', year: 1999 },\n  { id: '28', title: 'Star Wars: Episode IV – A New Hope', year: 1977 },\n  { id: '29', title: 'Terminator 2: Judgment Day', year: 1991 },\n  { id: '30', title: 'Back to the Future', year: 1985 },\n  { id: '31', title: 'Spirited Away', year: 2001 },\n  { id: '32', title: 'The Pianist', year: 2002 },\n  { id: '33', title: 'Psycho', year: 1960 },\n  { id: '34', title: 'Parasite', year: 2019 },\n  { id: '35', title: 'Gladiator', year: 2000 },\n  { id: '36', title: 'Léon: The Professional', year: 1994 },\n  { id: '37', title: 'American History X', year: 1998 },\n  { id: '38', title: 'The Departed', year: 2006 },\n  { id: '39', title: 'Whiplash', year: 2014 },\n  { id: '40', title: 'The Prestige', year: 2006 },\n  { id: '41', title: 'Grave of the Fireflies', year: 1988 },\n  { id: '42', title: 'The Usual Suspects', year: 1995 },\n  { id: '43', title: 'Casablanca', year: 1942 },\n  { id: '44', title: 'Harakiri', year: 1962 },\n  { id: '45', title: 'The Lion King', year: 1994 },\n  { id: '46', title: 'The Intouchables', year: 2011 },\n  { id: '47', title: 'Modern Times', year: 1936 },\n  { id: '48', title: 'The Lives of Others', year: 2006 },\n  { id: '49', title: 'Once Upon a Time in the West', year: 1968 },\n  { id: '50', title: 'Rear Window', year: 1954 },\n  { id: '51', title: 'Alien', year: 1979 },\n  { id: '52', title: 'City Lights', year: 1931 },\n  { id: '53', title: 'The Shining', year: 1980 },\n  { id: '54', title: 'Cinema Paradiso', year: 1988 },\n  { id: '55', title: 'Avengers: Infinity War', year: 2018 },\n  { id: '56', title: 'Paths of Glory', year: 1957 },\n  { id: '57', title: 'Django Unchained', year: 2012 },\n  { id: '58', title: 'WALL·E', year: 2008 },\n  { id: '59', title: 'Sunset Boulevard', year: 1950 },\n  { id: '60', title: 'The Great Dictator', year: 1940 },\n  { id: '61', title: 'The Dark Knight Rises', year: 2012 },\n  { id: '62', title: 'Princess Mononoke', year: 1997 },\n  { id: '63', title: 'Witness for the Prosecution', year: 1957 },\n  { id: '64', title: 'Oldboy', year: 2003 },\n  { id: '65', title: 'Aliens', year: 1986 },\n  { id: '66', title: 'Once Upon a Time in America', year: 1984 },\n  { id: '67', title: 'Coco', year: 2017 },\n  { id: '68', title: 'Your Name.', year: 2016 },\n  { id: '69', title: 'American Beauty', year: 1999 },\n  { id: '70', title: 'Braveheart', year: 1995 },\n  { id: '71', title: 'Das Boot', year: 1981 },\n  { id: '72', title: '3 Idiots', year: 2009 },\n  { id: '73', title: 'Toy Story', year: 1995 },\n  { id: '74', title: 'Inglourious Basterds', year: 2009 },\n  { id: '75', title: 'High and Low', year: 1963 },\n  { id: '76', title: 'Amadeus', year: 1984 },\n  { id: '77', title: 'Good Will Hunting', year: 1997 },\n  { id: '78', title: 'Star Wars: Episode VI – Return of the Jedi', year: 1983 },\n  { id: '79', title: 'The Hunt', year: 2012 },\n  { id: '80', title: 'Capharnaüm', year: 2018 },\n  { id: '81', title: 'Reservoir Dogs', year: 1992 },\n  { id: '82', title: 'Eternal Sunshine of the Spotless Mind', year: 2004 },\n  { id: '83', title: 'Requiem for a Dream', year: 2000 },\n  { id: '84', title: 'Come and See', year: 1985 },\n  { id: '85', title: 'Ikiru', year: 1952 },\n  { id: '86', title: 'Vertigo', year: 1958 },\n  { id: '87', title: 'Lawrence of Arabia', year: 1962 },\n  { id: '88', title: 'Citizen Kane', year: 1941 },\n  { id: '89', title: 'Memento', year: 2000 },\n  { id: '90', title: 'North by Northwest', year: 1959 },\n  { id: '91', title: 'Star Wars: Episode III – Revenge of the Sith', year: 2005 },\n  { id: '92', title: '2001: A Space Odyssey', year: 1968 },\n  { id: '93', title: 'Amélie', year: 2001 },\n  { id: '94', title: \"Singin' in the Rain\", year: 1952 },\n  { id: '95', title: 'Apocalypse Now', year: 1979 },\n  { id: '96', title: 'Taxi Driver', year: 1976 },\n  { id: '97', title: 'Downfall', year: 2004 },\n  { id: '98', title: 'The Wolf of Wall Street', year: 2013 },\n  { id: '99', title: 'A Clockwork Orange', year: 1971 },\n  { id: '100', title: 'Double Indemnity', year: 1944 },\n];\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/async/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoAutocompleteAsync = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/async/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Autocomplete } from '@base-ui/react/autocomplete';\n\nexport default function ExampleAsyncAutocomplete() {\n  const [searchValue, setSearchValue] = React.useState('');\n  const [searchResults, setSearchResults] = React.useState<Movie[]>([]);\n  const [error, setError] = React.useState<string | null>(null);\n\n  const [isPending, startTransition] = React.useTransition();\n\n  const { contains } = Autocomplete.useFilter();\n\n  const abortControllerRef = React.useRef<AbortController | null>(null);\n\n  function getStatus(): React.ReactNode | null {\n    if (isPending) {\n      return (\n        <React.Fragment>\n          <div\n            className=\"size-4 rounded-full border-2 border-gray-200 border-t-gray-600 animate-spin\"\n            aria-hidden\n          />\n          Searching…\n        </React.Fragment>\n      );\n    }\n\n    if (error) {\n      return error;\n    }\n\n    if (searchValue === '') {\n      return null;\n    }\n\n    if (searchResults.length === 0) {\n      return `Movie or year \"${searchValue}\" does not exist in the Top 100 IMDb movies`;\n    }\n\n    return `${searchResults.length} result${searchResults.length === 1 ? '' : 's'} found`;\n  }\n\n  const status = getStatus();\n\n  return (\n    <Autocomplete.Root\n      items={searchResults}\n      value={searchValue}\n      onValueChange={(nextSearchValue) => {\n        setSearchValue(nextSearchValue);\n\n        const controller = new AbortController();\n        abortControllerRef.current?.abort();\n        abortControllerRef.current = controller;\n\n        if (nextSearchValue === '') {\n          setSearchResults([]);\n          setError(null);\n          return;\n        }\n\n        startTransition(async () => {\n          setError(null);\n\n          const result = await searchMovies(nextSearchValue, contains);\n          if (controller.signal.aborted) {\n            return;\n          }\n\n          startTransition(() => {\n            setSearchResults(result.movies);\n            setError(result.error);\n          });\n        });\n      }}\n      itemToStringValue={(item) => item.title}\n      filter={null}\n    >\n      <label className=\"flex flex-col gap-1 text-sm leading-5 font-bold text-gray-900\">\n        Search movies by name or year\n        <Autocomplete.Input\n          placeholder=\"e.g. Pulp Fiction or 1994\"\n          className=\"bg-[canvas] h-10 w-[16rem] md:w-[20rem] font-normal rounded-md border border-gray-200 pl-3.5 text-base font-normal text-gray-900 focus:outline-2 focus:-outline-offset-1 focus:outline-blue-800\"\n        />\n      </label>\n\n      <Autocomplete.Portal hidden={!status}>\n        <Autocomplete.Positioner className=\"outline-hidden\" sideOffset={4} align=\"start\">\n          <Autocomplete.Popup\n            className=\"w-[var(--anchor-width)] max-h-[min(var(--available-height),23rem)] max-w-[var(--available-width)] overflow-y-auto scroll-pt-2 scroll-pb-2 overscroll-contain rounded-md bg-[canvas] py-2 text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300\"\n            aria-busy={isPending || undefined}\n          >\n            <Autocomplete.Status>\n              {status && (\n                <div className=\"flex items-center gap-2 py-1 pl-4 pr-8 text-sm text-gray-600\">\n                  {status}\n                </div>\n              )}\n            </Autocomplete.Status>\n            <Autocomplete.List>\n              {(movie: Movie) => (\n                <Autocomplete.Item\n                  key={movie.id}\n                  className=\"flex cursor-default py-2 pr-8 pl-4 text-base leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-2 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-sm data-[highlighted]:before:bg-gray-900\"\n                  value={movie}\n                >\n                  <div className=\"flex w-full flex-col gap-1\">\n                    <div className=\"font-bold leading-5\">{movie.title}</div>\n                    <div className=\"text-sm leading-4 opacity-80\">{movie.year}</div>\n                  </div>\n                </Autocomplete.Item>\n              )}\n            </Autocomplete.List>\n          </Autocomplete.Popup>\n        </Autocomplete.Positioner>\n      </Autocomplete.Portal>\n    </Autocomplete.Root>\n  );\n}\n\nasync function searchMovies(\n  query: string,\n  filter: (item: string, query: string) => boolean,\n): Promise<{ movies: Movie[]; error: string | null }> {\n  // Simulate network delay\n  await new Promise((resolve) => {\n    setTimeout(resolve, Math.random() * 500 + 100);\n  });\n\n  // Simulate occasional network errors (1% chance)\n  if (Math.random() < 0.01 || query === 'will_error') {\n    return {\n      movies: [],\n      error: 'Failed to fetch movies. Please try again.',\n    };\n  }\n\n  const movies = top100Movies.filter(\n    (movie) => filter(movie.title, query) || filter(movie.year.toString(), query),\n  );\n\n  return {\n    movies,\n    error: null,\n  };\n}\n\ninterface Movie {\n  id: string;\n  title: string;\n  year: number;\n}\n\nconst top100Movies: Movie[] = [\n  { id: '1', title: 'The Shawshank Redemption', year: 1994 },\n  { id: '2', title: 'The Godfather', year: 1972 },\n  { id: '3', title: 'The Dark Knight', year: 2008 },\n  { id: '4', title: 'The Godfather Part II', year: 1974 },\n  { id: '5', title: '12 Angry Men', year: 1957 },\n  { id: '6', title: 'The Lord of the Rings: The Return of the King', year: 2003 },\n  { id: '7', title: \"Schindler's List\", year: 1993 },\n  { id: '8', title: 'Pulp Fiction', year: 1994 },\n  { id: '9', title: 'The Lord of the Rings: The Fellowship of the Ring', year: 2001 },\n  { id: '10', title: 'The Good, the Bad and the Ugly', year: 1966 },\n  { id: '11', title: 'Forrest Gump', year: 1994 },\n  { id: '12', title: 'The Lord of the Rings: The Two Towers', year: 2002 },\n  { id: '13', title: 'Fight Club', year: 1999 },\n  { id: '14', title: 'Inception', year: 2010 },\n  { id: '15', title: 'Star Wars: Episode V – The Empire Strikes Back', year: 1980 },\n  { id: '16', title: 'The Matrix', year: 1999 },\n  { id: '17', title: 'Goodfellas', year: 1990 },\n  { id: '18', title: 'Interstellar', year: 2014 },\n  { id: '19', title: \"One Flew Over the Cuckoo's Nest\", year: 1975 },\n  { id: '20', title: 'Se7en', year: 1995 },\n  { id: '21', title: \"It's a Wonderful Life\", year: 1946 },\n  { id: '22', title: 'The Silence of the Lambs', year: 1991 },\n  { id: '23', title: 'Seven Samurai', year: 1954 },\n  { id: '24', title: 'Saving Private Ryan', year: 1998 },\n  { id: '25', title: 'City of God', year: 2002 },\n  { id: '26', title: 'Life Is Beautiful', year: 1997 },\n  { id: '27', title: 'The Green Mile', year: 1999 },\n  { id: '28', title: 'Star Wars: Episode IV – A New Hope', year: 1977 },\n  { id: '29', title: 'Terminator 2: Judgment Day', year: 1991 },\n  { id: '30', title: 'Back to the Future', year: 1985 },\n  { id: '31', title: 'Spirited Away', year: 2001 },\n  { id: '32', title: 'The Pianist', year: 2002 },\n  { id: '33', title: 'Psycho', year: 1960 },\n  { id: '34', title: 'Parasite', year: 2019 },\n  { id: '35', title: 'Gladiator', year: 2000 },\n  { id: '36', title: 'Léon: The Professional', year: 1994 },\n  { id: '37', title: 'American History X', year: 1998 },\n  { id: '38', title: 'The Departed', year: 2006 },\n  { id: '39', title: 'Whiplash', year: 2014 },\n  { id: '40', title: 'The Prestige', year: 2006 },\n  { id: '41', title: 'Grave of the Fireflies', year: 1988 },\n  { id: '42', title: 'The Usual Suspects', year: 1995 },\n  { id: '43', title: 'Casablanca', year: 1942 },\n  { id: '44', title: 'Harakiri', year: 1962 },\n  { id: '45', title: 'The Lion King', year: 1994 },\n  { id: '46', title: 'The Intouchables', year: 2011 },\n  { id: '47', title: 'Modern Times', year: 1936 },\n  { id: '48', title: 'The Lives of Others', year: 2006 },\n  { id: '49', title: 'Once Upon a Time in the West', year: 1968 },\n  { id: '50', title: 'Rear Window', year: 1954 },\n  { id: '51', title: 'Alien', year: 1979 },\n  { id: '52', title: 'City Lights', year: 1931 },\n  { id: '53', title: 'The Shining', year: 1980 },\n  { id: '54', title: 'Cinema Paradiso', year: 1988 },\n  { id: '55', title: 'Avengers: Infinity War', year: 2018 },\n  { id: '56', title: 'Paths of Glory', year: 1957 },\n  { id: '57', title: 'Django Unchained', year: 2012 },\n  { id: '58', title: 'WALL·E', year: 2008 },\n  { id: '59', title: 'Sunset Boulevard', year: 1950 },\n  { id: '60', title: 'The Great Dictator', year: 1940 },\n  { id: '61', title: 'The Dark Knight Rises', year: 2012 },\n  { id: '62', title: 'Princess Mononoke', year: 1997 },\n  { id: '63', title: 'Witness for the Prosecution', year: 1957 },\n  { id: '64', title: 'Oldboy', year: 2003 },\n  { id: '65', title: 'Aliens', year: 1986 },\n  { id: '66', title: 'Once Upon a Time in America', year: 1984 },\n  { id: '67', title: 'Coco', year: 2017 },\n  { id: '68', title: 'Your Name.', year: 2016 },\n  { id: '69', title: 'American Beauty', year: 1999 },\n  { id: '70', title: 'Braveheart', year: 1995 },\n  { id: '71', title: 'Das Boot', year: 1981 },\n  { id: '72', title: '3 Idiots', year: 2009 },\n  { id: '73', title: 'Toy Story', year: 1995 },\n  { id: '74', title: 'Inglourious Basterds', year: 2009 },\n  { id: '75', title: 'High and Low', year: 1963 },\n  { id: '76', title: 'Amadeus', year: 1984 },\n  { id: '77', title: 'Good Will Hunting', year: 1997 },\n  { id: '78', title: 'Star Wars: Episode VI – Return of the Jedi', year: 1983 },\n  { id: '79', title: 'The Hunt', year: 2012 },\n  { id: '80', title: 'Capharnaüm', year: 2018 },\n  { id: '81', title: 'Reservoir Dogs', year: 1992 },\n  { id: '82', title: 'Eternal Sunshine of the Spotless Mind', year: 2004 },\n  { id: '83', title: 'Requiem for a Dream', year: 2000 },\n  { id: '84', title: 'Come and See', year: 1985 },\n  { id: '85', title: 'Ikiru', year: 1952 },\n  { id: '86', title: 'Vertigo', year: 1958 },\n  { id: '87', title: 'Lawrence of Arabia', year: 1962 },\n  { id: '88', title: 'Citizen Kane', year: 1941 },\n  { id: '89', title: 'Memento', year: 2000 },\n  { id: '90', title: 'North by Northwest', year: 1959 },\n  { id: '91', title: 'Star Wars: Episode III – Revenge of the Sith', year: 2005 },\n  { id: '92', title: '2001: A Space Odyssey', year: 1968 },\n  { id: '93', title: 'Amélie', year: 2001 },\n  { id: '94', title: \"Singin' in the Rain\", year: 1952 },\n  { id: '95', title: 'Apocalypse Now', year: 1979 },\n  { id: '96', title: 'Taxi Driver', year: 1976 },\n  { id: '97', title: 'Downfall', year: 2004 },\n  { id: '98', title: 'The Wolf of Wall Street', year: 2013 },\n  { id: '99', title: 'A Clockwork Orange', year: 1971 },\n  { id: '100', title: 'Double Indemnity', year: 1944 },\n];\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/auto-highlight/css-modules/index.module.css",
    "content": ".Input {\n  box-sizing: border-box;\n  padding-left: 0.875rem;\n  margin: 0;\n  border: 1px solid var(--color-gray-200);\n  width: 16rem;\n  height: 2.5rem;\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  outline: none;\n\n  &:focus {\n    border-color: var(--color-blue);\n    outline: 1px solid var(--color-blue);\n  }\n\n  @media (min-width: 500px) {\n    width: 20rem;\n  }\n}\n\n.Label {\n  display: flex;\n  flex-direction: column;\n  gap: 0.25rem;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 700;\n  color: var(--color-gray-900);\n}\n\n.Positioner {\n  outline: 0;\n}\n\n.Popup {\n  box-sizing: border-box;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  width: var(--anchor-width);\n  max-height: 23rem;\n  max-width: var(--available-width);\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.List {\n  box-sizing: border-box;\n  overflow-y: auto;\n  overscroll-behavior: contain;\n  padding-block: 0.5rem;\n  scroll-padding-block: 0.5rem;\n  outline: 0;\n  max-height: min(23rem, var(--available-height));\n\n  &[data-empty] {\n    padding: 0;\n  }\n}\n\n.Item {\n  box-sizing: border-box;\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  padding-block: 0.5rem;\n  padding-left: 1rem;\n  padding-right: 2rem;\n  display: flex;\n  font-size: 1rem;\n  line-height: 1rem;\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.5rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.Empty:not(:empty) {\n  box-sizing: border-box;\n  padding: 1rem;\n  font-size: 0.925rem;\n  line-height: 1rem;\n  color: var(--color-gray-600);\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/auto-highlight/css-modules/index.tsx",
    "content": "'use client';\nimport { Autocomplete } from '@base-ui/react/autocomplete';\nimport styles from './index.module.css';\n\nexport default function ExampleAutocompleteAutoHighlight() {\n  return (\n    <Autocomplete.Root items={tags} autoHighlight>\n      <label className={styles.Label}>\n        Auto highlight on type\n        <Autocomplete.Input placeholder=\"e.g. feature\" className={styles.Input} />\n      </label>\n\n      <Autocomplete.Portal>\n        <Autocomplete.Positioner className={styles.Positioner} sideOffset={4}>\n          <Autocomplete.Popup className={styles.Popup}>\n            <Autocomplete.Empty className={styles.Empty}>No tags found.</Autocomplete.Empty>\n            <Autocomplete.List className={styles.List}>\n              {(tag: Tag) => (\n                <Autocomplete.Item key={tag.id} className={styles.Item} value={tag}>\n                  {tag.value}\n                </Autocomplete.Item>\n              )}\n            </Autocomplete.List>\n          </Autocomplete.Popup>\n        </Autocomplete.Positioner>\n      </Autocomplete.Portal>\n    </Autocomplete.Root>\n  );\n}\n\ninterface Tag {\n  id: string;\n  value: string;\n}\n\nconst tags: Tag[] = [\n  { id: 't1', value: 'feature' },\n  { id: 't2', value: 'fix' },\n  { id: 't3', value: 'bug' },\n  { id: 't4', value: 'docs' },\n  { id: 't5', value: 'internal' },\n  { id: 't6', value: 'mobile' },\n  { id: 'c-accordion', value: 'component: accordion' },\n  { id: 'c-alert-dialog', value: 'component: alert dialog' },\n  { id: 'c-autocomplete', value: 'component: autocomplete' },\n  { id: 'c-avatar', value: 'component: avatar' },\n  { id: 'c-checkbox', value: 'component: checkbox' },\n  { id: 'c-checkbox-group', value: 'component: checkbox group' },\n  { id: 'c-collapsible', value: 'component: collapsible' },\n  { id: 'c-combobox', value: 'component: combobox' },\n  { id: 'c-context-menu', value: 'component: context menu' },\n  { id: 'c-dialog', value: 'component: dialog' },\n  { id: 'c-field', value: 'component: field' },\n  { id: 'c-fieldset', value: 'component: fieldset' },\n  { id: 'c-filterable-menu', value: 'component: filterable menu' },\n  { id: 'c-form', value: 'component: form' },\n  { id: 'c-input', value: 'component: input' },\n  { id: 'c-menu', value: 'component: menu' },\n  { id: 'c-menubar', value: 'component: menubar' },\n  { id: 'c-meter', value: 'component: meter' },\n  { id: 'c-navigation-menu', value: 'component: navigation menu' },\n  { id: 'c-number-field', value: 'component: number field' },\n  { id: 'c-popover', value: 'component: popover' },\n  { id: 'c-preview-card', value: 'component: preview card' },\n  { id: 'c-progress', value: 'component: progress' },\n  { id: 'c-radio', value: 'component: radio' },\n  { id: 'c-scroll-area', value: 'component: scroll area' },\n  { id: 'c-select', value: 'component: select' },\n  { id: 'c-separator', value: 'component: separator' },\n  { id: 'c-slider', value: 'component: slider' },\n  { id: 'c-switch', value: 'component: switch' },\n  { id: 'c-tabs', value: 'component: tabs' },\n  { id: 'c-toast', value: 'component: toast' },\n  { id: 'c-toggle', value: 'component: toggle' },\n  { id: 'c-toggle-group', value: 'component: toggle group' },\n  { id: 'c-toolbar', value: 'component: toolbar' },\n  { id: 'c-tooltip', value: 'component: tooltip' },\n];\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/auto-highlight/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoAutocompleteAutoHighlight = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/auto-highlight/tailwind/index.tsx",
    "content": "'use client';\nimport { Autocomplete } from '@base-ui/react/autocomplete';\n\nexport default function ExampleAutocompleteAutoHighlight() {\n  return (\n    <Autocomplete.Root items={tags} autoHighlight>\n      <label className=\"flex flex-col gap-1 text-sm leading-5 font-bold text-gray-900\">\n        Auto highlight on type\n        <Autocomplete.Input\n          placeholder=\"e.g. feature\"\n          className=\"bg-[canvas] h-10 w-[16rem] md:w-[20rem] font-normal rounded-md border border-gray-200 pl-3.5 text-base font-normal text-gray-900 focus:outline-2 focus:-outline-offset-1 focus:outline-blue-800\"\n        />\n      </label>\n\n      <Autocomplete.Portal>\n        <Autocomplete.Positioner className=\"outline-hidden\" sideOffset={4}>\n          <Autocomplete.Popup className=\"w-[var(--anchor-width)] max-h-[23rem] max-w-[var(--available-width)] rounded-md bg-[canvas] text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300\">\n            <Autocomplete.Empty className=\"p-4 text-[0.925rem] leading-4 text-gray-600 empty:m-0 empty:p-0\">\n              No tags found.\n            </Autocomplete.Empty>\n            <Autocomplete.List className=\"outline-0 overflow-y-auto scroll-py-[0.5rem] py-2 overscroll-contain max-h-[min(23rem,var(--available-height))] data-[empty]:p-0\">\n              {(tag: Tag) => (\n                <Autocomplete.Item\n                  key={tag.id}\n                  className=\"flex cursor-default items-center gap-2 py-2 pr-8 pl-4 text-base leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-2 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\"\n                  value={tag}\n                >\n                  {tag.value}\n                </Autocomplete.Item>\n              )}\n            </Autocomplete.List>\n          </Autocomplete.Popup>\n        </Autocomplete.Positioner>\n      </Autocomplete.Portal>\n    </Autocomplete.Root>\n  );\n}\n\ninterface Tag {\n  id: string;\n  value: string;\n}\n\nconst tags: Tag[] = [\n  { id: 't1', value: 'feature' },\n  { id: 't2', value: 'fix' },\n  { id: 't3', value: 'bug' },\n  { id: 't4', value: 'docs' },\n  { id: 't5', value: 'internal' },\n  { id: 't6', value: 'mobile' },\n  { id: 'c-accordion', value: 'component: accordion' },\n  { id: 'c-alert-dialog', value: 'component: alert dialog' },\n  { id: 'c-autocomplete', value: 'component: autocomplete' },\n  { id: 'c-avatar', value: 'component: avatar' },\n  { id: 'c-checkbox', value: 'component: checkbox' },\n  { id: 'c-checkbox-group', value: 'component: checkbox group' },\n  { id: 'c-collapsible', value: 'component: collapsible' },\n  { id: 'c-combobox', value: 'component: combobox' },\n  { id: 'c-context-menu', value: 'component: context menu' },\n  { id: 'c-dialog', value: 'component: dialog' },\n  { id: 'c-field', value: 'component: field' },\n  { id: 'c-fieldset', value: 'component: fieldset' },\n  { id: 'c-filterable-menu', value: 'component: filterable menu' },\n  { id: 'c-form', value: 'component: form' },\n  { id: 'c-input', value: 'component: input' },\n  { id: 'c-menu', value: 'component: menu' },\n  { id: 'c-menubar', value: 'component: menubar' },\n  { id: 'c-meter', value: 'component: meter' },\n  { id: 'c-navigation-menu', value: 'component: navigation menu' },\n  { id: 'c-number-field', value: 'component: number field' },\n  { id: 'c-popover', value: 'component: popover' },\n  { id: 'c-preview-card', value: 'component: preview card' },\n  { id: 'c-progress', value: 'component: progress' },\n  { id: 'c-radio', value: 'component: radio' },\n  { id: 'c-scroll-area', value: 'component: scroll area' },\n  { id: 'c-select', value: 'component: select' },\n  { id: 'c-separator', value: 'component: separator' },\n  { id: 'c-slider', value: 'component: slider' },\n  { id: 'c-switch', value: 'component: switch' },\n  { id: 'c-tabs', value: 'component: tabs' },\n  { id: 'c-toast', value: 'component: toast' },\n  { id: 'c-toggle', value: 'component: toggle' },\n  { id: 'c-toggle-group', value: 'component: toggle group' },\n  { id: 'c-toolbar', value: 'component: toolbar' },\n  { id: 'c-tooltip', value: 'component: tooltip' },\n];\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/command-palette/css-modules/index.module.css",
    "content": ".Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n  cursor: default;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Backdrop {\n  position: fixed;\n  inset: 0;\n  background-color: black;\n  opacity: 0.2;\n  transition: opacity 150ms cubic-bezier(0.45, 1.005, 0, 1.005);\n\n  @media (prefers-color-scheme: dark) {\n    opacity: 0.7;\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n  }\n}\n\n.Viewport {\n  position: fixed;\n  inset: 0;\n  display: flex;\n  align-items: flex-start;\n  justify-content: center;\n  padding: 4.5rem 0.5rem 0.5rem;\n  overflow: hidden;\n}\n\n.Popup {\n  box-sizing: border-box;\n  position: relative;\n  width: calc(100vw - 1rem);\n  max-width: 28rem;\n  border-radius: 1rem;\n  background-color: white;\n  color: var(--color-gray-900);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n  max-height: min(36rem, calc(100dvh - 5rem));\n  display: flex;\n  flex-direction: column;\n  overflow: hidden;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: translateY(-1rem) scale(0.95);\n  }\n\n  outline: 1px solid rgb(0 0 0 / 0.04);\n  box-shadow:\n    0 0.5px 1px rgb(0 0 0 / 0.12),\n    0 1px 3px -1px rgb(0 0 0 / 0.04),\n    0 2px 4px -1px rgb(0 0 0 / 0.04),\n    0 4px 8px -2px rgb(0 0 0 / 0.04),\n    0 12px 14px -4px rgb(0 0 0 / 0.04),\n    0 24px 64px -8px rgb(0 0 0 / 0.04),\n    0 40px 48px -32px rgb(0 0 0 / 0.04);\n\n  @media (prefers-color-scheme: dark) {\n    background-color: oklch(20% 0.5% 264deg);\n    outline: 1px solid rgb(255 255 255 / 0.25);\n  }\n}\n\n.Input {\n  box-sizing: border-box;\n  padding: 1rem;\n  margin: 0;\n  border: none;\n  border-bottom: 1px solid var(--color-gray-100);\n  width: 100%;\n  height: auto;\n  border-radius: 0;\n  font-family: inherit;\n  font-size: 1rem;\n  background-color: transparent;\n  color: var(--color-gray-900);\n  letter-spacing: 0.016em;\n  outline: none;\n\n  &::placeholder {\n    font-weight: 400;\n    color: var(--color-gray-500);\n  }\n\n  &:focus {\n    border-bottom-color: var(--color-gray-100);\n    outline: none;\n  }\n}\n\n.ListArea {\n  position: relative;\n  display: flex;\n  flex: 0 1 auto;\n  min-height: 0;\n  max-height: min(60dvh, 24rem);\n  overflow: hidden;\n}\n\n.ListViewport {\n  box-sizing: border-box;\n  flex: 1 1 auto;\n  min-height: 0;\n  overscroll-behavior: contain;\n  scroll-padding-block: 0.25rem;\n\n  &:focus-visible {\n    outline: 1px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.List {\n  box-sizing: border-box;\n  padding: 0.5rem;\n\n  &:empty {\n    padding: 0;\n  }\n}\n\n.ListContent {\n  min-width: 100%;\n}\n\n.Group:not(:last-child) {\n  margin-block-end: 0.25rem;\n}\n\n.GroupLabel {\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  display: flex;\n  align-items: center;\n  min-height: 2rem;\n  padding: 0 0.875rem;\n  font-size: 0.9375rem;\n  letter-spacing: 0.00625em;\n  line-height: 1;\n  font-weight: 400;\n  color: var(--color-gray-600);\n}\n\n.Item {\n  box-sizing: border-box;\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  scroll-margin-block: 0.25rem;\n  min-height: 2rem;\n  padding: 0 0.75rem 0 2.25rem;\n  border-radius: 0.375rem;\n  display: grid;\n  grid-template-columns: minmax(0, 1fr) auto;\n  gap: 0.5rem;\n  align-items: center;\n  font-size: 0.9375rem;\n  letter-spacing: 0.016em;\n  line-height: 1.25;\n  font-weight: 400;\n\n  &[data-highlighted] {\n    background-color: var(--color-gray-100);\n  }\n}\n\n.ItemLabel {\n  min-width: 0;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  font-weight: 400;\n}\n\n.ItemType {\n  font-size: 0.875rem;\n  color: var(--color-gray-500);\n  letter-spacing: 0.00625em;\n  white-space: nowrap;\n\n  [data-highlighted] & {\n    color: var(--color-gray-700);\n  }\n}\n\n.Empty:not(:empty) {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  padding: 1rem;\n  font-size: 0.925rem;\n  line-height: 1rem;\n  color: var(--color-gray-600);\n  min-height: 8rem;\n}\n\n.Scrollbar {\n  display: flex;\n  justify-content: center;\n  width: 1.5rem;\n  margin-right: -0.25rem;\n  padding-top: 0.5rem;\n  padding-bottom: 0.5rem;\n}\n\n.ScrollbarThumb {\n  display: flex;\n  justify-content: center;\n  width: 100%;\n\n  &::before {\n    content: '';\n    display: block;\n    height: 100%;\n    width: 0.25rem;\n    border-radius: var(--radius-sm);\n    background-color: var(--color-gray-400);\n  }\n}\n\n.Footer {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  padding: 0.625rem 0.75rem;\n  border-top: 1px solid var(--color-gray-200);\n  font-size: 0.75rem;\n  color: var(--color-gray-600);\n  background-color: var(--color-gray-50);\n  border-radius: 0 0 1rem 1rem;\n}\n\n.FooterLeft,\n.FooterRight {\n  display: flex;\n  align-items: center;\n  gap: 0.5rem;\n}\n\n.Kbd {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  min-width: 1.25rem;\n  height: 1.25rem;\n  padding: 0 0.25rem;\n  font-size: 0.625rem;\n  font-family:\n    ui-monospace, SFMono-Regular, 'SF Mono', Consolas, 'Liberation Mono', Menlo, monospace;\n  font-weight: 400;\n  line-height: 1;\n  color: var(--color-gray-700);\n  background-color: var(--color-gray-100);\n  border: 1px solid var(--color-gray-300);\n  border-radius: 0.25rem;\n}\n\n.VisuallyHiddenClose {\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"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/command-palette/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Dialog } from '@base-ui/react/dialog';\nimport { Autocomplete } from '@base-ui/react/autocomplete';\nimport { ScrollArea } from '@base-ui/react/scroll-area';\nimport styles from './index.module.css';\n\nexport default function ExampleAutocompleteCommandPalette() {\n  const [open, setOpen] = React.useState(false);\n\n  function handleItemClick() {\n    setOpen(false);\n  }\n\n  return (\n    <Dialog.Root open={open} onOpenChange={setOpen}>\n      <Dialog.Trigger className={styles.Button}>Open command palette</Dialog.Trigger>\n      <Dialog.Portal>\n        <Dialog.Backdrop className={styles.Backdrop} />\n        <Dialog.Viewport className={styles.Viewport}>\n          <Dialog.Popup className={styles.Popup} aria-label=\"Command palette\">\n            <Autocomplete.Root\n              open\n              inline\n              items={groupedItems}\n              autoHighlight=\"always\"\n              keepHighlight\n            >\n              <Autocomplete.Input\n                className={styles.Input}\n                placeholder=\"Search for apps and commands...\"\n              />\n              <Dialog.Close className={styles.VisuallyHiddenClose}>\n                Close command palette\n              </Dialog.Close>\n\n              <ScrollArea.Root className={styles.ListArea}>\n                <ScrollArea.Viewport className={styles.ListViewport}>\n                  <ScrollArea.Content className={styles.ListContent}>\n                    <Autocomplete.Empty className={styles.Empty}>\n                      No results found.\n                    </Autocomplete.Empty>\n\n                    <Autocomplete.List className={styles.List}>\n                      {(group: Group) => (\n                        <Autocomplete.Group\n                          key={group.value}\n                          items={group.items}\n                          className={styles.Group}\n                        >\n                          <Autocomplete.GroupLabel className={styles.GroupLabel}>\n                            {group.value}\n                          </Autocomplete.GroupLabel>\n                          <Autocomplete.Collection>\n                            {(item: Item) => (\n                              <Autocomplete.Item\n                                key={item.value}\n                                value={item}\n                                className={styles.Item}\n                                onClick={handleItemClick}\n                              >\n                                <span className={styles.ItemLabel}>{item.label}</span>\n                                <span className={styles.ItemType}>\n                                  {group.value === 'Suggestions' ? 'Application' : 'Command'}\n                                </span>\n                              </Autocomplete.Item>\n                            )}\n                          </Autocomplete.Collection>\n                        </Autocomplete.Group>\n                      )}\n                    </Autocomplete.List>\n                  </ScrollArea.Content>\n                </ScrollArea.Viewport>\n                <ScrollArea.Scrollbar className={styles.Scrollbar}>\n                  <ScrollArea.Thumb className={styles.ScrollbarThumb} />\n                </ScrollArea.Scrollbar>\n              </ScrollArea.Root>\n\n              <div className={styles.Footer}>\n                <div className={styles.FooterLeft}>\n                  <span>Activate</span>\n                  <kbd className={styles.Kbd}>Enter</kbd>\n                </div>\n                <div className={styles.FooterRight}>\n                  <span>Actions</span>\n                  <kbd className={styles.Kbd}>Cmd</kbd>\n                  <kbd className={styles.Kbd}>K</kbd>\n                </div>\n              </div>\n            </Autocomplete.Root>\n          </Dialog.Popup>\n        </Dialog.Viewport>\n      </Dialog.Portal>\n    </Dialog.Root>\n  );\n}\n\ninterface Item {\n  value: string;\n  label: string;\n}\n\ninterface Group {\n  value: string;\n  items: Item[];\n}\n\nconst suggestions: Item[] = [\n  { value: 'linear', label: 'Linear' },\n  { value: 'figma', label: 'Figma' },\n  { value: 'slack', label: 'Slack' },\n  { value: 'youtube', label: 'YouTube' },\n  { value: 'raycast', label: 'Raycast' },\n  { value: 'notion', label: 'Notion' },\n  { value: 'github', label: 'GitHub' },\n  { value: 'jira', label: 'Jira' },\n  { value: 'calendar', label: 'Google Calendar' },\n  { value: 'chrome', label: 'Google Chrome' },\n  { value: 'mail', label: 'Apple Mail' },\n  { value: 'terminal', label: 'Terminal' },\n];\n\nconst commands: Item[] = [\n  { value: 'clipboard-history', label: 'Clipboard History' },\n  { value: 'import-extension', label: 'Import Extension' },\n  { value: 'create-snippet', label: 'Create Snippet' },\n  { value: 'system-preferences', label: 'System Preferences' },\n  { value: 'window-management', label: 'Window Management' },\n  { value: 'toggle-dark-mode', label: 'Toggle Dark Mode' },\n  { value: 'new-window', label: 'New Window' },\n  { value: 'new-tab', label: 'New Tab' },\n  { value: 'search-docs', label: 'Search Documentation' },\n  { value: 'capture-screen', label: 'Capture Screenshot' },\n  { value: 'close-sidebar', label: 'Toggle Sidebar' },\n  { value: 'toggle-terminal', label: 'Toggle Integrated Terminal' },\n  { value: 'run-script', label: 'Run Script' },\n];\n\nconst groupedItems: Group[] = [\n  { value: 'Suggestions', items: suggestions },\n  { value: 'Commands', items: commands },\n];\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/command-palette/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoAutocompleteCommandPalette = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/command-palette/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Autocomplete } from '@base-ui/react/autocomplete';\nimport { Dialog } from '@base-ui/react/dialog';\nimport { ScrollArea } from '@base-ui/react/scroll-area';\n\nexport default function ExampleAutocompleteCommandPalette() {\n  const [open, setOpen] = React.useState(false);\n\n  function handleItemClick() {\n    setOpen(false);\n  }\n\n  return (\n    <Dialog.Root open={open} onOpenChange={setOpen}>\n      <Dialog.Trigger className=\"flex h-10 cursor-default items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n        Open command palette\n      </Dialog.Trigger>\n      <Dialog.Portal>\n        <Dialog.Backdrop className=\"fixed inset-0 bg-black opacity-20 transition-opacity duration-150 ease-[cubic-bezier(0.45,1.005,0,1.005)] data-[ending-style]:opacity-0 data-[starting-style]:opacity-0 dark:opacity-70 supports-[-webkit-touch-callout:none]:absolute\" />\n        <Dialog.Viewport className=\"fixed inset-0 flex items-start justify-center overflow-hidden px-2 pt-18 pb-2\">\n          <Dialog.Popup\n            className=\"relative flex max-h-[min(36rem,calc(100dvh-5rem))] w-[calc(100vw-1rem)] max-w-[28rem] flex-col overflow-hidden rounded-2xl bg-white text-gray-900 outline-1 outline-black/4 shadow-[0_.5px_1px_hsl(0_0%_0%/12%),0_1px_3px_-1px_hsl(0_0%_0%/4%),0_2px_4px_-1px_hsl(0_0%_0%/4%),0_4px_8px_-2px_hsl(0_0%_0%/4%),0_12px_14px_-4px_hsl(0_0%_0%/4%),0_24px_64px_-8px_hsl(0_0%_0%/4%),0_40px_48px_-32px_hsl(0_0%_0%/4%)] transition-[opacity,transform,scale,translate] duration-150 data-ending-style:-translate-y-4 data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:-translate-y-4 data-starting-style:scale-95 data-starting-style:opacity-0 dark:bg-[oklch(20%_0.5%_264deg)] dark:outline-white/25\"\n            aria-label=\"Command palette\"\n          >\n            <Autocomplete.Root\n              open\n              inline\n              items={groupedItems}\n              autoHighlight=\"always\"\n              keepHighlight\n            >\n              <Autocomplete.Input\n                className=\"w-full border-0 border-b border-gray-100 bg-transparent p-4 text-base font-normal tracking-[0.016em] text-gray-900 placeholder:text-gray-500 outline-none\"\n                placeholder=\"Search for apps and commands...\"\n              />\n              <Dialog.Close className=\"sr-only\">Close command palette</Dialog.Close>\n\n              <ScrollArea.Root className=\"relative flex max-h-[min(60dvh,24rem)] min-h-0 flex-[0_1_auto] overflow-hidden\">\n                <ScrollArea.Viewport className=\"min-h-0 flex-1 overscroll-contain [scroll-padding-block:0.25rem] focus-visible:outline focus-visible:outline-1 focus-visible:-outline-offset-1 focus-visible:outline-blue-800\">\n                  <ScrollArea.Content style={{ minWidth: '100%' }}>\n                    <Autocomplete.Empty className=\"flex min-h-32 items-center justify-center p-4 text-[0.925rem] leading-4 text-gray-600 empty:m-0 empty:min-h-0 empty:p-0\">\n                      No results found.\n                    </Autocomplete.Empty>\n\n                    <Autocomplete.List className=\"p-2\">\n                      {(group: Group) => (\n                        <Autocomplete.Group\n                          key={group.value}\n                          items={group.items}\n                          className=\"not-last:mb-1\"\n                        >\n                          <Autocomplete.GroupLabel className=\"m-0 flex h-8 items-center px-3.5 text-[0.9375rem] tracking-[0.00625em] font-normal leading-none text-gray-600 select-none outline-none\">\n                            {group.value}\n                          </Autocomplete.GroupLabel>\n                          <Autocomplete.Collection>\n                            {(item: Item) => (\n                              <Autocomplete.Item\n                                key={item.value}\n                                value={item}\n                                onClick={handleItemClick}\n                                className=\"grid min-h-8 cursor-default grid-cols-[minmax(0,1fr)_auto] items-center gap-2 rounded-md pl-9 pr-3 text-[0.9375rem] tracking-[0.016em] font-normal leading-[1.25] select-none outline-none [scroll-margin-block:0.25rem] data-[highlighted]:bg-gray-100\"\n                              >\n                                <span className=\"truncate font-normal\">{item.label}</span>\n                                <span className=\"shrink-0 whitespace-nowrap text-[0.875rem] tracking-[0.00625em] text-gray-500 data-[highlighted]:text-gray-700\">\n                                  {group.value === 'Suggestions' ? 'Application' : 'Command'}\n                                </span>\n                              </Autocomplete.Item>\n                            )}\n                          </Autocomplete.Collection>\n                        </Autocomplete.Group>\n                      )}\n                    </Autocomplete.List>\n                  </ScrollArea.Content>\n                </ScrollArea.Viewport>\n                <ScrollArea.Scrollbar className=\"-mr-1 flex w-6 justify-center py-2\">\n                  <ScrollArea.Thumb className=\"flex w-full justify-center before:block before:h-full before:w-1 before:rounded-sm before:bg-gray-400 before:content-['']\" />\n                </ScrollArea.Scrollbar>\n              </ScrollArea.Root>\n\n              <div className=\"flex items-center justify-between border-t border-gray-200 bg-gray-50 px-3 py-2.5 text-xs text-gray-600\">\n                <div className=\"flex items-center gap-2\">\n                  <span>Activate</span>\n                  <kbd className=\"inline-flex h-5 min-w-5 items-center justify-center rounded border border-gray-300 bg-gray-100 px-1 text-[0.625rem] font-normal text-gray-700\">\n                    Enter\n                  </kbd>\n                </div>\n                <div className=\"flex items-center gap-2\">\n                  <span>Actions</span>\n                  <kbd className=\"inline-flex h-5 min-w-5 items-center justify-center rounded border border-gray-300 bg-gray-100 px-1 text-[0.625rem] font-normal text-gray-700\">\n                    Cmd\n                  </kbd>\n                  <kbd className=\"inline-flex h-5 min-w-5 items-center justify-center rounded border border-gray-300 bg-gray-100 px-1 text-[0.625rem] font-normal text-gray-700\">\n                    K\n                  </kbd>\n                </div>\n              </div>\n            </Autocomplete.Root>\n          </Dialog.Popup>\n        </Dialog.Viewport>\n      </Dialog.Portal>\n    </Dialog.Root>\n  );\n}\n\ninterface Item {\n  value: string;\n  label: string;\n}\n\ninterface Group {\n  value: string;\n  items: Item[];\n}\n\nconst suggestions: Item[] = [\n  { value: 'linear', label: 'Linear' },\n  { value: 'figma', label: 'Figma' },\n  { value: 'slack', label: 'Slack' },\n  { value: 'youtube', label: 'YouTube' },\n  { value: 'raycast', label: 'Raycast' },\n  { value: 'notion', label: 'Notion' },\n  { value: 'github', label: 'GitHub' },\n  { value: 'jira', label: 'Jira' },\n  { value: 'calendar', label: 'Google Calendar' },\n  { value: 'chrome', label: 'Google Chrome' },\n  { value: 'mail', label: 'Apple Mail' },\n  { value: 'terminal', label: 'Terminal' },\n];\n\nconst commands: Item[] = [\n  { value: 'clipboard-history', label: 'Clipboard History' },\n  { value: 'import-extension', label: 'Import Extension' },\n  { value: 'create-snippet', label: 'Create Snippet' },\n  { value: 'system-preferences', label: 'System Preferences' },\n  { value: 'window-management', label: 'Window Management' },\n  { value: 'toggle-dark-mode', label: 'Toggle Dark Mode' },\n  { value: 'new-window', label: 'New Window' },\n  { value: 'new-tab', label: 'New Tab' },\n  { value: 'search-docs', label: 'Search Documentation' },\n  { value: 'capture-screen', label: 'Capture Screenshot' },\n  { value: 'close-sidebar', label: 'Toggle Sidebar' },\n  { value: 'toggle-terminal', label: 'Toggle Integrated Terminal' },\n  { value: 'run-script', label: 'Run Script' },\n];\n\nconst groupedItems: Group[] = [\n  { value: 'Suggestions', items: suggestions },\n  { value: 'Commands', items: commands },\n];\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/fuzzy-matching/css-modules/index.module.css",
    "content": ".Label {\n  display: flex;\n  flex-direction: column;\n  gap: 0.25rem;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 700;\n  color: var(--color-gray-900);\n}\n\n.Input {\n  box-sizing: border-box;\n  padding-left: 0.875rem;\n  margin: 0;\n  border: 1px solid var(--color-gray-200);\n  width: 16rem;\n  height: 2.5rem;\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  outline: none;\n\n  @media (min-width: 500px) {\n    width: 20rem;\n  }\n\n  &:focus {\n    border-color: var(--color-blue);\n    outline: 1px solid var(--color-blue);\n  }\n}\n\n.Positioner {\n  outline: 0;\n}\n\n.Popup {\n  box-sizing: border-box;\n  padding-block: 0.5rem;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  width: var(--anchor-width);\n  max-height: min(var(--available-height), 28rem);\n  max-width: var(--available-width);\n  overflow-y: auto;\n  scroll-padding-block: 0.5rem;\n  overscroll-behavior: contain;\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.List {\n  box-sizing: border-box;\n  display: flex;\n  flex-direction: column;\n}\n\n.Item {\n  box-sizing: border-box;\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  padding: 0.75rem 1rem;\n  display: flex;\n  font-size: 1rem;\n  line-height: 1.5rem;\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.5rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-200);\n  }\n}\n\n.ItemContent {\n  display: flex;\n  flex-direction: column;\n  gap: 0.25rem;\n  width: 100%;\n}\n\n.ItemHeader {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  gap: 0.75rem;\n}\n\n.ItemTitle {\n  font-weight: 700;\n  line-height: 1.25rem;\n  flex: 1;\n}\n\n.ItemDescription {\n  font-size: 0.875rem;\n  color: var(--color-gray-600);\n  line-height: 1.25rem;\n}\n\n.Empty:not(:empty) {\n  box-sizing: border-box;\n  font-size: 0.925rem;\n  line-height: 1rem;\n  color: var(--color-gray-600);\n  padding: 0.5rem 1rem;\n}\n\n.Item mark {\n  background-color: transparent;\n  color: var(--color-blue);\n  font-weight: 700;\n}\n\n.ItemCategory mark {\n  color: var(--color-blue);\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/fuzzy-matching/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Autocomplete } from '@base-ui/react/autocomplete';\nimport { matchSorter } from 'match-sorter';\nimport styles from './index.module.css';\n\nexport default function ExampleFuzzyMatchingAutocomplete() {\n  return (\n    <Autocomplete.Root\n      items={fuzzyItems}\n      filter={fuzzyFilter}\n      itemToStringValue={(item) => item.title}\n    >\n      <label className={styles.Label}>\n        Fuzzy search documentation\n        <Autocomplete.Input placeholder=\"e.g. React\" className={styles.Input} />\n      </label>\n\n      <Autocomplete.Portal>\n        <Autocomplete.Positioner className={styles.Positioner} sideOffset={4}>\n          <Autocomplete.Popup className={styles.Popup}>\n            <Autocomplete.Empty className={styles.Empty}>\n              No results found for \"{<Autocomplete.Value />}\"\n            </Autocomplete.Empty>\n\n            <Autocomplete.List className={styles.List}>\n              {(item: FuzzyItem) => (\n                <Autocomplete.Item key={item.title} value={item} className={styles.Item}>\n                  <Autocomplete.Value>\n                    {(value) => (\n                      <div className={styles.ItemContent}>\n                        <div className={styles.ItemHeader}>\n                          <div className={styles.ItemTitle}>{highlightText(item.title, value)}</div>\n                        </div>\n                        <div className={styles.ItemDescription}>\n                          {highlightText(item.description, value)}\n                        </div>\n                      </div>\n                    )}\n                  </Autocomplete.Value>\n                </Autocomplete.Item>\n              )}\n            </Autocomplete.List>\n          </Autocomplete.Popup>\n        </Autocomplete.Positioner>\n      </Autocomplete.Portal>\n    </Autocomplete.Root>\n  );\n}\n\nfunction highlightText(text: string, query: string): React.ReactNode {\n  const trimmed = query.trim();\n  if (!trimmed) {\n    return text;\n  }\n\n  const limited = trimmed.slice(0, 100);\n  const escaped = limited.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n  const regex = new RegExp(`(${escaped})`, 'gi');\n\n  return text\n    .split(regex)\n    .map((part, idx) => (regex.test(part) ? <mark key={idx}>{part}</mark> : part));\n}\n\nfunction fuzzyFilter(item: FuzzyItem, query: string): boolean {\n  if (!query) {\n    return true;\n  }\n\n  const results = matchSorter([item], query, {\n    keys: [\n      'title',\n      'description',\n      'category',\n      { key: 'title', threshold: matchSorter.rankings.CONTAINS },\n      { key: 'description', threshold: matchSorter.rankings.WORD_STARTS_WITH },\n    ],\n  });\n\n  return results.length > 0;\n}\n\ninterface FuzzyItem {\n  title: string;\n  description: string;\n  category: string;\n}\n\nconst fuzzyItems: FuzzyItem[] = [\n  {\n    title: 'React Hooks Guide',\n    description: 'Learn how to use React Hooks like useState, useEffect, and custom hooks',\n    category: 'React',\n  },\n  {\n    title: 'JavaScript Array Methods',\n    description: 'Master array methods like map, filter, reduce, and forEach in JavaScript',\n    category: 'JavaScript',\n  },\n  {\n    title: 'CSS Flexbox Layout',\n    description: 'Complete guide to CSS Flexbox for responsive web design',\n    category: 'CSS',\n  },\n  {\n    title: 'TypeScript Interfaces',\n    description: 'Understanding TypeScript interfaces and type definitions',\n    category: 'TypeScript',\n  },\n  {\n    title: 'React Performance Optimization',\n    description: 'Tips and techniques for optimizing React application performance',\n    category: 'React',\n  },\n  {\n    title: 'HTML Semantic Elements',\n    description: 'Using semantic HTML elements for better accessibility and SEO',\n    category: 'HTML',\n  },\n  {\n    title: 'Node.js Express Server',\n    description: 'Building RESTful APIs with Node.js and Express framework',\n    category: 'Node.js',\n  },\n  {\n    title: 'Vue Composition API',\n    description: 'Modern Vue.js development using the Composition API',\n    category: 'Vue.js',\n  },\n  {\n    title: 'Angular Components',\n    description: 'Creating reusable Angular components with TypeScript',\n    category: 'Angular',\n  },\n  {\n    title: 'Python Django Framework',\n    description: 'Web development with Python Django framework',\n    category: 'Python',\n  },\n  {\n    title: 'CSS Grid Layout',\n    description: 'Advanced CSS Grid techniques for complex layouts',\n    category: 'CSS',\n  },\n  {\n    title: 'React Testing Library',\n    description: 'Testing React components with React Testing Library',\n    category: 'React',\n  },\n  {\n    title: 'MongoDB Queries',\n    description: 'Advanced MongoDB queries and aggregation pipelines',\n    category: 'Database',\n  },\n  {\n    title: 'Webpack Configuration',\n    description: 'Optimizing Webpack configuration for production builds',\n    category: 'Build Tools',\n  },\n  {\n    title: 'SASS/SCSS Guide',\n    description: 'Writing maintainable CSS with SASS and SCSS',\n    category: 'CSS',\n  },\n];\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/fuzzy-matching/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoAutocompleteFuzzyMatching = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/fuzzy-matching/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Autocomplete } from '@base-ui/react/autocomplete';\nimport { matchSorter } from 'match-sorter';\n\nexport default function ExampleFuzzyMatchingAutocomplete() {\n  return (\n    <Autocomplete.Root\n      items={fuzzyItems}\n      filter={fuzzyFilter}\n      itemToStringValue={(item) => item.title}\n    >\n      <label className=\"flex flex-col gap-1 text-sm leading-5 font-bold text-gray-900\">\n        Fuzzy search documentation\n        <Autocomplete.Input\n          placeholder=\"e.g. React\"\n          className=\"bg-[canvas] h-10 w-[16rem] md:w-[20rem] font-normal rounded-md border border-gray-200 pl-3.5 text-base font-normal text-gray-900 focus:outline-2 focus:-outline-offset-1 focus:outline-blue-800\"\n        />\n      </label>\n\n      <Autocomplete.Portal>\n        <Autocomplete.Positioner className=\"outline-hidden\" sideOffset={4}>\n          <Autocomplete.Popup className=\"w-[var(--anchor-width)] max-h-[min(var(--available-height),28rem)] max-w-[var(--available-width)] overflow-y-auto scroll-pt-2 scroll-pb-2 overscroll-contain rounded-md bg-[canvas] py-2 text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300\">\n            <Autocomplete.Empty className=\"px-4 py-2 text-[0.925rem] leading-4 text-gray-600 empty:m-0 empty:p-0\">\n              No results found for \"{<Autocomplete.Value />}\"\n            </Autocomplete.Empty>\n\n            <Autocomplete.List className=\"flex flex-col\">\n              {(item: FuzzyItem) => (\n                <Autocomplete.Item\n                  key={item.title}\n                  value={item}\n                  className=\"flex cursor-default py-2 pr-8 pl-4 text-base leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-2 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-sm data-[highlighted]:before:bg-gray-200\"\n                >\n                  <Autocomplete.Value>\n                    {(value) => (\n                      <div className=\"flex w-full flex-col gap-1\">\n                        <div className=\"flex items-center justify-between gap-3\">\n                          <div className=\"flex-1 font-bold leading-5\">\n                            {highlightText(item.title, value)}\n                          </div>\n                        </div>\n                        <div className=\"text-sm leading-5 text-gray-600\">\n                          {highlightText(item.description, value)}\n                        </div>\n                      </div>\n                    )}\n                  </Autocomplete.Value>\n                </Autocomplete.Item>\n              )}\n            </Autocomplete.List>\n          </Autocomplete.Popup>\n        </Autocomplete.Positioner>\n      </Autocomplete.Portal>\n    </Autocomplete.Root>\n  );\n}\n\nfunction highlightText(text: string, query: string): React.ReactNode {\n  const trimmed = query.trim();\n  if (!trimmed) {\n    return text;\n  }\n\n  const limited = trimmed.slice(0, 100);\n  const escaped = limited.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n  const regex = new RegExp(`(${escaped})`, 'gi');\n\n  return text.split(regex).map((part, idx) =>\n    regex.test(part) ? (\n      <mark key={idx} className=\"text-blue-800 bg-transparent font-bold\">\n        {part}\n      </mark>\n    ) : (\n      part\n    ),\n  );\n}\n\nfunction fuzzyFilter(item: FuzzyItem, query: string): boolean {\n  if (!query) {\n    return true;\n  }\n\n  const results = matchSorter([item], query, {\n    keys: [\n      'title',\n      'description',\n      'category',\n      { key: 'title', threshold: matchSorter.rankings.CONTAINS },\n      { key: 'description', threshold: matchSorter.rankings.WORD_STARTS_WITH },\n    ],\n  });\n\n  return results.length > 0;\n}\n\ninterface FuzzyItem {\n  title: string;\n  description: string;\n  category: string;\n}\n\nconst fuzzyItems: FuzzyItem[] = [\n  {\n    title: 'React Hooks Guide',\n    description: 'Learn how to use React Hooks like useState, useEffect, and custom hooks',\n    category: 'React',\n  },\n  {\n    title: 'JavaScript Array Methods',\n    description: 'Master array methods like map, filter, reduce, and forEach in JavaScript',\n    category: 'JavaScript',\n  },\n  {\n    title: 'CSS Flexbox Layout',\n    description: 'Complete guide to CSS Flexbox for responsive web design',\n    category: 'CSS',\n  },\n  {\n    title: 'TypeScript Interfaces',\n    description: 'Understanding TypeScript interfaces and type definitions',\n    category: 'TypeScript',\n  },\n  {\n    title: 'React Performance Optimization',\n    description: 'Tips and techniques for optimizing React application performance',\n    category: 'React',\n  },\n  {\n    title: 'HTML Semantic Elements',\n    description: 'Using semantic HTML elements for better accessibility and SEO',\n    category: 'HTML',\n  },\n  {\n    title: 'Node.js Express Server',\n    description: 'Building RESTful APIs with Node.js and Express framework',\n    category: 'Node.js',\n  },\n  {\n    title: 'Vue Composition API',\n    description: 'Modern Vue.js development using the Composition API',\n    category: 'Vue.js',\n  },\n  {\n    title: 'Angular Components',\n    description: 'Creating reusable Angular components with TypeScript',\n    category: 'Angular',\n  },\n  {\n    title: 'Python Django Framework',\n    description: 'Web development with Python Django framework',\n    category: 'Python',\n  },\n  {\n    title: 'CSS Grid Layout',\n    description: 'Advanced CSS Grid techniques for complex layouts',\n    category: 'CSS',\n  },\n  {\n    title: 'React Testing Library',\n    description: 'Testing React components with React Testing Library',\n    category: 'React',\n  },\n  {\n    title: 'MongoDB Queries',\n    description: 'Advanced MongoDB queries and aggregation pipelines',\n    category: 'Database',\n  },\n  {\n    title: 'Webpack Configuration',\n    description: 'Optimizing Webpack configuration for production builds',\n    category: 'Build Tools',\n  },\n  {\n    title: 'SASS/SCSS Guide',\n    description: 'Writing maintainable CSS with SASS and SCSS',\n    category: 'CSS',\n  },\n];\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/grid/css-modules/index.module.css",
    "content": ".Container {\n  width: 16rem;\n  margin: 0 auto;\n}\n\n.InputGroup {\n  display: flex;\n  gap: 0.5rem;\n  align-items: center;\n}\n\n.TextInput {\n  box-sizing: border-box;\n  padding-left: 0.875rem;\n  margin: 0;\n  border: 1px solid var(--color-gray-200);\n  flex: 1;\n  height: 2.5rem;\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  outline: none;\n\n  &:focus {\n    border-color: var(--color-blue);\n    outline: 1px solid var(--color-blue);\n  }\n}\n\n.EmojiButton {\n  box-sizing: border-box;\n  width: 2.5rem;\n  height: 2.5rem;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: canvas;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  font-size: 1.25rem;\n  color: var(--color-gray-900);\n  outline: none;\n\n  &:hover {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    border-color: var(--color-blue);\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n}\n\n.Trigger {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 0.75rem;\n  height: 2.5rem;\n  padding-left: 0.875rem;\n  padding-right: 0.75rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  cursor: default;\n  -webkit-user-select: none;\n  user-select: none;\n  min-width: 9rem;\n  background-color: canvas;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.TriggerIcon {\n  display: flex;\n}\n\n.InputContainer {\n  box-sizing: border-box;\n  width: 16rem;\n  height: calc(var(--input-container-height));\n  text-align: center;\n  background: canvas;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin: 0 0.25rem;\n}\n\n.Input {\n  box-sizing: border-box;\n  padding-left: 0.875rem;\n  margin: 0;\n  border: 1px solid var(--color-gray-300);\n  width: 100%;\n  height: 2.5rem;\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  outline: none;\n\n  &:focus {\n    border-color: var(--color-blue);\n    outline: 1px solid var(--color-blue);\n  }\n}\n\n.Positioner {\n  outline: 0;\n}\n\n.Popup {\n  --input-container-height: 3rem;\n  box-sizing: border-box;\n  border-radius: 0.5rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n  max-width: var(--available-width);\n  max-height: 20.5rem;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n  }\n}\n\n.List {\n  overflow: auto;\n  scroll-padding-top: 2.5rem;\n  scroll-padding-bottom: 0.35rem;\n  overscroll-behavior: contain;\n  max-height: min(\n    calc(20.5rem - var(--input-container-height)),\n    calc(var(--available-height) - var(--input-container-height))\n  );\n\n  &:empty {\n    padding: 0;\n  }\n}\n\n.ListContainer {\n  padding: 0.5rem;\n}\n\n.GroupLabel {\n  box-sizing: border-box;\n  padding: 0.5rem 1rem 0.25rem;\n  font-size: 0.75rem;\n  font-weight: 700;\n  color: var(--color-gray-600);\n  text-transform: uppercase;\n  letter-spacing: 0.025em;\n  background-color: canvas;\n  border-bottom: 1px solid var(--color-gray-100);\n  position: sticky;\n  z-index: 1;\n  top: 0;\n  margin: 0;\n  width: 100%;\n}\n\n.Group {\n  display: block;\n}\n\n.Grid {\n  padding: 0.25rem;\n}\n\n.Row {\n  display: grid;\n  grid-template-columns: repeat(var(--cols, 5), 1fr);\n}\n\n.Item {\n  box-sizing: border-box;\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  min-width: var(--anchor-width);\n  height: 2.5rem;\n  padding: 0.5rem 0.125rem;\n  border-radius: 0.375rem;\n  background: transparent;\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n\n    &::before {\n      content: '';\n      z-index: -1;\n      position: absolute;\n      inset: 0;\n      border-radius: 0.375rem;\n      background-color: var(--color-gray-200);\n    }\n  }\n}\n\n.Emoji {\n  font-size: 1.5rem;\n  line-height: 1;\n  margin-bottom: 0.25rem;\n}\n\n.Empty:not(:empty) {\n  box-sizing: border-box;\n  padding: 0.5rem 1rem 1rem;\n  font-size: 0.925rem;\n  line-height: 1rem;\n  color: var(--color-gray-600);\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/grid/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Autocomplete } from '@base-ui/react/autocomplete';\nimport styles from './index.module.css';\n\nexport default function ExampleEmojiPicker() {\n  const [pickerOpen, setPickerOpen] = React.useState(false);\n  const [textValue, setTextValue] = React.useState('');\n  const [searchValue, setSearchValue] = React.useState('');\n\n  const textInputRef = React.useRef<HTMLInputElement | null>(null);\n\n  function handleInsertEmoji(value: string | null) {\n    if (!value || !textInputRef.current) {\n      return;\n    }\n\n    const emoji = value;\n    const start = textInputRef.current.selectionStart ?? textInputRef.current.value.length ?? 0;\n    const end = textInputRef.current.selectionEnd ?? textInputRef.current.value.length ?? 0;\n\n    setTextValue((prev) => prev.slice(0, start) + emoji + prev.slice(end));\n    setPickerOpen(false);\n\n    const input = textInputRef.current;\n    if (input) {\n      input.focus();\n      const caretPos = start + emoji.length;\n      input.setSelectionRange(caretPos, caretPos);\n    }\n  }\n\n  return (\n    <div className={styles.Container}>\n      <div className={styles.InputGroup}>\n        <input\n          ref={textInputRef}\n          type=\"text\"\n          className={styles.TextInput}\n          placeholder=\"iMessage\"\n          value={textValue}\n          onChange={(event) => setTextValue(event.target.value)}\n        />\n\n        <Autocomplete.Root\n          items={emojiGroups}\n          grid\n          open={pickerOpen}\n          onOpenChange={setPickerOpen}\n          onOpenChangeComplete={() => setSearchValue('')}\n          value={searchValue}\n          onValueChange={(value, details) => {\n            if (details.reason !== 'item-press') {\n              setSearchValue(value);\n            }\n          }}\n        >\n          <Autocomplete.Trigger className={styles.EmojiButton} aria-label=\"Choose emoji\">\n            😀\n          </Autocomplete.Trigger>\n          <Autocomplete.Portal>\n            <Autocomplete.Positioner className={styles.Positioner} sideOffset={4} align=\"end\">\n              <Autocomplete.Popup className={styles.Popup} aria-label=\"Select emoji\">\n                <div className={styles.InputContainer}>\n                  <Autocomplete.Input placeholder=\"Search emojis…\" className={styles.Input} />\n                </div>\n                <Autocomplete.Empty className={styles.Empty}>No emojis found</Autocomplete.Empty>\n                <Autocomplete.List\n                  className={styles.List}\n                  style={{ '--cols': COLUMNS } as React.CSSProperties}\n                >\n                  {(group: EmojiGroup) => (\n                    <Autocomplete.Group\n                      key={group.value}\n                      items={group.items}\n                      className={styles.Group}\n                    >\n                      <Autocomplete.GroupLabel className={styles.GroupLabel}>\n                        {group.label}\n                      </Autocomplete.GroupLabel>\n                      <div className={styles.Grid} role=\"presentation\">\n                        {chunkArray(group.items, COLUMNS).map((row, rowIdx) => (\n                          <Autocomplete.Row key={rowIdx} className={styles.Row}>\n                            {row.map((rowItem) => (\n                              <Autocomplete.Item\n                                key={rowItem.emoji}\n                                value={rowItem}\n                                className={styles.Item}\n                                onClick={() => {\n                                  handleInsertEmoji(rowItem.emoji);\n                                  setPickerOpen(false);\n                                }}\n                              >\n                                <span className={styles.Emoji}>{rowItem.emoji}</span>\n                              </Autocomplete.Item>\n                            ))}\n                          </Autocomplete.Row>\n                        ))}\n                      </div>\n                    </Autocomplete.Group>\n                  )}\n                </Autocomplete.List>\n              </Autocomplete.Popup>\n            </Autocomplete.Positioner>\n          </Autocomplete.Portal>\n        </Autocomplete.Root>\n      </div>\n    </div>\n  );\n}\n\nconst COLUMNS = 5;\n\nfunction chunkArray<T>(array: T[], size: number): T[][] {\n  const result: T[][] = [];\n  for (let i = 0; i < array.length; i += size) {\n    result.push(array.slice(i, i + size));\n  }\n  return result;\n}\n\ninterface EmojiItem {\n  emoji: string;\n  value: string;\n  name: string;\n}\n\ninterface EmojiGroup {\n  value: string;\n  label: string;\n  items: EmojiItem[];\n}\n\nexport const emojiCategories = [\n  {\n    label: 'Smileys & Emotion',\n    emojis: [\n      { emoji: '😀', name: 'grinning face' },\n      { emoji: '😃', name: 'grinning face with big eyes' },\n      { emoji: '😄', name: 'grinning face with smiling eyes' },\n      { emoji: '😁', name: 'beaming face with smiling eyes' },\n      { emoji: '😆', name: 'grinning squinting face' },\n      { emoji: '😅', name: 'grinning face with sweat' },\n      { emoji: '🤣', name: 'rolling on the floor laughing' },\n      { emoji: '😂', name: 'face with tears of joy' },\n      { emoji: '🙂', name: 'slightly smiling face' },\n      { emoji: '🙃', name: 'upside-down face' },\n      { emoji: '😉', name: 'winking face' },\n      { emoji: '😊', name: 'smiling face with smiling eyes' },\n      { emoji: '😇', name: 'smiling face with halo' },\n      { emoji: '🥰', name: 'smiling face with hearts' },\n      { emoji: '😍', name: 'smiling face with heart-eyes' },\n      { emoji: '🤩', name: 'star-struck' },\n      { emoji: '😘', name: 'face blowing a kiss' },\n      { emoji: '😗', name: 'kissing face' },\n      { emoji: '☺️', name: 'smiling face' },\n      { emoji: '😚', name: 'kissing face with closed eyes' },\n      { emoji: '😙', name: 'kissing face with smiling eyes' },\n      { emoji: '🥲', name: 'smiling face with tear' },\n      { emoji: '😋', name: 'face savoring food' },\n      { emoji: '😛', name: 'face with tongue' },\n      { emoji: '😜', name: 'winking face with tongue' },\n      { emoji: '🤪', name: 'zany face' },\n      { emoji: '😝', name: 'squinting face with tongue' },\n      { emoji: '🤑', name: 'money-mouth face' },\n      { emoji: '🤗', name: 'hugging face' },\n      { emoji: '🤭', name: 'face with hand over mouth' },\n    ],\n  },\n  {\n    label: 'Animals & Nature',\n    emojis: [\n      { emoji: '🐶', name: 'dog face' },\n      { emoji: '🐱', name: 'cat face' },\n      { emoji: '🐭', name: 'mouse face' },\n      { emoji: '🐹', name: 'hamster' },\n      { emoji: '🐰', name: 'rabbit face' },\n      { emoji: '🦊', name: 'fox' },\n      { emoji: '🐻', name: 'bear' },\n      { emoji: '🐼', name: 'panda' },\n      { emoji: '🐨', name: 'koala' },\n      { emoji: '🐯', name: 'tiger face' },\n      { emoji: '🦁', name: 'lion' },\n      { emoji: '🐮', name: 'cow face' },\n      { emoji: '🐷', name: 'pig face' },\n      { emoji: '🐽', name: 'pig nose' },\n      { emoji: '🐸', name: 'frog' },\n      { emoji: '🐵', name: 'monkey face' },\n      { emoji: '🙈', name: 'see-no-evil monkey' },\n      { emoji: '🙉', name: 'hear-no-evil monkey' },\n      { emoji: '🙊', name: 'speak-no-evil monkey' },\n      { emoji: '🐒', name: 'monkey' },\n      { emoji: '🐔', name: 'chicken' },\n      { emoji: '🐧', name: 'penguin' },\n      { emoji: '🐦', name: 'bird' },\n      { emoji: '🐤', name: 'baby chick' },\n      { emoji: '🐣', name: 'hatching chick' },\n      { emoji: '🐥', name: 'front-facing baby chick' },\n      { emoji: '🦆', name: 'duck' },\n      { emoji: '🦅', name: 'eagle' },\n      { emoji: '🦉', name: 'owl' },\n      { emoji: '🦇', name: 'bat' },\n    ],\n  },\n  {\n    label: 'Food & Drink',\n    emojis: [\n      { emoji: '🍎', name: 'red apple' },\n      { emoji: '🍏', name: 'green apple' },\n      { emoji: '🍊', name: 'tangerine' },\n      { emoji: '🍋', name: 'lemon' },\n      { emoji: '🍌', name: 'banana' },\n      { emoji: '🍉', name: 'watermelon' },\n      { emoji: '🍇', name: 'grapes' },\n      { emoji: '🍓', name: 'strawberry' },\n      { emoji: '🫐', name: 'blueberries' },\n      { emoji: '🍈', name: 'melon' },\n      { emoji: '🍒', name: 'cherries' },\n      { emoji: '🍑', name: 'peach' },\n      { emoji: '🥭', name: 'mango' },\n      { emoji: '🍍', name: 'pineapple' },\n      { emoji: '🥥', name: 'coconut' },\n      { emoji: '🥝', name: 'kiwi fruit' },\n      { emoji: '🍅', name: 'tomato' },\n      { emoji: '🍆', name: 'eggplant' },\n      { emoji: '🥑', name: 'avocado' },\n      { emoji: '🥦', name: 'broccoli' },\n      { emoji: '🥬', name: 'leafy greens' },\n      { emoji: '🥒', name: 'cucumber' },\n      { emoji: '🌶️', name: 'hot pepper' },\n      { emoji: '🫑', name: 'bell pepper' },\n      { emoji: '🌽', name: 'ear of corn' },\n      { emoji: '🥕', name: 'carrot' },\n      { emoji: '🫒', name: 'olive' },\n      { emoji: '🧄', name: 'garlic' },\n      { emoji: '🧅', name: 'onion' },\n      { emoji: '🥔', name: 'potato' },\n    ],\n  },\n];\n\nconst emojiGroups: EmojiGroup[] = emojiCategories.map((category) => ({\n  value: category.label,\n  label: category.label,\n  items: category.emojis.map((emoji) => ({\n    ...emoji,\n    value: emoji.name.toLowerCase(),\n  })),\n}));\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/grid/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoAutocompleteGrid = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/grid/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Autocomplete } from '@base-ui/react/autocomplete';\n\nexport default function ExampleEmojiPicker() {\n  const [pickerOpen, setPickerOpen] = React.useState(false);\n  const [textValue, setTextValue] = React.useState('');\n  const [searchValue, setSearchValue] = React.useState('');\n\n  const textInputRef = React.useRef<HTMLInputElement | null>(null);\n\n  function handleInsertEmoji(value: string | null) {\n    if (!value || !textInputRef.current) {\n      return;\n    }\n\n    const emoji = value;\n    const start = textInputRef.current.selectionStart ?? textInputRef.current.value.length ?? 0;\n    const end = textInputRef.current.selectionEnd ?? textInputRef.current.value.length ?? 0;\n\n    setTextValue((prev) => prev.slice(0, start) + emoji + prev.slice(end));\n    setPickerOpen(false);\n\n    const input = textInputRef.current;\n    if (input) {\n      input.focus();\n      const caretPos = start + emoji.length;\n      input.setSelectionRange(caretPos, caretPos);\n    }\n  }\n\n  return (\n    <div className=\"mx-auto w-[16rem]\">\n      <div className=\"flex items-center gap-2\">\n        <input\n          ref={textInputRef}\n          type=\"text\"\n          className=\"h-10 flex-1 font-normal rounded-md border border-gray-200 pl-3.5 text-base text-gray-900 focus:outline-2 focus:-outline-offset-1 focus:outline-blue-800\"\n          placeholder=\"iMessage\"\n          value={textValue}\n          onChange={(event) => setTextValue(event.target.value)}\n        />\n\n        <Autocomplete.Root\n          items={emojiGroups}\n          grid\n          open={pickerOpen}\n          onOpenChange={setPickerOpen}\n          onOpenChangeComplete={() => setSearchValue('')}\n          value={searchValue}\n          onValueChange={(value, details) => {\n            if (details.reason !== 'item-press') {\n              setSearchValue(value);\n            }\n          }}\n        >\n          <Autocomplete.Trigger\n            className=\"size-10 rounded-md border border-gray-200 bg-[canvas] text-[1.25rem] text-gray-900 outline-hidden hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 data-[popup-open]:bg-gray-100\"\n            aria-label=\"Choose emoji\"\n          >\n            😀\n          </Autocomplete.Trigger>\n          <Autocomplete.Portal>\n            <Autocomplete.Positioner className=\"outline-hidden\" sideOffset={4} align=\"end\">\n              <Autocomplete.Popup className=\"[--input-container-height:3rem] max-w-[var(--available-width)] max-h-[20.5rem] origin-[var(--transform-origin)] rounded-lg bg-[canvas] shadow-lg shadow-gray-200 text-gray-900 outline-1 outline-gray-200 transition-[transform,scale,opacity] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300\">\n                <div className=\"mx-1 flex h-[var(--input-container-height)] w-64 items-center justify-center bg-[canvas] text-center\">\n                  <Autocomplete.Input\n                    placeholder=\"Search emojis…\"\n                    className=\"h-10 w-[16rem] md:w-[20rem] font-normal rounded-md border border-gray-200 pl-3.5 text-base font-normal text-gray-900 focus:outline-2 focus:-outline-offset-1 focus:outline-blue-800\"\n                  />\n                </div>\n                <Autocomplete.Empty className=\"px-4 pb-4 pt-2 text-[0.925rem] leading-4 text-gray-600 empty:m-0 empty:p-0\">\n                  No emojis found\n                </Autocomplete.Empty>\n                <Autocomplete.List className=\"max-h-[min(calc(20.5rem-var(--input-container-height)),calc(var(--available-height)-var(--input-container-height)))] overflow-auto scroll-pt-10 scroll-pb-[0.35rem] overscroll-contain\">\n                  {(group: EmojiGroup) => (\n                    <Autocomplete.Group key={group.value} items={group.items} className=\"block\">\n                      <Autocomplete.GroupLabel className=\"sticky top-0 z-[1] m-0 w-full border-b border-gray-100 bg-[canvas] px-4 pb-1 pt-2 text-[0.75rem] font-bold uppercase tracking-wide text-gray-600\">\n                        {group.label}\n                      </Autocomplete.GroupLabel>\n                      <div className=\"p-1\" role=\"presentation\">\n                        {chunkArray(group.items, COLUMNS).map((row, rowIdx) => (\n                          <Autocomplete.Row key={rowIdx} className=\"grid grid-cols-5\">\n                            {row.map((rowItem) => (\n                              <Autocomplete.Item\n                                key={rowItem.emoji}\n                                value={rowItem}\n                                className=\"group min-w-[var(--anchor-width)] select-none flex h-10 flex-col items-center justify-center rounded-md bg-transparent px-0.5 py-2 text-gray-900 outline-hidden data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-md data-[highlighted]:before:bg-gray-200\"\n                                onClick={() => {\n                                  handleInsertEmoji(rowItem.emoji);\n                                  setPickerOpen(false);\n                                }}\n                              >\n                                <span className=\"mb-1 text-[1.5rem] leading-none\">\n                                  {rowItem.emoji}\n                                </span>\n                              </Autocomplete.Item>\n                            ))}\n                          </Autocomplete.Row>\n                        ))}\n                      </div>\n                    </Autocomplete.Group>\n                  )}\n                </Autocomplete.List>\n              </Autocomplete.Popup>\n            </Autocomplete.Positioner>\n          </Autocomplete.Portal>\n        </Autocomplete.Root>\n      </div>\n    </div>\n  );\n}\n\nconst COLUMNS = 5;\n\nfunction chunkArray<T>(array: T[], size: number): T[][] {\n  const result: T[][] = [];\n  for (let i = 0; i < array.length; i += size) {\n    result.push(array.slice(i, i + size));\n  }\n  return result;\n}\n\ninterface EmojiItem {\n  emoji: string;\n  value: string;\n  name: string;\n}\n\ninterface EmojiGroup {\n  value: string;\n  label: string;\n  items: EmojiItem[];\n}\n\nexport const emojiCategories = [\n  {\n    label: 'Smileys & Emotion',\n    emojis: [\n      { emoji: '😀', name: 'grinning face' },\n      { emoji: '😃', name: 'grinning face with big eyes' },\n      { emoji: '😄', name: 'grinning face with smiling eyes' },\n      { emoji: '😁', name: 'beaming face with smiling eyes' },\n      { emoji: '😆', name: 'grinning squinting face' },\n      { emoji: '😅', name: 'grinning face with sweat' },\n      { emoji: '🤣', name: 'rolling on the floor laughing' },\n      { emoji: '😂', name: 'face with tears of joy' },\n      { emoji: '🙂', name: 'slightly smiling face' },\n      { emoji: '🙃', name: 'upside-down face' },\n      { emoji: '😉', name: 'winking face' },\n      { emoji: '😊', name: 'smiling face with smiling eyes' },\n      { emoji: '😇', name: 'smiling face with halo' },\n      { emoji: '🥰', name: 'smiling face with hearts' },\n      { emoji: '😍', name: 'smiling face with heart-eyes' },\n      { emoji: '🤩', name: 'star-struck' },\n      { emoji: '😘', name: 'face blowing a kiss' },\n      { emoji: '😗', name: 'kissing face' },\n      { emoji: '☺️', name: 'smiling face' },\n      { emoji: '😚', name: 'kissing face with closed eyes' },\n      { emoji: '😙', name: 'kissing face with smiling eyes' },\n      { emoji: '🥲', name: 'smiling face with tear' },\n      { emoji: '😋', name: 'face savoring food' },\n      { emoji: '😛', name: 'face with tongue' },\n      { emoji: '😜', name: 'winking face with tongue' },\n      { emoji: '🤪', name: 'zany face' },\n      { emoji: '😝', name: 'squinting face with tongue' },\n      { emoji: '🤑', name: 'money-mouth face' },\n      { emoji: '🤗', name: 'hugging face' },\n      { emoji: '🤭', name: 'face with hand over mouth' },\n    ],\n  },\n  {\n    label: 'Animals & Nature',\n    emojis: [\n      { emoji: '🐶', name: 'dog face' },\n      { emoji: '🐱', name: 'cat face' },\n      { emoji: '🐭', name: 'mouse face' },\n      { emoji: '🐹', name: 'hamster' },\n      { emoji: '🐰', name: 'rabbit face' },\n      { emoji: '🦊', name: 'fox' },\n      { emoji: '🐻', name: 'bear' },\n      { emoji: '🐼', name: 'panda' },\n      { emoji: '🐨', name: 'koala' },\n      { emoji: '🐯', name: 'tiger face' },\n      { emoji: '🦁', name: 'lion' },\n      { emoji: '🐮', name: 'cow face' },\n      { emoji: '🐷', name: 'pig face' },\n      { emoji: '🐽', name: 'pig nose' },\n      { emoji: '🐸', name: 'frog' },\n      { emoji: '🐵', name: 'monkey face' },\n      { emoji: '🙈', name: 'see-no-evil monkey' },\n      { emoji: '🙉', name: 'hear-no-evil monkey' },\n      { emoji: '🙊', name: 'speak-no-evil monkey' },\n      { emoji: '🐒', name: 'monkey' },\n      { emoji: '🐔', name: 'chicken' },\n      { emoji: '🐧', name: 'penguin' },\n      { emoji: '🐦', name: 'bird' },\n      { emoji: '🐤', name: 'baby chick' },\n      { emoji: '🐣', name: 'hatching chick' },\n      { emoji: '🐥', name: 'front-facing baby chick' },\n      { emoji: '🦆', name: 'duck' },\n      { emoji: '🦅', name: 'eagle' },\n      { emoji: '🦉', name: 'owl' },\n      { emoji: '🦇', name: 'bat' },\n    ],\n  },\n  {\n    label: 'Food & Drink',\n    emojis: [\n      { emoji: '🍎', name: 'red apple' },\n      { emoji: '🍏', name: 'green apple' },\n      { emoji: '🍊', name: 'tangerine' },\n      { emoji: '🍋', name: 'lemon' },\n      { emoji: '🍌', name: 'banana' },\n      { emoji: '🍉', name: 'watermelon' },\n      { emoji: '🍇', name: 'grapes' },\n      { emoji: '🍓', name: 'strawberry' },\n      { emoji: '🫐', name: 'blueberries' },\n      { emoji: '🍈', name: 'melon' },\n      { emoji: '🍒', name: 'cherries' },\n      { emoji: '🍑', name: 'peach' },\n      { emoji: '🥭', name: 'mango' },\n      { emoji: '🍍', name: 'pineapple' },\n      { emoji: '🥥', name: 'coconut' },\n      { emoji: '🥝', name: 'kiwi fruit' },\n      { emoji: '🍅', name: 'tomato' },\n      { emoji: '🍆', name: 'eggplant' },\n      { emoji: '🥑', name: 'avocado' },\n      { emoji: '🥦', name: 'broccoli' },\n      { emoji: '🥬', name: 'leafy greens' },\n      { emoji: '🥒', name: 'cucumber' },\n      { emoji: '🌶️', name: 'hot pepper' },\n      { emoji: '🫑', name: 'bell pepper' },\n      { emoji: '🌽', name: 'ear of corn' },\n      { emoji: '🥕', name: 'carrot' },\n      { emoji: '🫒', name: 'olive' },\n      { emoji: '🧄', name: 'garlic' },\n      { emoji: '🧅', name: 'onion' },\n      { emoji: '🥔', name: 'potato' },\n    ],\n  },\n];\n\nconst emojiGroups: EmojiGroup[] = emojiCategories.map((category) => ({\n  value: category.label,\n  label: category.label,\n  items: category.emojis.map((emoji) => ({\n    ...emoji,\n    value: emoji.name.toLowerCase(),\n  })),\n}));\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/grouped/css-modules/index.module.css",
    "content": ".Input {\n  box-sizing: border-box;\n  padding-left: 0.875rem;\n  margin: 0;\n  border: 1px solid var(--color-gray-200);\n  width: 16rem;\n  height: 2.5rem;\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  outline: none;\n\n  &:focus {\n    border-color: var(--color-blue);\n    outline: 1px solid var(--color-blue);\n  }\n\n  @media (min-width: 500px) {\n    width: 20rem;\n  }\n}\n\n.Label {\n  display: flex;\n  flex-direction: column;\n  gap: 0.25rem;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 700;\n  color: var(--color-gray-900);\n}\n\n.Positioner {\n  outline: 0;\n}\n\n.Popup {\n  box-sizing: border-box;\n  border-radius: 0.5rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  width: var(--anchor-width);\n  max-height: 22.5rem;\n  max-width: var(--available-width);\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n  }\n}\n\n.List {\n  box-sizing: border-box;\n  overflow-y: auto;\n  scroll-padding-top: 2.25rem;\n  scroll-padding-bottom: 0.5rem;\n  overscroll-behavior: contain;\n  max-height: min(22.5rem, var(--available-height));\n  outline: 0;\n\n  &[data-empty] {\n    padding: 0;\n  }\n}\n\n.Group {\n  display: block;\n  padding-bottom: 0.5rem;\n}\n\n.GroupLabel {\n  box-sizing: border-box;\n  padding: 0.5rem 1rem 0.25rem;\n  font-size: 0.75rem;\n  font-weight: 700;\n  text-transform: uppercase;\n  letter-spacing: 0.025em;\n  background-color: canvas;\n  position: sticky;\n  z-index: 1;\n  top: 0;\n  margin: 0 0.5rem 0 0;\n  width: calc(100% - 0.5rem);\n}\n\n.Item {\n  box-sizing: border-box;\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  padding: 0.5rem 2rem 0.5rem 1rem;\n  display: flex;\n  font-size: 1rem;\n  line-height: 1rem;\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.5rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.Separator {\n  margin: 0.375rem 1rem;\n  height: 1px;\n  background-color: var(--color-gray-200);\n}\n\n.Empty:not(:empty) {\n  box-sizing: border-box;\n  padding: 1rem;\n  font-size: 0.925rem;\n  line-height: 1rem;\n  color: var(--color-gray-600);\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/grouped/css-modules/index.tsx",
    "content": "'use client';\nimport { Autocomplete } from '@base-ui/react/autocomplete';\nimport styles from './index.module.css';\n\nexport default function ExampleGroupAutocomplete() {\n  return (\n    <Autocomplete.Root items={groupedTags}>\n      <label className={styles.Label}>\n        Select a tag\n        <Autocomplete.Input placeholder=\"e.g. feature\" className={styles.Input} />\n      </label>\n\n      <Autocomplete.Portal>\n        <Autocomplete.Positioner className={styles.Positioner} sideOffset={4}>\n          <Autocomplete.Popup className={styles.Popup}>\n            <Autocomplete.Empty className={styles.Empty}>No tags found.</Autocomplete.Empty>\n            <Autocomplete.List className={styles.List}>\n              {(group: TagGroup) => (\n                <Autocomplete.Group key={group.value} items={group.items} className={styles.Group}>\n                  <Autocomplete.GroupLabel className={styles.GroupLabel}>\n                    {group.value}\n                  </Autocomplete.GroupLabel>\n                  <Autocomplete.Collection>\n                    {(tag: Tag) => (\n                      <Autocomplete.Item key={tag.id} className={styles.Item} value={tag}>\n                        {tag.label}\n                      </Autocomplete.Item>\n                    )}\n                  </Autocomplete.Collection>\n                </Autocomplete.Group>\n              )}\n            </Autocomplete.List>\n          </Autocomplete.Popup>\n        </Autocomplete.Positioner>\n      </Autocomplete.Portal>\n    </Autocomplete.Root>\n  );\n}\n\ninterface Tag {\n  id: string;\n  label: string;\n  group: 'Type' | 'Component';\n}\n\ninterface TagGroup {\n  value: string;\n  items: Tag[];\n}\n\nconst tagsData: Tag[] = [\n  { id: 't1', label: 'feature', group: 'Type' },\n  { id: 't2', label: 'fix', group: 'Type' },\n  { id: 't3', label: 'bug', group: 'Type' },\n  { id: 't4', label: 'docs', group: 'Type' },\n  { id: 't5', label: 'internal', group: 'Type' },\n  { id: 't6', label: 'mobile', group: 'Type' },\n  { id: 'c-accordion', label: 'component: accordion', group: 'Component' },\n  { id: 'c-alert-dialog', label: 'component: alert dialog', group: 'Component' },\n  { id: 'c-autocomplete', label: 'component: autocomplete', group: 'Component' },\n  { id: 'c-avatar', label: 'component: avatar', group: 'Component' },\n  { id: 'c-checkbox', label: 'component: checkbox', group: 'Component' },\n  { id: 'c-checkbox-group', label: 'component: checkbox group', group: 'Component' },\n  { id: 'c-collapsible', label: 'component: collapsible', group: 'Component' },\n  { id: 'c-combobox', label: 'component: combobox', group: 'Component' },\n  { id: 'c-context-menu', label: 'component: context menu', group: 'Component' },\n  { id: 'c-dialog', label: 'component: dialog', group: 'Component' },\n  { id: 'c-field', label: 'component: field', group: 'Component' },\n  { id: 'c-fieldset', label: 'component: fieldset', group: 'Component' },\n  { id: 'c-filterable-menu', label: 'component: filterable menu', group: 'Component' },\n  { id: 'c-form', label: 'component: form', group: 'Component' },\n  { id: 'c-input', label: 'component: input', group: 'Component' },\n  { id: 'c-menu', label: 'component: menu', group: 'Component' },\n  { id: 'c-menubar', label: 'component: menubar', group: 'Component' },\n  { id: 'c-meter', label: 'component: meter', group: 'Component' },\n  { id: 'c-navigation-menu', label: 'component: navigation menu', group: 'Component' },\n  { id: 'c-number-field', label: 'component: number field', group: 'Component' },\n  { id: 'c-popover', label: 'component: popover', group: 'Component' },\n  { id: 'c-preview-card', label: 'component: preview card', group: 'Component' },\n  { id: 'c-progress', label: 'component: progress', group: 'Component' },\n  { id: 'c-radio', label: 'component: radio', group: 'Component' },\n  { id: 'c-scroll-area', label: 'component: scroll area', group: 'Component' },\n  { id: 'c-select', label: 'component: select', group: 'Component' },\n  { id: 'c-separator', label: 'component: separator', group: 'Component' },\n  { id: 'c-slider', label: 'component: slider', group: 'Component' },\n  { id: 'c-switch', label: 'component: switch', group: 'Component' },\n  { id: 'c-tabs', label: 'component: tabs', group: 'Component' },\n  { id: 'c-toast', label: 'component: toast', group: 'Component' },\n  { id: 'c-toggle', label: 'component: toggle', group: 'Component' },\n  { id: 'c-toggle-group', label: 'component: toggle group', group: 'Component' },\n  { id: 'c-toolbar', label: 'component: toolbar', group: 'Component' },\n  { id: 'c-tooltip', label: 'component: tooltip', group: 'Component' },\n];\n\nfunction groupTags(tags: Tag[]): TagGroup[] {\n  const groups: { [key: string]: Tag[] } = {};\n  tags.forEach((t) => {\n    (groups[t.group] ??= []).push(t);\n  });\n  const order = ['Type', 'Component'];\n  return order.map((value) => ({ value, items: groups[value] ?? [] }));\n}\n\nconst groupedTags: TagGroup[] = groupTags(tagsData);\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/grouped/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoAutocompleteGrouped = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/grouped/tailwind/index.tsx",
    "content": "'use client';\nimport { Autocomplete } from '@base-ui/react/autocomplete';\n\nexport default function ExampleGroupAutocomplete() {\n  return (\n    <Autocomplete.Root items={groupedTags}>\n      <label className=\"flex flex-col gap-1 text-sm leading-5 font-bold text-gray-900\">\n        Select a tag\n        <Autocomplete.Input\n          placeholder=\"e.g. feature\"\n          className=\"bg-[canvas] h-10 w-[16rem] md:w-[20rem] font-normal rounded-md border border-gray-200 pl-3.5 text-base font-normal text-gray-900 focus:outline-2 focus:-outline-offset-1 focus:outline-blue-800\"\n        />\n      </label>\n\n      <Autocomplete.Portal>\n        <Autocomplete.Positioner className=\"outline-hidden\" sideOffset={4}>\n          <Autocomplete.Popup className=\"w-[var(--anchor-width)] max-h-[22.5rem] max-w-[var(--available-width)] rounded-lg bg-[canvas] text-gray-900 outline-1 outline-gray-200 shadow-lg shadow-gray-200 dark:outline-gray-300 dark:shadow-none\">\n            <Autocomplete.Empty className=\"px-4 py-2 text-[0.925rem] leading-4 text-gray-600 empty:m-0 empty:p-0\">\n              No tags found.\n            </Autocomplete.Empty>\n            <Autocomplete.List className=\"outline-0 overflow-y-auto scroll-pt-[2.25rem] scroll-pb-[0.5rem] overscroll-contain max-h-[min(22.5rem,var(--available-height))] data-[empty]:p-0\">\n              {(group: TagGroup) => (\n                <Autocomplete.Group key={group.value} items={group.items} className=\"block pb-2\">\n                  <Autocomplete.GroupLabel className=\"sticky top-0 z-[1] mb-0 mr-2 mt-0 ml-0 w-[calc(100%-0.5rem)] bg-[canvas] px-4 pb-1 pt-2 text-xs font-bold uppercase tracking-wider\">\n                    {group.value}\n                  </Autocomplete.GroupLabel>\n                  <Autocomplete.Collection>\n                    {(tag: Tag) => (\n                      <Autocomplete.Item\n                        key={tag.id}\n                        className=\"flex cursor-default items-center gap-2 py-2 pr-8 pl-4 text-base leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-2 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\"\n                        value={tag}\n                      >\n                        {tag.label}\n                      </Autocomplete.Item>\n                    )}\n                  </Autocomplete.Collection>\n                </Autocomplete.Group>\n              )}\n            </Autocomplete.List>\n          </Autocomplete.Popup>\n        </Autocomplete.Positioner>\n      </Autocomplete.Portal>\n    </Autocomplete.Root>\n  );\n}\n\ninterface Tag {\n  id: string;\n  label: string;\n  group: 'Type' | 'Component';\n}\n\ninterface TagGroup {\n  value: string;\n  items: Tag[];\n}\n\nconst tagsData: Tag[] = [\n  { id: 't1', label: 'feature', group: 'Type' },\n  { id: 't2', label: 'fix', group: 'Type' },\n  { id: 't3', label: 'bug', group: 'Type' },\n  { id: 't4', label: 'docs', group: 'Type' },\n  { id: 't5', label: 'internal', group: 'Type' },\n  { id: 't6', label: 'mobile', group: 'Type' },\n  { id: 'c-accordion', label: 'component: accordion', group: 'Component' },\n  { id: 'c-alert-dialog', label: 'component: alert dialog', group: 'Component' },\n  { id: 'c-autocomplete', label: 'component: autocomplete', group: 'Component' },\n  { id: 'c-avatar', label: 'component: avatar', group: 'Component' },\n  { id: 'c-checkbox', label: 'component: checkbox', group: 'Component' },\n  { id: 'c-checkbox-group', label: 'component: checkbox group', group: 'Component' },\n  { id: 'c-collapsible', label: 'component: collapsible', group: 'Component' },\n  { id: 'c-combobox', label: 'component: combobox', group: 'Component' },\n  { id: 'c-context-menu', label: 'component: context menu', group: 'Component' },\n  { id: 'c-dialog', label: 'component: dialog', group: 'Component' },\n  { id: 'c-field', label: 'component: field', group: 'Component' },\n  { id: 'c-fieldset', label: 'component: fieldset', group: 'Component' },\n  { id: 'c-filterable-menu', label: 'component: filterable menu', group: 'Component' },\n  { id: 'c-form', label: 'component: form', group: 'Component' },\n  { id: 'c-input', label: 'component: input', group: 'Component' },\n  { id: 'c-menu', label: 'component: menu', group: 'Component' },\n  { id: 'c-menubar', label: 'component: menubar', group: 'Component' },\n  { id: 'c-meter', label: 'component: meter', group: 'Component' },\n  { id: 'c-navigation-menu', label: 'component: navigation menu', group: 'Component' },\n  { id: 'c-number-field', label: 'component: number field', group: 'Component' },\n  { id: 'c-popover', label: 'component: popover', group: 'Component' },\n  { id: 'c-preview-card', label: 'component: preview card', group: 'Component' },\n  { id: 'c-progress', label: 'component: progress', group: 'Component' },\n  { id: 'c-radio', label: 'component: radio', group: 'Component' },\n  { id: 'c-scroll-area', label: 'component: scroll area', group: 'Component' },\n  { id: 'c-select', label: 'component: select', group: 'Component' },\n  { id: 'c-separator', label: 'component: separator', group: 'Component' },\n  { id: 'c-slider', label: 'component: slider', group: 'Component' },\n  { id: 'c-switch', label: 'component: switch', group: 'Component' },\n  { id: 'c-tabs', label: 'component: tabs', group: 'Component' },\n  { id: 'c-toast', label: 'component: toast', group: 'Component' },\n  { id: 'c-toggle', label: 'component: toggle', group: 'Component' },\n  { id: 'c-toggle-group', label: 'component: toggle group', group: 'Component' },\n  { id: 'c-toolbar', label: 'component: toolbar', group: 'Component' },\n  { id: 'c-tooltip', label: 'component: tooltip', group: 'Component' },\n];\n\nfunction groupTags(tags: Tag[]): TagGroup[] {\n  const groups: { [key: string]: Tag[] } = {};\n  tags.forEach((t) => {\n    (groups[t.group] ??= []).push(t);\n  });\n  const order = ['Type', 'Component'];\n  return order.map((value) => ({ value, items: groups[value] ?? [] }));\n}\n\nconst groupedTags: TagGroup[] = groupTags(tagsData);\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/hero/css-modules/index.module.css",
    "content": ".Input {\n  box-sizing: border-box;\n  padding-left: 0.875rem;\n  margin: 0;\n  border: 1px solid var(--color-gray-200);\n  width: 16rem;\n  height: 2.5rem;\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  outline: none;\n\n  &:focus {\n    border-color: var(--color-blue);\n    outline: 1px solid var(--color-blue);\n  }\n\n  @media (min-width: 500px) {\n    width: 20rem;\n  }\n}\n\n.Label {\n  display: flex;\n  flex-direction: column;\n  gap: 0.25rem;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 700;\n  color: var(--color-gray-900);\n}\n\n.Positioner {\n  outline: 0;\n}\n\n.Popup {\n  box-sizing: border-box;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  width: var(--anchor-width);\n  max-height: 23rem;\n  max-width: var(--available-width);\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.List {\n  box-sizing: border-box;\n  overflow-y: auto;\n  overscroll-behavior: contain;\n  padding-block: 0.5rem;\n  scroll-padding-block: 0.5rem;\n  outline: 0;\n  max-height: min(23rem, var(--available-height));\n\n  &[data-empty] {\n    padding: 0;\n  }\n}\n\n.Item {\n  box-sizing: border-box;\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  padding-block: 0.5rem;\n  padding-left: 1rem;\n  padding-right: 2rem;\n  display: flex;\n  font-size: 1rem;\n  line-height: 1rem;\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.5rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.Separator {\n  margin: 0.375rem 1rem;\n  height: 1px;\n  background-color: var(--color-gray-200);\n}\n\n.Empty:not(:empty) {\n  box-sizing: border-box;\n  padding: 1rem;\n  font-size: 0.925rem;\n  line-height: 1rem;\n  color: var(--color-gray-600);\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/hero/css-modules/index.tsx",
    "content": "'use client';\nimport { Autocomplete } from '@base-ui/react/autocomplete';\nimport styles from './index.module.css';\n\nexport default function ExampleAutocomplete() {\n  return (\n    <Autocomplete.Root items={tags}>\n      <label className={styles.Label}>\n        Search tags\n        <Autocomplete.Input placeholder=\"e.g. feature\" className={styles.Input} />\n      </label>\n\n      <Autocomplete.Portal>\n        <Autocomplete.Positioner className={styles.Positioner} sideOffset={4}>\n          <Autocomplete.Popup className={styles.Popup}>\n            <Autocomplete.Empty className={styles.Empty}>No tags found.</Autocomplete.Empty>\n            <Autocomplete.List className={styles.List}>\n              {(tag: Tag) => (\n                <Autocomplete.Item key={tag.id} className={styles.Item} value={tag}>\n                  {tag.value}\n                </Autocomplete.Item>\n              )}\n            </Autocomplete.List>\n          </Autocomplete.Popup>\n        </Autocomplete.Positioner>\n      </Autocomplete.Portal>\n    </Autocomplete.Root>\n  );\n}\n\ninterface Tag {\n  id: string;\n  value: string;\n}\n\nconst tags: Tag[] = [\n  { id: 't1', value: 'feature' },\n  { id: 't2', value: 'fix' },\n  { id: 't3', value: 'bug' },\n  { id: 't4', value: 'docs' },\n  { id: 't5', value: 'internal' },\n  { id: 't6', value: 'mobile' },\n  { id: 'c-accordion', value: 'component: accordion' },\n  { id: 'c-alert-dialog', value: 'component: alert dialog' },\n  { id: 'c-autocomplete', value: 'component: autocomplete' },\n  { id: 'c-avatar', value: 'component: avatar' },\n  { id: 'c-checkbox', value: 'component: checkbox' },\n  { id: 'c-checkbox-group', value: 'component: checkbox group' },\n  { id: 'c-collapsible', value: 'component: collapsible' },\n  { id: 'c-combobox', value: 'component: combobox' },\n  { id: 'c-context-menu', value: 'component: context menu' },\n  { id: 'c-dialog', value: 'component: dialog' },\n  { id: 'c-field', value: 'component: field' },\n  { id: 'c-fieldset', value: 'component: fieldset' },\n  { id: 'c-filterable-menu', value: 'component: filterable menu' },\n  { id: 'c-form', value: 'component: form' },\n  { id: 'c-input', value: 'component: input' },\n  { id: 'c-menu', value: 'component: menu' },\n  { id: 'c-menubar', value: 'component: menubar' },\n  { id: 'c-meter', value: 'component: meter' },\n  { id: 'c-navigation-menu', value: 'component: navigation menu' },\n  { id: 'c-number-field', value: 'component: number field' },\n  { id: 'c-popover', value: 'component: popover' },\n  { id: 'c-preview-card', value: 'component: preview card' },\n  { id: 'c-progress', value: 'component: progress' },\n  { id: 'c-radio', value: 'component: radio' },\n  { id: 'c-scroll-area', value: 'component: scroll area' },\n  { id: 'c-select', value: 'component: select' },\n  { id: 'c-separator', value: 'component: separator' },\n  { id: 'c-slider', value: 'component: slider' },\n  { id: 'c-switch', value: 'component: switch' },\n  { id: 'c-tabs', value: 'component: tabs' },\n  { id: 'c-toast', value: 'component: toast' },\n  { id: 'c-toggle', value: 'component: toggle' },\n  { id: 'c-toggle-group', value: 'component: toggle group' },\n  { id: 'c-toolbar', value: 'component: toolbar' },\n  { id: 'c-tooltip', value: 'component: tooltip' },\n];\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoAutocompleteHero = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/hero/tailwind/index.tsx",
    "content": "'use client';\nimport { Autocomplete } from '@base-ui/react/autocomplete';\n\nexport default function ExampleAutocomplete() {\n  return (\n    <Autocomplete.Root items={tags}>\n      <label className=\"flex flex-col gap-1 text-sm leading-5 font-bold text-gray-900\">\n        Search tags\n        <Autocomplete.Input\n          placeholder=\"e.g. feature\"\n          className=\"bg-[canvas] h-10 w-[16rem] md:w-[20rem] font-normal rounded-md border border-gray-200 pl-3.5 text-base font-normal text-gray-900 focus:outline-2 focus:-outline-offset-1 focus:outline-blue-800\"\n        />\n      </label>\n\n      <Autocomplete.Portal>\n        <Autocomplete.Positioner className=\"outline-hidden\" sideOffset={4}>\n          <Autocomplete.Popup className=\"w-[var(--anchor-width)] max-h-[23rem] max-w-[var(--available-width)] rounded-md bg-[canvas] text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300\">\n            <Autocomplete.Empty className=\"p-4 text-[0.925rem] leading-4 text-gray-600 empty:m-0 empty:p-0\">\n              No tags found.\n            </Autocomplete.Empty>\n            <Autocomplete.List className=\"outline-0 overflow-y-auto scroll-py-[0.5rem] py-2 overscroll-contain max-h-[min(23rem,var(--available-height))] data-[empty]:p-0\">\n              {(tag: Tag) => (\n                <Autocomplete.Item\n                  key={tag.id}\n                  className=\"flex cursor-default items-center gap-2 py-2 pr-8 pl-4 text-base leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-2 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\"\n                  value={tag}\n                >\n                  {tag.value}\n                </Autocomplete.Item>\n              )}\n            </Autocomplete.List>\n          </Autocomplete.Popup>\n        </Autocomplete.Positioner>\n      </Autocomplete.Portal>\n    </Autocomplete.Root>\n  );\n}\n\ninterface Tag {\n  id: string;\n  value: string;\n}\n\nconst tags: Tag[] = [\n  { id: 't1', value: 'feature' },\n  { id: 't2', value: 'fix' },\n  { id: 't3', value: 'bug' },\n  { id: 't4', value: 'docs' },\n  { id: 't5', value: 'internal' },\n  { id: 't6', value: 'mobile' },\n  { id: 'c-accordion', value: 'component: accordion' },\n  { id: 'c-alert-dialog', value: 'component: alert dialog' },\n  { id: 'c-autocomplete', value: 'component: autocomplete' },\n  { id: 'c-avatar', value: 'component: avatar' },\n  { id: 'c-checkbox', value: 'component: checkbox' },\n  { id: 'c-checkbox-group', value: 'component: checkbox group' },\n  { id: 'c-collapsible', value: 'component: collapsible' },\n  { id: 'c-combobox', value: 'component: combobox' },\n  { id: 'c-context-menu', value: 'component: context menu' },\n  { id: 'c-dialog', value: 'component: dialog' },\n  { id: 'c-field', value: 'component: field' },\n  { id: 'c-fieldset', value: 'component: fieldset' },\n  { id: 'c-filterable-menu', value: 'component: filterable menu' },\n  { id: 'c-form', value: 'component: form' },\n  { id: 'c-input', value: 'component: input' },\n  { id: 'c-menu', value: 'component: menu' },\n  { id: 'c-menubar', value: 'component: menubar' },\n  { id: 'c-meter', value: 'component: meter' },\n  { id: 'c-navigation-menu', value: 'component: navigation menu' },\n  { id: 'c-number-field', value: 'component: number field' },\n  { id: 'c-popover', value: 'component: popover' },\n  { id: 'c-preview-card', value: 'component: preview card' },\n  { id: 'c-progress', value: 'component: progress' },\n  { id: 'c-radio', value: 'component: radio' },\n  { id: 'c-scroll-area', value: 'component: scroll area' },\n  { id: 'c-select', value: 'component: select' },\n  { id: 'c-separator', value: 'component: separator' },\n  { id: 'c-slider', value: 'component: slider' },\n  { id: 'c-switch', value: 'component: switch' },\n  { id: 'c-tabs', value: 'component: tabs' },\n  { id: 'c-toast', value: 'component: toast' },\n  { id: 'c-toggle', value: 'component: toggle' },\n  { id: 'c-toggle-group', value: 'component: toggle group' },\n  { id: 'c-toolbar', value: 'component: toolbar' },\n  { id: 'c-tooltip', value: 'component: tooltip' },\n];\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/inline/css-modules/index.module.css",
    "content": ".Container {\n  display: flex;\n  flex-direction: column;\n  gap: 1.5rem;\n}\n\n.Input {\n  box-sizing: border-box;\n  padding-left: 0.875rem;\n  margin: 0;\n  border: 1px solid var(--color-gray-200);\n  width: 16rem;\n  height: 2.5rem;\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  outline: none;\n\n  &:focus {\n    border-color: var(--color-blue);\n    outline: 1px solid var(--color-blue);\n  }\n\n  @media (min-width: 500px) {\n    width: 20rem;\n  }\n}\n\n.Label {\n  display: flex;\n  flex-direction: column;\n  gap: 0.25rem;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 700;\n  color: var(--color-gray-900);\n}\n\n.Positioner {\n  outline: 0;\n\n  &[data-empty] {\n    display: none;\n  }\n}\n\n.Popup {\n  box-sizing: border-box;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  width: var(--anchor-width);\n  max-height: 23rem;\n  max-width: var(--available-width);\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.List {\n  box-sizing: border-box;\n  overflow-y: auto;\n  overscroll-behavior: contain;\n  padding-block: 0.5rem;\n  scroll-padding-block: 0.5rem;\n  outline: 0;\n  max-height: min(23rem, var(--available-height));\n\n  &[data-empty] {\n    padding: 0;\n  }\n}\n\n.Item {\n  box-sizing: border-box;\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  padding-block: 0.5rem;\n  padding-left: 1rem;\n  padding-right: 2rem;\n  display: flex;\n  align-items: center;\n  font-size: 1rem;\n  line-height: 1rem;\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.5rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/inline/css-modules/index.tsx",
    "content": "'use client';\nimport { Autocomplete } from '@base-ui/react/autocomplete';\nimport styles from './index.module.css';\n\nexport default function ExampleAutocompleteInline() {\n  return (\n    <Autocomplete.Root items={tags} mode=\"both\">\n      <label className={styles.Label}>\n        Search tags\n        <Autocomplete.Input placeholder=\"e.g. feature\" className={styles.Input} />\n      </label>\n\n      <Autocomplete.Portal>\n        <Autocomplete.Positioner className={styles.Positioner} sideOffset={4}>\n          <Autocomplete.Popup className={styles.Popup}>\n            <Autocomplete.List className={styles.List}>\n              {(tag: Tag) => (\n                <Autocomplete.Item key={tag.id} className={styles.Item} value={tag}>\n                  {tag.value}\n                </Autocomplete.Item>\n              )}\n            </Autocomplete.List>\n          </Autocomplete.Popup>\n        </Autocomplete.Positioner>\n      </Autocomplete.Portal>\n    </Autocomplete.Root>\n  );\n}\n\ninterface Tag {\n  id: string;\n  value: string;\n}\n\nconst tags: Tag[] = [\n  { id: 't1', value: 'feature' },\n  { id: 't2', value: 'fix' },\n  { id: 't3', value: 'bug' },\n  { id: 't4', value: 'docs' },\n  { id: 't5', value: 'internal' },\n  { id: 't6', value: 'mobile' },\n  { id: 'c-accordion', value: 'component: accordion' },\n  { id: 'c-alert-dialog', value: 'component: alert dialog' },\n  { id: 'c-autocomplete', value: 'component: autocomplete' },\n  { id: 'c-avatar', value: 'component: avatar' },\n  { id: 'c-checkbox', value: 'component: checkbox' },\n  { id: 'c-checkbox-group', value: 'component: checkbox group' },\n  { id: 'c-collapsible', value: 'component: collapsible' },\n  { id: 'c-combobox', value: 'component: combobox' },\n  { id: 'c-context-menu', value: 'component: context menu' },\n  { id: 'c-dialog', value: 'component: dialog' },\n  { id: 'c-field', value: 'component: field' },\n  { id: 'c-fieldset', value: 'component: fieldset' },\n  { id: 'c-filterable-menu', value: 'component: filterable menu' },\n  { id: 'c-form', value: 'component: form' },\n  { id: 'c-input', value: 'component: input' },\n  { id: 'c-menu', value: 'component: menu' },\n  { id: 'c-menubar', value: 'component: menubar' },\n  { id: 'c-meter', value: 'component: meter' },\n  { id: 'c-navigation-menu', value: 'component: navigation menu' },\n  { id: 'c-number-field', value: 'component: number field' },\n  { id: 'c-popover', value: 'component: popover' },\n  { id: 'c-preview-card', value: 'component: preview card' },\n  { id: 'c-progress', value: 'component: progress' },\n  { id: 'c-radio', value: 'component: radio' },\n  { id: 'c-scroll-area', value: 'component: scroll area' },\n  { id: 'c-select', value: 'component: select' },\n  { id: 'c-separator', value: 'component: separator' },\n  { id: 'c-slider', value: 'component: slider' },\n  { id: 'c-switch', value: 'component: switch' },\n  { id: 'c-tabs', value: 'component: tabs' },\n  { id: 'c-toast', value: 'component: toast' },\n  { id: 'c-toggle', value: 'component: toggle' },\n  { id: 'c-toggle-group', value: 'component: toggle group' },\n  { id: 'c-toolbar', value: 'component: toolbar' },\n  { id: 'c-tooltip', value: 'component: tooltip' },\n];\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/inline/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoAutocompleteInline = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/inline/tailwind/index.tsx",
    "content": "'use client';\nimport { Autocomplete } from '@base-ui/react/autocomplete';\n\nexport default function ExampleAutocompleteInline() {\n  return (\n    <Autocomplete.Root items={tags} mode=\"both\">\n      <label className=\"flex flex-col gap-1 text-sm leading-5 font-bold text-gray-900\">\n        Search tags\n        <Autocomplete.Input\n          placeholder=\"e.g. feature\"\n          className=\"bg-[canvas] h-10 w-[16rem] md:w-[20rem] font-normal rounded-md border border-gray-200 pl-3.5 text-base font-normal text-gray-900 focus:outline-2 focus:-outline-offset-1 focus:outline-blue-800\"\n        />\n      </label>\n\n      <Autocomplete.Portal>\n        <Autocomplete.Positioner className=\"outline-hidden data-[empty]:hidden\" sideOffset={4}>\n          <Autocomplete.Popup className=\"w-[var(--anchor-width)] max-h-[23rem] max-w-[var(--available-width)] rounded-md bg-[canvas] text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300\">\n            <Autocomplete.List className=\"outline-0 overflow-y-auto scroll-py-[0.5rem] py-2 overscroll-contain max-h-[min(23rem,var(--available-height))] data-[empty]:p-0\">\n              {(tag: Tag) => (\n                <Autocomplete.Item\n                  key={tag.id}\n                  className=\"flex cursor-default items-center gap-2 py-2 pr-8 pl-4 text-base leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-2 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\"\n                  value={tag}\n                >\n                  {tag.value}\n                </Autocomplete.Item>\n              )}\n            </Autocomplete.List>\n          </Autocomplete.Popup>\n        </Autocomplete.Positioner>\n      </Autocomplete.Portal>\n    </Autocomplete.Root>\n  );\n}\n\ninterface Tag {\n  id: string;\n  value: string;\n}\n\nconst tags: Tag[] = [\n  { id: 't1', value: 'feature' },\n  { id: 't2', value: 'fix' },\n  { id: 't3', value: 'bug' },\n  { id: 't4', value: 'docs' },\n  { id: 't5', value: 'internal' },\n  { id: 't6', value: 'mobile' },\n  { id: 'c-accordion', value: 'component: accordion' },\n  { id: 'c-alert-dialog', value: 'component: alert dialog' },\n  { id: 'c-autocomplete', value: 'component: autocomplete' },\n  { id: 'c-avatar', value: 'component: avatar' },\n  { id: 'c-checkbox', value: 'component: checkbox' },\n  { id: 'c-checkbox-group', value: 'component: checkbox group' },\n  { id: 'c-collapsible', value: 'component: collapsible' },\n  { id: 'c-combobox', value: 'component: combobox' },\n  { id: 'c-context-menu', value: 'component: context menu' },\n  { id: 'c-dialog', value: 'component: dialog' },\n  { id: 'c-field', value: 'component: field' },\n  { id: 'c-fieldset', value: 'component: fieldset' },\n  { id: 'c-filterable-menu', value: 'component: filterable menu' },\n  { id: 'c-form', value: 'component: form' },\n  { id: 'c-input', value: 'component: input' },\n  { id: 'c-menu', value: 'component: menu' },\n  { id: 'c-menubar', value: 'component: menubar' },\n  { id: 'c-meter', value: 'component: meter' },\n  { id: 'c-navigation-menu', value: 'component: navigation menu' },\n  { id: 'c-number-field', value: 'component: number field' },\n  { id: 'c-popover', value: 'component: popover' },\n  { id: 'c-preview-card', value: 'component: preview card' },\n  { id: 'c-progress', value: 'component: progress' },\n  { id: 'c-radio', value: 'component: radio' },\n  { id: 'c-scroll-area', value: 'component: scroll area' },\n  { id: 'c-select', value: 'component: select' },\n  { id: 'c-separator', value: 'component: separator' },\n  { id: 'c-slider', value: 'component: slider' },\n  { id: 'c-switch', value: 'component: switch' },\n  { id: 'c-tabs', value: 'component: tabs' },\n  { id: 'c-toast', value: 'component: toast' },\n  { id: 'c-toggle', value: 'component: toggle' },\n  { id: 'c-toggle-group', value: 'component: toggle group' },\n  { id: 'c-toolbar', value: 'component: toolbar' },\n  { id: 'c-tooltip', value: 'component: tooltip' },\n];\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/limit/css-modules/index.module.css",
    "content": ".Input {\n  box-sizing: border-box;\n  padding-left: 0.875rem;\n  margin: 0;\n  border: 1px solid var(--color-gray-200);\n  width: 16rem;\n  height: 2.5rem;\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  outline: none;\n\n  &:focus {\n    border-color: var(--color-blue);\n    outline: 1px solid var(--color-blue);\n  }\n\n  @media (min-width: 500px) {\n    width: 20rem;\n  }\n}\n\n.Label {\n  display: flex;\n  flex-direction: column;\n  gap: 0.25rem;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 700;\n  color: var(--color-gray-900);\n}\n\n.Positioner {\n  outline: 0;\n}\n\n.Popup {\n  box-sizing: border-box;\n  padding-block: 0.5rem;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  width: var(--anchor-width);\n  max-height: min(var(--available-height), 23rem);\n  max-width: var(--available-width);\n  overflow-y: auto;\n  scroll-padding-block: 0.5rem;\n  overscroll-behavior: contain;\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Item {\n  box-sizing: border-box;\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  padding-block: 0.5rem;\n  padding-left: 1rem;\n  padding-right: 2rem;\n  display: flex;\n  font-size: 1rem;\n  line-height: 1rem;\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.5rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.Empty:not(:empty) {\n  box-sizing: border-box;\n  padding: 0.5rem 1rem;\n  font-size: 0.925rem;\n  line-height: 1rem;\n  color: var(--color-gray-600);\n}\n\n.Status:not(:empty) {\n  box-sizing: border-box;\n  margin-top: 0.25rem;\n  padding: 0.5rem 1rem;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  color: var(--color-gray-600);\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/limit/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Autocomplete } from '@base-ui/react/autocomplete';\nimport styles from './index.module.css';\n\nconst limit = 8;\n\nexport default function ExampleAutocompleteLimit() {\n  const [value, setValue] = React.useState('');\n\n  const { contains } = Autocomplete.useFilter({ sensitivity: 'base' });\n\n  const totalMatches = React.useMemo(() => {\n    const trimmed = value.trim();\n    if (!trimmed) {\n      return tags.length;\n    }\n    return tags.filter((t) => contains(t.value, trimmed)).length;\n  }, [value, contains]);\n\n  const moreCount = Math.max(0, totalMatches - limit);\n\n  return (\n    <Autocomplete.Root items={tags} value={value} onValueChange={setValue} limit={limit}>\n      <label className={styles.Label}>\n        Limit results to 8\n        <Autocomplete.Input placeholder=\"e.g. component\" className={styles.Input} />\n      </label>\n\n      <Autocomplete.Portal>\n        <Autocomplete.Positioner className={styles.Positioner} sideOffset={4}>\n          <Autocomplete.Popup className={styles.Popup}>\n            <Autocomplete.Empty className={styles.Empty}>\n              No results found for \"{value}\"\n            </Autocomplete.Empty>\n\n            <Autocomplete.List>\n              {(tag: Tag) => (\n                <Autocomplete.Item key={tag.id} className={styles.Item} value={tag}>\n                  {tag.value}\n                </Autocomplete.Item>\n              )}\n            </Autocomplete.List>\n\n            <Autocomplete.Status className={styles.Status}>\n              {moreCount > 0\n                ? `Hiding ${moreCount} results (type a more specific query to narrow results)`\n                : null}\n            </Autocomplete.Status>\n          </Autocomplete.Popup>\n        </Autocomplete.Positioner>\n      </Autocomplete.Portal>\n    </Autocomplete.Root>\n  );\n}\n\ninterface Tag {\n  id: string;\n  value: string;\n}\n\n// Larger dataset to make the limit visible.\nconst tags: Tag[] = [\n  { id: 't1', value: 'feature' },\n  { id: 't2', value: 'fix' },\n  { id: 't3', value: 'bug' },\n  { id: 't4', value: 'docs' },\n  { id: 't5', value: 'internal' },\n  { id: 't6', value: 'mobile' },\n  { id: 't7', value: 'frontend' },\n  { id: 't8', value: 'backend' },\n  { id: 't9', value: 'performance' },\n  { id: 't10', value: 'accessibility' },\n  { id: 't11', value: 'design' },\n  { id: 't12', value: 'research' },\n  { id: 't13', value: 'testing' },\n  { id: 't14', value: 'infrastructure' },\n  { id: 't15', value: 'documentation' },\n  { id: 'c-accordion', value: 'component: accordion' },\n  { id: 'c-alert-dialog', value: 'component: alert dialog' },\n  { id: 'c-autocomplete', value: 'component: autocomplete' },\n  { id: 'c-avatar', value: 'component: avatar' },\n  { id: 'c-checkbox', value: 'component: checkbox' },\n  { id: 'c-checkbox-group', value: 'component: checkbox group' },\n  { id: 'c-collapsible', value: 'component: collapsible' },\n  { id: 'c-combobox', value: 'component: combobox' },\n  { id: 'c-context-menu', value: 'component: context menu' },\n  { id: 'c-dialog', value: 'component: dialog' },\n  { id: 'c-field', value: 'component: field' },\n  { id: 'c-fieldset', value: 'component: fieldset' },\n  { id: 'c-filterable-menu', value: 'component: filterable menu' },\n  { id: 'c-form', value: 'component: form' },\n  { id: 'c-input', value: 'component: input' },\n  { id: 'c-menu', value: 'component: menu' },\n  { id: 'c-menubar', value: 'component: menubar' },\n  { id: 'c-meter', value: 'component: meter' },\n  { id: 'c-navigation-menu', value: 'component: navigation menu' },\n  { id: 'c-number-field', value: 'component: number field' },\n  { id: 'c-popover', value: 'component: popover' },\n  { id: 'c-preview-card', value: 'component: preview card' },\n  { id: 'c-progress', value: 'component: progress' },\n  { id: 'c-radio', value: 'component: radio' },\n  { id: 'c-scroll-area', value: 'component: scroll area' },\n  { id: 'c-select', value: 'component: select' },\n  { id: 'c-separator', value: 'component: separator' },\n  { id: 'c-slider', value: 'component: slider' },\n  { id: 'c-switch', value: 'component: switch' },\n  { id: 'c-tabs', value: 'component: tabs' },\n  { id: 'c-toast', value: 'component: toast' },\n  { id: 'c-toggle', value: 'component: toggle' },\n  { id: 'c-toggle-group', value: 'component: toggle group' },\n  { id: 'c-toolbar', value: 'component: toolbar' },\n  { id: 'c-tooltip', value: 'component: tooltip' },\n];\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/limit/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoAutocompleteLimit = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/limit/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Autocomplete } from '@base-ui/react/autocomplete';\n\nconst limit = 8;\n\nexport default function ExampleAutocompleteLimit() {\n  const [value, setValue] = React.useState('');\n\n  const { contains } = Autocomplete.useFilter({ sensitivity: 'base' });\n\n  const totalMatches = React.useMemo(() => {\n    const trimmed = value.trim();\n    if (!trimmed) {\n      return tags.length;\n    }\n    return tags.filter((t) => contains(t.value, trimmed)).length;\n  }, [value, contains]);\n\n  const moreCount = Math.max(0, totalMatches - limit);\n\n  return (\n    <Autocomplete.Root items={tags} value={value} onValueChange={setValue} limit={limit}>\n      <label className=\"flex flex-col gap-1 text-sm leading-5 font-bold text-gray-900\">\n        Limit results to 8\n        <Autocomplete.Input\n          placeholder=\"e.g. component\"\n          className=\"bg-[canvas] h-10 w-[16rem] md:w-[20rem] font-normal rounded-md border border-gray-200 pl-3.5 text-base font-normal text-gray-900 focus:outline-2 focus:-outline-offset-1 focus:outline-blue-800\"\n        />\n      </label>\n\n      <Autocomplete.Portal>\n        <Autocomplete.Positioner className=\"outline-hidden\" sideOffset={4}>\n          <Autocomplete.Popup className=\"w-[var(--anchor-width)] max-h-[min(var(--available-height),23rem)] max-w-[var(--available-width)] overflow-y-auto scroll-pt-2 scroll-pb-2 overscroll-contain rounded-md bg-[canvas] py-2 text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300\">\n            <Autocomplete.Empty className=\"px-4 py-2 text-[0.925rem] leading-4 text-gray-600 empty:m-0 empty:p-0\">\n              No results found for \"{value}\"\n            </Autocomplete.Empty>\n\n            <Autocomplete.List>\n              {(tag: Tag) => (\n                <Autocomplete.Item\n                  key={tag.id}\n                  className=\"flex cursor-default py-2 pr-8 pl-4 text-base leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-2 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-sm data-[highlighted]:before:bg-gray-900\"\n                  value={tag}\n                >\n                  {tag.value}\n                </Autocomplete.Item>\n              )}\n            </Autocomplete.List>\n\n            <Autocomplete.Status className=\"mt-1 px-4 py-2 text-sm leading-5 text-gray-600 empty:m-0 empty:p-0\">\n              {moreCount > 0\n                ? `Hiding ${moreCount} results (type a more specific query to narrow results)`\n                : null}\n            </Autocomplete.Status>\n          </Autocomplete.Popup>\n        </Autocomplete.Positioner>\n      </Autocomplete.Portal>\n    </Autocomplete.Root>\n  );\n}\n\ninterface Tag {\n  id: string;\n  value: string;\n}\n\n// Larger dataset to make the limit visible.\nconst tags: Tag[] = [\n  { id: 't1', value: 'feature' },\n  { id: 't2', value: 'fix' },\n  { id: 't3', value: 'bug' },\n  { id: 't4', value: 'docs' },\n  { id: 't5', value: 'internal' },\n  { id: 't6', value: 'mobile' },\n  { id: 't7', value: 'frontend' },\n  { id: 't8', value: 'backend' },\n  { id: 't9', value: 'performance' },\n  { id: 't10', value: 'accessibility' },\n  { id: 't11', value: 'design' },\n  { id: 't12', value: 'research' },\n  { id: 't13', value: 'testing' },\n  { id: 't14', value: 'infrastructure' },\n  { id: 't15', value: 'documentation' },\n  { id: 'c-accordion', value: 'component: accordion' },\n  { id: 'c-alert-dialog', value: 'component: alert dialog' },\n  { id: 'c-autocomplete', value: 'component: autocomplete' },\n  { id: 'c-avatar', value: 'component: avatar' },\n  { id: 'c-checkbox', value: 'component: checkbox' },\n  { id: 'c-checkbox-group', value: 'component: checkbox group' },\n  { id: 'c-collapsible', value: 'component: collapsible' },\n  { id: 'c-combobox', value: 'component: combobox' },\n  { id: 'c-context-menu', value: 'component: context menu' },\n  { id: 'c-dialog', value: 'component: dialog' },\n  { id: 'c-field', value: 'component: field' },\n  { id: 'c-fieldset', value: 'component: fieldset' },\n  { id: 'c-filterable-menu', value: 'component: filterable menu' },\n  { id: 'c-form', value: 'component: form' },\n  { id: 'c-input', value: 'component: input' },\n  { id: 'c-menu', value: 'component: menu' },\n  { id: 'c-menubar', value: 'component: menubar' },\n  { id: 'c-meter', value: 'component: meter' },\n  { id: 'c-navigation-menu', value: 'component: navigation menu' },\n  { id: 'c-number-field', value: 'component: number field' },\n  { id: 'c-popover', value: 'component: popover' },\n  { id: 'c-preview-card', value: 'component: preview card' },\n  { id: 'c-progress', value: 'component: progress' },\n  { id: 'c-radio', value: 'component: radio' },\n  { id: 'c-scroll-area', value: 'component: scroll area' },\n  { id: 'c-select', value: 'component: select' },\n  { id: 'c-separator', value: 'component: separator' },\n  { id: 'c-slider', value: 'component: slider' },\n  { id: 'c-switch', value: 'component: switch' },\n  { id: 'c-tabs', value: 'component: tabs' },\n  { id: 'c-toast', value: 'component: toast' },\n  { id: 'c-toggle', value: 'component: toggle' },\n  { id: 'c-toggle-group', value: 'component: toggle group' },\n  { id: 'c-toolbar', value: 'component: toolbar' },\n  { id: 'c-tooltip', value: 'component: tooltip' },\n];\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/virtualized/css-modules/index.module.css",
    "content": ".Input {\n  box-sizing: border-box;\n  padding-left: 0.875rem;\n  margin: 0;\n  border: 1px solid var(--color-gray-200);\n  width: 16rem;\n  height: 2.5rem;\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  outline: none;\n\n  &:focus {\n    border-color: var(--color-blue);\n    outline: 1px solid var(--color-blue);\n  }\n\n  @media (min-width: 500px) {\n    width: 20rem;\n  }\n}\n\n.Label {\n  display: flex;\n  flex-direction: column;\n  gap: 0.25rem;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 700;\n  color: var(--color-gray-900);\n}\n\n.Positioner {\n  outline: 0;\n}\n\n.Popup {\n  box-sizing: border-box;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  width: var(--anchor-width);\n  max-height: min(22rem, var(--available-height));\n  max-width: var(--available-width);\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Scroller {\n  box-sizing: border-box;\n  height: min(22rem, var(--total-size));\n  max-height: var(--available-height);\n  overflow: auto;\n  overscroll-behavior: contain;\n  scroll-padding-block: 0.5rem;\n}\n\n.VirtualizedPlaceholder {\n  width: 100%;\n  position: relative;\n}\n\n.List {\n  padding: 0;\n}\n\n.Item {\n  box-sizing: border-box;\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  padding-block: 0.5rem;\n  padding-left: 1rem;\n  padding-right: 2rem;\n  display: flex;\n  font-size: 1rem;\n  line-height: 1rem;\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.5rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.Empty:not(:empty) {\n  box-sizing: border-box;\n  font-size: 0.925rem;\n  line-height: 1rem;\n  color: var(--color-gray-600);\n  padding: 1rem;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/virtualized/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Autocomplete } from '@base-ui/react/autocomplete';\nimport { useVirtualizer } from '@tanstack/react-virtual';\nimport styles from './index.module.css';\n\nexport default function ExampleVirtualizedAutocomplete() {\n  const virtualizerRef = React.useRef<Virtualizer | null>(null);\n\n  return (\n    <Autocomplete.Root\n      virtualized\n      items={virtualizedItems}\n      openOnInputClick\n      itemToStringValue={getItemLabel}\n      onItemHighlighted={(item, { reason, index }) => {\n        const virtualizer = virtualizerRef.current;\n\n        if (!item || !virtualizer) {\n          return;\n        }\n\n        const isStart = index === 0;\n        const isEnd = index === virtualizer.options.count - 1;\n        const shouldScroll = reason === 'none' || (reason === 'keyboard' && (isStart || isEnd));\n\n        if (shouldScroll) {\n          queueMicrotask(() => {\n            virtualizer.scrollToIndex(index, { align: isEnd ? 'start' : 'end' });\n          });\n        }\n      }}\n    >\n      <label className={styles.Label}>\n        Search 10,000 items\n        <Autocomplete.Input className={styles.Input} />\n      </label>\n\n      <Autocomplete.Portal>\n        <Autocomplete.Positioner className={styles.Positioner} sideOffset={4}>\n          <Autocomplete.Popup className={styles.Popup}>\n            <Autocomplete.Empty className={styles.Empty}>No items found.</Autocomplete.Empty>\n            <Autocomplete.List className={styles.List}>\n              <VirtualizedList virtualizerRef={virtualizerRef} />\n            </Autocomplete.List>\n          </Autocomplete.Popup>\n        </Autocomplete.Positioner>\n      </Autocomplete.Portal>\n    </Autocomplete.Root>\n  );\n}\n\nfunction VirtualizedList({\n  virtualizerRef,\n}: {\n  virtualizerRef: React.RefObject<Virtualizer | null>;\n}) {\n  const filteredItems = Autocomplete.useFilteredItems<VirtualizedItem>();\n\n  const scrollElementRef = React.useRef<HTMLDivElement | null>(null);\n\n  const virtualizer = useVirtualizer({\n    count: filteredItems.length,\n    getScrollElement: () => scrollElementRef.current,\n    estimateSize: () => 32,\n    overscan: 20,\n    paddingStart: 8,\n    paddingEnd: 8,\n    scrollPaddingEnd: 8,\n    scrollPaddingStart: 8,\n  });\n\n  React.useImperativeHandle(virtualizerRef, () => virtualizer);\n\n  const handleScrollElementRef = React.useCallback(\n    (element: HTMLDivElement | null) => {\n      scrollElementRef.current = element;\n      if (element) {\n        virtualizer.measure();\n      }\n    },\n    [virtualizer],\n  );\n\n  const totalSize = virtualizer.getTotalSize();\n\n  if (!filteredItems.length) {\n    return null;\n  }\n\n  return (\n    <div\n      role=\"presentation\"\n      ref={handleScrollElementRef}\n      className={styles.Scroller}\n      style={{ '--total-size': `${totalSize}px` } as React.CSSProperties}\n    >\n      <div\n        role=\"presentation\"\n        className={styles.VirtualizedPlaceholder}\n        style={{ height: totalSize }}\n      >\n        {virtualizer.getVirtualItems().map((virtualItem) => {\n          const item = filteredItems[virtualItem.index];\n          if (!item) {\n            return null;\n          }\n\n          return (\n            <Autocomplete.Item\n              key={virtualItem.key}\n              index={virtualItem.index}\n              data-index={virtualItem.index}\n              ref={virtualizer.measureElement}\n              value={item}\n              className={styles.Item}\n              aria-setsize={filteredItems.length}\n              aria-posinset={virtualItem.index + 1}\n              style={{\n                position: 'absolute',\n                top: 0,\n                left: 0,\n                width: '100%',\n                height: virtualItem.size,\n                transform: `translateY(${virtualItem.start}px)`,\n              }}\n            >\n              {item.name}\n            </Autocomplete.Item>\n          );\n        })}\n      </div>\n    </div>\n  );\n}\n\ninterface VirtualizedItem {\n  id: string;\n  name: string;\n}\n\nfunction getItemLabel(item: VirtualizedItem | null) {\n  return item ? item.name : '';\n}\n\nconst virtualizedItems: VirtualizedItem[] = Array.from({ length: 10000 }, (_, index) => {\n  const id = String(index + 1);\n  const indexLabel = id.padStart(4, '0');\n  return { id, name: `Item ${indexLabel}` };\n});\n\ntype Virtualizer = ReturnType<typeof useVirtualizer<HTMLDivElement, Element>>;\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/virtualized/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoAutocompleteVirtualized = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/demos/virtualized/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Autocomplete } from '@base-ui/react/autocomplete';\nimport { useVirtualizer } from '@tanstack/react-virtual';\n\nexport default function ExampleVirtualizedAutocomplete() {\n  const virtualizerRef = React.useRef<Virtualizer | null>(null);\n\n  return (\n    <Autocomplete.Root\n      virtualized\n      items={virtualizedItems}\n      openOnInputClick\n      itemToStringValue={getItemLabel}\n      onItemHighlighted={(item, { reason, index }) => {\n        const virtualizer = virtualizerRef.current;\n\n        if (!item || !virtualizer) {\n          return;\n        }\n\n        const isStart = index === 0;\n        const isEnd = index === virtualizer.options.count - 1;\n        const shouldScroll = reason === 'none' || (reason === 'keyboard' && (isStart || isEnd));\n\n        if (shouldScroll) {\n          queueMicrotask(() => {\n            virtualizer.scrollToIndex(index, { align: isEnd ? 'start' : 'end' });\n          });\n        }\n      }}\n    >\n      <label className=\"flex flex-col gap-1 text-sm leading-5 font-bold text-gray-900\">\n        Search 10,000 items\n        <Autocomplete.Input className=\"bg-[canvas] h-10 w-[16rem] md:w-[20rem] font-normal rounded-md border border-gray-200 pl-3.5 text-base font-normal text-gray-900 focus:outline-2 focus:-outline-offset-1 focus:outline-blue-800\" />\n      </label>\n\n      <Autocomplete.Portal>\n        <Autocomplete.Positioner className=\"outline-hidden\" sideOffset={4}>\n          <Autocomplete.Popup className=\"w-[var(--anchor-width)] max-h-[min(22rem,var(--available-height))] max-w-[var(--available-width)] rounded-md bg-[canvas] text-gray-900 outline-1 outline-gray-200 shadow-lg shadow-gray-200 dark:-outline-offset-1 dark:outline-gray-300\">\n            <Autocomplete.Empty className=\"px-4 py-4 text-[0.925rem] leading-4 text-gray-600 empty:m-0 empty:p-0\">\n              No items found.\n            </Autocomplete.Empty>\n            <Autocomplete.List className=\"p-0\">\n              <VirtualizedList virtualizerRef={virtualizerRef} />\n            </Autocomplete.List>\n          </Autocomplete.Popup>\n        </Autocomplete.Positioner>\n      </Autocomplete.Portal>\n    </Autocomplete.Root>\n  );\n}\n\nfunction VirtualizedList({\n  virtualizerRef,\n}: {\n  virtualizerRef: React.RefObject<Virtualizer | null>;\n}) {\n  const filteredItems = Autocomplete.useFilteredItems<VirtualizedItem>();\n\n  const scrollElementRef = React.useRef<HTMLDivElement | null>(null);\n\n  const virtualizer = useVirtualizer({\n    count: filteredItems.length,\n    getScrollElement: () => scrollElementRef.current,\n    estimateSize: () => 32,\n    overscan: 20,\n    paddingStart: 8,\n    paddingEnd: 8,\n    scrollPaddingEnd: 8,\n    scrollPaddingStart: 8,\n  });\n\n  React.useImperativeHandle(virtualizerRef, () => virtualizer);\n\n  const handleScrollElementRef = React.useCallback(\n    (element: HTMLDivElement | null) => {\n      scrollElementRef.current = element;\n      if (element) {\n        virtualizer.measure();\n      }\n    },\n    [virtualizer],\n  );\n\n  const totalSize = virtualizer.getTotalSize();\n\n  if (!filteredItems.length) {\n    return null;\n  }\n\n  return (\n    <div\n      role=\"presentation\"\n      ref={handleScrollElementRef}\n      className=\"h-[min(22rem,var(--total-size))] max-h-[var(--available-height)] overflow-auto overscroll-contain scroll-p-2\"\n      style={{ '--total-size': `${totalSize}px` } as React.CSSProperties}\n    >\n      <div role=\"presentation\" className=\"relative w-full\" style={{ height: totalSize }}>\n        {virtualizer.getVirtualItems().map((virtualItem) => {\n          const item = filteredItems[virtualItem.index];\n          if (!item) {\n            return null;\n          }\n\n          return (\n            <Autocomplete.Item\n              key={virtualItem.key}\n              index={virtualItem.index}\n              data-index={virtualItem.index}\n              ref={virtualizer.measureElement}\n              value={item}\n              className=\"flex cursor-default py-2 pr-8 pl-4 text-base leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-2 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-sm data-[highlighted]:before:bg-gray-900\"\n              aria-setsize={filteredItems.length}\n              aria-posinset={virtualItem.index + 1}\n              style={{\n                position: 'absolute',\n                top: 0,\n                left: 0,\n                width: '100%',\n                height: virtualItem.size,\n                transform: `translateY(${virtualItem.start}px)`,\n              }}\n            >\n              {item.name}\n            </Autocomplete.Item>\n          );\n        })}\n      </div>\n    </div>\n  );\n}\n\ninterface VirtualizedItem {\n  id: string;\n  name: string;\n}\n\nfunction getItemLabel(item: VirtualizedItem | null) {\n  return item ? item.name : '';\n}\n\nconst virtualizedItems: VirtualizedItem[] = Array.from({ length: 10000 }, (_, index) => {\n  const id = String(index + 1);\n  const indexLabel = id.padStart(4, '0');\n  return { id, name: `Item ${indexLabel}` };\n});\n\ntype Virtualizer = ReturnType<typeof useVirtualizer<HTMLDivElement, Element>>;\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/autocomplete/page.mdx",
    "content": "# Autocomplete\n\n<Subtitle>An input that suggests options as you type.</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"A high-quality, unstyled React autocomplete component that renders an input with a list of filtered options.\"\n/>\n\nimport { DemoAutocompleteHero } from './demos/hero';\n\n<DemoAutocompleteHero />\n\n## Usage guidelines\n\n- **Avoid when selection state is needed**: Use [Combobox](/react/components/combobox) instead of Autocomplete if the selection should be remembered and the input value cannot be custom. Unlike Combobox, Autocomplete's input can contain free-form text, as its suggestions only _optionally_ autocomplete the text.\n- **Can be used for filterable command pickers**: The input can be used as a filter for command items that perform an action when clicked when rendered inside the popup.\n- **Form controls must have an accessible name**: It can be created using a `<label>` element or the `Field` component. See the [forms guide](/react/handbook/forms).\n\n## Anatomy\n\nImport the components and place them together:\n\n```jsx title=\"Anatomy\"\nimport { Autocomplete } from '@base-ui/react/autocomplete';\n\n<Autocomplete.Root>\n  <Autocomplete.InputGroup>\n    <Autocomplete.Input />\n    <Autocomplete.Trigger />\n    <Autocomplete.Icon />\n    <Autocomplete.Clear />\n    <Autocomplete.Value />\n  </Autocomplete.InputGroup>\n\n  <Autocomplete.Portal>\n    <Autocomplete.Backdrop />\n    <Autocomplete.Positioner>\n      <Autocomplete.Popup>\n        <Autocomplete.Arrow />\n\n        <Autocomplete.Status />\n        <Autocomplete.Empty />\n\n        <Autocomplete.List>\n          <Autocomplete.Row>\n            <Autocomplete.Item />\n          </Autocomplete.Row>\n\n          <Autocomplete.Separator />\n\n          <Autocomplete.Group>\n            <Autocomplete.GroupLabel />\n          </Autocomplete.Group>\n\n          <Autocomplete.Collection />\n        </Autocomplete.List>\n      </Autocomplete.Popup>\n    </Autocomplete.Positioner>\n  </Autocomplete.Portal>\n</Autocomplete.Root>;\n```\n\n## TypeScript inference\n\nAutocomplete infers the item type from the `items` prop passed to `<Autocomplete.Root>`.\nIf using `itemToStringValue`, the value prop on the `<Autocomplete.Item>` must match the type of an item in the `items` array.\n\n## Examples\n\n### Async search\n\nLoad items asynchronously while typing and render custom status content.\n\nimport { DemoAutocompleteAsync } from './demos/async';\n\n<DemoAutocompleteAsync compact />\n\n### Inline autocomplete\n\nAutofill the input with the highlighted item while navigating with arrow keys using the `mode` prop. Accepts `aria-autocomplete` values `list`, `both`, `inline`, or `none`.\n\nimport { DemoAutocompleteInline } from './demos/inline';\n\n<DemoAutocompleteInline compact />\n\n### Grouped\n\nOrganize related options with `<Autocomplete.Group>` and `<Autocomplete.GroupLabel>` to add section headings inside the popup.\n\nGroups are represented by an array of objects with an `items` property, which itself is an array of individual items for each group. An extra property, such as `value`, can be provided for the heading text when rendering the group label.\n\n```tsx title=\"Example\" {3,9,13}\ninterface ProduceGroupItem {\n  value: string;\n  items: string[];\n}\n\nconst groups: ProduceGroupItem[] = [\n  {\n    value: 'Fruits',\n    items: ['Apple', 'Banana', 'Orange'],\n  },\n  {\n    value: 'Vegetables',\n    items: ['Carrot', 'Lettuce', 'Spinach'],\n  },\n];\n```\n\nimport { DemoAutocompleteGrouped } from './demos/grouped';\n\n<DemoAutocompleteGrouped compact />\n\n### Fuzzy matching\n\nUse fuzzy matching to find relevant results even when the query doesn't exactly match the item text.\n\nimport { DemoAutocompleteFuzzyMatching } from './demos/fuzzy-matching';\n\n<DemoAutocompleteFuzzyMatching compact />\n\n### Limit results\n\nLimit the number of visible items using the `limit` prop and guide users to refine their query using `<Autocomplete.Status>`.\n\nimport { DemoAutocompleteLimit } from './demos/limit';\n\n<DemoAutocompleteLimit compact />\n\n### Auto highlight\n\nThe first matching item can be automatically highlighted as the user types by specifying the `autoHighlight` prop on `<Autocomplete.Root>`. Set the prop's value to `\"always\"` if the highlight should always be present, such as when the list is rendered inline within a dialog.\n\nThe prop can be combined with the `keepHighlight` and `highlightItemOnHover` props to configure how the highlight behaves during mouse interactions.\n\nimport { DemoAutocompleteAutoHighlight } from './demos/auto-highlight';\n\n<DemoAutocompleteAutoHighlight compact />\n\n### Command palette\n\nUse the autocomplete input to filter a list of command items that perform an action when clicked.\n\nimport { DemoAutocompleteCommandPalette } from './demos/command-palette';\n\n<DemoAutocompleteCommandPalette compact />\n\n### Grid layout\n\nDisplay items in a grid layout, wrapping each row in `<Autocomplete.Row>` components.\n\nimport { DemoAutocompleteGrid } from './demos/grid';\n\n<DemoAutocompleteGrid compact />\n\n### Virtualized\n\nEfficiently handle large datasets using a virtualization library like `@tanstack/react-virtual`.\n\nimport { DemoAutocompleteVirtualized } from './demos/virtualized';\n\n<DemoAutocompleteVirtualized compact />\n\n## API reference\n\n<Reference component=\"Autocomplete\" parts=\"Root, Value\" />\n<Reference\n  component=\"Combobox\"\n  as=\"Autocomplete\"\n  parts=\"Input, InputGroup, Trigger, Icon, Clear, List, Portal, Backdrop, Positioner, Popup, Arrow, Status, Empty, Collection, Row, Item, Group, GroupLabel, Separator\"\n/>\n\n## useFilter\n\nMatches items against a query using `Intl.Collator` for robust string matching.\nThis hook is used when externally filtering items.\n\n### Input parameters\n\nAccepts all `Intl.CollatorOptions`, plus the following option:\n\n<PropsReferenceTable\n  data={{\n    locale: {\n      type: 'Intl.LocalesArgument',\n      description: 'The locale to use for string comparison.',\n    },\n  }}\n/>\n\n### Return value\n\n<PropsReferenceTable\n  type=\"return\"\n  data={{\n    contains: {\n      type: '(itemValue: any, query: string) => boolean',\n      description: 'Returns whether the item matches the query anywhere.',\n    },\n    startsWith: {\n      type: '(itemValue: any, query: string) => boolean',\n      description: 'Returns whether the item starts with the query.',\n    },\n    endsWith: {\n      type: '(itemValue: any, query: string) => boolean',\n      description: 'Returns whether the item ends with the query.',\n    },\n  }}\n/>\n\nexport const metadata = {\n  keywords: [\n    'React Autocomplete',\n    'Autocomplete Component',\n    'Search Suggestions',\n    'Filterable Input',\n    'Async Autocomplete',\n    'Typeahead',\n    'Lookahead',\n    'Autosuggest',\n    'Incremental Search',\n    'Predictive Text',\n    'Search Input',\n    'Accessible Combobox',\n    'Headless React Components',\n    'Base UI',\n  ],\n};\n\n## useFilteredItems\n\nReturns the internally filtered items when called inside `<Autocomplete.Root>`.\n\n### Return value\n\n<PropsReferenceTable\n  type=\"return\"\n  data={{\n    filteredItems: {\n      type: 'any[]',\n      description: 'The filtered items.',\n    },\n  }}\n/>\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/avatar/demos/hero/css-modules/index.module.css",
    "content": ".Root {\n  display: inline-flex;\n  justify-content: center;\n  align-items: center;\n  vertical-align: middle;\n  border-radius: 100%;\n  user-select: none;\n  font-weight: 400;\n  color: var(--color-gray-900);\n  background-color: var(--color-gray-100);\n  font-size: 1rem;\n  line-height: 1;\n  overflow: hidden;\n  height: 3rem;\n  width: 3rem;\n}\n\n.Image {\n  object-fit: cover;\n  height: 100%;\n  width: 100%;\n}\n\n.Fallback {\n  align-items: center;\n  display: flex;\n  justify-content: center;\n  height: 100%;\n  width: 100%;\n  font-size: 1rem;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/avatar/demos/hero/css-modules/index.tsx",
    "content": "import { Avatar } from '@base-ui/react/avatar';\nimport styles from './index.module.css';\n\nexport default function ExampleAvatar() {\n  return (\n    <div style={{ display: 'flex', gap: 20 }}>\n      <Avatar.Root className={styles.Root}>\n        <Avatar.Image\n          src=\"https://images.unsplash.com/photo-1543610892-0b1f7e6d8ac1?w=128&h=128&dpr=2&q=80\"\n          width=\"48\"\n          height=\"48\"\n          className={styles.Image}\n        />\n        <Avatar.Fallback className={styles.Fallback}>LT</Avatar.Fallback>\n      </Avatar.Root>\n      <Avatar.Root className={styles.Root}>LT</Avatar.Root>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/avatar/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoAvatarHero = createDemoWithVariants(import.meta.url, { CssModules, Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/avatar/demos/hero/tailwind/index.tsx",
    "content": "import { Avatar } from '@base-ui/react/avatar';\n\nexport default function ExampleAvatar() {\n  return (\n    <div style={{ display: 'flex', gap: 20 }}>\n      <Avatar.Root className=\"inline-flex size-12 items-center justify-center overflow-hidden rounded-full bg-gray-100 align-middle text-base text-gray-900 select-none\">\n        <Avatar.Image\n          src=\"https://images.unsplash.com/photo-1543610892-0b1f7e6d8ac1?w=128&h=128&dpr=2&q=80\"\n          width=\"48\"\n          height=\"48\"\n          className=\"size-full object-cover\"\n        />\n        <Avatar.Fallback className=\"flex size-full items-center justify-center text-base\">\n          LT\n        </Avatar.Fallback>\n      </Avatar.Root>\n      <Avatar.Root className=\"inline-flex size-12 items-center justify-center overflow-hidden rounded-full bg-gray-100 align-middle text-base text-gray-900 select-none\">\n        LT\n      </Avatar.Root>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/avatar/page.mdx",
    "content": "# Avatar\n\n<Subtitle>An easily stylable avatar component.</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"A high-quality, unstyled React avatar component that is easy to customize.\"\n/>\n\nimport { DemoAvatarHero } from './demos/hero';\n\n<DemoAvatarHero />\n\n## Anatomy\n\nImport the component and assemble its parts:\n\n```jsx title=\"Anatomy\"\nimport { Avatar } from '@base-ui/react/avatar';\n\n<Avatar.Root>\n  <Avatar.Image src=\"\" />\n  <Avatar.Fallback>LT</Avatar.Fallback>\n</Avatar.Root>;\n```\n\n## API reference\n\n<Reference component=\"Avatar\" parts=\"Root, Image, Fallback\" />\n\nexport const metadata = {\n  keywords: [\n    'React Avatar',\n    'Avatar Component',\n    'Profile Image UI',\n    'User Image',\n    'User Avatar',\n    'Profile Picture',\n    'Account Image',\n    'Profile Icon',\n    'Initials Fallback',\n    'Accessible Avatar',\n    'Headless React Components',\n    'Customizable Avatar',\n    'Base UI',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/button/demos/hero/css-modules/index.module.css",
    "content": ".Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover:not([data-disabled]) {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active:not([data-disabled]) {\n    background-color: var(--color-gray-200);\n    box-shadow: inset 0 1px 3px var(--color-gray-200);\n    border-top-color: var(--color-gray-300);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n\n  &[data-disabled] {\n    color: var(--color-gray-500);\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/button/demos/hero/css-modules/index.tsx",
    "content": "import * as React from 'react';\nimport { Button } from '@base-ui/react/button';\nimport styles from './index.module.css';\n\nexport default function ExampleButton() {\n  return <Button className={styles.Button}>Submit</Button>;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/button/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoButtonHero = createDemoWithVariants(import.meta.url, { CssModules, Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/button/demos/hero/tailwind/index.tsx",
    "content": "import * as React from 'react';\nimport { Button } from '@base-ui/react/button';\n\nexport default function ExampleButton() {\n  return (\n    <Button className=\"flex items-center justify-center h-10 px-3.5 m-0 outline-0 border border-gray-200 rounded-md bg-gray-50 font-inherit text-base font-normal leading-6 text-gray-900 select-none hover:data-[disabled]:bg-gray-50 hover:bg-gray-100 active:data-[disabled]:bg-gray-50 active:bg-gray-200 active:shadow-[inset_0_1px_3px_rgba(0,0,0,0.1)] active:border-t-gray-300 active:data-[disabled]:shadow-none active:data-[disabled]:border-t-gray-200 focus-visible:outline-2 focus-visible:outline-blue-800 focus-visible:-outline-offset-1 data-[disabled]:text-gray-500\">\n      Submit\n    </Button>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/button/demos/loading/css-modules/index.module.css",
    "content": ".Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover:not([data-disabled]) {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active:not([data-disabled]) {\n    background-color: var(--color-gray-200);\n    box-shadow: inset 0 1px 3px var(--color-gray-200);\n    border-top-color: var(--color-gray-300);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n\n  &[data-disabled] {\n    color: var(--color-gray-500);\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/button/demos/loading/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Button } from '@base-ui/react/button';\nimport styles from './index.module.css';\n\nexport default function ExampleButton() {\n  const [loading, setLoading] = React.useState(false);\n\n  return (\n    <Button\n      className={styles.Button}\n      disabled={loading}\n      focusableWhenDisabled\n      onClick={() => {\n        setLoading(true);\n        setTimeout(() => {\n          setLoading(false);\n        }, 4000);\n      }}\n    >\n      {loading ? 'Submitting' : 'Submit'}\n    </Button>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/button/demos/loading/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoButtonLoading = createDemoWithVariants(import.meta.url, { CssModules, Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/button/demos/loading/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Button } from '@base-ui/react/button';\n\nexport default function ExampleButton() {\n  const [loading, setLoading] = React.useState(false);\n\n  return (\n    <Button\n      className=\"flex items-center justify-center h-10 px-3.5 m-0 outline-0 border border-gray-200 rounded-md bg-gray-50 font-inherit text-base font-normal leading-6 text-gray-900 select-none hover:data-[disabled]:bg-gray-50 hover:bg-gray-100 active:data-[disabled]:bg-gray-50 active:bg-gray-200 active:shadow-[inset_0_1px_3px_rgba(0,0,0,0.1)] active:border-t-gray-300 active:data-[disabled]:shadow-none active:data-[disabled]:border-t-gray-200 focus-visible:outline-2 focus-visible:outline-blue-800 focus-visible:-outline-offset-1 data-[disabled]:text-gray-500\"\n      disabled={loading}\n      focusableWhenDisabled\n      onClick={() => {\n        setLoading(true);\n        setTimeout(() => {\n          setLoading(false);\n        }, 4000);\n      }}\n    >\n      {loading ? 'Submitting' : 'Submit'}\n    </Button>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/button/page.mdx",
    "content": "# Button\n\n<Subtitle>\n  A button component that can be rendered as another tag or focusable when disabled.\n</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"A high-quality, unstyled React button component that can be rendered as another tag or focusable when disabled.\"\n/>\n\nimport { DemoButtonHero } from './demos/hero';\n\n<DemoButtonHero />\n\n## Usage guidelines\n\n- **Submit buttons**: Unlike the native button element, `type=\"submit\"` must be specified on Button for it to act as a submit button.\n- **Links**: The Button component enforces button semantics (`role=\"button\"`, keyboard interaction, disabled state). It should not be used for links. See [Rendering links as buttons](#rendering-links-as-buttons) below.\n\n## Anatomy\n\nImport the component:\n\n```jsx title=\"Anatomy\"\nimport { Button } from '@base-ui/react/button';\n\n<Button />;\n```\n\n## Examples\n\n### Rendering as another tag\n\nThe button can remain keyboard accessible while being rendered as another tag, such as a `<div>`, by specifying `nativeButton={false}`.\n\n```jsx title=\"Custom tag button\" \"nativeButton\"\nimport { Button } from '@base-ui/react/button';\n\n<Button render={<div />} nativeButton={false}>\n  Button that can contain complex children\n</Button>;\n```\n\n### Rendering links as buttons\n\nThe Button component enforces button semantics. `nativeButton={false}` signals that the rendered tag is not a `<button>`, but it must still be a tag that can receive button semantics (`role=\"button\"`, keyboard interaction handlers). Links (`<a>`) have their own semantics and should not be rendered as buttons through the `render` prop.\n\nIf a link needs to look like a button visually, style the `<a>` element directly with CSS rather than using the Button component.\n\n### Loading states\n\nFor buttons that enter a loading state after being clicked, specify the `focusableWhenDisabled` prop to ensure focus remains on the button when it becomes disabled. This prevents focus from being lost and maintains the tab order.\n\nimport { DemoButtonLoading } from './demos/loading';\n\n<DemoButtonLoading compact />\n\n## API reference\n\n<Reference component=\"Button\" />\n\nexport const metadata = {\n  keywords: [\n    'React Button',\n    'Button Component',\n    'Focusable Disabled Button',\n    'Custom Element Button',\n    'Clickable Element',\n    'Action Button',\n    'Submit Button',\n    'Accessible Button',\n    'Loading State Button',\n    'Button as Link',\n    'Link Button',\n    'Headless React Components',\n    'Base UI',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/calendar/demos/animation/css-modules/index.module.css",
    "content": ".Root {\n  /* Clip overflow to prevent content spilling out when transitioning. */\n  overflow: clip;\n  --duration: 0.2s;\n  --easing: cubic-bezier(0.22, 1, 0.36, 1);\n}\n\n.HeaderLabelWrapper {\n  width: 100%;\n  display: grid;\n  justify-content: center;\n}\n\n.HeaderLabel {\n  grid-row: 1;\n  grid-column: 1;\n  background-color: var(--color-background);\n  min-width: 185px;\n  text-align: center;\n  overflow: clip;\n  /* Apply transition only when `data-ending-style` is present to allow for smooth animations from original translated positions. */\n  &[data-ending-style] {\n    &[data-current],\n    &[data-previous] {\n      @media (prefers-reduced-motion: no-preference) {\n        transition:\n          opacity var(--duration) var(--easing),\n          transform var(--duration) var(--easing);\n      }\n    }\n  }\n\n  &[data-navigation-direction='next'][data-previous] {\n    &[data-starting-style] {\n      opacity: 1;\n      transform: translateY(0);\n    }\n    &[data-ending-style] {\n      opacity: 0;\n      transform: translateY(-20px);\n    }\n  }\n\n  &[data-navigation-direction='next'][data-current] {\n    &[data-starting-style] {\n      opacity: 0;\n      transform: translateY(20px);\n    }\n    &[data-ending-style] {\n      opacity: 1;\n      transform: translateY(0);\n    }\n  }\n\n  &[data-navigation-direction='previous'][data-previous] {\n    &[data-starting-style] {\n      opacity: 1;\n      transform: translateY(0);\n    }\n    &[data-ending-style] {\n      opacity: 0;\n      transform: translateY(20px);\n    }\n  }\n\n  &[data-navigation-direction='previous'][data-current] {\n    &[data-starting-style] {\n      opacity: 0;\n      transform: translateY(-20px);\n    }\n    &[data-ending-style] {\n      opacity: 1;\n      transform: translateY(0);\n    }\n  }\n}\n\n.DayGrid {\n  /* Need to use grid to position two tbody elements centered horizontally. */\n  display: grid;\n  grid-template-rows: min-content 1fr;\n  grid-template-columns: 1fr;\n}\n\n.DayGridBody {\n  grid-row: 2;\n  grid-column: 1;\n  transform: translateX(0);\n  opacity: 1;\n\n  /* Apply transition only when `data-ending-style` is present to allow for smooth animations from original translated positions. */\n  &[data-ending-style] {\n    &[data-current],\n    &[data-previous] {\n      @media (prefers-reduced-motion: no-preference) {\n        transition:\n          opacity var(--duration) var(--easing),\n          transform var(--duration) var(--easing);\n      }\n    }\n  }\n\n  &[data-navigation-direction='next'][data-previous] {\n    &[data-starting-style] {\n      transform: translateX(0);\n      opacity: 1;\n    }\n    &[data-ending-style] {\n      transform: translateX(-100%);\n      opacity: 0;\n    }\n  }\n\n  &[data-navigation-direction='next'][data-current] {\n    &[data-starting-style] {\n      transform: translateX(100%);\n      opacity: 0;\n    }\n    &[data-ending-style] {\n      transform: translateX(0);\n      opacity: 1;\n    }\n  }\n\n  &[data-navigation-direction='previous'][data-previous] {\n    &[data-starting-style] {\n      transform: translateX(0);\n      opacity: 1;\n    }\n    &[data-ending-style] {\n      transform: translateX(100%);\n      opacity: 0;\n    }\n  }\n\n  &[data-navigation-direction='previous'][data-current] {\n    &[data-starting-style] {\n      transform: translateX(-100%);\n      opacity: 0;\n    }\n    &[data-ending-style] {\n      transform: translateX(0);\n      opacity: 1;\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/calendar/demos/animation/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport clsx from 'clsx';\nimport { format } from 'date-fns/format';\nimport { Calendar } from '@base-ui/react/calendar';\nimport styles from '../../calendar.module.css';\nimport indexStyles from './index.module.css';\n\nexport default function AnimatedCalendar() {\n  return (\n    <Calendar.Root className={clsx(styles.Root, indexStyles.Root)}>\n      {({ visibleDate }) => (\n        <React.Fragment>\n          <header className={styles.Header}>\n            <Calendar.DecrementMonth className={styles.DecrementMonth}>\n              <ChevronLeftIcon />\n            </Calendar.DecrementMonth>\n            <div className={indexStyles.HeaderLabelWrapper}>\n              <Calendar.Viewport>\n                <span className={clsx(styles.HeaderLabel, indexStyles.HeaderLabel)}>\n                  {format(visibleDate, 'MMMM yyyy')}\n                </span>\n              </Calendar.Viewport>\n            </div>\n            <Calendar.IncrementMonth className={styles.IncrementMonth}>\n              <ChevronRightIcon />\n            </Calendar.IncrementMonth>\n          </header>\n          <Calendar.DayGrid className={clsx(styles.DayGrid, indexStyles.DayGrid)}>\n            <Calendar.DayGridHeader className={styles.DayGridHeader}>\n              <Calendar.DayGridHeaderRow className={styles.DayGridHeaderRow}>\n                {(day) => (\n                  <Calendar.DayGridHeaderCell\n                    value={day}\n                    key={day.getTime()}\n                    className={styles.DayGridHeaderCell}\n                  />\n                )}\n              </Calendar.DayGridHeaderRow>\n            </Calendar.DayGridHeader>\n            <Calendar.Viewport>\n              <Calendar.DayGridBody className={clsx(styles.DayGridBody, indexStyles.DayGridBody)}>\n                {(week) => (\n                  <Calendar.DayGridRow\n                    value={week}\n                    key={week.getTime()}\n                    className={styles.DayGridRow}\n                  >\n                    {(day) => (\n                      <Calendar.DayGridCell\n                        value={day}\n                        key={day.getTime()}\n                        className={styles.DayGridCell}\n                      >\n                        <Calendar.DayButton className={styles.DayButton} />\n                      </Calendar.DayGridCell>\n                    )}\n                  </Calendar.DayGridRow>\n                )}\n              </Calendar.DayGridBody>\n            </Calendar.Viewport>\n          </Calendar.DayGrid>\n        </React.Fragment>\n      )}\n    </Calendar.Root>\n  );\n}\n\nfunction ChevronLeftIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"m15 18-6-6 6-6\" />\n    </svg>\n  );\n}\n\nfunction ChevronRightIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"m9 18 6-6-6-6\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/calendar/demos/animation/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\n\nexport const DemoCalendarAnimation = createDemoWithVariants(import.meta.url, { CssModules });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/calendar/demos/animation-motion/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { format } from 'date-fns/format';\nimport { Calendar } from '@base-ui/react/calendar';\nimport { motion, AnimatePresence } from 'motion/react';\nimport styles from '../../calendar.module.css';\n\nexport default function AnimatedCalendarWithMotion() {\n  return (\n    <Calendar.Root className={styles.Root}>\n      {({ visibleDate }) => (\n        <React.Fragment>\n          <header className={styles.Header}>\n            <Calendar.DecrementMonth className={styles.DecrementMonth}>\n              <ChevronLeftIcon />\n            </Calendar.DecrementMonth>\n            <AnimatePresence initial={false} mode=\"popLayout\">\n              <motion.span layout className={styles.HeaderLabel}>\n                {format(visibleDate, 'MMMM yyyy')}\n              </motion.span>\n            </AnimatePresence>\n            <Calendar.IncrementMonth className={styles.IncrementMonth}>\n              <ChevronRightIcon />\n            </Calendar.IncrementMonth>\n          </header>\n          <Calendar.DayGrid className={styles.DayGrid}>\n            <Calendar.DayGridHeader className={styles.DayGridHeader}>\n              <Calendar.DayGridHeaderRow className={styles.DayGridHeaderRow}>\n                {(day) => (\n                  <Calendar.DayGridHeaderCell\n                    value={day}\n                    key={day.getTime()}\n                    className={styles.DayGridHeaderCell}\n                  />\n                )}\n              </Calendar.DayGridHeaderRow>\n            </Calendar.DayGridHeader>\n            <AnimatePresence initial={false} mode=\"popLayout\">\n              <Calendar.DayGridBody\n                key={`${visibleDate.getUTCFullYear()}-${visibleDate.getMonth()}`}\n                className={styles.DayGridBody}\n                render={\n                  <motion.tbody\n                    initial={{ opacity: 0, scale: 0 }}\n                    animate={{ opacity: 1, scale: 1 }}\n                    exit={{ opacity: 0, scale: 0 }}\n                  />\n                }\n              >\n                {(week) => (\n                  <Calendar.DayGridRow\n                    value={week}\n                    key={week.getTime()}\n                    className={styles.DayGridRow}\n                  >\n                    {(day) => (\n                      <Calendar.DayGridCell\n                        value={day}\n                        key={day.getTime()}\n                        className={styles.DayGridCell}\n                      >\n                        <Calendar.DayButton className={styles.DayButton} />\n                      </Calendar.DayGridCell>\n                    )}\n                  </Calendar.DayGridRow>\n                )}\n              </Calendar.DayGridBody>\n            </AnimatePresence>\n          </Calendar.DayGrid>\n        </React.Fragment>\n      )}\n    </Calendar.Root>\n  );\n}\n\nfunction ChevronLeftIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"m15 18-6-6 6-6\" />\n    </svg>\n  );\n}\n\nfunction ChevronRightIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"m9 18 6-6-6-6\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/calendar/demos/animation-motion/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\n\nexport const DemoCalendarAnimationMotion = createDemoWithVariants(import.meta.url, { CssModules });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/calendar/demos/calendar.module.css",
    "content": ".Root {\n  border: 1px solid var(--calendar-root-border-color);\n  border-radius: 8px;\n  height: 312px;\n  display: flex;\n  flex-direction: column;\n\n  --calendar-root-color: var(--color-gray-900);\n  --calendar-root-border-color: var(--color-gray-500);\n  --calendar-button-hover-bg-color: var(--color-gray-100);\n  --calendar-button-focus-border-color: var(--color-blue);\n  --calendar-day-grid-separator-bg-color: var(--color-gray-400);\n  --calendar-day-grid-header-color: var(--color-gray-500);\n\n  --calendar-cell-selected-bg-color: var(--color-gray-900);\n  --calendar-cell-selected-color: var(--color-gray-50);\n  --calendar-cell-outside-month-color: var(--color-gray-400);\n  --calendar-cell-disabled-color: var(--color-gray-500);\n  --calendar-cell-current-border-color: var(--color-gray-500);\n  --calendar-cell-unavailable-color: var(--color-red);\n}\n\n.Header {\n  box-sizing: border-box;\n  padding: 8px 12px;\n  height: 40px;\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n}\n\n.HeaderLabel {\n  color: var(--calendar-root-color);\n}\n\n.DecrementMonth,\n.IncrementMonth {\n  border: none;\n  -webkit-user-select: none;\n  user-select: none;\n  background-color: transparent;\n  border-radius: 4px;\n  color: var(--calendar-root-color);\n  margin: 0 6px;\n  padding: 0;\n  display: flex;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--calendar-button-hover-bg-color);\n    }\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--calendar-button-focus-border-color);\n  }\n\n  &[data-disabled] {\n    pointer-events: none;\n    color: var(--calendar-cell-disabled-color);\n  }\n}\n\n.DayGrid {\n  padding: 12px;\n  height: 276px;\n  display: flex;\n  flex-direction: column;\n  gap: 4px;\n  z-index: 1;\n  position: relative;\n}\n\n.DayGridBody {\n  display: flex;\n  flex-direction: column;\n  row-gap: 2px;\n}\n\n.DayGridRow,\n.DayGridHeaderRow {\n  display: flex;\n  justify-content: center;\n}\n\n.DayGridHeaderCell {\n  width: 36px;\n  text-align: center;\n  font-size: 0.75rem;\n  color: var(--calendar-day-grid-header-color);\n  font-weight: 700;\n  padding: 0;\n}\n\n.DayGridCell {\n  padding: 0;\n}\n\n.DayButton {\n  background: none;\n  padding: 0;\n  font: inherit;\n  height: 36px;\n  width: 36px;\n  position: relative;\n  user-select: none;\n  border: none;\n  background-color: transparent;\n  outline: none;\n  box-sizing: border-box;\n  border-radius: 4px;\n  color: var(--calendar-root-color);\n\n  &::before {\n    content: '';\n    position: absolute;\n    inset: 2px;\n    border-radius: 4px;\n    border: none;\n    z-index: -1;\n    background-color: transparent;\n  }\n\n  &::after {\n    content: '';\n    border-radius: 4px;\n    position: absolute;\n    inset: 2px;\n  }\n\n  &:not([data-outside-month]):focus-visible {\n    &::after {\n      outline: 2px solid var(--calendar-button-focus-border-color);\n    }\n  }\n\n  @media (hover: hover) {\n    &:not([data-selected]):hover::before {\n      background-color: var(--calendar-button-hover-bg-color);\n    }\n  }\n\n  &[data-selected]:not([data-outside-month]) {\n    color: var(--calendar-cell-selected-color);\n  }\n\n  &[data-selected]:not([data-outside-month])::before {\n    background-color: var(--calendar-cell-selected-bg-color);\n  }\n\n  &[data-disabled] {\n    pointer-events: none;\n  }\n\n  &:not([data-outside-month])[data-disabled] {\n    color: var(--calendar-cell-disabled-color);\n  }\n\n  &:not([data-outside-month])[data-invalid] {\n    text-decoration: line-through;\n  }\n\n  &[data-outside-month] {\n    color: var(--calendar-cell-outside-month-color);\n    pointer-events: none;\n  }\n\n  &[data-current]:not([data-selected], :focus-visible)::after {\n    outline: 1px solid var(--calendar-cell-current-border-color);\n  }\n\n  &:not([data-disabled], [data-outside-month])[data-unavailable] {\n    text-decoration: line-through;\n    color: var(--calendar-cell-unavailable-color);\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/calendar/demos/hero/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { format } from 'date-fns/format';\nimport { Calendar } from '@base-ui/react/calendar';\nimport styles from '../../calendar.module.css';\n\nexport default function ExampleCalendar() {\n  return (\n    <Calendar.Root className={styles.Root}>\n      {({ visibleDate }) => (\n        <React.Fragment>\n          <header className={styles.Header}>\n            <Calendar.DecrementMonth className={styles.DecrementMonth}>\n              <ChevronLeftIcon />\n            </Calendar.DecrementMonth>\n            <span className={styles.HeaderLabel}>{format(visibleDate, 'MMMM yyyy')}</span>\n            <Calendar.IncrementMonth className={styles.IncrementMonth}>\n              <ChevronRightIcon />\n            </Calendar.IncrementMonth>\n          </header>\n          <Calendar.DayGrid className={styles.DayGrid}>\n            <Calendar.DayGridHeader className={styles.DayGridHeader}>\n              <Calendar.DayGridHeaderRow className={styles.DayGridHeaderRow}>\n                {(day) => (\n                  <Calendar.DayGridHeaderCell\n                    value={day}\n                    key={day.getTime()}\n                    className={styles.DayGridHeaderCell}\n                  />\n                )}\n              </Calendar.DayGridHeaderRow>\n            </Calendar.DayGridHeader>\n            <Calendar.DayGridBody className={styles.DayGridBody}>\n              {(week) => (\n                <Calendar.DayGridRow\n                  value={week}\n                  key={week.getTime()}\n                  className={styles.DayGridRow}\n                >\n                  {(day) => (\n                    <Calendar.DayGridCell\n                      value={day}\n                      key={day.getTime()}\n                      className={styles.DayGridCell}\n                    >\n                      <Calendar.DayButton className={styles.DayButton} />\n                    </Calendar.DayGridCell>\n                  )}\n                </Calendar.DayGridRow>\n              )}\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </React.Fragment>\n      )}\n    </Calendar.Root>\n  );\n}\n\nfunction ChevronLeftIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"m15 18-6-6 6-6\" />\n    </svg>\n  );\n}\n\nfunction ChevronRightIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"m9 18 6-6-6-6\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/calendar/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\n\nexport const DemoCalendarHero = createDemoWithVariants(import.meta.url, { CssModules });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/calendar/demos/lazy-loading/css-modules/index.module.css",
    "content": ".Root {\n  height: 420px;\n}\n\n.DayGrid {\n  height: 384px;\n}\n\n.DayGridHeaderCell {\n  width: 44px;\n}\n\n.DayButton {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  height: 52px;\n  width: 44px;\n  gap: 2px;\n}\n\n.DayNumber {\n  font-size: 0.9375rem;\n  font-weight: 400;\n  line-height: 1;\n}\n\n.Price {\n  font-size: 0.625rem;\n  line-height: 1;\n  color: var(--color-gray-500);\n\n  @media (prefers-reduced-motion: no-preference) {\n    animation: fadeIn 0.2s ease both;\n  }\n}\n\n.PriceDeal {\n  color: oklch(52% 40% 150deg);\n  font-weight: 700;\n\n  @media (prefers-color-scheme: dark) {\n    color: oklch(72% 35% 150deg);\n  }\n}\n\n.PriceSoldOut {\n  font-size: 0.625rem;\n  line-height: 1;\n  color: var(--color-gray-400);\n\n  @media (prefers-reduced-motion: no-preference) {\n    animation: fadeIn 0.2s ease both;\n  }\n}\n\n.DayButton[data-selected]:not([data-outside-month]) .Price,\n.DayButton[data-selected]:not([data-outside-month]) .PriceSoldOut {\n  color: inherit;\n}\n\n.Skeleton {\n  display: block;\n  width: 24px;\n  height: 10px;\n  border-radius: 2px;\n  background: linear-gradient(\n    90deg,\n    var(--color-gray-300) 25%,\n    var(--color-gray-200) 50%,\n    var(--color-gray-300) 75%\n  );\n  background-size: 200% 100%;\n\n  @media (prefers-reduced-motion: no-preference) {\n    animation: shimmer 1.5s ease-in-out infinite;\n  }\n}\n\n@keyframes shimmer {\n  0% {\n    background-position: 200% 0;\n  }\n\n  100% {\n    background-position: -200% 0;\n  }\n}\n\n@keyframes fadeIn {\n  from {\n    opacity: 0;\n  }\n\n  to {\n    opacity: 1;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/calendar/demos/lazy-loading/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport clsx from 'clsx';\nimport { format } from 'date-fns/format';\nimport { Calendar } from '@base-ui/react/calendar';\nimport { useTimeout } from '@base-ui/utils/useTimeout';\nimport styles from '../../calendar.module.css';\nimport indexStyles from './index.module.css';\n\nfunction DayPrice({\n  loading,\n  price,\n  isDeal,\n  loadingDelay,\n  revealDelay,\n}: {\n  loading: boolean;\n  price: number | null | undefined;\n  isDeal: boolean;\n  loadingDelay: string;\n  revealDelay: string;\n}) {\n  if (loading) {\n    return (\n      <span\n        className={indexStyles.Skeleton}\n        style={{ animationDelay: loadingDelay }}\n        aria-hidden=\"true\"\n      />\n    );\n  }\n  if (price != null) {\n    return (\n      <span\n        className={clsx(indexStyles.Price, isDeal && indexStyles.PriceDeal)}\n        style={{ animationDelay: revealDelay }}\n      >\n        ${price}\n      </span>\n    );\n  }\n  return (\n    <span className={indexStyles.PriceSoldOut} style={{ animationDelay: revealDelay }}>\n      —\n    </span>\n  );\n}\n\nfunction CalendarContent() {\n  const { visibleDate } = Calendar.useContext();\n  const [prices, setPrices] = React.useState<Record<string, number | null>>({});\n  const [loading, setLoading] = React.useState(true);\n  const timeout = useTimeout();\n\n  const monthKey = format(visibleDate, 'yyyy-MM');\n\n  React.useEffect(() => {\n    const year = parseInt(monthKey.split('-')[0], 10);\n    const month = parseInt(monthKey.split('-')[1], 10) - 1;\n    setLoading(true);\n    timeout.start(800, () => {\n      setPrices((prev) => ({ ...prev, ...generateMonthPrices(year, month) }));\n      setLoading(false);\n    });\n  }, [monthKey, timeout]);\n\n  const minPrice = React.useMemo(() => {\n    const monthPrices = Object.entries(prices)\n      .filter(([key]) => key.startsWith(monthKey))\n      .flatMap(([, p]) => (p != null ? [p] : []));\n    return monthPrices.length > 0 ? Math.min(...monthPrices) : null;\n  }, [prices, monthKey]);\n\n  return (\n    <React.Fragment>\n      <header className={styles.Header}>\n        <Calendar.DecrementMonth className={styles.DecrementMonth}>\n          <ChevronLeftIcon />\n        </Calendar.DecrementMonth>\n        <span className={styles.HeaderLabel}>{format(visibleDate, 'MMMM yyyy')}</span>\n        <Calendar.IncrementMonth className={styles.IncrementMonth}>\n          <ChevronRightIcon />\n        </Calendar.IncrementMonth>\n      </header>\n      <Calendar.DayGrid className={clsx(styles.DayGrid, indexStyles.DayGrid)}>\n        <Calendar.DayGridHeader className={styles.DayGridHeader}>\n          <Calendar.DayGridHeaderRow className={styles.DayGridHeaderRow}>\n            {(day) => (\n              <Calendar.DayGridHeaderCell\n                value={day}\n                key={day.getTime()}\n                className={clsx(styles.DayGridHeaderCell, indexStyles.DayGridHeaderCell)}\n              />\n            )}\n          </Calendar.DayGridHeaderRow>\n        </Calendar.DayGridHeader>\n        <Calendar.DayGridBody className={styles.DayGridBody}>\n          {(week) => (\n            <Calendar.DayGridRow value={week} key={week.getTime()} className={styles.DayGridRow}>\n              {(day) => {\n                const dateKey = format(day, 'yyyy-MM-dd');\n                const inCurrentMonth = dateKey.startsWith(monthKey);\n                const price = prices[dateKey];\n                const isDeal = inCurrentMonth && price != null && price === minPrice;\n                const daySeed =\n                  day.getFullYear() * 10000 + (day.getMonth() + 1) * 100 + day.getDate();\n                const loadingDelay = `${(seededRandom(daySeed + 50) * 0.4).toFixed(3)}s`;\n                const revealDelay = `${(seededRandom(daySeed + 60) * 0.5).toFixed(3)}s`;\n                return (\n                  <Calendar.DayGridCell\n                    value={day}\n                    key={day.getTime()}\n                    className={styles.DayGridCell}\n                  >\n                    <Calendar.DayButton className={clsx(styles.DayButton, indexStyles.DayButton)}>\n                      <span className={indexStyles.DayNumber}>{format(day, 'd')}</span>\n                      {inCurrentMonth && (\n                        <DayPrice\n                          loading={loading}\n                          price={price}\n                          isDeal={isDeal}\n                          loadingDelay={loadingDelay}\n                          revealDelay={revealDelay}\n                        />\n                      )}\n                    </Calendar.DayButton>\n                  </Calendar.DayGridCell>\n                );\n              }}\n            </Calendar.DayGridRow>\n          )}\n        </Calendar.DayGridBody>\n      </Calendar.DayGrid>\n    </React.Fragment>\n  );\n}\n\nexport default function FlightPriceCalendar() {\n  return (\n    <Calendar.Root\n      className={clsx(styles.Root, indexStyles.Root)}\n      aria-label=\"Flight departure date\"\n    >\n      <CalendarContent />\n    </Calendar.Root>\n  );\n}\n\nfunction seededRandom(seed: number) {\n  const x = Math.sin(seed) * 10000;\n  return x - Math.floor(x);\n}\n\nfunction generateMonthPrices(year: number, month: number): Record<string, number | null> {\n  const prices: Record<string, number | null> = {};\n  const daysInMonth = new Date(year, month + 1, 0).getDate();\n  for (let d = 1; d <= daysInMonth; d += 1) {\n    const date = new Date(year, month, d);\n    const dateKey = format(date, 'yyyy-MM-dd');\n    const seed = year * 10000 + (month + 1) * 100 + d;\n    const rand = seededRandom(seed);\n    prices[dateKey] = rand < 0.15 ? null : Math.floor(79 + seededRandom(seed + 1) * 320);\n  }\n  return prices;\n}\n\nfunction ChevronLeftIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"m15 18-6-6 6-6\" />\n    </svg>\n  );\n}\n\nfunction ChevronRightIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"m9 18 6-6-6-6\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/calendar/demos/lazy-loading/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\n\nexport const DemoCalendarLazyLoading = createDemoWithVariants(import.meta.url, { CssModules });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/calendar/demos/timezone/css-modules/index.module.css",
    "content": ".Wrapper {\n  display: flex;\n  flex-flow: row wrap;\n  justify-content: center;\n}\n\n.Text {\n  color: var(--color-gray-900);\n  width: 100%;\n  text-align: center;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/calendar/demos/timezone/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { format } from 'date-fns/format';\nimport { TZDate } from '@date-fns/tz';\nimport { Calendar } from '@base-ui/react/calendar';\nimport styles from '../../calendar.module.css';\nimport indexStyles from './index.module.css';\n\nexport default function CalendarWithTimezone() {\n  const [value, setValue] = React.useState<Date | null>(\n    new TZDate(2025, 3, 17, 4, 45, 0, 0, 'Europe/Paris'),\n  );\n  return (\n    <div className={indexStyles.Wrapper}>\n      <p className={indexStyles.Text}>\n        Calendar is displayed in <strong>America/New_York</strong> timezone.\n      </p>\n      <Calendar.Root\n        className={styles.Root}\n        timezone=\"America/New_York\"\n        value={value}\n        onValueChange={setValue}\n      >\n        {({ visibleDate }) => (\n          <React.Fragment>\n            <header className={styles.Header}>\n              <Calendar.DecrementMonth className={styles.DecrementMonth}>\n                <ChevronLeftIcon />\n              </Calendar.DecrementMonth>\n              <span className={styles.HeaderLabel}>{format(visibleDate, 'MMMM yyyy')}</span>\n              <Calendar.IncrementMonth className={styles.IncrementMonth}>\n                <ChevronRightIcon />\n              </Calendar.IncrementMonth>\n            </header>\n            <Calendar.DayGrid className={styles.DayGrid}>\n              <Calendar.DayGridHeader className={styles.DayGridHeader}>\n                <Calendar.DayGridHeaderRow className={styles.DayGridHeaderRow}>\n                  {(day) => (\n                    <Calendar.DayGridHeaderCell\n                      value={day}\n                      key={day.getTime()}\n                      className={styles.DayGridHeaderCell}\n                    />\n                  )}\n                </Calendar.DayGridHeaderRow>\n              </Calendar.DayGridHeader>\n              <Calendar.DayGridBody className={styles.DayGridBody}>\n                {(week) => (\n                  <Calendar.DayGridRow\n                    value={week}\n                    key={week.getTime()}\n                    className={styles.DayGridRow}\n                  >\n                    {(day) => (\n                      <Calendar.DayGridCell\n                        value={day}\n                        key={day.getTime()}\n                        className={styles.DayGridCell}\n                      >\n                        <Calendar.DayButton className={styles.DayButton} />\n                      </Calendar.DayGridCell>\n                    )}\n                  </Calendar.DayGridRow>\n                )}\n              </Calendar.DayGridBody>\n            </Calendar.DayGrid>\n          </React.Fragment>\n        )}\n      </Calendar.Root>\n      {value && <p className={indexStyles.Text}>Stored date: {value.toString()}</p>}\n    </div>\n  );\n}\n\nfunction ChevronLeftIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"m15 18-6-6 6-6\" />\n    </svg>\n  );\n}\n\nfunction ChevronRightIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"m9 18 6-6-6-6\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/calendar/demos/timezone/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\n\nexport const DemoCalendarTimezone = createDemoWithVariants(import.meta.url, { CssModules });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/calendar/demos/timezone-display/css-modules/index.module.css",
    "content": ".Wrapper {\n  display: flex;\n  flex-flow: column wrap;\n  gap: 16px;\n}\n\n.Select {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 0.75rem;\n  height: 2.5rem;\n  padding-left: 0.875rem;\n  padding-right: 0.75rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: canvas;\n  font-family: inherit;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  -webkit-user-select: none;\n  user-select: none;\n  min-width: 10rem;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.SelectIcon {\n  display: flex;\n}\n\n.Positioner {\n  outline: none;\n  z-index: 1;\n  -webkit-user-select: none;\n  user-select: none;\n}\n\n.Popup {\n  box-sizing: border-box;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  background-clip: padding-box;\n  color: var(--color-gray-900);\n  min-width: var(--anchor-width);\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  &[data-side='none'] {\n    transition: none;\n    transform: none;\n    opacity: 1;\n    min-width: calc(var(--anchor-width) + 1rem);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n  }\n}\n\n.List {\n  box-sizing: border-box;\n  position: relative;\n  padding-block: 0.25rem;\n  overflow-y: auto;\n  max-height: var(--available-height);\n  scroll-padding-block: 1.5rem;\n}\n\n.Item {\n  box-sizing: border-box;\n  outline: 0;\n  font-size: 0.875rem;\n  line-height: 1rem;\n  padding-block: 0.5rem;\n  padding-left: 0.625rem;\n  padding-right: 1rem;\n  display: grid;\n  gap: 0.5rem;\n  align-items: center;\n  grid-template-columns: 0.75rem 1fr;\n  cursor: default;\n  -webkit-user-select: none;\n  user-select: none;\n\n  @media (pointer: coarse) {\n    padding-block: 0.625rem;\n    font-size: 0.925rem;\n  }\n\n  [data-side='none'] & {\n    font-size: 1rem;\n    padding-right: 3rem;\n  }\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.25rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.ItemIndicator {\n  grid-column-start: 1;\n}\n\n.ItemIndicatorIcon {\n  display: block;\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.ItemText {\n  grid-column-start: 2;\n}\n\n.ScrollArrow {\n  width: 100%;\n  background: canvas;\n  z-index: 1;\n  text-align: center;\n  cursor: default;\n  border-radius: 0.375rem;\n  height: 1rem;\n  font-size: 0.75rem;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n\n  &::before {\n    content: '';\n    position: absolute;\n    width: 100%;\n    height: 100%;\n    left: 0;\n  }\n\n  &[data-direction='up'] {\n    &[data-side='none'] {\n      &::before {\n        top: -100%;\n      }\n    }\n  }\n\n  &[data-direction='down'] {\n    bottom: 0;\n\n    &[data-side='none'] {\n      &::before {\n        bottom: -100%;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/calendar/demos/timezone-display/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { format } from 'date-fns/format';\nimport { Calendar } from '@base-ui/react/calendar';\nimport { Select } from '@base-ui/react/select';\nimport styles from '../../calendar.module.css';\nimport indexStyles from './index.module.css';\n\nexport default function CalendarWithTimezoneDisplay() {\n  const [timezone, setTimezone] = React.useState<Timezone | null>('Australia/Sydney');\n  return (\n    <div className={indexStyles.Wrapper}>\n      <TimezoneSelect\n        value={timezone}\n        onValueChange={(value) => setTimezone(value as Timezone | null)}\n      />\n      <Calendar.Root className={styles.Root} timezone={timezone ?? undefined}>\n        {({ visibleDate }) => (\n          <React.Fragment>\n            <header className={styles.Header}>\n              <Calendar.DecrementMonth className={styles.DecrementMonth}>\n                <ChevronLeftIcon />\n              </Calendar.DecrementMonth>\n              <span className={styles.HeaderLabel}>{format(visibleDate, 'MMMM yyyy')}</span>\n              <Calendar.IncrementMonth className={styles.IncrementMonth}>\n                <ChevronRightIcon />\n              </Calendar.IncrementMonth>\n            </header>\n            <Calendar.DayGrid className={styles.DayGrid}>\n              <Calendar.DayGridHeader className={styles.DayGridHeader}>\n                <Calendar.DayGridHeaderRow className={styles.DayGridHeaderRow}>\n                  {(day) => (\n                    <Calendar.DayGridHeaderCell\n                      value={day}\n                      key={day.getTime()}\n                      className={styles.DayGridHeaderCell}\n                    />\n                  )}\n                </Calendar.DayGridHeaderRow>\n              </Calendar.DayGridHeader>\n              <Calendar.DayGridBody className={styles.DayGridBody}>\n                {(week) => (\n                  <Calendar.DayGridRow\n                    value={week}\n                    key={week.getTime()}\n                    className={styles.DayGridRow}\n                  >\n                    {(day) => (\n                      <Calendar.DayGridCell\n                        value={day}\n                        key={day.getTime()}\n                        className={styles.DayGridCell}\n                      >\n                        <Calendar.DayButton className={styles.DayButton} />\n                      </Calendar.DayGridCell>\n                    )}\n                  </Calendar.DayGridRow>\n                )}\n              </Calendar.DayGridBody>\n            </Calendar.DayGrid>\n          </React.Fragment>\n        )}\n      </Calendar.Root>\n    </div>\n  );\n}\n\nfunction TimezoneSelect(props: Omit<Select.Root.Props<string, false>, 'children'>) {\n  return (\n    <Select.Root {...props}>\n      <Select.Trigger className={indexStyles.Select}>\n        <Select.Value placeholder=\"Select timezone\" />\n        <Select.Icon className={indexStyles.SelectIcon}>\n          <ChevronUpDownIcon />\n        </Select.Icon>\n      </Select.Trigger>\n      <Select.Portal>\n        <Select.Positioner className={indexStyles.Positioner} sideOffset={8}>\n          <Select.Popup className={indexStyles.Popup}>\n            <Select.ScrollUpArrow className={indexStyles.ScrollArrow} />\n            <Select.List className={indexStyles.List}>\n              {timezones.map((timezone) => (\n                <Select.Item key={timezone} value={timezone} className={indexStyles.Item}>\n                  <Select.ItemIndicator className={indexStyles.ItemIndicator}>\n                    <CheckIcon className={indexStyles.ItemIndicatorIcon} />\n                  </Select.ItemIndicator>\n                  <Select.ItemText className={indexStyles.ItemText}>{timezone}</Select.ItemText>\n                </Select.Item>\n              ))}\n            </Select.List>\n            <Select.ScrollDownArrow className={indexStyles.ScrollArrow} />\n          </Select.Popup>\n        </Select.Positioner>\n      </Select.Portal>\n    </Select.Root>\n  );\n}\n\ntype Timezone = (typeof timezones)[number];\n\nconst timezones = [\n  'America/Los_Angeles',\n  'Europe/Paris',\n  'Asia/Tokyo',\n  'Australia/Sydney',\n] as const;\n\nfunction ChevronUpDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"8\"\n      height=\"12\"\n      viewBox=\"0 0 8 12\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"1.5\"\n      {...props}\n    >\n      <path d=\"M0.5 4.5L4 1.5L7.5 4.5\" />\n      <path d=\"M0.5 7.5L4 10.5L7.5 7.5\" />\n    </svg>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\nfunction ChevronLeftIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"m15 18-6-6 6-6\" />\n    </svg>\n  );\n}\n\nfunction ChevronRightIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"m9 18 6-6-6-6\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/calendar/demos/timezone-display/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\n\nexport const DemoCalendarTimezoneDisplay = createDemoWithVariants(import.meta.url, { CssModules });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/calendar/demos/unavailable-dates/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { format } from 'date-fns/format';\nimport { Calendar } from '@base-ui/react/calendar';\nimport styles from '../../calendar.module.css';\n\nconst holidays: Array<[number, number]> = [\n  [0, 1], // New Year's Day\n  [6, 4], // Independence Day\n  [10, 11], // Veterans Day\n  [11, 25], // Christmas Day\n];\n\nfunction isDateUnavailable(date: Date) {\n  const day = date.getDay();\n  const month = date.getMonth();\n  const dayOfMonth = date.getDate();\n\n  // Weekends\n  if (day === 0 || day === 6) {\n    return true;\n  }\n\n  // US holidays\n  if (holidays.some(([m, d]) => month === m && dayOfMonth === d)) {\n    return true;\n  }\n\n  // First Monday of every month (maintenance day)\n  if (day === 1 && dayOfMonth <= 7) {\n    return true;\n  }\n\n  return false;\n}\n\nexport default function UnavailableDatesCalendar() {\n  return (\n    <Calendar.Root\n      className={styles.Root}\n      isDateUnavailable={isDateUnavailable}\n      aria-label=\"Appointment date\"\n    >\n      {({ visibleDate }) => (\n        <React.Fragment>\n          <header className={styles.Header}>\n            <Calendar.DecrementMonth className={styles.DecrementMonth}>\n              <ChevronLeftIcon />\n            </Calendar.DecrementMonth>\n            <span className={styles.HeaderLabel}>{format(visibleDate, 'MMMM yyyy')}</span>\n            <Calendar.IncrementMonth className={styles.IncrementMonth}>\n              <ChevronRightIcon />\n            </Calendar.IncrementMonth>\n          </header>\n          <Calendar.DayGrid className={styles.DayGrid}>\n            <Calendar.DayGridHeader className={styles.DayGridHeader}>\n              <Calendar.DayGridHeaderRow className={styles.DayGridHeaderRow}>\n                {(day) => (\n                  <Calendar.DayGridHeaderCell\n                    value={day}\n                    key={day.getTime()}\n                    className={styles.DayGridHeaderCell}\n                  />\n                )}\n              </Calendar.DayGridHeaderRow>\n            </Calendar.DayGridHeader>\n            <Calendar.DayGridBody className={styles.DayGridBody}>\n              {(week) => (\n                <Calendar.DayGridRow\n                  value={week}\n                  key={week.getTime()}\n                  className={styles.DayGridRow}\n                >\n                  {(day) => (\n                    <Calendar.DayGridCell\n                      value={day}\n                      key={day.getTime()}\n                      className={styles.DayGridCell}\n                    >\n                      <Calendar.DayButton className={styles.DayButton} />\n                    </Calendar.DayGridCell>\n                  )}\n                </Calendar.DayGridRow>\n              )}\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </React.Fragment>\n      )}\n    </Calendar.Root>\n  );\n}\n\nfunction ChevronLeftIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"m15 18-6-6 6-6\" />\n    </svg>\n  );\n}\n\nfunction ChevronRightIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"m9 18 6-6-6-6\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/calendar/demos/unavailable-dates/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\n\nexport const DemoCalendarUnavailableDates = createDemoWithVariants(import.meta.url, { CssModules });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/calendar/demos/validation/css-modules/index.module.css",
    "content": ".Wrapper {\n  display: flex;\n  flex-flow: row wrap;\n  gap: 24px;\n  justify-content: center;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/calendar/demos/validation/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { format } from 'date-fns/format';\nimport { Calendar } from '@base-ui/react/calendar';\nimport indexStyles from './index.module.css';\nimport styles from '../../calendar.module.css';\n\nconst today = new Date();\n\nexport default function MinMaxDateCalendars() {\n  return (\n    <div className={indexStyles.Wrapper}>\n      <ValidationCalendar minDate={today} />\n      <ValidationCalendar maxDate={today} />\n    </div>\n  );\n}\n\nfunction ValidationCalendar(props: Calendar.Root.Props) {\n  return (\n    <Calendar.Root className={styles.Root} {...props}>\n      {({ visibleDate }) => (\n        <React.Fragment>\n          <header className={styles.Header}>\n            <Calendar.DecrementMonth className={styles.DecrementMonth}>\n              <ChevronLeftIcon />\n            </Calendar.DecrementMonth>\n            <span className={styles.HeaderLabel}>{format(visibleDate, 'MMMM yyyy')}</span>\n            <Calendar.IncrementMonth className={styles.IncrementMonth}>\n              <ChevronRightIcon />\n            </Calendar.IncrementMonth>\n          </header>\n          <Calendar.DayGrid className={styles.DayGrid}>\n            <Calendar.DayGridHeader className={styles.DayGridHeader}>\n              <Calendar.DayGridHeaderRow className={styles.DayGridHeaderRow}>\n                {(day) => (\n                  <Calendar.DayGridHeaderCell\n                    value={day}\n                    key={day.getTime()}\n                    className={styles.DayGridHeaderCell}\n                  />\n                )}\n              </Calendar.DayGridHeaderRow>\n            </Calendar.DayGridHeader>\n            <Calendar.DayGridBody className={styles.DayGridBody}>\n              {(week) => (\n                <Calendar.DayGridRow\n                  value={week}\n                  key={week.getTime()}\n                  className={styles.DayGridRow}\n                >\n                  {(day) => (\n                    <Calendar.DayGridCell\n                      value={day}\n                      key={day.getTime()}\n                      className={styles.DayGridCell}\n                    >\n                      <Calendar.DayButton className={styles.DayButton} />\n                    </Calendar.DayGridCell>\n                  )}\n                </Calendar.DayGridRow>\n              )}\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </React.Fragment>\n      )}\n    </Calendar.Root>\n  );\n}\n\nfunction ChevronLeftIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"m15 18-6-6 6-6\" />\n    </svg>\n  );\n}\n\nfunction ChevronRightIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"m9 18 6-6-6-6\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/calendar/demos/validation/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\n\nexport const DemoCalendarValidation = createDemoWithVariants(import.meta.url, { CssModules });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/calendar/demos/week-numbers/css-modules/index.module.css",
    "content": ".DayWeekNumber {\n  height: 36px;\n  width: 36px;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  font-size: 0.75rem;\n  color: var(--calendar-day-grid-header-color);\n  /* Avoid the bold font weight from the th element */\n  font-weight: 700;\n  padding: 0;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/calendar/demos/week-numbers/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { format } from 'date-fns/format';\nimport { startOfWeek } from 'date-fns/startOfWeek';\nimport { startOfMonth } from 'date-fns/startOfMonth';\nimport { getWeek } from 'date-fns/getWeek';\nimport { Calendar } from '@base-ui/react/calendar';\nimport styles from '../../calendar.module.css';\nimport indexStyles from './index.module.css';\n\nexport default function CalendarWithWeekNumbers() {\n  const getWeekList = Calendar.useWeekList();\n  const getDayList = Calendar.useDayList();\n\n  return (\n    <Calendar.Root className={styles.Root}>\n      {({ visibleDate }) => (\n        <React.Fragment>\n          <header className={styles.Header}>\n            <Calendar.DecrementMonth className={styles.DecrementMonth}>\n              <ChevronLeftIcon />\n            </Calendar.DecrementMonth>\n            <span className={styles.HeaderLabel}>{format(visibleDate, 'MMMM yyyy')}</span>\n            <Calendar.IncrementMonth className={styles.IncrementMonth}>\n              <ChevronRightIcon />\n            </Calendar.IncrementMonth>\n          </header>\n          <Calendar.DayGrid className={styles.DayGrid}>\n            <Calendar.DayGridHeader className={styles.DayGridHeader}>\n              <Calendar.DayGridHeaderRow className={styles.DayGridHeaderRow}>\n                <th\n                  role=\"columnheader\"\n                  aria-label=\"Week number\"\n                  className={styles.DayGridHeaderCell}\n                >\n                  #\n                </th>\n                {getDayList({ date: startOfWeek(new Date()), amount: 7 }).map((day) => (\n                  <Calendar.DayGridHeaderCell\n                    value={day}\n                    key={day.getTime()}\n                    className={styles.DayGridHeaderCell}\n                  />\n                ))}\n              </Calendar.DayGridHeaderRow>\n            </Calendar.DayGridHeader>\n            <Calendar.DayGridBody className={styles.DayGridBody}>\n              {getWeekList({\n                date: startOfMonth(visibleDate),\n                amount: 'end-of-month',\n              }).map((week) => (\n                <Calendar.DayGridRow\n                  value={week}\n                  key={week.getTime()}\n                  className={styles.DayGridRow}\n                >\n                  <th\n                    className={indexStyles.DayWeekNumber}\n                    scope=\"row\"\n                    aria-label={`Week ${getWeek(week)}`}\n                  >\n                    {getWeek(week)}\n                  </th>\n                  {getDayList({ date: week, amount: 7 }).map((day) => (\n                    <Calendar.DayGridCell\n                      value={day}\n                      key={day.getTime()}\n                      className={styles.DayGridCell}\n                    >\n                      <Calendar.DayButton className={styles.DayButton} />\n                    </Calendar.DayGridCell>\n                  ))}\n                </Calendar.DayGridRow>\n              ))}\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </React.Fragment>\n      )}\n    </Calendar.Root>\n  );\n}\n\nfunction ChevronLeftIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"m15 18-6-6 6-6\" />\n    </svg>\n  );\n}\n\nfunction ChevronRightIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"m9 18 6-6-6-6\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/calendar/demos/week-numbers/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\n\nexport const DemoCalendarWeekNumber = createDemoWithVariants(import.meta.url, { CssModules });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/calendar/demos/year-month-select/css-modules/index.module.css",
    "content": ".HeaderSelectWrapper {\n  display: flex;\n  flex: 1;\n  justify-content: space-between;\n}\n\n.Select {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 0.3rem;\n  height: 1.75rem;\n  padding-left: 0.5rem;\n  padding-right: 0.375rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: canvas;\n  font-family: inherit;\n  font-size: 1rem;\n  line-height: 1.25rem;\n  color: var(--color-gray-900);\n  -webkit-user-select: none;\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n\n  &[data-month-select] {\n    min-width: 6.854rem;\n  }\n}\n\n.SelectIcon {\n  display: flex;\n}\n\n.Positioner {\n  outline: none;\n  z-index: 1;\n  -webkit-user-select: none;\n  user-select: none;\n}\n\n.Popup {\n  box-sizing: border-box;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  background-clip: padding-box;\n  color: var(--color-gray-900);\n  min-width: var(--anchor-width);\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  &[data-side='none'] {\n    transition: none;\n    transform: none;\n    opacity: 1;\n    min-width: calc(var(--anchor-width) + 1rem);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n  }\n}\n\n.List {\n  box-sizing: border-box;\n  position: relative;\n  padding-block: 0.25rem;\n  overflow-y: auto;\n  max-height: var(--available-height);\n  scroll-padding-block: 1.5rem;\n}\n\n.Item {\n  box-sizing: border-box;\n  outline: 0;\n  font-size: 0.875rem;\n  line-height: 1rem;\n  padding-block: 0.5rem;\n  padding-left: 0.625rem;\n  padding-right: 1rem;\n  display: grid;\n  gap: 0.5rem;\n  align-items: center;\n  grid-template-columns: 0.75rem 1fr;\n  cursor: default;\n  -webkit-user-select: none;\n  user-select: none;\n\n  @media (pointer: coarse) {\n    padding-block: 0.625rem;\n    font-size: 0.925rem;\n  }\n\n  [data-side='none'] & {\n    font-size: 1rem;\n    padding-right: 3rem;\n  }\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.25rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.ItemIndicator {\n  grid-column-start: 1;\n}\n\n.ItemIndicatorIcon {\n  display: block;\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.ItemText {\n  grid-column-start: 2;\n}\n\n.ScrollArrow {\n  width: 100%;\n  background: canvas;\n  z-index: 1;\n  text-align: center;\n  cursor: default;\n  border-radius: 0.375rem;\n  height: 1rem;\n  font-size: 0.75rem;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n\n  &::before {\n    content: '';\n    position: absolute;\n    width: 100%;\n    height: 100%;\n    left: 0;\n  }\n\n  &[data-direction='up'] {\n    &[data-side='none'] {\n      &::before {\n        top: -100%;\n      }\n    }\n  }\n\n  &[data-direction='down'] {\n    bottom: 0;\n\n    &[data-side='none'] {\n      &::before {\n        bottom: -100%;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/calendar/demos/year-month-select/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { format } from 'date-fns/format';\nimport { getMonth } from 'date-fns/getMonth';\nimport { getYear } from 'date-fns/getYear';\nimport { Calendar } from '@base-ui/react/calendar';\nimport { Select } from '@base-ui/react/select';\nimport styles from '../../calendar.module.css';\nimport indexStyles from './index.module.css';\n\nconst YEAR_PAST = 40;\nconst YEAR_FUTURE = 10;\nconst MONTHS = Array.from({ length: 12 }, (_, i) => format(new Date(2000, i, 1), 'MMMM'));\n\nfunction CalendarContent() {\n  const { visibleDate, setVisibleDate } = Calendar.useContext();\n  const currentMonth = getMonth(visibleDate);\n  const currentYear = getYear(visibleDate);\n  const todayYear = getYear(new Date());\n  const years = Array.from(\n    { length: YEAR_PAST + YEAR_FUTURE + 1 },\n    (_, i) => todayYear - YEAR_PAST + i,\n  );\n\n  return (\n    <React.Fragment>\n      <header className={styles.Header}>\n        <Calendar.DecrementMonth className={styles.DecrementMonth}>\n          <ChevronLeftIcon />\n        </Calendar.DecrementMonth>\n        <div className={indexStyles.HeaderSelectWrapper}>\n          <Select.Root\n            value={currentMonth}\n            onValueChange={(value, eventDetails) => {\n              if (value != null) {\n                setVisibleDate(\n                  new Date(currentYear, value, 1),\n                  eventDetails.event,\n                  eventDetails.trigger as HTMLElement,\n                  'month-change',\n                );\n              }\n            }}\n          >\n            <Select.Trigger className={indexStyles.Select} aria-label=\"Month\" data-month-select>\n              <Select.Value>{(value: number) => MONTHS[value]}</Select.Value>\n              <Select.Icon className={indexStyles.SelectIcon}>\n                <ChevronUpDownIcon />\n              </Select.Icon>\n            </Select.Trigger>\n            <Select.Portal>\n              <Select.Positioner className={indexStyles.Positioner} sideOffset={8}>\n                <Select.Popup className={indexStyles.Popup}>\n                  <Select.ScrollUpArrow className={indexStyles.ScrollArrow} />\n                  <Select.List className={indexStyles.List}>\n                    {MONTHS.map((name, index) => (\n                      <Select.Item key={name} value={index} className={indexStyles.Item}>\n                        <Select.ItemIndicator className={indexStyles.ItemIndicator}>\n                          <CheckIcon className={indexStyles.ItemIndicatorIcon} />\n                        </Select.ItemIndicator>\n                        <Select.ItemText className={indexStyles.ItemText}>{name}</Select.ItemText>\n                      </Select.Item>\n                    ))}\n                  </Select.List>\n                  <Select.ScrollDownArrow className={indexStyles.ScrollArrow} />\n                </Select.Popup>\n              </Select.Positioner>\n            </Select.Portal>\n          </Select.Root>\n          <Select.Root\n            value={currentYear}\n            onValueChange={(value, eventDetails) => {\n              if (value != null) {\n                setVisibleDate(\n                  new Date(value, currentMonth, 1),\n                  eventDetails.event,\n                  eventDetails.trigger as HTMLElement,\n                  'month-change',\n                );\n              }\n            }}\n          >\n            <Select.Trigger className={indexStyles.Select} aria-label=\"Year\">\n              <Select.Value />\n              <Select.Icon className={indexStyles.SelectIcon}>\n                <ChevronUpDownIcon />\n              </Select.Icon>\n            </Select.Trigger>\n            <Select.Portal>\n              <Select.Positioner className={indexStyles.Positioner} sideOffset={8}>\n                <Select.Popup className={indexStyles.Popup}>\n                  <Select.ScrollUpArrow className={indexStyles.ScrollArrow} />\n                  <Select.List className={indexStyles.List}>\n                    {years.map((year) => (\n                      <Select.Item key={year} value={year} className={indexStyles.Item}>\n                        <Select.ItemIndicator className={indexStyles.ItemIndicator}>\n                          <CheckIcon className={indexStyles.ItemIndicatorIcon} />\n                        </Select.ItemIndicator>\n                        <Select.ItemText className={indexStyles.ItemText}>{year}</Select.ItemText>\n                      </Select.Item>\n                    ))}\n                  </Select.List>\n                  <Select.ScrollDownArrow className={indexStyles.ScrollArrow} />\n                </Select.Popup>\n              </Select.Positioner>\n            </Select.Portal>\n          </Select.Root>\n        </div>\n        <Calendar.IncrementMonth className={styles.IncrementMonth}>\n          <ChevronRightIcon />\n        </Calendar.IncrementMonth>\n      </header>\n      <Calendar.DayGrid className={styles.DayGrid}>\n        <Calendar.DayGridHeader className={styles.DayGridHeader}>\n          <Calendar.DayGridHeaderRow className={styles.DayGridHeaderRow}>\n            {(day) => (\n              <Calendar.DayGridHeaderCell\n                value={day}\n                key={day.getTime()}\n                className={styles.DayGridHeaderCell}\n              />\n            )}\n          </Calendar.DayGridHeaderRow>\n        </Calendar.DayGridHeader>\n        <Calendar.DayGridBody className={styles.DayGridBody}>\n          {(week) => (\n            <Calendar.DayGridRow value={week} key={week.getTime()} className={styles.DayGridRow}>\n              {(day) => (\n                <Calendar.DayGridCell\n                  value={day}\n                  key={day.getTime()}\n                  className={styles.DayGridCell}\n                >\n                  <Calendar.DayButton className={styles.DayButton} />\n                </Calendar.DayGridCell>\n              )}\n            </Calendar.DayGridRow>\n          )}\n        </Calendar.DayGridBody>\n      </Calendar.DayGrid>\n    </React.Fragment>\n  );\n}\n\nexport default function ExampleCalendarYearMonthSelect() {\n  return (\n    <Calendar.Root className={styles.Root}>\n      <CalendarContent />\n    </Calendar.Root>\n  );\n}\n\nfunction ChevronUpDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"8\"\n      height=\"12\"\n      viewBox=\"0 0 8 12\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"1.5\"\n      {...props}\n    >\n      <path d=\"M0.5 4.5L4 1.5L7.5 4.5\" />\n      <path d=\"M0.5 7.5L4 10.5L7.5 7.5\" />\n    </svg>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\nfunction ChevronLeftIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"m15 18-6-6 6-6\" />\n    </svg>\n  );\n}\n\nfunction ChevronRightIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"m9 18 6-6-6-6\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/calendar/demos/year-month-select/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\n\nexport const DemoCalendarYearMonthSelect = createDemoWithVariants(import.meta.url, { CssModules });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/calendar/page.mdx",
    "content": "# Calendar\n\n<Subtitle>An easily stylable calendar component.</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"A high-quality, unstyled React calendar that is easy to customize.\"\n/>\n\nimport { DemoCalendarHero } from './demos/hero';\n\n<DemoCalendarHero />\n\n## Anatomy\n\nImport the component and assemble its parts:\n\n```jsx title=\"Anatomy\"\nimport { Calendar } from '@base-ui/react/calendar';\n\n<Calendar.Root>\n  <Calendar.DecrementMonth />\n  <Calendar.IncrementMonth />\n  <Calendar.DayGrid>\n    <Calendar.DayGridHeader>\n      <Calendar.DayGridHeaderRow>\n        <Calendar.DayGridHeaderCell />\n      </Calendar.DayGridHeaderRow>\n    </Calendar.DayGridHeader>\n    <Calendar.DayGridBody>\n      <Calendar.DayGridRow>\n        <Calendar.DayGridCell>\n          <Calendar.DayButton />\n        </Calendar.DayGridCell>\n      </Calendar.DayGridRow>\n    </Calendar.DayGridBody>\n  </Calendar.DayGrid>\n</Calendar.Root>;\n```\n\n## Examples\n\n### Unavailable dates\n\nYou can use the `isDateUnavailable()` prop to mark specific dates as unavailable. Unavailable dates are visually indicated and cannot be selected, but are focusable.\n\nThis example disables weekends, US holidays, and the first Monday of every month.\n\nimport { DemoCalendarUnavailableDates } from './demos/unavailable-dates';\n\n<DemoCalendarUnavailableDates />\n\n### Validation\n\nYou can use the `minDate` and `maxDate` props to limit the range of selectable dates.\n\nimport { DemoCalendarValidation } from './demos/validation';\n\n<DemoCalendarValidation />\n\n### Lazy loading\n\nYou can render custom content inside `<Calendar.DayButton>` and use `Calendar.useContext()` to\nreact to month changes — for example, to lazily load per-day data.\n\nimport { DemoCalendarLazyLoading } from './demos/lazy-loading';\n\n<DemoCalendarLazyLoading />\n\n### Display week numbers\n\nYou can use `useWeekList()` and `useDayList()` hooks to build a calendar with additional DOM elements, like week numbers.\n\nimport { DemoCalendarWeekNumber } from './demos/week-numbers';\n\n<DemoCalendarWeekNumber />\n\n### Timezone support\n\nYou can use the `timezone` prop to display data in the calendar in your preferred timezone.\n\nTimezone support is based on [@date-fns/tz](https://github.com/date-fns/tz).\n\nimport { DemoCalendarTimezone } from './demos/timezone';\n\n<DemoCalendarTimezone />\n\nThe calendar can display the date in a selected timezone.\n\nimport { DemoCalendarTimezoneDisplay } from './demos/timezone-display';\n\n<DemoCalendarTimezoneDisplay />\n\n### Month and year select\n\nUse `Calendar.useContext()` inside the calendar to read `visibleDate` and call `setVisibleDate`, enabling custom controls like the [Select](/react/components/select) component for direct month and year navigation.\n\nimport { DemoCalendarYearMonthSelect } from './demos/year-month-select';\n\n<DemoCalendarYearMonthSelect />\n\n### Animating month changes\n\nYou can use the `<Calendar.Viewport>` component to animate month transitions.\nWrapping the `<Calendar.DayGridBody>` in the `<Calendar.Viewport>` renders the previous month if a transition is in progress.\n\nimport { DemoCalendarAnimation } from './demos/animation';\n\n<DemoCalendarAnimation />\n\n#### Animating with `motion/react`\n\nYou can use external animation libraries like [Framer Motion](https://www.framer.com/motion/) to animate month transitions.\n\nimport { DemoCalendarAnimationMotion } from './demos/animation-motion';\n\n<DemoCalendarAnimationMotion />\n\n### Localization\n\nThe calendar's locale is controlled by the date library. It handles month and day names, as well as the start of the week.\nYou can use the [`<LocalizationProvider>`](/react/utils/localization-provider) to customize these settings.\n\n## API reference\n\n<Reference\n  component=\"Calendar\"\n  parts=\"Root, DayGrid, DayGridHeader, DayGridHeaderCell, DayGridBody, DayGridRow, DayGridCell, DayButton, DecrementMonth, IncrementMonth, Viewport\"\n/>\n\nexport const metadata =\n  /** @type {import('@mui/internal-docs-infra/createSitemap/types').NextMetadata} */ ({\n    keywords: [\n      'React Calendar',\n      'React Date Picker',\n      'React Date Calendar',\n      'Calendar',\n      'Date Picker',\n      'Temporal',\n      'Base UI',\n    ],\n    robots: {\n      index: false,\n    },\n    other: {\n      audience: 'private',\n    },\n  });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/checkbox/demos/hero/css-modules/index.module.css",
    "content": ".Label {\n  display: flex;\n  align-items: center;\n  gap: 0.5rem;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  font-weight: 400;\n}\n\n.Checkbox {\n  box-sizing: border-box;\n  display: flex;\n  width: 1.25rem;\n  height: 1.25rem;\n  align-items: center;\n  justify-content: center;\n  border-radius: 0.25rem;\n  outline: 0;\n  padding: 0;\n  margin: 0;\n  border: none;\n\n  &[data-unchecked] {\n    border: 1px solid var(--color-gray-300);\n    background-color: transparent;\n  }\n\n  &[data-checked] {\n    background-color: var(--color-gray-900);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: 2px;\n  }\n}\n\n.Indicator {\n  display: flex;\n  color: var(--color-gray-50);\n\n  &[data-unchecked] {\n    display: none;\n  }\n}\n\n.Icon {\n  width: 0.75rem;\n  height: 0.75rem;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/checkbox/demos/hero/css-modules/index.tsx",
    "content": "import * as React from 'react';\nimport { Checkbox } from '@base-ui/react/checkbox';\nimport styles from './index.module.css';\n\nexport default function ExampleCheckbox() {\n  return (\n    <label className={styles.Label}>\n      <Checkbox.Root defaultChecked className={styles.Checkbox}>\n        <Checkbox.Indicator className={styles.Indicator}>\n          <CheckIcon className={styles.Icon} />\n        </Checkbox.Indicator>\n      </Checkbox.Root>\n      Enable notifications\n    </label>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/checkbox/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoCheckboxBasic = createDemoWithVariants(import.meta.url, { CssModules, Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/checkbox/demos/hero/tailwind/index.tsx",
    "content": "import * as React from 'react';\nimport { Checkbox } from '@base-ui/react/checkbox';\n\nexport default function ExampleCheckbox() {\n  return (\n    <label className=\"flex items-center gap-2 text-base text-gray-900 font-normal\">\n      <Checkbox.Root\n        defaultChecked\n        className=\"flex size-5 items-center justify-center rounded-xs focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-800 data-[checked]:bg-gray-900 data-[unchecked]:border data-[unchecked]:border-gray-300\"\n      >\n        <Checkbox.Indicator className=\"flex text-gray-50 data-[unchecked]:hidden\">\n          <CheckIcon className=\"size-3\" />\n        </Checkbox.Indicator>\n      </Checkbox.Root>\n      Enable notifications\n    </label>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/checkbox/page.mdx",
    "content": "# Checkbox\n\n<Subtitle>An easily stylable checkbox component.</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"A high-quality, unstyled React checkbox component that is easy to customize.\"\n/>\n\nimport { DemoCheckboxBasic } from './demos/hero';\n\n<DemoCheckboxBasic />\n\n## Usage guidelines\n\n- **Form controls must have an accessible name**: It can be created using a `<label>` element or the `Field` component. See [Labeling a checkbox](#labeling-a-checkbox) and the [forms guide](/react/handbook/forms).\n\n## Anatomy\n\nImport the component and assemble its parts:\n\n```jsx title=\"Anatomy\"\nimport { Checkbox } from '@base-ui/react/checkbox';\n\n<Checkbox.Root>\n  <Checkbox.Indicator />\n</Checkbox.Root>;\n```\n\n## Examples\n\n### Labeling a checkbox\n\nAn enclosing `<label>` is the simplest labeling pattern:\n\n```tsx title=\"Wrapping a label around a checkbox\" {1,4}\n<label>\n  <Checkbox.Root />\n  Accept terms and conditions\n</label>\n```\n\n### Rendering as a native button\n\nBy default, `<Checkbox.Root>` renders a `<span>` element to support enclosing labels. Prefer rendering the checkbox as a native button when using sibling labels (`htmlFor`/`id`).\n\n```tsx title=\"Sibling label pattern with a native button\" \"nativeButton\" \"render\"\n<div>\n  <label htmlFor=\"notifications-checkbox\">Enable notifications</label>\n  <Checkbox.Root id=\"notifications-checkbox\" nativeButton render={<button />}>\n    <Checkbox.Indicator />\n  </Checkbox.Root>\n</div>\n```\n\nNative buttons with wrapping labels are supported by using the `render` callback to avoid invalid HTML, so the hidden input is placed outside the label:\n\n```tsx title=\"Render callback\" {3-8}\n<Checkbox.Root\n  nativeButton\n  render={(buttonProps) => (\n    <label>\n      <button {...buttonProps} />\n      Enable notifications\n    </label>\n  )}\n/>\n```\n\n### Form integration\n\nUse [Field](/react/components/field) to handle label associations and form integration:\n\n```tsx title=\"Using Checkbox in a form\" {2}\n<Form>\n  <Field.Root name=\"stayLoggedIn\">\n    <Field.Label>\n      <Checkbox.Root />\n      Stay logged in for 7 days\n    </Field.Label>\n  </Field.Root>\n</Form>\n```\n\n## API reference\n\n<Reference component=\"Checkbox\" parts=\"Root, Indicator\" />\n\nexport const metadata = {\n  keywords: [\n    'React Checkbox',\n    'Checkbox Component',\n    'Accessible Checkbox',\n    'Customizable Checkbox',\n    'Form Control Checkbox',\n    'Checkmark',\n    'Tick Box',\n    'Selection Control',\n    'Checkbox Input',\n    'Binary Input',\n    'Checked State',\n    'Indeterminate Checkbox',\n    'Tri-State Checkbox',\n    'Headless React Components',\n    'Input Indicator',\n    'Base UI',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/checkbox-group/demos/hero/css-modules/index.module.css",
    "content": ".CheckboxGroup {\n  display: flex;\n  flex-direction: column;\n  align-items: start;\n  gap: 0.25rem;\n  color: var(--color-gray-900);\n}\n\n.Caption {\n  font-weight: 700;\n}\n\n.Item {\n  display: flex;\n  align-items: center;\n  gap: 0.5rem;\n  font-weight: 400;\n}\n\n.Checkbox {\n  box-sizing: border-box;\n  display: flex;\n  width: 1.25rem;\n  height: 1.25rem;\n  align-items: center;\n  justify-content: center;\n  border-radius: 0.25rem;\n  outline: 0;\n  padding: 0;\n  margin: 0;\n  border: none;\n\n  &[data-unchecked] {\n    border: 1px solid var(--color-gray-300);\n    background-color: transparent;\n  }\n\n  &[data-checked] {\n    background-color: var(--color-gray-900);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: 2px;\n  }\n}\n\n.Indicator {\n  display: flex;\n  color: var(--color-gray-50);\n\n  &[data-unchecked] {\n    display: none;\n  }\n}\n\n.Icon {\n  width: 0.75rem;\n  height: 0.75rem;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/checkbox-group/demos/hero/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Checkbox } from '@base-ui/react/checkbox';\nimport { CheckboxGroup } from '@base-ui/react/checkbox-group';\nimport styles from './index.module.css';\n\nexport default function ExampleCheckboxGroup() {\n  const id = React.useId();\n  return (\n    <CheckboxGroup\n      aria-labelledby={id}\n      defaultValue={['fuji-apple']}\n      className={styles.CheckboxGroup}\n    >\n      <div className={styles.Caption} id={id}>\n        Apples\n      </div>\n\n      <label className={styles.Item}>\n        <Checkbox.Root name=\"apple\" value=\"fuji-apple\" className={styles.Checkbox}>\n          <Checkbox.Indicator className={styles.Indicator}>\n            <CheckIcon className={styles.Icon} />\n          </Checkbox.Indicator>\n        </Checkbox.Root>\n        Fuji\n      </label>\n\n      <label className={styles.Item}>\n        <Checkbox.Root name=\"apple\" value=\"gala-apple\" className={styles.Checkbox}>\n          <Checkbox.Indicator className={styles.Indicator}>\n            <CheckIcon className={styles.Icon} />\n          </Checkbox.Indicator>\n        </Checkbox.Root>\n        Gala\n      </label>\n\n      <label className={styles.Item}>\n        <Checkbox.Root name=\"apple\" value=\"granny-smith-apple\" className={styles.Checkbox}>\n          <Checkbox.Indicator className={styles.Indicator}>\n            <CheckIcon className={styles.Icon} />\n          </Checkbox.Indicator>\n        </Checkbox.Root>\n        Granny Smith\n      </label>\n    </CheckboxGroup>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/checkbox-group/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoCheckboxGroupHero = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/checkbox-group/demos/hero/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Checkbox } from '@base-ui/react/checkbox';\nimport { CheckboxGroup } from '@base-ui/react/checkbox-group';\n\nexport default function ExampleCheckboxGroup() {\n  const id = React.useId();\n  return (\n    <CheckboxGroup\n      aria-labelledby={id}\n      defaultValue={['fuji-apple']}\n      className=\"flex flex-col items-start gap-1 text-gray-900\"\n    >\n      <div className=\"font-bold\" id={id}>\n        Apples\n      </div>\n\n      <label className=\"flex items-center gap-2 font-normal\">\n        <Checkbox.Root\n          name=\"apple\"\n          value=\"fuji-apple\"\n          className=\"flex size-5 items-center justify-center rounded-xs focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-800 data-[checked]:bg-gray-900 data-[unchecked]:border data-[unchecked]:border-gray-300\"\n        >\n          <Checkbox.Indicator className=\"flex text-gray-50 data-[unchecked]:hidden\">\n            <CheckIcon className=\"size-3\" />\n          </Checkbox.Indicator>\n        </Checkbox.Root>\n        Fuji\n      </label>\n\n      <label className=\"flex items-center gap-2 font-normal\">\n        <Checkbox.Root\n          name=\"apple\"\n          value=\"gala-apple\"\n          className=\"flex size-5 items-center justify-center rounded-xs focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-800 data-[checked]:bg-gray-900 data-[unchecked]:border data-[unchecked]:border-gray-300\"\n        >\n          <Checkbox.Indicator className=\"flex text-gray-50 data-[unchecked]:hidden\">\n            <CheckIcon className=\"size-3\" />\n          </Checkbox.Indicator>\n        </Checkbox.Root>\n        Gala\n      </label>\n\n      <label className=\"flex items-center gap-2 font-normal\">\n        <Checkbox.Root\n          name=\"apple\"\n          value=\"granny-smith-apple\"\n          className=\"flex size-5 items-center justify-center rounded-xs focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-800 data-[checked]:bg-gray-900 data-[unchecked]:border data-[unchecked]:border-gray-300\"\n        >\n          <Checkbox.Indicator className=\"flex text-gray-50 data-[unchecked]:hidden\">\n            <CheckIcon className=\"size-3\" />\n          </Checkbox.Indicator>\n        </Checkbox.Root>\n        Granny Smith\n      </label>\n    </CheckboxGroup>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/checkbox-group/demos/nested/css-modules/index.module.css",
    "content": ".CheckboxGroup {\n  display: flex;\n  flex-direction: column;\n  align-items: start;\n  gap: 0.25rem;\n  color: var(--color-gray-900);\n}\n\n.Caption {\n  font-weight: 700;\n}\n\n.Item {\n  display: flex;\n  align-items: center;\n  gap: 0.5rem;\n  font-weight: 400;\n}\n\n.Checkbox {\n  box-sizing: border-box;\n  display: flex;\n  width: 1.25rem;\n  height: 1.25rem;\n  align-items: center;\n  justify-content: center;\n  border-radius: 0.25rem;\n  outline: 0;\n  padding: 0;\n  margin: 0;\n  border: none;\n\n  &[data-unchecked] {\n    border: 1px solid var(--color-gray-300);\n    background-color: transparent;\n  }\n\n  &[data-checked] {\n    background-color: var(--color-gray-900);\n  }\n\n  &[data-indeterminate] {\n    border: 1px solid var(--color-gray-300);\n    background-color: canvas;\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: 2px;\n  }\n}\n\n.Indicator {\n  display: flex;\n  color: var(--color-gray-50);\n\n  &[data-unchecked] {\n    display: none;\n  }\n\n  &[data-indeterminate] {\n    color: var(--color-gray-900);\n  }\n}\n\n.Icon {\n  width: 0.75rem;\n  height: 0.75rem;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/checkbox-group/demos/nested/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Checkbox } from '@base-ui/react/checkbox';\nimport { CheckboxGroup } from '@base-ui/react/checkbox-group';\nimport styles from './index.module.css';\n\nconst mainPermissions = ['view-dashboard', 'manage-users', 'access-reports'];\nconst userManagementPermissions = ['create-user', 'edit-user', 'delete-user', 'assign-roles'];\n\nexport default function PermissionsForm() {\n  const id = React.useId();\n  const [mainValue, setMainValue] = React.useState<string[]>([]);\n  const [managementValue, setManagementValue] = React.useState<string[]>([]);\n\n  return (\n    <CheckboxGroup\n      aria-labelledby={id}\n      value={mainValue}\n      onValueChange={(value) => {\n        if (value.includes('manage-users')) {\n          setManagementValue(userManagementPermissions);\n        } else if (managementValue.length === userManagementPermissions.length) {\n          setManagementValue([]);\n        }\n        setMainValue(value);\n      }}\n      allValues={mainPermissions}\n      className={styles.CheckboxGroup}\n      style={{ marginLeft: '1rem' }}\n    >\n      <label className={styles.Item} id={id} style={{ marginLeft: '-1rem' }}>\n        <Checkbox.Root\n          className={styles.Checkbox}\n          parent\n          indeterminate={\n            managementValue.length > 0 &&\n            managementValue.length !== userManagementPermissions.length\n          }\n        >\n          <Checkbox.Indicator\n            className={styles.Indicator}\n            render={(props, state) => (\n              <span {...props}>\n                {state.indeterminate ? (\n                  <HorizontalRuleIcon className={styles.Icon} />\n                ) : (\n                  <CheckIcon className={styles.Icon} />\n                )}\n              </span>\n            )}\n          />\n        </Checkbox.Root>\n        User Permissions\n      </label>\n\n      <label className={styles.Item}>\n        <Checkbox.Root value=\"view-dashboard\" className={styles.Checkbox}>\n          <Checkbox.Indicator className={styles.Indicator}>\n            <CheckIcon className={styles.Icon} />\n          </Checkbox.Indicator>\n        </Checkbox.Root>\n        View Dashboard\n      </label>\n\n      <label className={styles.Item}>\n        <Checkbox.Root value=\"access-reports\" className={styles.Checkbox}>\n          <Checkbox.Indicator className={styles.Indicator}>\n            <CheckIcon className={styles.Icon} />\n          </Checkbox.Indicator>\n        </Checkbox.Root>\n        Access Reports\n      </label>\n\n      <CheckboxGroup\n        aria-labelledby=\"manage-users-caption\"\n        className={styles.CheckboxGroup}\n        value={managementValue}\n        onValueChange={(value) => {\n          if (value.length === userManagementPermissions.length) {\n            setMainValue((prev) => Array.from(new Set([...prev, 'manage-users'])));\n          } else {\n            setMainValue((prev) => prev.filter((v) => v !== 'manage-users'));\n          }\n          setManagementValue(value);\n        }}\n        allValues={userManagementPermissions}\n        style={{ marginLeft: '1rem' }}\n      >\n        <label className={styles.Item} id=\"manage-users-caption\" style={{ marginLeft: '-1rem' }}>\n          <Checkbox.Root className={styles.Checkbox} parent>\n            <Checkbox.Indicator\n              className={styles.Indicator}\n              render={(props, state) => (\n                <span {...props}>\n                  {state.indeterminate ? (\n                    <HorizontalRuleIcon className={styles.Icon} />\n                  ) : (\n                    <CheckIcon className={styles.Icon} />\n                  )}\n                </span>\n              )}\n            />\n          </Checkbox.Root>\n          Manage Users\n        </label>\n\n        <label className={styles.Item}>\n          <Checkbox.Root value=\"create-user\" className={styles.Checkbox}>\n            <Checkbox.Indicator className={styles.Indicator}>\n              <CheckIcon className={styles.Icon} />\n            </Checkbox.Indicator>\n          </Checkbox.Root>\n          Create User\n        </label>\n\n        <label className={styles.Item}>\n          <Checkbox.Root value=\"edit-user\" className={styles.Checkbox}>\n            <Checkbox.Indicator className={styles.Indicator}>\n              <CheckIcon className={styles.Icon} />\n            </Checkbox.Indicator>\n          </Checkbox.Root>\n          Edit User\n        </label>\n\n        <label className={styles.Item}>\n          <Checkbox.Root value=\"delete-user\" className={styles.Checkbox}>\n            <Checkbox.Indicator className={styles.Indicator}>\n              <CheckIcon className={styles.Icon} />\n            </Checkbox.Indicator>\n          </Checkbox.Root>\n          Delete User\n        </label>\n\n        <label className={styles.Item}>\n          <Checkbox.Root value=\"assign-roles\" className={styles.Checkbox}>\n            <Checkbox.Indicator className={styles.Indicator}>\n              <CheckIcon className={styles.Icon} />\n            </Checkbox.Indicator>\n          </Checkbox.Root>\n          Assign Roles\n        </label>\n      </CheckboxGroup>\n    </CheckboxGroup>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\nfunction HorizontalRuleIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"10\"\n      height=\"10\"\n      viewBox=\"0 0 24 24\"\n      fill=\"currentcolor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <line\n        x1=\"3\"\n        y1=\"12\"\n        x2=\"21\"\n        y2=\"12\"\n        stroke=\"currentColor\"\n        strokeWidth={3}\n        strokeLinecap=\"round\"\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/checkbox-group/demos/nested/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\n\nexport const DemoCheckboxGroupNested = createDemoWithVariants(import.meta.url, { CssModules });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/checkbox-group/demos/parent/css-modules/index.module.css",
    "content": ".CheckboxGroup {\n  display: flex;\n  flex-direction: column;\n  align-items: start;\n  gap: 0.25rem;\n  color: var(--color-gray-900);\n}\n\n.Caption {\n  font-weight: 700;\n}\n\n.Item {\n  display: flex;\n  align-items: center;\n  gap: 0.5rem;\n  font-weight: 400;\n}\n\n.Checkbox {\n  box-sizing: border-box;\n  display: flex;\n  width: 1.25rem;\n  height: 1.25rem;\n  align-items: center;\n  justify-content: center;\n  border-radius: 0.25rem;\n  outline: 0;\n  padding: 0;\n  margin: 0;\n  border: none;\n\n  &[data-unchecked] {\n    border: 1px solid var(--color-gray-300);\n    background-color: transparent;\n  }\n\n  &[data-checked] {\n    background-color: var(--color-gray-900);\n  }\n\n  &[data-indeterminate] {\n    border: 1px solid var(--color-gray-300);\n    background-color: canvas;\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: 2px;\n  }\n}\n\n.Indicator {\n  display: flex;\n  color: var(--color-gray-50);\n\n  &[data-unchecked] {\n    display: none;\n  }\n\n  &[data-indeterminate] {\n    color: var(--color-gray-900);\n  }\n}\n\n.Icon {\n  width: 0.75rem;\n  height: 0.75rem;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/checkbox-group/demos/parent/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Checkbox } from '@base-ui/react/checkbox';\nimport { CheckboxGroup } from '@base-ui/react/checkbox-group';\nimport styles from './index.module.css';\n\nconst fruits = ['fuji-apple', 'gala-apple', 'granny-smith-apple'];\n\nexport default function ExampleCheckboxGroup() {\n  const id = React.useId();\n  const [value, setValue] = React.useState<string[]>([]);\n\n  return (\n    <CheckboxGroup\n      aria-labelledby={id}\n      value={value}\n      onValueChange={setValue}\n      allValues={fruits}\n      className={styles.CheckboxGroup}\n      style={{ marginLeft: '1rem' }}\n    >\n      <label className={styles.Item} id={id} style={{ marginLeft: '-1rem' }}>\n        <Checkbox.Root className={styles.Checkbox} parent>\n          <Checkbox.Indicator\n            className={styles.Indicator}\n            render={(props, state) => (\n              <span {...props}>\n                {state.indeterminate ? (\n                  <HorizontalRuleIcon className={styles.Icon} />\n                ) : (\n                  <CheckIcon className={styles.Icon} />\n                )}\n              </span>\n            )}\n          />\n        </Checkbox.Root>\n        Apples\n      </label>\n\n      <label className={styles.Item}>\n        <Checkbox.Root value=\"fuji-apple\" className={styles.Checkbox}>\n          <Checkbox.Indicator className={styles.Indicator}>\n            <CheckIcon className={styles.Icon} />\n          </Checkbox.Indicator>\n        </Checkbox.Root>\n        Fuji\n      </label>\n\n      <label className={styles.Item}>\n        <Checkbox.Root value=\"gala-apple\" className={styles.Checkbox}>\n          <Checkbox.Indicator className={styles.Indicator}>\n            <CheckIcon className={styles.Icon} />\n          </Checkbox.Indicator>\n        </Checkbox.Root>\n        Gala\n      </label>\n\n      <label className={styles.Item}>\n        <Checkbox.Root value=\"granny-smith-apple\" className={styles.Checkbox}>\n          <Checkbox.Indicator className={styles.Indicator}>\n            <CheckIcon className={styles.Icon} />\n          </Checkbox.Indicator>\n        </Checkbox.Root>\n        Granny Smith\n      </label>\n    </CheckboxGroup>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\nfunction HorizontalRuleIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"10\"\n      height=\"10\"\n      viewBox=\"0 0 24 24\"\n      fill=\"currentcolor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <line\n        x1=\"3\"\n        y1=\"12\"\n        x2=\"21\"\n        y2=\"12\"\n        stroke=\"currentColor\"\n        strokeWidth={3}\n        strokeLinecap=\"round\"\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/checkbox-group/demos/parent/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\n\nexport const DemoCheckboxGroupParent = createDemoWithVariants(import.meta.url, { CssModules });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/checkbox-group/page.mdx",
    "content": "# Checkbox Group\n\n<Subtitle>Provides shared state to a series of checkboxes.</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"A high-quality, unstyled React checkbox group component that provides a shared state for a series of checkboxes.\"\n/>\n\nimport { DemoCheckboxGroupHero } from './demos/hero';\n\n<DemoCheckboxGroupHero />\n\n## Usage guidelines\n\n- **Form controls must have an accessible name**: It can be created using `<label>` elements, or the `Field` and `Fieldset` components. See [Labeling a checkbox group](#labeling-a-checkbox-group) and the [forms guide](/react/handbook/forms).\n\n## Anatomy\n\nCheckbox Group is composed together with [Checkbox](/react/components/checkbox). Import the components and place them together:\n\n```jsx title=\"Anatomy\"\nimport { Checkbox } from '@base-ui/react/checkbox';\nimport { CheckboxGroup } from '@base-ui/react/checkbox-group';\n\n<CheckboxGroup>\n  <Checkbox.Root />\n</CheckboxGroup>;\n```\n\n## Examples\n\n### Labeling a checkbox group\n\nLabel the group with `aria-labelledby` and a sibling label element:\n\n```tsx title=\"Using aria-labelledby to label a checkbox group\"\n<div id=\"protocols-label\">Allowed network protocols</div>\n<CheckboxGroup aria-labelledby=\"protocols-label\">{/* ... */}</CheckboxGroup>\n```\n\nAn enclosing `<label>` is the simplest labeling pattern for each checkbox:\n\n```tsx title=\"Using an enclosing label to label a checkbox\" {1,4}\n<label>\n  <Checkbox.Root value=\"http\" />\n  HTTP\n</label>\n```\n\n### Rendering as a native button\n\nBy default, `<Checkbox.Root>` renders a `<span>` element to support enclosing labels. Prefer rendering each checkbox as a native button when using sibling labels (`htmlFor`/`id`).\n\n```tsx title=\"Sibling label pattern with a native button\" \"nativeButton\" \"render\"\n<div id=\"protocols-label\">Allowed network protocols</div>\n<CheckboxGroup aria-labelledby=\"protocols-label\">\n  <div>\n    <label htmlFor=\"protocol-http\">HTTP</label>\n    <Checkbox.Root id=\"protocol-http\" value=\"http\" nativeButton render={<button />}>\n      <Checkbox.Indicator />\n    </Checkbox.Root>\n  </div>\n</CheckboxGroup>\n```\n\nNative buttons with wrapping labels are supported by using the `render` callback to avoid invalid HTML, so the hidden input is placed outside the label:\n\n```tsx title=\"Render callback\" {6-11}\n<div id=\"protocols-label\">Allowed network protocols</div>\n<CheckboxGroup aria-labelledby=\"protocols-label\">\n  <Checkbox.Root\n    value=\"http\"\n    nativeButton\n    render={(buttonProps) => (\n      <label>\n        <button {...buttonProps} />\n        HTTP\n      </label>\n    )}\n  />\n</CheckboxGroup>\n```\n\n### Form integration\n\nUse [Field](/react/components/field) and [Fieldset](/react/components/fieldset) for group labeling and form integration:\n\n```tsx title=\"Using Checkbox Group in a form\" {2}\n<Form>\n  <Field.Root name=\"allowedNetworkProtocols\">\n    <Fieldset.Root render={<CheckboxGroup />}>\n      <Fieldset.Legend>Allowed network protocols</Fieldset.Legend>\n      <Field.Item>\n        <Field.Label>\n          <Checkbox.Root value=\"http\" />\n          HTTP\n        </Field.Label>\n      </Field.Item>\n      <Field.Item>\n        <Field.Label>\n          <Checkbox.Root value=\"https\" />\n          HTTPS\n        </Field.Label>\n      </Field.Item>\n      <Field.Item>\n        <Field.Label>\n          <Checkbox.Root value=\"ssh\" />\n          SSH\n        </Field.Label>\n      </Field.Item>\n    </Fieldset.Root>\n  </Field.Root>\n</Form>\n```\n\n### Parent checkbox\n\nA checkbox that controls other checkboxes within a `<CheckboxGroup>` can be created:\n\n1. Make `<CheckboxGroup>` a controlled component\n2. Pass an array of all the child checkbox values to the `allValues` prop on the `<CheckboxGroup>` component\n3. Add the `parent` boolean prop to the parent `<Checkbox.Root>`\n\nThe group controls the parent checkbox's [indeterminate](/react/components/checkbox#CheckboxRoot-indeterminate) state when some, but not all, child checkboxes are checked.\n\nimport { DemoCheckboxGroupParent } from './demos/parent';\n\n<DemoCheckboxGroupParent />\n\n### Nested parent checkbox\n\nimport { DemoCheckboxGroupNested } from './demos/nested';\n\n<DemoCheckboxGroupNested compact />\n\n## API reference\n\n<Reference component=\"CheckboxGroup\" />\n\nexport const metadata = {\n  keywords: [\n    'React Checkbox Group',\n    'Checkbox Group Component',\n    'Grouped Checkboxes',\n    'Multiple Selection',\n    'Checkbox List',\n    'Multi-Select Checkboxes',\n    'Parent Child Checkbox',\n    'Accessible Checkbox Group',\n    'Form Fieldset Control',\n    'Headless React Components',\n    'Base UI',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/collapsible/demos/hero/css-modules/index.module.css",
    "content": ".Collapsible {\n  display: flex;\n  width: 14rem;\n  min-height: 9rem;\n  flex-direction: column;\n  justify-content: center;\n  color: var(--color-gray-900);\n}\n\n.Icon {\n  width: 0.75rem;\n  height: 0.75rem;\n  transition: transform 150ms ease-out;\n}\n\n.Trigger {\n  display: flex;\n  align-items: center;\n  gap: 0.5rem;\n  margin: 0;\n  border: 0;\n  outline: 0;\n  padding: 0.25rem 0.5rem;\n  border-radius: 0.25rem;\n  background-color: var(--color-gray-100);\n  color: var(--color-gray-900);\n  font-family: inherit;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 400;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-200);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-200);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n  }\n\n  &[data-panel-open] .Icon {\n    transform: rotate(90deg);\n  }\n}\n\n.Panel {\n  display: flex;\n  height: var(--collapsible-panel-height);\n  flex-direction: column;\n  justify-content: end;\n  overflow: hidden;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  transition: all 150ms ease-out;\n\n  &[hidden]:not([hidden='until-found']) {\n    display: none;\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    height: 0;\n  }\n}\n\n.Content {\n  display: flex;\n  flex-direction: column;\n  gap: 0.5rem;\n  margin-top: 0.25rem;\n  padding: 0.5rem 0 0.5rem 1.75rem;\n  border-radius: 0.25rem;\n  background-color: var(--color-gray-100);\n  cursor: text;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/collapsible/demos/hero/css-modules/index.tsx",
    "content": "import * as React from 'react';\nimport { Collapsible } from '@base-ui/react/collapsible';\nimport styles from './index.module.css';\n\nexport default function ExampleCollapsible() {\n  return (\n    <Collapsible.Root className={styles.Collapsible}>\n      <Collapsible.Trigger className={styles.Trigger}>\n        <ChevronIcon className={styles.Icon} />\n        Recovery keys\n      </Collapsible.Trigger>\n      <Collapsible.Panel className={styles.Panel}>\n        <div className={styles.Content}>\n          <div>alien-bean-pasta</div>\n          <div>wild-irish-burrito</div>\n          <div>horse-battery-staple</div>\n        </div>\n      </Collapsible.Panel>\n    </Collapsible.Root>\n  );\n}\n\nexport function ChevronIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M3.5 9L7.5 5L3.5 1\" stroke=\"currentcolor\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/collapsible/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoCollapsibleHero = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/collapsible/demos/hero/tailwind/index.tsx",
    "content": "import * as React from 'react';\nimport { Collapsible } from '@base-ui/react/collapsible';\n\nexport default function ExampleCollapsible() {\n  return (\n    <Collapsible.Root className=\"flex min-h-36 w-56 flex-col justify-center text-gray-900\">\n      <Collapsible.Trigger className=\"group flex items-center gap-2 rounded-xs bg-gray-100 px-2 py-1 text-sm font-normal hover:bg-gray-200 focus-visible:outline-2 focus-visible:outline-blue-800 active:bg-gray-200\">\n        <ChevronIcon className=\"size-3 transition-all ease-out group-data-[panel-open]:rotate-90\" />\n        Recovery keys\n      </Collapsible.Trigger>\n      <Collapsible.Panel className=\"flex [&[hidden]:not([hidden='until-found'])]:hidden h-[var(--collapsible-panel-height)] flex-col justify-end overflow-hidden text-sm transition-all ease-out data-[ending-style]:h-0 data-[starting-style]:h-0 duration-150\">\n        <div className=\"mt-1 flex cursor-text flex-col gap-2 rounded-xs bg-gray-100 py-2 pl-7\">\n          <div>alien-bean-pasta</div>\n          <div>wild-irish-burrito</div>\n          <div>horse-battery-staple</div>\n        </div>\n      </Collapsible.Panel>\n    </Collapsible.Root>\n  );\n}\n\nexport function ChevronIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M3.5 9L7.5 5L3.5 1\" stroke=\"currentcolor\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/collapsible/page.mdx",
    "content": "# Collapsible\n\n<Subtitle>A collapsible panel controlled by a button.</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"A high-quality, unstyled React collapsible component that displays a panel controlled by a button.\"\n/>\n\nimport { DemoCollapsibleHero } from './demos/hero';\n\n<DemoCollapsibleHero />\n\n## Anatomy\n\nImport the component and assemble its parts:\n\n```jsx title=\"Anatomy\"\nimport { Collapsible } from '@base-ui/react/collapsible';\n\n<Collapsible.Root>\n  <Collapsible.Trigger />\n  <Collapsible.Panel />\n</Collapsible.Root>;\n```\n\n## API reference\n\n<Reference component=\"Collapsible\" parts=\"Root, Trigger, Panel\" />\n\nexport const metadata = {\n  keywords: [\n    'React Collapsible',\n    'Collapsible Component',\n    'Expandable Panel',\n    'Toggle Panel Button',\n    'Disclosure Widget',\n    'Show/Hide Content',\n    'Accordion Item',\n    'Accessible Disclosure',\n    'Headless React Components',\n    'Collapsible Trigger Panel',\n    'Base UI',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/combobox/demos/async-multiple/css-modules/index.module.css",
    "content": ".Container {\n  display: flex;\n  flex-direction: column;\n  gap: 0.25rem;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  color: var(--color-gray-900);\n}\n\n.Label {\n  display: inline-flex;\n  color: inherit;\n  font-weight: 700;\n}\n\n.InputGroup {\n  box-sizing: border-box;\n  position: relative;\n  display: flex;\n  flex-wrap: wrap;\n  align-items: center;\n  gap: 0.25rem;\n  padding: 0.25rem 0.375rem;\n  min-height: 2.5rem;\n  width: 16rem;\n  cursor: text;\n\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: canvas;\n\n  &:focus-within {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n\n  @media (min-width: 500px) {\n    width: 20rem;\n  }\n}\n\n.Chips {\n  display: flex;\n  flex-wrap: wrap;\n  align-items: center;\n  gap: 0.25rem;\n  width: 100%;\n}\n\n.Chip {\n  display: flex;\n  align-items: center;\n  gap: 0.25rem;\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-100);\n  color: var(--color-gray-900);\n  padding: 0.25rem 0.25rem 0.25rem 0.5rem;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  outline: 0;\n  cursor: default;\n\n  &:focus-within {\n    background-color: var(--color-blue);\n    color: var(--color-gray-50);\n  }\n\n  @media (hover: hover) {\n    &[data-highlighted] {\n      background-color: var(--color-blue);\n      color: var(--color-gray-50);\n    }\n  }\n}\n\n.ChipRemove {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  padding: 0.2rem;\n  border: none;\n  background: none;\n  color: inherit;\n  border-radius: 0.375rem;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-200);\n    }\n  }\n}\n\n.Input {\n  box-sizing: border-box;\n  flex: 1;\n  min-width: 6rem;\n  height: 2rem;\n  padding: 0;\n  border: none;\n  border-radius: 0.375rem;\n  padding-left: 0.5rem;\n  background: none;\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  color: var(--color-gray-900);\n\n  &:focus {\n    outline: none;\n  }\n}\n\n.Positioner {\n  outline: 0;\n}\n\n.Popup {\n  box-sizing: border-box;\n  padding-block: 0.5rem;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  width: var(--anchor-width);\n  max-height: min(var(--available-height), 23rem);\n  max-width: var(--available-width);\n  overflow-y: auto;\n  scroll-padding-block: 0.5rem;\n  overscroll-behavior: contain;\n  transition:\n    opacity 0.1s,\n    transform 0.1s;\n  transform-origin: var(--transform-origin);\n\n  &[data-starting-style] {\n    opacity: 0;\n    transform: scale(0.95);\n  }\n\n  &[data-ending-style] {\n    transition: none;\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Status {\n  display: flex;\n  align-items: center;\n  gap: 0.5rem;\n  padding-block: 0.25rem;\n  padding-left: 1rem;\n  padding-right: 1.25rem;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  color: var(--color-gray-600);\n}\n\n.Status:empty {\n  display: none;\n}\n\n.Spinner {\n  box-sizing: border-box;\n  width: 0.75rem;\n  height: 0.75rem;\n  border-radius: 10rem;\n  border: 1px solid currentColor;\n  border-right-color: transparent;\n  animation: comboboxSpinner 0.75s linear infinite;\n}\n\n.Spinner:dir(rtl) {\n  border-right-color: currentColor;\n  border-left-color: transparent;\n}\n\n@keyframes comboboxSpinner {\n  100% {\n    transform: rotate(360deg);\n  }\n}\n\n.Empty:not(:empty) {\n  box-sizing: border-box;\n  font-size: 0.875rem;\n  line-height: 1rem;\n  color: var(--color-gray-600);\n  padding: 0.5rem 1rem;\n}\n\n.Item {\n  box-sizing: border-box;\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  padding-block: 0.5rem;\n  padding-inline: 1rem;\n  padding-right: 1.25rem;\n  display: grid;\n  gap: 0.5rem;\n  align-items: flex-start;\n  grid-template-columns: 0.75rem 1fr;\n  font-size: 1rem;\n  line-height: 1.2rem;\n\n  @media (hover: hover) {\n    &[data-highlighted] {\n      z-index: 0;\n      position: relative;\n      color: var(--color-gray-900);\n    }\n\n    &[data-highlighted]::before {\n      content: '';\n      z-index: -1;\n      position: absolute;\n      inset-block: 0;\n      inset-inline: 0.5rem;\n      border-radius: 0.25rem;\n      background-color: var(--color-gray-100);\n    }\n  }\n}\n\n.ItemIndicator {\n  grid-column-start: 1;\n  margin-top: 0.25rem;\n}\n\n.ItemIndicatorIcon {\n  display: block;\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.ItemText {\n  grid-column-start: 2;\n  display: flex;\n  flex-direction: column;\n  gap: 0.25rem;\n}\n\n.ItemTitle {\n  font-size: 0.95rem;\n  font-weight: 700;\n}\n\n.ItemSubtitle {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 0.5rem;\n  font-size: 0.8125rem;\n  color: var(--color-gray-600);\n}\n\n.ItemUsername {\n  opacity: 0.8;\n}\n\n.ItemEmail {\n  font-size: 0.75rem;\n  color: var(--color-gray-500);\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/combobox/demos/async-multiple/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Combobox } from '@base-ui/react/combobox';\nimport styles from './index.module.css';\n\nexport default function ExampleAsyncMultipleCombobox() {\n  const id = React.useId();\n\n  const [searchResults, setSearchResults] = React.useState<DirectoryUser[]>([]);\n  const [selectedValues, setSelectedValues] = React.useState<DirectoryUser[]>([]);\n  const [searchValue, setSearchValue] = React.useState('');\n  const [error, setError] = React.useState<string | null>(null);\n  const [blockStartStatus, setBlockStartStatus] = React.useState(false);\n\n  const [isPending, startTransition] = React.useTransition();\n\n  const { contains } = Combobox.useFilter();\n\n  const abortControllerRef = React.useRef<AbortController | null>(null);\n  const selectedValuesRef = React.useRef<DirectoryUser[]>([]);\n\n  const trimmedSearchValue = searchValue.trim();\n\n  const items = React.useMemo(() => {\n    if (selectedValues.length === 0) {\n      return searchResults;\n    }\n\n    const merged = [...searchResults];\n\n    selectedValues.forEach((user) => {\n      if (!searchResults.some((result) => result.id === user.id)) {\n        merged.push(user);\n      }\n    });\n\n    return merged;\n  }, [searchResults, selectedValues]);\n\n  function getStatus() {\n    if (isPending) {\n      return (\n        <React.Fragment>\n          <span className={styles.Spinner} aria-hidden />\n          Searching…\n        </React.Fragment>\n      );\n    }\n\n    if (error) {\n      return error;\n    }\n\n    if (trimmedSearchValue === '' && !blockStartStatus) {\n      return selectedValues.length > 0 ? null : 'Start typing to search people…';\n    }\n\n    if (searchResults.length === 0 && !blockStartStatus) {\n      return `No matches for \"${trimmedSearchValue}\".`;\n    }\n\n    return null;\n  }\n\n  function getEmptyMessage() {\n    if (trimmedSearchValue === '' || isPending || searchResults.length > 0 || error) {\n      return null;\n    }\n\n    return 'Try a different search term.';\n  }\n\n  return (\n    <Combobox.Root\n      items={items}\n      itemToStringLabel={(user: DirectoryUser) => user.name}\n      multiple\n      filter={null}\n      onOpenChangeComplete={(open) => {\n        if (!open) {\n          setSearchResults(selectedValuesRef.current);\n          setBlockStartStatus(false);\n        }\n      }}\n      onValueChange={(nextSelectedValues) => {\n        selectedValuesRef.current = nextSelectedValues;\n        setSelectedValues(nextSelectedValues);\n        setSearchValue('');\n        setError(null);\n\n        if (nextSelectedValues.length === 0) {\n          setSearchResults([]);\n          setBlockStartStatus(false);\n        } else {\n          setBlockStartStatus(true);\n        }\n      }}\n      onInputValueChange={(nextSearchValue, { reason }) => {\n        setSearchValue(nextSearchValue);\n\n        const controller = new AbortController();\n        abortControllerRef.current?.abort();\n        abortControllerRef.current = controller;\n\n        if (nextSearchValue === '') {\n          setSearchResults(selectedValuesRef.current);\n          setError(null);\n          setBlockStartStatus(false);\n          return;\n        }\n\n        if (reason === 'item-press') {\n          return;\n        }\n\n        startTransition(async () => {\n          setError(null);\n\n          const result = await searchUsers(nextSearchValue, contains);\n\n          if (controller.signal.aborted) {\n            return;\n          }\n\n          startTransition(() => {\n            setSearchResults(result.users);\n            setError(result.error);\n          });\n        });\n      }}\n    >\n      <div className={styles.Container}>\n        <label className={styles.Label} htmlFor={id}>\n          Assign reviewers\n        </label>\n        <Combobox.InputGroup className={styles.InputGroup}>\n          <Combobox.Chips className={styles.Chips}>\n            <Combobox.Value>\n              {(value: DirectoryUser[]) => (\n                <React.Fragment>\n                  {value.map((user) => (\n                    <Combobox.Chip key={user.id} className={styles.Chip} aria-label={user.name}>\n                      {user.name}\n                      <Combobox.ChipRemove className={styles.ChipRemove} aria-label=\"Remove\">\n                        <XIcon />\n                      </Combobox.ChipRemove>\n                    </Combobox.Chip>\n                  ))}\n                  <Combobox.Input\n                    id={id}\n                    placeholder={value.length > 0 ? '' : 'e.g. Michael'}\n                    className={styles.Input}\n                  />\n                </React.Fragment>\n              )}\n            </Combobox.Value>\n          </Combobox.Chips>\n        </Combobox.InputGroup>\n      </div>\n\n      <Combobox.Portal>\n        <Combobox.Positioner className={styles.Positioner} sideOffset={4}>\n          <Combobox.Popup className={styles.Popup} aria-busy={isPending || undefined}>\n            <Combobox.Status className={styles.Status}>{getStatus()}</Combobox.Status>\n            <Combobox.Empty className={styles.Empty}>{getEmptyMessage()}</Combobox.Empty>\n            <Combobox.List>\n              {(user: DirectoryUser) => (\n                <Combobox.Item key={user.id} className={styles.Item} value={user}>\n                  <Combobox.ItemIndicator className={styles.ItemIndicator}>\n                    <CheckIcon className={styles.ItemIndicatorIcon} />\n                  </Combobox.ItemIndicator>\n                  <div className={styles.ItemText}>\n                    <div className={styles.ItemTitle}>{user.name}</div>\n                    <div className={styles.ItemSubtitle}>\n                      <span className={styles.ItemUsername}>@{user.username}</span>\n                      <span>{user.title}</span>\n                    </div>\n                    <div className={styles.ItemEmail}>{user.email}</div>\n                  </div>\n                </Combobox.Item>\n              )}\n            </Combobox.List>\n          </Combobox.Popup>\n        </Combobox.Positioner>\n      </Combobox.Portal>\n    </Combobox.Root>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\nfunction XIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width={16}\n      height={16}\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      aria-hidden\n      {...props}\n    >\n      <path d=\"M18 6 6 18\" />\n      <path d=\"m6 6 12 12\" />\n    </svg>\n  );\n}\n\ninterface DirectoryUser {\n  id: string;\n  name: string;\n  username: string;\n  email: string;\n  title: string;\n}\n\nasync function searchUsers(\n  query: string,\n  filter: (item: string, query: string) => boolean,\n): Promise<{ users: DirectoryUser[]; error: string | null }> {\n  // Simulate network delay\n  await new Promise((resolve) => {\n    setTimeout(resolve, Math.random() * 500 + 100);\n  });\n\n  // Simulate occasional network errors (1% chance)\n  if (Math.random() < 0.01 || query === 'will_error') {\n    return {\n      users: [],\n      error: 'Failed to fetch people. Please try again.',\n    };\n  }\n\n  const users = allUsers.filter((user) => {\n    return (\n      filter(user.name, query) ||\n      filter(user.username, query) ||\n      filter(user.email, query) ||\n      filter(user.title, query)\n    );\n  });\n\n  return {\n    users,\n    error: null,\n  };\n}\n\nconst allUsers: DirectoryUser[] = [\n  {\n    id: 'leslie-alexander',\n    name: 'Leslie Alexander',\n    username: 'leslie',\n    email: 'leslie.alexander@example.com',\n    title: 'Product Manager',\n  },\n  {\n    id: 'kathryn-murphy',\n    name: 'Kathryn Murphy',\n    username: 'kathryn',\n    email: 'kathryn.murphy@example.com',\n    title: 'Marketing Lead',\n  },\n  {\n    id: 'courtney-henry',\n    name: 'Courtney Henry',\n    username: 'courtney',\n    email: 'courtney.henry@example.com',\n    title: 'Design Systems',\n  },\n  {\n    id: 'michael-foster',\n    name: 'Michael Foster',\n    username: 'michael',\n    email: 'michael.foster@example.com',\n    title: 'Engineering Manager',\n  },\n  {\n    id: 'lindsay-walton',\n    name: 'Lindsay Walton',\n    username: 'lindsay',\n    email: 'lindsay.walton@example.com',\n    title: 'Product Designer',\n  },\n  {\n    id: 'tom-cook',\n    name: 'Tom Cook',\n    username: 'tom',\n    email: 'tom.cook@example.com',\n    title: 'Frontend Engineer',\n  },\n  {\n    id: 'whitney-francis',\n    name: 'Whitney Francis',\n    username: 'whitney',\n    email: 'whitney.francis@example.com',\n    title: 'Customer Success',\n  },\n  {\n    id: 'jacob-jones',\n    name: 'Jacob Jones',\n    username: 'jacob',\n    email: 'jacob.jones@example.com',\n    title: 'Security Engineer',\n  },\n  {\n    id: 'arlene-mccoy',\n    name: 'Arlene McCoy',\n    username: 'arlene',\n    email: 'arlene.mccoy@example.com',\n    title: 'Data Analyst',\n  },\n  {\n    id: 'marvin-mckinney',\n    name: 'Marvin McKinney',\n    username: 'marvin',\n    email: 'marvin.mckinney@example.com',\n    title: 'QA Specialist',\n  },\n  {\n    id: 'eleanor-pena',\n    name: 'Eleanor Pena',\n    username: 'eleanor',\n    email: 'eleanor.pena@example.com',\n    title: 'Operations',\n  },\n  {\n    id: 'jerome-bell',\n    name: 'Jerome Bell',\n    username: 'jerome',\n    email: 'jerome.bell@example.com',\n    title: 'DevOps Engineer',\n  },\n];\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/combobox/demos/async-multiple/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoComboboxAsyncMultiple = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/combobox/demos/async-multiple/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Combobox } from '@base-ui/react/combobox';\n\nexport default function ExampleAsyncMultipleCombobox() {\n  const id = React.useId();\n\n  const [searchResults, setSearchResults] = React.useState<DirectoryUser[]>([]);\n  const [selectedValues, setSelectedValues] = React.useState<DirectoryUser[]>([]);\n  const [searchValue, setSearchValue] = React.useState('');\n  const [error, setError] = React.useState<string | null>(null);\n  const [blockStartStatus, setBlockStartStatus] = React.useState(false);\n\n  const [isPending, startTransition] = React.useTransition();\n\n  const { contains } = Combobox.useFilter();\n\n  const abortControllerRef = React.useRef<AbortController | null>(null);\n  const selectedValuesRef = React.useRef<DirectoryUser[]>([]);\n\n  const trimmedSearchValue = searchValue.trim();\n\n  const items = React.useMemo(() => {\n    if (selectedValues.length === 0) {\n      return searchResults;\n    }\n\n    const merged = [...searchResults];\n\n    selectedValues.forEach((user) => {\n      if (!searchResults.some((result) => result.id === user.id)) {\n        merged.push(user);\n      }\n    });\n\n    return merged;\n  }, [searchResults, selectedValues]);\n\n  function getStatus() {\n    if (isPending) {\n      return (\n        <React.Fragment>\n          <span\n            aria-hidden\n            className=\"inline-block size-3 animate-[spin_0.75s_linear_infinite] rounded-full border border-current border-r-transparent rtl:border-r-current rtl:border-l-transparent\"\n          />\n          Searching…\n        </React.Fragment>\n      );\n    }\n\n    if (error) {\n      return error;\n    }\n\n    if (trimmedSearchValue === '' && !blockStartStatus) {\n      return selectedValues.length > 0 ? null : 'Start typing to search people…';\n    }\n\n    if (searchResults.length === 0 && !blockStartStatus) {\n      return `No matches for \"${trimmedSearchValue}\".`;\n    }\n\n    return null;\n  }\n\n  function getEmptyMessage() {\n    if (trimmedSearchValue === '' || isPending || searchResults.length > 0 || error) {\n      return null;\n    }\n\n    return 'Try a different search term.';\n  }\n\n  return (\n    <Combobox.Root\n      items={items}\n      itemToStringLabel={(user: DirectoryUser) => user.name}\n      multiple\n      filter={null}\n      onOpenChangeComplete={(open) => {\n        if (!open) {\n          setSearchResults(selectedValuesRef.current);\n          setBlockStartStatus(false);\n        }\n      }}\n      onValueChange={(nextSelectedValues) => {\n        selectedValuesRef.current = nextSelectedValues;\n        setSelectedValues(nextSelectedValues);\n        setSearchValue('');\n        setError(null);\n\n        if (nextSelectedValues.length === 0) {\n          setSearchResults([]);\n          setBlockStartStatus(false);\n        } else {\n          setBlockStartStatus(true);\n        }\n      }}\n      onInputValueChange={(nextSearchValue, { reason }) => {\n        setSearchValue(nextSearchValue);\n\n        const controller = new AbortController();\n        abortControllerRef.current?.abort();\n        abortControllerRef.current = controller;\n\n        if (nextSearchValue === '') {\n          setSearchResults(selectedValuesRef.current);\n          setError(null);\n          setBlockStartStatus(false);\n          return;\n        }\n\n        if (reason === 'item-press') {\n          return;\n        }\n\n        startTransition(async () => {\n          setError(null);\n\n          const result = await searchUsers(nextSearchValue, contains);\n\n          if (controller.signal.aborted) {\n            return;\n          }\n\n          startTransition(() => {\n            setSearchResults(result.users);\n            setError(result.error);\n          });\n        });\n      }}\n    >\n      <div className=\"flex flex-col gap-1 text-sm text-gray-900\">\n        <label className=\"inline-flex text-inherit font-bold\" htmlFor={id}>\n          Assign reviewers\n        </label>\n        <Combobox.InputGroup className=\"relative flex min-h-10 w-[16rem] cursor-text rounded-md border border-gray-200 bg-[canvas] px-1.5 py-1 focus-within:outline-2 focus-within:-outline-offset-1 focus-within:outline-blue-800 md:w-[20rem]\">\n          <Combobox.Chips className=\"flex w-full flex-wrap items-center gap-1\">\n            <Combobox.Value>\n              {(value: DirectoryUser[]) => (\n                <React.Fragment>\n                  {value.map((user) => (\n                    <Combobox.Chip\n                      key={user.id}\n                      className=\"flex cursor-default items-center gap-1 rounded-md bg-gray-100 py-1 pl-2 pr-1 text-sm text-gray-900 outline-none focus-within:bg-blue-800 focus-within:text-gray-50 [@media(hover:hover)]:[&[data-highlighted]]:bg-blue-800 [@media(hover:hover)]:[&[data-highlighted]]:text-gray-50\"\n                      aria-label={user.name}\n                    >\n                      {user.name}\n                      <Combobox.ChipRemove\n                        className=\"inline-flex items-center justify-center rounded-md border-none bg-transparent p-[0.2rem] text-inherit hover:bg-gray-200\"\n                        aria-label=\"Remove\"\n                      >\n                        <XIcon />\n                      </Combobox.ChipRemove>\n                    </Combobox.Chip>\n                  ))}\n                  <Combobox.Input\n                    id={id}\n                    placeholder={value.length > 0 ? '' : 'e.g. Michael'}\n                    className=\"h-8 min-w-24 flex-1 rounded-md border-0 bg-transparent pl-2 text-base font-normal text-gray-900 outline-none placeholder:font-normal\"\n                  />\n                </React.Fragment>\n              )}\n            </Combobox.Value>\n          </Combobox.Chips>\n        </Combobox.InputGroup>\n      </div>\n\n      <Combobox.Portal>\n        <Combobox.Positioner className=\"outline-none\" sideOffset={4}>\n          <Combobox.Popup\n            className=\"box-border w-[var(--anchor-width)] max-h-[min(var(--available-height),23rem)] max-w-[var(--available-width)] origin-[var(--transform-origin)] overflow-y-auto scroll-pb-2 scroll-pt-2 overscroll-contain rounded-md bg-[canvas] py-2 text-gray-900 shadow-[0_10px_15px_-3px_var(--color-gray-200),0_4px_6px_-4px_var(--color-gray-200)] outline outline-1 outline-gray-200 transition-[opacity,transform,scale] duration-100 data-[ending-style]:transition-none data-[starting-style]:scale-95 data-[starting-style]:opacity-0 dark:-outline-offset-1 dark:shadow-none dark:outline-gray-300\"\n            aria-busy={isPending || undefined}\n          >\n            <Combobox.Status className=\"flex items-center gap-2 py-1 pl-4 pr-5 text-sm text-gray-600 empty:hidden\">\n              {getStatus()}\n            </Combobox.Status>\n            <Combobox.Empty className=\"box-border px-4 py-2 text-sm leading-4 text-gray-600 empty:hidden\">\n              {getEmptyMessage()}\n            </Combobox.Empty>\n            <Combobox.List>\n              {(user: DirectoryUser) => (\n                <Combobox.Item\n                  key={user.id}\n                  value={user}\n                  className=\"grid cursor-default select-none grid-cols-[0.75rem_1fr] items-start gap-2 py-2 pl-4 pr-5 text-base leading-[1.2rem] outline-none [@media(hover:hover)]:[&[data-highlighted]]:relative [@media(hover:hover)]:[&[data-highlighted]]:z-0 [@media(hover:hover)]:[&[data-highlighted]]:text-gray-900 [@media(hover:hover)]:[&[data-highlighted]]:before:absolute [@media(hover:hover)]:[&[data-highlighted]]:before:inset-y-0 [@media(hover:hover)]:[&[data-highlighted]]:before:inset-x-2 [@media(hover:hover)]:[&[data-highlighted]]:before:z-[-1] [@media(hover:hover)]:[&[data-highlighted]]:before:rounded [@media(hover:hover)]:[&[data-highlighted]]:before:bg-gray-100 [@media(hover:hover)]:[&[data-highlighted]]:before:content-['']\"\n                >\n                  <Combobox.ItemIndicator className=\"col-start-1 mt-1\">\n                    <CheckIcon className=\"size-3\" />\n                  </Combobox.ItemIndicator>\n                  <div className=\"col-start-2 flex flex-col gap-1\">\n                    <div className=\"text-[0.95rem] font-bold\">{user.name}</div>\n                    <div className=\"flex flex-wrap gap-2 text-[0.8125rem] text-gray-600\">\n                      <span className=\"opacity-80\">@{user.username}</span>\n                      <span>{user.title}</span>\n                    </div>\n                    <div className=\"text-xs text-gray-500\">{user.email}</div>\n                  </div>\n                </Combobox.Item>\n              )}\n            </Combobox.List>\n          </Combobox.Popup>\n        </Combobox.Positioner>\n      </Combobox.Portal>\n    </Combobox.Root>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\nfunction XIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width={16}\n      height={16}\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      aria-hidden\n      {...props}\n    >\n      <path d=\"M18 6 6 18\" />\n      <path d=\"m6 6 12 12\" />\n    </svg>\n  );\n}\n\ninterface DirectoryUser {\n  id: string;\n  name: string;\n  username: string;\n  email: string;\n  title: string;\n}\n\nasync function searchUsers(\n  query: string,\n  filter: (item: string, query: string) => boolean,\n): Promise<{ users: DirectoryUser[]; error: string | null }> {\n  // Simulate network delay\n  await new Promise((resolve) => {\n    setTimeout(resolve, Math.random() * 500 + 100);\n  });\n\n  // Simulate occasional network errors (1% chance)\n  if (Math.random() < 0.01 || query === 'will_error') {\n    return {\n      users: [],\n      error: 'Failed to fetch people. Please try again.',\n    };\n  }\n\n  const users = allUsers.filter((user) => {\n    return (\n      filter(user.name, query) ||\n      filter(user.username, query) ||\n      filter(user.email, query) ||\n      filter(user.title, query)\n    );\n  });\n\n  return {\n    users,\n    error: null,\n  };\n}\n\nconst allUsers: DirectoryUser[] = [\n  {\n    id: 'leslie-alexander',\n    name: 'Leslie Alexander',\n    username: 'leslie',\n    email: 'leslie.alexander@example.com',\n    title: 'Product Manager',\n  },\n  {\n    id: 'kathryn-murphy',\n    name: 'Kathryn Murphy',\n    username: 'kathryn',\n    email: 'kathryn.murphy@example.com',\n    title: 'Marketing Lead',\n  },\n  {\n    id: 'courtney-henry',\n    name: 'Courtney Henry',\n    username: 'courtney',\n    email: 'courtney.henry@example.com',\n    title: 'Design Systems',\n  },\n  {\n    id: 'michael-foster',\n    name: 'Michael Foster',\n    username: 'michael',\n    email: 'michael.foster@example.com',\n    title: 'Engineering Manager',\n  },\n  {\n    id: 'lindsay-walton',\n    name: 'Lindsay Walton',\n    username: 'lindsay',\n    email: 'lindsay.walton@example.com',\n    title: 'Product Designer',\n  },\n  {\n    id: 'tom-cook',\n    name: 'Tom Cook',\n    username: 'tom',\n    email: 'tom.cook@example.com',\n    title: 'Frontend Engineer',\n  },\n  {\n    id: 'whitney-francis',\n    name: 'Whitney Francis',\n    username: 'whitney',\n    email: 'whitney.francis@example.com',\n    title: 'Customer Success',\n  },\n  {\n    id: 'jacob-jones',\n    name: 'Jacob Jones',\n    username: 'jacob',\n    email: 'jacob.jones@example.com',\n    title: 'Security Engineer',\n  },\n  {\n    id: 'arlene-mccoy',\n    name: 'Arlene McCoy',\n    username: 'arlene',\n    email: 'arlene.mccoy@example.com',\n    title: 'Data Analyst',\n  },\n  {\n    id: 'marvin-mckinney',\n    name: 'Marvin McKinney',\n    username: 'marvin',\n    email: 'marvin.mckinney@example.com',\n    title: 'QA Specialist',\n  },\n  {\n    id: 'eleanor-pena',\n    name: 'Eleanor Pena',\n    username: 'eleanor',\n    email: 'eleanor.pena@example.com',\n    title: 'Operations',\n  },\n  {\n    id: 'jerome-bell',\n    name: 'Jerome Bell',\n    username: 'jerome',\n    email: 'jerome.bell@example.com',\n    title: 'DevOps Engineer',\n  },\n];\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/combobox/demos/async-single/css-modules/index.module.css",
    "content": ".Label {\n  display: flex;\n  flex-direction: column;\n  gap: 0.25rem;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 700;\n  color: var(--color-gray-900);\n  position: relative;\n}\n\n.InputGroup {\n  position: relative;\n  width: 16rem;\n  height: 2.5rem;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: canvas;\n\n  &:focus-within {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n\n  @media (min-width: 500px) {\n    width: 20rem;\n  }\n\n  &:has(.Clear) .Input {\n    padding-right: calc(0.5rem + 1.5rem * 2);\n  }\n}\n\n.Input {\n  box-sizing: border-box;\n  margin: 0;\n  width: 100%;\n  height: 100%;\n  padding-left: 0.875rem;\n  padding-right: calc(0.5rem + 1.5rem);\n  border: none;\n  border-radius: inherit;\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  background-color: transparent;\n  color: var(--color-gray-900);\n\n  &:focus {\n    outline: none;\n  }\n}\n\n.ActionButtons {\n  box-sizing: border-box;\n  position: absolute;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  bottom: 0;\n  height: 2.5rem;\n  right: 0.5rem;\n  border-radius: 0.25rem;\n  border: none;\n  color: var(--color-gray-600);\n  padding: 0;\n}\n\n.Trigger,\n.Clear {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 1.5rem;\n  height: 2.5rem;\n  color: var(--color-gray-600);\n  border: none;\n  padding: 0;\n  border-radius: 0.25rem;\n  background: none;\n}\n\n.ClearIcon,\n.TriggerIcon {\n  width: 1rem;\n  height: 1rem;\n}\n\n.Positioner {\n  outline: 0;\n}\n\n.Popup {\n  box-sizing: border-box;\n  padding-block: 0.5rem;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  width: var(--anchor-width);\n  max-height: min(var(--available-height), 23rem);\n  max-width: var(--available-width);\n  overflow-y: auto;\n  scroll-padding-block: 0.5rem;\n  overscroll-behavior: contain;\n  transition:\n    opacity 0.1s,\n    transform 0.1s;\n  transform-origin: var(--transform-origin);\n\n  &[data-starting-style] {\n    opacity: 0;\n    transform: scale(0.95);\n  }\n\n  &[data-ending-style] {\n    transition: none;\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Status {\n  display: flex;\n  align-items: center;\n  gap: 0.5rem;\n  padding-block: 0.25rem;\n  padding-left: 1rem;\n  padding-right: 1.25rem;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  color: var(--color-gray-600);\n}\n\n.Status:empty {\n  display: none;\n}\n\n.Spinner {\n  box-sizing: border-box;\n  width: 0.75rem;\n  height: 0.75rem;\n  border-radius: 10rem;\n  border: 1px solid currentColor;\n  border-right-color: transparent;\n  animation: comboboxSpinner 0.75s linear infinite;\n}\n\n.Spinner:dir(rtl) {\n  border-right-color: currentColor;\n  border-left-color: transparent;\n}\n\n@keyframes comboboxSpinner {\n  100% {\n    transform: rotate(360deg);\n  }\n}\n\n.Item {\n  box-sizing: border-box;\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  padding-block: 0.5rem;\n  padding-inline: 1rem;\n  padding-right: 1.25rem;\n  display: grid;\n  gap: 0.5rem;\n  align-items: flex-start;\n  grid-template-columns: 0.75rem 1fr;\n  font-size: 1rem;\n  line-height: 1.2rem;\n\n  @media (hover: hover) {\n    &[data-highlighted] {\n      z-index: 0;\n      position: relative;\n      color: var(--color-gray-900);\n    }\n\n    &[data-highlighted]::before {\n      content: '';\n      z-index: -1;\n      position: absolute;\n      inset-block: 0;\n      inset-inline: 0.5rem;\n      border-radius: 0.25rem;\n      background-color: var(--color-gray-100);\n    }\n  }\n}\n\n.ItemIndicator {\n  grid-column-start: 1;\n  margin-top: 0.25rem;\n}\n\n.ItemIndicatorIcon {\n  display: block;\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.ItemText {\n  grid-column-start: 2;\n  display: flex;\n  flex-direction: column;\n  gap: 0.25rem;\n}\n\n.ItemTitle {\n  font-size: 0.95rem;\n  font-weight: 700;\n}\n\n.ItemSubtitle {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 0.75rem;\n  font-size: 0.8125rem;\n  color: var(--color-gray-600);\n}\n\n.ItemUsername {\n  opacity: 0.8;\n}\n\n.ItemEmail {\n  font-size: 0.75rem;\n  opacity: 0.8;\n}\n\n.Empty:not(:empty) {\n  box-sizing: border-box;\n  font-size: 0.875rem;\n  line-height: 1rem;\n  color: var(--color-gray-600);\n  padding: 0.5rem 1rem;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/combobox/demos/async-single/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Combobox } from '@base-ui/react/combobox';\nimport styles from './index.module.css';\n\nexport default function ExampleAsyncSingleCombobox() {\n  const id = React.useId();\n\n  const [searchResults, setSearchResults] = React.useState<DirectoryUser[]>([]);\n  const [selectedValue, setSelectedValue] = React.useState<DirectoryUser | null>(null);\n  const [searchValue, setSearchValue] = React.useState('');\n  const [error, setError] = React.useState<string | null>(null);\n  const [isPending, startTransition] = React.useTransition();\n\n  const { contains } = Combobox.useFilter();\n\n  const abortControllerRef = React.useRef<AbortController | null>(null);\n\n  const trimmedSearchValue = searchValue.trim();\n\n  const items = React.useMemo(() => {\n    if (!selectedValue || searchResults.some((user) => user.id === selectedValue.id)) {\n      return searchResults;\n    }\n\n    return [...searchResults, selectedValue];\n  }, [searchResults, selectedValue]);\n\n  function getStatus() {\n    if (isPending) {\n      return (\n        <React.Fragment>\n          <span className={styles.Spinner} aria-hidden />\n          Searching…\n        </React.Fragment>\n      );\n    }\n\n    if (error) {\n      return error;\n    }\n\n    if (trimmedSearchValue === '') {\n      return selectedValue ? null : 'Start typing to search people…';\n    }\n\n    if (searchResults.length === 0) {\n      return `No matches for \"${trimmedSearchValue}\".`;\n    }\n\n    return null;\n  }\n\n  function getEmptyMessage() {\n    if (trimmedSearchValue === '' || isPending || searchResults.length > 0 || error) {\n      return null;\n    }\n\n    return 'Try a different search term.';\n  }\n\n  return (\n    <Combobox.Root\n      items={items}\n      itemToStringLabel={(user: DirectoryUser) => user.name}\n      filter={null}\n      onOpenChangeComplete={(open) => {\n        if (!open && selectedValue) {\n          setSearchResults([selectedValue]);\n        }\n      }}\n      onValueChange={(nextSelectedValue) => {\n        setSelectedValue(nextSelectedValue);\n        setSearchValue('');\n        setError(null);\n      }}\n      onInputValueChange={(nextSearchValue, { reason }) => {\n        setSearchValue(nextSearchValue);\n\n        const controller = new AbortController();\n        abortControllerRef.current?.abort();\n        abortControllerRef.current = controller;\n\n        if (nextSearchValue === '') {\n          setSearchResults([]);\n          setError(null);\n          return;\n        }\n\n        if (reason === 'item-press') {\n          return;\n        }\n\n        startTransition(async () => {\n          setError(null);\n\n          const result = await searchUsers(nextSearchValue, contains);\n\n          if (controller.signal.aborted) {\n            return;\n          }\n\n          startTransition(() => {\n            setSearchResults(result.users);\n            setError(result.error);\n          });\n        });\n      }}\n    >\n      <div className={styles.Label}>\n        <label htmlFor={id}>Assign reviewer</label>\n        <Combobox.InputGroup className={styles.InputGroup}>\n          <Combobox.Input id={id} placeholder=\"e.g. Michael\" className={styles.Input} />\n          <div className={styles.ActionButtons}>\n            <Combobox.Clear className={styles.Clear} aria-label=\"Clear selection\">\n              <ClearIcon className={styles.ClearIcon} />\n            </Combobox.Clear>\n            <Combobox.Trigger className={styles.Trigger} aria-label=\"Open popup\">\n              <ChevronDownIcon className={styles.TriggerIcon} />\n            </Combobox.Trigger>\n          </div>\n        </Combobox.InputGroup>\n      </div>\n\n      <Combobox.Portal>\n        <Combobox.Positioner className={styles.Positioner} sideOffset={4}>\n          <Combobox.Popup className={styles.Popup} aria-busy={isPending || undefined}>\n            <Combobox.Status className={styles.Status}>{getStatus()}</Combobox.Status>\n            <Combobox.Empty className={styles.Empty}>{getEmptyMessage()}</Combobox.Empty>\n            <Combobox.List>\n              {(user: DirectoryUser) => (\n                <Combobox.Item key={user.id} className={styles.Item} value={user}>\n                  <Combobox.ItemIndicator className={styles.ItemIndicator}>\n                    <CheckIcon className={styles.ItemIndicatorIcon} />\n                  </Combobox.ItemIndicator>\n                  <div className={styles.ItemText}>\n                    <div className={styles.ItemTitle}>{user.name}</div>\n                    <div className={styles.ItemSubtitle}>\n                      <span className={styles.ItemUsername}>@{user.username}</span>\n                      <span>{user.title}</span>\n                    </div>\n                    <div className={styles.ItemEmail}>{user.email}</div>\n                  </div>\n                </Combobox.Item>\n              )}\n            </Combobox.List>\n          </Combobox.Popup>\n        </Combobox.Positioner>\n      </Combobox.Portal>\n    </Combobox.Root>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\nfunction ClearIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M18 6L6 18\" />\n      <path d=\"M6 6l12 12\" />\n    </svg>\n  );\n}\n\nfunction ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M6 9l6 6 6-6\" />\n    </svg>\n  );\n}\n\ninterface DirectoryUser {\n  id: string;\n  name: string;\n  username: string;\n  email: string;\n  title: string;\n}\n\nasync function searchUsers(\n  query: string,\n  filter: (item: string, query: string) => boolean,\n): Promise<{ users: DirectoryUser[]; error: string | null }> {\n  // Simulate network delay\n  await new Promise((resolve) => {\n    setTimeout(resolve, Math.random() * 500 + 100);\n  });\n\n  // Simulate occasional network errors (1% chance)\n  if (Math.random() < 0.01 || query === 'will_error') {\n    return {\n      users: [],\n      error: 'Failed to fetch people. Please try again.',\n    };\n  }\n\n  const users = allUsers.filter((user) => {\n    return (\n      filter(user.name, query) ||\n      filter(user.username, query) ||\n      filter(user.email, query) ||\n      filter(user.title, query)\n    );\n  });\n\n  return {\n    users,\n    error: null,\n  };\n}\n\nconst allUsers: DirectoryUser[] = [\n  {\n    id: 'leslie-alexander',\n    name: 'Leslie Alexander',\n    username: 'leslie',\n    email: 'leslie.alexander@example.com',\n    title: 'Product Manager',\n  },\n  {\n    id: 'kathryn-murphy',\n    name: 'Kathryn Murphy',\n    username: 'kathryn',\n    email: 'kathryn.murphy@example.com',\n    title: 'Marketing Lead',\n  },\n  {\n    id: 'courtney-henry',\n    name: 'Courtney Henry',\n    username: 'courtney',\n    email: 'courtney.henry@example.com',\n    title: 'Design Systems',\n  },\n  {\n    id: 'michael-foster',\n    name: 'Michael Foster',\n    username: 'michael',\n    email: 'michael.foster@example.com',\n    title: 'Engineering Manager',\n  },\n  {\n    id: 'lindsay-walton',\n    name: 'Lindsay Walton',\n    username: 'lindsay',\n    email: 'lindsay.walton@example.com',\n    title: 'Product Designer',\n  },\n  {\n    id: 'tom-cook',\n    name: 'Tom Cook',\n    username: 'tom',\n    email: 'tom.cook@example.com',\n    title: 'Frontend Engineer',\n  },\n  {\n    id: 'whitney-francis',\n    name: 'Whitney Francis',\n    username: 'whitney',\n    email: 'whitney.francis@example.com',\n    title: 'Customer Success',\n  },\n  {\n    id: 'jacob-jones',\n    name: 'Jacob Jones',\n    username: 'jacob',\n    email: 'jacob.jones@example.com',\n    title: 'Security Engineer',\n  },\n  {\n    id: 'arlene-mccoy',\n    name: 'Arlene McCoy',\n    username: 'arlene',\n    email: 'arlene.mccoy@example.com',\n    title: 'Data Analyst',\n  },\n  {\n    id: 'marvin-mckinney',\n    name: 'Marvin McKinney',\n    username: 'marvin',\n    email: 'marvin.mckinney@example.com',\n    title: 'QA Specialist',\n  },\n  {\n    id: 'eleanor-pena',\n    name: 'Eleanor Pena',\n    username: 'eleanor',\n    email: 'eleanor.pena@example.com',\n    title: 'Operations',\n  },\n  {\n    id: 'jerome-bell',\n    name: 'Jerome Bell',\n    username: 'jerome',\n    email: 'jerome.bell@example.com',\n    title: 'DevOps Engineer',\n  },\n];\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/combobox/demos/async-single/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoComboboxAsyncSingle = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/combobox/demos/async-single/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Combobox } from '@base-ui/react/combobox';\n\nexport default function ExampleAsyncSingleCombobox() {\n  const id = React.useId();\n\n  const [searchResults, setSearchResults] = React.useState<DirectoryUser[]>([]);\n  const [selectedValue, setSelectedValue] = React.useState<DirectoryUser | null>(null);\n  const [searchValue, setSearchValue] = React.useState('');\n  const [error, setError] = React.useState<string | null>(null);\n  const [isPending, startTransition] = React.useTransition();\n\n  const { contains } = Combobox.useFilter();\n\n  const abortControllerRef = React.useRef<AbortController | null>(null);\n\n  const trimmedSearchValue = searchValue.trim();\n\n  const items = React.useMemo(() => {\n    if (!selectedValue || searchResults.some((user) => user.id === selectedValue.id)) {\n      return searchResults;\n    }\n\n    return [...searchResults, selectedValue];\n  }, [searchResults, selectedValue]);\n\n  function getStatus() {\n    if (isPending) {\n      return (\n        <React.Fragment>\n          <span\n            aria-hidden\n            className=\"inline-block size-3 animate-spin rounded-full border border-current border-r-transparent rtl:border-r-current rtl:border-l-transparent\"\n          />\n          Searching…\n        </React.Fragment>\n      );\n    }\n\n    if (error) {\n      return error;\n    }\n\n    if (trimmedSearchValue === '') {\n      return selectedValue ? null : 'Start typing to search people…';\n    }\n\n    if (searchResults.length === 0) {\n      return `No matches for \"${trimmedSearchValue}\".`;\n    }\n\n    return null;\n  }\n\n  function getEmptyMessage() {\n    if (trimmedSearchValue === '' || isPending || searchResults.length > 0 || error) {\n      return null;\n    }\n    return 'Try a different search term.';\n  }\n\n  return (\n    <Combobox.Root\n      items={items}\n      itemToStringLabel={(user: DirectoryUser) => user.name}\n      filter={null}\n      onOpenChangeComplete={(open) => {\n        if (!open && selectedValue) {\n          setSearchResults([selectedValue]);\n        }\n      }}\n      onValueChange={(nextSelectedValue) => {\n        setSelectedValue(nextSelectedValue);\n        setSearchValue('');\n        setError(null);\n      }}\n      onInputValueChange={(nextSearchValue, { reason }) => {\n        setSearchValue(nextSearchValue);\n\n        if (nextSearchValue === '') {\n          setSearchResults([]);\n          setError(null);\n          return;\n        }\n\n        if (reason === 'item-press') {\n          return;\n        }\n\n        const controller = new AbortController();\n        abortControllerRef.current?.abort();\n        abortControllerRef.current = controller;\n\n        startTransition(async () => {\n          setError(null);\n\n          const result = await searchUsers(nextSearchValue, contains);\n\n          if (controller.signal.aborted) {\n            return;\n          }\n\n          startTransition(() => {\n            setSearchResults(result.users);\n            setError(result.error);\n          });\n        });\n      }}\n    >\n      <div className=\"relative flex flex-col gap-1 text-sm font-bold leading-5 text-gray-900\">\n        <label htmlFor={id}>Assign reviewer</label>\n        <Combobox.InputGroup className=\"relative box-content h-10 w-[16rem] rounded-md border border-gray-200 bg-[canvas] focus-within:outline-2 focus-within:-outline-offset-1 focus-within:outline-blue-800 md:w-[20rem] [&>input]:pr-[calc(0.5rem+1.5rem)] has-[.combobox-clear]:[&>input]:pr-[calc(0.5rem+1.5rem*2)]\">\n          <Combobox.Input\n            id={id}\n            placeholder=\"e.g. Michael\"\n            className=\"box-border h-full w-full border-0 bg-transparent pl-3.5 text-base font-normal text-gray-900 outline-none\"\n          />\n          <div className=\"absolute bottom-0 right-2 flex h-10 items-center justify-center text-gray-600\">\n            <Combobox.Clear\n              className=\"combobox-clear flex h-10 w-6 items-center justify-center rounded border-0 bg-transparent p-0\"\n              aria-label=\"Clear selection\"\n            >\n              <ClearIcon className=\"size-4\" />\n            </Combobox.Clear>\n            <Combobox.Trigger\n              className=\"flex h-10 w-6 items-center justify-center rounded border-0 bg-transparent p-0\"\n              aria-label=\"Open popup\"\n            >\n              <ChevronDownIcon className=\"size-4\" />\n            </Combobox.Trigger>\n          </div>\n        </Combobox.InputGroup>\n      </div>\n\n      <Combobox.Portal>\n        <Combobox.Positioner className=\"outline-none\" sideOffset={4}>\n          <Combobox.Popup\n            className=\"box-border w-[var(--anchor-width)] max-h-[min(var(--available-height),23rem)] max-w-[var(--available-width)] origin-[var(--transform-origin)] overflow-y-auto scroll-pb-2 scroll-pt-2 overscroll-contain rounded-md bg-[canvas] py-2 text-gray-900 shadow-[0_10px_15px_-3px_var(--color-gray-200),0_4px_6px_-4px_var(--color-gray-200)] outline outline-1 outline-gray-200 transition-[transform,scale,opacity] data-[ending-style]:transition-none data-[starting-style]:scale-95 data-[starting-style]:opacity-0 dark:-outline-offset-1 dark:shadow-none dark:outline-gray-300\"\n            aria-busy={isPending || undefined}\n          >\n            <Combobox.Status className=\"flex items-center gap-2 py-1 pl-4 pr-5 text-sm text-gray-600 empty:hidden\">\n              {getStatus()}\n            </Combobox.Status>\n            <Combobox.Empty className=\"px-4 py-2 text-[0.875rem] leading-4 text-gray-600 empty:hidden\">\n              {getEmptyMessage()}\n            </Combobox.Empty>\n            <Combobox.List>\n              {(user: DirectoryUser) => (\n                <Combobox.Item\n                  key={user.id}\n                  value={user}\n                  className=\"grid cursor-default select-none grid-cols-[0.75rem_1fr] items-start gap-2 py-2 pl-4 pr-5 text-base leading-[1.2rem] outline-none [@media(hover:hover)]:[&[data-highlighted]]:relative [@media(hover:hover)]:[&[data-highlighted]]:z-0 [@media(hover:hover)]:[&[data-highlighted]]:text-gray-900 [@media(hover:hover)]:[&[data-highlighted]]:before:absolute [@media(hover:hover)]:[&[data-highlighted]]:before:inset-y-0 [@media(hover:hover)]:[&[data-highlighted]]:before:inset-x-2 [@media(hover:hover)]:[&[data-highlighted]]:before:z-[-1] [@media(hover:hover)]:[&[data-highlighted]]:before:rounded [@media(hover:hover)]:[&[data-highlighted]]:before:bg-gray-100 [@media(hover:hover)]:[&[data-highlighted]]:before:content-['']\"\n                >\n                  <Combobox.ItemIndicator className=\"col-start-1 mt-1\">\n                    <CheckIcon className=\"size-3\" />\n                  </Combobox.ItemIndicator>\n                  <div className=\"col-start-2 flex flex-col gap-1\">\n                    <div className=\"text-[0.95rem] font-bold\">{user.name}</div>\n                    <div className=\"flex flex-wrap gap-3 text-[0.8125rem] text-gray-600\">\n                      <span className=\"opacity-80\">@{user.username}</span>\n                      <span>{user.title}</span>\n                    </div>\n                    <div className=\"text-xs opacity-80\">{user.email}</div>\n                  </div>\n                </Combobox.Item>\n              )}\n            </Combobox.List>\n          </Combobox.Popup>\n        </Combobox.Positioner>\n      </Combobox.Portal>\n    </Combobox.Root>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\nfunction ClearIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M18 6L6 18\" />\n      <path d=\"M6 6l12 12\" />\n    </svg>\n  );\n}\n\nfunction ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M6 9l6 6 6-6\" />\n    </svg>\n  );\n}\n\ninterface DirectoryUser {\n  id: string;\n  name: string;\n  username: string;\n  email: string;\n  title: string;\n}\n\nasync function searchUsers(\n  query: string,\n  filter: (item: string, query: string) => boolean,\n): Promise<{ users: DirectoryUser[]; error: string | null }> {\n  // Simulate network delay\n  await new Promise((resolve) => {\n    setTimeout(resolve, Math.random() * 500 + 100);\n  });\n\n  // Simulate occasional network errors (1% chance)\n  if (Math.random() < 0.01 || query === 'will_error') {\n    return {\n      users: [],\n      error: 'Failed to fetch people. Please try again.',\n    };\n  }\n\n  const users = allUsers.filter((user) => {\n    return (\n      filter(user.name, query) ||\n      filter(user.username, query) ||\n      filter(user.email, query) ||\n      filter(user.title, query)\n    );\n  });\n\n  return {\n    users,\n    error: null,\n  };\n}\n\nconst allUsers: DirectoryUser[] = [\n  {\n    id: 'leslie-alexander',\n    name: 'Leslie Alexander',\n    username: 'leslie',\n    email: 'leslie.alexander@example.com',\n    title: 'Product Manager',\n  },\n  {\n    id: 'kathryn-murphy',\n    name: 'Kathryn Murphy',\n    username: 'kathryn',\n    email: 'kathryn.murphy@example.com',\n    title: 'Marketing Lead',\n  },\n  {\n    id: 'courtney-henry',\n    name: 'Courtney Henry',\n    username: 'courtney',\n    email: 'courtney.henry@example.com',\n    title: 'Design Systems',\n  },\n  {\n    id: 'michael-foster',\n    name: 'Michael Foster',\n    username: 'michael',\n    email: 'michael.foster@example.com',\n    title: 'Engineering Manager',\n  },\n  {\n    id: 'lindsay-walton',\n    name: 'Lindsay Walton',\n    username: 'lindsay',\n    email: 'lindsay.walton@example.com',\n    title: 'Product Designer',\n  },\n  {\n    id: 'tom-cook',\n    name: 'Tom Cook',\n    username: 'tom',\n    email: 'tom.cook@example.com',\n    title: 'Frontend Engineer',\n  },\n  {\n    id: 'whitney-francis',\n    name: 'Whitney Francis',\n    username: 'whitney',\n    email: 'whitney.francis@example.com',\n    title: 'Customer Success',\n  },\n  {\n    id: 'jacob-jones',\n    name: 'Jacob Jones',\n    username: 'jacob',\n    email: 'jacob.jones@example.com',\n    title: 'Security Engineer',\n  },\n  {\n    id: 'arlene-mccoy',\n    name: 'Arlene McCoy',\n    username: 'arlene',\n    email: 'arlene.mccoy@example.com',\n    title: 'Data Analyst',\n  },\n  {\n    id: 'marvin-mckinney',\n    name: 'Marvin McKinney',\n    username: 'marvin',\n    email: 'marvin.mckinney@example.com',\n    title: 'QA Specialist',\n  },\n  {\n    id: 'eleanor-pena',\n    name: 'Eleanor Pena',\n    username: 'eleanor',\n    email: 'eleanor.pena@example.com',\n    title: 'Operations',\n  },\n  {\n    id: 'jerome-bell',\n    name: 'Jerome Bell',\n    username: 'jerome',\n    email: 'jerome.bell@example.com',\n    title: 'DevOps Engineer',\n  },\n];\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/combobox/demos/creatable/css-modules/index.module.css",
    "content": ".Container {\n  max-width: 28rem;\n  display: flex;\n  flex-direction: column;\n  gap: 0.25rem;\n}\n\n.Label {\n  display: flex;\n  flex-direction: column;\n  gap: 0.25rem;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 700;\n  color: var(--color-gray-900);\n}\n\n.InputGroup {\n  box-sizing: border-box;\n  display: flex;\n  flex-wrap: wrap;\n  align-items: center;\n  gap: 0.125rem;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: canvas;\n  padding: 0.25rem 0.375rem;\n  width: 16rem;\n  cursor: text;\n\n  &:focus-within {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n\n  @media (min-width: 500px) {\n    width: 22rem;\n  }\n}\n\n.Chips {\n  display: flex;\n  flex-wrap: wrap;\n  align-items: center;\n  gap: 0.125rem;\n  width: 100%;\n}\n\n.Chip {\n  display: flex;\n  align-items: center;\n  background-color: var(--color-gray-100);\n  color: var(--color-gray-900);\n  border-radius: 0.375rem;\n  font-size: 0.875rem;\n  padding: 0.2rem 0.2rem 0.2rem 0.4rem;\n  overflow: hidden;\n  gap: 0.25rem;\n  outline: 0;\n  cursor: default;\n\n  &:focus-within {\n    background-color: var(--color-blue);\n    color: var(--color-gray-50);\n  }\n\n  @media (hover: hover) {\n    &[data-highlighted] {\n      background-color: var(--color-blue);\n      color: var(--color-gray-50);\n    }\n  }\n}\n\n.ChipRemove {\n  border: none;\n  background: none;\n  padding: 0.25rem;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  color: inherit;\n  border-radius: 0.375rem;\n\n  &:hover {\n    background-color: rgb(0 0 0 / 0.1);\n  }\n}\n\n.Input {\n  flex: 1;\n  box-sizing: border-box;\n  padding-left: 0.5rem;\n  margin: 0;\n  border: none;\n  height: 2rem;\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  background-color: transparent;\n  color: var(--color-gray-900);\n  min-width: 3rem;\n\n  &:focus {\n    outline: none;\n  }\n}\n\n.Positioner {\n  outline: 0;\n  z-index: 50;\n}\n\n.Popup {\n  box-sizing: border-box;\n  border-radius: 0.5rem;\n  padding-block: 0.5rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  width: var(--anchor-width);\n  max-width: var(--available-width);\n  max-height: min(var(--available-height), 24rem);\n  overflow-y: auto;\n  scroll-padding-block: 0.5rem;\n  overscroll-behavior: contain;\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Item {\n  box-sizing: border-box;\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  padding-block: 0.5rem;\n  padding-left: 1rem;\n  padding-right: 2rem;\n  display: grid;\n  gap: 0.5rem;\n  align-items: center;\n  grid-template-columns: 0.75rem 1fr;\n  font-size: 1rem;\n  line-height: 1rem;\n\n  &[data-selected] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-900);\n  }\n\n  &[data-selected]::before,\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.5rem;\n    border-radius: 0.25rem;\n  }\n\n  @media (hover: hover) {\n    &[data-highlighted] {\n      z-index: 0;\n      position: relative;\n      color: var(--color-gray-50);\n    }\n\n    &[data-highlighted]::before {\n      background-color: var(--color-gray-900);\n    }\n  }\n}\n\n.ItemText {\n  grid-column-start: 2;\n}\n\n.ItemIndicator {\n  grid-column-start: 1;\n}\n\n.ItemIndicatorIcon {\n  display: block;\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.Empty:not(:empty) {\n  box-sizing: border-box;\n  font-size: 0.925rem;\n  line-height: 1rem;\n  color: var(--color-gray-600);\n  padding: 0.5rem 1rem;\n}\n\n/* Creatable option styling */\n.CreateButton {\n  box-sizing: border-box;\n  width: 100%;\n  display: grid;\n  grid-template-columns: 0.75rem 1fr;\n  align-items: center;\n  gap: 0.5rem;\n  border: none;\n  background: none;\n  text-align: left;\n  color: var(--color-gray-900);\n  padding: 0.5rem 2rem 0.5rem 1rem;\n  cursor: default;\n  border-radius: 0.25rem;\n}\n\n.CreateIcon {\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.CreateText {\n  grid-column-start: 2;\n}\n\n/* Dialog styles (reused from dialog hero demo) */\n.Backdrop {\n  position: fixed;\n  min-height: 100dvh;\n  inset: 0;\n  background-color: black;\n  opacity: 0.2;\n  transition: opacity 150ms cubic-bezier(0.45, 1.005, 0, 1.005);\n\n  /* iOS 26+: Ensure the backdrop covers the entire visible viewport. */\n  @supports (-webkit-touch-callout: none) {\n    position: absolute;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    opacity: 0.7;\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n  }\n}\n\n.DialogPopup {\n  box-sizing: border-box;\n  position: fixed;\n  top: 50%;\n  left: 50%;\n  transform: translate(-50%, -50%);\n  width: 24rem;\n  max-width: calc(100vw - 3rem);\n  margin-top: -2rem;\n  padding: 1.5rem;\n  border-radius: 0.5rem;\n  outline: 1px solid var(--color-gray-200);\n  background-color: canvas;\n  color: var(--color-gray-900);\n  transition: all 150ms;\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: translate(-50%, -50%) scale(0.9);\n  }\n}\n\n.Title {\n  margin-top: -0.375rem;\n  margin-bottom: 0.25rem;\n  font-size: 1.125rem;\n  line-height: 1.75rem;\n  letter-spacing: -0.0025em;\n  font-weight: 700;\n}\n\n.Description {\n  margin: 0 0 1rem;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-600);\n}\n\n.TextField {\n  box-sizing: border-box;\n  width: 100%;\n  height: 2.5rem;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  padding: 0 0.625rem;\n  font: inherit;\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Actions {\n  display: flex;\n  justify-content: end;\n  gap: 1rem;\n  margin-top: 1rem;\n}\n\n.Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/combobox/demos/creatable/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Combobox } from '@base-ui/react/combobox';\nimport { Dialog } from '@base-ui/react/dialog';\nimport styles from './index.module.css';\n\nexport default function ExampleCreatableCombobox() {\n  const id = React.useId();\n\n  const [labels, setLabels] = React.useState<LabelItem[]>(initialLabels);\n  const [selected, setSelected] = React.useState<LabelItem[]>([]);\n  const [query, setQuery] = React.useState('');\n  const [openDialog, setOpenDialog] = React.useState(false);\n\n  const createInputRef = React.useRef<HTMLInputElement | null>(null);\n  const comboboxInputRef = React.useRef<HTMLInputElement | null>(null);\n  const pendingQueryRef = React.useRef('');\n  const highlightedItemRef = React.useRef<LabelItem | undefined>(undefined);\n\n  function handleInputKeyDown(event: React.KeyboardEvent<HTMLInputElement>) {\n    if (event.key !== 'Enter' || highlightedItemRef.current) {\n      return;\n    }\n\n    const currentTrimmed = query.trim();\n    if (currentTrimmed === '') {\n      return;\n    }\n\n    const normalized = currentTrimmed.toLocaleLowerCase();\n    const existing = labels.find((label) => label.value.trim().toLocaleLowerCase() === normalized);\n\n    if (existing) {\n      setSelected((prev) =>\n        prev.some((item) => item.id === existing.id) ? prev : [...prev, existing],\n      );\n      setQuery('');\n      return;\n    }\n\n    pendingQueryRef.current = currentTrimmed;\n    setOpenDialog(true);\n  }\n\n  function handleCreate() {\n    const input = createInputRef.current || comboboxInputRef.current;\n    const value = input ? input.value.trim() : '';\n    if (!value) {\n      return;\n    }\n\n    const normalized = value.toLocaleLowerCase();\n    const baseId = normalized.replace(/\\s+/g, '-');\n    const existing = labels.find((l) => l.value.trim().toLocaleLowerCase() === normalized);\n\n    if (existing) {\n      setSelected((prev) => (prev.some((i) => i.id === existing.id) ? prev : [...prev, existing]));\n      setOpenDialog(false);\n      setQuery('');\n      return;\n    }\n\n    // Ensure we don't collide with an existing id (e.g., value \"docs\" vs. existing id \"docs\")\n    const existingIds = new Set(labels.map((l) => l.id));\n    let uniqueId = baseId;\n    if (existingIds.has(uniqueId)) {\n      let i = 2;\n      while (existingIds.has(`${baseId}-${i}`)) {\n        i += 1;\n      }\n      uniqueId = `${baseId}-${i}`;\n    }\n\n    const newItem: LabelItem = { id: uniqueId, value };\n\n    if (!selected.find((item) => item.id === newItem.id)) {\n      setLabels((prev) => [...prev, newItem]);\n      setSelected((prev) => [...prev, newItem]);\n    }\n\n    setOpenDialog(false);\n    setQuery('');\n  }\n\n  function handleCreateSubmit(event: React.FormEvent<HTMLFormElement>) {\n    event.preventDefault();\n    handleCreate();\n  }\n\n  const trimmed = query.trim();\n  const lowered = trimmed.toLocaleLowerCase();\n  const exactExists = labels.some((l) => l.value.trim().toLocaleLowerCase() === lowered);\n  // Show the creatable item alongside matches if there's no exact match\n  const itemsForView: Array<LabelItem> =\n    trimmed !== '' && !exactExists\n      ? [...labels, { creatable: trimmed, id: `create:${lowered}`, value: `Create \"${trimmed}\"` }]\n      : labels;\n\n  return (\n    <React.Fragment>\n      <Combobox.Root\n        items={itemsForView}\n        multiple\n        onValueChange={(next) => {\n          const creatableSelection = next.find(\n            (item) => item.creatable && !selected.some((current) => current.id === item.id),\n          );\n\n          if (creatableSelection && creatableSelection.creatable) {\n            pendingQueryRef.current = creatableSelection.creatable;\n            setOpenDialog(true);\n            return;\n          }\n          const clean = next.filter((i) => !i.creatable);\n          setSelected(clean);\n          setQuery('');\n        }}\n        value={selected}\n        inputValue={query}\n        onInputValueChange={setQuery}\n        onItemHighlighted={(item) => {\n          highlightedItemRef.current = item;\n        }}\n      >\n        <div className={styles.Container}>\n          <label className={styles.Label} htmlFor={id}>\n            Labels\n          </label>\n          <Combobox.InputGroup className={styles.InputGroup}>\n            <Combobox.Chips className={styles.Chips}>\n              <Combobox.Value>\n                {(value: LabelItem[]) => (\n                  <React.Fragment>\n                    {value.map((label) => (\n                      <Combobox.Chip\n                        key={label.id}\n                        className={styles.Chip}\n                        aria-label={label.value}\n                      >\n                        {label.value}\n                        <Combobox.ChipRemove className={styles.ChipRemove} aria-label=\"Remove\">\n                          <XIcon />\n                        </Combobox.ChipRemove>\n                      </Combobox.Chip>\n                    ))}\n                    <Combobox.Input\n                      ref={comboboxInputRef}\n                      id={id}\n                      placeholder={value.length > 0 ? '' : 'e.g. bug'}\n                      className={styles.Input}\n                      onKeyDown={handleInputKeyDown}\n                    />\n                  </React.Fragment>\n                )}\n              </Combobox.Value>\n            </Combobox.Chips>\n          </Combobox.InputGroup>\n        </div>\n\n        <Combobox.Portal>\n          <Combobox.Positioner className={styles.Positioner} sideOffset={4}>\n            <Combobox.Popup className={styles.Popup}>\n              <Combobox.Empty className={styles.Empty}>No labels found.</Combobox.Empty>\n              <Combobox.List>\n                {(item: LabelItem) =>\n                  item.creatable ? (\n                    <Combobox.Item key={item.id} className={styles.Item} value={item}>\n                      <span className={styles.ItemIndicator}>\n                        <PlusIcon className={styles.CreateIcon} />\n                      </span>\n                      <div className={styles.ItemText}>Create \"{item.creatable}\"</div>\n                    </Combobox.Item>\n                  ) : (\n                    <Combobox.Item key={item.id} className={styles.Item} value={item}>\n                      <Combobox.ItemIndicator className={styles.ItemIndicator}>\n                        <CheckIcon className={styles.ItemIndicatorIcon} />\n                      </Combobox.ItemIndicator>\n                      <div className={styles.ItemText}>{item.value}</div>\n                    </Combobox.Item>\n                  )\n                }\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>\n\n      <Dialog.Root open={openDialog} onOpenChange={setOpenDialog}>\n        <Dialog.Portal>\n          <Dialog.Backdrop className={styles.Backdrop} />\n          <Dialog.Popup className={styles.DialogPopup} initialFocus={createInputRef}>\n            <Dialog.Title className={styles.Title}>Create new label</Dialog.Title>\n            <Dialog.Description className={styles.Description}>\n              Add a new label to select.\n            </Dialog.Description>\n            <form onSubmit={handleCreateSubmit}>\n              <input\n                ref={createInputRef}\n                className={styles.TextField}\n                placeholder=\"Label name\"\n                defaultValue={pendingQueryRef.current}\n              />\n              <div className={styles.Actions}>\n                <Dialog.Close className={styles.Button}>Cancel</Dialog.Close>\n                <button type=\"submit\" className={styles.Button}>\n                  Create\n                </button>\n              </div>\n            </form>\n          </Dialog.Popup>\n        </Dialog.Portal>\n      </Dialog.Root>\n    </React.Fragment>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\nfunction PlusIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"12\"\n      height=\"12\"\n      viewBox=\"0 0 12 12\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"butt\"\n      strokeLinejoin=\"miter\"\n      aria-hidden\n      {...props}\n    >\n      <path d=\"M6 1v10M1 6h10\" />\n    </svg>\n  );\n}\n\nfunction XIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width={16}\n      height={16}\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      aria-hidden\n      {...props}\n    >\n      <path d=\"M18 6 6 18\" />\n      <path d=\"m6 6 12 12\" />\n    </svg>\n  );\n}\n\ninterface LabelItem {\n  creatable?: string;\n  id: string;\n  value: string;\n}\n\nconst initialLabels: LabelItem[] = [\n  { id: 'bug', value: 'bug' },\n  { id: 'docs', value: 'documentation' },\n  { id: 'enhancement', value: 'enhancement' },\n  { id: 'help-wanted', value: 'help wanted' },\n  { id: 'good-first-issue', value: 'good first issue' },\n];\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/combobox/demos/creatable/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoComboboxCreatable = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/combobox/demos/creatable/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Combobox } from '@base-ui/react/combobox';\nimport { Dialog } from '@base-ui/react/dialog';\n\nexport default function ExampleCreatableCombobox() {\n  const id = React.useId();\n\n  const [labels, setLabels] = React.useState<LabelItem[]>(initialLabels);\n  const [selected, setSelected] = React.useState<LabelItem[]>([]);\n  const [query, setQuery] = React.useState('');\n  const [openDialog, setOpenDialog] = React.useState(false);\n\n  const createInputRef = React.useRef<HTMLInputElement | null>(null);\n  const comboboxInputRef = React.useRef<HTMLInputElement | null>(null);\n  const pendingQueryRef = React.useRef('');\n  const highlightedItemRef = React.useRef<LabelItem | undefined>(undefined);\n\n  function handleInputKeyDown(event: React.KeyboardEvent<HTMLInputElement>) {\n    if (event.key !== 'Enter' || highlightedItemRef.current) {\n      return;\n    }\n\n    const currentTrimmed = query.trim();\n    if (currentTrimmed === '') {\n      return;\n    }\n\n    const normalized = currentTrimmed.toLocaleLowerCase();\n    const existing = labels.find((label) => label.value.trim().toLocaleLowerCase() === normalized);\n\n    if (existing) {\n      setSelected((prev) =>\n        prev.some((item) => item.id === existing.id) ? prev : [...prev, existing],\n      );\n      setQuery('');\n      return;\n    }\n\n    pendingQueryRef.current = currentTrimmed;\n    setOpenDialog(true);\n  }\n\n  function handleCreate() {\n    const input = createInputRef.current || comboboxInputRef.current;\n    const value = input ? input.value.trim() : '';\n    if (!value) {\n      return;\n    }\n\n    const normalized = value.toLocaleLowerCase();\n    const baseId = normalized.replace(/\\s+/g, '-');\n    const existing = labels.find((l) => l.value.trim().toLocaleLowerCase() === normalized);\n\n    if (existing) {\n      setSelected((prev) => (prev.some((i) => i.id === existing.id) ? prev : [...prev, existing]));\n      setOpenDialog(false);\n      setQuery('');\n      return;\n    }\n\n    // Ensure we don't collide with an existing id (e.g., value \"docs\" vs. existing id \"docs\")\n    const existingIds = new Set(labels.map((l) => l.id));\n    let uniqueId = baseId;\n    if (existingIds.has(uniqueId)) {\n      let i = 2;\n      while (existingIds.has(`${baseId}-${i}`)) {\n        i += 1;\n      }\n      uniqueId = `${baseId}-${i}`;\n    }\n\n    const newItem: LabelItem = { id: uniqueId, value };\n\n    if (!selected.find((item) => item.id === newItem.id)) {\n      setLabels((prev) => [...prev, newItem]);\n      setSelected((prev) => [...prev, newItem]);\n    }\n\n    setOpenDialog(false);\n    setQuery('');\n  }\n\n  function handleCreateSubmit(event: React.FormEvent<HTMLFormElement>) {\n    event.preventDefault();\n    handleCreate();\n  }\n\n  const trimmed = query.trim();\n  const lowered = trimmed.toLocaleLowerCase();\n  const exactExists = labels.some((l) => l.value.trim().toLocaleLowerCase() === lowered);\n  // Show the creatable item alongside matches if there's no exact match\n  const itemsForView: Array<LabelItem> =\n    trimmed !== '' && !exactExists\n      ? [...labels, { creatable: trimmed, id: `create:${lowered}`, value: `Create \"${trimmed}\"` }]\n      : labels;\n\n  return (\n    <React.Fragment>\n      <Combobox.Root\n        items={itemsForView}\n        multiple\n        onValueChange={(next) => {\n          const creatableSelection = next.find(\n            (item) => item.creatable && !selected.some((current) => current.id === item.id),\n          );\n\n          if (creatableSelection && creatableSelection.creatable) {\n            pendingQueryRef.current = creatableSelection.creatable;\n            setOpenDialog(true);\n            return;\n          }\n          const clean = next.filter((i) => !i.creatable);\n          setSelected(clean);\n          setQuery('');\n        }}\n        value={selected}\n        inputValue={query}\n        onInputValueChange={setQuery}\n        onItemHighlighted={(item) => {\n          highlightedItemRef.current = item;\n        }}\n      >\n        <div className=\"max-w-112 flex flex-col gap-1\">\n          <label className=\"text-sm leading-5 font-bold text-gray-900\" htmlFor={id}>\n            Labels\n          </label>\n          <Combobox.InputGroup className=\"w-64 cursor-text rounded-md border border-gray-200 bg-[canvas] px-1.5 py-1 focus-within:outline-2 focus-within:-outline-offset-1 focus-within:outline-blue-800 min-[500px]:w-[22rem]\">\n            <Combobox.Chips className=\"flex w-full flex-wrap items-center gap-0.5\">\n              <Combobox.Value>\n                {(value: LabelItem[]) => (\n                  <React.Fragment>\n                    {value.map((label) => (\n                      <Combobox.Chip\n                        key={label.id}\n                        className=\"flex items-center gap-1 rounded-md bg-gray-100 px-1.5 py-[0.2rem] text-sm text-gray-900 outline-none cursor-default [@media(hover:hover)]:[&[data-highlighted]]:bg-blue-800 [@media(hover:hover)]:[&[data-highlighted]]:text-gray-50 focus-within:bg-blue-800 focus-within:text-gray-50\"\n                        aria-label={label.value}\n                      >\n                        {label.value}\n                        <Combobox.ChipRemove\n                          className=\"rounded-md p-1 text-inherit hover:bg-gray-200\"\n                          aria-label=\"Remove\"\n                        >\n                          <XIcon />\n                        </Combobox.ChipRemove>\n                      </Combobox.Chip>\n                    ))}\n                    <Combobox.Input\n                      ref={comboboxInputRef}\n                      id={id}\n                      placeholder={value.length > 0 ? '' : 'e.g. bug'}\n                      className=\"min-w-12 flex-1 h-8 rounded-md border-0 bg-transparent pl-2 text-base font-normal text-gray-900 outline-none\"\n                      onKeyDown={handleInputKeyDown}\n                    />\n                  </React.Fragment>\n                )}\n              </Combobox.Value>\n            </Combobox.Chips>\n          </Combobox.InputGroup>\n        </div>\n\n        <Combobox.Portal>\n          <Combobox.Positioner className=\"z-50 outline-none\" sideOffset={4}>\n            <Combobox.Popup className=\"w-[var(--anchor-width)] max-h-[min(var(--available-height),24rem)] max-w-[var(--available-width)] overflow-y-auto scroll-pt-2 scroll-pb-2 overscroll-contain rounded-lg bg-[canvas] py-2 text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300\">\n              <Combobox.Empty className=\"px-4 py-2 text-[0.925rem] leading-4 text-gray-600 empty:m-0 empty:p-0\">\n                No labels found.\n              </Combobox.Empty>\n              <Combobox.List>\n                {(item: LabelItem) =>\n                  item.creatable ? (\n                    <Combobox.Item\n                      key={item.id}\n                      className=\"grid cursor-default grid-cols-[0.75rem_1fr] items-center gap-2 py-2 pr-8 pl-4 text-base leading-4 outline-none select-none [@media(hover:hover)]:[&[data-highlighted]]:relative [@media(hover:hover)]:[&[data-highlighted]]:z-0 [@media(hover:hover)]:[&[data-highlighted]]:text-gray-50 [@media(hover:hover)]:[&[data-highlighted]]:before:absolute [@media(hover:hover)]:[&[data-highlighted]]:before:inset-x-2 [@media(hover:hover)]:[&[data-highlighted]]:before:inset-y-0 [@media(hover:hover)]:[&[data-highlighted]]:before:z-[-1] [@media(hover:hover)]:[&[data-highlighted]]:before:rounded-sm [@media(hover:hover)]:[&[data-highlighted]]:before:bg-gray-900\"\n                      value={item}\n                    >\n                      <span className=\"col-start-1\">\n                        <PlusIcon className=\"size-3\" />\n                      </span>\n                      <div className=\"col-start-2\">Create \"{item.creatable}\"</div>\n                    </Combobox.Item>\n                  ) : (\n                    <Combobox.Item\n                      key={item.id}\n                      className=\"grid cursor-default grid-cols-[0.75rem_1fr] items-center gap-2 py-2 pr-8 pl-4 text-base leading-4 outline-none select-none [@media(hover:hover)]:[&[data-highlighted]]:relative [@media(hover:hover)]:[&[data-highlighted]]:z-0 [@media(hover:hover)]:[&[data-highlighted]]:text-gray-50 [@media(hover:hover)]:[&[data-highlighted]]:before:absolute [@media(hover:hover)]:[&[data-highlighted]]:before:inset-x-2 [@media(hover:hover)]:[&[data-highlighted]]:before:inset-y-0 [@media(hover:hover)]:[&[data-highlighted]]:before:z-[-1] [@media(hover:hover)]:[&[data-highlighted]]:before:rounded-sm [@media(hover:hover)]:[&[data-highlighted]]:before:bg-gray-900\"\n                      value={item}\n                    >\n                      <Combobox.ItemIndicator className=\"col-start-1\">\n                        <CheckIcon className=\"size-3\" />\n                      </Combobox.ItemIndicator>\n                      <div className=\"col-start-2\">{item.value}</div>\n                    </Combobox.Item>\n                  )\n                }\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>\n\n      <Dialog.Root open={openDialog} onOpenChange={setOpenDialog}>\n        <Dialog.Portal>\n          <Dialog.Backdrop className=\"fixed inset-0 min-h-dvh bg-black opacity-20 transition-opacity dark:opacity-70 data-[starting-style]:opacity-0 data-[ending-style]:opacity-0 supports-[-webkit-touch-callout:none]:absolute\" />\n          <Dialog.Popup\n            className=\"fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 mt-[-2rem] w-[24rem] max-w-[calc(100vw-3rem)] rounded-lg bg-[canvas] p-6 text-gray-900 outline-1 outline-gray-200 transition-all data-[starting-style]:opacity-0 data-[starting-style]:scale-90 data-[ending-style]:opacity-0 data-[ending-style]:scale-90 dark:-outline-offset-1 dark:outline-gray-300\"\n            initialFocus={createInputRef}\n          >\n            <Dialog.Title className=\"-mt-1.5 mb-1 text-lg leading-7 tracking-[-0.0025em] font-bold\">\n              Create new label\n            </Dialog.Title>\n            <Dialog.Description className=\"mb-4 text-base leading-6 text-gray-600\">\n              Add a new label to select.\n            </Dialog.Description>\n            <form onSubmit={handleCreateSubmit}>\n              <input\n                ref={createInputRef}\n                className=\"w-full h-10 rounded-md border border-gray-200 bg-[canvas] text-gray-900 px-2.5 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 font-normal\"\n                placeholder=\"Label name\"\n                defaultValue={pendingQueryRef.current}\n              />\n              <div className=\"mt-4 flex justify-end gap-4\">\n                <Dialog.Close className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base text-gray-900 select-none hover:bg-gray-100 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100 font-normal\">\n                  Cancel\n                </Dialog.Close>\n                <button\n                  type=\"submit\"\n                  className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\"\n                >\n                  Create\n                </button>\n              </div>\n            </form>\n          </Dialog.Popup>\n        </Dialog.Portal>\n      </Dialog.Root>\n    </React.Fragment>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\nfunction PlusIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"12\"\n      height=\"12\"\n      viewBox=\"0 0 12 12\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"butt\"\n      strokeLinejoin=\"miter\"\n      aria-hidden\n      {...props}\n    >\n      <path d=\"M6 1v10M1 6h10\" />\n    </svg>\n  );\n}\n\nfunction XIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width={16}\n      height={16}\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      aria-hidden\n      {...props}\n    >\n      <path d=\"M18 6 6 18\" />\n      <path d=\"m6 6 12 12\" />\n    </svg>\n  );\n}\n\ninterface LabelItem {\n  creatable?: string;\n  id: string;\n  value: string;\n}\n\nconst initialLabels: LabelItem[] = [\n  { id: 'bug', value: 'bug' },\n  { id: 'docs', value: 'documentation' },\n  { id: 'enhancement', value: 'enhancement' },\n  { id: 'help-wanted', value: 'help wanted' },\n  { id: 'good-first-issue', value: 'good first issue' },\n];\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/combobox/demos/grouped/css-modules/index.module.css",
    "content": ".InputGroup {\n  position: relative;\n  width: 16rem;\n  height: 2.5rem;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: canvas;\n\n  &:focus-within {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.InputGroup:has(.Clear) .Input {\n  padding-right: calc(0.5rem + 1.5rem * 2);\n}\n\n.Input {\n  box-sizing: border-box;\n  padding-left: 0.875rem;\n  padding-right: calc(0.5rem + 1.5rem);\n  margin: 0;\n  border: none;\n  width: 100%;\n  height: 100%;\n  border-radius: inherit;\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  background-color: transparent;\n  color: var(--color-gray-900);\n  outline: none;\n\n  &:focus {\n    outline: none;\n  }\n}\n\n.Label {\n  display: flex;\n  flex-direction: column;\n  gap: 0.25rem;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 700;\n  color: var(--color-gray-900);\n  position: relative;\n}\n\n.ActionButtons {\n  --size: 1.5rem;\n  box-sizing: border-box;\n  position: absolute;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  bottom: 0;\n  height: 2.5rem;\n  right: 0.5rem;\n  border-radius: 0.25rem;\n  border: none;\n  color: var(--color-gray-600);\n  padding: 0;\n}\n\n.Trigger,\n.Clear {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: var(--size);\n  height: 2.5rem;\n  color: var(--color-gray-600);\n  border: none;\n  padding: 0;\n  border-radius: 0.25rem;\n  background: none;\n}\n\n.ClearIcon,\n.TriggerIcon {\n  width: 1rem;\n  height: 1rem;\n}\n\n.Positioner {\n  outline: 0;\n}\n\n.Popup {\n  box-sizing: border-box;\n  border-radius: 0.375rem;\n  overflow: hidden;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  width: var(--anchor-width);\n  max-height: 23rem;\n  max-width: var(--available-width);\n  transition:\n    opacity 0.1s,\n    transform 0.1s;\n  transform-origin: var(--transform-origin);\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.95);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n  }\n}\n\n.List {\n  box-sizing: border-box;\n  overflow: auto;\n  overscroll-behavior: contain;\n  scroll-padding-top: 2.25rem;\n  scroll-padding-bottom: 0.5rem;\n  max-height: min(23rem, var(--available-height));\n  outline: 0;\n}\n\n.Group {\n  display: block;\n  padding-bottom: 0.5rem;\n}\n\n.GroupLabel {\n  box-sizing: border-box;\n  padding: 0.75rem 1rem 0.25rem calc(1rem + 0.75rem + 0.5rem);\n  font-size: 0.6875rem;\n  font-weight: 700;\n  text-transform: uppercase;\n  letter-spacing: 0.05em;\n  background-color: canvas;\n  position: sticky;\n  z-index: 1;\n  top: 0;\n  margin: 0 0.5rem 0 0;\n  width: calc(100% - 0.5rem);\n}\n\n.Item {\n  box-sizing: border-box;\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  padding: 0.5rem 2rem 0.5rem 1rem;\n  display: grid;\n  grid-template-columns: 0.75rem 1fr;\n  gap: 0.5rem;\n  align-items: center;\n  font-size: 1rem;\n  line-height: 1rem;\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.5rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.ItemIndicator {\n  grid-column-start: 1;\n}\n\n.ItemIndicatorIcon {\n  display: block;\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.ItemText {\n  grid-column-start: 2;\n}\n\n.Empty:not(:empty) {\n  box-sizing: border-box;\n  padding: 1rem;\n  font-size: 0.925rem;\n  line-height: 1rem;\n  color: var(--color-gray-600);\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/combobox/demos/grouped/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Combobox } from '@base-ui/react/combobox';\nimport styles from './index.module.css';\n\nexport default function ExampleGroupedCombobox() {\n  const id = React.useId();\n  return (\n    <Combobox.Root items={groupedProduce}>\n      <div className={styles.Label}>\n        <label htmlFor={id}>Select produce</label>\n        <Combobox.InputGroup className={styles.InputGroup}>\n          <Combobox.Input placeholder=\"e.g. Mango\" className={styles.Input} id={id} />\n          <div className={styles.ActionButtons}>\n            <Combobox.Clear className={styles.Clear} aria-label=\"Clear selection\">\n              <ClearIcon className={styles.ClearIcon} />\n            </Combobox.Clear>\n            <Combobox.Trigger className={styles.Trigger} aria-label=\"Open popup\">\n              <ChevronDownIcon className={styles.TriggerIcon} />\n            </Combobox.Trigger>\n          </div>\n        </Combobox.InputGroup>\n      </div>\n\n      <Combobox.Portal>\n        <Combobox.Positioner className={styles.Positioner} sideOffset={4}>\n          <Combobox.Popup className={styles.Popup}>\n            <Combobox.Empty className={styles.Empty}>No produce found.</Combobox.Empty>\n            <Combobox.List className={styles.List}>\n              {(group: ProduceGroup) => (\n                <Combobox.Group key={group.value} items={group.items} className={styles.Group}>\n                  <Combobox.GroupLabel className={styles.GroupLabel}>\n                    {group.value}\n                  </Combobox.GroupLabel>\n                  <Combobox.Collection>\n                    {(item: Produce) => (\n                      <Combobox.Item key={item.id} className={styles.Item} value={item}>\n                        <Combobox.ItemIndicator className={styles.ItemIndicator}>\n                          <CheckIcon className={styles.ItemIndicatorIcon} />\n                        </Combobox.ItemIndicator>\n                        <div className={styles.ItemText}>{item.label}</div>\n                      </Combobox.Item>\n                    )}\n                  </Combobox.Collection>\n                </Combobox.Group>\n              )}\n            </Combobox.List>\n          </Combobox.Popup>\n        </Combobox.Positioner>\n      </Combobox.Portal>\n    </Combobox.Root>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\nfunction ClearIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M18 6L6 18\" />\n      <path d=\"M6 6l12 12\" />\n    </svg>\n  );\n}\n\nfunction ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M6 9l6 6 6-6\" />\n    </svg>\n  );\n}\n\ninterface Produce {\n  id: string;\n  label: string;\n  group: 'Fruits' | 'Vegetables';\n}\n\ninterface ProduceGroup {\n  value: string;\n  items: Produce[];\n}\n\nconst produceData: Produce[] = [\n  { id: 'fruit-apple', label: 'Apple', group: 'Fruits' },\n  { id: 'fruit-banana', label: 'Banana', group: 'Fruits' },\n  { id: 'fruit-mango', label: 'Mango', group: 'Fruits' },\n  { id: 'fruit-kiwi', label: 'Kiwi', group: 'Fruits' },\n  { id: 'fruit-grape', label: 'Grape', group: 'Fruits' },\n  { id: 'fruit-orange', label: 'Orange', group: 'Fruits' },\n  { id: 'fruit-strawberry', label: 'Strawberry', group: 'Fruits' },\n  { id: 'fruit-watermelon', label: 'Watermelon', group: 'Fruits' },\n  { id: 'veg-broccoli', label: 'Broccoli', group: 'Vegetables' },\n  { id: 'veg-carrot', label: 'Carrot', group: 'Vegetables' },\n  { id: 'veg-cauliflower', label: 'Cauliflower', group: 'Vegetables' },\n  { id: 'veg-cucumber', label: 'Cucumber', group: 'Vegetables' },\n  { id: 'veg-kale', label: 'Kale', group: 'Vegetables' },\n  { id: 'veg-pepper', label: 'Bell pepper', group: 'Vegetables' },\n  { id: 'veg-spinach', label: 'Spinach', group: 'Vegetables' },\n  { id: 'veg-zucchini', label: 'Zucchini', group: 'Vegetables' },\n];\n\nfunction groupProduce(items: Produce[]): ProduceGroup[] {\n  const groups: Record<string, Produce[]> = {};\n  items.forEach((item) => {\n    (groups[item.group] ??= []).push(item);\n  });\n  const order = ['Fruits', 'Vegetables'];\n  return order.map((value) => ({ value, items: groups[value] ?? [] }));\n}\n\nconst groupedProduce: ProduceGroup[] = groupProduce(produceData);\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/combobox/demos/grouped/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoComboboxGrouped = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/combobox/demos/grouped/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Combobox } from '@base-ui/react/combobox';\n\nexport default function ExampleGroupedCombobox() {\n  const id = React.useId();\n  return (\n    <Combobox.Root items={groupedProduce}>\n      <div className=\"relative flex flex-col gap-1 text-sm leading-5 font-bold text-gray-900\">\n        <label htmlFor={id}>Select produce</label>\n        <Combobox.InputGroup className=\"relative box-content h-10 w-[16rem] rounded-md border border-gray-200 bg-[canvas] focus-within:outline-2 focus-within:-outline-offset-1 focus-within:outline-blue-800 [&>input]:pr-[calc(0.5rem+1.5rem)] has-[.combobox-clear]:[&>input]:pr-[calc(0.5rem+1.5rem*2)]\">\n          <Combobox.Input\n            placeholder=\"e.g. Mango\"\n            id={id}\n            className=\"h-full w-full border-0 bg-transparent pl-3.5 text-base font-normal text-gray-900 outline-none\"\n          />\n          <div className=\"absolute right-2 bottom-0 flex h-10 items-center justify-center text-gray-600\">\n            <Combobox.Clear\n              className=\"combobox-clear flex h-10 w-6 items-center justify-center rounded bg-transparent p-0\"\n              aria-label=\"Clear selection\"\n            >\n              <ClearIcon className=\"size-4\" />\n            </Combobox.Clear>\n            <Combobox.Trigger\n              className=\"flex h-10 w-6 items-center justify-center rounded bg-transparent p-0\"\n              aria-label=\"Open popup\"\n            >\n              <ChevronDownIcon className=\"size-4\" />\n            </Combobox.Trigger>\n          </div>\n        </Combobox.InputGroup>\n      </div>\n\n      <Combobox.Portal>\n        <Combobox.Positioner className=\"outline-none\" sideOffset={4}>\n          <Combobox.Popup className=\"w-[var(--anchor-width)] max-h-[23rem] max-w-[var(--available-width)] origin-[var(--transform-origin)] overflow-hidden rounded-md bg-[canvas] pt-0 text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 transition-[transform,scale,opacity] data-[ending-style]:scale-95 data-[ending-style]:opacity-0 data-[starting-style]:scale-95 data-[starting-style]:opacity-0 dark:shadow-none dark:outline-gray-300 duration-100\">\n            <Combobox.Empty className=\"p-4 text-[0.925rem] leading-4 text-gray-600 empty:m-0 empty:p-0\">\n              No produce found.\n            </Combobox.Empty>\n            <Combobox.List className=\"outline-0 overflow-y-auto scroll-pt-[2.25rem] scroll-pb-[0.5rem] overscroll-contain max-h-[min(23rem,var(--available-height))]\">\n              {(group: ProduceGroup) => (\n                <Combobox.Group key={group.value} items={group.items} className=\"block pb-2\">\n                  <Combobox.GroupLabel className=\"sticky top-0 z-[1] mb-0 mr-2 mt-0 ml-0 w-[calc(100%-0.5rem)] bg-[canvas] pr-4 pl-[2.25rem] pt-3 pb-1 text-[0.7rem] font-bold uppercase tracking-wider\">\n                    {group.value}\n                  </Combobox.GroupLabel>\n                  <Combobox.Collection>\n                    {(item: Produce) => (\n                      <Combobox.Item\n                        key={item.id}\n                        className=\"grid cursor-default grid-cols-[0.75rem_1fr] items-center gap-2 py-2 pr-8 pl-4 text-base leading-4 outline-none select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-2 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-sm data-[highlighted]:before:bg-gray-900\"\n                        value={item}\n                      >\n                        <Combobox.ItemIndicator className=\"col-start-1 flex items-center justify-center\">\n                          <CheckIcon className=\"size-3\" />\n                        </Combobox.ItemIndicator>\n                        <div className=\"col-start-2\">{item.label}</div>\n                      </Combobox.Item>\n                    )}\n                  </Combobox.Collection>\n                </Combobox.Group>\n              )}\n            </Combobox.List>\n          </Combobox.Popup>\n        </Combobox.Positioner>\n      </Combobox.Portal>\n    </Combobox.Root>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\nfunction ClearIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M18 6L6 18\" />\n      <path d=\"M6 6l12 12\" />\n    </svg>\n  );\n}\n\nfunction ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M6 9l6 6 6-6\" />\n    </svg>\n  );\n}\n\ninterface Produce {\n  id: string;\n  label: string;\n  group: 'Fruits' | 'Vegetables';\n}\n\ninterface ProduceGroup {\n  value: string;\n  items: Produce[];\n}\n\nconst produceData: Produce[] = [\n  { id: 'fruit-apple', label: 'Apple', group: 'Fruits' },\n  { id: 'fruit-banana', label: 'Banana', group: 'Fruits' },\n  { id: 'fruit-mango', label: 'Mango', group: 'Fruits' },\n  { id: 'fruit-kiwi', label: 'Kiwi', group: 'Fruits' },\n  { id: 'fruit-grape', label: 'Grape', group: 'Fruits' },\n  { id: 'fruit-orange', label: 'Orange', group: 'Fruits' },\n  { id: 'fruit-strawberry', label: 'Strawberry', group: 'Fruits' },\n  { id: 'fruit-watermelon', label: 'Watermelon', group: 'Fruits' },\n  { id: 'veg-broccoli', label: 'Broccoli', group: 'Vegetables' },\n  { id: 'veg-carrot', label: 'Carrot', group: 'Vegetables' },\n  { id: 'veg-cauliflower', label: 'Cauliflower', group: 'Vegetables' },\n  { id: 'veg-cucumber', label: 'Cucumber', group: 'Vegetables' },\n  { id: 'veg-kale', label: 'Kale', group: 'Vegetables' },\n  { id: 'veg-pepper', label: 'Bell pepper', group: 'Vegetables' },\n  { id: 'veg-spinach', label: 'Spinach', group: 'Vegetables' },\n  { id: 'veg-zucchini', label: 'Zucchini', group: 'Vegetables' },\n];\n\nfunction groupProduce(items: Produce[]): ProduceGroup[] {\n  const groups: Record<string, Produce[]> = {};\n  items.forEach((item) => {\n    (groups[item.group] ??= []).push(item);\n  });\n  const order = ['Fruits', 'Vegetables'];\n  return order.map((value) => ({ value, items: groups[value] ?? [] }));\n}\n\nconst groupedProduce: ProduceGroup[] = groupProduce(produceData);\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/combobox/demos/hero/css-modules/index.module.css",
    "content": ".InputGroup {\n  position: relative;\n  width: 16rem;\n  height: 2.5rem;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: canvas;\n\n  &:focus-within {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n\n  &:has(.Clear) .Input {\n    padding-right: calc(0.5rem + 1.5rem * 2);\n  }\n}\n\n.Input {\n  box-sizing: border-box;\n  padding-left: 0.875rem;\n  padding-right: calc(0.5rem + 1.5rem);\n  margin: 0;\n  border: none;\n  width: 100%;\n  height: 100%;\n  border-radius: inherit;\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  background-color: transparent;\n  color: var(--color-gray-900);\n\n  &:focus {\n    outline: none;\n  }\n}\n\n.Label {\n  display: flex;\n  flex-direction: column;\n  gap: 0.25rem;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 700;\n  color: var(--color-gray-900);\n  position: relative;\n}\n\n.ActionButtons {\n  box-sizing: border-box;\n  position: absolute;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  bottom: 0;\n  height: 2.5rem;\n  right: 0.5rem;\n  border-radius: 0.25rem;\n  border: none;\n  color: var(--color-gray-600);\n  padding: 0;\n}\n\n.Trigger,\n.Clear {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 1.5rem;\n  height: 2.5rem;\n  color: var(--color-gray-600);\n  border: none;\n  padding: 0;\n  border-radius: 0.25rem;\n  background: none;\n}\n\n.ClearIcon,\n.TriggerIcon {\n  width: 1rem;\n  height: 1rem;\n}\n\n.Positioner {\n  outline: 0;\n}\n\n.Popup {\n  box-sizing: border-box;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  width: var(--anchor-width);\n  max-height: 23rem;\n  max-width: var(--available-width);\n  transition:\n    opacity 0.1s,\n    transform 0.1s;\n  transform-origin: var(--transform-origin);\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.95);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.List {\n  box-sizing: border-box;\n  overflow-y: auto;\n  overscroll-behavior: contain;\n  padding-block: 0.5rem;\n  scroll-padding-block: 0.5rem;\n  outline: 0;\n  max-height: min(23rem, var(--available-height));\n\n  &[data-empty] {\n    padding: 0;\n  }\n}\n\n.Item {\n  box-sizing: border-box;\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  padding-block: 0.5rem;\n  padding-left: 1rem;\n  padding-right: 2rem;\n  display: grid;\n  gap: 0.5rem;\n  align-items: center;\n  grid-template-columns: 0.75rem 1fr;\n  font-size: 1rem;\n  line-height: 1rem;\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.5rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.ItemText {\n  grid-column-start: 2;\n}\n\n.ItemIndicator {\n  grid-column-start: 1;\n}\n\n.ItemIndicatorIcon {\n  display: block;\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.Empty:not(:empty) {\n  font-size: 0.925rem;\n  line-height: 1rem;\n  color: var(--color-gray-600);\n  padding: 1rem;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/combobox/demos/hero/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Combobox } from '@base-ui/react/combobox';\nimport styles from './index.module.css';\n\nexport default function ExampleCombobox() {\n  const id = React.useId();\n  return (\n    <Combobox.Root items={fruits}>\n      <div className={styles.Label}>\n        <label htmlFor={id}>Choose a fruit</label>\n        <Combobox.InputGroup className={styles.InputGroup}>\n          <Combobox.Input placeholder=\"e.g. Apple\" id={id} className={styles.Input} />\n          <div className={styles.ActionButtons}>\n            <Combobox.Clear className={styles.Clear} aria-label=\"Clear selection\">\n              <ClearIcon className={styles.ClearIcon} />\n            </Combobox.Clear>\n            <Combobox.Trigger className={styles.Trigger} aria-label=\"Open popup\">\n              <ChevronDownIcon className={styles.TriggerIcon} />\n            </Combobox.Trigger>\n          </div>\n        </Combobox.InputGroup>\n      </div>\n\n      <Combobox.Portal>\n        <Combobox.Positioner className={styles.Positioner} sideOffset={4}>\n          <Combobox.Popup className={styles.Popup}>\n            <Combobox.Empty className={styles.Empty}>No fruits found.</Combobox.Empty>\n            <Combobox.List className={styles.List}>\n              {(item: Fruit) => (\n                <Combobox.Item key={item.value} value={item} className={styles.Item}>\n                  <Combobox.ItemIndicator className={styles.ItemIndicator}>\n                    <CheckIcon className={styles.ItemIndicatorIcon} />\n                  </Combobox.ItemIndicator>\n                  <div className={styles.ItemText}>{item.label}</div>\n                </Combobox.Item>\n              )}\n            </Combobox.List>\n          </Combobox.Popup>\n        </Combobox.Positioner>\n      </Combobox.Portal>\n    </Combobox.Root>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\nfunction ClearIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M18 6L6 18\" />\n      <path d=\"M6 6l12 12\" />\n    </svg>\n  );\n}\n\nfunction ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M6 9l6 6 6-6\" />\n    </svg>\n  );\n}\n\ninterface Fruit {\n  label: string;\n  value: string;\n}\n\nconst fruits: Fruit[] = [\n  { label: 'Apple', value: 'apple' },\n  { label: 'Banana', value: 'banana' },\n  { label: 'Orange', value: 'orange' },\n  { label: 'Pineapple', value: 'pineapple' },\n  { label: 'Grape', value: 'grape' },\n  { label: 'Mango', value: 'mango' },\n  { label: 'Strawberry', value: 'strawberry' },\n  { label: 'Blueberry', value: 'blueberry' },\n  { label: 'Raspberry', value: 'raspberry' },\n  { label: 'Blackberry', value: 'blackberry' },\n  { label: 'Cherry', value: 'cherry' },\n  { label: 'Peach', value: 'peach' },\n  { label: 'Pear', value: 'pear' },\n  { label: 'Plum', value: 'plum' },\n  { label: 'Kiwi', value: 'kiwi' },\n  { label: 'Watermelon', value: 'watermelon' },\n  { label: 'Cantaloupe', value: 'cantaloupe' },\n  { label: 'Honeydew', value: 'honeydew' },\n  { label: 'Papaya', value: 'papaya' },\n  { label: 'Guava', value: 'guava' },\n  { label: 'Lychee', value: 'lychee' },\n  { label: 'Pomegranate', value: 'pomegranate' },\n  { label: 'Apricot', value: 'apricot' },\n  { label: 'Grapefruit', value: 'grapefruit' },\n  { label: 'Passionfruit', value: 'passionfruit' },\n];\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/combobox/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoComboboxHero = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/combobox/demos/hero/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Combobox } from '@base-ui/react/combobox';\n\nexport default function ExampleCombobox() {\n  const id = React.useId();\n  return (\n    <Combobox.Root items={fruits}>\n      <div className=\"relative flex flex-col gap-1 text-sm leading-5 font-bold text-gray-900\">\n        <label htmlFor={id}>Choose a fruit</label>\n        <Combobox.InputGroup className=\"relative box-content h-10 w-64 rounded-md border border-gray-200 bg-[canvas] focus-within:outline-2 focus-within:-outline-offset-1 focus-within:outline-blue-800 [&>input]:pr-[calc(0.5rem+1.5rem)] has-[.combobox-clear]:[&>input]:pr-[calc(0.5rem+1.5rem*2)]\">\n          <Combobox.Input\n            placeholder=\"e.g. Apple\"\n            id={id}\n            className=\"h-full w-full border-0 bg-transparent pl-3.5 text-base font-normal text-gray-900 outline-none\"\n          />\n          <div className=\"absolute right-2 bottom-0 flex h-10 items-center justify-center text-gray-600\">\n            <Combobox.Clear\n              className=\"combobox-clear flex h-10 w-6 items-center justify-center rounded bg-transparent p-0\"\n              aria-label=\"Clear selection\"\n            >\n              <ClearIcon className=\"size-4\" />\n            </Combobox.Clear>\n            <Combobox.Trigger\n              className=\"flex h-10 w-6 items-center justify-center rounded bg-transparent p-0\"\n              aria-label=\"Open popup\"\n            >\n              <ChevronDownIcon className=\"size-4\" />\n            </Combobox.Trigger>\n          </div>\n        </Combobox.InputGroup>\n      </div>\n\n      <Combobox.Portal>\n        <Combobox.Positioner className=\"outline-none\" sideOffset={4}>\n          <Combobox.Popup className=\"w-[var(--anchor-width)] max-h-[23rem] max-w-[var(--available-width)] origin-[var(--transform-origin)] rounded-md bg-[canvas] text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 transition-[transform,scale,opacity] data-[ending-style]:scale-95 data-[ending-style]:opacity-0 data-[starting-style]:scale-95 data-[starting-style]:opacity-0 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300 duration-100\">\n            <Combobox.Empty className=\"p-4 text-[0.925rem] leading-4 text-gray-600 empty:m-0 empty:p-0\">\n              No fruits found.\n            </Combobox.Empty>\n            <Combobox.List className=\"outline-0 overflow-y-auto scroll-py-[0.5rem] py-2 overscroll-contain max-h-[min(23rem,var(--available-height))] data-[empty]:p-0\">\n              {(item: Fruit) => (\n                <Combobox.Item\n                  key={item.value}\n                  value={item}\n                  className=\"grid cursor-default grid-cols-[0.75rem_1fr] items-center gap-2 py-2 pr-8 pl-4 text-base leading-4 outline-none select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-2 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-sm data-[highlighted]:before:bg-gray-900\"\n                >\n                  <Combobox.ItemIndicator className=\"col-start-1\">\n                    <CheckIcon className=\"size-3\" />\n                  </Combobox.ItemIndicator>\n                  <div className=\"col-start-2\">{item.label}</div>\n                </Combobox.Item>\n              )}\n            </Combobox.List>\n          </Combobox.Popup>\n        </Combobox.Positioner>\n      </Combobox.Portal>\n    </Combobox.Root>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\nfunction ClearIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M18 6L6 18\" />\n      <path d=\"M6 6l12 12\" />\n    </svg>\n  );\n}\n\nfunction ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M6 9l6 6 6-6\" />\n    </svg>\n  );\n}\n\ninterface Fruit {\n  label: string;\n  value: string;\n}\n\nconst fruits: Fruit[] = [\n  { label: 'Apple', value: 'apple' },\n  { label: 'Banana', value: 'banana' },\n  { label: 'Orange', value: 'orange' },\n  { label: 'Pineapple', value: 'pineapple' },\n  { label: 'Grape', value: 'grape' },\n  { label: 'Mango', value: 'mango' },\n  { label: 'Strawberry', value: 'strawberry' },\n  { label: 'Blueberry', value: 'blueberry' },\n  { label: 'Raspberry', value: 'raspberry' },\n  { label: 'Blackberry', value: 'blackberry' },\n  { label: 'Cherry', value: 'cherry' },\n  { label: 'Peach', value: 'peach' },\n  { label: 'Pear', value: 'pear' },\n  { label: 'Plum', value: 'plum' },\n  { label: 'Kiwi', value: 'kiwi' },\n  { label: 'Watermelon', value: 'watermelon' },\n  { label: 'Cantaloupe', value: 'cantaloupe' },\n  { label: 'Honeydew', value: 'honeydew' },\n  { label: 'Papaya', value: 'papaya' },\n  { label: 'Guava', value: 'guava' },\n  { label: 'Lychee', value: 'lychee' },\n  { label: 'Pomegranate', value: 'pomegranate' },\n  { label: 'Apricot', value: 'apricot' },\n  { label: 'Grapefruit', value: 'grapefruit' },\n  { label: 'Passionfruit', value: 'passionfruit' },\n];\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/combobox/demos/input-inside-popup/css-modules/index.module.css",
    "content": ".Field {\n  display: flex;\n  flex-direction: column;\n  align-items: start;\n  gap: 0.25rem;\n}\n\n.Trigger {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 0.75rem;\n  height: 2.5rem;\n  padding-left: 0.875rem;\n  padding-right: 0.75rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  font-weight: 400;\n  color: var(--color-gray-900);\n  cursor: default;\n  background-color: canvas;\n  -webkit-user-select: none;\n  user-select: none;\n  min-width: 12rem;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.TriggerIcon {\n  display: flex;\n}\n\n.Placeholder {\n  opacity: 0.6;\n}\n\n.InputContainer {\n  box-sizing: border-box;\n  width: 20rem;\n  height: var(--input-container-height);\n  text-align: center;\n  padding: 0.5rem;\n}\n\n.Input {\n  box-sizing: border-box;\n  padding-left: 0.875rem;\n  margin: 0;\n  border: 1px solid var(--color-gray-300);\n  min-width: 18rem;\n  width: 100%;\n  height: 2.5rem;\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  outline: none;\n\n  &:focus {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Label {\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 700;\n  color: var(--color-gray-900);\n  cursor: default;\n}\n\n.Positioner {\n  outline: 0;\n}\n\n.Popup {\n  --input-container-height: 3rem;\n  box-sizing: border-box;\n  border-radius: 0.5rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n  max-width: var(--available-width);\n  max-height: 24rem;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.List {\n  box-sizing: border-box;\n  overflow: auto;\n  scroll-padding-block: 0.5rem;\n  padding-block: 0.5rem;\n  overscroll-behavior: contain;\n  max-height: min(\n    calc(24rem - var(--input-container-height)),\n    calc(var(--available-height) - var(--input-container-height))\n  );\n\n  &:empty {\n    padding: 0;\n  }\n}\n\n.Item {\n  box-sizing: border-box;\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  min-width: var(--anchor-width);\n  padding-block: 0.5rem;\n  padding-left: 1rem;\n  padding-right: 2rem;\n  display: grid;\n  gap: 0.5rem;\n  align-items: center;\n  grid-template-columns: 0.75rem 1fr;\n  font-size: 1rem;\n  line-height: 1rem;\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.5rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.ItemText {\n  grid-column-start: 2;\n}\n\n.ItemIndicator {\n  grid-column-start: 1;\n}\n\n.ItemIndicatorIcon {\n  display: block;\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.Separator {\n  margin: 0.375rem 1rem;\n  height: 1px;\n  background-color: var(--color-gray-200);\n}\n\n.Empty:not(:empty) {\n  box-sizing: border-box;\n  font-size: 0.925rem;\n  line-height: 1rem;\n  color: var(--color-gray-600);\n  padding: 1rem;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/combobox/demos/input-inside-popup/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Combobox } from '@base-ui/react/combobox';\nimport styles from './index.module.css';\n\nexport default function ExamplePopoverCombobox() {\n  return (\n    <div className={styles.Field}>\n      <Combobox.Root items={countries}>\n        <Combobox.Label className={styles.Label}>Country</Combobox.Label>\n        <Combobox.Trigger className={styles.Trigger}>\n          <Combobox.Value\n            placeholder={<span className={styles.Placeholder}>Select country</span>}\n          />\n          <Combobox.Icon className={styles.TriggerIcon}>\n            <ChevronUpDownIcon />\n          </Combobox.Icon>\n        </Combobox.Trigger>\n        <Combobox.Portal>\n          <Combobox.Positioner align=\"start\" sideOffset={4}>\n            <Combobox.Popup className={styles.Popup} aria-label=\"Select country\">\n              <div className={styles.InputContainer}>\n                <Combobox.Input placeholder=\"e.g. United Kingdom\" className={styles.Input} />\n              </div>\n              <Combobox.Empty className={styles.Empty}>No countries found.</Combobox.Empty>\n              <Combobox.List className={styles.List}>\n                {(country: Country) => (\n                  <Combobox.Item key={country.code} value={country} className={styles.Item}>\n                    <Combobox.ItemIndicator className={styles.ItemIndicator}>\n                      <CheckIcon className={styles.ItemIndicatorIcon} />\n                    </Combobox.ItemIndicator>\n                    <div className={styles.ItemText}>{country.label}</div>\n                  </Combobox.Item>\n                )}\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>\n    </div>\n  );\n}\n\nfunction ChevronUpDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"8\"\n      height=\"12\"\n      viewBox=\"0 0 8 12\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"1.5\"\n      {...props}\n    >\n      <path d=\"M0.5 4.5L4 1.5L7.5 4.5\" />\n      <path d=\"M0.5 7.5L4 10.5L7.5 7.5\" />\n    </svg>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\ninterface Country {\n  code: string;\n  value: string;\n  continent: string;\n  label: string;\n}\n\nconst countries: Country[] = [\n  { code: 'af', value: 'afghanistan', label: 'Afghanistan', continent: 'Asia' },\n  { code: 'al', value: 'albania', label: 'Albania', continent: 'Europe' },\n  { code: 'dz', value: 'algeria', label: 'Algeria', continent: 'Africa' },\n  { code: 'ad', value: 'andorra', label: 'Andorra', continent: 'Europe' },\n  { code: 'ao', value: 'angola', label: 'Angola', continent: 'Africa' },\n  { code: 'ar', value: 'argentina', label: 'Argentina', continent: 'South America' },\n  { code: 'am', value: 'armenia', label: 'Armenia', continent: 'Asia' },\n  { code: 'au', value: 'australia', label: 'Australia', continent: 'Oceania' },\n  { code: 'at', value: 'austria', label: 'Austria', continent: 'Europe' },\n  { code: 'az', value: 'azerbaijan', label: 'Azerbaijan', continent: 'Asia' },\n  { code: 'bs', value: 'bahamas', label: 'Bahamas', continent: 'North America' },\n  { code: 'bh', value: 'bahrain', label: 'Bahrain', continent: 'Asia' },\n  { code: 'bd', value: 'bangladesh', label: 'Bangladesh', continent: 'Asia' },\n  { code: 'bb', value: 'barbados', label: 'Barbados', continent: 'North America' },\n  { code: 'by', value: 'belarus', label: 'Belarus', continent: 'Europe' },\n  { code: 'be', value: 'belgium', label: 'Belgium', continent: 'Europe' },\n  { code: 'bz', value: 'belize', label: 'Belize', continent: 'North America' },\n  { code: 'bj', value: 'benin', label: 'Benin', continent: 'Africa' },\n  { code: 'bt', value: 'bhutan', label: 'Bhutan', continent: 'Asia' },\n  { code: 'bo', value: 'bolivia', label: 'Bolivia', continent: 'South America' },\n  {\n    code: 'ba',\n    value: 'bosnia-and-herzegovina',\n    label: 'Bosnia and Herzegovina',\n    continent: 'Europe',\n  },\n  { code: 'bw', value: 'botswana', label: 'Botswana', continent: 'Africa' },\n  { code: 'br', value: 'brazil', label: 'Brazil', continent: 'South America' },\n  { code: 'bn', value: 'brunei', label: 'Brunei', continent: 'Asia' },\n  { code: 'bg', value: 'bulgaria', label: 'Bulgaria', continent: 'Europe' },\n  { code: 'bf', value: 'burkina-faso', label: 'Burkina Faso', continent: 'Africa' },\n  { code: 'bi', value: 'burundi', label: 'Burundi', continent: 'Africa' },\n  { code: 'kh', value: 'cambodia', label: 'Cambodia', continent: 'Asia' },\n  { code: 'cm', value: 'cameroon', label: 'Cameroon', continent: 'Africa' },\n  { code: 'ca', value: 'canada', label: 'Canada', continent: 'North America' },\n  { code: 'cv', value: 'cape-verde', label: 'Cape Verde', continent: 'Africa' },\n  {\n    code: 'cf',\n    value: 'central-african-republic',\n    label: 'Central African Republic',\n    continent: 'Africa',\n  },\n  { code: 'td', value: 'chad', label: 'Chad', continent: 'Africa' },\n  { code: 'cl', value: 'chile', label: 'Chile', continent: 'South America' },\n  { code: 'cn', value: 'china', label: 'China', continent: 'Asia' },\n  { code: 'co', value: 'colombia', label: 'Colombia', continent: 'South America' },\n  { code: 'km', value: 'comoros', label: 'Comoros', continent: 'Africa' },\n  { code: 'cg', value: 'congo', label: 'Congo', continent: 'Africa' },\n  { code: 'cr', value: 'costa-rica', label: 'Costa Rica', continent: 'North America' },\n  { code: 'hr', value: 'croatia', label: 'Croatia', continent: 'Europe' },\n  { code: 'cu', value: 'cuba', label: 'Cuba', continent: 'North America' },\n  { code: 'cy', value: 'cyprus', label: 'Cyprus', continent: 'Asia' },\n  { code: 'cz', value: 'czech-republic', label: 'Czech Republic', continent: 'Europe' },\n  { code: 'dk', value: 'denmark', label: 'Denmark', continent: 'Europe' },\n  { code: 'dj', value: 'djibouti', label: 'Djibouti', continent: 'Africa' },\n  { code: 'dm', value: 'dominica', label: 'Dominica', continent: 'North America' },\n  {\n    code: 'do',\n    value: 'dominican-republic',\n    label: 'Dominican Republic',\n    continent: 'North America',\n  },\n  { code: 'ec', value: 'ecuador', label: 'Ecuador', continent: 'South America' },\n  { code: 'eg', value: 'egypt', label: 'Egypt', continent: 'Africa' },\n  { code: 'sv', value: 'el-salvador', label: 'El Salvador', continent: 'North America' },\n  { code: 'gq', value: 'equatorial-guinea', label: 'Equatorial Guinea', continent: 'Africa' },\n  { code: 'er', value: 'eritrea', label: 'Eritrea', continent: 'Africa' },\n  { code: 'ee', value: 'estonia', label: 'Estonia', continent: 'Europe' },\n  { code: 'et', value: 'ethiopia', label: 'Ethiopia', continent: 'Africa' },\n  { code: 'fj', value: 'fiji', label: 'Fiji', continent: 'Oceania' },\n  { code: 'fi', value: 'finland', label: 'Finland', continent: 'Europe' },\n  { code: 'fr', value: 'france', label: 'France', continent: 'Europe' },\n  { code: 'ga', value: 'gabon', label: 'Gabon', continent: 'Africa' },\n  { code: 'gm', value: 'gambia', label: 'Gambia', continent: 'Africa' },\n  { code: 'ge', value: 'georgia', label: 'Georgia', continent: 'Asia' },\n  { code: 'de', value: 'germany', label: 'Germany', continent: 'Europe' },\n  { code: 'gh', value: 'ghana', label: 'Ghana', continent: 'Africa' },\n  { code: 'gr', value: 'greece', label: 'Greece', continent: 'Europe' },\n  { code: 'gd', value: 'grenada', label: 'Grenada', continent: 'North America' },\n  { code: 'gt', value: 'guatemala', label: 'Guatemala', continent: 'North America' },\n  { code: 'gn', value: 'guinea', label: 'Guinea', continent: 'Africa' },\n  { code: 'gw', value: 'guinea-bissau', label: 'Guinea-Bissau', continent: 'Africa' },\n  { code: 'gy', value: 'guyana', label: 'Guyana', continent: 'South America' },\n  { code: 'ht', value: 'haiti', label: 'Haiti', continent: 'North America' },\n  { code: 'hn', value: 'honduras', label: 'Honduras', continent: 'North America' },\n  { code: 'hu', value: 'hungary', label: 'Hungary', continent: 'Europe' },\n  { code: 'is', value: 'iceland', label: 'Iceland', continent: 'Europe' },\n  { code: 'in', value: 'india', label: 'India', continent: 'Asia' },\n  { code: 'id', value: 'indonesia', label: 'Indonesia', continent: 'Asia' },\n  { code: 'ir', value: 'iran', label: 'Iran', continent: 'Asia' },\n  { code: 'iq', value: 'iraq', label: 'Iraq', continent: 'Asia' },\n  { code: 'ie', value: 'ireland', label: 'Ireland', continent: 'Europe' },\n  { code: 'il', value: 'israel', label: 'Israel', continent: 'Asia' },\n  { code: 'it', value: 'italy', label: 'Italy', continent: 'Europe' },\n  { code: 'jm', value: 'jamaica', label: 'Jamaica', continent: 'North America' },\n  { code: 'jp', value: 'japan', label: 'Japan', continent: 'Asia' },\n  { code: 'jo', value: 'jordan', label: 'Jordan', continent: 'Asia' },\n  { code: 'kz', value: 'kazakhstan', label: 'Kazakhstan', continent: 'Asia' },\n  { code: 'ke', value: 'kenya', label: 'Kenya', continent: 'Africa' },\n  { code: 'kw', value: 'kuwait', label: 'Kuwait', continent: 'Asia' },\n  { code: 'kg', value: 'kyrgyzstan', label: 'Kyrgyzstan', continent: 'Asia' },\n  { code: 'la', value: 'laos', label: 'Laos', continent: 'Asia' },\n  { code: 'lv', value: 'latvia', label: 'Latvia', continent: 'Europe' },\n  { code: 'lb', value: 'lebanon', label: 'Lebanon', continent: 'Asia' },\n  { code: 'ls', value: 'lesotho', label: 'Lesotho', continent: 'Africa' },\n  { code: 'lr', value: 'liberia', label: 'Liberia', continent: 'Africa' },\n  { code: 'ly', value: 'libya', label: 'Libya', continent: 'Africa' },\n  { code: 'li', value: 'liechtenstein', label: 'Liechtenstein', continent: 'Europe' },\n  { code: 'lt', value: 'lithuania', label: 'Lithuania', continent: 'Europe' },\n  { code: 'lu', value: 'luxembourg', label: 'Luxembourg', continent: 'Europe' },\n  { code: 'mg', value: 'madagascar', label: 'Madagascar', continent: 'Africa' },\n  { code: 'mw', value: 'malawi', label: 'Malawi', continent: 'Africa' },\n  { code: 'my', value: 'malaysia', label: 'Malaysia', continent: 'Asia' },\n  { code: 'mv', value: 'maldives', label: 'Maldives', continent: 'Asia' },\n  { code: 'ml', value: 'mali', label: 'Mali', continent: 'Africa' },\n  { code: 'mt', value: 'malta', label: 'Malta', continent: 'Europe' },\n  { code: 'mh', value: 'marshall-islands', label: 'Marshall Islands', continent: 'Oceania' },\n  { code: 'mr', value: 'mauritania', label: 'Mauritania', continent: 'Africa' },\n  { code: 'mu', value: 'mauritius', label: 'Mauritius', continent: 'Africa' },\n  { code: 'mx', value: 'mexico', label: 'Mexico', continent: 'North America' },\n  { code: 'fm', value: 'micronesia', label: 'Micronesia', continent: 'Oceania' },\n  { code: 'md', value: 'moldova', label: 'Moldova', continent: 'Europe' },\n  { code: 'mc', value: 'monaco', label: 'Monaco', continent: 'Europe' },\n  { code: 'mn', value: 'mongolia', label: 'Mongolia', continent: 'Asia' },\n  { code: 'me', value: 'montenegro', label: 'Montenegro', continent: 'Europe' },\n  { code: 'ma', value: 'morocco', label: 'Morocco', continent: 'Africa' },\n  { code: 'mz', value: 'mozambique', label: 'Mozambique', continent: 'Africa' },\n  { code: 'mm', value: 'myanmar', label: 'Myanmar', continent: 'Asia' },\n  { code: 'na', value: 'namibia', label: 'Namibia', continent: 'Africa' },\n  { code: 'nr', value: 'nauru', label: 'Nauru', continent: 'Oceania' },\n  { code: 'np', value: 'nepal', label: 'Nepal', continent: 'Asia' },\n  { code: 'nl', value: 'netherlands', label: 'Netherlands', continent: 'Europe' },\n  { code: 'nz', value: 'new-zealand', label: 'New Zealand', continent: 'Oceania' },\n  { code: 'ni', value: 'nicaragua', label: 'Nicaragua', continent: 'North America' },\n  { code: 'ne', value: 'niger', label: 'Niger', continent: 'Africa' },\n  { code: 'ng', value: 'nigeria', label: 'Nigeria', continent: 'Africa' },\n  { code: 'kp', value: 'north-korea', label: 'North Korea', continent: 'Asia' },\n  { code: 'mk', value: 'north-macedonia', label: 'North Macedonia', continent: 'Europe' },\n  { code: 'no', value: 'norway', label: 'Norway', continent: 'Europe' },\n  { code: 'om', value: 'oman', label: 'Oman', continent: 'Asia' },\n  { code: 'pk', value: 'pakistan', label: 'Pakistan', continent: 'Asia' },\n  { code: 'pw', value: 'palau', label: 'Palau', continent: 'Oceania' },\n  { code: 'ps', value: 'palestine', label: 'Palestine', continent: 'Asia' },\n  { code: 'pa', value: 'panama', label: 'Panama', continent: 'North America' },\n  { code: 'pg', value: 'papua-new-guinea', label: 'Papua New Guinea', continent: 'Oceania' },\n  { code: 'py', value: 'paraguay', label: 'Paraguay', continent: 'South America' },\n  { code: 'pe', value: 'peru', label: 'Peru', continent: 'South America' },\n  { code: 'ph', value: 'philippines', label: 'Philippines', continent: 'Asia' },\n  { code: 'pl', value: 'poland', label: 'Poland', continent: 'Europe' },\n  { code: 'pt', value: 'portugal', label: 'Portugal', continent: 'Europe' },\n  { code: 'qa', value: 'qatar', label: 'Qatar', continent: 'Asia' },\n  { code: 'ro', value: 'romania', label: 'Romania', continent: 'Europe' },\n  { code: 'ru', value: 'russia', label: 'Russia', continent: 'Europe' },\n  { code: 'rw', value: 'rwanda', label: 'Rwanda', continent: 'Africa' },\n  { code: 'ws', value: 'samoa', label: 'Samoa', continent: 'Oceania' },\n  { code: 'sm', value: 'san-marino', label: 'San Marino', continent: 'Europe' },\n  { code: 'sa', value: 'saudi-arabia', label: 'Saudi Arabia', continent: 'Asia' },\n  { code: 'sn', value: 'senegal', label: 'Senegal', continent: 'Africa' },\n  { code: 'rs', value: 'serbia', label: 'Serbia', continent: 'Europe' },\n  { code: 'sc', value: 'seychelles', label: 'Seychelles', continent: 'Africa' },\n  { code: 'sl', value: 'sierra-leone', label: 'Sierra Leone', continent: 'Africa' },\n  { code: 'sg', value: 'singapore', label: 'Singapore', continent: 'Asia' },\n  { code: 'sk', value: 'slovakia', label: 'Slovakia', continent: 'Europe' },\n  { code: 'si', value: 'slovenia', label: 'Slovenia', continent: 'Europe' },\n  { code: 'sb', value: 'solomon-islands', label: 'Solomon Islands', continent: 'Oceania' },\n  { code: 'so', value: 'somalia', label: 'Somalia', continent: 'Africa' },\n  { code: 'za', value: 'south-africa', label: 'South Africa', continent: 'Africa' },\n  { code: 'kr', value: 'south-korea', label: 'South Korea', continent: 'Asia' },\n  { code: 'ss', value: 'south-sudan', label: 'South Sudan', continent: 'Africa' },\n  { code: 'es', value: 'spain', label: 'Spain', continent: 'Europe' },\n  { code: 'lk', value: 'sri-lanka', label: 'Sri Lanka', continent: 'Asia' },\n  { code: 'sd', value: 'sudan', label: 'Sudan', continent: 'Africa' },\n  { code: 'sr', value: 'suriname', label: 'Suriname', continent: 'South America' },\n  { code: 'se', value: 'sweden', label: 'Sweden', continent: 'Europe' },\n  { code: 'ch', value: 'switzerland', label: 'Switzerland', continent: 'Europe' },\n  { code: 'sy', value: 'syria', label: 'Syria', continent: 'Asia' },\n  { code: 'tw', value: 'taiwan', label: 'Taiwan', continent: 'Asia' },\n  { code: 'tj', value: 'tajikistan', label: 'Tajikistan', continent: 'Asia' },\n  { code: 'tz', value: 'tanzania', label: 'Tanzania', continent: 'Africa' },\n  { code: 'th', value: 'thailand', label: 'Thailand', continent: 'Asia' },\n  { code: 'tl', value: 'timor-leste', label: 'Timor-Leste', continent: 'Asia' },\n  { code: 'tg', value: 'togo', label: 'Togo', continent: 'Africa' },\n  { code: 'to', value: 'tonga', label: 'Tonga', continent: 'Oceania' },\n  {\n    code: 'tt',\n    value: 'trinidad-and-tobago',\n    label: 'Trinidad and Tobago',\n    continent: 'North America',\n  },\n  { code: 'tn', value: 'tunisia', label: 'Tunisia', continent: 'Africa' },\n  { code: 'tr', value: 'turkey', label: 'Turkey', continent: 'Asia' },\n  { code: 'tm', value: 'turkmenistan', label: 'Turkmenistan', continent: 'Asia' },\n  { code: 'tv', value: 'tuvalu', label: 'Tuvalu', continent: 'Oceania' },\n  { code: 'ug', value: 'uganda', label: 'Uganda', continent: 'Africa' },\n  { code: 'ua', value: 'ukraine', label: 'Ukraine', continent: 'Europe' },\n  { code: 'ae', value: 'united-arab-emirates', label: 'United Arab Emirates', continent: 'Asia' },\n  { code: 'gb', value: 'united-kingdom', label: 'United Kingdom', continent: 'Europe' },\n  { code: 'us', value: 'united-states', label: 'United States', continent: 'North America' },\n  { code: 'uy', value: 'uruguay', label: 'Uruguay', continent: 'South America' },\n  { code: 'uz', value: 'uzbekistan', label: 'Uzbekistan', continent: 'Asia' },\n  { code: 'vu', value: 'vanuatu', label: 'Vanuatu', continent: 'Oceania' },\n  { code: 'va', value: 'vatican-city', label: 'Vatican City', continent: 'Europe' },\n  { code: 've', value: 'venezuela', label: 'Venezuela', continent: 'South America' },\n  { code: 'vn', value: 'vietnam', label: 'Vietnam', continent: 'Asia' },\n  { code: 'ye', value: 'yemen', label: 'Yemen', continent: 'Asia' },\n  { code: 'zm', value: 'zambia', label: 'Zambia', continent: 'Africa' },\n  { code: 'zw', value: 'zimbabwe', label: 'Zimbabwe', continent: 'Africa' },\n];\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/combobox/demos/input-inside-popup/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoComboboxInputInsidePopup = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/combobox/demos/input-inside-popup/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Combobox } from '@base-ui/react/combobox';\n\nexport default function ExamplePopoverCombobox() {\n  return (\n    <div className=\"flex flex-col gap-1\">\n      <Combobox.Root items={countries}>\n        <Combobox.Label className=\"cursor-default text-sm leading-5 font-bold text-gray-900\">\n          Country\n        </Combobox.Label>\n        <Combobox.Trigger className=\"flex bg-[canvas] h-10 min-w-[12rem] items-center justify-between gap-3 rounded-md border border-gray-200 pr-3 pl-3.5 text-base text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 data-[popup-open]:bg-gray-100 cursor-default font-normal\">\n          <Combobox.Value placeholder={<span className=\"opacity-60\">Select country</span>} />\n          <Combobox.Icon className=\"flex\">\n            <ChevronUpDownIcon />\n          </Combobox.Icon>\n        </Combobox.Trigger>\n        <Combobox.Portal>\n          <Combobox.Positioner align=\"start\" sideOffset={4}>\n            <Combobox.Popup\n              className=\"[--input-container-height:3rem] origin-[var(--transform-origin)] max-w-[var(--available-width)] max-h-[24rem] rounded-lg bg-[canvas] shadow-lg shadow-gray-200 text-gray-900 outline-1 outline-gray-200 transition-[transform,scale,opacity] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300\"\n              aria-label=\"Select country\"\n            >\n              <div className=\"w-80 h-[var(--input-container-height)] text-center p-2\">\n                <Combobox.Input\n                  placeholder=\"e.g. United Kingdom\"\n                  className=\"h-10 w-full font-normal rounded-md border border-gray-200 pl-3.5 text-base font-normal text-gray-900 focus:outline-2 focus:-outline-offset-1 focus:outline-blue-800\"\n                />\n              </div>\n              <Combobox.Empty className=\"p-4 text-[0.925rem] leading-4 text-gray-600 empty:m-0 empty:p-0\">\n                No countries found.\n              </Combobox.Empty>\n              <Combobox.List className=\"overflow-y-auto scroll-py-2 py-2 overscroll-contain max-h-[min(calc(24rem-var(--input-container-height)),calc(var(--available-height)-var(--input-container-height)))] empty:p-0\">\n                {(country: Country) => (\n                  <Combobox.Item\n                    key={country.code}\n                    value={country}\n                    className=\"grid min-w-[var(--anchor-width)] cursor-default grid-cols-[0.75rem_1fr] items-center gap-2 py-2 pr-8 pl-4 text-base leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-2 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\"\n                  >\n                    <Combobox.ItemIndicator className=\"col-start-1\">\n                      <CheckIcon className=\"size-3\" />\n                    </Combobox.ItemIndicator>\n                    <div className=\"col-start-2\">{country.label}</div>\n                  </Combobox.Item>\n                )}\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>\n    </div>\n  );\n}\n\nfunction ChevronUpDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"8\"\n      height=\"12\"\n      viewBox=\"0 0 8 12\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"1.5\"\n      {...props}\n    >\n      <path d=\"M0.5 4.5L4 1.5L7.5 4.5\" />\n      <path d=\"M0.5 7.5L4 10.5L7.5 7.5\" />\n    </svg>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\ninterface Country {\n  code: string;\n  value: string;\n  continent: string;\n  label: string;\n}\n\nconst countries: Country[] = [\n  { code: 'af', value: 'afghanistan', label: 'Afghanistan', continent: 'Asia' },\n  { code: 'al', value: 'albania', label: 'Albania', continent: 'Europe' },\n  { code: 'dz', value: 'algeria', label: 'Algeria', continent: 'Africa' },\n  { code: 'ad', value: 'andorra', label: 'Andorra', continent: 'Europe' },\n  { code: 'ao', value: 'angola', label: 'Angola', continent: 'Africa' },\n  { code: 'ar', value: 'argentina', label: 'Argentina', continent: 'South America' },\n  { code: 'am', value: 'armenia', label: 'Armenia', continent: 'Asia' },\n  { code: 'au', value: 'australia', label: 'Australia', continent: 'Oceania' },\n  { code: 'at', value: 'austria', label: 'Austria', continent: 'Europe' },\n  { code: 'az', value: 'azerbaijan', label: 'Azerbaijan', continent: 'Asia' },\n  { code: 'bs', value: 'bahamas', label: 'Bahamas', continent: 'North America' },\n  { code: 'bh', value: 'bahrain', label: 'Bahrain', continent: 'Asia' },\n  { code: 'bd', value: 'bangladesh', label: 'Bangladesh', continent: 'Asia' },\n  { code: 'bb', value: 'barbados', label: 'Barbados', continent: 'North America' },\n  { code: 'by', value: 'belarus', label: 'Belarus', continent: 'Europe' },\n  { code: 'be', value: 'belgium', label: 'Belgium', continent: 'Europe' },\n  { code: 'bz', value: 'belize', label: 'Belize', continent: 'North America' },\n  { code: 'bj', value: 'benin', label: 'Benin', continent: 'Africa' },\n  { code: 'bt', value: 'bhutan', label: 'Bhutan', continent: 'Asia' },\n  { code: 'bo', value: 'bolivia', label: 'Bolivia', continent: 'South America' },\n  {\n    code: 'ba',\n    value: 'bosnia-and-herzegovina',\n    label: 'Bosnia and Herzegovina',\n    continent: 'Europe',\n  },\n  { code: 'bw', value: 'botswana', label: 'Botswana', continent: 'Africa' },\n  { code: 'br', value: 'brazil', label: 'Brazil', continent: 'South America' },\n  { code: 'bn', value: 'brunei', label: 'Brunei', continent: 'Asia' },\n  { code: 'bg', value: 'bulgaria', label: 'Bulgaria', continent: 'Europe' },\n  { code: 'bf', value: 'burkina-faso', label: 'Burkina Faso', continent: 'Africa' },\n  { code: 'bi', value: 'burundi', label: 'Burundi', continent: 'Africa' },\n  { code: 'kh', value: 'cambodia', label: 'Cambodia', continent: 'Asia' },\n  { code: 'cm', value: 'cameroon', label: 'Cameroon', continent: 'Africa' },\n  { code: 'ca', value: 'canada', label: 'Canada', continent: 'North America' },\n  { code: 'cv', value: 'cape-verde', label: 'Cape Verde', continent: 'Africa' },\n  {\n    code: 'cf',\n    value: 'central-african-republic',\n    label: 'Central African Republic',\n    continent: 'Africa',\n  },\n  { code: 'td', value: 'chad', label: 'Chad', continent: 'Africa' },\n  { code: 'cl', value: 'chile', label: 'Chile', continent: 'South America' },\n  { code: 'cn', value: 'china', label: 'China', continent: 'Asia' },\n  { code: 'co', value: 'colombia', label: 'Colombia', continent: 'South America' },\n  { code: 'km', value: 'comoros', label: 'Comoros', continent: 'Africa' },\n  { code: 'cg', value: 'congo', label: 'Congo', continent: 'Africa' },\n  { code: 'cr', value: 'costa-rica', label: 'Costa Rica', continent: 'North America' },\n  { code: 'hr', value: 'croatia', label: 'Croatia', continent: 'Europe' },\n  { code: 'cu', value: 'cuba', label: 'Cuba', continent: 'North America' },\n  { code: 'cy', value: 'cyprus', label: 'Cyprus', continent: 'Asia' },\n  { code: 'cz', value: 'czech-republic', label: 'Czech Republic', continent: 'Europe' },\n  { code: 'dk', value: 'denmark', label: 'Denmark', continent: 'Europe' },\n  { code: 'dj', value: 'djibouti', label: 'Djibouti', continent: 'Africa' },\n  { code: 'dm', value: 'dominica', label: 'Dominica', continent: 'North America' },\n  {\n    code: 'do',\n    value: 'dominican-republic',\n    label: 'Dominican Republic',\n    continent: 'North America',\n  },\n  { code: 'ec', value: 'ecuador', label: 'Ecuador', continent: 'South America' },\n  { code: 'eg', value: 'egypt', label: 'Egypt', continent: 'Africa' },\n  { code: 'sv', value: 'el-salvador', label: 'El Salvador', continent: 'North America' },\n  { code: 'gq', value: 'equatorial-guinea', label: 'Equatorial Guinea', continent: 'Africa' },\n  { code: 'er', value: 'eritrea', label: 'Eritrea', continent: 'Africa' },\n  { code: 'ee', value: 'estonia', label: 'Estonia', continent: 'Europe' },\n  { code: 'et', value: 'ethiopia', label: 'Ethiopia', continent: 'Africa' },\n  { code: 'fj', value: 'fiji', label: 'Fiji', continent: 'Oceania' },\n  { code: 'fi', value: 'finland', label: 'Finland', continent: 'Europe' },\n  { code: 'fr', value: 'france', label: 'France', continent: 'Europe' },\n  { code: 'ga', value: 'gabon', label: 'Gabon', continent: 'Africa' },\n  { code: 'gm', value: 'gambia', label: 'Gambia', continent: 'Africa' },\n  { code: 'ge', value: 'georgia', label: 'Georgia', continent: 'Asia' },\n  { code: 'de', value: 'germany', label: 'Germany', continent: 'Europe' },\n  { code: 'gh', value: 'ghana', label: 'Ghana', continent: 'Africa' },\n  { code: 'gr', value: 'greece', label: 'Greece', continent: 'Europe' },\n  { code: 'gd', value: 'grenada', label: 'Grenada', continent: 'North America' },\n  { code: 'gt', value: 'guatemala', label: 'Guatemala', continent: 'North America' },\n  { code: 'gn', value: 'guinea', label: 'Guinea', continent: 'Africa' },\n  { code: 'gw', value: 'guinea-bissau', label: 'Guinea-Bissau', continent: 'Africa' },\n  { code: 'gy', value: 'guyana', label: 'Guyana', continent: 'South America' },\n  { code: 'ht', value: 'haiti', label: 'Haiti', continent: 'North America' },\n  { code: 'hn', value: 'honduras', label: 'Honduras', continent: 'North America' },\n  { code: 'hu', value: 'hungary', label: 'Hungary', continent: 'Europe' },\n  { code: 'is', value: 'iceland', label: 'Iceland', continent: 'Europe' },\n  { code: 'in', value: 'india', label: 'India', continent: 'Asia' },\n  { code: 'id', value: 'indonesia', label: 'Indonesia', continent: 'Asia' },\n  { code: 'ir', value: 'iran', label: 'Iran', continent: 'Asia' },\n  { code: 'iq', value: 'iraq', label: 'Iraq', continent: 'Asia' },\n  { code: 'ie', value: 'ireland', label: 'Ireland', continent: 'Europe' },\n  { code: 'il', value: 'israel', label: 'Israel', continent: 'Asia' },\n  { code: 'it', value: 'italy', label: 'Italy', continent: 'Europe' },\n  { code: 'jm', value: 'jamaica', label: 'Jamaica', continent: 'North America' },\n  { code: 'jp', value: 'japan', label: 'Japan', continent: 'Asia' },\n  { code: 'jo', value: 'jordan', label: 'Jordan', continent: 'Asia' },\n  { code: 'kz', value: 'kazakhstan', label: 'Kazakhstan', continent: 'Asia' },\n  { code: 'ke', value: 'kenya', label: 'Kenya', continent: 'Africa' },\n  { code: 'kw', value: 'kuwait', label: 'Kuwait', continent: 'Asia' },\n  { code: 'kg', value: 'kyrgyzstan', label: 'Kyrgyzstan', continent: 'Asia' },\n  { code: 'la', value: 'laos', label: 'Laos', continent: 'Asia' },\n  { code: 'lv', value: 'latvia', label: 'Latvia', continent: 'Europe' },\n  { code: 'lb', value: 'lebanon', label: 'Lebanon', continent: 'Asia' },\n  { code: 'ls', value: 'lesotho', label: 'Lesotho', continent: 'Africa' },\n  { code: 'lr', value: 'liberia', label: 'Liberia', continent: 'Africa' },\n  { code: 'ly', value: 'libya', label: 'Libya', continent: 'Africa' },\n  { code: 'li', value: 'liechtenstein', label: 'Liechtenstein', continent: 'Europe' },\n  { code: 'lt', value: 'lithuania', label: 'Lithuania', continent: 'Europe' },\n  { code: 'lu', value: 'luxembourg', label: 'Luxembourg', continent: 'Europe' },\n  { code: 'mg', value: 'madagascar', label: 'Madagascar', continent: 'Africa' },\n  { code: 'mw', value: 'malawi', label: 'Malawi', continent: 'Africa' },\n  { code: 'my', value: 'malaysia', label: 'Malaysia', continent: 'Asia' },\n  { code: 'mv', value: 'maldives', label: 'Maldives', continent: 'Asia' },\n  { code: 'ml', value: 'mali', label: 'Mali', continent: 'Africa' },\n  { code: 'mt', value: 'malta', label: 'Malta', continent: 'Europe' },\n  { code: 'mh', value: 'marshall-islands', label: 'Marshall Islands', continent: 'Oceania' },\n  { code: 'mr', value: 'mauritania', label: 'Mauritania', continent: 'Africa' },\n  { code: 'mu', value: 'mauritius', label: 'Mauritius', continent: 'Africa' },\n  { code: 'mx', value: 'mexico', label: 'Mexico', continent: 'North America' },\n  { code: 'fm', value: 'micronesia', label: 'Micronesia', continent: 'Oceania' },\n  { code: 'md', value: 'moldova', label: 'Moldova', continent: 'Europe' },\n  { code: 'mc', value: 'monaco', label: 'Monaco', continent: 'Europe' },\n  { code: 'mn', value: 'mongolia', label: 'Mongolia', continent: 'Asia' },\n  { code: 'me', value: 'montenegro', label: 'Montenegro', continent: 'Europe' },\n  { code: 'ma', value: 'morocco', label: 'Morocco', continent: 'Africa' },\n  { code: 'mz', value: 'mozambique', label: 'Mozambique', continent: 'Africa' },\n  { code: 'mm', value: 'myanmar', label: 'Myanmar', continent: 'Asia' },\n  { code: 'na', value: 'namibia', label: 'Namibia', continent: 'Africa' },\n  { code: 'nr', value: 'nauru', label: 'Nauru', continent: 'Oceania' },\n  { code: 'np', value: 'nepal', label: 'Nepal', continent: 'Asia' },\n  { code: 'nl', value: 'netherlands', label: 'Netherlands', continent: 'Europe' },\n  { code: 'nz', value: 'new-zealand', label: 'New Zealand', continent: 'Oceania' },\n  { code: 'ni', value: 'nicaragua', label: 'Nicaragua', continent: 'North America' },\n  { code: 'ne', value: 'niger', label: 'Niger', continent: 'Africa' },\n  { code: 'ng', value: 'nigeria', label: 'Nigeria', continent: 'Africa' },\n  { code: 'kp', value: 'north-korea', label: 'North Korea', continent: 'Asia' },\n  { code: 'mk', value: 'north-macedonia', label: 'North Macedonia', continent: 'Europe' },\n  { code: 'no', value: 'norway', label: 'Norway', continent: 'Europe' },\n  { code: 'om', value: 'oman', label: 'Oman', continent: 'Asia' },\n  { code: 'pk', value: 'pakistan', label: 'Pakistan', continent: 'Asia' },\n  { code: 'pw', value: 'palau', label: 'Palau', continent: 'Oceania' },\n  { code: 'ps', value: 'palestine', label: 'Palestine', continent: 'Asia' },\n  { code: 'pa', value: 'panama', label: 'Panama', continent: 'North America' },\n  { code: 'pg', value: 'papua-new-guinea', label: 'Papua New Guinea', continent: 'Oceania' },\n  { code: 'py', value: 'paraguay', label: 'Paraguay', continent: 'South America' },\n  { code: 'pe', value: 'peru', label: 'Peru', continent: 'South America' },\n  { code: 'ph', value: 'philippines', label: 'Philippines', continent: 'Asia' },\n  { code: 'pl', value: 'poland', label: 'Poland', continent: 'Europe' },\n  { code: 'pt', value: 'portugal', label: 'Portugal', continent: 'Europe' },\n  { code: 'qa', value: 'qatar', label: 'Qatar', continent: 'Asia' },\n  { code: 'ro', value: 'romania', label: 'Romania', continent: 'Europe' },\n  { code: 'ru', value: 'russia', label: 'Russia', continent: 'Europe' },\n  { code: 'rw', value: 'rwanda', label: 'Rwanda', continent: 'Africa' },\n  { code: 'ws', value: 'samoa', label: 'Samoa', continent: 'Oceania' },\n  { code: 'sm', value: 'san-marino', label: 'San Marino', continent: 'Europe' },\n  { code: 'sa', value: 'saudi-arabia', label: 'Saudi Arabia', continent: 'Asia' },\n  { code: 'sn', value: 'senegal', label: 'Senegal', continent: 'Africa' },\n  { code: 'rs', value: 'serbia', label: 'Serbia', continent: 'Europe' },\n  { code: 'sc', value: 'seychelles', label: 'Seychelles', continent: 'Africa' },\n  { code: 'sl', value: 'sierra-leone', label: 'Sierra Leone', continent: 'Africa' },\n  { code: 'sg', value: 'singapore', label: 'Singapore', continent: 'Asia' },\n  { code: 'sk', value: 'slovakia', label: 'Slovakia', continent: 'Europe' },\n  { code: 'si', value: 'slovenia', label: 'Slovenia', continent: 'Europe' },\n  { code: 'sb', value: 'solomon-islands', label: 'Solomon Islands', continent: 'Oceania' },\n  { code: 'so', value: 'somalia', label: 'Somalia', continent: 'Africa' },\n  { code: 'za', value: 'south-africa', label: 'South Africa', continent: 'Africa' },\n  { code: 'kr', value: 'south-korea', label: 'South Korea', continent: 'Asia' },\n  { code: 'ss', value: 'south-sudan', label: 'South Sudan', continent: 'Africa' },\n  { code: 'es', value: 'spain', label: 'Spain', continent: 'Europe' },\n  { code: 'lk', value: 'sri-lanka', label: 'Sri Lanka', continent: 'Asia' },\n  { code: 'sd', value: 'sudan', label: 'Sudan', continent: 'Africa' },\n  { code: 'sr', value: 'suriname', label: 'Suriname', continent: 'South America' },\n  { code: 'se', value: 'sweden', label: 'Sweden', continent: 'Europe' },\n  { code: 'ch', value: 'switzerland', label: 'Switzerland', continent: 'Europe' },\n  { code: 'sy', value: 'syria', label: 'Syria', continent: 'Asia' },\n  { code: 'tw', value: 'taiwan', label: 'Taiwan', continent: 'Asia' },\n  { code: 'tj', value: 'tajikistan', label: 'Tajikistan', continent: 'Asia' },\n  { code: 'tz', value: 'tanzania', label: 'Tanzania', continent: 'Africa' },\n  { code: 'th', value: 'thailand', label: 'Thailand', continent: 'Asia' },\n  { code: 'tl', value: 'timor-leste', label: 'Timor-Leste', continent: 'Asia' },\n  { code: 'tg', value: 'togo', label: 'Togo', continent: 'Africa' },\n  { code: 'to', value: 'tonga', label: 'Tonga', continent: 'Oceania' },\n  {\n    code: 'tt',\n    value: 'trinidad-and-tobago',\n    label: 'Trinidad and Tobago',\n    continent: 'North America',\n  },\n  { code: 'tn', value: 'tunisia', label: 'Tunisia', continent: 'Africa' },\n  { code: 'tr', value: 'turkey', label: 'Turkey', continent: 'Asia' },\n  { code: 'tm', value: 'turkmenistan', label: 'Turkmenistan', continent: 'Asia' },\n  { code: 'tv', value: 'tuvalu', label: 'Tuvalu', continent: 'Oceania' },\n  { code: 'ug', value: 'uganda', label: 'Uganda', continent: 'Africa' },\n  { code: 'ua', value: 'ukraine', label: 'Ukraine', continent: 'Europe' },\n  { code: 'ae', value: 'united-arab-emirates', label: 'United Arab Emirates', continent: 'Asia' },\n  { code: 'gb', value: 'united-kingdom', label: 'United Kingdom', continent: 'Europe' },\n  { code: 'us', value: 'united-states', label: 'United States', continent: 'North America' },\n  { code: 'uy', value: 'uruguay', label: 'Uruguay', continent: 'South America' },\n  { code: 'uz', value: 'uzbekistan', label: 'Uzbekistan', continent: 'Asia' },\n  { code: 'vu', value: 'vanuatu', label: 'Vanuatu', continent: 'Oceania' },\n  { code: 'va', value: 'vatican-city', label: 'Vatican City', continent: 'Europe' },\n  { code: 've', value: 'venezuela', label: 'Venezuela', continent: 'South America' },\n  { code: 'vn', value: 'vietnam', label: 'Vietnam', continent: 'Asia' },\n  { code: 'ye', value: 'yemen', label: 'Yemen', continent: 'Asia' },\n  { code: 'zm', value: 'zambia', label: 'Zambia', continent: 'Africa' },\n  { code: 'zw', value: 'zimbabwe', label: 'Zimbabwe', continent: 'Africa' },\n];\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/combobox/demos/multiple/css-modules/index.module.css",
    "content": ".Container {\n  max-width: 28rem;\n  display: flex;\n  flex-direction: column;\n  gap: 0.25rem;\n}\n\n.Label {\n  display: flex;\n  flex-direction: column;\n  gap: 0.25rem;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 700;\n  color: var(--color-gray-900);\n}\n\n.InputGroup {\n  box-sizing: border-box;\n  display: flex;\n  flex-wrap: wrap;\n  align-items: center;\n  gap: 0.125rem;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: canvas;\n  padding: 0.25rem 0.375rem;\n  width: 16rem;\n  cursor: text;\n\n  &:focus-within {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n\n  @media (min-width: 500px) {\n    width: 22rem;\n  }\n}\n\n.Chips {\n  display: flex;\n  flex-wrap: wrap;\n  align-items: center;\n  gap: 0.125rem;\n  width: 100%;\n}\n\n.Chip {\n  display: flex;\n  align-items: center;\n  background-color: var(--color-gray-100);\n  color: var(--color-gray-900);\n  border-radius: 0.375rem;\n  font-size: 0.875rem;\n  padding: 0.2rem 0.2rem 0.2rem 0.4rem;\n  overflow: hidden;\n  gap: 0.25rem;\n  outline: 0;\n  cursor: default;\n\n  &:focus-within {\n    background-color: var(--color-blue);\n    color: var(--color-gray-50);\n  }\n\n  @media (hover: hover) {\n    &[data-highlighted] {\n      background-color: var(--color-blue);\n      color: var(--color-gray-50);\n    }\n  }\n}\n\n.ChipButton {\n  display: flex;\n  align-items: center;\n  background: none;\n  border: none;\n  padding: 0.125rem 0.25rem 0.125rem 0.5rem;\n  font-size: inherit;\n  color: inherit;\n  cursor: default;\n  flex: 1;\n  outline: 0;\n}\n\n.ChipRemove {\n  border: none;\n  background: none;\n  padding: 0.25rem;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  color: inherit;\n  border-radius: 0.375rem;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-200);\n    }\n  }\n}\n\n.Input {\n  flex: 1;\n  box-sizing: border-box;\n  padding-left: 0.5rem;\n  margin: 0;\n  border: none;\n  height: 2rem;\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  background-color: transparent;\n  color: var(--color-gray-900);\n  min-width: 3rem;\n\n  &:focus {\n    outline: none;\n  }\n}\n\n.Trigger {\n  box-sizing: border-box;\n  position: absolute;\n  right: 0.75rem;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 1.5rem;\n  height: 1.5rem;\n  border: none;\n  background: none;\n  color: var(--color-gray-500);\n  border-radius: 0.25rem;\n\n  &:hover {\n    color: var(--color-gray-700);\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus {\n    outline: 2px solid var(--color-blue-500);\n    outline-offset: 2px;\n  }\n}\n\n.TriggerIcon {\n  width: 1rem;\n  height: 1rem;\n}\n\n.Positioner {\n  outline: 0;\n  z-index: 50;\n}\n\n.Popup {\n  box-sizing: border-box;\n  border-radius: 0.5rem;\n  padding-block: 0.5rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  width: var(--anchor-width);\n  max-width: var(--available-width);\n  max-height: min(var(--available-height), 24rem);\n  overflow-y: auto;\n  scroll-padding-block: 0.5rem;\n  overscroll-behavior: contain;\n  transition:\n    opacity 0.1s,\n    transform 0.1s;\n  transform-origin: var(--transform-origin);\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.95);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Item {\n  box-sizing: border-box;\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  padding-block: 0.5rem;\n  padding-left: 1rem;\n  padding-right: 2rem;\n  display: grid;\n  gap: 0.5rem;\n  align-items: center;\n  grid-template-columns: 0.75rem 1fr;\n  font-size: 1rem;\n  line-height: 1rem;\n\n  &[data-selected] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-900);\n  }\n\n  &[data-selected]::before,\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.5rem;\n    border-radius: 0.25rem;\n  }\n\n  @media (hover: hover) {\n    &[data-highlighted] {\n      z-index: 0;\n      position: relative;\n      color: var(--color-gray-50);\n    }\n\n    &[data-highlighted]::before {\n      background-color: var(--color-gray-900);\n    }\n  }\n}\n\n.ItemText {\n  grid-column-start: 2;\n}\n\n.ItemIndicator {\n  grid-column-start: 1;\n}\n\n.ItemIndicatorIcon {\n  display: block;\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.ItemName {\n  font-weight: 700;\n  color: inherit;\n}\n\n.Empty:not(:empty) {\n  box-sizing: border-box;\n  font-size: 0.925rem;\n  line-height: 1rem;\n  color: var(--color-gray-600);\n  padding: 0.5rem 1rem;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/combobox/demos/multiple/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Combobox } from '@base-ui/react/combobox';\nimport styles from './index.module.css';\n\nexport default function ExampleMultipleCombobox() {\n  const id = React.useId();\n\n  return (\n    <Combobox.Root items={langs} multiple>\n      <div className={styles.Container}>\n        <label className={styles.Label} htmlFor={id}>\n          Programming languages\n        </label>\n        <Combobox.InputGroup className={styles.InputGroup}>\n          <Combobox.Chips className={styles.Chips}>\n            <Combobox.Value>\n              {(value: ProgrammingLanguage[]) => (\n                <React.Fragment>\n                  {value.map((language) => (\n                    <Combobox.Chip\n                      key={language.id}\n                      className={styles.Chip}\n                      aria-label={language.value}\n                    >\n                      {language.value}\n                      <Combobox.ChipRemove className={styles.ChipRemove} aria-label=\"Remove\">\n                        <XIcon />\n                      </Combobox.ChipRemove>\n                    </Combobox.Chip>\n                  ))}\n                  <Combobox.Input\n                    id={id}\n                    placeholder={value.length > 0 ? '' : 'e.g. TypeScript'}\n                    className={styles.Input}\n                  />\n                </React.Fragment>\n              )}\n            </Combobox.Value>\n          </Combobox.Chips>\n        </Combobox.InputGroup>\n      </div>\n\n      <Combobox.Portal>\n        <Combobox.Positioner className={styles.Positioner} sideOffset={4}>\n          <Combobox.Popup className={styles.Popup}>\n            <Combobox.Empty className={styles.Empty}>No languages found.</Combobox.Empty>\n            <Combobox.List>\n              {(language: ProgrammingLanguage) => (\n                <Combobox.Item key={language.id} className={styles.Item} value={language}>\n                  <Combobox.ItemIndicator className={styles.ItemIndicator}>\n                    <CheckIcon className={styles.ItemIndicatorIcon} />\n                  </Combobox.ItemIndicator>\n                  <div className={styles.ItemText}>{language.value}</div>\n                </Combobox.Item>\n              )}\n            </Combobox.List>\n          </Combobox.Popup>\n        </Combobox.Positioner>\n      </Combobox.Portal>\n    </Combobox.Root>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\nfunction XIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width={16}\n      height={16}\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      aria-hidden\n      {...props}\n    >\n      <path d=\"M18 6 6 18\" />\n      <path d=\"m6 6 12 12\" />\n    </svg>\n  );\n}\n\ninterface ProgrammingLanguage {\n  id: string;\n  value: string;\n}\n\nconst langs: ProgrammingLanguage[] = [\n  { id: 'js', value: 'JavaScript' },\n  { id: 'ts', value: 'TypeScript' },\n  { id: 'py', value: 'Python' },\n  { id: 'java', value: 'Java' },\n  { id: 'cpp', value: 'C++' },\n  { id: 'cs', value: 'C#' },\n  { id: 'php', value: 'PHP' },\n  { id: 'ruby', value: 'Ruby' },\n  { id: 'go', value: 'Go' },\n  { id: 'rust', value: 'Rust' },\n  { id: 'swift', value: 'Swift' },\n];\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/combobox/demos/multiple/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoComboboxMultiple = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/combobox/demos/multiple/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Combobox } from '@base-ui/react/combobox';\n\nexport default function ExampleMultipleCombobox() {\n  const id = React.useId();\n\n  return (\n    <Combobox.Root items={langs} multiple>\n      <div className=\"max-w-[28rem] flex flex-col gap-1\">\n        <label className=\"text-sm leading-5 font-bold text-gray-900\" htmlFor={id}>\n          Programming languages\n        </label>\n        <Combobox.InputGroup className=\"w-64 cursor-text rounded-md border border-gray-200 bg-[canvas] px-1.5 py-1 focus-within:outline-2 focus-within:-outline-offset-1 focus-within:outline-blue-800 min-[500px]:w-[22rem]\">\n          <Combobox.Chips className=\"flex w-full flex-wrap items-center gap-0.5\">\n            <Combobox.Value>\n              {(value: ProgrammingLanguage[]) => (\n                <React.Fragment>\n                  {value.map((language) => (\n                    <Combobox.Chip\n                      key={language.id}\n                      className=\"flex items-center gap-1 rounded-md bg-gray-100 px-1.5 py-[0.2rem] text-sm text-gray-900 outline-none cursor-default [@media(hover:hover)]:[&[data-highlighted]]:bg-blue-800 [@media(hover:hover)]:[&[data-highlighted]]:text-gray-50 focus-within:bg-blue-800 focus-within:text-gray-50\"\n                      aria-label={language.value}\n                    >\n                      {language.value}\n                      <Combobox.ChipRemove\n                        className=\"rounded-md p-1 text-inherit hover:bg-gray-200\"\n                        aria-label=\"Remove\"\n                      >\n                        <XIcon />\n                      </Combobox.ChipRemove>\n                    </Combobox.Chip>\n                  ))}\n                  <Combobox.Input\n                    id={id}\n                    placeholder={value.length > 0 ? '' : 'e.g. TypeScript'}\n                    className=\"min-w-12 flex-1 h-8 rounded-md border-0 bg-transparent pl-2 text-base font-normal text-gray-900 outline-none\"\n                  />\n                </React.Fragment>\n              )}\n            </Combobox.Value>\n          </Combobox.Chips>\n        </Combobox.InputGroup>\n      </div>\n\n      <Combobox.Portal>\n        <Combobox.Positioner className=\"z-50 outline-none\" sideOffset={4}>\n          <Combobox.Popup className=\"w-[var(--anchor-width)] max-h-[min(var(--available-height),23rem)] max-w-[var(--available-width)] origin-[var(--transform-origin)] overflow-y-auto scroll-pt-2 scroll-pb-2 overscroll-contain rounded-md bg-[canvas] py-2 text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 transition-[transform,scale,opacity] data-[ending-style]:scale-95 data-[ending-style]:opacity-0 data-[starting-style]:scale-95 data-[starting-style]:opacity-0 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300\">\n            <Combobox.Empty className=\"px-4 py-2 text-[0.925rem] leading-4 text-gray-600 empty:m-0 empty:p-0\">\n              No languages found.\n            </Combobox.Empty>\n            <Combobox.List>\n              {(language: ProgrammingLanguage) => (\n                <Combobox.Item\n                  key={language.id}\n                  className=\"grid cursor-default grid-cols-[0.75rem_1fr] items-center gap-2 py-2 pr-8 pl-4 text-base leading-4 outline-none select-none [@media(hover:hover)]:[&[data-highlighted]]:relative [@media(hover:hover)]:[&[data-highlighted]]:z-0 [@media(hover:hover)]:[&[data-highlighted]]:text-gray-50 [@media(hover:hover)]:[&[data-highlighted]]:before:absolute [@media(hover:hover)]:[&[data-highlighted]]:before:inset-x-2 [@media(hover:hover)]:[&[data-highlighted]]:before:inset-y-0 [@media(hover:hover)]:[&[data-highlighted]]:before:z-[-1] [@media(hover:hover)]:[&[data-highlighted]]:before:rounded-sm [@media(hover:hover)]:[&[data-highlighted]]:before:bg-gray-900\"\n                  value={language}\n                >\n                  <Combobox.ItemIndicator className=\"col-start-1\">\n                    <CheckIcon className=\"size-3\" />\n                  </Combobox.ItemIndicator>\n                  <div className=\"col-start-2\">{language.value}</div>\n                </Combobox.Item>\n              )}\n            </Combobox.List>\n          </Combobox.Popup>\n        </Combobox.Positioner>\n      </Combobox.Portal>\n    </Combobox.Root>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\nfunction XIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width={16}\n      height={16}\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      aria-hidden\n      {...props}\n    >\n      <path d=\"M18 6 6 18\" />\n      <path d=\"m6 6 12 12\" />\n    </svg>\n  );\n}\n\ninterface ProgrammingLanguage {\n  id: string;\n  value: string;\n}\n\nconst langs: ProgrammingLanguage[] = [\n  { id: 'js', value: 'JavaScript' },\n  { id: 'ts', value: 'TypeScript' },\n  { id: 'py', value: 'Python' },\n  { id: 'java', value: 'Java' },\n  { id: 'cpp', value: 'C++' },\n  { id: 'cs', value: 'C#' },\n  { id: 'php', value: 'PHP' },\n  { id: 'ruby', value: 'Ruby' },\n  { id: 'go', value: 'Go' },\n  { id: 'rust', value: 'Rust' },\n  { id: 'swift', value: 'Swift' },\n];\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/combobox/demos/virtualized/css-modules/index.module.css",
    "content": ".Input {\n  box-sizing: border-box;\n  padding-left: 0.875rem;\n  margin: 0;\n  border: 1px solid var(--color-gray-200);\n  width: 16rem;\n  height: 2.5rem;\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  outline: none;\n\n  &:focus {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Label {\n  display: flex;\n  flex-direction: column;\n  gap: 0.25rem;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 700;\n  color: var(--color-gray-900);\n}\n\n.Positioner {\n  outline: 0;\n}\n\n.Popup {\n  box-sizing: border-box;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  width: var(--anchor-width);\n  max-height: min(22rem, var(--available-height));\n  max-width: var(--available-width);\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Scroller {\n  box-sizing: border-box;\n  height: min(22rem, var(--total-size));\n  max-height: var(--available-height);\n  overflow: auto;\n  overscroll-behavior: contain;\n  scroll-padding-block: 0.5rem;\n}\n\n.VirtualizedPlaceholder {\n  width: 100%;\n  position: relative;\n}\n\n.List {\n  box-sizing: border-box;\n  padding: 0;\n}\n\n.Item {\n  box-sizing: border-box;\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  padding-block: 0.5rem;\n  padding-left: 1rem;\n  padding-right: 2rem;\n  display: grid;\n  gap: 0.5rem;\n  align-items: center;\n  grid-template-columns: 0.75rem 1fr;\n  font-size: 1rem;\n  line-height: 1rem;\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.5rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.ItemText {\n  grid-column-start: 2;\n}\n\n.ItemIndicator {\n  grid-column-start: 1;\n}\n\n.ItemIndicatorIcon {\n  display: block;\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.Empty:not(:empty) {\n  box-sizing: border-box;\n  font-size: 0.925rem;\n  line-height: 1rem;\n  color: var(--color-gray-600);\n  padding: 1rem;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/combobox/demos/virtualized/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Combobox } from '@base-ui/react/combobox';\nimport { useVirtualizer } from '@tanstack/react-virtual';\nimport styles from './index.module.css';\n\nexport default function ExampleVirtualizedCombobox() {\n  const virtualizerRef = React.useRef<Virtualizer | null>(null);\n\n  return (\n    <Combobox.Root\n      virtualized\n      items={virtualizedItems}\n      itemToStringLabel={getItemLabel}\n      onItemHighlighted={(item, { reason, index }) => {\n        const virtualizer = virtualizerRef.current;\n\n        if (!item || !virtualizer) {\n          return;\n        }\n\n        const isStart = index === 0;\n        const isEnd = index === virtualizer.options.count - 1;\n        const shouldScroll = reason === 'none' || (reason === 'keyboard' && (isStart || isEnd));\n\n        if (shouldScroll) {\n          queueMicrotask(() => {\n            virtualizer.scrollToIndex(index, { align: isEnd ? 'start' : 'end' });\n          });\n        }\n      }}\n    >\n      <label className={styles.Label}>\n        Search 10,000 items\n        <Combobox.Input className={styles.Input} />\n      </label>\n\n      <Combobox.Portal>\n        <Combobox.Positioner className={styles.Positioner} sideOffset={4}>\n          <Combobox.Popup className={styles.Popup}>\n            <Combobox.Empty className={styles.Empty}>No items found.</Combobox.Empty>\n            <Combobox.List className={styles.List}>\n              <VirtualizedList virtualizerRef={virtualizerRef} />\n            </Combobox.List>\n          </Combobox.Popup>\n        </Combobox.Positioner>\n      </Combobox.Portal>\n    </Combobox.Root>\n  );\n}\n\nfunction VirtualizedList({\n  virtualizerRef,\n}: {\n  virtualizerRef: React.RefObject<Virtualizer | null>;\n}) {\n  const filteredItems = Combobox.useFilteredItems<VirtualizedItem>();\n\n  const scrollElementRef = React.useRef<HTMLDivElement | null>(null);\n\n  const virtualizer = useVirtualizer({\n    count: filteredItems.length,\n    getScrollElement: () => scrollElementRef.current,\n    estimateSize: () => 32,\n    overscan: 20,\n    paddingStart: 8,\n    paddingEnd: 8,\n    scrollPaddingEnd: 8,\n    scrollPaddingStart: 8,\n  });\n\n  React.useImperativeHandle(virtualizerRef, () => virtualizer);\n\n  const handleScrollElementRef = React.useCallback(\n    (element: HTMLDivElement | null) => {\n      scrollElementRef.current = element;\n      if (element) {\n        virtualizer.measure();\n      }\n    },\n    [virtualizer],\n  );\n\n  const totalSize = virtualizer.getTotalSize();\n\n  if (!filteredItems.length) {\n    return null;\n  }\n\n  return (\n    <div\n      role=\"presentation\"\n      ref={handleScrollElementRef}\n      className={styles.Scroller}\n      style={{ '--total-size': `${totalSize}px` } as React.CSSProperties}\n    >\n      <div\n        role=\"presentation\"\n        className={styles.VirtualizedPlaceholder}\n        style={{ height: totalSize }}\n      >\n        {virtualizer.getVirtualItems().map((virtualItem) => {\n          const item = filteredItems[virtualItem.index];\n          if (!item) {\n            return null;\n          }\n\n          return (\n            <Combobox.Item\n              key={virtualItem.key}\n              index={virtualItem.index}\n              data-index={virtualItem.index}\n              ref={virtualizer.measureElement}\n              value={item}\n              className={styles.Item}\n              aria-setsize={filteredItems.length}\n              aria-posinset={virtualItem.index + 1}\n              style={{\n                position: 'absolute',\n                top: 0,\n                left: 0,\n                width: '100%',\n                height: virtualItem.size,\n                transform: `translateY(${virtualItem.start}px)`,\n              }}\n            >\n              <Combobox.ItemIndicator className={styles.ItemIndicator}>\n                <CheckIcon className={styles.ItemIndicatorIcon} />\n              </Combobox.ItemIndicator>\n              <div className={styles.ItemText}>{item.name}</div>\n            </Combobox.Item>\n          );\n        })}\n      </div>\n    </div>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\ninterface VirtualizedItem {\n  id: string;\n  name: string;\n}\n\nfunction getItemLabel(item: VirtualizedItem | null) {\n  return item ? item.name : '';\n}\n\nconst virtualizedItems: VirtualizedItem[] = Array.from({ length: 10000 }, (_, index) => {\n  const id = String(index + 1);\n  const indexLabel = id.padStart(4, '0');\n  return { id, name: `Item ${indexLabel}` };\n});\n\ntype Virtualizer = ReturnType<typeof useVirtualizer<HTMLDivElement, Element>>;\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/combobox/demos/virtualized/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoComboboxVirtualized = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/combobox/demos/virtualized/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Combobox } from '@base-ui/react/combobox';\nimport { useVirtualizer } from '@tanstack/react-virtual';\n\nexport default function ExampleVirtualizedCombobox() {\n  const [open, setOpen] = React.useState(false);\n  const virtualizerRef = React.useRef<Virtualizer | null>(null);\n\n  return (\n    <Combobox.Root\n      virtualized\n      items={virtualizedItems}\n      open={open}\n      onOpenChange={setOpen}\n      itemToStringLabel={getItemLabel}\n      onItemHighlighted={(item, { reason, index }) => {\n        const virtualizer = virtualizerRef.current;\n\n        if (!item || !virtualizer) {\n          return;\n        }\n\n        const isStart = index === 0;\n        const isEnd = index === virtualizer.options.count - 1;\n        const shouldScroll = reason === 'none' || (reason === 'keyboard' && (isStart || isEnd));\n\n        if (shouldScroll) {\n          queueMicrotask(() => {\n            virtualizer.scrollToIndex(index, { align: isEnd ? 'start' : 'end' });\n          });\n        }\n      }}\n    >\n      <label className=\"flex flex-col gap-1 text-sm leading-5 font-bold text-gray-900\">\n        Search 10,000 items\n        <Combobox.Input className=\"h-10 w-64 rounded-md font-normal border border-gray-200 pl-3.5 text-base text-gray-900 bg-[canvas] focus:outline-2 focus:-outline-offset-1 focus:outline-blue-800\" />\n      </label>\n\n      <Combobox.Portal>\n        <Combobox.Positioner className=\"outline-none\" sideOffset={4}>\n          <Combobox.Popup className=\"w-[var(--anchor-width)] max-h-[min(22rem,var(--available-height))] max-w-[var(--available-width)] rounded-md bg-[canvas] text-gray-900 outline-1 outline-gray-200 shadow-lg shadow-gray-200 dark:-outline-offset-1 dark:outline-gray-300\">\n            <Combobox.Empty className=\"px-4 py-4 text-[0.925rem] leading-4 text-gray-600 empty:m-0 empty:p-0\">\n              No items found.\n            </Combobox.Empty>\n            <Combobox.List className=\"p-0\">\n              <VirtualizedList open={open} virtualizerRef={virtualizerRef} />\n            </Combobox.List>\n          </Combobox.Popup>\n        </Combobox.Positioner>\n      </Combobox.Portal>\n    </Combobox.Root>\n  );\n}\n\nfunction VirtualizedList({\n  open,\n  virtualizerRef,\n}: {\n  open: boolean;\n  virtualizerRef: React.RefObject<Virtualizer | null>;\n}) {\n  const filteredItems = Combobox.useFilteredItems<VirtualizedItem>();\n\n  const scrollElementRef = React.useRef<HTMLDivElement | null>(null);\n\n  const virtualizer = useVirtualizer({\n    enabled: open,\n    count: filteredItems.length,\n    getScrollElement: () => scrollElementRef.current,\n    estimateSize: () => 32,\n    overscan: 20,\n    paddingStart: 8,\n    paddingEnd: 8,\n    scrollPaddingEnd: 8,\n    scrollPaddingStart: 8,\n  });\n\n  React.useImperativeHandle(virtualizerRef, () => virtualizer);\n\n  const handleScrollElementRef = React.useCallback(\n    (element: HTMLDivElement | null) => {\n      scrollElementRef.current = element;\n      if (element) {\n        virtualizer.measure();\n      }\n    },\n    [virtualizer],\n  );\n\n  const totalSize = virtualizer.getTotalSize();\n\n  if (!filteredItems.length) {\n    return null;\n  }\n\n  return (\n    <div\n      role=\"presentation\"\n      ref={handleScrollElementRef}\n      className=\"h-[min(22rem,var(--total-size))] max-h-[var(--available-height)] overflow-auto overscroll-contain scroll-p-2\"\n      style={{ '--total-size': `${totalSize}px` } as React.CSSProperties}\n    >\n      <div role=\"presentation\" className=\"relative w-full\" style={{ height: totalSize }}>\n        {virtualizer.getVirtualItems().map((virtualItem) => {\n          const item = filteredItems[virtualItem.index];\n          if (!item) {\n            return null;\n          }\n\n          return (\n            <Combobox.Item\n              key={virtualItem.key}\n              index={virtualItem.index}\n              data-index={virtualItem.index}\n              ref={virtualizer.measureElement}\n              value={item}\n              className=\"grid cursor-default grid-cols-[0.75rem_1fr] items-center gap-2 py-2 pr-8 pl-4 text-base leading-4 outline-none select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-2 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-sm data-[highlighted]:before:bg-gray-900\"\n              aria-setsize={filteredItems.length}\n              aria-posinset={virtualItem.index + 1}\n              style={{\n                position: 'absolute',\n                top: 0,\n                left: 0,\n                width: '100%',\n                height: virtualItem.size,\n                transform: `translateY(${virtualItem.start}px)`,\n              }}\n            >\n              <Combobox.ItemIndicator className=\"col-start-1\">\n                <CheckIcon className=\"size-3\" />\n              </Combobox.ItemIndicator>\n              <div className=\"col-start-2\">{item.name}</div>\n            </Combobox.Item>\n          );\n        })}\n      </div>\n    </div>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\ninterface VirtualizedItem {\n  id: string;\n  name: string;\n}\n\nfunction getItemLabel(item: VirtualizedItem | null) {\n  return item ? item.name : '';\n}\n\nconst virtualizedItems: VirtualizedItem[] = Array.from({ length: 10000 }, (_, index) => {\n  const id = String(index + 1);\n  const indexLabel = id.padStart(4, '0');\n  return { id, name: `Item ${indexLabel}` };\n});\n\ntype Virtualizer = ReturnType<typeof useVirtualizer<HTMLDivElement, Element>>;\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/combobox/page.mdx",
    "content": "# Combobox\n\n<Subtitle>An input combined with a list of predefined items to select.</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"A high-quality, unstyled React combobox component that renders an input combined with a list of predefined items to select.\"\n/>\n\nimport { DemoComboboxHero } from './demos/hero';\n\n<DemoComboboxHero />\n\n## Usage guidelines\n\n- **Combobox is a filterable Select**: Use Combobox when the input is restricted to a set of predefined selectable items, similar to [Select](/react/components/select) but whose items are filterable using an input. Prefer using Combobox over Select when the number of items is sufficiently large to warrant filtering.\n- **Avoid for simple search widgets**: Combobox does not allow free-form text input. For search widgets, consider using [Autocomplete](/react/components/autocomplete) instead.\n- **Avoid when not rendering an input**: Use [Select](/react/components/select) instead of Combobox if no input is being rendered, which includes accessibility features specific to a listbox without an input.\n- **Form controls must have an accessible name**: If `<Combobox.Input>` is the form control, label it with a native `<label>` or `<Field.Label>`, or provide an `aria-label` when no visible label is rendered. `<Combobox.Label>` labels `<Combobox.Trigger>` and is intended for the [input-inside-popup](#input-inside-popup) pattern, where the trigger is the form control. See the [forms guide](/react/handbook/forms).\n\n## Anatomy\n\nImport the components and place them together:\n\n```jsx title=\"Anatomy\"\nimport { Combobox } from '@base-ui/react/combobox';\n\n<Combobox.Root>\n  <Combobox.Label />\n\n  <Combobox.InputGroup>\n    <Combobox.Input />\n    <Combobox.Trigger />\n    <Combobox.Icon />\n    <Combobox.Clear />\n    <Combobox.Value />\n\n    <Combobox.Chips>\n      <Combobox.Chip>\n        <Combobox.ChipRemove />\n      </Combobox.Chip>\n    </Combobox.Chips>\n  </Combobox.InputGroup>\n\n  <Combobox.Portal>\n    <Combobox.Backdrop />\n    <Combobox.Positioner>\n      <Combobox.Popup>\n        <Combobox.Arrow />\n\n        <Combobox.Status />\n        <Combobox.Empty />\n\n        <Combobox.List>\n          <Combobox.Row>\n            <Combobox.Item>\n              <Combobox.ItemIndicator />\n            </Combobox.Item>\n          </Combobox.Row>\n\n          <Combobox.Separator />\n\n          <Combobox.Group>\n            <Combobox.GroupLabel />\n          </Combobox.Group>\n\n          <Combobox.Collection />\n        </Combobox.List>\n      </Combobox.Popup>\n    </Combobox.Positioner>\n  </Combobox.Portal>\n</Combobox.Root>;\n```\n\n## TypeScript\n\nCombobox infers the item type from the `defaultValue` or `value` props passed to `<Combobox.Root>`.\nThe type of items held in the `items` array must also match the `value` prop type passed to `<Combobox.Item>`.\n\n## Examples\n\n### Typed wrapper component\n\nThe following example shows a typed wrapper around the Combobox component with correct type inference and type safety:\n\n```tsx title=\"Specifying generic type parameters\"\nimport * as React from 'react';\nimport { Combobox } from '@base-ui/react/combobox';\n\nexport function MyCombobox<Value, Multiple extends boolean | undefined = false>(\n  props: Combobox.Root.Props<Value, Multiple>,\n): React.JSX.Element {\n  return <Combobox.Root {...props}>{/* ... */}</Combobox.Root>;\n}\n```\n\n### Multiple select\n\nThe combobox can allow multiple selections by adding the `multiple` prop to `<Combobox.Root>`.\nSelection chips are rendered with `<Combobox.Chip>` inside the input that can be removed.\n\nimport { DemoComboboxMultiple } from './demos/multiple';\n\n<DemoComboboxMultiple compact />\n\n### Input inside popup\n\n`<Combobox.Input>` can be rendered inside `<Combobox.Popup>` to create a searchable select popup.\n\nimport { DemoComboboxInputInsidePopup } from './demos/input-inside-popup';\n\n<DemoComboboxInputInsidePopup compact />\n\nUse `<Combobox.Label>` to provide a visible label for the combobox trigger in this pattern:\n\n```tsx title=\"Using Combobox.Label to label a combobox\" {2}\n<Combobox.Root>\n  <Combobox.Label>Favorite fruit</Combobox.Label>\n  {/* ... */}\n</Combobox.Root>\n```\n\n`<Combobox.Label>` renders a `<div>`, so clicking it focuses the combobox trigger without opening the popup.\n\n### Grouped\n\nOrganize related options with `<Combobox.Group>` and `<Combobox.GroupLabel>` to add section headings inside the popup.\n\nGroups are represented by an array of objects with an `items` property, which itself is an array of individual items for each group. An extra property, such as `value`, can be provided for the heading text when rendering the group label.\n\n```tsx title=\"Example\" {3,9,13}\ninterface ProduceGroupItem {\n  value: string;\n  items: string[];\n}\n\nconst groups: ProduceGroupItem[] = [\n  {\n    value: 'Fruits',\n    items: ['Apple', 'Banana', 'Orange'],\n  },\n  {\n    value: 'Vegetables',\n    items: ['Carrot', 'Lettuce', 'Spinach'],\n  },\n];\n```\n\nimport { DemoComboboxGrouped } from './demos/grouped';\n\n<DemoComboboxGrouped compact />\n\n### Async search (single)\n\nLoad items from a remote source by fetching on input changes. Keep the selected item in the `items` list so it remains available while new results stream in. This pattern avoids needing to load items upfront.\n\nimport { DemoComboboxAsyncSingle } from './demos/async-single';\n\n<DemoComboboxAsyncSingle compact />\n\n### Async search (multiple)\n\nLoad items from a remote source by fetching on input changes while supporting multiple selections. Selected items remain available in the list while new matches stream in. This pattern avoids needing to load items upfront.\n\nimport { DemoComboboxAsyncMultiple } from './demos/async-multiple';\n\n<DemoComboboxAsyncMultiple compact />\n\n### Creatable\n\nCreate a new item when the filter matches no items, opening a creation `<Dialog>`.\n\nimport { DemoComboboxCreatable } from './demos/creatable';\n\n<DemoComboboxCreatable compact />\n\n### Virtualized\n\nEfficiently handle large datasets using a virtualization library like `@tanstack/react-virtual`.\n\nimport { DemoComboboxVirtualized } from './demos/virtualized';\n\n<DemoComboboxVirtualized compact />\n\n## API reference\n\n<Reference\n  component=\"Combobox\"\n  parts=\"Root, Label, Value, Icon, Input, InputGroup, Clear, Trigger, Chips, Chip, ChipRemove, List, Portal, Backdrop, Positioner, Popup, Arrow, Status, Empty, Collection, Row, Item, ItemIndicator, Group, GroupLabel, Separator\"\n/>\n\n## useFilter\n\nMatches items against a query using `Intl.Collator` for robust string matching.\nThis hook is used when externally filtering items.\nPass the result to the `filter` prop of `<Combobox.Root>`.\n\n### Input parameters\n\nAccepts all `Intl.CollatorOptions`, plus the following options:\n\n<PropsReferenceTable\n  data={{\n    locale: {\n      type: 'Intl.LocalesArgument',\n      description: 'The locale to use for string comparison.',\n    },\n    multiple: {\n      type: 'boolean',\n      description: 'Whether the combobox is in multiple selection mode.',\n      default: 'false',\n    },\n    value: {\n      type: 'any',\n      description:\n        'The current value of the combobox. For single selection, pass this so all items are visible when the query is empty or matches the selection.',\n    },\n  }}\n/>\n\n### Return value\n\n<PropsReferenceTable\n  type=\"return\"\n  data={{\n    contains: {\n      type: '(itemValue: any, query: string, itemToString?: (itemValue) => string) => boolean',\n      description: 'Returns whether the item matches the query anywhere.',\n    },\n    startsWith: {\n      type: '(itemValue: any, query: string, itemToString?: (itemValue) => string) => boolean',\n      description: 'Returns whether the item starts with the query.',\n    },\n    endsWith: {\n      type: '(itemValue: any, query: string, itemToString?: (itemValue) => string) => boolean',\n      description: 'Returns whether the item ends with the query.',\n    },\n  }}\n/>\n\nexport const metadata = {\n  keywords: [\n    'React Combobox',\n    'Combobox Component',\n    'Combo Box',\n    'Filterable Select',\n    'Searchable Dropdown',\n    'Editable Select',\n    'Typeahead Select',\n    'Autocomplete Dropdown',\n    'Tags Input',\n    'Chip Input',\n    'Token Input',\n    'Multi-Value Input',\n    'Multiple Select Combobox',\n    'Async Combobox',\n    'Accessible Combobox',\n    'Base UI',\n  ],\n};\n\n## useFilteredItems\n\nReturns the internally filtered items when called inside `<Combobox.Root>`.\n\n### Return value\n\n<PropsReferenceTable\n  type=\"return\"\n  data={{\n    filteredItems: {\n      type: 'any[]',\n      description: 'The filtered items.',\n    },\n  }}\n/>\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/context-menu/demos/hero/css-modules/index.module.css",
    "content": ".Trigger {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 15rem;\n  height: 12rem;\n  border: 1px solid var(--color-gray-300);\n  color: var(--color-gray-900);\n  border-radius: 0.375rem;\n  font-weight: 400;\n  -webkit-user-select: none;\n  user-select: none;\n}\n\n.Positioner {\n  outline: 0;\n}\n\n.Popup {\n  box-sizing: border-box;\n  padding-block: 0.25rem;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n\n  &[data-ending-style] {\n    opacity: 0;\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Item {\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  padding-block: 0.5rem;\n  padding-left: 1rem;\n  padding-right: 2rem;\n  display: flex;\n  font-size: 0.875rem;\n  line-height: 1rem;\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.25rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.Separator {\n  margin: 0.375rem 1rem;\n  height: 1px;\n  background-color: var(--color-gray-200);\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/context-menu/demos/hero/css-modules/index.tsx",
    "content": "import { ContextMenu } from '@base-ui/react/context-menu';\nimport styles from './index.module.css';\n\nexport default function ExampleMenu() {\n  return (\n    <ContextMenu.Root>\n      <ContextMenu.Trigger className={styles.Trigger}>Right click here</ContextMenu.Trigger>\n      <ContextMenu.Portal>\n        <ContextMenu.Positioner className={styles.Positioner}>\n          <ContextMenu.Popup className={styles.Popup}>\n            <ContextMenu.Item className={styles.Item}>Add to Library</ContextMenu.Item>\n            <ContextMenu.Item className={styles.Item}>Add to Playlist</ContextMenu.Item>\n            <ContextMenu.Separator className={styles.Separator} />\n            <ContextMenu.Item className={styles.Item}>Play Next</ContextMenu.Item>\n            <ContextMenu.Item className={styles.Item}>Play Last</ContextMenu.Item>\n            <ContextMenu.Separator className={styles.Separator} />\n            <ContextMenu.Item className={styles.Item}>Favorite</ContextMenu.Item>\n            <ContextMenu.Item className={styles.Item}>Share</ContextMenu.Item>\n          </ContextMenu.Popup>\n        </ContextMenu.Positioner>\n      </ContextMenu.Portal>\n    </ContextMenu.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/context-menu/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoContextMenuHero = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/context-menu/demos/hero/tailwind/index.tsx",
    "content": "import { ContextMenu } from '@base-ui/react/context-menu';\n\nexport default function ExampleMenu() {\n  return (\n    <ContextMenu.Root>\n      <ContextMenu.Trigger className=\"flex h-[12rem] w-[15rem] items-center justify-center rounded-sm border border-gray-300 text-gray-900 select-none font-normal\">\n        Right click here\n      </ContextMenu.Trigger>\n      <ContextMenu.Portal>\n        <ContextMenu.Positioner className=\"outline-hidden\">\n          <ContextMenu.Popup className=\"origin-[var(--transform-origin)] rounded-md bg-[canvas] py-1 text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 transition-[opacity] data-[ending-style]:opacity-0 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300\">\n            <ContextMenu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\">\n              Add to Library\n            </ContextMenu.Item>\n            <ContextMenu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\">\n              Add to Playlist\n            </ContextMenu.Item>\n            <ContextMenu.Separator className=\"mx-4 my-1.5 h-px bg-gray-200\" />\n            <ContextMenu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\">\n              Play Next\n            </ContextMenu.Item>\n            <ContextMenu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\">\n              Play Last\n            </ContextMenu.Item>\n            <ContextMenu.Separator className=\"mx-4 my-1.5 h-px bg-gray-200\" />\n            <ContextMenu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\">\n              Favorite\n            </ContextMenu.Item>\n            <ContextMenu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\">\n              Share\n            </ContextMenu.Item>\n          </ContextMenu.Popup>\n        </ContextMenu.Positioner>\n      </ContextMenu.Portal>\n    </ContextMenu.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/context-menu/demos/submenu/css-modules/index.module.css",
    "content": ".Trigger {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 15rem;\n  height: 12rem;\n  border: 1px solid var(--color-gray-300);\n  color: var(--color-gray-900);\n  border-radius: 0.375rem;\n  font-weight: 400;\n  -webkit-user-select: none;\n  user-select: none;\n}\n\n.Positioner {\n  outline: 0;\n}\n\n.Popup,\n.SubmenuPopup {\n  box-sizing: border-box;\n  padding-block: 0.25rem;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n\n  &[data-ending-style] {\n    opacity: 0;\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.SubmenuPopup {\n  &[data-starting-style],\n  &[data-ending-style] {\n    transform: scale(0.9);\n    opacity: 0;\n  }\n}\n\n.Arrow {\n  display: flex;\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n\n.Item,\n.SubmenuTrigger {\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  padding-block: 0.5rem;\n  padding-left: 1rem;\n  padding-right: 2rem;\n  display: flex;\n  font-size: 0.875rem;\n  line-height: 1rem;\n\n  &[data-popup-open] {\n    z-index: 0;\n    position: relative;\n  }\n\n  &[data-popup-open]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.25rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-100);\n  }\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.25rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.SubmenuTrigger {\n  align-items: center;\n  justify-content: space-between;\n  gap: 1rem;\n  padding-right: 1rem;\n}\n\n.Separator {\n  margin: 0.375rem 1rem;\n  height: 1px;\n  background-color: var(--color-gray-200);\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/context-menu/demos/submenu/css-modules/index.tsx",
    "content": "import * as React from 'react';\nimport { ContextMenu } from '@base-ui/react/context-menu';\nimport { Menu } from '@base-ui/react/menu';\nimport styles from './index.module.css';\n\nexport default function ExampleContextMenu() {\n  return (\n    <ContextMenu.Root>\n      <ContextMenu.Trigger className={styles.Trigger}>Right click here</ContextMenu.Trigger>\n      <ContextMenu.Portal>\n        <ContextMenu.Positioner className={styles.Positioner}>\n          <ContextMenu.Popup className={styles.Popup}>\n            <ContextMenu.Item className={styles.Item}>Add to Library</ContextMenu.Item>\n\n            <ContextMenu.SubmenuRoot>\n              <ContextMenu.SubmenuTrigger className={styles.SubmenuTrigger}>\n                Add to Playlist\n                <ChevronRightIcon />\n              </ContextMenu.SubmenuTrigger>\n              <ContextMenu.Portal>\n                <ContextMenu.Positioner\n                  className={styles.Positioner}\n                  alignOffset={-4}\n                  sideOffset={-4}\n                >\n                  <ContextMenu.Popup className={styles.SubmenuPopup}>\n                    <ContextMenu.Item className={styles.Item}>Get Up!</ContextMenu.Item>\n                    <ContextMenu.Item className={styles.Item}>Inside Out</ContextMenu.Item>\n                    <ContextMenu.Item className={styles.Item}>Night Beats</ContextMenu.Item>\n                    <Menu.Separator className={styles.Separator} />\n                    <ContextMenu.Item className={styles.Item}>New playlist…</ContextMenu.Item>\n                  </ContextMenu.Popup>\n                </ContextMenu.Positioner>\n              </ContextMenu.Portal>\n            </ContextMenu.SubmenuRoot>\n\n            <ContextMenu.Separator className={styles.Separator} />\n            <ContextMenu.Item className={styles.Item}>Play Next</ContextMenu.Item>\n            <ContextMenu.Item className={styles.Item}>Play Last</ContextMenu.Item>\n            <ContextMenu.Separator className={styles.Separator} />\n            <ContextMenu.Item className={styles.Item}>Favorite</ContextMenu.Item>\n            <ContextMenu.Item className={styles.Item}>Share</ContextMenu.Item>\n          </ContextMenu.Popup>\n        </ContextMenu.Positioner>\n      </ContextMenu.Portal>\n    </ContextMenu.Root>\n  );\n}\n\nfunction ChevronRightIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M3.5 9L7.5 5L3.5 1\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/context-menu/demos/submenu/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoContextMenuSubmenu = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/context-menu/demos/submenu/tailwind/index.tsx",
    "content": "import * as React from 'react';\nimport { ContextMenu } from '@base-ui/react/context-menu';\n\nexport default function ExampleContextMenu() {\n  return (\n    <ContextMenu.Root>\n      <ContextMenu.Trigger className=\"flex h-[12rem] w-[15rem] items-center justify-center rounded-sm border border-gray-300 text-gray-900 select-none font-normal\">\n        Right click here\n      </ContextMenu.Trigger>\n      <ContextMenu.Portal>\n        <ContextMenu.Positioner className=\"outline-hidden\">\n          <ContextMenu.Popup className=\"origin-[var(--transform-origin)] rounded-md bg-[canvas] py-1 text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 transition-[opacity] data-[ending-style]:opacity-0 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300\">\n            <ContextMenu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900 data-[popup-open]:relative data-[popup-open]:z-0 data-[popup-open]:before:absolute data-[popup-open]:before:inset-x-1 data-[popup-open]:before:inset-y-0 data-[popup-open]:before:z-[-1] data-[popup-open]:before:rounded-xs data-[popup-open]:before:bg-gray-100 data-[highlighted]:data-[popup-open]:before:bg-gray-900\">\n              Add to Library\n            </ContextMenu.Item>\n\n            <ContextMenu.SubmenuRoot>\n              <ContextMenu.SubmenuTrigger className=\"flex cursor-default items-center justify-between gap-4 py-2 pr-4 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900 data-[popup-open]:relative data-[popup-open]:z-0 data-[popup-open]:before:absolute data-[popup-open]:before:inset-x-1 data-[popup-open]:before:inset-y-0 data-[popup-open]:before:z-[-1] data-[popup-open]:before:rounded-xs data-[popup-open]:before:bg-gray-100 data-[highlighted]:data-[popup-open]:before:bg-gray-900\">\n                Add to Playlist <ChevronRightIcon />\n              </ContextMenu.SubmenuTrigger>\n              <ContextMenu.Portal>\n                <ContextMenu.Positioner className=\"outline-hidden\" alignOffset={-4} sideOffset={-4}>\n                  <ContextMenu.Popup className=\"origin-[var(--transform-origin)] rounded-md bg-[canvas] py-1 text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 transition-[transform,scale,opacity] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300\">\n                    <ContextMenu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900 data-[popup-open]:relative data-[popup-open]:z-0 data-[popup-open]:before:absolute data-[popup-open]:before:inset-x-1 data-[popup-open]:before:inset-y-0 data-[popup-open]:before:z-[-1] data-[popup-open]:before:rounded-xs data-[popup-open]:before:bg-gray-100 data-[highlighted]:data-[popup-open]:before:bg-gray-900\">\n                      Get Up!\n                    </ContextMenu.Item>\n                    <ContextMenu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900 data-[popup-open]:relative data-[popup-open]:z-0 data-[popup-open]:before:absolute data-[popup-open]:before:inset-x-1 data-[popup-open]:before:inset-y-0 data-[popup-open]:before:z-[-1] data-[popup-open]:before:rounded-xs data-[popup-open]:before:bg-gray-100 data-[highlighted]:data-[popup-open]:before:bg-gray-900\">\n                      Inside Out\n                    </ContextMenu.Item>\n                    <ContextMenu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900 data-[popup-open]:relative data-[popup-open]:z-0 data-[popup-open]:before:absolute data-[popup-open]:before:inset-x-1 data-[popup-open]:before:inset-y-0 data-[popup-open]:before:z-[-1] data-[popup-open]:before:rounded-xs data-[popup-open]:before:bg-gray-100 data-[highlighted]:data-[popup-open]:before:bg-gray-900\">\n                      Night Beats\n                    </ContextMenu.Item>\n                    <ContextMenu.Separator className=\"mx-4 my-1.5 h-px bg-gray-200\" />\n                    <ContextMenu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900 data-[popup-open]:relative data-[popup-open]:z-0 data-[popup-open]:before:absolute data-[popup-open]:before:inset-x-1 data-[popup-open]:before:inset-y-0 data-[popup-open]:before:z-[-1] data-[popup-open]:before:rounded-xs data-[popup-open]:before:bg-gray-100 data-[highlighted]:data-[popup-open]:before:bg-gray-900\">\n                      New playlist…\n                    </ContextMenu.Item>\n                  </ContextMenu.Popup>\n                </ContextMenu.Positioner>\n              </ContextMenu.Portal>\n            </ContextMenu.SubmenuRoot>\n\n            <ContextMenu.Separator className=\"mx-4 my-1.5 h-px bg-gray-200\" />\n\n            <ContextMenu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900 data-[popup-open]:relative data-[popup-open]:z-0 data-[popup-open]:before:absolute data-[popup-open]:before:inset-x-1 data-[popup-open]:before:inset-y-0 data-[popup-open]:before:z-[-1] data-[popup-open]:before:rounded-xs data-[popup-open]:before:bg-gray-100 data-[highlighted]:data-[popup-open]:before:bg-gray-900\">\n              Play Next\n            </ContextMenu.Item>\n            <ContextMenu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900 data-[popup-open]:relative data-[popup-open]:z-0 data-[popup-open]:before:absolute data-[popup-open]:before:inset-x-1 data-[popup-open]:before:inset-y-0 data-[popup-open]:before:z-[-1] data-[popup-open]:before:rounded-xs data-[popup-open]:before:bg-gray-100 data-[highlighted]:data-[popup-open]:before:bg-gray-900\">\n              Play Last\n            </ContextMenu.Item>\n            <ContextMenu.Separator className=\"mx-4 my-1.5 h-px bg-gray-200\" />\n            <ContextMenu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900 data-[popup-open]:relative data-[popup-open]:z-0 data-[popup-open]:before:absolute data-[popup-open]:before:inset-x-1 data-[popup-open]:before:inset-y-0 data-[popup-open]:before:z-[-1] data-[popup-open]:before:rounded-xs data-[popup-open]:before:bg-gray-100 data-[highlighted]:data-[popup-open]:before:bg-gray-900\">\n              Favorite\n            </ContextMenu.Item>\n            <ContextMenu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900 data-[popup-open]:relative data-[popup-open]:z-0 data-[popup-open]:before:absolute data-[popup-open]:before:inset-x-1 data-[popup-open]:before:inset-y-0 data-[popup-open]:before:z-[-1] data-[popup-open]:before:rounded-xs data-[popup-open]:before:bg-gray-100 data-[highlighted]:data-[popup-open]:before:bg-gray-900\">\n              Share\n            </ContextMenu.Item>\n          </ContextMenu.Popup>\n        </ContextMenu.Positioner>\n      </ContextMenu.Portal>\n    </ContextMenu.Root>\n  );\n}\n\nfunction ChevronRightIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M3.5 9L7.5 5L3.5 1\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/context-menu/page.mdx",
    "content": "# Context Menu\n\n<Subtitle>A menu that appears at the pointer on right click or long press.</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"A high-quality, unstyled React context menu component that appears at the pointer on right click or long press.\"\n/>\n\nimport { DemoContextMenuHero } from './demos/hero';\n\n<DemoContextMenuHero />\n\n## Anatomy\n\nImport the components and place them together:\n\n```jsx title=\"Anatomy\"\nimport { ContextMenu } from '@base-ui/react/context-menu';\n\n<ContextMenu.Root>\n  <ContextMenu.Trigger />\n  <ContextMenu.Portal>\n    <ContextMenu.Backdrop />\n    <ContextMenu.Positioner>\n      <ContextMenu.Popup>\n        <ContextMenu.Arrow />\n        <ContextMenu.Item />\n        <ContextMenu.LinkItem />\n        <ContextMenu.Separator />\n        <ContextMenu.Group>\n          <ContextMenu.GroupLabel />\n        </ContextMenu.Group>\n        <ContextMenu.RadioGroup>\n          <ContextMenu.RadioItem />\n        </ContextMenu.RadioGroup>\n        <ContextMenu.CheckboxItem />\n        <ContextMenu.SubmenuRoot>\n          <ContextMenu.SubmenuTrigger />\n        </ContextMenu.SubmenuRoot>\n      </ContextMenu.Popup>\n    </ContextMenu.Positioner>\n  </ContextMenu.Portal>\n</ContextMenu.Root>;\n```\n\n## Examples\n\n[Menu](/react/components/menu#examples) displays additional demos, many of which apply to the context menu as well.\n\n### Nested menu\n\nTo create a submenu, create a `<ContextMenu.SubmenuRoot>` inside the parent context menu. Use the `<ContextMenu.SubmenuTrigger>` part for the menu item that opens the nested menu.\n\nimport { DemoContextMenuSubmenu } from './demos/submenu';\n\n<DemoContextMenuSubmenu />\n\n## API reference\n\n<Reference component=\"ContextMenu\" parts=\"Root, Trigger\" />\n<Reference\n  component=\"Menu\"\n  parts=\"Portal, Positioner, Popup, Arrow, Item, LinkItem, SubmenuRoot, SubmenuTrigger, Group, GroupLabel, RadioGroup, RadioItem, RadioItemIndicator, CheckboxItem, CheckboxItemIndicator, Separator\"\n/>\n\nexport const metadata = {\n  keywords: [\n    'React Context Menu',\n    'Context Menu Component',\n    'Right Click Menu',\n    'Secondary Click Menu',\n    'Pointer Menu',\n    'Touch Hold Menu',\n    'Long Press Menu',\n    'Nested Menu React',\n    'Accessible Context Menu',\n    'Headless React Components',\n    'Base UI',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/dialog/demos/_index.module.css",
    "content": ".Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Backdrop {\n  position: fixed;\n  min-height: 100dvh;\n  inset: 0;\n  background-color: black;\n  opacity: 0.2;\n  transition: opacity 150ms cubic-bezier(0.45, 1.005, 0, 1.005);\n\n  /* iOS 26+: Ensure the backdrop covers the entire visible viewport. */\n  @supports (-webkit-touch-callout: none) {\n    position: absolute;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    opacity: 0.7;\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n  }\n}\n\n.Popup {\n  box-sizing: border-box;\n  position: fixed;\n  top: 50%;\n  left: 50%;\n  transform: translate(-50%, -50%);\n  width: 24rem;\n  max-width: calc(100vw - 3rem);\n  margin-top: -2rem;\n  padding: 1.5rem;\n  border-radius: 0.5rem;\n  outline: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-50);\n  color: var(--color-gray-900);\n  transition: all 150ms;\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: translate(-50%, -50%) scale(0.9);\n  }\n}\n\n.Title {\n  margin-top: -0.375rem;\n  margin-bottom: 0.25rem;\n  font-size: 1.125rem;\n  line-height: 1.75rem;\n  letter-spacing: -0.0025em;\n  font-weight: 700;\n}\n\n.Description {\n  margin: 0 0 1.5rem;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-600);\n}\n\n.Actions {\n  display: flex;\n  justify-content: end;\n  gap: 1rem;\n}\n\n.Container {\n  display: flex;\n  gap: 8px;\n  flex-wrap: wrap;\n  justify-content: center;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/dialog/demos/close-confirmation/css-modules/index.module.css",
    "content": ".Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Backdrop {\n  position: fixed;\n  min-height: 100dvh;\n  inset: 0;\n  background-color: black;\n  opacity: 0.2;\n  transition: opacity 150ms cubic-bezier(0.45, 1.005, 0, 1.005);\n\n  /* iOS 26+: Ensure the backdrop covers the entire visible viewport. */\n  @supports (-webkit-touch-callout: none) {\n    position: absolute;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    opacity: 0.7;\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n  }\n}\n\n.Popup {\n  box-sizing: border-box;\n  position: fixed;\n  top: 50%;\n  left: 50%;\n  width: 24rem;\n  max-width: calc(100vw - 3rem);\n  margin-top: -2rem;\n  padding: 1.5rem;\n  border-radius: 0.5rem;\n  outline: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-50);\n  color: var(--color-gray-900);\n  transition: all 150ms;\n\n  transform: translate(-50%, -50%) scale(calc(1 - 0.1 * var(--nested-dialogs)));\n  translate: 0 calc(0px + 1.25rem * var(--nested-dialogs));\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n  }\n\n  &[data-nested-dialog-open] {\n    &::after {\n      content: '';\n      inset: 0;\n      position: absolute;\n      border-radius: inherit;\n      background-color: rgb(0 0 0 / 0.05);\n    }\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: translate(-50%, -50%) scale(0.9);\n  }\n}\n\n.Title {\n  margin-top: -0.375rem;\n  margin-bottom: 0.25rem;\n  font-size: 1.125rem;\n  line-height: 1.75rem;\n  letter-spacing: -0.0025em;\n  font-weight: 700;\n}\n\n.Description {\n  margin: 0 0 1.5rem;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-600);\n}\n\n.Actions {\n  display: flex;\n  justify-content: end;\n  gap: 1rem;\n}\n\n.TextareaContainer {\n  display: flex;\n  flex-direction: column;\n  gap: 1.5rem;\n  margin-top: 1rem;\n}\n\n.Textarea {\n  box-sizing: border-box;\n  padding-block: 0.5rem;\n  padding-inline: 0.875rem;\n  margin: 0;\n  border: 1px solid var(--color-gray-200);\n  width: 100%;\n  min-height: 12rem;\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  background-color: transparent;\n  color: var(--color-gray-900);\n\n  &:focus {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/dialog/demos/close-confirmation/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { AlertDialog } from '@base-ui/react/alert-dialog';\nimport { Dialog } from '@base-ui/react/dialog';\nimport styles from './index.module.css';\n\nexport default function ExampleDialog() {\n  const [dialogOpen, setDialogOpen] = React.useState(false);\n  const [confirmationOpen, setConfirmationOpen] = React.useState(false);\n  const [textareaValue, setTextareaValue] = React.useState('');\n  const titleId = React.useId();\n\n  return (\n    <Dialog.Root\n      open={dialogOpen}\n      onOpenChange={(open) => {\n        // Show the close confirmation if there’s text in the textarea\n        if (!open && textareaValue) {\n          setConfirmationOpen(true);\n        } else {\n          // Reset the text area value\n          setTextareaValue('');\n          // Open or close the dialog normally\n          setDialogOpen(open);\n        }\n      }}\n    >\n      <Dialog.Trigger className={styles.Button}>Tweet</Dialog.Trigger>\n      <Dialog.Portal>\n        <Dialog.Backdrop className={styles.Backdrop} />\n        <Dialog.Popup className={styles.Popup}>\n          <Dialog.Title id={titleId} className={styles.Title}>\n            New tweet\n          </Dialog.Title>\n          <form\n            className={styles.TextareaContainer}\n            onSubmit={(event) => {\n              event.preventDefault();\n              // Close the dialog when submitting\n              setDialogOpen(false);\n            }}\n          >\n            <textarea\n              aria-labelledby={titleId}\n              required\n              className={styles.Textarea}\n              placeholder=\"What’s on your mind?\"\n              value={textareaValue}\n              onChange={(event) => setTextareaValue(event.target.value)}\n            />\n            <div className={styles.Actions}>\n              <Dialog.Close className={styles.Button}>Cancel</Dialog.Close>\n              <button type=\"submit\" className={styles.Button}>\n                Tweet\n              </button>\n            </div>\n          </form>\n        </Dialog.Popup>\n      </Dialog.Portal>\n\n      {/* Confirmation dialog */}\n      <AlertDialog.Root open={confirmationOpen} onOpenChange={setConfirmationOpen}>\n        <AlertDialog.Portal>\n          <AlertDialog.Popup className={styles.Popup}>\n            <AlertDialog.Title className={styles.Title}>Discard tweet?</AlertDialog.Title>\n            <AlertDialog.Description className={styles.Description}>\n              Your tweet will be lost.\n            </AlertDialog.Description>\n            <div className={styles.Actions}>\n              <AlertDialog.Close className={styles.Button}>Go back</AlertDialog.Close>\n              <button\n                type=\"button\"\n                className={styles.Button}\n                onClick={() => {\n                  setConfirmationOpen(false);\n                  setDialogOpen(false);\n                }}\n              >\n                Discard\n              </button>\n            </div>\n          </AlertDialog.Popup>\n        </AlertDialog.Portal>\n      </AlertDialog.Root>\n    </Dialog.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/dialog/demos/close-confirmation/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoDialogCloseConfirmation = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/dialog/demos/close-confirmation/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { AlertDialog } from '@base-ui/react/alert-dialog';\nimport { Dialog } from '@base-ui/react/dialog';\n\nexport default function ExampleDialog() {\n  const [dialogOpen, setDialogOpen] = React.useState(false);\n  const [confirmationOpen, setConfirmationOpen] = React.useState(false);\n  const [textareaValue, setTextareaValue] = React.useState('');\n  const titleId = React.useId();\n\n  return (\n    <Dialog.Root\n      open={dialogOpen}\n      onOpenChange={(open) => {\n        // Show the close confirmation if there’s text in the textarea\n        if (!open && textareaValue) {\n          setConfirmationOpen(true);\n        } else {\n          // Reset the text area value\n          setTextareaValue('');\n          // Open or close the dialog normally\n          setDialogOpen(open);\n        }\n      }}\n    >\n      <Dialog.Trigger className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n        Tweet\n      </Dialog.Trigger>\n      <Dialog.Portal>\n        <Dialog.Backdrop className=\"fixed inset-0 min-h-dvh bg-black opacity-20 transition-all duration-150 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0 dark:opacity-70 supports-[-webkit-touch-callout:none]:absolute\" />\n        <Dialog.Popup className=\"fixed top-[calc(50%+1.25rem*var(--nested-dialogs))] left-1/2 -mt-8 w-96 max-w-[calc(100vw-3rem)] -translate-x-1/2 -translate-y-1/2 scale-[calc(1-0.1*var(--nested-dialogs))] rounded-lg bg-gray-50 p-6 text-gray-900 outline-1 outline-gray-200 transition-all duration-150 data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[nested-dialog-open]:after:absolute data-[nested-dialog-open]:after:inset-0 data-[nested-dialog-open]:after:rounded-[inherit] data-[nested-dialog-open]:after:bg-black/5 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 dark:outline-gray-300\">\n          <Dialog.Title id={titleId} className=\"-mt-1.5 mb-1 text-lg font-bold\">\n            New tweet\n          </Dialog.Title>\n          <form\n            className=\"mt-4 flex flex-col gap-6\"\n            onSubmit={(event) => {\n              event.preventDefault();\n              // Close the dialog when submitting\n              setDialogOpen(false);\n            }}\n          >\n            <textarea\n              aria-labelledby={titleId}\n              required\n              className=\"min-h-48 w-full rounded-md border border-gray-200 px-3.5 py-2 text-base font-normal text-gray-900 focus:outline-2 focus:-outline-offset-1 focus:outline-blue-800\"\n              placeholder=\"What’s on your mind?\"\n              value={textareaValue}\n              onChange={(event) => setTextareaValue(event.target.value)}\n            />\n            <div className=\"flex justify-end gap-4\">\n              <Dialog.Close className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n                Cancel\n              </Dialog.Close>\n              <button\n                type=\"submit\"\n                className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\"\n              >\n                Tweet\n              </button>\n            </div>\n          </form>\n        </Dialog.Popup>\n      </Dialog.Portal>\n\n      {/* Confirmation dialog */}\n      <AlertDialog.Root open={confirmationOpen} onOpenChange={setConfirmationOpen}>\n        <AlertDialog.Portal>\n          <AlertDialog.Popup className=\"fixed top-[calc(50%+1.25rem*var(--nested-dialogs))] left-1/2 -mt-8 w-96 max-w-[calc(100vw-3rem)] -translate-x-1/2 -translate-y-1/2 scale-[calc(1-0.1*var(--nested-dialogs))] rounded-lg bg-gray-50 p-6 text-gray-900 outline-1 outline-gray-200 transition-all duration-150 data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[nested-dialog-open]:after:absolute data-[nested-dialog-open]:after:inset-0 data-[nested-dialog-open]:after:rounded-[inherit] data-[nested-dialog-open]:after:bg-black/5 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 dark:outline-gray-300\">\n            <AlertDialog.Title className=\"-mt-1.5 mb-1 text-lg font-bold\">\n              Discard tweet?\n            </AlertDialog.Title>\n            <AlertDialog.Description className=\"mb-6 text-base text-gray-600\">\n              Your tweet will be lost.\n            </AlertDialog.Description>\n            <div className=\"flex items-center justify-end gap-4\">\n              <AlertDialog.Close className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n                Go back\n              </AlertDialog.Close>\n              <button\n                type=\"button\"\n                className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\"\n                onClick={() => {\n                  setConfirmationOpen(false);\n                  setDialogOpen(false);\n                }}\n              >\n                Discard\n              </button>\n            </div>\n          </AlertDialog.Popup>\n        </AlertDialog.Portal>\n      </AlertDialog.Root>\n    </Dialog.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/dialog/demos/detached-triggers-controlled/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Dialog } from '@base-ui/react/dialog';\nimport styles from '../../_index.module.css';\n\nconst demoDialog = Dialog.createHandle<number>();\n\nexport default function DialogDetachedTriggersControlledDemo() {\n  const [open, setOpen] = React.useState(false);\n  const [triggerId, setTriggerId] = React.useState<string | null>(null);\n\n  const handleOpenChange = (isOpen: boolean, eventDetails: Dialog.Root.ChangeEventDetails) => {\n    setOpen(isOpen);\n    setTriggerId(eventDetails.trigger?.id ?? null);\n  };\n\n  return (\n    <React.Fragment>\n      <div className={styles.Container}>\n        <Dialog.Trigger className={styles.Button} handle={demoDialog} id=\"trigger-1\" payload={1}>\n          Open 1\n        </Dialog.Trigger>\n\n        <Dialog.Trigger className={styles.Button} handle={demoDialog} id=\"trigger-2\" payload={2}>\n          Open 2\n        </Dialog.Trigger>\n\n        <Dialog.Trigger className={styles.Button} handle={demoDialog} id=\"trigger-3\" payload={3}>\n          Open 3\n        </Dialog.Trigger>\n\n        <button\n          className={styles.Button}\n          type=\"button\"\n          onClick={() => {\n            setTriggerId('trigger-2');\n            setOpen(true);\n          }}\n        >\n          Open programmatically\n        </button>\n      </div>\n\n      <Dialog.Root\n        handle={demoDialog}\n        open={open}\n        onOpenChange={handleOpenChange}\n        triggerId={triggerId}\n      >\n        {({ payload }) => (\n          <Dialog.Portal>\n            <Dialog.Backdrop className={styles.Backdrop} />\n            <Dialog.Popup className={styles.Popup}>\n              {payload !== undefined && (\n                <Dialog.Title className={styles.Title}>Dialog {payload}</Dialog.Title>\n              )}\n              <div className={styles.Actions}>\n                <Dialog.Close className={styles.Button}>Close</Dialog.Close>\n              </div>\n            </Dialog.Popup>\n          </Dialog.Portal>\n        )}\n      </Dialog.Root>\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/dialog/demos/detached-triggers-controlled/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoDialogDetachedTriggersControlled = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/dialog/demos/detached-triggers-controlled/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Dialog } from '@base-ui/react/dialog';\n\nconst demoDialog = Dialog.createHandle<number>();\n\nexport default function DialogDetachedTriggersControlledDemo() {\n  const [open, setOpen] = React.useState(false);\n  const [triggerId, setTriggerId] = React.useState<string | null>(null);\n\n  const handleOpenChange = (isOpen: boolean, eventDetails: Dialog.Root.ChangeEventDetails) => {\n    setOpen(isOpen);\n    setTriggerId(eventDetails.trigger?.id ?? null);\n  };\n\n  return (\n    <React.Fragment>\n      <div className=\"flex gap-2 flex-wrap justify-center\">\n        <Dialog.Trigger\n          className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\"\n          handle={demoDialog}\n          id=\"trigger-1\"\n          payload={1}\n        >\n          Open 1\n        </Dialog.Trigger>\n\n        <Dialog.Trigger\n          className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\"\n          handle={demoDialog}\n          id=\"trigger-2\"\n          payload={2}\n        >\n          Open 2\n        </Dialog.Trigger>\n\n        <Dialog.Trigger\n          className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\"\n          handle={demoDialog}\n          id=\"trigger-3\"\n          payload={3}\n        >\n          Open 3\n        </Dialog.Trigger>\n\n        <button\n          type=\"button\"\n          className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\"\n          onClick={() => {\n            setTriggerId('trigger-2');\n            setOpen(true);\n          }}\n        >\n          Open programmatically\n        </button>\n      </div>\n\n      <Dialog.Root\n        handle={demoDialog}\n        open={open}\n        onOpenChange={handleOpenChange}\n        triggerId={triggerId}\n      >\n        {({ payload }) => (\n          <Dialog.Portal>\n            <Dialog.Backdrop className=\"fixed inset-0 min-h-dvh bg-black opacity-20 transition-all duration-150 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0 dark:opacity-70 supports-[-webkit-touch-callout:none]:absolute\" />\n            <Dialog.Popup className=\"fixed top-1/2 left-1/2 -mt-8 w-96 max-w-[calc(100vw-3rem)] -translate-x-1/2 -translate-y-1/2 rounded-lg bg-gray-50 p-6 text-gray-900 outline-1 outline-gray-200 transition-all duration-150 data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 dark:outline-gray-300\">\n              <Dialog.Title className=\"-mt-1.5 mb-1 text-lg font-bold\">\n                Dialog {payload}\n              </Dialog.Title>\n\n              <div className=\"flex justify-end gap-4\">\n                <Dialog.Close className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n                  Close\n                </Dialog.Close>\n              </div>\n            </Dialog.Popup>\n          </Dialog.Portal>\n        )}\n      </Dialog.Root>\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/dialog/demos/detached-triggers-simple/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Dialog } from '@base-ui/react/dialog';\nimport styles from '../../_index.module.css';\n\nconst demoDialog = Dialog.createHandle();\n\nexport default function DialogDetachedTriggersSimpleDemo() {\n  return (\n    <React.Fragment>\n      <Dialog.Trigger className={styles.Button} handle={demoDialog}>\n        View notifications\n      </Dialog.Trigger>\n\n      <Dialog.Root handle={demoDialog}>\n        <Dialog.Portal>\n          <Dialog.Backdrop className={styles.Backdrop} />\n          <Dialog.Popup className={styles.Popup}>\n            <Dialog.Title className={styles.Title}>Notifications</Dialog.Title>\n            <Dialog.Description className={styles.Description}>\n              You are all caught up. Good job!\n            </Dialog.Description>\n            <div className={styles.Actions}>\n              <Dialog.Close className={styles.Button}>Close</Dialog.Close>\n            </div>\n          </Dialog.Popup>\n        </Dialog.Portal>\n      </Dialog.Root>\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/dialog/demos/detached-triggers-simple/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoDialogDetachedTriggersSimple = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/dialog/demos/detached-triggers-simple/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Dialog } from '@base-ui/react/dialog';\n\nconst demoDialog = Dialog.createHandle();\n\nexport default function DialogDetachedTriggersSimpleDemo() {\n  return (\n    <React.Fragment>\n      <Dialog.Trigger\n        className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\"\n        handle={demoDialog}\n      >\n        View notifications\n      </Dialog.Trigger>\n\n      <Dialog.Root handle={demoDialog}>\n        <Dialog.Portal>\n          <Dialog.Backdrop className=\"fixed inset-0 min-h-dvh bg-black opacity-20 transition-all duration-150 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0 dark:opacity-70 supports-[-webkit-touch-callout:none]:absolute\" />\n          <Dialog.Popup className=\"fixed top-1/2 left-1/2 -mt-8 w-96 max-w-[calc(100vw-3rem)] -translate-x-1/2 -translate-y-1/2 rounded-lg bg-gray-50 p-6 text-gray-900 outline-1 outline-gray-200 transition-all duration-150 data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 dark:outline-gray-300\">\n            <Dialog.Title className=\"-mt-1.5 mb-1 text-lg font-bold\">Notifications</Dialog.Title>\n            <Dialog.Description className=\"mb-6 text-base text-gray-600\">\n              You are all caught up. Good job!\n            </Dialog.Description>\n            <div className=\"flex justify-end gap-4\">\n              <Dialog.Close className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n                Close\n              </Dialog.Close>\n            </div>\n          </Dialog.Popup>\n        </Dialog.Portal>\n      </Dialog.Root>\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/dialog/demos/hero/css-modules/index.module.css",
    "content": ".Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Backdrop {\n  position: fixed;\n  min-height: 100dvh;\n  inset: 0;\n  background-color: black;\n  opacity: 0.2;\n  transition: opacity 150ms cubic-bezier(0.45, 1.005, 0, 1.005);\n  touch-action: none;\n\n  /* iOS 26+: Ensure the backdrop covers the entire visible viewport. */\n  @supports (-webkit-touch-callout: none) {\n    position: absolute;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    opacity: 0.7;\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n  }\n}\n\n.Popup {\n  box-sizing: border-box;\n  position: fixed;\n  top: 50%;\n  left: 50%;\n  transform: translate(-50%, -50%);\n  width: 24rem;\n  max-width: calc(100vw - 3rem);\n  margin-top: -2rem;\n  padding: 1.5rem;\n  border-radius: 0.5rem;\n  outline: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-50);\n  color: var(--color-gray-900);\n  transition: all 150ms;\n  touch-action: none;\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: translate(-50%, -50%) scale(0.9);\n  }\n}\n\n.Title {\n  margin-top: -0.375rem;\n  margin-bottom: 0.25rem;\n  font-size: 1.125rem;\n  line-height: 1.75rem;\n  letter-spacing: -0.0025em;\n  font-weight: 700;\n}\n\n.Description {\n  margin: 0 0 1.5rem;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-600);\n}\n\n.Actions {\n  display: flex;\n  justify-content: end;\n  gap: 1rem;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/dialog/demos/hero/css-modules/index.tsx",
    "content": "import { Dialog } from '@base-ui/react/dialog';\nimport styles from './index.module.css';\n\nexport default function ExampleDialog() {\n  return (\n    <Dialog.Root>\n      <Dialog.Trigger className={styles.Button}>View notifications</Dialog.Trigger>\n      <Dialog.Portal>\n        <Dialog.Backdrop className={styles.Backdrop} />\n        <Dialog.Popup className={styles.Popup}>\n          <Dialog.Title className={styles.Title}>Notifications</Dialog.Title>\n          <Dialog.Description className={styles.Description}>\n            You are all caught up. Good job!\n          </Dialog.Description>\n          <div className={styles.Actions}>\n            <Dialog.Close className={styles.Button}>Close</Dialog.Close>\n          </div>\n        </Dialog.Popup>\n      </Dialog.Portal>\n    </Dialog.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/dialog/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoDialogHero = createDemoWithVariants(import.meta.url, { CssModules, Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/dialog/demos/hero/tailwind/index.tsx",
    "content": "import { Dialog } from '@base-ui/react/dialog';\n\nexport default function ExampleDialog() {\n  return (\n    <Dialog.Root>\n      <Dialog.Trigger className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n        View notifications\n      </Dialog.Trigger>\n      <Dialog.Portal>\n        <Dialog.Backdrop className=\"fixed inset-0 min-h-dvh bg-black opacity-20 transition-all duration-150 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0 dark:opacity-70 supports-[-webkit-touch-callout:none]:absolute\" />\n        <Dialog.Popup className=\"fixed top-1/2 left-1/2 -mt-8 w-96 max-w-[calc(100vw-3rem)] -translate-x-1/2 -translate-y-1/2 rounded-lg bg-gray-50 p-6 text-gray-900 outline-1 outline-gray-200 transition-all duration-150 data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 dark:outline-gray-300\">\n          <Dialog.Title className=\"-mt-1.5 mb-1 text-lg font-bold\">Notifications</Dialog.Title>\n          <Dialog.Description className=\"mb-6 text-base text-gray-600\">\n            You are all caught up. Good job!\n          </Dialog.Description>\n          <div className=\"flex justify-end gap-4\">\n            <Dialog.Close className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n              Close\n            </Dialog.Close>\n          </div>\n        </Dialog.Popup>\n      </Dialog.Portal>\n    </Dialog.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/dialog/demos/inside-scroll/css-modules/index.module.css",
    "content": ".Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Backdrop {\n  position: fixed;\n  inset: 0;\n  background-color: black;\n  opacity: 0.2;\n  transition: opacity 250ms cubic-bezier(0.45, 1.005, 0, 1.005);\n\n  @supports (-webkit-touch-callout: none) {\n    position: absolute;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    opacity: 0.7;\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n  }\n}\n\n.Viewport {\n  position: fixed;\n  inset: 0;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  padding: 1.5rem 0;\n  overflow: hidden;\n\n  @media (min-height: 600px) {\n    padding: 2rem 0 3rem;\n  }\n}\n\n.ScrollViewport {\n  box-sizing: border-box;\n  height: 100%;\n  overscroll-behavior: contain;\n}\n\n.ScrollContent {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  min-height: 100%;\n}\n\n.Scrollbar {\n  position: absolute;\n  display: flex;\n  width: 0.25rem;\n  margin: 0.25rem;\n  justify-content: center;\n  border-radius: 1rem;\n  opacity: 0;\n  transition: opacity 250ms;\n  pointer-events: none;\n\n  &[data-hovering],\n  &[data-scrolling] {\n    opacity: 1;\n    transition-duration: 75ms;\n    transition-delay: 0ms;\n    pointer-events: auto;\n  }\n\n  @media (min-width: 768px) {\n    width: 0.325rem;\n  }\n}\n\n.ScrollbarThumb {\n  width: 100%;\n  border-radius: inherit;\n  background-color: var(--color-gray-500);\n\n  &::before {\n    content: '';\n    position: absolute;\n    top: 50%;\n    left: 50%;\n    transform: translate(-50%, -50%);\n    width: calc(100% + 1rem);\n    height: calc(100% + 1rem);\n  }\n}\n\n.Popup {\n  box-sizing: border-box;\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  width: min(40rem, calc(100vw - 2rem));\n  max-height: 100%;\n  max-width: 100%;\n  padding: 2rem;\n  border-radius: 0.5rem;\n  background-color: var(--color-gray-50);\n  color: var(--color-gray-900);\n  outline: 1px solid var(--color-gray-200);\n  box-shadow: 0 24px 45px rgb(15 23 42 / 0.18);\n  transition:\n    opacity 300ms cubic-bezier(0.45, 1.005, 0, 1.005),\n    transform 300ms cubic-bezier(0.45, 1.005, 0, 1.005);\n  overflow: hidden;\n  min-height: 0;\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.98);\n  }\n}\n\n.PopupHeader {\n  display: flex;\n  align-items: flex-start;\n  justify-content: space-between;\n  gap: 0.75rem;\n  margin-bottom: 0.5rem;\n}\n\n.Title {\n  margin: 0;\n  font-size: 1.25rem;\n  line-height: 1.875rem;\n  font-weight: 700;\n}\n\n.Description {\n  margin: 0 0 1rem;\n  font-size: 1rem;\n  line-height: 1.6rem;\n  color: var(--color-gray-600);\n}\n\n.Body {\n  position: relative;\n  flex: 1 1 auto;\n  display: flex;\n  min-height: 0;\n  overflow: hidden;\n\n  &::before,\n  &::after {\n    content: '';\n    position: absolute;\n    height: 1px;\n    width: 100%;\n    background: var(--color-gray-200);\n  }\n\n  &::before {\n    top: 0;\n  }\n  &::after {\n    bottom: 0;\n  }\n}\n\n.BodyViewport {\n  box-sizing: border-box;\n  flex: 1 1 auto;\n  min-height: 0;\n  overscroll-behavior: contain;\n  padding: 1.5rem 1.5rem 1.5rem 0.25rem;\n\n  &:focus-visible {\n    outline: 1px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.BodyContent {\n  display: flex;\n  flex-direction: column;\n  gap: 1.5rem;\n}\n\n.SectionTitle {\n  margin: 0 0 0.4rem;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  font-weight: 700;\n}\n\n.SectionBody {\n  margin: 0;\n  font-size: 0.95rem;\n  line-height: 1.55rem;\n  color: var(--color-gray-700);\n}\n\n.FooterNote {\n  margin: 0 0 1.5rem;\n  font-size: 0.95rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-600);\n}\n\n.Actions {\n  display: flex;\n  justify-content: flex-end;\n  gap: 0.75rem;\n  margin-top: 1rem;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/dialog/demos/inside-scroll/css-modules/index.tsx",
    "content": "import * as React from 'react';\nimport { Dialog } from '@base-ui/react/dialog';\nimport { ScrollArea } from '@base-ui/react/scroll-area';\nimport styles from './index.module.css';\n\nexport default function InsideScrollDialog() {\n  return (\n    <Dialog.Root>\n      <Dialog.Trigger className={styles.Button}>Open dialog</Dialog.Trigger>\n      <Dialog.Portal>\n        <Dialog.Backdrop className={styles.Backdrop} />\n        <Dialog.Viewport className={styles.Viewport}>\n          <Dialog.Popup className={styles.Popup}>\n            <div className={styles.PopupHeader}>\n              <Dialog.Title className={styles.Title}>Dialog</Dialog.Title>\n            </div>\n            <Dialog.Description className={styles.Description}>\n              This layout keeps the popup fully on screen while allowing its content to scroll.\n            </Dialog.Description>\n            <ScrollArea.Root className={styles.Body}>\n              <ScrollArea.Viewport className={styles.BodyViewport}>\n                <ScrollArea.Content className={styles.BodyContent}>\n                  {CONTENT_SECTIONS.map((item) => (\n                    <section key={item.title}>\n                      <h3 className={styles.SectionTitle}>{item.title}</h3>\n                      <p className={styles.SectionBody}>{item.body}</p>\n                    </section>\n                  ))}\n                </ScrollArea.Content>\n              </ScrollArea.Viewport>\n              <ScrollArea.Scrollbar className={styles.Scrollbar}>\n                <ScrollArea.Thumb className={styles.ScrollbarThumb} />\n              </ScrollArea.Scrollbar>\n            </ScrollArea.Root>\n            <div className={styles.Actions}>\n              <Dialog.Close className={styles.Button}>Close</Dialog.Close>\n            </div>\n          </Dialog.Popup>\n        </Dialog.Viewport>\n      </Dialog.Portal>\n    </Dialog.Root>\n  );\n}\n\nconst CONTENT_SECTIONS = [\n  {\n    title: 'What a dialog is for',\n    body: 'Use a dialog when you need the user to complete a focused task or read something important without navigating away. It opens on top of the page and returns focus back where it started when closed.',\n  },\n  {\n    title: 'Anatomy at a glance',\n    body: 'Root, Trigger, Portal, Backdrop, Popup, Title/Description, Close. Keep the title short and the first paragraph specific so screen readers announce something meaningful.',\n  },\n  {\n    title: 'Opening and closing',\n    body: 'Control it using external state via the `open` and `onOpenChange` props, or let it manage state for you internally.',\n  },\n  {\n    title: 'Keyboard and focus behavior',\n    body: 'Focus moves inside the dialog when it opens. Tab and Shift+Tab loop within, and Esc requests close.',\n  },\n  {\n    title: 'Accessible labeling',\n    body: 'Set an explicit title and description using the `Dialog.Title` and `Dialog.Description` components.',\n  },\n  {\n    title: 'Backdrop and page scrolling',\n    body: 'The backdrop visually separates layers while background content is inert. Don’t rely on dimness alone—keep copy clear and buttons obvious so actions are easy to choose.',\n  },\n  {\n    title: 'Portals and stacking',\n    body: 'Dialogs render in a portal so they sit above the `isolation: isolate` app content and avoid local z-index wars.',\n  },\n  {\n    title: 'Viewport overflow',\n    body: 'Let long content overflow the bottom edge and reveal as you scroll the page container. Keep generous padding at the top and bottom so the dialog doesn’t feel jammed against the edges.',\n  },\n  {\n    title: 'Nested dialogs and confirmations',\n    body: 'If closing a dialog needs confirmation, open a child alert dialog rather than mutating the current one. The parent stays visible behind it; only the topmost layer should feel interactive.',\n  },\n  {\n    title: 'Transitions that respect motion settings',\n    body: 'Use small, fast transitions (opacity plus a few pixels of Y translation or scale). Subtle motion helps people notice what changed without slowing them down.',\n  },\n  {\n    title: 'Controlled vs. uncontrolled',\n    body: 'Controlled state is best when other parts of the page need to react to open/close. Uncontrolled is fine for local cases where only the dialog matters.',\n  },\n  {\n    title: 'Close affordances',\n    body: 'Always offer a visible close button in the corner. Don’t rely only on Esc or the backdrop for pointer outside presses. Touch screen readers and accessibility users benefit from a clear, targetable control to click to close the dialog.',\n  },\n  {\n    title: 'Forms inside dialogs',\n    body: 'Keep forms short; longer flows usually deserve a full page. Validate inline, keep button text specific (“Create project”), and disable destructive actions until the input is valid.',\n  },\n  {\n    title: 'Content guidelines',\n    body: 'Lead with the outcome (“Rename project?”) and follow with one or two short, concrete sentences. Avoid long prose; link out for details instead.',\n  },\n  {\n    title: 'SSR and hydration notes',\n    body: 'Because dialogs render in a portal, make sure your portal container exists on the client.',\n  },\n  {\n    title: 'Mobile ergonomics',\n    body: 'Use larger touch targets and keep the close button reachable with the thumb. Avoid full-screen modals unless the task truly needs a whole screen.',\n  },\n  {\n    title: 'Theming and density',\n    body: 'Match spacing and corner radius to your system. Use a slightly denser layout than pages so the dialog feels purpose-built, not like a mini web page.',\n  },\n  {\n    title: 'Internationalization',\n    body: 'Plan for longer text. Buttons can grow to two lines; titles should wrap gracefully. Keep destructive terms consistent across locales.',\n  },\n  {\n    title: 'Performance',\n    body: 'Children are mounted lazily when the dialog opens. If the dialog can reopen often, consider the `keepMounted` prop sparingly to perform the work only once on mount to avoid re-initializing complex React trees on each open.',\n  },\n  {\n    title: 'When a popover is better',\n    body: 'If the content is a small hint or a few quick actions anchored to a control, use a popover or menu instead of a dialog. Dialogs interrupt on purpose—use that sparingly.',\n  },\n  {\n    title: 'Follow-up and cleanup',\n    body: 'After a successful action, close the dialog and show confirmation in context (toast, inline message, or updated UI) so people can see the result of what they just did.',\n  },\n];\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/dialog/demos/inside-scroll/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoDialogInsideScroll = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/dialog/demos/inside-scroll/tailwind/index.tsx",
    "content": "import * as React from 'react';\nimport { Dialog } from '@base-ui/react/dialog';\nimport { ScrollArea } from '@base-ui/react/scroll-area';\n\nexport default function InsideScrollDialog() {\n  return (\n    <Dialog.Root>\n      <Dialog.Trigger className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n        Open dialog\n      </Dialog.Trigger>\n      <Dialog.Portal>\n        <Dialog.Backdrop className=\"fixed inset-0 bg-black opacity-20 transition-opacity duration-[250ms] ease-[cubic-bezier(0.45,1.005,0,1.005)] data-[starting-style]:opacity-0 data-[ending-style]:opacity-0 dark:opacity-70 supports-[-webkit-touch-callout:none]:absolute\" />\n        <Dialog.Viewport className=\"fixed inset-0 flex items-center justify-center overflow-hidden py-6 [@media(min-height:600px)]:pb-12 [@media(min-height:600px)]:pt-8\">\n          <Dialog.Popup className=\"relative flex w-[min(40rem,calc(100vw-2rem))] max-h-full max-w-full min-h-0 flex-col overflow-hidden rounded-lg bg-gray-50 p-8 text-gray-900 shadow-[0_24px_45px_rgba(15,23,42,0.18)] outline-1 outline-gray-200 transition-all duration-[300ms] ease-[cubic-bezier(0.45,1.005,0,1.005)] data-[starting-style]:scale-[0.98] data-[starting-style]:opacity-0 data-[ending-style]:scale-[0.98] data-[ending-style]:opacity-0 dark:outline-gray-300\">\n            <div className=\"mb-2 flex items-start justify-between gap-3\">\n              <Dialog.Title className=\"m-0 text-xl font-bold leading-[1.875rem]\">\n                Dialog\n              </Dialog.Title>\n            </div>\n            <Dialog.Description className=\"m-0 mb-4 text-base leading-[1.6rem] text-gray-600\">\n              This layout keeps the popup fully on screen while allowing its content to scroll.\n            </Dialog.Description>\n            <ScrollArea.Root className=\"relative flex min-h-0 flex-1 overflow-hidden before:absolute before:top-0 before:h-px before:w-full before:bg-gray-200 before:content-[''] after:absolute after:bottom-0 after:h-px after:w-full after:bg-gray-200 after:content-['']\">\n              <ScrollArea.Viewport className=\"flex-1 min-h-0 overflow-y-auto overscroll-contain py-6 pr-6 pl-1 focus-visible:outline-1 focus-visible:-outline-offset-1 focus-visible:outline-blue-800\">\n                <ScrollArea.Content className=\"flex flex-col gap-6\">\n                  {CONTENT_SECTIONS.map((item) => (\n                    <section key={item.title}>\n                      <h3 className=\"mb-[0.4rem] text-base font-bold leading-6\">{item.title}</h3>\n                      <p className=\"m-0 text-[0.95rem] leading-[1.55rem] text-gray-700\">\n                        {item.body}\n                      </p>\n                    </section>\n                  ))}\n                </ScrollArea.Content>\n              </ScrollArea.Viewport>\n              <ScrollArea.Scrollbar className=\"pointer-events-none absolute m-1 flex w-[0.25rem] justify-center rounded-[1rem] opacity-0 transition-opacity duration-[250ms] data-[hovering]:pointer-events-auto data-[hovering]:opacity-100 data-[hovering]:duration-[75ms] data-[scrolling]:pointer-events-auto data-[scrolling]:opacity-100 data-[scrolling]:duration-[75ms] md:w-[0.325rem]\">\n                <ScrollArea.Thumb className=\"w-full rounded-[inherit] bg-gray-500 before:absolute before:left-1/2 before:top-1/2 before:h-[calc(100%+1rem)] before:w-[calc(100%+1rem)] before:-translate-x-1/2 before:-translate-y-1/2 before:content-['']\" />\n              </ScrollArea.Scrollbar>\n            </ScrollArea.Root>\n            <div className=\"mt-4 flex justify-end gap-3\">\n              <Dialog.Close className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n                Close\n              </Dialog.Close>\n            </div>\n          </Dialog.Popup>\n        </Dialog.Viewport>\n      </Dialog.Portal>\n    </Dialog.Root>\n  );\n}\n\nconst CONTENT_SECTIONS = [\n  {\n    title: 'What a dialog is for',\n    body: 'Use a dialog when you need the user to complete a focused task or read something important without navigating away. It opens on top of the page and returns focus back where it started when closed.',\n  },\n  {\n    title: 'Anatomy at a glance',\n    body: 'Root, Trigger, Portal, Backdrop, Popup, Title/Description, Close. Keep the title short and the first paragraph specific so screen readers announce something meaningful.',\n  },\n  {\n    title: 'Opening and closing',\n    body: 'Control it using external state via the `open` and `onOpenChange` props, or let it manage state for you internally.',\n  },\n  {\n    title: 'Keyboard and focus behavior',\n    body: 'Focus moves inside the dialog when it opens. Tab and Shift+Tab loop within, and Esc requests close.',\n  },\n  {\n    title: 'Accessible labeling',\n    body: 'Set an explicit title and description using the `Dialog.Title` and `Dialog.Description` components.',\n  },\n  {\n    title: 'Backdrop and page scrolling',\n    body: 'The backdrop visually separates layers while background content is inert. Don’t rely on dimness alone—keep copy clear and buttons obvious so actions are easy to choose.',\n  },\n  {\n    title: 'Portals and stacking',\n    body: 'Dialogs render in a portal so they sit above the `isolation: isolate` app content and avoid local z-index wars.',\n  },\n  {\n    title: 'Viewport overflow',\n    body: 'Let long content overflow the bottom edge and reveal as you scroll the page container. Keep generous padding at the top and bottom so the dialog doesn’t feel jammed against the edges.',\n  },\n  {\n    title: 'Nested dialogs and confirmations',\n    body: 'If closing a dialog needs confirmation, open a child alert dialog rather than mutating the current one. The parent stays visible behind it; only the topmost layer should feel interactive.',\n  },\n  {\n    title: 'Transitions that respect motion settings',\n    body: 'Use small, fast transitions (opacity plus a few pixels of Y translation or scale). Subtle motion helps people notice what changed without slowing them down.',\n  },\n  {\n    title: 'Controlled vs. uncontrolled',\n    body: 'Controlled state is best when other parts of the page need to react to open/close. Uncontrolled is fine for local cases where only the dialog matters.',\n  },\n  {\n    title: 'Close affordances',\n    body: 'Always offer a visible close button in the corner. Don’t rely only on Esc or the backdrop for pointer outside presses. Touch screen readers and accessibility users benefit from a clear, targetable control to click to close the dialog.',\n  },\n  {\n    title: 'Forms inside dialogs',\n    body: 'Keep forms short; longer flows usually deserve a full page. Validate inline, keep button text specific (“Create project”), and disable destructive actions until the input is valid.',\n  },\n  {\n    title: 'Content guidelines',\n    body: 'Lead with the outcome (“Rename project?”) and follow with one or two short, concrete sentences. Avoid long prose; link out for details instead.',\n  },\n  {\n    title: 'SSR and hydration notes',\n    body: 'Because dialogs render in a portal, make sure your portal container exists on the client.',\n  },\n  {\n    title: 'Mobile ergonomics',\n    body: 'Use larger touch targets and keep the close button reachable with the thumb. Avoid full-screen modals unless the task truly needs a whole screen.',\n  },\n  {\n    title: 'Theming and density',\n    body: 'Match spacing and corner radius to your system. Use a slightly denser layout than pages so the dialog feels purpose-built, not like a mini web page.',\n  },\n  {\n    title: 'Internationalization',\n    body: 'Plan for longer text. Buttons can grow to two lines; titles should wrap gracefully. Keep destructive terms consistent across locales.',\n  },\n  {\n    title: 'Performance',\n    body: 'Children are mounted lazily when the dialog opens. If the dialog can reopen often, consider the `keepMounted` prop sparingly to perform the work only once on mount to avoid re-initializing complex React trees on each open.',\n  },\n  {\n    title: 'When a popover is better',\n    body: 'If the content is a small hint or a few quick actions anchored to a control, use a popover or menu instead of a dialog. Dialogs interrupt on purpose—use that sparingly.',\n  },\n  {\n    title: 'Follow-up and cleanup',\n    body: 'After a successful action, close the dialog and show confirmation in context (toast, inline message, or updated UI) so people can see the result of what they just did.',\n  },\n];\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/dialog/demos/nested/css-modules/index.module.css",
    "content": ".Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.GhostButton {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  background-color: transparent;\n  color: var(--color-blue);\n  border-radius: 0.25rem;\n  padding: 0.125rem 0.375rem;\n  margin: -0.125rem -0.375rem;\n  border: 0;\n  outline: 0;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: color-mix(in oklch, var(--color-blue), transparent 95%);\n    }\n  }\n\n  &:active {\n    background-color: color-mix(in oklch, var(--color-blue), transparent 90%);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    @media (hover: hover) {\n      &:hover {\n        background-color: color-mix(in oklch, var(--color-blue), transparent 85%);\n      }\n    }\n\n    &:active {\n      background-color: color-mix(in oklch, var(--color-blue), transparent 75%);\n    }\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Backdrop {\n  position: fixed;\n  min-height: 100dvh;\n  inset: 0;\n  background-color: black;\n  opacity: 0.2;\n  transition: opacity 150ms cubic-bezier(0.45, 1.005, 0, 1.005);\n\n  /* iOS 26+: Ensure the backdrop covers the entire visible viewport. */\n  @supports (-webkit-touch-callout: none) {\n    position: absolute;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    opacity: 0.7;\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n  }\n}\n\n.Popup {\n  box-sizing: border-box;\n  position: fixed;\n  top: 50%;\n  left: 50%;\n  width: 24rem;\n  max-width: calc(100vw - 3rem);\n  margin-top: -2rem;\n  padding: 1.5rem;\n  border-radius: 0.5rem;\n  outline: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-50);\n  color: var(--color-gray-900);\n  transition: all 150ms;\n\n  transform: translate(-50%, -50%) scale(calc(1 - 0.1 * var(--nested-dialogs)));\n  translate: 0 calc(0px + 1.25rem * var(--nested-dialogs));\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n  }\n\n  &[data-nested-dialog-open] {\n    &::after {\n      content: '';\n      inset: 0;\n      position: absolute;\n      border-radius: inherit;\n      background-color: rgb(0 0 0 / 0.05);\n    }\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: translate(-50%, -50%) scale(0.9);\n  }\n}\n\n.Title {\n  margin-top: -0.375rem;\n  margin-bottom: 0.25rem;\n  font-size: 1.125rem;\n  line-height: 1.75rem;\n  letter-spacing: -0.0025em;\n  font-weight: 700;\n}\n\n.Description {\n  margin: 0 0 1.5rem;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-600);\n}\n\n.Actions {\n  display: flex;\n  align-items: center;\n  justify-content: end;\n  gap: 1rem;\n}\n\n.ActionsLeft {\n  margin-right: auto;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/dialog/demos/nested/css-modules/index.tsx",
    "content": "import { Dialog } from '@base-ui/react/dialog';\nimport styles from './index.module.css';\n\nexport default function ExampleDialog() {\n  return (\n    <Dialog.Root>\n      <Dialog.Trigger className={styles.Button}>View notifications</Dialog.Trigger>\n      <Dialog.Portal>\n        <Dialog.Backdrop className={styles.Backdrop} />\n        <Dialog.Popup className={styles.Popup}>\n          <Dialog.Title className={styles.Title}>Notifications</Dialog.Title>\n          <Dialog.Description className={styles.Description}>\n            You are all caught up. Good job!\n          </Dialog.Description>\n          <div className={styles.Actions}>\n            <div className={styles.ActionsLeft}>\n              <Dialog.Root>\n                <Dialog.Trigger className={styles.GhostButton}>Customize</Dialog.Trigger>\n                <Dialog.Portal>\n                  <Dialog.Popup className={styles.Popup}>\n                    <Dialog.Title className={styles.Title}>Customize notifications</Dialog.Title>\n                    <Dialog.Description className={styles.Description}>\n                      Review your settings here.\n                    </Dialog.Description>\n                    <div className={styles.Actions}>\n                      <Dialog.Close className={styles.Button}>Close</Dialog.Close>\n                    </div>\n                  </Dialog.Popup>\n                </Dialog.Portal>\n              </Dialog.Root>\n            </div>\n\n            <Dialog.Close className={styles.Button}>Close</Dialog.Close>\n          </div>\n        </Dialog.Popup>\n      </Dialog.Portal>\n    </Dialog.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/dialog/demos/nested/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoDialogNested = createDemoWithVariants(import.meta.url, { CssModules, Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/dialog/demos/nested/tailwind/index.tsx",
    "content": "import { Dialog } from '@base-ui/react/dialog';\n\nexport default function ExampleDialog() {\n  return (\n    <Dialog.Root>\n      <Dialog.Trigger className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n        View notifications\n      </Dialog.Trigger>\n      <Dialog.Portal>\n        <Dialog.Backdrop className=\"fixed inset-0 min-h-dvh bg-black opacity-20 transition-all duration-150 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0 dark:opacity-70 supports-[-webkit-touch-callout:none]:absolute\" />\n        <Dialog.Popup className=\"fixed top-[calc(50%+1.25rem*var(--nested-dialogs))] left-1/2 -mt-8 w-96 max-w-[calc(100vw-3rem)] -translate-x-1/2 -translate-y-1/2 scale-[calc(1-0.1*var(--nested-dialogs))] rounded-lg bg-gray-50 p-6 text-gray-900 outline-1 outline-gray-200 transition-all duration-150 data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[nested-dialog-open]:after:absolute data-[nested-dialog-open]:after:inset-0 data-[nested-dialog-open]:after:rounded-[inherit] data-[nested-dialog-open]:after:bg-black/5 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 dark:outline-gray-300\">\n          <Dialog.Title className=\"-mt-1.5 mb-1 text-lg font-bold\">Notifications</Dialog.Title>\n          <Dialog.Description className=\"mb-6 text-base text-gray-600\">\n            You are all caught up. Good job!\n          </Dialog.Description>\n          <div className=\"flex items-center justify-end gap-4\">\n            <div className=\"mr-auto flex\">\n              <Dialog.Root>\n                <Dialog.Trigger className=\"-mx-1.5 -my-0.5 flex items-center justify-center rounded-xs px-1.5 py-0.5 text-base font-normal text-blue-800 hover:bg-blue-800/5 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-blue-800/10 dark:hover:bg-blue-800/15 dark:active:bg-blue-800/25\">\n                  Customize\n                </Dialog.Trigger>\n                <Dialog.Portal>\n                  <Dialog.Popup className=\"fixed top-[calc(50%+1.25rem*var(--nested-dialogs))] left-1/2 -mt-8 w-96 max-w-[calc(100vw-3rem)] -translate-x-1/2 -translate-y-1/2 scale-[calc(1-0.1*var(--nested-dialogs))] rounded-lg bg-gray-50 p-6 text-gray-900 outline-1 outline-gray-200 transition-all duration-150 data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[nested-dialog-open]:after:absolute data-[nested-dialog-open]:after:inset-0 data-[nested-dialog-open]:after:rounded-[inherit] data-[nested-dialog-open]:after:bg-black/5 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 dark:outline-gray-300\">\n                    <Dialog.Title className=\"-mt-1.5 mb-1 text-lg font-bold\">\n                      Customize notification\n                    </Dialog.Title>\n                    <Dialog.Description className=\"mb-6 text-base text-gray-600\">\n                      Review your settings here.\n                    </Dialog.Description>\n                    <div className=\"flex items-center justify-end gap-4\">\n                      <Dialog.Close className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n                        Close\n                      </Dialog.Close>\n                    </div>\n                  </Dialog.Popup>\n                </Dialog.Portal>\n              </Dialog.Root>\n            </div>\n\n            <Dialog.Close className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n              Close\n            </Dialog.Close>\n          </div>\n        </Dialog.Popup>\n      </Dialog.Portal>\n    </Dialog.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/dialog/demos/outside-scroll/css-modules/index.module.css",
    "content": ".Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Backdrop {\n  position: fixed;\n  inset: 0;\n  transition-duration: 600ms;\n  transition-property: -webkit-backdrop-filter, backdrop-filter, opacity;\n  transition-timing-function: var(--ease-out-fast);\n  backdrop-filter: blur(1.5px);\n  background-image: linear-gradient(to bottom, rgb(0 0 0 / 5%) 0, rgb(0 0 0 / 10%) 50%);\n\n  @media (prefers-color-scheme: dark) {\n    opacity: 0.7;\n  }\n\n  @supports (-webkit-touch-callout: none) {\n    position: absolute;\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    backdrop-filter: blur(0);\n    opacity: 0;\n  }\n\n  &[data-ending-style] {\n    transition-duration: 350ms;\n    transition-timing-function: cubic-bezier(0.375, 0.015, 0.545, 0.455);\n  }\n}\n\n.Viewport {\n  position: fixed;\n  inset: 0;\n}\n\n.ScrollViewport {\n  box-sizing: border-box;\n  height: 100%;\n  overscroll-behavior: contain;\n\n  [data-ending-style] & {\n    pointer-events: none;\n  }\n}\n\n.ScrollContent {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  min-height: 100%;\n}\n\n.Scrollbar {\n  position: absolute;\n  display: flex;\n  width: 0.25rem;\n  margin: 0.4rem;\n  justify-content: center;\n  border-radius: 1rem;\n  opacity: 0;\n  transition: opacity 250ms;\n  pointer-events: none;\n\n  &:hover,\n  &[data-scrolling] {\n    opacity: 1;\n    transition-duration: 75ms;\n    transition-delay: 0ms;\n    pointer-events: auto;\n  }\n\n  [data-ending-style] & {\n    transition-duration: 250ms;\n    opacity: 0;\n  }\n\n  @media (min-width: 768px) {\n    width: 0.4375rem;\n  }\n}\n\n.ScrollbarThumb {\n  width: 100%;\n  border-radius: inherit;\n  background-color: var(--color-gray-500);\n\n  &::before {\n    content: '';\n    position: absolute;\n    top: 50%;\n    left: 50%;\n    transform: translate(-50%, -50%);\n    width: calc(100% + 1rem);\n    height: calc(100% + 1rem);\n  }\n}\n\n.Popup {\n  box-sizing: border-box;\n  position: relative;\n  width: min(40rem, calc(100vw - 2rem));\n  padding: 2rem;\n  margin: 4rem auto;\n  border-radius: 0.5rem;\n  background-color: var(--color-gray-50);\n  color: var(--color-gray-900);\n  box-shadow:\n    0 10px 64px -10px rgb(36 40 52 / 20%),\n    0 0.25px 0 1px var(--color-gray-200);\n  transition: transform 700ms cubic-bezier(0.45, 1.005, 0, 1.005);\n  outline: 0;\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    transition: none;\n  }\n\n  &[data-starting-style] {\n    transform: translateY(100dvh);\n  }\n\n  &[data-ending-style] {\n    transform: translateY(max(100dvh, 100%));\n    transition-duration: 350ms;\n    transition-timing-function: cubic-bezier(0.375, 0.015, 0.545, 0.455);\n  }\n}\n\n.PopupHeader {\n  display: flex;\n  align-items: flex-start;\n  justify-content: space-between;\n  gap: 0.75rem;\n  margin-bottom: 1rem;\n}\n\n.Title {\n  margin: 0;\n  font-size: 1.25rem;\n  line-height: 1.875rem;\n  font-weight: 700;\n}\n\n.Description {\n  margin: 0 0 1.5rem;\n  font-size: 1rem;\n  line-height: 1.6rem;\n  color: var(--color-gray-600);\n}\n\n.Close {\n  box-sizing: border-box;\n  display: inline-flex;\n  align-items: center;\n  position: relative;\n  right: -0.5rem;\n  top: -0.5rem;\n  justify-content: center;\n  width: 2.25rem;\n  height: 2.25rem;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  color: var(--color-gray-600);\n  transition:\n    background-color 120ms ease,\n    color 120ms ease;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n      color: var(--color-gray-900);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.CloseIcon {\n  width: 1.1rem;\n  height: 1.1rem;\n}\n\n.Body {\n  display: flex;\n  flex-direction: column;\n  gap: 1.5rem;\n  margin-bottom: 1.75rem;\n}\n\n.SectionTitle {\n  margin: 0 0 0.4rem;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  font-weight: 700;\n}\n\n.SectionBody {\n  margin: 0;\n  font-size: 0.95rem;\n  line-height: 1.55rem;\n  color: var(--color-gray-700);\n}\n\n.FooterNote {\n  margin: 0 0 1.5rem;\n  font-size: 0.95rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-600);\n}\n\n.FooterLink {\n  color: var(--color-gray-900);\n  text-decoration: underline;\n  text-decoration-thickness: 1px;\n  text-underline-offset: 0.16em;\n\n  @media (hover: hover) {\n    &:hover {\n      text-decoration: none;\n    }\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: 2px;\n    border-radius: 0.125rem;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/dialog/demos/outside-scroll/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Dialog } from '@base-ui/react/dialog';\nimport { ScrollArea } from '@base-ui/react/scroll-area';\nimport styles from './index.module.css';\n\nexport default function OutsideScrollDialog() {\n  const popupRef = React.useRef<HTMLDivElement>(null);\n  return (\n    <Dialog.Root>\n      <Dialog.Trigger className={styles.Button}>Open dialog</Dialog.Trigger>\n      <Dialog.Portal>\n        <Dialog.Backdrop className={styles.Backdrop} />\n        <Dialog.Viewport className={styles.Viewport}>\n          <ScrollArea.Root style={{ position: undefined }} className={styles.ScrollViewport}>\n            <ScrollArea.Viewport className={styles.ScrollViewport}>\n              <ScrollArea.Content className={styles.ScrollContent}>\n                <Dialog.Popup ref={popupRef} className={styles.Popup} initialFocus={popupRef}>\n                  <div className={styles.PopupHeader}>\n                    <Dialog.Title className={styles.Title}>Dialog</Dialog.Title>\n                    <Dialog.Close className={styles.Close} aria-label=\"Close\">\n                      <XIcon className={styles.CloseIcon} />\n                    </Dialog.Close>\n                  </div>\n\n                  <Dialog.Description className={styles.Description}>\n                    This layout keeps an outer container scrollable while the dialog can extend past\n                    the bottom edge.\n                  </Dialog.Description>\n\n                  <div className={styles.Body}>\n                    {CONTENT_SECTIONS.map((item) => (\n                      <section key={item.title}>\n                        <h3 className={styles.SectionTitle}>{item.title}</h3>\n                        <p className={styles.SectionBody}>{item.body}</p>\n                      </section>\n                    ))}\n                  </div>\n\n                  <p className={styles.FooterNote}>\n                    Related docs:{' '}\n                    {RELATED_LINKS.map((item, index) => (\n                      <React.Fragment key={item.href}>\n                        {index > 0 ? ', ' : null}\n                        <a className={styles.FooterLink} href={item.href}>\n                          {item.label}\n                        </a>\n                      </React.Fragment>\n                    ))}\n                    .\n                  </p>\n                </Dialog.Popup>\n              </ScrollArea.Content>\n            </ScrollArea.Viewport>\n            <ScrollArea.Scrollbar className={styles.Scrollbar}>\n              <ScrollArea.Thumb className={styles.ScrollbarThumb} />\n            </ScrollArea.Scrollbar>\n          </ScrollArea.Root>\n        </Dialog.Viewport>\n      </Dialog.Portal>\n    </Dialog.Root>\n  );\n}\n\nfunction XIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M18 6 6 18\" />\n      <path d=\"m6 6 12 12\" />\n    </svg>\n  );\n}\n\nconst CONTENT_SECTIONS = [\n  {\n    title: 'What a dialog is for',\n    body: 'Use a dialog when you need the user to complete a focused task or read something important without navigating away. It opens on top of the page and returns focus back where it started when closed.',\n  },\n  {\n    title: 'Anatomy at a glance',\n    body: 'Root, Trigger, Portal, Backdrop, Viewport, Popup, Title, Description, Close. Keep the title short and the first paragraph specific so screen readers announce something meaningful.',\n  },\n  {\n    title: 'Opening and closing',\n    body: 'Control it using external state via the `open` and `onOpenChange` props, or let it manage state for you internally.',\n  },\n  {\n    title: 'Keyboard and focus behavior',\n    body: 'Focus moves inside the dialog when it opens. Tab and Shift+Tab loop within, and Esc requests close.',\n  },\n  {\n    title: 'Accessible labeling',\n    body: 'Set an explicit title and description using the `Dialog.Title` and `Dialog.Description` components.',\n  },\n  {\n    title: 'Backdrop and page scrolling',\n    body: 'The backdrop visually separates layers while background content is inert. Don’t rely on dimness alone—keep copy clear and buttons obvious so actions are easy to choose.',\n  },\n  {\n    title: 'Portals and stacking',\n    body: 'Dialogs render in a portal so they sit above the `isolation: isolate` app content and avoid local z-index wars.',\n  },\n  {\n    title: 'Viewport overflow',\n    body: 'Let long content overflow the bottom edge and reveal as you scroll the page container. Keep generous padding at the top and bottom so the dialog doesn’t feel jammed against the edges.',\n  },\n  {\n    title: 'Nested dialogs and confirmations',\n    body: 'If closing a dialog needs confirmation, open a child alert dialog rather than mutating the current one. The parent stays visible behind it; only the topmost layer should feel interactive.',\n  },\n  {\n    title: 'Transitions that respect motion settings',\n    body: 'Use small, fast transitions (opacity plus a few pixels of Y translation or scale). Subtle motion helps people notice what changed without slowing them down.',\n  },\n  {\n    title: 'Controlled vs. uncontrolled',\n    body: 'Controlled state is best when other parts of the page need to react to open/close. Uncontrolled is fine for local cases where only the dialog matters.',\n  },\n  {\n    title: 'Close affordances',\n    body: 'Always offer a visible close button in the corner. Don’t rely only on Esc or the backdrop for pointer outside presses. Touch screen readers and accessibility users benefit from a clear, targetable control to click to close the dialog.',\n  },\n  {\n    title: 'Forms inside dialogs',\n    body: 'Keep forms short; longer flows usually deserve a full page. Validate inline, keep button text specific (“Create project”), and disable destructive actions until the input is valid.',\n  },\n  {\n    title: 'Content guidelines',\n    body: 'Lead with the outcome (“Rename project?”) and follow with one or two short, concrete sentences. Avoid long prose; link out for details instead.',\n  },\n  {\n    title: 'SSR and hydration notes',\n    body: 'Because dialogs render in a portal, make sure your portal container exists on the client.',\n  },\n  {\n    title: 'Mobile ergonomics',\n    body: 'Use larger touch targets and keep the close button reachable with the thumb. Avoid full-screen modals unless the task truly needs a whole screen.',\n  },\n  {\n    title: 'Theming and density',\n    body: 'Match spacing and corner radius to your system. Use a slightly denser layout than pages so the dialog feels purpose-built, not like a mini web page.',\n  },\n  {\n    title: 'Internationalization',\n    body: 'Plan for longer text. Buttons can grow to two lines; titles should wrap gracefully. Keep destructive terms consistent across locales.',\n  },\n  {\n    title: 'Performance',\n    body: 'Children are mounted lazily when the dialog opens. If the dialog can reopen often, consider the `keepMounted` prop sparingly to perform the work only once on mount to avoid re-initializing complex React trees on each open.',\n  },\n  {\n    title: 'When a popover is better',\n    body: 'If the content is a small hint or a few quick actions anchored to a control, use a popover or menu instead of a dialog. Dialogs interrupt on purpose—use that sparingly.',\n  },\n  {\n    title: 'Follow-up and cleanup',\n    body: 'After a successful action, close the dialog and show confirmation in context (toast, inline message, or updated UI) so people can see the result of what they just did.',\n  },\n];\n\nconst RELATED_LINKS = [\n  { href: '/react/components/scroll-area', label: 'Scroll Area' },\n  { href: '/react/components/drawer', label: 'Drawer' },\n  { href: '/react/components/popover', label: 'Popover' },\n] as const;\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/dialog/demos/outside-scroll/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoDialogOutsideScroll = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/dialog/demos/outside-scroll/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Dialog } from '@base-ui/react/dialog';\nimport { ScrollArea } from '@base-ui/react/scroll-area';\n\nexport default function OutsideScrollDialog() {\n  const popupRef = React.useRef<HTMLDivElement>(null);\n  return (\n    <Dialog.Root>\n      <Dialog.Trigger className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n        Open dialog\n      </Dialog.Trigger>\n      <Dialog.Portal>\n        <Dialog.Backdrop className=\"fixed inset-0 bg-[linear-gradient(to_bottom,rgb(0_0_0/5%)_0,rgb(0_0_0/10%)_50%)] opacity-100 transition-[backdrop-filter,opacity] duration-[600ms] ease-[var(--ease-out-fast)] backdrop-blur-[1.5px] data-[starting-style]:backdrop-blur-0 data-[starting-style]:opacity-0 data-[ending-style]:backdrop-blur-0 data-[ending-style]:opacity-0 data-[ending-style]:duration-[350ms] data-[ending-style]:ease-[cubic-bezier(0.375,0.015,0.545,0.455)] dark:opacity-70 supports-[-webkit-touch-callout:none]:absolute\" />\n        <Dialog.Viewport className=\"group/dialog fixed inset-0\">\n          <ScrollArea.Root\n            style={{ position: undefined }}\n            className=\"h-full overscroll-contain group-data-[ending-style]/dialog:pointer-events-none\"\n          >\n            <ScrollArea.Viewport className=\"h-full overscroll-contain group-data-[ending-style]/dialog:pointer-events-none\">\n              <ScrollArea.Content className=\"flex min-h-full items-center justify-center\">\n                <Dialog.Popup\n                  ref={popupRef}\n                  initialFocus={popupRef}\n                  className=\"outline-0 relative mx-auto my-18 w-[min(40rem,calc(100vw-2rem))] rounded-lg bg-gray-50 p-8 text-gray-900 shadow-[0_10px_64px_-10px_rgb(36_40_52/20%),0_0.25px_0_1px_oklch(12%_9%_264deg/7%)] transition-transform duration-[700ms] ease-[cubic-bezier(0.45,1.005,0,1.005)] data-[starting-style]:translate-y-[100dvh] data-[ending-style]:translate-y-[max(100dvh,100%)] data-[ending-style]:duration-[350ms] data-[ending-style]:ease-[cubic-bezier(0.375,0.015,0.545,0.455)] dark:shadow-[0_0_0_1px_oklch(29%_0.75%_264deg/80%)] dark:outline-1 dark:outline-gray-300 motion-reduce:transition-none\"\n                >\n                  <div className=\"mb-4 flex items-start justify-between gap-3\">\n                    <Dialog.Title className=\"m-0 text-xl font-bold leading-[1.875rem]\">\n                      Dialog\n                    </Dialog.Title>\n                    <Dialog.Close\n                      aria-label=\"Close\"\n                      className=\"relative top-[-0.5rem] right-[-0.5rem] flex items-center justify-center rounded-md border border-gray-200 bg-gray-50 w-[2.25rem] h-[2.25rem] text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\"\n                    >\n                      <XIcon className=\"h-[1.1rem] w-[1.1rem]\" />\n                    </Dialog.Close>\n                  </div>\n\n                  <Dialog.Description className=\"m-0 mb-6 text-base leading-[1.6rem] text-gray-600\">\n                    This layout keeps an outer container scrollable while the dialog can extend past\n                    the bottom edge.\n                  </Dialog.Description>\n\n                  <div className=\"mb-[1.75rem] flex flex-col gap-6\">\n                    {CONTENT_SECTIONS.map((item) => (\n                      <section key={item.title}>\n                        <h3 className=\"m-0 mb-[0.4rem] text-base font-bold leading-6\">\n                          {item.title}\n                        </h3>\n                        <p className=\"m-0 text-[0.95rem] leading-[1.55rem] text-gray-700\">\n                          {item.body}\n                        </p>\n                      </section>\n                    ))}\n                  </div>\n\n                  <p className=\"m-0 mb-6 text-[0.95rem] leading-6 text-gray-600\">\n                    Related docs:{' '}\n                    {RELATED_LINKS.map((item, index) => (\n                      <React.Fragment key={item.href}>\n                        {index > 0 ? ', ' : null}\n                        <a\n                          className=\"text-gray-900 underline underline-offset-[0.16em] decoration-[1px] hover:no-underline focus-visible:rounded-[0.125rem] focus-visible:outline-2 focus-visible:outline-blue-800 focus-visible:outline-offset-2\"\n                          href={item.href}\n                        >\n                          {item.label}\n                        </a>\n                      </React.Fragment>\n                    ))}\n                    .\n                  </p>\n                </Dialog.Popup>\n              </ScrollArea.Content>\n            </ScrollArea.Viewport>\n            <ScrollArea.Scrollbar className=\"pointer-events-none absolute m-[0.4rem] flex w-[0.25rem] justify-center rounded-[1rem] opacity-0 transition-opacity duration-[250ms] data-[scrolling]:pointer-events-auto data-[scrolling]:opacity-100 data-[scrolling]:duration-[75ms] data-[scrolling]:delay-[0ms] hover:pointer-events-auto hover:opacity-100 hover:duration-[75ms] hover:delay-[0ms] md:w-[0.4375rem] group-data-[ending-style]/dialog:opacity-0 group-data-[ending-style]/dialog:duration-300\">\n              <ScrollArea.Thumb className=\"w-full rounded-[inherit] bg-gray-500 before:absolute before:content-[''] before:top-1/2 before:left-1/2 before:h-[calc(100%+1rem)] before:w-[calc(100%+1rem)] before:-translate-x-1/2 before:-translate-y-1/2\" />\n            </ScrollArea.Scrollbar>\n          </ScrollArea.Root>\n        </Dialog.Viewport>\n      </Dialog.Portal>\n    </Dialog.Root>\n  );\n}\n\nfunction XIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M18 6 6 18\" />\n      <path d=\"m6 6 12 12\" />\n    </svg>\n  );\n}\n\nconst CONTENT_SECTIONS = [\n  {\n    title: 'What a dialog is for',\n    body: 'Use a dialog when you need the user to complete a focused task or read something important without navigating away. It opens on top of the page and returns focus back where it started when closed.',\n  },\n  {\n    title: 'Anatomy at a glance',\n    body: 'Root, Trigger, Portal, Backdrop, Viewport, Popup, Title, Description, Close. Keep the title short and the first paragraph specific so screen readers announce something meaningful.',\n  },\n  {\n    title: 'Opening and closing',\n    body: 'Control it using external state via the `open` and `onOpenChange` props, or let it manage state for you internally.',\n  },\n  {\n    title: 'Keyboard and focus behavior',\n    body: 'Focus moves inside the dialog when it opens. Tab and Shift+Tab loop within, and Esc requests close.',\n  },\n  {\n    title: 'Accessible labeling',\n    body: 'Set an explicit title and description using the `Dialog.Title` and `Dialog.Description` components.',\n  },\n  {\n    title: 'Backdrop and page scrolling',\n    body: 'The backdrop visually separates layers while background content is inert. Don’t rely on dimness alone—keep copy clear and buttons obvious so actions are easy to choose.',\n  },\n  {\n    title: 'Portals and stacking',\n    body: 'Dialogs render in a portal so they sit above the `isolation: isolate` app content and avoid local z-index wars.',\n  },\n  {\n    title: 'Viewport overflow',\n    body: 'Let long content overflow the bottom edge and reveal as you scroll the page container. Keep generous padding at the top and bottom so the dialog doesn’t feel jammed against the edges.',\n  },\n  {\n    title: 'Nested dialogs and confirmations',\n    body: 'If closing a dialog needs confirmation, open a child alert dialog rather than mutating the current one. The parent stays visible behind it; only the topmost layer should feel interactive.',\n  },\n  {\n    title: 'Transitions that respect motion settings',\n    body: 'Use small, fast transitions (opacity plus a few pixels of Y translation or scale). Subtle motion helps people notice what changed without slowing them down.',\n  },\n  {\n    title: 'Controlled vs. uncontrolled',\n    body: 'Controlled state is best when other parts of the page need to react to open/close. Uncontrolled is fine for local cases where only the dialog matters.',\n  },\n  {\n    title: 'Close affordances',\n    body: 'Always offer a visible close button in the corner. Don’t rely only on Esc or the backdrop for pointer outside presses. Touch screen readers and accessibility users benefit from a clear, targetable control to click to close the dialog.',\n  },\n  {\n    title: 'Forms inside dialogs',\n    body: 'Keep forms short; longer flows usually deserve a full page. Validate inline, keep button text specific (“Create project”), and disable destructive actions until the input is valid.',\n  },\n  {\n    title: 'Content guidelines',\n    body: 'Lead with the outcome (“Rename project?”) and follow with one or two short, concrete sentences. Avoid long prose; link out for details instead.',\n  },\n  {\n    title: 'SSR and hydration notes',\n    body: 'Because dialogs render in a portal, make sure your portal container exists on the client.',\n  },\n  {\n    title: 'Mobile ergonomics',\n    body: 'Use larger touch targets and keep the close button reachable with the thumb. Avoid full-screen modals unless the task truly needs a whole screen.',\n  },\n  {\n    title: 'Theming and density',\n    body: 'Match spacing and corner radius to your system. Use a slightly denser layout than pages so the dialog feels purpose-built, not like a mini web page.',\n  },\n  {\n    title: 'Internationalization',\n    body: 'Plan for longer text. Buttons can grow to two lines; titles should wrap gracefully. Keep destructive terms consistent across locales.',\n  },\n  {\n    title: 'Performance',\n    body: 'Children are mounted lazily when the dialog opens. If the dialog can reopen often, consider the `keepMounted` prop sparingly to perform the work only once on mount to avoid re-initializing complex React trees on each open.',\n  },\n  {\n    title: 'When a popover is better',\n    body: 'If the content is a small hint or a few quick actions anchored to a control, use a popover or menu instead of a dialog. Dialogs interrupt on purpose—use that sparingly.',\n  },\n  {\n    title: 'Follow-up and cleanup',\n    body: 'After a successful action, close the dialog and show confirmation in context (toast, inline message, or updated UI) so people can see the result of what they just did.',\n  },\n];\n\nconst RELATED_LINKS = [\n  { href: '/react/components/scroll-area', label: 'Scroll Area' },\n  { href: '/react/components/drawer', label: 'Drawer' },\n  { href: '/react/components/popover', label: 'Popover' },\n] as const;\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/dialog/demos/uncontained/css-modules/index.module.css",
    "content": ".Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Backdrop {\n  position: fixed;\n  min-height: 100dvh;\n  inset: 0;\n  background-color: black;\n  opacity: 0.7;\n  backdrop-filter: blur(2px);\n  transition:\n    opacity 150ms,\n    backdrop-filter 150ms;\n\n  /* iOS 26+: Ensure the backdrop covers the entire visible viewport. */\n  @supports (-webkit-touch-callout: none) {\n    position: absolute;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    opacity: 0.7;\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n  }\n}\n\n.Viewport {\n  position: fixed;\n  inset: 0;\n  display: grid;\n  place-items: center;\n  padding: 2.5rem 1rem;\n\n  @media (min-width: 80rem) {\n    padding-block: 1.5rem;\n  }\n}\n\n.PopupRoot {\n  display: flex;\n  justify-content: center;\n  width: 100%;\n  height: 100%;\n  transition: opacity 150ms;\n  pointer-events: none;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n  }\n}\n\n.Popup {\n  box-sizing: border-box;\n  width: 100%;\n  height: 100%;\n  max-width: 70rem;\n  padding: 1.5rem;\n  border-radius: 0.5rem;\n  pointer-events: auto;\n  outline: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-50);\n  color: var(--color-gray-900);\n  transition: transform 500ms cubic-bezier(0.22, 1, 0.36, 1);\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n  }\n\n  [data-starting-style] & {\n    transform: scale(1.1);\n  }\n}\n\n.Close {\n  box-sizing: border-box;\n  position: absolute;\n  top: 0.5rem;\n  right: 0.75rem;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  pointer-events: auto;\n  width: 1.75rem;\n  height: 1.75rem;\n  margin: 0;\n  border: none;\n  color: var(--color-gray-50);\n  border-radius: 0.375rem;\n\n  @media (prefers-color-scheme: dark) {\n    color: var(--color-gray-900);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-gray-50);\n    outline-offset: -1px;\n\n    @media (prefers-color-scheme: dark) {\n      outline-color: var(--color-gray-900);\n    }\n  }\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: rgb(255 255 255 / 0.1);\n    }\n  }\n\n  @media (min-width: 80rem) {\n    top: 0.75rem;\n    width: 2.5rem;\n    height: 2.5rem;\n  }\n}\n\n.CloseIcon {\n  width: 2rem;\n  height: 2rem;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/dialog/demos/uncontained/css-modules/index.tsx",
    "content": "import { Dialog } from '@base-ui/react/dialog';\nimport styles from './index.module.css';\n\nexport default function ExampleUncontainedDialog() {\n  return (\n    <Dialog.Root>\n      <Dialog.Trigger className={styles.Button}>Open dialog</Dialog.Trigger>\n      <Dialog.Portal>\n        <Dialog.Backdrop className={styles.Backdrop} />\n        <Dialog.Viewport className={styles.Viewport}>\n          <Dialog.Popup className={styles.PopupRoot}>\n            <Dialog.Close className={styles.Close} aria-label=\"Close\">\n              <XIcon className={styles.CloseIcon} />\n            </Dialog.Close>\n            <div className={styles.Popup} />\n          </Dialog.Popup>\n        </Dialog.Viewport>\n      </Dialog.Portal>\n    </Dialog.Root>\n  );\n}\n\nfunction XIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M18 6 6 18\" />\n      <path d=\"m6 6 12 12\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/dialog/demos/uncontained/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoDialogUncontained = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/dialog/demos/uncontained/tailwind/index.tsx",
    "content": "import { Dialog } from '@base-ui/react/dialog';\n\nexport default function ExampleUncontainedDialog() {\n  return (\n    <Dialog.Root>\n      <Dialog.Trigger className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n        Open dialog\n      </Dialog.Trigger>\n      <Dialog.Portal>\n        <Dialog.Backdrop className=\"fixed inset-0 min-h-dvh bg-black opacity-70 backdrop-blur-[2px] transition-[opacity,backdrop-filter] duration-150 data-[starting-style]:opacity-0 data-[ending-style]:opacity-0 supports-[-webkit-touch-callout:none]:absolute dark:opacity-70\" />\n        <Dialog.Viewport className=\"fixed inset-0 grid place-items-center px-4 py-10 xl:py-6\">\n          <Dialog.Popup className=\"group/popup flex h-full w-full justify-center pointer-events-none transition-opacity duration-150 data-[starting-style]:opacity-0 data-[ending-style]:opacity-0\">\n            <Dialog.Close\n              className=\"absolute right-3 top-2 flex h-7 w-7 items-center justify-center rounded-md border-0 bg-transparent text-gray-50 hover:bg-white/10 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-gray-50 xl:right-3 xl:top-3 xl:h-10 xl:w-10 dark:text-gray-900 dark:focus-visible:outline-gray-900 pointer-events-auto\"\n              aria-label=\"Close\"\n            >\n              <XIcon className=\"h-8 w-8\" />\n            </Dialog.Close>\n            <div className=\"pointer-events-auto box-border h-full w-full max-w-[70rem] rounded-lg bg-gray-50 p-6 text-gray-900 outline-1 outline-gray-200 transition-transform duration-500 ease-[cubic-bezier(0.22,1,0.36,1)] group-data-[starting-style]/popup:scale-110 dark:outline-gray-300\" />\n          </Dialog.Popup>\n        </Dialog.Viewport>\n      </Dialog.Portal>\n    </Dialog.Root>\n  );\n}\n\nfunction XIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M18 6 6 18\" />\n      <path d=\"m6 6 12 12\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/dialog/page.mdx",
    "content": "# Dialog\n\n<Subtitle>A popup that opens on top of the entire page.</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"A high-quality, unstyled React dialog component that opens on top of the entire page.\"\n/>\n\nimport { DemoDialogHero } from './demos/hero';\n\n<DemoDialogHero />\n\n## Usage guidelines\n\n- **Dialog doesn't support gestures:** Use [Drawer](/react/components/drawer) when you need gesture support or snap points. A panel that slides in from the edge of the screen and doesn't need gesture support is a positioned Dialog.\n\n## Anatomy\n\nImport the component and assemble its parts:\n\n```jsx title=\"Anatomy\"\nimport { Dialog } from '@base-ui/react/dialog';\n\n<Dialog.Root>\n  <Dialog.Trigger />\n  <Dialog.Portal>\n    <Dialog.Backdrop />\n    <Dialog.Viewport>\n      <Dialog.Popup>\n        <Dialog.Title />\n        <Dialog.Description />\n        <Dialog.Close />\n      </Dialog.Popup>\n    </Dialog.Viewport>\n  </Dialog.Portal>\n</Dialog.Root>;\n```\n\n## Examples\n\n### State\n\nBy default, Dialog is an uncontrolled component that manages its own state.\n\n```tsx title=\"Uncontrolled dialog\"\n<Dialog.Root>\n  <Dialog.Trigger>Open</Dialog.Trigger>\n  <Dialog.Portal>\n    <Dialog.Popup>\n      <Dialog.Title>Example dialog</Dialog.Title>\n      <Dialog.Close>Close</Dialog.Close>\n    </Dialog.Popup>\n  </Dialog.Portal>\n</Dialog.Root>\n```\n\nUse `open` and `onOpenChange` props if you need to access or control the state of the dialog.\nFor example, you can control the dialog state in order to open it imperatively from another place in your app.\n\n```tsx title=\"Controlled dialog\"\nconst [open, setOpen] = React.useState(false);\nreturn (\n  <Dialog.Root open={open} onOpenChange={setOpen}>\n    <Dialog.Trigger>Open</Dialog.Trigger>\n    <Dialog.Portal>\n      <Dialog.Popup>\n        <form\n          // Close the dialog once the form data is submitted\n          onSubmit={async () => {\n            await submitData();\n            setOpen(false);\n          }}\n        >\n          ...\n        </form>\n      </Dialog.Popup>\n    </Dialog.Portal>\n  </Dialog.Root>\n);\n```\n\nIt's also common to use `onOpenChange` if your app needs to do something when the dialog is closed or opened. This is recommended over `React.useEffect` when reacting to state changes.\n\n```tsx title=\"Running code when dialog state changes\"\n<Dialog.Root\n  open={open}\n  onOpenChange={(open) => {\n    // Do stuff when the dialog is closed\n    if (!open) {\n      doStuff();\n    }\n    // Set the new state\n    setOpen(open);\n  }}\n>\n```\n\n### Open from a menu\n\nIn order to open a dialog using a menu, control the dialog state and open it imperatively using the `onClick` handler on the menu item.\n\n```tsx {12-13,17-18,24-25,28-29} title=\"Connecting a dialog to a menu\"\nimport * as React from 'react';\nimport { Dialog } from '@base-ui/react/dialog';\nimport { Menu } from '@base-ui/react/menu';\n\nfunction ExampleMenu() {\n  const [dialogOpen, setDialogOpen] = React.useState(false);\n\n  return (\n    <React.Fragment>\n      <Menu.Root>\n        <Menu.Trigger>Open menu</Menu.Trigger>\n        <Menu.Portal>\n          <Menu.Positioner>\n            <Menu.Popup>\n              {/* Open the dialog when the menu item is clicked */}\n              <Menu.Item onClick={() => setDialogOpen(true)}>Open dialog</Menu.Item>\n            </Menu.Popup>\n          </Menu.Positioner>\n        </Menu.Portal>\n      </Menu.Root>\n\n      {/* Control the dialog state */}\n      <Dialog.Root open={dialogOpen} onOpenChange={setDialogOpen}>\n        <Dialog.Portal>\n          <Dialog.Backdrop />\n          <Dialog.Popup>\n            {/* prettier-ignore */}\n            {/* Rest of the dialog */}\n          </Dialog.Popup>\n        </Dialog.Portal>\n      </Dialog.Root>\n    </React.Fragment>\n  );\n}\n```\n\n### Nested dialogs\n\nYou can nest dialogs within one another normally.\n\nUse the `[data-nested-dialog-open]` selector and the `var(--nested-dialogs)` CSS variable to customize the styling of the parent dialog. Backdrops of the child dialogs won't be rendered so that you can present the parent dialog in a clean way behind the one on top of it.\n\nimport { DemoDialogNested } from './demos/nested';\n\n<DemoDialogNested compact />\n\n### Close confirmation\n\nThis example shows a nested confirmation dialog that opens if the text entered in the parent dialog is going to be discarded.\n\nTo implement this, both dialogs should be controlled. The confirmation dialog may be opened when `onOpenChange` callback of the parent dialog receives a request to close. This way, the confirmation is automatically shown when the user clicks the backdrop, presses the Esc key, or clicks a close button.\n\nimport { DemoDialogCloseConfirmation } from './demos/close-confirmation';\n\n<DemoDialogCloseConfirmation compact />\n\n### Outside scroll dialog\n\nThe dialog can be made scrollable by using `<Dialog.Viewport>` as an outer scrollable container for `<Dialog.Popup>` while the popup can extend past the bottom edge. The scrollable area uses the [Scroll Area component](/react/components/scroll-area) to provide custom scrollbars.\n\nimport { DemoDialogOutsideScroll } from './demos/outside-scroll';\n\n<DemoDialogOutsideScroll compact />\n\n### Inside scroll dialog\n\nThe dialog can be made scrollable by making an inner container scrollable while the popup stays fully on screen. `<Dialog.Viewport>` is used as a positioning container for `<Dialog.Popup>`, while an inner scrollable area is created using the [Scroll Area component](/react/components/scroll-area).\n\nimport { DemoDialogInsideScroll } from './demos/inside-scroll';\n\n<DemoDialogInsideScroll compact />\n\n### Placing elements outside the popup\n\nWhen adding elements that should appear \"outside\" the colored popup area, continue to place them inside `<Dialog.Popup>`, but create a child element that has the popup styles. This ensures they are kept in the tab order and announced correctly by screen readers.\n\n`<Dialog.Popup>` has `pointer-events: none`, while inner content (the colored popup and close button) has `pointer-events: auto` so clicks on the backdrop continue to be registered.\n\nimport { DemoDialogUncontained } from './demos/uncontained';\n\n<DemoDialogUncontained compact />\n\n### Detached triggers\n\nA dialog can be controlled by a trigger located either inside or outside the `<Dialog.Root>` component.\nFor simple, one-off interactions, place the `<Dialog.Trigger>` inside `<Dialog.Root>`, as shown in the example at the top of this page.\n\nHowever, if defining the dialog's content next to its trigger is not practical, you can use a detached trigger.\nThis involves placing the `<Dialog.Trigger>` outside of `<Dialog.Root>` and linking them with a `handle` created by the `Dialog.createHandle()` function.\n\n```jsx title=\"Detached triggers\" {3,5} \"handle={demoDialog}\"\nconst demoDialog = Dialog.createHandle();\n\n<Dialog.Trigger handle={demoDialog}>Open</Dialog.Trigger>\n\n<Dialog.Root handle={demoDialog}>\n  ...\n</Dialog.Root>\n```\n\nimport { DemoDialogDetachedTriggersSimple } from './demos/detached-triggers-simple';\n\n<DemoDialogDetachedTriggersSimple />\n\n### Multiple triggers\n\nA single dialog can be opened by multiple trigger elements.\nYou can achieve this by using the same `handle` for several detached triggers, or by placing multiple `<Dialog.Trigger>` components inside a single `<Dialog.Root>`.\n\n```jsx title=\"Multiple triggers within the Root part\"\n<Dialog.Root>\n  <Dialog.Trigger>Trigger 1</Dialog.Trigger>\n  <Dialog.Trigger>Trigger 2</Dialog.Trigger>\n  ...\n</Dialog.Root>\n```\n\n```jsx title=\"Multiple detached triggers\"\nconst demoDialog = Dialog.createHandle();\n\n<Dialog.Trigger handle={demoDialog}>Trigger 1</Dialog.Trigger>\n<Dialog.Trigger handle={demoDialog}>Trigger 2</Dialog.Trigger>\n<Dialog.Root handle={demoDialog}>\n  ...\n</Dialog.Root>\n```\n\nThe dialog can render different content depending on which trigger opened it.\nThis is achieved by passing a `payload` to the `<Dialog.Trigger>` and using the function-as-a-child pattern in `<Dialog.Root>`.\n\nThe payload can be strongly typed by providing a type argument to the `createHandle()` function:\n\n```jsx title=\"Detached triggers with payload\" {1,3,7,12}\nconst demoDialog = Dialog.createHandle<{ text: string }>();\n\n<Dialog.Trigger handle={demoDialog} payload={{ text: 'Trigger 1' }}>\n  Trigger 1\n</Dialog.Trigger>\n\n<Dialog.Trigger handle={demoDialog} payload={{ text: 'Trigger 2' }}>\n  Trigger 2\n</Dialog.Trigger>\n\n<Dialog.Root handle={demoDialog}>\n  {({ payload }) => (\n    <Dialog.Portal>\n      <Dialog.Popup>\n        <Dialog.Title>Dialog</Dialog.Title>\n        {payload !== undefined && (\n          <Dialog.Description>\n            This has been opened by {payload.text}\n          </Dialog.Description>\n        )}\n      </Dialog.Popup>\n    </Dialog.Portal>\n  )}\n</Dialog.Root>\n```\n\n### Controlled mode with multiple triggers\n\nYou can control the dialog's open state externally using the `open` and `onOpenChange` props on `<Dialog.Root>`.\nThis allows you to manage the dialog's visibility based on your application's state.\nWhen using multiple triggers, you have to manage which trigger is active with the `triggerId` prop on `<Dialog.Root>` and the `id` prop on each `<Dialog.Trigger>`.\n\nNote that there is no separate `onTriggerIdChange` prop.\nInstead, the `onOpenChange` callback receives an additional argument, `eventDetails`, which contains the trigger element that initiated the state change.\n\nimport { DemoDialogDetachedTriggersControlled } from './demos/detached-triggers-controlled';\n\n<DemoDialogDetachedTriggersControlled />\n\n## API reference\n\n<Reference\n  component=\"Dialog\"\n  parts=\"Root, Trigger, Portal, Backdrop, Viewport, Popup, Title, Description, Close\"\n/>\n\nexport const metadata = {\n  keywords: [\n    'React Dialog',\n    'Modal Dialog Component',\n    'Modal Popup',\n    'Modal Window',\n    'Popup Window',\n    'Modal Overlay',\n    'Overlay',\n    'Lightbox',\n    'Popover Dialog',\n    'Controlled Dialog State',\n    'Nested Dialog React',\n    'Accessible Dialog',\n    'Headless React Components',\n    'Base UI',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/demos/hero/css-modules/index.module.css",
    "content": ".Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Backdrop {\n  --backdrop-opacity: 0.2;\n  --bleed: 3rem;\n  position: fixed;\n  min-height: 100dvh;\n  inset: 0;\n  background-color: black;\n  opacity: calc(var(--backdrop-opacity) * (1 - var(--drawer-swipe-progress)));\n  transition-duration: 450ms;\n  transition-property: opacity;\n  transition-timing-function: cubic-bezier(0.32, 0.72, 0, 1);\n\n  /* iOS 26+: Ensure the backdrop covers the entire visible viewport. */\n  @supports (-webkit-touch-callout: none) {\n    position: absolute;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    --backdrop-opacity: 0.7;\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n  }\n\n  &[data-swiping] {\n    transition-duration: 0ms;\n  }\n\n  &[data-ending-style] {\n    transition-duration: calc(var(--drawer-swipe-strength) * 400ms);\n  }\n}\n\n.Viewport {\n  --viewport-padding: 0px;\n  position: fixed;\n  inset: 0;\n  display: flex;\n  justify-content: flex-end;\n  padding: var(--viewport-padding);\n\n  @supports (-webkit-touch-callout: none) {\n    --viewport-padding: 0.625rem;\n  }\n}\n\n.Popup {\n  --bleed: 3rem;\n  box-sizing: border-box;\n  width: calc(20rem + var(--bleed));\n  max-width: calc(100vw - 3rem + var(--bleed));\n  height: 100%;\n  padding: 1.5rem;\n  padding-right: calc(1.5rem + var(--bleed));\n  margin-right: calc(-1 * var(--bleed));\n  outline: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-50);\n  color: var(--color-gray-900);\n  overflow-y: auto;\n  overscroll-behavior: contain;\n  touch-action: auto;\n  transition: transform 450ms cubic-bezier(0.32, 0.72, 0, 1);\n  will-change: transform;\n  transform: translateX(var(--drawer-swipe-movement-x));\n\n  &[data-swiping] {\n    user-select: none;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    transform: translateX(calc(100% - var(--bleed) + var(--viewport-padding) + 2px));\n  }\n\n  &[data-ending-style] {\n    transition-duration: calc(var(--drawer-swipe-strength) * 400ms);\n  }\n\n  @supports (-webkit-touch-callout: none) {\n    --bleed: 0px;\n    margin-right: 0;\n    border-radius: 10px;\n  }\n}\n\n.Content {\n  width: 100%;\n  max-width: 32rem;\n  margin: 0 auto;\n}\n\n.Title {\n  margin-top: -0.375rem;\n  margin-bottom: 0.25rem;\n  font-size: 1.125rem;\n  line-height: 1.75rem;\n  letter-spacing: -0.0025em;\n  font-weight: 700;\n}\n\n.Description {\n  margin: 0 0 1.5rem;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-600);\n}\n\n.Actions {\n  display: flex;\n  justify-content: flex-end;\n  gap: 1rem;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/demos/hero/css-modules/index.tsx",
    "content": "import { Drawer } from '@base-ui/react/drawer';\nimport styles from './index.module.css';\n\nexport default function ExampleDrawer() {\n  return (\n    <Drawer.Root swipeDirection=\"right\">\n      <Drawer.Trigger className={styles.Button}>Open drawer</Drawer.Trigger>\n      <Drawer.Portal>\n        <Drawer.Backdrop className={styles.Backdrop} />\n        <Drawer.Viewport className={styles.Viewport}>\n          <Drawer.Popup className={styles.Popup}>\n            <Drawer.Content className={styles.Content}>\n              <Drawer.Title className={styles.Title}>Drawer</Drawer.Title>\n              <Drawer.Description className={styles.Description}>\n                This is a drawer that slides in from the side. You can swipe to dismiss it.\n              </Drawer.Description>\n              <div className={styles.Actions}>\n                <Drawer.Close className={styles.Button}>Close</Drawer.Close>\n              </div>\n            </Drawer.Content>\n          </Drawer.Popup>\n        </Drawer.Viewport>\n      </Drawer.Portal>\n    </Drawer.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoDrawerHero = createDemoWithVariants(import.meta.url, { CssModules, Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/demos/hero/tailwind/index.tsx",
    "content": "import { Drawer } from '@base-ui/react/drawer';\n\nexport default function ExampleDrawer() {\n  return (\n    <Drawer.Root swipeDirection=\"right\">\n      <Drawer.Trigger className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n        Open drawer\n      </Drawer.Trigger>\n      <Drawer.Portal>\n        <Drawer.Backdrop className=\"[--backdrop-opacity:0.2] [--bleed:3rem] dark:[--backdrop-opacity:0.7] fixed inset-0 min-h-dvh bg-black opacity-[calc(var(--backdrop-opacity)*(1-var(--drawer-swipe-progress)))] transition-opacity duration-[450ms] ease-[cubic-bezier(0.32,0.72,0,1)] data-[swiping]:duration-0 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0 data-[ending-style]:duration-[calc(var(--drawer-swipe-strength)*400ms)] supports-[-webkit-touch-callout:none]:absolute\" />\n        <Drawer.Viewport className=\"[--viewport-padding:0px] supports-[-webkit-touch-callout:none]:[--viewport-padding:0.625rem] fixed inset-0 flex items-stretch justify-end p-[var(--viewport-padding)]\">\n          <Drawer.Popup className=\"[--bleed:3rem] supports-[-webkit-touch-callout:none]:[--bleed:0px] h-full w-[calc(20rem+3rem)] max-w-[calc(100vw-3rem+3rem)] -mr-[3rem] bg-gray-50 p-6 pr-[calc(1.5rem+3rem)] text-gray-900 outline outline-1 outline-gray-200 overflow-y-auto overscroll-contain touch-auto [transform:translateX(var(--drawer-swipe-movement-x))] transition-transform duration-[450ms] ease-[cubic-bezier(0.32,0.72,0,1)] data-[swiping]:select-none data-[ending-style]:[transform:translateX(calc(100%-var(--bleed)+var(--viewport-padding)+2px))] data-[starting-style]:[transform:translateX(calc(100%-var(--bleed)+var(--viewport-padding)+2px))] data-[ending-style]:duration-[calc(var(--drawer-swipe-strength)*400ms)] supports-[-webkit-touch-callout:none]:mr-0 supports-[-webkit-touch-callout:none]:w-[20rem] supports-[-webkit-touch-callout:none]:max-w-[calc(100vw-20px)] supports-[-webkit-touch-callout:none]:rounded-[10px] supports-[-webkit-touch-callout:none]:pr-6 dark:outline-gray-300\">\n            <Drawer.Content className=\"mx-auto w-full max-w-[32rem]\">\n              <Drawer.Title className=\"-mt-1.5 mb-1 text-lg font-bold\">Drawer</Drawer.Title>\n              <Drawer.Description className=\"mb-6 text-base text-gray-600\">\n                This is a drawer that slides in from the side. You can swipe to dismiss it.\n              </Drawer.Description>\n              <div className=\"flex justify-end gap-4\">\n                <Drawer.Close className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n                  Close\n                </Drawer.Close>\n              </div>\n            </Drawer.Content>\n          </Drawer.Popup>\n        </Drawer.Viewport>\n      </Drawer.Portal>\n    </Drawer.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/demos/indent-provider/css-modules/index.module.css",
    "content": ".Root {\n  --bleed: 3rem;\n  position: relative;\n  width: 100%;\n  overflow: hidden;\n}\n\n.Indent {\n  --indent-progress: var(--drawer-swipe-progress);\n  --indent-radius: calc(1rem * (1 - var(--indent-progress)));\n  --indent-transition: calc(1 - clamp(0, calc(var(--drawer-swipe-progress) * 100000), 1));\n  position: relative;\n  min-height: 320px;\n  transition:\n    transform 0.4s cubic-bezier(0.32, 0.72, 0, 1),\n    border-radius 0.25s cubic-bezier(0.32, 0.72, 0, 1);\n  transform-origin: center top;\n  will-change: transform;\n  border: 1px solid var(--color-gray-200);\n  contain: layout;\n  padding: 1rem;\n  color: var(--color-gray-900);\n  background-color: var(--color-gray-50);\n  transition-duration:\n    calc(400ms * var(--indent-transition)), calc(250ms * var(--indent-transition));\n\n  &[data-active] {\n    transform: scale(calc(0.98 + (0.02 * var(--indent-progress))))\n      translateY(calc(0.5rem * (1 - var(--indent-progress))));\n    border-top-left-radius: var(--indent-radius);\n    border-top-right-radius: var(--indent-radius);\n  }\n}\n\n.Center {\n  min-height: 320px;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.IndentBackground {\n  position: absolute;\n  inset: 0;\n  background-color: black;\n\n  @media (prefers-color-scheme: dark) {\n    background-color: var(--color-gray-300);\n  }\n}\n\n.Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Backdrop {\n  --backdrop-opacity: 0.2;\n  --bleed: 3rem;\n  position: absolute;\n  min-height: 100dvh;\n  inset: 0;\n  background-color: black;\n  opacity: calc(var(--backdrop-opacity) * (1 - var(--drawer-swipe-progress)));\n  transition-duration: 450ms;\n  transition-property: opacity;\n  transition-timing-function: cubic-bezier(0.32, 0.72, 0, 1);\n\n  @media (prefers-color-scheme: dark) {\n    --backdrop-opacity: 0.7;\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n  }\n\n  &[data-swiping] {\n    transition-duration: 0ms;\n  }\n\n  &[data-ending-style] {\n    transition-duration: calc(var(--drawer-swipe-strength) * 400ms);\n  }\n}\n\n.Viewport {\n  position: absolute;\n  inset: 0;\n  display: flex;\n  align-items: flex-end;\n  justify-content: center;\n}\n\n.Popup {\n  box-sizing: border-box;\n  width: 100%;\n  max-height: calc(80vh + var(--bleed));\n  margin-bottom: calc(-1 * var(--bleed));\n  padding: 1rem 1.5rem 1.5rem;\n  padding-bottom: calc(1.5rem + env(safe-area-inset-bottom, 0px) + var(--bleed));\n  border-radius: 1rem 1rem 0 0;\n  outline: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-50);\n  color: var(--color-gray-900);\n  overflow-y: auto;\n  overscroll-behavior: contain;\n  transition: transform 450ms cubic-bezier(0.32, 0.72, 0, 1);\n  will-change: transform;\n  transform: translateY(var(--drawer-swipe-movement-y));\n\n  &[data-swiping] {\n    user-select: none;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    transform: translateY(calc(100% - var(--bleed) + 2px));\n  }\n\n  &[data-ending-style] {\n    transition-duration: calc(var(--drawer-swipe-strength) * 400ms);\n  }\n}\n\n.Content {\n  width: 100%;\n  max-width: 32rem;\n  margin: 0 auto;\n}\n\n.Handle {\n  width: 3rem;\n  height: 0.25rem;\n  margin: 0 auto 1rem;\n  border-radius: 9999px;\n  background-color: var(--color-gray-300);\n}\n\n.Title {\n  margin-top: 0;\n  margin-bottom: 0.25rem;\n  font-size: 1.125rem;\n  line-height: 1.75rem;\n  letter-spacing: -0.0025em;\n  font-weight: 700;\n  text-align: center;\n}\n\n.Description {\n  margin: 0 0 1.5rem;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-600);\n  text-align: center;\n}\n\n.Actions {\n  display: flex;\n  justify-content: center;\n  gap: 1rem;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/demos/indent-provider/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Drawer } from '@base-ui/react/drawer';\nimport styles from './index.module.css';\n\nexport default function ExampleDrawer() {\n  const [portalContainer, setPortalContainer] = React.useState<HTMLDivElement | null>(null);\n\n  return (\n    <Drawer.Provider>\n      <div className={styles.Root} ref={setPortalContainer}>\n        <Drawer.IndentBackground className={styles.IndentBackground} />\n        <Drawer.Indent className={styles.Indent}>\n          <div className={styles.Center}>\n            <Drawer.Root modal={false}>\n              <Drawer.Trigger className={styles.Button}>Open drawer</Drawer.Trigger>\n              <Drawer.Portal container={portalContainer}>\n                <Drawer.Backdrop className={styles.Backdrop} />\n                <Drawer.Viewport className={styles.Viewport}>\n                  <Drawer.Popup className={styles.Popup}>\n                    <div className={styles.Handle} />\n                    <Drawer.Content className={styles.Content}>\n                      <Drawer.Title className={styles.Title}>Notifications</Drawer.Title>\n                      <Drawer.Description className={styles.Description}>\n                        You are all caught up. Good job!\n                      </Drawer.Description>\n                      <div className={styles.Actions}>\n                        <Drawer.Close className={styles.Button}>Close</Drawer.Close>\n                      </div>\n                    </Drawer.Content>\n                  </Drawer.Popup>\n                </Drawer.Viewport>\n              </Drawer.Portal>\n            </Drawer.Root>\n          </div>\n        </Drawer.Indent>\n      </div>\n    </Drawer.Provider>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/demos/indent-provider/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoDrawerIndentProvider = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/demos/indent-provider/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Drawer } from '@base-ui/react/drawer';\n\nexport default function ExampleDrawer() {\n  const [portalContainer, setPortalContainer] = React.useState<HTMLDivElement | null>(null);\n\n  return (\n    <Drawer.Provider>\n      <div ref={setPortalContainer} className=\"[--bleed:3rem] relative w-full overflow-hidden\">\n        <Drawer.IndentBackground className=\"absolute inset-0 bg-black dark:bg-gray-300\" />\n        <Drawer.Indent className=\"[--indent-progress:var(--drawer-swipe-progress)] [--indent-radius:calc(1rem*(1-var(--indent-progress)))] [--indent-transition:calc(1-clamp(0,calc(var(--drawer-swipe-progress)*100000),1))] relative min-h-[320px] bg-gray-50 border border-gray-200 p-4 text-gray-900 [transition:transform_0.4s_cubic-bezier(0.32,0.72,0,1),border-radius_0.25s_cubic-bezier(0.32,0.72,0,1)] origin-[center_top] will-change-transform [transform:scale(1)_translateY(0)] [transition-duration:calc(400ms*var(--indent-transition)),calc(250ms*var(--indent-transition))] data-[active]:[transform:scale(calc(0.98+(0.02*var(--indent-progress))))_translateY(calc(0.5rem*(1-var(--indent-progress))))] data-[active]:[border-top-left-radius:var(--indent-radius)] data-[active]:[border-top-right-radius:var(--indent-radius)]\">\n          <div className=\"flex min-h-[320px] items-center justify-center\">\n            <Drawer.Root modal={false}>\n              <Drawer.Trigger className=\"box-border flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal leading-6 text-gray-900 select-none hover:bg-gray-100 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n                Open drawer\n              </Drawer.Trigger>\n              <Drawer.Portal container={portalContainer}>\n                <Drawer.Backdrop className=\"[--backdrop-opacity:0.2] [--bleed:3rem] dark:[--backdrop-opacity:0.7] absolute inset-0 min-h-dvh bg-black opacity-[calc(var(--backdrop-opacity)*(1-var(--drawer-swipe-progress)))] transition-opacity duration-[450ms] ease-[cubic-bezier(0.32,0.72,0,1)] data-[swiping]:duration-0 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0 data-[ending-style]:duration-[calc(var(--drawer-swipe-strength)*400ms)] supports-[-webkit-touch-callout:none]:absolute\" />\n                <Drawer.Viewport className=\"absolute inset-0 flex items-end justify-center\">\n                  <Drawer.Popup className=\"box-border w-full max-h-[calc(80vh+var(--bleed))] -mb-[var(--bleed)] rounded-t-2xl outline outline-1 outline-gray-200 bg-gray-50 px-6 py-4 pb-[calc(1.5rem+env(safe-area-inset-bottom,0px)+var(--bleed))] text-gray-900 overflow-y-auto overscroll-contain transition-transform duration-[450ms] ease-[cubic-bezier(0.32,0.72,0,1)] [transform:translateY(var(--drawer-swipe-movement-y))] data-[swiping]:select-none data-[ending-style]:[transform:translateY(calc(100%-var(--bleed)+2px))] data-[starting-style]:[transform:translateY(calc(100%-var(--bleed)+2px))] data-[ending-style]:duration-[calc(var(--drawer-swipe-strength)*400ms)] dark:outline-gray-300\">\n                    <div className=\"mx-auto mb-4 h-1 w-12 rounded-full bg-gray-300\" />\n                    <Drawer.Content className=\"mx-auto w-full max-w-[32rem]\">\n                      <Drawer.Title className=\"mt-0 mb-1 text-lg leading-7 font-bold tracking-[-0.0025em] text-center\">\n                        Notifications\n                      </Drawer.Title>\n                      <Drawer.Description className=\"mb-6 text-base leading-6 text-gray-600 text-center\">\n                        You are all caught up. Good job!\n                      </Drawer.Description>\n                      <div className=\"flex justify-center gap-4\">\n                        <Drawer.Close className=\"box-border flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal leading-6 text-gray-900 select-none hover:bg-gray-100 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n                          Close\n                        </Drawer.Close>\n                      </div>\n                    </Drawer.Content>\n                  </Drawer.Popup>\n                </Drawer.Viewport>\n              </Drawer.Portal>\n            </Drawer.Root>\n          </div>\n        </Drawer.Indent>\n      </div>\n    </Drawer.Provider>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/demos/mobile-nav/css-modules/index.module.css",
    "content": ".Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Backdrop {\n  --backdrop-opacity: 1;\n  position: fixed;\n  inset: 0;\n  min-height: 100dvh;\n  transition-duration: 600ms;\n  transition-property: -webkit-backdrop-filter, backdrop-filter, opacity;\n  transition-timing-function: var(--ease-out-fast);\n  backdrop-filter: blur(1.5px);\n  background-image: linear-gradient(to bottom, rgb(0 0 0 / 5%) 0, rgb(0 0 0 / 10%) 50%);\n  opacity: calc(var(--backdrop-opacity) * (1 - var(--drawer-swipe-progress)));\n\n  @supports (-webkit-touch-callout: none) {\n    position: absolute;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    --backdrop-opacity: 0.7;\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    backdrop-filter: blur(0);\n    opacity: 0;\n  }\n\n  &[data-ending-style] {\n    transition-duration: 350ms;\n    transition-timing-function: cubic-bezier(0.375, 0.015, 0.545, 0.455);\n  }\n}\n\n.Viewport {\n  position: fixed;\n  inset: 0;\n}\n\n.ScrollAreaRoot {\n  box-sizing: border-box;\n  height: 100%;\n  overscroll-behavior: contain;\n  transition: transform 600ms cubic-bezier(0.45, 1.005, 0, 1.005);\n\n  [data-ending-style] & {\n    pointer-events: none;\n  }\n\n  [data-starting-style] & {\n    transform: translateY(100dvh);\n  }\n}\n\n.ScrollAreaViewport {\n  box-sizing: border-box;\n  height: 100%;\n  overscroll-behavior: contain;\n  touch-action: auto;\n}\n\n.ScrollContent {\n  display: flex;\n  align-items: flex-end;\n  justify-content: center;\n  min-height: 100%;\n  padding-top: 2rem;\n\n  @media (min-width: 768px) {\n    padding-top: 4rem;\n    padding-bottom: 4rem;\n    padding-inline: 4rem;\n  }\n}\n\n.Content {\n  width: 100%;\n}\n\n.Popup {\n  box-sizing: border-box;\n  width: 100%;\n  max-width: 42rem;\n  margin: 0 auto;\n  outline: 0;\n  transition: transform 600ms cubic-bezier(0.45, 1.005, 0, 1.005);\n  will-change: transform;\n  transform: translateY(var(--drawer-swipe-movement-y));\n\n  &[data-swiping] {\n    user-select: none;\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    transition: none;\n  }\n\n  &[data-ending-style] {\n    transform: translateY(calc(max(100dvh, 100%) + 2px));\n    transition-duration: 350ms;\n    transition-timing-function: cubic-bezier(0.375, 0.015, 0.545, 0.455);\n  }\n}\n\n.Panel {\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  padding: 1rem 1.5rem 1.5rem;\n  border-top-left-radius: 1rem;\n  border-top-right-radius: 1rem;\n  outline: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-50);\n  box-shadow:\n    0 10px 64px -10px rgb(36 40 52 / 20%),\n    0 0.25px 0 1px var(--color-gray-200);\n  color: var(--color-gray-900);\n  transition: box-shadow 350ms cubic-bezier(0.375, 0.015, 0.545, 0.455);\n}\n\n@media (prefers-color-scheme: dark) {\n  .Panel {\n    outline: 1px solid var(--color-gray-300);\n    box-shadow: 0 0 0 1px var(--color-gray-200);\n  }\n}\n\n.Popup[data-ending-style] .Panel {\n  box-shadow:\n    0 10px 64px -10px rgb(36 40 52 / 0%),\n    0 0.25px 0 1px rgb(0 0 0 / 0%);\n}\n\n@media (prefers-color-scheme: dark) {\n  .Popup[data-ending-style] .Panel {\n    box-shadow: 0 0 0 1px rgb(0 0 0 / 0%);\n  }\n}\n\n@media (min-width: 768px) {\n  .Panel {\n    border-radius: 0.75rem;\n  }\n}\n\n.Header {\n  display: grid;\n  grid-template-columns: 1fr auto 1fr;\n  align-items: center;\n  margin-bottom: 0.75rem;\n}\n\n.HeaderSpacer {\n  width: 2.25rem;\n  height: 2.25rem;\n}\n\n.Handle {\n  width: 3rem;\n  height: 0.25rem;\n  border-radius: 9999px;\n  background-color: var(--color-gray-300);\n  justify-self: center;\n}\n\n.CloseButton {\n  width: 2.25rem;\n  height: 2.25rem;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  border-radius: 9999px;\n  border: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-50);\n  color: var(--color-gray-900);\n  justify-self: end;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Title {\n  margin: 0 0 0.25rem;\n  font-size: 1.125rem;\n  line-height: 1.75rem;\n  letter-spacing: -0.0025em;\n  font-weight: 700;\n}\n\n.Description {\n  margin: 0 0 1.25rem;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-600);\n}\n\n.ScrollArea {\n  padding-bottom: 2rem;\n}\n\n.List {\n  list-style: none;\n  padding: 0;\n  margin: 0;\n  display: grid;\n  gap: 0.25rem;\n}\n\n.LongList {\n  list-style: none;\n  padding: 0;\n  margin: 1.5rem 0 0;\n  display: grid;\n  gap: 0.25rem;\n}\n\n.Item {\n  display: flex;\n}\n\n.Link {\n  width: 100%;\n  padding: 0.75rem 1rem;\n  border-radius: 0.75rem;\n  color: inherit;\n  text-decoration: none;\n  background-color: var(--color-gray-100);\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Scrollbar {\n  position: absolute;\n  display: flex;\n  width: 0.25rem;\n  margin: 0.4rem;\n  justify-content: center;\n  border-radius: 1rem;\n  opacity: 0;\n  transition: opacity 250ms;\n  pointer-events: none;\n\n  &:hover,\n  &[data-scrolling] {\n    opacity: 1;\n    transition-duration: 75ms;\n    transition-delay: 0ms;\n    pointer-events: auto;\n  }\n\n  [data-ending-style] & {\n    transition-duration: 250ms;\n    opacity: 0;\n  }\n\n  @media (min-width: 768px) {\n    width: 0.4375rem;\n  }\n}\n\n.ScrollbarThumb {\n  width: 100%;\n  border-radius: inherit;\n  background-color: var(--color-gray-500);\n\n  &::before {\n    content: '';\n    position: absolute;\n    top: 50%;\n    left: 50%;\n    transform: translate(-50%, -50%);\n    width: calc(100% + 1rem);\n    height: calc(100% + 1rem);\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/demos/mobile-nav/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Drawer } from '@base-ui/react/drawer';\nimport { ScrollArea } from '@base-ui/react/scroll-area';\nimport styles from './index.module.css';\n\nconst ITEMS = [\n  { href: '/react/overview', label: 'Overview' },\n  { href: '/react/components', label: 'Components' },\n  { href: '/react/utils', label: 'Utilities' },\n  { href: '/react/overview/releases', label: 'Releases' },\n] as const;\n\nconst LONG_LIST = [\n  { href: '/react/components/accordion', label: 'Accordion' },\n  { href: '/react/components/alert-dialog', label: 'Alert Dialog' },\n  { href: '/react/components/autocomplete', label: 'Autocomplete' },\n  { href: '/react/components/avatar', label: 'Avatar' },\n  { href: '/react/components/button', label: 'Button' },\n  { href: '/react/components/checkbox', label: 'Checkbox' },\n  { href: '/react/components/checkbox-group', label: 'Checkbox Group' },\n  { href: '/react/components/collapsible', label: 'Collapsible' },\n  { href: '/react/components/combobox', label: 'Combobox' },\n  { href: '/react/components/context-menu', label: 'Context Menu' },\n  { href: '/react/components/dialog', label: 'Dialog' },\n  { href: '/react/components/drawer', label: 'Drawer' },\n  { href: '/react/components/field', label: 'Field' },\n  { href: '/react/components/fieldset', label: 'Fieldset' },\n  { href: '/react/components/form', label: 'Form' },\n  { href: '/react/components/input', label: 'Input' },\n  { href: '/react/components/menu', label: 'Menu' },\n  { href: '/react/components/menubar', label: 'Menubar' },\n  { href: '/react/components/meter', label: 'Meter' },\n  { href: '/react/components/navigation-menu', label: 'Navigation Menu' },\n  { href: '/react/components/number-field', label: 'Number Field' },\n  { href: '/react/components/popover', label: 'Popover' },\n  { href: '/react/components/preview-card', label: 'Preview Card' },\n  { href: '/react/components/progress', label: 'Progress' },\n  { href: '/react/components/radio', label: 'Radio' },\n  { href: '/react/components/scroll-area', label: 'Scroll Area' },\n  { href: '/react/components/select', label: 'Select' },\n  { href: '/react/components/separator', label: 'Separator' },\n  { href: '/react/components/slider', label: 'Slider' },\n  { href: '/react/components/switch', label: 'Switch' },\n  { href: '/react/components/tabs', label: 'Tabs' },\n  { href: '/react/components/toast', label: 'Toast' },\n  { href: '/react/components/toggle', label: 'Toggle' },\n  { href: '/react/components/toggle-group', label: 'Toggle Group' },\n  { href: '/react/components/toolbar', label: 'Toolbar' },\n  { href: '/react/components/tooltip', label: 'Tooltip' },\n] as const;\n\nexport default function ExampleDrawerMobileNav() {\n  return (\n    <Drawer.Root>\n      <Drawer.Trigger className={styles.Button}>Open mobile menu</Drawer.Trigger>\n      <Drawer.Portal>\n        <Drawer.Backdrop className={styles.Backdrop} />\n        <Drawer.Viewport className={styles.Viewport}>\n          <ScrollArea.Root style={{ position: undefined }} className={styles.ScrollAreaRoot}>\n            <ScrollArea.Viewport className={styles.ScrollAreaViewport}>\n              <ScrollArea.Content className={styles.ScrollContent}>\n                <Drawer.Popup className={styles.Popup}>\n                  <nav aria-label=\"Navigation\" className={styles.Panel}>\n                    <div className={styles.Header}>\n                      <div aria-hidden className={styles.HeaderSpacer} />\n                      <div className={styles.Handle} />\n                      <Drawer.Close aria-label=\"Close menu\" className={styles.CloseButton}>\n                        <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\">\n                          <path\n                            d=\"M0.75 0.75L6 6M11.25 11.25L6 6M6 6L0.75 11.25M6 6L11.25 0.75\"\n                            stroke=\"currentcolor\"\n                            strokeWidth=\"2\"\n                            strokeLinecap=\"round\"\n                            strokeLinejoin=\"round\"\n                          />\n                        </svg>\n                      </Drawer.Close>\n                    </div>\n\n                    <Drawer.Content className={styles.Content}>\n                      <Drawer.Title className={styles.Title}>Menu</Drawer.Title>\n                      <Drawer.Description className={styles.Description}>\n                        Scroll the long list. Flick down from the top to dismiss.\n                      </Drawer.Description>\n\n                      <div className={styles.ScrollArea}>\n                        <ul className={styles.List}>\n                          {ITEMS.map((item) => (\n                            <li key={item.label} className={styles.Item}>\n                              <a className={styles.Link} href={item.href}>\n                                {item.label}\n                              </a>\n                            </li>\n                          ))}\n                        </ul>\n\n                        <ul className={styles.LongList} aria-label=\"Component links\">\n                          {LONG_LIST.map((item) => (\n                            <li key={item.label} className={styles.Item}>\n                              <a className={styles.Link} href={item.href}>\n                                {item.label}\n                              </a>\n                            </li>\n                          ))}\n                        </ul>\n                      </div>\n                    </Drawer.Content>\n                  </nav>\n                </Drawer.Popup>\n              </ScrollArea.Content>\n            </ScrollArea.Viewport>\n            <ScrollArea.Scrollbar className={styles.Scrollbar}>\n              <ScrollArea.Thumb className={styles.ScrollbarThumb} />\n            </ScrollArea.Scrollbar>\n          </ScrollArea.Root>\n        </Drawer.Viewport>\n      </Drawer.Portal>\n    </Drawer.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/demos/mobile-nav/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoDrawerMobileNav = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/demos/mobile-nav/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Drawer } from '@base-ui/react/drawer';\nimport { ScrollArea } from '@base-ui/react/scroll-area';\n\nconst ITEMS = [\n  { href: '/react/overview', label: 'Overview' },\n  { href: '/react/components', label: 'Components' },\n  { href: '/react/utils', label: 'Utilities' },\n  { href: '/react/overview/releases', label: 'Releases' },\n] as const;\n\nconst LONG_LIST = [\n  { href: '/react/components/accordion', label: 'Accordion' },\n  { href: '/react/components/alert-dialog', label: 'Alert Dialog' },\n  { href: '/react/components/autocomplete', label: 'Autocomplete' },\n  { href: '/react/components/avatar', label: 'Avatar' },\n  { href: '/react/components/button', label: 'Button' },\n  { href: '/react/components/checkbox', label: 'Checkbox' },\n  { href: '/react/components/checkbox-group', label: 'Checkbox Group' },\n  { href: '/react/components/collapsible', label: 'Collapsible' },\n  { href: '/react/components/combobox', label: 'Combobox' },\n  { href: '/react/components/context-menu', label: 'Context Menu' },\n  { href: '/react/components/dialog', label: 'Dialog' },\n  { href: '/react/components/drawer', label: 'Drawer' },\n  { href: '/react/components/field', label: 'Field' },\n  { href: '/react/components/fieldset', label: 'Fieldset' },\n  { href: '/react/components/form', label: 'Form' },\n  { href: '/react/components/input', label: 'Input' },\n  { href: '/react/components/menu', label: 'Menu' },\n  { href: '/react/components/menubar', label: 'Menubar' },\n  { href: '/react/components/meter', label: 'Meter' },\n  { href: '/react/components/navigation-menu', label: 'Navigation Menu' },\n  { href: '/react/components/number-field', label: 'Number Field' },\n  { href: '/react/components/popover', label: 'Popover' },\n  { href: '/react/components/preview-card', label: 'Preview Card' },\n  { href: '/react/components/progress', label: 'Progress' },\n  { href: '/react/components/radio', label: 'Radio' },\n  { href: '/react/components/scroll-area', label: 'Scroll Area' },\n  { href: '/react/components/select', label: 'Select' },\n  { href: '/react/components/separator', label: 'Separator' },\n  { href: '/react/components/slider', label: 'Slider' },\n  { href: '/react/components/switch', label: 'Switch' },\n  { href: '/react/components/tabs', label: 'Tabs' },\n  { href: '/react/components/toast', label: 'Toast' },\n  { href: '/react/components/toggle', label: 'Toggle' },\n  { href: '/react/components/toggle-group', label: 'Toggle Group' },\n  { href: '/react/components/toolbar', label: 'Toolbar' },\n  { href: '/react/components/tooltip', label: 'Tooltip' },\n] as const;\n\nexport default function ExampleDrawerMobileNav() {\n  return (\n    <Drawer.Root>\n      <Drawer.Trigger className=\"box-border flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 m-0 outline-none text-base font-normal leading-6 text-gray-900 select-none hover:bg-gray-100 active:bg-gray-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-blue-800 focus-visible:-outline-offset-1\">\n        Open mobile menu\n      </Drawer.Trigger>\n      <Drawer.Portal>\n        <Drawer.Backdrop className=\"[--backdrop-opacity:1] dark:[--backdrop-opacity:0.7] fixed inset-0 min-h-[100dvh] bg-[linear-gradient(to_bottom,rgb(0_0_0/5%)_0,rgb(0_0_0/10%)_50%)] opacity-[calc(var(--backdrop-opacity)*(1-var(--drawer-swipe-progress)))] transition-[backdrop-filter,opacity] duration-[600ms] ease-[var(--ease-out-fast)] backdrop-blur-[1.5px] supports-[-webkit-touch-callout:none]:absolute data-[starting-style]:opacity-0 data-[ending-style]:opacity-0 data-[starting-style]:backdrop-blur-0 data-[ending-style]:backdrop-blur-0 data-[ending-style]:duration-[350ms] data-[ending-style]:ease-[cubic-bezier(0.375,0.015,0.545,0.455)]\" />\n        <Drawer.Viewport className=\"group fixed inset-0\">\n          <ScrollArea.Root\n            style={{ position: undefined }}\n            className=\"box-border h-full overscroll-contain transition-[transform,translate] duration-[600ms] ease-[cubic-bezier(0.45,1.005,0,1.005)] group-data-[starting-style]:translate-y-[100dvh] group-data-[ending-style]:pointer-events-none\"\n          >\n            <ScrollArea.Viewport className=\"box-border h-full overscroll-contain touch-auto\">\n              <ScrollArea.Content className=\"flex min-h-full items-end justify-center pt-8 md:py-16 md:px-16\">\n                <Drawer.Popup className=\"group box-border w-full max-w-[42rem] outline-none transition-transform duration-[800ms] ease-[cubic-bezier(0.45,1.005,0,1.005)] [transform:translateY(var(--drawer-swipe-movement-y))] data-[swiping]:select-none data-[ending-style]:[transform:translateY(calc(max(100dvh,100%)+2px))] data-[ending-style]:duration-[350ms] data-[ending-style]:ease-[cubic-bezier(0.375,0.015,0.545,0.455)]\">\n                  <nav\n                    aria-label=\"Navigation\"\n                    className=\"relative flex flex-col rounded-t-2xl bg-gray-50 px-6 pt-4 pb-6 text-gray-900 shadow-[0_10px_64px_-10px_rgb(36_40_52/20%),0_0.25px_0_1px_oklch(12%_9%_264deg/7%)] outline outline-1 outline-gray-200 transition-shadow duration-[350ms] ease-[cubic-bezier(0.375,0.015,0.545,0.455)] group-data-[ending-style]:shadow-[0_10px_64px_-10px_rgb(36_40_52/0%),0_0.25px_0_1px_rgb(0_0_0/0%)] dark:outline-gray-300 dark:shadow-[0_0_0_1px_oklch(29%_0.75%_264deg/80%)] dark:group-data-[ending-style]:shadow-[0_0_0_1px_rgb(0_0_0/0%)] md:rounded-xl\"\n                  >\n                    <div className=\"mb-3 grid grid-cols-[1fr_auto_1fr] items-center\">\n                      <div aria-hidden className=\"h-9 w-9\" />\n                      <div className=\"h-1 w-12 justify-self-center rounded-full bg-gray-300\" />\n                      <Drawer.Close\n                        aria-label=\"Close menu\"\n                        className=\"flex h-9 w-9 items-center justify-center justify-self-end rounded-full border border-gray-200 bg-gray-50 text-gray-900 hover:bg-gray-100 active:bg-gray-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-blue-800 focus-visible:-outline-offset-1\"\n                      >\n                        <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\">\n                          <path\n                            d=\"M0.75 0.75L6 6M11.25 11.25L6 6M6 6L0.75 11.25M6 6L11.25 0.75\"\n                            stroke=\"currentcolor\"\n                            strokeWidth=\"2\"\n                            strokeLinecap=\"round\"\n                            strokeLinejoin=\"round\"\n                          />\n                        </svg>\n                      </Drawer.Close>\n                    </div>\n\n                    <Drawer.Content className=\"w-full\">\n                      <Drawer.Title className=\"m-0 mb-1 text-lg font-bold leading-7 tracking-[-0.0025em]\">\n                        Menu\n                      </Drawer.Title>\n                      <Drawer.Description className=\"m-0 mb-5 text-base leading-6 text-gray-600\">\n                        Scroll the long list. Flick down from the top to dismiss.\n                      </Drawer.Description>\n\n                      <div className=\"pb-8\">\n                        <ul className=\"grid list-none gap-1 p-0 m-0\">\n                          {ITEMS.map((item) => (\n                            <li key={item.label} className=\"flex\">\n                              <a\n                                className=\"w-full rounded-xl bg-gray-100 px-4 py-3 text-gray-900 no-underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-blue-800 focus-visible:-outline-offset-1\"\n                                href={item.href}\n                              >\n                                {item.label}\n                              </a>\n                            </li>\n                          ))}\n                        </ul>\n\n                        <ul\n                          aria-label=\"Component links\"\n                          className=\"mt-6 grid list-none gap-1 p-0 m-0\"\n                        >\n                          {LONG_LIST.map((item) => (\n                            <li key={item.label} className=\"flex\">\n                              <a\n                                className=\"w-full rounded-xl bg-gray-100 px-4 py-3 text-gray-900 no-underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-blue-800 focus-visible:-outline-offset-1\"\n                                href={item.href}\n                              >\n                                {item.label}\n                              </a>\n                            </li>\n                          ))}\n                        </ul>\n                      </div>\n                    </Drawer.Content>\n                  </nav>\n                </Drawer.Popup>\n              </ScrollArea.Content>\n            </ScrollArea.Viewport>\n            <ScrollArea.Scrollbar className=\"pointer-events-none absolute m-[0.4rem] flex w-[0.25rem] justify-center rounded-[1rem] opacity-0 transition-opacity duration-[250ms] data-[scrolling]:pointer-events-auto data-[scrolling]:opacity-100 data-[scrolling]:duration-[75ms] data-[scrolling]:delay-[0ms] hover:pointer-events-auto hover:opacity-100 hover:duration-[75ms] hover:delay-[0ms] md:w-[0.4375rem] data-[ending-style]:opacity-0 data-[ending-style]:duration-[250ms]\">\n              <ScrollArea.Thumb className=\"w-full rounded-[inherit] bg-gray-500 before:absolute before:content-[''] before:top-1/2 before:left-1/2 before:h-[calc(100%+1rem)] before:w-[calc(100%+1rem)] before:-translate-x-1/2 before:-translate-y-1/2\" />\n            </ScrollArea.Scrollbar>\n          </ScrollArea.Root>\n        </Drawer.Viewport>\n      </Drawer.Portal>\n    </Drawer.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/demos/nested/css-modules/index.module.css",
    "content": ".Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.GhostButton {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  background-color: transparent;\n  color: var(--color-blue);\n  border-radius: 0.25rem;\n  padding: 0.125rem 0.375rem;\n  margin: -0.125rem -0.375rem;\n  border: 0;\n  outline: 0;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: color-mix(in oklch, var(--color-blue), transparent 95%);\n    }\n  }\n\n  &:active {\n    background-color: color-mix(in oklch, var(--color-blue), transparent 90%);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    @media (hover: hover) {\n      &:hover {\n        background-color: color-mix(in oklch, var(--color-blue), transparent 85%);\n      }\n    }\n\n    &:active {\n      background-color: color-mix(in oklch, var(--color-blue), transparent 75%);\n    }\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Backdrop {\n  --backdrop-opacity: 0.2;\n  --bleed: 3rem;\n  position: fixed;\n  min-height: 100dvh;\n  inset: 0;\n  background-color: black;\n  opacity: calc(var(--backdrop-opacity) * (1 - var(--drawer-swipe-progress)));\n  transition-duration: 450ms;\n  transition-property: opacity;\n  transition-timing-function: cubic-bezier(0.32, 0.72, 0, 1);\n\n  @media (prefers-color-scheme: dark) {\n    --backdrop-opacity: 0.7;\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n  }\n\n  &[data-swiping] {\n    transition-duration: 0ms;\n  }\n\n  &[data-ending-style] {\n    pointer-events: none;\n    transition-duration: calc(var(--drawer-swipe-strength) * 400ms);\n  }\n}\n\n.Viewport {\n  position: fixed;\n  inset: 0;\n  display: flex;\n  align-items: flex-end;\n  justify-content: center;\n}\n\n.Popup {\n  --bleed: 3rem;\n  --peek: 1rem;\n  --stack-progress: clamp(0, var(--drawer-swipe-progress), 1);\n  --stack-step: 0.05;\n  --stack-peek-offset: max(\n    0px,\n    calc((var(--nested-drawers) - var(--stack-progress)) * var(--peek))\n  );\n  --stack-scale-base: max(0, calc(1 - (var(--nested-drawers) * var(--stack-step))));\n  --stack-scale: calc(var(--stack-scale-base) + (var(--stack-step) * var(--stack-progress)));\n  --stack-shrink: calc(1 - var(--stack-scale));\n  --stack-height: max(\n    0px,\n    calc(var(--drawer-frontmost-height, var(--drawer-height)) - var(--bleed))\n  );\n  --translate-y: calc(\n    var(--drawer-swipe-movement-y) - var(--stack-peek-offset) -\n      (var(--stack-shrink) * var(--stack-height))\n  );\n\n  box-sizing: border-box;\n  position: relative;\n  width: 100%;\n  max-height: calc(80vh + var(--bleed));\n  height: var(--drawer-height, auto);\n  margin-bottom: calc(-1 * var(--bleed));\n  padding: 1rem 1.5rem 1.5rem;\n  padding-bottom: calc(1.5rem + env(safe-area-inset-bottom, 0px) + var(--bleed));\n  border-radius: 1rem 1rem 0 0;\n  outline: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-50);\n  color: var(--color-gray-900);\n  overflow-y: auto;\n  overscroll-behavior: contain;\n  box-shadow: 0 2px 10px rgb(0 0 0 / 0.1);\n  transform-origin: 50% calc(100% - var(--bleed));\n  transition:\n    transform 450ms cubic-bezier(0.32, 0.72, 0, 1),\n    height 450ms cubic-bezier(0.32, 0.72, 0, 1),\n    box-shadow 450ms cubic-bezier(0.32, 0.72, 0, 1);\n  will-change: transform;\n  transform: translateY(var(--translate-y)) scale(var(--stack-scale));\n\n  &[data-swiping],\n  &[data-nested-drawer-swiping] {\n    transition-duration: 0ms;\n  }\n\n  &[data-swiping] {\n    user-select: none;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n  }\n\n  &::after {\n    content: '';\n    inset: 0;\n    position: absolute;\n    border-radius: inherit;\n    background-color: transparent;\n    pointer-events: none;\n    transition: background-color 450ms cubic-bezier(0.32, 0.72, 0, 1);\n  }\n\n  &[data-nested-drawer-open] {\n    height: calc(var(--stack-height) + var(--bleed));\n    overflow: hidden;\n\n    &::after {\n      background-color: rgb(0 0 0 / 0.05);\n    }\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    transform: translateY(calc(100% - var(--bleed) + 2px));\n  }\n\n  &[data-ending-style] {\n    box-shadow: 0 2px 10px rgb(0 0 0 / 0);\n    transition-duration: calc(var(--drawer-swipe-strength) * 400ms);\n  }\n}\n\n.Content {\n  width: 100%;\n  max-width: 32rem;\n  margin: 0 auto;\n  transition: opacity 300ms cubic-bezier(0.45, 1.005, 0, 1.005);\n\n  [data-nested-drawer-open] & {\n    opacity: 0;\n  }\n\n  [data-nested-drawer-open][data-nested-drawer-swiping] & {\n    opacity: 1;\n  }\n}\n\n.Handle {\n  width: 3rem;\n  height: 0.25rem;\n  margin: 0 auto 1rem;\n  border-radius: 9999px;\n  background-color: var(--color-gray-300);\n  transition: opacity 0.2s;\n\n  [data-nested-drawer-open] & {\n    opacity: 0;\n  }\n\n  [data-nested-drawer-open][data-nested-drawer-swiping] & {\n    opacity: 1;\n  }\n}\n\n.Title {\n  margin-top: 0;\n  margin-bottom: 0.25rem;\n  font-size: 1.125rem;\n  line-height: 1.75rem;\n  letter-spacing: -0.0025em;\n  font-weight: 700;\n  text-align: center;\n}\n\n.Description {\n  margin: 0 0 1.5rem;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-600);\n  text-align: center;\n}\n\n.Actions {\n  display: flex;\n  align-items: center;\n  justify-content: end;\n  gap: 1rem;\n}\n\n.ActionsLeft {\n  margin-right: auto;\n}\n\n.List {\n  margin: 0 0 1.5rem;\n  padding-left: 1.25rem;\n  color: var(--color-gray-700);\n}\n\n.Field {\n  display: grid;\n  gap: 0.375rem;\n  margin-bottom: 1rem;\n}\n\n.Label {\n  font-size: 0.925rem;\n  line-height: 1.25rem;\n  font-weight: 700;\n  color: var(--color-gray-700);\n}\n\n.Input,\n.Textarea {\n  box-sizing: border-box;\n  width: 100%;\n  border-radius: 0.375rem;\n  border: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-50);\n  color: var(--color-gray-900);\n  padding: 0.5rem 0.625rem;\n  font: inherit;\n}\n\n.Textarea {\n  resize: vertical;\n}\n\n.Input:focus-visible,\n.Textarea:focus-visible {\n  outline: 2px solid var(--color-blue);\n  outline-offset: -1px;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/demos/nested/css-modules/index.tsx",
    "content": "'use client';\nimport { Drawer } from '@base-ui/react/drawer';\nimport styles from './index.module.css';\n\nexport default function ExampleDrawerNested() {\n  return (\n    <Drawer.Root>\n      <Drawer.Trigger className={styles.Button}>Open drawer stack</Drawer.Trigger>\n      <Drawer.Portal>\n        <Drawer.Backdrop className={styles.Backdrop} />\n        <Drawer.Viewport className={styles.Viewport}>\n          <Drawer.Popup className={styles.Popup}>\n            <div className={styles.Handle} />\n            <Drawer.Content className={styles.Content}>\n              <Drawer.Title className={styles.Title}>Account</Drawer.Title>\n              <Drawer.Description className={styles.Description}>\n                Nested drawers can be styled to stack, while each drawer remains independently focus\n                managed.\n              </Drawer.Description>\n\n              <div className={styles.Actions}>\n                <div className={styles.ActionsLeft}>\n                  <Drawer.Root>\n                    <Drawer.Trigger className={styles.GhostButton}>\n                      Security settings\n                    </Drawer.Trigger>\n                    <Drawer.Portal>\n                      <Drawer.Viewport className={styles.Viewport}>\n                        <Drawer.Popup className={styles.Popup}>\n                          <div className={styles.Handle} />\n                          <Drawer.Content className={styles.Content}>\n                            <Drawer.Title className={styles.Title}>Security</Drawer.Title>\n                            <Drawer.Description className={styles.Description}>\n                              Review sign-in activity and update your security preferences.\n                            </Drawer.Description>\n\n                            <ul className={styles.List}>\n                              <li>Passkeys enabled</li>\n                              <li>2FA via authenticator app</li>\n                              <li>3 signed-in devices</li>\n                            </ul>\n\n                            <div className={styles.Actions}>\n                              <div className={styles.ActionsLeft}>\n                                <Drawer.Root>\n                                  <Drawer.Trigger className={styles.GhostButton}>\n                                    Advanced options\n                                  </Drawer.Trigger>\n                                  <Drawer.Portal>\n                                    <Drawer.Viewport className={styles.Viewport}>\n                                      <Drawer.Popup className={styles.Popup}>\n                                        <div className={styles.Handle} />\n                                        <Drawer.Content className={styles.Content}>\n                                          <Drawer.Title className={styles.Title}>\n                                            Advanced\n                                          </Drawer.Title>\n                                          <Drawer.Description className={styles.Description}>\n                                            This drawer is taller to demonstrate variable-height\n                                            stacking.\n                                          </Drawer.Description>\n\n                                          <div className={styles.Field}>\n                                            <label className={styles.Label} htmlFor=\"device-name\">\n                                              Device name\n                                            </label>\n                                            <input\n                                              id=\"device-name\"\n                                              className={styles.Input}\n                                              defaultValue=\"Personal laptop\"\n                                            />\n                                          </div>\n\n                                          <div className={styles.Field}>\n                                            <label className={styles.Label} htmlFor=\"notes\">\n                                              Notes\n                                            </label>\n                                            <textarea\n                                              id=\"notes\"\n                                              className={styles.Textarea}\n                                              defaultValue=\"Rotate recovery codes and revoke older sessions.\"\n                                              rows={3}\n                                            />\n                                          </div>\n\n                                          <div className={styles.Actions}>\n                                            <Drawer.Close className={styles.Button}>\n                                              Done\n                                            </Drawer.Close>\n                                          </div>\n                                        </Drawer.Content>\n                                      </Drawer.Popup>\n                                    </Drawer.Viewport>\n                                  </Drawer.Portal>\n                                </Drawer.Root>\n                              </div>\n\n                              <Drawer.Close className={styles.Button}>Close</Drawer.Close>\n                            </div>\n                          </Drawer.Content>\n                        </Drawer.Popup>\n                      </Drawer.Viewport>\n                    </Drawer.Portal>\n                  </Drawer.Root>\n                </div>\n\n                <Drawer.Close className={styles.Button}>Close</Drawer.Close>\n              </div>\n            </Drawer.Content>\n          </Drawer.Popup>\n        </Drawer.Viewport>\n      </Drawer.Portal>\n    </Drawer.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/demos/nested/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoDrawerNested = createDemoWithVariants(import.meta.url, { CssModules, Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/demos/nested/tailwind/index.tsx",
    "content": "'use client';\nimport { Drawer } from '@base-ui/react/drawer';\n\nexport default function ExampleDrawerNested() {\n  return (\n    <Drawer.Root>\n      <Drawer.Trigger className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n        Open drawer stack\n      </Drawer.Trigger>\n      <Drawer.Portal>\n        <Drawer.Backdrop className=\"[--backdrop-opacity:0.2] [--bleed:3rem] dark:[--backdrop-opacity:0.7] fixed inset-0 min-h-dvh bg-black opacity-[calc(var(--backdrop-opacity)*(1-var(--drawer-swipe-progress)))] transition-opacity duration-[450ms] ease-[cubic-bezier(0.32,0.72,0,1)] data-[swiping]:duration-0 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0 data-[ending-style]:duration-[calc(var(--drawer-swipe-strength)*400ms)] supports-[-webkit-touch-callout:none]:absolute\" />\n        <Drawer.Viewport className=\"fixed inset-0 flex items-end justify-center\">\n          <Drawer.Popup className={popupClassName}>\n            <div className={handleClassName} />\n            <Drawer.Content className={contentClassName}>\n              <Drawer.Title className=\"mb-1 text-lg font-bold text-center\">Account</Drawer.Title>\n              <Drawer.Description className=\"mb-6 text-base text-gray-600 text-center\">\n                Nested drawers can be styled to stack, while each drawer remains independently focus\n                managed.\n              </Drawer.Description>\n\n              <div className=\"flex items-center justify-end gap-4\">\n                <div className=\"mr-auto\">\n                  <Drawer.Root>\n                    <Drawer.Trigger className=\"text-base text-blue-800 rounded px-1.5 py-0.5 -m-0.5 hover:bg-blue-800/5 active:bg-blue-800/10 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800\">\n                      Security settings\n                    </Drawer.Trigger>\n                    <Drawer.Portal>\n                      <Drawer.Viewport className=\"fixed inset-0 flex items-end justify-center\">\n                        <Drawer.Popup className={popupClassName}>\n                          <div className={handleClassName} />\n                          <Drawer.Content className={contentClassName}>\n                            <Drawer.Title className=\"mb-1 text-lg font-bold text-center\">\n                              Security\n                            </Drawer.Title>\n                            <Drawer.Description className=\"mb-6 text-base text-gray-600 text-center\">\n                              Review sign-in activity and update your security preferences.\n                            </Drawer.Description>\n\n                            <ul className=\"mb-6 list-disc pl-5 text-gray-700\">\n                              <li>Passkeys enabled</li>\n                              <li>2FA via authenticator app</li>\n                              <li>3 signed-in devices</li>\n                            </ul>\n\n                            <div className=\"flex items-center justify-end gap-4\">\n                              <div className=\"mr-auto\">\n                                <Drawer.Root>\n                                  <Drawer.Trigger className=\"text-base text-blue-800 rounded px-1.5 py-0.5 -m-0.5 hover:bg-blue-800/5 active:bg-blue-800/10 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800\">\n                                    Advanced options\n                                  </Drawer.Trigger>\n                                  <Drawer.Portal>\n                                    <Drawer.Viewport className=\"fixed inset-0 flex items-end justify-center\">\n                                      <Drawer.Popup className={popupClassName}>\n                                        <div className={handleClassName} />\n                                        <Drawer.Content className={contentClassName}>\n                                          <Drawer.Title className=\"mb-1 text-lg font-bold text-center\">\n                                            Advanced\n                                          </Drawer.Title>\n                                          <Drawer.Description className=\"mb-6 text-base text-gray-600 text-center\">\n                                            This drawer is taller to demonstrate variable-height\n                                            stacking.\n                                          </Drawer.Description>\n\n                                          <div className=\"grid gap-1.5 mb-4\">\n                                            <label\n                                              className=\"text-sm font-bold text-gray-700\"\n                                              htmlFor=\"device-name-tw\"\n                                            >\n                                              Device name\n                                            </label>\n                                            <input\n                                              id=\"device-name-tw\"\n                                              className=\"w-full rounded-md border border-gray-200 bg-gray-50 px-2.5 py-2 text-gray-900 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 font-normal\"\n                                              defaultValue=\"Personal laptop\"\n                                            />\n                                          </div>\n\n                                          <div className=\"grid gap-1.5 mb-6\">\n                                            <label\n                                              className=\"text-sm font-bold text-gray-700\"\n                                              htmlFor=\"notes-tw\"\n                                            >\n                                              Notes\n                                            </label>\n                                            <textarea\n                                              id=\"notes-tw\"\n                                              className=\"w-full rounded-md border border-gray-200 bg-gray-50 px-2.5 py-2 text-gray-900 resize-y focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 font-normal\"\n                                              defaultValue=\"Rotate recovery codes and revoke older sessions.\"\n                                              rows={3}\n                                            />\n                                          </div>\n\n                                          <div className=\"flex justify-end\">\n                                            <Drawer.Close className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n                                              Done\n                                            </Drawer.Close>\n                                          </div>\n                                        </Drawer.Content>\n                                      </Drawer.Popup>\n                                    </Drawer.Viewport>\n                                  </Drawer.Portal>\n                                </Drawer.Root>\n                              </div>\n\n                              <Drawer.Close className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n                                Close\n                              </Drawer.Close>\n                            </div>\n                          </Drawer.Content>\n                        </Drawer.Popup>\n                      </Drawer.Viewport>\n                    </Drawer.Portal>\n                  </Drawer.Root>\n                </div>\n\n                <Drawer.Close className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n                  Close\n                </Drawer.Close>\n              </div>\n            </Drawer.Content>\n          </Drawer.Popup>\n        </Drawer.Viewport>\n      </Drawer.Portal>\n    </Drawer.Root>\n  );\n}\n\nconst popupClassName =\n  \"[--bleed:3rem] [--peek:1rem] [--stack-progress:clamp(0,var(--drawer-swipe-progress),1)] [--stack-step:0.05] [--stack-peek-offset:max(0px,calc((var(--nested-drawers)-var(--stack-progress))*var(--peek)))] [--scale-base:calc(max(0,1-(var(--nested-drawers)*var(--stack-step))))] [--scale:clamp(0,calc(var(--scale-base)+(var(--stack-step)*var(--stack-progress))),1)] [--shrink:calc(1-var(--scale))] [--height:max(0px,calc(var(--drawer-frontmost-height,var(--drawer-height))-var(--bleed)))] group/popup relative -mb-[3rem] w-full max-h-[calc(80vh+3rem)] [height:var(--drawer-height,auto)] rounded-t-2xl bg-gray-50 px-6 pt-4 pb-[calc(1.5rem+env(safe-area-inset-bottom,0px)+3rem)] text-gray-900 outline outline-1 outline-gray-200 overflow-y-auto overscroll-contain touch-auto shadow-[0_2px_10px_rgb(0_0_0/0.1)] data-[ending-style]:shadow-[0_2px_10px_rgb(0_0_0/0)] [transform-origin:50%_calc(100%-var(--bleed))] [transform:translateY(calc(var(--drawer-swipe-movement-y)-var(--stack-peek-offset)-(var(--shrink)*var(--height))))_scale(var(--scale))] after:absolute after:inset-0 after:rounded-[inherit] after:bg-transparent after:pointer-events-none after:content-[''] after:transition-[background-color] after:duration-[450ms] after:ease-[cubic-bezier(0.32,0.72,0,1)] data-[swiping]:select-none data-[swiping]:duration-0 data-[nested-drawer-swiping]:duration-0 data-[ending-style]:[transform:translateY(calc(100%-var(--bleed)+2px))] data-[starting-style]:[transform:translateY(calc(100%-var(--bleed)+2px))] data-[ending-style]:duration-[calc(var(--drawer-swipe-strength)*400ms)] data-[nested-drawer-open]:h-[calc(var(--height)+var(--bleed))] data-[nested-drawer-open]:overflow-hidden data-[nested-drawer-open]:after:bg-black/5 dark:outline-gray-300 [transition:transform_450ms_cubic-bezier(0.32,0.72,0,1),height_450ms_cubic-bezier(0.32,0.72,0,1),box-shadow_450ms_cubic-bezier(0.32,0.72,0,1)]\";\n\nconst contentClassName =\n  'mx-auto w-full max-w-[32rem] transition-opacity duration-[300ms] ease-[cubic-bezier(0.45,1.005,0,1.005)] group-data-[nested-drawer-open]/popup:opacity-0 group-data-[nested-drawer-swiping]/popup:opacity-100';\n\nconst handleClassName =\n  'mx-auto mb-4 h-1 w-12 rounded-full bg-gray-300 transition-opacity duration-[200ms] group-data-[nested-drawer-open]/popup:opacity-0 group-data-[nested-drawer-swiping]/popup:opacity-100';\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/demos/non-modal/css-modules/index.module.css",
    "content": ".Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Backdrop {\n  --backdrop-opacity: 0.2;\n  --bleed: 3rem;\n  position: fixed;\n  min-height: 100dvh;\n  inset: 0;\n  background-color: black;\n  opacity: calc(var(--backdrop-opacity) * (1 - var(--drawer-swipe-progress)));\n  transition-duration: 450ms;\n  transition-property: opacity;\n  transition-timing-function: cubic-bezier(0.32, 0.72, 0, 1);\n\n  /* iOS 26+: Ensure the backdrop covers the entire visible viewport. */\n  @supports (-webkit-touch-callout: none) {\n    position: absolute;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    --backdrop-opacity: 0.7;\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n  }\n\n  &[data-swiping] {\n    transition-duration: 0ms;\n  }\n\n  &[data-ending-style] {\n    transition-duration: calc(var(--drawer-swipe-strength) * 400ms);\n  }\n}\n\n.Viewport {\n  --viewport-padding: 0px;\n  position: fixed;\n  inset: 0;\n  display: flex;\n  justify-content: flex-end;\n  padding: var(--viewport-padding);\n  pointer-events: none;\n\n  @supports (-webkit-touch-callout: none) {\n    --viewport-padding: 0.625rem;\n  }\n}\n\n.Popup {\n  --bleed: 3rem;\n  box-sizing: border-box;\n  width: calc(20rem + var(--bleed));\n  max-width: calc(100vw - 3rem + var(--bleed));\n  height: 100%;\n  padding: 1.5rem;\n  padding-right: calc(1.5rem + var(--bleed));\n  margin-right: calc(-1 * var(--bleed));\n  outline: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-50);\n  color: var(--color-gray-900);\n  overflow-y: auto;\n  overscroll-behavior: contain;\n  touch-action: auto;\n  pointer-events: auto;\n  box-shadow:\n    0 -16px 48px rgb(0 0 0 / 0.12),\n    0 6px 18px rgb(0 0 0 / 0.06);\n  transition:\n    transform 450ms cubic-bezier(0.32, 0.72, 0, 1),\n    box-shadow 450ms cubic-bezier(0.32, 0.72, 0, 1);\n  will-change: transform;\n  transform: translateX(var(--drawer-swipe-movement-x));\n\n  &[data-swiping] {\n    user-select: none;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    transform: translateX(calc(100% - var(--bleed) + var(--viewport-padding) + 2px));\n    box-shadow:\n      0 -16px 48px rgb(0 0 0 / 0),\n      0 6px 18px rgb(0 0 0 / 0);\n  }\n\n  &[data-ending-style] {\n    transition-duration: calc(var(--drawer-swipe-strength) * 400ms);\n  }\n\n  @supports (-webkit-touch-callout: none) {\n    --bleed: 0px;\n    margin-right: 0;\n    border-radius: 10px;\n  }\n}\n\n.Content {\n  width: 100%;\n  max-width: 32rem;\n  margin: 0 auto;\n}\n\n.Title {\n  margin-top: -0.375rem;\n  margin-bottom: 0.25rem;\n  font-size: 1.125rem;\n  line-height: 1.75rem;\n  letter-spacing: -0.0025em;\n  font-weight: 700;\n}\n\n.Description {\n  margin: 0 0 1.5rem;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-600);\n}\n\n.Actions {\n  display: flex;\n  justify-content: flex-end;\n  gap: 1rem;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/demos/non-modal/css-modules/index.tsx",
    "content": "import { Drawer } from '@base-ui/react/drawer';\nimport styles from './index.module.css';\n\nexport default function ExampleDrawer() {\n  return (\n    <Drawer.Root swipeDirection=\"right\" modal={false} disablePointerDismissal>\n      <Drawer.Trigger className={styles.Button}>Open non-modal drawer</Drawer.Trigger>\n      <Drawer.Portal>\n        <Drawer.Viewport className={styles.Viewport}>\n          <Drawer.Popup className={styles.Popup}>\n            <Drawer.Content className={styles.Content}>\n              <Drawer.Title className={styles.Title}>Non-modal drawer</Drawer.Title>\n              <Drawer.Description className={styles.Description}>\n                This drawer does not trap focus and ignores outside clicks. Use the close button or\n                swipe to dismiss it.\n              </Drawer.Description>\n              <div className={styles.Actions}>\n                <Drawer.Close className={styles.Button}>Close</Drawer.Close>\n              </div>\n            </Drawer.Content>\n          </Drawer.Popup>\n        </Drawer.Viewport>\n      </Drawer.Portal>\n    </Drawer.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/demos/non-modal/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoDrawerNonModal = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/demos/non-modal/tailwind/index.tsx",
    "content": "import { Drawer } from '@base-ui/react/drawer';\n\nexport default function ExampleDrawer() {\n  return (\n    <Drawer.Root swipeDirection=\"right\" modal={false} disablePointerDismissal>\n      <Drawer.Trigger className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n        Open non-modal drawer\n      </Drawer.Trigger>\n      <Drawer.Portal>\n        <Drawer.Viewport className=\"[--viewport-padding:0px] supports-[-webkit-touch-callout:none]:[--viewport-padding:0.625rem] fixed inset-0 flex items-stretch justify-end p-[var(--viewport-padding)] pointer-events-none\">\n          <Drawer.Popup className=\"[--bleed:3rem] supports-[-webkit-touch-callout:none]:[--bleed:0px] pointer-events-auto h-full w-[calc(20rem+3rem)] max-w-[calc(100vw-3rem+3rem)] -mr-[3rem] bg-gray-50 p-6 pr-[calc(1.5rem+3rem)] text-gray-900 outline outline-1 outline-gray-200 overflow-y-auto overscroll-contain touch-auto shadow-[0_-16px_48px_rgb(0_0_0/0.12),0_6px_18px_rgb(0_0_0/0.06)] data-[starting-style]:shadow-[0_-16px_48px_rgb(0_0_0/0),0_6px_18px_rgb(0_0_0/0)] data-[ending-style]:shadow-[0_-16px_48px_rgb(0_0_0/0),0_6px_18px_rgb(0_0_0/0)] [transform:translateX(var(--drawer-swipe-movement-x))] transition-[transform,box-shadow] duration-[450ms] ease-[cubic-bezier(0.32,0.72,0,1)] data-[swiping]:select-none data-[ending-style]:[transform:translateX(calc(100%-var(--bleed)+var(--viewport-padding)+2px))] data-[starting-style]:[transform:translateX(calc(100%-var(--bleed)+var(--viewport-padding)+2px))] data-[ending-style]:duration-[calc(var(--drawer-swipe-strength)*400ms)] supports-[-webkit-touch-callout:none]:mr-0 supports-[-webkit-touch-callout:none]:w-[20rem] supports-[-webkit-touch-callout:none]:max-w-[calc(100vw-20px)] supports-[-webkit-touch-callout:none]:rounded-[10px] supports-[-webkit-touch-callout:none]:pr-6 dark:outline-gray-300\">\n            <Drawer.Content className=\"mx-auto w-full max-w-[32rem]\">\n              <Drawer.Title className=\"-mt-1.5 mb-1 text-lg font-bold\">\n                Non-modal drawer\n              </Drawer.Title>\n              <Drawer.Description className=\"mb-6 text-base text-gray-600\">\n                This drawer does not trap focus and ignores outside clicks. Use the close button or\n                swipe to dismiss it.\n              </Drawer.Description>\n              <div className=\"flex justify-end gap-4\">\n                <Drawer.Close className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n                  Close\n                </Drawer.Close>\n              </div>\n            </Drawer.Content>\n          </Drawer.Popup>\n        </Drawer.Viewport>\n      </Drawer.Portal>\n    </Drawer.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/demos/position/css-modules/index.module.css",
    "content": ".Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Backdrop {\n  --backdrop-opacity: 0.2;\n  --bleed: 3rem;\n  position: fixed;\n  min-height: 100dvh;\n  inset: 0;\n  background-color: black;\n  opacity: calc(var(--backdrop-opacity) * (1 - var(--drawer-swipe-progress)));\n  transition-duration: 450ms;\n  transition-property: opacity;\n  transition-timing-function: cubic-bezier(0.32, 0.72, 0, 1);\n\n  @media (prefers-color-scheme: dark) {\n    --backdrop-opacity: 0.7;\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n  }\n\n  &[data-swiping] {\n    transition-duration: 0ms;\n  }\n\n  &[data-ending-style] {\n    transition-duration: calc(var(--drawer-swipe-strength) * 400ms);\n  }\n}\n\n.Viewport {\n  position: fixed;\n  inset: 0;\n  display: flex;\n  align-items: flex-end;\n  justify-content: center;\n}\n\n.Popup {\n  --bleed: 3rem;\n  box-sizing: border-box;\n  width: 100%;\n  max-height: calc(80vh + var(--bleed));\n  margin-bottom: calc(-1 * var(--bleed));\n  padding: 1rem 1.5rem 1.5rem;\n  padding-bottom: calc(1.5rem + env(safe-area-inset-bottom, 0px) + var(--bleed));\n  border-radius: 1rem 1rem 0 0;\n  outline: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-50);\n  color: var(--color-gray-900);\n  overflow-y: auto;\n  overscroll-behavior: contain;\n  transition: transform 450ms cubic-bezier(0.32, 0.72, 0, 1);\n  will-change: transform;\n  transform: translateY(var(--drawer-swipe-movement-y));\n\n  &[data-swiping] {\n    user-select: none;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    transform: translateY(calc(100% - var(--bleed) + 2px));\n  }\n\n  &[data-ending-style] {\n    transition-duration: calc(var(--drawer-swipe-strength) * 400ms);\n  }\n}\n\n.Content {\n  width: 100%;\n  max-width: 32rem;\n  margin: 0 auto;\n}\n\n.Handle {\n  width: 3rem;\n  height: 0.25rem;\n  margin: 0 auto 1rem;\n  border-radius: 9999px;\n  background-color: var(--color-gray-300);\n}\n\n.Title {\n  margin-top: 0;\n  margin-bottom: 0.25rem;\n  font-size: 1.125rem;\n  line-height: 1.75rem;\n  letter-spacing: -0.0025em;\n  font-weight: 700;\n  text-align: center;\n}\n\n.Description {\n  margin: 0 0 1.5rem;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-600);\n  text-align: center;\n}\n\n.Actions {\n  display: flex;\n  justify-content: center;\n  gap: 1rem;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/demos/position/css-modules/index.tsx",
    "content": "import { Drawer } from '@base-ui/react/drawer';\nimport styles from './index.module.css';\n\nexport default function ExampleDrawer() {\n  return (\n    <Drawer.Root>\n      <Drawer.Trigger className={styles.Button}>Open bottom drawer</Drawer.Trigger>\n      <Drawer.Portal>\n        <Drawer.Backdrop className={styles.Backdrop} />\n        <Drawer.Viewport className={styles.Viewport}>\n          <Drawer.Popup className={styles.Popup}>\n            <div className={styles.Handle} />\n            <Drawer.Content className={styles.Content}>\n              <Drawer.Title className={styles.Title}>Notifications</Drawer.Title>\n              <Drawer.Description className={styles.Description}>\n                You are all caught up. Good job!\n              </Drawer.Description>\n              <div className={styles.Actions}>\n                <Drawer.Close className={styles.Button}>Close</Drawer.Close>\n              </div>\n            </Drawer.Content>\n          </Drawer.Popup>\n        </Drawer.Viewport>\n      </Drawer.Portal>\n    </Drawer.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/demos/position/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoDrawerPosition = createDemoWithVariants(import.meta.url, { CssModules, Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/demos/position/tailwind/index.tsx",
    "content": "import { Drawer } from '@base-ui/react/drawer';\n\nexport default function ExampleDrawer() {\n  return (\n    <Drawer.Root>\n      <Drawer.Trigger className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n        Open bottom drawer\n      </Drawer.Trigger>\n      <Drawer.Portal>\n        <Drawer.Backdrop className=\"[--backdrop-opacity:0.2] [--bleed:3rem] dark:[--backdrop-opacity:0.7] fixed inset-0 min-h-dvh bg-black opacity-[calc(var(--backdrop-opacity)*(1-var(--drawer-swipe-progress)))] transition-opacity duration-[450ms] ease-[cubic-bezier(0.32,0.72,0,1)] data-[swiping]:duration-0 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0 data-[ending-style]:duration-[calc(var(--drawer-swipe-strength)*400ms)] supports-[-webkit-touch-callout:none]:absolute\" />\n        <Drawer.Viewport className=\"fixed inset-0 flex items-end justify-center\">\n          <Drawer.Popup className=\"-mb-[3rem] w-full max-h-[calc(80vh+3rem)] rounded-t-2xl bg-gray-50 px-6 pb-[calc(1.5rem+env(safe-area-inset-bottom,0px)+3rem)] pt-4 text-gray-900 outline outline-1 outline-gray-200 overflow-y-auto overscroll-contain touch-auto [transform:translateY(var(--drawer-swipe-movement-y))] transition-transform duration-[450ms] ease-[cubic-bezier(0.32,0.72,0,1)] data-[swiping]:select-none data-[ending-style]:[transform:translateY(calc(100%-3rem+2px))] data-[starting-style]:[transform:translateY(calc(100%-3rem+2px))] data-[ending-style]:duration-[calc(var(--drawer-swipe-strength)*400ms)] dark:outline-gray-300\">\n            <div className=\"w-12 h-1 mx-auto mb-4 rounded-full bg-gray-300\" />\n            <Drawer.Content className=\"mx-auto w-full max-w-[32rem]\">\n              <Drawer.Title className=\"mb-1 text-lg font-bold text-center\">\n                Notifications\n              </Drawer.Title>\n              <Drawer.Description className=\"mb-6 text-base text-gray-600 text-center\">\n                You are all caught up. Good job!\n              </Drawer.Description>\n              <div className=\"flex justify-center gap-4\">\n                <Drawer.Close className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n                  Close\n                </Drawer.Close>\n              </div>\n            </Drawer.Content>\n          </Drawer.Popup>\n        </Drawer.Viewport>\n      </Drawer.Portal>\n    </Drawer.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/demos/snap-points/css-modules/index.module.css",
    "content": ".Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Backdrop {\n  --backdrop-opacity: 0.2;\n  --bleed: 3rem;\n  position: fixed;\n  min-height: 100dvh;\n  inset: 0;\n  background-color: black;\n  opacity: calc(var(--backdrop-opacity) * (1 - var(--drawer-swipe-progress)));\n  transition-duration: 450ms;\n  transition-property: opacity;\n  transition-timing-function: cubic-bezier(0.32, 0.72, 0, 1);\n\n  @media (prefers-color-scheme: dark) {\n    --backdrop-opacity: 0.7;\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n  }\n\n  &[data-swiping] {\n    transition-duration: 0ms;\n  }\n\n  &[data-ending-style] {\n    transition-duration: calc(var(--drawer-swipe-strength) * 400ms);\n  }\n}\n\n.Viewport {\n  position: fixed;\n  inset: 0;\n  display: flex;\n  align-items: flex-end;\n  justify-content: center;\n  touch-action: none;\n}\n\n.Content {\n  width: 100%;\n  max-width: 350px;\n  margin: 0 auto;\n}\n\n.Popup {\n  --bleed: 3rem;\n  box-sizing: border-box;\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  width: 100%;\n  max-height: calc(100dvh - var(--top-margin));\n  padding-bottom: max(0px, calc(var(--drawer-snap-point-offset) + var(--drawer-swipe-movement-y)));\n  border-radius: 1rem 1rem 0 0;\n  outline: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-50);\n  color: var(--color-gray-900);\n  overflow: visible;\n  touch-action: none;\n  box-shadow:\n    0 -16px 48px rgb(0 0 0 / 0.12),\n    0 6px 18px rgb(0 0 0 / 0.06);\n  transition:\n    transform 450ms cubic-bezier(0.32, 0.72, 0, 1),\n    box-shadow 450ms cubic-bezier(0.32, 0.72, 0, 1);\n  transform: translateY(calc(var(--drawer-snap-point-offset) + var(--drawer-swipe-movement-y)));\n  will-change: transform;\n\n  &::after {\n    content: '';\n    position: absolute;\n    inset-inline: 0;\n    top: 100%;\n    height: var(--bleed);\n    background-color: inherit;\n    pointer-events: none;\n  }\n\n  &[data-swiping] {\n    user-select: none;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    transform: translateY(calc(100% + 2px));\n    padding-bottom: 0;\n    box-shadow:\n      0 -16px 48px rgb(0 0 0 / 0),\n      0 6px 18px rgb(0 0 0 / 0);\n  }\n\n  &[data-ending-style] {\n    transition-duration: calc(var(--drawer-swipe-strength) * 400ms);\n  }\n}\n\n.DragArea {\n  flex-shrink: 0;\n  padding: 0.875rem 1.5rem 0.75rem;\n  border-bottom: 1px solid var(--color-gray-200);\n  touch-action: none;\n\n  @media (prefers-color-scheme: dark) {\n    border-bottom-color: var(--color-gray-300);\n  }\n}\n\n.Scroll {\n  flex: 1 1 auto;\n  min-height: 0;\n  overflow-y: auto;\n  overscroll-behavior: contain;\n  touch-action: auto;\n  padding: 1rem 1.5rem calc(1.5rem + env(safe-area-inset-bottom, 0px));\n}\n\n.Handle {\n  width: 3rem;\n  height: 0.25rem;\n  margin: 0 auto 0.625rem;\n  flex-shrink: 0;\n  border-radius: 9999px;\n  background-color: var(--color-gray-300);\n}\n\n.Title {\n  margin: 0;\n  font-size: 1.125rem;\n  line-height: 1.75rem;\n  letter-spacing: -0.0025em;\n  font-weight: 700;\n  text-align: center;\n  cursor: default;\n}\n\n.Description {\n  margin: 0 0 1.5rem;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-600);\n  text-align: center;\n}\n\n.Meta {\n  margin: 0 0 1.5rem;\n  font-size: 0.9375rem;\n  line-height: 1.5rem;\n  color: var(--color-red);\n  text-align: center;\n}\n\n.Cards {\n  display: grid;\n  gap: 0.75rem;\n  margin: 0 0 1.5rem;\n}\n\n.Card {\n  height: 3rem;\n  border-radius: 0.75rem;\n  border: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-100);\n}\n\n.Actions {\n  display: flex;\n  align-items: center;\n  justify-content: end;\n  gap: 1rem;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/demos/snap-points/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Drawer } from '@base-ui/react/drawer';\nimport styles from './index.module.css';\n\nconst TOP_MARGIN_REM = 1;\nconst VISIBLE_SNAP_POINTS_REM = [30];\n\nfunction toViewportSnapPoint(heightRem: number) {\n  return `${heightRem + TOP_MARGIN_REM}rem`;\n}\n\nconst snapPoints = [...VISIBLE_SNAP_POINTS_REM.map(toViewportSnapPoint), 1];\n\nexport default function ExampleDrawerSnapPoints() {\n  return (\n    <Drawer.Root snapPoints={snapPoints}>\n      <Drawer.Trigger className={styles.Button}>Open snap drawer</Drawer.Trigger>\n      <Drawer.Portal>\n        <Drawer.Backdrop className={styles.Backdrop} />\n        <Drawer.Viewport className={styles.Viewport}>\n          <Drawer.Popup\n            className={styles.Popup}\n            style={{ '--top-margin': `${TOP_MARGIN_REM}rem` } as React.CSSProperties}\n          >\n            <div className={styles.DragArea}>\n              <div className={styles.Handle} />\n              <Drawer.Title className={styles.Title}>Snap points</Drawer.Title>\n            </div>\n            <Drawer.Content className={styles.Scroll}>\n              <div className={styles.Content}>\n                <Drawer.Description className={styles.Description}>\n                  Drag the sheet to snap between a compact peek and a near full-height view.\n                </Drawer.Description>\n                <div className={styles.Cards} aria-hidden>\n                  {Array.from({ length: 20 }, (_, index) => (\n                    <div className={styles.Card} key={index} />\n                  ))}\n                </div>\n                <div className={styles.Actions}>\n                  <Drawer.Close className={styles.Button}>Close</Drawer.Close>\n                </div>\n              </div>\n            </Drawer.Content>\n          </Drawer.Popup>\n        </Drawer.Viewport>\n      </Drawer.Portal>\n    </Drawer.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/demos/snap-points/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoDrawerSnapPoints = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/demos/snap-points/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Drawer } from '@base-ui/react/drawer';\n\nconst TOP_MARGIN_REM = 1;\nconst VISIBLE_SNAP_POINTS_REM = [30];\n\nfunction toViewportSnapPoint(heightRem: number) {\n  return `${heightRem + TOP_MARGIN_REM}rem`;\n}\n\nconst snapPoints = [...VISIBLE_SNAP_POINTS_REM.map(toViewportSnapPoint), 1];\n\nexport default function ExampleDrawerSnapPoints() {\n  return (\n    <Drawer.Root snapPoints={snapPoints}>\n      <Drawer.Trigger className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n        Open snap drawer\n      </Drawer.Trigger>\n      <Drawer.Portal>\n        <Drawer.Backdrop className=\"[--backdrop-opacity:0.2] [--bleed:3rem] dark:[--backdrop-opacity:0.7] fixed inset-0 min-h-dvh bg-black opacity-[calc(var(--backdrop-opacity)*(1-var(--drawer-swipe-progress)))] transition-opacity duration-[450ms] ease-[cubic-bezier(0.32,0.72,0,1)] data-[swiping]:duration-0 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0 data-[ending-style]:duration-[calc(var(--drawer-swipe-strength)*400ms)] supports-[-webkit-touch-callout:none]:absolute\" />\n        <Drawer.Viewport className=\"fixed inset-0 flex items-end justify-center touch-none\">\n          <Drawer.Popup\n            className=\"relative flex w-full max-h-[calc(100dvh-var(--top-margin))] min-h-0 flex-col overflow-visible rounded-t-2xl bg-gray-50 text-gray-900 outline outline-1 outline-gray-200 touch-none shadow-[0_-16px_48px_rgb(0_0_0/0.12),0_6px_18px_rgb(0_0_0/0.06)] [--bleed:3rem] [padding-bottom:max(0px,calc(var(--drawer-snap-point-offset)+var(--drawer-swipe-movement-y)))] [transform:translateY(calc(var(--drawer-snap-point-offset)+var(--drawer-swipe-movement-y)))] transition-[transform,box-shadow] duration-[450ms] ease-[cubic-bezier(0.32,0.72,0,1)] after:pointer-events-none after:absolute after:inset-x-0 after:top-full after:h-[var(--bleed)] after:bg-gray-50 after:content-[''] data-[swiping]:select-none data-[ending-style]:[transform:translateY(calc(100%+2px))] data-[starting-style]:[transform:translateY(calc(100%+2px))] data-[starting-style]:[padding-bottom:0] data-[ending-style]:[padding-bottom:0] data-[starting-style]:shadow-[0_-16px_48px_rgb(0_0_0/0),0_6px_18px_rgb(0_0_0/0)] data-[ending-style]:shadow-[0_-16px_48px_rgb(0_0_0/0),0_6px_18px_rgb(0_0_0/0)] data-[ending-style]:duration-[calc(var(--drawer-swipe-strength)*400ms)] dark:outline-gray-300\"\n            style={{ '--top-margin': `${TOP_MARGIN_REM}rem` } as React.CSSProperties}\n          >\n            <div className=\"shrink-0 border-b border-gray-200 px-6 pt-3.5 pb-3 touch-none dark:border-gray-300\">\n              <div className=\"mx-auto h-1 w-12 rounded-full bg-gray-300\" />\n              <Drawer.Title className=\"mt-2.5 cursor-default text-center text-lg font-bold\">\n                Snap points\n              </Drawer.Title>\n            </div>\n            <Drawer.Content className=\"min-h-0 flex-1 overflow-y-auto overscroll-contain touch-auto px-6 pt-4 pb-[calc(1.5rem+env(safe-area-inset-bottom,0px))]\">\n              <div className=\"mx-auto w-full max-w-[350px]\">\n                <Drawer.Description className=\"mb-4 text-base text-gray-600 text-center\">\n                  Drag the sheet to snap between a compact peek and a near full-height view.\n                </Drawer.Description>\n                <div className=\"grid gap-3 mb-6\" aria-hidden>\n                  {Array.from({ length: 20 }, (_, index) => (\n                    <div\n                      key={index}\n                      className=\"h-12 rounded-xl border border-gray-200 bg-gray-100\"\n                    />\n                  ))}\n                </div>\n                <div className=\"flex items-center justify-end gap-4\">\n                  <Drawer.Close className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n                    Close\n                  </Drawer.Close>\n                </div>\n              </div>\n            </Drawer.Content>\n          </Drawer.Popup>\n        </Drawer.Viewport>\n      </Drawer.Portal>\n    </Drawer.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/demos/swipe-area/css-modules/index.module.css",
    "content": ".Root {\n  position: relative;\n  width: 100%;\n  min-height: 320px;\n  overflow: hidden;\n  border: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-50);\n  color: var(--color-gray-900);\n}\n\n.Center {\n  min-height: 320px;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  padding: 1rem;\n}\n\n.Instructions {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  gap: 0.75rem;\n  text-align: center;\n}\n\n.Hint {\n  margin: 0;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-600);\n  text-align: center;\n  padding-right: 3rem;\n}\n\n.SwipeArea {\n  position: absolute;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  width: 2.5rem;\n  z-index: 1;\n  box-sizing: border-box;\n  border-left: 2px dashed var(--color-blue);\n  background-color: color-mix(in oklch, var(--color-blue), transparent 90%);\n}\n\n.SwipeLabel {\n  position: absolute;\n  right: 0;\n  top: 50%;\n  margin-right: 0.5rem;\n  transform: translateY(-50%) rotate(-90deg);\n  transform-origin: center;\n  z-index: 0;\n  font-size: 0.75rem;\n  font-weight: 700;\n  letter-spacing: 0.12em;\n  text-transform: uppercase;\n  color: var(--color-blue);\n  white-space: nowrap;\n  pointer-events: none;\n}\n\n.Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Backdrop {\n  --backdrop-opacity: 0.2;\n  --bleed: 3rem;\n  position: absolute;\n  min-height: 100dvh;\n  inset: 0;\n  background-color: black;\n  opacity: calc(var(--backdrop-opacity) * (1 - var(--drawer-swipe-progress)));\n  transition-duration: 450ms;\n  transition-property: opacity;\n  transition-timing-function: cubic-bezier(0.32, 0.72, 0, 1);\n\n  @media (prefers-color-scheme: dark) {\n    --backdrop-opacity: 0.7;\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n  }\n\n  &[data-swiping] {\n    transition-duration: 0ms;\n  }\n\n  &[data-ending-style] {\n    transition-duration: calc(var(--drawer-swipe-strength) * 400ms);\n  }\n}\n\n.Viewport {\n  --viewport-padding: 0px;\n  position: absolute;\n  inset: 0;\n  display: flex;\n  justify-content: flex-end;\n  padding: var(--viewport-padding);\n  z-index: 2;\n\n  @supports (-webkit-touch-callout: none) {\n    --viewport-padding: 0.625rem;\n  }\n}\n\n.Popup {\n  --bleed: 3rem;\n  box-sizing: border-box;\n  width: calc(20rem + var(--bleed));\n  max-width: calc(100vw - 3rem + var(--bleed));\n  height: 100%;\n  padding: 1.5rem;\n  padding-right: calc(1.5rem + var(--bleed));\n  margin-right: calc(-1 * var(--bleed));\n  outline: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-50);\n  color: var(--color-gray-900);\n  overflow-y: auto;\n  touch-action: auto;\n  transition: transform 450ms cubic-bezier(0.32, 0.72, 0, 1);\n  will-change: transform;\n  transform: translateX(var(--drawer-swipe-movement-x));\n\n  &[data-swiping] {\n    user-select: none;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    transform: translateX(calc(100% - var(--bleed) + var(--viewport-padding) + 2px));\n  }\n\n  &[data-ending-style] {\n    transition-duration: calc(var(--drawer-swipe-strength) * 400ms);\n  }\n\n  @supports (-webkit-touch-callout: none) {\n    --bleed: 0px;\n    margin-right: 0;\n    border-radius: 10px;\n  }\n}\n\n.Content {\n  width: 100%;\n  max-width: 32rem;\n  margin: 0 auto;\n}\n\n.Title {\n  margin-top: -0.375rem;\n  margin-bottom: 0.25rem;\n  font-size: 1.125rem;\n  line-height: 1.75rem;\n  letter-spacing: -0.0025em;\n  font-weight: 700;\n}\n\n.Description {\n  margin: 0 0 1.5rem;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-600);\n}\n\n.Actions {\n  display: flex;\n  justify-content: flex-end;\n  gap: 1rem;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/demos/swipe-area/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Drawer } from '@base-ui/react/drawer';\nimport styles from './index.module.css';\n\nexport default function ExampleDrawerSwipeArea() {\n  const [portalContainer, setPortalContainer] = React.useState<HTMLDivElement | null>(null);\n\n  return (\n    <div className={styles.Root} ref={setPortalContainer}>\n      <Drawer.Root swipeDirection=\"right\" modal={false}>\n        <Drawer.SwipeArea className={styles.SwipeArea}>\n          <span className={styles.SwipeLabel}>Swipe here</span>\n        </Drawer.SwipeArea>\n        <div className={styles.Center}>\n          <div className={styles.Instructions}>\n            <p className={styles.Hint}>Swipe from the right edge to open the drawer.</p>\n          </div>\n        </div>\n        <Drawer.Portal container={portalContainer}>\n          <Drawer.Backdrop className={styles.Backdrop} />\n          <Drawer.Viewport className={styles.Viewport}>\n            <Drawer.Popup className={styles.Popup}>\n              <Drawer.Content className={styles.Content}>\n                <Drawer.Title className={styles.Title}>Library</Drawer.Title>\n                <Drawer.Description className={styles.Description}>\n                  Swipe from the edge whenever you want to jump back into your playlists.\n                </Drawer.Description>\n                <div className={styles.Actions}>\n                  <Drawer.Close className={styles.Button}>Close</Drawer.Close>\n                </div>\n              </Drawer.Content>\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/demos/swipe-area/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoDrawerSwipeArea = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/demos/swipe-area/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Drawer } from '@base-ui/react/drawer';\n\nexport default function ExampleDrawerSwipeArea() {\n  const [portalContainer, setPortalContainer] = React.useState<HTMLDivElement | null>(null);\n\n  return (\n    <div\n      ref={setPortalContainer}\n      className=\"relative min-h-[320px] w-full overflow-hidden border border-gray-200 bg-gray-50 text-gray-900\"\n    >\n      <Drawer.Root swipeDirection=\"right\" modal={false}>\n        <Drawer.SwipeArea className=\"absolute inset-y-0 right-0 z-10 box-border w-10 border-l-2 border-dashed border-blue-800 bg-blue-800/10\">\n          <span className=\"pointer-events-none absolute right-0 top-1/2 mr-2 -translate-y-1/2 -rotate-90 origin-center whitespace-nowrap text-xs font-bold tracking-[0.12em] text-blue-800 uppercase\">\n            Swipe here\n          </span>\n        </Drawer.SwipeArea>\n        <div className=\"flex min-h-[320px] flex-col items-center justify-center gap-3 px-4 text-center\">\n          <p className=\"text-base text-gray-600 text-center pr-12\">\n            Swipe from the right edge to open the drawer.\n          </p>\n        </div>\n        <Drawer.Portal container={portalContainer}>\n          <Drawer.Backdrop className=\"[--backdrop-opacity:0.2] [--bleed:3rem] dark:[--backdrop-opacity:0.7] absolute inset-0 min-h-dvh bg-black opacity-[calc(var(--backdrop-opacity)*(1-var(--drawer-swipe-progress)))] transition-opacity duration-[450ms] ease-[cubic-bezier(0.32,0.72,0,1)] data-[swiping]:duration-0 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0 data-[ending-style]:duration-[calc(var(--drawer-swipe-strength)*400ms)] supports-[-webkit-touch-callout:none]:absolute\" />\n          <Drawer.Viewport className=\"[--viewport-padding:0px] supports-[-webkit-touch-callout:none]:[--viewport-padding:0.625rem] absolute inset-0 z-20 flex items-stretch justify-end p-[var(--viewport-padding)]\">\n            <Drawer.Popup className=\"[--bleed:3rem] supports-[-webkit-touch-callout:none]:[--bleed:0px] h-full w-[calc(20rem+3rem)] max-w-[calc(100vw-3rem+3rem)] -mr-[3rem] bg-gray-50 p-6 pr-[calc(1.5rem+3rem)] text-gray-900 outline outline-1 outline-gray-200 overflow-y-auto touch-auto [transform:translateX(var(--drawer-swipe-movement-x))] transition-transform duration-[450ms] ease-[cubic-bezier(0.32,0.72,0,1)] data-[swiping]:select-none data-[ending-style]:[transform:translateX(calc(100%-var(--bleed)+var(--viewport-padding)+2px))] data-[starting-style]:[transform:translateX(calc(100%-var(--bleed)+var(--viewport-padding)+2px))] data-[ending-style]:duration-[calc(var(--drawer-swipe-strength)*400ms)] supports-[-webkit-touch-callout:none]:mr-0 supports-[-webkit-touch-callout:none]:w-[20rem] supports-[-webkit-touch-callout:none]:max-w-[calc(100vw-20px)] supports-[-webkit-touch-callout:none]:rounded-[10px] supports-[-webkit-touch-callout:none]:pr-6 dark:outline-gray-300\">\n              <Drawer.Content className=\"mx-auto w-full max-w-[32rem]\">\n                <Drawer.Title className=\"-mt-1.5 mb-1 text-lg font-bold\">Library</Drawer.Title>\n                <Drawer.Description className=\"mb-6 text-base text-gray-600\">\n                  Swipe from the edge whenever you want to jump back into your playlists.\n                </Drawer.Description>\n                <div className=\"flex justify-end gap-4\">\n                  <Drawer.Close className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n                    Close\n                  </Drawer.Close>\n                </div>\n              </Drawer.Content>\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/demos/uncontained/css-modules/index.module.css",
    "content": ".Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Backdrop {\n  --backdrop-opacity: 0.4;\n  position: fixed;\n  min-height: 100dvh;\n  inset: 0;\n  background-color: black;\n  opacity: calc(var(--backdrop-opacity) * (1 - var(--drawer-swipe-progress)));\n  transition-duration: 450ms;\n  transition-property: opacity;\n  transition-timing-function: cubic-bezier(0.32, 0.72, 0, 1);\n\n  /* iOS 26+: Ensure the backdrop covers the entire visible viewport. */\n  @supports (-webkit-touch-callout: none) {\n    position: absolute;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    --backdrop-opacity: 0.7;\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n  }\n\n  &[data-swiping] {\n    transition-duration: 0ms;\n  }\n\n  &[data-ending-style] {\n    transition-duration: calc(var(--drawer-swipe-strength) * 400ms);\n  }\n}\n\n.Viewport {\n  position: fixed;\n  inset: 0;\n  display: flex;\n  align-items: flex-end;\n  justify-content: center;\n}\n\n.Popup {\n  box-sizing: border-box;\n  width: 100%;\n  max-width: 28rem;\n  display: flex;\n  flex-direction: column;\n  gap: 0.75rem;\n  padding: 0 1rem calc(1rem + env(safe-area-inset-bottom, 0px));\n  outline: 0;\n  pointer-events: none;\n  transform: translateY(var(--drawer-swipe-movement-y));\n  transition: transform 450ms cubic-bezier(0.32, 0.72, 0, 1);\n  will-change: transform;\n\n  &[data-swiping] {\n    user-select: none;\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    transform: translateY(calc(100% + 1rem + 2px));\n  }\n\n  &[data-ending-style] {\n    transition-duration: calc(var(--drawer-swipe-strength) * 400ms);\n  }\n}\n\n.Surface {\n  pointer-events: auto;\n  border-radius: 1rem;\n  outline: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-50);\n  color: var(--color-gray-900);\n  overflow: hidden;\n\n  @media (prefers-color-scheme: dark) {\n    outline-color: var(--color-gray-300);\n  }\n}\n\n.Actions {\n  list-style: none;\n  margin: 0;\n  padding: 0;\n}\n\n.Action:not(:first-child) {\n  border-top: 1px solid var(--color-gray-200);\n}\n\n.ActionButton {\n  box-sizing: border-box;\n  width: 100%;\n  margin: 0;\n  padding: 1rem 1.25rem;\n  border: 0;\n  background-color: transparent;\n  font-family: inherit;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  text-align: center;\n  color: inherit;\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:focus-visible {\n    outline: 0;\n    background-color: var(--color-gray-100);\n  }\n}\n\n.DangerSurface {\n  pointer-events: auto;\n  border-radius: 1rem;\n  outline: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-50);\n  overflow: hidden;\n\n  @media (prefers-color-scheme: dark) {\n    outline-color: var(--color-gray-300);\n  }\n}\n\n.DangerButton {\n  box-sizing: border-box;\n  width: 100%;\n  margin: 0;\n  padding: 1rem 1.25rem;\n  border: 0;\n  background-color: transparent;\n  font-family: inherit;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  text-align: center;\n  color: var(--color-red);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:focus-visible {\n    outline: 0;\n    background-color: var(--color-gray-100);\n  }\n}\n\n.VisuallyHidden {\n  position: absolute;\n  width: 1px;\n  height: 1px;\n  margin: -1px;\n  padding: 0;\n  overflow: hidden;\n  clip: rect(0 0 0 0);\n  white-space: nowrap;\n  border: 0;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/demos/uncontained/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Drawer } from '@base-ui/react/drawer';\nimport styles from './index.module.css';\n\nconst ACTIONS = ['Unfollow', 'Mute', 'Add to Favourites', 'Add to Close Friends', 'Restrict'];\n\nexport default function ExampleDrawerUncontained() {\n  const [open, setOpen] = React.useState(false);\n\n  return (\n    <Drawer.Root open={open} onOpenChange={setOpen}>\n      <Drawer.Trigger className={styles.Button}>Open action sheet</Drawer.Trigger>\n      <Drawer.Portal>\n        <Drawer.Backdrop className={styles.Backdrop} />\n        <Drawer.Viewport className={styles.Viewport}>\n          <Drawer.Popup className={styles.Popup}>\n            <Drawer.Content className={styles.Surface}>\n              <Drawer.Title className={styles.VisuallyHidden}>Profile actions</Drawer.Title>\n              <Drawer.Description className={styles.VisuallyHidden}>\n                Choose an action for this user.\n              </Drawer.Description>\n\n              <ul className={styles.Actions} aria-label=\"Profile actions\">\n                {ACTIONS.map((action, index) => (\n                  <li key={action} className={styles.Action}>\n                    {index === 0 && (\n                      <Drawer.Close className={styles.VisuallyHidden}>\n                        Close action sheet\n                      </Drawer.Close>\n                    )}\n                    <button\n                      type=\"button\"\n                      className={styles.ActionButton}\n                      onClick={() => setOpen(false)}\n                    >\n                      {action}\n                    </button>\n                  </li>\n                ))}\n              </ul>\n            </Drawer.Content>\n            <div className={styles.DangerSurface}>\n              <button type=\"button\" className={styles.DangerButton} onClick={() => setOpen(false)}>\n                Block User\n              </button>\n            </div>\n          </Drawer.Popup>\n        </Drawer.Viewport>\n      </Drawer.Portal>\n    </Drawer.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/demos/uncontained/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoDrawerUncontained = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/demos/uncontained/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Drawer } from '@base-ui/react/drawer';\n\nconst ACTIONS = ['Unfollow', 'Mute', 'Add to Favourites', 'Add to Close Friends', 'Restrict'];\n\nexport default function ExampleDrawerUncontained() {\n  const [open, setOpen] = React.useState(false);\n\n  return (\n    <Drawer.Root open={open} onOpenChange={setOpen}>\n      <Drawer.Trigger className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800\">\n        Open action sheet\n      </Drawer.Trigger>\n      <Drawer.Portal>\n        <Drawer.Backdrop className=\"[--backdrop-opacity:0.4] fixed inset-0 min-h-dvh bg-black opacity-[calc(var(--backdrop-opacity)*(1-var(--drawer-swipe-progress)))] transition-opacity duration-[450ms] ease-[cubic-bezier(0.32,0.72,0,1)] data-[swiping]:duration-0 data-[starting-style]:opacity-0 data-[ending-style]:opacity-0 data-[ending-style]:duration-[calc(var(--drawer-swipe-strength)*400ms)] supports-[-webkit-touch-callout:none]:absolute dark:[--backdrop-opacity:0.7]\" />\n        <Drawer.Viewport className=\"fixed inset-0 flex items-end justify-center\">\n          <Drawer.Popup className=\"box-border pointer-events-none flex w-full max-w-[28rem] flex-col gap-3 px-4 pb-[calc(1rem+env(safe-area-inset-bottom,0px))] outline-none focus-visible:outline-none [transform:translateY(var(--drawer-swipe-movement-y))] transition-transform duration-[450ms] ease-[cubic-bezier(0.32,0.72,0,1)] data-[swiping]:select-none data-[starting-style]:[transform:translateY(calc(100%+1rem+2px))] data-[ending-style]:[transform:translateY(calc(100%+1rem+2px))] data-[ending-style]:duration-[calc(var(--drawer-swipe-strength)*400ms)]\">\n            <Drawer.Content className=\"pointer-events-auto overflow-hidden rounded-2xl bg-gray-50 text-gray-900 outline outline-1 outline-gray-200 dark:outline-gray-300\">\n              <Drawer.Title className=\"sr-only\">Profile actions</Drawer.Title>\n              <Drawer.Description className=\"sr-only\">\n                Choose an action for this user.\n              </Drawer.Description>\n\n              <ul\n                className=\"m-0 list-none divide-y divide-gray-200 p-0\"\n                aria-label=\"Profile actions\"\n              >\n                {ACTIONS.map((action, index) => (\n                  <li key={action}>\n                    {index === 0 && (\n                      <Drawer.Close className=\"sr-only\">Close action sheet</Drawer.Close>\n                    )}\n                    <button\n                      type=\"button\"\n                      className=\"block w-full border-0 bg-transparent px-5 py-4 text-center text-base text-gray-900 select-none hover:bg-gray-100 focus-visible:bg-gray-100 focus-visible:outline-none\"\n                      onClick={() => setOpen(false)}\n                    >\n                      {action}\n                    </button>\n                  </li>\n                ))}\n              </ul>\n            </Drawer.Content>\n            <div className=\"pointer-events-auto overflow-hidden rounded-2xl bg-gray-50 outline outline-1 outline-gray-200 dark:outline-gray-300\">\n              <button\n                type=\"button\"\n                className=\"block w-full border-0 bg-transparent px-5 py-4 text-center text-base text-red-700 select-none hover:bg-gray-100 focus-visible:bg-gray-100 focus-visible:outline-none\"\n                onClick={() => setOpen(false)}\n              >\n                Block User\n              </button>\n            </div>\n          </Drawer.Popup>\n        </Drawer.Viewport>\n      </Drawer.Portal>\n    </Drawer.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/drawer/page.mdx",
    "content": "# Drawer\n\n<Subtitle>A panel that slides in from the edge of the screen.</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"A high-quality, unstyled React drawer component with swipe-to-dismiss gestures.\"\n/>\n\nimport { DemoDrawerHero } from './demos/hero';\n\n<DemoDrawerHero />\n\n## Usage guidelines\n\n- **Drawer extends [Dialog](/react/components/dialog):** It adds gesture support, snap points, and indent effects. If you don't need these, use Dialog instead. A panel that slides in from the edge of the screen and doesn't need gesture support is a positioned Dialog.\n\n## Anatomy\n\nImport the component and assemble its parts:\n\n```jsx title=\"Anatomy\"\nimport { Drawer } from '@base-ui/react/drawer';\n\n<Drawer.Provider>\n  <Drawer.IndentBackground />\n  <Drawer.Indent>\n    <Drawer.Root>\n      <Drawer.Trigger />\n      <Drawer.SwipeArea />\n      <Drawer.Portal>\n        <Drawer.Backdrop />\n        <Drawer.Viewport>\n          <Drawer.Popup>\n            <Drawer.Content>\n              <Drawer.Title />\n              <Drawer.Description />\n              <Drawer.Close />\n            </Drawer.Content>\n          </Drawer.Popup>\n        </Drawer.Viewport>\n      </Drawer.Portal>\n    </Drawer.Root>\n  </Drawer.Indent>\n</Drawer.Provider>;\n```\n\nDrawer supports swipe gestures to dismiss. Set `swipeDirection` to control which direction dismisses the drawer. `<Drawer.Content>` allows text selection of its children without swipe interference when using a mouse pointer. Add `data-base-ui-swipe-ignore` to a descendant when you need to opt that element out of swipe dismissal for all input types.\n\n## Examples\n\n### State\n\nBy default, Drawer is an uncontrolled component that manages its own state.\n\n```tsx title=\"Uncontrolled drawer\"\n<Drawer.Root>\n  <Drawer.Trigger>Open</Drawer.Trigger>\n  <Drawer.Portal>\n    <Drawer.Viewport>\n      <Drawer.Popup>\n        <Drawer.Content>\n          <Drawer.Title>Example drawer</Drawer.Title>\n          <Drawer.Close>Close</Drawer.Close>\n        </Drawer.Content>\n      </Drawer.Popup>\n    </Drawer.Viewport>\n  </Drawer.Portal>\n</Drawer.Root>\n```\n\nUse `open` and `onOpenChange` props if you need to access or control the state of the drawer.\n\n```tsx title=\"Controlled drawer\"\nconst [open, setOpen] = React.useState(false);\nreturn (\n  <Drawer.Root open={open} onOpenChange={setOpen}>\n    <Drawer.Trigger>Open</Drawer.Trigger>\n    <Drawer.Portal>\n      <Drawer.Viewport>\n        <Drawer.Popup>\n          <Drawer.Content>\n            <Drawer.Title>Example drawer</Drawer.Title>\n            <Drawer.Close>Close</Drawer.Close>\n          </Drawer.Content>\n        </Drawer.Popup>\n      </Drawer.Viewport>\n    </Drawer.Portal>\n  </Drawer.Root>\n);\n```\n\n### Position\n\nPositioning is handled by your styles. `swipeDirection` defaults to `\"down\"` for bottom sheets. Use `\"up\"`, `\"left\"`, or `\"right\"` for other drawer positions.\n\n```tsx title=\"Swipe directions\"\n<Drawer.Root swipeDirection=\"right\">\n```\n\nimport { DemoDrawerPosition } from './demos/position';\n\n<DemoDrawerPosition />\n\n### Nested drawers\n\nUse the `[data-nested-drawer-open]` selector and the `--nested-drawers` CSS variable to style drawers when a nested drawer is open.\n\nThis demo stacks nested drawers using a constant peek so the frontmost drawer stays anchored to the bottom while the ones behind it are scaled down and lifted. It also uses the `--drawer-height` and `--drawer-frontmost-height` CSS variables to handle varying drawer heights.\n\nimport { DemoDrawerNested } from './demos/nested';\n\n<DemoDrawerNested compact />\n\n### Snap points\n\nUse `snapPoints` to snap a bottom sheet drawer to preset heights. Numbers between 0 and 1 represent fractions of the viewport height, and numbers greater than 1 are treated as pixel values. String values support `px` and `rem` units (for example, `'148px'` or `'30rem'`).\n\n```tsx title=\"Snap points\"\nconst snapPoints = ['148px', 1];\nconst [snapPoint, setSnapPoint] = React.useState<Drawer.Root.SnapPoint | null>(snapPoints[0]);\n\n<Drawer.Root snapPoints={snapPoints} snapPoint={snapPoint} onSnapPointChange={setSnapPoint}>\n  {/* ... */}\n</Drawer.Root>;\n```\n\nApply the snap point offset in your styles when using vertical drawers:\n\n```css title=\"Snap point offset\"\n.DrawerPopup {\n  transform: translateY(calc(var(--drawer-snap-point-offset) + var(--drawer-swipe-movement-y)));\n}\n```\n\nimport { DemoDrawerSnapPoints } from './demos/snap-points';\n\n<DemoDrawerSnapPoints />\n\nBy default, the drawer can skip snap points when swiping quickly. Specify the `snapToSequentialPoints` prop to disable velocity-based skipping so the snap target is determined by drag distance (you can still drag past multiple points).\n\n### Indent effect\n\nScale the background down when any drawer opens by wrapping your app in `<Drawer.Provider>` and use `<Drawer.IndentBackground>` + `<Drawer.Indent>` at the top of your tree. Any `<Drawer.Root>` within the provider notifies it when it mounts, which activates the indent parts (they receive `[data-active]` state attributes).\n\nimport { DemoDrawerIndentProvider } from './demos/indent-provider';\n\n<DemoDrawerIndentProvider />\n\n### Non-modal\n\nSet `modal={false}` to opt out of focus trapping and `disablePointerDismissal` to keep the drawer open on outside clicks.\n\nimport { DemoDrawerNonModal } from './demos/non-modal';\n\n<DemoDrawerNonModal />\n\n### Mobile navigation\n\nYou can build a full-screen mobile navigation sheet using Drawer parts, including a flick-to-dismiss from the top gesture.\n\nimport { DemoDrawerMobileNav } from './demos/mobile-nav';\n\n<DemoDrawerMobileNav />\n\n### Swipe to open\n\nPlace `<Drawer.SwipeArea>` along the edge of the viewport to enable swipe-to-open gestures.\n\nimport { DemoDrawerSwipeArea } from './demos/swipe-area';\n\n<DemoDrawerSwipeArea />\n\n### Action sheet with separate destructive action\n\nThis demo builds an action sheet with a grouped list of actions plus a separate destructive action button.\n\nimport { DemoDrawerUncontained } from './demos/uncontained';\n\n<DemoDrawerUncontained compact />\n\n### Detached triggers\n\nA drawer can be controlled by a trigger located either inside or outside the `<Drawer.Root>` component. For simple, one-off interactions, place the `<Drawer.Trigger>` inside `<Drawer.Root>`.\n\nHowever, if defining the drawer's content next to its trigger is not practical, you can use a detached trigger. This involves placing the `<Drawer.Trigger>` outside of `<Drawer.Root>` and linking them with a `handle` created by the `Drawer.createHandle()` function.\n\n```jsx title=\"Detached triggers\"\nconst demoDrawer = Drawer.createHandle();\n\n<Drawer.Trigger handle={demoDrawer}>Open</Drawer.Trigger>\n\n<Drawer.Root handle={demoDrawer}>\n  ...\n</Drawer.Root>\n```\n\nThe drawer can render different content depending on which trigger opened it. This is achieved by passing a `payload` to the `<Drawer.Trigger>` and using the function-as-a-child pattern in `<Drawer.Root>`.\n\n```jsx title=\"Detached triggers with payload\"\nconst demoDrawer = Drawer.createHandle<{ title: string }>();\n\n<Drawer.Trigger handle={demoDrawer} payload={{ title: 'Profile' }}>\n  Profile\n</Drawer.Trigger>\n\n<Drawer.Trigger handle={demoDrawer} payload={{ title: 'Settings' }}>\n  Settings\n</Drawer.Trigger>\n\n<Drawer.Root handle={demoDrawer}>\n  {({ payload }) => (\n    <Drawer.Portal>\n      <Drawer.Popup>\n        <Drawer.Content>\n          <Drawer.Title>{payload?.title}</Drawer.Title>\n        </Drawer.Content>\n      </Drawer.Popup>\n    </Drawer.Portal>\n  )}\n</Drawer.Root>\n```\n\n### Stacking and animations\n\nUse CSS transitions or animations to animate drawer opening, closing, swipe interactions, and nested stacking. The `data-starting-style` attribute is applied when a drawer starts to open, and `data-ending-style` is applied when it starts to close.\n\nThe `--nested-drawers` CSS variable can be used to determine stack depth. The frontmost drawer has index `0`.\n\n```css title=\"Stack depth\"\n.DrawerPopup {\n  --stack-step: 0.05;\n  --stack-scale: calc(1 - (var(--nested-drawers) * var(--stack-step)));\n  transform: translateY(var(--drawer-swipe-movement-y)) scale(var(--stack-scale));\n}\n```\n\nWhen stacked drawers have varying heights, use the `--drawer-height` and `--drawer-frontmost-height` variables to keep collapsed drawers aligned with the frontmost one.\n\n```css title=\"Variable-height stacking\"\n.DrawerPopup {\n  --bleed: 3rem;\n  --stack-height: max(\n    0px,\n    calc(var(--drawer-frontmost-height, var(--drawer-height)) - var(--bleed))\n  );\n  height: var(--drawer-height, auto);\n}\n\n.DrawerPopup[data-nested-drawer-open] {\n  height: calc(var(--stack-height) + var(--bleed));\n  overflow: hidden;\n}\n```\n\nThe `data-nested-drawer-open` attribute marks drawers behind the frontmost drawer. Use it with `data-nested-drawer-swiping` to dim or hide parent drawer content while keeping it visible during nested swipe interactions.\n\n```css title=\"Nested content visibility\" \"data-nested-drawer-open\" \"data-nested-drawer-swiping\"\n.DrawerContent {\n  transition: opacity 300ms;\n}\n\n.DrawerPopup[data-nested-drawer-open] .DrawerContent {\n  opacity: 0;\n}\n\n.DrawerPopup[data-nested-drawer-open][data-nested-drawer-swiping] .DrawerContent {\n  opacity: 1;\n}\n```\n\nThe `--drawer-swipe-movement-x`, `--drawer-swipe-movement-y`, and `--drawer-snap-point-offset` CSS variables can be used to create smooth drag and snap offsets:\n\n```css title=\"Swipe and snap offset\" \"--drawer-swipe-movement-x\" \"--drawer-swipe-movement-y\"\n.DrawerPopup[data-swipe-direction='right'] {\n  transform: translateX(var(--drawer-swipe-movement-x));\n}\n\n.DrawerPopup[data-swipe-direction='down'] {\n  transform: translateY(calc(var(--drawer-snap-point-offset) + var(--drawer-swipe-movement-y)));\n}\n```\n\nThe `data-swipe-direction` attribute can be used with `data-ending-style` to animate directional dismissal:\n\n```css title=\"Swipe dismissal direction\" \"data-swipe-direction\"\n.DrawerPopup[data-ending-style][data-swipe-direction='right'] {\n  transform: translateX(100%);\n}\n\n.DrawerPopup[data-ending-style][data-swipe-direction='down'] {\n  transform: translateY(100%);\n}\n```\n\nUse `--drawer-swipe-progress` to fade the backdrop as the drawer is swiped, and `--drawer-swipe-strength` to scale release transition durations based on swipe velocity.\n\n```css title=\"Backdrop and release timing\"\n.DrawerBackdrop {\n  --backdrop-opacity: 0.2;\n  opacity: calc(var(--backdrop-opacity) * (1 - var(--drawer-swipe-progress)));\n}\n\n.DrawerPopup[data-ending-style],\n.DrawerBackdrop[data-ending-style] {\n  transition-duration: calc(var(--drawer-swipe-strength) * 400ms);\n}\n\n.DrawerPopup[data-swiping],\n.DrawerBackdrop[data-swiping] {\n  transition-duration: 0ms;\n}\n```\n\n## API reference\n\n<Reference\n  component=\"Drawer\"\n  parts=\"Provider, IndentBackground, Indent, Root, Trigger, SwipeArea, Portal, Backdrop, Viewport, Popup, Content, Title, Description, Close\"\n/>\n\nexport const metadata = {\n  keywords: [\n    'React Drawer',\n    'Drawer Component',\n    'Side Panel',\n    'Bottom Sheet',\n    'Swipe Dismiss Drawer',\n    'Offcanvas',\n    'Sliding Panel',\n    'Modal Drawer',\n    'Controlled Drawer State',\n    'Accessible Drawer',\n    'Headless React Components',\n    'Base UI',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/field/demos/hero/css-modules/index.module.css",
    "content": ".Field {\n  display: flex;\n  flex-direction: column;\n  align-items: start;\n  gap: 0.25rem;\n  width: 100%;\n  max-width: 16rem;\n}\n\n.Label {\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 700;\n  color: var(--color-gray-900);\n}\n\n.Input {\n  box-sizing: border-box;\n  padding-left: 0.875rem;\n  margin: 0;\n  border: 1px solid var(--color-gray-200);\n  width: 100%;\n  height: 2.5rem;\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  background-color: transparent;\n  color: var(--color-gray-900);\n\n  &:focus {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Error {\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  color: var(--color-red-800);\n}\n\n.Description {\n  margin: 0;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  color: var(--color-gray-600);\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/field/demos/hero/css-modules/index.tsx",
    "content": "import { Field } from '@base-ui/react/field';\nimport styles from './index.module.css';\n\nexport default function ExampleField() {\n  return (\n    <Field.Root className={styles.Field}>\n      <Field.Label className={styles.Label}>Name</Field.Label>\n      <Field.Control required placeholder=\"Required\" className={styles.Input} />\n\n      <Field.Error className={styles.Error} match=\"valueMissing\">\n        Please enter your name\n      </Field.Error>\n\n      <Field.Description className={styles.Description}>Visible on your profile</Field.Description>\n    </Field.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/field/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoFieldHero = createDemoWithVariants(import.meta.url, { CssModules, Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/field/demos/hero/tailwind/index.tsx",
    "content": "import { Field } from '@base-ui/react/field';\n\nexport default function ExampleField() {\n  return (\n    <Field.Root className=\"flex w-full max-w-64 flex-col items-start gap-1\">\n      <Field.Label className=\"text-sm font-bold text-gray-900\">Name</Field.Label>\n      <Field.Control\n        required\n        placeholder=\"Required\"\n        className=\"h-10 w-full rounded-md border border-gray-200 pl-3.5 text-base text-gray-900 focus:outline-2 focus:-outline-offset-1 focus:outline-blue-800 font-normal\"\n      />\n      <Field.Error className=\"text-sm text-red-800\" match=\"valueMissing\">\n        Please enter your name\n      </Field.Error>\n\n      <Field.Description className=\"text-sm text-gray-600\">\n        Visible on your profile\n      </Field.Description>\n    </Field.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/field/page.mdx",
    "content": "# Field\n\n<Subtitle>A component that provides labeling and validation for form controls.</Subtitle>\n\n<Meta\n  name=\"description\"\n  content=\"A high-quality, unstyled React field component that provides labeling and validation for form controls.\"\n/>\n\nimport { DemoFieldHero } from './demos/hero';\n\n<DemoFieldHero />\n\n## Anatomy\n\nImport the component and assemble its parts:\n\n```jsx title=\"Anatomy\"\nimport { Field } from '@base-ui/react/field';\n\n<Field.Root>\n  <Field.Label />\n  <Field.Control />\n  <Field.Description />\n  <Field.Item />\n  <Field.Error />\n  <Field.Validity />\n</Field.Root>;\n```\n\n## API reference\n\n<Reference component=\"Field\" parts=\"Root, Label, Control, Description, Item, Error, Validity\" />\n\nexport const metadata = {\n  keywords: [\n    'React Field Component',\n    'Form Field Labeling',\n    'Form Label',\n    'Input Wrapper',\n    'Form Validation',\n    'Accessible Form Field',\n    'Field Validation UI',\n    'Headless React Components',\n    'Custom Form Control Wrapper',\n    'Form Description Error State',\n    'Base UI',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/fieldset/demos/hero/css-modules/index.module.css",
    "content": ".Fieldset {\n  border: 0;\n  margin: 0;\n  padding: 0;\n  display: flex;\n  flex-direction: column;\n  gap: 1rem;\n  width: 100%;\n  max-width: 16rem;\n}\n\n.Legend {\n  border-bottom: 1px solid var(--color-gray-200);\n  padding-bottom: 0.75rem;\n  font-weight: 700;\n  font-size: 1.125rem;\n  line-height: 1.75rem;\n  letter-spacing: -0.0025em;\n  color: var(--color-gray-900);\n}\n\n.Field {\n  display: flex;\n  flex-direction: column;\n  align-items: start;\n  gap: 0.25rem;\n}\n\n.Label {\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 700;\n  color: var(--color-gray-900);\n}\n\n.Input {\n  box-sizing: border-box;\n  padding-left: 0.875rem;\n  margin: 0;\n  border: 1px solid var(--color-gray-200);\n  width: 100%;\n  height: 2.5rem;\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  background-color: transparent;\n  color: var(--color-gray-900);\n\n  &:focus {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Error {\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  color: var(--color-red-800);\n}\n\n.Description {\n  margin: 0;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  color: var(--color-gray-600);\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/fieldset/demos/hero/css-modules/index.tsx",
    "content": "import { Field } from '@base-ui/react/field';\nimport { Fieldset } from '@base-ui/react/fieldset';\nimport styles from './index.module.css';\n\nexport default function ExampleField() {\n  return (\n    <Fieldset.Root className={styles.Fieldset}>\n      <Fieldset.Legend className={styles.Legend}>Billing details</Fieldset.Legend>\n\n      <Field.Root className={styles.Field}>\n        <Field.Label className={styles.Label}>Company</Field.Label>\n        <Field.Control placeholder=\"Enter company name\" className={styles.Input} />\n      </Field.Root>\n\n      <Field.Root className={styles.Field}>\n        <Field.Label className={styles.Label}>Tax ID</Field.Label>\n        <Field.Control placeholder=\"Enter fiscal number\" className={styles.Input} />\n      </Field.Root>\n    </Fieldset.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/fieldset/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoFieldsetHero = createDemoWithVariants(import.meta.url, { CssModules, Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/fieldset/demos/hero/tailwind/index.tsx",
    "content": "import { Field } from '@base-ui/react/field';\nimport { Fieldset } from '@base-ui/react/fieldset';\n\nexport default function ExampleField() {\n  return (\n    <Fieldset.Root className=\"flex w-full max-w-64 flex-col gap-4\">\n      <Fieldset.Legend className=\"border-b border-gray-200 pb-3 text-lg font-bold text-gray-900\">\n        Billing details\n      </Fieldset.Legend>\n\n      <Field.Root className=\"flex flex-col items-start gap-1\">\n        <Field.Label className=\"text-sm font-bold text-gray-900\">Company</Field.Label>\n        <Field.Control\n          placeholder=\"Enter company name\"\n          className=\"h-10 w-full rounded-md border border-gray-200 pl-3.5 text-base text-gray-900 focus:outline-2 focus:-outline-offset-1 focus:outline-blue-800 font-normal\"\n        />\n      </Field.Root>\n\n      <Field.Root className=\"flex flex-col items-start gap-1\">\n        <Field.Label className=\"text-sm font-bold text-gray-900\">Tax ID</Field.Label>\n        <Field.Control\n          placeholder=\"Enter fiscal number\"\n          className=\"h-10 w-full rounded-md border border-gray-200 pl-3.5 text-base text-gray-900 focus:outline-2 focus:-outline-offset-1 focus:outline-blue-800 font-normal\"\n        />\n      </Field.Root>\n    </Fieldset.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/fieldset/page.mdx",
    "content": "# Fieldset\n\n<Subtitle>A native fieldset element with an easily stylable legend.</Subtitle>\n\n<Meta\n  name=\"description\"\n  content=\"A high-quality, unstyled React fieldset component with an easily stylable legend.\"\n/>\n\nimport { DemoFieldsetHero } from './demos/hero';\n\n<DemoFieldsetHero />\n\n## Anatomy\n\nImport the component and assemble its parts:\n\n```jsx title=\"Anatomy\"\nimport { Fieldset } from '@base-ui/react/fieldset';\n\n<Fieldset.Root>\n  <Fieldset.Legend />\n</Fieldset.Root>;\n```\n\n## API reference\n\n<Reference component=\"Fieldset\" parts=\"Root, Legend\" />\n\nexport const metadata = {\n  keywords: [\n    'React Fieldset',\n    'Fieldset Component',\n    'Form Field Grouping',\n    'Form Fieldset',\n    'Form Group',\n    'Input Grouping',\n    'Custom Legend Styling',\n    'Fieldset Legend',\n    'Accessible Fieldset',\n    'Headless React Components',\n    'Form Section Labeling',\n    'Base UI',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/form/demos/form-action/css-modules/index.module.css",
    "content": ".Form {\n  display: flex;\n  flex-direction: column;\n  gap: 1rem;\n  width: 100%;\n  max-width: 16rem;\n}\n\n.Field {\n  display: flex;\n  flex-direction: column;\n  align-items: start;\n  gap: 0.25rem;\n}\n\n.Label {\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 700;\n  color: var(--color-gray-900);\n}\n\n.Input {\n  box-sizing: border-box;\n  padding-left: 0.875rem;\n  margin: 0;\n  border: 1px solid var(--color-gray-200);\n  width: 100%;\n  height: 2.5rem;\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  background-color: transparent;\n  color: var(--color-gray-900);\n\n  &:focus {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Error {\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  color: var(--color-red-800);\n}\n\n.Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n\n  &[data-disabled] {\n    color: var(--color-gray-500);\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/form/demos/form-action/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Field } from '@base-ui/react/field';\nimport { Form } from '@base-ui/react/form';\nimport { Button } from '@base-ui/react/button';\nimport styles from './index.module.css';\n\ninterface FormState {\n  serverErrors?: Form.Props['errors'];\n}\n\nexport default function ActionStateForm() {\n  const [state, formAction, loading] = React.useActionState<FormState, FormData>(submitForm, {});\n\n  return (\n    <Form errors={state.serverErrors} action={formAction} className={styles.Form}>\n      <Field.Root name=\"username\" className={styles.Field}>\n        <Field.Label className={styles.Label}>Username</Field.Label>\n        <Field.Control\n          type=\"username\"\n          required\n          defaultValue=\"admin\"\n          placeholder=\"e.g. alice132\"\n          className={styles.Input}\n        />\n        <Field.Error className={styles.Error} />\n      </Field.Root>\n      <Button type=\"submit\" disabled={loading} focusableWhenDisabled className={styles.Button}>\n        Submit\n      </Button>\n    </Form>\n  );\n}\n\n// Mark this as a Server Function with `'use server'` in a supporting framework like Next.js\nasync function submitForm(_previousState: FormState, formData: FormData) {\n  // Mimic a server response\n  await new Promise((resolve) => {\n    setTimeout(resolve, 1000);\n  });\n\n  try {\n    const username = formData.get('username') as string | null;\n\n    if (username === 'admin') {\n      return { success: false, serverErrors: { username: \"'admin' is reserved for system use\" } };\n    }\n\n    // 50% chance the username is taken\n    const success = Math.random() > 0.5;\n\n    if (!success) {\n      return {\n        serverErrors: { username: `${username} is unavailable` },\n      };\n    }\n  } catch {\n    return { serverErrors: { username: 'A server error has occurred' } };\n  }\n\n  return {};\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/form/demos/form-action/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoFormAction = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/form/demos/form-action/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Field } from '@base-ui/react/field';\nimport { Form } from '@base-ui/react/form';\nimport { Button } from '@base-ui/react/button';\n\ninterface FormState {\n  serverErrors?: Form.Props['errors'];\n}\n\nexport default function ActionStateForm() {\n  const [state, formAction, loading] = React.useActionState<FormState, FormData>(submitForm, {});\n\n  return (\n    <Form\n      action={formAction}\n      errors={state.serverErrors}\n      className=\"flex w-full max-w-64 flex-col gap-4\"\n    >\n      <Field.Root name=\"username\" className=\"flex flex-col items-start gap-1\">\n        <Field.Label className=\"text-sm font-bold text-gray-900\">Username</Field.Label>\n        <Field.Control\n          type=\"username\"\n          required\n          defaultValue=\"admin\"\n          placeholder=\"e.g. alice132\"\n          className=\"h-10 w-full rounded-md border border-gray-200 pl-3.5 text-base text-gray-900 focus:outline-2 focus:-outline-offset-1 focus:outline-blue-800 font-normal\"\n        />\n        <Field.Error className=\"text-sm text-red-800\" />\n      </Field.Root>\n      <Button\n        type=\"submit\"\n        disabled={loading}\n        focusableWhenDisabled\n        className=\"flex items-center justify-center h-10 px-3.5 m-0 outline-0 border border-gray-200 rounded-md bg-gray-50 font-inherit text-base font-normal leading-6 text-gray-900 select-none hover:data-[disabled]:bg-gray-50 hover:bg-gray-100 active:data-[disabled]:bg-gray-50 active:bg-gray-200 active:shadow-[inset_0_1px_3px_rgba(0,0,0,0.1)] active:border-t-gray-300 active:data-[disabled]:shadow-none active:data-[disabled]:border-t-gray-200 focus-visible:outline-2 focus-visible:outline-blue-800 focus-visible:-outline-offset-1 data-[disabled]:text-gray-500\"\n      >\n        Submit\n      </Button>\n    </Form>\n  );\n}\n\n// Mark this as a Server Function with `'use server'` in a supporting framework like Next.js\nasync function submitForm(_previousState: FormState, formData: FormData) {\n  // Mimic a server response\n  await new Promise((resolve) => {\n    setTimeout(resolve, 1000);\n  });\n\n  try {\n    const username = formData.get('username') as string | null;\n\n    if (username === 'admin') {\n      return { success: false, serverErrors: { username: \"'admin' is reserved for system use\" } };\n    }\n\n    // 50% chance the username is taken\n    const success = Math.random() > 0.5;\n\n    if (!success) {\n      return {\n        serverErrors: { username: `${username} is unavailable` },\n      };\n    }\n  } catch {\n    return { serverErrors: { username: 'A server error has occurred' } };\n  }\n\n  return {};\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/form/demos/hero/css-modules/index.module.css",
    "content": ".Form {\n  display: flex;\n  flex-direction: column;\n  gap: 1rem;\n  width: 100%;\n  max-width: 16rem;\n}\n\n.Field {\n  display: flex;\n  flex-direction: column;\n  align-items: start;\n  gap: 0.25rem;\n}\n\n.Label {\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 700;\n  color: var(--color-gray-900);\n}\n\n.Input {\n  box-sizing: border-box;\n  padding-left: 0.875rem;\n  margin: 0;\n  border: 1px solid var(--color-gray-200);\n  width: 100%;\n  height: 2.5rem;\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  background-color: transparent;\n  color: var(--color-gray-900);\n\n  &:focus {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Error {\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  color: var(--color-red-800);\n}\n\n.Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover:not([data-disabled]) {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active:not([data-disabled]) {\n    background-color: var(--color-gray-200);\n    box-shadow: inset 0 1px 3px var(--color-gray-200);\n    border-top-color: var(--color-gray-300);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n\n  &[data-disabled] {\n    color: var(--color-gray-500);\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/form/demos/hero/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Field } from '@base-ui/react/field';\nimport { Form } from '@base-ui/react/form';\nimport { Button } from '@base-ui/react/button';\nimport styles from './index.module.css';\n\nexport default function ExampleForm() {\n  const [errors, setErrors] = React.useState({});\n  const [loading, setLoading] = React.useState(false);\n\n  return (\n    <Form\n      className={styles.Form}\n      errors={errors}\n      onSubmit={async (event) => {\n        event.preventDefault();\n        const formData = new FormData(event.currentTarget);\n        const value = formData.get('url') as string;\n\n        setLoading(true);\n        const response = await submitForm(value);\n        const serverErrors = {\n          url: response.error,\n        };\n\n        setErrors(serverErrors);\n        setLoading(false);\n      }}\n    >\n      <Field.Root name=\"url\" className={styles.Field}>\n        <Field.Label className={styles.Label}>Homepage</Field.Label>\n        <Field.Control\n          type=\"url\"\n          required\n          defaultValue=\"https://example.com\"\n          placeholder=\"https://example.com\"\n          pattern=\"https?://.*\"\n          className={styles.Input}\n        />\n        <Field.Error className={styles.Error} />\n      </Field.Root>\n      <Button type=\"submit\" disabled={loading} focusableWhenDisabled className={styles.Button}>\n        Submit\n      </Button>\n    </Form>\n  );\n}\n\nasync function submitForm(value: string) {\n  // Mimic a server response\n  await new Promise((resolve) => {\n    setTimeout(resolve, 1000);\n  });\n\n  try {\n    const url = new URL(value);\n\n    if (url.hostname.endsWith('example.com')) {\n      return { error: 'The example domain is not allowed' };\n    }\n  } catch {\n    return { error: 'This is not a valid URL' };\n  }\n\n  return { success: true };\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/form/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoFormHero = createDemoWithVariants(import.meta.url, { CssModules, Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/form/demos/hero/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Field } from '@base-ui/react/field';\nimport { Form } from '@base-ui/react/form';\nimport { Button } from '@base-ui/react/button';\n\nexport default function ExampleForm() {\n  const [errors, setErrors] = React.useState({});\n  const [loading, setLoading] = React.useState(false);\n\n  return (\n    <Form\n      className=\"flex w-full max-w-64 flex-col gap-4\"\n      errors={errors}\n      onSubmit={async (event) => {\n        event.preventDefault();\n        const formData = new FormData(event.currentTarget);\n        const value = formData.get('url') as string;\n\n        setLoading(true);\n        const response = await submitForm(value);\n        const serverErrors = {\n          url: response.error,\n        };\n\n        setErrors(serverErrors);\n        setLoading(false);\n      }}\n    >\n      <Field.Root name=\"url\" className=\"flex flex-col items-start gap-1\">\n        <Field.Label className=\"text-sm font-bold text-gray-900\">Homepage</Field.Label>\n        <Field.Control\n          type=\"url\"\n          required\n          defaultValue=\"https://example.com\"\n          placeholder=\"https://example.com\"\n          pattern=\"https?://.*\"\n          className=\"h-10 w-full rounded-md border border-gray-200 pl-3.5 text-base text-gray-900 focus:outline-2 focus:-outline-offset-1 focus:outline-blue-800 font-normal\"\n        />\n        <Field.Error className=\"text-sm text-red-800\" />\n      </Field.Root>\n      <Button\n        disabled={loading}\n        focusableWhenDisabled\n        type=\"submit\"\n        className=\"flex items-center justify-center h-10 px-3.5 m-0 outline-0 border border-gray-200 rounded-md bg-gray-50 font-inherit text-base font-normal leading-6 text-gray-900 select-none hover:data-[disabled]:bg-gray-50 hover:bg-gray-100 active:data-[disabled]:bg-gray-50 active:bg-gray-200 active:shadow-[inset_0_1px_3px_rgba(0,0,0,0.1)] active:border-t-gray-300 active:data-[disabled]:shadow-none active:data-[disabled]:border-t-gray-200 focus-visible:outline-2 focus-visible:outline-blue-800 focus-visible:-outline-offset-1 data-[disabled]:text-gray-500\"\n      >\n        Submit\n      </Button>\n    </Form>\n  );\n}\n\nasync function submitForm(value: string) {\n  // Mimic a server response\n  await new Promise((resolve) => {\n    setTimeout(resolve, 1000);\n  });\n\n  try {\n    const url = new URL(value);\n\n    if (url.hostname.endsWith('example.com')) {\n      return { error: 'The example domain is not allowed' };\n    }\n  } catch {\n    return { error: 'This is not a valid URL' };\n  }\n\n  return { success: true };\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/form/demos/zod/css-modules/index.module.css",
    "content": ".Form {\n  display: flex;\n  flex-direction: column;\n  gap: 1rem;\n  width: 100%;\n  max-width: 16rem;\n}\n\n.Field {\n  display: flex;\n  flex-direction: column;\n  align-items: start;\n  gap: 0.25rem;\n}\n\n.Label {\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 700;\n  color: var(--color-gray-900);\n}\n\n.Input {\n  box-sizing: border-box;\n  padding-left: 0.875rem;\n  margin: 0;\n  border: 1px solid var(--color-gray-200);\n  width: 100%;\n  height: 2.5rem;\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  background-color: transparent;\n  color: var(--color-gray-900);\n\n  &:focus {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Error {\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  color: var(--color-red-800);\n}\n\n.Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover:not([data-disabled]) {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active:not([data-disabled]) {\n    background-color: var(--color-gray-200);\n    box-shadow: inset 0 1px 3px var(--color-gray-200);\n    border-top-color: var(--color-gray-300);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n\n  &[data-disabled] {\n    color: var(--color-gray-500);\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/form/demos/zod/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { z } from 'zod';\nimport { Field } from '@base-ui/react/field';\nimport { Form } from '@base-ui/react/form';\nimport { Button } from '@base-ui/react/button';\nimport styles from './index.module.css';\n\nconst schema = z.object({\n  name: z.string().min(1, 'Name is required'),\n  age: z.coerce.number('Age must be a number').positive('Age must be a positive number'),\n});\n\nasync function submitForm(formValues: Form.Values) {\n  const result = schema.safeParse(formValues);\n\n  if (!result.success) {\n    return {\n      errors: z.flattenError(result.error).fieldErrors,\n    };\n  }\n\n  return {\n    errors: {},\n  };\n}\n\nexport default function Page() {\n  const [errors, setErrors] = React.useState({});\n\n  return (\n    <Form\n      className={styles.Form}\n      errors={errors}\n      onFormSubmit={async (formValues) => {\n        const response = await submitForm(formValues);\n        setErrors(response.errors);\n      }}\n    >\n      <Field.Root name=\"name\" className={styles.Field}>\n        <Field.Label className={styles.Label}>Name</Field.Label>\n        <Field.Control placeholder=\"Enter name\" className={styles.Input} />\n        <Field.Error className={styles.Error} />\n      </Field.Root>\n      <Field.Root name=\"age\" className={styles.Field}>\n        <Field.Label className={styles.Label}>Age</Field.Label>\n        <Field.Control placeholder=\"Enter age\" className={styles.Input} />\n        <Field.Error className={styles.Error} />\n      </Field.Root>\n      <Button type=\"submit\" className={styles.Button}>\n        Submit\n      </Button>\n    </Form>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/form/demos/zod/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoFormZod = createDemoWithVariants(import.meta.url, { CssModules, Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/form/demos/zod/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { z } from 'zod';\nimport { Field } from '@base-ui/react/field';\nimport { Form } from '@base-ui/react/form';\nimport { Button } from '@base-ui/react/button';\n\nconst schema = z.object({\n  name: z.string().min(1, 'Name is required'),\n  age: z.coerce.number('Age must be a number').positive('Age must be a positive number'),\n});\n\nasync function submitForm(formValues: Form.Values) {\n  const result = schema.safeParse(formValues);\n\n  if (!result.success) {\n    return {\n      errors: z.flattenError(result.error).fieldErrors,\n    };\n  }\n\n  return {\n    errors: {},\n  };\n}\n\nexport default function Page() {\n  const [errors, setErrors] = React.useState({});\n\n  return (\n    <Form\n      className=\"flex w-full max-w-64 flex-col gap-4\"\n      errors={errors}\n      onFormSubmit={async (formValues) => {\n        const response = await submitForm(formValues);\n        setErrors(response.errors);\n      }}\n    >\n      <Field.Root name=\"name\" className=\"flex flex-col items-start gap-1\">\n        <Field.Label className=\"text-sm font-bold text-gray-900\">Name</Field.Label>\n        <Field.Control\n          placeholder=\"Enter name\"\n          className=\"h-10 w-full rounded-md border border-gray-200 pl-3.5 text-base text-gray-900 focus:outline-2 focus:-outline-offset-1 focus:outline-blue-800 font-normal\"\n        />\n        <Field.Error className=\"text-sm text-red-800\" />\n      </Field.Root>\n      <Field.Root name=\"age\" className=\"flex flex-col items-start gap-1\">\n        <Field.Label className=\"text-sm font-bold text-gray-900\">Age</Field.Label>\n        <Field.Control\n          placeholder=\"Enter age\"\n          className=\"h-10 w-full rounded-md border border-gray-200 pl-3.5 text-base text-gray-900 focus:outline-2 focus:-outline-offset-1 focus:outline-blue-800 font-normal\"\n        />\n        <Field.Error className=\"text-sm text-red-800\" />\n      </Field.Root>\n      <Button\n        type=\"submit\"\n        className=\"flex items-center justify-center h-10 px-3.5 m-0 outline-0 border border-gray-200 rounded-md bg-gray-50 font-inherit text-base font-normal leading-6 text-gray-900 select-none hover:data-[disabled]:bg-gray-50 hover:bg-gray-100 active:data-[disabled]:bg-gray-50 active:bg-gray-200 active:shadow-[inset_0_1px_3px_rgba(0,0,0,0.1)] active:border-t-gray-300 active:data-[disabled]:shadow-none active:data-[disabled]:border-t-gray-200 focus-visible:outline-2 focus-visible:outline-blue-800 focus-visible:-outline-offset-1 data-[disabled]:text-gray-500\"\n      >\n        Submit\n      </Button>\n    </Form>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/form/page.mdx",
    "content": "# Form\n\n<Subtitle>A native form element with consolidated error handling.</Subtitle>\n\n<Meta\n  name=\"description\"\n  content=\"A high-quality, unstyled React form component with consolidated error handling.\"\n/>\n\nimport { DemoFormHero } from './demos/hero';\n\n<DemoFormHero />\n\n## Anatomy\n\nForm is composed together with [Field](/react/components/field). Import the components and place them together:\n\n```jsx title=\"Anatomy\"\nimport { Field } from '@base-ui/react/field';\nimport { Form } from '@base-ui/react/form';\n\n<Form>\n  <Field.Root>\n    <Field.Label />\n    <Field.Control />\n    <Field.Error />\n  </Field.Root>\n</Form>;\n```\n\n## Examples\n\n### Submit with a Server Function\n\nForms using `useActionState` can be submitted with a [Server Function](https://react.dev/reference/react-dom/components/form#handle-form-submission-with-a-server-function) instead of `onSubmit`.\n\nimport { DemoFormAction } from './demos/form-action';\n\n<DemoFormAction />\n\n### Submit form values as a JavaScript object\n\nYou can use `onFormSubmit` instead of the native `onSubmit` to access form values as a JavaScript object. This is useful when you need to transform the values before submission, or integrate with 3rd party APIs.\n\n```tsx title=\"Submission using onFormSubmit\"\n<Form\n  onFormSubmit={async (formValues: { id: string; quantity: number }) => {\n    const payload = {\n      product_id: formValues.id,\n      order_quantity: formValues.quantity,\n    };\n\n    const response = await fetch('https://api.example.com', {\n      method: 'POST',\n      body: payload,\n    });\n  }}\n/>\n```\n\nWhen used, `preventDefault` is called on the native submit event.\n\n### Using with Zod\n\nWhen parsing the schema using `schema.safeParse()`, the `z.flattenError(result.error).fieldErrors` data can be used to map the errors to each field's `name`.\n\nimport { DemoFormZod } from './demos/zod';\n\n<DemoFormZod />\n\n## API reference\n\n<Reference component=\"Form\" />\n\nexport const metadata = {\n  keywords: [\n    'React Form Component',\n    'Form Submission Handler',\n    'Form Validation',\n    'Form State',\n    'Server Function Form',\n    'JavaScript Form Values',\n    'Accessible Form',\n    'Headless React Components',\n    'Form Error Handling',\n    'Base UI',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/input/demos/hero/css-modules/index.module.css",
    "content": ".Label {\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n  gap: 0.25rem;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 700;\n  color: var(--color-gray-900);\n}\n\n.Input {\n  box-sizing: border-box;\n  padding-left: 0.875rem;\n  margin: 0;\n  border: 1px solid var(--color-gray-200);\n  width: 14rem;\n  height: 2.5rem;\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  background-color: transparent;\n  color: var(--color-gray-900);\n\n  &:focus {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/input/demos/hero/css-modules/index.tsx",
    "content": "import { Input } from '@base-ui/react/input';\nimport styles from './index.module.css';\n\nexport default function ExampleInput() {\n  return (\n    <label className={styles.Label}>\n      Name\n      <Input placeholder=\"Enter your name\" className={styles.Input} />\n    </label>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/input/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoInputHero = createDemoWithVariants(import.meta.url, { CssModules, Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/input/demos/hero/tailwind/index.tsx",
    "content": "import { Input } from '@base-ui/react/input';\n\nexport default function ExampleInput() {\n  return (\n    <label className=\"flex flex-col items-start gap-1 text-sm text-gray-900 font-bold\">\n      Name\n      <Input\n        placeholder=\"Enter your name\"\n        className=\"h-10 w-56 rounded-md border border-gray-200 pl-3.5 text-base text-gray-900 focus:outline-2 focus:-outline-offset-1 focus:outline-blue-800 font-normal\"\n      />\n    </label>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/input/page.mdx",
    "content": "# Input\n\n<Subtitle>\n  A native input element that automatically works with [Field](/react/components/field).\n</Subtitle>\n\n<Meta name=\"description\" content=\"A high-quality, unstyled React input component.\" />\n\nimport { DemoInputHero } from './demos/hero';\n\n<DemoInputHero />\n\n## Usage guidelines\n\n- **Form controls must have an accessible name**: It can be created using a `<label>` element or the `Field` component. See the [forms guide](/react/handbook/forms).\n\n## Anatomy\n\nImport the component and use it as a single part:\n\n```jsx title=\"Anatomy\"\nimport { Input } from '@base-ui/react/input';\n\n<Input />;\n```\n\n## API reference\n\n<Reference component=\"Input\" />\n\nexport const metadata = {\n  keywords: [\n    'React Input',\n    'Input Component',\n    'Input Field',\n    'Form Input Field',\n    'Text Field',\n    'Text Input',\n    'Text Box',\n    'Form Input',\n    'Textarea Alternative',\n    'Controlled Input',\n    'Form Element',\n    'Accessible Input',\n    'Headless React Components',\n    'Unstyled Input',\n    'Base UI',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menu/demos/_index.module.css",
    "content": ".IconButton {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 2.5rem;\n  height: 2.5rem;\n  padding: 0;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Icon {\n  width: 1.25rem;\n  height: 1.25rem;\n}\n\n.Container {\n  display: flex;\n  align-items: center;\n  gap: 0.5rem;\n  flex-wrap: wrap;\n}\n\n.Button {\n  box-sizing: border-box;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  gap: 0.375rem;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Positioner {\n  outline: 0;\n}\n\n.Popup {\n  box-sizing: border-box;\n  padding-block: 0.25rem;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Arrow {\n  display: flex;\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n\n.Item {\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  padding-block: 0.5rem;\n  padding-left: 1rem;\n  padding-right: 2rem;\n  display: flex;\n  font-size: 0.875rem;\n  line-height: 1rem;\n  color: inherit;\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.25rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.Label {\n  font-size: 0.75rem;\n  text-transform: uppercase;\n  letter-spacing: 0.05em;\n  color: var(--color-gray-500);\n  padding: 0.5rem 1rem;\n}\n\n.Separator {\n  margin: 0.375rem 1rem;\n  height: 1px;\n  background-color: var(--color-gray-200);\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menu/demos/checkbox-items/css-modules/index.module.css",
    "content": ".Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: 0.375rem;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.ButtonIcon {\n  margin-right: -0.25rem;\n}\n\n.Positioner {\n  outline: 0;\n}\n\n.Popup {\n  box-sizing: border-box;\n  padding-block: 0.25rem;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Arrow {\n  display: flex;\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n\n.CheckboxItem {\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  padding-block: 0.5rem;\n  padding-left: 0.625rem;\n  padding-right: 2rem;\n  font-size: 0.875rem;\n  line-height: 1rem;\n\n  display: grid;\n  gap: 0.5rem;\n  align-items: center;\n  grid-template-columns: 0.75rem 1fr;\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.25rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.CheckboxItemIndicator {\n  grid-column-start: 1;\n}\n\n.CheckboxItemIndicatorIcon {\n  display: block;\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.CheckboxItemText {\n  grid-column-start: 2;\n}\n\n.Separator {\n  margin: 0.375rem 1rem;\n  height: 1px;\n  background-color: var(--color-gray-200);\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menu/demos/checkbox-items/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\nimport styles from './index.module.css';\n\nexport default function ExampleMenu() {\n  const [showMinimap, setShowMinimap] = React.useState(true);\n  const [showSearch, setShowSearch] = React.useState(true);\n  const [showSidebar, setShowSidebar] = React.useState(false);\n\n  return (\n    <Menu.Root>\n      <Menu.Trigger className={styles.Button}>\n        Workspace <ChevronDownIcon className={styles.ButtonIcon} />\n      </Menu.Trigger>\n      <Menu.Portal>\n        <Menu.Positioner className={styles.Positioner} sideOffset={8}>\n          <Menu.Popup className={styles.Popup}>\n            <Menu.Arrow className={styles.Arrow}>\n              <ArrowSvg />\n            </Menu.Arrow>\n            <Menu.CheckboxItem\n              checked={showMinimap}\n              onCheckedChange={setShowMinimap}\n              className={styles.CheckboxItem}\n            >\n              <Menu.CheckboxItemIndicator className={styles.CheckboxItemIndicator}>\n                <CheckIcon className={styles.CheckboxItemIndicatorIcon} />\n              </Menu.CheckboxItemIndicator>\n              <span className={styles.CheckboxItemText}>Minimap</span>\n            </Menu.CheckboxItem>\n            <Menu.CheckboxItem\n              checked={showSearch}\n              onCheckedChange={setShowSearch}\n              className={styles.CheckboxItem}\n            >\n              <Menu.CheckboxItemIndicator className={styles.CheckboxItemIndicator}>\n                <CheckIcon className={styles.CheckboxItemIndicatorIcon} />\n              </Menu.CheckboxItemIndicator>\n              <span className={styles.CheckboxItemText}>Search</span>\n            </Menu.CheckboxItem>\n            <Menu.CheckboxItem\n              checked={showSidebar}\n              onCheckedChange={setShowSidebar}\n              className={styles.CheckboxItem}\n            >\n              <Menu.CheckboxItemIndicator className={styles.CheckboxItemIndicator}>\n                <CheckIcon className={styles.CheckboxItemIndicatorIcon} />\n              </Menu.CheckboxItemIndicator>\n              <span className={styles.CheckboxItemText}>Sidebar</span>\n            </Menu.CheckboxItem>\n          </Menu.Popup>\n        </Menu.Positioner>\n      </Menu.Portal>\n    </Menu.Root>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nfunction ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M1 3.5L5 7.5L9 3.5\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menu/demos/checkbox-items/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoMenuCheckboxItems = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menu/demos/checkbox-items/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\n\nexport default function ExampleMenu() {\n  const [showMinimap, setShowMinimap] = React.useState(true);\n  const [showSearch, setShowSearch] = React.useState(true);\n  const [showSidebar, setShowSidebar] = React.useState(false);\n\n  return (\n    <Menu.Root>\n      <Menu.Trigger className=\"flex h-10 items-center justify-center gap-1.5 rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100 data-[popup-open]:bg-gray-100\">\n        Workspace <ChevronDownIcon className=\"-mr-1\" />\n      </Menu.Trigger>\n      <Menu.Portal>\n        <Menu.Positioner className=\"outline-hidden\" sideOffset={8}>\n          <Menu.Popup className=\"origin-[var(--transform-origin)] rounded-md bg-[canvas] py-1 text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 transition-[transform,scale,opacity] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300\">\n            <Menu.Arrow className=\"data-[side=bottom]:top-[-8px] data-[side=left]:right-[-13px] data-[side=left]:rotate-90 data-[side=right]:left-[-13px] data-[side=right]:-rotate-90 data-[side=top]:bottom-[-8px] data-[side=top]:rotate-180\">\n              <ArrowSvg />\n            </Menu.Arrow>\n            <Menu.CheckboxItem\n              checked={showMinimap}\n              onCheckedChange={setShowMinimap}\n              className=\"grid cursor-default grid-cols-[0.75rem_1fr] items-center gap-2 py-2 pr-8 pl-2.5 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\"\n            >\n              <Menu.CheckboxItemIndicator className=\"col-start-1\">\n                <CheckIcon className=\"size-3\" />\n              </Menu.CheckboxItemIndicator>\n              <span className=\"col-start-2\">Minimap</span>\n            </Menu.CheckboxItem>\n            <Menu.CheckboxItem\n              checked={showSearch}\n              onCheckedChange={setShowSearch}\n              className=\"grid cursor-default grid-cols-[0.75rem_1fr] items-center gap-2 py-2 pr-8 pl-2.5 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\"\n            >\n              <Menu.CheckboxItemIndicator className=\"col-start-1\">\n                <CheckIcon className=\"size-3\" />\n              </Menu.CheckboxItemIndicator>\n              <span className=\"col-start-2\">Search</span>\n            </Menu.CheckboxItem>\n            <Menu.CheckboxItem\n              checked={showSidebar}\n              onCheckedChange={setShowSidebar}\n              className=\"grid cursor-default grid-cols-[0.75rem_1fr] items-center gap-2 py-2 pr-8 pl-2.5 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\"\n            >\n              <Menu.CheckboxItemIndicator className=\"col-start-1\">\n                <CheckIcon className=\"size-3\" />\n              </Menu.CheckboxItemIndicator>\n              <span className=\"col-start-2\">Sidebar</span>\n            </Menu.CheckboxItem>\n          </Menu.Popup>\n        </Menu.Positioner>\n      </Menu.Portal>\n    </Menu.Root>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className=\"fill-[canvas]\"\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className=\"fill-gray-200 dark:fill-none\"\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className=\"dark:fill-gray-300\"\n      />\n    </svg>\n  );\n}\n\nfunction ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M1 3.5L5 7.5L9 3.5\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menu/demos/detached-triggers-controlled/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\nimport styles from '../../_index.module.css';\n\n/* eslint-disable no-console */\nconst itemGroups = {\n  library: [\n    { label: 'Add to library', onClick: () => console.log('Adding to library') },\n    { label: 'Add to favorites', onClick: () => console.log('Adding to favorites') },\n  ],\n  playback: [\n    { label: 'Play', onClick: () => console.log('Playing') },\n    { label: 'Add to queue', onClick: () => console.log('Adding to queue') },\n  ],\n  share: [\n    { label: 'Share', onClick: () => console.log('Sharing') },\n    { label: 'Copy link', onClick: () => console.log('Copying link') },\n  ],\n} as const;\n/* eslint-enable no-console */\n\ntype MenuKey = keyof typeof itemGroups;\n\nconst demoMenu = Menu.createHandle<MenuKey>();\n\nexport default function MenuDetachedTriggersControlledDemo() {\n  const [open, setOpen] = React.useState(false);\n  const [activeTrigger, setActiveTrigger] = React.useState<string | null>(null);\n\n  const handleOpenChange = (isOpen: boolean, eventDetails: Menu.Root.ChangeEventDetails) => {\n    setOpen(isOpen);\n    if (isOpen) {\n      setActiveTrigger(eventDetails.trigger?.id ?? null);\n    }\n  };\n\n  return (\n    <React.Fragment>\n      <div className={styles.Container}>\n        <Menu.Trigger\n          className={styles.Button}\n          handle={demoMenu}\n          id=\"menu-trigger-1\"\n          payload=\"library\"\n        >\n          Library\n        </Menu.Trigger>\n\n        <Menu.Trigger\n          className={styles.Button}\n          handle={demoMenu}\n          id=\"menu-trigger-2\"\n          payload=\"playback\"\n        >\n          Playback\n        </Menu.Trigger>\n\n        <Menu.Trigger\n          className={styles.Button}\n          handle={demoMenu}\n          id=\"menu-trigger-3\"\n          payload=\"share\"\n        >\n          Share\n        </Menu.Trigger>\n\n        <button\n          type=\"button\"\n          className={styles.Button}\n          onClick={() => {\n            setActiveTrigger('menu-trigger-2');\n            setOpen(true);\n          }}\n        >\n          Open playback (controlled)\n        </button>\n      </div>\n\n      <Menu.Root\n        handle={demoMenu}\n        open={open}\n        triggerId={activeTrigger}\n        onOpenChange={handleOpenChange}\n      >\n        {({ payload }) => (\n          <Menu.Portal>\n            <Menu.Positioner className={styles.Positioner} sideOffset={8}>\n              <Menu.Popup className={styles.Popup}>\n                <Menu.Arrow className={styles.Arrow}>\n                  <ArrowSvg />\n                </Menu.Arrow>\n\n                {payload &&\n                  itemGroups[payload].map((item, index) => (\n                    <Menu.Item key={index} className={styles.Item} onClick={item.onClick}>\n                      {item.label}\n                    </Menu.Item>\n                  ))}\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        )}\n      </Menu.Root>\n    </React.Fragment>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menu/demos/detached-triggers-controlled/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoMenuDetachedTriggersControlled = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menu/demos/detached-triggers-controlled/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\n\nconst itemClass =\n  'flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900';\n\ninterface MenuItemDefinition {\n  label: string;\n  onClick?: () => void;\n}\n\n/* eslint-disable no-console */\nconst MENUS = {\n  library: [\n    { label: 'Add to library', onClick: () => console.log('Adding to library') },\n    { label: 'Add to favorites', onClick: () => console.log('Adding to favorites') },\n  ] as MenuItemDefinition[],\n  playback: [\n    { label: 'Play', onClick: () => console.log('Playing') },\n    { label: 'Add to queue', onClick: () => console.log('Adding to queue') },\n  ] as MenuItemDefinition[],\n  share: [\n    { label: 'Share', onClick: () => console.log('Sharing') },\n    { label: 'Copy link', onClick: () => console.log('Copying') },\n  ] as MenuItemDefinition[],\n};\n/* eslint-enable no-console */\n\ntype MenuKey = keyof typeof MENUS;\n\nconst demoMenu = Menu.createHandle<MenuKey>();\n\nexport default function MenuDetachedTriggersControlledDemo() {\n  const [open, setOpen] = React.useState(false);\n  const [activeTrigger, setActiveTrigger] = React.useState<string | null>(null);\n\n  const handleOpenChange = (isOpen: boolean, eventDetails: Menu.Root.ChangeEventDetails) => {\n    setOpen(isOpen);\n    if (isOpen) {\n      setActiveTrigger(eventDetails.trigger?.id ?? null);\n    }\n  };\n\n  return (\n    <React.Fragment>\n      <div className=\"flex flex-wrap items-center gap-2\">\n        <Menu.Trigger\n          handle={demoMenu}\n          payload={'library' as const}\n          id=\"menu-trigger-1\"\n          className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\"\n        >\n          Library\n        </Menu.Trigger>\n        <Menu.Trigger\n          handle={demoMenu}\n          payload={'playback' as const}\n          id=\"menu-trigger-2\"\n          className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\"\n        >\n          Playback\n        </Menu.Trigger>\n        <Menu.Trigger\n          handle={demoMenu}\n          payload={'share' as const}\n          id=\"menu-trigger-3\"\n          className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\"\n        >\n          Share\n        </Menu.Trigger>\n\n        <button\n          type=\"button\"\n          className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\"\n          onClick={() => {\n            setActiveTrigger('menu-trigger-2');\n            setOpen(true);\n          }}\n        >\n          Open playback (controlled)\n        </button>\n      </div>\n\n      <Menu.Root\n        handle={demoMenu}\n        open={open}\n        triggerId={activeTrigger}\n        onOpenChange={handleOpenChange}\n      >\n        {({ payload }) => (\n          <Menu.Portal>\n            <Menu.Positioner sideOffset={8} className=\"outline-hidden\">\n              <Menu.Popup className=\"origin-[var(--transform-origin)] rounded-md bg-[canvas] py-1 text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 transition-[transform,scale,opacity] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300\">\n                <Menu.Arrow className=\"data-[side=bottom]:top-[-8px] data-[side=left]:right-[-13px] data-[side=left]:rotate-90 data-[side=right]:left-[-13px] data-[side=right]:-rotate-90 data-[side=top]:bottom-[-8px] data-[side=top]:rotate-180\">\n                  <ArrowSvg />\n                </Menu.Arrow>\n\n                {payload &&\n                  MENUS[payload].map((item, index) => (\n                    <Menu.Item key={index} className={itemClass} onClick={item.onClick}>\n                      {item.label}\n                    </Menu.Item>\n                  ))}\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        )}\n      </Menu.Root>\n    </React.Fragment>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className=\"fill-[canvas]\"\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className=\"fill-gray-200 dark:fill-none\"\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className=\"dark:fill-gray-300\"\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menu/demos/detached-triggers-full/css-modules/index.module.css",
    "content": ".Positioner {\n  --easing: cubic-bezier(0.22, 1, 0.36, 1);\n  --animation-duration: 0.35s;\n\n  width: var(--positioner-width);\n  height: var(--positioner-height);\n  max-width: var(--available-width);\n\n  transition-property: top, left, right, bottom, transform;\n  transition-timing-function: var(--easing);\n  transition-duration: var(--animation-duration);\n\n  &[data-instant] {\n    transition: none;\n  }\n}\n\n.Popup {\n  position: relative;\n  width: var(--popup-width, auto);\n  height: var(--popup-height, auto);\n\n  transition-property: width, height, opacity, transform;\n  transition-timing-function: var(--easing);\n  transition-duration: var(--animation-duration);\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  &[data-instant] {\n    transition: none;\n  }\n}\n\n.Viewport {\n  box-sizing: border-box;\n  position: relative;\n  overflow: clip;\n  width: 100%;\n  height: 100%;\n  padding: 0;\n\n  [data-previous],\n  [data-current] {\n    width: var(--popup-width);\n    transform: translateX(0);\n    opacity: 1;\n    transition:\n      transform var(--animation-duration) var(--easing),\n      opacity calc(var(--animation-duration) / 2) var(--easing);\n  }\n\n  &[data-activation-direction~='right'] [data-previous][data-ending-style] {\n    transform: translateX(-50%);\n    opacity: 0;\n  }\n\n  &[data-activation-direction~='right'] [data-current][data-starting-style] {\n    transform: translateX(50%);\n    opacity: 0;\n  }\n\n  &[data-activation-direction~='left'] [data-previous][data-ending-style] {\n    transform: translateX(50%);\n    opacity: 0;\n  }\n\n  &[data-activation-direction~='left'] [data-current][data-starting-style] {\n    transform: translateX(-50%);\n    opacity: 0;\n  }\n}\n\n.Arrow {\n  transition: left calc(var(--animation-duration)) var(--easing);\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menu/demos/detached-triggers-full/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\nimport styles from '../../_index.module.css';\nimport transitionStyles from './index.module.css';\n\ntype MenuContent = {\n  heading: string;\n  groups: string[][];\n};\n\nconst MENUS = {\n  library: {\n    heading: 'Library',\n    groups: [\n      ['Add to library', 'Add to favorites'],\n      ['Create playlist', 'Create station'],\n    ],\n  },\n  playback: {\n    heading: 'Playback',\n    groups: [\n      ['Play now', 'Add to queue'],\n      ['Play next', 'Play last', 'Sleep timer'],\n    ],\n  },\n  share: {\n    heading: 'Share',\n    groups: [\n      ['Copy link', 'Copy embed code'],\n      ['Share to contacts', 'Share to social'],\n    ],\n  },\n} as const satisfies Record<string, MenuContent>;\n\ntype MenuKey = keyof typeof MENUS;\n\nconst demoMenu = Menu.createHandle<MenuKey>();\n\nexport default function MenuDetachedTriggersFullDemo() {\n  return (\n    <div className={styles.Container}>\n      <Menu.Trigger className={styles.Button} handle={demoMenu} payload=\"library\">\n        Library\n      </Menu.Trigger>\n      <Menu.Trigger className={styles.Button} handle={demoMenu} payload=\"playback\">\n        Playback\n      </Menu.Trigger>\n      <Menu.Trigger className={styles.Button} handle={demoMenu} payload=\"share\">\n        Share\n      </Menu.Trigger>\n\n      <Menu.Root handle={demoMenu} modal={false}>\n        {({ payload }) => (\n          <Menu.Portal>\n            <Menu.Positioner\n              sideOffset={8}\n              className={`${styles.Positioner} ${transitionStyles.Positioner}`}\n            >\n              <Menu.Popup className={`${styles.Popup} ${transitionStyles.Popup}`}>\n                <Menu.Arrow className={`${styles.Arrow} ${transitionStyles.Arrow}`}>\n                  <ArrowSvg />\n                </Menu.Arrow>\n\n                <Menu.Viewport className={transitionStyles.Viewport}>\n                  {payload &&\n                    MENUS[payload].groups.map((group, groupIndex) => (\n                      <React.Fragment key={groupIndex}>\n                        <Menu.Group>\n                          {groupIndex === 0 && (\n                            <Menu.GroupLabel className={styles.Label}>\n                              {MENUS[payload].heading}\n                            </Menu.GroupLabel>\n                          )}\n                          {group.map((item) => (\n                            <Menu.Item key={item} className={styles.Item}>\n                              {item}\n                            </Menu.Item>\n                          ))}\n                        </Menu.Group>\n                        {groupIndex < MENUS[payload].groups.length - 1 && (\n                          <Menu.Separator className={styles.Separator} />\n                        )}\n                      </React.Fragment>\n                    ))}\n                </Menu.Viewport>\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        )}\n      </Menu.Root>\n    </div>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menu/demos/detached-triggers-full/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoMenuDetachedTriggersFull = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menu/demos/detached-triggers-full/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\n\ntype MenuContent = {\n  heading: string;\n  groups: string[][];\n};\n\nconst MENUS = {\n  library: {\n    heading: 'Library',\n    groups: [\n      ['Add to library', 'Add to favorites'],\n      ['Create playlist', 'Create station'],\n    ],\n  },\n  playback: {\n    heading: 'Playback',\n    groups: [\n      ['Play now', 'Add to queue'],\n      ['Play next', 'Play last', 'Sleep timer'],\n    ],\n  },\n  share: {\n    heading: 'Share',\n    groups: [\n      ['Copy link', 'Copy embed code'],\n      ['Share to contacts', 'Share to social'],\n    ],\n  },\n} as const satisfies Record<string, MenuContent>;\n\ntype MenuKey = keyof typeof MENUS;\n\nconst demoMenu = Menu.createHandle<MenuKey>();\n\nconst triggerClass = `\n  flex h-10 items-center justify-center\n  rounded-md border border-gray-200 bg-gray-50\n  px-3.5 text-base font-normal text-gray-900\n  select-none\n  hover:bg-gray-100 active:bg-gray-100 data-popup-open:bg-gray-100\n  focus-visible:outline focus-visible:outline-2\n  focus-visible:-outline-offset-1 focus-visible:outline-blue-800\n`;\n\nconst itemClass = `\n  flex cursor-default py-2 pr-8 pl-4\n  text-sm leading-4 outline-none select-none\n  data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50\n  data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1\n  data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1]\n  data-[highlighted]:before:rounded-sm data-[highlighted]:before:bg-gray-900\n`;\n\nexport default function MenuDetachedTriggersFullDemo() {\n  return (\n    <div className=\"flex flex-wrap items-center gap-2\">\n      <Menu.Trigger handle={demoMenu} payload={'library' as const} className={triggerClass}>\n        Library\n      </Menu.Trigger>\n      <Menu.Trigger handle={demoMenu} payload={'playback' as const} className={triggerClass}>\n        Playback\n      </Menu.Trigger>\n      <Menu.Trigger handle={demoMenu} payload={'share' as const} className={triggerClass}>\n        Share\n      </Menu.Trigger>\n\n      <Menu.Root handle={demoMenu} modal={false}>\n        {({ payload }) => (\n          <Menu.Portal>\n            <Menu.Positioner\n              sideOffset={8}\n              className={`\n                outline-none\n                h-[var(--positioner-height)] w-[var(--positioner-width)] max-w-[var(--available-width)]\n                transition-[top,left,right,bottom,transform]\n                duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)]\n                data-instant:transition-none\n              `}\n            >\n              <Menu.Popup\n                className={`\n                  relative h-[var(--popup-height,auto)] w-[var(--popup-width,auto)] py-1\n                  origin-[var(--transform-origin)] rounded-md\n                  bg-[canvas] text-gray-900 shadow-lg shadow-gray-200\n                  outline outline-1 outline-gray-200\n                  transition-[width,height,opacity,scale]\n                  duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)]\n                  data-[starting-style]:scale-90 data-[starting-style]:opacity-0\n                  data-[ending-style]:scale-90 data-[ending-style]:opacity-0\n                  data-instant:transition-none\n                  dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300\n                `}\n              >\n                <Menu.Arrow\n                  className={`\n                    flex\n                    transition-[left]\n                    duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)]\n                    data-[side=bottom]:top-[-8px]\n                    data-[side=left]:right-[-13px] data-[side=left]:rotate-90\n                    data-[side=right]:left-[-13px] data-[side=right]:-rotate-90\n                    data-[side=top]:bottom-[-8px] data-[side=top]:rotate-180\n                  `}\n                >\n                  <ArrowSvg />\n                </Menu.Arrow>\n\n                <Menu.Viewport\n                  className={`\n                    relative h-full w-full box-border overflow-clip\n                    p-0\n                    [&_[data-current]]:w-[var(--popup-width)]\n                    [&_[data-current]]:translate-x-0 [&_[data-current]]:opacity-100\n                    [&_[data-current]]:transition-[translate,opacity]\n                    [&_[data-current]]:duration-[350ms,175ms]\n                    [&_[data-current]]:ease-[cubic-bezier(0.22,1,0.36,1)]\n                    data-[activation-direction~='left']:[&_[data-current][data-starting-style]]:-translate-x-1/2\n                    data-[activation-direction~='left']:[&_[data-current][data-starting-style]]:opacity-0\n                    data-[activation-direction~='right']:[&_[data-current][data-starting-style]]:translate-x-1/2\n                    data-[activation-direction~='right']:[&_[data-current][data-starting-style]]:opacity-0\n                    [&_[data-previous]]:w-[var(--popup-width)]\n                    [&_[data-previous]]:translate-x-0 [&_[data-previous]]:opacity-100\n                    [&_[data-previous]]:transition-[translate,opacity]\n                    [&_[data-previous]]:duration-[350ms,175ms]\n                    [&_[data-previous]]:ease-[cubic-bezier(0.22,1,0.36,1)]\n                    data-[activation-direction~='left']:[&_[data-previous][data-ending-style]]:translate-x-1/2\n                    data-[activation-direction~='left']:[&_[data-previous][data-ending-style]]:opacity-0\n                    data-[activation-direction~='right']:[&_[data-previous][data-ending-style]]:-translate-x-1/2\n                    data-[activation-direction~='right']:[&_[data-previous][data-ending-style]]:opacity-0\n                  `}\n                >\n                  {payload &&\n                    MENUS[payload].groups.map((group, groupIndex) => (\n                      <React.Fragment key={groupIndex}>\n                        <Menu.Group>\n                          {groupIndex === 0 && (\n                            <Menu.GroupLabel className=\"px-4 py-2 text-xs tracking-[0.05em] text-gray-500 uppercase\">\n                              {MENUS[payload].heading}\n                            </Menu.GroupLabel>\n                          )}\n                          {group.map((item) => (\n                            <Menu.Item key={item} className={itemClass}>\n                              {item}\n                            </Menu.Item>\n                          ))}\n                        </Menu.Group>\n                        {groupIndex < MENUS[payload].groups.length - 1 && (\n                          <Menu.Separator className=\"mx-4 my-1.5 h-px bg-gray-200\" />\n                        )}\n                      </React.Fragment>\n                    ))}\n                </Menu.Viewport>\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        )}\n      </Menu.Root>\n    </div>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className=\"fill-[canvas]\"\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className=\"fill-gray-200 dark:fill-none\"\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className=\"dark:fill-gray-300\"\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menu/demos/detached-triggers-simple/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\nimport styles from '../../_index.module.css';\n\nconst demoMenu = Menu.createHandle();\n\nexport default function MenuDetachedTriggersSimpleDemo() {\n  return (\n    <React.Fragment>\n      <Menu.Trigger className={styles.IconButton} handle={demoMenu} aria-label=\"Project actions\">\n        <DotsIcon className={styles.Icon} />\n      </Menu.Trigger>\n\n      <Menu.Root handle={demoMenu}>\n        <Menu.Portal>\n          <Menu.Positioner sideOffset={8} className={styles.Positioner}>\n            <Menu.Popup className={styles.Popup}>\n              <Menu.Arrow className={styles.Arrow}>\n                <ArrowSvg />\n              </Menu.Arrow>\n\n              <Menu.Item className={styles.Item}>Rename</Menu.Item>\n              <Menu.Item className={styles.Item}>Duplicate</Menu.Item>\n              <Menu.Item className={styles.Item}>Move to folder</Menu.Item>\n              <Menu.Separator className={styles.Separator} />\n              <Menu.Item className={styles.Item}>Archive</Menu.Item>\n              <Menu.Item className={styles.Item}>Delete</Menu.Item>\n            </Menu.Popup>\n          </Menu.Positioner>\n        </Menu.Portal>\n      </Menu.Root>\n    </React.Fragment>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nfunction DotsIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"20\"\n      height=\"20\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth={1.5}\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <circle cx=\"5\" cy=\"12\" r=\"1\" />\n      <circle cx=\"12\" cy=\"12\" r=\"1\" />\n      <circle cx=\"19\" cy=\"12\" r=\"1\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menu/demos/detached-triggers-simple/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoMenuDetachedTriggersSimple = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menu/demos/detached-triggers-simple/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\n\nconst demoMenu = Menu.createHandle();\nconst popupClass =\n  'origin-[var(--transform-origin)] rounded-md bg-[canvas] py-1 text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 transition-[transform,scale,opacity] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300';\nconst itemClass =\n  'flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900';\n\nexport default function MenuDetachedTriggersSimpleDemo() {\n  return (\n    <React.Fragment>\n      <Menu.Trigger\n        handle={demoMenu}\n        aria-label=\"Project actions\"\n        className=\"flex size-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100 data-[popup-open]:bg-gray-100 dark:border-gray-300 dark:bg-gray-100 dark:text-gray-900\"\n      >\n        <DotsIcon />\n      </Menu.Trigger>\n\n      <Menu.Root handle={demoMenu}>\n        <Menu.Portal>\n          <Menu.Positioner sideOffset={8} className=\"outline-hidden\">\n            <Menu.Popup className={popupClass}>\n              <Menu.Arrow className=\"data-[side=bottom]:top-[-8px] data-[side=left]:right-[-13px] data-[side=left]:rotate-90 data-[side=right]:left-[-13px] data-[side=right]:-rotate-90 data-[side=top]:bottom-[-8px] data-[side=top]:rotate-180\">\n                <ArrowSvg />\n              </Menu.Arrow>\n              <Menu.Item className={itemClass}>Rename</Menu.Item>\n              <Menu.Item className={itemClass}>Duplicate</Menu.Item>\n              <Menu.Item className={itemClass}>Move to folder</Menu.Item>\n              <Menu.Separator className=\"mx-4 my-1.5 h-px bg-gray-200\" />\n              <Menu.Item className={itemClass}>Archive</Menu.Item>\n              <Menu.Item className={`${itemClass} text-red-600`}>Delete</Menu.Item>\n            </Menu.Popup>\n          </Menu.Positioner>\n        </Menu.Portal>\n      </Menu.Root>\n    </React.Fragment>\n  );\n}\n\nexport function ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className=\"fill-[canvas]\"\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className=\"fill-gray-200 dark:fill-none\"\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className=\"dark:fill-gray-300\"\n      />\n    </svg>\n  );\n}\n\nexport function DotsIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"20\"\n      height=\"20\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth={1.5}\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <circle cx=\"5\" cy=\"12\" r=\"1\" />\n      <circle cx=\"12\" cy=\"12\" r=\"1\" />\n      <circle cx=\"19\" cy=\"12\" r=\"1\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menu/demos/group-labels/css-modules/index.module.css",
    "content": ".Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: 0.375rem;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.ButtonIcon {\n  margin-right: -0.25rem;\n}\n\n.Positioner {\n  outline: 0;\n}\n\n.Popup {\n  box-sizing: border-box;\n  padding-block: 0.25rem;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Arrow {\n  display: flex;\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n\n.CheckboxItem,\n.RadioItem {\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  padding-block: 0.5rem;\n  padding-left: 0.625rem;\n  padding-right: 2rem;\n  font-size: 0.875rem;\n  line-height: 1rem;\n  display: grid;\n  gap: 0.5rem;\n  align-items: center;\n  grid-template-columns: 0.75rem 1fr;\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.25rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.CheckboxItemIndicator,\n.RadioItemIndicator {\n  grid-column-start: 1;\n}\n\n.CheckboxItemIndicatorIcon,\n.RadioItemIndicatorIcon {\n  display: block;\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.CheckboxItemText,\n.RadioItemText {\n  grid-column-start: 2;\n}\n\n.Separator {\n  margin-block: 0.375rem;\n  margin-left: 1.875rem;\n  margin-right: 1rem;\n  height: 1px;\n  background-color: var(--color-gray-200);\n}\n\n.GroupLabel {\n  cursor: default;\n  user-select: none;\n  padding-block: 0.5rem;\n  padding-left: 1.875rem;\n  padding-right: 2rem;\n  font-size: 0.875rem;\n  line-height: 1rem;\n  color: var(--color-gray-600);\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menu/demos/group-labels/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\nimport styles from './index.module.css';\n\nexport default function ExampleMenu() {\n  const [value, setValue] = React.useState('date');\n  const [showMinimap, setShowMinimap] = React.useState(true);\n  const [showSearch, setShowSearch] = React.useState(true);\n  const [showSidebar, setShowSidebar] = React.useState(false);\n\n  return (\n    <Menu.Root>\n      <Menu.Trigger className={styles.Button}>\n        View <ChevronDownIcon className={styles.ButtonIcon} />\n      </Menu.Trigger>\n      <Menu.Portal>\n        <Menu.Positioner className={styles.Positioner} sideOffset={8}>\n          <Menu.Popup className={styles.Popup}>\n            <Menu.Arrow className={styles.Arrow}>\n              <ArrowSvg />\n            </Menu.Arrow>\n\n            <Menu.Group>\n              <Menu.GroupLabel className={styles.GroupLabel}>Sort</Menu.GroupLabel>\n              <Menu.RadioGroup value={value} onValueChange={setValue}>\n                <Menu.RadioItem className={styles.RadioItem} value=\"date\">\n                  <Menu.RadioItemIndicator className={styles.RadioItemIndicator}>\n                    <CheckIcon className={styles.RadioItemIndicatorIcon} />\n                  </Menu.RadioItemIndicator>\n                  <span className={styles.RadioItemText}>Date</span>\n                </Menu.RadioItem>\n                <Menu.RadioItem className={styles.RadioItem} value=\"name\">\n                  <Menu.RadioItemIndicator className={styles.RadioItemIndicator}>\n                    <CheckIcon className={styles.RadioItemIndicatorIcon} />\n                  </Menu.RadioItemIndicator>\n                  <span className={styles.RadioItemText}>Name</span>\n                </Menu.RadioItem>\n                <Menu.RadioItem className={styles.RadioItem} value=\"type\">\n                  <Menu.RadioItemIndicator className={styles.RadioItemIndicator}>\n                    <CheckIcon className={styles.RadioItemIndicatorIcon} />\n                  </Menu.RadioItemIndicator>\n                  <span className={styles.RadioItemText}>Type</span>\n                </Menu.RadioItem>\n              </Menu.RadioGroup>\n            </Menu.Group>\n\n            <Menu.Separator className={styles.Separator} />\n\n            <Menu.Group>\n              <Menu.GroupLabel className={styles.GroupLabel}>Workspace</Menu.GroupLabel>\n              <Menu.CheckboxItem\n                checked={showMinimap}\n                onCheckedChange={setShowMinimap}\n                className={styles.CheckboxItem}\n              >\n                <Menu.CheckboxItemIndicator className={styles.CheckboxItemIndicator}>\n                  <CheckIcon className={styles.CheckboxItemIndicatorIcon} />\n                </Menu.CheckboxItemIndicator>\n                <span className={styles.CheckboxItemText}>Minimap</span>\n              </Menu.CheckboxItem>\n              <Menu.CheckboxItem\n                checked={showSearch}\n                onCheckedChange={setShowSearch}\n                className={styles.CheckboxItem}\n              >\n                <Menu.CheckboxItemIndicator className={styles.CheckboxItemIndicator}>\n                  <CheckIcon className={styles.CheckboxItemIndicatorIcon} />\n                </Menu.CheckboxItemIndicator>\n                <span className={styles.CheckboxItemText}>Search</span>\n              </Menu.CheckboxItem>\n              <Menu.CheckboxItem\n                checked={showSidebar}\n                onCheckedChange={setShowSidebar}\n                className={styles.CheckboxItem}\n              >\n                <Menu.CheckboxItemIndicator className={styles.CheckboxItemIndicator}>\n                  <CheckIcon className={styles.CheckboxItemIndicatorIcon} />\n                </Menu.CheckboxItemIndicator>\n                <span className={styles.CheckboxItemText}>Sidebar</span>\n              </Menu.CheckboxItem>\n            </Menu.Group>\n          </Menu.Popup>\n        </Menu.Positioner>\n      </Menu.Portal>\n    </Menu.Root>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nfunction ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M1 3.5L5 7.5L9 3.5\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menu/demos/group-labels/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoMenuGroupLabels = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menu/demos/group-labels/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\n\nexport default function ExampleMenu() {\n  const [value, setValue] = React.useState('date');\n  const [showMinimap, setShowMinimap] = React.useState(true);\n  const [showSearch, setShowSearch] = React.useState(true);\n  const [showSidebar, setShowSidebar] = React.useState(false);\n\n  return (\n    <Menu.Root>\n      <Menu.Trigger className=\"flex h-10 items-center justify-center gap-1.5 rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100 data-[popup-open]:bg-gray-100\">\n        View <ChevronDownIcon className=\"-mr-1\" />\n      </Menu.Trigger>\n      <Menu.Portal>\n        <Menu.Positioner className=\"outline-hidden\" sideOffset={8}>\n          <Menu.Popup className=\"origin-[var(--transform-origin)] rounded-md bg-[canvas] py-1 text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 transition-[transform,scale,opacity] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300\">\n            <Menu.Arrow className=\"data-[side=bottom]:top-[-8px] data-[side=left]:right-[-13px] data-[side=left]:rotate-90 data-[side=right]:left-[-13px] data-[side=right]:-rotate-90 data-[side=top]:bottom-[-8px] data-[side=top]:rotate-180\">\n              <ArrowSvg />\n            </Menu.Arrow>\n\n            <Menu.Group>\n              <Menu.GroupLabel className=\"cursor-default py-2 pr-8 pl-7.5 text-sm leading-4 text-gray-600 select-none\">\n                Sort\n              </Menu.GroupLabel>\n              <Menu.RadioGroup value={value} onValueChange={setValue}>\n                <Menu.RadioItem\n                  value=\"date\"\n                  className=\"grid cursor-default grid-cols-[0.75rem_1fr] items-center gap-2 py-2 pr-8 pl-2.5 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\"\n                >\n                  <Menu.RadioItemIndicator className=\"col-start-1\">\n                    <CheckIcon className=\"size-3\" />\n                  </Menu.RadioItemIndicator>\n                  <span className=\"col-start-2\">Date</span>\n                </Menu.RadioItem>\n                <Menu.RadioItem\n                  value=\"name\"\n                  className=\"grid cursor-default grid-cols-[0.75rem_1fr] items-center gap-2 py-2 pr-8 pl-2.5 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\"\n                >\n                  <Menu.RadioItemIndicator className=\"col-start-1\">\n                    <CheckIcon className=\"size-3\" />\n                  </Menu.RadioItemIndicator>\n                  <span className=\"col-start-2\">Name</span>\n                </Menu.RadioItem>\n                <Menu.RadioItem\n                  value=\"type\"\n                  className=\"grid cursor-default grid-cols-[0.75rem_1fr] items-center gap-2 py-2 pr-8 pl-2.5 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\"\n                >\n                  <Menu.RadioItemIndicator className=\"col-start-1\">\n                    <CheckIcon className=\"size-3\" />\n                  </Menu.RadioItemIndicator>\n                  <span className=\"col-start-2\">Type</span>\n                </Menu.RadioItem>\n              </Menu.RadioGroup>\n            </Menu.Group>\n\n            <Menu.Separator className=\"my-1.5 mr-4 ml-7.5 h-px bg-gray-200\" />\n\n            <Menu.Group>\n              <Menu.GroupLabel className=\"cursor-default py-2 pr-8 pl-7.5 text-sm leading-4 text-gray-600 select-none\">\n                Workspace\n              </Menu.GroupLabel>\n              <Menu.CheckboxItem\n                checked={showMinimap}\n                onCheckedChange={setShowMinimap}\n                className=\"grid cursor-default grid-cols-[0.75rem_1fr] items-center gap-2 py-2 pr-8 pl-2.5 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\"\n              >\n                <Menu.CheckboxItemIndicator className=\"col-start-1\">\n                  <CheckIcon className=\"size-3\" />\n                </Menu.CheckboxItemIndicator>\n                <span className=\"col-start-2\">Minimap</span>\n              </Menu.CheckboxItem>\n              <Menu.CheckboxItem\n                checked={showSearch}\n                onCheckedChange={setShowSearch}\n                className=\"grid cursor-default grid-cols-[0.75rem_1fr] items-center gap-2 py-2 pr-8 pl-2.5 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\"\n              >\n                <Menu.CheckboxItemIndicator className=\"col-start-1\">\n                  <CheckIcon className=\"size-3\" />\n                </Menu.CheckboxItemIndicator>\n                <span className=\"col-start-2\">Search</span>\n              </Menu.CheckboxItem>\n              <Menu.CheckboxItem\n                checked={showSidebar}\n                onCheckedChange={setShowSidebar}\n                className=\"grid cursor-default grid-cols-[0.75rem_1fr] items-center gap-2 py-2 pr-8 pl-2.5 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\"\n              >\n                <Menu.CheckboxItemIndicator className=\"col-start-1\">\n                  <CheckIcon className=\"size-3\" />\n                </Menu.CheckboxItemIndicator>\n                <span className=\"col-start-2\">Sidebar</span>\n              </Menu.CheckboxItem>\n            </Menu.Group>\n          </Menu.Popup>\n        </Menu.Positioner>\n      </Menu.Portal>\n    </Menu.Root>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className=\"fill-[canvas]\"\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className=\"fill-gray-200 dark:fill-none\"\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className=\"dark:fill-gray-300\"\n      />\n    </svg>\n  );\n}\n\nfunction ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M1 3.5L5 7.5L9 3.5\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menu/demos/hero/css-modules/index.module.css",
    "content": ".Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: 0.375rem;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.ButtonIcon {\n  margin-right: -0.25rem;\n}\n\n.Positioner {\n  outline: 0;\n}\n\n.Popup {\n  box-sizing: border-box;\n  padding-block: 0.25rem;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Arrow {\n  display: flex;\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n\n.Item {\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  padding-block: 0.5rem;\n  padding-left: 1rem;\n  padding-right: 2rem;\n  display: flex;\n  font-size: 0.875rem;\n  line-height: 1rem;\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.25rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.Separator {\n  margin: 0.375rem 1rem;\n  height: 1px;\n  background-color: var(--color-gray-200);\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menu/demos/hero/css-modules/index.tsx",
    "content": "import * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\nimport styles from './index.module.css';\n\nexport default function ExampleMenu() {\n  return (\n    <Menu.Root>\n      <Menu.Trigger className={styles.Button}>\n        Song <ChevronDownIcon className={styles.ButtonIcon} />\n      </Menu.Trigger>\n      <Menu.Portal>\n        <Menu.Positioner className={styles.Positioner} sideOffset={8}>\n          <Menu.Popup className={styles.Popup}>\n            <Menu.Arrow className={styles.Arrow}>\n              <ArrowSvg />\n            </Menu.Arrow>\n            <Menu.Item className={styles.Item}>Add to Library</Menu.Item>\n            <Menu.Item className={styles.Item}>Add to Playlist</Menu.Item>\n            <Menu.Separator className={styles.Separator} />\n            <Menu.Item className={styles.Item}>Play Next</Menu.Item>\n            <Menu.Item className={styles.Item}>Play Last</Menu.Item>\n            <Menu.Separator className={styles.Separator} />\n            <Menu.Item className={styles.Item}>Favorite</Menu.Item>\n            <Menu.Item className={styles.Item}>Share</Menu.Item>\n          </Menu.Popup>\n        </Menu.Positioner>\n      </Menu.Portal>\n    </Menu.Root>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nfunction ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M1 3.5L5 7.5L9 3.5\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menu/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoMenuHero = createDemoWithVariants(import.meta.url, { CssModules, Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menu/demos/hero/tailwind/index.tsx",
    "content": "import * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\n\nexport default function ExampleMenu() {\n  return (\n    <Menu.Root>\n      <Menu.Trigger className=\"flex h-10 items-center justify-center gap-1.5 rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100 data-[popup-open]:bg-gray-100\">\n        Song <ChevronDownIcon className=\"-mr-1\" />\n      </Menu.Trigger>\n      <Menu.Portal>\n        <Menu.Positioner className=\"outline-hidden\" sideOffset={8}>\n          <Menu.Popup className=\"origin-[var(--transform-origin)] rounded-md bg-[canvas] py-1 text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 transition-[transform,scale,opacity] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300\">\n            <Menu.Arrow className=\"data-[side=bottom]:top-[-8px] data-[side=left]:right-[-13px] data-[side=left]:rotate-90 data-[side=right]:left-[-13px] data-[side=right]:-rotate-90 data-[side=top]:bottom-[-8px] data-[side=top]:rotate-180\">\n              <ArrowSvg />\n            </Menu.Arrow>\n            <Menu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\">\n              Add to Library\n            </Menu.Item>\n            <Menu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\">\n              Add to Playlist\n            </Menu.Item>\n            <Menu.Separator className=\"mx-4 my-1.5 h-px bg-gray-200\" />\n            <Menu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\">\n              Play Next\n            </Menu.Item>\n            <Menu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\">\n              Play Last\n            </Menu.Item>\n            <Menu.Separator className=\"mx-4 my-1.5 h-px bg-gray-200\" />\n            <Menu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\">\n              Favorite\n            </Menu.Item>\n            <Menu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\">\n              Share\n            </Menu.Item>\n          </Menu.Popup>\n        </Menu.Positioner>\n      </Menu.Portal>\n    </Menu.Root>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className=\"fill-[canvas]\"\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className=\"fill-gray-200 dark:fill-none\"\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className=\"dark:fill-gray-300\"\n      />\n    </svg>\n  );\n}\n\nfunction ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M1 3.5L5 7.5L9 3.5\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menu/demos/open-on-hover/css-modules/index.module.css",
    "content": ".Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: 0.375rem;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.ButtonIcon {\n  margin-right: -0.25rem;\n}\n\n.Positioner {\n  outline: 0;\n}\n\n.Popup {\n  box-sizing: border-box;\n  padding-block: 0.25rem;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Arrow {\n  display: flex;\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n\n.Item {\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  padding-block: 0.5rem;\n  padding-left: 1rem;\n  padding-right: 2rem;\n  display: flex;\n  font-size: 0.875rem;\n  line-height: 1rem;\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.25rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.Separator {\n  margin: 0.375rem 1rem;\n  height: 1px;\n  background-color: var(--color-gray-200);\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menu/demos/open-on-hover/css-modules/index.tsx",
    "content": "import * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\nimport styles from './index.module.css';\n\nexport default function ExampleMenu() {\n  return (\n    <Menu.Root>\n      <Menu.Trigger openOnHover className={styles.Button}>\n        Add to playlist <ChevronDownIcon className={styles.ButtonIcon} />\n      </Menu.Trigger>\n      <Menu.Portal>\n        <Menu.Positioner className={styles.Positioner} sideOffset={8}>\n          <Menu.Popup className={styles.Popup}>\n            <Menu.Arrow className={styles.Arrow}>\n              <ArrowSvg />\n            </Menu.Arrow>\n            <Menu.Item className={styles.Item}>Get Up!</Menu.Item>\n            <Menu.Item className={styles.Item}>Inside Out</Menu.Item>\n            <Menu.Item className={styles.Item}>Night Beats</Menu.Item>\n            <Menu.Separator className={styles.Separator} />\n            <Menu.Item className={styles.Item}>New playlist…</Menu.Item>\n          </Menu.Popup>\n        </Menu.Positioner>\n      </Menu.Portal>\n    </Menu.Root>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nfunction ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M1 3.5L5 7.5L9 3.5\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menu/demos/open-on-hover/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoMenuOpenOnHover = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menu/demos/open-on-hover/tailwind/index.tsx",
    "content": "import * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\n\nexport default function ExampleMenu() {\n  return (\n    <Menu.Root>\n      <Menu.Trigger\n        openOnHover\n        className=\"flex h-10 items-center justify-center gap-1.5 rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100 data-[popup-open]:bg-gray-100\"\n      >\n        Add to playlist <ChevronDownIcon className=\"-mr-1\" />\n      </Menu.Trigger>\n      <Menu.Portal>\n        <Menu.Positioner className=\"outline-hidden\" sideOffset={8}>\n          <Menu.Popup className=\"origin-[var(--transform-origin)] rounded-md bg-[canvas] py-1 text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 transition-[transform,scale,opacity] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300\">\n            <Menu.Arrow className=\"data-[side=bottom]:top-[-8px] data-[side=left]:right-[-13px] data-[side=left]:rotate-90 data-[side=right]:left-[-13px] data-[side=right]:-rotate-90 data-[side=top]:bottom-[-8px] data-[side=top]:rotate-180\">\n              <ArrowSvg />\n            </Menu.Arrow>\n            <Menu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\">\n              Get Up!\n            </Menu.Item>\n            <Menu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\">\n              Inside Out\n            </Menu.Item>\n            <Menu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\">\n              Night Beats\n            </Menu.Item>\n            <Menu.Separator className=\"mx-4 my-1.5 h-px bg-gray-200\" />\n            <Menu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\">\n              New playlist…\n            </Menu.Item>\n          </Menu.Popup>\n        </Menu.Positioner>\n      </Menu.Portal>\n    </Menu.Root>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className=\"fill-[canvas]\"\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className=\"fill-gray-200 dark:fill-none\"\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className=\"dark:fill-gray-300\"\n      />\n    </svg>\n  );\n}\n\nfunction ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M1 3.5L5 7.5L9 3.5\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menu/demos/radio-items/css-modules/index.module.css",
    "content": ".Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: 0.375rem;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.ButtonIcon {\n  margin-right: -0.25rem;\n}\n\n.Positioner {\n  outline: 0;\n}\n\n.Popup {\n  box-sizing: border-box;\n  padding-block: 0.25rem;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Arrow {\n  display: flex;\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n\n.RadioItem {\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  padding-block: 0.5rem;\n  padding-left: 0.625rem;\n  padding-right: 2rem;\n  font-size: 0.875rem;\n  line-height: 1rem;\n  display: grid;\n  gap: 0.5rem;\n  align-items: center;\n  grid-template-columns: 0.75rem 1fr;\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.25rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.RadioItemIndicator {\n  grid-column-start: 1;\n}\n\n.RadioItemIndicatorIcon {\n  display: block;\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.RadioItemText {\n  grid-column-start: 2;\n}\n\n.Separator {\n  margin: 0.375rem 1rem;\n  height: 1px;\n  background-color: var(--color-gray-200);\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menu/demos/radio-items/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\nimport styles from './index.module.css';\n\nexport default function ExampleMenu() {\n  const [value, setValue] = React.useState('date');\n  return (\n    <Menu.Root>\n      <Menu.Trigger className={styles.Button}>\n        Sort <ChevronDownIcon className={styles.ButtonIcon} />\n      </Menu.Trigger>\n      <Menu.Portal>\n        <Menu.Positioner className={styles.Positioner} sideOffset={8}>\n          <Menu.Popup className={styles.Popup}>\n            <Menu.Arrow className={styles.Arrow}>\n              <ArrowSvg />\n            </Menu.Arrow>\n            <Menu.RadioGroup value={value} onValueChange={setValue}>\n              <Menu.RadioItem className={styles.RadioItem} value=\"date\">\n                <Menu.RadioItemIndicator className={styles.RadioItemIndicator}>\n                  <CheckIcon className={styles.RadioItemIndicatorIcon} />\n                </Menu.RadioItemIndicator>\n                <span className={styles.RadioItemText}>Date</span>\n              </Menu.RadioItem>\n              <Menu.RadioItem className={styles.RadioItem} value=\"name\">\n                <Menu.RadioItemIndicator className={styles.RadioItemIndicator}>\n                  <CheckIcon className={styles.RadioItemIndicatorIcon} />\n                </Menu.RadioItemIndicator>\n                <span className={styles.RadioItemText}>Name</span>\n              </Menu.RadioItem>\n              <Menu.RadioItem className={styles.RadioItem} value=\"type\">\n                <Menu.RadioItemIndicator className={styles.RadioItemIndicator}>\n                  <CheckIcon className={styles.RadioItemIndicatorIcon} />\n                </Menu.RadioItemIndicator>\n                <span className={styles.RadioItemText}>Type</span>\n              </Menu.RadioItem>\n            </Menu.RadioGroup>\n          </Menu.Popup>\n        </Menu.Positioner>\n      </Menu.Portal>\n    </Menu.Root>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nfunction ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M1 3.5L5 7.5L9 3.5\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menu/demos/radio-items/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoMenuRadioItems = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menu/demos/radio-items/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\n\nexport default function ExampleMenu() {\n  const [value, setValue] = React.useState('date');\n  return (\n    <Menu.Root>\n      <Menu.Trigger className=\"flex h-10 items-center justify-center gap-1.5 rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100 data-[popup-open]:bg-gray-100\">\n        Sort <ChevronDownIcon className=\"-mr-1\" />\n      </Menu.Trigger>\n      <Menu.Portal>\n        <Menu.Positioner className=\"outline-hidden\" sideOffset={8}>\n          <Menu.Popup className=\"origin-[var(--transform-origin)] rounded-md bg-[canvas] py-1 text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 transition-[transform,scale,opacity] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300\">\n            <Menu.Arrow className=\"data-[side=bottom]:top-[-8px] data-[side=left]:right-[-13px] data-[side=left]:rotate-90 data-[side=right]:left-[-13px] data-[side=right]:-rotate-90 data-[side=top]:bottom-[-8px] data-[side=top]:rotate-180\">\n              <ArrowSvg />\n            </Menu.Arrow>\n            <Menu.RadioGroup value={value} onValueChange={setValue}>\n              <Menu.RadioItem\n                value=\"date\"\n                className=\"grid cursor-default grid-cols-[0.75rem_1fr] items-center gap-2 py-2 pr-8 pl-2.5 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\"\n              >\n                <Menu.RadioItemIndicator className=\"col-start-1\">\n                  <CheckIcon className=\"size-3\" />\n                </Menu.RadioItemIndicator>\n                <span className=\"col-start-2\">Date</span>\n              </Menu.RadioItem>\n              <Menu.RadioItem\n                value=\"name\"\n                className=\"grid cursor-default grid-cols-[0.75rem_1fr] items-center gap-2 py-2 pr-8 pl-2.5 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\"\n              >\n                <Menu.RadioItemIndicator className=\"col-start-1\">\n                  <CheckIcon className=\"size-3\" />\n                </Menu.RadioItemIndicator>\n                <span className=\"col-start-2\">Name</span>\n              </Menu.RadioItem>\n              <Menu.RadioItem\n                value=\"type\"\n                className=\"grid cursor-default grid-cols-[0.75rem_1fr] items-center gap-2 py-2 pr-8 pl-2.5 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\"\n              >\n                <Menu.RadioItemIndicator className=\"col-start-1\">\n                  <CheckIcon className=\"size-3\" />\n                </Menu.RadioItemIndicator>\n                <span className=\"col-start-2\">Type</span>\n              </Menu.RadioItem>\n            </Menu.RadioGroup>\n          </Menu.Popup>\n        </Menu.Positioner>\n      </Menu.Portal>\n    </Menu.Root>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className=\"fill-[canvas]\"\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className=\"fill-gray-200 dark:fill-none\"\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className=\"dark:fill-gray-300\"\n      />\n    </svg>\n  );\n}\n\nfunction ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M1 3.5L5 7.5L9 3.5\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menu/demos/submenu/css-modules/index.module.css",
    "content": ".Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: 0.375rem;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.ButtonIcon {\n  margin-right: -0.25rem;\n}\n\n.Positioner {\n  outline: 0;\n}\n\n.Popup {\n  box-sizing: border-box;\n  padding-block: 0.25rem;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Arrow {\n  display: flex;\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n\n.Item,\n.SubmenuTrigger {\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  padding-block: 0.5rem;\n  padding-left: 1rem;\n  padding-right: 2rem;\n  display: flex;\n  font-size: 0.875rem;\n  line-height: 1rem;\n\n  &[data-popup-open] {\n    z-index: 0;\n    position: relative;\n  }\n\n  &[data-popup-open]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.25rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-100);\n  }\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.25rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.SubmenuTrigger {\n  align-items: center;\n  justify-content: space-between;\n  gap: 1rem;\n  padding-right: 1rem;\n}\n\n.Separator {\n  margin: 0.375rem 1rem;\n  height: 1px;\n  background-color: var(--color-gray-200);\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menu/demos/submenu/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\nimport styles from './index.module.css';\n\nexport default function ExampleMenu() {\n  return (\n    <Menu.Root>\n      <Menu.Trigger className={styles.Button}>\n        Song <ChevronDownIcon className={styles.ButtonIcon} />\n      </Menu.Trigger>\n      <Menu.Portal>\n        <Menu.Positioner className={styles.Positioner} sideOffset={8}>\n          <Menu.Popup className={styles.Popup}>\n            <Menu.Arrow className={styles.Arrow}>\n              <ArrowSvg />\n            </Menu.Arrow>\n            <Menu.Item className={styles.Item}>Add to Library</Menu.Item>\n\n            <Menu.SubmenuRoot>\n              <Menu.SubmenuTrigger className={styles.SubmenuTrigger}>\n                Add to Playlist\n                <ChevronRightIcon />\n              </Menu.SubmenuTrigger>\n              <Menu.Portal>\n                <Menu.Positioner\n                  className={styles.Positioner}\n                  sideOffset={getOffset}\n                  alignOffset={getOffset}\n                >\n                  <Menu.Popup className={styles.Popup}>\n                    <Menu.Item className={styles.Item}>Get Up!</Menu.Item>\n                    <Menu.Item className={styles.Item}>Inside Out</Menu.Item>\n                    <Menu.Item className={styles.Item}>Night Beats</Menu.Item>\n                    <Menu.Separator className={styles.Separator} />\n                    <Menu.Item className={styles.Item}>New playlist…</Menu.Item>\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            </Menu.SubmenuRoot>\n\n            <Menu.Separator className={styles.Separator} />\n            <Menu.Item className={styles.Item}>Play Next</Menu.Item>\n            <Menu.Item className={styles.Item}>Play Last</Menu.Item>\n            <Menu.Separator className={styles.Separator} />\n            <Menu.Item className={styles.Item}>Favorite</Menu.Item>\n            <Menu.Item className={styles.Item}>Share</Menu.Item>\n          </Menu.Popup>\n        </Menu.Positioner>\n      </Menu.Portal>\n    </Menu.Root>\n  );\n}\n\nfunction getOffset({ side }: { side: Menu.Positioner.Props['side'] }) {\n  return side === 'top' || side === 'bottom' ? 4 : -4;\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nfunction ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M1 3.5L5 7.5L9 3.5\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n\nfunction ChevronRightIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M3.5 9L7.5 5L3.5 1\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menu/demos/submenu/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoMenuSubmenu = createDemoWithVariants(import.meta.url, { CssModules, Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menu/demos/submenu/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\n\nexport default function ExampleMenu() {\n  return (\n    <Menu.Root>\n      <Menu.Trigger className=\"flex h-10 items-center justify-center gap-1.5 rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100 data-[popup-open]:bg-gray-100\">\n        Song <ChevronDownIcon className=\"-mr-1\" />\n      </Menu.Trigger>\n      <Menu.Portal>\n        <Menu.Positioner className=\"outline-hidden\" sideOffset={8}>\n          <Menu.Popup className=\"origin-[var(--transform-origin)] rounded-md bg-[canvas] py-1 text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 transition-[transform,scale,opacity] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300\">\n            <Menu.Arrow className=\"data-[side=bottom]:top-[-8px] data-[side=left]:right-[-13px] data-[side=left]:rotate-90 data-[side=right]:left-[-13px] data-[side=right]:-rotate-90 data-[side=top]:bottom-[-8px] data-[side=top]:rotate-180\">\n              <ArrowSvg />\n            </Menu.Arrow>\n            <Menu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900 data-[popup-open]:relative data-[popup-open]:z-0 data-[popup-open]:before:absolute data-[popup-open]:before:inset-x-1 data-[popup-open]:before:inset-y-0 data-[popup-open]:before:z-[-1] data-[popup-open]:before:rounded-xs data-[popup-open]:before:bg-gray-100 data-[highlighted]:data-[popup-open]:before:bg-gray-900\">\n              Add to Library\n            </Menu.Item>\n\n            <Menu.SubmenuRoot>\n              <Menu.SubmenuTrigger className=\"flex cursor-default items-center justify-between gap-4 py-2 pr-4 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900 data-[popup-open]:relative data-[popup-open]:z-0 data-[popup-open]:before:absolute data-[popup-open]:before:inset-x-1 data-[popup-open]:before:inset-y-0 data-[popup-open]:before:z-[-1] data-[popup-open]:before:rounded-xs data-[popup-open]:before:bg-gray-100 data-[highlighted]:data-[popup-open]:before:bg-gray-900\">\n                Add to Playlist <ChevronRightIcon />\n              </Menu.SubmenuTrigger>\n              <Menu.Portal>\n                <Menu.Positioner\n                  className=\"outline-hidden\"\n                  sideOffset={getOffset}\n                  alignOffset={getOffset}\n                >\n                  <Menu.Popup className=\"origin-[var(--transform-origin)] rounded-md bg-[canvas] py-1 text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 transition-[transform,scale,opacity] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300\">\n                    <Menu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900 data-[popup-open]:relative data-[popup-open]:z-0 data-[popup-open]:before:absolute data-[popup-open]:before:inset-x-1 data-[popup-open]:before:inset-y-0 data-[popup-open]:before:z-[-1] data-[popup-open]:before:rounded-xs data-[popup-open]:before:bg-gray-100 data-[highlighted]:data-[popup-open]:before:bg-gray-900\">\n                      Add to Library\n                    </Menu.Item>\n                    <Menu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900 data-[popup-open]:relative data-[popup-open]:z-0 data-[popup-open]:before:absolute data-[popup-open]:before:inset-x-1 data-[popup-open]:before:inset-y-0 data-[popup-open]:before:z-[-1] data-[popup-open]:before:rounded-xs data-[popup-open]:before:bg-gray-100 data-[highlighted]:data-[popup-open]:before:bg-gray-900\">\n                      Add to Playlist\n                    </Menu.Item>\n                    <Menu.Separator className=\"mx-4 my-1.5 h-px bg-gray-200\" />\n                    <Menu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900 data-[popup-open]:relative data-[popup-open]:z-0 data-[popup-open]:before:absolute data-[popup-open]:before:inset-x-1 data-[popup-open]:before:inset-y-0 data-[popup-open]:before:z-[-1] data-[popup-open]:before:rounded-xs data-[popup-open]:before:bg-gray-100 data-[highlighted]:data-[popup-open]:before:bg-gray-900\">\n                      Play Next\n                    </Menu.Item>\n                    <Menu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900 data-[popup-open]:relative data-[popup-open]:z-0 data-[popup-open]:before:absolute data-[popup-open]:before:inset-x-1 data-[popup-open]:before:inset-y-0 data-[popup-open]:before:z-[-1] data-[popup-open]:before:rounded-xs data-[popup-open]:before:bg-gray-100 data-[highlighted]:data-[popup-open]:before:bg-gray-900\">\n                      Play Last\n                    </Menu.Item>\n                    <Menu.Separator className=\"mx-4 my-1.5 h-px bg-gray-200\" />\n                    <Menu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900 data-[popup-open]:relative data-[popup-open]:z-0 data-[popup-open]:before:absolute data-[popup-open]:before:inset-x-1 data-[popup-open]:before:inset-y-0 data-[popup-open]:before:z-[-1] data-[popup-open]:before:rounded-xs data-[popup-open]:before:bg-gray-100 data-[highlighted]:data-[popup-open]:before:bg-gray-900\">\n                      Favorite\n                    </Menu.Item>\n                    <Menu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900 data-[popup-open]:relative data-[popup-open]:z-0 data-[popup-open]:before:absolute data-[popup-open]:before:inset-x-1 data-[popup-open]:before:inset-y-0 data-[popup-open]:before:z-[-1] data-[popup-open]:before:rounded-xs data-[popup-open]:before:bg-gray-100 data-[highlighted]:data-[popup-open]:before:bg-gray-900\">\n                      Share\n                    </Menu.Item>\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            </Menu.SubmenuRoot>\n\n            <Menu.Separator className=\"mx-4 my-1.5 h-px bg-gray-200\" />\n\n            <Menu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900 data-[popup-open]:relative data-[popup-open]:z-0 data-[popup-open]:before:absolute data-[popup-open]:before:inset-x-1 data-[popup-open]:before:inset-y-0 data-[popup-open]:before:z-[-1] data-[popup-open]:before:rounded-xs data-[popup-open]:before:bg-gray-100 data-[highlighted]:data-[popup-open]:before:bg-gray-900\">\n              Play Next\n            </Menu.Item>\n            <Menu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900 data-[popup-open]:relative data-[popup-open]:z-0 data-[popup-open]:before:absolute data-[popup-open]:before:inset-x-1 data-[popup-open]:before:inset-y-0 data-[popup-open]:before:z-[-1] data-[popup-open]:before:rounded-xs data-[popup-open]:before:bg-gray-100 data-[highlighted]:data-[popup-open]:before:bg-gray-900\">\n              Play Last\n            </Menu.Item>\n            <Menu.Separator className=\"mx-4 my-1.5 h-px bg-gray-200\" />\n            <Menu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900 data-[popup-open]:relative data-[popup-open]:z-0 data-[popup-open]:before:absolute data-[popup-open]:before:inset-x-1 data-[popup-open]:before:inset-y-0 data-[popup-open]:before:z-[-1] data-[popup-open]:before:rounded-xs data-[popup-open]:before:bg-gray-100 data-[highlighted]:data-[popup-open]:before:bg-gray-900\">\n              Favorite\n            </Menu.Item>\n            <Menu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900 data-[popup-open]:relative data-[popup-open]:z-0 data-[popup-open]:before:absolute data-[popup-open]:before:inset-x-1 data-[popup-open]:before:inset-y-0 data-[popup-open]:before:z-[-1] data-[popup-open]:before:rounded-xs data-[popup-open]:before:bg-gray-100 data-[highlighted]:data-[popup-open]:before:bg-gray-900\">\n              Share\n            </Menu.Item>\n          </Menu.Popup>\n        </Menu.Positioner>\n      </Menu.Portal>\n    </Menu.Root>\n  );\n}\n\nfunction getOffset({ side }: { side: Menu.Positioner.Props['side'] }) {\n  return side === 'top' || side === 'bottom' ? 4 : -4;\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className=\"fill-[canvas]\"\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className=\"fill-gray-200 dark:fill-none\"\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className=\"dark:fill-gray-300\"\n      />\n    </svg>\n  );\n}\n\nfunction ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M1 3.5L5 7.5L9 3.5\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n\nfunction ChevronRightIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M3.5 9L7.5 5L3.5 1\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menu/page.mdx",
    "content": "# Menu\n\n<Subtitle>A list of actions in a dropdown, enhanced with keyboard navigation.</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"A high-quality, unstyled React menu component that displays list of actions in a dropdown, enhanced with keyboard navigation.\"\n/>\n\nimport { DemoMenuHero } from './demos/hero';\n\n<DemoMenuHero />\n\n## Anatomy\n\nImport the component and assemble its parts:\n\n```jsx title=\"Anatomy\"\nimport { Menu } from '@base-ui/react/menu';\n\n<Menu.Root>\n  <Menu.Trigger />\n  <Menu.Portal>\n    <Menu.Backdrop />\n    <Menu.Positioner>\n      <Menu.Popup>\n        <Menu.Arrow />\n        <Menu.Viewport>\n          <Menu.Item />\n          <Menu.LinkItem />\n          <Menu.Separator />\n          <Menu.Group>\n            <Menu.GroupLabel />\n          </Menu.Group>\n          <Menu.RadioGroup>\n            <Menu.RadioItem />\n          </Menu.RadioGroup>\n          <Menu.CheckboxItem />\n          <Menu.SubmenuRoot>\n            <Menu.SubmenuTrigger />\n          </Menu.SubmenuRoot>\n        </Menu.Viewport>\n      </Menu.Popup>\n    </Menu.Positioner>\n  </Menu.Portal>\n</Menu.Root>;\n```\n\n## Examples\n\n### Open on hover\n\nTo create a menu that opens on hover, add the `openOnHover` prop to `<Menu.Trigger>`. You can additionally configure how quickly the menu opens on hover using the `delay` prop.\n\nimport { DemoMenuOpenOnHover } from './demos/open-on-hover';\n\n<DemoMenuOpenOnHover compact />\n\n### Checkbox items\n\nUse the `<Menu.CheckboxItem>` part to create a menu item that can toggle a setting on or off.\n\nimport { DemoMenuCheckboxItems } from './demos/checkbox-items';\n\n<DemoMenuCheckboxItems compact />\n\n### Radio items\n\nUse the `<Menu.RadioGroup>` and `<Menu.RadioItem>` parts to create menu items that work like radio buttons.\n\nimport { DemoMenuRadioItems } from './demos/radio-items';\n\n<DemoMenuRadioItems compact />\n\n### Close on click\n\nUse the `closeOnClick` prop to change whether the menu closes when an item is clicked.\n\n```jsx title=\"Control whether the menu closes on click\"\n// Close the menu when a checkbox item is clicked\n<Menu.CheckboxItem closeOnClick />\n\n// Keep the menu open when an item is clicked\n<Menu.Item closeOnClick={false} />\n```\n\n### Group labels\n\nUse the `<Menu.GroupLabel>` part to add a label to a `<Menu.Group>`\n\nimport { DemoMenuGroupLabels } from './demos/group-labels';\n\n<DemoMenuGroupLabels compact />\n\n### Nested menu\n\nTo create a submenu, nest another menu inside the parent menu with `<Menu.SubmenuRoot>`. Use the `<Menu.SubmenuTrigger>` part for the menu item that opens the nested menu.\n\n```jsx {11-21} {12}#strong title=\"Adding a submenu\"\n<Menu.Root>\n  <Menu.Trigger />\n  <Menu.Portal>\n    <Menu.Positioner>\n      <Menu.Popup>\n        <Menu.Arrow />\n        <Menu.Item />\n\n        {/* Submenu */}\n        <Menu.SubmenuRoot>\n          <Menu.SubmenuTrigger />\n          <Menu.Portal>\n            <Menu.Positioner>\n              <Menu.Popup>\n                {/* prettier-ignore */}\n                {/* Submenu items  */}\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.SubmenuRoot>\n      </Menu.Popup>\n    </Menu.Positioner>\n  </Menu.Portal>\n</Menu.Root>\n```\n\nimport { DemoMenuSubmenu } from './demos/submenu';\n\n<DemoMenuSubmenu compact />\n\n### Navigate to another page\n\nUse the `<Menu.LinkItem>` part to create a link.\n\n```jsx title=\"A menu item that opens a link\"\n<Menu.LinkItem href=\"/projects\">Go to Projects</Menu.LinkItem>\n```\n\n### Open a dialog\n\nIn order to open a dialog using a menu, control the dialog state and open it imperatively using the `onClick` handler on the menu item.\n\n```tsx {12-13,17-18,24-25,28-29} title=\"Connecting a dialog to a menu\"\nimport * as React from 'react';\nimport { Dialog } from '@base-ui/react/dialog';\nimport { Menu } from '@base-ui/react/menu';\n\nfunction ExampleMenu() {\n  const [dialogOpen, setDialogOpen] = React.useState(false);\n\n  return (\n    <React.Fragment>\n      <Menu.Root>\n        <Menu.Trigger>Open menu</Menu.Trigger>\n        <Menu.Portal>\n          <Menu.Positioner>\n            <Menu.Popup>\n              {/* Open the dialog when the menu item is clicked */}\n              <Menu.Item onClick={() => setDialogOpen(true)}>Open dialog</Menu.Item>\n            </Menu.Popup>\n          </Menu.Positioner>\n        </Menu.Portal>\n      </Menu.Root>\n\n      {/* Control the dialog state */}\n      <Dialog.Root open={dialogOpen} onOpenChange={setDialogOpen}>\n        <Dialog.Portal>\n          <Dialog.Backdrop />\n          <Dialog.Popup>\n            {/* prettier-ignore */}\n            {/* Rest of the dialog */}\n          </Dialog.Popup>\n        </Dialog.Portal>\n      </Dialog.Root>\n    </React.Fragment>\n  );\n}\n```\n\n### Detached triggers\n\nA menu can be opened by a trigger that lives either inside or outside the `<Menu.Root>`.\nKeep the trigger inside `<Menu.Root>` for simple, tightly coupled layouts like the hero demo at the top of this page.\nWhen the trigger and menu content need to live in different parts of the tree (for example, in a card list that controls a menu rendered near the document root), create a `handle` with `Menu.createHandle()` and pass it to both the trigger and the root.\n\nNote that only top-level menus can have detached triggers.\nSubmenus must have their triggers defined within the `SubmenuRoot` part.\n\n```jsx title=\"Detached triggers\" {3,7} \"handle={demoMenu}\"\nconst demoMenu = Menu.createHandle();\n\n<Menu.Trigger handle={demoMenu}>\n  Actions\n</Menu.Trigger>\n\n<Menu.Root handle={demoMenu}>\n  <Menu.Portal>\n    <Menu.Positioner>\n      <Menu.Popup>\n        <Menu.Item>Edit</Menu.Item>\n        <Menu.Item>Share</Menu.Item>\n      </Menu.Popup>\n    </Menu.Positioner>\n  </Menu.Portal>\n</Menu.Root>\n```\n\nimport { DemoMenuDetachedTriggersSimple } from './demos/detached-triggers-simple';\n\n<DemoMenuDetachedTriggersSimple />\n\n### Multiple triggers\n\nOne menu can be opened by several triggers.\nYou can either render multiple `<Menu.Trigger>` components inside the same `<Menu.Root>`, or attach several detached triggers to the same `handle`.\n\n```jsx title=\"Multiple triggers within the Root part\"\n<Menu.Root>\n  <Menu.Trigger>Row actions</Menu.Trigger>\n  <Menu.Trigger>Quick actions</Menu.Trigger>\n  {/* Rest of the menu */}\n</Menu.Root>\n```\n\n```jsx title=\"Multiple detached triggers\"\nconst projectMenu = Menu.createHandle();\n\n<Menu.Trigger handle={projectMenu}>Row actions</Menu.Trigger>\n<Menu.Trigger handle={projectMenu}>Quick actions</Menu.Trigger>\n\n<Menu.Root handle={projectMenu}>\n  {/* Rest of the menu */}\n</Menu.Root>\n```\n\nMenus can render different content depending on which trigger opened them.\nPass a `payload` prop to each `<Menu.Trigger>` and read it via a function child on `<Menu.Root>`.\nProvide a type argument to `createHandle()` to strongly type the payload.\n\n```jsx title=\"Detached triggers with payload\" {6,8,12,17}\nconst menus = {\n  file: ['New', 'Open', 'Save'],\n  edit: ['Undo', 'Redo', 'Cut', 'Copy', 'Paste'],\n}\n\nconst demoMenu = Menu.createHandle<{ items: string[] }>();\n\n<Menu.Trigger handle={demoMenu} payload={{ items: menus.file }}>\n  File\n</Menu.Trigger>\n\n<Menu.Trigger handle={demoMenu} payload={{ items: menus.edit }}>\n  Edit\n</Menu.Trigger>\n\n<Menu.Root handle={demoMenu}>\n  {({ payload }) => (\n    <Menu.Portal>\n      <Menu.Positioner>\n        <Menu.Popup>\n          <Menu.Viewport>\n            {(payload?.items ?? []).map((item) => (\n              <Menu.Item key={item}>{item}</Menu.Item>\n            ))}\n          </Menu.Viewport>\n        </Menu.Popup>\n      </Menu.Positioner>\n    </Menu.Portal>\n  )}\n</Menu.Root>\n```\n\n### Controlled mode with multiple triggers\n\nControl a menu's open state externally with the `open` and `onOpenChange` props on `<Menu.Root>`.\nWhen more than one trigger can open the menu, track the active trigger with the `triggerId` prop on `<Menu.Root>` and matching `id` props on each `<Menu.Trigger>`.\nThe `onOpenChange` callback receives `eventDetails`, which includes the DOM element that initiated the change, so you can update your `triggerId` state when the user activates a different trigger.\n\nimport { DemoMenuDetachedTriggersControlled } from './demos/detached-triggers-controlled';\n\n<DemoMenuDetachedTriggersControlled compact />\n\n### Animating the Menu\n\nWhen one menu is opened by multiple detached triggers, you can animate the menu as it moves between triggers.\nThis includes animating position, size, and content.\n\n#### Position and Size\n\nTo animate the menu's position, apply CSS transitions to the `left`, `right`, `top`, and `bottom` properties of the **Positioner** part.\nTo animate its size, transition the `width` and `height` of the **Popup** part.\n\n#### Content\n\nThe menu also supports content transitions.\nThis is useful when different triggers display different content within the same menu.\n\nTo enable content animations, wrap the menu content in the `<Menu.Viewport>` part.\nThis part renders a `div` with `data-activation-direction` (`left`, `right`, `up`, or `down`) so you can make direction-aware animations.\n\nInside `<Menu.Viewport>`, content is wrapped in `div`s with transition data attributes:\n\n- `data-current`: The currently visible content when no transitions are present or the incoming content.\n- `data-previous`: The outgoing content during a transition.\n\nimport { DemoMenuDetachedTriggersFull } from './demos/detached-triggers-full';\n\n<DemoMenuDetachedTriggersFull />\n\n## API reference\n\n<Reference\n  component=\"Menu\"\n  parts=\"Root, Trigger, Portal, Backdrop, Positioner, Popup, Viewport, Arrow, Item, LinkItem, SubmenuRoot, SubmenuTrigger, Group, GroupLabel, RadioGroup, RadioItem, RadioItemIndicator, CheckboxItem, CheckboxItemIndicator, Separator\"\n/>\n\nexport const metadata = {\n  keywords: [\n    'React Menu',\n    'Menu Component',\n    'Dropdown Menu',\n    'Dropdown Actions',\n    'Command Menu',\n    'Action List',\n    'Keyboard Navigation Menu',\n    'Nested Menu React',\n    'Context Actions',\n    'Headless React Components',\n    'Base UI',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menubar/demos/hero/css-modules/index.module.css",
    "content": ".Menubar {\n  display: flex;\n  background-color: var(--color-gray-50);\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  padding: 0.125rem;\n}\n\n.MenuTrigger {\n  box-sizing: border-box;\n  background: none;\n  padding: 0 0.75rem;\n  margin: 0;\n  outline: 0;\n  border: 0;\n  color: var(--color-gray-600);\n  border-radius: 0.25rem;\n  user-select: none;\n  height: 2rem;\n  font-family: inherit;\n  font-size: 0.875rem;\n  font-weight: 400;\n\n  &[data-pressed],\n  &:focus-visible {\n    background-color: var(--color-gray-100);\n    outline: none;\n  }\n\n  &[data-disabled] {\n    opacity: 0.5;\n  }\n}\n\n.MenuPositioner {\n  outline: 0;\n}\n\n.MenuPopup {\n  box-sizing: border-box;\n  padding-block: 0.25rem;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  transform-origin: var(--transform-origin);\n\n  &[data-ending-style] {\n    opacity: 0;\n    transition: opacity 150ms;\n  }\n\n  &[data-instant] {\n    transition: none;\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.MenuItem {\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  padding: 0.5rem 1rem;\n  display: flex;\n  font-size: 0.875rem;\n  line-height: 1rem;\n  font-weight: 400;\n  align-items: center;\n  justify-content: space-between;\n  gap: 1rem;\n\n  &[data-popup-open] {\n    z-index: 0;\n    position: relative;\n  }\n\n  &[data-popup-open]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.25rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-100);\n  }\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.25rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.MenuSeparator {\n  margin: 0.375rem 1rem;\n  height: 1px;\n  background-color: var(--color-gray-200);\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menubar/demos/hero/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Menubar } from '@base-ui/react/menubar';\nimport { Menu } from '@base-ui/react/menu';\nimport styles from './index.module.css';\n\nexport default function ExampleMenubar() {\n  return (\n    <Menubar className={styles.Menubar}>\n      <Menu.Root>\n        <Menu.Trigger className={styles.MenuTrigger}>File</Menu.Trigger>\n        <Menu.Portal>\n          <Menu.Positioner className={styles.MenuPositioner} sideOffset={6} alignOffset={-2}>\n            <Menu.Popup className={styles.MenuPopup}>\n              <Menu.Item className={styles.MenuItem} onClick={handleClick}>\n                New\n              </Menu.Item>\n              <Menu.Item className={styles.MenuItem} onClick={handleClick}>\n                Open\n              </Menu.Item>\n              <Menu.Item className={styles.MenuItem} onClick={handleClick}>\n                Save\n              </Menu.Item>\n\n              <Menu.SubmenuRoot>\n                <Menu.SubmenuTrigger className={styles.MenuItem}>\n                  Export\n                  <ChevronRightIcon />\n                </Menu.SubmenuTrigger>\n                <Menu.Portal>\n                  <Menu.Positioner alignOffset={-4}>\n                    <Menu.Popup className={styles.MenuPopup}>\n                      <Menu.Item className={styles.MenuItem} onClick={handleClick}>\n                        PDF\n                      </Menu.Item>\n                      <Menu.Item className={styles.MenuItem} onClick={handleClick}>\n                        PNG\n                      </Menu.Item>\n                      <Menu.Item className={styles.MenuItem} onClick={handleClick}>\n                        SVG\n                      </Menu.Item>\n                    </Menu.Popup>\n                  </Menu.Positioner>\n                </Menu.Portal>\n              </Menu.SubmenuRoot>\n\n              <Menu.Separator className={styles.MenuSeparator} />\n              <Menu.Item className={styles.MenuItem} onClick={handleClick}>\n                Print\n              </Menu.Item>\n            </Menu.Popup>\n          </Menu.Positioner>\n        </Menu.Portal>\n      </Menu.Root>\n\n      <Menu.Root>\n        <Menu.Trigger className={styles.MenuTrigger}>Edit</Menu.Trigger>\n        <Menu.Portal>\n          <Menu.Positioner className={styles.MenuPositioner} sideOffset={6}>\n            <Menu.Popup className={styles.MenuPopup}>\n              <Menu.Item className={styles.MenuItem} onClick={handleClick}>\n                Cut\n              </Menu.Item>\n              <Menu.Item className={styles.MenuItem} onClick={handleClick}>\n                Copy\n              </Menu.Item>\n              <Menu.Item className={styles.MenuItem} onClick={handleClick}>\n                Paste\n              </Menu.Item>\n            </Menu.Popup>\n          </Menu.Positioner>\n        </Menu.Portal>\n      </Menu.Root>\n\n      <Menu.Root>\n        <Menu.Trigger className={styles.MenuTrigger}>View</Menu.Trigger>\n        <Menu.Portal>\n          <Menu.Positioner className={styles.MenuPositioner} sideOffset={6}>\n            <Menu.Popup className={styles.MenuPopup}>\n              <Menu.Item className={styles.MenuItem} onClick={handleClick}>\n                Zoom In\n              </Menu.Item>\n              <Menu.Item className={styles.MenuItem} onClick={handleClick}>\n                Zoom Out\n              </Menu.Item>\n\n              <Menu.SubmenuRoot>\n                <Menu.SubmenuTrigger className={styles.MenuItem}>\n                  Layout\n                  <ChevronRightIcon />\n                </Menu.SubmenuTrigger>\n                <Menu.Portal>\n                  <Menu.Positioner alignOffset={-4}>\n                    <Menu.Popup className={styles.MenuPopup}>\n                      <Menu.Item className={styles.MenuItem} onClick={handleClick}>\n                        Single Page\n                      </Menu.Item>\n                      <Menu.Item className={styles.MenuItem} onClick={handleClick}>\n                        Two Pages\n                      </Menu.Item>\n                      <Menu.Item className={styles.MenuItem} onClick={handleClick}>\n                        Continuous\n                      </Menu.Item>\n                    </Menu.Popup>\n                  </Menu.Positioner>\n                </Menu.Portal>\n              </Menu.SubmenuRoot>\n\n              <Menu.Separator className={styles.MenuSeparator} />\n              <Menu.Item className={styles.MenuItem} onClick={handleClick}>\n                Full Screen\n              </Menu.Item>\n            </Menu.Popup>\n          </Menu.Positioner>\n        </Menu.Portal>\n      </Menu.Root>\n\n      <Menu.Root disabled>\n        <Menu.Trigger className={styles.MenuTrigger}>Help</Menu.Trigger>\n      </Menu.Root>\n    </Menubar>\n  );\n}\n\nfunction handleClick(event: React.MouseEvent<HTMLElement>) {\n  // eslint-disable-next-line no-console\n  console.log(`${event.currentTarget.textContent} clicked`);\n}\n\nfunction ChevronRightIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" {...props}>\n      <path\n        d=\"M6 12L10 8L6 4\"\n        stroke=\"currentColor\"\n        strokeWidth=\"1.5\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menubar/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoMenubarHero = createDemoWithVariants(import.meta.url, { CssModules, Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menubar/demos/hero/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Menubar } from '@base-ui/react/menubar';\nimport { Menu } from '@base-ui/react/menu';\n\nexport default function ExampleMenubar() {\n  return (\n    <Menubar className=\"flex rounded-md border border-gray-200 bg-gray-50 p-0.5\">\n      <Menu.Root>\n        <Menu.Trigger className=\"h-8 rounded-sm px-3 text-sm font-normal text-gray-600 outline-hidden select-none focus-visible:bg-gray-100 data-[disabled]:opacity-50 data-[popup-open]:bg-gray-100\">\n          File\n        </Menu.Trigger>\n        <Menu.Portal>\n          <Menu.Positioner className=\"outline-hidden\" sideOffset={6}>\n            <Menu.Popup className=\"origin-[var(--transform-origin)] rounded-md bg-[canvas] py-1 text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 data-[ending-style]:opacity-0 data-[ending-style]:transition-opacity data-[instant]:transition-none dark:shadow-none dark:outline-1 dark:-outline-offset-1 dark:outline-gray-300\">\n              <Menu.Item\n                onClick={handleClick}\n                className=\"flex cursor-default items-center justify-between gap-4 px-4 py-2 text-sm font-normal leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\"\n              >\n                New\n              </Menu.Item>\n              <Menu.Item\n                onClick={handleClick}\n                className=\"flex cursor-default items-center justify-between gap-4 px-4 py-2 text-sm font-normal leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\"\n              >\n                Open\n              </Menu.Item>\n              <Menu.Item\n                onClick={handleClick}\n                className=\"flex cursor-default items-center justify-between gap-4 px-4 py-2 text-sm font-normal leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\"\n              >\n                Save\n              </Menu.Item>\n\n              <Menu.SubmenuRoot>\n                <Menu.SubmenuTrigger className=\"flex w-full cursor-default items-center justify-between gap-4 px-4 py-2 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900 data-[popup-open]:relative data-[popup-open]:z-0 data-[popup-open]:before:absolute data-[popup-open]:before:inset-x-1 data-[popup-open]:before:inset-y-0 data-[popup-open]:before:z-[-1] data-[popup-open]:before:rounded-xs data-[popup-open]:before:bg-gray-100 data-[highlighted]:data-[popup-open]:before:bg-gray-900\">\n                  Export\n                  <ChevronRightIcon />\n                </Menu.SubmenuTrigger>\n                <Menu.Portal>\n                  <Menu.Positioner>\n                    <Menu.Popup className=\"origin-[var(--transform-origin)] rounded-md bg-[canvas] py-1 text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 data-[ending-style]:opacity-0 data-[ending-style]:transition-opacity data-[instant]:transition-none dark:shadow-none dark:outline-1 dark:-outline-offset-1 dark:outline-gray-300\">\n                      <Menu.Item\n                        onClick={handleClick}\n                        className=\"flex cursor-default items-center justify-between gap-4 px-4 py-2 text-sm font-normal leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\"\n                      >\n                        PDF\n                      </Menu.Item>\n                      <Menu.Item\n                        onClick={handleClick}\n                        className=\"flex cursor-default items-center justify-between gap-4 px-4 py-2 text-sm font-normal leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\"\n                      >\n                        PNG\n                      </Menu.Item>\n                      <Menu.Item\n                        onClick={handleClick}\n                        className=\"flex cursor-default items-center justify-between gap-4 px-4 py-2 text-sm font-normal leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\"\n                      >\n                        SVG\n                      </Menu.Item>\n                    </Menu.Popup>\n                  </Menu.Positioner>\n                </Menu.Portal>\n              </Menu.SubmenuRoot>\n\n              <Menu.Separator className=\"mx-4 my-1.5 h-px bg-gray-200\" />\n              <Menu.Item\n                onClick={handleClick}\n                className=\"flex cursor-default items-center justify-between gap-4 px-4 py-2 text-sm font-normal leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\"\n              >\n                Print\n              </Menu.Item>\n            </Menu.Popup>\n          </Menu.Positioner>\n        </Menu.Portal>\n      </Menu.Root>\n\n      <Menu.Root>\n        <Menu.Trigger className=\"h-8 rounded-sm px-3 text-sm font-normal text-gray-600 outline-hidden select-none focus-visible:bg-gray-100 data-[disabled]:opacity-50 data-[popup-open]:bg-gray-100\">\n          Edit\n        </Menu.Trigger>\n        <Menu.Portal>\n          <Menu.Positioner className=\"outline-hidden\" sideOffset={6}>\n            <Menu.Popup className=\"origin-[var(--transform-origin)] rounded-md bg-[canvas] py-1 text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 data-[ending-style]:opacity-0 data-[ending-style]:transition-opacity data-[instant]:transition-none dark:shadow-none dark:outline-1 dark:-outline-offset-1 dark:outline-gray-300\">\n              <Menu.Item\n                onClick={handleClick}\n                className=\"flex cursor-default items-center justify-between gap-4 px-4 py-2 text-sm font-normal leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\"\n              >\n                Cut\n              </Menu.Item>\n              <Menu.Item\n                onClick={handleClick}\n                className=\"flex cursor-default items-center justify-between gap-4 px-4 py-2 text-sm font-normal leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\"\n              >\n                Copy\n              </Menu.Item>\n              <Menu.Item\n                onClick={handleClick}\n                className=\"flex cursor-default items-center justify-between gap-4 px-4 py-2 text-sm font-normal leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\"\n              >\n                Paste\n              </Menu.Item>\n            </Menu.Popup>\n          </Menu.Positioner>\n        </Menu.Portal>\n      </Menu.Root>\n\n      <Menu.Root>\n        <Menu.Trigger className=\"h-8 rounded-sm px-3 text-sm font-normal text-gray-600 outline-hidden select-none focus-visible:bg-gray-100 data-[disabled]:opacity-50 data-[popup-open]:bg-gray-100\">\n          View\n        </Menu.Trigger>\n        <Menu.Portal>\n          <Menu.Positioner className=\"outline-hidden\" sideOffset={6}>\n            <Menu.Popup className=\"origin-[var(--transform-origin)] rounded-md bg-[canvas] py-1 text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 data-[ending-style]:opacity-0 data-[ending-style]:transition-opacity data-[instant]:transition-none dark:shadow-none dark:outline-1 dark:-outline-offset-1 dark:outline-gray-300\">\n              <Menu.Item\n                onClick={handleClick}\n                className=\"flex cursor-default items-center justify-between gap-4 px-4 py-2 text-sm font-normal leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\"\n              >\n                Zoom In\n              </Menu.Item>\n              <Menu.Item\n                onClick={handleClick}\n                className=\"flex cursor-default items-center justify-between gap-4 px-4 py-2 text-sm font-normal leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\"\n              >\n                Zoom Out\n              </Menu.Item>\n\n              <Menu.SubmenuRoot>\n                <Menu.SubmenuTrigger className=\"flex w-full cursor-default items-center justify-between gap-4 px-4 py-2 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900 data-[popup-open]:relative data-[popup-open]:z-0 data-[popup-open]:before:absolute data-[popup-open]:before:inset-x-1 data-[popup-open]:before:inset-y-0 data-[popup-open]:before:z-[-1] data-[popup-open]:before:rounded-xs data-[popup-open]:before:bg-gray-100 data-[highlighted]:data-[popup-open]:before:bg-gray-900\">\n                  Layout\n                  <ChevronRightIcon />\n                </Menu.SubmenuTrigger>\n                <Menu.Portal>\n                  <Menu.Positioner>\n                    <Menu.Popup className=\"origin-[var(--transform-origin)] rounded-md bg-[canvas] py-1 text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 data-[ending-style]:opacity-0 data-[ending-style]:transition-opacity data-[instant]:transition-none dark:shadow-none dark:outline-1 dark:-outline-offset-1 dark:outline-gray-300\">\n                      <Menu.Item\n                        onClick={handleClick}\n                        className=\"flex cursor-default items-center justify-between gap-4 px-4 py-2 text-sm font-normal leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\"\n                      >\n                        Single Page\n                      </Menu.Item>\n                      <Menu.Item\n                        onClick={handleClick}\n                        className=\"flex cursor-default items-center justify-between gap-4 px-4 py-2 text-sm font-normal leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\"\n                      >\n                        Two Pages\n                      </Menu.Item>\n                      <Menu.Item\n                        onClick={handleClick}\n                        className=\"flex cursor-default items-center justify-between gap-4 px-4 py-2 text-sm font-normal leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\"\n                      >\n                        Continuous\n                      </Menu.Item>\n                    </Menu.Popup>\n                  </Menu.Positioner>\n                </Menu.Portal>\n              </Menu.SubmenuRoot>\n\n              <Menu.Separator className=\"mx-4 my-1.5 h-px bg-gray-200\" />\n              <Menu.Item\n                onClick={handleClick}\n                className=\"flex cursor-default items-center justify-between gap-4 px-4 py-2 text-sm font-normal leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900\"\n              >\n                Full Screen\n              </Menu.Item>\n            </Menu.Popup>\n          </Menu.Positioner>\n        </Menu.Portal>\n      </Menu.Root>\n\n      <Menu.Root disabled>\n        <Menu.Trigger className=\"h-8 rounded-sm px-3 text-sm font-normal text-gray-600 outline-hidden select-none focus-visible:bg-gray-100 data-[disabled]:opacity-50 data-[popup-open]:bg-gray-100\">\n          Help\n        </Menu.Trigger>\n      </Menu.Root>\n    </Menubar>\n  );\n}\n\nfunction handleClick(event: React.MouseEvent<HTMLElement>) {\n  // eslint-disable-next-line no-console\n  console.log(`${event.currentTarget.textContent} clicked`);\n}\n\nfunction ChevronRightIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" {...props}>\n      <path\n        d=\"M6 12L10 8L6 4\"\n        stroke=\"currentColor\"\n        strokeWidth=\"1.5\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/menubar/page.mdx",
    "content": "# Menubar\n\n<Subtitle>A menu bar providing commands and options for your application.</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"A menu bar providing commands and options for your application.\"\n/>\n\nimport { DemoMenubarHero } from './demos/hero';\n\n<DemoMenubarHero />\n\n## Anatomy\n\nImport the component and assemble its parts:\n\n```jsx title=\"Anatomy\"\nimport { Menubar } from '@base-ui/react/menubar';\nimport { Menu } from '@base-ui/react/menu';\n\n<Menubar>\n  <Menu.Root>\n    <Menu.Trigger />\n    <Menu.Portal>\n      <Menu.Backdrop />\n      <Menu.Positioner>\n        <Menu.Popup>\n          <Menu.Arrow />\n          <Menu.Item />\n          <Menu.Separator />\n          <Menu.Group>\n            <Menu.GroupLabel />\n          </Menu.Group>\n          <Menu.RadioGroup>\n            <Menu.RadioItem />\n          </Menu.RadioGroup>\n          <Menu.CheckboxItem />\n        </Menu.Popup>\n      </Menu.Positioner>\n    </Menu.Portal>\n  </Menu.Root>\n</Menubar>;\n```\n\n## API reference\n\n<Reference component=\"Menubar\" />\n\nexport const metadata = {\n  keywords: [\n    'React Menubar',\n    'Menubar Component',\n    'Application Menu Bar',\n    'App Menu',\n    'Navigation Bar',\n    'Command Bar',\n    'Desktop Style Menu',\n    'Keyboard Navigation Menu',\n    'Accessible Menubar',\n    'Headless React Components',\n    'Base UI',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/meter/demos/hero/css-modules/index.module.css",
    "content": ".Meter {\n  box-sizing: border-box;\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n  grid-row-gap: 0.5rem;\n  width: 12rem;\n}\n\n.Label {\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 400;\n  color: var(--color-gray-900);\n}\n\n.Value {\n  grid-column-start: 2;\n  margin: 0;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  color: var(--color-gray-900);\n  text-align: right;\n}\n\n.Track {\n  grid-column: 1 / 3;\n  overflow: hidden;\n  background-color: var(--color-gray-100);\n  box-shadow: inset 0 0 0 1px var(--color-gray-200);\n  height: 0.5rem;\n}\n\n.Indicator {\n  background-color: var(--color-gray-500);\n  transition: width 500ms;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/meter/demos/hero/css-modules/index.tsx",
    "content": "import { Meter } from '@base-ui/react/meter';\nimport styles from './index.module.css';\n\nexport default function ExampleMeter() {\n  return (\n    <Meter.Root className={styles.Meter} value={24}>\n      <Meter.Label className={styles.Label}>Storage Used</Meter.Label>\n      <Meter.Value className={styles.Value} />\n      <Meter.Track className={styles.Track}>\n        <Meter.Indicator className={styles.Indicator} />\n      </Meter.Track>\n    </Meter.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/meter/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoMeterHero = createDemoWithVariants(import.meta.url, { CssModules, Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/meter/demos/hero/tailwind/index.tsx",
    "content": "import { Meter } from '@base-ui/react/meter';\n\nexport default function ExampleMeter() {\n  return (\n    <Meter.Root className=\"box-border grid w-48 grid-cols-2 gap-y-2\" value={24}>\n      <Meter.Label className=\"text-sm font-normal text-gray-900\">Storage Used</Meter.Label>\n      <Meter.Value className=\"col-start-2 m-0 text-right text-sm leading-5 text-gray-900\" />\n      <Meter.Track className=\"col-span-2 block h-2 w-48 overflow-hidden bg-gray-100 shadow-[inset_0_0_0_1px] shadow-gray-200\">\n        <Meter.Indicator className=\"block bg-gray-500 transition-all duration-500\" />\n      </Meter.Track>\n    </Meter.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/meter/page.mdx",
    "content": "# Meter\n\n<Subtitle>A graphical display of a numeric value within a range.</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"A high-quality, unstyled React meter component that provides a graphical display of a numeric value.\"\n/>\n\nimport { DemoMeterHero } from './demos/hero';\n\n<DemoMeterHero />\n\n## Anatomy\n\nImport the component and assemble its parts:\n\n```jsx title=\"Anatomy\"\nimport { Meter } from '@base-ui/react/meter';\n\n<Meter.Root>\n  <Meter.Label />\n  <Meter.Track>\n    <Meter.Indicator />\n  </Meter.Track>\n  <Meter.Value />\n</Meter.Root>;\n```\n\n## API reference\n\n<Reference component=\"Meter\" parts=\"Root, Track, Indicator, Value, Label\" />\n\nexport const metadata = {\n  keywords: [\n    'React Meter',\n    'Meter Component',\n    'Progress Meter',\n    'Gauge',\n    'Level Indicator',\n    'Measurement Display',\n    'Capacity Indicator',\n    'Value Indicator',\n    'Rating Meter',\n    'Fuel Gauge',\n    'Accessible Meter',\n    'Headless React Components',\n    'Graphical Value Display',\n    'Base UI',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/navigation-menu/demos/hero/css-modules/index.module.css",
    "content": ".Root {\n  background-color: var(--color-gray-50);\n  border-radius: 0.5rem;\n  padding: 0.25rem;\n  color: var(--color-gray-900);\n  min-width: max-content;\n}\n\n.List {\n  display: flex;\n  position: relative;\n  list-style: none;\n  padding: 0;\n  margin: 0;\n}\n\n.Trigger {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: 0.375rem;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: none;\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n  text-decoration: none;\n\n  @media (max-width: 500px) {\n    font-size: 0.925rem;\n    padding: 0 0.5rem;\n  }\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    position: relative;\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Icon {\n  transition: transform 0.2s ease;\n\n  &[data-popup-open] {\n    transform: rotate(180deg);\n  }\n}\n\n.Positioner {\n  --easing: cubic-bezier(0.22, 1, 0.36, 1);\n  --duration: 0.35s;\n  box-sizing: border-box;\n  transition-property: top, left, right, bottom;\n  transition-duration: var(--duration);\n  transition-timing-function: var(--easing);\n  width: var(--positioner-width);\n  height: var(--positioner-height);\n  max-width: var(--available-width);\n\n  &::before {\n    content: '';\n    position: absolute;\n  }\n\n  &[data-side='top']::before {\n    left: 0;\n    right: 0;\n    bottom: -10px;\n    height: 10px;\n  }\n\n  &[data-side='bottom']::before {\n    left: 0;\n    right: 0;\n    top: -10px;\n    height: 10px;\n  }\n\n  &[data-side='left']::before {\n    top: 0;\n    bottom: 0;\n    right: -10px;\n    width: 10px;\n  }\n\n  &[data-side='right']::before {\n    top: 0;\n    bottom: 0;\n    left: -10px;\n    width: 10px;\n  }\n\n  &[data-instant] {\n    transition: none;\n  }\n}\n\n.Popup {\n  position: relative;\n  box-sizing: border-box;\n  border-radius: 0.5rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  transform-origin: var(--transform-origin);\n  transition-property: opacity, transform, width, height;\n  transition-duration: var(--duration);\n  transition-timing-function: var(--easing);\n  width: var(--popup-width);\n  height: var(--popup-height);\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  &[data-ending-style] {\n    transition-timing-function: ease;\n    transition-duration: 0.15s;\n  }\n}\n\n@media (prefers-color-scheme: light) {\n  .Popup {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n}\n\n@media (prefers-color-scheme: dark) {\n  .Popup {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Content {\n  box-sizing: border-box;\n  transition:\n    opacity calc(var(--duration) * 0.5) ease,\n    transform var(--duration) var(--easing);\n  padding: 1.5rem;\n  width: calc(100vw - 40px);\n  height: 100%;\n\n  @media (min-width: 500px) {\n    width: max-content;\n    min-width: 400px;\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n  }\n\n  &[data-starting-style] {\n    &[data-activation-direction='left'] {\n      transform: translateX(-50%);\n    }\n    &[data-activation-direction='right'] {\n      transform: translateX(50%);\n    }\n  }\n\n  &[data-ending-style] {\n    &[data-activation-direction='left'] {\n      transform: translateX(50%);\n    }\n    &[data-activation-direction='right'] {\n      transform: translateX(-50%);\n    }\n  }\n}\n\n.Viewport {\n  position: relative;\n  overflow: hidden;\n  width: 100%;\n  height: 100%;\n}\n\n.GridLinkList {\n  display: grid;\n  grid-template-columns: 12rem 12rem;\n  list-style: none;\n  padding: 0;\n  margin: 0;\n\n  @media (max-width: 500px) {\n    grid-template-columns: 1fr;\n  }\n}\n\n.FlexLinkList {\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  max-width: 400px;\n  padding: 0;\n  margin: 0;\n  list-style: none;\n}\n\n.LinkCard {\n  box-sizing: border-box;\n  display: block;\n  padding: 0.5rem;\n  border-radius: 0.375rem;\n  text-decoration: none;\n  color: inherit;\n  border: none;\n  background-color: transparent;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:focus-visible {\n    position: relative;\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n\n  @media (min-width: 425px) {\n    padding: 0.75rem;\n  }\n}\n\n.LinkTitle {\n  margin: 0 0 4px;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.25rem;\n}\n\n.LinkDescription {\n  margin: 0;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  color: var(--color-gray-500);\n}\n\n.Arrow {\n  display: flex;\n  transition: left calc(var(--duration)) var(--easing);\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/navigation-menu/demos/hero/css-modules/index.tsx",
    "content": "import * as React from 'react';\nimport { NavigationMenu } from '@base-ui/react/navigation-menu';\nimport styles from './index.module.css';\n\nexport default function ExampleNavigationMenu() {\n  return (\n    <NavigationMenu.Root className={styles.Root}>\n      <NavigationMenu.List className={styles.List}>\n        <NavigationMenu.Item>\n          <NavigationMenu.Trigger className={styles.Trigger}>\n            Overview\n            <NavigationMenu.Icon className={styles.Icon}>\n              <ChevronDownIcon />\n            </NavigationMenu.Icon>\n          </NavigationMenu.Trigger>\n          <NavigationMenu.Content className={styles.Content}>\n            <ul className={styles.GridLinkList}>\n              {overviewLinks.map((item) => (\n                <li key={item.href}>\n                  <Link className={styles.LinkCard} href={item.href}>\n                    <h3 className={styles.LinkTitle}>{item.title}</h3>\n                    <p className={styles.LinkDescription}>{item.description}</p>\n                  </Link>\n                </li>\n              ))}\n            </ul>\n          </NavigationMenu.Content>\n        </NavigationMenu.Item>\n\n        <NavigationMenu.Item>\n          <NavigationMenu.Trigger className={styles.Trigger}>\n            Handbook\n            <NavigationMenu.Icon className={styles.Icon}>\n              <ChevronDownIcon />\n            </NavigationMenu.Icon>\n          </NavigationMenu.Trigger>\n          <NavigationMenu.Content className={styles.Content}>\n            <ul className={styles.FlexLinkList}>\n              {handbookLinks.map((item) => (\n                <li key={item.href}>\n                  <Link className={styles.LinkCard} href={item.href}>\n                    <h3 className={styles.LinkTitle}>{item.title}</h3>\n                    <p className={styles.LinkDescription}>{item.description}</p>\n                  </Link>\n                </li>\n              ))}\n            </ul>\n          </NavigationMenu.Content>\n        </NavigationMenu.Item>\n\n        <NavigationMenu.Item>\n          <Link className={styles.Trigger} href=\"https://github.com/mui/base-ui\">\n            GitHub\n          </Link>\n        </NavigationMenu.Item>\n      </NavigationMenu.List>\n\n      <NavigationMenu.Portal>\n        <NavigationMenu.Positioner\n          className={styles.Positioner}\n          sideOffset={10}\n          collisionPadding={{ top: 5, bottom: 5, left: 20, right: 20 }}\n          collisionAvoidance={{ side: 'none' }}\n        >\n          <NavigationMenu.Popup className={styles.Popup}>\n            <NavigationMenu.Arrow className={styles.Arrow}>\n              <ArrowSvg />\n            </NavigationMenu.Arrow>\n            <NavigationMenu.Viewport className={styles.Viewport} />\n          </NavigationMenu.Popup>\n        </NavigationMenu.Positioner>\n      </NavigationMenu.Portal>\n    </NavigationMenu.Root>\n  );\n}\n\nfunction Link(props: NavigationMenu.Link.Props) {\n  return (\n    <NavigationMenu.Link\n      render={\n        // Use the `render` prop to render your framework's Link component\n        // for client-side routing.\n        // e.g. `<NextLink href={props.href} />` instead of `<a />`.\n        <a />\n      }\n      {...props}\n    />\n  );\n}\n\nfunction ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M1 3.5L5 7.5L9 3.5\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nconst overviewLinks = [\n  {\n    href: '/react/overview/quick-start',\n    title: 'Quick Start',\n    description: 'Install and assemble your first component.',\n  },\n  {\n    href: '/react/overview/accessibility',\n    title: 'Accessibility',\n    description: 'Learn how we build accessible components.',\n  },\n  {\n    href: '/react/overview/releases',\n    title: 'Releases',\n    description: 'See what’s new in the latest Base UI versions.',\n  },\n  {\n    href: '/react/overview/about',\n    title: 'About',\n    description: 'Learn more about Base UI and our mission.',\n  },\n] as const;\n\nconst handbookLinks = [\n  {\n    href: '/react/handbook/styling',\n    title: 'Styling',\n    description:\n      'Base UI components can be styled with plain CSS, Tailwind CSS, CSS-in-JS, or CSS Modules.',\n  },\n  {\n    href: '/react/handbook/animation',\n    title: 'Animation',\n    description:\n      'Base UI components can be animated with CSS transitions, CSS animations, or JavaScript libraries.',\n  },\n  {\n    href: '/react/handbook/composition',\n    title: 'Composition',\n    description:\n      'Base UI components can be replaced and composed with your own existing components.',\n  },\n] as const;\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/navigation-menu/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoNavigationMenuHero = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/navigation-menu/demos/hero/tailwind/index.tsx",
    "content": "import * as React from 'react';\nimport { NavigationMenu } from '@base-ui/react/navigation-menu';\n\nexport default function ExampleNavigationMenu() {\n  return (\n    <NavigationMenu.Root className=\"min-w-max rounded-lg bg-gray-50 p-1 text-gray-900\">\n      <NavigationMenu.List className=\"relative flex\">\n        <NavigationMenu.Item>\n          <NavigationMenu.Trigger className={triggerClassName}>\n            Overview\n            <NavigationMenu.Icon className=\"transition-transform duration-200 ease-in-out data-[popup-open]:rotate-180\">\n              <ChevronDownIcon />\n            </NavigationMenu.Icon>\n          </NavigationMenu.Trigger>\n\n          <NavigationMenu.Content className={contentClassName}>\n            <ul className=\"grid list-none grid-cols-1 gap-0 [@media(min-width:32rem)]:grid-cols-[12rem_12rem]\">\n              {overviewLinks.map((item) => (\n                <li key={item.href}>\n                  <Link href={item.href} className={linkCardClassName}>\n                    <h3 className=\"m-0 mb-1 text-base leading-5 font-normal\">{item.title}</h3>\n                    <p className=\"m-0 text-sm leading-5 text-gray-500\">{item.description}</p>\n                  </Link>\n                </li>\n              ))}\n            </ul>\n          </NavigationMenu.Content>\n        </NavigationMenu.Item>\n\n        <NavigationMenu.Item>\n          <NavigationMenu.Trigger className={triggerClassName}>\n            Handbook\n            <NavigationMenu.Icon className=\"transition-transform duration-200 ease-in-out data-[popup-open]:rotate-180\">\n              <ChevronDownIcon />\n            </NavigationMenu.Icon>\n          </NavigationMenu.Trigger>\n\n          <NavigationMenu.Content className={contentClassName}>\n            <ul className=\"flex max-w-[400px] flex-col justify-center\">\n              {handbookLinks.map((item) => (\n                <li key={item.href}>\n                  <Link href={item.href} className={linkCardClassName}>\n                    <h3 className=\"m-0 mb-1 text-base leading-5 font-normal\">{item.title}</h3>\n                    <p className=\"m-0 text-sm leading-5 text-gray-500\">{item.description}</p>\n                  </Link>\n                </li>\n              ))}\n            </ul>\n          </NavigationMenu.Content>\n        </NavigationMenu.Item>\n\n        <NavigationMenu.Item>\n          <Link className={triggerClassName} href=\"https://github.com/mui/base-ui\">\n            GitHub\n          </Link>\n        </NavigationMenu.Item>\n      </NavigationMenu.List>\n\n      <NavigationMenu.Portal>\n        <NavigationMenu.Positioner\n          sideOffset={10}\n          collisionPadding={{ top: 5, bottom: 5, left: 20, right: 20 }}\n          collisionAvoidance={{ side: 'none' }}\n          className=\"box-border h-[var(--positioner-height)] w-[var(--positioner-width)] max-w-[var(--available-width)] transition-[top,left,right,bottom] duration-[var(--duration)] ease-[var(--easing)] before:absolute before:content-[''] data-[instant]:transition-none data-[side=bottom]:before:top-[-10px] data-[side=bottom]:before:right-0 data-[side=bottom]:before:left-0 data-[side=bottom]:before:h-2.5 data-[side=left]:before:top-0 data-[side=left]:before:right-[-10px] data-[side=left]:before:bottom-0 data-[side=left]:before:w-2.5 data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:left-[-10px] data-[side=right]:before:w-2.5 data-[side=top]:before:right-0 data-[side=top]:before:bottom-[-10px] data-[side=top]:before:left-0 data-[side=top]:before:h-2.5\"\n          style={{\n            ['--duration' as string]: '0.35s',\n            ['--easing' as string]: 'cubic-bezier(0.22, 1, 0.36, 1)',\n          }}\n        >\n          <NavigationMenu.Popup className=\"data-[ending-style]:easing-[ease] relative h-[var(--popup-height)] origin-[var(--transform-origin)] rounded-lg bg-[canvas] text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 transition-[opacity,transform,width,height,scale,translate] duration-[var(--duration)] ease-[var(--easing)] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[ending-style]:duration-150 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 w-[var(--popup-width)] dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300\">\n            <NavigationMenu.Arrow className=\"flex transition-[left] duration-[var(--duration)] ease-[var(--easing)] data-[side=bottom]:top-[-8px] data-[side=left]:right-[-13px] data-[side=left]:rotate-90 data-[side=right]:left-[-13px] data-[side=right]:-rotate-90 data-[side=top]:bottom-[-8px] data-[side=top]:rotate-180\">\n              <ArrowSvg />\n            </NavigationMenu.Arrow>\n            <NavigationMenu.Viewport className=\"relative h-full w-full overflow-hidden\" />\n          </NavigationMenu.Popup>\n        </NavigationMenu.Positioner>\n      </NavigationMenu.Portal>\n    </NavigationMenu.Root>\n  );\n}\n\nfunction Link(props: NavigationMenu.Link.Props) {\n  return (\n    <NavigationMenu.Link\n      render={\n        // Use the `render` prop to render your framework's Link component\n        // for client-side routing.\n        // e.g. `<NextLink href={props.href} />` instead of `<a />`.\n        <a />\n      }\n      {...props}\n    />\n  );\n}\n\nfunction ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M1 3.5L5 7.5L9 3.5\" stroke=\"currentColor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className=\"fill-[canvas]\"\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className=\"fill-gray-200 dark:fill-none\"\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className=\"dark:fill-gray-300\"\n      />\n    </svg>\n  );\n}\n\nconst triggerClassName =\n  'box-border flex items-center justify-center gap-1.5 h-10 ' +\n  'px-2 [@media(min-width:32rem)]:px-3.5 m-0 rounded-md bg-gray-50 text-gray-900 font-normal ' +\n  'text-[0.925rem] [@media(min-width:32rem)]:text-base leading-6 select-none no-underline ' +\n  'hover:bg-gray-100 active:bg-gray-100 data-[popup-open]:bg-gray-100 ' +\n  'focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 focus-visible:relative';\n\nconst contentClassName =\n  'w-[calc(100vw_-_40px)] h-full p-6 [@media(min-width:32rem)]:w-max [@media(min-width:32rem)]:min-w-[400px] ' +\n  'transition-[opacity,transform,translate] duration-[var(--duration)] ease-[var(--easing)] ' +\n  'data-[starting-style]:opacity-0 data-[ending-style]:opacity-0 ' +\n  'data-[starting-style]:data-[activation-direction=left]:translate-x-[-50%] ' +\n  'data-[starting-style]:data-[activation-direction=right]:translate-x-[50%] ' +\n  'data-[ending-style]:data-[activation-direction=left]:translate-x-[50%] ' +\n  'data-[ending-style]:data-[activation-direction=right]:translate-x-[-50%]';\n\nconst linkCardClassName =\n  'block rounded-md p-2 [@media(min-width:32rem)]:p-3 no-underline text-inherit ' +\n  'hover:bg-gray-100 focus-visible:relative focus-visible:outline-2 ' +\n  'focus-visible:-outline-offset-1 focus-visible:outline-blue-800';\n\nconst overviewLinks = [\n  {\n    href: '/react/overview/quick-start',\n    title: 'Quick Start',\n    description: 'Install and assemble your first component.',\n  },\n  {\n    href: '/react/overview/accessibility',\n    title: 'Accessibility',\n    description: 'Learn how we build accessible components.',\n  },\n  {\n    href: '/react/overview/releases',\n    title: 'Releases',\n    description: 'See what’s new in the latest Base UI versions.',\n  },\n  {\n    href: '/react/overview/about',\n    title: 'About',\n    description: 'Learn more about Base UI and our mission.',\n  },\n] as const;\n\nconst handbookLinks = [\n  {\n    href: '/react/handbook/styling',\n    title: 'Styling',\n    description:\n      'Base UI components can be styled with plain CSS, Tailwind CSS, CSS-in-JS, or CSS Modules.',\n  },\n  {\n    href: '/react/handbook/animation',\n    title: 'Animation',\n    description:\n      'Base UI components can be animated with CSS transitions, CSS animations, or JavaScript libraries.',\n  },\n  {\n    href: '/react/handbook/composition',\n    title: 'Composition',\n    description:\n      'Base UI components can be replaced and composed with your own existing components.',\n  },\n] as const;\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/navigation-menu/demos/nested/css-modules/index.module.css",
    "content": ".Root {\n  background-color: var(--color-gray-50);\n  border-radius: 0.5rem;\n  padding: 0.25rem;\n  color: var(--color-gray-900);\n  min-width: max-content;\n}\n\n.List {\n  display: flex;\n  position: relative;\n  list-style: none;\n  padding: 0;\n  margin: 0;\n}\n\n.Trigger {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: 0.375rem;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: none;\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n  text-decoration: none;\n\n  @media (max-width: 640px) {\n    font-size: 0.925rem;\n    padding: 0 0.5rem;\n  }\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    position: relative;\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Icon {\n  transition: transform 0.2s ease;\n\n  &[data-popup-open] {\n    transform: rotate(180deg);\n  }\n}\n\n.Positioner {\n  --easing: cubic-bezier(0.22, 1, 0.36, 1);\n  --duration: 0.35s;\n  box-sizing: border-box;\n  transition-property: top, left, right, bottom;\n  transition-duration: var(--duration);\n  transition-timing-function: var(--easing);\n  width: var(--positioner-width);\n  height: var(--positioner-height);\n  max-width: var(--available-width);\n\n  &::before {\n    content: '';\n    position: absolute;\n  }\n\n  &[data-side='top']::before {\n    left: 0;\n    right: 0;\n    bottom: -10px;\n    height: 10px;\n  }\n\n  &[data-side='bottom']::before {\n    left: 0;\n    right: 0;\n    top: -10px;\n    height: 10px;\n  }\n\n  &[data-side='left']::before {\n    top: 0;\n    bottom: 0;\n    right: -10px;\n    width: 10px;\n  }\n\n  &[data-side='right']::before {\n    top: 0;\n    bottom: 0;\n    left: -10px;\n    width: 10px;\n  }\n\n  &[data-instant] {\n    transition: none;\n  }\n}\n\n.Popup {\n  position: relative;\n  overflow: visible;\n  box-sizing: border-box;\n  border-radius: 0.5rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  transform-origin: var(--transform-origin);\n  transition-property: opacity, transform, width, height;\n  transition-duration: var(--duration);\n  transition-timing-function: var(--easing);\n  width: var(--popup-width);\n  height: var(--popup-height);\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  &[data-ending-style] {\n    transition-timing-function: ease;\n    transition-duration: 0.15s;\n  }\n}\n\n@media (prefers-color-scheme: light) {\n  .Popup {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n}\n\n@media (prefers-color-scheme: dark) {\n  .Popup {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Content {\n  box-sizing: border-box;\n  transition:\n    opacity calc(var(--duration) * 0.5) ease,\n    transform var(--duration) var(--easing);\n  padding: 1.5rem;\n  width: calc(100vw - 40px);\n  height: 100%;\n\n  @media (min-width: 500px) {\n    width: max-content;\n    min-width: 400px;\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n  }\n\n  &[data-starting-style] {\n    &[data-activation-direction='left'] {\n      transform: translateX(-50%);\n    }\n    &[data-activation-direction='right'] {\n      transform: translateX(50%);\n    }\n  }\n\n  &[data-ending-style] {\n    &[data-activation-direction='left'] {\n      transform: translateX(50%);\n    }\n    &[data-activation-direction='right'] {\n      transform: translateX(-50%);\n    }\n  }\n}\n\n.Viewport {\n  position: relative;\n  overflow: hidden;\n  width: 100%;\n  height: 100%;\n}\n\n.GridLinkList {\n  display: grid;\n  grid-template-columns: 1fr;\n  padding: 0;\n  margin: 0;\n  list-style: none;\n\n  @media (min-width: 640px) {\n    grid-template-columns: 12rem 12rem;\n  }\n}\n\n.FlexLinkList {\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  max-width: 400px;\n  padding: 0;\n  margin: 0;\n  list-style: none;\n}\n\n.LinkCard {\n  box-sizing: border-box;\n  position: relative;\n  display: block;\n  width: 100%;\n  height: 100%;\n  padding: 0.5rem;\n  border-radius: 0.375rem;\n  text-decoration: none;\n  color: inherit;\n  text-align: left;\n  border: none;\n  background-color: transparent;\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-50);\n  }\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:focus-visible {\n    position: relative;\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n\n  @media (min-width: 500px) {\n    padding: 0.75rem;\n  }\n}\n\n.LinkTitle {\n  margin: 0 0 4px;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.25rem;\n}\n\n.LinkDescription {\n  margin: 0;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  color: var(--color-gray-500);\n}\n\n.Arrow {\n  display: flex;\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n\n.NestedIcon {\n  position: absolute;\n  top: 50%;\n  right: 0.6rem;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 0.6rem;\n  height: 0.6rem;\n  margin-top: -0.3rem;\n  transition: transform 0.2s ease;\n\n  &[data-popup-open] {\n    transform: rotate(180deg);\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/navigation-menu/demos/nested/css-modules/index.tsx",
    "content": "import * as React from 'react';\nimport { NavigationMenu } from '@base-ui/react/navigation-menu';\nimport styles from './index.module.css';\n\nexport default function ExampleNavigationMenu() {\n  return (\n    <NavigationMenu.Root className={styles.Root}>\n      <NavigationMenu.List className={styles.List}>\n        <NavigationMenu.Item>\n          <NavigationMenu.Trigger className={styles.Trigger}>\n            Overview\n            <NavigationMenu.Icon className={styles.Icon}>\n              <ChevronDownIcon />\n            </NavigationMenu.Icon>\n          </NavigationMenu.Trigger>\n          <NavigationMenu.Content className={styles.Content}>\n            <ul className={styles.GridLinkList}>\n              {overviewLinks.map((item) => (\n                <li key={item.href}>\n                  <Link className={styles.LinkCard} href={item.href}>\n                    <h3 className={styles.LinkTitle}>{item.title}</h3>\n                    <p className={styles.LinkDescription}>{item.description}</p>\n                  </Link>\n                </li>\n              ))}\n              <li>\n                <NavigationMenu.Root orientation=\"vertical\">\n                  <NavigationMenu.List>\n                    <NavigationMenu.Item>\n                      <NavigationMenu.Trigger className={styles.LinkCard}>\n                        <span className={styles.LinkTitle}>Handbook</span>\n                        <p className={styles.LinkDescription}>How to use Base UI effectively.</p>\n                        <NavigationMenu.Icon className={styles.NestedIcon}>\n                          <ChevronRightIcon />\n                        </NavigationMenu.Icon>\n                      </NavigationMenu.Trigger>\n                      <NavigationMenu.Content className={styles.Content}>\n                        <ul className={styles.FlexLinkList}>\n                          {handbookLinks.map((item) => (\n                            <li key={item.href}>\n                              <Link className={styles.LinkCard} href={item.href}>\n                                <h3 className={styles.LinkTitle}>{item.title}</h3>\n                                <p className={styles.LinkDescription}>{item.description}</p>\n                              </Link>\n                            </li>\n                          ))}\n                        </ul>\n                      </NavigationMenu.Content>\n                    </NavigationMenu.Item>\n                  </NavigationMenu.List>\n\n                  <NavigationMenu.Portal>\n                    <NavigationMenu.Positioner\n                      className={styles.Positioner}\n                      sideOffset={24}\n                      alignOffset={-24}\n                      align=\"end\"\n                      side=\"right\"\n                    >\n                      <NavigationMenu.Popup className={styles.Popup}>\n                        <NavigationMenu.Viewport className={styles.Viewport} />\n                      </NavigationMenu.Popup>\n                    </NavigationMenu.Positioner>\n                  </NavigationMenu.Portal>\n                </NavigationMenu.Root>\n              </li>\n            </ul>\n          </NavigationMenu.Content>\n        </NavigationMenu.Item>\n      </NavigationMenu.List>\n\n      <NavigationMenu.Portal>\n        <NavigationMenu.Positioner\n          className={styles.Positioner}\n          sideOffset={10}\n          collisionPadding={{ top: 5, bottom: 5, left: 20, right: 20 }}\n        >\n          <NavigationMenu.Popup className={styles.Popup}>\n            <NavigationMenu.Arrow className={styles.Arrow}>\n              <ArrowSvg />\n            </NavigationMenu.Arrow>\n            <NavigationMenu.Viewport className={styles.Viewport} />\n          </NavigationMenu.Popup>\n        </NavigationMenu.Positioner>\n      </NavigationMenu.Portal>\n    </NavigationMenu.Root>\n  );\n}\n\nfunction Link(props: NavigationMenu.Link.Props) {\n  return (\n    <NavigationMenu.Link\n      render={\n        // Use the `render` prop to render your framework's Link component\n        // for client-side routing.\n        // e.g. `<NextLink href={props.href} />` instead of `<a />`.\n        <a />\n      }\n      {...props}\n    />\n  );\n}\n\nfunction ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M1 3.5L5 7.5L9 3.5\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n\nfunction ChevronRightIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M3.5 1L7.5 5L3.5 9\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nconst overviewLinks = [\n  {\n    href: '/react/overview/quick-start',\n    title: 'Quick Start',\n    description: 'Install and assemble your first component.',\n  },\n  {\n    href: '/react/overview/accessibility',\n    title: 'Accessibility',\n    description: 'Learn how we build accessible components.',\n  },\n  {\n    href: '/react/overview/releases',\n    title: 'Releases',\n    description: 'See what’s new in the latest Base UI versions.',\n  },\n] as const;\n\nconst handbookLinks = [\n  {\n    href: '/react/handbook/styling',\n    title: 'Styling',\n    description:\n      'Base UI components can be styled with plain CSS, Tailwind CSS, CSS-in-JS, or CSS Modules.',\n  },\n  {\n    href: '/react/handbook/animation',\n    title: 'Animation',\n    description:\n      'Base UI components can be animated with CSS transitions, CSS animations, or JavaScript libraries.',\n  },\n  {\n    href: '/react/handbook/composition',\n    title: 'Composition',\n    description:\n      'Base UI components can be replaced and composed with your own existing components.',\n  },\n] as const;\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/navigation-menu/demos/nested/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoNavigationMenuNested = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/navigation-menu/demos/nested/tailwind/index.tsx",
    "content": "import * as React from 'react';\nimport { NavigationMenu } from '@base-ui/react/navigation-menu';\n\nexport default function ExampleNavigationMenu() {\n  return (\n    <NavigationMenu.Root className=\"min-w-max rounded-lg bg-gray-50 p-1 text-gray-900\">\n      <NavigationMenu.List className=\"relative flex\">\n        <NavigationMenu.Item>\n          <NavigationMenu.Trigger className={triggerClassName}>\n            Overview\n            <NavigationMenu.Icon className=\"transition-transform duration-200 ease-in-out data-[popup-open]:rotate-180\">\n              <ChevronDownIcon />\n            </NavigationMenu.Icon>\n          </NavigationMenu.Trigger>\n\n          <NavigationMenu.Content className={contentClassName}>\n            <ul className=\"grid list-none grid-cols-1 gap-0 sm:grid-cols-[12rem_12rem]\">\n              {overviewLinks.map((item) => (\n                <li key={item.href}>\n                  <Link href={item.href} className={linkCardClassName}>\n                    <h3 className=\"m-0 mb-1 text-base leading-5 font-normal\">{item.title}</h3>\n                    <p className=\"m-0 text-sm leading-5 text-gray-500\">{item.description}</p>\n                  </Link>\n                </li>\n              ))}\n              <li>\n                <NavigationMenu.Root orientation=\"vertical\">\n                  <NavigationMenu.Item>\n                    <NavigationMenu.Trigger className={linkCardClassName}>\n                      <span className=\"m-0 mb-1 text-base leading-5 font-normal\">Handbook</span>\n                      <p className=\"m-0 text-sm leading-5 text-gray-500\">\n                        How to use Base UI effectively.\n                      </p>\n                      <NavigationMenu.Icon className=\"absolute top-1/2 right-2.5 flex h-2.5 w-2.5 -translate-y-1/2 items-center justify-center transition-transform duration-200 ease-in-out data-[popup-open]:rotate-180\">\n                        <ChevronRightIcon />\n                      </NavigationMenu.Icon>\n                    </NavigationMenu.Trigger>\n                    <NavigationMenu.Content className={contentClassName}>\n                      <ul className=\"flex max-w-[400px] flex-col justify-center\">\n                        {handbookLinks.map((item) => (\n                          <li key={item.href}>\n                            <Link href={item.href} className={linkCardClassName}>\n                              <h3 className=\"m-0 mb-1 text-base leading-5 font-normal\">\n                                {item.title}\n                              </h3>\n                              <p className=\"m-0 text-sm leading-5 text-gray-500\">\n                                {item.description}\n                              </p>\n                            </Link>\n                          </li>\n                        ))}\n                      </ul>\n                    </NavigationMenu.Content>\n                  </NavigationMenu.Item>\n\n                  <NavigationMenu.Portal>\n                    <NavigationMenu.Positioner\n                      sideOffset={24}\n                      alignOffset={-24}\n                      align=\"end\"\n                      side=\"right\"\n                      className=\"box-border h-[var(--positioner-height)] w-[var(--positioner-width)] max-w-[var(--available-width)] transition-[top,left,right,bottom] duration-[var(--duration)] ease-[var(--easing)] before:absolute before:content-[''] data-[instant]:transition-none data-[side=bottom]:before:top-[-10px] data-[side=bottom]:before:right-0 data-[side=bottom]:before:left-0 data-[side=bottom]:before:h-2.5 data-[side=left]:before:top-0 data-[side=left]:before:right-[-10px] data-[side=left]:before:bottom-0 data-[side=left]:before:w-2.5 data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:left-[-10px] data-[side=right]:before:w-2.5 data-[side=top]:before:right-0 data-[side=top]:before:bottom-[-10px] data-[side=top]:before:left-0 data-[side=top]:before:h-2.5\"\n                      style={{\n                        ['--duration' as string]: '0.35s',\n                        ['--easing' as string]: 'cubic-bezier(0.22, 1, 0.36, 1)',\n                      }}\n                    >\n                      <NavigationMenu.Popup className=\"data-[ending-style]:easing-[ease] relative h-[var(--popup-height)] w-[var(--popup-width)] origin-[var(--transform-origin)] rounded-lg bg-[canvas] text-gray-900 shadow-lg shadow-gray-200 outline outline-1 outline-gray-200 transition-[opacity,transform,width,height,scale,translate] duration-[var(--duration)] ease-[var(--easing)] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[ending-style]:duration-150 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300\">\n                        <NavigationMenu.Viewport className=\"relative h-full w-full overflow-hidden\" />\n                      </NavigationMenu.Popup>\n                    </NavigationMenu.Positioner>\n                  </NavigationMenu.Portal>\n                </NavigationMenu.Root>\n              </li>\n            </ul>\n          </NavigationMenu.Content>\n        </NavigationMenu.Item>\n      </NavigationMenu.List>\n\n      <NavigationMenu.Portal>\n        <NavigationMenu.Positioner\n          sideOffset={10}\n          collisionPadding={{ top: 5, bottom: 5, left: 20, right: 20 }}\n          className=\"box-border h-[var(--positioner-height)] w-[var(--positioner-width)] max-w-[var(--available-width)] transition-[top,left,right,bottom] duration-[var(--duration)] ease-[var(--easing)] before:absolute before:content-[''] data-[instant]:transition-none data-[side=bottom]:before:top-[-10px] data-[side=bottom]:before:right-0 data-[side=bottom]:before:left-0 data-[side=bottom]:before:h-2.5 data-[side=left]:before:top-0 data-[side=left]:before:right-[-10px] data-[side=left]:before:bottom-0 data-[side=left]:before:w-2.5 data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:left-[-10px] data-[side=right]:before:w-2.5 data-[side=top]:before:right-0 data-[side=top]:before:bottom-[-10px] data-[side=top]:before:left-0 data-[side=top]:before:h-2.5\"\n          style={{\n            ['--duration' as string]: '0.35s',\n            ['--easing' as string]: 'cubic-bezier(0.22, 1, 0.36, 1)',\n          }}\n        >\n          <NavigationMenu.Popup className=\"data-[ending-style]:easing-[ease] relative h-[var(--popup-height)] origin-[var(--transform-origin)] rounded-lg bg-[canvas] text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 transition-[opacity,transform,width,height,scale,translate] duration-[var(--duration)] ease-[var(--easing)] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[ending-style]:duration-150 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 w-[var(--popup-width)] dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300\">\n            <NavigationMenu.Arrow className=\"flex transition-[left] duration-[var(--duration)] ease-[var(--easing)] data-[side=bottom]:top-[-8px] data-[side=left]:right-[-13px] data-[side=left]:rotate-90 data-[side=right]:left-[-13px] data-[side=right]:-rotate-90 data-[side=top]:bottom-[-8px] data-[side=top]:rotate-180\">\n              <ArrowSvg />\n            </NavigationMenu.Arrow>\n            <NavigationMenu.Viewport className=\"relative h-full w-full overflow-hidden\" />\n          </NavigationMenu.Popup>\n        </NavigationMenu.Positioner>\n      </NavigationMenu.Portal>\n    </NavigationMenu.Root>\n  );\n}\n\nfunction Link(props: NavigationMenu.Link.Props) {\n  return (\n    <NavigationMenu.Link\n      render={\n        // Use the `render` prop to render your framework's Link component\n        // for client-side routing.\n        // e.g. `<NextLink href={props.href} />` instead of `<a />`.\n        <a />\n      }\n      {...props}\n    />\n  );\n}\n\nfunction ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M1 3.5L5 7.5L9 3.5\" stroke=\"currentColor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n\nfunction ChevronRightIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M3.5 1L7.5 5L3.5 9\" stroke=\"currentColor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className=\"fill-[canvas]\"\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className=\"fill-gray-200 dark:fill-none\"\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className=\"dark:fill-gray-300\"\n      />\n    </svg>\n  );\n}\n\nconst triggerClassName =\n  'box-border flex items-center justify-center gap-1.5 h-10 ' +\n  'px-2 sm:px-3.5 m-0 rounded-md bg-gray-50 text-gray-900 font-normal ' +\n  'text-[0.925rem] sm:text-base leading-6 select-none no-underline ' +\n  'hover:bg-gray-100 active:bg-gray-100 data-[popup-open]:bg-gray-100 ' +\n  'focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 focus-visible:relative';\n\nconst contentClassName =\n  'w-[calc(100vw_-_40px)] h-full p-6 [@media(min-width:32rem)]:w-max [@media(min-width:32rem)]:min-w-[400px] ' +\n  'transition-[opacity,transform,translate] duration-[var(--duration)] ease-[var(--easing)] ' +\n  'data-[starting-style]:opacity-0 data-[ending-style]:opacity-0 ' +\n  'data-[starting-style]:data-[activation-direction=left]:translate-x-[-50%] ' +\n  'data-[starting-style]:data-[activation-direction=right]:translate-x-[50%] ' +\n  'data-[ending-style]:data-[activation-direction=left]:translate-x-[50%] ' +\n  'data-[ending-style]:data-[activation-direction=right]:translate-x-[-50%]';\n\nconst linkCardClassName =\n  'w-full text-left relative block rounded-md p-2 sm:p-3 no-underline text-inherit ' +\n  'hover:bg-gray-100 focus-visible:relative focus-visible:outline-2 ' +\n  'focus-visible:-outline-offset-1 focus-visible:outline-blue-800 ' +\n  'data-[popup-open]:bg-gray-100';\n\nconst overviewLinks = [\n  {\n    href: '/react/overview/quick-start',\n    title: 'Quick Start',\n    description: 'Install and assemble your first component.',\n  },\n  {\n    href: '/react/overview/accessibility',\n    title: 'Accessibility',\n    description: 'Learn how we build accessible components.',\n  },\n  {\n    href: '/react/overview/releases',\n    title: 'Releases',\n    description: 'See what’s new in the latest Base UI versions.',\n  },\n] as const;\n\nconst handbookLinks = [\n  {\n    href: '/react/handbook/styling',\n    title: 'Styling',\n    description:\n      'Base UI components can be styled with plain CSS, Tailwind CSS, CSS-in-JS, or CSS Modules.',\n  },\n  {\n    href: '/react/handbook/animation',\n    title: 'Animation',\n    description:\n      'Base UI components can be animated with CSS transitions, CSS animations, or JavaScript libraries.',\n  },\n  {\n    href: '/react/handbook/composition',\n    title: 'Composition',\n    description:\n      'Base UI components can be replaced and composed with your own existing components.',\n  },\n] as const;\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/navigation-menu/demos/nested-inline/css-modules/index.module.css",
    "content": ".Root {\n  background-color: var(--color-gray-50);\n  border-radius: 0.5rem;\n  padding: 0.25rem;\n  color: var(--color-gray-900);\n  min-width: max-content;\n}\n\n.List {\n  display: flex;\n  position: relative;\n  list-style: none;\n  padding: 0;\n  margin: 0;\n}\n\n.Trigger {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: 0.375rem;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: none;\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n  text-decoration: none;\n\n  @media (max-width: 640px) {\n    font-size: 0.925rem;\n    padding: 0 0.5rem;\n  }\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    position: relative;\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Icon {\n  transition: transform 0.2s ease;\n\n  &[data-popup-open] {\n    transform: rotate(180deg);\n  }\n}\n\n.Positioner {\n  --easing: cubic-bezier(0.22, 1, 0.36, 1);\n  --duration: 0.35s;\n  box-sizing: border-box;\n  transition-property: top, left, right, bottom;\n  transition-duration: var(--duration);\n  transition-timing-function: var(--easing);\n  width: var(--positioner-width);\n  height: var(--positioner-height);\n  max-width: var(--available-width);\n\n  &::before {\n    content: '';\n    position: absolute;\n  }\n\n  &[data-side='top']::before {\n    left: 0;\n    right: 0;\n    bottom: -10px;\n    height: 10px;\n  }\n\n  &[data-side='bottom']::before {\n    left: 0;\n    right: 0;\n    top: -10px;\n    height: 10px;\n  }\n\n  &[data-side='left']::before {\n    top: 0;\n    bottom: 0;\n    right: -10px;\n    width: 10px;\n  }\n\n  &[data-side='right']::before {\n    top: 0;\n    bottom: 0;\n    left: -10px;\n    width: 10px;\n  }\n\n  &[data-instant] {\n    transition: none;\n  }\n}\n\n.Popup {\n  position: relative;\n  overflow: visible;\n  box-sizing: border-box;\n  border-radius: 0.5rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  transform-origin: var(--transform-origin);\n  transition-property: opacity, transform, width, height;\n  transition-duration: var(--duration);\n  transition-timing-function: var(--easing);\n  width: var(--popup-width);\n  height: var(--popup-height);\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  &[data-ending-style] {\n    transition-property: opacity, transform;\n    transition-timing-function: ease;\n    transition-duration: 0.15s;\n  }\n}\n\n@media (prefers-color-scheme: light) {\n  .Popup {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n}\n\n@media (prefers-color-scheme: dark) {\n  .Popup {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Content {\n  box-sizing: border-box;\n  transition:\n    opacity calc(var(--duration) * 0.5) ease,\n    transform var(--duration) cubic-bezier(0.4, 0, 0.2, 1);\n  padding: 1.25rem;\n  width: calc(100vw - 40px);\n  height: 100%;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n  }\n\n  &[data-starting-style] {\n    &[data-activation-direction='left'] {\n      transform: translateX(-2rem);\n    }\n    &[data-activation-direction='right'] {\n      transform: translateX(2rem);\n    }\n  }\n\n  &[data-ending-style] {\n    transition-duration: calc(var(--duration) * 0.5);\n    transition-timing-function: ease;\n\n    &[data-activation-direction='left'] {\n      transform: translateX(2rem);\n    }\n    &[data-activation-direction='right'] {\n      transform: translateX(-2rem);\n    }\n  }\n}\n\n.ProductContent {\n  padding: 0;\n\n  @media (min-width: 700px) {\n    width: min(675px, calc(100vw - 40px));\n  }\n}\n\n.GuidesContent {\n  padding: 0;\n\n  @media (min-width: 700px) {\n    width: min(500px, calc(100vw - 40px));\n  }\n}\n\n.Viewport {\n  position: relative;\n  overflow: hidden;\n  width: 100%;\n  height: 100%;\n}\n\n.SubmenuRoot {\n  color: var(--color-gray-900);\n  overflow: hidden;\n  overflow: clip;\n}\n\n.SubmenuLayout {\n  display: grid;\n  grid-template-columns: 1fr;\n  border-radius: 0.5rem;\n  overflow: hidden;\n  overflow: clip;\n}\n\n@media (min-width: 700px) {\n  .SubmenuLayout {\n    grid-template-columns: 13rem minmax(0, 1fr);\n  }\n}\n\n.SubmenuList {\n  list-style: none;\n  margin: 0;\n  padding: 1rem;\n  display: flex;\n  flex-direction: row;\n  gap: 0.25rem;\n  overflow-x: auto;\n  background-color: var(--color-gray-100);\n}\n\n@media (prefers-color-scheme: dark) {\n  .SubmenuList {\n    background-color: rgb(0 0 0 / 20%);\n  }\n}\n\n@media (min-width: 700px) {\n  .SubmenuList {\n    box-sizing: border-box;\n    flex-direction: column;\n    gap: 0;\n    overflow-x: visible;\n    overflow-y: auto;\n    border-right: 1px solid var(--color-gray-200);\n    height: var(--popup-height);\n    transition: height var(--duration) var(--easing);\n  }\n}\n\n@media (min-width: 700px) and (prefers-color-scheme: dark) {\n  .SubmenuList {\n    border-right-color: var(--color-gray-300);\n  }\n}\n\n.SubmenuTrigger {\n  box-sizing: border-box;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n  gap: 0.25rem;\n  width: 100%;\n  min-width: 10rem;\n  margin: 0;\n  padding: 0.625rem 0.75rem;\n  border: 0;\n  border-radius: 0.5rem;\n  background-color: transparent;\n  color: inherit;\n  font-family: inherit;\n  text-align: left;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &[data-popup-open] {\n    background-color: canvas;\n    box-shadow:\n      0 1px 2px rgb(0 0 0 / 8%),\n      0 1px 1px rgb(0 0 0 / 4%);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n@media (prefers-color-scheme: dark) {\n  .SubmenuTrigger[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n}\n\n.SubmenuLabel {\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.2;\n  color: var(--color-gray-900);\n}\n\n.SubmenuHint {\n  font-size: 0.875rem;\n  line-height: 1.35;\n  color: var(--color-gray-500);\n}\n\n.SubmenuViewport {\n  position: relative;\n  overflow: hidden;\n  min-height: 16.5rem;\n  border-top: 1px solid var(--color-gray-200);\n}\n\n@media (prefers-color-scheme: dark) {\n  .SubmenuViewport {\n    border-top-color: var(--color-gray-300);\n  }\n}\n\n@media (min-width: 700px) {\n  .SubmenuViewport {\n    border-top: 0;\n  }\n}\n\n.SubmenuContent {\n  height: 100%;\n  padding: 1.75rem;\n  transform: translateX(0);\n  transition:\n    opacity var(--duration) var(--easing),\n    transform var(--duration) var(--easing),\n    filter var(--duration) var(--easing);\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n  }\n\n  &[data-starting-style] {\n    &[data-activation-direction='left'] {\n      transform: translateX(-50%);\n    }\n\n    &[data-activation-direction='right'] {\n      transform: translateX(50%);\n    }\n  }\n\n  &[data-ending-style] {\n    &[data-activation-direction='left'] {\n      transform: translateX(50%);\n    }\n\n    &[data-activation-direction='right'] {\n      transform: translateX(-50%);\n    }\n  }\n\n  @media (min-width: 700px) {\n    padding: 2rem;\n    transform: translateY(0);\n    filter: blur(0);\n    transition-duration: calc(var(--duration) * 1.35);\n    transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1);\n\n    &[data-starting-style] {\n      &[data-activation-direction='up'] {\n        transform: translateY(-72px);\n      }\n\n      &[data-activation-direction='down'] {\n        transform: translateY(72px);\n      }\n    }\n\n    &[data-ending-style] {\n      &[data-activation-direction='up'] {\n        transform: translateY(72px);\n      }\n\n      &[data-activation-direction='down'] {\n        transform: translateY(-72px);\n      }\n    }\n\n    &[data-starting-style],\n    &[data-ending-style] {\n      filter: blur(2px);\n    }\n  }\n}\n\n.SubmenuTitle {\n  margin: 0;\n  font-size: 1.125rem;\n  line-height: 1.3;\n  font-weight: 400;\n}\n\n.SubmenuDescription {\n  margin: 0.625rem 0 0;\n  color: var(--color-gray-500);\n  font-size: 1rem;\n  line-height: 1.5;\n}\n\n.LinkList {\n  list-style: none;\n  margin: 1rem -0.5rem 0;\n  padding: 0;\n  display: grid;\n  gap: 0;\n}\n\n.LinkCard {\n  box-sizing: border-box;\n  display: block;\n  padding: 0.75rem 0.5rem;\n  border-radius: 0.5rem;\n  text-decoration: none;\n  color: inherit;\n  background-color: transparent;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.LinkTitle {\n  margin: 0;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.25;\n}\n\n.LinkDescription {\n  margin: 0.35rem 0 0;\n  color: var(--color-gray-500);\n  font-size: 0.95rem;\n  line-height: 1.45;\n}\n\n.GuidesPanel {\n  padding: 1.75rem;\n\n  @media (min-width: 700px) {\n    padding: 2rem;\n  }\n}\n\n.Arrow {\n  display: flex;\n  transition: left calc(var(--duration)) var(--easing);\n\n  @media (max-width: 699px) {\n    display: none;\n  }\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/navigation-menu/demos/nested-inline/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { NavigationMenu } from '@base-ui/react/navigation-menu';\nimport { useMediaQuery } from '@base-ui/react/unstable-use-media-query';\nimport { audienceMenus, guideLinks, guidesPanel } from '../data';\nimport styles from './index.module.css';\n\nexport default function ExampleNavigationMenu() {\n  const isDesktop = useMediaQuery('(min-width: 700px)', { defaultMatches: true });\n\n  return (\n    <NavigationMenu.Root className={styles.Root}>\n      <NavigationMenu.List className={styles.List}>\n        <NavigationMenu.Item>\n          <NavigationMenu.Trigger className={styles.Trigger}>\n            Product\n            <NavigationMenu.Icon className={styles.Icon}>\n              <ChevronDownIcon />\n            </NavigationMenu.Icon>\n          </NavigationMenu.Trigger>\n          <NavigationMenu.Content className={`${styles.Content} ${styles.ProductContent}`}>\n            <NavigationMenu.Root\n              className={styles.SubmenuRoot}\n              orientation={isDesktop ? 'vertical' : 'horizontal'}\n              defaultValue=\"developers\"\n            >\n              <div className={styles.SubmenuLayout}>\n                <NavigationMenu.List className={styles.SubmenuList}>\n                  {audienceMenus.map((menu) => (\n                    <NavigationMenu.Item key={menu.value} value={menu.value}>\n                      <NavigationMenu.Trigger className={styles.SubmenuTrigger}>\n                        <span className={styles.SubmenuLabel}>{menu.label}</span>\n                        <span className={styles.SubmenuHint}>{menu.hint}</span>\n                      </NavigationMenu.Trigger>\n                      <NavigationMenu.Content className={styles.SubmenuContent}>\n                        <h4 className={styles.SubmenuTitle}>{menu.title}</h4>\n                        <p className={styles.SubmenuDescription}>{menu.description}</p>\n                        <ul className={styles.LinkList}>\n                          {menu.links.map((link) => (\n                            <li key={link.href}>\n                              <Link className={styles.LinkCard} href={link.href}>\n                                <h5 className={styles.LinkTitle}>{link.title}</h5>\n                                <p className={styles.LinkDescription}>{link.description}</p>\n                              </Link>\n                            </li>\n                          ))}\n                        </ul>\n                      </NavigationMenu.Content>\n                    </NavigationMenu.Item>\n                  ))}\n                </NavigationMenu.List>\n\n                <NavigationMenu.Viewport className={styles.SubmenuViewport} />\n              </div>\n            </NavigationMenu.Root>\n          </NavigationMenu.Content>\n        </NavigationMenu.Item>\n\n        <NavigationMenu.Item>\n          <NavigationMenu.Trigger className={styles.Trigger}>\n            Learn\n            <NavigationMenu.Icon className={styles.Icon}>\n              <ChevronDownIcon />\n            </NavigationMenu.Icon>\n          </NavigationMenu.Trigger>\n          <NavigationMenu.Content className={`${styles.Content} ${styles.GuidesContent}`}>\n            <div className={styles.GuidesPanel}>\n              <h4 className={styles.SubmenuTitle}>{guidesPanel.title}</h4>\n              <p className={styles.SubmenuDescription}>{guidesPanel.description}</p>\n              <ul className={styles.LinkList}>\n                {guideLinks.map((link) => (\n                  <li key={link.href}>\n                    <Link className={styles.LinkCard} href={link.href}>\n                      <h5 className={styles.LinkTitle}>{link.title}</h5>\n                      <p className={styles.LinkDescription}>{link.description}</p>\n                    </Link>\n                  </li>\n                ))}\n              </ul>\n            </div>\n          </NavigationMenu.Content>\n        </NavigationMenu.Item>\n\n        <NavigationMenu.Item>\n          <Link className={styles.Trigger} href=\"/react/overview/releases\">\n            Releases\n          </Link>\n        </NavigationMenu.Item>\n\n        <NavigationMenu.Item>\n          <Link className={styles.Trigger} href=\"https://github.com/mui/base-ui\">\n            GitHub\n          </Link>\n        </NavigationMenu.Item>\n      </NavigationMenu.List>\n\n      <NavigationMenu.Portal>\n        <NavigationMenu.Positioner\n          className={styles.Positioner}\n          sideOffset={10}\n          collisionPadding={{ top: 5, bottom: 5, left: 20, right: 20 }}\n          collisionAvoidance={{ side: 'none' }}\n        >\n          <NavigationMenu.Popup className={styles.Popup}>\n            <NavigationMenu.Arrow className={styles.Arrow}>\n              <ArrowSvg />\n            </NavigationMenu.Arrow>\n            <NavigationMenu.Viewport className={styles.Viewport} />\n          </NavigationMenu.Popup>\n        </NavigationMenu.Positioner>\n      </NavigationMenu.Portal>\n    </NavigationMenu.Root>\n  );\n}\n\nfunction Link(props: NavigationMenu.Link.Props) {\n  return (\n    <NavigationMenu.Link\n      render={\n        // Use the `render` prop to render your framework's Link component\n        // for client-side routing.\n        // e.g. `<NextLink href={props.href} />` instead of `<a />`.\n        <a />\n      }\n      {...props}\n    />\n  );\n}\n\nfunction ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M1 3.5L5 7.5L9 3.5\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/navigation-menu/demos/nested-inline/data.ts",
    "content": "export const audienceMenus = [\n  {\n    value: 'developers',\n    label: 'Developers',\n    hint: 'Go from idea to UI faster.',\n    title: 'Build product UI without giving up control',\n    description:\n      'Start with accessible parts and shape them to your app instead of working around a preset design system.',\n    links: [\n      {\n        href: '/react/overview/quick-start',\n        title: 'Quick start',\n        description: 'Install Base UI and get your first interactive primitive on screen fast.',\n      },\n      {\n        href: '/react/handbook/composition',\n        title: 'Composition',\n        description: 'Wrap and combine parts to match your product structure without hacks.',\n      },\n    ],\n  },\n  {\n    value: 'systems',\n    label: 'Design Systems',\n    hint: 'Keep patterns aligned across teams.',\n    title: 'Turn shared standards into working components',\n    description:\n      'Connect tokens, states, and accessibility rules once, then give every product team the same solid starting point.',\n    links: [\n      {\n        href: '/react/handbook/styling',\n        title: 'Styling',\n        description: 'Map tokens and component states to your own CSS or utility setup.',\n      },\n      {\n        href: '/react/overview/accessibility',\n        title: 'Accessibility',\n        description: 'Review keyboard support and semantic defaults before anything ships.',\n      },\n      {\n        href: '/react/components/tooltip',\n        title: 'Tooltip',\n        description: 'Set one clear pattern for lightweight help, hints, and field guidance.',\n      },\n      {\n        href: '/react/components/popover',\n        title: 'Popover',\n        description: 'Handle richer anchored panels like menus, inspectors, and onboarding.',\n      },\n    ],\n  },\n  {\n    value: 'managers',\n    label: 'Engineering Leads',\n    hint: 'Roll out shared UI without drag.',\n    title: 'Give squads clear defaults and room to move',\n    description:\n      'Use the docs to align on quality bars, upgrades, and extension points while still leaving teams space to customize.',\n    links: [\n      {\n        href: '/react/overview/releases',\n        title: 'Releases',\n        description: 'Track version changes and migration notes before upgrades surprise teams.',\n      },\n      {\n        href: '/react/handbook/typescript',\n        title: 'TypeScript',\n        description: 'See how the primitives type custom wrappers and shared abstractions.',\n      },\n      {\n        href: '/react/handbook/forms',\n        title: 'Forms',\n        description: 'Standardize validation and field patterns teams reach for constantly.',\n      },\n    ],\n  },\n  {\n    value: 'startups',\n    label: 'Startups',\n    hint: 'Ship polished basics while things change.',\n    title: 'Get sturdy UI foundations in place early',\n    description:\n      'Cover the hard interaction details now so your team can spend more time on the product ideas that actually differentiate you.',\n    links: [\n      {\n        href: '/react/overview/quick-start',\n        title: 'Quick start',\n        description: 'Get the package installed and your first component working in minutes.',\n      },\n      {\n        href: '/react/components/menu',\n        title: 'Menu',\n        description: 'Add action menus with keyboard support and focus handling already done.',\n      },\n      {\n        href: '/react/components/dialog',\n        title: 'Dialog',\n        description: 'Launch settings or upgrade flows without rebuilding focus management.',\n      },\n    ],\n  },\n] as const;\n\nexport const guidesPanel = {\n  title: 'Where teams usually start',\n  description:\n    'These are the docs people reach for first when they are turning a prototype into shared UI.',\n} as const;\n\nexport const guideLinks = [\n  {\n    href: '/react/overview/accessibility',\n    title: 'Accessibility handbook',\n    description: 'Take a practical pass over focus order, semantics, and keyboard support.',\n  },\n  {\n    href: '/react/handbook/composition',\n    title: 'Composition handbook',\n    description: 'Learn when to wrap parts, share behavior, and expose flexible APIs.',\n  },\n  {\n    href: '/react/handbook/styling',\n    title: 'Styling handbook',\n    description: 'Apply tokens and state styles without fighting the underlying markup.',\n  },\n] as const;\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/navigation-menu/demos/nested-inline/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoNavigationMenuNestedInline = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/navigation-menu/demos/nested-inline/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { NavigationMenu } from '@base-ui/react/navigation-menu';\nimport { useMediaQuery } from '@base-ui/react/unstable-use-media-query';\nimport { audienceMenus, guideLinks, guidesPanel } from '../data';\n\nexport default function ExampleNavigationMenu() {\n  const isDesktop = useMediaQuery('(min-width: 700px)', { defaultMatches: true });\n\n  return (\n    <NavigationMenu.Root className=\"min-w-max rounded-lg bg-gray-50 p-1 text-gray-900\">\n      <NavigationMenu.List className=\"relative flex\">\n        <NavigationMenu.Item>\n          <NavigationMenu.Trigger className={triggerClassName}>\n            Product\n            <NavigationMenu.Icon className=\"transition-transform duration-200 ease-in-out data-[popup-open]:rotate-180\">\n              <ChevronDownIcon />\n            </NavigationMenu.Icon>\n          </NavigationMenu.Trigger>\n\n          <NavigationMenu.Content className={productContentClassName}>\n            <NavigationMenu.Root\n              className=\"overflow-hidden text-gray-900\"\n              orientation={isDesktop ? 'vertical' : 'horizontal'}\n              defaultValue=\"developers\"\n            >\n              <div className=\"grid grid-cols-1 overflow-clip rounded-lg min-[700px]:grid-cols-[13rem_minmax(0,1fr)]\">\n                <NavigationMenu.List className=\"m-0 flex list-none flex-row gap-1 overflow-x-auto bg-gray-100 p-4 min-[700px]:box-border min-[700px]:h-[var(--popup-height)] min-[700px]:flex-col min-[700px]:gap-0 min-[700px]:overflow-x-visible min-[700px]:overflow-y-auto min-[700px]:border-r min-[700px]:border-r-gray-200 min-[700px]:transition-[height] min-[700px]:duration-[var(--duration)] min-[700px]:ease-[var(--easing)] dark:bg-black/20 dark:border-r-gray-300\">\n                  {audienceMenus.map((menu) => (\n                    <NavigationMenu.Item key={menu.value} value={menu.value}>\n                      <NavigationMenu.Trigger className={submenuTriggerClassName}>\n                        <span className=\"text-base leading-[1.2] font-normal text-gray-900\">\n                          {menu.label}\n                        </span>\n                        <span className=\"text-sm leading-[1.35] text-gray-500\">{menu.hint}</span>\n                      </NavigationMenu.Trigger>\n                      <NavigationMenu.Content className={submenuContentClassName}>\n                        <h4 className=\"m-0 text-[1.125rem] leading-[1.3] font-normal\">\n                          {menu.title}\n                        </h4>\n                        <p className=\"m-0 mt-2.5 text-base leading-[1.5] text-gray-500\">\n                          {menu.description}\n                        </p>\n                        <ul className=\"-mx-2 m-0 mt-4 grid list-none gap-0 p-0\">\n                          {menu.links.map((link) => (\n                            <li key={link.href}>\n                              <Link className={linkCardClassName} href={link.href}>\n                                <h5 className=\"m-0 text-base leading-[1.25] font-normal\">\n                                  {link.title}\n                                </h5>\n                                <p className=\"m-0 mt-[0.35rem] text-[0.95rem] leading-[1.45] text-gray-500\">\n                                  {link.description}\n                                </p>\n                              </Link>\n                            </li>\n                          ))}\n                        </ul>\n                      </NavigationMenu.Content>\n                    </NavigationMenu.Item>\n                  ))}\n                </NavigationMenu.List>\n                <NavigationMenu.Viewport className=\"relative min-h-[16.5rem] overflow-hidden border-t border-gray-200 min-[700px]:border-t-0 dark:border-gray-300\" />\n              </div>\n            </NavigationMenu.Root>\n          </NavigationMenu.Content>\n        </NavigationMenu.Item>\n\n        <NavigationMenu.Item>\n          <NavigationMenu.Trigger className={triggerClassName}>\n            Learn\n            <NavigationMenu.Icon className=\"transition-transform duration-200 ease-in-out data-[popup-open]:rotate-180\">\n              <ChevronDownIcon />\n            </NavigationMenu.Icon>\n          </NavigationMenu.Trigger>\n\n          <NavigationMenu.Content className={guidesContentClassName}>\n            <div className=\"p-7 text-gray-900 min-[700px]:p-8\">\n              <h4 className=\"m-0 text-[1.125rem] leading-[1.3] font-normal\">{guidesPanel.title}</h4>\n              <p className=\"m-0 mt-2.5 text-base leading-[1.5] text-gray-500\">\n                {guidesPanel.description}\n              </p>\n              <ul className=\"-mx-2 m-0 mt-4 grid list-none gap-0 p-0\">\n                {guideLinks.map((link) => (\n                  <li key={link.href}>\n                    <Link className={linkCardClassName} href={link.href}>\n                      <h5 className=\"m-0 text-base leading-[1.25] font-normal\">{link.title}</h5>\n                      <p className=\"m-0 mt-[0.35rem] text-[0.95rem] leading-[1.45] text-gray-500\">\n                        {link.description}\n                      </p>\n                    </Link>\n                  </li>\n                ))}\n              </ul>\n            </div>\n          </NavigationMenu.Content>\n        </NavigationMenu.Item>\n\n        <NavigationMenu.Item>\n          <Link className={triggerClassName} href=\"/react/overview/releases\">\n            Releases\n          </Link>\n        </NavigationMenu.Item>\n\n        <NavigationMenu.Item>\n          <Link className={triggerClassName} href=\"https://github.com/mui/base-ui\">\n            GitHub\n          </Link>\n        </NavigationMenu.Item>\n      </NavigationMenu.List>\n\n      <NavigationMenu.Portal>\n        <NavigationMenu.Positioner\n          sideOffset={10}\n          collisionPadding={{ top: 5, bottom: 5, left: 20, right: 20 }}\n          collisionAvoidance={{ side: 'none' }}\n          className=\"box-border h-[var(--positioner-height)] w-[var(--positioner-width)] max-w-[var(--available-width)] transition-[top,left,right,bottom] duration-[var(--duration)] ease-[var(--easing)] before:absolute before:content-[''] data-[instant]:transition-none data-[side=bottom]:before:top-[-10px] data-[side=bottom]:before:right-0 data-[side=bottom]:before:left-0 data-[side=bottom]:before:h-2.5 data-[side=left]:before:top-0 data-[side=left]:before:right-[-10px] data-[side=left]:before:bottom-0 data-[side=left]:before:w-2.5 data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:left-[-10px] data-[side=right]:before:w-2.5 data-[side=top]:before:right-0 data-[side=top]:before:bottom-[-10px] data-[side=top]:before:left-0 data-[side=top]:before:h-2.5\"\n          style={{\n            ['--duration' as string]: '0.35s',\n            ['--easing' as string]: 'cubic-bezier(0.22, 1, 0.36, 1)',\n          }}\n        >\n          <NavigationMenu.Popup className=\"data-[ending-style]:easing-[ease] relative h-[var(--popup-height)] w-[var(--popup-width)] origin-[var(--transform-origin)] rounded-lg bg-[canvas] text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 transition-[opacity,scale,width,height] duration-[var(--duration)] ease-[var(--easing)] data-[ending-style]:transition-[opacity,scale] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[ending-style]:duration-150 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300\">\n            <NavigationMenu.Arrow className=\"flex transition-[left] duration-[var(--duration)] ease-[var(--easing)] data-[side=bottom]:top-[-8px] data-[side=left]:right-[-13px] data-[side=left]:rotate-90 data-[side=right]:left-[-13px] data-[side=right]:-rotate-90 data-[side=top]:bottom-[-8px] data-[side=top]:rotate-180\">\n              <ArrowSvg />\n            </NavigationMenu.Arrow>\n            <NavigationMenu.Viewport className=\"relative h-full w-full overflow-hidden\" />\n          </NavigationMenu.Popup>\n        </NavigationMenu.Positioner>\n      </NavigationMenu.Portal>\n    </NavigationMenu.Root>\n  );\n}\n\nfunction Link(props: NavigationMenu.Link.Props) {\n  return (\n    <NavigationMenu.Link\n      render={\n        // Use the `render` prop to render your framework's Link component\n        // for client-side routing.\n        // e.g. `<NextLink href={props.href} />` instead of `<a />`.\n        <a />\n      }\n      {...props}\n    />\n  );\n}\n\nfunction ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M1 3.5L5 7.5L9 3.5\" stroke=\"currentColor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className=\"fill-[canvas]\"\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className=\"fill-gray-200 dark:fill-none\"\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className=\"dark:fill-gray-300\"\n      />\n    </svg>\n  );\n}\n\nconst triggerClassName =\n  'box-border flex items-center justify-center gap-1.5 h-10 ' +\n  'px-2 sm:px-3.5 m-0 rounded-md bg-gray-50 text-gray-900 font-normal' +\n  'text-[0.925rem] sm:text-base leading-6 select-none no-underline ' +\n  'hover:bg-gray-100 active:bg-gray-100 data-[popup-open]:bg-gray-100 ' +\n  'focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 focus-visible:relative';\n\nconst sharedContentClassName =\n  'h-full w-[calc(100vw_-_40px)] ' +\n  'transition-[opacity,translate] duration-[calc(var(--duration)*0.5),var(--duration)] ease-[ease,cubic-bezier(0.4,0,0.2,1)] ' +\n  'data-[starting-style]:data-[activation-direction=left]:opacity-0 data-[starting-style]:data-[activation-direction=right]:opacity-0 data-[ending-style]:opacity-0 ' +\n  'data-[ending-style]:duration-[calc(var(--duration)*0.5)] data-[ending-style]:ease-[ease] ' +\n  'data-[starting-style]:data-[activation-direction=left]:translate-x-[-2rem] ' +\n  'data-[starting-style]:data-[activation-direction=right]:translate-x-[2rem] ' +\n  'data-[ending-style]:data-[activation-direction=left]:translate-x-[2rem] ' +\n  'data-[ending-style]:data-[activation-direction=right]:translate-x-[-2rem]';\n\nconst productContentClassName = `${sharedContentClassName} min-[700px]:max-w-[675px] p-0`;\n\nconst guidesContentClassName = `${sharedContentClassName} min-[700px]:max-w-[500px] p-0`;\n\nconst submenuTriggerClassName =\n  'box-border m-0 flex w-full min-w-[10rem] flex-col items-start gap-0.5 rounded-lg ' +\n  'bg-transparent px-3 py-2.5 text-left text-inherit ' +\n  'hover:bg-gray-100 data-[popup-open]:bg-[canvas] dark:data-[popup-open]:bg-gray-100 data-[popup-open]:shadow-[0_1px_2px_rgb(0_0_0_/_0.08),0_1px_1px_rgb(0_0_0_/_0.04)] focus-visible:outline-2 ' +\n  'focus-visible:-outline-offset-1 focus-visible:outline-blue-800';\n\nconst submenuContentClassName =\n  'h-full translate-x-0 p-7 min-[700px]:p-8 min-[700px]:blur-0 transition-[opacity,translate,filter] duration-[var(--duration)] ease-[var(--easing)] min-[700px]:duration-[calc(var(--duration)*1.35)] min-[700px]:ease-[cubic-bezier(0.16,1,0.3,1)] ' +\n  'data-[starting-style]:data-[activation-direction=left]:opacity-0 data-[starting-style]:data-[activation-direction=right]:opacity-0 data-[starting-style]:data-[activation-direction=left]:translate-x-[-50%] data-[starting-style]:data-[activation-direction=right]:translate-x-[50%] ' +\n  'data-[ending-style]:opacity-0 data-[ending-style]:data-[activation-direction=left]:translate-x-[50%] data-[ending-style]:data-[activation-direction=right]:translate-x-[-50%] ' +\n  'min-[700px]:data-[starting-style]:data-[activation-direction=up]:opacity-0 min-[700px]:data-[starting-style]:data-[activation-direction=down]:opacity-0 min-[700px]:data-[starting-style]:data-[activation-direction=up]:translate-y-[-72px] min-[700px]:data-[starting-style]:data-[activation-direction=down]:translate-y-[72px] min-[700px]:data-[starting-style]:blur-[2px] ' +\n  'min-[700px]:data-[ending-style]:data-[activation-direction=up]:translate-y-[72px] min-[700px]:data-[ending-style]:data-[activation-direction=down]:translate-y-[-72px] min-[700px]:data-[ending-style]:blur-[2px]';\n\nconst linkCardClassName =\n  'box-border block rounded-lg bg-transparent px-2 py-3 no-underline text-inherit ' +\n  'hover:bg-gray-100 focus-visible:outline-2 ' +\n  'focus-visible:-outline-offset-1 focus-visible:outline-blue-800';\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/navigation-menu/page.mdx",
    "content": "# Navigation Menu\n\n<Subtitle>A collection of links and menus for website navigation.</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"A high-quality, unstyled React navigation menu component that displays a collection of links and menus for website navigation.\"\n/>\n\nimport { DemoNavigationMenuHero } from './demos/hero';\n\n<DemoNavigationMenuHero />\n\n## Anatomy\n\nImport the component and assemble its parts:\n\n```jsx title=\"Anatomy\"\nimport { NavigationMenu } from '@base-ui/react/navigation-menu';\n\n<NavigationMenu.Root>\n  <NavigationMenu.List>\n    <NavigationMenu.Item>\n      <NavigationMenu.Trigger>\n        <NavigationMenu.Icon />\n      </NavigationMenu.Trigger>\n      <NavigationMenu.Content>\n        <NavigationMenu.Link />\n      </NavigationMenu.Content>\n    </NavigationMenu.Item>\n  </NavigationMenu.List>\n\n  <NavigationMenu.Portal>\n    <NavigationMenu.Backdrop />\n    <NavigationMenu.Positioner>\n      <NavigationMenu.Popup>\n        <NavigationMenu.Arrow />\n        <NavigationMenu.Viewport />\n      </NavigationMenu.Popup>\n    </NavigationMenu.Positioner>\n  </NavigationMenu.Portal>\n</NavigationMenu.Root>;\n```\n\n## Examples\n\n### Nested submenus\n\n`<NavigationMenu.Root>` component can be nested within a higher-level `<NavigationMenu.Content>` part to create a multi-level navigation menu.\n\nimport { DemoNavigationMenuNested } from './demos/nested';\n\n<DemoNavigationMenuNested compact />\n\n### Nested inline submenus\n\nFor second-level navigation that should stay in the same panel, omit the nested `<NavigationMenu.Portal>` and render only `List` + `Viewport` with a `defaultValue`.\n\nimport { DemoNavigationMenuNestedInline } from './demos/nested-inline';\n\n<DemoNavigationMenuNestedInline compact />\n\n### Custom links\n\nThe `<NavigationMenu.Link>` part can be customized to render the link from your framework using the `render` prop to enable client-side routing.\n\n```jsx title=\"Next.js example\" {1,7}\nimport NextLink from 'next/link';\nimport { NavigationMenu } from '@base-ui/react/navigation-menu';\n\nfunction Link(props: NavigationMenu.Link.Props) {\n  return (\n    <NavigationMenu.Link\n      render={<NextLink href={props.href} />}\n      {...props}\n    />\n  );\n}\n```\n\n### Large menus\n\nWhen you have large menu content that doesn't fit in the viewport in some cases, you usually have two choices:\n\n1. Compress the navigation menu content\n\nYou can change the layout of the navigation menu to render less content or be more compact by reducing the space it takes up.\nIf your content is flexible, you can use the `max-height` property on `.Popup` to limit the height of the navigation menu to let it compress itself while preventing overflow.\n\n```css title=\"Compact layout\"\n.Content,\n.Popup {\n  max-height: var(--available-height);\n}\n```\n\n2. Make the navigation menu scrollable\n\n```css title=\"Scrollable layout\"\n.Content,\n.Popup {\n  max-height: var(--available-height);\n}\n\n.Content {\n  overflow-y: auto;\n}\n```\n\nNative scrollbars are visible while transitioning content, so we recommend using the [Scroll Area](/react/components/scroll-area) component instead of native scrollbars to keep them hidden, which also allows the `Arrow` to be centered correctly.\n\n## API reference\n\n<Reference\n  component=\"NavigationMenu\"\n  parts=\"Root, List, Item, Trigger, Icon, Content, Link, Backdrop, Portal, Positioner, Popup, Viewport, Arrow\"\n/>\n\nexport const metadata = {\n  keywords: [\n    'React Navigation Menu',\n    'Navigation Menu Component',\n    'Mega Menu React',\n    'Dropdown Nav',\n    'Flyout Menu',\n    'Site Navigation',\n    'Navbar',\n    'Header Menu',\n    'Multi Level Navigation',\n    'Custom Link Menu',\n    'Scrollable Navigation Menu',\n    'Headless React Components',\n    'Base UI',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/number-field/demos/hero/css-modules/index.module.css",
    "content": ".Field {\n  display: flex;\n  flex-direction: column;\n  align-items: start;\n  gap: 0.25rem;\n}\n\n.ScrubArea {\n  cursor: ew-resize;\n  font-weight: 700;\n  user-select: none;\n}\n\n.ScrubAreaCursor {\n  filter: drop-shadow(0 1px 1px #0008);\n}\n\n.Label {\n  cursor: ew-resize;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 700;\n  color: var(--color-gray-900);\n}\n\n.Group {\n  display: flex;\n}\n\n.Input {\n  box-sizing: border-box;\n  margin: 0;\n  padding: 0;\n  border-radius: 0;\n  border-top: 1px solid var(--color-gray-200);\n  border-bottom: 1px solid var(--color-gray-200);\n  border-left: none;\n  border-right: none;\n  width: 6rem;\n  height: 2.5rem;\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  background-color: transparent;\n  color: var(--color-gray-900);\n\n  text-align: center;\n  font-variant-numeric: tabular-nums;\n\n  &:focus {\n    z-index: 1;\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Decrement,\n.Increment {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 2.5rem;\n  height: 2.5rem;\n  margin: 0;\n  outline: 0;\n  padding: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  background-clip: padding-box;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n}\n\n.Decrement {\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n\n.Increment {\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/number-field/demos/hero/css-modules/index.tsx",
    "content": "import * as React from 'react';\nimport { NumberField } from '@base-ui/react/number-field';\nimport styles from './index.module.css';\n\nexport default function ExampleNumberField() {\n  const id = React.useId();\n  return (\n    <NumberField.Root id={id} defaultValue={100} className={styles.Field}>\n      <NumberField.ScrubArea className={styles.ScrubArea}>\n        <label htmlFor={id} className={styles.Label}>\n          Amount\n        </label>\n        <NumberField.ScrubAreaCursor className={styles.ScrubAreaCursor}>\n          <CursorGrowIcon />\n        </NumberField.ScrubAreaCursor>\n      </NumberField.ScrubArea>\n\n      <NumberField.Group className={styles.Group}>\n        <NumberField.Decrement className={styles.Decrement}>\n          <MinusIcon />\n        </NumberField.Decrement>\n        <NumberField.Input className={styles.Input} />\n        <NumberField.Increment className={styles.Increment}>\n          <PlusIcon />\n        </NumberField.Increment>\n      </NumberField.Group>\n    </NumberField.Root>\n  );\n}\n\nfunction CursorGrowIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"26\"\n      height=\"14\"\n      viewBox=\"0 0 24 14\"\n      fill=\"black\"\n      stroke=\"white\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M19.5 5.5L6.49737 5.51844V2L1 6.9999L6.5 12L6.49737 8.5L19.5 8.5V12L25 6.9999L19.5 2V5.5Z\" />\n    </svg>\n  );\n}\n\nfunction PlusIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"10\"\n      height=\"10\"\n      viewBox=\"0 0 10 10\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"1.6\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M0 5H5M10 5H5M5 5V0M5 5V10\" />\n    </svg>\n  );\n}\n\nfunction MinusIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"10\"\n      height=\"10\"\n      viewBox=\"0 0 10 10\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"1.6\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M0 5H10\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/number-field/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoNumberFieldHero = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/number-field/demos/hero/tailwind/index.tsx",
    "content": "import * as React from 'react';\nimport { NumberField } from '@base-ui/react/number-field';\n\nexport default function ExampleNumberField() {\n  const id = React.useId();\n  return (\n    <NumberField.Root id={id} defaultValue={100} className=\"flex flex-col items-start gap-1\">\n      <NumberField.ScrubArea className=\"cursor-ew-resize\">\n        <label htmlFor={id} className=\"cursor-ew-resize text-sm font-bold text-gray-900\">\n          Amount\n        </label>\n        <NumberField.ScrubAreaCursor className=\"drop-shadow-[0_1px_1px_#0008] filter\">\n          <CursorGrowIcon />\n        </NumberField.ScrubAreaCursor>\n      </NumberField.ScrubArea>\n\n      <NumberField.Group className=\"flex\">\n        <NumberField.Decrement className=\"flex size-10 items-center justify-center rounded-tl-md rounded-bl-md border border-gray-200 bg-gray-50 bg-clip-padding text-gray-900 select-none hover:bg-gray-100 active:bg-gray-100\">\n          <MinusIcon />\n        </NumberField.Decrement>\n        <NumberField.Input className=\"h-10 w-24 border-t border-b border-gray-200 text-center text-base text-gray-900 tabular-nums focus:z-1 focus:outline-2 focus:-outline-offset-1 focus:outline-blue-800 font-normal\" />\n        <NumberField.Increment className=\"flex size-10 items-center justify-center rounded-tr-md rounded-br-md border border-gray-200 bg-gray-50 bg-clip-padding text-gray-900 select-none hover:bg-gray-100 active:bg-gray-100\">\n          <PlusIcon />\n        </NumberField.Increment>\n      </NumberField.Group>\n    </NumberField.Root>\n  );\n}\n\nfunction CursorGrowIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"26\"\n      height=\"14\"\n      viewBox=\"0 0 24 14\"\n      fill=\"black\"\n      stroke=\"white\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M19.5 5.5L6.49737 5.51844V2L1 6.9999L6.5 12L6.49737 8.5L19.5 8.5V12L25 6.9999L19.5 2V5.5Z\" />\n    </svg>\n  );\n}\n\nfunction PlusIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"10\"\n      height=\"10\"\n      viewBox=\"0 0 10 10\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"1.6\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M0 5H5M10 5H5M5 5V0M5 5V10\" />\n    </svg>\n  );\n}\n\nfunction MinusIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"10\"\n      height=\"10\"\n      viewBox=\"0 0 10 10\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"1.6\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M0 5H10\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/number-field/page.mdx",
    "content": "# Number Field\n\n<Subtitle>A numeric input element with increment and decrement buttons, and a scrub area.</Subtitle>\n\n<Meta\n  name=\"description\"\n  content=\"A high-quality, unstyled React number field component with increment and decrement buttons, and a scrub area.\"\n/>\n\nimport { DemoNumberFieldHero } from './demos/hero';\n\n<DemoNumberFieldHero />\n\n## Usage guidelines\n\n- **Form controls must have an accessible name**: It can be created using a `<label>` element or the `Field` component. See the [forms guide](/react/handbook/forms).\n\n## Anatomy\n\nImport the component and assemble its parts:\n\n```jsx title=\"Anatomy\"\nimport { NumberField } from '@base-ui/react/number-field';\n\n<NumberField.Root>\n  <NumberField.ScrubArea>\n    <NumberField.ScrubAreaCursor />\n  </NumberField.ScrubArea>\n  <NumberField.Group>\n    <NumberField.Decrement />\n    <NumberField.Input />\n    <NumberField.Increment />\n  </NumberField.Group>\n</NumberField.Root>;\n```\n\n## API reference\n\n<Reference\n  component=\"NumberField\"\n  parts=\"Root, ScrubArea, ScrubAreaCursor, Group, Decrement, Input, Increment\"\n/>\n\nexport const metadata = {\n  keywords: [\n    'React Number Field',\n    'Number Input Component',\n    'Number Spinner',\n    'Spinner Input',\n    'Spin Button',\n    'Stepper',\n    'Numeric Input',\n    'Increment Decrement Buttons',\n    'Up Down Control',\n    'Scrub Area Input',\n    'Numeric Form Control',\n    'Accessible Number Field',\n    'Headless React Components',\n    'Base UI',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/page.mdx",
    "content": "# Components\n\n[//]: # 'This section is autogenerated, but the following list order, title, and [Tag]s can be modified, but nothing within the parentheses.'\n\n- Accordion - ([Outline](#accordion), [Contents](./accordion/page.mdx))\n- Alert Dialog - ([Outline](#alert-dialog), [Contents](./alert-dialog/page.mdx))\n- Autocomplete - ([Outline](#autocomplete), [Contents](./autocomplete/page.mdx))\n- Avatar - ([Outline](#avatar), [Contents](./avatar/page.mdx))\n- Button - ([Outline](#button), [Contents](./button/page.mdx))\n- Calendar [New] - (Private, [Outline](#calendar), [Contents](./calendar/page.mdx))\n- Checkbox - ([Outline](#checkbox), [Contents](./checkbox/page.mdx))\n- Checkbox Group - ([Outline](#checkbox-group), [Contents](./checkbox-group/page.mdx))\n- Collapsible - ([Outline](#collapsible), [Contents](./collapsible/page.mdx))\n- Combobox - ([Outline](#combobox), [Contents](./combobox/page.mdx))\n- Context Menu - ([Outline](#context-menu), [Contents](./context-menu/page.mdx))\n- Dialog - ([Outline](#dialog), [Contents](./dialog/page.mdx))\n- Drawer [New] - ([Outline](#drawer), [Contents](./drawer/page.mdx))\n- Field - ([Outline](#field), [Contents](./field/page.mdx))\n- Fieldset - ([Outline](#fieldset), [Contents](./fieldset/page.mdx))\n- Form - ([Outline](#form), [Contents](./form/page.mdx))\n- Input - ([Outline](#input), [Contents](./input/page.mdx))\n- Menu - ([Outline](#menu), [Contents](./menu/page.mdx))\n- Menubar - ([Outline](#menubar), [Contents](./menubar/page.mdx))\n- Meter - ([Outline](#meter), [Contents](./meter/page.mdx))\n- Navigation Menu - ([Outline](#navigation-menu), [Contents](./navigation-menu/page.mdx))\n- Number Field - ([Outline](#number-field), [Contents](./number-field/page.mdx))\n- Popover - ([Outline](#popover), [Contents](./popover/page.mdx))\n- Preview Card - ([Outline](#preview-card), [Contents](./preview-card/page.mdx))\n- Progress - ([Outline](#progress), [Contents](./progress/page.mdx))\n- Radio - ([Outline](#radio), [Contents](./radio/page.mdx))\n- Scroll Area - ([Outline](#scroll-area), [Contents](./scroll-area/page.mdx))\n- Select - ([Outline](#select), [Contents](./select/page.mdx))\n- Separator - ([Outline](#separator), [Contents](./separator/page.mdx))\n- Slider - ([Outline](#slider), [Contents](./slider/page.mdx))\n- Switch - ([Outline](#switch), [Contents](./switch/page.mdx))\n- Tabs - ([Outline](#tabs), [Contents](./tabs/page.mdx))\n- Toast - ([Outline](#toast), [Contents](./toast/page.mdx))\n- Toggle - ([Outline](#toggle), [Contents](./toggle/page.mdx))\n- Toggle Group - ([Outline](#toggle-group), [Contents](./toggle-group/page.mdx))\n- Toolbar - ([Outline](#toolbar), [Contents](./toolbar/page.mdx))\n- Tooltip - ([Outline](#tooltip), [Contents](./tooltip/page.mdx))\n\n[//]: # 'This section is autogenerated, DO NOT EDIT AFTER THIS LINE, run: pnpm docs:validate \"(docs)/react/components\"'\n\n## Accordion\n\nA set of collapsible panels with headings.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Accordion, Accordion Component, Collapsible Content, Expandable Panel, Accessible Accordion, Multi-Panel UI, Disclosure Widget, FAQ Component, Headless React Components, Base UI\n- Sections:\n  - Anatomy\n  - Examples\n    - Open multiple panels\n  - API reference\n- Exports:\n  - Accordion - Root\n    - Props: className, defaultValue, disabled, hiddenUntilFound, keepMounted, loopFocus, multiple, onValueChange, orientation, render, style, value\n    - Data Attributes: data-disabled, data-orientation\n  - Accordion - Item\n    - Props: className, disabled, onOpenChange, render, style, value\n    - Data Attributes: data-disabled, data-index, data-open\n  - Accordion - Header\n    - Props: className, render, style\n    - Data Attributes: data-disabled, data-index, data-open\n  - Accordion - Trigger\n    - Props: className, nativeButton, render, style\n    - Data Attributes: data-disabled, data-panel-open\n  - Accordion - Panel\n    - Props: className, hiddenUntilFound, keepMounted, render, style\n    - Data Attributes: data-disabled, data-ending-style, data-index, data-open, data-orientation, data-starting-style\n    - CSS Variables: --accordion-panel-height, --accordion-panel-width\n\n</details>\n\n[Read more](./accordion/page.mdx)\n\n## Alert Dialog\n\nA dialog that requires a user response to proceed.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Alert Dialog, Alert Dialog Component, Confirmation Modal, Warning Dialog, Blocking Modal, Interrupt Dialog, Destructive Action Confirm, Accessible Dialog, Headless React Components, React UI Overlay, Base UI\n- Sections:\n  - Anatomy\n  - Examples\n    - Open from a menu\n    - Close confirmation\n    - Detached triggers\n    - Multiple triggers\n    - Controlled mode with multiple triggers\n  - API reference\n- Exports:\n  - Alert Dialog - Root\n    - Props: actionsRef, children, defaultOpen, defaultTriggerId, handle, onOpenChange, onOpenChangeComplete, open, triggerId\n  - Alert Dialog - Backdrop\n    - Props: className, forceRender, render, style\n    - Data Attributes: data-closed, data-ending-style, data-open, data-starting-style\n  - Alert Dialog - Close\n    - Props: className, nativeButton, render, style\n    - Data Attributes: data-disabled\n  - Alert Dialog - Description\n    - Props: className, render, style\n  - Alert Dialog - Popup\n    - Props: className, finalFocus, initialFocus, render, style\n    - Data Attributes: data-closed, data-ending-style, data-nested, data-nested-dialog-open, data-open, data-starting-style\n    - CSS Variables: --nested-dialogs\n  - Alert Dialog - Portal\n    - Props: className, container, keepMounted, render, style\n  - Alert Dialog - Title\n    - Props: className, render, style\n  - Alert Dialog - Trigger\n    - Props: className, handle, id, nativeButton, payload, render, style\n    - Data Attributes: data-disabled, data-popup-open\n  - Alert Dialog - Viewport\n    - Props: className, render, style\n    - Data Attributes: data-closed, data-ending-style, data-nested, data-nested-dialog-open, data-open, data-starting-style\n\n</details>\n\n[Read more](./alert-dialog/page.mdx)\n\n## Autocomplete\n\nAn input that suggests options as you type.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Autocomplete, Autocomplete Component, Search Suggestions, Filterable Input, Async Autocomplete, Typeahead, Lookahead, Autosuggest, Incremental Search, Predictive Text, Search Input, Accessible Combobox, Headless React Components, Base UI\n- Sections:\n  - Usage guidelines\n  - Anatomy\n  - TypeScript inference\n  - Examples\n    - Async search\n    - Inline autocomplete\n    - Grouped\n    - Fuzzy matching\n    - Limit results\n    - Auto highlight\n    - Command palette\n    - Grid layout\n    - Virtualized\n  - API reference\n  - useFilter\n    - Input parameters\n    - Return value\n  - useFilteredItems\n    - Return value\n- Exports:\n  - Autocomplete - Root\n    - Props: actionsRef, autoHighlight, children, defaultOpen, defaultValue, disabled, filter, filteredItems, grid, highlightItemOnHover, id, inline, inputRef, itemToStringValue, items, keepHighlight, limit, locale, loopFocus, modal, mode, name, onItemHighlighted, onOpenChange, onOpenChangeComplete, onValueChange, open, openOnInputClick, readOnly, required, submitOnItemClick, value, virtualized\n  - Autocomplete - Value\n    - Props: children\n\n</details>\n\n[Read more](./autocomplete/page.mdx)\n\n## Avatar\n\nAn easily stylable avatar component.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Avatar, Avatar Component, Profile Image UI, User Image, User Avatar, Profile Picture, Account Image, Profile Icon, Initials Fallback, Accessible Avatar, Headless React Components, Customizable Avatar, Base UI\n- Sections:\n  - Anatomy\n  - API reference\n- Exports:\n  - Avatar - Root\n    - Props: className, render, style\n  - Avatar - Image\n    - Props: className, onLoadingStatusChange, render, style\n    - Data Attributes: data-ending-style, data-starting-style\n  - Avatar - Fallback\n    - Props: className, delay, render, style\n\n</details>\n\n[Read more](./avatar/page.mdx)\n\n## Button\n\nA button component that can be rendered as another tag or focusable when disabled.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Button, Button Component, Focusable Disabled Button, Custom Element Button, Clickable Element, Action Button, Submit Button, Accessible Button, Loading State Button, Button as Link, Link Button, Headless React Components, Base UI\n- Sections:\n  - Usage guidelines\n  - Anatomy\n  - Examples\n    - Rendering as another tag\n    - Rendering links as buttons\n    - Loading states\n  - API reference\n- Exports:\n  - Button\n    - Props: className, focusableWhenDisabled, nativeButton, render, style\n    - Data Attributes: data-disabled\n\n</details>\n\n[Read more](./button/page.mdx)\n\n## Calendar\n\nAn easily stylable calendar component.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Calendar, React Date Picker, React Date Calendar, Calendar, Date Picker, Temporal, Base UI\n- Sections:\n  - Anatomy\n  - Examples\n    - Unavailable dates\n    - Validation\n    - Lazy loading\n    - Display week numbers\n    - Timezone support\n    - Month and year select\n    - Animating month changes\n      - Animating with `motion/react`\n    - Localization\n  - API reference\n- Exports:\n  - Calendar - DayButton\n    - Props: className, focusableWhenDisabled, format, nativeButton, render, style\n    - Data Attributes: data-current, data-disabled, data-outside-month, data-selected, data-unavailable\n  - Calendar - DayGrid\n    - Props: className, render, style\n  - Calendar - DayGridBody\n    - Props: children, className, fixedWeekNumber, offset, render, style\n  - Calendar - DayGridCell\n    - Props: className, render, style, value\n  - Calendar - DayGridHeader\n    - Props: className, render, style\n  - Calendar - DayGridHeaderCell\n    - Props: className, formatter, render, style, value\n  - Calendar - DayGridHeaderRow\n    - Props: children, className, render, style\n  - Calendar - DayGridRow\n    - Props: children, className, render, style, value\n  - Calendar - DecrementMonth\n    - Props: className, nativeButton, render, style\n    - Data Attributes: data-disabled\n  - Calendar - IncrementMonth\n    - Props: className, nativeButton, render, style\n    - Data Attributes: data-disabled\n  - Calendar - Root\n    - Props: children, className, defaultValue, defaultVisibleDate, disabled, invalid, isDateUnavailable, maxDate, minDate, monthPageSize, onValueChange, onVisibleDateChange, readOnly, referenceDate, render, style, timezone, value, visibleDate\n    - Data Attributes: data-disabled, data-empty, data-invalid, data-navigation-direction, data-readonly\n  - Calendar - Viewport\n    - Props: children\n    - Data Attributes: data-current, data-ending-style, data-navigation-direction, data-previous, data-starting-style\n\n</details>\n\n[Read more](./calendar/page.mdx)\n\n## Checkbox\n\nAn easily stylable checkbox component.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Checkbox, Checkbox Component, Accessible Checkbox, Customizable Checkbox, Form Control Checkbox, Checkmark, Tick Box, Selection Control, Checkbox Input, Binary Input, Checked State, Indeterminate Checkbox, Tri-State Checkbox, Headless React Components, Input Indicator, Base UI\n- Sections:\n  - Usage guidelines\n  - Anatomy\n  - Examples\n    - Labeling a checkbox\n    - Rendering as a native button\n    - Form integration\n  - API reference\n- Exports:\n  - Checkbox - Root\n    - Props: checked, className, defaultChecked, disabled, id, indeterminate, inputRef, name, nativeButton, onCheckedChange, parent, readOnly, render, required, style, uncheckedValue, value\n    - Data Attributes: data-checked, data-dirty, data-disabled, data-filled, data-focused, data-indeterminate, data-invalid, data-readonly, data-required, data-touched, data-unchecked, data-valid\n  - Checkbox - Indicator\n    - Props: className, keepMounted, render, style\n    - Data Attributes: data-checked, data-dirty, data-disabled, data-ending-style, data-filled, data-focused, data-indeterminate, data-invalid, data-readonly, data-required, data-starting-style, data-touched, data-unchecked, data-valid\n\n</details>\n\n[Read more](./checkbox/page.mdx)\n\n## Checkbox Group\n\nProvides shared state to a series of checkboxes.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Checkbox Group, Checkbox Group Component, Grouped Checkboxes, Multiple Selection, Checkbox List, Multi-Select Checkboxes, Parent Child Checkbox, Accessible Checkbox Group, Form Fieldset Control, Headless React Components, Base UI\n- Sections:\n  - Usage guidelines\n  - Anatomy\n  - Examples\n    - Labeling a checkbox group\n    - Rendering as a native button\n    - Form integration\n    - Parent checkbox\n    - Nested parent checkbox\n  - API reference\n- Exports:\n  - CheckboxGroup\n    - Props: allValues, className, defaultValue, disabled, onValueChange, render, style, value\n    - Data Attributes: data-disabled\n\n</details>\n\n[Read more](./checkbox-group/page.mdx)\n\n## Collapsible\n\nA collapsible panel controlled by a button.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Collapsible, Collapsible Component, Expandable Panel, Toggle Panel Button, Disclosure Widget, Show/Hide Content, Accordion Item, Accessible Disclosure, Headless React Components, Collapsible Trigger Panel, Base UI\n- Sections:\n  - Anatomy\n  - API reference\n- Exports:\n  - Collapsible - Root\n    - Props: className, defaultOpen, disabled, onOpenChange, open, render, style\n  - Collapsible - Trigger\n    - Props: className, nativeButton, render, style\n    - Data Attributes: data-panel-open\n  - Collapsible - Panel\n    - Props: className, hiddenUntilFound, keepMounted, render, style\n    - Data Attributes: data-closed, data-ending-style, data-open, data-starting-style\n    - CSS Variables: --collapsible-panel-height, --collapsible-panel-width\n\n</details>\n\n[Read more](./collapsible/page.mdx)\n\n## Combobox\n\nAn input combined with a list of predefined items to select.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Combobox, Combobox Component, Combo Box, Filterable Select, Searchable Dropdown, Editable Select, Typeahead Select, Autocomplete Dropdown, Tags Input, Chip Input, Token Input, Multi-Value Input, Multiple Select Combobox, Async Combobox, Accessible Combobox, Base UI\n- Sections:\n  - Usage guidelines\n  - Anatomy\n  - TypeScript\n  - Examples\n    - Typed wrapper component\n    - Multiple select\n    - Input inside popup\n    - Grouped\n    - Async search (single)\n    - Async search (multiple)\n    - Creatable\n    - Virtualized\n  - API reference\n  - useFilter\n    - Input parameters\n    - Return value\n  - useFilteredItems\n    - Return value\n- Exports:\n  - Combobox - Root\n    - Props: actionsRef, autoComplete, autoHighlight, children, defaultInputValue, defaultOpen, defaultValue, disabled, filter, filteredItems, grid, highlightItemOnHover, id, inline, inputRef, inputValue, isItemEqualToValue, itemToStringLabel, itemToStringValue, items, limit, locale, loopFocus, modal, multiple, name, onInputValueChange, onItemHighlighted, onOpenChange, onOpenChangeComplete, onValueChange, open, openOnInputClick, readOnly, required, value, virtualized\n  - Combobox - Label\n    - Props: className, render, style\n  - Combobox - Trigger\n    - Props: className, disabled, nativeButton, render, style\n    - Data Attributes: data-dirty, data-disabled, data-filled, data-focused, data-invalid, data-list-empty, data-placeholder, data-popup-open, data-popup-side, data-pressed, data-readonly, data-required, data-touched, data-valid\n  - Combobox - Input\n    - Props: className, disabled, render, style\n    - Data Attributes: data-dirty, data-disabled, data-filled, data-focused, data-invalid, data-list-empty, data-popup-open, data-popup-side, data-pressed, data-readonly, data-required, data-touched, data-valid\n  - Combobox - InputGroup\n    - Props: className, render, style\n    - Data Attributes: data-dirty, data-disabled, data-filled, data-focused, data-invalid, data-list-empty, data-placeholder, data-popup-open, data-popup-side, data-pressed, data-readonly, data-touched, data-valid\n  - Combobox - Popup\n    - Props: className, finalFocus, initialFocus, render, style\n    - Data Attributes: data-align, data-closed, data-empty, data-ending-style, data-instant, data-open, data-side, data-starting-style\n  - Combobox - Positioner\n    - Props: align, alignOffset, anchor, arrowPadding, className, collisionAvoidance, collisionBoundary, collisionPadding, disableAnchorTracking, positionMethod, render, side, sideOffset, sticky, style\n    - Data Attributes: data-align, data-anchor-hidden, data-closed, data-empty, data-open, data-side\n    - CSS Variables: --anchor-height, --anchor-width, --available-height, --available-width, --transform-origin\n  - Combobox - List\n    - Props: children, className, render, style\n  - Combobox - Item\n    - Props: children, className, disabled, index, nativeButton, onClick, render, style, value\n    - Data Attributes: data-disabled, data-highlighted, data-selected\n  - Combobox - ItemIndicator\n    - Props: children, className, keepMounted, render, style\n    - Data Attributes: data-ending-style, data-starting-style\n  - Combobox - Value\n    - Props: children, placeholder\n  - Combobox - Icon\n    - Props: className, render, style\n  - Combobox - Arrow\n    - Props: className, render, style\n    - Data Attributes: data-align, data-closed, data-open, data-side, data-uncentered\n  - Combobox - Backdrop\n    - Props: className, render, style\n    - Data Attributes: data-closed, data-ending-style, data-open, data-starting-style\n  - Combobox - Portal\n    - Props: className, container, keepMounted, render, style\n  - Combobox - Empty\n    - Props: className, render, style\n  - Combobox - Group\n    - Props: className, items, render, style\n  - Combobox - GroupLabel\n    - Props: className, render, style\n  - Combobox - Row\n    - Props: className, render, style\n  - Combobox - Chips\n    - Props: className, render, style\n  - Combobox - Chip\n    - Props: className, render, style\n  - Combobox - ChipRemove\n    - Props: className, nativeButton, render, style\n  - Combobox - Clear\n    - Props: className, disabled, keepMounted, nativeButton, render, style\n    - Data Attributes: data-disabled, data-ending-style, data-popup-open, data-starting-style\n  - Combobox - Status\n    - Props: className, render, style\n  - Combobox - Collection\n    - Props: children\n\n</details>\n\n[Read more](./combobox/page.mdx)\n\n## Context Menu\n\nA menu that appears at the pointer on right click or long press.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Context Menu, Context Menu Component, Right Click Menu, Secondary Click Menu, Pointer Menu, Touch Hold Menu, Long Press Menu, Nested Menu React, Accessible Context Menu, Headless React Components, Base UI\n- Sections:\n  - Anatomy\n  - Examples\n    - Nested menu\n  - API reference\n- Exports:\n  - Context Menu - Root\n    - Props: actionsRef, children, closeParentOnEsc, defaultOpen, defaultTriggerId, disabled, handle, highlightItemOnHover, loopFocus, onOpenChange, onOpenChangeComplete, open, orientation, triggerId\n  - Context Menu - Trigger\n    - Props: className, render, style\n    - Data Attributes: data-popup-open, data-pressed\n\n</details>\n\n[Read more](./context-menu/page.mdx)\n\n## Dialog\n\nA popup that opens on top of the entire page.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Dialog, Modal Dialog Component, Modal Popup, Modal Window, Popup Window, Modal Overlay, Overlay, Lightbox, Popover Dialog, Controlled Dialog State, Nested Dialog React, Accessible Dialog, Headless React Components, Base UI\n- Sections:\n  - Usage guidelines\n  - Anatomy\n  - Examples\n    - State\n    - Open from a menu\n    - Nested dialogs\n    - Close confirmation\n    - Outside scroll dialog\n    - Inside scroll dialog\n    - Placing elements outside the popup\n    - Detached triggers\n    - Multiple triggers\n    - Controlled mode with multiple triggers\n  - API reference\n- Exports:\n  - Dialog - Root\n    - Props: actionsRef, children, defaultOpen, defaultTriggerId, disablePointerDismissal, handle, modal, onOpenChange, onOpenChangeComplete, open, triggerId\n  - Dialog - Trigger\n    - Props: className, handle, id, nativeButton, payload, render, style\n    - Data Attributes: data-disabled, data-popup-open\n  - Dialog - Portal\n    - Props: className, container, keepMounted, render, style\n  - Dialog - Popup\n    - Props: className, finalFocus, initialFocus, render, style\n    - Data Attributes: data-closed, data-ending-style, data-nested, data-nested-dialog-open, data-open, data-starting-style\n    - CSS Variables: --nested-dialogs\n  - Dialog - Backdrop\n    - Props: className, forceRender, render, style\n    - Data Attributes: data-closed, data-ending-style, data-open, data-starting-style\n  - Dialog - Title\n    - Props: className, render, style\n  - Dialog - Description\n    - Props: className, render, style\n  - Dialog - Close\n    - Props: className, nativeButton, render, style\n    - Data Attributes: data-disabled\n  - Dialog - Viewport\n    - Props: className, render, style\n    - Data Attributes: data-closed, data-ending-style, data-nested, data-nested-dialog-open, data-open, data-starting-style\n\n</details>\n\n[Read more](./dialog/page.mdx)\n\n## Drawer\n\nA panel that slides in from the edge of the screen.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Drawer, Drawer Component, Side Panel, Bottom Sheet, Swipe Dismiss Drawer, Offcanvas, Sliding Panel, Modal Drawer, Controlled Drawer State, Accessible Drawer, Headless React Components, Base UI\n- Sections:\n  - Usage guidelines\n  - Anatomy\n  - Examples\n    - State\n    - Position\n    - Nested drawers\n    - Snap points\n    - Indent effect\n    - Non-modal\n    - Mobile navigation\n    - Swipe to open\n    - Action sheet with separate destructive action\n    - Detached triggers\n    - Stacking and animations\n  - API reference\n- Exports:\n  - Drawer - Root\n    - Props: actionsRef, children, defaultOpen, defaultSnapPoint, defaultTriggerId, disablePointerDismissal, handle, modal, onOpenChange, onOpenChangeComplete, onSnapPointChange, open, snapPoint, snapPoints, snapToSequentialPoints, swipeDirection, triggerId\n  - Drawer - Provider\n    - Props: children\n  - Drawer - Indent\n    - Props: className, render, style\n  - Drawer - IndentBackground\n    - Props: className, render, style\n  - Drawer - Trigger\n    - Props: className, handle, id, nativeButton, payload, render, style\n  - Drawer - Portal\n    - Props: className, container, keepMounted, render, style\n  - Drawer - Popup\n    - Props: className, finalFocus, initialFocus, render, style\n    - Data Attributes: data-closed, data-ending-style, data-expanded, data-nested-drawer-open, data-nested-drawer-swiping, data-open, data-starting-style, data-swipe-direction, data-swipe-dismiss, data-swiping\n    - CSS Variables: --drawer-frontmost-height, --drawer-height, --drawer-snap-point-offset, --drawer-swipe-movement-x, --drawer-swipe-movement-y, --drawer-swipe-strength, --nested-drawers\n  - Drawer - SwipeArea\n    - Props: className, disabled, render, style, swipeDirection\n    - Data Attributes: data-closed, data-disabled, data-open, data-swipe-direction, data-swiping\n  - Drawer - Content\n    - Props: className, render, style\n  - Drawer - Backdrop\n    - Props: className, forceRender, render, style\n    - Data Attributes: data-closed, data-ending-style, data-open, data-starting-style\n    - CSS Variables: --drawer-swipe-progress\n  - Drawer - Viewport\n    - Props: className, render, style\n    - Data Attributes: data-closed, data-ending-style, data-nested, data-open, data-starting-style\n  - Drawer - Title\n    - Props: className, render, style\n  - Drawer - Description\n    - Props: className, render, style\n  - Drawer - Close\n    - Props: className, nativeButton, render, style\n\n</details>\n\n[Read more](./drawer/page.mdx)\n\n## Field\n\nA component that provides labeling and validation for form controls.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Field Component, Form Field Labeling, Form Label, Input Wrapper, Form Validation, Accessible Form Field, Field Validation UI, Headless React Components, Custom Form Control Wrapper, Form Description Error State, Base UI\n- Sections:\n  - Anatomy\n  - API reference\n- Exports:\n  - Field - Root\n    - Props: actionsRef, className, dirty, disabled, invalid, name, render, style, touched, validate, validationDebounceTime, validationMode\n    - Data Attributes: data-dirty, data-disabled, data-filled, data-focused, data-invalid, data-touched, data-valid\n  - Field - Label\n    - Props: className, nativeLabel, render, style\n    - Data Attributes: data-dirty, data-disabled, data-filled, data-focused, data-invalid, data-touched, data-valid\n  - Field - Description\n    - Props: className, render, style\n    - Data Attributes: data-dirty, data-disabled, data-filled, data-focused, data-invalid, data-touched, data-valid\n  - Field - Error\n    - Props: className, match, render, style\n    - Data Attributes: data-dirty, data-disabled, data-ending-style, data-filled, data-focused, data-invalid, data-starting-style, data-touched, data-valid\n  - Field - Control\n    - Props: className, defaultValue, onValueChange, render, style\n    - Data Attributes: data-dirty, data-disabled, data-filled, data-focused, data-invalid, data-touched, data-valid\n  - Field - Validity\n    - Props: children\n  - Field - Item\n    - Props: className, disabled, render, style\n\n</details>\n\n[Read more](./field/page.mdx)\n\n## Fieldset\n\nA native fieldset element with an easily stylable legend.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Fieldset, Fieldset Component, Form Field Grouping, Form Fieldset, Form Group, Input Grouping, Custom Legend Styling, Fieldset Legend, Accessible Fieldset, Headless React Components, Form Section Labeling, Base UI\n- Sections:\n  - Anatomy\n  - API reference\n- Exports:\n  - Fieldset - Root\n    - Props: className, render, style\n  - Fieldset - Legend\n    - Props: className, render, style\n\n</details>\n\n[Read more](./fieldset/page.mdx)\n\n## Form\n\nA native form element with consolidated error handling.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Form Component, Form Submission Handler, Form Validation, Form State, Server Function Form, JavaScript Form Values, Accessible Form, Headless React Components, Form Error Handling, Base UI\n- Sections:\n  - Anatomy\n  - Examples\n    - Submit with a Server Function\n    - Submit form values as a JavaScript object\n    - Using with Zod\n  - API reference\n- Exports:\n  - Form\n    - Props: actionsRef, className, errors, onFormSubmit, render, style, validationMode\n\n</details>\n\n[Read more](./form/page.mdx)\n\n## Input\n\nA native input element that automatically works with [Field](/react/components/field).\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Input, Input Component, Input Field, Form Input Field, Text Field, Text Input, Text Box, Form Input, Textarea Alternative, Controlled Input, Form Element, Accessible Input, Headless React Components, Unstyled Input, Base UI\n- Sections:\n  - Usage guidelines\n  - Anatomy\n  - API reference\n- Exports:\n  - Input\n    - Props: className, defaultValue, onValueChange, render, style, value\n    - Data Attributes: data-dirty, data-disabled, data-filled, data-focused, data-invalid, data-touched, data-valid\n\n</details>\n\n[Read more](./input/page.mdx)\n\n## Menu\n\nA list of actions in a dropdown, enhanced with keyboard navigation.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Menu, Menu Component, Dropdown Menu, Dropdown Actions, Command Menu, Action List, Keyboard Navigation Menu, Nested Menu React, Context Actions, Headless React Components, Base UI\n- Sections:\n  - Anatomy\n  - Examples\n    - Open on hover\n    - Checkbox items\n    - Radio items\n    - Close on click\n    - Group labels\n    - Nested menu\n    - Navigate to another page\n    - Open a dialog\n    - Detached triggers\n    - Multiple triggers\n    - Controlled mode with multiple triggers\n    - Animating the Menu\n      - Position and Size\n      - Content\n  - API reference\n- Exports:\n  - Menu - Root\n    - Props: actionsRef, children, closeParentOnEsc, defaultOpen, defaultTriggerId, disabled, handle, highlightItemOnHover, loopFocus, modal, onOpenChange, onOpenChangeComplete, open, orientation, triggerId\n  - Menu - Arrow\n    - Props: className, render, style\n    - Data Attributes: data-align, data-closed, data-open, data-side, data-uncentered\n  - Menu - Backdrop\n    - Props: className, render, style\n    - Data Attributes: data-closed, data-ending-style, data-open, data-starting-style\n  - Menu - CheckboxItem\n    - Props: checked, className, closeOnClick, defaultChecked, disabled, label, nativeButton, onCheckedChange, onClick, render, style\n    - Data Attributes: data-checked, data-disabled, data-highlighted, data-unchecked\n  - Menu - CheckboxItemIndicator\n    - Props: className, keepMounted, render, style\n    - Data Attributes: data-checked, data-disabled, data-ending-style, data-starting-style, data-unchecked\n  - Menu - GroupLabel\n    - Props: className, render, style\n  - Menu - Group\n    - Props: children, className, render, style\n  - Menu - Item\n    - Props: className, closeOnClick, disabled, label, nativeButton, onClick, render, style\n    - Data Attributes: data-disabled, data-highlighted\n  - Menu - LinkItem\n    - Props: className, closeOnClick, label, render, style\n    - Data Attributes: data-highlighted\n  - Menu - Popup\n    - Props: children, className, finalFocus, render, style\n    - Data Attributes: data-align, data-closed, data-ending-style, data-instant, data-open, data-side, data-starting-style\n  - Menu - Portal\n    - Props: className, container, keepMounted, render, style\n  - Menu - Positioner\n    - Props: align, alignOffset, anchor, arrowPadding, className, collisionAvoidance, collisionBoundary, collisionPadding, disableAnchorTracking, positionMethod, render, side, sideOffset, sticky, style\n    - Data Attributes: data-align, data-anchor-hidden, data-closed, data-open, data-side\n    - CSS Variables: --anchor-height, --anchor-width, --available-height, --available-width, --transform-origin\n  - Menu - RadioGroup\n    - Props: children, className, defaultValue, disabled, onValueChange, render, style, value\n  - Menu - RadioItem\n    - Props: className, closeOnClick, disabled, label, nativeButton, onClick, render, style, value\n    - Data Attributes: data-checked, data-disabled, data-highlighted, data-unchecked\n  - Menu - RadioItemIndicator\n    - Props: className, keepMounted, render, style\n    - Data Attributes: data-checked, data-disabled, data-ending-style, data-starting-style, data-unchecked\n  - Menu - SubmenuRoot\n    - Props: actionsRef, children, closeParentOnEsc, defaultOpen, defaultTriggerId, disabled, handle, highlightItemOnHover, loopFocus, onOpenChange, onOpenChangeComplete, open, orientation, triggerId\n  - Menu - Trigger\n    - Props: children, className, closeDelay, delay, disabled, handle, nativeButton, openOnHover, payload, render, style\n    - Data Attributes: data-popup-open, data-pressed\n  - Menu - SubmenuTrigger\n    - Props: className, closeDelay, delay, disabled, label, nativeButton, onClick, openOnHover, render, style\n    - Data Attributes: data-disabled, data-highlighted, data-popup-open\n  - Menu - Viewport\n    - Props: children, className, render, style\n    - Data Attributes: data-activation-direction, data-current, data-instant, data-previous, data-transitioning\n    - CSS Variables: --popup-height, --popup-width\n\n</details>\n\n[Read more](./menu/page.mdx)\n\n## Menubar\n\nA menu bar providing commands and options for your application.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Menubar, Menubar Component, Application Menu Bar, App Menu, Navigation Bar, Command Bar, Desktop Style Menu, Keyboard Navigation Menu, Accessible Menubar, Headless React Components, Base UI\n- Sections:\n  - Anatomy\n  - API reference\n- Exports:\n  - Menubar\n    - Props: className, disabled, loopFocus, modal, orientation, render, style\n\n</details>\n\n[Read more](./menubar/page.mdx)\n\n## Meter\n\nA graphical display of a numeric value within a range.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Meter, Meter Component, Progress Meter, Gauge, Level Indicator, Measurement Display, Capacity Indicator, Value Indicator, Rating Meter, Fuel Gauge, Accessible Meter, Headless React Components, Graphical Value Display, Base UI\n- Sections:\n  - Anatomy\n  - API reference\n- Exports:\n  - Meter - Root\n    - Props: aria-valuetext, className, format, getAriaValueText, locale, max, min, render, style, value\n  - Meter - Indicator\n    - Props: className, render, style\n  - Meter - Label\n    - Props: className, render, style\n  - Meter - Track\n    - Props: className, render, style\n  - Meter - Value\n    - Props: children, className, render, style\n\n</details>\n\n[Read more](./meter/page.mdx)\n\n## Navigation Menu\n\nA collection of links and menus for website navigation.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Navigation Menu, Navigation Menu Component, Mega Menu React, Dropdown Nav, Flyout Menu, Site Navigation, Navbar, Header Menu, Multi Level Navigation, Custom Link Menu, Scrollable Navigation Menu, Headless React Components, Base UI\n- Sections:\n  - Anatomy\n  - Examples\n    - Nested submenus\n    - Nested inline submenus\n    - Custom links\n    - Large menus\n  - API reference\n- Exports:\n  - Navigation Menu - Root\n    - Props: actionsRef, className, closeDelay, defaultValue, delay, onOpenChangeComplete, onValueChange, orientation, render, style, value\n  - Navigation Menu - Trigger\n    - Props: className, nativeButton, render, style\n    - Data Attributes: data-popup-open, data-pressed\n  - Navigation Menu - Portal\n    - Props: className, container, keepMounted, render, style\n  - Navigation Menu - Positioner\n    - Props: align, alignOffset, anchor, arrowPadding, className, collisionAvoidance, collisionBoundary, collisionPadding, disableAnchorTracking, positionMethod, render, side, sideOffset, sticky, style\n    - Data Attributes: data-align, data-anchor-hidden, data-closed, data-instant, data-open, data-side\n    - CSS Variables: --anchor-height, --anchor-width, --available-height, --available-width, --positioner-height, --positioner-width, --transform-origin\n  - Navigation Menu - Viewport\n    - Props: className, render, style\n  - Navigation Menu - List\n    - Props: className, render, style\n  - Navigation Menu - Item\n    - Props: className, render, style, value\n  - Navigation Menu - Content\n    - Props: className, keepMounted, render, style\n    - Data Attributes: data-activation-direction, data-closed, data-ending-style, data-open, data-starting-style\n  - Navigation Menu - Popup\n    - Props: className, render, style\n    - Data Attributes: data-align, data-closed, data-ending-style, data-open, data-side, data-starting-style\n    - CSS Variables: --popup-height, --popup-width\n  - Navigation Menu - Backdrop\n    - Props: className, render, style\n    - Data Attributes: data-closed, data-ending-style, data-open, data-starting-style\n  - Navigation Menu - Arrow\n    - Props: className, render, style\n    - Data Attributes: data-align, data-closed, data-open, data-side, data-uncentered\n  - Navigation Menu - Link\n    - Props: active, className, closeOnClick, render, style\n    - Data Attributes: data-active\n  - Navigation Menu - Icon\n    - Props: className, render, style\n\n</details>\n\n[Read more](./navigation-menu/page.mdx)\n\n## Number Field\n\nA numeric input element with increment and decrement buttons, and a scrub area.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Number Field, Number Input Component, Number Spinner, Spinner Input, Spin Button, Stepper, Numeric Input, Increment Decrement Buttons, Up Down Control, Scrub Area Input, Numeric Form Control, Accessible Number Field, Headless React Components, Base UI\n- Sections:\n  - Usage guidelines\n  - Anatomy\n  - API reference\n- Exports:\n  - Number Field - Root\n    - Props: allowOutOfRange, allowWheelScrub, className, defaultValue, disabled, format, id, inputRef, largeStep, locale, max, min, name, onValueChange, onValueCommitted, readOnly, render, required, smallStep, snapOnStep, step, style, value\n    - Data Attributes: data-dirty, data-disabled, data-filled, data-focused, data-invalid, data-readonly, data-required, data-scrubbing, data-touched, data-valid\n  - Number Field - Group\n    - Props: className, render, style\n    - Data Attributes: data-dirty, data-disabled, data-filled, data-focused, data-invalid, data-readonly, data-required, data-scrubbing, data-touched, data-valid\n  - Number Field - Increment\n    - Props: className, nativeButton, render, style\n    - Data Attributes: data-dirty, data-disabled, data-filled, data-focused, data-invalid, data-readonly, data-required, data-scrubbing, data-touched, data-valid\n  - Number Field - Decrement\n    - Props: className, nativeButton, render, style\n    - Data Attributes: data-dirty, data-disabled, data-filled, data-focused, data-invalid, data-readonly, data-required, data-scrubbing, data-touched, data-valid\n  - Number Field - Input\n    - Props: aria-roledescription, className, render, style\n    - Data Attributes: data-dirty, data-disabled, data-filled, data-focused, data-invalid, data-readonly, data-required, data-scrubbing, data-touched, data-valid\n  - Number Field - ScrubArea\n    - Props: className, direction, pixelSensitivity, render, style, teleportDistance\n    - Data Attributes: data-dirty, data-disabled, data-filled, data-focused, data-invalid, data-readonly, data-required, data-scrubbing, data-touched, data-valid\n  - Number Field - ScrubAreaCursor\n    - Props: className, render, style\n    - Data Attributes: data-dirty, data-disabled, data-filled, data-focused, data-invalid, data-readonly, data-required, data-scrubbing, data-touched, data-valid\n\n</details>\n\n[Read more](./number-field/page.mdx)\n\n## Popover\n\nAn accessible popup anchored to a button.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Popover, Popover Component, Popover Menu, Popup Menu, Anchored Popup, Dropdown, Overlay, Floating Panel, Tooltip Alternative, Info Box, Callout, Popup Content, Detached Trigger Popover, Multi Trigger Popover, Animated Popover, Headless React Components, Base UI\n- Sections:\n  - Anatomy\n  - Examples\n    - Opening on hover\n    - Detached triggers\n    - Multiple triggers\n    - Controlled mode with multiple triggers\n    - Animating the Popover\n      - Position and Size\n      - Content\n  - API reference\n- Exports:\n  - Popover - Root\n    - Props: actionsRef, children, defaultOpen, defaultTriggerId, handle, modal, onOpenChange, onOpenChangeComplete, open, triggerId\n  - Popover - Trigger\n    - Props: className, closeDelay, delay, handle, id, nativeButton, openOnHover, payload, render, style\n    - Data Attributes: data-popup-open, data-pressed\n  - Popover - Portal\n    - Props: className, container, keepMounted, render, style\n  - Popover - Positioner\n    - Props: align, alignOffset, anchor, arrowPadding, className, collisionAvoidance, collisionBoundary, collisionPadding, disableAnchorTracking, positionMethod, render, side, sideOffset, sticky, style\n    - Data Attributes: data-align, data-anchor-hidden, data-closed, data-open, data-side\n    - CSS Variables: --anchor-height, --anchor-width, --available-height, --available-width, --positioner-height, --positioner-width, --transform-origin\n  - Popover - Popup\n    - Props: className, finalFocus, initialFocus, render, style\n    - Data Attributes: data-align, data-closed, data-ending-style, data-instant, data-open, data-side, data-starting-style\n    - CSS Variables: --popup-height, --popup-width\n  - Popover - Arrow\n    - Props: className, render, style\n    - Data Attributes: data-align, data-closed, data-open, data-side, data-uncentered\n  - Popover - Backdrop\n    - Props: className, render, style\n    - Data Attributes: data-closed, data-ending-style, data-open, data-starting-style\n  - Popover - Title\n    - Props: className, render, style\n  - Popover - Description\n    - Props: className, render, style\n  - Popover - Close\n    - Props: className, nativeButton, render, style\n  - Popover - Viewport\n    - Props: children, className, render, style\n    - Data Attributes: data-activation-direction, data-current, data-instant, data-previous, data-transitioning\n    - CSS Variables: --popup-height, --popup-width\n\n</details>\n\n[Read more](./popover/page.mdx)\n\n## Preview Card\n\nA popup that appears when a link is hovered, showing a preview for sighted users.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Preview Card, Preview Card Component, Link Hover Preview, Hover Card React, Hover Card, Link Tooltip, URL Preview, Link Preview Popup, Content Preview, Accessible Preview Popup, Detached Trigger Preview Card, Multiple Preview Card Triggers, Animated Preview Card, Headless React Components, Link Preview UI, Base UI\n- Sections:\n  - Usage guidelines\n  - Anatomy\n  - Examples\n    - Detached triggers\n    - Multiple triggers\n    - Controlled mode with multiple triggers\n    - Animating the Preview Card\n      - Position and Size\n      - Content\n  - API reference\n- Exports:\n  - Preview Card - Root\n    - Props: actionsRef, children, defaultOpen, defaultTriggerId, handle, onOpenChange, onOpenChangeComplete, open, triggerId\n  - Preview Card - Trigger\n    - Props: className, closeDelay, delay, handle, payload, render, style\n    - Data Attributes: data-popup-open\n  - Preview Card - Portal\n    - Props: className, container, keepMounted, render, style\n  - Preview Card - Positioner\n    - Props: align, alignOffset, anchor, arrowPadding, className, collisionAvoidance, collisionBoundary, collisionPadding, disableAnchorTracking, positionMethod, render, side, sideOffset, sticky, style\n    - Data Attributes: data-align, data-anchor-hidden, data-closed, data-open, data-side\n    - CSS Variables: --anchor-height, --anchor-width, --available-height, --available-width, --transform-origin\n  - Preview Card - Popup\n    - Props: className, render, style\n    - Data Attributes: data-align, data-closed, data-ending-style, data-open, data-side, data-starting-style\n  - Preview Card - Arrow\n    - Props: className, render, style\n    - Data Attributes: data-align, data-closed, data-open, data-side, data-uncentered\n  - Preview Card - Backdrop\n    - Props: className, render, style\n    - Data Attributes: data-closed, data-ending-style, data-open, data-starting-style\n  - Preview Card - Viewport\n    - Props: children, className, render, style\n    - Data Attributes: data-activation-direction, data-current, data-instant, data-previous, data-transitioning\n    - CSS Variables: --popup-height, --popup-width\n\n</details>\n\n[Read more](./preview-card/page.mdx)\n\n## Progress\n\nDisplays the status of a task that takes a long time.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Progress Bar, Progress Component, Progress Indicator, Loader, Loading Bar, Upload Progress, Download Progress, Task Status Indicator, Determinate Progress, Indeterminate Progress, Accessible Progress, Headless React Components, Loading Indicator, Base UI\n- Sections:\n  - Anatomy\n  - API reference\n- Exports:\n  - Progress - Root\n    - Props: aria-valuetext, className, format, getAriaValueText, locale, max, min, render, style, value\n    - Data Attributes: data-complete, data-indeterminate, data-progressing\n  - Progress - Indicator\n    - Props: className, render, style\n    - Data Attributes: data-complete, data-indeterminate, data-progressing\n  - Progress - Label\n    - Props: className, render, style\n    - Data Attributes: data-complete, data-indeterminate, data-progressing\n  - Progress - Track\n    - Props: className, render, style\n    - Data Attributes: data-complete, data-indeterminate, data-progressing\n  - Progress - Value\n    - Props: children, className, render, style\n    - Data Attributes: data-complete, data-indeterminate, data-progressing\n\n</details>\n\n[Read more](./progress/page.mdx)\n\n## Radio\n\nAn easily stylable radio button component.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Radio Button, Radio Group Component, Radio Button, Radio Input, Single Choice, Exclusive Choice, Mutually Exclusive, Option Selector, Option Button, Form Radio Control, Accessible Radio Group, Customizable Radio, Headless React Components, Base UI\n- Sections:\n  - Usage guidelines\n  - Anatomy\n  - Examples\n    - Labeling a radio group\n    - Rendering as a native button\n    - Form integration\n  - API reference\n    - RadioGroup\n- Exports:\n  - Radio - Root\n    - Props: className, disabled, inputRef, nativeButton, readOnly, render, required, style, value\n    - Data Attributes: data-checked, data-dirty, data-disabled, data-filled, data-focused, data-invalid, data-readonly, data-required, data-touched, data-unchecked, data-valid\n  - Radio - Indicator\n    - Props: className, keepMounted, render, style\n    - Data Attributes: data-checked, data-dirty, data-disabled, data-ending-style, data-filled, data-focused, data-invalid, data-readonly, data-required, data-starting-style, data-touched, data-unchecked, data-valid\n\n</details>\n\n[Read more](./radio/page.mdx)\n\n## Scroll Area\n\nA native scroll container with custom scrollbars.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Scroll Area, Custom Scrollbars, Custom Scroll, Scrollbar, Styled Scrollbar, Virtual Scroll, Scroll Area Component, Overflow Container, Scrollable Region, Scrollable Content, Overflow Scroll, Gradient Scroll Fade, Scrollable Container, Headless React Components, Base UI\n- Sections:\n  - Anatomy\n  - Examples\n    - Both scrollbars\n    - Gradient scroll fade\n  - API reference\n- Exports:\n  - Scroll Area - Root\n    - Props: className, overflowEdgeThreshold, render, style\n    - Data Attributes: data-has-overflow-x, data-has-overflow-y, data-overflow-x-end, data-overflow-x-start, data-overflow-y-end, data-overflow-y-start, data-scrolling\n    - CSS Variables: --scroll-area-corner-height, --scroll-area-corner-width\n  - Scroll Area - Viewport\n    - Props: className, render, style\n    - Data Attributes: data-has-overflow-x, data-has-overflow-y, data-overflow-x-end, data-overflow-x-start, data-overflow-y-end, data-overflow-y-start, data-scrolling\n    - CSS Variables: --scroll-area-overflow-x-end, --scroll-area-overflow-x-start, --scroll-area-overflow-y-end, --scroll-area-overflow-y-start\n  - Scroll Area - Scrollbar\n    - Props: className, keepMounted, orientation, render, style\n    - Data Attributes: data-has-overflow-x, data-has-overflow-y, data-hovering, data-orientation, data-overflow-x-end, data-overflow-x-start, data-overflow-y-end, data-overflow-y-start, data-scrolling\n    - CSS Variables: --scroll-area-thumb-height, --scroll-area-thumb-width\n  - Scroll Area - Content\n    - Props: className, render, style\n  - Scroll Area - Thumb\n    - Props: className, render, style\n    - Data Attributes: data-orientation\n  - Scroll Area - Corner\n    - Props: className, render, style\n\n</details>\n\n[Read more](./scroll-area/page.mdx)\n\n## Select\n\nA common form component for choosing a predefined value in a dropdown menu.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Select Component, Dropdown Select, Select Box, Select Menu, Picker, Listbox, Choice Selector, Option List, Dropdown Menu, Combo Box, Multi Select React, Multiselect, Object Value Select, Accessible Select, Align Item with Trigger, Headless React Components, Base UI\n- Sections:\n  - Usage guidelines\n  - Anatomy\n  - Positioning\n  - Examples\n    - Typed wrapper component\n    - Formatting the value\n    - Labeling a select\n    - Placeholder values\n    - Multiple selection\n    - Object values\n    - Grouped\n  - API reference\n- Exports:\n  - Select - Root\n    - Props: actionsRef, autoComplete, children, defaultOpen, defaultValue, disabled, highlightItemOnHover, id, inputRef, isItemEqualToValue, itemToStringLabel, itemToStringValue, items, modal, multiple, name, onOpenChange, onOpenChangeComplete, onValueChange, open, readOnly, required, value\n  - Select - Label\n    - Props: className, render, style\n  - Select - Trigger\n    - Props: children, className, disabled, nativeButton, render, style\n    - Data Attributes: data-dirty, data-disabled, data-filled, data-focused, data-invalid, data-placeholder, data-popup-open, data-pressed, data-readonly, data-required, data-touched, data-valid\n  - Select - Value\n    - Props: children, className, placeholder, render, style\n    - Data Attributes: data-placeholder\n  - Select - Icon\n    - Props: className, render, style\n    - Data Attributes: data-popup-open\n  - Select - Portal\n    - Props: className, container, render, style\n  - Select - Backdrop\n    - Props: className, render, style\n    - Data Attributes: data-closed, data-ending-style, data-open, data-starting-style\n  - Select - Positioner\n    - Props: align, alignItemWithTrigger, alignOffset, anchor, arrowPadding, className, collisionAvoidance, collisionBoundary, collisionPadding, disableAnchorTracking, positionMethod, render, side, sideOffset, sticky, style\n    - Data Attributes: data-align, data-anchor-hidden, data-closed, data-open, data-side\n    - CSS Variables: --anchor-height, --anchor-width, --available-height, --available-width, --transform-origin\n  - Select - Popup\n    - Props: children, className, finalFocus, render, style\n    - Data Attributes: data-align, data-closed, data-ending-style, data-open, data-side, data-starting-style\n  - Select - List\n    - Props: className, render, style\n  - Select - Item\n    - Props: children, className, disabled, label, nativeButton, render, style, value\n    - Data Attributes: data-disabled, data-highlighted, data-selected\n  - Select - ItemIndicator\n    - Props: children, className, keepMounted, render, style\n    - Data Attributes: data-ending-style, data-starting-style\n  - Select - ItemText\n    - Props: className, render, style\n  - Select - Arrow\n    - Props: className, render, style\n    - Data Attributes: data-align, data-closed, data-open, data-side, data-uncentered\n  - Select - ScrollDownArrow\n    - Props: className, keepMounted, render, style\n    - Data Attributes: data-direction, data-ending-style, data-side, data-starting-style, data-visible\n  - Select - ScrollUpArrow\n    - Props: className, keepMounted, render, style\n    - Data Attributes: data-direction, data-ending-style, data-side, data-starting-style, data-visible\n  - Select - Group\n    - Props: className, render, style\n  - Select - GroupLabel\n    - Props: className, render, style\n\n</details>\n\n[Read more](./select/page.mdx)\n\n## Separator\n\nA separator element accessible to screen readers.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Separator, Divider Component, Divider, Horizontal Rule, Horizontal Divider, Vertical Divider, Visual Separator, Section Divider, HR, Accessible Separator, Screen Reader Separator, Headless React Components, Base UI\n- Sections:\n  - Anatomy\n  - API reference\n- Exports:\n  - Separator\n    - Props: className, orientation, render, style\n\n</details>\n\n[Read more](./separator/page.mdx)\n\n## Slider\n\nAn easily stylable range input.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Slider, Range Slider Component, Range Input, Range Control, Track Control, Value Selector, Dual Slider, Double Slider, Two-Handle Slider, Multi Thumb Slider, Thumb Alignment Edge, Accessible Slider, Headless React Components, Base UI\n- Sections:\n  - Usage guidelines\n  - Anatomy\n  - Examples\n    - Range slider\n    - Thumb alignment\n    - Labeling a slider\n    - Vertical\n    - Form integration\n  - API reference\n- Exports:\n  - Slider - Root\n    - Props: className, defaultValue, disabled, format, largeStep, locale, max, min, minStepsBetweenValues, name, onValueChange, onValueCommitted, orientation, render, step, style, thumbAlignment, thumbCollisionBehavior, value\n    - Data Attributes: data-dirty, data-disabled, data-dragging, data-focused, data-invalid, data-orientation, data-touched, data-valid\n  - Slider - Label\n    - Props: className, render, style\n  - Slider - Value\n    - Props: children, className, render, style\n    - Data Attributes: data-dirty, data-disabled, data-dragging, data-focused, data-invalid, data-orientation, data-touched, data-valid\n  - Slider - Control\n    - Props: className, render, style\n    - Data Attributes: data-dirty, data-disabled, data-dragging, data-focused, data-invalid, data-orientation, data-touched, data-valid\n  - Slider - Track\n    - Props: className, render, style\n    - Data Attributes: data-dirty, data-disabled, data-dragging, data-focused, data-invalid, data-orientation, data-touched, data-valid\n  - Slider - Thumb\n    - Props: className, disabled, getAriaLabel, getAriaValueText, index, inputRef, onBlur, onFocus, render, style, tabIndex\n    - Data Attributes: data-dirty, data-disabled, data-dragging, data-focused, data-index, data-invalid, data-orientation, data-touched, data-valid\n  - Slider - Indicator\n    - Props: className, render, style\n    - Data Attributes: data-dirty, data-disabled, data-dragging, data-focused, data-invalid, data-orientation, data-touched, data-valid\n\n</details>\n\n[Read more](./slider/page.mdx)\n\n## Switch\n\nA control that indicates whether a setting is on or off.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Switch, Toggle Switch Component, Toggle Switch, Toggle Button, On/Off Control, On Off Switch, Binary Switch, Checkbox Alternative, Form Toggle Control, Accessible Switch, Headless React Components, Base UI\n- Sections:\n  - Usage guidelines\n  - Anatomy\n  - Examples\n    - Labeling a switch\n    - Rendering as a native button\n    - Form integration\n  - API reference\n- Exports:\n  - Switch - Root\n    - Props: checked, className, defaultChecked, disabled, id, inputRef, name, nativeButton, onCheckedChange, readOnly, render, required, style, uncheckedValue, value\n    - Data Attributes: data-checked, data-dirty, data-disabled, data-filled, data-focused, data-invalid, data-readonly, data-required, data-touched, data-unchecked, data-valid\n  - Switch - Thumb\n    - Props: className, render, style\n    - Data Attributes: data-checked, data-dirty, data-disabled, data-filled, data-focused, data-invalid, data-readonly, data-required, data-touched, data-unchecked, data-valid\n\n</details>\n\n[Read more](./switch/page.mdx)\n\n## Tabs\n\nA component for toggling between related panels on the same page.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Tabs, Tabbed Interface Component, Tab Panel, Tab Control, Tabbed Navigation, Tabbed UI, Page Switcher, Content Switcher, View Switcher, Carousel Alternative, Tab List Indicator, Accessible Tabs, Headless React Components, Base UI\n- Sections:\n  - Anatomy\n  - Examples\n    - Links\n  - API reference\n- Exports:\n  - Tabs - Root\n    - Props: className, defaultValue, onValueChange, orientation, render, style, value\n    - Data Attributes: data-activation-direction, data-orientation\n  - Tabs - Indicator\n    - Props: className, render, renderBeforeHydration, style\n    - Data Attributes: data-activation-direction, data-orientation\n    - CSS Variables: --active-tab-bottom, --active-tab-height, --active-tab-left, --active-tab-right, --active-tab-top, --active-tab-width\n  - Tabs - Tab\n    - Props: className, disabled, nativeButton, render, style, value\n    - Data Attributes: data-activation-direction, data-active, data-disabled, data-orientation\n  - Tabs - Panel\n    - Props: className, keepMounted, render, style, value\n    - Data Attributes: data-activation-direction, data-ending-style, data-hidden, data-index, data-orientation, data-starting-style\n  - Tabs - List\n    - Props: activateOnFocus, className, loopFocus, render, style\n    - Data Attributes: data-activation-direction, data-orientation\n\n</details>\n\n[Read more](./tabs/page.mdx)\n\n## Toast\n\nGenerates toast notifications.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Toast, Toast Notification, Toast Notifications, Notification Component, Notification, Snackbar, Message Notification, Alert Notification, Alert Message, Flash Message, Push Notification, Temporary Message, Dismissible Notification, Toast Message, Toast Manager, Anchored Toast, Swipe Dismiss Toast, Accessible Toast, Headless React Components, Base UI\n- Sections:\n  - Anatomy\n  - General usage\n  - Global manager\n  - Stacking and animations\n  - Examples\n    - Anchored toasts\n    - Custom position\n    - Undo action\n    - Promise\n    - Custom\n    - Varying heights\n  - API reference\n  - useToastManager\n    - Return value\n    - Method options\n    - `add` method\n    - `update` method\n    - `close` method\n    - `promise` method\n- Exports:\n  - Toast - Root\n    - Props: className, render, style, swipeDirection, toast\n    - Data Attributes: data-ending-style, data-expanded, data-limited, data-starting-style, data-swipe-direction, data-swiping, data-type\n    - CSS Variables: --toast-height, --toast-index, --toast-offset-y, --toast-swipe-movement-x, --toast-swipe-movement-y\n  - Toast - Provider\n    - Props: children, limit, timeout, toastManager\n  - Toast - Viewport\n    - Props: className, render, style\n    - Data Attributes: data-expanded\n    - CSS Variables: --toast-frontmost-height\n  - Toast - Content\n    - Props: className, render, style\n    - Data Attributes: data-behind, data-expanded\n  - Toast - Description\n    - Props: className, render, style\n    - Data Attributes: data-type\n  - Toast - Title\n    - Props: className, render, style\n    - Data Attributes: data-type\n  - Toast - Close\n    - Props: className, nativeButton, render, style\n    - Data Attributes: data-type\n  - Toast - Action\n    - Props: className, nativeButton, render, style\n    - Data Attributes: data-type\n  - Toast - Portal\n    - Props: className, container, render, style\n  - Toast - Positioner\n    - Props: align, alignOffset, anchor, arrowPadding, className, collisionAvoidance, collisionBoundary, collisionPadding, disableAnchorTracking, positionMethod, render, side, sideOffset, sticky, style, toast\n    - Data Attributes: data-align, data-anchor-hidden, data-side\n    - CSS Variables: --anchor-height, --anchor-width, --available-height, --available-width, --transform-origin\n  - Toast - Arrow\n    - Props: className, render, style\n    - Data Attributes: data-align, data-side, data-uncentered\n\n</details>\n\n[Read more](./toast/page.mdx)\n\n## Toggle\n\nA two-state button that can be on or off.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Toggle, Toggle Button Component, Two State Button, Press Button, Push Toggle, Pressed State Button, Toggle Control, ARIA Pressed Control, Accessible Toggle, Headless React Components, Base UI\n- Sections:\n  - Anatomy\n  - API reference\n- Exports:\n  - Toggle\n    - Props: className, defaultPressed, disabled, nativeButton, onPressedChange, pressed, render, style, value\n    - Data Attributes: data-pressed\n\n</details>\n\n[Read more](./toggle/page.mdx)\n\n## Toggle Group\n\nProvides a shared state to a series of toggle buttons.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Toggle Group, Toggle Button Group, Segmented Control, Button Set, Radio Group Alternative, Exclusive Selection Toggle, Multi Select Toggle Buttons, Accessible Toggle Group, Headless React Components, Base UI\n- Sections:\n  - Anatomy\n  - Examples\n    - Multiple\n  - API reference\n- Exports:\n  - ToggleGroup\n    - Props: className, defaultValue, disabled, loopFocus, multiple, onValueChange, orientation, render, style, value\n    - Data Attributes: data-disabled, data-multiple, data-orientation\n\n</details>\n\n[Read more](./toggle-group/page.mdx)\n\n## Toolbar\n\nA container for grouping a set of buttons and controls.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Toolbar, Toolbar Button Group, Action Bar, Button Bar, Command Strip, Headless Toolbar Component, Accessible Toolbar, Popup Trigger Toolbar, Base UI\n- Sections:\n  - Usage guidelines\n  - Anatomy\n  - Examples\n    - Using with Menu\n    - Using with Tooltip\n    - Using with NumberField\n  - API reference\n- Exports:\n  - Toolbar - Root\n    - Props: className, disabled, loopFocus, orientation, render, style\n    - Data Attributes: data-disabled, data-orientation\n  - Toolbar - Group\n    - Props: className, disabled, render, style\n    - Data Attributes: data-disabled, data-orientation\n  - Toolbar - Button\n    - Props: className, disabled, focusableWhenDisabled, nativeButton, render, style\n    - Data Attributes: data-disabled, data-focusable, data-orientation\n  - Toolbar - Link\n    - Props: className, render, style\n    - Data Attributes: data-orientation\n  - Toolbar - Input\n    - Props: className, defaultValue, disabled, focusableWhenDisabled, render, style\n    - Data Attributes: data-disabled, data-focusable, data-orientation\n  - Toolbar - Separator\n    - Props: className, orientation, render, style\n    - Data Attributes: data-orientation\n\n</details>\n\n[Read more](./toolbar/page.mdx)\n\n## Tooltip\n\nA popup that appears when an element is hovered or focused, showing a hint for sighted users.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: React Tooltip, Tooltip Component, Detached Trigger Tooltip, Hint, Help Text, Hover Text, Hover Hint, Info Popup, Infotip, Flyout, Accessible Tooltip, Multiple Tooltip Triggers, Animated Tooltip, Headless React Components, Base UI\n- Sections:\n  - Usage guidelines\n  - Anatomy\n  - Alternatives to tooltips\n    - Infotips\n    - Description text\n    - Contextual feedback messages\n  - Examples\n    - Detached triggers\n    - Multiple triggers\n    - Controlled mode with multiple triggers\n    - Animating the Tooltip\n      - Position and Size\n      - Content\n  - API reference\n- Exports:\n  - Tooltip - Provider\n    - Props: children, closeDelay, delay, timeout\n  - Tooltip - Root\n    - Props: actionsRef, children, defaultOpen, defaultTriggerId, disableHoverablePopup, disabled, handle, onOpenChange, onOpenChangeComplete, open, trackCursorAxis, triggerId\n  - Tooltip - Trigger\n    - Props: className, closeDelay, closeOnClick, delay, disabled, handle, payload, render, style\n    - Data Attributes: data-popup-open, data-trigger-disabled\n  - Tooltip - Portal\n    - Props: className, container, keepMounted, render, style\n  - Tooltip - Positioner\n    - Props: align, alignOffset, anchor, arrowPadding, className, collisionAvoidance, collisionBoundary, collisionPadding, disableAnchorTracking, positionMethod, render, side, sideOffset, sticky, style\n    - Data Attributes: data-align, data-anchor-hidden, data-closed, data-open, data-side\n    - CSS Variables: --anchor-height, --anchor-width, --available-height, --available-width, --transform-origin\n  - Tooltip - Popup\n    - Props: className, render, style\n    - Data Attributes: data-align, data-closed, data-ending-style, data-instant, data-open, data-side, data-starting-style\n  - Tooltip - Arrow\n    - Props: className, render, style\n    - Data Attributes: data-align, data-closed, data-instant, data-open, data-side, data-uncentered\n  - Tooltip - Viewport\n    - Props: children, className, render, style\n    - Data Attributes: data-activation-direction, data-current, data-instant, data-previous, data-transitioning\n    - CSS Variables: --popup-height, --popup-width\n\n</details>\n\n[Read more](./tooltip/page.mdx)\n\n[//]: # 'The above section is autogenerated, but the remainder of the file can be modified.'\n\nexport const metadata =\n  /** @type {import('@mui/internal-docs-infra/createSitemap/types').NextMetadata} */ ({\n    robots: {\n      index: false,\n    },\n    other: {\n      audience: 'private',\n    },\n  });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/popover/demos/_index.module.css",
    "content": ".IconButton {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 2.5rem;\n  height: 2.5rem;\n  padding: 0;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Icon {\n  width: 1.25rem;\n  height: 1.25rem;\n}\n\n.Positioner {\n  width: var(--positioner-width);\n  height: var(--positioner-height);\n  max-width: var(--available-width);\n}\n\n.Popup {\n  box-sizing: border-box;\n  padding: 1rem 1.5rem;\n  border-radius: 0.5rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n\n  width: var(--popup-width, auto);\n  height: var(--popup-height, auto);\n  max-width: 500px;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Arrow {\n  display: flex;\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n\n.Title {\n  margin: 0;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  font-weight: 700;\n}\n\n.Description {\n  margin: 0;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-600);\n}\n\n.Container {\n  display: flex;\n  gap: 8px;\n  flex-wrap: wrap;\n  justify-content: center;\n}\n\n.Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: 0.375rem;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/popover/demos/detached-triggers-controlled/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Popover } from '@base-ui/react/popover';\nimport styles from '../../_index.module.css';\n\nconst demoPopover = Popover.createHandle();\n\nexport default function PopoverDetachedTriggersControlledDemo() {\n  const [open, setOpen] = React.useState(false);\n  const [triggerId, setTriggerId] = React.useState<string | null>(null);\n\n  const handleOpenChange = (isOpen: boolean, eventDetails: Popover.Root.ChangeEventDetails) => {\n    setOpen(isOpen);\n    setTriggerId(eventDetails.trigger?.id ?? null);\n  };\n\n  return (\n    <React.Fragment>\n      <div className={styles.Container}>\n        <Popover.Trigger className={styles.IconButton} handle={demoPopover} id=\"trigger-1\">\n          <BellIcon aria-label=\"Notifications\" className={styles.Icon} />\n        </Popover.Trigger>\n\n        <Popover.Trigger className={styles.IconButton} handle={demoPopover} id=\"trigger-2\">\n          <BellIcon aria-label=\"Notifications\" className={styles.Icon} />\n        </Popover.Trigger>\n\n        <Popover.Trigger className={styles.IconButton} handle={demoPopover} id=\"trigger-3\">\n          <BellIcon aria-label=\"Notifications\" className={styles.Icon} />\n        </Popover.Trigger>\n\n        <button\n          className={styles.Button}\n          type=\"button\"\n          onClick={() => {\n            setTriggerId('trigger-2');\n            setOpen(true);\n          }}\n        >\n          Open programmatically\n        </button>\n      </div>\n\n      <Popover.Root\n        handle={demoPopover}\n        open={open}\n        onOpenChange={handleOpenChange}\n        triggerId={triggerId}\n      >\n        <Popover.Portal>\n          <Popover.Positioner className={styles.Positioner} sideOffset={8}>\n            <Popover.Popup className={styles.Popup}>\n              <Popover.Arrow className={styles.Arrow}>\n                <ArrowSvg />\n              </Popover.Arrow>\n              <Popover.Title className={styles.Title}>Notifications</Popover.Title>\n              <Popover.Description className={styles.Description}>\n                You are all caught up. Good job!\n              </Popover.Description>\n            </Popover.Popup>\n          </Popover.Positioner>\n        </Popover.Portal>\n      </Popover.Root>\n    </React.Fragment>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nfunction BellIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"20\" height=\"20\" viewBox=\"0 0 16 16\" {...props}>\n      <path d=\"M 8 1 C 7.453125 1 7 1.453125 7 2 L 7 3.140625 C 5.28125 3.589844 4 5.144531 4 7 L 4 10.984375 C 4 10.984375 3.984375 11.261719 3.851563 11.519531 C 3.71875 11.78125 3.558594 12 3 12 L 3 13 L 13 13 L 13 12 C 12.40625 12 12.253906 11.78125 12.128906 11.53125 C 12.003906 11.277344 12 11.003906 12 11.003906 L 12 7 C 12 5.144531 10.71875 3.589844 9 3.140625 L 9 2 C 9 1.453125 8.546875 1 8 1 Z M 8 13 C 7.449219 13 7 13.449219 7 14 C 7 14.550781 7.449219 15 8 15 C 8.550781 15 9 14.550781 9 14 C 9 13.449219 8.550781 13 8 13 Z M 8 4 C 9.664063 4 11 5.335938 11 7 L 11 10.996094 C 11 10.996094 10.988281 11.472656 11.234375 11.96875 C 11.238281 11.980469 11.246094 11.988281 11.25 12 L 4.726563 12 C 4.730469 11.992188 4.738281 11.984375 4.742188 11.980469 C 4.992188 11.488281 5 11.015625 5 11.015625 L 5 7 C 5 5.335938 6.335938 4 8 4 Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/popover/demos/detached-triggers-controlled/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoPopoverDetachedTriggersControlled = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/popover/demos/detached-triggers-controlled/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Popover } from '@base-ui/react/popover';\nimport { ArrowSvg, BellIcon } from '../../icons-tw';\n\nconst demoPopover = Popover.createHandle();\n\nexport default function PopoverDetachedTriggersSimpleDemo() {\n  const [open, setOpen] = React.useState(false);\n  const [triggerId, setTriggerId] = React.useState<string | null>(null);\n\n  const handleOpenChange = (isOpen: boolean, eventDetails: Popover.Root.ChangeEventDetails) => {\n    setOpen(isOpen);\n    setTriggerId(eventDetails.trigger?.id ?? null);\n  };\n\n  return (\n    <React.Fragment>\n      <div className=\"flex gap-2 flex-wrap justify-center\">\n        <Popover.Trigger\n          className=\"flex size-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100 data-[popup-open]:bg-gray-100\"\n          handle={demoPopover}\n          id=\"trigger-1\"\n        >\n          <BellIcon aria-label=\"Notifications\" />\n        </Popover.Trigger>\n\n        <Popover.Trigger\n          className=\"flex size-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100 data-[popup-open]:bg-gray-100\"\n          handle={demoPopover}\n          id=\"trigger-2\"\n        >\n          <BellIcon aria-label=\"Notifications\" />\n        </Popover.Trigger>\n\n        <Popover.Trigger\n          className=\"flex size-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100 data-[popup-open]:bg-gray-100\"\n          handle={demoPopover}\n          id=\"trigger-3\"\n        >\n          <BellIcon aria-label=\"Notifications\" />\n        </Popover.Trigger>\n\n        <button\n          type=\"button\"\n          className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\"\n          onClick={() => {\n            setTriggerId('trigger-2');\n            setOpen(true);\n          }}\n        >\n          Open programmatically\n        </button>\n      </div>\n\n      <Popover.Root\n        handle={demoPopover}\n        open={open}\n        onOpenChange={handleOpenChange}\n        triggerId={triggerId}\n      >\n        <Popover.Portal>\n          <Popover.Positioner\n            className=\"h-(--positioner-height) w-(--positioner-width) max-w-(--available-width)\"\n            sideOffset={8}\n          >\n            <Popover.Popup\n              className={`\n              h-(--popup-height,auto)\n              w-(--popup-width,auto) max-w-[500px]\n              origin-[var(--transform-origin)] rounded-lg bg-[canvas]\n              px-6 py-4\n              text-gray-900 shadow-lg\n              shadow-gray-200\n              outline-1\n              outline-gray-200\n              transition-[transform,scale,opacity]\n              data-[ending-style]:scale-90\n              data-[ending-style]:opacity-0\n              data-[starting-style]:scale-90\n              data-[starting-style]:opacity-0\n              dark:shadow-none\n              dark:-outline-offset-1\n              dark:outline-gray-300`}\n            >\n              <Popover.Arrow className=\"data-[side=bottom]:top-[-8px] data-[side=left]:right-[-13px] data-[side=left]:rotate-90 data-[side=right]:left-[-13px] data-[side=right]:-rotate-90 data-[side=top]:bottom-[-8px] data-[side=top]:rotate-180\">\n                <ArrowSvg />\n              </Popover.Arrow>\n              <Popover.Title className=\"text-base font-bold\">Notifications</Popover.Title>\n              <Popover.Description className=\"text-base text-gray-600\">\n                You are all caught up. Good job!\n              </Popover.Description>\n            </Popover.Popup>\n          </Popover.Positioner>\n        </Popover.Portal>\n      </Popover.Root>\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/popover/demos/detached-triggers-full/css-modules/index.module.css",
    "content": ".IconButton {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 2.5rem;\n  height: 2.5rem;\n  padding: 0;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  color: var(--color-gray-900);\n  user-select: none;\n  font-size: 1rem;\n  font-weight: 400;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Icon {\n  width: 1.25rem;\n  height: 1.25rem;\n}\n\n.Positioner {\n  --easing: cubic-bezier(0.22, 1, 0.36, 1);\n  --animation-duration: 0.35s;\n\n  width: var(--positioner-width);\n  height: var(--positioner-height);\n  max-width: var(--available-width);\n\n  transition-property: top, left, right, bottom, transform;\n  transition-timing-function: var(--easing);\n  transition-duration: var(--animation-duration);\n\n  /* Disable transitions when data-instant is set (used for the initial positioning of the popup) */\n  &[data-instant] {\n    transition: none;\n  }\n}\n\n.Popup {\n  position: relative;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  border-radius: 0.5rem;\n  transform-origin: var(--transform-origin);\n\n  /* These are required to make the size animations work */\n  width: var(--popup-width, auto);\n  height: var(--popup-height, auto);\n\n  max-width: 500px;\n\n  /* width and height are essential for the resize animation; opacity and transform handle the enter/exit animation */\n  transition-property: width, height, opacity, transform;\n  transition-timing-function: var(--easing);\n  transition-duration: var(--animation-duration);\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  &[data-instant] {\n    transition: none;\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Viewport {\n  --viewport-inline-padding: 1.5rem;\n  box-sizing: border-box;\n  /* Required to clip the overflowing content during the slide in/out animations */\n  position: relative;\n  overflow: clip;\n  width: 100%;\n  height: 100%;\n  padding: 1rem var(--viewport-inline-padding);\n\n  [data-previous],\n  [data-current] {\n    /* This freezes the width of the content while transitioning.\n       Width is set to the content area of the parent popup (--popup-width measures the border-box).\n       The 'previous` container receives the width of the previous content, while the `next` container\n        receives the width of the new content.\n    */\n    width: calc(var(--popup-width) - 2 * var(--viewport-inline-padding));\n    transform: translateX(0);\n    opacity: 1;\n    transition:\n      transform var(--animation-duration) var(--easing),\n      opacity calc(var(--animation-duration) / 2) var(--easing);\n  }\n\n  &[data-activation-direction~='right'] [data-previous][data-ending-style] {\n    transform: translateX(-50%);\n    opacity: 0;\n  }\n\n  &[data-activation-direction~='right'] [data-current][data-starting-style] {\n    transform: translateX(50%);\n    opacity: 0;\n  }\n\n  &[data-activation-direction~='left'] [data-previous][data-ending-style] {\n    transform: translateX(50%);\n    opacity: 0;\n  }\n\n  &[data-activation-direction~='left'] [data-current][data-starting-style] {\n    transform: translateX(-50%);\n    opacity: 0;\n  }\n}\n\n.Arrow {\n  display: flex;\n  transition: left calc(var(--animation-duration)) var(--easing);\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n\n.Title {\n  margin: 0;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  font-weight: 700;\n}\n\n.Description {\n  margin: 0;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-600);\n}\n\n.Container {\n  display: flex;\n  gap: 8px;\n}\n\n.ProfilePanel {\n  display: grid;\n  grid-template-columns: auto auto;\n  column-gap: 16px;\n  margin: 0 -8px;\n\n  .Title {\n    grid-column: 2;\n    grid-row: 1;\n  }\n\n  .Plan {\n    grid-column: 2;\n    grid-row: 2;\n\n    font-size: 0.875rem;\n    color: var(--color-gray-600);\n  }\n\n  .Avatar {\n    grid-column: 1;\n    grid-row: 1 / span 2;\n\n    display: inline-flex;\n    justify-content: center;\n    align-items: center;\n    vertical-align: middle;\n    border-radius: 100%;\n    user-select: none;\n    font-weight: 700;\n    color: var(--color-gray-900);\n    background-color: var(--color-gray-100);\n    font-size: 1rem;\n    line-height: 1;\n    overflow: hidden;\n    height: 3rem;\n    width: 3rem;\n  }\n\n  .AvatarImage {\n    object-fit: cover;\n    width: 100%;\n    height: 100%;\n  }\n\n  .ProfileActions {\n    grid-column: 1 / span 2;\n    grid-row: 3;\n    display: flex;\n    flex-direction: column;\n    gap: 8px;\n    margin-top: 0.5rem;\n    padding-top: 0.5rem;\n    border-top: 1px solid var(--color-gray-200);\n    font-size: 0.875rem;\n\n    a {\n      color: var(--color-gray-900);\n      text-decoration: none;\n\n      @media (hover: hover) {\n        &:hover {\n          text-decoration: underline;\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/popover/demos/detached-triggers-full/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Popover } from '@base-ui/react/popover';\nimport { Avatar } from '@base-ui/react/avatar';\nimport styles from './index.module.css';\n\nconst demoPopover = Popover.createHandle<React.ComponentType>();\n\nexport default function PopoverDetachedTriggersFullDemo() {\n  return (\n    <div className={styles.Container}>\n      <Popover.Trigger\n        className={styles.IconButton}\n        handle={demoPopover}\n        payload={NotificationsPanel}\n      >\n        <BellIcon aria-label=\"Notifications\" className={styles.Icon} />\n      </Popover.Trigger>\n\n      <Popover.Trigger className={styles.IconButton} handle={demoPopover} payload={ActivityPanel}>\n        <ListIcon aria-label=\"Activity\" className={styles.Icon} />\n      </Popover.Trigger>\n\n      <Popover.Trigger className={styles.IconButton} handle={demoPopover} payload={ProfilePanel}>\n        <UserIcon aria-label=\"Profile\" className={styles.Icon} />\n      </Popover.Trigger>\n\n      <Popover.Root handle={demoPopover}>\n        {({ payload: Payload }) => (\n          <Popover.Portal>\n            <Popover.Positioner sideOffset={8} className={styles.Positioner}>\n              <Popover.Popup className={styles.Popup}>\n                <Popover.Arrow className={styles.Arrow}>\n                  <ArrowSvg />\n                </Popover.Arrow>\n\n                <Popover.Viewport className={styles.Viewport}>\n                  {Payload !== undefined && <Payload />}\n                </Popover.Viewport>\n              </Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        )}\n      </Popover.Root>\n    </div>\n  );\n}\n\nfunction NotificationsPanel() {\n  return (\n    <React.Fragment>\n      <Popover.Title className={styles.Title}>Notifications</Popover.Title>\n      <Popover.Description className={styles.Description}>\n        You are all caught up. Good job!\n      </Popover.Description>\n    </React.Fragment>\n  );\n}\n\nfunction ProfilePanel() {\n  return (\n    <div className={styles.ProfilePanel}>\n      <Popover.Title className={styles.Title}>Jason Eventon</Popover.Title>\n      <Avatar.Root className={styles.Avatar}>\n        <Avatar.Image\n          src=\"https://images.unsplash.com/photo-1543610892-0b1f7e6d8ac1?w=128&h=128&dpr=2&q=80\"\n          width=\"48\"\n          height=\"48\"\n          className={styles.AvatarImage}\n        />\n      </Avatar.Root>\n      <span className={styles.Plan}>Pro plan</span>\n      <div className={styles.ProfileActions}>\n        <a href=\"#\">Profile settings</a>\n        <a href=\"#\">Log out</a>\n      </div>\n    </div>\n  );\n}\n\nfunction ActivityPanel() {\n  return (\n    <React.Fragment>\n      <Popover.Title className={styles.Title}>Activity</Popover.Title>\n      <Popover.Description className={styles.Description}>\n        Nothing interesting happened recently.\n      </Popover.Description>\n    </React.Fragment>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nfunction BellIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"20\" height=\"20\" viewBox=\"0 0 16 16\" {...props}>\n      <path d=\"M 8 1 C 7.453125 1 7 1.453125 7 2 L 7 3.140625 C 5.28125 3.589844 4 5.144531 4 7 L 4 10.984375 C 4 10.984375 3.984375 11.261719 3.851563 11.519531 C 3.71875 11.78125 3.558594 12 3 12 L 3 13 L 13 13 L 13 12 C 12.40625 12 12.253906 11.78125 12.128906 11.53125 C 12.003906 11.277344 12 11.003906 12 11.003906 L 12 7 C 12 5.144531 10.71875 3.589844 9 3.140625 L 9 2 C 9 1.453125 8.546875 1 8 1 Z M 8 13 C 7.449219 13 7 13.449219 7 14 C 7 14.550781 7.449219 15 8 15 C 8.550781 15 9 14.550781 9 14 C 9 13.449219 8.550781 13 8 13 Z M 8 4 C 9.664063 4 11 5.335938 11 7 L 11 10.996094 C 11 10.996094 10.988281 11.472656 11.234375 11.96875 C 11.238281 11.980469 11.246094 11.988281 11.25 12 L 4.726563 12 C 4.730469 11.992188 4.738281 11.984375 4.742188 11.980469 C 4.992188 11.488281 5 11.015625 5 11.015625 L 5 7 C 5 5.335938 6.335938 4 8 4 Z\" />\n    </svg>\n  );\n}\n\nfunction UserIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth={1.5}\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M18 20a6 6 0 0 0-12 0\" />\n      <circle cx=\"12\" cy=\"10\" r=\"4\" />\n      <circle cx=\"12\" cy=\"12\" r=\"10\" />\n    </svg>\n  );\n}\n\nfunction ListIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth={1.5}\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M3 5h.01\" />\n      <path d=\"M3 12h.01\" />\n      <path d=\"M3 19h.01\" />\n      <path d=\"M8 5h13\" />\n      <path d=\"M8 12h13\" />\n      <path d=\"M8 19h13\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/popover/demos/detached-triggers-full/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoPopoverDetachedTriggersFull = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/popover/demos/detached-triggers-full/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Popover } from '@base-ui/react/popover';\nimport { Avatar } from '@base-ui/react/avatar';\nimport { ArrowSvg, BellIcon, ListIcon, UserIcon } from '../../icons-tw';\n\nconst demoPopover = Popover.createHandle<React.ComponentType>();\n\nexport default function PopoverDetachedTriggersFullDemo() {\n  return (\n    <div className=\"flex gap-2\">\n      <Popover.Trigger\n        className={`\n          box-border flex\n          size-10 items-center justify-center\n          rounded-md border border-gray-200\n          bg-gray-50\n          text-base font-normal text-gray-900\n          select-none\n          hover:bg-gray-100 focus-visible:outline-2\n          focus-visible:-outline-offset-1\n          focus-visible:outline-blue-600 active:bg-gray-100 data-popup-open:bg-gray-100`}\n        handle={demoPopover}\n        payload={NotificationsPanel}\n      >\n        <BellIcon aria-label=\"Notifications\" className=\"size-5\" />\n      </Popover.Trigger>\n\n      <Popover.Trigger\n        className={`\n          box-border flex\n          size-10 items-center justify-center\n          rounded-md border border-gray-200\n          bg-gray-50\n          text-base font-normal text-gray-900\n          select-none\n          hover:bg-gray-100 focus-visible:outline-2\n          focus-visible:-outline-offset-1\n          focus-visible:outline-blue-600 active:bg-gray-100 data-popup-open:bg-gray-100`}\n        handle={demoPopover}\n        payload={ActivityPanel}\n      >\n        <ListIcon aria-label=\"Activity\" className=\"size-5\" />\n      </Popover.Trigger>\n\n      <Popover.Trigger\n        className={`\n          box-border flex\n          size-10 items-center justify-center\n          rounded-md border border-gray-200\n          bg-gray-50\n          text-base font-normal text-gray-900\n          select-none\n          hover:bg-gray-100 focus-visible:outline-2\n          focus-visible:-outline-offset-1\n          focus-visible:outline-blue-600 active:bg-gray-100 data-popup-open:bg-gray-100`}\n        handle={demoPopover}\n        payload={ProfilePanel}\n      >\n        <UserIcon aria-label=\"Profile\" className=\"size-5\" />\n      </Popover.Trigger>\n\n      <Popover.Root handle={demoPopover}>\n        {({ payload: Payload }) => (\n          <Popover.Portal>\n            <Popover.Positioner\n              sideOffset={8}\n              className={`\n                h-(--positioner-height) w-(--positioner-width)\n                max-w-(--available-width)\n                transition-[top,left,right,bottom,transform]\n                duration-[0.35s]\n                ease-[cubic-bezier(0.22,1,0.36,1)]\n                data-instant:transition-none`}\n            >\n              <Popover.Popup\n                className={`\n                  relative h-(--popup-height,auto) w-(--popup-width,auto)\n                  max-w-[500px] origin-(--transform-origin)\n                  rounded-lg bg-[canvas] text-gray-900\n                  shadow-lg\n                  shadow-gray-200\n                  outline-1\n                  outline-gray-200\n                  transition-[width,height,opacity,scale]\n                  duration-[0.35s]\n                  ease-[cubic-bezier(0.22,1,0.36,1)]\n                  data-ending-style:scale-90\n                  data-ending-style:opacity-0 data-instant:transition-none\n                  data-starting-style:scale-90\n                  data-starting-style:opacity-0\n                  dark:shadow-none\n                  dark:-outline-offset-1\n                  dark:outline-gray-300`}\n              >\n                <Popover.Arrow\n                  className={`\n                    flex\n                    transition-[left] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)]\n                    data-[side=bottom]:top-[-8px]\n                    data-[side=left]:right-[-13px]\n                    data-[side=left]:rotate-90\n                    data-[side=right]:left-[-13px]\n                    data-[side=right]:-rotate-90\n                    data-[side=top]:bottom-[-8px]\n                    data-[side=top]:rotate-180`}\n                >\n                  <ArrowSvg />\n                </Popover.Arrow>\n\n                <Popover.Viewport\n                  className={`\n                    relative h-full w-full overflow-clip p-[1rem_1.5rem]\n                    [&_[data-current]]:w-[calc(var(--popup-width)-3rem)]\n                    [&_[data-current]]:translate-x-0\n                    [&_[data-current]]:opacity-100\n                    [&_[data-current]]:transition-[translate,opacity]\n                    [&_[data-current]]:duration-[350ms,175ms]\n                    [&_[data-current]]:ease-[cubic-bezier(0.22,1,0.36,1)]\n                    data-[activation-direction~='left']:[&_[data-current][data-starting-style]]:-translate-x-1/2\n                    data-[activation-direction~='left']:[&_[data-current][data-starting-style]]:opacity-0\n                    data-[activation-direction~='right']:[&_[data-current][data-starting-style]]:translate-x-1/2\n                    data-[activation-direction~='right']:[&_[data-current][data-starting-style]]:opacity-0\n                    [&_[data-previous]]:w-[calc(var(--popup-width)-3rem)]\n                    [&_[data-previous]]:translate-x-0\n                    [&_[data-previous]]:opacity-100\n                    [&_[data-previous]]:transition-[translate,opacity]\n                    [&_[data-previous]]:duration-[350ms,175ms]\n                    [&_[data-previous]]:ease-[cubic-bezier(0.22,1,0.36,1)]\n                    data-[activation-direction~='left']:[&_[data-previous][data-ending-style]]:translate-x-1/2\n                    data-[activation-direction~='left']:[&_[data-previous][data-ending-style]]:opacity-0\n                    data-[activation-direction~='right']:[&_[data-previous][data-ending-style]]:-translate-x-1/2\n                    data-[activation-direction~='right']:[&_[data-previous][data-ending-style]]:opacity-0`}\n                >\n                  {Payload !== undefined && <Payload />}\n                </Popover.Viewport>\n              </Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        )}\n      </Popover.Root>\n    </div>\n  );\n}\n\nfunction NotificationsPanel() {\n  return (\n    <React.Fragment>\n      <Popover.Title className=\"m-0 text-base font-bold\">Notifications</Popover.Title>\n      <Popover.Description className=\"m-0 text-base text-gray-600\">\n        You are all caught up. Good job!\n      </Popover.Description>\n    </React.Fragment>\n  );\n}\n\nfunction ProfilePanel() {\n  return (\n    <div className=\"-mx-2 grid grid-cols-[auto_auto] gap-x-4\">\n      <Popover.Title className=\"col-start-2 col-end-3 row-start-1 row-end-2 m-0 text-base font-bold\">\n        Jason Eventon\n      </Popover.Title>\n      <Avatar.Root className=\"col-start-1 col-end-2 row-start-1 row-end-3 inline-flex h-12 w-12 items-center justify-center overflow-hidden rounded-full bg-gray-100 align-middle text-base leading-none font-bold text-gray-900 select-none\">\n        <Avatar.Image\n          src=\"https://images.unsplash.com/photo-1543610892-0b1f7e6d8ac1?w=128&h=128&dpr=2&q=80\"\n          width=\"48\"\n          height=\"48\"\n          className=\"h-full w-full object-cover\"\n        />\n      </Avatar.Root>\n      <span className=\"col-start-2 col-end-3 row-start-2 row-end-3 text-sm text-gray-600\">\n        Pro plan\n      </span>\n      <div className=\"col-start-1 col-end-3 row-start-3 row-end-4 mt-2 flex flex-col gap-2 border-t border-gray-200 pt-2 text-sm\">\n        <a href=\"#\" className=\"text-gray-900 no-underline hover:underline\">\n          Profile settings\n        </a>\n        <a href=\"#\" className=\"text-gray-900 no-underline hover:underline\">\n          Log out\n        </a>\n      </div>\n    </div>\n  );\n}\n\nfunction ActivityPanel() {\n  return (\n    <React.Fragment>\n      <Popover.Title className=\"m-0 text-base font-bold\">Activity</Popover.Title>\n      <Popover.Description className=\"m-0 text-base text-gray-600\">\n        Nothing interesting happened recently.\n      </Popover.Description>\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/popover/demos/detached-triggers-simple/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Popover } from '@base-ui/react/popover';\nimport styles from '../../_index.module.css';\n\nconst demoPopover = Popover.createHandle();\n\nexport default function PopoverDetachedTriggersSimpleDemo() {\n  return (\n    <React.Fragment>\n      <Popover.Trigger className={styles.IconButton} handle={demoPopover}>\n        <BellIcon aria-label=\"Notifications\" className={styles.Icon} />\n      </Popover.Trigger>\n\n      <Popover.Root handle={demoPopover}>\n        <Popover.Portal>\n          <Popover.Positioner sideOffset={8}>\n            <Popover.Popup className={styles.Popup}>\n              <Popover.Arrow className={styles.Arrow}>\n                <ArrowSvg />\n              </Popover.Arrow>\n              <Popover.Title className={styles.Title}>Notifications</Popover.Title>\n              <Popover.Description className={styles.Description}>\n                You are all caught up. Good job!\n              </Popover.Description>\n            </Popover.Popup>\n          </Popover.Positioner>\n        </Popover.Portal>\n      </Popover.Root>\n    </React.Fragment>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nfunction BellIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"20\" height=\"20\" viewBox=\"0 0 16 16\" {...props}>\n      <path d=\"M 8 1 C 7.453125 1 7 1.453125 7 2 L 7 3.140625 C 5.28125 3.589844 4 5.144531 4 7 L 4 10.984375 C 4 10.984375 3.984375 11.261719 3.851563 11.519531 C 3.71875 11.78125 3.558594 12 3 12 L 3 13 L 13 13 L 13 12 C 12.40625 12 12.253906 11.78125 12.128906 11.53125 C 12.003906 11.277344 12 11.003906 12 11.003906 L 12 7 C 12 5.144531 10.71875 3.589844 9 3.140625 L 9 2 C 9 1.453125 8.546875 1 8 1 Z M 8 13 C 7.449219 13 7 13.449219 7 14 C 7 14.550781 7.449219 15 8 15 C 8.550781 15 9 14.550781 9 14 C 9 13.449219 8.550781 13 8 13 Z M 8 4 C 9.664063 4 11 5.335938 11 7 L 11 10.996094 C 11 10.996094 10.988281 11.472656 11.234375 11.96875 C 11.238281 11.980469 11.246094 11.988281 11.25 12 L 4.726563 12 C 4.730469 11.992188 4.738281 11.984375 4.742188 11.980469 C 4.992188 11.488281 5 11.015625 5 11.015625 L 5 7 C 5 5.335938 6.335938 4 8 4 Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/popover/demos/detached-triggers-simple/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoPopoverDetachedTriggersSimple = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/popover/demos/detached-triggers-simple/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Popover } from '@base-ui/react/popover';\nimport { ArrowSvg, BellIcon } from '../../icons-tw';\n\nconst demoPopover = Popover.createHandle();\n\nexport default function PopoverDetachedTriggersSimpleDemo() {\n  return (\n    <React.Fragment>\n      <Popover.Trigger\n        className=\"flex size-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100 data-[popup-open]:bg-gray-100\"\n        handle={demoPopover}\n      >\n        <BellIcon aria-label=\"Notifications\" />\n      </Popover.Trigger>\n\n      <Popover.Root handle={demoPopover}>\n        <Popover.Portal>\n          <Popover.Positioner sideOffset={8}>\n            <Popover.Popup className=\"origin-[var(--transform-origin)] rounded-lg bg-[canvas] px-6 py-4 text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 transition-[transform,scale,opacity] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300\">\n              <Popover.Arrow className=\"data-[side=bottom]:top-[-8px] data-[side=left]:right-[-13px] data-[side=left]:rotate-90 data-[side=right]:left-[-13px] data-[side=right]:-rotate-90 data-[side=top]:bottom-[-8px] data-[side=top]:rotate-180\">\n                <ArrowSvg />\n              </Popover.Arrow>\n              <Popover.Title className=\"text-base font-bold\">Notifications</Popover.Title>\n              <Popover.Description className=\"text-base text-gray-600\">\n                You are all caught up. Good job!\n              </Popover.Description>\n            </Popover.Popup>\n          </Popover.Positioner>\n        </Popover.Portal>\n      </Popover.Root>\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/popover/demos/hero/css-modules/index.tsx",
    "content": "import * as React from 'react';\nimport { Popover } from '@base-ui/react/popover';\nimport styles from '../../_index.module.css';\n\nexport default function ExamplePopover() {\n  return (\n    <Popover.Root>\n      <Popover.Trigger className={styles.IconButton}>\n        <BellIcon aria-label=\"Notifications\" className={styles.Icon} />\n      </Popover.Trigger>\n      <Popover.Portal>\n        <Popover.Positioner sideOffset={8}>\n          <Popover.Popup className={styles.Popup}>\n            <Popover.Arrow className={styles.Arrow}>\n              <ArrowSvg />\n            </Popover.Arrow>\n            <Popover.Title className={styles.Title}>Notifications</Popover.Title>\n            <Popover.Description className={styles.Description}>\n              You are all caught up. Good job!\n            </Popover.Description>\n          </Popover.Popup>\n        </Popover.Positioner>\n      </Popover.Portal>\n    </Popover.Root>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nfunction BellIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"20\" height=\"20\" viewBox=\"0 0 16 16\" {...props}>\n      <path d=\"M 8 1 C 7.453125 1 7 1.453125 7 2 L 7 3.140625 C 5.28125 3.589844 4 5.144531 4 7 L 4 10.984375 C 4 10.984375 3.984375 11.261719 3.851563 11.519531 C 3.71875 11.78125 3.558594 12 3 12 L 3 13 L 13 13 L 13 12 C 12.40625 12 12.253906 11.78125 12.128906 11.53125 C 12.003906 11.277344 12 11.003906 12 11.003906 L 12 7 C 12 5.144531 10.71875 3.589844 9 3.140625 L 9 2 C 9 1.453125 8.546875 1 8 1 Z M 8 13 C 7.449219 13 7 13.449219 7 14 C 7 14.550781 7.449219 15 8 15 C 8.550781 15 9 14.550781 9 14 C 9 13.449219 8.550781 13 8 13 Z M 8 4 C 9.664063 4 11 5.335938 11 7 L 11 10.996094 C 11 10.996094 10.988281 11.472656 11.234375 11.96875 C 11.238281 11.980469 11.246094 11.988281 11.25 12 L 4.726563 12 C 4.730469 11.992188 4.738281 11.984375 4.742188 11.980469 C 4.992188 11.488281 5 11.015625 5 11.015625 L 5 7 C 5 5.335938 6.335938 4 8 4 Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/popover/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoPopoverHero = createDemoWithVariants(import.meta.url, { CssModules, Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/popover/demos/hero/tailwind/index.tsx",
    "content": "import { Popover } from '@base-ui/react/popover';\nimport { BellIcon, ArrowSvg } from '../../icons-tw';\n\nexport default function ExamplePopover() {\n  return (\n    <Popover.Root>\n      <Popover.Trigger className=\"flex size-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100 data-[popup-open]:bg-gray-100 font-normal\">\n        <BellIcon aria-label=\"Notifications\" />\n      </Popover.Trigger>\n      <Popover.Portal>\n        <Popover.Positioner sideOffset={8}>\n          <Popover.Popup className=\"origin-[var(--transform-origin)] rounded-lg bg-[canvas] px-6 py-4 text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 transition-[transform,scale,opacity] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300\">\n            <Popover.Arrow className=\"data-[side=bottom]:top-[-8px] data-[side=left]:right-[-13px] data-[side=left]:rotate-90 data-[side=right]:left-[-13px] data-[side=right]:-rotate-90 data-[side=top]:bottom-[-8px] data-[side=top]:rotate-180\">\n              <ArrowSvg />\n            </Popover.Arrow>\n            <Popover.Title className=\"text-base font-bold\">Notifications</Popover.Title>\n            <Popover.Description className=\"text-base text-gray-600\">\n              You are all caught up. Good job!\n            </Popover.Description>\n          </Popover.Popup>\n        </Popover.Positioner>\n      </Popover.Portal>\n    </Popover.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/popover/demos/icons-tw.tsx",
    "content": "import * as React from 'react';\n\nexport function ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className=\"fill-[canvas]\"\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className=\"fill-gray-200 dark:fill-none\"\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className=\"dark:fill-gray-300\"\n      />\n    </svg>\n  );\n}\n\nexport function BellIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"20\" height=\"20\" viewBox=\"0 0 16 16\" {...props}>\n      <path d=\"M 8 1 C 7.453125 1 7 1.453125 7 2 L 7 3.140625 C 5.28125 3.589844 4 5.144531 4 7 L 4 10.984375 C 4 10.984375 3.984375 11.261719 3.851563 11.519531 C 3.71875 11.78125 3.558594 12 3 12 L 3 13 L 13 13 L 13 12 C 12.40625 12 12.253906 11.78125 12.128906 11.53125 C 12.003906 11.277344 12 11.003906 12 11.003906 L 12 7 C 12 5.144531 10.71875 3.589844 9 3.140625 L 9 2 C 9 1.453125 8.546875 1 8 1 Z M 8 13 C 7.449219 13 7 13.449219 7 14 C 7 14.550781 7.449219 15 8 15 C 8.550781 15 9 14.550781 9 14 C 9 13.449219 8.550781 13 8 13 Z M 8 4 C 9.664063 4 11 5.335938 11 7 L 11 10.996094 C 11 10.996094 10.988281 11.472656 11.234375 11.96875 C 11.238281 11.980469 11.246094 11.988281 11.25 12 L 4.726563 12 C 4.730469 11.992188 4.738281 11.984375 4.742188 11.980469 C 4.992188 11.488281 5 11.015625 5 11.015625 L 5 7 C 5 5.335938 6.335938 4 8 4 Z\" />\n    </svg>\n  );\n}\n\nexport function UserIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth={1.5}\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M18 20a6 6 0 0 0-12 0\" />\n      <circle cx=\"12\" cy=\"10\" r=\"4\" />\n      <circle cx=\"12\" cy=\"12\" r=\"10\" />\n    </svg>\n  );\n}\n\nexport function ListIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth={1.5}\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M3 5h.01\" />\n      <path d=\"M3 12h.01\" />\n      <path d=\"M3 19h.01\" />\n      <path d=\"M8 5h13\" />\n      <path d=\"M8 12h13\" />\n      <path d=\"M8 19h13\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/popover/demos/open-on-hover/css-modules/index.tsx",
    "content": "import * as React from 'react';\nimport { Popover } from '@base-ui/react/popover';\nimport styles from '../../_index.module.css';\n\nexport default function ExamplePopover() {\n  return (\n    <Popover.Root>\n      <Popover.Trigger openOnHover className={styles.IconButton}>\n        <BellIcon aria-label=\"Notifications\" className={styles.Icon} />\n      </Popover.Trigger>\n      <Popover.Portal>\n        <Popover.Positioner sideOffset={8}>\n          <Popover.Popup className={styles.Popup}>\n            <Popover.Arrow className={styles.Arrow}>\n              <ArrowSvg />\n            </Popover.Arrow>\n            <Popover.Title className={styles.Title}>Notifications</Popover.Title>\n            <Popover.Description className={styles.Description}>\n              You are all caught up. Good job!\n            </Popover.Description>\n          </Popover.Popup>\n        </Popover.Positioner>\n      </Popover.Portal>\n    </Popover.Root>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nfunction BellIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"20\" height=\"20\" viewBox=\"0 0 16 16\" {...props}>\n      <path d=\"M 8 1 C 7.453125 1 7 1.453125 7 2 L 7 3.140625 C 5.28125 3.589844 4 5.144531 4 7 L 4 10.984375 C 4 10.984375 3.984375 11.261719 3.851563 11.519531 C 3.71875 11.78125 3.558594 12 3 12 L 3 13 L 13 13 L 13 12 C 12.40625 12 12.253906 11.78125 12.128906 11.53125 C 12.003906 11.277344 12 11.003906 12 11.003906 L 12 7 C 12 5.144531 10.71875 3.589844 9 3.140625 L 9 2 C 9 1.453125 8.546875 1 8 1 Z M 8 13 C 7.449219 13 7 13.449219 7 14 C 7 14.550781 7.449219 15 8 15 C 8.550781 15 9 14.550781 9 14 C 9 13.449219 8.550781 13 8 13 Z M 8 4 C 9.664063 4 11 5.335938 11 7 L 11 10.996094 C 11 10.996094 10.988281 11.472656 11.234375 11.96875 C 11.238281 11.980469 11.246094 11.988281 11.25 12 L 4.726563 12 C 4.730469 11.992188 4.738281 11.984375 4.742188 11.980469 C 4.992188 11.488281 5 11.015625 5 11.015625 L 5 7 C 5 5.335938 6.335938 4 8 4 Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/popover/demos/open-on-hover/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoPopoverOpenOnHover = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/popover/demos/open-on-hover/tailwind/index.tsx",
    "content": "import { Popover } from '@base-ui/react/popover';\nimport { BellIcon, ArrowSvg } from '../../icons-tw';\n\nexport default function ExamplePopover() {\n  return (\n    <Popover.Root>\n      <Popover.Trigger\n        openOnHover\n        className=\"flex size-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100 data-[popup-open]:bg-gray-100\"\n      >\n        <BellIcon aria-label=\"Notifications\" />\n      </Popover.Trigger>\n      <Popover.Portal>\n        <Popover.Positioner sideOffset={8}>\n          <Popover.Popup className=\"origin-[var(--transform-origin)] rounded-lg bg-[canvas] px-6 py-4 text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 transition-[transform,scale,opacity] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300\">\n            <Popover.Arrow className=\"data-[side=bottom]:top-[-8px] data-[side=left]:right-[-13px] data-[side=left]:rotate-90 data-[side=right]:left-[-13px] data-[side=right]:-rotate-90 data-[side=top]:bottom-[-8px] data-[side=top]:rotate-180\">\n              <ArrowSvg />\n            </Popover.Arrow>\n            <Popover.Title className=\"text-base font-bold\">Notifications</Popover.Title>\n            <Popover.Description className=\"text-base text-gray-600\">\n              You are all caught up. Good job!\n            </Popover.Description>\n          </Popover.Popup>\n        </Popover.Positioner>\n      </Popover.Portal>\n    </Popover.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/popover/page.mdx",
    "content": "# Popover\n\n<Subtitle>An accessible popup anchored to a button.</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"A high-quality, unstyled React popover component that displays an accessible popup anchored to a button.\"\n/>\n\nimport { DemoPopoverHero } from './demos/hero';\n\n<DemoPopoverHero />\n\n## Anatomy\n\nImport the component and assemble its parts:\n\n```jsx title=\"Anatomy\"\nimport { Popover } from '@base-ui/react/popover';\n\n<Popover.Root>\n  <Popover.Trigger />\n  <Popover.Portal>\n    <Popover.Backdrop />\n    <Popover.Positioner>\n      <Popover.Popup>\n        <Popover.Arrow />\n        <Popover.Viewport>\n          <Popover.Title />\n          <Popover.Description />\n          <Popover.Close />\n        </Popover.Viewport>\n      </Popover.Popup>\n    </Popover.Positioner>\n  </Popover.Portal>\n</Popover.Root>;\n```\n\n## Examples\n\n### Opening on hover\n\nThis example shows how you can configure the popover to open on hover using the `openOnHover` prop.\n\nYou can use the `delay` prop to specify how long to wait (in milliseconds) before the popover opens on hover.\n\nimport { DemoPopoverOpenOnHover } from './demos/open-on-hover';\n\n<DemoPopoverOpenOnHover compact />\n\n### Detached triggers\n\nA popover can be controlled by a trigger located either inside or outside the `<Popover.Root>` component.\nFor simple, one-off interactions, place the `<Popover.Trigger>` inside `<Popover.Root>`, as shown in the example at the top of this page.\n\nHowever, if defining the popover's content next to its trigger is not practical, you can use a detached trigger.\nThis involves placing the `<Popover.Trigger>` outside of `<Popover.Root>` and linking them with a `handle` created by the `Popover.createHandle()` function.\n\n```jsx title=\"Detached triggers\"\nconst demoPopover = Popover.createHandle();\n\n<Popover.Trigger handle={demoPopover}>\n  Trigger\n</Popover.Trigger>\n\n<Popover.Root handle={demoPopover}>\n  ...\n</Popover.Root>\n```\n\nimport { DemoPopoverDetachedTriggersSimple } from './demos/detached-triggers-simple';\n\n<DemoPopoverDetachedTriggersSimple />\n\n### Multiple triggers\n\nA single popover can be opened by multiple trigger elements.\nYou can achieve this by using the same `handle` for several detached triggers, or by placing multiple `<Popover.Trigger>` components inside a single `<Popover.Root>`.\n\n```jsx title=\"Multiple triggers within the Root part\"\n<Popover.Root>\n  <Popover.Trigger>Trigger 1</Popover.Trigger>\n  <Popover.Trigger>Trigger 2</Popover.Trigger>\n  ...\n</Popover.Root>\n```\n\n```jsx title=\"Multiple detached triggers\"\nconst demoPopover = Popover.createHandle();\n\n<Popover.Trigger handle={demoPopover}>\n  Trigger 1\n</Popover.Trigger>\n\n<Popover.Trigger handle={demoPopover}>\n  Trigger 2\n</Popover.Trigger>\n\n<Popover.Root handle={demoPopover}>\n  ...\n</Popover.Root>\n```\n\nThe popover can render different content depending on which trigger opened it.\nThis is achieved by passing a `payload` to the `<Popover.Trigger>` and using the function-as-a-child pattern in `<Popover.Root>`.\n\nThe payload can be strongly typed by providing a type argument to the `createHandle()` function:\n\n```jsx title=\"Detached triggers with payload\"\nconst demoPopover = Popover.createHandle<{ text: string }>();\n\n<Popover.Trigger handle={demoPopover} payload={{ text: 'Trigger 1' }}>\n  Trigger 1\n</Popover.Trigger>\n\n<Popover.Trigger handle={demoPopover} payload={{ text: 'Trigger 2' }}>\n  Trigger 2\n</Popover.Trigger>\n\n<Popover.Root handle={demoPopover}>\n  {({ payload }) => (\n    <Popover.Portal>\n      <Popover.Positioner sideOffset={8}>\n        <Popover.Popup className={styles.Popup}>\n          <Popover.Arrow className={styles.Arrow}>\n            <ArrowSvg />\n          </Popover.Arrow>\n          <Popover.Title className={styles.Title}>Popover</Popover.Title>\n          {payload !== undefined && (\n            <Popover.Description className={styles.Description}>\n              This has been opened by {payload.text}\n            </Popover.Description>\n          )}\n        </Popover.Popup>\n      </Popover.Positioner>\n    </Popover.Portal>\n  )}\n</Popover.Root>\n```\n\n### Controlled mode with multiple triggers\n\nYou can control the popover's open state externally using the `open` and `onOpenChange` props on `<Popover.Root>`.\nThis allows you to manage the popover's visibility based on your application's state.\nWhen using multiple triggers, you have to manage which trigger is active with the `triggerId` prop on `<Popover.Root>` and the `id` prop on each `<Popover.Trigger>`.\n\nNote that there is no separate `onTriggerIdChange` prop.\nInstead, the `onOpenChange` callback receives an additional argument, `eventDetails`, which contains the trigger element that initiated the state change.\n\nimport { DemoPopoverDetachedTriggersControlled } from './demos/detached-triggers-controlled';\n\n<DemoPopoverDetachedTriggersControlled />\n\n### Animating the Popover\n\nYou can animate a popover as it moves between different trigger elements.\nThis includes animating its position, size, and content.\n\n#### Position and Size\n\nTo animate the popover's position, apply CSS transitions to the `left`, `right`, `top`, and `bottom` properties of the **Positioner** part.\nTo animate its size, transition the `width` and `height` of the **Popup** part.\n\n#### Content\n\nThe popover also supports content transitions.\nThis is useful when different triggers display different content within the same popover.\n\nTo enable content animations, wrap the content in the `<Popover.Viewport>` part.\nThis part provides features to create direction-aware animations.\nIt renders a `div` with a `data-activation-direction` attribute (`left`, `right`, `up`, or `down`) that indicates the new trigger's position relative to the previous one.\n\nInside the `<Popover.Viewport>`, the content is further wrapped in `div`s with data attributes to help with styling:\n\n- `data-current`: The currently visible content when no transitions are present or the incoming content.\n- `data-previous`: The outgoing content during a transition.\n\nYou can use these attributes to style the enter and exit animations.\n\nimport { DemoPopoverDetachedTriggersFull } from './demos/detached-triggers-full';\n\n<DemoPopoverDetachedTriggersFull />\n\n## API reference\n\n<Reference\n  component=\"Popover\"\n  parts=\"Root, Trigger, Backdrop, Portal, Positioner, Popup, Arrow, Title, Description, Close, Viewport\"\n/>\n\nexport const metadata = {\n  keywords: [\n    'React Popover',\n    'Popover Component',\n    'Popover Menu',\n    'Popup Menu',\n    'Anchored Popup',\n    'Dropdown',\n    'Overlay',\n    'Floating Panel',\n    'Tooltip Alternative',\n    'Info Box',\n    'Callout',\n    'Popup Content',\n    'Detached Trigger Popover',\n    'Multi Trigger Popover',\n    'Animated Popover',\n    'Headless React Components',\n    'Base UI',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/preview-card/demos/detached-triggers-controlled/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { PreviewCard } from '@base-ui/react/preview-card';\nimport styles from '../../index.module.css';\n\nconst demoPreviewCard = PreviewCard.createHandle<React.ReactElement>();\n\nconst cardContents = {\n  typography: (\n    <div className={styles.PopupContent}>\n      <img\n        width=\"224\"\n        height=\"150\"\n        className={styles.Image}\n        src=\"https://images.unsplash.com/photo-1619615391095-dfa29e1672ef?q=80&w=448&h=300\"\n        alt=\"Station Hofplein signage in Rotterdam, Netherlands\"\n      />\n      <p className={styles.Summary}>\n        <strong>Typography</strong> is the art and science of arranging type.\n      </p>\n    </div>\n  ),\n  design: (\n    <div className={styles.PopupContent}>\n      <img\n        width=\"241\"\n        height=\"240\"\n        className={styles.Image}\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/b/b7/Braun_ABW30_%28schwarz%29.jpg/250px-Braun_ABW30_%28schwarz%29.jpg\"\n        alt=\"Braun ABW30\"\n      />\n      <p className={styles.Summary}>\n        A <strong>design</strong> is the concept or proposal for an object, process, or system.\n      </p>\n    </div>\n  ),\n  art: (\n    <div className={styles.PopupContent}>\n      <img\n        width=\"206\"\n        height=\"240\"\n        className={styles.Image}\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/5/5f/MonaLisa_sfumato.jpeg/250px-MonaLisa_sfumato.jpeg\"\n        alt=\"Mona Lisa\"\n      />\n      <p className={styles.Summary}>\n        <strong>Art</strong> is a diverse range of cultural activity centered around works utilizing\n        creative or imaginative talents, which are expected to evoke a worthwhile experience,\n        generally through an expression of emotional power, conceptual ideas, technical proficiency,\n        or beauty.\n      </p>\n    </div>\n  ),\n};\n\nexport default function PreviewCardDetachedTriggersControlledDemo() {\n  const [open, setOpen] = React.useState(false);\n  const [triggerId, setTriggerId] = React.useState<string | null>(null);\n\n  const handleOpenChange = (isOpen: boolean, eventDetails: PreviewCard.Root.ChangeEventDetails) => {\n    setOpen(isOpen);\n    setTriggerId(eventDetails.trigger?.id ?? null);\n  };\n\n  return (\n    <div>\n      <div className={styles.Container}>\n        <p className={styles.Paragraph}>\n          Discover{' '}\n          <PreviewCard.Trigger\n            className={styles.Link}\n            handle={demoPreviewCard}\n            href=\"https://en.wikipedia.org/wiki/Typography\"\n            id=\"trigger-1\"\n            payload={cardContents.typography}\n          >\n            typography\n          </PreviewCard.Trigger>\n          ,{' '}\n          <PreviewCard.Trigger\n            className={styles.Link}\n            handle={demoPreviewCard}\n            href=\"https://en.wikipedia.org/wiki/Industrial_design\"\n            id=\"trigger-2\"\n            payload={cardContents.design}\n          >\n            design\n          </PreviewCard.Trigger>\n          , or{' '}\n          <PreviewCard.Trigger\n            className={styles.Link}\n            handle={demoPreviewCard}\n            href=\"https://en.wikipedia.org/wiki/Art\"\n            id=\"trigger-3\"\n            payload={cardContents.art}\n          >\n            art\n          </PreviewCard.Trigger>\n          .\n        </p>\n        <button\n          type=\"button\"\n          className={styles.Button}\n          onClick={() => {\n            setTriggerId('trigger-2');\n            setOpen(true);\n          }}\n        >\n          Open programmatically\n        </button>\n      </div>\n\n      <PreviewCard.Root\n        handle={demoPreviewCard}\n        open={open}\n        onOpenChange={handleOpenChange}\n        triggerId={triggerId}\n      >\n        {({ payload }) => (\n          <PreviewCard.Portal>\n            <PreviewCard.Positioner sideOffset={8} className={styles.Positioner}>\n              <PreviewCard.Popup className={styles.Popup}>\n                <PreviewCard.Arrow className={styles.Arrow}>\n                  <ArrowSvg />\n                </PreviewCard.Arrow>\n                {payload}\n              </PreviewCard.Popup>\n            </PreviewCard.Positioner>\n          </PreviewCard.Portal>\n        )}\n      </PreviewCard.Root>\n    </div>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/preview-card/demos/detached-triggers-controlled/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoPreviewCardDetachedTriggersControlled = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/preview-card/demos/detached-triggers-controlled/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { PreviewCard } from '@base-ui/react/preview-card';\n\nconst demoPreviewCard = PreviewCard.createHandle<React.ReactElement>();\n\nconst cardContents = {\n  typography: (\n    <div className=\"w-min flex flex-col gap-2 p-2 box-border\">\n      <img\n        width=\"224\"\n        height=\"150\"\n        className=\"block rounded-xs max-w-none\"\n        src=\"https://images.unsplash.com/photo-1619615391095-dfa29e1672ef?q=80&w=448&h=300\"\n        alt=\"Station Hofplein signage in Rotterdam, Netherlands\"\n      />\n      <p className=\"m-0 text-sm leading-5 text-gray-900 text-pretty\">\n        <strong>Typography</strong> is the art and science of arranging type.\n      </p>\n    </div>\n  ),\n  design: (\n    <div className=\"w-min flex flex-col gap-2 p-2 box-border\">\n      <img\n        width=\"241\"\n        height=\"240\"\n        className=\"block rounded-xs max-w-none\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/b/b7/Braun_ABW30_%28schwarz%29.jpg/250px-Braun_ABW30_%28schwarz%29.jpg\"\n        alt=\"Braun ABW30\"\n      />\n      <p className=\"m-0 text-sm leading-5 text-gray-900 text-pretty\">\n        A <strong>design</strong> is the concept or proposal for an object, process, or system.\n      </p>\n    </div>\n  ),\n  art: (\n    <div className=\"w-min flex flex-col gap-2 p-2 box-border\">\n      <img\n        width=\"206\"\n        height=\"240\"\n        className=\"block rounded-xs max-w-none\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/5/5f/MonaLisa_sfumato.jpeg/250px-MonaLisa_sfumato.jpeg\"\n        alt=\"Mona Lisa\"\n      />\n      <p className=\"m-0 text-sm leading-5 text-gray-900 text-pretty\">\n        <strong>Art</strong> is a diverse range of cultural activity centered around works utilizing\n        creative or imaginative talents, which are expected to evoke a worthwhile experience,\n        generally through an expression of emotional power, conceptual ideas, technical proficiency,\n        or beauty.\n      </p>\n    </div>\n  ),\n};\n\nexport default function PreviewCardDetachedTriggersControlledDemo() {\n  const [open, setOpen] = React.useState(false);\n  const [triggerId, setTriggerId] = React.useState<string | null>(null);\n\n  const handleOpenChange = (isOpen: boolean, eventDetails: PreviewCard.Root.ChangeEventDetails) => {\n    setOpen(isOpen);\n    setTriggerId(eventDetails.trigger?.id ?? null);\n  };\n\n  return (\n    <div>\n      <div className=\"flex gap-2 flex-wrap justify-center items-baseline\">\n        <p className=\"m-0 text-base leading-6 text-gray-900 text-balance\">\n          Discover{' '}\n          <PreviewCard.Trigger\n            className=\"text-blue-800 underline decoration-blue-800/60 decoration-1 underline-offset-2 outline-0 hover:decoration-blue-800 data-[popup-open]:decoration-blue-800 focus-visible:rounded-[2px] focus-visible:no-underline focus-visible:outline-2 focus-visible:outline-blue-800\"\n            handle={demoPreviewCard}\n            href=\"https://en.wikipedia.org/wiki/Typography\"\n            id=\"trigger-1\"\n            payload={cardContents.typography}\n          >\n            typography\n          </PreviewCard.Trigger>\n          ,{' '}\n          <PreviewCard.Trigger\n            className=\"text-blue-800 underline decoration-blue-800/60 decoration-1 underline-offset-2 outline-0 hover:decoration-blue-800 data-[popup-open]:decoration-blue-800 focus-visible:rounded-[2px] focus-visible:no-underline focus-visible:outline-2 focus-visible:outline-blue-800\"\n            handle={demoPreviewCard}\n            href=\"https://en.wikipedia.org/wiki/Industrial_design\"\n            id=\"trigger-2\"\n            payload={cardContents.design}\n          >\n            design\n          </PreviewCard.Trigger>\n          , or{' '}\n          <PreviewCard.Trigger\n            className=\"text-blue-800 underline decoration-blue-800/60 decoration-1 underline-offset-2 outline-0 hover:decoration-blue-800 data-[popup-open]:decoration-blue-800 focus-visible:rounded-[2px] focus-visible:no-underline focus-visible:outline-2 focus-visible:outline-blue-800\"\n            handle={demoPreviewCard}\n            href=\"https://en.wikipedia.org/wiki/Art\"\n            id=\"trigger-3\"\n            payload={cardContents.art}\n          >\n            art\n          </PreviewCard.Trigger>\n          .\n        </p>\n        <button\n          type=\"button\"\n          className=\"box-border flex items-center justify-center h-10 px-3.5 m-0 outline-0 border border-gray-200 rounded-md bg-gray-50 font-inherit text-base font-normal leading-6 text-gray-900 select-none hover:bg-gray-100 active:bg-gray-100 focus-visible:outline-2 focus-visible:outline-blue-800 focus-visible:-outline-offset-1\"\n          onClick={() => {\n            setTriggerId('trigger-2');\n            setOpen(true);\n          }}\n        >\n          Open programmatically\n        </button>\n      </div>\n\n      <PreviewCard.Root\n        handle={demoPreviewCard}\n        open={open}\n        onOpenChange={handleOpenChange}\n        triggerId={triggerId}\n      >\n        {({ payload }) => (\n          <PreviewCard.Portal>\n            <PreviewCard.Positioner\n              sideOffset={8}\n              className=\"h-[var(--positioner-height)] w-[var(--positioner-width)] max-w-[var(--available-width)]\"\n            >\n              <PreviewCard.Popup className=\"box-border w-[var(--popup-width,auto)] h-[var(--popup-height,auto)] rounded-lg bg-[canvas] origin-[var(--transform-origin)] transition-[scale,opacity] duration-150 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 data-[ending-style]:scale-90 data-[ending-style]:opacity-0 shadow-lg shadow-gray-200 outline-1 outline-gray-200 dark:shadow-none dark:outline-gray-300 dark:-outline-offset-1\">\n                <PreviewCard.Arrow className=\"flex data-[side=bottom]:top-[-8px] data-[side=left]:right-[-13px] data-[side=left]:rotate-90 data-[side=right]:left-[-13px] data-[side=right]:-rotate-90 data-[side=top]:bottom-[-8px] data-[side=top]:rotate-180\">\n                  <ArrowSvg />\n                </PreviewCard.Arrow>\n                {payload}\n              </PreviewCard.Popup>\n            </PreviewCard.Positioner>\n          </PreviewCard.Portal>\n        )}\n      </PreviewCard.Root>\n    </div>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className=\"fill-[canvas]\"\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className=\"fill-gray-200 dark:fill-none\"\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className=\"dark:fill-gray-300\"\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/preview-card/demos/detached-triggers-full/css-modules/index.module.css",
    "content": ".Positioner {\n  --easing: cubic-bezier(0.22, 1, 0.36, 1);\n  --animation-duration: 0.35s;\n\n  height: var(--positioner-height);\n  width: var(--positioner-width);\n  max-width: var(--available-width);\n\n  transition-property: top, left, right, bottom, transform;\n  transition-timing-function: var(--easing);\n  transition-duration: var(--animation-duration);\n}\n\n.Popup {\n  position: relative;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  border-radius: 0.5rem;\n  transform-origin: var(--transform-origin);\n\n  /* These are required to make the size animations work */\n  width: var(--popup-width, auto);\n  height: var(--popup-height, auto);\n\n  /* width and height are essential for the resize animation; opacity and transform handle the enter/exit animation */\n  transition-property: width, height, opacity, transform;\n  transition-timing-function: var(--easing);\n  transition-duration: var(--animation-duration);\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Arrow {\n  display: flex;\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n\n.Viewport {\n  /* Required to clip the overflowing content during the slide in/out animations */\n  position: relative;\n  overflow: clip;\n  width: 100%;\n  height: 100%;\n\n  & [data-previous],\n  & [data-current] {\n    /* This freezes the width of the content while transitioning.\n       The 'previous` container receives the width of the previous content, while the `next` container\n       receives the width of the new content.\n    */\n    width: var(--popup-width);\n    translate: 0;\n    opacity: 1;\n    transition:\n      translate var(--animation-duration) var(--easing),\n      opacity calc(var(--animation-duration) / 2) var(--easing);\n  }\n\n  &[data-activation-direction~='left'] [data-current][data-starting-style] {\n    translate: -30% 0;\n    opacity: 0;\n  }\n\n  &[data-activation-direction~='right'] [data-current][data-starting-style] {\n    translate: 30% 0;\n    opacity: 0;\n  }\n\n  &[data-activation-direction~='left'] [data-previous][data-ending-style] {\n    translate: 30% 0;\n    opacity: 0;\n  }\n\n  &[data-activation-direction~='right'] [data-previous][data-ending-style] {\n    translate: -30% 0;\n    opacity: 0;\n  }\n}\n\n.PopupContent {\n  width: min-content;\n  display: flex;\n  flex-direction: column;\n  gap: 0.5rem;\n  padding: 0.5rem;\n  box-sizing: border-box;\n}\n\n.Image {\n  display: block;\n  border-radius: 0.25rem;\n  max-width: none;\n}\n\n.Summary {\n  margin: 0;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  color: var(--color-gray-900);\n  text-wrap: pretty;\n}\n\n.Paragraph {\n  margin: 0;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  text-wrap: balance;\n}\n\n.Link {\n  outline: 0;\n  color: var(--color-blue);\n  text-decoration-line: underline;\n  text-decoration-thickness: 1px;\n  text-decoration-color: color-mix(in oklab, var(--color-blue), transparent 40%);\n  text-underline-offset: 2px;\n\n  @media (hover: hover) {\n    &:hover {\n      text-decoration-color: var(--color-blue);\n    }\n  }\n\n  &[data-popup-open] {\n    text-decoration-color: var(--color-blue);\n  }\n\n  &:focus-visible {\n    border-radius: 0.125rem;\n    outline: 2px solid var(--color-blue);\n    text-decoration-line: none;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/preview-card/demos/detached-triggers-full/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { PreviewCard } from '@base-ui/react/preview-card';\nimport styles from './index.module.css';\n\nconst demoPreviewCard = PreviewCard.createHandle<React.ReactElement>();\n\nconst cardContents = {\n  typography: (\n    <div className={styles.PopupContent}>\n      <img\n        width=\"224\"\n        height=\"150\"\n        className={styles.Image}\n        src=\"https://images.unsplash.com/photo-1619615391095-dfa29e1672ef?q=80&w=448&h=300\"\n        alt=\"Station Hofplein signage in Rotterdam, Netherlands\"\n      />\n      <p className={styles.Summary}>\n        <strong>Typography</strong> is the art and science of arranging type.\n      </p>\n    </div>\n  ),\n  design: (\n    <div className={styles.PopupContent}>\n      <img\n        width=\"250\"\n        height=\"249\"\n        className={styles.Image}\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/b/b7/Braun_ABW30_%28schwarz%29.jpg/250px-Braun_ABW30_%28schwarz%29.jpg\"\n        alt=\"Braun ABW30\"\n      />\n      <p className={styles.Summary}>\n        A <strong>design</strong> is the concept or proposal for an object, process, or system.\n      </p>\n    </div>\n  ),\n  art: (\n    <div className={styles.PopupContent}>\n      <img\n        width=\"250\"\n        height=\"290\"\n        className={styles.Image}\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/5/5f/MonaLisa_sfumato.jpeg/250px-MonaLisa_sfumato.jpeg\"\n        alt=\"Mona Lisa\"\n      />\n      <p className={styles.Summary}>\n        <strong>Art</strong> is a diverse range of cultural activity centered around works utilizing\n        creative or imaginative talents, which are expected to evoke a worthwhile experience,\n        generally through an expression of emotional power, conceptual ideas, technical proficiency,\n        or beauty.\n      </p>\n    </div>\n  ),\n};\n\nexport default function PreviewCardDetachedTriggersFullDemo() {\n  return (\n    <div>\n      <p className={styles.Paragraph}>\n        Discover{' '}\n        <PreviewCard.Trigger\n          className={styles.Link}\n          handle={demoPreviewCard}\n          href=\"https://en.wikipedia.org/wiki/Typography\"\n          payload={cardContents.typography}\n        >\n          typography\n        </PreviewCard.Trigger>\n        ,{' '}\n        <PreviewCard.Trigger\n          className={styles.Link}\n          handle={demoPreviewCard}\n          href=\"https://en.wikipedia.org/wiki/Design\"\n          payload={cardContents.design}\n        >\n          design\n        </PreviewCard.Trigger>\n        , or{' '}\n        <PreviewCard.Trigger\n          className={styles.Link}\n          handle={demoPreviewCard}\n          href=\"https://en.wikipedia.org/wiki/Art\"\n          payload={cardContents.art}\n        >\n          art\n        </PreviewCard.Trigger>\n        .\n      </p>\n\n      <PreviewCard.Root handle={demoPreviewCard}>\n        {({ payload }) => (\n          <PreviewCard.Portal>\n            <PreviewCard.Positioner sideOffset={8} className={styles.Positioner}>\n              <PreviewCard.Popup className={styles.Popup}>\n                <PreviewCard.Arrow className={styles.Arrow}>\n                  <ArrowSvg />\n                </PreviewCard.Arrow>\n                <PreviewCard.Viewport className={styles.Viewport}>{payload}</PreviewCard.Viewport>\n              </PreviewCard.Popup>\n            </PreviewCard.Positioner>\n          </PreviewCard.Portal>\n        )}\n      </PreviewCard.Root>\n    </div>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/preview-card/demos/detached-triggers-full/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoPreviewCardDetachedTriggersFull = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/preview-card/demos/detached-triggers-full/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { PreviewCard } from '@base-ui/react/preview-card';\n\nconst demoPreviewCard = PreviewCard.createHandle<React.ReactElement>();\n\nconst cardContents = {\n  typography: (\n    <div className=\"w-min box-border flex flex-col gap-2 p-2\">\n      <img\n        width=\"224\"\n        height=\"150\"\n        className=\"block rounded-xs max-w-none\"\n        src=\"https://images.unsplash.com/photo-1619615391095-dfa29e1672ef?q=80&w=448&h=300\"\n        alt=\"Station Hofplein signage in Rotterdam, Netherlands\"\n      />\n      <p className=\"m-0 text-sm leading-5 text-gray-900 text-pretty\">\n        <strong>Typography</strong> is the art and science of arranging type.\n      </p>\n    </div>\n  ),\n  design: (\n    <div className=\"w-min box-border flex flex-col gap-2 p-2\">\n      <img\n        width=\"250\"\n        height=\"249\"\n        className=\"block rounded-xs max-w-none\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/b/b7/Braun_ABW30_%28schwarz%29.jpg/250px-Braun_ABW30_%28schwarz%29.jpg\"\n        alt=\"Braun ABW30\"\n      />\n      <p className=\"m-0 text-sm leading-5 text-gray-900 text-pretty\">\n        A <strong>design</strong> is the concept or proposal for an object, process, or system.\n      </p>\n    </div>\n  ),\n  art: (\n    <div className=\"w-min box-border flex flex-col gap-2 p-2\">\n      <img\n        width=\"250\"\n        height=\"290\"\n        className=\"block rounded-xs max-w-none\"\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/5/5f/MonaLisa_sfumato.jpeg/250px-MonaLisa_sfumato.jpeg\"\n        alt=\"Mona Lisa\"\n      />\n      <p className=\"m-0 text-sm leading-5 text-gray-900 text-pretty\">\n        <strong>Art</strong> is a diverse range of cultural activity centered around works utilizing\n        creative or imaginative talents, which are expected to evoke a worthwhile experience,\n        generally through an expression of emotional power, conceptual ideas, technical proficiency,\n        or beauty.\n      </p>\n    </div>\n  ),\n};\n\nexport default function PreviewCardDetachedTriggersFullDemo() {\n  return (\n    <div>\n      <p className=\"m-0 text-base leading-6 text-gray-900 text-balance\">\n        Discover{' '}\n        <PreviewCard.Trigger\n          className=\"text-blue-800 underline decoration-blue-800/60 decoration-1 underline-offset-2 outline-0 hover:decoration-blue-800 data-[popup-open]:decoration-blue-800 focus-visible:rounded-[2px] focus-visible:no-underline focus-visible:outline-2 focus-visible:outline-blue-800\"\n          handle={demoPreviewCard}\n          href=\"https://en.wikipedia.org/wiki/Typography\"\n          payload={cardContents.typography}\n        >\n          typography\n        </PreviewCard.Trigger>\n        ,{' '}\n        <PreviewCard.Trigger\n          className=\"text-blue-800 underline decoration-blue-800/60 decoration-1 underline-offset-2 outline-0 hover:decoration-blue-800 data-[popup-open]:decoration-blue-800 focus-visible:rounded-[2px] focus-visible:no-underline focus-visible:outline-2 focus-visible:outline-blue-800\"\n          handle={demoPreviewCard}\n          href=\"https://en.wikipedia.org/wiki/Design\"\n          payload={cardContents.design}\n        >\n          design\n        </PreviewCard.Trigger>\n        , or{' '}\n        <PreviewCard.Trigger\n          className=\"text-blue-800 underline decoration-blue-800/60 decoration-1 underline-offset-2 outline-0 hover:decoration-blue-800 data-[popup-open]:decoration-blue-800 focus-visible:rounded-[2px] focus-visible:no-underline focus-visible:outline-2 focus-visible:outline-blue-800\"\n          handle={demoPreviewCard}\n          href=\"https://en.wikipedia.org/wiki/Art\"\n          payload={cardContents.art}\n        >\n          art\n        </PreviewCard.Trigger>\n        .\n      </p>\n\n      <PreviewCard.Root handle={demoPreviewCard}>\n        {({ payload }) => (\n          <PreviewCard.Portal>\n            <PreviewCard.Positioner\n              sideOffset={8}\n              className=\"h-[var(--positioner-height)] w-[var(--positioner-width)] max-w-[var(--available-width)] transition-[top,left,right,bottom,transform] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)]\"\n            >\n              <PreviewCard.Popup className=\"box-border relative w-[var(--popup-width,auto)] h-[var(--popup-height,auto)] rounded-lg bg-[canvas] origin-[var(--transform-origin)] transition-[width,height,opacity,scale] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)] data-[starting-style]:scale-90 data-[starting-style]:opacity-0 data-[ending-style]:scale-90 data-[ending-style]:opacity-0 shadow-lg shadow-gray-200 outline-1 outline-gray-200 dark:shadow-none dark:outline-gray-300 dark:-outline-offset-1\">\n                <PreviewCard.Arrow className=\"flex data-[side=bottom]:top-[-8px] data-[side=left]:right-[-13px] data-[side=left]:rotate-90 data-[side=right]:left-[-13px] data-[side=right]:-rotate-90 data-[side=top]:bottom-[-8px] data-[side=top]:rotate-180\">\n                  <ArrowSvg />\n                </PreviewCard.Arrow>\n\n                <PreviewCard.Viewport className=\"relative overflow-clip w-full h-full [&_[data-previous]]:w-[var(--popup-width)] [&_[data-previous]]:translate-x-0 [&_[data-previous]]:opacity-100 [&_[data-previous]]:transition-[translate,opacity] [&_[data-previous]]:duration-[350ms,175ms] [&_[data-previous]]:ease-[cubic-bezier(0.22,1,0.36,1)] [&_[data-current]]:w-[var(--popup-width)] [&_[data-current]]:translate-x-0 [&_[data-current]]:opacity-100 [&_[data-current]]:transition-[translate,opacity] [&_[data-current]]:duration-[350ms,175ms] [&_[data-current]]:ease-[cubic-bezier(0.22,1,0.36,1)] data-[activation-direction~='left']:[&_[data-current][data-starting-style]]:-translate-x-[30%] data-[activation-direction~='left']:[&_[data-current][data-starting-style]]:opacity-0 data-[activation-direction~='right']:[&_[data-current][data-starting-style]]:translate-x-[30%] data-[activation-direction~='right']:[&_[data-current][data-starting-style]]:opacity-0 data-[activation-direction~='left']:[&_[data-previous][data-ending-style]]:translate-x-[30%] data-[activation-direction~='left']:[&_[data-previous][data-ending-style]]:opacity-0 data-[activation-direction~='right']:[&_[data-previous][data-ending-style]]:-translate-x-[30%] data-[activation-direction~='right']:[&_[data-previous][data-ending-style]]:opacity-0\">\n                  {payload}\n                </PreviewCard.Viewport>\n              </PreviewCard.Popup>\n            </PreviewCard.Positioner>\n          </PreviewCard.Portal>\n        )}\n      </PreviewCard.Root>\n    </div>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className=\"fill-[canvas]\"\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className=\"fill-gray-200 dark:fill-none\"\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className=\"dark:fill-gray-300\"\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/preview-card/demos/detached-triggers-simple/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { PreviewCard } from '@base-ui/react/preview-card';\nimport styles from '../../index.module.css';\n\nconst demoPreviewCard = PreviewCard.createHandle();\n\nexport default function PreviewCardDetachedTriggersSimpleDemo() {\n  return (\n    <div>\n      <p className={styles.Paragraph}>\n        The principles of good{' '}\n        <PreviewCard.Trigger\n          className={styles.Link}\n          handle={demoPreviewCard}\n          href=\"https://en.wikipedia.org/wiki/Typography\"\n        >\n          typography\n        </PreviewCard.Trigger>{' '}\n        remain in the digital age.\n      </p>\n\n      <PreviewCard.Root handle={demoPreviewCard}>\n        <PreviewCard.Portal>\n          <PreviewCard.Positioner sideOffset={8}>\n            <PreviewCard.Popup className={styles.Popup}>\n              <PreviewCard.Arrow className={styles.Arrow}>\n                <ArrowSvg />\n              </PreviewCard.Arrow>\n              <div className={styles.PopupContent}>\n                <img\n                  width=\"224\"\n                  height=\"150\"\n                  className={styles.Image}\n                  src=\"https://images.unsplash.com/photo-1619615391095-dfa29e1672ef?q=80&w=448&h=300\"\n                  alt=\"Station Hofplein signage in Rotterdam, Netherlands\"\n                />\n                <p className={styles.Summary}>\n                  <strong>Typography</strong> is the art and science of arranging type to make\n                  written language clear, visually appealing, and effective in communication.\n                </p>\n              </div>\n            </PreviewCard.Popup>\n          </PreviewCard.Positioner>\n        </PreviewCard.Portal>\n      </PreviewCard.Root>\n    </div>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/preview-card/demos/detached-triggers-simple/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoPreviewCardDetachedTriggersSimple = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/preview-card/demos/detached-triggers-simple/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { PreviewCard } from '@base-ui/react/preview-card';\n\nconst demoPreviewCard = PreviewCard.createHandle();\n\nexport default function PreviewCardDetachedTriggersSimpleDemo() {\n  return (\n    <div>\n      <p className=\"m-0 text-base leading-6 text-gray-900 text-balance\">\n        The principles of good{' '}\n        <PreviewCard.Trigger\n          className=\"text-blue-800 underline decoration-blue-800/60 decoration-1 underline-offset-2 outline-0 hover:decoration-blue-800 data-[popup-open]:decoration-blue-800 focus-visible:rounded-[2px] focus-visible:no-underline focus-visible:outline-2 focus-visible:outline-blue-800\"\n          handle={demoPreviewCard}\n          href=\"https://en.wikipedia.org/wiki/Typography\"\n        >\n          typography\n        </PreviewCard.Trigger>{' '}\n        remain in the digital age.\n      </p>\n\n      <PreviewCard.Root handle={demoPreviewCard}>\n        <PreviewCard.Portal>\n          <PreviewCard.Positioner sideOffset={8}>\n            <PreviewCard.Popup className=\"box-border w-[var(--popup-width,auto)] h-[var(--popup-height,auto)] rounded-lg bg-[canvas] origin-[var(--transform-origin)] transition-[scale,opacity] duration-150 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 data-[ending-style]:scale-90 data-[ending-style]:opacity-0 shadow-lg shadow-gray-200 outline-1 outline-gray-200 dark:shadow-none dark:outline-gray-300 dark:-outline-offset-1\">\n              <PreviewCard.Arrow className=\"flex data-[side=bottom]:top-[-8px] data-[side=left]:right-[-13px] data-[side=left]:rotate-90 data-[side=right]:left-[-13px] data-[side=right]:-rotate-90 data-[side=top]:bottom-[-8px] data-[side=top]:rotate-180\">\n                <ArrowSvg />\n              </PreviewCard.Arrow>\n              <div className=\"w-min flex flex-col gap-2 p-2 box-border\">\n                <img\n                  width=\"224\"\n                  height=\"150\"\n                  className=\"block rounded-xs max-w-none\"\n                  src=\"https://images.unsplash.com/photo-1619615391095-dfa29e1672ef?q=80&w=448&h=300\"\n                  alt=\"Station Hofplein signage in Rotterdam, Netherlands\"\n                />\n                <p className=\"m-0 text-sm leading-5 text-gray-900 text-pretty\">\n                  <strong>Typography</strong> is the art and science of arranging type to make\n                  written language clear, visually appealing, and effective in communication.\n                </p>\n              </div>\n            </PreviewCard.Popup>\n          </PreviewCard.Positioner>\n        </PreviewCard.Portal>\n      </PreviewCard.Root>\n    </div>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className=\"fill-[canvas]\"\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className=\"fill-gray-200 dark:fill-none\"\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className=\"dark:fill-gray-300\"\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/preview-card/demos/hero/css-modules/index.tsx",
    "content": "import * as React from 'react';\nimport { PreviewCard } from '@base-ui/react/preview-card';\nimport styles from '../../index.module.css';\n\nexport default function ExamplePreviewCard() {\n  return (\n    <PreviewCard.Root>\n      <p className={styles.Paragraph}>\n        The principles of good{' '}\n        <PreviewCard.Trigger\n          className={styles.Link}\n          href=\"https://en.wikipedia.org/wiki/Typography\"\n        >\n          typography\n        </PreviewCard.Trigger>{' '}\n        remain in the digital age.\n      </p>\n\n      <PreviewCard.Portal>\n        <PreviewCard.Positioner sideOffset={8}>\n          <PreviewCard.Popup className={styles.Popup}>\n            <PreviewCard.Arrow className={styles.Arrow}>\n              <ArrowSvg />\n            </PreviewCard.Arrow>\n            <div className={styles.PopupContent}>\n              <img\n                width=\"224\"\n                height=\"150\"\n                className={styles.Image}\n                src=\"https://images.unsplash.com/photo-1619615391095-dfa29e1672ef?q=80&w=448&h=300\"\n                alt=\"Station Hofplein signage in Rotterdam, Netherlands\"\n              />\n              <p className={styles.Summary}>\n                <strong>Typography</strong> is the art and science of arranging type to make written\n                language clear, visually appealing, and effective in communication.\n              </p>\n            </div>\n          </PreviewCard.Popup>\n        </PreviewCard.Positioner>\n      </PreviewCard.Portal>\n    </PreviewCard.Root>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/preview-card/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoPreviewCardHero = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/preview-card/demos/hero/tailwind/index.tsx",
    "content": "import * as React from 'react';\nimport { PreviewCard } from '@base-ui/react/preview-card';\n\nexport default function ExamplePreviewCard() {\n  return (\n    <PreviewCard.Root>\n      <p className=\"m-0 text-base leading-6 text-gray-900 text-balance\">\n        The principles of good{' '}\n        <PreviewCard.Trigger\n          className=\"text-blue-800 underline decoration-blue-800/60 decoration-1 underline-offset-2 outline-0 hover:decoration-blue-800 data-[popup-open]:decoration-blue-800 focus-visible:rounded-[2px] focus-visible:no-underline focus-visible:outline-2 focus-visible:outline-blue-800\"\n          href=\"https://en.wikipedia.org/wiki/Typography\"\n        >\n          typography\n        </PreviewCard.Trigger>{' '}\n        remain in the digital age.\n      </p>\n\n      <PreviewCard.Portal>\n        <PreviewCard.Positioner sideOffset={8}>\n          <PreviewCard.Popup className=\"box-border w-[var(--popup-width,auto)] h-[var(--popup-height,auto)] rounded-lg bg-[canvas] origin-[var(--transform-origin)] transition-[scale,opacity] duration-150 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 data-[ending-style]:scale-90 data-[ending-style]:opacity-0 shadow-lg shadow-gray-200 outline-1 outline-gray-200 dark:shadow-none dark:outline-gray-300 dark:-outline-offset-1\">\n            <PreviewCard.Arrow className=\"flex data-[side=bottom]:top-[-8px] data-[side=left]:right-[-13px] data-[side=left]:rotate-90 data-[side=right]:left-[-13px] data-[side=right]:-rotate-90 data-[side=top]:bottom-[-8px] data-[side=top]:rotate-180\">\n              <ArrowSvg />\n            </PreviewCard.Arrow>\n            <div className=\"w-min flex flex-col gap-2 p-2 box-border\">\n              <img\n                width=\"224\"\n                height=\"150\"\n                className=\"block rounded-xs max-w-none\"\n                src=\"https://images.unsplash.com/photo-1619615391095-dfa29e1672ef?q=80&w=448&h=300\"\n                alt=\"Station Hofplein signage in Rotterdam, Netherlands\"\n              />\n              <p className=\"m-0 text-sm leading-5 text-gray-900 text-pretty\">\n                <strong>Typography</strong> is the art and science of arranging type to make written\n                language clear, visually appealing, and effective in communication.\n              </p>\n            </div>\n          </PreviewCard.Popup>\n        </PreviewCard.Positioner>\n      </PreviewCard.Portal>\n    </PreviewCard.Root>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className=\"fill-[canvas]\"\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className=\"fill-gray-200 dark:fill-none\"\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className=\"dark:fill-gray-300\"\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/preview-card/demos/index.module.css",
    "content": ".Positioner {\n  height: var(--positioner-height);\n  width: var(--positioner-width);\n  max-width: var(--available-width);\n}\n\n.Popup {\n  box-sizing: border-box;\n  width: var(--popup-width, auto);\n  height: var(--popup-height, auto);\n  border-radius: 0.5rem;\n  background-color: canvas;\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Arrow {\n  display: flex;\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n\n.PopupContent {\n  width: min-content;\n  display: flex;\n  flex-direction: column;\n  gap: 0.5rem;\n  padding: 0.5rem;\n  box-sizing: border-box;\n}\n\n.Image {\n  display: block;\n  border-radius: 0.25rem;\n  max-width: none;\n}\n\n.Summary {\n  margin: 0;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  color: var(--color-gray-900);\n  text-wrap: pretty;\n}\n\n.Container {\n  display: flex;\n  gap: 0.5rem;\n  flex-wrap: wrap;\n  justify-content: center;\n  align-items: baseline;\n}\n\n.Paragraph {\n  margin: 0;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  text-wrap: balance;\n}\n\n.Link {\n  outline: 0;\n  color: var(--color-blue);\n  text-decoration-line: underline;\n  text-decoration-thickness: 1px;\n  text-decoration-color: color-mix(in oklab, var(--color-blue), transparent 40%);\n  text-underline-offset: 2px;\n\n  @media (hover: hover) {\n    &:hover {\n      text-decoration-color: var(--color-blue);\n    }\n  }\n\n  &[data-popup-open] {\n    text-decoration-color: var(--color-blue);\n  }\n\n  &:focus-visible {\n    border-radius: 0.125rem;\n    outline: 2px solid var(--color-blue);\n    text-decoration-line: none;\n  }\n}\n\n.LinkGroup {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 0.25rem;\n  align-items: baseline;\n}\n\n.Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/preview-card/page.mdx",
    "content": "# Preview Card\n\n<Subtitle>\n  A popup that appears when a link is hovered, showing a preview for sighted users.\n</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"A high-quality, unstyled React preview card component that appears when a link is hovered, showing a preview for sighted users.\"\n/>\n\nimport { DemoPreviewCardHero } from './demos/hero';\n\n<DemoPreviewCardHero />\n<link\n  as=\"image\"\n  rel=\"preload\"\n  href=\"https://images.unsplash.com/photo-1619615391095-dfa29e1672ef?q=80&w=448&h=300\"\n/>\n\n## Usage guidelines\n\n- **Popup content should reflect the link destination**: Avoid placing unique or essential information in the popup unless it is also available on the linked page, so all users can access the same information. Preview cards are a visual enhancement for sighted mouse and keyboard users and are not accessible to touch or screen reader users.\n\n## Anatomy\n\nImport the component and assemble its parts:\n\n```jsx title=\"Anatomy\"\nimport { PreviewCard } from '@base-ui/react/preview-card';\n\n<PreviewCard.Root>\n  <PreviewCard.Trigger />\n  <PreviewCard.Portal>\n    <PreviewCard.Backdrop />\n    <PreviewCard.Positioner>\n      <PreviewCard.Popup>\n        <PreviewCard.Arrow />\n      </PreviewCard.Popup>\n    </PreviewCard.Positioner>\n  </PreviewCard.Portal>\n</PreviewCard.Root>;\n```\n\n## Examples\n\n### Detached triggers\n\nA preview card can be controlled by a trigger located either inside or outside the `<PreviewCard.Root>` component.\nFor simple, one-off interactions, place the `<PreviewCard.Trigger>` inside `<PreviewCard.Root>`, as shown in the example at the top of this page.\n\nHowever, if defining the preview card's content next to its trigger is not practical, you can use a detached trigger.\nThis involves placing the `<PreviewCard.Trigger>` outside of `<PreviewCard.Root>` and linking them with a `handle` created by the `PreviewCard.createHandle()` function.\n\n```jsx title=\"Detached triggers\" {3,5} \"handle={demoPreviewCard}\"\nconst demoPreviewCard = PreviewCard.createHandle();\n\n<PreviewCard.Trigger handle={demoPreviewCard} href=\"#\">\n  Link\n</PreviewCard.Trigger>\n\n<PreviewCard.Root handle={demoPreviewCard}>\n  ...\n</PreviewCard.Root>\n```\n\nimport { DemoPreviewCardDetachedTriggersSimple } from './demos/detached-triggers-simple';\n\n<DemoPreviewCardDetachedTriggersSimple />\n\n### Multiple triggers\n\nA single preview card can be opened by multiple trigger elements.\nYou can achieve this by using the same `handle` for several detached triggers, or by placing multiple `<PreviewCard.Trigger>` components inside a single `<PreviewCard.Root>`.\n\n```jsx title=\"Multiple triggers within the Root part\"\n<PreviewCard.Root>\n  <PreviewCard.Trigger href=\"#\">Trigger 1</PreviewCard.Trigger>\n  <PreviewCard.Trigger href=\"#\">Trigger 2</PreviewCard.Trigger>\n  ...\n</PreviewCard.Root>\n```\n\n```jsx title=\"Multiple detached triggers\"\nconst demoPreviewCard = PreviewCard.createHandle();\n\n<PreviewCard.Trigger handle={demoPreviewCard} href=\"#\">\n  Trigger 1\n</PreviewCard.Trigger>\n\n<PreviewCard.Trigger handle={demoPreviewCard} href=\"#\">\n  Trigger 2\n</PreviewCard.Trigger>\n\n<PreviewCard.Root handle={demoPreviewCard}>\n  ...\n</PreviewCard.Root>\n```\n\nThe preview card can render different content depending on which trigger opened it.\nThis is achieved by passing a `payload` to the `<PreviewCard.Trigger>` and using the function-as-a-child pattern in `<PreviewCard.Root>`.\n\nThe payload can be strongly typed by providing a type argument to the `createHandle()` function:\n\n```jsx title=\"Detached triggers with payload\" {1,3,7} \"payload\"\nconst demoPreviewCard = PreviewCard.createHandle<{ title: string }>();\n\n<PreviewCard.Trigger handle={demoPreviewCard} payload={{ title: 'Trigger 1' }} href=\"#\">\n  Trigger 1\n</PreviewCard.Trigger>\n\n<PreviewCard.Trigger handle={demoPreviewCard} payload={{ title: 'Trigger 2' }} href=\"#\">\n  Trigger 2\n</PreviewCard.Trigger>\n\n<PreviewCard.Root handle={demoPreviewCard}>\n  {({ payload }) => (\n    <PreviewCard.Portal>\n      <PreviewCard.Positioner sideOffset={8}>\n        <PreviewCard.Popup>\n          {payload !== undefined && (\n            <span>\n              Preview card opened by {payload.title}\n            </span>\n          )}\n        </PreviewCard.Popup>\n      </PreviewCard.Positioner>\n    </PreviewCard.Portal>\n  )}\n</PreviewCard.Root>\n```\n\n### Controlled mode with multiple triggers\n\nYou can control the preview card's open state externally using the `open` and `onOpenChange` props on `<PreviewCard.Root>`.\nThis allows you to manage the preview card's visibility based on your application's state.\nWhen using multiple triggers, you have to manage which trigger is active with the `triggerId` prop on `<PreviewCard.Root>` and the `id` prop on each `<PreviewCard.Trigger>`.\n\nNote that there is no separate `onTriggerIdChange` prop.\nInstead, the `onOpenChange` callback receives an additional argument, `eventDetails`, which contains the trigger element that initiated the state change.\n\nimport { DemoPreviewCardDetachedTriggersControlled } from './demos/detached-triggers-controlled';\n\n<DemoPreviewCardDetachedTriggersControlled />\n\n### Animating the Preview Card\n\nYou can animate a preview card as it moves between different trigger elements.\nThis includes animating its position, size, and content.\n\n#### Position and Size\n\nTo animate the preview card's position, apply CSS transitions to the `left`, `right`, `top`, and `bottom` properties of the **Positioner** part.\nTo animate its size, transition the `width` and `height` of the **Popup** part.\n\n#### Content\n\nThe preview card also supports content transitions.\nThis is useful when different triggers display different content within the same preview card.\n\nTo enable content animations, wrap the content in the `<PreviewCard.Viewport>` part.\nThis part provides features to create direction-aware animations.\nIt renders a `div` with a `data-activation-direction` attribute (`left`, `right`, `up`, or `down`) that indicates the new trigger's position relative to the previous one.\n\nInside the `<PreviewCard.Viewport>`, the content is further wrapped in `div`s with data attributes to help with styling:\n\n- `data-current`: The currently visible content when no transitions are present or the incoming content.\n- `data-previous`: The outgoing content during a transition.\n\nYou can use these attributes to style the enter and exit animations.\n\nimport { DemoPreviewCardDetachedTriggersFull } from './demos/detached-triggers-full';\n\n<DemoPreviewCardDetachedTriggersFull />\n\n## API reference\n\n<Reference\n  component=\"PreviewCard\"\n  parts=\"Root, Trigger, Portal, Backdrop, Positioner, Popup, Viewport, Arrow\"\n/>\n\nexport const metadata = {\n  keywords: [\n    'React Preview Card',\n    'Preview Card Component',\n    'Link Hover Preview',\n    'Hover Card React',\n    'Hover Card',\n    'Link Tooltip',\n    'URL Preview',\n    'Link Preview Popup',\n    'Content Preview',\n    'Accessible Preview Popup',\n    'Detached Trigger Preview Card',\n    'Multiple Preview Card Triggers',\n    'Animated Preview Card',\n    'Headless React Components',\n    'Link Preview UI',\n    'Base UI',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/progress/demos/hero/css-modules/index.module.css",
    "content": ".Progress {\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n  grid-gap: 0.25rem;\n  grid-row-gap: 0.5rem;\n  width: 12rem;\n}\n\n.Label {\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 400;\n  color: var(--color-gray-900);\n}\n\n.Value {\n  grid-column-start: 2;\n  margin: 0;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  color: var(--color-gray-900);\n  text-align: right;\n}\n\n.Track {\n  grid-column: 1 / 3;\n  overflow: hidden;\n  background-color: var(--color-gray-200);\n  box-shadow: inset 0 0 0 1px var(--color-gray-200);\n  height: 0.25rem;\n  border-radius: 0.25rem;\n}\n\n.Indicator {\n  display: block;\n  background-color: var(--color-gray-500);\n  transition: width 500ms;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/progress/demos/hero/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Progress } from '@base-ui/react/progress';\nimport styles from './index.module.css';\n\nexport default function ExampleProgress() {\n  const [value, setValue] = React.useState(20);\n\n  // Simulate changes\n  React.useEffect(() => {\n    const interval = setInterval(() => {\n      setValue((current) => Math.min(100, Math.round(current + Math.random() * 25)));\n    }, 1000);\n    return () => clearInterval(interval);\n  }, []);\n\n  return (\n    <Progress.Root className={styles.Progress} value={value}>\n      <Progress.Label className={styles.Label}>Export data</Progress.Label>\n      <Progress.Value className={styles.Value} />\n      <Progress.Track className={styles.Track}>\n        <Progress.Indicator className={styles.Indicator} />\n      </Progress.Track>\n    </Progress.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/progress/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoProgressHero = createDemoWithVariants(import.meta.url, { CssModules, Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/progress/demos/hero/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Progress } from '@base-ui/react/progress';\n\nexport default function ExampleProgress() {\n  const [value, setValue] = React.useState(20);\n\n  // Simulate changes\n  React.useEffect(() => {\n    const interval = setInterval(() => {\n      setValue((current) => Math.min(100, Math.round(current + Math.random() * 25)));\n    }, 1000);\n    return () => clearInterval(interval);\n  }, []);\n\n  return (\n    <Progress.Root className=\"grid w-48 grid-cols-2 gap-y-2\" value={value}>\n      <Progress.Label className=\"text-sm font-normal text-gray-900\">Export data</Progress.Label>\n      <Progress.Value className=\"col-start-2 text-right text-sm text-gray-900\" />\n      <Progress.Track className=\"col-span-full h-1 overflow-hidden rounded-sm bg-gray-200 shadow-[inset_0_0_0_1px] shadow-gray-200\">\n        <Progress.Indicator className=\"block bg-gray-500 transition-all duration-500\" />\n      </Progress.Track>\n    </Progress.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/progress/page.mdx",
    "content": "# Progress\n\n<Subtitle>Displays the status of a task that takes a long time.</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"A high-quality, unstyled React progress bar component that displays the status of a task that takes a long time.\"\n/>\n\nimport { DemoProgressHero } from './demos/hero';\n\n<DemoProgressHero />\n\n## Anatomy\n\nImport the component and assemble its parts:\n\n```jsx title=\"Anatomy\"\nimport { Progress } from '@base-ui/react/progress';\n\n<Progress.Root>\n  <Progress.Label />\n  <Progress.Track>\n    <Progress.Indicator />\n  </Progress.Track>\n  <Progress.Value />\n</Progress.Root>;\n```\n\n## API reference\n\n<Reference component=\"Progress\" parts=\"Root, Track, Indicator, Value, Label\" />\n\nexport const metadata = {\n  keywords: [\n    'React Progress Bar',\n    'Progress Component',\n    'Progress Indicator',\n    'Loader',\n    'Loading Bar',\n    'Upload Progress',\n    'Download Progress',\n    'Task Status Indicator',\n    'Determinate Progress',\n    'Indeterminate Progress',\n    'Accessible Progress',\n    'Headless React Components',\n    'Loading Indicator',\n    'Base UI',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/radio/demos/hero/css-modules/index.module.css",
    "content": ".RadioGroup {\n  display: flex;\n  flex-direction: column;\n  align-items: start;\n  gap: 0.25rem;\n  color: var(--color-gray-900);\n}\n\n.Caption {\n  font-weight: 700;\n}\n\n.Item {\n  display: flex;\n  align-items: center;\n  gap: 0.5rem;\n  font-weight: 400;\n}\n\n.Radio {\n  box-sizing: border-box;\n  display: flex;\n  width: 1.25rem;\n  height: 1.25rem;\n  align-items: center;\n  justify-content: center;\n  border-radius: 100%;\n  outline: 0;\n  padding: 0;\n  margin: 0;\n  border: none;\n\n  &[data-unchecked] {\n    border: 1px solid var(--color-gray-300);\n    background-color: transparent;\n  }\n\n  &[data-checked] {\n    background-color: var(--color-gray-900);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: 2px;\n  }\n}\n\n.Indicator {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n\n  &[data-unchecked] {\n    display: none;\n  }\n\n  &::before {\n    content: '';\n    border-radius: 100%;\n    width: 0.5rem;\n    height: 0.5rem;\n    background-color: var(--color-gray-50);\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/radio/demos/hero/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Radio } from '@base-ui/react/radio';\nimport { RadioGroup } from '@base-ui/react/radio-group';\nimport styles from './index.module.css';\n\nexport default function ExampleRadioGroup() {\n  const id = React.useId();\n  return (\n    <RadioGroup aria-labelledby={id} defaultValue=\"fuji-apple\" className={styles.RadioGroup}>\n      <div className={styles.Caption} id={id}>\n        Best apple\n      </div>\n\n      <label className={styles.Item}>\n        <Radio.Root value=\"fuji-apple\" className={styles.Radio}>\n          <Radio.Indicator className={styles.Indicator} />\n        </Radio.Root>\n        Fuji\n      </label>\n\n      <label className={styles.Item}>\n        <Radio.Root value=\"gala-apple\" className={styles.Radio}>\n          <Radio.Indicator className={styles.Indicator} />\n        </Radio.Root>\n        Gala\n      </label>\n\n      <label className={styles.Item}>\n        <Radio.Root value=\"granny-smith-apple\" className={styles.Radio}>\n          <Radio.Indicator className={styles.Indicator} />\n        </Radio.Root>\n        Granny Smith\n      </label>\n    </RadioGroup>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/radio/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoRadioHero = createDemoWithVariants(import.meta.url, { CssModules, Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/radio/demos/hero/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Radio } from '@base-ui/react/radio';\nimport { RadioGroup } from '@base-ui/react/radio-group';\n\nexport default function ExampleRadioGroup() {\n  const id = React.useId();\n  return (\n    <RadioGroup\n      aria-labelledby={id}\n      defaultValue=\"fuji-apple\"\n      className=\"flex flex-col items-start gap-1 text-gray-900\"\n    >\n      <div className=\"font-bold\" id={id}>\n        Best apple\n      </div>\n\n      <label className=\"flex items-center gap-2 font-normal\">\n        <Radio.Root\n          value=\"fuji-apple\"\n          className=\"flex size-5 items-center justify-center rounded-full focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-800 data-[checked]:bg-gray-900 data-[unchecked]:border data-[unchecked]:border-gray-300\"\n        >\n          <Radio.Indicator className=\"flex before:size-2 before:rounded-full before:bg-gray-50 data-[unchecked]:hidden\" />\n        </Radio.Root>\n        Fuji\n      </label>\n\n      <label className=\"flex items-center gap-2 font-normal\">\n        <Radio.Root\n          value=\"gala-apple\"\n          className=\"flex size-5 items-center justify-center rounded-full focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-800 data-[checked]:bg-gray-900 data-[unchecked]:border data-[unchecked]:border-gray-300\"\n        >\n          <Radio.Indicator className=\"flex before:size-2 before:rounded-full before:bg-gray-50 data-[unchecked]:hidden\" />\n        </Radio.Root>\n        Gala\n      </label>\n\n      <label className=\"flex items-center gap-2 font-normal\">\n        <Radio.Root\n          value=\"granny-smith-apple\"\n          className=\"flex size-5 items-center justify-center rounded-full focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-800 data-[checked]:bg-gray-900 data-[unchecked]:border data-[unchecked]:border-gray-300\"\n        >\n          <Radio.Indicator className=\"flex before:size-2 before:rounded-full before:bg-gray-50 data-[unchecked]:hidden\" />\n        </Radio.Root>\n        Granny Smith\n      </label>\n    </RadioGroup>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/radio/page.mdx",
    "content": "# Radio\n\n<Subtitle>An easily stylable radio button component.</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"A high-quality, unstyled React radio button component that is easy to style.\"\n/>\n\nimport { DemoRadioHero } from './demos/hero';\n\n<DemoRadioHero />\n\n## Usage guidelines\n\n- **Form controls must have an accessible name**: It can be created using `<label>` elements, or the `Field` and `Fieldset` components. See [Labeling a radio group](#labeling-a-radio-group) and the [forms guide](/react/handbook/forms).\n\n## Anatomy\n\nRadio is always placed within Radio Group. Import the components and place them together:\n\n```jsx title=\"Anatomy\"\nimport { Radio } from '@base-ui/react/radio';\nimport { RadioGroup } from '@base-ui/react/radio-group';\n\n<RadioGroup>\n  <Radio.Root>\n    <Radio.Indicator />\n  </Radio.Root>\n</RadioGroup>;\n```\n\n## Examples\n\n### Labeling a radio group\n\nLabel the group with `aria-labelledby` and a sibling label element:\n\n```tsx title=\"Using aria-labelledby to label a radio group\"\n<div id=\"storage-type-label\">Storage type</div>\n<RadioGroup aria-labelledby=\"storage-type-label\">{/* ... */}</RadioGroup>\n```\n\nAn enclosing `<label>` is the simplest labeling pattern for each radio:\n\n```tsx title=\"Using an enclosing label to label a radio button\" {1,4}\n<label>\n  <Radio.Root value=\"ssd\" />\n  SSD\n</label>\n```\n\n### Rendering as a native button\n\nBy default, `<Radio.Root>` renders a `<span>` element to support enclosing labels. Prefer rendering each radio as a native button when using sibling labels (`htmlFor`/`id`).\n\n```tsx title=\"Sibling label pattern with a native button\" \"nativeButton\" \"render\"\n<div id=\"storage-type\">Storage type</div>\n<RadioGroup defaultValue=\"ssd\" aria-labelledby=\"storage-type\">\n  <div>\n    <label htmlFor=\"storage-type-ssd\">SSD</label>\n    <Radio.Root value=\"ssd\" id=\"storage-type-ssd\" nativeButton render={<button />}>\n      <Radio.Indicator />\n    </Radio.Root>\n  </div>\n</RadioGroup>\n```\n\nNative buttons with wrapping labels are supported by using the `render` callback to avoid invalid HTML, so the hidden input is placed outside the label:\n\n```tsx title=\"Render callback\" {6-11}\n<div id=\"storage-type\">Storage type</div>\n<RadioGroup defaultValue=\"ssd\" aria-labelledby=\"storage-type\">\n  <Radio.Root\n    value=\"ssd\"\n    nativeButton\n    render={(buttonProps) => (\n      <label>\n        <button {...buttonProps} />\n        SSD\n      </label>\n    )}\n  />\n</RadioGroup>\n```\n\n### Form integration\n\nUse [Field](/react/components/field) and [Fieldset](/react/components/fieldset) for group labeling and form integration:\n\n```tsx title=\"Using Radio Group in a form\" {2}\n<Form>\n  <Field.Root name=\"storageType\">\n    <Fieldset.Root render={<RadioGroup />}>\n      <Fieldset.Legend>Storage type</Fieldset.Legend>\n      <Field.Item>\n        <Field.Label>\n          <Radio.Root value=\"ssd\" />\n          SSD\n        </Field.Label>\n      </Field.Item>\n      <Field.Item>\n        <Field.Label>\n          <Radio.Root value=\"hdd\" />\n          HDD\n        </Field.Label>\n      </Field.Item>\n    </Fieldset.Root>\n  </Field.Root>\n</Form>\n```\n\n## API reference\n\n### RadioGroup\n\nProvides a shared state to a series of radio buttons. Renders a `<div>` element.\n\n<Reference component=\"RadioGroup\" />\n<Reference component=\"Radio\" parts=\"Root, Indicator\" />\n\nexport const metadata = {\n  keywords: [\n    'React Radio Button',\n    'Radio Group Component',\n    'Radio Button',\n    'Radio Input',\n    'Single Choice',\n    'Exclusive Choice',\n    'Mutually Exclusive',\n    'Option Selector',\n    'Option Button',\n    'Form Radio Control',\n    'Accessible Radio Group',\n    'Customizable Radio',\n    'Headless React Components',\n    'Base UI',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/scroll-area/demos/both/css-modules/index.module.css",
    "content": ".ScrollArea {\n  box-sizing: border-box;\n  width: 20rem;\n  height: 20rem;\n  max-width: calc(100vw - 8rem);\n}\n\n.Viewport {\n  height: 100%;\n  border-radius: 0.5rem;\n  outline: 1px solid var(--color-gray-200);\n  outline-offset: -1px;\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n  }\n}\n\n.Content {\n  padding: 1.25rem;\n}\n\n.Grid {\n  display: grid;\n  grid-template-columns: repeat(10, 6.25rem);\n  grid-template-rows: repeat(10, 6.25rem);\n  gap: 0.75rem;\n  margin: 0;\n  padding: 0;\n  list-style: none;\n}\n\n.Item {\n  border-radius: 0.5rem;\n  background-color: var(--color-gray-100);\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  font-size: 0.875rem;\n  font-weight: 700;\n  color: var(--color-gray-600);\n}\n\n.Scrollbar {\n  display: flex;\n  position: relative;\n  background-color: var(--color-gray-200);\n  border-radius: 0.375rem;\n  margin: 0.5rem;\n  opacity: 0;\n  transition: opacity 150ms;\n  pointer-events: none;\n\n  &::before {\n    content: '';\n    position: absolute;\n  }\n\n  &[data-scrolling] {\n    transition-duration: 0ms;\n  }\n\n  &[data-hovering],\n  &[data-scrolling] {\n    opacity: 1;\n    pointer-events: auto;\n  }\n\n  &[data-orientation='vertical'] {\n    width: 0.25rem;\n    margin: 0.5rem;\n\n    &::before {\n      width: 1.25rem;\n      height: 100%;\n      left: 50%;\n      transform: translateX(-50%);\n    }\n  }\n\n  &[data-orientation='horizontal'] {\n    height: 0.25rem;\n    margin: 0.5rem;\n\n    &::before {\n      width: 100%;\n      height: 1.25rem;\n      left: 0;\n      right: 0;\n      bottom: -0.5rem;\n    }\n  }\n}\n\n.Thumb {\n  width: 100%;\n  border-radius: inherit;\n  background-color: var(--color-gray-500);\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/scroll-area/demos/both/css-modules/index.tsx",
    "content": "import { ScrollArea } from '@base-ui/react/scroll-area';\nimport styles from './index.module.css';\n\nexport default function ExampleScrollAreaBoth() {\n  return (\n    <ScrollArea.Root className={styles.ScrollArea}>\n      <ScrollArea.Viewport className={styles.Viewport}>\n        <ScrollArea.Content className={styles.Content}>\n          <ul className={styles.Grid}>\n            {Array.from({ length: 100 }, (_, i) => (\n              <li key={i} className={styles.Item}>\n                {i + 1}\n              </li>\n            ))}\n          </ul>\n        </ScrollArea.Content>\n      </ScrollArea.Viewport>\n      <ScrollArea.Scrollbar className={styles.Scrollbar}>\n        <ScrollArea.Thumb className={styles.Thumb} />\n      </ScrollArea.Scrollbar>\n      <ScrollArea.Scrollbar className={styles.Scrollbar} orientation=\"horizontal\">\n        <ScrollArea.Thumb className={styles.Thumb} />\n      </ScrollArea.Scrollbar>\n      <ScrollArea.Corner />\n    </ScrollArea.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/scroll-area/demos/both/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoScrollAreaBoth = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/scroll-area/demos/both/tailwind/index.tsx",
    "content": "import { ScrollArea } from '@base-ui/react/scroll-area';\n\nexport default function ExampleScrollAreaBoth() {\n  return (\n    <ScrollArea.Root className=\"h-80 w-80 max-w-[calc(100vw-8rem)]\">\n      <ScrollArea.Viewport className=\"h-full rounded-lg border border-gray-200 focus-visible:outline focus-visible:outline-blue-800 focus-visible:outline-offset-2\">\n        <ScrollArea.Content className=\"p-5\">\n          <ul className=\"m-0 grid list-none grid-cols-[repeat(10,6.25rem)] grid-rows-[repeat(10,6.25rem)] gap-3 p-0\">\n            {Array.from({ length: 100 }, (_, i) => (\n              <li\n                key={i}\n                className=\"flex items-center justify-center rounded-lg bg-gray-100 text-sm font-bold text-gray-600\"\n              >\n                {i + 1}\n              </li>\n            ))}\n          </ul>\n        </ScrollArea.Content>\n      </ScrollArea.Viewport>\n      <ScrollArea.Scrollbar className=\"relative flex rounded-sm bg-gray-200 opacity-0 transition-opacity pointer-events-none before:absolute before:content-[''] data-[orientation=vertical]:m-2 data-[orientation=vertical]:w-1 data-[orientation=vertical]:before:h-full data-[orientation=vertical]:before:w-5 data-[orientation=vertical]:before:left-1/2 data-[orientation=vertical]:before:-translate-x-1/2 data-[orientation=horizontal]:m-2 data-[orientation=horizontal]:h-1 data-[orientation=horizontal]:before:h-5 data-[orientation=horizontal]:before:w-full data-[orientation=horizontal]:before:left-0 data-[orientation=horizontal]:before:right-0 data-[orientation=horizontal]:before:-bottom-2 data-[hovering]:pointer-events-auto data-[hovering]:opacity-100 data-[hovering]:delay-0 data-[scrolling]:pointer-events-auto data-[scrolling]:opacity-100 data-[scrolling]:duration-0\">\n        <ScrollArea.Thumb className=\"w-full rounded-sm bg-gray-500\" />\n      </ScrollArea.Scrollbar>\n      <ScrollArea.Scrollbar\n        className=\"relative flex rounded-sm bg-gray-200 opacity-0 transition-opacity pointer-events-none before:absolute before:content-[''] data-[orientation=vertical]:m-2 data-[orientation=vertical]:w-1 data-[orientation=vertical]:before:h-full data-[orientation=vertical]:before:w-5 data-[orientation=vertical]:before:left-1/2 data-[orientation=vertical]:before:-translate-x-1/2 data-[orientation=horizontal]:m-2 data-[orientation=horizontal]:h-1 data-[orientation=horizontal]:before:h-5 data-[orientation=horizontal]:before:w-full data-[orientation=horizontal]:before:left-0 data-[orientation=horizontal]:before:right-0 data-[orientation=horizontal]:before:-bottom-2 data-[hovering]:pointer-events-auto data-[hovering]:opacity-100 data-[hovering]:delay-0 data-[scrolling]:pointer-events-auto data-[scrolling]:opacity-100 data-[scrolling]:duration-0\"\n        orientation=\"horizontal\"\n      >\n        <ScrollArea.Thumb className=\"w-full rounded-sm bg-gray-500\" />\n      </ScrollArea.Scrollbar>\n      <ScrollArea.Corner />\n    </ScrollArea.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/scroll-area/demos/hero/css-modules/index.module.css",
    "content": ".ScrollArea {\n  box-sizing: border-box;\n  width: 24rem;\n  height: 8.5rem;\n  max-width: calc(100vw - 8rem);\n}\n\n.Viewport {\n  height: 100%;\n  border-radius: 0.375rem;\n  outline: 1px solid var(--color-gray-200);\n  outline-offset: -1px;\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n  }\n}\n\n.Content {\n  display: flex;\n  flex-direction: column;\n  gap: 1rem;\n  padding-block: 0.75rem;\n  padding-left: 1rem;\n  padding-right: 1.5rem;\n}\n\n.Paragraph {\n  margin: 0;\n  font-size: 0.875rem;\n  line-height: 1.375rem;\n  color: var(--color-gray-900);\n}\n\n.Scrollbar {\n  display: flex;\n  justify-content: center;\n  background-color: var(--color-gray-200);\n  width: 0.25rem;\n  border-radius: 0.375rem;\n  margin: 0.5rem;\n  opacity: 0;\n  transition: opacity 150ms;\n  pointer-events: none;\n\n  &[data-scrolling] {\n    transition-duration: 0ms;\n  }\n\n  &[data-hovering],\n  &[data-scrolling] {\n    opacity: 1;\n    pointer-events: auto;\n  }\n\n  &::before {\n    content: '';\n    position: absolute;\n    width: 1.25rem;\n    height: 100%;\n  }\n}\n\n.Thumb {\n  width: 100%;\n  border-radius: inherit;\n  background-color: var(--color-gray-500);\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/scroll-area/demos/hero/css-modules/index.tsx",
    "content": "import { ScrollArea } from '@base-ui/react/scroll-area';\nimport styles from './index.module.css';\n\nexport default function ExampleScrollArea() {\n  return (\n    <ScrollArea.Root className={styles.ScrollArea}>\n      <ScrollArea.Viewport className={styles.Viewport}>\n        <ScrollArea.Content className={styles.Content}>\n          <p className={styles.Paragraph}>\n            Vernacular architecture is building done outside any academic tradition, and without\n            professional guidance. It is not a particular architectural movement or style, but\n            rather a broad category, encompassing a wide range and variety of building types, with\n            differing methods of construction, from around the world, both historical and extant and\n            classical and modern. Vernacular architecture constitutes 95% of the world's built\n            environment, as estimated in 1995 by Amos Rapoport, as measured against the small\n            percentage of new buildings every year designed by architects and built by engineers.\n          </p>\n          <p className={styles.Paragraph}>\n            This type of architecture usually serves immediate, local needs, is constrained by the\n            materials available in its particular region and reflects local traditions and cultural\n            practices. The study of vernacular architecture does not examine formally schooled\n            architects, but instead that of the design skills and tradition of local builders, who\n            were rarely given any attribution for the work. More recently, vernacular architecture\n            has been examined by designers and the building industry in an effort to be more energy\n            conscious with contemporary design and construction—part of a broader interest in\n            sustainable design.\n          </p>\n        </ScrollArea.Content>\n      </ScrollArea.Viewport>\n      <ScrollArea.Scrollbar className={styles.Scrollbar}>\n        <ScrollArea.Thumb className={styles.Thumb} />\n      </ScrollArea.Scrollbar>\n    </ScrollArea.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/scroll-area/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoScrollAreaHero = createDemoWithVariants(import.meta.url, { CssModules, Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/scroll-area/demos/hero/tailwind/index.tsx",
    "content": "import { ScrollArea } from '@base-ui/react/scroll-area';\n\nexport default function ExampleScrollArea() {\n  return (\n    <ScrollArea.Root className=\"h-[8.5rem] w-96 max-w-[calc(100vw-8rem)]\">\n      <ScrollArea.Viewport className=\"h-full rounded-md outline-1 -outline-offset-1 outline-gray-200 focus-visible:outline-2 focus-visible:outline-blue-800\">\n        <div className=\"flex flex-col gap-4 py-3 pr-6 pl-4 text-sm leading-[1.375rem] text-gray-900\">\n          <p>\n            Vernacular architecture is building done outside any academic tradition, and without\n            professional guidance. It is not a particular architectural movement or style, but\n            rather a broad category, encompassing a wide range and variety of building types, with\n            differing methods of construction, from around the world, both historical and extant and\n            classical and modern. Vernacular architecture constitutes 95% of the world's built\n            environment, as estimated in 1995 by Amos Rapoport, as measured against the small\n            percentage of new buildings every year designed by architects and built by engineers.\n          </p>\n          <p>\n            This type of architecture usually serves immediate, local needs, is constrained by the\n            materials available in its particular region and reflects local traditions and cultural\n            practices. The study of vernacular architecture does not examine formally schooled\n            architects, but instead that of the design skills and tradition of local builders, who\n            were rarely given any attribution for the work. More recently, vernacular architecture\n            has been examined by designers and the building industry in an effort to be more energy\n            conscious with contemporary design and construction—part of a broader interest in\n            sustainable design.\n          </p>\n        </div>\n      </ScrollArea.Viewport>\n      <ScrollArea.Scrollbar className=\"m-2 flex w-1 justify-center rounded-sm bg-gray-200 opacity-0 transition-opacity pointer-events-none data-[hovering]:opacity-100 data-[hovering]:delay-0 data-[hovering]:pointer-events-auto data-[scrolling]:opacity-100 data-[scrolling]:duration-0 data-[scrolling]:pointer-events-auto\">\n        <ScrollArea.Thumb className=\"w-full rounded-sm bg-gray-500\" />\n      </ScrollArea.Scrollbar>\n    </ScrollArea.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/scroll-area/demos/scroll-fade/css-modules/index.module.css",
    "content": ".ScrollArea {\n  box-sizing: border-box;\n  width: 24rem;\n  height: 12rem;\n  max-width: calc(100vw - 8rem);\n  background-color: var(--color-gray-50);\n  border-radius: 0.5rem;\n}\n\n.Viewport {\n  height: 100%;\n  border-radius: 0.375rem;\n  background: var(--color-gray-50);\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n  }\n\n  &::before,\n  &::after {\n    content: '';\n    display: block;\n    left: 0;\n    width: 100%;\n    position: absolute;\n    pointer-events: none;\n    border-radius: 0.375rem;\n    transition: height 0.1s ease-out;\n  }\n\n  &::before {\n    --scroll-area-overflow-y-start: inherit;\n    top: 0;\n    height: min(40px, var(--scroll-area-overflow-y-start));\n    background: linear-gradient(to bottom, var(--color-gray-50), transparent);\n  }\n\n  &::after {\n    --scroll-area-overflow-y-end: inherit;\n    bottom: 0;\n    height: min(40px, var(--scroll-area-overflow-y-end, 40px));\n    background: linear-gradient(to top, var(--color-gray-50), transparent);\n  }\n}\n\n.Content {\n  display: flex;\n  flex-direction: column;\n  gap: 1rem;\n  padding-block: 0.75rem;\n  padding-left: 1rem;\n  padding-right: 1.5rem;\n}\n\n.Paragraph {\n  margin: 0;\n  font-size: 0.875rem;\n  line-height: 1.375rem;\n  color: var(--color-gray-900);\n}\n\n.Scrollbar {\n  display: flex;\n  justify-content: center;\n  background-color: var(--color-gray-200);\n  width: 0.25rem;\n  border-radius: 0.375rem;\n  margin: 0.5rem;\n  opacity: 0;\n  transition: opacity 150ms;\n  pointer-events: none;\n\n  &[data-scrolling] {\n    transition-duration: 0ms;\n  }\n\n  &[data-hovering],\n  &[data-scrolling] {\n    opacity: 1;\n    pointer-events: auto;\n  }\n\n  &::before {\n    content: '';\n    position: absolute;\n    width: 1.25rem;\n    height: 100%;\n  }\n}\n\n.Thumb {\n  width: 100%;\n  border-radius: inherit;\n  background-color: var(--color-gray-500);\n}\n\n.ScrollUp {\n  position: absolute;\n  top: 0;\n  z-index: 1;\n  opacity: 0;\n  transition: opacity 0.15s;\n  pointer-events: none;\n\n  [data-overflow-y-start] & {\n    opacity: 1;\n    pointer-events: auto;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/scroll-area/demos/scroll-fade/css-modules/index.tsx",
    "content": "import { ScrollArea } from '@base-ui/react/scroll-area';\nimport styles from './index.module.css';\n\nexport default function ExampleScrollAreaScrollFade() {\n  return (\n    <ScrollArea.Root className={styles.ScrollArea}>\n      <ScrollArea.Viewport className={styles.Viewport}>\n        <ScrollArea.Content className={styles.Content}>\n          <p className={styles.Paragraph}>\n            Vernacular architecture is building done outside any academic tradition, and without\n            professional guidance. It is not a particular architectural movement or style, but\n            rather a broad category, encompassing a wide range and variety of building types, with\n            differing methods of construction, from around the world, both historical and extant and\n            classical and modern. Vernacular architecture constitutes 95% of the world's built\n            environment, as estimated in 1995 by Amos Rapoport, as measured against the small\n            percentage of new buildings every year designed by architects and built by engineers.\n          </p>\n          <p className={styles.Paragraph}>\n            This type of architecture usually serves immediate, local needs, is constrained by the\n            materials available in its particular region and reflects local traditions and cultural\n            practices. The study of vernacular architecture does not examine formally schooled\n            architects, but instead that of the design skills and tradition of local builders, who\n            were rarely given any attribution for the work. More recently, vernacular architecture\n            has been examined by designers and the building industry in an effort to be more energy\n            conscious with contemporary design and construction—part of a broader interest in\n            sustainable design.\n          </p>\n        </ScrollArea.Content>\n      </ScrollArea.Viewport>\n      <ScrollArea.Scrollbar className={styles.Scrollbar}>\n        <ScrollArea.Thumb className={styles.Thumb} />\n      </ScrollArea.Scrollbar>\n    </ScrollArea.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/scroll-area/demos/scroll-fade/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoScrollAreaScrollFade = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/scroll-area/demos/scroll-fade/tailwind/index.tsx",
    "content": "import { ScrollArea } from '@base-ui/react/scroll-area';\n\nexport default function ExampleScrollAreaScrollFade() {\n  return (\n    <ScrollArea.Root className=\"box-border h-48 w-96 max-w-[calc(100vw-8rem)] rounded-lg bg-gray-50\">\n      <ScrollArea.Viewport className=\"h-full rounded-md bg-gray-50 focus-visible:outline-2 focus-visible:outline-blue-800 before:[--scroll-area-overflow-y-start:inherit] after:[--scroll-area-overflow-y-end:inherit] before:content-[''] after:content-[''] before:block after:block before:absolute after:absolute before:left-0 after:left-0 before:w-full after:w-full before:pointer-events-none after:pointer-events-none before:rounded-md after:rounded-md before:transition-[height] after:transition-[height] before:duration-100 after:duration-100 before:ease-out after:ease-out before:top-0 after:bottom-0 before:bg-gradient-to-b before:from-gray-50 before:to-transparent after:bg-gradient-to-t after:from-gray-50 after:to-transparent before:[height:min(40px,var(--scroll-area-overflow-y-start))] after:[height:min(40px,var(--scroll-area-overflow-y-end,40px))]\">\n        <ScrollArea.Content className=\"flex flex-col gap-4 py-3 pr-6 pl-4 text-sm leading-[1.375rem] text-gray-900\">\n          <p>\n            Vernacular architecture is building done outside any academic tradition, and without\n            professional guidance. It is not a particular architectural movement or style, but\n            rather a broad category, encompassing a wide range and variety of building types, with\n            differing methods of construction, from around the world, both historical and extant and\n            classical and modern. Vernacular architecture constitutes 95% of the world's built\n            environment, as estimated in 1995 by Amos Rapoport, as measured against the small\n            percentage of new buildings every year designed by architects and built by engineers.\n          </p>\n          <p>\n            This type of architecture usually serves immediate, local needs, is constrained by the\n            materials available in its particular region and reflects local traditions and cultural\n            practices. The study of vernacular architecture does not examine formally schooled\n            architects, but instead that of the design skills and tradition of local builders, who\n            were rarely given any attribution for the work. More recently, vernacular architecture\n            has been examined by designers and the building industry in an effort to be more energy\n            conscious with contemporary design and construction—part of a broader interest in\n            sustainable design.\n          </p>\n        </ScrollArea.Content>\n      </ScrollArea.Viewport>\n      <ScrollArea.Scrollbar className=\"pointer-events-none m-2 flex w-1 justify-center rounded-sm bg-gray-200 opacity-0 transition-opacity duration-150 data-[hovering]:pointer-events-auto data-[hovering=true]:pointer-events-auto data-[hovering]:opacity-100 data-[hovering=true]:opacity-100 data-[scrolling]:pointer-events-auto data-[scrolling=true]:pointer-events-auto data-[scrolling]:opacity-100 data-[scrolling=true]:opacity-100 data-[scrolling]:duration-0 data-[scrolling=true]:duration-0 before:absolute before:h-full before:w-5 before:content-['']\">\n        <ScrollArea.Thumb className=\"w-full rounded-sm bg-gray-500\" />\n      </ScrollArea.Scrollbar>\n    </ScrollArea.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/scroll-area/page.mdx",
    "content": "# Scroll Area\n\n<Subtitle>A native scroll container with custom scrollbars.</Subtitle>\n\n<Meta\n  name=\"description\"\n  content=\"A high-quality, unstyled React scroll area that provides a native scroll container with custom scrollbars.\"\n/>\n\nimport { DemoScrollAreaHero } from './demos/hero';\n\n<DemoScrollAreaHero />\n\n## Anatomy\n\nImport the component and assemble its parts:\n\n```jsx title=\"Anatomy\"\nimport { ScrollArea } from '@base-ui/react/scroll-area';\n\n<ScrollArea.Root>\n  <ScrollArea.Viewport>\n    <ScrollArea.Content />\n  </ScrollArea.Viewport>\n  <ScrollArea.Scrollbar>\n    <ScrollArea.Thumb />\n  </ScrollArea.Scrollbar>\n  <ScrollArea.Corner />\n</ScrollArea.Root>;\n```\n\n## Examples\n\n### Both scrollbars\n\nUse `<ScrollArea.Corner>` to prevent the scrollbars from intersecting.\n\nimport { DemoScrollAreaBoth } from './demos/both';\n\n<DemoScrollAreaBoth compact />\n\n### Gradient scroll fade\n\nimport { DemoScrollAreaScrollFade } from './demos/scroll-fade';\n\nUse the viewport overflow CSS variables to fade the scroll edges, which gradually increases in strength as the user scrolls away from the edges.\n\nThe CSS variables do not inherit by default to improve rendering performance in complex scroll areas with deep subtrees. To use them in child elements (or pseudo-elements on `<ScrollArea.Viewport>`), you must manually set each variable to `inherit`.\n\n```css title=\"scroll-area.module.css\" {15,22}\n.Viewport {\n  &::before,\n  &::after {\n    content: '';\n    display: block;\n    left: 0;\n    width: 100%;\n    position: absolute;\n    pointer-events: none;\n    border-radius: 0.375rem;\n    transition: height 0.1s ease-out;\n  }\n\n  &::before {\n    --scroll-area-overflow-y-start: inherit;\n    top: 0;\n    height: min(40px, var(--scroll-area-overflow-y-start));\n    background: linear-gradient(to bottom, var(--color-gray-50), transparent);\n  }\n\n  &::after {\n    --scroll-area-overflow-y-end: inherit;\n    bottom: 0;\n    height: min(40px, var(--scroll-area-overflow-y-end, 40px));\n    background: linear-gradient(to top, var(--color-gray-50), transparent);\n  }\n}\n```\n\nFor SSR, a fallback can be used as part of the `var()` function to provide a default height.\n\n```css title=\"SSR fallback\" \", 40px\"\n.Viewport::after {\n  height: min(40px, var(--scroll-area-overflow-y-end, 40px));\n}\n```\n\n<DemoScrollAreaScrollFade compact />\n\n## API reference\n\n<Reference component=\"ScrollArea\" parts=\"Root, Viewport, Content, Scrollbar, Thumb, Corner\" />\n\nexport const metadata = {\n  keywords: [\n    'React Scroll Area',\n    'Custom Scrollbars',\n    'Custom Scroll',\n    'Scrollbar',\n    'Styled Scrollbar',\n    'Virtual Scroll',\n    'Scroll Area Component',\n    'Overflow Container',\n    'Scrollable Region',\n    'Scrollable Content',\n    'Overflow Scroll',\n    'Gradient Scroll Fade',\n    'Scrollable Container',\n    'Headless React Components',\n    'Base UI',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/select/demos/grouped/css-modules/index.module.css",
    "content": ".Field {\n  display: flex;\n  flex-direction: column;\n  align-items: start;\n  gap: 0.25rem;\n}\n\n.Label {\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 700;\n  color: var(--color-gray-900);\n  cursor: default;\n}\n\n.Value[data-placeholder] {\n  opacity: 0.6;\n}\n\n.Select {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 0.75rem;\n  height: 2.5rem;\n  padding-left: 0.875rem;\n  padding-right: 0.75rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: canvas;\n  font-family: inherit;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  font-weight: 400;\n  color: var(--color-gray-900);\n  -webkit-user-select: none;\n  user-select: none;\n  min-width: 11rem;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.SelectIcon {\n  display: flex;\n}\n\n.Positioner {\n  outline: none;\n  z-index: 1;\n  -webkit-user-select: none;\n  user-select: none;\n}\n\n.Popup {\n  box-sizing: border-box;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  background-clip: padding-box;\n  color: var(--color-gray-900);\n  min-width: var(--anchor-width);\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  &[data-side='none'] {\n    transition: none;\n    transform: none;\n    opacity: 1;\n    min-width: calc(var(--anchor-width) + 1rem);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n  }\n}\n\n.List {\n  box-sizing: border-box;\n  position: relative;\n  padding-block: 0.25rem;\n  overflow-y: auto;\n  max-height: var(--available-height);\n  scroll-padding-block-start: 2.25rem;\n  scroll-padding-block-end: 1.5rem;\n}\n\n.Group {\n  display: block;\n  padding-bottom: 0.125rem;\n}\n\n.GroupLabel {\n  box-sizing: border-box;\n  padding: 0.5rem 1rem 0.25rem calc(0.625rem + 0.75rem + 0.5rem);\n  font-size: 0.6875rem;\n  font-weight: 700;\n  text-transform: uppercase;\n  letter-spacing: 0.05em;\n  color: var(--color-gray-700);\n  background-color: canvas;\n  position: sticky;\n  z-index: 1;\n  top: 0;\n}\n\n.Item {\n  box-sizing: border-box;\n  outline: 0;\n  font-size: 0.875rem;\n  line-height: 1rem;\n  padding-block: 0.5rem;\n  padding-left: 0.625rem;\n  padding-right: 1rem;\n  display: grid;\n  gap: 0.5rem;\n  align-items: center;\n  grid-template-columns: 0.75rem 1fr;\n  cursor: default;\n  -webkit-user-select: none;\n  user-select: none;\n\n  @media (pointer: coarse) {\n    padding-block: 0.625rem;\n    font-size: 0.925rem;\n  }\n\n  [data-side='none'] & {\n    font-size: 1rem;\n    padding-right: 3rem;\n  }\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.25rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.ItemIndicator {\n  grid-column-start: 1;\n}\n\n.ItemIndicatorIcon {\n  display: block;\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.ItemText {\n  grid-column-start: 2;\n}\n\n.Separator {\n  margin-block: 0.5rem;\n  margin-inline: 1rem;\n  height: 1px;\n  background-color: var(--color-gray-200);\n\n  @media (prefers-color-scheme: dark) {\n    background-color: var(--color-gray-300);\n  }\n}\n\n.ScrollArrow {\n  width: 100%;\n  background: canvas;\n  z-index: 2;\n  text-align: center;\n  cursor: default;\n  border-radius: 0.375rem;\n  height: 1rem;\n  font-size: 0.75rem;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n\n  &::before {\n    content: '';\n    position: absolute;\n    width: 100%;\n    height: 100%;\n    left: 0;\n  }\n\n  &[data-direction='up'] {\n    &[data-side='none'] {\n      &::before {\n        top: -100%;\n      }\n    }\n  }\n\n  &[data-direction='down'] {\n    bottom: 0;\n\n    &[data-side='none'] {\n      &::before {\n        bottom: -100%;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/select/demos/grouped/css-modules/index.tsx",
    "content": "import * as React from 'react';\nimport { Select } from '@base-ui/react/select';\nimport { Field } from '@base-ui/react/field';\nimport styles from './index.module.css';\n\nexport default function ExampleSelectGrouped() {\n  return (\n    <Field.Root className={styles.Field}>\n      <Field.Label className={styles.Label} nativeLabel={false} render={<div />}>\n        Produce\n      </Field.Label>\n      <Select.Root items={groupedProduce}>\n        <Select.Trigger className={styles.Select}>\n          <Select.Value className={styles.Value} placeholder=\"Select produce\" />\n          <Select.Icon className={styles.SelectIcon}>\n            <ChevronUpDownIcon />\n          </Select.Icon>\n        </Select.Trigger>\n        <Select.Portal>\n          <Select.Positioner className={styles.Positioner} sideOffset={8}>\n            <Select.Popup className={styles.Popup}>\n              <Select.ScrollUpArrow className={styles.ScrollArrow} />\n              <Select.List className={styles.List}>\n                {groupedProduce.map((group, index) => (\n                  <React.Fragment key={group.value}>\n                    <Select.Group className={styles.Group}>\n                      <Select.GroupLabel className={styles.GroupLabel}>\n                        {group.value}\n                      </Select.GroupLabel>\n                      {group.items.map((item) => (\n                        <Select.Item key={item.value} value={item.value} className={styles.Item}>\n                          <Select.ItemIndicator className={styles.ItemIndicator}>\n                            <CheckIcon className={styles.ItemIndicatorIcon} />\n                          </Select.ItemIndicator>\n                          <Select.ItemText className={styles.ItemText}>\n                            {item.label}\n                          </Select.ItemText>\n                        </Select.Item>\n                      ))}\n                    </Select.Group>\n                    {index < groupedProduce.length - 1 ? (\n                      <Select.Separator className={styles.Separator} />\n                    ) : null}\n                  </React.Fragment>\n                ))}\n              </Select.List>\n              <Select.ScrollDownArrow className={styles.ScrollArrow} />\n            </Select.Popup>\n          </Select.Positioner>\n        </Select.Portal>\n      </Select.Root>\n    </Field.Root>\n  );\n}\n\nfunction ChevronUpDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"8\"\n      height=\"12\"\n      viewBox=\"0 0 8 12\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"1.5\"\n      {...props}\n    >\n      <path d=\"M0.5 4.5L4 1.5L7.5 4.5\" />\n      <path d=\"M0.5 7.5L4 10.5L7.5 7.5\" />\n    </svg>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\nconst groupedProduce = [\n  {\n    value: 'Fruits',\n    items: [\n      { value: 'apple', label: 'Apple' },\n      { value: 'banana', label: 'Banana' },\n      { value: 'mango', label: 'Mango' },\n      { value: 'kiwi', label: 'Kiwi' },\n      { value: 'grape', label: 'Grape' },\n      { value: 'orange', label: 'Orange' },\n      { value: 'strawberry', label: 'Strawberry' },\n      { value: 'watermelon', label: 'Watermelon' },\n    ],\n  },\n  {\n    value: 'Vegetables',\n    items: [\n      { value: 'broccoli', label: 'Broccoli' },\n      { value: 'carrot', label: 'Carrot' },\n      { value: 'cauliflower', label: 'Cauliflower' },\n      { value: 'cucumber', label: 'Cucumber' },\n      { value: 'kale', label: 'Kale' },\n      { value: 'pepper', label: 'Bell pepper' },\n      { value: 'spinach', label: 'Spinach' },\n      { value: 'zucchini', label: 'Zucchini' },\n    ],\n  },\n];\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/select/demos/grouped/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoSelectGrouped = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/select/demos/grouped/tailwind/index.tsx",
    "content": "import * as React from 'react';\nimport { Select } from '@base-ui/react/select';\nimport { Field } from '@base-ui/react/field';\n\nexport default function ExampleSelectGrouped() {\n  return (\n    <Field.Root className=\"flex flex-col gap-1\">\n      <Field.Label\n        className=\"cursor-default text-sm leading-5 font-bold text-gray-900\"\n        nativeLabel={false}\n        render={<div />}\n      >\n        Produce\n      </Field.Label>\n      <Select.Root items={groupedProduce}>\n        <Select.Trigger className=\"flex h-10 min-w-44 items-center justify-between gap-3 rounded-md border border-gray-200 pr-3 pl-3.5 text-base bg-[canvas] text-gray-900 select-none hover:bg-gray-100 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 data-[popup-open]:bg-gray-100 font-normal\">\n          <Select.Value className=\"data-[placeholder]:opacity-60\" placeholder=\"Select produce\" />\n          <Select.Icon className=\"flex\">\n            <ChevronUpDownIcon />\n          </Select.Icon>\n        </Select.Trigger>\n        <Select.Portal>\n          <Select.Positioner className=\"outline-none select-none z-10\" sideOffset={8}>\n            <Select.Popup className=\"group min-w-[var(--anchor-width)] origin-[var(--transform-origin)] bg-clip-padding rounded-md bg-[canvas] text-gray-900 shadow-lg shadow-gray-200 outline outline-1 outline-gray-200 transition-[transform,scale,opacity] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[side=none]:min-w-[calc(var(--anchor-width)+1rem)] data-[side=none]:data-[ending-style]:transition-none data-[starting-style]:scale-90 data-[starting-style]:opacity-0 data-[side=none]:data-[starting-style]:scale-100 data-[side=none]:data-[starting-style]:opacity-100 data-[side=none]:data-[starting-style]:transition-none dark:shadow-none dark:outline-gray-300\">\n              <Select.ScrollUpArrow className=\"top-0 z-[2] flex h-4 w-full cursor-default items-center justify-center rounded-md bg-[canvas] text-center text-xs before:absolute data-[side=none]:before:top-[-100%] before:left-0 before:h-full before:w-full before:content-['']\" />\n              <Select.List className=\"relative py-1 scroll-pt-[2.25rem] scroll-pb-6 overflow-y-auto max-h-[var(--available-height)]\">\n                {groupedProduce.map((group, index) => (\n                  <React.Fragment key={group.value}>\n                    <Select.Group className=\"block pb-0.5\">\n                      <Select.GroupLabel className=\"sticky top-0 z-[1] bg-[canvas] pr-4 pb-1 pl-[1.875rem] pt-2 text-[0.6875rem] font-bold text-[var(--color-gray-700)] uppercase tracking-wider\">\n                        {group.value}\n                      </Select.GroupLabel>\n                      {group.items.map((item) => (\n                        <Select.Item\n                          key={item.value}\n                          value={item.value}\n                          className=\"grid cursor-default grid-cols-[0.75rem_1fr] items-center gap-2 py-2 pr-4 pl-2.5 text-sm leading-4 outline-none select-none group-data-[side=none]:pr-12 group-data-[side=none]:text-base group-data-[side=none]:leading-4 data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-sm data-[highlighted]:before:bg-gray-900 pointer-coarse:py-2.5 pointer-coarse:text-[0.925rem]\"\n                        >\n                          <Select.ItemIndicator className=\"col-start-1\">\n                            <CheckIcon className=\"size-3\" />\n                          </Select.ItemIndicator>\n                          <Select.ItemText className=\"col-start-2\">{item.label}</Select.ItemText>\n                        </Select.Item>\n                      ))}\n                    </Select.Group>\n                    {index < groupedProduce.length - 1 ? (\n                      <Select.Separator className=\"my-2 mx-4 h-px bg-gray-200 dark:bg-gray-300\" />\n                    ) : null}\n                  </React.Fragment>\n                ))}\n              </Select.List>\n              <Select.ScrollDownArrow className=\"bottom-0 z-[2] flex h-4 w-full cursor-default items-center justify-center rounded-md bg-[canvas] text-center text-xs before:absolute before:left-0 before:h-full before:w-full before:content-[''] bottom-0 data-[side=none]:before:bottom-[-100%]\" />\n            </Select.Popup>\n          </Select.Positioner>\n        </Select.Portal>\n      </Select.Root>\n    </Field.Root>\n  );\n}\n\nfunction ChevronUpDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"8\"\n      height=\"12\"\n      viewBox=\"0 0 8 12\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"1.5\"\n      {...props}\n    >\n      <path d=\"M0.5 4.5L4 1.5L7.5 4.5\" />\n      <path d=\"M0.5 7.5L4 10.5L7.5 7.5\" />\n    </svg>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\nconst groupedProduce = [\n  {\n    value: 'Fruits',\n    items: [\n      { value: 'apple', label: 'Apple' },\n      { value: 'banana', label: 'Banana' },\n      { value: 'mango', label: 'Mango' },\n      { value: 'kiwi', label: 'Kiwi' },\n      { value: 'grape', label: 'Grape' },\n      { value: 'orange', label: 'Orange' },\n      { value: 'strawberry', label: 'Strawberry' },\n      { value: 'watermelon', label: 'Watermelon' },\n    ],\n  },\n  {\n    value: 'Vegetables',\n    items: [\n      { value: 'broccoli', label: 'Broccoli' },\n      { value: 'carrot', label: 'Carrot' },\n      { value: 'cauliflower', label: 'Cauliflower' },\n      { value: 'cucumber', label: 'Cucumber' },\n      { value: 'kale', label: 'Kale' },\n      { value: 'pepper', label: 'Bell pepper' },\n      { value: 'spinach', label: 'Spinach' },\n      { value: 'zucchini', label: 'Zucchini' },\n    ],\n  },\n];\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/select/demos/hero/css-modules/index.module.css",
    "content": ".Field {\n  display: flex;\n  flex-direction: column;\n  align-items: start;\n  gap: 0.25rem;\n}\n\n.Label {\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 700;\n  color: var(--color-gray-900);\n  cursor: default;\n}\n\n.Value[data-placeholder] {\n  opacity: 0.6;\n}\n\n.Select {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 0.75rem;\n  height: 2.5rem;\n  padding-left: 0.875rem;\n  padding-right: 0.75rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: canvas;\n  font-family: inherit;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  font-weight: 400;\n  color: var(--color-gray-900);\n  -webkit-user-select: none;\n  user-select: none;\n  min-width: 10rem;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.SelectIcon {\n  display: flex;\n}\n\n.Positioner {\n  outline: none;\n  z-index: 1;\n  -webkit-user-select: none;\n  user-select: none;\n}\n\n.Popup {\n  box-sizing: border-box;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  background-clip: padding-box;\n  color: var(--color-gray-900);\n  min-width: var(--anchor-width);\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  &[data-side='none'] {\n    transition: none;\n    transform: none;\n    opacity: 1;\n    min-width: calc(var(--anchor-width) + 1rem);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n  }\n}\n\n.List {\n  box-sizing: border-box;\n  position: relative;\n  padding-block: 0.25rem;\n  overflow-y: auto;\n  max-height: var(--available-height);\n  scroll-padding-block: 1.5rem;\n}\n\n.Arrow {\n  display: flex;\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n\n.Item {\n  box-sizing: border-box;\n  outline: 0;\n  font-size: 0.875rem;\n  line-height: 1rem;\n  padding-block: 0.5rem;\n  padding-left: 0.625rem;\n  padding-right: 1rem;\n  display: grid;\n  gap: 0.5rem;\n  align-items: center;\n  grid-template-columns: 0.75rem 1fr;\n  cursor: default;\n  -webkit-user-select: none;\n  user-select: none;\n\n  @media (pointer: coarse) {\n    padding-block: 0.625rem;\n    font-size: 0.925rem;\n  }\n\n  [data-side='none'] & {\n    font-size: 1rem;\n    padding-right: 3rem;\n  }\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.25rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.ItemIndicator {\n  grid-column-start: 1;\n}\n\n.ItemIndicatorIcon {\n  display: block;\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.ItemText {\n  grid-column-start: 2;\n}\n\n.ScrollArrow {\n  width: 100%;\n  background: canvas;\n  z-index: 1;\n  text-align: center;\n  cursor: default;\n  border-radius: 0.375rem;\n  height: 1rem;\n  font-size: 0.75rem;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n\n  &::before {\n    content: '';\n    position: absolute;\n    width: 100%;\n    height: 100%;\n    left: 0;\n  }\n\n  &[data-direction='up'] {\n    &[data-side='none'] {\n      &::before {\n        top: -100%;\n      }\n    }\n  }\n\n  &[data-direction='down'] {\n    bottom: 0;\n\n    &[data-side='none'] {\n      &::before {\n        bottom: -100%;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/select/demos/hero/css-modules/index.tsx",
    "content": "import * as React from 'react';\nimport { Select } from '@base-ui/react/select';\nimport styles from './index.module.css';\n\nconst apples = [\n  { label: 'Gala', value: 'gala' },\n  { label: 'Fuji', value: 'fuji' },\n  { label: 'Honeycrisp', value: 'honeycrisp' },\n  { label: 'Granny Smith', value: 'granny-smith' },\n  { label: 'Pink Lady', value: 'pink-lady' },\n];\n\nexport default function ExampleSelect() {\n  return (\n    <div className={styles.Field}>\n      <Select.Root items={apples}>\n        <Select.Label className={styles.Label}>Apple</Select.Label>\n        <Select.Trigger className={styles.Select}>\n          <Select.Value className={styles.Value} placeholder=\"Select apple\" />\n          <Select.Icon className={styles.SelectIcon}>\n            <ChevronUpDownIcon />\n          </Select.Icon>\n        </Select.Trigger>\n        <Select.Portal>\n          <Select.Positioner className={styles.Positioner} sideOffset={8}>\n            <Select.Popup className={styles.Popup}>\n              <Select.ScrollUpArrow className={styles.ScrollArrow} />\n              <Select.List className={styles.List}>\n                {apples.map(({ label, value }) => (\n                  <Select.Item key={label} value={value} className={styles.Item}>\n                    <Select.ItemIndicator className={styles.ItemIndicator}>\n                      <CheckIcon className={styles.ItemIndicatorIcon} />\n                    </Select.ItemIndicator>\n                    <Select.ItemText className={styles.ItemText}>{label}</Select.ItemText>\n                  </Select.Item>\n                ))}\n              </Select.List>\n              <Select.ScrollDownArrow className={styles.ScrollArrow} />\n            </Select.Popup>\n          </Select.Positioner>\n        </Select.Portal>\n      </Select.Root>\n    </div>\n  );\n}\n\nfunction ChevronUpDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"8\"\n      height=\"12\"\n      viewBox=\"0 0 8 12\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"1.5\"\n      {...props}\n    >\n      <path d=\"M0.5 4.5L4 1.5L7.5 4.5\" />\n      <path d=\"M0.5 7.5L4 10.5L7.5 7.5\" />\n    </svg>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/select/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoSelectHero = createDemoWithVariants(import.meta.url, { CssModules, Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/select/demos/hero/tailwind/index.tsx",
    "content": "import * as React from 'react';\nimport { Select } from '@base-ui/react/select';\n\nconst apples = [\n  { label: 'Gala', value: 'gala' },\n  { label: 'Fuji', value: 'fuji' },\n  { label: 'Honeycrisp', value: 'honeycrisp' },\n  { label: 'Granny Smith', value: 'granny-smith' },\n  { label: 'Pink Lady', value: 'pink-lady' },\n];\n\nexport default function ExampleSelect() {\n  return (\n    <div className=\"flex flex-col gap-1\">\n      <Select.Root items={apples}>\n        <Select.Label className=\"cursor-default text-sm leading-5 font-bold text-gray-900\">\n          Apple\n        </Select.Label>\n        <Select.Trigger className=\"flex h-10 min-w-40 items-center justify-between gap-3 rounded-md border border-gray-200 pr-3 pl-3.5 text-base bg-[canvas] text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 data-[popup-open]:bg-gray-100 font-normal\">\n          <Select.Value className=\"data-[placeholder]:opacity-60\" placeholder=\"Select apple\" />\n          <Select.Icon className=\"flex\">\n            <ChevronUpDownIcon />\n          </Select.Icon>\n        </Select.Trigger>\n        <Select.Portal>\n          <Select.Positioner className=\"outline-hidden select-none z-10\" sideOffset={8}>\n            <Select.Popup className=\"group min-w-[var(--anchor-width)] origin-[var(--transform-origin)] bg-clip-padding rounded-md bg-[canvas] text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 transition-[transform,scale,opacity] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[side=none]:min-w-[calc(var(--anchor-width)+1rem)] data-[side=none]:data-[ending-style]:transition-none data-[starting-style]:scale-90 data-[starting-style]:opacity-0 data-[side=none]:data-[starting-style]:scale-100 data-[side=none]:data-[starting-style]:opacity-100 data-[side=none]:data-[starting-style]:transition-none dark:shadow-none dark:outline-gray-300\">\n              <Select.ScrollUpArrow className=\"top-0 z-[1] flex h-4 w-full cursor-default items-center justify-center rounded-md bg-[canvas] text-center text-xs before:absolute data-[side=none]:before:top-[-100%] before:left-0 before:h-full before:w-full before:content-['']\" />\n              <Select.List className=\"relative py-1 scroll-py-6 overflow-y-auto max-h-[var(--available-height)]\">\n                {apples.map(({ label, value }) => (\n                  <Select.Item\n                    key={label}\n                    value={value}\n                    className=\"grid cursor-default grid-cols-[0.75rem_1fr] items-center gap-2 py-2 pr-4 pl-2.5 text-sm leading-4 outline-hidden select-none group-data-[side=none]:pr-12 group-data-[side=none]:text-base group-data-[side=none]:leading-4 data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900 pointer-coarse:py-2.5 pointer-coarse:text-[0.925rem]\"\n                  >\n                    <Select.ItemIndicator className=\"col-start-1\">\n                      <CheckIcon className=\"size-3\" />\n                    </Select.ItemIndicator>\n                    <Select.ItemText className=\"col-start-2\">{label}</Select.ItemText>\n                  </Select.Item>\n                ))}\n              </Select.List>\n              <Select.ScrollDownArrow className=\"bottom-0 z-[1] flex h-4 w-full cursor-default items-center justify-center rounded-md bg-[canvas] text-center text-xs before:absolute before:left-0 before:h-full before:w-full before:content-[''] bottom-0 data-[side=none]:before:bottom-[-100%]\" />\n            </Select.Popup>\n          </Select.Positioner>\n        </Select.Portal>\n      </Select.Root>\n    </div>\n  );\n}\n\nfunction ChevronUpDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"8\"\n      height=\"12\"\n      viewBox=\"0 0 8 12\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"1.5\"\n      {...props}\n    >\n      <path d=\"M0.5 4.5L4 1.5L7.5 4.5\" />\n      <path d=\"M0.5 7.5L4 10.5L7.5 7.5\" />\n    </svg>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/select/demos/multiple/css-modules/index.module.css",
    "content": ".Field {\n  display: flex;\n  flex-direction: column;\n  align-items: start;\n  gap: 0.25rem;\n}\n\n.Label {\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 700;\n  color: var(--color-gray-900);\n  cursor: default;\n}\n\n.Value[data-placeholder] {\n  opacity: 0.6;\n}\n\n.Select {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 0.75rem;\n  height: 2.5rem;\n  padding-left: 0.875rem;\n  padding-right: 0.75rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: canvas;\n  font-family: inherit;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  font-weight: 400;\n  color: var(--color-gray-900);\n  user-select: none;\n  min-width: 14rem;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.SelectIcon {\n  display: flex;\n}\n\n.Positioner {\n  outline: none;\n  z-index: 1;\n}\n\n.Popup {\n  box-sizing: border-box;\n  padding-block: 0.25rem;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  background-clip: padding-box;\n  color: var(--color-gray-900);\n  min-width: var(--anchor-width);\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n  overflow-y: auto;\n  max-height: var(--available-height);\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  &[data-side='none'] {\n    transition: none;\n    transform: none;\n    opacity: 1;\n    min-width: calc(var(--anchor-width) + 1rem);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n  }\n}\n\n.Item {\n  box-sizing: border-box;\n  outline: 0;\n  font-size: 0.875rem;\n  line-height: 1rem;\n  padding-block: 0.5rem;\n  padding-left: 0.625rem;\n  padding-right: 1rem;\n  display: grid;\n  gap: 0.5rem;\n  align-items: center;\n  grid-template-columns: 0.75rem 1fr;\n  cursor: default;\n  user-select: none;\n  scroll-margin-block: 0.25rem;\n\n  @media (pointer: coarse) {\n    padding-block: 0.625rem;\n    font-size: 0.925rem;\n  }\n\n  [data-side='none'] & {\n    font-size: 1rem;\n    padding-right: 3rem;\n  }\n\n  @media (hover: hover) {\n    &[data-highlighted] {\n      z-index: 0;\n      position: relative;\n      color: var(--color-gray-50);\n    }\n\n    &[data-highlighted]::before {\n      content: '';\n      z-index: -1;\n      position: absolute;\n      inset-block: 0;\n      inset-inline: 0.25rem;\n      border-radius: 0.25rem;\n      background-color: var(--color-gray-900);\n    }\n  }\n}\n\n.ItemIndicator {\n  grid-column-start: 1;\n}\n\n.ItemIndicatorIcon {\n  display: block;\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.ItemText {\n  grid-column-start: 2;\n}\n\n.ScrollArrow {\n  width: 100%;\n  background: canvas;\n  z-index: 1;\n  text-align: center;\n  cursor: default;\n  border-radius: 0.375rem;\n  height: 1rem;\n  font-size: 0.75rem;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n\n  &::before {\n    content: '';\n    position: absolute;\n    width: 100%;\n    height: 100%;\n    left: 0;\n  }\n\n  &[data-direction='up'] {\n    top: 0;\n\n    &::before {\n      top: -100%;\n    }\n  }\n\n  &[data-direction='down'] {\n    bottom: 0;\n\n    &::before {\n      bottom: -100%;\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/select/demos/multiple/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Select } from '@base-ui/react/select';\nimport styles from './index.module.css';\n\nconst languages = {\n  javascript: 'JavaScript',\n  typescript: 'TypeScript',\n  python: 'Python',\n  java: 'Java',\n  csharp: 'C#',\n  php: 'PHP',\n  cpp: 'C++',\n  rust: 'Rust',\n  go: 'Go',\n  swift: 'Swift',\n};\n\ntype Language = keyof typeof languages;\n\nconst values = Object.keys(languages) as Language[];\n\nfunction renderValue(value: Language[]) {\n  if (value.length === 0) {\n    return 'Select languages…';\n  }\n\n  const firstLanguage = languages[value[0]];\n  const additionalLanguages = value.length > 1 ? ` (+${value.length - 1} more)` : '';\n  return firstLanguage + additionalLanguages;\n}\n\nexport default function MultiSelectExample() {\n  return (\n    <div className={styles.Field}>\n      <Select.Root multiple defaultValue={['javascript', 'typescript']}>\n        <Select.Label className={styles.Label}>Languages</Select.Label>\n        <Select.Trigger className={styles.Select}>\n          <Select.Value className={styles.Value}>{renderValue}</Select.Value>\n          <Select.Icon className={styles.SelectIcon}>\n            <ChevronUpDownIcon />\n          </Select.Icon>\n        </Select.Trigger>\n        <Select.Portal>\n          <Select.Positioner\n            className={styles.Positioner}\n            sideOffset={8}\n            alignItemWithTrigger={false}\n          >\n            <Select.Popup className={styles.Popup}>\n              {values.map((value) => (\n                <Select.Item key={value} value={value} className={styles.Item}>\n                  <Select.ItemIndicator className={styles.ItemIndicator}>\n                    <CheckIcon className={styles.ItemIndicatorIcon} />\n                  </Select.ItemIndicator>\n                  <Select.ItemText className={styles.ItemText}>{languages[value]}</Select.ItemText>\n                </Select.Item>\n              ))}\n            </Select.Popup>\n          </Select.Positioner>\n        </Select.Portal>\n      </Select.Root>\n    </div>\n  );\n}\n\nfunction ChevronUpDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"8\"\n      height=\"12\"\n      viewBox=\"0 0 8 12\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"1.5\"\n      {...props}\n    >\n      <path d=\"M0.5 4.5L4 1.5L7.5 4.5\" />\n      <path d=\"M0.5 7.5L4 10.5L7.5 7.5\" />\n    </svg>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/select/demos/multiple/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoSelectMultiple = createDemoWithVariants(import.meta.url, { CssModules, Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/select/demos/multiple/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Select } from '@base-ui/react/select';\n\nconst languages = {\n  javascript: 'JavaScript',\n  typescript: 'TypeScript',\n  python: 'Python',\n  java: 'Java',\n  csharp: 'C#',\n  php: 'PHP',\n  cpp: 'C++',\n  rust: 'Rust',\n  go: 'Go',\n  swift: 'Swift',\n};\n\ntype Language = keyof typeof languages;\n\nconst values = Object.keys(languages) as Language[];\n\nfunction renderValue(value: Language[]) {\n  if (value.length === 0) {\n    return 'Select languages…';\n  }\n\n  const firstLanguage = languages[value[0]];\n  const additionalLanguages = value.length > 1 ? ` (+${value.length - 1} more)` : '';\n  return firstLanguage + additionalLanguages;\n}\n\nexport default function MultiSelectExample() {\n  return (\n    <div className=\"flex flex-col gap-1\">\n      <Select.Root multiple defaultValue={['javascript', 'typescript']}>\n        <Select.Label className=\"cursor-default text-sm leading-5 font-bold text-gray-900\">\n          Languages\n        </Select.Label>\n        <Select.Trigger className=\"flex h-10 min-w-[14rem] items-center justify-between gap-3 rounded-md border border-gray-200 pr-3 pl-3.5 text-base bg-[canvas] text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 data-[popup-open]:bg-gray-100 font-normal\">\n          <Select.Value className=\"data-[placeholder]:opacity-60\">{renderValue}</Select.Value>\n          <Select.Icon className=\"flex\">\n            <ChevronUpDownIcon />\n          </Select.Icon>\n        </Select.Trigger>\n        <Select.Portal>\n          <Select.Positioner\n            className=\"outline-hidden z-10\"\n            sideOffset={8}\n            alignItemWithTrigger={false}\n          >\n            <Select.Popup className=\"group max-h-[var(--available-height)] min-w-[var(--anchor-width)] origin-[var(--transform-origin)] bg-clip-padding overflow-y-auto rounded-md bg-[canvas] py-1 text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 transition-[transform,scale,opacity] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[side=none]:min-w-[calc(var(--anchor-width)+1rem)] data-[side=none]:data-[ending-style]:transition-none data-[starting-style]:scale-90 data-[starting-style]:opacity-0 data-[side=none]:data-[starting-style]:scale-100 data-[side=none]:data-[starting-style]:opacity-100 data-[side=none]:data-[starting-style]:transition-none dark:shadow-none dark:outline-gray-300\">\n              {values.map((value) => (\n                <Select.Item\n                  key={value}\n                  value={value}\n                  className=\"grid cursor-default grid-cols-[0.75rem_1fr] items-center gap-2 py-2 pr-4 pl-2.5 text-sm leading-4 outline-hidden select-none scroll-my-1 group-data-[side=none]:pr-12 group-data-[side=none]:text-base group-data-[side=none]:leading-4 pointer-coarse:py-2.5 pointer-coarse:text-[0.925rem] [@media(hover:hover)]:[&[data-highlighted]]:relative [@media(hover:hover)]:[&[data-highlighted]]:z-0 [@media(hover:hover)]:[&[data-highlighted]]:text-gray-50 [@media(hover:hover)]:[&[data-highlighted]]:before:content-[''] [@media(hover:hover)]:[&[data-highlighted]]:before:absolute [@media(hover:hover)]:[&[data-highlighted]]:before:inset-y-0 [@media(hover:hover)]:[&[data-highlighted]]:before:inset-x-1 [@media(hover:hover)]:[&[data-highlighted]]:before:rounded-xs [@media(hover:hover)]:[&[data-highlighted]]:before:bg-gray-900 [@media(hover:hover)]:[&[data-highlighted]]:before:z-[-1]\"\n                >\n                  <Select.ItemIndicator className=\"col-start-1\">\n                    <CheckIcon className=\"size-3\" />\n                  </Select.ItemIndicator>\n                  <Select.ItemText className=\"col-start-2\">{languages[value]}</Select.ItemText>\n                </Select.Item>\n              ))}\n            </Select.Popup>\n          </Select.Positioner>\n        </Select.Portal>\n      </Select.Root>\n    </div>\n  );\n}\n\nfunction ChevronUpDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"8\"\n      height=\"12\"\n      viewBox=\"0 0 8 12\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"1.5\"\n      {...props}\n    >\n      <path d=\"M0.5 4.5L4 1.5L7.5 4.5\" />\n      <path d=\"M0.5 7.5L4 10.5L7.5 7.5\" />\n    </svg>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/select/demos/object-values/css-modules/index.module.css",
    "content": ".Field {\n  display: flex;\n  flex-direction: column;\n  align-items: start;\n  gap: 0.25rem;\n}\n\n.Label {\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 700;\n  color: var(--color-gray-900);\n  cursor: default;\n}\n\n.Select {\n  box-sizing: border-box;\n  display: flex;\n  align-items: flex-start;\n  justify-content: space-between;\n  gap: 0.75rem;\n  min-height: 2.5rem;\n  padding-block: 0.5rem;\n  padding-left: 0.875rem;\n  padding-right: 0.75rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: canvas;\n  font-family: inherit;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  -webkit-user-select: none;\n  user-select: none;\n  min-width: 16rem;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.ValueText {\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n  gap: 0.125rem;\n}\n\n.ValuePrimary {\n  font-size: 1rem;\n  line-height: 1.5rem;\n}\n\n.ValueSecondary {\n  font-size: 0.825rem;\n  line-height: 1rem;\n  color: var(--color-gray-600);\n}\n\n.SelectIcon {\n  display: flex;\n  align-items: center;\n  align-self: center;\n}\n\n.Positioner {\n  outline: none;\n  z-index: 1;\n  -webkit-user-select: none;\n  user-select: none;\n}\n\n.Popup {\n  box-sizing: border-box;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  background-clip: padding-box;\n  color: var(--color-gray-900);\n  min-width: var(--anchor-width);\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  &[data-side='none'] {\n    transition: none;\n    transform: none;\n    opacity: 1;\n    min-width: calc(var(--anchor-width) + 1rem);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n  }\n}\n\n.List {\n  box-sizing: border-box;\n  position: relative;\n  padding-block: 0.25rem;\n  overflow-y: auto;\n  max-height: var(--available-height);\n  scroll-padding-block: 1.5rem;\n}\n\n.Item {\n  box-sizing: border-box;\n  outline: 0;\n  font-size: 0.875rem;\n  line-height: 1rem;\n  padding-block: 0.5rem;\n  padding-left: 0.625rem;\n  padding-right: 1rem;\n  display: grid;\n  gap: 0.5rem;\n  align-items: flex-start;\n  grid-template-columns: 0.75rem 1fr;\n  cursor: default;\n  -webkit-user-select: none;\n  user-select: none;\n\n  @media (pointer: coarse) {\n    padding-block: 0.625rem;\n    font-size: 0.925rem;\n  }\n\n  [data-side='none'] & {\n    font-size: 1rem;\n    padding-right: 3rem;\n  }\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n\n    .ItemDescription {\n      color: currentColor;\n    }\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.25rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.ItemIndicator {\n  grid-column-start: 1;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  align-self: start;\n  position: relative;\n  top: 0.4em;\n}\n\n.ItemIndicatorIcon {\n  display: block;\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.ItemText {\n  grid-column-start: 2;\n  display: flex;\n  flex-direction: column;\n  gap: 0.125rem;\n}\n\n.ItemLabel {\n  font-size: 1rem;\n  line-height: 1.5rem;\n}\n\n.ItemDescription {\n  font-size: 0.825rem;\n  line-height: 1rem;\n  opacity: 0.8;\n}\n\n.ScrollArrow {\n  width: 100%;\n  background: canvas;\n  z-index: 1;\n  text-align: center;\n  cursor: default;\n  border-radius: 0.375rem;\n  height: 1rem;\n  font-size: 0.75rem;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n\n  &::before {\n    content: '';\n    position: absolute;\n    width: 100%;\n    height: 100%;\n    left: 0;\n  }\n\n  &[data-direction='up'] {\n    &[data-side='none'] {\n      &::before {\n        top: -100%;\n      }\n    }\n  }\n\n  &[data-direction='down'] {\n    bottom: 0;\n\n    &[data-side='none'] {\n      &::before {\n        bottom: -100%;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/select/demos/object-values/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Select } from '@base-ui/react/select';\nimport styles from './index.module.css';\n\nexport default function ObjectValueSelect() {\n  return (\n    <div className={styles.Field}>\n      <Select.Root defaultValue={shippingMethods[0]} itemToStringValue={(item) => item.id}>\n        <Select.Label className={styles.Label}>Shipping method</Select.Label>\n        <Select.Trigger className={styles.Select}>\n          <Select.Value>\n            {(method: ShippingMethod) => (\n              <span className={styles.ValueText}>\n                <span className={styles.ValuePrimary}>{method.name}</span>\n                <span className={styles.ValueSecondary}>\n                  {method.duration} ({method.price})\n                </span>\n              </span>\n            )}\n          </Select.Value>\n          <Select.Icon className={styles.SelectIcon}>\n            <ChevronUpDownIcon />\n          </Select.Icon>\n        </Select.Trigger>\n        <Select.Portal>\n          <Select.Positioner className={styles.Positioner} sideOffset={8}>\n            <Select.Popup className={styles.Popup}>\n              <Select.ScrollUpArrow className={styles.ScrollArrow} />\n              <Select.List className={styles.List}>\n                {shippingMethods.map((method) => (\n                  <Select.Item key={method.id} value={method} className={styles.Item}>\n                    <Select.ItemIndicator className={styles.ItemIndicator}>\n                      <CheckIcon className={styles.ItemIndicatorIcon} />\n                    </Select.ItemIndicator>\n                    <Select.ItemText className={styles.ItemText}>\n                      <span className={styles.ItemLabel}>{method.name}</span>\n                      <span className={styles.ItemDescription}>\n                        {method.duration} ({method.price})\n                      </span>\n                    </Select.ItemText>\n                  </Select.Item>\n                ))}\n              </Select.List>\n              <Select.ScrollDownArrow className={styles.ScrollArrow} />\n            </Select.Popup>\n          </Select.Positioner>\n        </Select.Portal>\n      </Select.Root>\n    </div>\n  );\n}\n\nfunction ChevronUpDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"8\"\n      height=\"12\"\n      viewBox=\"0 0 8 12\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"1.5\"\n      {...props}\n    >\n      <path d=\"M0.5 4.5L4 1.5L7.5 4.5\" />\n      <path d=\"M0.5 7.5L4 10.5L7.5 7.5\" />\n    </svg>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\ninterface ShippingMethod {\n  id: string;\n  name: string;\n  duration: string;\n  price: string;\n}\n\nconst shippingMethods: ShippingMethod[] = [\n  {\n    id: 'standard',\n    name: 'Standard',\n    duration: 'Delivers in 4-6 business days',\n    price: '$4.99',\n  },\n  {\n    id: 'express',\n    name: 'Express',\n    duration: 'Delivers in 2-3 business days',\n    price: '$9.99',\n  },\n  {\n    id: 'overnight',\n    name: 'Overnight',\n    duration: 'Delivers next business day',\n    price: '$19.99',\n  },\n];\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/select/demos/object-values/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoSelectObjectValues = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/select/demos/object-values/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Select } from '@base-ui/react/select';\n\nexport default function ObjectValueSelect() {\n  return (\n    <div className=\"flex flex-col gap-1\">\n      <Select.Root defaultValue={shippingMethods[0]} itemToStringValue={(item) => item.id}>\n        <Select.Label className=\"cursor-default text-sm leading-5 font-bold text-gray-900\">\n          Shipping method\n        </Select.Label>\n        <Select.Trigger className=\"flex min-h-10 min-w-[16rem] items-start justify-between gap-3 rounded-md border border-gray-200 pr-3 pl-3.5 py-2 text-base bg-[canvas] text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 data-[popup-open]:bg-gray-100\">\n          <Select.Value>\n            {(method: ShippingMethod) => (\n              <span className=\"flex flex-col items-start gap-0.5\">\n                <span className=\"text-base leading-6\">{method.name}</span>\n                <span className=\"text-xs leading-4 text-gray-600\">\n                  {method.duration} ({method.price})\n                </span>\n              </span>\n            )}\n          </Select.Value>\n          <Select.Icon className=\"flex items-center self-center\">\n            <ChevronUpDownIcon />\n          </Select.Icon>\n        </Select.Trigger>\n        <Select.Portal>\n          <Select.Positioner className=\"outline-hidden select-none z-10\" sideOffset={8}>\n            <Select.Popup className=\"group min-w-[var(--anchor-width)] origin-[var(--transform-origin)] bg-clip-padding rounded-md bg-[canvas] text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 transition-[transform,scale,opacity] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[side=none]:min-w-[calc(var(--anchor-width)+1rem)] data-[side=none]:data-[ending-style]:transition-none data-[starting-style]:scale-90 data-[starting-style]:opacity-0 data-[side=none]:data-[starting-style]:scale-100 data-[side=none]:data-[starting-style]:opacity-100 data-[side=none]:data-[starting-style] :transition-none dark:shadow-none dark:outline-gray-300\">\n              <Select.ScrollUpArrow className=\"top-0 z-[1] flex h-4 w-full cursor-default items-center justify-center rounded-md bg-[canvas] text-center text-xs before:absolute data-[side=none]:before:top-[-100%] before:left-0 before:h-full before:w-full before:content-['']\" />\n              <Select.List className=\"relative py-1 scroll-py-6 overflow-y-auto max-h-[var(--available-height)]\">\n                {shippingMethods.map((method) => (\n                  <Select.Item\n                    key={method.id}\n                    value={method}\n                    className=\"grid cursor-default grid-cols-[0.75rem_1fr] items-start gap-2 py-2.5 pr-4 pl-2.5 text-sm leading-4 outline-hidden select-none group-data-[side=none]:pr-12 group-data-[side=none]:text-base group-data-[side=none]:leading-5 pointer-coarse:py-2.5 pointer-coarse:text-[0.925rem] [@media(hover:hover)]:[&[data-highlighted]]:relative [@media(hover:hover)]:[&[data-highlighted]]:z-0 [@media(hover:hover)]:[&[data-highlighted]]:text-gray-50 [@media(hover:hover)]:[&[data-highlighted]]:before:content-[''] [@media(hover:hover)]:[&[data-highlighted]]:before:absolute [@media(hover:hover)]:[&[data-highlighted]]:before:inset-y-0 [@media(hover:hover)]:[&[data-highlighted]]:before:inset-x-1 [@media(hover:hover)]:[&[data-highlighted]]:before:rounded-xs [@media(hover:hover)]:[&[data-highlighted]]:before:bg-gray-900 [@media(hover:hover)]:[&[data-highlighted]]:before:z-[-1]\"\n                  >\n                    <Select.ItemIndicator className=\"col-start-1 flex items-center self-start relative top-[0.4em]\">\n                      <CheckIcon className=\"size-3\" />\n                    </Select.ItemIndicator>\n                    <Select.ItemText className=\"col-start-2 flex flex-col items-start gap-0.5\">\n                      <span className=\"text-base leading-6\">{method.name}</span>\n                      <span className=\"text-xs leading-4 opacity-80\">\n                        {method.duration} ({method.price})\n                      </span>\n                    </Select.ItemText>\n                  </Select.Item>\n                ))}\n              </Select.List>\n              <Select.ScrollDownArrow className=\"bottom-0 z-[1] flex h-4 w-full cursor-default items-center justify-center rounded-md bg-[canvas] text-center text-xs before:absolute before:left-0 before:h-full before:w-full before:content-[''] bottom-0 data-[side=none]:before:bottom-[-100%]\" />\n            </Select.Popup>\n          </Select.Positioner>\n        </Select.Portal>\n      </Select.Root>\n    </div>\n  );\n}\n\nfunction ChevronUpDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"8\"\n      height=\"12\"\n      viewBox=\"0 0 8 12\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"1.5\"\n      {...props}\n    >\n      <path d=\"M0.5 4.5L4 1.5L7.5 4.5\" />\n      <path d=\"M0.5 7.5L4 10.5L7.5 7.5\" />\n    </svg>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\ninterface ShippingMethod {\n  id: string;\n  name: string;\n  duration: string;\n  price: string;\n}\n\nconst shippingMethods: ShippingMethod[] = [\n  {\n    id: 'standard',\n    name: 'Standard',\n    duration: 'Delivers in 4-6 business days',\n    price: '$4.99',\n  },\n  {\n    id: 'express',\n    name: 'Express',\n    duration: 'Delivers in 2-3 business days',\n    price: '$9.99',\n  },\n  {\n    id: 'overnight',\n    name: 'Overnight',\n    duration: 'Delivers next business day',\n    price: '$19.99',\n  },\n];\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/select/page.mdx",
    "content": "# Select\n\n<Subtitle>A common form component for choosing a predefined value in a dropdown menu.</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"A high-quality, unstyled React select component for choosing a predefined value in a dropdown menu.\"\n/>\n\nimport { DemoSelectHero } from './demos/hero';\n\n<DemoSelectHero />\n\n## Usage guidelines\n\n- **Prefer Combobox for large lists**: Select is not filterable, aside from basic keyboard typeahead functionality to find items by focusing and highlighting them. Prefer [Combobox](/react/components/combobox) instead of Select when the number of items is sufficiently large to warrant filtering.\n- **Special positioning behavior**: The select popup by default overlaps its trigger so the selected item's text is aligned with the trigger's value text. This behavior [can be disabled or customized](/react/components/select#positioning).\n- **Form controls must have an accessible name**: Prefer `<Select.Label>`, or provide an `aria-label` on `<Select.Trigger>` when no visible label is rendered. See [Labeling a select](#labeling-a-select) and the [forms guide](/react/handbook/forms).\n\n## Anatomy\n\nImport the component and assemble its parts:\n\n```jsx title=\"Anatomy\"\nimport { Select } from '@base-ui/react/select';\n\n<Select.Root>\n  <Select.Label />\n  <Select.Trigger>\n    <Select.Value />\n    <Select.Icon />\n  </Select.Trigger>\n\n  <Select.Portal>\n    <Select.Backdrop />\n    <Select.Positioner>\n      <Select.ScrollUpArrow />\n      <Select.Popup>\n        <Select.Arrow />\n        <Select.List>\n          <Select.Item>\n            <Select.ItemText />\n            <Select.ItemIndicator />\n          </Select.Item>\n          <Select.Separator />\n          <Select.Group>\n            <Select.GroupLabel />\n          </Select.Group>\n        </Select.List>\n      </Select.Popup>\n      <Select.ScrollDownArrow />\n    </Select.Positioner>\n  </Select.Portal>\n</Select.Root>;\n```\n\n## Positioning\n\n`<Select.Positioner>` has a special prop called `alignItemWithTrigger` which causes the positioning to act differently by default from other `Positioner` components.\nThe prop makes the select popup overlap the trigger so the selected item's text is aligned with the trigger's value text.\n\nFor styling, `data-side` is `\"none\"` on the `.Popup` and `.Positioner` parts when the mode is active.\n\nTo prevent the select popup from overlapping its trigger, set the `alignItemWithTrigger` prop to `false`.\nWhen set to `true` (its default) there are a few important points to note about its behavior:\n\n- **Interaction type dependent**: For UX reasons, the `alignItemWithTrigger` positioning mode is disabled if touch was the pointer type used to open the popup.\n- **Viewport space dependent**: There must be enough space in the viewport to align the selected item's text with the trigger's value text without causing the popup to be too vertically small - otherwise, it falls back to the default positioning mode.\n  This can be customized by setting `min-height` on the `<Select.Positioner>` element; a smaller value will fallback less often.\n  Additionally, the trigger must be at least 20px from the edges of the top and bottom of the viewport, or it will also fall back.\n- **Other positioning props are ignored**: Props like `side` or `align` have no effect unless the prop is set to `false` or when in fallback mode.\n\n## Examples\n\n### Typed wrapper component\n\nThe following example shows a typed wrapper around the Select component with correct type inference and type safety:\n\n```tsx title=\"Specifying generic type parameters\"\nimport * as React from 'react';\nimport { Select } from '@base-ui/react/select';\n\nexport function MySelect<Value, Multiple extends boolean | undefined = false>(\n  props: Select.Root.Props<Value, Multiple>,\n): React.JSX.Element {\n  return <Select.Root {...props}>{/* ... */}</Select.Root>;\n}\n```\n\n### Formatting the value\n\nBy default, the `<Select.Value>` component renders the raw `value`.\n\nPassing the `items` prop to `<Select.Root>` instead renders the matching label for the rendered value:\n\n```jsx title=\"items prop\" \"items\"1,3\nconst items = [\n  { value: null, label: 'Select theme' },\n  { value: 'system', label: 'System default' },\n  { value: 'light', label: 'Light' },\n  { value: 'dark', label: 'Dark' },\n];\n\n<Select.Root items={items}>\n  <Select.Value />\n</Select.Root>;\n```\n\nA function can also be passed as the `children` prop of `<Select.Value>` to render a formatted value:\n\n```jsx title=\"Lookup map\" {8-12}\nconst items = {\n  monospace: 'Monospace',\n  serif: 'Serif',\n  'san-serif': 'Sans-serif',\n};\n\n<Select.Value>\n  {(value: keyof typeof items) => (\n    <span style={{ fontFamily: value }}>\n      {items[value]}\n    </span>\n  )}\n</Select.Value>;\n```\n\nTo avoid lookup, [object values](#object-values) for each item can also be used.\n\n### Labeling a select\n\nUse `<Select.Label>` to provide a visible label for the select trigger:\n\n```tsx title=\"Using Select.Label to label a select\" {2}\n<Select.Root>\n  <Select.Label>Theme</Select.Label>\n  {/* ... */}\n</Select.Root>\n```\n\n`<Select.Label>` renders a `<div>`, so clicking it focuses the select trigger without opening the popup.\n\n### Placeholder values\n\nTo show a placeholder value, use the `placeholder` prop on `<Select.Value>`:\n\n```jsx title=\"Placeholder item\" {8}\nconst items = [\n  { value: 'system', label: 'System default' },\n  { value: 'light', label: 'Light' },\n  { value: 'dark', label: 'Dark' },\n];\n\n<Select.Root items={items}>\n  <Select.Value placeholder=\"Select theme\" />\n</Select.Root>;\n```\n\nWith placeholders, users cannot clear selected values using the select itself. If the select value should be clearable from the popup (instead of an external \"reset\" button), use a `null` item rendered in the list itself:\n\n```jsx title=\"Clearable item\" {2}\nconst items = [\n  { value: null, label: 'Select theme' },\n  { value: 'system', label: 'System default' },\n  { value: 'light', label: 'Light' },\n  { value: 'dark', label: 'Dark' },\n];\n\n<Select.Root items={items}>\n  <Select.Value />\n</Select.Root>;\n```\n\n### Multiple selection\n\nAdd the `multiple` prop to the `<Select.Root>` component to allow multiple selections.\n\nimport { DemoSelectMultiple } from './demos/multiple';\n\n<DemoSelectMultiple compact />\n\n### Object values\n\nSelect items can use objects as values instead of primitives.\nThis lets you access the full object in custom render functions, and can avoid needing to specify `items` for lookup.\n\nimport { DemoSelectObjectValues } from './demos/object-values';\n\n<DemoSelectObjectValues compact />\n\n### Grouped\n\nOrganize related options with `<Select.Group>` and `<Select.GroupLabel>` to add section headings inside the popup.\n\nGroups are represented by an array of objects with an `items` property, which itself is an array of individual items for each group. An extra property, such as `value`, can be provided for the heading text when rendering the group label.\n\n```tsx title=\"Example\" {3,9,13}\ninterface ProduceGroupItem {\n  value: string;\n  items: string[];\n}\n\nconst groups: ProduceGroupItem[] = [\n  {\n    value: 'Fruits',\n    items: ['Apple', 'Banana', 'Orange'],\n  },\n  {\n    value: 'Vegetables',\n    items: ['Carrot', 'Lettuce', 'Spinach'],\n  },\n];\n```\n\nimport { DemoSelectGrouped } from './demos/grouped';\n\n<DemoSelectGrouped compact />\n\n## API reference\n\n<Reference\n  component=\"Select\"\n  parts=\"Root, Label, Trigger, Value, Icon, Backdrop, Portal, Positioner, Popup, List, Arrow, Item, ItemText, ItemIndicator, Group, GroupLabel, ScrollUpArrow, ScrollDownArrow, Separator\"\n/>\n\nexport const metadata = {\n  keywords: [\n    'React Select Component',\n    'Dropdown Select',\n    'Select Box',\n    'Select Menu',\n    'Picker',\n    'Listbox',\n    'Choice Selector',\n    'Option List',\n    'Dropdown Menu',\n    'Combo Box',\n    'Multi Select React',\n    'Multiselect',\n    'Object Value Select',\n    'Accessible Select',\n    'Align Item with Trigger',\n    'Headless React Components',\n    'Base UI',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/separator/demos/hero/css-modules/index.module.css",
    "content": ".Container {\n  display: flex;\n  gap: 1rem;\n  text-wrap: nowrap;\n}\n\n.Separator {\n  width: 1px;\n  background-color: var(--color-gray-300);\n}\n\n.Link {\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  color: var(--color-gray-900);\n  text-decoration-color: var(--color-gray-400);\n  text-decoration-thickness: 1px;\n  text-decoration-line: none;\n  text-underline-offset: 2px;\n\n  @media (hover: hover) {\n    &:hover {\n      text-decoration-line: underline;\n    }\n  }\n\n  &:focus-visible {\n    border-radius: 0.125rem;\n    outline: 2px solid var(--color-blue);\n    text-decoration-line: none;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/separator/demos/hero/css-modules/index.tsx",
    "content": "import { Separator } from '@base-ui/react/separator';\nimport styles from './index.module.css';\n\nexport default function ExampleSeparator() {\n  return (\n    <div className={styles.Container}>\n      <a href=\"#\" className={styles.Link}>\n        Home\n      </a>\n      <a href=\"#\" className={styles.Link}>\n        Pricing\n      </a>\n      <a href=\"#\" className={styles.Link}>\n        Blog\n      </a>\n      <a href=\"#\" className={styles.Link}>\n        Support\n      </a>\n\n      <Separator orientation=\"vertical\" className={styles.Separator} />\n\n      <a href=\"#\" className={styles.Link}>\n        Log in\n      </a>\n      <a href=\"#\" className={styles.Link}>\n        Sign up\n      </a>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/separator/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoSeparatorHero = createDemoWithVariants(import.meta.url, { CssModules, Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/separator/demos/hero/tailwind/index.tsx",
    "content": "import { Separator } from '@base-ui/react/separator';\n\nexport default function ExampleSeparator() {\n  return (\n    <div className=\"flex gap-4 text-nowrap\">\n      <a\n        href=\"#\"\n        className=\"text-sm text-gray-900 decoration-gray-400 decoration-1 underline-offset-2 outline-hidden hover:underline focus-visible:rounded-xs focus-visible:no-underline focus-visible:outline-2 focus-visible:outline-blue-800\"\n      >\n        Home\n      </a>\n      <a\n        href=\"#\"\n        className=\"text-sm text-gray-900 decoration-gray-400 decoration-1 underline-offset-2 outline-hidden hover:underline focus-visible:rounded-xs focus-visible:no-underline focus-visible:outline-2 focus-visible:outline-blue-800\"\n      >\n        Pricing\n      </a>\n      <a\n        href=\"#\"\n        className=\"text-sm text-gray-900 decoration-gray-400 decoration-1 underline-offset-2 outline-hidden hover:underline focus-visible:rounded-xs focus-visible:no-underline focus-visible:outline-2 focus-visible:outline-blue-800\"\n      >\n        Blog\n      </a>\n      <a\n        href=\"#\"\n        className=\"text-sm text-gray-900 decoration-gray-400 decoration-1 underline-offset-2 outline-hidden hover:underline focus-visible:rounded-xs focus-visible:no-underline focus-visible:outline-2 focus-visible:outline-blue-800\"\n      >\n        Support\n      </a>\n\n      <Separator orientation=\"vertical\" className=\"w-px bg-gray-300\" />\n\n      <a\n        href=\"#\"\n        className=\"text-sm text-gray-900 decoration-gray-400 decoration-1 underline-offset-2 outline-hidden hover:underline focus-visible:rounded-xs focus-visible:no-underline focus-visible:outline-2 focus-visible:outline-blue-800\"\n      >\n        Log in\n      </a>\n      <a\n        href=\"#\"\n        className=\"text-sm text-gray-900 decoration-gray-400 decoration-1 underline-offset-2 outline-hidden hover:underline focus-visible:rounded-xs focus-visible:no-underline focus-visible:outline-2 focus-visible:outline-blue-800\"\n      >\n        Sign up\n      </a>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/separator/page.mdx",
    "content": "# Separator\n\n<Subtitle>A separator element accessible to screen readers.</Subtitle>\n\n<Meta\n  name=\"description\"\n  content=\"A high-quality, unstyled React separator component that is accessible to screen readers.\"\n/>\n\nimport { DemoSeparatorHero } from './demos/hero';\n\n<DemoSeparatorHero />\n\n## Anatomy\n\nImport the component and use it as a single part:\n\n```jsx title=\"Anatomy\"\nimport { Separator } from '@base-ui/react/separator';\n\n<Separator />;\n```\n\n## API reference\n\n<Reference component=\"Separator\" />\n\nexport const metadata = {\n  keywords: [\n    'React Separator',\n    'Divider Component',\n    'Divider',\n    'Horizontal Rule',\n    'Horizontal Divider',\n    'Vertical Divider',\n    'Visual Separator',\n    'Section Divider',\n    'HR',\n    'Accessible Separator',\n    'Screen Reader Separator',\n    'Headless React Components',\n    'Base UI',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/slider/demos/edge-alignment/css-modules/index.module.css",
    "content": ".Control {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  width: 14rem;\n  padding-block: 0.75rem;\n  touch-action: none;\n  user-select: none;\n}\n\n.Track {\n  width: 100%;\n  height: 0.25rem;\n  background-color: var(--color-gray-200);\n  box-shadow: inset 0 0 0 1px var(--color-gray-200);\n  border-radius: 0.25rem;\n  user-select: none;\n}\n\n.Indicator {\n  border-radius: 0.25rem;\n  background-color: var(--color-gray-700);\n  user-select: none;\n}\n\n.Thumb {\n  width: 1rem;\n  height: 1rem;\n  border-radius: 100%;\n  background-color: white;\n  outline: 1px solid var(--color-gray-300);\n  user-select: none;\n\n  &:has(:focus-visible) {\n    outline: 2px solid var(--color-blue);\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/slider/demos/edge-alignment/css-modules/index.tsx",
    "content": "import { Slider } from '@base-ui/react/slider';\nimport styles from './index.module.css';\n\nexport default function EdgeAlignedThumb() {\n  return (\n    <Slider.Root thumbAlignment=\"edge\" defaultValue={25}>\n      <Slider.Control className={styles.Control}>\n        <Slider.Track className={styles.Track}>\n          <Slider.Indicator className={styles.Indicator} />\n          <Slider.Thumb className={styles.Thumb} />\n        </Slider.Track>\n      </Slider.Control>\n    </Slider.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/slider/demos/edge-alignment/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoSliderEdgeAlignment = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/slider/demos/edge-alignment/tailwind/index.tsx",
    "content": "import { Slider } from '@base-ui/react/slider';\n\nexport default function EdgeAlignedThumb() {\n  return (\n    <Slider.Root thumbAlignment=\"edge\" defaultValue={25}>\n      <Slider.Control className=\"flex w-56 touch-none items-center py-3 select-none\">\n        <Slider.Track className=\"h-1 w-full rounded-sm bg-gray-200 shadow-[inset_0_0_0_1px] shadow-gray-200 select-none\">\n          <Slider.Indicator className=\"rounded-sm bg-gray-700 select-none\" />\n          <Slider.Thumb className=\"size-4 rounded-full bg-white outline-1 outline-gray-300 select-none has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-blue-800\" />\n        </Slider.Track>\n      </Slider.Control>\n    </Slider.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/slider/demos/hero/css-modules/index.module.css",
    "content": ".Control {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  width: 14rem;\n  padding-block: 0.75rem;\n  touch-action: none;\n  user-select: none;\n}\n\n.Track {\n  width: 100%;\n  height: 0.25rem;\n  background-color: var(--color-gray-200);\n  box-shadow: inset 0 0 0 1px var(--color-gray-200);\n  border-radius: 0.25rem;\n  user-select: none;\n}\n\n.Indicator {\n  border-radius: 0.25rem;\n  background-color: var(--color-gray-700);\n  user-select: none;\n}\n\n.Thumb {\n  width: 1rem;\n  height: 1rem;\n  border-radius: 100%;\n  background-color: white;\n  outline: 1px solid var(--color-gray-300);\n  user-select: none;\n\n  &:has(:focus-visible) {\n    outline: 2px solid var(--color-blue);\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/slider/demos/hero/css-modules/index.tsx",
    "content": "import { Slider } from '@base-ui/react/slider';\nimport styles from './index.module.css';\n\nexport default function ExampleSlider() {\n  return (\n    <Slider.Root defaultValue={25}>\n      <Slider.Control className={styles.Control}>\n        <Slider.Track className={styles.Track}>\n          <Slider.Indicator className={styles.Indicator} />\n          <Slider.Thumb aria-label=\"Volume\" className={styles.Thumb} />\n        </Slider.Track>\n      </Slider.Control>\n    </Slider.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/slider/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoSliderHero = createDemoWithVariants(import.meta.url, { CssModules, Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/slider/demos/hero/tailwind/index.tsx",
    "content": "import { Slider } from '@base-ui/react/slider';\n\nexport default function ExampleSlider() {\n  return (\n    <Slider.Root defaultValue={25}>\n      <Slider.Control className=\"flex w-56 touch-none items-center py-3 select-none\">\n        <Slider.Track className=\"h-1 w-full rounded-sm bg-gray-200 shadow-[inset_0_0_0_1px] shadow-gray-200 select-none\">\n          <Slider.Indicator className=\"rounded-sm bg-gray-700 select-none\" />\n          <Slider.Thumb\n            aria-label=\"Volume\"\n            className=\"size-4 rounded-full bg-white outline-1 outline-gray-300 select-none has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-blue-800\"\n          />\n        </Slider.Track>\n      </Slider.Control>\n    </Slider.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/slider/demos/range-slider/css-modules/index.module.css",
    "content": ".Control {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  width: 14rem;\n  padding-block: 0.75rem;\n  touch-action: none;\n  user-select: none;\n}\n\n.Track {\n  width: 100%;\n  height: 0.25rem;\n  background-color: var(--color-gray-200);\n  box-shadow: inset 0 0 0 1px var(--color-gray-200);\n  border-radius: 0.25rem;\n  user-select: none;\n}\n\n.Indicator {\n  border-radius: 0.25rem;\n  background-color: var(--color-gray-700);\n  user-select: none;\n}\n\n.Thumb {\n  width: 1rem;\n  height: 1rem;\n  border-radius: 100%;\n  background-color: white;\n  outline: 1px solid var(--color-gray-300);\n  user-select: none;\n\n  &:has(:focus-visible) {\n    outline: 2px solid var(--color-blue);\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/slider/demos/range-slider/css-modules/index.tsx",
    "content": "import { Slider } from '@base-ui/react/slider';\nimport styles from './index.module.css';\n\nexport default function RangeSlider() {\n  return (\n    <Slider.Root defaultValue={[25, 45]}>\n      <Slider.Control className={styles.Control}>\n        <Slider.Track className={styles.Track}>\n          <Slider.Indicator className={styles.Indicator} />\n          <Slider.Thumb index={0} aria-label=\"Minimum value\" className={styles.Thumb} />\n          <Slider.Thumb index={1} aria-label=\"Maximum value\" className={styles.Thumb} />\n        </Slider.Track>\n      </Slider.Control>\n    </Slider.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/slider/demos/range-slider/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoSliderRangeSlider = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/slider/demos/range-slider/tailwind/index.tsx",
    "content": "import { Slider } from '@base-ui/react/slider';\n\nexport default function RangeSlider() {\n  return (\n    <Slider.Root defaultValue={[25, 45]}>\n      <Slider.Control className=\"flex w-56 touch-none items-center py-3 select-none\">\n        <Slider.Track className=\"h-1 w-full rounded-sm bg-gray-200 shadow-[inset_0_0_0_1px] shadow-gray-200 select-none\">\n          <Slider.Indicator className=\"rounded-sm bg-gray-700 select-none\" />\n          <Slider.Thumb\n            index={0}\n            aria-label=\"Minimum value\"\n            className=\"size-4 rounded-full bg-white outline-1 outline-gray-300 select-none has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-blue-800\"\n          />\n          <Slider.Thumb\n            index={1}\n            aria-label=\"Maximum value\"\n            className=\"size-4 rounded-full bg-white outline-1 outline-gray-300 select-none has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-blue-800\"\n          />\n        </Slider.Track>\n      </Slider.Control>\n    </Slider.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/slider/demos/vertical/css-modules/index.module.css",
    "content": ".Control {\n  box-sizing: border-box;\n  display: flex;\n  touch-action: none;\n  user-select: none;\n\n  &[data-orientation='vertical'] {\n    height: 8rem;\n    padding-inline: 0.75rem;\n  }\n}\n\n.Track {\n  background-color: var(--color-gray-200);\n  box-shadow: inset 0 0 0 1px var(--color-gray-200);\n  border-radius: 0.25rem;\n  user-select: none;\n\n  &[data-orientation='vertical'] {\n    height: 100%;\n    width: 0.25rem;\n  }\n}\n\n.Indicator {\n  border-radius: 0.25rem;\n  background-color: var(--color-gray-700);\n  user-select: none;\n}\n\n.Thumb {\n  width: 1rem;\n  height: 1rem;\n  border-radius: 100%;\n  background-color: white;\n  outline: 1px solid var(--color-gray-300);\n  user-select: none;\n\n  &:has(:focus-visible) {\n    outline: 2px solid var(--color-blue);\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/slider/demos/vertical/css-modules/index.tsx",
    "content": "import { Slider } from '@base-ui/react/slider';\nimport styles from './index.module.css';\n\nexport default function VerticalSlider() {\n  return (\n    <Slider.Root orientation=\"vertical\" defaultValue={35}>\n      <Slider.Control className={styles.Control}>\n        <Slider.Track className={styles.Track}>\n          <Slider.Indicator className={styles.Indicator} />\n          <Slider.Thumb aria-label=\"Volume\" className={styles.Thumb} />\n        </Slider.Track>\n      </Slider.Control>\n    </Slider.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/slider/demos/vertical/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoSliderVertical = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/slider/demos/vertical/tailwind/index.tsx",
    "content": "import { Slider } from '@base-ui/react/slider';\n\nexport default function VerticalSlider() {\n  return (\n    <Slider.Root orientation=\"vertical\" defaultValue={35}>\n      <Slider.Control className=\"flex touch-none select-none data-[orientation=vertical]:h-32 data-[orientation=vertical]:px-3\">\n        <Slider.Track className=\"rounded-sm bg-gray-200 shadow-[inset_0_0_0_1px] shadow-gray-200 select-none data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1\">\n          <Slider.Indicator className=\"rounded-sm bg-gray-700 select-none\" />\n          <Slider.Thumb\n            aria-label=\"Volume\"\n            className=\"size-4 rounded-full bg-white outline-1 outline-gray-300 select-none has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-blue-800\"\n          />\n        </Slider.Track>\n      </Slider.Control>\n    </Slider.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/slider/page.mdx",
    "content": "# Slider\n\n<Subtitle>An easily stylable range input.</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"A high-quality, unstyled React slider component that works like a range input and is easy to style.\"\n/>\n\nimport { DemoSliderHero } from './demos/hero';\n\n<DemoSliderHero />\n\n## Usage guidelines\n\n- **Form controls must have an accessible name**: Prefer `<Slider.Label>`, or provide an `aria-label` on each `<Slider.Thumb>` when no visible label is rendered. See [Labeling a slider](#labeling-a-slider) and the [forms guide](/react/handbook/forms).\n\n## Anatomy\n\nImport the component and assemble its parts:\n\n```jsx title=\"Anatomy\"\nimport { Slider } from '@base-ui/react/slider';\n\n<Slider.Root>\n  <Slider.Label />\n  <Slider.Value />\n  <Slider.Control>\n    <Slider.Track>\n      <Slider.Indicator />\n      <Slider.Thumb />\n    </Slider.Track>\n  </Slider.Control>\n</Slider.Root>;\n```\n\n## Examples\n\n### Range slider\n\nTo create a range slider:\n\n1. Pass an array of values and place a `<Slider.Thumb>` for each value in the array\n2. Additionally for server-side rendering, specify a numeric `index` for each thumb that corresponds to the index of its value in the value array\n\nThumbs can be configured to behave differently when they collide during pointer interactions using the `thumbCollisionBehavior` prop on `<Slider.Root>`.\n\nimport { DemoSliderRangeSlider } from './demos/range-slider';\n\n<DemoSliderRangeSlider compact />\n\n### Thumb alignment\n\nSet `thumbAlignment=\"edge\"` to inset the thumb such that its edge aligns with the edge of the control when the value is at `min` or `max`, without overflowing the control like the default `\"center\"` alignment.\n\nA client-only alternative `thumbAlignment=\"edge-client-only\"` can be used to reduce bundle size but only renders after React hydration.\n\nimport { DemoSliderEdgeAlignment } from './demos/edge-alignment';\n\n<DemoSliderEdgeAlignment compact />\n\n### Labeling a slider\n\nA single-thumb slider without a visible label (such as a volume control) can be labeled using `aria-label` on `<Slider.Thumb>`:\n\n```tsx title=\"Slider with invisible label\" {5}\n<Slider.Root>\n  <Slider.Control>\n    <Slider.Track>\n      <Slider.Indicator />\n      <Slider.Thumb aria-label=\"Volume\" />\n    </Slider.Track>\n  </Slider.Control>\n</Slider.Root>\n```\n\nA visible label can be created using `<Slider.Label>`:\n\n```tsx title=\"Slider with visible label\" {2}\n<Slider.Root>\n  <Slider.Label>Volume</Slider.Label>\n  <Slider.Control>\n    <Slider.Track>\n      <Slider.Indicator />\n      <Slider.Thumb />\n    </Slider.Track>\n  </Slider.Control>\n</Slider.Root>\n```\n\nFor a multi-thumb range slider with a visible label, add `aria-label` on each `<Slider.Thumb>` to distinguish them:\n\n```tsx title=\"Labeling multi-thumb range sliders\" {6-7}\n<Slider.Root defaultValue={[25, 75]}>\n  <Slider.Label>Price range</Slider.Label>\n  <Slider.Control>\n    <Slider.Track>\n      <Slider.Indicator />\n      <Slider.Thumb index={0} aria-label=\"Minimum price\" />\n      <Slider.Thumb index={1} aria-label=\"Maximum price\" />\n    </Slider.Track>\n  </Slider.Control>\n</Slider.Root>\n```\n\n### Vertical\n\nSet `orientation=\"vertical\"` on `<Slider.Root>` to build a vertical slider.\n\nimport { DemoSliderVertical } from './demos/vertical';\n\n<DemoSliderVertical compact />\n\n### Form integration\n\nTo use a slider in a form, pass the slider `name` to `<Slider.Root>`:\n\n```tsx title=\"Using Slider in a form\" {2}\n<Form>\n  <Slider.Root name=\"volume\">\n    <Slider.Label>Volume</Slider.Label>\n    <Slider.Control>\n      <Slider.Track>\n        <Slider.Indicator />\n        <Slider.Thumb />\n      </Slider.Track>\n    </Slider.Control>\n  </Slider.Root>\n</Form>\n```\n\nFor grouped multi-thumb range sliders in forms, [Fieldset](/react/components/fieldset) can provide the shared visible label while each thumb keeps its own `aria-label`:\n\n```tsx title=\"Using Fieldset with a multi-thumb slider\" {2,3,7-8}\n<Field.Root>\n  <Fieldset.Root render={<Slider.Root />}>\n    <Fieldset.Legend>Price range</Fieldset.Legend>\n    <Slider.Control>\n      <Slider.Track>\n        <Slider.Indicator />\n        <Slider.Thumb index={0} aria-label=\"Minimum price\" />\n        <Slider.Thumb index={1} aria-label=\"Maximum price\" />\n      </Slider.Track>\n    </Slider.Control>\n  </Fieldset.Root>\n</Field.Root>\n```\n\n## API reference\n\n<Reference component=\"Slider\" parts=\"Root, Label, Value, Control, Track, Indicator, Thumb\" />\n\nexport const metadata = {\n  keywords: [\n    'React Slider',\n    'Range Slider Component',\n    'Range Input',\n    'Range Control',\n    'Track Control',\n    'Value Selector',\n    'Dual Slider',\n    'Double Slider',\n    'Two-Handle Slider',\n    'Multi Thumb Slider',\n    'Thumb Alignment Edge',\n    'Accessible Slider',\n    'Headless React Components',\n    'Base UI',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/switch/demos/hero/css-modules/index.module.css",
    "content": ".Label {\n  display: flex;\n  align-items: center;\n  gap: 0.5rem;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n}\n\n.Switch {\n  box-sizing: border-box;\n  position: relative;\n  display: flex;\n  appearance: none;\n  border: 0;\n  margin: 0;\n  padding: 1px;\n  width: 2.5rem;\n  height: 1.5rem;\n  border-radius: 1.5rem;\n  outline: 1px solid;\n  outline-offset: -1px;\n  background-color: transparent;\n  background-image: linear-gradient(to right, var(--color-gray-700) 35%, var(--color-gray-200) 65%);\n  background-size: 6.5rem 100%;\n  background-position-x: 100%;\n  background-repeat: no-repeat;\n  transition-property: background-position, box-shadow;\n  transition-timing-function: cubic-bezier(0.26, 0.75, 0.38, 0.45);\n  transition-duration: 125ms;\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &[data-checked] {\n    background-position-x: 0%;\n  }\n\n  &[data-checked]:active {\n    background-color: var(--color-gray-500);\n  }\n\n  @media (prefers-color-scheme: light) {\n    box-shadow: var(--color-gray-200) 0 1.5px 2px inset;\n    outline-color: var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    box-shadow: rgb(0 0 0 / 75%) 0 1.5px 2px inset;\n    outline-color: rgb(255 255 255 / 15%);\n    background-image: linear-gradient(\n      to right,\n      var(--color-gray-500) 35%,\n      var(--color-gray-200) 65%\n    );\n\n    &[data-checked] {\n      box-shadow: none;\n    }\n  }\n\n  &:focus-visible {\n    &::before {\n      content: '';\n      inset: 0;\n      position: absolute;\n      border-radius: inherit;\n      outline: 2px solid var(--color-blue);\n      outline-offset: 2px;\n    }\n  }\n}\n\n.Thumb {\n  aspect-ratio: 1 / 1;\n  height: 100%;\n  border-radius: 100%;\n  background-color: white;\n  transition: translate 150ms ease;\n\n  &[data-checked] {\n    translate: 1rem 0;\n  }\n\n  @media (prefers-color-scheme: light) {\n    box-shadow:\n      0 0 1px 1px var(--color-gray-100),\n      0 1px 1px var(--color-gray-100),\n      1px 2px 4px -1px var(--color-gray-100);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    box-shadow:\n      0 0 1px 1px rgb(0 0 0 / 25%),\n      0 1px 1px rgb(0 0 0 / 25%),\n      1px 2px 4px -1px rgb(0 0 0 / 25%);\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/switch/demos/hero/css-modules/index.tsx",
    "content": "import { Switch } from '@base-ui/react/switch';\nimport styles from './index.module.css';\n\nexport default function ExampleSwitch() {\n  return (\n    <label className={styles.Label}>\n      <Switch.Root defaultChecked className={styles.Switch}>\n        <Switch.Thumb className={styles.Thumb} />\n      </Switch.Root>\n      Notifications\n    </label>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/switch/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoSwitchHero = createDemoWithVariants(import.meta.url, { CssModules, Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/switch/demos/hero/tailwind/index.tsx",
    "content": "import { Switch } from '@base-ui/react/switch';\n\nexport default function ExampleSwitch() {\n  return (\n    <label className=\"flex items-center gap-2 text-base text-gray-900\">\n      <Switch.Root\n        defaultChecked\n        className=\"relative flex h-6 w-10 rounded-full bg-gradient-to-r from-gray-700 from-35% to-gray-200 to-65% bg-[length:6.5rem_100%] bg-[100%_0%] bg-no-repeat p-px shadow-[inset_0_1.5px_2px] shadow-gray-200 outline-1 -outline-offset-1 outline-gray-200 transition-[background-position,box-shadow] duration-[125ms] ease-[cubic-bezier(0.26,0.75,0.38,0.45)] before:absolute before:rounded-full before:outline-offset-2 before:outline-blue-800 focus-visible:before:inset-0 focus-visible:before:outline focus-visible:before:outline-2 active:bg-gray-100 data-[checked]:bg-[0%_0%] data-[checked]:active:bg-gray-500 dark:from-gray-500 dark:shadow-black/75 dark:outline-white/15 dark:data-[checked]:shadow-none\"\n      >\n        <Switch.Thumb className=\"aspect-square h-full rounded-full bg-white shadow-[0_0_1px_1px,0_1px_1px,1px_2px_4px_-1px] shadow-gray-100 transition-transform duration-150 data-[checked]:translate-x-4 dark:shadow-black/25\" />\n      </Switch.Root>\n      Notifications\n    </label>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/switch/page.mdx",
    "content": "# Switch\n\n<Subtitle>A control that indicates whether a setting is on or off.</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"A high-quality, unstyled React switch component that indicates whether a setting is on or off.\"\n/>\n\nimport { DemoSwitchHero } from './demos/hero';\n\n<DemoSwitchHero />\n\n## Usage guidelines\n\n- **Form controls must have an accessible name**: It can be created using a `<label>` element or the `Field` component. See [Labeling a switch](#labeling-a-switch) and the [forms guide](/react/handbook/forms).\n\n## Anatomy\n\nImport the component and assemble its parts:\n\n```jsx title=\"Anatomy\"\nimport { Switch } from '@base-ui/react/switch';\n\n<Switch.Root>\n  <Switch.Thumb />\n</Switch.Root>;\n```\n\n## Examples\n\n### Labeling a switch\n\nAn enclosing `<label>` is the simplest labeling pattern:\n\n```tsx title=\"Wrapping a label around a switch\" {1,4}\n<label>\n  <Switch.Root />\n  Notifications\n</label>\n```\n\n### Rendering as a native button\n\nBy default, `<Switch.Root>` renders a `<span>` element to support enclosing labels. Prefer rendering the switch as a native button when using sibling labels (`htmlFor`/`id`).\n\n```tsx title=\"Sibling label pattern with a native button\" \"nativeButton\" \"render\"\n<div>\n  <label htmlFor=\"notifications-switch\">Notifications</label>\n  <Switch.Root id=\"notifications-switch\" nativeButton render={<button />}>\n    <Switch.Thumb />\n  </Switch.Root>\n</div>\n```\n\nNative buttons with wrapping labels are supported by using the `render` callback to avoid invalid HTML, so the hidden input is placed outside the label:\n\n```tsx title=\"Render callback\" {3-8}\n<Switch.Root\n  nativeButton\n  render={(buttonProps) => (\n    <label>\n      <button {...buttonProps} />\n      Notifications\n    </label>\n  )}\n/>\n```\n\n### Form integration\n\nUse [Field](/react/components/field) to handle label associations and form integration:\n\n```tsx title=\"Using Switch in a form\" {2}\n<Form>\n  <Field.Root name=\"notifications\">\n    <Field.Label>\n      <Switch.Root />\n      Notifications\n    </Field.Label>\n  </Field.Root>\n</Form>\n```\n\n## API reference\n\n<Reference component=\"Switch\" parts=\"Root, Thumb\" />\n\nexport const metadata = {\n  keywords: [\n    'React Switch',\n    'Toggle Switch Component',\n    'Toggle Switch',\n    'Toggle Button',\n    'On/Off Control',\n    'On Off Switch',\n    'Binary Switch',\n    'Checkbox Alternative',\n    'Form Toggle Control',\n    'Accessible Switch',\n    'Headless React Components',\n    'Base UI',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/tabs/demos/hero/css-modules/index.module.css",
    "content": ".Tabs {\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n}\n\n.List {\n  display: flex;\n  position: relative;\n  z-index: 0;\n  padding-inline: 0.25rem;\n  gap: 0.25rem;\n  box-shadow: inset 0 -1px var(--color-gray-200);\n}\n\n.Tab {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  border: 0;\n  margin: 0;\n  outline: 0;\n  background: none;\n  appearance: none;\n  color: var(--color-gray-600);\n  font-family: inherit;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 400;\n  user-select: none;\n  white-space: nowrap;\n  word-break: keep-all;\n  padding-inline: 0.5rem;\n  padding-block: 0;\n  height: 2rem;\n\n  &[data-active] {\n    color: var(--color-gray-900);\n  }\n\n  @media (hover: hover) {\n    &:hover {\n      color: var(--color-gray-900);\n    }\n  }\n\n  &:focus-visible {\n    position: relative;\n\n    &::before {\n      content: '';\n      position: absolute;\n      inset: 0.25rem 0;\n      border-radius: 0.25rem;\n      outline: 2px solid var(--color-blue);\n      outline-offset: -1px;\n    }\n  }\n}\n\n.Indicator {\n  position: absolute;\n  z-index: -1;\n  left: 0;\n  top: 50%;\n  translate: var(--active-tab-left) -50%;\n  width: var(--active-tab-width);\n  height: 1.5rem;\n  border-radius: 0.25rem;\n  background-color: var(--color-gray-100);\n  transition-property: translate, width;\n  transition-duration: 200ms;\n  transition-timing-function: ease-in-out;\n}\n\n.Panel {\n  position: relative;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 8rem;\n  outline: 0;\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n    border-radius: 0.375rem;\n  }\n\n  &[hidden] {\n    display: none;\n  }\n}\n\n.Icon {\n  width: 2.5rem;\n  height: 2.5rem;\n  color: var(--color-gray-300);\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/tabs/demos/hero/css-modules/index.tsx",
    "content": "import * as React from 'react';\nimport { Tabs } from '@base-ui/react/tabs';\nimport styles from './index.module.css';\n\nexport default function ExampleTabs() {\n  return (\n    <Tabs.Root className={styles.Tabs} defaultValue=\"overview\">\n      <Tabs.List className={styles.List}>\n        <Tabs.Tab className={styles.Tab} value=\"overview\">\n          Overview\n        </Tabs.Tab>\n        <Tabs.Tab className={styles.Tab} value=\"projects\">\n          Projects\n        </Tabs.Tab>\n        <Tabs.Tab className={styles.Tab} value=\"account\">\n          Account\n        </Tabs.Tab>\n        <Tabs.Indicator className={styles.Indicator} />\n      </Tabs.List>\n      <Tabs.Panel className={styles.Panel} value=\"overview\">\n        <OverviewIcon className={styles.Icon} />\n      </Tabs.Panel>\n      <Tabs.Panel className={styles.Panel} value=\"projects\">\n        <ProjectIcon className={styles.Icon} />\n      </Tabs.Panel>\n      <Tabs.Panel className={styles.Panel} value=\"account\">\n        <PersonIcon className={styles.Icon} />\n      </Tabs.Panel>\n    </Tabs.Root>\n  );\n}\n\nfunction OverviewIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"40\" height=\"40\" viewBox=\"0 0 30 30\" fill=\"currentcolor\" {...props}>\n      <path d=\"M 6 4 C 4.895 4 4 4.895 4 6 L 4 12 C 4 13.105 4.895 14 6 14 L 12 14 C 13.105 14 14 13.105 14 12 L 14 6 C 14 4.895 13.105 4 12 4 L 6 4 z M 18 4 C 16.895 4 16 4.895 16 6 L 16 12 C 16 13.105 16.895 14 18 14 L 24 14 C 25.105 14 26 13.105 26 12 L 26 6 C 26 4.895 25.105 4 24 4 L 18 4 z M 9 6 C 10.657 6 12 7.343 12 9 C 12 10.657 10.657 12 9 12 C 7.343 12 6 10.657 6 9 C 6 7.343 7.343 6 9 6 z M 18 6 L 24 6 L 24 12 L 18 12 L 18 6 z M 6 16 C 4.895 16 4 16.895 4 18 L 4 24 C 4 25.105 4.895 26 6 26 L 12 26 C 13.105 26 14 25.105 14 24 L 14 18 C 14 16.895 13.105 16 12 16 L 6 16 z M 18 16 C 16.895 16 16 16.895 16 18 L 16 24 C 16 25.105 16.895 26 18 26 L 24 26 C 25.105 26 26 25.105 26 24 L 26 18 C 26 16.895 25.105 16 24 16 L 18 16 z M 21 17.5 L 24.5 21 L 21 24.5 L 17.5 21 L 21 17.5 z M 9 18 L 11.886719 23 L 6.1132812 23 L 9 18 z\" />\n    </svg>\n  );\n}\n\nfunction ProjectIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"40\" height=\"40\" viewBox=\"0 0 30 30\" fill=\"currentcolor\" {...props}>\n      <path d=\"M 14.984375 1.9863281 A 1.0001 1.0001 0 0 0 14 3 L 14 4 L 5 4 L 4 4 A 1.0001 1.0001 0 1 0 3.9804688 6 C 3.9348612 9.0608831 3.6893807 11.887023 3.1523438 14.142578 C 2.5565033 16.645108 1.6039585 18.395538 0.4453125 19.167969 A 1.0001 1.0001 0 0 0 1 21 L 4 21 C 4 22.105 4.895 23 6 23 L 11.787109 23 L 10.148438 26.042969 A 1.5 1.5 0 0 0 9 27.5 A 1.5 1.5 0 0 0 10.5 29 A 1.5 1.5 0 0 0 12 27.5 A 1.5 1.5 0 0 0 11.910156 26.992188 L 14.060547 23 L 15.939453 23 L 18.089844 26.992188 A 1.5 1.5 0 0 0 18 27.5 A 1.5 1.5 0 0 0 19.5 29 A 1.5 1.5 0 0 0 21 27.5 A 1.5 1.5 0 0 0 19.851562 26.042969 L 18.212891 23 L 24 23 C 25.105 23 26 22.105 26 21 L 26 6 A 1.0001 1.0001 0 1 0 26 4 L 25 4 L 16 4 L 16 3 A 1.0001 1.0001 0 0 0 14.984375 1.9863281 z M 5.9589844 6 L 14.832031 6 A 1.0001 1.0001 0 0 0 15.158203 6 L 23.958984 6 C 23.912194 9.0500505 23.687726 11.893974 23.152344 14.142578 C 22.583328 16.532444 21.674397 18.178754 20.585938 19 L 3.1523438 19 C 3.9976592 17.786874 4.6791735 16.365049 5.0976562 14.607422 C 5.6877248 12.129135 5.9137751 9.1554725 5.9589844 6 z\" />\n    </svg>\n  );\n}\n\nfunction PersonIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"40\" height=\"40\" viewBox=\"0 0 30 30\" fill=\"currentcolor\" {...props}>\n      <path d=\"M15,3C8.373,3,3,8.373,3,15c0,6.627,5.373,12,12,12s12-5.373,12-12C27,8.373,21.627,3,15,3z M8,22.141 c1.167-3.5,4.667-2.134,5.25-4.03v-1.264c-0.262-0.141-1.013-1.109-1.092-1.865c-0.207-0.018-0.531-0.223-0.627-1.034 c-0.051-0.435,0.153-0.68,0.276-0.757c0,0-0.308-0.702-0.308-1.399C11.5,9.72,12.526,8,15,8c1.336,0,1.75,0.947,1.75,0.947 c1.194,0,1.75,1.309,1.75,2.844c0,0.765-0.308,1.399-0.308,1.399c0.124,0.077,0.328,0.322,0.277,0.757 c-0.096,0.811-0.42,1.016-0.627,1.034c-0.079,0.756-0.829,1.724-1.092,1.865v1.264c0.583,1.897,4.083,0.531,5.25,4.031 c0,0-2.618,2.859-7,2.859C10.593,25,8,22.141,8,22.141z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/tabs/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoTabsHero = createDemoWithVariants(import.meta.url, { CssModules, Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/tabs/demos/hero/tailwind/index.tsx",
    "content": "import * as React from 'react';\nimport { Tabs } from '@base-ui/react/tabs';\n\nexport default function ExampleTabs() {\n  return (\n    <Tabs.Root className=\"rounded-md border border-gray-200\" defaultValue=\"overview\">\n      <Tabs.List className=\"relative z-0 flex gap-1 px-1 shadow-[inset_0_-1px] shadow-gray-200\">\n        <Tabs.Tab\n          className=\"flex h-8 items-center justify-center border-0 px-2 text-sm font-normal break-keep whitespace-nowrap text-gray-600 outline-hidden select-none before:inset-x-0 before:inset-y-1 before:rounded-xs before:-outline-offset-1 before:outline-blue-800 hover:text-gray-900 focus-visible:relative focus-visible:before:absolute focus-visible:before:outline focus-visible:before:outline-2 data-[active]:text-gray-900\"\n          value=\"overview\"\n        >\n          Overview\n        </Tabs.Tab>\n        <Tabs.Tab\n          className=\"flex h-8 items-center justify-center border-0 px-2 text-sm font-normal break-keep whitespace-nowrap text-gray-600 outline-hidden select-none before:inset-x-0 before:inset-y-1 before:rounded-xs before:-outline-offset-1 before:outline-blue-800 hover:text-gray-900 focus-visible:relative focus-visible:before:absolute focus-visible:before:outline focus-visible:before:outline-2 data-[active]:text-gray-900\"\n          value=\"projects\"\n        >\n          Projects\n        </Tabs.Tab>\n        <Tabs.Tab\n          className=\"flex h-8 items-center justify-center border-0 px-2 text-sm font-normal break-keep whitespace-nowrap text-gray-600 outline-hidden select-none before:inset-x-0 before:inset-y-1 before:rounded-xs before:-outline-offset-1 before:outline-blue-800 hover:text-gray-900 focus-visible:relative focus-visible:before:absolute focus-visible:before:outline focus-visible:before:outline-2 data-[active]:text-gray-900\"\n          value=\"account\"\n        >\n          Account\n        </Tabs.Tab>\n        <Tabs.Indicator className=\"absolute top-1/2 left-0 z-[-1] h-6 w-[var(--active-tab-width)] translate-x-[var(--active-tab-left)] -translate-y-1/2 rounded-xs bg-gray-100 transition-all duration-200 ease-in-out\" />\n      </Tabs.List>\n      <Tabs.Panel\n        className=\"relative flex h-32 items-center justify-center -outline-offset-1 outline-blue-800 focus-visible:rounded-md focus-visible:outline-2\"\n        value=\"overview\"\n      >\n        <OverviewIcon className=\"size-10 text-gray-300\" />\n      </Tabs.Panel>\n      <Tabs.Panel\n        className=\"relative flex h-32 items-center justify-center -outline-offset-1 outline-blue-800 focus-visible:rounded-md focus-visible:outline-2\"\n        value=\"projects\"\n      >\n        <ProjectIcon className=\"size-10 text-gray-300\" />\n      </Tabs.Panel>\n      <Tabs.Panel\n        className=\"relative flex h-32 items-center justify-center -outline-offset-1 outline-blue-800 focus-visible:rounded-md focus-visible:outline-2\"\n        value=\"account\"\n      >\n        <PersonIcon className=\"size-10 text-gray-300\" />\n      </Tabs.Panel>\n    </Tabs.Root>\n  );\n}\n\nfunction OverviewIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"40\" height=\"40\" viewBox=\"0 0 30 30\" fill=\"currentcolor\" {...props}>\n      <path d=\"M 6 4 C 4.895 4 4 4.895 4 6 L 4 12 C 4 13.105 4.895 14 6 14 L 12 14 C 13.105 14 14 13.105 14 12 L 14 6 C 14 4.895 13.105 4 12 4 L 6 4 z M 18 4 C 16.895 4 16 4.895 16 6 L 16 12 C 16 13.105 16.895 14 18 14 L 24 14 C 25.105 14 26 13.105 26 12 L 26 6 C 26 4.895 25.105 4 24 4 L 18 4 z M 9 6 C 10.657 6 12 7.343 12 9 C 12 10.657 10.657 12 9 12 C 7.343 12 6 10.657 6 9 C 6 7.343 7.343 6 9 6 z M 18 6 L 24 6 L 24 12 L 18 12 L 18 6 z M 6 16 C 4.895 16 4 16.895 4 18 L 4 24 C 4 25.105 4.895 26 6 26 L 12 26 C 13.105 26 14 25.105 14 24 L 14 18 C 14 16.895 13.105 16 12 16 L 6 16 z M 18 16 C 16.895 16 16 16.895 16 18 L 16 24 C 16 25.105 16.895 26 18 26 L 24 26 C 25.105 26 26 25.105 26 24 L 26 18 C 26 16.895 25.105 16 24 16 L 18 16 z M 21 17.5 L 24.5 21 L 21 24.5 L 17.5 21 L 21 17.5 z M 9 18 L 11.886719 23 L 6.1132812 23 L 9 18 z\" />\n    </svg>\n  );\n}\n\nfunction ProjectIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"40\" height=\"40\" viewBox=\"0 0 30 30\" fill=\"currentcolor\" {...props}>\n      <path d=\"M 14.984375 1.9863281 A 1.0001 1.0001 0 0 0 14 3 L 14 4 L 5 4 L 4 4 A 1.0001 1.0001 0 1 0 3.9804688 6 C 3.9348612 9.0608831 3.6893807 11.887023 3.1523438 14.142578 C 2.5565033 16.645108 1.6039585 18.395538 0.4453125 19.167969 A 1.0001 1.0001 0 0 0 1 21 L 4 21 C 4 22.105 4.895 23 6 23 L 11.787109 23 L 10.148438 26.042969 A 1.5 1.5 0 0 0 9 27.5 A 1.5 1.5 0 0 0 10.5 29 A 1.5 1.5 0 0 0 12 27.5 A 1.5 1.5 0 0 0 11.910156 26.992188 L 14.060547 23 L 15.939453 23 L 18.089844 26.992188 A 1.5 1.5 0 0 0 18 27.5 A 1.5 1.5 0 0 0 19.5 29 A 1.5 1.5 0 0 0 21 27.5 A 1.5 1.5 0 0 0 19.851562 26.042969 L 18.212891 23 L 24 23 C 25.105 23 26 22.105 26 21 L 26 6 A 1.0001 1.0001 0 1 0 26 4 L 25 4 L 16 4 L 16 3 A 1.0001 1.0001 0 0 0 14.984375 1.9863281 z M 5.9589844 6 L 14.832031 6 A 1.0001 1.0001 0 0 0 15.158203 6 L 23.958984 6 C 23.912194 9.0500505 23.687726 11.893974 23.152344 14.142578 C 22.583328 16.532444 21.674397 18.178754 20.585938 19 L 3.1523438 19 C 3.9976592 17.786874 4.6791735 16.365049 5.0976562 14.607422 C 5.6877248 12.129135 5.9137751 9.1554725 5.9589844 6 z\" />\n    </svg>\n  );\n}\n\nfunction PersonIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"40\" height=\"40\" viewBox=\"0 0 30 30\" fill=\"currentcolor\" {...props}>\n      <path d=\"M15,3C8.373,3,3,8.373,3,15c0,6.627,5.373,12,12,12s12-5.373,12-12C27,8.373,21.627,3,15,3z M8,22.141 c1.167-3.5,4.667-2.134,5.25-4.03v-1.264c-0.262-0.141-1.013-1.109-1.092-1.865c-0.207-0.018-0.531-0.223-0.627-1.034 c-0.051-0.435,0.153-0.68,0.276-0.757c0,0-0.308-0.702-0.308-1.399C11.5,9.72,12.526,8,15,8c1.336,0,1.75,0.947,1.75,0.947 c1.194,0,1.75,1.309,1.75,2.844c0,0.765-0.308,1.399-0.308,1.399c0.124,0.077,0.328,0.322,0.277,0.757 c-0.096,0.811-0.42,1.016-0.627,1.034c-0.079,0.756-0.829,1.724-1.092,1.865v1.264c0.583,1.897,4.083,0.531,5.25,4.031 c0,0-2.618,2.859-7,2.859C10.593,25,8,22.141,8,22.141z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/tabs/page.mdx",
    "content": "# Tabs\n\n<Subtitle>A component for toggling between related panels on the same page.</Subtitle>\n\n<Meta\n  name=\"description\"\n  content=\"A high-quality, unstyled React tabs component for toggling between related panels on the same page.\"\n/>\n\nimport { DemoTabsHero } from './demos/hero';\n\n<DemoTabsHero />\n\n## Anatomy\n\nImport the component and assemble its parts:\n\n```jsx title=\"Anatomy\"\nimport { Tabs } from '@base-ui/react/tabs';\n\n<Tabs.Root>\n  <Tabs.List>\n    <Tabs.Tab />\n    <Tabs.Indicator />\n  </Tabs.List>\n  <Tabs.Panel />\n</Tabs.Root>;\n```\n\n## Examples\n\n### Links\n\nUse the `render` prop and set `nativeButton={false}` on `<Tabs.Tab>` to render tabs as anchor elements.\n\n```jsx title=\"Tabs as links\" {6-8}\nimport { Tabs } from '@base-ui/react/tabs';\nimport Link from 'next/link';\n\n<Tabs.Root>\n  <Tabs.List>\n    <Tabs.Tab nativeButton={false} render={<Link href=\"/overview\" />} value=\"overview\">\n      Overview\n    </Tabs.Tab>\n  </Tabs.List>\n  {/* ... */}\n</Tabs.Root>;\n```\n\n## API reference\n\n<Reference component=\"Tabs\" parts=\"Root, List, Tab, Indicator, Panel\" />\n\nexport const metadata = {\n  keywords: [\n    'React Tabs',\n    'Tabbed Interface Component',\n    'Tab Panel',\n    'Tab Control',\n    'Tabbed Navigation',\n    'Tabbed UI',\n    'Page Switcher',\n    'Content Switcher',\n    'View Switcher',\n    'Carousel Alternative',\n    'Tab List Indicator',\n    'Accessible Tabs',\n    'Headless React Components',\n    'Base UI',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toast/demos/anchored/css-modules/index.module.css",
    "content": ".ButtonGroup {\n  display: flex;\n  gap: 0.5rem;\n  align-items: center;\n}\n\n.Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.CopyButton {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 2.5rem;\n  height: 2.5rem;\n  padding: 0;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.AnchoredViewport {\n  outline: 0;\n}\n\n.AnchoredPositioner {\n  z-index: calc(1000 - var(--toast-index));\n}\n\n.AnchoredToast {\n  box-sizing: border-box;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  display: flex;\n  flex-direction: column;\n  width: max-content;\n  padding: 0.25rem 0.5rem;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n\n    &:focus-visible {\n      outline: 1px solid var(--color-blue-800);\n      outline-offset: -1px;\n    }\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n\n    &:focus-visible {\n      outline: 1px solid var(--color-blue-400);\n      outline-offset: -1px;\n    }\n  }\n}\n\n.StackedViewport {\n  position: fixed;\n  z-index: 1;\n  width: 250px;\n  margin: 0 auto;\n  bottom: 1rem;\n  right: 1rem;\n  left: auto;\n  top: auto;\n\n  @media (min-width: 500px) {\n    bottom: 2rem;\n    right: 2rem;\n    width: 300px;\n  }\n}\n\n.StackedToast {\n  --gap: 0.75rem;\n  --peek: 0.75rem;\n  --scale: calc(max(0, 1 - (var(--toast-index) * 0.1)));\n  --shrink: calc(1 - var(--scale));\n  --height: var(--toast-frontmost-height, var(--toast-height));\n  --offset-y: calc(\n    var(--toast-offset-y) * -1 + (var(--toast-index) * var(--gap) * -1) +\n      var(--toast-swipe-movement-y)\n  );\n  position: absolute;\n  right: 0;\n  margin: 0 auto;\n  box-sizing: border-box;\n  background: var(--color-gray-50);\n  color: var(--color-gray-900);\n  border: 1px solid var(--color-gray-200);\n  padding: 1rem;\n  width: 100%;\n  box-shadow: 0 2px 10px rgb(0 0 0 / 0.1);\n  background-clip: padding-box;\n  border-radius: 0.5rem;\n  transform-origin: bottom center;\n  bottom: 0;\n  left: auto;\n  margin-right: 0;\n  -webkit-user-select: none;\n  user-select: none;\n  transition:\n    transform 0.5s cubic-bezier(0.22, 1, 0.36, 1),\n    opacity 0.5s,\n    height 0.15s;\n  cursor: default;\n  z-index: calc(1000 - var(--toast-index));\n  height: var(--height);\n  transform: translateX(var(--toast-swipe-movement-x))\n    translateY(\n      calc(\n        var(--toast-swipe-movement-y) - (var(--toast-index) * var(--peek)) -\n          (var(--shrink) * var(--height))\n      )\n    )\n    scale(var(--scale));\n\n  &[data-expanded] {\n    transform: translateX(var(--toast-swipe-movement-x)) translateY(var(--offset-y));\n    height: var(--toast-height);\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    transform: translateY(150%);\n  }\n\n  &[data-limited] {\n    opacity: 0;\n  }\n\n  &[data-ending-style] {\n    opacity: 0;\n\n    &[data-swipe-direction='up'] {\n      transform: translateY(calc(var(--toast-swipe-movement-y) - 150%));\n    }\n    &[data-swipe-direction='left'] {\n      transform: translateX(calc(var(--toast-swipe-movement-x) - 150%)) translateY(var(--offset-y));\n    }\n    &[data-swipe-direction='right'] {\n      transform: translateX(calc(var(--toast-swipe-movement-x) + 150%)) translateY(var(--offset-y));\n    }\n    &[data-swipe-direction='down'] {\n      transform: translateY(calc(var(--toast-swipe-movement-y) + 150%));\n    }\n  }\n\n  &::after {\n    content: '';\n    position: absolute;\n    top: 100%;\n    width: 100%;\n    left: 0;\n    height: calc(var(--gap) + 1px);\n  }\n}\n\n.Content {\n  overflow: hidden;\n  transition: opacity 0.25s;\n\n  &[data-behind] {\n    opacity: 0;\n  }\n\n  &[data-expanded] {\n    opacity: 1;\n  }\n}\n\n.Title {\n  font-weight: 700;\n  font-size: 0.975rem;\n  line-height: 1.25rem;\n  margin: 0;\n}\n\n.Description {\n  font-size: 0.925rem;\n  line-height: 1.25rem;\n  margin: 0;\n}\n\n.Close {\n  position: absolute;\n  top: 0.5rem;\n  right: 0.5rem;\n  padding: 0;\n  border: none;\n  background: transparent;\n  width: 1.25rem;\n  height: 1.25rem;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  border-radius: 0.25rem;\n\n  &:hover {\n    background-color: var(--color-gray-100);\n  }\n}\n\n.Icon {\n  width: 1rem;\n  height: 1rem;\n}\n\n.Tooltip {\n  box-sizing: border-box;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  display: flex;\n  flex-direction: column;\n  padding: 0.25rem 0.5rem;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  &[data-instant] {\n    transition-duration: 0ms;\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Arrow {\n  display: flex;\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n\n.Viewport {\n  outline: 0;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toast/demos/anchored/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Toast } from '@base-ui/react/toast';\nimport { Button } from '@base-ui/react/button';\nimport { Tooltip } from '@base-ui/react/tooltip';\nimport styles from './index.module.css';\n\nconst anchoredToastManager = Toast.createToastManager();\nconst stackedToastManager = Toast.createToastManager();\n\nexport default function ExampleToast() {\n  return (\n    <Tooltip.Provider>\n      <Toast.Provider toastManager={anchoredToastManager}>\n        <AnchoredToasts />\n      </Toast.Provider>\n      <Toast.Provider toastManager={stackedToastManager}>\n        <StackedToasts />\n      </Toast.Provider>\n\n      <div className={styles.ButtonGroup}>\n        <CopyButton />\n        <StackedToastButton />\n      </div>\n    </Tooltip.Provider>\n  );\n}\n\nfunction StackedToastButton() {\n  function createToast() {\n    stackedToastManager.add({\n      description: 'Copied',\n    });\n  }\n\n  return (\n    <button type=\"button\" className={styles.Button} onClick={createToast}>\n      Stacked toast\n    </button>\n  );\n}\n\nfunction CopyButton() {\n  const [copied, setCopied] = React.useState(false);\n  const buttonRef = React.useRef<HTMLButtonElement | null>(null);\n\n  function handleCopy() {\n    setCopied(true);\n\n    anchoredToastManager.add({\n      description: 'Copied',\n      positionerProps: {\n        anchor: buttonRef.current,\n        sideOffset: 8,\n      },\n      timeout: 1500,\n      onClose() {\n        setCopied(false);\n      },\n    });\n  }\n\n  return (\n    <Tooltip.Root disabled={copied}>\n      <Tooltip.Trigger\n        ref={buttonRef}\n        closeOnClick={false}\n        className={styles.CopyButton}\n        onClick={handleCopy}\n        aria-label=\"Copy to clipboard\"\n        render={<Button disabled={copied} focusableWhenDisabled />}\n      >\n        {copied ? <CheckIcon className={styles.Icon} /> : <ClipboardIcon className={styles.Icon} />}\n      </Tooltip.Trigger>\n      <Tooltip.Portal>\n        <Tooltip.Positioner sideOffset={8}>\n          <Tooltip.Popup className={styles.Tooltip}>\n            <Tooltip.Arrow className={styles.Arrow}>\n              <ArrowSvg />\n            </Tooltip.Arrow>\n            Copy\n          </Tooltip.Popup>\n        </Tooltip.Positioner>\n      </Tooltip.Portal>\n    </Tooltip.Root>\n  );\n}\n\nfunction AnchoredToasts() {\n  const { toasts } = Toast.useToastManager();\n  return (\n    <Toast.Portal>\n      <Toast.Viewport className={styles.AnchoredViewport}>\n        {toasts.map((toast) => (\n          <Toast.Positioner key={toast.id} toast={toast} className={styles.AnchoredPositioner}>\n            <Toast.Root toast={toast} className={styles.AnchoredToast}>\n              <Toast.Arrow className={styles.Arrow}>\n                <ArrowSvg />\n              </Toast.Arrow>\n              <Toast.Content>\n                <Toast.Description />\n              </Toast.Content>\n            </Toast.Root>\n          </Toast.Positioner>\n        ))}\n      </Toast.Viewport>\n    </Toast.Portal>\n  );\n}\n\nfunction StackedToasts() {\n  const { toasts } = Toast.useToastManager();\n  return (\n    <Toast.Portal>\n      <Toast.Viewport className={styles.StackedViewport}>\n        {toasts.map((toast) => (\n          <Toast.Root key={toast.id} toast={toast} className={styles.StackedToast}>\n            <Toast.Content className={styles.Content}>\n              <Toast.Title className={styles.Title} />\n              <Toast.Description className={styles.Description} />\n              <Toast.Close className={styles.Close} aria-label=\"Close\">\n                <XIcon className={styles.Icon} />\n              </Toast.Close>\n            </Toast.Content>\n          </Toast.Root>\n        ))}\n      </Toast.Viewport>\n    </Toast.Portal>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nfunction ClipboardIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <rect width=\"8\" height=\"4\" x=\"8\" y=\"2\" rx=\"1\" ry=\"1\" />\n      <path d=\"M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2\" />\n    </svg>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M20 6 9 17l-5-5\" />\n    </svg>\n  );\n}\n\nfunction XIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M18 6 6 18\" />\n      <path d=\"m6 6 12 12\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toast/demos/anchored/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoToastAnchored = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toast/demos/anchored/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Toast } from '@base-ui/react/toast';\nimport { Button } from '@base-ui/react/button';\nimport { Tooltip } from '@base-ui/react/tooltip';\n\nconst stackedToastManager = Toast.createToastManager();\nconst anchoredToastManager = Toast.createToastManager();\n\nexport default function ExampleToast() {\n  return (\n    <Tooltip.Provider>\n      <Toast.Provider toastManager={anchoredToastManager}>\n        <AnchoredToasts />\n      </Toast.Provider>\n      <Toast.Provider toastManager={stackedToastManager}>\n        <StackedToasts />\n      </Toast.Provider>\n\n      <div className=\"flex items-center gap-2\">\n        <CopyButton />\n        <StackedToastButton />\n      </div>\n    </Tooltip.Provider>\n  );\n}\n\nfunction StackedToastButton() {\n  function createToast() {\n    stackedToastManager.add({\n      description: 'Copied',\n    });\n  }\n\n  return (\n    <button\n      type=\"button\"\n      className=\"box-border flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 py-0 font-normal text-gray-900 outline-0 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\"\n      onClick={createToast}\n    >\n      Stacked toast\n    </button>\n  );\n}\n\nfunction CopyButton() {\n  const [copied, setCopied] = React.useState(false);\n  const buttonRef = React.useRef<HTMLButtonElement | null>(null);\n\n  function handleCopy() {\n    setCopied(true);\n\n    anchoredToastManager.add({\n      description: 'Copied',\n      positionerProps: {\n        anchor: buttonRef.current,\n        sideOffset: 8,\n      },\n      timeout: 1500,\n      onClose() {\n        setCopied(false);\n      },\n    });\n  }\n\n  return (\n    <Tooltip.Root disabled={copied}>\n      <Tooltip.Trigger\n        ref={buttonRef}\n        closeOnClick={false}\n        className=\"box-border flex h-10 w-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 text-gray-900 outline-0 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\"\n        onClick={handleCopy}\n        aria-label=\"Copy to clipboard\"\n        render={<Button disabled={copied} focusableWhenDisabled />}\n      >\n        {copied ? <CheckIcon className=\"h-5 w-5\" /> : <ClipboardIcon className=\"h-5 w-5\" />}\n      </Tooltip.Trigger>\n      <Tooltip.Portal>\n        <Tooltip.Positioner sideOffset={8}>\n          <Tooltip.Popup className=\"flex origin-(--transform-origin) flex-col rounded-md bg-[canvas] px-2 py-1 text-sm shadow-lg shadow-gray-200 outline-1 outline-gray-200 transition-[transform,scale,opacity] data-ending-style:scale-90 data-ending-style:opacity-0 data-instant:duration-0 data-starting-style:scale-90 data-starting-style:opacity-0 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300\">\n            <Tooltip.Arrow className=\"data-[side=bottom]:-top-2 data-[side=left]:right-[-13px] data-[side=left]:rotate-90 data-[side=right]:left-[-13px] data-[side=right]:-rotate-90 data-[side=top]:-bottom-2 data-[side=top]:rotate-180\">\n              <ArrowSvg />\n            </Tooltip.Arrow>\n            Copy\n          </Tooltip.Popup>\n        </Tooltip.Positioner>\n      </Tooltip.Portal>\n    </Tooltip.Root>\n  );\n}\n\nfunction AnchoredToasts() {\n  const { toasts } = Toast.useToastManager();\n  return (\n    <Toast.Portal>\n      <Toast.Viewport className=\"outline-0\">\n        {toasts.map((toast) => (\n          <Toast.Positioner\n            key={toast.id}\n            toast={toast}\n            className=\"z-[calc(1000-var(--toast-index))]\"\n          >\n            <Toast.Root\n              toast={toast}\n              className=\"group flex w-max origin-(--transform-origin) flex-col rounded-md bg-[canvas] px-2 py-1 text-sm shadow-lg shadow-gray-200 outline-1 outline-gray-200 transition-[transform,scale,opacity] data-ending-style:scale-90 data-ending-style:opacity-0 data-starting-style:scale-90 data-starting-style:opacity-0 focus-visible:outline-1 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300 dark:focus-visible:outline-blue-400\"\n            >\n              <Toast.Arrow className=\"data-[side=bottom]:-top-2 data-[side=left]:right-[-13px] data-[side=left]:rotate-90 data-[side=right]:left-[-13px] data-[side=right]:-rotate-90 data-[side=top]:-bottom-2 data-[side=top]:rotate-180\">\n                <ArrowSvg />\n              </Toast.Arrow>\n              <Toast.Content>\n                <Toast.Description />\n              </Toast.Content>\n            </Toast.Root>\n          </Toast.Positioner>\n        ))}\n      </Toast.Viewport>\n    </Toast.Portal>\n  );\n}\n\nfunction StackedToasts() {\n  const { toasts } = Toast.useToastManager();\n  return (\n    <Toast.Portal>\n      <Toast.Viewport className=\"fixed z-10 top-auto right-[1rem] bottom-[1rem] mx-auto flex w-[250px] sm:right-[2rem] sm:bottom-[2rem] sm:w-[300px]\">\n        {toasts.map((toast) => (\n          <Toast.Root\n            key={toast.id}\n            toast={toast}\n            className=\"[--gap:0.75rem] [--peek:0.75rem] [--scale:calc(max(0,1-(var(--toast-index)*0.1)))] [--shrink:calc(1-var(--scale))] [--height:var(--toast-frontmost-height,var(--toast-height))] [--offset-y:calc(var(--toast-offset-y)*-1+calc(var(--toast-index)*var(--gap)*-1)+var(--toast-swipe-movement-y))] absolute right-0 bottom-0 left-auto z-[calc(1000-var(--toast-index))] mr-0 w-full origin-bottom [transform:translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)-(var(--toast-index)*var(--peek))-(var(--shrink)*var(--height))))_scale(var(--scale))] rounded-lg border border-gray-200 bg-gray-50 bg-clip-padding p-4 shadow-lg select-none after:absolute after:top-full after:left-0 after:h-[calc(var(--gap)+1px)] after:w-full after:content-[''] data-[ending-style]:opacity-0 data-[expanded]:[transform:translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--offset-y)))] data-[limited]:opacity-0 data-[starting-style]:[transform:translateY(150%)] [&[data-ending-style]:not([data-limited]):not([data-swipe-direction])]:[transform:translateY(150%)] data-[ending-style]:data-[swipe-direction=down]:[transform:translateY(calc(var(--toast-swipe-movement-y)+150%))] data-[expanded]:data-[ending-style]:data-[swipe-direction=down]:[transform:translateY(calc(var(--toast-swipe-movement-y)+150%))] data-[ending-style]:data-[swipe-direction=left]:[transform:translateX(calc(var(--toast-swipe-movement-x)-150%))_translateY(var(--offset-y))] data-[expanded]:data-[ending-style]:data-[swipe-direction=left]:[transform:translateX(calc(var(--toast-swipe-movement-x)-150%))_translateY(var(--offset-y))] data-[ending-style]:data-[swipe-direction=right]:[transform:translateX(calc(var(--toast-swipe-movement-x)+150%))_translateY(var(--offset-y))] data-[expanded]:data-[ending-style]:data-[swipe-direction=right]:[transform:translateX(calc(var(--toast-swipe-movement-x)+150%))_translateY(var(--offset-y))] data-[ending-style]:data-[swipe-direction=up]:[transform:translateY(calc(var(--toast-swipe-movement-y)-150%))] data-[expanded]:data-[ending-style]:data-[swipe-direction=up]:[transform:translateY(calc(var(--toast-swipe-movement-y)-150%))] h-[var(--height)] data-[expanded]:h-[var(--toast-height)] [transition:transform_0.5s_cubic-bezier(0.22,1,0.36,1),opacity_0.5s,height_0.15s]\"\n          >\n            <Toast.Content className=\"overflow-hidden transition-opacity [transition-duration:250ms] data-[behind]:pointer-events-none data-[behind]:opacity-0 data-[expanded]:pointer-events-auto data-[expanded]:opacity-100\">\n              <Toast.Title className=\"text-[0.975rem] leading-5 font-bold\" />\n              <Toast.Description className=\"text-[0.925rem] leading-5 text-gray-700\" />\n              <Toast.Close\n                className=\"absolute top-2 right-2 flex h-5 w-5 items-center justify-center rounded-sm border-none bg-transparent text-gray-500 hover:bg-gray-100 hover:text-gray-700\"\n                aria-label=\"Close\"\n              >\n                <XIcon className=\"h-4 w-4\" />\n              </Toast.Close>\n            </Toast.Content>\n          </Toast.Root>\n        ))}\n      </Toast.Viewport>\n    </Toast.Portal>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className=\"fill-[canvas]\"\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className=\"fill-gray-200 dark:fill-none\"\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className=\"dark:fill-gray-300\"\n      />\n    </svg>\n  );\n}\n\nfunction ClipboardIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <rect width=\"8\" height=\"4\" x=\"8\" y=\"2\" rx=\"1\" ry=\"1\" />\n      <path d=\"M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2\" />\n    </svg>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M20 6 9 17l-5-5\" />\n    </svg>\n  );\n}\n\nfunction XIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M18 6 6 18\" />\n      <path d=\"m6 6 12 12\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toast/demos/custom/css-modules/index.module.css",
    "content": ".Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Viewport {\n  position: fixed;\n  z-index: 1;\n  width: 250px;\n  margin: 0 auto;\n  bottom: 1rem;\n  right: 1rem;\n  left: auto;\n  top: auto;\n\n  @media (min-width: 500px) {\n    bottom: 2rem;\n    right: 2rem;\n    width: 300px;\n  }\n}\n\n.Toast {\n  --gap: 0.75rem;\n  --peek: 0.75rem;\n  --scale: calc(max(0, 1 - (var(--toast-index) * 0.1)));\n  --shrink: calc(1 - var(--scale));\n  --height: var(--toast-frontmost-height, var(--toast-height));\n  --offset-y: calc(\n    var(--toast-offset-y) * -1 + (var(--toast-index) * var(--gap) * -1) +\n      var(--toast-swipe-movement-y)\n  );\n  position: absolute;\n  right: 0;\n  margin: 0 auto;\n  box-sizing: border-box;\n  background: var(--color-gray-50);\n  color: var(--color-gray-900);\n  border: 1px solid var(--color-gray-200);\n  padding: 1rem;\n  width: 100%;\n  box-shadow: 0 2px 10px rgb(0 0 0 / 0.1);\n  background-clip: padding-box;\n  border-radius: 0.5rem;\n  transform-origin: bottom center;\n  bottom: 0;\n  left: auto;\n  margin-right: 0;\n  -webkit-user-select: none;\n  user-select: none;\n  transition:\n    transform 0.5s cubic-bezier(0.22, 1, 0.36, 1),\n    opacity 0.5s,\n    height 0.15s;\n  cursor: default;\n  z-index: calc(1000 - var(--toast-index));\n  height: var(--height);\n  transform: translateX(var(--toast-swipe-movement-x))\n    translateY(\n      calc(\n        var(--toast-swipe-movement-y) - (var(--toast-index) * var(--peek)) -\n          (var(--shrink) * var(--height))\n      )\n    )\n    scale(var(--scale));\n\n  &[data-expanded] {\n    transform: translateX(var(--toast-swipe-movement-x)) translateY(var(--offset-y));\n    height: var(--toast-height);\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    transform: translateY(150%);\n  }\n\n  &[data-limited] {\n    opacity: 0;\n  }\n\n  &[data-ending-style] {\n    opacity: 0;\n\n    &[data-swipe-direction='up'] {\n      transform: translateY(calc(var(--toast-swipe-movement-y) - 150%));\n    }\n    &[data-swipe-direction='left'] {\n      transform: translateX(calc(var(--toast-swipe-movement-x) - 150%)) translateY(var(--offset-y));\n    }\n    &[data-swipe-direction='right'] {\n      transform: translateX(calc(var(--toast-swipe-movement-x) + 150%)) translateY(var(--offset-y));\n    }\n    &[data-swipe-direction='down'] {\n      transform: translateY(calc(var(--toast-swipe-movement-y) + 150%));\n    }\n  }\n\n  &::after {\n    content: '';\n    position: absolute;\n    top: 100%;\n    width: 100%;\n    left: 0;\n    height: calc(var(--gap) + 1px);\n  }\n}\n\n.Content {\n  overflow: hidden;\n  transition: opacity 0.25s cubic-bezier(0.22, 1, 0.36, 1);\n\n  &[data-behind] {\n    opacity: 0;\n  }\n\n  &[data-expanded] {\n    opacity: 1;\n  }\n}\n\n.Title {\n  font-weight: 700;\n  font-size: 0.975rem;\n  line-height: 1.25rem;\n  margin: 0;\n}\n\n.Description {\n  font-size: 0.925rem;\n  line-height: 1.25rem;\n  margin: 0;\n}\n\n.Close {\n  position: absolute;\n  top: 0.5rem;\n  right: 0.5rem;\n  padding: 0;\n  border: none;\n  background: transparent;\n  width: 1.25rem;\n  height: 1.25rem;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  border-radius: 0.25rem;\n\n  &:hover {\n    background-color: var(--color-gray-100);\n  }\n}\n\n.Icon {\n  width: 1rem;\n  height: 1rem;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toast/demos/custom/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Toast } from '@base-ui/react/toast';\nimport styles from './index.module.css';\n\ninterface CustomToastData {\n  userId: string;\n}\n\nfunction isCustomToast(\n  toast: Toast.Root.ToastObject,\n): toast is Toast.Root.ToastObject<CustomToastData> {\n  return toast.data?.userId !== undefined;\n}\n\nexport default function CustomToastExample() {\n  return (\n    <Toast.Provider>\n      <CustomToast />\n      <Toast.Portal>\n        <Toast.Viewport className={styles.Viewport}>\n          <ToastList />\n        </Toast.Viewport>\n      </Toast.Portal>\n    </Toast.Provider>\n  );\n}\n\nfunction CustomToast() {\n  const toastManager = Toast.useToastManager();\n\n  function action() {\n    const data: CustomToastData = {\n      userId: '123',\n    };\n\n    toastManager.add({\n      title: 'Toast with custom data',\n      data,\n    });\n  }\n\n  return (\n    <button type=\"button\" onClick={action} className={styles.Button}>\n      Create custom toast\n    </button>\n  );\n}\n\nfunction ToastList() {\n  const { toasts } = Toast.useToastManager();\n  return toasts.map((toast) => (\n    <Toast.Root key={toast.id} toast={toast} className={styles.Toast}>\n      <Toast.Content className={styles.Content}>\n        <Toast.Title className={styles.Title}>{toast.title}</Toast.Title>\n        {isCustomToast(toast) && toast.data ? (\n          <Toast.Description className={styles.Description}>\n            `data.userId` is {toast.data.userId}\n          </Toast.Description>\n        ) : (\n          <Toast.Description className={styles.Description} />\n        )}\n        <Toast.Close className={styles.Close} aria-label=\"Close\">\n          <XIcon className={styles.Icon} />\n        </Toast.Close>\n      </Toast.Content>\n    </Toast.Root>\n  ));\n}\n\nfunction XIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M18 6 6 18\" />\n      <path d=\"m6 6 12 12\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toast/demos/custom/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\n\nexport const DemoToastCustom = createDemoWithVariants(import.meta.url, { CssModules });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toast/demos/hero/css-modules/index.module.css",
    "content": ".Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Viewport {\n  position: fixed;\n  z-index: 1;\n  width: 250px;\n  margin: 0 auto;\n  bottom: 1rem;\n  right: 1rem;\n  left: auto;\n  top: auto;\n\n  @media (min-width: 500px) {\n    bottom: 2rem;\n    right: 2rem;\n    width: 300px;\n  }\n}\n\n.Toast {\n  --gap: 0.75rem;\n  --peek: 0.75rem;\n  --scale: calc(max(0, 1 - (var(--toast-index) * 0.1)));\n  --shrink: calc(1 - var(--scale));\n  --height: var(--toast-frontmost-height, var(--toast-height));\n  --offset-y: calc(\n    var(--toast-offset-y) * -1 + (var(--toast-index) * var(--gap) * -1) +\n      var(--toast-swipe-movement-y)\n  );\n  position: absolute;\n  right: 0;\n  margin: 0 auto;\n  box-sizing: border-box;\n  background: var(--color-gray-50);\n  color: var(--color-gray-900);\n  border: 1px solid var(--color-gray-200);\n  padding: 1rem;\n  width: 100%;\n  box-shadow: 0 2px 10px rgb(0 0 0 / 0.1);\n  background-clip: padding-box;\n  border-radius: 0.5rem;\n  transform-origin: bottom center;\n  bottom: 0;\n  left: auto;\n  margin-right: 0;\n  -webkit-user-select: none;\n  user-select: none;\n  transition:\n    transform 0.5s cubic-bezier(0.22, 1, 0.36, 1),\n    opacity 0.5s,\n    height 0.15s;\n  cursor: default;\n  z-index: calc(1000 - var(--toast-index));\n  height: var(--height);\n  transform: translateX(var(--toast-swipe-movement-x))\n    translateY(\n      calc(\n        var(--toast-swipe-movement-y) - (var(--toast-index) * var(--peek)) -\n          (var(--shrink) * var(--height))\n      )\n    )\n    scale(var(--scale));\n\n  &[data-expanded] {\n    transform: translateX(var(--toast-swipe-movement-x)) translateY(var(--offset-y));\n    height: var(--toast-height);\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    transform: translateY(150%);\n  }\n\n  &[data-limited] {\n    opacity: 0;\n  }\n\n  &[data-ending-style] {\n    opacity: 0;\n\n    &[data-swipe-direction='up'] {\n      transform: translateY(calc(var(--toast-swipe-movement-y) - 150%));\n    }\n    &[data-swipe-direction='left'] {\n      transform: translateX(calc(var(--toast-swipe-movement-x) - 150%)) translateY(var(--offset-y));\n    }\n    &[data-swipe-direction='right'] {\n      transform: translateX(calc(var(--toast-swipe-movement-x) + 150%)) translateY(var(--offset-y));\n    }\n    &[data-swipe-direction='down'] {\n      transform: translateY(calc(var(--toast-swipe-movement-y) + 150%));\n    }\n  }\n\n  &::after {\n    content: '';\n    position: absolute;\n    top: 100%;\n    width: 100%;\n    left: 0;\n    height: calc(var(--gap) + 1px);\n  }\n}\n\n.Content {\n  overflow: hidden;\n  transition: opacity 0.25s;\n\n  &[data-behind] {\n    opacity: 0;\n  }\n\n  &[data-expanded] {\n    opacity: 1;\n  }\n}\n\n.Title {\n  font-weight: 700;\n  font-size: 0.975rem;\n  line-height: 1.25rem;\n  margin: 0;\n}\n\n.Description {\n  font-size: 0.925rem;\n  line-height: 1.25rem;\n  margin: 0;\n}\n\n.Close {\n  position: absolute;\n  top: 0.5rem;\n  right: 0.5rem;\n  padding: 0;\n  border: none;\n  background: transparent;\n  width: 1.25rem;\n  height: 1.25rem;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  border-radius: 0.25rem;\n\n  &:hover {\n    background-color: var(--color-gray-100);\n  }\n}\n\n.Icon {\n  width: 1rem;\n  height: 1rem;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toast/demos/hero/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Toast } from '@base-ui/react/toast';\nimport styles from './index.module.css';\n\nexport default function ExampleToast() {\n  return (\n    <Toast.Provider>\n      <ToastButton />\n      <Toast.Portal>\n        <Toast.Viewport className={styles.Viewport}>\n          <ToastList />\n        </Toast.Viewport>\n      </Toast.Portal>\n    </Toast.Provider>\n  );\n}\n\nfunction ToastButton() {\n  const toastManager = Toast.useToastManager();\n  const [count, setCount] = React.useState(0);\n\n  function createToast() {\n    setCount((prev) => prev + 1);\n    toastManager.add({\n      title: `Toast ${count + 1} created`,\n      description: 'This is a toast notification.',\n    });\n  }\n\n  return (\n    <button type=\"button\" className={styles.Button} onClick={createToast}>\n      Create toast\n    </button>\n  );\n}\n\nfunction ToastList() {\n  const { toasts } = Toast.useToastManager();\n  return toasts.map((toast) => (\n    <Toast.Root key={toast.id} toast={toast} className={styles.Toast}>\n      <Toast.Content className={styles.Content}>\n        <Toast.Title className={styles.Title} />\n        <Toast.Description className={styles.Description} />\n        <Toast.Close className={styles.Close} aria-label=\"Close\">\n          <XIcon className={styles.Icon} />\n        </Toast.Close>\n      </Toast.Content>\n    </Toast.Root>\n  ));\n}\n\nfunction XIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M18 6 6 18\" />\n      <path d=\"m6 6 12 12\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toast/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoToastHero = createDemoWithVariants(import.meta.url, { CssModules, Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toast/demos/hero/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Toast } from '@base-ui/react/toast';\n\nexport default function ExampleToast() {\n  return (\n    <Toast.Provider>\n      <ToastButton />\n      <Toast.Portal>\n        <Toast.Viewport className=\"fixed z-10 top-auto right-[1rem] bottom-[1rem] mx-auto flex w-[250px] sm:right-[2rem] sm:bottom-[2rem] sm:w-[300px]\">\n          <ToastList />\n        </Toast.Viewport>\n      </Toast.Portal>\n    </Toast.Provider>\n  );\n}\n\nfunction ToastButton() {\n  const toastManager = Toast.useToastManager();\n  const [count, setCount] = React.useState(0);\n\n  function createToast() {\n    setCount((prev) => prev + 1);\n    toastManager.add({\n      title: `Toast ${count + 1} created`,\n      description: 'This is a toast notification.',\n    });\n  }\n\n  return (\n    <button\n      type=\"button\"\n      className=\"box-border flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 py-0 font-normal text-gray-900 outline-0 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue active:bg-gray-100\"\n      onClick={createToast}\n    >\n      Create toast\n    </button>\n  );\n}\n\nfunction ToastList() {\n  const { toasts } = Toast.useToastManager();\n  return toasts.map((toast) => (\n    <Toast.Root\n      key={toast.id}\n      toast={toast}\n      className=\"[--gap:0.75rem] [--peek:0.75rem] [--scale:calc(max(0,1-(var(--toast-index)*0.1)))] [--shrink:calc(1-var(--scale))] [--height:var(--toast-frontmost-height,var(--toast-height))] [--offset-y:calc(var(--toast-offset-y)*-1+calc(var(--toast-index)*var(--gap)*-1)+var(--toast-swipe-movement-y))] absolute right-0 bottom-0 left-auto z-[calc(1000-var(--toast-index))] mr-0 w-full origin-bottom [transform:translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)-(var(--toast-index)*var(--peek))-(var(--shrink)*var(--height))))_scale(var(--scale))] rounded-lg border border-gray-200 bg-gray-50 bg-clip-padding p-4 shadow-lg select-none after:absolute after:top-full after:left-0 after:h-[calc(var(--gap)+1px)] after:w-full after:content-[''] data-[ending-style]:opacity-0 data-[expanded]:[transform:translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--offset-y)))] data-[limited]:opacity-0 data-[starting-style]:[transform:translateY(150%)] [&[data-ending-style]:not([data-limited]):not([data-swipe-direction])]:[transform:translateY(150%)] data-[ending-style]:data-[swipe-direction=down]:[transform:translateY(calc(var(--toast-swipe-movement-y)+150%))] data-[expanded]:data-[ending-style]:data-[swipe-direction=down]:[transform:translateY(calc(var(--toast-swipe-movement-y)+150%))] data-[ending-style]:data-[swipe-direction=left]:[transform:translateX(calc(var(--toast-swipe-movement-x)-150%))_translateY(var(--offset-y))] data-[expanded]:data-[ending-style]:data-[swipe-direction=left]:[transform:translateX(calc(var(--toast-swipe-movement-x)-150%))_translateY(var(--offset-y))] data-[ending-style]:data-[swipe-direction=right]:[transform:translateX(calc(var(--toast-swipe-movement-x)+150%))_translateY(var(--offset-y))] data-[expanded]:data-[ending-style]:data-[swipe-direction=right]:[transform:translateX(calc(var(--toast-swipe-movement-x)+150%))_translateY(var(--offset-y))] data-[ending-style]:data-[swipe-direction=up]:[transform:translateY(calc(var(--toast-swipe-movement-y)-150%))] data-[expanded]:data-[ending-style]:data-[swipe-direction=up]:[transform:translateY(calc(var(--toast-swipe-movement-y)-150%))] h-[var(--height)] data-[expanded]:h-[var(--toast-height)] [transition:transform_0.5s_cubic-bezier(0.22,1,0.36,1),opacity_0.5s,height_0.15s]\"\n    >\n      <Toast.Content className=\"overflow-hidden transition-opacity [transition-duration:250ms] data-[behind]:pointer-events-none data-[behind]:opacity-0 data-[expanded]:pointer-events-auto data-[expanded]:opacity-100\">\n        <Toast.Title className=\"text-[0.975rem] leading-5 font-bold\" />\n        <Toast.Description className=\"text-[0.925rem] leading-5 text-gray-700\" />\n        <Toast.Close\n          className=\"absolute top-2 right-2 flex h-5 w-5 items-center justify-center rounded-sm border-none bg-transparent text-gray-500 hover:bg-gray-100 hover:text-gray-700\"\n          aria-label=\"Close\"\n        >\n          <XIcon className=\"h-4 w-4\" />\n        </Toast.Close>\n      </Toast.Content>\n    </Toast.Root>\n  ));\n}\n\nfunction XIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M18 6 6 18\" />\n      <path d=\"m6 6 12 12\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toast/demos/position/css-modules/index.module.css",
    "content": ".Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Viewport {\n  position: fixed;\n  z-index: 1;\n  width: 100%;\n  max-width: 300px;\n  margin: 0 auto;\n  top: 1rem;\n  right: 0;\n  left: 0;\n  bottom: auto;\n}\n\n.Toast {\n  --gap: 0.75rem;\n  --peek: 0.75rem;\n  --scale: calc(max(0, 1 - (var(--toast-index) * 0.1)));\n  --shrink: calc(1 - var(--scale));\n  --height: var(--toast-frontmost-height, var(--toast-height));\n  --offset-y: calc(\n    var(--toast-offset-y) + (var(--toast-index) * var(--gap)) + var(--toast-swipe-movement-y)\n  );\n  position: absolute;\n  margin: 0 auto;\n  box-sizing: border-box;\n  background: var(--color-gray-50);\n  color: var(--color-gray-900);\n  border: 1px solid var(--color-gray-200);\n  padding: 1rem;\n  width: 100%;\n  box-shadow: 0 2px 10px rgb(0 0 0 / 0.1);\n  background-clip: padding-box;\n  border-radius: 0.5rem;\n  transform-origin: top center;\n  top: 0;\n  left: 0;\n  right: 0;\n  margin-right: auto;\n  margin-left: auto;\n  -webkit-user-select: none;\n  user-select: none;\n  transition:\n    transform 0.5s cubic-bezier(0.22, 1, 0.36, 1),\n    opacity 0.5s,\n    height 0.15s;\n  cursor: default;\n  z-index: calc(1000 - var(--toast-index));\n  height: var(--height);\n  transform: translateX(var(--toast-swipe-movement-x))\n    translateY(\n      calc(\n        var(--toast-swipe-movement-y) + (var(--toast-index) * var(--peek)) +\n          (var(--shrink) * var(--height))\n      )\n    )\n    scale(var(--scale));\n\n  &[data-expanded] {\n    transform: translateX(var(--toast-swipe-movement-x)) translateY(var(--offset-y));\n    height: var(--toast-height);\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    transform: translateY(-150%);\n  }\n\n  &[data-limited] {\n    opacity: 0;\n  }\n\n  &[data-ending-style] {\n    opacity: 0;\n\n    &[data-swipe-direction='up'] {\n      transform: translateY(calc(var(--toast-swipe-movement-y) - 150%));\n    }\n    &[data-swipe-direction='left'] {\n      transform: translateX(calc(var(--toast-swipe-movement-x) - 150%)) translateY(var(--offset-y));\n    }\n    &[data-swipe-direction='right'] {\n      transform: translateX(calc(var(--toast-swipe-movement-x) + 150%)) translateY(var(--offset-y));\n    }\n    &[data-swipe-direction='down'] {\n      transform: translateY(calc(var(--toast-swipe-movement-y) + 150%));\n    }\n  }\n\n  &::after {\n    content: '';\n    position: absolute;\n    width: 100%;\n    bottom: 100%;\n    left: 0;\n    height: calc(var(--gap) + 1px);\n  }\n}\n\n.Content {\n  overflow: hidden;\n  transition: opacity 0.25s;\n\n  &[data-behind] {\n    opacity: 0;\n  }\n\n  &[data-expanded] {\n    opacity: 1;\n  }\n}\n\n.Title {\n  font-weight: 700;\n  font-size: 0.975rem;\n  line-height: 1.25rem;\n  margin: 0;\n}\n\n.Description {\n  font-size: 0.925rem;\n  line-height: 1.25rem;\n  margin: 0;\n}\n\n.Close {\n  position: absolute;\n  top: 0.5rem;\n  right: 0.5rem;\n  padding: 0;\n  border: none;\n  background: transparent;\n  width: 1.25rem;\n  height: 1.25rem;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  border-radius: 0.25rem;\n\n  &:hover {\n    background-color: var(--color-gray-100);\n  }\n}\n\n.Icon {\n  width: 1rem;\n  height: 1rem;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toast/demos/position/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Toast } from '@base-ui/react/toast';\nimport styles from './index.module.css';\n\nexport default function ExampleToast() {\n  return (\n    <Toast.Provider>\n      <ToastButton />\n      <Toast.Portal>\n        <Toast.Viewport className={styles.Viewport}>\n          <ToastList />\n        </Toast.Viewport>\n      </Toast.Portal>\n    </Toast.Provider>\n  );\n}\n\nfunction ToastButton() {\n  const toastManager = Toast.useToastManager();\n  const [count, setCount] = React.useState(0);\n\n  function createToast() {\n    setCount((prev) => prev + 1);\n    toastManager.add({\n      title: `Toast ${count + 1} created`,\n      description: 'This is a toast notification.',\n    });\n  }\n\n  return (\n    <button type=\"button\" className={styles.Button} onClick={createToast}>\n      Create toast\n    </button>\n  );\n}\n\nfunction ToastList() {\n  const { toasts } = Toast.useToastManager();\n  return toasts.map((toast) => (\n    <Toast.Root key={toast.id} toast={toast} swipeDirection=\"up\" className={styles.Toast}>\n      <Toast.Content className={styles.Content}>\n        <Toast.Title className={styles.Title} />\n        <Toast.Description className={styles.Description} />\n        <Toast.Close className={styles.Close} aria-label=\"Close\">\n          <XIcon className={styles.Icon} />\n        </Toast.Close>\n      </Toast.Content>\n    </Toast.Root>\n  ));\n}\n\nfunction XIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M18 6 6 18\" />\n      <path d=\"m6 6 12 12\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toast/demos/position/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoToastPosition = createDemoWithVariants(import.meta.url, { CssModules, Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toast/demos/position/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Toast } from '@base-ui/react/toast';\n\nexport default function ExampleToast() {\n  return (\n    <Toast.Provider>\n      <ToastButton />\n      <Toast.Portal>\n        <Toast.Viewport className=\"fixed z-10 top-[1rem] right-0 bottom-auto left-0 mx-auto flex w-full max-w-[300px]\">\n          <ToastList />\n        </Toast.Viewport>\n      </Toast.Portal>\n    </Toast.Provider>\n  );\n}\n\nfunction ToastButton() {\n  const toastManager = Toast.useToastManager();\n  const [count, setCount] = React.useState(0);\n\n  function createToast() {\n    setCount((prev) => prev + 1);\n    toastManager.add({\n      title: `Toast ${count + 1} created`,\n      description: 'This is a toast notification.',\n    });\n  }\n\n  return (\n    <button\n      type=\"button\"\n      className=\"box-border flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 py-0 font-normal text-gray-900 outline-0 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue active:bg-gray-100\"\n      onClick={createToast}\n    >\n      Create toast\n    </button>\n  );\n}\n\nfunction ToastList() {\n  const { toasts } = Toast.useToastManager();\n  return toasts.map((toast) => (\n    <Toast.Root\n      key={toast.id}\n      toast={toast}\n      swipeDirection=\"up\"\n      className=\"[--gap:0.75rem] [--peek:0.75rem] [--scale:calc(max(0,1-(var(--toast-index)*0.1)))] [--shrink:calc(1-var(--scale))] [--height:var(--toast-frontmost-height,var(--toast-height))] [--offset-y:calc(var(--toast-offset-y)+(var(--toast-index)*var(--gap))+var(--toast-swipe-movement-y))] absolute right-0 top-0 left-0 z-[calc(1000-var(--toast-index))] mx-auto w-[300px] origin-top [transform:translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)+(var(--toast-index)*var(--peek))+(var(--shrink)*var(--height))))_scale(var(--scale))] rounded-lg border border-gray-200 bg-gray-50 bg-clip-padding p-4 shadow-lg select-none after:absolute after:bottom-full after:left-0 after:h-[calc(var(--gap)+1px)] after:w-full after:content-[''] data-[ending-style]:opacity-0 data-[expanded]:[transform:translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--offset-y)))] data-[limited]:opacity-0 data-[starting-style]:[transform:translateY(-150%)] [&[data-ending-style]:not([data-limited]):not([data-swipe-direction])]:[transform:translateY(-150%)] data-[ending-style]:data-[swipe-direction=down]:[transform:translateY(calc(var(--toast-swipe-movement-y)+150%))] data-[expanded]:data-[ending-style]:data-[swipe-direction=down]:[transform:translateY(calc(var(--toast-swipe-movement-y)+150%))] data-[ending-style]:data-[swipe-direction=left]:[transform:translateX(calc(var(--toast-swipe-movement-x)-150%))_translateY(var(--offset-y))] data-[expanded]:data-[ending-style]:data-[swipe-direction=left]:[transform:translateX(calc(var(--toast-swipe-movement-x)-150%))_translateY(var(--offset-y))] data-[ending-style]:data-[swipe-direction=right]:[transform:translateX(calc(var(--toast-swipe-movement-x)+150%))_translateY(var(--offset-y))] data-[expanded]:data-[ending-style]:data-[swipe-direction=right]:[transform:translateX(calc(var(--toast-swipe-movement-x)+150%))_translateY(var(--offset-y))] data-[ending-style]:data-[swipe-direction=up]:[transform:translateY(calc(var(--toast-swipe-movement-y)-150%))] data-[expanded]:data-[ending-style]:data-[swipe-direction=up]:[transform:translateY(calc(var(--toast-swipe-movement-y)-150%))] h-[var(--height)] data-[expanded]:h-[var(--toast-height)] [transition:transform_0.5s_cubic-bezier(0.22,1,0.36,1),opacity_0.5s,height_0.15s]\"\n    >\n      <Toast.Content className=\"overflow-hidden transition-opacity [transition-duration:250ms] data-[behind]:pointer-events-none data-[behind]:opacity-0 data-[expanded]:pointer-events-auto data-[expanded]:opacity-100\">\n        <Toast.Title className=\"text-[0.975rem] leading-5 font-bold\" />\n        <Toast.Description className=\"text-[0.925rem] leading-5 text-gray-700\" />\n        <Toast.Close\n          className=\"absolute top-2 right-2 flex h-5 w-5 items-center justify-center rounded-sm border-none bg-transparent text-gray-500 hover:bg-gray-100 hover:text-gray-700\"\n          aria-label=\"Close\"\n        >\n          <XIcon className=\"h-4 w-4\" />\n        </Toast.Close>\n      </Toast.Content>\n    </Toast.Root>\n  ));\n}\n\nfunction XIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M18 6 6 18\" />\n      <path d=\"m6 6 12 12\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toast/demos/promise/css-modules/index.module.css",
    "content": ".Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Viewport {\n  position: fixed;\n  z-index: 1;\n  width: 250px;\n  margin: 0 auto;\n  bottom: 1rem;\n  right: 1rem;\n  left: auto;\n  top: auto;\n\n  @media (min-width: 500px) {\n    bottom: 2rem;\n    right: 2rem;\n    width: 300px;\n  }\n}\n\n.Toast {\n  --gap: 0.75rem;\n  --peek: 0.75rem;\n  --scale: calc(max(0, 1 - (var(--toast-index) * 0.1)));\n  --shrink: calc(1 - var(--scale));\n  --height: var(--toast-frontmost-height, var(--toast-height));\n  --offset-y: calc(\n    var(--toast-offset-y) * -1 + (var(--toast-index) * var(--gap) * -1) +\n      var(--toast-swipe-movement-y)\n  );\n  position: absolute;\n  right: 0;\n  margin: 0 auto;\n  box-sizing: border-box;\n  background: var(--color-gray-50);\n  color: var(--color-gray-900);\n  border: 1px solid var(--color-gray-200);\n  padding: 1rem;\n  width: 100%;\n  box-shadow: 0 2px 10px rgb(0 0 0 / 0.1);\n  background-clip: padding-box;\n  border-radius: 0.5rem;\n  transform-origin: bottom center;\n  bottom: 0;\n  left: auto;\n  margin-right: 0;\n  -webkit-user-select: none;\n  user-select: none;\n  transition:\n    transform 0.5s cubic-bezier(0.22, 1, 0.36, 1),\n    opacity 0.5s,\n    height 0.15s;\n  cursor: default;\n  z-index: calc(1000 - var(--toast-index));\n  height: var(--height);\n  transform: translateX(var(--toast-swipe-movement-x))\n    translateY(\n      calc(\n        var(--toast-swipe-movement-y) - (var(--toast-index) * var(--peek)) -\n          (var(--shrink) * var(--height))\n      )\n    )\n    scale(var(--scale));\n\n  &[data-expanded] {\n    transform: translateX(var(--toast-swipe-movement-x)) translateY(var(--offset-y));\n    height: var(--toast-height);\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    transform: translateY(150%);\n  }\n\n  &[data-limited] {\n    opacity: 0;\n  }\n\n  &[data-ending-style] {\n    opacity: 0;\n\n    &[data-swipe-direction='up'] {\n      transform: translateY(calc(var(--toast-swipe-movement-y) - 150%));\n    }\n    &[data-swipe-direction='left'] {\n      transform: translateX(calc(var(--toast-swipe-movement-x) - 150%)) translateY(var(--offset-y));\n    }\n    &[data-swipe-direction='right'] {\n      transform: translateX(calc(var(--toast-swipe-movement-x) + 150%)) translateY(var(--offset-y));\n    }\n    &[data-swipe-direction='down'] {\n      transform: translateY(calc(var(--toast-swipe-movement-y) + 150%));\n    }\n  }\n\n  &::after {\n    content: '';\n    position: absolute;\n    width: 100%;\n    top: 100%;\n    left: 0;\n    height: calc(var(--gap) + 1px);\n  }\n\n  &[data-type='success'] {\n    background-color: lightgreen;\n    color: black;\n  }\n\n  &[data-type='error'] {\n    background-color: lightpink;\n    color: black;\n  }\n}\n\n.Content {\n  overflow: hidden;\n  transition: opacity 0.25s;\n\n  &[data-behind] {\n    opacity: 0;\n  }\n\n  &[data-expanded] {\n    opacity: 1;\n  }\n}\n\n.Title {\n  font-weight: 700;\n  font-size: 0.975rem;\n  line-height: 1.25rem;\n  margin: 0;\n}\n\n.Description {\n  font-size: 0.925rem;\n  line-height: 1.25rem;\n  margin: 0;\n}\n\n.Close {\n  position: absolute;\n  top: 0.5rem;\n  right: 0.5rem;\n  padding: 0;\n  border: none;\n  background: transparent;\n  width: 1.25rem;\n  height: 1.25rem;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  border-radius: 0.25rem;\n\n  &:hover {\n    background-color: var(--color-gray-100);\n  }\n}\n\n.Icon {\n  width: 1rem;\n  height: 1rem;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toast/demos/promise/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Toast } from '@base-ui/react/toast';\nimport styles from './index.module.css';\n\nexport default function PromiseToastExample() {\n  return (\n    <Toast.Provider>\n      <PromiseDemo />\n      <Toast.Portal>\n        <Toast.Viewport className={styles.Viewport}>\n          <ToastList />\n        </Toast.Viewport>\n      </Toast.Portal>\n    </Toast.Provider>\n  );\n}\n\nfunction PromiseDemo() {\n  const toastManager = Toast.useToastManager();\n\n  function runPromise() {\n    toastManager.promise(\n      // Simulate an API request with a promise that resolves after 2 seconds\n      new Promise<string>((resolve, reject) => {\n        const shouldSucceed = Math.random() > 0.3; // 70% success rate\n        setTimeout(() => {\n          if (shouldSucceed) {\n            resolve('operation completed');\n          } else {\n            reject(new Error('operation failed'));\n          }\n        }, 2000);\n      }),\n      {\n        loading: 'Loading data…',\n        success: (data: string) => `Success: ${data}`,\n        error: (err: Error) => `Error: ${err.message}`,\n      },\n    );\n  }\n\n  return (\n    <button type=\"button\" onClick={runPromise} className={styles.Button}>\n      Run promise\n    </button>\n  );\n}\n\nfunction ToastList() {\n  const { toasts } = Toast.useToastManager();\n  return toasts.map((toast) => (\n    <Toast.Root key={toast.id} toast={toast} className={styles.Toast}>\n      <Toast.Content className={styles.Content}>\n        <Toast.Title className={styles.Title} />\n        <Toast.Description className={styles.Description} />\n        <Toast.Close className={styles.Close} aria-label=\"Close\">\n          <XIcon className={styles.Icon} />\n        </Toast.Close>\n      </Toast.Content>\n    </Toast.Root>\n  ));\n}\n\nfunction XIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M18 6 6 18\" />\n      <path d=\"m6 6 12 12\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toast/demos/promise/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\n\nexport const DemoToastPromise = createDemoWithVariants(import.meta.url, { CssModules });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toast/demos/undo/css-modules/index.module.css",
    "content": ".Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Viewport {\n  position: fixed;\n  z-index: 1;\n  width: 275px;\n  margin: 0 auto;\n  bottom: 1rem;\n  right: 1rem;\n  left: auto;\n  top: auto;\n\n  @media (min-width: 500px) {\n    bottom: 2rem;\n    right: 2rem;\n    width: 300px;\n  }\n}\n\n.Toast {\n  --gap: 0.75rem;\n  --peek: 0.75rem;\n  --scale: calc(max(0, 1 - (var(--toast-index) * 0.1)));\n  --shrink: calc(1 - var(--scale));\n  --height: var(--toast-frontmost-height, var(--toast-height));\n  --offset-y: calc(\n    var(--toast-offset-y) * -1 + (var(--toast-index) * var(--gap) * -1) +\n      var(--toast-swipe-movement-y)\n  );\n  position: absolute;\n  right: 0;\n  margin: 0 auto;\n  box-sizing: border-box;\n  background: var(--color-gray-50);\n  color: var(--color-gray-900);\n  border: 1px solid var(--color-gray-200);\n  padding: 1rem;\n  width: 100%;\n  box-shadow: 0 2px 10px rgb(0 0 0 / 0.1);\n  background-clip: padding-box;\n  border-radius: 0.5rem;\n  transform-origin: bottom center;\n  bottom: 0;\n  left: auto;\n  margin-right: 0;\n  -webkit-user-select: none;\n  user-select: none;\n  transition:\n    transform 0.5s cubic-bezier(0.22, 1, 0.36, 1),\n    opacity 0.5s,\n    height 0.15s;\n  cursor: default;\n  z-index: calc(1000 - var(--toast-index));\n  height: var(--height);\n  transform: translateX(var(--toast-swipe-movement-x))\n    translateY(\n      calc(\n        var(--toast-swipe-movement-y) - (var(--toast-index) * var(--peek)) -\n          (var(--shrink) * var(--height))\n      )\n    )\n    scale(var(--scale));\n\n  &[data-expanded] {\n    transform: translateX(var(--toast-swipe-movement-x)) translateY(var(--offset-y));\n    height: var(--toast-height);\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    transform: translateY(150%);\n  }\n\n  &[data-limited] {\n    opacity: 0;\n  }\n\n  &[data-ending-style] {\n    opacity: 0;\n\n    &[data-swipe-direction='up'] {\n      transform: translateY(calc(var(--toast-swipe-movement-y) - 150%));\n    }\n    &[data-swipe-direction='left'] {\n      transform: translateX(calc(var(--toast-swipe-movement-x) - 150%)) translateY(var(--offset-y));\n    }\n    &[data-swipe-direction='right'] {\n      transform: translateX(calc(var(--toast-swipe-movement-x) + 150%)) translateY(var(--offset-y));\n    }\n    &[data-swipe-direction='down'] {\n      transform: translateY(calc(var(--toast-swipe-movement-y) + 150%));\n    }\n  }\n\n  &::after {\n    content: '';\n    position: absolute;\n    width: 100%;\n    top: 100%;\n    left: 0;\n    height: calc(var(--gap) + 1px);\n  }\n}\n\n.Content {\n  overflow: hidden;\n  transition: opacity 0.25s;\n\n  &[data-behind] {\n    opacity: 0;\n  }\n\n  &[data-expanded] {\n    opacity: 1;\n  }\n}\n\n.Title {\n  font-weight: 700;\n  font-size: 0.975rem;\n  line-height: 1.25rem;\n  margin: 0;\n}\n\n.Description {\n  font-size: 0.925rem;\n  line-height: 1.25rem;\n  margin: 0;\n}\n\n.UndoButton {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  height: 2rem;\n  padding: 0 0.75rem;\n  font-size: 0.875rem;\n  font-weight: 400;\n  line-height: 1.25rem;\n  border-radius: 0.25rem;\n  margin-top: 0.5rem;\n  background-color: var(--color-gray-900);\n  color: var(--color-gray-50);\n  border: none;\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Close {\n  position: absolute;\n  top: 0.5rem;\n  right: 0.5rem;\n  padding: 0;\n  border: none;\n  background: transparent;\n  width: 1.25rem;\n  height: 1.25rem;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  border-radius: 0.25rem;\n\n  &:hover {\n    background-color: var(--color-gray-100);\n  }\n}\n\n.Icon {\n  width: 1rem;\n  height: 1rem;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toast/demos/undo/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Toast } from '@base-ui/react/toast';\nimport styles from './index.module.css';\n\nexport default function UndoToastExample() {\n  return (\n    <Toast.Provider>\n      <Form />\n      <Toast.Portal>\n        <Toast.Viewport className={styles.Viewport}>\n          <ToastList />\n        </Toast.Viewport>\n      </Toast.Portal>\n    </Toast.Provider>\n  );\n}\n\nfunction Form() {\n  const toastManager = Toast.useToastManager();\n\n  function action() {\n    const id = toastManager.add({\n      title: 'Action performed',\n      description: 'You can undo this action.',\n      type: 'success',\n      actionProps: {\n        children: 'Undo',\n        onClick() {\n          toastManager.close(id);\n          toastManager.add({\n            title: 'Action undone',\n          });\n        },\n      },\n    });\n  }\n\n  return (\n    <button type=\"button\" onClick={action} className={styles.Button}>\n      Perform action\n    </button>\n  );\n}\n\nfunction ToastList() {\n  const { toasts } = Toast.useToastManager();\n  return toasts.map((toast) => (\n    <Toast.Root key={toast.id} toast={toast} className={styles.Toast}>\n      <Toast.Content className={styles.Content}>\n        <Toast.Title className={styles.Title} />\n        <Toast.Description className={styles.Description} />\n        <Toast.Action className={styles.UndoButton} />\n        <Toast.Close className={styles.Close} aria-label=\"Close\">\n          <XIcon className={styles.Icon} />\n        </Toast.Close>\n      </Toast.Content>\n    </Toast.Root>\n  ));\n}\n\nfunction XIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M18 6 6 18\" />\n      <path d=\"m6 6 12 12\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toast/demos/undo/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\n\nexport const DemoToastUndo = createDemoWithVariants(import.meta.url, { CssModules });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toast/demos/varying-heights/css-modules/index.module.css",
    "content": ".Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Viewport {\n  position: fixed;\n  z-index: 1;\n  width: 250px;\n  margin: 0 auto;\n  bottom: 1rem;\n  right: 1rem;\n  left: auto;\n  top: auto;\n\n  @media (min-width: 500px) {\n    bottom: 2rem;\n    right: 2rem;\n    width: 300px;\n  }\n}\n\n.Toast {\n  --gap: 0.75rem;\n  --peek: 0.75rem;\n  --scale: calc(max(0, 1 - (var(--toast-index) * 0.1)));\n  --shrink: calc(1 - var(--scale));\n  --height: var(--toast-frontmost-height, var(--toast-height));\n  --offset-y: calc(\n    var(--toast-offset-y) * -1 + (var(--toast-index) * var(--gap) * -1) +\n      var(--toast-swipe-movement-y)\n  );\n  position: absolute;\n  right: 0;\n  margin: 0 auto;\n  box-sizing: border-box;\n  background: var(--color-gray-50);\n  color: var(--color-gray-900);\n  border: 1px solid var(--color-gray-200);\n  padding: 1rem;\n  width: 100%;\n  box-shadow: 0 2px 10px rgb(0 0 0 / 0.1);\n  background-clip: padding-box;\n  border-radius: 0.5rem;\n  bottom: 0;\n  left: auto;\n  margin-right: 0;\n  -webkit-user-select: none;\n  user-select: none;\n  transition:\n    transform 0.5s cubic-bezier(0.22, 1, 0.36, 1),\n    opacity 0.5s,\n    height 0.15s;\n  cursor: default;\n  z-index: calc(1000 - var(--toast-index));\n  height: var(--height);\n  transform-origin: bottom center;\n  transform: translateX(var(--toast-swipe-movement-x))\n    translateY(\n      calc(\n        var(--toast-swipe-movement-y) - (var(--toast-index) * var(--peek)) -\n          (var(--shrink) * var(--height))\n      )\n    )\n    scale(var(--scale));\n\n  &[data-expanded] {\n    transform: translateX(var(--toast-swipe-movement-x)) translateY(var(--offset-y));\n    height: var(--toast-height);\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    transform: translateY(150%);\n  }\n\n  &[data-limited] {\n    opacity: 0;\n  }\n\n  &[data-ending-style] {\n    opacity: 0;\n\n    &[data-swipe-direction='up'] {\n      transform: translateY(calc(var(--toast-swipe-movement-y) - 150%));\n    }\n    &[data-swipe-direction='left'] {\n      transform: translateX(calc(var(--toast-swipe-movement-x) - 150%)) translateY(var(--offset-y));\n    }\n    &[data-swipe-direction='right'] {\n      transform: translateX(calc(var(--toast-swipe-movement-x) + 150%)) translateY(var(--offset-y));\n    }\n    &[data-swipe-direction='down'] {\n      transform: translateY(calc(var(--toast-swipe-movement-y) + 150%));\n    }\n  }\n\n  &::after {\n    content: '';\n    position: absolute;\n    width: 100%;\n    top: 100%;\n    left: 0;\n    height: calc(var(--gap) + 1px);\n  }\n}\n\n.Content {\n  overflow: hidden;\n  transition: opacity 0.25s;\n\n  &[data-behind] {\n    opacity: 0;\n  }\n\n  &[data-expanded] {\n    opacity: 1;\n  }\n}\n\n.Title {\n  font-weight: 700;\n  font-size: 0.975rem;\n  line-height: 1.25rem;\n  margin: 0;\n}\n\n.Description {\n  font-size: 0.925rem;\n  line-height: 1.25rem;\n  margin: 0;\n}\n\n.Close {\n  position: absolute;\n  top: 0.5rem;\n  right: 0.5rem;\n  padding: 0;\n  border: none;\n  background: transparent;\n  width: 1.25rem;\n  height: 1.25rem;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  border-radius: 0.25rem;\n\n  &:hover {\n    background-color: var(--color-gray-100);\n  }\n}\n\n.Icon {\n  width: 1rem;\n  height: 1rem;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toast/demos/varying-heights/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Toast } from '@base-ui/react/toast';\nimport styles from './index.module.css';\n\nexport default function VaryingHeightsToast() {\n  return (\n    <Toast.Provider>\n      <ToastButton />\n      <Toast.Portal>\n        <Toast.Viewport className={styles.Viewport}>\n          <ToastList />\n        </Toast.Viewport>\n      </Toast.Portal>\n    </Toast.Provider>\n  );\n}\n\nfunction ToastButton() {\n  const toastManager = Toast.useToastManager();\n  const [count, setCount] = React.useState(0);\n\n  function createToast() {\n    setCount((prev) => prev + 1);\n    const description = TEXTS[Math.floor(Math.random() * TEXTS.length)];\n    toastManager.add({\n      title: `Toast ${count + 1} created`,\n      description,\n    });\n  }\n\n  return (\n    <button type=\"button\" className={styles.Button} onClick={createToast}>\n      Create varying height toast\n    </button>\n  );\n}\n\nfunction ToastList() {\n  const { toasts } = Toast.useToastManager();\n  return toasts.map((toast) => (\n    <Toast.Root key={toast.id} toast={toast} className={styles.Toast}>\n      <Toast.Content className={styles.Content}>\n        <Toast.Title className={styles.Title} />\n        <Toast.Description className={styles.Description} />\n        <Toast.Close className={styles.Close} aria-label=\"Close\">\n          <XIcon className={styles.Icon} />\n        </Toast.Close>\n      </Toast.Content>\n    </Toast.Root>\n  ));\n}\n\nfunction XIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M18 6 6 18\" />\n      <path d=\"m6 6 12 12\" />\n    </svg>\n  );\n}\n\nconst TEXTS = [\n  'Short message.',\n  'A bit longer message that spans two lines.',\n  'This is a longer description that intentionally takes more vertical space to demonstrate stacking with varying heights.',\n  'An even longer description that should span multiple lines so we can verify the clamped collapsed height and smooth expansion animation when hovering or focusing the viewport.',\n];\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toast/demos/varying-heights/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\n\nexport const DemoToastVaryingHeights = createDemoWithVariants(import.meta.url, { CssModules });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toast/page.mdx",
    "content": "# Toast\n\n<Subtitle>Generates toast notifications.</Subtitle>\n\n<Meta\n  name=\"description\"\n  content=\"A high-quality, unstyled React toast component to generate notifications.\"\n/>\n\nimport { DemoToastHero } from './demos/hero';\n\n<DemoToastHero />\n\n## Anatomy\n\nImport the component and assemble its parts:\n\n```jsx title=\"Anatomy\"\nimport { Toast } from '@base-ui/react/toast';\n\n<Toast.Provider>\n  <Toast.Portal>\n    <Toast.Viewport>\n      {/* Stacked toasts */}\n      <Toast.Root>\n        <Toast.Content>\n          <Toast.Title />\n          <Toast.Description />\n          <Toast.Action />\n          <Toast.Close />\n        </Toast.Content>\n      </Toast.Root>\n\n      {/* Anchored toasts */}\n      <Toast.Positioner>\n        <Toast.Root>\n          <Toast.Arrow />\n          <Toast.Content>\n            <Toast.Title />\n            <Toast.Description />\n            <Toast.Action />\n            <Toast.Close />\n          </Toast.Content>\n        </Toast.Root>\n      </Toast.Positioner>\n    </Toast.Viewport>\n  </Toast.Portal>\n</Toast.Provider>;\n```\n\n## General usage\n\n- `<Toast.Provider>` can be wrapped around your entire app, ensuring all toasts are rendered in the same viewport.\n- <kbd>F6</kbd> lets users jump into the toast viewport landmark region to navigate toasts with\n  keyboard focus.\n- The `data-base-ui-swipe-ignore` attribute can be manually added to elements inside of a toast to prevent swipe-to-dismiss gestures on them. Interactive elements are automatically prevented.\n\n## Global manager\n\nA global toast manager can be created by passing the `toastManager` prop to the `<Toast.Provider>`.\nThis enables you to queue a toast from anywhere in the app (such as in functions outside the React tree) while still using the same toast renderer.\n\nThe created `toastManager` object has the same properties and methods as the `Toast.useToastManager()` hook.\n\n```tsx title=\"Creating a manager instance\"\nconst toastManager = Toast.createToastManager();\n```\n\n```jsx title=\"Using the instance\"\n<Toast.Provider toastManager={toastManager}>\n```\n\n## Stacking and animations\n\nThe `--toast-index` CSS variable can be used to determine the stacking order of the toasts.\nThe 0th index toast appears at the front.\n\n```css title=\"z-index stacking\"\n.Toast {\n  z-index: calc(1000 - var(--toast-index));\n  transform: scale(1 - calc(0.1 * var(--toast-index)));\n}\n```\n\nThe `--toast-offset-y` CSS variable can be used to determine the vertical offset of the toasts when positioned absolutely with a translation offset — this is usually used with the `data-expanded` attribute, present when the toast viewport is being hovered or has focus.\n\n```css title=\"Expanded offset\"\n.Toast[data-expanded] {\n  transform: translateY(var(--toast-offset-y));\n}\n```\n\n`<Toast.Content>` is used to hide overflow from taller toasts while the stack is collapsed.\nThe `data-behind` attribute marks content that sits behind the frontmost toast and pairs with the `data-expanded` attribute so the content fades back in when the viewport expands:\n\n```css title=\"Collapsed content\" \"data-behind\" \"data-expanded\"\n.ToastContent {\n  overflow: hidden;\n  transition: opacity 0.25s;\n}\n\n.ToastContent[data-behind] {\n  opacity: 0;\n}\n\n.ToastContent[data-expanded] {\n  opacity: 1;\n}\n```\n\nThe `--toast-swipe-movement-x` and `--toast-swipe-movement-y` CSS variables are used to determine the swipe movement of the toasts in order to add a translation offset.\n\n```css title=\"Swipe offset\"  \"--toast-swipe-movement-x\" \"--toast-swipe-movement-y\"\n.Toast {\n  transform: scale(1 - calc(0.1 * var(--toast-index))) translateX(var(--toast-swipe-movement-x))\n    translateY(calc(var(--toast-swipe-movement-y) + (var(--toast-index) * -20%)));\n}\n```\n\nThe `data-swipe-direction` attribute can be used to determine the swipe direction of the toasts to add a translation offset upon dismissal.\n\n```css title=\"Swipe direction\" \"data-swipe-direction\"\n&[data-ending-style] {\n  opacity: 0;\n\n  &[data-swipe-direction='up'] {\n    transform: translateY(calc(var(--toast-swipe-movement-y) - 150%));\n  }\n  &[data-swipe-direction='down'] {\n    transform: translateY(calc(var(--toast-swipe-movement-y) + 150%));\n  }\n  /* Note: --offset-y is defined locally in these examples and derives from\n   --toast-offset-y, --toast-index, and swipe movement values */\n  &[data-swipe-direction='left'] {\n    transform: translateX(calc(var(--toast-swipe-movement-x) - 150%)) translateY(var(--offset-y));\n  }\n  &[data-swipe-direction='right'] {\n    transform: translateX(calc(var(--toast-swipe-movement-x) + 150%)) translateY(var(--offset-y));\n  }\n}\n```\n\nThe `data-limited` attribute indicates that the toast was removed from the list due to exceeding the `limit` option.\nThis is useful for animating the toast differently when it is removed from the list.\n\n## Examples\n\n### Anchored toasts\n\nToasts can be anchored to a specific element using `<Toast.Positioner>` and the `positionerProps` option when adding a toast. This is useful for showing contextual feedback like transient \"Copied\" toasts that appear near the button that triggered the action.\n\nAnchored toasts should be rendered in a separate `<Toast.Provider>` from stacked toasts. A global toast manager can be created for each to manage them separately throughout your app:\n\n```tsx title=\"Mixing stacked and anchored toasts\"\nconst anchoredToastManager = Toast.createToastManager();\nconst stackedToastManager = Toast.createToastManager();\n\nfunction App() {\n  return (\n    <React.Fragment>\n      <Toast.Provider toastManager={anchoredToastManager}>\n        <AnchoredToasts />\n      </Toast.Provider>\n      <Toast.Provider toastManager={stackedToastManager}>\n        <StackedToasts />\n      </Toast.Provider>\n\n      {/* App content */}\n    </React.Fragment>\n  );\n}\n\nfunction AnchoredToasts() {\n  const { toasts } = Toast.useToastManager();\n  return (\n    <Toast.Portal>\n      <Toast.Viewport>\n        {toasts.map((toast) => (\n          <Toast.Positioner key={toast.id} toast={toast}>\n            <Toast.Root toast={toast}>{/* ... */}</Toast.Root>\n          </Toast.Positioner>\n        ))}\n      </Toast.Viewport>\n    </Toast.Portal>\n  );\n}\n\nfunction StackedToasts() {\n  const { toasts } = Toast.useToastManager();\n  return (\n    <Toast.Portal>\n      <Toast.Viewport>\n        {toasts.map((toast) => (\n          <Toast.Root key={toast.id} toast={toast}>\n            {/* ... */}\n          </Toast.Root>\n        ))}\n      </Toast.Viewport>\n    </Toast.Portal>\n  );\n}\n```\n\nimport { DemoToastAnchored } from './demos/anchored';\n\n<DemoToastAnchored compact />\n\n### Custom position\n\nThe position of the toasts is controlled by your own CSS.\nTo change the toasts' position, you can modify the `.Viewport` and `.Root` styles.\nA more general component could accept a `data-position` attribute, which the CSS handles for each variation.\nThe following shows a top-center position:\n\nimport { DemoToastPosition } from './demos/position';\n\n<DemoToastPosition compact />\n\n### Undo action\n\nWhen adding a toast, the `actionProps` option can be used to define props for an action button inside of it—this enables the ability to undo an action associated with the toast.\n\nimport { DemoToastUndo } from './demos/undo';\n\n<DemoToastUndo compact />\n\n### Promise\n\nAn asynchronous toast can be created with three possible states: `loading`, `success`, and `error`.\nThe `type` string matches these states to change the styling.\nEach of the states also accepts the [method options](/react/components/toast#method-options) object for more granular control.\n\nimport { DemoToastPromise } from './demos/promise';\n\n<DemoToastPromise compact />\n\n### Custom\n\nA toast with custom data can be created by passing any typed object interface to the `data` option.\nThis enables you to pass any data (including functions) you need to the toast and access it in the toast's rendering logic.\n\nimport { DemoToastCustom } from './demos/custom';\n\n<DemoToastCustom compact />\n\n### Varying heights\n\nToasts with varying heights are stacked by ensuring that the `<Toast.Content>` element has `overflow: hidden` set, along with all toasts' heights matching the frontmost toast at index 0.\nThis prevents taller toasts from overflowing the stack when collapsed.\n\nimport { DemoToastVaryingHeights } from './demos/varying-heights';\n\n<DemoToastVaryingHeights compact />\n\n## API reference\n\n<Reference\n  component=\"Toast\"\n  parts=\"Provider, Viewport, Portal, Root, Content, Title, Description, Action, Close, Positioner, Arrow\"\n/>\n\n## useToastManager\n\nManages toasts, called inside of a `<Toast.Provider>`.\n\n```tsx title=\"Usage\"\nconst toastManager = Toast.useToastManager();\n```\n\n### Return value\n\n<PropsReferenceTable\n  type=\"return\"\n  data={{\n    toasts: {\n      type: 'Toast.Root.ToastObject[]',\n      description: 'The array of toast objects.',\n    },\n    add: {\n      type: '(options: ToastManagerAddOptions) => string',\n      description: 'Add a toast to the toast list.',\n    },\n    close: {\n      type: '(toastId?: string) => void',\n      description:\n        'Closes and removes a toast from the toast list. If no ID is passed, all toasts will be closed.',\n    },\n    update: {\n      type: '(toastId: string, options: ToastManagerUpdateOptions) => void',\n      description: 'Update a toast in the toast list.',\n    },\n    promise: {\n      type: '<Value>(promise: Promise<Value>, options: ToastManagerPromiseOptions) => Promise<Value>',\n      description:\n        'Create a toast that resolves with a value, with three possible states for the toast: `loading`, `success`, and `error`.',\n    },\n  }}\n/>\n\n### Method options\n\n<PropsReferenceTable\n  data={{\n    title: {\n      type: 'React.ReactNode',\n      description: 'The title of the toast.',\n    },\n    description: {\n      type: 'React.ReactNode',\n      description: 'The description of the toast.',\n    },\n    type: {\n      type: 'string',\n      description:\n        'The type of the toast. Used to conditionally style the toast or render different elements.',\n    },\n    timeout: {\n      type: 'number',\n      description: 'The amount of time (in ms) before the toast is auto dismissed.',\n    },\n    priority: {\n      type: \"'low' | 'high'\",\n      description: `\n        The priority of the toast.\n        - \\`low\\` - The toast will be announced politely.\n        - \\`high\\` - The toast will be announced urgently.\n      `,\n      default: \"'low'\",\n    },\n    onClose: {\n      type: '() => void',\n      description: 'A callback invoked when the toast is closed.',\n    },\n    onRemove: {\n      type: '() => void',\n      description:\n        'A callback invoked when the toast is removed from the list after animations complete when closed.',\n    },\n    actionProps: {\n      type: \"React.ComponentPropsWithRef<'button'>\",\n      description: 'The props of the action button.',\n    },\n    data: {\n      type: 'Record<string, unknown>',\n      description: 'The data of the toast.',\n    },\n  }}\n/>\n\n### `add` method\n\nCreates a toast by adding it to the toast list.\n\nReturns a `toastId` that can be used to update or close the toast later.\n\n```jsx title=\"Usage\"\nconst toastId = toastManager.add({\n  description: 'Hello, world!',\n});\n```\n\n```jsx title=\"Example\" {2,7-9}\nfunction App() {\n  const toastManager = Toast.useToastManager();\n  return (\n    <button\n      type=\"button\"\n      onClick={() => {\n        toastManager.add({\n          description: 'Hello, world!',\n        });\n      }}\n    >\n      Add toast\n    </button>\n  );\n}\n```\n\nFor high priority toasts, the `title` and `description` strings are what are used to announce the toast to screen readers.\nScreen readers do not announce any extra content rendered inside `<Toast.Root>`, including the `<Toast.Title>` or `<Toast.Description>` components, unless they intentionally navigate to the toast viewport.\n\n### `update` method\n\nUpdates the toast with new options.\n\n```jsx title=\"Usage\"\ntoastManager.update(toastId, {\n  description: 'New description',\n});\n```\n\n### `close` method\n\nCloses the toast, removing it from the toast list after any animations complete.\n\n```jsx title=\"Usage\"\ntoastManager.close(toastId);\n```\n\nOr you can close all toasts at once by not passing an ID:\n\n```jsx title=\"Close all toasts\"\ntoastManager.close();\n```\n\n### `promise` method\n\nCreates an asynchronous toast with three possible states: `loading`, `success`, and `error`.\n\n```tsx title=\"Description configuration\"\nconst promise = toastManager.promise(\n  new Promise((resolve) => {\n    setTimeout(() => resolve('world!'), 1000);\n  }),\n  {\n    // Each are a shortcut for the `description` option\n    loading: 'Loading…',\n    success: (data) => `Hello ${data}`,\n    error: (err) => `Error: ${err}`,\n  },\n);\n```\n\nEach state also accepts the [method options](/react/components/toast#method-options) object to granularly control the toast for each state:\n\n```tsx title=\"Method options configuration\"\nconst promise = toastManager.promise(\n  new Promise((resolve) => {\n    setTimeout(() => resolve('world!'), 1000);\n  }),\n  {\n    loading: {\n      title: 'Loading…',\n      description: 'The promise is loading.',\n    },\n    success: {\n      title: 'Success',\n      description: 'The promise resolved successfully.',\n    },\n    error: {\n      title: 'Error',\n      description: 'The promise rejected.',\n      actionProps: {\n        children: 'Contact support',\n        onClick() {\n          // Redirect to support page\n        },\n      },\n    },\n  },\n);\n```\n\nexport const metadata = {\n  keywords: [\n    'React Toast',\n    'Toast Notification',\n    'Toast Notifications',\n    'Notification Component',\n    'Notification',\n    'Snackbar',\n    'Message Notification',\n    'Alert Notification',\n    'Alert Message',\n    'Flash Message',\n    'Push Notification',\n    'Temporary Message',\n    'Dismissible Notification',\n    'Toast Message',\n    'Toast Manager',\n    'Anchored Toast',\n    'Swipe Dismiss Toast',\n    'Accessible Toast',\n    'Headless React Components',\n    'Base UI',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toggle/demos/hero/css-modules/index.module.css",
    "content": ".Panel {\n  display: flex;\n  gap: 1px;\n  border: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-50);\n  border-radius: 0.375rem;\n  padding: 0.125rem;\n}\n\n.Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 2rem;\n  height: 2rem;\n  padding: 0;\n  margin: 0;\n  outline: 0;\n  border: 0;\n  border-radius: 0.25rem;\n  background-color: transparent;\n  color: var(--color-gray-600);\n  user-select: none;\n\n  &:focus-visible {\n    background-color: transparent;\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-200);\n  }\n\n  &[data-pressed] {\n    color: var(--color-gray-900);\n  }\n}\n\n.Icon {\n  width: 1.25rem;\n  height: 1.25rem;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toggle/demos/hero/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Toggle } from '@base-ui/react/toggle';\nimport styles from './index.module.css';\n\nexport default function ExampleToggle() {\n  return (\n    <div className={styles.Panel}>\n      <Toggle\n        aria-label=\"Favorite\"\n        className={styles.Button}\n        render={(props, state) => {\n          if (state.pressed) {\n            return (\n              <button type=\"button\" {...props}>\n                <HeartFilledIcon className={styles.Icon} />\n              </button>\n            );\n          }\n\n          return (\n            <button type=\"button\" {...props}>\n              <HeartOutlineIcon className={styles.Icon} />\n            </button>\n          );\n        }}\n      />\n    </div>\n  );\n}\n\nfunction HeartFilledIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentcolor\" {...props}>\n      <path d=\"M7.99961 13.8667C7.88761 13.8667 7.77561 13.8315 7.68121 13.7611C7.43321 13.5766 1.59961 9.1963 1.59961 5.8667C1.59961 3.80856 3.27481 2.13336 5.33294 2.13336C6.59054 2.13336 7.49934 2.81176 7.99961 3.3131C8.49988 2.81176 9.40868 2.13336 10.6663 2.13336C12.7244 2.13336 14.3996 3.80803 14.3996 5.8667C14.3996 9.1963 8.56601 13.5766 8.31801 13.7616C8.22361 13.8315 8.11161 13.8667 7.99961 13.8667Z\" />\n    </svg>\n  );\n}\n\nfunction HeartOutlineIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentcolor\" {...props}>\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M7.99961 4.8232L7.24456 4.06654C6.84123 3.66235 6.18866 3.20003 5.33294 3.20003C3.86391 3.20003 2.66628 4.39767 2.66628 5.8667C2.66628 6.4079 2.91276 7.1023 3.41967 7.91383C3.91548 8.70759 4.59649 9.51244 5.31278 10.2503C6.38267 11.3525 7.47318 12.2465 7.99983 12.6605C8.52734 12.2456 9.61718 11.352 10.6864 10.2504C11.4027 9.51248 12.0837 8.70762 12.5796 7.91384C13.0865 7.1023 13.3329 6.4079 13.3329 5.8667C13.3329 4.39723 12.1354 3.20003 10.6663 3.20003C9.81056 3.20003 9.15799 3.66235 8.75466 4.06654L7.99961 4.8232ZM7.98574 3.29926C7.48264 2.79938 6.57901 2.13336 5.33294 2.13336C3.27481 2.13336 1.59961 3.80856 1.59961 5.8667C1.59961 9.1963 7.43321 13.5766 7.68121 13.7611C7.77561 13.8315 7.88761 13.8667 7.99961 13.8667C8.11161 13.8667 8.22361 13.8315 8.31801 13.7616C8.56601 13.5766 14.3996 9.1963 14.3996 5.8667C14.3996 3.80803 12.7244 2.13336 10.6663 2.13336C9.42013 2.13336 8.51645 2.79947 8.01337 3.29936C8.00877 3.30393 8.00421 3.30849 7.99967 3.31303C7.99965 3.31305 7.99963 3.31307 7.99961 3.3131C7.99502 3.3085 7.9904 3.30389 7.98574 3.29926Z\"\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toggle/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoToggleHero = createDemoWithVariants(import.meta.url, { CssModules, Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toggle/demos/hero/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Toggle } from '@base-ui/react/toggle';\n\nexport default function ExampleToggle() {\n  return (\n    <div className=\"flex gap-px rounded-md border border-gray-200 bg-gray-50 p-0.5\">\n      <Toggle\n        aria-label=\"Favorite\"\n        className=\"flex size-8 items-center justify-center rounded-xs text-gray-600 select-none hover:bg-gray-100 focus-visible:bg-none focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-200 data-[pressed]:text-gray-900\"\n        render={(props, state) => {\n          if (state.pressed) {\n            return (\n              <button type=\"button\" {...props}>\n                <HeartFilledIcon className=\"size-5\" />\n              </button>\n            );\n          }\n\n          return (\n            <button type=\"button\" {...props}>\n              <HeartOutlineIcon className=\"size-5\" />\n            </button>\n          );\n        }}\n      />\n    </div>\n  );\n}\n\nfunction HeartFilledIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentcolor\" {...props}>\n      <path d=\"M7.99961 13.8667C7.88761 13.8667 7.77561 13.8315 7.68121 13.7611C7.43321 13.5766 1.59961 9.1963 1.59961 5.8667C1.59961 3.80856 3.27481 2.13336 5.33294 2.13336C6.59054 2.13336 7.49934 2.81176 7.99961 3.3131C8.49988 2.81176 9.40868 2.13336 10.6663 2.13336C12.7244 2.13336 14.3996 3.80803 14.3996 5.8667C14.3996 9.1963 8.56601 13.5766 8.31801 13.7616C8.22361 13.8315 8.11161 13.8667 7.99961 13.8667Z\" />\n    </svg>\n  );\n}\n\nfunction HeartOutlineIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentcolor\" {...props}>\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M7.99961 4.8232L7.24456 4.06654C6.84123 3.66235 6.18866 3.20003 5.33294 3.20003C3.86391 3.20003 2.66628 4.39767 2.66628 5.8667C2.66628 6.4079 2.91276 7.1023 3.41967 7.91383C3.91548 8.70759 4.59649 9.51244 5.31278 10.2503C6.38267 11.3525 7.47318 12.2465 7.99983 12.6605C8.52734 12.2456 9.61718 11.352 10.6864 10.2504C11.4027 9.51248 12.0837 8.70762 12.5796 7.91384C13.0865 7.1023 13.3329 6.4079 13.3329 5.8667C13.3329 4.39723 12.1354 3.20003 10.6663 3.20003C9.81056 3.20003 9.15799 3.66235 8.75466 4.06654L7.99961 4.8232ZM7.98574 3.29926C7.48264 2.79938 6.57901 2.13336 5.33294 2.13336C3.27481 2.13336 1.59961 3.80856 1.59961 5.8667C1.59961 9.1963 7.43321 13.5766 7.68121 13.7611C7.77561 13.8315 7.88761 13.8667 7.99961 13.8667C8.11161 13.8667 8.22361 13.8315 8.31801 13.7616C8.56601 13.5766 14.3996 9.1963 14.3996 5.8667C14.3996 3.80803 12.7244 2.13336 10.6663 2.13336C9.42013 2.13336 8.51645 2.79947 8.01337 3.29936C8.00877 3.30393 8.00421 3.30849 7.99967 3.31303C7.99965 3.31305 7.99963 3.31307 7.99961 3.3131C7.99502 3.3085 7.9904 3.30389 7.98574 3.29926Z\"\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toggle/page.mdx",
    "content": "# Toggle\n\n<Subtitle>A two-state button that can be on or off.</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"A high-quality, unstyled React toggle component that displays a two-state button that can be on or off.\"\n/>\n\nimport { DemoToggleHero } from './demos/hero';\n\n<DemoToggleHero />\n\n## Anatomy\n\nImport the component and use it as a single part:\n\n```jsx title=\"Anatomy\"\nimport { Toggle } from '@base-ui/react/toggle';\n\n<Toggle />;\n```\n\n## API reference\n\n<Reference component=\"Toggle\" />\n\nexport const metadata = {\n  keywords: [\n    'React Toggle',\n    'Toggle Button Component',\n    'Two State Button',\n    'Press Button',\n    'Push Toggle',\n    'Pressed State Button',\n    'Toggle Control',\n    'ARIA Pressed Control',\n    'Accessible Toggle',\n    'Headless React Components',\n    'Base UI',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toggle-group/demos/hero/css-modules/index.module.css",
    "content": ".Panel {\n  display: flex;\n  gap: 1px;\n  border: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-50);\n  border-radius: 0.375rem;\n  padding: 0.125rem;\n}\n\n.Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 2rem;\n  height: 2rem;\n  padding: 0;\n  margin: 0;\n  outline: 0;\n  border: 0;\n  border-radius: 0.25rem;\n  background-color: transparent;\n  color: var(--color-gray-600);\n  user-select: none;\n\n  &:focus-visible {\n    background-color: transparent;\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n\n  &:active {\n    background-color: var(--color-gray-200);\n  }\n\n  &[data-pressed] {\n    background-color: var(--color-gray-100);\n    color: var(--color-gray-900);\n  }\n}\n\n.Icon {\n  width: 1rem;\n  height: 1rem;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toggle-group/demos/hero/css-modules/index.tsx",
    "content": "import * as React from 'react';\nimport { Toggle } from '@base-ui/react/toggle';\nimport { ToggleGroup } from '@base-ui/react/toggle-group';\nimport styles from './index.module.css';\n\nexport default function ExampleToggleGroup() {\n  return (\n    <ToggleGroup defaultValue={['left']} className={styles.Panel}>\n      <Toggle aria-label=\"Align left\" value=\"left\" className={styles.Button}>\n        <AlignLeftIcon className={styles.Icon} />\n      </Toggle>\n      <Toggle aria-label=\"Align center\" value=\"center\" className={styles.Button}>\n        <AlignCenterIcon className={styles.Icon} />\n      </Toggle>\n      <Toggle aria-label=\"Align right\" value=\"right\" className={styles.Button}>\n        <AlignRightIcon className={styles.Icon} />\n      </Toggle>\n    </ToggleGroup>\n  );\n}\n\nfunction AlignLeftIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      stroke=\"currentcolor\"\n      strokeLinecap=\"round\"\n      {...props}\n    >\n      <path d=\"M2.5 3.5H13.5\" />\n      <path d=\"M2.5 9.5H13.5\" />\n      <path d=\"M2.5 6.5H10.5\" />\n      <path d=\"M2.5 12.5H10.5\" />\n    </svg>\n  );\n}\n\nfunction AlignCenterIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      stroke=\"currentcolor\"\n      strokeLinecap=\"round\"\n      {...props}\n    >\n      <path d=\"M3 3.5H14\" />\n      <path d=\"M3 9.5H14\" />\n      <path d=\"M4.5 6.5H12.5\" />\n      <path d=\"M4.5 12.5H12.5\" />\n    </svg>\n  );\n}\n\nfunction AlignRightIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      stroke=\"currentcolor\"\n      strokeLinecap=\"round\"\n      {...props}\n    >\n      <path d=\"M2.5 3.5H13.5\" />\n      <path d=\"M2.5 9.5H13.5\" />\n      <path d=\"M5.5 6.5H13.5\" />\n      <path d=\"M5.5 12.5H13.5\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toggle-group/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoToggleGroupHero = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toggle-group/demos/hero/tailwind/index.tsx",
    "content": "import * as React from 'react';\nimport { Toggle } from '@base-ui/react/toggle';\nimport { ToggleGroup } from '@base-ui/react/toggle-group';\n\nexport default function ExampleToggleGroup() {\n  return (\n    <ToggleGroup\n      defaultValue={['left']}\n      className=\"flex gap-px rounded-md border border-gray-200 bg-gray-50 p-0.5\"\n    >\n      <Toggle\n        aria-label=\"Align left\"\n        value=\"left\"\n        className=\"flex size-8 items-center justify-center rounded-sm text-gray-600 select-none focus-visible:bg-none focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-200 data-[pressed]:bg-gray-100 data-[pressed]:text-gray-900\"\n      >\n        <AlignLeftIcon className=\"size-4\" />\n      </Toggle>\n      <Toggle\n        aria-label=\"Align center\"\n        value=\"center\"\n        className=\"flex size-8 items-center justify-center rounded-sm text-gray-600 select-none focus-visible:bg-none focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-200 data-[pressed]:bg-gray-100 data-[pressed]:text-gray-900\"\n      >\n        <AlignCenterIcon className=\"size-4\" />\n      </Toggle>\n      <Toggle\n        aria-label=\"Align right\"\n        value=\"right\"\n        className=\"flex size-8 items-center justify-center rounded-sm text-gray-600 select-none focus-visible:bg-none focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-200 data-[pressed]:bg-gray-100 data-[pressed]:text-gray-900\"\n      >\n        <AlignRightIcon className=\"size-4\" />\n      </Toggle>\n    </ToggleGroup>\n  );\n}\n\nfunction AlignLeftIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      stroke=\"currentcolor\"\n      strokeLinecap=\"round\"\n      {...props}\n    >\n      <path d=\"M2.5 3.5H13.5\" />\n      <path d=\"M2.5 9.5H13.5\" />\n      <path d=\"M2.5 6.5H10.5\" />\n      <path d=\"M2.5 12.5H10.5\" />\n    </svg>\n  );\n}\n\nfunction AlignCenterIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      stroke=\"currentcolor\"\n      strokeLinecap=\"round\"\n      {...props}\n    >\n      <path d=\"M3 3.5H14\" />\n      <path d=\"M3 9.5H14\" />\n      <path d=\"M4.5 6.5H12.5\" />\n      <path d=\"M4.5 12.5H12.5\" />\n    </svg>\n  );\n}\n\nfunction AlignRightIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      stroke=\"currentcolor\"\n      strokeLinecap=\"round\"\n      {...props}\n    >\n      <path d=\"M2.5 3.5H13.5\" />\n      <path d=\"M2.5 9.5H13.5\" />\n      <path d=\"M5.5 6.5H13.5\" />\n      <path d=\"M5.5 12.5H13.5\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toggle-group/demos/multiple/css-modules/index.module.css",
    "content": ".Panel {\n  display: flex;\n  gap: 1px;\n  border: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-50);\n  border-radius: 0.375rem;\n  padding: 0.125rem;\n}\n\n.Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 2rem;\n  height: 2rem;\n  padding: 0;\n  margin: 0;\n  outline: 0;\n  border: 0;\n  border-radius: 0.25rem;\n  background-color: transparent;\n  font-family: inherit;\n  font-size: 1rem;\n  color: var(--color-gray-600);\n  user-select: none;\n\n  &:focus-visible {\n    background-color: transparent;\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n\n  &:active {\n    background-color: var(--color-gray-200);\n  }\n\n  &[data-pressed] {\n    background-color: var(--color-gray-100);\n    color: var(--color-gray-900);\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toggle-group/demos/multiple/css-modules/index.tsx",
    "content": "import { Toggle } from '@base-ui/react/toggle';\nimport { ToggleGroup } from '@base-ui/react/toggle-group';\nimport styles from './index.module.css';\n\nexport default function ExampleToggleGroupMultiple() {\n  return (\n    <ToggleGroup\n      multiple\n      defaultValue={['bold', 'italic']}\n      aria-label=\"Text formatting options\"\n      className={styles.Panel}\n    >\n      <Toggle aria-label=\"Bold\" value=\"bold\" className={styles.Button}>\n        <BoldIcon />\n      </Toggle>\n      <Toggle aria-label=\"Italic\" value=\"italic\" className={styles.Button}>\n        <ItalicIcon />\n      </Toggle>\n      <Toggle aria-label=\"Underline\" value=\"underline\" className={styles.Button}>\n        <UnderlineIcon />\n      </Toggle>\n    </ToggleGroup>\n  );\n}\n\nfunction BoldIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentcolor\" {...props}>\n      <path d=\"M3.73353 2.13333C3.4386 2.13333 3.2002 2.37226 3.2002 2.66666C3.2002 2.96106 3.4386 3.2 3.73353 3.2H4.26686V12.8H3.73353C3.4386 12.8 3.2002 13.0389 3.2002 13.3333C3.2002 13.6277 3.4386 13.8667 3.73353 13.8667H9.86686C11.7783 13.8667 13.3335 12.3115 13.3335 10.4C13.3335 8.9968 12.4945 7.78881 11.2929 7.24375C11.8897 6.70615 12.2669 5.93066 12.2669 5.06666C12.2669 3.44906 10.9506 2.13333 9.33353 2.13333H3.73353ZM6.93353 3.2H8.26686C9.29619 3.2 10.1335 4.03733 10.1335 5.06666C10.1335 6.096 9.29619 6.93333 8.26686 6.93333H6.93353V3.2ZM6.93353 8H7.73353H8.26686C9.59006 8 10.6669 9.0768 10.6669 10.4C10.6669 11.7232 9.59006 12.8 8.26686 12.8H6.93353V8Z\" />\n    </svg>\n  );\n}\n\nfunction ItalicIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentcolor\" {...props}>\n      <path d=\"M8.52599 2.12186C8.48583 2.12267 8.44578 2.1265 8.4062 2.13332H6.93328C6.86261 2.13232 6.79244 2.14538 6.72686 2.17173C6.66127 2.19808 6.60158 2.23721 6.55125 2.28683C6.50092 2.33646 6.46096 2.39559 6.43368 2.46079C6.4064 2.526 6.39235 2.59597 6.39235 2.66665C6.39235 2.73733 6.4064 2.80731 6.43368 2.87251C6.46096 2.93772 6.50092 2.99685 6.55125 3.04647C6.60158 3.0961 6.66127 3.13522 6.72686 3.16157C6.79244 3.18793 6.86261 3.20099 6.93328 3.19999H7.70099L6.69057 12.8H5.86661C5.79594 12.799 5.72577 12.812 5.66019 12.8384C5.59461 12.8648 5.53492 12.9039 5.48459 12.9535C5.43425 13.0031 5.39429 13.0623 5.36701 13.1275C5.33973 13.1927 5.32568 13.2626 5.32568 13.3333C5.32568 13.404 5.33973 13.474 5.36701 13.5392C5.39429 13.6044 5.43425 13.6635 5.48459 13.7131C5.53492 13.7628 5.59461 13.8019 5.66019 13.8282C5.72577 13.8546 5.79594 13.8677 5.86661 13.8667H9.06661C9.13729 13.8677 9.20745 13.8546 9.27304 13.8282C9.33862 13.8019 9.39831 13.7628 9.44864 13.7131C9.49897 13.6635 9.53894 13.6044 9.56622 13.5392C9.5935 13.474 9.60754 13.404 9.60754 13.3333C9.60754 13.2626 9.5935 13.1927 9.56622 13.1275C9.53894 13.0623 9.49897 13.0031 9.44864 12.9535C9.39831 12.9039 9.33862 12.8648 9.27304 12.8384C9.20745 12.812 9.13729 12.799 9.06661 12.8H8.2989L9.30932 3.19999H10.1333C10.204 3.20099 10.2741 3.18793 10.3397 3.16157C10.4053 3.13522 10.465 3.0961 10.5153 3.04647C10.5656 2.99685 10.6056 2.93772 10.6329 2.87251C10.6602 2.80731 10.6742 2.73733 10.6742 2.66665C10.6742 2.59597 10.6602 2.526 10.6329 2.46079C10.6056 2.39559 10.5656 2.33646 10.5153 2.28683C10.465 2.23721 10.4053 2.19808 10.3397 2.17173C10.2741 2.14538 10.204 2.13232 10.1333 2.13332H8.66349C8.61807 2.12555 8.57207 2.12171 8.52599 2.12186Z\" />\n    </svg>\n  );\n}\n\nfunction UnderlineIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentcolor\" {...props}>\n      <path d=\"M3.73331 2.13332C3.66264 2.13232 3.59247 2.14538 3.52689 2.17173C3.46131 2.19809 3.40161 2.23721 3.35128 2.28684C3.30095 2.33646 3.26099 2.39559 3.23371 2.4608C3.20643 2.526 3.19238 2.59598 3.19238 2.66666C3.19238 2.73734 3.20643 2.80731 3.23371 2.87252C3.26099 2.93772 3.30095 2.99685 3.35128 3.04648C3.40161 3.0961 3.46131 3.13523 3.52689 3.16158C3.59247 3.18793 3.66264 3.20099 3.73331 3.19999V7.99999C3.73331 10.224 5.55144 12.2667 7.99998 12.2667C10.4485 12.2667 12.2666 10.224 12.2666 7.99999V3.19999C12.3373 3.20099 12.4075 3.18793 12.4731 3.16158C12.5386 3.13523 12.5983 3.0961 12.6487 3.04648C12.699 2.99685 12.739 2.93772 12.7662 2.87252C12.7935 2.80731 12.8076 2.73734 12.8076 2.66666C12.8076 2.59598 12.7935 2.526 12.7662 2.4608C12.739 2.39559 12.699 2.33646 12.6487 2.28684C12.5983 2.23721 12.5386 2.19809 12.4731 2.17173C12.4075 2.14538 12.3373 2.13232 12.2666 2.13332H10.1333C10.0626 2.13232 9.99247 2.14538 9.92689 2.17173C9.8613 2.19809 9.80161 2.23721 9.75128 2.28684C9.70095 2.33646 9.66099 2.39559 9.63371 2.4608C9.60643 2.526 9.59238 2.59598 9.59238 2.66666C9.59238 2.73734 9.60643 2.80731 9.63371 2.87252C9.66099 2.93772 9.70095 2.99685 9.75128 3.04648C9.80161 3.0961 9.8613 3.13523 9.92689 3.16158C9.99247 3.18793 10.0626 3.20099 10.1333 3.19999V8.97187C10.1333 10.0855 9.32179 11.0818 8.21352 11.1896C6.94152 11.3138 5.86665 10.3136 5.86665 9.06666V3.19999C5.93732 3.20099 6.00748 3.18793 6.07307 3.16158C6.13865 3.13523 6.19834 3.0961 6.24867 3.04648C6.299 2.99685 6.33897 2.93772 6.36625 2.87252C6.39353 2.80731 6.40757 2.73734 6.40757 2.66666C6.40757 2.59598 6.39353 2.526 6.36625 2.4608C6.33897 2.39559 6.299 2.33646 6.24867 2.28684C6.19834 2.23721 6.13865 2.19809 6.07307 2.17173C6.00748 2.14538 5.93732 2.13232 5.86665 2.13332H3.73331ZM3.73331 13.3333C3.66264 13.3323 3.59247 13.3454 3.52689 13.3717C3.46131 13.3981 3.40161 13.4372 3.35128 13.4868C3.30095 13.5365 3.26099 13.5956 3.23371 13.6608C3.20643 13.726 3.19238 13.796 3.19238 13.8667C3.19238 13.9373 3.20643 14.0073 3.23371 14.0725C3.26099 14.1377 3.30095 14.1969 3.35128 14.2465C3.40161 14.2961 3.46131 14.3352 3.52689 14.3616C3.59247 14.3879 3.66264 14.401 3.73331 14.4H12.2666C12.3373 14.401 12.4075 14.3879 12.4731 14.3616C12.5386 14.3352 12.5983 14.2961 12.6487 14.2465C12.699 14.1969 12.739 14.1377 12.7662 14.0725C12.7935 14.0073 12.8076 13.9373 12.8076 13.8667C12.8076 13.796 12.7935 13.726 12.7662 13.6608C12.739 13.5956 12.699 13.5365 12.6487 13.4868C12.5983 13.4372 12.5386 13.3981 12.4731 13.3717C12.4075 13.3454 12.3373 13.3323 12.2666 13.3333H3.73331Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toggle-group/demos/multiple/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoToggleGroupMultiple = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toggle-group/demos/multiple/tailwind/index.tsx",
    "content": "import { Toggle } from '@base-ui/react/toggle';\nimport { ToggleGroup } from '@base-ui/react/toggle-group';\n\nexport default function ExampleToggleGroupMultiple() {\n  return (\n    <ToggleGroup\n      multiple\n      defaultValue={['bold', 'italic']}\n      aria-label=\"Text formatting options\"\n      className=\"flex gap-px rounded-md border border-gray-200 bg-gray-50 p-0.5\"\n    >\n      <Toggle\n        aria-label=\"Bold\"\n        value=\"bold\"\n        className=\"flex size-8 items-center justify-center rounded-sm text-gray-600 select-none focus-visible:bg-none focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-200 data-[pressed]:bg-gray-100 data-[pressed]:text-gray-900\"\n      >\n        <BoldIcon />\n      </Toggle>\n      <Toggle\n        aria-label=\"Italic\"\n        value=\"italic\"\n        className=\"flex size-8 items-center justify-center rounded-sm text-gray-600 select-none focus-visible:bg-none focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-200 data-[pressed]:bg-gray-100 data-[pressed]:text-gray-900\"\n      >\n        <ItalicIcon />\n      </Toggle>\n      <Toggle\n        aria-label=\"Underline\"\n        value=\"underline\"\n        className=\"flex size-8 items-center justify-center rounded-sm text-gray-600 select-none focus-visible:bg-none focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-200 data-[pressed]:bg-gray-100 data-[pressed]:text-gray-900\"\n      >\n        <UnderlineIcon />\n      </Toggle>\n    </ToggleGroup>\n  );\n}\n\nfunction BoldIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentcolor\" {...props}>\n      <path d=\"M3.73353 2.13333C3.4386 2.13333 3.2002 2.37226 3.2002 2.66666C3.2002 2.96106 3.4386 3.2 3.73353 3.2H4.26686V12.8H3.73353C3.4386 12.8 3.2002 13.0389 3.2002 13.3333C3.2002 13.6277 3.4386 13.8667 3.73353 13.8667H9.86686C11.7783 13.8667 13.3335 12.3115 13.3335 10.4C13.3335 8.9968 12.4945 7.78881 11.2929 7.24375C11.8897 6.70615 12.2669 5.93066 12.2669 5.06666C12.2669 3.44906 10.9506 2.13333 9.33353 2.13333H3.73353ZM6.93353 3.2H8.26686C9.29619 3.2 10.1335 4.03733 10.1335 5.06666C10.1335 6.096 9.29619 6.93333 8.26686 6.93333H6.93353V3.2ZM6.93353 8H7.73353H8.26686C9.59006 8 10.6669 9.0768 10.6669 10.4C10.6669 11.7232 9.59006 12.8 8.26686 12.8H6.93353V8Z\" />\n    </svg>\n  );\n}\n\nfunction ItalicIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentcolor\" {...props}>\n      <path d=\"M8.52599 2.12186C8.48583 2.12267 8.44578 2.1265 8.4062 2.13332H6.93328C6.86261 2.13232 6.79244 2.14538 6.72686 2.17173C6.66127 2.19808 6.60158 2.23721 6.55125 2.28683C6.50092 2.33646 6.46096 2.39559 6.43368 2.46079C6.4064 2.526 6.39235 2.59597 6.39235 2.66665C6.39235 2.73733 6.4064 2.80731 6.43368 2.87251C6.46096 2.93772 6.50092 2.99685 6.55125 3.04647C6.60158 3.0961 6.66127 3.13522 6.72686 3.16157C6.79244 3.18793 6.86261 3.20099 6.93328 3.19999H7.70099L6.69057 12.8H5.86661C5.79594 12.799 5.72577 12.812 5.66019 12.8384C5.59461 12.8648 5.53492 12.9039 5.48459 12.9535C5.43425 13.0031 5.39429 13.0623 5.36701 13.1275C5.33973 13.1927 5.32568 13.2626 5.32568 13.3333C5.32568 13.404 5.33973 13.474 5.36701 13.5392C5.39429 13.6044 5.43425 13.6635 5.48459 13.7131C5.53492 13.7628 5.59461 13.8019 5.66019 13.8282C5.72577 13.8546 5.79594 13.8677 5.86661 13.8667H9.06661C9.13729 13.8677 9.20745 13.8546 9.27304 13.8282C9.33862 13.8019 9.39831 13.7628 9.44864 13.7131C9.49897 13.6635 9.53894 13.6044 9.56622 13.5392C9.5935 13.474 9.60754 13.404 9.60754 13.3333C9.60754 13.2626 9.5935 13.1927 9.56622 13.1275C9.53894 13.0623 9.49897 13.0031 9.44864 12.9535C9.39831 12.9039 9.33862 12.8648 9.27304 12.8384C9.20745 12.812 9.13729 12.799 9.06661 12.8H8.2989L9.30932 3.19999H10.1333C10.204 3.20099 10.2741 3.18793 10.3397 3.16157C10.4053 3.13522 10.465 3.0961 10.5153 3.04647C10.5656 2.99685 10.6056 2.93772 10.6329 2.87251C10.6602 2.80731 10.6742 2.73733 10.6742 2.66665C10.6742 2.59597 10.6602 2.526 10.6329 2.46079C10.6056 2.39559 10.5656 2.33646 10.5153 2.28683C10.465 2.23721 10.4053 2.19808 10.3397 2.17173C10.2741 2.14538 10.204 2.13232 10.1333 2.13332H8.66349C8.61807 2.12555 8.57207 2.12171 8.52599 2.12186Z\" />\n    </svg>\n  );\n}\n\nfunction UnderlineIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentcolor\" {...props}>\n      <path d=\"M3.73331 2.13332C3.66264 2.13232 3.59247 2.14538 3.52689 2.17173C3.46131 2.19809 3.40161 2.23721 3.35128 2.28684C3.30095 2.33646 3.26099 2.39559 3.23371 2.4608C3.20643 2.526 3.19238 2.59598 3.19238 2.66666C3.19238 2.73734 3.20643 2.80731 3.23371 2.87252C3.26099 2.93772 3.30095 2.99685 3.35128 3.04648C3.40161 3.0961 3.46131 3.13523 3.52689 3.16158C3.59247 3.18793 3.66264 3.20099 3.73331 3.19999V7.99999C3.73331 10.224 5.55144 12.2667 7.99998 12.2667C10.4485 12.2667 12.2666 10.224 12.2666 7.99999V3.19999C12.3373 3.20099 12.4075 3.18793 12.4731 3.16158C12.5386 3.13523 12.5983 3.0961 12.6487 3.04648C12.699 2.99685 12.739 2.93772 12.7662 2.87252C12.7935 2.80731 12.8076 2.73734 12.8076 2.66666C12.8076 2.59598 12.7935 2.526 12.7662 2.4608C12.739 2.39559 12.699 2.33646 12.6487 2.28684C12.5983 2.23721 12.5386 2.19809 12.4731 2.17173C12.4075 2.14538 12.3373 2.13232 12.2666 2.13332H10.1333C10.0626 2.13232 9.99247 2.14538 9.92689 2.17173C9.8613 2.19809 9.80161 2.23721 9.75128 2.28684C9.70095 2.33646 9.66099 2.39559 9.63371 2.4608C9.60643 2.526 9.59238 2.59598 9.59238 2.66666C9.59238 2.73734 9.60643 2.80731 9.63371 2.87252C9.66099 2.93772 9.70095 2.99685 9.75128 3.04648C9.80161 3.0961 9.8613 3.13523 9.92689 3.16158C9.99247 3.18793 10.0626 3.20099 10.1333 3.19999V8.97187C10.1333 10.0855 9.32179 11.0818 8.21352 11.1896C6.94152 11.3138 5.86665 10.3136 5.86665 9.06666V3.19999C5.93732 3.20099 6.00748 3.18793 6.07307 3.16158C6.13865 3.13523 6.19834 3.0961 6.24867 3.04648C6.299 2.99685 6.33897 2.93772 6.36625 2.87252C6.39353 2.80731 6.40757 2.73734 6.40757 2.66666C6.40757 2.59598 6.39353 2.526 6.36625 2.4608C6.33897 2.39559 6.299 2.33646 6.24867 2.28684C6.19834 2.23721 6.13865 2.19809 6.07307 2.17173C6.00748 2.14538 5.93732 2.13232 5.86665 2.13332H3.73331ZM3.73331 13.3333C3.66264 13.3323 3.59247 13.3454 3.52689 13.3717C3.46131 13.3981 3.40161 13.4372 3.35128 13.4868C3.30095 13.5365 3.26099 13.5956 3.23371 13.6608C3.20643 13.726 3.19238 13.796 3.19238 13.8667C3.19238 13.9373 3.20643 14.0073 3.23371 14.0725C3.26099 14.1377 3.30095 14.1969 3.35128 14.2465C3.40161 14.2961 3.46131 14.3352 3.52689 14.3616C3.59247 14.3879 3.66264 14.401 3.73331 14.4H12.2666C12.3373 14.401 12.4075 14.3879 12.4731 14.3616C12.5386 14.3352 12.5983 14.2961 12.6487 14.2465C12.699 14.1969 12.739 14.1377 12.7662 14.0725C12.7935 14.0073 12.8076 13.9373 12.8076 13.8667C12.8076 13.796 12.7935 13.726 12.7662 13.6608C12.739 13.5956 12.699 13.5365 12.6487 13.4868C12.5983 13.4372 12.5386 13.3981 12.4731 13.3717C12.4075 13.3454 12.3373 13.3323 12.2666 13.3333H3.73331Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toggle-group/page.mdx",
    "content": "# Toggle Group\n\n<Subtitle>Provides a shared state to a series of toggle buttons.</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"A high-quality, unstyled React toggle group component that provides shared state to a series of toggle buttons.\"\n/>\n\nimport { DemoToggleGroupHero } from './demos/hero';\n\n<DemoToggleGroupHero />\n\n## Anatomy\n\nImport the component and use it as a single part:\n\n```jsx title=\"Anatomy\"\nimport { ToggleGroup } from '@base-ui/react/toggle-group';\n\n<ToggleGroup />;\n```\n\n## Examples\n\n### Multiple\n\nAdd the `multiple` prop to allow pressing more than one toggle at a time.\n\nimport { DemoToggleGroupMultiple } from './demos/multiple';\n\n<DemoToggleGroupMultiple />\n\n## API reference\n\n<Reference component=\"ToggleGroup\" />\n\nexport const metadata = {\n  keywords: [\n    'React Toggle Group',\n    'Toggle Button Group',\n    'Segmented Control',\n    'Button Set',\n    'Radio Group Alternative',\n    'Exclusive Selection Toggle',\n    'Multi Select Toggle Buttons',\n    'Accessible Toggle Group',\n    'Headless React Components',\n    'Base UI',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toolbar/demos/hero/css-modules/index.module.css",
    "content": ".Toolbar {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  gap: 1px;\n  border: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-50);\n  border-radius: 0.375rem;\n  padding: 0.125rem;\n  width: 37.5rem;\n}\n\n.Group {\n  display: flex;\n  gap: 0.25rem;\n}\n\n.Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  min-width: 2rem;\n  height: 2rem;\n  padding: 0;\n  margin: 0;\n  outline: 0;\n  border: 0;\n  border-radius: 0.25rem;\n  background-color: transparent;\n  color: var(--color-gray-600);\n  user-select: none;\n  font-family: inherit;\n  font-size: 0.875rem;\n  font-weight: 400;\n\n  &:focus-visible {\n    background-color: transparent;\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-200);\n  }\n\n  &[data-pressed] {\n    background-color: var(--color-gray-100);\n    color: var(--color-gray-900);\n  }\n\n  &[aria-pressed] {\n    padding: 0 0.75rem;\n  }\n\n  &[role='combobox'] {\n    min-width: 8rem;\n    justify-content: space-between;\n    padding: 0 0.75rem;\n  }\n}\n\n.Separator {\n  width: 1px;\n  height: 16px;\n  margin: 0.25rem;\n  background-color: var(--color-gray-300);\n}\n\n.Link {\n  color: var(--color-gray-500);\n  font-family: inherit;\n  font-size: 0.875rem;\n  text-decoration: none;\n  align-self: center;\n  flex: 0 0 auto;\n  margin-inline: auto 0.875rem;\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -2px;\n    border-radius: var(--radius-sm);\n  }\n\n  @media (hover: hover) {\n    &:hover {\n      color: var(--color-blue);\n    }\n  }\n}\n\n.Select {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 0.75rem;\n  height: 2.5rem;\n  padding-left: 0.875rem;\n  padding-right: 0.75rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  cursor: default;\n  -webkit-user-select: none;\n  user-select: none;\n  min-width: 9rem;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.SelectIcon {\n  display: flex;\n}\n\n.Positioner {\n  outline: 0;\n  -webkit-user-select: none;\n  user-select: none;\n  z-index: 1;\n}\n\n.Popup {\n  box-sizing: border-box;\n  padding-block: 0.25rem;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n  overflow-y: auto;\n  max-height: var(--available-height);\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  &[data-side='none'] {\n    transition: none;\n    transform: none;\n    opacity: 1;\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Arrow {\n  display: flex;\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n\n.Item {\n  box-sizing: border-box;\n  outline: 0;\n  line-height: 1rem;\n  padding-block: 0.5rem;\n  padding-left: 0.625rem;\n  padding-right: 1rem;\n  min-width: var(--anchor-width);\n  display: grid;\n  gap: 0.5rem;\n  align-items: center;\n  grid-template-columns: 0.75rem 1fr;\n  cursor: default;\n  -webkit-user-select: none;\n  user-select: none;\n  scroll-margin-block: 1rem;\n  font-size: 0.875rem;\n\n  @media (pointer: coarse) {\n    padding-block: 0.625rem;\n  }\n\n  [data-side='none'] & {\n    padding-right: 3rem;\n    min-width: calc(var(--anchor-width) + 1rem);\n  }\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.25rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.ItemIndicator {\n  grid-column-start: 1;\n}\n\n.ItemIndicatorIcon {\n  display: block;\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.ItemText {\n  grid-column-start: 2;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toolbar/demos/hero/css-modules/index.tsx",
    "content": "import * as React from 'react';\nimport { Toolbar } from '@base-ui/react/toolbar';\nimport { ToggleGroup } from '@base-ui/react/toggle-group';\nimport { Toggle } from '@base-ui/react/toggle';\nimport { Select } from '@base-ui/react/select';\nimport styles from './index.module.css';\n\nexport default function ExampleToolbar() {\n  return (\n    <Toolbar.Root className={styles.Toolbar}>\n      <ToggleGroup className={styles.Group} aria-label=\"Alignment\">\n        <Toolbar.Button\n          render={<Toggle />}\n          aria-label=\"Align left\"\n          value=\"align-left\"\n          className={styles.Button}\n        >\n          Align Left\n        </Toolbar.Button>\n        <Toolbar.Button\n          render={<Toggle />}\n          aria-label=\"Align right\"\n          value=\"align-right\"\n          className={styles.Button}\n        >\n          Align Right\n        </Toolbar.Button>\n      </ToggleGroup>\n      <Toolbar.Separator className={styles.Separator} />\n      <Toolbar.Group className={styles.Group} aria-label=\"Numerical format\">\n        <Toolbar.Button className={styles.Button} aria-label=\"Format as currency\">\n          $\n        </Toolbar.Button>\n        <Toolbar.Button className={styles.Button} aria-label=\"Format as percent\">\n          %\n        </Toolbar.Button>\n      </Toolbar.Group>\n      <Toolbar.Separator className={styles.Separator} />\n      <Select.Root defaultValue=\"Helvetica\">\n        <Toolbar.Button render={<Select.Trigger />} className={styles.Button}>\n          <Select.Value />\n          <Select.Icon className={styles.SelectIcon}>\n            <ChevronUpDownIcon />\n          </Select.Icon>\n        </Toolbar.Button>\n        <Select.Portal>\n          <Select.Positioner className={styles.Positioner} sideOffset={8}>\n            <Select.Popup className={styles.Popup}>\n              <Select.Item className={styles.Item} value=\"Helvetica\">\n                <Select.ItemIndicator className={styles.ItemIndicator}>\n                  <CheckIcon className={styles.ItemIndicatorIcon} />\n                </Select.ItemIndicator>\n                <Select.ItemText className={styles.ItemText}>Helvetica</Select.ItemText>\n              </Select.Item>\n              <Select.Item className={styles.Item} value=\"Arial\">\n                <Select.ItemIndicator className={styles.ItemIndicator}>\n                  <CheckIcon className={styles.ItemIndicatorIcon} />\n                </Select.ItemIndicator>\n                <Select.ItemText className={styles.ItemText}>Arial</Select.ItemText>\n              </Select.Item>\n            </Select.Popup>\n          </Select.Positioner>\n        </Select.Portal>\n      </Select.Root>\n      <Toolbar.Separator className={styles.Separator} />\n      <Toolbar.Link className={styles.Link} href=\"#\">\n        Edited 51m ago\n      </Toolbar.Link>\n    </Toolbar.Root>\n  );\n}\n\nfunction ChevronUpDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"8\"\n      height=\"12\"\n      viewBox=\"0 0 8 12\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"1.5\"\n      {...props}\n    >\n      <path d=\"M0.5 4.5L4 1.5L7.5 4.5\" />\n      <path d=\"M0.5 7.5L4 10.5L7.5 7.5\" />\n    </svg>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toolbar/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoToolbarHero = createDemoWithVariants(import.meta.url, { CssModules, Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toolbar/demos/hero/tailwind/index.tsx",
    "content": "import * as React from 'react';\nimport { Toolbar } from '@base-ui/react/toolbar';\nimport { ToggleGroup } from '@base-ui/react/toggle-group';\nimport { Toggle } from '@base-ui/react/toggle';\nimport { Select } from '@base-ui/react/select';\n\nexport default function ExampleToolbar() {\n  return (\n    <Toolbar.Root className=\"flex w-150 items-center gap-px rounded-md border border-gray-200 bg-gray-50 p-0.5\">\n      <ToggleGroup className=\"flex gap-1\" aria-label=\"Alignment\">\n        <Toolbar.Button\n          render={<Toggle />}\n          aria-label=\"Align left\"\n          value=\"align-left\"\n          className=\"flex h-8 items-center justify-center rounded-xs px-[0.75rem] font-[inherit] text-sm font-normal text-gray-600 select-none hover:bg-gray-100 focus-visible:bg-none focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-200 data-[pressed]:bg-gray-100 data-[pressed]:text-gray-900\"\n        >\n          Align Left\n        </Toolbar.Button>\n        <Toolbar.Button\n          render={<Toggle />}\n          aria-label=\"Align right\"\n          value=\"align-right\"\n          className=\"flex h-8 items-center justify-center rounded-xs px-[0.75rem] font-[inherit] text-sm font-normal text-gray-600 select-none hover:bg-gray-100 focus-visible:bg-none focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-200 data-[pressed]:bg-gray-100 data-[pressed]:text-gray-900\"\n        >\n          Align Right\n        </Toolbar.Button>\n      </ToggleGroup>\n      <Toolbar.Separator className=\"m-1 h-4 w-px bg-gray-300\" />\n      <Toolbar.Group className=\"flex gap-1\" aria-label=\"Numerical format\">\n        <Toolbar.Button\n          className=\"flex size-8 items-center justify-center rounded-xs px-[0.75rem] font-[inherit] text-sm font-normal text-gray-600 select-none hover:bg-gray-100 focus-visible:bg-none focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-200 data-[pressed]:bg-gray-100 data-[pressed]:text-gray-900\"\n          aria-label=\"Format as currency\"\n        >\n          $\n        </Toolbar.Button>\n        <Toolbar.Button\n          className=\"flex size-8 items-center justify-center rounded-xs px-[0.75rem] font-[inherit] text-sm font-normal text-gray-600 select-none hover:bg-gray-100 focus-visible:bg-none focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-200 data-[pressed]:bg-gray-100 data-[pressed]:text-gray-900\"\n          aria-label=\"Format as percent\"\n        >\n          %\n        </Toolbar.Button>\n      </Toolbar.Group>\n      <Toolbar.Separator className=\"m-1 h-4 w-px bg-gray-300\" />\n      <Select.Root defaultValue=\"Helvetica\">\n        <Toolbar.Button\n          render={<Select.Trigger />}\n          className=\"flex min-w-[8rem] h-8 text-sm font-normal items-center justify-between gap-3 rounded-md pr-3 pl-3 text-gray-600 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 data-[popup-open]:bg-gray-100 cursor-default\"\n        >\n          <Select.Value />\n          <Select.Icon className=\"flex\">\n            <ChevronUpDownIcon />\n          </Select.Icon>\n        </Toolbar.Button>\n        <Select.Portal>\n          <Select.Positioner className=\"outline-hidden select-none\" sideOffset={8}>\n            <Select.Popup className=\"group max-h-[var(--available-height)] origin-[var(--transform-origin)] overflow-y-auto rounded-md bg-[canvas] py-1 text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 transition-[transform,scale,opacity] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[side=none]:data-[ending-style]:transition-none data-[starting-style]:scale-90 data-[starting-style]:opacity-0 data-[side=none]:data-[starting-style]:scale-100 data-[side=none]:data-[starting-style]:opacity-100 data-[side=none]:data-[starting-style]:transition-none dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300\">\n              <Select.Item\n                value=\"Helvetica\"\n                className=\"grid min-w-[var(--anchor-width)] cursor-default grid-cols-[0.75rem_1fr] items-center gap-2 py-1.5 pr-4 pl-2.5 leading-4 outline-hidden select-none group-data-[side=none]:min-w-[calc(var(--anchor-width)+1rem)] group-data-[side=none]:pr-12 group-data-[side=none]:leading-4 data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900 pointer-coarse:py-2.5\"\n              >\n                <Select.ItemIndicator className=\"col-start-1\">\n                  <CheckIcon className=\"size-3\" />\n                </Select.ItemIndicator>\n                <Select.ItemText className=\"col-start-2 text-sm\">Helvetica</Select.ItemText>\n              </Select.Item>\n              <Select.Item\n                value=\"Arial\"\n                className=\"grid min-w-[var(--anchor-width)] cursor-default grid-cols-[0.75rem_1fr] items-center gap-2 py-1.5 pr-4 pl-2.5 leading-4 outline-hidden select-none group-data-[side=none]:min-w-[calc(var(--anchor-width)+1rem)] group-data-[side=none]:pr-12 group-data-[side=none]:leading-4 data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-xs data-[highlighted]:before:bg-gray-900 pointer-coarse:py-2.5\"\n              >\n                <Select.ItemIndicator className=\"col-start-1\">\n                  <CheckIcon className=\"size-3\" />\n                </Select.ItemIndicator>\n                <Select.ItemText className=\"col-start-2 text-sm\">Arial</Select.ItemText>\n              </Select.Item>\n            </Select.Popup>\n          </Select.Positioner>\n        </Select.Portal>\n      </Select.Root>\n      <Toolbar.Separator className=\"m-1 h-4 w-px bg-gray-300\" />\n      <Toolbar.Link\n        className=\"mr-[0.875rem] ml-auto flex-none self-center text-sm text-gray-500 no-underline hover:text-blue-800 focus-visible:rounded-xs focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-blue-800\"\n        href=\"#\"\n      >\n        Edited 51m ago\n      </Toolbar.Link>\n    </Toolbar.Root>\n  );\n}\n\nfunction ChevronUpDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"8\"\n      height=\"12\"\n      viewBox=\"0 0 8 12\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"1.5\"\n      {...props}\n    >\n      <path d=\"M0.5 4.5L4 1.5L7.5 4.5\" />\n      <path d=\"M0.5 7.5L4 10.5L7.5 7.5\" />\n    </svg>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/toolbar/page.mdx",
    "content": "# Toolbar\n\n<Subtitle>A container for grouping a set of buttons and controls.</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"A high-quality, unstyled React toolbar component that groups a set of buttons and controls.\"\n/>\n\nimport { DemoToolbarHero } from './demos/hero';\n\n<DemoToolbarHero />\n\n## Usage guidelines\n\nTo ensure that toolbars are accessible and helpful, follow these guidelines:\n\n- **Use inputs sparingly**: Left and right arrow keys are used to both move the text insertion cursor in an input, and to navigate among controls in horizontal toolbars. When using an input in a horizontal toolbar, use only one and place it as the last element of the toolbar.\n\n## Anatomy\n\nImport the component and assemble its parts:\n\n```jsx title=\"Anatomy\"\nimport { Toolbar } from '@base-ui/react/toolbar';\n\n<Toolbar.Root>\n  <Toolbar.Button />\n  <Toolbar.Link />\n  <Toolbar.Separator />\n  <Toolbar.Group>\n    <Toolbar.Button />\n    <Toolbar.Button />\n  </Toolbar.Group>\n  <Toolbar.Input />\n</Toolbar.Root>;\n```\n\n## Examples\n\n### Using with Menu\n\nAll Base UI popup components that provide a `Trigger` component can be integrated with a toolbar by passing the trigger to `<Toolbar.Button>` with the `render` prop:\n\n```tsx {4,12} title=\"Using popups with toolbar\"\nreturn (\n  <Toolbar.Root>\n    <Menu.Root>\n      <Toolbar.Button render={<Menu.Trigger />} />\n      <Menu.Portal>\n        {/* prettier-ignore */}\n        {/* Compose the rest of the menu */}\n      </Menu.Portal>\n    </Menu.Root>\n  </Toolbar.Root>;\n)\n```\n\nThis applies to `<AlertDialog>`, `<Dialog>`, `<Menu>`, `<Popover>`, and `<Select>`.\n\n### Using with Tooltip\n\nUnlike other popups, the toolbar item should be passed to the `render` prop of `<Tooltip.Trigger>`:\n\n```tsx {4} title=\"Using popups with toolbar\"\nreturn (\n  <Toolbar.Root>\n    <Tooltip.Root>\n      <Tooltip.Trigger render={<Toolbar.Button />} />\n      <Tooltip.Portal>\n        {/* prettier-ignore */}\n        {/* Compose the rest of the tooltip */}\n      </Tooltip.Portal>\n    </Tooltip.Root>\n  </Toolbar.Root>;\n)\n```\n\n### Using with NumberField\n\nTo use a NumberField in the toolbar, pass `<NumberField.Input>` to `<Toolbar.Input>` using the `render` prop:\n\n```tsx {6} title=\"Using NumberField with toolbar\"\nreturn (\n  <Toolbar.Root>\n    <NumberField.Root>\n      <NumberField.Group>\n        <NumberField.Decrement />\n        <Toolbar.Input render={<NumberField.Input />} />\n        <NumberField.Increment />\n      </NumberField.Group>\n    </NumberField.Root>\n  </Toolbar.Root>;\n)\n```\n\n## API reference\n\n<Reference component=\"Toolbar\" parts=\"Root, Button, Link, Input, Group, Separator\" />\n\nexport const metadata = {\n  keywords: [\n    'React Toolbar',\n    'Toolbar Button Group',\n    'Action Bar',\n    'Button Bar',\n    'Command Strip',\n    'Headless Toolbar Component',\n    'Accessible Toolbar',\n    'Popup Trigger Toolbar',\n    'Base UI',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/tooltip/demos/detached-triggers-controlled/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Tooltip } from '@base-ui/react/tooltip';\nimport styles from '../../index.module.css';\n\nconst demoTooltip = Tooltip.createHandle();\n\nexport default function TooltipDetachedTriggersControlledDemo() {\n  const [open, setOpen] = React.useState(false);\n  const [triggerId, setTriggerId] = React.useState<string | null>(null);\n\n  const handleOpenChange = (isOpen: boolean, eventDetails: Tooltip.Root.ChangeEventDetails) => {\n    setOpen(isOpen);\n    setTriggerId(eventDetails.trigger?.id ?? null);\n  };\n\n  return (\n    <Tooltip.Provider>\n      <div className={styles.Container}>\n        <div className={styles.ButtonGroup}>\n          <Tooltip.Trigger className={styles.IconButton} handle={demoTooltip} id=\"trigger-1\">\n            <InfoIcon aria-label=\"Controlled tooltip\" className={styles.Icon} />\n          </Tooltip.Trigger>\n\n          <Tooltip.Trigger className={styles.IconButton} handle={demoTooltip} id=\"trigger-2\">\n            <InfoIcon aria-label=\"Controlled tooltip\" className={styles.Icon} />\n          </Tooltip.Trigger>\n\n          <Tooltip.Trigger className={styles.IconButton} handle={demoTooltip} id=\"trigger-3\">\n            <InfoIcon aria-label=\"Controlled tooltip\" className={styles.Icon} />\n          </Tooltip.Trigger>\n        </div>\n\n        <button\n          type=\"button\"\n          className={styles.Button}\n          onClick={() => {\n            setTriggerId('trigger-2');\n            setOpen(true);\n          }}\n        >\n          Open programmatically\n        </button>\n      </div>\n\n      <Tooltip.Root\n        handle={demoTooltip}\n        open={open}\n        onOpenChange={handleOpenChange}\n        triggerId={triggerId}\n      >\n        <Tooltip.Portal>\n          <Tooltip.Positioner sideOffset={10} className={styles.Positioner}>\n            <Tooltip.Popup className={styles.Popup}>\n              <Tooltip.Arrow className={styles.Arrow}>\n                <ArrowSvg />\n              </Tooltip.Arrow>\n              Controlled tooltip\n            </Tooltip.Popup>\n          </Tooltip.Positioner>\n        </Tooltip.Portal>\n      </Tooltip.Root>\n    </Tooltip.Provider>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nfunction InfoIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"20\"\n      height=\"20\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth={2}\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <circle cx=\"12\" cy=\"12\" r=\"10\" />\n      <path d=\"M12 16v-4\" />\n      <path d=\"M12 8h.01\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/tooltip/demos/detached-triggers-controlled/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoTooltipDetachedTriggersControlled = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/tooltip/demos/detached-triggers-controlled/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Tooltip } from '@base-ui/react/tooltip';\nimport { ArrowSvg, InfoIcon } from '../../icons-tw';\n\nconst demoTooltip = Tooltip.createHandle();\n\nexport default function TooltipDetachedTriggersControlledDemo() {\n  const [open, setOpen] = React.useState(false);\n  const [triggerId, setTriggerId] = React.useState<string | null>(null);\n\n  const handleOpenChange = (isOpen: boolean, eventDetails: Tooltip.Root.ChangeEventDetails) => {\n    setOpen(isOpen);\n    setTriggerId(eventDetails.trigger?.id ?? null);\n  };\n\n  return (\n    <Tooltip.Provider>\n      <div className=\"flex gap-2 flex-wrap justify-center\">\n        <div className=\"flex\">\n          <Tooltip.Trigger\n            className=\"\n              flex size-10 items-center justify-center\n              border border-gray-200 rounded-l-md\n              bg-gray-50\n              text-gray-900\n              select-none\n              data-popup-open:bg-gray-100\n              focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800\n              hover:bg-gray-100\n              active:bg-gray-100\"\n            handle={demoTooltip}\n            id=\"trigger-1\"\n          >\n            <InfoIcon aria-label=\"Controlled tooltip\" />\n          </Tooltip.Trigger>\n\n          <Tooltip.Trigger\n            className=\"\n              flex size-10 items-center justify-center\n              border-y border-r border-gray-200\n              bg-gray-50\n              text-gray-900\n              select-none\n              data-popup-open:bg-gray-100\n              focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800\n              hover:bg-gray-100\n              active:bg-gray-100\"\n            handle={demoTooltip}\n            id=\"trigger-2\"\n          >\n            <InfoIcon aria-label=\"Controlled tooltip\" />\n          </Tooltip.Trigger>\n\n          <Tooltip.Trigger\n            className=\"\n              flex size-10 items-center justify-center\n              border-y border-r border-gray-200 rounded-r-md\n              bg-gray-50\n              text-gray-900\n              select-none\n              data-popup-open:bg-gray-100\n              focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800\n              hover:bg-gray-100\n              active:bg-gray-100\"\n            handle={demoTooltip}\n            id=\"trigger-3\"\n          >\n            <InfoIcon aria-label=\"Controlled tooltip\" />\n          </Tooltip.Trigger>\n        </div>\n\n        <button\n          type=\"button\"\n          className=\"\n            flex h-10 items-center justify-center\n            border border-gray-200 rounded-md\n            bg-gray-50\n            px-3.5\n            text-base font-normal text-gray-900\n            select-none\n            focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800\n            hover:bg-gray-100\n            active:bg-gray-100\"\n          onClick={() => {\n            setTriggerId('trigger-2');\n            setOpen(true);\n          }}\n        >\n          Open programmatically\n        </button>\n      </div>\n\n      <Tooltip.Root\n        handle={demoTooltip}\n        open={open}\n        onOpenChange={handleOpenChange}\n        triggerId={triggerId}\n      >\n        <Tooltip.Portal>\n          <Tooltip.Positioner\n            className=\"\n              h-(--positioner-height)\n              w-(--positioner-width)\n              max-w-(--available-width)\n            \"\n            sideOffset={10}\n          >\n            <Tooltip.Popup\n              className=\"\n                px-2 py-1\n                rounded-md\n                bg-[canvas]\n                text-sm\n                origin-(--transform-origin)\n                shadow-lg shadow-gray-200 outline-1 outline-gray-200\n                transition-[transform,scale,opacity]\n                data-ending-style:opacity-0 data-ending-style:scale-90\n                data-instant:transition-none\n                data-starting-style:opacity-0 data-starting-style:scale-90\n                dark:shadow-none dark:outline-gray-300 dark:-outline-offset-1\"\n            >\n              <Tooltip.Arrow\n                className=\"\n                  flex\n                  data-[side=bottom]:-top-2 data-[side=bottom]:rotate-0\n                  data-[side=left]:right-[-13px] data-[side=left]:rotate-90\n                  data-[side=right]:left-[-13px] data-[side=right]:-rotate-90\n                  data-[side=top]:-bottom-2 data-[side=top]:rotate-180\"\n              >\n                <ArrowSvg />\n              </Tooltip.Arrow>\n              Controlled tooltip\n            </Tooltip.Popup>\n          </Tooltip.Positioner>\n        </Tooltip.Portal>\n      </Tooltip.Root>\n    </Tooltip.Provider>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/tooltip/demos/detached-triggers-full/css-modules/index.module.css",
    "content": ".Container {\n  display: flex;\n}\n\n.ButtonGroup {\n  display: flex;\n}\n\n.Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 2.5rem;\n  height: 2.5rem;\n  padding: 0;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-50);\n  color: var(--color-gray-900);\n  user-select: none;\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:only-child {\n    border-radius: 0.375rem;\n  }\n\n  &:first-child:not(:only-child) {\n    border-radius: 0.375rem 0 0 0.375rem;\n  }\n\n  &:last-child:not(:only-child) {\n    border-radius: 0 0.375rem 0.375rem 0;\n  }\n\n  &:not(:first-child) {\n    border-left: none;\n  }\n}\n\n.Icon {\n  width: 1.25rem;\n  height: 1.25rem;\n}\n\n.Positioner {\n  height: var(--positioner-height);\n  width: var(--positioner-width);\n  max-width: var(--available-width);\n  transition:\n    top 0.35s cubic-bezier(0.22, 1, 0.36, 1),\n    left 0.35s cubic-bezier(0.22, 1, 0.36, 1),\n    right 0.35s cubic-bezier(0.22, 1, 0.36, 1),\n    bottom 0.35s cubic-bezier(0.22, 1, 0.36, 1),\n    transform 0.35s cubic-bezier(0.22, 1, 0.36, 1);\n\n  &[data-instant] {\n    transition: none;\n  }\n}\n\n.Popup {\n  position: relative;\n  height: var(--popup-height, auto);\n  width: var(--popup-width, auto);\n  max-width: 500px;\n  transform-origin: var(--transform-origin);\n  border-radius: 0.375rem;\n  background-color: canvas;\n\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  transition:\n    width 0.35s cubic-bezier(0.22, 1, 0.36, 1),\n    height 0.35s cubic-bezier(0.22, 1, 0.36, 1),\n    opacity 0.35s cubic-bezier(0.22, 1, 0.36, 1),\n    transform 0.35s cubic-bezier(0.22, 1, 0.36, 1);\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  &[data-instant] {\n    transition: none;\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Arrow {\n  display: flex;\n  transition: left 0.35s cubic-bezier(0.22, 1, 0.36, 1);\n\n  &[data-instant] {\n    transition: none;\n  }\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n\n.Viewport {\n  --viewport-inline-padding: 0.5rem;\n  box-sizing: border-box;\n  position: relative;\n  height: 100%;\n  width: 100%;\n  overflow: clip;\n  padding: 0.25rem var(--viewport-inline-padding);\n\n  [data-previous],\n  [data-current] {\n    width: calc(var(--popup-width) - 2 * var(--viewport-inline-padding));\n    translate: 0;\n    opacity: 1;\n    transition:\n      translate 350ms cubic-bezier(0.22, 1, 0.36, 1),\n      opacity 175ms cubic-bezier(0.22, 1, 0.36, 1);\n\n    [data-instant] & {\n      transition: none;\n    }\n  }\n\n  &[data-activation-direction~='left'] [data-current][data-starting-style] {\n    translate: -50% 0;\n    opacity: 0;\n  }\n\n  &[data-activation-direction~='right'] [data-current][data-starting-style] {\n    translate: 50% 0;\n    opacity: 0;\n  }\n\n  &[data-activation-direction~='left'] [data-previous][data-ending-style] {\n    translate: 50% 0;\n    opacity: 0;\n  }\n\n  &[data-activation-direction~='right'] [data-previous][data-ending-style] {\n    translate: -50% 0;\n    opacity: 0;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/tooltip/demos/detached-triggers-full/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Tooltip } from '@base-ui/react/tooltip';\nimport styles from './index.module.css';\n\nconst demoTooltip = Tooltip.createHandle<React.ComponentType>();\n\nexport default function TooltipDetachedTriggersFullDemo() {\n  return (\n    <Tooltip.Provider>\n      <div className={styles.ButtonGroup}>\n        <Tooltip.Trigger className={styles.Button} handle={demoTooltip} payload={InfoContent}>\n          <InfoIcon aria-label=\"This is information about the feature\" className={styles.Icon} />\n        </Tooltip.Trigger>\n\n        <Tooltip.Trigger className={styles.Button} handle={demoTooltip} payload={HelpContent}>\n          <HelpIcon aria-label=\"Need help?\" className={styles.Icon} />\n        </Tooltip.Trigger>\n\n        <Tooltip.Trigger className={styles.Button} handle={demoTooltip} payload={AlertContent}>\n          <AlertIcon aria-label=\"Warning: This action cannot be undone\" className={styles.Icon} />\n        </Tooltip.Trigger>\n      </div>\n\n      <Tooltip.Root handle={demoTooltip}>\n        {({ payload: Payload }) => (\n          <Tooltip.Portal>\n            <Tooltip.Positioner sideOffset={10} className={styles.Positioner}>\n              <Tooltip.Popup className={styles.Popup}>\n                <Tooltip.Arrow className={styles.Arrow}>\n                  <ArrowSvg />\n                </Tooltip.Arrow>\n\n                <Tooltip.Viewport className={styles.Viewport}>\n                  {Payload !== undefined && <Payload />}\n                </Tooltip.Viewport>\n              </Tooltip.Popup>\n            </Tooltip.Positioner>\n          </Tooltip.Portal>\n        )}\n      </Tooltip.Root>\n    </Tooltip.Provider>\n  );\n}\n\nfunction InfoContent() {\n  return <span>This is information about the feature</span>;\n}\n\nfunction HelpContent() {\n  return <span>Need help?</span>;\n}\n\nfunction AlertContent() {\n  return <span>Warning: This action cannot be undone</span>;\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nfunction InfoIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"20\"\n      height=\"20\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth={2}\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <circle cx=\"12\" cy=\"12\" r=\"10\" />\n      <path d=\"M12 16v-4\" />\n      <path d=\"M12 8h.01\" />\n    </svg>\n  );\n}\n\nfunction HelpIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"20\"\n      height=\"20\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth={2}\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <circle cx=\"12\" cy=\"12\" r=\"10\" />\n      <path d=\"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3\" />\n      <path d=\"M12 17h.01\" />\n    </svg>\n  );\n}\n\nfunction AlertIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"20\"\n      height=\"20\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth={2}\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z\" />\n      <path d=\"M12 9v4\" />\n      <path d=\"M12 17h.01\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/tooltip/demos/detached-triggers-full/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoTooltipDetachedTriggersFull = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/tooltip/demos/detached-triggers-full/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Tooltip } from '@base-ui/react/tooltip';\nimport { ArrowSvg, InfoIcon, HelpIcon, AlertIcon } from '../../icons-tw';\n\nconst demoTooltip = Tooltip.createHandle<React.ComponentType>();\n\nexport default function TooltipDetachedTriggersFullDemo() {\n  return (\n    <Tooltip.Provider>\n      <div className=\"flex\">\n        <Tooltip.Trigger\n          className=\"\n            box-border flex size-10 items-center justify-center\n            border border-gray-200 rounded-l-md\n            bg-gray-50\n            text-base text-gray-900\n            select-none\n            data-popup-open:bg-gray-100\n            focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-600\n            hover:bg-gray-100\n            active:bg-gray-100\"\n          handle={demoTooltip}\n          payload={InfoContent}\n        >\n          <InfoIcon aria-label=\"This is information about the feature\" className=\"size-5\" />\n        </Tooltip.Trigger>\n\n        <Tooltip.Trigger\n          className=\"\n            box-border flex size-10 items-center justify-center\n            border-y border-r border-gray-200\n            bg-gray-50\n            text-base text-gray-900\n            select-none\n            data-popup-open:bg-gray-100\n            focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-600\n            hover:bg-gray-100\n            active:bg-gray-100\"\n          handle={demoTooltip}\n          payload={HelpContent}\n        >\n          <HelpIcon aria-label=\"Need help?\" className=\"size-5\" />\n        </Tooltip.Trigger>\n\n        <Tooltip.Trigger\n          className=\"\n            box-border flex size-10 items-center justify-center\n            border-y border-r border-gray-200 rounded-r-md\n            bg-gray-50\n            text-base text-gray-900\n            select-none\n            data-popup-open:bg-gray-100\n            focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-600\n            hover:bg-gray-100\n            active:bg-gray-100\"\n          handle={demoTooltip}\n          payload={AlertContent}\n        >\n          <AlertIcon aria-label=\"Warning: This action cannot be undone\" className=\"size-5\" />\n        </Tooltip.Trigger>\n      </div>\n\n      <Tooltip.Root handle={demoTooltip}>\n        {({ payload: Payload }) => (\n          <Tooltip.Portal>\n            <Tooltip.Positioner\n              sideOffset={10}\n              className=\"\n                h-(--positioner-height) w-(--positioner-width)\n                max-w-(--available-width)\n                transition-[top,left,right,bottom,transform]\n                duration-[0.35s]\n                ease-[cubic-bezier(0.22,1,0.36,1)]\n                data-instant:transition-none\"\n            >\n              <Tooltip.Popup\n                className=\"\n                  relative\n                  h-(--popup-height,auto) w-(--popup-width,auto)\n                  max-w-[500px]\n                  rounded-md\n                  bg-[canvas]\n                  text-sm\n                  origin-(--transform-origin)\n                  shadow-lg shadow-gray-200 outline-1 outline-gray-200\n                  transition-[width,height,opacity,scale]\n                  duration-[0.35s]\n                  ease-[cubic-bezier(0.22,1,0.36,1)]\n                  data-ending-style:opacity-0 data-ending-style:scale-90\n                  data-instant:transition-none\n                  data-starting-style:opacity-0 data-starting-style:scale-90\n                  dark:shadow-none dark:outline-gray-300 dark:-outline-offset-1\"\n              >\n                <Tooltip.Arrow\n                  className=\"\n                    flex\n                    transition-[left]\n                    duration-[0.35s]\n                    ease-[cubic-bezier(0.22,1,0.36,1)]\n                    data-instant:transition-none\n                    data-[side=bottom]:-top-2 data-[side=bottom]:rotate-0\n                    data-[side=left]:right-[-13px] data-[side=left]:rotate-90\n                    data-[side=right]:left-[-13px] data-[side=right]:-rotate-90\n                    data-[side=top]:-bottom-2 data-[side=top]:rotate-180\"\n                >\n                  <ArrowSvg />\n                </Tooltip.Arrow>\n\n                <Tooltip.Viewport\n                  className=\"\n                    [--viewport-inline-padding:0.5rem]\n                    relative\n                    h-full w-full\n                    overflow-clip\n                    px-[var(--viewport-inline-padding)] py-1\n                    [&_[data-previous]]:w-[calc(var(--popup-width)-2*var(--viewport-inline-padding))]\n                    [&_[data-previous]]:translate-x-0\n                    [&_[data-previous]]:opacity-100\n                    [&_[data-previous]]:transition-[translate,opacity]\n                    [&_[data-previous]]:duration-[350ms,175ms]\n                    [&_[data-previous]]:ease-[cubic-bezier(0.22,1,0.36,1)]\n                    [&_[data-current]]:w-[calc(var(--popup-width)-2*var(--viewport-inline-padding))]\n                    [&_[data-current]]:translate-x-0\n                    [&_[data-current]]:opacity-100\n                    [&_[data-current]]:transition-[translate,opacity]\n                    [&_[data-current]]:duration-[350ms,175ms]\n                    [&_[data-current]]:ease-[cubic-bezier(0.22,1,0.36,1)]\n                    data-[activation-direction~='left']:[&_[data-current][data-starting-style]]:-translate-x-1/2\n                    data-[activation-direction~='left']:[&_[data-current][data-starting-style]]:opacity-0\n                    data-[activation-direction~='right']:[&_[data-current][data-starting-style]]:translate-x-1/2\n                    data-[activation-direction~='right']:[&_[data-current][data-starting-style]]:opacity-0\n                    [[data-instant]_&_[data-previous]]:transition-none\n                    [[data-instant]_&_[data-current]]:transition-none\n                    data-[activation-direction~='left']:[&_[data-previous][data-ending-style]]:translate-x-1/2\n                    data-[activation-direction~='left']:[&_[data-previous][data-ending-style]]:opacity-0\n                    data-[activation-direction~='right']:[&_[data-previous][data-ending-style]]:-translate-x-1/2\n                    data-[activation-direction~='right']:[&_[data-previous][data-ending-style]]:opacity-0\"\n                >\n                  {Payload !== undefined && <Payload />}\n                </Tooltip.Viewport>\n              </Tooltip.Popup>\n            </Tooltip.Positioner>\n          </Tooltip.Portal>\n        )}\n      </Tooltip.Root>\n    </Tooltip.Provider>\n  );\n}\n\nfunction InfoContent() {\n  return <span>This is information about the feature</span>;\n}\n\nfunction HelpContent() {\n  return <span>Need help?</span>;\n}\n\nfunction AlertContent() {\n  return <span>Warning: This action cannot be undone</span>;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/tooltip/demos/detached-triggers-simple/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Tooltip } from '@base-ui/react/tooltip';\nimport styles from '../../index.module.css';\n\nconst demoTooltip = Tooltip.createHandle();\n\nexport default function TooltipDetachedTriggersSimpleDemo() {\n  return (\n    <Tooltip.Provider>\n      <Tooltip.Trigger className={styles.IconButton} handle={demoTooltip}>\n        <InfoIcon aria-label=\"This is a detached tooltip\" className={styles.Icon} />\n      </Tooltip.Trigger>\n\n      <Tooltip.Root handle={demoTooltip}>\n        <Tooltip.Portal>\n          <Tooltip.Positioner sideOffset={10}>\n            <Tooltip.Popup className={styles.Popup}>\n              <Tooltip.Arrow className={styles.Arrow}>\n                <ArrowSvg />\n              </Tooltip.Arrow>\n              This is a detached tooltip\n            </Tooltip.Popup>\n          </Tooltip.Positioner>\n        </Tooltip.Portal>\n      </Tooltip.Root>\n    </Tooltip.Provider>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nfunction InfoIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"20\"\n      height=\"20\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth={2}\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <circle cx=\"12\" cy=\"12\" r=\"10\" />\n      <path d=\"M12 16v-4\" />\n      <path d=\"M12 8h.01\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/tooltip/demos/detached-triggers-simple/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoTooltipDetachedTriggersSimple = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/tooltip/demos/detached-triggers-simple/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Tooltip } from '@base-ui/react/tooltip';\nimport { ArrowSvg, InfoIcon } from '../../icons-tw';\n\nconst demoTooltip = Tooltip.createHandle();\n\nexport default function TooltipDetachedTriggersSimpleDemo() {\n  return (\n    <Tooltip.Provider>\n      <Tooltip.Trigger\n        className=\"\n          flex size-10 items-center justify-center\n          border border-gray-200 rounded-md\n          bg-gray-50\n          text-gray-900\n          select-none\n          data-popup-open:bg-gray-100\n          focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800\n          hover:bg-gray-100\n          active:bg-gray-100\"\n        handle={demoTooltip}\n      >\n        <InfoIcon aria-label=\"This is a detached tooltip\" />\n      </Tooltip.Trigger>\n\n      <Tooltip.Root handle={demoTooltip}>\n        <Tooltip.Portal>\n          <Tooltip.Positioner sideOffset={10}>\n            <Tooltip.Popup\n              className=\"\n                px-2 py-1\n                rounded-md\n                bg-[canvas]\n                text-sm\n                origin-(--transform-origin)\n                shadow-lg shadow-gray-200 outline-1 outline-gray-200\n                transition-[transform,scale,opacity]\n                data-ending-style:opacity-0 data-ending-style:scale-90\n                data-instant:transition-none\n                data-starting-style:opacity-0 data-starting-style:scale-90\n                dark:shadow-none dark:outline-gray-300 dark:-outline-offset-1\"\n            >\n              <Tooltip.Arrow\n                className=\"\n                  flex\n                  data-[side=bottom]:-top-2 data-[side=bottom]:rotate-0\n                  data-[side=left]:right-[-13px] data-[side=left]:rotate-90\n                  data-[side=right]:left-[-13px] data-[side=right]:-rotate-90\n                  data-[side=top]:-bottom-2 data-[side=top]:rotate-180\"\n              >\n                <ArrowSvg />\n              </Tooltip.Arrow>\n              This is a detached tooltip\n            </Tooltip.Popup>\n          </Tooltip.Positioner>\n        </Tooltip.Portal>\n      </Tooltip.Root>\n    </Tooltip.Provider>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/tooltip/demos/hero/css-modules/index.module.css",
    "content": ".Panel {\n  display: flex;\n  border: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-50);\n  border-radius: 0.375rem;\n  padding: 0.125rem;\n}\n\n.Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 2rem;\n  height: 2rem;\n  padding: 0;\n  margin: 0;\n  outline: 0;\n  border: 0;\n  border-radius: 0.25rem;\n  background-color: transparent;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    background-color: transparent;\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-200);\n  }\n}\n\n.Icon {\n  width: 1rem;\n  height: 1rem;\n}\n\n.Popup {\n  box-sizing: border-box;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  display: flex;\n  flex-direction: column;\n  padding: 0.25rem 0.5rem;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  &[data-instant] {\n    transition: none;\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Arrow {\n  display: flex;\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/tooltip/demos/hero/css-modules/index.tsx",
    "content": "import * as React from 'react';\nimport { Tooltip } from '@base-ui/react/tooltip';\nimport styles from './index.module.css';\n\nexport default function ExampleTooltip() {\n  return (\n    <Tooltip.Provider>\n      <div className={styles.Panel}>\n        <Tooltip.Root>\n          <Tooltip.Trigger className={styles.Button}>\n            <BoldIcon className={styles.Icon} aria-label=\"Bold\" />\n          </Tooltip.Trigger>\n          <Tooltip.Portal>\n            <Tooltip.Positioner sideOffset={10}>\n              <Tooltip.Popup className={styles.Popup}>\n                <Tooltip.Arrow className={styles.Arrow}>\n                  <ArrowSvg />\n                </Tooltip.Arrow>\n                Bold\n              </Tooltip.Popup>\n            </Tooltip.Positioner>\n          </Tooltip.Portal>\n        </Tooltip.Root>\n\n        <Tooltip.Root>\n          <Tooltip.Trigger className={styles.Button}>\n            <ItalicIcon className={styles.Icon} aria-label=\"Italic\" />\n          </Tooltip.Trigger>\n          <Tooltip.Portal>\n            <Tooltip.Positioner sideOffset={10}>\n              <Tooltip.Popup className={styles.Popup}>\n                <Tooltip.Arrow className={styles.Arrow}>\n                  <ArrowSvg />\n                </Tooltip.Arrow>\n                Italic\n              </Tooltip.Popup>\n            </Tooltip.Positioner>\n          </Tooltip.Portal>\n        </Tooltip.Root>\n\n        <Tooltip.Root>\n          <Tooltip.Trigger className={styles.Button}>\n            <UnderlineIcon className={styles.Icon} aria-label=\"Underline\" />\n          </Tooltip.Trigger>\n          <Tooltip.Portal>\n            <Tooltip.Positioner sideOffset={10}>\n              <Tooltip.Popup className={styles.Popup}>\n                <Tooltip.Arrow className={styles.Arrow}>\n                  <ArrowSvg />\n                </Tooltip.Arrow>\n                Underline\n              </Tooltip.Popup>\n            </Tooltip.Positioner>\n          </Tooltip.Portal>\n        </Tooltip.Root>\n      </div>\n    </Tooltip.Provider>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nfunction BoldIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentcolor\" {...props}>\n      <path d=\"M3.73353 2.13333C3.4386 2.13333 3.2002 2.37226 3.2002 2.66666C3.2002 2.96106 3.4386 3.2 3.73353 3.2H4.26686V12.8H3.73353C3.4386 12.8 3.2002 13.0389 3.2002 13.3333C3.2002 13.6277 3.4386 13.8667 3.73353 13.8667H9.86686C11.7783 13.8667 13.3335 12.3115 13.3335 10.4C13.3335 8.9968 12.4945 7.78881 11.2929 7.24375C11.8897 6.70615 12.2669 5.93066 12.2669 5.06666C12.2669 3.44906 10.9506 2.13333 9.33353 2.13333H3.73353ZM6.93353 3.2H8.26686C9.29619 3.2 10.1335 4.03733 10.1335 5.06666C10.1335 6.096 9.29619 6.93333 8.26686 6.93333H6.93353V3.2ZM6.93353 8H7.73353H8.26686C9.59006 8 10.6669 9.0768 10.6669 10.4C10.6669 11.7232 9.59006 12.8 8.26686 12.8H6.93353V8Z\" />\n    </svg>\n  );\n}\n\nfunction ItalicIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentcolor\" {...props}>\n      <path d=\"M8.52599 2.12186C8.48583 2.12267 8.44578 2.1265 8.4062 2.13332H6.93328C6.86261 2.13232 6.79244 2.14538 6.72686 2.17173C6.66127 2.19808 6.60158 2.23721 6.55125 2.28683C6.50092 2.33646 6.46096 2.39559 6.43368 2.46079C6.4064 2.526 6.39235 2.59597 6.39235 2.66665C6.39235 2.73733 6.4064 2.80731 6.43368 2.87251C6.46096 2.93772 6.50092 2.99685 6.55125 3.04647C6.60158 3.0961 6.66127 3.13522 6.72686 3.16157C6.79244 3.18793 6.86261 3.20099 6.93328 3.19999H7.70099L6.69057 12.8H5.86661C5.79594 12.799 5.72577 12.812 5.66019 12.8384C5.59461 12.8648 5.53492 12.9039 5.48459 12.9535C5.43425 13.0031 5.39429 13.0623 5.36701 13.1275C5.33973 13.1927 5.32568 13.2626 5.32568 13.3333C5.32568 13.404 5.33973 13.474 5.36701 13.5392C5.39429 13.6044 5.43425 13.6635 5.48459 13.7131C5.53492 13.7628 5.59461 13.8019 5.66019 13.8282C5.72577 13.8546 5.79594 13.8677 5.86661 13.8667H9.06661C9.13729 13.8677 9.20745 13.8546 9.27304 13.8282C9.33862 13.8019 9.39831 13.7628 9.44864 13.7131C9.49897 13.6635 9.53894 13.6044 9.56622 13.5392C9.5935 13.474 9.60754 13.404 9.60754 13.3333C9.60754 13.2626 9.5935 13.1927 9.56622 13.1275C9.53894 13.0623 9.49897 13.0031 9.44864 12.9535C9.39831 12.9039 9.33862 12.8648 9.27304 12.8384C9.20745 12.812 9.13729 12.799 9.06661 12.8H8.2989L9.30932 3.19999H10.1333C10.204 3.20099 10.2741 3.18793 10.3397 3.16157C10.4053 3.13522 10.465 3.0961 10.5153 3.04647C10.5656 2.99685 10.6056 2.93772 10.6329 2.87251C10.6602 2.80731 10.6742 2.73733 10.6742 2.66665C10.6742 2.59597 10.6602 2.526 10.6329 2.46079C10.6056 2.39559 10.5656 2.33646 10.5153 2.28683C10.465 2.23721 10.4053 2.19808 10.3397 2.17173C10.2741 2.14538 10.204 2.13232 10.1333 2.13332H8.66349C8.61807 2.12555 8.57207 2.12171 8.52599 2.12186Z\" />\n    </svg>\n  );\n}\n\nfunction UnderlineIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentcolor\" {...props}>\n      <path d=\"M3.73331 2.13332C3.66264 2.13232 3.59247 2.14538 3.52689 2.17173C3.46131 2.19809 3.40161 2.23721 3.35128 2.28684C3.30095 2.33646 3.26099 2.39559 3.23371 2.4608C3.20643 2.526 3.19238 2.59598 3.19238 2.66666C3.19238 2.73734 3.20643 2.80731 3.23371 2.87252C3.26099 2.93772 3.30095 2.99685 3.35128 3.04648C3.40161 3.0961 3.46131 3.13523 3.52689 3.16158C3.59247 3.18793 3.66264 3.20099 3.73331 3.19999V7.99999C3.73331 10.224 5.55144 12.2667 7.99998 12.2667C10.4485 12.2667 12.2666 10.224 12.2666 7.99999V3.19999C12.3373 3.20099 12.4075 3.18793 12.4731 3.16158C12.5386 3.13523 12.5983 3.0961 12.6487 3.04648C12.699 2.99685 12.739 2.93772 12.7662 2.87252C12.7935 2.80731 12.8076 2.73734 12.8076 2.66666C12.8076 2.59598 12.7935 2.526 12.7662 2.4608C12.739 2.39559 12.699 2.33646 12.6487 2.28684C12.5983 2.23721 12.5386 2.19809 12.4731 2.17173C12.4075 2.14538 12.3373 2.13232 12.2666 2.13332H10.1333C10.0626 2.13232 9.99247 2.14538 9.92689 2.17173C9.8613 2.19809 9.80161 2.23721 9.75128 2.28684C9.70095 2.33646 9.66099 2.39559 9.63371 2.4608C9.60643 2.526 9.59238 2.59598 9.59238 2.66666C9.59238 2.73734 9.60643 2.80731 9.63371 2.87252C9.66099 2.93772 9.70095 2.99685 9.75128 3.04648C9.80161 3.0961 9.8613 3.13523 9.92689 3.16158C9.99247 3.18793 10.0626 3.20099 10.1333 3.19999V8.97187C10.1333 10.0855 9.32179 11.0818 8.21352 11.1896C6.94152 11.3138 5.86665 10.3136 5.86665 9.06666V3.19999C5.93732 3.20099 6.00748 3.18793 6.07307 3.16158C6.13865 3.13523 6.19834 3.0961 6.24867 3.04648C6.299 2.99685 6.33897 2.93772 6.36625 2.87252C6.39353 2.80731 6.40757 2.73734 6.40757 2.66666C6.40757 2.59598 6.39353 2.526 6.36625 2.4608C6.33897 2.39559 6.299 2.33646 6.24867 2.28684C6.19834 2.23721 6.13865 2.19809 6.07307 2.17173C6.00748 2.14538 5.93732 2.13232 5.86665 2.13332H3.73331ZM3.73331 13.3333C3.66264 13.3323 3.59247 13.3454 3.52689 13.3717C3.46131 13.3981 3.40161 13.4372 3.35128 13.4868C3.30095 13.5365 3.26099 13.5956 3.23371 13.6608C3.20643 13.726 3.19238 13.796 3.19238 13.8667C3.19238 13.9373 3.20643 14.0073 3.23371 14.0725C3.26099 14.1377 3.30095 14.1969 3.35128 14.2465C3.40161 14.2961 3.46131 14.3352 3.52689 14.3616C3.59247 14.3879 3.66264 14.401 3.73331 14.4H12.2666C12.3373 14.401 12.4075 14.3879 12.4731 14.3616C12.5386 14.3352 12.5983 14.2961 12.6487 14.2465C12.699 14.1969 12.739 14.1377 12.7662 14.0725C12.7935 14.0073 12.8076 13.9373 12.8076 13.8667C12.8076 13.796 12.7935 13.726 12.7662 13.6608C12.739 13.5956 12.699 13.5365 12.6487 13.4868C12.5983 13.4372 12.5386 13.3981 12.4731 13.3717C12.4075 13.3454 12.3373 13.3323 12.2666 13.3333H3.73331Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/tooltip/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoTooltipHero = createDemoWithVariants(import.meta.url, { CssModules, Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/tooltip/demos/hero/tailwind/index.tsx",
    "content": "import * as React from 'react';\nimport { Tooltip } from '@base-ui/react/tooltip';\n\nexport default function ExampleTooltip() {\n  return (\n    <Tooltip.Provider>\n      <div className=\"flex border border-gray-200 rounded-md bg-gray-50 p-0.5\">\n        <Tooltip.Root>\n          <Tooltip.Trigger\n            className=\"\n              flex size-8 items-center justify-center\n              border-0 rounded-sm\n              bg-transparent\n              text-gray-900\n              select-none\n              data-popup-open:bg-gray-100\n              focus-visible:bg-none\n              focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800\n              hover:bg-gray-100\n              active:bg-gray-200\n              focus-visible:not-[&:hover]:bg-transparent\"\n          >\n            <BoldIcon aria-label=\"Bold\" className=\"size-4\" />\n          </Tooltip.Trigger>\n          <Tooltip.Portal>\n            <Tooltip.Positioner sideOffset={10}>\n              <Tooltip.Popup\n                className=\"\n                  flex flex-col\n                  px-2 py-1\n                  rounded-md\n                  bg-[canvas]\n                  text-sm\n                  origin-(--transform-origin)\n                  shadow-lg shadow-gray-200 outline-1 outline-gray-200\n                  transition-[transform,scale,opacity]\n                  data-ending-style:opacity-0 data-ending-style:scale-90\n                  data-instant:transition-none\n                  data-starting-style:opacity-0 data-starting-style:scale-90\n                  dark:shadow-none dark:outline-gray-300 dark:-outline-offset-1\"\n              >\n                <Tooltip.Arrow\n                  className=\"\n                    flex\n                    data-[side=bottom]:-top-2 data-[side=bottom]:rotate-0\n                    data-[side=left]:right-[-13px] data-[side=left]:rotate-90\n                    data-[side=right]:left-[-13px] data-[side=right]:-rotate-90\n                    data-[side=top]:-bottom-2 data-[side=top]:rotate-180\"\n                >\n                  <ArrowSvg />\n                </Tooltip.Arrow>\n                Bold\n              </Tooltip.Popup>\n            </Tooltip.Positioner>\n          </Tooltip.Portal>\n        </Tooltip.Root>\n\n        <Tooltip.Root>\n          <Tooltip.Trigger\n            className=\"\n              flex size-8 items-center justify-center\n              border-0 rounded-sm\n              bg-transparent\n              text-gray-900\n              select-none\n              data-popup-open:bg-gray-100\n              focus-visible:bg-none\n              focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800\n              hover:bg-gray-100\n              active:bg-gray-200\n              focus-visible:not-[&:hover]:bg-transparent\"\n          >\n            <ItalicIcon aria-label=\"Italic\" className=\"size-4\" />\n          </Tooltip.Trigger>\n          <Tooltip.Portal>\n            <Tooltip.Positioner sideOffset={10}>\n              <Tooltip.Popup\n                className=\"\n                  flex flex-col\n                  px-2 py-1\n                  rounded-md\n                  bg-[canvas]\n                  text-sm\n                  origin-(--transform-origin)\n                  shadow-lg shadow-gray-200 outline-1 outline-gray-200\n                  transition-[transform,scale,opacity]\n                  data-ending-style:opacity-0 data-ending-style:scale-90\n                  data-instant:transition-none\n                  data-starting-style:opacity-0 data-starting-style:scale-90\n                  dark:shadow-none dark:outline-gray-300 dark:-outline-offset-1\"\n              >\n                <Tooltip.Arrow\n                  className=\"\n                    flex\n                    data-[side=bottom]:-top-2 data-[side=bottom]:rotate-0\n                    data-[side=left]:right-[-13px] data-[side=left]:rotate-90\n                    data-[side=right]:left-[-13px] data-[side=right]:-rotate-90\n                    data-[side=top]:-bottom-2 data-[side=top]:rotate-180\"\n                >\n                  <ArrowSvg />\n                </Tooltip.Arrow>\n                Italic\n              </Tooltip.Popup>\n            </Tooltip.Positioner>\n          </Tooltip.Portal>\n        </Tooltip.Root>\n\n        <Tooltip.Root>\n          <Tooltip.Trigger\n            className=\"\n              flex size-8 items-center justify-center\n              border-0 rounded-sm\n              bg-transparent\n              text-gray-900\n              select-none\n              data-popup-open:bg-gray-100\n              focus-visible:bg-none\n              focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800\n              hover:bg-gray-100\n              active:bg-gray-200\n              focus-visible:not-[&:hover]:bg-transparent\"\n          >\n            <UnderlineIcon aria-label=\"Underline\" className=\"size-4\" />\n          </Tooltip.Trigger>\n          <Tooltip.Portal>\n            <Tooltip.Positioner sideOffset={10}>\n              <Tooltip.Popup\n                className=\"\n                  flex flex-col\n                  px-2 py-1\n                  rounded-md\n                  bg-[canvas]\n                  text-sm\n                  origin-(--transform-origin)\n                  shadow-lg shadow-gray-200 outline-1 outline-gray-200\n                  transition-[transform,scale,opacity]\n                  data-ending-style:opacity-0 data-ending-style:scale-90\n                  data-instant:transition-none\n                  data-starting-style:opacity-0 data-starting-style:scale-90\n                  dark:shadow-none dark:outline-gray-300 dark:-outline-offset-1\"\n              >\n                <Tooltip.Arrow\n                  className=\"\n                    flex\n                    data-[side=bottom]:-top-2 data-[side=bottom]:rotate-0\n                    data-[side=left]:right-[-13px] data-[side=left]:rotate-90\n                    data-[side=right]:left-[-13px] data-[side=right]:-rotate-90\n                    data-[side=top]:-bottom-2 data-[side=top]:rotate-180\"\n                >\n                  <ArrowSvg />\n                </Tooltip.Arrow>\n                Underline\n              </Tooltip.Popup>\n            </Tooltip.Positioner>\n          </Tooltip.Portal>\n        </Tooltip.Root>\n      </div>\n    </Tooltip.Provider>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className=\"fill-[canvas]\"\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className=\"fill-gray-200 dark:fill-none\"\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className=\"dark:fill-gray-300\"\n      />\n    </svg>\n  );\n}\n\nfunction BoldIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentcolor\" {...props}>\n      <path d=\"M3.73353 2.13333C3.4386 2.13333 3.2002 2.37226 3.2002 2.66666C3.2002 2.96106 3.4386 3.2 3.73353 3.2H4.26686V12.8H3.73353C3.4386 12.8 3.2002 13.0389 3.2002 13.3333C3.2002 13.6277 3.4386 13.8667 3.73353 13.8667H9.86686C11.7783 13.8667 13.3335 12.3115 13.3335 10.4C13.3335 8.9968 12.4945 7.78881 11.2929 7.24375C11.8897 6.70615 12.2669 5.93066 12.2669 5.06666C12.2669 3.44906 10.9506 2.13333 9.33353 2.13333H3.73353ZM6.93353 3.2H8.26686C9.29619 3.2 10.1335 4.03733 10.1335 5.06666C10.1335 6.096 9.29619 6.93333 8.26686 6.93333H6.93353V3.2ZM6.93353 8H7.73353H8.26686C9.59006 8 10.6669 9.0768 10.6669 10.4C10.6669 11.7232 9.59006 12.8 8.26686 12.8H6.93353V8Z\" />\n    </svg>\n  );\n}\n\nfunction ItalicIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentcolor\" {...props}>\n      <path d=\"M8.52599 2.12186C8.48583 2.12267 8.44578 2.1265 8.4062 2.13332H6.93328C6.86261 2.13232 6.79244 2.14538 6.72686 2.17173C6.66127 2.19808 6.60158 2.23721 6.55125 2.28683C6.50092 2.33646 6.46096 2.39559 6.43368 2.46079C6.4064 2.526 6.39235 2.59597 6.39235 2.66665C6.39235 2.73733 6.4064 2.80731 6.43368 2.87251C6.46096 2.93772 6.50092 2.99685 6.55125 3.04647C6.60158 3.0961 6.66127 3.13522 6.72686 3.16157C6.79244 3.18793 6.86261 3.20099 6.93328 3.19999H7.70099L6.69057 12.8H5.86661C5.79594 12.799 5.72577 12.812 5.66019 12.8384C5.59461 12.8648 5.53492 12.9039 5.48459 12.9535C5.43425 13.0031 5.39429 13.0623 5.36701 13.1275C5.33973 13.1927 5.32568 13.2626 5.32568 13.3333C5.32568 13.404 5.33973 13.474 5.36701 13.5392C5.39429 13.6044 5.43425 13.6635 5.48459 13.7131C5.53492 13.7628 5.59461 13.8019 5.66019 13.8282C5.72577 13.8546 5.79594 13.8677 5.86661 13.8667H9.06661C9.13729 13.8677 9.20745 13.8546 9.27304 13.8282C9.33862 13.8019 9.39831 13.7628 9.44864 13.7131C9.49897 13.6635 9.53894 13.6044 9.56622 13.5392C9.5935 13.474 9.60754 13.404 9.60754 13.3333C9.60754 13.2626 9.5935 13.1927 9.56622 13.1275C9.53894 13.0623 9.49897 13.0031 9.44864 12.9535C9.39831 12.9039 9.33862 12.8648 9.27304 12.8384C9.20745 12.812 9.13729 12.799 9.06661 12.8H8.2989L9.30932 3.19999H10.1333C10.204 3.20099 10.2741 3.18793 10.3397 3.16157C10.4053 3.13522 10.465 3.0961 10.5153 3.04647C10.5656 2.99685 10.6056 2.93772 10.6329 2.87251C10.6602 2.80731 10.6742 2.73733 10.6742 2.66665C10.6742 2.59597 10.6602 2.526 10.6329 2.46079C10.6056 2.39559 10.5656 2.33646 10.5153 2.28683C10.465 2.23721 10.4053 2.19808 10.3397 2.17173C10.2741 2.14538 10.204 2.13232 10.1333 2.13332H8.66349C8.61807 2.12555 8.57207 2.12171 8.52599 2.12186Z\" />\n    </svg>\n  );\n}\n\nfunction UnderlineIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentcolor\" {...props}>\n      <path d=\"M3.73331 2.13332C3.66264 2.13232 3.59247 2.14538 3.52689 2.17173C3.46131 2.19809 3.40161 2.23721 3.35128 2.28684C3.30095 2.33646 3.26099 2.39559 3.23371 2.4608C3.20643 2.526 3.19238 2.59598 3.19238 2.66666C3.19238 2.73734 3.20643 2.80731 3.23371 2.87252C3.26099 2.93772 3.30095 2.99685 3.35128 3.04648C3.40161 3.0961 3.46131 3.13523 3.52689 3.16158C3.59247 3.18793 3.66264 3.20099 3.73331 3.19999V7.99999C3.73331 10.224 5.55144 12.2667 7.99998 12.2667C10.4485 12.2667 12.2666 10.224 12.2666 7.99999V3.19999C12.3373 3.20099 12.4075 3.18793 12.4731 3.16158C12.5386 3.13523 12.5983 3.0961 12.6487 3.04648C12.699 2.99685 12.739 2.93772 12.7662 2.87252C12.7935 2.80731 12.8076 2.73734 12.8076 2.66666C12.8076 2.59598 12.7935 2.526 12.7662 2.4608C12.739 2.39559 12.699 2.33646 12.6487 2.28684C12.5983 2.23721 12.5386 2.19809 12.4731 2.17173C12.4075 2.14538 12.3373 2.13232 12.2666 2.13332H10.1333C10.0626 2.13232 9.99247 2.14538 9.92689 2.17173C9.8613 2.19809 9.80161 2.23721 9.75128 2.28684C9.70095 2.33646 9.66099 2.39559 9.63371 2.4608C9.60643 2.526 9.59238 2.59598 9.59238 2.66666C9.59238 2.73734 9.60643 2.80731 9.63371 2.87252C9.66099 2.93772 9.70095 2.99685 9.75128 3.04648C9.80161 3.0961 9.8613 3.13523 9.92689 3.16158C9.99247 3.18793 10.0626 3.20099 10.1333 3.19999V8.97187C10.1333 10.0855 9.32179 11.0818 8.21352 11.1896C6.94152 11.3138 5.86665 10.3136 5.86665 9.06666V3.19999C5.93732 3.20099 6.00748 3.18793 6.07307 3.16158C6.13865 3.13523 6.19834 3.0961 6.24867 3.04648C6.299 2.99685 6.33897 2.93772 6.36625 2.87252C6.39353 2.80731 6.40757 2.73734 6.40757 2.66666C6.40757 2.59598 6.39353 2.526 6.36625 2.4608C6.33897 2.39559 6.299 2.33646 6.24867 2.28684C6.19834 2.23721 6.13865 2.19809 6.07307 2.17173C6.00748 2.14538 5.93732 2.13232 5.86665 2.13332H3.73331ZM3.73331 13.3333C3.66264 13.3323 3.59247 13.3454 3.52689 13.3717C3.46131 13.3981 3.40161 13.4372 3.35128 13.4868C3.30095 13.5365 3.26099 13.5956 3.23371 13.6608C3.20643 13.726 3.19238 13.796 3.19238 13.8667C3.19238 13.9373 3.20643 14.0073 3.23371 14.0725C3.26099 14.1377 3.30095 14.1969 3.35128 14.2465C3.40161 14.2961 3.46131 14.3352 3.52689 14.3616C3.59247 14.3879 3.66264 14.401 3.73331 14.4H12.2666C12.3373 14.401 12.4075 14.3879 12.4731 14.3616C12.5386 14.3352 12.5983 14.2961 12.6487 14.2465C12.699 14.1969 12.739 14.1377 12.7662 14.0725C12.7935 14.0073 12.8076 13.9373 12.8076 13.8667C12.8076 13.796 12.7935 13.726 12.7662 13.6608C12.739 13.5956 12.699 13.5365 12.6487 13.4868C12.5983 13.4372 12.5386 13.3981 12.4731 13.3717C12.4075 13.3454 12.3373 13.3323 12.2666 13.3333H3.73331Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/tooltip/demos/icons-tw.tsx",
    "content": "import * as React from 'react';\n\nexport function ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className=\"fill-[canvas]\"\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className=\"fill-gray-200 dark:fill-none\"\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className=\"dark:fill-gray-300\"\n      />\n    </svg>\n  );\n}\n\nexport function InfoIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"20\"\n      height=\"20\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth={2}\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <circle cx=\"12\" cy=\"12\" r=\"10\" />\n      <path d=\"M12 16v-4\" />\n      <path d=\"M12 8h.01\" />\n    </svg>\n  );\n}\n\nexport function HelpIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"20\"\n      height=\"20\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth={2}\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <circle cx=\"12\" cy=\"12\" r=\"10\" />\n      <path d=\"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3\" />\n      <path d=\"M12 17h.01\" />\n    </svg>\n  );\n}\n\nexport function AlertIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"20\"\n      height=\"20\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth={2}\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z\" />\n      <path d=\"M12 9v4\" />\n      <path d=\"M12 17h.01\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/tooltip/demos/index.module.css",
    "content": ".Container {\n  display: flex;\n  gap: 0.5rem;\n  flex-wrap: wrap;\n  justify-content: center;\n}\n\n.ButtonGroup {\n  display: flex;\n}\n\n.IconButton {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 2.5rem;\n  height: 2.5rem;\n  padding: 0;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-50);\n  color: var(--color-gray-900);\n  user-select: none;\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:only-child {\n    border-radius: 0.375rem;\n  }\n\n  &:first-child:not(:only-child) {\n    border-radius: 0.375rem 0 0 0.375rem;\n  }\n\n  &:last-child:not(:only-child) {\n    border-radius: 0 0.375rem 0.375rem 0;\n  }\n\n  &:not(:first-child) {\n    border-left: none;\n  }\n}\n\n.Icon {\n  width: 1.25rem;\n  height: 1.25rem;\n}\n\n.Positioner {\n  height: var(--positioner-height);\n  width: var(--positioner-width);\n  max-width: var(--available-width);\n}\n\n.Popup {\n  box-sizing: border-box;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  padding: 0.25rem 0.5rem;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  &[data-instant] {\n    transition: none;\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Arrow {\n  display: flex;\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n\n.Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/components/tooltip/page.mdx",
    "content": "# Tooltip\n\n<Subtitle>\n  A popup that appears when an element is hovered or focused, showing a hint for sighted users.\n</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"A high-quality, unstyled React tooltip component that appears when an element is hovered or focused, showing a hint for sighted users.\"\n/>\n\nimport { DemoTooltipHero } from './demos/hero';\n\n<DemoTooltipHero />\n\n## Usage guidelines\n\n- **Prefer using tooltips as visual labels only**: Tooltips should act as supplementary visual labels for sighted mouse and keyboard users. Tooltips alone are not accessible to touch or screen reader users. See [Alternatives to tooltips](#alternatives-to-tooltips) for more details.\n- **Provide an accessible name for the trigger**: Tooltips are visual-only elements and are not a replacement for labeling the trigger. The tooltip's trigger must have an `aria-label` attribute that closely matches the tooltip's content to ensure consistency for screen reader users.\n\n## Anatomy\n\nImport the component and assemble its parts:\n\n```jsx title=\"Anatomy\"\nimport { Tooltip } from '@base-ui/react/tooltip';\n\n<Tooltip.Provider>\n  <Tooltip.Root>\n    <Tooltip.Trigger />\n    <Tooltip.Portal>\n      <Tooltip.Positioner>\n        <Tooltip.Popup>\n          <Tooltip.Arrow />\n        </Tooltip.Popup>\n      </Tooltip.Positioner>\n    </Tooltip.Portal>\n  </Tooltip.Root>\n</Tooltip.Provider>;\n```\n\n## Alternatives to tooltips\n\nTooltips should be supplementary popups that provide non-essential clarity in high-density UIs. A user should not miss critical information if they never see a tooltip.\n\nTooltips don't work well with touch input. Unlike mouse pointers with hover capability, there's no easily discoverable way to reveal a tooltip before tapping its trigger on a touch device.\n\niOS doesn't provide a system-standard, touch-friendly tooltip affordance, while Android may show a tooltip on long press. However, on the web, long press is often used to trigger contextual menus in the browser, which can lead to potential conflicts. For this reason, tooltips are disabled on touch devices.\n\n### Infotips\n\nPopups that open when hovering an info icon should use [Popover](/react/components/popover) with the `openOnHover` prop on the trigger instead of a tooltip. This way, touch users and screen reader users can access the content.\n\nTo know when to reach for a popover instead of a tooltip, consider the **purpose** of the trigger element:\nIf the trigger's purpose is to open the popup itself, it's a popover. If the trigger's purpose is unrelated to opening the popup, it's a tooltip.\n\n### Description text\n\nTooltips are designed for sighted users and are not a reliable way to deliver important information to touch users or assistive technologies. If the description is important to understanding the element, don't hide it behind a tooltip — use inline text or [Popover](/react/components/popover) if space is limited, so the information is accessible to everyone.\n\nSince tooltips serve sighted mouse and keyboard users, iconography should clearly communicate the purpose of icon-only triggers, especially on mobile where the text label may not be visible.\n\nIf the description is not critical, a tooltip can still be used to provide extra clarity for sighted mouse or keyboard users.\n\n### Contextual feedback messages\n\nUse the Toast component's [anchoring ability](/react/components/toast#anchored-toasts) for more ergonomic DX, to ensure the message is announced to screen readers, and to support complex content.\n\n## Examples\n\n### Detached triggers\n\nA tooltip can be controlled by a trigger located either inside or outside the `<Tooltip.Root>` component.\nFor simple, one-off interactions, place the `<Tooltip.Trigger>` inside `<Tooltip.Root>`, as shown in the example at the top of this page.\n\nHowever, if defining the tooltip's content next to its trigger is not practical, you can use a detached trigger.\nThis involves placing the `<Tooltip.Trigger>` outside of `<Tooltip.Root>` and linking them with a `handle` created by the `Tooltip.createHandle()` function.\n\n```jsx title=\"Detached triggers\" {3,5} \"handle={demoTooltip}\"\nconst demoTooltip = Tooltip.createHandle();\n\n<Tooltip.Trigger handle={demoTooltip}>Button</Tooltip.Trigger>\n\n<Tooltip.Root handle={demoTooltip}>\n  ...\n</Tooltip.Root>\n```\n\nimport { DemoTooltipDetachedTriggersSimple } from './demos/detached-triggers-simple';\n\n<DemoTooltipDetachedTriggersSimple />\n\n### Multiple triggers\n\nA single tooltip can be opened by multiple trigger elements.\nYou can achieve this by using the same `handle` for several detached triggers, or by placing multiple `<Tooltip.Trigger>` components inside a single `<Tooltip.Root>`.\n\n```jsx title=\"Multiple triggers within the Root part\"\n<Tooltip.Root>\n  <Tooltip.Trigger>Trigger 1</Tooltip.Trigger>\n  <Tooltip.Trigger>Trigger 2</Tooltip.Trigger>\n  ...\n</Tooltip.Root>\n```\n\n```jsx title=\"Multiple detached triggers\"\nconst demoTooltip = Tooltip.createHandle();\n\n<Tooltip.Trigger handle={demoTooltip}>\n  Trigger 1\n</Tooltip.Trigger>\n\n<Tooltip.Trigger handle={demoTooltip}>\n  Trigger 2\n</Tooltip.Trigger>\n\n<Tooltip.Root handle={demoTooltip}>\n  ...\n</Tooltip.Root>\n```\n\nThe tooltip can render different content depending on which trigger opened it.\nThis is achieved by passing a `payload` to the `<Tooltip.Trigger>` and using the function-as-a-child pattern in `<Tooltip.Root>`.\n\nThe payload can be strongly typed by providing a type argument to the `createHandle()` function:\n\n```jsx title=\"Detached triggers with payload\" {1,3,7} \"payload\"\nconst demoTooltip = Tooltip.createHandle<{ text: string }>();\n\n<Tooltip.Trigger handle={demoTooltip} payload={{ text: 'Trigger 1' }}>\n  Trigger 1\n</Tooltip.Trigger>\n\n<Tooltip.Trigger handle={demoTooltip} payload={{ text: 'Trigger 2' }}>\n  Trigger 2\n</Tooltip.Trigger>\n\n<Tooltip.Root handle={demoTooltip}>\n  {({ payload }) => (\n    <Tooltip.Portal>\n      <Tooltip.Positioner sideOffset={8}>\n        <Tooltip.Popup className={styles.Popup}>\n          <Tooltip.Arrow className={styles.Arrow}>\n            <ArrowSvg />\n          </Tooltip.Arrow>\n          {payload !== undefined && (\n            <span>\n              Tooltip opened by {payload.text}\n            </span>\n          )}\n        </Tooltip.Popup>\n      </Tooltip.Positioner>\n    </Tooltip.Portal>\n  )}\n</Tooltip.Root>\n```\n\n### Controlled mode with multiple triggers\n\nYou can control the tooltip's open state externally using the `open` and `onOpenChange` props on `<Tooltip.Root>`.\nThis allows you to manage the tooltip's visibility based on your application's state.\nWhen using multiple triggers, you have to manage which trigger is active with the `triggerId` prop on `<Tooltip.Root>` and the `id` prop on each `<Tooltip.Trigger>`.\n\nNote that there is no separate `onTriggerIdChange` prop.\nInstead, the `onOpenChange` callback receives an additional argument, `eventDetails`, which contains the trigger element that initiated the state change.\n\nimport { DemoTooltipDetachedTriggersControlled } from './demos/detached-triggers-controlled';\n\n<DemoTooltipDetachedTriggersControlled />\n\n### Animating the Tooltip\n\nYou can animate a tooltip as it moves between different trigger elements.\nThis includes animating its position, size, and content.\n\n#### Position and Size\n\nTo animate the tooltip's position, apply CSS transitions to the `left`, `right`, `top`, and `bottom` properties of the **Positioner** part.\nTo animate its size, transition the `width` and `height` of the **Popup** part.\n\n#### Content\n\nThe tooltip also supports content transitions.\nThis is useful when different triggers display different content within the same tooltip.\n\nTo enable content animations, wrap the content in the `<Tooltip.Viewport>` part.\nThis part provides features to create direction-aware animations.\nIt renders a `div` with a `data-activation-direction` attribute (`left`, `right`, `up`, or `down`) that indicates the new trigger's position relative to the previous one.\n\nInside the `<Tooltip.Viewport>`, the content is further wrapped in `div`s with data attributes to help with styling:\n\n- `data-current`: The currently visible content when no transitions are present or the incoming content.\n- `data-previous`: The outgoing content during a transition.\n\nYou can use these attributes to style the enter and exit animations.\n\nimport { DemoTooltipDetachedTriggersFull } from './demos/detached-triggers-full';\n\n<DemoTooltipDetachedTriggersFull />\n\n## API reference\n\n<Reference component=\"Tooltip\" parts=\"Provider, Root, Trigger, Portal, Positioner, Popup, Arrow\" />\n\nexport const metadata = {\n  keywords: [\n    'React Tooltip',\n    'Tooltip Component',\n    'Detached Trigger Tooltip',\n    'Hint',\n    'Help Text',\n    'Hover Text',\n    'Hover Hint',\n    'Info Popup',\n    'Infotip',\n    'Flyout',\n    'Accessible Tooltip',\n    'Multiple Tooltip Triggers',\n    'Animated Tooltip',\n    'Headless React Components',\n    'Base UI',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/animation/demos/animated-popover-motion-keep-mounted-false/css-modules/index.module.css",
    "content": ".Trigger {\n  box-sizing: border-box;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Positioner {\n  width: var(--positioner-width);\n  height: var(--positioner-height);\n  max-width: var(--available-width);\n}\n\n.Popup {\n  box-sizing: border-box;\n  padding: 1rem 1.5rem;\n  border-radius: 0.5rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  transform-origin: var(--transform-origin);\n\n  width: var(--popup-width, auto);\n  height: var(--popup-height, auto);\n  max-width: 500px;\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/animation/demos/animated-popover-motion-keep-mounted-false/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Popover } from '@base-ui/react/popover';\nimport { AnimatePresence, motion } from 'motion/react';\nimport styles from './index.module.css';\n\nexport default function AnimatedPopoverMotionKeepMountedFalseDemo() {\n  const [open, setOpen] = React.useState(false);\n\n  return (\n    <Popover.Root open={open} onOpenChange={setOpen}>\n      <Popover.Trigger className={styles.Trigger}>Trigger</Popover.Trigger>\n      <AnimatePresence>\n        {open && (\n          <Popover.Portal keepMounted>\n            <Popover.Positioner className={styles.Positioner} sideOffset={8}>\n              <Popover.Popup\n                className={styles.Popup}\n                render={\n                  <motion.div\n                    initial={{ opacity: 0, scale: 0.8 }}\n                    animate={{ opacity: 1, scale: 1 }}\n                    exit={{ opacity: 0, scale: 0.8 }}\n                  />\n                }\n              >\n                Popup\n              </Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        )}\n      </AnimatePresence>\n    </Popover.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/animation/demos/animated-popover-motion-keep-mounted-false/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\n\nexport const DemoAnimatedPopoverMotionKeepMountedFalse = createDemoWithVariants(import.meta.url, {\n  CssModules,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/animation/demos/animated-popover-motion-keep-mounted-true/css-modules/index.module.css",
    "content": ".Trigger {\n  box-sizing: border-box;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Positioner {\n  width: var(--positioner-width);\n  height: var(--positioner-height);\n  max-width: var(--available-width);\n}\n\n.Popup {\n  box-sizing: border-box;\n  padding: 1rem 1.5rem;\n  border-radius: 0.5rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  transform-origin: var(--transform-origin);\n\n  width: var(--popup-width, auto);\n  height: var(--popup-height, auto);\n  max-width: 500px;\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/animation/demos/animated-popover-motion-keep-mounted-true/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Popover } from '@base-ui/react/popover';\nimport { motion, type HTMLMotionProps } from 'motion/react';\nimport styles from './index.module.css';\n\nexport default function AnimatedPopoverMotionKeepMountedTrueDemo() {\n  return (\n    <Popover.Root>\n      <Popover.Trigger className={styles.Trigger}>Trigger</Popover.Trigger>\n      <Popover.Portal keepMounted>\n        <Popover.Positioner className={styles.Positioner} sideOffset={8}>\n          <Popover.Popup\n            className={styles.Popup}\n            render={(props, state) => (\n              <motion.div\n                {...(props as HTMLMotionProps<'div'>)}\n                initial={false}\n                animate={{\n                  opacity: state.open ? 1 : 0,\n                  scale: state.open ? 1 : 0.8,\n                }}\n              />\n            )}\n          >\n            Popup\n          </Popover.Popup>\n        </Popover.Positioner>\n      </Popover.Portal>\n    </Popover.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/animation/demos/animated-popover-motion-keep-mounted-true/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\n\nexport const DemoAnimatedPopoverMotionKeepMountedTrue = createDemoWithVariants(import.meta.url, {\n  CssModules,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/animation/demos/animated-select-motion/css-modules/index.module.css",
    "content": ".Select {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 0.75rem;\n  height: 2.5rem;\n  padding-left: 0.875rem;\n  padding-right: 0.75rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: canvas;\n  font-family: inherit;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  -webkit-user-select: none;\n  user-select: none;\n  min-width: 9rem;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.SelectIcon {\n  display: flex;\n}\n\n.Positioner {\n  outline: none;\n  z-index: 1;\n  -webkit-user-select: none;\n  user-select: none;\n}\n\n.Popup {\n  box-sizing: border-box;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  background-clip: padding-box;\n  color: var(--color-gray-900);\n  min-width: var(--anchor-width);\n  transform-origin: var(--transform-origin);\n\n  &[data-side='none'] {\n    min-width: calc(var(--anchor-width) + 1rem);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n  }\n}\n\n.List {\n  box-sizing: border-box;\n  position: relative;\n  padding-block: 0.25rem;\n  overflow-y: auto;\n  max-height: var(--available-height);\n  scroll-padding-block: 1.5rem;\n}\n\n.Arrow {\n  display: flex;\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n\n.Item {\n  box-sizing: border-box;\n  outline: 0;\n  font-size: 0.875rem;\n  line-height: 1rem;\n  padding-block: 0.5rem;\n  padding-left: 0.625rem;\n  padding-right: 1rem;\n  display: grid;\n  gap: 0.5rem;\n  align-items: center;\n  grid-template-columns: 0.75rem 1fr;\n  cursor: default;\n  -webkit-user-select: none;\n  user-select: none;\n\n  @media (pointer: coarse) {\n    padding-block: 0.625rem;\n    font-size: 0.925rem;\n  }\n\n  [data-side='none'] & {\n    font-size: 1rem;\n    padding-right: 3rem;\n  }\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.25rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.ItemIndicator {\n  grid-column-start: 1;\n}\n\n.ItemIndicatorIcon {\n  display: block;\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.ItemText {\n  grid-column-start: 2;\n}\n\n.ScrollArrow {\n  width: 100%;\n  background: canvas;\n  z-index: 1;\n  text-align: center;\n  cursor: default;\n  border-radius: 0.375rem;\n  height: 1rem;\n  font-size: 0.75rem;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n\n  &::before {\n    content: '';\n    position: absolute;\n    width: 100%;\n    height: 100%;\n    left: 0;\n  }\n\n  &[data-direction='up'] {\n    &[data-side='none'] {\n      &::before {\n        top: -100%;\n      }\n    }\n  }\n\n  &[data-direction='down'] {\n    bottom: 0;\n\n    &[data-side='none'] {\n      &::before {\n        bottom: -100%;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/animation/demos/animated-select-motion/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Select } from '@base-ui/react/select';\nimport { AnimatePresence, motion } from 'motion/react';\nimport styles from './index.module.css';\n\nconst fonts = [\n  { label: 'Select font', value: null },\n  { label: 'Sans-serif', value: 'sans' },\n  { label: 'Serif', value: 'serif' },\n  { label: 'Monospace', value: 'mono' },\n  { label: 'Cursive', value: 'cursive' },\n];\n\nexport default function AnimatedSelectMotionDemo() {\n  const [open, setOpen] = React.useState(false);\n  const [mounted, setMounted] = React.useState(false);\n\n  const positionerRef = React.useCallback(() => {\n    setMounted(true);\n  }, []);\n\n  const portalMounted = open || mounted;\n\n  // Once the trigger has been interacted with, the popup will always be\n  // mounted in the DOM. We can use this to determine which animation variant\n  // to use: if it's already mounted, we switch to use \"keepMounted\" animations.\n  const motionElement = mounted ? (\n    <motion.div\n      initial={false}\n      animate={{\n        opacity: open ? 1 : 0,\n        scale: open ? 1 : 0.8,\n      }}\n    />\n  ) : (\n    <motion.div\n      initial={{ opacity: 0, scale: 0.8 }}\n      animate={{ opacity: 1, scale: 1 }}\n      exit={{ opacity: 0, scale: 0.8 }}\n    />\n  );\n\n  return (\n    <Select.Root items={fonts} open={open} onOpenChange={setOpen}>\n      <Select.Trigger className={styles.Select}>\n        <Select.Value />\n        <Select.Icon className={styles.SelectIcon}>\n          <ChevronUpDownIcon />\n        </Select.Icon>\n      </Select.Trigger>\n      <AnimatePresence>\n        {portalMounted && (\n          <Select.Portal>\n            <Select.Positioner className={styles.Positioner} sideOffset={8} ref={positionerRef}>\n              <Select.Popup className={styles.Popup} render={motionElement}>\n                <Select.ScrollUpArrow className={styles.ScrollArrow} />\n                <Select.List className={styles.List}>\n                  {fonts.map(({ label, value }) => (\n                    <Select.Item key={label} value={value} className={styles.Item}>\n                      <Select.ItemIndicator className={styles.ItemIndicator}>\n                        <CheckIcon className={styles.ItemIndicatorIcon} />\n                      </Select.ItemIndicator>\n                      <Select.ItemText className={styles.ItemText}>{label}</Select.ItemText>\n                    </Select.Item>\n                  ))}\n                </Select.List>\n                <Select.ScrollDownArrow className={styles.ScrollArrow} />\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        )}\n      </AnimatePresence>\n    </Select.Root>\n  );\n}\n\nfunction ChevronUpDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"8\"\n      height=\"12\"\n      viewBox=\"0 0 8 12\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"1.5\"\n      {...props}\n    >\n      <path d=\"M0.5 4.5L4 1.5L7.5 4.5\" />\n      <path d=\"M0.5 7.5L4 10.5L7.5 7.5\" />\n    </svg>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/animation/demos/animated-select-motion/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\n\nexport const DemoAnimatedSelectMotion = createDemoWithVariants(import.meta.url, {\n  CssModules,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/animation/page.mdx",
    "content": "# Animation\n\n<Subtitle>A guide to animating Base UI components.</Subtitle>\n<Meta name=\"description\" content=\"A guide to animating Base UI components.\" />\n\nBase UI components can be animated using CSS transitions, CSS animations, or JavaScript animation libraries. Each component provides a number of data attributes to target its states, as well as a few attributes specifically for animation.\n\n## CSS transitions\n\nUse the following Base UI attributes for creating transitions when a component becomes visible or hidden:\n\n- `[data-starting-style]` corresponds to the initial style to transition from.\n- `[data-ending-style]` corresponds to the final style to transition to.\n\nTransitions are recommended over CSS animations, because a transition can be smoothly cancelled midway.\nFor example, if the user closes a popup before it finishes opening, with CSS transitions it will smoothly animate to its closed state without any abrupt changes.\n\n```css title=\"popover.css\" {10-14}\n.Popup {\n  box-sizing: border-box;\n  padding: 1rem 1.5rem;\n  background-color: canvas;\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n}\n```\n\n## CSS animations\n\nUse the following Base UI attributes for creating CSS animations when a component becomes visible or hidden:\n\n- `[data-open]` corresponds to the style applied when a component becomes visible.\n- `[data-closed]` corresponds to the style applied before a component becomes hidden.\n\n```css title=\"popover.css\"\n@keyframes scaleIn {\n  from {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n  to {\n    opacity: 1;\n    transform: scale(1);\n  }\n}\n\n@keyframes scaleOut {\n  from {\n    opacity: 1;\n    transform: scale(1);\n  }\n  to {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n}\n\n.Popup[data-open] {\n  animation: scaleIn 250ms ease-out;\n}\n\n.Popup[data-closed] {\n  animation: scaleOut 250ms ease-in;\n}\n```\n\n## JavaScript animations\n\nJavaScript animation libraries such as [Motion](https://motion.dev) require control of the mounting and unmounting lifecycle of components in order for exit animations to play.\n\nBase UI relies on [`element.getAnimations()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getAnimations) to detect if animations have finished on an element. When using Motion, `opacity` animations are reflected in `element.getAnimations()`, so Base UI automatically waits for the animation finish before unmounting the component. If `opacity` isn't part of your animation (such as in a translating drawer component), you should still animate it using a value close to `1` (such as `opacity: 0.9999`), so that Base UI can detect the animation.\n\n### Animating components unmounted from DOM when closed with Motion\n\nMost popup components like Popover, Dialog, Tooltip, and Menu are unmounted from the DOM when they are closed by default. To animate them with Motion:\n\n- Make the component controlled with the `open` prop so `<AnimatePresence>` can see the state as a child\n- Specify `keepMounted` on the `<Portal>` part\n- Use the `render` prop to compose the `<Popup>` with `motion.div`\n\nimport { DemoAnimatedPopoverMotionKeepMountedFalse } from './demos/animated-popover-motion-keep-mounted-false';\n\n<DemoAnimatedPopoverMotionKeepMountedFalse compact showExtraPlaygroundLink />\n\n```jsx title=\"animated-popover.tsx\" {12-18} \"keepMounted\"\nfunction App() {\n  const [open, setOpen] = React.useState(false);\n\n  return (\n    <Popover.Root open={open} onOpenChange={setOpen}>\n      <Popover.Trigger>Trigger</Popover.Trigger>\n      <AnimatePresence>\n        {open && (\n          <Popover.Portal keepMounted>\n            <Popover.Positioner>\n              <Popover.Popup\n                render={\n                  <motion.div\n                    initial={{ opacity: 0, scale: 0.8 }}\n                    animate={{ opacity: 1, scale: 1 }}\n                    exit={{ opacity: 0, scale: 0.8 }}\n                  />\n                }\n              >\n                Popup\n              </Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        )}\n      </AnimatePresence>\n    </Popover.Root>\n  );\n}\n```\n\n### Animating components kept in DOM when closed with Motion\n\nComponents that specify `keepMounted` remain rendered in the DOM when they are closed. These elements need a different approach to be animated with Motion:\n\n- Use the `render` prop to compose the `<Popup>` with `motion.div`\n- Animate the properties based on the `open` state, avoiding `<AnimatePresence>`\n\nimport { DemoAnimatedPopoverMotionKeepMountedTrue } from './demos/animated-popover-motion-keep-mounted-true';\n\n<DemoAnimatedPopoverMotionKeepMountedTrue compact showExtraPlaygroundLink />\n\n```jsx title=\"animated-popover.tsx\" {8-17} \"keepMounted\"\nfunction App() {\n  return (\n    <Popover.Root>\n      <Popover.Trigger>Trigger</Popover.Trigger>\n      <Popover.Portal keepMounted>\n        <Popover.Positioner>\n          <Popover.Popup\n            render={(props, state) => (\n              <motion.div\n                {...(props as HTMLMotionProps<'div'>)}\n                initial={false}\n                animate={{\n                  opacity: state.open ? 1 : 0,\n                  scale: state.open ? 1 : 0.8,\n                }}\n              />\n            )}\n          >\n            Popup\n          </Popover.Popup>\n        </Popover.Positioner>\n      </Popover.Portal>\n    </Popover.Root>\n  );\n}\n```\n\n### Animating Select component with Motion\n\nThe Select component is initially unmounted but remains mounted after interaction. To animate it with Motion, a mix of the two previous approaches is needed.\n\nimport { DemoAnimatedSelectMotion } from './demos/animated-select-motion';\n\n<DemoAnimatedSelectMotion compact showExtraPlaygroundLink />\n\n### Manual unmounting\n\nFor full control, you can manually unmount the component when it's closed once animations have finished using an `actionsRef` passed to the `<Root>`:\n\n```jsx title=\"manual-unmount.tsx\" \"actionsRef\"\nfunction App() {\n  const [open, setOpen] = React.useState(false);\n  const actionsRef = React.useRef(null);\n\n  return (\n    <Popover.Root open={open} onOpenChange={setOpen} actionsRef={actionsRef}>\n      <Popover.Trigger>Trigger</Popover.Trigger>\n      <AnimatePresence>\n        {open && (\n          <Popover.Portal keepMounted>\n            <Popover.Positioner>\n              <Popover.Popup\n                render={\n                  <motion.div\n                    initial={{ scale: 0 }}\n                    animate={{ scale: 1 }}\n                    exit={{ scale: 0 }}\n                    onAnimationComplete={() => {\n                      if (!open) {\n                        actionsRef.current.unmount();\n                      }\n                    }}\n                  />\n                }\n              >\n                Popup\n              </Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        )}\n      </AnimatePresence>\n    </Popover.Root>\n  );\n}\n```\n\nexport const metadata = {\n  keywords: [\n    'Base UI Animation',\n    'React Component Animations',\n    'CSS Transition Guide',\n    'Motion Framer Integration',\n    'Animation Data Attributes',\n    'Handbook Animation',\n    'Spring Animations',\n    'Enter Exit Animations',\n    'Transition Hooks',\n    'Animated Components',\n    'Keyframe Animations',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/composition/page.mdx",
    "content": "# Composition\n\n<Subtitle>A guide to composing Base UI components with your own React components.</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"A guide to composing Base UI components with your own React components.\"\n/>\n\n## Composing custom React components\n\nUse the `render` prop to compose a Base UI part with your own React components.\n\nFor example, most triggers render a `<button>` by default.\nThe code snippet below shows how to use a custom button instead.\n\n```tsx title=\"index.tsx\"\n// prettier-ignore\n<Menu.Trigger render={<MyButton size=\"md\" />}>\n  Open menu\n</Menu.Trigger>\n```\n\nThe custom component must forward the `ref`, and spread all the received props on its underlying DOM node.\n\n## Composing multiple components\n\nIn situations where you need to compose multiple Base UI components with custom React components, `render` props can be nested as deeply as necessary.\nWorking with Tooltip is a common example.\n\n```tsx title=\"index.tsx\"\n<Dialog.Root>\n  <Tooltip.Root>\n    <Tooltip.Trigger\n      render={\n        <Dialog.Trigger\n          // prettier-ignore\n          render={\n            <Menu.Trigger render={<MyButton size=\"md\" />}>\n              Open menu\n            </Menu.Trigger>\n          }\n        />\n      }\n    />\n    <Tooltip.Portal>...</Tooltip.Portal>\n  </Tooltip.Root>\n  <Dialog.Portal>...</Dialog.Portal>\n</Dialog.Root>\n```\n\n## Changing the default rendered element\n\nYou can also use the `render` prop to override the rendered element of the component.\n\nFor example, `<Menu.Item>` renders a `<div>` by default.\nThe code snippet below shows how to render it as an `<a>` element so that it works like a link.\n\n```tsx title=\"index.tsx\"\nimport { Menu } from '@base-ui/react/menu';\n\nexport default () => (\n  <Menu.Root>\n    <Menu.Trigger>Song</Menu.Trigger>\n    <Menu.Portal>\n      <Menu.Positioner>\n        <Menu.Popup>\n          {/* prettier-ignore */}\n          <Menu.Item render={<a href=\"base-ui.com\" />}>\n            Add to Library\n          </Menu.Item>\n        </Menu.Popup>\n      </Menu.Positioner>\n    </Menu.Portal>\n  </Menu.Root>\n);\n```\n\nEach Base UI component renders the most appropriate element by default, and in most cases, rendering a different element is recommended only on a case-by-case basis.\n\n## Render function\n\nIf you are working in an extremely performance-sensitive application, you might want to pass a function to the `render` prop instead of a React element.\n\n```tsx title=\"switch.tsx\"\n<Switch.Thumb\n  render={(props, state) =>\n    // prettier-ignore\n    <span {...props}>\n      {state.checked ? <CheckedIcon /> : <UncheckedIcon />}\n    </span>\n  }\n/>\n```\n\nUsing a function gives you complete control over spreading props and also allows you to render different content based on the component's state.\n\nexport const metadata = {\n  keywords: [\n    'Base UI Composition',\n    'React Render Prop',\n    'Compose Headless Components',\n    'Custom Trigger Render',\n    'Nested Render Props',\n    'Composition Handbook',\n    'Component Composition',\n    'Render Props Pattern',\n    'Compound Components',\n    'Polymorphic Components',\n    'As Prop',\n    'Custom Elements',\n    'Wrapper Components',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/customization/page.mdx",
    "content": "# Customization\n\n<Subtitle>A guide to customizing the behavior of Base UI components.</Subtitle>\n<Meta name=\"description\" content=\"A guide to customizing the behavior of Base UI components.\" />\n\n## Base UI events\n\nChange events such as `onOpenChange`, `onValueChange`, and `onPressedChange` are custom to Base UI.\nThey can be emitted by various different DOM events, effects, or even during rendering.\n\n```js title=\"Base UI event signatures\"\nonOpenChange: (open, eventDetails) => void\nonValueChange: (value, eventDetails) => void\nonPressedChange: (pressed, eventDetails) => void\n```\n\nThe `eventDetails` property is passed as a second argument to Base UI event handlers.\nThis enables the event to be customized, and also allows you to conditionally run side effects based on the reason or DOM event that caused the change.\n\n```tsx title=\"eventDetails object\"\ninterface BaseUIChangeEventDetails {\n  reason: string;\n  event: Event;\n  cancel: () => void;\n  allowPropagation: () => void;\n  isCanceled: boolean;\n  isPropagationAllowed: boolean;\n}\n```\n\n- `reason` is used to determine why the change event occurred, which can be useful to conditionally run certain side effects.\n  Most IDEs show the possible string values after typing `reason === '`.\n- `event` is the native DOM event that caused the change.\n- `cancel` stops the component from changing its internal state.\n- `allowPropagation` allows the DOM event to propagate in cases where Base UI stops the propagation.\n- `isCanceled` indicates whether the change event has been canceled.\n- `isPropagationAllowed` indicates whether the DOM event is allowed to propagate.\n\n### Canceling a Base UI event\n\nAn event can be canceled with the `cancel()` method on `eventDetails`:\n\n```tsx title=\"Prevent a tooltip from closing when pressing the trigger\" \"cancel\"\n<Tooltip.Root\n  onOpenChange={(open, eventDetails) => {\n    if (eventDetails.reason === 'trigger-press') {\n      // Stop the open change (false) from happening.\n      eventDetails.cancel();\n    }\n  }}\n>\n  ...\n</Tooltip.Root>\n```\n\nThis lets you leave the component uncontrolled as its internal state is prevented from updating.\nThis is an alternative to controlling the component with external state and guarding the state updates conditionally.\n\n### Allowing propagation of the DOM event\n\nIn most components, pressing the <kbd>Esc</kbd> key stops the propagation of the event so parent popups don't close simultaneously.\nThis can also be customized with the `allowPropagation()` method:\n\n```tsx title=\"Allowing propagation of the event\" \"allowPropagation\"\n<Tooltip.Root\n  onOpenChange={(open, eventDetails) => {\n    if (eventDetails.reason === 'escape-key') {\n      // Allow the DOM event to propagate.\n      eventDetails.allowPropagation();\n    }\n  }}\n>\n  ...\n</Tooltip.Root>\n```\n\n## Preventing Base UI from handling a React event\n\nTo prevent Base UI from handling a React event like `onClick`, you can use the `preventBaseUIHandler()` method on the event object:\n\n```tsx title=\"Prevent Base UI's default behavior\"\n<NumberField.Input\n  onPaste={(event) => {\n    event.preventBaseUIHandler();\n  }}\n/>\n```\n\nThis should be used as an escape hatch in cases where there isn't a prop yet to customize the behavior.\nIn various cases, native events are used instead of React events, so this method has no effect.\n\n## Controlling components with state\n\nChange event handlers enable a component to be controlled with your own external state.\n\nComponents are uncontrolled by default, meaning that they manage their own state internally.\n\n```tsx title=\"Uncontrolled dialog\"\n<Dialog.Root>\n  <Dialog.Trigger /> {/* Opens the dialog when clicked. */}\n</Dialog.Root>\n```\n\nA component can be made controlled by passing external state to a prop, such as `open` or `value`, and the state's setter to its corresponding change handler, such as `onOpenChange` or `onValueChange`.\n\nFor instance, you can open [Dialog](/react/components/dialog) after a timeout, without a trigger:\n\n```tsx title=\"Controlled dialog\"\nconst [open, setOpen] = React.useState(false);\n\nReact.useEffect(() => {\n  const timeout = setTimeout(() => {\n    setOpen(true);\n  }, 1000);\n  return () => clearTimeout(timeout);\n}, []);\n\nreturn (\n  <Dialog.Root open={open} onOpenChange={setOpen}>\n    No trigger is needed in this case.\n  </Dialog.Root>\n);\n```\n\nThis also allows you to read the state of the component outside of the root component.\n\nexport const metadata = {\n  keywords: [\n    'Base UI Customization',\n    'React eventDetails',\n    'Control Base UI State',\n    'Cancel Base UI Events',\n    'Allow Propagation Tooltip',\n    'Customization Handbook',\n    'Event Handlers',\n    'Controlled Components',\n    'Uncontrolled Components',\n    'Component Behavior',\n    'Event Callbacks',\n    'State Management',\n    'Component Props',\n    'Override Behavior',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/forms/demos/components/autocomplete.tsx",
    "content": "import * as React from 'react';\nimport clsx from 'clsx';\nimport { Autocomplete } from '@base-ui/react/autocomplete';\n\nexport function Root(props: Autocomplete.Root.Props<any>) {\n  return <Autocomplete.Root {...props} />;\n}\n\nexport const Input = React.forwardRef<HTMLInputElement, Autocomplete.Input.Props>(function Input(\n  { className, ...props }: Autocomplete.Input.Props,\n  forwardedRef: React.ForwardedRef<HTMLInputElement>,\n) {\n  return (\n    <Autocomplete.Input\n      ref={forwardedRef}\n      className={clsx(\n        'bg-[canvas] h-10 w-[16rem] md:w-[20rem] font-normal rounded-md border border-gray-200 pl-3.5 text-base text-gray-900 focus:outline-2 focus:-outline-offset-1 focus:outline-blue-800',\n        className,\n      )}\n      {...props}\n    />\n  );\n});\n\nexport function Portal(props: Autocomplete.Portal.Props) {\n  return <Autocomplete.Portal {...props} />;\n}\n\nexport function Positioner({ className, ...props }: Autocomplete.Positioner.Props) {\n  return (\n    <Autocomplete.Positioner\n      className={clsx('outline-none data-[empty]:hidden', className)}\n      sideOffset={4}\n      {...props}\n    />\n  );\n}\n\nexport function Popup({ className, ...props }: Autocomplete.Popup.Props) {\n  return (\n    <Autocomplete.Popup\n      className={clsx(\n        'w-[var(--anchor-width)] max-h-[min(var(--available-height),23rem)] max-w-[var(--available-width)] overflow-y-auto scroll-pt-2 scroll-pb-2 overscroll-contain rounded-md bg-[canvas] py-2 text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function List(props: Autocomplete.List.Props) {\n  return <Autocomplete.List {...props} />;\n}\n\nexport function Item({ className, ...props }: Autocomplete.Item.Props) {\n  return (\n    <Autocomplete.Item\n      className={clsx(\n        'flex flex-col gap-0.25 cursor-default py-2 pr-8 pl-4 text-base leading-4 outline-none select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-2 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded data-[highlighted]:before:bg-gray-900',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/forms/demos/components/button.tsx",
    "content": "import * as React from 'react';\nimport { Button as BaseButton } from '@base-ui/react/button';\nimport clsx from 'clsx';\n\nexport function Button({ className, ...props }: React.ComponentPropsWithoutRef<'button'>) {\n  return (\n    <BaseButton\n      type=\"button\"\n      className={clsx(\n        'flex items-center justify-center h-10 px-3.5 m-0 outline-0 border border-gray-200 rounded-md bg-gray-50 font-inherit text-base font-normal leading-6 text-gray-900 select-none hover:data-[disabled]:bg-gray-50 hover:bg-gray-100 active:data-[disabled]:bg-gray-50 active:bg-gray-200 active:shadow-[inset_0_1px_3px_rgba(0,0,0,0.1)] active:border-t-gray-300 active:data-[disabled]:shadow-none active:data-[disabled]:border-t-gray-200 focus-visible:outline-2 focus-visible:outline-blue-800 focus-visible:-outline-offset-1 data-[disabled]:text-gray-500',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/forms/demos/components/checkbox-group.tsx",
    "content": "import * as React from 'react';\nimport clsx from 'clsx';\nimport { CheckboxGroup as BaseCheckboxGroup } from '@base-ui/react/checkbox-group';\n\nexport function CheckboxGroup({ className, ...props }: BaseCheckboxGroup.Props) {\n  return (\n    <BaseCheckboxGroup\n      className={clsx('flex flex-col items-start gap-1 text-gray-900', className)}\n      {...props}\n    />\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/forms/demos/components/checkbox.tsx",
    "content": "import * as React from 'react';\nimport clsx from 'clsx';\nimport { Checkbox } from '@base-ui/react/checkbox';\n\nexport function Root({ className, ...props }: Checkbox.Root.Props) {\n  return (\n    <Checkbox.Root\n      className={clsx(\n        'flex size-5 items-center justify-center rounded-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-800 data-[checked]:bg-gray-900 data-[unchecked]:border data-[unchecked]:border-gray-300',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function Indicator({ className, ...props }: Checkbox.Indicator.Props) {\n  return (\n    <Checkbox.Indicator\n      className={clsx('flex text-gray-50 data-[unchecked]:hidden', className)}\n      {...props}\n    />\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/forms/demos/components/combobox.tsx",
    "content": "import * as React from 'react';\nimport clsx from 'clsx';\nimport { Combobox } from '@base-ui/react/combobox';\nimport { X } from 'lucide-react';\n\nexport function Root(props: Combobox.Root.Props<any, any>) {\n  return <Combobox.Root {...props} />;\n}\n\nexport const Input = React.forwardRef<HTMLInputElement, Combobox.Input.Props>(function Input(\n  { className, ...props }: Combobox.Input.Props,\n  forwardedRef: React.ForwardedRef<HTMLInputElement>,\n) {\n  return (\n    <Combobox.Input\n      ref={forwardedRef}\n      className={clsx(\n        'h-10 w-64 rounded-md font-normal border border-gray-200 pl-3.5 text-base text-gray-900 bg-[canvas] focus:outline-2 focus:-outline-offset-1 focus:outline-blue-800',\n        className,\n      )}\n      {...props}\n    />\n  );\n});\n\nexport function Clear({ className, ...props }: Combobox.Clear.Props) {\n  return (\n    <Combobox.Clear\n      className={clsx(\n        'combobox-clear flex h-10 w-6 items-center justify-center rounded bg-transparent p-0',\n        className,\n      )}\n      {...props}\n    >\n      <X className=\"size-4\" />\n    </Combobox.Clear>\n  );\n}\n\nexport function Trigger({ className, ...props }: Combobox.Trigger.Props) {\n  return (\n    <Combobox.Trigger\n      className={clsx(\n        'flex h-10 w-6 items-center justify-center rounded bg-transparent p-0',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function Portal(props: Combobox.Portal.Props) {\n  return <Combobox.Portal {...props} />;\n}\n\nexport function Positioner({ className, ...props }: Combobox.Positioner.Props) {\n  return (\n    <Combobox.Positioner className={clsx('outline-none', className)} sideOffset={4} {...props} />\n  );\n}\n\nexport function Popup({ className, ...props }: Combobox.Popup.Props) {\n  return (\n    <Combobox.Popup\n      className={clsx(\n        'w-[var(--anchor-width)] max-h-[23rem] max-w-[var(--available-width)] origin-[var(--transform-origin)] rounded-md bg-[canvas] text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 transition-[transform,scale,opacity] data-[ending-style]:scale-95 data-[ending-style]:opacity-0 data-[starting-style]:scale-95 data-[starting-style]:opacity-0 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300 duration-100',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function Empty({ className, ...props }: Combobox.Empty.Props) {\n  return (\n    <Combobox.Empty\n      className={clsx('p-4 text-[0.925rem] leading-4 text-gray-600 empty:m-0 empty:p-0', className)}\n      {...props}\n    />\n  );\n}\n\nexport function List({ className, ...props }: Combobox.List.Props) {\n  return (\n    <Combobox.List\n      className={clsx(\n        'outline-0 overflow-y-auto scroll-py-[0.5rem] py-2 overscroll-contain max-h-[min(23rem,var(--available-height))] data-[empty]:p-0',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function Item({ className, ...props }: Combobox.Item.Props) {\n  return (\n    <Combobox.Item\n      className={clsx(\n        'grid cursor-default grid-cols-[0.75rem_1fr] items-center gap-2 py-2 pr-8 pl-4 text-base leading-4 outline-none select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-2 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-sm data-[highlighted]:before:bg-gray-900',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function ItemIndicator({ className, ...props }: Combobox.ItemIndicator.Props) {\n  return <Combobox.ItemIndicator className={clsx('col-start-1', className)} {...props} />;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/forms/demos/components/field.tsx",
    "content": "import * as React from 'react';\nimport clsx from 'clsx';\nimport { Field } from '@base-ui/react/field';\n\nexport function Root({ className, ...props }: Field.Root.Props) {\n  return <Field.Root className={clsx('flex flex-col items-start gap-1', className)} {...props} />;\n}\n\nexport function Label({ className, ...props }: Field.Label.Props) {\n  return (\n    <Field.Label\n      className={clsx(\n        'text-sm font-bold text-gray-900 has-[[role=\"checkbox\"]]:flex has-[[role=\"checkbox\"]]:items-center has-[[role=\"checkbox\"]]:gap-2 has-[[role=\"checkbox\"]]:font-normal has-[[role=\"radio\"]]:flex has-[[role=\"radio\"]]:items-center has-[[role=\"radio\"]]:gap-2 has-[[role=\"radio\"]]:font-normal has-[[role=\"switch\"]]:flex has-[[role=\"switch\"]]:items-center',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function Description({ className, ...props }: Field.Description.Props) {\n  return <Field.Description className={clsx('text-sm text-gray-600', className)} {...props} />;\n}\n\nexport const Control = React.forwardRef<HTMLInputElement, Field.Control.Props>(\n  function FieldControl(\n    { className, ...props }: Field.Control.Props,\n    forwardedRef: React.ForwardedRef<HTMLInputElement>,\n  ) {\n    return (\n      <Field.Control\n        ref={forwardedRef}\n        className={clsx(\n          'h-10 w-full max-w-xs rounded-md bg-[canvas] border border-gray-200 pl-3.5 text-base text-gray-900 focus:outline-2 focus:-outline-offset-1 focus:outline-blue-800',\n          className,\n        )}\n        {...props}\n      />\n    );\n  },\n);\n\nexport function Error({ className, ...props }: Field.Error.Props) {\n  return <Field.Error className={clsx('text-sm text-red-800', className)} {...props} />;\n}\n\nexport function Item(props: Field.Item.Props) {\n  return <Field.Item {...props} />;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/forms/demos/components/fieldset.tsx",
    "content": "import * as React from 'react';\nimport clsx from 'clsx';\nimport { Fieldset } from '@base-ui/react/fieldset';\n\nexport function Root(props: Fieldset.Root.Props) {\n  return <Fieldset.Root {...props} />;\n}\n\nexport function Legend({ className, ...props }: Fieldset.Legend.Props) {\n  return (\n    <Fieldset.Legend className={clsx('text-sm font-bold text-gray-900', className)} {...props} />\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/forms/demos/components/form.tsx",
    "content": "import * as React from 'react';\nimport clsx from 'clsx';\nimport { Form as BaseForm } from '@base-ui/react/form';\n\nexport function Form({ className, ...props }: BaseForm.Props) {\n  return (\n    <BaseForm\n      className={clsx('flex w-full max-w-3xs sm:max-w-[20rem] flex-col gap-5', className)}\n      {...props}\n    />\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/forms/demos/components/number-field.tsx",
    "content": "import * as React from 'react';\nimport clsx from 'clsx';\nimport { NumberField } from '@base-ui/react/number-field';\n\nexport function Root({ className, ...props }: NumberField.Root.Props) {\n  return (\n    <NumberField.Root className={clsx('flex flex-col items-start gap-1', className)} {...props} />\n  );\n}\n\nexport function Group({ className, ...props }: NumberField.Group.Props) {\n  return <NumberField.Group className={clsx('flex', className)} {...props} />;\n}\n\nexport function Decrement({ className, ...props }: NumberField.Decrement.Props) {\n  return (\n    <NumberField.Decrement\n      className={clsx(\n        'flex size-10 items-center justify-center rounded-tl-md rounded-bl-md border border-gray-200 bg-gray-50 bg-clip-padding text-gray-900 select-none hover:bg-gray-100 active:bg-gray-100',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport const Input = React.forwardRef<HTMLInputElement, NumberField.Input.Props>(function Input(\n  { className, ...props }: NumberField.Input.Props,\n  forwardedRef: React.ForwardedRef<HTMLInputElement>,\n) {\n  return (\n    <NumberField.Input\n      ref={forwardedRef}\n      className={clsx(\n        'h-10 w-24 border-t border-b border-gray-200 text-center text-base text-gray-900 tabular-nums focus:z-1 focus:outline-2 focus:-outline-offset-1 focus:outline-blue-800',\n        className,\n      )}\n      {...props}\n    />\n  );\n});\n\nexport function Increment({ className, ...props }: NumberField.Increment.Props) {\n  return (\n    <NumberField.Increment\n      className={clsx(\n        'flex size-10 items-center justify-center rounded-tr-md rounded-br-md border border-gray-200 bg-gray-50 bg-clip-padding text-gray-900 select-none hover:bg-gray-100 active:bg-gray-100',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/forms/demos/components/radio-group.tsx",
    "content": "import * as React from 'react';\nimport clsx from 'clsx';\nimport { RadioGroup as BaseRadioGroup } from '@base-ui/react/radio-group';\n\nexport function RadioGroup<Value>({ className, ...props }: BaseRadioGroup.Props<Value>) {\n  return (\n    <BaseRadioGroup\n      className={clsx('w-full flex flex-row items-start gap-1 text-gray-900', className)}\n      {...props}\n    />\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/forms/demos/components/radio.tsx",
    "content": "import * as React from 'react';\nimport clsx from 'clsx';\nimport { Radio } from '@base-ui/react/radio';\n\nexport function Root({ className, ...props }: Radio.Root.Props) {\n  return (\n    <Radio.Root\n      className={clsx(\n        'flex size-5 items-center justify-center rounded-full focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-800 data-[checked]:bg-gray-900 data-[unchecked]:border data-[unchecked]:border-gray-300',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function Indicator({ className, ...props }: Radio.Indicator.Props) {\n  return (\n    <Radio.Indicator\n      className={clsx(\n        'flex before:size-2 before:rounded-full before:bg-gray-50 data-[unchecked]:hidden',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/forms/demos/components/select.tsx",
    "content": "import * as React from 'react';\nimport clsx from 'clsx';\nimport { Select } from '@base-ui/react/select';\n\nexport function Root(props: Select.Root.Props<any>) {\n  return <Select.Root {...props} />;\n}\n\nexport function Label({ className, ...props }: Select.Label.Props) {\n  return <Select.Label className={clsx('text-sm font-bold text-gray-900', className)} {...props} />;\n}\n\nexport function Trigger({ className, ...props }: Select.Trigger.Props) {\n  return (\n    <Select.Trigger\n      className={clsx(\n        'flex h-10 min-w-36 items-center justify-between gap-3 rounded-md border border-gray-200 pr-3 pl-3.5 text-base text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 data-[popup-open]:bg-gray-100 cursor-default not-[[data-filled]]:text-gray-500 bg-[canvas]',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function Value({ className, ...props }: Select.Value.Props) {\n  return <Select.Value className={clsx('', className)} {...props} />;\n}\n\nexport function Icon({ className, ...props }: Select.Icon.Props) {\n  return <Select.Icon className={clsx('flex', className)} {...props} />;\n}\n\nexport function Portal(props: Select.Portal.Props) {\n  return <Select.Portal {...props} />;\n}\n\nexport function Positioner({ className, ...props }: Select.Positioner.Props) {\n  return (\n    <Select.Positioner\n      className={clsx('outline-none select-none z-10', className)}\n      sideOffset={8}\n      {...props}\n    />\n  );\n}\n\nexport function Popup({ className, ...props }: Select.Popup.Props) {\n  return (\n    <Select.Popup\n      className={clsx(\n        'group origin-[var(--transform-origin)] bg-clip-padding rounded-md bg-[canvas] text-gray-900 shadow-lg shadow-gray-200 outline outline-gray-200 transition-[transform,scale,opacity] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[side=none]:data-[ending-style]:transition-none data-[starting-style]:scale-90 data-[starting-style]:opacity-0 data-[side=none]:data-[starting-style]:scale-100 data-[side=none]:data-[starting-style]:opacity-100 data-[side=none]:data-[starting-style]:transition-none dark:shadow-none dark:outline-gray-300',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function ScrollUpArrow({ className, ...props }: Select.ScrollUpArrow.Props) {\n  return (\n    <Select.ScrollUpArrow\n      className={clsx(\n        \"top-0 z-[1] flex h-4 w-full cursor-default items-center justify-center rounded-md bg-[canvas] text-center text-xs before:absolute data-[side=none]:before:top-[-100%] before:left-0 before:h-full before:w-full before:content-['']\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function ScrollDownArrow({ className, ...props }: Select.ScrollDownArrow.Props) {\n  return (\n    <Select.ScrollDownArrow\n      className={clsx(\n        \"bottom-0 z-[1] flex h-4 w-full cursor-default items-center justify-center rounded-md bg-[canvas] text-center text-xs before:absolute before:left-0 before:h-full before:w-full before:content-[''] data-[side=none]:before:bottom-[-100%]\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function List({ className, ...props }: Select.List.Props) {\n  return (\n    <Select.List\n      className={clsx(\n        'relative py-1 scroll-py-6 overflow-y-auto max-h-[var(--available-height)]',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function Item({ className, ...props }: Select.Item.Props) {\n  return (\n    <Select.Item\n      className={clsx(\n        'grid min-w-[var(--anchor-width)] cursor-default grid-cols-[0.75rem_1fr] items-center gap-3 py-2 pr-4 pl-2.5 text-sm leading-4 outline-none select-none group-data-[side=none]:min-w-[calc(var(--anchor-width)+1rem)] group-data-[side=none]:pr-12 group-data-[side=none]:text-base group-data-[side=none]:leading-4 data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-sm data-[highlighted]:before:bg-gray-900 pointer-coarse:py-2.5 pointer-coarse:text-[0.925rem]',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function ItemIndicator({ className, ...props }: Select.ItemIndicator.Props) {\n  return <Select.ItemIndicator className={clsx('col-start-1', className)} {...props} />;\n}\n\nexport function ItemText({ className, ...props }: Select.ItemText.Props) {\n  return <Select.ItemText className={clsx('col-start-2', className)} {...props} />;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/forms/demos/components/slider.tsx",
    "content": "import * as React from 'react';\nimport clsx from 'clsx';\nimport { Slider } from '@base-ui/react/slider';\n\nexport function Root({ className, ...props }: Slider.Root.Props<any>) {\n  return <Slider.Root className={clsx('grid grid-cols-2', className)} {...props} />;\n}\n\nexport function Value({ className, ...props }: Slider.Value.Props) {\n  return (\n    <Slider.Value className={clsx('text-sm font-normal text-gray-900', className)} {...props} />\n  );\n}\n\nexport function Control({ className, ...props }: Slider.Control.Props) {\n  return (\n    <Slider.Control\n      className={clsx('flex col-span-2 touch-none items-center py-3 select-none', className)}\n      {...props}\n    />\n  );\n}\n\nexport function Track({ className, ...props }: Slider.Track.Props) {\n  return (\n    <Slider.Track\n      className={clsx(\n        'h-1 w-full rounded bg-gray-200 shadow-[inset_0_0_0_1px] shadow-gray-200 select-none',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function Indicator({ className, ...props }: Slider.Indicator.Props) {\n  return (\n    <Slider.Indicator className={clsx('rounded bg-gray-700 select-none', className)} {...props} />\n  );\n}\n\nexport function Thumb({ className, ...props }: Slider.Thumb.Props) {\n  return (\n    <Slider.Thumb\n      className={clsx(\n        'size-4 rounded-full bg-white outline outline-gray-300 select-none has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-blue-800',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/forms/demos/components/switch.tsx",
    "content": "import * as React from 'react';\nimport clsx from 'clsx';\nimport { Switch } from '@base-ui/react/switch';\n\nexport function Root({ className, ...props }: Switch.Root.Props) {\n  return (\n    <Switch.Root\n      className={clsx(\n        'relative flex h-6 w-10 rounded-full bg-gradient-to-r from-gray-700 from-35% to-gray-200 to-65% bg-[length:6.5rem_100%] bg-[100%_0%] bg-no-repeat p-px shadow-[inset_0_1.5px_2px] shadow-gray-200 outline-1 -outline-offset-1 outline-gray-200 transition-[background-position,box-shadow] duration-[125ms] ease-[cubic-bezier(0.26,0.75,0.38,0.45)] before:absolute before:rounded-full before:outline-offset-2 before:outline-blue-800 focus-visible:before:inset-0 focus-visible:before:outline focus-visible:before:outline-2 active:bg-gray-100 data-[checked]:bg-[0%_0%] data-[checked]:active:bg-gray-500 dark:from-gray-500 dark:shadow-black/75 dark:outline-white/15 dark:data-[checked]:shadow-none',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function Thumb({ className, ...props }: Switch.Thumb.Props) {\n  return (\n    <Switch.Thumb\n      className={clsx(\n        'aspect-square h-full rounded-full bg-white shadow-[0_0_1px_1px,0_1px_1px,1px_2px_4px_-1px] shadow-gray-100 transition-transform duration-150 data-[checked]:translate-x-4 dark:shadow-black/25',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/forms/demos/components/toast.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Toast } from '@base-ui/react/toast';\nimport { X } from 'lucide-react';\n\nfunction Toasts() {\n  const { toasts } = Toast.useToastManager();\n  return toasts.map((toast) => (\n    <Toast.Root\n      key={toast.id}\n      toast={toast}\n      className=\"[--gap:0.75rem] [--peek:0.75rem] [--scale:calc(max(0,1-(var(--toast-index)*0.1)))] [--shrink:calc(1-var(--scale))] [--height:var(--toast-frontmost-height,var(--toast-height))] [--offset-y:calc(var(--toast-offset-y)*-1+calc(var(--toast-index)*var(--gap)*-1)+var(--toast-swipe-movement-y))] absolute right-0 bottom-0 left-auto z-[calc(1000-var(--toast-index))] mr-0 w-full origin-bottom [transform:translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)-(var(--toast-index)*var(--peek))-(var(--shrink)*var(--height))))_scale(var(--scale))] rounded-lg border border-gray-200 bg-gray-50 bg-clip-padding p-4 shadow-lg select-none after:absolute after:top-full after:left-0 after:h-[calc(var(--gap)+1px)] after:w-full after:content-[''] data-[ending-style]:opacity-0 data-[limited]:opacity-0 data-[starting-style]:[transform:translateY(150%)] [&[data-ending-style]:not([data-limited]):not([data-swipe-direction])]:[transform:translateY(150%)] data-[ending-style]:data-[swipe-direction=down]:[transform:translateY(calc(var(--toast-swipe-movement-y)+150%))] data-[ending-style]:data-[swipe-direction=left]:[transform:translateX(calc(var(--toast-swipe-movement-x)-150%))_translateY(var(--offset-y))] data-[ending-style]:data-[swipe-direction=right]:[transform:translateX(calc(var(--toast-swipe-movement-x)+150%))_translateY(var(--offset-y))] data-[ending-style]:data-[swipe-direction=up]:[transform:translateY(calc(var(--toast-swipe-movement-y)-150%))] h-[var(--height)] [transition:transform_0.5s_cubic-bezier(0.22,1,0.36,1),opacity_0.5s,height_0.15s]\"\n    >\n      <Toast.Content className=\"overflow-hidden transition-opacity [transition-duration:250ms]\">\n        <Toast.Title className=\"text-[0.975rem] leading-5 font-bold\" />\n        <Toast.Description className=\"text-[0.925rem] leading-5 text-gray-700\" />\n        <div\n          className=\"text-xs mt-2 p-3 py-2 bg-gray-100 text-gray-900 rounded-md select-text\"\n          data-base-ui-swipe-ignore\n        >\n          <pre className=\"whitespace-pre-wrap\">{JSON.stringify(toast.data, null, 2)}</pre>\n        </div>\n        <Toast.Close\n          className=\"absolute top-2 right-2 flex h-5 w-5 items-center justify-center rounded border-none bg-transparent text-gray-500 hover:bg-gray-100 hover:text-gray-700\"\n          aria-label=\"Close\"\n        >\n          <X className=\"size-4\" />\n        </Toast.Close>\n      </Toast.Content>\n    </Toast.Root>\n  ));\n}\n\nexport function ToastProvider(props: { children: React.ReactNode }) {\n  return (\n    <Toast.Provider limit={1}>\n      {props.children}\n      <Toast.Portal>\n        <Toast.Viewport className=\"fixed z-10 top-auto right-[1rem] bottom-[1rem] mx-auto flex w-[250px] sm:right-[2rem] sm:bottom-[2rem] sm:w-[360px]\">\n          <Toasts />\n        </Toast.Viewport>\n      </Toast.Portal>\n    </Toast.Provider>\n  );\n}\n\nexport const useToastManager = Toast.useToastManager;\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/forms/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport Tailwind from './tailwind';\n\nexport const DemoBaseUIForm = createDemoWithVariants(import.meta.url, { Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/forms/demos/hero/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { ChevronDown, ChevronsUpDown, Check, Plus, Minus } from 'lucide-react';\nimport { Button } from '../../components/button';\nimport { CheckboxGroup } from '../../components/checkbox-group';\nimport { Form } from '../../components/form';\nimport { RadioGroup } from '../../components/radio-group';\nimport { ToastProvider, useToastManager } from '../../components/toast';\nimport * as Autocomplete from '../../components/autocomplete';\nimport * as Checkbox from '../../components/checkbox';\nimport * as Combobox from '../../components/combobox';\nimport * as Field from '../../components/field';\nimport * as Fieldset from '../../components/fieldset';\nimport * as NumberField from '../../components/number-field';\nimport * as Radio from '../../components/radio';\nimport * as Select from '../../components/select';\nimport * as Slider from '../../components/slider';\nimport * as Switch from '../../components/switch';\n\nfunction ExampleForm() {\n  const toastManager = useToastManager();\n  return (\n    <Form\n      aria-label=\"Launch new cloud server\"\n      onFormSubmit={(formValues) => {\n        toastManager.add({\n          title: 'Form submitted',\n          description: 'The form contains these values:',\n          data: formValues,\n        });\n      }}\n    >\n      <Field.Root name=\"serverName\">\n        <Field.Label>Server name</Field.Label>\n        <Field.Control\n          defaultValue=\"\"\n          placeholder=\"e.g. api-server-01\"\n          required\n          minLength={3}\n          pattern=\".*[A-Za-z].*\"\n        />\n        <Field.Description>Must be 3 or more characters long</Field.Description>\n        <Field.Error />\n      </Field.Root>\n\n      <Field.Root name=\"region\">\n        <Combobox.Root items={REGIONS} required>\n          <div className=\"relative flex flex-col gap-1 text-sm leading-5 text-gray-900\">\n            <Field.Label>Region</Field.Label>\n            <Combobox.Input placeholder=\"e.g. eu-central-1\" />\n            <div className=\"absolute right-2 bottom-0 flex h-10 items-center justify-center text-gray-600\">\n              <Combobox.Clear />\n              <Combobox.Trigger>\n                <ChevronDown className=\"size-4\" />\n              </Combobox.Trigger>\n            </div>\n          </div>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.Empty>No matches</Combobox.Empty>\n                <Combobox.List>\n                  {(region: string) => {\n                    return (\n                      <Combobox.Item key={region} value={region}>\n                        <Combobox.ItemIndicator>\n                          <Check className=\"size-4\" />\n                        </Combobox.ItemIndicator>\n                        <div className=\"col-start-2\">{region}</div>\n                      </Combobox.Item>\n                    );\n                  }}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>\n        <Field.Error />\n      </Field.Root>\n\n      <Field.Root name=\"containerImage\">\n        <Autocomplete.Root\n          items={IMAGES}\n          mode=\"both\"\n          itemToStringValue={(itemValue: Image) => itemValue.url}\n          required\n        >\n          <Field.Label>Container image</Field.Label>\n          <Autocomplete.Input placeholder=\"e.g. docker.io/library/node:latest\" />\n          <Field.Description>Enter a registry URL with optional tags</Field.Description>\n          <Autocomplete.Portal>\n            <Autocomplete.Positioner>\n              <Autocomplete.Popup>\n                <Autocomplete.List>\n                  {(image: Image) => {\n                    return (\n                      <Autocomplete.Item key={image.url} value={image}>\n                        <span className=\"text-base leading-6\">{image.name}</span>\n                        <span className=\"font-mono whitespace-nowrap text-xs leading-4 opacity-80\">\n                          {image.url}\n                        </span>\n                      </Autocomplete.Item>\n                    );\n                  }}\n                </Autocomplete.List>\n              </Autocomplete.Popup>\n            </Autocomplete.Positioner>\n          </Autocomplete.Portal>\n        </Autocomplete.Root>\n        <Field.Error />\n      </Field.Root>\n\n      <Field.Root name=\"serverType\">\n        <Select.Root items={SERVER_TYPES} required>\n          <div className=\"flex flex-col items-start gap-1\">\n            <Select.Label>Server type</Select.Label>\n            <Select.Trigger className=\"w-48\">\n              <Select.Value />\n              <Select.Icon>\n                <ChevronsUpDown className=\"size-4\" />\n              </Select.Icon>\n            </Select.Trigger>\n          </div>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.ScrollUpArrow />\n                <Select.List>\n                  {SERVER_TYPES.map(({ label, value }) => {\n                    return (\n                      <Select.Item key={value} value={value}>\n                        <Select.ItemIndicator>\n                          <Check className=\"size-4\" />\n                        </Select.ItemIndicator>\n                        <Select.ItemText>{label}</Select.ItemText>\n                      </Select.Item>\n                    );\n                  })}\n                </Select.List>\n                <Select.ScrollDownArrow />\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>\n        <Field.Error />\n      </Field.Root>\n\n      <Field.Root name=\"numOfInstances\">\n        <NumberField.Root defaultValue={undefined} min={1} max={64} required>\n          <Field.Label>Number of instances</Field.Label>\n          <NumberField.Group>\n            <NumberField.Decrement>\n              <Minus className=\"size-4\" />\n            </NumberField.Decrement>\n            <NumberField.Input className=\"!w-16\" />\n            <NumberField.Increment>\n              <Plus className=\"size-4\" />\n            </NumberField.Increment>\n          </NumberField.Group>\n        </NumberField.Root>\n        <Field.Error />\n      </Field.Root>\n\n      <Field.Root name=\"scalingThreshold\">\n        <Fieldset.Root\n          render={\n            <Slider.Root\n              defaultValue={[0.2, 0.8]}\n              thumbAlignment=\"edge\"\n              min={0}\n              max={1}\n              step={0.01}\n              format={{\n                style: 'percent',\n                minimumFractionDigits: 0,\n                maximumFractionDigits: 0,\n              }}\n              className=\"w-98/100 gap-y-2\"\n            />\n          }\n        >\n          <Fieldset.Legend>Scaling threshold</Fieldset.Legend>\n          <Slider.Value className=\"col-start-2 text-end\" />\n          <Slider.Control>\n            <Slider.Track>\n              <Slider.Indicator />\n              <Slider.Thumb index={0} aria-label=\"Minimum threshold\" />\n              <Slider.Thumb index={1} aria-label=\"Maximum threshold\" />\n            </Slider.Track>\n          </Slider.Control>\n        </Fieldset.Root>\n      </Field.Root>\n\n      <Field.Root name=\"storageType\">\n        <Fieldset.Root render={<RadioGroup<'ssd' | 'hdd'> className=\"gap-4\" defaultValue=\"ssd\" />}>\n          <Fieldset.Legend className=\"-mt-px\">Storage type</Fieldset.Legend>\n          <Field.Item>\n            <Field.Label>\n              <Radio.Root value=\"ssd\">\n                <Radio.Indicator />\n              </Radio.Root>\n              SSD\n            </Field.Label>\n          </Field.Item>\n          <Field.Item>\n            <Field.Label>\n              <Radio.Root value=\"hdd\">\n                <Radio.Indicator />\n              </Radio.Root>\n              HDD\n            </Field.Label>\n          </Field.Item>\n        </Fieldset.Root>\n      </Field.Root>\n\n      <Field.Root name=\"restartOnFailure\">\n        <Field.Label className=\"gap-4\">\n          Restart on failure\n          <Switch.Root defaultChecked>\n            <Switch.Thumb />\n          </Switch.Root>\n        </Field.Label>\n      </Field.Root>\n\n      <Field.Root name=\"allowedNetworkProtocols\">\n        <Fieldset.Root render={<CheckboxGroup defaultValue={[]} />}>\n          <Fieldset.Legend className=\"mb-2\">Allowed network protocols</Fieldset.Legend>\n          <div className=\"flex gap-4\">\n            {['http', 'https', 'ssh'].map((val) => {\n              return (\n                <Field.Item key={val}>\n                  <Field.Label className=\"uppercase\">\n                    <Checkbox.Root value={val}>\n                      <Checkbox.Indicator>\n                        <Check className=\"size-3\" />\n                      </Checkbox.Indicator>\n                    </Checkbox.Root>\n                    {val}\n                  </Field.Label>\n                </Field.Item>\n              );\n            })}\n          </div>\n        </Fieldset.Root>\n      </Field.Root>\n\n      <Button type=\"submit\" className=\"mt-3\">\n        Launch server\n      </Button>\n    </Form>\n  );\n}\n\nexport default function App() {\n  return (\n    <ToastProvider>\n      <ExampleForm />\n    </ToastProvider>\n  );\n}\n\nfunction cartesian<T extends string[][]>(...arrays: T): string[][] {\n  return arrays.reduce<string[][]>(\n    (acc, curr) => acc.flatMap((a) => curr.map((b) => [...a, b])),\n    [[]],\n  );\n}\n\nconst REGIONS = cartesian(['us', 'eu', 'ap'], ['central', 'east', 'west'], ['1', '2', '3']).map(\n  (part) => part.join('-'),\n);\n\ninterface Image {\n  url: string;\n  name: string;\n}\n/* prettier-ignore */\nconst IMAGES: Image[] = ['nginx:1.29-alpine', 'node:22-slim', 'postgres:18', 'redis:8.2.2-alpine'].map((name) => ({\n  url: `docker.io/library/${name}`,\n  name,\n}));\n\nconst SERVER_TYPES = [\n  { label: 'Select server type', value: null },\n  ...cartesian(['t', 'm'], ['1', '2'], ['small', 'medium', 'large']).map((part) => {\n    const value = part.join('.').replace('.', '');\n    return { label: value, value };\n  }),\n];\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/forms/demos/react-hook-form/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport Tailwind from './tailwind';\n\nexport const DemoReactHookForm = createDemoWithVariants(import.meta.url, { Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/forms/demos/react-hook-form/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useForm, Controller } from 'react-hook-form';\nimport { ChevronDown, ChevronsUpDown, Check, Plus, Minus } from 'lucide-react';\nimport { Button } from '../../components/button';\nimport { CheckboxGroup } from '../../components/checkbox-group';\nimport { Form } from '../../components/form';\nimport { RadioGroup } from '../../components/radio-group';\nimport { ToastProvider, useToastManager } from '../../components/toast';\nimport * as Autocomplete from '../../components/autocomplete';\nimport * as Checkbox from '../../components/checkbox';\nimport * as Combobox from '../../components/combobox';\nimport * as Field from '../../components/field';\nimport * as Fieldset from '../../components/fieldset';\nimport * as NumberField from '../../components/number-field';\nimport * as Radio from '../../components/radio';\nimport * as Select from '../../components/select';\nimport * as Slider from '../../components/slider';\nimport * as Switch from '../../components/switch';\n\ninterface FormValues {\n  serverName: string;\n  region: string | null;\n  containerImage: string;\n  serverType: string | null;\n  numOfInstances: number | null;\n  scalingThreshold: number[];\n  storageType: 'ssd' | 'hdd';\n  restartOnFailure: boolean;\n  allowedNetworkProtocols: string[];\n}\n\nfunction ReactHookForm() {\n  const toastManager = useToastManager();\n\n  const { control, handleSubmit } = useForm<FormValues>({\n    defaultValues: {\n      serverName: '',\n      region: null,\n      containerImage: '',\n      serverType: null,\n      numOfInstances: null,\n      scalingThreshold: [0.2, 0.8],\n      storageType: 'ssd',\n      restartOnFailure: true,\n      allowedNetworkProtocols: [],\n    },\n  });\n\n  function submitForm(data: FormValues) {\n    toastManager.add({\n      title: 'Form submitted',\n      description: 'The form contains these values:',\n      data,\n    });\n  }\n\n  return (\n    <Form aria-label=\"Launch new cloud server\" onSubmit={handleSubmit(submitForm)}>\n      <Controller\n        name=\"serverName\"\n        control={control}\n        rules={{\n          required: 'This field is required.',\n          minLength: { value: 3, message: 'At least 3 characters.' },\n        }}\n        render={({\n          field: { ref, name, value, onBlur, onChange },\n          fieldState: { invalid, isTouched, isDirty, error },\n        }) => (\n          <Field.Root name={name} invalid={invalid} touched={isTouched} dirty={isDirty}>\n            <Field.Label>Server name</Field.Label>\n            <Field.Control\n              ref={ref}\n              value={value}\n              onBlur={onBlur}\n              onValueChange={onChange}\n              placeholder=\"e.g. api-server-01\"\n            />\n            <Field.Description>Must be 3 or more characters long</Field.Description>\n            <Field.Error match={!!error}>{error?.message}</Field.Error>\n          </Field.Root>\n        )}\n      />\n\n      <Controller\n        name=\"region\"\n        control={control}\n        rules={{\n          required: 'This field is required.',\n        }}\n        render={({\n          field: { ref, name, value, onBlur, onChange },\n          fieldState: { invalid, isTouched, isDirty, error },\n        }) => (\n          <Field.Root name={name} invalid={invalid} touched={isTouched} dirty={isDirty}>\n            <Combobox.Root items={REGIONS} value={value} onValueChange={onChange}>\n              <div className=\"relative flex flex-col gap-1 text-sm leading-5 text-gray-900\">\n                <Field.Label>Region</Field.Label>\n                <Combobox.Input placeholder=\"e.g. eu-central-1\" ref={ref} onBlur={onBlur} />\n                <div className=\"absolute right-2 bottom-0 flex h-10 items-center justify-center text-gray-600\">\n                  <Combobox.Clear />\n                  <Combobox.Trigger>\n                    <ChevronDown className=\"size-4\" />\n                  </Combobox.Trigger>\n                </div>\n              </div>\n              <Combobox.Portal>\n                <Combobox.Positioner>\n                  <Combobox.Popup>\n                    <Combobox.Empty>No matches</Combobox.Empty>\n                    <Combobox.List>\n                      {(region: string) => {\n                        return (\n                          <Combobox.Item key={region} value={region}>\n                            <Combobox.ItemIndicator>\n                              <Check className=\"size-4\" />\n                            </Combobox.ItemIndicator>\n                            <div className=\"col-start-2\">{region}</div>\n                          </Combobox.Item>\n                        );\n                      }}\n                    </Combobox.List>\n                  </Combobox.Popup>\n                </Combobox.Positioner>\n              </Combobox.Portal>\n            </Combobox.Root>\n            <Field.Error match={!!error}>{error?.message}</Field.Error>\n          </Field.Root>\n        )}\n      />\n\n      <Controller\n        name=\"containerImage\"\n        control={control}\n        rules={{\n          required: 'This field is required.',\n        }}\n        render={({\n          field: { ref, name, value, onBlur, onChange },\n          fieldState: { invalid, isTouched, isDirty, error },\n        }) => (\n          <Field.Root name={name} invalid={invalid} touched={isTouched} dirty={isDirty}>\n            <Autocomplete.Root\n              items={IMAGES}\n              mode=\"both\"\n              itemToStringValue={(itemValue: Image) => itemValue.url}\n              value={value}\n              onValueChange={onChange}\n            >\n              <Field.Label>Container image</Field.Label>\n              <Autocomplete.Input\n                placeholder=\"e.g. docker.io/library/node:latest\"\n                ref={ref}\n                onBlur={onBlur}\n              />\n              <Field.Description>Enter a registry URL with optional tags</Field.Description>\n              <Autocomplete.Portal>\n                <Autocomplete.Positioner>\n                  <Autocomplete.Popup>\n                    <Autocomplete.List>\n                      {(image: Image) => {\n                        return (\n                          <Autocomplete.Item key={image.url} value={image}>\n                            <span className=\"text-base leading-6\">{image.name}</span>\n                            <span className=\"font-mono whitespace-nowrap text-xs leading-4 opacity-80\">\n                              {image.url}\n                            </span>\n                          </Autocomplete.Item>\n                        );\n                      }}\n                    </Autocomplete.List>\n                  </Autocomplete.Popup>\n                </Autocomplete.Positioner>\n              </Autocomplete.Portal>\n            </Autocomplete.Root>\n            <Field.Error match={!!error}>{error?.message}</Field.Error>\n          </Field.Root>\n        )}\n      />\n\n      <Controller\n        name=\"serverType\"\n        control={control}\n        rules={{\n          required: 'This field is required.',\n        }}\n        render={({\n          field: { ref, name, value, onBlur, onChange },\n          fieldState: { invalid, isTouched, isDirty, error },\n        }) => (\n          <Field.Root name={name} invalid={invalid} touched={isTouched} dirty={isDirty}>\n            <Select.Root items={SERVER_TYPES} value={value} onValueChange={onChange} inputRef={ref}>\n              <div className=\"flex flex-col items-start gap-1\">\n                <Select.Label>Server type</Select.Label>\n                <Select.Trigger className=\"w-48\" onBlur={onBlur}>\n                  <Select.Value />\n                  <Select.Icon>\n                    <ChevronsUpDown className=\"size-4\" />\n                  </Select.Icon>\n                </Select.Trigger>\n              </div>\n              <Select.Portal>\n                <Select.Positioner>\n                  <Select.Popup>\n                    <Select.ScrollUpArrow />\n                    <Select.List>\n                      {SERVER_TYPES.map(({ label, value: serverType }) => {\n                        return (\n                          <Select.Item key={serverType} value={serverType}>\n                            <Select.ItemIndicator>\n                              <Check className=\"size-4\" />\n                            </Select.ItemIndicator>\n                            <Select.ItemText>{label}</Select.ItemText>\n                          </Select.Item>\n                        );\n                      })}\n                    </Select.List>\n                    <Select.ScrollDownArrow />\n                  </Select.Popup>\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n            <Field.Error match={!!error}>{error?.message}</Field.Error>\n          </Field.Root>\n        )}\n      />\n\n      <Controller\n        name=\"numOfInstances\"\n        control={control}\n        rules={{\n          required: 'This field is required.',\n        }}\n        render={({\n          field: { ref, name, value, onBlur, onChange },\n          fieldState: { invalid, isTouched, isDirty, error },\n        }) => (\n          <Field.Root name={name} invalid={invalid} touched={isTouched} dirty={isDirty}>\n            <NumberField.Root value={value} min={1} max={64} onValueChange={onChange}>\n              <Field.Label>Number of instances</Field.Label>\n              <NumberField.Group>\n                <NumberField.Decrement>\n                  <Minus className=\"size-4\" />\n                </NumberField.Decrement>\n                <NumberField.Input className=\"!w-16\" ref={ref} onBlur={onBlur} />\n                <NumberField.Increment>\n                  <Plus className=\"size-4\" />\n                </NumberField.Increment>\n              </NumberField.Group>\n            </NumberField.Root>\n            <Field.Error match={!!error}>{error?.message}</Field.Error>\n          </Field.Root>\n        )}\n      />\n\n      <Controller\n        name=\"scalingThreshold\"\n        control={control}\n        render={({\n          field: { ref, name, value, onBlur, onChange },\n          fieldState: { invalid, isTouched, isDirty },\n        }) => (\n          <Field.Root name={name} invalid={invalid} touched={isTouched} dirty={isDirty}>\n            <Fieldset.Root\n              render={\n                <Slider.Root\n                  value={value}\n                  onValueChange={onChange}\n                  onValueCommitted={onChange}\n                  thumbAlignment=\"edge\"\n                  min={0}\n                  max={1}\n                  step={0.01}\n                  format={{\n                    style: 'percent',\n                    minimumFractionDigits: 0,\n                    maximumFractionDigits: 0,\n                  }}\n                  className=\"w-98/100 gap-y-2\"\n                />\n              }\n            >\n              <Fieldset.Legend>Scaling threshold</Fieldset.Legend>\n              <Slider.Value className=\"col-start-2 text-end\" />\n              <Slider.Control>\n                <Slider.Track>\n                  <Slider.Indicator />\n                  <Slider.Thumb\n                    index={0}\n                    aria-label=\"Minimum threshold\"\n                    onBlur={onBlur}\n                    inputRef={ref}\n                  />\n                  <Slider.Thumb index={1} aria-label=\"Maximum threshold\" onBlur={onBlur} />\n                </Slider.Track>\n              </Slider.Control>\n            </Fieldset.Root>\n          </Field.Root>\n        )}\n      />\n\n      <Controller\n        name=\"storageType\"\n        control={control}\n        render={({\n          field: { ref, name, value, onBlur, onChange },\n          fieldState: { invalid, isTouched, isDirty },\n        }) => (\n          <Field.Root name={name} invalid={invalid} touched={isTouched} dirty={isDirty}>\n            <Fieldset.Root\n              render={\n                <RadioGroup\n                  className=\"gap-4\"\n                  value={value}\n                  onValueChange={onChange}\n                  inputRef={ref}\n                />\n              }\n            >\n              <Fieldset.Legend className=\"-mt-px\">Storage type</Fieldset.Legend>\n              <Field.Item>\n                <Field.Label>\n                  <Radio.Root value=\"ssd\" onBlur={onBlur}>\n                    <Radio.Indicator />\n                  </Radio.Root>\n                  SSD\n                </Field.Label>\n              </Field.Item>\n              <Field.Item>\n                <Field.Label>\n                  <Radio.Root value=\"hdd\" onBlur={onBlur}>\n                    <Radio.Indicator />\n                  </Radio.Root>\n                  HDD\n                </Field.Label>\n              </Field.Item>\n            </Fieldset.Root>\n          </Field.Root>\n        )}\n      />\n\n      <Controller\n        name=\"restartOnFailure\"\n        control={control}\n        render={({\n          field: { ref, name, value, onBlur, onChange },\n          fieldState: { invalid, isTouched, isDirty },\n        }) => (\n          <Field.Root name={name} invalid={invalid} touched={isTouched} dirty={isDirty}>\n            <Field.Label className=\"gap-4\">\n              Restart on failure\n              <Switch.Root\n                checked={value}\n                inputRef={ref}\n                onCheckedChange={onChange}\n                onBlur={onBlur}\n              >\n                <Switch.Thumb />\n              </Switch.Root>\n            </Field.Label>\n          </Field.Root>\n        )}\n      />\n\n      <Controller\n        name=\"allowedNetworkProtocols\"\n        control={control}\n        render={({\n          field: { ref, name, value, onBlur, onChange },\n          fieldState: { invalid, isTouched, isDirty },\n        }) => (\n          <Field.Root name={name} invalid={invalid} touched={isTouched} dirty={isDirty}>\n            <Fieldset.Root render={<CheckboxGroup value={value} onValueChange={onChange} />}>\n              <Fieldset.Legend className=\"mb-2\">Allowed network protocols</Fieldset.Legend>\n              <div className=\"flex gap-4\">\n                {['http', 'https', 'ssh'].map((val) => {\n                  return (\n                    <Field.Item key={val}>\n                      <Field.Label className=\"uppercase\">\n                        <Checkbox.Root\n                          value={val}\n                          inputRef={val === 'http' ? ref : undefined}\n                          onBlur={onBlur}\n                        >\n                          <Checkbox.Indicator>\n                            <Check className=\"size-3\" />\n                          </Checkbox.Indicator>\n                        </Checkbox.Root>\n                        {val}\n                      </Field.Label>\n                    </Field.Item>\n                  );\n                })}\n              </div>\n            </Fieldset.Root>\n          </Field.Root>\n        )}\n      />\n\n      <Button type=\"submit\" className=\"mt-3\">\n        Launch server\n      </Button>\n    </Form>\n  );\n}\n\nexport default function App() {\n  return (\n    <ToastProvider>\n      <ReactHookForm />\n    </ToastProvider>\n  );\n}\n\nfunction cartesian<T extends string[][]>(...arrays: T): string[][] {\n  return arrays.reduce<string[][]>(\n    (acc, curr) => acc.flatMap((a) => curr.map((b) => [...a, b])),\n    [[]],\n  );\n}\n\nconst REGIONS = cartesian(['us', 'eu', 'ap'], ['central', 'east', 'west'], ['1', '2', '3']).map(\n  (part) => part.join('-'),\n);\n\ninterface Image {\n  url: string;\n  name: string;\n}\n/* prettier-ignore */\nconst IMAGES: Image[] = ['nginx:1.29-alpine', 'node:22-slim', 'postgres:18', 'redis:8.2.2-alpine'].map((name) => ({\n  url: `docker.io/library/${name}`,\n  name,\n}));\n\nconst SERVER_TYPES = [\n  { label: 'Select server type', value: null },\n  ...cartesian(['t', 'm'], ['1', '2'], ['small', 'medium', 'large']).map((part) => {\n    const value = part.join('.').replace('.', '');\n    return { label: value, value };\n  }),\n];\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/forms/demos/tanstack-form/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport Tailwind from './tailwind';\n\nexport const DemoTanstackForm = createDemoWithVariants(import.meta.url, { Tailwind });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/forms/demos/tanstack-form/tailwind/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useForm, revalidateLogic, DeepKeys, ValidationError } from '@tanstack/react-form';\nimport { ChevronDown, ChevronsUpDown, Check, Plus, Minus } from 'lucide-react';\nimport { Button } from '../../components/button';\nimport { CheckboxGroup } from '../../components/checkbox-group';\nimport { RadioGroup } from '../../components/radio-group';\nimport { ToastProvider, useToastManager } from '../../components/toast';\nimport * as Autocomplete from '../../components/autocomplete';\nimport * as Checkbox from '../../components/checkbox';\nimport * as Combobox from '../../components/combobox';\nimport * as Field from '../../components/field';\nimport * as Fieldset from '../../components/fieldset';\nimport * as NumberField from '../../components/number-field';\nimport * as Radio from '../../components/radio';\nimport * as Select from '../../components/select';\nimport * as Slider from '../../components/slider';\nimport * as Switch from '../../components/switch';\n\ninterface FormValues {\n  serverName: string;\n  region: string | null;\n  containerImage: string;\n  serverType: string | null;\n  numOfInstances: number | null;\n  scalingThreshold: number[];\n  storageType: 'ssd' | 'hdd';\n  restartOnFailure: boolean;\n  allowedNetworkProtocols: string[];\n}\n\nconst defaultValues: FormValues = {\n  serverName: '',\n  region: null,\n  containerImage: '',\n  serverType: null,\n  numOfInstances: null,\n  scalingThreshold: [0.2, 0.8],\n  storageType: 'ssd',\n  restartOnFailure: true,\n  allowedNetworkProtocols: [],\n};\n\nfunction TanstackForm() {\n  const toastManager = useToastManager();\n\n  const form = useForm({\n    defaultValues,\n    onSubmit: ({ value: formValues }) => {\n      toastManager.add({\n        title: 'Form submitted',\n        description: 'The form contains these values:',\n        data: formValues,\n      });\n    },\n    validationLogic: revalidateLogic({\n      mode: 'submit',\n      modeAfterSubmission: 'change',\n    }),\n    validators: {\n      onDynamic: ({ value: formValues }) => {\n        const errors: Partial<Record<DeepKeys<FormValues>, ValidationError>> = {};\n\n        (\n          ['serverName', 'region', 'containerImage', 'serverType', 'numOfInstances'] as const\n        ).forEach((requiredField) => {\n          if (!formValues[requiredField]) {\n            errors[requiredField] = 'This is a required field.';\n          }\n        });\n\n        if (formValues.serverName && formValues.serverName.length < 3) {\n          errors.serverName = 'At least 3 characters.';\n        }\n\n        return isEmpty(errors) ? undefined : { form: errors, fields: errors };\n      },\n    },\n  });\n\n  /* eslint-disable react/no-children-prop */\n  return (\n    <form\n      aria-label=\"Launch new cloud server\"\n      className=\"flex w-full max-w-3xs sm:max-w-[20rem] flex-col gap-5\"\n      noValidate\n      onSubmit={(event) => {\n        event.preventDefault();\n        form.handleSubmit();\n      }}\n    >\n      <form.Field\n        name=\"serverName\"\n        children={(field) => {\n          return (\n            <Field.Root\n              name={field.name}\n              invalid={!field.state.meta.isValid}\n              dirty={field.state.meta.isDirty}\n              touched={field.state.meta.isTouched}\n            >\n              <Field.Label>Server name</Field.Label>\n              <Field.Control\n                value={field.state.value}\n                onValueChange={field.handleChange}\n                onBlur={field.handleBlur}\n                placeholder=\"e.g. api-server-01\"\n              />\n              <Field.Description>Must be 3 or more characters long</Field.Description>\n              <Field.Error match={!field.state.meta.isValid}>\n                {field.state.meta.errors.join(',')}\n              </Field.Error>\n            </Field.Root>\n          );\n        }}\n      />\n\n      <form.Field\n        name=\"region\"\n        children={(field) => {\n          return (\n            <Field.Root\n              name={field.name}\n              invalid={!field.state.meta.isValid}\n              dirty={field.state.meta.isDirty}\n              touched={field.state.meta.isTouched}\n            >\n              <Combobox.Root\n                items={REGIONS}\n                value={field.state.value}\n                onValueChange={field.handleChange}\n              >\n                <div className=\"relative flex flex-col gap-1 text-sm leading-5 text-gray-900\">\n                  <Field.Label>Region</Field.Label>\n                  <Combobox.Input placeholder=\"e.g. eu-central-1\" onBlur={field.handleBlur} />\n                  <div className=\"absolute right-2 bottom-0 flex h-10 items-center justify-center text-gray-600\">\n                    <Combobox.Clear />\n                    <Combobox.Trigger>\n                      <ChevronDown className=\"size-4\" />\n                    </Combobox.Trigger>\n                  </div>\n                </div>\n                <Combobox.Portal>\n                  <Combobox.Positioner>\n                    <Combobox.Popup>\n                      <Combobox.Empty>No matches</Combobox.Empty>\n                      <Combobox.List>\n                        {(region: string) => {\n                          return (\n                            <Combobox.Item key={region} value={region}>\n                              <Combobox.ItemIndicator>\n                                <Check className=\"size-3\" />\n                              </Combobox.ItemIndicator>\n                              <div className=\"col-start-2\">{region}</div>\n                            </Combobox.Item>\n                          );\n                        }}\n                      </Combobox.List>\n                    </Combobox.Popup>\n                  </Combobox.Positioner>\n                </Combobox.Portal>\n              </Combobox.Root>\n\n              <Field.Error match={!field.state.meta.isValid}>\n                {field.state.meta.errors.join(',')}\n              </Field.Error>\n            </Field.Root>\n          );\n        }}\n      />\n\n      <form.Field\n        name=\"containerImage\"\n        children={(field) => {\n          return (\n            <Field.Root\n              name={field.name}\n              invalid={!field.state.meta.isValid}\n              dirty={field.state.meta.isDirty}\n              touched={field.state.meta.isTouched}\n            >\n              <Autocomplete.Root\n                items={IMAGES}\n                mode=\"both\"\n                value={field.state.value}\n                onValueChange={field.handleChange}\n                itemToStringValue={(itemValue: Image) => itemValue.url}\n              >\n                <Field.Label>Container image</Field.Label>\n                <Autocomplete.Input\n                  placeholder=\"e.g. docker.io/library/node:latest\"\n                  onBlur={field.handleBlur}\n                />\n                <Field.Description>Enter a registry URL with optional tags</Field.Description>\n                <Autocomplete.Portal>\n                  <Autocomplete.Positioner>\n                    <Autocomplete.Popup>\n                      <Autocomplete.List>\n                        {(image: Image) => {\n                          return (\n                            <Autocomplete.Item key={image.url} value={image}>\n                              <span className=\"text-base leading-6\">{image.name}</span>\n                              <span className=\"font-mono whitespace-nowrap text-xs leading-4 opacity-80\">\n                                {image.url}\n                              </span>\n                            </Autocomplete.Item>\n                          );\n                        }}\n                      </Autocomplete.List>\n                    </Autocomplete.Popup>\n                  </Autocomplete.Positioner>\n                </Autocomplete.Portal>\n              </Autocomplete.Root>\n              <Field.Error match={!field.state.meta.isValid}>\n                {field.state.meta.errors.join(',')}\n              </Field.Error>\n            </Field.Root>\n          );\n        }}\n      />\n\n      <form.Field\n        name=\"serverType\"\n        children={(field) => {\n          return (\n            <Field.Root\n              name={field.name}\n              invalid={!field.state.meta.isValid}\n              dirty={field.state.meta.isDirty}\n              touched={field.state.meta.isTouched}\n            >\n              <Select.Root\n                items={SERVER_TYPES}\n                value={field.state.value}\n                onValueChange={field.handleChange}\n              >\n                <div className=\"flex flex-col items-start gap-1\">\n                  <Select.Label>Server type</Select.Label>\n                  <Select.Trigger className=\"!w-48\" onBlur={field.handleBlur}>\n                    <Select.Value />\n                    <Select.Icon>\n                      <ChevronsUpDown className=\"size-4\" />\n                    </Select.Icon>\n                  </Select.Trigger>\n                </div>\n                <Select.Portal>\n                  <Select.Positioner>\n                    <Select.Popup>\n                      <Select.ScrollUpArrow />\n                      <Select.List>\n                        {SERVER_TYPES.map(({ label, value }) => {\n                          return (\n                            <Select.Item key={value} value={value}>\n                              <Select.ItemIndicator>\n                                <Check className=\"size-3\" />\n                              </Select.ItemIndicator>\n                              <Select.ItemText>{label}</Select.ItemText>\n                            </Select.Item>\n                          );\n                        })}\n                      </Select.List>\n                      <Select.ScrollDownArrow />\n                    </Select.Popup>\n                  </Select.Positioner>\n                </Select.Portal>\n              </Select.Root>\n              <Field.Error match={!field.state.meta.isValid}>\n                {field.state.meta.errors.join(',')}\n              </Field.Error>\n            </Field.Root>\n          );\n        }}\n      />\n\n      <form.Field\n        name=\"numOfInstances\"\n        children={(field) => {\n          return (\n            <Field.Root\n              name={field.name}\n              invalid={!field.state.meta.isValid}\n              dirty={field.state.meta.isDirty}\n              touched={field.state.meta.isTouched}\n            >\n              <NumberField.Root\n                value={field.state.value}\n                onValueChange={field.handleChange}\n                min={1}\n                max={64}\n              >\n                <Field.Label>Number of instances</Field.Label>\n                <NumberField.Group>\n                  <NumberField.Decrement>\n                    <Minus className=\"size-4\" />\n                  </NumberField.Decrement>\n                  <NumberField.Input className=\"!w-16\" onBlur={field.handleBlur} />\n                  <NumberField.Increment>\n                    <Plus className=\"size-4\" />\n                  </NumberField.Increment>\n                </NumberField.Group>\n              </NumberField.Root>\n              <Field.Error match={!field.state.meta.isValid}>\n                {field.state.meta.errors.join(',')}\n              </Field.Error>\n            </Field.Root>\n          );\n        }}\n      />\n\n      <form.Field\n        name=\"scalingThreshold\"\n        children={(field) => {\n          return (\n            <Field.Root\n              name={field.name}\n              invalid={!field.state.meta.isValid}\n              dirty={field.state.meta.isDirty}\n              touched={field.state.meta.isTouched}\n            >\n              <Fieldset.Root\n                render={\n                  <Slider.Root\n                    value={field.state.value}\n                    onValueChange={field.handleChange}\n                    onValueCommitted={field.handleChange}\n                    thumbAlignment=\"edge\"\n                    min={0}\n                    max={1}\n                    step={0.01}\n                    format={{\n                      style: 'percent',\n                      minimumFractionDigits: 0,\n                      maximumFractionDigits: 0,\n                    }}\n                    className=\"w-98/100 gap-y-2\"\n                  />\n                }\n              >\n                <Fieldset.Legend>Scaling threshold</Fieldset.Legend>\n                <Slider.Value className=\"col-start-2 text-end\" />\n                <Slider.Control>\n                  <Slider.Track>\n                    <Slider.Indicator />\n                    <Slider.Thumb\n                      index={0}\n                      aria-label=\"Minimum threshold\"\n                      onBlur={field.handleBlur}\n                    />\n                    <Slider.Thumb\n                      index={1}\n                      aria-label=\"Maximum threshold\"\n                      onBlur={field.handleBlur}\n                    />\n                  </Slider.Track>\n                </Slider.Control>\n              </Fieldset.Root>\n              <Field.Error match={!field.state.meta.isValid}>\n                {field.state.meta.errors.join(',')}\n              </Field.Error>\n            </Field.Root>\n          );\n        }}\n      />\n\n      <form.Field\n        name=\"storageType\"\n        children={(field) => {\n          return (\n            <Field.Root\n              name={field.name}\n              invalid={!field.state.meta.isValid}\n              dirty={field.state.meta.isDirty}\n              touched={field.state.meta.isTouched}\n            >\n              <Fieldset.Root\n                render={\n                  <RadioGroup\n                    value={field.state.value}\n                    onValueChange={field.handleChange}\n                    className=\"gap-4\"\n                  />\n                }\n              >\n                <Fieldset.Legend className=\"-mt-px\">Storage type</Fieldset.Legend>\n                {['ssd', 'hdd'].map((radioValue) => (\n                  <Field.Item key={radioValue}>\n                    <Field.Label className=\"uppercase\">\n                      <Radio.Root value={radioValue}>\n                        <Radio.Indicator />\n                      </Radio.Root>\n                      {radioValue}\n                    </Field.Label>\n                  </Field.Item>\n                ))}\n              </Fieldset.Root>\n              <Field.Error match={!field.state.meta.isValid}>\n                {field.state.meta.errors.join(',')}\n              </Field.Error>\n            </Field.Root>\n          );\n        }}\n      />\n\n      <form.Field\n        name=\"restartOnFailure\"\n        children={(field) => {\n          return (\n            <Field.Root\n              name={field.name}\n              invalid={!field.state.meta.isValid}\n              dirty={field.state.meta.isDirty}\n              touched={field.state.meta.isTouched}\n            >\n              <Field.Label className=\"gap-4\">\n                Restart on failure\n                <Switch.Root\n                  checked={field.state.value}\n                  onCheckedChange={field.handleChange}\n                  onBlur={field.handleBlur}\n                >\n                  <Switch.Thumb />\n                </Switch.Root>\n              </Field.Label>\n              <Field.Error match={!field.state.meta.isValid}>\n                {field.state.meta.errors.join(',')}\n              </Field.Error>\n            </Field.Root>\n          );\n        }}\n      />\n\n      <form.Field\n        name=\"allowedNetworkProtocols\"\n        children={(field) => {\n          return (\n            <Field.Root\n              name={field.name}\n              invalid={!field.state.meta.isValid}\n              dirty={field.state.meta.isDirty}\n              touched={field.state.meta.isTouched}\n            >\n              <Fieldset.Root\n                render={\n                  <CheckboxGroup value={field.state.value} onValueChange={field.handleChange} />\n                }\n              >\n                <Fieldset.Legend className=\"mb-2\">Allowed network protocols</Fieldset.Legend>\n                <div className=\"flex gap-4\">\n                  {['http', 'https', 'ssh'].map((checkboxValue) => {\n                    return (\n                      <Field.Item key={checkboxValue}>\n                        <Field.Label className=\"uppercase\">\n                          <Checkbox.Root value={checkboxValue} onBlur={field.handleBlur}>\n                            <Checkbox.Indicator>\n                              <Check className=\"size-3\" />\n                            </Checkbox.Indicator>\n                          </Checkbox.Root>\n                          {checkboxValue}\n                        </Field.Label>\n                      </Field.Item>\n                    );\n                  })}\n                </div>\n              </Fieldset.Root>\n              <Field.Error match={!field.state.meta.isValid}>\n                {field.state.meta.errors.join(',')}\n              </Field.Error>\n            </Field.Root>\n          );\n        }}\n      />\n\n      <Button type=\"submit\" className=\"mt-3\">\n        Launch server\n      </Button>\n    </form>\n  );\n}\n\nexport default function App() {\n  return (\n    <ToastProvider>\n      <TanstackForm />\n    </ToastProvider>\n  );\n}\n\nfunction isEmpty(object: Partial<Record<DeepKeys<FormValues>, ValidationError>>) {\n  // eslint-disable-next-line\n  for (const _ in object) {\n    return false;\n  }\n  return true;\n}\n\nfunction cartesian<T extends string[][]>(...arrays: T): string[][] {\n  return arrays.reduce<string[][]>(\n    (acc, curr) => acc.flatMap((a) => curr.map((b) => [...a, b])),\n    [[]],\n  );\n}\n\nconst REGIONS = cartesian(['us', 'eu', 'ap'], ['central', 'east', 'west'], ['1', '2', '3']).map(\n  (part) => part.join('-'),\n);\n\ninterface Image {\n  url: string;\n  name: string;\n}\n/* prettier-ignore */\nconst IMAGES: Image[] = ['nginx:1.29-alpine', 'node:22-slim', 'postgres:18', 'redis:8.2.2-alpine'].map((name) => ({\n  url: `docker.io/library/${name}`,\n  name,\n}));\n\nconst SERVER_TYPES = [\n  { label: 'Select server type', value: null },\n  ...cartesian(['t', 'm'], ['1', '2'], ['small', 'medium', 'large']).map((part) => {\n    const value = part.join('.').replace('.', '');\n    return { label: value, value };\n  }),\n];\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/forms/page.mdx",
    "content": "# Forms\n\n<Subtitle>A guide to building forms with Base UI components.</Subtitle>\n<Meta name=\"description\" content=\"A guide to building forms with Base UI components.\" />\n\nBase UI form control components extend the native [constraint validation API](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#the-constraint-validation-api) so you can build forms for collecting user input or providing control over an interface. They also integrate seamlessly with third-party libraries like [React Hook Form](#react-hook-form) and [TanStack Form](#tanstack-form).\n\nimport { DemoBaseUIForm } from './demos/hero';\n\n<DemoBaseUIForm showExtraPlaygroundLink />\n\n## Naming form controls\n\nForm controls must have an accessible name in order to be recognized by assistive technologies. Use the label strategy below for each control type.\n\n### Input controls\n\nUse `<Field.Label>` or a native `<label>` to label the following controls:\n\n- `Input`\n- `NumberField`\n- `Autocomplete`\n- `Combobox` (input outside popup)\n- `Checkbox`\n- `Radio`\n- `Switch`\n\nYou can implicitly label `<Checkbox>`, `<Radio>` and `<Switch>` components by enclosing them with `<Field.Label>`:\n\n```tsx title=\"Implicitly labeling a switch\"\nimport { Field } from '@base-ui/react/field';\nimport { Switch } from '@base-ui/react/switch';\n\n<Field.Root>\n  <Field.Label>\n    <Switch.Root />\n    Developer mode\n  </Field.Label>\n  <Field.Description>Enables extra tools for web developers</Field.Description>\n</Field.Root>;\n```\n\n### Trigger-based controls\n\n- `Combobox` (input inside popup): use `<Combobox.Label>`.\n- `Select`: use `<Select.Label>`.\n- `Slider`: use `<Slider.Label>`. For multi-thumb sliders, also add an `aria-label` on each\n  `<Slider.Thumb>` to distinguish the thumbs.\n\n### Fallback\n\nIf no visible label is rendered, provide `aria-label` on the actual form control.\n\n### Describing the control\n\n`<Field.Description>` automatically assigns an accessible description:\n\n```tsx title=\"Labeling select and slider\"\nimport { Form } from '@base-ui/react/form';\nimport { Field } from '@base-ui/react/field';\nimport { Select } from '@base-ui/react/select';\nimport { Slider } from '@base-ui/react/slider';\n\n<Form>\n  <Field.Root>\n    <Select.Root>\n      <Select.Label>Time zone</Select.Label>\n      <Select.Trigger />\n    </Select.Root>\n    <Field.Description>Used for notifications and reminders</Field.Description>\n  </Field.Root>\n\n  <Field.Root>\n    <Slider.Root defaultValue={50}>\n      <Slider.Label>Zoom level</Slider.Label>\n      <Field.Description>Adjust the size of the user interface</Field.Description>\n      <Slider.Control>\n        <Slider.Track>\n          <Slider.Thumb />\n        </Slider.Track>\n      </Slider.Control>\n    </Slider.Root>\n  </Field.Root>\n</Form>;\n```\n\n### Labeling control groups\n\nCompose `<Fieldset>` when a single label applies to multiple controls, such as a range slider with multiple thumbs or a section that combines several inputs. For checkbox and radio groups, keep the group label in `<Fieldset.Legend>` and wrap each option with `<Field.Item>`:\n\n```tsx {10-11, 18, 22-23, 26} title=\"Composing range slider and radio group with fieldset\"\nimport { Form } from '@base-ui/react/form';\nimport { Field } from '@base-ui/react/field';\nimport { Fieldset } from '@base-ui/react/fieldset';\nimport { Radio } from '@base-ui/react/radio';\nimport { RadioGroup } from '@base-ui/react/radio-group';\nimport { Slider } from '@base-ui/react/slider';\n\n<Form>\n  <Field.Root>\n    <Fieldset.Root render={<Slider.Root />}>\n      <Fieldset.Legend>Price range</Fieldset.Legend>\n      <Slider.Control>\n        <Slider.Track>\n          <Slider.Thumb aria-label=\"Minimum price\" />\n          <Slider.Thumb aria-label=\"Maximum price\" />\n        </Slider.Track>\n      </Slider.Control>\n    </Fieldset.Root>\n  </Field.Root>\n\n  <Field.Root>\n    <Fieldset.Root render={<RadioGroup />}>\n      <Fieldset.Legend>Storage type</Fieldset.Legend>\n      <Radio.Root value=\"ssd\" />\n      <Radio.Root value=\"hdd\" />\n    </Fieldset.Root>\n  </Field.Root>\n</Form>;\n```\n\n`<Field.Item>` should enclose each checkbox or radio option so every control has its own label and description:\n\n```tsx {10,14-15,19} title=\"Explicitly labeling checkboxes in a checkbox group\"\nimport { Form } from '@base-ui/react/form';\nimport { Field } from '@base-ui/react/field';\nimport { Fieldset } from '@base-ui/react/fieldset';\nimport { Checkbox } from '@base-ui/react/checkbox';\nimport { CheckboxGroup } from '@base-ui/react/checkbox-group';\n\n<Field.Root>\n  <Fieldset.Root render={<CheckboxGroup />}>\n    <Fieldset.Legend>Backup schedule</Fieldset.Legend>\n    <Field.Item>\n      <Checkbox.Root value=\"daily\" />\n      <Field.Label>Daily</Field.Label>\n      <Field.Description>Daily at 00:00</Field.Description>\n    </Field.Item>\n    <Field.Item>\n      <Checkbox.Root value=\"monthly\" />\n      <Field.Label>Monthly</Field.Label>\n      <Field.Description>On the 5th of every month at 23:59</Field.Description>\n    </Field.Item>\n  </Fieldset.Root>\n</Field.Root>;\n```\n\n## Building form fields\n\nPass the `name` prop to `<Field.Root>` to include the wrapped control's value when a parent form is submitted:\n\n```tsx {6} title=\"Assigning field name to combobox\" \"name\"\nimport { Form } from '@base-ui/react/form';\nimport { Field } from '@base-ui/react/field';\nimport { Combobox } from '@base-ui/react/combobox';\n\n<Form>\n  <Field.Root name=\"country\">\n    <Field.Label>Country of residence</Field.Label>\n    <Combobox.Root />\n  </Field.Root>\n</Form>;\n```\n\n## Submitting data\n\nYou can take over form submission using the native `onSubmit`, or custom `onFormSubmit` props:\n\n```tsx {4-9} title=\"Native submission using onSubmit\"\nimport { Form } from '@base-ui/react/form';\n\n<Form\n  onSubmit={async (event) => {\n    // Prevent the browser's default full-page refresh\n    event.preventDefault();\n    // Create a FormData object\n    const formData = new FormData(event.currentTarget);\n    // Send the FormData instance in a fetch request\n    await fetch('https://api.example.com', {\n      method: 'POST',\n      body: formData,\n    });\n  }}\n/>;\n```\n\nWhen using `onFormSubmit`, you receive form values as a JavaScript object, with `eventDetails` provided as a second argument. Additionally, `preventDefault()` is automatically called on the native submit event:\n\n```tsx {4-9} title=\"Submission using onFormSubmit\"\nimport { Form } from '@base-ui/react/form';\n\n<Form\n  onFormSubmit={async (formValues) => {\n    const payload = {\n      product_id: formValues.id,\n      order_quantity: formValues.quantity,\n    };\n    await fetch('https://api.example.com', {\n      method: 'POST',\n      body: JSON.stringify(payload),\n    });\n  }}\n/>;\n```\n\n## Constraint validation\n\nBase UI form components support native HTML validation attributes for many validation rules:\n\n- `required` specifies a required field.\n- `minLength` and `maxLength` specify a valid length for text fields.\n- `pattern` specifies a regular expression that the field value must match.\n- `step` specifies an increment that numeric field values must be an integral multiple of.\n\n```tsx title=\"Defining constraint validation on a text field\"\nimport { Field } from '@base-ui/react/field';\n\n<Field.Root name=\"website\">\n  <Field.Control type=\"url\" required pattern=\"https?://.*\" />\n  <Field.Error />\n</Field.Root>;\n```\n\nBase UI form components use a hidden input to participate in native form submission and validation.\nTo anchor the hidden input near a control so the native validation bubble points to the correct area, ensure the component has been given a `name`, and wrap controls in a relatively positioned container for best results.\n\n```tsx title=\"Positioning hidden inputs\"\nimport { Field } from '@base-ui/react/field';\nimport { Select } from '@base-ui/react/select';\n\n<Field.Root name=\"apple\">\n  <Select.Root>\n    <Select.Label>Apple</Select.Label>\n    <div className=\"relative\">\n      <Select.Trigger />\n    </div>\n  </Select.Root>\n</Field.Root>;\n```\n\n## Custom validation\n\nYou can add custom validation logic by passing a synchronous or asynchronous validation function to the `validate` prop, which runs after native validations have passed.\n\nUse the `validationMode` prop to configure when validation is performed:\n\n- `onSubmit` (default) validates all fields when the containing `<Form>` is submitted, afterwards invalid fields revalidate when their value changes.\n- `onBlur` validates the field when focus moves away.\n- `onChange` validates the field when the value changes, for example, after each keypress in a text field or when a checkbox is checked or unchecked.\n\n`validationDebounceTime` can be used to debounce the function in use cases such as asynchronous requests or text fields that validate `onChange`.\n\n```tsx {5-7} title=\"Text input using custom asynchronous validation\"\nimport { Field } from '@base-ui/react/field';\n\n<Field.Root\n  name=\"username\"\n  validationMode=\"onChange\"\n  validationDebounceTime={300}\n  validate={async (value) => {\n    if (value === 'admin') {\n      /* return an error message when invalid */\n      return 'Reserved for system use.';\n    }\n\n    const result = await fetch(\n      {/* prettier-ignore */},\n      /* check the availability of a username from an external API */\n    );\n\n    if (!result) {\n      return `${value} is unavailable.`;\n    }\n\n    /* return `null` when valid */\n    return null;\n  }}\n>\n  <Field.Control required minLength={3} />\n  <Field.Error />\n</Field.Root>;\n```\n\n## Server-side validation\n\nYou can pass errors returned by (post-submission) server-side validation to the `errors` prop, which will be merged into the client-side field state for display.\n\nThis should be an object with field names as keys, and an error string or array of strings as the value. Once a field's value changes, any corresponding error in `errors` will be cleared from the field state.\n\n```tsx title=\"Displaying errors returned by server-side validation\" \"errors\"\nimport { Form } from '@base-ui/react/form';\nimport { Field } from '@base-ui/react/field';\n\nasync function submitToServer(/* payload */) {\n  return {\n    errors: {\n      promoCode: 'This promo code has expired',\n    },\n  };\n}\n\nconst [errors, setErrors] = React.useState();\n\n<Form\n  errors={errors}\n  onSubmit={async (event) => {\n    event.preventDefault();\n    const response = await submitToServer(/* data */);\n    setErrors(response.errors);\n  }}\n>\n  <Field.Root name=\"promoCode\" />\n</Form>;\n```\n\nWhen using [Server Functions with Form Actions](https://react.dev/reference/rsc/server-functions#server-functions-with-use-action-state) you can return server-side errors from `useActionState` to the `errors` prop. A demo is available [here](/react/components/form#submit-with-a-server-function).\n\n```tsx title=\"Returning errors from useActionState\" \"state\" \"errors\" \"formAction\"\n// app/form.tsx\n/* prettier-ignore */\n'use client';\nimport { Form } from '@base-ui/react/form';\nimport { Field } from '@base-ui/react/field';\nimport { login } from './actions';\n\nconst [state, formAction, loading] = React.useActionState(login, {});\n\n<Form action={formAction} errors={state.errors}>\n  <Field.Root name=\"password\">\n    <Field.Control />\n    <Field.Error />\n  </Field.Root>\n</Form>;\n\n// app/actions.ts\n/* prettier-ignore */\n'use server';\nexport async function login(formData: FormData) {\n  const result = authenticateUser(formData);\n\n  if (!result.success) {\n    return {\n      errors: {\n        password: 'Invalid username or password',\n      },\n    };\n  }\n  /* redirect on the server on success */\n}\n```\n\n## Displaying errors\n\nUse `<Field.Error>` without `children` to automatically display the field's native error message when invalid. The `match` prop can be used to customize the message based on the validity state, and manage internationalization from your application logic:\n\n```tsx title=\"Customizing error message for a required field\"\n<Field.Error match=\"valueMissing\">You must create a username</Field.Error>\n```\n\n## React Hook Form\n\n[React Hook Form](https://react-hook-form.com) is a popular library that you can integrate with Base UI to externally manage form and field state for your existing components.\n\nimport { DemoReactHookForm } from './demos/react-hook-form';\n\n<DemoReactHookForm showExtraPlaygroundLink />\n\n### Initialize the form\n\nInitialize the form with the `useForm` hook, assigning the initial value of each field by their name in the `defaultValues` parameter:\n\n```tsx title=\"Initialize a form instance\"\nimport { useForm } from 'react-hook-form';\n\nconst { control, handleSubmit } = useForm<FormValues>({\n  defaultValues: {\n    username: '',\n    email: '',\n  },\n});\n```\n\n### Integrate components\n\nUse the `<Controller>` component to integrate with any `<Field>` component, forwarding the `name`, `field`, and `fieldState` render props to the appropriate part:\n\n```tsx {11-17,22-26} title=\"Integrating the controller component with Base UI field\" \"ref\" \"value\" \"onBlur\" \"onChange\" \"invalid\" \"isTouched\" \"isDirty\" \"error\"\nimport { useForm, Controller } from \"react-hook-form\"\nimport { Field } from '@base-ui/react/field';\n\nconst { control, handleSubmit} = useForm({\n  defaultValues: {\n    username: '',\n  }\n})\n\n<Controller\n  name=\"username\"\n  control={control}\n  render={({\n    field: { name, ref, value, onBlur, onChange },\n    fieldState: { invalid, isTouched, isDirty, error },\n  }) => (\n    <Field.Root name={name} invalid={invalid} touched={isTouched} dirty={isDirty}>\n      <Field.Label>Username</Field.Label>\n      <Field.Description>\n        May appear where you contribute or are mentioned. You can remove it at any time.\n      </Field.Description>\n      <Field.Control\n        placeholder=\"e.g. alice132\"\n        value={value}\n        onBlur={onBlur}\n        onValueChange={onChange}\n        ref={ref}\n      />\n      <Field.Error match={!!error}>\n        {error?.message}\n      </Field.Error>\n    </Field.Root>\n  )}\n/>\n```\n\nFor React Hook Form to focus invalid fields when performing validation, you must ensure that any wrapping components forward the `ref` to the underlying Base UI component. You can typically accomplish this using the `inputRef` prop, or directly as the `ref` for components that render an input element like `<NumberField.Input>`.\n\n### Field validation\n\nSpecify `rules` on the `<Controller>` in the same format as [`register`](https://react-hook-form.com/docs/useform/register) options, and use the `match` prop to delegate control of the error rendering:\n\n```tsx {5-15, 33-35} title=\"Defining validation rules and displaying errors\"\nimport { Controller } from \"react-hook-form\"\nimport { Field } from '@base-ui/react/field';\n\n<Controller\n  name=\"username\"\n  control={control}\n  rules={{\n    required: 'This is a required field',\n    minLength: { value: 2, message: 'Too short' },\n    validate: (value) => {\n      if (/* custom logic */) {\n        return 'Invalid'\n      }\n      return null;\n    },\n  }}\n  render={({\n    field: { name, ref, value, onBlur, onChange },\n    fieldState: { invalid, isTouched, isDirty, error },\n  }) => (\n    <Field.Root name={name} invalid={invalid} touched={isTouched} dirty={isDirty}>\n      <Field.Label>Username</Field.Label>\n      <Field.Description>\n        May appear where you contribute or are mentioned. You can remove it at any time.\n      </Field.Description>\n      <Field.Control\n        placeholder=\"e.g. alice132\"\n        value={value}\n        onBlur={onBlur}\n        onValueChange={onChange}\n        ref={ref}\n      />\n      <Field.Error match={!!error}>\n        {error?.message}\n      </Field.Error>\n    </Field.Root>\n  )}\n/>\n```\n\n### Submitting data\n\nWrap your submit handler function with `handleSubmit` to receive the form values as a JavaScript object for further handling:\n\n```tsx title=\"Form submission handler\"\nimport { useForm } from 'react-hook-form';\nimport { Form } from '@base-ui/react/form';\n\ninterface FormValues {\n  username: string;\n  email: string;\n}\n\nconst { handleSubmit } = useForm<FormValues>();\n\nasync function submitForm(data: FormValues) {\n  // transform the object and/or submit it to a server\n  await fetch(/* ... */);\n}\n\n<Form onSubmit={handleSubmit(submitForm)} />;\n```\n\n## TanStack Form\n\n[TanStack Form](https://tanstack.com/form/v1/docs/overview) is a form library with a function-based API for orchestrating validations that can also be integrated with Base UI.\n\nimport { DemoTanstackForm } from './demos/tanstack-form';\n\n<DemoTanstackForm showExtraPlaygroundLink />\n\n### Initialize the form\n\nCreate a form instance with the `useForm` hook, assigning the initial value of each field by their name in the `defaultValues` parameter:\n\n```tsx {13-14} title=\"Initialize a form instance\"\nimport { useForm } from '@tanstack/react-form';\n\ninterface FormValues {\n  username: string;\n  email: string;\n}\n\nconst defaultValues: FormValues = {\n  username: '',\n  email: '',\n};\n\n/* useForm returns a form instance */\nconst form = useForm<FormValues>({\n  defaultValues,\n});\n```\n\n### Integrate components\n\nUse the `<form.Field>` component from the form instance to integrate with Base UI components using the `children` prop, forwarding the various `field` render props to the appropriate part:\n\n```tsx {7-9, 11-14, 18-20, 24} title=\"Integrating TanStack Form with Base UI components\" \"field.name\" \"value\" \"isValid\" \"isDirty\" \"isTouched\" \"handleChange\" \"handleBlur\"\nimport { useForm } from '@tanstack/react-form';\nimport { Field } from '@base-ui/react/field';\n\nconst form = useForm(/* defaultValues, other parameters */)\n\n<form>\n  <form.Field\n    name=\"username\"\n    children={(field) => (\n      <Field.Root\n        name={field.name}\n        invalid={!field.state.meta.isValid}\n        dirty={field.state.meta.isDirty}\n        touched={field.state.meta.isTouched}\n      >\n        <Field.Label>Username</Field.Label>\n        <Field.Control\n          value={field.state.value}\n          onValueChange={field.handleChange}\n          onBlur={field.handleBlur}\n          placeholder=\"e.g. bob276\"\n        />\n\n        <Field.Error match={!field.state.meta.isValid}>\n          {field.state.meta.errors.join(',')}\n        </Field.Error>\n      </Field.Root>\n    )}\n  />\n</form>\n```\n\nThe Base UI `<Form>` component is not needed when using TanStack Form.\n\n### Form validation\n\nTo configure a native `<form>`-like validation strategy:\n\n1. Use the additional `revalidateLogic` hook and pass it to `useForm`.\n2. Pass a validation function to the `validators.onDynamic` prop on `<form.Field>` that returns an error object with keys corresponding to the field `name`s.\n\nThis validates all fields when the first submission is attempted, and revalidates any invalid fields when their values change again.\n\n```tsx {8, 13} title=\"Form-level validators\" \"revalidateLogic\" \"onDynamic\"\nimport { useForm, revalidateLogic } from '@tanstack/react-form';\n\nconst form = useForm({\n  defaultValues: {\n    username: '',\n    email: '',\n  },\n  validationLogic: revalidateLogic({\n    mode: 'submit',\n    modeAfterSubmission: 'change',\n  }),\n  validators: {\n    onDynamic: ({ value: formValues }) => {\n      const errors = {};\n\n      if (!formValues.username) {\n        errors.username = 'Username is required.';\n      } else if (formValues.username.length < 3) {\n        errors.username = 'At least 3 characters.';\n      }\n\n      if (!formValues.email) {\n        errors.email = 'Email is required.';\n      } else if (!isValidEmail(formValues.email)) {\n        errors.email = 'Invalid email address.';\n      }\n\n      return { form: errors, fields: errors };\n    },\n  },\n});\n```\n\n### Field validation\n\nYou can pass additional validator functions to individual `<form.Field>` components to add validations on top of the form-level validators:\n\n```tsx {8-16} title=\"Field-level validators\"\nimport { Field } from '@base-ui/react/field';\nimport { useForm } from '@tanstack/react-form';\n\nconst form = useForm();\n\n<form.Field\n  name=\"username\"\n  validators={{\n    onChangeAsync: async ({ value: username }) => {\n      const result = await fetch(\n        /* check the availability of a username from an external API */\n      );\n\n      return result.success ? undefined : `${username} is not available.`\n    }\n  }}\n  children={(field) => (\n    <Field.Root name={field.name} /* forward the field props */ />\n  )}\n>\n```\n\n### Submitting data\n\nTo submit the form:\n\n1. Pass a submit handler function to the `onSubmit` parameter of `useForm`.\n2. Call `form.handleSubmit()` from an event handler such as form `onSubmit` or `onClick` on a button.\n\n```tsx {4-7, 13, 17} title=\"Form submission handler\" \"form.handleSubmit()\"\nimport { useForm } from '@tanstack/react-form';\n\nconst form = useForm({\n  onSubmit: async ({ value: formValues }) => {\n    /* prettier-ignore */\n    await fetch(/* POST the `formValues` to an external API */);\n  },\n});\n\n<form\n  onSubmit={(event) => {\n    event.preventDefault();\n    form.handleSubmit();\n  }}\n>\n  {/* form fields */}\n  <button type=\"submit\">Submit</button>\n</form>;\n```\n\nexport const metadata = {\n  keywords: [\n    'Base UI Forms',\n    'React Accessible Forms',\n    'Field Validation Handbook',\n    'React Hook Form Integration',\n    'TanStack Form Guide',\n    'Constraint Validation API',\n    'Handbook Forms',\n    'Form State',\n    'Form Library',\n    'Form Handling',\n    'Input Validation',\n    'Error Messages',\n    'Form Submission',\n    'Accessible Forms',\n    'ARIA Forms',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/page.mdx",
    "content": "# Handbook\n\n[//]: # 'This section is autogenerated, but the following list order, title, and [Tag]s can be modified, but nothing within the parentheses.'\n\n- Styling - ([Outline](#styling), [Contents](./styling/page.mdx))\n- Animation - ([Outline](#animation), [Contents](./animation/page.mdx))\n- Composition - ([Outline](#composition), [Contents](./composition/page.mdx))\n- Customization - ([Outline](#customization), [Contents](./customization/page.mdx))\n- Forms - ([Outline](#forms), [Contents](./forms/page.mdx))\n- TypeScript - ([Outline](#typescript), [Contents](./typescript/page.mdx))\n- [llms.txt](/llms.txt) [External]\n\n[//]: # 'This section is autogenerated, DO NOT EDIT AFTER THIS LINE, run: pnpm docs:validate \"(docs)/react/handbook\"'\n\n## Styling\n\nA guide to styling Base UI components with your preferred styling engine.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: Base UI Styling, Tailwind Base UI, CSS Modules Base UI, CSS-in-JS Base UI, Style Hooks Data Attributes, Unstyled React Components, Handbook Styling, Headless UI Styling, Theming, Custom Styles, Utility First CSS, Headless Components Styling\n- Sections:\n  - Style hooks\n    - CSS classes\n    - Data attributes\n    - CSS variables\n    - Style prop\n  - Tailwind CSS\n  - CSS Modules\n  - CSS-in-JS\n\n</details>\n\n[Read more](./styling/page.mdx)\n\n## Animation\n\nA guide to animating Base UI components.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: Base UI Animation, React Component Animations, CSS Transition Guide, Motion Framer Integration, Animation Data Attributes, Handbook Animation, Spring Animations, Enter Exit Animations, Transition Hooks, Animated Components, Keyframe Animations\n- Sections:\n  - CSS transitions\n  - CSS animations\n  - JavaScript animations\n    - Animating components unmounted from DOM when closed with Motion\n    - Animating components kept in DOM when closed with Motion\n    - Animating Select component with Motion\n    - Manual unmounting\n\n</details>\n\n[Read more](./animation/page.mdx)\n\n## Composition\n\nA guide to composing Base UI components with your own React components.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: Base UI Composition, React Render Prop, Compose Headless Components, Custom Trigger Render, Nested Render Props, Composition Handbook, Component Composition, Render Props Pattern, Compound Components, Polymorphic Components, As Prop, Custom Elements, Wrapper Components\n- Sections:\n  - Composing custom React components\n  - Composing multiple components\n  - Changing the default rendered element\n  - Render function\n\n</details>\n\n[Read more](./composition/page.mdx)\n\n## Customization\n\nA guide to customizing the behavior of Base UI components.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: Base UI Customization, React eventDetails, Control Base UI State, Cancel Base UI Events, Allow Propagation Tooltip, Customization Handbook, Event Handlers, Controlled Components, Uncontrolled Components, Component Behavior, Event Callbacks, State Management, Component Props, Override Behavior\n- Sections:\n  - Base UI events\n    - Canceling a Base UI event\n    - Allowing propagation of the DOM event\n  - Preventing Base UI from handling a React event\n  - Controlling components with state\n\n</details>\n\n[Read more](./customization/page.mdx)\n\n## Forms\n\nA guide to building forms with Base UI components.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: Base UI Forms, React Accessible Forms, Field Validation Handbook, React Hook Form Integration, TanStack Form Guide, Constraint Validation API, Handbook Forms, Form State, Form Library, Form Handling, Input Validation, Error Messages, Form Submission, Accessible Forms, ARIA Forms\n- Sections:\n  - Naming form controls\n    - Input controls\n    - Trigger-based controls\n    - Fallback\n    - Describing the control\n    - Labeling control groups\n  - Building form fields\n  - Submitting data\n  - Constraint validation\n  - Custom validation\n  - Server-side validation\n  - Displaying errors\n  - React Hook Form\n    - Initialize the form\n    - Integrate components\n    - Field validation\n    - Submitting data\n  - TanStack Form\n    - Initialize the form\n    - Integrate components\n    - Form validation\n    - Field validation\n    - Submitting data\n\n</details>\n\n[Read more](./forms/page.mdx)\n\n## TypeScript\n\nA guide to using TypeScript with Base UI.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: Base UI TypeScript, Component Props Namespace, Combobox ChangeEventDetails, Popover Positioner State, Toast Actions Ref Types, Use Render Component Props, Handbook TypeScript, Type Definitions, Type Safety, Generics, Component Types, Props Types, Event Types, Ref Types, Type Inference, Strongly Typed Components\n- Sections:\n  - Namespaces\n    - Props\n    - State\n    - Events\n    - Other accessible types\n\n</details>\n\n[Read more](./typescript/page.mdx)\n\n[//]: # 'The above section is autogenerated, but the remainder of the file can be modified.'\n\nexport const metadata =\n  /** @type {import('@mui/internal-docs-infra/createSitemap/types').NextMetadata} */ ({\n    robots: {\n      index: false,\n    },\n    other: {\n      audience: 'private',\n    },\n  });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/styling/page.mdx",
    "content": "# Styling\n\n<Subtitle>A guide to styling Base UI components with your preferred styling engine.</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"Learn how to style Base UI components with your preferred styling engine.\"\n/>\n\nBase UI components are unstyled, don't bundle CSS, and are compatible with Tailwind, CSS Modules, CSS-in-JS, or any other styling solution you prefer.\nYou retain total control of your styling layer.\n\n## Style hooks\n\n### CSS classes\n\nComponents that render an HTML element accept a `className` prop to style the element with CSS classes.\n\n```tsx title=\"switch.tsx\"\n<Switch.Thumb className=\"SwitchThumb\" />\n```\n\nThe prop can also be passed a function that takes the component's state as an argument.\n\n```tsx title=\"switch.tsx\"\n<Switch.Thumb className={(state) => (state.checked ? 'checked' : 'unchecked')} />\n```\n\n### Data attributes\n\nComponents provide data attributes designed for styling their states. For example, [Switch](/react/components/switch) can be styled using its `[data-checked]` and `[data-unchecked]` attributes, among others.\n\n```css title=\"switch.css\"\n.SwitchThumb[data-checked] {\n  background-color: green;\n}\n```\n\n### CSS variables\n\nComponents expose CSS variables to aid in styling, often containing dynamic numeric values to be used in sizing or transform calculations. For example, [Popover](/react/components/popover) exposes CSS variables on its `Popup` component like `--available-height` and `--anchor-width`.\n\n```css title=\"popover.css\"\n.Popup {\n  max-height: var(--available-height);\n}\n```\n\nCheck out each component's API reference for a complete list of available data attributes and CSS variables.\n\n### Style prop\n\nComponents that render an HTML element accept a `style` prop to style the element with a CSS object.\n\n```tsx title=\"switch.tsx\"\n<Switch.Thumb style={{ height: '100px' }} />\n```\n\nThe prop also accepts a function that takes the component's state as an argument.\n\n```tsx title=\"switch.tsx\"\n<Switch.Thumb style={(state) => ({ color: state.checked ? 'red' : 'blue' })} />\n```\n\n## Tailwind CSS\n\nApply Tailwind CSS classes to each part via the `className` prop.\n\n```tsx title=\"menu.tsx\"\nimport { Menu } from '@base-ui/react/menu';\n\nexport default function ExampleMenu() {\n  return (\n    <Menu.Root>\n      <Menu.Trigger className=\"flex h-10 items-center justify-center gap-1.5 rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-normal text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100 data-[popup-open]:bg-gray-100\">\n        Song\n      </Menu.Trigger>\n      <Menu.Portal>\n        <Menu.Positioner className=\"outline-none\" sideOffset={8}>\n          <Menu.Popup className=\"origin-[var(--transform-origin)] rounded-md bg-[canvas] py-1 text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 transition-[transform,scale,opacity] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300\">\n            <Menu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-none select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-sm data-[highlighted]:before:bg-gray-900\">\n              Add to Library\n            </Menu.Item>\n            <Menu.Item className=\"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-none select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-sm data-[highlighted]:before:bg-gray-900\">\n              Add to Playlist\n            </Menu.Item>\n          </Menu.Popup>\n        </Menu.Positioner>\n      </Menu.Portal>\n    </Menu.Root>\n  );\n}\n```\n\n## CSS Modules\n\nApply custom CSS classes to each part via the `className` prop.\nThen style those classes in a CSS Modules file.\n\n```tsx title=\"menu.tsx\"\nimport { Menu } from '@base-ui/react/menu';\nimport styles from './menu.module.css';\n\nexport default function ExampleMenu() {\n  return (\n    <Menu.Root>\n      <Menu.Trigger className={styles.Button}>Song</Menu.Trigger>\n      <Menu.Portal>\n        <Menu.Positioner className={styles.Positioner} sideOffset={8}>\n          <Menu.Popup className={styles.Popup}>\n            <Menu.Item className={styles.Item}>Add to Library</Menu.Item>\n            <Menu.Item className={styles.Item}>Add to Playlist</Menu.Item>\n          </Menu.Popup>\n        </Menu.Positioner>\n      </Menu.Portal>\n    </Menu.Root>\n  );\n}\n```\n\n## CSS-in-JS\n\nWrap each component part and apply styles, then assemble your styled components.\n\n```tsx title=\"menu.tsx\"\nimport { Menu } from '@base-ui/react/menu';\nimport styled from '@emotion/styled';\n\nconst StyledMenuTrigger = styled(Menu.Trigger)`\n  // Button styles\n`;\n\nconst StyledMenuPositioner = styled(Menu.Positioner)`\n  // Positioner styles\n`;\n\nconst StyledMenuPopup = styled(Menu.Popup)`\n  // Popup styles\n`;\n\nconst StyledMenuItem = styled(Menu.Item)`\n  // Menu item styles\n`;\n\nconst MenuExample = () => (\n  <Menu.Root>\n    <StyledMenuTrigger>Song</StyledMenuTrigger>\n    <Menu.Portal>\n      <StyledMenuPositioner>\n        <StyledMenuPopup>\n          <StyledMenuItem>Add to Library</StyledMenuItem>\n          <StyledMenuItem>Add to Playlist</StyledMenuItem>\n        </StyledMenuPopup>\n      </StyledMenuPositioner>\n    </Menu.Portal>\n  </Menu.Root>\n);\n\nexport default MenuExample;\n```\n\nexport const metadata = {\n  keywords: [\n    'Base UI Styling',\n    'Tailwind Base UI',\n    'CSS Modules Base UI',\n    'CSS-in-JS Base UI',\n    'Style Hooks Data Attributes',\n    'Unstyled React Components',\n    'Handbook Styling',\n    'Headless UI Styling',\n    'Theming',\n    'Custom Styles',\n    'Utility First CSS',\n    'Headless Components Styling',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/handbook/typescript/page.mdx",
    "content": "# TypeScript\n\n<Subtitle>A guide to using TypeScript with Base UI.</Subtitle>\n<Meta name=\"description\" content=\"A guide to using TypeScript with Base UI.\" />\n\n## Namespaces\n\nBase UI uses namespaces to organize types. Every component has two core interfaces:\n\n- `Props` (such as `Tooltip.Root.Props`)\n- `State` (such as `Tooltip.Root.State`)\n\n### Props\n\nWhen creating wrapping components, you can use the `Props` type to accept all of the underlying Base UI props for the component.\n\n```tsx title=\"Prop types\"\nimport { Tooltip } from '@base-ui/react/tooltip';\n\nfunction MyTooltip(props: Tooltip.Root.Props) {\n  return <Tooltip.Root {...props} />;\n}\n```\n\n### State\n\nThe `State` type is the internal state of the component.\nFor example, `Positioner` components (such as `<Popover.Positioner>`) have state that describes the position of the element in relation to their anchor.\n\n```tsx title=\"Positioner state\"\nfunction renderPositioner(props: Popover.Positioner.Props, state: Popover.Positioner.State) {\n  return (\n    <div {...props}>\n      <ul>\n        <li>The popover is {state.open ? 'open' : 'closed'}</li>\n        <li>I am on the {state.side} side of the anchor</li>\n        <li>I am aligned at the {state.align} of the side</li>\n        <li>The anchor is {state.anchorHidden ? 'hidden' : 'visible'}</li>\n      </ul>\n      {props.children}\n    </div>\n  );\n}\n\n<Popover.Positioner render={renderPositioner} />;\n```\n\n### Events\n\nTypes relating to custom Base UI events are also exported on component parts' namespaces.\n\n- `ChangeEventDetails` (such as `Combobox.Root.ChangeEventDetails`) is the object passed to change handlers like `onValueChange` and `onOpenChange`.\n- `ChangeEventReason` (such as `Combobox.Root.ChangeEventReason`) is the union of possible reason strings for a change event.\n\n```tsx title=\"Change event types\"\nfunction onValueChange(value: string, eventDetails: Combobox.Root.ChangeEventDetails) {\n  console.log(value, eventDetails);\n}\n\nfunction onOpenChange(open: boolean, eventDetails: Combobox.Root.ChangeEventDetails) {\n  console.log(open, eventDetails);\n}\n```\n\n### Other accessible types\n\nDepending on the component API, other types are also exported on component parts or utility functions.\nThe following list is non-exhaustive, and each of these are documented where necessary.\n\n- Popups have an `actionsRef` prop to access imperative methods. For example, `Menu.Root.Actions` gives access to the shape of the `actionsRef` object prop on `<Menu.Root>`.\n- The `toast` object on `<Toast.Root>` is a complex object with many properties. `Toast.Root.ToastObject` gives access to this interface.\n- Components that have a `render` prop have an extended `React.ComponentProps` type, enhanced with a `render` prop. The `useRender.ComponentProps` type on the function gives access to this interface.\n\nexport const metadata = {\n  keywords: [\n    'Base UI TypeScript',\n    'Component Props Namespace',\n    'Combobox ChangeEventDetails',\n    'Popover Positioner State',\n    'Toast Actions Ref Types',\n    'Use Render Component Props',\n    'Handbook TypeScript',\n    'Type Definitions',\n    'Type Safety',\n    'Generics',\n    'Component Types',\n    'Props Types',\n    'Event Types',\n    'Ref Types',\n    'Type Inference',\n    'Strongly Typed Components',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/overview/about/page.mdx",
    "content": "# About Base UI\n\n<Subtitle>An open-source React component library for building accessible user interfaces.</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"An overview of Base UI, providing information on its history, team, and goals.\"\n/>\n\nFrom the creators of Radix, Material UI, and Floating UI, Base UI is an unstyled React component library for building accessible user interfaces.\nOur focus is on accessibility, performance, and developer experience.\nOur goal is to provide a complete set of open-source UI components, with a delightful developer experience, in a sustainable way.\n\n## Features\n\n### Headless\n\nBase UI components are unstyled, don't bundle CSS, and don't prescribe a styling solution.\nYou retain complete control over your application's CSS layer.\nBase UI is compatible with Tailwind, CSS Modules, plain CSS, CSS-in-JS, or any other styling engine you prefer.\n\n### Accessible\n\nPoor accessibility can make your application difficult to navigate for all users, not just for users with disabilities.\nAccessibility is our primary focus.\nBase UI components adhere to [WAI-ARIA design patterns](https://www.w3.org/WAI/ARIA/apg/patterns/) and are tested on a wide range of platforms, devices, browsers, screen readers, and other environments.\n\n### Composable\n\nComponent APIs are fully open, so you have direct access to each node, you can easily add or remove parts, and you can wrap them however you prefer.\n\n## Team\n\n- **Colm Tuite** (Radix) [@colmtuite](https://x.com/colmtuite)\n- **James Nelson** (Floating UI) [@atomiksdev](https://x.com/atomiksdev)\n- **Michał Dudak** (Material UI) [@michaldudak](https://x.com/michaldudak)\n- **Marija Najdova** (Material UI + Fluent UI) [@marijanajdova](https://x.com/marijanajdova)\n- **Albert Yu** (Material UI) [@mj12albert](https://github.com/mj12albert)\n- **Lukas Tyla** (Material UI) [@LukasTy](https://github.com/LukasTy)\n\n## Browser support\n\nBase UI supports all modern browsers that implement features marked as [Baseline Widely Available](https://web.dev/baseline) at the time of the last major version release.\nThis means the features we use have been supported across major browsers for at least 30 months, ensuring broad compatibility and stability.\n\nFor the full list of supported browsers, refer to our [.browserslistrc](https://github.com/mui/base-ui/blob/master/.browserslistrc).\n\n## React versions\n\nBase UI supports React 17 and newer versions.\n\n## Community\n\n### GitHub\n\nBase UI is an open-source project.\nIf you want to file a bug report or contribute, visit our [GitHub](https://github.com/mui/base-ui).\n\n### Discord\n\nFor community support, questions, and tips, join our [Discord](https://base-ui.com/r/discord).\n\n### X\n\nThe best way to stay up-to-date on new releases and announcements is by following [Base UI on X](https://x.com/base_ui).\n\n### Bluesky\n\nWe're also on [Bluesky](https://bsky.app/profile/base-ui.com).\n\nexport const metadata = {\n  keywords: [\n    'About Base UI',\n    'Base UI Team',\n    'Headless React Components',\n    'Accessible React Library',\n    'Open Source UI Components',\n    'Base UI Community',\n    'Base UI Overview',\n    'Unstyled Components',\n    'Component Library',\n    'Headless UI Library',\n    'React UI Toolkit',\n    'MUI Team',\n    'Radix Alternative',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/overview/accessibility/page.mdx",
    "content": "# Accessibility\n\n<Subtitle>Learn how to make the most of Base UI's accessibility features and guidelines.</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"Learn how to make the most of Base UI's accessibility features and guidelines.\"\n/>\n\nAccessibility is a top priority for Base UI.\nBase UI components handle many complex accessibility details including ARIA attributes, role attributes, pointer interactions, keyboard navigation, and focus management.\nThe goal is to provide an accessible user experience out of the box, with intuitive APIs for configuration.\n\nThis page highlights some of the key accessibility features of Base UI, as well as some ways you need to augment the library, to ensure that your application is accessible to everyone.\n\n## Keyboard navigation\n\nBase UI components adhere to the [WAI-ARIA Authoring Practices](https://www.w3.org/WAI/ARIA/apg/) to provide basic keyboard accessibility out of the box.\nThis is critical for users who have difficulty using a pointer device, but it's also important for users who prefer navigating with a keyboard or other input mode.\n\nMany components provide support for arrow keys, alphanumeric keys, <kbd>Home</kbd>, <kbd>End</kbd>, <kbd>Enter</kbd>, and <kbd>Esc</kbd>.\n\n## Focus management\n\nBase UI components manage focus automatically following a user interaction.\nAdditionally, some components provide props like `initialFocus` and `finalFocus`, to configure focus management.\n\nWhile Base UI components manage focus, it's the developer's responsibility to visually indicate focus.\nThis is typically handled by styling the `:focus` or `:focus-visible` CSS pseudo-classes.\nWCAG provides [guidelines on focus appearance](https://www.w3.org/WAI/WCAG22/Understanding/focus-appearance).\n\n## Color contrast\n\nWhen styling elements, it's important to meet the minimum requirements for color contrast between each foreground element and its corresponding background element.\nUnless your application has strict requirements around compliance with current standards, consider adhering to [APCA](https://apcacontrast.com/), which is slated to become the new standard in WCAG 3.\n\n## Accessible labels\n\nBase UI provides components like Form, Input, Field, Fieldset to automatically associate form controls. Additionally, you can use the native HTML `<label>` element to provide context to corresponding inputs.\n\nMost applications present custom controls that require accessible names provided by markup features such as `alt`, `aria-label`, or `aria-labelledby`.\nWAI-ARIA provides guidelines on [providing accessible names](https://www.w3.org/TR/wai-aria-1.2/#namecalculation) to custom controls.\n\n## Testing\n\nBase UI components are tested on a broad spectrum of browsers, devices, platforms, screen readers, and environments.\n\nexport const metadata = {\n  keywords: [\n    'Base UI Accessibility',\n    'React Accessibility Guidelines',\n    'Keyboard Navigation Base UI',\n    'Focus Management Tips',\n    'Color Contrast Guidance',\n    'Accessible Labels Base UI',\n    'Accessibility Overview',\n    'WCAG Compliance',\n    'ARIA Attributes',\n    'A11y',\n    'Screen Reader Support',\n    'Accessible UI',\n    'Inclusive Design',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/overview/page.mdx",
    "content": "# Overview\n\n[//]: # 'This section is autogenerated, but the following list order, title, and [Tag]s can be modified, but nothing within the parentheses.'\n\n- Quick start - ([Outline](#quick-start), [Contents](./quick-start/page.mdx))\n- Accessibility - ([Outline](#accessibility), [Contents](./accessibility/page.mdx))\n- Releases - ([Outline](#releases), [Contents](./releases/page.mdx))\n- About Base UI - ([Outline](#about-base-ui), [Contents](./about/page.mdx))\n\n[//]: # 'This section is autogenerated, DO NOT EDIT AFTER THIS LINE, run: pnpm docs:validate \"(docs)/react/overview\"'\n\n## Quick start\n\nA quick guide to getting started with Base UI.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: Base UI Quick Start, Install Base UI, React Portal Setup, iOS Safari Backdrop Fix, Popover Assembly Example, llms.txt Link, Getting Started Guide, npm Install, Setup Guide, Installation Guide, Getting Started Tutorial, First Steps, Beginner Guide\n- Sections:\n  - Install the library\n  - Set up\n    - Portals\n    - iOS 26+ Safari\n  - Assemble a component\n  - Next steps\n  - Working with LLMs\n\n</details>\n\n[Read more](./quick-start/page.mdx)\n\n## Accessibility\n\nLearn how to make the most of Base UI's accessibility features and guidelines.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: Base UI Accessibility, React Accessibility Guidelines, Keyboard Navigation Base UI, Focus Management Tips, Color Contrast Guidance, Accessible Labels Base UI, Accessibility Overview, WCAG Compliance, ARIA Attributes, A11y, Screen Reader Support, Accessible UI, Inclusive Design\n- Sections:\n  - Keyboard navigation\n  - Focus management\n  - Color contrast\n  - Accessible labels\n  - Testing\n\n</details>\n\n[Read more](./accessibility/page.mdx)\n\n## Releases\n\nChangelogs for each Base UI release.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: Base UI Releases, Base UI Changelog, Release Notes Beta, Breaking Changes Base UI, Component Updates History, Headless React Release Log, Version History, Changelog History, Upgrade Guide, Migration Guide, What's New, Release Updates\n- Sections:\n  - Canary Releases\n  - Full Release Notes\n\n</details>\n\n[Read more](./releases/page.mdx)\n\n## About Base UI\n\nAn open-source React component library for building accessible user interfaces.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: About Base UI, Base UI Team, Headless React Components, Accessible React Library, Open Source UI Components, Base UI Community, Base UI Overview, Unstyled Components, Component Library, Headless UI Library, React UI Toolkit, MUI Team, Radix Alternative\n- Sections:\n  - Features\n    - Headless\n    - Accessible\n    - Composable\n  - Team\n  - Browser support\n  - React versions\n  - Community\n    - GitHub\n    - Discord\n    - X\n    - Bluesky\n\n</details>\n\n[Read more](./about/page.mdx)\n\n[//]: # 'The above section is autogenerated, but the remainder of the file can be modified.'\n\nexport const metadata =\n  /** @type {import('@mui/internal-docs-infra/createSitemap/types').NextMetadata} */ ({\n    robots: {\n      index: false,\n    },\n    other: {\n      audience: 'private',\n    },\n  });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/overview/quick-start/page.mdx",
    "content": "# Quick start\n\n<Subtitle>A quick guide to getting started with Base UI.</Subtitle>\n<Meta name=\"description\" content=\"A quick guide to getting started with Base UI.\" />\n\nimport { InstallationBlock } from 'docs/src/components/InstallationBlock';\n\n## Install the library\n\nInstall Base UI using a package manager.\n\n<InstallationBlock package=\"@base-ui/react\" />\n\nAll components are included in a single package. Base UI is tree-shakable, so your app bundle will contain only the components that you actually use.\n\n## Set up\n\n### Portals\n\nBase UI uses portals for components that render popups, such as Dialog and Popover.\nTo make portaled components always appear on top of the entire page, add the following style to your application layout root:\n\n```tsx title=\"layout.tsx\"\n<body>\n  <div className=\"root\">\n    {/* prettier-ignore */}\n    {children}\n  </div>\n</body>\n```\n\n```css title=\"styles.css\"\n.root {\n  isolation: isolate;\n}\n```\n\nThis style creates a separate stacking context for your application's `.root` element.\nThis way, popups always appear above the page contents, and any `z-index` property in your styles won't interfere with them.\n\n### iOS 26+ Safari\n\nStarting with iOS 26, Safari allows content beneath the UI chrome to be visible. Backdrops such as those used by dialogs must use `position: absolute` instead of `position: fixed` to cover the entire visual viewport. For this to work after the page is scrolled, the following style must be added to your global styles:\n\n```css title=\"styles.css\"\nbody {\n  position: relative;\n}\n```\n\n## Assemble a component\n\nThis demo shows you how to import a [Popover](/react/components/popover) component, assemble its parts, and apply styles.\nThere are examples for both Tailwind and CSS Modules below, but since Base UI is unstyled, you can use CSS-in-JS, plain CSS, or any other styling solution you prefer.\n\nimport { DemoPopoverHero } from '../../components/popover/demos/hero';\n\n<DemoPopoverHero />\n\n## Next steps\n\nThis walkthrough outlines the basics of putting together a Base UI component.\nBrowse the rest of the documentation to see what components are available in the library and how to use them.\n\n## Working with LLMs\n\nFor those of you working with LLMs, each docs page has a \"View as Markdown\" link at the top, which can be shared with AI chat assistants to help them understand Base UI concepts and component APIs.\n\nAdditionally, there is an [\"llms.txt\"](/llms.txt) link in the \"Handbook\" section of the navigation sidebar, which you can feed to AI chat assistants to help them navigate the docs.\n\nexport const metadata = {\n  keywords: [\n    'Base UI Quick Start',\n    'Install Base UI',\n    'React Portal Setup',\n    'iOS Safari Backdrop Fix',\n    'Popover Assembly Example',\n    'llms.txt Link',\n    'Getting Started Guide',\n    'npm Install',\n    'Setup Guide',\n    'Installation Guide',\n    'Getting Started Tutorial',\n    'First Steps',\n    'Beginner Guide',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/overview/releases/page.mdx",
    "content": "# Releases\n\n<Subtitle>Changelogs for each Base UI release.</Subtitle>\n\nimport { ReleaseTimeline } from 'docs/src/components/ReleaseTimeline';\n\n<ReleaseTimeline />\n\n## Canary Releases\n\nA canary release is published for every master commit and pull request. Install one by using the corresponding pkg.pr.new URL:\n\n```bash title=\"Terminal\"\n# Install by master commit hash\nnpm i https://pkg.pr.new/@base-ui/react@ad745f1\n\n# Install by PR number\nnpm i https://pkg.pr.new/@base-ui/react@3713\n```\n\nYour `package.json` will then reference the pkg.pr.new URL:\n\n```json title=\"package.json\"\n{\n  \"dependencies\": {\n    \"@base-ui/react\": \"https://pkg.pr.new/@base-ui/react@...\"\n  }\n}\n```\n\nCanary releases may contain breaking changes. Check the associated pull requests on GitHub for details.\n\n## Full Release Notes\n\nYou can see the [full changelog on GitHub](https://github.com/mui/base-ui/blob/master/CHANGELOG.md).\n\nexport const metadata = {\n  keywords: [\n    'Base UI Releases',\n    'Base UI Changelog',\n    'Release Notes Beta',\n    'Breaking Changes Base UI',\n    'Component Updates History',\n    'Headless React Release Log',\n    'Version History',\n    'Changelog History',\n    'Upgrade Guide',\n    'Migration Guide',\n    \"What's New\",\n    'Release Updates',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/overview/releases/v1-0-0/page.mdx",
    "content": "# v1.0.0\n\n<Subtitle>Dec 11, 2025</Subtitle>\n<Meta name=\"description\" content=\"v1.0.0 release notes. Dec 11, 2025.\" />\n\n## General changes\n\n- **Breaking change:** Rename packages to use the @base-ui org.<br />\n  The package name has changed from `@base-ui-components/react` to `@base-ui/react`.\n  ([#3462](https://github.com/mui/base-ui/pull/3462))\n\n## Combobox\n\n- Respect `itemToStringValue` for `onFormSubmit` ([#3441](https://github.com/mui/base-ui/pull/3441))\n- Add `null` as an option for the value prop ([#3488](https://github.com/mui/base-ui/pull/3488))\n\n## Menu\n\n- Fix submenu opens with 0 delay ([#3459](https://github.com/mui/base-ui/pull/3459))\n- Fix focus not returning to trigger on <kbd>Esc</kbd> while pointer rests on popup ([#3482](https://github.com/mui/base-ui/pull/3482))\n- Fix always `null` open method ([#3486](https://github.com/mui/base-ui/pull/3486))\n- Allow side axis fallback for submenus by default ([#3470](https://github.com/mui/base-ui/pull/3470))\n\n## Navigation Menu\n\n- Fix mount transitions on `Positioner` in Firefox ([#3424](https://github.com/mui/base-ui/pull/3424))\n\n## Number Field\n\n- Fix multiple scrub area support ([#3471](https://github.com/mui/base-ui/pull/3471))\n\n## Popover\n\n- Fix mount transitions on `Positioner` in Firefox ([#3424](https://github.com/mui/base-ui/pull/3424))\n- Fix skipped viewport transitions ([#3453](https://github.com/mui/base-ui/pull/3453))\n\n## Select\n\n- Respect `itemToStringValue` for `onFormSubmit` ([#3441](https://github.com/mui/base-ui/pull/3441))\n- Add `null` as an option for the value prop ([#3488](https://github.com/mui/base-ui/pull/3488))\n\n## Tabs\n\n- Fix indicator positioning in transformed containers ([#3439](https://github.com/mui/base-ui/pull/3439))\n- Do not initially select a disabled tab ([#3475](https://github.com/mui/base-ui/pull/3475))\n\n## Toast\n\n- Fix `flushSync` dev error when toast is added ([#3443](https://github.com/mui/base-ui/pull/3443))\n- Fix `<Toast.Close>` emitting `aria-hidden` warning on click ([#3469](https://github.com/mui/base-ui/pull/3469))\n\n## Toggle Group\n\n- More permissive towards falsy toggle values ([#3477](https://github.com/mui/base-ui/pull/3477))\n\n## Tooltip\n\n- Fix mount transitions on `Positioner` in Firefox ([#3424](https://github.com/mui/base-ui/pull/3424))\n- Fix shared tooltip closing with trigger gaps ([#3452](https://github.com/mui/base-ui/pull/3452))\n- Fix skipped viewport transitions ([#3453](https://github.com/mui/base-ui/pull/3453))\n"
  },
  {
    "path": "docs/src/app/(docs)/react/overview/releases/v1-0-0-alpha-4/page.mdx",
    "content": "# v1.0.0-alpha.4\n\n<Subtitle>Dec 17, 2024</Subtitle>\n<Meta name=\"description\" content=\"v1.0.0-alpha.4 release notes. Dec 17, 2024.\" />\n\nPublic alpha launch 🐣 Merry Xmas! 🎁\n"
  },
  {
    "path": "docs/src/app/(docs)/react/overview/releases/v1-0-0-alpha-5/page.mdx",
    "content": "# v1.0.0-alpha.5\n\n<Subtitle>Jan 10, 2025</Subtitle>\n<Meta name=\"description\" content=\"v1.0.0-alpha.5 release notes. Jan 10, 2025.\" />\n\n## AlertDialog\n\n- **Breaking change:** Require `Portal` part.\n  The AlertDialog must explicitly include the Portal part wrapping the Popup.\n  The `keepMounted` prop was removed from the Popup.\n  It's only present on the Portal part.\n  [#1222](https://github.com/mui/base-ui/pull/1222)\n- Don't call `onNestedDialogOpen` when unmounting a closed nested dialog [#1280](https://github.com/mui/base-ui/pull/1280)\n- Fix the nesting of different dialogs [#1167](https://github.com/mui/base-ui/pull/1167)\n- Remove `useFloating` call from the Popup [#1300](https://github.com/mui/base-ui/pull/1300)\n- Set `pointer-events` on `InternalBackdrop` based on `open` state [#1221](https://github.com/mui/base-ui/pull/1221)\n- Use internal backdrop for pointer modality [#1161](https://github.com/mui/base-ui/pull/1161)\n\n## Dialog\n\n- **Breaking change:** Require `Portal` part.\n  The Dialog must explicitly include the Portal part wrapping the Popup.\n  The `keepMounted` prop was removed from the Popup.\n  It's only present on the Portal part.\n  [#1222](https://github.com/mui/base-ui/pull/1222)\n- Don't call `onNestedDialogOpen` when unmounting a closed nested dialog [#1280](https://github.com/mui/base-ui/pull/1280)\n- Fix the nesting of different dialogs [#1167](https://github.com/mui/base-ui/pull/1167)\n- Remove `useFloating` call from the Popup [#1300](https://github.com/mui/base-ui/pull/1300)\n- Set `pointer-events` on `InternalBackdrop` based on `open` state [#1221](https://github.com/mui/base-ui/pull/1221)\n- Use internal backdrop for pointer modality [#1161](https://github.com/mui/base-ui/pull/1161)\n\n## Menu\n\n- **Breaking change:** Require `Portal` part.\n  The Menu must explicitly include the Portal part wrapping the Positioner.\n  The `keepMounted` prop was removed from the Positioner.\n  It's only present on the Portal part.\n  [#1222](https://github.com/mui/base-ui/pull/1222)\n- Apply `aria-hidden` to `Arrow` parts [#1196](https://github.com/mui/base-ui/pull/1196)\n- Fix `focusableWhenDisabled` components [#1313](https://github.com/mui/base-ui/pull/1313)\n- Fix `openOnHover` issues [#1191](https://github.com/mui/base-ui/pull/1191)\n- Fix closing the menu when clicking on checkboxitem/radioitem [#1301](https://github.com/mui/base-ui/pull/1301)\n- Fix <kbd>Enter</kbd> key preventDefault when rendering links [#1251](https://github.com/mui/base-ui/pull/1251)\n- Handle pseudo-element bounds in mouseup detection [#1250](https://github.com/mui/base-ui/pull/1250)\n- Set `pointer-events` on `InternalBackdrop` based on `open` state [#1221](https://github.com/mui/base-ui/pull/1221)\n- Use internal backdrop for pointer modality [#1161](https://github.com/mui/base-ui/pull/1161)\n\n## NumberField\n\n- Correctly handle quick touches [#1294](https://github.com/mui/base-ui/pull/1294)\n\n## Popover\n\n- **Breaking change:** Require `Portal` part.\n  The Popover must explicitly include the Portal part wrapping the Positioner.\n  The `keepMounted` prop was removed from the Positioner.\n  It's only present on the Portal part.\n  [#1222](https://github.com/mui/base-ui/pull/1222)\n- Apply `aria-hidden` to `Arrow` parts [#1196](https://github.com/mui/base-ui/pull/1196)\n- Fix PopoverTrigger and TooltipTrigger prop types [#1209](https://github.com/mui/base-ui/pull/1209)\n\n## PreviewCard\n\n- **Breaking change:** Require `Portal` part.\n  The PreviewCard must explicitly include the Portal part wrapping the Positioner.\n  The `keepMounted` prop was removed from the Positioner.\n  It's only present on the Portal part.\n  [#1222](https://github.com/mui/base-ui/pull/1222)\n- Apply `aria-hidden` to `Arrow` parts [#1196](https://github.com/mui/base-ui/pull/1196)\n- Use `FloatingPortalLite` [#1278](https://github.com/mui/base-ui/pull/1278)\n\n## Progress\n\n- Set zero width when value is zero [#1204](https://github.com/mui/base-ui/pull/1204)\n\n## ScrollArea\n\n- Differentiate `x`/`y` orientation `data-scrolling` [#1188](https://github.com/mui/base-ui/pull/1188)\n- Read `DirectionProvider` and use logical positioning CSS props [#1194](https://github.com/mui/base-ui/pull/1194)\n\n## Select\n\n- **Breaking change:** Require `Portal` part.\n  The Select must explicitly include the Portal part wrapping the Positioner.\n  The `keepMounted` prop was removed from the Positioner.\n  It's only present on the Portal part.\n  [#1222](https://github.com/mui/base-ui/pull/1222)\n- Allow `id` to be passed to trigger [#1174](https://github.com/mui/base-ui/pull/1174)\n- Fallback to standard positioning when pinch-zoomed in Safari [#1139](https://github.com/mui/base-ui/pull/1139)\n- Fix `focusableWhenDisabled` components [#1313](https://github.com/mui/base-ui/pull/1313)\n- Fix highlight flash on Safari [#1233](https://github.com/mui/base-ui/pull/1233)\n- Handle pseudo-element bounds in mouseup detection [#1250](https://github.com/mui/base-ui/pull/1250)\n- Use internal backdrop for pointer modality [#1161](https://github.com/mui/base-ui/pull/1161)\n\n## Separator\n\n- Support vertical orientation [#1304](https://github.com/mui/base-ui/pull/1304)\n\n## Slider\n\n- Ensure `onValueCommitted` is called with the same value as latest `onValueChange` [#1296](https://github.com/mui/base-ui/pull/1296)\n- Replace internal map with `Composite` metadata [#1082](https://github.com/mui/base-ui/pull/1082)\n- Set `position: relative` on range slider indicator [#1175](https://github.com/mui/base-ui/pull/1175)\n- Use un-rounded values to position thumbs [#1219](https://github.com/mui/base-ui/pull/1219)\n\n## Tabs\n\n- Expose width/height state in tabs indicator [#1288](https://github.com/mui/base-ui/pull/1288)\n\n## Tooltip\n\n- **Breaking change:** Require `Portal` part.\n  The Tooltip must explicitly include the Portal part wrapping the Positioner.\n  The `keepMounted` prop was removed from the Positioner.\n  It's only present on the Portal part.\n  [#1222](https://github.com/mui/base-ui/pull/1222)\n- Apply `aria-hidden` to `Arrow` parts [#1196](https://github.com/mui/base-ui/pull/1196)\n- Fix PopoverTrigger and TooltipTrigger prop types [#1209](https://github.com/mui/base-ui/pull/1209)\n- Use `FloatingPortalLite` [#1278](https://github.com/mui/base-ui/pull/1278)\n"
  },
  {
    "path": "docs/src/app/(docs)/react/overview/releases/v1-0-0-alpha-6/page.mdx",
    "content": "# v1.0.0-alpha.6\n\n<Subtitle>Feb 6, 2025</Subtitle>\n<Meta name=\"description\" content=\"v1.0.0-alpha.6 release notes. Feb 6, 2025.\" />\n\n## AlertDialog\n\n- `onOpenChangeComplete` prop ([#1305](https://github.com/mui/base-ui/pull/1305/))\n- Fix jump with `scroll-behavior` style ([#1343](https://github.com/mui/base-ui/pull/1343/))\n\n## Avatar\n\n- Add Avatar component ([#1210](https://github.com/mui/base-ui/pull/1210/))\n\n## Checkbox\n\n- Avoid applying `hidden` attr when `keepMounted=true` for indicators ([#1329](https://github.com/mui/base-ui/pull/1329/))\n\n## Dialog\n\n- Remove `modal={open}` state ([#1352](https://github.com/mui/base-ui/pull/1352/))\n- Support multiple non-nested modal backdrops ([#1327](https://github.com/mui/base-ui/pull/1327/))\n- Fix missing `id`s on Title and Description ([#1326](https://github.com/mui/base-ui/pull/1326/))\n- `onOpenChangeComplete` prop ([#1305](https://github.com/mui/base-ui/pull/1305/))\n- Fix jump with `scroll-behavior` style ([#1343](https://github.com/mui/base-ui/pull/1343/))\n\n## Field\n\n- Respect `validationMode` ([#1053](https://github.com/mui/base-ui/pull/1053/))\n- Add `filled` and `focused` style hooks ([#1341](https://github.com/mui/base-ui/pull/1341/))\n\n## Form\n\n- Fix focusing of invalid field controls on errors prop change ([#1364](https://github.com/mui/base-ui/pull/1364/))\n\n## Menu\n\n- Avoid applying `hidden` attr when `keepMounted=true` for indicators ([#1329](https://github.com/mui/base-ui/pull/1329/))\n- Support submenus with `openOnHover` prop ([#1338](https://github.com/mui/base-ui/pull/1338/))\n- Fix iPad detection when applying scroll lock ([#1342](https://github.com/mui/base-ui/pull/1342/))\n- `onOpenChangeComplete` prop ([#1305](https://github.com/mui/base-ui/pull/1305/))\n- Fix jump with `scroll-behavior` style ([#1343](https://github.com/mui/base-ui/pull/1343/))\n- Add `OffsetFunction` for `sideOffset` and `alignOffset` ([#1223](https://github.com/mui/base-ui/pull/1223/))\n- Ensure `keepMounted` is a private param on `Positioner` ([#1410](https://github.com/mui/base-ui/pull/1410/))\n\n## Popover\n\n- `onOpenChangeComplete` prop ([#1305](https://github.com/mui/base-ui/pull/1305/))\n- Add `OffsetFunction` for `sideOffset` and `alignOffset` ([#1223](https://github.com/mui/base-ui/pull/1223/))\n- Ensure `keepMounted` is a private param on `Positioner` ([#1410](https://github.com/mui/base-ui/pull/1410/))\n\n## PreviewCard\n\n- `onOpenChangeComplete` prop ([#1305](https://github.com/mui/base-ui/pull/1305/))\n- Add `OffsetFunction` for `sideOffset` and `alignOffset` ([#1223](https://github.com/mui/base-ui/pull/1223/))\n- Ensure `keepMounted` is a private param on `Positioner` ([#1410](https://github.com/mui/base-ui/pull/1410/))\n\n## Progress\n\n- Add `format` prop and `Value` component ([#1355](https://github.com/mui/base-ui/pull/1355/))\n\n## Radio\n\n- Avoid applying `hidden` attr when `keepMounted=true` for indicators ([#1329](https://github.com/mui/base-ui/pull/1329/))\n\n## Select\n\n- `onOpenChangeComplete` prop ([#1305](https://github.com/mui/base-ui/pull/1305/))\n- Fix jump with `scroll-behavior` style ([#1343](https://github.com/mui/base-ui/pull/1343/))\n- Add `OffsetFunction` for `sideOffset` and `alignOffset` ([#1223](https://github.com/mui/base-ui/pull/1223/))\n- Ensure `keepMounted` is a private param on `Positioner` ([#1410](https://github.com/mui/base-ui/pull/1410/))\n\n## Slider\n\n- Fix thumb positioning ([#1411](https://github.com/mui/base-ui/pull/1411/))\n\n## Tabs\n\n- Fix being able to activate a disabled tab ([#1359](https://github.com/mui/base-ui/pull/1359/))\n- Fix tabs activating incorrectly on non-primary button clicks ([#1318](https://github.com/mui/base-ui/pull/1318/))\n\n## Tooltip\n\n- `onOpenChangeComplete` prop ([#1305](https://github.com/mui/base-ui/pull/1305/))\n- Add `OffsetFunction` for `sideOffset` and `alignOffset` ([#1223](https://github.com/mui/base-ui/pull/1223/))\n- Ensure `keepMounted` is a private param on `Positioner` ([#1410](https://github.com/mui/base-ui/pull/1410/))\n"
  },
  {
    "path": "docs/src/app/(docs)/react/overview/releases/v1-0-0-alpha-7/page.mdx",
    "content": "# v1.0.0-alpha.7\n\n<Subtitle>Mar 20, 2025</Subtitle>\n<Meta name=\"description\" content=\"v1.0.0-alpha.7 release notes. Mar 20, 2025.\" />\n\n## Accordion\n\n- Fix `aria-labelledby` on accordion panel ([#1544](https://github.com/mui/base-ui/pull/1544/))\n\n## AlertDialog\n\n- Fix selection on outside press on Firefox with modal prop ([#1573](https://github.com/mui/base-ui/pull/1573/))\n- Fix non-interactive button disabled state ([#1473](https://github.com/mui/base-ui/pull/1473/))\n- `actionsRef` prop ([#1236](https://github.com/mui/base-ui/pull/1236/))\n\n## Avatar\n\n- Support cross origin in useImageLoadingStatus ([#1433](https://github.com/mui/base-ui/pull/1433/))\n- Add missing Avatar export ([#1428](https://github.com/mui/base-ui/pull/1428/))\n\n## Collapsible\n\n- Update props destructuring to fix Trigger disabled state ([#1469](https://github.com/mui/base-ui/pull/1469/))\n\n## Dialog\n\n- Fix selection on outside press on Firefox with modal prop ([#1573](https://github.com/mui/base-ui/pull/1573/))\n- Fix non-interactive button disabled state ([#1473](https://github.com/mui/base-ui/pull/1473/))\n- `actionsRef` prop ([#1236](https://github.com/mui/base-ui/pull/1236/))\n\n## Field\n\n- Fix `FieldControl` [data-filled] not reacting to external value changes ([#1565](https://github.com/mui/base-ui/pull/1565/))\n\n## Menu\n\n- Ensure submenu triggers respond to clicks when `openOnHover=false` ([#1583](https://github.com/mui/base-ui/pull/1583/))\n- Ensure `stickIfOpen` is reset to `true` correctly ([#1548](https://github.com/mui/base-ui/pull/1548/))\n- Fix selection on outside press on Firefox with modal prop ([#1573](https://github.com/mui/base-ui/pull/1573/))\n- Reset `hoverEnabled` state on close ([#1461](https://github.com/mui/base-ui/pull/1461/))\n- Fix prop merging issues ([#1445](https://github.com/mui/base-ui/pull/1445/))\n- Set `pointer-events: none` style on backdrops when hoverable ([#1351](https://github.com/mui/base-ui/pull/1351/))\n- `actionsRef` prop ([#1236](https://github.com/mui/base-ui/pull/1236/))\n\n## NumberField\n\n- Fix ScrubArea on Safari ([#1584])(https://github.com/mui/base-ui/pull/1584/))\n- Fix `large/smallStep` getting stuck ([#1578](https://github.com/mui/base-ui/pull/1578/))\n- Fix parse of numbers with spaces as thousands separators ([#1577](https://github.com/mui/base-ui/pull/1577/))\n- Prevent virtual cursor overlapping native one ([#1491](https://github.com/mui/base-ui/pull/1491/))\n- Fix disabled state on increment/decrement buttons ([#1462](https://github.com/mui/base-ui/pull/1462/))\n- Correct virtual cursor rendering ([#1484](https://github.com/mui/base-ui/pull/1484/))\n- Add `locale` prop ([#1488](https://github.com/mui/base-ui/pull/1488/))\n- Improve virtual cursor perf ([#1485](https://github.com/mui/base-ui/pull/1485/))\n\n## Popover\n\n- Ensure `stickIfOpen` is reset to `true` correctly ([#1548](https://github.com/mui/base-ui/pull/1548/))\n- Fix selection on outside press on Firefox with modal prop ([#1573](https://github.com/mui/base-ui/pull/1573/))\n- Set `pointer-events: none` style on backdrops when hoverable ([#1351](https://github.com/mui/base-ui/pull/1351/))\n- Fix non-interactive button disabled state ([#1473](https://github.com/mui/base-ui/pull/1473/))\n- `modal` prop ([#1459](https://github.com/mui/base-ui/pull/1459/))\n- `actionsRef` prop ([#1236](https://github.com/mui/base-ui/pull/1236/))\n\n## PreviewCard\n\n- Set `pointer-events: none` style on backdrops when hoverable ([#1351](https://github.com/mui/base-ui/pull/1351/))\n- `actionsRef` prop ([#1236](https://github.com/mui/base-ui/pull/1236/))\n\n## RadioGroup\n\n- Fix `Form`/`Field` validation integration ([#1448](https://github.com/mui/base-ui/pull/1448/))\n- Handle modifier keys ([#1529](https://github.com/mui/base-ui/pull/1529/))\n\n## Select\n\n- Fix selection on outside press on Firefox with modal prop ([#1573](https://github.com/mui/base-ui/pull/1573/))\n- `actionsRef` prop ([#1236](https://github.com/mui/base-ui/pull/1236/))\n- Improve `ScrollArrow` behavior ([#1564](https://github.com/mui/base-ui/pull/1564/))\n- Ensure switching controlled value to `null` updates `<Select.Value>` label ([#1561](https://github.com/mui/base-ui/pull/1561/))\n- Pass `value` as second argument to function children `<Select.Value>` ([#1562](https://github.com/mui/base-ui/pull/1562/))\n- Fix focus jump while hovering while navigating with keyboard ([#1563](https://github.com/mui/base-ui/pull/1563/))\n- Fix disabled state changing ([#1526](https://github.com/mui/base-ui/pull/1526/))\n\n## Slider\n\n- Fix thumb positioning when controlled value violates min/max/step ([#1541](https://github.com/mui/base-ui/pull/1541/))\n- Warn when `min` is not less than `max` ([#1475](https://github.com/mui/base-ui/pull/1475/))\n- Narrow the type of `value` in callbacks ([#1241](https://github.com/mui/base-ui/pull/1241/))\n\n## Tabs\n\n- Fix keyboard navigation involving disabled Tabs ([#1449](https://github.com/mui/base-ui/pull/1449/))\n- Handle modifier keys ([#1529](https://github.com/mui/base-ui/pull/1529/))\n\n## Toolbar\n\n- Add Toolbar components ([#1349](https://github.com/mui/base-ui/pull/1349/))\n\n## Tooltip\n\n- `actionsRef` prop ([#1236](https://github.com/mui/base-ui/pull/1236/))\n- Fix `Provider` `delay=0` not being respected ([#1416](https://github.com/mui/base-ui/pull/1416/))\n\n## useRender\n\n- Add public hook ([#1418](https://github.com/mui/base-ui/pull/1418/))\n- Refine docs and APIs ([#1551](https://github.com/mui/base-ui/pull/1551/))\n"
  },
  {
    "path": "docs/src/app/(docs)/react/overview/releases/v1-0-0-alpha-8/page.mdx",
    "content": "# v1.0.0-alpha.8\n\n<Subtitle>Apr 17, 2025</Subtitle>\n<Meta name=\"description\" content=\"v1.0.0-alpha.8 release notes. Apr 17, 2025.\" />\n\n## Accordion\n\n- Recalculate panel dimensions on layout resize ([#1704](https://github.com/mui/base-ui/pull/1704))\n- Rework animations and transitions ([#1601](https://github.com/mui/base-ui/pull/1601))\n\n## AlertDialog\n\n- **Breaking change:** Rename `data-has-nested-dialogs` to `data-nested-dialog-open` ([#1686](https://github.com/mui/base-ui/pull/1686))\n- Fix `onOpenChange` types for `event`/`reason` passing ([#1721](https://github.com/mui/base-ui/pull/1721))\n- Use consistent `inert` attr and map `[data-popup-open]` back to `open` ([#1650](https://github.com/mui/base-ui/pull/1650))\n- Fix text selection & right-clicks ([#1702](https://github.com/mui/base-ui/pull/1702))\n\n## CheckboxGroup\n\n- Parent checkbox/nested demos ([#1610](https://github.com/mui/base-ui/pull/1610))\n\n## Collapsible\n\n- Fix ForwardedRef type of CollapsiblePanel ([#1595](https://github.com/mui/base-ui/pull/1595))\n- Recalculate panel dimensions on layout resize ([#1704](https://github.com/mui/base-ui/pull/1704))\n- Rework animations and transitions ([#1601](https://github.com/mui/base-ui/pull/1601))\n\n## Dialog\n\n- **Breaking change:** Rename `data-has-nested-dialogs` to `data-nested-dialog-open` ([#1686](https://github.com/mui/base-ui/pull/1686))\n- **Breaking change:** Add new `trap-focus` value to `modal` prop.\n  Dialogs with `modal=false` no longer trap focus.\n  ([#1571](https://github.com/mui/base-ui/pull/1571))\n- Fix `onOpenChange` types for `event`/`reason` passing ([#1721](https://github.com/mui/base-ui/pull/1721))\n- Use consistent `inert` attr and map `[data-popup-open]` back to `open` ([#1650](https://github.com/mui/base-ui/pull/1650))\n- Fix text selection & right-clicks ([#1702](https://github.com/mui/base-ui/pull/1702))\n- Allow document to slide input into view on iOS when keyboard opens ([#1735](https://github.com/mui/base-ui/pull/1735))\n\n## Field\n\n- Fix forwarding of `name` and `disabled` props ([#1616](https://github.com/mui/base-ui/pull/1616))\n\n## Menu\n\n- Add missing item data attributes docs ([#1691](https://github.com/mui/base-ui/pull/1691))\n- Fix `inert` prop compatibility in React \\<19 ([#1618](https://github.com/mui/base-ui/pull/1618))\n- Fix stuck highlight on submenu trigger when submenu opens with keyboard ([#1698](https://github.com/mui/base-ui/pull/1698))\n- Fix `onOpenChange` types for `event`/`reason` passing ([#1721](https://github.com/mui/base-ui/pull/1721))\n- Use consistent `inert` attr and map `[data-popup-open]` back to `open` ([#1650](https://github.com/mui/base-ui/pull/1650))\n- Fix text selection & right-clicks ([#1702](https://github.com/mui/base-ui/pull/1702))\n\n## Meter\n\n- New Meter component ([#1435](https://github.com/mui/base-ui/pull/1435))\n\n## NumberField\n\n- Correct percentage parse handling ([#1676](https://github.com/mui/base-ui/pull/1676))\n- New `snapOnStep` prop ([#1560](https://github.com/mui/base-ui/pull/1560))\n\n## Popover\n\n- **Breaking change:** Add new `trap-focus` value to `modal` prop ([#1571](https://github.com/mui/base-ui/pull/1571))\n- Fix `inert` prop compatibility in React \\<19 ([#1618](https://github.com/mui/base-ui/pull/1618))\n- Fix `onOpenChange` types for `event`/`reason` passing ([#1721](https://github.com/mui/base-ui/pull/1721))\n- Use consistent `inert` attr and map `[data-popup-open]` back to `open` ([#1650](https://github.com/mui/base-ui/pull/1650))\n- Fix text selection & right-clicks ([#1702](https://github.com/mui/base-ui/pull/1702))\n\n## Progress\n\n- **Breaking change:** Add `Progress.Label` and `locale` prop.\n  The `getAriaLabel` prop was removed as `<Progress.Label>` should be used to provide an accessible name.\n  ([#1666](https://github.com/mui/base-ui/pull/1666))\n\n## Radio\n\n- Fix value forwarding and null handling ([#1697](https://github.com/mui/base-ui/pull/1697))\n\n## ScrollArea\n\n- **Breaking change:** Add `Content` part.\n  It is now required to include the `<ScrollArea.Content>` within `<ScrollArea.Viewport>` part when the content is horizontally scrollable.\n  ([#1607](https://github.com/mui/base-ui/pull/1607))\n- Handle visibility change and nesting ([#1598](https://github.com/mui/base-ui/pull/1598))\n- Correct thumb sizing with scrollbar margins ([#1606](https://github.com/mui/base-ui/pull/1606))\n\n## Select\n\n- **Breaking change:** Improve item highlight performance.\n  The highlighted state is now removed. It's not possible to customize the `data-highlighted` attribute anymore.\n  ([#1570](https://github.com/mui/base-ui/pull/1570))\n- Avoid double commit on value change ([#1597](https://github.com/mui/base-ui/pull/1597))\n- Reset `selectedIndex` when set to `null` ([#1596](https://github.com/mui/base-ui/pull/1596))\n- Add missing item data attributes docs ([#1691](https://github.com/mui/base-ui/pull/1691))\n- Fix `onOpenChange` types for `event`/`reason` passing ([#1721](https://github.com/mui/base-ui/pull/1721))\n- Use consistent `inert` attr and map `[data-popup-open]` back to `open` ([#1650](https://github.com/mui/base-ui/pull/1650))\n- Fix text selection & right-clicks ([#1702](https://github.com/mui/base-ui/pull/1702))\n\n## Slider\n\n- Correct thumb positioning when control has padding ([#1661](https://github.com/mui/base-ui/pull/1661))\n- Prevent range slider thumbs from being dragged past each other ([#1612](https://github.com/mui/base-ui/pull/1612))\n- Fix incorrect CSS position on vertical slider indicator ([#1599](https://github.com/mui/base-ui/pull/1599))\n- Fix overlapping slider thumbs stuck at min or max ([#1732](https://github.com/mui/base-ui/pull/1732/))\n\n## Toast\n\n- New Toast component ([#1467](https://github.com/mui/base-ui/pull/1467))\n\n## Tooltip\n\n- Avoid re-rendering unrelated consumers ([#1677](https://github.com/mui/base-ui/pull/1677))\n- Add `disabled` prop ([#1682](https://github.com/mui/base-ui/pull/1682))\n- Fix `onOpenChange` types for `event`/`reason` passing ([#1721](https://github.com/mui/base-ui/pull/1721))\n- Use consistent `inert` attr and map `[data-popup-open]` back to `open` ([#1650](https://github.com/mui/base-ui/pull/1650))\n- Fix text selection & right-clicks ([#1702](https://github.com/mui/base-ui/pull/1702))\n"
  },
  {
    "path": "docs/src/app/(docs)/react/overview/releases/v1-0-0-beta-0/page.mdx",
    "content": "# v1.0.0-beta.0\n\n<Subtitle>May 29, 2025</Subtitle>\n<Meta name=\"description\" content=\"v1.0.0-beta.0 release notes. May 29, 2025.\" />\n\n## General changes\n\n- Remove proptypes ([#1760](https://github.com/mui/base-ui/pull/1760))\n- Unify component export patterns ([#1478](https://github.com/mui/base-ui/pull/1478))\n- Default `tabIndex` to `0` on `<button>` parts ([#1939](https://github.com/mui/base-ui/pull/1939))\n\n## Accordion\n\n- Stop event propagation to allow composite components to be used within popups ([#1871](https://github.com/mui/base-ui/pull/1871))\n\n## Alert Dialog\n\n- **Breaking change:** Refine `OpenChangeReason`. `hover` is now `trigger-hover`; `click` is now `trigger-press`; `focus` is now `trigger-focus`.\n  ([#1782](https://github.com/mui/base-ui/pull/1782))\n- Use basic scroll lock on iOS\n  ([#1890](https://github.com/mui/base-ui/pull/1890))\n\n## Checkbox\n\n- Set `aria-required`, use `useButton` ([#1777](https://github.com/mui/base-ui/pull/1777))\n\n## Checkbox Group\n\n- **Breaking change:** Enable submitting checkbox group value as one field.\n  For parent checkboxes, use `value` instead of `name` on each `<Checkbox.Root>` part to link as the values.\n  ([#1948](https://github.com/mui/base-ui/pull/1948))\n- Fix `validate` fn incorrectly running twice ([#1959](https://github.com/mui/base-ui/pull/1959))\n\n## Context Menu\n\n- New `ContextMenu` component ([#1665](https://github.com/mui/base-ui/pull/1665))\n\n## Dialog\n\n- **Breaking change:** Refine `OpenChangeReason`. `hover` is now `trigger-hover`; `click` is now `trigger-press`; `focus` is now `trigger-focus`.\n  ([#1782](https://github.com/mui/base-ui/pull/1782))\n- Use basic scroll lock on iOS\n  ([#1890](https://github.com/mui/base-ui/pull/1890))\n\n## Field\n\n- **Breaking change:** Consolidate `Field.Error` `forceShow` into `match` prop.\n  Use `match={true}` (or implicit boolean) instead of `forceShow`.\n  ([#1919](https://github.com/mui/base-ui/pull/1919))\n- Improve `Label` logic that prevents text selection on double click ([#1784](https://github.com/mui/base-ui/pull/1784))\n- Fix validation inconsistency ([#1779](https://github.com/mui/base-ui/pull/1779))\n- Fix integration of Base UI components ([#1755](https://github.com/mui/base-ui/pull/1755))\n- Set `valueMissing` to false if only error and not dirtied ([#1810](https://github.com/mui/base-ui/pull/1810))\n- `validate` with latest value on blur ([#1850](https://github.com/mui/base-ui/pull/1850))\n- Revalidate only `required` on change ([#1840](https://github.com/mui/base-ui/pull/1840))\n- Run validate function after native validations ([#1926](https://github.com/mui/base-ui/pull/1926))\n- Fix `validate` fn incorrectly running twice ([#1959](https://github.com/mui/base-ui/pull/1959))\n- Integrate range sliders with Form and Field ([#1929](https://github.com/mui/base-ui/pull/1929))\n\n## Form\n\n- Fix integration of Base UI components ([#1755](https://github.com/mui/base-ui/pull/1755))\n- Select inputs on focus ([#1858](https://github.com/mui/base-ui/pull/1858))\n- Exclude number formatting from form value ([#1957](https://github.com/mui/base-ui/pull/1957))\n- Integrate range sliders with Form and Field ([#1929](https://github.com/mui/base-ui/pull/1929))\n\n## Input\n\n- Fix `Input.Props` type ([#1915](https://github.com/mui/base-ui/pull/1915))\n- Extend `Field.Control.State` ([#1954](https://github.com/mui/base-ui/pull/1954))\n\n## Menu\n\n- **Breaking change:** Refine `OpenChangeReason`. `hover` is now `trigger-hover`; `click` is now `trigger-press`; `focus` is now `trigger-focus`.\n  ([#1782](https://github.com/mui/base-ui/pull/1782))\n- Fix function dependency handling ([#1787](https://github.com/mui/base-ui/pull/1787))\n- Add missing `'use client'` to `RadioGroup` part ([#1851](https://github.com/mui/base-ui/pull/1851))\n- Ensure `null` items are removed from composite lists ([#1847](https://github.com/mui/base-ui/pull/1847))\n- Avoid `:focus-visible` style appearing ([#1846](https://github.com/mui/base-ui/pull/1846))\n- Better handle dynamic and non-string items ([#1861](https://github.com/mui/base-ui/pull/1861))\n- Add `collisionAvoidance` prop ([#1849](https://github.com/mui/base-ui/pull/1849))\n- Add `finalFocus` and `closeDelay` props ([#1918](https://github.com/mui/base-ui/pull/1918))\n- Use basic scroll lock on iOS\n  ([#1890](https://github.com/mui/base-ui/pull/1890))\n\n## Menubar\n\n- New `Menubar` component ([#1684](https://github.com/mui/base-ui/pull/1684))\n\n## Navigation Menu\n\n- New `NavigationMenu` component ([#1741](https://github.com/mui/base-ui/pull/1741))\n\n## Number Field\n\n- `validate` with latest value on blur ([#1850](https://github.com/mui/base-ui/pull/1850))\n- Move scrubbing logic to `ScrubArea` component ([#1859](https://github.com/mui/base-ui/pull/1859))\n- Remove floating point errors when `snapOnStep` is disabled ([#1857](https://github.com/mui/base-ui/pull/1857))\n- Stop event propagation to allow composite components to be used within popups ([#1871](https://github.com/mui/base-ui/pull/1871))\n- Exclude number formatting from form value ([#1957](https://github.com/mui/base-ui/pull/1957))\n\n## Popover\n\n- **Breaking change:** Refine `OpenChangeReason`. `hover` is now `trigger-hover`; `click` is now `trigger-press`; `focus` is now `trigger-focus`.\n  ([#1782](https://github.com/mui/base-ui/pull/1782))\n- Fix function dependency handling ([#1787](https://github.com/mui/base-ui/pull/1787))\n- Avoid prop getters when merging props ([#1852](https://github.com/mui/base-ui/pull/1852))\n- Add `collisionAvoidance` prop ([#1849](https://github.com/mui/base-ui/pull/1849))\n- Fix nested `openOnHover` ([#1938](https://github.com/mui/base-ui/pull/1938))\n- Use basic scroll lock on iOS\n  ([#1890](https://github.com/mui/base-ui/pull/1890))\n\n## Preview Card\n\n- **Breaking change:** Refine `OpenChangeReason`. `hover` is now `trigger-hover`; `click` is now `trigger-press`; `focus` is now `trigger-focus`.\n  ([#1782](https://github.com/mui/base-ui/pull/1782))\n- Fix function dependency handling ([#1787](https://github.com/mui/base-ui/pull/1787))\n- Add `collisionAvoidance` prop ([#1849](https://github.com/mui/base-ui/pull/1849))\n\n## Radio Group\n\n- Fix composite focus of initially selected radio item ([#1753](https://github.com/mui/base-ui/pull/1753))\n- Add `inputRef` props ([#1683](https://github.com/mui/base-ui/pull/1683))\n- Stop event propagation to allow composite components to be used within popups ([#1871](https://github.com/mui/base-ui/pull/1871))\n\n## Select\n\n- **Breaking change:** Move item anchoring prop to `Positioner`.\n  Use `<Select.Positioner alignItemWithTrigger={false}>` instead of `<Select.Root alignItemToTrigger={false}>` (note the `With` instead of `To`).\n  ([#1713](https://github.com/mui/base-ui/pull/1713))\n- **Breaking change:** Defer mounting until typeahead is needed.\n  The `placeholder` prop is now required. Previously, only SSR needed it to prevent a hydration flash, but client-side rendering now also requires it.\n  ([#1906](https://github.com/mui/base-ui/pull/1906))\n- **Breaking change:** Refine `OpenChangeReason`. `hover` is now `trigger-hover`; `click` is now `trigger-press`; `focus` is now `trigger-focus`.\n  ([#1782](https://github.com/mui/base-ui/pull/1782))\n- Fix function dependency handling ([#1787](https://github.com/mui/base-ui/pull/1787))\n- Add `inputRef` props ([#1683](https://github.com/mui/base-ui/pull/1683))\n- Refactor to `useRenderElement` ([#1797](https://github.com/mui/base-ui/pull/1797))\n- Ensure `null` items are removed from composite lists ([#1847](https://github.com/mui/base-ui/pull/1847))\n- Fix `id` prop forwarding to hidden input ([#1862](https://github.com/mui/base-ui/pull/1862))\n- Avoid `:focus-visible` style appearing ([#1846](https://github.com/mui/base-ui/pull/1846))\n- Fix `transitionStatus` mapping on `ItemIndicator` ([#1925](https://github.com/mui/base-ui/pull/1925))\n- Better handle dynamic and non-string items ([#1861](https://github.com/mui/base-ui/pull/1861))\n- Use `<Select.ItemText>` ref to grab default text content ([#1943](https://github.com/mui/base-ui/pull/1943))\n- Add `collisionAvoidance` prop ([#1849](https://github.com/mui/base-ui/pull/1849))\n- Use basic scroll lock on iOS\n  ([#1890](https://github.com/mui/base-ui/pull/1890))\n\n## Slider\n\n- **Breaking change:** Drop `inputId` prop from Thumb.\n  ([#1914](https://github.com/mui/base-ui/pull/1914))\n- Position thumb based on value instead of pointer location when dragging ([#1750](https://github.com/mui/base-ui/pull/1750))\n- Use `useRenderElement` ([#1772](https://github.com/mui/base-ui/pull/1772))\n- Add `inputRef` props ([#1683](https://github.com/mui/base-ui/pull/1683))\n- Add `locale` prop ([#1796](https://github.com/mui/base-ui/pull/1796))\n- Stop event propagation to allow composite components to be used within popups ([#1871](https://github.com/mui/base-ui/pull/1871))\n- set `data-dragging` on touchstart and pointerdown ([#1874](https://github.com/mui/base-ui/pull/1874))\n- Integrate range sliders with Form and Field ([#1929](https://github.com/mui/base-ui/pull/1929))\n\n## Toast\n\n- **Breaking change:** Add `Portal` part.\n  Place `<Toast.Viewport>` inside of `<Toast.Portal>`.\n  ([#1962](https://github.com/mui/base-ui/pull/1962))\n- **Breaking change:** Avoid removing limited toasts from the DOM.\n  The `[data-limited]` styles in the demos were updated to handle limited toasts remaining in the DOM. They should now be a standalone style as `&[data-limited] { opacity: 0 }`.\n  ([#1953](https://github.com/mui/base-ui/pull/1953))\n- Fix swipe jump on iOS ([#1785](https://github.com/mui/base-ui/pull/1785))\n\n## Toggle\n\n- Stop event propagation to allow composite components to be used within popups ([#1871](https://github.com/mui/base-ui/pull/1871))\n\n## Toolbar\n\n- Stop event propagation to allow composite components to be used within popups ([#1871](https://github.com/mui/base-ui/pull/1871))\n\n## Tooltip\n\n- **Breaking change:** Refine `OpenChangeReason`. `hover` is now `trigger-hover`; `click` is now `trigger-press`; `focus` is now `trigger-focus`.\n  ([#1782](https://github.com/mui/base-ui/pull/1782))\n- Fix function dependency handling ([#1787](https://github.com/mui/base-ui/pull/1787))\n- Avoid prop getters when merging props ([#1852](https://github.com/mui/base-ui/pull/1852))\n- Remove `trackCursorAxis` type from `Positioner` ([#1895](https://github.com/mui/base-ui/pull/1895))\n- Apply `pointer-events: none` to `Positioner` when not hoverable ([#1917](https://github.com/mui/base-ui/pull/1917))\n- Add `collisionAvoidance` prop ([#1849](https://github.com/mui/base-ui/pull/1849))\n\n## useRender\n\n- **Breaking change:** Performance/refactor: `useRender`. An object with a `renderElement` property is no longer returned; instead, the hook returns the element directly (`const element = useRender(...)`). The `refs` option was also renamed to `ref`.\n  ([#1934](https://github.com/mui/base-ui/pull/1934))\n- Skip most of useRenderElement logic when unnecessary ([#1967](https://github.com/mui/base-ui/pull/1967))\n"
  },
  {
    "path": "docs/src/app/(docs)/react/overview/releases/v1-0-0-beta-1/page.mdx",
    "content": "# v1.0.0-beta.1\n\n<Subtitle>Jul 1, 2025</Subtitle>\n<Meta name=\"description\" content=\"v1.0.0-beta.1 release notes. Jul 1, 2025.\" />\n\n## General changes\n\n- Make error messages consistent ([#2049](https://github.com/mui/base-ui/pull/2049))\n- Do not overwrite event handler when `undefined` is passed explicitly ([#2151](https://github.com/mui/base-ui/pull/2151))\n\n## Accordion\n\n- Allow content to resize naturally ([#2043](https://github.com/mui/base-ui/pull/2043))\n- Fix transition status mapping ([#2169](https://github.com/mui/base-ui/pull/2169))\n- Fix `aria-controls` reference ([#2170](https://github.com/mui/base-ui/pull/2170))\n- Fix test warning about mixed animation types ([#2180](https://github.com/mui/base-ui/pull/2180))\n\n## Checkbox\n\n- **Breaking change:** Support implicit `<Field.Label>`.\n  If `<Field.Label>` encloses Switch/Checkbox/Radio, the `htmlFor`/`id` attributes are no longer explicitly set to associate them.\n  ([#2036](https://github.com/mui/base-ui/pull/2036))\n- Refactor to `useRenderElement` ([#2053](https://github.com/mui/base-ui/pull/2053))\n- Always set `id` on the `<input>` element ([#2115](https://github.com/mui/base-ui/pull/2115))\n\n## Checkbox Group\n\n- Fix `onCheckedChange` not running when parent checkbox is present ([#2155](https://github.com/mui/base-ui/pull/2155))\n\n## Collapsible\n\n- Allow content to resize naturally ([#2043](https://github.com/mui/base-ui/pull/2043))\n- Fix `aria-controls` reference ([#2170](https://github.com/mui/base-ui/pull/2170))\n- Fix test warning about mixed animation types ([#2180](https://github.com/mui/base-ui/pull/2180))\n\n## Context Menu\n\n- **Breaking change:** Add `SubmenuRoot` part.\n  Nested menus should be defined with `<Menu.SubmenuRoot>` instead of `<Menu.Root>` to to avoid ambiguity.\n  ([#2042](https://github.com/mui/base-ui/pull/2042))\n- Fix CheckboxItemIndicator export ([#2009](https://github.com/mui/base-ui/pull/2009))\n\n## Dialog\n\n- Fix popup prop merging ([#2119](https://github.com/mui/base-ui/pull/2119))\n\n## Field\n\n- **Breaking change:** Support implicit `<Field.Label>`.\n  If `<Field.Label>` encloses Switch/Checkbox/Radio, the `htmlFor`/`id` attributes are no longer explicitly set to associate them.\n  ([#2036](https://github.com/mui/base-ui/pull/2036))\n- Enable custom validation based on other form values ([#1941](https://github.com/mui/base-ui/pull/1941))\n- Fix `onValueChange` `value` type ([#2112](https://github.com/mui/base-ui/pull/2112))\n- Fix `<Field.Label>` focusing trigger ([#2118](https://github.com/mui/base-ui/pull/2118))\n- Fix slider field label ([#2154](https://github.com/mui/base-ui/pull/2154))\n\n## Fieldset\n\n- Refactor to `useRenderElement` ([#2053](https://github.com/mui/base-ui/pull/2053))\n\n## Form\n\n- Enable custom validation based on other form values ([#1941](https://github.com/mui/base-ui/pull/1941))\n\n## Input\n\n- Fix `onValueChange` `value` type ([#2112](https://github.com/mui/base-ui/pull/2112))\n\n## Menu\n\n- **Breaking change:** Add `SubmenuRoot` part.\n  Nested menus should be defined with `<Menu.SubmenuRoot>` instead of `<Menu.Root>` to to avoid ambiguity.\n  ([#2042](https://github.com/mui/base-ui/pull/2042))\n- Unset `role` from Trigger ([#2047](https://github.com/mui/base-ui/pull/2047))\n- Emit `close` event on `cancel-open` ([#2067](https://github.com/mui/base-ui/pull/2067))\n- Fix close toggle when rendering non-native button ([#2071](https://github.com/mui/base-ui/pull/2071))\n- Add `highlighted` to item `State` ([#2079](https://github.com/mui/base-ui/pull/2079))\n- Remove highlighted effect ([#2162](https://github.com/mui/base-ui/pull/2162))\n- Cut out internal backdrop to allow interacting with triggers ([#2141](https://github.com/mui/base-ui/pull/2141))\n- Fix active index sync on hover ([#2163](https://github.com/mui/base-ui/pull/2163))\n- Fix focus returning to root when submenus have exit transitions ([#2171](https://github.com/mui/base-ui/pull/2171))\n\n## Menubar\n\n- Fix `closeOnClick: false` not working in nested menus ([#2094](https://github.com/mui/base-ui/pull/2094))\n\n## Navigation Menu\n\n- **Breaking change:** Handle layout resize while open ([#2070](https://github.com/mui/base-ui/pull/2070)).\n  For large scrollable content, add the `max-height` style to `.Content` in addition to `.Popup`.\n- Fix positioner height when opening menu using the keyboard arrows ([#2060](https://github.com/mui/base-ui/pull/2060))\n\n## Number Field\n\n- Ensure `onValueChange` is called with already-formatted parsed value ([#1905](https://github.com/mui/base-ui/pull/1905))\n- Fix revalidation on change ([#2174](https://github.com/mui/base-ui/pull/2174))\n\n## Popover\n\n- Fix close toggle when rendering non-native button ([#2071](https://github.com/mui/base-ui/pull/2071))\n- Cut out internal backdrop to allow interacting with triggers ([#2141](https://github.com/mui/base-ui/pull/2141))\n\n## Radio Group\n\n- **Breaking change:** Support implicit `<Field.Label>`.\n  If `<Field.Label>` encloses Radio, the `htmlFor`/`id` attributes are no longer explicitly set to associate them.\n  ([#2036](https://github.com/mui/base-ui/pull/2036))\n- Refactor to `useRenderElement` ([#2053](https://github.com/mui/base-ui/pull/2053))\n\n## Scroll Area\n\n- Ignore `data-scrolling` during programmatic scroll ([#1908](https://github.com/mui/base-ui/pull/1908))\n\n## Select\n\n- **Breaking change:** Print raw value in `<Select.Value>`.\n  `<Select.Value>` now prints the raw value by default unless an `items` prop is specified on `<Select.Root>`.\n  See https://base-ui.com/react/components/select#formatting-the-value for more information.\n  ([#2087](https://github.com/mui/base-ui/pull/2087))\n- Performance: avoid re-renders ([#1961](https://github.com/mui/base-ui/pull/1961))\n- Fix close toggle when rendering non-native button ([#2071](https://github.com/mui/base-ui/pull/2071))\n- Fix `<Field.Label>` focusing trigger ([#2118](https://github.com/mui/base-ui/pull/2118))\n- Fix programmatic value changes and autofill handling ([#2084](https://github.com/mui/base-ui/pull/2084))\n- Add `highlighted` to item `State` ([#2079](https://github.com/mui/base-ui/pull/2079))\n- Cut out internal backdrop to allow interacting with triggers ([#2141](https://github.com/mui/base-ui/pull/2141))\n- Pass `value` as state ([#2153](https://github.com/mui/base-ui/pull/2153))\n- Extend `FieldRoot.State` type ([#2192](https://github.com/mui/base-ui/pull/2192))\n\n## Slider\n\n- Use pointer capture when dragging ([#2059](https://github.com/mui/base-ui/pull/2059))\n- Fix slider field label ([#2154](https://github.com/mui/base-ui/pull/2154))\n\n## Switch\n\n- **Breaking change:** Support implicit `<Field.Label>`.\n  If `<Field.Label>` encloses Switch, the `htmlFor`/`id` attributes are no longer explicitly set to associate them.\n  ([#2036](https://github.com/mui/base-ui/pull/2036))\n\n## Tabs\n\n- Fix indicator positioning when TabsList overflows ([#2093](https://github.com/mui/base-ui/pull/2093))\n- Fix focus going out of sync when selected value is changed externally ([#2107](https://github.com/mui/base-ui/pull/2107))\n- Remove highlighted state ([#2164](https://github.com/mui/base-ui/pull/2164))\n\n## Toolbar\n\n- Set `disabled` attr on toolbar button when `focusableWhenDisabled={false}` ([#2176](https://github.com/mui/base-ui/pull/2176))\n\n## useRender\n\n- Make useRender RSC-friendly ([#2134](https://github.com/mui/base-ui/pull/2134))\n"
  },
  {
    "path": "docs/src/app/(docs)/react/overview/releases/v1-0-0-beta-2/page.mdx",
    "content": "# v1.0.0-beta.2\n\n<Subtitle>Jul 30, 2025</Subtitle>\n<Meta name=\"description\" content=\"v1.0.0-beta.2 release notes. Jul 30, 2025.\" />\n\n## General changes\n\n- Fix navigator checks and ensure safe platform retrieval ([#2273](https://github.com/mui/base-ui/pull/2273))\n- Prevent <kbd>Space</kbd> key default on keydown ([#2295](https://github.com/mui/base-ui/pull/2295))\n- Check for `performance` existence on server ([#2316](https://github.com/mui/base-ui/pull/2316))\n\n## Accordion\n\n- Destructure `render` prop ([#2280](https://github.com/mui/base-ui/pull/2280))\n- Fix keyboard interactions with elements in the panel ([#2321](https://github.com/mui/base-ui/pull/2321))\n- Fix open transitions in Safari/Firefox ([#2327](https://github.com/mui/base-ui/pull/2327))\n\n## Alert Dialog\n\n- Support `ShadowRoot` containers ([#2236](https://github.com/mui/base-ui/pull/2236))\n- Add `forceRender` prop to `Backdrop` part ([#2037](https://github.com/mui/base-ui/pull/2037))\n- Improve outside press behavior with touch input ([#2334](https://github.com/mui/base-ui/pull/2334))\n\n## Checkbox\n\n- Fix focusing form controls with `inputRef` ([#2252](https://github.com/mui/base-ui/pull/2252))\n\n## Collapsible\n\n- Destructure render prop ([#2323](https://github.com/mui/base-ui/pull/2323))\n- Fix open transitions in Safari/Firefox ([#2327](https://github.com/mui/base-ui/pull/2327))\n\n## Dialog\n\n- Support `ShadowRoot` containers ([#2236](https://github.com/mui/base-ui/pull/2236))\n- Add `forceRender` prop to `Backdrop` part ([#2037](https://github.com/mui/base-ui/pull/2037))\n- Improve outside press behavior with touch input ([#2334](https://github.com/mui/base-ui/pull/2334))\n- Use `click` event for outside press dismissal ([#2275](https://github.com/mui/base-ui/pull/2275))\n\n## Field\n\n- Deregister fields from `Form` when unmounting ([#2231](https://github.com/mui/base-ui/pull/2231))\n\n## Form\n\n- Deregister fields from `Form` when unmounting ([#2231](https://github.com/mui/base-ui/pull/2231))\n\n## Menu\n\n- Support `ShadowRoot` containers ([#2236](https://github.com/mui/base-ui/pull/2236))\n- Avoid double `useRenderElement` passes ([#2256](https://github.com/mui/base-ui/pull/2256))\n- Improve outside press behavior with touch input ([#2334](https://github.com/mui/base-ui/pull/2334))\n- Close submenus when focus is lost by shift-tabbing ([#2290](https://github.com/mui/base-ui/pull/2290))\n\n## Menubar\n\n- Fix triggers role ([#2317](https://github.com/mui/base-ui/pull/2317))\n\n## Meter\n\n- Fix ARIA attributes and update docs ([#2267](https://github.com/mui/base-ui/pull/2267))\n\n## Navigation Menu\n\n- **Breaking change:** Support inlined nesting.\n  Ensure the popup's `width` is set to `var(--popup-width)` unconditionally (without the media query) on the `.Popup` class.\n  ([#2269](https://github.com/mui/base-ui/pull/2269))\n- Avoid double `useRenderElement` passes ([#2256](https://github.com/mui/base-ui/pull/2256))\n- Add `useButton` integration to `Trigger` ([#2296](https://github.com/mui/base-ui/pull/2296))\n- Fix popup size transitions on iOS ([#2387](https://github.com/mui/base-ui/pull/2387))\n\n## Number Field\n\n- Remove `invalid` prop ([#2315](https://github.com/mui/base-ui/pull/2315))\n- Fix button disabled state only including root disabled state ([#2268](https://github.com/mui/base-ui/pull/2268))\n\n## Popover\n\n- Support `ShadowRoot` containers ([#2236](https://github.com/mui/base-ui/pull/2236))\n- Remove ancestor nodes from inside elements for outside press detection ([#2339](https://github.com/mui/base-ui/pull/2339))\n- Improve outside press behavior with touch input ([#2334](https://github.com/mui/base-ui/pull/2334))\n- Use `click` event for outside press dismissal ([#2275](https://github.com/mui/base-ui/pull/2275))\n\n## Preview Card\n\n- Support `ShadowRoot` containers ([#2236](https://github.com/mui/base-ui/pull/2236))\n\n## Progress\n\n- Fix ARIA attributes and update docs ([#2267](https://github.com/mui/base-ui/pull/2267))\n\n## Radio Group\n\n- Add aria-required attribute ([#2227](https://github.com/mui/base-ui/pull/2227))\n- Extend state with `FieldRoot.State` ([#2251](https://github.com/mui/base-ui/pull/2251))\n- Fix focusing form controls with `inputRef` ([#2252](https://github.com/mui/base-ui/pull/2252))\n- Avoid double `useRenderElement` passes ([#2256](https://github.com/mui/base-ui/pull/2256))\n\n## Scroll Area\n\n- Disable `user-select` on scrollbar and non-main button interactions ([#2338](https://github.com/mui/base-ui/pull/2338))\n\n## Select\n\n- Support `ShadowRoot` containers ([#2236](https://github.com/mui/base-ui/pull/2236))\n- Add `value` and `readOnly` to `<Select.Trigger>` state ([#2237](https://github.com/mui/base-ui/pull/2237))\n- Add `multiple` prop ([#2173](https://github.com/mui/base-ui/pull/2173))\n- Allow typeahead while open for `multiple` mode ([#2274](https://github.com/mui/base-ui/pull/2274))\n- Ensure positionerElement is available in document mouseup ([#2276](https://github.com/mui/base-ui/pull/2276))\n- Fix `alignItemWithTrigger` fallback scroll jump ([#2241](https://github.com/mui/base-ui/pull/2241))\n- Support conditional `multiple` prop in types ([#2369](https://github.com/mui/base-ui/pull/2369))\n- Fix multiple ARIA behavior on touch ([#2333](https://github.com/mui/base-ui/pull/2333))\n- Improve outside press behavior with touch input ([#2334](https://github.com/mui/base-ui/pull/2334))\n\n## Slider\n\n- Fix focusing form controls with `inputRef` ([#2252](https://github.com/mui/base-ui/pull/2252))\n\n## Toast\n\n- Fix `promise` method timeout option handling ([#2294](https://github.com/mui/base-ui/pull/2294))\n- Make `<Toast.Viewport>` an announce container ([#2246](https://github.com/mui/base-ui/pull/2246))\n\n## Toggle\n\n- Avoid double `useRenderElement` passes ([#2256](https://github.com/mui/base-ui/pull/2256))\n\n## Toggle Group\n\n- Avoid double `useRenderElement` passes ([#2256](https://github.com/mui/base-ui/pull/2256))\n\n## Toolbar\n\n- Avoid double `useRenderElement` passes ([#2256](https://github.com/mui/base-ui/pull/2256))\n\n## Tooltip\n\n- Support `ShadowRoot` containers ([#2236](https://github.com/mui/base-ui/pull/2236))\n- Memoize leftover object in tooltip ([#2250](https://github.com/mui/base-ui/pull/2250))\n- Fix error when combining `defaultOpen` and `disabled` ([#2374](https://github.com/mui/base-ui/pull/2374))\n"
  },
  {
    "path": "docs/src/app/(docs)/react/overview/releases/v1-0-0-beta-3/page.mdx",
    "content": "# v1.0.0-beta.3\n\n<Subtitle>Sep 3, 2025</Subtitle>\n<Meta name=\"description\" content=\"v1.0.0-beta.3 release notes. Sep 3, 2025.\" />\n\n## General changes\n\n- **Breaking change:** Base UI event details.\n  Custom event callbacks provide BaseUIChangeEventDetails object as their second parameter.\n  This object contains the source event, reason and methods to customize the behavior (where applicable).\n  For example, `onOpenChange(open, event, reason)` becomes `onOpenChange(open, eventDetails)`, where `eventDetails` contains `event` and `reason` properties.\n  ([#2382](https://github.com/mui/base-ui/pull/2382))\n\n## Alert Dialog\n\n- **Breaking change:** Support `initialFocus` and `finalFocus` functions.\n  The `initialFocus` and `finalFocus` props can be functions that return DOM elements to focus.\n  This is a new feature for `finalFocus` and a breaking change for `initialFocus` as the element must be returned directly (not as a ref).\n  ([#2536](https://github.com/mui/base-ui/pull/2536))\n\n## Autocomplete\n\n- New Autocomplete component ([#2105](https://github.com/mui/base-ui/pull/2105))\n\n## Checkbox\n\n- Fix missing validity attributes when wrapped in `<Field>` ([#2572](https://github.com/mui/base-ui/pull/2572))\n\n## Combobox\n\n- New Combobox component ([#2105](https://github.com/mui/base-ui/pull/2105))\n\n## Context Menu\n\n- Fix default offsets when `align=\"center\"` or `side` differs ([#2601](https://github.com/mui/base-ui/pull/2601))\n\n## Dialog\n\n- **Breaking change:** Support `initialFocus` and `finalFocus` functions.\n  The `initialFocus` and `finalFocus` props can be functions that return DOM elements to focus.\n  This is a new feature for `finalFocus` and a breaking change for `initialFocus` as the element must be returned directly (not as a ref).\n  ([#2536](https://github.com/mui/base-ui/pull/2536))\n- Restore focus to popup when focused element is removed ([#2479](https://github.com/mui/base-ui/pull/2479))\n\n## Field\n\n- Prevent defaultValue reset on focus for uncontrolled inputs ([#2543](https://github.com/mui/base-ui/pull/2543))\n- Allow `onValueChange` to fire when `defaultValue`/`value` are not set ([#2600](https://github.com/mui/base-ui/pull/2600))\n\n## Input\n\n- Allow `onValueChange` to fire when `defaultValue`/`value` are not set ([#2600](https://github.com/mui/base-ui/pull/2600))\n\n## Menu\n\n- **Breaking change:** Fix `closeParentOnEsc` default value.\n  The default value of `closeParentOnEsc` in Menu.SubmenuRoot is now false.\n  When the <kbd>Esc</kbd> key is pressed in a Submenu, the Submenu closes, and the focus correctly moves to the SubmenuTrigger.\n  ([#2493](https://github.com/mui/base-ui/pull/2493))\n- **Breaking change:** Support `initialFocus` and `finalFocus` functions.\n  The `initialFocus` and `finalFocus` props can be functions that return DOM elements to focus.\n  This is a new feature for `finalFocus` and a breaking change for `initialFocus` as the element must be returned directly (not as a ref).\n  ([#2536](https://github.com/mui/base-ui/pull/2536))\n- Fix menu not opening when inside context menu trigger ([#2506](https://github.com/mui/base-ui/pull/2506))\n- Fix `transform-origin` variable calculation when Positioner `sideOffset` is a function ([#2511](https://github.com/mui/base-ui/pull/2511))\n- Fix submenu events ([#2483](https://github.com/mui/base-ui/pull/2483))\n- Fix `limitShift` offset based on arrow size ([#2571](https://github.com/mui/base-ui/pull/2571))\n\n## Navigation Menu\n\n- **Breaking change:** Semantic element structure and `active` page prop.\n  `<NavigationMenu.List>` renders `<ul>` and `<NavigationMenu.Item>` renders `<li>` by default.\n  ([#2526](https://github.com/mui/base-ui/pull/2526))\n- Unshare `AbortController` instance ([#2441](https://github.com/mui/base-ui/pull/2441))\n- Close on link click by default ([#2535](https://github.com/mui/base-ui/pull/2535))\n\n## Number Field\n\n- Fix duplicate `onValueChange` calls ([#2591](https://github.com/mui/base-ui/pull/2591))\n\n## Popover\n\n- **Breaking change:** Support `initialFocus` and `finalFocus` functions.\n  The `initialFocus` and `finalFocus` props can be functions that return DOM elements to focus.\n  This is a new feature for `finalFocus` and a breaking change for `initialFocus` as the element must be returned directly (not as a ref).\n  ([#2536](https://github.com/mui/base-ui/pull/2536))\n- Fix outside click after right clicking in popup ([#2508](https://github.com/mui/base-ui/pull/2508))\n- Fix unexpected close when nested inside two popovers ([#2481](https://github.com/mui/base-ui/pull/2481))\n- Fix `transform-origin` variable calculation when Positioner `sideOffset` is a function ([#2511](https://github.com/mui/base-ui/pull/2511))\n- Restore focus to popup when focused element is removed ([#2479](https://github.com/mui/base-ui/pull/2479))\n- Fix `limitShift` offset based on arrow size ([#2571](https://github.com/mui/base-ui/pull/2571))\n\n## Preview Card\n\n- Fix `transform-origin` variable calculation when Positioner `sideOffset` is a function ([#2511](https://github.com/mui/base-ui/pull/2511))\n- Fix `limitShift` offset based on arrow size ([#2571](https://github.com/mui/base-ui/pull/2571))\n\n## Radio Group\n\n- Return null in form data when no option selected ([#2473](https://github.com/mui/base-ui/pull/2473))\n\n## Scroll Area\n\n- Prevent pointer events from sibling portals triggering hover ([#2542](https://github.com/mui/base-ui/pull/2542))\n\n## Select\n\n- Fix stale `items` prop ([#2397](https://github.com/mui/base-ui/pull/2397))\n- Fix unexpected close when nested inside two popovers ([#2481](https://github.com/mui/base-ui/pull/2481))\n- Fix `onValueChange` type inference ([#2372](https://github.com/mui/base-ui/pull/2372))\n- Fix `transform-origin` variable calculation when Positioner `sideOffset` is a function ([#2511](https://github.com/mui/base-ui/pull/2511))\n- Reset state when selected item is removed ([#2577](https://github.com/mui/base-ui/pull/2577))\n- Fix `data-highlighted` and DOM focus item desync ([#2569](https://github.com/mui/base-ui/pull/2569))\n- Fix item click with `defaultOpen` prop ([#2570](https://github.com/mui/base-ui/pull/2570))\n- Fix scroll arrows not propagating scroll fully to start/end of list ([#2523](https://github.com/mui/base-ui/pull/2523))\n- Fix `limitShift` offset based on arrow size ([#2571](https://github.com/mui/base-ui/pull/2571))\n\n## Slider\n\n- **Breaking change:** Instead of the thumb div, the `input type=\"range\"` element receives focus. Focus styles that were targeting the thumb, should be updated.\n  For example `.Thumb:focus-visible` should be replaced with `.Thumb:has(:focus-visible)`.\n  The `tabIndex` prop is moved from Root to Thumb where it gets forwarded to the input.\n  The thumb's `render` prop no longer contains the third `inputProps` argument; the input element is instead merged with children.\n  ([#2578](https://github.com/mui/base-ui/pull/2578))\n- Reduce bundle size ([#2551](https://github.com/mui/base-ui/pull/2551))\n- Fix thumb `:focus-visible` with mixed keyboard and pointer modality ([#2584](https://github.com/mui/base-ui/pull/2584))\n- Add `index` prop to `<Slider.Thumb>` ([#2593](https://github.com/mui/base-ui/pull/2593))\n\n## Tabs\n\n- Fix tab size rounding ([#2488](https://github.com/mui/base-ui/pull/2488))\n- Fix highlight sync when focus is inside list ([#2487](https://github.com/mui/base-ui/pull/2487))\n\n## Tooltip\n\n- Fix `transform-origin` variable calculation when Positioner `sideOffset` is a function ([#2511](https://github.com/mui/base-ui/pull/2511))\n- Fix `limitShift` offset based on arrow size ([#2571](https://github.com/mui/base-ui/pull/2571))\n\n## useRender\n\n- Add support for data-\\* attributes ([#2524](https://github.com/mui/base-ui/pull/2524))\n- Add `defaultTagName` parameter ([#2527](https://github.com/mui/base-ui/pull/2527))\n"
  },
  {
    "path": "docs/src/app/(docs)/react/overview/releases/v1-0-0-beta-4/page.mdx",
    "content": "# v1.0.0-beta.4\n\n<Subtitle>Oct 1, 2025</Subtitle>\n<Meta name=\"description\" content=\"v1.0.0-beta.4 release notes. Oct 1, 2025.\" />\n\n## General changes\n\n- **Breaking change:** Generic event details.\n  The main exported type is now `BaseUIChangeEventDetails` (with a paired `BaseUIGenericEventDetails`), not `BaseUIEventDetails`.\n  ([#2796](https://github.com/mui/base-ui/pull/2796))\n- Update `disabled` prop of buttons when ref changes ([#2756](https://github.com/mui/base-ui/pull/2756))\n- Refine event details ([#2698](https://github.com/mui/base-ui/pull/2698))\n\n## Accordion\n\n- **Breaking change:** Use `useId` instead of composite index as fallback value.\n  Accordion items must have an explicit `value` set in order to be initially open. Inferring the value by their DOM index is no longer supported.\n  ([#2664](https://github.com/mui/base-ui/pull/2664))\n- **Breaking change:** Rename `openMultiple` prop to `multiple`\n  ([#2764](https://github.com/mui/base-ui/pull/2764))\n\n## Autocomplete\n\n- **Breaking change**: `onItemHighlighted` now has a `reason` property instead of `type` to be consistent with the `eventDetails` API. ([#2796](https://github.com/mui/base-ui/pull/2796))\n- **Breaking change:** Rename `cols` to `grid` prop.\n  Specify `grid={true}` instead of `cols={number}` - the columns are automatically inferred from `<Autocomplete.Row>`\n  ([#2683](https://github.com/mui/base-ui/pull/2683))\n- Fix duplicate `onOpenChange` calls and pass correct DOM `event`.\n  ([#2682](https://github.com/mui/base-ui/pull/2682))\n- Fix controlled input value updates ([#2707](https://github.com/mui/base-ui/pull/2707))\n- Fix input focus on close when clicking trigger. Fixes a jump to the bottom of the page in Safari ([#2723](https://github.com/mui/base-ui/pull/2723))\n- Add `alwaysSubmitOnEnter` prop and allow form submission on <kbd>Enter</kbd> if no item is highlighted by default ([#2700](https://github.com/mui/base-ui/pull/2700))\n- Use `ReadonlyArray` type for `items` ([#2819](https://github.com/mui/base-ui/pull/2819))\n\n## Collapsible\n\n- Fix `CollapsiblePanel` type to use its own state ([#2697](https://github.com/mui/base-ui/pull/2697))\n- Respect user's CSS `display` property on panel ([#2772](https://github.com/mui/base-ui/pull/2772))\n\n## Combobox\n\n- **Breaking change**: `onItemHighlighted` now has a `reason` property instead of `type` to be consistent with the `eventDetails` API. ([#2796](https://github.com/mui/base-ui/pull/2796))\n- **Breaking change:** Rename `cols` to `grid` prop.\n  Specify `grid={true}` instead of `cols={number}` - the columns are automatically inferred from `<Combobox.Row>`.\n  ([#2683](https://github.com/mui/base-ui/pull/2683))\n- Fix duplicate `onOpenChange` calls and pass correct DOM `event`.\n  ([#2682](https://github.com/mui/base-ui/pull/2682))\n- Fix initial closed typeahead ([#2665](https://github.com/mui/base-ui/pull/2665))\n- Support `autoHighlight` prop ([#2668](https://github.com/mui/base-ui/pull/2668))\n- Set default input value based on `value` prop ([#2680](https://github.com/mui/base-ui/pull/2680))\n- Fix controlled input value updates ([#2707](https://github.com/mui/base-ui/pull/2707))\n- Fix input focus on close when clicking trigger. Fixes a jump to the bottom of the page in Safari ([#2723](https://github.com/mui/base-ui/pull/2723))\n- Fix unexpected close with multiple selection and input inside popup ([#2771](https://github.com/mui/base-ui/pull/2771))\n- Allow form submission on <kbd>Enter</kbd> if no item is highlighted by default ([#2700](https://github.com/mui/base-ui/pull/2700))\n- Avoid refiltering with ending transition in multiple selection mode ([#2681](https://github.com/mui/base-ui/pull/2681))\n- Support object values with `isItemEqualToValue` prop ([#2704](https://github.com/mui/base-ui/pull/2704))\n- Use `ReadonlyArray` type for `items` ([#2819](https://github.com/mui/base-ui/pull/2819))\n- Fix misleading `item-press` reason in `onInputValueChange` ([#2830](https://github.com/mui/base-ui/pull/2830))\n- Clear single-select value on input clear ([#2860](https://github.com/mui/base-ui/pull/2860))\n- Fix `focusout` of input not closing popup under certain conditions ([#2864](https://github.com/mui/base-ui/pull/2864))\n\n## Context Menu\n\n- Ensure submenus close when parents close ([#2768](https://github.com/mui/base-ui/pull/2768))\n- Fix `onClick` firing twice on first click of item ([#2849](https://github.com/mui/base-ui/pull/2849))\n\n## Menu\n\n- Ensure submenus close when parents close ([#2768](https://github.com/mui/base-ui/pull/2768))\n- Allow non-nested portals across differing popup trees ([#2818](https://github.com/mui/base-ui/pull/2818))\n\n## Menubar\n\n- Fix Menubar not disabling child Menus ([#2736](https://github.com/mui/base-ui/pull/2736))\n- Ensure submenus close when parents close ([#2768](https://github.com/mui/base-ui/pull/2768))\n- Fix `<CompositeList>` not updating item order on reordering ([#2675](https://github.com/mui/base-ui/pull/2675))\n\n## Navigation Menu\n\n- Make link close on click configurable ([#2740](https://github.com/mui/base-ui/pull/2740))\n- Fix focus returning to trigger without animations ([#2779](https://github.com/mui/base-ui/pull/2779))\n\n## Number Field\n\n- Fix stuck virtual cursor after mouse tap ([#2720](https://github.com/mui/base-ui/pull/2720))\n- Improve parsing logic ([#2725](https://github.com/mui/base-ui/pull/2725))\n- Align value changes with `Slider`. An `onValueCommitted` callback has been added. ([#2726](https://github.com/mui/base-ui/pull/2726))\n\n## Popover\n\n- Allow non-nested portals across differing popup trees ([#2818](https://github.com/mui/base-ui/pull/2818))\n\n## Scroll Area\n\n- Add overflow presence state attributes and CSS variables ([#2478](https://github.com/mui/base-ui/pull/2478))\n- Fix RTL horizontal scrollbar on Safari ([#2776](https://github.com/mui/base-ui/pull/2776))\n- Fix thumb size flicker ([#2778](https://github.com/mui/base-ui/pull/2778))\n\n## Select\n\n- **Breaking change:** Add `<Select.List>` component. It is now possible for `<Select.ScrollArrow>` to show when in fallback (`alignItemWithTrigger` deactivated). As a result, if you want the scroll arrows to be hidden in this mode like before, change the styles to default to `display: none` on `.ScrollArrow`, and `display: block` when `[data-side=\"none\"]`. ([#2596](https://github.com/mui/base-ui/pull/2596))\n- Block opening the popup when provided `readOnly` ([#2717](https://github.com/mui/base-ui/pull/2717))\n- Add `open` state for `<Select.Icon>` and fix `ref` type ([#2714](https://github.com/mui/base-ui/pull/2714))\n- Support object values with `isItemEqualToValue` prop ([#2704](https://github.com/mui/base-ui/pull/2704))\n- Use `ReadonlyArray` type for `items` ([#2819](https://github.com/mui/base-ui/pull/2819))\n\n## Slider\n\n- **Breaking change:** `onValueChange` has `activeThumbIndex` as part of the `eventDetails` object as a second parameter, not third. ([#2796](https://github.com/mui/base-ui/pull/2796))\n- **Breaking change:** Remove redundant hidden inputs.\n  The `inputRef` prop is moved from `<Slider.Root>` to `<Slider.Thumb>`.\n  ([#2631](https://github.com/mui/base-ui/pull/2631))\n- Fix pointer tracking bugs ([#2688](https://github.com/mui/base-ui/pull/2688))\n- Fix input attributes ([#2728](https://github.com/mui/base-ui/pull/2728))\n- Add `thumbAlignment` prop ([#2540](https://github.com/mui/base-ui/pull/2540))\n\n## Switch\n\n- Fix duplicate `name` attribute ([#2763](https://github.com/mui/base-ui/pull/2763))\n\n## Toast\n\n- **Breaking change:** Support variable height stacking.\n  Toasts that have varying heights no longer force a `data-expanded` expanded state on the viewport. CSS should be amended to ensure larger toasts don't overflow a small toast stacked at the front. See this [diff](https://github.com/mui/base-ui/pull/2742/files#diff-e378460dafb74fe0c90ef960ad0ef1c38d68d74b63815520bb437f9041361917) for new styles, along with general improvements to stacking styles.\n  ([#2742](https://github.com/mui/base-ui/pull/2742))\n- Reduce stickiness of expanded state ([#2770](https://github.com/mui/base-ui/pull/2770))\n- Ensure toast is frozen at its current visual transform while swiping ([#2769](https://github.com/mui/base-ui/pull/2769))\n\n## Toggle Group\n\n- **Breaking change:** Rename `toggleMultiple` prop to `multiple`.\n  ([#2764](https://github.com/mui/base-ui/pull/2764))\n\n## Toolbar\n\n- Fix `<CompositeList>` not updating item order on reordering ([#2675](https://github.com/mui/base-ui/pull/2675))\n\n## useRender\n\n- Add div as a `defaultTagName` ([#2692](https://github.com/mui/base-ui/pull/2692))\n"
  },
  {
    "path": "docs/src/app/(docs)/react/overview/releases/v1-0-0-beta-5/page.mdx",
    "content": "# v1.0.0-beta.5\n\n<Subtitle>Nov 17, 2025</Subtitle>\n<Meta name=\"description\" content=\"v1.0.0-beta.5 release notes. Nov 17, 2025.\" />\n\n## General changes\n\n- **Breaking change:** Replace `trackAnchor` with `disableAnchorTracking`.<br />\n  If you were using `trackAnchor={false}`, be sure to update your usage to `disableAnchorTracking` instead.\n  ([#3188](https://github.com/mui/base-ui/pull/3188))\n- **Breaking change:** Rename `loop` to `loopFocus` ([#3186](https://github.com/mui/base-ui/pull/3186))\n- Fix type portability ([#2912](https://github.com/mui/base-ui/pull/2912))\n- Accept a function for the `style` prop ([#3038](https://github.com/mui/base-ui/pull/3038))\n- Create portal elements inside React ([#2889](https://github.com/mui/base-ui/pull/2889))\n- Avoid applying `hidden` attribute to indicator elements when they specify `keepMounted` and are invisible ([#3228](https://github.com/mui/base-ui/pull/3228))\n- Fix crash in Next.js 16 when accessing `render.props.ref` ([#3231](https://github.com/mui/base-ui/pull/3231))\n\n## Accordion\n\n- **Breaking change:** Change `multiple` prop to be false by default and add a demo ([#3141](https://github.com/mui/base-ui/pull/3141))\n- Fix flaky exit transition ([#3101](https://github.com/mui/base-ui/pull/3101))\n\n## Alert Dialog\n\n- Fix `initialFocus` as function being called on close ([#2949](https://github.com/mui/base-ui/pull/2949))\n- Support detached triggers ([#2974](https://github.com/mui/base-ui/pull/2974))\n- Place `overflow: hidden` on `<body>` for overlay scrollbars by default. Avoids sticky elements shifting if `<body>` has an `overflow` style specified. ([#3083](https://github.com/mui/base-ui/pull/3083))\n- Add `<AlertDialog.Viewport>` part ([#2808](https://github.com/mui/base-ui/pull/2808))\n\n## Autocomplete\n\n- **Breaking change:** Refactor `alwaysSubmitOnEnter` to `submitOnItemClick` prop.<br />\n  If you were using `alwaysSubmitOnEnter`, be sure to update your usage to `submitOnItemClick` instead.\n  ([#3018](https://github.com/mui/base-ui/pull/3018))\n- Prevent blocking filtering while composing text on Android ([#2944](https://github.com/mui/base-ui/pull/2944))\n- Add empty state to `List.State` ([#2934](https://github.com/mui/base-ui/pull/2934))\n- Fix `initialFocus` as function being called on close ([#2949](https://github.com/mui/base-ui/pull/2949))\n- Add `role=\"combobox\"` to `<Autocomplete.Trigger>` if `<Autocomplete.Input>` is inside Popup ([#2973](https://github.com/mui/base-ui/pull/2973))\n- Fix stale `onItemHighlighted` data when filtering with `autoHighlight` ([#2829](https://github.com/mui/base-ui/pull/2829))\n- Add empty and side styling attributes on `<Autocomplete.Input>` ([#2926](https://github.com/mui/base-ui/pull/2926))\n- Fix `<Autocomplete.Value>` component return type for React 17 ([#3050](https://github.com/mui/base-ui/pull/3050))\n- Support `autoHighlight: \"always\"`, and add `keepHighlight`, `highlightItemOnHover` props ([#2976](https://github.com/mui/base-ui/pull/2976))\n- Keep focus on input when pressing list element ([#3092](https://github.com/mui/base-ui/pull/3092))\n- Allow <kbd>Esc</kbd> to bubble if `<Autocomplete.Empty>` is not used ([#2935](https://github.com/mui/base-ui/pull/2935))\n- Add `dialog` role to popup when input is inside ([#3213](https://github.com/mui/base-ui/pull/3213))\n\n## Button\n\n- New `<Button>` component ([#2363](https://github.com/mui/base-ui/pull/2363))\n\n## Checkbox\n\n- **Breaking change:** Render root as `<span>` instead of `<button>`\n  ([#3205](https://github.com/mui/base-ui/pull/3205))\n\n## Collapsible\n\n- Fix `starting-style` state ([#2985](https://github.com/mui/base-ui/pull/2985))\n\n## Combobox\n\n- Take into account `isItemEqualToValue` when selecting an option in multiple mode ([#2893](https://github.com/mui/base-ui/pull/2893))\n- Move `CompositeList` to `List` component to make `Input` work with composites ([#2883](https://github.com/mui/base-ui/pull/2883))\n- Fix `onValueChange` type inference when `value` is unspecified ([#2897](https://github.com/mui/base-ui/pull/2897))\n- Fix `required` form submission with multiple values ([#2925](https://github.com/mui/base-ui/pull/2925))\n- Fix <kbd>Home</kbd>/<kbd>End</kbd> Input scroll in Chrome/Safari ([#2928](https://github.com/mui/base-ui/pull/2928))\n- Prevent blocking filtering while composing text on Android ([#2944](https://github.com/mui/base-ui/pull/2944))\n- Add empty state to `List.State` ([#2934](https://github.com/mui/base-ui/pull/2934))\n- Fix `initialFocus` as function being called on close ([#2949](https://github.com/mui/base-ui/pull/2949))\n- Add `role=\"combobox\"` to `<Combobox.Trigger>` if `<Combobox.Input>` is inside Popup ([#2973](https://github.com/mui/base-ui/pull/2973))\n- Fix Field control ref when input is inside popup ([#2971](https://github.com/mui/base-ui/pull/2971))\n- Fix stale `onItemHighlighted` data when filtering with `autoHighlight` ([#2829](https://github.com/mui/base-ui/pull/2829))\n- Add empty and side styling attributes on `<Combobox.Input>` ([#2926](https://github.com/mui/base-ui/pull/2926))\n- Fix `<Combobox.Value>` component return type for React 17 ([#3050](https://github.com/mui/base-ui/pull/3050))\n- Fix input value derivation on `value` and `items` prop updates ([#3067](https://github.com/mui/base-ui/pull/3067))\n- Support `autoHighlight: \"always\"`, and add `keepHighlight`, `highlightItemOnHover` props ([#2976](https://github.com/mui/base-ui/pull/2976))\n- Keep focus on input when pressing list element ([#3092](https://github.com/mui/base-ui/pull/3092))\n- Fix support of dialog + combobox pattern ([#3049](https://github.com/mui/base-ui/pull/3049))\n- Support drag-to-select ([#3167](https://github.com/mui/base-ui/pull/3167))\n- Allow <kbd>Esc</kbd> to bubble if `<Combobox.Empty>` is not used ([#2935](https://github.com/mui/base-ui/pull/2935))\n- Fix stuck filtering with differing stringifiers ([#3201](https://github.com/mui/base-ui/pull/3201))\n- Add `dialog` role to popup when input is inside ([#3213](https://github.com/mui/base-ui/pull/3213))\n\n## Context Menu\n\n- Add `open` state to `<ContextMenu.Trigger>` ([#3195](https://github.com/mui/base-ui/pull/3195))\n- Fix ignored `anchor` prop on `<ContextMenu.Positioner>` ([#3202](https://github.com/mui/base-ui/pull/3202))\n\n## Dialog\n\n- **Breaking change:** Replace `dismissible` with `disablePointerDismissal`.<br />\n  If you were using `dismissible={false}`, replace it with `disablePointerDismissal`.\n  ([#3190](https://github.com/mui/base-ui/pull/3190))\n- Fix `initialFocus` as function being called on close ([#2949](https://github.com/mui/base-ui/pull/2949))\n- Support detached triggers ([#2974](https://github.com/mui/base-ui/pull/2974))\n- Place `overflow: hidden` on `<body>` for overlay scrollbars by default. Avoids sticky elements shifting if `<body>` has an `overflow` style specified. ([#3083](https://github.com/mui/base-ui/pull/3083))\n- Add `<Dialog.Viewport>` part and scrollable demos on docs ([#2808](https://github.com/mui/base-ui/pull/2808))\n\n## Field\n\n- **Breaking change:** Add `onSubmit` validation mode and make it the default over `onBlur`.<br />\n  Fields that use non-`required` attribute validation no longer validate the control on blur. Instead, validation first occurs `onSubmit`, and afterwards revalidation occurs `onChange`.\n  ([#3013](https://github.com/mui/base-ui/pull/3013))\n- Add `dirty` and `touched` props ([#2950](https://github.com/mui/base-ui/pull/2950))\n- New `<Field.Item>` part ([#2810](https://github.com/mui/base-ui/pull/2810))\n- Fix `validationMode=\"onChange\"` not clearing custom error state ([#3048](https://github.com/mui/base-ui/pull/3048))\n- Fix external `onChange` validation mode errors ([#3137](https://github.com/mui/base-ui/pull/3137))\n\n## Form\n\n- **Breaking change:** The `onClearErrors` prop has been removed.<br />\n  Errors from the `errors` prop are always cleared when the value changes.\n  ([#3136](https://github.com/mui/base-ui/pull/3136))\n- Add `onSubmit` validation mode.<br />\n  Additionally, `validationMode` can be set on `<Form>`. ([#3013](https://github.com/mui/base-ui/pull/3013))\n- Add `onFormSubmit` callback ([#3131](https://github.com/mui/base-ui/pull/3131))\n\n## Menu\n\n- **Breaking change:** Support detached triggers.<br />\n  `openOnHover`, `delay`, and `closeDelay` props have been moved from `<Menu.Root>` to `<Menu.Trigger>`.<br />\n  Additionally, menus now must have at least one `<Menu.Trigger>` element.\n  ([#3170](https://github.com/mui/base-ui/pull/3170))\n- Ignore disabled item on initial focusing ([#2604](https://github.com/mui/base-ui/pull/2604))\n- Fix stealing focus from dialogs on close ([#2920](https://github.com/mui/base-ui/pull/2920))\n- Place `overflow: hidden` on `<body>` for overlay scrollbars by default. Avoids sticky elements shifting if `<body>` has an `overflow` style specified. ([#3083](https://github.com/mui/base-ui/pull/3083))\n\n## Navigation Menu\n\n- Fix nested popup dismiss actions ([#2978](https://github.com/mui/base-ui/pull/2978))\n- Fix error on React 17 ([#3204](https://github.com/mui/base-ui/pull/3204))\n\n## Number Field\n\n- Granular change reasons ([#3132](https://github.com/mui/base-ui/pull/3132))\n\n## Popover\n\n- **Breaking change:** Support detached triggers and multiple triggers per popover.<br />\n  `openOnHover`, `delay`, and `closeDelay` props have been moved from `<Popover.Root>` to `<Popover.Trigger>`.\n  ([#2336](https://github.com/mui/base-ui/pull/2336))\n- Fix `initialFocus` as function being called on close ([#2949](https://github.com/mui/base-ui/pull/2949))\n- Fix swiping or scrolling on nested popup dismissing popover on touch ([#3011](https://github.com/mui/base-ui/pull/3011))\n- Place `overflow: hidden` on `<body>` for overlay scrollbars by default. Avoids sticky elements shifting if `<body>` has an `overflow` style specified. ([#3083](https://github.com/mui/base-ui/pull/3083))\n\n## Preview Card\n\n- **Breaking change:** Move delay props to trigger.<br />\n  If you were using `delay` or `closeDelay` props, be sure to move them to from `<PreviewCard.Root>` to the `<PreviewCard.Trigger>` component.\n  ([#3182](https://github.com/mui/base-ui/pull/3182))\n\n## Radio Group\n\n- **Breaking change:** Render root as `<span>` instead of `<button>`\n  ([#3205](https://github.com/mui/base-ui/pull/3205))\n\n## Scroll Area\n\n- **Breaking change:** Improve CSS vars performance.<br />\n  The CSS variables are now on the `<ScrollArea.Viewport>` part, not `<ScrollArea.Root>`, and inheritance is disabled for all child elements (or pseudo-elements). Children must manually opt in using `--scroll-area-[variable-name]: inherit`.\n  ([#3156](https://github.com/mui/base-ui/pull/3156))\n\n## Select\n\n- **Breaking change:** Make the trigger native button by default.<br />\n  The trigger now renders a `<button>` element, be sure to adjust your code if necessary.\n  ([#3177](https://github.com/mui/base-ui/pull/3177))\n- Add `open` state type on `Select.Icon` interface ([#2919](https://github.com/mui/base-ui/pull/2919))\n- Fix `onValueChange` type inference when `value` is unspecified ([#2897](https://github.com/mui/base-ui/pull/2897))\n- Fix `required` form submission with multiple values ([#2925](https://github.com/mui/base-ui/pull/2925))\n- Avoid re-rendering on popup height expansion ([#2972](https://github.com/mui/base-ui/pull/2972))\n- Place `overflow: hidden` on `<body>` for overlay scrollbars by default. Avoids sticky elements shifting if `<body>` has an `overflow` style specified. ([#3083](https://github.com/mui/base-ui/pull/3083))\n- Add `data-placeholder` attribute ([#2737](https://github.com/mui/base-ui/pull/2737))\n\n## Slider\n\n- **Breaking change:** Add `thumbCollisionBehavior` prop.<br />\n  In range sliders, moving a thumb with a pointer will now push other thumbs it collides with to avoid blocking drag movements by default (the default value is `push`).<br />\n  The value `swap` was also added, which allows thumbs to be dragged past each other when they collide.<br />\n  Lastly, the value `none` is the same as the previous behavior, where thumbs can't be dragged past one another.<br />\n  Keyboard interactions always use `none` behavior.\n  ([#2856](https://github.com/mui/base-ui/pull/2856))\n- Granular change reasons ([#3132](https://github.com/mui/base-ui/pull/3132))\n\n## Switch\n\n- **Breaking change:** Render root as `<span>` instead of `<button>`\n  ([#3205](https://github.com/mui/base-ui/pull/3205))\n\n## Tabs\n\n- **Breaking change:** Fix selected/active state naming consistency.<br />\n  - Renamed `[data-selected]` to `[data-active]` in `<Tabs.Tab>`\n  - Removed `[data-highlighted]` (`:focus-visible` was already the recommendation in styles)\n  - `selectedTabPosition`/`selectedTabSize` are now `activeTabPosition`/`activeTabSize` in `Tabs.Indicator.State`\n    ([#3024](https://github.com/mui/base-ui/pull/3024))\n- **Breaking change:** Change `activateOnFocus` to false.<br />\n  If you need your Tabs to activate on focus, be sure to add `activateOnFocus` prop.\n  ([#3176](https://github.com/mui/base-ui/pull/3176))\n- Fix Next.js 16 error from `Math.random` id generation ([#3051](https://github.com/mui/base-ui/pull/3051))\n- Fix indicator sizing and offsets ([#3214](https://github.com/mui/base-ui/pull/3214))\n\n## Toast\n\n- Allow `React.ReactNode` for `title`/`description` properties ([#2929](https://github.com/mui/base-ui/pull/2929))\n- Add ability to anchor to an element ([#3096](https://github.com/mui/base-ui/pull/3096))\n\n## Toolbar\n\n- **Breaking change:** The `cols` prop has been removed.<br />\n  This prop was not supposed to be exposed.\n  ([#3133](https://github.com/mui/base-ui/pull/3133))\n\n## Tooltip\n\n- **Breaking change:** Support detached triggers.<br />\n  `delay` and `closeDelay` props have been moved from `<Tooltip.Root>` to `<Tooltip.Trigger>`.\n  ([#3071](https://github.com/mui/base-ui/pull/3071))\n- **Breaking change:** Change `hoverable` to `disableHoverablePopup`.<br />\n  In case you need to disable the hoverable popup behavior, be sure to add the `disableHoverablePopup` prop.\n  ([#3178](https://github.com/mui/base-ui/pull/3178))\n- Fix `data-instant` ending transition of same tooltip ([#2962](https://github.com/mui/base-ui/pull/2962))\n"
  },
  {
    "path": "docs/src/app/(docs)/react/overview/releases/v1-0-0-beta-6/page.mdx",
    "content": "# v1.0.0-beta.6\n\n<Subtitle>Nov 17, 2025</Subtitle>\n<Meta name=\"description\" content=\"v1.0.0-beta.6 release notes. Nov 17, 2025.\" />\n\nThis is a hotfix release with the following changes:\n\n- Fix for rendering of Alert Dialog, Dialog, Menu, Popover, and Tooltip in React Server Components ([#3241](https://github.com/mui/base-ui/pull/3241))\n- Fix of the types of the refs in the Checkbox, Switch and Radio components ([#3246](https://github.com/mui/base-ui/pull/3246))\n- Fix of the value type error with mergeProps ([#3247](https://github.com/mui/base-ui/pull/3247))\n"
  },
  {
    "path": "docs/src/app/(docs)/react/overview/releases/v1-0-0-beta-7/page.mdx",
    "content": "# v1.0.0-beta.7\n\n<Subtitle>Nov 27, 2025</Subtitle>\n<Meta name=\"description\" content=\"v1.0.0-beta.7 release notes. Nov 27, 2025.\" />\n\n## General changes\n\n- Fix error about `props.ref` access in React &lt;=18 ([#3257](https://github.com/mui/base-ui/pull/3257))\n- Prefer non-adaptive anchoring position in `<Positioner>` components and fix `autoFocus` scroll jumps ([#3250](https://github.com/mui/base-ui/pull/3250))\n- Make popups' `data-anchor-hidden` state attribute check for anchor presence in layout ([#3267](https://github.com/mui/base-ui/pull/3267))\n- Prevent popups from sticking after hover when pressing `<a>` tags inside them ([#3318](https://github.com/mui/base-ui/pull/3318))\n- Improve performance when detached triggers are used ([#3277](https://github.com/mui/base-ui/pull/3277))\n- Fix iOS VoiceOver voice control accessibility in non-modal popups ([#3340](https://github.com/mui/base-ui/pull/3340))\n\n## Alert Dialog\n\n- Fix trigger registration loop ([#3249](https://github.com/mui/base-ui/pull/3249))\n- Fix focus restoration when focused element is hidden with CSS ([#3313](https://github.com/mui/base-ui/pull/3313))\n\n## Checkbox Group\n\n- Fix `aria-describedby` on checkbox group ([#3269](https://github.com/mui/base-ui/pull/3269))\n\n## Combobox\n\n- Revert overload types to ensure typed wrappers work correctly ([#3254](https://github.com/mui/base-ui/pull/3254))\n- Fix ignored `filteredItems` instances ([#3272](https://github.com/mui/base-ui/pull/3272))\n- Fix loop when passing `undefined` to `items` prop ([#3348](https://github.com/mui/base-ui/pull/3348))\n\n## Context Menu\n\n- Block mouseup at initial cursor point ([#3274](https://github.com/mui/base-ui/pull/3274))\n\n## Dialog\n\n- Fix trigger registration loop ([#3249](https://github.com/mui/base-ui/pull/3249))\n- Fix focus restoration when focused element is hidden with CSS ([#3313](https://github.com/mui/base-ui/pull/3313))\n\n## Form\n\n- Fix cast `ref` type ([#3324](https://github.com/mui/base-ui/pull/3324))\n\n## Menu\n\n- Fix trigger registration loop ([#3249](https://github.com/mui/base-ui/pull/3249))\n- Do not pass `key` to the rendered element ([#3255](https://github.com/mui/base-ui/pull/3255))\n- Fix nested dialog from closing on <kbd>Shift+Tab</kbd> ([#3346](https://github.com/mui/base-ui/pull/3346))\n- Ensure submenu triggers participate in composite list ([#3344](https://github.com/mui/base-ui/pull/3344))\n\n## Navigation Menu\n\n- Fix Safari 18 issue where `<Positioner>` width may be set to 0 on hover ([#3309](https://github.com/mui/base-ui/pull/3309))\n\n## Number Field\n\n- Fix literal space handling with symbols ([#3334](https://github.com/mui/base-ui/pull/3334))\n\n## Popover\n\n- Fix trigger registration loop ([#3249](https://github.com/mui/base-ui/pull/3249))\n- Do not pass `key` to the rendered element ([#3255](https://github.com/mui/base-ui/pull/3255))\n- Fix focus restoration when focused element is hidden with CSS ([#3313](https://github.com/mui/base-ui/pull/3313))\n\n## Select\n\n- Revert overload types to ensure typed wrappers work correctly ([#3254](https://github.com/mui/base-ui/pull/3254))\n\n## Slider\n\n- Fix extra `onValueCommitted` calls ([#3312](https://github.com/mui/base-ui/pull/3312))\n- Fix cast `ref` type ([#3324](https://github.com/mui/base-ui/pull/3324))\n\n## Tooltip\n\n- Fix trigger registration loop ([#3249](https://github.com/mui/base-ui/pull/3249))\n"
  },
  {
    "path": "docs/src/app/(docs)/react/overview/releases/v1-0-0-rc-0/page.mdx",
    "content": "# v1.0.0-rc.0\n\n<Subtitle>Dec 4, 2025</Subtitle>\n<Meta name=\"description\" content=\"v1.0.0-rc.0 release notes. Dec 4, 2025.\" />\n\n## General changes\n\n- Fix missing `'use client'` directives ([#3408](https://github.com/mui/base-ui/pull/3408))\n\n## Autocomplete\n\n- Fix `keepHighlight` focus sync ([#3399](https://github.com/mui/base-ui/pull/3399))\n\n## Checkbox\n\n- **Breaking change:** Match native unchecked state in form submission.<br />\n  The Checkbox will not submit the `\"off\"` value with a form when unchecked anymore, unless the new `uncheckedValue` prop is set.\n  ([#3406](https://github.com/mui/base-ui/pull/3406))\n\n## Collapsible\n\n- Remove `render={null}` ([#3407](https://github.com/mui/base-ui/pull/3407))\n\n## Combobox\n\n- **Breaking change:** Removed the `keepHighlight` prop ([#3377](https://github.com/mui/base-ui/pull/3377))\n\n## Dialog\n\n- Close when pressing focusable element outside ([#3380](https://github.com/mui/base-ui/pull/3380))\n- Fix closing after pointer lock exit in Firefox ([#3379](https://github.com/mui/base-ui/pull/3379))\n\n## Menu\n\n- Add `highlightItemOnHover` prop ([#3377](https://github.com/mui/base-ui/pull/3377))\n- Do not import client components from MenuStore ([#3409](https://github.com/mui/base-ui/pull/3409))\n\n## Number Field\n\n- Ensure hidden input participates in form validation ([#3374](https://github.com/mui/base-ui/pull/3374))\n- Improve symbol replacement logic ([#3376](https://github.com/mui/base-ui/pull/3376))\n- Fix fractional step snapping ([#3375](https://github.com/mui/base-ui/pull/3375))\n- Fix parsing numbers with Swiss locale ([#3361](https://github.com/mui/base-ui/pull/3361))\n- Fix pointer lock release when soft clicking in Firefox ([#3378](https://github.com/mui/base-ui/pull/3378))\n\n## Popover\n\n- Close when pressing focusable element outside ([#3380](https://github.com/mui/base-ui/pull/3380))\n- Fix modal backdrop on touch ([#3383](https://github.com/mui/base-ui/pull/3383))\n- Fix popover glitching when flipped ([#3364](https://github.com/mui/base-ui/pull/3364))\n\n## Select\n\n- Add `highlightItemOnHover` prop ([#3377](https://github.com/mui/base-ui/pull/3377))\n\n## Switch\n\n- **Breaking change:** Match native off state in form submission.<br />\n  The Switch will not submit the `\"off\"` value with a form when unchecked anymore, unless the new `uncheckedValue` prop is set.\n  ([#3406](https://github.com/mui/base-ui/pull/3406))\n\n## Tabs\n\n- **Breaking change:** Fix Panel `keepMounted` behavior.<br />\n  The `value` prop is now required on `<Tabs.Tab>` and `<Tabs.Panel>`.\n  ([#3372](https://github.com/mui/base-ui/pull/3372))\n\n## Toast\n\n- Recalculate content height when layout size is fixed ([#3359](https://github.com/mui/base-ui/pull/3359))\n- Fix multiple swipe directions on same axis ([#3392](https://github.com/mui/base-ui/pull/3392))\n\n## Tooltip\n\n- Improve contained triggers performance ([#3385](https://github.com/mui/base-ui/pull/3385))\n"
  },
  {
    "path": "docs/src/app/(docs)/react/overview/releases/v1-0-0-rc-1/page.mdx",
    "content": "# v1.0.0-rc.1\n\n<Subtitle>Dec 11, 2025</Subtitle>\n<Meta name=\"description\" content=\"v1.0.0-rc.1 release notes. Dec 11, 2025.\" />\n\nThis release contains the same code as v1.0.0.\nPlease refer to that version to see the changes.\n"
  },
  {
    "path": "docs/src/app/(docs)/react/overview/releases/v1-0-0-rc-2/page.mdx",
    "content": "# v1.0.0-rc.2\n\n<Subtitle>Dec 11, 2025</Subtitle>\n<Meta name=\"description\" content=\"v1.0.0-rc.2 release notes. Dec 11, 2025.\" />\n\nThis release contains the same code as v1.0.0.\nPlease refer to that version to see the changes.\n"
  },
  {
    "path": "docs/src/app/(docs)/react/overview/releases/v1-1-0/page.mdx",
    "content": "# v1.1.0\n\n<Subtitle>Jan 15, 2026</Subtitle>\n<Meta name=\"description\" content=\"v1.1.0 release notes. Jan 15, 2026.\" />\n\n## General changes\n\n- Fix `onOpenChangeComplete(true)` timing ([#3558](https://github.com/mui/base-ui/pull/3558))\n- Fix touch `openMethod` when tapping outside element bounds on Safari ([#3541](https://github.com/mui/base-ui/pull/3541))\n- Fix visually hidden input styles across form components ([#3606](https://github.com/mui/base-ui/pull/3606))\n- Fix click and drags outside a nested popup component from closing its parents ([#3571](https://github.com/mui/base-ui/pull/3571))\n- Fix forwarded ref types ([#3638](https://github.com/mui/base-ui/pull/3638))\n- Fix detached trigger remounting ([#3724](https://github.com/mui/base-ui/pull/3724))\n- Include `ref` in `BaseUIComponentProps` ([#2813](https://github.com/mui/base-ui/pull/2813))\n- Remove duplicated `disabled` prop ([#3650](https://github.com/mui/base-ui/pull/3650))\n- Allow `actionsRef` to be `null` ([#3682](https://github.com/mui/base-ui/pull/3682))\n\n## Accordion\n\n- Fix keyboard navigation with non-interactive trigger elements ([#3684](https://github.com/mui/base-ui/pull/3684))\n\n## Autocomplete\n\n- Add `data-popup-side` and `data-list-empty` state attributes to `<Autocomplete.Trigger>` ([#3491](https://github.com/mui/base-ui/pull/3491))\n- Add `loopFocus` prop ([#3592](https://github.com/mui/base-ui/pull/3592))\n- Fix hidden input `id` and `required` props ([#3640](https://github.com/mui/base-ui/pull/3640))\n\n## Button\n\n- Remove discriminated props union ([#3643](https://github.com/mui/base-ui/pull/3643))\n\n## Checkbox\n\n- Fix hidden input `id` and `required` props ([#3640](https://github.com/mui/base-ui/pull/3640))\n\n## Combobox\n\n- Add `data-popup-side` and `data-list-empty` state attributes to `<Combobox.Trigger>` ([#3491](https://github.com/mui/base-ui/pull/3491))\n- Add `loopFocus` prop ([#3592](https://github.com/mui/base-ui/pull/3592))\n- Add `toolbar` role to `<Combobox.Chips>` to prevent NVDA from entering browse mode ([#3647](https://github.com/mui/base-ui/pull/3647))\n- Add `placeholder` prop to `<Combobox.Value>` ([#3604](https://github.com/mui/base-ui/pull/3604))\n- Fix controlled `value` prop when `items` change ([#3607](https://github.com/mui/base-ui/pull/3607))\n- Fix `multiple` values label resolution in `<Combobox.Value>` ([#3314](https://github.com/mui/base-ui/pull/3314))\n- Forward root `id` to visible form element ([#3722](https://github.com/mui/base-ui/pull/3722))\n- Do not trigger Field `onBlur` handlers when opening popup ([#3609](https://github.com/mui/base-ui/pull/3609))\n\n## Context Menu\n\n- Avoid creating sibling elements next to trigger ([#3645](https://github.com/mui/base-ui/pull/3645))\n\n## CSP Provider\n\n- Add `CSPProvider` ([#3553](https://github.com/mui/base-ui/pull/3553))\n\n## Dialog\n\n- Fix `Maximum update depth exceeded` error with Suspense ([#3700](https://github.com/mui/base-ui/pull/3700))\n- Fix `<Dialog.Title>` forwardedRef type ([#3736](https://github.com/mui/base-ui/pull/3736))\n\n## Field\n\n- Add `actionsRef` prop ([#3395](https://github.com/mui/base-ui/pull/3395))\n- Add `nativeLabel` prop to `<Field.Label>` ([#3723](https://github.com/mui/base-ui/pull/3723))\n- Add missing type export ([#3702](https://github.com/mui/base-ui/pull/3702))\n\n## Form\n\n- Add `actionsRef` prop ([#3395](https://github.com/mui/base-ui/pull/3395))\n\n## Menu\n\n- Fix focus guard handling ([#3654](https://github.com/mui/base-ui/pull/3654))\n- Avoid disabling modality on click after hover-open ([#3455](https://github.com/mui/base-ui/pull/3455))\n\n## Menubar\n\n- Fix submenu outside-press dismiss on touch ([#3556](https://github.com/mui/base-ui/pull/3556))\n\n## Number Field\n\n- Fix Field `data-focused` state ([#3563](https://github.com/mui/base-ui/pull/3563))\n- Fix hidden input focus on submit ([#3581](https://github.com/mui/base-ui/pull/3581))\n\n## Popover\n\n- Fix popup auto resize glitches ([#3591](https://github.com/mui/base-ui/pull/3591))\n- Fix focus guard handling ([#3654](https://github.com/mui/base-ui/pull/3654))\n- Prevent disabling focus management when clicking trigger before hover delay completes ([#3572](https://github.com/mui/base-ui/pull/3572))\n- Refactor popup auto resize logic. It is no longer necessary to specify `--positioner-width`/`--positioner-height` CSS variables on `<Popover.Positioner>` when using detached triggers unless the `Viewport` part has been added to the JSX. ([#3652](https://github.com/mui/base-ui/pull/3652))\n\n## Preview Card\n\n- Support detached triggers ([#3566](https://github.com/mui/base-ui/pull/3566))\n\n## Radio Group\n\n- Fix `value` type ([#3582](https://github.com/mui/base-ui/pull/3582))\n- Fix hidden input `id` and `required` props ([#3640](https://github.com/mui/base-ui/pull/3640))\n\n## Scroll Area\n\n- Perf improvements ([#3536](https://github.com/mui/base-ui/pull/3536))\n\n## Select\n\n- Add `placeholder` prop to `<Select.Value>` ([#3604](https://github.com/mui/base-ui/pull/3604))\n- Fix support for transform animations when `alignItemWithTrigger` is active ([#3532](https://github.com/mui/base-ui/pull/3532))\n- Fix support for `max-height` popup style when `alignItemWithTrigger` is active ([#3573](https://github.com/mui/base-ui/pull/3573))\n- Fix `data-filled` state in `multiple` mode ([#3608](https://github.com/mui/base-ui/pull/3608))\n- Fix highlight being removed on popup mouseout when `highlightItemOnHover` is disabled ([#3492](https://github.com/mui/base-ui/pull/3492))\n- Fix support for individual transform animations when `alignItemWithTrigger` is active ([#3637](https://github.com/mui/base-ui/pull/3637))\n- Fix `multiple` values label resolution in `<Select.Value>` ([#3314](https://github.com/mui/base-ui/pull/3314))\n- Forward root `id` to visible form element ([#3722](https://github.com/mui/base-ui/pull/3722))\n- Do not trigger Field `onBlur` handlers when opening popup ([#3609](https://github.com/mui/base-ui/pull/3609))\n\n## Slider\n\n- Fix `onValueCommitted` not called for range sliders ([#3600](https://github.com/mui/base-ui/pull/3600))\n\n## Switch\n\n- Add `value` prop ([#3676](https://github.com/mui/base-ui/pull/3676))\n- Fix hidden input `id` and `required` props ([#3640](https://github.com/mui/base-ui/pull/3640))\n\n## Toast\n\n- Fix timers not being rescheduled when updated ([#3564](https://github.com/mui/base-ui/pull/3564))\n\n## Tooltip\n\n- Fix popup auto resize glitches ([#3591](https://github.com/mui/base-ui/pull/3591))\n- Fix `trackCursorAxis` handling ([#3679](https://github.com/mui/base-ui/pull/3679))\n- Refactor popup auto resize logic. It is no longer necessary to specify `--positioner-width`/`--positioner-height` CSS variables on `<Tooltip.Positioner>` when using detached triggers unless the `Viewport` part has been added to the JSX. ([#3652](https://github.com/mui/base-ui/pull/3652))\n\n## mergeProps\n\n- Make `mergeProps` public ([#3642](https://github.com/mui/base-ui/pull/3642))\n\n## useRender\n\n- Export missing types ([#3565](https://github.com/mui/base-ui/pull/3565))\n"
  },
  {
    "path": "docs/src/app/(docs)/react/overview/releases/v1-2-0/page.mdx",
    "content": "# v1.2.0\n\n<Subtitle>Feb 12, 2026</Subtitle>\n<Meta name=\"description\" content=\"v1.2.0 release notes. Feb 12, 2026.\" />\n\n## General changes\n\n- Do not memoize `state` when not needed ([#3812](https://github.com/mui/base-ui/pull/3812))\n- Support lazy element in `render` prop ([#3856](https://github.com/mui/base-ui/pull/3856))\n- Replace Firefox deprecated mozInputSource check for virtual click detection ([#3942](https://github.com/mui/base-ui/pull/3942))\n- Use `WeakRef` for previously focused elements ([#3916](https://github.com/mui/base-ui/pull/3916))\n- Fix page scroll jump when input has focus on unmount in Safari ([#3925](https://github.com/mui/base-ui/pull/3925))\n- Fix flash at origin before positioning completes in Preact ([#3975](https://github.com/mui/base-ui/pull/3975))\n- Reduce style recalculation with classic scrollbars ([#3854](https://github.com/mui/base-ui/pull/3854))\n- Fix event handling in useEnhancedClickHandler ([#3981](https://github.com/mui/base-ui/pull/3981))\n\n## Autocomplete\n\n- Fix filter method's `useMemo` dependency ([#3862](https://github.com/mui/base-ui/pull/3862))\n- Fix Autocomplete not using its internal filter method when `mode` is `list` ([#3936](https://github.com/mui/base-ui/pull/3936))\n- Remove unnecessary double stringification of item in filtering logic ([#3945](https://github.com/mui/base-ui/pull/3945))\n- Add `useFilteredItems` hook ([#3732](https://github.com/mui/base-ui/pull/3732))\n- Fix popup closing on iOS VoiceOver ([#3859](https://github.com/mui/base-ui/pull/3859))\n- Remove `aria-readonly` prop from `Clear` and `Popup` components when `readOnly` ([#3907](https://github.com/mui/base-ui/pull/3907))\n\n## Avatar\n\n- Add transition attributes ([#3939](https://github.com/mui/base-ui/pull/3939))\n\n## Button\n\n- Capture component stack for `nativeButton` error message ([#3861](https://github.com/mui/base-ui/pull/3861))\n\n## Checkbox\n\n- Cleanup disabled state tracking ([#3913](https://github.com/mui/base-ui/pull/3913))\n- Preserve modifier key properties in the change event ([#3935](https://github.com/mui/base-ui/pull/3935))\n- Allow exit animations on `<Checkbox.Indicator>` when `keepMounted={false}` ([#3939](https://github.com/mui/base-ui/pull/3939))\n\n## Combobox\n\n- Fix the type of the ref of the `Icon` part ([#3796](https://github.com/mui/base-ui/pull/3796))\n- Avoid clearing selected value if item is not present in items array ([#3824](https://github.com/mui/base-ui/pull/3824))\n- Fix highlight change reason in `ChipRemove` ([#3980](https://github.com/mui/base-ui/pull/3980))\n- Keep highlight on last deselect ([#3923](https://github.com/mui/base-ui/pull/3923))\n- Fix inline filtering after selection in single mode ([#3978](https://github.com/mui/base-ui/pull/3978))\n- Clear highlight on inline blur when inline ([#3973](https://github.com/mui/base-ui/pull/3973))\n- Prevent opening popup on autofill change ([#3924](https://github.com/mui/base-ui/pull/3924))\n- Distinguish `input-press` from `trigger-press` in `onOpenChange` reason ([#4015](https://github.com/mui/base-ui/pull/4015))\n- Fix async items while popup is open ([#4034](https://github.com/mui/base-ui/pull/4034))\n- Prevent `Chip` from receiving focus when `disabled` ([#4044](https://github.com/mui/base-ui/pull/4044))\n- Add `useFilteredItems` hook ([#3732](https://github.com/mui/base-ui/pull/3732))\n- Fix popup closing on iOS VoiceOver ([#3859](https://github.com/mui/base-ui/pull/3859))\n- Remove `aria-readonly` prop from `Clear` and `Popup` components when `readOnly` ([#3907](https://github.com/mui/base-ui/pull/3907))\n- Fix `onClick` `Item` type ([#3964](https://github.com/mui/base-ui/pull/3964))\n- Use reactive `domReferenceElement` subscriptions ([#4017](https://github.com/mui/base-ui/pull/4017))\n- Add `autoComplete` prop for explicit browser autofill support ([#4005](https://github.com/mui/base-ui/pull/4005))\n- Fix inconsistent `isItemEqualToValue` argument order ([#4056](https://github.com/mui/base-ui/pull/4056))\n\n## Context Menu\n\n- Fix `disabled` prop not working ([#3806](https://github.com/mui/base-ui/pull/3806))\n- Fix explicit `collisionAvoidance` with `side: 'flip'` not working ([#3877](https://github.com/mui/base-ui/pull/3877))\n\n## Drawer\n\n- Create new Drawer / Sheet component ([#3680](https://github.com/mui/base-ui/pull/3680))\n\n## Field\n\n- Prevent re-renders when `Field.Control` is uncontrolled ([#3820](https://github.com/mui/base-ui/pull/3820))\n- Fix autofocus in SSR environments ([#3871](https://github.com/mui/base-ui/pull/3871))\n- Fix max update depth loop when using `<React.Activity>` ([#3931](https://github.com/mui/base-ui/pull/3931))\n- Add transition attributes ([#3939](https://github.com/mui/base-ui/pull/3939))\n\n## Input\n\n- Fix autofocus in SSR environments ([#3871](https://github.com/mui/base-ui/pull/3871))\n- Update ref type to `HTMLElement` ([#3866](https://github.com/mui/base-ui/pull/3866))\n\n## Menu\n\n- Fix `onClick` `Item` type ([#3964](https://github.com/mui/base-ui/pull/3964))\n- Fix submenu stuck glitch ([#3783](https://github.com/mui/base-ui/pull/3783))\n- Fix race conditions ([#3821](https://github.com/mui/base-ui/pull/3821))\n- Add `<Menu.LinkItem>` part ([#3400](https://github.com/mui/base-ui/pull/3400))\n\n## Navigation Menu\n\n- Fix forwarded ref types ([#3775](https://github.com/mui/base-ui/pull/3775))\n- Add `keepMounted` prop to `Content` part ([#3794](https://github.com/mui/base-ui/pull/3794))\n\n## Number Field\n\n- Fix click handlers on ScrubArea ([#3827](https://github.com/mui/base-ui/pull/3827))\n- Remove `event.isTrusted` ([#3920](https://github.com/mui/base-ui/pull/3920))\n- Stop repeat change at bounds ([#3915](https://github.com/mui/base-ui/pull/3915))\n- Add `allowOutOfRange` prop ([#3919](https://github.com/mui/base-ui/pull/3919))\n- Fix pen pointer handling ([#3917](https://github.com/mui/base-ui/pull/3917))\n- Fix missing field state data attributes ([#3909](https://github.com/mui/base-ui/pull/3909))\n\n## Popover\n\n- Fix missing `aria-owns` element ([#3959](https://github.com/mui/base-ui/pull/3959))\n- Use reactive `domReferenceElement` subscriptions ([#4017](https://github.com/mui/base-ui/pull/4017))\n- Fix broken scale transition with detached triggers ([#3810](https://github.com/mui/base-ui/pull/3810))\n\n## Preview Card\n\n- Fix broken scale transition with detached triggers ([#3810](https://github.com/mui/base-ui/pull/3810))\n\n## Progress\n\n- De-duplicate `formatValue` function ([#3805](https://github.com/mui/base-ui/pull/3805))\n\n## Radio Group\n\n- Preserve modifier key properties in the change event ([#3935](https://github.com/mui/base-ui/pull/3935))\n- Allow exit animations on `<Radio.Indicator>` when `keepMounted={false}` ([#3939](https://github.com/mui/base-ui/pull/3939))\n- Rely on individual radio hidden inputs ([#3826](https://github.com/mui/base-ui/pull/3826))\n- Add generic `Value` typing to `Radio` ([#4033](https://github.com/mui/base-ui/pull/4033))\n\n## Scroll Area\n\n- Add `data-scrolling` state attribute to `Root` and `Viewport` parts ([#3823](https://github.com/mui/base-ui/pull/3823))\n- Fix overflow edge rounding ([#3888](https://github.com/mui/base-ui/pull/3888))\n\n## Select\n\n- Add `finalFocus` prop ([#3785](https://github.com/mui/base-ui/pull/3785))\n- Fix `alignItemWithTrigger` transform with CSS animations ([#3831](https://github.com/mui/base-ui/pull/3831))\n- Fix `highlightItemOnHover` not being respected ([#3868](https://github.com/mui/base-ui/pull/3868))\n- Reset typeahead on external blur ([#2618](https://github.com/mui/base-ui/pull/2618))\n- Fix scroll height loop ([#3795](https://github.com/mui/base-ui/pull/3795))\n- Add `autoComplete` prop for explicit browser autofill support ([#4005](https://github.com/mui/base-ui/pull/4005))\n- Fix inconsistent `isItemEqualToValue` argument order ([#4056](https://github.com/mui/base-ui/pull/4056))\n\n## Slider\n\n- Fix missing field state data attributes ([#3909](https://github.com/mui/base-ui/pull/3909))\n- Fix change event cloning ([#3960](https://github.com/mui/base-ui/pull/3960))\n\n## Switch\n\n- Preserve modifier key properties in the change event ([#3935](https://github.com/mui/base-ui/pull/3935))\n\n## Tabs\n\n- Add transition attributes to `<Tabs.Panel>` part ([#3880](https://github.com/mui/base-ui/pull/3880))\n\n## Toast\n\n- Make `useToastManager` and `createToastManager` generic functions ([#3882](https://github.com/mui/base-ui/pull/3882))\n- Prevent dismissed promise toast from reopening on updates ([#4040](https://github.com/mui/base-ui/pull/4040))\n- Introduce a store ([#3464](https://github.com/mui/base-ui/pull/3464))\n\n## Toggle\n\n- Improve type safety and inference ([#3173](https://github.com/mui/base-ui/pull/3173))\n\n## Toggle Group\n\n- Type value as string to match Toggle ([#3770](https://github.com/mui/base-ui/pull/3770))\n- Enable `Home`/`End` key navigation ([#3971](https://github.com/mui/base-ui/pull/3971))\n- Improve type safety and inference ([#3173](https://github.com/mui/base-ui/pull/3173))\n\n## Tooltip\n\n- Prevent opening when focusing a disabled Trigger ([#3902](https://github.com/mui/base-ui/pull/3902))\n- Fix broken scale transition with detached triggers ([#3810](https://github.com/mui/base-ui/pull/3810))\n- Fix disabled prop on Triggers ([#4049](https://github.com/mui/base-ui/pull/4049))\n"
  },
  {
    "path": "docs/src/app/(docs)/react/overview/releases/v1-3-0/page.mdx",
    "content": "# v1.3.0\n\n<Subtitle>Mar 12, 2026</Subtitle>\n<Meta name=\"description\" content=\"v1.3.0 release notes. Mar 12, 2026.\" />\n\n## General Changes\n\n- Warn when a component function is rendered directly ([#4077](https://github.com/mui/base-ui/pull/4077))\n- Reset `openMethod` after close transition ([#4128](https://github.com/mui/base-ui/pull/4128))\n- Fire <kbd>Space</kbd> activation on `keydown` in composite widgets ([#4053](https://github.com/mui/base-ui/pull/4053))\n- Skip CSS-hidden items during keyboard navigation in composite widgets ([#4195](https://github.com/mui/base-ui/pull/4195))\n- Optimize hot paths in `useHover` hooks and `safePolygon` ([#4199](https://github.com/mui/base-ui/pull/4199))\n- Snap `--anchor-width` and `--anchor-height` to device pixel grid ([#4082](https://github.com/mui/base-ui/pull/4082))\n- Fix outside press dismissal when a component is portaled into a shadow DOM ([#4230](https://github.com/mui/base-ui/pull/4230))\n- Fix nested hoverable popups ([#4206](https://github.com/mui/base-ui/pull/4206))\n- Apply `data-base-ui-inert` to highest-level node ([#3955](https://github.com/mui/base-ui/pull/3955))\n- Fix portable types ([#4058](https://github.com/mui/base-ui/pull/4058))\n\n## Accordion\n\n- Add generic `Value` typing ([#4138](https://github.com/mui/base-ui/pull/4138))\n\n## Autocomplete\n\n- Respect a `null` `filter` prop ([#4117](https://github.com/mui/base-ui/pull/4117))\n- Add `InputGroup` part ([#3745](https://github.com/mui/base-ui/pull/3745))\n\n## Avatar\n\n- Remove fallback transition logic and prevent premature image display ([#4110](https://github.com/mui/base-ui/pull/4110))\n\n## Button\n\n- Avoid checking `disabled` twice in `onKeyDown` and `onKeyUp` ([#4132](https://github.com/mui/base-ui/pull/4132))\n\n## Checkbox\n\n- Add automatic `aria-labelledby` support ([#4142](https://github.com/mui/base-ui/pull/4142))\n\n## Combobox\n\n- Avoid applying field attributes to input when it is inside popup ([#4154](https://github.com/mui/base-ui/pull/4154))\n- Preserve inline input on `Enter` when nothing is highlighted ([#4235](https://github.com/mui/base-ui/pull/4235))\n- Fix ARIA attributes during SSR ([#4179](https://github.com/mui/base-ui/pull/4179))\n- Fix wrapping in virtualized grid arrow-key navigation ([#4164](https://github.com/mui/base-ui/pull/4164))\n- Add `InputGroup` part ([#3745](https://github.com/mui/base-ui/pull/3745))\n- Add support for a visually hidden close button and improve modal focus trapping ([#4084](https://github.com/mui/base-ui/pull/4084))\n- Add `Label` part ([#4167](https://github.com/mui/base-ui/pull/4167))\n\n## Context Menu\n\n- Ignore `mouseup` on non-Mac platforms ([#3944](https://github.com/mui/base-ui/pull/3944))\n\n## Drawer\n\n- **Breaking change:** `Drawer` is no longer marked as preview<br />\n  `Drawer` is now stable and should be imported as `{ Drawer } from '@base-ui/react/drawer'` ([#4293](https://github.com/mui/base-ui/pull/4293))\n- Fix React 17 support ([#4178](https://github.com/mui/base-ui/pull/4178))\n- Include border in frontmost height variable ([#4202](https://github.com/mui/base-ui/pull/4202))\n- Improve touch selection ([#4104](https://github.com/mui/base-ui/pull/4104))\n- Preserve cross-axis scrolling during touch gestures ([#4187](https://github.com/mui/base-ui/pull/4187))\n- Prevent swipe dismissal when component is controlled ([#4133](https://github.com/mui/base-ui/pull/4133))\n- Add `SwipeArea` part ([#4102](https://github.com/mui/base-ui/pull/4102))\n- Make `data-base-ui-swipe-ignore` explicit for touch interactions ([#4295](https://github.com/mui/base-ui/pull/4295))\n- Disable inheritance for swipe CSS variables ([#4099](https://github.com/mui/base-ui/pull/4099))\n\n## Field\n\n- Fix field validation when `Form` errors or `invalid` prop are present at same time ([#4112](https://github.com/mui/base-ui/pull/4112))\n\n## Menu\n\n- Prevent `pointerleave` from stealing focus from dialogs ([#4125](https://github.com/mui/base-ui/pull/4125))\n- Optimize `pointer-events` for submenu hover interactions ([#4231](https://github.com/mui/base-ui/pull/4231))\n- Fix `closeDelay` not being applied to `Menu.SubmenuTrigger` ([#4134](https://github.com/mui/base-ui/pull/4134))\n- Implement content transitions with `Viewport` ([#4060](https://github.com/mui/base-ui/pull/4060))\n\n## Meter\n\n- Fix label announcements in NVDA ([#4200](https://github.com/mui/base-ui/pull/4200))\n\n## Navigation Menu\n\n- Fix support for nested inline menus ([#4198](https://github.com/mui/base-ui/pull/4198))\n- Fix close propagation in nested hover menus ([#4285](https://github.com/mui/base-ui/pull/4285))\n- Close parent menus when nested link with `closeOnClick` is clicked ([#4276](https://github.com/mui/base-ui/pull/4276))\n- Fix duplicate `aria-orientation` ([#4309](https://github.com/mui/base-ui/pull/4309))\n- Fix delayed trigger switches in Safari ([#4310](https://github.com/mui/base-ui/pull/4310))\n\n## Number Field\n\n- Fix increment/decrement press `reason` values in `onValueCommitted` ([#4259](https://github.com/mui/base-ui/pull/4259))\n\n## Popover\n\n- Trap focus when `<Popover.Close>` is rendered inside and `modal` is `true`, and add support for a visually hidden close button ([#4084](https://github.com/mui/base-ui/pull/4084))\n- Fix nested hoverable popups ([#3798](https://github.com/mui/base-ui/pull/3798))\n\n## Preview Card\n\n- Fix nested hoverable popups ([#3798](https://github.com/mui/base-ui/pull/3798))\n\n## Progress\n\n- Fix label announcements in NVDA ([#4200](https://github.com/mui/base-ui/pull/4200))\n\n## Radio Group\n\n- Add automatic `aria-labelledby` support ([#4142](https://github.com/mui/base-ui/pull/4142))\n\n## Scroll Area\n\n- Fix focus trapping with a non-scrollable viewport ([#4220](https://github.com/mui/base-ui/pull/4220))\n- Fix thumb size after remounting ([#4107](https://github.com/mui/base-ui/pull/4107))\n\n## Select\n\n- Fix hidden input `id` fallback ([#4135](https://github.com/mui/base-ui/pull/4135))\n- Fix `Value` placeholder not rendering with `Record` items ([#4137](https://github.com/mui/base-ui/pull/4137))\n- Fire `onClick` during drag-to-select ([#3969](https://github.com/mui/base-ui/pull/3969))\n- Fix `items` type definition for groups ([#3884](https://github.com/mui/base-ui/pull/3884))\n- Fix `alignItemWithTrigger` fallback with browser zoom ([#4292](https://github.com/mui/base-ui/pull/4292))\n- Add `Label` part ([#4167](https://github.com/mui/base-ui/pull/4167))\n\n## Slider\n\n- Add `Label` part ([#4167](https://github.com/mui/base-ui/pull/4167))\n- Stop committing validation on `touchend` ([#4091](https://github.com/mui/base-ui/pull/4091))\n\n## Switch\n\n- Add automatic `aria-labelledby` support ([#4142](https://github.com/mui/base-ui/pull/4142))\n\n## Tabs\n\n- Re-render indicator position on tab resize ([#4165](https://github.com/mui/base-ui/pull/4165))\n\n## Toast\n\n- Enable closing all toasts ([#3979](https://github.com/mui/base-ui/pull/3979))\n- Prevent duplicate `onClose` calls for ending toasts ([#4280](https://github.com/mui/base-ui/pull/4280))\n\n## Tooltip\n\n- Add `closeOnClick` prop ([#4140](https://github.com/mui/base-ui/pull/4140))\n"
  },
  {
    "path": "docs/src/app/(docs)/react/page.mdx",
    "content": "# React\n\n[//]: # 'This section is autogenerated, but the following list order, title, and [Tag]s can be modified, but nothing within the parentheses.'\n\n- Overview - (Index, [Outline](#overview), [Contents](./overview/page.mdx))\n- Handbook - (Index, [Outline](#handbook), [Contents](./handbook/page.mdx))\n- Components - (Index, [Outline](#components), [Contents](./components/page.mdx))\n- Utils - (Index, [Outline](#utils), [Contents](./utils/page.mdx))\n\n[//]: # 'This section is autogenerated, DO NOT EDIT AFTER THIS LINE, run: pnpm docs:validate \"(docs)/react\"'\n\n## Overview\n\nNo description available\n\n<details>\n\n<summary>Outline</summary>\n\n- Sections:\n  - Quick start\n  - Accessibility\n  - Releases\n  - About Base UI\n\n</details>\n\n[Read more](./overview/page.mdx)\n\n## Handbook\n\nNo description available\n\n<details>\n\n<summary>Outline</summary>\n\n- Sections:\n  - Styling\n  - Animation\n  - Composition\n  - Customization\n  - Forms\n  - TypeScript\n\n</details>\n\n[Read more](./handbook/page.mdx)\n\n## Components\n\nNo description available\n\n<details>\n\n<summary>Outline</summary>\n\n- Sections:\n  - Accordion\n  - Alert Dialog\n  - Autocomplete\n  - Avatar\n  - Button\n  - Calendar\n  - Checkbox\n  - Checkbox Group\n  - Collapsible\n  - Combobox\n  - Context Menu\n  - Dialog\n  - Drawer\n  - Field\n  - Fieldset\n  - Form\n  - Input\n  - Menu\n  - Menubar\n  - Meter\n  - Navigation Menu\n  - Number Field\n  - Popover\n  - Preview Card\n  - Progress\n  - Radio\n  - Scroll Area\n  - Select\n  - Separator\n  - Slider\n  - Switch\n  - Tabs\n  - Toast\n  - Toggle\n  - Toggle Group\n  - Toolbar\n  - Tooltip\n\n</details>\n\n[Read more](./components/page.mdx)\n\n## Utils\n\nNo description available\n\n<details>\n\n<summary>Outline</summary>\n\n- Sections:\n  - CSP Provider\n  - Direction Provider\n  - mergeProps\n  - useRender\n  - Localization Provider\n\n</details>\n\n[Read more](./utils/page.mdx)\n\n[//]: # 'The above section is autogenerated, but the remainder of the file can be modified.'\n\nexport const metadata =\n  /** @type {import('@mui/internal-docs-infra/createSitemap/types').NextMetadata} */ ({\n    robots: {\n      index: false,\n    },\n    other: {\n      audience: 'private',\n    },\n  });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/utils/csp-provider/page.mdx",
    "content": "# CSP Provider\n\n<Subtitle>Configures CSP-related behavior for inline tags rendered by Base UI components.</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"A CSP provider component that applies a nonce to inline <style> and <script> tags rendered by Base UI components, and can disable inline <style> elements.\"\n/>\n\n## Anatomy\n\nImport the component and wrap it around your app:\n\n```jsx title=\"Anatomy\"\nimport { CSPProvider } from '@base-ui/react/csp-provider';\n\n// prettier-ignore\n<CSPProvider nonce=\"...\">\n  {/* Your app or a group of components */}\n</CSPProvider>\n```\n\nSome Base UI components render inline `<style>` or `<script>` tags for functionality such as removing scrollbars or pre-hydration behavior. Under a strict Content Security Policy (CSP), these tags may be blocked unless they include a matching [nonce](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/nonce) attribute.\n\n`CSPProvider` allows configuring this behavior globally for all Base UI components within its tree.\n\n## Supplying a nonce\n\nIf you enforce a CSP that blocks inline tags by default, configure your server to:\n\n1. Generate a random nonce per request\n2. Include it in your CSP header (via `style-src-elem`/`script-src`)\n3. Pass the same nonce into `CSPProvider` during rendering\n\n```ts title=\"Example\"\nconst nonce = crypto.randomUUID();\n\n// Example CSP header\nconst csp = [\n  `default-src 'self'`,\n  `script-src 'self' 'nonce-${nonce}'`,\n  `style-src-elem 'self' 'nonce-${nonce}'`,\n].join('; ');\n```\n\nThen:\n\n```jsx title=\"Providing the nonce\"\nimport { CSPProvider } from '@base-ui/react/csp-provider';\n\nfunction App({ nonce }) {\n  return <CSPProvider nonce={nonce}>{/* ... */}</CSPProvider>;\n}\n```\n\nThis will ensure that all inline `<style>` and `<script>` tags rendered by Base UI components include the correct nonce attribute, allowing them to function under your CSP.\n\n## Disable inline style elements\n\nYou can avoid supplying a `nonce` if you disable inline `<style>` elements entirely and rely on external stylesheets only. The relevant components are `<ScrollArea.Viewport>` and `<Select.Popup>` or `<Select.List>` when `alignItemWithTrigger` is enabled, which inject a style tag to disable native scrollbars.\n\n```html\n<style>\n  .base-ui-disable-scrollbar {\n    scrollbar-width: none;\n  }\n  .base-ui-disable-scrollbar::-webkit-scrollbar {\n    display: none;\n  }\n</style>\n```\n\nSpecify `disableStyleElements` to remove these tags:\n\n```jsx title=\"Disabling style elements\"\n<CSPProvider disableStyleElements>{/* ... */}</CSPProvider>\n```\n\n`<script>` tags across all components are opt-in, so they are not affected by this prop and don't have their own disable flag. A `nonce` is required if any component uses inline scripts.\n\n## Inline style attributes\n\n`CSPProvider` covers inline `<style>` and `<script>` tags rendered as elements, but it does not cover inline style attributes (for example, `<div style=\"...\">`). The `style-src-attr` directive in CSP governs inline style attributes encountered when parsing HTML from server pre-rendered components (it does not affect client-side JavaScript that sets styles).\n\nIn CSP, `style-src` applies to both `<style>` elements and `style=\"\"` attributes. If you only want to control `<style>` elements, use `style-src-elem` instead.\n\nIf your CSP blocks inline style _attributes_ in addition to _elements_, you have a few options:\n\n1. Relax your CSP by adding `'unsafe-inline'` to the `style-src-attr` directive (or using only `style-src-elem` instead of `style-src`). Style attributes specifically pose a less severe security risk than style elements, but this approach may not be acceptable in high-security environments.\n2. Render the affected components only on the client, so that no inline styles are present in the initial HTML.\n3. Manually unset inline styles and specify them in your CSS instead. Any component can have its inline styles unset, such as `<ScrollArea.Viewport style={{ overflow: undefined  }}>`. Note that you'll need to ensure you vet upgrades for any new inline styles added by Base UI components.\n\n## API reference\n\n<Reference component=\"CSPProvider\" />\n\nexport const metadata = {\n  keywords: [\n    'Base UI CSP Provider',\n    'Content Security Policy',\n    'CSP nonce',\n    'React CSP',\n    'Inline script nonce',\n    'Inline style nonce',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/utils/direction-provider/demos/hero/css-modules/index.module.css",
    "content": ".Control {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  width: 14rem;\n  padding-block: 0.75rem;\n}\n\n.Track {\n  width: 100%;\n  background-color: var(--color-gray-200);\n  box-shadow: inset 0 0 0 1px var(--color-gray-200);\n  height: 0.25rem;\n  border-radius: 0.25rem;\n  position: relative;\n}\n\n.Indicator {\n  border-radius: 0.25rem;\n  background-color: var(--color-gray-700);\n}\n\n.Thumb {\n  width: 1rem;\n  height: 1rem;\n  border-radius: 100%;\n  background-color: white;\n  outline: 1px solid var(--color-gray-300);\n\n  &:has(:focus-visible) {\n    outline: 2px solid var(--color-blue);\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/utils/direction-provider/demos/hero/css-modules/index.tsx",
    "content": "import { DirectionProvider } from '@base-ui/react/direction-provider';\nimport { Slider } from '@base-ui/react/slider';\nimport styles from './index.module.css';\n\nexport default function ExampleDirectionProvider() {\n  return (\n    <div dir=\"rtl\">\n      <DirectionProvider direction=\"rtl\">\n        <Slider.Root defaultValue={25}>\n          <Slider.Control className={styles.Control}>\n            <Slider.Track className={styles.Track}>\n              <Slider.Indicator className={styles.Indicator} />\n              <Slider.Thumb className={styles.Thumb} />\n            </Slider.Track>\n          </Slider.Control>\n        </Slider.Root>\n      </DirectionProvider>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/utils/direction-provider/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\nimport Tailwind from './tailwind';\n\nexport const DemoDirectionProviderHero = createDemoWithVariants(import.meta.url, {\n  CssModules,\n  Tailwind,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/utils/direction-provider/demos/hero/tailwind/index.tsx",
    "content": "import { Slider } from '@base-ui/react/slider';\nimport { DirectionProvider } from '@base-ui/react/direction-provider';\n\nexport default function ExampleDirectionProvider() {\n  return (\n    <div dir=\"rtl\">\n      <DirectionProvider direction=\"rtl\">\n        <Slider.Root defaultValue={25}>\n          <Slider.Control className=\"flex w-56 items-center py-3\">\n            <Slider.Track className=\"relative h-1 w-full rounded-sm bg-gray-200 shadow-[inset_0_0_0_1px] shadow-gray-200\">\n              <Slider.Indicator className=\"rounded-sm bg-gray-700\" />\n              <Slider.Thumb className=\"size-4 rounded-full bg-white outline-1 outline-gray-300 has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-blue-800\" />\n            </Slider.Track>\n          </Slider.Control>\n        </Slider.Root>\n      </DirectionProvider>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/utils/direction-provider/page.mdx",
    "content": "# Direction Provider\n\n<Subtitle>Enables RTL behavior for Base UI components.</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"A direction provider component that enables RTL behavior for Base UI components.\"\n/>\n\nimport { DemoDirectionProviderHero } from './demos/hero';\n\n<DemoDirectionProviderHero />\n\n## Anatomy\n\nImport the component and wrap it around your app:\n\n```jsx title=\"Anatomy\"\nimport { DirectionProvider } from '@base-ui/react/direction-provider';\n\n// prettier-ignore\n<DirectionProvider>\n  {/* Your app or a group of components */}\n</DirectionProvider>\n```\n\n`<DirectionProvider>` enables child Base UI components to adjust behavior based on RTL text direction, but does not affect HTML and CSS. The `dir=\"rtl\"` HTML attribute or `direction: rtl` CSS style must be set additionally by your own application code.\n\n## API reference\n\n<Reference component=\"DirectionProvider\" />\n\n## useDirection\n\nUse this hook to read the current text direction. This is useful for wrapping portaled components that may be rendered outside your application root and are unaffected by the `dir` attribute set within.\n\n### Return value\n\n<PropsReferenceTable\n  type=\"return\"\n  data={{\n    direction: {\n      type: 'TextDirection',\n      detailedType: \"'ltr' | 'rtl' | undefined\",\n      description: 'The current text direction.',\n      default: \"'ltr'\",\n    },\n  }}\n/>\n\nexport const metadata = {\n  keywords: [\n    'Base UI Direction Provider',\n    'React RTL Context',\n    'Text Direction Hook',\n    'useDirection Base UI',\n    'Headless RTL Components',\n    'Internationalization',\n    'i18n',\n    'Bidirectional Text',\n    'Locale Support',\n    'Arabic Hebrew Support',\n    'LTR RTL Switch',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(docs)/react/utils/localization-provider/calendar.module.css",
    "content": ".Root {\n  border: 1px solid var(--calendar-root-border-color);\n  border-radius: 8px;\n  height: 312px;\n  display: flex;\n  flex-direction: column;\n\n  --calendar-root-color: var(--color-gray-900);\n  --calendar-root-border-color: var(--color-gray-500);\n  --calendar-button-hover-bg-color: #e0f2fe;\n  --calendar-button-focus-border-color: #0ea5e9;\n  --calendar-day-grid-separator-bg-color: #9ca3af;\n  --calendar-day-grid-header-color: var(--color-gray-500);\n\n  --calendar-cell-selected-bg-color: #7dd3fc;\n  --calendar-cell-outside-month-color: var(--color-gray-400);\n  --calendar-cell-disabled-color: var(--color-gray-500);\n  --calendar-cell-current-border-color: var(--color-gray-500);\n  --calendar-cell-unavailable-color: #f87171;\n\n  @media (prefers-color-scheme: dark) {\n    --calendar-button-hover-bg-color: #075985;\n    --calendar-cell-selected-bg-color: #0369a1;\n  }\n}\n\n.Header {\n  box-sizing: border-box;\n  padding: 8px 12px;\n  height: 40px;\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n}\n\n.HeaderLabel {\n  color: var(--calendar-root-color);\n}\n\n.DecrementMonth,\n.IncrementMonth {\n  border: none;\n  user-select: none;\n  background-color: transparent;\n  border-radius: 4px;\n  color: var(--calendar-root-color);\n  margin: 0 6px;\n  padding: 0;\n  display: flex;\n\n  &:hover {\n    background-color: var(--calendar-button-hover-bg-color);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--calendar-button-focus-border-color);\n  }\n\n  &[data-disabled] {\n    pointer-events: none;\n    color: var(--calendar-cell-disabled-color);\n  }\n}\n\n.DayGrid {\n  padding: 12px;\n  height: 276px;\n  display: flex;\n  flex-direction: column;\n  gap: 4px;\n  z-index: 1;\n  position: relative;\n}\n\n.DayGridBody {\n  display: flex;\n  flex-direction: column;\n  row-gap: 2px;\n}\n\n.DayGridRow,\n.DayGridHeaderRow {\n  display: flex;\n  justify-content: center;\n}\n\n.DayGridHeaderCell {\n  width: 36px;\n  text-align: center;\n  font-size: 0.75rem;\n  color: var(--calendar-day-grid-header-color);\n  font-weight: 700;\n  padding: 0;\n}\n\n.DayGridCell {\n  padding: 0;\n}\n\n.DayButton {\n  background: none;\n  padding: 0;\n  font: inherit;\n  height: 36px;\n  width: 36px;\n  position: relative;\n  user-select: none;\n  border: none;\n  background-color: transparent;\n  outline: none;\n  box-sizing: border-box;\n  border-radius: 4px;\n  color: var(--calendar-root-color);\n\n  &::before {\n    content: '';\n    position: absolute;\n    inset: 2px;\n    border-radius: 4px;\n    border: none;\n    z-index: -1;\n    background-color: transparent;\n  }\n\n  &::after {\n    content: '';\n    border-radius: 4px;\n    position: absolute;\n    inset: 2px;\n  }\n\n  &:not([data-outside-month]):focus-visible {\n    &::after {\n      outline: 2px solid var(--calendar-button-focus-border-color);\n    }\n  }\n\n  &:not([data-selected]):hover::before {\n    background-color: var(--calendar-button-hover-bg-color);\n  }\n\n  &[data-selected]:not([data-outside-month])::before {\n    background-color: var(--calendar-cell-selected-bg-color);\n  }\n\n  &[data-disabled] {\n    pointer-events: none;\n  }\n\n  &:not([data-outside-month])[data-disabled] {\n    color: var(--calendar-cell-disabled-color);\n  }\n\n  &:not([data-outside-month])[data-invalid] {\n    text-decoration: line-through;\n  }\n\n  &[data-outside-month] {\n    color: var(--calendar-cell-outside-month-color);\n    pointer-events: none;\n  }\n\n  &[data-current]:not([data-selected], :focus-visible)::after {\n    outline: 1px solid var(--calendar-cell-current-border-color);\n  }\n\n  &:not([data-disabled], [data-outside-month])[data-unavailable] {\n    text-decoration: line-through;\n    color: var(--calendar-cell-unavailable-color);\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/utils/localization-provider/demos/hero/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { format } from 'date-fns/format';\nimport { fr } from 'date-fns/locale/fr';\nimport { LocalizationProvider } from '@base-ui/react/localization-provider';\nimport { Calendar } from '@base-ui/react/calendar';\nimport styles from '../../../calendar.module.css';\n\nexport default function ExampleCalendar() {\n  return (\n    <LocalizationProvider temporalLocale={fr}>\n      <MyCalendar />\n    </LocalizationProvider>\n  );\n}\n\nfunction MyCalendar() {\n  return (\n    <Calendar.Root className={styles.Root}>\n      {({ visibleDate }) => (\n        <React.Fragment>\n          <header className={styles.Header}>\n            <Calendar.DecrementMonth className={styles.DecrementMonth}>\n              <ChevronLeftIcon />\n            </Calendar.DecrementMonth>\n            <span className={styles.HeaderLabel}>\n              {format(visibleDate, 'MMMM yyyy', { locale: fr })}\n            </span>\n            <Calendar.IncrementMonth className={styles.IncrementMonth}>\n              <ChevronRightIcon />\n            </Calendar.IncrementMonth>\n          </header>\n          <Calendar.DayGrid className={styles.DayGrid}>\n            <Calendar.DayGridHeader className={styles.DayGridHeader}>\n              <Calendar.DayGridHeaderRow className={styles.DayGridHeaderRow}>\n                {(day) => (\n                  <Calendar.DayGridHeaderCell\n                    value={day}\n                    key={day.getTime()}\n                    className={styles.DayGridHeaderCell}\n                  />\n                )}\n              </Calendar.DayGridHeaderRow>\n            </Calendar.DayGridHeader>\n            <Calendar.DayGridBody className={styles.DayGridBody}>\n              {(week) => (\n                <Calendar.DayGridRow\n                  value={week}\n                  key={week.getTime()}\n                  className={styles.DayGridRow}\n                >\n                  {(day) => (\n                    <Calendar.DayGridCell\n                      value={day}\n                      key={day.getTime()}\n                      className={styles.DayGridCell}\n                    >\n                      <Calendar.DayButton className={styles.DayButton} />\n                    </Calendar.DayGridCell>\n                  )}\n                </Calendar.DayGridRow>\n              )}\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </React.Fragment>\n      )}\n    </Calendar.Root>\n  );\n}\n\nfunction ChevronLeftIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"m15 18-6-6 6-6\" />\n    </svg>\n  );\n}\n\nfunction ChevronRightIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"m9 18 6-6-6-6\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/utils/localization-provider/demos/hero/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\n\nexport const DemoLocalizationProviderHero = createDemoWithVariants(import.meta.url, {\n  CssModules,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/utils/localization-provider/demos/nesting/css-modules/index.module.css",
    "content": ".Wrapper {\n  display: flex;\n  flex-flow: row wrap;\n  gap: 24px;\n  justify-content: center;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/utils/localization-provider/demos/nesting/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { format } from 'date-fns/format';\nimport { fr, zhCN } from 'date-fns/locale';\nimport { LocalizationProvider, useTemporalLocale } from '@base-ui/react/localization-provider';\nimport { Calendar } from '@base-ui/react/calendar';\nimport styles from '../../../calendar.module.css';\nimport indexStyles from './index.module.css';\n\nexport default function NestedLocalizedCalendars() {\n  return (\n    <div className={indexStyles.Wrapper}>\n      <LocalizationProvider temporalLocale={fr}>\n        <LocalizedCalendar />\n        <LocalizationProvider temporalLocale={zhCN}>\n          <LocalizedCalendar />\n        </LocalizationProvider>\n      </LocalizationProvider>\n    </div>\n  );\n}\n\nfunction LocalizedCalendar() {\n  const locale = useTemporalLocale();\n  return (\n    <Calendar.Root className={styles.Root}>\n      {({ visibleDate }) => (\n        <React.Fragment>\n          <header className={styles.Header}>\n            <Calendar.DecrementMonth className={styles.DecrementMonth}>\n              <ChevronLeftIcon />\n            </Calendar.DecrementMonth>\n            <span className={styles.HeaderLabel}>\n              {format(visibleDate, 'MMMM yyyy', { locale })}\n            </span>\n            <Calendar.IncrementMonth className={styles.IncrementMonth}>\n              <ChevronRightIcon />\n            </Calendar.IncrementMonth>\n          </header>\n          <Calendar.DayGrid className={styles.DayGrid}>\n            <Calendar.DayGridHeader className={styles.DayGridHeader}>\n              <Calendar.DayGridHeaderRow className={styles.DayGridHeaderRow}>\n                {(day) => (\n                  <Calendar.DayGridHeaderCell\n                    value={day}\n                    key={day.getTime()}\n                    className={styles.DayGridHeaderCell}\n                  />\n                )}\n              </Calendar.DayGridHeaderRow>\n            </Calendar.DayGridHeader>\n            <Calendar.DayGridBody className={styles.DayGridBody}>\n              {(week) => (\n                <Calendar.DayGridRow\n                  value={week}\n                  key={week.getTime()}\n                  className={styles.DayGridRow}\n                >\n                  {(day) => (\n                    <Calendar.DayGridCell\n                      value={day}\n                      key={day.getTime()}\n                      className={styles.DayGridCell}\n                    >\n                      <Calendar.DayButton className={styles.DayButton} />\n                    </Calendar.DayGridCell>\n                  )}\n                </Calendar.DayGridRow>\n              )}\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </React.Fragment>\n      )}\n    </Calendar.Root>\n  );\n}\n\nfunction ChevronLeftIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"m15 18-6-6 6-6\" />\n    </svg>\n  );\n}\n\nfunction ChevronRightIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"m9 18 6-6-6-6\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/utils/localization-provider/demos/nesting/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\n\nexport const DemoLocalizationProviderNesting = createDemoWithVariants(import.meta.url, {\n  CssModules,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/utils/localization-provider/demos/start-of-week/css-modules/index.module.css",
    "content": ".Wrapper {\n  display: flex;\n  flex-flow: column wrap;\n  gap: 16px;\n}\n\n.Label {\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 700;\n  color: var(--color-gray-900);\n  cursor: default;\n  margin-bottom: 0.5rem;\n}\n\n.Select {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 0.75rem;\n  height: 2.5rem;\n  padding-left: 0.875rem;\n  padding-right: 0.75rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: canvas;\n  font-family: inherit;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  -webkit-user-select: none;\n  user-select: none;\n  min-width: 10rem;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.SelectIcon {\n  display: flex;\n}\n\n.Positioner {\n  outline: none;\n  z-index: 1;\n  -webkit-user-select: none;\n  user-select: none;\n}\n\n.Popup {\n  box-sizing: border-box;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  background-clip: padding-box;\n  color: var(--color-gray-900);\n  min-width: var(--anchor-width);\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  &[data-side='none'] {\n    transition: none;\n    transform: none;\n    opacity: 1;\n    min-width: calc(var(--anchor-width) + 1rem);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n  }\n}\n\n.List {\n  box-sizing: border-box;\n  position: relative;\n  padding-block: 0.25rem;\n  overflow-y: auto;\n  max-height: var(--available-height);\n  scroll-padding-block: 1.5rem;\n}\n\n.Item {\n  box-sizing: border-box;\n  outline: 0;\n  font-size: 0.875rem;\n  line-height: 1rem;\n  padding-block: 0.5rem;\n  padding-left: 0.625rem;\n  padding-right: 1rem;\n  display: grid;\n  gap: 0.5rem;\n  align-items: center;\n  grid-template-columns: 0.75rem 1fr;\n  cursor: default;\n  -webkit-user-select: none;\n  user-select: none;\n\n  @media (pointer: coarse) {\n    padding-block: 0.625rem;\n    font-size: 0.925rem;\n  }\n\n  [data-side='none'] & {\n    font-size: 1rem;\n    padding-right: 3rem;\n  }\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.25rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.ItemIndicator {\n  grid-column-start: 1;\n}\n\n.ItemIndicatorIcon {\n  display: block;\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.ItemText {\n  grid-column-start: 2;\n}\n\n.ScrollArrow {\n  width: 100%;\n  background: canvas;\n  z-index: 1;\n  text-align: center;\n  cursor: default;\n  border-radius: 0.375rem;\n  height: 1rem;\n  font-size: 0.75rem;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n\n  &::before {\n    content: '';\n    position: absolute;\n    width: 100%;\n    height: 100%;\n    left: 0;\n  }\n\n  &[data-direction='up'] {\n    &[data-side='none'] {\n      &::before {\n        top: -100%;\n      }\n    }\n  }\n\n  &[data-direction='down'] {\n    bottom: 0;\n\n    &[data-side='none'] {\n      &::before {\n        bottom: -100%;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/utils/localization-provider/demos/start-of-week/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { format } from 'date-fns/format';\nimport { enUS } from 'date-fns/locale/en-US';\nimport type { Day } from 'date-fns';\nimport { LocalizationProvider } from '@base-ui/react/localization-provider';\nimport { Calendar } from '@base-ui/react/calendar';\nimport { Select } from '@base-ui/react/select';\nimport styles from '../../../calendar.module.css';\nimport indexStyles from './index.module.css';\n\nconst dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];\n\nexport default function StartOfWeekCalendar() {\n  const [weekStartsOn, setWeekStartsOn] = React.useState<Day>(1);\n  const locale = React.useMemo(\n    () => ({ ...enUS, options: { ...enUS.options, weekStartsOn } }),\n    [weekStartsOn],\n  );\n\n  return (\n    <div className={indexStyles.Wrapper}>\n      <div>\n        <Select.Root value={weekStartsOn} onValueChange={(value) => setWeekStartsOn(value as Day)}>\n          <Select.Label className={indexStyles.Label}>First day of the week</Select.Label>\n          <Select.Trigger className={indexStyles.Select}>\n            <Select.Value>{(value: Day) => dayNames[value]}</Select.Value>\n            <Select.Icon className={indexStyles.SelectIcon}>\n              <ChevronUpDownIcon />\n            </Select.Icon>\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner className={indexStyles.Positioner} sideOffset={8}>\n              <Select.Popup className={indexStyles.Popup}>\n                <Select.ScrollUpArrow className={indexStyles.ScrollArrow} />\n                <Select.List className={indexStyles.List}>\n                  {dayNames.map((day, index) => (\n                    <Select.Item key={day} value={index} className={indexStyles.Item}>\n                      <Select.ItemIndicator className={indexStyles.ItemIndicator}>\n                        <CheckIcon className={indexStyles.ItemIndicatorIcon} />\n                      </Select.ItemIndicator>\n                      <Select.ItemText className={indexStyles.ItemText}>{day}</Select.ItemText>\n                    </Select.Item>\n                  ))}\n                </Select.List>\n                <Select.ScrollDownArrow className={indexStyles.ScrollArrow} />\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>\n      </div>\n      <LocalizationProvider temporalLocale={locale}>\n        <Calendar.Root className={styles.Root}>\n          {({ visibleDate }) => (\n            <React.Fragment>\n              <header className={styles.Header}>\n                <Calendar.DecrementMonth className={styles.DecrementMonth}>\n                  <ChevronLeftIcon />\n                </Calendar.DecrementMonth>\n                <span className={styles.HeaderLabel}>{format(visibleDate, 'MMMM yyyy')}</span>\n                <Calendar.IncrementMonth className={styles.IncrementMonth}>\n                  <ChevronRightIcon />\n                </Calendar.IncrementMonth>\n              </header>\n              <Calendar.DayGrid className={styles.DayGrid}>\n                <Calendar.DayGridHeader className={styles.DayGridHeader}>\n                  <Calendar.DayGridHeaderRow className={styles.DayGridHeaderRow}>\n                    {(day) => (\n                      <Calendar.DayGridHeaderCell\n                        value={day}\n                        key={day.getTime()}\n                        className={styles.DayGridHeaderCell}\n                      />\n                    )}\n                  </Calendar.DayGridHeaderRow>\n                </Calendar.DayGridHeader>\n                <Calendar.DayGridBody className={styles.DayGridBody}>\n                  {(week) => (\n                    <Calendar.DayGridRow\n                      value={week}\n                      key={week.getTime()}\n                      className={styles.DayGridRow}\n                    >\n                      {(day) => (\n                        <Calendar.DayGridCell\n                          value={day}\n                          key={day.getTime()}\n                          className={styles.DayGridCell}\n                        >\n                          <Calendar.DayButton className={styles.DayButton} />\n                        </Calendar.DayGridCell>\n                      )}\n                    </Calendar.DayGridRow>\n                  )}\n                </Calendar.DayGridBody>\n              </Calendar.DayGrid>\n            </React.Fragment>\n          )}\n        </Calendar.Root>\n      </LocalizationProvider>\n    </div>\n  );\n}\n\nfunction ChevronUpDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"8\"\n      height=\"12\"\n      viewBox=\"0 0 8 12\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"1.5\"\n      {...props}\n    >\n      <path d=\"M0.5 4.5L4 1.5L7.5 4.5\" />\n      <path d=\"M0.5 7.5L4 10.5L7.5 7.5\" />\n    </svg>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\nfunction ChevronLeftIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"m15 18-6-6 6-6\" />\n    </svg>\n  );\n}\n\nfunction ChevronRightIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"m9 18 6-6-6-6\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/utils/localization-provider/demos/start-of-week/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\n\nexport const DemoLocalizationProviderStartOfWeek = createDemoWithVariants(import.meta.url, {\n  CssModules,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/utils/localization-provider/page.mdx",
    "content": "# Localization Provider\n\n<Subtitle>Defines the locale to use in the temporal components.</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"A localization provider component that defines the locale to use in the temporal components.\"\n/>\n\nimport { DemoLocalizationProviderHero } from './demos/hero';\n\n<DemoLocalizationProviderHero />\n\n## Anatomy\n\nImport the component plus the `date-fns` library locale and wrap them around your app.\n\nIf every temporal component in your app shares the same locale, you should use the provider once at the root of your application.\n\n```jsx title=\"Anatomy\"\nimport { fr } from 'date-fns/locale/fr';\nimport { LocalizationProvider } from '@base-ui/react/localization-provider';\n\n<LocalizationProvider temporalLocale={fr}>\n  {/* Your app or a group of components */}\n</LocalizationProvider>;\n```\n\n## Examples\n\n### Nesting locales\n\nThe `<LocalizationProvider>` can be nested to change the locale for a part of the application.\n\nThis could be useful when you need some temporal components in your application to use a different locale than the rest of your app.\n\nimport { DemoLocalizationProviderNesting } from './demos/nesting';\n\n<DemoLocalizationProviderNesting />\n\n### Start of week\n\nStart of the week is handled by the date library locale. You can override the default start of the week by spreading the locale and setting `options.weekStartsOn` to a value between `0` (Sunday) and `6` (Saturday).\n\nimport { DemoLocalizationProviderStartOfWeek } from './demos/start-of-week';\n\n<DemoLocalizationProviderStartOfWeek />\n\n## API reference\n\n<Reference component=\"LocalizationProvider\" />\n\nexport const metadata =\n  /** @type {import('@mui/internal-docs-infra/createSitemap/types').NextMetadata} */ ({\n    keywords: ['Localization', 'Locale', 'Temporal', 'Date Fns', 'Base UI'],\n    robots: {\n      index: false,\n    },\n    other: {\n      audience: 'private',\n    },\n  });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/utils/merge-props/demos/prevent-base-ui-handler/css-modules/index.module.css",
    "content": ".Container {\n  display: flex;\n  align-items: center;\n  gap: 1rem;\n}\n\n.ToggleRow {\n  display: flex;\n  align-items: center;\n  gap: 0.75rem;\n}\n\n.Label {\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n}\n\n.Panel {\n  display: flex;\n  gap: 1px;\n  border: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-50);\n  border-radius: 0.375rem;\n  padding: 0.125rem;\n}\n\n.Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 2rem;\n  height: 2rem;\n  padding: 0;\n  margin: 0;\n  outline: 0;\n  border: 0;\n  border-radius: 0.25rem;\n  background-color: transparent;\n  color: var(--color-gray-600);\n  user-select: none;\n\n  &:focus-visible {\n    background-color: transparent;\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-200);\n  }\n\n  &[data-pressed] {\n    color: var(--color-gray-900);\n  }\n}\n\n.Icon {\n  width: 1.25rem;\n  height: 1.25rem;\n}\n\n.LockButton {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/utils/merge-props/demos/prevent-base-ui-handler/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { mergeProps } from '@base-ui/react/merge-props';\nimport { Toggle } from '@base-ui/react/toggle';\nimport styles from './index.module.css';\n\nexport default function ExamplePreventBaseUIHandler() {\n  const [locked, setLocked] = React.useState(true);\n  const [pressed, setPressed] = React.useState(true);\n  const getToggleProps = (props: React.ComponentProps<'button'>) =>\n    mergeProps<'button'>(props, {\n      onClick(event) {\n        if (locked) {\n          event.preventBaseUIHandler();\n        }\n      },\n    });\n\n  return (\n    <div className={styles.Container}>\n      <div className={styles.ToggleRow}>\n        <div className={styles.Panel}>\n          <Toggle\n            aria-label=\"Favorite\"\n            pressed={pressed}\n            onPressedChange={setPressed}\n            className={styles.Button}\n            render={(props, state) => (\n              <button type=\"button\" {...getToggleProps(props)}>\n                {state.pressed ? (\n                  <HeartFilledIcon className={styles.Icon} />\n                ) : (\n                  <HeartOutlineIcon className={styles.Icon} />\n                )}\n              </button>\n            )}\n          />\n        </div>\n        <span className={styles.Label}>Favorite {locked ? '(locked)' : '(unlocked)'}</span>\n      </div>\n      <button type=\"button\" className={styles.LockButton} onClick={() => setLocked((l) => !l)}>\n        {locked ? 'Unlock' : 'Lock'}\n      </button>\n    </div>\n  );\n}\n\nfunction HeartFilledIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentcolor\" {...props}>\n      <path d=\"M7.99961 13.8667C7.88761 13.8667 7.77561 13.8315 7.68121 13.7611C7.43321 13.5766 1.59961 9.1963 1.59961 5.8667C1.59961 3.80856 3.27481 2.13336 5.33294 2.13336C6.59054 2.13336 7.49934 2.81176 7.99961 3.3131C8.49988 2.81176 9.40868 2.13336 10.6663 2.13336C12.7244 2.13336 14.3996 3.80803 14.3996 5.8667C14.3996 9.1963 8.56601 13.5766 8.31801 13.7616C8.22361 13.8315 8.11161 13.8667 7.99961 13.8667Z\" />\n    </svg>\n  );\n}\n\nfunction HeartOutlineIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentcolor\" {...props}>\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M7.99961 4.8232L7.24456 4.06654C6.84123 3.66235 6.18866 3.20003 5.33294 3.20003C3.86391 3.20003 2.66628 4.39767 2.66628 5.8667C2.66628 6.4079 2.91276 7.1023 3.41967 7.91383C3.91548 8.70759 4.59649 9.51244 5.31278 10.2503C6.38267 11.3525 7.47318 12.2465 7.99983 12.6605C8.52734 12.2456 9.61718 11.352 10.6864 10.2504C11.4027 9.51248 12.0837 8.70762 12.5796 7.91384C13.0865 7.1023 13.3329 6.4079 13.3329 5.8667C13.3329 4.39723 12.1354 3.20003 10.6663 3.20003C9.81056 3.20003 9.15799 3.66235 8.75466 4.06654L7.99961 4.8232ZM7.98574 3.29926C7.48264 2.79938 6.57901 2.13336 5.33294 2.13336C3.27481 2.13336 1.59961 3.80856 1.59961 5.8667C1.59961 9.1963 7.43321 13.5766 7.68121 13.7611C7.77561 13.8315 7.88761 13.8667 7.99961 13.8667C8.11161 13.8667 8.22361 13.8315 8.31801 13.7616C8.56601 13.5766 14.3996 9.1963 14.3996 5.8667C14.3996 3.80803 12.7244 2.13336 10.6663 2.13336C9.42013 2.13336 8.51645 2.79947 8.01337 3.29936C8.00877 3.30393 8.00421 3.30849 7.99967 3.31303C7.99965 3.31305 7.99963 3.31307 7.99961 3.3131C7.99502 3.3085 7.9904 3.30389 7.98574 3.29926Z\"\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/utils/merge-props/demos/prevent-base-ui-handler/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\n\nexport const DemoPreventBaseUIHandler = createDemoWithVariants(import.meta.url, { CssModules });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/utils/merge-props/page.mdx",
    "content": "# mergeProps\n\n<Subtitle>A utility to merge multiple sets of React props.</Subtitle>\n<Meta\n  name=\"description\"\n  content=\"A utility to merge multiple sets of React props, handling event handlers, className, and style props intelligently.\"\n/>\n\n`mergeProps` helps you combine multiple prop objects (for example, internal props + user props) into a single set of props you can spread onto an element.\nIt behaves like `Object.assign` (rightmost wins) with a few special cases, so common React patterns work as expected.\n\n## How merging works\n\n- For most keys (everything except `className`, `style`, and event handlers), the value from the rightmost object wins:\n  ```ts title=\"returns { id: 'b', dir: 'ltr' }\"\n  mergeProps({ id: 'a', dir: 'ltr' }, { id: 'b' });\n  ```\n- `ref` is not merged. Only the rightmost ref is kept:\n  ```ts title=\"only refB is used\"\n  mergeProps({ ref: refA }, { ref: refB });\n  ```\n- `className` values are concatenated right-to-left (rightmost first):\n  ```ts title=\"className is 'b a'\"\n  mergeProps({ className: 'a' }, { className: 'b' });\n  ```\n- `style` objects are merged, with keys from the rightmost style overwriting earlier ones.\n- Event handlers are merged and executed right-to-left (rightmost first):\n\n  ```ts title=\"b runs before a\"\n  mergeProps({ onClick: a }, { onClick: b });\n  ```\n\n  - For React synthetic events, Base UI adds `event.preventBaseUIHandler()`. Calling it prevents Base UI's internal logic from running.\n    This does not call `preventDefault()` or `stopPropagation()`.\n  - For non-synthetic events (custom events with primitive/object values), this mechanism isn't available and all handlers always execute.\n\n### Preventing Base UI's default behavior\n\nimport { DemoPreventBaseUIHandler } from './demos/prevent-base-ui-handler';\n\nWhen using the function form of the `render` prop, props are not merged automatically.\nYou can use `mergeProps` to combine Base UI's props with your own, and call `preventBaseUIHandler()` to stop Base UI's internal logic from running:\n\n<DemoPreventBaseUIHandler />\n\n## Passing a function instead of an object\n\nEach argument can be a props object or a function that receives the merged props up to that point (left to right) and returns a props object.\nThis is useful when you need to compute the next props from whatever has already been merged.\n\nNote that the function's return value completely replaces the accumulated props up to that point.\nIf you want to chain event handlers from the previous props, you must call them manually:\n\n```tsx title=\"Manually chaining handlers in a function\"\nconst merged = mergeProps(\n  {\n    onClick(event) {\n      // Handler from previous props\n    },\n  },\n  (props) => ({\n    onClick(event) {\n      // Manually call the previous handler\n      props.onClick?.(event);\n      // Your logic here\n    },\n  }),\n);\n```\n\n## API reference\n\n### mergeProps\n\nThis function accepts up to 5 arguments, each being either a props object or a function that returns a props object.\nIf you need to merge more than 5 sets of props, use `mergePropsN` instead.\n\n<Reference component=\"mergeProps\" />\n\n### mergePropsN\n\nThis function accepts an array of props objects or functions that return props objects.\nIt is slightly less efficient than `mergeProps`, so only use it when you need to merge more than 5 sets of props.\n\n<Reference component=\"mergePropsN\" />\n"
  },
  {
    "path": "docs/src/app/(docs)/react/utils/page.mdx",
    "content": "# Utils\n\n[//]: # 'This section is autogenerated, but the following list order, title, and [Tag]s can be modified, but nothing within the parentheses.'\n\n- CSP Provider - ([Outline](#csp-provider), [Contents](./csp-provider/page.mdx))\n- Direction Provider - ([Outline](#direction-provider), [Contents](./direction-provider/page.mdx))\n- mergeProps - ([Outline](#mergeprops), [Contents](./merge-props/page.mdx))\n- useRender - ([Outline](#userender), [Contents](./use-render/page.mdx))\n- Localization Provider [New] - (Private, [Outline](#localization-provider), [Contents](./localization-provider/page.mdx))\n\n[//]: # 'This section is autogenerated, DO NOT EDIT AFTER THIS LINE, run: pnpm docs:validate \"(docs)/react/utils\"'\n\n## CSP Provider\n\nConfigures CSP-related behavior for inline tags rendered by Base UI components.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: Base UI CSP Provider, Content Security Policy, CSP nonce, React CSP, Inline script nonce, Inline style nonce\n- Sections:\n  - Anatomy\n  - Supplying a nonce\n  - Disable inline style elements\n  - Inline style attributes\n  - API reference\n\n</details>\n\n[Read more](./csp-provider/page.mdx)\n\n## Direction Provider\n\nEnables RTL behavior for Base UI components.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: Base UI Direction Provider, React RTL Context, Text Direction Hook, useDirection Base UI, Headless RTL Components, Internationalization, i18n, Bidirectional Text, Locale Support, Arabic Hebrew Support, LTR RTL Switch\n- Sections:\n  - Anatomy\n  - API reference\n  - useDirection\n    - Return value\n\n</details>\n\n[Read more](./direction-provider/page.mdx)\n\n## mergeProps\n\nA utility to merge multiple sets of React props.\n\n<details>\n\n<summary>Outline</summary>\n\n- Sections:\n  - How merging works\n    - Preventing Base UI's default behavior\n  - Passing a function instead of an object\n  - API reference\n    - mergeProps\n    - mergePropsN\n\n</details>\n\n[Read more](./merge-props/page.mdx)\n\n## useRender\n\nHook for enabling a render prop in custom components.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: Base UI useRender, React Render Prop Hook, Merge Props Helper, Merge Refs Hook, Radix asChild Migration, Component Composition, Props Spreading, Ref Forwarding, Custom Components, asChild Pattern, Slot Pattern\n- Sections:\n  - Examples\n  - Merging props\n  - Merging refs\n  - TypeScript\n  - Migrating from Radix UI\n  - Render prop and polymorphism\n  - API reference\n    - Input parameters\n    - Return value\n\n</details>\n\n[Read more](./use-render/page.mdx)\n\n## Localization Provider\n\nDefines the locale to use in the temporal components.\n\n<details>\n\n<summary>Outline</summary>\n\n- Keywords: Localization, Locale, Temporal, Date Fns, Base UI\n- Sections:\n  - Anatomy\n  - Examples\n    - Nesting locales\n    - Start of week\n  - API reference\n\n</details>\n\n[Read more](./localization-provider/page.mdx)\n\n[//]: # 'The above section is autogenerated, but the remainder of the file can be modified.'\n\nexport const metadata =\n  /** @type {import('@mui/internal-docs-infra/createSitemap/types').NextMetadata} */ ({\n    robots: {\n      index: false,\n    },\n    other: {\n      audience: 'private',\n    },\n  });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/utils/use-render/demos/render/css-modules/index.module.css",
    "content": ".Text {\n  font-size: 0.875rem;\n  line-height: 1rem;\n  color: var(--color-gray-900);\n\n  strong& {\n    font-weight: 700;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/utils/use-render/demos/render/css-modules/index.tsx",
    "content": "'use client';\nimport { useRender } from '@base-ui/react/use-render';\nimport { mergeProps } from '@base-ui/react/merge-props';\nimport styles from './index.module.css';\n\ninterface TextProps extends useRender.ComponentProps<'p'> {}\n\nfunction Text(props: TextProps) {\n  const { render, ...otherProps } = props;\n\n  const element = useRender({\n    defaultTagName: 'p',\n    render,\n    props: mergeProps<'p'>({ className: styles.Text }, otherProps),\n  });\n\n  return element;\n}\n\nexport default function ExampleText() {\n  return (\n    <div>\n      <Text>Text component rendered as a paragraph tag</Text>\n      <Text render={<strong />}>Text component rendered as a strong tag</Text>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/utils/use-render/demos/render/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\n\nexport const DemoUseRenderRender = createDemoWithVariants(import.meta.url, { CssModules });\n"
  },
  {
    "path": "docs/src/app/(docs)/react/utils/use-render/demos/render-callback/css-modules/index.module.css",
    "content": ".Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  & span {\n    font-variant-numeric: tabular-nums;\n    display: inline-block;\n    text-align: end;\n    min-width: 2.5ch;\n  }\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.suffix {\n  margin-left: 0.125rem;\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/utils/use-render/demos/render-callback/css-modules/index.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useRender } from '@base-ui/react/use-render';\nimport { mergeProps } from '@base-ui/react/merge-props';\nimport styles from './index.module.css';\n\ninterface CounterState {\n  odd: boolean;\n}\n\ninterface CounterProps extends useRender.ComponentProps<'button', CounterState> {}\n\nfunction Counter(props: CounterProps) {\n  const { render, ...otherProps } = props;\n\n  const [count, setCount] = React.useState(0);\n  const odd = count % 2 === 1;\n  const state = React.useMemo(() => ({ odd }), [odd]);\n\n  const defaultProps: useRender.ElementProps<'button'> = {\n    className: styles.Button,\n    type: 'button',\n    children: (\n      <React.Fragment>\n        Counter: <span>{count}</span>\n      </React.Fragment>\n    ),\n    onClick() {\n      setCount((prev) => prev + 1);\n    },\n    'aria-label': `Count is ${count}, click to increase.`,\n  };\n\n  const element = useRender({\n    defaultTagName: 'button',\n    render,\n    state,\n    props: mergeProps<'button'>(defaultProps, otherProps),\n  });\n\n  return element;\n}\n\nexport default function ExampleCounter() {\n  return (\n    <Counter\n      render={(props, state) => (\n        <button {...props}>\n          {props.children}\n          <span className={styles.suffix}>{state.odd ? '👎' : '👍'}</span>\n        </button>\n      )}\n    />\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(docs)/react/utils/use-render/demos/render-callback/index.ts",
    "content": "import { createDemoWithVariants } from 'docs/src/utils/createDemo';\nimport CssModules from './css-modules';\n\nexport const DemoUseRenderRenderCallback = createDemoWithVariants(import.meta.url, {\n  CssModules,\n});\n"
  },
  {
    "path": "docs/src/app/(docs)/react/utils/use-render/page.mdx",
    "content": "# useRender\n\n<Subtitle>Hook for enabling a render prop in custom components.</Subtitle>\n\n<Meta name=\"description\" content=\"Hook for enabling a render prop in custom components.\" />\n\nThe `useRender` hook lets you build custom components that provide a `render` prop to override the default rendered element.\n\n## Examples\n\nA `render` prop for a custom Text component lets consumers use it to replace the default rendered `p` element with a different tag or component.\n\nimport { DemoUseRenderRender } from './demos/render';\n\n<DemoUseRenderRender />\n\nThe callback version of the `render` prop enables more control of how props are spread, and also passes the internal `state` of a component.\n\nimport { DemoUseRenderRenderCallback } from './demos/render-callback';\n\n<DemoUseRenderRenderCallback />\n\n## Merging props\n\nThe `mergeProps` function merges two or more sets of React props together. It safely merges three types of props:\n\n1. Event handlers, so that all are invoked\n2. `className` strings\n3. `style` properties\n\n`mergeProps` merges objects from left to right, so that subsequent objects' properties in the arguments overwrite previous ones. Merging props is useful when creating custom components, as well as inside the callback version of the `render` prop for any Base UI component.\n\n```tsx title=\"Using mergeProps in the render callback\"\nimport { mergeProps } from '@base-ui/react/merge-props';\nimport styles from './index.module.css';\n\nfunction Button() {\n  return (\n    <Component\n      render={(props, state) => (\n        <button\n          {...mergeProps<'button'>(props, {\n            className: styles.Button,\n          })}\n        />\n      )}\n    />\n  );\n}\n```\n\n## Merging refs\n\nWhen building custom components, you often need to control a ref internally while still letting external consumers pass their own—merging refs lets both parties have access to the underlying DOM element. The `ref` option in `useRender` enables this, which holds an array of refs to be merged together.\n\nIn React 19, `React.forwardRef()` is not needed when building primitive components, as the external ref prop is already contained inside `props`. Your internal ref can be passed to `ref` to be merged with `props.ref`:\n\n```tsx title=\"React 19\" {6} \"internalRef\"\nfunction Text({ render, ...props }: TextProps) {\n  const internalRef = React.useRef<HTMLElement | null>(null);\n\n  const element = useRender({\n    defaultTagName: 'p',\n    ref: internalRef,\n    props,\n    render,\n  });\n\n  return element;\n}\n```\n\nIn older versions of React, you need to use `React.forwardRef()` and add the forwarded ref to the `ref` array along with your own internal ref.\n\nThe [examples](#examples) above assume React 19, and should be modified to use `React.forwardRef()` to support React 18 and 17.\n\n```tsx title=\"React 18 and 17\" {9} \"forwardedRef\" \"internalRef\"\nconst Text = React.forwardRef(function Text(\n  { render, ...props }: TextProps,\n  forwardedRef: React.ForwardedRef<HTMLElement>,\n) {\n  const internalRef = React.useRef<HTMLElement | null>(null);\n\n  const element = useRender({\n    defaultTagName: 'p',\n    ref: [forwardedRef, internalRef],\n    props,\n    render,\n  });\n\n  return element;\n});\n```\n\n## TypeScript\n\nTo type props, there are two interfaces:\n\n- `useRender.ComponentProps` for a component's external (public) props. It types the `render` prop and HTML attributes.\n- `useRender.ElementProps` for the element's internal (private) props. It types HTML attributes alone.\n\n```tsx title=\"Typing props\" {1,4}\ninterface ButtonProps extends useRender.ComponentProps<'button'> {}\n\nfunction Button({ render, ...props }: ButtonProps) {\n  const defaultProps: useRender.ElementProps<'button'> = {\n    className: styles.Button,\n    type: 'button',\n    children: 'Click me',\n  };\n\n  const element = useRender({\n    defaultTagName: 'button',\n    render,\n    props: mergeProps<'button'>(defaultProps, props),\n  });\n\n  return element;\n}\n```\n\n## Migrating from Radix UI\n\nRadix UI uses an `asChild` prop, while Base UI uses a `render` prop. Learn more about how composition works in Base UI in the [composition guide](/react/handbook/composition).\n\nIn Radix UI, the `Slot` component lets you implement an `asChild` prop.\n\n```jsx title=\"Radix UI Slot component\"\nimport { Slot } from 'radix-ui';\n\nfunction Button({ asChild, ...props }) {\n  const Comp = asChild ? Slot.Root : 'button';\n  return <Comp {...props} />;\n}\n\n// Usage\n<Button asChild>\n  <MyButton className=\"primary\">Submit</MyButton>\n</Button>;\n```\n\nIn Base UI, `useRender` lets you implement a `render` prop. The example below is the equivalent implementation to the Radix example above.\n\n```jsx title=\"Base UI render prop\"\nimport { useRender } from '@base-ui/react/use-render';\n\nfunction Button({ render, ...props }) {\n  return useRender({\n    defaultTagName: 'button',\n    render,\n    props,\n  });\n}\n\n// Usage\n<Button render={<MyButton className=\"primary\" />}>Submit</Button>;\n```\n\n## Render prop and polymorphism\n\nThe `render` prop is primarily designed for composing event handlers and behavioral props. In most cases it should render the same tag as the default element.\n\nUsing `render` for polymorphism (rendering a different tag) requires more care, as some default props may not be valid on the new element. For example, `type=\"button\"` is only valid on a `<button>`. Since the component can't know what element `render` will produce at render time and before hydration, props like these need an explicit signal. This is why Base UI's [Button](/react/components/button) provides a `nativeButton` prop to control which defaults are applied.\n\n## API reference\n\n### Input parameters\n\n<PropsReferenceTable\n  data={{\n    defaultTagName: {\n      type: 'keyof React.JSX.IntrinsicElements',\n      description:\n        'The default tag name to use for the rendered element when `render` is not provided.',\n    },\n    render: {\n      type: 'RenderProp<State>',\n      description:\n        'The React element or a function that returns one to override the default element.',\n    },\n    props: {\n      type: 'Record<string, unknown>',\n      description:\n        'Props to be spread on the rendered element.\\n\\nThey are merged with the internal props\\n of the component, so that event handlers are merged,\\n`className` strings and `style` properties are joined, and other external props overwrite the internal ones.',\n    },\n    ref: {\n      type: 'React.Ref<RenderedElementType> | React.Ref<RenderedElementType>[]',\n      description: 'The refs to apply to the rendered element.',\n    },\n    state: {\n      type: 'State',\n      description:\n        'The state of the component. It will be used as a parameter for the render callback.',\n    },\n    stateAttributesMapping: {\n      type: 'StateAttributesMapping<State>',\n      description: 'Custom mapping for converting state properties to data-* attributes.',\n    },\n  }}\n/>\n\n### Return value\n\n<PropsReferenceTable\n  type=\"return\"\n  data={{\n    element: {\n      type: 'React.ReactElement',\n      description: 'The rendered React element',\n    },\n  }}\n/>\n\n```tsx title=\"Usage\"\nconst element = useRender({\n  // Input parameters\n});\n```\n\nexport const metadata = {\n  keywords: [\n    'Base UI useRender',\n    'React Render Prop Hook',\n    'Merge Props Helper',\n    'Merge Refs Hook',\n    'Radix asChild Migration',\n    'Component Composition',\n    'Props Spreading',\n    'Ref Forwarding',\n    'Custom Components',\n    'asChild Pattern',\n    'Slot Pattern',\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(private)/docs-theme/page.module.css",
    "content": ".page {\n  padding: var(--space-40);\n  color: var(--gray-t2);\n  background: var(--gray-s1);\n  font-family: var(--font-sans-a);\n}\n\n.hero {\n  max-width: 52rem;\n}\n\n.hero h1 {\n  margin: 0;\n  font-family: var(--font-sans-b);\n  font-size: var(--font-size-42);\n  font-weight: var(--font-weight-700);\n}\n\n.kicker {\n  margin: 0 0 var(--space-8);\n  color: var(--gray-t1);\n  font-size: var(--font-size-12);\n  letter-spacing: 0.06em;\n  text-transform: uppercase;\n}\n\n.section {\n  padding: var(--space-48) 0;\n}\n\n.section + .section {\n  border-top: 1px solid var(--gray-c1);\n}\n\n.tokenName {\n  margin: 0;\n  color: var(--gray-t1);\n  font-family: var(--font-mono);\n  font-size: var(--font-size-12);\n}\n\n.colorMatrixSection + .colorMatrixSection {\n  margin-top: var(--space-32);\n}\n\n.colorMatrixGrid {\n  display: grid;\n  grid-template-columns: 7rem repeat(9, 6.5rem);\n  row-gap: 2px;\n  align-items: start;\n  width: max-content;\n}\n\n.colorMatrixRow {\n  grid-column: 1 / -1;\n  display: grid;\n  grid-template-columns: subgrid;\n  column-gap: 2px;\n}\n\n.colorMatrixRowLabel {\n  display: flex;\n  align-items: center;\n  color: var(--gray-t1);\n  font-family: var(--font-mono);\n  font-size: var(--font-size-12);\n}\n\n.colorMatrixHeaderCell {\n  color: var(--gray-p2);\n  font-family: var(--font-mono);\n  font-size: var(--font-size-12);\n  text-transform: lowercase;\n  text-align: center;\n}\n\n.swatch {\n  position: relative;\n  isolation: isolate;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 100%;\n  height: 3rem;\n  --swatch-composite: rgb(\n    from var(--swatch-color) calc(r * alpha + 255 * (1 - alpha)) calc(g * alpha + 255 * (1 - alpha))\n      calc(b * alpha + 255 * (1 - alpha))\n  );\n}\n\n.swatch:hover {\n  outline: 2px solid var(--gray-t2);\n}\n\n.swatchTokenName {\n  position: absolute;\n  inset: 0;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  color: black;\n  font-family: var(--font-mono);\n  font-size: var(--font-size-12);\n  opacity: 0;\n  pointer-events: none;\n}\n\n.swatch:hover .swatchTokenName {\n  opacity: 1;\n}\n\n@supports (color: lch(from white 50 0 0deg)) {\n  .swatchTokenName {\n    /* Automatic black/white label based on the swatch color lightness */\n    color: lch(from var(--swatch-composite) round((100 - l), 100) 0 0deg);\n  }\n}\n\n/* Tune blackA readability (auto color can be too faint on near-white alpha steps) */\n.colorMatrixRowAlpha > .colorMatrixCell {\n  background-color: var(--gray-s1);\n  background-image:\n    linear-gradient(45deg, var(--gray-c1) 25%, transparent 25%),\n    linear-gradient(-45deg, var(--gray-c1) 25%, transparent 25%),\n    linear-gradient(45deg, transparent 75%, var(--gray-c1) 75%),\n    linear-gradient(-45deg, transparent 75%, var(--gray-c1) 75%);\n  background-size: 12px 12px;\n  background-position:\n    0 0,\n    0 6px,\n    6px -6px,\n    -6px 0;\n}\n\n.colorMatrixRowAlpha > .colorMatrixCell:nth-child(-n + 6) .swatchTokenName {\n  color: black;\n}\n\n.colorMatrixRowAlpha > .colorMatrixCell:nth-child(7) .swatchTokenName {\n  color: white;\n}\n\n@media (prefers-color-scheme: dark) {\n  .colorMatrixRowAlpha > .colorMatrixCell:nth-child(-n + 6) .swatchTokenName,\n  .colorMatrixRowAlpha > .colorMatrixCell:nth-child(7) .swatchTokenName {\n    color: white;\n  }\n}\n\n.typeGrid {\n  display: flex;\n  flex-direction: column;\n  gap: var(--space-24);\n}\n\n.radiusGrid {\n  display: grid;\n  grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));\n  gap: var(--space-24);\n}\n\n.shadowGrid {\n  display: grid;\n  grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));\n  gap: var(--space-24);\n}\n\n.typeCard,\n.radiusCard,\n.shadowCard {\n  border-radius: var(--radius-5);\n}\n\n.typeSample {\n  margin: var(--space-8) 0 0;\n  font-size: var(--font-size-16);\n}\n\n.typeScale {\n  display: grid;\n  gap: var(--space-32);\n}\n\n.typeScaleRow {\n  border-radius: var(--radius-5);\n}\n\n.typeScaleRow p:last-child {\n  margin: var(--space-8) 0 0;\n}\n\n.spaceList {\n  display: flex;\n  flex-direction: column;\n  gap: var(--space-16);\n}\n\n.spaceRow {\n  display: flex;\n  align-items: center;\n  gap: var(--space-16);\n  border-radius: var(--radius-5);\n}\n\n.spaceRow .tokenName {\n  width: 6.5rem;\n  flex: 0 0 6.5rem;\n}\n\n.spaceBar {\n  min-width: 1px;\n  height: 0.75rem;\n  background: var(--gray-t2);\n}\n\n.radiusShape {\n  margin-top: var(--space-12);\n  background: var(--gray-t2);\n}\n\n.radiusShapeSquare {\n  width: 4rem;\n  height: 4rem;\n}\n\n.radiusShapePill {\n  width: 6rem;\n  height: 4rem;\n}\n\n.shadowSurface {\n  height: 5rem;\n  margin-top: var(--space-12);\n  border-radius: var(--radius-4);\n  background: var(--gray-s1);\n\n  /* Hack to make the surface visible in dark mode since there are no shadows in dark mode */\n  @media (prefers-color-scheme: dark) {\n    background: var(--gray-s2);\n  }\n}\n\n@media (max-width: 48rem) {\n  .page {\n    padding: var(--space-24);\n  }\n\n  .hero h1 {\n    font-size: var(--font-size-36);\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/docs-theme/page.tsx",
    "content": "import type { Metadata } from 'next';\nimport * as React from 'react';\n\nimport 'docs/src/css/theme-redesign.css';\nimport styles from './page.module.css';\n\nconst coreScaleSteps = ['s1', 's2', 'c1', 'c2', 'c3', 'p1', 'p2', 't1', 't2'] as const;\nconst coreColorRows = ['gray', 'indigo'] as const;\nconst accentColorRows = ['poppy', 'blue', 'green', 'orange', 'pink', 'grape', 'lime'] as const;\nconst alphaColorRows = ['blackA'] as const;\nconst alphaScaleSteps = ['1', '2', '3', '4', '5', '6'] as const;\nconst allCoreRows = [...coreColorRows, ...accentColorRows];\n\nconst typefaces = [\n  { token: 'font-sans-a', sample: 'Die Grotesk A for UI copy' },\n  { token: 'font-sans-b', sample: 'Die Grotesk B for headings' },\n  { token: 'font-mono', sample: 'Söhne Mono for code and data' },\n  { token: 'font-serif', sample: 'Georgia for editorial accents' },\n] as const;\n\nconst fontWeights = [\n  { token: 'font-weight-400', sample: 'Regular' },\n  { token: 'font-weight-700', sample: 'Bold' },\n] as const;\n\nconst fontSizeScale = ['12', '14', '15', '16', '18', '21', '24', '36', '42'] as const;\nconst spaceScale = ['4', '8', '12', '16', '20', '24', '28', '32', '40', '48'] as const;\nconst radiusScale = ['2', '3', '4', '6', '8', '12', '16', 'pill', 'circle'] as const;\nconst shadows = ['1', '2', '3', '4', '5'] as const;\n\nexport default function ThemePage() {\n  return (\n    <main className={styles.page} data-theme=\"redesign\">\n      <header className={styles.hero}>\n        <p className={styles.kicker}>\n          <span aria-hidden=\"true\">🔒</span> Private\n        </p>\n        <h1>Docs Theme</h1>\n      </header>\n\n      <section className={styles.section}>\n        <div className={styles.colorMatrixSection}>\n          <div className={styles.colorMatrixGrid}>\n            <div className={styles.colorMatrixRow}>\n              <div className={styles.colorMatrixHeaderCell} />\n              {coreScaleSteps.map((step) => (\n                <div key={`core-step-${step}`} className={styles.colorMatrixHeaderCell}>\n                  {step}\n                </div>\n              ))}\n            </div>\n\n            {allCoreRows.map((row) => {\n              const isAccentRow = accentColorRows.includes(row as (typeof accentColorRows)[number]);\n\n              return (\n                <div key={`core-row-${row}`} className={styles.colorMatrixRow}>\n                  <div className={styles.colorMatrixRowLabel}>{row}</div>\n                  {coreScaleSteps.map((step) => {\n                    if (isAccentRow && step !== 't1') {\n                      return <div key={`${row}-${step}`} />;\n                    }\n\n                    const token = isAccentRow ? `${row}-t1` : `${row}-${step}`;\n                    const swatchStyle = {\n                      backgroundColor: `var(--${token})`,\n                      '--swatch-color': `var(--${token})`,\n                    } as React.CSSProperties;\n\n                    return (\n                      <div key={token} className={styles.colorMatrixCell}>\n                        <div className={styles.swatch} style={swatchStyle}>\n                          <span className={styles.swatchTokenName}>--{token}</span>\n                        </div>\n                      </div>\n                    );\n                  })}\n                </div>\n              );\n            })}\n          </div>\n        </div>\n\n        <div className={styles.colorMatrixSection}>\n          <div className={styles.colorMatrixGrid}>\n            <div className={styles.colorMatrixRow}>\n              <div className={styles.colorMatrixHeaderCell} />\n              {alphaScaleSteps.map((step) => (\n                <div key={`alpha-step-${step}`} className={styles.colorMatrixHeaderCell}>\n                  {step}\n                </div>\n              ))}\n            </div>\n\n            {alphaColorRows.map((row) => (\n              <div\n                key={`alpha-row-${row}`}\n                className={`${styles.colorMatrixRow} ${styles.colorMatrixRowAlpha}`}\n              >\n                <div className={styles.colorMatrixRowLabel}>{row}</div>\n                {alphaScaleSteps.map((step) => {\n                  const token = `${row}-${step}`;\n                  const swatchStyle = {\n                    backgroundColor: `var(--${token})`,\n                    '--swatch-color': `var(--${token})`,\n                  } as React.CSSProperties;\n\n                  return (\n                    <div key={token} className={styles.colorMatrixCell}>\n                      <div className={styles.swatch} style={swatchStyle}>\n                        <span className={styles.swatchTokenName}>--{token}</span>\n                      </div>\n                    </div>\n                  );\n                })}\n              </div>\n            ))}\n          </div>\n        </div>\n      </section>\n\n      <section className={styles.section}>\n        <div className={styles.typeGrid}>\n          {typefaces.map((typeface) => (\n            <article key={typeface.token} className={styles.typeCard}>\n              <p className={styles.tokenName}>--{typeface.token}</p>\n              <p className={styles.typeSample} style={{ fontFamily: `var(--${typeface.token})` }}>\n                {typeface.sample}\n              </p>\n            </article>\n          ))}\n        </div>\n      </section>\n\n      <section className={styles.section}>\n        <div className={styles.typeScale}>\n          {fontSizeScale.map((step) => (\n            <article key={step} className={styles.typeScaleRow}>\n              <p className={styles.tokenName}>--font-size-{step}</p>\n              <p\n                style={{\n                  fontFamily: 'var(--font-sans-a)',\n                  fontSize: `var(--font-size-${step})`,\n                }}\n              >\n                The quick brown fox jumps over the lazy dog.\n              </p>\n            </article>\n          ))}\n        </div>\n      </section>\n\n      <section className={styles.section}>\n        <div className={styles.typeGrid}>\n          {fontWeights.map((weight) => (\n            <article key={weight.token} className={styles.typeCard}>\n              <p className={styles.tokenName}>--{weight.token}</p>\n              <p\n                className={styles.typeSample}\n                style={{ fontFamily: 'var(--font-sans-a)', fontWeight: `var(--${weight.token})` }}\n              >\n                {weight.sample}\n              </p>\n            </article>\n          ))}\n        </div>\n      </section>\n\n      <section className={styles.section}>\n        <div className={styles.spaceList}>\n          {spaceScale.map((step) => (\n            <article key={step} className={styles.spaceRow}>\n              <p className={styles.tokenName}>--space-{step}</p>\n              <div className={styles.spaceBar} style={{ width: `var(--space-${step})` }} />\n            </article>\n          ))}\n        </div>\n      </section>\n\n      <section className={styles.section}>\n        <div className={styles.radiusGrid}>\n          {radiusScale.map((step) => (\n            <article key={step} className={styles.radiusCard}>\n              <p className={styles.tokenName}>--radius-{step}</p>\n              <div\n                className={`${styles.radiusShape} ${step === 'pill' ? styles.radiusShapePill : styles.radiusShapeSquare}`}\n                style={{\n                  borderRadius: `var(--radius-${step})`,\n                }}\n              />\n            </article>\n          ))}\n        </div>\n      </section>\n\n      <section className={styles.section}>\n        <div className={styles.shadowGrid}>\n          {shadows.map((step) => (\n            <article key={step} className={styles.shadowCard}>\n              <p className={styles.tokenName}>--shadow-{step}</p>\n              <div\n                className={styles.shadowSurface}\n                style={{ boxShadow: `var(--shadow-${step})` }}\n              />\n            </article>\n          ))}\n        </div>\n      </section>\n    </main>\n  );\n}\n\nexport const metadata: Metadata = {\n  title: 'Docs Theme',\n  robots: {\n    index: false,\n    follow: false,\n  },\n};\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/[...slug]/page.tsx",
    "content": "import { type Metadata } from 'next';\nimport { notFound } from 'next/navigation';\nimport { dirname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { globby } from 'globby';\nimport { Sidebar } from '../_components/Sidebar';\nimport { ExperimentRoot } from '../_components/ExperimentRoot';\nimport classes from '../_components/ExperimentRoot.module.css';\nimport { ExperimentSettingsProvider } from '../_components/SettingsPanel';\n\nconst currentDirectory = dirname(fileURLToPath(import.meta.url));\nconst experimentsRootDirectory = resolve(currentDirectory, '..');\n\ninterface Props {\n  params: Promise<{\n    slug: string[];\n  }>;\n}\n\nexport default async function Page(props: Props) {\n  const { slug } = await props.params;\n\n  const fullPath = resolve(currentDirectory, `../${slug.join('/')}.tsx`);\n\n  try {\n    const experimentModule = await import(`../${slug.join('/')}.tsx`);\n    const Experiment = experimentModule.default;\n    const settingsMetadata = experimentModule.settingsMetadata;\n\n    return (\n      <ExperimentSettingsProvider metadata={settingsMetadata}>\n        <ExperimentRoot\n          sidebar={\n            <Sidebar\n              experimentPath={fullPath}\n              className={classes.sidebar}\n              settingsMetadata={settingsMetadata}\n            />\n          }\n        >\n          <Experiment />\n        </ExperimentRoot>\n      </ExperimentSettingsProvider>\n    );\n  } catch (error) {\n    notFound();\n  }\n}\n\nexport async function generateStaticParams() {\n  const files = await globby(\n    ['**/*.tsx', '!infra/**/*', '!**/page.tsx', '!**/layout.tsx', '!_components/**/*'],\n    {\n      cwd: experimentsRootDirectory,\n    },\n  );\n\n  return files\n    .filter((file) => file.split('/').length <= 2)\n    .map((file) => {\n      return {\n        slug: file.replace(/\\.tsx$/, '').split('/'),\n      };\n    });\n}\n\nexport async function generateMetadata(props: Props): Promise<Metadata> {\n  const params = await props.params;\n  const { slug } = params;\n\n  return {\n    title: `${slug[slug.length - 1]} - Experiments`,\n  };\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/_components/Button.module.css",
    "content": ".root {\n  all: unset;\n  display: block;\n  box-sizing: border-box;\n  padding: 4px 8px;\n  border-radius: var(--radius-md);\n  cursor: pointer;\n}\n\n.text {\n  color: var(--color-foreground);\n\n  &:hover {\n    background-color: var(--color-gray-100);\n  }\n}\n\n.fullWidth {\n  width: calc(100% + 16px);\n  margin-left: -8px;\n  margin-right: -8px;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/_components/Button.tsx",
    "content": "import * as React from 'react';\nimport clsx from 'clsx';\nimport classes from './Button.module.css';\nimport { Tooltip } from './Tooltip';\n\nexport function Button(props: ButtonProps) {\n  const { children, className, variant, fullWidth = false, tooltip, ...otherProps } = props;\n  const button = (\n    <button\n      type=\"button\"\n      {...otherProps}\n      className={clsx(classes.root, classes[variant], fullWidth && classes.fullWidth, className)}\n    >\n      {children}\n    </button>\n  );\n\n  if (tooltip) {\n    return <Tooltip text={tooltip}>{button}</Tooltip>;\n  }\n\n  return button;\n}\n\nexport interface ButtonProps extends React.HTMLAttributes<HTMLButtonElement> {\n  variant: 'text';\n  fullWidth?: boolean;\n  tooltip?: string;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/_components/EditPanel.tsx",
    "content": "import * as React from 'react';\nimport { fileURLToPath } from 'node:url';\nimport { existsSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { basename, dirname, extname, resolve } from 'node:path';\nimport type { DemoFile } from 'docs/src/blocks/Demo';\nimport { highlighter } from 'docs/src/syntax-highlighting';\nimport { SandboxLink } from './SandboxLink';\n\n/**\n * Looks for local imports in the file content and resolves them to absolute paths.\n *\n * @param content JS/TS file content.\n * @param baseDirectory Directory the file is located in.\n */\nfunction getLocalImports(content: string, baseDirectory: string): string[] {\n  const localPaths = [\n    // import { foo } from './foo'\n    ...(content.match(/from ['\"]\\.\\.?\\/[^'\"]+['\"]/g)?.map((match) => match.slice(6, -1)) ?? []),\n    // import './foo'\n    ...(content.match(/import ['\"]\\.\\.?\\/[^'\"]+['\"]/g)?.map((match) => match.slice(8, -1)) ?? []),\n  ];\n\n  return localPaths.map((file) => resolve(baseDirectory, file));\n}\n\nconst shikiLanguageMapping = {\n  jsx: 'js',\n  ts: 'tsx',\n} as Record<string, string>;\n\n/**\n * Lists all the dependencies of the provided files, including transitive dependencies (only in case of JS/TS files).\n *\n * @param paths Paths to the files to read.\n * @param preferTs Whether to prefer TS files over JS files when resolving extensionless imports.\n */\nexport async function getDependencyFiles(\n  paths: string[],\n  preferTs: boolean,\n  visited: Set<string> = new Set(),\n): Promise<DemoFile[]> {\n  const files = await Promise.all(\n    paths.map(async (path) => {\n      let extension = extname(path);\n\n      if (extension === '') {\n        path = resolveExtensionlessFile(path, preferTs);\n        extension = extname(path);\n      }\n\n      if (visited.has(path)) {\n        return [];\n      }\n      visited.add(path);\n\n      let type: string;\n      if (extension === '.ts' || extension === '.tsx') {\n        type = 'ts';\n      } else if (extension === '.js' || extension === '.jsx') {\n        type = 'js';\n      } else {\n        type = extension.slice(1);\n      }\n\n      const content = await readFile(path, 'utf-8');\n      const prettyContent = highlighter.codeToHtml(content, {\n        lang: shikiLanguageMapping[extension.slice(1)] ?? extension.slice(1),\n        theme: 'base-ui',\n      });\n\n      const canHaveDependencies = type === 'ts' || type === 'js';\n      const transitiveDependencies = canHaveDependencies\n        ? await getDependencyFiles(getLocalImports(content, dirname(path)), type === 'ts', visited)\n        : [];\n\n      return [\n        {\n          name: basename(path),\n          content,\n          prettyContent,\n          path,\n          type,\n        } satisfies DemoFile,\n        ...transitiveDependencies,\n      ];\n    }),\n  );\n\n  return files.flat();\n}\n\n/**\n * Given a file path without an extension, resolves it to a file with one of the supported extensions.\n *\n * @param filePath Path to the file without an extension.\n * @param preferTs Whether to prefer TS files over JS files.\n */\nfunction resolveExtensionlessFile(filePath: string, preferTs: boolean): string {\n  const extensions = preferTs\n    ? ['.tsx', '.ts', '.jsx', '.js', '.json']\n    : ['.jsx', '.js', '.tsx', '.ts', '.json'];\n\n  for (const extension of extensions) {\n    const fullPath = `${filePath}${extension}`;\n    if (existsSync(fullPath)) {\n      return fullPath;\n    }\n  }\n\n  for (const extension of extensions) {\n    const fullPath = `${filePath}/index${extension}`;\n    if (existsSync(fullPath)) {\n      return fullPath;\n    }\n  }\n\n  throw new Error(\n    `Could not find the file ${filePath} with any of the supported extensions: ${extensions.join(', ')}.`,\n  );\n}\n\nconst currentDirectory = dirname(fileURLToPath(import.meta.url));\n\nexport async function EditPanel(props: EditPanelProps) {\n  const { experimentPath, ...otherProps } = props;\n\n  const dependencies = await getDependencyFiles(\n    [experimentPath, resolve(currentDirectory, './SettingsPanel.tsx')],\n    true,\n  );\n\n  return (\n    <div {...otherProps}>\n      <h2>Edit</h2>\n      <SandboxLink files={dependencies}>Open in CodeSandbox ({experimentPath})</SandboxLink>\n    </div>\n  );\n}\n\ninterface EditPanelProps extends React.HTMLAttributes<HTMLDivElement> {\n  experimentPath: string;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/_components/ExperimentRoot.module.css",
    "content": ".root {\n  display: flex;\n  min-height: 100vh;\n  font-family:\n    ui-sans-serif,\n    -apple-system,\n    BlinkMacSystemFont,\n    'Segoe UI Variable Display',\n    'Segoe UI',\n    Helvetica,\n    'Apple Color Emoji',\n    Arial,\n    sans-serif;\n\n  & h1 {\n    font-size: 2rem;\n    margin-bottom: 1rem;\n    font-weight: 500;\n  }\n\n  --sidebar-width: 0;\n\n  &.withSidebar {\n    --sidebar-width: 260px;\n\n    @media screen and (max-width: 1300px) {\n      --sidebar-width: max(20vw, 150px);\n    }\n\n    @media screen and (max-width: 650px) {\n      --sidebar-width: 0;\n    }\n  }\n}\n\n.main {\n  flex: 1 1 100%;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  font-family: var(--ff-sans);\n  padding: 20px;\n  background-color: var(--color-background);\n  margin-left: var(--sidebar-width);\n}\n\n.sidebar {\n  box-sizing: border-box;\n  width: var(--sidebar-width);\n  font-size: var(--text-sm);\n  position: fixed;\n  top: 0;\n  bottom: 0;\n  left: 0;\n\n  @media screen and (max-width: 650px) {\n    display: none;\n  }\n}\n\n.landing {\n  padding-top: 30vh;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/_components/ExperimentRoot.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport clsx from 'clsx';\nimport { ShowSidebar } from './ShowSidebar';\nimport classes from './ExperimentRoot.module.css';\n\nexport interface ExperimentRootContext {\n  sidebarVisible: boolean;\n  setSidebarVisible: React.Dispatch<React.SetStateAction<boolean>>;\n}\n\nexport const ExperimentRootContext = React.createContext<ExperimentRootContext | undefined>(\n  undefined,\n);\n\nexport function ExperimentRoot(props: ExperimentRootProps) {\n  const { children, sidebar } = props;\n  const [sidebarVisible, setSidebarVisible] = React.useState(true);\n\n  const rootContext = React.useMemo(\n    () => ({\n      sidebarVisible,\n      setSidebarVisible,\n    }),\n    [sidebarVisible],\n  );\n\n  return (\n    <ExperimentRootContext value={rootContext}>\n      <div className={clsx(classes.root, sidebarVisible && classes.withSidebar)}>\n        {sidebarVisible ? sidebar : <ShowSidebar />}\n        <main className={classes.main}>{children}</main>\n      </div>\n    </ExperimentRootContext>\n  );\n}\n\nexport interface ExperimentRootProps {\n  children: React.ReactNode;\n  sidebar?: React.ReactNode;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/_components/ExperimentsList.module.css",
    "content": ".list {\n  display: flex;\n  flex-direction: column;\n  gap: 2px;\n\n  & a {\n    display: block;\n    text-decoration: none;\n    color: var(--color-foreground);\n    padding: 4px 8px;\n    border-radius: var(--radius-md);\n    margin-left: -8px;\n\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  & h3 {\n    font-weight: 500;\n    color: var(--color-gray-500);\n    margin-top: 2px;\n  }\n}\n\n.groupItems {\n  margin-left: 8px;\n  margin-bottom: 0.75em;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/_components/ExperimentsList.tsx",
    "content": "import * as React from 'react';\nimport { globbySync } from 'globby';\nimport Link from 'next/link';\nimport clsx from 'clsx';\nimport { dirname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { camelToSentenceCase } from 'docs/src/utils/camelToSentenceCase';\nimport classes from './ExperimentsList.module.css';\n\nconst currentDirectory = dirname(fileURLToPath(import.meta.url));\nconst experimentsRootDirectory = resolve(currentDirectory, '../');\n\nconst allExperimentFiles = globbySync(\n  ['**/*.tsx', '!infra/**/*', '!**/page.tsx', '!**/layout.tsx', '!**/_components'],\n  { cwd: experimentsRootDirectory },\n);\n\nconst groups: Record<string, { name: string; path: string }[]> = {};\n\nfor (const key of allExperimentFiles) {\n  const segments = key.split('/');\n\n  // Ignore nested entries like `perf/utils/*` to keep navigation at 1 level deep.\n  if (segments.length > 2) {\n    continue;\n  }\n  let group: string;\n  let name: string;\n\n  if (segments.length === 1) {\n    group = '*';\n    name = segments[0];\n  } else {\n    group = camelToSentenceCase(segments[0]);\n    name =\n      segments[1].toLowerCase().startsWith(`${group.toLowerCase()}-`) &&\n      segments[1].length > group.length\n        ? segments[1].slice(group.length + 1).trim()\n        : segments[1].trim();\n  }\n\n  if (!groups[group]) {\n    groups[group] = [];\n  }\n\n  if (!name.startsWith('_')) {\n    groups[group].push({\n      name: camelToSentenceCase(name.replace('.tsx', '').replace(/-/g, ' ')),\n      path: key.replace('.tsx', ''),\n    });\n  }\n}\n\nexport function ExperimentsList(props: React.HTMLAttributes<HTMLDivElement>) {\n  return (\n    <div {...props} className={clsx(classes.list, props.className)}>\n      <h2>All experiments</h2>\n      {Object.keys(groups)\n        .sort()\n        .filter((key) => key !== '*')\n        .map((group: string) => {\n          return (\n            <div key={group}>\n              <h3>{group}</h3>\n              <ul className={classes.groupItems}>\n                {groups[group]\n                  .sort((a, b) => a.name.localeCompare(b.name))\n                  .map((experiment) => (\n                    <li key={experiment.path}>\n                      <Link href={`/experiments/${experiment.path}`}>{experiment.name}</Link>\n                    </li>\n                  ))}\n              </ul>\n            </div>\n          );\n        })}\n      {groups['*'] && (\n        <div>\n          <h3>Other</h3>\n          <ul className={classes.groupItems}>\n            {groups['*']\n              .sort((a, b) => a.name.localeCompare(b.name))\n              .map((experiment) => (\n                <li key={experiment.path}>\n                  <Link href={`/experiments/${experiment.path}`}>{experiment.name}</Link>\n                </li>\n              ))}\n          </ul>\n        </div>\n      )}\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/_components/HideSidebar.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { ExperimentRootContext } from './ExperimentRoot';\nimport { Button } from './Button';\n\nexport function HideSidebar(props: React.HTMLAttributes<HTMLDivElement>) {\n  const rootContext = React.useContext(ExperimentRootContext);\n  if (!rootContext) {\n    return null;\n  }\n\n  return (\n    <div {...props}>\n      <h2>Sidebar visibility</h2>\n      <Button onClick={() => rootContext.setSidebarVisible(false)} variant=\"text\" fullWidth>\n        Hide sidebar\n      </Button>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/_components/Input.module.css",
    "content": ".input {\n  box-sizing: border-box;\n  padding-left: 0.375rem;\n  margin: 0;\n  border: 1px solid var(--color-gray-200);\n  height: 1.75rem;\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: var(--text-sm);\n  font-weight: normal;\n  background-color: transparent;\n  color: var(--color-gray-900);\n\n  &:focus {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/_components/Input.tsx",
    "content": "import clsx from 'clsx';\nimport { Input as BaseInput } from '@base-ui/react/input';\nimport styles from './Input.module.css';\n\nexport function Input(props: BaseInput.Props) {\n  return <BaseInput {...props} className={clsx(styles.input, props.className)} />;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/_components/SandboxLink.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { createCodeSandbox } from 'docs/src/blocks/createCodeSandbox/createCodeSandbox';\nimport { DemoFile } from 'docs/src/blocks/Demo';\nimport { resolveDependencies } from 'docs/src/utils/demoExportOptions';\nimport { Button } from './Button';\n\nexport function SandboxLink(props: SandboxLinkProps) {\n  const { files, ...otherProps } = props;\n\n  const handleClick = React.useCallback(() => {\n    createCodeSandbox({\n      demoFiles: files,\n      demoLanguage: 'ts',\n      title: 'Base UI experiment',\n      dependencies: {\n        react: '^19',\n        'react-dom': '^19',\n      },\n      devDependencies: {\n        '@types/react': '^19',\n        '@types/react-dom': '^19',\n        'react-scripts': 'latest',\n      },\n      dependencyResolver: resolveDependencies,\n      customIndexFile: indexTs,\n    });\n  }, [files]);\n\n  return (\n    <Button {...otherProps} onClick={handleClick} variant=\"text\" fullWidth>\n      Open in CodeSandbox\n    </Button>\n  );\n}\n\nconst indexTs = `import * as React from 'react';\nimport * as ReactDOM from 'react-dom/client';\nimport Experiment, { settingsMetadata } from './App';\nimport { SettingsPanel, ExperimentSettingsProvider } from './SettingsPanel';\n\nReactDOM.createRoot(document.querySelector(\"#root\")!).render(\n  <React.StrictMode>\n    <ExperimentSettingsProvider metadata={settingsMetadata}>\n      <Experiment />\n      <SettingsPanel metadata={settingsMetadata} />\n    </ExperimentSettingsProvider>\n  </React.StrictMode>\n);`;\n\ninterface SandboxLinkProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n  files: DemoFile[];\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/_components/Select.module.css",
    "content": ".Select {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 0.75rem;\n  height: 1.75rem;\n  padding-left: 0.375rem;\n  padding-right: 0.375rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: var(--text-sm);\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  cursor: default;\n  user-select: none;\n  min-width: 5rem;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.SelectIcon {\n  display: flex;\n}\n\n.Popup {\n  box-sizing: border-box;\n  padding-block: 0.25rem;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  min-width: var(--anchor-width);\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  &[data-side='none'] {\n    transition: none;\n    transform: none;\n    opacity: 1;\n    min-width: calc(var(--anchor-width) + 1rem);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Arrow {\n  display: flex;\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n\n.Item {\n  box-sizing: border-box;\n  outline: 0;\n  font-size: var(--text-sm);\n  line-height: 1rem;\n  padding-block: 0.5rem;\n  padding-left: 0.625rem;\n  padding-right: 1rem;\n  display: grid;\n  gap: 0.5rem;\n  align-items: center;\n  grid-template-columns: 0.75rem 1fr;\n  cursor: default;\n  user-select: none;\n\n  [data-side='none'] & {\n    padding-right: 3rem;\n  }\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.25rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.ItemIndicator {\n  grid-column-start: 1;\n}\n\n.ItemIndicatorIcon {\n  display: block;\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.ItemText {\n  grid-column-start: 2;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/_components/Select.tsx",
    "content": "import * as React from 'react';\nimport clsx from 'clsx';\nimport { Select as BaseSelect } from '@base-ui/react/select';\nimport classes from './Select.module.css';\n\nexport function Select(props: Select.Props) {\n  const { value, onChange, options, ...otherProps } = props;\n\n  return (\n    <BaseSelect.Root value={value} onValueChange={onChange}>\n      <BaseSelect.Trigger {...otherProps} className={clsx(classes.Select, otherProps.className)}>\n        <BaseSelect.Value />\n        <BaseSelect.Icon className={classes.SelectIcon}>\n          <ChevronUpDownIcon />\n        </BaseSelect.Icon>\n      </BaseSelect.Trigger>\n      <BaseSelect.Portal>\n        <BaseSelect.Positioner className={classes.Positioner} sideOffset={8}>\n          <BaseSelect.Popup className={classes.Popup}>\n            <BaseSelect.Arrow className={classes.Arrow}>\n              <ArrowSvg />\n            </BaseSelect.Arrow>\n            {options.map((option) => (\n              <BaseSelect.Item className={classes.Item} value={option} key={option}>\n                <BaseSelect.ItemIndicator className={classes.ItemIndicator}>\n                  <CheckIcon className={classes.ItemIndicatorIcon} />\n                </BaseSelect.ItemIndicator>\n                <BaseSelect.ItemText className={classes.ItemText}>{option}</BaseSelect.ItemText>\n              </BaseSelect.Item>\n            ))}\n          </BaseSelect.Popup>\n        </BaseSelect.Positioner>\n      </BaseSelect.Portal>\n    </BaseSelect.Root>\n  );\n}\n\nexport namespace Select {\n  export interface Props extends Omit<\n    React.HTMLAttributes<HTMLButtonElement>,\n    'defaultValue' | 'onChange'\n  > {\n    value: string | null;\n    onChange: (value: string | null) => void;\n    options: string[];\n  }\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={classes.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={classes.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={classes.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nfunction ChevronUpDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"8\"\n      height=\"12\"\n      viewBox=\"0 0 8 12\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"1.5\"\n      {...props}\n    >\n      <path d=\"M0.5 4.5L4 1.5L7.5 4.5\" />\n      <path d=\"M0.5 7.5L4 10.5L7.5 7.5\" />\n    </svg>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/_components/SettingsPanel.module.css",
    "content": ".popup {\n  box-sizing: border-box;\n  padding: 1rem 1.5rem;\n  border-radius: 0.5rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.settings {\n  font-size: 0.875rem;\n  display: flex;\n  flex-direction: column;\n  gap: 8px;\n}\n\n.trigger {\n  position: fixed;\n  top: 20px;\n  right: 20px;\n  padding: 4px;\n  box-sizing: border-box;\n  width: 34px;\n  height: 34px;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: 0.375rem;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 500;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.singleLineField {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  gap: 12px;\n  min-height: 1.75rem;\n}\n\n.multiLineField {\n  display: flex;\n  flex-direction: column;\n  gap: 4px;\n}\n\n.fieldLabel {\n  flex: 0 1 max-content;\n}\n\n.input {\n  width: 100%;\n}\n\n.numberInput {\n  flex: 0 1 60%;\n  max-width: 60%;\n}\n\n.select {\n  width: 100%;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/_components/SettingsPanel.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport clsx from 'clsx';\nimport { Popover } from '@base-ui/react/popover';\nimport { Field } from '@base-ui/react/field';\nimport { Switch } from './Switch';\nimport { Input } from './Input';\nimport { Select } from './Select';\nimport classes from './SettingsPanel.module.css';\n\nexport interface ExperimentsSettingsContext<Settings = Record<string, unknown>> {\n  settings: Settings;\n  setSettings: React.Dispatch<React.SetStateAction<Settings>>;\n}\n\nexport const ExperimentSettingsContext = React.createContext<\n  ExperimentsSettingsContext | undefined\n>(undefined);\n\nexport function ExperimentSettingsProvider<Settings extends {}>(\n  props: React.PropsWithChildren<{ metadata: SettingsMetadata<Settings> }>,\n) {\n  const [settingsState, setSettings] = React.useState<Settings>({} as Settings);\n  const { metadata, children } = props;\n  const isInitializedRef = React.useRef(false);\n\n  const settings = settingsState;\n\n  if (!isInitializedRef.current && metadata != null) {\n    Object.keys(metadata).forEach((key) => {\n      const fieldMetadata = metadata[key as keyof Settings];\n      if (fieldMetadata.default !== undefined) {\n        settings[key as keyof Settings] = fieldMetadata.default as any;\n      } else {\n        switch (fieldMetadata.type) {\n          case 'boolean':\n            (settings[key as keyof Settings] as any) = false;\n            break;\n          case 'number':\n            (settings[key as keyof Settings] as any) = 0;\n            break;\n          case 'string':\n            (settings[key as keyof Settings] as any) = '';\n            break;\n          default:\n            break;\n        }\n      }\n    });\n\n    isInitializedRef.current = true;\n    setSettings(settings);\n  }\n\n  const context = React.useMemo(\n    () => ({\n      settings,\n      setSettings,\n    }),\n    [settings],\n  );\n\n  return (\n    <ExperimentSettingsContext value={context as ExperimentsSettingsContext}>\n      {children}\n    </ExperimentSettingsContext>\n  );\n}\n\nexport function useExperimentSettings<Settings extends {}>() {\n  const context = React.useContext(ExperimentSettingsContext);\n  if (!context) {\n    throw new Error('useExperimentSettings must be used within an ExperimentSettingsProvider');\n  }\n\n  return context as ExperimentsSettingsContext<Settings>;\n}\n\nexport function SettingsPanel<Settings extends {}>(props: SettingsPanelProps<Settings>) {\n  const { metadata, renderAsPopup = true, className, ...otherProps } = props;\n  const { settings, setSettings } = useExperimentSettings<Settings>();\n\n  const createChangeHandler = React.useCallback(\n    (key: string) => (value: unknown) => {\n      setSettings((oldSettings) => ({\n        ...oldSettings,\n        [key]: value,\n      }));\n    },\n    [setSettings],\n  );\n\n  if (!metadata) {\n    return null;\n  }\n\n  const controls = (\n    <div {...otherProps} className={clsx(classes.settings, className)}>\n      <h2>Settings</h2>\n      {Object.keys(metadata).map((key) => {\n        const value = (settings as Record<string, unknown>)[key];\n        const fieldMetadata: FieldMetadata = metadata[key as keyof Settings];\n        switch (fieldMetadata.type) {\n          case 'boolean':\n            return renderSwitch(\n              key,\n              fieldMetadata.label,\n              value as boolean,\n              createChangeHandler(key),\n            );\n          case 'number':\n            return renderNumberInput(\n              key,\n              fieldMetadata.label,\n              value as number,\n              createChangeHandler(key),\n            );\n          case 'string':\n            if (fieldMetadata.options) {\n              return renderSelect(\n                key,\n                fieldMetadata.label,\n                value as string,\n                fieldMetadata.options,\n                createChangeHandler(key),\n              );\n            }\n            return renderTextInput(\n              key,\n              fieldMetadata.label,\n              value as string,\n              createChangeHandler(key),\n            );\n          default:\n            return null;\n        }\n      })}\n    </div>\n  );\n\n  if (renderAsPopup) {\n    return <SettingsPopup>{controls}</SettingsPopup>;\n  }\n\n  return controls;\n}\n\nexport interface FieldMetadata {\n  label: string;\n  type: 'boolean' | 'number' | 'string';\n  options?: string[];\n  default?: unknown;\n}\n\nexport type SettingsMetadata<Settings> = Record<keyof Settings, FieldMetadata>;\n\nexport interface SettingsPanelProps<Settings> extends React.HTMLAttributes<HTMLDivElement> {\n  metadata: SettingsMetadata<Settings>;\n  renderAsPopup?: boolean;\n}\n\nfunction SettingsPopup(props: React.PropsWithChildren<{}>) {\n  const [open, setOpen] = React.useState(false);\n  const handleOpenChange = React.useCallback(\n    (nextOpen: boolean, eventDetails: { reason: string }) => {\n      const reason = eventDetails.reason;\n      if (!nextOpen && (reason === 'outside-press' || reason === 'focus-out')) {\n        return;\n      }\n\n      setOpen(nextOpen);\n    },\n    [],\n  );\n\n  return (\n    <Popover.Root open={open} onOpenChange={handleOpenChange}>\n      <Popover.Trigger className={classes.trigger}>\n        <SettingsIcon className={classes.icon} />\n      </Popover.Trigger>\n\n      <Popover.Portal>\n        <Popover.Positioner align=\"end\" side=\"bottom\" sideOffset={8} positionMethod=\"fixed\">\n          <Popover.Popup className={classes.popup}>{props.children}</Popover.Popup>\n        </Popover.Positioner>\n      </Popover.Portal>\n    </Popover.Root>\n  );\n}\n\nfunction renderSwitch(\n  key: string,\n  label: string,\n  value: boolean,\n  onChange: (value: boolean) => void,\n) {\n  return (\n    <Switch\n      key={key}\n      checked={value}\n      onCheckedChange={onChange}\n      label={label}\n      className={classes.singleLineField}\n    />\n  );\n}\n\nfunction renderTextInput(\n  key: string,\n  label: string,\n  value: string,\n  onChange: (value: string) => void,\n) {\n  return (\n    <Field.Root key={key} className={classes.multiLineField}>\n      <Field.Label className={classes.fieldLabel}>{label}</Field.Label>\n      <Input\n        type=\"text\"\n        value={value}\n        onChange={(event) => onChange(event.currentTarget.value)}\n        className={classes.input}\n      />\n    </Field.Root>\n  );\n}\n\nfunction renderNumberInput(\n  key: string,\n  label: string,\n  value: number,\n  onChange: (value: number) => void,\n) {\n  return (\n    <Field.Root key={key} className={classes.singleLineField}>\n      <Field.Label className={classes.fieldLabel}>{label}</Field.Label>\n      <Input\n        type=\"number\"\n        value={value}\n        onChange={(event) => onChange(event.currentTarget.valueAsNumber)}\n        className={classes.numberInput}\n      />\n    </Field.Root>\n  );\n}\n\nfunction renderSelect(\n  key: string,\n  label: string,\n  value: string,\n  options: string[],\n  onChange: (value: string | null) => void,\n) {\n  return (\n    <Field.Root key={key} className={classes.multiLineField}>\n      <Field.Label>{label}</Field.Label>\n      <Select\n        value={value}\n        onChange={(newValue) => onChange(newValue)}\n        options={options}\n        className={classes.select}\n      />\n    </Field.Root>\n  );\n}\n\nfunction SettingsIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      enableBackground=\"new 0 0 24 24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      width=\"24\"\n      fill=\"currentcolor\"\n      {...props}\n    >\n      <g>\n        <path d=\"M0,0h24v24H0V0z\" fill=\"none\" />\n        <path d=\"M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z\" />\n      </g>\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/_components/ShowSidebar.module.css",
    "content": ".root {\n  width: 32px;\n  height: 32px;\n  border-radius: 16px;\n  background-color: canvas;\n  position: fixed;\n  top: 16px;\n  left: 16px;\n  display: flex;\n  flex-direction: column;\n  gap: 2px;\n  justify-content: center;\n  align-items: center;\n\n  &::before,\n  &::after {\n    content: '';\n    display: block;\n    width: 16px;\n    height: 2px;\n    background-color: var(--color-gray-500);\n    border-radius: 1px;\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n\n    &:hover {\n      box-shadow:\n        0 15px 20px -3px var(--color-gray-200),\n        0 6px 9px -4px var(--color-gray-200);\n    }\n\n    transition: box-shadow 200ms;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n\n    &:hover {\n      outline: 2px solid var(--color-gray-200);\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/_components/ShowSidebar.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { ExperimentRootContext } from './ExperimentRoot';\nimport classes from './ShowSidebar.module.css';\n\nexport function ShowSidebar() {\n  const rootContext = React.useContext(ExperimentRootContext);\n  if (!rootContext) {\n    return null;\n  }\n\n  return (\n    <button\n      type=\"button\"\n      onClick={() => rootContext.setSidebarVisible(true)}\n      className={classes.root}\n      title=\"Show sidebar\"\n      aria-label=\"Show sidebar\"\n    />\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/_components/Sidebar.module.css",
    "content": ".root {\n  border-right: 1px solid var(--color-gridline);\n  overflow: auto;\n\n  & h2 {\n    text-transform: uppercase;\n    font-weight: bold;\n    font-size: var(--text-xs);\n    color: var(--color-gray-500);\n  }\n}\n\n.panel:has(*) {\n  padding: 16px;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/_components/Sidebar.tsx",
    "content": "import * as React from 'react';\nimport clsx from 'clsx';\nimport { ExperimentsList } from './ExperimentsList';\nimport { EditPanel } from './EditPanel';\nimport classes from './Sidebar.module.css';\nimport { HideSidebar } from './HideSidebar';\nimport { SettingsMetadata, SettingsPanel } from './SettingsPanel';\n\nexport function Sidebar(props: SidebarProps) {\n  const { experimentPath, settingsMetadata, className, ...otherProps } = props;\n\n  return (\n    <div {...otherProps} className={clsx(classes.root, className)}>\n      {experimentPath && (\n        <React.Fragment>\n          {settingsMetadata && (\n            <SettingsPanel\n              className={classes.panel}\n              metadata={settingsMetadata}\n              renderAsPopup={false}\n            />\n          )}\n          <EditPanel className={classes.panel} experimentPath={experimentPath} />\n          <HideSidebar className={classes.panel} />\n        </React.Fragment>\n      )}\n      <ExperimentsList className={classes.panel} />\n    </div>\n  );\n}\n\ninterface SidebarProps extends React.HTMLAttributes<HTMLDivElement> {\n  experimentPath?: string;\n  settingsMetadata?: SettingsMetadata<unknown>;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/_components/Switch.module.css",
    "content": ".Switch {\n  --width: 1.75rem;\n  --height: 1rem;\n\n  position: relative;\n  display: flex;\n  appearance: none;\n  border: 0;\n  margin: 0;\n  padding: 1px;\n  width: var(--width);\n  height: var(--height);\n  border-radius: var(--height);\n  outline: 1px solid;\n  outline-offset: -1px;\n  background-color: transparent;\n  background-image: linear-gradient(to right, var(--color-gray-700) 35%, var(--color-gray-200) 65%);\n  background-size: 6.5rem 100%;\n  background-position-x: 100%;\n  background-repeat: no-repeat;\n  transition-property: background-position, box-shadow;\n  transition-timing-function: cubic-bezier(0.26, 0.75, 0.38, 0.45);\n  transition-duration: 125ms;\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &[data-checked] {\n    background-position-x: 0%;\n  }\n\n  &[data-checked]:active {\n    background-color: var(--color-gray-500);\n  }\n\n  @media (prefers-color-scheme: light) {\n    box-shadow: var(--color-gray-200) 0 1.5px 2px inset;\n    outline-color: var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    box-shadow: rgb(0 0 0 / 75%) 0 1.5px 2px inset;\n    outline-color: rgb(255 255 255 / 15%);\n    background-image: linear-gradient(\n      to right,\n      var(--color-gray-500) 35%,\n      var(--color-gray-200) 65%\n    );\n\n    &[data-checked] {\n      box-shadow: none;\n    }\n  }\n\n  &:focus-visible {\n    &::before {\n      content: '';\n      inset: 0;\n      position: absolute;\n      border-radius: inherit;\n      outline: 2px solid var(--color-blue);\n      outline-offset: 2px;\n    }\n  }\n}\n\n.Thumb {\n  aspect-ratio: 1 / 1;\n  height: 100%;\n  border-radius: 100%;\n  background-color: white;\n  transition: translate 150ms ease;\n\n  &[data-checked] {\n    translate: calc(var(--width) - var(--height)) 0;\n  }\n\n  @media (prefers-color-scheme: light) {\n    box-shadow:\n      0 0 1px 1px var(--color-gray-100),\n      0 1px 1px var(--color-gray-100),\n      1px 2px 4px -1px var(--color-gray-100);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    box-shadow:\n      0 0 1px 1px rgb(0 0 0 / 25%),\n      0 1px 1px rgb(0 0 0 / 25%),\n      1px 2px 4px -1px rgb(0 0 0 / 25%);\n  }\n}\n\n.Label {\n  width: 100%;\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 500;\n  color: var(--color-gray-900);\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/_components/Switch.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Field } from '@base-ui/react/field';\nimport { Switch as BaseSwitch } from '@base-ui/react/switch';\nimport classes from './Switch.module.css';\n\nexport function Switch(props: Switch.Props) {\n  const { label, checked, onCheckedChange, defaultChecked, ...otherProps } = props;\n\n  const component = (\n    <BaseSwitch.Root\n      className={classes.Switch}\n      checked={checked}\n      onCheckedChange={onCheckedChange}\n      defaultChecked={defaultChecked}\n    >\n      <BaseSwitch.Thumb className={classes.Thumb} />\n    </BaseSwitch.Root>\n  );\n\n  return (\n    <Field.Root {...otherProps}>\n      {label ? (\n        <Field.Label className={classes.Label}>\n          {label}\n          {component}\n        </Field.Label>\n      ) : (\n        component\n      )}\n    </Field.Root>\n  );\n}\n\nexport namespace Switch {\n  export interface Props extends React.HTMLAttributes<HTMLDivElement> {\n    label?: string;\n    checked?: boolean;\n    onCheckedChange: (checked: boolean) => void;\n    defaultChecked?: boolean;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/_components/Tooltip.module.css",
    "content": ".popup {\n  box-sizing: border-box;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  display: flex;\n  flex-direction: column;\n  padding: 0.25rem 0.5rem;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  &[data-instant] {\n    transition-duration: 0ms;\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.arrow {\n  display: flex;\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.arrowFill {\n  fill: canvas;\n}\n\n.arrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.arrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/_components/Tooltip.tsx",
    "content": "import * as React from 'react';\nimport { Tooltip as BaseTooltip } from '@base-ui/react/tooltip';\nimport classes from './Tooltip.module.css';\n\nexport function Tooltip(props: TooltipProps) {\n  const { text, children } = props;\n\n  return (\n    <BaseTooltip.Root>\n      <BaseTooltip.Trigger render={children} />\n      <BaseTooltip.Portal>\n        <BaseTooltip.Positioner sideOffset={10}>\n          <BaseTooltip.Popup className={classes.popup}>\n            <BaseTooltip.Arrow className={classes.arrow}>\n              <ArrowSvg />\n            </BaseTooltip.Arrow>\n            {text}\n          </BaseTooltip.Popup>\n        </BaseTooltip.Positioner>\n      </BaseTooltip.Portal>\n    </BaseTooltip.Root>\n  );\n}\n\nexport interface TooltipProps {\n  text: string;\n  children: React.ReactElement<Record<string, unknown>>;\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={classes.arrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={classes.arrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={classes.arrowInnerStroke}\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/accordion/animations.module.css",
    "content": ".Accordion {\n  box-sizing: border-box;\n  display: flex;\n  width: 24rem;\n  max-width: calc(100vw - 8rem);\n  flex-direction: column;\n  justify-content: center;\n  color: var(--color-gray-900);\n}\n\n.Item {\n  border-bottom: 1px solid var(--color-gray-200);\n}\n\n.Header {\n  margin: 0;\n}\n\n.Trigger {\n  box-sizing: border-box;\n  display: flex;\n  width: 100%;\n  gap: 1rem;\n  align-items: baseline;\n  justify-content: space-between;\n  padding: 0.5rem 0;\n  color: var(--color-gray-900);\n  font-family: inherit;\n  font-weight: 500;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  background: none;\n  border: none;\n  outline: none;\n  cursor: pointer;\n  text-align: left;\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n  }\n}\n\n.TriggerIcon {\n  box-sizing: border-box;\n  flex-shrink: 0;\n  width: 0.75rem;\n  height: 0.75rem;\n  margin-right: 0.5rem;\n  transition: transform 150ms ease-out;\n\n  [data-panel-open] > & {\n    transform: rotate(45deg) scale(1.1);\n  }\n}\n\n@keyframes slide-down {\n  from {\n    height: 0;\n  }\n\n  to {\n    height: var(--accordion-panel-height);\n  }\n}\n\n@keyframes slide-up {\n  from {\n    height: var(--accordion-panel-height);\n  }\n\n  to {\n    height: 0;\n  }\n}\n\n.Panel {\n  --duration: 200ms;\n\n  width: 100%;\n  box-sizing: border-box;\n  overflow: hidden;\n  color: var(--color-gray-600);\n  font-size: 1rem;\n  line-height: 1.5rem;\n\n  &[data-open] {\n    animation: slide-down var(--duration) ease-out;\n  }\n\n  &[data-closed] {\n    animation: slide-up var(--duration) ease-in;\n  }\n}\n\n.Content {\n  padding-bottom: 0.75rem;\n}\n\n.grid {\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n  grid-gap: 5rem;\n}\n\n.wrapper {\n  font-family: system-ui, sans-serif;\n  line-height: 1.4;\n  display: flex;\n  flex-flow: column nowrap;\n  align-items: stretch;\n  gap: 1rem;\n  align-self: flex-start;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/accordion/animations.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Accordion } from '@base-ui/react/accordion';\nimport styles from './animations.module.css';\nimport { SettingsMetadata, useExperimentSettings } from '../_components/SettingsPanel';\n\ninterface Settings extends Record<string, boolean> {}\n\nexport const settingsMetadata: SettingsMetadata<Settings> = {\n  multiple: {\n    type: 'boolean',\n    label: 'Allow multiple open',\n    default: true,\n  },\n};\n\n// the `value` prop is set manually on Accordion.Items to ensure animations are\n// cancelled when they are initially open\nfunction ExampleAccordion({ keepMounted, multiple }: { keepMounted: boolean; multiple: boolean }) {\n  return (\n    <Accordion.Root className={styles.Accordion} defaultValue={[0]} multiple={multiple}>\n      <Accordion.Item className={styles.Item} value={0}>\n        <Accordion.Header className={styles.Header}>\n          <Accordion.Trigger className={styles.Trigger}>\n            What is Base UI?\n            <PlusIcon className={styles.TriggerIcon} />\n          </Accordion.Trigger>\n        </Accordion.Header>\n        <Accordion.Panel className={styles.Panel} keepMounted={keepMounted}>\n          <div className={styles.Content}>\n            Base UI is a library of high-quality unstyled React components for design systems and\n            web apps.\n          </div>\n        </Accordion.Panel>\n      </Accordion.Item>\n\n      <Accordion.Item className={styles.Item} value={1}>\n        <Accordion.Header className={styles.Header}>\n          <Accordion.Trigger className={styles.Trigger}>\n            How do I get started?\n            <PlusIcon className={styles.TriggerIcon} />\n          </Accordion.Trigger>\n        </Accordion.Header>\n        <Accordion.Panel className={styles.Panel} keepMounted={keepMounted}>\n          <div className={styles.Content}>\n            Head to the “Quick start” guide in the docs. If you’ve used unstyled libraries before,\n            you’ll feel at home.\n          </div>\n        </Accordion.Panel>\n      </Accordion.Item>\n\n      <Accordion.Item className={styles.Item} value={2}>\n        <Accordion.Header className={styles.Header}>\n          <Accordion.Trigger className={styles.Trigger}>\n            Can I use it for my project?\n            <PlusIcon className={styles.TriggerIcon} />\n          </Accordion.Trigger>\n        </Accordion.Header>\n        <Accordion.Panel className={styles.Panel} keepMounted={keepMounted}>\n          <div className={styles.Content}>Of course! Base UI is free and open source.</div>\n        </Accordion.Panel>\n      </Accordion.Item>\n    </Accordion.Root>\n  );\n}\n\nexport default function CssAnimations() {\n  const { settings } = useExperimentSettings<Settings>();\n  const multiple = settings.multiple;\n  return (\n    <div className={styles.grid}>\n      <div className={styles.wrapper}>\n        <pre>keepMounted: true</pre>\n        <ExampleAccordion keepMounted multiple={multiple} />\n        <small>———</small>\n      </div>\n\n      <div className={styles.wrapper}>\n        <pre>keepMounted: false</pre>\n        <ExampleAccordion keepMounted={false} multiple={multiple} />\n        <small>———</small>\n      </div>\n    </div>\n  );\n}\n\nfunction PlusIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg viewBox=\"0 0 12 12\" fill=\"currentcolor\" {...props}>\n      <path d=\"M6.75 0H5.25V5.25H0V6.75L5.25 6.75V12H6.75V6.75L12 6.75V5.25H6.75V0Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/accordion/horizontal.module.css",
    "content": ".Wrapper {\n  --duration: 300ms;\n\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n  gap: 2rem;\n  width: 100%;\n}\n\n.Section {\n  width: 100%;\n}\n\n.Root {\n  font-family: system-ui, sans-serif;\n  border-radius: 0.3rem;\n  height: 30rem;\n  display: inline-flex;\n  flex-direction: column;\n\n  writing-mode: vertical-lr;\n\n  &:dir(rtl) {\n    writing-mode: vertical-rl;\n  }\n}\n\n.Item {\n  position: relative;\n  color: var(--color-foreground);\n  display: flex;\n  flex-direction: column;\n\n  &:has([data-value='one']) {\n    background-color: var(--color-gray-100);\n  }\n  &:has([data-value='two']) {\n    background-color: var(--color-gray-200);\n  }\n  &:has([data-value='three']) {\n    background-color: var(--color-gray-300);\n  }\n\n  &:not(:first-of-type) {\n    margin-inline-start: 1px;\n  }\n\n  &:first-of-type {\n    border-start-start-radius: 0.25rem;\n    border-start-end-radius: 0.25rem;\n  }\n\n  &:last-of-type {\n    border-end-start-radius: 0.25rem;\n    border-end-end-radius: 0.25rem;\n  }\n}\n\n.Header {\n  margin: 0;\n  width: 4rem;\n}\n\n.Trigger {\n  appearance: none;\n  background-color: transparent;\n  border: 0;\n  color: inherit;\n  cursor: pointer;\n  padding: 1rem;\n  position: relative;\n  height: 100%;\n  width: 100%;\n  display: flex;\n  flex-flow: column nowrap;\n  align-items: center;\n}\n\n.Trigger:focus-visible {\n  outline: 2px solid var(--color-blue);\n}\n\n.Label {\n  font-size: 1rem;\n  line-height: 1.5;\n  margin-bottom: 1rem;\n}\n\n.Trigger[data-panel-open] svg {\n  transform: rotate(180deg);\n}\n\n.Panel {\n  box-sizing: border-box;\n  overflow: hidden;\n\n  width: var(--accordion-panel-width);\n  transition: all var(--duration) ease-out;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    width: 0;\n    opacity: 0;\n  }\n}\n\n.Content {\n  padding: 1.5rem 1rem 1.5rem 2.75rem;\n  cursor: text;\n\n  & > p {\n    margin-block-end: 1rem;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/accordion/horizontal.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { DirectionProvider } from '@base-ui/react/direction-provider';\nimport { Accordion } from '@base-ui/react/accordion';\nimport styles from './horizontal.module.css';\n\nconst displayValueMap = {\n  one: '一',\n  two: '二',\n  three: '三',\n} as any;\n// for CSS reasons it's easier to use vertical text (i.e. not horizontal writing\n// mode like English) in combination with animated width using --panel-width\nexport default function App() {\n  const [val, setVal] = React.useState(['one']);\n  return (\n    <div className={styles.Wrapper}>\n      <div className={styles.Section}>\n        <h2>Horizontal LTR</h2>\n        <Accordion.Root\n          className={styles.Root}\n          aria-label=\"Uncontrolled Horizontal Accordion\"\n          multiple={false}\n        >\n          {['one', 'two', 'three'].map((value) => (\n            <Accordion.Item className={styles.Item} key={value}>\n              <Accordion.Header className={styles.Header}>\n                <Accordion.Trigger className={styles.Trigger} data-value={value}>\n                  <span className={styles.Label}>{displayValueMap[value]}</span>\n                </Accordion.Trigger>\n              </Accordion.Header>\n              <Accordion.Panel className={styles.Panel}>\n                <div className={styles.Content}>\n                  <p>\n                    老婆の話が完ると、下人は嘲（あざけ）るような声で念を押した。そうして、一足前\n                    へ出ると、不意に、右の手を面皰から離して、老婆の襟上（えりがみ）をつかみながら、\n                    こう云った。\n                  </p>\n                  <p>\n                    「では、己が引剥（ひはぎ）をしようと恨むまいな。己もそうしなければ、饑死をす\n                    る体なのだ。」\n                  </p>\n                  <p>\n                    下人は、すばやく、老婆の着物を剥ぎとった。それから、足にしがみつこうとする老\n                    婆を、手荒く屍骸の上へ蹴倒した。梯子の口までは、僅に五歩を数えるばかりである。\n                    下人は、剥ぎとった桧肌色の着物をわきにかかえて、またたく間に急な梯子を夜の底へ\n                    かけ下りた。\n                  </p>\n                </div>\n              </Accordion.Panel>\n            </Accordion.Item>\n          ))}\n        </Accordion.Root>\n      </div>\n\n      <div className={styles.Section} dir=\"rtl\">\n        <span>\n          <h2>Horizontal RTL</h2>\n          <p>one section must remain open</p>\n        </span>\n        <DirectionProvider direction=\"rtl\">\n          <Accordion.Root\n            className={styles.Root}\n            aria-label=\"Controlled Horizontal RTL Accordion\"\n            multiple={false}\n            orientation=\"horizontal\"\n            value={val}\n            onValueChange={(newValue: Accordion.Root.Props['value']) => {\n              if (Array.isArray(newValue) && newValue.length > 0) {\n                setVal(newValue);\n              }\n            }}\n          >\n            {['one', 'two', 'three'].map((value) => (\n              <Accordion.Item className={styles.Item} key={value} value={value}>\n                <Accordion.Header className={styles.Header}>\n                  <Accordion.Trigger className={styles.Trigger} data-value={value}>\n                    <span className={styles.Label}>{displayValueMap[value]}</span>\n                  </Accordion.Trigger>\n                </Accordion.Header>\n                <Accordion.Panel className={styles.Panel} keepMounted={false}>\n                  <div className={styles.Content}>\n                    <p>\n                      老婆の話が完ると、下人は嘲（あざけ）るような声で念を押した。そうして、一足前\n                      へ出ると、不意に、右の手を面皰から離して、老婆の襟上（えりがみ）をつかみながら、\n                      こう云った。\n                    </p>\n                    <p>\n                      「では、己が引剥（ひはぎ）をしようと恨むまいな。己もそうしなければ、饑死をす\n                      る体なのだ。」\n                    </p>\n                    <p>\n                      下人は、すばやく、老婆の着物を剥ぎとった。それから、足にしがみつこうとする老\n                      婆を、手荒く屍骸の上へ蹴倒した。梯子の口までは、僅に五歩を数えるばかりである。\n                      下人は、剥ぎとった桧肌色の着物をわきにかかえて、またたく間に急な梯子を夜の底へ\n                      かけ下りた。\n                    </p>\n                  </div>\n                </Accordion.Panel>\n              </Accordion.Item>\n            ))}\n          </Accordion.Root>\n        </DirectionProvider>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/accordion/transitions.module.css",
    "content": ".Accordion {\n  --duration: 200ms;\n\n  box-sizing: border-box;\n  display: flex;\n  width: 24rem;\n  max-width: calc(100vw - 8rem);\n  flex-direction: column;\n  justify-content: center;\n  color: var(--color-gray-900);\n}\n\n.Item {\n  border-bottom: 1px solid var(--color-gray-200);\n}\n\n.Header {\n  margin: 0;\n}\n\n.Trigger {\n  box-sizing: border-box;\n  display: flex;\n  width: 100%;\n  gap: 1rem;\n  align-items: baseline;\n  justify-content: space-between;\n  padding: 0.5rem 0;\n  color: var(--color-gray-900);\n  font-family: inherit;\n  font-weight: 500;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  background: none;\n  border: none;\n  outline: none;\n  cursor: pointer;\n  text-align: left;\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n  }\n}\n\n.TriggerIcon {\n  box-sizing: border-box;\n  flex-shrink: 0;\n  width: 0.75rem;\n  height: 0.75rem;\n  margin-right: 0.5rem;\n  transition: transform 150ms ease-out;\n\n  [data-panel-open] > & {\n    transform: rotate(45deg) scale(1.1);\n  }\n}\n\n.Panel {\n  box-sizing: border-box;\n  overflow: hidden;\n  color: var(--color-gray-600);\n  font-size: 1rem;\n  line-height: 1.5rem;\n\n  height: var(--accordion-panel-height);\n\n  transition: all var(--duration) ease-out;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    height: 0;\n    opacity: 0;\n  }\n}\n\n.Content {\n  padding-bottom: 0.75rem;\n}\n\n.grid {\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n  grid-gap: 5rem;\n}\n\n.wrapper {\n  font-family: system-ui, sans-serif;\n  line-height: 1.4;\n  display: flex;\n  flex-flow: column nowrap;\n  align-items: stretch;\n  gap: 1rem;\n  align-self: flex-start;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/accordion/transitions.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Accordion } from '@base-ui/react/accordion';\nimport styles from './transitions.module.css';\nimport { SettingsMetadata, useExperimentSettings } from '../_components/SettingsPanel';\n\ninterface Settings extends Record<string, boolean> {}\n\nexport const settingsMetadata: SettingsMetadata<Settings> = {\n  multiple: {\n    type: 'boolean',\n    label: 'Allow multiple open',\n    default: true,\n  },\n};\n\n// the `value` prop is set manually on Accordion.Items to ensure transitions are\n// cancelled when they are initially open\nfunction ExampleAccordion({ keepMounted, multiple }: { keepMounted: boolean; multiple: boolean }) {\n  return (\n    <Accordion.Root className={styles.Accordion} defaultValue={[0]} multiple={multiple}>\n      <Accordion.Item className={styles.Item} value={0}>\n        <Accordion.Header className={styles.Header}>\n          <Accordion.Trigger className={styles.Trigger}>\n            What is Base UI?\n            <PlusIcon className={styles.TriggerIcon} />\n          </Accordion.Trigger>\n        </Accordion.Header>\n        <Accordion.Panel className={styles.Panel} keepMounted={keepMounted}>\n          <div className={styles.Content}>\n            Base UI is a library of high-quality unstyled React components for design systems and\n            web apps.\n          </div>\n        </Accordion.Panel>\n      </Accordion.Item>\n\n      <Accordion.Item className={styles.Item} value={1}>\n        <Accordion.Header className={styles.Header}>\n          <Accordion.Trigger className={styles.Trigger}>\n            How do I get started?\n            <PlusIcon className={styles.TriggerIcon} />\n          </Accordion.Trigger>\n        </Accordion.Header>\n        <Accordion.Panel className={styles.Panel} keepMounted={keepMounted}>\n          <div className={styles.Content}>\n            Head to the “Quick start” guide in the docs. If you’ve used unstyled libraries before,\n            you’ll feel at home.\n          </div>\n        </Accordion.Panel>\n      </Accordion.Item>\n\n      <Accordion.Item className={styles.Item} value={2}>\n        <Accordion.Header className={styles.Header}>\n          <Accordion.Trigger className={styles.Trigger}>\n            Can I use it for my project?\n            <PlusIcon className={styles.TriggerIcon} />\n          </Accordion.Trigger>\n        </Accordion.Header>\n        <Accordion.Panel className={styles.Panel} keepMounted={keepMounted}>\n          <div className={styles.Content}>Of course! Base UI is free and open source.</div>\n        </Accordion.Panel>\n      </Accordion.Item>\n    </Accordion.Root>\n  );\n}\n\nexport default function CssTransitions() {\n  const { settings } = useExperimentSettings<Settings>();\n  const multiple = settings.multiple;\n  return (\n    <div className={styles.grid}>\n      <div className={styles.wrapper}>\n        <pre>keepMounted: true</pre>\n        <ExampleAccordion keepMounted multiple={multiple} />\n        <small>———</small>\n      </div>\n\n      <div className={styles.wrapper}>\n        <pre>keepMounted: false</pre>\n        <ExampleAccordion keepMounted={false} multiple={multiple} />\n        <small>———</small>\n      </div>\n    </div>\n  );\n}\n\nfunction PlusIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg viewBox=\"0 0 12 12\" fill=\"currentcolor\" {...props}>\n      <path d=\"M6.75 0H5.25V5.25H0V6.75L5.25 6.75V12H6.75V6.75L12 6.75V5.25H6.75V0Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/anchor-positioning.module.css",
    "content": ".controls {\n  fieldset {\n    display: flex;\n    gap: 5px;\n  }\n\n  label {\n    display: flex;\n    align-items: center;\n    gap: 5px;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/anchor-positioning.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport * as ReactDOM from 'react-dom';\n\nimport {\n  useAnchorPositioning,\n  type UseAnchorPositioningParameters,\n} from '../../../../../packages/react/src/utils/useAnchorPositioning';\nimport { FloatingRootStore } from '../../../../../packages/react/src/floating-ui-react/components/FloatingRootStore';\nimport { PopupTriggerMap } from '../../../../../packages/react/src/utils/popups';\nimport styles from './anchor-positioning.module.css';\n\nconst oppositeSideMap = {\n  top: 'bottom',\n  bottom: 'top',\n  left: 'right',\n  right: 'left',\n  'inline-start': 'right',\n  'inline-end': 'left',\n} as const;\n\ntype Size = 'xs' | 's' | 'm' | 'l' | 'xl';\n\nexport default function AnchorPositioning() {\n  const [anchorEl, setAnchorEl] = React.useState<HTMLDivElement | null>(null);\n  const [popupSize, setPopupSize] = React.useState<Size>('xs');\n  const [anchorSize, setAnchorSize] = React.useState<Size>('m');\n  const [side, setSide] = React.useState<'top' | 'bottom' | 'left' | 'right'>('top');\n  const [align, setAlign] = React.useState<'start' | 'center' | 'end'>('center');\n  const [sideOffset, setSideOffset] = React.useState(0);\n  const [alignOffset, setAlignOffset] = React.useState(0);\n  const [collisionPadding, setCollisionPadding] = React.useState(5);\n  const [arrowPadding, setArrowPadding] = React.useState(5);\n  const [arrow, setArrow] = React.useState(true);\n  const [hideArrowWhenUncentered, setHideArrowWhenUncentered] = React.useState(false);\n  const [sticky, setSticky] = React.useState(false);\n  const [constrainSize, setConstrainSize] = React.useState(false);\n  const [visible, setVisible] = React.useState(false);\n  const [disableAnchorTracking, setDisableAnchorTracking] = React.useState(false);\n  const [collisionAvoidance, setCollisionAvoidance] = React.useState<\n    UseAnchorPositioningParameters['collisionAvoidance']\n  >({\n    side: 'flip',\n    align: 'flip',\n    fallbackAxisSide: 'end',\n  });\n\n  const floatingRootContext = new FloatingRootStore({\n    open: true,\n    referenceElement: anchorEl,\n    floatingElement: null,\n    triggerElements: new PopupTriggerMap(),\n    floatingId: '',\n    nested: false,\n    noEmit: false,\n    onOpenChange: undefined,\n  });\n\n  const {\n    refs,\n    positionerStyles,\n    arrowStyles,\n    arrowRef,\n    side: renderedSide,\n    arrowUncentered,\n  } = useAnchorPositioning({\n    floatingRootContext,\n    side,\n    align,\n    sideOffset,\n    alignOffset,\n    collisionPadding,\n    sticky,\n    arrowPadding,\n    disableAnchorTracking,\n    collisionAvoidance,\n    mounted: true,\n    keepMounted: true,\n  });\n\n  const handleInitialScroll = React.useCallback((node: HTMLDivElement | null) => {\n    if (node) {\n      node.scrollLeft = 285;\n      node.scrollTop = 625;\n      setVisible(true);\n    }\n  }, []);\n\n  const anchorLength = {\n    xs: 5,\n    s: 25,\n    m: 50,\n    l: 100,\n    xl: 250,\n  }[anchorSize];\n\n  const popup = (\n    <div\n      ref={refs.setFloating}\n      style={{\n        visibility: visible ? 'visible' : 'hidden',\n        ...positionerStyles,\n      }}\n    >\n      <div\n        style={{\n          background: 'white',\n          boxSizing: 'border-box',\n          padding: 10,\n          ...(constrainSize && {\n            maxWidth: 'var(--available-width)',\n            maxHeight: 'var(--available-height)',\n            overflow: 'auto',\n          }),\n        }}\n      >\n        {`Content `.repeat(\n          {\n            xs: 1,\n            s: 3,\n            m: 10,\n            l: 50,\n            xl: 200,\n          }[popupSize],\n        )}\n      </div>\n      {arrow && (\n        <div\n          ref={arrowRef as React.RefObject<HTMLDivElement>}\n          style={{\n            ...arrowStyles,\n            background: 'rgba(0, 0, 255, 0.5)',\n            width: 20,\n            height: 20,\n            [oppositeSideMap[renderedSide]]: -10,\n            ...(arrowUncentered && hideArrowWhenUncentered && { visibility: 'hidden' }),\n          }}\n        />\n      )}\n    </div>\n  );\n\n  const popupNode = !disableAnchorTracking ? popup : ReactDOM.createPortal(popup, document.body);\n\n  return (\n    <div style={{ fontFamily: 'sans-serif', margin: 50 }}>\n      <h1>Anchor Positioning Playground</h1>\n      <div style={{ display: 'flex', gap: 20 }} className={styles.controls}>\n        <div\n          ref={handleInitialScroll}\n          style={{\n            overflow: 'auto',\n            background: 'black',\n            position: 'relative',\n            width: 375,\n            height: 800,\n          }}\n        >\n          <div style={{ width: 1000 + anchorLength / 2, height: 1000 }} />\n          <div\n            ref={setAnchorEl}\n            style={{\n              display: 'grid',\n              placeItems: 'center',\n              width: anchorLength,\n              height: anchorLength,\n              background: 'tomato',\n              position: 'relative',\n              left: 450,\n              fontFamily: 'sans-serif',\n              color: 'white',\n              fontSize: 12,\n              fontWeight: 'bold',\n            }}\n          >\n            {anchorSize !== 'xs' ? 'A' : null}\n          </div>\n          {popupNode}\n          <div style={{ width: 1000 + anchorLength / 2, height: 1000 }} />\n        </div>\n        <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>\n          <fieldset>\n            <legend>Popup Size</legend>\n            {(['xs', 's', 'm', 'l', 'xl'] as const).map((s) => (\n              <label key={s}>\n                <input\n                  name=\"popup-size\"\n                  type=\"radio\"\n                  checked={s === popupSize}\n                  onChange={() => setPopupSize(s)}\n                />\n                {s}\n              </label>\n            ))}\n          </fieldset>\n\n          <fieldset>\n            <legend>Anchor Size</legend>\n            {(['xs', 's', 'm', 'l', 'xl'] as const).map((s) => (\n              <label key={s}>\n                <input\n                  name=\"anchor-size\"\n                  type=\"radio\"\n                  checked={s === anchorSize}\n                  onChange={() => setAnchorSize(s)}\n                />\n                {s}\n              </label>\n            ))}\n          </fieldset>\n\n          <fieldset>\n            <legend>Side</legend>\n            {(['top', 'bottom', 'left', 'right'] as const).map((s) => (\n              <label key={s}>\n                <input name=\"side\" type=\"radio\" checked={s === side} onChange={() => setSide(s)} />\n                {s}\n              </label>\n            ))}\n          </fieldset>\n\n          <fieldset>\n            <legend>Align</legend>\n            {(['start', 'center', 'end'] as const).map((a) => (\n              <label key={a}>\n                <input\n                  name=\"align\"\n                  type=\"radio\"\n                  checked={a === align}\n                  onChange={() => setAlign(a)}\n                />\n                {a}\n              </label>\n            ))}\n          </fieldset>\n\n          <fieldset>\n            <legend>Side Offset</legend>\n            <input\n              type=\"range\"\n              min={0}\n              max={50}\n              value={sideOffset}\n              onChange={(event) => setSideOffset(Number(event.target.value))}\n            />\n            {sideOffset}\n          </fieldset>\n\n          <fieldset>\n            <legend>Align Offset</legend>\n            <input\n              type=\"range\"\n              min={0}\n              max={50}\n              value={alignOffset}\n              onChange={(event) => setAlignOffset(Number(event.target.value))}\n            />\n            {alignOffset}\n          </fieldset>\n\n          <fieldset>\n            <legend>Collision Padding</legend>\n            <input\n              type=\"range\"\n              min={0}\n              max={50}\n              value={collisionPadding}\n              onChange={(event) => setCollisionPadding(Number(event.target.value))}\n            />\n            {collisionPadding}\n          </fieldset>\n\n          <fieldset>\n            <legend>Arrow Padding</legend>\n            <input\n              type=\"range\"\n              min={0}\n              max={20}\n              value={arrowPadding}\n              onChange={(event) => setArrowPadding(Number(event.target.value))}\n            />\n            {arrowPadding}\n          </fieldset>\n\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={constrainSize}\n              onChange={() => setConstrainSize((prev) => !prev)}\n            />\n            Constrain size\n          </label>\n\n          <label>\n            <input type=\"checkbox\" checked={arrow} onChange={() => setArrow((prev) => !prev)} />\n            Arrow\n          </label>\n\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={hideArrowWhenUncentered}\n              onChange={() => setHideArrowWhenUncentered((prev) => !prev)}\n            />\n            Hide arrow when uncentered\n          </label>\n\n          <label>\n            <input type=\"checkbox\" checked={sticky} onChange={() => setSticky((prev) => !prev)} />\n            Sticky\n          </label>\n\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={disableAnchorTracking}\n              onChange={() => setDisableAnchorTracking((prev) => !prev)}\n            />\n            Disable tracking anchor\n          </label>\n\n          <fieldset>\n            <legend>Collision Avoidance Side</legend>\n            {(['flip', 'shift', 'none'] as const).map((mode) => (\n              <label key={mode}>\n                <input\n                  name=\"collision-side\"\n                  type=\"radio\"\n                  checked={collisionAvoidance.side === mode}\n                  onChange={() => {\n                    if (mode === 'shift') {\n                      setCollisionAvoidance((prev) => ({\n                        ...prev,\n                        side: mode,\n                        align: mode,\n                      }));\n                    } else {\n                      setCollisionAvoidance((prev) => ({\n                        ...prev,\n                        side: mode,\n                      }));\n                    }\n                  }}\n                />\n                {mode}\n              </label>\n            ))}\n          </fieldset>\n\n          <fieldset>\n            <legend>Collision Avoidance Align</legend>\n            {(['flip', 'shift', 'none'] as const).map((mode) => (\n              <label key={mode}>\n                <input\n                  name=\"collision-align\"\n                  type=\"radio\"\n                  checked={collisionAvoidance.align === mode}\n                  onChange={() => {\n                    if (mode === 'shift' || mode === 'none') {\n                      setCollisionAvoidance((prev) => ({\n                        ...prev,\n                        align: mode,\n                      }));\n                    } else {\n                      setCollisionAvoidance((prev) => ({\n                        ...prev,\n                        side: mode,\n                        align: mode,\n                      }));\n                    }\n                  }}\n                />\n                {mode}\n              </label>\n            ))}\n          </fieldset>\n\n          <fieldset>\n            <legend>Fallback Axis Side</legend>\n            {(['start', 'end', 'none'] as const).map((mode) => (\n              <label key={mode}>\n                <input\n                  name=\"collision-fallback-axis-side\"\n                  type=\"radio\"\n                  checked={collisionAvoidance.fallbackAxisSide === mode}\n                  onChange={() =>\n                    setCollisionAvoidance((prev) => ({\n                      ...prev,\n                      fallbackAxisSide: mode,\n                    }))\n                  }\n                />\n                {mode}\n              </label>\n            ))}\n          </fieldset>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/anchor-side-animations.module.css",
    "content": "@keyframes translate-down {\n  to {\n    opacity: 0;\n    transform: translateY(20px);\n  }\n}\n\n@keyframes translate-up {\n  from {\n    opacity: 0;\n    transform: translateY(20px);\n  }\n}\n\n.Popup {\n  background-color: black;\n  width: 200px;\n  height: 200px;\n\n  &[data-type='transition'] {\n    transition:\n      opacity 0.5s,\n      transform 0.5s;\n\n    &[data-starting-style],\n    &[data-ending-style] {\n      opacity: 0;\n\n      &[data-side='bottom'] {\n        transform: translateY(20px);\n      }\n    }\n  }\n\n  &[data-type='animation'] {\n    &[data-side='bottom'] {\n      animation: translate-up 0.5s;\n    }\n\n    &[data-ending-style] {\n      &[data-side='bottom'] {\n        animation: translate-down 0.5s;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/anchor-side-animations.tsx",
    "content": "import { Popover } from '@base-ui/react/popover';\nimport classes from './anchor-side-animations.module.css';\n\nexport default function AnchorSideAnimations() {\n  return (\n    <div style={{ maxWidth: 500 }}>\n      <p>\n        The animation should always play translating <strong>toward</strong> the trigger on the\n        first open. Its side is `top` by default, but flips to the bottom as the result of a\n        collision. This demo determines if it successfully waits for the side to be calculated\n        before playing the animation.\n      </p>\n      <Popover.Root>\n        <Popover.Trigger>transition</Popover.Trigger>\n        <Popover.Portal>\n          <Popover.Positioner side=\"top\">\n            <Popover.Popup className={classes.Popup} data-type=\"transition\" />\n          </Popover.Positioner>\n        </Popover.Portal>\n      </Popover.Root>\n\n      <Popover.Root>\n        <Popover.Trigger>animation</Popover.Trigger>\n        <Popover.Portal>\n          <Popover.Positioner side=\"top\">\n            <Popover.Popup className={classes.Popup} data-type=\"animation\" />\n          </Popover.Positioner>\n        </Popover.Portal>\n      </Popover.Root>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/calendar/calendar-basic.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { format } from 'date-fns/format';\nimport { Calendar } from '@base-ui/react/calendar';\nimport { ChevronLeft, ChevronRight } from 'lucide-react';\nimport styles from './calendar.module.css';\n\nexport default function CalendarBasic() {\n  return (\n    <Calendar.Root className={styles.Root}>\n      {({ visibleDate }) => (\n        <React.Fragment>\n          <header className={styles.Header}>\n            <Calendar.DecrementMonth className={styles.DecrementMonth}>\n              <ChevronLeft />\n            </Calendar.DecrementMonth>\n            <span className={styles.HeaderLabel}>{format(visibleDate, 'MMMM yyyy')}</span>\n            <Calendar.IncrementMonth className={styles.IncrementMonth}>\n              <ChevronRight />\n            </Calendar.IncrementMonth>\n          </header>\n          <Calendar.DayGrid className={styles.DayGrid}>\n            <Calendar.DayGridHeader className={styles.DayGridHeader}>\n              <Calendar.DayGridHeaderRow className={styles.DayGridHeaderRow}>\n                {(day) => (\n                  <Calendar.DayGridHeaderCell\n                    value={day}\n                    key={day.getTime()}\n                    className={styles.DayGridHeaderCell}\n                  />\n                )}\n              </Calendar.DayGridHeaderRow>\n            </Calendar.DayGridHeader>\n            <Calendar.DayGridBody className={styles.DayGridBody}>\n              {(week) => (\n                <Calendar.DayGridRow\n                  value={week}\n                  key={week.getTime()}\n                  className={styles.DayGridRow}\n                >\n                  {(day) => (\n                    <Calendar.DayGridCell\n                      value={day}\n                      key={day.getTime()}\n                      className={styles.DayGridCell}\n                    >\n                      <Calendar.DayButton className={styles.DayButton} />\n                    </Calendar.DayGridCell>\n                  )}\n                </Calendar.DayGridRow>\n              )}\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </React.Fragment>\n      )}\n    </Calendar.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/calendar/calendar-timezone.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { format } from 'date-fns/format';\nimport { TZDate } from '@date-fns/tz';\nimport { Calendar } from '@base-ui/react/calendar';\nimport { ChevronLeft, ChevronRight } from 'lucide-react';\nimport styles from './calendar.module.css';\n\nexport default function CalendarWithTimezone() {\n  const [value, setValue] = React.useState<Date | null>(\n    new TZDate(2025, 3, 17, 4, 45, 0, 0, 'Europe/Paris'),\n  );\n  return (\n    <div className={styles.Wrapper}>\n      <Calendar.Root\n        className={styles.Root}\n        timezone=\"America/New_York\"\n        value={value}\n        onValueChange={setValue}\n      >\n        {({ visibleDate }) => (\n          <React.Fragment>\n            <header className={styles.Header}>\n              <Calendar.DecrementMonth className={styles.DecrementMonth}>\n                <ChevronLeft />\n              </Calendar.DecrementMonth>\n              <span className={styles.HeaderLabel}>{format(visibleDate, 'MMMM yyyy')}</span>\n              <Calendar.IncrementMonth className={styles.IncrementMonth}>\n                <ChevronRight />\n              </Calendar.IncrementMonth>\n            </header>\n            <Calendar.DayGrid className={styles.DayGrid}>\n              <Calendar.DayGridHeader className={styles.DayGridHeader}>\n                <Calendar.DayGridHeaderRow className={styles.DayGridHeaderRow}>\n                  {(day) => (\n                    <Calendar.DayGridHeaderCell\n                      value={day}\n                      key={day.getTime()}\n                      className={styles.DayGridHeaderCell}\n                    />\n                  )}\n                </Calendar.DayGridHeaderRow>\n              </Calendar.DayGridHeader>\n              <Calendar.DayGridBody className={styles.DayGridBody}>\n                {(week) => (\n                  <Calendar.DayGridRow\n                    value={week}\n                    key={week.getTime()}\n                    className={styles.DayGridRow}\n                  >\n                    {(day) => (\n                      <Calendar.DayGridCell\n                        value={day}\n                        key={day.getTime()}\n                        className={styles.DayGridCell}\n                      >\n                        <Calendar.DayButton className={styles.DayButton} />\n                      </Calendar.DayGridCell>\n                    )}\n                  </Calendar.DayGridRow>\n                )}\n              </Calendar.DayGridBody>\n            </Calendar.DayGrid>\n          </React.Fragment>\n        )}\n      </Calendar.Root>\n      {value && <p className={styles.Text}>Stored date: {value.toString()}</p>}\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/calendar/calendar-two-months.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { format } from 'date-fns/format';\nimport { addMonths } from 'date-fns/addMonths';\nimport { Calendar } from '@base-ui/react/calendar';\nimport { Separator } from '@base-ui/react/separator';\nimport { ChevronLeft, ChevronRight } from 'lucide-react';\nimport styles from './calendar.module.css';\n\nfunction Header() {\n  const { visibleDate } = Calendar.useContext();\n\n  return (\n    <header className={styles.Header}>\n      <div className={styles.HeaderPanel}>\n        <Calendar.DecrementMonth className={styles.DecrementMonth}>\n          <ChevronLeft />\n        </Calendar.DecrementMonth>\n        <span className={styles.HeaderLabel}>{format(visibleDate, 'MMMM yyyy')}</span>\n        <span />\n      </div>\n      <div className={styles.HeaderPanel}>\n        <span />\n        <span className={styles.HeaderLabel}>{format(addMonths(visibleDate, 1), 'MMMM yyyy')}</span>\n        <Calendar.IncrementMonth className={styles.IncrementMonth}>\n          <ChevronRight />\n        </Calendar.IncrementMonth>\n      </div>\n    </header>\n  );\n}\n\nfunction DayGrid(props: { offset: 0 | 1 }) {\n  const { offset } = props;\n\n  return (\n    <Calendar.DayGrid className={styles.DayGrid}>\n      <Calendar.DayGridHeader className={styles.DayGridHeader}>\n        <Calendar.DayGridHeaderRow className={styles.DayGridHeaderRow}>\n          {(day) => (\n            <Calendar.DayGridHeaderCell\n              value={day}\n              key={day.getTime()}\n              className={styles.DayGridHeaderCell}\n            />\n          )}\n        </Calendar.DayGridHeaderRow>\n      </Calendar.DayGridHeader>\n      <Calendar.DayGridBody className={styles.DayGridBody} offset={offset}>\n        {(week) => (\n          <Calendar.DayGridRow value={week} key={week.getTime()} className={styles.DayGridRow}>\n            {(day) => (\n              <Calendar.DayGridCell value={day} key={day.getTime()} className={styles.DayGridCell}>\n                <Calendar.DayButton className={styles.DayButton} />\n              </Calendar.DayGridCell>\n            )}\n          </Calendar.DayGridRow>\n        )}\n      </Calendar.DayGridBody>\n    </Calendar.DayGrid>\n  );\n}\n\nexport default function CalendarTwoMonths() {\n  return (\n    <Calendar.Root monthPageSize={2} className={styles.Root}>\n      <Header />\n      <div className={styles.RootWithTwoPanelsContent}>\n        <DayGrid offset={0} />\n        <Separator className={styles.DayGridSeparator} />\n        <DayGrid offset={1} />\n      </div>\n    </Calendar.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/calendar/calendar-unavailable-dates.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { format } from 'date-fns/format';\nimport { addDays } from 'date-fns/addDays';\nimport { Calendar } from '@base-ui/react/calendar';\nimport { ChevronLeft, ChevronRight } from 'lucide-react';\nimport styles from './calendar.module.css';\n\nfunction isDateUnavailable(date: Date) {\n  return date.getDay() === 6 || date.getDay() === 0; // Unavailable on weekends\n}\n\nexport default function CalendarUnavailableDates() {\n  const minDate = React.useMemo(() => addDays(new Date(), -8), []);\n\n  return (\n    <Calendar.Root className={styles.Root} minDate={minDate} isDateUnavailable={isDateUnavailable}>\n      {({ visibleDate }) => (\n        <React.Fragment>\n          <header className={styles.Header}>\n            <Calendar.DecrementMonth className={styles.DecrementMonth}>\n              <ChevronLeft />\n            </Calendar.DecrementMonth>\n            <span className={styles.HeaderLabel}>{format(visibleDate, 'MMMM yyyy')}</span>\n            <Calendar.IncrementMonth className={styles.IncrementMonth}>\n              <ChevronRight />\n            </Calendar.IncrementMonth>\n          </header>\n          <Calendar.DayGrid className={styles.DayGrid}>\n            <Calendar.DayGridHeader className={styles.DayGridHeader}>\n              <Calendar.DayGridHeaderRow className={styles.DayGridHeaderRow}>\n                {(day) => (\n                  <Calendar.DayGridHeaderCell\n                    value={day}\n                    key={day.getTime()}\n                    className={styles.DayGridHeaderCell}\n                  />\n                )}\n              </Calendar.DayGridHeaderRow>\n            </Calendar.DayGridHeader>\n            <Calendar.DayGridBody className={styles.DayGridBody}>\n              {(week) => (\n                <Calendar.DayGridRow\n                  value={week}\n                  key={week.getTime()}\n                  className={styles.DayGridRow}\n                >\n                  {(day) => (\n                    <Calendar.DayGridCell\n                      value={day}\n                      key={day.getTime()}\n                      className={styles.DayGridCell}\n                    >\n                      <Calendar.DayButton className={styles.DayButton} />\n                    </Calendar.DayGridCell>\n                  )}\n                </Calendar.DayGridRow>\n              )}\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </React.Fragment>\n      )}\n    </Calendar.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/calendar/calendar-validation.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { format } from 'date-fns/format';\nimport { addDays } from 'date-fns/addDays';\nimport { Calendar } from '@base-ui/react/calendar';\nimport { ChevronLeft, ChevronRight } from 'lucide-react';\nimport styles from './calendar.module.css';\n\nconst today = new Date();\n\nexport default function CalendarValidation() {\n  return (\n    <Calendar.Root\n      className={styles.Root}\n      minDate={addDays(today, -3)}\n      maxDate={addDays(today, 15)}\n    >\n      {({ visibleDate }) => (\n        <React.Fragment>\n          <header className={styles.Header}>\n            <Calendar.DecrementMonth className={styles.DecrementMonth}>\n              <ChevronLeft />\n            </Calendar.DecrementMonth>\n            <span className={styles.HeaderLabel}>{format(visibleDate, 'MMMM yyyy')}</span>\n            <Calendar.IncrementMonth className={styles.IncrementMonth}>\n              <ChevronRight />\n            </Calendar.IncrementMonth>\n          </header>\n          <Calendar.DayGrid className={styles.DayGrid}>\n            <Calendar.DayGridHeader className={styles.DayGridHeader}>\n              <Calendar.DayGridHeaderRow className={styles.DayGridHeaderRow}>\n                {(day) => (\n                  <Calendar.DayGridHeaderCell\n                    value={day}\n                    key={day.getTime()}\n                    className={styles.DayGridHeaderCell}\n                  />\n                )}\n              </Calendar.DayGridHeaderRow>\n            </Calendar.DayGridHeader>\n            <Calendar.DayGridBody className={styles.DayGridBody}>\n              {(week) => (\n                <Calendar.DayGridRow\n                  value={week}\n                  key={week.getTime()}\n                  className={styles.DayGridRow}\n                >\n                  {(day) => (\n                    <Calendar.DayGridCell\n                      value={day}\n                      key={day.getTime()}\n                      className={styles.DayGridCell}\n                    >\n                      <Calendar.DayButton className={styles.DayButton} focusableWhenDisabled />\n                    </Calendar.DayGridCell>\n                  )}\n                </Calendar.DayGridRow>\n              )}\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </React.Fragment>\n      )}\n    </Calendar.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/calendar/calendar.module.css",
    "content": ".Root {\n  border: 1px solid var(--calendar-root-border-color);\n  border-radius: 8px;\n  height: 312px;\n  display: flex;\n  flex-direction: column;\n\n  --calendar-root-color: var(--color-gray-900);\n  --calendar-root-border-color: var(--color-gray-500);\n  --calendar-button-hover-bg-color: #e0f2fe;\n  --calendar-button-focus-border-color: #0ea5e9;\n  --calendar-day-grid-separator-bg-color: #9ca3af;\n  --calendar-day-grid-header-color: var(--color-gray-500);\n\n  --calendar-cell-selected-bg-color: #7dd3fc;\n  --calendar-cell-outside-month-color: var(--color-gray-400);\n  --calendar-cell-disabled-color: var(--color-gray-500);\n  --calendar-cell-current-border-color: var(--color-gray-500);\n  --calendar-cell-unavailable-color: #f87171;\n\n  @media (prefers-color-scheme: dark) {\n    --calendar-button-hover-bg-color: #075985;\n    --calendar-cell-selected-bg-color: #0369a1;\n  }\n}\n\n.RootWithTwoPanelsContent {\n  display: flex;\n  flex-direction: row;\n  justify-content: space-between;\n}\n\n.HeaderPanel {\n  display: flex;\n  flex-grow: 1;\n  justify-content: space-between;\n}\n\n.DayGridSeparator {\n  background-color: var(--calendar-day-grid-separator-bg-color);\n  margin: 24px 0;\n  width: 1px;\n}\n\n.Header {\n  box-sizing: border-box;\n  padding: 8px 12px;\n  height: 40px;\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n}\n\n.HeaderLabel {\n  color: var(--calendar-root-color);\n}\n\n.DecrementMonth,\n.IncrementMonth {\n  border: none;\n  user-select: none;\n  background-color: transparent;\n  border-radius: 4px;\n  color: var(--calendar-root-color);\n  margin: 0 6px;\n  padding: 0;\n  display: flex;\n\n  &:hover {\n    background-color: var(--calendar-button-hover-bg-color);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--calendar-button-focus-border-color);\n  }\n\n  &[data-disabled] {\n    pointer-events: none;\n    color: var(--calendar-cell-disabled-color);\n  }\n}\n\n.DayGrid {\n  padding: 12px;\n  height: 276px;\n  display: flex;\n  flex-direction: column;\n  gap: 4px;\n  z-index: 1;\n  position: relative;\n}\n\n.DayGridBody {\n  display: flex;\n  flex-direction: column;\n  row-gap: 2px;\n}\n\n.DayGridRow,\n.DayGridHeaderRow {\n  display: flex;\n  justify-content: center;\n}\n\n.DayGridHeaderCell {\n  width: 36px;\n  text-align: center;\n  font-size: 0.75rem;\n  color: var(--calendar-day-grid-header-color);\n  font-weight: 500;\n  padding: 0;\n}\n\n.DayGridCell {\n  padding: 0;\n}\n\n.DayButton {\n  background: none;\n  padding: 0;\n  font: inherit;\n  height: 36px;\n  width: 36px;\n  position: relative;\n  user-select: none;\n  border: none;\n  background-color: transparent;\n  outline: none;\n  box-sizing: border-box;\n  border-radius: 4px;\n  color: var(--calendar-root-color);\n\n  &::before {\n    content: '';\n    position: absolute;\n    inset: 2px;\n    border-radius: 4px;\n    border: none;\n    z-index: -1;\n    background-color: transparent;\n  }\n\n  &::after {\n    content: '';\n    border-radius: 4px;\n    position: absolute;\n    inset: 2px;\n  }\n\n  &:not([data-outside-month]):focus-visible {\n    &::after {\n      outline: 2px solid var(--calendar-button-focus-border-color);\n    }\n  }\n\n  &:not([data-selected]):hover::before {\n    background-color: var(--calendar-button-hover-bg-color);\n  }\n\n  &[data-selected]:not([data-outside-month])::before {\n    background-color: var(--calendar-cell-selected-bg-color);\n  }\n\n  &[data-disabled] {\n    pointer-events: none;\n  }\n\n  &:not([data-outside-month])[data-disabled] {\n    color: var(--calendar-cell-disabled-color);\n  }\n\n  &:not([data-outside-month])[data-invalid] {\n    text-decoration: line-through;\n  }\n\n  &[data-outside-month] {\n    color: var(--calendar-cell-outside-month-color);\n    pointer-events: none;\n  }\n\n  &[data-current]:not([data-selected], :focus-visible)::after {\n    outline: 1px solid var(--calendar-cell-current-border-color);\n  }\n\n  &:not([data-disabled], [data-outside-month])[data-unavailable] {\n    text-decoration: line-through;\n    color: var(--calendar-cell-unavailable-color);\n  }\n}\n\n.Wrapper {\n  display: flex;\n  flex-flow: row wrap;\n  justify-content: center;\n}\n\n.Text {\n  color: var(--color-gray-900);\n  width: 100%;\n  text-align: center;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/collapsible/_icons.tsx",
    "content": "'use client';\nimport * as React from 'react';\n\nexport default function Nothing() {\n  return <div>This is just a dummy file to hold icons</div>;\n}\n\nexport function ChevronIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M3.5 9L7.5 5L3.5 1\" stroke=\"currentcolor\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/collapsible/animate-presence.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Collapsible } from '@base-ui/react/collapsible';\nimport { motion, AnimatePresence } from 'motion/react';\nimport styles from './motion.module.css';\nimport { ChevronIcon } from './_icons';\n\nexport default function CollapsibleAnimatePresence() {\n  const [open, setOpen] = React.useState(false);\n\n  return (\n    <div className={styles.wrapper}>\n      <Collapsible.Root open={open} onOpenChange={setOpen} className={styles.Root}>\n        <Collapsible.Trigger className={styles.Trigger}>\n          <ChevronIcon className={styles.Icon} />\n          Trigger\n        </Collapsible.Trigger>\n        <AnimatePresence>\n          {open && (\n            <Collapsible.Panel\n              key=\"hey\"\n              className={styles.Panel}\n              // this is a workaround for Tailwind v4 that uses `display: none !important`\n              // on [hidden] in the resets\n              // motion cannot directly override !important\n              // https://github.com/motiondivision/motion/issues/1285#issuecomment-934332108\n              // unsetting the hidden attr completely ensures the panel is visible during\n              // the exit animation\n              hidden={undefined}\n              keepMounted\n              render={\n                <motion.div\n                  // https://github.com/framer/motion/issues/368#issuecomment-898055607\n                  // it's possible to animate padding on Collapsible.Panel with motion\n                  // it looks much less janky than using CSS animations or transitions, but\n                  // still noticeably un-smooth\n                  initial={{ height: 0 }}\n                  animate={{\n                    height: '100%',\n                    transition: { duration: 1 },\n                  }}\n                  exit={{\n                    height: 0,\n                    // ensure the `display` property is set here to override\n                    // `display: none` that's usually default on the [hidden]\n                    // attribute\n                    // Tailwind 4 preflight interferes with this though\n                    display: 'block',\n                    transition: { duration: 1 },\n                  }}\n                />\n              }\n            >\n              <div className={styles.Content}>\n                <p>\n                  “Certainly; it would indeed be very impertinent and inhuman in me to trouble you\n                  with any inquisitiveness of mine.”\n                </p>\n                <p>\n                  “And yet you rescued me from a strange and perilous situation; you have\n                  benevolently restored me to life.”\n                </p>\n                <p>\n                  Soon after this he inquired if I thought that the breaking up of the ice had\n                  destroyed the other sledge. I replied that I could not answer with any degree of\n                  certainty, for the ice had not broken until near midnight, and the traveller might\n                  have arrived at a place of safety before that time; but of this I could not judge.\n                </p>\n              </div>\n            </Collapsible.Panel>\n          )}\n        </AnimatePresence>\n      </Collapsible.Root>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/collapsible/animations.module.css",
    "content": "@keyframes slide-down {\n  from {\n    height: 0;\n  }\n\n  to {\n    height: var(--collapsible-panel-height);\n  }\n}\n\n@keyframes slide-up {\n  from {\n    height: var(--collapsible-panel-height);\n  }\n\n  to {\n    height: 0;\n  }\n}\n\n.Panel {\n  overflow: hidden;\n  box-sizing: border-box;\n  width: 100%;\n\n  &[data-open] {\n    animation: slide-down var(--duration) ease-out;\n  }\n\n  &[data-closed] {\n    animation: slide-up var(--duration) ease-in;\n  }\n}\n\n/* the styles below are irrelevant to the features */\n.grid {\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n  grid-gap: 5rem;\n}\n\n.wrapper {\n  font-family: system-ui, sans-serif;\n  line-height: 1.4;\n  display: flex;\n  flex-flow: column nowrap;\n  align-items: stretch;\n  gap: 1rem;\n  align-self: flex-start;\n}\n\n.Root {\n  --width: 320px;\n  --duration: 900ms;\n\n  width: var(--width);\n\n  & + .Root {\n    margin-top: 2rem;\n  }\n}\n\n.Trigger {\n  display: flex;\n  width: 100%;\n  align-items: center;\n  gap: 0.4rem;\n  padding: 0.25rem 0.5rem;\n  border-radius: 0.25rem;\n  background-color: var(--color-gray-200);\n  color: var(--color-gray-900);\n\n  &[data-panel-open] .Icon {\n    transform: rotate(90deg);\n  }\n}\n\n.Icon {\n  width: 0.75rem;\n  height: 0.75rem;\n  transition: transform 150ms ease-out;\n}\n\n.Content {\n  display: flex;\n  flex-direction: column;\n  gap: 0.5rem;\n  margin-top: 0.25rem;\n  padding: 0.5rem;\n  border-radius: 0.25rem;\n  background-color: var(--color-gray-200);\n  cursor: text;\n\n  & p {\n    overflow-wrap: break-word;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/collapsible/animations.tsx",
    "content": "'use client';\nimport { Collapsible } from '@base-ui/react/collapsible';\nimport { ChevronIcon } from './_icons';\nimport styles from './animations.module.css';\n\nexport default function CssAnimations() {\n  return (\n    <div className={styles.grid}>\n      <div className={styles.wrapper}>\n        <pre>keepMounted: true</pre>\n        <Collapsible.Root className={styles.Root} defaultOpen>\n          <Collapsible.Trigger className={styles.Trigger}>\n            <ChevronIcon className={styles.Icon} />\n            Trigger\n          </Collapsible.Trigger>\n          <Collapsible.Panel className={styles.Panel} keepMounted>\n            <div className={styles.Content}>\n              <p>\n                He rubbed his eyes, and came close to the picture, and examined it again. There were\n                no signs of any change when he looked into the actual painting, and yet there was no\n                doubt that the whole expression had altered. It was not a mere fancy of his own. The\n                thing was horribly apparent.\n              </p>\n            </div>\n          </Collapsible.Panel>\n        </Collapsible.Root>\n\n        <Collapsible.Root className={styles.Root} defaultOpen={false}>\n          <Collapsible.Trigger className={styles.Trigger}>\n            <ChevronIcon className={styles.Icon} />\n            Trigger\n          </Collapsible.Trigger>\n          <Collapsible.Panel className={styles.Panel} keepMounted>\n            <div className={styles.Content}>\n              <p>\n                He rubbed his eyes, and came close to the picture, and examined it again. There were\n                no signs of any change when he looked into the actual painting, and yet there was no\n                doubt that the whole expression had altered. It was not a mere fancy of his own. The\n                thing was horribly apparent.\n              </p>\n            </div>\n          </Collapsible.Panel>\n        </Collapsible.Root>\n        <small>———</small>\n      </div>\n\n      <div className={styles.wrapper}>\n        <pre>keepMounted: false</pre>\n        <Collapsible.Root className={styles.Root} defaultOpen>\n          <Collapsible.Trigger className={styles.Trigger}>\n            <ChevronIcon className={styles.Icon} />\n            Trigger\n          </Collapsible.Trigger>\n          <Collapsible.Panel className={styles.Panel} keepMounted={false}>\n            <div className={styles.Content}>\n              <p>\n                He rubbed his eyes, and came close to the picture, and examined it again. There were\n                no signs of any change when he looked into the actual painting, and yet there was no\n                doubt that the whole expression had altered. It was not a mere fancy of his own. The\n                thing was horribly apparent.\n              </p>\n            </div>\n          </Collapsible.Panel>\n        </Collapsible.Root>\n\n        <Collapsible.Root className={styles.Root} defaultOpen={false}>\n          <Collapsible.Trigger className={styles.Trigger}>\n            <ChevronIcon className={styles.Icon} />\n            Trigger\n          </Collapsible.Trigger>\n          <Collapsible.Panel className={styles.Panel} keepMounted={false}>\n            <div className={styles.Content}>\n              <p>\n                He rubbed his eyes, and came close to the picture, and examined it again. There were\n                no signs of any change when he looked into the actual painting, and yet there was no\n                doubt that the whole expression had altered. It was not a mere fancy of his own. The\n                thing was horribly apparent.\n              </p>\n            </div>\n          </Collapsible.Panel>\n        </Collapsible.Root>\n        <small>———</small>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/collapsible/collapsible.module.css",
    "content": ".grid {\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n  grid-gap: 5rem;\n}\n\n.wrapper {\n  font-family: system-ui, sans-serif;\n  line-height: 1.4;\n  display: flex;\n  flex-flow: column nowrap;\n  align-items: stretch;\n  gap: 1rem;\n  align-self: flex-start;\n}\n\n.Root {\n  --width: 320px;\n  --duration: 1000ms;\n\n  width: var(--width);\n\n  & + .Root {\n    margin-top: 2rem;\n  }\n}\n\n.Trigger {\n  display: flex;\n  width: 100%;\n  align-items: center;\n  gap: 0.4rem;\n  padding: 0.25rem 0.5rem;\n  border-radius: 0.25rem;\n  background-color: var(--color-gray-200);\n  color: var(--color-gray-900);\n\n  &[data-panel-open] .Icon {\n    transform: rotate(90deg);\n  }\n}\n\n.Icon {\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.Panel {\n  overflow: hidden;\n  box-sizing: border-box;\n  width: 100%;\n  height: auto;\n  /*  height: var(--collapsible-panel-height); */\n}\n\n.Content {\n  display: flex;\n  flex-direction: column;\n  gap: 0.5rem;\n  margin-top: 0.25rem;\n  padding: 0.5rem;\n  border-radius: 0.25rem;\n  background-color: var(--color-gray-200);\n  cursor: text;\n\n  & p {\n    overflow-wrap: break-word;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/collapsible/motion.module.css",
    "content": ".grid {\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n  grid-gap: 5rem;\n}\n\n.wrapper {\n  font-family: system-ui, sans-serif;\n  line-height: 1.4;\n  display: flex;\n  flex-flow: column nowrap;\n  align-items: stretch;\n  gap: 1rem;\n  align-self: flex-start;\n}\n\n.Root {\n  --width: 320px;\n  --duration: 1000ms;\n\n  width: var(--width);\n\n  & + .Root {\n    margin-top: 2rem;\n  }\n}\n\n.Trigger {\n  display: flex;\n  width: 100%;\n  align-items: center;\n  gap: 0.4rem;\n  padding: 0.25rem 0.5rem;\n  border-radius: 0.25rem;\n  background-color: var(--color-gray-200);\n  color: var(--color-gray-900);\n\n  &[data-panel-open] .Icon {\n    transform: rotate(90deg);\n  }\n}\n\n.Icon {\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.Panel {\n  overflow: hidden;\n  box-sizing: border-box;\n  width: var(--width);\n}\n\n.Content {\n  display: flex;\n  flex-direction: column;\n  gap: 0.5rem;\n  margin-top: 0.25rem;\n  padding: 0.5rem;\n  border-radius: 0.25rem;\n  background-color: var(--color-gray-200);\n  cursor: text;\n\n  & p {\n    overflow-wrap: break-word;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/collapsible/plain.tsx",
    "content": "'use client';\nimport { Collapsible } from '@base-ui/react/collapsible';\nimport { ChevronIcon } from './_icons';\nimport styles from './collapsible.module.css';\n\nexport default function PlainCollapsible() {\n  return (\n    <div className={styles.grid}>\n      <div className={styles.wrapper}>\n        <pre>keepMounted: true</pre>\n        <Collapsible.Root className={styles.Root} defaultOpen>\n          <Collapsible.Trigger className={styles.Trigger}>\n            <ChevronIcon className={styles.Icon} />\n            Trigger\n          </Collapsible.Trigger>\n          <Collapsible.Panel className={styles.Panel} keepMounted>\n            <div className={styles.Content}>\n              <p>\n                He rubbed his eyes, and came close to the picture, and examined it again. There were\n                no signs of any change when he looked into the actual painting, and yet there was no\n                doubt that the whole expression had altered. It was not a mere fancy of his own. The\n                thing was horribly apparent.\n              </p>\n            </div>\n          </Collapsible.Panel>\n        </Collapsible.Root>\n\n        <Collapsible.Root className={styles.Root} defaultOpen={false}>\n          <Collapsible.Trigger className={styles.Trigger}>\n            <ChevronIcon className={styles.Icon} />\n            Trigger\n          </Collapsible.Trigger>\n          <Collapsible.Panel className={styles.Panel} keepMounted>\n            <div className={styles.Content}>\n              <p>\n                He rubbed his eyes, and came close to the picture, and examined it again. There were\n                no signs of any change when he looked into the actual painting, and yet there was no\n                doubt that the whole expression had altered. It was not a mere fancy of his own. The\n                thing was horribly apparent.\n              </p>\n            </div>\n          </Collapsible.Panel>\n        </Collapsible.Root>\n        <small>———</small>\n      </div>\n\n      <div className={styles.wrapper}>\n        <pre>keepMounted: false</pre>\n        <Collapsible.Root className={styles.Root} defaultOpen>\n          <Collapsible.Trigger className={styles.Trigger}>\n            <ChevronIcon className={styles.Icon} />\n            Trigger\n          </Collapsible.Trigger>\n          <Collapsible.Panel className={styles.Panel} keepMounted={false}>\n            <div className={styles.Content}>\n              <p>\n                He rubbed his eyes, and came close to the picture, and examined it again. There were\n                no signs of any change when he looked into the actual painting, and yet there was no\n                doubt that the whole expression had altered. It was not a mere fancy of his own. The\n                thing was horribly apparent.\n              </p>\n            </div>\n          </Collapsible.Panel>\n        </Collapsible.Root>\n\n        <Collapsible.Root className={styles.Root} defaultOpen={false}>\n          <Collapsible.Trigger className={styles.Trigger}>\n            <ChevronIcon className={styles.Icon} />\n            Trigger\n          </Collapsible.Trigger>\n          <Collapsible.Panel className={styles.Panel} keepMounted={false}>\n            <div className={styles.Content}>\n              <p>\n                He rubbed his eyes, and came close to the picture, and examined it again. There were\n                no signs of any change when he looked into the actual painting, and yet there was no\n                doubt that the whole expression had altered. It was not a mere fancy of his own. The\n                thing was horribly apparent.\n              </p>\n            </div>\n          </Collapsible.Panel>\n        </Collapsible.Root>\n        <small>———</small>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/collapsible/transitions.module.css",
    "content": ".Root {\n  --width: 320px;\n  --duration: 1000ms;\n\n  width: var(--width);\n\n  & + .Root {\n    margin-top: 2rem;\n  }\n}\n\n.Panel {\n  overflow: hidden;\n  box-sizing: border-box;\n  width: 100%;\n\n  height: var(--collapsible-panel-height);\n\n  transition: all var(--duration) ease-out;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    height: 0;\n    opacity: 0;\n  }\n}\n\n.Content {\n  display: flex;\n  flex-direction: column;\n  gap: 0.5rem;\n  margin-top: 0.25rem;\n  padding: 0.5rem;\n  border-radius: 0.25rem;\n  background-color: var(--color-gray-200);\n  cursor: text;\n\n  & p {\n    overflow-wrap: break-word;\n  }\n}\n\n.Trigger {\n  display: flex;\n  width: 100%;\n  align-items: center;\n  gap: 0.4rem;\n  padding: 0.25rem 0.5rem;\n  border-radius: 0.25rem;\n  background-color: var(--color-gray-200);\n  color: var(--color-gray-900);\n\n  &[data-panel-open] .Icon {\n    transform: rotate(90deg);\n  }\n}\n\n.Icon {\n  width: 0.75rem;\n  height: 0.75rem;\n  transition: transform var(--duration) ease-out;\n}\n\n.grid {\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n  grid-gap: 5rem;\n}\n\n.wrapper {\n  font-family: system-ui, sans-serif;\n  line-height: 1.4;\n  display: flex;\n  flex-flow: column nowrap;\n  align-items: stretch;\n  gap: 1rem;\n  align-self: flex-start;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/collapsible/transitions.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Collapsible } from '@base-ui/react/collapsible';\nimport { ChevronIcon } from './_icons';\nimport styles from './transitions.module.css';\n\nexport default function CssTransitions() {\n  const [open, setOpen] = React.useState(false);\n  return (\n    <div className={styles.grid}>\n      <div className={styles.wrapper}>\n        <pre>keepMounted: true</pre>\n        <Collapsible.Root className={styles.Root} defaultOpen>\n          <Collapsible.Trigger className={styles.Trigger}>\n            <ChevronIcon className={styles.Icon} />\n            Trigger\n          </Collapsible.Trigger>\n          <Collapsible.Panel className={styles.Panel} keepMounted>\n            <div className={styles.Content}>\n              <p>\n                He rubbed his eyes, and came close to the picture, and examined it again. There were\n                no signs of any change when he looked into the actual painting, and yet there was no\n                doubt that the whole expression had altered. It was not a mere fancy of his own. The\n                thing was horribly apparent.\n              </p>\n            </div>\n          </Collapsible.Panel>\n        </Collapsible.Root>\n\n        <Collapsible.Root className={styles.Root} defaultOpen={false}>\n          <Collapsible.Trigger className={styles.Trigger}>\n            <ChevronIcon className={styles.Icon} />\n            Trigger\n          </Collapsible.Trigger>\n          <Collapsible.Panel className={styles.Panel} keepMounted>\n            <div className={styles.Content}>\n              <p>\n                He rubbed his eyes, and came close to the picture, and examined it again. There were\n                no signs of any change when he looked into the actual painting, and yet there was no\n                doubt that the whole expression had altered. It was not a mere fancy of his own. The\n                thing was horribly apparent.\n              </p>\n            </div>\n          </Collapsible.Panel>\n        </Collapsible.Root>\n        <small>———</small>\n      </div>\n\n      <div className={styles.wrapper}>\n        <pre>keepMounted: false</pre>\n        <Collapsible.Root className={styles.Root} defaultOpen>\n          <Collapsible.Trigger className={styles.Trigger}>\n            <ChevronIcon className={styles.Icon} />\n            Trigger\n          </Collapsible.Trigger>\n          <Collapsible.Panel className={styles.Panel} keepMounted={false}>\n            <div className={styles.Content}>\n              <p>\n                He rubbed his eyes, and came close to the picture, and examined it again. There were\n                no signs of any change when he looked into the actual painting, and yet there was no\n                doubt that the whole expression had altered. It was not a mere fancy of his own. The\n                thing was horribly apparent.\n              </p>\n            </div>\n          </Collapsible.Panel>\n        </Collapsible.Root>\n\n        <Collapsible.Root className={styles.Root} defaultOpen={false}>\n          <Collapsible.Trigger className={styles.Trigger}>\n            <ChevronIcon className={styles.Icon} />\n            Trigger\n          </Collapsible.Trigger>\n          <Collapsible.Panel className={styles.Panel} keepMounted={false}>\n            <div className={styles.Content}>\n              <p>\n                He rubbed his eyes, and came close to the picture, and examined it again. There were\n                no signs of any change when he looked into the actual painting, and yet there was no\n                doubt that the whole expression had altered. It was not a mere fancy of his own. The\n                thing was horribly apparent.\n              </p>\n            </div>\n          </Collapsible.Panel>\n        </Collapsible.Root>\n        <small>———</small>\n      </div>\n\n      <div className={styles.wrapper}>\n        <pre>controlled</pre>\n        <Collapsible.Root className={styles.Root} open={open} onOpenChange={setOpen}>\n          <Collapsible.Trigger className={styles.Trigger}>\n            <ChevronIcon className={styles.Icon} />\n            Trigger\n          </Collapsible.Trigger>\n          <Collapsible.Panel className={styles.Panel} keepMounted>\n            <div className={styles.Content}>\n              <p>\n                He rubbed his eyes, and came close to the picture, and examined it again. There were\n                no signs of any change when he looked into the actual painting, and yet there was no\n                doubt that the whole expression had altered. It was not a mere fancy of his own. The\n                thing was horribly apparent.\n              </p>\n            </div>\n          </Collapsible.Panel>\n        </Collapsible.Root>\n        <small>———</small>\n\n        <pre>nested</pre>\n        <Collapsible.Root className={styles.Root}>\n          <Collapsible.Trigger className={styles.Trigger}>\n            <ChevronIcon className={styles.Icon} />\n            Trigger\n          </Collapsible.Trigger>\n          <Collapsible.Panel className={styles.Panel} keepMounted>\n            <div className={styles.Content}>\n              <p>\n                He rubbed his eyes, and came close to the picture, and examined it again. There were\n                no signs of any change when he looked into the actual painting, and yet there was no\n                doubt that the whole expression had altered. It was not a mere fancy of his own. The\n                thing was horribly apparent.\n              </p>\n              <Collapsible.Root className={styles.Root}>\n                <Collapsible.Trigger className={styles.Trigger}>\n                  <ChevronIcon className={styles.Icon} />\n                  Trigger\n                </Collapsible.Trigger>\n                <Collapsible.Panel className={styles.Panel} keepMounted>\n                  <div className={styles.Content}>\n                    <p>\n                      He rubbed his eyes, and came close to the picture, and examined it again.\n                      There were no signs of any change when he looked into the actual painting, and\n                      yet there was no doubt that the whole expression had altered. It was not a\n                      mere fancy of his own. The thing was horribly apparent.\n                    </p>\n                  </div>\n                </Collapsible.Panel>\n              </Collapsible.Root>\n            </div>\n          </Collapsible.Panel>\n        </Collapsible.Root>\n        <small>———</small>\n\n        <div style={{ height: 1000 }} />\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/combobox/creatable-tags.module.css",
    "content": ".Container {\n  max-width: 28rem;\n}\n\n.Intro {\n  margin-bottom: 1rem;\n}\n\n.Field {\n  display: flex;\n  flex-direction: column;\n  gap: 0.25rem;\n}\n\n.Chips {\n  display: flex;\n  flex-wrap: wrap;\n  align-items: center;\n  gap: 0.125rem;\n  width: 16rem;\n  min-width: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  padding: 0.25rem 0.375rem;\n}\n\n@media (min-width: 500px) {\n  .Chips {\n    width: 22rem;\n  }\n}\n\n.Chips:focus-within {\n  outline: 2px solid var(--color-blue-800);\n  outline-offset: -1px;\n}\n\n.Chip {\n  display: flex;\n  align-items: center;\n  gap: 0.25rem;\n  cursor: default;\n  border-radius: 0.375rem;\n  background: var(--color-gray-100);\n  padding: 0.2rem 0.375rem;\n  font-size: var(--text-sm);\n  line-height: var(--text-sm--line-height);\n  color: var(--color-gray-900);\n  outline: none;\n}\n\n.Chip[data-highlighted],\n.Chip:focus-within {\n  background: var(--color-blue-800);\n  color: var(--color-gray-50);\n}\n\n.ChipRemove {\n  border-radius: 0.375rem;\n  padding: 0.25rem;\n  color: inherit;\n}\n\n.ChipRemove:hover {\n  background: var(--color-gray-200);\n}\n\n.Input {\n  height: 2rem;\n  min-width: 3rem;\n  flex: 1;\n  border: 0;\n  border-radius: 0.375rem;\n  background: transparent;\n  padding-left: 0.5rem;\n  font-size: var(--text-base);\n  line-height: var(--text-base--line-height);\n  color: var(--color-gray-900);\n  outline: none;\n}\n\n.Positioner {\n  z-index: 50;\n  outline: none;\n}\n\n.Popup {\n  width: var(--anchor-width);\n  max-width: var(--available-width);\n  max-height: min(var(--available-height), 24rem);\n  overflow-y: auto;\n  overscroll-behavior: contain;\n  scroll-padding-top: 0.5rem;\n  scroll-padding-bottom: 0.5rem;\n  border-radius: 0.5rem;\n  background: canvas;\n  padding: 0.5rem 0;\n  color: var(--color-gray-900);\n  outline: 1px solid var(--color-gray-200);\n  box-shadow: var(--shadow-lg);\n}\n\n@media (prefers-color-scheme: dark) {\n  .Popup {\n    box-shadow: none;\n    outline-color: var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Item {\n  position: relative;\n  z-index: 0;\n  display: grid;\n  grid-template-columns: 0.75rem 1fr;\n  align-items: center;\n  gap: 0.5rem;\n  cursor: default;\n  padding: 0.5rem 2rem 0.5rem 1rem;\n  font-size: var(--text-base);\n  line-height: 1rem;\n  outline: none;\n  user-select: none;\n}\n\n.Item[data-highlighted] {\n  color: var(--color-gray-50);\n}\n\n.Item[data-highlighted]::before {\n  content: '';\n  position: absolute;\n  inset: 0 0.5rem;\n  z-index: -1;\n  border-radius: 0.25rem;\n  background: var(--color-gray-900);\n}\n\n.ItemIndicator {\n  grid-column-start: 1;\n}\n\n.ItemText {\n  grid-column-start: 2;\n}\n\n.TinyIcon {\n  width: 0.75rem;\n  height: 0.75rem;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/combobox/creatable-tags.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Combobox as BaseCombobox } from '@base-ui/react/combobox';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport styles from './creatable-tags.module.css';\n\nconst INITIAL_ITEMS = [\n  'Black',\n  'Blue',\n  'Cyan',\n  'Gray',\n  'Green',\n  'Magenta',\n  'Orange',\n  'Purple',\n  'Red',\n  'White',\n  'Yellow',\n];\n\ninterface ComboboxProps {\n  items?: string[];\n  selectedItems: string[];\n  onSelectedItemsChange: (items: string[]) => void;\n  onCreate?: (label: string) => void;\n  placeholder?: string;\n}\n\nexport default function Experiment() {\n  const [items, setItems] = React.useState(INITIAL_ITEMS);\n  const [selectedItems, setSelectedItems] = React.useState<string[]>([]);\n\n  return (\n    <div className={styles.Container}>\n      <h1>Creatable tags</h1>\n      <p className={styles.Intro}>\n        Select multiple items from the list or create new ones by typing and pressing Enter or\n        comma.\n      </p>\n      <Combobox\n        items={items}\n        selectedItems={selectedItems}\n        onSelectedItemsChange={setSelectedItems}\n        onCreate={(label) => {\n          setItems((prev) => [...prev, label].sort());\n        }}\n        placeholder=\"Red, Green, Blue...\"\n      />\n    </div>\n  );\n}\n\nfunction Combobox(props: ComboboxProps) {\n  const {\n    items = [],\n    selectedItems,\n    onSelectedItemsChange,\n    onCreate,\n    placeholder: placeholderProp,\n  } = props;\n  const [inputValue, setInputValue] = React.useState('');\n\n  const containerRef = React.useRef<HTMLDivElement | null>(null);\n  const comboboxInputRef = React.useRef<HTMLInputElement | null>(null);\n  const highlightedItemRef = React.useRef<InternalComboboxItem | null>(null);\n\n  const handleCreate = useStableCallback(async (labelToAdd: string) => {\n    if (labelToAdd === '') {\n      return;\n    }\n\n    onCreate?.(labelToAdd);\n    const next = [...selectedItems, labelToAdd];\n    onSelectedItemsChange(next);\n    setInputValue('');\n    return;\n  });\n\n  const trimmedValue = inputValue.trim();\n  const exactMatchExists = items.some(\n    (item) => item.trim().toLocaleLowerCase() === trimmedValue.toLocaleLowerCase(),\n  );\n\n  // Show the creatable item alongside matches if there's no exact match, and keep selections on top\n  const itemsForView: InternalComboboxItem[] = React.useMemo(() => {\n    const selectedSet = new Set(selectedItems);\n    const normalizedItems = items.map((value) => ({ value }));\n    const ordered = [\n      ...normalizedItems.filter((item) => selectedSet.has(item.value)),\n      ...normalizedItems.filter((item) => !selectedSet.has(item.value)),\n    ];\n\n    return trimmedValue !== '' && !exactMatchExists\n      ? [...ordered, { value: trimmedValue, isNew: true }]\n      : ordered;\n  }, [items, selectedItems, trimmedValue, exactMatchExists]);\n\n  const handleCommit = useStableCallback((item?: InternalComboboxItem | null) => {\n    if (item) {\n      if (item.isNew) {\n        void handleCreate(item.value);\n        return;\n      }\n\n      if (selectedItems.includes(item.value)) {\n        setInputValue('');\n        return;\n      }\n\n      onSelectedItemsChange([...selectedItems, item.value]);\n      setInputValue('');\n      return;\n    }\n\n    if (trimmedValue === '') {\n      return;\n    }\n\n    const normalized = trimmedValue.toLocaleLowerCase();\n    const existing = items.find((candidate) => candidate.trim().toLocaleLowerCase() === normalized);\n\n    if (existing) {\n      const next = selectedItems.some((i) => i === existing)\n        ? selectedItems\n        : [...selectedItems, existing];\n      onSelectedItemsChange(next);\n      setInputValue('');\n      return;\n    }\n\n    void handleCreate(trimmedValue);\n  });\n\n  const handleInputKeyDown = useStableCallback((event: React.KeyboardEvent<HTMLInputElement>) => {\n    // Treat comma as Enter\n    if (event.key === ',') {\n      event.preventDefault();\n      if (highlightedItemRef.current) {\n        handleCommit(highlightedItemRef.current);\n        return;\n      }\n      handleCommit();\n      return;\n    }\n\n    if (event.key === 'Enter' && highlightedItemRef.current == null) {\n      event.preventDefault();\n      handleCommit();\n    }\n  });\n\n  const selectedItemsForView = React.useMemo(() => {\n    const map = new Map(itemsForView.map((item) => [item.value, item] as const));\n    return selectedItems\n      .map((selectedValue) => map.get(selectedValue))\n      .filter((item): item is InternalComboboxItem => Boolean(item));\n  }, [itemsForView, selectedItems]);\n\n  return (\n    <BaseCombobox.Root\n      items={itemsForView}\n      multiple\n      onValueChange={(nextSelectedItems: InternalComboboxItem[]) => {\n        if (nextSelectedItems.length === 0) {\n          onSelectedItemsChange([]);\n          return;\n        }\n\n        const lastItem = nextSelectedItems[nextSelectedItems.length - 1];\n        if (!lastItem) {\n          return;\n        }\n\n        if (lastItem.isNew) {\n          void handleCreate(lastItem.value);\n          return;\n        }\n\n        const clean = nextSelectedItems.filter((item) => !item.isNew);\n        onSelectedItemsChange(clean.map((i) => i.value));\n        setInputValue('');\n      }}\n      value={selectedItemsForView}\n      inputValue={inputValue}\n      onInputValueChange={setInputValue}\n      onItemHighlighted={(item) => {\n        highlightedItemRef.current = item ?? null;\n      }}\n    >\n      <div className={styles.Field}>\n        <BaseCombobox.Chips className={styles.Chips} ref={containerRef}>\n          <BaseCombobox.Value>\n            {(itemsToRender: InternalComboboxItem[]) => (\n              <React.Fragment>\n                {itemsToRender.map((item) => (\n                  <BaseCombobox.Chip\n                    key={item.value}\n                    className={styles.Chip}\n                    aria-label={item.value}\n                  >\n                    {item.value}\n                    <BaseCombobox.ChipRemove className={styles.ChipRemove} aria-label=\"Remove\">\n                      <XIcon />\n                    </BaseCombobox.ChipRemove>\n                  </BaseCombobox.Chip>\n                ))}\n                <BaseCombobox.Input\n                  ref={comboboxInputRef}\n                  placeholder={itemsToRender.length > 0 ? '' : placeholderProp}\n                  className={styles.Input}\n                  onKeyDown={handleInputKeyDown}\n                />\n              </React.Fragment>\n            )}\n          </BaseCombobox.Value>\n        </BaseCombobox.Chips>\n      </div>\n\n      <BaseCombobox.Portal>\n        <BaseCombobox.Positioner className={styles.Positioner} sideOffset={4} anchor={containerRef}>\n          <BaseCombobox.Popup className={styles.Popup}>\n            <BaseCombobox.List>\n              {(item: InternalComboboxItem) =>\n                item.isNew ? renderCreateItem(item) : renderRegularItem(item)\n              }\n            </BaseCombobox.List>\n          </BaseCombobox.Popup>\n        </BaseCombobox.Positioner>\n      </BaseCombobox.Portal>\n    </BaseCombobox.Root>\n  );\n}\n\nfunction renderRegularItem(item: InternalComboboxItem) {\n  return (\n    <BaseCombobox.Item key={String(item.value)} className={styles.Item} value={item}>\n      <BaseCombobox.ItemIndicator className={styles.ItemIndicator}>\n        <CheckIcon className={styles.TinyIcon} />\n      </BaseCombobox.ItemIndicator>\n      <div className={styles.ItemText}>{item.value}</div>\n    </BaseCombobox.Item>\n  );\n}\n\nfunction renderCreateItem(item: InternalComboboxItem) {\n  return (\n    <BaseCombobox.Item key={`new:${item.value}`} className={styles.Item} value={item}>\n      <span className={styles.ItemIndicator}>\n        <PlusIcon className={styles.TinyIcon} />\n      </span>\n\n      <div className={styles.ItemText}>{item.value}</div>\n    </BaseCombobox.Item>\n  );\n}\n\ninterface InternalComboboxItem {\n  value: string;\n  isNew?: boolean;\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\nfunction PlusIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"12\"\n      height=\"12\"\n      viewBox=\"0 0 12 12\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"butt\"\n      strokeLinejoin=\"miter\"\n      aria-hidden\n      {...props}\n    >\n      <path d=\"M6 1v10M1 6h10\" />\n    </svg>\n  );\n}\n\nfunction XIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width={16}\n      height={16}\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      aria-hidden\n      {...props}\n    >\n      <path d=\"M18 6 6 18\" />\n      <path d=\"m6 6 12 12\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/combobox/dialog-combobox-multiple.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Combobox, Dialog } from '@base-ui/react';\nimport styles from './dialog-combobox.module.css';\nimport { fruits } from './dialog-combobox';\n\nexport default function DialogComboboxMultiple() {\n  const [open, setOpen] = React.useState(false);\n  const inputId = React.useId();\n\n  return (\n    <Combobox.Root items={fruits} multiple open={open} onOpenChange={setOpen} inline>\n      <Dialog.Root open={open} onOpenChange={setOpen}>\n        <Dialog.Trigger className={styles.Button}>\n          <Combobox.Value>\n            {(value: string[]) => {\n              if (value.length === 0) {\n                return 'Select fruits';\n              }\n              if (value.length === 1) {\n                return value[0];\n              }\n              return `${value.length} fruits selected`;\n            }}\n          </Combobox.Value>\n        </Dialog.Trigger>\n        <Dialog.Portal>\n          <Dialog.Backdrop className={styles.Backdrop} />\n          <div className={styles.Viewport}>\n            <Dialog.Popup className={styles.Popup}>\n              <Dialog.Title className={styles.Title}>Choose fruits</Dialog.Title>\n              <Dialog.Description className={styles.Description}>\n                Pick one or more fruits to fill today&apos;s order.\n              </Dialog.Description>\n              <div className={styles.Label}>\n                <label htmlFor={inputId}>Fruits</label>\n                <Combobox.Chips className={styles.Chips}>\n                  <Combobox.Value>\n                    {(value: string[]) => (\n                      <React.Fragment>\n                        {value.map((fruit) => (\n                          <Combobox.Chip key={fruit} className={styles.Chip} aria-label={fruit}>\n                            {fruit}\n                            <Combobox.ChipRemove\n                              className={styles.ChipRemove}\n                              aria-label={`Remove ${fruit}`}\n                            >\n                              <XIcon className={styles.ChipRemoveIcon} />\n                            </Combobox.ChipRemove>\n                          </Combobox.Chip>\n                        ))}\n                        <Combobox.Input\n                          id={inputId}\n                          placeholder={value.length === 0 ? 'e.g. Apple' : ''}\n                          className={styles.ChipsInput}\n                        />\n                      </React.Fragment>\n                    )}\n                  </Combobox.Value>\n                </Combobox.Chips>\n              </div>\n              <div className={styles.Results}>\n                <Combobox.Empty className={styles.Empty}>No fruits found.</Combobox.Empty>\n                <Combobox.List className={styles.List}>\n                  {(item: string) => (\n                    <Combobox.Item key={item} value={item} className={styles.Item}>\n                      <Combobox.ItemIndicator className={styles.ItemIndicator}>\n                        <CheckIcon className={styles.ItemIndicatorIcon} />\n                      </Combobox.ItemIndicator>\n                      <div className={styles.ItemText}>{item}</div>\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </div>\n              <div className={styles.Actions}>\n                <Dialog.Close className={styles.Button}>Done</Dialog.Close>\n              </div>\n            </Dialog.Popup>\n          </div>\n        </Dialog.Portal>\n      </Dialog.Root>\n    </Combobox.Root>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\nfunction XIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width={16}\n      height={16}\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      aria-hidden\n      {...props}\n    >\n      <path d=\"M18 6 6 18\" />\n      <path d=\"m6 6 12 12\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/combobox/dialog-combobox.module.css",
    "content": ".Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 500;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Backdrop {\n  position: fixed;\n  min-height: 100dvh;\n  inset: 0;\n  background-color: black;\n  opacity: 0.2;\n  transition: opacity 150ms cubic-bezier(0.45, 1.005, 0, 1.005);\n\n  @supports (-webkit-touch-callout: none) {\n    position: absolute;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    opacity: 0.7;\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n  }\n}\n\n.Popup {\n  box-sizing: border-box;\n  position: fixed;\n  width: 24rem;\n  top: 5rem;\n  max-width: calc(100vw - 3rem);\n  max-height: min(32rem, calc(100dvh - 6rem));\n  padding: 1.5rem;\n  border-radius: 0.5rem;\n  outline: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-50);\n  color: var(--color-gray-900);\n  display: flex;\n  justify-content: center;\n  flex-direction: column;\n  overflow: hidden;\n  transition:\n    opacity 0.25s,\n    transform 0.25s;\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: translateY(-1rem);\n  }\n}\n\n.Title {\n  margin-top: -0.375rem;\n  margin-bottom: 0.25rem;\n  font-size: 1.125rem;\n  line-height: 1.75rem;\n  letter-spacing: -0.0025em;\n  font-weight: 500;\n}\n\n.Description {\n  margin-bottom: 1rem;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-600);\n}\n\n.Actions {\n  display: flex;\n  justify-content: end;\n  gap: 1rem;\n}\n\n.Label {\n  display: flex;\n  flex-direction: column;\n  gap: 0.25rem;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 500;\n  color: var(--color-gray-900);\n  position: relative;\n}\n\n.InputWrapper {\n  position: relative;\n\n  &:has(.Clear) .Input {\n    padding-right: calc(0.5rem + 1.5rem * 2);\n  }\n}\n\n.Input {\n  box-sizing: border-box;\n  padding-left: 0.875rem;\n  padding-right: calc(0.5rem + 1.5rem);\n  margin: 0;\n  border: 1px solid var(--color-gray-200);\n  width: 100%;\n  height: 2.5rem;\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n\n  &:focus {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Chips {\n  box-sizing: border-box;\n  display: flex;\n  flex-wrap: wrap;\n  align-items: center;\n  gap: 0.125rem;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  padding: 0.25rem 0.375rem;\n  min-height: 2.5rem;\n  background-color: canvas;\n  width: 100%;\n\n  &:focus-within {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Chip {\n  display: flex;\n  align-items: center;\n  background-color: var(--color-gray-100);\n  color: var(--color-gray-900);\n  border-radius: 0.375rem;\n  font-size: 0.875rem;\n  padding: 0.25rem 0.25rem 0.25rem 0.5rem;\n  gap: 0.25rem;\n  outline: 0;\n  cursor: default;\n\n  &:focus-within {\n    background-color: var(--color-blue);\n    color: var(--color-gray-50);\n  }\n\n  @media (hover: hover) {\n    &[data-highlighted] {\n      background-color: var(--color-blue);\n      color: var(--color-gray-50);\n    }\n  }\n}\n\n.ChipRemove {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  border: none;\n  background: none;\n  border-radius: 0.375rem;\n  padding: 0.125rem;\n  color: inherit;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-200);\n    }\n  }\n}\n\n.ChipRemoveIcon {\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.ChipsInput {\n  flex: 1;\n  min-width: 6rem;\n  border: none;\n  height: 2rem;\n  padding-left: 0.5rem;\n  margin: 0;\n  font: inherit;\n  color: var(--color-gray-900);\n  background: none;\n\n  &:focus {\n    outline: none;\n  }\n}\n\n.Results {\n  display: flex;\n  flex-direction: column;\n  flex: 1;\n  min-height: 0;\n  gap: 0.25rem;\n  overflow: hidden;\n}\n\n.List {\n  box-sizing: border-box;\n  overflow-y: auto;\n  overscroll-behavior: contain;\n  padding-block: 0.5rem;\n  scroll-padding-block: 0.5rem;\n  outline: 0;\n  max-height: min(23rem, 60vh);\n  margin: 0;\n  flex: 1;\n  min-height: 0;\n\n  &[data-empty] {\n    padding: 0;\n  }\n}\n\n.Item {\n  box-sizing: border-box;\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  padding-block: 0.5rem;\n  padding-left: 1rem;\n  padding-right: 2rem;\n  display: grid;\n  gap: 0.5rem;\n  align-items: center;\n  grid-template-columns: 0.75rem 1fr;\n  font-size: 1rem;\n  line-height: 1rem;\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.5rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.ItemText {\n  grid-column-start: 2;\n}\n\n.ItemIndicator {\n  grid-column-start: 1;\n}\n\n.ItemIndicatorIcon {\n  display: block;\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.Empty:not(:empty) {\n  font-size: 0.925rem;\n  line-height: 1rem;\n  color: var(--color-gray-600);\n  padding: 1rem;\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-100);\n}\n\n.Viewport {\n  display: flex;\n  justify-content: center;\n  align-items: center;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/combobox/dialog-combobox.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Combobox, Dialog } from '@base-ui/react';\nimport styles from './dialog-combobox.module.css';\n\nexport default function DialogCombobox() {\n  const [open, setOpen] = React.useState<boolean>(false);\n  const inputId = React.useId();\n\n  return (\n    <Combobox.Root items={fruits} open={open} onOpenChange={setOpen} inline>\n      <Dialog.Root open={open} onOpenChange={setOpen}>\n        <Dialog.Trigger className={styles.Button}>\n          <Combobox.Value>{(value: string | null) => value || 'Select a fruit'}</Combobox.Value>\n        </Dialog.Trigger>\n        <Dialog.Portal>\n          <Dialog.Backdrop className={styles.Backdrop} />\n          <div className={styles.Viewport}>\n            <Dialog.Popup className={styles.Popup}>\n              <Dialog.Title className={styles.Title}>Choose a fruit</Dialog.Title>\n              <Dialog.Description className={styles.Description}>\n                Pick a fruit to fill today&apos;s order.\n              </Dialog.Description>\n              <div className={styles.Label}>\n                <label htmlFor={inputId}>Fruit</label>\n                <div className={styles.InputWrapper}>\n                  <Combobox.Input id={inputId} placeholder=\"e.g. Apple\" className={styles.Input} />\n                </div>\n              </div>\n              <div className={styles.Results}>\n                <Combobox.Empty className={styles.Empty}>No fruits found.</Combobox.Empty>\n                <Combobox.List className={styles.List}>\n                  {(item: string) => (\n                    <Combobox.Item key={item} value={item} className={styles.Item}>\n                      <Combobox.ItemIndicator className={styles.ItemIndicator}>\n                        <CheckIcon className={styles.ItemIndicatorIcon} />\n                      </Combobox.ItemIndicator>\n                      <div className={styles.ItemText}>{item}</div>\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </div>\n              <div className={styles.Actions}>\n                <Dialog.Close className={styles.Button}>Done</Dialog.Close>\n              </div>\n            </Dialog.Popup>\n          </div>\n        </Dialog.Portal>\n      </Dialog.Root>\n    </Combobox.Root>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\nexport const fruits: string[] = [\n  'Apple',\n  'Banana',\n  'Orange',\n  'Pineapple',\n  'Grape',\n  'Mango',\n  'Strawberry',\n  'Blueberry',\n  'Raspberry',\n  'Blackberry',\n  'Cherry',\n  'Peach',\n  'Pear',\n  'Plum',\n  'Kiwi',\n  'Watermelon',\n  'Cantaloupe',\n  'Honeydew',\n  'Papaya',\n  'Guava',\n  'Lychee',\n  'Pomegranate',\n  'Apricot',\n  'Grapefruit',\n  'Passionfruit',\n];\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/combobox/priority-combobox.module.css",
    "content": ".Trigger {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  height: 2rem;\n  cursor: default;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.5rem;\n  background: canvas;\n  background-clip: padding-box;\n  padding: 0 0.75rem;\n  font-size: var(--text-sm);\n  line-height: var(--text-sm--line-height);\n  letter-spacing: var(--text-sm--letter-spacing);\n  color: var(--color-gray-900);\n  user-select: none;\n}\n\n.Trigger:hover,\n.Trigger[data-popup-open] {\n  background: var(--color-gray-50);\n}\n\n.Trigger:focus-visible {\n  outline: 2px solid var(--color-blue-800);\n  outline-offset: -1px;\n}\n\n.TriggerValue {\n  display: flex;\n  align-items: center;\n  gap: 0.5rem;\n}\n\n.Popup {\n  --input-container-height: 3rem;\n  max-width: 15rem;\n  max-height: min(24rem, var(--available-height));\n  transform-origin: var(--transform-origin);\n  border-radius: 0.5rem;\n  background: canvas;\n  color: var(--color-gray-900);\n  background-clip: padding-box;\n  box-shadow:\n    0 1px 2px rgb(0 0 0 / 2.5%),\n    0 1px 3px rgb(0 0 0 / 2.5%);\n  outline: 1px solid var(--color-gray-200);\n  transition:\n    transform 150ms,\n    scale 150ms,\n    opacity 150ms;\n}\n\n.Popup[data-starting-style],\n.Popup[data-ending-style] {\n  scale: 0.9;\n  opacity: 0;\n}\n\n@media (prefers-color-scheme: dark) {\n  .Popup {\n    box-shadow: none;\n    outline-color: var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.InputRow {\n  display: grid;\n  grid-template-columns: 1fr 1.4rem;\n  gap: 0.5rem;\n  width: 15rem;\n  padding: 0.25rem 0.5rem;\n  text-align: center;\n}\n\n.Input {\n  grid-column-start: 1;\n  width: 100%;\n  height: 2rem;\n  padding-left: 0.875rem;\n  font-size: var(--text-sm);\n  line-height: var(--text-sm--line-height);\n  letter-spacing: var(--text-sm--letter-spacing);\n  font-weight: 400;\n  color: var(--color-gray-900);\n}\n\n.Input:focus {\n  outline: 0;\n}\n\n.ShortcutKey {\n  grid-column-start: 2;\n  align-self: center;\n  display: flex;\n  justify-content: center;\n  height: 1.4rem;\n  border: 1px solid var(--color-gray-300);\n  border-radius: 0.375rem;\n  font-size: 0.75rem;\n  color: var(--color-gray-800);\n}\n\n.Separator {\n  border-top: 1px solid var(--color-gray-200);\n}\n\n.Empty {\n  padding: 1rem;\n  font-size: 0.925rem;\n  line-height: 1rem;\n  color: var(--color-gray-600);\n}\n\n.Empty:empty {\n  margin: 0;\n  padding: 0;\n}\n\n.List {\n  overflow-y: auto;\n  overscroll-behavior: contain;\n  max-height: min(\n    calc(24rem - var(--input-container-height)),\n    calc(var(--available-height) - var(--input-container-height))\n  );\n  padding: 0.5rem 0;\n  scroll-padding: 0.5rem;\n}\n\n.List:empty {\n  padding: 0;\n}\n\n.Item {\n  position: relative;\n  z-index: 0;\n  display: grid;\n  grid-template-columns: 1rem 1fr 0.75rem 0.75rem;\n  min-width: var(--anchor-width);\n  align-items: center;\n  gap: 0.5rem;\n  cursor: default;\n  padding: 0.5rem 1rem;\n  font-size: var(--text-sm);\n  line-height: 1rem;\n  user-select: none;\n  outline: none;\n}\n\n.Item[data-highlighted] {\n  color: var(--color-gray-800);\n}\n\n.Item[data-highlighted]::before {\n  content: '';\n  position: absolute;\n  inset: 0 0.5rem;\n  z-index: -1;\n  border-radius: 0.25rem;\n  background: var(--color-gray-100);\n}\n\n.ItemIcon {\n  grid-column-start: 1;\n}\n\n.ItemText {\n  grid-column-start: 2;\n}\n\n.ItemIndicator {\n  grid-column-start: 3;\n}\n\n.ItemIndicatorIcon {\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.ItemCode {\n  grid-column-start: 4;\n  text-align: center;\n  font-size: 0.75rem;\n  color: var(--color-gray-600);\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/combobox/priority-combobox.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Combobox } from '@base-ui/react/combobox';\nimport styles from './priority-combobox.module.css';\n\nfunction CustomCombobox(props: { items: Priority[] }) {\n  return (\n    <Combobox.Root items={props.items} defaultValue={props.items?.[0]} autoHighlight>\n      <Combobox.Trigger className={styles.Trigger}>\n        <Combobox.Value>\n          {(priority: Priority) => (\n            <div className={styles.TriggerValue}>\n              {priority.icon}\n              <span>{priority.label ?? priority.value}</span>\n            </div>\n          )}\n        </Combobox.Value>\n      </Combobox.Trigger>\n      <Combobox.Portal>\n        <Combobox.Positioner align=\"start\" sideOffset={4} disableAnchorTracking={true}>\n          <Combobox.Popup className={styles.Popup} aria-label=\"Select priority\">\n            <div className={styles.InputRow}>\n              <Combobox.Input placeholder=\"Set priority to...\" className={styles.Input} />\n              <div className={styles.ShortcutKey}>P</div>\n            </div>\n            <Combobox.Separator className={styles.Separator} />\n            <Combobox.Empty className={styles.Empty}>No priority found.</Combobox.Empty>\n            <Combobox.List className={styles.List}>\n              {(priority: Priority) => (\n                <Combobox.Item key={priority.code} value={priority} className={styles.Item}>\n                  <div className={styles.ItemIcon}>{priority.icon}</div>\n                  <div className={styles.ItemText}>{priority.label ?? priority.value}</div>\n                  <Combobox.ItemIndicator className={styles.ItemIndicator}>\n                    <CheckIcon className={styles.ItemIndicatorIcon} />\n                  </Combobox.ItemIndicator>\n                  <div className={styles.ItemCode}>{priority.code}</div>\n                </Combobox.Item>\n              )}\n            </Combobox.List>\n          </Combobox.Popup>\n        </Combobox.Positioner>\n      </Combobox.Portal>\n    </Combobox.Root>\n  );\n}\n\nexport default function PriorityCombobox() {\n  return <CustomCombobox items={priorities} />;\n}\n\ninterface Priority {\n  code: string;\n  value: string | null;\n  icon: React.ReactNode;\n  label: string;\n}\n\nconst priorities: Priority[] = [\n  { code: '0', value: '0', icon: <MinusIcon />, label: 'No priority' },\n  { code: '1', value: '1', icon: <CircleAlertIcon />, label: 'Urgent' },\n  { code: '2', value: '2', icon: <SignalHighIcon />, label: 'High' },\n  { code: '3', value: '3', icon: <SignalMediumIcon />, label: 'Medium' },\n  { code: '4', value: '4', icon: <SignalLowIcon />, label: 'Low' },\n];\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\nfunction MinusIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"2\"\n      {...props}\n    >\n      <path d=\"M0 8H15\"></path>\n    </svg>\n  );\n}\n\nfunction CircleAlertIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <circle cx=\"12\" cy=\"12\" r=\"10\" />\n      <line x1=\"12\" x2=\"12\" y1=\"8\" y2=\"12\" />\n      <line x1=\"12\" x2=\"12.01\" y1=\"16\" y2=\"16\" />\n    </svg>\n  );\n}\n\nfunction SignalHighIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path strokeWidth=\"4\" d=\"M2 20h.01\" />\n      <path strokeWidth=\"4\" d=\"M8 20v-4\" />\n      <path strokeWidth=\"4\" d=\"M14 20v-8\" />\n      <path strokeWidth=\"4\" d=\"M20 20V8\" />\n    </svg>\n  );\n}\n\nfunction SignalMediumIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path strokeWidth=\"4\" d=\"M2 20h.01\" />\n      <path strokeWidth=\"4\" d=\"M8 20v-4\" />\n      <path strokeWidth=\"4\" d=\"M14 20v-8\" />\n      <path strokeWidth=\"4\" d=\"M20 20V8\" stroke=\"lightgrey\" />\n    </svg>\n  );\n}\n\nfunction SignalLowIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path strokeWidth=\"4\" d=\"M2 20h.01\" />\n      <path strokeWidth=\"4\" d=\"M8 20v-4\" />\n      <path strokeWidth=\"4\" d=\"M14 20v-8\" stroke=\"lightgrey\" />\n      <path strokeWidth=\"4\" d=\"M20 20V8\" stroke=\"lightgrey\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/combobox-composition.module.css",
    "content": ".Root {\n  padding: 1.5rem;\n}\n\n.Field {\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  gap: 0.25rem;\n  font-size: var(--text-sm);\n  line-height: var(--text-sm--line-height);\n  font-weight: 500;\n  color: var(--color-gray-900);\n}\n\n.Input {\n  height: 2.5rem;\n  width: 16rem;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background: canvas;\n  padding-left: 0.875rem;\n  font-size: var(--text-base);\n  line-height: var(--text-base--line-height);\n  font-weight: 400;\n  color: var(--color-gray-900);\n}\n\n.Input:focus {\n  outline: 2px solid var(--color-blue-800);\n  outline-offset: -1px;\n}\n\n.Actions {\n  position: absolute;\n  right: 0.5rem;\n  bottom: 0;\n  display: flex;\n  height: 2.5rem;\n  align-items: center;\n  justify-content: center;\n  color: var(--color-gray-600);\n}\n\n.ActionButton {\n  display: flex;\n  height: 2.5rem;\n  width: 1.5rem;\n  align-items: center;\n  justify-content: center;\n  border-radius: 0.25rem;\n  background: transparent;\n  padding: 0;\n}\n\n.ActionIcon {\n  width: 1rem;\n  height: 1rem;\n}\n\n.Positioner {\n  outline: none;\n}\n\n.Popup {\n  width: var(--anchor-width);\n  max-width: var(--available-width);\n  max-height: min(var(--available-height), 23rem);\n  overflow-y: auto;\n  overscroll-behavior: contain;\n  scroll-padding-top: 0.5rem;\n  scroll-padding-bottom: 0.5rem;\n  transform-origin: var(--transform-origin);\n  border-radius: 0.375rem;\n  background: canvas;\n  padding: 0.5rem 0;\n  color: var(--color-gray-900);\n  outline: 1px solid var(--color-gray-200);\n  box-shadow: var(--shadow-lg);\n  transition:\n    transform 150ms,\n    scale 150ms,\n    opacity 150ms;\n}\n\n.Popup[data-starting-style],\n.Popup[data-ending-style] {\n  scale: 0.95;\n  opacity: 0;\n}\n\n@media (prefers-color-scheme: dark) {\n  .Popup {\n    box-shadow: none;\n    outline-color: var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Empty {\n  padding: 0.5rem 1rem;\n  font-size: 0.925rem;\n  line-height: 1rem;\n  color: var(--color-gray-600);\n}\n\n.Empty:empty {\n  margin: 0;\n  padding: 0;\n}\n\n.Item {\n  position: relative;\n  z-index: 0;\n  display: grid;\n  grid-template-columns: 0.75rem 1fr;\n  align-items: center;\n  gap: 0.5rem;\n  cursor: default;\n  padding: 0.5rem 2rem 0.5rem 1rem;\n  font-size: 1rem;\n  line-height: 1rem;\n  outline: none;\n  user-select: none;\n}\n\n.Item[data-highlighted] {\n  color: var(--color-gray-50);\n}\n\n.Item[data-highlighted]::before {\n  content: '';\n  position: absolute;\n  inset: 0 0.5rem;\n  z-index: -1;\n  border-radius: 0.25rem;\n  background: var(--color-gray-900);\n}\n\n.ItemIndicator {\n  grid-column-start: 1;\n}\n\n.ItemIndicatorIcon {\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.ItemText {\n  grid-column-start: 2;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/combobox-composition.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Combobox } from '@base-ui/react/combobox';\nimport styles from './combobox-composition.module.css';\n\nexport default function ComboboxComposition() {\n  const id = React.useId();\n  const [inputValue, setInputValue] = React.useState('');\n  return (\n    <div className={styles.Root}>\n      <Combobox.Root items={fruitsKo} inputValue={inputValue} onInputValueChange={setInputValue}>\n        <div className={styles.Field}>\n          <label htmlFor={id}>과일을 선택하세요</label>\n          <Combobox.Input placeholder=\"예: 사과\" id={id} className={styles.Input} />\n          <div className={styles.Actions}>\n            <Combobox.Clear className={styles.ActionButton} aria-label=\"선택 지우기\">\n              <ClearIcon className={styles.ActionIcon} />\n            </Combobox.Clear>\n            <Combobox.Trigger className={styles.ActionButton} aria-label=\"팝업 열기\">\n              <ChevronDownIcon className={styles.ActionIcon} />\n            </Combobox.Trigger>\n          </div>\n        </div>\n\n        <Combobox.Portal>\n          <Combobox.Positioner className={styles.Positioner} sideOffset={4}>\n            <Combobox.Popup className={styles.Popup}>\n              <Combobox.Empty className={styles.Empty}>해당하는 과일이 없습니다.</Combobox.Empty>\n              <Combobox.List>\n                {(item: string) => (\n                  <Combobox.Item key={item} value={item} className={styles.Item}>\n                    <Combobox.ItemIndicator className={styles.ItemIndicator}>\n                      <CheckIcon className={styles.ItemIndicatorIcon} />\n                    </Combobox.ItemIndicator>\n                    <div className={styles.ItemText}>{item}</div>\n                  </Combobox.Item>\n                )}\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>\n    </div>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\nfunction ClearIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M18 6L6 18\" />\n      <path d=\"M6 6l12 12\" />\n    </svg>\n  );\n}\n\nfunction ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M6 9l6 6 6-6\" />\n    </svg>\n  );\n}\n\nconst fruitsKo = [\n  '사과',\n  '바나나',\n  '오렌지',\n  '파인애플',\n  '포도',\n  '망고',\n  '딸기',\n  '블루베리',\n  '라즈베리',\n  '블랙베리',\n  '체리',\n  '복숭아',\n  '배',\n  '자두',\n  '키위',\n  '수박',\n  '칸탈루프',\n  '허니듀',\n  '파파야',\n  '구아바',\n  '리치',\n  '석류',\n  '살구',\n  '자몽',\n  '패션프루트',\n];\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/combobox-perf.module.css",
    "content": ".Input {\n  box-sizing: border-box;\n  padding-left: 0.875rem;\n  margin: 0;\n  border: 1px solid var(--color-gray-200);\n  width: 16rem;\n  height: 2.5rem;\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n\n  &:focus {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Label {\n  display: flex;\n  flex-direction: column;\n  gap: 0.25rem;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 500;\n  color: var(--color-gray-900);\n  position: relative;\n}\n\n.ActionButtons {\n  --size: 1.5rem;\n  box-sizing: border-box;\n  position: absolute;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  bottom: 0;\n  height: 2.5rem;\n  right: 0.5rem;\n  border-radius: 0.25rem;\n  border: none;\n  color: var(--color-gray-600);\n  padding: 0;\n}\n\n.Trigger,\n.Clear {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: var(--size);\n  height: 2.5rem;\n  color: var(--color-gray-600);\n  border: none;\n  padding: 0;\n  border-radius: 0.25rem;\n  background: none;\n\n  &:focus {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.ClearIcon,\n.TriggerIcon {\n  width: 1rem;\n  height: 1rem;\n}\n\n.Positioner {\n  outline: 0;\n}\n\n.Popup {\n  box-sizing: border-box;\n  padding-block: 0.5rem;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  width: var(--anchor-width);\n  max-height: min(var(--available-height), 23rem);\n  max-width: var(--available-width);\n  overflow-y: auto;\n  scroll-padding-block: 0.5rem;\n  overscroll-behavior: contain;\n  transition:\n    opacity 0.1s,\n    transform 0.1s;\n  transform-origin: var(--transform-origin);\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.95);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Item {\n  box-sizing: border-box;\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  padding-block: 0.5rem;\n  padding-left: 1rem;\n  padding-right: 2rem;\n  display: grid;\n  gap: 0.5rem;\n  align-items: center;\n  grid-template-columns: 0.75rem 1fr;\n  font-size: 1rem;\n  line-height: 1rem;\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.5rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.ItemText {\n  grid-column-start: 2;\n}\n\n.ItemIndicator {\n  grid-column-start: 1;\n}\n\n.ItemIndicatorIcon {\n  display: block;\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.Empty:not(:empty) {\n  box-sizing: border-box;\n  font-size: 0.925rem;\n  line-height: 1rem;\n  color: var(--color-gray-600);\n  padding: 0.5rem 1rem;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/combobox-perf.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Combobox } from '@base-ui/react/combobox';\nimport styles from './combobox-perf.module.css';\n\nexport default function ExampleCombobox() {\n  return (\n    <div>\n      <h2>With manually specified index</h2>\n      <Combobox.Root items={fruits}>\n        <label className={styles.Label}>\n          Choose a fruit\n          <Combobox.Input placeholder=\"e.g. Apple\" className={styles.Input} />\n          <div className={styles.ActionButtons}>\n            <Combobox.Clear className={styles.Clear} aria-label=\"Clear selection\">\n              <ClearIcon className={styles.ClearIcon} />\n            </Combobox.Clear>\n            <Combobox.Trigger className={styles.Trigger} aria-label=\"Open popup\">\n              <ChevronDownIcon className={styles.TriggerIcon} />\n            </Combobox.Trigger>\n          </div>\n        </label>\n\n        <Combobox.Portal>\n          <Combobox.Positioner className={styles.Positioner} sideOffset={4}>\n            <Combobox.Popup className={styles.Popup}>\n              <Combobox.Empty className={styles.Empty}>No fruits found.</Combobox.Empty>\n              <Combobox.List className={styles.List}>\n                {(item: string, index: number) => (\n                  <Combobox.Item key={item} value={item} index={index} className={styles.Item}>\n                    <Combobox.ItemIndicator className={styles.ItemIndicator}>\n                      <CheckIcon className={styles.ItemIndicatorIcon} />\n                    </Combobox.ItemIndicator>\n                    <div className={styles.ItemText}>{item}</div>\n                  </Combobox.Item>\n                )}\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>\n\n      <hr className=\"bui-my-4\" />\n\n      <h2>Without manually specified index</h2>\n      <Combobox.Root items={fruits}>\n        <label className={styles.Label}>\n          Choose a fruit\n          <Combobox.Input placeholder=\"e.g. Apple\" className={styles.Input} />\n          <div className={styles.ActionButtons}>\n            <Combobox.Clear className={styles.Clear} aria-label=\"Clear selection\">\n              <ClearIcon className={styles.ClearIcon} />\n            </Combobox.Clear>\n            <Combobox.Trigger className={styles.Trigger} aria-label=\"Open popup\">\n              <ChevronDownIcon className={styles.TriggerIcon} />\n            </Combobox.Trigger>\n          </div>\n        </label>\n\n        <Combobox.Portal>\n          <Combobox.Positioner className={styles.Positioner} sideOffset={4}>\n            <Combobox.Popup className={styles.Popup}>\n              <Combobox.Empty className={styles.Empty}>No fruits found.</Combobox.Empty>\n              <Combobox.List className={styles.List}>\n                {(item: string) => (\n                  <Combobox.Item key={item} value={item} className={styles.Item}>\n                    <Combobox.ItemIndicator className={styles.ItemIndicator}>\n                      <CheckIcon className={styles.ItemIndicatorIcon} />\n                    </Combobox.ItemIndicator>\n                    <div className={styles.ItemText}>{item}</div>\n                  </Combobox.Item>\n                )}\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>\n\n      <hr className=\"bui-my-4\" />\n\n      <h2>Open API (no filtering)</h2>\n      <Combobox.Root>\n        <label className={styles.Label}>\n          Choose a fruit\n          <Combobox.Input placeholder=\"e.g. Apple\" className={styles.Input} />\n          <div className={styles.ActionButtons}>\n            <Combobox.Clear className={styles.Clear} aria-label=\"Clear selection\">\n              <ClearIcon className={styles.ClearIcon} />\n            </Combobox.Clear>\n            <Combobox.Trigger className={styles.Trigger} aria-label=\"Open popup\">\n              <ChevronDownIcon className={styles.TriggerIcon} />\n            </Combobox.Trigger>\n          </div>\n        </label>\n\n        <Combobox.Portal>\n          <Combobox.Positioner className={styles.Positioner} sideOffset={4}>\n            <Combobox.Popup className={styles.Popup}>\n              <Combobox.List className={styles.List}>\n                {fruits.map((item) => (\n                  <Combobox.Item key={item} value={item} className={styles.Item}>\n                    <Combobox.ItemIndicator className={styles.ItemIndicator}>\n                      <CheckIcon className={styles.ItemIndicatorIcon} />\n                    </Combobox.ItemIndicator>\n                    <div className={styles.ItemText}>{item}</div>\n                  </Combobox.Item>\n                ))}\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>\n    </div>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\nfunction ClearIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M18 6L6 18\" />\n      <path d=\"M6 6l12 12\" />\n    </svg>\n  );\n}\n\nfunction ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M6 9l6 6 6-6\" />\n    </svg>\n  );\n}\n\nconst fruits = Array.from({ length: 1000 }, (_, i) => `Item ${i + 1}`);\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/context-menu.module.css",
    "content": ".Root {\n  padding: 2rem;\n}\n\n.Title {\n  margin: 0;\n  font-size: 2rem;\n  line-height: 2.5rem;\n  font-weight: 500;\n  letter-spacing: var(--text-2xl--letter-spacing);\n}\n\n.Section {\n  margin: 0;\n  margin-bottom: 3rem;\n}\n\n.Section:last-of-type {\n  margin-bottom: 0;\n}\n\n.SectionTitle {\n  margin: 0 0 1rem;\n  font-size: var(--text-xl);\n  line-height: var(--text-xl--line-height);\n  letter-spacing: var(--text-xl--letter-spacing);\n  font-weight: 600;\n}\n\n.Description {\n  margin: 0 0 1rem;\n  color: var(--color-gray-600);\n}\n\n.Positioner {\n  outline: none;\n}\n\n.Popup180,\n.Popup160,\n.Popup220 {\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background: white;\n  padding: 0.5rem 0;\n  box-shadow: var(--shadow-lg);\n}\n\n.Popup180 {\n  min-width: 180px;\n}\n\n.Popup160 {\n  min-width: 160px;\n}\n\n.Popup220 {\n  min-width: 220px;\n}\n\n.Item {\n  cursor: default;\n  padding: 0.5rem 0.75rem;\n  font-size: var(--text-sm);\n}\n\n.Item:hover {\n  background: var(--color-gray-100);\n}\n\n.ItemDanger {\n  color: var(--color-red-600);\n}\n\n.SubmenuTrigger {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  cursor: default;\n  padding: 0.5rem 0.75rem;\n  font-size: var(--text-sm);\n}\n\n.SubmenuTrigger:hover {\n  background: var(--color-gray-100);\n}\n\n.Separator {\n  height: 1px;\n  margin: 0.25rem 0;\n  background: var(--color-gray-200);\n}\n\n.ContextBox {\n  border: 2px solid;\n  border-radius: 0.5rem;\n  padding: 2rem;\n}\n\n.ContextBoxInner {\n  display: inline-block;\n  margin-top: 1rem;\n  border: 2px solid;\n  border-radius: 0.25rem;\n  padding: 1rem;\n}\n\n.ContextBoxCenter {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  margin-top: 1rem;\n}\n\n.Blue {\n  border-color: var(--color-blue-300);\n  background: var(--color-blue-50);\n}\n\n.Red {\n  border-color: var(--color-red-300);\n  background: var(--color-red-50);\n}\n\n.Green {\n  border-color: var(--color-green-300);\n  background: var(--color-green-50);\n}\n\n.Purple {\n  border-color: var(--color-purple-300);\n  background: var(--color-purple-50);\n}\n\n.Orange {\n  border-color: var(--color-orange-300);\n  background: var(--color-orange-50);\n}\n\n.Cyan {\n  border-color: var(--color-cyan-300);\n  background: var(--color-cyan-50);\n}\n\n.Indigo {\n  border-color: var(--color-indigo-300);\n  background: var(--color-indigo-50);\n}\n\n.CenterText {\n  text-align: center;\n}\n\n.LabelPrimary {\n  display: block;\n  font-weight: 500;\n}\n\n.LabelSecondary {\n  display: block;\n  font-size: var(--text-sm);\n  line-height: var(--text-sm--line-height);\n  letter-spacing: var(--text-sm--letter-spacing);\n}\n\n.BluePrimary {\n  color: var(--color-blue-700);\n}\n\n.BlueSecondary {\n  color: var(--color-blue-600);\n}\n\n.RedPrimary {\n  color: var(--color-red-700);\n}\n\n.RedSecondary {\n  color: var(--color-red-600);\n}\n\n.GreenPrimary {\n  color: var(--color-green-700);\n}\n\n.GreenSecondary {\n  color: var(--color-green-600);\n}\n\n.PurplePrimary {\n  color: var(--color-purple-700);\n}\n\n.PurpleSecondary {\n  color: var(--color-purple-600);\n}\n\n.OrangePrimary {\n  color: var(--color-orange-700);\n}\n\n.OrangeSecondary {\n  color: var(--color-orange-600);\n}\n\n.CyanPrimary {\n  color: var(--color-cyan-700);\n}\n\n.CyanSecondary {\n  color: var(--color-cyan-600);\n}\n\n.PopoverTrigger {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-top: 0.5rem;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background: var(--color-gray-50);\n  padding: 1rem;\n  color: var(--color-gray-900);\n  user-select: none;\n}\n\n.PopoverTrigger:hover,\n.PopoverTrigger:active,\n.PopoverTrigger[data-popup-open] {\n  background: var(--color-gray-100);\n}\n\n.PopoverTrigger:focus-visible {\n  outline: 2px solid var(--color-blue-800);\n  outline-offset: -1px;\n}\n\n.PopoverPopup {\n  transform-origin: var(--transform-origin);\n  border-radius: 0.5rem;\n  background: canvas;\n  padding: 1rem 1.5rem;\n  color: var(--color-gray-900);\n  outline: 1px solid var(--color-gray-200);\n  box-shadow: var(--shadow-lg);\n  transition:\n    transform 150ms,\n    scale 150ms,\n    opacity 150ms;\n}\n\n.PopoverPopup[data-starting-style],\n.PopoverPopup[data-ending-style] {\n  scale: 0.9;\n  opacity: 0;\n}\n\n.PopoverPopupBody {\n  display: flex;\n  flex-direction: column;\n  gap: 0.5rem;\n  font-size: var(--text-base);\n  color: var(--color-gray-600);\n}\n\n.MenuTrigger {\n  display: inline-flex;\n  align-items: center;\n  gap: 0.5rem;\n  cursor: default;\n  border: 1px solid var(--color-gray-300);\n  border-radius: 0.375rem;\n  padding: 0.375rem 0.75rem;\n  font-size: var(--text-sm);\n  color: var(--color-gray-800);\n}\n\n.MenuTrigger:hover {\n  background: var(--color-gray-50);\n}\n\n.InlineContextTrigger {\n  display: inline-block;\n  margin-top: 1rem;\n  border: 2px solid var(--color-red-300);\n  border-radius: 0.375rem;\n  background: var(--color-red-50);\n  padding: 1rem;\n}\n\n.DisabledToggle {\n  display: flex;\n  align-items: center;\n  gap: 0.5rem;\n  cursor: pointer;\n  margin-bottom: 1rem;\n}\n\n.DisabledCheckbox {\n  width: 1rem;\n  height: 1rem;\n  border: 1px solid var(--color-gray-300);\n  border-radius: 0.25rem;\n}\n\n.DisabledLabel {\n  font-size: var(--text-sm);\n  line-height: var(--text-sm--line-height);\n  letter-spacing: var(--text-sm--letter-spacing);\n  color: var(--color-gray-700);\n}\n\n.DisabledTextActive {\n  color: var(--color-indigo-700);\n}\n\n.DisabledHintActive {\n  color: var(--color-indigo-600);\n}\n\n.DisabledTextMuted {\n  color: var(--color-gray-500);\n}\n\n.DisabledHintMuted {\n  color: var(--color-gray-400);\n}\n\n.SpecialTrigger {\n  display: block;\n  width: 100%;\n  cursor: default;\n  padding: 0.5rem 0.75rem;\n  text-align: left;\n  font-size: var(--text-sm);\n}\n\n.SpecialTrigger:hover {\n  background: var(--color-gray-100);\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/context-menu.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport clsx from 'clsx';\nimport { ContextMenu } from '@base-ui/react/context-menu';\nimport { Menu } from '@base-ui/react/menu';\nimport { Popover } from '@base-ui/react/popover';\nimport styles from './context-menu.module.css';\n\nexport default function ContextMenuExperiment() {\n  const [disabled, setDisabled] = React.useState(false);\n\n  return (\n    <div className={styles.Root}>\n      <h1 className={styles.Title}>Context Menu Experiments</h1>\n\n      {/* Scenario 1: Context menu within a context menu trigger */}\n      <section className={styles.Section}>\n        <h2 className={styles.SectionTitle}>1. Context Menu within Context Menu Trigger</h2>\n        <p className={styles.Description}>\n          Right-click on the outer box, then right-click on the inner box to see nested context\n          menus.\n        </p>\n\n        <ContextMenu.Root>\n          <ContextMenu.Trigger className={clsx(styles.ContextBox, styles.Blue)}>\n            <div className={styles.CenterText}>\n              <span className={clsx(styles.LabelPrimary, styles.BluePrimary)}>\n                Outer Context Menu\n              </span>\n              <span className={clsx(styles.LabelSecondary, styles.BlueSecondary)}>\n                Right-click me\n              </span>\n\n              <ContextMenu.Root>\n                <ContextMenu.Trigger className={clsx(styles.ContextBoxInner, styles.Red)}>\n                  <span className={clsx(styles.LabelPrimary, styles.RedPrimary)}>\n                    Inner Context Menu\n                  </span>\n                  <span className={clsx(styles.LabelSecondary, styles.RedSecondary)}>\n                    Right-click me too!\n                  </span>\n                </ContextMenu.Trigger>\n                <ContextMenu.Portal>\n                  <ContextMenu.Positioner className={styles.Positioner}>\n                    <ContextMenu.Popup className={styles.Popup180}>\n                      <ContextMenu.Item className={styles.Item}>Inner Action 1</ContextMenu.Item>\n                      <ContextMenu.Item className={styles.Item}>Inner Action 2</ContextMenu.Item>\n                      <ContextMenu.Separator className={styles.Separator} />\n                      <ContextMenu.Item className={styles.Item}>Inner Delete</ContextMenu.Item>\n                    </ContextMenu.Popup>\n                  </ContextMenu.Positioner>\n                </ContextMenu.Portal>\n              </ContextMenu.Root>\n            </div>\n          </ContextMenu.Trigger>\n          <ContextMenu.Portal>\n            <ContextMenu.Positioner className={styles.Positioner}>\n              <ContextMenu.Popup className={styles.Popup180}>\n                <ContextMenu.Item className={styles.Item}>Outer Action 1</ContextMenu.Item>\n                <ContextMenu.Item className={styles.Item}>Outer Action 2</ContextMenu.Item>\n                <ContextMenu.Separator className={styles.Separator} />\n                <ContextMenu.Item className={styles.Item}>Outer Delete</ContextMenu.Item>\n              </ContextMenu.Popup>\n            </ContextMenu.Positioner>\n          </ContextMenu.Portal>\n        </ContextMenu.Root>\n      </section>\n\n      {/* Scenario 2: Context menu within context menu trigger, both with submenus */}\n      <section className={styles.Section}>\n        <h2 className={styles.SectionTitle}>2. Nested Context Menus with Submenus</h2>\n        <p className={styles.Description}>\n          Right-click on either box and explore the submenu options.\n        </p>\n\n        <ContextMenu.Root>\n          <ContextMenu.Trigger className={clsx(styles.ContextBox, styles.Green)}>\n            <div className={styles.CenterText}>\n              <span className={clsx(styles.LabelPrimary, styles.GreenPrimary)}>\n                Outer Context Menu with Submenu\n              </span>\n              <span className={clsx(styles.LabelSecondary, styles.GreenSecondary)}>\n                Right-click me\n              </span>\n\n              <ContextMenu.Root>\n                <ContextMenu.Trigger className={clsx(styles.ContextBoxInner, styles.Purple)}>\n                  <span className={clsx(styles.LabelPrimary, styles.PurplePrimary)}>\n                    Inner Context Menu with Submenu\n                  </span>\n                  <span className={clsx(styles.LabelSecondary, styles.PurpleSecondary)}>\n                    Right-click me too!\n                  </span>\n                </ContextMenu.Trigger>\n                <ContextMenu.Portal>\n                  <ContextMenu.Positioner className={styles.Positioner}>\n                    <ContextMenu.Popup className={styles.Popup180}>\n                      <ContextMenu.Item className={styles.Item}>\n                        Inner Basic Action\n                      </ContextMenu.Item>\n\n                      <Menu.SubmenuRoot>\n                        <Menu.SubmenuTrigger className={styles.SubmenuTrigger}>\n                          Inner Submenu\n                          <ChevronRightIcon />\n                        </Menu.SubmenuTrigger>\n                        <Menu.Portal>\n                          <Menu.Positioner className={styles.Positioner} sideOffset={4}>\n                            <Menu.Popup className={styles.Popup160}>\n                              <Menu.Item className={styles.Item}>Inner Sub Action 1</Menu.Item>\n                              <Menu.Item className={styles.Item}>Inner Sub Action 2</Menu.Item>\n                            </Menu.Popup>\n                          </Menu.Positioner>\n                        </Menu.Portal>\n                      </Menu.SubmenuRoot>\n\n                      <ContextMenu.Separator className={styles.Separator} />\n                      <ContextMenu.Item className={styles.Item}>Inner Delete</ContextMenu.Item>\n                    </ContextMenu.Popup>\n                  </ContextMenu.Positioner>\n                </ContextMenu.Portal>\n              </ContextMenu.Root>\n            </div>\n          </ContextMenu.Trigger>\n          <ContextMenu.Portal>\n            <ContextMenu.Positioner className={styles.Positioner}>\n              <ContextMenu.Popup className={styles.Popup180}>\n                <ContextMenu.Item className={styles.Item}>Outer Basic Action</ContextMenu.Item>\n\n                <Menu.SubmenuRoot>\n                  <Menu.SubmenuTrigger className={styles.SubmenuTrigger}>\n                    Outer Submenu\n                    <ChevronRightIcon />\n                  </Menu.SubmenuTrigger>\n                  <Menu.Portal>\n                    <Menu.Positioner className={styles.Positioner} sideOffset={4}>\n                      <Menu.Popup className={styles.Popup160}>\n                        <Menu.Item className={styles.Item}>Outer Sub Action 1</Menu.Item>\n                        <Menu.Item className={styles.Item}>Outer Sub Action 2</Menu.Item>\n                      </Menu.Popup>\n                    </Menu.Positioner>\n                  </Menu.Portal>\n                </Menu.SubmenuRoot>\n\n                <ContextMenu.Separator className={styles.Separator} />\n                <ContextMenu.Item className={styles.Item}>Outer Delete</ContextMenu.Item>\n              </ContextMenu.Popup>\n            </ContextMenu.Positioner>\n          </ContextMenu.Portal>\n        </ContextMenu.Root>\n      </section>\n\n      {/* Scenario 3: Context menu within context menu popup item, both with submenus */}\n      <section className={styles.Section}>\n        <h2 className={styles.SectionTitle}>3. Context Menu within Context Menu Popup Item</h2>\n        <p className={styles.Description}>\n          Right-click on the box, then right-click on the \"Special Item\" in the popup.\n        </p>\n\n        <ContextMenu.Root>\n          <ContextMenu.Trigger className={clsx(styles.ContextBox, styles.Orange)}>\n            <div className={styles.CenterText}>\n              <span className={clsx(styles.LabelPrimary, styles.OrangePrimary)}>\n                Complex Context Menu\n              </span>\n              <span className={clsx(styles.LabelSecondary, styles.OrangeSecondary)}>\n                Right-click me, then right-click \"Special Item\"\n              </span>\n            </div>\n          </ContextMenu.Trigger>\n          <ContextMenu.Portal>\n            <ContextMenu.Positioner className={styles.Positioner}>\n              <ContextMenu.Popup className={styles.Popup180}>\n                <ContextMenu.Item className={styles.Item}>Regular Action</ContextMenu.Item>\n\n                <Menu.SubmenuRoot>\n                  <Menu.SubmenuTrigger className={styles.SubmenuTrigger}>\n                    Main Submenu\n                    <ChevronRightIcon />\n                  </Menu.SubmenuTrigger>\n                  <Menu.Portal>\n                    <Menu.Positioner className={styles.Positioner} sideOffset={4}>\n                      <Menu.Popup className={styles.Popup160}>\n                        <Menu.Item className={styles.Item}>Sub Action 1</Menu.Item>\n                        <Menu.Item className={styles.Item}>Sub Action 2</Menu.Item>\n                      </Menu.Popup>\n                    </Menu.Positioner>\n                  </Menu.Portal>\n                </Menu.SubmenuRoot>\n\n                <ContextMenu.Separator className={styles.Separator} />\n\n                {/* Context menu within popup item */}\n                <ContextMenu.Root>\n                  <ContextMenu.Trigger className={styles.SpecialTrigger}>\n                    Special Item (right-click me!)\n                  </ContextMenu.Trigger>\n                  <ContextMenu.Portal>\n                    <ContextMenu.Positioner className={styles.Positioner}>\n                      <ContextMenu.Popup className={styles.Popup180}>\n                        <ContextMenu.Item className={styles.Item}>Nested Action 1</ContextMenu.Item>\n\n                        <Menu.SubmenuRoot>\n                          <Menu.SubmenuTrigger className={styles.SubmenuTrigger}>\n                            Nested Submenu\n                            <ChevronRightIcon />\n                          </Menu.SubmenuTrigger>\n                          <Menu.Portal>\n                            <Menu.Positioner className={styles.Positioner} sideOffset={4}>\n                              <Menu.Popup className={styles.Popup160}>\n                                <Menu.Item className={styles.Item}>Deep Action 1</Menu.Item>\n                                <Menu.Item className={styles.Item}>Deep Action 2</Menu.Item>\n                                <Menu.Separator className={styles.Separator} />\n                                <Menu.Item className={clsx(styles.Item, styles.ItemDanger)}>\n                                  Deep Delete\n                                </Menu.Item>\n                              </Menu.Popup>\n                            </Menu.Positioner>\n                          </Menu.Portal>\n                        </Menu.SubmenuRoot>\n\n                        <ContextMenu.Separator className={styles.Separator} />\n                        <ContextMenu.Item className={clsx(styles.Item, styles.ItemDanger)}>\n                          Nested Delete\n                        </ContextMenu.Item>\n                      </ContextMenu.Popup>\n                    </ContextMenu.Positioner>\n                  </ContextMenu.Portal>\n                </ContextMenu.Root>\n\n                <ContextMenu.Separator className={styles.Separator} />\n                <ContextMenu.Item className={clsx(styles.Item, styles.ItemDanger)}>\n                  Delete\n                </ContextMenu.Item>\n              </ContextMenu.Popup>\n            </ContextMenu.Positioner>\n          </ContextMenu.Portal>\n        </ContextMenu.Root>\n      </section>\n\n      {/* Scenario 4: [FINAL BOSS ⚔️] Nested context menu with a menu inside */}\n      <section className={styles.Section}>\n        <h2 className={styles.SectionTitle}>\n          4. [FINAL BOSS ⚔️] Nested context menus, popover with a menu and a context menu inside\n        </h2>\n        <p className={styles.Description}>\n          Right-click the outer box, then right-click the inner box. Inside the inner context menu,\n          open the popover, then open the menu inside the popover, then right-click the context menu\n          inside the popover.\n        </p>\n\n        <ContextMenu.Root>\n          <ContextMenu.Trigger className={clsx(styles.ContextBox, styles.Cyan)}>\n            <div className={styles.CenterText}>\n              <span className={clsx(styles.LabelPrimary, styles.CyanPrimary)}>\n                Outer Context Menu\n              </span>\n              <span className={clsx(styles.LabelSecondary, styles.CyanSecondary)}>\n                Right-click me\n              </span>\n\n              <ContextMenu.Root>\n                <ContextMenu.Trigger\n                  className={clsx(styles.ContextBox, styles.Cyan, styles.ContextBoxCenter)}\n                >\n                  <div className={styles.CenterText}>\n                    <span className={clsx(styles.LabelPrimary, styles.CyanPrimary)}>\n                      Inner Context Menu\n                    </span>\n                    <span className={clsx(styles.LabelSecondary, styles.CyanSecondary)}>\n                      Right-click me\n                    </span>\n\n                    <Popover.Root>\n                      <Popover.Trigger className={styles.PopoverTrigger}>\n                        Open popover\n                      </Popover.Trigger>\n                      <Popover.Portal>\n                        <Popover.Positioner sideOffset={8}>\n                          <Popover.Popup className={styles.PopoverPopup}>\n                            <div className={styles.PopoverPopupBody}>\n                              <Menu.Root>\n                                <Menu.Trigger className={styles.MenuTrigger}>\n                                  Open Actions Menu\n                                </Menu.Trigger>\n                                <Menu.Portal>\n                                  <Menu.Positioner className={styles.Positioner} side=\"top\">\n                                    <Menu.Popup className={styles.Popup180}>\n                                      <Menu.Item className={styles.Item}>Menu Action</Menu.Item>\n                                    </Menu.Popup>\n                                  </Menu.Positioner>\n                                </Menu.Portal>\n                              </Menu.Root>\n\n                              <ContextMenu.Root>\n                                <ContextMenu.Trigger className={styles.InlineContextTrigger}>\n                                  <span className={clsx(styles.LabelPrimary, styles.RedPrimary)}>\n                                    Popover Context Menu\n                                  </span>\n                                  <span\n                                    className={clsx(styles.LabelSecondary, styles.RedSecondary)}\n                                  >\n                                    Right-click me!\n                                  </span>\n                                </ContextMenu.Trigger>\n                                <ContextMenu.Portal>\n                                  <ContextMenu.Positioner className={styles.Positioner}>\n                                    <ContextMenu.Popup className={styles.Popup180}>\n                                      <ContextMenu.Item className={styles.Item}>\n                                        Popover context Action 1\n                                      </ContextMenu.Item>\n                                    </ContextMenu.Popup>\n                                  </ContextMenu.Positioner>\n                                </ContextMenu.Portal>\n                              </ContextMenu.Root>\n                            </div>\n                          </Popover.Popup>\n                        </Popover.Positioner>\n                      </Popover.Portal>\n                    </Popover.Root>\n                  </div>\n                </ContextMenu.Trigger>\n                <ContextMenu.Portal>\n                  <ContextMenu.Positioner className={styles.Positioner}>\n                    <ContextMenu.Popup className={styles.Popup220}>\n                      <ContextMenu.Item className={styles.Item}>Inner Action</ContextMenu.Item>\n                    </ContextMenu.Popup>\n                  </ContextMenu.Positioner>\n                </ContextMenu.Portal>\n              </ContextMenu.Root>\n            </div>\n          </ContextMenu.Trigger>\n          <ContextMenu.Portal>\n            <ContextMenu.Positioner className={styles.Positioner}>\n              <ContextMenu.Popup className={styles.Popup220}>\n                <ContextMenu.Item className={styles.Item}>Outer Action</ContextMenu.Item>\n              </ContextMenu.Popup>\n            </ContextMenu.Positioner>\n          </ContextMenu.Portal>\n        </ContextMenu.Root>\n      </section>\n\n      {/* Scenario 5: Disabled context menu */}\n      <section className={styles.Section}>\n        <h2 className={styles.SectionTitle}>5. Disabled Context Menu</h2>\n        <p className={styles.Description}>\n          Toggle the checkbox to disable the context menu. When disabled, right-clicking will not\n          open the menu.\n        </p>\n\n        <label className={styles.DisabledToggle}>\n          <input\n            type=\"checkbox\"\n            checked={disabled}\n            onChange={(event) => setDisabled(event.target.checked)}\n            className={styles.DisabledCheckbox}\n          />\n          <span className={styles.DisabledLabel}>Disable context menu</span>\n        </label>\n\n        <ContextMenu.Root disabled={disabled}>\n          <ContextMenu.Trigger className={clsx(styles.ContextBox, styles.Indigo)}>\n            <div className={styles.CenterText}>\n              <span\n                className={clsx(\n                  styles.LabelPrimary,\n                  disabled ? styles.DisabledTextMuted : styles.DisabledTextActive,\n                )}\n              >\n                Right-click here\n              </span>\n              <span\n                className={clsx(\n                  styles.LabelSecondary,\n                  disabled ? styles.DisabledHintMuted : styles.DisabledHintActive,\n                )}\n              >\n                {disabled ? 'Context menu is disabled' : 'Context menu will open on right-click'}\n              </span>\n            </div>\n          </ContextMenu.Trigger>\n          <ContextMenu.Portal>\n            <ContextMenu.Positioner className={styles.Positioner}>\n              <ContextMenu.Popup className={styles.Popup180}>\n                <ContextMenu.Item className={styles.Item}>Cut</ContextMenu.Item>\n                <ContextMenu.Item className={styles.Item}>Copy</ContextMenu.Item>\n                <ContextMenu.Item className={styles.Item}>Paste</ContextMenu.Item>\n                <ContextMenu.Separator className={styles.Separator} />\n                <ContextMenu.Item className={styles.Item}>Select All</ContextMenu.Item>\n                <ContextMenu.Separator className={styles.Separator} />\n                <ContextMenu.Item className={clsx(styles.Item, styles.ItemDanger)}>\n                  Delete\n                </ContextMenu.Item>\n              </ContextMenu.Popup>\n            </ContextMenu.Positioner>\n          </ContextMenu.Portal>\n        </ContextMenu.Root>\n      </section>\n    </div>\n  );\n}\n\nfunction ChevronRightIcon() {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\">\n      <path d=\"M3.5 9L7.5 5L3.5 1\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/dialog/dialog.module.css",
    "content": ".Page {\n  display: flex;\n  flex-direction: column;\n  gap: 1.5rem;\n  padding-bottom: 30vh;\n  max-width: 650px;\n\n  h2 {\n    margin-bottom: 0.75rem;\n  }\n}\n\n.Container {\n  display: flex;\n  flex-wrap: wrap;\n  align-items: center;\n  gap: 0.75rem;\n  margin-bottom: 32px;\n}\n\n.Button {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  white-space: nowrap;\n}\n\n.DialogSection {\n  display: flex;\n  flex-direction: column;\n  gap: 0.75rem;\n  margin: 1rem 0;\n  font-size: 0.95rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-700);\n\n  &:last-child {\n    margin-bottom: 0;\n  }\n}\n\n.DialogSection + .DialogSection {\n  padding-top: 1rem;\n  border-top: 1px solid var(--color-gray-200);\n}\n\n.Actions {\n  margin-top: 1.5rem;\n}\n\n.Form {\n  display: flex;\n  flex-direction: column;\n  gap: 0.75rem;\n  align-items: flex-start;\n}\n\n.Label {\n  display: flex;\n  flex-direction: column;\n  gap: 0.5rem;\n  font-weight: 500;\n  color: var(--color-gray-900);\n}\n\n.Input {\n  width: 100%;\n  height: 2.5rem;\n  padding: 0 0.75rem;\n  border-radius: 0.375rem;\n  border: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-50);\n  font: inherit;\n  color: inherit;\n  transition:\n    border-color 150ms ease,\n    outline 150ms ease;\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: 1px;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/dialog/dialogs.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport clsx from 'clsx';\nimport { Dialog } from '@base-ui/react/dialog';\nimport demoStyles from 'docs/src/app/(docs)/react/components/dialog/demos/hero/css-modules/index.module.css';\nimport { SettingsMetadata, useExperimentSettings } from '../_components/SettingsPanel';\nimport styles from './dialog.module.css';\n\ninterface Settings {\n  modal: boolean;\n  disablePointerDismissal: boolean;\n  keepMounted: boolean;\n  renderBackdrop?: boolean;\n}\n\nconst dialogContents = {\n  Profile: (\n    <React.Fragment>\n      <p>Manage profile information and preferences from a focused workspace.</p>\n      <p>Changes apply immediately once you save them, so review carefully before closing.</p>\n    </React.Fragment>\n  ),\n  Invites: (\n    <React.Fragment>\n      <p>Invite teammates to access the latest project artifacts and timelines.</p>\n      <p>Decide who can edit, comment, or just follow progress from the audience controls.</p>\n    </React.Fragment>\n  ),\n  Tasks: <p>Review the highest priority tasks assigned to you this week and adjust due dates.</p>,\n  Notifications: (\n    <React.Fragment>\n      <p>Fine tune which notifications should reach your inbox versus stay muted.</p>\n      <p>Turn off sources that create noise while keeping critical alerts ready.</p>\n    </React.Fragment>\n  ),\n};\n\nconst triggerLabels = Object.keys(dialogContents) as (keyof typeof dialogContents)[];\n\nconst detachedDialog = Dialog.createHandle<keyof typeof dialogContents>();\nconst controlledDetachedDialog = Dialog.createHandle<keyof typeof dialogContents>();\nconst handleControlledDialog = Dialog.createHandle<keyof typeof dialogContents>();\n\nexport default function DialogExperiment() {\n  const { settings } = useExperimentSettings<Settings>();\n\n  const [singleTriggerOpen, setSingleTriggerOpen] = React.useState(false);\n\n  const [controlledWithinRootOpen, setControlledWithinRootOpen] = React.useState(false);\n  const [controlledWithinRootTriggerId, setControlledWithinRootTriggerId] = React.useState<\n    string | null\n  >(null);\n\n  const [controlledDetachedOpen, setControlledDetachedOpen] = React.useState(false);\n  const [controlledDetachedTriggerId, setControlledDetachedTriggerId] = React.useState<\n    string | null\n  >(null);\n\n  return (\n    <div className={styles.Page}>\n      <h1>Dialogs</h1>\n\n      <h2>Uncontrolled, single trigger</h2>\n      <div className={styles.Container}>\n        <Dialog.Root\n          modal={settings.modal}\n          disablePointerDismissal={settings.disablePointerDismissal}\n        >\n          <StyledTrigger>Invites</StyledTrigger>\n          {renderDialogContent('Invites', settings)}\n        </Dialog.Root>\n      </div>\n\n      <h2>Controlled, single trigger</h2>\n      <div className={styles.Container}>\n        <Dialog.Root\n          modal={settings.modal}\n          disablePointerDismissal={settings.disablePointerDismissal}\n          open={singleTriggerOpen}\n          onOpenChange={(nextOpen) => setSingleTriggerOpen(nextOpen)}\n        >\n          <StyledTrigger>Profile</StyledTrigger>\n          {renderDialogContent('Profile', settings)}\n        </Dialog.Root>\n        <button\n          type=\"button\"\n          className={clsx(demoStyles.Button, styles.Button)}\n          onClick={() => setSingleTriggerOpen(true)}\n        >\n          Open programmatically\n        </button>\n      </div>\n\n      <h2>Uncontrolled, multiple triggers within Root</h2>\n      <div className={styles.Container}>\n        <Dialog.Root\n          modal={settings.modal}\n          disablePointerDismissal={settings.disablePointerDismissal}\n        >\n          {({ payload }) => (\n            <React.Fragment>\n              <StyledTrigger payload={triggerLabels[0]}>{triggerLabels[0]}</StyledTrigger>\n              <StyledTrigger payload={triggerLabels[1]}>{triggerLabels[1]}</StyledTrigger>\n              <StyledTrigger payload={triggerLabels[2]}>{triggerLabels[2]}</StyledTrigger>\n              {renderDialogContent(payload as keyof typeof dialogContents, settings)}\n            </React.Fragment>\n          )}\n        </Dialog.Root>\n      </div>\n\n      <h2>Controlled, multiple triggers within Root</h2>\n      <div className={styles.Container}>\n        <Dialog.Root\n          modal={settings.modal}\n          disablePointerDismissal={settings.disablePointerDismissal}\n          open={controlledWithinRootOpen}\n          onOpenChange={(nextOpen, eventDetails) => {\n            setControlledWithinRootOpen(nextOpen);\n            setControlledWithinRootTriggerId(eventDetails.trigger?.id ?? null);\n          }}\n          triggerId={controlledWithinRootTriggerId}\n        >\n          {({ payload }) => (\n            <React.Fragment>\n              <StyledTrigger payload={triggerLabels[0]}>{triggerLabels[0]}</StyledTrigger>\n              <StyledTrigger payload={triggerLabels[1]} id=\"within-root-second-trigger\">\n                {triggerLabels[1]}\n              </StyledTrigger>\n              <StyledTrigger payload={triggerLabels[2]}>{triggerLabels[2]}</StyledTrigger>\n              {renderDialogContent(payload as keyof typeof dialogContents, settings)}\n            </React.Fragment>\n          )}\n        </Dialog.Root>\n        <button\n          type=\"button\"\n          className={clsx(demoStyles.Button, styles.Button)}\n          onClick={() => {\n            setControlledWithinRootOpen(true);\n            setControlledWithinRootTriggerId('within-root-second-trigger');\n          }}\n        >\n          Open programmatically (Invites)\n        </button>\n      </div>\n\n      <h2>Uncontrolled, detached triggers</h2>\n      <div className={styles.Container}>\n        <StyledDialog handle={detachedDialog} />\n        <StyledTrigger handle={detachedDialog} payload={triggerLabels[0]}>\n          {triggerLabels[0]}\n        </StyledTrigger>\n        <StyledTrigger handle={detachedDialog} payload={triggerLabels[1]}>\n          {triggerLabels[1]}\n        </StyledTrigger>\n        <StyledTrigger handle={detachedDialog} payload={triggerLabels[2]}>\n          {triggerLabels[2]}\n        </StyledTrigger>\n        <StyledTrigger handle={detachedDialog} payload={triggerLabels[3]}>\n          {triggerLabels[3]}\n        </StyledTrigger>\n      </div>\n\n      <h2>Controlled, detached triggers</h2>\n      <div className={styles.Container}>\n        <StyledDialog\n          handle={controlledDetachedDialog}\n          open={controlledDetachedOpen}\n          triggerId={controlledDetachedTriggerId}\n          onOpenChange={(nextOpen, eventDetails) => {\n            setControlledDetachedOpen(nextOpen);\n            setControlledDetachedTriggerId(eventDetails.trigger?.id ?? null);\n          }}\n        />\n        <StyledTrigger handle={controlledDetachedDialog} payload={triggerLabels[0]}>\n          {triggerLabels[0]}\n        </StyledTrigger>\n        <StyledTrigger\n          handle={controlledDetachedDialog}\n          payload={triggerLabels[1]}\n          id=\"detached-second-trigger\"\n        >\n          {triggerLabels[1]}\n        </StyledTrigger>\n        <StyledTrigger handle={controlledDetachedDialog} payload={triggerLabels[2]}>\n          {triggerLabels[2]}\n        </StyledTrigger>\n        <StyledTrigger handle={controlledDetachedDialog} payload={triggerLabels[3]}>\n          {triggerLabels[3]}\n        </StyledTrigger>\n        <button\n          type=\"button\"\n          className={clsx(demoStyles.Button, styles.Button)}\n          onClick={() => {\n            setControlledDetachedOpen(true);\n            setControlledDetachedTriggerId('detached-second-trigger');\n          }}\n        >\n          Controlled open (Invites)\n        </button>\n      </div>\n\n      <h2>Handle-controlled dialog</h2>\n      <div className={styles.Container}>\n        <StyledDialog handle={handleControlledDialog} />\n        <StyledTrigger handle={handleControlledDialog} payload={triggerLabels[0]}>\n          {triggerLabels[0]}\n        </StyledTrigger>\n        <StyledTrigger\n          handle={handleControlledDialog}\n          payload={triggerLabels[1]}\n          id=\"handle-controlled-second-trigger\"\n        >\n          {triggerLabels[1]}\n        </StyledTrigger>\n        <StyledTrigger handle={handleControlledDialog} payload={triggerLabels[2]}>\n          {triggerLabels[2]}\n        </StyledTrigger>\n\n        <button\n          type=\"button\"\n          className={clsx(demoStyles.Button, styles.Button)}\n          onClick={() => {\n            handleControlledDialog.open('handle-controlled-second-trigger');\n          }}\n        >\n          Open (Invites)\n        </button>\n\n        <button\n          type=\"button\"\n          className={clsx(demoStyles.Button, styles.Button)}\n          onClick={() => {\n            handleControlledDialog.open(null);\n          }}\n        >\n          Open (unassigned trigger, should display nothing)\n        </button>\n\n        <button\n          type=\"button\"\n          className={clsx(demoStyles.Button, styles.Button)}\n          onClick={() => {\n            handleControlledDialog.openWithPayload('Notifications');\n          }}\n        >\n          Open (unassigned trigger, custom payload)\n        </button>\n      </div>\n    </div>\n  );\n}\n\ntype StyledDialogProps<Payload> = Pick<\n  Dialog.Root.Props<Payload>,\n  'handle' | 'open' | 'onOpenChange' | 'triggerId'\n>;\n\nfunction StyledTrigger<Payload>(\n  props: Dialog.Trigger.Props<Payload> & React.RefAttributes<HTMLButtonElement>,\n) {\n  const { className, children, ...other } = props;\n  const combinedClassName = clsx(demoStyles.Button, styles.Button, className);\n\n  return (\n    <Dialog.Trigger {...other} className={combinedClassName}>\n      {children}\n    </Dialog.Trigger>\n  );\n}\n\nfunction StyledDialog(props: StyledDialogProps<keyof typeof dialogContents>) {\n  const { handle, open, onOpenChange, triggerId } = props;\n  const { settings } = useExperimentSettings<Settings>();\n\n  return (\n    <Dialog.Root\n      handle={handle}\n      open={open}\n      onOpenChange={onOpenChange}\n      triggerId={triggerId}\n      modal={settings.modal}\n      disablePointerDismissal={settings.disablePointerDismissal}\n    >\n      {({ payload }) => renderDialogContent(payload as keyof typeof dialogContents, settings)}\n    </Dialog.Root>\n  );\n}\n\nfunction renderDialogContent(contentKey: keyof typeof dialogContents, settings: Settings) {\n  const content = dialogContents[contentKey];\n\n  return (\n    <Dialog.Portal keepMounted={settings.keepMounted}>\n      {settings.renderBackdrop && <Dialog.Backdrop className={demoStyles.Backdrop} />}\n      <Dialog.Popup className={demoStyles.Popup} key={contentKey}>\n        {contentKey == null ? (\n          <p>No payload</p>\n        ) : (\n          <React.Fragment>\n            <Dialog.Title className={demoStyles.Title}>{contentKey}</Dialog.Title>\n            <Dialog.Description className={demoStyles.Description}>\n              Example dialog for {contentKey}.\n            </Dialog.Description>\n            <div className={styles.DialogSection}>{content}</div>\n            <div className={styles.DialogSection}>\n              <StatefulComponent />\n            </div>\n            <div className={clsx(demoStyles.Actions, styles.Actions)}>\n              <Dialog.Close className={clsx(demoStyles.Button, styles.Button)}>Close</Dialog.Close>\n            </div>\n          </React.Fragment>\n        )}\n      </Dialog.Popup>\n    </Dialog.Portal>\n  );\n}\n\nexport const settingsMetadata: SettingsMetadata<Settings> = {\n  modal: {\n    type: 'boolean',\n    label: 'Modal',\n    default: true,\n  },\n  disablePointerDismissal: {\n    type: 'boolean',\n    label: 'Disable pointer dismissal',\n    default: false,\n  },\n  keepMounted: {\n    type: 'boolean',\n    label: 'Keep mounted',\n    default: false,\n  },\n  renderBackdrop: {\n    type: 'boolean',\n    label: 'Render backdrop',\n    default: true,\n  },\n};\n\nfunction StatefulComponent() {\n  const [note, setNote] = React.useState('');\n\n  return (\n    <div className={styles.Form}>\n      <label className={styles.Label}>\n        Quick note (stateful component to verify correct resetting):\n        <input\n          className={styles.Input}\n          type=\"text\"\n          value={note}\n          onChange={(event) => setNote(event.target.value)}\n          placeholder=\"Capture an idea\"\n        />\n      </label>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/dialog/nested.module.css",
    "content": "@keyframes dialog-opening-transform {\n  from {\n    transform: translate(-50%, -35%) scale(0.8) translateY(0);\n  }\n\n  to {\n    transform: translate(-50%, -50%) scale(calc(pow(0.95, var(--nested-dialogs))))\n      translateY(calc(-30px * var(--nested-dialogs)));\n  }\n}\n\n@keyframes dialog-opening-opacity {\n  from {\n    opacity: 0;\n  }\n\n  to {\n    opacity: 1;\n    visibility: visible;\n  }\n}\n\n@keyframes dialog-closing {\n  from {\n    transform: translate(-50%, -50%) scale(calc(pow(0.95, var(--nested-dialogs))))\n      translateY(calc(-30px * var(--nested-dialogs)));\n    opacity: 1;\n    visibility: visible;\n  }\n\n  to {\n    transform: translate(-50%, -35%) scale(0.8, calc(pow(0.95, var(--nested-dialogs))))\n      translateY(calc(-30px * var(--nested-dialogs)));\n    opacity: 0.5;\n    visibility: hidden;\n  }\n}\n\n@keyframes backdrop-opening {\n  from {\n    backdrop-filter: blur(1px);\n    opacity: 0;\n  }\n\n  to {\n    backdrop-filter: blur(6px);\n    opacity: 1;\n    visibility: visible;\n  }\n}\n\n@keyframes backdrop-closing {\n  from {\n    opacity: 1;\n    backdrop-filter: blur(6px);\n  }\n\n  to {\n    backdrop-filter: blur(1px);\n    opacity: 0;\n    visibility: hidden;\n  }\n}\n\n.dialog {\n  --transition-duration: 150ms;\n\n  background: #fff;\n  border: 1px solid #f5f5f5;\n  min-width: 300px;\n  max-width: 500px;\n  border-radius: 4px;\n  box-shadow: rgb(0 0 0 / 0.2) 0 18px 50px -10px;\n  position: fixed;\n  top: 50%;\n  left: 50%;\n  padding: 16px;\n  font-family: 'IBM Plex Sans', sans-serif;\n  z-index: 1;\n  transform: translate(-50%, -50%);\n  opacity: calc(pow(0.95, var(--nested-dialogs)));\n\n  &.withTransitions {\n    transition:\n      transform var(--transition-duration) ease-in,\n      opacity var(--transition-duration) ease-in;\n\n    &[data-ending-style] {\n      transform: translate(-50%, -35%) scale(0.8, calc(pow(0.95, var(--nested-dialogs))))\n        translateY(calc(-30px * var(--nested-dialogs)));\n      opacity: 0;\n    }\n\n    &[data-open] {\n      &[data-starting-style] {\n        transform: translate(-50%, -35%) scale(0.8) translateY(0);\n        opacity: 0;\n      }\n\n      transform: translate(-50%, -50%) scale(calc(pow(0.95, var(--nested-dialogs))))\n        translateY(calc(-30px * var(--nested-dialogs)));\n      transition:\n        transform var(--transition-duration) ease-out,\n        opacity var(--transition-duration) ease-out;\n    }\n  }\n\n  &.withAnimations {\n    transform: translate(-50%, -35%) scale(0.8, 0.9) translateY(0);\n\n    &[data-open] {\n      animation:\n        dialog-opening-transform var(--transition-duration) ease-out,\n        dialog-opening-opacity var(--transition-duration) ease-out forwards;\n      transform: translate(-50%, -50%) scale(calc(pow(0.95, var(--nested-dialogs))))\n        translateY(calc(-30px * var(--nested-dialogs)));\n      transition: transform var(--transition-duration) ease-out;\n    }\n\n    &[data-ending-style] {\n      animation: dialog-closing var(--transition-duration) ease-in forwards;\n    }\n  }\n\n  &.withReactSpringTransition {\n    top: 50vh;\n    left: 50vw;\n    visibility: visible;\n    opacity: 1;\n  }\n}\n\n.backdrop {\n  background: radial-gradient(#cecdcf36, #8b94ab47);\n  z-index: 0;\n  position: fixed;\n  inset: 0;\n\n  &.withTransitions {\n    transition:\n      backdrop-filter 300ms ease-in,\n      opacity 300ms ease-in,\n      visibility 300ms step-end;\n\n    &[data-open] {\n      &[data-starting-style] {\n        opacity: 0;\n        backdrop-filter: blur(1px);\n      }\n\n      backdrop-filter: blur(6px);\n      opacity: 1;\n      visibility: visible;\n      transition:\n        backdrop-filter 500ms ease-out,\n        opacity 500ms ease-out;\n    }\n  }\n\n  &.withAnimations {\n    &[data-open] {\n      animation: backdrop-opening 500ms ease-out forwards;\n    }\n\n    &[data-ending-style] {\n      animation: backdrop-closing 500ms ease-in forwards;\n    }\n  }\n}\n\n.title {\n  font-size: 1.5rem;\n  font-weight: 600;\n}\n\n.page {\n  max-width: 1000px;\n  margin: 0 auto;\n  padding: 16px;\n  font-family: 'IBM Plex Sans', sans-serif;\n\n  & h1 {\n    font-family: 'General Sans', sans-serif;\n    font-weight: 600;\n    font-size: 2rem;\n  }\n\n  & h2 {\n    font-size: 1.5rem;\n    font-weight: 600;\n  }\n\n  & label {\n    font-size: 0.8333rem;\n  }\n\n  & label + label {\n    margin-left: 16px;\n  }\n}\n\n.springWrapper {\n  position: fixed;\n  top: 0;\n  left: 0;\n  right: 0;\n}\n\n.button {\n  background: #eee;\n  padding: 8px 16px;\n  border: 1px solid #d8d8d8;\n  border-radius: 4px;\n  font-family: inherit;\n\n  &:hover {\n    background: #ffbf2b;\n  }\n\n  &:focus-visible {\n    outline: 2px solid #ffbf2b;\n  }\n\n  &:active {\n    background: #cc9922;\n    border-color: #cc9922;\n\n    &:focus-visible {\n      outline-color: #cc9922;\n    }\n  }\n}\n\n.form {\n  display: flex;\n  gap: 16px;\n  margin-top: 24px;\n  flex-wrap: wrap;\n\n  & input[type='text'],\n  & textarea {\n    padding: 8px;\n    border: 1px solid #d8d8d8;\n    border-radius: 4px;\n    font-family: inherit;\n    box-sizing: border-box;\n  }\n\n  & textarea {\n    resize: vertical;\n    min-height: 100px;\n    width: 100%;\n  }\n\n  & > * {\n    flex: 1 0 auto;\n    margin: 0;\n  }\n}\n\n.controls {\n  display: flex;\n  gap: 16px;\n  margin-top: 16px;\n  border-top: 1px solid #d8d8d8;\n  padding-top: 16px;\n\n  & > * {\n    flex: 1 1 50%;\n    margin: 0;\n  }\n}\n\n.demo {\n  margin-right: 8px;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/dialog/nested.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport clsx from 'clsx';\nimport { Dialog } from '@base-ui/react/dialog';\nimport { animated as springAnimated, useSpring, useSpringRef } from '@react-spring/web';\nimport { SettingsMetadata, useExperimentSettings } from '../_components/SettingsPanel';\nimport { useTransitionStatus } from '../../../../../../packages/react/src/utils/useTransitionStatus';\nimport classes from './nested.module.css';\n\nconst NESTED_DIALOGS = 8;\n\ninterface Settings {\n  keepMounted: boolean;\n  modal: boolean;\n  disablePointerDismissal: boolean;\n}\n\nfunction renderContent(\n  title: string,\n  includeNested: number,\n  nestedClassName: string,\n  modal: boolean,\n  disablePointerDismissal: boolean,\n) {\n  return (\n    <React.Fragment>\n      <Dialog.Title className={classes.title}>{title}</Dialog.Title>\n      <Dialog.Description>This is a sample dialog.</Dialog.Description>\n      <p>\n        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam eget sapien id dolor rutrum\n        porta. Sed enim nulla, placerat eu tincidunt non, ultrices in lectus. Curabitur pellentesque\n        diam nec ligula hendrerit dapibus.\n      </p>\n\n      <div className={classes.form}>\n        <textarea placeholder=\"Testing focus\" />\n        <input type=\"text\" placeholder=\"Testing focus\" />\n        <input type=\"text\" placeholder=\"Testing focus\" />\n      </div>\n\n      <div className={classes.controls}>\n        {includeNested > 0 ? (\n          <Dialog.Root modal={modal} disablePointerDismissal={disablePointerDismissal}>\n            <Dialog.Trigger className={classes.button}>Open nested</Dialog.Trigger>\n            <Dialog.Backdrop className={clsx(classes.backdrop, nestedClassName)} />\n            <Dialog.Portal>\n              <Dialog.Popup className={clsx(classes.dialog, nestedClassName)}>\n                {renderContent(\n                  `Nested dialog ${NESTED_DIALOGS + 1 - includeNested}`,\n                  includeNested - 1,\n                  nestedClassName,\n                  modal,\n                  disablePointerDismissal,\n                )}\n              </Dialog.Popup>\n            </Dialog.Portal>\n          </Dialog.Root>\n        ) : null}\n\n        <Dialog.Close className={classes.button}>Close</Dialog.Close>\n      </div>\n    </React.Fragment>\n  );\n}\n\nfunction CssTransitionDialogDemo({ keepMounted, modal, disablePointerDismissal }: Settings) {\n  return (\n    <span className={classes.demo}>\n      <Dialog.Root modal={modal} disablePointerDismissal={disablePointerDismissal}>\n        <Dialog.Trigger className={classes.button}>Open with CSS transition</Dialog.Trigger>\n\n        <Dialog.Portal keepMounted={keepMounted}>\n          <Dialog.Backdrop className={clsx(classes.backdrop, classes.withTransitions)} />\n          <Dialog.Popup className={clsx(classes.dialog, classes.withTransitions)}>\n            {renderContent(\n              'Dialog with CSS transitions',\n              NESTED_DIALOGS,\n              classes.withTransitions,\n              modal,\n              disablePointerDismissal,\n            )}\n          </Dialog.Popup>\n        </Dialog.Portal>\n      </Dialog.Root>\n    </span>\n  );\n}\n\nfunction CssAnimationDialogDemo({ keepMounted, modal, disablePointerDismissal }: Settings) {\n  return (\n    <span className={classes.demo}>\n      <Dialog.Root modal={modal} disablePointerDismissal={disablePointerDismissal}>\n        <Dialog.Trigger className={classes.button}>Open with CSS animation</Dialog.Trigger>\n\n        <Dialog.Portal keepMounted={keepMounted}>\n          <Dialog.Backdrop className={clsx(classes.backdrop, classes.withAnimations)} />\n          <Dialog.Popup className={clsx(classes.dialog, classes.withAnimations)}>\n            {renderContent(\n              'Dialog with CSS animations',\n              NESTED_DIALOGS,\n              classes.withAnimations,\n              modal,\n              disablePointerDismissal,\n            )}\n          </Dialog.Popup>\n        </Dialog.Portal>\n      </Dialog.Root>\n    </span>\n  );\n}\n\n// @ts-expect-error To be used later\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nfunction ReactSpringDialogDemo({ keepMounted, modal, disablePointerDismissal }: DemoProps) {\n  const [open, setOpen] = React.useState(false);\n\n  return (\n    <span className={classes.demo}>\n      <Dialog.Root disablePointerDismissal open={open} onOpenChange={setOpen}>\n        <Dialog.Trigger className={classes.button}>\n          Open with React Spring transition\n        </Dialog.Trigger>\n\n        <Dialog.Backdrop className={`${classes.backdrop} ${classes.withAnimations}`} />\n\n        <ReactSpringTransition open={open}>\n          <Dialog.Portal keepMounted={keepMounted}>\n            <Dialog.Popup className={`${classes.dialog} ${classes.withReactSpringTransition}`}>\n              {renderContent(\n                'Dialog with ReactSpring transitions',\n                3,\n                classes.withReactSpringTransition,\n                modal,\n                disablePointerDismissal,\n              )}\n            </Dialog.Popup>\n          </Dialog.Portal>\n        </ReactSpringTransition>\n      </Dialog.Root>\n    </span>\n  );\n}\n\nfunction ReactSpringTransition(props: { open: boolean; children?: React.ReactElement<unknown> }) {\n  const { open, children } = props;\n\n  const api = useSpringRef();\n  const springs = useSpring({\n    ref: api,\n    from: { opacity: 0, transform: 'translateY(-8px) scale(0.95)' },\n  });\n\n  const { mounted, setMounted } = useTransitionStatus(open);\n\n  React.useEffect(() => {\n    if (open) {\n      api.start({\n        opacity: 1,\n        transform: 'translateY(0) scale(1)',\n        config: { tension: 250, friction: 10 },\n      });\n    } else {\n      api.start({\n        opacity: 0,\n        transform: 'translateY(-8px) scale(0.95)',\n        config: { tension: 170, friction: 26 },\n        onRest: () => setMounted(false),\n      });\n    }\n  }, [api, open, mounted, setMounted]);\n\n  return mounted ? (\n    <springAnimated.div style={springs} className={classes.springWrapper}>\n      {children}\n    </springAnimated.div>\n  ) : null;\n}\n\nexport default function DialogExperiment() {\n  const { settings } = useExperimentSettings<Settings>();\n\n  return (\n    <div className={classes.page}>\n      <h1>Nested dialogs</h1>\n      <CssTransitionDialogDemo\n        keepMounted={settings.keepMounted}\n        modal={settings.modal}\n        disablePointerDismissal={settings.disablePointerDismissal}\n      />\n      <CssAnimationDialogDemo\n        keepMounted={settings.keepMounted}\n        modal={settings.modal}\n        disablePointerDismissal={settings.disablePointerDismissal}\n      />\n    </div>\n  );\n}\n\nexport const settingsMetadata: SettingsMetadata<Settings> = {\n  modal: {\n    type: 'boolean',\n    label: 'Modal',\n    default: true,\n  },\n  disablePointerDismissal: {\n    type: 'boolean',\n    label: 'Disable pointer Dismissal',\n    default: true,\n  },\n  keepMounted: {\n    type: 'boolean',\n    label: 'Keep mounted',\n    default: false,\n  },\n};\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/drawer/cross-axis-scroll.module.css",
    "content": ".Root {\n  display: flex;\n  flex-direction: column;\n  gap: 1rem;\n  width: 100%;\n  max-width: 72rem;\n}\n\n.Title {\n  margin: 0;\n  font-size: 1.5rem;\n  line-height: 2rem;\n  letter-spacing: -0.01em;\n}\n\n.Description {\n  margin: 0;\n  color: var(--color-gray-600);\n}\n\n.Grid {\n  display: grid;\n  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));\n  gap: 0.875rem;\n}\n\n.Card {\n  display: flex;\n  flex-direction: column;\n  gap: 0.75rem;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.5rem;\n  background: var(--color-gray-50);\n  padding: 0.875rem;\n}\n\n.CardTitle {\n  margin: 0;\n  font-size: 1rem;\n  line-height: 1.5rem;\n}\n\n.CardDescription {\n  margin: 0;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  color: var(--color-gray-700);\n}\n\n.ViewportLeft {\n  justify-content: flex-start;\n}\n\n.ViewportUp {\n  align-items: flex-start;\n}\n\n.PopupLeft {\n  margin-right: 0;\n  margin-left: calc(-1 * var(--bleed));\n  padding-right: 1.5rem;\n  padding-left: calc(1.5rem + var(--bleed));\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    transform: translateX(calc(-100% + var(--bleed) - var(--viewport-padding)));\n  }\n\n  @supports (-webkit-touch-callout: none) {\n    margin-left: 0;\n  }\n}\n\n.PopupUp {\n  --bleed: 0px;\n  margin-bottom: 0;\n  margin-top: 0;\n  border-radius: 0 0 1rem 1rem;\n  padding-top: 1rem;\n  padding-bottom: calc(1.5rem + env(safe-area-inset-bottom, 0px));\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    transform: translateY(-100%);\n  }\n}\n\n.PopupVerticalContent {\n  padding-top: 1.5rem;\n}\n\n.ScrollRegionY {\n  display: flex;\n  flex-direction: column;\n  gap: 0.375rem;\n  min-height: 0;\n  height: 12rem;\n  overflow-y: auto;\n  overscroll-behavior: contain;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.5rem;\n  background: white;\n  padding: 0.5rem;\n}\n\n.Row {\n  border-radius: 0.375rem;\n  border: 1px solid var(--color-gray-200);\n  background: var(--color-gray-50);\n  padding: 0.5rem 0.625rem;\n  font-size: 0.875rem;\n}\n\n.ScrollRegionX {\n  overflow-x: auto;\n  overflow-y: hidden;\n  overscroll-behavior: contain;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.5rem;\n  background: white;\n}\n\n.Track {\n  display: inline-flex;\n  gap: 0.5rem;\n  min-width: max-content;\n  padding: 0.625rem;\n}\n\n.Chip {\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.5rem;\n  background: var(--color-gray-50);\n  padding: 0.625rem 0.75rem;\n  font-size: 0.875rem;\n  white-space: nowrap;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/drawer/cross-axis-scroll.tsx",
    "content": "import { Drawer } from '@base-ui/react/drawer';\nimport clsx from 'clsx';\nimport heroStyles from 'docs/src/app/(docs)/react/components/drawer/demos/hero/css-modules/index.module.css';\nimport positionStyles from 'docs/src/app/(docs)/react/components/drawer/demos/position/css-modules/index.module.css';\nimport styles from './cross-axis-scroll.module.css';\n\ntype SwipeDirection = 'left' | 'right' | 'up' | 'down';\ntype OverflowAxis = 'x' | 'y';\n\ninterface DrawerCase {\n  id: string;\n  label: string;\n  swipeDirection: SwipeDirection;\n  overflowAxis: OverflowAxis;\n}\n\nconst drawerCases: DrawerCase[] = [\n  {\n    id: 'right-x',\n    label: 'Right drawer - X overflow',\n    swipeDirection: 'right',\n    overflowAxis: 'x',\n  },\n  {\n    id: 'right-y',\n    label: 'Right drawer - Y overflow',\n    swipeDirection: 'right',\n    overflowAxis: 'y',\n  },\n  {\n    id: 'left-x',\n    label: 'Left drawer - X overflow',\n    swipeDirection: 'left',\n    overflowAxis: 'x',\n  },\n  {\n    id: 'left-y',\n    label: 'Left drawer - Y overflow',\n    swipeDirection: 'left',\n    overflowAxis: 'y',\n  },\n  {\n    id: 'down-y',\n    label: 'Down drawer - Y overflow',\n    swipeDirection: 'down',\n    overflowAxis: 'y',\n  },\n  {\n    id: 'down-x',\n    label: 'Down drawer - X overflow',\n    swipeDirection: 'down',\n    overflowAxis: 'x',\n  },\n  {\n    id: 'up-y',\n    label: 'Up drawer - Y overflow',\n    swipeDirection: 'up',\n    overflowAxis: 'y',\n  },\n  {\n    id: 'up-x',\n    label: 'Up drawer - X overflow',\n    swipeDirection: 'up',\n    overflowAxis: 'x',\n  },\n];\n\nexport default function DrawerCrossAxisScrollExperiment() {\n  return (\n    <div className={styles.Root}>\n      <h1 className={styles.Title}>Drawer cross-axis scroll matrix</h1>\n      <p className={styles.Description}>\n        Open each case and test swipe-dismiss on the drawer axis and native scrolling on the\n        overflow axis.\n      </p>\n\n      <div className={styles.Grid}>\n        {drawerCases.map((testCase) => (\n          <DrawerCaseCard key={testCase.id} testCase={testCase} />\n        ))}\n      </div>\n    </div>\n  );\n}\n\nfunction DrawerCaseCard(props: { testCase: DrawerCase }) {\n  const { testCase } = props;\n  const sideDrawer = testCase.swipeDirection === 'left' || testCase.swipeDirection === 'right';\n  const verticalDrawer = !sideDrawer;\n\n  return (\n    <section className={styles.Card}>\n      <h2 className={styles.CardTitle}>{testCase.label}</h2>\n      <p className={styles.CardDescription}>\n        Swipe direction: <strong>{testCase.swipeDirection}</strong>. Scroll overflow axis:{' '}\n        <strong>{testCase.overflowAxis.toUpperCase()}</strong>.\n      </p>\n\n      <Drawer.Root swipeDirection={testCase.swipeDirection}>\n        <Drawer.Trigger className={heroStyles.Button}>Open case</Drawer.Trigger>\n        <Drawer.Portal>\n          <Drawer.Backdrop className={heroStyles.Backdrop} />\n          <Drawer.Viewport\n            className={clsx(\n              sideDrawer ? heroStyles.Viewport : positionStyles.Viewport,\n              testCase.swipeDirection === 'left' && styles.ViewportLeft,\n              testCase.swipeDirection === 'up' && styles.ViewportUp,\n            )}\n          >\n            <Drawer.Popup\n              className={clsx(\n                sideDrawer ? heroStyles.Popup : positionStyles.Popup,\n                testCase.swipeDirection === 'left' && styles.PopupLeft,\n                testCase.swipeDirection === 'up' && styles.PopupUp,\n                verticalDrawer && styles.PopupVerticalContent,\n              )}\n            >\n              <Drawer.Content className={heroStyles.Content}>\n                <Drawer.Title className={heroStyles.Title}>{testCase.label}</Drawer.Title>\n                <Drawer.Description className={heroStyles.Description}>\n                  Try scrolling in the panel first, then swipe to dismiss from the drawer edge.\n                </Drawer.Description>\n                <OverflowRegion axis={testCase.overflowAxis} />\n                <div className={heroStyles.Actions}>\n                  <Drawer.Close className={heroStyles.Button}>Close</Drawer.Close>\n                </div>\n              </Drawer.Content>\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>\n    </section>\n  );\n}\n\nfunction OverflowRegion(props: { axis: OverflowAxis }) {\n  const { axis } = props;\n\n  if (axis === 'y') {\n    return (\n      <div className={styles.ScrollRegionY}>\n        {Array.from({ length: 16 }, (_, index) => (\n          <div key={`row-${index}`} className={styles.Row}>\n            Vertical row {index + 1}\n          </div>\n        ))}\n      </div>\n    );\n  }\n\n  return (\n    <div className={styles.ScrollRegionX}>\n      <div className={styles.Track}>\n        {Array.from({ length: 12 }, (_, index) => (\n          <div key={`chip-${index}`} className={styles.Chip}>\n            Horizontal item {index + 1}\n          </div>\n        ))}\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/drawer/drawer-controlled-opening.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Drawer } from '@base-ui/react/drawer';\n\nexport default function ControlledOpening() {\n  const [open, setOpen] = React.useState(false);\n  const [locked, setLocked] = React.useState(false);\n  return (\n    <Drawer.Root\n      open={open}\n      onOpenChange={(nextOpen) => {\n        if (locked) {\n          return;\n        }\n        setOpen(nextOpen);\n      }}\n    >\n      <div className=\"flex items-center gap-4\">\n        <Drawer.Trigger className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-medium text-gray-900 select-none hover:bg-gray-100 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n          Open bottom drawer\n        </Drawer.Trigger>\n        <label className=\"flex items-center gap-2 text-sm\">\n          <input\n            type=\"checkbox\"\n            checked={locked}\n            onChange={(event) => setLocked(event.target.checked)}\n          />\n          Lock open state\n        </label>\n      </div>\n      <Drawer.Portal>\n        <Drawer.Backdrop className=\"[--backdrop-opacity:0.2] [--bleed:3rem] dark:[--backdrop-opacity:0.7] fixed inset-0 min-h-dvh bg-black opacity-[calc(var(--backdrop-opacity)*(1-var(--drawer-swipe-progress)))] transition-opacity duration-450 ease-[cubic-bezier(0.32,0.72,0,1)] data-swiping:duration-0 data-ending-style:opacity-0 data-starting-style:opacity-0 data-ending-style:duration-[calc(var(--drawer-swipe-strength)*400ms)] supports-[-webkit-touch-callout:none]:absolute\" />\n        <Drawer.Viewport className=\"fixed inset-0 flex items-end justify-center\">\n          <Drawer.Popup className=\"-mb-12 w-full max-h-[calc(80vh+3rem)] rounded-t-2xl bg-gray-50 px-6 pb-[calc(1.5rem+env(safe-area-inset-bottom,0px)+3rem)] pt-4 text-gray-900 outline outline-gray-200 overflow-y-auto overscroll-contain touch-auto transform-[translateY(var(--drawer-swipe-movement-y))] transition-transform duration-450 ease-[cubic-bezier(0.32,0.72,0,1)] data-swiping:select-none data-ending-style:transform-[translateY(calc(100%-3rem))] data-starting-style:transform-[translateY(calc(100%-3rem))] data-ending-style:duration-[calc(var(--drawer-swipe-strength)*400ms)] dark:outline-gray-300\">\n            <div className=\"w-12 h-1 mx-auto mb-4 rounded-full bg-gray-300\" />\n            <Drawer.Content className=\"mx-auto w-full max-w-lg\">\n              <Drawer.Title className=\"mb-1 text-lg font-medium text-center\">\n                Notifications\n              </Drawer.Title>\n              <Drawer.Description className=\"mb-6 text-base text-gray-600 text-center\">\n                You are all caught up. Good job!\n              </Drawer.Description>\n              <div className=\"flex justify-center gap-4\">\n                <Drawer.Close className=\"flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-medium text-gray-900 select-none hover:bg-gray-100 focus-visible:outline focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100\">\n                  Close\n                </Drawer.Close>\n              </div>\n              <label className=\"flex items-center justify-center gap-2 mt-4 text-sm\">\n                <input\n                  type=\"checkbox\"\n                  checked={locked}\n                  onChange={(event) => setLocked(event.target.checked)}\n                />\n                Lock open state\n              </label>\n            </Drawer.Content>\n          </Drawer.Popup>\n        </Drawer.Viewport>\n      </Drawer.Portal>\n    </Drawer.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/drawer/touch-ignore.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Drawer } from '@base-ui/react/drawer';\n\ntype EventName = 'plain div click' | 'ignored div click' | 'native button click' | 'drawer closed';\n\nexport default function DrawerTouchIgnoreExperiment() {\n  const [plainDivClicks, setPlainDivClicks] = React.useState(0);\n  const [ignoredDivClicks, setIgnoredDivClicks] = React.useState(0);\n  const [buttonClicks, setButtonClicks] = React.useState(0);\n  const [events, setEvents] = React.useState<EventName[]>([]);\n\n  function recordEvent(eventName: EventName) {\n    setEvents((previousEvents) => [eventName, ...previousEvents].slice(0, 8));\n  }\n\n  return (\n    <div className=\"mx-auto flex w-full max-w-3xl flex-col gap-6 px-6 py-10 text-slate-900\">\n      <div className=\"space-y-3\">\n        <h1 className=\"text-3xl font-semibold tracking-tight\">Drawer touch ignore experiment</h1>\n        <p className=\"max-w-2xl text-sm leading-6 text-slate-600\">\n          Use this to compare touch behavior inside <code>Drawer.Content</code>. The plain div\n          should still participate in swipe-to-dismiss, while the explicit{' '}\n          <code>data-base-ui-swipe-ignore</code> div should preserve taps.\n        </p>\n      </div>\n\n      <div className=\"grid gap-4 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm md:grid-cols-[1.2fr_0.8fr]\">\n        <div className=\"space-y-3 text-sm text-slate-700\">\n          <h2 className=\"text-base font-semibold text-slate-900\">What to test</h2>\n          <ol className=\"list-inside list-decimal space-y-2\">\n            <li>Tap the plain div on a touch device. It should still be part of swipe handling.</li>\n            <li>\n              Tap the <code>data-base-ui-swipe-ignore</code> div. Its click counter should\n              increment.\n            </li>\n            <li>Tap the native button. It should continue to work as before.</li>\n            <li>Drag from the plain div area to confirm swipe-to-dismiss still starts there.</li>\n          </ol>\n        </div>\n\n        <div className=\"rounded-xl bg-slate-950 p-4 text-sm text-slate-100\">\n          <h2 className=\"mb-3 text-base font-semibold\">Latest events</h2>\n          <div className=\"space-y-2\">\n            <CounterRow label=\"Plain div clicks\" value={plainDivClicks} />\n            <CounterRow label=\"Ignored div clicks\" value={ignoredDivClicks} />\n            <CounterRow label=\"Native button clicks\" value={buttonClicks} />\n          </div>\n          <div className=\"mt-4 border-t border-white/10 pt-4\">\n            <div className=\"mb-2 text-xs font-medium uppercase tracking-[0.2em] text-slate-400\">\n              Event log\n            </div>\n            <ul className=\"space-y-1 text-sm text-slate-300\">\n              {events.length === 0 ? <li>No events yet.</li> : null}\n              {events.map((eventName, index) => (\n                <li key={`${eventName}-${index}`}>{eventName}</li>\n              ))}\n            </ul>\n          </div>\n        </div>\n      </div>\n\n      <Drawer.Root\n        onOpenChange={(open) => {\n          if (!open) {\n            recordEvent('drawer closed');\n          }\n        }}\n      >\n        <Drawer.Trigger className=\"inline-flex h-11 w-fit items-center justify-center rounded-xl bg-slate-900 px-4 text-sm font-medium text-white transition hover:bg-slate-700\">\n          Open touch test drawer\n        </Drawer.Trigger>\n        <Drawer.Portal>\n          <Drawer.Backdrop className=\"fixed inset-0 bg-slate-950/30 transition-opacity data-starting-style:opacity-0 data-ending-style:opacity-0\" />\n          <Drawer.Viewport className=\"fixed inset-0 flex items-end justify-center\">\n            <Drawer.Popup className=\"flex w-full max-w-2xl max-h-[85vh] flex-col rounded-t-3xl bg-white px-6 pb-[calc(1.5rem+env(safe-area-inset-bottom,0px))] pt-4 text-slate-900 shadow-2xl outline outline-1 outline-slate-200 transition-transform data-swiping:select-none data-starting-style:translate-y-full data-ending-style:translate-y-full\">\n              <div className=\"mx-auto mb-4 h-1.5 w-12 rounded-full bg-slate-300\" />\n              <Drawer.Content className=\"space-y-4 overflow-y-auto overscroll-contain pb-2\">\n                <Drawer.Title className=\"text-lg font-semibold\">Touch behavior test</Drawer.Title>\n                <Drawer.Description className=\"text-sm leading-6 text-slate-600\">\n                  The tiles below intentionally use different interaction models so you can verify\n                  the drawer bugfix on a real touch device or emulator.\n                </Drawer.Description>\n\n                <div className=\"grid gap-3\">\n                  {/* Intentional non-interactive div to reproduce the touch click behavior. */}\n                  {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}\n                  <div\n                    className=\"rounded-2xl border border-amber-300 bg-amber-50 p-4 text-left\"\n                    onClick={() => {\n                      setPlainDivClicks((value) => value + 1);\n                      recordEvent('plain div click');\n                    }}\n                  >\n                    <div className=\"text-sm font-semibold text-amber-950\">\n                      Plain div inside Drawer.Content\n                    </div>\n                    <div className=\"mt-1 text-sm text-amber-800\">\n                      On touch, this area should still participate in swipe-to-dismiss.\n                    </div>\n                  </div>\n\n                  {/* Intentional non-interactive div to reproduce explicit swipe-ignore behavior. */}\n                  {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}\n                  <div\n                    data-base-ui-swipe-ignore\n                    className=\"rounded-2xl border border-emerald-300 bg-emerald-50 p-4 text-left\"\n                    onClick={() => {\n                      setIgnoredDivClicks((value) => value + 1);\n                      recordEvent('ignored div click');\n                    }}\n                  >\n                    <div className=\"text-sm font-semibold text-emerald-950\">\n                      Div with data-base-ui-swipe-ignore\n                    </div>\n                    <div className=\"mt-1 text-sm text-emerald-800\">\n                      Tapping here should preserve the click even on touch.\n                    </div>\n                  </div>\n\n                  <button\n                    type=\"button\"\n                    className=\"rounded-2xl border border-sky-300 bg-sky-50 p-4 text-left\"\n                    onClick={() => {\n                      setButtonClicks((value) => value + 1);\n                      recordEvent('native button click');\n                    }}\n                  >\n                    <div className=\"text-sm font-semibold text-sky-950\">Native button</div>\n                    <div className=\"mt-1 text-sm text-sky-800\">\n                      Control case to compare against the custom div targets.\n                    </div>\n                  </button>\n                </div>\n\n                <div className=\"flex gap-3 pt-2\">\n                  <Drawer.Close className=\"inline-flex h-10 items-center justify-center rounded-lg border border-slate-200 px-3.5 text-sm font-medium text-slate-900 transition hover:bg-slate-50\">\n                    Close\n                  </Drawer.Close>\n                </div>\n              </Drawer.Content>\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>\n    </div>\n  );\n}\n\nfunction CounterRow(props: { label: string; value: number }) {\n  const { label, value } = props;\n\n  return (\n    <div className=\"flex items-center justify-between gap-4 rounded-lg bg-white/5 px-3 py-2\">\n      <span>{label}</span>\n      <span className=\"font-mono text-base text-white\">{value}</span>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/drawer-slider.module.css",
    "content": ".Root {\n  min-height: 60vh;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  padding-block: 4rem;\n}\n\n.Trigger,\n.Close {\n  height: 2.5rem;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  border-radius: var(--radius-md);\n  border: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-50);\n  color: var(--color-gray-900);\n  font-size: var(--text-base);\n  line-height: var(--text-base--line-height);\n  font-weight: 500;\n  user-select: none;\n}\n\n.Trigger {\n  padding-inline: 1rem;\n  box-shadow:\n    0 1px 3px 0 rgb(0 0 0 / 10%),\n    0 1px 2px -1px rgb(0 0 0 / 10%);\n}\n\n.Close {\n  padding-inline: 0.875rem;\n}\n\n.Trigger:hover,\n.Close:hover {\n  background-color: var(--color-gray-100);\n}\n\n.Trigger:active,\n.Close:active {\n  background-color: var(--color-gray-100);\n}\n\n.Trigger:focus-visible,\n.Close:focus-visible {\n  outline: 2px solid var(--color-blue);\n  outline-offset: -1px;\n}\n\n.Backdrop {\n  --backdrop-opacity: 0.2;\n  position: fixed;\n  inset: 0;\n  min-height: 100dvh;\n  background-color: rgb(0 0 0);\n  opacity: calc(var(--backdrop-opacity) * (1 - var(--drawer-swipe-progress, 0)));\n  transition: opacity 450ms var(--ease-out-fast);\n}\n\n@supports (-webkit-touch-callout: none) {\n  .Backdrop {\n    position: absolute;\n  }\n}\n\n.Backdrop[data-swiping] {\n  transition-duration: 0ms;\n}\n\n.Backdrop[data-ending-style],\n.Backdrop[data-starting-style] {\n  opacity: 0;\n}\n\n.Viewport {\n  position: fixed;\n  inset: 0;\n  display: flex;\n  align-items: stretch;\n  justify-content: flex-end;\n}\n\n@supports (-webkit-touch-callout: none) {\n  .Viewport {\n    padding: 10px;\n  }\n}\n\n.Popup {\n  height: 100%;\n  width: calc(20rem + 3rem);\n  max-width: calc(100vw - 3rem + 3rem);\n  margin-right: -3rem;\n  overflow-y: auto;\n  padding: 1.5rem;\n  padding-right: calc(1.5rem + 3rem);\n  color: var(--color-gray-900);\n  background-color: var(--color-gray-50);\n  outline: 1px solid var(--color-gray-200);\n  transform: translateX(var(--drawer-swipe-movement-x));\n  transition: transform 525ms cubic-bezier(0.45, 1.005, 0, 1.005);\n}\n\n@supports (-webkit-touch-callout: none) {\n  .Popup {\n    margin-right: 0;\n    width: 20rem;\n    max-width: calc(100vw - 20px);\n    border-radius: 10px;\n    padding-right: 1.5rem;\n  }\n}\n\n.Popup[data-ending-style],\n.Popup[data-starting-style] {\n  transform: translateX(100%);\n  transition-duration: 180ms;\n  transition-timing-function: cubic-bezier(0.375, 0.015, 0.545, 0.455);\n}\n\n.Title {\n  margin-top: -0.375rem;\n  margin-bottom: 0.25rem;\n  text-align: center;\n  font-size: var(--text-lg);\n  line-height: var(--text-lg--line-height);\n  letter-spacing: var(--text-lg--letter-spacing);\n  font-weight: 500;\n}\n\n.Description {\n  margin-bottom: 1.5rem;\n  text-align: center;\n  color: var(--color-gray-600);\n  font-size: var(--text-base);\n  line-height: var(--text-base--line-height);\n}\n\n.SliderSection {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  gap: 1.5rem;\n  padding-block: 1rem;\n}\n\n.SliderControl {\n  width: 14rem;\n  display: flex;\n  align-items: center;\n  padding-block: 0.75rem;\n  touch-action: none;\n  user-select: none;\n}\n\n.SliderTrack {\n  width: 100%;\n  height: 0.25rem;\n  border-radius: 99px;\n  background-color: var(--color-gray-200);\n  box-shadow: inset 0 0 0 1px var(--color-gray-200);\n  user-select: none;\n}\n\n.SliderIndicator {\n  border-radius: 99px;\n  background-color: var(--color-gray-700);\n  user-select: none;\n}\n\n.SliderThumb {\n  width: 1rem;\n  height: 1rem;\n  border-radius: 9999px;\n  background-color: white;\n  outline: 1px solid var(--color-gray-300);\n  user-select: none;\n}\n\n.SliderThumb:has(:focus-visible) {\n  outline: 2px solid var(--color-blue);\n}\n\n.Actions {\n  margin-top: 2rem;\n  display: flex;\n  justify-content: flex-end;\n  gap: 1rem;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/drawer-slider.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Drawer } from '@base-ui/react/drawer';\nimport { Slider } from '@base-ui/react/slider';\nimport styles from './drawer-slider.module.css';\n\nexport default function DrawerSliderExperiment() {\n  return (\n    <div className={styles.Root}>\n      <Drawer.Root swipeDirection=\"right\">\n        <Drawer.Trigger className={styles.Trigger}>Open drawer</Drawer.Trigger>\n        <Drawer.Portal>\n          <Drawer.Backdrop className={styles.Backdrop} />\n          <Drawer.Viewport className={styles.Viewport}>\n            <Drawer.Popup className={styles.Popup}>\n              <Drawer.Title className={styles.Title}>Slider in Drawer</Drawer.Title>\n              <Drawer.Description className={styles.Description}>\n                Adjust the value using the slider below.\n              </Drawer.Description>\n              <div className={styles.SliderSection}>\n                <Slider.Root min={0} max={100} step={1}>\n                  <Slider.Control className={styles.SliderControl}>\n                    <Slider.Track className={styles.SliderTrack}>\n                      <Slider.Indicator className={styles.SliderIndicator} />\n                      <Slider.Thumb aria-label=\"Value\" className={styles.SliderThumb} />\n                    </Slider.Track>\n                  </Slider.Control>\n                </Slider.Root>\n              </div>\n              <div className={styles.Actions}>\n                <Drawer.Close className={styles.Close}>Close</Drawer.Close>\n              </div>\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/forms/_icons.tsx",
    "content": "'use client';\nimport * as React from 'react';\n\nexport default function Nothing() {\n  return <div>This is just a dummy file to hold icons</div>;\n}\n\nexport function ChevronUpDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"8\"\n      height=\"12\"\n      viewBox=\"0 0 8 12\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"1.5\"\n      {...props}\n    >\n      <path d=\"M0.5 4.5L4 1.5L7.5 4.5\" />\n      <path d=\"M0.5 7.5L4 10.5L7.5 7.5\" />\n    </svg>\n  );\n}\n\nexport function CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\nexport function HorizontalRuleIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"10\"\n      height=\"10\"\n      viewBox=\"0 0 24 24\"\n      fill=\"currentcolor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <line\n        x1=\"3\"\n        y1=\"12\"\n        x2=\"21\"\n        y2=\"12\"\n        stroke=\"currentColor\"\n        strokeWidth={3}\n        strokeLinecap=\"round\"\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/forms/autofill.module.css",
    "content": ".Page {\n  font-family: var(--font-sans);\n  padding: 24px;\n  display: flex;\n  flex-direction: column;\n  gap: 24px;\n}\n\n.Title {\n  font-size: 1.5rem;\n  font-weight: 600;\n}\n\n.Description {\n  max-width: 720px;\n  line-height: 1.6;\n  color: var(--color-gray-700);\n}\n\n.Form {\n  display: grid;\n  gap: 20px;\n  max-width: 760px;\n}\n\n.Section {\n  display: grid;\n  gap: 12px;\n  padding: 16px;\n  border-radius: 14px;\n  border: 1px solid var(--color-gray-200);\n  background: var(--color-gray-50);\n}\n\n.SectionTitle {\n  font-weight: 600;\n  font-size: 1.05rem;\n  color: var(--color-gray-900);\n}\n\n.Row {\n  display: grid;\n  gap: 16px;\n  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n}\n\n.Field {\n  display: flex;\n  flex-direction: column;\n  align-items: start;\n  gap: 0.25rem;\n}\n\n.Label {\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 500;\n  color: var(--color-gray-900);\n  cursor: default;\n}\n\n.TextInput {\n  box-sizing: border-box;\n  padding-left: 0.875rem;\n  padding-right: 0.75rem;\n  margin: 0;\n  border: 1px solid var(--color-gray-200);\n  width: 100%;\n  height: 2.5rem;\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n}\n\n.TextInput:focus {\n  outline: 2px solid var(--color-blue);\n  outline-offset: -1px;\n}\n\n.Readout {\n  font-size: 0.9rem;\n  color: var(--color-gray-700);\n}\n\n.Hint {\n  font-size: 0.85rem;\n  color: var(--color-gray-600);\n}\n\n/* Select hero styles */\n.SelectTrigger {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 0.75rem;\n  height: 2.5rem;\n  padding-left: 0.875rem;\n  padding-right: 0.75rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: canvas;\n  font-family: inherit;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n  width: 100%;\n  min-width: 10rem;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.SelectValue[data-placeholder] {\n  opacity: 0.6;\n}\n\n.SelectIcon {\n  display: flex;\n}\n\n.SelectPositioner {\n  outline: none;\n  z-index: 1;\n  user-select: none;\n}\n\n.SelectPopup {\n  box-sizing: border-box;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  background-clip: padding-box;\n  color: var(--color-gray-900);\n  min-width: var(--anchor-width);\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  &[data-side='none'] {\n    transition: none;\n    transform: none;\n    opacity: 1;\n    min-width: calc(var(--anchor-width) + 1rem);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n  }\n}\n\n.SelectList {\n  box-sizing: border-box;\n  position: relative;\n  padding-block: 0.25rem;\n  overflow-y: auto;\n  max-height: var(--available-height);\n  scroll-padding-block: 1.5rem;\n}\n\n.SelectItem {\n  box-sizing: border-box;\n  outline: 0;\n  font-size: 0.875rem;\n  line-height: 1rem;\n  padding-block: 0.5rem;\n  padding-left: 0.625rem;\n  padding-right: 1rem;\n  display: grid;\n  gap: 0.5rem;\n  align-items: center;\n  grid-template-columns: 0.75rem 1fr;\n  cursor: default;\n  user-select: none;\n\n  @media (pointer: coarse) {\n    padding-block: 0.625rem;\n    font-size: 0.925rem;\n  }\n\n  [data-side='none'] & {\n    font-size: 1rem;\n    padding-right: 3rem;\n  }\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.25rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.SelectItemIndicator {\n  grid-column-start: 1;\n}\n\n.SelectItemIndicatorIcon {\n  display: block;\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.SelectItemText {\n  grid-column-start: 2;\n}\n\n.SelectScrollArrow {\n  width: 100%;\n  background: canvas;\n  z-index: 1;\n  text-align: center;\n  cursor: default;\n  border-radius: 0.375rem;\n  height: 1rem;\n  font-size: 0.75rem;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n\n  &::before {\n    content: '';\n    position: absolute;\n    width: 100%;\n    height: 100%;\n    left: 0;\n  }\n\n  &[data-direction='up'] {\n    &[data-side='none'] {\n      &::before {\n        top: -100%;\n      }\n    }\n  }\n\n  &[data-direction='down'] {\n    bottom: 0;\n\n    &[data-side='none'] {\n      &::before {\n        bottom: -100%;\n      }\n    }\n  }\n}\n\n/* Combobox hero styles */\n.ComboboxLabel {\n  display: flex;\n  flex-direction: column;\n  gap: 0.25rem;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 500;\n  color: var(--color-gray-900);\n  position: relative;\n}\n\n.ComboboxInputWrapper {\n  position: relative;\n\n  &:has(.ComboboxClear) .ComboboxInput {\n    padding-right: calc(0.5rem + 1.5rem * 2);\n  }\n}\n\n.ComboboxInput {\n  box-sizing: border-box;\n  padding-left: 0.875rem;\n  padding-right: calc(0.5rem + 1.5rem);\n  margin: 0;\n  border: 1px solid var(--color-gray-200);\n  width: 100%;\n  height: 2.5rem;\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n\n  &:focus {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.ComboboxActionButtons {\n  box-sizing: border-box;\n  position: absolute;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  bottom: 0;\n  height: 2.5rem;\n  right: 0.5rem;\n  border-radius: 0.25rem;\n  border: none;\n  color: var(--color-gray-600);\n  padding: 0;\n}\n\n.ComboboxTrigger,\n.ComboboxClear {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 1.5rem;\n  height: 2.5rem;\n  color: var(--color-gray-600);\n  border: none;\n  padding: 0;\n  border-radius: 0.25rem;\n  background: none;\n}\n\n.ComboboxClearIcon,\n.ComboboxTriggerIcon {\n  width: 1rem;\n  height: 1rem;\n}\n\n.ComboboxPositioner {\n  outline: 0;\n}\n\n.ComboboxPopup {\n  box-sizing: border-box;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  width: var(--anchor-width);\n  max-height: 23rem;\n  max-width: var(--available-width);\n  transition:\n    opacity 0.1s,\n    transform 0.1s;\n  transform-origin: var(--transform-origin);\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.95);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.ComboboxList {\n  box-sizing: border-box;\n  overflow-y: auto;\n  overscroll-behavior: contain;\n  padding-block: 0.5rem;\n  scroll-padding-block: 0.5rem;\n  outline: 0;\n  max-height: min(23rem, var(--available-height));\n\n  &[data-empty] {\n    padding: 0;\n  }\n}\n\n.ComboboxItem {\n  box-sizing: border-box;\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  padding-block: 0.5rem;\n  padding-left: 1rem;\n  padding-right: 2rem;\n  display: grid;\n  gap: 0.5rem;\n  align-items: center;\n  grid-template-columns: 0.75rem 1fr;\n  font-size: 1rem;\n  line-height: 1rem;\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.5rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.ComboboxItemText {\n  grid-column-start: 2;\n}\n\n.ComboboxItemIndicator {\n  grid-column-start: 1;\n}\n\n.ComboboxItemIndicatorIcon {\n  display: block;\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.ComboboxEmpty:not(:empty) {\n  font-size: 0.925rem;\n  line-height: 1rem;\n  color: var(--color-gray-600);\n  padding: 1rem;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/forms/autofill.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Autocomplete } from '@base-ui/react/autocomplete';\nimport { Combobox } from '@base-ui/react/combobox';\nimport { Field } from '@base-ui/react/field';\nimport { Select } from '@base-ui/react/select';\nimport styles from './autofill.module.css';\n\nconst states = ['NSW', 'VIC', 'QLD', 'WA', 'SA', 'TAS', 'ACT', 'NT'];\nconst suburbs = ['Darlinghurst', 'Sydney', 'Surry Hills', 'Paddington', 'Redfern'];\nconst addressLines = [\n  '4/59 Test Street',\n  '21 King Street',\n  '88 George Street',\n  '12 Crown Street',\n  '9 Oxford Street',\n];\n\nexport default function Page() {\n  const [stateValue, setStateValue] = React.useState<string | null>(null);\n  const [suburbValue, setSuburbValue] = React.useState<string | null>(null);\n\n  return (\n    <div className={styles.Page}>\n      <div>\n        <h1 className={styles.Title}>Autofill (real browser)</h1>\n        <p className={styles.Description}>\n          This page mirrors an Australian address profile (Australia, NSW, Darlinghurst, 2000). Use\n          Chrome&apos;s saved address profiles to autofill the hidden inputs used by Select and\n          Combobox.\n        </p>\n      </div>\n\n      <form autoComplete=\"on\" className={styles.Form}>\n        <div className={styles.Section}>\n          <div className={styles.SectionTitle}>Address profile fields</div>\n          <div className={styles.Field}>\n            <label className={styles.Label} htmlFor=\"country-name\">\n              Country/Region\n            </label>\n            <input\n              id=\"country-name\"\n              name=\"country\"\n              autoComplete=\"country-name\"\n              className={styles.TextInput}\n              placeholder=\"Australia\"\n            />\n          </div>\n          <div className={styles.Field}>\n            <label className={styles.Label} htmlFor=\"organization\">\n              Organisation\n            </label>\n            <input\n              id=\"organization\"\n              name=\"organization\"\n              autoComplete=\"organization\"\n              className={styles.TextInput}\n              placeholder=\"Software\"\n            />\n          </div>\n          <div className={styles.Field}>\n            <label className={styles.Label} htmlFor=\"full-name\">\n              Name\n            </label>\n            <input\n              id=\"full-name\"\n              name=\"name\"\n              autoComplete=\"name\"\n              className={styles.TextInput}\n              placeholder=\"Jane Doe\"\n            />\n          </div>\n          <div className={styles.Field}>\n            <label className={styles.Label} htmlFor=\"street-address\">\n              Street address\n            </label>\n            <input\n              id=\"street-address\"\n              name=\"street-address\"\n              autoComplete=\"street-address\"\n              className={styles.TextInput}\n              placeholder=\"4/59 Test Street\"\n            />\n          </div>\n          <div className={styles.Row}>\n            <div className={styles.Field}>\n              <Autocomplete.Root items={addressLines} name=\"address-line1\" openOnInputClick={false}>\n                <div className={styles.ComboboxLabel}>\n                  <label htmlFor=\"address-line1-autocomplete\">Address line 1 (Autocomplete)</label>\n                  <div className={styles.ComboboxInputWrapper}>\n                    <Autocomplete.Input\n                      id=\"address-line1-autocomplete\"\n                      className={styles.ComboboxInput}\n                      placeholder=\"4/59 Test Street\"\n                      autoComplete=\"address-line1\"\n                    />\n                    <div className={styles.ComboboxActionButtons}>\n                      <Autocomplete.Trigger\n                        className={styles.ComboboxTrigger}\n                        aria-label=\"Open address line list\"\n                      >\n                        <ChevronDownIcon className={styles.ComboboxTriggerIcon} />\n                      </Autocomplete.Trigger>\n                    </div>\n                  </div>\n                </div>\n                <Autocomplete.Portal>\n                  <Autocomplete.Positioner className={styles.ComboboxPositioner} sideOffset={4}>\n                    <Autocomplete.Popup className={styles.ComboboxPopup}>\n                      <Autocomplete.Empty className={styles.ComboboxEmpty}>\n                        No addresses found.\n                      </Autocomplete.Empty>\n                      <Autocomplete.List className={styles.ComboboxList}>\n                        {(addressLine: string) => (\n                          <Autocomplete.Item\n                            key={addressLine}\n                            value={addressLine}\n                            className={styles.ComboboxItem}\n                          >\n                            <span className={styles.ComboboxItemIndicator} />\n                            <div className={styles.ComboboxItemText}>{addressLine}</div>\n                          </Autocomplete.Item>\n                        )}\n                      </Autocomplete.List>\n                    </Autocomplete.Popup>\n                  </Autocomplete.Positioner>\n                </Autocomplete.Portal>\n              </Autocomplete.Root>\n            </div>\n            <div className={styles.Field}>\n              <Combobox.Root\n                items={suburbs}\n                name=\"address-level2\"\n                autoComplete=\"address-level2\"\n                openOnInputClick={false}\n                value={suburbValue}\n                onValueChange={setSuburbValue}\n              >\n                <div className={styles.ComboboxLabel}>\n                  <label htmlFor=\"suburb-combobox\">Suburb</label>\n                  <div className={styles.ComboboxInputWrapper}>\n                    <Combobox.Input\n                      id=\"suburb-combobox\"\n                      className={styles.ComboboxInput}\n                      placeholder=\"Darlinghurst\"\n                      autoComplete=\"off\"\n                    />\n                    <div className={styles.ComboboxActionButtons}>\n                      <Combobox.Clear className={styles.ComboboxClear} aria-label=\"Clear suburb\">\n                        <ClearIcon className={styles.ComboboxClearIcon} />\n                      </Combobox.Clear>\n                      <Combobox.Trigger\n                        className={styles.ComboboxTrigger}\n                        aria-label=\"Open suburb list\"\n                      >\n                        <ChevronDownIcon className={styles.ComboboxTriggerIcon} />\n                      </Combobox.Trigger>\n                    </div>\n                  </div>\n                </div>\n                <Combobox.Portal>\n                  <Combobox.Positioner className={styles.ComboboxPositioner} sideOffset={4}>\n                    <Combobox.Popup className={styles.ComboboxPopup}>\n                      <Combobox.Empty className={styles.ComboboxEmpty}>\n                        No suburbs found.\n                      </Combobox.Empty>\n                      <Combobox.List className={styles.ComboboxList}>\n                        {(suburb: string) => (\n                          <Combobox.Item\n                            key={suburb}\n                            value={suburb}\n                            className={styles.ComboboxItem}\n                          >\n                            <Combobox.ItemIndicator className={styles.ComboboxItemIndicator}>\n                              <CheckIcon className={styles.ComboboxItemIndicatorIcon} />\n                            </Combobox.ItemIndicator>\n                            <div className={styles.ComboboxItemText}>{suburb}</div>\n                          </Combobox.Item>\n                        )}\n                      </Combobox.List>\n                    </Combobox.Popup>\n                  </Combobox.Positioner>\n                </Combobox.Portal>\n              </Combobox.Root>\n            </div>\n            <Field.Root className={styles.Field}>\n              <Field.Label className={styles.Label} nativeLabel={false} render={<div />}>\n                State\n              </Field.Label>\n              <Select.Root\n                name=\"address-level1\"\n                autoComplete=\"address-level1\"\n                value={stateValue}\n                onValueChange={setStateValue}\n              >\n                <Select.Trigger className={styles.SelectTrigger}>\n                  <Select.Value className={styles.SelectValue} placeholder=\"Select state\" />\n                  <Select.Icon className={styles.SelectIcon}>\n                    <ChevronUpDownIcon />\n                  </Select.Icon>\n                </Select.Trigger>\n                <Select.Portal>\n                  <Select.Positioner className={styles.SelectPositioner} sideOffset={8}>\n                    <Select.Popup className={styles.SelectPopup}>\n                      <Select.ScrollUpArrow className={styles.SelectScrollArrow} />\n                      <Select.List className={styles.SelectList}>\n                        {states.map((state) => (\n                          <Select.Item key={state} value={state} className={styles.SelectItem}>\n                            <Select.ItemIndicator className={styles.SelectItemIndicator}>\n                              <CheckIcon className={styles.SelectItemIndicatorIcon} />\n                            </Select.ItemIndicator>\n                            <Select.ItemText className={styles.SelectItemText}>\n                              {state}\n                            </Select.ItemText>\n                          </Select.Item>\n                        ))}\n                      </Select.List>\n                      <Select.ScrollDownArrow className={styles.SelectScrollArrow} />\n                    </Select.Popup>\n                  </Select.Positioner>\n                </Select.Portal>\n              </Select.Root>\n            </Field.Root>\n            <div className={styles.Field}>\n              <label className={styles.Label} htmlFor=\"postal-code\">\n                Postcode\n              </label>\n              <input\n                id=\"postal-code\"\n                name=\"postal-code\"\n                autoComplete=\"postal-code\"\n                className={styles.TextInput}\n                placeholder=\"2000\"\n              />\n            </div>\n          </div>\n          <div className={styles.Row}>\n            <div className={styles.Field}>\n              <label className={styles.Label} htmlFor=\"phone\">\n                Phone\n              </label>\n              <input\n                id=\"phone\"\n                name=\"tel\"\n                autoComplete=\"tel\"\n                className={styles.TextInput}\n                placeholder=\"04199000199\"\n              />\n            </div>\n            <div className={styles.Field}>\n              <label className={styles.Label} htmlFor=\"email\">\n                Email\n              </label>\n              <input\n                id=\"email\"\n                name=\"email\"\n                autoComplete=\"email\"\n                className={styles.TextInput}\n                placeholder=\"test@example.com\"\n              />\n            </div>\n          </div>\n        </div>\n      </form>\n    </div>\n  );\n}\n\nfunction ChevronUpDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"8\"\n      height=\"12\"\n      viewBox=\"0 0 8 12\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n      {...props}\n    >\n      <path d=\"M0.5 4.5L4 1.5L7.5 4.5\" />\n      <path d=\"M0.5 7.5L4 10.5L7.5 7.5\" />\n    </svg>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentColor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\nfunction ClearIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M18 6L6 18\" />\n      <path d=\"M6 6l12 12\" />\n    </svg>\n  );\n}\n\nfunction ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M6 9l6 6 6-6\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/forms/button-controls.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Field } from '@base-ui/react/field';\nimport { Fieldset } from '@base-ui/react/fieldset';\nimport { Form } from '@base-ui/react/form';\nimport { Checkbox } from '@base-ui/react/checkbox';\nimport { CheckboxGroup } from '@base-ui/react/checkbox-group';\nimport { Radio } from '@base-ui/react/radio';\nimport { RadioGroup } from '@base-ui/react/radio-group';\nimport styles from './form.module.css';\nimport { CheckIcon } from './_icons';\n\nfunction PullRequestsCheckboxGroup() {\n  return (\n    <Field.Root\n      name=\"pull-requests\"\n      className={styles.Field}\n      validate={(value) => {\n        return (value as string[]).length === 0 ? 'Required' : null;\n      }}\n      render={\n        <Fieldset.Root\n          render={<CheckboxGroup defaultValue={[]} className={styles.CheckboxGroup} />}\n        />\n      }\n    >\n      <Fieldset.Legend className={styles.Legend}>Pull Requests (Required)</Fieldset.Legend>\n\n      <Field.Item className={styles.FieldItem}>\n        <Checkbox.Root value=\"merge\" className={styles.Checkbox}>\n          <Checkbox.Indicator className={styles.CheckboxIndicator}>\n            <CheckIcon className={styles.Icon} />\n          </Checkbox.Indicator>\n        </Checkbox.Root>\n        <div className={styles.FieldItemName}>\n          <Field.Label className={styles.Label}>Allow merge commits</Field.Label>\n          <Field.Description className={styles.Description}>\n            Add all commits from the head branch to the base branch with a merge commit.\n          </Field.Description>\n        </div>\n      </Field.Item>\n\n      <Field.Item className={styles.FieldItem}>\n        <Checkbox.Root value=\"squash\" className={styles.Checkbox}>\n          <Checkbox.Indicator className={styles.CheckboxIndicator}>\n            <CheckIcon className={styles.Icon} />\n          </Checkbox.Indicator>\n        </Checkbox.Root>\n        <div className={styles.FieldItemName}>\n          <Field.Label className={styles.Label}>Allow squash merging</Field.Label>\n          <Field.Description className={styles.Description}>\n            Combine all commits from the head branch into a single commit in the base branch.\n          </Field.Description>\n        </div>\n      </Field.Item>\n\n      <Field.Item className={styles.FieldItem}>\n        <Checkbox.Root value=\"rebase\" className={styles.Checkbox}>\n          <Checkbox.Indicator className={styles.CheckboxIndicator}>\n            <CheckIcon className={styles.Icon} />\n          </Checkbox.Indicator>\n        </Checkbox.Root>\n        <div className={styles.FieldItemName}>\n          <Field.Label className={styles.Label}>Allow rebase merging</Field.Label>\n          <Field.Description className={styles.Description}>\n            Add all commits from the head branch onto the base branch individually.\n          </Field.Description>\n        </div>\n      </Field.Item>\n      <Field.Error className={styles.Error} />\n    </Field.Root>\n  );\n}\n\nfunction StickersRadioGroup() {\n  return (\n    <Field.Root\n      name=\"stickers\"\n      className={styles.Field}\n      render={<Fieldset.Root render={<RadioGroup required />} className={styles.RadioGroup} />}\n    >\n      <Fieldset.Legend className={styles.Legend}>Stickers</Fieldset.Legend>\n      <Field.Item className={styles.FieldItem}>\n        <Field.Label className={styles.Label}>\n          <Radio.Root value=\"always\" className={styles.Radio}>\n            <Radio.Indicator className={styles.Indicator} />\n          </Radio.Root>\n          Always animate\n        </Field.Label>\n      </Field.Item>\n\n      <Field.Item className={styles.FieldItem}>\n        <Field.Label\n          className={styles.Label}\n          style={{ display: 'grid', gridRowGap: 0, gridTemplateColumns: 'min-content 1fr' }}\n        >\n          <Radio.Root value=\"interaction\" className={styles.Radio}>\n            <Radio.Indicator className={styles.Indicator} />\n          </Radio.Root>\n          Animate on interaction\n          <Field.Description\n            aria-hidden // don't re-read\n            className={styles.Description}\n            render={<span />}\n            style={{ gridColumn: '2/3' }}\n          >\n            On the desktop client, stickers will animate on hover or focus. On mobile clients,\n            stickers will animate on long-press.\n          </Field.Description>\n        </Field.Label>\n      </Field.Item>\n\n      <Field.Item className={styles.FieldItem}>\n        <Field.Label className={styles.Label}>\n          <Radio.Root value=\"never\" className={styles.Radio}>\n            <Radio.Indicator className={styles.Indicator} />\n          </Radio.Root>\n          Never animate\n        </Field.Label>\n      </Field.Item>\n      <Field.Error className={styles.Error} />\n    </Field.Root>\n  );\n}\n\nfunction DmSpamRadioGroup() {\n  return (\n    <Field.Root\n      className={styles.Field}\n      name=\"dmSpam\"\n      validationMode=\"onChange\"\n      render={<Fieldset.Root render={<RadioGroup required />} className={styles.RadioGroup} />}\n    >\n      <Fieldset.Legend className={styles.Legend}>Direct Message spam</Fieldset.Legend>\n      <Field.Item className={styles.FieldItem}>\n        <Radio.Root value=\"all\" className={styles.Radio}>\n          <Radio.Indicator className={styles.Indicator} />\n        </Radio.Root>\n        <div className={styles.FieldItemName}>\n          <Field.Label className={styles.Label}>Filter all</Field.Label>\n          <Field.Description className={styles.Description}>\n            All DMs will be filtered for spam\n          </Field.Description>\n        </div>\n      </Field.Item>\n\n      <Field.Item className={styles.FieldItem}>\n        <Radio.Root value=\"non-friends\" className={styles.Radio}>\n          <Radio.Indicator className={styles.Indicator} />\n        </Radio.Root>\n        <div className={styles.FieldItemName}>\n          <Field.Label className={styles.Label}>Filter from non-friends</Field.Label>\n          <Field.Description className={styles.Description}>\n            DMs from non-friends will be filtered for spam\n          </Field.Description>\n        </div>\n      </Field.Item>\n\n      <Field.Item className={styles.FieldItem}>\n        <Radio.Root value=\"none\" className={styles.Radio}>\n          <Radio.Indicator className={styles.Indicator} />\n        </Radio.Root>\n        <div className={styles.FieldItemName}>\n          <Field.Label className={styles.Label}>Do not filter</Field.Label>\n          <Field.Description className={styles.Description}>\n            DMs will not be filtered for spam\n          </Field.Description>\n        </div>\n      </Field.Item>\n\n      <Field.Error className={styles.Error} />\n    </Field.Root>\n  );\n}\n\nexport default function ButtonControlsForm() {\n  return (\n    <Form\n      className={styles.Form}\n      onSubmit={(event) => {\n        event.preventDefault();\n        const formData = new FormData(event.currentTarget);\n        console.log('submitting:', formData.getAll('pull-requests'));\n      }}\n      style={{\n        fontFamily: 'var(--font-sans)',\n      }}\n    >\n      <PullRequestsCheckboxGroup />\n      <StickersRadioGroup />\n      <DmSpamRadioGroup />\n\n      <button type=\"submit\" className={styles.Button}>\n        Submit\n      </button>\n    </Form>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/forms/form.module.css",
    "content": ".Form {\n  display: flex;\n  flex-direction: column;\n  gap: 24px;\n  width: 100%;\n  max-width: 20rem;\n}\n\n.Field {\n  display: flex;\n  flex-direction: column;\n  align-items: start;\n  gap: 0.25rem;\n}\n\n.Label {\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 500;\n  color: var(--color-gray-900);\n\n  &:has(.Checkbox),\n  &:has(.Radio),\n  &:has(.Switch) {\n    display: flex;\n    align-items: center;\n    gap: 8px;\n  }\n\n  &:has(.Radio) {\n    font-weight: 400;\n  }\n}\n\n.Legend {\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 500;\n  color: var(--color-gray-900);\n  margin-bottom: 4px;\n}\n\n.Description {\n  margin: 0;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  color: var(--color-gray-600);\n}\n\n.Input {\n  box-sizing: border-box;\n  padding-left: 0.875rem;\n  margin: 0;\n  border: 1px solid var(--color-gray-200);\n  width: 100%;\n  height: 2.5rem;\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  background-color: transparent;\n  color: var(--color-gray-900);\n\n  &:focus {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Error {\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  color: var(--color-red-800);\n}\n\n.Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 500;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  &[data-color='red'] {\n    color: var(--color-red);\n  }\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:disabled {\n    cursor: not-allowed;\n    color: var(--color-gray-400);\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Select {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 0.75rem;\n  height: 2.5rem;\n  padding-left: 0.875rem;\n  padding-right: 0.75rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  cursor: default;\n  user-select: none;\n  min-width: 9rem;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus,\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.SelectIcon {\n  display: flex;\n}\n\n.Popup {\n  box-sizing: border-box;\n  padding-block: 0.25rem;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n  overflow-y: auto;\n  max-height: var(--available-height);\n  min-width: var(--anchor-width);\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  &[data-side='none'] {\n    transition: none;\n    transform: none;\n    opacity: 1;\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Arrow {\n  display: flex;\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n\n.Item {\n  box-sizing: border-box;\n  outline: 0;\n  font-size: 0.875rem;\n  line-height: 1rem;\n  padding-block: 0.5rem;\n  padding-left: 0.625rem;\n  padding-right: 1rem;\n  display: flex;\n  gap: 0.5rem;\n  align-items: center;\n  grid-template-columns: 0.75rem 1fr;\n  cursor: default;\n  user-select: none;\n  scroll-margin-block: 1rem;\n\n  [data-side='none'] & {\n    font-size: 1rem;\n    padding-right: 3rem;\n    min-width: calc(var(--anchor-width) + 1rem);\n  }\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.25rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.ItemIndicator {\n  grid-column-start: 1;\n}\n\n.ItemIndicatorIcon {\n  display: block;\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.ItemText {\n  grid-column-start: 2;\n}\n\n.ScrollArrow {\n  width: 100%;\n  background: canvas;\n  z-index: 1;\n  text-align: center;\n  cursor: default;\n  border-radius: 0.375rem;\n  height: 1rem;\n  font-size: 0.75rem;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n\n  &::before {\n    content: '';\n    position: absolute;\n    width: 100%;\n    height: 100%;\n    left: 0;\n  }\n\n  &[data-direction='up'] {\n    &::before {\n      top: -100%;\n    }\n  }\n\n  &[data-direction='down'] {\n    bottom: 0;\n\n    &::before {\n      bottom: -100%;\n    }\n  }\n}\n\n.RadioGroup,\n.CheckboxGroup {\n  display: flex;\n  flex-direction: column;\n  align-items: start;\n  gap: 0.5rem;\n  color: var(--color-gray-900);\n}\n\n.Caption {\n  font-weight: 500;\n}\n\n.Radio {\n  box-sizing: border-box;\n  display: flex;\n  width: 1.25rem;\n  height: 1.25rem;\n  align-items: center;\n  justify-content: center;\n  flex-shrink: 0;\n  border-radius: 100%;\n  outline: 0;\n  padding: 0;\n  margin: 0;\n  border: none;\n\n  &[data-unchecked] {\n    border: 1px solid var(--color-gray-300);\n    background-color: transparent;\n  }\n\n  &[data-checked] {\n    background-color: var(--color-gray-900);\n  }\n\n  &:focus,\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: 2px;\n  }\n}\n\n.Indicator {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n\n  &[data-unchecked] {\n    display: none;\n  }\n\n  &::before {\n    content: '';\n    border-radius: 100%;\n    width: 0.5rem;\n    height: 0.5rem;\n    background-color: var(--color-gray-50);\n  }\n}\n\n.Caption {\n  font-weight: 500;\n}\n\n.CheckboxGroup:has([data-parent]) .FieldItem:not(:has([data-parent])) {\n  margin-left: 1rem;\n}\n\n.Checkbox {\n  box-sizing: border-box;\n  display: flex;\n  width: 1.25rem;\n  height: 1.25rem;\n  align-items: center;\n  justify-content: center;\n  flex-shrink: 0;\n  border-radius: 0.25rem;\n  outline: 0;\n  padding: 0;\n  margin: 0;\n  border: none;\n\n  &[data-unchecked] {\n    border: 1px solid var(--color-gray-300);\n    background-color: transparent;\n  }\n\n  &[data-checked] {\n    background-color: var(--color-gray-900);\n  }\n\n  &[data-indeterminate] {\n    border: 1px solid var(--color-gray-300);\n    background-color: canvas;\n  }\n\n  &:focus,\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: 2px;\n  }\n}\n\n.CheckboxIndicator {\n  display: flex;\n  color: var(--color-gray-50);\n\n  &[data-unchecked] {\n    display: none;\n  }\n\n  &[data-indeterminate] {\n    color: var(--color-gray-900);\n  }\n}\n\n.Radio,\n.Checkbox {\n  .FieldItem & {\n    align-self: flex-start;\n  }\n}\n\n.Switch {\n  position: relative;\n  display: flex;\n  appearance: none;\n  border: 0;\n  margin: 0;\n  padding: 1px;\n  width: 2.5rem;\n  height: 1.5rem;\n  border-radius: 1.5rem;\n  outline: 1px solid;\n  outline-offset: -1px;\n  background-color: transparent;\n  background-image: linear-gradient(to right, var(--color-gray-700) 35%, var(--color-gray-200) 65%);\n  background-size: 6.5rem 100%;\n  background-position-x: 100%;\n  background-repeat: no-repeat;\n  transition-property: background-position, box-shadow;\n  transition-timing-function: cubic-bezier(0.26, 0.75, 0.38, 0.45);\n  transition-duration: 125ms;\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &[data-checked] {\n    background-position-x: 0%;\n  }\n\n  &[data-checked]:active {\n    background-color: var(--color-gray-500);\n  }\n\n  @media (prefers-color-scheme: light) {\n    box-shadow: var(--color-gray-200) 0 1.5px 2px inset;\n    outline-color: var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    box-shadow: rgb(0 0 0 / 75%) 0 1.5px 2px inset;\n    outline-color: rgb(255 255 255 / 15%);\n    background-image: linear-gradient(\n      to right,\n      var(--color-gray-500) 35%,\n      var(--color-gray-200) 65%\n    );\n\n    &[data-checked] {\n      box-shadow: none;\n    }\n  }\n\n  &:focus,\n  &:focus-visible {\n    &::before {\n      content: '';\n      inset: 0;\n      position: absolute;\n      border-radius: inherit;\n      outline: 2px solid var(--color-blue);\n      outline-offset: 2px;\n    }\n  }\n}\n\n.Thumb {\n  aspect-ratio: 1 / 1;\n  height: 100%;\n  border-radius: 100%;\n  background-color: white;\n  transition: translate 150ms ease;\n\n  &[data-checked] {\n    translate: 1rem 0;\n  }\n\n  @media (prefers-color-scheme: light) {\n    box-shadow:\n      0 0 1px 1px var(--color-gray-100),\n      0 1px 1px var(--color-gray-100),\n      1px 2px 4px -1px var(--color-gray-100);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    box-shadow:\n      0 0 1px 1px rgb(0 0 0 / 25%),\n      0 1px 1px rgb(0 0 0 / 25%),\n      1px 2px 4px -1px rgb(0 0 0 / 25%);\n  }\n}\n\n.Slider {\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n  grid-gap: 0.25rem;\n  grid-row-gap: 0.5rem;\n  width: 100%;\n}\n\n.SliderValue {\n  grid-column-start: 2;\n  margin: 0;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  color: var(--color-gray-900);\n  text-align: end;\n  white-space: nowrap;\n}\n\n.SliderControl {\n  grid-column: 1 / 3;\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  width: 100%;\n  padding-block: 0.75rem;\n  touch-action: none;\n  user-select: none;\n}\n\n.SliderTrack {\n  width: 100%;\n  height: 0.25rem;\n  background-color: var(--color-gray-200);\n  box-shadow: inset 0 0 0 1px var(--color-gray-200);\n  border-radius: 0.25rem;\n  user-select: none;\n}\n\n.SliderIndicator {\n  border-radius: 0.25rem;\n  background-color: var(--color-gray-700);\n  user-select: none;\n}\n\n.SliderThumb {\n  width: 1rem;\n  height: 1rem;\n  border-radius: 100%;\n  background-color: white;\n  outline: 1px solid var(--color-gray-300);\n  user-select: none;\n\n  &:has(:focus),\n  &:has(:focus-visible) {\n    outline: 2px solid var(--color-blue);\n  }\n}\n\n.NumberField {\n  display: flex;\n\n  & .Input {\n    box-sizing: border-box;\n    margin: 0;\n    padding: 0;\n    border-radius: 0;\n    border-top: 1px solid var(--color-gray-200);\n    border-bottom: 1px solid var(--color-gray-200);\n    border-left: none;\n    border-right: none;\n    width: 6rem;\n    height: 2.5rem;\n    font-family: inherit;\n    font-size: 1rem;\n    font-weight: normal;\n    background-color: transparent;\n    color: var(--color-gray-900);\n\n    text-align: center;\n    font-variant-numeric: tabular-nums;\n\n    &:focus {\n      z-index: 1;\n      outline: 2px solid var(--color-blue);\n      outline-offset: -1px;\n    }\n  }\n}\n\n.Decrement,\n.Increment {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 2.5rem;\n  height: 2.5rem;\n  margin: 0;\n  outline: 0;\n  padding: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  background-clip: padding-box;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n}\n\n.Decrement {\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n\n.Increment {\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n\n.FieldItem {\n  display: flex;\n  align-items: center;\n  gap: 8px;\n}\n\n.FieldItemName {\n  margin-top: -1px;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/forms/form.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Field } from '@base-ui/react/field';\nimport { Fieldset } from '@base-ui/react/fieldset';\nimport { Form } from '@base-ui/react/form';\nimport { Select } from '@base-ui/react/select';\nimport { Radio } from '@base-ui/react/radio';\nimport { RadioGroup } from '@base-ui/react/radio-group';\nimport { Checkbox } from '@base-ui/react/checkbox';\nimport { CheckboxGroup } from '@base-ui/react/checkbox-group';\nimport { Switch } from '@base-ui/react/switch';\nimport { NumberField } from '@base-ui/react/number-field';\nimport { Slider } from '@base-ui/react/slider';\nimport { Combobox } from '@base-ui/react/combobox';\nimport { Autocomplete } from '@base-ui/react/autocomplete';\nimport { z } from 'zod';\nimport styles from './form.module.css';\n\nimport { SettingsMetadata, useExperimentSettings } from '../_components/SettingsPanel';\n\nconst fonts = [\n  { value: 'sans', label: 'Sans-serif' },\n  { value: 'serif', label: 'Serif' },\n  { value: 'mono', label: 'Monospace' },\n  { value: 'cursive', label: 'Cursive' },\n];\n\nconst schema = z.object({\n  input: z.string().min(1, 'This field is required'),\n  'required-checkbox': z.enum(['on']),\n  switch: z.enum(['on']),\n  slider: z.number().max(90, 'Too loud'),\n  'range-slider': z.array(z.number()),\n  'number-field': z.number().min(0).max(100),\n  select: z.enum(['sans', 'serif', 'mono', 'cursive']),\n  'radio-group': z.enum(['auto', 'scrolling', 'always']),\n  'multi-select': z.array(z.enum(['sans', 'serif', 'mono', 'cursive'])).min(1),\n  combobox: z.string().min(1, 'Please select a framework'),\n  autocomplete: z.string().min(1, 'Please input a framework'),\n});\n\ninterface Settings {\n  native: boolean;\n  validationMode: Form.Props['validationMode'];\n}\n\nconst frameworks = ['React', 'Vue', 'Angular', 'Svelte', 'Next.js', 'Nuxt.js', 'Gatsby', 'Remix'];\n\ninterface MyFormValues {\n  input: string;\n  'required-checkbox': boolean;\n  switch: boolean;\n  slider: number;\n  'range-slider': number[];\n  'number-field': number;\n  select: string[];\n  'radio-group': string[];\n  'multi-select': string[];\n  combobox: string;\n  autocomplete: string;\n}\n\nexport const settingsMetadata: SettingsMetadata<Settings> = {\n  native: {\n    type: 'boolean',\n    label: 'Native validation',\n    default: true,\n  },\n  validationMode: {\n    type: 'string',\n    label: 'Validation mode',\n    options: ['onSubmit', 'onBlur', 'onChange'],\n    default: 'onSubmit',\n  },\n};\n\nasync function submitForm(values: MyFormValues, native: boolean) {\n  if (native) {\n    return {\n      errors: {},\n    };\n  }\n\n  const result = schema.safeParse(values);\n\n  if (!result.success) {\n    return {\n      errors: z.flattenError(result.error).fieldErrors,\n    };\n  }\n\n  return {\n    errors: {},\n  };\n}\n\nexport default function Page() {\n  const { settings } = useExperimentSettings<Settings>();\n  const native = settings.native;\n  const [errors, setErrors] = React.useState({});\n\n  const numberFieldValueRef = React.useRef<number | null>(null);\n  const [checkboxGroupValue, setCheckboxGroupValue] = React.useState<string[]>([]);\n\n  return (\n    <div style={{ fontFamily: 'var(--font-sans)' }}>\n      <h1>Form</h1>\n\n      <hr style={{ margin: '1rem 0' }} />\n\n      <Form<MyFormValues>\n        className={styles.Form}\n        errors={errors}\n        onFormSubmit={async (values) => {\n          const response = await submitForm(values, native);\n          setErrors(response.errors);\n        }}\n        validationMode={settings.validationMode}\n      >\n        <Field.Root name=\"input\" className={styles.Field}>\n          <Field.Label className={styles.Label}>Local hostname</Field.Label>\n          <Field.Control\n            required={native}\n            placeholder=\"e.g. martin.local\"\n            className={styles.Input}\n          />\n          <Field.Description className={styles.Description}>\n            Use this name to reach this computer from your local subnet\n          </Field.Description>\n          <Field.Error className={styles.Error} />\n        </Field.Root>\n\n        <Field.Root name=\"checkbox\" className={styles.Field}>\n          <Field.Label className={styles.Label}>\n            <Checkbox.Root className={styles.Checkbox}>\n              <Checkbox.Indicator className={styles.CheckboxIndicator}>\n                <CheckIcon />\n              </Checkbox.Indicator>\n            </Checkbox.Root>\n            Reduce motion\n          </Field.Label>\n        </Field.Root>\n\n        <Field.Root name=\"required-checkbox\" className={styles.Field}>\n          <Field.Label className={styles.Label}>\n            <Checkbox.Root required={native} className={styles.Checkbox}>\n              <Checkbox.Indicator className={styles.CheckboxIndicator}>\n                <CheckIcon />\n              </Checkbox.Indicator>\n            </Checkbox.Root>\n            I have downloaded or saved these backup codes\n          </Field.Label>\n          <Field.Error className={styles.Error} />\n        </Field.Root>\n\n        <Field.Root name=\"switch\" className={styles.Field}>\n          <Field.Label className={styles.Label}>\n            <Switch.Root required={native} className={styles.Switch}>\n              <Switch.Thumb className={styles.Thumb} />\n            </Switch.Root>\n            Increase contrast\n          </Field.Label>\n          <Field.Error className={styles.Error} />\n        </Field.Root>\n\n        <Field.Root name=\"slider\" className={styles.Field}>\n          <Slider.Root defaultValue={25} className={styles.Slider}>\n            <Field.Label className={styles.Label}>Volume</Field.Label>\n            <Slider.Value className={styles.SliderValue} />\n            <Slider.Control className={styles.SliderControl}>\n              <Slider.Track className={styles.SliderTrack}>\n                <Slider.Indicator className={styles.SliderIndicator} />\n                <Slider.Thumb className={styles.SliderThumb} />\n              </Slider.Track>\n            </Slider.Control>\n          </Slider.Root>\n          <Field.Error className={styles.Error} />\n        </Field.Root>\n\n        <Field.Root name=\"range-slider\" className={styles.Field}>\n          <Fieldset.Root\n            render={\n              <Slider.Root\n                defaultValue={[500, 1200]}\n                min={100}\n                max={2000}\n                step={1}\n                minStepsBetweenValues={1}\n                className={styles.Slider}\n                format={{\n                  style: 'currency',\n                  currency: 'EUR',\n                }}\n                locale=\"nl-NL\"\n              />\n            }\n          >\n            <Fieldset.Legend className={styles.Label}>Price range</Fieldset.Legend>\n            <Slider.Value className={styles.SliderValue} />\n            <Slider.Control className={styles.SliderControl}>\n              <Slider.Track className={styles.SliderTrack}>\n                <Slider.Indicator className={styles.SliderIndicator} />\n                <Slider.Thumb index={0} className={styles.SliderThumb} />\n                <Slider.Thumb index={1} className={styles.SliderThumb} />\n              </Slider.Track>\n            </Slider.Control>\n          </Fieldset.Root>\n          <Field.Error className={styles.Error} />\n        </Field.Root>\n\n        <Field.Root name=\"number-field\" className={styles.Field}>\n          <Field.Label className={styles.Label}>Quantity</Field.Label>\n          <NumberField.Root\n            required={native}\n            className={styles.Field}\n            onValueChange={(value) => {\n              numberFieldValueRef.current = value;\n            }}\n          >\n            <NumberField.Group className={styles.NumberField}>\n              <NumberField.Decrement className={styles.Decrement}>-</NumberField.Decrement>\n              <NumberField.Input className={styles.Input} />\n              <NumberField.Increment className={styles.Increment}>+</NumberField.Increment>\n            </NumberField.Group>\n          </NumberField.Root>\n          <Field.Error className={styles.Error} />\n        </Field.Root>\n\n        <Field.Root name=\"select\" className={styles.Field}>\n          <Field.Label className={styles.Label}>Font</Field.Label>\n          <Select.Root required={native}>\n            <Select.Trigger className={styles.Select}>\n              <Select.Value />\n              <Select.Icon className={styles.SelectIcon}>\n                <ChevronUpDownIcon />\n              </Select.Icon>\n            </Select.Trigger>\n            <Select.Portal>\n              <Select.Positioner className={styles.Positioner} sideOffset={8}>\n                <Select.ScrollUpArrow className={styles.ScrollArrow} />\n                <Select.Popup className={styles.Popup}>\n                  <Select.Item className={styles.Item} value=\"sans\">\n                    <Select.ItemIndicator className={styles.ItemIndicator}>\n                      <CheckIcon className={styles.ItemIndicatorIcon} />\n                    </Select.ItemIndicator>\n\n                    <Select.ItemText className={styles.ItemText}>Sans-serif</Select.ItemText>\n                  </Select.Item>\n                  <Select.Item className={styles.Item} value=\"serif\">\n                    <Select.ItemIndicator className={styles.ItemIndicator}>\n                      <CheckIcon className={styles.ItemIndicatorIcon} />\n                    </Select.ItemIndicator>\n\n                    <Select.ItemText className={styles.ItemText}>Serif</Select.ItemText>\n                  </Select.Item>\n                  <Select.Item className={styles.Item} value=\"mono\">\n                    <Select.ItemIndicator className={styles.ItemIndicator}>\n                      <CheckIcon className={styles.ItemIndicatorIcon} />\n                    </Select.ItemIndicator>\n\n                    <Select.ItemText className={styles.ItemText}>Monospace</Select.ItemText>\n                  </Select.Item>\n                  <Select.Item className={styles.Item} value=\"cursive\">\n                    <Select.ItemIndicator className={styles.ItemIndicator}>\n                      <CheckIcon className={styles.ItemIndicatorIcon} />\n                    </Select.ItemIndicator>\n\n                    <Select.ItemText className={styles.ItemText}>Cursive</Select.ItemText>\n                  </Select.Item>\n                </Select.Popup>\n                <Select.ScrollDownArrow className={styles.ScrollArrow} />\n              </Select.Positioner>\n            </Select.Portal>\n          </Select.Root>\n          <Field.Error className={styles.Error} />\n        </Field.Root>\n\n        <Field.Root name=\"radio-group\" className={styles.Field}>\n          <Fieldset.Root render={<RadioGroup required={native} className={styles.RadioGroup} />}>\n            <Fieldset.Legend className={styles.Legend}>Show scroll bars</Fieldset.Legend>\n\n            <Field.Item className={styles.FieldItem}>\n              <Field.Label className={styles.Label}>\n                <Radio.Root value=\"auto\" className={styles.Radio}>\n                  <Radio.Indicator className={styles.Indicator} />\n                </Radio.Root>\n                Automatically based on mouse or trackpad\n              </Field.Label>\n            </Field.Item>\n\n            <Field.Item className={styles.FieldItem}>\n              <Field.Label className={styles.Label}>\n                <Radio.Root value=\"scrolling\" className={styles.Radio}>\n                  <Radio.Indicator className={styles.Indicator} />\n                </Radio.Root>\n                When scrolling\n              </Field.Label>\n            </Field.Item>\n\n            <Field.Item className={styles.FieldItem}>\n              <Field.Label className={styles.Label}>\n                <Radio.Root value=\"always\" className={styles.Radio}>\n                  <Radio.Indicator className={styles.Indicator} />\n                </Radio.Root>\n                Always\n              </Field.Label>\n            </Field.Item>\n          </Fieldset.Root>\n          <Field.Error className={styles.Error} />\n        </Field.Root>\n\n        <Field.Root name=\"combobox\" className={styles.Field}>\n          <Field.Label className={styles.Label}>Framework (Combobox)</Field.Label>\n          <Combobox.Root required={native} items={frameworks}>\n            <Combobox.Input placeholder=\"Select a framework\" className={styles.Input} />\n            <Combobox.Portal>\n              <Combobox.Positioner className={styles.Positioner} sideOffset={8}>\n                <Combobox.Popup className={styles.Popup}>\n                  <Combobox.Empty className={styles.Empty}>No frameworks found</Combobox.Empty>\n                  <Combobox.List>\n                    {(framework) => (\n                      <Combobox.Item key={framework} className={styles.Item} value={framework}>\n                        {framework}\n                      </Combobox.Item>\n                    )}\n                  </Combobox.List>\n                </Combobox.Popup>\n              </Combobox.Positioner>\n            </Combobox.Portal>\n          </Combobox.Root>\n          <Field.Error className={styles.Error} />\n        </Field.Root>\n\n        <Field.Root name=\"autocomplete\" className={styles.Field}>\n          <Field.Label className={styles.Label}>Framework (Autocomplete)</Field.Label>\n          <Autocomplete.Root required={native} items={frameworks}>\n            <Autocomplete.Input placeholder=\"Input framework\" className={styles.Input} />\n            <Autocomplete.Portal>\n              <Autocomplete.Positioner className={styles.Positioner} sideOffset={8}>\n                <Autocomplete.Popup className={styles.Popup}>\n                  <Autocomplete.Empty className={styles.Empty}>\n                    No frameworks found\n                  </Autocomplete.Empty>\n                  <Autocomplete.List>\n                    {(framework) => (\n                      <Autocomplete.Item key={framework} className={styles.Item} value={framework}>\n                        {framework}\n                      </Autocomplete.Item>\n                    )}\n                  </Autocomplete.List>\n                </Autocomplete.Popup>\n              </Autocomplete.Positioner>\n            </Autocomplete.Portal>\n          </Autocomplete.Root>\n          <Field.Error className={styles.Error} />\n        </Field.Root>\n\n        <Field.Root name=\"checkbox-group\" className={styles.Field}>\n          <Fieldset.Root\n            render={\n              <CheckboxGroup\n                value={checkboxGroupValue}\n                onValueChange={setCheckboxGroupValue}\n                allValues={ALL_CHECKBOX_GROUP_VALUES}\n                className={styles.CheckboxGroup}\n              />\n            }\n          >\n            <Fieldset.Legend className={styles.Legend}>Content blocking</Fieldset.Legend>\n            <Field.Item className={styles.FieldItem}>\n              <Field.Label className={styles.Label}>\n                <Checkbox.Root parent className={styles.Checkbox}>\n                  <Checkbox.Indicator\n                    className={styles.CheckboxIndicator}\n                    render={(props, state) => (\n                      <span {...props}>\n                        {state.indeterminate ? (\n                          <HorizontalRuleIcon className={styles.Icon} />\n                        ) : (\n                          <CheckIcon className={styles.Icon} />\n                        )}\n                      </span>\n                    )}\n                  />\n                </Checkbox.Root>\n                Block everything\n              </Field.Label>\n            </Field.Item>\n\n            <Field.Item className={styles.FieldItem}>\n              <Field.Label className={styles.Label}>\n                <Checkbox.Root value=\"ads\" className={styles.Checkbox}>\n                  <Checkbox.Indicator className={styles.CheckboxIndicator}>\n                    <CheckIcon className={styles.Icon} />\n                  </Checkbox.Indicator>\n                </Checkbox.Root>\n                Block ads\n              </Field.Label>\n            </Field.Item>\n\n            <Field.Item className={styles.FieldItem}>\n              <Checkbox.Root value=\"annoyances\" className={styles.Checkbox}>\n                <Checkbox.Indicator className={styles.CheckboxIndicator}>\n                  <CheckIcon className={styles.Icon} />\n                </Checkbox.Indicator>\n              </Checkbox.Root>\n              <div className={styles.FieldItemName}>\n                <Field.Label className={styles.Label}>Block annoyances</Field.Label>\n                <Field.Description className={styles.Description}>\n                  Blocks social media content and in-page pop-ups\n                </Field.Description>\n              </div>\n            </Field.Item>\n\n            <Field.Item className={styles.FieldItem}>\n              <Field.Label className={styles.Label}>\n                <Checkbox.Root value=\"comments\" className={styles.Checkbox}>\n                  <Checkbox.Indicator className={styles.CheckboxIndicator}>\n                    <CheckIcon className={styles.Icon} />\n                  </Checkbox.Indicator>\n                </Checkbox.Root>\n                Block comments\n              </Field.Label>\n            </Field.Item>\n\n            <Field.Item className={styles.FieldItem}>\n              <Field.Label className={styles.Label}>\n                <Checkbox.Root value=\"trackers\" className={styles.Checkbox}>\n                  <Checkbox.Indicator className={styles.CheckboxIndicator}>\n                    <CheckIcon className={styles.Icon} />\n                  </Checkbox.Indicator>\n                </Checkbox.Root>\n                Block trackers\n              </Field.Label>\n            </Field.Item>\n          </Fieldset.Root>\n        </Field.Root>\n\n        <Field.Root name=\"multi-select\" className={styles.Field}>\n          <Field.Label className={styles.Label}>Fonts (multiple)</Field.Label>\n          <Select.Root multiple required={native} items={fonts}>\n            <Select.Trigger className={styles.Select}>\n              <Select.Value>\n                {(value: string[]) =>\n                  value.length > 0\n                    ? value.map((v) => fonts.find((f) => f.value === v)?.label).join(', ')\n                    : 'Select fonts…'\n                }\n              </Select.Value>\n              <Select.Icon className={styles.SelectIcon}>\n                <ChevronUpDownIcon />\n              </Select.Icon>\n            </Select.Trigger>\n            <Select.Portal>\n              <Select.Positioner className={styles.Positioner} sideOffset={8}>\n                <Select.ScrollUpArrow className={styles.ScrollArrow} />\n                <Select.Popup className={styles.Popup}>\n                  {fonts.map(({ value, label }) => (\n                    <Select.Item key={value} className={styles.Item} value={value}>\n                      <Select.ItemIndicator className={styles.ItemIndicator}>\n                        <CheckIcon className={styles.ItemIndicatorIcon} />\n                      </Select.ItemIndicator>\n                      <Select.ItemText className={styles.ItemText}>{label}</Select.ItemText>\n                    </Select.Item>\n                  ))}\n                </Select.Popup>\n                <Select.ScrollDownArrow className={styles.ScrollArrow} />\n              </Select.Positioner>\n            </Select.Portal>\n          </Select.Root>\n          <Field.Error className={styles.Error} />\n        </Field.Root>\n\n        <button type=\"submit\" className={styles.Button}>\n          Submit\n        </button>\n      </Form>\n    </div>\n  );\n}\n\nconst ALL_CHECKBOX_GROUP_VALUES = ['ads', 'annoyances', 'comments', 'trackers'];\n\nfunction ChevronUpDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"8\"\n      height=\"12\"\n      viewBox=\"0 0 8 12\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"1.5\"\n      {...props}\n    >\n      <path d=\"M0.5 4.5L4 1.5L7.5 4.5\" />\n      <path d=\"M0.5 7.5L4 10.5L7.5 7.5\" />\n    </svg>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\nfunction HorizontalRuleIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"10\"\n      height=\"10\"\n      viewBox=\"0 0 24 24\"\n      fill=\"currentcolor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <line\n        x1=\"3\"\n        y1=\"12\"\n        x2=\"21\"\n        y2=\"12\"\n        stroke=\"currentColor\"\n        strokeWidth={3}\n        strokeLinecap=\"round\"\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/forms/rhf.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useForm, Controller, type Mode } from 'react-hook-form';\nimport { Form } from '@base-ui/react/form';\nimport { Fieldset } from '@base-ui/react/fieldset';\nimport { Field } from '@base-ui/react/field';\nimport { Checkbox } from '@base-ui/react/checkbox';\nimport { Switch } from '@base-ui/react/switch';\nimport { Slider } from '@base-ui/react/slider';\nimport { NumberField } from '@base-ui/react/number-field';\nimport { Select } from '@base-ui/react/select';\nimport { RadioGroup } from '@base-ui/react/radio-group';\nimport { Radio } from '@base-ui/react/radio';\nimport { CheckboxGroup } from '@base-ui/react/checkbox-group';\n\nimport styles from './form.module.css';\nimport { CheckIcon, ChevronUpDownIcon, HorizontalRuleIcon } from './_icons';\n\nimport { SettingsMetadata, useExperimentSettings } from '../_components/SettingsPanel';\n\ninterface Settings {\n  validationMode: Mode;\n}\n\ninterface FormValues {\n  username: string;\n  checkbox: boolean;\n  requiredCheckbox: boolean;\n  switch: boolean;\n  slider: number;\n  numberField: number;\n  selectCountry: string;\n  radioGroup: string;\n  checkboxGroup: string[];\n}\n\nexport const settingsMetadata: SettingsMetadata<Settings> = {\n  validationMode: {\n    type: 'string',\n    label: 'Validation mode',\n    options: ['onSubmit', 'onBlur'],\n    default: 'onBlur',\n  },\n};\n\nexport default function ExampleForm() {\n  const { settings } = useExperimentSettings<Settings>();\n\n  const [loading, setLoading] = React.useState(false);\n\n  const { handleSubmit, control, reset, setError, setFocus } = useForm<FormValues>({\n    defaultValues: {\n      username: '',\n      checkbox: true,\n      requiredCheckbox: false,\n      switch: false,\n      slider: 45,\n      numberField: 5,\n      selectCountry: '',\n      radioGroup: 'auto',\n      checkboxGroup: [],\n    },\n    mode: settings.validationMode,\n  });\n\n  async function submitForm(data: FormValues) {\n    setLoading(true);\n\n    // Mimic a server response\n    await new Promise((resolve) => {\n      setTimeout(resolve, 500);\n    });\n\n    console.log('submitted', data);\n\n    if (data.username === 'alice') {\n      setError('username', {\n        type: 'serverError',\n        message: 'Username is already taken',\n      });\n      setFocus('username');\n    }\n\n    setLoading(false);\n    return { success: true };\n  }\n\n  return (\n    <div style={{ fontFamily: 'var(--font-sans)' }}>\n      <h1>react-hook-form</h1>\n\n      <hr style={{ margin: '1rem 0' }} />\n      <Form className={styles.Form} onSubmit={handleSubmit(submitForm)}>\n        <Controller\n          name=\"username\"\n          control={control}\n          rules={{\n            required: 'Username is required',\n            minLength: { value: 2, message: 'Too short' },\n          }}\n          render={({ field, fieldState }) => {\n            return (\n              <Field.Root\n                name={field.name}\n                invalid={fieldState.invalid}\n                touched={fieldState.isTouched}\n                dirty={fieldState.isDirty}\n                className={styles.Field}\n              >\n                <Field.Label className={styles.Label}>Username</Field.Label>\n                <Field.Control\n                  placeholder=\"Required\"\n                  className={styles.Input}\n                  value={field.value}\n                  onBlur={field.onBlur}\n                  onValueChange={field.onChange}\n                  ref={field.ref}\n                />\n                <Field.Error className={styles.Error} match={!!fieldState.error}>\n                  {fieldState.error?.message ?? ''}\n                </Field.Error>\n              </Field.Root>\n            );\n          }}\n        />\n\n        <Controller\n          name=\"checkbox\"\n          control={control}\n          render={({ field, fieldState }) => {\n            return (\n              <Field.Root\n                name={field.name}\n                invalid={fieldState.invalid}\n                touched={fieldState.isTouched}\n                dirty={fieldState.isDirty}\n                className={styles.Field}\n              >\n                <Field.Label className={styles.Label}>\n                  <Checkbox.Root\n                    checked={field.value}\n                    onBlur={field.onBlur}\n                    onCheckedChange={field.onChange}\n                    inputRef={field.ref}\n                    className={styles.Checkbox}\n                  >\n                    <Checkbox.Indicator className={styles.CheckboxIndicator}>\n                      <CheckIcon />\n                    </Checkbox.Indicator>\n                  </Checkbox.Root>\n                  Reduce motion\n                </Field.Label>\n              </Field.Root>\n            );\n          }}\n        />\n\n        <Controller\n          name=\"requiredCheckbox\"\n          control={control}\n          rules={{\n            required: 'You must check this to continue',\n          }}\n          render={({ field, fieldState }) => {\n            return (\n              <Field.Root\n                name={field.name}\n                invalid={fieldState.invalid}\n                touched={fieldState.isTouched}\n                dirty={fieldState.isDirty}\n                className={styles.Field}\n              >\n                <Field.Label className={styles.Label}>\n                  <Checkbox.Root\n                    checked={field.value}\n                    onBlur={field.onBlur}\n                    onCheckedChange={field.onChange}\n                    inputRef={field.ref}\n                    className={styles.Checkbox}\n                  >\n                    <Checkbox.Indicator className={styles.CheckboxIndicator}>\n                      <CheckIcon />\n                    </Checkbox.Indicator>\n                  </Checkbox.Root>\n                  I have read the EULA\n                </Field.Label>\n                <Field.Error className={styles.Error} match={!!fieldState.error}>\n                  {fieldState.error?.message ?? ''}\n                </Field.Error>\n              </Field.Root>\n            );\n          }}\n        />\n\n        <Controller\n          name=\"switch\"\n          control={control}\n          render={({ field, fieldState }) => {\n            return (\n              <Field.Root\n                name={field.name}\n                invalid={fieldState.invalid}\n                touched={fieldState.isTouched}\n                dirty={fieldState.isDirty}\n                className={styles.Field}\n              >\n                <Field.Label className={styles.Label}>\n                  <Switch.Root\n                    checked={field.value}\n                    onBlur={field.onBlur}\n                    onCheckedChange={field.onChange}\n                    inputRef={field.ref}\n                    className={styles.Switch}\n                  >\n                    <Switch.Thumb className={styles.Thumb} />\n                  </Switch.Root>\n                  Night shift\n                </Field.Label>\n              </Field.Root>\n            );\n          }}\n        />\n\n        <Controller\n          name=\"slider\"\n          control={control}\n          rules={{\n            validate: (value) => {\n              if (value > 90) {\n                return 'Too loud';\n              }\n              return true;\n            },\n          }}\n          render={({ field, fieldState }) => {\n            return (\n              <Field.Root\n                name={field.name}\n                invalid={fieldState.invalid}\n                touched={fieldState.isTouched}\n                dirty={fieldState.isDirty}\n                className={styles.Field}\n              >\n                <Field.Label className={styles.Label}>Volume</Field.Label>\n                <Slider.Root\n                  value={field.value}\n                  onValueChange={field.onChange}\n                  className={styles.Slider}\n                >\n                  <Slider.Control className={styles.SliderControl}>\n                    <Slider.Track className={styles.SliderTrack}>\n                      <Slider.Indicator className={styles.SliderIndicator} />\n                      <Slider.Thumb\n                        inputRef={field.ref}\n                        onBlur={field.onBlur}\n                        className={styles.SliderThumb}\n                      />\n                    </Slider.Track>\n                  </Slider.Control>\n                </Slider.Root>\n                <Field.Error className={styles.Error} match={!!fieldState.error}>\n                  {fieldState.error?.message ?? ''}\n                </Field.Error>\n              </Field.Root>\n            );\n          }}\n        />\n\n        <Controller\n          name=\"numberField\"\n          control={control}\n          rules={{\n            validate: (value) => {\n              if (value > 20) {\n                return 'Out of stock';\n              }\n              return true;\n            },\n          }}\n          render={({ field, fieldState }) => {\n            return (\n              <Field.Root\n                name={field.name}\n                invalid={fieldState.invalid}\n                touched={fieldState.isTouched}\n                dirty={fieldState.isDirty}\n                className={styles.Field}\n              >\n                <Field.Label className={styles.Label}>Quantity</Field.Label>\n                <NumberField.Root\n                  value={field.value}\n                  onValueChange={field.onChange}\n                  className={styles.Field}\n                >\n                  <NumberField.Group className={styles.NumberField}>\n                    <NumberField.Decrement className={styles.Decrement}>-</NumberField.Decrement>\n                    <NumberField.Input\n                      onBlur={field.onBlur}\n                      ref={field.ref}\n                      className={styles.Input}\n                    />\n                    <NumberField.Increment className={styles.Increment}>+</NumberField.Increment>\n                  </NumberField.Group>\n                </NumberField.Root>\n                <Field.Error className={styles.Error} match={!!fieldState.error}>\n                  {fieldState.error?.message ?? ''}\n                </Field.Error>\n              </Field.Root>\n            );\n          }}\n        />\n\n        <Controller\n          name=\"selectCountry\"\n          control={control}\n          rules={{\n            required: 'You must select a country',\n          }}\n          render={({ field, fieldState }) => {\n            return (\n              <Field.Root\n                name={field.name}\n                invalid={fieldState.invalid}\n                touched={fieldState.isTouched}\n                dirty={fieldState.isDirty}\n                className={styles.Field}\n              >\n                <Field.Label className={styles.Label}>Country</Field.Label>\n                <Select.Root\n                  value={field.value}\n                  onValueChange={field.onChange}\n                  inputRef={field.ref}\n                >\n                  <Select.Trigger onBlur={field.onBlur} className={styles.Select}>\n                    <Select.Value>\n                      {(value) => {\n                        if (value == null) {\n                          return 'Select…';\n                        }\n                        const country = COUNTRIES.find((c) => c.iso2 === value);\n                        return country?.name;\n                      }}\n                    </Select.Value>\n                    <Select.Icon className={styles.SelectIcon}>\n                      <ChevronUpDownIcon />\n                    </Select.Icon>\n                  </Select.Trigger>\n                  <Select.Portal>\n                    <Select.Positioner className={styles.Positioner} sideOffset={8}>\n                      <Select.ScrollUpArrow className={styles.ScrollArrow} />\n                      <Select.Popup className={styles.Popup}>\n                        {COUNTRIES.map((country) => {\n                          return (\n                            <Select.Item\n                              key={country.iso3}\n                              value={country.iso2}\n                              className={styles.Item}\n                            >\n                              <Select.ItemIndicator className={styles.ItemIndicator}>\n                                <CheckIcon className={styles.ItemIndicatorIcon} />\n                              </Select.ItemIndicator>\n                              <Select.ItemText className={styles.ItemText}>\n                                {country.name}\n                              </Select.ItemText>\n                            </Select.Item>\n                          );\n                        })}\n                      </Select.Popup>\n                      <Select.ScrollDownArrow className={styles.ScrollArrow} />\n                    </Select.Positioner>\n                  </Select.Portal>\n                </Select.Root>\n                <Field.Error className={styles.Error} match={!!fieldState.error}>\n                  {fieldState.error?.message ?? ''}\n                </Field.Error>\n              </Field.Root>\n            );\n          }}\n        />\n\n        <Controller\n          name=\"radioGroup\"\n          control={control}\n          rules={{\n            required: 'This field is required',\n          }}\n          render={({ field, fieldState }) => {\n            // TODO: where exactly to put field.onBlur?\n            return (\n              <Field.Root\n                name={field.name}\n                invalid={fieldState.invalid}\n                touched={fieldState.isTouched}\n                dirty={fieldState.isDirty}\n                render={<Fieldset.Root />}\n                className={styles.Field}\n              >\n                <Fieldset.Legend className={styles.Legend}>Show scroll bars</Fieldset.Legend>\n                <RadioGroup\n                  value={field.value}\n                  onValueChange={field.onChange}\n                  inputRef={field.ref}\n                  className={styles.RadioGroup}\n                >\n                  <Field.Label className={styles.Label}>\n                    <Radio.Root value=\"auto\" className={styles.Radio}>\n                      <Radio.Indicator className={styles.Indicator} />\n                    </Radio.Root>\n                    Automatically based on mouse or trackpad\n                  </Field.Label>\n\n                  <Field.Label className={styles.Label}>\n                    <Radio.Root value=\"scrolling\" className={styles.Radio}>\n                      <Radio.Indicator className={styles.Indicator} />\n                    </Radio.Root>\n                    When scrolling\n                  </Field.Label>\n\n                  <Field.Label className={styles.Label}>\n                    <Radio.Root value=\"always\" className={styles.Radio}>\n                      <Radio.Indicator className={styles.Indicator} />\n                    </Radio.Root>\n                    Always\n                  </Field.Label>\n                </RadioGroup>\n                <Field.Error className={styles.Error} match={!!fieldState.error}>\n                  {fieldState.error?.message ?? ''}\n                </Field.Error>\n              </Field.Root>\n            );\n          }}\n        />\n\n        <Controller\n          name=\"checkboxGroup\"\n          control={control}\n          rules={{\n            required: 'This field is required',\n          }}\n          render={({ field, fieldState }) => {\n            // TODO: where exactly to put field.onBlur?\n            return (\n              <Field.Root\n                name={field.name}\n                invalid={fieldState.invalid}\n                touched={fieldState.isTouched}\n                dirty={fieldState.isDirty}\n                render={<Fieldset.Root />}\n                className={styles.Field}\n              >\n                <Fieldset.Legend className={styles.Legend}>Content blocking</Fieldset.Legend>\n                <CheckboxGroup\n                  aria-labelledby=\"parent-label\"\n                  value={field.value}\n                  onValueChange={field.onChange}\n                  allValues={ALL_CHECKBOX_GROUP_VALUES}\n                  className={styles.CheckboxGroup}\n                  style={{ marginLeft: '1rem' }}\n                >\n                  <Field.Label\n                    className={styles.Label}\n                    style={{ marginLeft: '-1rem' }}\n                    id=\"parent-label\"\n                  >\n                    <Checkbox.Root\n                      parent\n                      onBlur={field.onBlur}\n                      inputRef={field.ref}\n                      className={styles.Checkbox}\n                    >\n                      <Checkbox.Indicator\n                        className={styles.CheckboxIndicator}\n                        render={(props, state) => (\n                          <span {...props}>\n                            {state.indeterminate ? (\n                              <HorizontalRuleIcon className={styles.Icon} />\n                            ) : (\n                              <CheckIcon className={styles.Icon} />\n                            )}\n                          </span>\n                        )}\n                      />\n                    </Checkbox.Root>\n                    Block everything\n                  </Field.Label>\n\n                  <Field.Label className={styles.Label}>\n                    <Checkbox.Root value=\"ads\" onBlur={field.onBlur} className={styles.Checkbox}>\n                      <Checkbox.Indicator className={styles.CheckboxIndicator}>\n                        <CheckIcon className={styles.Icon} />\n                      </Checkbox.Indicator>\n                    </Checkbox.Root>\n                    Block ads\n                  </Field.Label>\n\n                  <Field.Label className={styles.Label}>\n                    <Checkbox.Root\n                      value=\"annoyances\"\n                      onBlur={field.onBlur}\n                      className={styles.Checkbox}\n                    >\n                      <Checkbox.Indicator className={styles.CheckboxIndicator}>\n                        <CheckIcon className={styles.Icon} />\n                      </Checkbox.Indicator>\n                    </Checkbox.Root>\n                    Block annoyances\n                  </Field.Label>\n\n                  <Field.Label className={styles.Label}>\n                    <Checkbox.Root\n                      value=\"comments\"\n                      onBlur={field.onBlur}\n                      className={styles.Checkbox}\n                    >\n                      <Checkbox.Indicator className={styles.CheckboxIndicator}>\n                        <CheckIcon className={styles.Icon} />\n                      </Checkbox.Indicator>\n                    </Checkbox.Root>\n                    Block comments\n                  </Field.Label>\n\n                  <Field.Label className={styles.Label}>\n                    <Checkbox.Root\n                      value=\"trackers\"\n                      onBlur={field.onBlur}\n                      className={styles.Checkbox}\n                    >\n                      <Checkbox.Indicator className={styles.CheckboxIndicator}>\n                        <CheckIcon className={styles.Icon} />\n                      </Checkbox.Indicator>\n                    </Checkbox.Root>\n                    Block trackers\n                  </Field.Label>\n                </CheckboxGroup>\n                <Field.Error className={styles.Error} match={!!fieldState.error}>\n                  {fieldState.error?.message ?? ''}\n                </Field.Error>\n              </Field.Root>\n            );\n          }}\n        />\n\n        <button disabled={loading} type=\"submit\" className={styles.Button}>\n          Submit\n        </button>\n\n        <button\n          type=\"button\"\n          disabled={loading}\n          data-color=\"red\"\n          className={styles.Button}\n          onClick={() => reset()}\n        >\n          Reset\n        </button>\n      </Form>\n    </div>\n  );\n}\n\nconst ALL_CHECKBOX_GROUP_VALUES = ['ads', 'annoyances', 'comments', 'trackers'];\nconst COUNTRIES = [\n  {\n    iso2: 'BR',\n    iso3: 'BRA',\n    name: 'Brazil',\n    capital: 'Brasília',\n    region: 'Americas',\n    subregion: 'South America',\n    states: [\n      {\n        code: 'AC',\n        name: 'Acre',\n        subdivision: null,\n      },\n      {\n        code: 'AL',\n        name: 'Alagoas',\n        subdivision: null,\n      },\n      {\n        code: 'AP',\n        name: 'Amapá',\n        subdivision: null,\n      },\n      {\n        code: 'AM',\n        name: 'Amazonas',\n        subdivision: null,\n      },\n      {\n        code: 'BA',\n        name: 'Bahia',\n        subdivision: null,\n      },\n      {\n        code: 'CE',\n        name: 'Ceará',\n        subdivision: null,\n      },\n      {\n        code: 'DF',\n        name: 'Distrito Federal',\n        subdivision: null,\n      },\n      {\n        code: 'ES',\n        name: 'Espírito Santo',\n        subdivision: null,\n      },\n      {\n        code: 'GO',\n        name: 'Goiás',\n        subdivision: null,\n      },\n      {\n        code: 'MA',\n        name: 'Maranhão',\n        subdivision: null,\n      },\n      {\n        code: 'MT',\n        name: 'Mato Grosso',\n        subdivision: null,\n      },\n      {\n        code: 'MS',\n        name: 'Mato Grosso do Sul',\n        subdivision: null,\n      },\n      {\n        code: 'MG',\n        name: 'Minas Gerais',\n        subdivision: null,\n      },\n      {\n        code: 'PR',\n        name: 'Paraná',\n        subdivision: null,\n      },\n      {\n        code: 'PB',\n        name: 'Paraíba',\n        subdivision: null,\n      },\n      {\n        code: 'PA',\n        name: 'Pará',\n        subdivision: null,\n      },\n      {\n        code: 'PE',\n        name: 'Pernambuco',\n        subdivision: null,\n      },\n      {\n        code: 'PI',\n        name: 'Piauí',\n        subdivision: null,\n      },\n      {\n        code: 'RN',\n        name: 'Rio Grande do Norte',\n        subdivision: null,\n      },\n      {\n        code: 'RS',\n        name: 'Rio Grande do Sul',\n        subdivision: null,\n      },\n      {\n        code: 'RJ',\n        name: 'Rio de Janeiro',\n        subdivision: null,\n      },\n      {\n        code: 'RO',\n        name: 'Rondônia',\n        subdivision: null,\n      },\n      {\n        code: 'RR',\n        name: 'Roraima',\n        subdivision: null,\n      },\n      {\n        code: 'SC',\n        name: 'Santa Catarina',\n        subdivision: null,\n      },\n      {\n        code: 'SE',\n        name: 'Sergipe',\n        subdivision: null,\n      },\n      {\n        code: 'SP',\n        name: 'São Paulo',\n        subdivision: null,\n      },\n      {\n        code: 'TO',\n        name: 'Tocantins',\n        subdivision: null,\n      },\n    ],\n  },\n  {\n    iso2: 'JP',\n    iso3: 'JPN',\n    name: 'Japan',\n    capital: 'Tokyo',\n    region: 'Asia',\n    subregion: 'Eastern Asia',\n    states: [\n      {\n        code: '23',\n        name: 'Aiti',\n        subdivision: null,\n      },\n      {\n        code: '05',\n        name: 'Akita',\n        subdivision: null,\n      },\n      {\n        code: '02',\n        name: 'Aomori',\n        subdivision: null,\n      },\n      {\n        code: '38',\n        name: 'Ehime',\n        subdivision: null,\n      },\n      {\n        code: '21',\n        name: 'Gihu',\n        subdivision: null,\n      },\n      {\n        code: '10',\n        name: 'Gunma',\n        subdivision: null,\n      },\n      {\n        code: '34',\n        name: 'Hirosima',\n        subdivision: null,\n      },\n      {\n        code: '01',\n        name: 'Hokkaidô',\n        subdivision: null,\n      },\n      {\n        code: '18',\n        name: 'Hukui',\n        subdivision: null,\n      },\n      {\n        code: '40',\n        name: 'Hukuoka',\n        subdivision: null,\n      },\n      {\n        code: '07',\n        name: 'Hukusima',\n        subdivision: null,\n      },\n      {\n        code: '28',\n        name: 'Hyôgo',\n        subdivision: null,\n      },\n      {\n        code: '08',\n        name: 'Ibaraki',\n        subdivision: null,\n      },\n      {\n        code: '17',\n        name: 'Isikawa',\n        subdivision: null,\n      },\n      {\n        code: '03',\n        name: 'Iwate',\n        subdivision: null,\n      },\n      {\n        code: '37',\n        name: 'Kagawa',\n        subdivision: null,\n      },\n      {\n        code: '46',\n        name: 'Kagosima',\n        subdivision: null,\n      },\n      {\n        code: '14',\n        name: 'Kanagawa',\n        subdivision: null,\n      },\n      {\n        code: '43',\n        name: 'Kumamoto',\n        subdivision: null,\n      },\n      {\n        code: '26',\n        name: 'Kyôto',\n        subdivision: null,\n      },\n      {\n        code: '39',\n        name: 'Kôti',\n        subdivision: null,\n      },\n      {\n        code: '24',\n        name: 'Mie',\n        subdivision: null,\n      },\n      {\n        code: '04',\n        name: 'Miyagi',\n        subdivision: null,\n      },\n      {\n        code: '45',\n        name: 'Miyazaki',\n        subdivision: null,\n      },\n      {\n        code: '20',\n        name: 'Nagano',\n        subdivision: null,\n      },\n      {\n        code: '42',\n        name: 'Nagasaki',\n        subdivision: null,\n      },\n      {\n        code: '29',\n        name: 'Nara',\n        subdivision: null,\n      },\n      {\n        code: '15',\n        name: 'Niigata',\n        subdivision: null,\n      },\n      {\n        code: '33',\n        name: 'Okayama',\n        subdivision: null,\n      },\n      {\n        code: '47',\n        name: 'Okinawa',\n        subdivision: null,\n      },\n      {\n        code: '41',\n        name: 'Saga',\n        subdivision: null,\n      },\n      {\n        code: '11',\n        name: 'Saitama',\n        subdivision: null,\n      },\n      {\n        code: '25',\n        name: 'Siga',\n        subdivision: null,\n      },\n      {\n        code: '32',\n        name: 'Simane',\n        subdivision: null,\n      },\n      {\n        code: '22',\n        name: 'Sizuoka',\n        subdivision: null,\n      },\n      {\n        code: '12',\n        name: 'Tiba',\n        subdivision: null,\n      },\n      {\n        code: '36',\n        name: 'Tokusima',\n        subdivision: null,\n      },\n      {\n        code: '09',\n        name: 'Totigi',\n        subdivision: null,\n      },\n      {\n        code: '31',\n        name: 'Tottori',\n        subdivision: null,\n      },\n      {\n        code: '16',\n        name: 'Toyama',\n        subdivision: null,\n      },\n      {\n        code: '13',\n        name: 'Tôkyô',\n        subdivision: null,\n      },\n      {\n        code: '30',\n        name: 'Wakayama',\n        subdivision: null,\n      },\n      {\n        code: '06',\n        name: 'Yamagata',\n        subdivision: null,\n      },\n      {\n        code: '35',\n        name: 'Yamaguti',\n        subdivision: null,\n      },\n      {\n        code: '19',\n        name: 'Yamanasi',\n        subdivision: null,\n      },\n      {\n        code: '44',\n        name: 'Ôita',\n        subdivision: null,\n      },\n      {\n        code: '27',\n        name: 'Ôsaka',\n        subdivision: null,\n      },\n    ],\n  },\n  {\n    iso2: 'SG',\n    iso3: 'SGP',\n    name: 'Singapore',\n    capital: 'Singapore',\n    region: 'Asia',\n    subregion: 'South-Eastern Asia',\n    states: [\n      {\n        code: '01',\n        name: 'Central Singapore',\n        subdivision: null,\n      },\n      {\n        code: '02',\n        name: 'North East',\n        subdivision: null,\n      },\n      {\n        code: '03',\n        name: 'North West',\n        subdivision: null,\n      },\n      {\n        code: '04',\n        name: 'South East',\n        subdivision: null,\n      },\n      {\n        code: '05',\n        name: 'South West',\n        subdivision: null,\n      },\n    ],\n  },\n];\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/layout.css",
    "content": "@layer base {\n  body:has(.experiments-layout-root) {\n    position: relative;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/layout.tsx",
    "content": "import * as React from 'react';\nimport './layout.css';\n\nexport default function ExperimentsLayout({ children }: React.PropsWithChildren) {\n  return <div className=\"experiments-layout-root\">{children}</div>;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/long-select.module.css",
    "content": ".Select {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 0.75rem;\n  height: 2.5rem;\n  padding-left: 0.875rem;\n  padding-right: 0.75rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  cursor: default;\n  -webkit-user-select: none;\n  user-select: none;\n  min-width: 9rem;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.SelectIcon {\n  display: flex;\n}\n\n.Positioner {\n  outline: none;\n  z-index: 1;\n  -webkit-user-select: none;\n  user-select: none;\n}\n\n.Popup {\n  box-sizing: border-box;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  background-clip: padding-box;\n  color: var(--color-gray-900);\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n\n  &[data-old] {\n    padding-block: 0.25rem;\n    overflow-y: auto;\n    max-height: var(--available-height);\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  &[data-side='none'] {\n    transition: none;\n    transform: none;\n    opacity: 1;\n    scroll-padding-block: 1.25rem;\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n  }\n}\n\n.List {\n  position: relative;\n  padding-block: 0.25rem;\n  overflow-y: auto;\n  max-height: var(--available-height);\n  scroll-padding-block: 1.25rem;\n}\n\n.Arrow {\n  display: flex;\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n\n.Item {\n  box-sizing: border-box;\n  outline: 0;\n  font-size: 0.875rem;\n  line-height: 1rem;\n  padding-block: 0.5rem;\n  padding-left: 0.625rem;\n  padding-right: 1rem;\n  min-width: var(--anchor-width);\n  display: grid;\n  gap: 0.5rem;\n  align-items: center;\n  grid-template-columns: 0.75rem 1fr;\n  cursor: default;\n  -webkit-user-select: none;\n  user-select: none;\n  scroll-margin-block: 0.25rem;\n\n  @media (pointer: coarse) {\n    padding-block: 0.625rem;\n    font-size: 0.925rem;\n  }\n\n  [data-side='none'] & {\n    font-size: 1rem;\n    padding-right: 3rem;\n    min-width: calc(var(--anchor-width) + 1rem);\n  }\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.25rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.ItemIndicator {\n  grid-column-start: 1;\n}\n\n.ItemIndicatorIcon {\n  display: block;\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.ItemText {\n  grid-column-start: 2;\n}\n\n.ScrollArrow {\n  width: 100%;\n  background: canvas;\n  z-index: 1;\n  text-align: center;\n  cursor: default;\n  border-radius: 0.375rem;\n  height: 1rem;\n  font-size: 0.75rem;\n  align-items: center;\n  justify-content: center;\n\n  &[data-old] {\n    display: none;\n\n    &[data-side='none'] {\n      display: flex;\n    }\n  }\n\n  &::before {\n    content: '';\n    position: absolute;\n    width: 100%;\n    height: 100%;\n    left: 0;\n  }\n\n  &[data-direction='up'] {\n    &::before {\n      top: -100%;\n    }\n  }\n\n  &[data-direction='down'] {\n    bottom: 0;\n\n    &::before {\n      bottom: -100%;\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/long-select.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Select } from '@base-ui/react/select';\nimport styles from './long-select.module.css';\n\nexport default function ExampleSelect() {\n  return (\n    <div>\n      Scroll down\n      <div style={{ width: 1, height: 2000, background: 'black' }} />\n      <h2>With Select.List (new)</h2>\n      <ul>\n        <li>Scroll arrows rendered inside Select.Popup (handles animations)</li>\n        <li>\n          Scrollbar invisible when in fallback mode as well (alignItemWithTrigger deactivated)\n        </li>\n      </ul>\n      <Select.Root>\n        <Select.Trigger className={styles.Select}>\n          <Select.Value>\n            {(value) => countries.find((country) => country.code === value)?.name}\n          </Select.Value>\n          <Select.Icon className={styles.SelectIcon}>\n            <ChevronUpDownIcon />\n          </Select.Icon>\n        </Select.Trigger>\n        <Select.Portal>\n          <Select.Positioner className={styles.Positioner} sideOffset={8}>\n            <Select.Popup className={styles.Popup}>\n              <Select.ScrollUpArrow className={styles.ScrollArrow} />\n              <Select.Arrow className={styles.Arrow}>\n                <ArrowSvg />\n              </Select.Arrow>\n              <Select.List className={styles.List}>\n                <div aria-hidden style={{ height: 75 }}>\n                  Start\n                </div>\n                {countries.map((country) => (\n                  <Select.Item key={country.code} className={styles.Item} value={country.code}>\n                    <Select.ItemIndicator className={styles.ItemIndicator}>\n                      <CheckIcon className={styles.ItemIndicatorIcon} />\n                    </Select.ItemIndicator>\n                    <Select.ItemText className={styles.ItemText}>{country.name}</Select.ItemText>\n                  </Select.Item>\n                ))}\n                <div aria-hidden style={{ height: 75 }}>\n                  End\n                </div>\n              </Select.List>\n              <Select.ScrollDownArrow className={styles.ScrollArrow} />\n            </Select.Popup>\n          </Select.Positioner>\n        </Select.Portal>\n      </Select.Root>\n      <br />\n      <h2>Without Select.List (old)</h2>\n      <ul>\n        <li>Scroll arrows rendered inside Select.Positioner</li>\n        <li>Scrollbar visible when in fallback mode (alignItemWithTrigger deactivated)</li>\n      </ul>\n      <Select.Root>\n        <Select.Trigger className={styles.Select}>\n          <Select.Value>\n            {(value) => countries.find((country) => country.code === value)?.name}\n          </Select.Value>\n          <Select.Icon className={styles.SelectIcon}>\n            <ChevronUpDownIcon />\n          </Select.Icon>\n        </Select.Trigger>\n        <Select.Portal>\n          <Select.Positioner className={styles.Positioner} sideOffset={8}>\n            <Select.ScrollUpArrow className={styles.ScrollArrow} data-old />\n            <Select.Popup className={styles.Popup} data-old>\n              <div aria-hidden style={{ height: 75 }}>\n                Start\n              </div>\n              <Select.Arrow className={styles.Arrow}>\n                <ArrowSvg />\n              </Select.Arrow>\n              {countries.map((country) => (\n                <Select.Item key={country.code} className={styles.Item} value={country.code}>\n                  <Select.ItemIndicator className={styles.ItemIndicator}>\n                    <CheckIcon className={styles.ItemIndicatorIcon} />\n                  </Select.ItemIndicator>\n                  <Select.ItemText className={styles.ItemText}>{country.name}</Select.ItemText>\n                </Select.Item>\n              ))}\n              <div aria-hidden style={{ height: 75 }}>\n                End\n              </div>\n            </Select.Popup>\n            <Select.ScrollDownArrow className={styles.ScrollArrow} data-old />\n          </Select.Positioner>\n        </Select.Portal>\n      </Select.Root>\n      <div style={{ width: 1, height: 2000, background: 'black' }} />\n    </div>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nfunction ChevronUpDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"8\"\n      height=\"12\"\n      viewBox=\"0 0 8 12\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"1.5\"\n      {...props}\n    >\n      <path d=\"M0.5 4.5L4 1.5L7.5 4.5\" />\n      <path d=\"M0.5 7.5L4 10.5L7.5 7.5\" />\n    </svg>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\ninterface Country {\n  code: string;\n  name: string;\n  continent: string;\n}\n\nexport const countries: Country[] = [\n  { code: 'af', name: 'Afghanistan', continent: 'Asia' },\n  { code: 'al', name: 'Albania', continent: 'Europe' },\n  { code: 'dz', name: 'Algeria', continent: 'Africa' },\n  { code: 'as', name: 'American Samoa', continent: 'Oceania' },\n  { code: 'ad', name: 'Andorra', continent: 'Europe' },\n  { code: 'ao', name: 'Angola', continent: 'Africa' },\n  { code: 'ai', name: 'Anguilla', continent: 'North America' },\n  { code: 'aq', name: 'Antarctica', continent: 'Antarctica' },\n  { code: 'ag', name: 'Antigua and Barbuda', continent: 'North America' },\n  { code: 'ar', name: 'Argentina', continent: 'South America' },\n  { code: 'am', name: 'Armenia', continent: 'Asia' },\n  { code: 'aw', name: 'Aruba', continent: 'North America' },\n  { code: 'au', name: 'Australia', continent: 'Oceania' },\n  { code: 'at', name: 'Austria', continent: 'Europe' },\n  { code: 'az', name: 'Azerbaijan', continent: 'Asia' },\n  { code: 'bs', name: 'Bahamas', continent: 'North America' },\n  { code: 'bh', name: 'Bahrain', continent: 'Asia' },\n  { code: 'bd', name: 'Bangladesh', continent: 'Asia' },\n  { code: 'bb', name: 'Barbados', continent: 'North America' },\n  { code: 'by', name: 'Belarus', continent: 'Europe' },\n  { code: 'be', name: 'Belgium', continent: 'Europe' },\n  { code: 'bz', name: 'Belize', continent: 'North America' },\n  { code: 'bj', name: 'Benin', continent: 'Africa' },\n  { code: 'bm', name: 'Bermuda', continent: 'North America' },\n  { code: 'bt', name: 'Bhutan', continent: 'Asia' },\n  { code: 'bo', name: 'Bolivia', continent: 'South America' },\n  { code: 'ba', name: 'Bosnia and Herzegovina', continent: 'Europe' },\n  { code: 'bw', name: 'Botswana', continent: 'Africa' },\n  { code: 'br', name: 'Brazil', continent: 'South America' },\n  { code: 'io', name: 'British Indian Ocean Territory', continent: 'Asia' },\n  { code: 'vg', name: 'British Virgin Islands', continent: 'North America' },\n  { code: 'bn', name: 'Brunei', continent: 'Asia' },\n  { code: 'bg', name: 'Bulgaria', continent: 'Europe' },\n  { code: 'bf', name: 'Burkina Faso', continent: 'Africa' },\n  { code: 'bi', name: 'Burundi', continent: 'Africa' },\n  { code: 'cv', name: 'Cabo Verde', continent: 'Africa' },\n  { code: 'kh', name: 'Cambodia', continent: 'Asia' },\n  { code: 'cm', name: 'Cameroon', continent: 'Africa' },\n  { code: 'ca', name: 'Canada', continent: 'North America' },\n  { code: 'ky', name: 'Cayman Islands', continent: 'North America' },\n  { code: 'cf', name: 'Central African Republic', continent: 'Africa' },\n  { code: 'td', name: 'Chad', continent: 'Africa' },\n  { code: 'cl', name: 'Chile', continent: 'South America' },\n  { code: 'cn', name: 'China', continent: 'Asia' },\n  { code: 'co', name: 'Colombia', continent: 'South America' },\n  { code: 'km', name: 'Comoros', continent: 'Africa' },\n  { code: 'cg', name: 'Congo', continent: 'Africa' },\n  { code: 'cd', name: 'Congo (DRC)', continent: 'Africa' },\n  { code: 'ck', name: 'Cook Islands', continent: 'Oceania' },\n  { code: 'cr', name: 'Costa Rica', continent: 'North America' },\n  { code: 'ci', name: 'Côte d’Ivoire', continent: 'Africa' },\n  { code: 'hr', name: 'Croatia', continent: 'Europe' },\n  { code: 'cu', name: 'Cuba', continent: 'North America' },\n  { code: 'cw', name: 'Curaçao', continent: 'North America' },\n  { code: 'cy', name: 'Cyprus', continent: 'Asia' },\n  { code: 'cz', name: 'Czechia', continent: 'Europe' },\n  { code: 'dk', name: 'Denmark', continent: 'Europe' },\n  { code: 'dj', name: 'Djibouti', continent: 'Africa' },\n  { code: 'dm', name: 'Dominica', continent: 'North America' },\n  { code: 'do', name: 'Dominican Republic', continent: 'North America' },\n  { code: 'ec', name: 'Ecuador', continent: 'South America' },\n  { code: 'eg', name: 'Egypt', continent: 'Africa' },\n  { code: 'sv', name: 'El Salvador', continent: 'North America' },\n  { code: 'gq', name: 'Equatorial Guinea', continent: 'Africa' },\n  { code: 'er', name: 'Eritrea', continent: 'Africa' },\n  { code: 'ee', name: 'Estonia', continent: 'Europe' },\n  { code: 'sz', name: 'Eswatini', continent: 'Africa' },\n  { code: 'et', name: 'Ethiopia', continent: 'Africa' },\n  { code: 'fj', name: 'Fiji', continent: 'Oceania' },\n  { code: 'fi', name: 'Finland', continent: 'Europe' },\n  { code: 'fr', name: 'France', continent: 'Europe' },\n  { code: 'gf', name: 'French Guiana', continent: 'South America' },\n  { code: 'pf', name: 'French Polynesia', continent: 'Oceania' },\n  { code: 'ga', name: 'Gabon', continent: 'Africa' },\n  { code: 'gm', name: 'Gambia', continent: 'Africa' },\n  { code: 'ge', name: 'Georgia', continent: 'Asia' },\n  { code: 'de', name: 'Germany', continent: 'Europe' },\n  { code: 'gh', name: 'Ghana', continent: 'Africa' },\n  { code: 'gi', name: 'Gibraltar', continent: 'Europe' },\n  { code: 'gr', name: 'Greece', continent: 'Europe' },\n  { code: 'gl', name: 'Greenland', continent: 'North America' },\n  { code: 'gd', name: 'Grenada', continent: 'North America' },\n  { code: 'gp', name: 'Guadeloupe', continent: 'North America' },\n  { code: 'gu', name: 'Guam', continent: 'Oceania' },\n  { code: 'gt', name: 'Guatemala', continent: 'North America' },\n  { code: 'gn', name: 'Guinea', continent: 'Africa' },\n  { code: 'gw', name: 'Guinea-Bissau', continent: 'Africa' },\n  { code: 'gy', name: 'Guyana', continent: 'South America' },\n  { code: 'ht', name: 'Haiti', continent: 'North America' },\n  { code: 'hn', name: 'Honduras', continent: 'North America' },\n  { code: 'hk', name: 'Hong Kong', continent: 'Asia' },\n  { code: 'hu', name: 'Hungary', continent: 'Europe' },\n  { code: 'is', name: 'Iceland', continent: 'Europe' },\n  { code: 'in', name: 'India', continent: 'Asia' },\n  { code: 'id', name: 'Indonesia', continent: 'Asia' },\n  { code: 'ir', name: 'Iran', continent: 'Asia' },\n  { code: 'iq', name: 'Iraq', continent: 'Asia' },\n  { code: 'ie', name: 'Ireland', continent: 'Europe' },\n  { code: 'il', name: 'Israel', continent: 'Asia' },\n  { code: 'it', name: 'Italy', continent: 'Europe' },\n  { code: 'jm', name: 'Jamaica', continent: 'North America' },\n  { code: 'jp', name: 'Japan', continent: 'Asia' },\n  { code: 'jo', name: 'Jordan', continent: 'Asia' },\n  { code: 'kz', name: 'Kazakhstan', continent: 'Asia' },\n  { code: 'ke', name: 'Kenya', continent: 'Africa' },\n  { code: 'ki', name: 'Kiribati', continent: 'Oceania' },\n  { code: 'kp', name: 'North Korea', continent: 'Asia' },\n  { code: 'kr', name: 'South Korea', continent: 'Asia' },\n  { code: 'kw', name: 'Kuwait', continent: 'Asia' },\n  { code: 'kg', name: 'Kyrgyzstan', continent: 'Asia' },\n  { code: 'la', name: 'Laos', continent: 'Asia' },\n  { code: 'lv', name: 'Latvia', continent: 'Europe' },\n  { code: 'lb', name: 'Lebanon', continent: 'Asia' },\n  { code: 'ls', name: 'Lesotho', continent: 'Africa' },\n  { code: 'lr', name: 'Liberia', continent: 'Africa' },\n  { code: 'ly', name: 'Libya', continent: 'Africa' },\n  { code: 'li', name: 'Liechtenstein', continent: 'Europe' },\n  { code: 'lt', name: 'Lithuania', continent: 'Europe' },\n  { code: 'lu', name: 'Luxembourg', continent: 'Europe' },\n  { code: 'mo', name: 'Macau', continent: 'Asia' },\n  { code: 'mg', name: 'Madagascar', continent: 'Africa' },\n  { code: 'mw', name: 'Malawi', continent: 'Africa' },\n  { code: 'my', name: 'Malaysia', continent: 'Asia' },\n  { code: 'mv', name: 'Maldives', continent: 'Asia' },\n  { code: 'ml', name: 'Mali', continent: 'Africa' },\n  { code: 'mt', name: 'Malta', continent: 'Europe' },\n  { code: 'mh', name: 'Marshall Islands', continent: 'Oceania' },\n  { code: 'mq', name: 'Martinique', continent: 'North America' },\n  { code: 'mr', name: 'Mauritania', continent: 'Africa' },\n  { code: 'mu', name: 'Mauritius', continent: 'Africa' },\n  { code: 'yt', name: 'Mayotte', continent: 'Africa' },\n  { code: 'mx', name: 'Mexico', continent: 'North America' },\n  { code: 'fm', name: 'Micronesia', continent: 'Oceania' },\n  { code: 'md', name: 'Moldova', continent: 'Europe' },\n  { code: 'mc', name: 'Monaco', continent: 'Europe' },\n  { code: 'mn', name: 'Mongolia', continent: 'Asia' },\n  { code: 'me', name: 'Montenegro', continent: 'Europe' },\n  { code: 'ms', name: 'Montserrat', continent: 'North America' },\n  { code: 'ma', name: 'Morocco', continent: 'Africa' },\n  { code: 'mz', name: 'Mozambique', continent: 'Africa' },\n  { code: 'mm', name: 'Myanmar', continent: 'Asia' },\n  { code: 'na', name: 'Namibia', continent: 'Africa' },\n  { code: 'nr', name: 'Nauru', continent: 'Oceania' },\n  { code: 'np', name: 'Nepal', continent: 'Asia' },\n  { code: 'nl', name: 'Netherlands', continent: 'Europe' },\n  { code: 'nc', name: 'New Caledonia', continent: 'Oceania' },\n  { code: 'nz', name: 'New Zealand', continent: 'Oceania' },\n  { code: 'ni', name: 'Nicaragua', continent: 'North America' },\n  { code: 'ne', name: 'Niger', continent: 'Africa' },\n  { code: 'ng', name: 'Nigeria', continent: 'Africa' },\n  { code: 'nu', name: 'Niue', continent: 'Oceania' },\n  { code: 'mk', name: 'North Macedonia', continent: 'Europe' },\n  { code: 'no', name: 'Norway', continent: 'Europe' },\n  { code: 'om', name: 'Oman', continent: 'Asia' },\n  { code: 'pk', name: 'Pakistan', continent: 'Asia' },\n  { code: 'pw', name: 'Palau', continent: 'Oceania' },\n  { code: 'ps', name: 'Palestine', continent: 'Asia' },\n  { code: 'pa', name: 'Panama', continent: 'North America' },\n  { code: 'pg', name: 'Papua New Guinea', continent: 'Oceania' },\n  { code: 'py', name: 'Paraguay', continent: 'South America' },\n  { code: 'pe', name: 'Peru', continent: 'South America' },\n  { code: 'ph', name: 'Philippines', continent: 'Asia' },\n  { code: 'pl', name: 'Poland', continent: 'Europe' },\n  { code: 'pt', name: 'Portugal', continent: 'Europe' },\n  { code: 'pr', name: 'Puerto Rico', continent: 'North America' },\n  { code: 'qa', name: 'Qatar', continent: 'Asia' },\n  { code: 're', name: 'Réunion', continent: 'Africa' },\n  { code: 'ro', name: 'Romania', continent: 'Europe' },\n  { code: 'ru', name: 'Russia', continent: 'Europe' },\n  { code: 'rw', name: 'Rwanda', continent: 'Africa' },\n  { code: 'ws', name: 'Samoa', continent: 'Oceania' },\n  { code: 'sm', name: 'San Marino', continent: 'Europe' },\n  { code: 'st', name: 'São Tomé and Príncipe', continent: 'Africa' },\n  { code: 'sa', name: 'Saudi Arabia', continent: 'Asia' },\n  { code: 'sn', name: 'Senegal', continent: 'Africa' },\n  { code: 'rs', name: 'Serbia', continent: 'Europe' },\n  { code: 'sc', name: 'Seychelles', continent: 'Africa' },\n  { code: 'sl', name: 'Sierra Leone', continent: 'Africa' },\n  { code: 'sg', name: 'Singapore', continent: 'Asia' },\n  { code: 'sx', name: 'Sint Maarten', continent: 'North America' },\n  { code: 'sk', name: 'Slovakia', continent: 'Europe' },\n  { code: 'si', name: 'Slovenia', continent: 'Europe' },\n  { code: 'sb', name: 'Solomon Islands', continent: 'Oceania' },\n  { code: 'so', name: 'Somalia', continent: 'Africa' },\n  { code: 'za', name: 'South Africa', continent: 'Africa' },\n  { code: 'ss', name: 'South Sudan', continent: 'Africa' },\n  { code: 'es', name: 'Spain', continent: 'Europe' },\n  { code: 'lk', name: 'Sri Lanka', continent: 'Asia' },\n  { code: 'sd', name: 'Sudan', continent: 'Africa' },\n  { code: 'sr', name: 'Suriname', continent: 'South America' },\n  { code: 'se', name: 'Sweden', continent: 'Europe' },\n  { code: 'ch', name: 'Switzerland', continent: 'Europe' },\n  { code: 'sy', name: 'Syria', continent: 'Asia' },\n  { code: 'tw', name: 'Taiwan', continent: 'Asia' },\n  { code: 'tj', name: 'Tajikistan', continent: 'Asia' },\n  { code: 'tz', name: 'Tanzania', continent: 'Africa' },\n  { code: 'th', name: 'Thailand', continent: 'Asia' },\n  { code: 'tl', name: 'Timor-Leste', continent: 'Asia' },\n  { code: 'tg', name: 'Togo', continent: 'Africa' },\n  { code: 'tk', name: 'Tokelau', continent: 'Oceania' },\n  { code: 'to', name: 'Tonga', continent: 'Oceania' },\n  { code: 'tt', name: 'Trinidad and Tobago', continent: 'North America' },\n  { code: 'tn', name: 'Tunisia', continent: 'Africa' },\n  { code: 'tr', name: 'Turkey', continent: 'Asia' },\n  { code: 'tm', name: 'Turkmenistan', continent: 'Asia' },\n  { code: 'tc', name: 'Turks and Caicos Islands', continent: 'North America' },\n  { code: 'tv', name: 'Tuvalu', continent: 'Oceania' },\n  { code: 'ug', name: 'Uganda', continent: 'Africa' },\n  { code: 'ua', name: 'Ukraine', continent: 'Europe' },\n  { code: 'ae', name: 'United Arab Emirates', continent: 'Asia' },\n  { code: 'gb', name: 'United Kingdom', continent: 'Europe' },\n  { code: 'us', name: 'United States', continent: 'North America' },\n  { code: 'uy', name: 'Uruguay', continent: 'South America' },\n  { code: 'uz', name: 'Uzbekistan', continent: 'Asia' },\n  { code: 'vu', name: 'Vanuatu', continent: 'Oceania' },\n  { code: 'va', name: 'Vatican City', continent: 'Europe' },\n  { code: 've', name: 'Venezuela', continent: 'South America' },\n  { code: 'vn', name: 'Vietnam', continent: 'Asia' },\n  { code: 'wf', name: 'Wallis and Futuna', continent: 'Oceania' },\n  { code: 'eh', name: 'Western Sahara', continent: 'Africa' },\n  { code: 'ye', name: 'Yemen', continent: 'Asia' },\n  { code: 'zm', name: 'Zambia', continent: 'Africa' },\n  { code: 'zw', name: 'Zimbabwe', continent: 'Africa' },\n];\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/menu/complex-nesting.tsx",
    "content": "'use client';\nimport { Menu } from '@base-ui/react/menu';\nimport { Dialog } from '@base-ui/react/dialog';\nimport styles from './menu.module.css';\n\nexport default function MenuComplexNestingExperiment() {\n  return (\n    <div className={styles.ExperimentRoot}>\n      <h1 className={styles.ExperimentTitle}>Menu Complex Nesting Experiments</h1>\n      <p className={styles.ExperimentDescription}>\n        Testing how independent menus work when nested in React tree through dialogs and other\n        components.\n      </p>\n\n      <section className={styles.Section}>\n        <h2 className={styles.SectionTitle}>1. Menu → Item → Dialog → Menu</h2>\n        <p className={styles.SectionDescription}>\n          Click the menu button, select \"Open Settings\", then use the menu inside the dialog. The\n          dialog is opened from within a menu item.\n        </p>\n\n        <Menu.Root>\n          <Menu.Trigger className={styles.Button}>Main Menu</Menu.Trigger>\n          <Menu.Portal>\n            <Menu.Positioner className={styles.Positioner}>\n              <Menu.Popup className={styles.Popup}>\n                <Menu.Item className={styles.Item}>New File</Menu.Item>\n                <Menu.Item className={styles.Item}>Save</Menu.Item>\n\n                <Menu.SubmenuRoot>\n                  <Menu.SubmenuTrigger className={styles.SubmenuTrigger}>\n                    Export\n                    <ChevronRightIcon />\n                  </Menu.SubmenuTrigger>\n                  <Menu.Portal>\n                    <Menu.Positioner className={styles.Positioner} sideOffset={4}>\n                      <Menu.Popup className={styles.Popup}>\n                        <Menu.Item className={styles.Item}>Export as PDF</Menu.Item>\n                        <Menu.Item className={styles.Item}>Export as PNG</Menu.Item>\n                        <Menu.Item className={styles.Item}>Export as SVG</Menu.Item>\n                      </Menu.Popup>\n                    </Menu.Positioner>\n                  </Menu.Portal>\n                </Menu.SubmenuRoot>\n\n                <Menu.Separator className={styles.Separator} />\n\n                <Dialog.Root>\n                  <Menu.Item\n                    render={<Dialog.Trigger />}\n                    className={styles.Item}\n                    closeOnClick={false}\n                    nativeButton\n                  >\n                    Open Settings\n                  </Menu.Item>\n\n                  <Dialog.Portal>\n                    <Dialog.Backdrop className={styles.DialogBackdrop} />\n                    <Dialog.Popup className={styles.DialogPopup}>\n                      <Dialog.Title className={styles.DialogTitle}>\n                        Settings (Nested Dialog)\n                      </Dialog.Title>\n\n                      <div className={styles.DialogBody}>\n                        <p className={styles.DialogDescription}>\n                          This dialog is nested within the menu popup. It contains an independent\n                          menu.\n                        </p>\n\n                        {/* Menu nested within Dialog which is nested within Menu */}\n                        <div className={styles.Card}>\n                          <h3 className={styles.CardTitle}>Theme Settings</h3>\n                          <Menu.Root>\n                            <Menu.Trigger className={styles.Button}>Theme Options</Menu.Trigger>\n                            <Menu.Portal>\n                              <Menu.Positioner className={styles.Positioner}>\n                                <Menu.Popup className={styles.Popup}>\n                                  <Menu.Item className={styles.Item}>Light Theme</Menu.Item>\n                                  <Menu.Item className={styles.Item}>Dark Theme</Menu.Item>\n\n                                  <Menu.SubmenuRoot>\n                                    <Menu.SubmenuTrigger className={styles.SubmenuTrigger}>\n                                      Advanced\n                                      <ChevronRightIcon />\n                                    </Menu.SubmenuTrigger>\n                                    <Menu.Portal>\n                                      <Menu.Positioner className={styles.Positioner} sideOffset={4}>\n                                        <Menu.Popup className={styles.Popup}>\n                                          <Menu.Item className={styles.Item}>\n                                            Custom Colors\n                                          </Menu.Item>\n                                          <Menu.Item className={styles.Item}>\n                                            Font Settings\n                                          </Menu.Item>\n                                          <Menu.Separator className={styles.Separator} />\n                                          <Menu.Item className={styles.Item}>\n                                            Reset to Default\n                                          </Menu.Item>\n                                        </Menu.Popup>\n                                      </Menu.Positioner>\n                                    </Menu.Portal>\n                                  </Menu.SubmenuRoot>\n\n                                  <Menu.Separator className={styles.Separator} />\n                                  <Menu.Item className={styles.Item}>Auto Theme</Menu.Item>\n                                </Menu.Popup>\n                              </Menu.Positioner>\n                            </Menu.Portal>\n                          </Menu.Root>\n                        </div>\n\n                        <div className={styles.DialogActions}>\n                          <Dialog.Close className={styles.DialogCancel}>Cancel</Dialog.Close>\n                          <Dialog.Close className={styles.DialogConfirm}>Save</Dialog.Close>\n                        </div>\n                      </div>\n                    </Dialog.Popup>\n                  </Dialog.Portal>\n                </Dialog.Root>\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>\n      </section>\n    </div>\n  );\n}\n\nfunction ChevronRightIcon() {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\">\n      <path d=\"M3.5 9L7.5 5L3.5 1\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/menu/menu-anchor-el.tsx",
    "content": "'use client';\n\nimport * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\n\nexport default function Page() {\n  const [anchorEl, setAnchor] = React.useState<HTMLDivElement | null>(null);\n  const handleRef = React.useCallback((element: HTMLDivElement | null) => {\n    setAnchor(element);\n  }, []);\n\n  return (\n    <div>\n      <h1>Element passed to anchor</h1>\n      <Menu.Root>\n        <Menu.Trigger>Trigger</Menu.Trigger>\n        <Menu.Portal>\n          <Menu.Positioner side=\"bottom\" align=\"start\" arrowPadding={0} anchor={anchorEl}>\n            <Menu.Popup>\n              <Menu.Item style={{ background: 'lightgray', padding: '5px' }}>One</Menu.Item>\n              <Menu.Item style={{ background: 'lightgray', padding: '5px' }}>Two</Menu.Item>\n            </Menu.Popup>\n          </Menu.Positioner>\n        </Menu.Portal>\n      </Menu.Root>\n      <div\n        data-testid=\"anchor\"\n        style={{\n          margin: '100px',\n          background: 'yellowgreen',\n          height: '50px',\n          width: '200px',\n        }}\n        ref={handleRef}\n      >\n        Anchor\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/menu/menu-anchor-ref.tsx",
    "content": "'use client';\n\nimport * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\n\nexport default function Page() {\n  const anchor = React.useRef<HTMLDivElement>(null);\n\n  return (\n    <div>\n      <h1>Ref passed to anchor</h1>\n      <Menu.Root>\n        <Menu.Trigger>Trigger</Menu.Trigger>\n        <Menu.Portal>\n          <Menu.Positioner side=\"bottom\" align=\"start\" arrowPadding={0} anchor={anchor}>\n            <Menu.Popup>\n              <Menu.Item style={{ background: 'lightgray', padding: '5px' }}>One</Menu.Item>\n              <Menu.Item style={{ background: 'lightgray', padding: '5px' }}>Two</Menu.Item>\n            </Menu.Popup>\n          </Menu.Positioner>\n        </Menu.Portal>\n      </Menu.Root>\n      <div\n        data-testid=\"anchor\"\n        style={{\n          margin: '100px',\n          background: 'yellowgreen',\n          height: '50px',\n          width: '200px',\n        }}\n        ref={anchor}\n      >\n        Anchor\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/menu/menu-disabled-items.tsx",
    "content": "import * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\nimport styles from './menu.module.css';\n\nexport default function ExampleMenu() {\n  return (\n    <Menu.Root>\n      <Menu.Trigger className={styles.Button}>\n        Song <ChevronDownIcon className={styles.ButtonIcon} />\n      </Menu.Trigger>\n      <Menu.Portal>\n        <Menu.Positioner className={styles.Positioner} sideOffset={8}>\n          <Menu.Popup className={styles.Popup}>\n            <Menu.Arrow className={styles.Arrow}>\n              <ArrowSvg />\n            </Menu.Arrow>\n            <Menu.Item className={styles.Item} disabled>\n              Add to Library\n            </Menu.Item>\n\n            <Menu.SubmenuRoot>\n              <Menu.SubmenuTrigger className={styles.SubmenuTrigger}>\n                Add to Playlist\n                <ChevronRightIcon />\n              </Menu.SubmenuTrigger>\n              <Menu.Portal>\n                <Menu.Positioner className={styles.Positioner} alignOffset={-4} sideOffset={-4}>\n                  <Menu.Popup className={styles.Popup}>\n                    <Menu.Item className={styles.Item} disabled>\n                      Get Up!\n                    </Menu.Item>\n                    <Menu.Item className={styles.Item}>Inside Out</Menu.Item>\n                    <Menu.Item className={styles.Item}>Night Beats</Menu.Item>\n                    <Menu.Separator className={styles.Separator} />\n                    <Menu.Item className={styles.Item}>New playlist…</Menu.Item>\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            </Menu.SubmenuRoot>\n\n            <Menu.Separator className={styles.Separator} />\n            <Menu.Item className={styles.Item}>Play Next</Menu.Item>\n            <Menu.Item className={styles.Item}>Play Last</Menu.Item>\n            <Menu.Separator className={styles.Separator} />\n            <Menu.Item className={styles.Item}>Favorite</Menu.Item>\n            <Menu.Item className={styles.Item}>Share</Menu.Item>\n          </Menu.Popup>\n        </Menu.Positioner>\n      </Menu.Portal>\n    </Menu.Root>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nfunction ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M1 3.5L5 7.5L9 3.5\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n\nfunction ChevronRightIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M3.5 9L7.5 5L3.5 1\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/menu/menu-fully-featured.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\nimport clsx from 'clsx';\nimport NextLink from 'next/link';\nimport { SettingsMetadata, useExperimentSettings } from '../_components/SettingsPanel';\nimport '../../../../demo-data/theme/css-modules/theme.css';\nimport classes from './menu.module.css';\n\ninterface Settings {\n  customAnchor: boolean;\n  modal: boolean;\n  openOnHover: boolean;\n  disabled: boolean;\n  customTriggerElement: boolean;\n  side: Menu.Positioner.Props['side'];\n  align: Menu.Positioner.Props['align'];\n}\n\nexport default function MenuFullyFeatured() {\n  const { settings } = useExperimentSettings<Settings>();\n\n  const anchorRef = React.useRef<HTMLDivElement>(null);\n\n  const triggerRender = React.useMemo(\n    () => (settings.customTriggerElement ? <span /> : undefined),\n    [settings.customTriggerElement],\n  );\n\n  const handleItemClick = React.useCallback((event: React.MouseEvent<HTMLDivElement>) => {\n    console.log(`${event.currentTarget.textContent} clicked`);\n  }, []);\n\n  return (\n    <div>\n      <h1>Fully featured menu</h1>\n      <Menu.Root modal={settings.modal} disabled={settings.disabled}>\n        <Menu.Trigger\n          className={classes.Button}\n          render={triggerRender}\n          nativeButton={triggerRender === undefined}\n          openOnHover={settings.openOnHover}\n        >\n          Menu <ChevronDownIcon className={classes.ButtonIcon} />\n        </Menu.Trigger>\n        <Menu.Portal>\n          <Menu.Positioner\n            className={classes.Positioner}\n            sideOffset={8}\n            anchor={settings.customAnchor ? anchorRef : undefined}\n            side={settings.side}\n            align={settings.align}\n          >\n            <Menu.Popup className={classes.Popup}>\n              <Menu.Arrow className={classes.Arrow}>\n                <ArrowIcon />\n              </Menu.Arrow>\n              <Menu.Item className={classes.Item} onClick={handleItemClick}>\n                Item 1\n              </Menu.Item>\n              <Menu.Item className={classes.Item} onClick={handleItemClick}>\n                Item 2\n              </Menu.Item>\n              <Menu.LinkItem\n                href=\"https://base-ui.com\"\n                className={clsx(classes.Item, classes.LinkItem)}\n              >\n                Link 1 (base-ui.com)\n              </Menu.LinkItem>\n              <Menu.LinkItem\n                render={<a href=\"https://github.com\">Link 2 (github.com)</a>}\n                className={clsx(classes.Item, classes.LinkItem)}\n              />\n              <Menu.LinkItem\n                render={<NextLink href=\"/experiments\">Link 3 (/experiments)</NextLink>}\n                className={clsx(classes.Item, classes.LinkItem)}\n              />\n              <Menu.Item className={classes.Item} onClick={handleItemClick}>\n                Item 3\n              </Menu.Item>\n              <Menu.Separator className={classes.Separator} />\n              <Menu.Item className={classes.Item} closeOnClick={false} onClick={handleItemClick}>\n                Item (close on click disabled)\n              </Menu.Item>\n              <Menu.Item className={classes.Item} disabled onClick={handleItemClick}>\n                Disabled Item\n              </Menu.Item>\n              <Menu.Separator className={classes.Separator} />\n\n              <Menu.SubmenuRoot>\n                <Menu.SubmenuTrigger className={classes.SubmenuTrigger}>\n                  Nested menu\n                  <ChevronRightIcon />\n                </Menu.SubmenuTrigger>\n\n                <Menu.Portal>\n                  <Menu.Positioner className={classes.Positioner} sideOffset={8}>\n                    <Menu.Popup className={classes.Popup}>\n                      <div className={classes.NonFocusableText}>Non-focusable text</div>\n                      <Menu.Group>\n                        <Menu.GroupLabel className={classes.GroupLabel}>\n                          Radio items\n                        </Menu.GroupLabel>\n                        <Menu.RadioGroup>\n                          <Menu.RadioItem className={classes.RadioItem} value=\"o1\">\n                            <Menu.RadioItemIndicator className={classes.RadioItemIndicator}>\n                              <CheckIcon className={classes.RadioItemIndicatorIcon} />\n                            </Menu.RadioItemIndicator>\n                            <span className={classes.RadioItemText}>Option 1</span>\n                          </Menu.RadioItem>\n                          <Menu.RadioItem className={classes.RadioItem} value=\"o2\">\n                            <Menu.RadioItemIndicator className={classes.RadioItemIndicator}>\n                              <CheckIcon className={classes.RadioItemIndicatorIcon} />\n                            </Menu.RadioItemIndicator>\n                            <span className={classes.RadioItemText}>Option 2</span>\n                          </Menu.RadioItem>\n                          <Menu.RadioItem className={classes.RadioItem} value=\"o3\" closeOnClick>\n                            <Menu.RadioItemIndicator className={classes.RadioItemIndicator}>\n                              <CheckIcon className={classes.RadioItemIndicatorIcon} />\n                            </Menu.RadioItemIndicator>\n                            <span className={classes.RadioItemText}>Option 3 (close on click)</span>\n                          </Menu.RadioItem>\n                          <Menu.RadioItem className={classes.RadioItem} value=\"o4\" disabled>\n                            <Menu.RadioItemIndicator className={classes.RadioItemIndicator}>\n                              <CheckIcon className={classes.RadioItemIndicatorIcon} />\n                            </Menu.RadioItemIndicator>\n                            <span className={classes.RadioItemText}>Disabled option</span>\n                          </Menu.RadioItem>\n                        </Menu.RadioGroup>\n                      </Menu.Group>\n\n                      <Menu.Separator className={classes.Separator} />\n\n                      <Menu.Group>\n                        <Menu.GroupLabel className={classes.GroupLabel}>\n                          Checkbox Items\n                        </Menu.GroupLabel>\n                        <Menu.CheckboxItem className={classes.CheckboxItem}>\n                          <Menu.CheckboxItemIndicator className={classes.CheckboxItemIndicator}>\n                            <CheckIcon className={classes.CheckboxItemIndicatorIcon} />\n                          </Menu.CheckboxItemIndicator>\n                          <span className={classes.CheckboxItemText}>Option A</span>\n                        </Menu.CheckboxItem>\n                        <Menu.CheckboxItem className={classes.CheckboxItem}>\n                          <Menu.CheckboxItemIndicator className={classes.CheckboxItemIndicator}>\n                            <CheckIcon className={classes.CheckboxItemIndicatorIcon} />\n                          </Menu.CheckboxItemIndicator>\n                          <span className={classes.CheckboxItemText}>Option B</span>\n                        </Menu.CheckboxItem>\n                        <Menu.CheckboxItem className={classes.CheckboxItem} closeOnClick>\n                          <Menu.CheckboxItemIndicator className={classes.CheckboxItemIndicator}>\n                            <CheckIcon className={classes.CheckboxItemIndicatorIcon} />\n                          </Menu.CheckboxItemIndicator>\n                          <span className={classes.CheckboxItemText}>\n                            Option C (close on click)\n                          </span>\n                        </Menu.CheckboxItem>\n                        <Menu.CheckboxItem className={classes.CheckboxItem} disabled>\n                          <Menu.CheckboxItemIndicator className={classes.CheckboxItemIndicator}>\n                            <CheckIcon className={classes.CheckboxItemIndicatorIcon} />\n                          </Menu.CheckboxItemIndicator>\n                          <span className={classes.CheckboxItemText}>Disabled option</span>\n                        </Menu.CheckboxItem>\n                      </Menu.Group>\n\n                      <Menu.Separator className={classes.Separator} />\n\n                      <Menu.SubmenuRoot>\n                        <Menu.SubmenuTrigger className={classes.SubmenuTrigger}>\n                          Nested menu\n                          <ChevronRightIcon />\n                        </Menu.SubmenuTrigger>\n                        <Menu.Portal>\n                          <Menu.Positioner className={classes.Positioner} sideOffset={8}>\n                            <Menu.Popup className={classes.Popup}>\n                              <Menu.Item className={classes.Item} onClick={handleItemClick}>\n                                Submenu item 1\n                              </Menu.Item>\n                              <Menu.Item className={classes.Item} onClick={handleItemClick}>\n                                Submenu item 2\n                              </Menu.Item>\n                              <Menu.Item className={classes.Item} onClick={handleItemClick}>\n                                Submenu item 3\n                              </Menu.Item>\n                            </Menu.Popup>\n                          </Menu.Positioner>\n                        </Menu.Portal>\n                      </Menu.SubmenuRoot>\n                    </Menu.Popup>\n                  </Menu.Positioner>\n                </Menu.Portal>\n              </Menu.SubmenuRoot>\n\n              <Menu.SubmenuRoot>\n                <Menu.SubmenuTrigger className={classes.SubmenuTrigger}>\n                  Adjacent nested menu\n                  <ChevronRightIcon />\n                </Menu.SubmenuTrigger>\n                <Menu.Portal>\n                  <Menu.Positioner className={classes.Positioner} sideOffset={8}>\n                    <Menu.Popup className={classes.Popup}>\n                      <Menu.Item className={classes.Item}>Item 1</Menu.Item>\n                    </Menu.Popup>\n                  </Menu.Positioner>\n                </Menu.Portal>\n              </Menu.SubmenuRoot>\n\n              <Menu.SubmenuRoot disabled>\n                <Menu.SubmenuTrigger className={classes.SubmenuTrigger}>\n                  Disabled nested menu\n                  <ChevronRightIcon />\n                </Menu.SubmenuTrigger>\n                <Menu.Portal>\n                  <Menu.Positioner className={classes.Positioner} sideOffset={8}>\n                    <Menu.Popup className={classes.Popup}>\n                      <Menu.Item className={classes.Item}>This should not appear</Menu.Item>\n                    </Menu.Popup>\n                  </Menu.Positioner>\n                </Menu.Portal>\n              </Menu.SubmenuRoot>\n            </Menu.Popup>\n          </Menu.Positioner>\n        </Menu.Portal>\n      </Menu.Root>\n\n      {settings.customAnchor && (\n        <div className={classes.CustomAnchor} ref={anchorRef}>\n          Menu will be anchored here\n        </div>\n      )}\n    </div>\n  );\n}\n\nexport const settingsMetadata: SettingsMetadata<Settings> = {\n  customAnchor: {\n    type: 'boolean',\n    label: 'Custom anchor',\n  },\n  modal: {\n    type: 'boolean',\n    label: 'Modal',\n    default: true,\n  },\n  openOnHover: {\n    type: 'boolean',\n    label: 'Open on hover',\n  },\n  disabled: {\n    type: 'boolean',\n    label: 'Disabled',\n  },\n  customTriggerElement: {\n    type: 'boolean',\n    label: 'Trigger as <span>',\n  },\n  side: {\n    type: 'string',\n    label: 'Side',\n    options: ['top', 'right', 'bottom', 'left'],\n    default: 'bottom',\n  },\n  align: {\n    type: 'string',\n    label: 'Align',\n    options: ['start', 'center', 'end'],\n    default: 'center',\n  },\n};\n\nfunction ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M1 3.5L5 7.5L9 3.5\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n\nfunction ChevronRightIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M3.5 9L7.5 5L3.5 1\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n\nfunction ArrowIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={classes.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={classes.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={classes.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/menu/menu-horizontal.module.css",
    "content": ".Container {\n  display: flex;\n  min-height: 110vh;\n  box-sizing: border-box;\n  align-items: center;\n  gap: 20px;\n}\n\n.MenuPopup {\n  font-family: 'IBM Plex Sans', sans-serif;\n  font-size: 0.875rem;\n  box-sizing: border-box;\n  padding: 6px;\n  min-width: 200px;\n  border-radius: 12px;\n  overflow: visible;\n  outline: 0;\n  background: #fff;\n  border: 1px solid var(--color-gray-200);\n  color: var(--color-gray-900);\n  box-shadow: 0 4px 30px var(--color-gray-200);\n  z-index: 1;\n  transform-origin: var(--transform-origin);\n  opacity: 1;\n  transform: scale(1, 1);\n  transition:\n    opacity 100ms ease-in,\n    transform 100ms ease-in;\n\n  &[data-nested] {\n    margin-top: -6px;\n  }\n\n  &[data-starting-style] {\n    opacity: 0;\n    transform: scale(0.8);\n  }\n\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.8);\n    transition:\n      opacity 200ms ease-in,\n      transform 200ms ease-in;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    background: var(--color-gray-900);\n    border: 1px solid var(--color-gray-700);\n    color: var(--color-gray-300);\n    box-shadow: 0 4px 30px var(--color-gray-900);\n  }\n}\n\n.MenuRootPopup {\n  font-family: 'IBM Plex Sans', sans-serif;\n  font-size: 0.875rem;\n  box-sizing: border-box;\n  padding: 6px;\n  min-width: 200px;\n  border-radius: 12px;\n  overflow: visible;\n  outline: 0;\n  background: #fff;\n  border: 1px solid var(--color-gray-200);\n  color: var(--color-gray-900);\n  box-shadow: 0 4px 30px var(--color-gray-200);\n  z-index: 1;\n  transform-origin: var(--transform-origin);\n  opacity: 1;\n  transform: scale(1, 1);\n  transition:\n    opacity 100ms ease-in,\n    transform 100ms ease-in;\n  display: flex;\n\n  &[data-nested] {\n    margin-top: -6px;\n  }\n\n  &[data-starting-style] {\n    opacity: 0;\n    transform: scale(0.8);\n  }\n\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.8);\n    transition:\n      opacity 200ms ease-in,\n      transform 200ms ease-in;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    background: var(--color-gray-900);\n    border: 1px solid var(--color-gray-700);\n    color: var(--color-gray-300);\n    box-shadow: 0 4px 30px var(--color-gray-900);\n  }\n}\n\n.MenuItem {\n  list-style: none;\n  padding: 8px;\n  border-radius: 8px;\n  cursor: default;\n  user-select: none;\n\n  &:last-of-type {\n    border-bottom: none;\n  }\n\n  &:focus,\n  &:hover {\n    background-color: var(--color-gray-100);\n    color: var(--color-gray-900);\n  }\n\n  &:focus-visible {\n    outline: none;\n  }\n\n  &[data-disabled] {\n    color: var(--color-gray-400);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    &:focus,\n    &:hover {\n      background-color: var(--color-gray-800);\n      color: var(--color-gray-300);\n    }\n\n    &[data-disabled] {\n      color: var(--color-gray-700);\n    }\n  }\n}\n\n.SubmenuTrigger {\n  list-style: none;\n  padding: 8px;\n  border-radius: 8px;\n  cursor: default;\n  user-select: none;\n\n  &:last-of-type {\n    border-bottom: none;\n  }\n\n  &::after {\n    content: '›';\n    float: right;\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-50);\n    color: var(--color-gray-900);\n  }\n\n  &:focus,\n  &:hover {\n    background-color: var(--color-gray-100);\n    color: var(--color-gray-900);\n  }\n\n  &:focus-visible {\n    outline: none;\n  }\n\n  &[data-disabled] {\n    color: var(--color-gray-400);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    &[data-popup-open] {\n      background-color: var(--color-gray-900);\n      color: var(--color-gray-300);\n    }\n\n    &:focus,\n    &:hover {\n      background-color: var(--color-gray-800);\n      color: var(--color-gray-300);\n    }\n\n    &[data-disabled] {\n      color: var(--color-gray-700);\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/menu/menu-horizontal.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\nimport styles from './menu-horizontal.module.css';\n\nexport default function NestedMenu() {\n  const createHandleMenuClick = (menuItem: string) => {\n    return () => {\n      console.log(`Clicked on ${menuItem}`);\n    };\n  };\n\n  const containerRef = React.useRef<HTMLDivElement>(null);\n\n  return (\n    <div className={styles.Container}>\n      <div ref={containerRef} />\n      <Menu.Root orientation=\"horizontal\" open modal={false}>\n        <Menu.Portal>\n          <Menu.Positioner side=\"bottom\" align=\"start\" sideOffset={6} anchor={containerRef}>\n            <Menu.Popup className={styles.MenuRootPopup}>\n              <Menu.SubmenuRoot>\n                <Menu.SubmenuTrigger openOnHover={false} className={styles.SubmenuTrigger}>\n                  Text color\n                </Menu.SubmenuTrigger>\n                <Menu.Portal>\n                  <Menu.Positioner align=\"start\" side=\"bottom\" sideOffset={12}>\n                    <Menu.Popup className={styles.MenuPopup}>\n                      <Menu.Item\n                        onClick={createHandleMenuClick('Text color/Black')}\n                        className={styles.MenuItem}\n                      >\n                        Black\n                      </Menu.Item>\n                      <Menu.Item\n                        onClick={createHandleMenuClick('Text color/Dark grey')}\n                        className={styles.MenuItem}\n                      >\n                        Dark grey\n                      </Menu.Item>\n                      <Menu.Item\n                        onClick={createHandleMenuClick('Text color/Accent')}\n                        className={styles.MenuItem}\n                      >\n                        Accent\n                      </Menu.Item>\n                    </Menu.Popup>\n                  </Menu.Positioner>\n                </Menu.Portal>\n              </Menu.SubmenuRoot>\n\n              <Menu.SubmenuRoot>\n                <Menu.SubmenuTrigger openOnHover={false} className={styles.SubmenuTrigger}>\n                  Style\n                </Menu.SubmenuTrigger>\n                <Menu.Portal>\n                  <Menu.Positioner align=\"start\" side=\"bottom\" sideOffset={12}>\n                    <Menu.Popup className={styles.MenuPopup}>\n                      <Menu.SubmenuRoot>\n                        <Menu.SubmenuTrigger className={styles.SubmenuTrigger}>\n                          Heading\n                        </Menu.SubmenuTrigger>\n                        <Menu.Portal>\n                          <Menu.Positioner align=\"start\" side=\"right\" sideOffset={12}>\n                            <Menu.Popup className={styles.MenuPopup}>\n                              <Menu.Item\n                                onClick={createHandleMenuClick('Style/Heading/Level 1')}\n                                className={styles.MenuItem}\n                              >\n                                Level 1\n                              </Menu.Item>\n                              <Menu.Item\n                                onClick={createHandleMenuClick('Style/Heading/Level 2')}\n                                className={styles.MenuItem}\n                              >\n                                Level 2\n                              </Menu.Item>\n                              <Menu.Item\n                                onClick={createHandleMenuClick('Style/Heading/Level 3')}\n                                className={styles.MenuItem}\n                              >\n                                Level 3\n                              </Menu.Item>\n                            </Menu.Popup>\n                          </Menu.Positioner>\n                        </Menu.Portal>\n                      </Menu.SubmenuRoot>\n                      <Menu.Item\n                        onClick={createHandleMenuClick('Style/Paragraph')}\n                        className={styles.MenuItem}\n                      >\n                        Paragraph\n                      </Menu.Item>\n                      <Menu.SubmenuRoot disabled>\n                        <Menu.SubmenuTrigger className={styles.SubmenuTrigger}>\n                          List\n                        </Menu.SubmenuTrigger>\n                        <Menu.Portal>\n                          <Menu.Positioner align=\"start\" side=\"bottom\" sideOffset={12}>\n                            <Menu.Popup className={styles.MenuPopup}>\n                              <Menu.Item\n                                onClick={createHandleMenuClick('Style/List/Ordered')}\n                                className={styles.MenuItem}\n                              >\n                                Ordered\n                              </Menu.Item>\n                              <Menu.Item\n                                onClick={createHandleMenuClick('Style/List/Unordered')}\n                                className={styles.MenuItem}\n                              >\n                                Unordered\n                              </Menu.Item>\n                            </Menu.Popup>\n                          </Menu.Positioner>\n                        </Menu.Portal>\n                      </Menu.SubmenuRoot>\n                    </Menu.Popup>\n                  </Menu.Positioner>\n                </Menu.Portal>\n              </Menu.SubmenuRoot>\n            </Menu.Popup>\n          </Menu.Positioner>\n        </Menu.Portal>\n      </Menu.Root>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/menu/menu-nested.module.css",
    "content": ".Container {\n  display: flex;\n  min-height: 110vh;\n  box-sizing: border-box;\n  align-items: center;\n  gap: 20px;\n}\n\n.MenuPopup {\n  font-family: 'IBM Plex Sans', sans-serif;\n  font-size: 0.875rem;\n  box-sizing: border-box;\n  padding: 6px;\n  min-width: 200px;\n  border-radius: 12px;\n  overflow: visible;\n  outline: 0;\n  background: #fff;\n  border: 1px solid var(--color-gray-200);\n  color: var(--color-gray-900);\n  box-shadow: 0 4px 30px var(--color-gray-200);\n  z-index: 1;\n  transform-origin: var(--transform-origin);\n  opacity: 1;\n  transform: scale(1, 1);\n  transition:\n    opacity 100ms ease-in,\n    transform 100ms ease-in;\n\n  &[data-nested] {\n    margin-top: -6px;\n  }\n\n  &[data-starting-style] {\n    opacity: 0;\n    transform: scale(0.8);\n  }\n\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.8);\n    transition:\n      opacity 200ms ease-in,\n      transform 200ms ease-in;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    background: var(--color-gray-900);\n    border: 1px solid var(--color-gray-700);\n    color: var(--color-gray-300);\n    box-shadow: 0 4px 30px var(--color-gray-900);\n  }\n}\n\n.MenuItem {\n  list-style: none;\n  padding: 8px;\n  border-radius: 8px;\n  cursor: default;\n  user-select: none;\n\n  &:last-of-type {\n    border-bottom: none;\n  }\n\n  &:focus,\n  &:hover {\n    background-color: var(--color-gray-100);\n    color: var(--color-gray-900);\n  }\n\n  &:focus-visible {\n    outline: none;\n  }\n\n  &[data-disabled] {\n    color: var(--color-gray-400);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    &:focus,\n    &:hover {\n      background-color: var(--color-gray-800);\n      color: var(--color-gray-300);\n    }\n\n    &[data-disabled] {\n      color: var(--color-gray-700);\n    }\n  }\n}\n\n.SubmenuTrigger {\n  list-style: none;\n  padding: 8px;\n  border-radius: 8px;\n  cursor: default;\n  user-select: none;\n\n  &:last-of-type {\n    border-bottom: none;\n  }\n\n  &::after {\n    content: '›';\n    float: right;\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-50);\n    color: var(--color-gray-900);\n  }\n\n  &:focus,\n  &:hover {\n    background-color: var(--color-gray-100);\n    color: var(--color-gray-900);\n  }\n\n  &:focus-visible {\n    outline: none;\n  }\n\n  &[data-disabled] {\n    color: var(--color-gray-400);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    &[data-popup-open] {\n      background-color: var(--color-gray-900);\n      color: var(--color-gray-300);\n    }\n\n    &:focus,\n    &:hover {\n      background-color: var(--color-gray-800);\n      color: var(--color-gray-300);\n    }\n\n    &[data-disabled] {\n      color: var(--color-gray-700);\n    }\n  }\n}\n\n.MenuButton {\n  font-family: 'IBM Plex Sans', sans-serif;\n  font-weight: 600;\n  font-size: 0.875rem;\n  line-height: 1.5;\n  padding: 8px 16px;\n  border-radius: 8px;\n  transition: all 150ms ease;\n  cursor: pointer;\n  background: #fff;\n  border: 1px solid var(--color-gray-200);\n  color: var(--color-gray-900);\n  box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);\n\n  &:hover {\n    background: var(--color-gray-50);\n    border-color: var(--color-gray-300);\n  }\n\n  &:active {\n    background: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    box-shadow: 0 0 0 2px var(--color-blue);\n    outline: none;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    background: var(--color-gray-900);\n    border: 1px solid var(--color-gray-700);\n    color: var(--color-gray-200);\n\n    &:hover {\n      background: var(--color-gray-800);\n      border-color: var(--color-gray-600);\n    }\n\n    &:active {\n      background: var(--color-gray-700);\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/menu/menu-nested.tsx",
    "content": "'use client';\nimport { Menu } from '@base-ui/react/menu';\nimport styles from './menu-nested.module.css';\n\nexport default function NestedMenu() {\n  const createHandleMenuClick = (menuItem: string) => {\n    return () => {\n      console.log(`Clicked on ${menuItem}`);\n    };\n  };\n\n  return (\n    <div className={styles.Container}>\n      <Menu.Root>\n        <input type=\"text\" placeholder=\"Previous element\" />\n        <Menu.Trigger className={styles.MenuButton}>Format</Menu.Trigger>\n        <Menu.Portal>\n          <Menu.Positioner side=\"bottom\" align=\"start\" sideOffset={6}>\n            <Menu.Popup className={styles.MenuPopup}>\n              <Menu.SubmenuRoot>\n                <Menu.SubmenuTrigger className={styles.SubmenuTrigger}>\n                  Text color\n                </Menu.SubmenuTrigger>\n                <Menu.Portal>\n                  <Menu.Positioner align=\"start\" side=\"right\" sideOffset={12}>\n                    <Menu.Popup className={styles.MenuPopup}>\n                      <Menu.Item\n                        onClick={createHandleMenuClick('Text color/Black')}\n                        className={styles.MenuItem}\n                      >\n                        Black\n                      </Menu.Item>\n                      <Menu.Item\n                        onClick={createHandleMenuClick('Text color/Dark grey')}\n                        className={styles.MenuItem}\n                      >\n                        Dark grey\n                      </Menu.Item>\n                      <Menu.Item\n                        onClick={createHandleMenuClick('Text color/Accent')}\n                        className={styles.MenuItem}\n                      >\n                        Accent\n                      </Menu.Item>\n                    </Menu.Popup>\n                  </Menu.Positioner>\n                </Menu.Portal>\n              </Menu.SubmenuRoot>\n\n              <Menu.SubmenuRoot>\n                <Menu.SubmenuTrigger className={styles.SubmenuTrigger} closeDelay={1000}>\n                  Style\n                </Menu.SubmenuTrigger>\n                <Menu.Portal>\n                  <Menu.Positioner align=\"start\" side=\"right\" sideOffset={12}>\n                    <Menu.Popup className={styles.MenuPopup}>\n                      <Menu.SubmenuRoot>\n                        <Menu.SubmenuTrigger className={styles.SubmenuTrigger}>\n                          Heading\n                        </Menu.SubmenuTrigger>\n                        <Menu.Portal>\n                          <Menu.Positioner align=\"start\" side=\"right\" sideOffset={12}>\n                            <Menu.Popup className={styles.MenuPopup}>\n                              <Menu.Item\n                                onClick={createHandleMenuClick('Style/Heading/Level 1')}\n                                className={styles.MenuItem}\n                              >\n                                Level 1\n                              </Menu.Item>\n                              <Menu.Item\n                                onClick={createHandleMenuClick('Style/Heading/Level 2')}\n                                className={styles.MenuItem}\n                              >\n                                Level 2\n                              </Menu.Item>\n                              <Menu.Item\n                                onClick={createHandleMenuClick('Style/Heading/Level 3')}\n                                className={styles.MenuItem}\n                              >\n                                Level 3\n                              </Menu.Item>\n                            </Menu.Popup>\n                          </Menu.Positioner>\n                        </Menu.Portal>\n                      </Menu.SubmenuRoot>\n                      <Menu.Item\n                        onClick={createHandleMenuClick('Style/Paragraph')}\n                        className={styles.MenuItem}\n                      >\n                        Paragraph\n                      </Menu.Item>\n                      <Menu.SubmenuRoot disabled>\n                        <Menu.SubmenuTrigger className={styles.SubmenuTrigger}>\n                          List\n                        </Menu.SubmenuTrigger>\n                        <Menu.Portal>\n                          <Menu.Positioner align=\"start\" side=\"right\" sideOffset={12}>\n                            <Menu.Popup className={styles.MenuPopup}>\n                              <Menu.Item\n                                onClick={createHandleMenuClick('Style/List/Ordered')}\n                                className={styles.MenuItem}\n                              >\n                                Ordered\n                              </Menu.Item>\n                              <Menu.Item\n                                onClick={createHandleMenuClick('Style/List/Unordered')}\n                                className={styles.MenuItem}\n                              >\n                                Unordered\n                              </Menu.Item>\n                            </Menu.Popup>\n                          </Menu.Positioner>\n                        </Menu.Portal>\n                      </Menu.SubmenuRoot>\n                    </Menu.Popup>\n                  </Menu.Positioner>\n                </Menu.Portal>\n              </Menu.SubmenuRoot>\n\n              <Menu.Item\n                onClick={createHandleMenuClick('Clear formatting')}\n                className={styles.MenuItem}\n              >\n                Clear formatting\n              </Menu.Item>\n            </Menu.Popup>\n          </Menu.Positioner>\n        </Menu.Portal>\n        <input type=\"text\" placeholder=\"Next element\" />\n      </Menu.Root>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/menu/menu-submenus.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { SettingsMetadata, useExperimentSettings } from '../_components/SettingsPanel';\nimport '../../../../demo-data/theme/css-modules/theme.css';\nimport classes from './menu.module.css';\n\ninterface Settings {\n  customAnchor: boolean;\n  modal: boolean;\n  openOnHover: boolean;\n  disabled: boolean;\n  customTriggerElement: boolean;\n  side: Menu.Positioner.Props['side'];\n  align: Menu.Positioner.Props['align'];\n}\n\nexport default function MenuSubmenus() {\n  const { settings } = useExperimentSettings<Settings>();\n\n  const anchorRef = React.useRef<HTMLDivElement>(null);\n\n  const triggerRender = React.useMemo(\n    () => (settings.customTriggerElement ? <span /> : undefined),\n    [settings.customTriggerElement],\n  );\n\n  const handleItemClick = useStableCallback((event: React.MouseEvent<HTMLDivElement>) => {\n    console.log(`${event.currentTarget.textContent} clicked`);\n  });\n\n  const renderMenu = (\n    label: string,\n    submenuTriggerDelay?: Menu.SubmenuTrigger.Props['delay'],\n    submenuTriggerClassName: string = classes.SubmenuTrigger,\n    popupClassName: string = classes.Popup,\n  ) => (\n    <Menu.Root modal={settings.modal} disabled={settings.disabled}>\n      <Menu.Trigger\n        className={classes.Button}\n        render={triggerRender}\n        nativeButton={triggerRender === undefined}\n        openOnHover={settings.openOnHover}\n      >\n        {label} <ChevronDownIcon className={classes.ButtonIcon} />\n      </Menu.Trigger>\n      <Menu.Portal>\n        <Menu.Positioner\n          className={classes.Positioner}\n          sideOffset={8}\n          anchor={settings.customAnchor ? anchorRef : undefined}\n          side={settings.side}\n          align={settings.align}\n        >\n          <Menu.Popup\n            className={popupClassName}\n            style={{ maxHeight: 'var(--available-height)', overflowY: 'scroll' }}\n          >\n            {Array.from({ length: 50 }).map((_, submenuIndex) => (\n              <Menu.SubmenuRoot key={submenuIndex}>\n                <Menu.SubmenuTrigger\n                  className={submenuTriggerClassName}\n                  delay={submenuTriggerDelay}\n                >\n                  Submenu test index {submenuIndex + 1}\n                  <ChevronRightIcon />\n                </Menu.SubmenuTrigger>\n                <Menu.Portal>\n                  <Menu.Positioner className={classes.Positioner} sideOffset={8}>\n                    <Menu.Popup className={popupClassName}>\n                      {Array.from({ length: 12 }).map((__, itemIndex) => (\n                        <Menu.SubmenuRoot key={itemIndex}>\n                          <Menu.SubmenuTrigger\n                            className={submenuTriggerClassName}\n                            delay={submenuTriggerDelay}\n                          >\n                            Submenu test index {submenuIndex + 1} - Item {itemIndex + 1}\n                            <ChevronRightIcon />\n                          </Menu.SubmenuTrigger>\n                          <Menu.Portal>\n                            <Menu.Positioner className={classes.Positioner} sideOffset={8}>\n                              <Menu.Popup className={popupClassName}>\n                                {Array.from({ length: 8 }).map((___, nestedIndex) => (\n                                  <Menu.Item\n                                    key={nestedIndex}\n                                    className={classes.Item}\n                                    onClick={handleItemClick}\n                                  >\n                                    Nested submenu {submenuIndex + 1}.{itemIndex + 1} - Item{' '}\n                                    {nestedIndex + 1}\n                                  </Menu.Item>\n                                ))}\n                              </Menu.Popup>\n                            </Menu.Positioner>\n                          </Menu.Portal>\n                        </Menu.SubmenuRoot>\n                      ))}\n                    </Menu.Popup>\n                  </Menu.Positioner>\n                </Menu.Portal>\n              </Menu.SubmenuRoot>\n            ))}\n          </Menu.Popup>\n        </Menu.Positioner>\n      </Menu.Portal>\n    </Menu.Root>\n  );\n\n  return (\n    <div>\n      <h1>Many adjacent submenus</h1>\n      <div className={classes.TriggerRow}>\n        {renderMenu('Menu')}\n        {renderMenu(\n          'Menu (submenu delay=0)',\n          0,\n          `${classes.SubmenuTrigger} ${classes.PopupOpenAsHighlighted}`,\n          `${classes.Popup} ${classes.PopupNoAnimation}`,\n        )}\n      </div>\n\n      {settings.customAnchor && (\n        <div className={classes.CustomAnchor} ref={anchorRef}>\n          Menu will be anchored here\n        </div>\n      )}\n    </div>\n  );\n}\n\nexport const settingsMetadata: SettingsMetadata<Settings> = {\n  customAnchor: {\n    type: 'boolean',\n    label: 'Custom anchor',\n  },\n  modal: {\n    type: 'boolean',\n    label: 'Modal',\n    default: true,\n  },\n  openOnHover: {\n    type: 'boolean',\n    label: 'Open on hover',\n  },\n  disabled: {\n    type: 'boolean',\n    label: 'Disabled',\n  },\n  customTriggerElement: {\n    type: 'boolean',\n    label: 'Trigger as <span>',\n  },\n  side: {\n    type: 'string',\n    label: 'Side',\n    options: ['top', 'right', 'bottom', 'left'],\n    default: 'bottom',\n  },\n  align: {\n    type: 'string',\n    label: 'Align',\n    options: ['start', 'center', 'end'],\n    default: 'center',\n  },\n};\n\nfunction ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M1 3.5L5 7.5L9 3.5\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n\nfunction ChevronRightIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M3.5 9L7.5 5L3.5 1\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/menu/menu.module.css",
    "content": ".Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: 0.375rem;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: var(--text-base);\n  font-weight: 500;\n  line-height: var(--text-base--line-height);\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.ButtonIcon {\n  margin-right: -0.25rem;\n}\n\n.TriggerRow {\n  display: flex;\n  gap: 0.75rem;\n  flex-wrap: wrap;\n}\n\n.Positioner {\n  outline: 0;\n}\n\n.Popup {\n  box-sizing: border-box;\n  padding-block: 0.25rem;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.PopupNoAnimation {\n  transition: none;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 1;\n    transform: none;\n  }\n}\n\n.Arrow {\n  display: flex;\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='inline-start'] {\n    inset-inline-end: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='inline-end'] {\n    inset-inline-start: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n\n.Item,\n.SubmenuTrigger {\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  padding-block: 0.5rem;\n  padding-left: 1rem;\n  padding-right: 2rem;\n  display: flex;\n  font-size: var(--text-sm);\n  line-height: 1rem;\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.25rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n\n  &[data-disabled][data-highlighted]::before {\n    background-color: var(--color-gray-300);\n  }\n\n  &[data-disabled] {\n    color: var(--color-gray-400);\n  }\n}\n\n.SubmenuTrigger {\n  align-items: center;\n  justify-content: space-between;\n  gap: 1rem;\n  padding-right: 1rem;\n}\n\n.PopupOpenAsHighlighted {\n  &[data-highlighted],\n  &[data-popup-open] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before,\n  &[data-popup-open]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.25rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n\n  &[data-disabled][data-highlighted]::before,\n  &[data-disabled][data-popup-open]::before {\n    background-color: var(--color-gray-300);\n  }\n}\n\n.Separator {\n  margin: 0.375rem 1rem;\n  height: 1px;\n  background-color: var(--color-gray-200);\n}\n\n.CheckboxItem,\n.RadioItem {\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  padding-block: 0.5rem;\n  padding-left: 0.625rem;\n  padding-right: 2rem;\n  font-size: var(--text-sm);\n  line-height: 1rem;\n  display: grid;\n  gap: 0.5rem;\n  align-items: center;\n  grid-template-columns: 0.75rem 1fr;\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.25rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n\n  &[data-disabled][data-highlighted]::before {\n    background-color: var(--color-gray-300);\n  }\n\n  &[data-disabled] {\n    color: var(--color-gray-400);\n  }\n}\n\n.CheckboxItemIndicator,\n.RadioItemIndicator {\n  grid-column-start: 1;\n}\n\n.CheckboxItemIndicatorIcon,\n.RadioItemIndicatorIcon {\n  display: block;\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.CheckboxItemText,\n.RadioItemText {\n  grid-column-start: 2;\n}\n\n.GroupLabel {\n  cursor: default;\n  user-select: none;\n  padding-block: 0.5rem;\n  padding-left: 1.875rem;\n  padding-right: 2rem;\n  font-size: var(--text-sm);\n  line-height: 1rem;\n  color: var(--color-gray-600);\n}\n\n.CustomAnchor {\n  margin-top: 3rem;\n  text-align: center;\n  padding: 1rem;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.25rem;\n}\n\n.ExperimentRoot {\n  padding: 2rem;\n}\n\n.ExperimentTitle {\n  margin: 0;\n  font-size: 2rem;\n  line-height: 2.5rem;\n  font-weight: 500;\n  letter-spacing: var(--text-2xl--letter-spacing);\n}\n\n.ExperimentDescription {\n  margin: 0 0 3rem;\n  color: var(--color-gray-600);\n}\n\n.Section {\n  margin: 0;\n}\n\n.SectionTitle {\n  margin: 0 0 1rem;\n  font-size: var(--text-xl);\n  line-height: var(--text-xl--line-height);\n  font-weight: 600;\n  letter-spacing: var(--text-xl--letter-spacing);\n}\n\n.SectionDescription {\n  margin: 0 0 1rem;\n  color: var(--color-gray-600);\n}\n\n.DialogBackdrop {\n  position: fixed;\n  inset: 0;\n  background: rgb(0 0 0 / 0.5);\n}\n\n.DialogPopup {\n  position: fixed;\n  top: 50%;\n  left: 50%;\n  width: 500px;\n  max-width: 90vw;\n  transform: translate(-50%, -50%);\n  border-radius: 0.5rem;\n  background: white;\n  padding: 1.5rem;\n  box-shadow:\n    0 20px 25px -5px rgb(0 0 0 / 0.1),\n    0 8px 10px -6px rgb(0 0 0 / 0.1);\n}\n\n.DialogTitle {\n  margin: 0 0 1rem;\n  font-size: 1.25rem;\n  line-height: var(--text-lg--line-height);\n  font-weight: 600;\n}\n\n.DialogBody > :not([hidden]) ~ :not([hidden]) {\n  margin-top: 1rem;\n}\n\n.DialogDescription {\n  margin: 0;\n  color: var(--color-gray-600);\n}\n\n.Card {\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.5rem;\n  background: var(--color-gray-50);\n  padding: 1rem;\n}\n\n.CardTitle {\n  margin: 0 0 0.75rem;\n  font-weight: 500;\n}\n\n.DialogActions {\n  display: flex;\n  justify-content: flex-end;\n}\n\n.DialogActions > :not([hidden]) ~ :not([hidden]) {\n  margin-left: 0.5rem;\n}\n\n.DialogCancel,\n.DialogConfirm {\n  border-radius: 0.25rem;\n  padding: 0.5rem 1rem;\n}\n\n.DialogCancel {\n  color: var(--color-gray-600);\n}\n\n@media (hover: hover) {\n  .DialogCancel:hover {\n    background: var(--color-gray-100);\n  }\n\n  .DialogConfirm:hover {\n    background: var(--color-blue-700);\n  }\n}\n\n.DialogConfirm {\n  background: var(--color-blue-600);\n}\n\n.LinkItem {\n  cursor: pointer;\n}\n\n.NonFocusableText {\n  display: flex;\n  align-items: center;\n  padding: 0.5rem 0 0.5rem 1.875rem;\n  font-size: 0.75rem;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/menu/nested-detached-triggers.module.css",
    "content": ".Page {\n  display: flex;\n  flex-direction: column;\n  padding-bottom: 30vh;\n\n  h2 {\n    margin-block: 1rem;\n    font-weight: bold;\n  }\n}\n\n.Menubar {\n  display: flex;\n  background-color: var(--color-gray-50);\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  padding: 0.125rem;\n  margin-bottom: 16px;\n}\n\n.Toolbar {\n  display: flex;\n  align-items: center;\n  gap: 1px;\n  border: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-50);\n  border-radius: 0.375rem;\n  padding: 0.125rem;\n  width: 37.5rem;\n  margin-bottom: 16px;\n}\n\n.ToolbarButton {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  min-width: 2rem;\n  height: 2rem;\n  padding: 0;\n  margin: 0;\n  outline: 0;\n  border: 0;\n  border-radius: 0.25rem;\n  background-color: transparent;\n  color: var(--color-gray-600);\n  user-select: none;\n  font-family: inherit;\n  font-size: 0.875rem;\n  font-weight: 500;\n\n  &:focus-visible {\n    background-color: transparent;\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-200);\n  }\n\n  &[data-pressed] {\n    background-color: var(--color-gray-100);\n    color: var(--color-gray-900);\n  }\n\n  &[aria-pressed] {\n    padding: 0 0.75rem;\n  }\n\n  &[role='combobox'] {\n    min-width: 8rem;\n    justify-content: space-between;\n    padding: 0 0.75rem;\n  }\n}\n\n.MenuTrigger {\n  box-sizing: border-box;\n  background: none;\n  padding: 0 0.75rem;\n  margin: 0;\n  outline: 0;\n  border: 0;\n  color: var(--color-gray-600);\n  border-radius: 0.25rem;\n  user-select: none;\n  height: 2rem;\n  font-family: inherit;\n  font-size: 0.875rem;\n  font-weight: 500;\n\n  &[data-pressed],\n  &:focus-visible {\n    background-color: var(--color-gray-100);\n    outline: none;\n  }\n\n  &[data-disabled] {\n    opacity: 0.5;\n  }\n}\n\n.StandaloneTriggerContainer {\n  display: flex;\n  gap: 8px;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/menu/nested-detached-triggers.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\nimport { Menubar } from '@base-ui/react/menubar';\nimport { Toolbar } from '@base-ui/react/toolbar';\nimport { useRefWithInit } from '@base-ui/utils/useRefWithInit';\nimport { StoreInspector } from '@base-ui/utils/store';\nimport demoStyles from 'docs/src/app/(docs)/react/components/menu/demos/submenu/css-modules/index.module.css';\nimport styles from './nested-detached-triggers.module.css';\n\nconst contents = {\n  Library: [\n    { type: 'item', label: 'Add to Library' },\n    {\n      type: 'submenu',\n      label: 'Add to Playlist',\n      menu: [\n        { type: 'item', label: 'Favorites' },\n        { type: 'item', label: 'Chill Vibes' },\n        { type: 'item', label: 'Workout Mix' },\n      ],\n    },\n    { type: 'separator' },\n    { type: 'item', label: \"Favorite (don't close on click)\", closeOnClick: false },\n    {\n      type: 'submenu',\n      label: 'Share',\n      menu: [\n        { type: 'item', label: 'Copy Link' },\n        {\n          type: 'submenu',\n          label: 'Social Media',\n          menu: [\n            { type: 'item', label: 'Facebook' },\n            { type: 'item', label: 'Twitter' },\n            { type: 'item', label: 'Instagram' },\n          ],\n        },\n      ],\n    },\n  ] as MenuContentItem[],\n  Playback: [\n    { type: 'item', label: 'Play Next' },\n    { type: 'item', label: 'Play Last' },\n    { type: 'separator' },\n    { type: 'item', label: 'Play on Device' },\n    { type: 'item', label: 'Download for Offline' },\n  ] as MenuContentItem[],\n  Sharing: [\n    { type: 'item', label: 'Start Radio' },\n    { type: 'item', label: 'Create Station' },\n    { type: 'separator' },\n    { type: 'item', label: 'Follow Artist' },\n    { type: 'item', label: 'View Artist' },\n  ] as MenuContentItem[],\n};\n\nconst topLevelContentKeys: ContentKey[] = Object.keys(contents) as ContentKey[];\n\nexport default function Experiment() {\n  const menu1Handle = useRefWithInit(() => Menu.createHandle<ContentKey>()).current;\n\n  return (\n    <div className={styles.Page}>\n      <h1>Nested menus with detached triggers</h1>\n      <p>This experiment shows the same Menu instance used across different components.</p>\n      <StoreInspector store={menu1Handle.store} />\n\n      <h2>In Menubar</h2>\n      <div>\n        <h3>Detached</h3>\n        <Menubar className={styles.Menubar}>\n          {topLevelContentKeys.map((contentKey) => (\n            <Menu.Trigger\n              className={styles.MenuTrigger}\n              handle={menu1Handle}\n              payload={contentKey}\n              key={contentKey}\n            >\n              {contentKey}\n            </Menu.Trigger>\n          ))}\n        </Menubar>\n\n        <h3>Multiple contained</h3>\n        <Menubar className={styles.Menubar}>\n          <ReusableMenu>\n            {topLevelContentKeys.map((contentKey) => (\n              <Menu.Trigger className={styles.MenuTrigger} payload={contentKey} key={contentKey}>\n                {contentKey}\n              </Menu.Trigger>\n            ))}\n          </ReusableMenu>\n        </Menubar>\n\n        <h2>In Toolbar</h2>\n        <Toolbar.Root className={styles.Toolbar}>\n          {topLevelContentKeys.map((contentKey) => (\n            <Toolbar.Button\n              className={styles.ToolbarButton}\n              key={contentKey}\n              render={\n                <Menu.Trigger\n                  className={styles.MenuTrigger}\n                  handle={menu1Handle}\n                  payload={contentKey}\n                  key={contentKey}\n                />\n              }\n            >\n              {contentKey}\n            </Toolbar.Button>\n          ))}\n        </Toolbar.Root>\n\n        <Toolbar.Root className={styles.Toolbar}>\n          {topLevelContentKeys.map((contentKey) => (\n            <Toolbar.Button\n              className={styles.ToolbarButton}\n              key={contentKey}\n              render={\n                <Menu.Trigger\n                  className={styles.MenuTrigger}\n                  handle={menu1Handle}\n                  payload={contentKey}\n                  key={contentKey}\n                />\n              }\n            >\n              {contentKey}\n            </Toolbar.Button>\n          ))}\n        </Toolbar.Root>\n\n        <h2>Standalone</h2>\n        <div className={styles.StandaloneTriggerContainer}>\n          {topLevelContentKeys.map((contentKey) => (\n            <Menu.Trigger\n              className={demoStyles.Button}\n              handle={menu1Handle}\n              payload={contentKey}\n              key={contentKey}\n            >\n              {contentKey}\n            </Menu.Trigger>\n          ))}\n        </div>\n\n        <ReusableMenu handle={menu1Handle} />\n      </div>\n    </div>\n  );\n}\n\nfunction ReusableMenu(props: { handle?: Menu.Handle<ContentKey>; children?: React.ReactNode }) {\n  const { handle, children } = props;\n\n  return (\n    <Menu.Root handle={handle}>\n      {({ payload: contentKey }) => {\n        const items = contentKey ? contents[contentKey] : undefined;\n        return (\n          <React.Fragment>\n            {children}\n            <Menu.Portal>\n              <Menu.Positioner sideOffset={8} className={demoStyles.Positioner}>\n                <Menu.Popup className={demoStyles.Popup}>\n                  <Menu.Arrow className={demoStyles.Arrow}>\n                    <ArrowSvg />\n                  </Menu.Arrow>\n                  {items ? (\n                    items.map((item, index) => renderMenuContentItem(item, `item-${index}`))\n                  ) : (\n                    <div className={styles.MenuSection}>No content for this trigger.</div>\n                  )}\n                </Menu.Popup>\n              </Menu.Positioner>\n            </Menu.Portal>\n          </React.Fragment>\n        );\n      }}\n    </Menu.Root>\n  );\n}\n\ntype MenuContentRegularItem = {\n  type: 'item';\n  label: string;\n  disabled?: boolean;\n  closeOnClick?: boolean;\n  onClick?: () => void;\n};\n\ntype MenuContentSeparator = { type: 'separator' };\n\ntype MenuContentSubmenu = {\n  type: 'submenu';\n  label: string;\n  menu: MenuContentItem[];\n};\n\ntype MenuContentItem = MenuContentRegularItem | MenuContentSeparator | MenuContentSubmenu;\n\ntype ContentKey = keyof typeof contents;\n\nfunction renderMenuContentItem(item: MenuContentItem, key: string) {\n  switch (item.type) {\n    case 'item':\n      return (\n        <Menu.Item\n          className={demoStyles.Item}\n          disabled={item.disabled}\n          closeOnClick={item.closeOnClick}\n          onClick={item.onClick ?? (() => console.log('Clicked on', item.label))}\n          key={key}\n        >\n          {item.label}\n        </Menu.Item>\n      );\n    case 'separator':\n      return <Menu.Separator className={demoStyles.Separator} key={key} />;\n    case 'submenu':\n      return (\n        <Menu.SubmenuRoot key={key}>\n          <Menu.SubmenuTrigger className={demoStyles.SubmenuTrigger}>\n            {item.label}\n            <ChevronRightIcon />\n          </Menu.SubmenuTrigger>\n          <Menu.Portal>\n            <Menu.Positioner className={demoStyles.Positioner} alignOffset={-4} sideOffset={-4}>\n              <Menu.Popup className={demoStyles.Popup}>\n                {item.menu.map((subItem, subIndex) =>\n                  renderMenuContentItem(subItem, `${key}.${subIndex}`),\n                )}\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.SubmenuRoot>\n      );\n    default:\n      return null;\n  }\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={demoStyles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={demoStyles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={demoStyles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nfunction ChevronRightIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M3.5 9L7.5 5L3.5 1\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/menu/perf-contained.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport demoStyles from 'docs/src/app/(docs)/react/components/menu/demos/submenu/css-modules/index.module.css';\nimport { Menu } from '@base-ui/react/menu';\nimport styles from '../perf/perf.module.css';\n\ntype RowData = {\n  label: string;\n  index: number;\n};\n\nconst rowCount = 500;\nconst menuItemCount = 50;\n\nconst rows = Array.from({ length: rowCount }).map((_, i) => ({\n  label: `Row ${i + 1}`,\n  index: i + 1,\n}));\n\nconst menuItems = Array.from({ length: menuItemCount }).map((_, i) => ({\n  label: `Menu Item ${i + 1}`,\n  index: i + 1,\n}));\n\nexport default function PerfMenuExperiment() {\n  return (\n    <div className={styles.container}>\n      <h1>Menu performance - contained triggers</h1>\n      <div className={styles.rows}>\n        {rows.map((row) => (\n          <div key={row.index} className={styles.row}>\n            <span className={styles.label}>{row.label}</span>\n            <RowMenu rowData={row} />\n          </div>\n        ))}\n      </div>\n    </div>\n  );\n}\n\ninterface RowMenuProps {\n  rowData: RowData;\n}\n\nfunction RowMenu({ rowData }: RowMenuProps) {\n  return (\n    <Menu.Root>\n      <Menu.Trigger className={demoStyles.Trigger} data-id={rowData.index}>\n        •••\n      </Menu.Trigger>\n      <Menu.Portal>\n        <Menu.Positioner sideOffset={8} className={demoStyles.Positioner}>\n          <Menu.Popup className={demoStyles.Popup}>\n            <Menu.Arrow className={demoStyles.Arrow}>\n              <ArrowSvg />\n            </Menu.Arrow>\n            {menuItems.map((item) => (\n              <Menu.Item\n                key={item.index}\n                onClick={() => console.log(`Clicked ${item.label} for ${rowData.label}`)}\n                className={demoStyles.Item}\n              >\n                {item.label} for {rowData.label}\n              </Menu.Item>\n            ))}\n          </Menu.Popup>\n        </Menu.Positioner>\n      </Menu.Portal>\n    </Menu.Root>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={demoStyles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={demoStyles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={demoStyles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/menu/perf-detached.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport demoStyles from 'docs/src/app/(docs)/react/components/menu/demos/submenu/css-modules/index.module.css';\nimport { Menu } from '@base-ui/react/menu';\nimport styles from '../perf/perf.module.css';\n\ntype RowData = {\n  label: string;\n  index: number;\n};\n\nconst rowCount = 500;\nconst menuItemCount = 50;\n\nconst rows = Array.from({ length: rowCount }).map((_, i) => ({\n  label: `Row ${i + 1}`,\n  index: i + 1,\n}));\n\nconst menuItems = Array.from({ length: menuItemCount }).map((_, i) => ({\n  label: `Menu Item ${i + 1}`,\n  index: i + 1,\n}));\n\nconst rowMenuHandle = Menu.createHandle<RowData>();\n\nexport default function PerfMenuExperiment() {\n  return (\n    <div className={styles.container}>\n      <h1>Menu performance - detached triggers</h1>\n      <div className={styles.rows}>\n        {rows.map((row) => (\n          <div key={row.index} className={styles.row}>\n            <span className={styles.label}>{row.label}</span>\n            <Menu.Trigger\n              className={demoStyles.Trigger}\n              handle={rowMenuHandle}\n              payload={row}\n              data-id={row.index}\n            >\n              •••\n            </Menu.Trigger>\n          </div>\n        ))}\n      </div>\n      <RowMenu />\n    </div>\n  );\n}\n\nfunction RowMenu() {\n  return (\n    <Menu.Root handle={rowMenuHandle}>\n      {({ payload: rowData }) => (\n        <Menu.Portal>\n          <Menu.Positioner sideOffset={8} className={demoStyles.Positioner}>\n            <Menu.Popup className={demoStyles.Popup}>\n              <Menu.Arrow className={demoStyles.Arrow}>\n                <ArrowSvg />\n              </Menu.Arrow>\n              {rowData && (\n                <React.Fragment>\n                  {menuItems.map((item) => (\n                    <Menu.Item\n                      key={item.index}\n                      onClick={() => console.log(`Clicked ${item.label} for ${rowData.label}`)}\n                      className={demoStyles.Item}\n                    >\n                      {item.label} for {rowData.label}\n                    </Menu.Item>\n                  ))}\n                </React.Fragment>\n              )}\n            </Menu.Popup>\n          </Menu.Positioner>\n        </Menu.Portal>\n      )}\n    </Menu.Root>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={demoStyles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={demoStyles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={demoStyles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/menu/pointer-events-scope.module.css",
    "content": ".root {\n  display: flex;\n  flex-direction: column;\n  gap: 1rem;\n  max-width: 1200px;\n}\n\n.controls {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 0.75rem 1rem;\n  align-items: end;\n  padding: 0.75rem;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.5rem;\n}\n\n.field {\n  display: flex;\n  flex-direction: column;\n  gap: 0.35rem;\n  min-width: 8rem;\n}\n\n.fieldLabel {\n  font-size: 0.8125rem;\n  color: var(--color-gray-700);\n}\n\n.modeGroup {\n  display: inline-flex;\n  border: 1px solid var(--color-gray-300);\n  border-radius: 0.375rem;\n  overflow: hidden;\n}\n\n.modeButton {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  margin: 0;\n  outline: 0;\n  border: 0;\n  border-inline-end: 1px solid var(--color-gray-300);\n  background: var(--color-gray-50);\n  color: var(--color-gray-900);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 500;\n  line-height: 1.5rem;\n  padding: 0 0.875rem;\n  user-select: none;\n}\n\n.modeButton:last-child {\n  border-inline-end: 0;\n}\n\n.modeButton[data-active='true'] {\n  background: var(--color-gray-900);\n  color: var(--color-gray-50);\n}\n\n.numberInput {\n  border: 1px solid var(--color-gray-300);\n  border-radius: 0.375rem;\n  padding: 0.35rem 0.5rem;\n  max-width: 7rem;\n}\n\n.stats {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 0.75rem 1rem;\n  font-size: 0.9rem;\n}\n\n.stat {\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  padding: 0.45rem 0.6rem;\n  background: var(--color-gray-50);\n}\n\n.statusValue {\n  display: inline-block;\n  min-width: 6ch;\n}\n\n.canvas {\n  display: grid;\n  grid-template-columns: 1fr minmax(360px, 42%);\n  gap: 1rem;\n  min-height: 70vh;\n}\n\n.outside {\n  position: relative;\n  min-height: 60vh;\n  max-height: 70vh;\n  overflow: auto;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.5rem;\n  padding: 0.5rem;\n}\n\n.outsideSurface {\n  position: relative;\n  min-height: 1650px;\n}\n\n.hitTestMesh {\n  position: absolute;\n  inset: 0;\n  z-index: 2;\n}\n\n.hitTestNode {\n  position: absolute;\n  left: var(--mesh-left);\n  top: var(--mesh-top);\n  width: var(--mesh-width);\n  height: var(--mesh-height);\n  transform: translate(-50%, -50%) rotate(var(--mesh-rotate));\n  border: 1px solid var(--color-gray-300);\n  border-radius: 999px;\n  background: rgb(17 24 39 / 4%);\n  margin: 0;\n  padding: 0;\n}\n\n.hitTestNodeInner {\n  display: grid;\n  place-items: center;\n  width: 100%;\n  height: 100%;\n  border-radius: inherit;\n  outline: 1px solid rgb(17 24 39 / 10%);\n}\n\n.hitTestNodeCore {\n  width: 40%;\n  height: 40%;\n  border-radius: 999px;\n  background: rgb(17 24 39 / 15%);\n}\n\n.outsideGrid {\n  position: relative;\n  z-index: 1;\n  display: grid;\n  grid-template-columns: repeat(4, minmax(0, 1fr));\n  gap: 0.35rem;\n}\n\n.tile {\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.25rem;\n  padding: 0.25rem 0.35rem;\n  font-size: 0.75rem;\n  color: var(--color-gray-700);\n  background: white;\n}\n\n.menuWrap {\n  position: relative;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.5rem;\n  min-height: 60vh;\n  max-height: 70vh;\n  overflow: hidden;\n  background: white;\n}\n\n.menuHeader {\n  padding: 0.65rem 0.8rem;\n  border-bottom: 1px solid var(--color-gray-200);\n  background: var(--color-gray-50);\n  font-size: 0.9rem;\n}\n\n.menuBody {\n  padding: 0.75rem;\n}\n\n.rootTrigger {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 500;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n}\n\n@media (hover: hover) {\n  .rootTrigger:hover:not([data-disabled]) {\n    background-color: var(--color-gray-100);\n  }\n}\n\n.rootTrigger:active:not([data-disabled]) {\n  background-color: var(--color-gray-200);\n  box-shadow: inset 0 1px 3px var(--color-gray-200);\n  border-top-color: var(--color-gray-300);\n}\n\n.rootTrigger:focus-visible {\n  outline: 2px solid var(--color-blue);\n  outline-offset: -1px;\n}\n\n.rootTrigger[data-disabled] {\n  color: var(--color-gray-500);\n}\n\n.positioner {\n  z-index: 2;\n}\n\n.menuRow {\n  border-radius: 0.35rem;\n  padding: 0.45rem 0.55rem;\n  cursor: default;\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  font-size: 0.875rem;\n  user-select: none;\n}\n\n.menuRow[data-highlighted],\n.menuRow[data-popup-open] {\n  background: var(--color-gray-900);\n  color: var(--color-gray-50);\n}\n\n.popup {\n  width: min(360px, 80vw);\n  max-height: calc(70vh - 6rem);\n  overflow: auto;\n  border: 1px solid var(--color-gray-300);\n  border-radius: 0.375rem;\n  background: white;\n  box-shadow:\n    0 10px 15px -3px rgb(0 0 0 / 18%),\n    0 4px 6px -4px rgb(0 0 0 / 15%);\n}\n\n.submenuPopup {\n  width: min(240px, 60vw);\n  max-height: calc(70vh - 3.4rem);\n  overflow: auto;\n  border: 1px solid var(--color-gray-300);\n  border-radius: 0.375rem;\n  background: white;\n  box-shadow:\n    0 10px 15px -3px rgb(0 0 0 / 18%),\n    0 4px 6px -4px rgb(0 0 0 / 15%);\n}\n\n.popupTitle {\n  font-size: 0.8rem;\n  color: var(--color-gray-700);\n  padding: 0.45rem 0.6rem;\n  border-bottom: 1px solid var(--color-gray-200);\n  background: var(--color-gray-50);\n}\n\n.popupList {\n  list-style: none;\n  margin: 0;\n  padding: 0.3rem;\n}\n\n.popupItem {\n  border-radius: 0.3rem;\n  padding: 0.35rem 0.45rem;\n  font-size: 0.8rem;\n}\n\n.popupItem:hover {\n  background: var(--color-gray-100);\n}\n\n@media (max-width: 980px) {\n  .canvas {\n    grid-template-columns: 1fr;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/menu/pointer-events-scope.tsx",
    "content": "'use client';\n\nimport * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\nimport styles from './pointer-events-scope.module.css';\n\nconst menuItemCount = 240;\nconst popupItemCount = 60;\nconst nestedPopupItemCount = 12;\nconst outsideTileCount = 14000;\nconst outsideMeshNodeCount = 6000;\nconst outsideMeshColumns = 30;\n\nconst menuItems = Array.from({ length: menuItemCount }, (_, index) => ({\n  id: index,\n  label: `Submenu trigger ${index + 1}`,\n}));\n\nconst popupItems = Array.from({ length: popupItemCount }, (_, index) => ({\n  id: index,\n  label: `Item ${index + 1}`,\n}));\n\nconst nestedPopupItems = Array.from({ length: nestedPopupItemCount }, (_, index) => ({\n  id: index,\n  label: `Leaf item ${index + 1}`,\n}));\n\nconst outsideTiles = Array.from({ length: outsideTileCount }, (_, index) => ({\n  id: index,\n  label: `Node ${index + 1}`,\n}));\n\nconst outsideMeshNodes = Array.from({ length: outsideMeshNodeCount }, (_, index) => {\n  const row = Math.floor(index / outsideMeshColumns);\n  const col = index % outsideMeshColumns;\n  const rowCount = Math.ceil(outsideMeshNodeCount / outsideMeshColumns);\n\n  const jitterX = (index * 17) % 9;\n  const jitterY = (index * 29) % 11;\n\n  return {\n    id: index,\n    left: (col / (outsideMeshColumns - 1)) * 92 + 2 + jitterX * 0.35,\n    top: (row / (rowCount - 1)) * 96 + 2 + jitterY * 0.2,\n    width: 10 + ((index * 13) % 28),\n    height: 8 + ((index * 7) % 18),\n    rotate: ((index * 11) % 32) - 16,\n  };\n});\n\nexport default function PointerEventsScopeExperiment() {\n  const [menuOpen, setMenuOpen] = React.useState(false);\n  const [modal, setModal] = React.useState(true);\n  const [submenuItemsAreSubmenuTriggers, setSubmenuItemsAreSubmenuTriggers] = React.useState(false);\n  const [mainMenuEntryCount, setMainMenuEntryCount] = React.useState(menuItemCount);\n\n  const mainMenuEntries = Math.max(1, Math.min(menuItemCount, Math.trunc(mainMenuEntryCount) || 1));\n  const visibleMenuItems = menuItems.slice(0, mainMenuEntries);\n\n  return (\n    <div className={styles.root}>\n      <h1>Pointer events scope benchmark (real Menu)</h1>\n\n      <div className={styles.controls}>\n        <div className={styles.field}>\n          <span className={styles.fieldLabel}>Submenu items</span>\n          <div className={styles.modeGroup}>\n            <button\n              className={styles.modeButton}\n              data-active={!submenuItemsAreSubmenuTriggers}\n              type=\"button\"\n              onClick={() => setSubmenuItemsAreSubmenuTriggers(false)}\n            >\n              items\n            </button>\n            <button\n              className={styles.modeButton}\n              data-active={submenuItemsAreSubmenuTriggers}\n              type=\"button\"\n              onClick={() => setSubmenuItemsAreSubmenuTriggers(true)}\n            >\n              triggers\n            </button>\n          </div>\n        </div>\n\n        <div className={styles.field}>\n          <span className={styles.fieldLabel}>Modal</span>\n          <div className={styles.modeGroup}>\n            <button\n              className={styles.modeButton}\n              data-active={modal}\n              type=\"button\"\n              onClick={() => setModal(true)}\n            >\n              true\n            </button>\n            <button\n              className={styles.modeButton}\n              data-active={!modal}\n              type=\"button\"\n              onClick={() => setModal(false)}\n            >\n              false\n            </button>\n          </div>\n        </div>\n\n        <label className={styles.field}>\n          <span className={styles.fieldLabel}>Main menu entries</span>\n          <input\n            className={styles.numberInput}\n            max={menuItemCount}\n            min={1}\n            step={1}\n            type=\"number\"\n            value={mainMenuEntryCount}\n            onChange={(event) => {\n              const next = Number(event.target.value);\n              setMainMenuEntryCount(Number.isFinite(next) ? next : 1);\n            }}\n          />\n        </label>\n      </div>\n\n      <div className={styles.stats}>\n        <div className={styles.stat}>Scope mode: scoped</div>\n        <div className={styles.stat}>Outside nodes: {outsideTileCount + outsideMeshNodeCount}</div>\n        <div className={styles.stat}>Main menu entries: {mainMenuEntries}</div>\n        <div className={styles.stat}>\n          Submenu items as triggers: {submenuItemsAreSubmenuTriggers ? 'yes' : 'no'}\n        </div>\n        <div className={styles.stat}>Modal: {modal ? 'true' : 'false'}</div>\n        <div className={styles.stat}>\n          Root menu: <span className={styles.statusValue}>{menuOpen ? 'open' : 'closed'}</span>\n        </div>\n      </div>\n\n      <div className={styles.canvas}>\n        <section className={styles.menuWrap}>\n          <div className={styles.menuHeader}>Menu region (real Menu primitives)</div>\n          <div className={styles.menuBody}>\n            <Menu.Root modal={modal} open={menuOpen} onOpenChange={setMenuOpen}>\n              <Menu.Trigger className={styles.rootTrigger}>Open Menu</Menu.Trigger>\n\n              <Menu.Portal keepMounted>\n                <Menu.Positioner sideOffset={6} className={styles.positioner}>\n                  <Menu.Popup className={styles.popup}>\n                    {visibleMenuItems.map((menuItem) => (\n                      <Menu.SubmenuRoot key={menuItem.id}>\n                        <Menu.SubmenuTrigger\n                          className={styles.menuRow}\n                          data-bench-submenu-trigger\n                          delay={0}\n                        >\n                          <span>{menuItem.label}</span>\n                          <span>▶</span>\n                        </Menu.SubmenuTrigger>\n                        <Menu.Portal>\n                          <Menu.Positioner\n                            className={styles.positioner}\n                            alignOffset={-4}\n                            sideOffset={-4}\n                          >\n                            <Menu.Popup className={styles.submenuPopup}>\n                              <div className={styles.popupTitle}>Nested items</div>\n                              <div className={styles.popupList}>\n                                {submenuItemsAreSubmenuTriggers\n                                  ? popupItems.map((popupItem) => (\n                                      <Menu.SubmenuRoot key={popupItem.id}>\n                                        <Menu.SubmenuTrigger className={styles.popupItem} delay={0}>\n                                          <span>{popupItem.label}</span>\n                                          <span>▶</span>\n                                        </Menu.SubmenuTrigger>\n                                        <Menu.Portal>\n                                          <Menu.Positioner\n                                            className={styles.positioner}\n                                            alignOffset={-4}\n                                            sideOffset={-4}\n                                          >\n                                            <Menu.Popup className={styles.submenuPopup}>\n                                              <div className={styles.popupTitle}>Leaf items</div>\n                                              <div className={styles.popupList}>\n                                                {nestedPopupItems.map((nestedPopupItem) => (\n                                                  <Menu.Item\n                                                    key={`${popupItem.id}-${nestedPopupItem.id}`}\n                                                    className={styles.popupItem}\n                                                  >\n                                                    {nestedPopupItem.label}\n                                                  </Menu.Item>\n                                                ))}\n                                              </div>\n                                            </Menu.Popup>\n                                          </Menu.Positioner>\n                                        </Menu.Portal>\n                                      </Menu.SubmenuRoot>\n                                    ))\n                                  : popupItems.map((popupItem) => (\n                                      <Menu.Item key={popupItem.id} className={styles.popupItem}>\n                                        {popupItem.label}\n                                      </Menu.Item>\n                                    ))}\n                              </div>\n                            </Menu.Popup>\n                          </Menu.Positioner>\n                        </Menu.Portal>\n                      </Menu.SubmenuRoot>\n                    ))}\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            </Menu.Root>\n          </div>\n        </section>\n\n        <section className={styles.outside}>\n          <h2>Outside document area (large DOM + hit-test mesh)</h2>\n          <div className={styles.outsideSurface}>\n            <div className={styles.hitTestMesh} aria-hidden>\n              {outsideMeshNodes.map((meshNode) => (\n                <div\n                  key={`mesh-${meshNode.id}`}\n                  className={styles.hitTestNode}\n                  data-bench-outside-node\n                  style={\n                    {\n                      '--mesh-height': `${meshNode.height}px`,\n                      '--mesh-left': `${meshNode.left}%`,\n                      '--mesh-rotate': `${meshNode.rotate}deg`,\n                      '--mesh-top': `${meshNode.top}%`,\n                      '--mesh-width': `${meshNode.width}px`,\n                    } as React.CSSProperties\n                  }\n                >\n                  <span className={styles.hitTestNodeInner}>\n                    <span className={styles.hitTestNodeCore} />\n                  </span>\n                </div>\n              ))}\n            </div>\n            <div className={styles.outsideGrid}>\n              {outsideTiles.map((tile) => (\n                <div key={tile.id} className={styles.tile} data-bench-outside-node>\n                  {tile.label}\n                </div>\n              ))}\n            </div>\n          </div>\n        </section>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/menu/triggers.module.css",
    "content": ".Page {\n  display: flex;\n  flex-direction: column;\n  padding-bottom: 30vh;\n\n  h2 {\n    margin-bottom: 1rem;\n  }\n}\n\n.Container {\n  display: flex;\n  flex-direction: row;\n  align-items: center;\n  gap: 0;\n  margin-bottom: 32px;\n}\n\n.Trigger {\n  border-radius: 0;\n}\n\n.ActionButton {\n  border-radius: 0;\n}\n\n.Trigger,\n.ActionButton {\n  &:first-of-type {\n    border-radius: 0.375rem 0 0 0.375rem;\n  }\n\n  &:last-of-type {\n    border-radius: 0 0.375rem 0.375rem 0;\n    border-right-style: solid;\n  }\n}\n\n.MenuSection {\n  margin-top: 0.5rem;\n  margin-bottom: 0.5rem;\n\n  &:not(:last-child) {\n    padding-bottom: 0.5rem;\n    border-bottom: 1px solid var(--color-gray-200);\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/menu/triggers.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\nimport { StoreInspector } from '@base-ui/utils/store';\nimport demoStyles from 'docs/src/app/(docs)/react/components/menu/demos/submenu/css-modules/index.module.css';\nimport viewportStyles from 'docs/src/app/(docs)/react/components/menu/demos/detached-triggers-full/css-modules/index.module.css';\nimport { SettingsMetadata, useExperimentSettings } from '../_components/SettingsPanel';\nimport styles from './triggers.module.css';\n\ninterface Settings {\n  openOnHover: boolean;\n  delay: number;\n  closeDelay: number;\n  side: Menu.Positioner.Props['side'];\n  modal: boolean;\n  keepMounted: boolean;\n}\n\ntype MenuContentRegularItem = {\n  type: 'item';\n  label: string;\n  disabled?: boolean;\n  closeOnClick?: boolean;\n  onClick?: () => void;\n};\n\ntype MenuContentSeparator = { type: 'separator' };\n\ntype MenuContentSubmenu = {\n  type: 'submenu';\n  label: string;\n  menu: MenuContentItem[];\n};\n\ntype MenuContentItem = MenuContentRegularItem | MenuContentSeparator | MenuContentSubmenu;\n\nconst contents = {\n  Library: [\n    { type: 'item', label: 'Add to Library' },\n    {\n      type: 'submenu',\n      label: 'Add to Playlist',\n      menu: [\n        { type: 'item', label: 'Favorites' },\n        { type: 'item', label: 'Chill Vibes' },\n        { type: 'item', label: 'Workout Mix' },\n      ],\n    },\n    { type: 'separator' },\n    { type: 'item', label: \"Favorite (don't close on click)\", closeOnClick: false },\n    {\n      type: 'submenu',\n      label: 'Share',\n      menu: [\n        { type: 'item', label: 'Copy Link' },\n        {\n          type: 'submenu',\n          label: 'Social Media',\n          menu: [\n            { type: 'item', label: 'Facebook' },\n            { type: 'item', label: 'Twitter' },\n            { type: 'item', label: 'Instagram' },\n          ],\n        },\n      ],\n    },\n  ] as MenuContentItem[],\n  Playback: [\n    { type: 'item', label: 'Play Next' },\n    { type: 'item', label: 'Play Last' },\n    { type: 'separator' },\n    { type: 'item', label: 'Play on Device' },\n    { type: 'item', label: 'Download for Offline' },\n  ] as MenuContentItem[],\n  Sharing: [\n    { type: 'item', label: 'Start Radio' },\n    { type: 'item', label: 'Create Station' },\n    { type: 'separator' },\n    { type: 'item', label: 'Follow Artist' },\n    { type: 'item', label: 'View Artist' },\n  ] as MenuContentItem[],\n};\n\ntype ContentKey = keyof typeof contents;\n\nconst menu1 = Menu.createHandle<ContentKey>();\nconst menu2 = Menu.createHandle<ContentKey>();\n\nexport default function MenuTriggers() {\n  const { settings } = useExperimentSettings<Settings>();\n\n  const [singleTriggerOpen, setSingleTriggerOpen] = React.useState(false);\n\n  const [controlledWithinRootOpen, setControlledWithinRootOpen] = React.useState(false);\n  const [controlledWithinRootTriggerId, setControlledWithinRootTriggerId] = React.useState<\n    string | null\n  >(null);\n\n  const [controlledDetachedOpen, setControlledDetachedOpen] = React.useState(false);\n  const [controlledDetachedTriggerId, setControlledDetachedTriggerId] = React.useState<\n    string | null\n  >(null);\n\n  return (\n    <div className={styles.Page}>\n      <h1>Menus</h1>\n\n      <h2>Uncontrolled, single trigger</h2>\n      <div className={styles.Container}>\n        <Menu.Root modal={settings.modal}>\n          <StyledTrigger>Library</StyledTrigger>\n          {renderMenuContent('Library', settings)}\n        </Menu.Root>\n        <Menu.Root modal={settings.modal}>\n          <StyledTrigger>Playback</StyledTrigger>\n          {renderMenuContent('Playback', settings)}\n        </Menu.Root>\n        <Menu.Root modal={settings.modal}>\n          <StyledTrigger>Sharing</StyledTrigger>\n          {renderMenuContent('Sharing', settings)}\n        </Menu.Root>\n      </div>\n\n      <h2>Controlled, single trigger</h2>\n      <div className={styles.Container}>\n        <Menu.Root\n          modal={settings.modal}\n          open={singleTriggerOpen}\n          onOpenChange={(nextOpen) => setSingleTriggerOpen(nextOpen)}\n        >\n          <StyledTrigger>Library</StyledTrigger>\n          {renderMenuContent('Library', settings)}\n        </Menu.Root>\n        <button\n          type=\"button\"\n          className={`${demoStyles.Button} ${styles.ActionButton}`}\n          onClick={() => setSingleTriggerOpen(true)}\n        >\n          Open externally\n        </button>\n      </div>\n\n      <h2>Uncontrolled, multiple triggers within Root</h2>\n      <div className={styles.Container}>\n        <Menu.Root modal={settings.modal}>\n          {({ payload }) => (\n            <React.Fragment>\n              <StyledTrigger payload={'Library'} />\n              <StyledTrigger payload={'Playback'} />\n              <StyledTrigger payload={'Sharing'} />\n              {renderMenuContent(payload as ContentKey, settings)}\n            </React.Fragment>\n          )}\n        </Menu.Root>\n      </div>\n\n      <h2>Controlled, multiple triggers within Root</h2>\n      <div className={styles.Container}>\n        <Menu.Root\n          modal={settings.modal}\n          open={controlledWithinRootOpen}\n          onOpenChange={(open, eventDetails) => {\n            setControlledWithinRootOpen(open);\n            setControlledWithinRootTriggerId(eventDetails.trigger?.id ?? null);\n          }}\n          triggerId={controlledWithinRootTriggerId}\n        >\n          {({ payload }) => (\n            <React.Fragment>\n              <StyledTrigger payload={'Library'} />\n              <StyledTrigger payload={'Playback'} id=\"within-root-second-trigger\" />\n              <StyledTrigger payload={'Sharing'} />\n              {renderMenuContent(payload as ContentKey, settings)}\n            </React.Fragment>\n          )}\n        </Menu.Root>\n        <button\n          type=\"button\"\n          className={`${demoStyles.Button} ${styles.ActionButton}`}\n          onClick={() => {\n            setControlledWithinRootOpen(true);\n            setControlledWithinRootTriggerId('within-root-second-trigger');\n          }}\n        >\n          Open externally (2nd trigger)\n        </button>\n      </div>\n\n      <h2>Uncontrolled, detached triggers</h2>\n      <StoreInspector store={menu1.store} title=\"Uncontrolled, detached triggers\" />\n      <div className={styles.Container}>\n        <StyledMenu handle={menu1} />\n        <StyledTrigger handle={menu1} payload={'Library' as const} />\n        <StyledTrigger handle={menu1} payload={'Playback' as const} />\n        <StyledTrigger handle={menu1} payload={'Sharing' as const} />\n      </div>\n\n      <h2>Controlled, detached triggers</h2>\n      <StoreInspector store={menu2.store} title=\"Controlled, detached triggers\" />\n      <div className={styles.Container}>\n        <StyledMenu\n          handle={menu2}\n          open={controlledDetachedOpen}\n          triggerId={controlledDetachedTriggerId}\n          onOpenChange={(open, eventDetails) => {\n            setControlledDetachedOpen(open);\n            setControlledDetachedTriggerId(eventDetails.trigger?.id ?? null);\n          }}\n        />\n        <StyledTrigger handle={menu2} payload={'Library' as const} />\n        <StyledTrigger handle={menu2} payload={'Playback' as const} id=\"detached-second-trigger\" />\n        <StyledTrigger handle={menu2} payload={'Sharing' as const} />\n        <button\n          type=\"button\"\n          className={`${demoStyles.Button} ${styles.ActionButton}`}\n          onClick={() => {\n            setControlledDetachedOpen(true);\n            setControlledDetachedTriggerId('detached-second-trigger');\n          }}\n        >\n          Open externally (2nd trigger)\n        </button>\n        <button\n          type=\"button\"\n          className={`${demoStyles.Button} ${styles.ActionButton}`}\n          onClick={() => {\n            menu2.open('detached-second-trigger');\n          }}\n        >\n          Open via handle (2nd trigger)\n        </button>\n      </div>\n    </div>\n  );\n}\n\ntype StyledMenuProps<Payload> = Pick<\n  Menu.Root.Props<Payload>,\n  'handle' | 'open' | 'onOpenChange' | 'triggerId'\n>;\n\nfunction StyledMenu(props: StyledMenuProps<ContentKey>) {\n  const { handle, open, onOpenChange, triggerId } = props;\n  const { settings } = useExperimentSettings<Settings>();\n\n  return (\n    <Menu.Root\n      handle={handle}\n      open={open}\n      onOpenChange={onOpenChange}\n      triggerId={triggerId}\n      modal={settings.modal}\n    >\n      {({ payload }) => renderMenuContent(payload as ContentKey, settings)}\n    </Menu.Root>\n  );\n}\n\ntype MenuTriggerPropsWithFeatures<Payload> = Menu.Trigger.Props<Payload> & {\n  handle?: Menu.Handle<Payload>;\n  payload?: Payload;\n};\n\nfunction StyledTrigger(\n  props: MenuTriggerPropsWithFeatures<ContentKey> & React.RefAttributes<HTMLButtonElement>,\n) {\n  const { settings } = useExperimentSettings<Settings>();\n  const { className, children, payload, ...restProps } = props;\n\n  const triggerProps: Menu.Trigger.Props<ContentKey> = {\n    ...restProps,\n    className: [demoStyles.Button, styles.Trigger, className].filter(Boolean).join(' '),\n    openOnHover: settings.openOnHover,\n    delay: settings.delay,\n    closeDelay: settings.closeDelay,\n    payload,\n  };\n\n  const content = payload ?? children;\n\n  return (\n    <Menu.Trigger {...triggerProps}>\n      {content}\n      <ChevronDownIcon className={demoStyles.ButtonIcon} />\n    </Menu.Trigger>\n  );\n}\n\nfunction renderMenuContent(contentKey: ContentKey | undefined, settings: Settings) {\n  const items = contentKey ? contents[contentKey] : undefined;\n\n  return (\n    <Menu.Portal keepMounted={settings.keepMounted}>\n      <Menu.Positioner\n        sideOffset={8}\n        className={`${demoStyles.Positioner} ${viewportStyles.Positioner}`}\n        side={settings.side}\n      >\n        <Menu.Popup className={`${demoStyles.Popup} ${viewportStyles.Popup}`}>\n          <Menu.Arrow className={`${demoStyles.Arrow} ${viewportStyles.Arrow}`}>\n            <ArrowSvg />\n          </Menu.Arrow>\n          <Menu.Viewport className={viewportStyles.Viewport}>\n            {items ? (\n              items.map((item, index) => renderMenuContentItem(item, `item-${index}`))\n            ) : (\n              <div className={styles.MenuSection}>No content for this trigger.</div>\n            )}\n          </Menu.Viewport>\n        </Menu.Popup>\n      </Menu.Positioner>\n    </Menu.Portal>\n  );\n}\n\nfunction renderMenuContentItem(item: MenuContentItem, key: string) {\n  switch (item.type) {\n    case 'item':\n      return (\n        <Menu.Item\n          className={demoStyles.Item}\n          disabled={item.disabled}\n          closeOnClick={item.closeOnClick}\n          onClick={item.onClick ?? (() => console.log('Clicked on', item.label))}\n          key={key}\n        >\n          {item.label}\n        </Menu.Item>\n      );\n    case 'separator':\n      return <Menu.Separator className={demoStyles.Separator} key={key} />;\n    case 'submenu':\n      return (\n        <Menu.SubmenuRoot key={key}>\n          <Menu.SubmenuTrigger className={demoStyles.SubmenuTrigger}>\n            {item.label}\n            <ChevronRightIcon />\n          </Menu.SubmenuTrigger>\n          <Menu.Portal>\n            <Menu.Positioner className={demoStyles.Positioner} alignOffset={-4} sideOffset={-4}>\n              <Menu.Popup className={demoStyles.Popup}>\n                {item.menu.map((subItem, subIndex) =>\n                  renderMenuContentItem(subItem, `${key}.${subIndex}`),\n                )}\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.SubmenuRoot>\n      );\n    default:\n      return null;\n  }\n}\n\nexport const settingsMetadata: SettingsMetadata<Settings> = {\n  openOnHover: {\n    type: 'boolean',\n    label: 'Open on hover',\n  },\n  delay: {\n    type: 'number',\n    label: 'Delay',\n    default: 100,\n  },\n  closeDelay: {\n    type: 'number',\n    label: 'Close Delay',\n    default: 0,\n  },\n  side: {\n    type: 'string',\n    label: 'Side',\n    options: ['top', 'bottom', 'inline-start', 'inline-end'],\n    default: 'bottom',\n  },\n  modal: {\n    type: 'boolean',\n    label: 'Modal',\n    default: false,\n  },\n  keepMounted: {\n    type: 'boolean',\n    label: 'Keep mounted',\n    default: false,\n  },\n};\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={demoStyles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={demoStyles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={demoStyles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nfunction ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M1 3.5L5 7.5L9 3.5\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n\nfunction ChevronRightIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M3.5 9L7.5 5L3.5 1\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/menubar.module.css",
    "content": ".Root {\n  display: flex;\n  gap: 1px;\n  background-color: var(--color-gray-50);\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  padding: 0.125rem;\n  flex-direction: row;\n  justify-content: space-between;\n  align-items: center;\n\n  &:focus-visible {\n    outline: none;\n  }\n\n  &[data-orientation='vertical'] {\n    flex-direction: column;\n    align-items: stretch;\n  }\n}\n\n.Item {\n  box-sizing: border-box;\n  background: none;\n  padding: 0 0.75rem;\n  margin: 0;\n  outline: 0;\n  border: 0;\n  color: var(--color-gray-600);\n  border-radius: 0.25rem;\n  user-select: none;\n  height: 2rem;\n  font-family: inherit;\n  font-size: 0.875rem;\n  font-weight: 500;\n\n  &:focus-visible {\n    outline: none;\n  }\n\n  &:active {\n    background-color: var(--color-gray-200);\n  }\n\n  &[data-pressed],\n  &:focus {\n    background-color: var(--color-gray-100);\n  }\n\n  &[data-disabled] {\n    opacity: 0.5;\n  }\n}\n\n.Separator {\n  color: var(--color-gray-200);\n  margin-block: 2rem;\n}\n\n.Input {\n  border: 1px solid var(--color-gray-200);\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/menubar.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\nimport { Menubar } from '@base-ui/react/menubar';\nimport { SettingsMetadata, useExperimentSettings } from './_components/SettingsPanel';\nimport '../../../demo-data/theme/css-modules/theme.css';\nimport menuClasses from './menu/menu.module.css';\nimport classes from './menubar.module.css';\n\ninterface Settings {\n  modal: boolean;\n  loopFocus: boolean;\n  orientation: 'horizontal' | 'vertical';\n}\n\nfunction getSubmenuPositionProps(parentOrientation: Menu.Root.Props['orientation']) {\n  return {\n    side: parentOrientation === 'horizontal' ? 'bottom' : 'right',\n    align: 'start',\n    sideOffset: 6,\n  } as const;\n}\n\nexport default function MenubarExperiment() {\n  const { settings } = useExperimentSettings<Settings>();\n\n  return (\n    <div style={{ isolation: 'isolate' }}>\n      <h1>Menubar</h1>\n      <Menubar\n        className={classes.Root}\n        loopFocus={settings.loopFocus}\n        orientation={settings.orientation}\n        modal={settings.modal}\n      >\n        <Menu.Root>\n          <Menu.Trigger className={classes.Item}>File</Menu.Trigger>\n\n          <Menu.Portal>\n            <Menu.Positioner\n              className={menuClasses.Positioner}\n              {...getSubmenuPositionProps(settings.orientation)}\n            >\n              <Menu.Popup className={menuClasses.Popup}>\n                <Menu.Item className={menuClasses.Item}>Open...</Menu.Item>\n                <Menu.Item className={menuClasses.Item}>Save</Menu.Item>\n                <Menu.Item className={menuClasses.Item}>Save as...</Menu.Item>\n                <Menu.Separator className={menuClasses.Separator} />\n\n                <Menu.SubmenuRoot>\n                  <Menu.SubmenuTrigger className={menuClasses.SubmenuTrigger}>\n                    Share\n                    <ChevronRightIcon />\n                  </Menu.SubmenuTrigger>\n\n                  <Menu.Portal>\n                    <Menu.Positioner className={menuClasses.Positioner} sideOffset={8}>\n                      <Menu.Popup className={menuClasses.Popup}>\n                        <Menu.Arrow className={menuClasses.Arrow}>\n                          <ArrowIcon />\n                        </Menu.Arrow>\n                        <Menu.Item className={menuClasses.Item}>AirDrop</Menu.Item>\n                        <Menu.Item className={menuClasses.Item}>Email link</Menu.Item>\n                      </Menu.Popup>\n                    </Menu.Positioner>\n                  </Menu.Portal>\n                </Menu.SubmenuRoot>\n\n                <Menu.Separator className={menuClasses.Separator} />\n                <Menu.Item className={menuClasses.Item}>Close</Menu.Item>\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>\n\n        <Menu.Root>\n          <Menu.Trigger className={classes.Item}>Edit</Menu.Trigger>\n\n          <Menu.Portal>\n            <Menu.Positioner\n              className={menuClasses.Positioner}\n              {...getSubmenuPositionProps(settings.orientation)}\n            >\n              <Menu.Popup className={menuClasses.Popup}>\n                <Menu.Item className={menuClasses.Item}>Cut</Menu.Item>\n                <Menu.Item className={menuClasses.Item}>Copy</Menu.Item>\n                <Menu.Item className={menuClasses.Item}>Paste</Menu.Item>\n\n                <Menu.Separator className={menuClasses.Separator} />\n\n                <Menu.SubmenuRoot>\n                  <Menu.SubmenuTrigger className={menuClasses.SubmenuTrigger}>\n                    Find on page\n                    <ChevronRightIcon />\n                  </Menu.SubmenuTrigger>\n\n                  <Menu.Portal>\n                    <Menu.Positioner className={menuClasses.Positioner} sideOffset={8}>\n                      <Menu.Popup className={menuClasses.Popup}>\n                        <Menu.Item className={menuClasses.Item}>Find...</Menu.Item>\n                        <Menu.Item className={menuClasses.Item}>Find next</Menu.Item>\n                      </Menu.Popup>\n                    </Menu.Positioner>\n                  </Menu.Portal>\n                </Menu.SubmenuRoot>\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>\n\n        <Menu.Root>\n          <Menu.Trigger className={classes.Item}>View</Menu.Trigger>\n\n          <Menu.Portal>\n            <Menu.Positioner\n              className={menuClasses.Positioner}\n              {...getSubmenuPositionProps(settings.orientation)}\n            >\n              <Menu.Popup className={menuClasses.Popup}>\n                <Menu.RadioGroup value=\"light\">\n                  <Menu.RadioItem className={menuClasses.RadioItem} value=\"light\">\n                    <Menu.RadioItemIndicator className={menuClasses.RadioItemIndicator}>\n                      <CheckIcon className={menuClasses.RadioItemIndicatorIcon} />\n                    </Menu.RadioItemIndicator>\n                    <span className={menuClasses.RadioItemText}>Light mode</span>\n                  </Menu.RadioItem>\n                  <Menu.RadioItem className={menuClasses.RadioItem} value=\"dark\">\n                    <Menu.RadioItemIndicator className={menuClasses.RadioItemIndicator}>\n                      <CheckIcon className={menuClasses.RadioItemIndicatorIcon} />\n                    </Menu.RadioItemIndicator>\n                    <span className={menuClasses.RadioItemText}>Dark mode</span>\n                  </Menu.RadioItem>\n                </Menu.RadioGroup>\n\n                <Menu.SubmenuRoot>\n                  <Menu.SubmenuTrigger className={menuClasses.SubmenuTrigger}>\n                    Layout\n                    <ChevronRightIcon />\n                  </Menu.SubmenuTrigger>\n\n                  <Menu.Portal>\n                    <Menu.Positioner className={menuClasses.Positioner} sideOffset={8}>\n                      <Menu.Popup className={menuClasses.Popup}>\n                        <Menu.RadioGroup defaultValue=\"light\">\n                          <Menu.RadioItem className={menuClasses.RadioItem} value=\"light\">\n                            <Menu.RadioItemIndicator className={menuClasses.RadioItemIndicator}>\n                              <CheckIcon className={menuClasses.RadioItemIndicatorIcon} />\n                            </Menu.RadioItemIndicator>\n                            <span className={menuClasses.RadioItemText}>Single column</span>\n                          </Menu.RadioItem>\n                          <Menu.RadioItem className={menuClasses.RadioItem} value=\"dark\">\n                            <Menu.RadioItemIndicator className={menuClasses.RadioItemIndicator}>\n                              <CheckIcon className={menuClasses.RadioItemIndicatorIcon} />\n                            </Menu.RadioItemIndicator>\n                            <span className={menuClasses.RadioItemText}>Multiple columns</span>\n                          </Menu.RadioItem>\n                        </Menu.RadioGroup>\n                      </Menu.Popup>\n                    </Menu.Positioner>\n                  </Menu.Portal>\n                </Menu.SubmenuRoot>\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>\n\n        <Menu.Root disabled>\n          <Menu.Trigger className={classes.Item}>Develop</Menu.Trigger>\n          <Menu.Portal>\n            <Menu.Positioner\n              className={menuClasses.Positioner}\n              {...getSubmenuPositionProps(settings.orientation)}\n            >\n              <Menu.Popup className={menuClasses.Popup}>\n                <Menu.Item className={menuClasses.Item}>This should not appear</Menu.Item>\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>\n      </Menubar>\n      <hr className={classes.Separator} />\n      <input className={classes.Input} placeholder=\"focus tester\" />\n    </div>\n  );\n}\n\nexport const settingsMetadata: SettingsMetadata<Settings> = {\n  loopFocus: {\n    type: 'boolean',\n    label: 'Focus loop',\n    default: true,\n  },\n  modal: {\n    type: 'boolean',\n    label: 'Modal',\n    default: true,\n  },\n  orientation: {\n    type: 'string',\n    label: 'Orientation',\n    options: ['horizontal', 'vertical'],\n    default: 'horizontal',\n  },\n};\n\nfunction ChevronRightIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M3.5 9L7.5 5L3.5 1\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n\nfunction ArrowIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={menuClasses.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={menuClasses.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={menuClasses.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/meter.module.css",
    "content": ".Root {\n  --icon-size: 44px;\n  display: flex;\n  margin-top: 50dvh;\n}\n\n.Track {\n  position: relative;\n  width: 40px;\n  height: 26px;\n  border-radius: 5px;\n  border: 3px solid currentColor;\n  padding: 2px;\n  display: flex;\n\n  @media (prefers-color-scheme: dark) {\n    border-width: 1px;\n    padding: 3px;\n  }\n}\n\n.Track::after {\n  content: '';\n  background-color: currentColor;\n  position: absolute;\n  z-index: 1;\n  top: 3px;\n  right: -6px;\n  width: 3px;\n  height: 14px;\n  border-radius: 0 6px 6px 0;\n\n  @media (prefers-color-scheme: dark) {\n    top: 4px;\n    right: -4px;\n    height: 15px;\n    border-radius: 0 8px 7px 0;\n  }\n}\n\n.Icon {\n  position: absolute;\n  width: var(--icon-size);\n  height: var(--icon-size);\n  transform: translate(-2px, -20%);\n}\n\n.Indicator {\n  background-color: rgb(40 205 65);\n  border-radius: 3px;\n  height: revert-layer !important;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/meter.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Meter } from '@base-ui/react/meter';\nimport { SettingsMetadata, useExperimentSettings } from './_components/SettingsPanel';\nimport styles from './meter.module.css';\n\ninterface Settings {\n  value: number;\n}\n\nexport const settingsMetadata: SettingsMetadata<Settings> = {\n  value: {\n    type: 'number',\n    label: 'Value',\n    default: 77,\n  },\n};\n\nexport default function BatteryMeter() {\n  const { settings } = useExperimentSettings<Settings>();\n  return (\n    <Meter.Root\n      className={styles.Root}\n      value={settings.value}\n      aria-label=\"Battery percentage remaining\"\n    >\n      <Meter.Track className={styles.Track}>\n        <Meter.Indicator className={styles.Indicator} />\n      </Meter.Track>\n      <BoltIcon className={styles.Icon} />\n    </Meter.Root>\n  );\n}\n\nfunction BoltIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" {...props}>\n      <path\n        d=\"M10.67 21c-.35 0-.62-.31-.57-.66L11 14H7.5c-.88 0-.33-.75-.31-.78 1.26-2.23 3.15-5.53 5.65-9.93.1-.18.3-.29.5-.29.35 0 .62.31.57.66l-.9 6.34h3.51c.4 0 .62.19.4.66-3.29 5.74-5.2 9.09-5.75 10.05-.1.18-.29.29-.5.29z\"\n        fill=\"currentColor\"\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/mobile-scroll-lock.module.css",
    "content": ".Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 500;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Backdrop {\n  position: fixed;\n  inset: 0;\n  background-color: black;\n  opacity: 0.2;\n  transition: opacity 150ms cubic-bezier(0.45, 1.005, 0, 1.005);\n\n  @media (prefers-color-scheme: dark) {\n    opacity: 0.7;\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n  }\n}\n\n.Popup {\n  box-sizing: border-box;\n  position: fixed;\n  top: 50%;\n  left: 50%;\n  transform: translate(-50%, -50%);\n  width: 24rem;\n  max-width: calc(100vw - 3rem);\n  margin-top: -2rem;\n  padding: 1.5rem;\n  border-radius: 0.5rem;\n  outline: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-50);\n  color: var(--color-gray-900);\n  transition: all 150ms;\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: translate(-50%, -50%) scale(0.9);\n  }\n}\n\n.Title {\n  margin-top: -0.375rem;\n  margin-bottom: 0.25rem;\n  font-size: 1.125rem;\n  line-height: 1.75rem;\n  letter-spacing: -0.0025em;\n  font-weight: 500;\n}\n\n.Description {\n  margin: 0 0 1.5rem;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-600);\n}\n\n.Actions {\n  display: flex;\n  justify-content: end;\n  gap: 1rem;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/mobile-scroll-lock.tsx",
    "content": "import { Dialog } from '@base-ui/react/dialog';\nimport styles from './mobile-scroll-lock.module.css';\n\nexport default function ExampleDialog() {\n  return (\n    <div style={{ height: 2000 }}>\n      <Dialog.Root>\n        <Dialog.Trigger className={styles.Button} style={{ marginTop: 500 }}>\n          View notifications\n        </Dialog.Trigger>\n        <Dialog.Portal>\n          <Dialog.Backdrop className={styles.Backdrop} />\n          <Dialog.Popup className={styles.Popup}>\n            <textarea placeholder=\"What's on your mind?\" style={{ width: '100%', height: 300 }} />\n            <div className={styles.Actions}>\n              <Dialog.Close className={styles.Button}>Close</Dialog.Close>\n            </div>\n            <Dialog.Root>\n              <Dialog.Trigger className={styles.Button}>View notifications</Dialog.Trigger>\n              <Dialog.Portal>\n                <Dialog.Backdrop className={styles.Backdrop} />\n                <Dialog.Popup className={styles.Popup}>\n                  <textarea\n                    placeholder=\"What's on your mind?\"\n                    style={{ width: '100%', height: 300 }}\n                  />\n                  <div className={styles.Actions}>\n                    <Dialog.Close className={styles.Button}>Close</Dialog.Close>\n                  </div>\n                </Dialog.Popup>\n              </Dialog.Portal>\n            </Dialog.Root>\n          </Dialog.Popup>\n        </Dialog.Portal>\n      </Dialog.Root>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/modality.module.css",
    "content": ".Trigger {\n  font-family: 'IBM Plex Sans', sans-serif;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  padding: 6px 12px;\n  border-radius: 5px;\n  background-color: black;\n  color: white;\n  border: none;\n  font-size: 100%;\n  line-height: 1.5;\n  user-select: none;\n  cursor: default;\n\n  &:hover {\n    background-color: #0072e6;\n  }\n\n  &:focus-visible {\n    outline: 2px solid black;\n    outline-offset: 2px;\n  }\n}\n\n.SelectDropdownArrow {\n  margin-left: 6px;\n  font-size: 10px;\n  line-height: 1;\n  height: 6px;\n}\n\n.Positioner {\n  z-index: 2001;\n\n  &:focus-visible {\n    outline: 0;\n  }\n}\n\n.SelectPopup {\n  overflow-y: auto;\n  background-color: white;\n  padding: 6px;\n  border-radius: 5px;\n  box-shadow:\n    0 2px 4px rgb(0 0 0 / 0.1),\n    0 0 0 1px rgb(0 0 0 / 0.1);\n  max-height: var(--available-height);\n  min-width: min(calc(var(--available-width) - 12px), calc(var(--anchor-width) + 20px));\n  scroll-padding: 4px;\n\n  &[data-side='none'] {\n    scroll-padding: 15px;\n  }\n\n  --padding: 6px;\n  --icon-size: 16px;\n  --icon-margin: 4px;\n}\n\n.SelectItem {\n  outline: 0;\n  cursor: default;\n  border-radius: 4px;\n  user-select: none;\n  display: flex;\n  align-items: center;\n  line-height: 1.5;\n  padding-block: var(--padding);\n  padding-inline: calc(var(--padding) + var(--icon-margin) + var(--icon-size));\n\n  &[data-selected] {\n    padding-left: var(--padding);\n  }\n\n  &[data-disabled] {\n    opacity: 0.5;\n  }\n\n  &[data-highlighted] {\n    background-color: black;\n    color: white;\n  }\n}\n\n.SelectItemIndicator {\n  margin-right: var(--icon-margin);\n  visibility: hidden;\n  width: var(--icon-size);\n  height: var(--icon-size);\n\n  &[data-selected] {\n    visibility: visible;\n  }\n}\n\n.MenuPopup {\n  position: relative;\n  font-family: 'IBM Plex Sans', sans-serif;\n  font-size: 0.875rem;\n  box-sizing: border-box;\n  padding: 6px;\n  min-width: 200px;\n  border-radius: 12px;\n  outline: 0;\n  background: #fff;\n  border: 1px solid var(--color-gray-200);\n  color: var(--color-gray-900);\n  box-shadow: 0 4px 30px var(--color-gray-200);\n  z-index: 1;\n  transform-origin: var(--transform-origin);\n  opacity: 1;\n  transform: scale(1, 1);\n  transition:\n    opacity 100ms ease-in,\n    transform 100ms ease-in;\n\n  @starting-style {\n    opacity: 0;\n    transform: scale(0.8);\n  }\n\n  &[data-exiting] {\n    opacity: 0;\n    transform: scale(0.8);\n    transition:\n      opacity 200ms ease-in,\n      transform 200ms ease-in;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    background: var(--color-gray-900);\n    border: 1px solid var(--color-gray-700);\n    color: var(--color-gray-300);\n    box-shadow: 0 4px 30px var(--color-gray-900);\n  }\n}\n\n.MenuItem {\n  list-style: none;\n  padding: 8px;\n  border-radius: 8px;\n  cursor: default;\n  user-select: none;\n\n  &:last-of-type {\n    border-bottom: none;\n  }\n\n  &:focus {\n    outline: 3px solid var(--color-blue);\n    background-color: var(--color-gray-100);\n    color: var(--color-gray-900);\n  }\n\n  &[data-disabled] {\n    color: var(--color-gray-400);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    &:focus {\n      outline: 3px solid var(--color-blue);\n      background-color: var(--color-gray-800);\n      color: var(--color-gray-300);\n    }\n\n    &[data-disabled] {\n      color: var(--color-gray-700);\n    }\n  }\n}\n\n.DialogPopup {\n  background: var(--color-gray-50);\n  border: 1px solid var(--color-gray-100);\n  min-width: 400px;\n  border-radius: 4px;\n  box-shadow: rgb(0 0 0 / 0.2) 0 18px 50px -10px;\n  position: fixed;\n  top: 50%;\n  left: 50%;\n  font-family: 'IBM Plex Sans', sans-serif;\n  transform: translate(-50%, -50%);\n  padding: 16px;\n  z-index: 2100;\n\n  @media (prefers-color-scheme: dark) {\n    background: var(--color-gray-900);\n    border: 1px solid var(--color-gray-700);\n  }\n}\n\n.DialogControls {\n  display: flex;\n  flex-direction: row-reverse;\n  background: var(--color-gray-100);\n  gap: 8px;\n  padding: 16px;\n  margin: 32px -16px -16px;\n\n  @media (prefers-color-scheme: dark) {\n    background: var(--color-gray-800);\n  }\n}\n\n.DialogCloseButton {\n  background-color: transparent;\n  border: 1px solid var(--color-gray-500);\n  color: var(--color-gray-900);\n  padding: 8px 16px;\n  border-radius: 4px;\n  font-family: 'IBM Plex Sans', sans-serif;\n  min-width: 80px;\n\n  &:hover {\n    background-color: var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    border: 1px solid var(--color-gray-300);\n    color: var(--color-gray-50);\n\n    &:hover {\n      background-color: var(--color-gray-700);\n    }\n  }\n}\n\n.Backdrop {\n  background: rgb(0 0 0 / 0.35);\n  position: fixed;\n  inset: 0;\n  backdrop-filter: blur(4px);\n  z-index: 2000;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/modality.tsx",
    "content": "'use client';\n\nimport * as React from 'react';\nimport { Select } from '@base-ui/react/select';\nimport { Menu } from '@base-ui/react/menu';\nimport { Dialog } from '@base-ui/react/dialog';\nimport styles from './modality.module.css';\n\nexport default function Modality() {\n  const [modal, setModal] = React.useState(true);\n  const [withBackdrop, setWithBackdrop] = React.useState(false);\n\n  return (\n    <div style={{ display: 'flex', flexDirection: 'column', gap: '30px' }}>\n      <label>\n        <input\n          type=\"checkbox\"\n          checked={modal}\n          onChange={(event) => setModal(event.target.checked)}\n        />{' '}\n        Modal\n      </label>\n      <label>\n        <input\n          type=\"checkbox\"\n          checked={withBackdrop}\n          onChange={() => setWithBackdrop(!withBackdrop)}\n        />{' '}\n        With backdrop\n      </label>\n      <SelectDemo modal={modal} withBackdrop={withBackdrop} />\n      <MenuDemo modal={modal} withBackdrop={withBackdrop} />\n      <DialogDemo modal={modal} withBackdrop={withBackdrop} />\n    </div>\n  );\n}\n\nfunction SelectDemo({ modal, withBackdrop }: Props) {\n  return (\n    <Select.Root defaultValue=\"system\" modal={modal}>\n      <Select.Trigger aria-label=\"Select font\" className={styles.Trigger}>\n        <Select.Value />\n        <Select.Icon className={styles.SelectDropdownArrow} />\n      </Select.Trigger>\n\n      {withBackdrop && <Select.Backdrop className={styles.Backdrop} />}\n\n      <Select.Portal>\n        <Select.Positioner\n          sideOffset={5}\n          className={styles.Positioner}\n          alignItemWithTrigger={false}\n        >\n          <Select.Popup className={styles.SelectPopup}>\n            <Select.Item value=\"system\" className={styles.SelectItem}>\n              <Select.ItemIndicator render={<CheckIcon />} className={styles.SelectItemIndicator} />\n              <Select.ItemText>System font</Select.ItemText>\n            </Select.Item>\n            <Select.Item value=\"arial\" className={styles.SelectItem}>\n              <Select.ItemIndicator render={<CheckIcon />} className={styles.SelectItemIndicator} />\n              <Select.ItemText>Arial</Select.ItemText>\n            </Select.Item>\n            <Select.Item value=\"roboto\" className={styles.SelectItem}>\n              <Select.ItemIndicator render={<CheckIcon />} className={styles.SelectItemIndicator} />\n              <Select.ItemText>Roboto</Select.ItemText>\n            </Select.Item>\n          </Select.Popup>\n        </Select.Positioner>\n      </Select.Portal>\n    </Select.Root>\n  );\n}\n\nfunction MenuDemo({ modal, withBackdrop }: Props) {\n  return (\n    <Menu.Root modal={modal}>\n      <Menu.Trigger className={styles.Trigger}>Open Menu</Menu.Trigger>\n\n      {withBackdrop && <Menu.Backdrop className={styles.Backdrop} />}\n\n      <Menu.Portal>\n        <Menu.Positioner align=\"start\" sideOffset={8} className={styles.Positioner}>\n          <Menu.Popup className={styles.MenuPopup}>\n            <Menu.Item onClick={() => console.log('Log out clicked')} className={styles.MenuItem}>\n              Log out\n            </Menu.Item>\n          </Menu.Popup>\n        </Menu.Positioner>\n      </Menu.Portal>\n    </Menu.Root>\n  );\n}\n\nfunction DialogDemo({ modal, withBackdrop }: Props) {\n  return (\n    <Dialog.Root modal={modal}>\n      <Dialog.Trigger className={styles.Trigger}>Open Dialog</Dialog.Trigger>\n\n      {withBackdrop && <Dialog.Backdrop className={styles.Backdrop} />}\n\n      <Dialog.Portal>\n        <Dialog.Popup className={styles.DialogPopup}>\n          <Dialog.Title>Subscribe</Dialog.Title>\n          <Dialog.Description>\n            Enter your email address to subscribe to our newsletter.\n          </Dialog.Description>\n          <div className={styles.DialogControls}>\n            <Dialog.Close className={styles.DialogCloseButton}>Subscribe</Dialog.Close>\n            <Dialog.Close className={styles.DialogCloseButton}>Cancel</Dialog.Close>\n          </div>\n        </Dialog.Popup>\n      </Dialog.Portal>\n    </Dialog.Root>\n  );\n}\n\ninterface Props {\n  modal: boolean;\n  withBackdrop: boolean;\n}\n\nconst CheckIcon = function CheckIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n    >\n      <path d=\"M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z\" fill=\"currentColor\" />\n    </svg>\n  );\n};\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/motion.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Popover } from '@base-ui/react/popover';\nimport { motion, AnimatePresence } from 'motion/react';\n\nfunction ConditionallyMounted() {\n  const [open, setOpen] = React.useState(false);\n  return (\n    <Popover.Root open={open} onOpenChange={setOpen}>\n      <Popover.Trigger>Trigger</Popover.Trigger>\n      <AnimatePresence>\n        {open && (\n          <Popover.Portal keepMounted>\n            <Popover.Positioner>\n              <Popover.Popup\n                render={\n                  <motion.div\n                    initial={{ scale: 0, opacity: 0 }}\n                    animate={{ scale: 1, opacity: 1 }}\n                    exit={{ scale: 0, opacity: 0 }}\n                  />\n                }\n              >\n                Popup\n              </Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        )}\n      </AnimatePresence>\n    </Popover.Root>\n  );\n}\n\nfunction AlwaysMounted() {\n  const [open, setOpen] = React.useState(false);\n  return (\n    <Popover.Root open={open} onOpenChange={setOpen}>\n      <Popover.Trigger>Trigger</Popover.Trigger>\n      <Popover.Portal keepMounted>\n        <Popover.Positioner>\n          <Popover.Popup\n            render={\n              <motion.div\n                initial={false}\n                animate={{\n                  scale: open ? 1 : 0,\n                  opacity: open ? 1 : 0,\n                }}\n              />\n            }\n          >\n            Popup\n          </Popover.Popup>\n        </Popover.Positioner>\n      </Popover.Portal>\n    </Popover.Root>\n  );\n}\n\nfunction NoOpacity() {\n  const [open, setOpen] = React.useState(false);\n  const actionsRef = React.useRef<Popover.Root.Actions>(null);\n\n  return (\n    <Popover.Root\n      open={open}\n      onOpenChange={(nextOpen, eventDetails) => {\n        setOpen(nextOpen);\n        eventDetails.preventUnmountOnClose();\n      }}\n      actionsRef={actionsRef}\n    >\n      <Popover.Trigger>Trigger</Popover.Trigger>\n      <AnimatePresence>\n        {open && (\n          <Popover.Portal keepMounted>\n            <Popover.Positioner>\n              <Popover.Popup\n                render={\n                  <motion.div\n                    initial={{ scale: 0 }}\n                    animate={{ scale: 1 }}\n                    exit={{ scale: 0 }}\n                    onAnimationComplete={() => {\n                      if (!open) {\n                        actionsRef.current?.unmount();\n                      }\n                    }}\n                  />\n                }\n              >\n                Popup\n              </Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        )}\n      </AnimatePresence>\n    </Popover.Root>\n  );\n}\n\nexport default function Page() {\n  return (\n    <div>\n      <h2>Conditionally mounted</h2>\n      <ConditionallyMounted />\n      <h2>Always mounted</h2>\n      <AlwaysMounted />\n      <h2>No opacity</h2>\n      <NoOpacity />\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/navigation-menu-popups.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { NavigationMenu } from '@base-ui/react/navigation-menu';\nimport { Dialog } from '@base-ui/react/dialog';\nimport { Popover } from '@base-ui/react/popover';\nimport { AlertDialog } from '@base-ui/react/alert-dialog';\nimport styles from './navigation-menu.module.css';\n\nexport default function ExampleNavigationMenu() {\n  return (\n    <React.Fragment>\n      <NavigationMenu.Root className={styles.Root}>\n        <NavigationMenu.List className={styles.List}>\n          <NavigationMenu.Item>\n            <NavigationMenu.Trigger className={triggerClassName}>\n              Dialog Test\n            </NavigationMenu.Trigger>\n\n            <NavigationMenu.Content className={styles.Content}>\n              <Dialog.Root>\n                <Dialog.Trigger className={styles.ActionButton}>First Dialog</Dialog.Trigger>\n                <Dialog.Portal>\n                  <Dialog.Backdrop className={styles.Backdrop} />\n                  <Dialog.Popup\n                    onClick={(event) => event.stopPropagation()}\n                    className={styles.ModalPopup}\n                  >\n                    <Dialog.Title className={styles.ModalTitle}>Notifications</Dialog.Title>\n                    <Dialog.Description className={styles.ModalDescription}>\n                      You are all caught up. Good job!\n                    </Dialog.Description>\n                    <div className={styles.ModalActions}>\n                      <Dialog.Close className={styles.ActionButton}>Close</Dialog.Close>\n                    </div>\n                  </Dialog.Popup>\n                </Dialog.Portal>\n              </Dialog.Root>\n\n              <Dialog.Root>\n                <Dialog.Trigger className={styles.ActionButton}>Second Dialog</Dialog.Trigger>\n                <Dialog.Portal>\n                  <Dialog.Backdrop className={styles.Backdrop} />\n                  <Dialog.Popup\n                    onClick={(event) => event.stopPropagation()}\n                    className={styles.ModalPopup}\n                  >\n                    <Dialog.Title className={styles.ModalTitle}>Notifications</Dialog.Title>\n                    <Dialog.Description className={styles.ModalDescription}>\n                      You are all caught up. Good job!\n                    </Dialog.Description>\n                    <div className={styles.ModalActions}>\n                      <Dialog.Close className={styles.ActionButton}>Close</Dialog.Close>\n                    </div>\n                  </Dialog.Popup>\n                </Dialog.Portal>\n              </Dialog.Root>\n\n              <Dialog.Root>\n                <Dialog.Trigger className={styles.ActionButton}>Third Dialog</Dialog.Trigger>\n                <Dialog.Portal>\n                  <Dialog.Backdrop className={styles.Backdrop} />\n                  <Dialog.Popup\n                    onClick={(event) => event.stopPropagation()}\n                    className={styles.ModalPopup}\n                  >\n                    <Dialog.Title className={styles.ModalTitle}>Notifications</Dialog.Title>\n                    <Dialog.Description className={styles.ModalDescription}>\n                      You are all caught up. Good job!\n                    </Dialog.Description>\n                    <div className={styles.ModalActions}>\n                      <Dialog.Close className={styles.ActionButton}>Close</Dialog.Close>\n                    </div>\n                  </Dialog.Popup>\n                </Dialog.Portal>\n              </Dialog.Root>\n            </NavigationMenu.Content>\n          </NavigationMenu.Item>\n\n          <NavigationMenu.Item>\n            <NavigationMenu.Trigger className={triggerClassName}>\n              Popover Test\n            </NavigationMenu.Trigger>\n\n            <NavigationMenu.Content className={styles.Content}>\n              <Popover.Root>\n                <Popover.Trigger className={`${styles.ActionButton} ${styles.IconActionButton}`}>\n                  <BellIcon aria-label=\"Notifications\" />\n                </Popover.Trigger>\n                <Popover.Portal>\n                  <Popover.Positioner sideOffset={8}>\n                    <Popover.Popup className={styles.PopoverPopup}>\n                      <Popover.Arrow className={styles.Arrow}>\n                        <ArrowSvg />\n                      </Popover.Arrow>\n                      <Popover.Title>Notifications</Popover.Title>\n                      <Popover.Description className={styles.ModalDescription}>\n                        You are all caught up. Good job!\n                      </Popover.Description>\n                    </Popover.Popup>\n                  </Popover.Positioner>\n                </Popover.Portal>\n              </Popover.Root>\n            </NavigationMenu.Content>\n          </NavigationMenu.Item>\n\n          <NavigationMenu.Item>\n            <NavigationMenu.Trigger className={triggerClassName}>\n              Alert Dialog Test\n            </NavigationMenu.Trigger>\n\n            <NavigationMenu.Content className={styles.Content}>\n              <AlertDialog.Root>\n                <AlertDialog.Trigger\n                  className={`${styles.ActionButton} ${styles.DangerActionButton}`}\n                >\n                  Discard draft\n                </AlertDialog.Trigger>\n                <AlertDialog.Portal>\n                  <AlertDialog.Backdrop className={styles.Backdrop} />\n                  <AlertDialog.Popup\n                    onClick={(event) => event.stopPropagation()}\n                    className={styles.ModalPopup}\n                  >\n                    <AlertDialog.Title className={styles.ModalTitle}>\n                      Discard draft?\n                    </AlertDialog.Title>\n                    <AlertDialog.Description className={styles.ModalDescription}>\n                      You can’t undo this action.\n                    </AlertDialog.Description>\n                    <div className={styles.ModalActions}>\n                      <AlertDialog.Close\n                        className={`${styles.ActionButton} ${styles.DangerActionButton}`}\n                      >\n                        Discard\n                      </AlertDialog.Close>\n                    </div>\n                  </AlertDialog.Popup>\n                </AlertDialog.Portal>\n              </AlertDialog.Root>\n            </NavigationMenu.Content>\n          </NavigationMenu.Item>\n        </NavigationMenu.List>\n\n        <NavigationMenu.Portal>\n          <NavigationMenu.Positioner\n            sideOffset={10}\n            collisionPadding={{ top: 5, bottom: 5, left: 20, right: 20 }}\n            className={styles.Positioner}\n            style={{\n              ['--duration' as string]: '0.35s',\n              ['--easing' as string]: 'cubic-bezier(0.22, 1, 0.36, 1)',\n            }}\n          >\n            <NavigationMenu.Popup className={styles.Popup}>\n              <NavigationMenu.Arrow className={styles.Arrow}>\n                <ArrowSvg />\n              </NavigationMenu.Arrow>\n              <NavigationMenu.Viewport className={styles.Viewport} />\n            </NavigationMenu.Popup>\n          </NavigationMenu.Positioner>\n        </NavigationMenu.Portal>\n      </NavigationMenu.Root>\n\n      <AlertDialog.Root>\n        <AlertDialog.Trigger className={`${styles.ActionButton} ${styles.DangerActionButton}`}>\n          Discard draft\n        </AlertDialog.Trigger>\n        <AlertDialog.Portal>\n          <AlertDialog.Backdrop className={styles.Backdrop} />\n          <AlertDialog.Popup className={styles.ModalPopup}>\n            <AlertDialog.Title className={styles.ModalTitle}>Discard draft?</AlertDialog.Title>\n            <AlertDialog.Description className={styles.ModalDescription}>\n              You can’t undo this action.\n            </AlertDialog.Description>\n            <div className={styles.ModalActions}>\n              <AlertDialog.Close className={`${styles.ActionButton} ${styles.DangerActionButton}`}>\n                Discard\n              </AlertDialog.Close>\n            </div>\n          </AlertDialog.Popup>\n        </AlertDialog.Portal>\n      </AlertDialog.Root>\n    </React.Fragment>\n  );\n}\n\nfunction BellIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"20\" height=\"20\" viewBox=\"0 0 16 16\" {...props}>\n      <path d=\"M 8 1 C 7.453125 1 7 1.453125 7 2 L 7 3.140625 C 5.28125 3.589844 4 5.144531 4 7 L 4 10.984375 C 4 10.984375 3.984375 11.261719 3.851563 11.519531 C 3.71875 11.78125 3.558594 12 3 12 L 3 13 L 13 13 L 13 12 C 12.40625 12 12.253906 11.78125 12.128906 11.53125 C 12.003906 11.277344 12 11.003906 12 11.003906 L 12 7 C 12 5.144531 10.71875 3.589844 9 3.140625 L 9 2 C 9 1.453125 8.546875 1 8 1 Z M 8 13 C 7.449219 13 7 13.449219 7 14 C 7 14.550781 7.449219 15 8 15 C 8.550781 15 9 14.550781 9 14 C 9 13.449219 8.550781 13 8 13 Z M 8 4 C 9.664063 4 11 5.335938 11 7 L 11 10.996094 C 11 10.996094 10.988281 11.472656 11.234375 11.96875 C 11.238281 11.980469 11.246094 11.988281 11.25 12 L 4.726563 12 C 4.730469 11.992188 4.738281 11.984375 4.742188 11.980469 C 4.992188 11.488281 5 11.015625 5 11.015625 L 5 7 C 5 5.335938 6.335938 4 8 4 Z\" />\n    </svg>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nconst triggerClassName = styles.Trigger;\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/navigation-menu.module.css",
    "content": ".Root {\n  background-color: var(--color-gray-50);\n  border-radius: 0.5rem;\n  padding: 0.25rem;\n  color: var(--color-gray-900);\n  min-width: max-content;\n}\n\n.List {\n  display: flex;\n  position: relative;\n  list-style: none;\n  padding: 0;\n  margin: 0;\n}\n\n.Trigger {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: 0.375rem;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: none;\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: var(--text-base);\n  font-weight: 500;\n  line-height: var(--text-base--line-height);\n  color: var(--color-gray-900);\n  user-select: none;\n  text-decoration: none;\n\n  @media (max-width: 500px) {\n    font-size: 0.925rem;\n    padding: 0 0.5rem;\n  }\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    position: relative;\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Icon {\n  transition: transform 0.2s ease;\n\n  &[data-popup-open] {\n    transform: rotate(180deg);\n  }\n}\n\n.Positioner {\n  --easing: cubic-bezier(0.22, 1, 0.36, 1);\n  --duration: 0.35s;\n  box-sizing: border-box;\n  transition-property: top, left, right, bottom;\n  transition-duration: var(--duration);\n  transition-timing-function: var(--easing);\n  width: var(--positioner-width);\n  height: var(--positioner-height);\n  max-width: var(--available-width);\n\n  &::before {\n    content: '';\n    position: absolute;\n  }\n\n  &[data-side='top']::before {\n    left: 0;\n    right: 0;\n    bottom: -10px;\n    height: 10px;\n  }\n\n  &[data-side='bottom']::before {\n    left: 0;\n    right: 0;\n    top: -10px;\n    height: 10px;\n  }\n\n  &[data-side='left']::before {\n    top: 0;\n    bottom: 0;\n    right: -10px;\n    width: 10px;\n  }\n\n  &[data-side='right']::before {\n    top: 0;\n    bottom: 0;\n    left: -10px;\n    width: 10px;\n  }\n\n  &[data-instant] {\n    transition: none;\n  }\n}\n\n.Popup {\n  position: relative;\n  box-sizing: border-box;\n  border-radius: 0.5rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  transform-origin: var(--transform-origin);\n  transition-property: opacity, transform, width, height;\n  transition-duration: var(--duration);\n  transition-timing-function: var(--easing);\n  width: var(--popup-width);\n  height: var(--popup-height);\n  max-height: var(--available-height);\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  &[data-ending-style] {\n    transition-timing-function: ease;\n    transition-duration: 0.15s;\n  }\n}\n\n@media (prefers-color-scheme: light) {\n  .Popup {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n}\n\n@media (prefers-color-scheme: dark) {\n  .Popup {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Content {\n  box-sizing: border-box;\n  transition:\n    opacity calc(var(--duration) * 0.5) ease,\n    transform var(--duration) var(--easing);\n  padding: 1.5rem;\n  width: calc(100vw - 40px);\n  height: 100%;\n  overflow-y: auto;\n  max-height: var(--available-height);\n\n  @media (min-width: 500px) {\n    width: max-content;\n    min-width: 400px;\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n  }\n\n  &[data-starting-style] {\n    &[data-activation-direction='left'] {\n      transform: translateX(-50%);\n    }\n    &[data-activation-direction='right'] {\n      transform: translateX(50%);\n    }\n  }\n\n  &[data-ending-style] {\n    &[data-activation-direction='left'] {\n      transform: translateX(50%);\n    }\n    &[data-activation-direction='right'] {\n      transform: translateX(-50%);\n    }\n  }\n}\n\n.Viewport {\n  position: relative;\n  overflow: hidden;\n  width: 100%;\n  height: 100%;\n}\n\n.GridLinkList {\n  display: grid;\n  grid-template-columns: 12rem 12rem;\n  list-style: none;\n  padding: 0;\n  margin: 0;\n\n  @media (max-width: 500px) {\n    grid-template-columns: 1fr;\n  }\n}\n\n.FlexLinkList {\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  max-width: 400px;\n  padding: 0;\n  margin: 0;\n  list-style: none;\n}\n\n.LinkCard {\n  display: block;\n  padding: 0.5rem;\n  border-radius: 0.375rem;\n  text-decoration: none;\n  color: inherit;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:focus-visible {\n    position: relative;\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n\n  @media (min-width: 425px) {\n    padding: 0.75rem;\n  }\n}\n\n.LinkTitle {\n  margin: 0 0 4px;\n  font-size: var(--text-base);\n  font-weight: 500;\n  line-height: var(--text-sm--line-height);\n}\n\n.LinkDescription {\n  margin: 0;\n  font-size: var(--text-sm);\n  line-height: var(--text-sm--line-height);\n  color: var(--color-gray-500);\n}\n\n.Arrow {\n  display: flex;\n  transition: left calc(var(--duration)) var(--easing);\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n\n.ActionButton {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background: var(--color-gray-50);\n  padding: 0 0.875rem;\n  font-size: var(--text-base);\n  line-height: var(--text-base--line-height);\n  font-weight: 500;\n  color: var(--color-gray-900);\n  user-select: none;\n}\n\n@media (hover: hover) {\n  .ActionButton:hover {\n    background: var(--color-gray-100);\n  }\n}\n\n.ActionButton:active,\n.ActionButton[data-popup-open] {\n  background: var(--color-gray-100);\n}\n\n.ActionButton:focus-visible {\n  outline: 2px solid var(--color-blue);\n  outline-offset: -1px;\n}\n\n.IconActionButton {\n  width: 2.5rem;\n  padding: 0;\n}\n\n.DangerActionButton {\n  color: var(--color-red-800);\n}\n\n.Backdrop {\n  position: fixed;\n  inset: 0;\n  background: black;\n  opacity: 0.2;\n  transition: all 150ms;\n}\n\n.Backdrop[data-starting-style],\n.Backdrop[data-ending-style] {\n  opacity: 0;\n}\n\n.ModalPopup {\n  position: fixed;\n  top: 50%;\n  left: 50%;\n  margin-top: -2rem;\n  width: 24rem;\n  max-width: calc(100vw - 3rem);\n  transform: translate(-50%, -50%);\n  border-radius: 0.5rem;\n  background: var(--color-gray-50);\n  padding: 1.5rem;\n  color: var(--color-gray-900);\n  outline: 1px solid var(--color-gray-200);\n  transition: all 150ms;\n}\n\n.ModalPopup[data-starting-style],\n.ModalPopup[data-ending-style] {\n  transform: translate(-50%, -50%) scale(0.9);\n  opacity: 0;\n}\n\n.ModalTitle {\n  margin: -0.375rem 0 0.25rem;\n  font-size: var(--text-lg);\n  line-height: var(--text-lg--line-height);\n  font-weight: 500;\n}\n\n.ModalDescription {\n  margin: 0 0 1.5rem;\n  font-size: var(--text-base);\n  line-height: var(--text-base--line-height);\n  color: var(--color-gray-600);\n}\n\n.ModalActions {\n  display: flex;\n  justify-content: flex-end;\n  gap: 1rem;\n}\n\n.PopoverPopup {\n  transform-origin: var(--transform-origin);\n  border-radius: 0.5rem;\n  background: canvas;\n  padding: 1rem 1.5rem;\n  color: var(--color-gray-900);\n  transition:\n    transform 150ms,\n    scale 150ms,\n    opacity 150ms;\n}\n\n.PopoverPopup[data-starting-style],\n.PopoverPopup[data-ending-style] {\n  scale: 0.9;\n  opacity: 0;\n}\n\n@media (prefers-color-scheme: light) {\n  .PopoverPopup {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n}\n\n@media (prefers-color-scheme: dark) {\n  .Backdrop {\n    opacity: 0.7;\n  }\n\n  .ModalPopup {\n    outline: 1px solid var(--color-gray-300);\n  }\n\n  .PopoverPopup {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n    box-shadow: none;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/navigation-menu.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { NavigationMenu } from '@base-ui/react/navigation-menu';\nimport { Collapsible } from '@base-ui/react/collapsible';\nimport styles from './navigation-menu.module.css';\n\nexport default function ExampleNavigationMenu() {\n  const [renderExtraItem, setRenderExtraItem] = React.useState(false);\n  const [hoveredLink, setHoveredLink] = React.useState<string | null>(null);\n\n  return (\n    <div>\n      <NavigationMenu.Root className={styles.Root}>\n        <NavigationMenu.List className={styles.List}>\n          <NavigationMenu.Item>\n            <NavigationMenu.Trigger className={styles.Trigger}>\n              Overview\n              <NavigationMenu.Icon className={styles.Icon}>\n                <ChevronDownIcon />\n              </NavigationMenu.Icon>\n            </NavigationMenu.Trigger>\n            <NavigationMenu.Content className={styles.Content}>\n              <div style={{ display: 'flex', gap: 20 }}>\n                {/* Left side - Links */}\n                <div style={{ flex: 1 }}>\n                  <ul className={styles.GridLinkList}>\n                    {overviewLinks.map((item) => (\n                      <li key={item.href}>\n                        <Link\n                          className={styles.LinkCard}\n                          href={item.href}\n                          onMouseEnter={() => {\n                            setHoveredLink(item.href);\n                          }}\n                        >\n                          <h3 className={styles.LinkTitle}>{item.title}</h3>\n                          <p className={styles.LinkDescription}>{item.description}</p>\n                        </Link>\n                      </li>\n                    ))}\n                  </ul>\n                </div>\n\n                {/* Right side - Dynamic content */}\n                <div style={{ flex: 1 }}>\n                  {hoveredLink && (\n                    <div\n                      style={{\n                        backgroundColor: 'black',\n                        color: 'white',\n                        padding: 20,\n                        borderRadius: 8,\n                        minHeight: 200,\n                        width: hoveredLink === '/react/overview/quick-start' ? 200 : 400,\n                        height: hoveredLink === '/react/overview/accessibility' ? 150 : 300,\n                      }}\n                    >\n                      {hoveredLink === '/react/overview/quick-start' && (\n                        <div>\n                          <h4 style={{ marginBottom: 10 }}>Quick Start Guide</h4>\n                          <p>\n                            All components are included in a single package. Base UI is\n                            tree-shakeable, so your app bundle will contain only the components that\n                            you actually use.\n                          </p>\n                        </div>\n                      )}\n                      {hoveredLink === '/react/overview/accessibility' && (\n                        <div>\n                          <h4 style={{ marginBottom: 10 }}>Accessibility Features</h4>\n                          <p>\n                            ARIA attributes built-in, keyboard navigation, screen reader support,\n                            and focus management.\n                          </p>\n                        </div>\n                      )}\n                      {hoveredLink === '/react/overview/releases' && (\n                        <div>\n                          <h4 style={{ marginBottom: 10 }}>Latest Releases</h4>\n                          <p>\n                            v1.0.0 - Major release with new components, performance improvements,\n                            and bug fixes.\n                          </p>\n                        </div>\n                      )}\n                      {hoveredLink === '/react/overview/about' && (\n                        <div>\n                          <h4 style={{ marginBottom: 10 }}>About Base UI</h4>\n                          <p>\n                            Unstyled components that are fully customizable, and built by the MUI\n                            team.\n                          </p>\n                        </div>\n                      )}\n                    </div>\n                  )}\n                </div>\n              </div>\n            </NavigationMenu.Content>\n          </NavigationMenu.Item>\n\n          <NavigationMenu.Item>\n            <NavigationMenu.Trigger className={styles.Trigger}>\n              Handbook\n              <NavigationMenu.Icon className={styles.Icon}>\n                <ChevronDownIcon />\n              </NavigationMenu.Icon>\n            </NavigationMenu.Trigger>\n            <NavigationMenu.Content className={styles.Content}>\n              <Collapsible.Root className={styles.CollapsibleRoot}>\n                <Collapsible.Trigger className={styles.CollapsibleTrigger}>\n                  Recovery keys\n                </Collapsible.Trigger>\n                <Collapsible.Panel className={styles.CollapsiblePanel}>\n                  <div className={styles.RecoveryList}>\n                    <div>alien-bean-pasta</div>\n                    <div>wild-irish-burrito</div>\n                    <div>horse-battery-staple</div>\n                  </div>\n                </Collapsible.Panel>\n              </Collapsible.Root>\n\n              <button type=\"button\" onClick={() => setRenderExtraItem(!renderExtraItem)}>\n                Render extra item\n              </button>\n\n              <ul className={styles.FlexLinkList}>\n                {handbookLinks.map((item) => (\n                  <li key={item.href}>\n                    <Link className={styles.LinkCard} href={item.href}>\n                      <h3 className={styles.LinkTitle}>{item.title}</h3>\n                      <p className={styles.LinkDescription}>{item.description}</p>\n                    </Link>\n                  </li>\n                ))}\n                {renderExtraItem && (\n                  <li>\n                    <Link className={styles.LinkCard} href=\"/react/overview/quick-start\">\n                      <h3 className={styles.LinkTitle}>Quick Start</h3>\n                      <p className={styles.LinkDescription}>\n                        Install and assemble your first component.\n                      </p>\n                    </Link>\n                  </li>\n                )}\n              </ul>\n            </NavigationMenu.Content>\n          </NavigationMenu.Item>\n\n          <NavigationMenu.Item>\n            <NavigationMenu.Trigger className={styles.Trigger}>\n              Nested\n              <NavigationMenu.Icon className={styles.Icon}>\n                <ChevronDownIcon />\n              </NavigationMenu.Icon>\n            </NavigationMenu.Trigger>\n            <NavigationMenu.Content className={styles.Content}>\n              <NavigationMenu.Root className={styles.Root} defaultValue=\"overview\">\n                <NavigationMenu.List className={styles.List}>\n                  <NavigationMenu.Item value=\"overview\">\n                    <NavigationMenu.Trigger className={styles.Trigger}>\n                      Overview\n                      <NavigationMenu.Icon className={styles.Icon}>\n                        <ChevronDownIcon />\n                      </NavigationMenu.Icon>\n                    </NavigationMenu.Trigger>\n                    <NavigationMenu.Content className={styles.Content}>\n                      <ul className={styles.GridLinkList}>\n                        {overviewLinks.map((item) => (\n                          <li key={item.href}>\n                            <Link className={styles.LinkCard} href={item.href}>\n                              <h3 className={styles.LinkTitle}>{item.title}</h3>\n                              <p className={styles.LinkDescription}>{item.description}</p>\n                            </Link>\n                          </li>\n                        ))}\n                      </ul>\n                    </NavigationMenu.Content>\n                  </NavigationMenu.Item>\n\n                  <NavigationMenu.Item value=\"handbook\">\n                    <NavigationMenu.Trigger className={styles.Trigger}>\n                      Handbook\n                      <NavigationMenu.Icon className={styles.Icon}>\n                        <ChevronDownIcon />\n                      </NavigationMenu.Icon>\n                    </NavigationMenu.Trigger>\n                    <NavigationMenu.Content className={styles.Content}>\n                      <button type=\"button\" onClick={() => setRenderExtraItem(!renderExtraItem)}>\n                        Render extra item\n                      </button>\n\n                      <ul className={styles.FlexLinkList}>\n                        {handbookLinks.map((item) => (\n                          <li key={item.href}>\n                            <Link className={styles.LinkCard} href={item.href}>\n                              <h3 className={styles.LinkTitle}>{item.title}</h3>\n                              <p className={styles.LinkDescription}>{item.description}</p>\n                            </Link>\n                          </li>\n                        ))}\n                        {renderExtraItem && (\n                          <li>\n                            <Link className={styles.LinkCard} href=\"/react/overview/quick-start\">\n                              <h3 className={styles.LinkTitle}>Quick Start</h3>\n                              <p className={styles.LinkDescription}>\n                                Install and assemble your first component.\n                              </p>\n                            </Link>\n                          </li>\n                        )}\n                      </ul>\n                    </NavigationMenu.Content>\n                  </NavigationMenu.Item>\n\n                  <NavigationMenu.Item>\n                    <Link className={styles.Trigger} href=\"https://github.com/mui/base-ui\">\n                      GitHub\n                    </Link>\n                  </NavigationMenu.Item>\n                </NavigationMenu.List>\n\n                <NavigationMenu.Viewport className={styles.Viewport} />\n              </NavigationMenu.Root>\n            </NavigationMenu.Content>\n          </NavigationMenu.Item>\n\n          <NavigationMenu.Item>\n            <NavigationMenu.Trigger className={styles.Trigger}>\n              Long List\n              <NavigationMenu.Icon className={styles.Icon}>\n                <ChevronDownIcon />\n              </NavigationMenu.Icon>\n            </NavigationMenu.Trigger>\n            <NavigationMenu.Content className={styles.Content}>\n              <ul className={styles.LongList}>\n                {Array.from({ length: 50 }, (_, index) => (\n                  <li key={index}>\n                    <Link className={styles.LinkCard} href={`/item-${index}`}>\n                      <h3 className={styles.LinkTitle}>Item {index + 1}</h3>\n                      <p className={styles.LinkDescription}>\n                        This is item number {index + 1} in the long list for testing scrollable\n                        menus.\n                      </p>\n                    </Link>\n                  </li>\n                ))}\n              </ul>\n            </NavigationMenu.Content>\n          </NavigationMenu.Item>\n\n          <NavigationMenu.Item>\n            <Link className={styles.Trigger} href=\"https://github.com/mui/base-ui\">\n              GitHub\n            </Link>\n          </NavigationMenu.Item>\n        </NavigationMenu.List>\n\n        <NavigationMenu.Portal>\n          <NavigationMenu.Positioner\n            align=\"start\"\n            className={styles.Positioner}\n            sideOffset={10}\n            alignOffset={-100}\n            collisionPadding={{ top: 5, bottom: 5, left: 20, right: 20 }}\n          >\n            <NavigationMenu.Popup className={styles.Popup}>\n              <NavigationMenu.Arrow className={styles.Arrow}>\n                <ArrowSvg />\n              </NavigationMenu.Arrow>\n              <NavigationMenu.Viewport className={styles.Viewport} />\n            </NavigationMenu.Popup>\n          </NavigationMenu.Positioner>\n        </NavigationMenu.Portal>\n      </NavigationMenu.Root>\n\n      <NavigationMenu.Root className={styles.Root} style={{ position: 'absolute', bottom: 100 }}>\n        <NavigationMenu.List className={styles.List}>\n          <NavigationMenu.Item>\n            <NavigationMenu.Trigger className={styles.Trigger}>\n              Overview\n              <NavigationMenu.Icon className={styles.Icon}>\n                <ChevronDownIcon />\n              </NavigationMenu.Icon>\n            </NavigationMenu.Trigger>\n            <NavigationMenu.Content className={styles.Content}>\n              <ul className={styles.GridLinkList}>\n                {overviewLinks.map((item) => (\n                  <li key={item.href}>\n                    <Link className={styles.LinkCard} href={item.href}>\n                      <h3 className={styles.LinkTitle}>{item.title}</h3>\n                      <p className={styles.LinkDescription}>{item.description}</p>\n                    </Link>\n                  </li>\n                ))}\n              </ul>\n            </NavigationMenu.Content>\n          </NavigationMenu.Item>\n\n          <NavigationMenu.Item>\n            <NavigationMenu.Trigger className={styles.Trigger}>\n              Handbook\n              <NavigationMenu.Icon className={styles.Icon}>\n                <ChevronDownIcon />\n              </NavigationMenu.Icon>\n            </NavigationMenu.Trigger>\n            <NavigationMenu.Content className={styles.Content}>\n              <button type=\"button\" onClick={() => setRenderExtraItem(!renderExtraItem)}>\n                Render extra item\n              </button>\n\n              <ul className={styles.FlexLinkList}>\n                {handbookLinks.map((item) => (\n                  <li key={item.href}>\n                    <Link className={styles.LinkCard} href={item.href}>\n                      <h3 className={styles.LinkTitle}>{item.title}</h3>\n                      <p className={styles.LinkDescription}>{item.description}</p>\n                    </Link>\n                  </li>\n                ))}\n                {renderExtraItem && (\n                  <li>\n                    <Link className={styles.LinkCard} href=\"/react/overview/quick-start\">\n                      <h3 className={styles.LinkTitle}>Quick Start</h3>\n                      <p className={styles.LinkDescription}>\n                        Install and assemble your first component.\n                      </p>\n                    </Link>\n                  </li>\n                )}\n              </ul>\n            </NavigationMenu.Content>\n          </NavigationMenu.Item>\n\n          <NavigationMenu.Item>\n            <Link className={styles.Trigger} href=\"https://github.com/mui/base-ui\">\n              GitHub\n            </Link>\n          </NavigationMenu.Item>\n        </NavigationMenu.List>\n\n        <NavigationMenu.Portal>\n          <NavigationMenu.Positioner\n            className={styles.Positioner}\n            sideOffset={10}\n            collisionPadding={{ top: 5, bottom: 5, left: 20, right: 20 }}\n          >\n            <NavigationMenu.Popup className={styles.Popup}>\n              <NavigationMenu.Arrow className={styles.Arrow}>\n                <ArrowSvg />\n              </NavigationMenu.Arrow>\n              <NavigationMenu.Viewport className={styles.Viewport} />\n            </NavigationMenu.Popup>\n          </NavigationMenu.Positioner>\n        </NavigationMenu.Portal>\n      </NavigationMenu.Root>\n    </div>\n  );\n}\n\nfunction Link(props: NavigationMenu.Link.Props) {\n  return (\n    <NavigationMenu.Link\n      render={\n        // Use the `render` prop to render your framework's Link component\n        // for client-side routing.\n        // e.g. `<NextLink href={props.href} />` instead of `<a />`.\n        <a />\n      }\n      {...props}\n    />\n  );\n}\n\nfunction ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M1 3.5L5 7.5L9 3.5\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nconst overviewLinks = [\n  {\n    href: '/react/overview/quick-start',\n    title: 'Quick Start',\n    description: 'Install and assemble your first component.',\n  },\n  {\n    href: '/react/overview/accessibility',\n    title: 'Accessibility',\n    description: 'Learn how we build accessible components.',\n  },\n  {\n    href: '/react/overview/releases',\n    title: 'Releases',\n    description: 'See what’s new in the latest Base UI versions.',\n  },\n  {\n    href: '/react/overview/about',\n    title: 'About',\n    description: 'Learn more about Base UI and our mission.',\n  },\n] as const;\n\nconst handbookLinks = [\n  {\n    href: '/react/handbook/styling',\n    title: 'Styling',\n    description:\n      'Base UI components can be styled with plain CSS, Tailwind CSS, CSS-in-JS, or CSS Modules.',\n  },\n  {\n    href: '/react/handbook/animation',\n    title: 'Animation',\n    description:\n      'Base UI components can be animated with CSS transitions, CSS animations, or JavaScript libraries.',\n  },\n  {\n    href: '/react/handbook/composition',\n    title: 'Composition',\n    description:\n      'Base UI components can be replaced and composed with your own existing components.',\n  },\n] as const;\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/page.tsx",
    "content": "import clsx from 'clsx';\nimport { Sidebar } from './_components/Sidebar';\nimport classes from './_components/ExperimentRoot.module.css';\n\nexport default async function Experiments() {\n  return (\n    <div className={clsx(classes.root, classes.withSidebar)}>\n      <Sidebar className={classes.sidebar} />\n      <main className={clsx(classes.main, classes.landing)}>\n        <h1>Base UI experiments</h1>\n        <p>← Choose an experiment from the list</p>\n      </main>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/perf/contained-triggers.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\nimport { Tooltip } from '@base-ui/react/tooltip';\nimport { Popover } from '@base-ui/react/popover';\nimport { Dialog } from '@base-ui/react/dialog';\nimport menuDemoStyles from 'docs/src/app/(docs)/react/components/menu/demos/submenu/css-modules/index.module.css';\nimport tooltipDemoStyles from 'docs/src/app/(docs)/react/components/tooltip/demos/hero/css-modules/index.module.css';\nimport popoverDemoStyles from 'docs/src/app/(docs)/react/components/popover/demos/_index.module.css';\nimport dialogDemoStyles from 'docs/src/app/(docs)/react/components/dialog/demos/_index.module.css';\nimport { SettingsMetadata, useExperimentSettings } from '../_components/SettingsPanel';\nimport styles from './perf.module.css';\nimport PerformanceBenchmark from './utils/benchmark';\n\ninterface Settings {\n  renderDialog: boolean;\n  renderMenu: boolean;\n  renderPopover: boolean;\n  renderTooltip: boolean;\n}\n\ninterface RowData {\n  label: string;\n  index: number;\n}\n\ninterface RowProps {\n  rowData: RowData;\n}\n\nconst rowCount = 500;\nconst menuItemCount = 50;\n\nconst rows = Array.from({ length: rowCount }).map((_, i) => ({\n  label: `Row ${i + 1}`,\n  index: i + 1,\n}));\n\nconst menuItems = Array.from({ length: menuItemCount }).map((_, i) => ({\n  label: `Menu Item ${i + 1}`,\n  index: i + 1,\n}));\n\nexport default function PerfExperiment() {\n  return (\n    <div className={styles.container}>\n      <h1>Component performance - contained triggers</h1>\n      <PerformanceBenchmark>\n        <TestComponent />\n      </PerformanceBenchmark>\n    </div>\n  );\n}\n\nfunction TestComponent() {\n  const { settings } = useExperimentSettings<Settings>();\n  const { renderMenu, renderTooltip, renderPopover, renderDialog } = settings;\n  return (\n    <div className={styles.rows}>\n      {rows.map((row) => (\n        <div key={row.index} className={styles.row}>\n          <span className={styles.label}>{row.label}</span>\n          <span className={styles.actions}>\n            {renderDialog && <RowDialog rowData={row} />}\n            {renderMenu && <RowMenu rowData={row} />}\n            {renderPopover && <RowPopover rowData={row} />}\n            {renderTooltip && <RowTooltip rowData={row} />}\n          </span>\n        </div>\n      ))}\n    </div>\n  );\n}\n\nfunction RowMenu({ rowData }: RowProps) {\n  return (\n    <Menu.Root>\n      <Menu.Trigger className={styles.button} data-id={rowData.index}>\n        Menu\n      </Menu.Trigger>\n      <Menu.Portal>\n        <Menu.Positioner sideOffset={8} className={menuDemoStyles.Positioner}>\n          <Menu.Popup className={menuDemoStyles.Popup}>\n            <Menu.Arrow className={menuDemoStyles.Arrow}>\n              <ArrowSvg />\n            </Menu.Arrow>\n            {menuItems.map((item) => (\n              <Menu.Item\n                key={item.index}\n                onClick={() => console.log(`Clicked ${item.label} for ${rowData.label}`)}\n                className={menuDemoStyles.Item}\n              >\n                {item.label} for {rowData.label}\n              </Menu.Item>\n            ))}\n          </Menu.Popup>\n        </Menu.Positioner>\n      </Menu.Portal>\n    </Menu.Root>\n  );\n}\n\nfunction RowPopover({ rowData }: RowProps) {\n  return (\n    <Popover.Root>\n      <Popover.Trigger className={styles.button} data-id={rowData.index}>\n        Popover\n      </Popover.Trigger>\n      <Popover.Portal>\n        <Popover.Positioner sideOffset={8} className={popoverDemoStyles.Positioner}>\n          <Popover.Popup className={popoverDemoStyles.Popup}>\n            <Popover.Arrow className={popoverDemoStyles.Arrow}>\n              <ArrowSvg />\n            </Popover.Arrow>\n            {rowData && <div>Popover for {rowData.label}</div>}\n          </Popover.Popup>\n        </Popover.Positioner>\n      </Popover.Portal>\n    </Popover.Root>\n  );\n}\n\nfunction RowTooltip({ rowData }: RowProps) {\n  return (\n    <Tooltip.Root>\n      <Tooltip.Trigger className={styles.button} data-id={rowData.index}>\n        Tooltip\n      </Tooltip.Trigger>\n      <Tooltip.Portal>\n        <Tooltip.Positioner sideOffset={10}>\n          <Tooltip.Popup className={tooltipDemoStyles.Popup}>\n            <Tooltip.Arrow className={tooltipDemoStyles.Arrow}>\n              <ArrowSvg />\n            </Tooltip.Arrow>\n            Tooltip for {rowData.label}\n          </Tooltip.Popup>\n        </Tooltip.Positioner>\n      </Tooltip.Portal>\n    </Tooltip.Root>\n  );\n}\n\nfunction RowDialog({ rowData }: RowProps) {\n  return (\n    <Dialog.Root>\n      <Dialog.Trigger className={styles.button} data-id={rowData.index}>\n        Dialog\n      </Dialog.Trigger>\n      <Dialog.Portal>\n        <Dialog.Backdrop className={dialogDemoStyles.Backdrop} />\n        <Dialog.Popup className={dialogDemoStyles.Popup}>\n          <Dialog.Title className={dialogDemoStyles.Title}>Dialog</Dialog.Title>\n          <Dialog.Description className={dialogDemoStyles.Description}>\n            Dialog content for {rowData.label}\n          </Dialog.Description>\n          <div className={dialogDemoStyles.Actions}>\n            <Dialog.Close className={dialogDemoStyles.Button}>Close</Dialog.Close>\n          </div>\n        </Dialog.Popup>\n      </Dialog.Portal>\n    </Dialog.Root>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={menuDemoStyles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={menuDemoStyles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={menuDemoStyles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nexport const settingsMetadata: SettingsMetadata<Settings> = {\n  renderDialog: {\n    type: 'boolean',\n    default: true,\n    label: 'Render Dialog',\n  },\n  renderMenu: {\n    type: 'boolean',\n    default: true,\n    label: 'Render Menu',\n  },\n  renderPopover: {\n    type: 'boolean',\n    default: true,\n    label: 'Render Popover',\n  },\n  renderTooltip: {\n    type: 'boolean',\n    default: true,\n    label: 'Render Tooltip',\n  },\n};\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/perf/detached-triggers.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\nimport { Tooltip } from '@base-ui/react/tooltip';\nimport { Popover } from '@base-ui/react/popover';\nimport { Dialog } from '@base-ui/react/dialog';\nimport menuDemoStyles from 'docs/src/app/(docs)/react/components/menu/demos/submenu/css-modules/index.module.css';\nimport tooltipDemoStyles from 'docs/src/app/(docs)/react/components/tooltip/demos/hero/css-modules/index.module.css';\nimport popoverDemoStyles from 'docs/src/app/(docs)/react/components/popover/demos/_index.module.css';\nimport dialogDemoStyles from 'docs/src/app/(docs)/react/components/dialog/demos/_index.module.css';\nimport { SettingsMetadata, useExperimentSettings } from '../_components/SettingsPanel';\nimport styles from './perf.module.css';\nimport PerformanceBenchmark from './utils/benchmark';\n\ninterface Settings {\n  renderDialog: boolean;\n  renderMenu: boolean;\n  renderPopover: boolean;\n  renderTooltip: boolean;\n}\n\ninterface RowData {\n  label: string;\n  index: number;\n}\n\nconst rowCount = 500;\nconst menuItemCount = 50;\n\nconst rows = Array.from({ length: rowCount }).map((_, i) => ({\n  label: `Row ${i + 1}`,\n  index: i + 1,\n}));\n\nconst menuItems = Array.from({ length: menuItemCount }).map((_, i) => ({\n  label: `Menu Item ${i + 1}`,\n  index: i + 1,\n}));\n\nconst rowMenuHandle = Menu.createHandle<RowData>();\nconst genericTooltipHandle = Tooltip.createHandle<RowData>();\nconst rowPopoverHandle = Popover.createHandle<RowData>();\nconst rowDialogHandle = Dialog.createHandle<RowData>();\n\nexport default function PerfExperiment() {\n  return (\n    <div className={styles.container}>\n      <h1>Initial render performance - detached triggers</h1>\n      <PerformanceBenchmark>\n        <TestComponent />\n      </PerformanceBenchmark>\n    </div>\n  );\n}\n\nfunction TestComponent() {\n  const { settings } = useExperimentSettings<Settings>();\n  const { renderMenu, renderTooltip, renderPopover, renderDialog } = settings;\n  return (\n    <div>\n      <div className={styles.rows}>\n        {rows.map((row) => (\n          <div key={row.index} className={styles.row}>\n            <span className={styles.label}>{row.label}</span>\n            <span className={styles.actions}>\n              {renderDialog && (\n                <Dialog.Trigger\n                  handle={rowDialogHandle}\n                  payload={row}\n                  className={styles.button}\n                  data-id={row.index}\n                >\n                  Dialog\n                </Dialog.Trigger>\n              )}\n              {renderMenu && (\n                <Menu.Trigger\n                  handle={rowMenuHandle}\n                  payload={row}\n                  className={styles.button}\n                  data-id={row.index}\n                >\n                  Menu\n                </Menu.Trigger>\n              )}\n              {renderPopover && (\n                <Popover.Trigger\n                  handle={rowPopoverHandle}\n                  payload={row}\n                  className={styles.button}\n                  data-id={row.index}\n                >\n                  Popover\n                </Popover.Trigger>\n              )}\n\n              {renderTooltip && (\n                <Tooltip.Trigger\n                  handle={genericTooltipHandle}\n                  payload={row}\n                  className={styles.button}\n                  data-id={row.index}\n                >\n                  Tooltip\n                </Tooltip.Trigger>\n              )}\n            </span>\n          </div>\n        ))}\n      </div>\n      {renderDialog && <RowDialog />}\n      {renderMenu && <RowMenu />}\n      {renderPopover && <RowPopover />}\n      {renderTooltip && <RowTooltip />}\n    </div>\n  );\n}\n\nfunction RowMenu() {\n  return (\n    <Menu.Root handle={rowMenuHandle}>\n      {({ payload: rowData }) => (\n        <Menu.Portal>\n          <Menu.Positioner sideOffset={8} className={menuDemoStyles.Positioner}>\n            <Menu.Popup className={menuDemoStyles.Popup}>\n              <Menu.Arrow className={menuDemoStyles.Arrow}>\n                <ArrowSvg />\n              </Menu.Arrow>\n              {rowData && (\n                <React.Fragment>\n                  {menuItems.map((item) => (\n                    <Menu.Item\n                      key={item.index}\n                      onClick={() => console.log(`Clicked ${item.label} for ${rowData.label}`)}\n                      className={menuDemoStyles.Item}\n                    >\n                      {item.label} for {rowData.label}\n                    </Menu.Item>\n                  ))}\n                </React.Fragment>\n              )}\n            </Menu.Popup>\n          </Menu.Positioner>\n        </Menu.Portal>\n      )}\n    </Menu.Root>\n  );\n}\n\nfunction RowPopover() {\n  return (\n    <Popover.Root handle={rowPopoverHandle}>\n      {({ payload: rowData }) => (\n        <Popover.Portal>\n          <Popover.Positioner sideOffset={8} className={popoverDemoStyles.Positioner}>\n            <Popover.Popup className={popoverDemoStyles.Popup}>\n              <Popover.Arrow className={popoverDemoStyles.Arrow}>\n                <ArrowSvg />\n              </Popover.Arrow>\n              {rowData && <div>Popover for {rowData.label}</div>}\n            </Popover.Popup>\n          </Popover.Positioner>\n        </Popover.Portal>\n      )}\n    </Popover.Root>\n  );\n}\n\nfunction RowTooltip() {\n  return (\n    <Tooltip.Root handle={genericTooltipHandle}>\n      {({ payload: rowData }) =>\n        rowData ? (\n          <Tooltip.Portal>\n            <Tooltip.Positioner sideOffset={10}>\n              <Tooltip.Popup className={tooltipDemoStyles.Popup}>\n                <Tooltip.Arrow className={tooltipDemoStyles.Arrow}>\n                  <ArrowSvg />\n                </Tooltip.Arrow>\n                Tooltip for {rowData.label}\n              </Tooltip.Popup>\n            </Tooltip.Positioner>\n          </Tooltip.Portal>\n        ) : null\n      }\n    </Tooltip.Root>\n  );\n}\n\nfunction RowDialog() {\n  return (\n    <Dialog.Root handle={rowDialogHandle}>\n      {({ payload: rowData }) =>\n        rowData ? (\n          <Dialog.Portal>\n            <Dialog.Backdrop className={dialogDemoStyles.Backdrop} />\n            <Dialog.Popup className={dialogDemoStyles.Popup}>\n              <Dialog.Title className={dialogDemoStyles.Title}>Dialog</Dialog.Title>\n              <Dialog.Description className={dialogDemoStyles.Description}>\n                Dialog content for {rowData.label}\n              </Dialog.Description>\n              <div className={dialogDemoStyles.Actions}>\n                <Dialog.Close className={dialogDemoStyles.Button}>Close</Dialog.Close>\n              </div>\n            </Dialog.Popup>\n          </Dialog.Portal>\n        ) : null\n      }\n    </Dialog.Root>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={menuDemoStyles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={menuDemoStyles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={menuDemoStyles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nexport const settingsMetadata: SettingsMetadata<Settings> = {\n  renderDialog: {\n    type: 'boolean',\n    default: true,\n    label: 'Render Dialog',\n  },\n  renderMenu: {\n    type: 'boolean',\n    default: true,\n    label: 'Render Menu',\n  },\n  renderPopover: {\n    type: 'boolean',\n    default: true,\n    label: 'Render Popover',\n  },\n  renderTooltip: {\n    type: 'boolean',\n    default: true,\n    label: 'Render Tooltip',\n  },\n};\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/perf/perf.module.css",
    "content": ".container {\n  max-width: 700px;\n}\n\n.rows {\n  display: flex;\n  flex-direction: column;\n  margin-top: 16px;\n}\n\n.row {\n  padding: 8px 0;\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  border-bottom: 1px solid var(--color-gray-200);\n}\n\n.actions {\n  display: flex;\n  gap: 8px;\n}\n\n.controls {\n  display: flex;\n  gap: 0.5rem;\n  align-items: baseline;\n}\n\n.button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 1.75rem;\n  padding: 0 0.375rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/perf/radix-triggers.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { DropdownMenu, Tooltip, Popover, Dialog } from 'radix-ui';\nimport menuDemoStyles from 'docs/src/app/(docs)/react/components/menu/demos/submenu/css-modules/index.module.css';\nimport tooltipDemoStyles from 'docs/src/app/(docs)/react/components/tooltip/demos/hero/css-modules/index.module.css';\nimport popoverDemoStyles from 'docs/src/app/(docs)/react/components/popover/demos/_index.module.css';\nimport dialogDemoStyles from 'docs/src/app/(docs)/react/components/dialog/demos/_index.module.css';\nimport { SettingsMetadata, useExperimentSettings } from '../_components/SettingsPanel';\nimport styles from './perf.module.css';\nimport PerformanceBenchmark from './utils/benchmark';\n\ninterface Settings {\n  renderDialog: boolean;\n  renderMenu: boolean;\n  renderPopover: boolean;\n  renderTooltip: boolean;\n}\n\ninterface RowData {\n  label: string;\n  index: number;\n}\n\ninterface RowProps {\n  rowData: RowData;\n}\n\nconst rowCount = 500;\nconst menuItemCount = 50;\n\nconst rows = Array.from({ length: rowCount }).map((_, i) => ({\n  label: `Row ${i + 1}`,\n  index: i + 1,\n}));\n\nconst menuItems = Array.from({ length: menuItemCount }).map((_, i) => ({\n  label: `Menu Item ${i + 1}`,\n  index: i + 1,\n}));\n\nexport default function PerfExperiment() {\n  return (\n    <div className={styles.container}>\n      <h1>Component performance - Radix</h1>\n      <PerformanceBenchmark>\n        <Tooltip.Provider>\n          <TestComponent />\n        </Tooltip.Provider>\n      </PerformanceBenchmark>\n    </div>\n  );\n}\n\nfunction TestComponent() {\n  const { settings } = useExperimentSettings<Settings>();\n  const { renderMenu, renderTooltip, renderPopover, renderDialog } = settings;\n  return (\n    <div className={styles.rows}>\n      {rows.map((row) => (\n        <div key={row.index} className={styles.row}>\n          <span className={styles.label}>{row.label}</span>\n          <span className={styles.actions}>\n            {renderDialog && <RowDialog rowData={row} />}\n            {renderMenu && <RowMenu rowData={row} />}\n            {renderPopover && <RowPopover rowData={row} />}\n            {renderTooltip && <RowTooltip rowData={row} />}\n          </span>\n        </div>\n      ))}\n    </div>\n  );\n}\n\nfunction RowMenu({ rowData }: RowProps) {\n  return (\n    <DropdownMenu.Root>\n      <DropdownMenu.Trigger className={styles.button} data-id={rowData.index}>\n        Menu\n      </DropdownMenu.Trigger>\n      <DropdownMenu.Portal>\n        <DropdownMenu.Content sideOffset={8} className={menuDemoStyles.Popup}>\n          <DropdownMenu.Arrow asChild className={menuDemoStyles.Arrow}>\n            <ArrowSvg />\n          </DropdownMenu.Arrow>\n          {menuItems.map((item) => (\n            <DropdownMenu.Item\n              key={item.index}\n              onSelect={() => console.log(`Clicked ${item.label} for ${rowData.label}`)}\n              className={menuDemoStyles.Item}\n            >\n              {item.label} for {rowData.label}\n            </DropdownMenu.Item>\n          ))}\n        </DropdownMenu.Content>\n      </DropdownMenu.Portal>\n    </DropdownMenu.Root>\n  );\n}\n\nfunction RowPopover({ rowData }: RowProps) {\n  return (\n    <Popover.Root>\n      <Popover.Trigger className={styles.button} data-id={rowData.index}>\n        Popover\n      </Popover.Trigger>\n      <Popover.Portal>\n        <Popover.Content sideOffset={8} className={popoverDemoStyles.Popup}>\n          <Popover.Arrow asChild className={popoverDemoStyles.Arrow}>\n            <ArrowSvg />\n          </Popover.Arrow>\n          {rowData && <div>Details for {rowData.label}</div>}\n        </Popover.Content>\n      </Popover.Portal>\n    </Popover.Root>\n  );\n}\n\nfunction RowTooltip({ rowData }: RowProps) {\n  return (\n    <Tooltip.Root>\n      <Tooltip.Trigger className={styles.button} data-id={rowData.index}>\n        Tooltip\n      </Tooltip.Trigger>\n      <Tooltip.Portal>\n        <Tooltip.Content sideOffset={10} className={tooltipDemoStyles.Popup}>\n          <Tooltip.Arrow asChild className={tooltipDemoStyles.Arrow}>\n            <ArrowSvg />\n          </Tooltip.Arrow>\n          Tooltip for {rowData.label}\n        </Tooltip.Content>\n      </Tooltip.Portal>\n    </Tooltip.Root>\n  );\n}\n\nfunction RowDialog({ rowData }: RowProps) {\n  return (\n    <Dialog.Root>\n      <Dialog.Trigger className={styles.button} data-id={rowData.index}>\n        Dialog\n      </Dialog.Trigger>\n      <Dialog.Portal>\n        <Dialog.Overlay className={dialogDemoStyles.Backdrop} />\n        <Dialog.Content className={dialogDemoStyles.Popup}>\n          <Dialog.Title className={dialogDemoStyles.Title}>Dialog</Dialog.Title>\n          <Dialog.Description className={dialogDemoStyles.Description}>\n            Dialog content for {rowData.label}\n          </Dialog.Description>\n          <div className={dialogDemoStyles.Actions}>\n            <Dialog.Close className={dialogDemoStyles.Button}>Close</Dialog.Close>\n          </div>\n        </Dialog.Content>\n      </Dialog.Portal>\n    </Dialog.Root>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={menuDemoStyles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={menuDemoStyles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={menuDemoStyles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nexport const settingsMetadata: SettingsMetadata<Settings> = {\n  renderDialog: {\n    type: 'boolean',\n    default: true,\n    label: 'Render Dialog',\n  },\n  renderMenu: {\n    type: 'boolean',\n    default: true,\n    label: 'Render Menu',\n  },\n  renderPopover: {\n    type: 'boolean',\n    default: true,\n    label: 'Render Popover',\n  },\n  renderTooltip: {\n    type: 'boolean',\n    default: true,\n    label: 'Render Tooltip',\n  },\n};\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/perf/utils/benchmark.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useTimeout } from '@base-ui/utils/useTimeout';\nimport styles from '../perf.module.css';\n\nconst DOM_SETTLE_QUIET_WINDOW_MS = 32;\n\nconst Controls = React.memo(function Controls(props: {\n  setShowBenchmark: React.Dispatch<React.SetStateAction<boolean>>;\n  benchmarkRootRef: React.RefObject<HTMLDivElement | null>;\n}) {\n  const { setShowBenchmark, benchmarkRootRef } = props;\n  const settleTimeout = useTimeout();\n  const [isRunning, setIsRunning] = React.useState(false);\n  const [shouldRemoveOutliers, setShouldRemoveOutliers] = React.useState(true);\n\n  const measureDomSettled = useStableCallback(() => {\n    const start = performance.now();\n    let lastMutationAt = start;\n    let observer: MutationObserver | null = null;\n    let resolved = false;\n\n    return new Promise<number>((resolve) => {\n      const finish = () => {\n        if (resolved) {\n          return;\n        }\n        resolved = true;\n        observer?.disconnect();\n        settleTimeout.clear();\n        resolve(Math.max(0, lastMutationAt - start));\n      };\n\n      const root = benchmarkRootRef.current;\n\n      if (root) {\n        observer = new MutationObserver(() => {\n          lastMutationAt = performance.now();\n          settleTimeout.start(DOM_SETTLE_QUIET_WINDOW_MS, finish);\n        });\n\n        observer.observe(root, {\n          attributes: true,\n          childList: true,\n          characterData: true,\n          subtree: true,\n        });\n\n        // Resolve once the DOM stays quiet for a small window after the last mutation.\n        settleTimeout.start(DOM_SETTLE_QUIET_WINDOW_MS, finish);\n      } else {\n        finish();\n      }\n\n      ReactDOM.flushSync(() => {\n        setShowBenchmark(true);\n      });\n    });\n  });\n\n  const runBenchmark = useStableCallback(async (iterations: number, warmupIterations: number) => {\n    if (isRunning) {\n      console.warn('Benchmark is already running.');\n      return;\n    }\n\n    setIsRunning(true);\n    console.log(`Running benchmark: ${iterations} iterations (+${warmupIterations} warmup)...`);\n    try {\n      const results = [] as number[];\n\n      for (let i = 0; i < warmupIterations + iterations; i += 1) {\n        ReactDOM.flushSync(() => {\n          setShowBenchmark(false);\n        });\n\n        // eslint-disable-next-line no-await-in-loop\n        const domSettleDuration = await measureDomSettled();\n\n        if (i < warmupIterations) {\n          continue;\n        }\n\n        results.push(Math.round(domSettleDuration * 10) / 10);\n      }\n\n      logResults(shouldRemoveOutliers ? removeOutliers(results) : results);\n    } finally {\n      setIsRunning(false);\n    }\n  });\n\n  return (\n    <div className={styles.controls}>\n      <button\n        type=\"button\"\n        onClick={() => setShowBenchmark((prev: boolean) => !prev)}\n        className={styles.button}\n        disabled={isRunning}\n      >\n        Toggle\n      </button>\n      <button\n        type=\"button\"\n        onClick={() => runBenchmark(10, 5)}\n        className={styles.button}\n        disabled={isRunning}\n      >\n        Run 10\n      </button>\n      <button\n        type=\"button\"\n        onClick={() => runBenchmark(20, 5)}\n        className={styles.button}\n        disabled={isRunning}\n      >\n        Run 20\n      </button>\n      <button\n        type=\"button\"\n        onClick={() => runBenchmark(50, 5)}\n        className={styles.button}\n        disabled={isRunning}\n      >\n        Run 50\n      </button>\n      <label style={{ marginLeft: 8 }}>\n        <input\n          type=\"checkbox\"\n          style={{ marginRight: 4 }}\n          checked={shouldRemoveOutliers}\n          onChange={(ev) => setShouldRemoveOutliers(ev.target.checked)}\n        />\n        Remove outliers\n      </label>\n    </div>\n  );\n});\n\nexport default function PerformanceBenchmark(props: React.PropsWithChildren<{}>) {\n  const [showBenchmark, setShowBenchmark] = React.useState(false);\n  const benchmarkRootRef = React.useRef<HTMLDivElement>(null);\n\n  return (\n    <div>\n      <Controls setShowBenchmark={setShowBenchmark} benchmarkRootRef={benchmarkRootRef} />\n      <div ref={benchmarkRootRef}>{showBenchmark && props.children}</div>\n    </div>\n  );\n}\n\nfunction logResults(results: number[]) {\n  console.log(results);\n  console.log(\n    'Average:',\n    Math.round((results.reduce((a, b) => a + b, 0) / results.length) * 10) / 10,\n  );\n  console.log(\n    'Std Dev:',\n    (() => {\n      const avg = results.reduce((a, b) => a + b, 0) / results.length;\n      const squareDiffs = results.map((value) => {\n        const diff = value - avg;\n        return diff * diff;\n      });\n      const avgSquareDiff = squareDiffs.reduce((a, b) => a + b, 0) / squareDiffs.length;\n      return +Math.sqrt(avgSquareDiff).toFixed(2);\n    })(),\n  );\n}\n\nfunction removeOutliers(data: number[]) {\n  const sortedData = data.slice().sort((a, b) => a - b);\n  const q1Index = Math.floor(sortedData.length / 4);\n  const q3Index = Math.floor((sortedData.length * 3) / 4);\n  const q1 = sortedData[q1Index];\n  const q3 = sortedData[q3Index];\n  const iqr = q3 - q1;\n  const lowerBound = q1 - 1.5 * iqr;\n  const upperBound = q3 + 1.5 * iqr;\n\n  return data.filter((value) => value >= lowerBound && value <= upperBound);\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/popover/calendar-shared.ts",
    "content": "import { Popover } from '@base-ui/react/popover';\n\nexport const eventPopover = Popover.createHandle<EventData>();\n\nexport interface EventData {\n  title: string;\n  dayOfWeek: number;\n  dateString: string;\n  description?: string;\n  location?: string;\n  imageUrl?: string;\n  startTime: number;\n  endTime: number;\n  id: number;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/popover/calendar.module.css",
    "content": ".Page {\n  width: 100%;\n  height: 100dvh;\n  padding: 50px 50px 50px 130px;\n}\n\n.Calendar {\n  position: relative;\n  width: 100%;\n  height: 100%;\n\n  display: grid;\n  grid-template-columns: repeat(5, 1fr);\n  grid-template-rows: 1fr;\n  column-gap: 3px;\n\n  background-size: calc(100% / 5) calc(100% / var(--hours-shown));\n\n  @media (prefers-color-scheme: light) {\n    background-image:\n      linear-gradient(to right, #d5d5d5 1px, transparent 1px),\n      linear-gradient(to bottom, #e2e2e2 1px, transparent 1px);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    background-image:\n      linear-gradient(to right, #303030 1px, transparent 1px),\n      linear-gradient(to bottom, #161616 1px, transparent 1px);\n  }\n}\n\n.HourLabels {\n  position: absolute;\n  text-align: right;\n  top: -0.75em;\n  left: -60px;\n  height: 100%;\n  width: 50px;\n  box-sizing: border-box;\n  font-size: 0.75rem;\n  color: var(--color-gray-500);\n  pointer-events: none;\n  display: grid;\n  grid-template-rows: repeat(var(--hours-shown), 1fr);\n}\n\n.Event {\n  z-index: 10;\n  position: relative;\n  border-radius: 4px;\n  box-sizing: border-box;\n  background: linear-gradient(135deg, rebeccapurple, indigo);\n  padding: 4px;\n  min-height: max(calc(100% / var(--hours-shown) * 0.5), 2em);\n  font-size: 0.875rem;\n  user-select: none;\n  box-shadow:\n    0.5px 1px 0.5px rgb(255 255 255 / 0.2) inset,\n    -0.5px -1px 1px rgb(0 0 0) inset;\n\n  @media (prefers-color-scheme: light) {\n    color: var(--color-gray-50);\n  }\n\n  &:hover {\n    background: linear-gradient(135deg, rgb(107 64 151), indigo);\n  }\n\n  .Title {\n    opacity: 0.9;\n  }\n\n  .Time {\n    position: absolute;\n    bottom: 2px;\n    right: 4px;\n    font-size: 0.75rem;\n    opacity: 0.75;\n  }\n}\n\n.Positioner {\n  z-index: 20;\n  --easing: cubic-bezier(0.22, 1, 0.36, 1);\n  --animation-duration: 0.35s;\n\n  width: var(--positioner-width);\n  height: var(--positioner-height);\n  max-width: var(--available-width);\n\n  transition-property: top, left, right, bottom, transform;\n  transition-timing-function: var(--easing);\n  transition-duration: var(--animation-duration);\n\n  &[data-instant] {\n    transition: none;\n  }\n}\n\n.EventDetails {\n  box-sizing: border-box;\n  padding: 1rem 1.5rem 1.5rem;\n  border-radius: 0.5rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  transform-origin: var(--transform-origin);\n  z-index: 20;\n  overflow: clip;\n  position: relative;\n\n  transition-property: width, height, transform, opacity;\n  transition-timing-function: ease-out;\n  transition-duration: 150ms;\n\n  width: var(--popup-width, auto);\n  height: var(--popup-height, auto);\n  min-width: 350px;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n\n  .Header {\n    display: flex;\n    margin-bottom: 0.5rem;\n    padding-bottom: 0.5rem;\n    border-bottom: 1px solid var(--color-gray-200);\n  }\n\n  .EventTitle {\n    flex-grow: 1;\n    font-weight: 600;\n    font-size: 1.25rem;\n    line-height: 1.2;\n  }\n\n  .EventTime {\n    font-size: 0.875rem;\n    color: var(--color-gray-500);\n    margin-bottom: 0.75rem;\n  }\n\n  .EventImageWrapper {\n    width: 500px;\n    height: 333px;\n  }\n\n  .EventActions {\n    display: flex;\n    gap: 0.5rem;\n  }\n\n  .EventLocation {\n    font-size: 0.875rem;\n    color: var(--color-gray-700);\n    margin-bottom: 0.75rem;\n  }\n\n  .EventDescription {\n    font-size: 0.875rem;\n    color: var(--color-gray-700);\n    margin-bottom: 0.75rem;\n  }\n\n  .EventDescription,\n  .EventLocation,\n  .EventImageWrapper {\n    &:last-child {\n      margin-bottom: 0;\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/popover/calendar.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Popover } from '@base-ui/react';\nimport { Ellipsis, X } from 'lucide-react';\nimport { eventPopover, type EventData } from './calendar-shared';\nimport styles from './calendar.module.css';\n\nconst EVENTS: EventData[] = [\n  {\n    title: 'React Conf 2025 talk',\n    dayOfWeek: 2,\n    startTime: 15.4167,\n    endTime: 15.5833,\n    dateString: 'October 7, 2025',\n    location: 'The Westin Lake Las Vegas Resort & Spa, Main Stage',\n    imageUrl:\n      'https://images.unsplash.com/photo-1626125345510-4603468eedfb?ixlib=rb-4.1.0&q=85&fm=jpg&crop=entropy&cs=srgb&w=1000',\n    id: 1,\n  },\n  {\n    title: 'Booth time',\n    dayOfWeek: 3,\n    startTime: 9.5,\n    endTime: 17,\n    dateString: 'October 8, 2025',\n    location: 'The Westin Lake Las Vegas Resort & Spa, MUI booth',\n    imageUrl:\n      'https://images.unsplash.com/photo-1560439514-4e9645039924?ixlib=rb-4.1.0&q=85&fm=jpg&crop=entropy&cs=srgb&w=1000',\n    id: 2,\n  },\n  {\n    title: 'Chilling out',\n    dayOfWeek: 4,\n    startTime: 9,\n    endTime: 12,\n    id: 3,\n    dateString: 'October 9, 2025',\n    description: 'Pool, spa, and more',\n  },\n];\n\nexport default function CalendarDemo() {\n  return (\n    <div className={styles.Page}>\n      <Calendar startHour={6} endHour={22} events={EVENTS} />\n    </div>\n  );\n}\n\ninterface CalendarProps {\n  startHour?: number;\n  endHour?: number;\n  events: EventData[];\n}\n\nfunction Calendar(props: CalendarProps) {\n  const { startHour = 0, endHour = 24, events } = props;\n  const hoursShown = endHour - startHour;\n\n  return (\n    <React.Fragment>\n      <div\n        className={styles.Calendar}\n        style={{ '--hours-shown': hoursShown } as React.CSSProperties}\n      >\n        <div className={styles.HourLabels}>\n          {[...Array(hoursShown)].map((_, i) => (\n            <div key={i} style={{ '--hour': i } as React.CSSProperties}>\n              {startHour + i}:00\n            </div>\n          ))}\n        </div>\n        {events.map((event) => (\n          <Event\n            key={event.id}\n            event={event}\n            calendarStartHour={startHour}\n            calendarEndHour={endHour}\n          />\n        ))}\n      </div>\n      <EventDetails />\n    </React.Fragment>\n  );\n}\n\ninterface EventProps {\n  event: EventData;\n  calendarStartHour: number;\n  calendarEndHour: number;\n}\n\nfunction Event(props: EventProps) {\n  const { event, calendarStartHour, calendarEndHour } = props;\n  const hoursShown = calendarEndHour - calendarStartHour;\n\n  return (\n    <Popover.Trigger\n      handle={eventPopover}\n      className={styles.Event}\n      payload={event}\n      style={{\n        top: `${(event.startTime - calendarStartHour) * (100 / hoursShown)}%`,\n        height: `${(event.endTime - event.startTime) * (100 / hoursShown)}%`,\n        gridColumn: event.dayOfWeek,\n      }}\n      render={<div />}\n      nativeButton={false}\n    >\n      <span className={styles.Title}>{event.title}</span>\n      <span className={styles.Time}>\n        {formatTime(event.startTime)} - {formatTime(event.endTime)}\n      </span>\n    </Popover.Trigger>\n  );\n}\n\nfunction EventDetails() {\n  return (\n    <Popover.Root handle={eventPopover}>\n      {({ payload }) => {\n        if (!payload) {\n          return null;\n        }\n\n        return (\n          <Popover.Portal>\n            <Popover.Positioner side=\"right\" sideOffset={8} className={styles.Positioner}>\n              <Popover.Popup className={styles.EventDetails}>\n                <div className={styles.Header}>\n                  <Popover.Title className={styles.EventTitle}>{payload.title}</Popover.Title>\n                  <div className={styles.EventActions}>\n                    <button type=\"button\">\n                      <Ellipsis />\n                    </button>\n                    <Popover.Close>\n                      <X />\n                    </Popover.Close>\n                  </div>\n                </div>\n                <p className={styles.EventTime}>\n                  {payload.dateString} &middot; {formatTime(payload.startTime)} -{' '}\n                  {formatTime(payload.endTime)}\n                </p>\n\n                {payload.description && (\n                  <p className={styles.EventDescription}>{payload.description}</p>\n                )}\n                {payload.location && (\n                  <p className={styles.EventLocation}>Location: {payload.location}</p>\n                )}\n                {payload.imageUrl && (\n                  <div className={styles.EventImageWrapper}>\n                    <img\n                      src={payload.imageUrl}\n                      alt={payload.location}\n                      className={styles.EventImage}\n                    />\n                  </div>\n                )}\n              </Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        );\n      }}\n    </Popover.Root>\n  );\n}\n\n// Convert time as number (e.g. 13.5) to string (e.g. \"13:30\")\nfunction formatTime(hour: number) {\n  const h = Math.floor(hour);\n  const m = Math.round((hour - h) * 60);\n  return `${h}:${m.toString().padStart(2, '0')}`;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/popover/dynamic-size.module.css",
    "content": ".Controls {\n  display: flex;\n  gap: 0.5rem;\n  margin: 0.5rem 0;\n}\n\n.Button {\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.125rem;\n  padding: 0.25rem;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/popover/dynamic-size.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Popover } from '@base-ui/react/popover';\nimport styles from 'docs/src/app/(docs)/react/components/popover/demos/detached-triggers-full/css-modules/index.module.css';\nimport localStyles from './dynamic-size.module.css';\n\nconst demoPopover = Popover.createHandle<number>();\n\nexport default function Experiment() {\n  const [payload, setPayload] = React.useState(5);\n\n  return (\n    <div className={styles.Container}>\n      <Popover.Trigger className={styles.IconButton} handle={demoPopover} payload={payload}>\n        <BellIcon aria-label=\"Notifications\" className={styles.Icon} />\n      </Popover.Trigger>\n\n      <Popover.Trigger className={styles.IconButton} handle={demoPopover} payload={5}>\n        <BellIcon aria-label=\"Notifications\" className={styles.Icon} />\n      </Popover.Trigger>\n\n      <Popover.Root handle={demoPopover}>\n        {({ payload: paragraphCount }) => (\n          <Popover.Portal>\n            <Popover.Positioner sideOffset={8} className={styles.Positioner}>\n              <Popover.Popup className={styles.Popup}>\n                <Popover.Arrow className={styles.Arrow}>\n                  <ArrowSvg />\n                </Popover.Arrow>\n\n                <Popover.Viewport className={styles.Viewport}>\n                  <p>Popover</p>\n                  <div className={localStyles.Controls}>\n                    <button\n                      type=\"button\"\n                      onClick={() => setPayload(10)}\n                      className={localStyles.Button}\n                    >\n                      Grow\n                    </button>\n                    <button\n                      type=\"button\"\n                      onClick={() => setPayload(1)}\n                      className={localStyles.Button}\n                    >\n                      Shrink\n                    </button>\n                  </div>\n                  {Array.from({ length: paragraphCount ?? 0 }, (_, i) => i + 1).map((num) => (\n                    <p key={num}>Item {num}</p>\n                  ))}\n                </Popover.Viewport>\n              </Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        )}\n      </Popover.Root>\n    </div>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nfunction BellIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"20\" height=\"20\" viewBox=\"0 0 16 16\" {...props}>\n      <path d=\"M 8 1 C 7.453125 1 7 1.453125 7 2 L 7 3.140625 C 5.28125 3.589844 4 5.144531 4 7 L 4 10.984375 C 4 10.984375 3.984375 11.261719 3.851563 11.519531 C 3.71875 11.78125 3.558594 12 3 12 L 3 13 L 13 13 L 13 12 C 12.40625 12 12.253906 11.78125 12.128906 11.53125 C 12.003906 11.277344 12 11.003906 12 11.003906 L 12 7 C 12 5.144531 10.71875 3.589844 9 3.140625 L 9 2 C 9 1.453125 8.546875 1 8 1 Z M 8 13 C 7.449219 13 7 13.449219 7 14 C 7 14.550781 7.449219 15 8 15 C 8.550781 15 9 14.550781 9 14 C 9 13.449219 8.550781 13 8 13 Z M 8 4 C 9.664063 4 11 5.335938 11 7 L 11 10.996094 C 11 10.996094 10.988281 11.472656 11.234375 11.96875 C 11.238281 11.980469 11.246094 11.988281 11.25 12 L 4.726563 12 C 4.730469 11.992188 4.738281 11.984375 4.742188 11.980469 C 4.992188 11.488281 5 11.015625 5 11.015625 L 5 7 C 5 5.335938 6.335938 4 8 4 Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/popover/nested-open-on-hover.module.css",
    "content": ".IconButton {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 2.5rem;\n  height: 2.5rem;\n  padding: 0;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Icon {\n  width: 1.25rem;\n  height: 1.25rem;\n}\n\n.Positioner {\n  width: var(--positioner-width);\n  height: var(--positioner-height);\n  max-width: var(--available-width);\n}\n\n.Popup {\n  box-sizing: border-box;\n  padding: 1rem 1.5rem;\n  border-radius: 0.5rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n\n  width: var(--popup-width, auto);\n  height: var(--popup-height, auto);\n  max-width: 500px;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Arrow {\n  display: flex;\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n\n.Title {\n  margin: 0;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  font-weight: 500;\n}\n\n.Description {\n  margin: 0;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-600);\n}\n\n.Container {\n  display: flex;\n  gap: 8px;\n  flex-wrap: wrap;\n  justify-content: center;\n}\n\n.Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: 0.375rem;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 500;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/popover/nested-open-on-hover.tsx",
    "content": "import * as React from 'react';\nimport { Popover } from '@base-ui/react/popover';\nimport styles from './nested-open-on-hover.module.css';\n\nexport default function NestedOpenOnHover() {\n  return (\n    <Popover.Root>\n      <Popover.Trigger className={styles.IconButton} openOnHover>\n        <BellIcon aria-label=\"Notifications\" className={styles.Icon} />\n      </Popover.Trigger>\n      <Popover.Portal>\n        <Popover.Positioner sideOffset={8}>\n          <Popover.Popup className={styles.Popup}>\n            <Popover.Root>\n              <Popover.Trigger className={styles.IconButton} openOnHover>\n                <BellIcon aria-label=\"Notifications\" className={styles.Icon} />\n              </Popover.Trigger>\n              <Popover.Portal>\n                <Popover.Positioner sideOffset={8}>\n                  <Popover.Popup className={styles.Popup}>\n                    <Popover.Arrow className={styles.Arrow}>\n                      <ArrowSvg />\n                    </Popover.Arrow>\n                    <Popover.Title className={styles.Title}>Notifications</Popover.Title>\n                    <Popover.Description className={styles.Description}>\n                      You are all caught up. Good job!\n                    </Popover.Description>\n                  </Popover.Popup>\n                </Popover.Positioner>\n              </Popover.Portal>\n            </Popover.Root>\n          </Popover.Popup>\n        </Popover.Positioner>\n      </Popover.Portal>\n    </Popover.Root>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nfunction BellIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"20\" height=\"20\" viewBox=\"0 0 16 16\" {...props}>\n      <path d=\"M 8 1 C 7.453125 1 7 1.453125 7 2 L 7 3.140625 C 5.28125 3.589844 4 5.144531 4 7 L 4 10.984375 C 4 10.984375 3.984375 11.261719 3.851563 11.519531 C 3.71875 11.78125 3.558594 12 3 12 L 3 13 L 13 13 L 13 12 C 12.40625 12 12.253906 11.78125 12.128906 11.53125 C 12.003906 11.277344 12 11.003906 12 11.003906 L 12 7 C 12 5.144531 10.71875 3.589844 9 3.140625 L 9 2 C 9 1.453125 8.546875 1 8 1 Z M 8 13 C 7.449219 13 7 13.449219 7 14 C 7 14.550781 7.449219 15 8 15 C 8.550781 15 9 14.550781 9 14 C 9 13.449219 8.550781 13 8 13 Z M 8 4 C 9.664063 4 11 5.335938 11 7 L 11 10.996094 C 11 10.996094 10.988281 11.472656 11.234375 11.96875 C 11.238281 11.980469 11.246094 11.988281 11.25 12 L 4.726563 12 C 4.730469 11.992188 4.738281 11.984375 4.742188 11.980469 C 4.992188 11.488281 5 11.015625 5 11.015625 L 5 7 C 5 5.335938 6.335938 4 8 4 Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/popover/popovers.module.css",
    "content": ".Page {\n  display: flex;\n  flex-direction: column;\n  padding-bottom: 30vh;\n\n  h2 {\n    margin-bottom: 1rem;\n  }\n}\n\n.Container {\n  display: flex;\n  flex-direction: row;\n  align-items: center;\n  gap: 0;\n  margin-bottom: 32px;\n}\n\n.Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: 0.375rem;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 500;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.IconButton {\n  border-radius: 0;\n}\n\n.IconButton,\n.Button {\n  &:first-of-type {\n    border-radius: 0.375rem 0 0 0.375rem;\n  }\n\n  &:last-of-type {\n    border-radius: 0 0.375rem 0.375rem 0;\n    border-right-style: solid;\n  }\n}\n\n.PopoverSection {\n  margin-top: 0.5rem;\n  margin-bottom: 0.5rem;\n\n  &:not(:last-child) {\n    padding-bottom: 0.5rem;\n    border-bottom: 1px solid var(--color-gray-200);\n  }\n\n  & button {\n    padding: 0.25rem 0.75rem;\n    background-color: var(--color-gray-200);\n    border-radius: 4px;\n    margin-top: 0.5rem;\n\n    &:hover {\n      background-color: var(--color-gray-300);\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/popover/popovers.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Popover } from '@base-ui/react/popover';\nimport demoStyles from 'docs/src/app/(docs)/react/components/popover/demos/detached-triggers-full/css-modules/index.module.css';\nimport { SettingsMetadata, useExperimentSettings } from '../_components/SettingsPanel';\nimport styles from './popovers.module.css';\n\nconst popover1 = Popover.createHandle<number>();\nconst popover2 = Popover.createHandle<number>();\n\ninterface Settings {\n  openOnHover: boolean;\n  delay: number;\n  closeDelay: number;\n  side: 'top' | 'bottom' | 'left' | 'right';\n  modal: boolean;\n  keepMounted: boolean;\n}\n\nconst contents = [\n  <React.Fragment>\n    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>\n    <p>\n      Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,\n      quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n    </p>\n  </React.Fragment>,\n  <p>Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>,\n  <p>Ut enim ad minim veniam, quis nostrud exercitation.</p>,\n  <p>Lorem</p>,\n  <React.Fragment>\n    <p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat.</p>\n    <p>\n      Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim\n      id est laborum.\n    </p>\n    <p>\n      Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla\n      pariatur.\n    </p>\n    <p>\n      Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim\n      id est laborum.\n    </p>\n  </React.Fragment>,\n];\n\nexport default function Popovers() {\n  const { settings } = useExperimentSettings<Settings>();\n\n  const [singleTriggerOpen, setSingleTriggerOpen] = React.useState(false);\n\n  const [controlledWithinRootOpen, setControlledWithinRootOpen] = React.useState(false);\n  const [controlledWithinRootTriggerId, setControlledWithinRootTriggerId] = React.useState<\n    string | null\n  >(null);\n\n  const [controlledDetachedOpen, setControlledDetachedOpen] = React.useState(false);\n  const [controlledDetachedTriggerId, setControlledDetachedTriggerId] = React.useState<\n    string | null\n  >(null);\n\n  return (\n    <div className={styles.Page}>\n      <h1>Popovers</h1>\n      <h2>Uncontrolled, single trigger</h2>\n      <div className={styles.Container}>\n        <Popover.Root modal={settings.modal}>\n          <StyledTrigger />\n          {renderPopoverContent(0, settings)}\n        </Popover.Root>\n        <Popover.Root modal={settings.modal}>\n          <StyledTrigger />\n          {renderPopoverContent(1, settings)}\n        </Popover.Root>\n        <Popover.Root modal={settings.modal}>\n          <StyledTrigger />\n          {renderPopoverContent(2, settings)}\n        </Popover.Root>\n      </div>\n\n      <h2>Controlled, single trigger</h2>\n      <div className={styles.Container}>\n        <Popover.Root\n          open={singleTriggerOpen}\n          onOpenChange={(nextOpen) => setSingleTriggerOpen(nextOpen)}\n          modal={settings.modal}\n        >\n          <StyledTrigger />\n          {renderPopoverContent(0, settings)}\n        </Popover.Root>\n        <button type=\"button\" className={styles.Button} onClick={() => setSingleTriggerOpen(true)}>\n          Open externally\n        </button>\n      </div>\n\n      <h2>Uncontrolled, multiple triggers within Root</h2>\n      <div className={styles.Container}>\n        <Popover.Root modal={settings.modal}>\n          {({ payload }) => (\n            <React.Fragment>\n              <StyledTrigger payload={0} />\n              <StyledTrigger payload={1} />\n              <StyledTrigger payload={2} />\n              {renderPopoverContent(payload as number, settings)}\n            </React.Fragment>\n          )}\n        </Popover.Root>\n      </div>\n\n      <h2>Controlled, multiple triggers within Root</h2>\n      <div className={styles.Container}>\n        <Popover.Root\n          open={controlledWithinRootOpen}\n          onOpenChange={(open, eventDetails) => {\n            setControlledWithinRootOpen(open);\n            setControlledWithinRootTriggerId(eventDetails.trigger?.id ?? null);\n          }}\n          modal={settings.modal}\n          triggerId={controlledWithinRootTriggerId}\n        >\n          {({ payload }) => (\n            <React.Fragment>\n              <StyledTrigger payload={0} />\n              <StyledTrigger payload={1} id=\"within-root-second-trigger\" />\n              <StyledTrigger payload={2} />\n              {renderPopoverContent(payload as number, settings)}\n            </React.Fragment>\n          )}\n        </Popover.Root>\n        <button\n          type=\"button\"\n          className={styles.Button}\n          onClick={() => {\n            setControlledWithinRootOpen(true);\n            setControlledWithinRootTriggerId('within-root-second-trigger');\n          }}\n        >\n          Open externally (2nd trigger)\n        </button>\n      </div>\n\n      <h2>Uncontrolled, detached triggers</h2>\n      <div className={styles.Container}>\n        <StyledPopover handle={popover1} />\n        <StyledTrigger handle={popover1} payload={0} />\n        <StyledTrigger handle={popover1} payload={1} />\n        <StyledTrigger handle={popover1} payload={2} />\n        <StyledTrigger handle={popover1} payload={3} />\n        <StyledTrigger handle={popover1} payload={4} />\n      </div>\n\n      <h2>Controlled, detached triggers</h2>\n      <div className={styles.Container}>\n        <StyledPopover\n          handle={popover2}\n          open={controlledDetachedOpen}\n          triggerId={controlledDetachedTriggerId}\n          onOpenChange={(open, eventDetails) => {\n            setControlledDetachedOpen(open);\n            setControlledDetachedTriggerId(eventDetails.trigger?.id ?? null);\n          }}\n        />\n        <StyledTrigger handle={popover2} payload={0} />\n        <StyledTrigger handle={popover2} payload={1} id=\"detached-second-trigger\" />\n        <StyledTrigger handle={popover2} payload={2} />\n        <StyledTrigger handle={popover2} payload={3} />\n        <StyledTrigger handle={popover2} payload={4} />\n        <button\n          type=\"button\"\n          className={styles.Button}\n          onClick={() => {\n            setControlledDetachedOpen(true);\n            setControlledDetachedTriggerId('detached-second-trigger');\n          }}\n        >\n          Open externally (2nd trigger)\n        </button>\n        <button\n          type=\"button\"\n          className={styles.Button}\n          onClick={() => {\n            popover2.open('detached-second-trigger');\n          }}\n        >\n          Open via handle (2nd trigger)\n        </button>\n      </div>\n    </div>\n  );\n}\n\ntype StyledPopoverProps<Payload> = Pick<\n  Popover.Root.Props<Payload>,\n  'handle' | 'open' | 'onOpenChange' | 'triggerId'\n>;\n\nfunction StyledTrigger<Payload>(\n  props: Popover.Trigger.Props<Payload> & React.RefAttributes<HTMLButtonElement>,\n) {\n  const { settings } = useExperimentSettings<Settings>();\n  return (\n    <Popover.Trigger\n      className={`${demoStyles.IconButton} ${styles.IconButton}`}\n      openOnHover={settings.openOnHover}\n      delay={settings.delay}\n      closeDelay={settings.closeDelay}\n      {...props}\n    >\n      <PopupIcon aria-label=\"Notifications\" className={demoStyles.Icon} />\n    </Popover.Trigger>\n  );\n}\n\nfunction StyledPopover(props: StyledPopoverProps<number>) {\n  const { handle, open, onOpenChange, triggerId } = props;\n  const { settings } = useExperimentSettings<Settings>();\n\n  return (\n    <Popover.Root\n      handle={handle}\n      open={open}\n      onOpenChange={onOpenChange}\n      triggerId={triggerId}\n      modal={settings.modal}\n    >\n      {({ payload }) => renderPopoverContent(payload, settings)}\n    </Popover.Root>\n  );\n}\n\nfunction renderPopoverContent(contentIndex: number | undefined, settings: Settings) {\n  return (\n    <Popover.Portal keepMounted={settings.keepMounted}>\n      <Popover.Positioner sideOffset={8} className={demoStyles.Positioner} side={settings.side}>\n        <Popover.Popup className={demoStyles.Popup}>\n          <Popover.Arrow className={demoStyles.Arrow}>\n            <ArrowSvg />\n          </Popover.Arrow>\n          <Popover.Viewport className={demoStyles.Viewport}>\n            {contentIndex !== undefined ? (\n              <React.Fragment>\n                <Popover.Title className={demoStyles.Title}>Popover {contentIndex}</Popover.Title>\n                <div>\n                  <div className={styles.PopoverSection}>{contents[contentIndex]}</div>\n\n                  <div className={styles.PopoverSection}>\n                    <StatefulComponent key={contentIndex} />\n                  </div>\n                </div>\n              </React.Fragment>\n            ) : (\n              <p className={styles.PopoverSection}>No content for this trigger.</p>\n            )}\n          </Popover.Viewport>\n        </Popover.Popup>\n      </Popover.Positioner>\n    </Popover.Portal>\n  );\n}\n\nexport const settingsMetadata: SettingsMetadata<Settings> = {\n  openOnHover: {\n    type: 'boolean',\n    label: 'Open on hover',\n  },\n  delay: {\n    type: 'number',\n    label: 'Delay',\n    default: 200,\n  },\n  closeDelay: {\n    type: 'number',\n    label: 'Close Delay',\n    default: 0,\n  },\n  side: {\n    type: 'string',\n    label: 'Side',\n    options: ['top', 'bottom', 'left', 'right'],\n    default: 'bottom',\n  },\n  modal: {\n    type: 'boolean',\n    label: 'Modal',\n    default: false,\n  },\n  keepMounted: {\n    type: 'boolean',\n    label: 'Keep mounted',\n    default: false,\n  },\n};\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={demoStyles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={demoStyles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={demoStyles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nfunction PopupIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n      height=\"24px\"\n      viewBox=\"0 -960 960 960\"\n      width=\"24px\"\n      fill=\"#606060\"\n    >\n      <path d=\"M480-80 373-240H160q-33 0-56.5-23.5T80-320v-480q0-33 23.5-56.5T160-880h640q33 0 56.5 23.5T880-800v480q0 33-23.5 56.5T800-240H587L480-80Zm0-144 64-96h256v-480H160v480h256l64 96Zm0-336Z\" />\n    </svg>\n  );\n}\n\nfunction StatefulComponent() {\n  const [state, setState] = React.useState('');\n\n  return (\n    <div>\n      <input\n        type=\"text\"\n        value={state}\n        onChange={(event) => setState(event.target.value)}\n        placeholder=\"Local state\"\n      />\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/popover/vertical-shared.ts",
    "content": "import { Popover } from '@base-ui/react/popover';\n\nexport const demoPopover = Popover.createHandle<React.ComponentType>();\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/popover/vertical.module.css",
    "content": ".IconButton {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 2.5rem;\n  height: 2.5rem;\n  padding: 0;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  color: var(--color-gray-900);\n  user-select: none;\n  font-size: 1rem;\n  font-weight: bold;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Icon {\n  width: 1.25rem;\n  height: 1.25rem;\n}\n\n.Positioner {\n  --easing: var(--ease-out-fast);\n  --animation-duration: 0.35s;\n\n  width: var(--positioner-width);\n  height: var(--positioner-height);\n  max-width: var(--available-width);\n\n  transition-property: top, left, right, bottom, transform;\n  transition-timing-function: var(--easing);\n  transition-duration: var(--animation-duration);\n\n  /* Disable transitions when data-instant is set (used for the initial positioning of the popup) */\n  &[data-instant] {\n    transition: none;\n  }\n}\n\n.Popup {\n  position: relative;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  border-radius: 0.5rem;\n  transform-origin: var(--transform-origin);\n\n  /* These are required to make the size animations work */\n  width: var(--popup-width, auto);\n  height: var(--popup-height, auto);\n\n  max-width: 500px;\n\n  /* width and height are essential for the resize animation; opacity and transform handle the enter/exit animation */\n  transition-property: width, height, opacity, transform;\n  transition-timing-function: var(--easing);\n  transition-duration: var(--animation-duration);\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  &[data-instant] {\n    transition: none;\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Viewport {\n  --viewport-inline-padding: 1.5rem;\n  /* Required to clip the overflowing content during the slide in/out animations */\n  position: relative;\n  overflow: clip;\n  width: 100%;\n  height: 100%;\n  padding: 1rem var(--viewport-inline-padding);\n\n  [data-previous],\n  [data-current] {\n    /* This freezes the width of the content while transitioning.\n       Width is set to the content area of the parent popup (--popup-width measures the border-box).\n       The 'previous` container received the width of the previous content, while the `next` container\n        receives the width of the new content.\n    */\n    width: calc(var(--popup-width) - 2 * var(--viewport-inline-padding));\n    transform: translateX(0);\n    opacity: 1;\n    transition:\n      transform var(--animation-duration) var(--easing),\n      opacity calc(var(--animation-duration) / 2) var(--easing);\n  }\n\n  &[data-activation-direction~='down'] {\n    [data-previous][data-ending-style] {\n      transform: translateY(-50%);\n      opacity: 0;\n    }\n\n    [data-current][data-starting-style] {\n      transform: translateY(50%);\n      opacity: 0;\n    }\n  }\n\n  &[data-activation-direction~='up'] {\n    [data-previous][data-ending-style] {\n      transform: translateY(50%);\n      opacity: 0;\n    }\n\n    [data-current][data-starting-style] {\n      transform: translateY(-50%);\n      opacity: 0;\n    }\n  }\n}\n\n.Arrow {\n  display: flex;\n  transition: left calc(var(--animation-duration)) var(--easing);\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n\n.Title {\n  margin: 0;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  font-weight: 500;\n  margin-bottom: 0.75rem;\n  padding-bottom: 0.75rem;\n  border-bottom: 1px solid var(--color-gray-200);\n}\n\n.Description {\n  margin: 0;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-600);\n}\n\n.Actions {\n  display: flex;\n  justify-content: end;\n  gap: 1rem;\n}\n\n.Container {\n  display: flex;\n  flex-direction: column;\n  gap: 4px;\n  border: 1px solid var(--color-gray-300);\n  border-radius: 10px;\n  background-color: var(--color-gray-50);\n  padding: 4px;\n}\n\n.PropertiesPanel {\n  width: 400px;\n\n  .Fields {\n    display: flex;\n    flex-direction: column;\n    gap: 0.75rem;\n\n    > div {\n      display: flex;\n      gap: 8px;\n      align-items: baseline;\n    }\n\n    label {\n      width: 100px;\n      display: inline-block;\n    }\n\n    select {\n      border: 1px solid var(--color-gray-200);\n      border-radius: 0.375rem;\n      padding: 2px 8px;\n    }\n\n    input {\n      flex-grow: 1;\n    }\n  }\n}\n\n.HistoryPanel {\n  ol {\n    list-style: none;\n    display: flex;\n    flex-direction: column;\n    gap: 1rem;\n  }\n  li {\n    display: flex;\n    gap: 1rem;\n    align-items: center;\n  }\n\n  .UserName {\n    font-weight: 500;\n  }\n\n  .Time {\n    color: var(--color-gray-600);\n  }\n}\n\n.DiscussionPanel {\n  min-width: 400px;\n\n  .NoComments {\n    color: var(--color-gray-600);\n    margin-top: 0;\n    margin-bottom: 1rem;\n  }\n}\n\n.Avatar {\n  grid-column: 1;\n  grid-row: 1 / span 2;\n\n  display: inline-flex;\n  justify-content: center;\n  align-items: center;\n  vertical-align: middle;\n  border-radius: 100%;\n  user-select: none;\n  font-weight: 600;\n  color: var(--color-gray-900);\n  background-color: var(--color-gray-100);\n  font-size: 1rem;\n  line-height: 1;\n  overflow: hidden;\n  height: 3rem;\n  width: 3rem;\n}\n\n.AvatarImage {\n  object-fit: cover;\n  width: 100%;\n  height: 100%;\n}\n\n.TextArea {\n  box-sizing: border-box;\n  width: 100%;\n  min-height: 4rem;\n  padding: 0.5rem;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  resize: vertical;\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 500;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Input {\n  box-sizing: border-box;\n  padding-left: 0.875rem;\n  margin: 0;\n  border: 1px solid var(--color-gray-200);\n  width: 100%;\n  height: 2.5rem;\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  background-color: transparent;\n  color: var(--color-gray-900);\n\n  &:focus {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Textarea {\n  box-sizing: border-box;\n  padding-block: 0.5rem;\n  padding-inline: 0.875rem;\n  margin: 0;\n  border: 1px solid var(--color-gray-200);\n  width: 100%;\n  min-height: 12rem;\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  background-color: transparent;\n  color: var(--color-gray-900);\n\n  &:focus {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/popover/vertical.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Popover } from '@base-ui/react/popover';\nimport { Avatar } from '@base-ui/react/avatar';\nimport { demoPopover } from './vertical-shared';\nimport styles from './vertical.module.css';\n\nexport default function MorphingToolbarDemo() {\n  return (\n    <div className={styles.Container}>\n      <Popover.Trigger\n        className={styles.IconButton}\n        handle={demoPopover}\n        payload={PropertiesPanel}\n        id=\"properties-button\"\n      >\n        <PropertiesIcon aria-label=\"Notifications\" className={styles.Icon} />\n      </Popover.Trigger>\n\n      <Popover.Trigger className={styles.IconButton} handle={demoPopover} payload={HistoryPanel}>\n        <HistoryIcon aria-label=\"Profile\" className={styles.Icon} />\n      </Popover.Trigger>\n\n      <Popover.Trigger className={styles.IconButton} handle={demoPopover} payload={DiscussionPanel}>\n        <DiscussionIcon aria-label=\"Activity\" className={styles.Icon} />\n      </Popover.Trigger>\n\n      <Popover.Root handle={demoPopover}>\n        {({ payload: Payload }) => (\n          <Popover.Portal>\n            <Popover.Positioner\n              sideOffset={12}\n              className={styles.Positioner}\n              side=\"right\"\n              align=\"start\"\n            >\n              <Popover.Popup className={styles.Popup}>\n                <Popover.Arrow className={styles.Arrow}>\n                  <ArrowSvg />\n                </Popover.Arrow>\n\n                <Popover.Viewport className={styles.Viewport}>\n                  {Payload !== undefined && <Payload />}\n                </Popover.Viewport>\n              </Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        )}\n      </Popover.Root>\n    </div>\n  );\n}\n\nfunction PropertiesPanel() {\n  return (\n    <div className={styles.PropertiesPanel}>\n      <Popover.Title className={styles.Title}>Properties</Popover.Title>\n      <div className={styles.Fields}>\n        <div>\n          <label className={styles.Label}>Title</label>\n          <input className={styles.Input} defaultValue=\"React Conf 2025 talk outline\" />\n        </div>\n        <div>\n          <label className={styles.Label}>Visibility</label>\n          <select className={styles.Input} defaultValue=\"Public\">\n            <option value=\"Public\">Public</option>\n            <option value=\"Private\">Private</option>\n          </select>\n        </div>\n      </div>\n    </div>\n  );\n}\n\nfunction HistoryPanel() {\n  return (\n    <div className={styles.HistoryPanel}>\n      <Popover.Title className={styles.Title}>History</Popover.Title>\n      <ol>\n        <li>\n          <Avatar.Root className={styles.Avatar}>\n            <Avatar.Image\n              src=\"https://images.unsplash.com/photo-1543610892-0b1f7e6d8ac1?w=128&h=128&dpr=2&q=80\"\n              width=\"48\"\n              height=\"48\"\n              className={styles.AvatarImage}\n            />\n          </Avatar.Root>\n          <p>\n            <span className={styles.UserName}>Jason Eventon</span> created this document{' '}\n            <span className={styles.Time}>2 weeks ago</span>.\n          </p>\n        </li>\n        <li>\n          <Avatar.Root className={styles.Avatar}>\n            <Avatar.Image\n              src=\"https://images.unsplash.com/photo-1654110455429-cf322b40a906?ixlib=rb-4.1.0&q=85&fm=jpg&crop=entropy&cs=srgb&w=128\"\n              width=\"48\"\n              height=\"48\"\n              className={styles.AvatarImage}\n            />\n          </Avatar.Root>\n          <p>\n            <span className={styles.UserName}>John Doe</span> approved{' '}\n            <span className={styles.Time}>36m ago</span>.\n          </p>\n        </li>\n      </ol>\n    </div>\n  );\n}\n\nfunction DiscussionPanel() {\n  return (\n    <div className={styles.DiscussionPanel}>\n      <Popover.Title className={styles.Title}>Discussion</Popover.Title>\n      <p className={styles.NoComments}>There aren't any comments yet.</p>\n      <textarea className={styles.TextArea} placeholder=\"Write a comment...\" />\n      <div className={styles.Actions}>\n        <button className={styles.Button} type=\"button\">\n          Post\n        </button>\n      </div>\n    </div>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nfunction PropertiesIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <rect width=\"8\" height=\"4\" x=\"8\" y=\"2\" rx=\"1\" ry=\"1\" />\n      <path d=\"M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2\" />\n      <path d=\"M12 11h4\" />\n      <path d=\"M12 16h4\" />\n      <path d=\"M8 11h.01\" />\n      <path d=\"M8 16h.01\" />\n    </svg>\n  );\n}\n\nfunction HistoryIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8\" />\n      <path d=\"M3 3v5h5\" />\n      <path d=\"M12 7v5l4 2\" />\n    </svg>\n  );\n}\n\nfunction DiscussionIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M16 10a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 14.286V4a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z\" />\n      <path d=\"M20 9a2 2 0 0 1 2 2v10.286a.71.71 0 0 1-1.212.502l-2.202-2.202A2 2 0 0 0 17.172 19H10a2 2 0 0 1-2-2v-1\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/popup-tabbing.module.css",
    "content": ".Page {\n  padding: 1.5rem;\n}\n\n.Title {\n  font-size: var(--text-lg);\n  line-height: var(--text-lg--line-height);\n  font-weight: 600;\n  letter-spacing: var(--text-lg--letter-spacing);\n  color: var(--color-gray-900);\n}\n\n.Description {\n  margin-top: 0.25rem;\n  font-size: var(--text-sm);\n  line-height: var(--text-sm--line-height);\n  letter-spacing: var(--text-sm--letter-spacing);\n  color: var(--color-gray-600);\n}\n\n.SwitchLabel {\n  margin-top: 1rem;\n  display: flex;\n  align-items: center;\n  gap: 0.5rem;\n  font-size: var(--text-sm);\n  line-height: var(--text-sm--line-height);\n  font-weight: 500;\n  letter-spacing: var(--text-sm--letter-spacing);\n  color: var(--color-gray-900);\n}\n\n.SwitchRoot {\n  position: relative;\n  display: flex;\n  width: 2.25rem;\n  height: 1.25rem;\n  border-radius: 9999px;\n  background: var(--color-gray-200);\n  padding: 1px;\n  outline: 1px solid var(--color-gray-300);\n  outline-offset: -1px;\n  transition: background-color 150ms;\n}\n\n.SwitchRoot:focus-visible {\n  outline: 2px solid var(--color-blue-800);\n}\n\n.SwitchRoot[data-checked] {\n  background: var(--color-gray-900);\n  outline-color: var(--color-gray-900);\n}\n\n.SwitchThumb {\n  height: 100%;\n  aspect-ratio: 1 / 1;\n  border-radius: 9999px;\n  background: white;\n  transition: transform 150ms;\n}\n\n.SwitchThumb[data-checked] {\n  transform: translateX(1rem);\n}\n\n.Sections {\n  margin-top: 1.5rem;\n}\n\n.Sections > :not([hidden]) ~ :not([hidden]) {\n  margin-top: 2.5rem;\n}\n\n.Section {\n  margin: 0;\n}\n\n.SectionTitle {\n  margin-bottom: 0.75rem;\n  font-size: var(--text-base);\n  font-weight: 600;\n  color: var(--color-gray-900);\n}\n\n.Row {\n  display: flex;\n  flex-wrap: wrap;\n  align-items: center;\n  gap: 0.75rem;\n}\n\n.Button {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background: var(--color-gray-50);\n  padding: 0 0.875rem;\n  font-size: var(--text-base);\n  font-weight: 500;\n  color: var(--color-gray-900);\n}\n\n.Button:hover,\n.Button:active,\n.Button[data-popup-open] {\n  background: var(--color-gray-100);\n}\n\n.Button:focus-visible {\n  outline: 2px solid var(--color-blue-800);\n  outline-offset: -1px;\n}\n\n.PopoverPopupColumn {\n  display: flex;\n  flex-direction: column;\n  gap: 0.75rem;\n  transform-origin: var(--transform-origin);\n  border-radius: 0.5rem;\n  background: canvas;\n  padding: 1rem;\n  color: var(--color-gray-900);\n  outline: 1px solid var(--color-gray-200);\n  box-shadow: var(--shadow-lg);\n  transition:\n    transform 150ms,\n    scale 150ms,\n    opacity 150ms;\n}\n\n.PopoverPopupRow {\n  display: flex;\n  align-items: center;\n  gap: 0.75rem;\n  transform-origin: var(--transform-origin);\n  border-radius: 0.5rem;\n  background: canvas;\n  padding: 1rem;\n  color: var(--color-gray-900);\n  outline: 1px solid var(--color-gray-200);\n  box-shadow: var(--shadow-lg);\n  transition:\n    transform 150ms,\n    scale 150ms,\n    opacity 150ms;\n}\n\n.PopoverPopupColumn[data-starting-style],\n.PopoverPopupColumn[data-ending-style],\n.PopoverPopupRow[data-starting-style],\n.PopoverPopupRow[data-ending-style] {\n  scale: 0.9;\n  opacity: 0;\n}\n\n@media (prefers-color-scheme: dark) {\n  .PopoverPopupColumn,\n  .PopoverPopupRow {\n    box-shadow: none;\n    outline-color: var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.MenuPositioner {\n  outline: none;\n}\n\n.MenuPopup {\n  transform-origin: var(--transform-origin);\n  border-radius: 0.375rem;\n  background: canvas;\n  padding: 0.25rem 0;\n  color: var(--color-gray-900);\n  outline: 1px solid var(--color-gray-200);\n  box-shadow: var(--shadow-lg);\n  transition:\n    transform 150ms,\n    scale 150ms,\n    opacity 150ms;\n}\n\n.MenuPopup[data-starting-style],\n.MenuPopup[data-ending-style] {\n  scale: 0.9;\n  opacity: 0;\n}\n\n.MenuArrow[data-side='bottom'] {\n  top: -8px;\n}\n\n.MenuArrow[data-side='left'] {\n  right: -13px;\n  rotate: 90deg;\n}\n\n.MenuArrow[data-side='right'] {\n  left: -13px;\n  rotate: -90deg;\n}\n\n.MenuArrow[data-side='top'] {\n  bottom: -8px;\n  rotate: 180deg;\n}\n\n.MenuItem {\n  position: relative;\n  z-index: 0;\n  display: flex;\n  cursor: default;\n  padding: 0.5rem 2rem 0.5rem 1rem;\n  font-size: var(--text-sm);\n  line-height: 1rem;\n  user-select: none;\n  outline: none;\n}\n\n.MenuItem[data-highlighted] {\n  color: var(--color-gray-50);\n}\n\n.MenuItem[data-highlighted]::before {\n  content: '';\n  position: absolute;\n  inset: 0 0.25rem;\n  z-index: -1;\n  border-radius: 0.25rem;\n  background: var(--color-gray-900);\n}\n\n.MenuSeparator {\n  margin: 0.375rem 1rem;\n  height: 1px;\n  background: var(--color-gray-200);\n}\n\n.ComboboxField {\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  gap: 0.25rem;\n  font-size: var(--text-sm);\n  line-height: var(--text-sm--line-height);\n  font-weight: 500;\n  color: var(--color-gray-900);\n}\n\n.ComboboxInputWrap {\n  position: relative;\n}\n\n.ComboboxInput {\n  height: 2.5rem;\n  width: 16rem;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background: canvas;\n  padding-left: 0.875rem;\n  padding-right: 2rem;\n  font-size: var(--text-base);\n  color: var(--color-gray-900);\n}\n\n.ComboboxInput:focus {\n  outline: 2px solid var(--color-blue-800);\n  outline-offset: -1px;\n}\n\n.ComboboxActions {\n  position: absolute;\n  right: 0.5rem;\n  bottom: 0;\n  display: flex;\n  height: 2.5rem;\n  align-items: center;\n  justify-content: center;\n  color: var(--color-gray-600);\n}\n\n.ComboboxTriggerButton {\n  display: flex;\n  height: 2.5rem;\n  width: 1.5rem;\n  align-items: center;\n  justify-content: center;\n  border-radius: 0.25rem;\n  background: transparent;\n  padding: 0;\n}\n\n.ComboboxIcon {\n  width: 1rem;\n  height: 1rem;\n}\n\n.ComboboxPopup {\n  width: var(--anchor-width);\n  max-width: var(--available-width);\n  max-height: min(var(--available-height), 23rem);\n  overflow-y: auto;\n  overscroll-behavior: contain;\n  scroll-padding: 0.5rem;\n  transform-origin: var(--transform-origin);\n  border-radius: 0.375rem;\n  background: canvas;\n  padding: 0.5rem 0;\n  color: var(--color-gray-900);\n  outline: 1px solid var(--color-gray-200);\n  box-shadow: var(--shadow-lg);\n  transition:\n    transform 150ms,\n    scale 150ms,\n    opacity 150ms;\n}\n\n.ComboboxPopup[data-starting-style],\n.ComboboxPopup[data-ending-style] {\n  scale: 0.95;\n  opacity: 0;\n}\n\n.ComboboxEmpty {\n  padding: 0.5rem 1rem;\n  font-size: 0.925rem;\n  line-height: 1rem;\n  color: var(--color-gray-600);\n}\n\n.ComboboxEmpty:empty {\n  margin: 0;\n  padding: 0;\n}\n\n.ComboboxItem {\n  position: relative;\n  z-index: 0;\n  display: flex;\n  cursor: default;\n  padding: 0.5rem 2rem 0.5rem 1rem;\n  font-size: var(--text-base);\n  line-height: 1rem;\n  user-select: none;\n  outline: none;\n}\n\n.ComboboxItem[data-highlighted] {\n  color: var(--color-gray-50);\n}\n\n.ComboboxItem[data-highlighted]::before {\n  content: '';\n  position: absolute;\n  inset: 0 0.5rem;\n  z-index: -1;\n  border-radius: 0.25rem;\n  background: var(--color-gray-900);\n}\n\n.InnerTitle {\n  font-size: var(--text-sm);\n  font-weight: 500;\n}\n\n.InnerDescription {\n  margin-top: 0.25rem;\n  font-size: var(--text-sm);\n  color: var(--color-gray-600);\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowStrokeLight {\n  fill: var(--color-gray-200);\n}\n\n.ArrowStrokeDark {\n  fill: transparent;\n}\n\n@media (prefers-color-scheme: dark) {\n  .ArrowStrokeDark {\n    fill: var(--color-gray-300);\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/popup-tabbing.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Combobox } from '@base-ui/react/combobox';\nimport { Menu } from '@base-ui/react/menu';\nimport { Popover } from '@base-ui/react/popover';\nimport { Switch } from '@base-ui/react/switch';\nimport styles from './popup-tabbing.module.css';\n\nfunction ExampleMenu() {\n  return (\n    <Menu.Root>\n      <Menu.Trigger openOnHover className={styles.Button}>\n        Song <ChevronDownIcon />\n      </Menu.Trigger>\n      <Menu.Portal>\n        <Menu.Positioner className={styles.MenuPositioner} sideOffset={8}>\n          <Menu.Popup className={styles.MenuPopup}>\n            <Menu.Arrow className={styles.MenuArrow}>\n              <ArrowSvg />\n            </Menu.Arrow>\n            <Menu.Item className={styles.MenuItem}>Add to Library</Menu.Item>\n            <Menu.Item className={styles.MenuItem}>Add to Playlist</Menu.Item>\n            <Menu.Separator className={styles.MenuSeparator} />\n            <Menu.Item className={styles.MenuItem}>Play Next</Menu.Item>\n            <Menu.Item className={styles.MenuItem}>Play Last</Menu.Item>\n            <Menu.Separator className={styles.MenuSeparator} />\n            <Menu.Item className={styles.MenuItem}>Favorite</Menu.Item>\n            <Menu.Item className={styles.MenuItem}>Share</Menu.Item>\n          </Menu.Popup>\n        </Menu.Positioner>\n      </Menu.Portal>\n    </Menu.Root>\n  );\n}\n\nfunction ExamplePopoverInPopover() {\n  return (\n    <Popover.Root>\n      <Popover.Trigger className={styles.Button}>Inner popover</Popover.Trigger>\n      <Popover.Portal>\n        <Popover.Positioner sideOffset={8}>\n          <Popover.Popup className={styles.PopoverPopupColumn}>\n            <div className={styles.InnerTitle}>Inner content</div>\n            <div className={styles.InnerDescription}>Try Tab / Shift+Tab out of here.</div>\n            <button type=\"button\" className={styles.Button}>\n              Inner button\n            </button>\n          </Popover.Popup>\n        </Popover.Positioner>\n      </Popover.Portal>\n    </Popover.Root>\n  );\n}\n\nconst fruits = ['Apple', 'Banana', 'Orange', 'Grape', 'Mango', 'Strawberry'];\n\nfunction ExampleCombobox() {\n  const id = React.useId();\n\n  return (\n    <Combobox.Root items={fruits}>\n      <div className={styles.ComboboxField}>\n        <label htmlFor={id}>Choose a fruit</label>\n        <div className={styles.ComboboxInputWrap}>\n          <Combobox.Input placeholder=\"e.g. Apple\" id={id} className={styles.ComboboxInput} />\n          <div className={styles.ComboboxActions}>\n            <Combobox.Trigger className={styles.ComboboxTriggerButton} aria-label=\"Open popup\">\n              <ChevronDownIcon className={styles.ComboboxIcon} />\n            </Combobox.Trigger>\n          </div>\n        </div>\n      </div>\n\n      <Combobox.Portal>\n        <Combobox.Positioner className={styles.MenuPositioner} sideOffset={4}>\n          <Combobox.Popup className={styles.ComboboxPopup}>\n            <Combobox.Empty className={styles.ComboboxEmpty}>No fruits found.</Combobox.Empty>\n            <Combobox.List>\n              {(item: string) => (\n                <Combobox.Item key={item} value={item} className={styles.ComboboxItem}>\n                  {item}\n                </Combobox.Item>\n              )}\n            </Combobox.List>\n          </Combobox.Popup>\n        </Combobox.Positioner>\n      </Combobox.Portal>\n    </Combobox.Root>\n  );\n}\n\nexport default function PopupTabbing() {\n  const [renderInsideButtons, setRenderInsideButtons] = React.useState(true);\n\n  return (\n    <div className={styles.Page}>\n      <h1 className={styles.Title}>popup-tabbing</h1>\n      <p className={styles.Description}>\n        Repros for tab order and focus handoff between nested popups.\n      </p>\n\n      <label className={styles.SwitchLabel}>\n        <Switch.Root\n          checked={renderInsideButtons}\n          onCheckedChange={setRenderInsideButtons}\n          className={styles.SwitchRoot}\n        >\n          <Switch.Thumb className={styles.SwitchThumb} />\n        </Switch.Root>\n        Render inside before/after buttons\n      </label>\n\n      <div className={styles.Sections}>\n        <section className={styles.Section}>\n          <h2 className={styles.SectionTitle}>Combobox inside Popover</h2>\n          <div className={styles.Row}>\n            <button type=\"button\" className={styles.Button}>\n              Outside before\n            </button>\n\n            <Popover.Root>\n              <Popover.Trigger className={styles.Button}>Outer popover</Popover.Trigger>\n              <Popover.Portal>\n                <Popover.Positioner sideOffset={8}>\n                  <Popover.Popup className={styles.PopoverPopupColumn}>\n                    {renderInsideButtons ? (\n                      <button type=\"button\" className={styles.Button}>\n                        Inside before\n                      </button>\n                    ) : null}\n\n                    <ExampleCombobox />\n\n                    {renderInsideButtons ? (\n                      <button type=\"button\" className={styles.Button}>\n                        Inside after\n                      </button>\n                    ) : null}\n                  </Popover.Popup>\n                </Popover.Positioner>\n              </Popover.Portal>\n            </Popover.Root>\n\n            <button type=\"button\" className={styles.Button}>\n              Outside after\n            </button>\n          </div>\n        </section>\n\n        <section className={styles.Section}>\n          <h2 className={styles.SectionTitle}>Menu inside Popover</h2>\n          <div className={styles.Row}>\n            <button type=\"button\" className={styles.Button}>\n              Outside before\n            </button>\n\n            <Popover.Root>\n              <Popover.Trigger className={styles.Button}>Outer popover</Popover.Trigger>\n              <Popover.Portal>\n                <Popover.Positioner sideOffset={8}>\n                  <Popover.Popup className={styles.PopoverPopupRow}>\n                    {renderInsideButtons ? (\n                      <button type=\"button\" className={styles.Button}>\n                        Inside before\n                      </button>\n                    ) : null}\n                    <ExampleMenu />\n                    {renderInsideButtons ? (\n                      <button type=\"button\" className={styles.Button}>\n                        Inside after\n                      </button>\n                    ) : null}\n                  </Popover.Popup>\n                </Popover.Positioner>\n              </Popover.Portal>\n            </Popover.Root>\n\n            <button type=\"button\" className={styles.Button}>\n              Outside after\n            </button>\n          </div>\n        </section>\n\n        <section className={styles.Section}>\n          <h2 className={styles.SectionTitle}>Popover inside Popover</h2>\n          <div className={styles.Row}>\n            <button type=\"button\" className={styles.Button}>\n              Outside before\n            </button>\n\n            <Popover.Root>\n              <Popover.Trigger className={styles.Button}>Outer popover</Popover.Trigger>\n              <Popover.Portal>\n                <Popover.Positioner sideOffset={8}>\n                  <Popover.Popup className={styles.PopoverPopupColumn}>\n                    {renderInsideButtons ? (\n                      <button type=\"button\" className={styles.Button}>\n                        Inside before\n                      </button>\n                    ) : null}\n                    <ExamplePopoverInPopover />\n                    {renderInsideButtons ? (\n                      <button type=\"button\" className={styles.Button}>\n                        Inside after\n                      </button>\n                    ) : null}\n                  </Popover.Popup>\n                </Popover.Positioner>\n              </Popover.Portal>\n            </Popover.Root>\n\n            <button type=\"button\" className={styles.Button}>\n              Outside after\n            </button>\n          </div>\n        </section>\n      </div>\n    </div>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowStrokeLight}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowStrokeDark}\n      />\n    </svg>\n  );\n}\n\nfunction ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M1 3.5L5 7.5L9 3.5\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/popups/popups-in-popups.module.css",
    "content": ".Launchers {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 12px;\n}\n\n.PopupGrid {\n  display: grid;\n  grid-template-columns: repeat(3, minmax(0, 1fr));\n  gap: 12px;\n  align-items: start;\n\n  @media (max-width: 720px) {\n    grid-template-columns: 1fr;\n  }\n}\n\n.NestedTriggerRow {\n  margin-top: 16px;\n}\n\n.Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: 0.375rem;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 500;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.ButtonIcon {\n  display: block;\n  width: 0.625rem;\n  height: 0.625rem;\n  flex: none;\n  margin-right: -0.25rem;\n}\n\n.DialogBackdrop {\n  position: fixed;\n  min-height: 100dvh;\n  inset: 0;\n  background-color: black;\n  opacity: 0.2;\n  transition: opacity 150ms cubic-bezier(0.45, 1.005, 0, 1.005);\n  touch-action: none;\n  backdrop-filter: blur(4px);\n\n  @supports (-webkit-touch-callout: none) {\n    position: absolute;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    opacity: 0.7;\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n  }\n}\n\n.DialogPopup {\n  box-sizing: border-box;\n  position: fixed;\n  top: 50%;\n  left: 50%;\n  transform: translate(-50%, -50%);\n  width: min(52rem, calc(100vw - 3rem));\n  margin-top: -2rem;\n  padding: 1.5rem;\n  border-radius: 0.5rem;\n  outline: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-50);\n  color: var(--color-gray-900);\n  transition: all 150ms;\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: translate(-50%, -50%) scale(0.9);\n  }\n}\n\n.DialogTitle {\n  margin-top: -0.375rem;\n  margin-bottom: 0.25rem;\n  font-size: 1.125rem;\n  line-height: 1.75rem;\n  letter-spacing: -0.0025em;\n  font-weight: 500;\n}\n\n.DialogDescription {\n  margin: 0 0 1.5rem;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-600);\n}\n\n.DialogControls {\n  display: flex;\n  justify-content: flex-end;\n  gap: 1rem;\n  margin-top: 1.5rem;\n}\n\n.DrawerBackdrop {\n  --backdrop-opacity: 0.2;\n  position: fixed;\n  min-height: 100dvh;\n  inset: 0;\n  background-color: black;\n  opacity: calc(var(--backdrop-opacity) * (1 - var(--drawer-swipe-progress)));\n  transition-duration: 450ms;\n  transition-property: opacity;\n  transition-timing-function: cubic-bezier(0.32, 0.72, 0, 1);\n\n  @supports (-webkit-touch-callout: none) {\n    position: absolute;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    --backdrop-opacity: 0.7;\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n  }\n\n  &[data-swiping] {\n    transition-duration: 0ms;\n  }\n\n  &[data-ending-style] {\n    transition-duration: calc(var(--drawer-swipe-strength) * 400ms);\n  }\n}\n\n.DrawerViewport {\n  position: fixed;\n  inset: 0;\n  display: flex;\n  align-items: flex-end;\n  justify-content: center;\n}\n\n.DrawerPopup {\n  --bleed: 3rem;\n  box-sizing: border-box;\n  width: 100%;\n  max-height: calc(80vh + var(--bleed));\n  margin-bottom: calc(-1 * var(--bleed));\n  padding: 1rem 1.5rem 1.5rem;\n  padding-bottom: calc(1.5rem + env(safe-area-inset-bottom, 0px) + var(--bleed));\n  border-radius: 1rem 1rem 0 0;\n  outline: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-50);\n  color: var(--color-gray-900);\n  overflow-y: auto;\n  overscroll-behavior: contain;\n  transition: transform 450ms cubic-bezier(0.32, 0.72, 0, 1);\n  will-change: transform;\n  transform: translateY(var(--drawer-swipe-movement-y));\n\n  &[data-swiping] {\n    user-select: none;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    transform: translateY(calc(100% - var(--bleed) + 2px));\n  }\n\n  &[data-ending-style] {\n    transition-duration: calc(var(--drawer-swipe-strength) * 400ms);\n  }\n}\n\n.DrawerHandle {\n  width: 3rem;\n  height: 0.25rem;\n  margin: 0 auto 1rem;\n  border-radius: 9999px;\n  background-color: var(--color-gray-300);\n}\n\n.DrawerContent {\n  width: 100%;\n  max-width: 48rem;\n  margin: 0 auto;\n}\n\n.DrawerTitle {\n  margin-top: 0;\n  margin-bottom: 0.25rem;\n  font-size: 1.125rem;\n  line-height: 1.75rem;\n  letter-spacing: -0.0025em;\n  font-weight: 500;\n  text-align: center;\n}\n\n.DrawerDescription {\n  margin: 0 0 1.5rem;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-600);\n  text-align: center;\n}\n\n.DrawerControls {\n  display: flex;\n  justify-content: center;\n  gap: 1rem;\n  margin-top: 1.5rem;\n}\n\n.Field {\n  display: flex;\n  flex-direction: column;\n  align-items: start;\n  gap: 0.25rem;\n}\n\n.Label {\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 500;\n  color: var(--color-gray-900);\n  cursor: default;\n}\n\n.Value[data-placeholder] {\n  opacity: 0.6;\n}\n\n.Select {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 0.75rem;\n  height: 2.5rem;\n  padding-left: 0.875rem;\n  padding-right: 0.75rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: canvas;\n  font-family: inherit;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n  min-width: 10rem;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.SelectIcon {\n  display: flex;\n}\n\n.SelectPositioner {\n  outline: none;\n  z-index: 1;\n  user-select: none;\n}\n\n.SelectPopup {\n  box-sizing: border-box;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  background-clip: padding-box;\n  color: var(--color-gray-900);\n  min-width: var(--anchor-width);\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  &[data-side='none'] {\n    transition: none;\n    transform: none;\n    opacity: 1;\n    min-width: calc(var(--anchor-width) + 1rem);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n  }\n}\n\n.SelectList {\n  box-sizing: border-box;\n  position: relative;\n  padding-block: 0.25rem;\n  overflow-y: auto;\n  max-height: var(--available-height);\n  scroll-padding-block: 1.5rem;\n}\n\n.SelectItem {\n  box-sizing: border-box;\n  outline: 0;\n  font-size: 0.875rem;\n  line-height: 1rem;\n  padding-block: 0.5rem;\n  padding-left: 0.625rem;\n  padding-right: 1rem;\n  display: grid;\n  gap: 0.5rem;\n  align-items: center;\n  grid-template-columns: 0.75rem 1fr;\n  cursor: default;\n  user-select: none;\n\n  @media (pointer: coarse) {\n    padding-block: 0.625rem;\n    font-size: 0.925rem;\n  }\n\n  [data-side='none'] & {\n    font-size: 1rem;\n    padding-right: 3rem;\n  }\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.25rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.SelectItemIndicator {\n  grid-column-start: 1;\n}\n\n.SelectItemIndicatorIcon {\n  display: block;\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.SelectItemText {\n  grid-column-start: 2;\n}\n\n.SelectScrollArrow {\n  width: 100%;\n  background: canvas;\n  z-index: 1;\n  text-align: center;\n  cursor: default;\n  border-radius: 0.375rem;\n  height: 1rem;\n  font-size: 0.75rem;\n}\n\n.MenuPositioner {\n  outline: 0;\n}\n\n.MenuPopup {\n  box-sizing: border-box;\n  max-height: var(--available-height);\n  overflow-y: auto;\n  padding-block: 0.25rem;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.MenuArrow {\n  display: flex;\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n\n.MenuItem {\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  padding-block: 0.5rem;\n  padding-left: 1rem;\n  padding-right: 2rem;\n  display: flex;\n  font-size: 0.875rem;\n  line-height: 1rem;\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.25rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n\n  &[data-disabled] {\n    color: var(--color-gray-500);\n  }\n}\n\n.SubmenuTrigger {\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  padding-block: 0.5rem;\n  padding-left: 1rem;\n  padding-right: 2rem;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 1rem;\n  font-size: 0.875rem;\n  line-height: 1rem;\n\n  &::after {\n    content: '›';\n  }\n\n  &[data-popup-open],\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-popup-open]::before,\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.25rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.ComboboxLabel {\n  display: flex;\n  flex-direction: column;\n  gap: 0.25rem;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 500;\n  color: var(--color-gray-900);\n  position: relative;\n}\n\n.ComboboxInputGroup {\n  position: relative;\n  width: min(100%, 16rem);\n  height: 2.5rem;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: canvas;\n\n  &:focus-within {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.ComboboxInput {\n  box-sizing: border-box;\n  width: 100%;\n  height: 100%;\n  margin: 0;\n  border: none;\n  border-radius: inherit;\n  padding-left: 0.875rem;\n  padding-right: calc(0.5rem + 1.5rem * 2);\n  font-family: inherit;\n  font-size: 1rem;\n  background-color: transparent;\n  color: var(--color-gray-900);\n\n  &:focus {\n    outline: none;\n  }\n}\n\n.ComboboxActionButtons {\n  box-sizing: border-box;\n  position: absolute;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  bottom: 0;\n  right: 0.5rem;\n  height: 2.5rem;\n  border-radius: 0.25rem;\n  border: none;\n  color: var(--color-gray-600);\n  padding: 0;\n}\n\n.ComboboxTrigger,\n.ComboboxClear {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 1.5rem;\n  height: 2.5rem;\n  color: var(--color-gray-600);\n  border: none;\n  padding: 0;\n  border-radius: 0.25rem;\n  background: none;\n}\n\n.ComboboxClearIcon,\n.ComboboxTriggerIcon {\n  width: 1rem;\n  height: 1rem;\n}\n\n.ComboboxPositioner {\n  outline: 0;\n}\n\n.ComboboxPopup {\n  box-sizing: border-box;\n  width: var(--anchor-width);\n  max-width: var(--available-width);\n  border-radius: 0.375rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  transition:\n    opacity 0.1s,\n    transform 0.1s;\n  transform-origin: var(--transform-origin);\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.95);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.ComboboxEmpty:not(:empty) {\n  font-size: 0.925rem;\n  line-height: 1rem;\n  color: var(--color-gray-600);\n  padding: 1rem;\n}\n\n.ComboboxList {\n  box-sizing: border-box;\n  overflow-y: auto;\n  overscroll-behavior: contain;\n  padding-block: 0.5rem;\n  scroll-padding-block: 0.5rem;\n  outline: 0;\n  max-height: min(23rem, var(--available-height));\n\n  &[data-empty] {\n    padding: 0;\n  }\n}\n\n.ComboboxItem {\n  box-sizing: border-box;\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  padding-block: 0.5rem;\n  padding-left: 1rem;\n  padding-right: 2rem;\n  display: grid;\n  gap: 0.5rem;\n  align-items: center;\n  grid-template-columns: 0.75rem 1fr;\n  font-size: 1rem;\n  line-height: 1rem;\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.5rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.ComboboxItemIndicator {\n  grid-column-start: 1;\n}\n\n.ComboboxItemIndicatorIcon {\n  display: block;\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.ComboboxItemText {\n  grid-column-start: 2;\n}\n\n.TooltipPopup {\n  box-sizing: border-box;\n  display: flex;\n  flex-direction: column;\n  padding: 0.25rem 0.5rem;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  font-size: 0.9375rem;\n  line-height: 1.375rem;\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  &[data-instant] {\n    transition-duration: 0ms;\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/popups/popups-in-popups.tsx",
    "content": "'use client';\n\nimport * as React from 'react';\nimport { Combobox } from '@base-ui/react/combobox';\nimport { Select } from '@base-ui/react/select';\nimport { Menu } from '@base-ui/react/menu';\nimport { Drawer } from '@base-ui/react/drawer';\nimport { Dialog } from '@base-ui/react/dialog';\nimport { Tooltip } from '@base-ui/react/tooltip';\nimport styles from './popups-in-popups.module.css';\n\nexport default function PopupsInPopups() {\n  const [modal, setModal] = React.useState(true);\n  const [withBackdrop, setWithBackdrop] = React.useState(true);\n\n  return (\n    <div style={{ display: 'flex', flexDirection: 'column', gap: '30px' }}>\n      <label>\n        <input\n          type=\"checkbox\"\n          checked={modal}\n          onChange={(event) => setModal(event.target.checked)}\n        />{' '}\n        Modal\n      </label>\n      <label>\n        <input\n          type=\"checkbox\"\n          checked={withBackdrop}\n          onChange={() => setWithBackdrop(!withBackdrop)}\n        />{' '}\n        With backdrop\n      </label>\n      <div className={styles.Launchers}>\n        <DialogDemo modal={modal} withBackdrop={withBackdrop} />\n      </div>\n    </div>\n  );\n}\n\nfunction DialogDemo({ modal, withBackdrop }: Props & { withBackdrop: boolean }) {\n  return (\n    <Dialog.Root modal={modal}>\n      <Dialog.Trigger className={styles.Button}>Open dialog</Dialog.Trigger>\n\n      {withBackdrop && <Dialog.Backdrop className={styles.DialogBackdrop} />}\n\n      <Dialog.Portal>\n        <Dialog.Popup className={styles.DialogPopup}>\n          <Dialog.Title className={styles.DialogTitle}>Popup playground</Dialog.Title>\n          <Dialog.Description className={styles.DialogDescription}>\n            Test dialog, drawer, and nested portaled popups with the same hero-demo styling as the\n            public docs.\n          </Dialog.Description>\n          <PopupDemoContent\n            modal={modal}\n            withBackdrop={withBackdrop}\n            closeButton={<Dialog.Close className={styles.Button}>Cancel</Dialog.Close>}\n          />\n        </Dialog.Popup>\n      </Dialog.Portal>\n    </Dialog.Root>\n  );\n}\n\nfunction NestedDrawerDemo({ modal, withBackdrop }: Props & { withBackdrop: boolean }) {\n  return (\n    <Drawer.Root modal={modal} swipeDirection=\"down\">\n      <Drawer.Trigger className={styles.Button}>Open drawer</Drawer.Trigger>\n\n      <Drawer.Portal>\n        {withBackdrop ? <Drawer.Backdrop className={styles.DrawerBackdrop} /> : null}\n        <Drawer.Viewport className={styles.DrawerViewport}>\n          <Drawer.Popup className={styles.DrawerPopup}>\n            <div className={styles.DrawerHandle} />\n            <Drawer.Content className={styles.DrawerContent}>\n              <Drawer.Title className={styles.DrawerTitle}>Nested drawer</Drawer.Title>\n              <Drawer.Description className={styles.DrawerDescription}>\n                This drawer sits inside the dialog so you can test portaled popups in both layers.\n              </Drawer.Description>\n              <PopupDemoContent\n                modal={modal}\n                withBackdrop={withBackdrop}\n                closeButton={<Drawer.Close className={styles.Button}>Close</Drawer.Close>}\n                controlsClassName={styles.DrawerControls}\n              />\n            </Drawer.Content>\n          </Drawer.Popup>\n        </Drawer.Viewport>\n      </Drawer.Portal>\n    </Drawer.Root>\n  );\n}\n\nfunction PopupDemoContent({\n  modal,\n  withBackdrop,\n  closeButton,\n  controlsClassName = styles.DialogControls,\n}: Props & {\n  withBackdrop: boolean;\n  closeButton: React.ReactNode;\n  controlsClassName?: string;\n}) {\n  return (\n    <React.Fragment>\n      <div className={styles.PopupGrid}>\n        <SelectDemo modal={modal} />\n        <MenuDemo modal={modal} />\n        <ExampleCombobox />\n      </div>\n      <div className={styles.NestedTriggerRow}>\n        <NestedDrawerDemo modal={modal} withBackdrop={withBackdrop} />\n      </div>\n      <div className={controlsClassName}>{closeButton}</div>\n    </React.Fragment>\n  );\n}\n\nfunction SelectDemo({ modal }: Props) {\n  return (\n    <div className={styles.Field}>\n      <Select.Root modal={modal} defaultValue={fontOptions[0].value} items={fontOptions}>\n        <Select.Label className={styles.Label}>Font</Select.Label>\n        <Tooltip.Root>\n          <Select.Trigger render={<Tooltip.Trigger className={styles.Select} />} nativeButton>\n            <Select.Value className={styles.Value} placeholder=\"Select font\" />\n            <Select.Icon className={styles.SelectIcon}>\n              <ChevronUpDownIcon />\n            </Select.Icon>\n          </Select.Trigger>\n          <Tooltip.Portal>\n            <Tooltip.Positioner sideOffset={10}>\n              <Tooltip.Popup className={styles.TooltipPopup}>Choose a font</Tooltip.Popup>\n            </Tooltip.Positioner>\n          </Tooltip.Portal>\n        </Tooltip.Root>\n\n        <Select.Portal>\n          <Select.Positioner\n            sideOffset={8}\n            className={styles.SelectPositioner}\n            alignItemWithTrigger={false}\n          >\n            <Select.Popup className={styles.SelectPopup}>\n              <Select.ScrollUpArrow className={styles.SelectScrollArrow} />\n              <Select.List className={styles.SelectList}>\n                {fontOptions.map((option) => (\n                  <Select.Item\n                    key={option.value}\n                    className={styles.SelectItem}\n                    value={option.value}\n                  >\n                    <Select.ItemIndicator className={styles.SelectItemIndicator}>\n                      <CheckIcon className={styles.SelectItemIndicatorIcon} />\n                    </Select.ItemIndicator>\n                    <Select.ItemText className={styles.SelectItemText}>\n                      {option.label}\n                    </Select.ItemText>\n                  </Select.Item>\n                ))}\n              </Select.List>\n              <Select.ScrollDownArrow className={styles.SelectScrollArrow} />\n            </Select.Popup>\n          </Select.Positioner>\n        </Select.Portal>\n      </Select.Root>\n    </div>\n  );\n}\n\nconst createHandleMenuClick = (menuItem: string) => {\n  return () => {\n    console.log(`Clicked on ${menuItem}`);\n  };\n};\n\nfunction MenuDemo({ modal }: Props) {\n  return (\n    <Menu.Root modal={modal}>\n      <Menu.Trigger className={styles.Button}>\n        Format <ChevronDownIcon className={styles.ButtonIcon} />\n      </Menu.Trigger>\n      <Menu.Portal>\n        <Menu.Positioner\n          side=\"bottom\"\n          align=\"start\"\n          sideOffset={8}\n          className={styles.MenuPositioner}\n        >\n          <Menu.Popup className={styles.MenuPopup}>\n            <Menu.Arrow className={styles.MenuArrow}>\n              <ArrowSvg />\n            </Menu.Arrow>\n            <Menu.SubmenuRoot closeParentOnEsc={false}>\n              <Menu.SubmenuTrigger className={styles.SubmenuTrigger}>\n                Text color\n              </Menu.SubmenuTrigger>\n              <Menu.Portal>\n                <Menu.Positioner\n                  align=\"start\"\n                  side=\"right\"\n                  sideOffset={12}\n                  className={styles.MenuPositioner}\n                >\n                  <Menu.Popup className={styles.MenuPopup}>\n                    {textColors.map((color) => (\n                      <Menu.Item\n                        key={color}\n                        className={styles.MenuItem}\n                        onClick={createHandleMenuClick(`Text color/${color}`)}\n                      >\n                        {color}\n                      </Menu.Item>\n                    ))}\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            </Menu.SubmenuRoot>\n\n            <Menu.SubmenuRoot>\n              <Menu.SubmenuTrigger className={styles.SubmenuTrigger}>Style</Menu.SubmenuTrigger>\n              <Menu.Portal>\n                <Menu.Positioner\n                  align=\"start\"\n                  side=\"right\"\n                  sideOffset={12}\n                  className={styles.MenuPositioner}\n                >\n                  <Menu.Popup className={styles.MenuPopup}>\n                    <Menu.SubmenuRoot>\n                      <Menu.SubmenuTrigger className={styles.SubmenuTrigger}>\n                        Heading\n                      </Menu.SubmenuTrigger>\n                      <Menu.Portal>\n                        <Menu.Positioner\n                          align=\"start\"\n                          side=\"right\"\n                          sideOffset={12}\n                          className={styles.MenuPositioner}\n                        >\n                          <Menu.Popup className={styles.MenuPopup}>\n                            {headingLevels.map((level) => (\n                              <Menu.Item\n                                key={level}\n                                className={styles.MenuItem}\n                                onClick={createHandleMenuClick(`Style/Heading/${level}`)}\n                              >\n                                {level}\n                              </Menu.Item>\n                            ))}\n                          </Menu.Popup>\n                        </Menu.Positioner>\n                      </Menu.Portal>\n                    </Menu.SubmenuRoot>\n                    {styleItems.map((item) => (\n                      <Menu.Item\n                        key={item}\n                        className={styles.MenuItem}\n                        onClick={createHandleMenuClick(`Style/${item}`)}\n                      >\n                        {item}\n                      </Menu.Item>\n                    ))}\n                    <Menu.Item className={styles.MenuItem} disabled>\n                      List\n                    </Menu.Item>\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            </Menu.SubmenuRoot>\n\n            {rootMenuItems.map((item) => (\n              <Menu.Item\n                key={item}\n                className={styles.MenuItem}\n                onClick={createHandleMenuClick(item)}\n              >\n                {item}\n              </Menu.Item>\n            ))}\n          </Menu.Popup>\n        </Menu.Positioner>\n      </Menu.Portal>\n    </Menu.Root>\n  );\n}\n\ninterface Props {\n  modal: boolean;\n}\n\nfunction ExampleCombobox() {\n  const id = React.useId();\n  return (\n    <Combobox.Root items={fruits}>\n      <div className={styles.ComboboxLabel}>\n        <label htmlFor={id}>Choose a fruit</label>\n        <Combobox.InputGroup className={styles.ComboboxInputGroup}>\n          <Combobox.Input placeholder=\"e.g. Apple\" id={id} className={styles.ComboboxInput} />\n          <div className={styles.ComboboxActionButtons}>\n            <Combobox.Clear className={styles.ComboboxClear} aria-label=\"Clear selection\">\n              <ClearIcon className={styles.ComboboxClearIcon} />\n            </Combobox.Clear>\n            <Combobox.Trigger className={styles.ComboboxTrigger} aria-label=\"Open popup\">\n              <ChevronDownIcon className={styles.ComboboxTriggerIcon} />\n            </Combobox.Trigger>\n          </div>\n        </Combobox.InputGroup>\n      </div>\n\n      <Combobox.Portal>\n        <Combobox.Positioner className={styles.ComboboxPositioner} sideOffset={4}>\n          <Combobox.Popup className={styles.ComboboxPopup}>\n            <Combobox.Empty className={styles.ComboboxEmpty}>No fruits found.</Combobox.Empty>\n            <Combobox.List className={styles.ComboboxList}>\n              {(item: string) => (\n                <Combobox.Item key={item} value={item} className={styles.ComboboxItem}>\n                  <Combobox.ItemIndicator className={styles.ComboboxItemIndicator}>\n                    <CheckIcon className={styles.ComboboxItemIndicatorIcon} />\n                  </Combobox.ItemIndicator>\n                  <div className={styles.ComboboxItemText}>{item}</div>\n                </Combobox.Item>\n              )}\n            </Combobox.List>\n          </Combobox.Popup>\n        </Combobox.Positioner>\n      </Combobox.Portal>\n    </Combobox.Root>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\nfunction ClearIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M18 6L6 18\" />\n      <path d=\"M6 6l12 12\" />\n    </svg>\n  );\n}\n\nfunction ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"10\"\n      height=\"10\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      {...props}\n    >\n      <path d=\"M6 9l6 6 6-6\" />\n    </svg>\n  );\n}\n\nfunction ChevronUpDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"8\"\n      height=\"12\"\n      viewBox=\"0 0 8 12\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"1.5\"\n      {...props}\n    >\n      <path d=\"M0.5 4.5L4 1.5L7.5 4.5\" />\n      <path d=\"M0.5 7.5L4 10.5L7.5 7.5\" />\n    </svg>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nconst fruits = [\n  'Apple',\n  'Banana',\n  'Orange',\n  'Pineapple',\n  'Grape',\n  'Mango',\n  'Strawberry',\n  'Blueberry',\n  'Raspberry',\n  'Blackberry',\n  'Cherry',\n  'Peach',\n  'Pear',\n  'Plum',\n  'Kiwi',\n  'Watermelon',\n  'Cantaloupe',\n  'Honeydew',\n  'Papaya',\n  'Guava',\n  'Lychee',\n  'Pomegranate',\n  'Apricot',\n  'Grapefruit',\n  'Passionfruit',\n];\n\nconst fontOptions = [\n  { value: 'system', label: 'System font' },\n  { value: 'arial', label: 'Arial' },\n  { value: 'roboto', label: 'Roboto' },\n  { value: 'georgia', label: 'Georgia' },\n  { value: 'times', label: 'Times New Roman' },\n  { value: 'helvetica', label: 'Helvetica' },\n  { value: 'verdana', label: 'Verdana' },\n  { value: 'tahoma', label: 'Tahoma' },\n  { value: 'garamond', label: 'Garamond' },\n  { value: 'futura', label: 'Futura' },\n  { value: 'avenir', label: 'Avenir' },\n  { value: 'courier', label: 'Courier New' },\n  { value: 'monaco', label: 'Monaco' },\n  { value: 'inter', label: 'Inter' },\n  { value: 'plex', label: 'IBM Plex Sans' },\n];\n\nconst textColors = [\n  'Black',\n  'Dark grey',\n  'Slate',\n  'Navy',\n  'Indigo',\n  'Blue',\n  'Cyan',\n  'Teal',\n  'Green',\n  'Lime',\n  'Olive',\n  'Gold',\n  'Orange',\n  'Coral',\n  'Red',\n  'Magenta',\n  'Purple',\n  'Brown',\n];\n\nconst headingLevels = [\n  'Level 1',\n  'Level 2',\n  'Level 3',\n  'Level 4',\n  'Level 5',\n  'Level 6',\n  'Display',\n  'Eyebrow',\n];\n\nconst styleItems = [\n  'Paragraph',\n  'Lead paragraph',\n  'Quote',\n  'Callout',\n  'Code block',\n  'Caption',\n  'Pull quote',\n  'Divider',\n];\n\nconst rootMenuItems = [\n  'Clear formatting',\n  'Duplicate block',\n  'Turn into note',\n  'Turn into to-do',\n  'Pin section',\n  'Lock section',\n  'Export snippet',\n  'Archive block',\n];\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/popups/popups-transform-origin.module.css",
    "content": ".Popup {\n  transition: transform 1s;\n  transform-origin: var(--transform-origin);\n  background: black;\n  width: 100px;\n  height: 100px;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    transform: scale(0);\n  }\n}\n\n.Trigger {\n  background: #ddd;\n  width: 75px;\n  height: 50px;\n  border: none;\n  font-size: inherit;\n}\n\n.Arrow {\n  width: 10px;\n  height: 10px;\n  background: red;\n\n  &[data-side='top'] {\n    bottom: -10px;\n  }\n\n  &[data-side='bottom'] {\n    top: -10px;\n  }\n\n  &[data-side='left'] {\n    right: -10px;\n  }\n\n  &[data-side='right'] {\n    left: -10px;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/popups/popups-transform-origin.tsx",
    "content": "import { Popover as PopoverPrimitive } from '@base-ui/react/popover';\nimport styles from './popups-transform-origin.module.css';\nimport type { Side } from '../../../../../../packages/react/src/utils/useAnchorPositioning';\n\nfunction Popover({ side }: { side: Side }) {\n  return (\n    <PopoverPrimitive.Root>\n      <PopoverPrimitive.Trigger className={styles.Trigger}>{side}</PopoverPrimitive.Trigger>\n      <PopoverPrimitive.Portal>\n        <PopoverPrimitive.Positioner side={side} sideOffset={20}>\n          <PopoverPrimitive.Popup className={styles.Popup} />\n        </PopoverPrimitive.Positioner>\n      </PopoverPrimitive.Portal>\n    </PopoverPrimitive.Root>\n  );\n}\n\nfunction PopoverWithArrow({ side }: { side: Side }) {\n  return (\n    <PopoverPrimitive.Root>\n      <PopoverPrimitive.Trigger className={styles.Trigger}>{side}</PopoverPrimitive.Trigger>\n      <PopoverPrimitive.Portal>\n        <PopoverPrimitive.Positioner side={side} sideOffset={20}>\n          <PopoverPrimitive.Popup className={styles.Popup}>\n            <PopoverPrimitive.Arrow className={styles.Arrow} />\n          </PopoverPrimitive.Popup>\n        </PopoverPrimitive.Positioner>\n      </PopoverPrimitive.Portal>\n    </PopoverPrimitive.Root>\n  );\n}\n\nfunction ShiftSide({ side }: { side: Side }) {\n  return (\n    <PopoverPrimitive.Root>\n      <PopoverPrimitive.Trigger className={styles.Trigger}>{side}</PopoverPrimitive.Trigger>\n      <PopoverPrimitive.Portal>\n        <PopoverPrimitive.Positioner\n          side={side}\n          sideOffset={20}\n          collisionAvoidance={{ side: 'shift' }}\n        >\n          <PopoverPrimitive.Popup\n            className={styles.Popup}\n            style={{ height: 600, maxHeight: 'var(--available-height)' }}\n          />\n        </PopoverPrimitive.Positioner>\n      </PopoverPrimitive.Portal>\n    </PopoverPrimitive.Root>\n  );\n}\n\nexport default function PopupTransformOrigin() {\n  return (\n    <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>\n      <p>Popovers should emanate from the anchor's center or arrow tip.</p>\n      <div style={{ display: 'flex', gap: 10 }}>\n        <Popover side=\"top\" />\n        <Popover side=\"right\" />\n        <Popover side=\"bottom\" />\n        <Popover side=\"left\" />\n      </div>\n      <h2>With arrow</h2>\n      <div style={{ display: 'flex', gap: 10 }}>\n        <PopoverWithArrow side=\"top\" />\n        <PopoverWithArrow side=\"right\" />\n        <PopoverWithArrow side=\"bottom\" />\n        <PopoverWithArrow side=\"left\" />\n      </div>\n      <h2>Shift side</h2>\n      <div style={{ display: 'flex', gap: 10 }}>\n        <ShiftSide side=\"top\" />\n        <ShiftSide side=\"bottom\" />\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/preview-card/nested.module.css",
    "content": ".Paragraph {\n  margin: 0;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n}\n\n.Link {\n  outline: 0;\n  color: var(--color-blue);\n  text-decoration-line: none;\n  text-decoration-thickness: 1px;\n  text-decoration-color: color-mix(in oklab, var(--color-blue), transparent 40%);\n  text-underline-offset: 2px;\n\n  @media (hover: hover) {\n    &:hover {\n      text-decoration-line: underline;\n    }\n  }\n\n  &[data-popup-open] {\n    text-decoration-line: underline;\n  }\n\n  &:focus-visible {\n    border-radius: 0.125rem;\n    outline: 2px solid var(--color-blue);\n    text-decoration-line: none;\n  }\n}\n\n.Popup {\n  box-sizing: border-box;\n  width: 280px;\n  display: flex;\n  flex-direction: column;\n  gap: 0.5rem;\n  padding: 0.5rem;\n  border-radius: 0.5rem;\n  background-color: canvas;\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Arrow {\n  display: flex;\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n\n.Image {\n  display: block;\n  width: 100%;\n  height: auto;\n  border-radius: 0.25rem;\n}\n\n.Summary {\n  margin: 0;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  color: var(--color-gray-900);\n  text-wrap: pretty;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/preview-card/nested.tsx",
    "content": "import * as React from 'react';\nimport { PreviewCard } from '@base-ui/react/preview-card';\nimport styles from './nested.module.css';\n\nexport default function ExamplePreviewCard({ levels = 2 }: { levels?: number }) {\n  return (\n    <PreviewCard.Root>\n      <div className={styles.Paragraph}>\n        The principles of good{' '}\n        <PreviewCard.Trigger\n          className={styles.Link}\n          href=\"https://en.wikipedia.org/wiki/Typography\"\n        >\n          typography\n        </PreviewCard.Trigger>{' '}\n        remain into the digital age.\n      </div>\n\n      <PreviewCard.Portal>\n        <PreviewCard.Positioner sideOffset={8} collisionAvoidance={{ side: 'none' }}>\n          <PreviewCard.Popup className={styles.Popup}>\n            <PreviewCard.Arrow className={styles.Arrow}>\n              <ArrowSvg />\n            </PreviewCard.Arrow>\n            <img\n              width=\"448\"\n              height=\"300\"\n              className={styles.Image}\n              src=\"https://images.unsplash.com/photo-1619615391095-dfa29e1672ef?q=80&w=448&h=300\"\n              alt=\"Station Hofplein signage in Rotterdam, Netherlands\"\n            />\n            <div className={styles.Summary}>\n              <strong>Typography</strong> is the art and science of arranging type to make written\n              language clear, visually appealing, and effective in communication.\n              {levels > 0 && <ExamplePreviewCard levels={levels - 1} />}\n            </div>\n          </PreviewCard.Popup>\n        </PreviewCard.Positioner>\n      </PreviewCard.Portal>\n    </PreviewCard.Root>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/preview-card/triggers.module.css",
    "content": ".Container {\n  display: flex;\n  flex-direction: column;\n  gap: 0.75rem;\n  margin-bottom: 2rem;\n}\n\n.Link {\n  outline: 0;\n  color: var(--color-blue);\n  text-decoration-line: none;\n  text-decoration-thickness: 1px;\n  text-decoration-color: color-mix(in oklab, var(--color-blue), transparent 40%);\n  text-underline-offset: 2px;\n\n  @media (hover: hover) {\n    &:hover {\n      text-decoration-line: underline;\n    }\n  }\n\n  &[data-popup-open] {\n    text-decoration-line: underline;\n  }\n\n  &:focus-visible {\n    border-radius: 0.125rem;\n    outline: 2px solid var(--color-blue);\n    text-decoration-line: none;\n  }\n}\n\n.ControlButton {\n  box-sizing: border-box;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  gap: 0.375rem;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 500;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Popup {\n  box-sizing: border-box;\n  width: 240px;\n  display: flex;\n  flex-direction: column;\n  gap: 0.5rem;\n  padding: 0.5rem;\n  border-radius: 0.5rem;\n  background-color: canvas;\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Arrow {\n  display: flex;\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n\n.Image {\n  display: block;\n  width: 100%;\n  height: auto;\n  border-radius: 0.25rem;\n}\n\n.Summary {\n  margin: 0;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  color: var(--color-gray-900);\n  text-wrap: pretty;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/preview-card/triggers.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { PreviewCard } from '@base-ui/react/preview-card';\nimport { StoreInspector } from '@base-ui/utils/store';\nimport demoStyles from 'docs/src/app/(docs)/react/components/preview-card/demos/detached-triggers-full/css-modules/index.module.css';\nimport { SettingsMetadata, useExperimentSettings } from '../_components/SettingsPanel';\nimport styles from './triggers.module.css';\n\nconst previewCard1Handle = PreviewCard.createHandle<React.ReactElement>();\nconst previewCard2Handle = PreviewCard.createHandle<React.ReactElement>();\n\ninterface Settings {\n  delay: number;\n  closeDelay: number;\n  side: 'top' | 'bottom' | 'left' | 'right';\n  keepMounted: boolean;\n}\nconst cardContents = {\n  typography: (\n    <div className={demoStyles.PopupContent}>\n      <img\n        width=\"224\"\n        height=\"150\"\n        className={demoStyles.Image}\n        src=\"https://images.unsplash.com/photo-1619615391095-dfa29e1672ef?q=80&w=448&h=300\"\n        alt=\"Station Hofplein signage in Rotterdam, Netherlands\"\n      />\n      <p className={demoStyles.Summary}>\n        <strong>Typography</strong> is the art and science of arranging type.\n      </p>\n    </div>\n  ),\n  design: (\n    <div className={demoStyles.PopupContent}>\n      <img\n        width=\"250\"\n        height=\"249\"\n        className={demoStyles.Image}\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/b/b7/Braun_ABW30_%28schwarz%29.jpg/250px-Braun_ABW30_%28schwarz%29.jpg\"\n        alt=\"Braun ABW30\"\n      />\n      <p className={demoStyles.Summary}>\n        A <strong>design</strong> is the concept or proposal for an object, process, or system.\n      </p>\n    </div>\n  ),\n  art: (\n    <div className={demoStyles.PopupContent}>\n      <img\n        width=\"250\"\n        height=\"290\"\n        className={demoStyles.Image}\n        src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/5/5f/MonaLisa_sfumato.jpeg/250px-MonaLisa_sfumato.jpeg\"\n        alt=\"Mona Lisa\"\n      />\n      <p className={demoStyles.Summary}>\n        <strong>Art</strong> is a diverse range of cultural activity centered around works utilizing\n        creative or imaginative talents, which are expected to evoke a worthwhile experience,\n        generally through an expression of emotional power, conceptual ideas, technical proficiency,\n        or beauty.\n      </p>\n    </div>\n  ),\n};\n\nexport default function PreviewCardExperiment() {\n  const { settings } = useExperimentSettings<Settings>();\n  const [singleTriggerOpen, setSingleTriggerOpen] = React.useState(false);\n  const [controlledWithinRootOpen, setControlledWithinRootOpen] = React.useState(false);\n  const [controlledWithinRootTriggerId, setControlledWithinRootTriggerId] = React.useState<\n    string | null\n  >(null);\n  const [controlledDetachedOpen, setControlledDetachedOpen] = React.useState(false);\n  const [controlledDetachedTriggerId, setControlledDetachedTriggerId] = React.useState<\n    string | null\n  >(null);\n\n  return (\n    <div>\n      <h1>Preview Card</h1>\n      <div className={styles.Container}>\n        <h2>Uncontrolled, single trigger</h2>\n        <div className={styles.Panel}>\n          <PreviewCard.Root>\n            <PreviewCardTrigger\n              delay={settings.delay}\n              closeDelay={settings.closeDelay}\n              href=\"https://en.wikipedia.org/wiki/Typography\"\n            >\n              Typography\n            </PreviewCardTrigger>\n            <PreviewCardContent side={settings.side} keepMounted={settings.keepMounted}>\n              {cardContents.typography}\n            </PreviewCardContent>\n          </PreviewCard.Root>\n          ,&nbsp;\n          <PreviewCard.Root>\n            <PreviewCardTrigger\n              delay={settings.delay}\n              closeDelay={settings.closeDelay}\n              href=\"https://en.wikipedia.org/wiki/Design\"\n            >\n              Design\n            </PreviewCardTrigger>\n            <PreviewCardContent side={settings.side} keepMounted={settings.keepMounted}>\n              {cardContents.design}\n            </PreviewCardContent>\n          </PreviewCard.Root>\n          ,&nbsp;\n          <PreviewCard.Root>\n            <PreviewCardTrigger\n              delay={settings.delay}\n              closeDelay={settings.closeDelay}\n              href=\"https://en.wikipedia.org/wiki/Art\"\n            >\n              Art\n            </PreviewCardTrigger>\n            <PreviewCardContent side={settings.side} keepMounted={settings.keepMounted}>\n              {cardContents.art}\n            </PreviewCardContent>\n          </PreviewCard.Root>\n        </div>\n      </div>\n\n      <div className={styles.Container}>\n        <h2>Controlled, single trigger</h2>\n        <div className={styles.Panel}>\n          <PreviewCard.Root\n            open={singleTriggerOpen}\n            onOpenChange={(nextOpen) => setSingleTriggerOpen(nextOpen)}\n          >\n            <PreviewCardTrigger\n              delay={settings.delay}\n              closeDelay={settings.closeDelay}\n              href=\"https://en.wikipedia.org/wiki/Typography\"\n            >\n              Typography\n            </PreviewCardTrigger>\n            <PreviewCardContent side={settings.side} keepMounted={settings.keepMounted}>\n              {cardContents.typography}\n            </PreviewCardContent>\n          </PreviewCard.Root>\n        </div>\n        <button\n          type=\"button\"\n          className={styles.ControlButton}\n          onClick={() => setSingleTriggerOpen(true)}\n        >\n          Open externally\n        </button>\n      </div>\n\n      <div className={styles.Container}>\n        <h2>Uncontrolled, multiple triggers within Root</h2>\n        <div className={styles.Panel}>\n          <PreviewCard.Root>\n            {({ payload }) => (\n              <React.Fragment>\n                <PreviewCardTrigger\n                  payload={cardContents.typography}\n                  delay={settings.delay}\n                  closeDelay={settings.closeDelay}\n                  href=\"https://en.wikipedia.org/wiki/Typography\"\n                >\n                  Typography\n                </PreviewCardTrigger>\n                ,&nbsp;\n                <PreviewCardTrigger\n                  payload={cardContents.design}\n                  delay={settings.delay}\n                  closeDelay={settings.closeDelay}\n                  href=\"https://en.wikipedia.org/wiki/Design\"\n                >\n                  Design\n                </PreviewCardTrigger>\n                ,&nbsp;\n                <PreviewCardTrigger\n                  payload={cardContents.art}\n                  delay={settings.delay}\n                  closeDelay={settings.closeDelay}\n                  href=\"https://en.wikipedia.org/wiki/Art\"\n                >\n                  Art\n                </PreviewCardTrigger>\n                <PreviewCardContent side={settings.side} keepMounted={settings.keepMounted}>\n                  {payload as string}\n                </PreviewCardContent>\n              </React.Fragment>\n            )}\n          </PreviewCard.Root>\n        </div>\n      </div>\n\n      <div className={styles.Container}>\n        <h2>Controlled, multiple triggers within Root</h2>\n        <div className={styles.Panel}>\n          <PreviewCard.Root\n            open={controlledWithinRootOpen}\n            onOpenChange={(open, eventDetails) => {\n              setControlledWithinRootOpen(open);\n              setControlledWithinRootTriggerId(eventDetails.trigger?.id ?? null);\n            }}\n            triggerId={controlledWithinRootTriggerId}\n          >\n            {({ payload }) => (\n              <React.Fragment>\n                <PreviewCardTrigger\n                  payload={cardContents.typography}\n                  delay={settings.delay}\n                  closeDelay={settings.closeDelay}\n                  href=\"https://en.wikipedia.org/wiki/Typography\"\n                  id=\"within-root-typography\"\n                >\n                  Typography\n                </PreviewCardTrigger>\n                ,&nbsp;\n                <PreviewCardTrigger\n                  payload={cardContents.design}\n                  delay={settings.delay}\n                  closeDelay={settings.closeDelay}\n                  href=\"https://en.wikipedia.org/wiki/Design\"\n                  id=\"within-root-design\"\n                >\n                  Design\n                </PreviewCardTrigger>\n                ,&nbsp;\n                <PreviewCardTrigger\n                  payload={cardContents.art}\n                  delay={settings.delay}\n                  closeDelay={settings.closeDelay}\n                  href=\"https://en.wikipedia.org/wiki/Art\"\n                  id=\"within-root-art\"\n                >\n                  Art\n                </PreviewCardTrigger>\n                <PreviewCardContent side={settings.side} keepMounted={settings.keepMounted}>\n                  {payload as string}\n                </PreviewCardContent>\n              </React.Fragment>\n            )}\n          </PreviewCard.Root>\n        </div>\n        <button\n          type=\"button\"\n          className={styles.ControlButton}\n          onClick={() => {\n            setControlledWithinRootOpen(true);\n            setControlledWithinRootTriggerId('within-root-design');\n          }}\n        >\n          Open externally (Design)\n        </button>\n      </div>\n\n      <div className={styles.Container}>\n        <h2>\n          Uncontrolled, detached triggers <StoreInspector store={previewCard1Handle.store} />\n        </h2>\n\n        <div className={styles.Panel}>\n          <PreviewCardTrigger\n            payload={cardContents.typography}\n            delay={settings.delay}\n            closeDelay={settings.closeDelay}\n            href=\"https://en.wikipedia.org/wiki/Typography\"\n            handle={previewCard1Handle}\n          >\n            Typography\n          </PreviewCardTrigger>\n          ,&nbsp;\n          <PreviewCardTrigger\n            payload={cardContents.design}\n            delay={settings.delay}\n            closeDelay={settings.closeDelay}\n            href=\"https://en.wikipedia.org/wiki/Design\"\n            handle={previewCard1Handle}\n          >\n            Design\n          </PreviewCardTrigger>\n          ,&nbsp;\n          <PreviewCardTrigger\n            payload={cardContents.art}\n            delay={settings.delay}\n            closeDelay={settings.closeDelay}\n            href=\"https://en.wikipedia.org/wiki/Art\"\n            handle={previewCard1Handle}\n          >\n            Art\n          </PreviewCardTrigger>\n        </div>\n\n        <PreviewCard.Root handle={previewCard1Handle}>\n          {({ payload }) => (\n            <PreviewCardContent side={settings.side} keepMounted={settings.keepMounted}>\n              {payload}\n            </PreviewCardContent>\n          )}\n        </PreviewCard.Root>\n      </div>\n\n      <div className={styles.Container}>\n        <h2>Controlled, detached triggers</h2>\n        <div className={styles.Panel}>\n          <PreviewCardTrigger\n            payload={cardContents.typography}\n            delay={settings.delay}\n            closeDelay={settings.closeDelay}\n            href=\"https://en.wikipedia.org/wiki/Typography\"\n            handle={previewCard2Handle}\n            id=\"detached-typography-trigger\"\n          >\n            Typography\n          </PreviewCardTrigger>\n          ,&nbsp;\n          <PreviewCardTrigger\n            payload={cardContents.design}\n            delay={settings.delay}\n            closeDelay={settings.closeDelay}\n            href=\"https://en.wikipedia.org/wiki/Design\"\n            handle={previewCard2Handle}\n            id=\"detached-design-trigger\"\n          >\n            Design\n          </PreviewCardTrigger>\n          ,&nbsp;\n          <PreviewCardTrigger\n            payload={cardContents.art}\n            delay={settings.delay}\n            closeDelay={settings.closeDelay}\n            href=\"https://en.wikipedia.org/wiki/Art\"\n            handle={previewCard2Handle}\n            id=\"detached-art-trigger\"\n          >\n            Art\n          </PreviewCardTrigger>\n        </div>\n\n        <PreviewCard.Root\n          handle={previewCard2Handle}\n          open={controlledDetachedOpen}\n          triggerId={controlledDetachedTriggerId}\n          onOpenChange={(open, eventDetails) => {\n            setControlledDetachedOpen(open);\n            setControlledDetachedTriggerId(eventDetails.trigger?.id ?? null);\n          }}\n        >\n          {({ payload }) => (\n            <PreviewCardContent side={settings.side} keepMounted={settings.keepMounted}>\n              {payload}\n            </PreviewCardContent>\n          )}\n        </PreviewCard.Root>\n\n        <button\n          type=\"button\"\n          className={styles.ControlButton}\n          onClick={() => {\n            setControlledDetachedOpen(true);\n            setControlledDetachedTriggerId('detached-design-trigger');\n          }}\n        >\n          Open externally (Design)\n        </button>\n        <button\n          type=\"button\"\n          className={styles.ControlButton}\n          onClick={() => {\n            previewCard2Handle.open('detached-design-trigger');\n          }}\n        >\n          Open via handle (Design)\n        </button>\n      </div>\n    </div>\n  );\n}\n\nfunction PreviewCardTrigger<Payload = string>(\n  props: PreviewCard.Trigger.Props<Payload>,\n): React.ReactElement {\n  return <PreviewCard.Trigger className={demoStyles.Link} {...(props as any)} />;\n}\n\nfunction PreviewCardContent(\n  props: PreviewCard.Popup.Props & {\n    side: 'top' | 'bottom' | 'left' | 'right';\n    keepMounted: boolean;\n  },\n) {\n  const { children, side, keepMounted, ...otherProps } = props;\n  return (\n    <PreviewCard.Portal keepMounted={keepMounted}>\n      <PreviewCard.Positioner sideOffset={8} side={side} className={demoStyles.Positioner}>\n        <PreviewCard.Popup className={demoStyles.Popup} {...otherProps}>\n          <PreviewCard.Arrow className={demoStyles.Arrow}>\n            <ArrowSvg />\n          </PreviewCard.Arrow>\n          <PreviewCard.Viewport className={demoStyles.Viewport}>{children}</PreviewCard.Viewport>\n        </PreviewCard.Popup>\n      </PreviewCard.Positioner>\n    </PreviewCard.Portal>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={demoStyles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={demoStyles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={demoStyles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nexport const settingsMetadata: SettingsMetadata<Settings> = {\n  delay: {\n    type: 'number',\n    label: 'Delay',\n    default: 600,\n  },\n  closeDelay: {\n    type: 'number',\n    label: 'Close Delay',\n    default: 300,\n  },\n  side: {\n    type: 'string',\n    label: 'Side',\n    options: ['top', 'bottom', 'left', 'right'],\n    default: 'bottom',\n  },\n  keepMounted: {\n    type: 'boolean',\n    label: 'Keep mounted',\n    default: false,\n  },\n};\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/rtl.module.css",
    "content": ".rtl {\n  width: 100%;\n  min-height: 100vh;\n  display: flex;\n  flex-flow: row nowrap;\n  justify-content: center;\n  align-items: center;\n  gap: 1rem;\n}\n\n.popup {\n  font-family: 'IBM Plex Sans', sans-serif;\n  font-size: 0.875rem;\n  box-sizing: border-box;\n  padding: 6px;\n  margin: 12px 0;\n  min-width: 200px;\n  border-radius: 12px;\n  /* overflow: auto; */\n  outline: 0;\n  background: #fff;\n  border: 1px solid var(--color-gray-200);\n  color: black;\n  box-shadow: 0 4px 30px var(--color-gray-200);\n  z-index: 1;\n  transform-origin: var(--transform-origin);\n  opacity: 1;\n  transform: scale(1, 1);\n  transition:\n    opacity 100ms ease-in,\n    transform 100ms ease-in;\n\n  @starting-style {\n    opacity: 0;\n    transform: scale(0.8);\n  }\n\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.8);\n    transition:\n      opacity 200ms ease-in,\n      transform 200ms ease-in;\n  }\n}\n\n.item {\n  list-style: none;\n  padding: 8px;\n  border-radius: 8px;\n  cursor: default;\n  user-select: none;\n  color: black;\n\n  &:last-of-type {\n    border-bottom: none;\n  }\n\n  &:focus,\n  &:hover {\n    background-color: #ddd;\n    color: blue;\n  }\n\n  &:focus-visible {\n    outline: none;\n  }\n\n  &[data-disabled] {\n    color: var(--color-gray-400);\n  }\n}\n\n.submenutrigger {\n  list-style: none;\n  padding: 8px;\n  border-radius: 8px;\n  cursor: default;\n  user-select: none;\n  color: #444;\n\n  &:last-of-type {\n    border-bottom: none;\n  }\n\n  &::after {\n    content: '›';\n    float: right;\n  }\n\n  &:dir(rtl)::after {\n    content: '›';\n    float: left;\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-50);\n    color: var(--color-gray-900);\n  }\n\n  &:focus,\n  &:hover {\n    background-color: #ddd;\n    color: blue;\n  }\n\n  &:focus-visible {\n    outline: none;\n  }\n\n  &[data-disabled] {\n    color: var(--color-gray-400);\n  }\n}\n\n.trigger {\n  font-family: 'IBM Plex Sans', sans-serif;\n  font-weight: 600;\n  font-size: 0.875rem;\n  line-height: 1.5;\n  padding: 8px 16px;\n  border-radius: 8px;\n  transition: all 150ms ease;\n  cursor: pointer;\n  background: #fff;\n  border: 1px solid var(--color-gray-200);\n  color: var(--color-gray-200);\n  box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);\n\n  &:hover {\n    background: var(--color-gray-50);\n    border-color: var(--color-gray-300);\n    color: var(--color-gray-900);\n  }\n\n  &:active {\n    background: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    box-shadow: 0 0 0 4px blue;\n    outline: none;\n  }\n}\n\n.arrow {\n  width: 10px;\n  height: 10px;\n  transform: rotate(45deg);\n  background: white;\n  z-index: -1;\n\n  &[data-side='top'] {\n    bottom: -5px;\n  }\n\n  &[data-side='right'] {\n    left: -5px;\n  }\n\n  &[data-side='bottom'] {\n    top: -5px;\n  }\n\n  &[data-side='left'] {\n    right: -5px;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/rtl.tsx",
    "content": "'use client';\nimport clsx from 'clsx';\nimport { DirectionProvider, useDirection } from '@base-ui/react/direction-provider';\nimport { Menu } from '@base-ui/react/menu';\nimport { Popover } from '@base-ui/react/popover';\nimport { PreviewCard } from '@base-ui/react/preview-card';\nimport c from './rtl.module.css';\n\nconst dir = 'rtl';\n\nexport default function RtlNestedMenu() {\n  const createHandleMenuClick = (menuItem: string) => {\n    return () => {\n      console.log(`Clicked on ${menuItem}`);\n    };\n  };\n\n  return (\n    <div className={c.rtl} dir={dir}>\n      <DirectionProvider direction={dir}>\n        <Menu.Root>\n          <Menu.Trigger className={c.trigger}>Menu.Trigger</Menu.Trigger>\n          <Menu.Portal>\n            <MenuPositioner side=\"bottom\" align=\"start\">\n              <Menu.Popup className={c.popup}>\n                <Menu.SubmenuRoot>\n                  <Menu.SubmenuTrigger className={c.submenutrigger}>Text color</Menu.SubmenuTrigger>\n                  <Menu.Portal>\n                    <MenuPositioner align=\"start\" side=\"inline-end\">\n                      <Menu.Popup className={c.popup}>\n                        <Menu.Item\n                          className={c.item}\n                          onClick={createHandleMenuClick('Text color/Black')}\n                        >\n                          Black\n                        </Menu.Item>\n                        <Menu.Item\n                          className={c.item}\n                          onClick={createHandleMenuClick('Text color/Dark grey')}\n                        >\n                          Dark grey\n                        </Menu.Item>\n                        <Menu.Item\n                          className={c.item}\n                          onClick={createHandleMenuClick('Text color/Accent')}\n                        >\n                          Accent\n                        </Menu.Item>\n                      </Menu.Popup>\n                    </MenuPositioner>\n                  </Menu.Portal>\n                </Menu.SubmenuRoot>\n\n                <Menu.SubmenuRoot>\n                  <Menu.SubmenuTrigger className={c.submenutrigger}>Style</Menu.SubmenuTrigger>\n                  <Menu.Portal>\n                    <MenuPositioner align=\"start\" side=\"inline-end\">\n                      <Menu.Popup className={c.popup}>\n                        <Menu.SubmenuRoot>\n                          <Menu.SubmenuTrigger className={c.submenutrigger}>\n                            Heading\n                          </Menu.SubmenuTrigger>\n                          <Menu.Portal>\n                            <MenuPositioner align=\"start\" side=\"inline-end\">\n                              <Menu.Popup className={c.popup}>\n                                <Menu.Item\n                                  className={c.item}\n                                  onClick={createHandleMenuClick('Style/Heading/Level 1')}\n                                >\n                                  Level 1\n                                </Menu.Item>\n                                <Menu.Item\n                                  className={c.item}\n                                  onClick={createHandleMenuClick('Style/Heading/Level 2')}\n                                >\n                                  Level 2\n                                </Menu.Item>\n                                <Menu.Item\n                                  className={c.item}\n                                  onClick={createHandleMenuClick('Style/Heading/Level 3')}\n                                >\n                                  Level 3\n                                </Menu.Item>\n                              </Menu.Popup>\n                            </MenuPositioner>\n                          </Menu.Portal>\n                        </Menu.SubmenuRoot>\n                        <Menu.Item\n                          className={c.item}\n                          onClick={createHandleMenuClick('Style/Paragraph')}\n                        >\n                          Paragraph\n                        </Menu.Item>\n                        <Menu.SubmenuRoot>\n                          <Menu.SubmenuTrigger className={c.submenutrigger}>\n                            List\n                          </Menu.SubmenuTrigger>\n                          <Menu.Portal>\n                            <MenuPositioner align=\"start\" side=\"inline-end\">\n                              <Menu.Popup className={c.popup}>\n                                <Menu.Item\n                                  className={c.item}\n                                  onClick={createHandleMenuClick('Style/List/Ordered')}\n                                >\n                                  Ordered\n                                </Menu.Item>\n                                <Menu.Item\n                                  className={c.item}\n                                  onClick={createHandleMenuClick('Style/List/Unordered')}\n                                >\n                                  Unordered\n                                </Menu.Item>\n                              </Menu.Popup>\n                            </MenuPositioner>\n                          </Menu.Portal>\n                        </Menu.SubmenuRoot>\n                      </Menu.Popup>\n                    </MenuPositioner>\n                  </Menu.Portal>\n                </Menu.SubmenuRoot>\n\n                <Menu.Item className={c.item} onClick={createHandleMenuClick('Clear formatting')}>\n                  Clear formatting\n                </Menu.Item>\n              </Menu.Popup>\n            </MenuPositioner>\n          </Menu.Portal>\n        </Menu.Root>\n\n        <PreviewCard.Root>\n          <PreviewCard.Trigger href=\"#\" className={c.trigger}>\n            PreviewCard.Trigger\n          </PreviewCard.Trigger>\n          <PreviewCard.Portal>\n            <PreviewCardPositioner sideOffset={8} side=\"inline-end\" align=\"center\">\n              <PreviewCard.Popup className={c.popup}>\n                <img\n                  src=\"https://pbs.twimg.com/profile_images/1798056009291997184/B-prVmUP_400x400.jpg\"\n                  alt=\"Base UI Logo\"\n                  width={80}\n                  height={80}\n                  style={{ borderRadius: '50%' }}\n                />\n                <h2 style={{ fontSize: 20, margin: 0 }}>Base UI</h2>\n                <p>Unstyled React components and hooks (@base-ui/react), by @MUI_hq.</p>\n                <div style={{ display: 'flex', gap: 10 }}>\n                  <span>\n                    <strong>1</strong> Following\n                  </span>\n                  <span>\n                    <strong>1,000</strong> Followers\n                  </span>\n                </div>\n                <PreviewCard.Arrow className={c.arrow} />\n              </PreviewCard.Popup>\n            </PreviewCardPositioner>\n          </PreviewCard.Portal>\n        </PreviewCard.Root>\n\n        <Popover.Root>\n          <Popover.Trigger className={c.trigger}>Popover.Trigger</Popover.Trigger>\n          <Popover.Portal>\n            <Popover.Positioner sideOffset={8} side=\"inline-end\">\n              <Popover.Popup className={c.popup}>\n                <Popover.Title>Popover Title</Popover.Title>\n                <Popover.Description>Popover Description</Popover.Description>\n                <Popover.Arrow className={c.arrow} />\n              </Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        </Popover.Root>\n      </DirectionProvider>\n    </div>\n  );\n}\n\nfunction MenuPositioner({ className, ...props }: Menu.Positioner.Props) {\n  const direction = useDirection();\n  return <Menu.Positioner className={clsx('bui-ol-n', className)} dir={direction} {...props} />;\n}\n\nfunction PreviewCardPositioner({ className, ...props }: PreviewCard.Positioner.Props) {\n  const direction = useDirection();\n  return (\n    <PreviewCard.Positioner className={clsx('bui-ol-n', className)} dir={direction} {...props} />\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/scroll-area/inside-menu.module.css",
    "content": ".Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: 0.375rem;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 500;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.ButtonIcon {\n  margin-right: -0.25rem;\n}\n\n.Positioner {\n  outline: 0;\n}\n\n.Popup {\n  box-sizing: border-box;\n  padding-block: 0.25rem;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Arrow {\n  display: flex;\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n\n.Item,\n.SubmenuTrigger {\n  outline: 0;\n  cursor: default;\n  user-select: none;\n  padding-block: 0.5rem;\n  padding-left: 1rem;\n  padding-right: 2rem;\n  display: flex;\n  font-size: 0.875rem;\n  line-height: 1rem;\n\n  &[data-popup-open] {\n    z-index: 0;\n    position: relative;\n  }\n\n  &[data-popup-open]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.25rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-100);\n  }\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.25rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.SubmenuTrigger {\n  align-items: center;\n  justify-content: space-between;\n  gap: 1rem;\n  padding-right: 1rem;\n}\n\n.Separator {\n  margin: 0.375rem 1rem;\n  height: 1px;\n  background-color: var(--color-gray-200);\n}\n\n.Viewport {\n  max-height: 400px;\n}\n\n.Scrollbar {\n  display: flex;\n  justify-content: center;\n  background-color: var(--color-gray-200);\n  width: 0.25rem;\n  border-radius: 0.375rem;\n  margin: 0.25rem;\n  opacity: 0;\n\n  &::before {\n    content: '';\n    position: absolute;\n    width: 1.25rem;\n    height: 100%;\n  }\n\n  &[data-hovering] {\n    opacity: 1;\n  }\n}\n\n.Thumb {\n  width: 100%;\n  border-radius: inherit;\n  background-color: var(--color-gray-500);\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/scroll-area/inside-menu.tsx",
    "content": "'use client';\n\nimport * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\nimport { ScrollArea } from '@base-ui/react/scroll-area';\nimport styles from './inside-menu.module.css';\n\nexport default function ExampleMenu() {\n  return (\n    <Menu.Root>\n      <Menu.Trigger className={styles.Button}>\n        Song <ChevronDownIcon className={styles.ButtonIcon} />\n      </Menu.Trigger>\n      <Menu.Portal>\n        <Menu.Positioner className={styles.Positioner} sideOffset={8}>\n          <Menu.Popup className={styles.Popup}>\n            <Menu.Arrow className={styles.Arrow}>\n              <ArrowSvg />\n            </Menu.Arrow>\n            <ScrollArea.Root className={styles.ScrollArea}>\n              <ScrollArea.Viewport className={styles.Viewport} tabIndex={-1}>\n                <Menu.SubmenuRoot>\n                  <Menu.SubmenuTrigger className={styles.SubmenuTrigger}>\n                    Submenu\n                    <ChevronRightIcon />\n                  </Menu.SubmenuTrigger>\n                  <Menu.Portal>\n                    <Menu.Positioner className={styles.Positioner} alignOffset={-4} sideOffset={-4}>\n                      <Menu.Popup className={styles.Popup}>\n                        <Menu.Item className={styles.Item}>Get Up!</Menu.Item>\n                        <Menu.Item className={styles.Item}>Inside Out</Menu.Item>\n                        <Menu.Item className={styles.Item}>Night Beats</Menu.Item>\n                        <Menu.Separator className={styles.Separator} />\n                        <Menu.Item className={styles.Item}>New playlist…</Menu.Item>\n                      </Menu.Popup>\n                    </Menu.Positioner>\n                  </Menu.Portal>\n                </Menu.SubmenuRoot>\n\n                {Array.from({ length: 100 }, (_, i) => (\n                  <Menu.Item className={styles.Item} key={`random-item-${i + 1}`}>\n                    Item {i + 1}\n                  </Menu.Item>\n                ))}\n              </ScrollArea.Viewport>\n              <ScrollArea.Scrollbar className={styles.Scrollbar}>\n                <ScrollArea.Thumb className={styles.Thumb} />\n              </ScrollArea.Scrollbar>\n            </ScrollArea.Root>\n          </Menu.Popup>\n        </Menu.Positioner>\n      </Menu.Portal>\n    </Menu.Root>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nfunction ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M1 3.5L5 7.5L9 3.5\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n\nfunction ChevronRightIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M3.5 9L7.5 5L3.5 1\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/scroll-area/inside-select.module.css",
    "content": ".Select {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 0.75rem;\n  height: 2.5rem;\n  padding-left: 0.875rem;\n  padding-right: 0.75rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  cursor: default;\n  user-select: none;\n  min-width: 9rem;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.SelectIcon {\n  display: flex;\n}\n\n.Popup {\n  box-sizing: border-box;\n  padding-block: 0.25rem;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  transform-origin: var(--transform-origin);\n  max-height: var(--available-height);\n\n  &[data-side='none'] {\n    transition: none;\n    transform: none;\n    opacity: 1;\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Arrow {\n  display: flex;\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n\n.Item {\n  box-sizing: border-box;\n  outline: 0;\n  font-size: 0.875rem;\n  line-height: 1rem;\n  padding-block: 0.5rem;\n  padding-left: 0.625rem;\n  padding-right: 1rem;\n  min-width: var(--anchor-width);\n  display: grid;\n  gap: 0.5rem;\n  align-items: center;\n  grid-template-columns: 0.75rem 1fr;\n  cursor: default;\n  user-select: none;\n  scroll-margin-block: 1rem;\n\n  [data-side='none'] & {\n    font-size: 1rem;\n    padding-right: 3rem;\n    min-width: calc(var(--anchor-width) + 1rem);\n  }\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.25rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.ItemIndicator {\n  grid-column-start: 1;\n}\n\n.ItemIndicatorIcon {\n  display: block;\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.ItemText {\n  grid-column-start: 2;\n}\n\n.Viewport {\n  max-height: calc(var(--available-height) - 1rem);\n}\n\n.Content {\n  display: flex;\n  flex-direction: column;\n  gap: 1rem;\n  padding-block: 0.75rem;\n  padding-left: 1rem;\n  padding-right: 1.5rem;\n}\n\n.Scrollbar {\n  display: flex;\n  justify-content: center;\n  background-color: var(--color-gray-200);\n  width: 0.25rem;\n  border-radius: 0.375rem;\n  margin: 0.5rem;\n  transition: opacity 150ms 300ms;\n\n  &::before {\n    content: '';\n    position: absolute;\n    width: 1.25rem;\n    height: 100%;\n  }\n}\n\n.Thumb {\n  width: 100%;\n  border-radius: inherit;\n  background-color: var(--color-gray-500);\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/scroll-area/inside-select.tsx",
    "content": "import * as React from 'react';\nimport { Select } from '@base-ui/react/select';\nimport { ScrollArea } from '@base-ui/react/scroll-area';\nimport styles from './inside-select.module.css';\n\nexport default function ExampleSelect() {\n  return (\n    <Select.Root defaultValue=\"item-1\">\n      <Select.Trigger className={styles.Select}>\n        <Select.Value />\n        <Select.Icon className={styles.SelectIcon}>\n          <ChevronUpDownIcon />\n        </Select.Icon>\n      </Select.Trigger>\n      <Select.Portal>\n        <Select.Positioner\n          className={styles.Positioner}\n          sideOffset={8}\n          alignItemWithTrigger={false}\n        >\n          <Select.Popup className={styles.Popup}>\n            <ScrollArea.Root className={styles.ScrollArea}>\n              <ScrollArea.Viewport className={styles.Viewport} tabIndex={-1}>\n                {[...Array(300)].map((_, i) => (\n                  <Select.Item key={i + 1} className={styles.Item} value={`item-${i + 1}`}>\n                    <Select.ItemIndicator className={styles.ItemIndicator}>\n                      <CheckIcon className={styles.ItemIndicatorIcon} />\n                    </Select.ItemIndicator>\n                    <Select.ItemText className={styles.ItemText}>Item {i + 1}</Select.ItemText>\n                  </Select.Item>\n                ))}\n              </ScrollArea.Viewport>\n              <ScrollArea.Scrollbar className={styles.Scrollbar}>\n                <ScrollArea.Thumb className={styles.Thumb} />\n              </ScrollArea.Scrollbar>\n            </ScrollArea.Root>\n          </Select.Popup>\n        </Select.Positioner>\n      </Select.Portal>\n    </Select.Root>\n  );\n}\n\nfunction ChevronUpDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"8\"\n      height=\"12\"\n      viewBox=\"0 0 8 12\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"1.5\"\n      {...props}\n    >\n      <path d=\"M0.5 4.5L4 1.5L7.5 4.5\" />\n      <path d=\"M0.5 7.5L4 10.5L7.5 7.5\" />\n    </svg>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/scroll-area/scroll-area-inset.module.css",
    "content": ".Root {\n  width: 300px;\n  height: 300px;\n  --scrollbar-size: 20px;\n}\n\n.Viewport {\n  width: 100%;\n  height: 100%;\n}\n\n.Content {\n  width: 600px;\n  height: 600px;\n  padding-inline-end: var(--scrollbar-size);\n  padding-bottom: var(--scrollbar-size);\n}\n\n.Scrollbar {\n  background: lightgray;\n\n  &[data-orientation='vertical'] {\n    width: var(--scrollbar-size);\n  }\n\n  &[data-orientation='horizontal'] {\n    height: var(--scrollbar-size);\n  }\n}\n\n.Thumb {\n  background: black;\n\n  &[data-orientation='vertical'] {\n    width: var(--scrollbar-size);\n  }\n\n  &[data-orientation='horizontal'] {\n    height: var(--scrollbar-size);\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/scroll-area/scroll-area-inset.tsx",
    "content": "import { ScrollArea } from '@base-ui/react/scroll-area';\nimport { DirectionProvider } from '@base-ui/react/direction-provider';\nimport styles from './scroll-area-inset.module.css';\n\nexport default function ScrollAreaInset() {\n  return (\n    <div>\n      <h2>RTL</h2>\n      <DirectionProvider direction=\"rtl\">\n        <p>Scroll content is not clipped by inset scrollbars (user-defined paddings)</p>\n        <ScrollArea.Root className={styles.Root} style={{ direction: 'rtl' }}>\n          <ScrollArea.Viewport className={styles.Viewport}>\n            <ScrollArea.Content className={styles.Content}>\n              <p>\n                Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor\n                incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud\n                exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute\n                irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla\n                pariatur.\n              </p>\n            </ScrollArea.Content>\n          </ScrollArea.Viewport>\n          <ScrollArea.Scrollbar className={styles.Scrollbar} orientation=\"vertical\">\n            <ScrollArea.Thumb className={styles.Thumb} />\n          </ScrollArea.Scrollbar>\n          <ScrollArea.Scrollbar className={styles.Scrollbar} orientation=\"horizontal\">\n            <ScrollArea.Thumb className={styles.Thumb} />\n          </ScrollArea.Scrollbar>\n          <ScrollArea.Corner />\n        </ScrollArea.Root>\n      </DirectionProvider>\n\n      <h2>LTR</h2>\n      <ScrollArea.Root className={styles.Root}>\n        <ScrollArea.Viewport className={styles.Viewport}>\n          <ScrollArea.Content className={styles.Content}>\n            <p>\n              Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor\n              incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud\n              exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure\n              dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n            </p>\n          </ScrollArea.Content>\n        </ScrollArea.Viewport>\n        <ScrollArea.Scrollbar className={styles.Scrollbar} orientation=\"vertical\">\n          <ScrollArea.Thumb className={styles.Thumb} />\n        </ScrollArea.Scrollbar>\n        <ScrollArea.Scrollbar className={styles.Scrollbar} orientation=\"horizontal\">\n          <ScrollArea.Thumb className={styles.Thumb} />\n        </ScrollArea.Scrollbar>\n        <ScrollArea.Corner />\n      </ScrollArea.Root>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/scroll-area/scroll-area-slight.module.css",
    "content": ".ScrollArea {\n  width: 340px;\n  height: 100px;\n  background: rgb(0 0 0 / 0.1);\n}\n\n.Viewport {\n  height: 100%;\n}\n\n.Scrollbar {\n  display: flex;\n  background: rgb(0 0 0 / 0.1);\n\n  &[data-orientation='vertical'] {\n    margin-block: 20px;\n    width: 8px;\n  }\n\n  &[data-orientation='horizontal'] {\n    flex-direction: column;\n    height: 8px;\n    margin-inline: 20px;\n  }\n}\n\n.Thumb {\n  background: rgb(0 0 0 / 0.5);\n  flex: 1;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/scroll-area/scroll-area-slight.tsx",
    "content": "import { ScrollArea } from '@base-ui/react/scroll-area';\nimport styles from './scroll-area-slight.module.css';\n\nexport default function ScrollAreaSlight() {\n  return (\n    <ScrollArea.Root className={styles.ScrollArea}>\n      <ScrollArea.Viewport className={styles.Viewport}>\n        <div className={styles.Paragraph}>\n          <div\n            style={{\n              height: 10,\n              width: 350,\n              background: 'linear-gradient(to right, red, orange)',\n            }}\n          />\n        </div>\n      </ScrollArea.Viewport>\n      <ScrollArea.Scrollbar className={styles.Scrollbar} orientation=\"horizontal\">\n        <ScrollArea.Thumb className={styles.Thumb} />\n      </ScrollArea.Scrollbar>\n    </ScrollArea.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/scroll-area/scroll-area.module.css",
    "content": ".ScrollAreaRoot {\n  width: 300px;\n  height: 100vh;\n  border-radius: 6px;\n  background: #f5f5f5;\n}\n\n.ScrollAreaViewport {\n  width: 100%;\n  height: 100%;\n  border-radius: 6px;\n\n  &:focus-visible {\n    outline: 2px solid rgb(0 0 0 / 0.5);\n  }\n}\n\n.ScrollAreaScrollbar {\n  visibility: hidden;\n  background: transparent;\n  box-sizing: border-box;\n  transition:\n    opacity 0.2s,\n    background 0.2s,\n    visibility 0.2s;\n  opacity: 0;\n  display: flex;\n\n  &:hover {\n    background: rgb(0 0 0 / 0.1);\n  }\n\n  &[data-orientation='vertical'] {\n    padding-block: 20px;\n    width: 10px;\n  }\n\n  &[data-orientation='horizontal'] {\n    padding-inline: 20px;\n    flex-direction: column;\n    height: 10px;\n  }\n\n  &[data-hovering],\n  &[data-scrolling],\n  &:hover {\n    visibility: visible;\n    opacity: 1;\n  }\n\n  &[data-scrolling]:not(:hover) {\n    transition: none;\n  }\n}\n\n.ScrollAreaThumb {\n  background: rgb(0 0 0 / 0.5);\n  border-radius: 20px;\n  flex: 1;\n  position: relative;\n\n  &[data-orientation='vertical'] {\n    margin-block: 10px;\n  }\n\n  &[data-orientation='horizontal'] {\n    margin-inline: 10px;\n  }\n\n  &::before {\n    content: '';\n    display: block;\n    position: absolute;\n    width: 100%;\n    height: 100%;\n    top: 50%;\n    left: 50%;\n    transform: translate(-50%, -50%);\n    min-width: 22px;\n    min-height: 22px;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/scroll-area/scroll-area.tsx",
    "content": "'use client';\nimport { ScrollArea } from '@base-ui/react/scroll-area';\nimport styles from './scroll-area.module.css';\n\nexport default function ScrollAreaIntroduction() {\n  return (\n    <ScrollArea.Root className={styles.ScrollAreaRoot}>\n      <ScrollArea.Viewport className={styles.ScrollAreaViewport}>\n        <div\n          style={{\n            width: 1000,\n            height: 1000,\n            background: 'linear-gradient(to bottom, red, white)',\n          }}\n        />\n      </ScrollArea.Viewport>\n      <ScrollArea.Scrollbar className={styles.ScrollAreaScrollbar}>\n        <ScrollArea.Thumb className={styles.ScrollAreaThumb} />\n      </ScrollArea.Scrollbar>\n      <ScrollArea.Scrollbar orientation=\"horizontal\" className={styles.ScrollAreaScrollbar}>\n        <ScrollArea.Thumb className={styles.ScrollAreaThumb} />\n      </ScrollArea.Scrollbar>\n    </ScrollArea.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/scroll-area/tabs-scroll-area.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { ScrollArea } from '@base-ui/react/scroll-area';\nimport { Tabs } from '@base-ui/react/tabs';\n\nconst ITEMS = Array.from({ length: 32 }, (_, i) => `Item ${i + 1}`);\n\nexport default function ScrollAreaTabsScrollArea() {\n  const [keepMounted, setKeepMounted] = React.useState(false);\n\n  return (\n    <div\n      style={{\n        height: '100dvh',\n        display: 'flex',\n        flexDirection: 'column',\n        padding: 24,\n        gap: 12,\n      }}\n    >\n      <label style={{ display: 'flex', alignItems: 'center', gap: 8 }}>\n        <input\n          type=\"checkbox\"\n          checked={keepMounted}\n          onChange={(event) => setKeepMounted(event.target.checked)}\n        />\n        keepMounted on Scrollable panel\n      </label>\n\n      <p style={{ margin: 0 }}>\n        Repro flow: switch to <strong>Other</strong>, then back to <strong>Scrollable</strong>.\n      </p>\n\n      <Tabs.Root\n        defaultValue=\"scrollable\"\n        style={{\n          display: 'flex',\n          flexDirection: 'column',\n          flex: 1,\n          minHeight: 0,\n        }}\n      >\n        <Tabs.List style={{ display: 'flex', gap: 4, marginBottom: 8 }}>\n          <Tabs.Tab value=\"scrollable\">Scrollable</Tabs.Tab>\n          <Tabs.Tab value=\"other\">Other</Tabs.Tab>\n        </Tabs.List>\n\n        <Tabs.Panel\n          value=\"scrollable\"\n          keepMounted={keepMounted}\n          style={{ display: 'flex', flex: 1, minHeight: 0 }}\n        >\n          <ScrollArea.Root\n            style={{\n              width: '100%',\n              height: '100%',\n              position: 'relative',\n            }}\n          >\n            <ScrollArea.Viewport style={{ maxHeight: '100%' }}>\n              <ScrollArea.Content>\n                <ul\n                  style={{\n                    margin: 0,\n                    padding: 0,\n                    listStyle: 'none',\n                  }}\n                >\n                  {ITEMS.map((item) => (\n                    <li\n                      key={item}\n                      style={{\n                        padding: 12,\n                        borderBottom: '1px solid #eee',\n                      }}\n                    >\n                      {item} - filler text\n                    </li>\n                  ))}\n                </ul>\n              </ScrollArea.Content>\n            </ScrollArea.Viewport>\n\n            <ScrollArea.Scrollbar\n              orientation=\"vertical\"\n              style={{\n                display: 'flex',\n                width: 16,\n                background: '#ddd',\n              }}\n            >\n              <ScrollArea.Thumb\n                style={{\n                  background: '#888',\n                  width: '100%',\n                  padding: 2,\n                }}\n              />\n            </ScrollArea.Scrollbar>\n          </ScrollArea.Root>\n        </Tabs.Panel>\n\n        <Tabs.Panel value=\"other\" style={{ padding: 16 }}>\n          <p style={{ margin: 0 }}>Now switch back to Scrollable.</p>\n        </Tabs.Panel>\n      </Tabs.Root>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/scroll-lock.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useScrollLock } from '@base-ui/utils/useScrollLock';\nimport { isWebKit } from '@base-ui/utils/detectBrowser';\n\nexport default function ScrollLock() {\n  const [enabled, setEnabled] = React.useState(false);\n  const [bodyScrollY, setBodyScrollY] = React.useState(false);\n  const [htmlScrollY, setHtmlScrollY] = React.useState(false);\n  const [longContent, setLongContent] = React.useState(true);\n  const [webkitScrollbars, setWebkitScrollbars] = React.useState(false);\n\n  useScrollLock(enabled);\n\n  React.useEffect(() => {\n    document.body.style.overflowY = bodyScrollY ? 'scroll' : 'visible';\n  }, [bodyScrollY]);\n\n  React.useEffect(() => {\n    document.documentElement.style.overflowY = htmlScrollY ? 'scroll' : '';\n  }, [htmlScrollY]);\n\n  React.useEffect(() => {\n    if (isWebKit && webkitScrollbars) {\n      // WORKAROUND:\n      // WebKit has a bug where ::-webkit-scrollbar styles are not applied immediately\n      const element = document.documentElement;\n      const originalOverflow = element.style.overflow;\n      element.style.overflow = 'hidden';\n      element.getBoundingClientRect();\n      element.style.overflow = originalOverflow;\n    }\n  }, [webkitScrollbars]);\n\n  return (\n    <div>\n      {webkitScrollbars && (\n        <style>\n          {`\n          ::-webkit-scrollbar {\n            width: 0.75rem;\n            height: 0.75rem;\n          }\n          ::-webkit-scrollbar-track {\n            background: var(--color-gray-200);\n          }\n          ::-webkit-scrollbar-thumb {\n            background: var(--color-gray-500);\n          }\n          `}\n        </style>\n      )}\n\n      <h1>useScrollLock</h1>\n      <p>On macOS, enable `Show scroll bars: Always` in `Appearance` Settings.</p>\n      <div\n        style={{\n          position: 'fixed',\n          top: 0,\n          left: 0,\n          width: '100%',\n          background: 'lightgray',\n          textAlign: 'right',\n          padding: 5,\n        }}\n      >\n        Fixed content should not shift\n      </div>\n      <div\n        style={{\n          position: 'fixed',\n          top: 15,\n          display: 'flex',\n          gap: 10,\n          background: 'rgba(0,0,0,0.1)',\n          backdropFilter: 'blur(20px)',\n          padding: 10,\n        }}\n      >\n        <div>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={enabled}\n              onChange={(event) => setEnabled(event.target.checked)}\n            />\n            Scroll lock\n          </label>\n        </div>\n        <div>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={bodyScrollY}\n              onChange={(event) => setBodyScrollY(event.target.checked)}\n            />\n            body `overflow`\n          </label>\n        </div>\n        <div>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={htmlScrollY}\n              onChange={(event) => setHtmlScrollY(event.target.checked)}\n            />\n            html `overflow`\n          </label>\n        </div>\n        <div>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={longContent}\n              onChange={(event) => setLongContent(event.target.checked)}\n            />\n            Long content\n          </label>\n        </div>\n        <div>\n          <label>\n            <input\n              type=\"checkbox\"\n              checked={webkitScrollbars}\n              onChange={(event) => setWebkitScrollbars(event.target.checked)}\n            />\n            Webkit scrollbars\n          </label>\n        </div>\n      </div>\n      {[...Array(longContent ? 100 : 10)].map((_, i) => (\n        <p key={i}>Scroll locking text content</p>\n      ))}\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/select-perf.module.css",
    "content": ".Select {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 0.75rem;\n  height: 2.5rem;\n  padding-left: 0.875rem;\n  padding-right: 0.75rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  cursor: default;\n  user-select: none;\n  min-width: 9rem;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.SelectIcon {\n  display: flex;\n}\n\n.Popup {\n  box-sizing: border-box;\n  padding-block: 0.25rem;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  color: var(--color-gray-900);\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n  max-height: var(--available-height);\n  overflow-y: auto;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  &[data-side='none'] {\n    transition: none;\n    transform: none;\n    opacity: 1;\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Arrow {\n  display: flex;\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n\n.Item {\n  box-sizing: border-box;\n  outline: 0;\n  font-size: 0.875rem;\n  line-height: 1rem;\n  padding-block: 0.5rem;\n  padding-left: 0.625rem;\n  padding-right: 1rem;\n  min-width: var(--anchor-width);\n  display: grid;\n  gap: 0.5rem;\n  align-items: center;\n  grid-template-columns: 0.75rem 1fr;\n  cursor: default;\n  user-select: none;\n\n  [data-side='none'] & {\n    font-size: 1rem;\n    padding-right: 3rem;\n    min-width: calc(var(--anchor-width) + 1rem);\n  }\n\n  &[data-highlighted] {\n    z-index: 0;\n    position: relative;\n    color: var(--color-gray-50);\n  }\n\n  &[data-highlighted]::before {\n    content: '';\n    z-index: -1;\n    position: absolute;\n    inset-block: 0;\n    inset-inline: 0.25rem;\n    border-radius: 0.25rem;\n    background-color: var(--color-gray-900);\n  }\n}\n\n.ItemIndicator {\n  grid-column-start: 1;\n}\n\n.ItemIndicatorIcon {\n  display: block;\n  width: 0.75rem;\n  height: 0.75rem;\n}\n\n.ItemText {\n  grid-column-start: 2;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/select-perf.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Select } from '@base-ui/react/select';\nimport styles from './select-perf.module.css';\n\nconst items = Array.from({ length: 1000 }, (_, i) => `Item ${i + 1}`);\n\nexport default function ExampleSelect() {\n  return (\n    <Select.Root defaultValue=\"Item 1\">\n      <Select.Trigger className={styles.Select}>\n        <Select.Value />\n        <Select.Icon className={styles.SelectIcon}>\n          <ChevronUpDownIcon />\n        </Select.Icon>\n      </Select.Trigger>\n      <Select.Portal>\n        <Select.Positioner className={styles.Positioner} sideOffset={8}>\n          <Select.Popup className={styles.Popup}>\n            <Select.Arrow className={styles.Arrow}>\n              <ArrowSvg />\n            </Select.Arrow>\n            {items.map((item) => (\n              <Select.Item key={item} className={styles.Item} value={item}>\n                <Select.ItemIndicator className={styles.ItemIndicator}>\n                  <CheckIcon className={styles.ItemIndicatorIcon} />\n                </Select.ItemIndicator>\n                <Select.ItemText className={styles.ItemText}>{item}</Select.ItemText>\n              </Select.Item>\n            ))}\n          </Select.Popup>\n        </Select.Positioner>\n      </Select.Portal>\n    </Select.Root>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nfunction ChevronUpDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"8\"\n      height=\"12\"\n      viewBox=\"0 0 8 12\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"1.5\"\n      {...props}\n    >\n      <path d=\"M0.5 4.5L4 1.5L7.5 4.5\" />\n      <path d=\"M0.5 7.5L4 10.5L7.5 7.5\" />\n    </svg>\n  );\n}\n\nfunction CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/slider/inset.module.css",
    "content": ".InsetRoot {\n  border: 1px solid var(--track-border-color);\n  border-radius: 9999px;\n  background: var(--track-bg);\n}\n\n@media (prefers-color-scheme: dark) {\n  .InsetRoot {\n    background: var(--color-gray-200);\n  }\n}\n\n.InsetControl {\n  position: relative;\n  z-index: 10;\n  box-sizing: border-box;\n  border-radius: 9999px;\n  touch-action: none;\n  user-select: none;\n}\n\n.InsetTrack {\n  position: relative;\n  user-select: none;\n}\n\n.InsetTrack[data-orientation='horizontal'] {\n  width: var(--h-track-width);\n  height: var(--h-track-height);\n}\n\n.InsetTrack[data-orientation='vertical'] {\n  width: var(--h-track-height);\n  height: var(--h-track-width);\n}\n\n.InsetIndicator {\n  background: var(--indicator-bg);\n  user-select: none;\n}\n\n@media (prefers-color-scheme: dark) {\n  .InsetIndicator {\n    background: var(--color-gray-950);\n  }\n}\n\n.InsetIndicatorSingle[data-orientation='horizontal'] {\n  border-radius: 9999px 0 0 9999px;\n}\n\n.InsetIndicatorSingle[data-orientation='vertical'] {\n  border-radius: 0 0 9999px 9999px;\n}\n\n.InsetThumb {\n  z-index: 1;\n  width: calc(var(--thumb-radius) * 2);\n  height: calc(var(--thumb-radius) * 2);\n  border-radius: 9999px;\n  background: white;\n  user-select: none;\n  box-shadow: 0 0 3px 0 var(--color-gray-500);\n}\n\n.InsetThumb[data-dragging] {\n  box-shadow: 0 0 2px 1px var(--color-gray-500);\n}\n\n.InsetThumb:has(:focus-visible) {\n  outline: 2px solid var(--color-blue);\n}\n\n.DemoRoot {\n  --base-color: var(--color-blue);\n  --dragging-color: color-mix(in srgb, var(--base-color) 85%, white 15%);\n  --indicator-bg: var(--base-color);\n  --thumb-bg: var(--base-color);\n  --thumb-radius: 0.625rem;\n  --track-bg: var(--color-gray-200);\n  --track-height: 0.625rem;\n  --track-border-color: var(--color-gray-400);\n  --track-width: 6.25rem;\n}\n\n.DemoControl {\n  position: relative;\n  box-sizing: border-box;\n  width: var(--track-width);\n  touch-action: none;\n  user-select: none;\n}\n\n.DemoTrack {\n  width: var(--track-width);\n  height: var(--track-height);\n  border-radius: 9999px;\n  background: var(--track-bg);\n  user-select: none;\n}\n\n.DemoIndicator {\n  background: var(--indicator-bg);\n  user-select: none;\n}\n\n.DemoIndicatorSingle {\n  border-radius: 9999px 0 0 9999px;\n}\n\n.DemoIndicator[data-dragging] {\n  background: var(--dragging-color);\n}\n\n.DemoThumb {\n  z-index: 1;\n  width: calc(var(--thumb-radius) * 2);\n  height: calc(var(--thumb-radius) * 2);\n  border-radius: 9999px;\n  background: var(--indicator-bg);\n  user-select: none;\n}\n\n.DemoThumb[data-dragging] {\n  background: var(--dragging-color);\n  box-shadow: 0 0 0 1px var(--dragging-color);\n}\n\n.DemoThumb:has(:focus-visible) {\n  outline: 2px solid var(--base-color);\n  outline-offset: 2px;\n}\n\n.RadixRoot {\n  position: relative;\n  display: flex;\n  touch-action: none;\n  user-select: none;\n}\n\n.RadixRoot[data-orientation='horizontal'] {\n  align-items: center;\n  width: 240px;\n  height: 20px;\n}\n\n.RadixRoot[data-orientation='vertical'] {\n  justify-content: center;\n  width: 20px;\n  height: 240px;\n}\n\n.RadixControl {\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  width: inherit;\n  height: inherit;\n}\n\n.RadixTrack {\n  border-radius: 9999px;\n  background: var(--black-a10);\n}\n\n.RadixTrack[data-orientation='horizontal'] {\n  width: inherit;\n  height: 3px;\n}\n\n.RadixTrack[data-orientation='vertical'] {\n  width: 3px;\n  height: inherit;\n}\n\n.RadixIndicator {\n  background: white;\n}\n\n.RadixIndicator[data-orientation='horizontal'] {\n  height: 100%;\n  border-radius: 9999px 0 0 9999px;\n}\n\n.RadixIndicator[data-orientation='vertical'] {\n  width: inherit;\n  border-radius: 0 0 9999px 9999px;\n}\n\n.RadixThumb {\n  width: 20px;\n  height: 20px;\n  border-radius: 10px;\n  background: white;\n  box-shadow: 0 2px 10px var(--black-a7);\n}\n\n.RadixThumb:hover,\n.RadixThumb[data-dragging] {\n  background: var(--violet-3);\n}\n\n.RadixThumb:has(:focus-visible) {\n  outline: none;\n  box-shadow: 0 0 0 5px var(--black-a8);\n}\n\n.HeroRow {\n  display: flex;\n  flex-wrap: nowrap;\n  align-items: center;\n  justify-content: center;\n  gap: 4rem;\n  align-self: flex-start;\n  height: 400px;\n  margin-bottom: 2rem;\n  padding: 0 2rem;\n  background: linear-gradient(330deg, var(--purple-9) 0, var(--indigo-9) 100%);\n}\n\n.InsetGrid {\n  display: grid;\n  grid-template-columns: repeat(3, minmax(min-content, auto));\n  gap: 2rem;\n  align-self: flex-start;\n  margin-bottom: 2rem;\n}\n\n.InsetColumn {\n  display: grid;\n  align-content: center;\n  gap: 2rem;\n}\n\n.Caption {\n  align-self: flex-start;\n  margin-bottom: 1rem;\n}\n\n.DemoGrid {\n  display: grid;\n  grid-template-columns: repeat(2, minmax(min-content, auto));\n  gap: 2rem;\n  align-self: flex-start;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/slider/inset.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport clsx from 'clsx';\nimport { Slider } from '@base-ui/react/slider';\nimport '../../../../demo-data/theme/css-modules/theme.css';\nimport styles from './inset.module.css';\n\nfunction InsetSlider(props: Slider.Root.Props) {\n  const value = props.defaultValue ?? props.value;\n  const range = Array.isArray(value);\n  const prefix = React.useId();\n  return (\n    <Slider.Root thumbAlignment=\"edge\" {...props} className={styles.InsetRoot}>\n      <Slider.Control className={styles.InsetControl}>\n        <Slider.Track className={styles.InsetTrack}>\n          <Slider.Indicator\n            className={clsx(styles.InsetIndicator, !range && styles.InsetIndicatorSingle)}\n          />\n\n          {!range && <Slider.Thumb className={styles.InsetThumb} />}\n\n          {range &&\n            value.map((_v, i) => {\n              return <Slider.Thumb key={`${prefix}${i}`} index={i} className={styles.InsetThumb} />;\n            })}\n        </Slider.Track>\n      </Slider.Control>\n    </Slider.Root>\n  );\n}\n\nfunction DemoSlider(props: Slider.Root.Props & { wide?: boolean }) {\n  const { wide, style: styleProp, ...rest } = props;\n  const value = rest.value ?? rest.defaultValue;\n  const range = Array.isArray(value);\n  const prefix = React.useId();\n  return (\n    <Slider.Root\n      thumbAlignment=\"edge\"\n      {...rest}\n      className={styles.DemoRoot}\n      style={\n        {\n          ['--base-color']:\n            props.thumbAlignment !== 'edge-client-only' ? 'var(--color-red)' : 'var(--color-blue)',\n          ['--track-width']: wide ? '320px' : undefined,\n          ...(styleProp ?? {}),\n        } as React.CSSProperties\n      }\n    >\n      <Slider.Control className={styles.DemoControl}>\n        <Slider.Track className={styles.DemoTrack}>\n          <Slider.Indicator\n            className={clsx(styles.DemoIndicator, !range && styles.DemoIndicatorSingle)}\n          />\n\n          {!range && <Slider.Thumb className={styles.DemoThumb} />}\n\n          {range &&\n            value.map((_v, i) => {\n              return <Slider.Thumb key={`${prefix}${i}`} index={i} className={styles.DemoThumb} />;\n            })}\n        </Slider.Track>\n      </Slider.Control>\n    </Slider.Root>\n  );\n}\n\nfunction RadixSlider(props: Slider.Root.Props) {\n  return (\n    <Slider.Root thumbAlignment=\"edge\" {...props} className={styles.RadixRoot}>\n      <Slider.Control className={styles.RadixControl}>\n        <Slider.Track className={styles.RadixTrack}>\n          <Slider.Indicator className={styles.RadixIndicator} />\n          <Slider.Thumb className={styles.RadixThumb} />\n        </Slider.Track>\n      </Slider.Control>\n    </Slider.Root>\n  );\n}\n\nexport default function App() {\n  return (\n    <React.Fragment>\n      <div\n        style={\n          {\n            '--black-a7': 'color(display-p3 0 0 0/0.5)',\n            '--black-a8': 'color(display-p3 0 0 0/0.6)',\n            '--black-a10': 'color(display-p3 0 0 0/0.8)',\n            '--indigo-9': 'color(display-p3 0.276 0.384 0.837)',\n            '--purple-9': 'color(display-p3 0.523 0.318 0.751)',\n            '--violet-3': 'color(display-p3 0.154 0.123 0.256)',\n          } as React.CSSProperties\n        }\n        className={styles.HeroRow}\n      >\n        <RadixSlider defaultValue={40} />\n\n        <RadixSlider defaultValue={40} orientation=\"vertical\" />\n      </div>\n\n      <div\n        style={\n          {\n            '--thumb-radius': '0.625rem',\n            '--h-track-height': 'calc(var(--thumb-radius) * 2)',\n            '--h-track-width': '14rem',\n\n            '--indicator-bg': 'white',\n            '--track-bg': 'var(--color-gray-200)',\n            '--track-border-color': 'var(--color-gray-400)',\n          } as React.CSSProperties\n        }\n        className={styles.InsetGrid}\n      >\n        <InsetSlider defaultValue={45} orientation=\"vertical\" />\n\n        <InsetSlider defaultValue={[25, 50]} orientation=\"vertical\" />\n\n        <div className={styles.InsetColumn}>\n          <InsetSlider defaultValue={45} />\n\n          <InsetSlider defaultValue={[25, 50]} />\n        </div>\n      </div>\n\n      <p className={styles.Caption}>\n        Red <code>thumbAlignment=\"edge\"</code>, Blue <code>thumbAlignment=\"edge-client-only\"</code>\n      </p>\n      <div className={styles.DemoGrid}>\n        <DemoSlider defaultValue={30} thumbAlignment=\"edge-client-only\" />\n\n        <DemoSlider defaultValue={30} wide thumbAlignment=\"edge-client-only\" />\n\n        <DemoSlider defaultValue={30} />\n\n        <DemoSlider defaultValue={30} wide />\n\n        <DemoSlider defaultValue={[20, 64]} thumbAlignment=\"edge-client-only\" />\n\n        <DemoSlider defaultValue={[20, 64]} wide thumbAlignment=\"edge-client-only\" />\n\n        <DemoSlider defaultValue={[20, 64]} />\n\n        <DemoSlider defaultValue={[20, 64]} wide />\n      </div>\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/slider/slider.module.css",
    "content": ".Root {\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n  grid-gap: 0.25rem;\n  grid-row-gap: 0.5rem;\n  width: 14rem;\n}\n\n.Label {\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 700;\n  color: var(--color-gray-900);\n}\n\n.Value {\n  grid-column-start: 2;\n  margin: 0;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  color: var(--color-gray-900);\n  text-align: end;\n  white-space: nowrap;\n}\n\n.Control {\n  grid-column: 1 / 3;\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  width: 100%;\n  padding: 0.75rem 0.25rem;\n  touch-action: none;\n  user-select: none;\n}\n\n.Track {\n  width: 100%;\n  height: 0.25rem;\n  background-color: var(--color-gray-200);\n  box-shadow: inset 0 0 0 1px var(--color-gray-200);\n  border-radius: 0.25rem;\n  user-select: none;\n}\n\n.Indicator {\n  border-radius: 0.25rem;\n  background-color: var(--color-gray-700);\n  user-select: none;\n}\n\n.Thumb {\n  width: 1rem;\n  height: 1rem;\n  border-radius: 100%;\n  background-color: white;\n  outline: 1px solid var(--color-gray-300);\n  user-select: none;\n\n  &:has(:focus-visible) {\n    outline: 2px solid var(--color-blue);\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/slider/slider.tsx",
    "content": "'use client';\nimport { Slider } from '@base-ui/react/slider';\nimport { DirectionProvider } from '@base-ui/react/direction-provider';\nimport { SettingsMetadata, useExperimentSettings } from '../_components/SettingsPanel';\nimport '../../../../demo-data/theme/css-modules/theme.css';\nimport styles from './slider.module.css';\nimport verticalStyles from './vertical.module.css';\nimport smallStyles from './small.module.css';\n\ninterface Settings extends Record<string, boolean> {}\n\nexport const settingsMetadata: SettingsMetadata<Settings> = {\n  rtl: {\n    type: 'boolean',\n    label: 'RTL',\n    default: false,\n  },\n};\n\nfunction DelayUntilRepeat() {\n  // small numeric range with a small number of steps\n  return (\n    <Slider.Root\n      defaultValue={0}\n      min={0}\n      max={5}\n      step={1}\n      className={smallStyles.Root}\n      aria-labelledby=\"label-5\"\n    >\n      <span className={smallStyles.Label} id=\"label-5\">\n        Delay until repeat\n      </span>\n      <Slider.Control className={smallStyles.Control}>\n        <Slider.Track className={smallStyles.Track}>\n          <span className={smallStyles.Mark} />\n          <span className={smallStyles.Mark} />\n          <span className={smallStyles.Mark} />\n          <span className={smallStyles.Mark} />\n          <span className={smallStyles.Mark} />\n          <span className={smallStyles.Mark} />\n          <Slider.Thumb\n            /* getAriaValueText could be used so more meaningful values are announced  */\n            className={smallStyles.Thumb}\n          />\n        </Slider.Track>\n      </Slider.Control>\n      <small className={smallStyles.Small}>Long</small>\n      <small className={smallStyles.Small}>Short</small>\n    </Slider.Root>\n  );\n}\n\nfunction Volume() {\n  return (\n    <Slider.Root\n      defaultValue={0}\n      min={0}\n      max={100}\n      step={1}\n      className={styles.Root}\n      aria-labelledby=\"label-1\"\n    >\n      <span className={styles.Label} id=\"label-1\">\n        Volume\n      </span>\n      <Slider.Value className={styles.Value} />\n      <Slider.Control className={styles.Control}>\n        <Slider.Track className={styles.Track}>\n          <Slider.Indicator className={styles.Indicator} />\n          <Slider.Thumb className={styles.Thumb} />\n        </Slider.Track>\n      </Slider.Control>\n    </Slider.Root>\n  );\n}\n\nfunction Brightness() {\n  return (\n    <Slider.Root\n      defaultValue={0}\n      min={0}\n      max={100}\n      step={1}\n      className={verticalStyles.Root}\n      aria-labelledby=\"label-2\"\n      orientation=\"vertical\"\n    >\n      <Slider.Value className={verticalStyles.Value} />\n      <Slider.Control className={verticalStyles.Control}>\n        <Slider.Track className={verticalStyles.Track}>\n          <Slider.Indicator className={verticalStyles.Indicator} />\n          <Slider.Thumb className={verticalStyles.Thumb} />\n        </Slider.Track>\n      </Slider.Control>\n      <span className={verticalStyles.Label} id=\"label-2\">\n        Brightness\n      </span>\n    </Slider.Root>\n  );\n}\n\nfunction PriceRange() {\n  return (\n    <Slider.Root\n      defaultValue={[500, 1200]}\n      min={100}\n      max={2000}\n      step={1}\n      minStepsBetweenValues={1}\n      className={styles.Root}\n      aria-labelledby=\"label-3\"\n      format={{\n        style: 'currency',\n        currency: 'EUR',\n      }}\n      locale=\"de-DE\"\n      style={{ width: '18rem' }}\n    >\n      <span className={styles.Label} id=\"label-3\">\n        Price Range\n      </span>\n      <Slider.Value className={styles.Value} />\n      <Slider.Control className={styles.Control}>\n        <Slider.Track className={styles.Track}>\n          <Slider.Indicator className={styles.Indicator} />\n          <Slider.Thumb index={0} className={styles.Thumb} />\n          <Slider.Thumb index={1} className={styles.Thumb} />\n        </Slider.Track>\n      </Slider.Control>\n    </Slider.Root>\n  );\n}\n\nfunction TemperatureRange() {\n  return (\n    <Slider.Root\n      defaultValue={[20, 24]}\n      min={10}\n      max={32}\n      step={0.5}\n      orientation=\"vertical\"\n      className={verticalStyles.Root}\n      aria-labelledby=\"label-3\"\n      format={{\n        style: 'unit',\n        unit: 'celsius',\n        minimumFractionDigits: 1,\n        maximumFractionDigits: 1,\n      }}\n      style={{ width: '18rem' }}\n    >\n      <Slider.Value className={verticalStyles.Value} />\n      <Slider.Control className={verticalStyles.Control}>\n        <Slider.Track className={verticalStyles.Track}>\n          <Slider.Indicator className={verticalStyles.Indicator} />\n          <Slider.Thumb index={0} className={verticalStyles.Thumb} />\n          <Slider.Thumb index={1} className={verticalStyles.Thumb} />\n        </Slider.Track>\n      </Slider.Control>\n      <span className={verticalStyles.Label} id=\"label-3\">\n        Temperature Range\n      </span>\n    </Slider.Root>\n  );\n}\n\nexport default function App() {\n  const { settings } = useExperimentSettings<Settings>();\n  const direction = settings.rtl ? 'rtl' : 'ltr';\n  return (\n    <DirectionProvider direction={direction}>\n      <div\n        dir={direction}\n        style={{\n          padding: '4rem 8rem',\n          display: 'flex',\n          flexDirection: 'column',\n          gap: '5rem',\n          alignItems: 'flex-start',\n        }}\n      >\n        <DelayUntilRepeat />\n        <Volume />\n        <Brightness />\n        <PriceRange />\n        <TemperatureRange />\n      </div>\n    </DirectionProvider>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/slider/small.module.css",
    "content": ".Root {\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n  grid-gap: 0.25rem;\n  grid-row-gap: 0.5rem;\n  width: 14rem;\n}\n\n.Label {\n  grid-column: 1 / 3;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 500;\n  color: var(--color-gray-900);\n}\n\n.Control {\n  grid-column: 1 / 3;\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  width: 100%;\n  padding: 0.75rem 0.25rem;\n  touch-action: none;\n  user-select: none;\n}\n\n.Track {\n  width: 100%;\n  height: 0.625rem;\n\n  display: flex;\n  flex-flow: row nowrap;\n  justify-content: space-between;\n  user-select: none;\n\n  &::before {\n    content: '';\n    width: 100%;\n    height: 0.25rem;\n    position: absolute;\n    top: 30%;\n    background-color: var(--color-gray-200);\n  }\n}\n\n.Indicator {\n  border-radius: 0.25rem;\n  background-color: var(--color-gray-700);\n  user-select: none;\n}\n\n.Thumb {\n  width: 0.5rem;\n  height: 1.5rem;\n  border-radius: 9999px;\n  background-color: white;\n  outline: 1px solid var(--color-gray-300);\n  user-select: none;\n\n  &:has(:focus-visible) {\n    outline: 2px solid var(--color-blue);\n  }\n}\n\n.Mark {\n  z-index: 0;\n  width: 1px;\n  height: 100%;\n  background-color: var(--color-gray-500);\n}\n\n.Small {\n  font-size: 0.75rem;\n\n  &:last-of-type {\n    text-align: end;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/slider/vertical.module.css",
    "content": ".Root {\n  align-self: center;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  gap: 0.25rem;\n}\n\n.Label {\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 700;\n  color: var(--color-gray-900);\n}\n\n.Value {\n  grid-column-start: 2;\n  margin: 0;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  color: var(--color-gray-900);\n}\n\n.Control {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  height: 14rem;\n  padding: 0.75rem;\n  touch-action: none;\n  user-select: none;\n}\n\n.Track {\n  height: 100%;\n  width: 0.25rem;\n  background-color: var(--color-gray-200);\n  box-shadow: inset 0 0 0 1px var(--color-gray-200);\n  border-radius: 0.25rem;\n  user-select: none;\n}\n\n.Indicator {\n  border-radius: 0.25rem;\n  background-color: var(--color-gray-700);\n  user-select: none;\n}\n\n.Thumb {\n  width: 1rem;\n  height: 1rem;\n  border-radius: 100%;\n  background-color: white;\n  outline: 1px solid var(--color-gray-300);\n  user-select: none;\n\n  &:has(:focus-visible) {\n    outline: 2px solid var(--color-blue);\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/storeWithControlledValues.module.css",
    "content": ".Container {\n  width: 32rem;\n  margin: 0 auto;\n}\n\n.Heading {\n  margin-bottom: 1rem;\n  font-size: var(--text-lg);\n  line-height: var(--text-lg--line-height);\n  letter-spacing: var(--text-lg--letter-spacing);\n}\n\n.Row {\n  display: flex;\n  gap: 0.5rem;\n  margin-bottom: 4rem;\n  align-items: baseline;\n}\n\n.Button {\n  border: 1px solid var(--color-gray-300);\n  border-radius: 0.25rem;\n  padding: 0.25rem 1rem;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/storeWithControlledValues.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useRefWithInit } from '@base-ui/utils/useRefWithInit';\nimport { ReactStore } from '@base-ui/utils/store';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport styles from './storeWithControlledValues.module.css';\n\nexport default function Playground() {\n  const [open, setOpen] = React.useState(false);\n  const [value, setValue] = React.useState(0);\n\n  return (\n    <div className={styles.Container}>\n      <h2 className={styles.Heading}>Controlled mode</h2>\n      <div className={styles.Row}>\n        <button type=\"button\" className={styles.Button} onClick={() => setOpen((o) => !o)}>\n          Toggle externally\n        </button>\n        <ControllableComponent\n          open={open}\n          onOpenChange={(nextOpen, reason) => {\n            setOpen(nextOpen);\n            console.log('reason', reason);\n          }}\n          value={value}\n        />\n      </div>\n\n      <h2 className={styles.Heading}>Uncontrolled mode (open by default)</h2>\n      <div className={styles.Row}>\n        <ControllableComponent\n          defaultOpen={true}\n          onOpenChange={(_, reason) => {\n            console.log('reason', reason);\n          }}\n          value={value}\n        />\n      </div>\n\n      <h2 className={styles.Heading}>Uncontrolled mode (closed by default)</h2>\n      <div className={styles.Row}>\n        <ControllableComponent\n          defaultOpen={false}\n          onOpenChange={(_, reason) => {\n            console.log('reason', reason);\n          }}\n          value={value}\n        />\n      </div>\n\n      <h2>Value updater</h2>\n      <div className={styles.Row}>\n        <input\n          type=\"number\"\n          value={value}\n          onChange={(event) => setValue(event.target.valueAsNumber)}\n        />\n      </div>\n    </div>\n  );\n}\n\ninterface Props {\n  open?: boolean;\n  defaultOpen?: boolean;\n  onOpenChange?: (open: boolean, reason: string) => void;\n  value?: number;\n}\n\nfunction ControllableComponent(props: Props) {\n  const store = useRefWithInit(\n    () =>\n      new ReactStore(\n        {\n          open: props.defaultOpen ?? false,\n          openProp: props.open,\n          value: 0,\n        },\n        {},\n        selectors,\n      ),\n  ).current;\n\n  store.useControlledProp('openProp', props.open);\n  store.useSyncedValue('value', props.value ?? 0);\n\n  const open = store.useState('open');\n  const value = store.useState('value');\n\n  const handleClick = useStableCallback(() => {\n    store.set('open', !open);\n    props.onOpenChange?.(!open, 'toggle-button');\n  });\n\n  return (\n    <React.Fragment>\n      <span>open: {open?.toString() ?? 'undefined'}</span>\n      <ChildComponent store={store} />\n      <button type=\"button\" className={styles.Button} onClick={handleClick}>\n        Toggle internally\n      </button>\n      <span>value: {value}</span>\n    </React.Fragment>\n  );\n}\n\ninterface ChildProps {\n  store: ReactStore<State, {}, typeof selectors>;\n}\n\nfunction ChildComponent(props: ChildProps) {\n  const open = props.store.useState('open');\n  return <span>child sees open: {open.toString()}</span>;\n}\n\ninterface State {\n  open: boolean;\n  openProp: boolean | undefined;\n  value: number;\n}\n\nconst selectors = {\n  open: (state: State) => state.openProp ?? state.open,\n  value: (state: State) => state.value,\n};\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/tabs-animations.module.css",
    "content": ".experiment {\n  display: flex;\n  flex-direction: column;\n  gap: 1.5rem;\n  padding: 1.5rem;\n  color: var(--color-gray-900);\n  font-family: system-ui, sans-serif;\n}\n\n.header {\n  display: flex;\n  flex-direction: column;\n  gap: 0.35rem;\n}\n\n.subtitle {\n  margin: 0;\n  color: var(--color-gray-600);\n  font-size: 0.95rem;\n}\n\n.tabs {\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.75rem;\n  background: var(--color-gray-50);\n  overflow: hidden;\n\n  &[data-orientation='vertical'] {\n    display: grid;\n    grid-template-columns: minmax(10rem, 12rem) 1fr;\n    min-height: 14rem;\n  }\n}\n\n.list {\n  display: flex;\n  position: relative;\n  z-index: 0;\n  padding: 0.35rem;\n  gap: 0.25rem;\n  box-shadow: inset 0 -1px var(--color-gray-200);\n  background: var(--color-gray-50);\n\n  &[data-orientation='vertical'] {\n    flex-direction: column;\n    padding: 0.5rem;\n    box-shadow: inset -1px 0 var(--color-gray-200);\n  }\n}\n\n.tab {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  flex-shrink: 0;\n  border: 0;\n  margin: 0;\n  outline: 0;\n  background: none;\n  appearance: none;\n  color: var(--color-gray-600);\n  font-family: inherit;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 600;\n  user-select: none;\n  white-space: nowrap;\n  word-break: keep-all;\n  padding-inline: 0.65rem;\n  padding-block: 0.25rem;\n  border-radius: 0.35rem;\n\n  &[data-orientation='vertical'] {\n    height: 2rem;\n    justify-content: flex-start;\n  }\n\n  &[data-active] {\n    color: var(--color-gray-900);\n  }\n\n  &[data-disabled] {\n    color: var(--color-gray-400);\n  }\n\n  @media (hover: hover) {\n    &:not([data-disabled]):hover {\n      color: var(--color-gray-900);\n    }\n  }\n\n  &:focus-visible {\n    position: relative;\n  }\n\n  &:focus-visible::before {\n    content: '';\n    position: absolute;\n    inset: 0;\n    border-radius: 0.35rem;\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.indicator {\n  flex-shrink: 0;\n  position: absolute;\n  z-index: -1;\n  top: var(--active-tab-top);\n  right: var(--active-tab-right);\n  bottom: var(--active-tab-bottom);\n  left: var(--active-tab-left);\n  border-radius: 0.35rem;\n  background-color: var(--color-gray-100);\n  transition-property: left, right, top, bottom;\n  transition-duration: 180ms;\n  transition-timing-function: ease-in-out;\n\n  &[data-orientation='vertical'] {\n    height: var(--active-tab-height);\n  }\n\n  &.elastic {\n    &[data-activation-direction='right'] {\n      transition:\n        left 0.55s 0.1s,\n        right 0.25s,\n        top 0.25s,\n        bottom 0.25s;\n    }\n\n    &[data-activation-direction='down'] {\n      transition:\n        top 0.55s 0.1s,\n        bottom 0.25s;\n    }\n\n    &[data-activation-direction='left'] {\n      transition:\n        left 0.25s,\n        right 0.55s 0.1s,\n        top 0.25s,\n        bottom 0.25s;\n    }\n\n    &[data-activation-direction='up'] {\n      transition:\n        top 0.25s,\n        bottom 0.55s 0.1s;\n    }\n  }\n}\n\n.panels {\n  position: relative;\n  min-height: 12rem;\n  overflow: hidden;\n  background: var(--color-gray-50);\n}\n\n.panel {\n  position: absolute;\n  inset: 0;\n  display: grid;\n  place-items: center;\n  padding: 1.5rem;\n  outline: none;\n  transform: translate3d(0, 0, 0);\n  filter: blur(0);\n  transition:\n    opacity 320ms var(--ease-out-fast),\n    transform 650ms var(--ease-out-fast),\n    filter 320ms var(--ease-out-fast);\n  will-change: opacity, transform, filter;\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -2px;\n    border-radius: 0.75rem;\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    filter: blur(12px);\n  }\n\n  &[data-activation-direction='right'] {\n    &[data-starting-style] {\n      transform: translateX(100%);\n    }\n\n    &[data-ending-style] {\n      transform: translateX(-100%);\n    }\n  }\n\n  &[data-activation-direction='left'] {\n    &[data-starting-style] {\n      transform: translateX(-100%);\n    }\n\n    &[data-ending-style] {\n      transform: translateX(100%);\n    }\n  }\n\n  &[data-activation-direction='down'] {\n    &[data-starting-style] {\n      transform: translateY(100%);\n    }\n\n    &[data-ending-style] {\n      transform: translateY(-100%);\n    }\n  }\n\n  &[data-activation-direction='up'] {\n    &[data-starting-style] {\n      transform: translateY(-100%);\n    }\n\n    &[data-ending-style] {\n      transform: translateY(100%);\n    }\n  }\n}\n\n.panelContent {\n  display: flex;\n  flex-direction: column;\n  gap: 0.6rem;\n  width: min(26rem, 100%);\n  text-align: center;\n}\n\n.panelTitle {\n  margin: 0;\n  font-size: 1.25rem;\n}\n\n.panelDescription {\n  margin: 0;\n  color: var(--color-gray-600);\n  font-size: 0.95rem;\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .panel {\n    transition-duration: 0ms;\n  }\n\n  .panel[data-starting-style],\n  .panel[data-ending-style] {\n    filter: none;\n    transform: none;\n  }\n\n  .indicator {\n    transition-duration: 0ms;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/tabs-animations.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport clsx from 'clsx';\nimport { DirectionProvider } from '@base-ui/react/direction-provider';\nimport { Tabs } from '@base-ui/react/tabs';\nimport { SettingsMetadata, useExperimentSettings } from './_components/SettingsPanel';\nimport '../../../demo-data/theme/css-modules/theme.css';\nimport classes from './tabs-animations.module.css';\n\nconst PANELS = [\n  {\n    title: 'Overview',\n    description: 'Track progress across releases and monitor status updates.',\n  },\n  {\n    title: 'Metrics',\n    description: 'Compare usage trends and adoption between environments.',\n  },\n  {\n    title: 'Activity',\n    description: 'Review recent changes and confirm deployment timings.',\n  },\n  {\n    title: 'Settings',\n    description: 'Adjust notifications, access, and panel behaviors.',\n  },\n];\n\nexport default function TabsAnimationsExperiment() {\n  const { settings } = useExperimentSettings<Settings>();\n  const [value, setValue] = React.useState<number | null>(0);\n\n  return (\n    <div className={classes.experiment} dir={settings.direction}>\n      <DirectionProvider direction={settings.direction}>\n        <header className={classes.header}>\n          <h1>Tabs panel transitions</h1>\n          <p className={classes.subtitle}>\n            Panels animate based on data-starting-style, data-ending-style, and activation\n            direction.\n          </p>\n        </header>\n        <Tabs.Root\n          className={classes.tabs}\n          value={value}\n          onValueChange={setValue}\n          orientation={settings.orientation}\n        >\n          <Tabs.List className={classes.list}>\n            {PANELS.map((panel, index) => (\n              <Tabs.Tab className={classes.tab} key={panel.title} value={index}>\n                {panel.title}\n              </Tabs.Tab>\n            ))}\n            <Tabs.Indicator\n              className={clsx(classes.indicator, settings.elastic && classes.elastic)}\n            />\n          </Tabs.List>\n          <div className={classes.panels}>\n            {PANELS.map((panel, index) => (\n              <Tabs.Panel\n                className={classes.panel}\n                key={panel.title}\n                keepMounted={settings.keepMounted}\n                value={index}\n              >\n                <div className={classes.panelContent}>\n                  <h2 className={classes.panelTitle}>{panel.title}</h2>\n                  <p className={classes.panelDescription}>{panel.description}</p>\n                </div>\n              </Tabs.Panel>\n            ))}\n          </div>\n        </Tabs.Root>\n      </DirectionProvider>\n    </div>\n  );\n}\n\ninterface Settings {\n  direction: 'ltr' | 'rtl';\n  orientation: 'horizontal' | 'vertical';\n  keepMounted: boolean;\n  elastic: boolean;\n}\n\nexport const settingsMetadata: SettingsMetadata<Settings> = {\n  direction: {\n    type: 'string',\n    label: 'Direction',\n    options: ['ltr', 'rtl'],\n    default: 'ltr',\n  },\n  orientation: {\n    type: 'string',\n    label: 'Orientation',\n    options: ['horizontal', 'vertical'],\n    default: 'horizontal',\n  },\n  keepMounted: {\n    type: 'boolean',\n    label: 'Keep mounted',\n    default: false,\n  },\n  elastic: {\n    type: 'boolean',\n    label: 'Elastic indicator',\n    default: false,\n  },\n};\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/tabs-overflow.module.css",
    "content": ".experiment {\n  display: flex;\n  flex-direction: column;\n  gap: 1.25rem;\n  max-width: 640px;\n  margin: 0 auto;\n  padding-block: 2rem;\n}\n\n.variant {\n  border: 1px solid var(--color-gray-200);\n  border-radius: 1rem;\n  padding: 1rem;\n  background-color: var(--color-white);\n  box-shadow: 0 10px 25px rgb(15 23 42 / 0.08);\n  display: flex;\n  flex-direction: column;\n  gap: 0.5rem;\n}\n\n.variantTitle {\n  margin: 0;\n  font-size: 1rem;\n  font-weight: 600;\n  color: var(--color-gray-900);\n}\n\n.variantSubtitle {\n  margin: 0;\n  font-size: 0.875rem;\n  color: var(--color-gray-500);\n}\n\n.variantBody {\n  display: flex;\n  width: 100%;\n  justify-content: center;\n}\n\n.tabs {\n  max-width: 100%;\n}\n\n.list {\n  display: flex;\n  border-radius: 0.75rem;\n  position: relative;\n  min-height: 3rem;\n  width: 100%;\n  overflow-x: auto;\n  max-width: 100%;\n  z-index: 1;\n}\n\n.tab {\n  padding-block: 0.35rem;\n  padding-inline: 0.75rem;\n  border-radius: 0.5rem;\n  border: none;\n  background: none;\n  color: var(--color-gray-800);\n  font-weight: 600;\n  font-size: inherit;\n  line-height: 1.25rem;\n  white-space: nowrap;\n  flex-shrink: 0;\n  z-index: 1;\n}\n\n.tab[data-active] {\n  color: white;\n}\n\n.tab:focus-visible {\n  outline: 2px solid var(--color-blue);\n  outline-offset: 2px;\n}\n\n.indicator {\n  position: absolute;\n  left: 0;\n  top: 50%;\n  translate: var(--active-tab-left, 0) -50%;\n  width: var(--active-tab-width, 0);\n  height: var(--active-tab-height, 0);\n  border-radius: 0.25rem;\n  background-color: var(--color-gray-900);\n  transition-property: translate, width, height;\n  transition-duration: 200ms;\n  transition-timing-function: ease-in-out;\n  z-index: -1;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/tabs-overflow.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Tabs } from '@base-ui/react/tabs';\nimport classes from './tabs-overflow.module.css';\n\nconst OVERFLOW_LABELS = Array.from({ length: 20 }, (_, index) => `Tab ${index + 1}`);\n\nconst VARIANTS = [\n  {\n    title: 'Micro copy',\n    description: 'Tiny labels emulate condensed navigation without wrapping.',\n    fontSize: '0.75rem',\n    labels: ['Home', 'Docs', 'Blog', 'Team'],\n  },\n  {\n    title: 'Standard size',\n    description: 'Default typography with four concise tabs.',\n    styles: {\n      fontSize: '1rem',\n      border: '2px solid black',\n    },\n    labels: ['Overview', 'Features', 'Releases', 'Support'],\n  },\n  {\n    title: 'All caps',\n    description: 'Uppercase words stretch to the indicator boundary.',\n    styles: {\n      fontSize: '0.85rem',\n      border: '4px solid black',\n    },\n    labels: ['CODE', 'GUIDES', 'API', 'STATUS'],\n  },\n  {\n    title: 'Overflow check',\n    description: 'Twenty tabs make the list exceed the container and force scrolling.',\n    fontSize: '0.85rem',\n    labels: OVERFLOW_LABELS,\n  },\n];\n\nexport default function TabsOverflowExperiment() {\n  const [activeValues, setActiveValues] = React.useState(() => VARIANTS.map(() => 0));\n\n  const handleValueChange = (variantIndex: number) => (value: number) => {\n    setActiveValues((prev) => {\n      const next = [...prev];\n      next[variantIndex] = value;\n      return next;\n    });\n  };\n\n  return (\n    <section className={classes.experiment}>\n      <h1>Indicator precision check</h1>\n      {VARIANTS.map((variant, index) => (\n        <article className={classes.variant} key={variant.title}>\n          <div>\n            <h2 className={classes.variantTitle}>{variant.title}</h2>\n            <p className={classes.variantSubtitle}>{variant.description}</p>\n          </div>\n          <div className={classes.variantBody}>\n            <Tabs.Root\n              className={classes.tabs}\n              value={activeValues[index]}\n              onValueChange={handleValueChange(index)}\n            >\n              <Tabs.List className={classes.list} style={variant.styles}>\n                {variant.labels.map((label, tabIndex) => (\n                  <Tabs.Tab className={classes.tab} key={label} value={tabIndex}>\n                    {label}\n                  </Tabs.Tab>\n                ))}\n                <Tabs.Indicator className={classes.indicator} renderBeforeHydration />\n              </Tabs.List>\n            </Tabs.Root>\n          </div>\n        </article>\n      ))}\n    </section>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/tabs.module.css",
    "content": ".tabs {\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n\n  &[data-orientation='vertical'] {\n    display: flex;\n    flex-direction: row;\n    gap: 0.5rem;\n    box-shadow: inset -1px 0 var(--color-gray-200);\n    min-width: 400px;\n  }\n}\n\n.list {\n  display: flex;\n  position: relative;\n  z-index: 0;\n  padding: 0.25rem;\n  gap: 0.25rem;\n  box-shadow: inset 0 -1px var(--color-gray-200);\n\n  &[data-orientation='vertical'] {\n    flex-direction: column;\n    padding: 0.5rem;\n    box-shadow: inset -1px 0 var(--color-gray-200);\n  }\n}\n\n.tab {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  flex-shrink: 0;\n  border: 0;\n  margin: 0;\n  outline: 0;\n  background: none;\n  appearance: none;\n  color: var(--color-gray-600);\n  font-family: inherit;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 500;\n  user-select: none;\n  white-space: nowrap;\n  word-break: keep-all;\n  padding-inline: 0.5rem;\n  padding-block: 0;\n  height: 1.5rem;\n\n  &[data-orientation='vertical'] {\n    height: 2rem;\n  }\n\n  &[data-active] {\n    color: var(--color-gray-900);\n  }\n\n  &[data-disabled] {\n    color: var(--color-gray-400);\n  }\n\n  @media (hover: hover) {\n    &:not([data-disabled]):hover {\n      color: var(--color-gray-900);\n    }\n  }\n\n  &:focus-visible {\n    position: relative;\n\n    &::before {\n      content: '';\n      position: absolute;\n      inset: 0;\n      border-radius: 0.25rem;\n      outline: 2px solid var(--color-blue);\n      outline-offset: -1px;\n    }\n  }\n}\n\n.indicator {\n  flex-shrink: 0;\n  position: absolute;\n  z-index: -1;\n  /* easier to debug than a single `inset:` */\n  top: var(--active-tab-top);\n  right: var(--active-tab-right);\n  bottom: var(--active-tab-bottom);\n  left: var(--active-tab-left);\n  border-radius: 0.25rem;\n  background-color: var(--color-gray-100);\n  transition-property: left, right, top, bottom;\n  transition-duration: 200ms;\n  transition-timing-function: ease-in-out;\n\n  &[data-orientation='vertical'] {\n    /*\n      handles an edge case when the tabs list is overflowing vertically,\n      the indicator could be incorrectly sized when arrow keys change the tab\n      and scroll the container at the same time\n    */\n    height: var(--active-tab-height);\n  }\n\n  &.elastic {\n    &[data-activation-direction='right'] {\n      transition:\n        left 0.6s 0.1s,\n        right 0.3s,\n        top 0.3s,\n        bottom 0.3s;\n    }\n\n    &[data-activation-direction='down'] {\n      transition:\n        top 0.6s 0.1s,\n        bottom 0.3s;\n    }\n\n    &[data-activation-direction='left'] {\n      transition:\n        left 0.3s,\n        right 0.6s 0.1s,\n        top 0.3s,\n        bottom 0.3s;\n    }\n\n    &[data-activation-direction='up'] {\n      transition:\n        top 0.3s,\n        bottom 0.6s 0.1s;\n    }\n  }\n}\n\n.panel {\n  position: relative;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  outline: 0;\n  flex: 1 0 auto;\n\n  &[data-hidden] {\n    display: none;\n  }\n\n  &[data-orientation='horizontal'] {\n    height: 8rem;\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n    border-radius: 0.375rem;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/tabs.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport clsx from 'clsx';\nimport { DirectionProvider } from '@base-ui/react/direction-provider';\nimport { Tabs } from '@base-ui/react/tabs';\nimport { SettingsMetadata, useExperimentSettings } from './_components/SettingsPanel';\nimport '../../../demo-data/theme/css-modules/theme.css';\nimport classes from './tabs.module.css';\n\nexport default function TabsExperiment() {\n  const [value, setValue] = React.useState<string | number | null>(0);\n  const { settings } = useExperimentSettings<Settings>();\n\n  const direction = settings.direction;\n\n  return (\n    <div dir={direction}>\n      <DirectionProvider direction={direction}>\n        <h1>Tabs</h1>\n        <Tabs.Root\n          className={classes.tabs}\n          value={value}\n          onValueChange={(val) => setValue(val)}\n          orientation={settings.orientation}\n        >\n          <Tabs.List className={classes.list} activateOnFocus={settings.activateOnFocus}>\n            <Tabs.Tab className={classes.tab} value={0}>\n              Code\n            </Tabs.Tab>\n            <Tabs.Tab className={classes.tab} value={1}>\n              Issues\n            </Tabs.Tab>\n            <Tabs.Tab className={classes.tab} value={2}>\n              Pull Requests\n            </Tabs.Tab>\n            <Tabs.Tab className={classes.tab} value={3} disabled>\n              Discussions\n            </Tabs.Tab>\n            <Tabs.Tab className={classes.tab} value={4}>\n              Actions\n            </Tabs.Tab>\n            <Tabs.Indicator\n              className={clsx(classes.indicator, settings.elasticIndicator && classes.elastic)}\n            />\n          </Tabs.List>\n          <Tabs.Panel className={classes.panel} value={0} keepMounted>\n            Code panel\n          </Tabs.Panel>\n          <Tabs.Panel className={classes.panel} value={1} keepMounted>\n            Issues panel\n          </Tabs.Panel>\n          <Tabs.Panel className={classes.panel} value={2} keepMounted>\n            Pull Requests panel\n          </Tabs.Panel>\n          <Tabs.Panel className={classes.panel} value={3} keepMounted>\n            Discussions panel\n          </Tabs.Panel>\n          <Tabs.Panel className={classes.panel} value={4} keepMounted>\n            Actions panel\n          </Tabs.Panel>\n        </Tabs.Root>\n      </DirectionProvider>\n    </div>\n  );\n}\n\ninterface Settings {\n  direction: 'ltr' | 'rtl';\n  orientation: 'horizontal' | 'vertical';\n  activateOnFocus: boolean;\n  elasticIndicator: boolean;\n}\n\nexport const settingsMetadata: SettingsMetadata<Settings> = {\n  direction: {\n    type: 'string',\n    label: 'Direction',\n    options: ['ltr', 'rtl'],\n    default: 'ltr',\n  },\n  orientation: {\n    type: 'string',\n    label: 'Orientation',\n    options: ['horizontal', 'vertical'],\n    default: 'horizontal',\n  },\n  activateOnFocus: {\n    type: 'boolean',\n    label: 'Activate on focus',\n    default: true,\n  },\n  elasticIndicator: {\n    type: 'boolean',\n    label: 'Elastic indicator',\n  },\n};\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/toast.module.css",
    "content": ".viewport {\n  width: 300px;\n  position: fixed;\n  left: 50%;\n  top: 20px;\n  transform: translateX(-50%);\n}\n\n.root {\n  position: absolute;\n  left: 0;\n  right: 0;\n  margin: 0 auto;\n  transform-origin: top center;\n  background: white;\n  padding: 1rem;\n  width: 300px;\n  box-shadow: 0 2px 10px rgb(0 0 0 / 0.1);\n  border-radius: 10px;\n  z-index: calc(2147483647 - var(--toast-index));\n  transition:\n    transform 0.5s cubic-bezier(0.22, 1, 0.36, 1),\n    opacity 0.5s;\n  transform: translateY(calc(var(--toast-index) * 15px)) scale(calc(1 - (var(--toast-index) * 0.1)));\n  opacity: 1;\n  --gap: 10px;\n  user-select: none;\n\n  &::after {\n    content: '';\n    position: absolute;\n    left: 0;\n    top: 100%;\n    width: 100%;\n    height: calc(var(--gap) + 1px);\n  }\n\n  &[data-expanded] {\n    transform: translateY(calc(var(--toast-offset-y) + calc(var(--toast-index) * var(--gap))));\n  }\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    transform: translateY(-150%);\n  }\n\n  &[data-ending-style] {\n    transition-duration: 0.2s;\n    transition-timing-function: ease-in;\n  }\n\n  &[data-swipe='end'][data-ending-style] {\n    transition-duration: calc(0.3s / var(--toast-momentum, 1));\n    transition-timing-function: linear;\n  }\n}\n\n.content {\n  overflow: hidden;\n  transition: opacity 0.25s;\n\n  &[data-behind] {\n    opacity: 0;\n  }\n\n  &[data-expanded] {\n    opacity: 1;\n  }\n}\n\n.title {\n  font-weight: 600;\n  margin-bottom: 4px;\n}\n\n.button {\n  background: royalblue;\n  color: white;\n  padding: 0.5rem;\n  border-radius: 5px;\n  text-align: center;\n  margin-top: 1rem;\n}\n\n.close {\n  position: absolute;\n  right: 0;\n  top: 0;\n  padding: 0.5rem;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/toast.tsx",
    "content": "'use client';\nimport { Toast } from '@base-ui/react/toast';\nimport styles from './toast.module.css';\n\nconst globalToastManager = Toast.createToastManager();\n\nfunction showGlobalToast() {\n  globalToastManager.promise(fetchUserData(), {\n    error: 'Failed to fetch user data',\n    success: 'User data loaded!',\n    loading: 'Fetching user data…',\n  });\n}\n\nfunction fetchUserData() {\n  return new Promise((resolve, reject) => {\n    // Simulate API call with a 50% chance of success\n    setTimeout(() => {\n      const success = Math.random() > 0.5;\n      if (success) {\n        resolve({ name: 'John Doe', email: 'john@example.com' });\n      } else {\n        reject(new Error('Failed to fetch user data'));\n      }\n    }, 1000);\n  });\n}\n\nexport default function Page() {\n  return (\n    <Toast.Provider toastManager={globalToastManager}>\n      <Toast.Viewport className={styles.viewport}>\n        <Toasts />\n      </Toast.Viewport>\n      <ToastButtons />\n    </Toast.Provider>\n  );\n}\n\nfunction ToastButtons() {\n  const toastManager = Toast.useToastManager();\n\n  function showRegularToast() {\n    toastManager.add({\n      title: 'Toast created',\n      description: 'This is a toast.',\n    });\n  }\n\n  function showActionToast() {\n    toastManager.add({\n      type: 'undo',\n      title: 'Message deleted',\n    });\n  }\n\n  return (\n    <div\n      style={{\n        position: 'fixed',\n        bottom: 20,\n        display: 'flex',\n        gap: 10,\n        justifyContent: 'center',\n      }}\n    >\n      <button className={styles.button} type=\"button\" onClick={showRegularToast}>\n        Show regular toast\n      </button>\n      <button className={styles.button} type=\"button\" onClick={showActionToast}>\n        Show action toast\n      </button>\n      <button className={styles.button} type=\"button\" onClick={showGlobalToast}>\n        Show global toast\n      </button>\n      <ToastPromiseExample />\n    </div>\n  );\n}\n\nfunction Toasts() {\n  const { toasts } = Toast.useToastManager();\n  return toasts.map((toast) => (\n    <Toast.Root key={toast.id} toast={toast} className={styles.root}>\n      <Toast.Content className={styles.content}>\n        <Toast.Title />\n        <Toast.Description />\n        {toast.type === 'undo' && (\n          <button className={styles.button} type=\"button\" onClick={() => alert('Action undone')}>\n            Undo\n          </button>\n        )}\n        <Toast.Close className={styles.close} aria-label=\"Close\">\n          x\n        </Toast.Close>\n      </Toast.Content>\n    </Toast.Root>\n  ));\n}\n\nfunction ToastPromiseExample() {\n  const toastManager = Toast.useToastManager();\n\n  const handlePromiseClick = () => {\n    toastManager\n      .promise(fetchUserData(), {\n        loading: 'Fetching user data…',\n        success: 'User data loaded!',\n        error: 'Failed to load user data',\n      })\n      .then((data) => {\n        console.log('User data:', data);\n      })\n      .catch((err) => {\n        console.error('Error handled:', err);\n      });\n  };\n\n  return (\n    <button className={styles.button} type=\"button\" onClick={handlePromiseClick}>\n      Show promise toast\n    </button>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/toggle-group.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { ToggleGroup } from '@base-ui/react/toggle-group';\nimport { Toggle } from '@base-ui/react/toggle';\nimport classes from './toggle.module.css';\n\nexport default function ToggleGroupDemo() {\n  const [value, setValue] = React.useState(['align-center']);\n  return (\n    <div className={classes.grid}>\n      <h4 style={{ margin: 0 }}>LTR</h4>\n\n      <ToggleGroup\n        value={value}\n        onValueChange={(newValue) => {\n          // one button must always remain pressed\n          if (newValue.length > 0) {\n            setValue(newValue);\n          }\n        }}\n        aria-label=\"Text alignment\"\n        className={classes.group}\n      >\n        <Toggle value=\"align-left\" aria-label=\"Align left\" className={classes.button}>\n          <AlignLeftIcon />\n        </Toggle>\n\n        <Toggle value=\"align-center\" aria-label=\"Align center\" className={classes.button}>\n          <AlignCenterIcon />\n        </Toggle>\n\n        <Toggle value=\"align-right\" aria-label=\"Align right\" className={classes.button}>\n          <AlignRightIcon />\n        </Toggle>\n      </ToggleGroup>\n\n      <h4 style={{ margin: 0 }}>RTL</h4>\n\n      <div dir=\"rtl\">\n        <div>\n          <div>\n            <ToggleGroup multiple aria-label=\"Text formatting\" className={classes.group}>\n              <Toggle value=\"bold\" aria-label=\"Toggle bold\" className={classes.button}>\n                <BoldIcon />\n              </Toggle>\n\n              <Toggle value=\"italics\" aria-label=\"Toggle italics\" className={classes.button}>\n                <ItalicsIcon />\n              </Toggle>\n\n              <Toggle value=\"underline\" aria-label=\"Toggle underline\" className={classes.button}>\n                <UnderlineIcon />\n              </Toggle>\n            </ToggleGroup>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n\nfunction AlignLeftIcon() {\n  return (\n    <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\">\n      <line x1=\"17\" y1=\"10\" x2=\"3\" y2=\"10\" />\n      <line x1=\"21\" y1=\"6\" x2=\"3\" y2=\"6\" />\n      <line x1=\"21\" y1=\"14\" x2=\"3\" y2=\"14\" />\n      <line x1=\"17\" y1=\"18\" x2=\"3\" y2=\"18\" />\n    </svg>\n  );\n}\n\nfunction AlignCenterIcon() {\n  return (\n    <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\">\n      <line x1=\"18\" y1=\"10\" x2=\"6\" y2=\"10\" />\n      <line x1=\"21\" y1=\"6\" x2=\"3\" y2=\"6\" />\n      <line x1=\"21\" y1=\"14\" x2=\"3\" y2=\"14\" />\n      <line x1=\"18\" y1=\"18\" x2=\"6\" y2=\"18\" />\n    </svg>\n  );\n}\n\nfunction AlignRightIcon() {\n  return (\n    <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\">\n      <line x1=\"21\" y1=\"10\" x2=\"7\" y2=\"10\" />\n      <line x1=\"21\" y1=\"6\" x2=\"3\" y2=\"6\" />\n      <line x1=\"21\" y1=\"14\" x2=\"3\" y2=\"14\" />\n      <line x1=\"21\" y1=\"18\" x2=\"7\" y2=\"18\" />\n    </svg>\n  );\n}\n\nfunction BoldIcon() {\n  return (\n    <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\">\n      <path d=\"M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z\" />\n      <path d=\"M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z\" />\n    </svg>\n  );\n}\n\nfunction ItalicsIcon() {\n  return (\n    <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\">\n      <line x1=\"19\" y1=\"4\" x2=\"10\" y2=\"4\" />\n      <line x1=\"14\" y1=\"20\" x2=\"5\" y2=\"20\" />\n      <line x1=\"15\" y1=\"4\" x2=\"9\" y2=\"20\" />\n    </svg>\n  );\n}\n\nfunction UnderlineIcon() {\n  return (\n    <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\">\n      <path d=\"M6 3v7a6 6 0 0 0 6 6 6 6 0 0 0 6-6V3\" />\n      <line x1=\"4\" y1=\"21\" x2=\"20\" y2=\"21\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/toggle.module.css",
    "content": ".grid {\n  display: grid;\n  grid-gap: 2rem;\n}\n\n.group {\n  display: flex;\n}\n\n.button {\n  --size: 2.5rem;\n  --corner-radius: 0.4rem;\n  --border-color: var(--gray-outline-2);\n  --border-radius-right: 0 var(--corner-radius) var(--corner-radius) 0;\n  --border-radius-left: var(--corner-radius) 0 0 var(--corner-radius);\n\n  display: flex;\n  flex-flow: row nowrap;\n  justify-content: center;\n  align-items: center;\n  height: var(--size);\n  width: var(--size);\n  border: 1px solid var(--border-color);\n  background-color: var(--gray-container-2);\n  color: var(--gray-400);\n}\n\n.button:disabled {\n  cursor: not-allowed;\n}\n\n.button:focus-visible {\n  outline: 2px solid black;\n  z-index: 1;\n}\n\n.button:first-child {\n  border-radius: var(--border-radius-left);\n  border-inline-end-color: transparent;\n}\n\n.button:last-child {\n  border-radius: var(--border-radius-right);\n  border-inline-start-color: transparent;\n}\n\n.button:first-child:dir(rtl) {\n  border-radius: var(--border-radius-right);\n}\n\n.button:last-child:dir(rtl) {\n  border-radius: var(--border-radius-left);\n}\n\n.button:not(:disabled):hover {\n  background-color: var(--gray-surface-1);\n  outline: 1px solid #444;\n  outline-offset: -1px;\n  color: var(--gray-text-2);\n  cursor: pointer;\n  z-index: 1;\n}\n\n.button svg {\n  fill: none;\n  stroke: currentColor;\n  stroke-width: 2;\n  stroke-linecap: round;\n  stroke-linejoin: round;\n}\n\n.button[data-pressed] {\n  background-color: #fefefe;\n  color: var(--gray-text-2);\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/toolbar/_icons.tsx",
    "content": "'use client';\nimport * as React from 'react';\n\nexport default function Nothing() {\n  return <div>This is just a dummy file to hold icons</div>;\n}\n\nexport function AlignLeftIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" {...props}>\n      <line x1=\"17\" y1=\"10\" x2=\"3\" y2=\"10\" />\n      <line x1=\"21\" y1=\"6\" x2=\"3\" y2=\"6\" />\n      <line x1=\"21\" y1=\"14\" x2=\"3\" y2=\"14\" />\n      <line x1=\"17\" y1=\"18\" x2=\"3\" y2=\"18\" />\n    </svg>\n  );\n}\n\nexport function AlignCenterIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" {...props}>\n      <line x1=\"18\" y1=\"10\" x2=\"6\" y2=\"10\" />\n      <line x1=\"21\" y1=\"6\" x2=\"3\" y2=\"6\" />\n      <line x1=\"21\" y1=\"14\" x2=\"3\" y2=\"14\" />\n      <line x1=\"18\" y1=\"18\" x2=\"6\" y2=\"18\" />\n    </svg>\n  );\n}\n\nexport function AlignRightIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" {...props}>\n      <line x1=\"21\" y1=\"10\" x2=\"7\" y2=\"10\" />\n      <line x1=\"21\" y1=\"6\" x2=\"3\" y2=\"6\" />\n      <line x1=\"21\" y1=\"14\" x2=\"3\" y2=\"14\" />\n      <line x1=\"21\" y1=\"18\" x2=\"7\" y2=\"18\" />\n    </svg>\n  );\n}\n\nexport function BoldIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" {...props}>\n      <path d=\"M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z\" />\n      <path d=\"M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z\" />\n    </svg>\n  );\n}\n\nexport function ItalicsIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" {...props}>\n      <line x1=\"19\" y1=\"4\" x2=\"10\" y2=\"4\" />\n      <line x1=\"14\" y1=\"20\" x2=\"5\" y2=\"20\" />\n      <line x1=\"15\" y1=\"4\" x2=\"9\" y2=\"20\" />\n    </svg>\n  );\n}\n\nexport function UnderlineIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" {...props}>\n      <path d=\"M6 3v7a6 6 0 0 0 6 6 6 6 0 0 0 6-6V3\" />\n      <line x1=\"4\" y1=\"21\" x2=\"20\" y2=\"21\" />\n    </svg>\n  );\n}\n\nexport function ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        fill={props.fill ?? 'canvas'}\n      />\n      <path d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\" />\n      <path d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\" />\n    </svg>\n  );\n}\n\nexport function ChevronUpDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"8\"\n      height=\"12\"\n      viewBox=\"0 0 8 12\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"1.5\"\n      {...props}\n    >\n      <path d=\"M0.5 4.5L4 1.5L7.5 4.5\" />\n      <path d=\"M0.5 7.5L4 10.5L7.5 7.5\" />\n    </svg>\n  );\n}\n\nexport function CheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n\nexport function MoreHorizontalIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" {...props}>\n      <circle cx=\"12\" cy=\"12\" r=\"1\" />\n      <circle cx=\"19\" cy=\"12\" r=\"1\" />\n      <circle cx=\"5\" cy=\"12\" r=\"1\" />\n    </svg>\n  );\n}\n\nexport function ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M1 3.5L5 7.5L9 3.5\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n\nexport function ChevronRightIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" {...props}>\n      <path d=\"M3.5 9L7.5 5L3.5 1\" stroke=\"currentcolor\" strokeWidth=\"1.5\" />\n    </svg>\n  );\n}\n\nexport function MessageCircleIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" {...props}>\n      <path d=\"M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z\" />\n    </svg>\n  );\n}\n\nexport function MousePointerIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" {...props}>\n      <path d=\"M3 3l7.07 16.97 2.51-7.39 7.39-2.51L3 3z\" />\n      <path d=\"M13 13l6 6\" />\n    </svg>\n  );\n}\n\nexport function CursorGrowIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"26\"\n      height=\"14\"\n      viewBox=\"0 0 24 14\"\n      fill=\"black\"\n      stroke=\"white\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M19.5 5.5L6.49737 5.51844V2L1 6.9999L6.5 12L6.49737 8.5L19.5 8.5V12L25 6.9999L19.5 2V5.5Z\" />\n    </svg>\n  );\n}\n\nexport function PlusIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"10\"\n      height=\"10\"\n      viewBox=\"0 0 10 10\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"1.6\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M0 5H5M10 5H5M5 5V0M5 5V10\" />\n    </svg>\n  );\n}\n\nexport function MinusIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"10\"\n      height=\"10\"\n      viewBox=\"0 0 10 10\"\n      fill=\"none\"\n      stroke=\"currentcolor\"\n      strokeWidth=\"1.6\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M0 5H10\" />\n    </svg>\n  );\n}\n\nexport function BellIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"20\" height=\"20\" viewBox=\"0 0 16 16\" {...props}>\n      <path d=\"M 8 1 C 7.453125 1 7 1.453125 7 2 L 7 3.140625 C 5.28125 3.589844 4 5.144531 4 7 L 4 10.984375 C 4 10.984375 3.984375 11.261719 3.851563 11.519531 C 3.71875 11.78125 3.558594 12 3 12 L 3 13 L 13 13 L 13 12 C 12.40625 12 12.253906 11.78125 12.128906 11.53125 C 12.003906 11.277344 12 11.003906 12 11.003906 L 12 7 C 12 5.144531 10.71875 3.589844 9 3.140625 L 9 2 C 9 1.453125 8.546875 1 8 1 Z M 8 13 C 7.449219 13 7 13.449219 7 14 C 7 14.550781 7.449219 15 8 15 C 8.550781 15 9 14.550781 9 14 C 9 13.449219 8.550781 13 8 13 Z M 8 4 C 9.664063 4 11 5.335938 11 7 L 11 10.996094 C 11 10.996094 10.988281 11.472656 11.234375 11.96875 C 11.238281 11.980469 11.246094 11.988281 11.25 12 L 4.726563 12 C 4.730469 11.992188 4.738281 11.984375 4.742188 11.980469 C 4.992188 11.488281 5 11.015625 5 11.015625 L 5 7 C 5 5.335938 6.335938 4 8 4 Z\" />\n    </svg>\n  );\n}\n\nexport function TrashIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      {...props}\n    >\n      <polyline points=\"3 6 5 6 21 6\" />\n      <path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\" />\n      <line x1=\"10\" y1=\"11\" x2=\"10\" y2=\"17\" />\n      <line x1=\"14\" y1=\"11\" x2=\"14\" y2=\"17\" />\n    </svg>\n  );\n}\n\nexport function SlidersIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      {...props}\n    >\n      <line x1=\"4\" y1=\"21\" x2=\"4\" y2=\"14\" />\n      <line x1=\"4\" y1=\"10\" x2=\"4\" y2=\"3\" />\n      <line x1=\"12\" y1=\"21\" x2=\"12\" y2=\"12\" />\n      <line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"3\" />\n      <line x1=\"20\" y1=\"21\" x2=\"20\" y2=\"16\" />\n      <line x1=\"20\" y1=\"12\" x2=\"20\" y2=\"3\" />\n      <line x1=\"1\" y1=\"14\" x2=\"7\" y2=\"14\" />\n      <line x1=\"9\" y1=\"8\" x2=\"15\" y2=\"8\" />\n      <line x1=\"17\" y1=\"16\" x2=\"23\" y2=\"16\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/toolbar/basic.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Toolbar } from '@base-ui/react/toolbar';\nimport { DirectionProvider } from '@base-ui/react/direction-provider';\nimport toolbarClasses from './toolbar.module.css';\nimport inputClasses from '../../../(docs)/react/components/input/demos/hero/css-modules/index.module.css';\nimport '../../../../demo-data/theme/css-modules/theme.css';\n\nimport { SettingsMetadata, useExperimentSettings } from '../_components/SettingsPanel';\n\nconst styles = {\n  toolbar: toolbarClasses,\n  input: inputClasses,\n};\n\nconst TEXT = `Shows the basic anatomy:\n- Toolbar.Root\n- Toolbar.Button\n- Toolbar.Link\n- Toolbar.Input\n- Toolbar.Separator\n- Toolbar.Group\n`;\n\ninterface Settings extends Record<string, boolean> {}\n\nexport const settingsMetadata: SettingsMetadata<Settings> = {\n  isRtl: {\n    type: 'boolean',\n    label: 'RTL',\n    default: false,\n  },\n  toolbarDisabled: {\n    type: 'boolean',\n    label: 'Everything disabled',\n    default: false,\n  },\n};\n\nexport default function App() {\n  const { settings } = useExperimentSettings<Settings>();\n  const dir = settings.isRtl ? 'rtl' : 'ltr';\n  const inputDefaultValue = settings.isRtl ? 'نص نائب' : 'hello world';\n  return (\n    <React.Fragment>\n      <a\n        className={styles.toolbar.a}\n        href=\"https://www.w3.org/WAI/ARIA/apg/patterns/toolbar/\"\n        target=\"_blank\"\n        rel=\"noreferrer\"\n      >\n        <h3 className={styles.toolbar.h3}>Toolbar pattern</h3>\n      </a>\n      <div className={styles.toolbar.Wrapper} dir={dir}>\n        <DirectionProvider direction={dir}>\n          <Toolbar.Root\n            className={styles.toolbar.Root}\n            orientation=\"horizontal\"\n            disabled={settings.toolbarDisabled}\n          >\n            <Toolbar.Button\n              className={styles.toolbar.Button}\n              onClick={() => console.log('clicked a regular toolbar button')}\n            >\n              Button 1\n            </Toolbar.Button>\n\n            <Toolbar.Link\n              className={styles.toolbar.Button}\n              href=\"https://base-ui.com\"\n              target=\"_blank\"\n            >\n              Link\n            </Toolbar.Link>\n\n            <Toolbar.Separator className={styles.toolbar.Separator} />\n\n            <Toolbar.Group className={styles.toolbar.ToggleGroup}>\n              <Toolbar.Button\n                className={styles.toolbar.Button}\n                onClick={() => console.log('clicked button 1 inside a group')}\n                style={{ marginRight: '0.5rem' }}\n              >\n                Grouped Button 1\n              </Toolbar.Button>\n\n              <Toolbar.Button\n                className={styles.toolbar.Button}\n                onClick={() => console.log('clicked button 2 inside a group')}\n              >\n                Grouped Button 2\n              </Toolbar.Button>\n            </Toolbar.Group>\n\n            <Toolbar.Input className={styles.input.Input} defaultValue={inputDefaultValue} />\n          </Toolbar.Root>\n        </DirectionProvider>\n        <textarea className={styles.toolbar.Textarea} name=\"\" id=\"\" rows={8} defaultValue={TEXT} />\n      </div>\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/toolbar/slider.module.css",
    "content": ".Wrapper {\n  display: flex;\n  flex-direction: column;\n}\n\n.Row {\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  gap: 1rem;\n}\n\n.Slider {\n  display: flex;\n  gap: 1rem;\n}\n\n.Value {\n  line-height: 1.7;\n  width: 2rem;\n}\n\n.Control {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  width: 14rem;\n  padding-block: 0.75rem;\n}\n\n.Track {\n  width: 100%;\n  height: 0.25rem;\n  background-color: var(--color-gray-200);\n  box-shadow: inset 0 0 0 1px var(--color-gray-200);\n  border-radius: 0.25rem;\n}\n\n.Indicator {\n  border-radius: 0.25rem;\n  background-color: var(--color-gray-700);\n\n  &.r {\n    background-color: red;\n  }\n\n  &.g {\n    background-color: green;\n  }\n\n  &.b {\n    background-color: blue;\n  }\n}\n\n.Thumb {\n  width: 1rem;\n  height: 1rem;\n  border-radius: 100%;\n  background-color: white;\n  outline: 1px solid var(--color-gray-300);\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n  }\n}\n\n.Field {\n  display: flex;\n  flex-direction: column;\n  align-items: start;\n  gap: 0.25rem;\n  width: 4rem;\n  max-width: 4rem;\n}\n\n.Label {\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 500;\n  color: var(--color-gray-900);\n}\n\n.Group {\n  display: flex;\n}\n\n.Input {\n  box-sizing: border-box;\n  padding-left: 0.875rem;\n  margin: 0;\n  border: 1px solid var(--color-gray-200);\n  width: 100%;\n  height: 2.5rem;\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  background-color: transparent;\n  color: var(--color-gray-900);\n\n  &:focus {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Error {\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  color: var(--color-red-800);\n}\n\n.Description {\n  margin: 0;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  color: var(--color-gray-600);\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/toolbar/text-editor.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Toolbar } from '@base-ui/react/toolbar';\nimport { Tooltip } from '@base-ui/react/tooltip';\nimport { Toggle } from '@base-ui/react/toggle';\nimport { ToggleGroup } from '@base-ui/react/toggle-group';\nimport { Select } from '@base-ui/react/select';\nimport { NumberField } from '@base-ui/react/number-field';\nimport { Menu } from '@base-ui/react/menu';\nimport { SettingsMetadata, useExperimentSettings } from '../_components/SettingsPanel';\nimport toolbarClasses from './toolbar.module.css';\nimport selectClasses from '../../../(docs)/react/components/select/demos/hero/css-modules/index.module.css';\nimport tooltipClasses from '../../../(docs)/react/components/tooltip/demos/hero/css-modules/index.module.css';\nimport menuClasses from '../../../(docs)/react/components/menu/demos/submenu/css-modules/index.module.css';\nimport numberFieldClasses from '../../../(docs)/react/components/number-field/demos/hero/css-modules/index.module.css';\nimport '../../../../demo-data/theme/css-modules/theme.css';\nimport {\n  BoldIcon,\n  ItalicsIcon,\n  UnderlineIcon,\n  AlignLeftIcon,\n  AlignCenterIcon,\n  AlignRightIcon,\n  ArrowSvg,\n  ChevronUpDownIcon,\n  CheckIcon,\n  MoreHorizontalIcon,\n  ChevronRightIcon,\n  CursorGrowIcon,\n  MinusIcon,\n  PlusIcon,\n} from './_icons';\n\ninterface Settings extends Record<string, boolean> {}\n\nexport const settingsMetadata: SettingsMetadata<Settings> = {\n  selectDisabled: {\n    type: 'boolean',\n    label: 'Select disabled',\n    default: false,\n  },\n  boldDisabled: {\n    type: 'boolean',\n    label: 'Bold disabled',\n  },\n  italicsDisabled: {\n    type: 'boolean',\n    label: 'Italics disabled',\n  },\n  underlineDisabled: {\n    type: 'boolean',\n    label: 'Underline disabled',\n  },\n  menuDisabled: {\n    type: 'boolean',\n    label: 'Menu disabled',\n  },\n  toolbarDisabled: {\n    type: 'boolean',\n    label: 'Everything disabled',\n    default: false,\n  },\n};\n\nconst styles = {\n  toolbar: toolbarClasses,\n  tooltip: tooltipClasses,\n  select: selectClasses,\n  menu: menuClasses,\n  numField: numberFieldClasses,\n};\n\nfunction classNames(...c: Array<string | undefined | null | false>) {\n  return c.filter(Boolean).join(' ');\n}\n\nfunction renderToggleWithTooltip(args: {\n  key: string;\n  label: string;\n  icon: (props: React.ComponentProps<'svg'>) => React.JSX.Element;\n  disabled?: boolean;\n}) {\n  const { key, label, icon: Icon, disabled = false } = args;\n  return (\n    <Tooltip.Root key={key}>\n      <Tooltip.Trigger\n        render={\n          <Toolbar.Button\n            render={<Toggle />}\n            aria-label={label}\n            value={key}\n            className={styles.toolbar.Toggle}\n            disabled={disabled}\n          >\n            <Icon className={styles.toolbar.Icon} />\n          </Toolbar.Button>\n        }\n      />\n      <Tooltip.Portal>\n        <Tooltip.Positioner sideOffset={10}>\n          <Tooltip.Popup className={styles.tooltip.Popup}>\n            <Tooltip.Arrow\n              className={classNames(styles.tooltip.Arrow, styles.toolbar.TooltipArrow)}\n            >\n              <ArrowSvg className={styles.toolbar.ArrowSvg} />\n            </Tooltip.Arrow>\n            {label}\n          </Tooltip.Popup>\n        </Tooltip.Positioner>\n      </Tooltip.Portal>\n    </Tooltip.Root>\n  );\n}\n\nexport default function App() {\n  const { settings } = useExperimentSettings<Settings>();\n\n  const SELECT_DISABLED = settings.toolbarDisabled || settings.selectDisabled;\n  return (\n    <React.Fragment>\n      <a\n        className={styles.toolbar.a}\n        href=\"https://www.w3.org/WAI/ARIA/apg/patterns/toolbar/\"\n        target=\"_blank\"\n        rel=\"noreferrer\"\n      >\n        <h3 className={styles.toolbar.h3}>Toolbar pattern</h3>\n      </a>\n      <div className={styles.toolbar.Wrapper}>\n        <Tooltip.Provider>\n          <Toolbar.Root className={styles.toolbar.Root}>\n            <Select.Root defaultValue=\"sans\">\n              <Toolbar.Button\n                disabled={SELECT_DISABLED}\n                render={<Select.Trigger />}\n                nativeButton={false}\n                className={styles.select.Select}\n              >\n                <Select.Value />\n                <Select.Icon className={styles.select.SelectIcon}>\n                  <ChevronUpDownIcon />\n                </Select.Icon>\n              </Toolbar.Button>\n              <Select.Portal>\n                <Select.Positioner className={styles.select.Positioner} sideOffset={8}>\n                  <Select.Popup\n                    className={styles.select.Popup}\n                    style={{ backgroundColor: 'var(--color-gray-50)' }}\n                  >\n                    <Select.Arrow className={styles.select.Arrow}>\n                      <ArrowSvg className={styles.toolbar.ArrowSvg} />\n                    </Select.Arrow>\n                    <Select.Item className={styles.select.Item} value=\"sans\">\n                      <Select.ItemIndicator className={styles.select.ItemIndicator}>\n                        <CheckIcon className={styles.select.ItemIndicatorIcon} />\n                      </Select.ItemIndicator>\n                      <Select.ItemText className={styles.select.ItemText}>\n                        Sans-serif\n                      </Select.ItemText>\n                    </Select.Item>\n                    <Select.Item className={styles.select.Item} value=\"serif\">\n                      <Select.ItemIndicator className={styles.select.ItemIndicator}>\n                        <CheckIcon className={styles.select.ItemIndicatorIcon} />\n                      </Select.ItemIndicator>\n                      <Select.ItemText className={styles.select.ItemText}>Serif</Select.ItemText>\n                    </Select.Item>\n                    <Select.Item className={styles.select.Item} value=\"mono\">\n                      <Select.ItemIndicator className={styles.select.ItemIndicator}>\n                        <CheckIcon className={styles.select.ItemIndicatorIcon} />\n                      </Select.ItemIndicator>\n                      <Select.ItemText className={styles.select.ItemText}>\n                        Monospace\n                      </Select.ItemText>\n                    </Select.Item>\n                    <Select.Item className={styles.select.Item} value=\"cursive\">\n                      <Select.ItemIndicator className={styles.select.ItemIndicator}>\n                        <CheckIcon className={styles.select.ItemIndicatorIcon} />\n                      </Select.ItemIndicator>\n                      <Select.ItemText className={styles.select.ItemText}>Cursive</Select.ItemText>\n                    </Select.Item>\n                  </Select.Popup>\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n\n            <Tooltip.Root>\n              <Tooltip.Trigger\n                render={\n                  <NumberField.Root\n                    defaultValue={16}\n                    max={256}\n                    min={1}\n                    className={styles.numField.Field}\n                  >\n                    <NumberField.ScrubArea className={styles.numField.ScrubArea}>\n                      <NumberField.ScrubAreaCursor className={styles.numField.ScrubAreaCursor}>\n                        <CursorGrowIcon />\n                      </NumberField.ScrubAreaCursor>\n                    </NumberField.ScrubArea>\n\n                    <NumberField.Group\n                      className={classNames(styles.toolbar.NumberFieldGroup, styles.numField.Group)}\n                    >\n                      <NumberField.Decrement\n                        className={styles.numField.Decrement}\n                        disabled={settings.toolbarDisabled}\n                      >\n                        <MinusIcon />\n                      </NumberField.Decrement>\n\n                      <Toolbar.Input\n                        className={styles.toolbar.Input}\n                        disabled={settings.toolbarDisabled}\n                        render={<NumberField.Input />}\n                        aria-label=\"Font size\"\n                      />\n\n                      <NumberField.Increment\n                        className={styles.numField.Increment}\n                        disabled={settings.toolbarDisabled}\n                      >\n                        <PlusIcon />\n                      </NumberField.Increment>\n                    </NumberField.Group>\n                  </NumberField.Root>\n                }\n              />\n              <Tooltip.Portal>\n                <Tooltip.Positioner sideOffset={10}>\n                  <Tooltip.Popup className={styles.tooltip.Popup}>\n                    <Tooltip.Arrow\n                      className={classNames(styles.tooltip.Arrow, styles.toolbar.TooltipArrow)}\n                    >\n                      <ArrowSvg className={styles.toolbar.ArrowSvg} />\n                    </Tooltip.Arrow>\n                    Font size\n                  </Tooltip.Popup>\n                </Tooltip.Positioner>\n              </Tooltip.Portal>\n            </Tooltip.Root>\n\n            <Toolbar.Separator className={styles.toolbar.Separator} />\n\n            <ToggleGroup\n              multiple\n              defaultValue={[]}\n              className={styles.toolbar.ToggleGroup}\n              disabled={settings.toolbarDisabled}\n            >\n              {[\n                {\n                  key: 'bold',\n                  label: 'Bold',\n                  icon: BoldIcon,\n                  disabled: settings.boldDisabled,\n                },\n                {\n                  key: 'italics',\n                  label: 'Italics',\n                  icon: ItalicsIcon,\n                  disabled: settings.italicsDisabled,\n                },\n                {\n                  key: 'underline',\n                  label: 'Underline',\n                  icon: UnderlineIcon,\n                  disabled: settings.underlineDisabled,\n                },\n              ].map(renderToggleWithTooltip)}\n            </ToggleGroup>\n\n            <Toolbar.Separator className={styles.toolbar.Separator} />\n\n            <ToggleGroup\n              defaultValue={['left']}\n              className={styles.toolbar.ToggleGroup}\n              disabled={settings.toolbarDisabled}\n            >\n              {[\n                {\n                  key: 'left',\n                  label: 'Align left',\n                  icon: AlignLeftIcon,\n                  disabled: settings.toolbarDisabled,\n                },\n                {\n                  key: 'center',\n                  label: 'Align center',\n                  icon: AlignCenterIcon,\n                  disabled: settings.toolbarDisabled,\n                },\n                {\n                  key: 'right',\n                  label: 'Align right',\n                  icon: AlignRightIcon,\n                  disabled: settings.toolbarDisabled,\n                },\n              ].map(renderToggleWithTooltip)}\n            </ToggleGroup>\n\n            <Toolbar.Separator className={styles.toolbar.Separator} />\n\n            <Menu.Root>\n              <Toolbar.Button\n                disabled={settings.toolbarDisabled || settings.menuDisabled}\n                render={<Menu.Trigger />}\n                className={styles.toolbar.More}\n              >\n                <MoreHorizontalIcon className={styles.toolbar.Icon} />\n              </Toolbar.Button>\n              <Menu.Portal>\n                <Menu.Positioner\n                  className={styles.menu.Positioner}\n                  align=\"start\"\n                  side=\"inline-end\"\n                  sideOffset={8}\n                >\n                  <Menu.Popup className={styles.menu.Popup} style={{ backgroundColor: 'canvas' }}>\n                    <Menu.Arrow\n                      className={styles.menu.Arrow}\n                      style={{\n                        color: 'canvas',\n                        transform: 'rotate(-90deg) translateY(-124%)',\n                      }}\n                    >\n                      <ArrowSvg className={styles.toolbar.ArrowSvg} />\n                    </Menu.Arrow>\n                    <Menu.Item className={styles.menu.Item}>Help</Menu.Item>\n                    <Menu.Item className={styles.menu.Item}>Keyboard Shortcuts</Menu.Item>\n                    <Menu.Item className={styles.menu.Item}>Release Notes</Menu.Item>\n                    <Menu.Separator className={styles.menu.Separator} />\n                    <Menu.SubmenuRoot>\n                      <Menu.SubmenuTrigger className={styles.menu.SubmenuTrigger}>\n                        Debug\n                        <ChevronRightIcon />\n                      </Menu.SubmenuTrigger>\n                      <Menu.Portal>\n                        <Menu.Positioner\n                          className={styles.menu.Positioner}\n                          alignOffset={-4}\n                          sideOffset={-4}\n                        >\n                          <Menu.Popup className={styles.menu.Popup}>\n                            <Menu.Item className={styles.menu.Item}>Show debug log</Menu.Item>\n                            <Menu.Item className={styles.menu.Item}>Show network log</Menu.Item>\n                            <Menu.Item className={styles.menu.Item}>Show all logs</Menu.Item>\n                            <Menu.Separator className={styles.menu.Separator} />\n                            <Menu.Item className={styles.menu.Item}>Clear cache</Menu.Item>\n                            <Menu.Item className={styles.menu.Item}>Clear local storage</Menu.Item>\n                          </Menu.Popup>\n                        </Menu.Positioner>\n                      </Menu.Portal>\n                    </Menu.SubmenuRoot>\n                    <Menu.Separator className={styles.menu.Separator} />\n                    <Menu.Item className={styles.menu.Item}>Log Out</Menu.Item>\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            </Menu.Root>\n          </Toolbar.Root>\n        </Tooltip.Provider>\n        <textarea className={styles.toolbar.Textarea} name=\"\" id=\"\" rows={10} defaultValue=\"\" />\n      </div>\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/toolbar/toolbar.module.css",
    "content": ".Root {\n  display: flex;\n  align-items: center;\n  gap: 0.5rem;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  padding: 0.5rem 0.6rem;\n  text-wrap: nowrap;\n}\n\n.Grid {\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n  padding: 0.5rem;\n}\n\n/* disabled buttons + focusableWhenDisabled */\n.Root [type='button']:disabled,\n.Root [type='button'][data-disabled],\n.Root input[data-disabled],\n.Root button[data-disabled] {\n  color: color-mix(in srgb, currentColor 30%, transparent) !important;\n  cursor: not-allowed !important;\n}\n\n.Separator[data-orientation='vertical'] {\n  width: 1px;\n  align-self: stretch;\n  margin-block: 4px;\n  background-color: var(--color-gray-300);\n}\n\n.Separator[data-orientation='horizontal'] {\n  height: 1px;\n  align-self: stretch;\n  /*  margin-block: 2px; */\n  background-color: var(--color-gray-300);\n  margin-block: 24px;\n}\n\n.Link {\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  color: var(--color-gray-900);\n  text-decoration-color: var(--color-gray-400);\n  text-decoration-thickness: 1px;\n  text-decoration-line: none;\n  text-underline-offset: 2px;\n\n  @media (hover: hover) {\n    &:hover {\n      text-decoration-line: underline;\n    }\n  }\n\n  &:focus-visible {\n    border-radius: 0.125rem;\n    outline: 2px solid var(--color-blue);\n    text-decoration-line: none;\n  }\n}\n\n.Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding-inline: 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  line-height: 1.5rem;\n  background-color: transparent;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  &:focus-visible {\n    background-color: transparent;\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-200);\n  }\n}\n\n/* ToggleGroup  */\n.ToggleGroup {\n  display: flex;\n  gap: 1px;\n  border-radius: 0.375rem;\n  padding: 0.125rem;\n}\n\n.Toggle {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 2rem;\n  height: 2rem;\n  padding: 0;\n  margin: 0;\n  outline: 0;\n  border: 0;\n  border-radius: 0.25rem;\n  background-color: transparent;\n  color: var(--color-gray-600);\n  user-select: none;\n\n  &:focus-visible {\n    background-color: transparent;\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-200);\n  }\n\n  &[data-pressed] {\n    background-color: var(--color-gray-100);\n    color: var(--color-gray-900);\n  }\n}\n\n.Icon {\n  width: 1rem;\n  height: 1rem;\n  fill: none;\n  stroke: currentColor;\n  stroke-width: 2;\n  stroke-linecap: round;\n  stroke-linejoin: round;\n}\n\n/* Menu  */\n.More {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: 0.375rem;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 500;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n/* Select  */\n/* ArrowOuterStroke  */\n.ArrowSvg > path:nth-child(2) {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n/* Select  */\n/* ArrowInnerStroke  */\n.ArrowSvg > path:nth-child(3) {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n\n/* NumberField  */\n.NumberFieldGroup {\n  --corner-radius: 0.375rem;\n  --border-radius-right: 0 var(--corner-radius) var(--corner-radius) 0;\n  --border-radius-left: var(--corner-radius) 0 0 var(--corner-radius);\n\n  & button:first-of-type {\n    border-radius: var(--border-radius-left);\n    /*    border-inline-end-color: transparent; */\n  }\n\n  & button:last-of-type {\n    border-radius: var(--border-radius-right);\n    /*    border-inline-start-color: transparent; */\n  }\n\n  & button:first-of-type:dir(rtl) {\n    border-radius: var(--border-radius-right);\n  }\n\n  & button:last-of-type:dir(rtl) {\n    border-radius: var(--border-radius-left);\n  }\n}\n\n.Input {\n  box-sizing: border-box;\n  margin: 0;\n  padding: 0;\n  border-top: 1px solid var(--color-gray-200);\n  border-bottom: 1px solid var(--color-gray-200);\n  border-left: none;\n  border-right: none;\n  width: 3rem;\n  height: 2.5rem;\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: normal;\n  background-color: transparent;\n  color: var(--color-gray-900);\n\n  text-align: center;\n  font-variant-numeric: tabular-nums;\n\n  &:focus {\n    z-index: 1;\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n/* Misc stuff  */\n.Textarea {\n  box-sizing: border-box;\n  padding: 0.875rem;\n  margin: 0;\n  border: 1px solid var(--color-gray-200);\n  width: 100%;\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: normal;\n  background-color: transparent;\n  color: var(--color-gray-900);\n\n  &:focus {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Wrapper {\n  min-width: 50dvw;\n  display: flex;\n  flex-direction: column;\n  gap: 1rem;\n  align-items: self-start;\n}\n\n.h3 {\n  margin-bottom: 1rem;\n}\n\n.a {\n  color: var(--color-blue);\n  text-decoration: underline;\n  align-self: flex-start;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/toolbar/triggers.module.css",
    "content": ".Root {\n  display: inline-flex;\n  background-color: var(--color-gray-100);\n  padding: 0 0.75rem;\n\n  &[data-orientation='vertical'] {\n    flex-direction: column;\n    align-self: flex-start;\n    padding: 0.75rem 0;\n  }\n}\n\n.Toggle {\n  &[data-orientation='horizontal'] {\n    margin-block: 0.5rem;\n  }\n\n  &[data-orientation='vertical'] {\n    margin-inline: 0.5rem;\n  }\n}\n\n.Separator[data-orientation='vertical'] {\n  width: 1px;\n  align-self: stretch;\n  background-color: var(--color-gray-200);\n}\n\n.Separator[data-orientation='horizontal'] {\n  height: 1px;\n  align-self: stretch;\n  background-color: var(--color-gray-200);\n}\n\n.Switch[data-orientation='horizontal'] {\n  inset-inline: 0.5rem;\n}\n\n.Switch[data-orientation='vertical'] {\n  margin-block: 0.25rem;\n  justify-content: center;\n  align-items: center;\n}\n\n.Switch[data-disabled] {\n  cursor: not-allowed !important;\n  background-image: linear-gradient(\n    to right,\n    color-mix(in srgb, var(--color-gray-700) 30%, transparent) 35%,\n    color-mix(in srgb, var(--color-gray-200) 30%, transparent) 65%\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/toolbar/triggers.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Toolbar } from '@base-ui/react/toolbar';\nimport { Switch } from '@base-ui/react/switch';\nimport { NumberField } from '@base-ui/react/number-field';\nimport { Slider } from '@base-ui/react/slider';\nimport { Tooltip } from '@base-ui/react/tooltip';\nimport { Popover } from '@base-ui/react/popover';\nimport { Dialog } from '@base-ui/react/dialog';\nimport { AlertDialog } from '@base-ui/react/alert-dialog';\nimport { Menu } from '@base-ui/react/menu';\nimport toolbarClasses from './toolbar.module.css';\nimport triggerToolbarClasses from './triggers.module.css';\nimport menuClasses from '../../../(docs)/react/components/menu/demos/submenu/css-modules/index.module.css';\nimport tooltipClasses from '../../../(docs)/react/components/tooltip/demos/hero/css-modules/index.module.css';\nimport switchClasses from '../../../(docs)/react/components/switch/demos/hero/css-modules/index.module.css';\nimport dialogClasses from '../../../(docs)/react/components/alert-dialog/demos/hero/css-modules/index.module.css';\nimport popoverClasses from '../../../(docs)/react/components/popover/demos/_index.module.css';\nimport comboSliderClasses from './slider.module.css';\nimport {\n  SlidersIcon,\n  TrashIcon,\n  MessageCircleIcon,\n  ArrowSvg,\n  BellIcon,\n  MoreHorizontalIcon,\n  ChevronRightIcon,\n} from './_icons';\nimport { SettingsMetadata, useExperimentSettings } from '../_components/SettingsPanel';\n\ninterface Settings extends Record<string, boolean> {}\n\nexport const settingsMetadata: SettingsMetadata<Settings> = {\n  verticalOrientation: {\n    type: 'boolean',\n    label: 'Vertical',\n    default: false,\n  },\n  dialogDisabled: {\n    type: 'boolean',\n    label: 'Dialog disabled',\n  },\n  interactivePopoverDisabled: {\n    type: 'boolean',\n    label: 'Popover (interactive) disabled',\n  },\n  popoverDisabled: {\n    type: 'boolean',\n    label: 'Popover disabled',\n  },\n  alertDialogDisabled: {\n    type: 'boolean',\n    label: 'Alert Dialog disabled',\n  },\n  switchDisabled: {\n    type: 'boolean',\n    label: 'Switch disabled',\n    default: false,\n  },\n  toolbarDisabled: {\n    type: 'boolean',\n    label: 'Everything disabled',\n    default: false,\n  },\n};\n\nconst styles = {\n  demo: triggerToolbarClasses,\n  toolbar: toolbarClasses,\n  switch: switchClasses,\n  dialog: dialogClasses,\n  popover: popoverClasses,\n  slider: comboSliderClasses,\n  tooltip: tooltipClasses,\n  menu: menuClasses,\n};\n\nconst TEXT = `Shows toolbar buttons as various triggers:\n- Menu.Trigger\n- Dialog.Trigger / AlertDialog.Trigger\n- Popover.Trigger\n- Tooltip.Trigger\n- and a Switch\n`;\n\nfunction classNames(...c: Array<string | undefined | null | false>) {\n  return c.filter(Boolean).join(' ');\n}\n\nfunction renderTriggerWithTooltip(args: {\n  render: any;\n  key: string;\n  label: string;\n  disabled?: boolean;\n}) {\n  const { render, key, label, disabled = false } = args;\n  return (\n    <Tooltip.Root key={key}>\n      <Tooltip.Trigger\n        render={\n          <Toolbar.Button\n            className={classNames(styles.toolbar.Toggle, styles.demo.Toggle)}\n            disabled={disabled}\n            render={render}\n          />\n        }\n      />\n      <Tooltip.Portal>\n        <Tooltip.Positioner sideOffset={10}>\n          <Tooltip.Popup className={styles.tooltip.Popup}>\n            <Tooltip.Arrow\n              className={classNames(styles.tooltip.Arrow, styles.toolbar.TooltipArrow)}\n            >\n              <ArrowSvg className={styles.toolbar.ArrowSvg} />\n            </Tooltip.Arrow>\n            {label}\n          </Tooltip.Popup>\n        </Tooltip.Positioner>\n      </Tooltip.Portal>\n    </Tooltip.Root>\n  );\n}\n\nexport default function App() {\n  const { settings } = useExperimentSettings<Settings>();\n  const DIALOG_DISABLED = settings.dialogDisabled || settings.toolbarDisabled;\n  const ALERT_DIALOG_DISABLED = settings.alertDialogDisabled || settings.toolbarDisabled;\n  const POPOVER_DISABLED = settings.popoverDisabled || settings.toolbarDisabled;\n  const INT_POPOVER_DISABLED = settings.interactivePopoverDisabled || settings.toolbarDisabled;\n  const SWITCH_DISABLED = settings.switchDisabled || settings.toolbarDisabled;\n  const MENU_DISABLED = settings.menuDisabled || settings.toolbarDisabled;\n  return (\n    <React.Fragment>\n      <a\n        className={styles.toolbar.a}\n        href=\"https://www.w3.org/WAI/ARIA/apg/patterns/toolbar/\"\n        target=\"_blank\"\n        rel=\"noreferrer\"\n      >\n        <h3 className={styles.toolbar.h3}>Toolbar pattern</h3>\n      </a>\n      <div className={styles.toolbar.Wrapper}>\n        <Toolbar.Root\n          className={classNames(styles.toolbar.Root, styles.demo.Root)}\n          orientation={settings.verticalOrientation ? 'vertical' : 'horizontal'}\n        >\n          <Menu.Root>\n            {renderTriggerWithTooltip({\n              render: (\n                <Menu.Trigger>\n                  <MoreHorizontalIcon className={styles.toolbar.Icon} />\n                </Menu.Trigger>\n              ),\n              key: 'menu',\n              label: 'More actions',\n              disabled: MENU_DISABLED,\n            })}\n\n            <Menu.Portal keepMounted>\n              <Menu.Positioner className={styles.menu.Positioner}>\n                <Menu.Popup className={styles.menu.Popup}>\n                  <Menu.Item className={styles.menu.Item}>Save</Menu.Item>\n                  <Menu.Item className={styles.menu.Item}>Save as...</Menu.Item>\n                  <Menu.Separator className={styles.menu.Separator} />\n                  <Menu.SubmenuRoot>\n                    <Menu.SubmenuTrigger className={styles.menu.SubmenuTrigger}>\n                      Recent\n                      <ChevronRightIcon />\n                    </Menu.SubmenuTrigger>\n                    <Menu.Portal keepMounted>\n                      <Menu.Positioner className={styles.menu.Positioner}>\n                        <Menu.Popup className={styles.menu.Popup}>\n                          <Menu.Item className={styles.menu.Item}>index.tsx</Menu.Item>\n                          <Menu.Item className={styles.menu.Item}>index.module.css</Menu.Item>\n                        </Menu.Popup>\n                      </Menu.Positioner>\n                    </Menu.Portal>\n                  </Menu.SubmenuRoot>\n                  <Menu.Separator className={styles.menu.Separator} />\n                  <Menu.Item className={styles.menu.Item}>Close</Menu.Item>\n                </Menu.Popup>\n              </Menu.Positioner>\n            </Menu.Portal>\n          </Menu.Root>\n\n          <Toolbar.Separator className={styles.demo.Separator} />\n\n          <Dialog.Root>\n            {renderTriggerWithTooltip({\n              render: (\n                <Dialog.Trigger>\n                  <MessageCircleIcon className={styles.toolbar.Icon} />\n                </Dialog.Trigger>\n              ),\n              key: 'comment-dialog',\n              label: 'Add a comment',\n              disabled: DIALOG_DISABLED,\n            })}\n\n            <Dialog.Portal keepMounted>\n              <Dialog.Backdrop className={styles.dialog.Backdrop} />\n              <Dialog.Popup className={styles.dialog.Popup}>\n                <Dialog.Title className={styles.dialog.Title}>Write a comment</Dialog.Title>\n                <textarea name=\"\" id=\"\" className={styles.toolbar.Textarea} />\n                <div className={styles.dialog.Actions}>\n                  <Dialog.Close data-color=\"red\" className={styles.dialog.Button}>\n                    Close\n                  </Dialog.Close>\n                  <Dialog.Close className={styles.dialog.Button}>Submit</Dialog.Close>\n                </div>\n              </Dialog.Popup>\n            </Dialog.Portal>\n          </Dialog.Root>\n\n          <Toolbar.Separator className={styles.demo.Separator} />\n\n          <Popover.Root>\n            <Tooltip.Root>\n              {renderTriggerWithTooltip({\n                render: (\n                  <Popover.Trigger>\n                    <SlidersIcon aria-label=\"RGB color picker\" className={styles.popover.Icon} />\n                  </Popover.Trigger>\n                ),\n                key: 'rgb-color-picker',\n                label: 'RGB color picker',\n                disabled: INT_POPOVER_DISABLED,\n              })}\n            </Tooltip.Root>\n\n            <Popover.Portal>\n              <Popover.Positioner sideOffset={8}>\n                <Popover.Popup className={styles.popover.Popup}>\n                  <Popover.Arrow className={styles.popover.Arrow}>\n                    <ArrowSvg />\n                  </Popover.Arrow>\n                  <Popover.Title className={styles.popover.Title} style={{ marginBottom: '1rem' }}>\n                    RGB Color Picker\n                  </Popover.Title>\n                  <ComboSlider color=\"r\" />\n                  <ComboSlider color=\"g\" />\n                  <ComboSlider color=\"b\" />\n                </Popover.Popup>\n              </Popover.Positioner>\n            </Popover.Portal>\n          </Popover.Root>\n\n          <Toolbar.Separator className={styles.demo.Separator} />\n\n          <Popover.Root>\n            {renderTriggerWithTooltip({\n              render: (\n                <Popover.Trigger>\n                  <BellIcon aria-label=\"Notifications\" className={styles.popover.Icon} />\n                </Popover.Trigger>\n              ),\n              key: 'notifications',\n              label: 'Notifications',\n              disabled: POPOVER_DISABLED,\n            })}\n\n            <Popover.Portal>\n              <Popover.Positioner sideOffset={8}>\n                <Popover.Popup className={styles.popover.Popup}>\n                  <Popover.Arrow className={styles.popover.Arrow}>\n                    <ArrowSvg />\n                  </Popover.Arrow>\n                  <Popover.Title className={styles.popover.Title}>Notifications</Popover.Title>\n                  <Popover.Description className={styles.popover.Description}>\n                    You are all caught up. Good job!\n                  </Popover.Description>\n                </Popover.Popup>\n              </Popover.Positioner>\n            </Popover.Portal>\n          </Popover.Root>\n\n          <Toolbar.Separator className={styles.demo.Separator} />\n\n          <AlertDialog.Root>\n            {renderTriggerWithTooltip({\n              render: (\n                <AlertDialog.Trigger>\n                  <TrashIcon className={styles.toolbar.Icon} />\n                </AlertDialog.Trigger>\n              ),\n              key: 'delete',\n              label: 'Delete',\n              disabled: ALERT_DIALOG_DISABLED,\n            })}\n\n            <AlertDialog.Portal keepMounted>\n              <AlertDialog.Backdrop className={styles.dialog.Backdrop} />\n              <AlertDialog.Popup className={styles.dialog.Popup}>\n                <AlertDialog.Title className={styles.dialog.Title}>\n                  Delete everything\n                </AlertDialog.Title>\n                <AlertDialog.Description className={styles.dialog.Description}>\n                  Are you sure? You cannot undo this.\n                </AlertDialog.Description>\n                <div className={styles.dialog.Actions}>\n                  <AlertDialog.Close className={styles.dialog.Button}>No, Cancel</AlertDialog.Close>\n                  <AlertDialog.Close data-color=\"red\" className={styles.dialog.Button}>\n                    Yes, Delete\n                  </AlertDialog.Close>\n                </div>\n              </AlertDialog.Popup>\n            </AlertDialog.Portal>\n          </AlertDialog.Root>\n\n          <Toolbar.Separator className={styles.demo.Separator} />\n\n          <Toolbar.Button\n            disabled={SWITCH_DISABLED}\n            className={classNames(styles.toolbar.Toggle, styles.demo.Toggle, styles.demo.Switch)}\n            render={\n              <Switch.Root defaultChecked className={styles.switch.Switch}>\n                <Switch.Thumb className={styles.switch.Thumb} style={{ marginRight: 'auto' }} />\n              </Switch.Root>\n            }\n          />\n        </Toolbar.Root>\n\n        <textarea className={styles.toolbar.Textarea} name=\"\" id=\"\" rows={8} defaultValue={TEXT} />\n      </div>\n    </React.Fragment>\n  );\n}\n\nfunction ComboSlider({ color }: { color: 'r' | 'g' | 'b' }) {\n  const id = React.useId();\n  const [val, setVal] = React.useState(20);\n  return (\n    <div className={styles.slider.Row}>\n      <Slider.Root\n        value={val}\n        onValueChange={(newValue, eventDetails) => {\n          if (color === 'r') {\n            eventDetails.event.preventDefault();\n          }\n          setVal(newValue as number);\n        }}\n        className={styles.slider.Slider}\n        min={0}\n        max={255}\n      >\n        <Slider.Value className={styles.slider.Value} />\n        <Slider.Control className={styles.slider.Control}>\n          <Slider.Track className={styles.slider.Track}>\n            <Slider.Indicator\n              className={classNames(styles.slider.Indicator, styles.slider[color])}\n            />\n            <Slider.Thumb className={styles.slider.Thumb} />\n          </Slider.Track>\n        </Slider.Control>\n      </Slider.Root>\n      <NumberField.Root\n        id={id}\n        value={val}\n        onValueChange={(newValue) => {\n          if (newValue != null) {\n            setVal(newValue);\n          }\n        }}\n        className={styles.slider.Field}\n      >\n        <NumberField.Group className={styles.slider.Group}>\n          <NumberField.Input className={styles.slider.Input} />\n        </NumberField.Group>\n      </NumberField.Root>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/tooltip/disabled.module.css",
    "content": ".Container {\n  display: flex;\n  gap: 0.5rem;\n  flex-wrap: wrap;\n  justify-content: center;\n}\n\n.ButtonGroup {\n  display: flex;\n}\n\n.IconButton {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 2.5rem;\n  height: 2.5rem;\n  padding: 0;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-50);\n  color: var(--color-gray-900);\n  user-select: none;\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:only-child {\n    border-radius: 0.375rem;\n  }\n\n  &:first-child:not(:only-child) {\n    border-radius: 0.375rem 0 0 0.375rem;\n  }\n\n  &:last-child:not(:only-child) {\n    border-radius: 0 0.375rem 0.375rem 0;\n  }\n\n  &:not(:first-child) {\n    border-left: none;\n  }\n}\n\n.Icon {\n  width: 1.25rem;\n  height: 1.25rem;\n}\n\n.Positioner {\n  height: var(--positioner-height);\n  width: var(--positioner-width);\n  max-width: var(--available-width);\n}\n\n.Popup {\n  box-sizing: border-box;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  padding: 0.25rem 0.5rem;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  transform-origin: var(--transform-origin);\n  transition:\n    transform 150ms,\n    opacity 150ms;\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  &[data-instant] {\n    transition: none;\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Arrow {\n  display: flex;\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n\n.Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  min-height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 500;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover:not([data-disabled]) {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active:not([data-disabled]) {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n\n  &[data-disabled] {\n    opacity: 0.5;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/tooltip/disabled.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Button } from '@base-ui/react/button';\nimport { Tooltip } from '@base-ui/react/tooltip';\nimport styles from './disabled.module.css';\n\nconst demoTooltip = Tooltip.createHandle();\n\nexport default function TooltipDetachedTriggersSimpleDemo() {\n  return (\n    <React.Fragment>\n      <div className={styles.ButtonGroup}>\n        <Tooltip.Trigger className={styles.Button} handle={demoTooltip}>\n          Enabled trigger\n        </Tooltip.Trigger>\n\n        <Tooltip.Trigger className={styles.Button} handle={demoTooltip} disabled>\n          Disabled trigger\n        </Tooltip.Trigger>\n\n        <Tooltip.Trigger\n          className={styles.Button}\n          handle={demoTooltip}\n          render={<Button disabled focusableWhenDisabled />}\n        >\n          Enabled trigger rendered as disabled button\n        </Tooltip.Trigger>\n\n        <Tooltip.Trigger\n          className={styles.Button}\n          handle={demoTooltip}\n          disabled\n          render={<Button disabled focusableWhenDisabled />}\n        >\n          Disabled trigger rendered as disabled button\n        </Tooltip.Trigger>\n      </div>\n\n      <Tooltip.Root handle={demoTooltip}>\n        <Tooltip.Portal>\n          <Tooltip.Positioner sideOffset={10}>\n            <Tooltip.Popup className={styles.Popup}>\n              <Tooltip.Arrow className={styles.Arrow}>\n                <ArrowSvg />\n              </Tooltip.Arrow>\n              Tooltip\n            </Tooltip.Popup>\n          </Tooltip.Positioner>\n        </Tooltip.Portal>\n      </Tooltip.Root>\n    </React.Fragment>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/tooltip/prevent-open.module.css",
    "content": ".Root {\n  display: flex;\n  border: 1px solid var(--color-gray-200);\n  border-radius: var(--radius-md);\n  background-color: var(--color-gray-50);\n  padding: 0.125rem;\n}\n\n.Trigger {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 2rem;\n  height: 2rem;\n  border-radius: var(--radius-sm);\n  color: var(--color-gray-900);\n  user-select: none;\n  outline: none;\n}\n\n.Trigger:hover {\n  background-color: var(--color-gray-100);\n}\n\n.Trigger:active {\n  background-color: var(--color-gray-200);\n}\n\n.Trigger:focus-visible {\n  outline: 2px solid var(--color-blue-800);\n  outline-offset: -1px;\n}\n\n.Trigger:focus-visible:not(:hover) {\n  background-color: transparent;\n}\n\n.Trigger[data-popup-open] {\n  background-color: var(--color-gray-100);\n}\n\n.TriggerIcon {\n  width: 1rem;\n  height: 1rem;\n}\n\n.Popup {\n  display: flex;\n  flex-direction: column;\n  transform-origin: var(--transform-origin);\n  border-radius: var(--radius-md);\n  background-color: canvas;\n  padding: 0.25rem 0.5rem;\n  font-size: var(--text-sm);\n  box-shadow: var(--shadow-lg);\n  outline: 1px solid var(--color-gray-200);\n  transition:\n    transform 150ms,\n    scale 150ms,\n    opacity 150ms;\n}\n\n.Popup[data-starting-style],\n.Popup[data-ending-style] {\n  scale: 0.9;\n  opacity: 0;\n}\n\n.Popup[data-instant] {\n  transition-duration: 0ms;\n}\n\n.Arrow[data-side='bottom'] {\n  top: -8px;\n}\n\n.Arrow[data-side='left'] {\n  right: -13px;\n  rotate: 90deg;\n}\n\n.Arrow[data-side='right'] {\n  left: -13px;\n  rotate: -90deg;\n}\n\n.Arrow[data-side='top'] {\n  bottom: -8px;\n  rotate: 180deg;\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowStrokeLight {\n  fill: var(--color-gray-200);\n}\n\n.ArrowStrokeDark {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n\n@media (prefers-color-scheme: dark) {\n  .Popup {\n    box-shadow: none;\n    outline-color: var(--color-gray-300);\n    outline-offset: -1px;\n  }\n\n  .ArrowStrokeLight {\n    fill: none;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/tooltip/prevent-open.tsx",
    "content": "/* eslint-disable jsx-a11y/no-static-element-interactions */\n'use client';\nimport * as React from 'react';\nimport { Tooltip } from '@base-ui/react/tooltip';\nimport styles from './prevent-open.module.css';\n\nexport default function ExampleTooltip() {\n  return (\n    <Tooltip.Provider>\n      <div\n        className={styles.Root}\n        onKeyDown={(event) => {\n          if (event.key === 'Escape') {\n            console.log('Escape');\n          }\n        }}\n      >\n        <Tooltip.Root\n          onOpenChange={(open, eventDetails) => {\n            if (eventDetails.reason === 'trigger-press') {\n              eventDetails.cancel();\n            }\n          }}\n        >\n          <Tooltip.Trigger className={styles.Trigger}>\n            <BoldIcon aria-label=\"Bold\" className={styles.TriggerIcon} />\n          </Tooltip.Trigger>\n          <Tooltip.Portal>\n            <Tooltip.Positioner sideOffset={10}>\n              <Tooltip.Popup className={styles.Popup}>\n                <Tooltip.Arrow className={styles.Arrow}>\n                  <ArrowSvg />\n                </Tooltip.Arrow>\n                Bold\n              </Tooltip.Popup>\n            </Tooltip.Positioner>\n          </Tooltip.Portal>\n        </Tooltip.Root>\n\n        <Tooltip.Root\n          onOpenChange={(open, eventDetails) => {\n            if (eventDetails.reason === 'escape-key') {\n              eventDetails.allowPropagation();\n            }\n          }}\n        >\n          <Tooltip.Trigger className={styles.Trigger}>\n            <ItalicIcon aria-label=\"Italic\" className={styles.TriggerIcon} />\n          </Tooltip.Trigger>\n          <Tooltip.Portal>\n            <Tooltip.Positioner sideOffset={10}>\n              <Tooltip.Popup className={styles.Popup}>\n                <Tooltip.Arrow className={styles.Arrow}>\n                  <ArrowSvg />\n                </Tooltip.Arrow>\n                Italic\n              </Tooltip.Popup>\n            </Tooltip.Positioner>\n          </Tooltip.Portal>\n        </Tooltip.Root>\n\n        <Tooltip.Root>\n          <Tooltip.Trigger className={styles.Trigger}>\n            <UnderlineIcon aria-label=\"Underline\" className={styles.TriggerIcon} />\n          </Tooltip.Trigger>\n          <Tooltip.Portal>\n            <Tooltip.Positioner sideOffset={10}>\n              <Tooltip.Popup className={styles.Popup}>\n                <Tooltip.Arrow className={styles.Arrow}>\n                  <ArrowSvg />\n                </Tooltip.Arrow>\n                Underline\n              </Tooltip.Popup>\n            </Tooltip.Positioner>\n          </Tooltip.Portal>\n        </Tooltip.Root>\n      </div>\n    </Tooltip.Provider>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={styles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={styles.ArrowStrokeLight}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={styles.ArrowStrokeDark}\n      />\n    </svg>\n  );\n}\n\nfunction BoldIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentcolor\" {...props}>\n      <path d=\"M3.73353 2.13333C3.4386 2.13333 3.2002 2.37226 3.2002 2.66666C3.2002 2.96106 3.4386 3.2 3.73353 3.2H4.26686V12.8H3.73353C3.4386 12.8 3.2002 13.0389 3.2002 13.3333C3.2002 13.6277 3.4386 13.8667 3.73353 13.8667H9.86686C11.7783 13.8667 13.3335 12.3115 13.3335 10.4C13.3335 8.9968 12.4945 7.78881 11.2929 7.24375C11.8897 6.70615 12.2669 5.93066 12.2669 5.06666C12.2669 3.44906 10.9506 2.13333 9.33353 2.13333H3.73353ZM6.93353 3.2H8.26686C9.29619 3.2 10.1335 4.03733 10.1335 5.06666C10.1335 6.096 9.29619 6.93333 8.26686 6.93333H6.93353V3.2ZM6.93353 8H7.73353H8.26686C9.59006 8 10.6669 9.0768 10.6669 10.4C10.6669 11.7232 9.59006 12.8 8.26686 12.8H6.93353V8Z\" />\n    </svg>\n  );\n}\n\nfunction ItalicIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentcolor\" {...props}>\n      <path d=\"M8.52599 2.12186C8.48583 2.12267 8.44578 2.1265 8.4062 2.13332H6.93328C6.86261 2.13232 6.79244 2.14538 6.72686 2.17173C6.66127 2.19808 6.60158 2.23721 6.55125 2.28683C6.50092 2.33646 6.46096 2.39559 6.43368 2.46079C6.4064 2.526 6.39235 2.59597 6.39235 2.66665C6.39235 2.73733 6.4064 2.80731 6.43368 2.87251C6.46096 2.93772 6.50092 2.99685 6.55125 3.04647C6.60158 3.0961 6.66127 3.13522 6.72686 3.16157C6.79244 3.18793 6.86261 3.20099 6.93328 3.19999H7.70099L6.69057 12.8H5.86661C5.79594 12.799 5.72577 12.812 5.66019 12.8384C5.59461 12.8648 5.53492 12.9039 5.48459 12.9535C5.43425 13.0031 5.39429 13.0623 5.36701 13.1275C5.33973 13.1927 5.32568 13.2626 5.32568 13.3333C5.32568 13.404 5.33973 13.474 5.36701 13.5392C5.39429 13.6044 5.43425 13.6635 5.48459 13.7131C5.53492 13.7628 5.59461 13.8019 5.66019 13.8282C5.72577 13.8546 5.79594 13.8677 5.86661 13.8667H9.06661C9.13729 13.8677 9.20745 13.8546 9.27304 13.8282C9.33862 13.8019 9.39831 13.7628 9.44864 13.7131C9.49897 13.6635 9.53894 13.6044 9.56622 13.5392C9.5935 13.474 9.60754 13.404 9.60754 13.3333C9.60754 13.2626 9.5935 13.1927 9.56622 13.1275C9.53894 13.0623 9.49897 13.0031 9.44864 12.9535C9.39831 12.9039 9.33862 12.8648 9.27304 12.8384C9.20745 12.812 9.13729 12.799 9.06661 12.8H8.2989L9.30932 3.19999H10.1333C10.204 3.20099 10.2741 3.18793 10.3397 3.16157C10.4053 3.13522 10.465 3.0961 10.5153 3.04647C10.5656 2.99685 10.6056 2.93772 10.6329 2.87251C10.6602 2.80731 10.6742 2.73733 10.6742 2.66665C10.6742 2.59597 10.6602 2.526 10.6329 2.46079C10.6056 2.39559 10.5656 2.33646 10.5153 2.28683C10.465 2.23721 10.4053 2.19808 10.3397 2.17173C10.2741 2.14538 10.204 2.13232 10.1333 2.13332H8.66349C8.61807 2.12555 8.57207 2.12171 8.52599 2.12186Z\" />\n    </svg>\n  );\n}\n\nfunction UnderlineIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentcolor\" {...props}>\n      <path d=\"M3.73331 2.13332C3.66264 2.13232 3.59247 2.14538 3.52689 2.17173C3.46131 2.19809 3.40161 2.23721 3.35128 2.28684C3.30095 2.33646 3.26099 2.39559 3.23371 2.4608C3.20643 2.526 3.19238 2.59598 3.19238 2.66666C3.19238 2.73734 3.20643 2.80731 3.23371 2.87252C3.26099 2.93772 3.30095 2.99685 3.35128 3.04648C3.40161 3.0961 3.46131 3.13523 3.52689 3.16158C3.59247 3.18793 3.66264 3.20099 3.73331 3.19999V7.99999C3.73331 10.224 5.55144 12.2667 7.99998 12.2667C10.4485 12.2667 12.2666 10.224 12.2666 7.99999V3.19999C12.3373 3.20099 12.4075 3.18793 12.4731 3.16158C12.5386 3.13523 12.5983 3.0961 12.6487 3.04648C12.699 2.99685 12.739 2.93772 12.7662 2.87252C12.7935 2.80731 12.8076 2.73734 12.8076 2.66666C12.8076 2.59598 12.7935 2.526 12.7662 2.4608C12.739 2.39559 12.699 2.33646 12.6487 2.28684C12.5983 2.23721 12.5386 2.19809 12.4731 2.17173C12.4075 2.14538 12.3373 2.13232 12.2666 2.13332H10.1333C10.0626 2.13232 9.99247 2.14538 9.92689 2.17173C9.8613 2.19809 9.80161 2.23721 9.75128 2.28684C9.70095 2.33646 9.66099 2.39559 9.63371 2.4608C9.60643 2.526 9.59238 2.59598 9.59238 2.66666C9.59238 2.73734 9.60643 2.80731 9.63371 2.87252C9.66099 2.93772 9.70095 2.99685 9.75128 3.04648C9.80161 3.0961 9.8613 3.13523 9.92689 3.16158C9.99247 3.18793 10.0626 3.20099 10.1333 3.19999V8.97187C10.1333 10.0855 9.32179 11.0818 8.21352 11.1896C6.94152 11.3138 5.86665 10.3136 5.86665 9.06666V3.19999C5.93732 3.20099 6.00748 3.18793 6.07307 3.16158C6.13865 3.13523 6.19834 3.0961 6.24867 3.04648C6.299 2.99685 6.33897 2.93772 6.36625 2.87252C6.39353 2.80731 6.40757 2.73734 6.40757 2.66666C6.40757 2.59598 6.39353 2.526 6.36625 2.4608C6.33897 2.39559 6.299 2.33646 6.24867 2.28684C6.19834 2.23721 6.13865 2.19809 6.07307 2.17173C6.00748 2.14538 5.93732 2.13232 5.86665 2.13332H3.73331ZM3.73331 13.3333C3.66264 13.3323 3.59247 13.3454 3.52689 13.3717C3.46131 13.3981 3.40161 13.4372 3.35128 13.4868C3.30095 13.5365 3.26099 13.5956 3.23371 13.6608C3.20643 13.726 3.19238 13.796 3.19238 13.8667C3.19238 13.9373 3.20643 14.0073 3.23371 14.0725C3.26099 14.1377 3.30095 14.1969 3.35128 14.2465C3.40161 14.2961 3.46131 14.3352 3.52689 14.3616C3.59247 14.3879 3.66264 14.401 3.73331 14.4H12.2666C12.3373 14.401 12.4075 14.3879 12.4731 14.3616C12.5386 14.3352 12.5983 14.2961 12.6487 14.2465C12.699 14.1969 12.739 14.1377 12.7662 14.0725C12.7935 14.0073 12.8076 13.9373 12.8076 13.8667C12.8076 13.796 12.7935 13.726 12.7662 13.6608C12.739 13.5956 12.699 13.5365 12.6487 13.4868C12.5983 13.4372 12.5386 13.3981 12.4731 13.3717C12.4075 13.3454 12.3373 13.3323 12.2666 13.3333H3.73331Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/tooltip/tooltips.module.css",
    "content": ".Container {\n  display: flex;\n  flex-direction: column;\n  gap: 0.75rem;\n  margin-bottom: 2rem;\n}\n\n.Panel {\n  display: flex;\n  border: 1px solid var(--color-gray-200);\n  background-color: var(--color-gray-50);\n  border-radius: 0.375rem;\n  padding: 0.125rem;\n  width: max-content;\n}\n\n.Button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 2rem;\n  height: 2rem;\n  padding: 0;\n  margin: 0;\n  outline: 0;\n  border: 0;\n  border-radius: 0.25rem;\n  background-color: transparent;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    background-color: transparent;\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-200);\n  }\n}\n\n.Icon {\n  width: 1rem;\n  height: 1rem;\n}\n\n.ControlButton {\n  box-sizing: border-box;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  gap: 0.375rem;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 500;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.Positioner {\n  --easing: cubic-bezier(0.22, 1, 0.36, 1);\n  --animation-duration: 0.35s;\n\n  width: var(--positioner-width);\n  height: var(--positioner-height);\n  max-width: var(--available-width);\n\n  transition-property: top, left, right, bottom, transform;\n  transition-timing-function: var(--easing);\n  transition-duration: var(--animation-duration);\n\n  /* Disable transitions when data-instant is set (used for the initial positioning of the popup) */\n  &[data-instant] {\n    transition: none;\n  }\n}\n\n.Popup {\n  box-sizing: border-box;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  display: flex;\n  flex-direction: column;\n  padding: 0.25rem 0.5rem;\n  border-radius: 0.375rem;\n  background-color: canvas;\n  transform-origin: var(--transform-origin);\n  transition:\n    width 150ms,\n    height 150ms,\n    transform 150ms,\n    opacity 150ms;\n\n  /* These are required to make the size animations work */\n  width: var(--popup-width, auto);\n  height: var(--popup-height, auto);\n\n  &[data-starting-style],\n  &[data-ending-style] {\n    opacity: 0;\n    transform: scale(0.9);\n  }\n\n  &[data-instant] {\n    transition-duration: 0ms;\n  }\n\n  @media (prefers-color-scheme: light) {\n    outline: 1px solid var(--color-gray-200);\n    box-shadow:\n      0 10px 15px -3px var(--color-gray-200),\n      0 4px 6px -4px var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    outline: 1px solid var(--color-gray-300);\n    outline-offset: -1px;\n  }\n}\n\n.Arrow {\n  display: flex;\n\n  &[data-side='top'] {\n    bottom: -8px;\n    rotate: 180deg;\n  }\n\n  &[data-side='bottom'] {\n    top: -8px;\n    rotate: 0deg;\n  }\n\n  &[data-side='left'] {\n    right: -13px;\n    rotate: 90deg;\n  }\n\n  &[data-side='right'] {\n    left: -13px;\n    rotate: -90deg;\n  }\n}\n\n.ArrowFill {\n  fill: canvas;\n}\n\n.ArrowOuterStroke {\n  @media (prefers-color-scheme: light) {\n    fill: var(--color-gray-200);\n  }\n}\n\n.ArrowInnerStroke {\n  @media (prefers-color-scheme: dark) {\n    fill: var(--color-gray-300);\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/tooltip/tooltips.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Tooltip } from '@base-ui/react/tooltip';\nimport { StoreInspector } from '@base-ui/utils/store';\nimport demoStyles from 'docs/src/app/(docs)/react/components/tooltip/demos/detached-triggers-full/css-modules/index.module.css';\nimport { SettingsMetadata, useExperimentSettings } from '../_components/SettingsPanel';\nimport styles from './tooltips.module.css';\n\nconst tooltip1Handle = Tooltip.createHandle<string>();\nconst tooltip2Handle = Tooltip.createHandle<string>();\n\ninterface Settings {\n  delay: number;\n  closeDelay: number;\n  side: 'top' | 'bottom' | 'left' | 'right';\n  disableHoverablePopup: boolean;\n  trackCursorAxis: 'none' | 'x' | 'y' | 'both';\n  keepMounted: boolean;\n}\n\nexport default function TooltipsExperiment() {\n  const { settings } = useExperimentSettings<Settings>();\n  const [singleTriggerOpen, setSingleTriggerOpen] = React.useState(false);\n  const [controlledWithinRootOpen, setControlledWithinRootOpen] = React.useState(false);\n  const [controlledWithinRootTriggerId, setControlledWithinRootTriggerId] = React.useState<\n    string | null\n  >(null);\n  const [controlledDetachedOpen, setControlledDetachedOpen] = React.useState(false);\n  const [controlledDetachedTriggerId, setControlledDetachedTriggerId] = React.useState<\n    string | null\n  >(null);\n\n  return (\n    <div>\n      <h1>Tooltips</h1>\n      <div className={styles.Container}>\n        <h2>Uncontrolled, single trigger</h2>\n        <Tooltip.Provider>\n          <div className={styles.Panel}>\n            <Tooltip.Root\n              disableHoverablePopup={settings.disableHoverablePopup}\n              trackCursorAxis={settings.trackCursorAxis}\n            >\n              <TooltipTriggerButton delay={settings.delay} closeDelay={settings.closeDelay}>\n                <BoldIcon />\n              </TooltipTriggerButton>\n              <TooltipContent>Bold</TooltipContent>\n            </Tooltip.Root>\n\n            <Tooltip.Root\n              disableHoverablePopup={settings.disableHoverablePopup}\n              trackCursorAxis={settings.trackCursorAxis}\n            >\n              <TooltipTriggerButton delay={settings.delay} closeDelay={settings.closeDelay}>\n                <ItalicIcon />\n              </TooltipTriggerButton>\n              <TooltipContent>Italic</TooltipContent>\n            </Tooltip.Root>\n\n            <Tooltip.Root\n              disableHoverablePopup={settings.disableHoverablePopup}\n              trackCursorAxis={settings.trackCursorAxis}\n            >\n              <TooltipTriggerButton delay={settings.delay} closeDelay={settings.closeDelay}>\n                <UnderlineIcon />\n              </TooltipTriggerButton>\n              <TooltipContent>Underline</TooltipContent>\n            </Tooltip.Root>\n          </div>\n        </Tooltip.Provider>\n      </div>\n\n      <div className={styles.Container}>\n        <h2>Controlled, single trigger</h2>\n        <Tooltip.Provider>\n          <div className={styles.Panel}>\n            <Tooltip.Root\n              open={singleTriggerOpen}\n              onOpenChange={(nextOpen) => setSingleTriggerOpen(nextOpen)}\n              disableHoverablePopup={settings.disableHoverablePopup}\n              trackCursorAxis={settings.trackCursorAxis}\n            >\n              <TooltipTriggerButton delay={settings.delay} closeDelay={settings.closeDelay}>\n                <BoldIcon />\n              </TooltipTriggerButton>\n              <TooltipContent>Bold</TooltipContent>\n            </Tooltip.Root>\n          </div>\n        </Tooltip.Provider>\n        <button\n          type=\"button\"\n          className={styles.ControlButton}\n          onClick={() => setSingleTriggerOpen(true)}\n        >\n          Open externally\n        </button>\n      </div>\n\n      <div className={styles.Container}>\n        <h2>Uncontrolled, multiple triggers within Root</h2>\n        <Tooltip.Provider>\n          <div className={styles.Panel}>\n            <Tooltip.Root\n              disableHoverablePopup={settings.disableHoverablePopup}\n              trackCursorAxis={settings.trackCursorAxis}\n            >\n              {({ payload }) => (\n                <React.Fragment>\n                  <TooltipTriggerButton\n                    payload=\"Bold\"\n                    delay={settings.delay}\n                    closeDelay={settings.closeDelay}\n                  >\n                    <BoldIcon />\n                  </TooltipTriggerButton>\n                  <TooltipTriggerButton\n                    payload=\"Italic\"\n                    delay={settings.delay}\n                    closeDelay={settings.closeDelay}\n                  >\n                    <ItalicIcon />\n                  </TooltipTriggerButton>\n                  <TooltipTriggerButton\n                    payload=\"Underline\"\n                    delay={settings.delay}\n                    closeDelay={settings.closeDelay}\n                  >\n                    <UnderlineIcon />\n                  </TooltipTriggerButton>\n                  <TooltipContent>{payload as string}</TooltipContent>\n                </React.Fragment>\n              )}\n            </Tooltip.Root>\n          </div>\n        </Tooltip.Provider>\n      </div>\n\n      <div className={styles.Container}>\n        <h2>Controlled, multiple triggers within Root</h2>\n        <Tooltip.Provider>\n          <div className={styles.Panel}>\n            <Tooltip.Root\n              open={controlledWithinRootOpen}\n              onOpenChange={(open, eventDetails) => {\n                setControlledWithinRootOpen(open);\n                setControlledWithinRootTriggerId(eventDetails.trigger?.id ?? null);\n              }}\n              triggerId={controlledWithinRootTriggerId}\n              disableHoverablePopup={settings.disableHoverablePopup}\n              trackCursorAxis={settings.trackCursorAxis}\n            >\n              {({ payload }) => (\n                <React.Fragment>\n                  <TooltipTriggerButton\n                    payload=\"Bold\"\n                    id=\"within-root-bold\"\n                    delay={settings.delay}\n                    closeDelay={settings.closeDelay}\n                  >\n                    <BoldIcon />\n                  </TooltipTriggerButton>\n                  <TooltipTriggerButton\n                    payload=\"Italic\"\n                    id=\"within-root-italic\"\n                    delay={settings.delay}\n                    closeDelay={settings.closeDelay}\n                  >\n                    <ItalicIcon />\n                  </TooltipTriggerButton>\n                  <TooltipTriggerButton\n                    payload=\"Underline\"\n                    id=\"within-root-underline\"\n                    delay={settings.delay}\n                    closeDelay={settings.closeDelay}\n                  >\n                    <UnderlineIcon />\n                  </TooltipTriggerButton>\n                  <TooltipContent>{payload as string}</TooltipContent>\n                </React.Fragment>\n              )}\n            </Tooltip.Root>\n          </div>\n        </Tooltip.Provider>\n        <button\n          type=\"button\"\n          className={styles.ControlButton}\n          onClick={() => {\n            setControlledWithinRootOpen(true);\n            setControlledWithinRootTriggerId('within-root-italic');\n          }}\n        >\n          Open externally (Italic)\n        </button>\n      </div>\n\n      <div className={styles.Container}>\n        <h2>Uncontrolled, detached triggers</h2>\n        <StoreInspector store={tooltip1Handle.store} />\n        <Tooltip.Provider>\n          <div className={styles.Panel}>\n            <TooltipTriggerButton\n              payload=\"Bold\"\n              handle={tooltip1Handle}\n              delay={settings.delay}\n              closeDelay={settings.closeDelay}\n            >\n              <BoldIcon />\n            </TooltipTriggerButton>\n            <TooltipTriggerButton\n              payload=\"Italic\"\n              handle={tooltip1Handle}\n              delay={settings.delay}\n              closeDelay={settings.closeDelay}\n            >\n              <ItalicIcon />\n            </TooltipTriggerButton>\n            <TooltipTriggerButton\n              payload=\"Underline\"\n              handle={tooltip1Handle}\n              delay={settings.delay}\n              closeDelay={settings.closeDelay}\n            >\n              <UnderlineIcon />\n            </TooltipTriggerButton>\n          </div>\n        </Tooltip.Provider>\n\n        <Tooltip.Root\n          handle={tooltip1Handle}\n          disableHoverablePopup={settings.disableHoverablePopup}\n          trackCursorAxis={settings.trackCursorAxis}\n        >\n          {({ payload }) => <TooltipContent>{payload as string}</TooltipContent>}\n        </Tooltip.Root>\n      </div>\n\n      <div className={styles.Container}>\n        <h2>Controlled, detached triggers</h2>\n        <Tooltip.Provider>\n          <div className={styles.Panel}>\n            <TooltipTriggerButton\n              payload=\"Bold\"\n              handle={tooltip2Handle}\n              id=\"detached-bold-trigger\"\n              delay={settings.delay}\n              closeDelay={settings.closeDelay}\n            >\n              <BoldIcon />\n            </TooltipTriggerButton>\n            <TooltipTriggerButton\n              payload=\"Italic\"\n              handle={tooltip2Handle}\n              id=\"detached-italic-trigger\"\n              delay={settings.delay}\n              closeDelay={settings.closeDelay}\n            >\n              <ItalicIcon />\n            </TooltipTriggerButton>\n            <TooltipTriggerButton\n              payload=\"Underline\"\n              handle={tooltip2Handle}\n              id=\"detached-underline-trigger\"\n              delay={settings.delay}\n              closeDelay={settings.closeDelay}\n            >\n              <UnderlineIcon />\n            </TooltipTriggerButton>\n          </div>\n        </Tooltip.Provider>\n\n        <Tooltip.Root\n          handle={tooltip2Handle}\n          open={controlledDetachedOpen}\n          triggerId={controlledDetachedTriggerId}\n          onOpenChange={(open, eventDetails) => {\n            setControlledDetachedOpen(open);\n            setControlledDetachedTriggerId(eventDetails.trigger?.id ?? null);\n          }}\n          disableHoverablePopup={settings.disableHoverablePopup}\n          trackCursorAxis={settings.trackCursorAxis}\n        >\n          {({ payload }) => <TooltipContent>{payload as string}</TooltipContent>}\n        </Tooltip.Root>\n\n        <button\n          type=\"button\"\n          className={styles.ControlButton}\n          onClick={() => {\n            setControlledDetachedOpen(true);\n            setControlledDetachedTriggerId('detached-italic-trigger');\n          }}\n        >\n          Open externally (Italic)\n        </button>\n        <button\n          type=\"button\"\n          className={styles.ControlButton}\n          onClick={() => {\n            tooltip2Handle.open('detached-italic-trigger');\n          }}\n        >\n          Open via handle (Italic)\n        </button>\n      </div>\n    </div>\n  );\n}\n\nfunction TooltipTriggerButton(props: Tooltip.Trigger.Props<string>) {\n  return <Tooltip.Trigger className={`${demoStyles.Button} ${styles.Button}`} {...props} />;\n}\n\nfunction TooltipContent(props: Tooltip.Popup.Props) {\n  const { settings } = useExperimentSettings<Settings>();\n  const { children, ...otherProps } = props;\n  return (\n    <Tooltip.Portal keepMounted={settings.keepMounted}>\n      <Tooltip.Positioner sideOffset={10} className={demoStyles.Positioner} side={settings.side}>\n        <Tooltip.Popup className={demoStyles.Popup} {...otherProps}>\n          <Tooltip.Arrow className={demoStyles.Arrow}>\n            <ArrowSvg />\n          </Tooltip.Arrow>\n          <Tooltip.Viewport className={demoStyles.Viewport}>{children}</Tooltip.Viewport>\n        </Tooltip.Popup>\n      </Tooltip.Positioner>\n    </Tooltip.Portal>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={demoStyles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={demoStyles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={demoStyles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nfunction BoldIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentcolor\" {...props}>\n      <path d=\"M3.73353 2.13333C3.4386 2.13333 3.2002 2.37226 3.2002 2.66666C3.2002 2.96106 3.4386 3.2 3.73353 3.2H4.26686V12.8H3.73353C3.4386 12.8 3.2002 13.0389 3.2002 13.3333C3.2002 13.6277 3.4386 13.8667 3.73353 13.8667H9.86686C11.7783 13.8667 13.3335 12.3115 13.3335 10.4C13.3335 8.9968 12.4945 7.78881 11.2929 7.24375C11.8897 6.70615 12.2669 5.93066 12.2669 5.06666C12.2669 3.44906 10.9506 2.13333 9.33353 2.13333H3.73353ZM6.93353 3.2H8.26686C9.29619 3.2 10.1335 4.03733 10.1335 5.06666C10.1335 6.096 9.29619 6.93333 8.26686 6.93333H6.93353V3.2ZM6.93353 8H7.73353H8.26686C9.59006 8 10.6669 9.0768 10.6669 10.4C10.6669 11.7232 9.59006 12.8 8.26686 12.8H6.93353V8Z\" />\n    </svg>\n  );\n}\n\nfunction ItalicIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentcolor\" {...props}>\n      <path d=\"M8.52599 2.12186C8.48583 2.12267 8.44578 2.1265 8.4062 2.13332H6.93328C6.86261 2.13232 6.79244 2.14538 6.72686 2.17173C6.66127 2.19808 6.60158 2.23721 6.55125 2.28683C6.50092 2.33646 6.46096 2.39559 6.43368 2.46079C6.4064 2.526 6.39235 2.59597 6.39235 2.66665C6.39235 2.73733 6.4064 2.80731 6.43368 2.87251C6.46096 2.93772 6.50092 2.99685 6.55125 3.04647C6.60158 3.0961 6.66127 3.13522 6.72686 3.16157C6.79244 3.18793 6.86261 3.20099 6.93328 3.19999H7.70099L6.69057 12.8H5.86661C5.79594 12.799 5.72577 12.812 5.66019 12.8384C5.59461 12.8648 5.53492 12.9039 5.48459 12.9535C5.43425 13.0031 5.39429 13.0623 5.36701 13.1275C5.33973 13.1927 5.32568 13.2626 5.32568 13.3333C5.32568 13.404 5.33973 13.474 5.36701 13.5392C5.39429 13.6044 5.43425 13.6635 5.48459 13.7131C5.53492 13.7628 5.59461 13.8019 5.66019 13.8282C5.72577 13.8546 5.79594 13.8677 5.86661 13.8667H9.06661C9.13729 13.8677 9.20745 13.8546 9.27304 13.8282C9.33862 13.8019 9.39831 13.7628 9.44864 13.7131C9.49897 13.6635 9.53894 13.6044 9.56622 13.5392C9.5935 13.474 9.60754 13.404 9.60754 13.3333C9.60754 13.2626 9.5935 13.1927 9.56622 13.1275C9.53894 13.0623 9.49897 13.0031 9.44864 12.9535C9.39831 12.9039 9.33862 12.8648 9.27304 12.8384C9.20745 12.812 9.13729 12.799 9.06661 12.8H8.2989L9.30932 3.19999H10.1333C10.204 3.20099 10.2741 3.18793 10.3397 3.16157C10.4053 3.13522 10.465 3.0961 10.5153 3.04647C10.5656 2.99685 10.6056 2.93772 10.6329 2.87251C10.6602 2.80731 10.6742 2.73733 10.6742 2.66665C10.6742 2.59597 10.6602 2.526 10.6329 2.46079C10.6056 2.39559 10.5656 2.33646 10.5153 2.28683C10.465 2.23721 10.4053 2.19808 10.3397 2.17173C10.2741 2.14538 10.204 2.13232 10.1333 2.13332H8.66349C8.61807 2.12555 8.57207 2.12171 8.52599 2.12186Z\" />\n    </svg>\n  );\n}\n\nfunction UnderlineIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentcolor\" {...props}>\n      <path d=\"M3.73331 2.13332C3.66264 2.13232 3.59247 2.14538 3.52689 2.17173C3.46131 2.19809 3.40161 2.23721 3.35128 2.28684C3.30095 2.33646 3.26099 2.39559 3.23371 2.4608C3.20643 2.526 3.19238 2.59598 3.19238 2.66666C3.19238 2.73734 3.20643 2.80731 3.23371 2.87252C3.26099 2.93772 3.30095 2.99685 3.35128 3.04648C3.40161 3.0961 3.46131 3.13523 3.52689 3.16158C3.59247 3.18793 3.66264 3.20099 3.73331 3.19999V7.99999C3.73331 10.224 5.55144 12.2667 7.99998 12.2667C10.4485 12.2667 12.2666 10.224 12.2666 7.99999V3.19999C12.3373 3.20099 12.4075 3.18793 12.4731 3.16158C12.5386 3.13523 12.5983 3.0961 12.6487 3.04648C12.699 2.99685 12.739 2.93772 12.7662 2.87252C12.7935 2.80731 12.8076 2.73734 12.8076 2.66666C12.8076 2.59598 12.7935 2.526 12.7662 2.4608C12.739 2.39559 12.699 2.33646 12.6487 2.28684C12.5983 2.23721 12.5386 2.19809 12.4731 2.17173C12.4075 2.14538 12.3373 2.13232 12.2666 2.13332H10.1333C10.0626 2.13232 9.99247 2.14538 9.92689 2.17173C9.8613 2.19809 9.80161 2.23721 9.75128 2.28684C9.70095 2.33646 9.66099 2.39559 9.63371 2.4608C9.60643 2.526 9.59238 2.59598 9.59238 2.66666C9.59238 2.73734 9.60643 2.80731 9.63371 2.87252C9.66099 2.93772 9.70095 2.99685 9.75128 3.04648C9.80161 3.0961 9.8613 3.13523 9.92689 3.16158C9.99247 3.18793 10.0626 3.20099 10.1333 3.19999V8.97187C10.1333 10.0855 9.32179 11.0818 8.21352 11.1896C6.94152 11.3138 5.86665 10.3136 5.86665 9.06666V3.19999C5.93732 3.20099 6.00748 3.18793 6.07307 3.16158C6.13865 3.13523 6.19834 3.0961 6.24867 3.04648C6.299 2.99685 6.33897 2.93772 6.36625 2.87252C6.39353 2.80731 6.40757 2.73734 6.40757 2.66666C6.40757 2.59598 6.39353 2.526 6.36625 2.4608C6.33897 2.39559 6.299 2.33646 6.24867 2.28684C6.19834 2.23721 6.13865 2.19809 6.07307 2.17173C6.00748 2.14538 5.93732 2.13232 5.86665 2.13332H3.73331ZM3.73331 13.3333C3.66264 13.3323 3.59247 13.3454 3.52689 13.3717C3.46131 13.3981 3.40161 13.4372 3.35128 13.4868C3.30095 13.5365 3.26099 13.5956 3.23371 13.6608C3.20643 13.726 3.19238 13.796 3.19238 13.8667C3.19238 13.9373 3.20643 14.0073 3.23371 14.0725C3.26099 14.1377 3.30095 14.1969 3.35128 14.2465C3.40161 14.2961 3.46131 14.3352 3.52689 14.3616C3.59247 14.3879 3.66264 14.401 3.73331 14.4H12.2666C12.3373 14.401 12.4075 14.3879 12.4731 14.3616C12.5386 14.3352 12.5983 14.2961 12.6487 14.2465C12.699 14.1969 12.739 14.1377 12.7662 14.0725C12.7935 14.0073 12.8076 13.9373 12.8076 13.8667C12.8076 13.796 12.7935 13.726 12.7662 13.6608C12.739 13.5956 12.699 13.5365 12.6487 13.4868C12.5983 13.4372 12.5386 13.3981 12.4731 13.3717C12.4075 13.3454 12.3373 13.3323 12.2666 13.3333H3.73331Z\" />\n    </svg>\n  );\n}\n\nexport const settingsMetadata: SettingsMetadata<Settings> = {\n  delay: {\n    type: 'number',\n    label: 'Delay',\n    default: 600,\n  },\n  closeDelay: {\n    type: 'number',\n    label: 'Close Delay',\n    default: 0,\n  },\n  side: {\n    type: 'string',\n    label: 'Side',\n    options: ['top', 'bottom', 'left', 'right'],\n    default: 'top',\n  },\n  disableHoverablePopup: {\n    type: 'boolean',\n    label: 'Disable hoverable popup',\n    default: true,\n  },\n  trackCursorAxis: {\n    type: 'string',\n    label: 'Track Cursor Axis',\n    options: ['none', 'x', 'y', 'both'],\n    default: 'none',\n  },\n  keepMounted: {\n    type: 'boolean',\n    label: 'Keep mounted',\n    default: false,\n  },\n};\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/tooltip/transitions.module.css",
    "content": "@keyframes scaleIn {\n  from {\n    opacity: 0;\n    transform: scale(0.8);\n  }\n}\n\n@keyframes scaleOut {\n  to {\n    opacity: 0;\n    transform: scale(0);\n  }\n}\n\n.TooltipPopup {\n  font-family: 'IBM Plex Sans', sans-serif;\n  background: black;\n  color: white;\n  padding: 4px 6px;\n  border-radius: 4px;\n  font-size: 95%;\n  cursor: default;\n  transform-origin: var(--transform-origin);\n\n  &[data-instant] {\n    transition-duration: 0s !important;\n    animation-duration: 0s !important;\n  }\n\n  &[data-type='css-animation'] {\n    animation: scaleIn 0.2s forwards;\n\n    &[data-ending-style] {\n      animation: scaleOut 0.2s forwards;\n    }\n  }\n\n  &[data-type='css-animation-keep-mounted'] {\n    animation: scaleIn 0.2s forwards;\n\n    &[data-ending-style] {\n      animation: scaleOut 0.2s forwards;\n    }\n  }\n\n  &[data-type='css-transition'] {\n    transition-property: opacity, transform;\n    transition-duration: 0.2s;\n\n    &[data-ending-style] {\n      opacity: 0;\n      transform: scale(0);\n    }\n\n    &[data-starting-style] {\n      opacity: 0;\n      transform: scale(0.8);\n    }\n  }\n\n  &[data-type='css-transition-keep-mounted'] {\n    transition-property: opacity, transform;\n    transition-duration: 0.2s;\n\n    &[data-ending-style] {\n      opacity: 0;\n      transform: scale(0);\n    }\n\n    &[data-starting-style] {\n      opacity: 0;\n      transform: scale(0.8);\n    }\n  }\n\n  &[data-type='css-transition-starting-style'] {\n    transition-property: opacity, transform, visibility;\n    transition-duration: 0.2s;\n\n    @starting-style {\n      opacity: 0;\n      transform: scale(0.8);\n    }\n\n    &[data-ending-style] {\n      opacity: 0;\n      transform: scale(0);\n    }\n  }\n}\n\n.AnchorButton {\n  border: none;\n  background: #0072e6;\n  color: white;\n  padding: 8px 16px;\n  border-radius: 4px;\n  font-size: 16px;\n\n  &:focus-visible {\n    outline: 2px solid #3399ff;\n    outline-offset: 2px;\n  }\n\n  &:hover,\n  &[data-popup-open] {\n    background: #004c99;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/tooltip/transitions.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Tooltip } from '@base-ui/react/tooltip';\nimport { motion, AnimatePresence } from 'motion/react';\nimport styles from './transitions.module.css';\n\nexport function TooltipPopup({ children, ...props }: React.ComponentProps<typeof Tooltip.Popup>) {\n  return (\n    <Tooltip.Popup {...props} className={styles.TooltipPopup}>\n      {children}\n    </Tooltip.Popup>\n  );\n}\n\nexport function AnchorButton({ children, ...props }: React.ComponentProps<typeof Tooltip.Trigger>) {\n  return (\n    <Tooltip.Trigger {...props} className={styles.AnchorButton}>\n      {children}\n    </Tooltip.Trigger>\n  );\n}\n\nexport default function TooltipTransitionExperiment() {\n  return (\n    <div\n      style={{\n        width: 700,\n        padding: 50,\n        margin: '0 auto',\n        fontFamily: '\"IBM Plex Sans\", sans-serif',\n      }}\n    >\n      <h1>Base UI Tooltip Popup Animations</h1>\n      <hr />\n\n      <h2>Conditional Rendering</h2>\n\n      <h3>CSS Animation Group</h3>\n      <div style={{ display: 'flex', gap: 5 }}>\n        <Tooltip.Provider closeDelay={200}>\n          <Tooltip.Root>\n            <AnchorButton>Anchor</AnchorButton>\n            <Tooltip.Portal>\n              <Tooltip.Positioner sideOffset={(sizes) => sizes.anchor.height}>\n                <TooltipPopup data-type=\"css-animation\">Tooltip</TooltipPopup>\n              </Tooltip.Positioner>\n            </Tooltip.Portal>\n          </Tooltip.Root>\n          <Tooltip.Root>\n            <AnchorButton>Anchor</AnchorButton>\n            <Tooltip.Portal>\n              <Tooltip.Positioner sideOffset={7}>\n                <TooltipPopup data-type=\"css-animation\">Tooltip</TooltipPopup>\n              </Tooltip.Positioner>\n            </Tooltip.Portal>\n          </Tooltip.Root>\n        </Tooltip.Provider>\n      </div>\n      <h3>CSS Animation</h3>\n      <Tooltip.Root>\n        <AnchorButton>Anchor</AnchorButton>\n        <Tooltip.Portal>\n          <Tooltip.Positioner sideOffset={7}>\n            <TooltipPopup data-type=\"css-animation\">Tooltip</TooltipPopup>\n          </Tooltip.Positioner>\n        </Tooltip.Portal>\n      </Tooltip.Root>\n\n      <h3>CSS Transition Group</h3>\n      <div style={{ display: 'flex', gap: 5 }}>\n        <Tooltip.Provider closeDelay={200}>\n          <Tooltip.Root>\n            <AnchorButton>Anchor</AnchorButton>\n            <Tooltip.Portal>\n              <Tooltip.Positioner sideOffset={7}>\n                <TooltipPopup data-type=\"css-transition\">Tooltip</TooltipPopup>\n              </Tooltip.Positioner>\n            </Tooltip.Portal>\n          </Tooltip.Root>\n          <Tooltip.Root>\n            <AnchorButton>Anchor</AnchorButton>\n            <Tooltip.Portal>\n              <Tooltip.Positioner sideOffset={7}>\n                <TooltipPopup data-type=\"css-transition\">Tooltip</TooltipPopup>\n              </Tooltip.Positioner>\n            </Tooltip.Portal>\n          </Tooltip.Root>\n        </Tooltip.Provider>\n      </div>\n      <h3>CSS Transition</h3>\n      <Tooltip.Root>\n        <AnchorButton>Anchor</AnchorButton>\n        <Tooltip.Portal>\n          <Tooltip.Positioner sideOffset={7}>\n            <TooltipPopup data-type=\"css-transition\">Tooltip</TooltipPopup>\n          </Tooltip.Positioner>\n        </Tooltip.Portal>\n      </Tooltip.Root>\n\n      <h3>CSS Transition with `@starting-style`</h3>\n      <p>\n        No longer supported due to{' '}\n        <a href=\"https://github.com/mui/base-ui/pull/992\">\n          https://github.com/mui/base-ui/pull/992\n        </a>\n      </p>\n      <Tooltip.Root>\n        <AnchorButton>Anchor</AnchorButton>\n        <Tooltip.Portal>\n          <Tooltip.Positioner sideOffset={7}>\n            <TooltipPopup data-type=\"css-transition-starting-style\">Tooltip</TooltipPopup>\n          </Tooltip.Positioner>\n        </Tooltip.Portal>\n      </Tooltip.Root>\n\n      <hr />\n\n      <h2>Keep Mounted</h2>\n\n      <h3>CSS Animation Group</h3>\n      <div style={{ display: 'flex', gap: 5 }}>\n        <Tooltip.Provider closeDelay={200}>\n          <Tooltip.Root>\n            <AnchorButton>Anchor</AnchorButton>\n            <Tooltip.Portal keepMounted>\n              <Tooltip.Positioner sideOffset={7}>\n                <TooltipPopup data-type=\"css-animation-keep-mounted\">Tooltip</TooltipPopup>\n              </Tooltip.Positioner>\n            </Tooltip.Portal>\n          </Tooltip.Root>\n          <Tooltip.Root>\n            <AnchorButton>Anchor</AnchorButton>\n            <Tooltip.Portal keepMounted>\n              <Tooltip.Positioner sideOffset={7}>\n                <TooltipPopup data-type=\"css-animation-keep-mounted\">Tooltip</TooltipPopup>\n              </Tooltip.Positioner>\n            </Tooltip.Portal>\n          </Tooltip.Root>\n        </Tooltip.Provider>\n      </div>\n      <h3>CSS Animation</h3>\n      <Tooltip.Root>\n        <AnchorButton>Anchor</AnchorButton>\n        <Tooltip.Portal keepMounted>\n          <Tooltip.Positioner sideOffset={7}>\n            <TooltipPopup data-type=\"css-animation-keep-mounted\">Tooltip</TooltipPopup>\n          </Tooltip.Positioner>\n        </Tooltip.Portal>\n      </Tooltip.Root>\n\n      <h3>CSS Transition Group</h3>\n      <div style={{ display: 'flex', gap: 5 }}>\n        <Tooltip.Provider closeDelay={200}>\n          <Tooltip.Root>\n            <AnchorButton>Anchor</AnchorButton>\n            <Tooltip.Portal keepMounted>\n              <Tooltip.Positioner sideOffset={7}>\n                <TooltipPopup data-type=\"css-transition-keep-mounted\">Tooltip</TooltipPopup>\n              </Tooltip.Positioner>\n            </Tooltip.Portal>\n          </Tooltip.Root>\n          <Tooltip.Root>\n            <AnchorButton>Anchor</AnchorButton>\n            <Tooltip.Portal keepMounted>\n              <Tooltip.Positioner sideOffset={7}>\n                <TooltipPopup data-type=\"css-transition-keep-mounted\">Tooltip</TooltipPopup>\n              </Tooltip.Positioner>\n            </Tooltip.Portal>\n          </Tooltip.Root>\n        </Tooltip.Provider>\n      </div>\n      <h3>CSS Transition</h3>\n      <Tooltip.Root>\n        <AnchorButton>Anchor</AnchorButton>\n        <Tooltip.Portal keepMounted>\n          <Tooltip.Positioner sideOffset={7}>\n            <TooltipPopup data-type=\"css-transition-keep-mounted\">Tooltip</TooltipPopup>\n          </Tooltip.Positioner>\n        </Tooltip.Portal>\n      </Tooltip.Root>\n\n      <hr />\n\n      <h2>JavaScript Animation (`framer-motion`)</h2>\n      <FramerMotion />\n    </div>\n  );\n}\n\nfunction FramerMotion() {\n  const [isOpen, setIsOpen] = React.useState(false);\n  return (\n    <Tooltip.Root open={isOpen} onOpenChange={setIsOpen}>\n      <AnchorButton>Anchor</AnchorButton>\n      <AnimatePresence>\n        {isOpen && (\n          <Tooltip.Portal keepMounted>\n            <Tooltip.Positioner sideOffset={7}>\n              <TooltipPopup\n                data-type=\"framer-motion\"\n                render={\n                  <motion.div\n                    animate={{ opacity: 1, scale: 1 }}\n                    initial={{ opacity: 0, scale: 0.8 }}\n                    exit={{ opacity: 0, scale: 0 }}\n                  />\n                }\n              >\n                Tooltip\n              </TooltipPopup>\n            </Tooltip.Positioner>\n          </Tooltip.Portal>\n        )}\n      </AnimatePresence>\n    </Tooltip.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/transition-attrs.module.css",
    "content": ".page {\n  display: grid;\n  gap: 24px;\n  width: 900px;\n  max-width: 100%;\n  margin: 0 auto;\n  padding: 24px;\n}\n\n.section {\n  border: 1px solid #e0e0e0;\n  border-radius: 12px;\n  display: grid;\n  gap: 12px;\n  padding: 16px;\n}\n\n.sectionTitle {\n  font-size: 16px;\n  margin: 0;\n}\n\n.stack {\n  display: grid;\n  gap: 12px;\n}\n\n.row {\n  display: flex;\n  align-items: center;\n  gap: 12px;\n  flex-wrap: wrap;\n}\n\n.button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 2.5rem;\n  padding: 0 0.875rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 500;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover:not([data-disabled]) {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active:not([data-disabled]) {\n    background-color: var(--color-gray-200);\n    box-shadow: inset 0 1px 3px var(--color-gray-200);\n    border-top-color: var(--color-gray-300);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n\n  &[data-disabled] {\n    color: var(--color-gray-500);\n  }\n}\n\n.input {\n  box-sizing: border-box;\n  padding-left: 0.875rem;\n  margin: 0;\n  border: 1px solid var(--color-gray-200);\n  width: 100%;\n  height: 2.5rem;\n  border-radius: 0.375rem;\n  font-family: inherit;\n  font-size: 1rem;\n  background-color: transparent;\n  color: var(--color-gray-900);\n\n  &:focus {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.fade {\n  transition:\n    opacity 140ms ease,\n    transform 140ms ease;\n}\n\n.fade[data-starting-style],\n.fade[data-ending-style] {\n  opacity: 0;\n  transform: translateY(-4px) scale(0.98);\n}\n\n.indicatorFade {\n  transition:\n    opacity 140ms ease,\n    transform 140ms ease;\n  transform-origin: center;\n}\n\n.indicatorFade[data-starting-style],\n.indicatorFade[data-ending-style] {\n  opacity: 0;\n  transform: scale(0);\n}\n\n.fieldRoot {\n  display: flex;\n  flex-direction: column;\n  align-items: start;\n  gap: 0.25rem;\n  width: 100%;\n  max-width: 16rem;\n}\n\n.errorSlot {\n  height: 0;\n  overflow: hidden;\n  transition: height 180ms ease;\n  interpolate-size: allow-keywords;\n}\n\n.errorSlot:has(.error) {\n  height: auto;\n}\n\n.errorSlot:has(.error[data-starting-style]),\n.errorSlot:has(.error[data-ending-style]) {\n  height: 0;\n}\n\n.validitySlot {\n  height: 0;\n  overflow: hidden;\n  transition: height 180ms ease;\n  interpolate-size: allow-keywords;\n}\n\n.validitySlot:has(.validityBadge) {\n  height: auto;\n}\n\n.validitySlot:has(.validityBadge[data-starting-style]),\n.validitySlot:has(.validityBadge[data-ending-style]) {\n  height: 0;\n}\n\n.label {\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  font-weight: 500;\n  color: var(--color-gray-900);\n}\n\n.error {\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  color: var(--color-red-800);\n}\n\n.description {\n  margin: 0;\n  font-size: 0.875rem;\n  line-height: 1.25rem;\n  color: var(--color-gray-600);\n}\n\n.validityBadge {\n  align-items: center;\n  color: var(--color-red-800);\n  display: inline-flex;\n  font-size: 0.875rem;\n  gap: 0.375rem;\n  line-height: 1.25rem;\n}\n\n.validityBadge[data-starting-style],\n.validityBadge[data-ending-style] {\n  opacity: 0;\n  transform: translateY(-2px) scale(0.98);\n}\n\n.validityIcon {\n  background: var(--color-red-800);\n  border-radius: 999px;\n  display: inline-flex;\n  height: 0.4rem;\n  width: 0.4rem;\n}\n\n.avatarRoot {\n  align-items: center;\n  background: #f0f0f0;\n  border-radius: 999px;\n  display: inline-flex;\n  height: 48px;\n  justify-content: center;\n  overflow: hidden;\n  position: relative;\n  width: 48px;\n}\n\n.avatarImage {\n  height: 100%;\n  object-fit: cover;\n  position: absolute;\n  inset: 0;\n  width: 100%;\n  transition:\n    opacity 160ms ease,\n    filter 160ms ease,\n    transform 160ms ease;\n}\n\n.avatarImage[data-starting-style],\n.avatarImage[data-ending-style] {\n  opacity: 0;\n  filter: blur(10px);\n  transform: scale(0.98);\n}\n\n.avatarFallback {\n  color: #444;\n  font-size: 12px;\n  inset: 0;\n  position: absolute;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  transition:\n    opacity 160ms ease,\n    filter 160ms ease;\n}\n\n.avatarFallback[data-starting-style],\n.avatarFallback[data-ending-style] {\n  opacity: 0;\n  filter: blur(10px);\n}\n\n.option {\n  align-items: center;\n  display: inline-flex;\n  gap: 8px;\n}\n\n.radioGroup {\n  display: grid;\n  gap: 8px;\n}\n\n.checkboxBox,\n.radioBox {\n  align-items: center;\n  border: 1px solid #777;\n  display: inline-flex;\n  height: 16px;\n  justify-content: center;\n  width: 16px;\n}\n\n.checkboxBox {\n  border-radius: 4px;\n}\n\n.radioBox {\n  border-radius: 999px;\n}\n\n.checkboxIndicator {\n  background: currentcolor;\n  border-radius: 2px;\n  height: 8px;\n  width: 8px;\n}\n\n.radioIndicator {\n  background: currentcolor;\n  border-radius: 999px;\n  height: 8px;\n  width: 8px;\n}\n"
  },
  {
    "path": "docs/src/app/(private)/experiments/transition-attrs.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Avatar } from '@base-ui/react/avatar';\nimport { Checkbox } from '@base-ui/react/checkbox';\nimport { Field } from '@base-ui/react/field';\nimport { Radio } from '@base-ui/react/radio';\nimport { RadioGroup } from '@base-ui/react/radio-group';\nimport styles from './transition-attrs.module.css';\n\nconst AVATAR_SRC =\n  'https://upload.wikimedia.org/wikipedia/commons/thumb/2/22/Malus_domestica_a1.jpg/500px-Malus_domestica_a1.jpg';\n\nexport default function TransitionAttrsExperiment() {\n  return (\n    <div className={styles.page}>\n      <Section title=\"Field.Error\">\n        <FieldErrorDemo />\n      </Section>\n\n      <Section title=\"Field.Validity\">\n        <FieldValidityDemo />\n      </Section>\n\n      <Section title=\"Avatar.Image / Avatar.Fallback\">\n        <AvatarDemo />\n      </Section>\n\n      <Section title=\"Checkbox.Indicator\">\n        <CheckboxDemo />\n      </Section>\n\n      <Section title=\"Radio.Indicator\">\n        <RadioDemo />\n      </Section>\n    </div>\n  );\n}\n\nfunction Section(props: { title: string; children: React.ReactNode }) {\n  return (\n    <section className={styles.section}>\n      <h2 className={styles.sectionTitle}>{props.title}</h2>\n      {props.children}\n    </section>\n  );\n}\n\nfunction FieldErrorDemo() {\n  const [showError, setShowError] = React.useState(false);\n  const [errorIndex, setErrorIndex] = React.useState(0);\n  const actionsRef = React.useRef<Field.Root.Actions | null>(null);\n  const showErrorRef = React.useRef(showError);\n  const errorIndexRef = React.useRef(errorIndex);\n  const errors = ['Please enter your name', 'Name must be at least 3 characters'];\n\n  function commitState(nextShowError: boolean, nextErrorIndex: number) {\n    showErrorRef.current = nextShowError;\n    errorIndexRef.current = nextErrorIndex;\n    setShowError(nextShowError);\n    setErrorIndex(nextErrorIndex);\n  }\n\n  function runValidation() {\n    actionsRef.current?.validate();\n  }\n\n  function handleToggleError() {\n    const nextShowError = !showErrorRef.current;\n    commitState(nextShowError, errorIndexRef.current);\n    runValidation();\n  }\n\n  function handleNextError() {\n    const nextIndex = (errorIndexRef.current + 1) % errors.length;\n    commitState(true, nextIndex);\n    runValidation();\n  }\n\n  function validateField() {\n    return showErrorRef.current ? errors[errorIndexRef.current] : null;\n  }\n\n  return (\n    <div className={styles.stack}>\n      <div className={styles.row}>\n        <button className={styles.button} onClick={handleToggleError} type=\"button\">\n          {showError ? 'Clear error' : 'Show error'}\n        </button>\n        <button className={styles.button} onClick={handleNextError} type=\"button\">\n          Next error\n        </button>\n      </div>\n      <Field.Root\n        actionsRef={actionsRef}\n        validationMode=\"onChange\"\n        validate={validateField}\n        className={styles.fieldRoot}\n      >\n        <Field.Label className={styles.label}>Name</Field.Label>\n        <Field.Control required className={styles.input} placeholder=\"Required\" />\n        <Field.Description className={styles.description}>\n          Visible on your profile\n        </Field.Description>\n        <div className={styles.errorSlot}>\n          <Field.Error className={`${styles.error} ${styles.fade}`} match=\"customError\" />\n        </div>\n      </Field.Root>\n    </div>\n  );\n}\n\ntype TransitionDataAttributes = {\n  'data-starting-style'?: '';\n  'data-ending-style'?: '';\n};\n\nfunction getTransitionDataAttributes(transitionStatus: 'starting' | 'ending' | 'idle' | undefined) {\n  if (transitionStatus === 'starting') {\n    return { 'data-starting-style': '' } as TransitionDataAttributes;\n  }\n  if (transitionStatus === 'ending') {\n    return { 'data-ending-style': '' } as TransitionDataAttributes;\n  }\n  return {} as TransitionDataAttributes;\n}\n\nfunction FieldValidityDemo() {\n  const [showInvalid, setShowInvalid] = React.useState(false);\n  const [renderIndicator, setRenderIndicator] = React.useState(false);\n  const actionsRef = React.useRef<Field.Root.Actions | null>(null);\n  const showInvalidRef = React.useRef(showInvalid);\n\n  function commitShowInvalid(nextValue: boolean) {\n    showInvalidRef.current = nextValue;\n    setShowInvalid(nextValue);\n  }\n\n  function runValidation() {\n    actionsRef.current?.validate();\n  }\n\n  function handleToggleValidity() {\n    const nextShowInvalid = !showInvalidRef.current;\n    if (nextShowInvalid) {\n      setRenderIndicator(true);\n    }\n    commitShowInvalid(nextShowInvalid);\n    runValidation();\n  }\n\n  function validateField() {\n    return showInvalidRef.current ? 'invalid' : null;\n  }\n\n  function handleIndicatorTransitionEnd(event: React.TransitionEvent<HTMLDivElement>) {\n    if (event.target !== event.currentTarget) {\n      return;\n    }\n    if (!showInvalidRef.current) {\n      setRenderIndicator(false);\n    }\n  }\n\n  return (\n    <div className={styles.stack}>\n      <div className={styles.row}>\n        <button className={styles.button} onClick={handleToggleValidity} type=\"button\">\n          {showInvalid ? 'Set valid' : 'Set invalid'}\n        </button>\n      </div>\n      <Field.Root\n        actionsRef={actionsRef}\n        validationMode=\"onChange\"\n        validate={validateField}\n        className={styles.fieldRoot}\n      >\n        <Field.Label className={styles.label}>Status</Field.Label>\n        <Field.Control className={styles.input} placeholder=\"Toggle validity\" />\n        <div className={styles.validitySlot}>\n          <Field.Validity>\n            {(state) => {\n              if (!renderIndicator) {\n                return null;\n              }\n\n              return (\n                <div\n                  className={`${styles.validityBadge} ${styles.fade}`}\n                  onTransitionEnd={handleIndicatorTransitionEnd}\n                  {...getTransitionDataAttributes(state.transitionStatus)}\n                >\n                  <span className={styles.validityIcon} aria-hidden />\n                  Invalid\n                </div>\n              );\n            }}\n          </Field.Validity>\n        </div>\n      </Field.Root>\n    </div>\n  );\n}\n\nfunction AvatarDemo() {\n  return (\n    <div className={styles.stack}>\n      <Avatar.Root className={styles.avatarRoot}>\n        <Avatar.Image className={styles.avatarImage} src={AVATAR_SRC} />\n        <Avatar.Fallback className={styles.avatarFallback}>AV</Avatar.Fallback>\n      </Avatar.Root>\n    </div>\n  );\n}\n\nfunction CheckboxDemo() {\n  const [checked, setChecked] = React.useState(false);\n\n  function handleCheckedChange(next: boolean) {\n    setChecked(next);\n  }\n\n  return (\n    <label className={styles.option}>\n      <Checkbox.Root\n        checked={checked}\n        onCheckedChange={handleCheckedChange}\n        className={styles.checkboxBox}\n      >\n        <Checkbox.Indicator className={`${styles.checkboxIndicator} ${styles.indicatorFade}`} />\n      </Checkbox.Root>\n      Enable notifications\n    </label>\n  );\n}\n\nfunction RadioDemo() {\n  return (\n    <RadioGroup defaultValue=\"alpha\" className={styles.radioGroup}>\n      <label className={styles.option}>\n        <Radio.Root value=\"alpha\" className={styles.radioBox}>\n          <Radio.Indicator className={`${styles.radioIndicator} ${styles.indicatorFade}`} />\n        </Radio.Root>\n        Alpha\n      </label>\n      <label className={styles.option}>\n        <Radio.Root value=\"beta\" className={styles.radioBox}>\n          <Radio.Indicator className={`${styles.radioIndicator} ${styles.indicatorFade}`} />\n        </Radio.Root>\n        Beta\n      </label>\n    </RadioGroup>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/hydration-repro/page.tsx",
    "content": "'use client';\n\nimport { ScrollArea } from '@base-ui/react/scroll-area';\nimport { Tabs } from '@base-ui/react/tabs';\n\nconst VIEWPORT_SIZE = 200;\nconst SCROLLABLE_CONTENT_SIZE = 1000;\n\nexport default function Page() {\n  return (\n    <div style={{ padding: 24 }}>\n      <Tabs.Root defaultValue=\"scrollable\">\n        <Tabs.List style={{ display: 'flex', gap: 8, marginBottom: 12 }}>\n          <Tabs.Tab value=\"scrollable\">Scrollable</Tabs.Tab>\n          <Tabs.Tab value=\"other\">Other</Tabs.Tab>\n        </Tabs.List>\n\n        <Tabs.Panel value=\"scrollable\">\n          <ScrollArea.Root style={{ width: VIEWPORT_SIZE, height: VIEWPORT_SIZE }}>\n            <ScrollArea.Viewport style={{ width: '100%', height: '100%' }}>\n              <ScrollArea.Content>\n                <div style={{ width: SCROLLABLE_CONTENT_SIZE, height: SCROLLABLE_CONTENT_SIZE }} />\n              </ScrollArea.Content>\n            </ScrollArea.Viewport>\n            <ScrollArea.Scrollbar orientation=\"vertical\" keepMounted>\n              <ScrollArea.Thumb />\n            </ScrollArea.Scrollbar>\n            <ScrollArea.Scrollbar orientation=\"horizontal\" keepMounted>\n              <ScrollArea.Thumb />\n            </ScrollArea.Scrollbar>\n          </ScrollArea.Root>\n        </Tabs.Panel>\n\n        <Tabs.Panel value=\"other\">\n          <div>Other panel</div>\n        </Tabs.Panel>\n      </Tabs.Root>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(private)/layout.css",
    "content": "@layer base {\n  html {\n    color-scheme: light dark;\n    background-color: var(--color-gray-50);\n  }\n\n  body {\n    font-family: system-ui;\n    -webkit-font-smoothing: antialiased;\n    -moz-osx-font-smoothing: grayscale;\n    background-color: var(--color-background);\n    min-height: 100vh;\n  }\n\n  html,\n  body {\n    padding: 0;\n    margin: 0;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(private)/layout.tsx",
    "content": "import * as React from 'react';\nimport { Metadata, Viewport } from 'next';\nimport 'docs/src/css/index.css';\nimport './layout.css';\n\nexport default function Layout({ children }: React.PropsWithChildren) {\n  return (\n    // Use suppressHydrationWarning to avoid https://github.com/facebook/react/issues/24430\n    <html lang=\"en\">\n      <head>\n        <link\n          rel=\"preload\"\n          href=\"/fonts/die-grotesk-a-regular.woff2\"\n          as=\"font\"\n          type=\"font/woff2\"\n          crossOrigin=\"anonymous\"\n        />\n        <link\n          rel=\"preload\"\n          href=\"/fonts/die-grotesk-a-bold.woff2\"\n          as=\"font\"\n          type=\"font/woff2\"\n          crossOrigin=\"anonymous\"\n        />\n        <link\n          rel=\"preload\"\n          href=\"/fonts/die-grotesk-b-bold.woff2\"\n          as=\"font\"\n          type=\"font/woff2\"\n          crossOrigin=\"anonymous\"\n        />\n      </head>\n      <body suppressHydrationWarning>{children}</body>\n    </html>\n  );\n}\n\nexport const viewport: Viewport = {\n  initialScale: 1,\n  width: 'device-width',\n};\n\nexport const metadata: Metadata = {\n  title: {\n    template: '%s · Base UI',\n    default: 'Base UI',\n  },\n  twitter: {\n    site: '@base_ui',\n    card: 'summary_large_image',\n  },\n  openGraph: {\n    type: 'website',\n    locale: 'en_US',\n    title: {\n      template: '%s · Base UI',\n      default: 'Base UI',\n    },\n    ttl: 604800,\n  },\n  icons: {\n    icon: [\n      {\n        rel: 'icon',\n        url:\n          process.env.NODE_ENV !== 'production' ? '/static/favicon-dev.ico' : '/static/favicon.ico',\n        sizes: '32x32',\n      },\n      {\n        rel: 'icon',\n        type: 'image/svg+xml',\n        url:\n          process.env.NODE_ENV !== 'production' ? '/static/favicon-dev.svg' : '/static/favicon.svg',\n      },\n    ],\n    apple: [\n      {\n        rel: 'apple-touch-icon',\n        url: '/static/apple-touch-icon.png',\n      },\n    ],\n  },\n  robots: {\n    index: false,\n    follow: false,\n  },\n};\n"
  },
  {
    "path": "docs/src/app/(private)/playground/[slug]/page.tsx",
    "content": "import { type Metadata } from 'next';\nimport { notFound } from 'next/navigation';\nimport { type Dirent } from 'node:fs';\nimport { basename, extname } from 'node:path';\nimport { readdir } from 'node:fs/promises';\n\ninterface Props {\n  params: Promise<{\n    slug: string;\n  }>;\n}\n\nconst DUMMY_SLUG = '_';\n\nexport default async function Page(props: Props) {\n  const { slug } = await props.params;\n\n  if (slug === DUMMY_SLUG) {\n    notFound();\n  }\n\n  try {\n    const Playground = (await import(`../${slug}.tsx`)).default;\n    return <Playground />;\n  } catch (error) {\n    notFound();\n  }\n}\n\nexport async function generateStaticParams() {\n  const routes = (\n    await readdir('src/app/(private)/playground', { withFileTypes: true })\n  )\n    .filter(\n      (entry: Dirent) =>\n        entry.name.endsWith('.tsx') && entry.name !== 'page.tsx' && entry.isFile(),\n    )\n    .map((entry: Dirent) => ({ slug: basename(entry.name, extname(entry.name)) }));\n\n  if (routes.length === 0) {\n    return [{ slug: DUMMY_SLUG }];\n  }\n\n  return routes;\n}\n\nexport async function generateMetadata(props: Props): Promise<Metadata> {\n  const params = await props.params;\n  const { slug } = params;\n\n  return {\n    title: `${slug} - Playground`,\n  };\n}\n"
  },
  {
    "path": "docs/src/app/(website)/careers/design-engineer/page.tsx",
    "content": "import * as React from 'react';\nimport type { Metadata } from 'next';\nimport { Link } from 'docs/src/components/Link';\n\nexport default function DesignEngineerPage() {\n  return (\n    <React.Fragment>\n      <section className=\"bui-d-c\">\n        <h1 className=\"Text size-3 bp2:size-4 bui-gcs-1 bui-gce-9 bp4:bui-gce-5\">\n          Design Engineer\n        </h1>\n        <div className=\"bui-gcs-1 bui-gce-9\">\n          <Link\n            className=\"Text size-2 Link bui-d-if\"\n            href=\"https://jobs.ashbyhq.com/MUI/353c3d7c-7e58-44f7-83b0-3b87edb6bebd/application\"\n            skipExternalIcon\n          >\n            Apply now\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"20\"\n              height=\"20\"\n              viewBox=\"0 0 16 16\"\n              fill=\"none\"\n              stroke=\"currentColor\"\n              strokeWidth=\"1\"\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n              className=\"Icon\"\n            >\n              <path className=\"LinkArrowCaret\" d=\"M6 12L10 8L6 4\"></path>\n              <path className=\"LinkArrowLine\" d=\"M2 8L13 8\"></path>\n            </svg>\n          </Link>\n        </div>\n      </section>\n\n      <section className=\"bui-d-c\">\n        <div className=\"bui-d-f bui-fd-c bui-g-4 bui-gcs-1 bui-gce-9 bp2:bui-gcs-3 bp4:bui-gce-7\">\n          <p className=\"Text size-2\">\n            Help us make Base UI the most intuitive, accessible, and powerful open-source UI library\n            for React.\n          </p>\n        </div>\n      </section>\n\n      <div className=\"bui-gcs-1 bui-gce-9 bp3:bui-gcs-3\">\n        <div className=\"Separator\" role=\"separator\" aria-hidden=\"true\"></div>\n      </div>\n\n      <section className=\"bui-d-c\">\n        <div className=\"bui-gcs-1 bui-gce-9 bp2:bui-gce-3\">\n          <h2 className=\"Text size-2\">About Base UI</h2>\n        </div>\n        <div className=\"bui-d-f bui-fd-c bui-g-4 bui-gcs-1 bui-gce-9 bp2:bui-gcs-3 bp4:bui-gce-7\">\n          <p className=\"Text size-2\">\n            From the creators of Radix, Material UI, and Floating UI, Base UI is an unstyled React\n            component library for building accessible user interfaces. Our focus is on\n            accessibility, performance, and developer experience. Our goal is to provide a complete\n            set of open-source UI components, with a delightful developer experience, in a\n            sustainable way.\n          </p>\n          <p className=\"Text size-2\">\n            The Base UI team is a small group of engineers, designers, and product people, working\n            to solve incredibly complex and challenging UI problems on the web. In our work, we\n            value craft, flexibility, and accessibility.\n          </p>\n          <p className=\"Text size-2\">\n            We're looking for an experienced Design Engineer to join the team at Staff–Senior Staff\n            level. You will help us improve the library across the board, including API design,\n            performance, a11y, testing workflows, docs, support, and implementing components.\n          </p>\n          <p className=\"Text size-2\">\n            We're looking for someone who enjoys wearing many hats. Think less about spending many\n            months implementing a single component, and more about leading developer experience, API\n            design, a11y, docs, and maintaining overall product quality.\n          </p>\n          <p className=\"Text size-2\">\n            You will have a lot of autonomy to push for improvements. We are looking for someone who\n            is passionate about UI design, with an exceptional eye for detail, and great taste in\n            both visual design and API design.\n          </p>\n        </div>\n      </section>\n\n      <div className=\"bui-gcs-1 bui-gce-9 bp3:bui-gcs-3\">\n        <div className=\"Separator\" role=\"separator\" aria-hidden=\"true\"></div>\n      </div>\n\n      <section className=\"bui-d-c\">\n        <div className=\"bui-gcs-1 bui-gce-9 bp2:bui-gce-3\">\n          <h2 className=\"Text size-2\">Responsibilities</h2>\n        </div>\n        <div className=\"bui-d-f bui-fd-c bui-g-4 bui-gcs-1 bui-gce-9 bp2:bui-gcs-3 bp4:bui-gce-7\">\n          <ul className=\"BulletList Text size-2\">\n            <li>Report to our OSS Engineering Manager.</li>\n            <li>\n              Contribute to the component design process through a11y research, user research,\n              writing design specs, collaborating with engineers, and exploring UI design patterns\n              on both web and mobile.\n            </li>\n            <li>\n              Contribute to the API design process at both the component level and the library\n              level. Work to ensure APIs are intuitive, configurable, and consistent.\n            </li>\n            <li>\n              Lead the docs design and user experience. Design and implement new features, new\n              content, a11y enhancements, and visual language enhancements.\n            </li>\n            <li>Build and maintain the Base UI website.</li>\n            <li>\n              Build out our test environment by composing and styling component examples, then\n              rigorously testing them in many different environments including desktop, mobile, and\n              screen readers.\n            </li>\n            <li>\n              Create a healthy feedback loop with the engineering team, pushing for iterative\n              improvements to accessibility, usability, and performance.\n            </li>\n            <li>\n              Champion Base UI both internally and externally, contributing to marketing efforts\n              through social media, blogs, conference talks, podcasts, and other communication\n              channels.\n            </li>\n            <li>Help out with developer support on both Github and Discord.</li>\n          </ul>\n        </div>\n      </section>\n\n      <section className=\"bui-d-c\">\n        <div className=\"bui-gcs-1 bui-gce-9 bp2:bui-gce-3\">\n          <h2 className=\"Text size-2\">Requirements</h2>\n        </div>\n        <div className=\"bui-d-f bui-fd-c bui-g-4 bui-gcs-1 bui-gce-9 bp2:bui-gcs-3 bp4:bui-gce-7\">\n          <ul className=\"BulletList Text size-2\">\n            <li>We are targeting 7+ years of web development experience.</li>\n            <li>\n              Working knowledge of React, TypeScript, Next.js, MDX, Git, Figma, and other popular\n              tools in the React, JavaScript, and design ecosystems.\n            </li>\n            <li>\n              Expert knowledge of CSS, including familiarity with common CSS tooling, and knowledge\n              of bleeding-edge CSS features.\n            </li>\n            <li>\n              Deep familiarity with the headless UI ecosystem. Strong opinions on the pros and cons\n              of API design choices across headless UI libraries.\n            </li>\n            <li>\n              Expert knowledge of a11y, including deep familiarity with ARIA guidelines, WCAG\n              success criterion, and screen reader technologies.\n            </li>\n            <li>Advanced understanding of design principles.</li>\n            <li>A passion for craft, a keen eye for detail, and exquisite taste.</li>\n            <li>Excellent written and verbal communication skills.</li>\n            <li>Experience working remotely and communicating asynchronously.</li>\n          </ul>\n        </div>\n      </section>\n\n      <section className=\"bui-d-c\">\n        <div className=\"bui-gcs-1 bui-gce-9 bp2:bui-gce-3\">\n          <h2 className=\"Text size-2\">Benefits</h2>\n        </div>\n        <div className=\"bui-d-f bui-fd-c bui-g-4 bui-gcs-1 bui-gce-9 bp2:bui-gcs-3 bp4:bui-gce-7\">\n          <ul className=\"BulletList Text size-2\">\n            <li>$200k–$290k USD base salary.</li>\n            <li>Subsidized healthcare package (dependent on employment path and location).</li>\n            <li>Flexible time-off. We provide 33 days of paid time-off globally.</li>\n            <li>100% remote. Our entire company is globally distributed.</li>\n            <li>Company retreats. We meet up every 8 months for a week of work and fun.</li>\n            <li>Office equipment. We let you choose the hardware of your choice.</li>\n            <li>\n              20% development time. Allocate 20% of your time towards personal and professional\n              development.\n            </li>\n            <li>\n              Education budget. We provide mentorship and send you to events that help you build\n              your network and skills.\n            </li>\n          </ul>\n\n          <p className=\"Text size-2\">\n            The actual salary will be determined by skill-level, experience, and location. More\n            information about the specific compensation package will be shared during the hiring\n            process.\n          </p>\n        </div>\n      </section>\n\n      <section className=\"bui-d-c\">\n        <div className=\"bui-gcs-1 bui-gce-9 bp2:bui-gce-3\">\n          <h2 className=\"Text size-2\">Application</h2>\n        </div>\n        <div className=\"bui-d-f bui-fd-c bui-g-4 bui-gcs-1 bui-gce-9 bp2:bui-gcs-3 bp4:bui-gce-7\">\n          <p className=\"Text size-2\">The hiring process will consist of 6 stages:</p>\n          <ul className=\"BulletList Text size-2\">\n            <li>Resume review.</li>\n            <li>45 minute Phone screen.</li>\n            <li>React challenge (asynchronous).</li>\n            <li>60 minute meeting with Product lead.</li>\n            <li>90 minute meeting with the Base UI team.</li>\n            <li>60 minute culture-fit interview.</li>\n          </ul>\n          <p className=\"Text size-2\">\n            <Link\n              className=\"Text size-2 Link bui-d-if\"\n              href=\"https://jobs.ashbyhq.com/MUI/353c3d7c-7e58-44f7-83b0-3b87edb6bebd/application\"\n              skipExternalIcon\n            >\n              Apply now\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                width=\"20\"\n                height=\"20\"\n                viewBox=\"0 0 16 16\"\n                fill=\"none\"\n                stroke=\"currentColor\"\n                strokeWidth=\"1\"\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n                className=\"Icon\"\n              >\n                <path className=\"LinkArrowCaret\" d=\"M6 12L10 8L6 4\"></path>\n                <path className=\"LinkArrowLine\" d=\"M2 8L13 8\"></path>\n              </svg>\n            </Link>\n          </p>\n        </div>\n      </section>\n    </React.Fragment>\n  );\n}\n\nconst description = 'Unstyled UI components for building accessible web apps and design systems.';\n\nexport const metadata: Metadata = {\n  description,\n  twitter: {\n    description,\n  },\n  openGraph: {\n    description,\n  },\n};\n"
  },
  {
    "path": "docs/src/app/(website)/css/components/Accordion.css",
    "content": "@layer components {\n  .AccordionRoot {\n    border-top: 1px solid var(--border);\n  }\n\n  .AccordionItem {\n    box-sizing: border-box;\n    border-bottom: 1px solid var(--border);\n  }\n\n  .AccordionHeader {\n    margin: 0;\n  }\n\n  .AccordionTrigger {\n    box-sizing: border-box;\n    position: relative;\n    display: flex;\n    width: 100%;\n    gap: 1rem;\n    align-items: center;\n    justify-content: space-between;\n    color: inherit;\n    font-family: inherit;\n    background-color: transparent;\n    font-size: 1rem;\n    padding-block: 0.75rem;\n    padding-inline: 0;\n    line-height: 1.5rem;\n    border: none;\n    outline: none;\n    text-align: left;\n    -webkit-tap-highlight-color: transparent;\n  }\n\n  .AccordionTrigger:focus-visible {\n    outline: 2px solid currentColor;\n  }\n\n  .AccordionIconMinus {\n    display: none;\n  }\n\n  .AccordionIconPlus {\n    display: block;\n  }\n\n  .AccordionTrigger[data-panel-open] .AccordionIconPlus {\n    display: none;\n  }\n\n  .AccordionTrigger[data-panel-open] .AccordionIconMinus {\n    display: block;\n  }\n\n  .AccordionPanel {\n    padding-block-start: 4px;\n    padding-block-end: 12px;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(website)/css/components/Body.css",
    "content": "@layer components {\n  .Body {\n    box-sizing: border-box;\n    margin: 0;\n    color: var(--gray-t2);\n    background-color: var(--gray-s1);\n  }\n\n  .Body::selection {\n    background-color: var(--selection);\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(website)/css/components/BulletList.css",
    "content": "@layer components {\n  .BulletList {\n    list-style-type: disc;\n    padding-left: 1em;\n    margin: 1em 0;\n\n    & > li {\n      margin: 0.5em 0;\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(website)/css/components/Button.css",
    "content": "@layer components {\n  .Button {\n    position: relative;\n    box-sizing: border-box;\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;\n    height: 32px;\n    gap: 4px;\n    border-radius: 8px;\n    font-size: 14px;\n    line-height: 1;\n    vertical-align: middle;\n    font-family: 'Die Grotesk A', sans-serif;\n    padding-left: 12px;\n    padding-right: 12px;\n    z-index: 0;\n    background: linear-gradient(to bottom, hsl(0deg 0% 0% / 5%), hsl(0deg 0% 0% / 12%));\n  }\n\n  .Button::before,\n  .Button::after {\n    content: '';\n    position: absolute;\n    inset: 1px;\n  }\n\n  .Button::before {\n    border-radius: 7px;\n    background: linear-gradient(to bottom, white, hsl(0deg 0% 98%));\n    box-shadow: inset 0 0 0 1px white;\n    z-index: -1;\n  }\n\n  .Button::after {\n    border-radius: 8px;\n    z-index: -2;\n    box-shadow:\n      0 0.5px 1px hsl(0deg 0% 0% / 12%),\n      0 1px 3px -1px hsl(0deg 0% 0% / 5%),\n      0 2px 4px -1px hsl(0deg 0% 0% / 5%);\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(website)/css/components/Figure.css",
    "content": "@layer components {\n  .Figure {\n    box-sizing: border-box;\n    margin: 0;\n    background-color: var(--gray-s2);\n    aspect-ratio: 1 / 1;\n    overflow: hidden;\n    user-select: none;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(website)/css/components/Icon.css",
    "content": "@layer components {\n  .Icon {\n    box-sizing: border-box;\n    display: block;\n    flex-shrink: 0;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(website)/css/components/Key.css",
    "content": "@layer components {\n  .Key {\n    position: relative;\n    box-sizing: border-box;\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;\n    width: 48px;\n    height: 24px;\n    border-radius: 8px;\n    line-height: 1;\n    vertical-align: middle;\n    z-index: 0;\n    background: linear-gradient(to bottom, hsl(0deg 0% 0% / 8%), hsl(0deg 0% 0% / 16%));\n    animation-duration: var(--keyboardMenuDuration);\n    animation-timing-function: linear;\n    animation-iteration-count: infinite;\n    transform: translateY(0);\n    --key-shadow-opacity: 1;\n  }\n\n  .Key::before,\n  .Key::after {\n    content: '';\n    position: absolute;\n    inset: 1px;\n  }\n\n  .Key::before {\n    border-radius: 7px;\n    background: linear-gradient(to bottom, white, hsl(0deg 0% 97%));\n    opacity: var(--key-shadow-opacity);\n    box-shadow: inset 0 0 0 1px white;\n    z-index: -1;\n  }\n\n  .Key::after {\n    border-radius: 8px;\n    z-index: -2;\n    opacity: var(--key-shadow-opacity);\n    box-shadow:\n      0 1px 0.5px hsl(0deg 0% 0% / 12%),\n      0 1px 3px -1px hsl(0deg 0% 0% / 5%),\n      0 2px 4px -1px hsl(0deg 0% 0% / 5%),\n      0 3px 6px -2px hsl(0deg 0% 0% / 5%);\n  }\n\n  .Key--down {\n    animation-name: keyboardMenuDownKey;\n  }\n\n  .Key--up {\n    animation-name: keyboardMenuUpKey;\n  }\n\n  @keyframes keyboardMenuDownKey {\n    0%,\n    4.9999% {\n      transform: translateY(0);\n      --key-shadow-opacity: 1;\n    }\n\n    5%,\n    5.9999% {\n      transform: translateY(1px);\n      --key-shadow-opacity: 0.8;\n    }\n\n    6%,\n    9.9999% {\n      transform: translateY(0);\n      --key-shadow-opacity: 1;\n    }\n\n    10%,\n    10.9999% {\n      transform: translateY(1px);\n      --key-shadow-opacity: 0.8;\n    }\n\n    11%,\n    14.9999% {\n      transform: translateY(0);\n      --key-shadow-opacity: 1;\n    }\n\n    15%,\n    15.9999% {\n      transform: translateY(1px);\n      --key-shadow-opacity: 0.8;\n    }\n\n    16%,\n    99.9999% {\n      transform: translateY(0);\n      --key-shadow-opacity: 1;\n    }\n  }\n\n  @keyframes keyboardMenuUpKey {\n    0%,\n    39.9999% {\n      transform: translateY(0);\n      --key-shadow-opacity: 1;\n    }\n\n    40%,\n    40.9999% {\n      transform: translateY(1px);\n      --key-shadow-opacity: 0.8;\n    }\n\n    41%,\n    44.9999% {\n      transform: translateY(0);\n      --key-shadow-opacity: 1;\n    }\n\n    45%,\n    45.9999% {\n      transform: translateY(1px);\n      --key-shadow-opacity: 0.8;\n    }\n\n    46%,\n    49.9999% {\n      transform: translateY(0);\n      --key-shadow-opacity: 1;\n    }\n\n    50%,\n    50.9999% {\n      transform: translateY(1px);\n      --key-shadow-opacity: 0.8;\n    }\n\n    51%,\n    99.9999% {\n      transform: translateY(0);\n      --key-shadow-opacity: 1;\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(website)/css/components/Link.css",
    "content": "@layer components {\n  .Link {\n    display: inline;\n    text-decoration-thickness: 1px;\n    text-underline-offset: 4px;\n    width: fit-content;\n    color: currentColor;\n    align-items: center;\n    gap: 4px;\n    -webkit-tap-highlight-color: transparent;\n  }\n\n  .Link:active {\n    background-color: var(--gray-t2);\n    color: var(--gray-s1);\n  }\n\n  .Link:focus-visible {\n    outline: 2px solid currentColor;\n    outline-offset: 2px;\n  }\n\n  .LinkArrowLine {\n    opacity: 0;\n    transition: opacity 100ms ease;\n  }\n\n  .LinkArrowCaret {\n    transition: transform 100ms ease;\n  }\n\n  @media (hover: hover) {\n    .Link:hover {\n      text-decoration: none;\n    }\n\n    .Link:hover .LinkArrowLine {\n      opacity: 1;\n    }\n\n    .Link:hover .LinkArrowCaret {\n      transform: translateX(4px);\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(website)/css/components/List.css",
    "content": "@layer components {\n  .List {\n    box-sizing: border-box;\n    list-style: none;\n    padding: 0;\n    margin: 0;\n  }\n\n  .ListItem {\n    border-bottom-width: 1px;\n    border-bottom-style: solid;\n    border-bottom-color: var(--border);\n    padding-block-start: 12px;\n    padding-block-end: 12px;\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(website)/css/components/Menu.css",
    "content": "@layer components {\n  .Menu {\n    position: relative;\n    --keyboardMenuDuration: 5s;\n    --keyboardMenuItemBg: #fff;\n    --keyboardMenuItemHighlight: hsl(0deg 0% 95%);\n  }\n\n  .MenuItem {\n    box-sizing: border-box;\n    display: flex;\n    align-items: center;\n    gap: 12px;\n    padding: 0 12px;\n    height: 32px;\n    background-color: transparent;\n    border-radius: 4px;\n    font-size: 14px;\n    line-height: 1;\n    font-family: 'Die Grotesk A', sans-serif;\n    position: relative;\n    z-index: 1;\n    white-space: nowrap;\n  }\n\n  .MenuItemHighlight {\n    position: absolute;\n    left: 4px;\n    right: 4px;\n    height: 32px;\n    background-color: var(--keyboardMenuItemHighlight);\n    border-radius: 4px;\n    pointer-events: none;\n    animation: menuItemHighlighted var(--keyboardMenuDuration) steps(8, end) infinite;\n    transform: translateY(0);\n  }\n\n  @keyframes menuItemHighlighted {\n    0%,\n    4.9999% {\n      transform: translateY(0);\n    }\n\n    5%,\n    9.9999% {\n      transform: translateY(32px);\n    }\n\n    10%,\n    14.9999% {\n      transform: translateY(64px);\n    }\n\n    15%,\n    39.9999% {\n      transform: translateY(96px);\n    }\n\n    40%,\n    44.9999% {\n      transform: translateY(64px);\n    }\n\n    45%,\n    49.9999% {\n      transform: translateY(32px);\n    }\n\n    50%,\n    99.9999% {\n      transform: translateY(0);\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(website)/css/components/Popup.css",
    "content": "@layer components {\n  .Popup {\n    position: relative;\n    box-sizing: border-box;\n    border-radius: 8px;\n    z-index: 0;\n    background: linear-gradient(to bottom, hsl(0deg 0% 0% / 5%), hsl(0deg 0% 0% / 12%));\n  }\n\n  .Popup::before,\n  .Popup::after {\n    content: '';\n    position: absolute;\n    inset: 1px;\n  }\n\n  .Popup::before {\n    border-radius: 7px;\n    background: linear-gradient(to bottom, white, hsl(0deg 0% 98%));\n    box-shadow: inset 0 0 0 1px white;\n    z-index: -1;\n  }\n\n  .Popup::after {\n    border-radius: 8px;\n    z-index: -2;\n    box-shadow:\n      0 0.5px 1px hsl(0deg 0% 0% / 12%),\n      0 1px 3px hsl(0deg 0% 0% / 5%),\n      0 2px 4px -1px hsl(0deg 0% 0% / 5%),\n      0 4px 12px -2px hsl(0deg 0% 0% / 5%),\n      0 8px 24px -4px hsl(0deg 0% 0% / 5%),\n      0 16px 48px -4px hsl(0deg 0% 0% / 5%);\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(website)/css/components/Separator.css",
    "content": "@layer components {\n  .Separator {\n    box-sizing: border-box;\n    height: 1px;\n    width: 32px;\n    background-color: var(--gray-s2);\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(website)/css/components/Text.css",
    "content": "@layer components {\n  .Text {\n    box-sizing: border-box;\n    font-family:\n      'die grotesk a',\n      system-ui,\n      -apple-system,\n      BlinkMacSystemFont,\n      'Helvetica Neue',\n      arial,\n      sans-serif;\n    margin: 0;\n    font-weight: 400;\n    -webkit-font-smoothing: antialiased;\n    -moz-osx-font-smoothing: grayscale;\n    text-rendering: optimizeLegibility;\n    text-wrap: pretty;\n  }\n\n  .Text.size-1 {\n    font-size: 15px;\n    line-height: 22px;\n  }\n\n  .Text.size-2 {\n    font-size: 18px;\n    line-height: 25px;\n  }\n\n  .Text.size-3 {\n    font-size: 36px;\n    line-height: 38px;\n    text-wrap: balance;\n    font-family:\n      'die grotesk b',\n      system-ui,\n      -apple-system,\n      BlinkMacSystemFont,\n      'Helvetica Neue',\n      arial,\n      sans-serif;\n  }\n\n  .Text.size-4 {\n    font-size: 42px;\n    line-height: 44px;\n    text-wrap: balance;\n    font-family:\n      'die grotesk b',\n      system-ui,\n      -apple-system,\n      BlinkMacSystemFont,\n      'Helvetica Neue',\n      arial,\n      sans-serif;\n  }\n\n  @media (min-width: 48rem) {\n    .Text.bp2\\:size-1 {\n      font-size: 15px;\n      line-height: 22px;\n    }\n\n    .Text.bp2\\:size-2 {\n      font-size: 18px;\n      line-height: 25px;\n    }\n\n    .Text.bp2\\:size-3 {\n      font-size: 36px;\n      line-height: 38px;\n      text-wrap: balance;\n      font-family:\n        'die grotesk b',\n        system-ui,\n        -apple-system,\n        BlinkMacSystemFont,\n        'Helvetica Neue',\n        arial,\n        sans-serif;\n    }\n\n    .Text.bp2\\:size-4 {\n      font-size: 42px;\n      line-height: 44px;\n      text-wrap: balance;\n      font-family:\n        'die grotesk b',\n        system-ui,\n        -apple-system,\n        BlinkMacSystemFont,\n        'Helvetica Neue',\n        arial,\n        sans-serif;\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(website)/css/index.css",
    "content": "@layer theme;\n@layer components;\n@layer utilities;\n\n/* Theme */\n@import './theme.css';\n\n/* Fonts */\n@import 'docs/src/css/fonts/index.css';\n\n/* Components */\n@import './components/Accordion.css';\n@import './components/Body.css';\n@import './components/BulletList.css';\n@import './components/Button.css';\n@import './components/Figure.css';\n@import './components/Icon.css';\n@import './components/Key.css';\n@import './components/Link.css';\n@import './components/List.css';\n@import './components/Menu.css';\n@import './components/Popup.css';\n@import './components/Separator.css';\n@import './components/Text.css';\n\n/* Utilities */\n@import 'docs/src/css/utilities/index.css';\n"
  },
  {
    "path": "docs/src/app/(website)/css/theme.css",
    "content": "@layer theme {\n  :root {\n    --gray-s1: white;\n    --gray-s2: hsl(0deg 0% 97%);\n    --gray-t2: hsl(0deg 0% 18%);\n    --border: hsl(0deg 0% 18%);\n    --selection: hsl(0deg 0% 92%);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    :root {\n      --gray-s1: hsl(0deg 0% 6%);\n      --gray-s2: hsl(0deg 0% 12%);\n      --gray-t2: hsl(0deg 0% 93%);\n      --border: hsl(0deg 0% 20%);\n      --selection: hsl(0deg 0% 18%);\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/app/(website)/icons/MinusIcon.tsx",
    "content": "import * as React from 'react';\n\nexport function MinusIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"20\"\n      height=\"20\"\n      viewBox=\"0 0 15 15\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      className=\"Icon\"\n      {...props}\n    >\n      <path\n        d=\"M2.25 7.5C2.25 7.22386 2.47386 7 2.75 7H12.25C12.5261 7 12.75 7.22386 12.75 7.5C12.75 7.77614 12.5261 8 12.25 8H2.75C2.47386 8 2.25 7.77614 2.25 7.5Z\"\n        fill=\"currentColor\"\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(website)/icons/PlusIcon.tsx",
    "content": "import * as React from 'react';\n\nexport function PlusIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"20\"\n      height=\"20\"\n      viewBox=\"0 0 15 15\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      className=\"Icon\"\n      {...props}\n    >\n      <path\n        d=\"M8 2.75C8 2.47386 7.77614 2.25 7.5 2.25C7.22386 2.25 7 2.47386 7 2.75V7H2.75C2.47386 7 2.25 7.22386 2.25 7.5C2.25 7.77614 2.47386 8 2.75 8H7V12.25C7 12.5261 7.22386 12.75 7.5 12.75C7.77614 12.75 8 12.5261 8 12.25V8H12.25C12.5261 8 12.75 7.77614 12.75 7.5C12.75 7.22386 12.5261 7 12.25 7H8V2.75Z\"\n        fill=\"currentColor\"\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(website)/layout.tsx",
    "content": "import * as React from 'react';\nimport type { Metadata, Viewport } from 'next/types';\nimport { GoogleAnalytics } from 'docs/src/components/GoogleAnalytics';\nimport { Link } from 'docs/src/components/Link';\nimport { Logo } from 'docs/src/components/Logo';\nimport './css/index.css';\n\nexport default function Layout({ children }: React.PropsWithChildren) {\n  return (\n    // Use suppressHydrationWarning to avoid https://github.com/facebook/react/issues/24430\n    <html lang=\"en\">\n      <head>\n        <link\n          rel=\"preload\"\n          href=\"/fonts/die-grotesk-a-regular.woff2\"\n          as=\"font\"\n          type=\"font/woff2\"\n          crossOrigin=\"anonymous\"\n        />\n        <link\n          rel=\"preload\"\n          href=\"/fonts/die-grotesk-b-regular.woff2\"\n          as=\"font\"\n          type=\"font/woff2\"\n          crossOrigin=\"anonymous\"\n        />\n      </head>\n      <body suppressHydrationWarning className=\"Body bui-p-6 bp2:bui-py-7 bp2:bui-px-9\">\n        <GoogleAnalytics>\n          <div\n            className=\"bui-bs-bb bui-d-g bui-gtc-8 bui-g-8 bp2:bui-g-9\"\n            style={{ maxWidth: '1480px', marginInline: 'auto' }}\n          >\n            <header className=\"bui-d-c\">\n              <div className=\"bui-gcs-1 bui-gce-4\">\n                <Logo aria-label=\"Base UI\" />\n              </div>\n              <nav\n                className=\"bui-d-f bui-fd-c bui-g-2 bui-gcs-5 bui-gce-8 bp2:bui-gcs-5 bp2:bui-gce-9 bp3:bui-gcs-5 bp3:bui-gce-7\"\n                aria-label=\"social links\"\n              >\n                <a className=\"Text size-1 Link\" href=\"https://x.com/base_ui\">\n                  X\n                </a>\n                <a className=\"Text size-1 Link\" href=\"https://github.com/mui/base-ui\">\n                  GitHub\n                </a>\n                <a className=\"Text size-1 Link\" href=\"https://discord.com/invite/g6C3hUtuxz\">\n                  Discord\n                </a>\n              </nav>\n              <div className=\"bui-d-n bp3:bui-d-b bui-gcs-7 bui-gce-9\">\n                <Link className=\"Text size-1 Link\" href=\"/react/components/accordion\">\n                  Components\n                </Link>\n              </div>\n            </header>\n            <main id=\"main\" className=\"bui-d-c\">\n              {children}\n            </main>\n            <div className=\"bui-gcs-1 bui-gce-9 bp3:bui-gcs-3\">\n              <div className=\"Separator\" role=\"separator\" aria-hidden=\"true\"></div>\n            </div>\n            <footer className=\"bui-d-c\">\n              <div className=\"bui-gcs-1 bui-gce-9 bp2:bui-gce-3\">\n                <span className=\"Text size-1\">© Base UI</span>\n              </div>\n              <nav\n                className=\"bui-d-f bui-fd-c bui-g-2 bui-gcs-1 bui-gce-9 bp2:bui-gcs-3 bp4:bui-gce-7\"\n                aria-label=\"social links\"\n              >\n                <a className=\"Text size-1 Link\" href=\"https://x.com/base_ui\">\n                  X\n                </a>\n                <a className=\"Text size-1 Link\" href=\"https://github.com/mui/base-ui\">\n                  GitHub\n                </a>\n                <a className=\"Text size-1 Link\" href=\"https://discord.com/invite/g6C3hUtuxz\">\n                  Discord\n                </a>\n                <a className=\"Text size-1 Link\" href=\"https://www.npmjs.com/package/@base-ui/react\">\n                  npm\n                </a>\n                <a\n                  className=\"Text size-1 Link\"\n                  href=\"https://bsky.app/profile/did:plc:nwr6peuxqzdzlbi72qr5kldc\"\n                >\n                  Bluesky\n                </a>\n              </nav>\n            </footer>\n          </div>\n        </GoogleAnalytics>\n      </body>\n    </html>\n  );\n}\n\nexport const metadata: Metadata = {\n  title: {\n    template: '%s · Base UI',\n    default: 'Base UI',\n  },\n  twitter: {\n    site: '@base_ui',\n    card: 'summary_large_image',\n  },\n  openGraph: {\n    type: 'website',\n    locale: 'en_US',\n    title: {\n      template: '%s · Base UI',\n      default: 'Base UI',\n    },\n    ttl: 604800,\n  },\n  metadataBase: new URL('https://base-ui.com'),\n  alternates: {\n    canonical: './',\n  },\n  icons: {\n    icon: [\n      {\n        rel: 'icon',\n        url:\n          process.env.NODE_ENV !== 'production' ? '/static/favicon-dev.ico' : '/static/favicon.ico',\n        sizes: '32x32',\n      },\n      {\n        rel: 'icon',\n        type: 'image/svg+xml',\n        url:\n          process.env.NODE_ENV !== 'production' ? '/static/favicon-dev.svg' : '/static/favicon.svg',\n      },\n    ],\n    apple: [\n      {\n        rel: 'apple-touch-icon',\n        url: '/static/apple-touch-icon.png',\n      },\n    ],\n  },\n};\n\nexport const viewport: Viewport = {\n  initialScale: 1,\n  width: 'device-width',\n  themeColor: [\n    // Desktop Safari page background\n    {\n      media: '(prefers-color-scheme: light) and (min-width: 1024px)',\n      color: 'oklch(95% 0.25% 264)',\n    },\n    {\n      media: '(prefers-color-scheme: dark) and (min-width: 1024px)',\n      color: 'oklch(25% 1% 264)',\n    },\n    // Mobile Safari header background (match the page content)\n    {\n      media: '(prefers-color-scheme: light)',\n      color: '#FFF',\n    },\n    {\n      media: '(prefers-color-scheme: dark)',\n      color: 'hsl(0deg 0% 6%)',\n    },\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/(website)/logos/GitHub.tsx",
    "content": "import * as React from 'react';\n\nexport function GitHub(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      height=\"32\"\n      width=\"32\"\n      viewBox=\"0 0 24 24\"\n      fill=\"currentColor\"\n      {...props}\n    >\n      <path d=\"M12 1C5.923 1 1 5.923 1 12c0 4.867 3.149 8.979 7.521 10.436.55.096.756-.233.756-.522 0-.262-.013-1.128-.013-2.049-2.764.509-3.479-.674-3.699-1.292-.124-.317-.66-1.293-1.127-1.554-.385-.207-.936-.715-.014-.729.866-.014 1.485.797 1.691 1.128.99 1.663 2.571 1.196 3.204.907.096-.715.385-1.196.701-1.471-2.448-.275-5.005-1.224-5.005-5.432 0-1.196.426-2.186 1.128-2.956-.111-.275-.496-1.402.11-2.915 0 0 .921-.288 3.024 1.128a10.193 10.193 0 0 1 2.75-.371c.936 0 1.871.123 2.75.371 2.104-1.43 3.025-1.128 3.025-1.128.605 1.513.221 2.64.111 2.915.701.77 1.127 1.747 1.127 2.956 0 4.222-2.571 5.157-5.019 5.432.399.344.743 1.004.743 2.035 0 1.471-.014 2.654-.014 3.025 0 .289.206.632.756.522C19.851 20.979 23 16.854 23 12c0-6.077-4.922-11-11-11Z\"></path>\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(website)/logos/HighlightAI.tsx",
    "content": "import * as React from 'react';\n\nexport function HighlightAI(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"32\"\n      height=\"32\"\n      viewBox=\"0 0 32 32\"\n      fill=\"none\"\n      {...props}\n    >\n      <path\n        d=\"M0.799805 6.4002C0.799805 4.63288 2.21757 3.2002 3.96647 3.2002C5.71537 3.2002 7.13314 4.63288 7.13314 6.4002V25.6002C7.13314 27.3675 5.71537 28.8002 3.96647 28.8002C2.21757 28.8002 0.799805 27.3675 0.799805 25.6002V6.4002Z\"\n        fill=\"currentcolor\"\n      />\n      <path\n        d=\"M24.8667 6.4002C24.8667 4.63288 26.2845 3.2002 28.0334 3.2002C29.7823 3.2002 31.2001 4.63288 31.2001 6.4002V25.6002C31.2001 27.3675 29.7823 28.8002 28.0334 28.8002C26.2845 28.8002 24.8667 27.3675 24.8667 25.6002V6.4002Z\"\n        fill=\"currentcolor\"\n      />\n      <path\n        d=\"M23.5999 15.9998C23.5999 20.2413 20.1973 23.6798 15.9999 23.6798C11.8025 23.6798 8.3999 20.2413 8.3999 15.9998C8.3999 11.7583 11.8025 8.31982 15.9999 8.31982C20.1973 8.31982 23.5999 11.7583 23.5999 15.9998Z\"\n        fill=\"currentcolor\"\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(website)/logos/Interfere.tsx",
    "content": "import * as React from 'react';\n\nexport function Interfere(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"43\"\n      height=\"32\"\n      viewBox=\"0 0 43 32\"\n      fill=\"none\"\n      {...props}\n    >\n      <path d=\"M0 15.2002H1.6V16.8002H0V15.2002Z\" fill=\"currentColor\" />\n      <path d=\"M5.6001 8H8.8001V11.2H5.6001V8Z\" fill=\"currentColor\" />\n      <path d=\"M5.6001 20.7998H8.8001V23.9998H5.6001V20.7998Z\" fill=\"currentColor\" />\n      <path d=\"M10.9331 0.533203H16.2664V5.86654H10.9331V0.533203Z\" fill=\"currentColor\" />\n      <path d=\"M12 14.3999H15.2V17.5999H12V14.3999Z\" fill=\"currentColor\" />\n      <path d=\"M10.9331 26.1333H16.2664V31.4666H10.9331V26.1333Z\" fill=\"currentColor\" />\n      <path d=\"M17.3335 6.93311H22.6668V12.2664H17.3335V6.93311Z\" fill=\"currentColor\" />\n      <path d=\"M17.3335 19.7334H22.6668V25.0667H17.3335V19.7334Z\" fill=\"currentColor\" />\n      <path d=\"M23.2002 0H29.6002V6.4H23.2002V0Z\" fill=\"currentColor\" />\n      <path d=\"M23.7334 13.3335H29.0667V18.6668H23.7334V13.3335Z\" fill=\"currentColor\" />\n      <path d=\"M23.2002 25.6001H29.6002V32.0001H23.2002V25.6001Z\" fill=\"currentColor\" />\n      <path d=\"M29.6001 6.3999H36.0001V12.7999H29.6001V6.3999Z\" fill=\"currentColor\" />\n      <path d=\"M29.6001 19.2002H36.0001V25.6002H29.6001V19.2002Z\" fill=\"currentColor\" />\n      <path d=\"M36 12.7998H42.4V19.1998H36V12.7998Z\" fill=\"currentColor\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(website)/logos/Operate.tsx",
    "content": "import * as React from 'react';\n\nexport function Operate(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"61\"\n      height=\"32\"\n      viewBox=\"0 0 61 32\"\n      fill=\"none\"\n      {...props}\n    >\n      <path\n        d=\"M54.6779 0.924763C50.2554 0.377394 44.2469 4.0675 39.7431 9.68266C39.6554 9.79098 39.5381 9.87186 39.4054 9.91558C39.2726 9.95931 39.1299 9.96404 38.9945 9.92919C38.8591 9.89434 38.7367 9.8214 38.642 9.71912C38.5472 9.61684 38.4842 9.48956 38.4604 9.35255C38.069 7.01992 36.6897 5.44855 34.3903 5.1656C30.8625 4.72771 26.1587 7.43929 22.4106 11.6953C22.3202 11.7972 22.2028 11.8717 22.0717 11.9102C21.9407 11.9487 21.8013 11.9498 21.6697 11.9132C21.5381 11.8766 21.4195 11.8039 21.3276 11.7034C21.2357 11.6029 21.1741 11.4787 21.15 11.345C20.7772 9.29529 19.5402 7.91592 17.4967 7.66329C13.1844 7.13108 6.92001 11.8115 3.50401 18.1189C0.0880105 24.4246 0.813232 29.9707 5.12559 30.5029C8.10443 30.8734 12.0152 28.7513 15.2753 25.3256C15.3714 25.2257 15.4943 25.1553 15.6294 25.1227C15.7645 25.0901 15.9062 25.0967 16.0376 25.1418C16.1691 25.1868 16.2848 25.2684 16.371 25.3768C16.4572 25.4852 16.5104 25.6159 16.5241 25.7534C16.78 28.3909 18.1898 30.1913 20.6687 30.4978C24.1576 30.9307 28.7987 28.2831 32.5265 24.1046C32.6221 23.9983 32.7472 23.9225 32.8861 23.8867C33.025 23.851 33.1715 23.8569 33.307 23.9037C33.4425 23.9505 33.561 24.0362 33.6477 24.1498C33.7343 24.2635 33.7852 24.4001 33.7939 24.5424C33.982 27.8435 35.6425 30.1172 38.6637 30.4911C44.2452 31.1816 52.3548 25.1235 56.7773 16.9584C61.1998 8.7934 60.2594 1.61529 54.6779 0.924763Z\"\n        fill=\"currentColor\"\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(website)/logos/Paper.tsx",
    "content": "import * as React from 'react';\n\nexport function Paper(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"32\"\n      height=\"32\"\n      viewBox=\"0 0 32 32\"\n      fill=\"none\"\n      {...props}\n    >\n      <path\n        d=\"M19.6749 0H4.91873V4.92314H19.6749V19.6923H4.91873V4.92314H0V19.6923V32H4.91873H19.6749V19.6923H31.9717V4.92314V0H19.6749Z\"\n        fill=\"currentColor\"\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(website)/logos/Unsplash.tsx",
    "content": "import * as React from 'react';\n\nexport function Unsplash(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"32\"\n      height=\"32\"\n      viewBox=\"0 0 32 32\"\n      fill=\"none\"\n      {...props}\n    >\n      <g clipPath=\"url(#clip0_88_3022)\">\n        <path d=\"M10 9V0H22V9H10ZM22 14H32V32H0V14H10V23H22V14Z\" fill=\"currentColor\" />\n      </g>\n      <defs>\n        <clipPath id=\"clip0_88_3022\">\n          <rect width=\"32\" height=\"32\" fill=\"white\" />\n        </clipPath>\n      </defs>\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(website)/logos/Zed.tsx",
    "content": "import * as React from 'react';\n\nexport function Zed(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"32\"\n      height=\"32\"\n      viewBox=\"0 0 32 32\"\n      fill=\"none\"\n      {...props}\n    >\n      <g clipPath=\"url(#clip0_88_3019)\">\n        <path\n          fillRule=\"evenodd\"\n          clipRule=\"evenodd\"\n          d=\"M3 2C2.44772 2 2 2.44772 2 3V25H0V3C0 1.34315 1.34315 0 3 0H29.7929C31.1292 0 31.7985 1.61572 30.8535 2.56066L14.3517 19.0625H19V17H21V19.5625C21 20.3909 20.3284 21.0625 19.5 21.0625H12.3517L8.9142 24.5H24.5V12H26.5V24.5C26.5 25.6046 25.6046 26.5 24.5 26.5H6.9142L3.41422 30H29C29.5523 30 30 29.5523 30 29V7H32V29C32 30.6569 30.6569 32 29 32H2.20711C0.870748 32 0.201502 30.3843 1.14645 29.4393L17.5858 13H13V15H11V12.5C11 11.6716 11.6716 11 12.5 11H19.5858L23.0858 7.50002H7.50002V20H5.50002V7.50002C5.50002 6.39541 6.39541 5.50002 7.50002 5.50002H25.0858L28.5858 2H3Z\"\n          fill=\"currentColor\"\n        />\n      </g>\n      <defs>\n        <clipPath id=\"clip0_88_3019\">\n          <rect width=\"32\" height=\"32\" fill=\"white\" />\n        </clipPath>\n      </defs>\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/(website)/page.tsx",
    "content": "import * as React from 'react';\nimport type { Metadata, Viewport } from 'next';\nimport { Accordion } from '@base-ui/react/accordion';\nimport { Link } from 'docs/src/components/Link';\nimport { Paper } from './logos/Paper';\nimport { Zed } from './logos/Zed';\nimport { Unsplash } from './logos/Unsplash';\nimport { Operate } from './logos/Operate';\nimport { GitHub } from './logos/GitHub';\nimport { Interfere } from './logos/Interfere';\nimport { PlusIcon } from './icons/PlusIcon';\nimport { MinusIcon } from './icons/MinusIcon';\n\nexport default function Homepage() {\n  return (\n    <React.Fragment>\n      {/* Set the Site name for Google results. https://developers.google.com/search/docs/appearance/site-names */}\n      <script\n        type=\"application/ld+json\"\n        // eslint-disable-next-line react/no-danger\n        dangerouslySetInnerHTML={{\n          __html: JSON.stringify({\n            '@context': 'https://schema.org',\n            '@type': 'WebSite',\n            name: 'Base UI',\n            url: 'https://base-ui.com',\n          }),\n        }}\n      />\n\n      <section className=\"bui-d-c\">\n        <h1 className=\"Text size-3 bp2:size-4 bui-gcs-1 bui-gce-9 bp4:bui-gce-5\">\n          Unstyled UI components for building accessible user interfaces\n        </h1>\n        <div className=\"bui-gcs-1 bui-gce-9\">\n          <Link className=\"Text size-2 Link bui-d-if\" href=\"/react/overview/quick-start\">\n            Documentation\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"20\"\n              height=\"20\"\n              viewBox=\"0 0 16 16\"\n              fill=\"none\"\n              stroke=\"currentColor\"\n              strokeWidth=\"1\"\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n              className=\"Icon\"\n            >\n              <path className=\"LinkArrowCaret\" d=\"M6 12L10 8L6 4\"></path>\n              <path className=\"LinkArrowLine\" d=\"M2 8L13 8\"></path>\n            </svg>\n          </Link>\n        </div>\n      </section>\n      <section className=\"bui-d-c\">\n        <div className=\"bui-d-f bui-fd-c bui-g-4 bui-gcs-1 bui-gce-9 bp2:bui-gcs-3 bp4:bui-gce-7\">\n          <p className=\"Text size-2\">\n            From the creators of Radix, Floating&nbsp;UI, and Material&nbsp;UI, Base&nbsp;UI is a\n            comprehensive UI component library for building accessible user interfaces with React.\n          </p>\n          <p className=\"Text size-2\">\n            Each Base UI component is meticulously designed for composability, consistency, and\n            craft. The library's architecture prioritizes flexibility—without imposing visual\n            opinions—helping teams craft distinctive interfaces that are fundamentally accessible\n            and reliable.\n          </p>\n          <p className=\"Text size-2\">\n            Collectively, we've been building component libraries for multiple decades. We've\n            learned what works, what lasts, and what doesn't. And we really, really sweat the\n            details.\n          </p>\n          <p className=\"Text size-2\">\n            Base UI is built to last. It is designed with care and maintained with intent. Our\n            mission is to provide a future-proof foundation for professional interface design on the\n            Web.\n          </p>\n        </div>\n      </section>\n      <div className=\"bui-gcs-1 bui-gce-9 bp3:bui-gcs-3\">\n        <div className=\"Separator\" role=\"separator\" aria-hidden=\"true\"></div>\n      </div>\n      <section className=\"bui-d-c\">\n        <div className=\"bui-gcs-1 bui-gce-9 bp2:bui-gce-3\">\n          <h2 className=\"Text size-2\">Made for the makers</h2>\n        </div>\n        <ul\n          className=\"List bui-gcs-1 bui-gce-9 bp3:bui-gcs-3 bui-d-g bui-gtc-2 bp2:bui-gtc-4 bp3:bui-gtc-6 bui-g-8 bp2:bui-g-9\"\n          aria-label=\"companies using Base UI\"\n        >\n          <li>\n            <div className=\"bui-d-f bui-fd-c bui-g-2\">\n              <div className=\"Figure\" aria-hidden=\"true\">\n                <div className=\"bui-d-f bui-ai-c bui-jc-c bui-h-100\">\n                  <Paper />\n                </div>\n              </div>\n              <span className=\"Text size-1\">Paper</span>\n            </div>\n          </li>\n          <li>\n            <div className=\"bui-d-f bui-fd-c bui-g-2\">\n              <div className=\"Figure\" aria-hidden=\"true\">\n                <div className=\"bui-d-f bui-ai-c bui-jc-c bui-h-100\">\n                  <GitHub />\n                </div>\n              </div>\n              <span className=\"Text size-1\">GitHub</span>\n            </div>\n          </li>\n          <li>\n            <div className=\"bui-d-f bui-fd-c bui-g-2\">\n              <div className=\"Figure\" aria-hidden=\"true\">\n                <div className=\"bui-d-f bui-ai-c bui-jc-c bui-h-100\">\n                  <Zed />\n                </div>\n              </div>\n              <span className=\"Text size-1\">Zed</span>\n            </div>\n          </li>\n          <li>\n            <div className=\"bui-d-f bui-fd-c bui-g-2\">\n              <div className=\"Figure\" aria-hidden=\"true\">\n                <div className=\"bui-d-f bui-ai-c bui-jc-c bui-h-100\">\n                  <Unsplash />\n                </div>\n              </div>\n              <span className=\"Text size-1\">Unsplash</span>\n            </div>\n          </li>\n          <li>\n            <div className=\"bui-d-f bui-fd-c bui-g-2\">\n              <div className=\"Figure\" aria-hidden=\"true\">\n                <div className=\"bui-d-f bui-ai-c bui-jc-c bui-h-100\">\n                  <Operate />\n                </div>\n              </div>\n              <span className=\"Text size-1\">Operate</span>\n            </div>\n          </li>\n          <li>\n            <div className=\"bui-d-f bui-fd-c bui-g-2\">\n              <div className=\"Figure\" aria-hidden=\"true\">\n                <div className=\"bui-d-f bui-ai-c bui-jc-c bui-h-100\">\n                  <Interfere />\n                </div>\n              </div>\n              <span className=\"Text size-1\">Interfere</span>\n            </div>\n          </li>\n        </ul>\n      </section>\n      <div className=\"bui-gcs-1 bui-gce-9 bp3:bui-gcs-3\">\n        <div className=\"Separator\" role=\"separator\" aria-hidden=\"true\"></div>\n      </div>\n      <section className=\"bui-d-c\">\n        <div className=\"bui-gcs-1 bui-gce-9 bp2:bui-gce-3\">\n          <h2 className=\"Text size-2\">So you know who to blame</h2>\n        </div>\n        <div className=\"bui-gcs-1 bui-gce-9 bp2:bui-gcs-3 bp4:bui-gce-7\">\n          <ul\n            className=\"List\"\n            aria-label=\"team members\"\n            style={{ borderTop: '1px solid var(--border)' }}\n          >\n            <li className=\"ListItem bui-d-g bui-gtc-2 bui-g-8 bp3:bui-g-9\">\n              <span className=\"Text size-2\">Colm Tuite</span>\n              <span className=\"Text size-2\">Director of Design Engineering</span>\n            </li>\n            <li className=\"ListItem bui-d-g bui-gtc-2 bui-g-8 bp3:bui-g-9\">\n              <span className=\"Text size-2\">Marija Najdova</span>\n              <span className=\"Text size-2\">Director of Engineering</span>\n            </li>\n            <li className=\"ListItem bui-d-g bui-gtc-2 bui-g-8 bp3:bui-g-9\">\n              <span className=\"Text size-2\">Albert Yu</span>\n              <span className=\"Text size-2\">Engineer</span>\n            </li>\n            <li className=\"ListItem bui-d-g bui-gtc-2 bui-g-8 bp3:bui-g-9\">\n              <span className=\"Text size-2\">Flavien Delangle</span>\n              <span className=\"Text size-2\">Engineer</span>\n            </li>\n            <li className=\"ListItem bui-d-g bui-gtc-2 bui-g-8 bp3:bui-g-9\">\n              <span className=\"Text size-2\">James Nelson</span>\n              <span className=\"Text size-2\">Engineer</span>\n            </li>\n            <li className=\"ListItem bui-d-g bui-gtc-2 bui-g-8 bp3:bui-g-9\">\n              <span className=\"Text size-2\">Lukas Tyla</span>\n              <span className=\"Text size-2\">Engineer</span>\n            </li>\n            <li className=\"ListItem bui-d-g bui-gtc-2 bui-g-8 bp3:bui-g-9\">\n              <span className=\"Text size-2\">Michał Dudak</span>\n              <span className=\"Text size-2\">Engineer</span>\n            </li>\n            <li className=\"ListItem bui-d-g bui-gtc-2 bui-g-8 bp3:bui-g-9\">\n              <span className=\"Text size-2\">Vlad Moroz</span>\n              <span className=\"Text size-2\">Contributor</span>\n            </li>\n          </ul>\n        </div>\n      </section>\n      <div className=\"bui-gcs-1 bui-gce-9 bp3:bui-gcs-3\">\n        <div className=\"Separator\" role=\"separator\" aria-hidden=\"true\"></div>\n      </div>\n      <section className=\"bui-d-c\">\n        <div className=\"bui-gcs-1 bui-gce-9 bp2:bui-gce-3\">\n          <h2 className=\"Text size-2\">The fine print</h2>\n        </div>\n        <div className=\"bui-gcs-1 bui-gce-9 bp2:bui-gcs-3 bp4:bui-gce-7\">\n          <Accordion.Root className=\"AccordionRoot\">\n            <Accordion.Item className=\"AccordionItem\">\n              <Accordion.Header className=\"AccordionHeader\">\n                <Accordion.Trigger className=\"AccordionTrigger Text size-2\">\n                  What is Base UI?\n                  <PlusIcon className=\"AccordionIcon AccordionIconPlus\" />\n                  <MinusIcon className=\"AccordionIcon AccordionIconMinus\" />\n                </Accordion.Trigger>\n              </Accordion.Header>\n              <Accordion.Panel className=\"AccordionPanel\">\n                <p className=\"Text size-2\">\n                  Base UI is a library of unstyled UI components for building accessible component\n                  libraries, user interfaces, web applications, and websites with React. Base UI\n                  components are highly configurable, composable, and customizable.\n                </p>\n              </Accordion.Panel>\n            </Accordion.Item>\n            <Accordion.Item className=\"AccordionItem\">\n              <Accordion.Header className=\"AccordionHeader\">\n                <Accordion.Trigger className=\"AccordionTrigger Text size-2\">\n                  Does Base UI work with any styling library?\n                  <PlusIcon className=\"AccordionIcon AccordionIconPlus\" />\n                  <MinusIcon className=\"AccordionIcon AccordionIconMinus\" />\n                </Accordion.Trigger>\n              </Accordion.Header>\n              <Accordion.Panel className=\"AccordionPanel\">\n                <p className=\"Text size-2\">\n                  Yes. Base UI works with Tailwind, CSS Modules, CSS-in-JS, plain CSS, and any other\n                  styling library you prefer. It also works with JavaScript animation libraries like\n                  Motion, or just plain CSS transitions. Base UI is an unstyled component library.\n                  The package does not bundle any CSS, and does not prescribe any styling solution.\n                </p>\n              </Accordion.Panel>\n            </Accordion.Item>\n            <Accordion.Item className=\"AccordionItem\">\n              <Accordion.Header className=\"AccordionHeader\">\n                <Accordion.Trigger className=\"AccordionTrigger Text size-2\">\n                  Which accessibility standards does Base UI follow?\n                  <PlusIcon className=\"AccordionIcon AccordionIconPlus\" />\n                  <MinusIcon className=\"AccordionIcon AccordionIconMinus\" />\n                </Accordion.Trigger>\n              </Accordion.Header>\n              <Accordion.Panel className=\"AccordionPanel\">\n                <p className=\"Text size-2\">\n                  When designing and speccing components, we follow{' '}\n                  <a className=\"Link\" href=\"https://www.w3.org/WAI/ARIA/apg/patterns/\">\n                    ARIA Authoring Practices Guide patterns\n                  </a>\n                  , and comply with the{' '}\n                  <a className=\"Link\" href=\"https://www.w3.org/TR/WCAG22/#new-features-in-wcag-2-2\">\n                    WCAG 2.2 standard\n                  </a>\n                  . Base UI is compliant with all Success Criteria levels relating to component\n                  behavior. However, in most cases, we go way beyond these guides. Base UI\n                  components are tested across a wide range of browsers, devices, platforms, and\n                  environments, and are designed to be accessible.\n                </p>\n              </Accordion.Panel>\n            </Accordion.Item>\n            <Accordion.Item className=\"AccordionItem\">\n              <Accordion.Header className=\"AccordionHeader\">\n                <Accordion.Trigger className=\"AccordionTrigger Text size-2\">\n                  How does Base UI differ from Radix UI?\n                  <PlusIcon className=\"AccordionIcon AccordionIconPlus\" />\n                  <MinusIcon className=\"AccordionIcon AccordionIconMinus\" />\n                </Accordion.Trigger>\n              </Accordion.Header>\n              <Accordion.Panel className=\"AccordionPanel\">\n                <div className=\"bui-d-f bui-fd-c bui-g-4\">\n                  <p className=\"Text size-2\">\n                    In terms of API design, both libraries are very similar. We intentionally kept\n                    our APIs close to Radix UI for an easier migration path. Base UI provides more\n                    complex components such as Combobox and Autocomplete. Base UI also provides\n                    deeper feature support such as input scrubbing, nested dialogs, and triggering\n                    menus on hover. Base UI is more robust and more polished in terms of a11y and\n                    edge case handling.\n                  </p>\n                  <p className=\"Text size-2\">\n                    But the most important difference is that Base UI is actively maintained and\n                    developed, with a dedicated team of 7 developers, designers, and managers\n                    working on it full-time.\n                  </p>\n                </div>\n              </Accordion.Panel>\n            </Accordion.Item>\n            <Accordion.Item className=\"AccordionItem\">\n              <Accordion.Header className=\"AccordionHeader\">\n                <Accordion.Trigger className=\"AccordionTrigger Text size-2\">\n                  Can I use Base UI without React?\n                  <PlusIcon className=\"AccordionIcon AccordionIconPlus\" />\n                  <MinusIcon className=\"AccordionIcon AccordionIconMinus\" />\n                </Accordion.Trigger>\n              </Accordion.Header>\n              <Accordion.Panel className=\"AccordionPanel\">\n                <p className=\"Text size-2\">\n                  Base UI is a React library. It is not designed to be used without React. We may\n                  consider supporting other libraries at some point, but for the foreseeable future,\n                  React is our primary focus.\n                </p>\n              </Accordion.Panel>\n            </Accordion.Item>\n            <Accordion.Item className=\"AccordionItem\">\n              <Accordion.Header className=\"AccordionHeader\">\n                <Accordion.Trigger className=\"AccordionTrigger Text size-2\">\n                  Is Base UI free for commercial use?\n                  <PlusIcon className=\"AccordionIcon AccordionIconPlus\" />\n                  <MinusIcon className=\"AccordionIcon AccordionIconMinus\" />\n                </Accordion.Trigger>\n              </Accordion.Header>\n              <Accordion.Panel className=\"AccordionPanel\">\n                <p className=\"Text size-2\">\n                  Yes. Base UI is licensed under the MIT license, and is free for commercial use.\n                  You are free to use it in your commercial projects, and to modify it to suit your\n                  needs.\n                </p>\n              </Accordion.Panel>\n            </Accordion.Item>\n            <Accordion.Item className=\"AccordionItem\">\n              <Accordion.Header className=\"AccordionHeader\">\n                <Accordion.Trigger className=\"AccordionTrigger Text size-2\">\n                  Do you offer enterprise SLAs?\n                  <PlusIcon className=\"AccordionIcon AccordionIconPlus\" />\n                  <MinusIcon className=\"AccordionIcon AccordionIconMinus\" />\n                </Accordion.Trigger>\n              </Accordion.Header>\n              <Accordion.Panel className=\"AccordionPanel\">\n                <p className=\"Text size-2\">\n                  Not currently. We do provide dedicated support channels to some very large\n                  enterprise companies who are working with us as design partners. But we do not\n                  currently provide Service Level Agreements, guaranteed response times, issue\n                  escalation, feature prioritization, or any other formal support guarantees.\n                </p>\n              </Accordion.Panel>\n            </Accordion.Item>\n          </Accordion.Root>\n        </div>\n      </section>\n    </React.Fragment>\n  );\n}\n\nconst description = 'Unstyled UI components for building accessible web apps and design systems.';\n\nexport const metadata: Metadata = {\n  description,\n  twitter: {\n    description,\n  },\n  openGraph: {\n    description,\n  },\n};\n\n// Custom viewport for the homepage because on mobile it doesn't have a header\nexport const viewport: Viewport = {\n  themeColor: [\n    // Desktop Safari page background\n    {\n      media: '(prefers-color-scheme: light) and (min-width: 1024px)',\n      color: 'oklch(95% 0.25% 264)',\n    },\n    {\n      media: '(prefers-color-scheme: dark) and (min-width: 1024px)',\n      color: 'oklch(25% 1% 264)',\n    },\n\n    // Mobile Safari header background (match the page content)\n    {\n      media: '(prefers-color-scheme: light)',\n      color: '#FFF',\n    },\n    {\n      media: '(prefers-color-scheme: dark)',\n      color: '#000',\n    },\n  ],\n};\n"
  },
  {
    "path": "docs/src/app/global-not-found.tsx",
    "content": "import { Link } from 'docs/src/components/Link';\nimport Layout from './(website)/layout';\n\nexport default function NotFoundPage() {\n  return (\n    <Layout>\n      <section className=\"bui-d-c\">\n        <h1 className=\"Text size-3 bp2:size-4 bui-gcs-1 bui-gce-9 bp4:bui-gce-5\">Page not found</h1>\n        <div className=\"bui-gcs-1 bui-gce-9\">\n          <p className=\"Text size-2\">\n            This page couldn't be found. Please return to the{' '}\n            <Link href=\"/react/overview/quick-start\">docs</Link> or create a corresponding issue on{' '}\n            <Link href=\"https://github.com/mui/base-ui\" skipExternalIcon>\n              GitHub\n            </Link>\n            .\n          </p>\n        </div>\n      </section>\n    </Layout>\n  );\n}\n"
  },
  {
    "path": "docs/src/app/sitemap/index.ts",
    "content": "import { createSitemap } from '@mui/internal-docs-infra/createSitemap';\nimport Overview from '../(docs)/react/overview/page.mdx';\nimport Handbook from '../(docs)/react/handbook/page.mdx';\nimport Components from '../(docs)/react/components/page.mdx';\nimport Utils from '../(docs)/react/utils/page.mdx';\n\nexport const sitemap = createSitemap(import.meta.url, {\n  Overview,\n  Handbook,\n  Components,\n  Utils,\n});\n"
  },
  {
    "path": "docs/src/blocks/Demo/DemoContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { DemoFile, DemoVariant } from './types';\n\nexport interface DemoContext {\n  selectedFile: DemoFile;\n  selectedVariant: DemoVariant;\n  setSelectedFile: (file: DemoFile) => void;\n  setSelectedVariant: (variant: DemoVariant) => void;\n  variants: DemoVariant[];\n}\n\nexport const DemoContext = React.createContext<DemoContext | undefined>(undefined);\n\nif (process.env.NODE_ENV !== 'production') {\n  DemoContext.displayName = 'DemoContext';\n}\n\nexport function useDemoContext() {\n  const context = React.useContext(DemoContext);\n  if (!context) {\n    throw new Error('useDemoContext must be used within a DemoProvider');\n  }\n  return context;\n}\n"
  },
  {
    "path": "docs/src/blocks/Demo/DemoPlayground.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { DemoContext } from './DemoContext';\n\nexport const DemoPlayground = React.forwardRef(function DemoPlayground(\n  props: DemoPlayground.Props,\n  ref: React.ForwardedRef<HTMLDivElement>,\n) {\n  const demoContext = React.useContext(DemoContext);\n  if (!demoContext) {\n    throw new Error('Demo.Playground must be used within a Demo.Root');\n  }\n\n  const { selectedVariant } = demoContext;\n\n  const { component: DemoComponent } = selectedVariant;\n\n  return (\n    <div {...props} ref={ref}>\n      <DemoComponent />\n    </div>\n  );\n});\n\nexport namespace DemoPlayground {\n  export interface Props extends React.HTMLAttributes<HTMLDivElement> {\n    className?: string;\n  }\n}\n"
  },
  {
    "path": "docs/src/blocks/Demo/DemoRoot.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { DemoVariant, DemoFile } from './types';\nimport { DemoContext } from './DemoContext';\n\nexport function DemoRoot(props: DemoRoot.Props) {\n  const { variants, children, ...other } = props;\n\n  if (variants.length === 0) {\n    throw new Error('No demo variants provided');\n  }\n\n  const [selectedVariant, setSelectedVariant] = React.useState(variants[0]);\n  const [selectedFile, setSelectedFile] = React.useState<DemoFile>(selectedVariant.files[0]);\n\n  React.useEffect(() => {\n    setSelectedFile(selectedVariant.files[0]);\n  }, [selectedVariant]);\n\n  const contextValue: DemoContext = React.useMemo(\n    () =>\n      ({\n        variants,\n        selectedVariant,\n        selectedFile,\n        setSelectedVariant,\n        setSelectedFile,\n      }) satisfies DemoContext,\n    [selectedVariant, selectedFile, variants],\n  );\n\n  return (\n    <DemoContext.Provider value={contextValue}>\n      <div {...other}>{children}</div>\n    </DemoContext.Provider>\n  );\n}\n\nexport namespace DemoRoot {\n  export interface Props extends React.HTMLAttributes<HTMLDivElement> {\n    variants: DemoVariant[];\n  }\n}\n"
  },
  {
    "path": "docs/src/blocks/Demo/DemoSourceBrowser.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport clsx from 'clsx';\nimport { camelCase } from 'es-toolkit/string';\nimport { DemoContext } from './DemoContext';\n\nexport const DemoSourceBrowser = React.forwardRef<HTMLPreElement, React.ComponentProps<'pre'>>(\n  (props, forwardedRef) => {\n    const demoContext = React.useContext(DemoContext);\n    if (!demoContext) {\n      throw new Error('Demo.Playground must be used within a Demo.Root');\n    }\n\n    const { selectedFile } = demoContext;\n\n    if (selectedFile.prettyContent != null) {\n      // Unwrap the incoming `<pre>` and parse out its attributes to put on the node we own\n      const [, pre, code] = selectedFile.prettyContent.match(/(<pre.+?>)(.+)<\\/pre>/s) ?? [];\n\n      if (!pre || !code) {\n        throw new Error('Couldn’t parse prettyContent');\n      }\n\n      const [, className = ''] = pre.match(/class=\"(.+?)\"/) ?? [];\n      const [, styleAttr = ''] = pre.match(/style=\"(.+?)\"/) ?? [];\n      const style = Object.fromEntries(\n        styleAttr\n          .split(';')\n          .map((str) => str.split(':'))\n          .map(([key, value]) => [camelCase(key), value]),\n      );\n\n      return (\n        <pre\n          {...props}\n          ref={forwardedRef}\n          data-language={selectedFile.type}\n          className={clsx(className, props.className)}\n          style={{ ...style, ...props.style }}\n          // eslint-disable-next-line react/no-danger\n          dangerouslySetInnerHTML={{ __html: code }}\n        />\n      );\n    }\n\n    return (\n      <pre ref={forwardedRef}>\n        <code>{selectedFile.content}</code>\n      </pre>\n    );\n  },\n);\n\nexport namespace DemoSourceBrowser {}\n"
  },
  {
    "path": "docs/src/blocks/Demo/DemoSourceCopy.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport copy from 'clipboard-copy';\nimport { useDemoContext } from './DemoContext';\n\nexport const DemoSourceCopy = React.forwardRef<HTMLButtonElement, DemoSourceCopy.Props>(\n  function DemoSource(props, forwardedRef) {\n    const { onCopied, onError, onClick, render, ...other } = props;\n\n    const { selectedFile } = useDemoContext();\n\n    const handleClick = React.useCallback(\n      async (event: React.MouseEvent<HTMLButtonElement>) => {\n        try {\n          await copy(selectedFile.content);\n          onCopied?.();\n        } catch (error) {\n          onError?.(error);\n        }\n\n        onClick?.(event);\n      },\n      [onCopied, onError, selectedFile, onClick],\n    );\n\n    if (!selectedFile) {\n      return null;\n    }\n\n    if (render) {\n      return React.cloneElement(render, {\n        ...other,\n        onClick: handleClick,\n        ref: forwardedRef,\n      });\n    }\n\n    return <button type=\"button\" {...other} onClick={handleClick} ref={forwardedRef} />;\n  },\n);\n\nexport namespace DemoSourceCopy {\n  export interface Props extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n    onCopied?: () => void;\n    onError?: (error: unknown) => void;\n    render?: React.ReactElement<React.ComponentPropsWithRef<'button'>>;\n  }\n}\n"
  },
  {
    "path": "docs/src/blocks/Demo/index.ts",
    "content": "export { DemoRoot as Root } from './DemoRoot';\nexport { DemoPlayground as Playground } from './DemoPlayground';\nexport { DemoSourceBrowser as SourceBrowser } from './DemoSourceBrowser';\nexport { DemoSourceCopy as SourceCopy } from './DemoSourceCopy';\n\nexport { DemoContext } from './DemoContext';\nexport type * from './types';\n"
  },
  {
    "path": "docs/src/blocks/Demo/types.ts",
    "content": "export interface DemoFile {\n  /**\n   * Absolute path to the file.\n   */\n  path: string;\n  /**\n   * Base name of the file.\n   */\n  name: string;\n  /**\n   * Content of the file.\n   */\n  content: string;\n  /**\n   * Pretty content of the file as HTML with highlighted syntax.\n   */\n  prettyContent?: string;\n  /**\n   * Type of the file.\n   */\n  type: string;\n}\n\nexport interface DemoVariant {\n  /**\n   * Variant identifier.\n   */\n  name: string;\n  /**\n   * Language of the entry point file.\n   */\n  language: 'ts' | 'js';\n  /**\n   * Runnable demo component.\n   */\n  component: React.ComponentType;\n  /**\n   * Files the demo consists of.\n   */\n  files: DemoFile[];\n}\n"
  },
  {
    "path": "docs/src/blocks/GoogleAnalyticsProvider.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useMediaQuery } from '@base-ui/react/unstable-use-media-query';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\n\ndeclare global {\n  interface Window {\n    dataLayer: unknown[];\n  }\n}\n\nexport interface GoogleAnalyticsEvent {\n  category: string;\n  action: string;\n  label?: string;\n  params?: Record<string, string | number | boolean>;\n}\n\nexport interface GoogleAnalyticsContextValue {\n  trackEvent: (event: GoogleAnalyticsEvent) => void;\n}\n\nexport const GoogleAnalyticsContext = React.createContext<GoogleAnalyticsContextValue | null>(null);\n\nexport function useGoogleAnalytics() {\n  return React.useContext(GoogleAnalyticsContext);\n}\n\nexport interface GoogleAnalyticsProviderProps {\n  id: string;\n  productId: string;\n  productCategoryId: string;\n  codeStylingVariant: string | null;\n  codeLanguage: string;\n  currentRoute: string;\n  userLanguage: string;\n  children?: React.ReactNode;\n}\n\nexport function GoogleAnalyticsProvider({\n  id,\n  productId,\n  productCategoryId,\n  codeLanguage,\n  codeStylingVariant,\n  currentRoute,\n  userLanguage,\n  children,\n}: GoogleAnalyticsProviderProps) {\n  useIsoLayoutEffect(() => {\n    window.dataLayer = window.dataLayer || [];\n\n    const gtag: Gtag.Gtag = function gtag() {\n      // gtag expects the Arguments object\n      // eslint-disable-next-line prefer-rest-params\n      window.dataLayer.push(arguments);\n    };\n\n    window.gtag = gtag;\n\n    gtag('js', new Date());\n\n    gtag('config', id, {\n      send_page_view: false,\n    });\n  }, [id]);\n\n  const timeout = React.useRef<ReturnType<typeof setTimeout> | null>(null);\n\n  React.useEffect(() => {\n    // Wait for the title to be updated.\n    // React fires useEffect twice in dev mode\n    clearTimeout(timeout.current ?? undefined);\n    timeout.current = setTimeout(() => {\n      // Remove hash as it's never sent to the server\n      // https://github.com/vercel/next.js/issues/25202\n      const canonicalAsServer = window.location.pathname.replace(/#(.*)$/, '');\n\n      // https://developers.google.com/analytics/devguides/collection/ga4/views?client_type=gtag\n      window.gtag('event', 'page_view', {\n        page_title: document.title,\n        page_location: canonicalAsServer,\n        productId,\n        productCategoryId,\n      });\n    });\n  }, [currentRoute, productCategoryId, productId]);\n\n  React.useEffect(() => {\n    window.gtag('set', 'user_properties', {\n      codeVariant: codeLanguage,\n    });\n  }, [codeLanguage]);\n\n  React.useEffect(() => {\n    window.gtag('set', 'user_properties', {\n      codeStylingVariant,\n    });\n  }, [codeStylingVariant]);\n\n  React.useEffect(() => {\n    window.gtag('set', 'user_properties', {\n      userLanguage,\n    });\n  }, [userLanguage]);\n\n  React.useEffect(() => {\n    /**\n     * Based on https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio#Monitoring_screen_resolution_or_zoom_level_changes\n     * Adjusted to track 3 or more different ratios\n     */\n    function trackDevicePixelRatio() {\n      const devicePixelRatio = Math.round(window.devicePixelRatio * 10) / 10;\n      window.gtag('set', 'user_properties', {\n        devicePixelRatio,\n      });\n    }\n\n    trackDevicePixelRatio();\n\n    const matchMedia: MediaQueryList = window.matchMedia(\n      `(resolution: ${window.devicePixelRatio}dppx)`,\n    );\n\n    matchMedia.addEventListener('change', trackDevicePixelRatio);\n    return () => {\n      matchMedia.removeEventListener('change', trackDevicePixelRatio);\n    };\n  }, []);\n\n  const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)', { noSsr: true });\n  const preferredColorScheme = prefersDarkMode ? 'dark' : 'light';\n\n  React.useEffect(() => {\n    window.gtag('set', 'user_properties', {\n      colorSchemeOS: preferredColorScheme,\n    });\n  }, [preferredColorScheme]);\n\n  const contextValue = React.useMemo<GoogleAnalyticsContextValue>(\n    () => ({\n      trackEvent({ category, action, label, params }) {\n        window.gtag('event', category, {\n          action,\n          label,\n          ...params,\n        });\n      },\n    }),\n    [],\n  );\n\n  return (\n    <GoogleAnalyticsContext.Provider value={contextValue}>\n      {children}\n    </GoogleAnalyticsContext.Provider>\n  );\n}\n"
  },
  {
    "path": "docs/src/blocks/GoogleTagManager.tsx",
    "content": "import * as React from 'react';\nimport Script from 'next/script';\n\ninterface GoogleTagManagerProps {\n  id: string;\n}\n\nexport function GoogleTagManager(props: React.PropsWithChildren<GoogleTagManagerProps>) {\n  const { id } = props;\n  return (\n    /**\n     * A better alternative to <script async>, to delay its execution\n     * https://developer.chrome.com/blog/script-component/\n     */\n    <Script strategy=\"afterInteractive\" src={`https://www.googletagmanager.com/gtag/js?id=${id}`} />\n  );\n}\n"
  },
  {
    "path": "docs/src/blocks/PackageManagerSnippet/PackageManagerSnippetCode.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Tabs } from '@base-ui/react/tabs';\n\nexport function PackageManagerSnippetCode(props: PackageManagerSnippetCode.Props) {\n  const { value, children } = props;\n\n  return <Tabs.Panel value={value}>{children}</Tabs.Panel>;\n}\n\nexport namespace PackageManagerSnippetCode {\n  export type Props = {\n    children: React.ReactNode;\n    value: string;\n  };\n}\n"
  },
  {
    "path": "docs/src/blocks/PackageManagerSnippet/PackageManagerSnippetProvider.tsx",
    "content": "'use client';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport * as React from 'react';\n\nexport interface PackageManagerSnippetContext {\n  packageManager: string;\n  setPackageManager: (variant: string) => void;\n}\n\nexport const PackageManagerSnippetContext =\n  React.createContext<PackageManagerSnippetContext | null>(null);\n\nexport const usePackageManagerSnippetContext = () => {\n  const context = React.useContext(PackageManagerSnippetContext);\n  if (!context) {\n    throw new Error('Missing PackageManagerSnippetContext');\n  }\n\n  return context;\n};\n\ninterface PackageManagerSnippetProviderProps {\n  children: React.ReactNode;\n  defaultValue: string;\n}\n\nconst STORAGE_KEY = 'preferredPackageManager';\n\nexport function PackageManagerSnippetProvider(props: PackageManagerSnippetProviderProps) {\n  const { children, defaultValue } = props;\n  const [value, setValue] = React.useState(defaultValue);\n\n  const handleValueChange = React.useCallback((newValue: string) => {\n    setValue(newValue);\n    localStorage.setItem(STORAGE_KEY, newValue);\n  }, []);\n\n  useIsoLayoutEffect(() => {\n    const savedValue = localStorage.getItem(STORAGE_KEY);\n\n    if (savedValue) {\n      setValue(savedValue);\n    }\n  }, []);\n\n  const contextValue = React.useMemo(\n    () => ({\n      packageManager: value,\n      setPackageManager: handleValueChange,\n    }),\n    [value, handleValueChange],\n  );\n\n  return (\n    <PackageManagerSnippetContext.Provider value={contextValue}>\n      {children}\n    </PackageManagerSnippetContext.Provider>\n  );\n}\n"
  },
  {
    "path": "docs/src/blocks/PackageManagerSnippet/PackageManagerSnippetRoot.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Tabs } from '@base-ui/react/tabs';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { usePackageManagerSnippetContext } from './PackageManagerSnippetProvider';\n\nexport function PackageManagerSnippetRoot(props: PackageManagerSnippetRoot.Props) {\n  const { children, options, renderTab, renderTabsList } = props;\n\n  const { packageManager: globalPreference, setPackageManager: setGlobalPreference } =\n    usePackageManagerSnippetContext();\n\n  const [value, setValue] = React.useState(options[0].value);\n\n  const handleValueChange = React.useCallback(\n    (newValue: string) => {\n      setGlobalPreference(newValue);\n    },\n    [setGlobalPreference],\n  );\n\n  useIsoLayoutEffect(() => {\n    if (options.some((option) => option.value === globalPreference)) {\n      setValue(globalPreference);\n    }\n  }, [options, globalPreference]);\n\n  return (\n    <Tabs.Root value={value} onValueChange={handleValueChange}>\n      <Tabs.List render={renderTabsList} aria-label=\"Package manager selector\">\n        {options.map((option) => (\n          <Tabs.Tab key={option.value} value={option.value} render={renderTab}>\n            {option.label}\n          </Tabs.Tab>\n        ))}\n      </Tabs.List>\n      {children}\n    </Tabs.Root>\n  );\n}\n\nexport namespace PackageManagerSnippetRoot {\n  export type Props = {\n    children: React.ReactNode;\n    options: Array<{ value: string; label: string }>;\n    renderTab?: Tabs.Tab.Props['render'];\n    renderTabsList?: Tabs.List.Props['render'];\n  };\n}\n"
  },
  {
    "path": "docs/src/blocks/PackageManagerSnippet/index.ts",
    "content": "export { PackageManagerSnippetRoot as Root } from './PackageManagerSnippetRoot';\nexport { PackageManagerSnippetCode as Code } from './PackageManagerSnippetCode';\n"
  },
  {
    "path": "docs/src/blocks/README.txt",
    "content": "This directory houses unstyled building blocks for the docs.\n"
  },
  {
    "path": "docs/src/blocks/createCodeSandbox/CreateReactApp.ts",
    "content": "interface GetHtmlParameters {\n  title: string;\n  language: string;\n  additionalHeadContent?: string;\n}\n\nexport const getHtml = ({ title, language, additionalHeadContent }: GetHtmlParameters) => {\n  return `<!DOCTYPE html>\n<html lang=\"${language}\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>${title}</title>\n    <meta name=\"viewport\" content=\"initial-scale=1, width=device-width\" />\n    ${additionalHeadContent ?? ''}\n  </head>\n  <body>\n    <div id=\"root\"></div>\n  </body>\n</html>`;\n};\n\nexport function getRootIndex(useTypescript: boolean) {\n  // document.querySelector returns 'Element | null' but createRoot expects 'Element | DocumentFragment'.\n  const type = useTypescript ? '!' : '';\n\n  return `import * as React from 'react';\nimport * as ReactDOM from 'react-dom/client';\nimport App from './App';\n\nReactDOM.createRoot(document.querySelector(\"#root\")${type}).render(\n  <React.StrictMode>\n    <App />\n  </React.StrictMode>\n);`;\n}\n\nexport const getTsconfig = () => `{\n  \"compilerOptions\": {\n    \"target\": \"es2022\",\n    \"lib\": [\n      \"dom\",\n      \"dom.iterable\",\n      \"esnext\"\n    ],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"noEmit\": true,\n    \"jsx\": \"react\"\n  },\n  \"include\": [\n    \"src\"\n  ]\n}\n`;\n"
  },
  {
    "path": "docs/src/blocks/createCodeSandbox/addHiddenInput.ts",
    "content": "export function addHiddenInput(form: HTMLFormElement, name: string, value: string) {\n  const input = document.createElement('input');\n  input.type = 'hidden';\n  input.name = name;\n  input.value = value;\n  form.appendChild(input);\n}\n"
  },
  {
    "path": "docs/src/blocks/createCodeSandbox/createCodeSandbox.ts",
    "content": "import LZString from 'lz-string';\nimport { DemoFile } from 'docs/src/blocks/Demo';\nimport * as CRA from './CreateReactApp';\nimport { packDemo } from './packDemo';\nimport { addHiddenInput } from './addHiddenInput';\n\nexport function createCodeSandbox(options: createCodeSandbox.Options) {\n  const payload = createCodeSandboxRequestPayload(options);\n  const initialFile = Object.keys(payload)[0];\n  const parameters = compress({ files: payload });\n\n  // ref: https://codesandbox.io/docs/learn/sandboxes/cli-api#define-api\n  const form = document.createElement('form');\n  form.method = 'POST';\n  form.target = '_blank';\n  form.action = 'https://codesandbox.io/api/v1/sandboxes/define';\n  addHiddenInput(form, 'parameters', parameters);\n  addHiddenInput(form, 'query', `file=${initialFile}`);\n  document.body.appendChild(form);\n  form.submit();\n  document.body.removeChild(form);\n}\n\nexport namespace createCodeSandbox {\n  export interface Options {\n    /**\n     * The title of the sandbox.\n     */\n    title: string;\n    /**\n     * The natural language of the sandbox (to be placed in the `html` lang attribute).\n     */\n    naturalLanguage?: string;\n    /**\n     * The description of the sandbox.\n     */\n    description?: string;\n    /**\n     * The demo files to be included in the sandbox.\n     */\n    demoFiles: DemoFile[];\n    /**\n     * The language of the entry point file.\n     */\n    demoLanguage: 'js' | 'ts';\n    /**\n     * The dependencies to be included in the sandbox.\n     * They are added on top of dependencies found in the demo files.\n     */\n    dependencies: Record<string, string>;\n    /**\n     * The dev dependencies to be included in the sandbox.\n     */\n    devDependencies?: Record<string, string>;\n    /**\n     * A function to resolve dependencies versions.\n     * By default, it resolves to the `latest` version.\n     */\n    dependencyResolver?: (importPath: string) => Record<string, string>;\n    /**\n     * Additional content to be placed in the `head` tag of the `index.html` file.\n     */\n    additionalHtmlHeadContent?: string;\n    /**\n     * The custom index (JS/TS) file content.\n     */\n    customIndexFile?: string;\n    /**\n     * A function to customize the files to be included in the sandbox.\n     * It is called for each of the demo files with its name and content.\n     * It expects a tuple with the new file name and content or `null` to apply standard rules\n     * (place the file in the `src` directory).\n     */\n    onAddingFile?: (fileName: string, content: string) => [string, string] | null;\n  }\n}\n\nfunction createCodeSandboxRequestPayload(options: createCodeSandbox.Options) {\n  const {\n    title,\n    naturalLanguage = 'en',\n    customIndexFile,\n    demoFiles,\n    demoLanguage,\n    additionalHtmlHeadContent,\n    onAddingFile,\n  } = options;\n\n  const packagedDemo = packDemo(demoFiles);\n  const indexExtension = demoLanguage === 'ts' ? '.tsx' : '.js';\n\n  const demoFilesToInclude = transformDemoFiles(packagedDemo.processedFiles, onAddingFile);\n\n  const files: Record<string, { content: string | object }> = {\n    ...demoFilesToInclude,\n    [`src/index${indexExtension}`]: {\n      content: customIndexFile ?? CRA.getRootIndex(demoLanguage === 'ts'),\n    },\n    'public/index.html': {\n      content: CRA.getHtml({\n        title,\n        language: naturalLanguage,\n        additionalHeadContent: additionalHtmlHeadContent,\n      }),\n    },\n    'package.json': {\n      content: createPackageJson(\n        options,\n        packagedDemo.externalImports,\n        `src/index${indexExtension}`,\n      ),\n    },\n  };\n\n  if (demoLanguage === 'ts') {\n    files['tsconfig.json'] = {\n      content: CRA.getTsconfig(),\n    };\n  }\n\n  return files;\n}\n\nfunction defaultDependencyResolver(importPath: string) {\n  return {\n    [importPath]: 'latest',\n  };\n}\n\nfunction createPackageJson(\n  options: createCodeSandbox.Options,\n  sourceDependencies: string[],\n  mainFile: string,\n) {\n  const {\n    dependencies: hardcodedDependencies,\n    description,\n    devDependencies,\n    dependencyResolver = defaultDependencyResolver,\n  } = options;\n  const dependencies = new Map<string, string>();\n\n  sourceDependencies.forEach((dependency) => {\n    const resolvedDependencies = dependencyResolver(dependency);\n    Object.entries(resolvedDependencies).forEach(([name, version]) => {\n      dependencies.set(name, version);\n    });\n  });\n\n  Object.entries(hardcodedDependencies).forEach(([name, version]) => {\n    dependencies.set(name, version);\n  });\n\n  return {\n    description,\n    dependencies: Object.fromEntries(dependencies),\n    devDependencies,\n    scripts: {\n      start: 'react-scripts start',\n      build: 'react-scripts build',\n      test: 'react-scripts test',\n      eject: 'react-scripts eject',\n    },\n    main: mainFile,\n  };\n}\n\nfunction transformDemoFiles(\n  files: Record<string, string>,\n  onAddingFile: ((fileName: string, content: string) => [string, string] | null) | undefined,\n) {\n  const processedFiles: Record<string, { content: string }> = {};\n  Object.keys(files).forEach((file) => {\n    const customProcessed = onAddingFile?.(file, files[file]);\n    if (customProcessed) {\n      processedFiles[customProcessed[0]] = { content: customProcessed[1] };\n    } else {\n      processedFiles[`src/${file}`] = { content: files[file] };\n    }\n  });\n\n  return processedFiles;\n}\n\nfunction compress(object: any) {\n  return LZString.compressToBase64(JSON.stringify(object))\n    .replace(/\\+/g, '-') // Convert '+' to '-'\n    .replace(/\\//g, '_') // Convert '/' to '_'\n    .replace(/=+$/, ''); // Remove ending '='\n}\n"
  },
  {
    "path": "docs/src/blocks/createCodeSandbox/createStackBlitzProject.ts",
    "content": "import { DemoFile } from 'docs/src/blocks/Demo';\nimport * as CRA from './CreateReactApp';\nimport { packDemo } from './packDemo';\nimport { addHiddenInput } from './addHiddenInput';\n\nexport function createStackBlitzProject(options: createStackBlitzProject.Options) {\n  const { files, dependencies, devDependencies } = createRequestPayload(options);\n  const initialFile = Object.keys(files)[0];\n\n  const form = document.createElement('form');\n  form.method = 'POST';\n  form.target = '_blank';\n  form.action = `https://stackblitz.com/run?file=${initialFile}`;\n\n  addHiddenInput(form, 'project[template]', 'create-react-app');\n  addHiddenInput(form, 'project[title]', options.title);\n  addHiddenInput(form, 'project[description]', `# ${options.title}\\n${options.description}`);\n  addHiddenInput(form, 'project[dependencies]', JSON.stringify(dependencies));\n  addHiddenInput(form, 'project[devDependencies]', JSON.stringify(devDependencies));\n\n  Object.keys(files).forEach((key) => {\n    const value = files[key];\n    addHiddenInput(form, `project[files][${key}]`, value);\n  });\n\n  document.body.appendChild(form);\n  form.submit();\n  document.body.removeChild(form);\n}\n\nexport namespace createStackBlitzProject {\n  export interface Options {\n    /**\n     * The title of the sandbox.\n     */\n    title: string;\n    /**\n     * The natural language of the sandbox (to be placed in the `html` lang attribute).\n     */\n    naturalLanguage?: string;\n    /**\n     * The description of the sandbox.\n     */\n    description?: string;\n    /**\n     * The demo files to be included in the sandbox.\n     */\n    demoFiles: DemoFile[];\n    /**\n     * The language of the entry point file.\n     */\n    demoLanguage: 'js' | 'ts';\n    /**\n     * The dependencies to be included in the sandbox.\n     * They are added on top of dependencies found in the demo files.\n     */\n    dependencies: Record<string, string>;\n    /**\n     * The dev dependencies to be included in the sandbox.\n     */\n    devDependencies?: Record<string, string>;\n    /**\n     * A function to resolve dependencies versions.\n     * By default, it resolves to the `latest` version.\n     */\n    dependencyResolver?: (importPath: string) => Record<string, string>;\n    /**\n     * Additional content to be placed in the `head` tag of the `index.html` file.\n     */\n    additionalHtmlHeadContent?: string;\n    /**\n     * The custom index (JS/TS) file content.\n     */\n    customIndexFile?: string;\n    /**\n     * A function to customize the files to be included in the sandbox.\n     * It is called for each of the demo files with its name and content.\n     * It expects a tuple with the new file name and content or `null` to apply standard rules\n     * (place the file in the `src` directory).\n     */\n    onAddingFile?: (fileName: string, content: string) => [string, string] | null;\n  }\n}\n\nfunction createRequestPayload(options: createStackBlitzProject.Options) {\n  const {\n    title,\n    naturalLanguage = 'en',\n    customIndexFile,\n    demoFiles,\n    demoLanguage,\n    additionalHtmlHeadContent,\n    onAddingFile,\n  } = options;\n\n  const packagedDemo = packDemo(demoFiles);\n  const indexExtension = demoLanguage === 'ts' ? '.tsx' : '.js';\n\n  const demoFilesToInclude = transformDemoFiles(packagedDemo.processedFiles, onAddingFile);\n\n  const files: Record<string, string> = {\n    ...demoFilesToInclude,\n    [`src/index${indexExtension}`]: customIndexFile ?? CRA.getRootIndex(demoLanguage === 'ts'),\n    'public/index.html': CRA.getHtml({\n      title,\n      language: naturalLanguage,\n      additionalHeadContent: additionalHtmlHeadContent,\n    }),\n  };\n\n  if (demoLanguage === 'ts') {\n    files['tsconfig.json'] = CRA.getTsconfig();\n  }\n\n  return {\n    files,\n    dependencies: getDependencies(options, packagedDemo.externalImports),\n    devDependencies: options.devDependencies ?? {},\n  };\n}\n\nfunction defaultDependencyResolver(importPath: string) {\n  return {\n    [importPath]: 'latest',\n  };\n}\n\nfunction getDependencies(options: createStackBlitzProject.Options, sourceDependencies: string[]) {\n  const { dependencies: hardcodedDependencies, dependencyResolver = defaultDependencyResolver } =\n    options;\n  const dependencies = new Map<string, string>();\n\n  sourceDependencies.forEach((dependency) => {\n    const resolvedDependencies = dependencyResolver(dependency);\n    Object.entries(resolvedDependencies).forEach(([name, version]) => {\n      dependencies.set(name, version);\n    });\n  });\n\n  Object.entries(hardcodedDependencies).forEach(([name, version]) => {\n    dependencies.set(name, version);\n  });\n\n  return Object.fromEntries(dependencies);\n}\n\nfunction transformDemoFiles(\n  files: Record<string, string>,\n  onAddingFile: ((fileName: string, content: string) => [string, string] | null) | undefined,\n) {\n  const processedFiles: Record<string, string> = {};\n  Object.keys(files).forEach((file) => {\n    const customProcessed = onAddingFile?.(file, files[file]);\n    if (customProcessed) {\n      processedFiles[customProcessed[0]] = customProcessed[1];\n    } else {\n      processedFiles[`src/${file}`] = files[file];\n    }\n  });\n\n  return processedFiles;\n}\n"
  },
  {
    "path": "docs/src/blocks/createCodeSandbox/flattenRelativeImports.ts",
    "content": "export default function flattenRelativeImports(rawCode: string, modulePaths: string[] = []) {\n  let newCode = rawCode;\n  modulePaths.forEach((path: string) => {\n    const pathWithoutExtension = path.replace(/\\.[a-z]*$/g, '');\n    // Move the relative import to the current directory\n    const newPath = `./${pathWithoutExtension.replace(/^.*[\\\\/]/g, '')}`;\n    newCode = newCode.replace(pathWithoutExtension, newPath);\n  });\n  return newCode;\n}\n"
  },
  {
    "path": "docs/src/blocks/createCodeSandbox/packDemo.test.ts",
    "content": "import { describe, it, expect } from 'vitest';\nimport { DemoFile } from 'docs/src/blocks/Demo';\nimport { packDemo } from './packDemo';\n\ndescribe('packDemo', () => {\n  it('transforms the files into an object and extracts dependencies', () => {\n    const files: DemoFile[] = [\n      {\n        name: 'file.js',\n        content: \"import * as React from 'react';\",\n        path: 'data/Test/file.js',\n        type: 'js',\n      },\n    ];\n\n    const result = packDemo(files);\n    expect(result.processedFiles['App.js']).toBe(files[0].content);\n\n    expect(result.externalImports.length).toBe(1);\n    expect(result.externalImports[0]).toBe('react');\n  });\n\n  it('flattens relative imports', () => {\n    const files: DemoFile[] = [\n      {\n        name: 'file1.js',\n        content: \"import file2 from './deps/file2';\",\n        path: 'data/Test/file1.js',\n        type: 'js',\n      },\n      {\n        name: 'file2.js',\n        content: \"export default 'file2';\",\n        path: 'data/Test/deps/file2.js',\n        type: 'js',\n      },\n    ];\n\n    const result = packDemo(files);\n    expect(result.processedFiles['App.js']).toBe(\"import file2 from './file2';\");\n    expect(result.processedFiles['file2.js']).toBe(files[1].content);\n    expect(result.externalImports.length).toBe(0);\n  });\n\n  it('flattens relative imports from parent directories', () => {\n    const files: DemoFile[] = [\n      {\n        name: 'file1.js',\n        content: \"import file2 from '../file2';\",\n        path: 'data/Test/file1.js',\n        type: 'js',\n      },\n      {\n        name: 'file2.js',\n        content: \"export default 'file2';\",\n        path: 'data/file2.js',\n        type: 'js',\n      },\n    ];\n\n    const result = packDemo(files);\n    expect(result.processedFiles['App.js']).toBe(\"import file2 from './file2';\");\n    expect(result.processedFiles['file2.js']).toBe(files[1].content);\n    expect(result.externalImports.length).toBe(0);\n  });\n});\n"
  },
  {
    "path": "docs/src/blocks/createCodeSandbox/packDemo.ts",
    "content": "import { DemoFile } from 'docs/src/blocks/Demo';\nimport flattenRelativeImports from './flattenRelativeImports';\n\nconst SCRIPT_EXTENSIONS = ['js', 'ts', 'jsx', 'tsx'];\n\n/**\n * Packs a demo into a format that can be used to create a CodeSandbox or StackBlitz project.\n * Also flattens the local dependencies so they are all in the same directory.\n *\n * WARNING: To simplify processing, each file name must be unique\n * (i.e., you can't have foo.ts and bar/foo.ts in the same demo)\n *\n * @param demoFiles Files that make a demo\n */\nexport function packDemo(demoFiles: DemoFile[]) {\n  const processedFiles: Record<string, string> = {};\n  const externalImports: Set<string> = new Set<string>();\n\n  demoFiles.forEach((file, index) => {\n    const fileExtension = file.name.split('.').pop() ?? '';\n    let newContent = file.content;\n\n    if (SCRIPT_EXTENSIONS.includes(fileExtension)) {\n      const relativeImports: Set<string> = new Set<string>();\n      const imports = extractImports(file.content);\n      imports.forEach((importPath) => {\n        if (importPath.startsWith('.')) {\n          relativeImports.add(importPath);\n        } else {\n          externalImports.add(getPackageName(importPath));\n        }\n      });\n\n      newContent = flattenRelativeImports(file.content, [...relativeImports]);\n    }\n\n    // the entry point is renamed to App.{ext}\n    const newFileName = index === 0 ? `App.${fileExtension}` : file.name;\n    processedFiles[newFileName] = newContent;\n  });\n\n  return {\n    processedFiles,\n    externalImports: [...externalImports],\n  };\n}\n\nfunction extractImports(code: string) {\n  return [...code.matchAll(/import\\s+(?:[^'\"]*?\\s+from\\s+)?['\"]([^\"'\\n]*?)['\"]/gm)].map(\n    (match) => match[1],\n  );\n}\n\nfunction getPackageName(importPath: string) {\n  if (importPath.startsWith('@')) {\n    return importPath.split('/').slice(0, 2).join('/');\n  }\n\n  return importPath.split('/')[0];\n}\n"
  },
  {
    "path": "docs/src/components/Accordion.css",
    "content": "@layer components {\n  .AccordionRoot {\n    --color-trigger-hover: hsl(0deg 0 99);\n    --color-open-trigger-shadow: var(--color-gray-300);\n\n    @media (prefers-color-scheme: dark) {\n      --color-trigger-hover: hsl(0deg 0 9);\n      --color-open-trigger-shadow: hsl(0deg 0 5);\n    }\n\n    box-sizing: border-box;\n    display: flex;\n    flex-direction: column;\n    justify-content: center;\n    color: var(--color-gray);\n\n    border: 1px solid var(--color-gray-200);\n    border-collapse: separate;\n    border-radius: var(--radius-md);\n  }\n\n  .AccordionHeaderRow {\n    border-radius: var(--radius-md) var(--radius-md) 0 0;\n    border-bottom: 1px solid var(--color-gray-200);\n    background-color: var(--color-gray-50);\n  }\n\n  .AccordionHeaderCell {\n    padding: 0.5rem 0.75rem;\n    font-size: var(--text-sm);\n    font-weight: 700;\n    color: var(--color-foreground);\n  }\n\n  .AccordionItem:not(:last-child) {\n    border-bottom: 1px solid var(--color-gray-200);\n  }\n\n  .AccordionTrigger {\n    box-sizing: border-box;\n    position: relative;\n    display: flex;\n    width: 100%;\n    min-height: 2.5rem;\n    align-items: center;\n    /* use padding to match TableCell height in CSS vars or data attributes table */\n    padding-block: 0.5rem;\n    padding-inline: 0.75rem 0.5rem;\n    border: none;\n    outline: none;\n    text-align: left;\n    background-color: var(--color-content);\n\n    /* remove default disclosure triangle  */\n    &::-webkit-details-marker {\n      display: none;\n    }\n    list-style: none;\n\n    @media (hover: hover) {\n      &:hover {\n        background-color: var(--color-trigger-hover);\n        cursor: default;\n      }\n    }\n\n    &:focus-visible {\n      outline: 2px solid var(--color-blue);\n      z-index: 1;\n    }\n\n    [open] & {\n      box-shadow:\n        0 2px 4px -3px var(--color-open-trigger-shadow),\n        inset 0 -1px 1px transparent;\n    }\n\n    .AccordionItem:not([open]):last-child & {\n      border-bottom-left-radius: var(--radius-md);\n      border-bottom-right-radius: var(--radius-md);\n    }\n  }\n\n  .AccordionIcon {\n    box-sizing: border-box;\n    flex-shrink: 0;\n    width: 0.75rem;\n    height: 0.75rem;\n\n    [open] & {\n      transform: rotate(180deg);\n    }\n  }\n\n  .AccordionPanel {\n    box-sizing: border-box;\n    height: auto;\n    overflow: hidden;\n    color: var(--color-foreground);\n    font-size: 1rem;\n    line-height: 1.5rem;\n  }\n\n  .AccordionContent {\n    padding-block: 1px;\n\n    [open] & {\n      background-color: var(--color-gray-50);\n      box-shadow: inset 0 1px 0 0 var(--color-gray-100);\n    }\n\n    .AccordionItem:last-child & {\n      border-bottom-left-radius: var(--radius-md);\n      border-bottom-right-radius: var(--radius-md);\n    }\n  }\n\n  .AccordionScrollable {\n    overflow-x: hidden;\n\n    &[data-scrollable] {\n      position: relative;\n\n      /* Overscroll overlay */\n      &::after {\n        content: '';\n        position: absolute;\n        pointer-events: none;\n        top: 1px;\n        bottom: 1px;\n        right: -1px;\n        width: 1.25rem;\n        background-image: linear-gradient(to right, transparent, var(--scrollable-gradient-color));\n        animation: accordion-overscroll-overlay 500ms;\n      }\n\n      @media (hover: hover) {\n        .AccordionTrigger:hover &::after {\n          background-image: linear-gradient(to right, transparent, var(--color-trigger-hover));\n        }\n      }\n    }\n  }\n\n  @keyframes accordion-overscroll-overlay {\n    from {\n      opacity: 0;\n    }\n  }\n\n  .AccordionScrollableInner {\n    /* Ensure consistent height of the cells */\n    display: flex;\n    align-items: center;\n    gap: 0.5rem;\n    min-height: 2.5rem;\n    padding-block: 0.5rem;\n    padding-right: 1rem;\n\n    /* Make individual cells scrollable */\n    overflow-x: auto;\n    overscroll-behavior-x: contain;\n    scrollbar-width: none;\n\n    &::-webkit-scrollbar {\n      display: none;\n    }\n  }\n\n  .AccordionScrollable:has(pre) .AccordionScrollableInner {\n    min-height: unset;\n    padding: 0;\n    gap: 0;\n    padding-right: 0.5rem;\n  }\n}\n"
  },
  {
    "path": "docs/src/components/Accordion.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport clsx from 'clsx';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useGoogleAnalytics } from 'docs/src/blocks/GoogleAnalyticsProvider';\nimport { observeScrollableInner } from '../utils/observeScrollableInner';\n\nconst ARROW_UP = 'ArrowUp';\nconst ARROW_DOWN = 'ArrowDown';\nconst ARROW_LEFT = 'ArrowLeft';\nconst ARROW_RIGHT = 'ArrowRight';\nconst HOME = 'Home';\nconst END = 'End';\n\nconst SUPPORTED_KEYS = new Set([ARROW_DOWN, ARROW_UP, ARROW_RIGHT, ARROW_LEFT, HOME, END]);\n\nconst AccordionContext = React.createContext<\n  { rootRef: React.RefObject<HTMLElement | null> } | undefined\n>(undefined);\n\nexport function Root(props: React.ComponentProps<'section'>) {\n  const rootRef = React.useRef<HTMLElement>(null);\n\n  const context = React.useMemo(() => ({ rootRef }), [rootRef]);\n\n  return (\n    <AccordionContext.Provider value={context}>\n      <section {...props} ref={rootRef} className={clsx('AccordionRoot', props.className)} />\n    </AccordionContext.Provider>\n  );\n}\n\nexport function Trigger({\n  index: thisIndex,\n  id,\n  ...props\n}: React.ComponentProps<'summary'> & {\n  index: number;\n}) {\n  const context = React.useContext(AccordionContext);\n\n  return (\n    <summary\n      {...props}\n      id={id}\n      className={clsx('AccordionTrigger', props.className)}\n      onKeyDown={(event) => {\n        if (!context || !SUPPORTED_KEYS.has(event.key)) {\n          return;\n        }\n\n        event.preventDefault();\n        event.stopPropagation();\n\n        const rootElement = context.rootRef?.current;\n        if (!rootElement) {\n          return;\n        }\n\n        const triggers = rootElement.querySelectorAll<HTMLElement>('summary');\n\n        let nextIndex = -1;\n        const lastIndex = triggers.length - 1;\n\n        switch (event.key) {\n          case ARROW_LEFT:\n          case ARROW_UP:\n            nextIndex = thisIndex === 0 ? lastIndex : thisIndex - 1;\n            break;\n          case ARROW_RIGHT:\n          case ARROW_DOWN:\n            nextIndex = thisIndex + 1 > lastIndex ? 0 : thisIndex + 1;\n            break;\n          case 'Home':\n            nextIndex = 0;\n            break;\n          case 'End':\n            nextIndex = lastIndex;\n            break;\n          default:\n            break;\n        }\n\n        if (nextIndex > -1) {\n          triggers.item(nextIndex).focus();\n        }\n\n        props.onKeyDown?.(event);\n      }}\n      onClick={(event) => {\n        const selection = window.getSelection();\n        if (!selection?.isCollapsed) {\n          event.preventDefault();\n        }\n        props.onClick?.(event);\n      }}\n      onMouseDown={(event) => {\n        if (!event.defaultPrevented && event.detail > 1) {\n          event.preventDefault();\n        }\n      }}\n    />\n  );\n}\n\nexport function Item({\n  gaCategory,\n  gaLabel,\n  gaParams,\n  ...props\n}: React.ComponentProps<'details'> & {\n  gaCategory?: string;\n  gaLabel?: string;\n  gaParams?: Record<string, string | number | boolean>;\n}) {\n  const [open, setOpen] = React.useState<boolean>(false);\n  const ga = useGoogleAnalytics();\n  // in Chrome, the <details> opens automatically when the hash part of a URL\n  // matches the `id` on <summary> but needs to be manually handled for Safari\n  // and Firefox\n  const handleRef = useStableCallback((element: HTMLDetailsElement | null) => {\n    if (element) {\n      const trigger = element.querySelector<HTMLElement>('summary');\n      const triggerId = trigger?.getAttribute('id');\n      const hash = window.location.hash.slice(1);\n\n      if (triggerId && hash && triggerId === hash) {\n        setOpen(true);\n      }\n    }\n  });\n\n  return (\n    <details\n      {...props}\n      ref={handleRef}\n      open={open || undefined}\n      className={clsx('AccordionItem', props.className)}\n      onToggle={(event) => {\n        props.onToggle?.(event);\n        setOpen(event.currentTarget.open);\n        if (gaCategory && event.currentTarget.open && event.nativeEvent.isTrusted) {\n          ga?.trackEvent({\n            category: gaCategory,\n            action: 'expand',\n            label: gaLabel,\n            params: gaParams,\n          });\n        }\n      }}\n    />\n  );\n}\n\nexport function Panel(props: React.ComponentProps<'div'>) {\n  return <div {...props} className={clsx('AccordionPanel', props.className)} />;\n}\n\nexport function Content(props: React.ComponentProps<'div'>) {\n  return <div {...props} className={clsx('AccordionContent', props.className)} />;\n}\n\n/* Scroll container with an overscroll overlay graphic for overflowing content inside the trigger */\nexport function Scrollable({\n  children,\n  className,\n  tag: Tag = 'span',\n  gradientColor = 'var(--color-content)',\n  ...props\n}: React.ComponentProps<'span'> & {\n  gradientColor?: string;\n  tag?: React.ElementType;\n}) {\n  return (\n    <Tag\n      ref={observeScrollableInner}\n      className={clsx('AccordionScrollable', className)}\n      style={{ '--scrollable-gradient-color': gradientColor } as React.CSSProperties}\n      {...props}\n    >\n      <Tag className=\"AccordionScrollableInner\">{children}</Tag>\n    </Tag>\n  );\n}\n\n/* Fake <tr> */\nexport function HeaderRow(props: React.ComponentProps<'div'>) {\n  return <div {...props} aria-hidden className={clsx('AccordionHeaderRow', props.className)} />;\n}\n\n/* Fake <th scope=\"col\"> */\nexport function HeaderCell(props: React.ComponentProps<'div'>) {\n  return <div {...props} className={clsx('AccordionHeaderCell', props.className)} />;\n}\n"
  },
  {
    "path": "docs/src/components/Code.css",
    "content": "@layer components {\n  .Code {\n    letter-spacing: normal;\n\n    /* In a code block */\n    pre & {\n      white-space: normal;\n    }\n\n    & [data-line] {\n      display: block;\n      white-space: pre;\n      padding-inline: 0.75rem;\n    }\n\n    & [data-line]:empty {\n      height: 1lh;\n    }\n\n    & mark {\n      display: inline-block;\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/components/Code.tsx",
    "content": "import clsx from 'clsx';\nimport * as React from 'react';\n\nexport function Code(props: React.ComponentProps<'code'>) {\n  return <code {...props} className={clsx('Code', props.className)} />;\n}\n"
  },
  {
    "path": "docs/src/components/CodeBlock.css",
    "content": "@layer components {\n  .CodeBlockRoot {\n    align-self: stretch;\n    background-color: var(--color-content);\n    border: 1px solid var(--color-gray-200);\n    border-radius: var(--radius-md);\n    margin-top: calc(var(--spacing) * 5);\n    margin-bottom: calc(var(--spacing) * 6);\n  }\n\n  .CodeBlockPanel {\n    font-size: var(--text-xs);\n    line-height: 1;\n    background-color: var(--color-gray-50);\n    background-clip: padding-box;\n    color: var(--color-gray);\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n    white-space: nowrap;\n    padding: 0 0.75rem;\n    height: 2.25rem;\n    gap: 1rem;\n    border-top-left-radius: inherit;\n    border-top-right-radius: inherit;\n    border-bottom: 1px solid var(--color-gray-200);\n\n    /* Scroll */\n    overflow: auto hidden;\n    overscroll-behavior-x: contain;\n    scrollbar-width: none;\n\n    &::-webkit-scrollbar {\n      display: none;\n    }\n\n    /* Scroll containers may be focusable */\n    &:focus-visible {\n      position: relative;\n      outline: 2px solid var(--color-blue);\n      outline-offset: -1px;\n      z-index: 1;\n    }\n  }\n\n  .CodeBlockPanelTitle {\n    font-size: var(--text-sm);\n    line-height: var(--text-sm--line-height);\n    color: var(--color-gray-700);\n    font-weight: 400;\n  }\n\n  .CodeBlockCopyIcon {\n    display: flex;\n    width: 14px;\n    height: 14px;\n    align-items: center;\n    justify-content: center;\n  }\n\n  .CodeBlockPre {\n    font-size: var(--text-xs);\n    line-height: var(--text-xs--line-height);\n    letter-spacing: var(--text-xs--letter-spacing);\n    cursor: text;\n    color: var(--color-foreground);\n    background-color: var(--color-content);\n    padding: 0.5rem 0;\n    border-radius: var(--radius-md);\n    outline: 0;\n\n    /* Scroll */\n    display: flex;\n    overflow: auto;\n    /* Prevent Chrome/Safari page navigation gestures when scrolling horizontally */\n    overscroll-behavior-x: contain;\n\n    & code {\n      /* Different fonts may introduce vertical align issues */\n      display: block;\n      /* Make sure selection highlight spans full container width in Safari */\n      flex-grow: 1;\n    }\n  }\n\n  .CodeBlockPreContainer {\n    outline: 0;\n  }\n}\n"
  },
  {
    "path": "docs/src/components/CodeBlock.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport copy from 'clipboard-copy';\nimport clsx from 'clsx';\nimport { usePathname } from 'next/navigation';\nimport { useGoogleAnalytics } from 'docs/src/blocks/GoogleAnalyticsProvider';\nimport * as ScrollArea from './ScrollArea';\nimport { CopyIcon } from '../icons/CopyIcon';\nimport { CheckIcon } from '../icons/CheckIcon';\nimport { GhostButton } from './GhostButton';\nimport './CodeBlock.css';\n\nconst CodeBlockContext = React.createContext({ codeId: '', titleId: '' });\n\nexport function Root(props: React.ComponentPropsWithoutRef<'div'>) {\n  const titleId = React.useId();\n  const codeId = React.useId();\n  const context = React.useMemo(() => ({ codeId, titleId }), [codeId, titleId]);\n  return (\n    <CodeBlockContext.Provider value={context}>\n      <div\n        role=\"figure\"\n        aria-labelledby={titleId}\n        {...props}\n        className={clsx('CodeBlockRoot', props.className)}\n      />\n    </CodeBlockContext.Provider>\n  );\n}\n\nexport function Panel({ className, children, ...other }: React.ComponentPropsWithoutRef<'div'>) {\n  const { codeId, titleId } = React.useContext(CodeBlockContext);\n  const [copyTimeout, setCopyTimeout] = React.useState<number>(0);\n  const ga = useGoogleAnalytics();\n  const pathname = usePathname();\n\n  return (\n    <div className={clsx('CodeBlockPanel', className)} {...other}>\n      <div id={titleId} className=\"CodeBlockPanelTitle\">\n        {children}\n      </div>\n      <GhostButton\n        aria-label=\"Copy code\"\n        onClick={async () => {\n          const code = document.getElementById(codeId)?.textContent;\n\n          if (code) {\n            await copy(code);\n            const title = document.getElementById(titleId)?.textContent ?? undefined;\n            const codeBlockId = title ? `${pathname}#${title}` : pathname;\n            ga?.trackEvent({\n              category: 'code_block',\n              action: 'copy',\n              label: codeBlockId,\n              params: { copy: codeBlockId, slug: title || '' },\n            });\n            /* eslint-disable no-restricted-syntax */\n            const newTimeout = window.setTimeout(() => {\n              window.clearTimeout(newTimeout);\n              setCopyTimeout(0);\n            }, 2000);\n            window.clearTimeout(copyTimeout);\n            setCopyTimeout(newTimeout);\n            /* eslint-enable no-restricted-syntax */\n          }\n        }}\n      >\n        Copy\n        <span className=\"CodeBlockCopyIcon\">{copyTimeout ? <CheckIcon /> : <CopyIcon />}</span>\n      </GhostButton>\n    </div>\n  );\n}\n\nexport function Pre(props: React.ComponentProps<'pre'>) {\n  const { codeId } = React.useContext(CodeBlockContext);\n  return (\n    <ScrollArea.Root\n      // Select code block contents on Ctrl/Cmd + A\n      tabIndex={-1}\n      className=\"CodeBlockPreContainer\"\n      onKeyDown={(event) => {\n        if (\n          (event.ctrlKey || event.metaKey) &&\n          String.fromCharCode(event.keyCode) === 'A' &&\n          !event.shiftKey &&\n          !event.altKey\n        ) {\n          event.preventDefault();\n          window.getSelection()?.selectAllChildren(event.currentTarget);\n        }\n      }}\n    >\n      <ScrollArea.Viewport\n        style={{ overflow: undefined }}\n        render={<pre {...props} id={codeId} className={clsx('CodeBlockPre', props.className)} />}\n      />\n      <ScrollArea.Scrollbar orientation=\"horizontal\" />\n    </ScrollArea.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/components/Demo/CodeHighlighting.css",
    "content": "/* Syntax highlighting theme using prettylights with global color variables.\n * Based on prettylights classes with extensions for Base UI docs.\n * See styles.css for global theme color definitions. */\n@layer theme {\n  :root {\n    /* Prettylights syntax colors mapped to global theme */\n    --color-prettylights-syntax-comment: var(--color-gray);\n    --color-prettylights-syntax-constant: var(--color-blue);\n    --color-prettylights-syntax-constant-other-reference-link: var(--color-navy);\n    --color-prettylights-syntax-entity: var(--color-violet);\n    --color-prettylights-syntax-entity-tag: var(--color-green);\n    --color-prettylights-syntax-keyword: var(--color-red);\n    --color-prettylights-syntax-string: var(--color-navy);\n    --color-prettylights-syntax-string-regexp: var(--color-green);\n    --color-prettylights-syntax-variable: var(--color-red);\n    --color-prettylights-syntax-storage-modifier-import: var(--color-navy);\n    --color-prettylights-syntax-brackethighlighter-angle: var(--color-gray);\n    --color-prettylights-syntax-brackethighlighter-unmatched: var(--color-red);\n    --color-prettylights-syntax-sublimelinter-gutter-mark: var(--color-gray);\n\n    /* Invalid/error states */\n    --color-prettylights-syntax-invalid-illegal-bg: var(--color-red);\n    --color-prettylights-syntax-invalid-illegal-text: var(--color-content);\n    --color-prettylights-syntax-carriage-return-bg: var(--color-red);\n    --color-prettylights-syntax-carriage-return-text: var(--color-content);\n\n    /* Markup/markdown (unused in code demos but included for completeness) */\n    --color-prettylights-syntax-markup-heading: var(--color-blue);\n    --color-prettylights-syntax-markup-bold: var(--color-foreground);\n    --color-prettylights-syntax-markup-italic: var(--color-foreground);\n    --color-prettylights-syntax-markup-list: var(--color-red);\n    --color-prettylights-syntax-markup-changed-bg: transparent;\n    --color-prettylights-syntax-markup-changed-text: var(--color-red);\n    --color-prettylights-syntax-markup-deleted-bg: transparent;\n    --color-prettylights-syntax-markup-deleted-text: var(--color-red);\n    --color-prettylights-syntax-markup-inserted-bg: transparent;\n    --color-prettylights-syntax-markup-inserted-text: var(--color-green);\n    --color-prettylights-syntax-markup-ignored-bg: transparent;\n    --color-prettylights-syntax-markup-ignored-text: var(--color-gray);\n    --color-prettylights-syntax-meta-diff-range: var(--color-violet);\n\n    /* Docs-infra extensions for concepts not in prettylights */\n    --color-docs-infra-syntax-nullish: var(--color-gray-500);\n    --color-docs-infra-syntax-other: var(--color-foreground);\n    --color-docs-infra-syntax-bracket-tag: var(--color-gray);\n    --color-docs-infra-syntax-bracket-curly: var(--color-gray);\n    --color-docs-infra-syntax-bracket-round: var(--color-gray);\n    --color-docs-infra-syntax-bracket-square: var(--color-gray);\n    --color-docs-infra-syntax-bracket-angle: var(--color-gray);\n    --color-docs-infra-syntax-bracket-quote: var(--color-gray);\n  }\n}\n\n@layer components {\n  .pl-c {\n    color: var(--color-prettylights-syntax-comment);\n  }\n\n  .pl-c1,\n  .pl-s .pl-v {\n    color: var(--color-prettylights-syntax-constant);\n  }\n\n  .pl-e,\n  .pl-en {\n    color: var(--color-prettylights-syntax-entity);\n  }\n\n  .pl-smi,\n  .pl-s .pl-s1 {\n    color: var(--color-prettylights-syntax-storage-modifier-import);\n  }\n\n  .pl-ent {\n    color: var(--color-prettylights-syntax-entity-tag);\n  }\n\n  .pl-k {\n    color: var(--color-prettylights-syntax-keyword);\n  }\n\n  .pl-s,\n  .pl-pds,\n  .pl-s .pl-pse .pl-s1,\n  .pl-sr,\n  .pl-sr .pl-cce,\n  .pl-sr .pl-sre,\n  .pl-sr .pl-sra {\n    color: var(--color-prettylights-syntax-string);\n  }\n\n  .pl-v,\n  .pl-smw {\n    color: var(--color-prettylights-syntax-variable);\n  }\n\n  .pl-bu {\n    color: var(--color-prettylights-syntax-brackethighlighter-unmatched);\n  }\n\n  .pl-ii {\n    color: var(--color-prettylights-syntax-invalid-illegal-text);\n    background-color: var(--color-prettylights-syntax-invalid-illegal-bg);\n  }\n\n  .pl-c2 {\n    color: var(--color-prettylights-syntax-carriage-return-text);\n    background-color: var(--color-prettylights-syntax-carriage-return-bg);\n  }\n\n  .pl-sr .pl-cce {\n    font-weight: bold;\n    color: var(--color-prettylights-syntax-string-regexp);\n  }\n\n  .pl-ml {\n    color: var(--color-prettylights-syntax-markup-list);\n  }\n\n  .pl-mh,\n  .pl-mh .pl-en,\n  .pl-ms {\n    font-weight: bold;\n    color: var(--color-prettylights-syntax-markup-heading);\n  }\n\n  .pl-mi {\n    font-style: italic;\n    color: var(--color-prettylights-syntax-markup-italic);\n  }\n\n  .pl-mb {\n    font-weight: bold;\n    color: var(--color-prettylights-syntax-markup-bold);\n  }\n\n  .pl-md {\n    color: var(--color-prettylights-syntax-markup-deleted-text);\n    background-color: var(--color-prettylights-syntax-markup-deleted-bg);\n  }\n\n  .pl-mi1 {\n    color: var(--color-prettylights-syntax-markup-inserted-text);\n    background-color: var(--color-prettylights-syntax-markup-inserted-bg);\n  }\n\n  .pl-mc {\n    color: var(--color-prettylights-syntax-markup-changed-text);\n    background-color: var(--color-prettylights-syntax-markup-changed-bg);\n  }\n\n  .pl-mi2 {\n    color: var(--color-prettylights-syntax-markup-ignored-text);\n    background-color: var(--color-prettylights-syntax-markup-ignored-bg);\n  }\n\n  .pl-mdr {\n    font-weight: bold;\n    color: var(--color-prettylights-syntax-meta-diff-range);\n  }\n\n  .pl-ba {\n    color: var(--color-prettylights-syntax-brackethighlighter-angle);\n  }\n\n  .pl-sg {\n    color: var(--color-prettylights-syntax-sublimelinter-gutter-mark);\n  }\n\n  .pl-corl {\n    text-decoration: underline;\n    color: var(--color-prettylights-syntax-constant-other-reference-link);\n  }\n\n  /* Docs-infra extensions (.di-* classes) */\n\n  /* Nullish values (null, undefined) */\n  .di-n {\n    color: var(--color-docs-infra-syntax-nullish);\n  }\n\n  /* Default text color */\n  .di-d {\n    color: var(--color-foreground);\n  }\n\n  /* Parameter highlighting */\n  .di-p {\n    color: var(--color-prettylights-syntax-storage-modifier-import);\n  }\n\n  /* Misc/other markup styling */\n  .di-o {\n    color: var(--color-docs-infra-syntax-other);\n  }\n\n  /* Bracket types */\n  .di-bt {\n    color: var(--color-docs-infra-syntax-bracket-tag);\n  }\n\n  .di-bc {\n    color: var(--color-docs-infra-syntax-bracket-curly);\n  }\n\n  .di-br {\n    color: var(--color-docs-infra-syntax-bracket-round);\n  }\n\n  .di-bs {\n    color: var(--color-docs-infra-syntax-bracket-square);\n  }\n\n  .di-ba {\n    color: var(--color-docs-infra-syntax-bracket-angle);\n  }\n\n  .di-bq {\n    color: var(--color-docs-infra-syntax-bracket-quote);\n  }\n\n  /* Diff additions */\n  .di-ins {\n    color: var(--color-prettylights-syntax-markup-inserted-text);\n    background-color: var(--color-prettylights-syntax-markup-inserted-bg);\n  }\n\n  /* Diff deletions */\n  .di-del {\n    color: var(--color-prettylights-syntax-markup-deleted-text);\n    background-color: var(--color-prettylights-syntax-markup-deleted-bg);\n  }\n\n  /* Diff changes */\n  .di-chg {\n    color: var(--color-prettylights-syntax-markup-changed-text);\n    background-color: var(--color-prettylights-syntax-markup-changed-bg);\n  }\n\n  /* Diff ignored/untracked */\n  .di-ign {\n    color: var(--color-prettylights-syntax-markup-ignored-text);\n    background-color: var(--color-prettylights-syntax-markup-ignored-bg);\n  }\n\n  /* Diff range indicator */\n  .di-rng {\n    font-weight: bold;\n    color: var(--color-prettylights-syntax-meta-diff-range);\n  }\n\n  /* Special handling for CSS language */\n  [data-language='css'] {\n    --color-prettylights-syntax-variable: var(--color-navy);\n  }\n\n  /* Target CSS property names in CSS code blocks\n   Property names are .pl-c1 elements that are followed by a text node containing ':'\n   This distinguishes them from function names like 'var' which are followed by '(' */\n  [data-language='css'] .line > .pl-c1:first-of-type {\n    color: var(--color-foreground);\n  }\n\n  /* Inline code and highlighted characters - collapse most colors to blue */\n  [data-inline],\n  [data-highlighted-chars] {\n    --color-prettylights-syntax-comment: var(--color-blue);\n    --color-prettylights-syntax-constant: var(--color-blue);\n    --color-prettylights-syntax-entity: var(--color-blue);\n    --color-prettylights-syntax-keyword: var(--color-blue);\n    --color-prettylights-syntax-string: var(--color-blue);\n    --color-prettylights-syntax-variable: var(--color-blue);\n    --color-prettylights-syntax-storage-modifier-import: var(--color-blue);\n    --color-docs-infra-syntax-nullish: var(--color-blue);\n  }\n\n  /* Table code - recover some syntax highlighting */\n  [data-table-code] {\n    --color-prettylights-syntax-entity: var(--color-violet);\n    --color-prettylights-syntax-keyword: var(--color-red);\n    --color-prettylights-syntax-string: var(--color-navy);\n    --color-docs-infra-syntax-nullish: var(--color-gray-500);\n  }\n\n  /* Line highlighting */\n  pre [data-hl] {\n    background-color: var(--color-line-highlight);\n  }\n\n  pre [data-hl='strong'] {\n    background-color: var(--color-line-highlight-strong);\n  }\n\n  /* Character/word highlighting */\n  pre [data-hlc] {\n    color: var(--color-blue);\n    background-color: var(--color-inline-highlight);\n  }\n}\n"
  },
  {
    "path": "docs/src/components/Demo/Demo.css",
    "content": "@import 'docs/src/css/custom-media.css';\n\n@layer components {\n  .DemoRoot {\n    background-color: var(--color-content);\n    border: 1px solid var(--color-gray-200);\n    border-radius: var(--radius-md);\n    margin-bottom: calc(var(--spacing) * 6);\n  }\n\n  .DemoPlaygroundExternalLink {\n    position: absolute;\n    top: 0.75rem;\n    right: 1.125rem;\n  }\n\n  .DemoCopyIconWrap {\n    display: flex;\n    width: 0.875rem;\n    height: 0.875rem;\n    align-items: center;\n    justify-content: center;\n  }\n\n  .DemoPlayground {\n    position: relative;\n\n    background-color: var(--color-content);\n    border-top-left-radius: var(--radius-md);\n    border-top-right-radius: var(--radius-md);\n\n    /* Scroll */\n    overflow: auto hidden;\n    overscroll-behavior-x: contain;\n    scrollbar-width: thin;\n\n    /* Scroll containers may be focusable */\n    &:focus-visible {\n      outline: 2px solid var(--color-blue);\n      outline-offset: -1px;\n      z-index: 1;\n    }\n  }\n\n  /* A separate element for the padding to work when there is horizontal scroll */\n  .DemoPlaygroundInner {\n    padding: 2rem 1.5rem;\n    min-height: 8rem;\n\n    /* Make sure contents aren't squashed smaller than their natural width */\n    min-width: fit-content;\n\n    /* Center the contents vertically */\n    display: flex;\n    justify-content: center;\n    align-items: center;\n\n    @media (--xs) {\n      padding: 3rem 1.5rem;\n      min-height: 11.25rem; /* Match .DemoCodeBlockRoot pre min-height */\n    }\n  }\n\n  .DemoToolbar {\n    font-size: var(--text-xs);\n    line-height: var(--text-xs--line-height);\n    white-space: nowrap;\n    color: var(--color-gray);\n    background-color: var(--color-gray-50);\n    background-clip: padding-box;\n    border-block: 1px solid var(--color-gray-200);\n    display: flex;\n    align-items: center;\n    gap: 2rem;\n    height: 2.25rem;\n    padding: 0;\n    padding-left: 0.75rem;\n    user-select: none;\n\n    /* Scroll */\n    overflow: auto hidden;\n    overscroll-behavior-x: contain;\n    scrollbar-width: none;\n\n    &::-webkit-scrollbar {\n      display: none;\n    }\n\n    /* Scroll containers may be focusable */\n    &:focus-visible {\n      position: relative;\n      outline: 2px solid var(--color-blue);\n      outline-offset: -1px;\n      z-index: 1;\n    }\n  }\n\n  .DemoToolbarActions {\n    position: relative;\n    /* Apply sticky only on wider viewports to avoid actions taking up most of it */\n    @media (min-width: 48rem) {\n      position: sticky;\n      padding-left: 1rem;\n    }\n    right: 0;\n    display: flex;\n    align-items: center;\n    gap: 1rem;\n    margin-left: auto;\n    padding-right: 0.75rem;\n    background-color: var(--color-gray-50);\n    height: 100%;\n\n    &::before {\n      content: '';\n      position: absolute;\n      top: 0;\n      left: -24px;\n      width: 24px;\n      height: 100%;\n      background: linear-gradient(to right, transparent, var(--color-gray-50));\n      pointer-events: none;\n    }\n  }\n\n  .DemoFilename {\n    font-family: var(--font-mono);\n    color: var(--color-foreground);\n    font-weight: bold;\n    user-select: text;\n  }\n\n  .DemoFilename:hover {\n    text-decoration: underline;\n  }\n\n  .DemoTabsList {\n    display: flex;\n    gap: 1rem;\n  }\n\n  .DemoTab {\n    font-family: var(--font-mono);\n    cursor: default;\n    position: relative;\n    z-index: 0;\n    outline: 0;\n\n    @media (hover: hover) {\n      &:hover {\n        &::before {\n          background-color: var(--color-gray-100);\n        }\n      }\n    }\n\n    &::before,\n    &::after {\n      content: '';\n      position: absolute;\n      z-index: -1;\n    }\n\n    &::before {\n      inset: -0.125rem -0.375rem;\n      border-radius: var(--radius-sm);\n    }\n\n    /* Increase the clickable size  */\n    &::after {\n      inset: -0.375rem -0.5rem;\n    }\n\n    &[data-active] {\n      color: var(--color-foreground);\n      font-weight: bold;\n\n      &::before {\n        background-color: white;\n        outline: 1px solid var(--color-gray-300);\n        outline-offset: -1px;\n        box-shadow:\n          0 2px 3px -2px var(--color-gray-300),\n          inset 0 -1px 1px var(--color-gray-200);\n      }\n\n      @media (prefers-color-scheme: dark) {\n        &::before {\n          outline: none;\n          background-color: var(--color-gray-50);\n          box-shadow:\n            0 0 3px 0 var(--color-gray-300),\n            inset 0 0 0 1px var(--color-gray-400);\n        }\n      }\n    }\n\n    &:focus-visible::before {\n      outline: 2px solid var(--color-blue);\n      outline-offset: -1px;\n    }\n  }\n\n  .DemoCodeBlockRoot {\n    display: flex;\n    flex-direction: column;\n    position: relative;\n    outline: 0;\n\n    &[data-closed] {\n      &::before {\n        content: '';\n        position: absolute;\n        pointer-events: none;\n        height: 7.5rem;\n        bottom: 0;\n        left: 0;\n        right: 0;\n        background: linear-gradient(to bottom, rgb(255 255 255 / 0), rgb(255 255 255 / 60%));\n      }\n\n      @media (prefers-color-scheme: dark) {\n        &::before {\n          background: linear-gradient(to bottom, rgb(0 0 0 / 0), rgb(0 0 0 / 60%));\n        }\n      }\n    }\n  }\n\n  .DemoCodeBlockViewport {\n    /* Prevent Chrome/Safari page navigation gestures when scrolling horizontally */\n    overscroll-behavior-x: contain;\n\n    /* Max height for code blocks, if we ever want to recover this (https://github.com/mui/base-ui/pull/1187) */\n    /* max-height: clamp(8.75lh + 0.5rem, 80vh, 23.75lh + 0.5rem); */\n\n    &[data-closed] {\n      overflow: hidden;\n      max-height: calc(8.75lh + 0.5rem); /* Show almost 9 code lines plus top padding */\n    }\n  }\n\n  .DemoSourceBrowser {\n    font-size: var(--text-xs);\n    line-height: var(--text-xs--line-height);\n    letter-spacing: var(--text-xs--letter-spacing);\n    cursor: text;\n    display: flex;\n\n    & pre {\n      /* Different fonts may introduce vertical align issues */\n      display: flex;\n      /* Make sure selection highlight spans full container width in Safari */\n      flex-grow: 1;\n    }\n\n    & code {\n      padding: 0.5rem 0;\n    }\n\n    & code .frame {\n      display: block;\n      padding-left: 0.75rem;\n      padding-right: 0.75rem;\n    }\n\n    & code .frame[data-lined] {\n      display: block;\n      white-space: normal;\n      padding-left: 0;\n      padding-right: 0;\n    }\n\n    * code .frame[data-lined] .line {\n      display: block;\n      white-space: pre;\n      padding-left: 0.75rem;\n      padding-right: 0.75rem;\n    }\n  }\n\n  .DemoCollapseButton {\n    font-size: var(--text-xs);\n    line-height: var(--text-xs--line-height);\n    background-color: var(--color-gray-50);\n    cursor: default;\n    width: 100%;\n    color: var(--color-gray);\n    height: 2.25rem;\n    border-top: 1px solid var(--color-gray-200);\n    border-bottom-left-radius: 0.375rem;\n    border-bottom-right-radius: 0.375rem;\n    border-bottom: 1px solid transparent;\n    background-clip: padding-box;\n    margin-bottom: -1px;\n    user-select: none;\n\n    &[data-sticky] {\n      position: sticky;\n      bottom: -1px;\n      z-index: 1;\n    }\n\n    @media (hover: hover) {\n      &:hover {\n        background-color: var(--color-gray-75);\n      }\n    }\n\n    &:active {\n      background-color: color-mix(in oklch, var(--color-gray-75), var(--color-gray-600) 5%);\n    }\n\n    &:focus-visible {\n      outline: 2px solid var(--color-blue);\n      outline-offset: -1px;\n      z-index: 1;\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/components/Demo/Demo.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport { Collapsible } from '@base-ui/react/collapsible';\nimport * as Menu from 'docs/src/components/Menu';\nimport { usePathname } from 'next/navigation';\nimport type { ContentProps } from '@mui/internal-docs-infra/CodeHighlighter/types';\nimport { useDemo } from '@mui/internal-docs-infra/useDemo';\nimport { CopyIcon } from 'docs/src/icons/CopyIcon';\nimport copy from 'clipboard-copy';\nimport clsx from 'clsx';\nimport kebabCase from 'es-toolkit/compat/kebabCase';\nimport { CheckIcon } from 'docs/src/icons/CheckIcon';\nimport { ExternalLinkIcon } from 'docs/src/icons/ExternalLinkIcon';\nimport { GitHubIcon } from 'docs/src/icons/GitHubIcon';\nimport { MoreVertIcon } from 'docs/src/icons/MoreVertIcon';\nimport { exportCodeSandbox, exportOpts } from 'docs/src/utils/demoExportOptions';\nimport { getGitHubDemoUrl } from 'docs/src/utils/getGitHubDemoUrl';\nimport { isSafari, isEdge } from '@base-ui/utils/detectBrowser';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useTimeout } from '@base-ui/utils/useTimeout';\nimport { useGoogleAnalytics } from 'docs/src/blocks/GoogleAnalyticsProvider';\nimport { DemoVariantSelector } from './DemoVariantSelector';\nimport { DemoFileSelector } from './DemoFileSelector';\nimport { DemoCodeBlock } from './DemoCodeBlock';\nimport { GhostButton } from '../GhostButton';\nimport { DemoPlayground } from './DemoPlayground';\nimport './Demo.css';\n\nexport type DemoProps = ContentProps<{\n  defaultOpen?: boolean;\n  compact?: boolean;\n  className?: string;\n  showExtraPlaygroundLink?: boolean;\n}>;\n\nexport function Demo({\n  defaultOpen = false,\n  compact = false,\n  showExtraPlaygroundLink = false,\n  className,\n  ...demoProps\n}: DemoProps) {\n  const collapsibleTriggerRef = React.useRef<HTMLButtonElement>(null);\n  const [copyTimeout, setCopyTimeout] = React.useState<number>(0);\n  const [sourceLinkCopied, setSourceLinkCopied] = React.useState(false);\n  const sourceLinkCopyResetTimeout = useTimeout();\n  const ga = useGoogleAnalytics();\n  const pathname = usePathname();\n  const demoSlug = React.useMemo(\n    () => demoProps.slug || (demoProps.name ? kebabCase(demoProps.name) : undefined),\n    [demoProps.slug, demoProps.name],\n  );\n  const demoId = demoSlug ? `${pathname}#${demoSlug}` : pathname;\n  const hasLoggedInteraction = React.useRef(false);\n\n  const onPlaygroundInteraction = React.useCallback(() => {\n    if (!hasLoggedInteraction.current) {\n      hasLoggedInteraction.current = true;\n      ga?.trackEvent({\n        category: 'demo',\n        action: 'interaction',\n        label: demoId,\n        params: { interaction: demoId, slug: demoSlug || '' },\n      });\n    }\n  }, [ga, demoId, demoSlug]);\n\n  const onCopied = React.useCallback(() => {\n    ga?.trackEvent({\n      category: 'demo',\n      action: 'copy',\n      label: demoId,\n      params: { copy: demoId, slug: demoSlug || '' },\n    });\n\n    /* eslint-disable no-restricted-syntax */\n    const newTimeout = window.setTimeout(() => {\n      window.clearTimeout(newTimeout);\n      setCopyTimeout(0);\n    }, 2000);\n    window.clearTimeout(copyTimeout);\n    setCopyTimeout(newTimeout);\n    /* eslint-enable no-restricted-syntax */\n  }, [copyTimeout, ga, demoId, demoSlug]);\n\n  const demo = useDemo(demoProps, {\n    copy: { onCopied },\n    defaultOpen,\n    export: exportOpts,\n    exportCodeSandbox,\n  });\n\n  const githubUrl = getGitHubDemoUrl(demoProps.url, demo.selectedVariant);\n\n  const onViewSource = useStableCallback(() => {\n    ga?.trackEvent({\n      category: 'demo',\n      action: 'open_github',\n      label: demoId,\n      params: { github: demoId, slug: demoSlug || '' },\n    });\n  });\n\n  const onCopySourceLink = useStableCallback(async () => {\n    if (!githubUrl) {\n      return;\n    }\n    await copy(githubUrl);\n    setSourceLinkCopied(true);\n\n    ga?.trackEvent({\n      category: 'demo',\n      action: 'copy_source_link',\n      label: demoId,\n      params: { copy_source_link: demoId, slug: demoSlug || '' },\n    });\n\n    sourceLinkCopyResetTimeout.start(2000, () => setSourceLinkCopied(false));\n  });\n\n  const onOpenChange = useStableCallback((nextOpen: boolean) => {\n    const action = nextOpen ? 'expand' : 'collapse';\n    ga?.trackEvent({\n      category: 'demo',\n      action,\n      label: demoId,\n      params: { [action]: demoId, slug: demoSlug || '' },\n    });\n\n    if (!nextOpen && collapsibleTriggerRef.current != null) {\n      const triggerEl = collapsibleTriggerRef.current;\n      const rectTopBeforeClose = triggerEl.getBoundingClientRect().top;\n\n      ReactDOM.flushSync(() => demo.setExpanded(nextOpen));\n\n      const rectTopAfterClose = triggerEl.getBoundingClientRect().top;\n      const delta = rectTopAfterClose - rectTopBeforeClose;\n      // don't scroll if the trigger is still in the viewport after closing\n      if (rectTopAfterClose < 0) {\n        window.scrollBy({\n          top: delta,\n          behavior: 'instant',\n        });\n      }\n      return;\n    }\n\n    demo.setExpanded(nextOpen);\n  });\n\n  const onSelectFile = useStableCallback((fileName: string) => {\n    ga?.trackEvent({\n      category: 'demo',\n      action: 'file_select',\n      label: demoId,\n      params: { file_select: fileName, slug: demoSlug || '' },\n    });\n    demo.selectFileName(fileName);\n  });\n\n  const [fallbackToCodeSandbox, setFallbackToCodeSandbox] = React.useState(false);\n  React.useEffect(() => {\n    if (isSafari || isEdge) {\n      setFallbackToCodeSandbox(true);\n    }\n  }, []);\n\n  const externalPlaygroundLink = fallbackToCodeSandbox ? (\n    <GhostButton aria-label=\"Open in CodeSandbox\" type=\"button\" onClick={demo.openCodeSandbox}>\n      CodeSandbox\n      <ExternalLinkIcon />\n    </GhostButton>\n  ) : (\n    <GhostButton aria-label=\"Open in StackBlitz\" type=\"button\" onClick={demo.openStackBlitz}>\n      StackBlitz\n      <ExternalLinkIcon />\n    </GhostButton>\n  );\n\n  return (\n    <div className={clsx('DemoRoot', className)}>\n      {demo.allFilesSlugs.map(({ slug }) => (\n        <span key={slug} id={slug} className=\"bui-scroll-mt-4\" />\n      ))}\n      <div onPointerDown={onPlaygroundInteraction} onKeyDownCapture={onPlaygroundInteraction}>\n        <DemoPlayground component={demo.component} variant={demo.selectedVariant}>\n          {showExtraPlaygroundLink && (\n            <span className=\"DemoPlaygroundExternalLink\">{externalPlaygroundLink}</span>\n          )}\n        </DemoPlayground>\n      </div>\n      <Collapsible.Root open={demo.expanded} onOpenChange={onOpenChange}>\n        <div role=\"figure\" aria-label=\"Component demo code\">\n          {(compact ? demo.expanded : true) && (\n            <div className=\"DemoToolbar\">\n              <DemoFileSelector\n                files={demo.files}\n                selectedFileName={demo.selectedFileName}\n                selectFileName={onSelectFile}\n                onTabChange={demo.expand}\n              />\n\n              <div className=\"DemoToolbarActions\">\n                <DemoVariantSelector\n                  className=\"contents\"\n                  onVariantChange={demo.expand}\n                  variants={demo.variants}\n                  selectedVariant={demo.selectedVariant}\n                  selectVariant={demo.selectVariant as any}\n                  availableTransforms={demo.availableTransforms}\n                  selectedTransform={demo.selectedTransform}\n                  selectTransform={demo.selectTransform}\n                />\n                {externalPlaygroundLink}\n                <GhostButton aria-label=\"Copy code\" onClick={demo.copy}>\n                  Copy\n                  <span className=\"DemoCopyIconWrap\">\n                    {copyTimeout ? <CheckIcon /> : <CopyIcon />}\n                  </span>\n                </GhostButton>\n\n                {githubUrl && (\n                  <Menu.Root>\n                    <Menu.Trigger\n                      render={\n                        <GhostButton layout=\"icon\" aria-label=\"More actions\">\n                          <MoreVertIcon aria-hidden=\"true\" />\n                        </GhostButton>\n                      }\n                    />\n                    <Menu.Popup align=\"end\" alignOffset={-5}>\n                      <Menu.LinkItem\n                        href={githubUrl}\n                        target=\"_blank\"\n                        rel=\"noopener\"\n                        onClick={onViewSource}\n                      >\n                        <GitHubIcon aria-hidden=\"true\" height={14} width={14} />\n                        View source on GitHub\n                      </Menu.LinkItem>\n\n                      <Menu.Item closeOnClick={false} onClick={onCopySourceLink}>\n                        <span className=\"DemoCopyIconWrap\">\n                          {sourceLinkCopied ? (\n                            <CheckIcon aria-hidden=\"true\" />\n                          ) : (\n                            <CopyIcon aria-hidden=\"true\" />\n                          )}\n                        </span>\n                        Copy link to source\n                        <span className=\"sr-only\" aria-live=\"polite\">\n                          {sourceLinkCopied && 'Link copied!'}\n                        </span>\n                      </Menu.Item>\n                    </Menu.Popup>\n                  </Menu.Root>\n                )}\n              </div>\n            </div>\n          )}\n\n          <DemoCodeBlock\n            selectedFile={demo.selectedFile}\n            selectedFileName={demo.selectedFileName}\n            selectedFileLines={demo.selectedFileLines}\n            collapsibleOpen={demo.expanded}\n            collapsibleTriggerRef={collapsibleTriggerRef}\n            compact={compact}\n          />\n        </div>\n      </Collapsible.Root>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/components/Demo/DemoCodeBlock.tsx",
    "content": "import * as React from 'react';\nimport { Collapsible } from '@base-ui/react/collapsible';\nimport * as ScrollArea from '../ScrollArea';\n\nimport './CodeHighlighting.css';\n\nfunction fileNameToLanguage(fileName: string | undefined) {\n  if (!fileName) {\n    return 'text';\n  }\n  if (fileName.endsWith('.tsx') || fileName.endsWith('.ts')) {\n    return 'tsx';\n  }\n  if (fileName.endsWith('.js') || fileName.endsWith('.jsx')) {\n    return 'jsx';\n  }\n  if (fileName.endsWith('.json')) {\n    return 'json';\n  }\n  if (fileName.endsWith('.html')) {\n    return 'html';\n  }\n  if (fileName.endsWith('.css')) {\n    return 'css';\n  }\n  if (fileName.endsWith('.mdx')) {\n    return 'mdx';\n  }\n  return 'text';\n}\n\ninterface DemoCodeBlockProps {\n  selectedFile: React.ReactNode;\n  selectedFileName: string | undefined;\n  selectedFileLines: number;\n  collapsibleOpen: boolean;\n  /** How many lines should the code block have to get collapsed instead of rendering fully */\n  collapsibleLinesThreshold?: number;\n  collapsibleTriggerRef: React.Ref<HTMLButtonElement>;\n  /** When compact, we don't show a preview of the collapse code */\n  compact: boolean;\n}\n\nfunction Root(props: React.ComponentProps<typeof ScrollArea.Root>) {\n  return (\n    <ScrollArea.Root\n      {...props}\n      className=\"DemoCodeBlockRoot\"\n      tabIndex={-1}\n      onKeyDown={(event: React.KeyboardEvent) => {\n        if (\n          (event.ctrlKey || event.metaKey) &&\n          String.fromCharCode(event.keyCode) === 'A' &&\n          !event.shiftKey &&\n          !event.altKey\n        ) {\n          event.preventDefault();\n          window.getSelection()?.selectAllChildren(event.currentTarget);\n        }\n      }}\n    />\n  );\n}\n\nexport function DemoCodeBlock({\n  selectedFile,\n  selectedFileName,\n  selectedFileLines,\n  compact,\n  collapsibleOpen,\n  collapsibleLinesThreshold = 12,\n  collapsibleTriggerRef,\n}: DemoCodeBlockProps) {\n  if (selectedFileLines < collapsibleLinesThreshold) {\n    return (\n      <Root>\n        <ScrollArea.Viewport>\n          <div className=\"DemoSourceBrowser\" data-language={fileNameToLanguage(selectedFileName)}>\n            {selectedFile}\n          </div>\n        </ScrollArea.Viewport>\n        <ScrollArea.Corner />\n        <ScrollArea.Scrollbar orientation=\"vertical\" />\n        <ScrollArea.Scrollbar orientation=\"horizontal\" />\n      </Root>\n    );\n  }\n\n  return (\n    <React.Fragment>\n      <Root\n        render={\n          <Collapsible.Panel\n            keepMounted={compact ? undefined : true}\n            hidden={compact ? undefined : false}\n          />\n        }\n      >\n        <ScrollArea.Viewport\n          aria-hidden={!collapsibleOpen}\n          data-closed={collapsibleOpen ? undefined : ''}\n          className=\"DemoCodeBlockViewport\"\n          {...(!collapsibleOpen && { tabIndex: undefined, style: { overflow: undefined } })}\n        >\n          <div className=\"DemoSourceBrowser\" data-language={fileNameToLanguage(selectedFileName)}>\n            {selectedFile}\n          </div>\n        </ScrollArea.Viewport>\n\n        {collapsibleOpen && (\n          <React.Fragment>\n            <ScrollArea.Corner />\n            <ScrollArea.Scrollbar orientation=\"vertical\" />\n            <ScrollArea.Scrollbar orientation=\"horizontal\" />\n          </React.Fragment>\n        )}\n      </Root>\n\n      <Collapsible.Trigger\n        ref={collapsibleTriggerRef}\n        className=\"DemoCollapseButton\"\n        data-sticky={collapsibleOpen ? '' : undefined}\n      >\n        {collapsibleOpen ? 'Hide' : 'Show'} code\n      </Collapsible.Trigger>\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "docs/src/components/Demo/DemoErrorFallback.tsx",
    "content": "import { FallbackProps } from 'react-error-boundary';\n\nexport function DemoErrorFallback(props: FallbackProps) {\n  const { error, resetErrorBoundary } = props;\n\n  return (\n    <div role=\"alert\">\n      <p>There was an error while rendering the demo.</p>\n      <pre>{(error instanceof Error ? error.message : null) ?? 'Unknown error'}</pre>\n      <button type=\"button\" onClick={resetErrorBoundary}>\n        Try again\n      </button>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/components/Demo/DemoFileSelector.tsx",
    "content": "'use client';\n\nimport * as React from 'react';\nimport { Tabs } from '@base-ui/react/tabs';\n\ninterface DemoFileSelectorProps {\n  files: Array<{ name: string; slug?: string; component: React.ReactNode }>;\n  selectedFileName: string | undefined;\n  selectFileName: (fileName: string) => void;\n  onTabChange?: () => void;\n}\n\ntype Tab = { id: string; name: string; slug?: string };\n\nexport function DemoFileSelector({\n  files,\n  selectedFileName,\n  selectFileName,\n  onTabChange,\n}: DemoFileSelectorProps) {\n  const tabs: Tab[] = React.useMemo(\n    () => files.map(({ name, slug }) => ({ id: name, name, slug })),\n    [files],\n  );\n\n  const modifierKeysRef = React.useRef(false);\n\n  const onValueChange = React.useCallback(\n    (value: string) => {\n      // Don't change tab if modifier keys were pressed (user is opening in new tab)\n      if (modifierKeysRef.current) {\n        return;\n      }\n\n      selectFileName(value);\n      onTabChange?.();\n    },\n    [selectFileName, onTabChange],\n  );\n\n  const onTabPointerDown = React.useCallback((event: React.PointerEvent) => {\n    // Track modifier keys before focus event fires\n    modifierKeysRef.current = event.ctrlKey || event.metaKey;\n  }, []);\n\n  const onTabClick = React.useCallback((event: React.MouseEvent<HTMLAnchorElement>) => {\n    // Allow Ctrl+Click (or Cmd+Click on Mac) to open in new tab\n    if (event.ctrlKey || event.metaKey) {\n      // Reset modifier keys flag after all event handlers complete\n      queueMicrotask(() => {\n        modifierKeysRef.current = false;\n      });\n    } else {\n      // Prevent scroll jump to anchor\n      event.preventDefault();\n      modifierKeysRef.current = false;\n    }\n  }, []);\n\n  if (files.length === 1) {\n    return (\n      <a className=\"DemoFilename\" href={files[0].slug ? `#${files[0].slug}` : undefined}>\n        {files[0].name}\n      </a>\n    );\n  }\n\n  return (\n    <Tabs.Root value={selectedFileName} onValueChange={onValueChange}>\n      <Tabs.List className=\"DemoTabsList\" aria-label=\"Files\">\n        {tabs.map((tab: Tab) => (\n          <Tabs.Tab\n            // eslint-disable-next-line jsx-a11y/control-has-associated-label\n            render={<a href={tab.slug ? `#${tab.slug}` : undefined} onClick={onTabClick} />}\n            className=\"DemoTab\"\n            value={tab.id}\n            key={tab.id}\n            nativeButton={false}\n            onPointerDown={onTabPointerDown}\n          >\n            {tab.name}\n          </Tabs.Tab>\n        ))}\n      </Tabs.List>\n    </Tabs.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/components/Demo/DemoPlayground.tsx",
    "content": "import { ErrorBoundary } from 'react-error-boundary';\nimport kebabCase from 'es-toolkit/compat/kebabCase';\nimport { DemoErrorFallback } from './DemoErrorFallback';\n\nexport type DemoPlaygroundProps = {\n  component: React.ReactNode;\n  variant?: string;\n  // Only used for the extra Stackblitz/CSB link at the top of demo, it has to\n  // specifically be placed here for the reading order to make sense for SRs\n  children?: React.ReactNode;\n};\n\nexport function DemoPlayground({ component, variant, children }: DemoPlaygroundProps) {\n  return (\n    <ErrorBoundary FallbackComponent={DemoErrorFallback}>\n      <div className=\"DemoPlayground\">\n        <div\n          aria-label=\"Component demo\"\n          data-demo={kebabCase(variant)}\n          className=\"DemoPlaygroundInner\"\n        >\n          {component}\n        </div>\n        {children}\n      </div>\n    </ErrorBoundary>\n  );\n}\n"
  },
  {
    "path": "docs/src/components/Demo/DemoVariantSelector.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport * as Select from 'docs/src/components/Select';\n\nconst translations = {\n  variants: {\n    Default: 'Default',\n    System: 'MUI System',\n    Css: 'Plain CSS',\n    CssModules: 'CSS Modules',\n    Tailwind: 'Tailwind v4',\n  } as Record<string, string>,\n};\n\nexport interface DemoVariantSelectorProps extends React.HtmlHTMLAttributes<HTMLDivElement> {\n  onVariantChange?: () => void;\n  showLanguageSelector?: boolean;\n  variants: string[];\n  selectedVariant: string | null;\n  selectVariant: React.Dispatch<React.SetStateAction<string | null>>;\n  availableTransforms: string[];\n  selectedTransform: string | null | undefined;\n  selectTransform: (transformName: string | null) => void;\n}\n\nexport function DemoVariantSelector({\n  variants,\n  selectedVariant,\n  selectVariant,\n  availableTransforms,\n  selectedTransform,\n  selectTransform,\n  showLanguageSelector,\n  onVariantChange,\n  ...props\n}: DemoVariantSelectorProps) {\n  const hasJsTransform = availableTransforms.includes('js');\n  const handleLanguageChange = React.useCallback(\n    (value: string | null) => {\n      selectTransform(value === 'ts' ? null : value);\n    },\n    [selectTransform],\n  );\n\n  const handleVariantChange = React.useCallback(\n    (value: string | null) => {\n      selectVariant(value);\n      onVariantChange?.();\n    },\n    [selectVariant, onVariantChange],\n  );\n\n  return (\n    <div {...props}>\n      {hasJsTransform && (\n        <Select.Root\n          items={[\n            { value: 'ts', label: 'TS' },\n            { value: 'js', label: 'JS' },\n          ]}\n          value={selectedTransform || 'ts'}\n          onValueChange={handleLanguageChange}\n        >\n          <Select.Trigger />\n          <Select.Popup>\n            <Select.Item value=\"ts\">TS</Select.Item>\n            <Select.Item value=\"js\">JS</Select.Item>\n          </Select.Popup>\n        </Select.Root>\n      )}\n\n      {variants.length > 1 && (\n        <Select.Root\n          items={translations.variants}\n          value={selectedVariant}\n          onValueChange={handleVariantChange}\n        >\n          <Select.Trigger aria-label=\"Styling method\" />\n          <Select.Popup>\n            {variants.map((variantName) => (\n              <Select.Item key={variantName} value={variantName}>\n                {translations.variants[variantName]}\n              </Select.Item>\n            ))}\n          </Select.Popup>\n        </Select.Root>\n      )}\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/components/Demo/DemoVariantSelectorProvider.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\n\nexport interface DemoVariantSelectorContext {\n  selectedVariant: string | null;\n  setSelectedVariant: (variant: string | null) => void;\n  selectedLanguage: string;\n  setSelectedLanguage: (language: string) => void;\n}\n\nexport const DemoVariantSelectorContext = React.createContext<DemoVariantSelectorContext | null>(\n  null,\n);\n\nexport const useDemoVariantSelectorContext = () => {\n  const context = React.useContext(DemoVariantSelectorContext);\n  if (!context) {\n    throw new Error('Missing DemoVariantSelectorContext');\n  }\n\n  return context;\n};\n\ninterface DemoVariantSelectorProviderProps {\n  children: React.ReactNode;\n  defaultVariant: string;\n  defaultLanguage: string;\n}\n\nconst VARIANT_STORAGE_KEY = 'preferredDemoVariant';\nconst LANGUAGE_STORAGE_KEY = 'preferredDemoLanguage';\n\nexport function DemoVariantSelectorProvider(props: DemoVariantSelectorProviderProps) {\n  const { children, defaultVariant, defaultLanguage } = props;\n  const [selectedVariant, setSelectedVariant] = React.useState<string | null>(defaultVariant);\n  const [selectedLanguage, setSelectedLanguage] = React.useState(defaultLanguage);\n\n  const handleSelectedVariantChange = React.useCallback((value: string | null) => {\n    setSelectedVariant(value);\n    localStorage.setItem(VARIANT_STORAGE_KEY, value || '');\n  }, []);\n\n  const handleSelectedLanguageChange = React.useCallback((value: string) => {\n    setSelectedLanguage(value);\n    localStorage.setItem(LANGUAGE_STORAGE_KEY, value);\n  }, []);\n\n  useIsoLayoutEffect(() => {\n    const variantPreference = localStorage.getItem(VARIANT_STORAGE_KEY);\n    const languagePreference = localStorage.getItem(LANGUAGE_STORAGE_KEY);\n\n    if (variantPreference) {\n      setSelectedVariant(variantPreference);\n    }\n\n    if (languagePreference) {\n      setSelectedLanguage(languagePreference);\n    }\n  }, []);\n\n  const contextValue = React.useMemo(\n    () => ({\n      selectedVariant,\n      setSelectedVariant: handleSelectedVariantChange,\n      selectedLanguage,\n      setSelectedLanguage: handleSelectedLanguageChange,\n    }),\n    [selectedVariant, handleSelectedVariantChange, selectedLanguage, handleSelectedLanguageChange],\n  );\n\n  return (\n    <DemoVariantSelectorContext.Provider value={contextValue}>\n      {children}\n    </DemoVariantSelectorContext.Provider>\n  );\n}\n"
  },
  {
    "path": "docs/src/components/DescriptionList.css",
    "content": "@import 'docs/src/css/custom-media.css';\n\n@layer components {\n  .DescriptionList {\n    width: 100%;\n    display: grid;\n    grid-template-columns: auto 1fr;\n    row-gap: --spacing(3);\n  }\n\n  .DescriptionListItem {\n    position: relative;\n\n    @media (--xs) {\n      display: grid;\n      grid-template-columns: subgrid;\n      grid-column: span 3;\n      align-items: start;\n    }\n  }\n\n  .DescriptionTerm {\n    font-size: var(--text-sm);\n    line-height: var(--text-sm--line-height);\n    letter-spacing: var(--text-sm--letter-spacing);\n    font-weight: 700;\n    color: var(--color-foreground);\n    white-space: nowrap;\n    word-break: keep-all;\n    position: relative;\n    display: flex;\n    align-items: center;\n    margin-bottom: --spacing(1);\n\n    @media (--xs) {\n      font-weight: revert-layer;\n      color: revert-layer;\n      margin-bottom: 0;\n    }\n\n    &.separator {\n      &::before {\n        content: '';\n        box-shadow: inset 0 1px 0 0 var(--color-gray-200);\n        position: absolute;\n        top: 0;\n        inset-inline: 0.75rem 1rem;\n        height: 1px;\n      }\n\n      @media (--xs) {\n        &::before {\n          height: 0;\n        }\n      }\n    }\n  }\n\n  .DescriptionListDetails {\n    font-size: var(--text-sm);\n    line-height: var(--text-sm--line-height);\n\n    color: var(--color-foreground);\n    grid-column: span 2;\n  }\n\n  .DescriptionListInner {\n    display: flex;\n    flex-direction: column;\n    justify-content: center;\n    align-items: start;\n    padding-inline: 0.75rem;\n    width: 100%;\n    gap: --spacing(3);\n\n    [data-rehype-pretty-code-figure] {\n      margin: 0;\n      max-width: 100%;\n    }\n\n    .separator & {\n      padding-top: --spacing(2);\n    }\n\n    @media (--xs) {\n      min-height: 2.5rem;\n\n      &,\n      .separator & {\n        padding: 0.625rem 0.75rem;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/components/DescriptionList.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport clsx from 'clsx';\n\nexport function Root(props: React.ComponentProps<'dl'>) {\n  return <dl {...props} className={clsx('DescriptionList', props.className)} />;\n}\n\nexport function Term({\n  separator = false,\n  ...props\n}: React.ComponentProps<'dt'> & { separator?: boolean }) {\n  return (\n    <dt {...props} className={clsx('DescriptionTerm', separator && 'separator', props.className)}>\n      <Inner>{props.children}</Inner>\n    </dt>\n  );\n}\n\nexport function Details(props: React.ComponentProps<'dd'>) {\n  return (\n    <dd {...props} className={clsx('DescriptionListDetails', props.className)}>\n      <Inner>{props.children}</Inner>\n    </dd>\n  );\n}\n\nexport function Item(props: React.ComponentProps<'div'>) {\n  return <div {...props} className={clsx('DescriptionListItem', props.className)} />;\n}\n\nfunction Inner(props: React.ComponentProps<'div'>) {\n  return <div {...props} className={clsx('DescriptionListInner', props.className)} />;\n}\n"
  },
  {
    "path": "docs/src/components/DocsProviders.tsx",
    "content": "import * as React from 'react';\nimport { Tooltip } from '@base-ui/react/tooltip';\nimport { DemoVariantSelectorProvider } from 'docs/src/components/Demo/DemoVariantSelectorProvider';\nimport { PackageManagerSnippetProvider } from 'docs/src/blocks/PackageManagerSnippet/PackageManagerSnippetProvider';\n\nexport function DocsProviders({ children }: { children: React.ReactNode }) {\n  return (\n    <Tooltip.Provider delay={350}>\n      <DemoVariantSelectorProvider defaultVariant=\"css-modules\" defaultLanguage=\"ts\">\n        <PackageManagerSnippetProvider defaultValue=\"npm\">{children}</PackageManagerSnippetProvider>\n      </DemoVariantSelectorProvider>\n    </Tooltip.Provider>\n  );\n}\n"
  },
  {
    "path": "docs/src/components/GhostButton.css",
    "content": "@layer components {\n  .GhostButton {\n    font-size: var(--text-xs);\n    line-height: var(--text-xs--line-height);\n\n    color: var(--color-gray);\n    cursor: default;\n    position: relative;\n    z-index: 0;\n    display: inline-flex;\n    align-items: center;\n    gap: 0.375rem;\n    outline: 0;\n    user-select: none;\n\n    &::before,\n    &::after {\n      content: '';\n      position: absolute;\n      z-index: -1;\n    }\n\n    &[data-layout='text'] {\n      &::before {\n        inset: -0.125rem -0.375rem;\n        border-radius: var(--radius-sm);\n      }\n    }\n\n    &[data-layout='icon'] {\n      min-width: 1rem;\n      min-height: 1rem;\n      justify-content: center;\n\n      &::before {\n        inset: -0.25rem;\n        border-radius: var(--radius-sm);\n      }\n    }\n\n    /* Increase the clickable size  */\n    &::after {\n      inset: -0.375rem -0.5rem;\n    }\n\n    &:focus-visible::before {\n      content: '';\n      position: absolute;\n      pointer-events: none;\n      outline: 2px solid var(--color-blue);\n      outline-offset: -1px;\n    }\n\n    &:not(:focus-visible) {\n      @media (hover: hover) {\n        &:hover {\n          &::before {\n            background-color: var(--color-gray-100);\n          }\n        }\n      }\n\n      &[data-popup-open] {\n        &::before {\n          background-color: var(--color-gray-100);\n        }\n      }\n\n      /* No pressed state for menus */\n      &:not([aria-haspopup]):active {\n        &::before {\n          background-color: var(--color-gray-200);\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/components/GhostButton.tsx",
    "content": "import clsx from 'clsx';\nimport * as React from 'react';\nimport './GhostButton.css';\n\ninterface GhostButtonProps extends React.ComponentProps<'button'> {\n  layout?: 'text' | 'icon';\n}\n\nexport function GhostButton({ className, layout = 'text', ...props }: GhostButtonProps) {\n  return (\n    <button\n      data-layout={layout}\n      type=\"button\"\n      className={clsx('GhostButton', className)}\n      {...props}\n    />\n  );\n}\n"
  },
  {
    "path": "docs/src/components/GoogleAnalytics.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { usePathname } from 'next/navigation';\nimport { usePreference } from '@mui/internal-docs-infra/usePreference';\nimport { GoogleAnalyticsProvider } from 'docs/src/blocks/GoogleAnalyticsProvider';\nimport { DemoVariantSelectorContext } from './Demo/DemoVariantSelectorProvider';\nimport { GoogleTagManager } from '../blocks/GoogleTagManager';\n\nconst PRODUCTION_GA =\n  process.env.DEPLOY_ENV === 'production' || process.env.DEPLOY_ENV === 'staging';\nconst GOOGLE_ANALYTICS_ID_V4 = PRODUCTION_GA ? 'G-FE5XQBD0BH' : 'G-LSE9X5R2CN';\n\nexport function GoogleAnalytics({ children }: { children?: React.ReactNode }) {\n  const currentRoute = usePathname();\n  const demoVariantSelectorContext = React.useContext(DemoVariantSelectorContext);\n  const [stylingVariant] = usePreference('variant', 'CssModules:Tailwind', 'CssModules');\n\n  return (\n    <React.Fragment>\n      <GoogleTagManager id={GOOGLE_ANALYTICS_ID_V4} />\n      <GoogleAnalyticsProvider\n        id={GOOGLE_ANALYTICS_ID_V4}\n        productId=\"base-ui\"\n        productCategoryId=\"core\"\n        currentRoute={currentRoute}\n        codeLanguage={demoVariantSelectorContext?.selectedLanguage ?? 'ts'}\n        codeStylingVariant={stylingVariant}\n        userLanguage=\"en\"\n      >\n        {children}\n      </GoogleAnalyticsProvider>\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "docs/src/components/Header.css",
    "content": "@import 'docs/src/css/custom-media.css';\n\n@layer components {\n  .Header {\n    font-size: var(--text-md);\n    line-height: var(--text-md--line-height);\n    position: absolute;\n    left: 0;\n    top: 0;\n    height: var(--header-height);\n    width: 100%;\n  }\n\n  .HeaderInner {\n    height: inherit;\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n    padding-inline: 1.5rem;\n\n    position: fixed;\n    top: 0;\n    inset-inline: 0;\n    box-shadow: inset 0 -1px var(--color-gridline);\n    /* It's also the iOS header overscroll background */\n    background-color: var(--color-gray-50);\n    z-index: 2;\n\n    @media (--show-side-nav) {\n      position: static;\n      box-shadow: none;\n      background-color: transparent;\n    }\n  }\n\n  .HeaderLink,\n  .HeaderButton {\n    display: flex;\n    align-items: center;\n    gap: 0.375rem;\n    padding: 0.25rem 0.5rem;\n    margin: -0.25rem -0.5rem;\n    border-radius: var(--radius-md);\n\n    @media not (hover: hover) {\n      &:active {\n        color: var(--color-gray-500);\n      }\n    }\n\n    &:focus-visible {\n      z-index: 1;\n      outline: 2px solid var(--color-blue);\n      outline-offset: -2px;\n    }\n\n    & > * {\n      flex-shrink: 0;\n    }\n  }\n\n  .HeaderButton {\n    @media (hover: hover) {\n      &:hover {\n        background-color: var(--color-gray-100);\n      }\n    }\n\n    @media not (hover: hover) {\n      &:active {\n        background-color: var(--color-gray-100);\n      }\n    }\n  }\n\n  .HeaderLink {\n    flex-shrink: 0;\n\n    @media (hover: hover) {\n      &:hover {\n        text-decoration: underline;\n        text-decoration-color: var(--color-gray-500);\n        text-decoration-thickness: 1px;\n        text-underline-offset: 2px;\n      }\n    }\n  }\n\n  .HeaderLogoLink {\n    display: flex;\n    padding: 0.25rem 0.5rem;\n    margin: -0.25rem -0.5rem;\n\n    &:active {\n      color: var(--color-gray-500);\n    }\n\n    & svg {\n      margin-top: -0.125rem;\n    }\n\n    &:focus-visible {\n      border-radius: var(--radius-md);\n      outline: 2px solid var(--color-blue);\n      outline-offset: -1px;\n    }\n  }\n\n  .HeaderDesktopActions {\n    display: none;\n    gap: 1.5rem;\n\n    @media (--lg) {\n      display: flex;\n    }\n  }\n\n  .HeaderMobileActions {\n    display: flex;\n    align-items: center;\n    gap: 0.5rem;\n\n    @media (--lg) {\n      display: none;\n    }\n  }\n\n  .HeaderMobileSearch {\n    display: flex;\n    padding-inline: 1rem;\n  }\n\n  .HeaderNavTrigger {\n    white-space: nowrap;\n  }\n\n  .HeaderNavTriggerBars {\n    display: flex;\n    width: 1rem;\n    flex-direction: column;\n    align-items: center;\n    gap: 0.25rem;\n  }\n\n  .HeaderNavTriggerBars::before,\n  .HeaderNavTriggerBars::after {\n    content: '';\n    display: block;\n    width: 0.875rem;\n    height: 2px;\n    background-color: currentColor;\n  }\n\n  .HeaderResourceRow {\n    display: flex;\n    flex: 1;\n    align-items: baseline;\n    justify-content: space-between;\n  }\n\n  .HeaderVersion {\n    font-size: var(--text-md);\n    line-height: var(--text-md--line-height);\n    color: var(--color-gray-600);\n  }\n\n  .HeaderGitHubIcon {\n    margin-top: -2px;\n  }\n}\n"
  },
  {
    "path": "docs/src/components/Header.tsx",
    "content": "import NextLink from 'next/link';\nimport { GitHubIcon } from 'docs/src/icons/GitHubIcon';\nimport * as MobileNav from './MobileNav';\nimport { sitemap } from '../app/sitemap';\nimport { NpmIcon } from '../icons/NpmIcon';\nimport { Logo } from './Logo';\nimport { SkipNav } from './SkipNav';\nimport { Search } from './Search';\nimport './Header.css';\n\nexport const titleMap: Record<string, string> = {\n  'About Base\\xa0UI': 'About',\n};\n\nexport const HEADER_HEIGHT = 48;\n\nconst showPrivatePages = process.env.SHOW_PRIVATE_PAGES === 'true';\n\nexport function Header() {\n  return (\n    <header className=\"Header\">\n      <div className=\"HeaderInner\">\n        <SkipNav>Skip to contents</SkipNav>\n        <NextLink href=\"/\" className=\"HeaderLogoLink\">\n          <Logo aria-label=\"Base UI\" />\n        </NextLink>\n        <div className=\"HeaderDesktopActions\">\n          <Search containedScroll enableKeyboardShortcut />\n          <a\n            className=\"HeaderLink\"\n            href=\"https://www.npmjs.com/package/@base-ui/react\"\n            rel=\"noopener\"\n          >\n            <NpmIcon />\n            {process.env.LIB_VERSION}\n          </a>\n          <a className=\"HeaderLink\" href=\"https://github.com/mui/base-ui\" rel=\"noopener\">\n            <GitHubIcon />\n            GitHub\n          </a>\n        </div>\n        <div className=\"HeaderMobileActions\">\n          <div className=\"HeaderMobileSearch\">\n            <Search />\n          </div>\n          {sitemap && (\n            <MobileNav.Root>\n              <MobileNav.Trigger className=\"HeaderButton HeaderNavTrigger\">\n                <span className=\"HeaderNavTriggerBars\" />\n                Navigation\n              </MobileNav.Trigger>\n              <MobileNav.Portal>\n                <MobileNav.Backdrop />\n                <MobileNav.Popup>\n                  {Object.entries(sitemap.data).map(([name, section]) => (\n                    <MobileNav.Section key={name}>\n                      <MobileNav.Heading>{name}</MobileNav.Heading>\n                      <MobileNav.List>\n                        {section.pages\n                          .filter((page) => (page.audience === 'private' ? showPrivatePages : true))\n                          .map((page) => {\n                            const isNewPage = page.tags?.includes('New');\n                            const isPreviewPage = page.tags?.includes('Preview');\n                            const isPrivatePage = page.audience === 'private';\n                            return (\n                              <MobileNav.Item\n                                key={page.title}\n                                href={\n                                  page.path.startsWith('./')\n                                    ? `${section.prefix}${page.path.replace(/^\\.\\//, '').replace(/\\/page\\.mdx$/, '')}`\n                                    : page.path\n                                }\n                                external={page.tags?.includes('External')}\n                              >\n                                {(page.title && titleMap[page.title]) || page.title}\n                                {isPrivatePage && <MobileNav.Badge>Private</MobileNav.Badge>}\n                                {isPreviewPage && <MobileNav.Badge>Preview</MobileNav.Badge>}\n                                {isNewPage && !isPreviewPage && !isPrivatePage && (\n                                  <MobileNav.Badge>New</MobileNav.Badge>\n                                )}\n                              </MobileNav.Item>\n                            );\n                          })}\n                      </MobileNav.List>\n                    </MobileNav.Section>\n                  ))}\n                  <MobileNav.Section>\n                    <MobileNav.Heading>Resources</MobileNav.Heading>\n                    <MobileNav.List>\n                      <MobileNav.Item\n                        href=\"https://www.npmjs.com/package/@base-ui/react\"\n                        rel=\"noopener\"\n                      >\n                        <NpmIcon />\n                        <span className=\"HeaderResourceRow\">\n                          npm package\n                          <span className=\"HeaderVersion\">{process.env.LIB_VERSION}</span>\n                        </span>\n                      </MobileNav.Item>\n                      <MobileNav.Item href=\"https://github.com/mui/base-ui\" rel=\"noopener\">\n                        <GitHubIcon className=\"HeaderGitHubIcon\" />\n                        GitHub\n                      </MobileNav.Item>\n                    </MobileNav.List>\n                  </MobileNav.Section>\n                </MobileNav.Popup>\n              </MobileNav.Portal>\n            </MobileNav.Root>\n          )}\n        </div>\n      </div>\n    </header>\n  );\n}\n"
  },
  {
    "path": "docs/src/components/HeadingLink.css",
    "content": "@layer components {\n  .HeadingLink {\n    border-radius: var(--radius-md);\n    position: relative;\n\n    &:focus-visible {\n      /* https://github.com/mui/base-ui/pull/2318#issuecomment-3101118590 */\n      outline: none;\n      &::before {\n        --focus-v-padding: 0.25rem;\n        --focus-h-padding: 0.5rem;\n        content: '';\n        z-index: 1;\n        outline: 2px solid var(--color-blue);\n        outline-offset: -2px;\n        position: absolute;\n        top: calc(0px - var(--focus-v-padding));\n        left: calc(0px - var(--focus-h-padding));\n        width: calc(100% + var(--focus-h-padding) * 2);\n        height: calc(100% + var(--focus-v-padding) * 2);\n        border-radius: var(--radius-md);\n      }\n    }\n\n    @media (hover: hover) {\n      &:hover {\n        text-decoration: underline;\n        text-underline-offset: 4px;\n        text-decoration-thickness: 1px;\n        text-decoration-color: currentColor;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/components/HeadingLink.tsx",
    "content": "import * as React from 'react';\nimport clsx from 'clsx';\nimport './HeadingLink.css';\n\nexport function HeadingLink({ className, id, ...props }: React.ComponentPropsWithoutRef<'a'>) {\n  return <a className={clsx('HeadingLink', className)} href={`#${id}`} {...props} />;\n}\n"
  },
  {
    "path": "docs/src/components/InstallationBlock.css",
    "content": "@layer components {\n  .InstallationBlockTabsList {\n    display: flex;\n    gap: 1rem;\n  }\n\n  .InstallationBlockTab {\n    font-size: var(--text-xs);\n    line-height: var(--text-xs--line-height);\n    letter-spacing: var(--text-xs--letter-spacing);\n    cursor: default;\n    position: relative;\n    z-index: 0;\n    outline: 0;\n\n    @media (hover: hover) {\n      &:hover {\n        &::before {\n          background-color: var(--color-gray-100);\n        }\n      }\n    }\n\n    &::before,\n    &::after {\n      content: '';\n      position: absolute;\n      z-index: -1;\n    }\n\n    &::before {\n      inset: -0.125rem -0.375rem;\n      border-radius: var(--radius-sm);\n    }\n\n    /* Increase the clickable size */\n    &::after {\n      inset: -0.375rem -0.5rem;\n    }\n\n    &[data-active] {\n      color: var(--color-foreground);\n      font-weight: 700;\n\n      &::before {\n        background-color: white;\n        outline: 1px solid var(--color-gray-300);\n        outline-offset: -1px;\n        box-shadow:\n          0 2px 3px -2px var(--color-gray-300),\n          inset 0 -1px 1px var(--color-gray-200);\n      }\n\n      @media (prefers-color-scheme: dark) {\n        &::before {\n          outline: none;\n          background-color: var(--color-gray-50);\n          box-shadow:\n            0 0 3px 0 var(--color-gray-300),\n            inset 0 0 0 1px var(--color-gray-400);\n        }\n      }\n    }\n\n    &:focus-visible::before {\n      outline: 2px solid var(--color-blue);\n      outline-offset: -1px;\n    }\n  }\n\n  .InstallationBlockTabLabel {\n    display: inline-grid;\n  }\n\n  .InstallationBlockTabLabel::before {\n    content: attr(data-text);\n    font-weight: 700;\n    visibility: hidden;\n    height: 0;\n    pointer-events: none;\n    grid-area: 1 / 1;\n  }\n}\n"
  },
  {
    "path": "docs/src/components/InstallationBlock.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport copy from 'clipboard-copy';\nimport { Tabs } from '@base-ui/react/tabs';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useTimeout } from '@base-ui/utils/useTimeout';\nimport { usePackageManagerSnippetContext } from 'docs/src/blocks/PackageManagerSnippet/PackageManagerSnippetProvider';\nimport { GhostButton } from './GhostButton';\nimport { CopyIcon } from '../icons/CopyIcon';\nimport { CheckIcon } from '../icons/CheckIcon';\nimport './InstallationBlock.css';\n\nconst PACKAGE_MANAGERS = [\n  { value: 'pnpm', label: 'pnpm', command: 'add' },\n  { value: 'npm', label: 'npm', command: 'i' },\n  { value: 'yarn', label: 'yarn', command: 'add' },\n  { value: 'bun', label: 'bun', command: 'add' },\n] as const;\n\nconst PRE_STYLE = {\n  backgroundColor: 'var(--color-content)',\n  color: 'var(--syntax-default)',\n} as const;\n\nconst ENTITY_STYLE = { color: 'var(--syntax-entity)' } as const;\nconst STRING_STYLE = { color: 'var(--syntax-string)' } as const;\n\ninterface InstallationBlockProps {\n  package: string;\n}\n\nexport function InstallationBlock(props: InstallationBlockProps) {\n  const packageName = props.package;\n\n  const { packageManager: globalPreference, setPackageManager: setGlobalPreference } =\n    usePackageManagerSnippetContext();\n\n  const [value, setValue] = React.useState<string>(PACKAGE_MANAGERS[0].value);\n  const [copied, setCopied] = React.useState(false);\n\n  const codeRef = React.useRef<HTMLPreElement>(null);\n  const timeout = useTimeout();\n\n  useIsoLayoutEffect(() => {\n    if (PACKAGE_MANAGERS.some((pm) => pm.value === globalPreference)) {\n      setValue(globalPreference);\n    }\n  }, [globalPreference]);\n\n  const handleValueChange = useStableCallback((newValue: string) => {\n    setValue(newValue);\n    setGlobalPreference(newValue);\n  });\n\n  const handleCopy = useStableCallback(async () => {\n    const code = codeRef.current?.textContent;\n    if (!code) {\n      return;\n    }\n\n    await copy(code);\n    setCopied(true);\n    timeout.start(2000, () => {\n      setCopied(false);\n    });\n  });\n\n  return (\n    <Tabs.Root value={value} onValueChange={handleValueChange}>\n      <div role=\"figure\" aria-label=\"Installation command\" className=\"CodeBlockRoot\">\n        <div className=\"CodeBlockPanel\">\n          <Tabs.List className=\"InstallationBlockTabsList\" aria-label=\"Package manager\">\n            {PACKAGE_MANAGERS.map((pm) => (\n              <Tabs.Tab key={pm.value} value={pm.value} className=\"InstallationBlockTab\">\n                <span className=\"InstallationBlockTabLabel\" data-text={pm.label}>\n                  {pm.label}\n                </span>\n              </Tabs.Tab>\n            ))}\n          </Tabs.List>\n\n          <GhostButton className=\"ml-auto\" onClick={handleCopy}>\n            Copy\n            <span className=\"flex size-3.5 items-center justify-center\" aria-hidden>\n              {copied ? <CheckIcon /> : <CopyIcon />}\n            </span>\n          </GhostButton>\n          <span className=\"sr-only\" aria-live=\"polite\">\n            {copied ? 'Copied to clipboard' : ''}\n          </span>\n        </div>\n\n        {PACKAGE_MANAGERS.map((pm) => (\n          <Tabs.Panel key={pm.value} value={pm.value}>\n            <pre\n              ref={pm.value === value ? codeRef : undefined}\n              className=\"CodeBlockPre\"\n              style={PRE_STYLE}\n              data-language=\"bash\"\n              data-theme=\"base-ui\"\n            >\n              <code\n                className=\"Code data-inline:mx-[0.1em]\"\n                data-language=\"bash\"\n                data-theme=\"base-ui\"\n              >\n                <span data-line>\n                  <span style={ENTITY_STYLE}>{pm.value}</span>\n                  <span style={STRING_STYLE}> {pm.command}</span>\n                  <span style={STRING_STYLE}> {packageName}</span>\n                </span>\n              </code>\n            </pre>\n          </Tabs.Panel>\n        ))}\n      </div>\n    </Tabs.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/components/Kbd/Kbd.css",
    "content": "@layer components {\n  .Kbd {\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;\n    flex-shrink: 0;\n    margin-inline: 1px;\n    font-family: var(--default-font-family);\n    font-weight: 400;\n    white-space: nowrap;\n\n    position: relative;\n    top: -0.03em;\n\n    font-size: 0.8em;\n    min-width: 1.75em;\n    line-height: 1.65em;\n    box-sizing: border-box;\n    padding-block: 0.025em;\n    padding-inline: 0.5em;\n    word-spacing: -0.1em;\n    border-radius: 0.25em;\n\n    /* Make sure that the height is not stretched in a Flex/Grid layout */\n    height: fit-content;\n\n    color: var(--color-gray-700);\n    background-color: var(--color-gray-50);\n    box-shadow:\n      inset 0 round(0.05em, 1px) white,\n      inset 0 round(0.25em, 1px) round(0.75em, 1px) var(--color-gray-100),\n      inset 0 round(-0.05em, 1px) var(--color-gray-200),\n      0 0 0 round(0.05em, 1px) var(--color-gray-300),\n      0 round(0.08em, 1px) round(0.17em, 1px) var(--color-gray-300);\n\n    @media (prefers-color-scheme: dark) {\n      text-shadow: #fff8 0 0 1px;\n      color: var(--color-gray-950);\n      background-color: var(--color-content);\n      box-shadow:\n        inset 0 round(-0.1em, 1px) black,\n        inset 0 round(-0.05em, 1px) round(0.75em, 1px) var(--color-gray-100),\n        0 0 0 round(0.075em, 1px) var(--color-gray-500),\n        inset 0 round(0.05em, 1px) var(--color-gray-400);\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/components/Kbd/Kbd.tsx",
    "content": "import * as React from 'react';\nimport clsx from 'clsx';\nimport './Kbd.css';\n\nexport function Kbd({ className, ...props }: React.ComponentProps<'kbd'>) {\n  return <kbd className={clsx('Kbd', className)} {...props} />;\n}\n"
  },
  {
    "path": "docs/src/components/Kbd/rehypeKbd.mjs",
    "content": "import { visit } from 'unist-util-visit';\n\n/** Treat <kbd> as normal tags */\nexport function rehypeKbd() {\n  return (tree) => {\n    visit(tree, (node) => {\n      if (node.name === 'kbd') {\n        node.tagName = 'kbd';\n        node.type = 'element';\n        delete node.data;\n      }\n    });\n  };\n}\n"
  },
  {
    "path": "docs/src/components/Link.css",
    "content": "@layer components {\n  .Link {\n    color: var(--color-blue);\n    text-decoration: underline;\n    text-underline-offset: 2px;\n    text-decoration-thickness: 1px;\n    text-decoration-color: color-mix(in oklab, var(--color-blue), transparent);\n\n    &[target='_blank'] {\n      margin-right: 0.125em;\n      display: inline-flex;\n      align-items: center;\n      gap: 0.25em;\n    }\n\n    &:focus-visible {\n      outline: 2px solid var(--color-blue);\n      outline-offset: -2px;\n      border-radius: var(--radius-sm);\n    }\n\n    @media (hover: hover) {\n      &:hover {\n        text-decoration-color: var(--color-blue);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/components/Link.tsx",
    "content": "import * as React from 'react';\nimport clsx from 'clsx';\nimport NextLink from 'next/link';\nimport { ExternalLinkIcon } from 'docs/src/icons/ExternalLinkIcon';\nimport './Link.css';\n\ninterface LinkProps extends React.ComponentProps<typeof NextLink> {\n  skipExternalIcon?: boolean;\n}\n\nexport function Link(props: LinkProps) {\n  const { href, className, skipExternalIcon, ...rest } = props;\n  let pathname = typeof href === 'string' ? href : href.pathname!;\n\n  // Sometimes link come from component descriptions; in this case, remove the domain\n  if (pathname.startsWith('https://base-ui.com/')) {\n    pathname = pathname.replace('https://base-ui.com/', '/');\n  }\n\n  if (pathname.startsWith('http')) {\n    return (\n      <NextLink\n        target=\"_blank\"\n        rel=\"noopener\"\n        {...rest}\n        href={href}\n        className={clsx('Link', className)}\n      >\n        {props.children}\n        {!skipExternalIcon && <ExternalLinkIcon />}\n      </NextLink>\n    );\n  }\n\n  if (pathname.endsWith('.md') || pathname.endsWith('.txt')) {\n    // Relative URL, but outside the Next.js router\n    return <a {...rest} href={pathname} className={clsx('Link', className)} />;\n  }\n\n  return <NextLink {...rest} href={href} className={clsx('Link', className)} />;\n}\n"
  },
  {
    "path": "docs/src/components/Logo.tsx",
    "content": "import * as React from 'react';\n\nexport function Logo(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"17\" height=\"24\" viewBox=\"0 0 17 24\" fill=\"currentcolor\" {...props}>\n      <path d=\"M9.5001 7.01537C9.2245 6.99837 9 7.22385 9 7.49999V23C13.4183 23 17 19.4183 17 15C17 10.7497 13.6854 7.27351 9.5001 7.01537Z\" />\n      <path d=\"M8 9.8V12V23C3.58172 23 0 19.0601 0 14.2V12V1C4.41828 1 8 4.93989 8 9.8Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/components/Menu.css",
    "content": "@layer components {\n  .MenuPositioner {\n    /* The outline is sometimes seen in Safari when the positioner is focused intermittently */\n    outline: 0;\n    z-index: 1;\n\n    @media (prefers-color-scheme: light) {\n      filter: drop-shadow(0 0 1px var(--color-gray-200))\n        drop-shadow(0 0.5px 1px var(--color-gray-200)) drop-shadow(0 1px 2px var(--color-gray-200))\n        drop-shadow(0 3px 8px var(--color-gray-300));\n    }\n  }\n\n  .MenuPopup {\n    outline: 0;\n    max-width: var(--available-width);\n    max-height: var(--available-height);\n    border-radius: var(--radius-md);\n    background-color: var(--color-popup);\n    overflow: hidden;\n    cursor: default;\n    user-select: none;\n    padding-block: 0.25rem;\n\n    /* Make sure the layout doesn't fall apart on extreme zoom on mobile */\n    min-width: min-content;\n\n    @media (prefers-color-scheme: dark) {\n      box-shadow: inset 0 0 0 1px var(--color-gray-300);\n    }\n  }\n\n  .MenuItem {\n    font-size: var(--text-xs);\n    line-height: var(--text-xs--line-height);\n    letter-spacing: var(--text-xs--letter-spacing);\n    outline: 0;\n    display: flex;\n    align-items: center;\n    gap: 0.5rem;\n    height: 1.75rem;\n    padding-inline: 0.5rem;\n    white-space: nowrap;\n    z-index: 0;\n    color: var(--color-foreground);\n    text-decoration: none;\n    cursor: default;\n\n    &[data-highlighted] {\n      position: relative;\n      color: white;\n\n      &::before {\n        content: '';\n        position: absolute;\n        inset-inline: 0.25rem;\n        inset-block: 0;\n        z-index: -1;\n        border-radius: var(--radius-sm);\n        background-color: var(--color-highlight);\n      }\n    }\n\n    @media (pointer: coarse) {\n      font-size: var(--text-md);\n      line-height: var(--text-md--line-height);\n      letter-spacing: var(--text-md--letter-spacing);\n      height: 2.25rem;\n    }\n  }\n\n  .MenuSeparator {\n    height: 1px;\n    margin-block: 0.25rem;\n    margin-inline: 0.25rem;\n    background-color: var(--color-gray-200);\n  }\n}\n"
  },
  {
    "path": "docs/src/components/Menu.tsx",
    "content": "import { Menu } from '@base-ui/react/menu';\nimport clsx from 'clsx';\nimport './Menu.css';\n\nexport const Root = Menu.Root;\n\nexport const Trigger = Menu.Trigger;\n\nexport function Popup({ children, className, sideOffset = 8, ...props }: Menu.Positioner.Props) {\n  return (\n    <Menu.Portal>\n      <Menu.Positioner sideOffset={sideOffset} className=\"MenuPositioner\" {...props}>\n        <Menu.Popup className={clsx('MenuPopup', className)}>{children}</Menu.Popup>\n      </Menu.Positioner>\n    </Menu.Portal>\n  );\n}\n\nexport function Item({ children, className, ...props }: Menu.Item.Props) {\n  return (\n    <Menu.Item className={clsx('MenuItem', className)} {...props}>\n      {children}\n    </Menu.Item>\n  );\n}\n\nexport function LinkItem({ children, className, ...props }: Menu.LinkItem.Props) {\n  return (\n    <Menu.LinkItem className={clsx('MenuItem', className)} {...props}>\n      {children}\n    </Menu.LinkItem>\n  );\n}\n\nexport function Separator({ className, ...props }: Menu.Separator.Props) {\n  return <Menu.Separator className={clsx('MenuSeparator', className)} {...props} />;\n}\n"
  },
  {
    "path": "docs/src/components/MobileNav.css",
    "content": "@import 'docs/src/css/custom-media.css';\n\n@layer components {\n  .MobileNavBackdrop {\n    position: fixed;\n    inset: 0;\n    min-height: 100dvh;\n\n    transition-duration: 600ms;\n    transition-property: -webkit-backdrop-filter, backdrop-filter, opacity;\n    transition-timing-function: var(--ease-out-fast);\n    backdrop-filter: blur(1.5px);\n    background-image: linear-gradient(to bottom, transparent 2rem, rgb(0 0 0 / 5%) 50%);\n\n    /* iOS 26+: Ensure the backdrop covers the entire visible viewport. */\n    @supports (-webkit-touch-callout: none) {\n      position: absolute;\n    }\n\n    @media (prefers-color-scheme: dark) {\n      background-image: linear-gradient(to bottom, transparent, rgb(0 0 0 / 25%) 6rem);\n    }\n\n    &[data-starting-style],\n    &[data-ending-style] {\n      backdrop-filter: blur(0);\n      opacity: 0;\n    }\n\n    &[data-ending-style] {\n      transition-duration: 250ms;\n      transition-timing-function: var(--ease-in-slow);\n    }\n  }\n\n  .MobileNavPopup {\n    position: fixed;\n    height: 100dvh;\n    inset: 0;\n    outline: 0;\n\n    /* Half the transition duration for blur so the element sort of comes into the focus when it's halfway in */\n    transition-duration: 600ms, 300ms;\n    transform-origin: top center;\n    transition-property: transform, filter;\n    transition-timing-function: var(--ease-out-fast);\n\n    &[data-starting-style],\n    &[data-ending-style] {\n      transform: translateY(100dvh);\n      filter: blur(1px);\n    }\n\n    &[data-ending-style] {\n      /* Delay the blur transition so it doesn't feel unfocused until halway out */\n      transition-delay: 0ms, 125ms;\n      transition-duration: 250ms;\n      transition-timing-function: var(--ease-in-slow);\n    }\n  }\n\n  .MobileNavViewport {\n    --mobile-nav-item-height: 2.5rem;\n    --mobile-nav-item-padding-x: 1.5rem;\n\n    position: absolute;\n    inset: 0;\n    height: 100dvh;\n    overflow-y: auto;\n\n    font-size: var(--text-base);\n    line-height: var(--text-base--line-height);\n\n    /* Native apps don't show scrollbars on sheets like this */\n    scrollbar-width: none;\n\n    &::-webkit-scrollbar {\n      display: none;\n    }\n\n    /* A decorative border between the status bar and the page */\n    &::after {\n      content: '';\n      position: fixed;\n      z-index: 1;\n      top: 0;\n      inset-inline: 0;\n      height: 1px;\n      transition: background-color 50ms;\n    }\n\n    &[data-clipped]::after {\n      background-color: var(--color-gridline);\n    }\n  }\n\n  .MobileNavViewportInner {\n    position: relative;\n\n    /* Prevent children's margin collapse */\n    display: flex;\n    flex-direction: column;\n  }\n\n  .MobileNavBackdropTapArea {\n    position: absolute;\n    inset: 0;\n  }\n\n  .MobileNavPanel {\n    margin-top: var(--header-height);\n    position: relative;\n    padding-block: 1rem;\n\n    border-top-left-radius: var(--radius-xl);\n    border-top-right-radius: var(--radius-xl);\n\n    box-shadow:\n      0 10px 64px -10px rgb(36 40 52 / 20%),\n      0 0.25px 0 1px var(--color-gray-200);\n\n    @media (prefers-color-scheme: dark) {\n      box-shadow: 0 0 0 1px var(--color-gray-200);\n    }\n\n    /* Make bottom overscroll edges soft; visible during extreme rubber band overscroll at the bottom */\n    background-image: linear-gradient(to bottom, var(--color-popup) calc(100% - 2rem), transparent);\n\n    /* Make the panel narrower on wider screens and lose the overscroll gradient hocus pocus for a bottom margin */\n    @media (--sm) {\n      margin-block: 5rem;\n      margin-inline: auto;\n      width: calc(100% - 6rem);\n      max-width: 40rem;\n      background-image: none;\n      background-color: var(--color-popup);\n      border-radius: var(--radius-xl);\n    }\n  }\n\n  /* Smoothly hides bottom overscroll edge */\n  .MobileNavBottomOverscroll {\n    position: absolute;\n    inset: 0;\n    background-image: linear-gradient(to bottom, transparent 30%, var(--color-popup) 50%);\n\n    @media (--sm) {\n      display: none;\n    }\n  }\n\n  .MobileNavSection {\n    margin-bottom: 1rem;\n  }\n\n  .MobileNavHeading {\n    font-size: var(--text-lg);\n    line-height: var(--text-lg--line-height);\n    font-weight: 700;\n    padding-inline: var(--mobile-nav-item-padding-x);\n\n    .MobileNavHeadingInner {\n      display: flex;\n      align-items: center;\n      height: var(--mobile-nav-item-height);\n    }\n\n    &::after {\n      content: '';\n      display: block;\n      margin-top: -1px;\n      border-bottom: 1px solid var(--color-gray-200);\n    }\n  }\n\n  /* TODO Vlad should headings be just items? */\n  .MobileNavItem {\n    /*\n     * Border is a separate element so that links\n     * span the entire screen width and have the\n     * tap-highlight-color applied edge to edge.\n     */\n    &::after {\n      content: '';\n      display: block;\n      margin-inline: var(--mobile-nav-item-padding-x);\n      border-bottom: 1px solid var(--color-gray-200);\n    }\n  }\n\n  .MobileNavLink {\n    flex-grow: 1;\n    display: flex;\n    gap: 4px;\n    align-items: center;\n    height: var(--mobile-nav-item-height);\n    padding-inline: var(--mobile-nav-item-padding-x);\n\n    &:focus-visible {\n      outline: 2px solid var(--color-blue);\n      outline-offset: -1px;\n    }\n\n    @supports not (-webkit-tap-highlight-color: black) {\n      &:active {\n        background-color: var(--color-gray-100);\n      }\n    }\n\n    @supports (-webkit-tap-highlight-color: black) {\n      /* Applying background-color on :active shows it when touching items while scrolling */\n      /* This activates only on real link taps */\n      -webkit-tap-highlight-color: var(--color-gray-300);\n    }\n  }\n\n  .MobileNavBadge {\n    color: var(--color-red);\n    line-height: inherit;\n    user-select: none;\n    text-transform: uppercase;\n    font-size: 0.6875rem;\n    font-weight: 700;\n    letter-spacing: 0.035em;\n    padding-inline: 2px;\n    translate: 0 -3px;\n  }\n\n  .MobileNavCloseContainer {\n    position: sticky;\n    top: 0.75rem;\n    left: 100%;\n    margin-right: 0.75rem;\n    height: 0;\n    width: fit-content;\n  }\n\n  .MobileNavBody {\n    display: flex;\n    flex-direction: column-reverse;\n  }\n\n  .MobileNavClose {\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    color: var(--color-gray-500);\n    outline: 0;\n\n    /* Visible circle */\n    width: 1.75rem;\n    height: 1.75rem;\n    border-radius: 100%;\n    background-color: var(--color-gray-200);\n\n    /* Blur the dividers behind */\n    backdrop-filter: blur(1rem);\n\n    &:focus-visible {\n      outline: 2px solid var(--color-blue);\n      outline-offset: -1px;\n    }\n\n    /* Tap target */\n    &::after {\n      content: '';\n      width: 3rem;\n      height: 3rem;\n      position: absolute;\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/components/MobileNav.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport clsx from 'clsx';\nimport NextLink from 'next/link';\nimport { Dialog } from '@base-ui/react/dialog';\nimport { useScrollLock } from '@base-ui/utils/useScrollLock';\nimport { HEADER_HEIGHT } from './Header';\nimport './MobileNav.css';\n\nconst MobileNavStateCallback = React.createContext<(open: boolean) => void>(() => undefined);\n\nexport function Root(props: Dialog.Root.Props) {\n  const state = React.useState(false);\n  const [open, setOpen] = state;\n\n  return (\n    <MobileNavStateCallback.Provider value={setOpen}>\n      <Dialog.Root open={open} onOpenChange={setOpen} {...props} />\n    </MobileNavStateCallback.Provider>\n  );\n}\n\nexport const Trigger = Dialog.Trigger;\n\nexport function Backdrop(props: Dialog.Backdrop.Props) {\n  return <Dialog.Backdrop {...props} className={clsx('MobileNavBackdrop', props.className)} />;\n}\n\nexport const Portal = Dialog.Portal;\n\nexport function Popup(props: Dialog.Popup.Props) {\n  return (\n    <Dialog.Popup {...props} className={clsx('MobileNavPopup', props.className)}>\n      <PopupImpl>{props.children}</PopupImpl>\n    </Dialog.Popup>\n  );\n}\n\nfunction PopupImpl(props: React.PropsWithChildren) {\n  const [forceScrollLock, setForceScrollLock] = React.useState(false);\n  const setOpen = React.useContext(MobileNavStateCallback);\n  const rem = React.useRef(16);\n  useScrollLock(forceScrollLock);\n\n  React.useEffect(() => {\n    rem.current = parseFloat(getComputedStyle(document.documentElement).fontSize);\n  }, []);\n\n  return (\n    <React.Fragment>\n      <div className=\"MobileNavBottomOverscroll\" />\n      <div\n        className=\"MobileNavViewport\"\n        onScroll={(event) => {\n          const viewport = event.currentTarget;\n          if (viewport.scrollTop > (HEADER_HEIGHT * rem.current) / 16) {\n            viewport.setAttribute('data-clipped', '');\n          } else {\n            viewport.removeAttribute('data-clipped');\n          }\n        }}\n        onTouchStart={(event) => {\n          const viewport = event.currentTarget;\n\n          // Consider flicks from scroll top only (iOS does the same with its sheets)\n          if (viewport.scrollTop <= 0) {\n            viewport.addEventListener(\n              'touchend',\n              function handleTouchEnd() {\n                // If touch ended and we are overscrolling past a threshold...\n                if (viewport.scrollTop < -32) {\n                  const y = viewport.scrollTop;\n                  // Scroll lock is forced during the flick down gesture to maintain\n                  // a continuous blend between the native scroll inertia and our own animation\n                  setForceScrollLock(true);\n\n                  viewport.addEventListener(\n                    'scroll',\n                    function handleNextScroll() {\n                      // ...look at whether the system's inertia scrolling is continuing the motion\n                      // in the same direction. If so, the flick is strong enough to close the dialog.\n                      if (viewport.scrollTop < y) {\n                        // It's gonna eventually bounce back to scrollTop 0. We need to counteract this\n                        // a bit so that the close transition doesn't appear slower than it should.\n                        viewport.style.translate = `0px -${y}px`;\n                        viewport.style.transform = `400ms`;\n                        setOpen(false);\n\n                        // Sometimes the first scroll event comes with the same scroll position\n                        // If so, give it another chance, call ourselves recursively\n                      } else if (viewport.scrollTop === y) {\n                        viewport.addEventListener('scroll', handleNextScroll, { once: true });\n                      } else {\n                        setForceScrollLock(false);\n                      }\n                    },\n                    { once: true },\n                  );\n                }\n              },\n              { once: true },\n            );\n          }\n        }}\n      >\n        <div className=\"MobileNavViewportInner\">\n          {/* We need the area behind the panel to close on tap but also to scroll the viewport. */}\n          <Dialog.Close\n            className=\"MobileNavBackdropTapArea\"\n            tabIndex={-1}\n            nativeButton={false}\n            render={<div />}\n          />\n\n          <nav className=\"MobileNavPanel\">\n            {/* Reverse order to place the close button at the end of the DOM, but at sticky top visually */}\n            <div className=\"MobileNavBody\">\n              <div>{props.children}</div>\n              <div className=\"MobileNavCloseContainer\">\n                <Dialog.Close aria-label=\"Close the navigation\" className=\"MobileNavClose\">\n                  <svg\n                    width=\"12\"\n                    height=\"12\"\n                    viewBox=\"0 0 12 12\"\n                    fill=\"none\"\n                    xmlns=\"http://www.w3.org/2000/svg\"\n                  >\n                    <path\n                      d=\"M0.75 0.75L6 6M11.25 11.25L6 6M6 6L0.75 11.25M6 6L11.25 0.75\"\n                      stroke=\"currentcolor\"\n                      strokeWidth=\"2\"\n                      strokeLinecap=\"round\"\n                      strokeLinejoin=\"round\"\n                    />\n                  </svg>\n                </Dialog.Close>\n              </div>\n            </div>\n          </nav>\n        </div>\n      </div>\n    </React.Fragment>\n  );\n}\n\nexport function Section(props: React.ComponentProps<'div'>) {\n  return <div {...props} className={clsx('MobileNavSection', props.className)} />;\n}\n\nexport function Heading(props: React.ComponentProps<'div'>) {\n  return (\n    <div {...props} className={clsx('MobileNavHeading', props.className)}>\n      <div className=\"MobileNavHeadingInner\">{props.children}</div>\n    </div>\n  );\n}\n\nexport function List(props: React.ComponentProps<'ul'>) {\n  return <ul {...props} className={clsx('MobileNavList', props.className)} />;\n}\n\nexport function Badge(props: React.ComponentProps<'span'>) {\n  return <span {...props} className={clsx('MobileNavBadge', props.className)} />;\n}\n\ninterface ItemProps extends React.ComponentPropsWithoutRef<'li'> {\n  active?: boolean;\n  href: string;\n  rel?: string;\n  external?: boolean;\n}\n\nexport function Item({ href, external, ...props }: ItemProps) {\n  const setOpen = React.useContext(MobileNavStateCallback);\n\n  const LinkComponent = external ? 'a' : NextLink;\n\n  return (\n    <li {...props} className={clsx('MobileNavItem', props.className)}>\n      <LinkComponent\n        aria-current={props.active ? 'page' : undefined}\n        className=\"MobileNavLink\"\n        href={href}\n        rel={props.rel}\n        // We handle scroll manually\n        scroll={external ? undefined : false}\n        onClick={() => {\n          if (href === window.location.pathname) {\n            // If the URL is the same, close, wait a little, and scroll to top smoothly\n            setOpen(false);\n            setTimeout(() => {\n              window.scrollTo({ top: 0, behavior: 'smooth' });\n            }, 500);\n          } else {\n            // Otherwise, wait for the URL change before closing and scroll up instantly\n            onUrlChange(() => {\n              ReactDOM.flushSync(() => setOpen(false));\n              window.scrollTo({ top: 0, behavior: 'instant' });\n            });\n          }\n        }}\n      >\n        {props.children}\n      </LinkComponent>\n    </li>\n  );\n}\n\nfunction onUrlChange(callback: () => void) {\n  const initialUrl = window.location.href;\n\n  function rafRecursively() {\n    requestAnimationFrame(() => {\n      if (initialUrl === window.location.href) {\n        rafRecursively();\n      } else {\n        callback();\n      }\n    });\n  }\n\n  rafRecursively();\n}\n"
  },
  {
    "path": "docs/src/components/Popup.css",
    "content": "@layer components {\n  .Popup {\n    max-width: var(--available-width, none);\n    max-height: var(--available-height, none);\n    border-radius: var(--radius-md);\n    background-color: var(--color-popup);\n    box-shadow:\n      0 154px 62px 0 rgb(36 40 52 / 1%),\n      0 87px 52px 0 rgb(36 40 52 / 3%),\n      0 39px 39px 0 rgb(36 40 52 / 4%),\n      0 10px 21px 0 rgb(36 40 52 / 5%);\n\n    outline: 1px solid var(--color-gray-200);\n\n    @media (prefers-color-scheme: dark) {\n      /* Use stronger outline in dark mode because the shadow isn't really visible */\n      outline-color: var(--color-gray-300);\n      outline-offset: -1px;\n    }\n\n    transform-origin: var(--transform-origin, center);\n    transition-duration: 120ms;\n    transition-property: opacity, transform;\n    transition-timing-function: var(--ease-out-fast);\n\n    &[data-starting-style],\n    &[data-ending-style] {\n      opacity: 0;\n\n      @media (prefers-reduced-motion: no-preference) {\n        transform: scale(0.98);\n      }\n    }\n\n    &[data-ending-style] {\n      transition-timing-function: var(--ease-in-slow);\n    }\n\n    overflow: auto;\n    overscroll-behavior-x: contain;\n    scrollbar-width: none;\n\n    &::-webkit-scrollbar {\n      display: none;\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/components/Popup.tsx",
    "content": "import * as React from 'react';\nimport clsx from 'clsx';\nimport './Popup.css';\n\nexport function Popup({ className, ...props }: React.ComponentProps<'div'>) {\n  return <div className={clsx('Popup', className)} {...props} />;\n}\n"
  },
  {
    "path": "docs/src/components/QuickNav/QuickNav.css",
    "content": "@import 'docs/src/css/custom-media.css';\n\n@layer components {\n  .QuickNavContainer {\n    /* Quick Nav's container shouldn't have any paddings to maintain correct positioning */\n    padding: 0;\n    position: relative;\n  }\n\n  .QuickNavRoot {\n    --quick-nav-margin-x: 2rem;\n\n    /* Use line height instead of fixed item height in case text breaks into multiple lines */\n    --quick-nav-item-height: 2rem;\n    --quick-nav-item-line-height: var(--text-md--line-height);\n    --quick-nav-item-padding-y: calc(\n      var(--quick-nav-item-height) / 2 - var(--quick-nav-item-line-height) / 2\n    );\n\n    /* The variable is used in JS positioning logic */\n    --top: -1px;\n    --margin-top: 5.75rem; /* Match hero code block top */\n\n    font-size: var(--text-md);\n    line-height: var(--text-md--line-height);\n    z-index: 1;\n    position: sticky;\n    top: var(--top);\n    margin-top: var(--margin-top);\n    width: 0;\n    float: right;\n    contain: layout;\n    display: none;\n\n    @media (--show-quick-nav) {\n      display: block;\n    }\n  }\n\n  .QuickNavInner {\n    border-top: 1px solid var(--color-gray-200);\n    position: relative;\n    left: var(--quick-nav-margin-x);\n    padding-top: 0.75rem;\n    padding-bottom: 2.5rem;\n    width: calc(var(--sidebar-width) - var(--quick-nav-margin-x) * 2);\n  }\n\n  .QuickNavTitle {\n    padding-block: var(--quick-nav-item-padding-y);\n    font-weight: 700;\n  }\n\n  .QuickNavList {\n    color: var(--color-gray);\n    display: flex;\n    flex-direction: column;\n    align-items: start;\n\n    & & {\n      padding-left: 0.75rem;\n    }\n  }\n\n  .QuickNavLink {\n    display: flex;\n    padding: var(--quick-nav-item-padding-y) 0.5rem;\n    margin-inline: var(--quick-nav-item-padding-y) -0.5rem;\n    border-radius: var(--radius-md);\n\n    &:focus-visible {\n      z-index: 1;\n      outline: 2px solid var(--color-blue);\n      outline-offset: -2px;\n    }\n\n    @media (hover: hover) {\n      &:hover {\n        text-decoration: underline;\n        text-underline-offset: 2px;\n        text-decoration-thickness: 1px;\n        text-decoration-color: var(--color-gray-500);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/components/QuickNav/QuickNav.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport clsx from 'clsx';\nimport { usePathname } from 'next/navigation';\nimport { useGoogleAnalytics } from 'docs/src/blocks/GoogleAnalyticsProvider';\nimport './QuickNav.css';\n\nexport function Container({ className, ...props }: React.ComponentProps<'div'>) {\n  return <div className={clsx('QuickNavContainer', className)} {...props} />;\n}\n\nexport function Root({ children, className, ...props }: React.ComponentProps<'div'>) {\n  const ref = React.useRef<HTMLDivElement>(null);\n  React.useEffect(() => onMounted(ref), []);\n  return (\n    <nav aria-label=\"On this page\" ref={ref} className={clsx('QuickNavRoot', className)} {...props}>\n      <div className=\"QuickNavInner\">{children}</div>\n    </nav>\n  );\n}\n\n// When nav height is larger than viewport, we make document scroll push it around\n// so it sticks to top or bottom depending on scroll direction as if it was a real\n// physical object constrained by the viewport.\nfunction onMounted(ref: React.RefObject<HTMLDivElement | null>) {\n  if (!ref.current) {\n    return undefined;\n  }\n\n  const rem = parseFloat(getComputedStyle(document.documentElement).fontSize);\n  /** How much of the nav should be cut off at the bottom to stop using the default sticky top position */\n  const stickyTopThreshold = 2.25 * rem;\n\n  let top: number;\n  let bottom: number;\n  let prevScrollY = window.scrollY;\n  let resizeObserver: ResizeObserver | undefined;\n  let state: 'Scrollable' | 'StickyTop' | 'StickyBottom' = 'StickyTop';\n  let raf = 0;\n\n  const cssTop = parseFloat(getComputedStyle(ref.current).top);\n\n  let cachedPositions: {\n    staticTop: number;\n    absoluteTop: number;\n    absoluteBottom: number;\n  } | null = null;\n\n  function getCachedPositions() {\n    if (cachedPositions === null) {\n      cachedPositions = getNaturalPositions();\n    }\n\n    return cachedPositions;\n  }\n\n  function getNaturalPositions() {\n    if (!ref.current) {\n      return { absoluteTop: 0, staticTop: 0, absoluteBottom: 0 };\n    }\n\n    const initialStyles = {\n      top: ref.current.style.top,\n      bottom: ref.current.style.bottom,\n      marginTop: ref.current.style.marginTop,\n      marginBottom: ref.current.style.marginBottom,\n    };\n\n    ref.current.style.top = '0px';\n    ref.current.style.bottom = '';\n    ref.current.style.marginTop = '';\n    ref.current.style.marginBottom = '';\n\n    // Get the nav top Y coordinate from the start of the document\n    // if it was `position: static` and `position: absolute`\n    // relative to the start of the document\n    ref.current.style.position = 'static';\n    const staticTop = window.scrollY + Math.round(ref.current.getBoundingClientRect().y);\n    ref.current.style.marginTop = '0px';\n    ref.current.style.position = 'absolute';\n    const absoluteTop = window.scrollY + Math.round(ref.current.getBoundingClientRect().y);\n\n    // Get the nav bottom Y coordinate when it's at its maximum possible bottom position\n    // relative to the start of the document\n    ref.current.style.position = 'absolute';\n    ref.current.style.top = 'auto';\n    ref.current.style.bottom = '0';\n    const rect = ref.current.getBoundingClientRect();\n    const absoluteBottom = window.scrollY + Math.round(rect.bottom);\n\n    ref.current.style.position = '';\n    ref.current.style.top = initialStyles.top;\n    ref.current.style.bottom = initialStyles.bottom;\n    ref.current.style.marginTop = initialStyles.marginTop;\n    ref.current.style.marginBottom = initialStyles.marginBottom;\n    // Remove the style attribute if it's empty so that the DOM is tidy\n    if (ref.current?.style.length === 0) {\n      ref.current.removeAttribute('style');\n    }\n\n    return { absoluteTop, staticTop, absoluteBottom };\n  }\n\n  function setHeightProperty() {\n    if (!resizeObserver && ref.current) {\n      resizeObserver = new ResizeObserver(([entry]) => {\n        const [{ blockSize }] = entry.borderBoxSize;\n        ref.current?.style.setProperty('--height', `${blockSize}px`);\n      });\n      resizeObserver.observe(ref.current);\n    }\n  }\n\n  function stickToTop() {\n    if (ref.current) {\n      state = 'StickyTop';\n      ref.current.style.top = '';\n      ref.current.style.bottom = '';\n      ref.current.style.marginTop = '';\n      ref.current.style.marginBottom = '';\n    }\n  }\n\n  function stickToBottom() {\n    if (ref.current) {\n      state = 'StickyBottom';\n      setHeightProperty();\n      ref.current.style.top = `min(var(--top), 100dvh - var(--height))`;\n      ref.current.style.bottom = '';\n      ref.current.style.marginTop = '';\n      ref.current.style.marginBottom = '';\n    }\n  }\n\n  function unstick(newTop: number, newBottom: number) {\n    if (ref.current) {\n      state = 'Scrollable';\n      const { absoluteTop, absoluteBottom, staticTop } = getCachedPositions();\n      const marginTop = Math.max(staticTop - absoluteTop, window.scrollY + newTop - absoluteTop);\n      const marginBottom = Math.max(0, absoluteBottom - window.scrollY - newBottom);\n\n      // Choose the smaller margin because at document edges,\n      // the larger one may push the nav out of the container edges\n      if (marginTop < marginBottom) {\n        ref.current.style.top = 'auto';\n        ref.current.style.bottom = '0';\n        ref.current.style.marginTop = marginTop ? `${marginTop}px` : '';\n        ref.current.style.marginBottom = '';\n      } else {\n        ref.current.style.top = '';\n        ref.current.style.bottom = '';\n        ref.current.style.marginTop = '';\n        ref.current.style.marginBottom = marginBottom ? `${marginBottom}px` : '';\n      }\n    }\n  }\n\n  function handleUpdate() {\n    cancelAnimationFrame(raf);\n    raf = requestAnimationFrame(() => {\n      const isScrollLocked = document.documentElement.hasAttribute('data-base-ui-scroll-locked');\n\n      if (!ref.current || isScrollLocked) {\n        return;\n      }\n      const delta = window.scrollY - prevScrollY;\n      prevScrollY = window.scrollY;\n\n      const rect = ref.current.getBoundingClientRect();\n      top = rect.top;\n      bottom = rect.bottom;\n\n      // Skip when scrolling in the direction that matches the sticky position\n      if ((delta > 0 && state === 'StickyBottom') || (delta < 0 && state === 'StickyTop')) {\n        return;\n      }\n\n      // Should be top-sticky if the entire nav can fit in the viewport\n      if (rect.height + cssTop <= window.innerHeight) {\n        if (state !== 'StickyTop') {\n          stickToTop();\n        }\n        return;\n      }\n\n      if (state === 'StickyTop') {\n        const clippedAtBottom = bottom - window.innerHeight;\n\n        if (clippedAtBottom - top > stickyTopThreshold) {\n          if (delta >= clippedAtBottom) {\n            stickToBottom();\n            // Unstick if we are scrolling down (and not recovering from overscroll)\n          } else if (delta > 0 && !isOverscrolling()) {\n            unstick(Math.round(top) - delta, Math.round(bottom) - delta);\n          }\n        }\n        return;\n      }\n\n      if (state === 'StickyBottom') {\n        if (delta <= top) {\n          stickToTop();\n          // Unstick if we are scrolling up (and not recovering from overscroll)\n        } else if (delta < 0 && !isOverscrolling()) {\n          unstick(Math.round(top) - delta, Math.round(bottom) - delta);\n        }\n        return;\n      }\n\n      if (state === 'Scrollable' && delta < 0 && top - delta >= cssTop) {\n        stickToTop();\n        return;\n      }\n\n      if (state === 'Scrollable' && delta >= 0 && bottom - delta <= window.innerHeight) {\n        stickToBottom();\n      }\n    });\n  }\n  const requestIdleCallback = window.requestIdleCallback ?? setTimeout;\n  const cancelIdleCallback = window.cancelIdleCallback ?? clearTimeout;\n\n  let callbackId = 0;\n  function handleResize() {\n    cancelIdleCallback(callbackId);\n    callbackId = requestIdleCallback(() => {\n      cachedPositions = getNaturalPositions();\n      handleUpdate();\n    });\n  }\n\n  // Maintain nav position as much as possible to avoid layout shifts\n  let hash = window.location.hash;\n  let pathname = window.location.pathname;\n  function handlePopState() {\n    if (hash !== window.location.hash && pathname === window.location.pathname) {\n      window.removeEventListener('scroll', handleUpdate);\n\n      requestAnimationFrame(() => {\n        if (state === 'Scrollable') {\n          unstick(Math.min(cssTop, top), Math.max(window.innerHeight, bottom));\n        }\n\n        prevScrollY = window.scrollY;\n        window.addEventListener('scroll', handleUpdate);\n      });\n    }\n    hash = window.location.hash;\n    pathname = window.location.pathname;\n  }\n\n  requestIdleCallback(getCachedPositions);\n  requestIdleCallback(handleUpdate);\n  window.addEventListener('scroll', handleUpdate);\n  window.addEventListener('resize', handleResize);\n  window.addEventListener('popstate', handlePopState);\n\n  return () => {\n    resizeObserver?.disconnect();\n    window.removeEventListener('scroll', handleUpdate);\n    window.removeEventListener('resize', handleResize);\n    window.removeEventListener('popstate', handlePopState);\n  };\n}\n\nfunction isOverscrolling() {\n  return (\n    window.scrollY < 0 ||\n    window.scrollY + window.innerHeight > document.documentElement.scrollHeight\n  );\n}\n\nexport function Title({ className, ...props }: React.ComponentProps<'header'>) {\n  return <header className={clsx('QuickNavTitle', className)} {...props} />;\n}\n\nexport function List({ className, ...props }: React.ComponentProps<'ul'>) {\n  return <ul className={clsx('QuickNavList', className)} {...props} />;\n}\n\nexport function Item({ className, ...props }: React.ComponentProps<'li'>) {\n  return <li className={clsx('QuickNavItem', className)} {...props} />;\n}\n\nexport function Link({ className, onClick, ...props }: React.ComponentProps<'a'>) {\n  const ga = useGoogleAnalytics();\n  const pathname = usePathname();\n\n  const handleClick = React.useCallback(\n    (event: React.MouseEvent<HTMLAnchorElement>) => {\n      const slug = props.href ?? undefined;\n      const tocId = slug ? `${pathname}${slug}` : pathname;\n      ga?.trackEvent({\n        category: 'table_of_contents',\n        action: 'click',\n        label: tocId,\n        params: { click: tocId, slug: slug || '' },\n      });\n      onClick?.(event);\n    },\n    [ga, props.href, onClick, pathname],\n  );\n\n  // The anchor element is interactive via `href` from `...props`, but the\n  // lint rules can't see through the spread to know that.\n  // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions\n  return <a className={clsx('QuickNavLink', className)} {...props} onClick={handleClick} />;\n}\n"
  },
  {
    "path": "docs/src/components/QuickNav/rehypeConcatHeadings.mjs",
    "content": "/* eslint-disable no-plusplus */\nimport { headingRank } from 'hast-util-heading-rank';\nimport { toString } from 'hast-util-to-string';\nimport { visit, CONTINUE, EXIT } from 'unist-util-visit';\nimport { stringToUrl } from './rehypeSlug.mjs';\n\nexport function rehypeConcatHeadings() {\n  return (tree) => {\n    /**\n     * Forms page: prefix <h3>s under React Hook Form/TanStack Form with the library name\n     */\n    visit(tree, 'element', (node, _, parent) => {\n      if (headingRank(node) === 1 && toString(node) !== 'Forms') {\n        return EXIT;\n      }\n\n      if (headingRank(node) === 3) {\n        let index = parent.children.indexOf(node);\n        while (index--) {\n          const candidate = parent.children[index];\n          if (headingRank(candidate) === 2) {\n            const h2Text = toString(candidate);\n            if (h2Text === 'React Hook Form' || h2Text === 'TanStack Form') {\n              const h3Text = toString(node);\n              node.properties.id = stringToUrl(`${h2Text} ${h3Text}`);\n              break;\n            }\n          }\n        }\n      } else if (headingRank(node) && !node.properties.id) {\n        node.properties.id = stringToUrl(toString(node));\n      }\n      return CONTINUE;\n    });\n  };\n}\n"
  },
  {
    "path": "docs/src/components/QuickNav/rehypeQuickNav.mjs",
    "content": "// @ts-check\nimport { createMdxElement } from 'docs/src/mdx/createMdxElement.mjs';\n\nconst ROOT = 'QuickNav.Root';\nconst TITLE = 'QuickNav.Title';\nconst LIST = 'QuickNav.List';\nconst ITEM = 'QuickNav.Item';\nconst LINK = 'QuickNav.Link';\n\n/**\n * @typedef {Object} TocEntry\n * @property {string} value\n * @property {number} depth\n * @property {string} [id]\n * @property {TocEntry[]} [children]\n */\n\n/**\n * @returns {function(*, *): void}\n */\nexport function rehypeQuickNav() {\n  return (tree, file) => {\n    /** @type {TocEntry[]} */\n    const toc = file.data.toc;\n    const root = createMdxElement({\n      name: ROOT,\n      children: toc.flatMap(getNodeFromEntry).filter(Boolean),\n    });\n\n    if (!toc.length) {\n      return;\n    }\n\n    tree.children.unshift(root);\n  };\n}\n\n/**\n * @param {TocEntry} entry\n * @returns {Record<string, unknown> | Record<string, unknown>[]}\n */\nfunction getNodeFromEntry({ value, depth, id, children }) {\n  const sub = createMdxElement({\n    name: LIST,\n    children: [],\n  });\n\n  // Ignore <h4>'s and below\n  if (depth < 3 && children?.length) {\n    sub.children = children.map(getNodeFromEntry);\n  }\n\n  if (depth === 1) {\n    // Insert \"(Top)\" link\n    sub.children?.unshift(getNodeFromEntry({ value: '(Top)', id: '', depth: 2 }));\n\n    return [\n      // Insert a top-level title\n      createMdxElement({\n        name: TITLE,\n        children: [{ type: 'text', value }],\n      }),\n      sub,\n    ];\n  }\n\n  const link = createMdxElement({\n    name: LINK,\n    children: [{ type: 'text', value }],\n    props: {\n      href: `#${id}`,\n    },\n  });\n\n  return createMdxElement({\n    name: ITEM,\n    children: [link, sub],\n  });\n}\n"
  },
  {
    "path": "docs/src/components/QuickNav/rehypeSlug.mjs",
    "content": "import { headingRank } from 'hast-util-heading-rank';\nimport { toString } from 'hast-util-to-string';\nimport { visit } from 'unist-util-visit';\n\n// This is a fork of https://github.com/rehypejs/rehype-slug/blob/main/lib/index.js, but better\n\n/**\n * @typedef {import('hast').Root} Root\n */\n\n/**\n * @typedef Options\n *   Configuration (optional).\n * @property {string} [prefix='']\n *   Prefix to add in front of `id`s (default: `''`).\n */\n\n/** @type {Options} */\nconst emptyOptions = {};\n\n/**\n * Add `id`s to headings.\n *\n * @param {Options | null | undefined} [options]\n *   Configuration (optional).\n * @returns\n *   Transform.\n */\nexport function rehypeSlug(options) {\n  const settings = options || emptyOptions;\n  const prefix = settings.prefix || '';\n\n  /**\n   * @param {Root} tree\n   *   Tree.\n   * @returns {undefined}\n   *   Nothing.\n   */\n  return (tree) => {\n    visit(tree, 'element', (node) => {\n      if (headingRank(node) && !node.properties.id) {\n        node.properties.id = prefix + stringToUrl(toString(node));\n      }\n      return undefined;\n    });\n  };\n}\n\n/**\n * Converts a string into a well-formatted URL material, taking into account common contractions\n * `\"1. Here’s a wicked example & more.\" => \"1-here-is-a-wicked-example-and-more\"`\n */\nexport function stringToUrl(string) {\n  return string\n    .replace(/([a-z])('|’|‘)ll([^a-z])/gi, '$1-will$3') // \"you'll, we'll\" => \"you-will, we-will\"\n    .replace(/won('|’|‘)t([^a-z])/gi, 'will-not$2') // \"won't\" => \"will-not\"\n    .replace(/can('|’|‘)t([^a-z])/gi, 'cannot$2') // \"can't\" => \"cannot\"\n    .replace(/n('|’|‘)t([^a-z])/gi, '-not$2') // \"don't, doesn't, wouldn't, etc\" => \"do-not, does-not, would-not, etc\"\n    .replace(/([a-z])('|’|‘)ve/gi, '$1-have') // \"you've, could've\" => \"you-have, could-have\"\n    .replace(/([a-z])('|’|‘)re/gi, '$1-are') // \"you're, there're\" => \"you-are, there-are\"\n    .replace(/(that|there|they)('|’|‘)d/gi, '$1-would') // \"that'd\" => \"that-would\"\n    .replace(/(that|there|here)('|’|‘)s/gi, '$1-is') // \"that's\" => \"that-is\"\n    .replace(/([a-z])('|’|‘)s([^a-z])/gi, '$1s$3') // \"user's, client's\" => \"users, clients\"\n    .replace(/\\s+|_|–|—|'|’|‘|\"|“|”|\\./gi, '-')\n    .replace(/&/gi, '-and-')\n    .replace(/[^a-z0-9-]/gi, '')\n    .replace(/-+/gi, '-')\n    .replace(/^-|-$/gi, '')\n    .toLowerCase();\n}\n"
  },
  {
    "path": "docs/src/components/ReferenceTable/AttributesReferenceTable.tsx",
    "content": "import * as React from 'react';\nimport clsx from 'clsx';\nimport { createMdxComponent } from 'docs/src/mdx/createMdxComponent';\nimport { inlineMdxComponents } from 'docs/src/mdx-components';\nimport { rehypeSyntaxHighlighting } from 'docs/src/syntax-highlighting';\nimport type { AttributeDef } from './types';\nimport * as Table from '../Table';\nimport * as Accordion from '../Accordion';\nimport { TableCode } from '../TableCode';\n\ninterface AttributesReferenceTableProps extends React.ComponentProps<typeof Table.Root> {\n  data: Record<string, AttributeDef>;\n  name?: string;\n}\n\nconst CREATE_MDX_OPTIONS = {\n  rehypePlugins: rehypeSyntaxHighlighting,\n  useMDXComponents: () => inlineMdxComponents,\n};\n\nexport async function AttributesReferenceTable({\n  data,\n  name: partName,\n  ...props\n}: AttributesReferenceTableProps) {\n  return (\n    <React.Fragment>\n      <Accordion.Root {...props} className={clsx(props.className, 'bp0:bui-d-n')}>\n        <Accordion.HeaderRow>\n          <Accordion.HeaderCell className=\"bui-pl-3\">Attribute</Accordion.HeaderCell>\n        </Accordion.HeaderRow>\n        {Object.keys(data).map(async (name, index) => {\n          const attribute = data[name];\n\n          const AttributeDescription = await createMdxComponent(\n            attribute.description,\n            CREATE_MDX_OPTIONS,\n          );\n\n          return (\n            <Accordion.Item\n              key={name}\n              gaCategory=\"reference\"\n              gaLabel={`Attribute: ${partName ? `${partName}-` : ''}${name}`}\n              gaParams={{\n                type: 'attribute',\n                slug: `${partName ? `${partName}-` : ''}${name}`,\n                part_name: partName || '',\n              }}\n            >\n              <Accordion.Trigger index={index}>\n                <TableCode style={{ color: 'var(--color-navy)' }}>{name}</TableCode>\n                <svg\n                  className=\"AccordionIcon bui-ml-a bui-mr-1\"\n                  width=\"10\"\n                  height=\"10\"\n                  viewBox=\"0 0 10 10\"\n                  fill=\"none\"\n                  xmlns=\"http://www.w3.org/2000/svg\"\n                >\n                  <path d=\"M1 3.5L5 7.5L9 3.5\" stroke=\"currentcolor\" />\n                </svg>\n              </Accordion.Trigger>\n              <Accordion.Panel>\n                <Accordion.Content className=\"ReferenceCompactPanel\">\n                  <AttributeDescription />\n                </Accordion.Content>\n              </Accordion.Panel>\n            </Accordion.Item>\n          );\n        })}\n      </Accordion.Root>\n      <Table.Root {...props} className={clsx('bui-d-n', 'bp0:bui-d-b', props.className)}>\n        <Table.Head>\n          <Table.Row>\n            {/* widths must match the props table grid layout */}\n            <Table.ColumnHeader className=\"ReferenceWideNameColumn\">Attribute</Table.ColumnHeader>\n            <Table.ColumnHeader className=\"ReferenceWideDescriptionColumn\">\n              Description\n            </Table.ColumnHeader>\n            {/* A cell to maintain a layout consistent with the props table */}\n            <Table.ColumnHeader className=\"bui-w-10\" aria-hidden>\n              <span className=\"bui-v-h\">{'-'}</span>\n            </Table.ColumnHeader>\n          </Table.Row>\n        </Table.Head>\n        <Table.Body>\n          {Object.keys(data).map(async (name) => {\n            const attribute = data[name];\n\n            const AttributeDescription = await createMdxComponent(\n              attribute.description,\n              CREATE_MDX_OPTIONS,\n            );\n\n            return (\n              <Table.Row key={name}>\n                <Table.RowHeader>\n                  <TableCode className=\"bui-ws-nw\" style={{ color: 'var(--color-navy)' }}>\n                    {name}\n                  </TableCode>\n                </Table.RowHeader>\n                <Table.Cell colSpan={2}>\n                  <AttributeDescription />\n                </Table.Cell>\n              </Table.Row>\n            );\n          })}\n        </Table.Body>\n      </Table.Root>\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "docs/src/components/ReferenceTable/CssVariablesReferenceTable.tsx",
    "content": "import * as React from 'react';\nimport clsx from 'clsx';\nimport { createMdxComponent } from 'docs/src/mdx/createMdxComponent';\nimport { inlineMdxComponents } from 'docs/src/mdx-components';\nimport { rehypeSyntaxHighlighting } from 'docs/src/syntax-highlighting';\nimport * as Accordion from '../Accordion';\nimport * as Table from '../Table';\nimport type { CssVariableDef } from './types';\nimport { TableCode } from '../TableCode';\n\ninterface CssVariablesReferenceTableProps extends React.ComponentProps<typeof Table.Root> {\n  data: Record<string, CssVariableDef>;\n  name?: string;\n}\n\nconst CREATE_MDX_OPTIONS = {\n  rehypePlugins: rehypeSyntaxHighlighting,\n  useMDXComponents: () => inlineMdxComponents,\n};\n\nexport async function CssVariablesReferenceTable({\n  data,\n  name: partName,\n  ...props\n}: CssVariablesReferenceTableProps) {\n  return (\n    <React.Fragment>\n      <Accordion.Root {...props} className={clsx(props.className, 'bp0:bui-d-n')}>\n        <Accordion.HeaderRow>\n          <Accordion.HeaderCell className=\"bui-pl-3\">CSS Variable</Accordion.HeaderCell>\n        </Accordion.HeaderRow>\n        {Object.keys(data).map(async (name, index) => {\n          const attribute = data[name];\n\n          const AttributeDescription = await createMdxComponent(\n            attribute.description,\n            CREATE_MDX_OPTIONS,\n          );\n\n          return (\n            <Accordion.Item\n              key={name}\n              gaCategory=\"reference\"\n              gaLabel={`CSS variable: ${partName ? `${partName}-` : ''}${name}`}\n              gaParams={{\n                type: 'css_variable',\n                slug: `${partName ? `${partName}-` : ''}${name}`,\n                part_name: partName || '',\n              }}\n            >\n              <Accordion.Trigger index={index}>\n                <TableCode style={{ color: 'var(--color-navy)' }}>{name}</TableCode>\n                <svg\n                  className=\"AccordionIcon bui-ml-a bui-mr-1\"\n                  width=\"10\"\n                  height=\"10\"\n                  viewBox=\"0 0 10 10\"\n                  fill=\"none\"\n                  xmlns=\"http://www.w3.org/2000/svg\"\n                >\n                  <path d=\"M1 3.5L5 7.5L9 3.5\" stroke=\"currentcolor\" />\n                </svg>\n              </Accordion.Trigger>\n              <Accordion.Panel>\n                <Accordion.Content className=\"ReferenceCompactPanel\">\n                  <AttributeDescription />\n                </Accordion.Content>\n              </Accordion.Panel>\n            </Accordion.Item>\n          );\n        })}\n      </Accordion.Root>\n\n      <Table.Root {...props} className={clsx('bui-d-n', 'bp0:bui-d-b', props.className)}>\n        <Table.Head>\n          <Table.Row>\n            {/* widths must match the props table grid layout */}\n            <Table.ColumnHeader className=\"ReferenceWideNameColumn\">\n              CSS Variable\n            </Table.ColumnHeader>\n            <Table.ColumnHeader className=\"ReferenceWideDescriptionColumn\">\n              Description\n            </Table.ColumnHeader>\n            {/* A cell to maintain a layout consistent with the props table */}\n            <Table.ColumnHeader className=\"bui-w-10\" aria-hidden>\n              <span className=\"bui-v-h\">{'-'}</span>\n            </Table.ColumnHeader>\n          </Table.Row>\n        </Table.Head>\n        <Table.Body>\n          {Object.keys(data).map(async (name) => {\n            const cssVariable = data[name];\n\n            const CssVariableDescription = await createMdxComponent(\n              cssVariable.description,\n              CREATE_MDX_OPTIONS,\n            );\n\n            return (\n              <Table.Row key={name}>\n                <Table.RowHeader>\n                  <TableCode style={{ color: 'var(--color-navy)' }}>{name}</TableCode>\n                </Table.RowHeader>\n                <Table.Cell colSpan={2}>\n                  <CssVariableDescription />\n                </Table.Cell>\n              </Table.Row>\n            );\n          })}\n        </Table.Body>\n      </Table.Root>\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "docs/src/components/ReferenceTable/ParametersReferenceTable.tsx",
    "content": "import * as React from 'react';\nimport { ReferenceAccordion } from './ReferenceAccordion';\nimport type { FunctionParamDef, PropDef } from './types';\n\ninterface ParametersReferenceTableProps extends React.ComponentPropsWithoutRef<any> {\n  data: Record<string, FunctionParamDef>;\n  name: string;\n  renameFrom?: string;\n  renameTo?: string;\n}\n\nfunction normalizeParameters(data: Record<string, FunctionParamDef>) {\n  return Object.fromEntries(\n    Object.entries(data).map(([name, param]) => {\n      const { optional, ...rest } = param;\n      const normalized: PropDef = {\n        ...rest,\n        required: optional ? undefined : true,\n      };\n      return [name, normalized];\n    }),\n  );\n}\n\nexport async function ParametersReferenceTable({\n  data,\n  name,\n  renameFrom,\n  renameTo,\n  ...props\n}: ParametersReferenceTableProps) {\n  return (\n    <ReferenceAccordion\n      {...props}\n      name={name}\n      data={normalizeParameters(data)}\n      renameFrom={renameFrom}\n      renameTo={renameTo}\n      nameLabel=\"Parameter\"\n      caption=\"Function parameters table\"\n    />\n  );\n}\n"
  },
  {
    "path": "docs/src/components/ReferenceTable/ReferenceAccordion.tsx",
    "content": "import * as React from 'react';\nimport { visuallyHidden } from '@base-ui/utils/visuallyHidden';\nimport { createMdxComponent } from 'docs/src/mdx/createMdxComponent';\nimport { inlineMdxComponents, mdxComponents } from 'docs/src/mdx-components';\nimport { rehypeSyntaxHighlighting } from 'docs/src/syntax-highlighting';\nimport { Link } from 'docs/src/components/Link';\nimport * as Accordion from '../Accordion';\nimport * as DescriptionList from '../DescriptionList';\nimport type { PropDef as BasePropDef } from './types';\nimport { TableCode, type TableCodeProps } from '../TableCode';\nimport * as ReferenceTableTooltip from './ReferenceTableTooltip';\n\nfunction ExpandedCode(props: React.ComponentProps<'code'>) {\n  const { className = '', ...other } = props;\n  const cleaned = className\n    .split(' ')\n    .filter((c) => c !== 'Code')\n    .join(' ');\n  return <code {...other} className={cleaned} />;\n}\n\nfunction ExpandedPre(props: React.ComponentProps<'pre'>) {\n  return (\n    <Accordion.Scrollable tag=\"div\" gradientColor=\"var(--color-gray-50)\">\n      <pre {...props} className=\"ReferencePre\" style={{ backgroundColor: undefined }} />\n    </Accordion.Scrollable>\n  );\n}\n\ninterface PropDef extends BasePropDef {\n  detailedType?: string;\n  example?: string;\n}\n\ninterface Props extends React.ComponentPropsWithoutRef<any> {\n  data: Record<string, PropDef>;\n  type?: 'props' | 'return';\n  name: string;\n  // When reusing a component's reference for another component,\n  // replace occurrences of \"renameFrom.*\" with \"renameTo.*\" in types\n  renameFrom?: string;\n  renameTo?: string;\n  nameLabel?: string;\n  caption?: string;\n}\n\nfunction getShortPropType(name: string, type: string | undefined) {\n  if (/^(on|get)[A-Z].*/.test(name)) {\n    return { type: 'function', detailedType: true };\n  }\n\n  if (type === undefined || type === null) {\n    return { type: String(type), detailedType: false };\n  }\n\n  if (name === 'className') {\n    return { type: 'string | function', detailedType: true };\n  }\n\n  if (name === 'style') {\n    return { type: 'React.CSSProperties | function', detailedType: true };\n  }\n\n  if (name === 'render') {\n    return { type: 'ReactElement | function', detailedType: true };\n  }\n\n  if (\n    name.endsWith('Ref') ||\n    name === 'children' ||\n    type === 'boolean' ||\n    type === 'string' ||\n    type === 'number' ||\n    type.indexOf(' | ') === -1 ||\n    (type.split('|').length < 3 && type.length < 30)\n  ) {\n    return { type, detailedType: false };\n  }\n\n  return { type: 'Union', detailedType: true };\n}\n\nfunction escapeRegExp(input: string) {\n  return input.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nfunction replaceComponentPrefix(input: string | undefined, from?: string, to?: string) {\n  if (!input || !from || !to) {\n    return input ?? '';\n  }\n  const pattern = new RegExp(`\\\\b${escapeRegExp(from)}(?=\\\\.)`, 'g');\n  return input.replace(pattern, to);\n}\n\nexport async function ReferenceAccordion({\n  data,\n  name: partName,\n  renameFrom,\n  renameTo,\n  nameLabel = 'Prop',\n  caption = 'Component props table',\n  ...props\n}: Props) {\n  const captionId = `${partName}-caption`;\n\n  return (\n    <Accordion.Root aria-describedby={captionId} {...props}>\n      <span id={captionId} style={visuallyHidden} aria-hidden>\n        {caption}\n      </span>\n      <Accordion.HeaderRow className=\"ReferenceHeaderRow\">\n        <Accordion.HeaderCell>{nameLabel}</Accordion.HeaderCell>\n        <Accordion.HeaderCell className=\"ReferenceHeaderTypeCell\">Type</Accordion.HeaderCell>\n        <Accordion.HeaderCell className=\"ReferenceHeaderDefaultCell\">Default</Accordion.HeaderCell>\n        <Accordion.HeaderCell className=\"ReferenceHeaderIconCell\" />\n      </Accordion.HeaderRow>\n      {Object.keys(data).map(async (name, index) => {\n        const prop = data[name];\n\n        const displayType = replaceComponentPrefix(prop.type, renameFrom, renameTo);\n        const detailedDisplayType = replaceComponentPrefix(\n          prop.detailedType ?? prop.type,\n          renameFrom,\n          renameTo,\n        );\n\n        const PropType = await createMdxComponent(`\\`${displayType}\\``, {\n          rehypePlugins: rehypeSyntaxHighlighting,\n          useMDXComponents: () => ({ ...inlineMdxComponents, code: TableCode }),\n        });\n\n        const PropDetailedType = await createMdxComponent(\n          `\\`\\`\\`ts\\n${detailedDisplayType}\\n\\`\\`\\``,\n          {\n            rehypePlugins: rehypeSyntaxHighlighting,\n            useMDXComponents: () => ({\n              ...inlineMdxComponents,\n              figure: 'figure',\n              pre: ExpandedPre,\n              code: ExpandedCode,\n            }),\n          },\n        );\n\n        const { type: shortPropTypeName, detailedType } = getShortPropType(name, displayType);\n        const hasExpandedType = Boolean(prop.detailedType);\n\n        const ShortPropType = await createMdxComponent(`\\`${shortPropTypeName}\\``, {\n          rehypePlugins: rehypeSyntaxHighlighting,\n          useMDXComponents: () => ({\n            ...inlineMdxComponents,\n            code: (codeProps: TableCodeProps) => (\n              <TableCode {...codeProps} printWidth={name === 'children' ? 999 : undefined} />\n            ),\n          }),\n        });\n\n        const PropDefault = await createMdxComponent(`\\`${prop.default}\\``, {\n          rehypePlugins: rehypeSyntaxHighlighting,\n          useMDXComponents: () => ({ ...inlineMdxComponents, code: TableCode }),\n        });\n\n        const PropDescription = prop.description\n          ? await createMdxComponent(prop.description, {\n              rehypePlugins: rehypeSyntaxHighlighting,\n              useMDXComponents: () => ({ ...mdxComponents, p: 'p' }),\n            })\n          : null;\n\n        const ExampleSnippet = prop.example\n          ? await createMdxComponent(prop.example, {\n              rehypePlugins: rehypeSyntaxHighlighting,\n              useMDXComponents: () => inlineMdxComponents,\n            })\n          : null;\n\n        // anchor hash for each prop\n        const id = `${partName}-${name}`;\n\n        return (\n          <Accordion.Item\n            key={name}\n            gaCategory=\"reference\"\n            gaLabel={`${nameLabel}: ${id}`}\n            gaParams={{ type: nameLabel.toLowerCase(), slug: id, part_name: partName }}\n          >\n            <Accordion.Trigger\n              id={id}\n              index={index}\n              aria-label={`${nameLabel}: ${name},${prop.required ? ' required,' : ''} type: ${shortPropTypeName} ${prop.default !== undefined ? `(default: ${prop.default})` : ''}`}\n              className=\"ReferenceTrigger\"\n            >\n              <Accordion.Scrollable className=\"ReferenceNameCell\">\n                <TableCode className=\"bui-ws-nw\" style={{ color: 'var(--color-navy)' }}>\n                  {name}\n                  {prop.required ? <sup className=\"ReferenceRequired\">*</sup> : ''}\n                </TableCode>\n              </Accordion.Scrollable>\n              {prop.type && (\n                <Accordion.Scrollable className=\"ReferenceTypeCell\">\n                  {hasExpandedType || detailedType ? (\n                    <ReferenceTableTooltip.Root disableHoverablePopup>\n                      <ReferenceTableTooltip.Trigger render={<ShortPropType />} delay={300} />\n                      <ReferenceTableTooltip.Popup>\n                        {hasExpandedType ? <PropDetailedType /> : <PropType />}\n                      </ReferenceTableTooltip.Popup>\n                    </ReferenceTableTooltip.Root>\n                  ) : (\n                    <ShortPropType />\n                  )}\n                </Accordion.Scrollable>\n              )}\n              <Accordion.Scrollable className=\"ReferenceDefaultCell\">\n                {prop.required || prop.default === undefined ? (\n                  <TableCode style={{ color: 'var(--syntax-nullish)' }}>—</TableCode>\n                ) : (\n                  <PropDefault />\n                )}\n              </Accordion.Scrollable>\n              <span className=\"ReferenceIconWrap\">\n                <svg\n                  className=\"AccordionIcon ReferenceIcon\"\n                  width=\"10\"\n                  height=\"10\"\n                  viewBox=\"0 0 10 10\"\n                  fill=\"none\"\n                  xmlns=\"http://www.w3.org/2000/svg\"\n                >\n                  <path d=\"M1 3.5L5 7.5L9 3.5\" stroke=\"currentcolor\" />\n                </svg>\n              </span>\n            </Accordion.Trigger>\n            <Accordion.Panel>\n              <Accordion.Content>\n                <DescriptionList.Root className=\"ReferenceContent\" aria-label=\"Info\">\n                  <DescriptionList.Item>\n                    <DescriptionList.Term>Name</DescriptionList.Term>\n                    <DescriptionList.Details>\n                      <Link href={`#${id}`}>\n                        <TableCode style={{ color: 'var(--color-blue)' }}>{name}</TableCode>\n                      </Link>\n                    </DescriptionList.Details>\n                  </DescriptionList.Item>\n                  {PropDescription != null && (\n                    <DescriptionList.Item>\n                      <DescriptionList.Term separator>Description</DescriptionList.Term>\n                      {/* one-off override of the default mt/mb on CodeBlock.Root */}\n                      <DescriptionList.Details className=\"ReferenceDescription\">\n                        <PropDescription />\n                      </DescriptionList.Details>\n                    </DescriptionList.Item>\n                  )}\n                  <DescriptionList.Item>\n                    <DescriptionList.Term separator>Type</DescriptionList.Term>\n                    <DescriptionList.Details>\n                      <PropDetailedType />\n                    </DescriptionList.Details>\n                  </DescriptionList.Item>\n                  {prop.default !== undefined && (\n                    <DescriptionList.Item>\n                      <DescriptionList.Term separator>Default</DescriptionList.Term>\n                      <DescriptionList.Details>\n                        <PropDefault />\n                      </DescriptionList.Details>\n                    </DescriptionList.Item>\n                  )}\n                  {ExampleSnippet != null && (\n                    <DescriptionList.Item>\n                      <DescriptionList.Term separator>Example</DescriptionList.Term>\n                      <DescriptionList.Details className=\"ReferenceExampleReset\">\n                        <ExampleSnippet />\n                      </DescriptionList.Details>\n                    </DescriptionList.Item>\n                  )}\n                </DescriptionList.Root>\n              </Accordion.Content>\n            </Accordion.Panel>\n          </Accordion.Item>\n        );\n      })}\n    </Accordion.Root>\n  );\n}\n"
  },
  {
    "path": "docs/src/components/ReferenceTable/ReferenceTable.css",
    "content": "@layer components {\n  .ReferencePre {\n    margin: 0;\n    padding: 0;\n    font-size: var(--text-xs);\n    line-height: var(--text-xs--line-height);\n    letter-spacing: var(--text-xs--letter-spacing);\n  }\n\n  .ReferenceHeaderRow {\n    display: grid;\n  }\n\n  .ReferenceHeaderTypeCell {\n    display: none;\n  }\n\n  .ReferenceHeaderDefaultCell {\n    display: none;\n  }\n\n  .ReferenceHeaderIconCell {\n    display: none;\n    width: 2.5rem;\n  }\n\n  .ReferenceTrigger {\n    min-height: min-content;\n    scroll-margin-top: 3rem;\n    padding: 0;\n  }\n\n  .ReferenceNameCell {\n    padding-inline: 0.75rem;\n  }\n\n  .ReferenceRequired {\n    top: -0.3em;\n    font-size: var(--text-xs);\n    color: var(--color-red-800);\n  }\n\n  .ReferenceTypeCell {\n    display: none;\n    align-items: baseline;\n    padding-inline: 0.75rem;\n    font-size: var(--text-sm);\n    line-height: 1;\n    white-space: nowrap;\n  }\n\n  .ReferenceDefaultCell {\n    display: none;\n    padding-inline: 0.75rem;\n    white-space: nowrap;\n  }\n\n  .ReferenceIconWrap {\n    display: flex;\n    justify-content: center;\n  }\n\n  .ReferenceIcon {\n    transform: translateY(1px);\n  }\n\n  .ReferenceContent {\n    display: flex;\n    flex-direction: column;\n    color: var(--color-gray-600);\n  }\n\n  .ReferenceDescription [role='figure'] {\n    margin-top: 0.25rem;\n    margin-bottom: 0.25rem;\n  }\n\n  .ReferenceExampleReset > * {\n    margin-top: 0;\n    margin-bottom: 0;\n  }\n\n  .ReferenceCompactPanel {\n    display: flex;\n    flex-direction: column;\n    gap: 0.75rem;\n    padding: 1rem;\n    font-size: var(--text-md);\n    line-height: var(--text-md--line-height);\n    text-wrap: pretty;\n  }\n\n  .ReferenceWideNameColumn {\n    width: 100%;\n  }\n\n  .ReferenceWideDescriptionColumn {\n    width: 66.6667%;\n  }\n\n  .ReferenceReturnTypeColumn {\n    width: 40%;\n  }\n\n  .ReferenceReturnDescriptionColumn {\n    width: 60%;\n  }\n\n  @media (min-width: 32rem) {\n    .ReferenceHeaderRow {\n      grid-template-columns: 12rem 1fr 2.5rem;\n    }\n\n    .ReferenceTrigger {\n      display: grid;\n      grid-template-columns: 12rem 1fr 2.5rem;\n    }\n\n    .ReferenceHeaderTypeCell {\n      display: table-cell;\n    }\n\n    .ReferenceTypeCell {\n      display: flex;\n    }\n\n    .ReferenceWideNameColumn {\n      width: 12rem;\n    }\n\n    .ReferenceContent {\n      display: grid;\n      gap: 0;\n      grid-template-columns: 12rem 1fr 2.5rem;\n    }\n  }\n\n  @media (min-width: 40rem) {\n    .ReferenceHeaderRow {\n      grid-template-columns: 14rem 1fr 2.5rem;\n    }\n\n    .ReferenceTrigger {\n      grid-template-columns: 14rem 1fr 2.5rem;\n    }\n\n    .ReferenceWideNameColumn {\n      width: 14rem;\n    }\n\n    .ReferenceContent {\n      grid-template-columns: 14rem 1fr 2.5rem;\n    }\n  }\n\n  @media (min-width: 48rem) {\n    .ReferenceHeaderDefaultCell {\n      display: table-cell;\n    }\n\n    .ReferenceHeaderIconCell {\n      display: table-cell;\n    }\n\n    .ReferenceDefaultCell {\n      display: block;\n    }\n\n    .ReferenceHeaderRow {\n      grid-template-columns: 5fr 7fr 4.5fr 2.5rem;\n    }\n\n    .ReferenceTrigger {\n      scroll-margin-top: 0;\n      grid-template-columns: 5fr 7fr 4.5fr 2.5rem;\n    }\n\n    .ReferenceWideNameColumn {\n      width: calc(5 / 16.5 * 100%);\n    }\n\n    .ReferenceWideDescriptionColumn {\n      width: calc(11.5 / 16.5 * 100%);\n    }\n\n    .ReferenceContent {\n      grid-template-columns: 5fr 11.5fr 2.5rem;\n    }\n  }\n\n  @media (width < 32rem) {\n    .ReferenceIconWrap {\n      margin-left: auto;\n      margin-right: 0.75rem;\n    }\n\n    .ReferenceContent {\n      padding-top: 0.75rem;\n      padding-bottom: 0.75rem;\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/components/ReferenceTable/ReferenceTableTooltip.css",
    "content": "@layer components {\n  .ReferenceTooltipPopup {\n    padding: 0.5rem 0.75rem;\n    font-size: var(--text-md);\n    overflow: visible;\n  }\n\n  .ReferenceTooltipContent {\n    display: flex;\n    max-width: 30rem;\n    flex-direction: column;\n    gap: 0.75rem;\n    text-wrap: pretty;\n  }\n\n  .ReferenceTooltipArrow {\n    &[data-side='bottom'] {\n      top: -8px;\n    }\n\n    &[data-side='left'] {\n      right: -13px;\n      transform: rotate(90deg);\n    }\n\n    &[data-side='right'] {\n      left: -13px;\n      transform: rotate(-90deg);\n    }\n\n    &[data-side='top'] {\n      bottom: -8px;\n      transform: rotate(180deg);\n    }\n  }\n\n  .ReferenceTooltipArrowFill {\n    fill: var(--color-popup);\n  }\n\n  .ReferenceTooltipArrowStroke {\n    fill: var(--color-gray-200);\n  }\n\n  .ReferenceTooltipArrowStrokeDark {\n    @media (prefers-color-scheme: dark) {\n      fill: var(--color-gray-300);\n    }\n  }\n\n  @media (prefers-color-scheme: dark) {\n    .ReferenceTooltipArrowStroke {\n      fill: none;\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/components/ReferenceTable/ReferenceTableTooltip.tsx",
    "content": "import { Tooltip } from '@base-ui/react/tooltip';\nimport { Popup as BasePopup } from '../Popup';\nimport './ReferenceTableTooltip.css';\n\nexport function Trigger(props: Tooltip.Trigger.Props) {\n  return <Tooltip.Trigger {...props} />;\n}\n\nexport function Root(props: Tooltip.Root.Props) {\n  return <Tooltip.Root {...props} />;\n}\n\nexport function Popup({ children, ...props }: Tooltip.Popup.Props) {\n  return (\n    <Tooltip.Portal>\n      <Tooltip.Positioner\n        align=\"center\"\n        side=\"top\"\n        alignOffset={-4}\n        sideOffset={6}\n        collisionPadding={16}\n      >\n        <Tooltip.Popup render={<BasePopup className=\"ReferenceTooltipPopup\" />} {...props}>\n          <div className=\"ReferenceTooltipContent\">{children}</div>\n          <Tooltip.Arrow className=\"ReferenceTooltipArrow\">\n            <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\">\n              <path\n                d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n                className=\"ReferenceTooltipArrowFill\"\n              />\n              <path\n                d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n                className=\"ReferenceTooltipArrowStroke\"\n              />\n              <path\n                d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n                className=\"ReferenceTooltipArrowStrokeDark\"\n              />\n            </svg>\n          </Tooltip.Arrow>\n        </Tooltip.Popup>\n      </Tooltip.Positioner>\n    </Tooltip.Portal>\n  );\n}\n"
  },
  {
    "path": "docs/src/components/ReferenceTable/ReturnValueReferenceTable.tsx",
    "content": "import * as React from 'react';\nimport clsx from 'clsx';\nimport { createMdxComponent } from 'docs/src/mdx/createMdxComponent';\nimport { inlineMdxComponents } from 'docs/src/mdx-components';\nimport { rehypeSyntaxHighlighting } from 'docs/src/syntax-highlighting';\nimport * as Accordion from '../Accordion';\nimport * as Table from '../Table';\nimport { TableCode } from '../TableCode';\nimport type { PropDef } from './types';\n\ninterface ReturnValueReferenceTableProps extends React.ComponentProps<typeof Table.Root> {\n  data: Record<string, PropDef>;\n  name?: string;\n}\n\nconst TYPE_MDX_OPTIONS = {\n  rehypePlugins: rehypeSyntaxHighlighting,\n  useMDXComponents: () => ({\n    ...inlineMdxComponents,\n    code: TableCode,\n  }),\n};\n\nconst DESCRIPTION_MDX_OPTIONS = {\n  rehypePlugins: rehypeSyntaxHighlighting,\n  useMDXComponents: () => inlineMdxComponents,\n};\n\nfunction getDescription(def: PropDef, name: string, includeName: boolean) {\n  const baseDescription = [def.description, def.example].filter(Boolean).join('\\n\\n');\n  if (!includeName) {\n    return baseDescription;\n  }\n\n  const nameLabel = `**${name}**`;\n  return baseDescription ? `${nameLabel}: ${baseDescription}` : nameLabel;\n}\n\nexport async function ReturnValueReferenceTable({\n  data,\n  name: partName,\n  ...props\n}: ReturnValueReferenceTableProps) {\n  const entries = Object.entries(data);\n  const includeName = entries.length > 1;\n\n  return (\n    <React.Fragment>\n      <Accordion.Root {...props} className={clsx(props.className, 'bp0:bui-d-n')}>\n        <Accordion.HeaderRow>\n          <Accordion.HeaderCell className=\"bui-pl-3\">Type</Accordion.HeaderCell>\n        </Accordion.HeaderRow>\n        {entries.map(async ([name, def], index) => {\n          const typeValue = def.type ?? def.detailedType;\n          const descriptionText = getDescription(def, name, includeName);\n\n          const ReturnType = typeValue\n            ? await createMdxComponent(`\\`${typeValue}\\``, TYPE_MDX_OPTIONS)\n            : null;\n\n          const ReturnDescription = descriptionText\n            ? await createMdxComponent(descriptionText, DESCRIPTION_MDX_OPTIONS)\n            : null;\n\n          return (\n            <Accordion.Item\n              key={name}\n              gaCategory=\"reference\"\n              gaLabel={`Return value: ${partName ? `${partName}-` : ''}${name}`}\n              gaParams={{\n                type: 'return_value',\n                slug: `${partName ? `${partName}-` : ''}${name}`,\n                part_name: partName || '',\n              }}\n            >\n              <Accordion.Trigger index={index}>\n                {ReturnType ? (\n                  <ReturnType />\n                ) : (\n                  <TableCode style={{ color: 'var(--syntax-nullish)' }}>—</TableCode>\n                )}\n                <svg\n                  className=\"AccordionIcon bui-ml-a bui-mr-1\"\n                  width=\"10\"\n                  height=\"10\"\n                  viewBox=\"0 0 10 10\"\n                  fill=\"none\"\n                  xmlns=\"http://www.w3.org/2000/svg\"\n                >\n                  <path d=\"M1 3.5L5 7.5L9 3.5\" stroke=\"currentcolor\" />\n                </svg>\n              </Accordion.Trigger>\n              <Accordion.Panel>\n                <Accordion.Content className=\"ReferenceCompactPanel\">\n                  {ReturnDescription ? (\n                    <ReturnDescription />\n                  ) : (\n                    <TableCode style={{ color: 'var(--syntax-nullish)' }}>—</TableCode>\n                  )}\n                </Accordion.Content>\n              </Accordion.Panel>\n            </Accordion.Item>\n          );\n        })}\n      </Accordion.Root>\n\n      <Table.Root {...props} className={clsx('bui-d-n', 'bp0:bui-d-b', props.className)}>\n        <Table.Head>\n          <Table.Row>\n            <Table.ColumnHeader className=\"ReferenceReturnTypeColumn\">Type</Table.ColumnHeader>\n            <Table.ColumnHeader className=\"ReferenceReturnDescriptionColumn\">\n              Description\n            </Table.ColumnHeader>\n          </Table.Row>\n        </Table.Head>\n        <Table.Body>\n          {entries.map(async ([name, def]) => {\n            const typeValue = def.type ?? def.detailedType;\n            const descriptionText = getDescription(def, name, includeName);\n\n            const ReturnType = typeValue\n              ? await createMdxComponent(`\\`${typeValue}\\``, TYPE_MDX_OPTIONS)\n              : null;\n\n            const ReturnDescription = descriptionText\n              ? await createMdxComponent(descriptionText, DESCRIPTION_MDX_OPTIONS)\n              : null;\n\n            return (\n              <Table.Row key={name}>\n                <Table.Cell>\n                  {ReturnType ? (\n                    <ReturnType />\n                  ) : (\n                    <TableCode style={{ color: 'var(--syntax-nullish)' }}>—</TableCode>\n                  )}\n                </Table.Cell>\n                <Table.Cell>\n                  {ReturnDescription ? (\n                    <ReturnDescription />\n                  ) : (\n                    <TableCode style={{ color: 'var(--syntax-nullish)' }}>—</TableCode>\n                  )}\n                </Table.Cell>\n              </Table.Row>\n            );\n          })}\n        </Table.Body>\n      </Table.Root>\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "docs/src/components/ReferenceTable/referenceUtils.mjs",
    "content": "// @ts-check\n\n/**\n * @param {unknown} node\n * @param {string} attributeName\n * @returns {string | undefined}\n */\nexport function getAttributeValue(node, attributeName) {\n  if (!node || typeof node !== 'object') {\n    return undefined;\n  }\n\n  /** @type {any} */\n  const maybeNode = node;\n  const attributes = Array.isArray(maybeNode.attributes) ? maybeNode.attributes : [];\n  const match = attributes.find(\n    /** @param {{ name?: string }} attr */\n    (attr) => attr?.name === attributeName,\n  );\n\n  if (match) {\n    if (typeof match.value === 'string') {\n      return match.value;\n    }\n\n    if (match.value && typeof match.value === 'object' && typeof match.value.value === 'string') {\n      return match.value.value;\n    }\n  }\n\n  if (maybeNode.properties && typeof maybeNode.properties === 'object') {\n    const propValue = maybeNode.properties[attributeName];\n    if (typeof propValue === 'string') {\n      return propValue;\n    }\n  }\n\n  return undefined;\n}\n\n/**\n * @param {unknown} def\n * @returns {def is import('./types').ComponentDef}\n */\nexport function isComponentDef(def) {\n  return Boolean(def && typeof def === 'object' && 'props' in def);\n}\n\n/**\n * @param {unknown} def\n * @returns {def is import('./types').FunctionDef}\n */\nexport function isFunctionDef(def) {\n  return Boolean(def && typeof def === 'object' && ('parameters' in def || 'returnValue' in def));\n}\n\n/**\n * @param {import('./types').FunctionDef['returnValue'] | undefined} returnValue\n */\nexport function normalizeReturnValue(returnValue) {\n  if (!returnValue) {\n    return {};\n  }\n\n  if (typeof returnValue === 'string') {\n    return {\n      returnValue: {\n        type: returnValue,\n      },\n    };\n  }\n\n  if (isReturnValueMap(returnValue)) {\n    return returnValue;\n  }\n\n  return {\n    returnValue,\n  };\n}\n\n/**\n * @param {unknown} returnValue\n * @returns {returnValue is Record<string, import('./types').PropDef>}\n */\nfunction isReturnValueMap(returnValue) {\n  if (!returnValue || typeof returnValue !== 'object') {\n    return false;\n  }\n\n  const values = Object.values(returnValue);\n  if (values.length === 0) {\n    return true;\n  }\n  return values.some((value) => value && typeof value === 'object');\n}\n"
  },
  {
    "path": "docs/src/components/ReferenceTable/rehypeReference.mjs",
    "content": "// @ts-check\nimport { readFileSync, existsSync } from 'node:fs';\nimport { visitParents } from 'unist-util-visit-parents';\nimport { kebabCase } from 'es-toolkit/string';\nimport { join } from 'path';\nimport { createMdxElement } from 'docs/src/mdx/createMdxElement.mjs';\nimport { createHast } from 'docs/src/mdx/createHast.mjs';\nimport {\n  getAttributeValue,\n  isComponentDef,\n  isFunctionDef,\n  normalizeReturnValue,\n} from './referenceUtils.mjs';\n\n// The \"<Reference />\" component name as used in MDX\nconst REFERENCE = 'Reference';\n\n// The corresponding components exposed in \"mdx-components.tsx\"\nconst ATTRIBUTES_TABLE = 'AttributesReferenceTable';\nconst CSS_VARIABLES_TABLE = 'CssVariablesReferenceTable';\n\nconst PROPS_TABLE = 'PropsReferenceTable';\nconst PARAMETERS_TABLE = 'ParametersReferenceTable';\nconst RETURN_VALUE_TABLE = 'ReturnValueReferenceTable';\nconst PARAMETERS_HEADING = 'Parameters';\nconst RETURN_VALUE_HEADING = 'Return value';\n\n/**\n * Finds `<Reference />` in the MDX and transforms it into\n * API reference content for the specified component.\n *\n * @returns {function(*, *): void}\n */\nexport function rehypeReference() {\n  return (tree) => {\n    visitParents(tree, (node, ancestors) => {\n      if (node.name !== REFERENCE) {\n        return;\n      }\n\n      /** @type {string | undefined} */\n      const component = getAttributeValue(node, 'component');\n\n      /** @type {string | undefined} */\n      const name = getAttributeValue(node, 'name');\n\n      /** @type {string | undefined} */\n      const parts = getAttributeValue(node, 'parts');\n\n      /** @type {string | undefined} */\n      const asParam = getAttributeValue(node, 'as');\n\n      const referenceName = component ?? name;\n\n      if (!referenceName) {\n        throw new Error(`Missing \"component\" or \"name\" prop on the \"<Reference />\" component.`);\n      }\n\n      /** @type {import('./types').ComponentDef[]} */\n      let componentDefs = [];\n      /** @type {import('./types').FunctionDef | null} */\n      let functionDef = null;\n\n      if (parts) {\n        componentDefs = parts.split(/,\\s*/).map((part) => {\n          let filename = `${kebabCase(referenceName)}-${kebabCase(part)}.json`;\n          let pathname = join(process.cwd(), 'reference/generated', filename);\n\n          if (!existsSync(pathname)) {\n            filename = `${kebabCase(part)}.json`;\n            pathname = join(process.cwd(), 'reference/generated', filename);\n          }\n\n          const jsonContents = readFileSync(pathname, 'utf-8');\n          return JSON.parse(jsonContents);\n        });\n      } else {\n        const filename = `${kebabCase(referenceName)}.json`;\n        const pathname = join(process.cwd(), 'reference/generated', filename);\n        const jsonContents = readFileSync(pathname, 'utf-8');\n        const parsedDef = JSON.parse(jsonContents);\n\n        if (isFunctionDef(parsedDef) && !isComponentDef(parsedDef)) {\n          functionDef = parsedDef;\n        } else {\n          componentDefs = [parsedDef];\n        }\n      }\n\n      const parent = ancestors.slice(-1)[0];\n      const index = parent.children.indexOf(node);\n\n      // Replace \"<Reference />\" with content\n      const subtree = functionDef\n        ? describeFunction(functionDef)\n        : describeComponents(componentDefs, referenceName, parts, asParam);\n      parent.children.splice(index, 1, ...subtree);\n    });\n  };\n}\n\n/**\n * Creates tables with components props descriptions.\n *\n * @param {import('./types').ComponentDef[]} componentDefs\n * @param {string} referenceName\n * @param {string | undefined} parts\n * @param {string | undefined} asParam\n */\nfunction describeComponents(componentDefs, referenceName, parts, asParam) {\n  return componentDefs.flatMap((def) => {\n    const subtree = [];\n    const partName =\n      parts && def.name.startsWith(referenceName)\n        ? def.name.substring(referenceName.length)\n        : def.name;\n\n    // Insert an <h3> with the part name and parse descriptions as markdown.\n    // Single-part components headings and descriptions aren't displayed\n    // because they duplicate the page title and subtitle anyway.\n    if (parts) {\n      subtree.push(\n        createMdxElement({\n          name: 'h3',\n          children: [{ type: 'text', value: partName }],\n          props: {\n            id: kebabCase(partName),\n          },\n        }),\n      );\n\n      if (parts && def.description) {\n        subtree.push(...createHast(def.description).children);\n      }\n    }\n\n    if (Object.keys(def.props).length) {\n      subtree.push(\n        createMdxElement({\n          name: PROPS_TABLE,\n          props: {\n            name:\n              asParam && def.name.startsWith(referenceName)\n                ? `${asParam}${def.name.substring(referenceName.length)}`\n                : def.name,\n            data: def.props,\n            renameFrom: asParam ? referenceName : undefined,\n            renameTo: asParam,\n          },\n        }),\n      );\n    }\n\n    if (Object.keys(def.dataAttributes).length) {\n      subtree.push(\n        createMdxElement({\n          name: ATTRIBUTES_TABLE,\n          props: {\n            name: def.name,\n            data: def.dataAttributes,\n          },\n        }),\n      );\n    }\n\n    if (Object.keys(def.cssVariables).length) {\n      subtree.push(\n        createMdxElement({\n          name: CSS_VARIABLES_TABLE,\n          props: {\n            name: def.name,\n            data: def.cssVariables,\n          },\n        }),\n      );\n    }\n\n    return subtree;\n  });\n}\n\n/**\n * Creates tables with function parameters and return value descriptions.\n *\n * @param {import('./types').FunctionDef} functionDef\n */\nfunction describeFunction(functionDef) {\n  const subtree = [];\n  const parameters = functionDef.parameters ?? {};\n  const returnValue = normalizeReturnValue(functionDef.returnValue);\n\n  if (Object.keys(parameters).length) {\n    subtree.push(\n      createMdxElement({\n        name: 'h4',\n        children: [{ type: 'text', value: PARAMETERS_HEADING }],\n        props: {\n          id: `${kebabCase(functionDef.name)}-parameters`,\n        },\n      }),\n    );\n\n    subtree.push(\n      createMdxElement({\n        name: PARAMETERS_TABLE,\n        props: {\n          name: `${functionDef.name}-parameters`,\n          data: parameters,\n        },\n      }),\n    );\n  }\n\n  if (Object.keys(returnValue).length) {\n    subtree.push(\n      createMdxElement({\n        name: 'h4',\n        children: [{ type: 'text', value: RETURN_VALUE_HEADING }],\n        props: {\n          id: `${kebabCase(functionDef.name)}-return-value`,\n        },\n      }),\n    );\n\n    subtree.push(\n      createMdxElement({\n        name: RETURN_VALUE_TABLE,\n        props: {\n          name: `${functionDef.name}-return`,\n          data: returnValue,\n        },\n      }),\n    );\n  }\n\n  return subtree;\n}\n"
  },
  {
    "path": "docs/src/components/ReferenceTable/types.ts",
    "content": "export interface ComponentDef {\n  name: string;\n  description?: string;\n  props: Record<string, PropDef>;\n  dataAttributes: Record<string, AttributeDef>;\n  cssVariables: Record<string, CssVariableDef>;\n}\n\nexport interface PropDef {\n  type?: string;\n  expanded?: string;\n  default?: string;\n  required?: boolean;\n  description?: string;\n  detailedType?: string;\n  example?: string;\n}\n\nexport interface AttributeDef {\n  type?: string;\n  description?: string;\n}\n\nexport interface CssVariableDef {\n  type?: string;\n  description?: string;\n}\n\nexport interface FunctionParamDef extends PropDef {\n  optional?: boolean;\n}\n\nexport interface FunctionDef {\n  name: string;\n  description?: string;\n  parameters?: Record<string, FunctionParamDef>;\n  returnValue?: Record<string, PropDef> | PropDef | string;\n}\n"
  },
  {
    "path": "docs/src/components/ReleaseTimeline/ReleaseTimeline.css",
    "content": "@layer components {\n  .ReleaseTimeline {\n    position: relative;\n    list-style: none;\n    padding: 0;\n    display: flex;\n    flex-direction: column;\n    gap: 1rem;\n  }\n\n  @media (min-width: 48rem) {\n    .ReleaseTimeline {\n      --dot-size: 12px;\n      /* Vertically centered with .TimelineDate */\n      --dot-y: calc(1.25rem + var(--text-sm--line-height) / 2);\n      --column-gap: 4rem;\n\n      padding-block: 2rem;\n      margin-top: -1rem;\n    }\n  }\n\n  .TimelineSpine {\n    display: none;\n  }\n\n  .TimelineItem {\n    position: relative;\n  }\n\n  @media (min-width: 48rem) {\n    .TimelineSpine {\n      position: absolute;\n      display: block;\n      top: 0;\n      left: 50%;\n      bottom: 0;\n      width: 1px;\n      transform: translateX(-50%);\n      /* Fade out the ends */\n      background: linear-gradient(\n        to bottom,\n        transparent,\n        var(--color-gray-200) 2.5rem,\n        var(--color-gray-200) calc(100% - 2.5rem),\n        transparent\n      );\n    }\n\n    .TimelineItem {\n      padding-left: 0;\n      display: grid;\n      grid-template-columns: 1fr 1fr;\n      gap: var(--column-gap);\n      align-items: start;\n    }\n\n    .TimelineItem:nth-child(even) .TimelineCard {\n      grid-column: 1;\n      grid-row: 1;\n    }\n\n    .TimelineItem:nth-child(odd) .TimelineCard {\n      grid-column: 2;\n    }\n\n    /* Dot */\n    .TimelineItem::before {\n      content: '';\n      display: block;\n      position: absolute;\n      left: 50%;\n      top: calc(var(--dot-y) - var(--dot-size) / 2);\n      width: var(--dot-size);\n      height: var(--dot-size);\n      border-radius: 50%;\n      background: var(--color-content);\n      border: 1px solid var(--color-gray-300);\n      box-shadow: 0 0 0 4px var(--color-content);\n      transform: translateX(-50%);\n      z-index: 1;\n    }\n\n    /* Horizontal line connecting the dot with the card */\n    .TimelineItem::after {\n      content: '';\n      display: block;\n      position: absolute;\n      top: var(--dot-y);\n      width: calc(var(--column-gap) / 2 - var(--dot-size) / 2);\n      height: 1px;\n      background: var(--color-gray-200);\n    }\n\n    .TimelineItem:nth-child(odd)::after {\n      left: calc(50% + var(--dot-size) / 2);\n    }\n\n    .TimelineItem:nth-child(even)::after {\n      right: calc(50% + var(--dot-size) / 2);\n    }\n  }\n\n  .TimelineCard {\n    position: relative;\n    z-index: 0;\n    display: flex;\n    flex-direction: column;\n    align-items: start;\n    gap: 0.75rem;\n    background-color: var(--color-gray-200);\n    border-radius: var(--radius-lg);\n    padding: 1.25rem;\n    font-size: var(--text-md);\n    color: var(--color-foreground);\n    line-height: var(--text-md--line-height);\n  }\n\n  .TimelineCard::before,\n  .TimelineCard::after {\n    position: absolute;\n    content: '';\n  }\n\n  .TimelineCard::before {\n    inset: 1px;\n    background-color: var(--color-content);\n    border-radius: calc(var(--radius-lg) - 1px);\n    z-index: -1;\n  }\n\n  .TimelineCard::after {\n    inset: 1px;\n    border-radius: calc(var(--radius-lg) - 1px);\n    box-shadow:\n      0 1px 3px 0 var(--color-gray-200),\n      0 1px 2px -1px var(--color-gray-200);\n    z-index: -1;\n  }\n\n  .TimelineCardHeader {\n    display: flex;\n    flex-direction: column;\n    gap: 0.5rem;\n  }\n\n  .TimelineDate {\n    font-size: var(--text-sm);\n    line-height: var(--text-sm--line-height);\n    color: var(--color-gray-500);\n  }\n\n  .TimelineVersion {\n    display: flex;\n    align-items: center;\n    gap: 0.5rem;\n    margin: 0;\n    font-size: var(--text-lg);\n    line-height: var(--text-lg--line-height);\n    font-weight: 700;\n    color: var(--color-foreground);\n  }\n\n  .TimelineVersionLink {\n    outline: 0;\n    color: var(--color-blue);\n    text-decoration-line: none;\n    text-decoration-thickness: 1px;\n    text-decoration-color: color-mix(in oklab, var(--color-blue), transparent 40%);\n    text-underline-offset: 2px;\n\n    @media (hover: hover) {\n      &:hover {\n        text-decoration-line: underline;\n      }\n    }\n\n    &:focus-visible {\n      border-radius: 0.125rem;\n      outline: 2px solid var(--color-blue);\n      text-decoration-line: none;\n    }\n  }\n\n  .TimelineBadge {\n    display: inline-flex;\n    align-items: center;\n    height: 1.25rem;\n    padding: 0 0.375rem;\n    font-size: var(--text-xxs);\n    line-height: var(--text-xxs--line-height);\n    letter-spacing: var(--text-xxs--letter-spacing);\n    font-weight: 400;\n    color: var(--color-green);\n    border: 1px solid currentColor;\n    border-radius: 999px;\n    user-select: none;\n  }\n\n  .TimelineHighlights {\n    list-style: none;\n    padding: 0;\n  }\n\n  .TimelineHighlights li {\n    position: relative;\n    padding-left: 1rem;\n  }\n\n  .TimelineHighlights li + li {\n    margin-top: 0.5rem;\n  }\n\n  .TimelineHighlights li::before {\n    content: '•';\n    position: absolute;\n    left: 0;\n  }\n}\n"
  },
  {
    "path": "docs/src/components/ReleaseTimeline/ReleaseTimeline.tsx",
    "content": "'use client';\n\nimport * as React from 'react';\nimport Link from 'next/link';\nimport { Code } from 'docs/src/components/Code';\nimport { releases } from 'docs/src/data/releases';\nimport './ReleaseTimeline.css';\n\nconst dateFormatter = new Intl.DateTimeFormat('en-US', {\n  month: 'short',\n  day: 'numeric',\n  year: 'numeric',\n  timeZone: 'UTC',\n});\n\nfunction renderHighlight(text: string): React.ReactNode {\n  const parts = text.split(/`([^`]+)`/);\n  return parts.length === 1\n    ? text\n    : parts.map((part, i) =>\n        i % 2 === 1 ? (\n          <Code key={i} data-inline style={{ color: 'var(--syntax-default)' }}>\n            {part}\n          </Code>\n        ) : (\n          part\n        ),\n      );\n}\n\nexport function ReleaseTimeline() {\n  return (\n    <ul className=\"ReleaseTimeline\" aria-label=\"Release timeline\">\n      <div className=\"TimelineSpine\" />\n      {releases.map((release) => (\n        <li key={release.versionSlug} className=\"TimelineItem\">\n          <article className=\"TimelineCard\">\n            <div className=\"TimelineCardHeader\">\n              <time className=\"TimelineDate\" dateTime={release.date}>\n                {dateFormatter.format(new Date(release.date))}\n              </time>\n              <h3 className=\"TimelineVersion\">\n                <Link\n                  className=\"TimelineVersionLink\"\n                  href={`/react/overview/releases/${release.versionSlug}`}\n                >\n                  {release.version}\n                </Link>\n                {release.latest && <span className=\"TimelineBadge\">Latest</span>}\n              </h3>\n            </div>\n            <ul className=\"TimelineHighlights\">\n              {release.highlights.map((highlight, i) => (\n                <li key={i}>{renderHighlight(highlight)}</li>\n              ))}\n            </ul>\n          </article>\n        </li>\n      ))}\n    </ul>\n  );\n}\n"
  },
  {
    "path": "docs/src/components/ReleaseTimeline/index.tsx",
    "content": "export { ReleaseTimeline } from './ReleaseTimeline';\n"
  },
  {
    "path": "docs/src/components/ScrollArea.css",
    "content": "@layer components {\n  .ScrollAreaViewport {\n    height: 100%;\n\n    /* Scroll containers may be focusable */\n    &:focus-visible {\n      position: relative;\n      outline: 2px solid var(--color-blue);\n      outline-offset: -1px;\n      z-index: 1;\n    }\n  }\n\n  .ScrollAreaScrollbar {\n    display: flex;\n    border-radius: 0.375rem;\n\n    opacity: 0;\n    transition: opacity 150ms 300ms;\n    pointer-events: none;\n\n    &[data-hovering],\n    &[data-scrolling] {\n      pointer-events: auto;\n    }\n\n    &[data-scrolling] {\n      opacity: 1;\n      transition-duration: 75ms;\n      transition-delay: 0ms;\n    }\n\n    @media (hover: hover) {\n      &:hover {\n        transition-duration: 75ms;\n        transition-delay: 25ms;\n        opacity: 1;\n      }\n    }\n\n    &::before {\n      content: '';\n      position: absolute;\n      border-radius: inherit;\n    }\n\n    &[data-orientation='horizontal'] {\n      align-items: center;\n      height: 1.25rem;\n      margin-inline: 0.5rem;\n\n      &::before {\n        height: 0.25rem;\n        inset-inline: 0;\n        background-color: var(--color-gray-200);\n      }\n    }\n\n    &[data-orientation='vertical'] {\n      justify-content: center;\n      width: 1.25rem;\n      margin-block: 0.5rem;\n\n      &::before {\n        width: 0.25rem;\n        inset-block: 0;\n        background-color: var(--color-gray-200);\n      }\n    }\n  }\n\n  .ScrollAreaThumb {\n    position: relative;\n    border-radius: inherit;\n    background-color: var(--color-gray-400);\n\n    &[data-orientation='horizontal'] {\n      height: 0.25rem;\n    }\n\n    &[data-orientation='vertical'] {\n      width: 0.25rem;\n    }\n\n    /* Draggable area */\n    &::before {\n      content: '';\n      position: absolute;\n      inset: -0.5rem;\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/components/ScrollArea.tsx",
    "content": "import { ScrollArea } from '@base-ui/react/scroll-area';\nimport clsx from 'clsx';\nimport './ScrollArea.css';\n\nexport const Root = ScrollArea.Root;\n\nexport function Viewport({ className, ...props }: ScrollArea.Viewport.Props) {\n  return <ScrollArea.Viewport className={clsx('ScrollAreaViewport', className)} {...props} />;\n}\n\nexport function Scrollbar({ className, ...props }: ScrollArea.Scrollbar.Props) {\n  return (\n    <ScrollArea.Scrollbar className={clsx('ScrollAreaScrollbar', className)} {...props}>\n      <ScrollArea.Thumb className=\"ScrollAreaThumb\" />\n    </ScrollArea.Scrollbar>\n  );\n}\n\nexport function Corner({ className, ...props }: ScrollArea.Corner.Props) {\n  return <ScrollArea.Corner className={clsx('ScrollAreaCorner', className)} {...props} />;\n}\n"
  },
  {
    "path": "docs/src/components/Search/Search.tsx",
    "content": "'use client';\nimport { SearchBar } from './SearchBar';\n\nconst sitemap = () => import('../../app/sitemap');\n\nexport function Search({\n  enableKeyboardShortcut = false,\n  containedScroll = false,\n}: {\n  enableKeyboardShortcut?: boolean;\n  containedScroll?: boolean;\n}) {\n  return (\n    <SearchBar\n      sitemap={sitemap}\n      enableKeyboardShortcut={enableKeyboardShortcut}\n      containedScroll={containedScroll}\n    />\n  );\n}\n"
  },
  {
    "path": "docs/src/components/Search/SearchBar.css",
    "content": "@import 'docs/src/css/custom-media.css';\n\n@layer components {\n  .SearchScrollbar {\n    display: flex;\n    padding-top: calc(var(--spacing) * 2);\n    padding-bottom: calc(var(--spacing) * 2);\n\n    /* Click target width */\n    width: 1.5rem;\n    margin-right: -0.25rem;\n  }\n\n  .SearchScrollbarThumb {\n    display: flex;\n    justify-content: center;\n    width: 100%;\n\n    &::before {\n      content: '';\n      display: block;\n      height: 100%;\n      /* Visible thumb width */\n      width: 0.25rem;\n      border-radius: var(--radius-sm);\n      background-color: var(--color-gray-400);\n    }\n  }\n\n  .SearchTrigger {\n    position: relative;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    gap: calc(var(--spacing) * 2);\n    height: calc(var(--spacing) * 8);\n    padding-inline: calc(var(--spacing) * 3.5);\n    margin: 0;\n    border: none;\n    border-radius: 9999px;\n    background-color: hsl(0deg 0% 0% / 5%);\n    font: inherit;\n    font-size: 1rem;\n    font-weight: 400;\n    line-height: 1.5;\n    color: var(--color-gray-900);\n    user-select: none;\n    outline: none;\n\n    @media (prefers-color-scheme: dark) {\n      background-color: var(--color-gray-200);\n    }\n  }\n\n  .SearchTrigger::before {\n    content: '';\n    position: absolute;\n    inset: 1px;\n    border-radius: 9999px;\n    background-color: var(--color-popup);\n    z-index: 0;\n\n    @media (prefers-color-scheme: dark) {\n      background-color: var(--color-gray-75);\n    }\n  }\n\n  .SearchTrigger::after {\n    content: '';\n    position: absolute;\n    inset: 1px;\n    border-radius: 9999px;\n    box-shadow: 0 1px 2px hsl(0deg 0% 0% / 5%);\n    z-index: 0;\n    pointer-events: none;\n  }\n\n  .SearchTrigger > * {\n    position: relative;\n    z-index: 1;\n  }\n\n  .SearchTrigger:hover {\n    background-color: hsl(0deg 0% 0% / 15%);\n\n    @media (prefers-color-scheme: dark) {\n      background-color: var(--color-gray-300);\n    }\n  }\n\n  .SearchTrigger:hover::before {\n    @media (prefers-color-scheme: dark) {\n      background-color: oklch(21.5% 0.5% 264deg);\n    }\n  }\n\n  .SearchTrigger:hover .SearchTriggerKbd {\n    @media (prefers-color-scheme: dark) {\n      border-color: var(--color-gray-300);\n    }\n  }\n\n  .SearchTrigger:active {\n    background-color: hsl(0deg 0% 0% / 12%);\n  }\n\n  .SearchTrigger:active::before {\n    background-color: var(--color-gray-50);\n  }\n\n  .SearchTrigger:focus-visible {\n    outline: 2px solid var(--color-blue-800);\n    outline-offset: -1px;\n  }\n\n  .SearchTrigger[data-disabled] {\n    color: var(--color-gray-500);\n    cursor: default;\n  }\n\n  .SearchTrigger[data-disabled]:hover,\n  .SearchTrigger[data-disabled]:active {\n    background-color: hsl(0deg 0% 0% / 5%);\n  }\n\n  .SearchTriggerIcon {\n    width: 1rem;\n    height: 1rem;\n    color: var(--color-gray-500);\n  }\n\n  .SearchTriggerKbd {\n    display: none;\n    height: 20px;\n    line-height: 1;\n    align-items: center;\n    gap: 0.25rem;\n    padding-inline: 0.375rem;\n    border-radius: 9999px;\n    border: 1px solid var(--color-gray-300);\n    background-color: var(--color-gray-50);\n    pointer-events: none;\n    color: var(--color-gray-600);\n\n    @media (--lg) {\n      display: inline-flex;\n      margin-right: -0.5rem;\n    }\n  }\n\n  .SearchTriggerKbd kbd {\n    line-height: 1;\n  }\n\n  .SearchTriggerKbd kbd:first-child {\n    display: inline-flex;\n    margin-top: 1px;\n  }\n\n  .SearchTriggerCmd {\n    font-size: var(--text-lg);\n  }\n\n  .SearchTriggerCtrl {\n    font-size: var(--text-xs);\n  }\n\n  .SearchTriggerPlus {\n    font-size: var(--text-xs);\n    color: var(--color-gray-400);\n  }\n\n  .SearchTriggerK {\n    font-size: var(--text-xs);\n  }\n\n  .SearchBreadcrumbPart {\n    display: flex;\n    height: 100%;\n    align-items: center;\n    white-space: nowrap;\n\n    &.last {\n      overflow: hidden;\n      text-overflow: ellipsis;\n    }\n  }\n\n  .SearchBreadcrumbSeparator {\n    color: var(--color-gray-300);\n  }\n\n  .SearchScore {\n    margin-left: 0.375rem;\n    font-size: var(--text-xs);\n    opacity: 0.7;\n    white-space: nowrap;\n  }\n\n  .SearchEmptyState {\n    padding: 1.5rem 0.75rem;\n    text-align: center;\n    font-size: 0.9375rem;\n    font-weight: 400;\n    color: var(--color-gray-600);\n  }\n\n  .SearchInputRoot {\n    display: flex;\n    align-items: center;\n    gap: 0.5rem;\n    height: 2rem;\n    border-radius: var(--radius-md);\n    padding-inline: 0.75rem;\n  }\n\n  .SearchInputIcon {\n    width: 1rem;\n    height: 1rem;\n    flex-shrink: 0;\n    color: var(--color-gray-500);\n  }\n\n  .SearchInput {\n    width: 100%;\n    border: 0;\n    background: transparent;\n    font-size: var(--text-base);\n    font-weight: 400;\n    color: var(--color-gray-900);\n    outline: none;\n  }\n\n  .SearchInput::placeholder {\n    color: var(--color-gray-500);\n  }\n\n  .SearchGroup {\n    display: block;\n  }\n\n  .SearchGroupLabel {\n    margin: 0;\n    display: flex;\n    align-items: center;\n    height: 2rem;\n    padding-left: 0.875rem;\n    font-size: 0.9375rem;\n    font-weight: 400;\n    line-height: 1;\n    color: var(--color-gray-600);\n    user-select: none;\n  }\n\n  .SearchOptionItem {\n    display: flex;\n    align-items: center;\n    gap: 0.25rem;\n    height: 2rem;\n    padding-left: 2.25rem;\n    padding-right: 0.5rem;\n    border-radius: var(--radius-md);\n    font-size: 0.9375rem;\n    font-weight: 400;\n    line-height: 1;\n    color: var(--color-gray-900);\n    cursor: default;\n    user-select: none;\n    outline: none;\n  }\n\n  .SearchOptionItem[data-highlighted] {\n    background: var(--color-gray-100);\n  }\n\n  .SearchBackdrop {\n    position: fixed;\n    inset: 0;\n    min-height: 100dvh;\n    background: black;\n    opacity: 0.2;\n    transition: opacity 150ms ease;\n  }\n\n  .SearchBackdrop[data-ending-style],\n  .SearchBackdrop[data-starting-style] {\n    opacity: 0;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    .SearchBackdrop {\n      opacity: 0.7;\n    }\n  }\n\n  @supports (-webkit-touch-callout: none) {\n    .SearchBackdrop {\n      position: absolute;\n    }\n  }\n\n  .SearchViewportContained {\n    position: fixed;\n    inset: 0;\n    display: flex;\n    align-items: flex-start;\n    justify-content: center;\n    overflow: hidden;\n    padding-top: 4.5rem;\n  }\n\n  .SearchViewportDefault {\n    position: fixed;\n    inset: 0;\n  }\n\n  .SearchPopupContained,\n  .SearchPopupDefault {\n    position: relative;\n    display: flex;\n    flex-direction: column;\n    min-height: 0;\n    border-radius: 1rem;\n    background: var(--color-white);\n    color: var(--color-gray-900);\n    outline: 1px solid hsl(0deg 0% 0% / 4%);\n    box-shadow:\n      0 0.5px 1px hsl(0deg 0% 0% / 12%),\n      0 1px 3px -1px hsl(0deg 0% 0% / 4%),\n      0 2px 4px -1px hsl(0deg 0% 0% / 4%),\n      0 4px 8px -2px hsl(0deg 0% 0% / 4%),\n      0 12px 14px -4px hsl(0deg 0% 0% / 4%),\n      0 24px 64px -8px hsl(0deg 0% 0% / 4%),\n      0 40px 48px -32px hsl(0deg 0% 0% / 4%);\n    transition:\n      transform 150ms ease,\n      opacity 150ms ease;\n  }\n\n  .SearchPopupContained {\n    max-height: min(29.5rem, calc(100vh - 6rem));\n    width: min(34rem, calc(100vw - 2rem));\n    overflow: hidden;\n  }\n\n  .SearchPopupDefault {\n    margin: 4.5rem auto;\n    width: min(40rem, calc(100vw - 2rem));\n  }\n\n  .SearchPopupContained[data-ending-style],\n  .SearchPopupContained[data-starting-style],\n  .SearchPopupDefault[data-ending-style],\n  .SearchPopupDefault[data-starting-style] {\n    transform: translateY(-1rem) scale(0.9);\n    opacity: 0;\n  }\n\n  @media (prefers-color-scheme: dark) {\n    .SearchPopupContained,\n    .SearchPopupDefault {\n      background: oklch(20% 0.5% 264deg);\n      outline-color: hsl(0deg 0% 100% / 25%);\n    }\n  }\n\n  .SearchHeadContained {\n    flex-shrink: 0;\n    border-bottom: 1px solid var(--color-gray-100);\n    padding: 0.5rem 0.5rem 0.375rem;\n  }\n\n  .SearchHeadDefault {\n    border-bottom: 1px solid var(--color-gray-100);\n    padding: 0.5rem 0.5rem 0.375rem;\n  }\n\n  .SearchBody {\n    display: flex;\n    min-height: 0;\n    flex: 1;\n  }\n\n  .SearchScrollAreaRoot {\n    position: relative;\n    display: flex;\n    min-height: 0;\n    flex: 1;\n    overflow: hidden;\n  }\n\n  .SearchScrollAreaViewport {\n    flex: 1;\n    min-height: 0;\n    overflow-y: auto;\n    overscroll-behavior: contain;\n    scroll-padding-top: 2.25rem;\n    scroll-padding-bottom: 0.5rem;\n  }\n\n  .SearchList {\n    outline: 0;\n    padding: 0.5rem;\n  }\n\n  .SearchFooter {\n    border-top: 1px solid var(--color-gray-100);\n    padding: 0.5rem 0.5rem 0.5rem 0.75rem;\n    display: flex;\n    font-size: var(--text-xs);\n    line-height: var(--text-xs--line-height);\n    color: var(--color-gray-500);\n  }\n\n  .SearchFooterHint {\n    display: flex;\n    align-items: center;\n    gap: 0.75rem;\n  }\n\n  .SearchFooterEnter {\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    width: 1.25rem;\n    height: 1.25rem;\n    border-radius: var(--radius-sm);\n    border: 1px solid var(--color-gray-300);\n    background: var(--color-gray-50);\n    font-size: 10px;\n    color: var(--color-gray-600);\n  }\n\n  .SearchRootScrollable {\n    height: 100%;\n    overscroll-behavior: contain;\n  }\n\n  .SearchRootScrollable[data-ending-style] {\n    pointer-events: none;\n  }\n\n  .SearchContentWrap {\n    display: flex;\n    min-height: 100%;\n    align-items: flex-start;\n    justify-content: center;\n  }\n\n  .SearchListDefault {\n    outline: 0;\n    overflow-y: auto;\n    max-height: min(22.5rem, var(--available-height));\n    border-bottom-left-radius: 5px;\n    border-bottom-right-radius: 5px;\n    padding: 0.5rem;\n    overscroll-behavior: contain;\n    scroll-padding-top: 2.25rem;\n    scroll-padding-bottom: 0.5rem;\n  }\n}\n"
  },
  {
    "path": "docs/src/components/Search/SearchBar.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport Link from 'next/link';\nimport clsx from 'clsx';\nimport { useSearch } from '@mui/internal-docs-infra/useSearch';\nimport type {\n  SearchResult,\n  SearchResults,\n  Sitemap,\n} from '@mui/internal-docs-infra/useSearch/types';\nimport { Autocomplete } from '@base-ui/react/autocomplete';\nimport { Button } from '@base-ui/react/button';\nimport { Dialog } from '@base-ui/react/dialog';\nimport { ScrollArea } from '@base-ui/react/scroll-area';\nimport { isMac } from '@base-ui/utils/detectBrowser';\nimport { CornerDownLeft, Search } from 'lucide-react';\nimport { useGoogleAnalytics } from 'docs/src/blocks/GoogleAnalyticsProvider';\nimport { stringToUrl } from '../QuickNav/rehypeSlug.mjs';\nimport './SearchBar.css';\n\nconst showPrivatePages = process.env.SHOW_PRIVATE_PAGES === 'true';\n\n// Semver pattern to detect version headings (e.g., v1.0.0, v1.0.0-rc.0)\n// Used to match the behavior of rehypeConcatHeadings on the Releases page\nconst SEMVER_PATTERN =\n  /^v(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$/;\n\n/**\n * Slugify function that handles parent context concatenation.\n * Matches the behavior of rehypeConcatHeadings for Releases/Forms pages\n * where child heading IDs are prefixed with parent heading text.\n */\nfunction slugifyWithParentContext(text: string, parentTitles?: string[]): string {\n  const slug = stringToUrl(text);\n\n  // If there's a parent title that looks like a semver version, prepend it\n  // This matches the behavior of rehypeConcatHeadings on the Releases page\n  // which generates IDs like: v1.0.0-rc.0-autocomplete\n  if (parentTitles?.length && SEMVER_PATTERN.test(parentTitles[0])) {\n    return `${parentTitles[0]}-${slug}`;\n  }\n\n  return slug;\n}\n\nfunction normalizeGroup(group: string) {\n  return group.replace(/\\s+Pages$/, '').replace(/^React\\s+/, '');\n}\n\nconst SearchItem = React.memo(function SearchItem({ result }: { result: SearchResult }) {\n  return (\n    <React.Fragment>\n      {result.title?.split(' ‣ ').map((part, i, arr) => (\n        <React.Fragment key={part}>\n          <span className={clsx('SearchBreadcrumbPart', i === arr.length - 1 && 'last')}>\n            {part}\n          </span>\n          {i !== arr.length - 1 && (\n            <svg\n              className=\"SearchBreadcrumbSeparator\"\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"16\"\n              height=\"16\"\n              fill=\"none\"\n              viewBox=\"0 0 16 16\"\n            >\n              <path\n                fill=\"currentColor\"\n                fillRule=\"evenodd\"\n                d=\"M5.47 13.03a.75.75 0 0 1 0-1.06L9.44 8 5.47 4.03a.75.75 0 0 1 1.06-1.06l4.5 4.5a.75.75 0 0 1 0 1.06l-4.5 4.5a.75.75 0 0 1-1.06 0\"\n                clipRule=\"evenodd\"\n              />\n            </svg>\n          )}\n        </React.Fragment>\n      ))}\n      {process.env.NODE_ENV !== 'production' && result.score && (\n        <span className=\"SearchScore\">{result.score.toFixed(2)}</span>\n      )}\n    </React.Fragment>\n  );\n});\n\nconst EmptyState = React.memo(function EmptyState() {\n  return <div className=\"SearchEmptyState\">No results found.</div>;\n});\n\nexport function SearchBar({\n  sitemap: sitemapImport,\n  enableKeyboardShortcut = false,\n  containedScroll = false,\n}: {\n  sitemap: () => Promise<{ sitemap?: Sitemap }>;\n  enableKeyboardShortcut?: boolean;\n  containedScroll?: boolean;\n}) {\n  const [dialogOpen, setDialogOpen] = React.useState(false);\n  const inputRef = React.useRef<HTMLInputElement>(null);\n  const popupRef = React.useRef<HTMLDivElement>(null);\n  const ga = useGoogleAnalytics();\n\n  // Search session tracking\n  const searchQueryRef = React.useRef('');\n  const resultCountRef = React.useRef(0);\n  const attemptRef = React.useRef(0);\n  const selectedResultRef = React.useRef<SearchResult | null>(null);\n  const lastTrackedQueryRef = React.useRef('');\n  const queryDebounceRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);\n\n  // Clean up pending debounce on unmount\n  React.useEffect(() => {\n    return () => {\n      if (queryDebounceRef.current) {\n        clearTimeout(queryDebounceRef.current);\n      }\n    };\n  }, []);\n\n  // Use the generic search hook with Base UI specific configuration\n  const { results, search, defaultResults, buildResultUrl } = useSearch({\n    sitemap: sitemapImport,\n    generateSlug: slugifyWithParentContext,\n    tolerance: 0,\n    limit: 20,\n    enableStemming: true,\n    includeCategoryInGroup: true,\n    excludeSections: true,\n    showPrivatePages,\n  });\n\n  // Update search results when hook results change\n  const [searchResults, setSearchResults] =\n    React.useState<ReturnType<typeof useSearch>['results']>(defaultResults);\n  React.useEffect(() => {\n    // Track result count for the current query\n    const totalResults = results.results.reduce((sum, group) => sum + group.items.length, 0);\n    resultCountRef.current = totalResults;\n\n    const updateResults = () => {\n      setSearchResults(results);\n    };\n\n    // Delay empty results to avoid flashing \"No results\" while typing\n    if (results.results.length === 0) {\n      const timeoutId = setTimeout(updateResults, 400);\n      return () => clearTimeout(timeoutId);\n    }\n\n    updateResults();\n    return undefined;\n  }, [results]);\n\n  const handleOpenDialog = React.useCallback(() => {\n    // Reset search session tracking\n    searchQueryRef.current = '';\n    resultCountRef.current = 0;\n    attemptRef.current = 0;\n    selectedResultRef.current = null;\n    lastTrackedQueryRef.current = '';\n    if (queryDebounceRef.current) {\n      clearTimeout(queryDebounceRef.current);\n      queryDebounceRef.current = null;\n    }\n    ga?.trackEvent({ category: 'search', action: 'open' });\n    setDialogOpen(true);\n  }, [ga]);\n\n  const handleCloseDialog = React.useCallback(\n    (open: boolean) => {\n      if (!open) {\n        // Cancel any pending debounced query event\n        if (queryDebounceRef.current) {\n          clearTimeout(queryDebounceRef.current);\n          queryDebounceRef.current = null;\n        }\n\n        // Fire final search event for the current query\n        if (searchQueryRef.current) {\n          const selected = selectedResultRef.current;\n          ga?.trackEvent({\n            category: 'search',\n            action: selected ? 'select' : 'dismiss',\n            label: searchQueryRef.current,\n            params: {\n              search_term: searchQueryRef.current,\n              result_count: resultCountRef.current,\n              attempt: attemptRef.current,\n              ...(selected\n                ? {\n                    selected_result: selected.title || selected.slug,\n                    selected_type: selected.type || '',\n                  }\n                : { failed: searchQueryRef.current }),\n            },\n          });\n          lastTrackedQueryRef.current = searchQueryRef.current;\n        }\n\n        setDialogOpen(false);\n\n        // Wait for the closing animation to complete before resetting state\n        setTimeout(() => {\n          setSearchResults(defaultResults);\n        }, 200);\n      } else {\n        handleOpenDialog();\n      }\n    },\n    [handleOpenDialog, defaultResults, ga],\n  );\n\n  const handleAutocompleteEscape = React.useCallback(\n    (open: boolean, eventDetails: Autocomplete.Root.ChangeEventDetails) => {\n      if (!open && eventDetails.reason === 'escape-key') {\n        handleCloseDialog(false);\n      }\n    },\n    [handleCloseDialog],\n  );\n\n  React.useEffect(() => {\n    // Only enable keyboard shortcut if explicitly requested (for desktop version)\n    if (!enableKeyboardShortcut) {\n      return undefined;\n    }\n\n    const handleKeyDown = (event: KeyboardEvent) => {\n      if ((event.metaKey || event.ctrlKey) && event.key === 'k') {\n        event.preventDefault();\n        event.stopPropagation();\n\n        // Only open if not already open or in the process of opening/closing\n        if (!dialogOpen) {\n          handleOpenDialog();\n        }\n      }\n    };\n\n    window.addEventListener('keydown', handleKeyDown, { capture: true });\n    return () => window.removeEventListener('keydown', handleKeyDown, { capture: true });\n  }, [handleOpenDialog, enableKeyboardShortcut, dialogOpen]);\n\n  const handleValueChange = React.useCallback(\n    async (value: string) => {\n      // Cancel any pending debounced query event\n      if (queryDebounceRef.current) {\n        clearTimeout(queryDebounceRef.current);\n        queryDebounceRef.current = null;\n      }\n\n      const previousLength = searchQueryRef.current?.length ?? 0;\n      searchQueryRef.current = value;\n      if (value) {\n        // Increment attempt when starting a new query (transition from empty to non-empty)\n        if (previousLength === 0 && value.length > 0) {\n          attemptRef.current += 1;\n        }\n\n        // Fire a debounced 'query' event when the user pauses typing\n        queryDebounceRef.current = setTimeout(() => {\n          if (searchQueryRef.current && searchQueryRef.current !== lastTrackedQueryRef.current) {\n            ga?.trackEvent({\n              category: 'search',\n              action: 'query',\n              label: searchQueryRef.current,\n              params: {\n                search_term: searchQueryRef.current,\n                result_count: resultCountRef.current,\n                attempt: attemptRef.current,\n              },\n            });\n            lastTrackedQueryRef.current = searchQueryRef.current;\n          }\n        }, 1500);\n      }\n      await search(value, { groupBy: { properties: ['group'], maxResult: 5 } });\n    },\n    [search, ga],\n  );\n\n  const highlightedResultRef = React.useRef<SearchResult | undefined>(undefined);\n\n  const handleItemClick = React.useCallback(() => {\n    selectedResultRef.current = highlightedResultRef.current ?? null;\n    handleCloseDialog(false);\n  }, [handleCloseDialog]);\n\n  const handleItemHighlighted = React.useCallback((item: SearchResult | undefined) => {\n    highlightedResultRef.current = item;\n  }, []);\n\n  const handleKeyDownCapture = React.useCallback(\n    (event: React.KeyboardEvent) => {\n      // Only handle Enter with modifiers\n      if (event.key !== 'Enter' || (!event.metaKey && !event.ctrlKey && !event.altKey)) {\n        return;\n      }\n\n      const highlightedResult = highlightedResultRef.current;\n      if (!highlightedResult) {\n        return;\n      }\n\n      // Prevent the Input/List handlers from processing this\n      event.preventDefault();\n      event.stopPropagation();\n\n      // Open in new tab\n      const url = buildResultUrl(highlightedResult);\n      window.open(url, '_blank', 'noopener,noreferrer');\n    },\n    [buildResultUrl],\n  );\n\n  const showCmdSymbol = React.useSyncExternalStore(\n    () => () => {},\n    () => enableKeyboardShortcut && isMac,\n    () => true, // Show Cmd symbol on server-side render\n  );\n\n  // Memoized search input component\n  const searchInput = React.useMemo(\n    () => (\n      <div className=\"SearchInputRoot\">\n        <Search className=\"SearchInputIcon\" />\n        <Autocomplete.Input\n          id=\"search-input\"\n          ref={inputRef}\n          placeholder=\"Search\"\n          className=\"SearchInput\"\n          onKeyDownCapture={handleKeyDownCapture}\n        />\n      </div>\n    ),\n    [handleKeyDownCapture],\n  );\n\n  // Memoized callback for itemToStringValue\n  const itemToStringValue = React.useCallback(\n    (item: SearchResult | null) => (item ? item.title || item.slug : ''),\n    [],\n  );\n\n  // Memoized render function for result groups\n  const renderResultsList = React.useCallback(\n    (group: SearchResults[number]) => (\n      <Autocomplete.Group key={group.group} items={group.items} className=\"SearchGroup\">\n        {group.group !== 'Default' && (\n          <Autocomplete.GroupLabel id={`search-group-${group.group}`} className=\"SearchGroupLabel\">\n            {normalizeGroup(group.group)}\n          </Autocomplete.GroupLabel>\n        )}\n        <Autocomplete.Collection>\n          {(result: SearchResult, i) => (\n            <Autocomplete.Item\n              key={result.id || i}\n              value={result}\n              render={\n                <Link href={buildResultUrl(result)} onNavigate={handleItemClick} tabIndex={-1} />\n              }\n              className=\"SearchOptionItem\"\n            >\n              <SearchItem result={result} />\n            </Autocomplete.Item>\n          )}\n        </Autocomplete.Collection>\n      </Autocomplete.Group>\n    ),\n    [buildResultUrl, handleItemClick],\n  );\n\n  return (\n    <React.Fragment>\n      <Button onClick={handleOpenDialog} aria-label=\"Search\" className={`SearchTrigger`}>\n        <Search className=\"SearchTriggerIcon\" />\n        <div className=\"SearchTriggerKbd\">\n          {showCmdSymbol ? (\n            <kbd className=\"SearchTriggerCmd\">⌘</kbd>\n          ) : (\n            <React.Fragment>\n              <kbd className=\"SearchTriggerCtrl\">Ctrl</kbd>\n              <span className=\"SearchTriggerPlus\">+</span>\n            </React.Fragment>\n          )}\n          <kbd className=\"SearchTriggerK\">K</kbd>\n        </div>\n      </Button>\n      <Dialog.Root open={dialogOpen} onOpenChange={handleCloseDialog}>\n        <Dialog.Portal>\n          <Dialog.Backdrop className=\"SearchBackdrop\" />\n          {containedScroll ? (\n            <Dialog.Viewport className=\"SearchViewportContained\">\n              <Dialog.Popup\n                ref={popupRef}\n                initialFocus={inputRef}\n                data-open={dialogOpen}\n                className=\"SearchPopupContained\"\n              >\n                <Autocomplete.Root\n                  items={searchResults.results}\n                  onValueChange={handleValueChange}\n                  onOpenChange={handleAutocompleteEscape}\n                  onItemHighlighted={handleItemHighlighted}\n                  open\n                  inline\n                  itemToStringValue={itemToStringValue}\n                  filter={null}\n                  autoHighlight=\"always\"\n                  keepHighlight\n                >\n                  <div className=\"SearchHeadContained\">{searchInput}</div>\n                  <div className=\"SearchBody\">\n                    <ScrollArea.Root className=\"SearchScrollAreaRoot\">\n                      <ScrollArea.Viewport className=\"SearchScrollAreaViewport\">\n                        <ScrollArea.Content style={{ minWidth: '100%' }}>\n                          {searchResults.results.length === 0 ? (\n                            <EmptyState />\n                          ) : (\n                            <Autocomplete.List\n                              className=\"SearchList\"\n                              onKeyDownCapture={handleKeyDownCapture}\n                            >\n                              {renderResultsList}\n                            </Autocomplete.List>\n                          )}\n                        </ScrollArea.Content>\n                      </ScrollArea.Viewport>\n                      <ScrollArea.Scrollbar className=\"SearchScrollbar \">\n                        <ScrollArea.Thumb className=\"SearchScrollbarThumb\" />\n                      </ScrollArea.Scrollbar>\n                    </ScrollArea.Root>\n                  </div>\n                  <div className=\"SearchFooter\">\n                    <div className=\"SearchFooterHint\">\n                      <kbd aria-label=\"Enter\" className=\"SearchFooterEnter\">\n                        <CornerDownLeft size={12} />\n                      </kbd>\n                      <span>Go to page</span>\n                    </div>\n                  </div>\n                </Autocomplete.Root>\n              </Dialog.Popup>\n            </Dialog.Viewport>\n          ) : (\n            <Dialog.Viewport className=\"SearchViewportDefault\">\n              <ScrollArea.Root style={{ position: undefined }} className=\"SearchRootScrollable\">\n                <ScrollArea.Viewport className=\"SearchRootScrollable\">\n                  <ScrollArea.Content className=\"SearchContentWrap\">\n                    <Dialog.Popup\n                      ref={popupRef}\n                      initialFocus={inputRef}\n                      data-open={dialogOpen}\n                      className=\"SearchPopupDefault\"\n                    >\n                      <Autocomplete.Root\n                        items={searchResults.results}\n                        onValueChange={handleValueChange}\n                        onOpenChange={handleAutocompleteEscape}\n                        onItemHighlighted={handleItemHighlighted}\n                        open\n                        inline\n                        itemToStringValue={itemToStringValue}\n                        filter={null}\n                        autoHighlight\n                      >\n                        <div className=\"SearchHeadDefault\">{searchInput}</div>\n                        <div>\n                          {searchResults.results.length === 0 ? (\n                            <EmptyState />\n                          ) : (\n                            <Autocomplete.List\n                              className=\"SearchListDefault\"\n                              onKeyDownCapture={handleKeyDownCapture}\n                            >\n                              {renderResultsList}\n                            </Autocomplete.List>\n                          )}\n                        </div>\n                      </Autocomplete.Root>\n                    </Dialog.Popup>\n                  </ScrollArea.Content>\n                </ScrollArea.Viewport>\n                <ScrollArea.Scrollbar className=\"SearchScrollbar \">\n                  <ScrollArea.Thumb className=\"SearchScrollbarThumb\" />\n                </ScrollArea.Scrollbar>\n              </ScrollArea.Root>\n            </Dialog.Viewport>\n          )}\n        </Dialog.Portal>\n      </Dialog.Root>\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "docs/src/components/Search/index.ts",
    "content": "export * from './Search';\n"
  },
  {
    "path": "docs/src/components/Select.css",
    "content": "@layer components {\n  .SelectPositioner {\n    /* The outline is sometimes seen in Safari when the positioner is focused intermittently */\n    outline: 0;\n    z-index: 1;\n\n    @media (prefers-color-scheme: light) {\n      /* Use filter for the shadow in order to catch the arrow part */\n      filter: drop-shadow(0 0 1px var(--color-gray-200))\n        drop-shadow(0 0.5px 1px var(--color-gray-200)) drop-shadow(0 1px 2px var(--color-gray-200))\n        drop-shadow(0 3px 8px var(--color-gray-300));\n    }\n  }\n\n  .SelectPopup {\n    outline: 0;\n    max-width: var(--available-width);\n    max-height: var(--available-height);\n    border-radius: var(--radius-md);\n    background-color: var(--color-popup);\n    overflow: hidden;\n    cursor: default;\n    user-select: none;\n    padding-block: 0.25rem;\n\n    /* Make sure the layout doesn't fall apart on extreme zoom on mobile */\n    min-width: min-content;\n\n    @media (prefers-color-scheme: dark) {\n      box-shadow: inset 0 0 0 1px var(--color-gray-300);\n    }\n  }\n\n  .SelectItem {\n    font-size: var(--text-xs);\n    line-height: var(--text-xs--line-height);\n    outline: 0;\n    display: grid;\n    align-items: center;\n    height: 1.75rem;\n    white-space: nowrap;\n    z-index: 0;\n\n    /* Spacing is built into the grid (it's a bit easier this way) */\n    grid-template-columns: 0.625rem 0.75rem 0.375rem auto 1.75rem;\n\n    &[data-highlighted] {\n      position: relative;\n      color: white;\n\n      &::before {\n        content: '';\n        position: absolute;\n        inset-inline: 0.25rem;\n        inset-block: 0;\n        z-index: -1;\n        border-radius: var(--radius-sm);\n        background-color: var(--color-highlight);\n      }\n    }\n\n    @media (pointer: coarse) {\n      font-size: var(--text-md);\n      line-height: var(--text-md--line-height);\n      height: 2.25rem;\n      grid-template-columns: 0.5rem 0.75rem 0.5rem auto 1.5rem;\n    }\n  }\n\n  .SelectItemIndicator {\n    grid-column: 2;\n    margin-top: -1px;\n    width: 100%;\n    height: auto;\n  }\n\n  .SelectItemText {\n    grid-column: 4;\n  }\n\n  .SelectArrow {\n    &[data-side='bottom'] {\n      top: -6px;\n    }\n\n    &[data-side='top'] {\n      bottom: -6px;\n      transform: scaleY(-1);\n    }\n  }\n\n  .SelectArrowFill {\n    fill: var(--color-popup);\n  }\n\n  .SelectArrowStroke {\n    @media (prefers-color-scheme: dark) {\n      fill: var(--color-gray-300);\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/components/Select.tsx",
    "content": "import { Select } from '@base-ui/react/select';\nimport clsx from 'clsx';\nimport { ChevronDownIcon } from '../icons/ChevronDownIcon';\nimport { ThickCheckIcon } from '../icons/ThickCheckIcon';\nimport './Select.css';\n\nexport const Root = Select.Root;\n\ninterface TriggerProps extends Omit<Select.Trigger.Props, 'children'> {\n  children?: Select.Value.Props['children'];\n}\n\nexport function Trigger({ className, children, ...props }: TriggerProps) {\n  return (\n    // Implicitly relying on <GhostButton>, keep it in sync\n    <Select.Trigger data-layout=\"text\" className=\"GhostButton\" type={undefined} {...props}>\n      <Select.Value>{children}</Select.Value>\n      <Select.Icon render={<ChevronDownIcon className=\"bui-ml--0.5\" />} />\n    </Select.Trigger>\n  );\n}\n\nexport function Popup({ children, className, ...props }: Select.Positioner.Props) {\n  return (\n    <Select.Portal>\n      <Select.Positioner align=\"center\" sideOffset={7} className=\"SelectPositioner\" {...props}>\n        <Select.Popup className={clsx('SelectPopup', className)}>{children}</Select.Popup>\n\n        {/* Used on mobile */}\n        <Select.Arrow\n          className=\"SelectArrow\"\n          render={\n            <svg width=\"20\" height=\"8\" viewBox=\"0 0 20 8\" xmlns=\"http://www.w3.org/2000/svg\">\n              <path\n                className=\"SelectArrowFill\"\n                d=\"M9.66437 0.602068L4.80758 4.97318C4.07308 5.63423 3.11989 6 2.13172 6H0V8H20V6H18.5349C17.5468 6 16.5936 5.63423 15.8591 4.97318L11.0023 0.602068C10.622 0.259794 10.0447 0.259793 9.66437 0.602068Z\"\n              />\n              <path\n                fill=\"none\"\n                className=\"SelectArrowStroke\"\n                d=\"M10.3333 1.34537L5.47655 5.71648C4.55842 6.54279 3.36693 7.00001 2.13172 7.00001H1H0V6.00001H2.13172C3.11989 6.00001 4.07308 5.63423 4.80758 4.97318L9.66437 0.602073C10.0447 0.259799 10.622 0.259799 11.0023 0.602073L15.8591 4.97318C16.5936 5.63423 17.5468 6.00001 18.5349 6.00001H20V7.00001H19H18.5349C17.2997 7.00001 16.1082 6.54279 15.1901 5.71648L10.3333 1.34537Z\"\n              />\n            </svg>\n          }\n        />\n      </Select.Positioner>\n    </Select.Portal>\n  );\n}\n\nexport function Item({ children, className, ...props }: Select.Item.Props) {\n  return (\n    <Select.Item className={clsx('SelectItem', className)} {...props}>\n      <Select.ItemIndicator className=\"SelectItemIndicator\" render={<ThickCheckIcon />} />\n      <Select.ItemText className=\"SelectItemText\">{children}</Select.ItemText>\n    </Select.Item>\n  );\n}\n"
  },
  {
    "path": "docs/src/components/SideNav.css",
    "content": "@import 'docs/src/css/custom-media.css';\n\n@layer components {\n  .SideNavRoot {\n    /* Match quick nav spacing so side nav and quick nav are visually aligned */\n    --side-nav-item-height: 2rem;\n    --side-nav-item-line-height: var(--text-md--line-height);\n    --side-nav-item-padding-y: calc(\n      var(--side-nav-item-height) / 2 - var(--side-nav-item-line-height) / 2\n    );\n\n    --side-nav-scrollbar-thumb-width: 0.25rem;\n    --side-nav-scrollbar-width: 1.5rem;\n    --side-nav-scrollbar-gap-left: 1rem;\n    --side-nav-scrollbar-gap-right: 2.5rem;\n    margin-right: calc(\n      var(--side-nav-scrollbar-gap-right) - var(--side-nav-scrollbar-width) / 2 +\n        var(--side-nav-scrollbar-thumb-width) / 2\n    );\n\n    font-size: var(--text-md);\n    line-height: var(--text-md--line-height);\n    position: sticky;\n    top: 0;\n\n    display: none;\n\n    @media (--show-side-nav) {\n      display: block;\n    }\n  }\n\n  .SideNavViewport {\n    max-height: 100vh;\n    padding: 0.75rem\n      calc(\n        var(--side-nav-scrollbar-gap-left) + var(--side-nav-scrollbar-width) / 2 +\n          var(--side-nav-scrollbar-thumb-width) / 2\n      )\n      6rem 1.5rem;\n\n    /* Scroll containers are focusable */\n    outline: 0;\n\n    .SideNavRoot:has(&:focus-visible)::before {\n      content: '';\n      inset: 0;\n      pointer-events: none;\n      position: absolute;\n      outline: 2px solid var(--color-blue);\n      outline-offset: -2px;\n      /* Don't inset the outline on the right */\n      right: -2px;\n    }\n  }\n\n  .SideNavScrollbar {\n    display: flex;\n    padding-top: 1.5rem;\n    padding-bottom: 3rem;\n\n    /* Click target width */\n    width: var(--side-nav-scrollbar-width);\n\n    opacity: 0;\n    transition: opacity 200ms 500ms;\n\n    &:active,\n    &[data-scrolling],\n    .SideNavViewport:focus-visible + & {\n      transition-duration: 0ms;\n      transition-delay: 0ms;\n      opacity: 1;\n    }\n  }\n\n  .SideNavScrollbarThumb {\n    display: flex;\n    justify-content: center;\n    width: 100%;\n\n    &::before {\n      content: '';\n      display: block;\n      height: 100%;\n      /* Visible thumb width */\n      width: var(--side-nav-scrollbar-thumb-width);\n      border-radius: var(--radius-sm);\n      background-color: var(--color-gray-400);\n    }\n  }\n\n  .SideNavSection {\n    margin-bottom: 1rem;\n  }\n\n  .SideNavHeading {\n    display: inline-flex;\n    padding-block: var(--side-nav-item-padding-y);\n    font-weight: 400;\n    color: var(--color-gray-600);\n  }\n\n  .SideNavItem {\n    display: flex;\n  }\n\n  .SideNavLink {\n    display: flex;\n    align-items: center;\n    gap: 4px;\n    flex-grow: 1;\n    padding: calc(var(--side-nav-item-padding-y) - 1px) 0.75rem;\n    border-block: 1px solid transparent; /* Maintain a 1px gap between active an inactive item */\n    background-clip: padding-box;\n    border-radius: var(--radius-md);\n    user-select: none;\n\n    @media (hover: hover) {\n      &:hover {\n        background-color: var(--color-gray-50);\n      }\n    }\n\n    &[data-active] {\n      border: none;\n      padding: var(--side-nav-item-padding-y) 0.75rem;\n      background-color: var(--color-gray-50);\n      outline: 1px solid var(--color-gray-200);\n      outline-offset: -1px;\n      font-weight: 400;\n      word-spacing: -0.005em;\n      cursor: default;\n    }\n\n    &:focus-visible {\n      z-index: 1;\n      outline: 2px solid var(--color-blue);\n      outline-offset: -1px;\n    }\n  }\n\n  .SideNavBadge {\n    color: var(--color-red);\n    line-height: inherit;\n    user-select: none;\n    text-transform: uppercase;\n    font-size: 0.6875rem;\n    font-weight: 700;\n    letter-spacing: 0.035em;\n    padding-inline: 2px;\n    translate: 0 -2px;\n    word-break: keep-all;\n  }\n}\n"
  },
  {
    "path": "docs/src/components/SideNav.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport clsx from 'clsx';\nimport NextLink from 'next/link';\nimport { usePathname } from 'next/navigation';\nimport { ScrollArea } from '@base-ui/react/scroll-area';\nimport scrollIntoView from 'scroll-into-view-if-needed';\nimport { HEADER_HEIGHT } from './Header';\nimport './SideNav.css';\n\nexport function Root(props: React.ComponentProps<'div'>) {\n  return (\n    <nav aria-label=\"Main navigation\" {...props} className={clsx('SideNavRoot', props.className)}>\n      <ScrollArea.Root>\n        <ScrollArea.Viewport data-side-nav-viewport className=\"SideNavViewport\">\n          {props.children}\n        </ScrollArea.Viewport>\n        <ScrollArea.Scrollbar className=\"SideNavScrollbar\" orientation=\"vertical\">\n          <ScrollArea.Thumb className=\"SideNavScrollbarThumb\" />\n        </ScrollArea.Scrollbar>\n      </ScrollArea.Root>\n    </nav>\n  );\n}\n\nexport function Section(props: React.ComponentProps<'div'>) {\n  return <div {...props} className={clsx('SideNavSection', props.className)} />;\n}\n\nexport function Heading(props: React.ComponentProps<'div'>) {\n  return <div {...props} className={clsx('SideNavHeading', props.className)} />;\n}\n\nexport function List(props: React.ComponentProps<'ul'>) {\n  return <ul {...props} className={clsx('SideNavList', props.className)} />;\n}\n\nexport function Badge(props: React.ComponentProps<'span'>) {\n  return <span {...props} className={clsx('SideNavBadge', props.className)} />;\n}\n\ninterface ItemProps extends React.ComponentProps<'li'> {\n  active?: boolean;\n  href: string;\n  isNew?: boolean;\n  external?: boolean;\n}\n\nconst SCROLL_MARGIN = 48;\n\nexport function Item(props: ItemProps) {\n  const { children, className, href, external, ...other } = props;\n  const ref = React.useRef<HTMLLIElement>(null);\n  const pathname = usePathname();\n  const active = pathname === href;\n  const rem = React.useRef(16);\n\n  React.useEffect(() => {\n    rem.current = parseFloat(getComputedStyle(document.documentElement).fontSize);\n  }, []);\n\n  React.useEffect(() => {\n    if (ref.current && active) {\n      const scrollMargin = (SCROLL_MARGIN * rem.current) / 16;\n      const headerHeight = (HEADER_HEIGHT * rem.current) / 16;\n      const viewport = document.querySelector('[data-side-nav-viewport]');\n\n      if (!viewport) {\n        return;\n      }\n\n      scrollIntoView(ref.current, {\n        block: 'nearest',\n        scrollMode: 'if-needed',\n        boundary: (parent) => viewport.contains(parent),\n        behavior: (actions) => {\n          actions.forEach(({ top }) => {\n            const dir = viewport.scrollTop > top ? -1 : 1;\n            const offset = Math.max(0, headerHeight - Math.max(0, window.scrollY));\n            viewport.scrollTop = top + offset + scrollMargin * dir;\n          });\n        },\n      });\n    }\n  }, [active]);\n\n  const LinkComponent = external ? 'a' : NextLink;\n\n  return (\n    <li ref={ref} {...other} className={clsx('SideNavItem', className)}>\n      <LinkComponent\n        className=\"SideNavLink\"\n        href={href}\n        scroll={external ? undefined : !active}\n        {...(active\n          ? {\n              'aria-current': true,\n              'data-active': true,\n              onClick: () => {\n                // Scroll to top smoothly when clicking on the currently active item\n                window.scrollTo({ top: 0, behavior: 'smooth' });\n              },\n            }\n          : {})}\n      >\n        {children}\n      </LinkComponent>\n    </li>\n  );\n}\n"
  },
  {
    "path": "docs/src/components/SkipNav.css",
    "content": "@layer components {\n  .SkipNav {\n    clip: rect(0 0 0 0);\n    clip-path: inset(50%);\n    overflow: hidden;\n    white-space: nowrap;\n    border: 0;\n    position: absolute;\n    padding: 0;\n    width: 1px;\n    height: 1px;\n    margin: -1px;\n\n    &:focus-visible {\n      clip: auto;\n      clip-path: none;\n      height: var(--header-height);\n      overflow: visible;\n      width: auto;\n      white-space: normal;\n      left: 0;\n      z-index: 1;\n      display: inline-flex;\n      justify-content: center;\n      align-items: center;\n      padding-inline: 12px;\n      background-color: var(--color-background);\n      color: var(--color-blue);\n      text-decoration: underline;\n      text-underline-offset: 2px;\n      text-decoration-thickness: 1px;\n      text-decoration-color: color-mix(in oklab, var(--color-blue), transparent);\n      outline: 2px solid var(--color-blue);\n      outline-offset: -2px;\n      border-radius: var(--radius-sm);\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/components/SkipNav.tsx",
    "content": "import * as React from 'react';\nimport clsx from 'clsx';\nimport './SkipNav.css';\n\nexport const MAIN_CONTENT_ID = 'main-content';\nconst HREF = `#${MAIN_CONTENT_ID}`;\n\nexport function SkipNav({ className, ...props }: React.ComponentProps<'a'>) {\n  return <a className={clsx('SkipNav', className)} href={HREF} {...props} />;\n}\n"
  },
  {
    "path": "docs/src/components/Subtitle/MarkdownLink.css",
    "content": "@layer components {\n  .MarkdownLinkAnchor {\n    flex-shrink: 0;\n  }\n\n  .MarkdownLink {\n    font-size: var(--text-sm);\n    line-height: var(--text-sm--line-height);\n    display: inline-flex;\n    align-items: baseline;\n    gap: 0.5em;\n    padding: 0.5em;\n    margin-inline: -0.5em;\n    color: inherit;\n\n    &:focus-visible {\n      color: var(--color-foreground);\n      outline: 2px solid var(--color-blue);\n      outline-offset: -2px;\n      border-radius: var(--radius-sm);\n    }\n\n    @media (hover: hover) {\n      &:hover {\n        text-decoration: underline;\n        text-underline-offset: 2px;\n        text-decoration-thickness: 1px;\n        text-decoration-color: var(--color-gray-500);\n      }\n    }\n  }\n\n  .MarkdownLink svg {\n    /* visually center the icon so we can baseline align it */\n    transform: translateY(3px);\n  }\n}\n"
  },
  {
    "path": "docs/src/components/Subtitle/MarkdownLink.tsx",
    "content": "'use client';\nimport { usePathname } from 'next/navigation';\nimport { MarkdownIcon } from '../../icons/MarkdownIcon';\nimport './MarkdownLink.css';\n\nexport function MarkdownLink() {\n  const pathname = usePathname();\n\n  return (\n    <a\n      href={`${pathname}.md`}\n      className=\"MarkdownLinkAnchor\"\n      aria-label=\"View markdown source\"\n      rel=\"alternate\"\n      type=\"text/markdown\"\n    >\n      <span className=\"MarkdownLink\">\n        <MarkdownIcon />\n        View as Markdown\n      </span>\n    </a>\n  );\n}\n"
  },
  {
    "path": "docs/src/components/Subtitle/Subtitle.css",
    "content": "@layer components {\n  .Subtitle {\n    display: flex;\n    flex-direction: column;\n    align-items: baseline;\n    justify-content: space-between;\n    font-size: var(--text-lg);\n    line-height: var(--text-lg--line-height);\n    text-wrap: pretty;\n    color: var(--color-gray);\n    margin-bottom: 1.25rem;\n    gap: 0.5rem;\n\n    @media (min-width: 48rem) {\n      flex-direction: row;\n      gap: 2rem;\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/components/Subtitle/Subtitle.tsx",
    "content": "import * as React from 'react';\nimport clsx from 'clsx';\nimport { MarkdownLink } from './MarkdownLink';\nimport './Subtitle.css';\n\nexport function Subtitle({\n  className,\n  skipMarkdownLink = false,\n  ...props\n}: React.ComponentProps<'p'> & { skipMarkdownLink?: boolean }) {\n  return (\n    <div className={clsx('Subtitle', className)}>\n      <p {...props} />\n      {!skipMarkdownLink && <MarkdownLink />}\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/components/Subtitle/rehypeSubtitle.mjs",
    "content": "import { visitParents } from 'unist-util-visit-parents';\n\n/**\n * Unwrap potential paragraphs inside `<Subtitle>`\n */\nexport function rehypeSubtitle() {\n  return (tree) => {\n    visitParents(tree, (node, ancestors) => {\n      const parent = ancestors.slice(-1)[0];\n\n      if (parent?.name !== 'Subtitle' || node.tagName !== 'p') {\n        return;\n      }\n\n      const index = parent.children.indexOf(node);\n      parent.children.splice(index, 1, ...node.children);\n    });\n  };\n}\n"
  },
  {
    "path": "docs/src/components/Table.css",
    "content": "@layer components {\n  .TableRoot {\n    font-size: var(--text-sm);\n    line-height: var(--text-sm--line-height);\n    color: var(--color-gray);\n    border: 1px solid var(--color-gray-200);\n    border-collapse: separate;\n    border-radius: var(--radius-md);\n    white-space: nowrap;\n    overflow: hidden;\n  }\n\n  .TableRootTable {\n    table-layout: fixed;\n    width: 100%;\n    vertical-align: top;\n\n    /* Tailwind's border-collapse: collapse causes alpha blending issues in Safari */\n    border-collapse: separate;\n    border-spacing: 0;\n  }\n\n  .TableHead,\n  .TableBody {\n    vertical-align: inherit;\n  }\n\n  .TableColumnHeader {\n    text-align: initial;\n    font-weight: 700;\n    color: var(--color-foreground);\n    background-color: var(--color-gray-50);\n    border-bottom: 1px solid var(--color-gray-200);\n  }\n\n  .TableCell {\n    text-align: initial;\n    font-weight: 400;\n\n    :not(:last-child) > & {\n      border-bottom: 1px solid var(--color-gray-200);\n    }\n\n    &[data-scrollable] {\n      position: relative;\n\n      /* Overscroll overlay */\n      &::after {\n        content: '';\n        position: absolute;\n        pointer-events: none;\n        top: 1px;\n        bottom: 1px;\n        right: -1px; /* Browsers may miss half a pixel due to subpixel table cell widths */\n        width: 1.25rem;\n        background-image: linear-gradient(to right, transparent, var(--color-content));\n        animation: table-cell-overscroll-overlay 500ms;\n      }\n    }\n  }\n\n  @keyframes table-cell-overscroll-overlay {\n    from {\n      opacity: 0;\n    }\n  }\n\n  .TableCellInner {\n    /* Ensure consistent height of the cells */\n    display: flex;\n    align-items: center;\n    min-height: 2.5rem;\n    padding: 0.5rem 0.75rem;\n\n    /* Make individual cells scrollable */\n    overflow-x: auto;\n    overscroll-behavior-x: contain;\n    scrollbar-width: none;\n\n    &::-webkit-scrollbar {\n      display: none;\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/components/Table.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport clsx from 'clsx';\nimport { observeScrollableInner } from '../utils/observeScrollableInner';\n\nexport function Root({ className, children, ...other }: React.ComponentProps<'div'>) {\n  return (\n    <div className={clsx('TableRoot', className)} {...other}>\n      <table className=\"TableRootTable\">{children}</table>\n    </div>\n  );\n}\n\nexport function Head(props: React.ComponentProps<'thead'>) {\n  return <thead {...props} className={clsx('TableHead', props.className)} />;\n}\n\nexport function Body(props: React.ComponentProps<'tbody'>) {\n  return <tbody {...props} className={clsx('TableBody', props.className)} />;\n}\n\nexport function Row(props: React.ComponentProps<'tr'>) {\n  return <tr {...props} className={clsx('TableRow', props.className)} />;\n}\n\nexport function ColumnHeader({\n  children,\n  className,\n  ...other\n}: Omit<React.ComponentProps<'th'>, 'scope'>) {\n  return (\n    <th scope=\"col\" className={clsx('TableColumnHeader', className)} {...other}>\n      <span className=\"TableCellInner\">{children}</span>\n    </th>\n  );\n}\n\nexport function RowHeader({\n  children,\n  className,\n  ...other\n}: Omit<React.ComponentProps<'th'>, 'scope'>) {\n  return (\n    <th\n      scope=\"row\"\n      ref={observeScrollableInner}\n      className={clsx('TableCell', className)}\n      {...other}\n    >\n      <span className=\"TableCellInner\">{children}</span>\n    </th>\n  );\n}\n\nexport function Cell({ children, className, ...other }: React.ComponentProps<'td'>) {\n  return (\n    <td ref={observeScrollableInner} className={clsx('TableCell', className)} {...other}>\n      <span className=\"TableCellInner\">{children}</span>\n    </td>\n  );\n}\n"
  },
  {
    "path": "docs/src/components/TableCode.css",
    "content": "@layer components {\n  .TableCode {\n    font-size: var(--text-xs);\n    line-height: var(--text-xs--line-height);\n    letter-spacing: var(--text-xs--letter-spacing);\n  }\n}\n"
  },
  {
    "path": "docs/src/components/TableCode.tsx",
    "content": "import * as React from 'react';\nimport clsx from 'clsx';\nimport { Code } from './Code';\nimport { getChildrenText } from '../utils/getChildrenText';\n\nexport interface TableCodeProps extends React.ComponentProps<'code'> {\n  printWidth?: number;\n}\n\n/** An inline code component that breaks long union types into multiple lines */\nexport function TableCode({ children, className, printWidth = 40, ...props }: TableCodeProps) {\n  const text = getChildrenText(children);\n\n  if (text.includes('|') && text.length > printWidth) {\n    const unionGroups: React.ReactNode[][] = [];\n    const parts = React.Children.toArray(children);\n\n    let parenDepth = 0;\n    let braceDepth = 0;\n    let groupIndex = 0;\n    unionGroups.push([]);\n\n    parts.forEach((child, index) => {\n      const str = getChildrenText(child);\n\n      // Track parentheses depth\n      str.split('(').forEach(() => {\n        parenDepth += 1;\n      });\n      str.split(')').forEach(() => {\n        parenDepth -= 1;\n      });\n      // Track braces depth\n      str.split('{').forEach(() => {\n        braceDepth += 1;\n      });\n      str.split('}').forEach(() => {\n        braceDepth -= 1;\n      });\n\n      // Break only on top-level pipes (not inside parens or braces)\n      if (str.trim() === '|' && parenDepth <= 0 && braceDepth <= 0 && index !== 0) {\n        unionGroups.push([]);\n        groupIndex += 1;\n        return;\n      }\n\n      unionGroups[groupIndex].push(child);\n    });\n\n    // Insert pipe fragments before each group\n    if (unionGroups.length > 1) {\n      const enhanced: React.ReactNode[] = [];\n      unionGroups.forEach((group, idx) => {\n        const pipeElem = <span style={{ color: 'var(--syntax-keyword)' }}>| </span>;\n        if (idx === 0) {\n          // Leading pipe for first group\n          enhanced.push(<React.Fragment key={`pipe-${idx}`}>{pipeElem}</React.Fragment>);\n        } else {\n          // Newline plus pipe for subsequent groups\n          enhanced.push(\n            <React.Fragment key={`pipe-${idx}`}>\n              <br />\n              {pipeElem}\n            </React.Fragment>,\n          );\n        }\n        enhanced.push(...group);\n      });\n      children = enhanced;\n    }\n  }\n\n  return (\n    <Code data-table-code=\"\" className={clsx('TableCode', className)} {...props}>\n      {children}\n    </Code>\n  );\n}\n"
  },
  {
    "path": "docs/src/css/README.md",
    "content": "Read the [index.css](./index.css) file for more information on the styling conventions.\n"
  },
  {
    "path": "docs/src/css/custom-media.css",
    "content": "@custom-media --xs (min-width: 32rem); /* 512px */\n@custom-media --sm (min-width: 40rem); /* 640px */\n@custom-media --md (min-width: 48rem); /* 768px */\n@custom-media --lg (min-width: 64rem); /* 1024px */\n@custom-media --xl (min-width: 80rem); /* 1280px */\n/* stylelint-disable-next-line custom-media-pattern */\n@custom-media --2xl (min-width: 96rem); /* 1536px */\n@custom-media --show-side-nav (min-width: 64rem);\n@custom-media --show-quick-nav (min-width: 84rem);\n"
  },
  {
    "path": "docs/src/css/fonts/index.css",
    "content": "@font-face {\n  font-family: 'die grotesk a';\n  font-weight: normal;\n  src: url('/fonts/die-grotesk-a-regular.woff2') format('woff2');\n}\n\n@font-face {\n  font-family: 'die grotesk a';\n  font-weight: 700;\n  src: url('/fonts/die-grotesk-a-bold.woff2') format('woff2');\n}\n\n@font-face {\n  font-family: 'die grotesk b';\n  font-weight: normal;\n  src: url('/fonts/die-grotesk-b-regular.woff2') format('woff2');\n}\n\n@font-face {\n  font-family: 'die grotesk b';\n  font-weight: 700;\n  src: url('/fonts/die-grotesk-b-bold.woff2') format('woff2');\n}\n\n@font-face {\n  font-family: 'SF Mono';\n  font-weight: normal;\n  src: local('SFMono-Regular'), local('ui-monospace');\n  /* Match 14px Unica to 13.25px SF Mono */\n  size-adjust: calc(100% * 13.25 / 14);\n}\n\n@font-face {\n  font-family: 'SF Mono';\n  font-weight: bold;\n  src: local('SFMono-Bold'), local('ui-monospace');\n  /* Match 14px Unica to 13.25px SF Mono */\n  size-adjust: calc(100% * 13.25 / 14);\n}\n\n@font-face {\n  font-family: Menlo;\n  font-weight: normal;\n  src: local('Menlo-Regular');\n  /* Match other monospace fonts to SF Mono in width */\n  size-adjust: calc(100% * 13.5 / 14);\n  ascent-override: 95%;\n}\n\n@font-face {\n  font-family: Menlo;\n  font-weight: bold;\n  src: local('Menlo-Bold');\n  /* Match other monospace fonts to SF Mono in width */\n  size-adjust: calc(100% * 13.5 / 14);\n  ascent-override: 95%;\n}\n\n@font-face {\n  font-family: Consolas;\n  font-weight: normal;\n  src: local('Consolas');\n  /* Match other monospace fonts to SF Mono in width */\n  size-adjust: calc(100% * 14.75 / 14);\n  ascent-override: 85%;\n}\n\n@font-face {\n  font-family: Consolas;\n  font-weight: bold;\n  src: local('Consolas-Bold');\n  /* Match other monospace fonts to SF Mono in width */\n  size-adjust: calc(100% * 14.75 / 14);\n  ascent-override: 85%;\n}\n\n@font-face {\n  font-family: 'DejaVu Sans Mono';\n  font-weight: normal;\n  src: local('DejaVuSansMono');\n  size-adjust: calc(100% * 13.5 / 14);\n}\n\n@font-face {\n  font-family: 'DejaVu Sans Mono';\n  font-weight: bold;\n  src: local('DejaVuSansMono-Bold');\n  size-adjust: calc(100% * 13.5 / 14);\n}\n\n@font-face {\n  font-family: Inconsolata;\n  font-weight: normal;\n  src: local('Inconsolata-Regular');\n  size-adjust: calc(100% * 15.75 / 14);\n}\n\n@font-face {\n  font-family: Inconsolata;\n  font-weight: bold;\n  src: local('Inconsolata-Bold');\n  size-adjust: calc(100% * 15.75 / 14);\n}\n"
  },
  {
    "path": "docs/src/css/index.css",
    "content": "/*\n * Main stylesheet for docs/private surfaces. Also used in e2e/regression tests.\n *\n * This app has three separate parts, each one in a separate folder:\n *   1. (docs): public documentation.\n *     - Demos live inside (docs), but they follow other rules. Read below.\n *   2. (private): mostly experiments.\n *   3. (website): home page, careers, etc.\n *\n * The three parts are styled using our custom utilities and styles (except demos, read below):\n *   - (docs) and (private) use this stylesheet.\n *   - (website) uses its own stylesheet for now, but in the future it will also use this one.\n *\n * These rules apply to all parts (except demos):\n *   - Components' CSS must be wrapped in \"@layer components\".\n *   - Use component name as the prefix to avoid conflicts, e.g. \".SelectTriggerIcon\".\n *   - Prefix CSS variables with component name too, e.g. \"--root-layout-padding-x\".\n *   - Use custom utility classes (./utilities) for layout and one-off styles.\n *   - There are two reset layers:\n *     - \"@layer all\" is a truly global reset (includes all demos).\n *     - \"@layer base\" is Tailwind's reset (`tailwindcss/preflight`) that is reverted in non-Tailwind demos.\n *     - We rely on both to build the docs.\n *\n * Demos:\n * - Do not use our custom styles or CSS vars in demos.\n * - Only the Tailwind demos in the (docs) use Tailwind (`app/(docs)/react/**\\/tailwind\\/**\\/*`).\n *   - Use only what's available in Tailwind's default theme, both classes and CSS vars.\n */\n\n/* Extend Tailwind layers to add our global reset layer `all` */\n@layer all, base, theme, components, utilities;\n\n@import 'tailwindcss/preflight' layer(base);\n@import 'tailwindcss/theme' layer(theme);\n/* `source(none)` disables automatic detection of sources to generate Tailwind classes. Use the safelist below instead. */\n@import 'tailwindcss/utilities' layer(utilities) source(none);\n\n/* ═══ Demo-specific theme ═══ */\n/* We defined grays, red, and blue in the theme used for demos. */\n/* Uses gray scale that is roughly compatible with Tailwind's grays. */\n/* This is injected in demos when opening in a sandbox like StackBlitz. */\n@import '../demo-data/theme/css-modules/theme.css' layer(theme);\n\n/* ═══ Docs-only imports ═══ */\n\n@import './fonts/index.css';\n@import './custom-media.css';\n\n/* TODO: Remove these component imports once we make these components' styles truly isolated and they import their own styles. */\n@import '../components/Code.css';\n@import '../components/Accordion.css';\n@import '../components/Table.css';\n@import '../components/TableCode.css';\n@import '../components/DescriptionList.css';\n@import '../components/ReferenceTable/ReferenceTable.css';\n\n@import '../syntax-highlighting/index.css';\n\n@import './utilities/index.css';\n\n/* ═══ Safelist to generate Tailwind classes ═══ */\n\n@source '../app/(docs)/react/**/demos/**/*.{ts,tsx}';\n@source '../app/(private)/playground/**/*.{ts,tsx}';\n\n/* Temporary sources until Tailwind CSS vars are no longer used in them */\n@source './mdx-components.css';\n@source '../src/components/**/*.css';\n@source '../app/(private)/experiments/**/*.css';\n\n/* ═══ Tailwind theme ═══ */\n/* Register demo-relevant tokens so Tailwind can generate utility classes. */\n\n@theme {\n  /* Handy link to the default Tailwind theme: https://github.com/tailwindlabs/tailwindcss/blob/next/packages/tailwindcss/theme.css */\n\n  /* Color */\n  --color-*: initial; /* Disable all Tailwind colors to only add our own */\n  --color-white: white;\n  --color-black: black;\n\n  /* Gray, red and blue colors defined in file://../demo-data/theme/css-modules/theme.css */\n  --color-gray-50: inherit;\n  --color-gray-100: inherit;\n  --color-gray-200: inherit;\n  --color-gray-300: inherit;\n  --color-gray-400: inherit;\n  --color-gray-500: inherit;\n  --color-gray-600: inherit;\n  --color-gray-700: inherit;\n  --color-gray-800: inherit;\n  --color-gray-900: inherit;\n  --color-gray-950: inherit;\n\n  --color-blue: inherit;\n  --color-red: inherit;\n\n  --color-blue-800: var(--color-blue);\n  --color-red-800: var(--color-red);\n\n  /* Typography */\n  --font-mono: 'SF Mono', 'Menlo', 'DejaVu Sans Mono', 'Consolas', 'Inconsolata', monospace;\n  --font-sans: 'Die Grotesk A', system-ui, sans-serif;\n  --font-sans-b: 'Die Grotesk B', system-ui, sans-serif;\n\n  --text-xs: 0.8125rem;\n  --text-xs--line-height: 1.25rem;\n  --text-xs--letter-spacing: 0.001em;\n\n  --text-sm: 0.875rem;\n  --text-sm--line-height: 1.25rem;\n  --text-sm--letter-spacing: 0.016em;\n\n  --text-base: 1rem;\n  --text-base--line-height: 1.5rem;\n  --text-base--letter-spacing: 0em;\n\n  --text-lg: 1.125rem;\n  --text-lg--line-height: 1.75rem;\n  --text-lg--letter-spacing: -0.0025em;\n\n  --text-xl: 1.3125rem;\n  --text-xl--line-height: 1.625rem;\n  --text-xl--letter-spacing: -0.005em;\n\n  --text-2xl: 1.5rem;\n  --text-2xl--line-height: 1.25;\n  --text-2xl--letter-spacing: -0.0125em;\n\n  --text-3xl: 1.875rem;\n  --text-3xl--line-height: 1.2;\n  --text-3xl--letter-spacing: -0.015em;\n\n  --text-4xl: 2.25rem;\n  --text-4xl--line-height: 2.5rem;\n  --text-4xl--letter-spacing: -0.015em;\n\n  --text-5xl: 3rem;\n  --text-5xl--line-height: 1;\n  --text-5xl--letter-spacing: -0.015em;\n\n  --text-6xl: 3.75rem;\n  --text-6xl--line-height: 0.95;\n  --text-6xl--letter-spacing: -0.015em;\n}\n\n/* ═══ Demo isolation ═══ */\n/* Undo `tailwindcss/preflight` in non-Tailwind demos. */\n\n@layer base {\n  [data-demo]:not([data-demo='tailwind']) * {\n    all: revert-layer;\n  }\n\n  [data-demo='tailwind'] {\n    --text-sm--letter-spacing: 0em;\n    --text-md--letter-spacing: 0em;\n  }\n}\n\n/* ═══ Docs-only theme tokens ═══ */\n\n@layer theme {\n  :root {\n    --color-gray-75: oklch(97% 0.325% 264deg);\n\n    /* Functional colors */\n    --color-content: white;\n    --color-background: var(--color-gray-50);\n    --color-foreground: var(--color-gray-900);\n    --color-popup: white;\n    --color-gridline: oklch(91.6% 1% 264deg);\n    --color-selection: oklch(80% 50% 264deg / 25%);\n    --color-highlight: var(--color-blue);\n    --color-line-highlight: oklch(80% 50% 264deg / 10%);\n    --color-line-highlight-strong: oklch(80% 50% 264deg / 25%);\n    --color-inline-highlight: oklch(80% 50% 264deg / 15%);\n\n    /* Text colors */\n    --color-gray: var(--color-gray-600);\n    --color-navy: oklch(31% 25% 264deg);\n    --color-green: oklch(46% 30% 150deg);\n    --color-purple: oklch(40% 45% 360deg);\n    --color-violet: oklch(40% 60% 300deg);\n\n    /* Typography */\n    --text-xxs: 0.75rem;\n    --text-xxs--line-height: 1.25rem;\n    --text-xxs--letter-spacing: 0.001em;\n\n    --text-md: 0.9375rem;\n    --text-md--line-height: 1.375rem;\n    --text-md--letter-spacing: 0.016em;\n\n    /* Breakpoints (use custom-media.css where possible) */\n    --breakpoint-max-layout-width: 89rem; /* 1424px - this is a 1440px device minus 16px scrollbar */\n\n    --ease-out-fast: cubic-bezier(0.45, 1.005, 0, 1.005);\n    --ease-in-slow: cubic-bezier(0.375, 0.015, 0.545, 0.455);\n    --header-height: 3rem;\n  }\n\n  /* ═══ Dark mode overrides ═══ */\n\n  @media (prefers-color-scheme: dark) {\n    :root {\n      --color-gray-75: oklch(19% 0.5% 264deg);\n\n      /* Functional colors */\n      --color-content: black;\n      --color-background: black;\n      --color-foreground: oklch(90% 2% 264deg);\n      --color-popup: var(--color-gray-50);\n      --color-gridline: oklch(24% 1% 264deg);\n      --color-highlight: oklch(45% 40% 264deg);\n      --color-selection: oklch(50% 50% 264deg / 40%);\n      --color-line-highlight: oklch(50% 50% 264deg / 20%);\n      --color-line-highlight-strong: oklch(50% 50% 264deg / 35%);\n      --color-inline-highlight: oklch(50% 50% 264deg / 35%);\n\n      /* Text colors */\n      --color-gray: var(--color-gray-600);\n      --color-green: oklch(75% 25% 150deg);\n      --color-navy: oklch(85% 25% 264deg);\n      --color-purple: oklch(85% 30% 360deg);\n      --color-violet: oklch(80% 60% 280deg);\n    }\n  }\n\n  /* ═══ Disable docs-specific variables in demos ═══ */\n  /* Keep this in sync with the :root block above. */\n\n  [data-demo] {\n    --color-gray-75: : initial;\n\n    --color-content: initial;\n    --color-background: initial;\n    --color-foreground: initial;\n    --color-popup: initial;\n    --color-gridline: initial;\n    --color-highlight: initial;\n    --color-line-highlight: initial;\n    --color-line-highlight-strong: initial;\n    --color-inline-highlight: initial;\n\n    --color-gray: initial;\n    --color-navy: initial;\n    --color-green: initial;\n    --color-purple: initial;\n    --color-violet: initial;\n\n    --text-xxs: initial;\n    --text-xxs--line-height: initial;\n    --text-xxs--letter-spacing: initial;\n\n    --text-md: initial;\n    --text-md--line-height: initial;\n    --text-md--letter-spacing: initial;\n\n    --ease-out-fast: initial;\n    --ease-in-slow: initial;\n    --header-height: initial;\n\n    --breakpoint-max-layout-width: initial;\n  }\n}\n\n/* ═══ Global rules ═══ */\n\n@layer all {\n  html {\n    word-break: break-word;\n  }\n\n  body {\n    font-synthesis: none;\n    -webkit-font-smoothing: antialiased;\n    -moz-osx-font-smoothing: grayscale;\n    /* Stable scrollbar */\n    overflow-y: scroll;\n  }\n\n  /* Debugging red text? You just forgot to set or inherit color in your demo. */\n  :where([data-demo]) {\n    color: red;\n  }\n  /* Controls usually have #000 text that needs to be explicitly overridden; only Tailwind reset handles it */\n  :where([data-demo]:not([data-demo='tailwind'])) :where(button, input, textarea) {\n    color: red;\n  }\n}\n"
  },
  {
    "path": "docs/src/css/mdx-components.css",
    "content": "@import './custom-media.css';\n\n@layer components {\n  .MdInlineCode[data-inline] {\n    margin-inline: 0.1em;\n  }\n\n  .MdH1 {\n    margin-bottom: 1rem;\n    font-family: var(--font-sans-b);\n    font-size: var(--text-3xl);\n    line-height: var(--text-3xl--line-height);\n    font-weight: 700;\n    text-wrap: balance;\n  }\n\n  .MdH2 {\n    margin-top: 2.5rem;\n    margin-bottom: 1rem;\n    scroll-margin-top: 4.5rem;\n    font-family: var(--font-sans-b);\n    font-size: var(--text-xl);\n    line-height: var(--text-xl--line-height);\n    font-weight: 700;\n    text-wrap: balance;\n  }\n\n  .MdH3 {\n    margin-top: 2rem;\n    margin-bottom: 0.375rem;\n    scroll-margin-top: 4.5rem;\n    font-family: var(--font-sans-b);\n    font-size: var(--text-lg);\n    line-height: var(--text-lg--line-height);\n    font-weight: 700;\n    text-wrap: balance;\n  }\n\n  @media (--show-side-nav) {\n    .MdH2,\n    .MdH3 {\n      scroll-margin-top: 1.5rem;\n    }\n  }\n\n  .MdH4,\n  .MdH5,\n  .MdH6 {\n    margin-top: 2rem;\n    margin-bottom: 0.375rem;\n    scroll-margin-top: 1.5rem;\n    font-weight: 700;\n    text-wrap: balance;\n  }\n\n  .MdP {\n    margin-bottom: 1rem;\n  }\n\n  .MdListItem {\n    margin-bottom: 0.125rem;\n  }\n\n  .MdListItem > p {\n    margin-bottom: 0.5rem;\n  }\n\n  .MdUl {\n    margin-bottom: 1rem;\n    margin-left: 1.125rem;\n    list-style-type: disc;\n  }\n\n  .MdOl {\n    margin-bottom: 1rem;\n    margin-left: 1.75rem;\n    list-style-type: decimal;\n  }\n\n  .MdStrong {\n    font-weight: 700;\n  }\n\n  .MdTable {\n    margin-block: 1.25rem;\n  }\n\n  .MdSubtitle {\n    margin-top: -0.5rem;\n    margin-bottom: 1.25rem;\n  }\n\n  .MdReferenceBlock {\n    margin-top: 1.25rem;\n    margin-bottom: 1.5rem;\n  }\n}\n"
  },
  {
    "path": "docs/src/css/theme-redesign.css",
    "content": "@layer theme {\n  [data-theme='redesign'] {\n    color-scheme: light dark;\n\n    /* Typeface */\n    --font-sans-a:\n      'die grotesk a', system-ui, -apple-system, BlinkMacSystemFont, 'Helvetica Neue', arial,\n      sans-serif;\n    --font-sans-b:\n      'die grotesk b', system-ui, -apple-system, BlinkMacSystemFont, 'Helvetica Neue', arial,\n      sans-serif;\n    --font-mono:\n      'Söhne Mono', 'SF Mono', Menlo, 'DejaVu Sans Mono', Consolas, Inconsolata, monospace;\n    --font-serif: Georgia, 'Times New Roman', Times, 'Noto Serif', 'DejaVu Serif', serif;\n\n    /* Font size */\n    --font-size-12: 0.75rem; /* 12px */\n    --font-size-14: 0.875rem; /* 14px */\n    --font-size-15: 0.9375rem; /* 15px */\n    --font-size-16: 1rem; /* 16px */\n    --font-size-18: 1.125rem; /* 18px */\n    --font-size-21: 1.3125rem; /* 21px */\n    --font-size-24: 1.5rem; /* 24px */\n    --font-size-36: 2.25rem; /* 36px */\n    --font-size-42: 2.625rem; /* 42px */\n\n    /* Font weight */\n    --font-weight-400: 400;\n    --font-weight-700: 700;\n\n    /* Spacing */\n    --space-4: 0.25rem; /* 4px */\n    --space-8: 0.5rem; /* 8px */\n    --space-12: 0.75rem; /* 12px */\n    --space-16: 1rem; /* 16px */\n    --space-20: 1.25rem; /* 20px */\n    --space-24: 1.5rem; /* 24px */\n    --space-28: 1.75rem; /* 28px */\n    --space-32: 2rem; /* 32px */\n    --space-40: 2.5rem; /* 40px */\n    --space-48: 3rem; /* 48px */\n\n    /* Radius */\n    --radius-2: 0.125rem; /* 2px */\n    --radius-3: 0.1875rem; /* 3px */\n    --radius-4: 0.25rem; /* 4px */\n    --radius-6: 0.375rem; /* 6px */\n    --radius-8: 0.5rem; /* 8px */\n    --radius-12: 0.75rem; /* 12px */\n    --radius-16: 1rem; /* 16px */\n    --radius-pill: 9999px;\n    --radius-circle: 50%;\n\n    /* Shadow */\n    --shadow-1:\n      0 1px 1px 0 var(--blackA-1), 0 2px 1px -1px var(--blackA-1), 0 1px 3px 0 var(--blackA-1);\n    --shadow-2:\n      0 1px 1px 0 var(--blackA-1), 0 1px 3px -1px var(--blackA-1), 0 4px 8px -2px var(--blackA-1);\n    --shadow-3:\n      0 1px 1px 0 var(--blackA-1), 0 2px 6px -1px var(--blackA-1), 0 6px 12px -3px var(--blackA-1),\n      0 8px 16px -4px var(--blackA-1);\n    --shadow-4:\n      0 1px 1px 0 var(--blackA-1), 0 2px 4px -1px var(--blackA-1), 0 4px 8px -2px var(--blackA-1),\n      0 12px 32px -6px var(--blackA-3), 0 20px 48px -10px var(--blackA-1);\n    --shadow-5:\n      0 10px 32px var(--blackA-5), 0 1px 1px var(--blackA-1), 0 4px 6px var(--blackA-2),\n      0 1px 1px var(--blackA-2), 0 24px 68px var(--blackA-4);\n\n    /* Gray */\n    --gray-s1: hsl(0deg 0% 99%);\n    --gray-s2: hsl(0deg 0% 97.5%);\n    --gray-c1: hsl(0deg 0% 95.25%);\n    --gray-c2: hsl(0deg 0% 94%);\n    --gray-c3: hsl(0deg 0% 93.1%);\n    --gray-p1: hsl(0deg 0% 55%);\n    --gray-p2: hsl(0deg 0% 51%);\n    --gray-t1: hsl(0deg 0% 46.4%);\n    --gray-t2: hsl(0deg 0% 18%);\n\n    /* Indigo */\n    --indigo-s1: hsl(204deg 100% 98.75%);\n    --indigo-s2: hsl(198deg 100% 97.5%);\n    --indigo-c1: hsl(204deg 100% 95%);\n    --indigo-c2: hsl(205deg 100% 93.5%);\n    --indigo-c3: hsl(206deg 100% 92.5%);\n    --indigo-p1: hsl(214deg 98% 50%);\n    --indigo-p2: hsl(207deg 96% 48%);\n    --indigo-t1: hsl(221deg 74% 51%);\n    --indigo-t2: hsl(221deg 74% 19.8%);\n\n    /* Alpha for shadows/borders/backdrops */\n    --blackA-1: hsl(0deg 0% 0% / 4%);\n    --blackA-2: hsl(0deg 0% 0% / 8%);\n    --blackA-3: hsl(0deg 0% 0% / 12%);\n    --blackA-4: hsl(0deg 0% 0% / 16%);\n    --blackA-5: hsl(0deg 0% 0% / 24%);\n    --blackA-6: hsl(0deg 0% 0% / 64%);\n\n    /* Syntax + accent copy */\n    --poppy-t1: hsl(10deg 86% 48%);\n    --blue-t1: hsl(211deg 100% 49%);\n    --green-t1: hsl(156deg 81% 29%);\n    --orange-t1: hsl(14deg 96% 52.5%);\n    --pink-t1: hsl(316deg 84% 51%);\n    --grape-t1: hsl(276deg 66% 54%);\n    --lime-t1: hsl(91deg 64% 38.75%);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    [data-theme='redesign'] {\n      --gray-s1: hsl(0deg 0% 7.5%);\n      --gray-s2: hsl(0deg 0% 10%);\n      --gray-c1: hsl(0deg 0% 13%);\n      --gray-c2: hsl(0deg 0% 16%);\n      --gray-c3: hsl(0deg 0% 20%);\n      --gray-p1: hsl(0deg 0% 60%);\n      --gray-p2: hsl(0deg 0% 64%);\n      --gray-t1: hsl(0deg 0% 70%);\n      --gray-t2: hsl(0deg 0% 90%);\n\n      --indigo-s1: hsl(223deg 38% 10%);\n      --indigo-s2: hsl(223deg 42% 12%);\n      --indigo-c1: hsl(222deg 50% 16%);\n      --indigo-c2: hsl(221deg 56% 20%);\n      --indigo-c3: hsl(220deg 62% 24%);\n      --indigo-p1: hsl(214deg 94% 60%);\n      --indigo-p2: hsl(207deg 96% 64%);\n      --indigo-t1: hsl(214deg 93% 66%);\n      --indigo-t2: hsl(210deg 100% 86%);\n\n      --blackA-1: transparent;\n      --blackA-2: transparent;\n      --blackA-3: transparent;\n      --blackA-4: transparent;\n      --blackA-5: transparent;\n      --blackA-6: transparent;\n\n      --poppy-t1: hsl(10deg 96% 63%);\n      --blue-t1: hsl(210deg 98% 66%);\n      --green-t1: hsl(156deg 68% 54%);\n      --orange-t1: hsl(26deg 98% 63%);\n      --pink-t1: hsl(316deg 86% 68%);\n      --grape-t1: hsl(276deg 86% 70%);\n      --lime-t1: hsl(91deg 72% 56%);\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/css/utilities/align-items.css",
    "content": "@layer utilities {\n  .bui-ai-c {\n    align-items: center;\n  }\n\n  .bui-ai-e {\n    align-items: end;\n  }\n\n  .bui-ai-s {\n    align-items: start;\n  }\n}\n"
  },
  {
    "path": "docs/src/css/utilities/box-sizing.css",
    "content": "@layer utilities {\n  .bui-bs-bb {\n    box-sizing: border-box;\n  }\n}\n"
  },
  {
    "path": "docs/src/css/utilities/display.css",
    "content": "@layer utilities {\n  .bui-d-n {\n    display: none;\n  }\n\n  .bui-d-b {\n    display: block;\n  }\n\n  .bui-d-if {\n    display: inline-flex;\n  }\n\n  .bui-d-f {\n    display: flex;\n  }\n\n  .bui-d-g {\n    display: grid;\n  }\n\n  .bui-d-c {\n    display: contents;\n  }\n\n  @media (min-width: 32rem) {\n    .bp0\\:bui-d-b {\n      display: block;\n    }\n\n    .bp0\\:bui-d-n {\n      display: none;\n    }\n  }\n\n  @media (min-width: 48rem) {\n    .bp2\\:bui-d-n {\n      display: none;\n    }\n\n    .bp2\\:bui-d-b {\n      display: block;\n    }\n\n    .bp2\\:bui-d-if {\n      display: inline-flex;\n    }\n\n    .bp2\\:bui-d-f {\n      display: flex;\n    }\n\n    .bp2\\:bui-d-g {\n      display: grid;\n    }\n\n    .bp2\\:bui-d-c {\n      display: contents;\n    }\n  }\n\n  @media (min-width: 64rem) {\n    .bp3\\:bui-d-n {\n      display: none;\n    }\n\n    .bp3\\:bui-d-b {\n      display: block;\n    }\n\n    .bp3\\:bui-d-if {\n      display: inline-flex;\n    }\n\n    .bp3\\:bui-d-f {\n      display: flex;\n    }\n\n    .bp3\\:bui-d-g {\n      display: grid;\n    }\n\n    .bp3\\:bui-d-c {\n      display: contents;\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/css/utilities/flex-direction.css",
    "content": "@layer utilities {\n  .bui-fd-c {\n    flex-direction: column;\n  }\n}\n"
  },
  {
    "path": "docs/src/css/utilities/flex-wrap.css",
    "content": "@layer utilities {\n  .bui-fw-n {\n    flex-wrap: nowrap;\n  }\n\n  .bui-fw-w {\n    flex-wrap: wrap;\n  }\n}\n"
  },
  {
    "path": "docs/src/css/utilities/font-weight.css",
    "content": "@layer utilities {\n  .bui-fw-1 {\n    font-weight: 400;\n  }\n\n  .bui-fw-2 {\n    font-weight: 700;\n  }\n}\n"
  },
  {
    "path": "docs/src/css/utilities/gap.css",
    "content": "@layer utilities {\n  .bui-g-1 {\n    gap: 0.25rem;\n  }\n\n  .bui-g-2 {\n    gap: 0.5rem;\n  }\n\n  .bui-g-4 {\n    gap: 1rem;\n  }\n\n  .bui-g-5 {\n    gap: 1.25rem;\n  }\n\n  .bui-g-6 {\n    gap: 1.5rem;\n  }\n\n  .bui-g-8 {\n    gap: 2.5rem;\n  }\n\n  .bui-g-9 {\n    gap: 3rem;\n  }\n\n  @media (min-width: 32.5rem) {\n    .bp1\\:bui-g-1 {\n      gap: 0.25rem;\n    }\n\n    .bp1\\:bui-g-2 {\n      gap: 0.5rem;\n    }\n\n    .bp1\\:bui-g-4 {\n      gap: 1rem;\n    }\n\n    .bp1\\:bui-g-5 {\n      gap: 1.25rem;\n    }\n\n    .bp1\\:bui-g-6 {\n      gap: 1.5rem;\n    }\n\n    .bp1\\:bui-g-8 {\n      gap: 2.5rem;\n    }\n\n    .bp1\\:bui-g-9 {\n      gap: 3rem;\n    }\n  }\n\n  @media (min-width: 48rem) {\n    .bp2\\:bui-g-1 {\n      gap: 0.25rem;\n    }\n\n    .bp2\\:bui-g-2 {\n      gap: 0.5rem;\n    }\n\n    .bp2\\:bui-g-4 {\n      gap: 1rem;\n    }\n\n    .bp2\\:bui-g-5 {\n      gap: 1.25rem;\n    }\n\n    .bp2\\:bui-g-6 {\n      gap: 1.5rem;\n    }\n\n    .bp2\\:bui-g-8 {\n      gap: 2.5rem;\n    }\n\n    .bp2\\:bui-g-9 {\n      gap: 3rem;\n    }\n  }\n\n  @media (min-width: 64rem) {\n    .bp3\\:bui-g-1 {\n      gap: 0.25rem;\n    }\n\n    .bp3\\:bui-g-2 {\n      gap: 0.5rem;\n    }\n\n    .bp3\\:bui-g-4 {\n      gap: 1rem;\n    }\n\n    .bp3\\:bui-g-5 {\n      gap: 1.25rem;\n    }\n\n    .bp3\\:bui-g-6 {\n      gap: 1.5rem;\n    }\n\n    .bp3\\:bui-g-8 {\n      gap: 2.5rem;\n    }\n\n    .bp3\\:bui-g-9 {\n      gap: 3rem;\n    }\n  }\n\n  @media (min-width: 80rem) {\n    .bp4\\:bui-g-1 {\n      gap: 0.25rem;\n    }\n\n    .bp4\\:bui-g-2 {\n      gap: 0.5rem;\n    }\n\n    .bp4\\:bui-g-4 {\n      gap: 1rem;\n    }\n\n    .bp4\\:bui-g-5 {\n      gap: 1.25rem;\n    }\n\n    .bp4\\:bui-g-6 {\n      gap: 1.5rem;\n    }\n\n    .bp4\\:bui-g-8 {\n      gap: 2.5rem;\n    }\n\n    .bp4\\:bui-g-9 {\n      gap: 3rem;\n    }\n  }\n\n  @media (min-width: 102.5rem) {\n    .bp5\\:bui-g-1 {\n      gap: 0.25rem;\n    }\n\n    .bp5\\:bui-g-2 {\n      gap: 0.5rem;\n    }\n\n    .bp5\\:bui-g-4 {\n      gap: 1rem;\n    }\n\n    .bp5\\:bui-g-5 {\n      gap: 1.25rem;\n    }\n\n    .bp5\\:bui-g-6 {\n      gap: 1.5rem;\n    }\n\n    .bp5\\:bui-g-8 {\n      gap: 2.5rem;\n    }\n\n    .bp5\\:bui-g-9 {\n      gap: 3rem;\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/css/utilities/grid-auto-rows.css",
    "content": "@layer utilities {\n  .bui-gar-8 {\n    grid-auto-rows: var(--space-8);\n  }\n}\n"
  },
  {
    "path": "docs/src/css/utilities/grid-column-end.css",
    "content": "@layer utilities {\n  .bui-gce-1 {\n    grid-column-end: 1;\n  }\n\n  .bui-gce-2 {\n    grid-column-end: 2;\n  }\n\n  .bui-gce-3 {\n    grid-column-end: 3;\n  }\n\n  .bui-gce-4 {\n    grid-column-end: 4;\n  }\n\n  .bui-gce-5 {\n    grid-column-end: 5;\n  }\n\n  .bui-gce-6 {\n    grid-column-end: 6;\n  }\n\n  .bui-gce-7 {\n    grid-column-end: 7;\n  }\n\n  .bui-gce-8 {\n    grid-column-end: 8;\n  }\n\n  .bui-gce-9 {\n    grid-column-end: 9;\n  }\n\n  @media (min-width: 32.5rem) {\n    .bp1\\:bui-gce-1 {\n      grid-column-end: 1;\n    }\n\n    .bp1\\:bui-gce-2 {\n      grid-column-end: 2;\n    }\n\n    .bp1\\:bui-gce-3 {\n      grid-column-end: 3;\n    }\n\n    .bp1\\:bui-gce-4 {\n      grid-column-end: 4;\n    }\n\n    .bp1\\:bui-gce-5 {\n      grid-column-end: 5;\n    }\n\n    .bp1\\:bui-gce-6 {\n      grid-column-end: 6;\n    }\n\n    .bp1\\:bui-gce-7 {\n      grid-column-end: 7;\n    }\n\n    .bp1\\:bui-gce-8 {\n      grid-column-end: 8;\n    }\n\n    .bp1\\:bui-gce-9 {\n      grid-column-end: 9;\n    }\n  }\n\n  @media (min-width: 48rem) {\n    .bp2\\:bui-gce-1 {\n      grid-column-end: 1;\n    }\n\n    .bp2\\:bui-gce-2 {\n      grid-column-end: 2;\n    }\n\n    .bp2\\:bui-gce-3 {\n      grid-column-end: 3;\n    }\n\n    .bp2\\:bui-gce-4 {\n      grid-column-end: 4;\n    }\n\n    .bp2\\:bui-gce-5 {\n      grid-column-end: 5;\n    }\n\n    .bp2\\:bui-gce-6 {\n      grid-column-end: 6;\n    }\n\n    .bp2\\:bui-gce-7 {\n      grid-column-end: 7;\n    }\n\n    .bp2\\:bui-gce-8 {\n      grid-column-end: 8;\n    }\n\n    .bp2\\:bui-gce-9 {\n      grid-column-end: 9;\n    }\n  }\n\n  @media (min-width: 64rem) {\n    .bp3\\:bui-gce-1 {\n      grid-column-end: 1;\n    }\n\n    .bp3\\:bui-gce-2 {\n      grid-column-end: 2;\n    }\n\n    .bp3\\:bui-gce-3 {\n      grid-column-end: 3;\n    }\n\n    .bp3\\:bui-gce-4 {\n      grid-column-end: 4;\n    }\n\n    .bp3\\:bui-gce-5 {\n      grid-column-end: 5;\n    }\n\n    .bp3\\:bui-gce-6 {\n      grid-column-end: 6;\n    }\n\n    .bp3\\:bui-gce-7 {\n      grid-column-end: 7;\n    }\n\n    .bp3\\:bui-gce-8 {\n      grid-column-end: 8;\n    }\n\n    .bp3\\:bui-gce-9 {\n      grid-column-end: 9;\n    }\n  }\n\n  @media (min-width: 80rem) {\n    .bp4\\:bui-gce-1 {\n      grid-column-end: 1;\n    }\n\n    .bp4\\:bui-gce-2 {\n      grid-column-end: 2;\n    }\n\n    .bp4\\:bui-gce-3 {\n      grid-column-end: 3;\n    }\n\n    .bp4\\:bui-gce-4 {\n      grid-column-end: 4;\n    }\n\n    .bp4\\:bui-gce-5 {\n      grid-column-end: 5;\n    }\n\n    .bp4\\:bui-gce-6 {\n      grid-column-end: 6;\n    }\n\n    .bp4\\:bui-gce-7 {\n      grid-column-end: 7;\n    }\n\n    .bp4\\:bui-gce-8 {\n      grid-column-end: 8;\n    }\n\n    .bp4\\:bui-gce-9 {\n      grid-column-end: 9;\n    }\n  }\n\n  @media (min-width: 102.5rem) {\n    .bp5\\:bui-gce-1 {\n      grid-column-end: 1;\n    }\n\n    .bp5\\:bui-gce-2 {\n      grid-column-end: 2;\n    }\n\n    .bp5\\:bui-gce-3 {\n      grid-column-end: 3;\n    }\n\n    .bp5\\:bui-gce-4 {\n      grid-column-end: 4;\n    }\n\n    .bp5\\:bui-gce-5 {\n      grid-column-end: 5;\n    }\n\n    .bp5\\:bui-gce-6 {\n      grid-column-end: 6;\n    }\n\n    .bp5\\:bui-gce-7 {\n      grid-column-end: 7;\n    }\n\n    .bp5\\:bui-gce-8 {\n      grid-column-end: 8;\n    }\n\n    .bp5\\:bui-gce-9 {\n      grid-column-end: 9;\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/css/utilities/grid-column-start.css",
    "content": "@layer utilities {\n  .bui-gcs-1 {\n    grid-column-start: 1;\n  }\n\n  .bui-gcs-2 {\n    grid-column-start: 2;\n  }\n\n  .bui-gcs-3 {\n    grid-column-start: 3;\n  }\n\n  .bui-gcs-4 {\n    grid-column-start: 4;\n  }\n\n  .bui-gcs-5 {\n    grid-column-start: 5;\n  }\n\n  .bui-gcs-6 {\n    grid-column-start: 6;\n  }\n\n  .bui-gcs-7 {\n    grid-column-start: 7;\n  }\n\n  .bui-gcs-8 {\n    grid-column-start: 8;\n  }\n\n  .bui-gcs-9 {\n    grid-column-start: 9;\n  }\n\n  @media (min-width: 32.5rem) {\n    .bp1\\:bui-gcs-1 {\n      grid-column-start: 1;\n    }\n\n    .bp1\\:bui-gcs-2 {\n      grid-column-start: 2;\n    }\n\n    .bp1\\:bui-gcs-3 {\n      grid-column-start: 3;\n    }\n\n    .bp1\\:bui-gcs-4 {\n      grid-column-start: 4;\n    }\n\n    .bp1\\:bui-gcs-5 {\n      grid-column-start: 5;\n    }\n\n    .bp1\\:bui-gcs-6 {\n      grid-column-start: 6;\n    }\n\n    .bp1\\:bui-gcs-7 {\n      grid-column-start: 7;\n    }\n\n    .bp1\\:bui-gcs-8 {\n      grid-column-start: 8;\n    }\n\n    .bp1\\:bui-gcs-9 {\n      grid-column-start: 9;\n    }\n  }\n\n  @media (min-width: 48rem) {\n    .bp2\\:bui-gcs-1 {\n      grid-column-start: 1;\n    }\n\n    .bp2\\:bui-gcs-2 {\n      grid-column-start: 2;\n    }\n\n    .bp2\\:bui-gcs-3 {\n      grid-column-start: 3;\n    }\n\n    .bp2\\:bui-gcs-4 {\n      grid-column-start: 4;\n    }\n\n    .bp2\\:bui-gcs-5 {\n      grid-column-start: 5;\n    }\n\n    .bp2\\:bui-gcs-6 {\n      grid-column-start: 6;\n    }\n\n    .bp2\\:bui-gcs-7 {\n      grid-column-start: 7;\n    }\n\n    .bp2\\:bui-gcs-8 {\n      grid-column-start: 8;\n    }\n\n    .bp2\\:bui-gcs-9 {\n      grid-column-start: 9;\n    }\n  }\n\n  @media (min-width: 64rem) {\n    .bp3\\:bui-gcs-1 {\n      grid-column-start: 1;\n    }\n\n    .bp3\\:bui-gcs-2 {\n      grid-column-start: 2;\n    }\n\n    .bp3\\:bui-gcs-3 {\n      grid-column-start: 3;\n    }\n\n    .bp3\\:bui-gcs-4 {\n      grid-column-start: 4;\n    }\n\n    .bp3\\:bui-gcs-5 {\n      grid-column-start: 5;\n    }\n\n    .bp3\\:bui-gcs-6 {\n      grid-column-start: 6;\n    }\n\n    .bp3\\:bui-gcs-7 {\n      grid-column-start: 7;\n    }\n\n    .bp3\\:bui-gcs-8 {\n      grid-column-start: 8;\n    }\n\n    .bp3\\:bui-gcs-9 {\n      grid-column-start: 9;\n    }\n  }\n\n  @media (min-width: 80rem) {\n    .bp4\\:bui-gcs-1 {\n      grid-column-start: 1;\n    }\n\n    .bp4\\:bui-gcs-2 {\n      grid-column-start: 2;\n    }\n\n    .bp4\\:bui-gcs-3 {\n      grid-column-start: 3;\n    }\n\n    .bp4\\:bui-gcs-4 {\n      grid-column-start: 4;\n    }\n\n    .bp4\\:bui-gcs-5 {\n      grid-column-start: 5;\n    }\n\n    .bp4\\:bui-gcs-6 {\n      grid-column-start: 6;\n    }\n\n    .bp4\\:bui-gcs-7 {\n      grid-column-start: 7;\n    }\n\n    .bp4\\:bui-gcs-8 {\n      grid-column-start: 8;\n    }\n\n    .bp4\\:bui-gcs-9 {\n      grid-column-start: 9;\n    }\n  }\n\n  @media (min-width: 102.5rem) {\n    .bp5\\:bui-gcs-1 {\n      grid-column-start: 1;\n    }\n\n    .bp5\\:bui-gcs-2 {\n      grid-column-start: 2;\n    }\n\n    .bp5\\:bui-gcs-3 {\n      grid-column-start: 3;\n    }\n\n    .bp5\\:bui-gcs-4 {\n      grid-column-start: 4;\n    }\n\n    .bp5\\:bui-gcs-5 {\n      grid-column-start: 5;\n    }\n\n    .bp5\\:bui-gcs-6 {\n      grid-column-start: 6;\n    }\n\n    .bp5\\:bui-gcs-7 {\n      grid-column-start: 7;\n    }\n\n    .bp5\\:bui-gcs-8 {\n      grid-column-start: 8;\n    }\n\n    .bp5\\:bui-gcs-9 {\n      grid-column-start: 9;\n    }\n  }\n\n  @media (min-width: 48rem) {\n    .bp2\\:bui-gcs-3 {\n      grid-column-start: 3;\n    }\n\n    .bp2\\:bui-gcs-5 {\n      grid-column-start: 5;\n    }\n  }\n\n  @media (min-width: 64rem) {\n    .bp3\\:bui-gcs-3 {\n      grid-column-start: 3;\n    }\n\n    .bp3\\:bui-gcs-5 {\n      grid-column-start: 5;\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/css/utilities/grid-template-columns.css",
    "content": "@layer utilities {\n  .bui-gtc-2 {\n    grid-template-columns: repeat(2, 1fr);\n  }\n\n  .bui-gtc-4 {\n    grid-template-columns: repeat(4, 1fr);\n  }\n\n  .bui-gtc-6 {\n    grid-template-columns: repeat(6, 1fr);\n  }\n\n  .bui-gtc-8 {\n    grid-template-columns: repeat(8, 1fr);\n  }\n\n  @media (min-width: 32.5rem) {\n    .bp1\\:bui-gtc-2 {\n      grid-template-columns: repeat(2, 1fr);\n    }\n\n    .bp1\\:bui-gtc-4 {\n      grid-template-columns: repeat(4, 1fr);\n    }\n\n    .bp1\\:bui-gtc-6 {\n      grid-template-columns: repeat(6, 1fr);\n    }\n\n    .bp1\\:bui-gtc-8 {\n      grid-template-columns: repeat(8, 1fr);\n    }\n  }\n\n  @media (min-width: 48rem) {\n    .bp2\\:bui-gtc-2 {\n      grid-template-columns: repeat(2, 1fr);\n    }\n\n    .bp2\\:bui-gtc-4 {\n      grid-template-columns: repeat(4, 1fr);\n    }\n\n    .bp2\\:bui-gtc-6 {\n      grid-template-columns: repeat(6, 1fr);\n    }\n\n    .bp2\\:bui-gtc-8 {\n      grid-template-columns: repeat(8, 1fr);\n    }\n  }\n\n  @media (min-width: 64rem) {\n    .bp3\\:bui-gtc-2 {\n      grid-template-columns: repeat(2, 1fr);\n    }\n\n    .bp3\\:bui-gtc-4 {\n      grid-template-columns: repeat(4, 1fr);\n    }\n\n    .bp3\\:bui-gtc-6 {\n      grid-template-columns: repeat(6, 1fr);\n    }\n\n    .bp3\\:bui-gtc-8 {\n      grid-template-columns: repeat(8, 1fr);\n    }\n  }\n\n  @media (min-width: 80rem) {\n    .bp4\\:bui-gtc-2 {\n      grid-template-columns: repeat(2, 1fr);\n    }\n\n    .bp4\\:bui-gtc-4 {\n      grid-template-columns: repeat(4, 1fr);\n    }\n\n    .bp4\\:bui-gtc-6 {\n      grid-template-columns: repeat(6, 1fr);\n    }\n\n    .bp4\\:bui-gtc-8 {\n      grid-template-columns: repeat(8, 1fr);\n    }\n  }\n\n  @media (min-width: 102.5rem) {\n    .bp5\\:bui-gtc-2 {\n      grid-template-columns: repeat(2, 1fr);\n    }\n\n    .bp5\\:bui-gtc-4 {\n      grid-template-columns: repeat(4, 1fr);\n    }\n\n    .bp5\\:bui-gtc-6 {\n      grid-template-columns: repeat(6, 1fr);\n    }\n\n    .bp5\\:bui-gtc-8 {\n      grid-template-columns: repeat(8, 1fr);\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/css/utilities/height.css",
    "content": "@layer utilities {\n  .bui-h-100 {\n    height: 100%;\n  }\n}\n"
  },
  {
    "path": "docs/src/css/utilities/index.css",
    "content": "@import './align-items.css';\n@import './box-sizing.css';\n@import './display.css';\n@import './flex-direction.css';\n@import './flex-wrap.css';\n@import './font-weight.css';\n@import './gap.css';\n@import './grid-auto-rows.css';\n@import './grid-column-end.css';\n@import './grid-column-start.css';\n@import './grid-template-columns.css';\n@import './height.css';\n@import './justify-content.css';\n@import './left.css';\n@import './margin.css';\n@import './outline.css';\n@import './padding.css';\n@import './position.css';\n@import './scroll-margin-top.css';\n@import './text-align.css';\n@import './top.css';\n@import './visibility.css';\n@import './width.css';\n@import './white-space.css';\n"
  },
  {
    "path": "docs/src/css/utilities/justify-content.css",
    "content": "@layer utilities {\n  .bui-jc-c {\n    justify-content: center;\n  }\n\n  .bui-jc-fs {\n    justify-content: flex-start;\n  }\n\n  .bui-jc-fe {\n    justify-content: flex-end;\n  }\n\n  .bui-jc-sb {\n    justify-content: space-between;\n  }\n}\n"
  },
  {
    "path": "docs/src/css/utilities/left.css",
    "content": "@layer utilities {\n  .bui-l-50 {\n    left: 50%;\n  }\n}\n"
  },
  {
    "path": "docs/src/css/utilities/margin.css",
    "content": "@layer utilities {\n  .bui-ml--0\\.5 {\n    margin-left: -0.125rem;\n  }\n\n  .bui-ml-a {\n    margin-left: auto;\n  }\n\n  .bui-mr-1 {\n    margin-right: 0.25rem;\n  }\n\n  .bui-my-4 {\n    margin-block: 1rem;\n  }\n}\n"
  },
  {
    "path": "docs/src/css/utilities/outline.css",
    "content": "@layer utilities {\n  .bui-ol-n {\n    outline: none;\n  }\n}\n"
  },
  {
    "path": "docs/src/css/utilities/padding.css",
    "content": "@layer utilities {\n  .bui-p-1 {\n    padding: 0.25rem;\n  }\n\n  .bui-p-4 {\n    padding: 1rem;\n  }\n\n  .bui-p-5 {\n    padding: 1.25rem;\n  }\n\n  .bui-p-6 {\n    padding: 1.5rem;\n  }\n\n  .bui-py-6 {\n    padding-top: 1.5rem;\n    padding-bottom: 1.5rem;\n  }\n\n  .bui-px-6 {\n    padding-left: 1.5rem;\n    padding-right: 1.5rem;\n  }\n\n  .bui-py-7 {\n    padding-top: 2rem;\n    padding-bottom: 2rem;\n  }\n\n  .bui-px-9 {\n    padding-left: 3rem;\n    padding-right: 3rem;\n  }\n\n  .bui-pl-3 {\n    padding-left: 0.75rem;\n  }\n\n  @media (min-width: 32.5rem) {\n    .bp1\\:bui-p-1 {\n      padding: 0.25rem;\n    }\n\n    .bp1\\:bui-p-4 {\n      padding: 1rem;\n    }\n\n    .bp1\\:bui-p-5 {\n      padding: 1.25rem;\n    }\n\n    .bp1\\:bui-py-6 {\n      padding-top: 1.5rem;\n      padding-bottom: 1.5rem;\n    }\n\n    .bp1\\:bui-px-6 {\n      padding-left: 1.5rem;\n      padding-right: 1.5rem;\n    }\n\n    .bp1\\:bui-py-7 {\n      padding-top: 2rem;\n      padding-bottom: 2rem;\n    }\n\n    .bp1\\:bui-px-9 {\n      padding-left: 3rem;\n      padding-right: 3rem;\n    }\n  }\n\n  @media (min-width: 48rem) {\n    .bp2\\:bui-p-1 {\n      padding: 0.25rem;\n    }\n\n    .bp2\\:bui-p-4 {\n      padding: 1rem;\n    }\n\n    .bp2\\:bui-p-5 {\n      padding: 1.25rem;\n    }\n\n    .bp2\\:bui-py-6 {\n      padding-top: 1.5rem;\n      padding-bottom: 1.5rem;\n    }\n\n    .bp2\\:bui-px-6 {\n      padding-left: 1.5rem;\n      padding-right: 1.5rem;\n    }\n\n    .bp2\\:bui-py-7 {\n      padding-top: 2rem;\n      padding-bottom: 2rem;\n    }\n\n    .bp2\\:bui-px-9 {\n      padding-left: 3rem;\n      padding-right: 3rem;\n    }\n  }\n\n  @media (min-width: 64rem) {\n    .bp3\\:bui-p-1 {\n      padding: 0.25rem;\n    }\n\n    .bp3\\:bui-p-4 {\n      padding: 1rem;\n    }\n\n    .bp3\\:bui-p-5 {\n      padding: 1.25rem;\n    }\n\n    .bp3\\:bui-py-6 {\n      padding-top: 1.5rem;\n      padding-bottom: 1.5rem;\n    }\n\n    .bp3\\:bui-px-6 {\n      padding-left: 1.5rem;\n      padding-right: 1.5rem;\n    }\n\n    .bp3\\:bui-py-7 {\n      padding-top: 2rem;\n      padding-bottom: 2rem;\n    }\n\n    .bp3\\:bui-px-9 {\n      padding-left: 3rem;\n      padding-right: 3rem;\n    }\n  }\n\n  @media (min-width: 80rem) {\n    .bp4\\:bui-p-1 {\n      padding: 0.25rem;\n    }\n\n    .bp4\\:bui-p-4 {\n      padding: 1rem;\n    }\n\n    .bp4\\:bui-p-5 {\n      padding: 1.25rem;\n    }\n\n    .bp4\\:bui-py-6 {\n      padding-top: 1.5rem;\n      padding-bottom: 1.5rem;\n    }\n\n    .bp4\\:bui-px-6 {\n      padding-left: 1.5rem;\n      padding-right: 1.5rem;\n    }\n\n    .bp4\\:bui-py-7 {\n      padding-top: 2rem;\n      padding-bottom: 2rem;\n    }\n\n    .bp4\\:bui-px-9 {\n      padding-left: 3rem;\n      padding-right: 3rem;\n    }\n  }\n\n  @media (min-width: 102.5rem) {\n    .bp5\\:bui-p-1 {\n      padding: 0.25rem;\n    }\n\n    .bp5\\:bui-p-4 {\n      padding: 1rem;\n    }\n\n    .bp5\\:bui-p-5 {\n      padding: 1.25rem;\n    }\n\n    .bp5\\:bui-py-6 {\n      padding-top: 1.5rem;\n      padding-bottom: 1.5rem;\n    }\n\n    .bp5\\:bui-px-6 {\n      padding-left: 1.5rem;\n      padding-right: 1.5rem;\n    }\n\n    .bp5\\:bui-py-7 {\n      padding-top: 2rem;\n      padding-bottom: 2rem;\n    }\n\n    .bp5\\:bui-px-9 {\n      padding-left: 3rem;\n      padding-right: 3rem;\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/css/utilities/position.css",
    "content": "@layer utilities {\n  .bui-p-a {\n    position: absolute;\n  }\n}\n"
  },
  {
    "path": "docs/src/css/utilities/scroll-margin-top.css",
    "content": "@layer utilities {\n  .bui-scroll-mt-4 {\n    scroll-margin-top: 1rem;\n  }\n}\n"
  },
  {
    "path": "docs/src/css/utilities/text-align.css",
    "content": "@layer utilities {\n  .bui-ta-l {\n    text-align: left;\n  }\n\n  .bui-ta-c {\n    text-align: center;\n  }\n\n  .bui-ta-r {\n    text-align: right;\n  }\n}\n"
  },
  {
    "path": "docs/src/css/utilities/top.css",
    "content": "@layer utilities {\n  .bui-t-8 {\n    top: 2.5rem;\n  }\n}\n"
  },
  {
    "path": "docs/src/css/utilities/visibility.css",
    "content": "@layer utilities {\n  .bui-v-h {\n    visibility: hidden;\n  }\n}\n"
  },
  {
    "path": "docs/src/css/utilities/white-space.css",
    "content": "@layer utilities {\n  .bui-ws-nw {\n    white-space: nowrap;\n  }\n}\n"
  },
  {
    "path": "docs/src/css/utilities/width.css",
    "content": "@layer utilities {\n  .bui-w-10 {\n    width: 2.5rem;\n  }\n}\n"
  },
  {
    "path": "docs/src/data/releases.ts",
    "content": "export interface Release {\n  version: string;\n  versionSlug: string;\n  date: string;\n  highlights: string[];\n  latest?: true;\n}\n\nexport const releases: Release[] = [\n  {\n    latest: true,\n    version: 'v1.3.0',\n    versionSlug: 'v1-3-0',\n    date: '2026-03-12',\n    highlights: [\n      '`Drawer` is now stable.',\n      '`Menu` now supports content transitions with `Viewport`.',\n      'New `Label` parts for `Select`, `Combobox`, and `Slider`.',\n      'New `SwipeArea` part for `Drawer`.',\n      'New `InputGroup` parts for `Combobox` and `Autocomplete`.',\n      'New `closeOnClick` prop for `Tooltip`.',\n      'Many accessibility, performance, and bug fixes.',\n    ],\n  },\n  {\n    version: 'v1.2.0',\n    versionSlug: 'v1-2-0',\n    date: '2026-02-12',\n    highlights: [\n      'New `Drawer` component (preview).',\n      'New `useFilteredItems` hook for `Autocomplete` and `Combobox`.',\n      'Support lazy element in `render`.',\n      'Tons of `Combobox` and `Autocomplete` improvements.',\n      'Support `keepMounted` on `NavigationMenu`.',\n      'Support `finalFocus` on `Select`.',\n    ],\n  },\n  {\n    version: 'v1.1.0',\n    versionSlug: 'v1-1-0',\n    date: '2026-01-15',\n    highlights: [\n      '`loopFocus` support for `Autocomplete` + `Combobox`.',\n      'New state attributes for `Autocomplete` + `Combobox`.',\n      'New `placeholder` prop for `Combobox` + `Select`.',\n      'New `CSPProvider` for configuring CSP behaviour.',\n      'Many a11y and bug fixes.',\n    ],\n  },\n  {\n    version: 'v1.0.0',\n    versionSlug: 'v1-0-0',\n    date: '2025-12-11',\n    highlights: [\n      'Stable 🎉',\n      '35 unstyled UI components.',\n      'New `@base-ui/react` npm package.',\n      'New website.',\n      'Fixed focus and transition issues across multiple components.',\n      'Improved accessibility and form submission handling.',\n    ],\n  },\n  {\n    version: 'v1.0.0-rc.2',\n    versionSlug: 'v1-0-0-rc-2',\n    date: '2025-12-11',\n    highlights: ['Same code as v1.0.0.'],\n  },\n  {\n    version: 'v1.0.0-rc.1',\n    versionSlug: 'v1-0-0-rc-1',\n    date: '2025-12-11',\n    highlights: ['Same code as v1.0.0.'],\n  },\n  {\n    version: 'v1.0.0-rc.0',\n    versionSlug: 'v1-0-0-rc-0',\n    date: '2025-12-04',\n    highlights: [\n      \"Fixed missing `'use client'` directives.\",\n      'Breaking change: Match native unchecked state in `Checkbox` and `Switch`.',\n      'Breaking change: Fixed `Panel` `keepMounted` behavior in `Tabs`.',\n      'Breaking change: Removed the `keepHighlight` prop from `Combobox`.',\n      'New `highlightItemOnHover` prop for `Menu` and `Select`.',\n      'Improved `NumberField` parsing and validation.',\n      'Fixed `Dialog` and `Popover` closing behavior.',\n    ],\n  },\n  {\n    version: 'v1.0.0-beta.7',\n    versionSlug: 'v1-0-0-beta-7',\n    date: '2025-11-27',\n    highlights: [\n      'Fixed error about `props.ref` access in React <=18.',\n      'Improved performance when detached triggers are used.',\n      'Fixed iOS VoiceOver voice control accessibility.',\n      'Improved popups anchoring and auto-focus behavior.',\n    ],\n  },\n  {\n    version: 'v1.0.0-beta.6',\n    versionSlug: 'v1-0-0-beta-6',\n    date: '2025-11-17',\n    highlights: [\n      'Hotfix for `AlertDialog`, `Dialog`, `Menu`, `Popover`, and `Tooltip` in React Server Components.',\n      'Fixed refs types in `Checkbox`, `Switch` and `Radio` components.',\n    ],\n  },\n  {\n    version: 'v1.0.0-beta.5',\n    versionSlug: 'v1-0-0-beta-5',\n    date: '2025-11-17',\n    highlights: [\n      'New `Button` component.',\n      'Detachable triggers for popup components.',\n      'Improved scrollbar support for popups.',\n      'Huge `Autocomplete` + `Combobox` improvements.',\n      'Many a11y and bug fixes.',\n    ],\n  },\n  {\n    version: 'v1.0.0-beta.4',\n    versionSlug: 'v1-0-0-beta-4',\n    date: '2025-10-01',\n    highlights: [\n      'New `autoHighlight` prop on `Combobox`.',\n      '`openMultiple` + `toggleMultiple` renamed to `multiple`.',\n      'New `Select.List` component.',\n      'New `thumbAlignment` prop on `Slider`.',\n      'Support for variable height `Toast`.',\n      'Many a11y and bug fixes.',\n    ],\n  },\n  {\n    version: 'v1.0.0-beta.3',\n    versionSlug: 'v1-0-0-beta-3',\n    date: '2025-09-03',\n    highlights: [\n      'New `Combobox` + `Autocomplete` components.',\n      '`initialFocus` + `finalFocus` now accept functions.',\n      '`useRender` hook enhancements.',\n      'Improved SSR support.',\n      'Many a11y and bug fixes.',\n    ],\n  },\n  {\n    version: 'v1.0.0-beta.2',\n    versionSlug: 'v1-0-0-beta-2',\n    date: '2025-07-30',\n    highlights: [\n      'New `multiple` prop on `Select` to create a multi-select.',\n      'New `llms.txt` and markdown links for AI.',\n    ],\n  },\n  {\n    version: 'v1.0.0-beta.1',\n    versionSlug: 'v1-0-0-beta-1',\n    date: '2025-07-01',\n    highlights: [\n      'New `SubmenuRoot` part for menus.',\n      'Fixes for `Accordion` + `Collapsible` resizing.',\n      'Perf enhancements for `Select`.',\n      'Many small fixes for menus.',\n      '`useRender` now RSC compatible.',\n      'Many a11y and bug fixes.',\n    ],\n  },\n  {\n    version: 'v1.0.0-beta.0',\n    versionSlug: 'v1-0-0-beta-0',\n    date: '2025-05-29',\n    highlights: [\n      'New `Menubar` component.',\n      'New `NavigationMenu` component.',\n      'New `ContextMenu` component.',\n      'Improved performance.',\n      'Many a11y and bug fixes.',\n    ],\n  },\n  {\n    version: 'v1.0.0-alpha.8',\n    versionSlug: 'v1-0-0-alpha-8',\n    date: '2025-04-17',\n    highlights: [\n      'New `Toast` component.',\n      'New `Meter` component.',\n      'Composable popup modality.',\n      'Configurable `NumberField` snapping.',\n      'New `Content` part for `ScrollArea`.',\n      'New `Label` part for `Progress`.',\n      'Many a11y and bug fixes.',\n    ],\n  },\n  {\n    version: 'v1.0.0-alpha.7',\n    versionSlug: 'v1-0-0-alpha-7',\n    date: '2025-03-20',\n    highlights: [\n      'New `Toolbar` component.',\n      'New `useRender` hook.',\n      'New `modal` prop on `Popover`.',\n      'New `actionsRef` prop on popups.',\n      'New `locale` prop on `NumberField`.',\n    ],\n  },\n  {\n    version: 'v1.0.0-alpha.6',\n    versionSlug: 'v1-0-0-alpha-6',\n    date: '2025-02-06',\n    highlights: [\n      'New `Avatar` component.',\n      'New `filled` and `focused` style hooks for `Field`.',\n      'New `Value` part for `Progress`.',\n      'Support submenus when `openOnHover` is present.',\n      'Many a11y and bug fixes.',\n    ],\n  },\n  {\n    version: 'v1.0.0-alpha.5',\n    versionSlug: 'v1-0-0-alpha-5',\n    date: '2025-01-10',\n    highlights: [\n      'New `Portal` part for popup components.',\n      'Improved modality of popup components.',\n      'Fixed `openOnHover` issues for popup components.',\n      'Fixed Enter key bug when rendering menuitem as `<a>`.',\n      'Many a11y and bug fixes.',\n    ],\n  },\n  {\n    version: 'v1.0.0-alpha.4',\n    versionSlug: 'v1-0-0-alpha-4',\n    date: '2024-12-17',\n    highlights: [\n      '25 accessible UI components.',\n      'Unstyled. Compatible with any styling engine.',\n      'Fully composable with an open API.',\n    ],\n  },\n];\n"
  },
  {
    "path": "docs/src/demo-data/theme/css-modules/index.ts",
    "content": "import './theme.css';\n\nexport function DemoThemeProvider({ children }: { children: React.ReactNode }) {\n  return children;\n}\n"
  },
  {
    "path": "docs/src/demo-data/theme/css-modules/theme.css",
    "content": ":root {\n  --color-blue: oklch(45% 50% 264deg);\n  --color-red: oklch(50% 55% 31deg);\n\n  --color-gray-50: oklch(98% 0.25% 264deg);\n  --color-gray-100: oklch(12% 9.5% 264deg / 5%);\n  --color-gray-200: oklch(12% 9% 264deg / 7%);\n  --color-gray-300: oklch(12% 8.5% 264deg / 17%);\n  --color-gray-400: oklch(12% 8% 264deg / 38%);\n  --color-gray-500: oklch(12% 7.5% 264deg / 50%);\n  --color-gray-600: oklch(12% 7% 264deg / 67%);\n  --color-gray-700: oklch(12% 6% 264deg / 77%);\n  --color-gray-800: oklch(12% 5% 264deg / 85%);\n  --color-gray-900: oklch(12% 5% 264deg / 90%);\n  --color-gray-950: oklch(12% 5% 264deg / 95%);\n\n  @media (prefers-color-scheme: dark) {\n    --color-blue: oklch(69% 50% 264deg);\n    --color-red: oklch(80% 55% 31deg);\n\n    --color-gray-50: oklch(17% 0.25% 264deg);\n    --color-gray-100: oklch(28% 0.75% 264deg / 65%);\n    --color-gray-200: oklch(29% 0.75% 264deg / 80%);\n    --color-gray-300: oklch(35% 0.75% 264deg / 80%);\n    --color-gray-400: oklch(47% 0.875% 264deg / 80%);\n    --color-gray-500: oklch(64% 1% 264deg / 80%);\n    --color-gray-600: oklch(82% 1% 264deg / 80%);\n    --color-gray-700: oklch(92% 1.125% 264deg / 80%);\n    --color-gray-800: oklch(93% 0.875% 264deg / 85%);\n    --color-gray-900: oklch(95% 0.5% 264deg / 90%);\n    --color-gray-950: oklch(94% 0.375% 264deg / 95%);\n  }\n}\n"
  },
  {
    "path": "docs/src/demo-data/theme/index.ts",
    "content": "import { createDemoGlobalWithVariants } from '@mui/internal-docs-infra/createDemoData';\nimport type { DemoGlobalData } from '@mui/internal-docs-infra/createDemoData/types';\nimport { DemoThemeProvider as CssModules } from './css-modules';\n\nexport const DemoDataTheme: DemoGlobalData = createDemoGlobalWithVariants(import.meta.url, {\n  CssModules,\n});\n"
  },
  {
    "path": "docs/src/error-codes.json",
    "content": "{\n  \"1\": \"Unsupported number of selectors\",\n  \"2\": \"Unsupported number of arguments\",\n  \"3\": \"Base UI: CheckboxGroupContext is missing. CheckboxGroup parts must be placed within <CheckboxGroup>.\",\n  \"4\": \"Base UI: DirectionContext is missing.\",\n  \"5\": \"Base UI: MenubarContext is missing. Menubar parts must be placed within <Menubar>.\",\n  \"7\": \"Base UI: ToggleGroupContext is missing. ToggleGroup parts must be placed within <ToggleGroup>.\",\n  \"8\": \"Base UI: Render element or function are not defined.\",\n  \"9\": \"Base UI: AccordionItemContext is missing. Accordion parts must be placed within <Accordion.Item>.\",\n  \"10\": \"Base UI: AccordionRootContext is missing. Accordion parts must be placed within <Accordion.Root>.\",\n  \"11\": \"Base UI: <AlertDialog.Portal> is missing.\",\n  \"12\": \"Base UI: AlertDialogRootContext is missing. AlertDialog parts must be placed within <AlertDialog.Root>.\",\n  \"13\": \"Base UI: AvatarRootContext is missing. Avatar parts must be placed within <Avatar.Root>.\",\n  \"14\": \"Base UI: CheckboxRootContext is missing. Checkbox parts must be placed within <Checkbox.Root>.\",\n  \"15\": \"Base UI: CollapsibleRootContext is missing. Collapsible parts must be placed within <Collapsible.Root>.\",\n  \"16\": \"Base UI: CompositeRootContext is missing. Composite parts must be placed within <Composite.Root>.\",\n  \"17\": \"useComboboxChipContext must be used within a ComboboxChip\",\n  \"18\": \"Base UI: ComboboxGroupContext is missing. ComboboxGroup parts must be placed within <Combobox.Group>.\",\n  \"19\": \"Base UI: ComboboxItemContext is missing. ComboboxItem parts must be placed within <Combobox.Item>.\",\n  \"20\": \"Base UI: <Combobox.Portal> is missing.\",\n  \"21\": \"Base UI: <Combobox.Popup> and <Combobox.Arrow> must be used within the <Combobox.Positioner> component\",\n  \"22\": \"Base UI: ComboboxRootContext is missing. Combobox parts must be placed within <Combobox.Root>.\",\n  \"23\": \"Base UI: ComboboxFloatingContext is missing. Combobox parts must be placed within <Combobox.Root>.\",\n  \"24\": \"Base UI: ComboboxItemsContext is missing. Combobox parts must be placed within <Combobox.Root>.\",\n  \"25\": \"Base UI: ContextMenuRootContext is missing. ContextMenu parts must be placed within <ContextMenu.Root>.\",\n  \"26\": \"Base UI: <Dialog.Portal> is missing.\",\n  \"27\": \"Base UI: DialogRootContext is missing. Dialog parts must be placed within <Dialog.Root>.\",\n  \"28\": \"Base UI: FieldRootContext is missing. Field parts must be placed within <Field.Root>.\",\n  \"29\": \"[Floating UI]: Invalid grid - item width at index %s is greater than grid columns\",\n  \"30\": \"Base UI: MenuCheckboxItemContext is missing. MenuCheckboxItem parts must be placed within <Menu.CheckboxItem>.\",\n  \"31\": \"Base UI: MenuGroupRootContext is missing. Menu group parts must be used within <Menu.Group>.\",\n  \"32\": \"Base UI: <Menu.Portal> is missing.\",\n  \"33\": \"Base UI: MenuPositionerContext is missing. MenuPositioner parts must be placed within <Menu.Positioner>.\",\n  \"34\": \"Base UI: MenuRadioGroupContext is missing. MenuRadioGroup parts must be placed within <Menu.RadioGroup>.\",\n  \"35\": \"Base UI: MenuRadioItemContext is missing. MenuRadioItem parts must be placed within <Menu.RadioItem>.\",\n  \"36\": \"Base UI: MenuRootContext is missing. Menu parts must be placed within <Menu.Root>.\",\n  \"37\": \"Base UI: <Menu.SubmenuTrigger> must be placed in <Menu.SubmenuRoot>.\",\n  \"38\": \"Base UI: MeterRootContext is missing. Meter parts must be placed within <Meter.Root>.\",\n  \"39\": \"Base UI: NavigationMenuItem parts must be used within a <NavigationMenu.Item>.\",\n  \"40\": \"Base UI: <NavigationMenu.Portal> is missing.\",\n  \"41\": \"Base UI: NavigationMenuRootContext is missing. Navigation Menu parts must be placed within <NavigationMenu.Root>.\",\n  \"42\": \"Base UI: NavigationMenuPositionerContext is missing. NavigationMenuPositioner parts must be placed within <NavigationMenu.Positioner>.\",\n  \"43\": \"Base UI: NumberFieldRootContext is missing. NumberField parts must be placed within <NumberField.Root>.\",\n  \"44\": \"Base UI: NumberFieldScrubAreaContext is missing. NumberFieldScrubArea parts must be placed within <NumberField.ScrubArea>.\",\n  \"45\": \"Base UI: <Popover.Portal> is missing.\",\n  \"46\": \"Base UI: PopoverPositionerContext is missing. PopoverPositioner parts must be placed within <Popover.Positioner>.\",\n  \"47\": \"Base UI: PopoverRootContext is missing. Popover parts must be placed within <Popover.Root>.\",\n  \"48\": \"Base UI: <PreviewCard.Portal> is missing.\",\n  \"49\": \"Base UI: <PreviewCard.Popup> and <PreviewCard.Arrow> must be used within the <PreviewCard.Positioner> component\",\n  \"50\": \"Base UI: PreviewCardRootContext is missing. PreviewCard parts must be placed within <PreviewCard.Root>.\",\n  \"51\": \"Base UI: ProgressRootContext is missing. Progress parts must be placed within <Progress.Root>.\",\n  \"52\": \"Base UI: RadioRootContext is missing. Radio parts must be placed within <Radio.Root>.\",\n  \"53\": \"Base UI: ScrollAreaRootContext is missing. ScrollArea parts must be placed within <ScrollArea.Root>.\",\n  \"54\": \"Base UI: ScrollAreaScrollbarContext is missing. ScrollAreaScrollbar parts must be placed within <ScrollArea.Scrollbar>.\",\n  \"55\": \"Base UI: ScrollAreaViewportContext missing. ScrollAreaViewport parts must be placed within <ScrollArea.Viewport>.\",\n  \"56\": \"Base UI: SelectGroupContext is missing. SelectGroup parts must be placed within <Select.Group>.\",\n  \"57\": \"Base UI: SelectItemContext is missing. SelectItem parts must be placed within <Select.Item>.\",\n  \"58\": \"Base UI: <Select.Portal> is missing.\",\n  \"59\": \"Base UI: SelectPositionerContext is missing. SelectPositioner parts must be placed within <Select.Positioner>.\",\n  \"60\": \"Base UI: SelectRootContext is missing. Select parts must be placed within <Select.Root>.\",\n  \"61\": \"Base UI: SelectFloatingContext is missing. Select parts must be placed within <Select.Root>.\",\n  \"62\": \"Base UI: SliderRootContext is missing. Slider parts must be placed within <Slider.Root>.\",\n  \"63\": \"Base UI: SwitchRootContext is missing. Switch parts must be placed within <Switch.Root>.\",\n  \"64\": \"Base UI: TabsRootContext is missing. Tabs parts must be placed within <Tabs.Root>.\",\n  \"65\": \"Base UI: TabsListContext is missing. TabsList parts must be placed within <Tabs.List>.\",\n  \"66\": \"Base UI: ToastRootContext is missing. Toast parts must be used within <Toast.Root>.\",\n  \"67\": \"Base UI: ToastViewportContext is missing. Toast parts must be placed within <Toast.Viewport>.\",\n  \"68\": \"Base UI: ToolbarGroupContext is missing. ToolbarGroup parts must be placed within <Toolbar.Group>.\",\n  \"69\": \"Base UI: ToolbarRootContext is missing. Toolbar parts must be placed within <Toolbar.Root>.\",\n  \"70\": \"Base UI: <Tooltip.Portal> is missing.\",\n  \"71\": \"Base UI: TooltipPositionerContext is missing. TooltipPositioner parts must be placed within <Tooltip.Positioner>.\",\n  \"72\": \"Base UI: TooltipRootContext is missing. Tooltip parts must be placed within <Tooltip.Root>.\",\n  \"73\": \"Base UI: useToastManager must be used within <Toast.Provider>.\",\n  \"74\": \"Base UI: <Popover.Trigger> must be either used within a <Popover.Root> component or provided with a handle.\",\n  \"75\": \"Base UI: PopoverTrigger must have an `id` prop specified.\",\n  \"76\": \"Base UI: selectors are required to call useState.\",\n  \"77\": \"Base UI: Trigger must have an `id` prop specified.\",\n  \"78\": \"Base UI: <AlertDialog.Trigger> must be used within <AlertDialog.Root> or provided with a handle.\",\n  \"79\": \"Base UI: <Dialog.Trigger> must be used within <Dialog.Root> or provided with a handle.\",\n  \"80\": \"Base UI: PopoverHandle.open: No trigger found with id \\\"%s\\\".\",\n  \"81\": \"Base UI: TooltipHandle.open: No trigger found with id \\\"%s\\\".\",\n  \"82\": \"Base UI: <Tooltip.Trigger> must be either used within a <Tooltip.Root> component or provided with a handle.\",\n  \"83\": \"Base UI: MenuHandle.open: No trigger found with id \\\"%s\\\".\",\n  \"84\": \"Base UI: ToastPositionerContext is missing. ToastPositioner parts must be placed within <Toast.Positioner>.\",\n  \"85\": \"Base UI: <Menu.Trigger> must be either used within a <Menu.Root> component or provided with a handle.\",\n  \"86\": \"Base UI: FieldsetRootContext is missing. Fieldset parts must be placed within <Fieldset.Root>.\",\n  \"87\": \"Base UI: A trigger element cannot be registered under multiple IDs in PopupTriggerMap.\",\n  \"88\": \"Base UI: PreviewCardHandle.open: No trigger found with id \\\"%s\\\".\",\n  \"89\": \"Base UI: <PreviewCard.Trigger> must be either used within a <PreviewCard.Root> component or provided with a handle.\",\n  \"90\": \"Base UI: DrawerRootContext is missing. Drawer parts must be placed within <Drawer.Root>.\",\n  \"91\": \"Base UI: DrawerProviderContext is missing. Use <Drawer.Provider>.\",\n  \"92\": \"Base UI: DrawerViewportContext is missing. Drawer parts must be placed within <Drawer.Viewport>.\",\n  \"93\": \"Base UI: TemporalAdapterContext is missing. Temporal components must be place within <TemporalAdapterProvider />\",\n  \"94\": \"Base UI: LocalizationContext is missing. Temporal components must be place within <LocalizationProvider />\",\n  \"95\": \"Base UI: SharedCalendarDayGridBodyContext is missing. <Calendar.DayGridRow /> must be placed within <Calendar.DayGridBody /> and <RangeCalendar.DayGridRow /> must be placed within <RangeCalendar.DayGridBody />.\",\n  \"96\": \"Base UI: SharedCalendarDayGridCellContext is missing. <Calendar.DayButton /> must be placed within <Calendar.DayGridCell /> and <RangeCalendar.DayButton /> must be placed within <RangeCalendar.DayGridCell />.\",\n  \"97\": \"Base UI: SharedCalendarRootContext is missing. Calendar parts must be placed within <Calendar.Root /> and Range Calendar parts must be placed within <RangeCalendar.Root />.\"\n}\n"
  },
  {
    "path": "docs/src/icons/ArrowRightIcon.tsx",
    "content": "import * as React from 'react';\n\nexport function ArrowRightIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"12\"\n      height=\"12\"\n      viewBox=\"0 0 12 12\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path\n        d=\"M1.5 6H11.25M11.25 6L7.25 2M11.25 6L7.25 10\"\n        stroke=\"currentcolor\"\n        strokeWidth=\"1.25\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/icons/CheckIcon.tsx",
    "content": "export function CheckIcon() {\n  return (\n    <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n      <path\n        d=\"M0.833008 4.64229L3.8336 8.12297L9.53471 1.50968\"\n        stroke=\"currentcolor\"\n        strokeWidth=\"1.25\"\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/icons/ChevronDownIcon.tsx",
    "content": "import * as React from 'react';\n\nexport function ChevronDownIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"10\"\n      height=\"10\"\n      viewBox=\"0 0 10 10\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M1 3.5L5 7.5L9 3.5\" stroke=\"currentcolor\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/icons/CopyIcon.tsx",
    "content": "export function CopyIcon() {\n  return (\n    <svg\n      width=\"14\"\n      height=\"14\"\n      viewBox=\"0 0 14 14\"\n      fill=\"currentcolor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <path\n        d=\"M1.5 1C0.675781 1 0 1.67578 0 2.5V10.5C0 11.3242 0.675781 12 1.5 12H3V12.5C3 13.3242 3.67578 14 4.5 14H12.5C13.3242 14 14 13.3242 14 12.5V4.5C14 3.67578 13.3242 3 12.5 3H11V2.5C11 1.67578 10.3242 1 9.5 1H1.5ZM1.5 2H9.5C9.78125 2 10 2.21875 10 2.5V10.5C10 10.7812 9.78125 11 9.5 11H1.5C1.21875 11 1 10.7812 1 10.5V2.5C1 2.21875 1.21875 2 1.5 2ZM11 4H12.5C12.7812 4 13 4.21875 13 4.5V12.5C13 12.7812 12.7812 13 12.5 13H4.5C4.21875 13 4 12.7812 4 12.5V12H9.5C10.3242 12 11 11.3242 11 10.5V4Z\"\n        transform=\"translate(0 -1)\"\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/icons/ExternalLinkIcon.tsx",
    "content": "import * as React from 'react';\n\nexport function ExternalLinkIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"12\"\n      height=\"16\"\n      viewBox=\"0 0 12 16\"\n      fill=\"currentcolor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path\n        transform=\"translate(0, -0.5)\"\n        d=\"M7 2V3H10.293L4.02344 9.27344L4.72656 9.97656L11 3.70703V7H12V2H7ZM2 4C0.894531 4 0 4.89453 0 6V12C0 13.1055 0.894531 14 2 14H8C9.10547 14 10 13.1055 10 12V7L9 8V12C9 12.5508 8.55078 13 8 13H2C1.44922 13 1 12.5508 1 12V6C1 5.44922 1.44922 5 2 5H6L7 4H2Z\"\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/icons/GitHubIcon.tsx",
    "content": "import * as React from 'react';\n\nexport function GitHubIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"18\"\n      height=\"18\"\n      viewBox=\"0 0 18 18\"\n      fill=\"currentcolor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M9.375 0C4.6095 0 0.75 3.8595 0.75 8.625C0.75 12.4418 3.219 15.6652 6.64725 16.8075C7.0785 16.8832 7.23975 16.6245 7.23975 16.398C7.23975 16.1933 7.22925 15.5145 7.22925 14.7915C5.0625 15.1905 4.50225 14.2635 4.32975 13.779C4.23225 13.5308 3.81225 12.765 3.44475 12.5602C3.14325 12.3982 2.712 11.9993 3.43425 11.9888C4.11375 11.9783 4.599 12.6142 4.761 12.873C5.53725 14.178 6.777 13.8105 7.27275 13.584C7.34775 13.0237 7.57425 12.6465 7.8225 12.4305C5.90325 12.2153 3.8985 11.4713 3.8985 8.172C3.8985 7.2345 4.23225 6.45825 4.782 5.8545C4.69575 5.6385 4.39425 4.75425 4.86825 3.5685C4.86825 3.5685 5.5905 3.342 7.2405 4.45275C7.9305 4.2585 8.66325 4.16175 9.39675 4.16175C10.1295 4.16175 10.863 4.25925 11.553 4.45275C13.203 3.3315 13.9245 3.5685 13.9245 3.5685C14.3993 4.75425 14.097 5.6385 14.0107 5.8545C14.5605 6.45825 14.895 7.22325 14.895 8.172C14.895 11.4818 12.879 12.2145 10.9598 12.4305C11.2725 12.7005 11.5417 13.218 11.5417 14.0265C11.5417 15.18 11.5312 16.107 11.5312 16.398C11.5312 16.6245 11.6933 16.8945 12.1238 16.8083C15.5318 15.6652 18 12.4305 18 8.625C18 3.8595 14.1405 0 9.375 0Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/icons/MarkdownIcon.tsx",
    "content": "import * as React from 'react';\n\nexport function MarkdownIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      aria-hidden=\"true\"\n      {...props}\n    >\n      <title>Markdown</title>\n      <path\n        d=\"M22.269 19.385H1.731a1.73 1.73 0 0 1-1.73-1.73V6.345a1.73 1.73 0 0 1 1.73-1.73h20.538a1.73 1.73 0 0 1 1.73 1.73v11.308a1.73 1.73 0 0 1-1.73 1.731zm-16.5-3.462v-4.5l2.308 2.885 2.307-2.885v4.5h2.308V8.078h-2.308l-2.307 2.885-2.308-2.885H3.461v7.847zM21.231 12h-2.308V8.077h-2.307V12h-2.308l3.461 4.039z\"\n        fill=\"currentColor\"\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/icons/MoreVertIcon.tsx",
    "content": "import * as React from 'react';\n\nexport function MoreVertIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      width=\"14\"\n      height=\"14\"\n      viewBox=\"0 0 14 14\"\n      fill=\"currentcolor\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <circle cx=\"7\" cy=\"2.5\" r=\"1.25\" />\n      <circle cx=\"7\" cy=\"7\" r=\"1.25\" />\n      <circle cx=\"7\" cy=\"11.5\" r=\"1.25\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/icons/NpmIcon.tsx",
    "content": "import * as React from 'react';\n\nexport function NpmIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg\n      fill=\"currentcolor\"\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path fillRule=\"evenodd\" clipRule=\"evenodd\" d=\"M0 0V16H16V0H0ZM13 3H3V13H8V5H11V13H13V3Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/icons/ThickCheckIcon.tsx",
    "content": "import * as React from 'react';\n\nexport function ThickCheckIcon(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg fill=\"currentcolor\" width=\"12\" height=\"12\" viewBox=\"0 0 10 10\" {...props}>\n      <path d=\"M9.1603 1.12218C9.50684 1.34873 9.60427 1.81354 9.37792 2.16038L5.13603 8.66012C5.01614 8.8438 4.82192 8.96576 4.60451 8.99384C4.3871 9.02194 4.1683 8.95335 4.00574 8.80615L1.24664 6.30769C0.939709 6.02975 0.916013 5.55541 1.19372 5.24822C1.47142 4.94102 1.94536 4.91731 2.2523 5.19524L4.36085 7.10461L8.12299 1.33999C8.34934 0.993152 8.81376 0.895638 9.1603 1.12218Z\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "docs/src/mdx/createHast.mjs",
    "content": "import { unified } from 'unified';\nimport remarkParse from 'remark-parse';\nimport remarkRehype from 'remark-rehype';\n\n/** @param {string} markdown */\nexport function createHast(markdown) {\n  const processor = unified().use(remarkParse).use(remarkRehype);\n  const mdast = processor.parse(markdown);\n  return processor.runSync(mdast);\n}\n"
  },
  {
    "path": "docs/src/mdx/createMdxComponent.ts",
    "content": "import * as jsxRuntime from 'react/jsx-runtime';\nimport { evaluate, EvaluateOptions } from '@mdx-js/mdx';\n\nexport async function createMdxComponent(\n  markdown = '',\n  // Real EvaluateOptions types are really stingy and hard to use, so just the keys are enough for our purposes\n  options: Partial<Record<keyof EvaluateOptions, unknown>> = {},\n) {\n  const { default: Component } = await evaluate(markdown, {\n    ...jsxRuntime,\n    ...options,\n  } as EvaluateOptions);\n  return Component;\n}\n"
  },
  {
    "path": "docs/src/mdx/createMdxElement.mjs",
    "content": "// @ts-check\nimport { valueToEstree } from 'estree-util-value-to-estree';\n\n/**\n * @param {Object} args\n * @param {string} args.name\n * @param {unknown[]} [args.children]\n * @param {Record<string, unknown>} [args.props]\n */\nexport function createMdxElement({ name, children, props = {} }) {\n  // Native HTML elements are rendered as regular tags\n  if (name.toLowerCase() === name) {\n    return {\n      type: 'element',\n      tagName: name,\n      properties: props,\n      children,\n    };\n  }\n\n  return {\n    type: 'mdxJsxFlowElement',\n    name,\n    attributes: Object.entries(props).map(([key, value]) => ({\n      type: 'mdxJsxAttribute',\n      name: key,\n      value: getAttributeValue(value),\n    })),\n    data: { _mdxExplicitJsx: true },\n    children,\n  };\n}\n\n/**\n * @param {unknown} value\n */\nfunction getAttributeValue(value) {\n  if (typeof value === 'string') {\n    return value;\n  }\n\n  return {\n    type: 'mdxJsxAttributeValueExpression',\n    value: JSON.stringify(value),\n    data: {\n      estree: {\n        type: 'Program',\n        body: [\n          {\n            type: 'ExpressionStatement',\n            expression: valueToEstree(value),\n          },\n        ],\n        sourceType: 'module',\n      },\n    },\n  };\n}\n"
  },
  {
    "path": "docs/src/mdx-components.tsx",
    "content": "import * as React from 'react';\nimport * as CodeBlock from './components/CodeBlock';\nimport * as Table from './components/Table';\nimport * as QuickNav from './components/QuickNav/QuickNav';\nimport { Code } from './components/Code';\nimport { ReferenceAccordion } from './components/ReferenceTable/ReferenceAccordion';\nimport { ParametersReferenceTable } from './components/ReferenceTable/ParametersReferenceTable';\nimport { ReturnValueReferenceTable } from './components/ReferenceTable/ReturnValueReferenceTable';\nimport { AttributesReferenceTable } from './components/ReferenceTable/AttributesReferenceTable';\nimport { CssVariablesReferenceTable } from './components/ReferenceTable/CssVariablesReferenceTable';\nimport { Link } from './components/Link';\nimport { HeadingLink } from './components/HeadingLink';\nimport { Subtitle } from './components/Subtitle/Subtitle';\nimport { Kbd } from './components/Kbd/Kbd';\nimport './css/mdx-components.css';\n\ninterface MDXComponents {\n  [key: string]: React.FC<any> | MDXComponents;\n}\n\n// Maintain spacing between MDX components here\nexport const mdxComponents: MDXComponents = {\n  a: Link,\n  code: (props) => <Code className=\"MdInlineCode\" {...props} />,\n  h1: (props) => (\n    // Do not wrap heading tags in divs, that confuses Safari Reader\n    <h1 className=\"MdH1\" {...props} />\n  ),\n  h2: ({ children, id, ...otherProps }) => {\n    return (\n      <h2 className=\"MdH2\" id={id} {...otherProps}>\n        <HeadingLink id={id}>{children}</HeadingLink>\n      </h2>\n    );\n  },\n  h3: ({ children, id, ...otherProps }) => {\n    return (\n      <h3 className=\"MdH3\" id={id} {...otherProps}>\n        <HeadingLink id={id}>{children}</HeadingLink>\n      </h3>\n    );\n  },\n  h4: (props) => <h4 className=\"MdH4\" {...props} />,\n  h5: (props) => <h5 className=\"MdH5\" {...props} />,\n  h6: (props) => <h6 className=\"MdH6\" {...props} />,\n  p: (props) => <p className=\"MdP\" {...props} />,\n  li: (props) => <li className=\"MdListItem\" {...props} />,\n  ul: (props) => <ul className=\"MdUl\" {...props} />,\n  ol: (props) => <ol className=\"MdOl\" {...props} />,\n  kbd: Kbd,\n  strong: (props) => <strong className=\"MdStrong\" {...props} />,\n  figure: (props) => {\n    if ('data-rehype-pretty-code-figure' in props) {\n      return <CodeBlock.Root {...props} />;\n    }\n\n    return <figure {...props} />;\n  },\n  figcaption: (props) => {\n    if ('data-rehype-pretty-code-title' in props) {\n      return <CodeBlock.Panel {...props} />;\n    }\n\n    return <figcaption {...props} />;\n  },\n  // Don't pass the tabindex prop from shiki, most browsers\n  // now handle scroll containers focus out of the box\n  pre: ({ tabIndex, ...props }) => <CodeBlock.Pre {...props} />,\n  table: (props) => <Table.Root className=\"MdTable\" {...props} />,\n  thead: Table.Head,\n  tbody: Table.Body,\n  tr: Table.Row,\n  th: (props: React.ComponentProps<'th'>) =>\n    props.scope === 'row' ? <Table.RowHeader {...props} /> : <Table.ColumnHeader {...props} />,\n  td: Table.Cell,\n  // Custom components\n  QuickNav,\n  Meta: (props: React.ComponentProps<'meta'>) => {\n    if (props.name === 'description' && String(props.content).length > 170) {\n      throw new Error('Meta description shouldn’t be longer than 170 chars');\n    }\n    return <meta {...props} />;\n  },\n  Subtitle: (props) => <Subtitle className=\"MdSubtitle\" {...props} />,\n\n  // API reference components\n  AttributesReferenceTable: (props) => (\n    <AttributesReferenceTable className=\"MdReferenceBlock\" {...props} />\n  ),\n  CssVariablesReferenceTable: (props) => (\n    <CssVariablesReferenceTable className=\"MdReferenceBlock\" {...props} />\n  ),\n  PropsReferenceTable: (props) => <ReferenceAccordion className=\"MdReferenceBlock\" {...props} />,\n  ParametersReferenceTable: (props) => (\n    <ParametersReferenceTable className=\"MdReferenceBlock\" {...props} />\n  ),\n  ReturnValueReferenceTable: (props) => (\n    <ReturnValueReferenceTable className=\"MdReferenceBlock\" {...props} />\n  ),\n};\n\nexport const inlineMdxComponents: MDXComponents = {\n  ...mdxComponents,\n  p: (props) => props.children,\n};\n\nexport function useMDXComponents(): MDXComponents {\n  return mdxComponents;\n}\n"
  },
  {
    "path": "docs/src/syntax-highlighting/index.css",
    "content": "@layer theme {\n  :root {\n    --syntax-default: var(--color-foreground);\n    --syntax-comment: var(--color-gray);\n    --syntax-constant: var(--color-blue);\n    --syntax-entity: var(--color-violet);\n    --syntax-parameter: var(--color-navy);\n    --syntax-tag: var(--color-green);\n    --syntax-keyword: var(--color-red);\n    --syntax-string: var(--color-navy);\n    --syntax-variable: var(--color-red);\n    --syntax-invalid: var(--color-red);\n  }\n}\n\n@layer components {\n  [data-language='css'] {\n    --syntax-variable: var(--color-navy);\n\n    /* Target CSS property names – ugly but works */\n    & [style='color: var(--syntax-constant)']:first-child,\n    & [style='color:var(--syntax-constant)']:first-child {\n      --syntax-constant: var(--color-foreground);\n    }\n  }\n\n  /* Collapse most colors to blue for inline code */\n  [data-inline],\n  [data-highlighted-chars] {\n    --syntax-default: var(--color-blue);\n    --syntax-entity: var(--color-blue);\n    --syntax-parameter: var(--color-blue);\n    --syntax-variable: var(--color-blue);\n    --syntax-keyword: var(--color-blue);\n    --syntax-string: var(--color-blue);\n    --syntax-nullish: var(--color-blue);\n  }\n\n  /* Recover some of the syntax highlighting colors in tables */\n  [data-table-code] {\n    --syntax-default: var(--color-foreground);\n    --syntax-entity: var(--color-violet);\n    --syntax-keyword: var(--color-red);\n    --syntax-string: var(--color-navy);\n    --syntax-nullish: var(--color-gray-500);\n  }\n\n  [data-highlighted-line] {\n    background-color: var(--color-line-highlight);\n  }\n\n  [data-highlighted-line-id='strong'] {\n    background-color: var(--color-line-highlight-strong);\n  }\n\n  [data-highlighted-chars] {\n    color: var(--syntax-default);\n    background-color: var(--color-inline-highlight);\n  }\n}\n"
  },
  {
    "path": "docs/src/syntax-highlighting/index.mjs",
    "content": "import { createHighlighter } from 'shiki';\nimport rehypePrettyCode from 'rehype-pretty-code';\nimport { rehypeInlineCode } from './rehypeInlineCode.mjs';\nimport { rehypePrettierIgnore } from './rehypePrettierIgnore.mjs';\nimport { rehypeJsxExpressions } from './rehypeJsxExpressions.mjs';\n\n/** @type {Parameters<typeof import('shiki').createHighlighter>[0]['themes'][number] } */\nexport const theme = {\n  name: 'base-ui',\n  bg: 'var(--color-content)',\n  fg: 'var(--syntax-default)',\n  settings: [\n    {\n      scope: ['comment', 'punctuation.definition.comment', 'string.comment'],\n      settings: {\n        foreground: 'var(--syntax-comment)',\n      },\n    },\n    {\n      scope: [\n        'constant',\n        'entity.name.constant',\n        'variable.other.constant',\n        'variable.other.enummember',\n        'variable.language',\n      ],\n      settings: {\n        foreground: 'var(--syntax-constant)',\n      },\n    },\n    {\n      scope: ['entity', 'entity.name'],\n      settings: {\n        foreground: 'var(--syntax-entity)',\n      },\n    },\n    {\n      scope: 'variable.parameter.function',\n      settings: {\n        foreground: 'var(--syntax-parameter)',\n      },\n    },\n    {\n      scope: 'entity.name.tag',\n      settings: {\n        foreground: 'var(--syntax-tag)',\n      },\n    },\n    {\n      scope: 'keyword',\n      settings: {\n        foreground: 'var(--syntax-keyword)',\n      },\n    },\n    {\n      scope: ['storage', 'storage.type'],\n      settings: {\n        foreground: 'var(--syntax-keyword)',\n      },\n    },\n    {\n      scope: ['storage.modifier.package', 'storage.modifier.import', 'storage.type.java'],\n      settings: {\n        foreground: 'var(--syntax-parameter)',\n      },\n    },\n    {\n      scope: [\n        'string',\n        'punctuation.definition.string',\n        'string punctuation.section.embedded source',\n      ],\n      settings: {\n        foreground: 'var(--syntax-string)',\n      },\n    },\n    {\n      scope: 'support',\n      settings: {\n        foreground: 'var(--syntax-constant)',\n      },\n    },\n    {\n      scope: 'meta.property-name',\n      settings: {\n        foreground: 'var(--syntax-constant)',\n      },\n    },\n    {\n      scope: 'variable',\n      settings: {\n        foreground: 'var(--syntax-variable)',\n      },\n    },\n    {\n      scope: 'variable.other',\n      settings: {\n        foreground: 'var(--syntax-parameter)',\n      },\n    },\n    {\n      scope: 'invalid.broken',\n      settings: {\n        fontStyle: 'italic',\n        foreground: 'var(--syntax-invalid)',\n      },\n    },\n    {\n      scope: 'invalid.deprecated',\n      settings: {\n        fontStyle: 'italic',\n        foreground: 'var(--syntax-invalid)',\n      },\n    },\n    {\n      scope: 'invalid.illegal',\n      settings: {\n        fontStyle: 'italic',\n        foreground: 'var(--syntax-invalid)',\n      },\n    },\n    {\n      scope: 'invalid.unimplemented',\n      settings: {\n        fontStyle: 'italic',\n        foreground: 'var(--syntax-invalid)',\n      },\n    },\n    {\n      scope: 'carriage-return',\n      settings: {\n        fontStyle: 'italic underline',\n        background: 'var(--syntax-carriage-return-background)',\n        foreground: 'var(--syntax-carriage-return-foreground)',\n      },\n    },\n    {\n      scope: 'message.error',\n      settings: {\n        foreground: 'var(--syntax-invalid)',\n      },\n    },\n    {\n      scope: 'string variable',\n      settings: {\n        foreground: 'var(--syntax-constant)',\n      },\n    },\n    {\n      scope: ['source.regexp', 'string.regexp'],\n      settings: {\n        foreground: 'var(--syntax-string)',\n      },\n    },\n    {\n      scope: [\n        'string.regexp.character-class',\n        'string.regexp constant.character.escape',\n        'string.regexp source.ruby.embedded',\n        'string.regexp string.regexp.arbitrary-repetition',\n      ],\n      settings: {\n        foreground: 'var(--syntax-string)',\n      },\n    },\n    {\n      scope: 'string.regexp constant.character.escape',\n      settings: {\n        fontStyle: 'bold',\n        foreground: 'var(--syntax-tag)',\n      },\n    },\n    {\n      scope: 'support.constant',\n      settings: {\n        foreground: 'var(--syntax-entity)',\n      },\n    },\n    {\n      scope: 'support.variable',\n      settings: {\n        foreground: 'var(--syntax-constant)',\n      },\n    },\n    {\n      scope: 'meta.module-reference',\n      settings: {\n        foreground: 'var(--syntax-constant)',\n      },\n    },\n    {\n      scope: 'punctuation.definition.list.begin.markdown',\n      settings: {\n        foreground: 'var(--syntax-variable)',\n      },\n    },\n    {\n      scope: ['markup.heading', 'markup.heading entity.name'],\n      settings: {\n        fontStyle: 'bold',\n        foreground: 'var(--syntax-constant)',\n      },\n    },\n    {\n      scope: 'markup.quote',\n      settings: {\n        foreground: 'var(--syntax-tag)',\n      },\n    },\n    {\n      scope: 'markup.italic',\n      settings: {\n        fontStyle: 'italic',\n        foreground: 'var(--syntax-other)',\n      },\n    },\n    {\n      scope: 'markup.bold',\n      settings: {\n        fontStyle: 'bold',\n        foreground: 'var(--syntax-other)',\n      },\n    },\n    {\n      scope: ['markup.underline'],\n      settings: {\n        fontStyle: 'underline',\n      },\n    },\n    {\n      scope: ['markup.strikethrough'],\n      settings: {\n        fontStyle: 'strikethrough',\n      },\n    },\n    {\n      scope: 'markup.inline.raw',\n      settings: {\n        foreground: 'var(--syntax-constant)',\n      },\n    },\n    {\n      scope: ['markup.deleted', 'meta.diff.header.from-file', 'punctuation.definition.deleted'],\n      settings: {\n        background: 'var(--syntax-deleted-background)',\n        foreground: 'var(--syntax-deleted-foreground)',\n      },\n    },\n    {\n      scope: ['markup.inserted', 'meta.diff.header.to-file', 'punctuation.definition.inserted'],\n      settings: {\n        background: 'var(--syntax-inserted-background)',\n        foreground: 'var(--syntax-inserted-foreground)',\n      },\n    },\n    {\n      scope: ['markup.changed', 'punctuation.definition.changed'],\n      settings: {\n        background: 'var(--syntax-changed-background)',\n        foreground: 'var(--syntax-changed-foreground)',\n      },\n    },\n    {\n      scope: ['markup.ignored', 'markup.untracked'],\n      settings: {\n        background: 'var(--syntax-untracked-background)',\n        foreground: 'var(--syntax-untracked-foreground)',\n      },\n    },\n    {\n      scope: 'meta.diff.range',\n      settings: {\n        fontStyle: 'bold',\n        foreground: 'var(--syntax-entity)',\n      },\n    },\n    {\n      scope: 'meta.diff.header',\n      settings: {\n        foreground: 'var(--syntax-constant)',\n      },\n    },\n    {\n      scope: 'meta.separator',\n      settings: {\n        fontStyle: 'bold',\n        foreground: 'var(--syntax-constant)',\n      },\n    },\n    {\n      scope: 'meta.output',\n      settings: {\n        foreground: 'var(--syntax-constant)',\n      },\n    },\n    {\n      scope: [\n        'brackethighlighter.tag',\n        'brackethighlighter.curly',\n        'brackethighlighter.round',\n        'brackethighlighter.square',\n        'brackethighlighter.angle',\n        'brackethighlighter.quote',\n      ],\n      settings: {\n        foreground: 'var(--syntax-bracket-highlight)',\n      },\n    },\n    {\n      scope: 'brackethighlighter.unmatched',\n      settings: {\n        foreground: 'var(--syntax-invalid)',\n      },\n    },\n    {\n      scope: ['constant.other.reference.link', 'string.other.link'],\n      settings: {\n        fontStyle: 'underline',\n        foreground: 'var(--syntax-string)',\n      },\n    },\n  ],\n};\n\n// Next.js hot reload doesn't dispose previously created instances of the\n// Shiki highlighter, which leads to server crashes during moderately long\n// work sessions. We instantiate the highlighter as a property of `globalThis`\n// so that the object persists between hot reloads and doesn't leak memory.\n\nglobalThis.highlighter ??= await createHighlighter({\n  themes: [theme],\n  langs: ['tsx', 'jsx', 'css'],\n});\n\n/** @type {Awaited<ReturnType<typeof import('shiki').createHighlighter>> } */\nexport const highlighter = globalThis.highlighter;\n\n/** @type {import('unified').PluggableList} */\nexport const rehypeSyntaxHighlighting = [\n  [\n    rehypePrettyCode,\n    {\n      getHighlighter: () => globalThis.highlighter,\n      grid: false,\n      theme: 'base-ui',\n      defaultLang: 'tsx',\n    },\n  ],\n  rehypePrettierIgnore,\n  rehypeJsxExpressions,\n  rehypeInlineCode,\n];\n"
  },
  {
    "path": "docs/src/syntax-highlighting/index.ts",
    "content": "export { highlighter, rehypeSyntaxHighlighting } from './index.mjs';\n"
  },
  {
    "path": "docs/src/syntax-highlighting/rehypeEmptyLines.mjs",
    "content": "import { visitParents } from 'unist-util-visit-parents';\n\n/** Move line breaks from outside of empty [data-line] elements inside them for styling purposes */\nexport function rehypeEmptyLines() {\n  return (tree) => {\n    // Insert line breaks into empty [data-line] elements\n    visitParents(tree, (node) => {\n      if (node.properties?.['data-line'] === '' && node.children?.length === 0) {\n        node.children.push({ type: 'text', value: '\\n' });\n      }\n    });\n\n    // Remove line breaks from elements that don't belong to lines\n    visitParents(tree, (node, ancestors) => {\n      if (\n        node.type === 'text' &&\n        node.value === '\\n' &&\n        ancestors.find(({ tagName }) => tagName === 'pre') &&\n        !ancestors.find((ancestor) => ancestor.properties?.['data-line'])\n      ) {\n        const parent = ancestors.slice(-1)[0];\n        const index = parent.children.indexOf(node);\n        parent.children.splice(index, 1);\n      }\n    });\n  };\n}\n"
  },
  {
    "path": "docs/src/syntax-highlighting/rehypeInlineCode.mjs",
    "content": "import { toString } from 'hast-util-to-string';\nimport { visitParents } from 'unist-util-visit-parents';\n\n/**\n * - Adds a `data-inline` attribute to distinguish inline code from code blocks\n * - Tweaks how inline code syntax highlighting works\n */\nexport function rehypeInlineCode() {\n  return (tree) => {\n    visitParents(tree, (node, ancestors) => {\n      if (node.tagName !== 'code' || ancestors.find(({ tagName }) => tagName === 'pre')) {\n        return;\n      }\n\n      node.properties ??= {};\n      node.properties['data-inline'] = '';\n\n      // Unwrap the <span> that contains the <code> element\n      const span = ancestors.slice(-1)[0];\n      const spanParent = ancestors.slice(-2)[0];\n      const spanIndex = spanParent.children.indexOf(span);\n      spanParent.children[spanIndex] = node;\n      // Unwrap the <span> that is contained within the <code> element\n      node.children = node.children[0].children;\n\n      // We don't want a background-color and color on the inline <code> tags\n      delete node.properties.style;\n\n      // Tweak how `undefined`, `null`, and `\"\"` are highlighted\n      node.children?.forEach((part) => {\n        const text = part.children[0]?.value;\n        if (text === 'undefined' || text === 'null' || text === '\"\"' || text === \"''\") {\n          part.properties.style = 'color: var(--syntax-nullish, inherit)';\n        }\n      });\n\n      // Tweak `<tag>` highlights to paint the bracket with the tag highlight color\n      if (toString(node).match(/^<.+>$/)) {\n        const keyNode = node.children?.find(\n          (part) => part.properties.style !== 'color:var(--syntax-default)',\n        );\n        node.children?.forEach((part) => {\n          part.properties.style = keyNode.properties.style;\n        });\n      }\n    });\n  };\n}\n"
  },
  {
    "path": "docs/src/syntax-highlighting/rehypeJsxExpressions.mjs",
    "content": "import { toString } from 'hast-util-to-string';\nimport { visitParents } from 'unist-util-visit-parents';\n\n/** Remove the annoying trailing semicolon from solo JSX expressions, like in Anatomy code blocks */\nexport function rehypeJsxExpressions() {\n  return (tree) => {\n    visitParents(tree, (node, ancestors) => {\n      // First, filter out nodes that seem like a potential match\n      if (node.value?.includes('>;')) {\n        const line = ancestors.find((ancestor) => ancestor.properties?.['data-line'] !== undefined);\n\n        // Verify that the line passes a stricter check\n        if (/^\\s*<.+>;\\s*$/.test(toString(line))) {\n          node.value = node.value.replace('>;', '>');\n        }\n      }\n    });\n  };\n}\n"
  },
  {
    "path": "docs/src/syntax-highlighting/rehypePrettierIgnore.mjs",
    "content": "import { toString } from 'hast-util-to-string';\nimport { visitParents } from 'unist-util-visit-parents';\n\n/** Remove code block line nodes that include \"prettier-ignore\" as text */\nexport function rehypePrettierIgnore() {\n  return (tree) => {\n    visitParents(tree, (node, ancestors) => {\n      if (\n        node.type === 'text' &&\n        ancestors.find(({ tagName }) => tagName === 'pre') &&\n        toString(node).includes('prettier-ignore')\n      ) {\n        const code = ancestors.slice(-3)[0];\n        const line = ancestors.slice(-2)[0];\n        const index = code.children.indexOf(line);\n        // Remove the line and the line break after\n        code.children.splice(index, 2);\n      }\n    });\n  };\n}\n"
  },
  {
    "path": "docs/src/utils/camelToSentenceCase.ts",
    "content": "const abbreviations = ['UI', 'POC', 'RTL'];\n\nexport function camelToSentenceCase(camel: string) {\n  let text = camel\n    .replace(/([A-Z])/g, ' $1')\n    .toLowerCase()\n    .replace(/^./, (str) => str.toUpperCase());\n\n  for (const abbreviation of abbreviations) {\n    text = text.replace(new RegExp(`\\\\b${abbreviation}\\\\b`, 'gi'), abbreviation);\n  }\n\n  return text;\n}\n"
  },
  {
    "path": "docs/src/utils/createDemo.ts",
    "content": "import 'server-only';\n\nimport {\n  createDemoFactory,\n  createDemoWithVariantsFactory,\n} from '@mui/internal-docs-infra/abstractCreateDemo';\n\nimport { Demo as DemoContent } from '../components/Demo/Demo';\nimport { DemoDataTheme } from '../demo-data/theme';\n\nconst demoGlobalData = [DemoDataTheme];\n\n/**\n * Creates a demo component for displaying code examples with syntax highlighting.\n * @param url Depends on `import.meta.url` to determine the source file location.\n * @param component The component to be rendered in the demo.\n * @param meta Additional meta for the demo.\n */\nexport const createDemo = createDemoFactory({\n  DemoContent,\n  demoGlobalData,\n});\n\n/**\n * Creates a demo component for displaying code examples with syntax highlighting.\n * A variant is a different implementation style of the same component.\n * @param url Depends on `import.meta.url` to determine the source file location.\n * @param variants The variants of the component to be rendered in the demo.\n * @param meta Additional meta for the demo.\n */\nexport const createDemoWithVariants = createDemoWithVariantsFactory({\n  DemoContent,\n  demoGlobalData,\n});\n"
  },
  {
    "path": "docs/src/utils/demoExportOptions.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\nimport { exportOpts } from './demoExportOptions';\n\ntype HeadTemplate = NonNullable<typeof exportOpts.headTemplate>;\ntype HeadTemplateProps = Parameters<HeadTemplate>[0];\ntype ExtraFiles = NonNullable<HeadTemplateProps['variant']>['extraFiles'];\n\nfunction getTailwindHeadTemplate(source: string, extraFiles?: ExtraFiles) {\n  const headTemplate = exportOpts.headTemplate;\n  if (!headTemplate) {\n    throw new Error('Expected exportOpts.headTemplate to be defined');\n  }\n\n  const props: HeadTemplateProps = {\n    sourcePrefix: '',\n    assetPrefix: '',\n    variantName: 'Tailwind',\n    variant: {\n      source,\n      extraFiles,\n    },\n  };\n\n  return headTemplate(props);\n}\n\nfunction getInjectedClasses(headTemplate: string) {\n  const metaClassMatch = headTemplate.match(/<meta name=\"custom\" class=\"([^\"]+)\" \\/>/);\n  if (!metaClassMatch) {\n    throw new Error('Expected Tailwind class injection meta tag to be present');\n  }\n\n  return metaClassMatch[1].split(/\\s+/);\n}\n\nfunction getInjectedClassAttribute(headTemplate: string) {\n  const metaClassMatch = headTemplate.match(/<meta name=\"custom\" class=\"([^\"]+)\" \\/>/);\n  if (!metaClassMatch) {\n    throw new Error('Expected Tailwind class injection meta tag to be present');\n  }\n\n  return metaClassMatch[1];\n}\n\ndescribe('exportOpts Tailwind class injection', () => {\n  it('uses the Tailwind v4 browser runtime for StackBlitz exports', () => {\n    const headTemplate = getTailwindHeadTemplate(`\n      export default function Demo() {\n        return <div className=\"outline-1 outline-gray-200\">Demo</div>;\n      }\n    `);\n\n    expect(headTemplate).toContain('https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4');\n    expect(headTemplate).not.toContain('https://cdn.tailwindcss.com');\n  });\n\n  it('injects classes referenced via concatenated className constants', () => {\n    const source = `\n      const popupClassName =\n        \"data-[starting-style]:opacity-0 transition-transform \" +\n        \"duration-300\";\n\n      export default function Demo() {\n        return <div className={popupClassName}>Demo</div>;\n      }\n    `;\n\n    const headTemplate = getTailwindHeadTemplate(source);\n    const classes = getInjectedClasses(headTemplate);\n\n    expect(classes).toContain('data-[starting-style]:opacity-0');\n    expect(classes).toContain('transition-transform');\n    expect(classes).toContain('duration-300');\n  });\n\n  it('injects classes from derived template literals referenced via className constants', () => {\n    const source = `\n      const sharedClassName =\n        \"transition-transform duration-300\";\n      const popupClassName = \\`\\${sharedClassName} data-[starting-style]:opacity-0\\`;\n\n      export default function Demo() {\n        return <div className={popupClassName}>Demo</div>;\n      }\n    `;\n\n    const headTemplate = getTailwindHeadTemplate(source);\n    const classes = getInjectedClasses(headTemplate);\n\n    expect(classes).toContain('transition-transform');\n    expect(classes).toContain('duration-300');\n    expect(classes).toContain('data-[starting-style]:opacity-0');\n  });\n\n  it('injects classes from inline template expressions that reference constants', () => {\n    const source = `\n      const itemClassName =\n        \"transition-transform duration-300\";\n\n      export default function Demo() {\n        return <div className={\\`\\${itemClassName} text-red-600\\`}>Demo</div>;\n      }\n    `;\n\n    const headTemplate = getTailwindHeadTemplate(source);\n    const classes = getInjectedClasses(headTemplate);\n\n    expect(classes).toContain('transition-transform');\n    expect(classes).toContain('duration-300');\n    expect(classes).toContain('text-red-600');\n  });\n\n  it('injects classes from extra files referenced via derived className constants', () => {\n    const headTemplate = getTailwindHeadTemplate(\n      `\n        export default function Demo() {\n          return <Extra />;\n        }\n      `,\n      {\n        'Extra.tsx': {\n          source: `\n            const sharedClassName = \"data-[ending-style]:opacity-0\";\n            const contentClassName = \\`\\${sharedClassName} translate-x-2\\`;\n\n            export function Extra() {\n              return <div className={contentClassName}>Extra</div>;\n            }\n          `,\n        },\n      },\n    );\n    const classes = getInjectedClasses(headTemplate);\n\n    expect(classes).toContain('data-[ending-style]:opacity-0');\n    expect(classes).toContain('translate-x-2');\n  });\n\n  it('escapes the injected class attribute value to prevent HTML injection', () => {\n    const source = `\n      const popupClassName = 'safe \" onclick=\"alert(1) <script>alert(2)</script>';\n\n      export default function Demo() {\n        return <div className={popupClassName}>Demo</div>;\n      }\n    `;\n\n    const headTemplate = getTailwindHeadTemplate(source);\n    const classAttribute = getInjectedClassAttribute(headTemplate);\n\n    expect(classAttribute).toContain('&quot;');\n    expect(classAttribute).toContain('&lt;script&gt;alert(2)&lt;/script&gt;');\n    expect(classAttribute).not.toContain('<script>');\n    expect(classAttribute).not.toContain('\" onclick=');\n  });\n});\n"
  },
  {
    "path": "docs/src/utils/demoExportOptions.ts",
    "content": "import { stringOrHastToString } from '@mui/internal-docs-infra/pipeline/hastUtils';\nimport type { VariantExtraFiles } from '@mui/internal-docs-infra/CodeHighlighter/types';\nimport { ExportConfig } from '@mui/internal-docs-infra/useDemo';\nimport ts from 'typescript';\n\nconst defaultStylesLink = `<link rel=\"stylesheet\" href=\"demo.css\" />`;\nconst htmlHeadWithDefaultStyles: ExportConfig['headTemplate'] = () => defaultStylesLink;\nconst demoCss = `body {\n  font-family: system-ui;\n  margin: 0;\n\n  /* iOS 26+ Safari: https://base-ui.com/react/overview/quick-start#ios-26-safari */\n  position: relative;\n}\n\n#root {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  min-height: 100vh;\n  padding: 3rem;\n  isolation: isolate;\n}\n`;\n\n// CSS Modules Theme Styles (src/demo-data/theme)\nconst themeStylesLink = `<link rel=\"stylesheet\" href=\"theme.css\" />`;\nconst htmlHeadWithCssModulesTheme: ExportConfig['headTemplate'] = () => themeStylesLink;\n\n// Tailwind CSS Setup\nconst tailwindSetup = `\n<!-- Check out the Tailwind CSS' installation guide for setting it up: https://tailwindcss.com/docs/installation/framework-guides -->\n<script src=\"https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4\"></script>`;\nconst tailwindNote = `\n\n<!-- Inject classes used so that Tailwind loaded from the CDN can pre-render them. -->\n<!-- This is for the CodeSandbox example only. You don't need this in your app. -->\n`;\n\nfunction addClassNames(classNames: Set<string>, classes: string) {\n  classes.split(/\\s+/).forEach((className) => {\n    if (className) {\n      classNames.add(className);\n    }\n  });\n}\n\nfunction collectStringDeclarations(fileSource: ts.SourceFile) {\n  const declarations = new Map<string, ts.Expression>();\n\n  function visit(node: ts.Node) {\n    if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer) {\n      declarations.set(node.name.text, node.initializer);\n    }\n\n    ts.forEachChild(node, visit);\n  }\n\n  visit(fileSource);\n  return declarations;\n}\n\nfunction resolveStringExpression(\n  expression: ts.Expression,\n  declarations: Map<string, ts.Expression>,\n  seen = new Set<string>(),\n): string | null {\n  if (ts.isStringLiteral(expression) || ts.isNoSubstitutionTemplateLiteral(expression)) {\n    return expression.text;\n  }\n\n  if (ts.isParenthesizedExpression(expression) || ts.isAsExpression(expression)) {\n    return resolveStringExpression(expression.expression, declarations, seen);\n  }\n\n  if (\n    ts.isBinaryExpression(expression) &&\n    expression.operatorToken.kind === ts.SyntaxKind.PlusToken\n  ) {\n    const left = resolveStringExpression(expression.left, declarations, seen);\n    const right = resolveStringExpression(expression.right, declarations, seen);\n\n    if (left === null || right === null) {\n      return null;\n    }\n\n    return left + right;\n  }\n\n  if (ts.isTemplateExpression(expression)) {\n    let value = expression.head.text;\n\n    for (const span of expression.templateSpans) {\n      const resolvedExpression = resolveStringExpression(span.expression, declarations, seen);\n      if (resolvedExpression === null) {\n        return null;\n      }\n\n      value += resolvedExpression + span.literal.text;\n    }\n\n    return value;\n  }\n\n  if (ts.isIdentifier(expression)) {\n    if (seen.has(expression.text)) {\n      return null;\n    }\n\n    const declaration = declarations.get(expression.text);\n    if (!declaration) {\n      return null;\n    }\n\n    const nextSeen = new Set(seen);\n    nextSeen.add(expression.text);\n    return resolveStringExpression(declaration, declarations, nextSeen);\n  }\n\n  return null;\n}\n\nfunction collectTailwindClassNames(file: string, classNames: Set<string>) {\n  const sourceFile = ts.createSourceFile(\n    'demo.tsx',\n    file,\n    ts.ScriptTarget.Latest,\n    true,\n    ts.ScriptKind.TSX,\n  );\n  const declarations = collectStringDeclarations(sourceFile);\n\n  function visit(node: ts.Node) {\n    if (\n      ts.isJsxAttribute(node) &&\n      ts.isIdentifier(node.name) &&\n      node.name.text === 'className' &&\n      node.initializer\n    ) {\n      if (ts.isStringLiteral(node.initializer)) {\n        addClassNames(classNames, node.initializer.text);\n      } else if (ts.isJsxExpression(node.initializer) && node.initializer.expression) {\n        const value = resolveStringExpression(node.initializer.expression, declarations);\n        if (value !== null) {\n          addClassNames(classNames, value);\n        }\n      }\n    }\n\n    ts.forEachChild(node, visit);\n  }\n\n  visit(sourceFile);\n}\n\nfunction escapeHtmlAttribute(value: string) {\n  return value\n    .replaceAll('&', '&amp;')\n    .replaceAll('\"', '&quot;')\n    .replaceAll('<', '&lt;')\n    .replaceAll('>', '&gt;');\n}\n\nconst htmlHeadWithTailwind: ExportConfig['headTemplate'] = ({ variant }) => {\n  let head = tailwindSetup;\n\n  // Inject css classes used on the page so that initial animations aren't broken\n  // Otherwise, TW running in the browser won't see all the classes before the components\n  // mount for the first time\n  const files = variant\n    ? [\n        variant.source && stringOrHastToString(variant.source),\n        ...(variant.extraFiles\n          ? Object.keys(variant.extraFiles).map((fileName) =>\n              variant.extraFiles &&\n              typeof variant.extraFiles[fileName] === 'object' &&\n              variant.extraFiles[fileName].source\n                ? stringOrHastToString(variant.extraFiles[fileName].source)\n                : undefined,\n            )\n          : []),\n      ]\n    : [];\n\n  const classNames = new Set<string>();\n  files.forEach((file) => {\n    if (!file) {\n      return;\n    }\n\n    collectTailwindClassNames(file, classNames);\n  });\n\n  if (classNames.size > 0) {\n    head += tailwindNote;\n    head += `<meta name=\"custom\" class=\"${escapeHtmlAttribute(Array.from(classNames).join(' '))}\" />`;\n  }\n\n  return head;\n};\n\n// Head Template\nconst htmlHeadTemplate: ExportConfig['headTemplate'] = (props) => {\n  const head = [htmlHeadWithDefaultStyles(props)];\n\n  if (props.variantName === 'CssModules') {\n    head.push(htmlHeadWithCssModulesTheme(props));\n  } else if (props.variantName === 'Tailwind') {\n    head.push(htmlHeadWithTailwind(props));\n  }\n\n  return head.filter(Boolean).join('\\n');\n};\n\n// Transform Demo Files at Export\nconst transformVariant: ExportConfig['transformVariant'] = (variant, variantName, globals) => {\n  globals = { ...globals };\n  globals['demo.css'] = { source: demoCss };\n\n  // Add color-scheme to the theme file if it exists\n  if (variantName === 'CssModules' && globals['theme.css']) {\n    const cssTheme = globals['theme.css'];\n    const source =\n      typeof cssTheme === 'object' && cssTheme.source && stringOrHastToString(cssTheme.source);\n    if (source) {\n      if (!source.includes(':root {')) {\n        throw new Error('Expected to find a \":root\" selector in the demo theme file');\n      }\n\n      globals['theme.css'] = {\n        ...cssTheme,\n        source: source.replace(':root {', `:root {\\n  color-scheme: light dark;\\n`),\n      };\n    }\n  }\n\n  return { globals };\n};\n\nfunction exposeMetadataToPublic(extraFiles: VariantExtraFiles | undefined, fileName: string) {\n  if (!extraFiles) {\n    return; // No extra files to expose\n  }\n\n  if (!extraFiles[fileName]) {\n    return; // No file to expose\n  }\n\n  if (typeof extraFiles[fileName] === 'string') {\n    return; // It can't be metadata if it's a string\n  }\n\n  // TODO: re-evaluate this\n  if (fileName.startsWith('../')) {\n    return; // If the file has a backwards path, we don't know how to expose it safely\n  }\n\n  // rename the file to be in the public directory\n  extraFiles[`public/${fileName}`] = extraFiles[fileName];\n  delete extraFiles[fileName];\n}\n\n// Transform Variant for Create React App at Export\nconst transformVariantForCRA: ExportConfig['transformVariant'] = (\n  variant,\n  variantName,\n  globals,\n) => {\n  const transformed = transformVariant(variant, variantName, globals);\n  const { variant: transformedVariant, globals: transformedGlobals } = transformed || {};\n\n  const transformedGlobalsForCRA = { ...transformedGlobals };\n\n  if (variantName === 'CssModules') {\n    exposeMetadataToPublic(transformedGlobalsForCRA, 'theme.css');\n  }\n\n  exposeMetadataToPublic(transformedGlobalsForCRA, 'demo.css');\n\n  return {\n    variant: transformedVariant,\n    globals: transformedGlobalsForCRA,\n  };\n};\n\nconst versions = {\n  '@types/react': '^19',\n  '@types/react-dom': '^19',\n  react: '^19',\n  'react-dom': '^19',\n};\n\nconst COMMIT_REF = process.env.PULL_REQUEST_ID ? process.env.COMMIT_REF : undefined;\nconst SOURCE_CODE_REPO = process.env.SOURCE_CODE_REPO;\n\nexport function resolveDependencies(packageName: string): Record<string, string> {\n  switch (packageName) {\n    case '@base-ui/react':\n    case '@base-ui/utils': {\n      const version =\n        COMMIT_REF === undefined || SOURCE_CODE_REPO !== 'https://github.com/mui/base-ui'\n          ? 'latest' // #npm-tag-reference\n          : `https://pkg.pr.new/mui/base-ui/${packageName}@${COMMIT_REF}`;\n      return { [packageName]: version };\n    }\n\n    default:\n      return {\n        [packageName]: 'latest',\n      };\n  }\n}\n\nconst tsconfigOptions = {\n  allowJs: true,\n  esModuleInterop: true,\n  allowSyntheticDefaultImports: true,\n  forceConsistentCasingInFileNames: true,\n};\n\nconst craTsConfigOptions = {\n  ...tsconfigOptions,\n  lib: ['dom', 'dom.iterable', 'esnext'],\n  moduleResolution: 'node',\n  jsx: 'react',\n};\n\nexport const exportOpts: ExportConfig = {\n  titleSuffix: ' - Base UI Example',\n  headTemplate: htmlHeadTemplate,\n  transformVariant,\n  versions,\n  resolveDependencies,\n  tsconfigOptions,\n};\n\nexport const exportCodeSandbox: ExportConfig = {\n  transformVariant: transformVariantForCRA,\n  tsconfigOptions: craTsConfigOptions,\n};\n"
  },
  {
    "path": "docs/src/utils/getChildrenText.ts",
    "content": "import * as React from 'react';\n\nexport function getChildrenText(children?: React.ReactNode): string {\n  if (hasChildren(children)) {\n    return getChildrenText(children.props?.children);\n  }\n\n  if (Array.isArray(children)) {\n    return children.map(getChildrenText).flat().filter(Boolean).join('');\n  }\n\n  if (typeof children === 'string') {\n    return children;\n  }\n\n  return '';\n}\n\nfunction hasChildren(\n  element?: React.ReactNode,\n): element is React.ReactElement<React.PropsWithChildren> {\n  return (\n    React.isValidElement(element) &&\n    typeof element.props === 'object' &&\n    !!element.props &&\n    'children' in element.props &&\n    Boolean(element.props.children)\n  );\n}\n"
  },
  {
    "path": "docs/src/utils/getGitHubDemoUrl.test.ts",
    "content": "import { describe, it, expect } from 'vitest';\nimport { getGitHubDemoUrl, GITHUB_BASE } from './getGitHubDemoUrl';\n\ndescribe('getGitHubDemoUrl', () => {\n  const unixUrl =\n    'file:///home/user/base-ui/docs/src/app/(docs)/react/components/accordion/demos/hero/index.ts';\n  const windowsUrl =\n    'file:///C:/Users/Dev/base-ui/docs/src/app/(docs)/react/components/accordion/demos/hero/index.ts';\n\n  it('converts a Unix file URL to a GitHub directory URL', () => {\n    expect(getGitHubDemoUrl(unixUrl)).toBe(\n      `${GITHUB_BASE}/docs/src/app/(docs)/react/components/accordion/demos/hero`,\n    );\n  });\n\n  it('converts a Windows file URL to a GitHub directory URL', () => {\n    expect(getGitHubDemoUrl(windowsUrl)).toBe(\n      `${GITHUB_BASE}/docs/src/app/(docs)/react/components/accordion/demos/hero`,\n    );\n  });\n\n  it('appends the kebab-cased variant subdirectory for CssModules', () => {\n    expect(getGitHubDemoUrl(unixUrl, 'CssModules')).toBe(\n      `${GITHUB_BASE}/docs/src/app/(docs)/react/components/accordion/demos/hero/css-modules`,\n    );\n  });\n\n  it('appends the variant subdirectory for Tailwind', () => {\n    expect(getGitHubDemoUrl(unixUrl, 'Tailwind')).toBe(\n      `${GITHUB_BASE}/docs/src/app/(docs)/react/components/accordion/demos/hero/tailwind`,\n    );\n  });\n\n  it('does not append a subdirectory for the Default variant', () => {\n    expect(getGitHubDemoUrl(unixUrl, 'Default')).toBe(\n      `${GITHUB_BASE}/docs/src/app/(docs)/react/components/accordion/demos/hero`,\n    );\n  });\n\n  it('returns null for undefined url', () => {\n    expect(getGitHubDemoUrl(undefined)).toBeNull();\n  });\n\n  it('returns null for empty string', () => {\n    expect(getGitHubDemoUrl('')).toBeNull();\n  });\n\n  it('returns null when /docs/ is not in the path', () => {\n    expect(getGitHubDemoUrl('file:///home/user/other-project/src/index.ts')).toBeNull();\n  });\n\n  it('handles encoded URI components', () => {\n    const encoded = unixUrl.replace(/\\(/g, '%28').replace(/\\)/g, '%29');\n    expect(getGitHubDemoUrl(encoded)).toBe(\n      `${GITHUB_BASE}/docs/src/app/(docs)/react/components/accordion/demos/hero`,\n    );\n  });\n});\n"
  },
  {
    "path": "docs/src/utils/getGitHubDemoUrl.ts",
    "content": "import kebabCase from 'es-toolkit/compat/kebabCase';\n\nexport const GITHUB_BASE = 'https://github.com/mui/base-ui/tree/HEAD';\n\n/**\n * Converts a file:// URL from import.meta.url to a GitHub source URL\n * for the demo directory, optionally targeting a specific variant subdirectory.\n */\nexport function getGitHubDemoUrl(\n  fileUrl: string | undefined,\n  selectedVariant?: string,\n): string | null {\n  if (!fileUrl) {\n    return null;\n  }\n\n  try {\n    const normalized = decodeURIComponent(fileUrl).replace(/\\\\/g, '/');\n\n    const docsIndex = normalized.indexOf('/docs/');\n    if (docsIndex === -1) {\n      return null;\n    }\n\n    // Extract from \"docs/\" onward\n    const repoRelativePath = normalized.slice(docsIndex + 1);\n\n    // Strip the trailing filename to get the directory\n    const lastSlash = repoRelativePath.lastIndexOf('/');\n    if (lastSlash === -1) {\n      return null;\n    }\n    let dirPath = repoRelativePath.slice(0, lastSlash);\n\n    if (selectedVariant && selectedVariant !== 'Default') {\n      dirPath += `/${kebabCase(selectedVariant)}`;\n    }\n\n    return `${GITHUB_BASE}/${dirPath}`;\n  } catch {\n    return null;\n  }\n}\n"
  },
  {
    "path": "docs/src/utils/observeScrollableInner.ts",
    "content": "// Observe whether the inner node is scrollable and set a \"[data-scrollable]\"\n// attribute on the parent node. We are rawdogging the DOM changes here to skip unnecessary renders.\nexport function observeScrollableInner(ref: HTMLElement | null) {\n  if (!ref) {\n    return undefined;\n  }\n\n  const inner = ref.children[0] as HTMLElement;\n  let raf: ReturnType<typeof requestAnimationFrame> | null = null;\n  const observer = new ResizeObserver(() => {\n    const isScrollable = inner.scrollWidth > inner.offsetWidth;\n\n    // Schedule the DOM update to happen in the next frame to avoid layout trashing.\n    raf = requestAnimationFrame(() => {\n      if (isScrollable) {\n        ref.setAttribute('data-scrollable', '');\n      } else {\n        ref.removeAttribute('data-scrollable');\n      }\n      raf = null;\n    });\n  });\n\n  if (inner) {\n    observer.observe(inner);\n  } else if (process.env.NODE_ENV !== 'production') {\n    console.warn('Expected to find an inner element');\n  }\n\n  return () => {\n    observer.disconnect();\n    if (raf !== null) {\n      cancelAnimationFrame(raf);\n    }\n  };\n}\n"
  },
  {
    "path": "docs/tsconfig.json",
    "content": "{\n  \"extends\": \"../tsconfig.base.json\",\n  \"include\": [\n    \"data\",\n    \"scripts/*.mts\",\n    \"src\",\n    \"types.d.ts\",\n    \"next-env.d.ts\",\n    \"next.config.mjs\",\n    \".next/types/**/*.ts\",\n    \"export/types/**/*.ts\"\n  ],\n  \"compilerOptions\": {\n    \"allowJs\": true,\n    \"isolatedModules\": true,\n    /* files are emitted by webpack */\n    \"noEmit\": true,\n    \"noUnusedLocals\": true,\n    \"resolveJsonModule\": true,\n    \"skipLibCheck\": true,\n    \"esModuleInterop\": true,\n    \"types\": [\"react\"],\n    \"incremental\": true,\n    \"composite\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"plugins\": [\n      {\n        \"name\": \"next\"\n      }\n    ],\n    \"paths\": {\n      \"@base-ui/react\": [\"../packages/react/src\"],\n      \"@base-ui/react/*\": [\"../packages/react/src/*\"],\n      \"@base-ui/utils/*\": [\"../packages/utils/src/*\"]\n    },\n    \"strictNullChecks\": true,\n    \"jsx\": \"preserve\"\n  },\n  \"exclude\": [\"node_modules\"],\n  \"references\": [\n    {\n      \"path\": \"../packages/react/tsconfig.build.json\"\n    }\n  ]\n}\n"
  },
  {
    "path": "docs/types.d.ts",
    "content": "/// <reference types=\"gtag.js\" />\n\ndeclare module 'gtag.js';\n\ndeclare module '*.mdx' {\n  const MDXComponent: (props) => JSX.Element;\n  export default MDXComponent;\n}\n\ndeclare module '*.css';\n"
  },
  {
    "path": "docs/vitest.config.mts",
    "content": "import { mergeConfig, defineProject } from 'vitest/config';\n// eslint-disable-next-line import/no-relative-packages\nimport sharedConfig from '../vitest.shared.mts';\n\nexport default mergeConfig(\n  sharedConfig,\n  defineProject({\n    test: {\n      environment: 'node',\n      browser: {\n        enabled: false,\n        name: 'node',\n      },\n      env: {\n        VITEST_ENV: 'node',\n      },\n    },\n  }),\n);\n"
  },
  {
    "path": "eslint.config.mjs",
    "content": "import {\n  baseSpecRules,\n  createBaseConfig,\n  createDocsConfig,\n  createTestConfig,\n  EXTENSION_TEST_FILE,\n  EXTENSION_TS,\n} from '@mui/internal-code-infra/eslint';\nimport { defineConfig, globalIgnores } from 'eslint/config';\nimport * as path from 'node:path';\nimport { fileURLToPath } from 'url';\n\nconst filename = fileURLToPath(import.meta.url);\nconst dirname = path.dirname(filename);\nconst playgroundRootDir = path.join(dirname, 'playground', 'vite-app');\n\nconst OneLevelImportMessage = [\n  'Prefer one level nested imports to avoid bundling everything in dev mode or breaking CJS/ESM split.',\n  'See https://github.com/mui/material-ui/pull/24147 for the kind of win it can unlock.',\n].join('\\n');\n\nconst NO_RESTRICTED_IMPORTS_PATTERNS_DEEPLY_NESTED = [\n  {\n    group: ['@base-ui/react/*/*'],\n    message: OneLevelImportMessage,\n  },\n];\n\n// Add relevant packages to the list below.\nconst NO_RESTRICTED_IMPORTS_PATHS_TOP_LEVEL_PACKAGES = [\n  // { name: string, message: string }\n];\n\nexport default defineConfig(\n  globalIgnores(['./examples', './playground/vite-app/dist']),\n  createBaseConfig({\n    baseDirectory: dirname,\n  }),\n  {\n    name: 'Playground Vite app overrides',\n    files: ['playground/vite-app/**/*.{ts,tsx}'],\n    languageOptions: {\n      parserOptions: {\n        project: ['./tsconfig.app.json', './tsconfig.node.json'],\n        tsconfigRootDir: playgroundRootDir,\n      },\n    },\n    rules: {\n      'no-console': 'off',\n    },\n  },\n  {\n    name: 'Base UI overrides',\n    files: [`**/*${EXTENSION_TS}`],\n    settings: {\n      'import/resolver': {\n        typescript: {\n          project: ['tsconfig.json'],\n        },\n      },\n    },\n    /**\n     * Sorted alphanumerically within each group. built-in and each plugin form\n     * their own groups.\n     */\n    rules: {\n      // @TODO: Remove this once we move away from namespaces\n      '@typescript-eslint/no-namespace': 'off',\n      'import/export': 'off', // FIXME: Maximum call stack exceeded\n      'no-restricted-imports': [\n        'error',\n        {\n          patterns: NO_RESTRICTED_IMPORTS_PATTERNS_DEEPLY_NESTED,\n        },\n      ],\n      // We LOVE non-breaking spaces, and both straight and curly quotes here\n      'no-irregular-whitespace': ['warn', { skipJSXText: true, skipStrings: true }],\n      'react/react-in-jsx-scope': 'off',\n      'react/no-unescaped-entities': ['warn', { forbid: ['>', '}'] }],\n      'react/prop-types': 'off',\n      'react-hooks/exhaustive-deps': [\n        'error',\n        {\n          additionalHooks: 'useIsoLayoutEffect',\n        },\n      ],\n      // This prevents us from creating components like `<h1 {...props} />`\n      'jsx-a11y/heading-has-content': 'off',\n      'jsx-a11y/anchor-has-content': 'off',\n\n      // This rule doesn't recognise <label> wrapped around custom controls\n      'jsx-a11y/label-has-associated-control': 'off',\n      // Turn off new eslint-plugin-react-hooks rules till we can fix all warnings\n      'react-hooks/globals': 'off',\n      'react-hooks/immutability': 'off',\n      'react-hooks/incompatible-library': 'off',\n      'react-hooks/refs': 'off',\n    },\n  },\n  {\n    files: [`packages/*/src/**/*${EXTENSION_TS}`],\n    ignores: [`**/*${EXTENSION_TEST_FILE}`, `**/*.spec${EXTENSION_TS}`, `test/**/*${EXTENSION_TS}`],\n    rules: {\n      'mui/add-undef-to-optional': 'error',\n      'mui/disallow-react-api-in-server-components': 'error',\n    },\n  },\n  {\n    files: [\n      // matching the pattern of the test runner\n      `**/*${EXTENSION_TEST_FILE}`,\n    ],\n    extends: createTestConfig({ useMocha: false }),\n    rules: {\n      'mui/add-undef-to-optional': 'off',\n    },\n  },\n  baseSpecRules,\n  {\n    name: 'MUI ESLint config for docs',\n    files: [`docs/**/*${EXTENSION_TS}`],\n    extends: createDocsConfig(),\n    rules: {\n      '@typescript-eslint/no-use-before-define': 'off',\n      'import/extensions': [\n        'error',\n        // Ignores extensions in package imports as well as local ts/tsx imports but .mjs is always required\n        'ignorePackages',\n        {\n          ts: 'never',\n          tsx: 'never',\n          mjs: 'always',\n        },\n      ],\n      'no-restricted-imports': [\n        'error',\n        {\n          paths: NO_RESTRICTED_IMPORTS_PATHS_TOP_LEVEL_PACKAGES,\n          patterns: NO_RESTRICTED_IMPORTS_PATTERNS_DEEPLY_NESTED,\n        },\n      ],\n    },\n  },\n  {\n    files: [`docs/src/app/(private)/experiments/**/*${EXTENSION_TS}`],\n    rules: {\n      '@typescript-eslint/no-use-before-define': 'off',\n      'no-alert': 'off',\n      'no-console': 'off',\n      'import/no-relative-packages': 'off',\n    },\n  },\n  {\n    files: [`docs/src/app/(docs)/react/utils/use-render/demos/**/*${EXTENSION_TS}`],\n    rules: {\n      'jsx-a11y/control-has-associated-label': 'off',\n      'react/button-has-type': 'off',\n    },\n  },\n  {\n    name: 'Disable image rule for demos',\n    files: [\n      `docs/src/app/(docs)/**/demos/**/*${EXTENSION_TS}`,\n      `docs/src/app/(private)/experiments/**/*${EXTENSION_TS}`,\n    ],\n    ignores: ['docs/src/app/(private)/experiments/**/page.tsx'],\n    rules: {\n      '@next/next/no-img-element': 'off',\n    },\n  },\n  {\n    files: [`test/**/*${EXTENSION_TS}`],\n    rules: {\n      'guard-for-in': 'off',\n      'testing-library/prefer-screen-queries': 'off', // Enable usage of playwright queries\n      'testing-library/no-await-sync-queries': 'off',\n      'testing-library/render-result-naming-convention': 'off', // inconsequential in regression tests\n      'mui/consistent-production-guard': 'off',\n    },\n  },\n);\n"
  },
  {
    "path": "examples/tanstack-start-tailwind-css/.devcontainer/devcontainer.json",
    "content": "{\n  \"name\": \"Devcontainer\",\n  \"image\": \"mcr.microsoft.com/devcontainers/javascript-node:20\"\n}\n"
  },
  {
    "path": "examples/tanstack-start-tailwind-css/.gitignore",
    "content": "node_modules\n.DS_Store\ndist\ndist-ssr\n*.local\ncount.txt\n.env\n.nitro\n.tanstack\n.wrangler\n.output\n.vinxi\ntodos.json\n*.tsbuildinfo"
  },
  {
    "path": "examples/tanstack-start-tailwind-css/.prettierignore",
    "content": "package-lock.json\npnpm-lock.yaml\nyarn.lock\nrouteTree.gen.ts"
  },
  {
    "path": "examples/tanstack-start-tailwind-css/.vscode/settings.json",
    "content": "{\n  \"files.watcherExclude\": {\n    \"**/routeTree.gen.ts\": true\n  },\n  \"search.exclude\": {\n    \"**/routeTree.gen.ts\": true\n  },\n  \"files.readonlyInclude\": {\n    \"**/routeTree.gen.ts\": true\n  }\n}\n"
  },
  {
    "path": "examples/tanstack-start-tailwind-css/README.md",
    "content": "# Base UI + TanStack Start\n\nThis is a simple [TanStack Start](https://tanstack.com/start/latest/docs/framework/react/overview) app with [Base UI components](https://base-ui.com/react/overview/quick-start) styled using [Tailwind CSS](https://tailwindcss.com/).\n\n## Getting started\n\n[![Edit on CodeSandbox](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/p/sandbox/github/mui/base-ui/tree/master/examples/tanstack-start-tailwind-css)\n\n[![Edit on StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/mui/base-ui/tree/master/examples/tanstack-start-tailwind-css)\n\n**Note:** TanStack Start Server Functions used in the combobox example may not work in StackBlitz due to lack of AsyncLocalStorage support.\n\nOr to run it locally:\n\n```bash\npnpm install\npnpm start\n```\n\nThis starts a [Vite](https://vite.dev) development server at [http://localhost:3000](http://localhost:3000). [Node.js](https://nodejs.org/) version 20.19+ or 22.12+ is required.\n\n## Building for production\n\nTo build a production bundle:\n\n```bash\npnpm build\n```\n\nTo preview the production build:\n\n```bash\npnpm serve\n```\n\n## TypeScript\n\nTo check the types:\n\n```bash\npnpm typescript\n```\n\n## Linting & Formatting\n\nThis project uses [ESLint](https://eslint.org/) and [prettier](https://prettier.io/) for linting and formatting. ESLint is configured using [`@tanstack/eslint-config`](https://tanstack.com/config/latest/docs/eslint). The following scripts are available:\n\n```bash\n# lint only\npnpm lint\n# lint and prettier with autofix\npnpm check\n```\n\n## Routing\n\nThis app uses [TanStack Router](https://tanstack.com/router) with a file based router. Routes are managed as files in `src/routes`. The router also provides [`loader` functionality](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#loader-parameters) to load data for a route before it's rendered.\n"
  },
  {
    "path": "examples/tanstack-start-tailwind-css/eslint.config.js",
    "content": "//  @ts-check\n\nimport { tanstackConfig } from '@tanstack/eslint-config';\n\nexport default [\n  ...tanstackConfig,\n  {\n    files: ['**/*.js'],\n    languageOptions: {\n      parserOptions: { project: null, projectService: { allowDefaultProject: ['*.js'] } },\n    },\n  },\n];\n"
  },
  {
    "path": "examples/tanstack-start-tailwind-css/package.json",
    "content": "{\n  \"name\": \"base-ui-tanstack-start\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite dev --port 3000\",\n    \"build\": \"vite build\",\n    \"serve\": \"vite preview\",\n    \"lint\": \"eslint .\",\n    \"check\": \"prettier . --write && eslint . --fix\",\n    \"typescript\": \"tsc -b tsconfig.json\"\n  },\n  \"dependencies\": {\n    \"@base-ui/react\": \"latest\",\n    \"@base-ui/utils\": \"latest\",\n    \"@tanstack/react-devtools\": \"^0.9.2\",\n    \"@tanstack/react-router\": \"^1.154.12\",\n    \"@tanstack/react-router-devtools\": \"^1.154.12\",\n    \"@tanstack/react-start\": \"^1.154.12\",\n    \"@tanstack/router-plugin\": \"^1.154.12\",\n    \"clsx\": \"^2.1.1\",\n    \"lucide-react\": \"^0.562.0\",\n    \"react\": \"^19\",\n    \"react-dom\": \"^19\",\n    \"vite-tsconfig-paths\": \"^6.0.4\"\n  },\n  \"devDependencies\": {\n    \"@tailwindcss/vite\": \"^4.1.18\",\n    \"@tanstack/devtools-vite\": \"^0.4.1\",\n    \"@tanstack/eslint-config\": \"^0.3.4\",\n    \"eslint\": \"^10.0.2\",\n    \"@types/node\": \"22.18.13\",\n    \"@types/react\": \"^19\",\n    \"@types/react-dom\": \"^19\",\n    \"@vitejs/plugin-react\": \"^5.1.2\",\n    \"prettier\": \"^3.8.1\",\n    \"tailwindcss\": \"^4.1.18\",\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^7.3.1\"\n  }\n}\n"
  },
  {
    "path": "examples/tanstack-start-tailwind-css/prettier.config.js",
    "content": "//  @ts-check\n\n/** @type {import('prettier').Config} */\nconst config = {\n  printWidth: 100,\n  tabWidth: 2,\n  useTabs: false,\n  semi: true,\n  singleQuote: true,\n  trailingComma: 'all',\n  bracketSpacing: true,\n};\n\nexport default config;\n"
  },
  {
    "path": "examples/tanstack-start-tailwind-css/public/manifest.json",
    "content": "{\n  \"short_name\": \"Base UI + TanStack Start\",\n  \"name\": \"Base UI TanStack Start Example App\",\n  \"icons\": [\n    {\n      \"src\": \"favicon.ico\",\n      \"sizes\": \"64x64 32x32 24x24 16x16\",\n      \"type\": \"image/x-icon\"\n    },\n    {\n      \"src\": \"logo192.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"192x192\"\n    },\n    {\n      \"src\": \"logo512.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"512x512\"\n    }\n  ],\n  \"start_url\": \".\",\n  \"display\": \"standalone\",\n  \"theme_color\": \"#000000\",\n  \"background_color\": \"#ffffff\"\n}\n"
  },
  {
    "path": "examples/tanstack-start-tailwind-css/public/robots.txt",
    "content": "# https://www.robotstxt.org/robotstxt.html\nUser-agent: *\nDisallow:\n"
  },
  {
    "path": "examples/tanstack-start-tailwind-css/src/components/button.tsx",
    "content": "import { Button as BaseButton } from '@base-ui/react/button';\nimport clsx from 'clsx';\n\nexport function Button({ className, ...props }: React.ComponentPropsWithoutRef<'button'>) {\n  return (\n    <BaseButton\n      type=\"button\"\n      className={clsx(\n        'flex items-center justify-center h-10 px-3.5 m-0 outline-0 border border-gray-200 rounded-md bg-gray-50 font-inherit text-base font-medium leading-6 text-gray-900 select-none hover:data-[disabled]:bg-gray-50 hover:bg-gray-100 active:data-[disabled]:bg-gray-50 active:bg-gray-200 active:shadow-[inset_0_1px_3px_rgba(0,0,0,0.1)] active:border-t-gray-300 active:data-[disabled]:shadow-none active:data-[disabled]:border-t-gray-200 focus-visible:outline-2 focus-visible:outline-blue-800 focus-visible:-outline-offset-1 data-[disabled]:text-gray-500',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n"
  },
  {
    "path": "examples/tanstack-start-tailwind-css/src/components/combobox.tsx",
    "content": "import * as React from 'react';\nimport clsx from 'clsx';\nimport { Combobox } from '@base-ui/react/combobox';\nimport { Check, ChevronDown, X } from 'lucide-react';\n\nexport function Root(props: Combobox.Root.Props<any, any>) {\n  return <Combobox.Root {...props} />;\n}\n\nexport const Input = React.forwardRef<HTMLInputElement, Combobox.Input.Props>(function (\n  { className, ...props }: Combobox.Input.Props,\n  forwardedRef: React.ForwardedRef<HTMLInputElement>,\n) {\n  return (\n    <Combobox.Input\n      ref={forwardedRef}\n      className={clsx(\n        'h-10 w-64 rounded-md font-normal border border-gray-200 pl-3.5 text-base text-gray-900 bg-[canvas] focus:outline-2 focus:-outline-offset-1 focus:outline-blue-800',\n        className,\n      )}\n      {...props}\n    />\n  );\n});\n\nInput.displayName = 'Input';\n\nexport function Clear({ className, ...props }: Combobox.Clear.Props) {\n  return (\n    <Combobox.Clear\n      className={clsx(\n        'combobox-clear flex h-10 w-6 items-center justify-center rounded-sm bg-transparent p-0',\n        className,\n      )}\n      {...props}\n    >\n      <X className=\"size-4\" />\n    </Combobox.Clear>\n  );\n}\n\nexport function Trigger({ className, ...props }: Combobox.Trigger.Props) {\n  return (\n    <Combobox.Trigger\n      className={clsx(\n        'flex h-10 w-6 items-center justify-center rounded-sm bg-transparent p-0',\n        className,\n      )}\n      {...props}\n    >\n      <ChevronDown className=\"size-4\" />\n    </Combobox.Trigger>\n  );\n}\n\nexport function Portal(props: Combobox.Portal.Props) {\n  return <Combobox.Portal {...props} />;\n}\n\nexport function Positioner({ className, ...props }: Combobox.Positioner.Props) {\n  return (\n    <Combobox.Positioner className={clsx('outline-hidden', className)} sideOffset={4} {...props} />\n  );\n}\n\nexport function Popup({ className, ...props }: Combobox.Popup.Props) {\n  return (\n    <Combobox.Popup\n      className={clsx(\n        'w-[var(--anchor-width)] max-h-[23rem] max-w-[var(--available-width)] origin-[var(--transform-origin)] rounded-md bg-[canvas] text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 transition-[transform,scale,opacity] data-[ending-style]:scale-95 data-[ending-style]:opacity-0 data-[starting-style]:scale-95 data-[starting-style]:opacity-0 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300 duration-100',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function Empty({ className, ...props }: Combobox.Empty.Props) {\n  return (\n    <Combobox.Empty\n      className={clsx('p-4 text-[0.925rem] leading-4 text-gray-600 empty:m-0 empty:p-0', className)}\n      {...props}\n    />\n  );\n}\n\nexport function List({ className, ...props }: Combobox.List.Props) {\n  return (\n    <Combobox.List\n      className={clsx(\n        'outline-0 overflow-y-auto scroll-py-[0.5rem] py-2 overscroll-contain max-h-[min(23rem,var(--available-height))] data-[empty]:p-0',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function Item({ className, ...props }: Combobox.Item.Props) {\n  return (\n    <Combobox.Item\n      className={clsx(\n        'grid cursor-default grid-cols-[0.75rem_1fr] items-center gap-2 py-2 pr-8 pl-4 text-base leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-2 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-sm data-[highlighted]:before:bg-gray-900',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function ItemIndicator({ className, ...props }: Combobox.ItemIndicator.Props) {\n  return (\n    <Combobox.ItemIndicator className={clsx('col-start-1', className)} {...props}>\n      <Check className=\"size-4\" />\n    </Combobox.ItemIndicator>\n  );\n}\n"
  },
  {
    "path": "examples/tanstack-start-tailwind-css/src/components/dialog.tsx",
    "content": "import clsx from 'clsx';\nimport { Dialog } from '@base-ui/react/dialog';\n\nexport function Root(props: Dialog.Root.Props) {\n  return <Dialog.Root {...props} />;\n}\n\nexport function Trigger({ className, ...props }: Dialog.Trigger.Props) {\n  return (\n    <Dialog.Trigger\n      className={clsx(\n        'flex h-10 items-center justify-center gap-1.5 rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-medium text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100 data-[popup-open]:bg-gray-100',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function Portal(props: Dialog.Portal.Props) {\n  return <Dialog.Portal {...props} />;\n}\n\nexport function Backdrop({ className, ...props }: Dialog.Backdrop.Props) {\n  return (\n    <Dialog.Backdrop\n      className={clsx(\n        'fixed inset-0 min-h-dvh bg-black opacity-20 transition-all duration-150 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0 dark:opacity-70 supports-[-webkit-touch-callout:none]:absolute',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function Popup({ className, ...props }: Dialog.Popup.Props) {\n  return (\n    <Dialog.Popup\n      className={clsx(\n        'fixed top-[calc(50%+1.25rem*var(--nested-dialogs))] left-1/2 -mt-8 w-96 max-w-[calc(100vw-3rem)] -translate-x-1/2 -translate-y-1/2 scale-[calc(1-0.1*var(--nested-dialogs))] rounded-lg bg-gray-50 p-6 text-gray-900 outline-1 outline-gray-200 transition-all duration-150 data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[nested-dialog-open]:after:absolute data-[nested-dialog-open]:after:inset-0 data-[nested-dialog-open]:after:rounded-[inherit] data-[nested-dialog-open]:after:bg-black/5 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 dark:outline-gray-300',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function Title({ className, ...props }: Dialog.Title.Props) {\n  return (\n    <Dialog.Title className={clsx('-mt-1.5 mb-1 text-lg font-medium', className)} {...props} />\n  );\n}\n\nexport function Close({ className, ...props }: Dialog.Close.Props) {\n  return (\n    <Dialog.Close\n      className={clsx(\n        'flex h-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-medium text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n"
  },
  {
    "path": "examples/tanstack-start-tailwind-css/src/components/input.tsx",
    "content": "import * as React from 'react';\nimport clsx from 'clsx';\nimport { Input as BaseInput } from '@base-ui/react/input';\n\nexport const Input = React.forwardRef<HTMLInputElement, BaseInput.Props>(function (\n  { className, ...props }: BaseInput.Props,\n  forwardedRef: React.ForwardedRef<HTMLInputElement>,\n) {\n  return (\n    <BaseInput\n      ref={forwardedRef}\n      className={clsx(\n        'h-10 w-full rounded-md bg-[canvas] border border-gray-200 pl-3.5 text-base text-gray-900 focus:outline-2 focus:-outline-offset-1 focus:outline-blue-800 placeholder:text-gray-500',\n        className,\n      )}\n      {...props}\n    />\n  );\n});\n\nInput.displayName = 'Input';\n"
  },
  {
    "path": "examples/tanstack-start-tailwind-css/src/components/link.tsx",
    "content": "import clsx from 'clsx';\n\nexport function Link({ className, ...props }: React.ComponentPropsWithoutRef<'a'>) {\n  const external = props.href && !props.href.includes('base-ui') && props.href.startsWith('http');\n  return (\n    <a\n      className={clsx(styles, className)}\n      rel={external ? 'noreferrer noopener' : undefined}\n      target={external ? '_blank' : undefined}\n      {...props}\n    />\n  );\n}\n\nexport const styles = [\n  'text-[var(--color-blue)] underline underline-offset-2',\n  'decoration-[1px] decoration-[color-mix(in_oklab,var(--color-blue),transparent)] hover:decoration-[var(--color-blue)]',\n  'focus-visible:outline-2 focus-visible:outline-[var(--color-blue)] focus-visible:outline-offset-[-2px] focus-visible:rounded-[var(--radius-sm)]',\n];\n"
  },
  {
    "path": "examples/tanstack-start-tailwind-css/src/components/menu.tsx",
    "content": "import clsx from 'clsx';\nimport { Menu } from '@base-ui/react/menu';\nimport { ChevronDown, ChevronRight } from 'lucide-react';\n\nexport function Root(props: Menu.Root.Props) {\n  return <Menu.Root {...props} />;\n}\n\nexport function Trigger({ className, children, ...props }: Menu.Trigger.Props) {\n  return (\n    <Menu.Trigger\n      className={clsx(\n        'flex h-10 items-center justify-center gap-1.5 rounded-md border border-gray-200 bg-gray-50 px-3.5 text-base font-medium text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100 data-[popup-open]:bg-gray-100',\n        className,\n      )}\n      {...props}\n    >\n      {children}\n      <ChevronDown className=\"size-4\" />\n    </Menu.Trigger>\n  );\n}\n\nexport function Portal(props: Menu.Portal.Props) {\n  return <Menu.Portal {...props} />;\n}\n\nexport function Positioner({ className, ...props }: Menu.Positioner.Props) {\n  return <Menu.Positioner className={clsx('outline-hidden', className)} {...props} />;\n}\n\nexport function Popup({ className, ...props }: Menu.Popup.Props) {\n  return (\n    <Menu.Popup\n      className={clsx(\n        'origin-[var(--transform-origin)] rounded-md bg-[canvas] py-1 text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 transition-[transform,scale,opacity] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function Arrow({ className, ...props }: Menu.Arrow.Props) {\n  return (\n    <Menu.Arrow\n      className={clsx(\n        'data-[side=bottom]:top-[-8px] data-[side=left]:right-[-13px] data-[side=left]:rotate-90 data-[side=right]:left-[-13px] data-[side=right]:-rotate-90 data-[side=top]:bottom-[-8px] data-[side=top]:rotate-180',\n        className,\n      )}\n      {...props}\n    >\n      <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\">\n        <path\n          d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n          className=\"fill-[canvas]\"\n        />\n        <path\n          d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n          className=\"fill-gray-200 dark:fill-none\"\n        />\n        <path\n          d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n          className=\"dark:fill-gray-300\"\n        />\n      </svg>\n    </Menu.Arrow>\n  );\n}\n\nexport function Item({ className, ...props }: Menu.Item.Props) {\n  return (\n    <Menu.Item\n      className={clsx(\n        'flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-sm data-[highlighted]:before:bg-gray-900 data-[popup-open]:relative data-[popup-open]:z-0 data-[popup-open]:before:absolute data-[popup-open]:before:inset-x-1 data-[popup-open]:before:inset-y-0 data-[popup-open]:before:z-[-1] data-[popup-open]:before:rounded-sm data-[popup-open]:before:bg-gray-100 data-[highlighted]:data-[popup-open]:before:bg-gray-900',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function SubmenuRoot(props: Menu.SubmenuRoot.Props) {\n  return <Menu.SubmenuRoot {...props} />;\n}\n\nexport function SubmenuTrigger({ className, children, ...props }: Menu.SubmenuTrigger.Props) {\n  return (\n    <Menu.SubmenuTrigger\n      className={clsx(\n        'flex cursor-default items-center justify-between gap-4 py-2 pr-4 pl-4 text-sm leading-4 outline-hidden select-none data-[highlighted]:relative data-[highlighted]:z-0 data-[highlighted]:text-gray-50 data-[highlighted]:before:absolute data-[highlighted]:before:inset-x-1 data-[highlighted]:before:inset-y-0 data-[highlighted]:before:z-[-1] data-[highlighted]:before:rounded-sm data-[highlighted]:before:bg-gray-900 data-[popup-open]:relative data-[popup-open]:z-0 data-[popup-open]:before:absolute data-[popup-open]:before:inset-x-1 data-[popup-open]:before:inset-y-0 data-[popup-open]:before:z-[-1] data-[popup-open]:before:rounded-sm data-[popup-open]:before:bg-gray-100 data-[highlighted]:data-[popup-open]:before:bg-gray-900',\n        className,\n      )}\n      {...props}\n    >\n      {children}\n      <ChevronRight className=\"size-4\" />\n    </Menu.SubmenuTrigger>\n  );\n}\n\nexport function Separator({ className, ...props }: Menu.Separator.Props) {\n  return <Menu.Separator className={clsx('mx-4 my-1.5 h-px bg-gray-200', className)} {...props} />;\n}\n"
  },
  {
    "path": "examples/tanstack-start-tailwind-css/src/components/navigation-menu.tsx",
    "content": "import clsx from 'clsx';\nimport { NavigationMenu } from '@base-ui/react/navigation-menu';\nimport { ChevronDown } from 'lucide-react';\n\nexport function Root({ className, ...props }: NavigationMenu.Root.Props) {\n  return (\n    <NavigationMenu.Root\n      className={clsx(\n        'max-w-max rounded-lg border border-gray-200 bg-gray-50 p-1 text-gray-900 flex items-center',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function Trigger({ className, ...props }: NavigationMenu.Trigger.Props) {\n  return (\n    <NavigationMenu.Trigger\n      className={clsx(\n        'box-border flex items-center justify-center gap-1.5',\n        'px-2 xs:px-3.5 py-0.5 m-0 rounded-sm bg-gray-50 text-gray-900 font-medium',\n        'text-base leading-6 select-none no-underline',\n        'hover:bg-gray-100 active:bg-gray-100 data-[popup-open]:bg-gray-100',\n        'focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 focus-visible:relative',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function Icon({ className, ...props }: NavigationMenu.Icon.Props) {\n  return (\n    <NavigationMenu.Icon\n      className={clsx(\n        'transition-transform duration-200 ease-in-out data-[popup-open]:rotate-180',\n        className,\n      )}\n      {...props}\n    >\n      <ChevronDown className=\"size-4\" />\n    </NavigationMenu.Icon>\n  );\n}\n\nexport function Content({ className, ...props }: NavigationMenu.Content.Props) {\n  return (\n    <NavigationMenu.Content\n      className={clsx(\n        'h-full p-4 xs:w-max xs:min-w-[400px] xs:w-max',\n        'transition-[opacity,transform,translate] duration-[var(--duration)] ease-[var(--easing)]',\n        'data-[starting-style]:opacity-0 data-[ending-style]:opacity-0',\n        'data-[starting-style]:data-[activation-direction=left]:translate-x-[-50%]',\n        'data-[starting-style]:data-[activation-direction=right]:translate-x-[50%]',\n        'data-[ending-style]:data-[activation-direction=left]:translate-x-[50%]',\n        'data-[ending-style]:data-[activation-direction=right]:translate-x-[-50%]',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nconst cardLinkStyles = [\n  'block rounded-md p-2 xs:p-3 no-underline text-inherit',\n  'hover:bg-gray-100 focus-visible:relative focus-visible:outline-2',\n  'focus-visible:-outline-offset-1 focus-visible:outline-blue-800',\n];\n\nconst textLinkStyles = [\n  'box-border flex items-center justify-center gap-1.5',\n  'px-2 xs:px-3.5 py-0.5 m-0 rounded-sm bg-gray-50 text-gray-900 font-medium',\n  'text-base leading-6 select-none no-underline',\n  'hover:bg-gray-100 active:bg-gray-100 data-[popup-open]:bg-gray-100',\n  'focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 focus-visible:relative',\n];\n\nexport function Link({\n  className,\n  variant = 'text',\n  ...props\n}: NavigationMenu.Link.Props & { variant?: 'text' | 'card' }) {\n  return (\n    <NavigationMenu.Link\n      className={clsx(variant === 'text' ? textLinkStyles : cardLinkStyles, className)}\n      {...props}\n    />\n  );\n}\n\nexport function Item(props: NavigationMenu.Item.Props) {\n  return <NavigationMenu.Item {...props} />;\n}\n\nexport function List({ className, ...props }: NavigationMenu.List.Props) {\n  return <NavigationMenu.List className={clsx('relative flex', className)} {...props} />;\n}\n\nexport function Portal(props: NavigationMenu.Portal.Props) {\n  return <NavigationMenu.Portal {...props} />;\n}\n\nexport function Positioner({ className, style, ...props }: NavigationMenu.Positioner.Props) {\n  return (\n    <NavigationMenu.Positioner\n      className={clsx(\n        'box-border h-[var(--positioner-height)] w-[var(--positioner-width)] max-w-[var(--available-width)]',\n        'transition-[top,left,right,bottom] duration-[var(--duration)] ease-[var(--easing)]',\n        'before:absolute before:content-[\"\"]',\n        'data-[instant]:transition-none',\n        'data-[side=bottom]:before:top-[-10px] data-[side=bottom]:before:right-0 data-[side=bottom]:before:left-0 data-[side=bottom]:before:h-2.5',\n        'data-[side=left]:before:top-0 data-[side=left]:before:right-[-10px] data-[side=left]:before:bottom-0 data-[side=left]:before:w-2.5',\n        'data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:left-[-10px] data-[side=right]:before:w-2.5',\n        'data-[side=top]:before:right-0 data-[side=top]:before:bottom-[-10px] data-[side=top]:before:left-0 data-[side=top]:before:h-2.5',\n        className,\n      )}\n      style={{\n        ['--duration' as string]: '0.35s',\n        ['--easing' as string]: 'cubic-bezier(0.22, 1, 0.36, 1)',\n        ...style,\n      }}\n      {...props}\n    />\n  );\n}\n\nexport function Popup({ className, ...props }: NavigationMenu.Popup.Props) {\n  return (\n    <NavigationMenu.Popup\n      className={clsx(\n        'data-[ending-style]:easing-[ease] relative h-[var(--popup-height)] origin-[var(--transform-origin)] rounded-lg bg-[canvas] text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 transition-[opacity,transform,width,height,scale,translate] duration-[var(--duration)] ease-[var(--easing)] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[ending-style]:duration-150 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 w-[var(--popup-width)] xs:w-[var(--popup-width)] dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function Arrow({ className, ...props }: NavigationMenu.Arrow.Props) {\n  return (\n    <NavigationMenu.Arrow\n      className={clsx(\n        'flex transition-[left] duration-[var(--duration)] ease-[var(--easing)] data-[side=bottom]:top-[-8px] data-[side=left]:right-[-13px] data-[side=left]:rotate-90 data-[side=right]:left-[-13px] data-[side=right]:-rotate-90 data-[side=top]:bottom-[-8px] data-[side=top]:rotate-180',\n        className,\n      )}\n      {...props}\n    >\n      <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\">\n        <path\n          d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n          className=\"fill-[canvas]\"\n        />\n        <path\n          d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n          className=\"fill-gray-200 dark:fill-none\"\n        />\n        <path\n          d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n          className=\"dark:fill-gray-300\"\n        />\n      </svg>\n    </NavigationMenu.Arrow>\n  );\n}\n\nexport function Viewport({ className, ...props }: NavigationMenu.Viewport.Props) {\n  return (\n    <NavigationMenu.Viewport\n      className={clsx('relative h-full w-full overflow-hidden', className)}\n      {...props}\n    />\n  );\n}\n"
  },
  {
    "path": "examples/tanstack-start-tailwind-css/src/components/popover.tsx",
    "content": "import clsx from 'clsx';\nimport { Popover } from '@base-ui/react/popover';\n\nexport function Root(props: Popover.Root.Props) {\n  return <Popover.Root {...props} />;\n}\n\nexport function Trigger({ className, ...props }: Popover.Trigger.Props) {\n  return (\n    <Popover.Trigger\n      className={clsx(\n        'flex size-10 items-center justify-center rounded-md border border-gray-200 bg-gray-50 text-gray-900 select-none hover:bg-gray-100 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-100 data-[popup-open]:bg-gray-100\"',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function Portal(props: Popover.Portal.Props) {\n  return <Popover.Portal {...props} />;\n}\n\nexport function Positioner(props: Popover.Positioner.Props) {\n  return <Popover.Positioner {...props} />;\n}\n\nexport function Popup({ className, ...props }: Popover.Popup.Props) {\n  return (\n    <Popover.Popup\n      className={clsx(\n        'origin-[var(--transform-origin)] rounded-lg bg-[canvas] px-6 py-4 text-gray-900 shadow-lg shadow-gray-200 outline-1 outline-gray-200 transition-[transform,scale,opacity] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function Arrow({ className, ...props }: Popover.Arrow.Props) {\n  return (\n    <Popover.Arrow\n      className={clsx(\n        'data-[side=bottom]:top-[-8px] data-[side=left]:right-[-13px] data-[side=left]:rotate-90 data-[side=right]:left-[-13px] data-[side=right]:-rotate-90 data-[side=top]:bottom-[-8px] data-[side=top]:rotate-180',\n        className,\n      )}\n      {...props}\n    >\n      <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\">\n        <path\n          d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n          className=\"fill-[canvas]\"\n        />\n        <path\n          d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n          className=\"fill-gray-200 dark:fill-none\"\n        />\n        <path\n          d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n          className=\"dark:fill-gray-300\"\n        />\n      </svg>\n    </Popover.Arrow>\n  );\n}\n\nexport function Title({ className, ...props }: Popover.Title.Props) {\n  return <Popover.Title className={clsx('text-base font-medium', className)} {...props} />;\n}\n\nexport function Description({ className, ...props }: Popover.Description.Props) {\n  return <Popover.Description className={clsx('text-base text-gray-600', className)} {...props} />;\n}\n"
  },
  {
    "path": "examples/tanstack-start-tailwind-css/src/components/switch.tsx",
    "content": "import clsx from 'clsx';\nimport { Switch } from '@base-ui/react/switch';\n\nexport function Root({ className, ...props }: Switch.Root.Props) {\n  return (\n    <Switch.Root\n      className={clsx(\n        'relative flex h-6 w-10 rounded-full bg-gradient-to-r from-gray-700 from-35% to-gray-200 to-65% bg-[length:6.5rem_100%] bg-[100%_0%] bg-no-repeat p-px shadow-[inset_0_1.5px_2px] shadow-gray-200 outline-1 -outline-offset-1 outline-gray-200 transition-[background-position,box-shadow] duration-[125ms] ease-[cubic-bezier(0.26,0.75,0.38,0.45)] before:absolute before:rounded-full before:outline-offset-2 before:outline-blue-800 focus-visible:before:inset-0 focus-visible:before:outline focus-visible:before:outline-2 active:bg-gray-100 data-[checked]:bg-[0%_0%] data-[checked]:active:bg-gray-500 dark:from-gray-500 dark:shadow-black/75 dark:outline-white/15 dark:data-[checked]:shadow-none',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function Thumb({ className, ...props }: Switch.Thumb.Props) {\n  return (\n    <Switch.Thumb\n      className={clsx(\n        'aspect-square h-full rounded-full bg-white shadow-[0_0_1px_1px,0_1px_1px,1px_2px_4px_-1px] shadow-gray-100 transition-transform duration-150 data-[checked]:translate-x-4 dark:shadow-black/25',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n"
  },
  {
    "path": "examples/tanstack-start-tailwind-css/src/components/toggle.tsx",
    "content": "import * as React from 'react';\nimport clsx from 'clsx';\nimport { Toggle as BaseToggle } from '@base-ui/react/toggle';\n\nexport const Toggle = React.forwardRef<HTMLButtonElement, BaseToggle.Props>(function (\n  { className, ...props }: BaseToggle.Props,\n  forwardedRef: React.ForwardedRef<HTMLButtonElement>,\n) {\n  return (\n    <BaseToggle\n      ref={forwardedRef}\n      className={clsx(\n        'flex size-8 items-center justify-center rounded-sm text-gray-600 select-none hover:bg-gray-100 focus-visible:bg-none focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-blue-800 active:bg-gray-200 data-[pressed]:text-gray-900',\n        className,\n      )}\n      {...props}\n    />\n  );\n});\n\nToggle.displayName = 'Toggle';\n"
  },
  {
    "path": "examples/tanstack-start-tailwind-css/src/routeTree.gen.ts",
    "content": "/* eslint-disable */\n\n// @ts-nocheck\n\n// noinspection JSUnusedGlobalSymbols\n\n// This file was automatically generated by TanStack Router.\n// You should NOT make any changes in this file as it will be overwritten.\n// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.\n\nimport { Route as rootRouteImport } from './routes/__root'\nimport { Route as ComboboxServerFnRouteImport } from './routes/combobox-server-fn'\nimport { Route as IndexRouteImport } from './routes/index'\n\nconst ComboboxServerFnRoute = ComboboxServerFnRouteImport.update({\n  id: '/combobox-server-fn',\n  path: '/combobox-server-fn',\n  getParentRoute: () => rootRouteImport,\n} as any)\nconst IndexRoute = IndexRouteImport.update({\n  id: '/',\n  path: '/',\n  getParentRoute: () => rootRouteImport,\n} as any)\n\nexport interface FileRoutesByFullPath {\n  '/': typeof IndexRoute\n  '/combobox-server-fn': typeof ComboboxServerFnRoute\n}\nexport interface FileRoutesByTo {\n  '/': typeof IndexRoute\n  '/combobox-server-fn': typeof ComboboxServerFnRoute\n}\nexport interface FileRoutesById {\n  __root__: typeof rootRouteImport\n  '/': typeof IndexRoute\n  '/combobox-server-fn': typeof ComboboxServerFnRoute\n}\nexport interface FileRouteTypes {\n  fileRoutesByFullPath: FileRoutesByFullPath\n  fullPaths: '/' | '/combobox-server-fn'\n  fileRoutesByTo: FileRoutesByTo\n  to: '/' | '/combobox-server-fn'\n  id: '__root__' | '/' | '/combobox-server-fn'\n  fileRoutesById: FileRoutesById\n}\nexport interface RootRouteChildren {\n  IndexRoute: typeof IndexRoute\n  ComboboxServerFnRoute: typeof ComboboxServerFnRoute\n}\n\ndeclare module '@tanstack/react-router' {\n  interface FileRoutesByPath {\n    '/combobox-server-fn': {\n      id: '/combobox-server-fn'\n      path: '/combobox-server-fn'\n      fullPath: '/combobox-server-fn'\n      preLoaderRoute: typeof ComboboxServerFnRouteImport\n      parentRoute: typeof rootRouteImport\n    }\n    '/': {\n      id: '/'\n      path: '/'\n      fullPath: '/'\n      preLoaderRoute: typeof IndexRouteImport\n      parentRoute: typeof rootRouteImport\n    }\n  }\n}\n\nconst rootRouteChildren: RootRouteChildren = {\n  IndexRoute: IndexRoute,\n  ComboboxServerFnRoute: ComboboxServerFnRoute,\n}\nexport const routeTree = rootRouteImport\n  ._addFileChildren(rootRouteChildren)\n  ._addFileTypes<FileRouteTypes>()\n\nimport type { getRouter } from './router.tsx'\nimport type { createStart } from '@tanstack/react-start'\ndeclare module '@tanstack/react-start' {\n  interface Register {\n    ssr: true\n    router: Awaited<ReturnType<typeof getRouter>>\n  }\n}\n"
  },
  {
    "path": "examples/tanstack-start-tailwind-css/src/router.tsx",
    "content": "import { createRouter } from '@tanstack/react-router';\n\n// Import the generated route tree\nimport { routeTree } from './routeTree.gen';\n\n// Create a new router instance\nexport const getRouter = () => {\n  const router = createRouter({\n    routeTree,\n    scrollRestoration: true,\n    defaultPreloadStaleTime: 0,\n    defaultPendingMs: 200,\n  });\n\n  return router;\n};\n"
  },
  {
    "path": "examples/tanstack-start-tailwind-css/src/routes/__root.tsx",
    "content": "import { HeadContent, Scripts, createRootRoute } from '@tanstack/react-router';\nimport { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools';\nimport { TanStackDevtools } from '@tanstack/react-devtools';\n\nimport appCss from '../styles.css?url';\n\nexport const Route = createRootRoute({\n  head: () => ({\n    meta: [\n      {\n        charSet: 'utf-8',\n      },\n      {\n        name: 'viewport',\n        content: 'width=device-width, initial-scale=1',\n      },\n      {\n        title: 'Base UI + TanStack Start',\n      },\n    ],\n    links: [\n      {\n        rel: 'stylesheet',\n        href: appCss,\n      },\n    ],\n  }),\n\n  shellComponent: RootDocument,\n});\n\nfunction RootDocument({ children }: { children: React.ReactNode }) {\n  return (\n    <html lang=\"en\">\n      <head>\n        <HeadContent />\n      </head>\n      <body>\n        <div className=\"root\">{children}</div>\n\n        <TanStackDevtools\n          config={{\n            hideUntilHover: false,\n            position: 'bottom-right',\n          }}\n          plugins={[\n            {\n              name: 'Tanstack Router',\n              render: <TanStackRouterDevtoolsPanel />,\n            },\n          ]}\n        />\n        <Scripts />\n      </body>\n    </html>\n  );\n}\n"
  },
  {
    "path": "examples/tanstack-start-tailwind-css/src/routes/combobox-server-fn.tsx",
    "content": "import { Link as RouterLink, createFileRoute } from '@tanstack/react-router';\nimport { createServerFn } from '@tanstack/react-start';\nimport { LoaderCircle } from 'lucide-react';\nimport clsx from 'clsx';\nimport * as Combobox from '@/components/combobox';\nimport { Link, styles as linkStyles } from '@/components/link';\n\nconst getData = createServerFn({\n  method: 'GET',\n}).handler(async () => {\n  // Mimic server response time\n  await new Promise((resolve) => {\n    setTimeout(resolve, 1000);\n  });\n\n  return [\n    { value: 'js', label: 'JavaScript' },\n    { value: 'ts', label: 'TypeScript' },\n    { value: 'py', label: 'Python' },\n    { value: 'java', label: 'Java' },\n    { value: 'cpp', label: 'C++' },\n    { value: 'cs', label: 'C#' },\n    { value: 'php', label: 'PHP' },\n    { value: 'ruby', label: 'Ruby' },\n    { value: 'go', label: 'Go' },\n    { value: 'rust', label: 'Rust' },\n    { value: 'swift', label: 'Swift' },\n  ];\n});\n\nfunction Loading() {\n  return (\n    <div className=\"min-h-dvh flex justify-center items-center\">\n      <LoaderCircle className=\"size-8 animate-spin\" />\n    </div>\n  );\n}\n\nexport const Route = createFileRoute('/combobox-server-fn')({\n  component: RouteComponent,\n  loader: async () => await getData(),\n  pendingComponent: Loading,\n});\n\nfunction RouteComponent() {\n  const items = Route.useLoaderData();\n\n  return (\n    <div className=\"min-h-dvh flex justify-center items-center\">\n      <main className=\"flex flex-col items-start gap-4\">\n        <p className=\"text-sm\">\n          The combobox <code>items</code> are loaded <br />\n          using a{' '}\n          <Link href=\"https://tanstack.com/start/latest/docs/framework/react/guide/server-functions\">\n            Server Function\n          </Link>\n          .\n        </p>\n        <Combobox.Root items={items} onValueChange={console.log}>\n          <div className=\"relative inline-flex flex-col gap-1 text-sm leading-5 font-medium text-gray-900\">\n            <Combobox.Input placeholder=\"e.g. TypeScript\" />\n            <div className=\"absolute right-2 bottom-0 flex h-10 items-center justify-center text-gray-600\">\n              <Combobox.Clear />\n              <Combobox.Trigger />\n            </div>\n          </div>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.Empty>No matches</Combobox.Empty>\n                <Combobox.List>\n                  {(lang) => {\n                    return (\n                      <Combobox.Item key={lang.value} value={lang}>\n                        <Combobox.ItemIndicator />\n                        <div className=\"col-start-2\">{lang.label}</div>\n                      </Combobox.Item>\n                    );\n                  }}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>\n        <RouterLink className={clsx(linkStyles, 'text-sm')} to=\"/\">\n          Go back\n        </RouterLink>\n      </main>\n    </div>\n  );\n}\n"
  },
  {
    "path": "examples/tanstack-start-tailwind-css/src/routes/index.tsx",
    "content": "import { Link as RouterLink, createFileRoute } from '@tanstack/react-router';\nimport { Bell, Heart, Plus } from 'lucide-react';\nimport clsx from 'clsx';\nimport { Input } from '@/components/input';\nimport { Link } from '@/components/link';\nimport { Toggle } from '@/components/toggle';\nimport * as Dialog from '@/components/dialog';\nimport * as Menu from '@/components/menu';\nimport * as NavigationMenu from '@/components/navigation-menu';\nimport * as Popover from '@/components/popover';\n\nexport const Route = createFileRoute('/')({ component: App });\n\nfunction App() {\n  return (\n    <div className=\"min-h-dvh flex justify-center items-center\">\n      <main className=\"grid grid-cols-2 gap-4\">\n        <div className=\"w-[320px]\">\n          <h2 className=\"my-1 text-lg font-bold text-balance\">Base UI + TanStack Start</h2>\n          <p className=\"text-sm\">\n            This is a{' '}\n            <Link href=\"https://tanstack.com/start/latest/docs/framework/react/overview\">\n              TanStack Start\n            </Link>{' '}\n            app with <Link href=\"https://base-ui.com\">Base UI components</Link> and{' '}\n            <Link href=\"https://tailwindcss.com/\">Tailwind CSS</Link>.\n          </p>\n        </div>\n        <div className=\"w-[320px] flex flex-col gap-3\">\n          <div className=\"flex items-start gap-3\">\n            <Popover.Root>\n              <Popover.Trigger>\n                <Bell className=\"size-4\" />\n              </Popover.Trigger>\n              <Popover.Portal>\n                <Popover.Positioner side=\"top\" sideOffset={8}>\n                  <Popover.Popup>\n                    <Popover.Arrow />\n                    <Popover.Title>Notifications</Popover.Title>\n                    <Popover.Description>You are all caught up. Good job!</Popover.Description>\n                  </Popover.Popup>\n                </Popover.Positioner>\n              </Popover.Portal>\n            </Popover.Root>\n\n            <div className=\"flex gap-px rounded-md border border-gray-200 bg-gray-50 p-0.5\">\n              <Toggle\n                aria-label=\"Favorite\"\n                render={(props, state) => {\n                  return (\n                    <button type=\"button\" {...props}>\n                      <Heart className={clsx('size-5', state.pressed && 'fill-current')} />\n                    </button>\n                  );\n                }}\n              />\n            </div>\n\n            <Dialog.Root>\n              <Dialog.Trigger className=\"pl-2\">\n                <Plus className=\"size-4\" /> New project\n              </Dialog.Trigger>\n              <Dialog.Portal>\n                <Dialog.Backdrop />\n                <Dialog.Popup className=\"flex flex-col gap-4 items-start\">\n                  <Dialog.Title className=\"!text-base\">Project name</Dialog.Title>\n                  <Input placeholder=\"e.g. My Design System\" defaultValue=\"\" />\n                  <Dialog.Close className=\"mt-1 self-end\">Next</Dialog.Close>\n                </Dialog.Popup>\n              </Dialog.Portal>\n            </Dialog.Root>\n          </div>\n          <div className=\"flex gap-3\">\n            <NavigationMenu.Root>\n              <NavigationMenu.List className=\"gap-1\">\n                <NavigationMenu.Item>\n                  <NavigationMenu.Link href=\"https://github.com/mui/base-ui\" target=\"_blank\">\n                    GitHub\n                  </NavigationMenu.Link>\n                </NavigationMenu.Item>\n\n                <NavigationMenu.Item>\n                  <NavigationMenu.Trigger>\n                    More\n                    <NavigationMenu.Icon className=\"transition-transform duration-200 ease-in-out data-[popup-open]:rotate-180\" />\n                  </NavigationMenu.Trigger>\n\n                  <NavigationMenu.Content>\n                    <ul className=\"flex max-w-[320px] flex-col justify-center items-stretch gap-1\">\n                      <li>\n                        <NavigationMenu.Link\n                          variant=\"card\"\n                          render={<RouterLink to=\"/combobox-server-fn\" />}\n                        >\n                          <h3 className=\"m-0 mb-1 text-base leading-5 font-medium\">\n                            Combobox Example\n                          </h3>\n                          <p className=\"m-0 text-sm leading-5 text-gray-500\">\n                            An example using TanStack Start Server Functions to load combobox items.\n                          </p>\n                        </NavigationMenu.Link>\n                      </li>\n                      <li>\n                        <NavigationMenu.Link\n                          variant=\"card\"\n                          href=\"https://base-ui.com/react/handbook/forms#tanstack-form\"\n                          target=\"_blank\"\n                        >\n                          <h3 className=\"m-0 mb-1 text-base leading-5 font-medium\">\n                            TanStack Form Example\n                          </h3>\n                          <p className=\"m-0 text-sm leading-5 text-gray-500\">\n                            A guide to integrating Base UI components and TanStack Form.\n                          </p>\n                        </NavigationMenu.Link>\n                      </li>\n                    </ul>\n                  </NavigationMenu.Content>\n                </NavigationMenu.Item>\n              </NavigationMenu.List>\n\n              <NavigationMenu.Portal>\n                <NavigationMenu.Positioner\n                  sideOffset={10}\n                  collisionPadding={{ top: 5, bottom: 5, left: 20, right: 20 }}\n                  collisionAvoidance={{ side: 'none' }}\n                >\n                  <NavigationMenu.Popup>\n                    <NavigationMenu.Arrow>X</NavigationMenu.Arrow>\n                    <NavigationMenu.Viewport />\n                  </NavigationMenu.Popup>\n                </NavigationMenu.Positioner>\n              </NavigationMenu.Portal>\n            </NavigationMenu.Root>\n\n            <Menu.Root>\n              <Menu.Trigger>Song</Menu.Trigger>\n              <Menu.Portal>\n                <Menu.Positioner sideOffset={8}>\n                  <Menu.Popup>\n                    <Menu.Arrow />\n                    <Menu.Item>Add to Library</Menu.Item>\n                    <Menu.Item>Add to Playlist</Menu.Item>\n                    <Menu.Separator />\n                    <Menu.Item>Play Next</Menu.Item>\n                    <Menu.Item>Play Last</Menu.Item>\n                    <Menu.Separator />\n                    <Menu.Item>Favorite</Menu.Item>\n                    <Menu.SubmenuRoot>\n                      <Menu.SubmenuTrigger>Share</Menu.SubmenuTrigger>\n                      <Menu.Portal>\n                        <Menu.Positioner alignOffset={-4} sideOffset={-4}>\n                          <Menu.Popup>\n                            <Menu.Item>AirDrop</Menu.Item>\n                            <Menu.Item>Email</Menu.Item>\n                            <Menu.Item>Messages</Menu.Item>\n                          </Menu.Popup>\n                        </Menu.Positioner>\n                      </Menu.Portal>\n                    </Menu.SubmenuRoot>\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            </Menu.Root>\n          </div>\n        </div>\n      </main>\n    </div>\n  );\n}\n"
  },
  {
    "path": "examples/tanstack-start-tailwind-css/src/styles.css",
    "content": "@layer base;\n@layer preflight;\n@layer theme;\n@layer components;\n@layer utilities;\n\n@import 'tailwindcss/preflight' layer(preflight);\n@import 'tailwindcss/theme' layer(theme);\n@import 'tailwindcss/utilities' layer(utilities);\n\n:root {\n  @media (prefers-color-scheme: dark) {\n    --color-gray-50: oklch(17% 1% 264deg);\n    --color-gray-100: oklch(28% 3% 264deg / 65%);\n    --color-gray-200: oklch(31% 3% 264deg / 80%);\n    --color-gray-300: oklch(35% 3% 264deg / 80%);\n    --color-gray-400: oklch(47% 3.5% 264deg / 80%);\n    --color-gray-500: oklch(64% 4% 264deg / 80%);\n    --color-gray-600: oklch(82% 4% 264deg / 80%);\n    --color-gray-700: oklch(92% 4.5% 264deg / 80%);\n    --color-gray-800: oklch(93% 3.5% 264deg / 85%);\n    --color-gray-900: oklch(95% 2% 264deg / 90%);\n    --color-gray-950: oklch(94% 1.5% 264deg / 95%);\n\n    --color-blue: oklch(69% 50% 264deg);\n    --color-red: oklch(80% 55% 31deg);\n  }\n}\n\n@theme {\n  --color-*: initial;\n  --color-white: white;\n  --color-black: black;\n\n  --color-gray-50: oklch(98.42% 0.0034 247.86deg);\n  --color-gray-100: oklch(12% 9.5% 264deg / 5%);\n  --color-gray-200: oklch(12% 9% 264deg / 8%);\n  --color-gray-300: oklch(12% 8.5% 264deg / 17%);\n  --color-gray-400: oklch(12% 8% 264deg / 38%);\n  --color-gray-500: oklch(12% 7.5% 264deg / 50%);\n  --color-gray-600: oklch(12% 7% 264deg / 67%);\n  --color-gray-700: oklch(12% 6% 264deg / 77%);\n  --color-gray-800: oklch(12% 5% 264deg / 85%);\n  --color-gray-900: oklch(12% 5% 264deg / 90%);\n  --color-gray-950: oklch(12% 5% 264deg / 95%);\n\n  --color-blue: oklch(45% 50% 264deg);\n  --color-red: oklch(50% 55% 31deg);\n\n  --font-mono: 'SF Mono', 'Menlo', 'DejaVu Sans Mono', 'Consolas', 'Inconsolata', monospace;\n  --font-sans:\n    system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI (Custom)', Roboto, 'Helvetica Neue',\n    'Open Sans (Custom)', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji';\n\n  --default-font-family: var(--font-sans);\n\n  --text-sm: 0.875rem;\n  --text-sm--line-height: 1.25rem;\n  --text-sm--letter-spacing: 0.016em;\n\n  --text-md: 0.9375rem;\n  --text-md--line-height: 1.375rem;\n  --text-md--letter-spacing: 0.016em;\n\n  --text-base: 1rem;\n  --text-base--line-height: 1.5rem;\n  --text-base--letter-spacing: 0em;\n\n  --text-lg: 1.125rem;\n  --text-lg--line-height: 1.75rem;\n  --text-lg--letter-spacing: -0.0025em;\n\n  --text-xl: 1.3125rem;\n  --text-xl--line-height: 1.625rem;\n  --text-xl--letter-spacing: -0.005em;\n}\n\n@layer base {\n  html {\n    color-scheme: light dark;\n  }\n\n  body {\n    font-synthesis: none;\n    -webkit-font-smoothing: antialiased;\n    -moz-osx-font-smoothing: grayscale;\n  }\n\n  .root {\n    isolation: isolate;\n  }\n}\n"
  },
  {
    "path": "examples/tanstack-start-tailwind-css/tsconfig.json",
    "content": "{\n  \"include\": [\"**/*.ts\", \"**/*.tsx\", \"eslint.config.js\", \"prettier.config.js\", \"vite.config.js\"],\n\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"jsx\": \"react-jsx\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"ES2022\", \"DOM\", \"DOM.Iterable\"],\n    \"types\": [\"vite/client\"],\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"verbatimModuleSyntax\": false,\n    \"noEmit\": true,\n\n    /* Linting */\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"noUncheckedSideEffectImports\": true,\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    }\n  }\n}\n"
  },
  {
    "path": "examples/tanstack-start-tailwind-css/vite.config.ts",
    "content": "import { defineConfig } from 'vite';\nimport { devtools } from '@tanstack/devtools-vite';\nimport { tanstackStart } from '@tanstack/react-start/plugin/vite';\nimport tailwindcss from '@tailwindcss/vite';\nimport viteReact from '@vitejs/plugin-react';\nimport viteTsConfigPaths from 'vite-tsconfig-paths';\n\nconst config = defineConfig({\n  plugins: [\n    devtools(),\n    // this is the plugin that enables path aliases\n    viteTsConfigPaths({\n      projects: ['./tsconfig.json'],\n    }),\n    tanstackStart(),\n    viteReact(),\n    tailwindcss(),\n  ],\n});\n\nexport default config;\n"
  },
  {
    "path": "examples/vite-css/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "examples/vite-css/README.md",
    "content": "# React + TypeScript + Vite\n\nThis template provides a minimal setup to get React + Base UI (styled with plain CSS) working in Vite with HMR and some ESLint rules.\n\n## Getting Started\n\n### Prerequisites\n\n- [Node.js](https://nodejs.org/) (version 20.19+ or 22.12+ is required)\n- [pnpm](https://pnpm.io/) (or use npm/yarn if preferred)\n\n### Install dependencies\n\n```bash\npnpm install\n```\n\n### Run the development server\n\n```bash\npnpm dev\n```\n\nThis will start the Vite development server. Open [http://localhost:5173](http://localhost:5173) to view the app in your browser.\n\n### Build for production\n\n```bash\npnpm build\n```\n\n### Preview the production build\n\n```bash\npnpm preview\n```\n\nMake sure to build the project for production with `pnpm build` first.\n\n## Linting\n\nTo run ESLint:\n\n```bash\npnpm lint\n```\n"
  },
  {
    "path": "examples/vite-css/eslint.config.js",
    "content": "import js from '@eslint/js';\nimport globals from 'globals';\nimport reactHooks from 'eslint-plugin-react-hooks';\nimport { reactRefresh } from 'eslint-plugin-react-refresh';\nimport tseslint from 'typescript-eslint';\n\nexport default tseslint.config(\n  { ignores: ['dist'] },\n  {\n    extends: [js.configs.recommended, ...tseslint.configs.recommended],\n    files: ['**/*.{ts,tsx}'],\n    languageOptions: {\n      ecmaVersion: 2020,\n      globals: globals.browser,\n    },\n    plugins: {\n      'react-hooks': reactHooks,\n      'react-refresh': reactRefresh.plugin,\n    },\n    rules: {\n      ...reactHooks.configs.recommended.rules,\n      'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],\n    },\n  },\n);\n"
  },
  {
    "path": "examples/vite-css/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/vite.svg\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Vite + React + Base UI + TS</title>\n  </head>\n\n  <body>\n    <div id=\"root\"></div>\n    <script type=\"module\" src=\"/src/main.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/vite-css/package.json",
    "content": "{\n  \"name\": \"vite-css-base-ui-example\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc -b && vite build\",\n    \"lint\": \"eslint .\",\n    \"preview\": \"vite preview\"\n  },\n  \"dependencies\": {\n    \"@base-ui/react\": \"latest\",\n    \"clsx\": \"^2.1.1\",\n    \"react\": \"^19.1.1\",\n    \"react-dom\": \"^19.1.1\"\n  },\n  \"devDependencies\": {\n    \"@eslint/js\": \"^9.32.0\",\n    \"@types/react\": \"^19.1.9\",\n    \"@types/react-dom\": \"^19.1.7\",\n    \"@vitejs/plugin-react\": \"^5.0.0\",\n    \"eslint\": \"^10.0.2\",\n    \"eslint-plugin-react-hooks\": \"^5.2.0\",\n    \"eslint-plugin-react-refresh\": \"^0.5.0\",\n    \"globals\": \"^16.3.0\",\n    \"typescript\": \"^5.9.2\",\n    \"typescript-eslint\": \"^8.39.0\",\n    \"vite\": \"^7.1.1\"\n  }\n}\n"
  },
  {
    "path": "examples/vite-css/src/App.css",
    "content": "#root {\n  max-width: 1280px;\n  margin: 0 auto;\n  padding: 2rem;\n  text-align: center;\n}\n\n.logo {\n  height: 6em;\n  padding: 1.5em;\n  will-change: filter;\n  transition: filter 300ms;\n}\n\n.logo.vite:hover {\n  filter: drop-shadow(0 0 2em #646cffaa);\n}\n\n.logo.react:hover {\n  filter: drop-shadow(0 0 2em #61dafbaa);\n}\n\n.logo.base-ui:hover {\n  filter: drop-shadow(0 0 2em #626262aa);\n}\n\n@keyframes logo-spin {\n  from {\n    transform: rotate(0deg);\n  }\n\n  to {\n    transform: rotate(360deg);\n  }\n}\n\n@media (prefers-reduced-motion: no-preference) {\n  .logo.react {\n    animation: logo-spin infinite 20s linear;\n  }\n}\n\n.card {\n  padding: 2em;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n}\n\n.read-the-docs {\n  color: #888;\n}\n"
  },
  {
    "path": "examples/vite-css/src/App.tsx",
    "content": "import * as React from 'react';\nimport reactLogo from './assets/react.svg';\nimport viteLogo from './assets/vite.svg';\nimport baseUILogo from './assets/base-ui.svg';\nimport { Switch } from './widgets/Switch';\nimport './App.css';\n\nfunction App() {\n  const [count, setCount] = React.useState(0);\n\n  return (\n    <React.Fragment>\n      <div>\n        <a href=\"https://vite.dev\" target=\"_blank\" rel=\"noopener noreferrer\">\n          <img src={viteLogo} className=\"logo vite\" alt=\"Vite logo\" />\n        </a>\n        <a href=\"https://react.dev\" target=\"_blank\" rel=\"noopener noreferrer\">\n          <img src={reactLogo} className=\"logo react\" alt=\"React logo\" />\n        </a>\n        <a href=\"https://base-ui.com\" target=\"_blank\" rel=\"noopener noreferrer\">\n          <img src={baseUILogo} className=\"logo base-ui\" alt=\"Base UI logo\" />\n        </a>\n      </div>\n      <h1>Vite + React + Base UI</h1>\n      <div className=\"card\">\n        <Switch onCheckedChange={() => setCount((c) => c + 1)} />\n        <p>\n          Flicked the Switch {count} time{count !== 1 ? 's' : ''}.\n        </p>\n        <p>\n          Edit <code>src/App.tsx</code> and save to test HMR.\n        </p>\n      </div>\n      <p className=\"read-the-docs\">Click on the Vite, React, and Base UI logos to learn more.</p>\n    </React.Fragment>\n  );\n}\n\nexport default App;\n"
  },
  {
    "path": "examples/vite-css/src/index.css",
    "content": ":root {\n  font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;\n  line-height: 1.5;\n  font-weight: 400;\n\n  color-scheme: light dark;\n  color: rgb(255 255 255 / 0.87);\n  background-color: #242424;\n\n  font-synthesis: none;\n  text-rendering: optimizeLegibility;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n\n  --color-blue: oklch(45% 50% 264deg);\n  --color-red: oklch(50% 55% 31deg);\n\n  --color-gray-50: oklch(98.42% 0.0034 247.86deg);\n  --color-gray-100: oklch(12% 9.5% 264deg / 5%);\n  --color-gray-200: oklch(12% 9% 264deg / 8%);\n  --color-gray-300: oklch(12% 8.5% 264deg / 17%);\n  --color-gray-400: oklch(12% 8% 264deg / 38%);\n  --color-gray-500: oklch(12% 7.5% 264deg / 50%);\n  --color-gray-600: oklch(12% 7% 264deg / 67%);\n  --color-gray-700: oklch(12% 6% 264deg / 77%);\n  --color-gray-800: oklch(12% 5% 264deg / 85%);\n  --color-gray-900: oklch(12% 5% 264deg / 90%);\n  --color-gray-950: oklch(12% 5% 264deg / 95%);\n\n  @media (prefers-color-scheme: dark) {\n    --color-blue: oklch(69% 50% 264deg);\n    --color-red: oklch(80% 55% 31deg);\n\n    --color-gray-50: oklch(17% 1% 264deg);\n    --color-gray-100: oklch(28% 3% 264deg / 65%);\n    --color-gray-200: oklch(31% 3% 264deg / 80%);\n    --color-gray-300: oklch(35% 3% 264deg / 80%);\n    --color-gray-400: oklch(47% 3.5% 264deg / 80%);\n    --color-gray-500: oklch(64% 4% 264deg / 80%);\n    --color-gray-600: oklch(82% 4% 264deg / 80%);\n    --color-gray-700: oklch(92% 4.5% 264deg / 80%);\n    --color-gray-800: oklch(93% 3.5% 264deg / 85%);\n    --color-gray-900: oklch(95% 2% 264deg / 90%);\n    --color-gray-950: oklch(94% 1.5% 264deg / 95%);\n  }\n}\n\na {\n  font-weight: 500;\n  color: #646cff;\n  text-decoration: inherit;\n}\n\na:hover {\n  color: #535bf2;\n}\n\nbody {\n  margin: 0;\n  display: flex;\n  place-items: center;\n  min-width: 320px;\n  min-height: 100vh;\n}\n\nh1 {\n  font-size: 3.2em;\n  line-height: 1.1;\n}\n\nbutton {\n  border-radius: 8px;\n  border: 1px solid transparent;\n  padding: 0.6em 1.2em;\n  font-size: 1em;\n  font-weight: 500;\n  font-family: inherit;\n  background-color: #1a1a1a;\n  cursor: pointer;\n  transition: border-color 0.25s;\n}\n\nbutton:hover {\n  border-color: #646cff;\n}\n\nbutton:focus,\nbutton:focus-visible {\n  outline: 4px auto -webkit-focus-ring-color;\n}\n\n@media (prefers-color-scheme: light) {\n  :root {\n    color: #213547;\n    background-color: #ffffff;\n  }\n\n  a:hover {\n    color: #747bff;\n  }\n\n  button {\n    background-color: #f9f9f9;\n  }\n}\n"
  },
  {
    "path": "examples/vite-css/src/main.tsx",
    "content": "import * as React from 'react';\nimport * as ReactDOM from 'react-dom/client';\nimport App from './App.tsx';\nimport './index.css';\n\nReactDOM.createRoot(document.getElementById('root')!).render(\n  <React.StrictMode>\n    <App />\n  </React.StrictMode>,\n);\n"
  },
  {
    "path": "examples/vite-css/src/vite-env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "examples/vite-css/src/widgets/Switch.module.css",
    "content": ".Switch {\n  position: relative;\n  display: flex;\n  appearance: none;\n  border: 0;\n  margin: 0;\n  padding: 1px;\n  width: 2.5rem;\n  height: 1.5rem;\n  border-radius: 1.5rem;\n  outline: 1px solid;\n  outline-offset: -1px;\n  background-color: transparent;\n  background-image: linear-gradient(to right, var(--color-gray-700) 35%, var(--color-gray-200) 65%);\n  background-size: 6.5rem 100%;\n  background-position-x: 100%;\n  background-repeat: no-repeat;\n  transition-property: background-position, box-shadow;\n  transition-timing-function: cubic-bezier(0.26, 0.75, 0.38, 0.45);\n  transition-duration: 125ms;\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &[data-checked] {\n    background-position-x: 0%;\n  }\n\n  &[data-checked]:active {\n    background-color: var(--color-gray-500);\n  }\n\n  @media (prefers-color-scheme: light) {\n    box-shadow: var(--color-gray-200) 0 1.5px 2px inset;\n    outline-color: var(--color-gray-200);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    box-shadow: rgb(0 0 0 / 75%) 0 1.5px 2px inset;\n    outline-color: rgb(255 255 255 / 15%);\n    background-image: linear-gradient(\n      to right,\n      var(--color-gray-500) 35%,\n      var(--color-gray-200) 65%\n    );\n\n    &[data-checked] {\n      box-shadow: none;\n    }\n  }\n\n  &:focus-visible {\n    &::before {\n      content: '';\n      inset: 0;\n      position: absolute;\n      border-radius: inherit;\n      outline: 2px solid var(--color-blue);\n      outline-offset: 2px;\n    }\n  }\n}\n\n.Thumb {\n  aspect-ratio: 1 / 1;\n  height: 100%;\n  border-radius: 100%;\n  background-color: white;\n  transition: translate 150ms ease;\n\n  &[data-checked] {\n    translate: 1rem 0;\n  }\n\n  @media (prefers-color-scheme: light) {\n    box-shadow:\n      0 0 1px 1px var(--color-gray-100),\n      0 1px 1px var(--color-gray-100),\n      1px 2px 4px -1px var(--color-gray-100);\n  }\n\n  @media (prefers-color-scheme: dark) {\n    box-shadow:\n      0 0 1px 1px rgb(0 0 0 / 25%),\n      0 1px 1px rgb(0 0 0 / 25%),\n      1px 2px 4px -1px rgb(0 0 0 / 25%);\n  }\n}\n"
  },
  {
    "path": "examples/vite-css/src/widgets/Switch.tsx",
    "content": "import { Switch as BaseSwitch } from '@base-ui/react/switch';\nimport clsx from 'clsx';\nimport styles from './Switch.module.css';\n\nexport function Switch(props: SwitchProps) {\n  return (\n    <BaseSwitch.Root {...props} className={clsx(styles.Switch, props.className)}>\n      <BaseSwitch.Thumb className={styles.Thumb} />\n    </BaseSwitch.Root>\n  );\n}\n\nexport type SwitchProps = BaseSwitch.Root.Props;\n"
  },
  {
    "path": "examples/vite-css/tsconfig.app.json",
    "content": "{\n  \"compilerOptions\": {\n    \"tsBuildInfoFile\": \"./node_modules/.tmp/tsconfig.app.tsbuildinfo\",\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"],\n    \"module\": \"ESNext\",\n    \"skipLibCheck\": true,\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"verbatimModuleSyntax\": true,\n    \"moduleDetection\": \"force\",\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\",\n\n    /* Linting */\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"erasableSyntaxOnly\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"noUncheckedSideEffectImports\": true\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "examples/vite-css/tsconfig.json",
    "content": "{\n  \"files\": [],\n  \"references\": [{ \"path\": \"./tsconfig.app.json\" }, { \"path\": \"./tsconfig.node.json\" }]\n}\n"
  },
  {
    "path": "examples/vite-css/tsconfig.node.json",
    "content": "{\n  \"compilerOptions\": {\n    \"tsBuildInfoFile\": \"./node_modules/.tmp/tsconfig.node.tsbuildinfo\",\n    \"target\": \"ES2022\",\n    \"lib\": [\"ES2023\"],\n    \"module\": \"ESNext\",\n    \"skipLibCheck\": true,\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"verbatimModuleSyntax\": true,\n    \"moduleDetection\": \"force\",\n    \"noEmit\": true,\n\n    /* Linting */\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"erasableSyntaxOnly\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"noUncheckedSideEffectImports\": true\n  },\n  \"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "examples/vite-css/vite.config.ts",
    "content": "import { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\n\n// https://vite.dev/config/\nexport default defineConfig({\n  plugins: [react()],\n});\n"
  },
  {
    "path": "greptile.json",
    "content": "{\n  \"skipReview\": \"AUTOMATIC\"\n}\n"
  },
  {
    "path": "lerna.json",
    "content": "{\n  \"$schema\": \"node_modules/lerna/schemas/lerna-schema.json\",\n  \"npmClient\": \"pnpm\",\n  \"version\": \"independent\"\n}\n"
  },
  {
    "path": "netlify/functions/deploy-succeeded.js",
    "content": "/**\n * @param {object} event\n * @param {string} event.body - https://jsoneditoronline.org/#left=cloud.fb1a4fa30a4f475fa6887071c682e2c1\n */\nexports.handler = async (event) => {\n  const { payload } = JSON.parse(event.body);\n  const repo = payload.review_url.match(/github\\.com\\/(.*)\\/pull\\/(.*)/);\n  if (!repo) {\n    throw new Error(`No repo found at review_url: ${payload.review_url}`);\n  }\n\n  // eslint-disable-next-line no-console\n  console.info(`repo:`, repo[1]);\n  // eslint-disable-next-line no-console\n  console.info(`PR:`, repo[2]);\n  // eslint-disable-next-line no-console\n  console.info(`url:`, payload.deploy_ssl_url);\n\n  // for more details > https://circleci.com/docs/2.0/api-developers-guide/#\n  await fetch(`https://circleci.com/api/v2/project/gh/${repo[1]}/pipeline`, {\n    method: 'POST',\n    headers: {\n      'Content-type': 'application/json',\n      // Token from https://app.netlify.com/projects/base-ui/configuration/env#content\n      'Circle-Token': process.env.CIRCLE_CI_TOKEN,\n    },\n    body: JSON.stringify({\n      // For PR, /head is needed. https://support.circleci.com/hc/en-us/articles/360049841151\n      branch: `pull/${repo[2]}/head`,\n      parameters: {\n        // the parameters defined in .circleci/config.yml\n        workflow: 'e2e-website', // name of the workflow\n        'e2e-base-url': payload.deploy_ssl_url, // deploy preview url\n      },\n    }),\n  });\n  return {\n    statusCode: 200,\n    body: {},\n  };\n};\n"
  },
  {
    "path": "netlify.toml",
    "content": "[build]\n  # The deploy-ready HTML files and assets generated by the build.\n  publish = \"docs/export/\"\n\n  # Build command.\n  command = \"pnpm docs:build\"\n\n[build.environment]\n  NODE_VERSION = \"22.18\"\n  PNPM_FLAGS = \"--frozen-lockfile\"\n\n[[plugins]]\n  package = \"@mui/internal-netlify-cache\"\n"
  },
  {
    "path": "nx.json",
    "content": "{\n  \"$schema\": \"./node_modules/nx/schemas/nx-schema.json\",\n  \"extends\": \"nx/presets/npm.json\",\n  \"targetDefaults\": {\n    \"build\": {\n      \"cache\": true,\n      \"dependsOn\": [\"^build\"]\n    }\n  }\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"@base-ui/monorepo\",\n  \"version\": \"1.3.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"preinstall\": \"npx only-allow pnpm\",\n    \"deduplicate\": \"pnpm dedupe\",\n    \"build\": \"lerna run --no-private build\",\n    \"release:version\": \"lerna version --no-changelog --no-push --no-git-tag-version --no-private\",\n    \"release:build\": \"lerna run --concurrency 8 --no-private build --skip-nx-cache\",\n    \"release:changelog\": \"code-infra generate-changelog\",\n    \"release:changelog:docs\": \"FORMAT=docs code-infra generate-changelog\",\n    \"release:publish\": \"code-infra publish --github-release\",\n    \"release:publish:dry-run\": \"code-infra publish --github-release --dry-run \",\n    \"docs:api\": \"pnpm --filter api-docs-builder start\",\n    \"docs:build\": \"pnpm --filter docs generate-llms && pnpm --filter docs build && pnpm playground:build:export\",\n    \"docs:deploy\": \"pnpm --filter docs run deploy\",\n    \"docs:dev\": \"pnpm --filter docs dev\",\n    \"docs:size-why\": \"cross-env DOCS_STATS_ENABLED=true pnpm docs:build\",\n    \"docs:start\": \"pnpm --filter docs serve\",\n    \"playground:build:export\": \"cross-env PLAYGROUND_BASE=/vite-playground/ PLAYGROUND_OUT_DIR=../../docs/export/vite-playground pnpm -C playground/vite-app build\",\n    \"docs:link-check\": \"pnpm --filter docs link-check\",\n    \"docs:generate-llms\": \"pnpm --filter docs run generate-llms\",\n    \"docs:infra\": \"pnpm --filter docs run docs-infra\",\n    \"docs:validate\": \"pnpm --filter docs run internal-validate --command 'pnpm docs:validate'\",\n    \"extract-error-codes\": \"code-infra extract-error-codes --errorCodesPath docs/src/error-codes.json --detection opt-out\",\n    \"eslint\": \"pnpm -r lint && eslint . --cache --report-unused-disable-directives --max-warnings 0\",\n    \"eslint:ci\": \"pnpm -r lint && eslint . --report-unused-disable-directives --max-warnings 0\",\n    \"stylelint\": \"stylelint --reportInvalidScopeDisables --reportNeedlessDisables \\\"docs/**/*.{js,ts,tsx}\\\" \\\"**/*.css\\\" --ignore-path .lintignore\",\n    \"markdownlint\": \"markdownlint-cli2 \\\"**/*.{md,mdx}\\\"\",\n    \"valelint\": \"pnpm dlx --package @vvago/vale vale sync && git ls-files | grep -E \\\"\\\\.(md|mdx)$\\\" | xargs pnpm dlx --package @vvago/vale vale --filter='.Level==\\\"error\\\"'\",\n    \"prettier\": \"pretty-quick --ignore-path .lintignore --branch master\",\n    \"prettier:all\": \"prettier --write . --ignore-path .lintignore\",\n    \"size:snapshot\": \"pnpm -F ./test/bundle-size check\",\n    \"size:why\": \"pnpm size:snapshot --analyze\",\n    \"start\": \"pnpm install && pnpm docs:dev\",\n    \"test\": \"pnpm test:chromium\",\n    \"test:e2e\": \"cross-env NODE_ENV=production pnpm test:e2e:build && concurrently --success first --kill-others \\\"pnpm test:e2e:run\\\" \\\"pnpm test:e2e:server\\\"\",\n    \"test:e2e:build\": \"vite build --config test/e2e/vite.config.mjs\",\n    \"test:e2e:dev\": \"vite --config test/e2e/vite.config.mjs -l info --port 5173\",\n    \"test:e2e:run\": \"cross-env VITEST_ENV=chromium vitest run --project e2e\",\n    \"test:e2e:server\": \"serve test/e2e -p 5173\",\n    \"test:regressions\": \"cross-env NODE_ENV=production pnpm test:regressions:build && concurrently --success first --kill-others \\\"pnpm test:regressions:run\\\" \\\"pnpm test:regressions:server\\\"\",\n    \"test:regressions:build\": \"vite build --config test/regressions/vite.config.mjs\",\n    \"test:regressions:dev\": \"vite --config test/regressions/vite.config.mjs --port 5173\",\n    \"test:regressions:run\": \"cross-env VITEST_ENV=chromium vitest run --project regressions\",\n    \"test:regressions:server\": \"serve test/regressions -p 5173\",\n    \"test:_unit\": \"cross-env TZ=UTC vitest --project @base-ui/react --project @base-ui/utils --project docs\",\n    \"test:jsdom\": \"cross-env VITEST_ENV=jsdom pnpm test:_unit\",\n    \"test:jsdom:coverage\": \"pnpm test:jsdom --coverage\",\n    \"test:chromium\": \"cross-env VITEST_ENV=chromium pnpm test:_unit\",\n    \"test:chromium:ui\": \"pnpm test:chromium --browser.headless=false\",\n    \"test:firefox\": \"cross-env VITEST_ENV=firefox pnpm test:_unit\",\n    \"test:firefox:ui\": \"pnpm test:firefox --browser.headless=false\",\n    \"test:webkit\": \"cross-env VITEST_ENV=webkit pnpm test:_unit\",\n    \"test:webkit:ui\": \"pnpm test:webkit --browser.headless=false\",\n    \"test:browsers\": \"cross-env VITEST_ENV=all-browsers pnpm test:_unit\",\n    \"test:argos\": \"code-infra argos-push --folder test/regressions/screenshots/chrome\",\n    \"typescript\": \"tsc -b tsconfig.json\",\n    \"inline-scripts\": \"tsx ./scripts/inlineScripts.mts\",\n    \"use-react-18\": \"code-infra set-version-overrides --pkg react@18\"\n  },\n  \"devDependencies\": {\n    \"@arethetypeswrong/cli\": \"0.18.2\",\n    \"@babel/plugin-transform-react-constant-elements\": \"7.27.1\",\n    \"@base-ui/monorepo-tests\": \"workspace:*\",\n    \"@mui/internal-code-infra\": \"0.0.4-canary.8\",\n    \"@mui/internal-netlify-cache\": \"0.0.3-canary.2\",\n    \"@mui/internal-test-utils\": \"2.0.18-canary.16\",\n    \"@next/eslint-plugin-next\": \"16.1.6\",\n    \"@octokit/rest\": \"22.0.1\",\n    \"@playwright/test\": \"1.58.2\",\n    \"@tailwindcss/postcss\": \"4.2.1\",\n    \"@testing-library/jest-dom\": \"6.9.1\",\n    \"@types/node\": \"22.18.13\",\n    \"@types/react\": \"19.2.14\",\n    \"@types/yargs\": \"17.0.35\",\n    \"@vitejs/plugin-react\": \"5.1.4\",\n    \"@vitest/browser-playwright\": \"4.0.18\",\n    \"@vitest/coverage-istanbul\": \"4.0.18\",\n    \"@vitest/ui\": \"4.0.18\",\n    \"concurrently\": \"9.2.1\",\n    \"cross-env\": \"10.1.0\",\n    \"docs\": \"workspace:^\",\n    \"eslint\": \"10.0.3\",\n    \"execa\": \"9.6.1\",\n    \"globby\": \"16.1.1\",\n    \"jsdom\": \"27.4.0\",\n    \"lerna\": \"9.0.5\",\n    \"markdownlint-cli2\": \"0.21.0\",\n    \"pkg-pr-new\": \"^0.0.66\",\n    \"prettier\": \"3.8.1\",\n    \"pretty-quick\": \"4.2.2\",\n    \"publint\": \"0.3.18\",\n    \"react\": \"19.2.4\",\n    \"react-dom\": \"19.2.4\",\n    \"remark-mdx\": \"3.1.1\",\n    \"rimraf\": \"6.1.3\",\n    \"serve\": \"14.2.6\",\n    \"stylelint\": \"17.4.0\",\n    \"stylelint-config-tailwindcss\": \"1.0.1\",\n    \"tailwindcss\": \"4.2.1\",\n    \"terser\": \"5.46.0\",\n    \"tsx\": \"4.21.0\",\n    \"typescript\": \"5.9.3\",\n    \"vite\": \"7.3.1\",\n    \"vitest\": \"4.0.18\"\n  },\n  \"packageManager\": \"pnpm@10.32.0\",\n  \"engines\": {\n    \"pnpm\": \"10.32.0\",\n    \"node\": \">=22.18.0\"\n  },\n  \"resolutions\": {\n    \"@babel/core\": \"^7.29.0\",\n    \"@babel/plugin-transform-runtime\": \"^7.29.0\",\n    \"@babel/preset-env\": \"^7.29.0\",\n    \"@babel/preset-react\": \"^7.28.5\",\n    \"@babel/preset-typescript\": \"^7.28.5\",\n    \"@babel/types\": \"^7.29.0\",\n    \"@types/node\": \"22.18.13\",\n    \"@types/react\": \"19.2.14\",\n    \"@types/react-dom\": \"^19.2.3\"\n  }\n}\n"
  },
  {
    "path": "packages/react/.npmignore",
    "content": "*.tsbuildinfo\n"
  },
  {
    "path": "packages/react/README.md",
    "content": "<!-- markdownlint-disable MD041 -->\n\n<a href=\"https://base-ui.com\" rel=\"noopener\" target=\"_blank\"><img width=\"150\" height=\"133\" src=\"https://base-ui.com/static/logo.svg\" alt=\"Base UI logo\"></a>\n\nBase UI is a library of unstyled React components. You gain complete control over your app's CSS and accessibility features.\n\n## Installation\n\nInstall the package in your project directory with:\n\n```bash\nnpm install @base-ui/react\n```\n\n## Documentation\n\n<!-- #default-branch-switch -->\n\nVisit [base-ui.com](https://base-ui.com) to view the full documentation.\n\n## Questions\n\nFor how-to questions that don't involve making changes to the code base, please use [Stack Overflow](https://stackoverflow.com/questions/tagged/base-ui) instead of GitHub issues.\nUse the \"base-ui\" tag on Stack Overflow to make it easier for the community to find your question.\n\n## Contributing\n\nRead the [contributing guide](../../CONTRIBUTING.md) to learn about our development process, how to propose bug fixes and improvements, and how to build and test your changes.\n\nContributing to Base UI is about more than just issues and pull requests!\nThere are many other ways to [support Base UI](https://mui.com/material-ui/getting-started/faq/#mui-is-awesome-how-can-i-support-the-project) beyond contributing to the code base.\n\n## Changelog\n\nThe [changelog](https://github.com/mui/base-ui/releases) is regularly updated to reflect what's changed in each new release.\n\n## Roadmap\n\nFuture plans and high-priority features and enhancements can be found in the [roadmap](https://github.com/orgs/mui/projects/1).\n\n## License\n\nThis project is licensed under the terms of the [MIT license](../../LICENSE).\n\n## Security\n\nFor details of supported versions and contact details for reporting security issues, please refer to the [security policy](https://github.com/mui/base-ui/security/policy).\n"
  },
  {
    "path": "packages/react/package.json",
    "content": "{\n  \"name\": \"@base-ui/react\",\n  \"version\": \"1.3.0\",\n  \"author\": \"MUI Team\",\n  \"description\": \"Base UI is a library of headless ('unstyled') React components and low-level hooks. You gain complete control over your app's CSS and accessibility features.\",\n  \"keywords\": [\n    \"react\",\n    \"react-component\",\n    \"mui\",\n    \"unstyled\",\n    \"a11y\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mui/base-ui.git\",\n    \"directory\": \"packages/react\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mui/base-ui/issues\"\n  },\n  \"homepage\": \"https://base-ui.com\",\n  \"funding\": {\n    \"type\": \"opencollective\",\n    \"url\": \"https://opencollective.com/mui-org\"\n  },\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./accordion\": \"./src/accordion/index.ts\",\n    \"./alert-dialog\": \"./src/alert-dialog/index.ts\",\n    \"./autocomplete\": \"./src/autocomplete/index.ts\",\n    \"./avatar\": \"./src/avatar/index.ts\",\n    \"./button\": \"./src/button/index.ts\",\n    \"./checkbox\": \"./src/checkbox/index.ts\",\n    \"./checkbox-group\": \"./src/checkbox-group/index.ts\",\n    \"./collapsible\": \"./src/collapsible/index.ts\",\n    \"./combobox\": \"./src/combobox/index.ts\",\n    \"./csp-provider\": \"./src/csp-provider/index.ts\",\n    \"./context-menu\": \"./src/context-menu/index.ts\",\n    \"./dialog\": \"./src/dialog/index.ts\",\n    \"./direction-provider\": \"./src/direction-provider/index.ts\",\n    \"./drawer\": \"./src/drawer/index.ts\",\n    \"./field\": \"./src/field/index.ts\",\n    \"./fieldset\": \"./src/fieldset/index.ts\",\n    \"./form\": \"./src/form/index.ts\",\n    \"./input\": \"./src/input/index.ts\",\n    \"./menu\": \"./src/menu/index.ts\",\n    \"./menubar\": \"./src/menubar/index.ts\",\n    \"./merge-props\": \"./src/merge-props/index.ts\",\n    \"./meter\": \"./src/meter/index.ts\",\n    \"./navigation-menu\": \"./src/navigation-menu/index.ts\",\n    \"./number-field\": \"./src/number-field/index.ts\",\n    \"./popover\": \"./src/popover/index.ts\",\n    \"./preview-card\": \"./src/preview-card/index.ts\",\n    \"./progress\": \"./src/progress/index.ts\",\n    \"./radio\": \"./src/radio/index.ts\",\n    \"./radio-group\": \"./src/radio-group/index.ts\",\n    \"./scroll-area\": \"./src/scroll-area/index.ts\",\n    \"./select\": \"./src/select/index.ts\",\n    \"./separator\": \"./src/separator/index.ts\",\n    \"./slider\": \"./src/slider/index.ts\",\n    \"./switch\": \"./src/switch/index.ts\",\n    \"./tabs\": \"./src/tabs/index.ts\",\n    \"./toast\": \"./src/toast/index.ts\",\n    \"./toggle\": \"./src/toggle/index.ts\",\n    \"./toggle-group\": \"./src/toggle-group/index.ts\",\n    \"./toolbar\": \"./src/toolbar/index.ts\",\n    \"./tooltip\": \"./src/tooltip/index.ts\",\n    \"./types\": \"./src/types/index.ts\",\n    \"./unstable-use-media-query\": \"./src/unstable-use-media-query/index.ts\",\n    \"./use-render\": \"./src/use-render/index.ts\"\n  },\n  \"imports\": {\n    \"#test-utils\": \"./test/index.ts\",\n    \"#formatErrorMessage\": \"@base-ui/utils/formatErrorMessage\"\n  },\n  \"type\": \"commonjs\",\n  \"scripts\": {\n    \"prebuild\": \"rimraf --glob build build-tests \\\"*.tsbuildinfo\\\"\",\n    \"build\": \"code-infra build --ignore \\\"**/*.template.js\\\" --copy .npmignore\",\n    \"test:package\": \"publint --pack pnpm && attw --pack ./build --exclude-entrypoints package.json --exclude-entrypoints esm --exclude-entrypoints cjs\",\n    \"release\": \"pnpm build && pnpm publish\",\n    \"test\": \"cross-env VITEST_ENV=jsdom vitest\",\n    \"typescript\": \"tsc -b tsconfig.json\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.28.6\",\n    \"@base-ui/utils\": \"workspace:*\",\n    \"@floating-ui/react-dom\": \"^2.1.8\",\n    \"@floating-ui/utils\": \"^0.2.11\",\n    \"tabbable\": \"^6.4.0\",\n    \"use-sync-external-store\": \"^1.6.0\"\n  },\n  \"devDependencies\": {\n    \"@date-fns/tz\": \"^1.4.1\",\n    \"@testing-library/react\": \"16.3.2\",\n    \"@testing-library/user-event\": \"14.6.1\",\n    \"@types/luxon\": \"^3.7.1\",\n    \"@types/react\": \"19.2.14\",\n    \"@types/react-dom\": \"19.2.3\",\n    \"@types/use-sync-external-store\": \"1.5.0\",\n    \"clsx\": \"2.1.1\",\n    \"date-fns\": \"^4.1.0\",\n    \"luxon\": \"^3.7.2\",\n    \"react\": \"19.2.4\",\n    \"react-dom\": \"19.2.4\",\n    \"react-router\": \"7.13.1\",\n    \"typescript\": \"5.9.3\",\n    \"vitest-browser-react\": \"2.0.5\"\n  },\n  \"peerDependencies\": {\n    \"@date-fns/tz\": \"^1.2.0\",\n    \"@types/react\": \"^17 || ^18 || ^19\",\n    \"date-fns\": \"^4.0.0\",\n    \"react\": \"^17 || ^18 || ^19\",\n    \"react-dom\": \"^17 || ^18 || ^19\"\n  },\n  \"peerDependenciesMeta\": {\n    \"@types/react\": {\n      \"optional\": true\n    }\n  },\n  \"sideEffects\": false,\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"directory\": \"build\"\n  },\n  \"engines\": {\n    \"node\": \">=14.0.0\"\n  }\n}\n"
  },
  {
    "path": "packages/react/src/accordion/header/AccordionHeader.test.tsx",
    "content": "import { Accordion } from '@base-ui/react/accordion';\nimport { describeConformance, createRenderer } from '#test-utils';\n\ndescribe('<Accordion.Header />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Accordion.Header />, () => ({\n    render: (node) =>\n      render(\n        <Accordion.Root>\n          <Accordion.Item>{node}</Accordion.Item>\n        </Accordion.Root>,\n      ),\n    refInstanceof: window.HTMLHeadingElement,\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/accordion/header/AccordionHeader.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport type { AccordionItemState } from '../item/AccordionItem';\nimport { useAccordionItemContext } from '../item/AccordionItemContext';\nimport { accordionStateAttributesMapping } from '../item/stateAttributesMapping';\n\n/**\n * A heading that labels the corresponding panel.\n * Renders an `<h3>` element.\n *\n * Documentation: [Base UI Accordion](https://base-ui.com/react/components/accordion)\n */\nexport const AccordionHeader = React.forwardRef(function AccordionHeader(\n  componentProps: AccordionHeader.Props,\n  forwardedRef: React.ForwardedRef<HTMLHeadingElement>,\n) {\n  const { render, className, ...elementProps } = componentProps;\n\n  const { state } = useAccordionItemContext();\n\n  const element = useRenderElement('h3', componentProps, {\n    state,\n    ref: forwardedRef,\n    props: elementProps,\n    stateAttributesMapping: accordionStateAttributesMapping,\n  });\n\n  return element;\n});\n\nexport interface AccordionHeaderState extends AccordionItemState {}\n\nexport interface AccordionHeaderProps extends BaseUIComponentProps<'h3', AccordionHeaderState> {}\n\nexport namespace AccordionHeader {\n  export type State = AccordionHeaderState;\n  export type Props = AccordionHeaderProps;\n}\n"
  },
  {
    "path": "packages/react/src/accordion/header/AccordionHeaderDataAttributes.ts",
    "content": "export enum AccordionHeaderDataAttributes {\n  /**\n   * Indicates the index of the accordion item.\n   * @type {number}\n   */\n  index = 'data-index',\n  /**\n   * Present when the accordion item is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the accordion item is open.\n   */\n  open = 'data-open',\n}\n"
  },
  {
    "path": "packages/react/src/accordion/index.parts.ts",
    "content": "export { AccordionRoot as Root } from './root/AccordionRoot';\nexport { AccordionItem as Item } from './item/AccordionItem';\nexport { AccordionHeader as Header } from './header/AccordionHeader';\nexport { AccordionTrigger as Trigger } from './trigger/AccordionTrigger';\nexport { AccordionPanel as Panel } from './panel/AccordionPanel';\n"
  },
  {
    "path": "packages/react/src/accordion/index.ts",
    "content": "export * as Accordion from './index.parts';\n\nexport type * from './root/AccordionRoot';\nexport type * from './item/AccordionItem';\nexport type * from './header/AccordionHeader';\nexport type * from './trigger/AccordionTrigger';\nexport type * from './panel/AccordionPanel';\n"
  },
  {
    "path": "packages/react/src/accordion/item/AccordionItem.test.tsx",
    "content": "import { Accordion } from '@base-ui/react/accordion';\nimport { describeConformance, createRenderer } from '#test-utils';\n\ndescribe('<Accordion.Item />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Accordion.Item />, () => ({\n    render: (node) => {\n      return render(<Accordion.Root>{node}</Accordion.Root>);\n    },\n    refInstanceof: window.HTMLDivElement,\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/accordion/item/AccordionItem.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useMergedRefs } from '@base-ui/utils/useMergedRefs';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { useBaseUiId } from '../../utils/useBaseUiId';\nimport {\n  useCollapsibleRoot,\n  type UseCollapsibleRootParameters,\n} from '../../collapsible/root/useCollapsibleRoot';\nimport type { CollapsibleRoot, CollapsibleRootState } from '../../collapsible/root/CollapsibleRoot';\nimport { CollapsibleRootContext } from '../../collapsible/root/CollapsibleRootContext';\nimport { useCompositeListItem } from '../../composite/list/useCompositeListItem';\nimport type { AccordionRootState } from '../root/AccordionRoot';\nimport { useAccordionRootContext } from '../root/AccordionRootContext';\nimport { AccordionItemContext } from './AccordionItemContext';\nimport { accordionStateAttributesMapping } from './stateAttributesMapping';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { type BaseUIChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\n\n/**\n * Groups an accordion header with the corresponding panel.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Accordion](https://base-ui.com/react/components/accordion)\n */\nexport const AccordionItem = React.forwardRef(function AccordionItem(\n  componentProps: AccordionItem.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const {\n    className,\n    disabled: disabledProp = false,\n    onOpenChange: onOpenChangeProp,\n    render,\n    value: valueProp,\n    ...elementProps\n  } = componentProps;\n\n  const { ref: listItemRef, index } = useCompositeListItem();\n  const mergedRef = useMergedRefs(forwardedRef, listItemRef);\n\n  const {\n    disabled: contextDisabled,\n    handleValueChange,\n    state: rootState,\n    value: openValues,\n  } = useAccordionRootContext();\n\n  const fallbackValue = useBaseUiId();\n\n  const value = valueProp ?? fallbackValue;\n\n  const disabled = disabledProp || contextDisabled;\n\n  const isOpen = React.useMemo(() => {\n    if (!openValues) {\n      return false;\n    }\n\n    for (let i = 0; i < openValues.length; i += 1) {\n      if (openValues[i] === value) {\n        return true;\n      }\n    }\n\n    return false;\n  }, [openValues, value]);\n\n  const onOpenChange = useStableCallback(\n    (nextOpen: boolean, eventDetails: CollapsibleRoot.ChangeEventDetails) => {\n      onOpenChangeProp?.(nextOpen, eventDetails);\n\n      if (eventDetails.isCanceled) {\n        return;\n      }\n\n      handleValueChange(value, nextOpen);\n    },\n  );\n\n  const collapsible = useCollapsibleRoot({\n    open: isOpen,\n    onOpenChange,\n    disabled,\n  });\n\n  const collapsibleState: CollapsibleRootState = React.useMemo(\n    () => ({\n      open: collapsible.open,\n      disabled: collapsible.disabled,\n      hidden: !collapsible.mounted,\n      transitionStatus: collapsible.transitionStatus,\n    }),\n    [collapsible.open, collapsible.disabled, collapsible.mounted, collapsible.transitionStatus],\n  );\n\n  const collapsibleContext: CollapsibleRootContext = React.useMemo(\n    () => ({\n      ...collapsible,\n      onOpenChange,\n      state: collapsibleState,\n    }),\n    [collapsible, collapsibleState, onOpenChange],\n  );\n\n  const state: AccordionItemState = React.useMemo(\n    () => ({\n      ...rootState,\n      index,\n      disabled,\n      open: isOpen,\n    }),\n    [disabled, index, isOpen, rootState],\n  );\n\n  const [triggerId, setTriggerId] = React.useState<string | undefined>(useBaseUiId());\n\n  const accordionItemContext: AccordionItemContext = React.useMemo(\n    () => ({\n      open: isOpen,\n      state,\n      setTriggerId,\n      triggerId,\n    }),\n    [isOpen, state, setTriggerId, triggerId],\n  );\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: mergedRef,\n    props: elementProps,\n    stateAttributesMapping: accordionStateAttributesMapping,\n  });\n\n  return (\n    <CollapsibleRootContext.Provider value={collapsibleContext}>\n      <AccordionItemContext.Provider value={accordionItemContext}>\n        {element}\n      </AccordionItemContext.Provider>\n    </CollapsibleRootContext.Provider>\n  );\n});\n\nexport interface AccordionItemState extends AccordionRootState {\n  /**\n   * The item index.\n   */\n  index: number;\n  /**\n   * Whether the component is open.\n   */\n  open: boolean;\n}\n\nexport interface AccordionItemProps\n  extends\n    BaseUIComponentProps<'div', AccordionItemState>,\n    Partial<Pick<UseCollapsibleRootParameters, 'disabled'>> {\n  /**\n   * A unique value that identifies this accordion item.\n   * If no value is provided, a unique ID will be generated automatically.\n   * Use when controlling the accordion programmatically, or to set an initial\n   * open state.\n   * @example\n   * ```tsx\n   * <Accordion.Root value={['a']}>\n   *   <Accordion.Item value=\"a\" /> // initially open\n   *   <Accordion.Item value=\"b\" /> // initially closed\n   * </Accordion.Root>\n   * ```\n   */\n  value?: any;\n  /**\n   * Event handler called when the panel is opened or closed.\n   */\n  onOpenChange?:\n    | ((open: boolean, eventDetails: AccordionItem.ChangeEventDetails) => void)\n    | undefined;\n}\n\nexport type AccordionItemChangeEventReason = typeof REASONS.triggerPress | typeof REASONS.none;\n\nexport type AccordionItemChangeEventDetails =\n  BaseUIChangeEventDetails<AccordionItem.ChangeEventReason>;\n\nexport namespace AccordionItem {\n  export type State = AccordionItemState;\n  export type Props = AccordionItemProps;\n  export type ChangeEventReason = AccordionItemChangeEventReason;\n  export type ChangeEventDetails = AccordionItemChangeEventDetails;\n}\n"
  },
  {
    "path": "packages/react/src/accordion/item/AccordionItemContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport type { AccordionItemState } from './AccordionItem';\n\nexport interface AccordionItemContext {\n  open: boolean;\n  state: AccordionItemState;\n  setTriggerId: (id: string | undefined) => void;\n  triggerId?: string | undefined;\n}\n\nexport const AccordionItemContext = React.createContext<AccordionItemContext | undefined>(\n  undefined,\n);\n\nexport function useAccordionItemContext() {\n  const context = React.useContext(AccordionItemContext);\n  if (context === undefined) {\n    throw new Error(\n      'Base UI: AccordionItemContext is missing. Accordion parts must be placed within <Accordion.Item>.',\n    );\n  }\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/accordion/item/AccordionItemDataAttributes.ts",
    "content": "export enum AccordionItemDataAttributes {\n  /**\n   * Indicates the index of the accordion item.\n   * @type {number}\n   */\n  index = 'data-index',\n  /**\n   * Present when the accordion item is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the accordion item is open.\n   */\n  open = 'data-open',\n}\n"
  },
  {
    "path": "packages/react/src/accordion/item/stateAttributesMapping.ts",
    "content": "import type { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport { collapsibleOpenStateMapping as baseMapping } from '../../utils/collapsibleOpenStateMapping';\nimport type { AccordionItemState } from './AccordionItem';\nimport { transitionStatusMapping } from '../../utils/stateAttributesMapping';\nimport { AccordionItemDataAttributes } from './AccordionItemDataAttributes';\n\nexport const accordionStateAttributesMapping: StateAttributesMapping<AccordionItemState> = {\n  ...baseMapping,\n  index: (value) => {\n    return Number.isInteger(value) ? { [AccordionItemDataAttributes.index]: String(value) } : null;\n  },\n  ...transitionStatusMapping,\n  value: () => null,\n};\n"
  },
  {
    "path": "packages/react/src/accordion/panel/AccordionPanel.test.tsx",
    "content": "import { Accordion } from '@base-ui/react/accordion';\nimport { describeConformance, createRenderer } from '#test-utils';\n\ndescribe('<Accordion.Panel />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Accordion.Panel keepMounted />, () => ({\n    render: (node) =>\n      render(\n        <Accordion.Root>\n          <Accordion.Item>{node}</Accordion.Item>\n        </Accordion.Root>,\n      ),\n    refInstanceof: window.HTMLDivElement,\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/accordion/panel/AccordionPanel.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { warn } from '@base-ui/utils/warn';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { useCollapsibleRootContext } from '../../collapsible/root/CollapsibleRootContext';\nimport { useCollapsiblePanel } from '../../collapsible/panel/useCollapsiblePanel';\nimport { useAccordionRootContext } from '../root/AccordionRootContext';\nimport type { AccordionRoot } from '../root/AccordionRoot';\nimport type { AccordionItemState } from '../item/AccordionItem';\nimport { useAccordionItemContext } from '../item/AccordionItemContext';\nimport { accordionStateAttributesMapping } from '../item/stateAttributesMapping';\nimport { AccordionPanelCssVars } from './AccordionPanelCssVars';\nimport { useOpenChangeComplete } from '../../utils/useOpenChangeComplete';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport type { TransitionStatus } from '../../utils/useTransitionStatus';\n\n/**\n * A collapsible panel with the accordion item contents.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Accordion](https://base-ui.com/react/components/accordion)\n */\nexport const AccordionPanel = React.forwardRef(function AccordionPanel(\n  componentProps: AccordionPanel.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const {\n    className,\n    hiddenUntilFound: hiddenUntilFoundProp,\n    keepMounted: keepMountedProp,\n    id: idProp,\n    render,\n    ...elementProps\n  } = componentProps;\n\n  const { hiddenUntilFound: contextHiddenUntilFound, keepMounted: contextKeepMounted } =\n    useAccordionRootContext();\n\n  const {\n    abortControllerRef,\n    animationTypeRef,\n    height,\n    mounted,\n    onOpenChange,\n    open,\n    panelId,\n    panelRef,\n    runOnceAnimationsFinish,\n    setDimensions,\n    setHiddenUntilFound,\n    setKeepMounted,\n    setMounted,\n    setOpen,\n    setVisible,\n    transitionDimensionRef,\n    visible,\n    width,\n    setPanelIdState,\n    transitionStatus,\n  } = useCollapsibleRootContext();\n\n  const hiddenUntilFound = hiddenUntilFoundProp ?? contextHiddenUntilFound;\n  const keepMounted = keepMountedProp ?? contextKeepMounted;\n\n  if (process.env.NODE_ENV !== 'production') {\n    // eslint-disable-next-line react-hooks/rules-of-hooks\n    useIsoLayoutEffect(() => {\n      if (keepMountedProp === false && hiddenUntilFound) {\n        warn(\n          'The `keepMounted={false}` prop on a Accordion.Panel will be ignored when using `contextHiddenUntilFound` on the Panel or the Root since it requires the panel to remain mounted when closed.',\n        );\n      }\n    }, [hiddenUntilFound, keepMountedProp]);\n  }\n\n  useIsoLayoutEffect(() => {\n    if (idProp) {\n      setPanelIdState(idProp);\n      return () => {\n        setPanelIdState(undefined);\n      };\n    }\n    return undefined;\n  }, [idProp, setPanelIdState]);\n\n  useIsoLayoutEffect(() => {\n    setHiddenUntilFound(hiddenUntilFound);\n  }, [setHiddenUntilFound, hiddenUntilFound]);\n\n  useIsoLayoutEffect(() => {\n    setKeepMounted(keepMounted);\n  }, [setKeepMounted, keepMounted]);\n\n  useOpenChangeComplete({\n    open: open && transitionStatus === 'idle',\n    ref: panelRef,\n    onComplete() {\n      if (!open) {\n        return;\n      }\n\n      setDimensions({ width: undefined, height: undefined });\n    },\n  });\n\n  const { props } = useCollapsiblePanel({\n    abortControllerRef,\n    animationTypeRef,\n    externalRef: forwardedRef,\n    height,\n    hiddenUntilFound,\n    id: idProp ?? panelId,\n    keepMounted,\n    mounted,\n    onOpenChange,\n    open,\n    panelRef,\n    runOnceAnimationsFinish,\n    setDimensions,\n    setMounted,\n    setOpen,\n    setVisible,\n    transitionDimensionRef,\n    visible,\n    width,\n  });\n\n  const { state, triggerId } = useAccordionItemContext();\n\n  const panelState: AccordionPanelState = React.useMemo(\n    () => ({\n      ...state,\n      transitionStatus,\n    }),\n    [state, transitionStatus],\n  );\n\n  const element = useRenderElement('div', componentProps, {\n    state: panelState,\n    ref: [forwardedRef, panelRef],\n    props: [\n      props,\n      {\n        'aria-labelledby': triggerId,\n        role: 'region',\n        style: {\n          [AccordionPanelCssVars.accordionPanelHeight as string]:\n            height === undefined ? 'auto' : `${height}px`,\n          [AccordionPanelCssVars.accordionPanelWidth as string]:\n            width === undefined ? 'auto' : `${width}px`,\n        },\n      },\n      elementProps,\n    ],\n    stateAttributesMapping: accordionStateAttributesMapping,\n  });\n\n  const shouldRender = keepMounted || hiddenUntilFound || mounted;\n  if (!shouldRender) {\n    return null;\n  }\n\n  return element;\n});\n\nexport interface AccordionPanelState extends AccordionItemState {\n  /**\n   * The transition status of the component.\n   */\n  transitionStatus: TransitionStatus;\n}\n\nexport interface AccordionPanelProps\n  extends\n    BaseUIComponentProps<'div', AccordionPanelState>,\n    Pick<AccordionRoot.Props, 'hiddenUntilFound' | 'keepMounted'> {}\n\nexport namespace AccordionPanel {\n  export type State = AccordionPanelState;\n  export type Props = AccordionPanelProps;\n}\n"
  },
  {
    "path": "packages/react/src/accordion/panel/AccordionPanelCssVars.ts",
    "content": "export enum AccordionPanelCssVars {\n  /**\n   * The accordion panel's height.\n   * @type {number}\n   */\n  accordionPanelHeight = '--accordion-panel-height',\n  /**\n   * The accordion panel's width.\n   * @type {number}\n   */\n  accordionPanelWidth = '--accordion-panel-width',\n}\n"
  },
  {
    "path": "packages/react/src/accordion/panel/AccordionPanelDataAttributes.ts",
    "content": "import { TransitionStatusDataAttributes } from '../../utils/stateAttributesMapping';\n\nexport enum AccordionPanelDataAttributes {\n  /**\n   * Indicates the index of the accordion item.\n   * @type {number}\n   */\n  index = 'data-index',\n  /**\n   * Present when the accordion panel is open.\n   */\n  open = 'data-open',\n  /**\n   * Indicates the orientation of the accordion.\n   */\n  orientation = 'data-orientation',\n  /**\n   * Present when the accordion item is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the panel is animating in.\n   */\n  startingStyle = TransitionStatusDataAttributes.startingStyle,\n  /**\n   * Present when the panel is animating out.\n   */\n  endingStyle = TransitionStatusDataAttributes.endingStyle,\n}\n"
  },
  {
    "path": "packages/react/src/accordion/root/AccordionRoot.spec.tsx",
    "content": "import { expectType } from '#test-utils';\nimport { Accordion } from '@base-ui/react/accordion';\n\nconst stringValues = ['a'];\nconst nullableValues: (string | null)[] = ['a', null];\n\n<Accordion.Root\n  value={stringValues}\n  onValueChange={(value) => {\n    expectType<string[], typeof value>(value);\n  }}\n/>;\n\n<Accordion.Root\n  defaultValue={[1]}\n  onValueChange={(value) => {\n    expectType<number[], typeof value>(value);\n  }}\n/>;\n\n<Accordion.Root<'a' | 'b'> value={['a']} />;\n\n<Accordion.Root<'a' | 'b'>\n  onValueChange={(value) => {\n    expectType<('a' | 'b')[], typeof value>(value);\n  }}\n/>;\n\n<Accordion.Root<string | null>\n  value={nullableValues}\n  onValueChange={(value) => {\n    expectType<(string | null)[], typeof value>(value);\n  }}\n/>;\n\n<Accordion.Root\n  onValueChange={(value) => {\n    // Backward-compatible default: no explicit generic keeps permissive `any[]`.\n    expectType<any[], typeof value>(value);\n  }}\n/>;\n\n// @ts-expect-error value must match explicit generic type\n<Accordion.Root<'a' | 'b'> value={['c']} />;\n\ntype AccordionChangeHandler = NonNullable<Accordion.Root.Props<'a'>['onValueChange']>;\ntype AccordionDefaultChangeHandler = NonNullable<Accordion.Root.Props['onValueChange']>;\n\nconst handleValueChange: AccordionChangeHandler = (value) => {\n  expectType<'a'[], typeof value>(value);\n};\n\n<Accordion.Root<'a'> onValueChange={handleValueChange} />;\n\nconst handleDefaultValueChange: AccordionDefaultChangeHandler = (value) => {\n  expectType<any[], typeof value>(value);\n};\n\n<Accordion.Root onValueChange={handleDefaultValueChange} />;\n\nexport function Wrapper<Value>(props: Accordion.Root.Props<Value>) {\n  return <Accordion.Root {...props} />;\n}\n"
  },
  {
    "path": "packages/react/src/accordion/root/AccordionRoot.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport { fireEvent, screen } from '@mui/internal-test-utils';\nimport { DirectionProvider } from '@base-ui/react/direction-provider';\nimport { Accordion } from '@base-ui/react/accordion';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\n\nconst PANEL_CONTENT_1 = 'Panel contents 1';\nconst PANEL_CONTENT_2 = 'Panel contents 2';\n\ndescribe('<Accordion.Root />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Accordion.Root />, () => ({\n    render,\n    refInstanceof: window.HTMLDivElement,\n  }));\n\n  describe('ARIA attributes', () => {\n    it('renders correct ARIA attributes', async () => {\n      const { container } = await render(\n        <Accordion.Root defaultValue={[0]}>\n          <Accordion.Item value={0}>\n            <Accordion.Header>\n              <Accordion.Trigger>Trigger 1</Accordion.Trigger>\n            </Accordion.Header>\n            <Accordion.Panel>{PANEL_CONTENT_1}</Accordion.Panel>\n          </Accordion.Item>\n        </Accordion.Root>,\n      );\n\n      const root = container.firstElementChild as HTMLElement;\n      const trigger = screen.getByRole('button');\n      const panel = screen.queryByText(PANEL_CONTENT_1) as HTMLElement;\n\n      expect(root).toHaveAttribute('role', 'region');\n      expect(trigger).toHaveAttribute('aria-controls');\n      expect(panel.getAttribute('id')).toBe(trigger.getAttribute('aria-controls'));\n      expect(panel).toHaveAttribute('role', 'region');\n      expect(trigger.getAttribute('id')).toBe(panel.getAttribute('aria-labelledby'));\n    });\n\n    it('references manual panel id in trigger aria-controls', async () => {\n      await render(\n        <Accordion.Root defaultValue={[0]}>\n          <Accordion.Item value={0}>\n            <Accordion.Header>\n              <Accordion.Trigger>Trigger 1</Accordion.Trigger>\n            </Accordion.Header>\n            <Accordion.Panel id=\"custom-panel-id\">{PANEL_CONTENT_1}</Accordion.Panel>\n          </Accordion.Item>\n        </Accordion.Root>,\n      );\n\n      const trigger = screen.getByRole('button');\n      const panel = screen.queryByText(PANEL_CONTENT_1) as HTMLElement;\n\n      expect(trigger).toHaveAttribute('aria-controls', 'custom-panel-id');\n      expect(panel).toHaveAttribute('id', 'custom-panel-id');\n    });\n  });\n\n  describe('uncontrolled', () => {\n    it.skipIf(isJSDOM)('open state', async () => {\n      const { user } = await render(\n        <Accordion.Root>\n          <Accordion.Item>\n            <Accordion.Header>\n              <Accordion.Trigger>Trigger 1</Accordion.Trigger>\n            </Accordion.Header>\n            <Accordion.Panel>{PANEL_CONTENT_1}</Accordion.Panel>\n          </Accordion.Item>\n        </Accordion.Root>,\n      );\n\n      const trigger = screen.getByRole('button');\n\n      expect(trigger).toHaveAttribute('aria-expanded', 'false');\n      expect(screen.queryByText(PANEL_CONTENT_1)).toBe(null);\n\n      await user.pointer({ keys: '[MouseLeft]', target: trigger });\n\n      expect(trigger).toHaveAttribute('aria-expanded', 'true');\n      expect(trigger).toHaveAttribute('data-panel-open');\n      expect(screen.queryByText(PANEL_CONTENT_1)).not.toBe(null);\n      expect(screen.queryByText(PANEL_CONTENT_1)).toBeVisible();\n      expect(screen.queryByText(PANEL_CONTENT_1)).toHaveAttribute('data-open');\n\n      await user.pointer({ keys: '[MouseLeft]', target: trigger });\n\n      expect(trigger).toHaveAttribute('aria-expanded', 'false');\n      expect(screen.queryByText(PANEL_CONTENT_1)).toBe(null);\n    });\n\n    describe('prop: defaultValue', () => {\n      it('custom item value', async () => {\n        await render(\n          <Accordion.Root defaultValue={['first']}>\n            <Accordion.Item value=\"first\">\n              <Accordion.Header>\n                <Accordion.Trigger>Trigger 1</Accordion.Trigger>\n              </Accordion.Header>\n              <Accordion.Panel>{PANEL_CONTENT_1}</Accordion.Panel>\n            </Accordion.Item>\n            <Accordion.Item value=\"second\">\n              <Accordion.Header>\n                <Accordion.Trigger>Trigger 2</Accordion.Trigger>\n              </Accordion.Header>\n              <Accordion.Panel>{PANEL_CONTENT_2}</Accordion.Panel>\n            </Accordion.Item>\n          </Accordion.Root>,\n        );\n\n        expect(screen.queryByText(PANEL_CONTENT_1)).not.toBe(null);\n        expect(screen.queryByText(PANEL_CONTENT_1)).toBeVisible();\n        expect(screen.queryByText(PANEL_CONTENT_1)).toHaveAttribute('data-open');\n\n        expect(screen.queryByText(PANEL_CONTENT_2)).toBe(null);\n      });\n    });\n  });\n\n  describe('controlled', () => {\n    it.skipIf(isJSDOM)('open state', async () => {\n      const { setProps } = await render(\n        <Accordion.Root value={[]}>\n          <Accordion.Item value={0}>\n            <Accordion.Header>\n              <Accordion.Trigger>Trigger 1</Accordion.Trigger>\n            </Accordion.Header>\n            <Accordion.Panel>{PANEL_CONTENT_1}</Accordion.Panel>\n          </Accordion.Item>\n        </Accordion.Root>,\n      );\n\n      const trigger = screen.getByRole('button');\n\n      expect(trigger).toHaveAttribute('aria-expanded', 'false');\n      expect(screen.queryByText(PANEL_CONTENT_1)).toBe(null);\n\n      await setProps({ value: [0] });\n\n      expect(trigger).toHaveAttribute('aria-expanded', 'true');\n      expect(trigger).toHaveAttribute('data-panel-open');\n      expect(screen.queryByText(PANEL_CONTENT_1)).not.toBe(null);\n      expect(screen.queryByText(PANEL_CONTENT_1)).toBeVisible();\n      expect(screen.queryByText(PANEL_CONTENT_1)).toHaveAttribute('data-open');\n\n      await setProps({ value: [] });\n\n      expect(trigger).toHaveAttribute('aria-expanded', 'false');\n      expect(screen.queryByText(PANEL_CONTENT_1)).toBe(null);\n    });\n\n    describe('prop: value', () => {\n      it('custom item value', async () => {\n        await render(\n          <Accordion.Root value={['one']}>\n            <Accordion.Item value=\"one\">\n              <Accordion.Header>\n                <Accordion.Trigger>Trigger 1</Accordion.Trigger>\n              </Accordion.Header>\n              <Accordion.Panel>{PANEL_CONTENT_1}</Accordion.Panel>\n            </Accordion.Item>\n            <Accordion.Item value=\"second\">\n              <Accordion.Header>\n                <Accordion.Trigger>Trigger 2</Accordion.Trigger>\n              </Accordion.Header>\n              <Accordion.Panel>{PANEL_CONTENT_2}</Accordion.Panel>\n            </Accordion.Item>\n          </Accordion.Root>,\n        );\n\n        expect(screen.queryByText(PANEL_CONTENT_1)).not.toBe(null);\n        expect(screen.queryByText(PANEL_CONTENT_1)).toBeVisible();\n        expect(screen.queryByText(PANEL_CONTENT_1)).toHaveAttribute('data-open');\n\n        expect(screen.queryByText(PANEL_CONTENT_2)).toBe(null);\n      });\n    });\n  });\n\n  describe('prop: disabled', () => {\n    it('can disable the whole accordion', async () => {\n      await render(\n        <Accordion.Root defaultValue={[0]} disabled>\n          <Accordion.Item data-testid=\"item1\" value={0}>\n            <Accordion.Header>\n              <Accordion.Trigger>Trigger 1</Accordion.Trigger>\n            </Accordion.Header>\n            <Accordion.Panel>{PANEL_CONTENT_1}</Accordion.Panel>\n          </Accordion.Item>\n          <Accordion.Item data-testid=\"item2\" value={1}>\n            <Accordion.Header>\n              <Accordion.Trigger>Trigger 2</Accordion.Trigger>\n            </Accordion.Header>\n            <Accordion.Panel>{PANEL_CONTENT_2}</Accordion.Panel>\n          </Accordion.Item>\n        </Accordion.Root>,\n      );\n\n      const item1 = screen.getByTestId('item1');\n      const panel1 = screen.queryByText(PANEL_CONTENT_1);\n      const [header1, header2] = screen.getAllByRole('heading');\n      const [trigger1, trigger2] = screen.getAllByRole('button');\n      const item2 = screen.getByTestId('item2');\n\n      [item1, header1, trigger1, panel1, item2, header2, trigger2].forEach((element) => {\n        expect(element).toHaveAttribute('data-disabled');\n      });\n    });\n\n    it('can disable one accordion item', async () => {\n      await render(\n        <Accordion.Root defaultValue={[0]}>\n          <Accordion.Item data-testid=\"item1\" value={0} disabled>\n            <Accordion.Header>\n              <Accordion.Trigger>Trigger 1</Accordion.Trigger>\n            </Accordion.Header>\n            <Accordion.Panel>{PANEL_CONTENT_1}</Accordion.Panel>\n          </Accordion.Item>\n          <Accordion.Item data-testid=\"item2\" value={1}>\n            <Accordion.Header>\n              <Accordion.Trigger>Trigger 2</Accordion.Trigger>\n            </Accordion.Header>\n            <Accordion.Panel>{PANEL_CONTENT_2}</Accordion.Panel>\n          </Accordion.Item>\n        </Accordion.Root>,\n      );\n\n      const item1 = screen.getByTestId('item1');\n      const panel1 = screen.queryByText(PANEL_CONTENT_1);\n      const [header1, header2] = screen.getAllByRole('heading');\n      const [trigger1, trigger2] = screen.getAllByRole('button');\n      const item2 = screen.getByTestId('item2');\n\n      [item1, header1, trigger1, panel1].forEach((element) => {\n        expect(element).toHaveAttribute('data-disabled');\n      });\n      [item2, header2, trigger2].forEach((element) => {\n        expect(element).not.toHaveAttribute('data-disabled');\n      });\n    });\n  });\n\n  it('allows onMouseUp to call preventBaseUIHandler on the trigger', async () => {\n    await render(\n      <Accordion.Root>\n        <Accordion.Item value={0}>\n          <Accordion.Header>\n            <Accordion.Trigger onMouseUp={(event) => event.preventBaseUIHandler()}>\n              Trigger 1\n            </Accordion.Trigger>\n          </Accordion.Header>\n          <Accordion.Panel>{PANEL_CONTENT_1}</Accordion.Panel>\n        </Accordion.Item>\n      </Accordion.Root>,\n    );\n\n    const trigger = screen.getByRole('button', { name: 'Trigger 1' });\n\n    expect(() => fireEvent.mouseUp(trigger)).not.toThrow();\n  });\n\n  describe.skipIf(isJSDOM)('keyboard interactions', () => {\n    [true, false].forEach((isNativeButton) => {\n      describe(`rendering ${isNativeButton ? 'interactive' : 'non-interactive'} triggers`, () => {\n        ['Enter', 'Space'].forEach((key) => {\n          it(`key: ${key} toggles the Accordion open state`, async () => {\n            const { user } = await render(\n              <Accordion.Root>\n                <Accordion.Item>\n                  <Accordion.Header>\n                    <Accordion.Trigger\n                      nativeButton={isNativeButton}\n                      render={isNativeButton ? undefined : <span />}\n                    >\n                      Trigger 1\n                    </Accordion.Trigger>\n                  </Accordion.Header>\n                  <Accordion.Panel>{PANEL_CONTENT_1}</Accordion.Panel>\n                </Accordion.Item>\n              </Accordion.Root>,\n            );\n\n            const trigger = screen.getByRole('button');\n\n            expect(trigger).toHaveAttribute('aria-expanded', 'false');\n\n            expect(screen.queryByText(PANEL_CONTENT_1)).toBe(null);\n\n            await user.keyboard('[Tab]');\n            expect(trigger).toHaveFocus();\n            await user.keyboard(`[${key}]`);\n\n            expect(trigger).toHaveAttribute('aria-expanded', 'true');\n            expect(trigger).toHaveAttribute('data-panel-open');\n            expect(screen.queryByText(PANEL_CONTENT_1)).not.toBe(null);\n            expect(screen.queryByText(PANEL_CONTENT_1)).toBeVisible();\n            expect(screen.queryByText(PANEL_CONTENT_1)).toHaveAttribute('data-open');\n\n            await user.keyboard(`[${key}]`);\n\n            expect(trigger).toHaveAttribute('aria-expanded', 'false');\n            expect(screen.queryByText(PANEL_CONTENT_1)).toBe(null);\n          });\n        });\n\n        it('ArrowUp and ArrowDown moves focus between triggers and loops by default', async () => {\n          const { user } = await render(\n            <Accordion.Root>\n              <Accordion.Item>\n                <Accordion.Header>\n                  <Accordion.Trigger\n                    nativeButton={isNativeButton}\n                    render={isNativeButton ? undefined : <span />}\n                  >\n                    Trigger 1\n                  </Accordion.Trigger>\n                </Accordion.Header>\n                <Accordion.Panel>1</Accordion.Panel>\n              </Accordion.Item>\n              <Accordion.Item>\n                <Accordion.Header>\n                  <Accordion.Trigger>Trigger 2</Accordion.Trigger>\n                </Accordion.Header>\n                <Accordion.Panel>2</Accordion.Panel>\n              </Accordion.Item>\n            </Accordion.Root>,\n          );\n\n          const [trigger1, trigger2] = screen.getAllByRole('button');\n\n          await user.keyboard('[Tab]');\n          expect(trigger1).toHaveFocus();\n\n          await user.keyboard('[ArrowDown]');\n          expect(trigger2).toHaveFocus();\n\n          await user.keyboard('[ArrowUp]');\n          expect(trigger1).toHaveFocus();\n\n          await user.keyboard('[ArrowDown]');\n          expect(trigger2).toHaveFocus();\n\n          await user.keyboard('[ArrowDown]');\n          expect(trigger1).toHaveFocus();\n        });\n\n        it('Arrow keys should not put focus on disabled accordion items', async () => {\n          const { user } = await render(\n            <Accordion.Root>\n              <Accordion.Item>\n                <Accordion.Header>\n                  <Accordion.Trigger\n                    nativeButton={isNativeButton}\n                    render={isNativeButton ? undefined : <span />}\n                  >\n                    Trigger 1\n                  </Accordion.Trigger>\n                </Accordion.Header>\n                <Accordion.Panel>1</Accordion.Panel>\n              </Accordion.Item>\n              <Accordion.Item disabled>\n                <Accordion.Header>\n                  <Accordion.Trigger>Trigger 2</Accordion.Trigger>\n                </Accordion.Header>\n                <Accordion.Panel>2</Accordion.Panel>\n              </Accordion.Item>\n              <Accordion.Item>\n                <Accordion.Header>\n                  <Accordion.Trigger>Trigger 3</Accordion.Trigger>\n                </Accordion.Header>\n                <Accordion.Panel>This is the contents of Accordion.Panel 3</Accordion.Panel>\n              </Accordion.Item>\n            </Accordion.Root>,\n          );\n\n          const [trigger1, , trigger3] = screen.getAllByRole('button');\n\n          await user.keyboard('[Tab]');\n          expect(trigger1).toHaveFocus();\n\n          await user.keyboard('[ArrowDown]');\n          expect(trigger3).toHaveFocus();\n\n          await user.keyboard('[ArrowUp]');\n          expect(trigger1).toHaveFocus();\n        });\n\n        describe('key: End/Home', () => {\n          it('End key moves focus to the last trigger', async () => {\n            const { user } = await render(\n              <Accordion.Root>\n                <Accordion.Item>\n                  <Accordion.Header>\n                    <Accordion.Trigger\n                      nativeButton={isNativeButton}\n                      render={isNativeButton ? undefined : <span />}\n                    >\n                      Trigger 1\n                    </Accordion.Trigger>\n                  </Accordion.Header>\n                  <Accordion.Panel>1</Accordion.Panel>\n                </Accordion.Item>\n                <Accordion.Item disabled>\n                  <Accordion.Header>\n                    <Accordion.Trigger>Trigger 2</Accordion.Trigger>\n                  </Accordion.Header>\n                  <Accordion.Panel>2</Accordion.Panel>\n                </Accordion.Item>\n                <Accordion.Item>\n                  <Accordion.Header>\n                    <Accordion.Trigger>Trigger 3</Accordion.Trigger>\n                  </Accordion.Header>\n                  <Accordion.Panel>This is the contents of Accordion.Panel 3</Accordion.Panel>\n                </Accordion.Item>\n                <Accordion.Item>\n                  <Accordion.Header>\n                    <Accordion.Trigger>Trigger 4</Accordion.Trigger>\n                  </Accordion.Header>\n                  <Accordion.Panel>This is the contents of Accordion.Panel 4</Accordion.Panel>\n                </Accordion.Item>\n              </Accordion.Root>,\n            );\n\n            const [trigger1, , , trigger4] = screen.getAllByRole('button');\n\n            await user.keyboard('[Tab]');\n            expect(trigger1).toHaveFocus();\n\n            await user.keyboard('[End]');\n            expect(trigger4).toHaveFocus();\n          });\n\n          it('Home key moves focus to the first trigger', async () => {\n            const { user } = await render(\n              <Accordion.Root>\n                <Accordion.Item>\n                  <Accordion.Header>\n                    <Accordion.Trigger\n                      nativeButton={isNativeButton}\n                      render={isNativeButton ? undefined : <span />}\n                    >\n                      Trigger 1\n                    </Accordion.Trigger>\n                  </Accordion.Header>\n                  <Accordion.Panel>1</Accordion.Panel>\n                </Accordion.Item>\n                <Accordion.Item disabled>\n                  <Accordion.Header>\n                    <Accordion.Trigger>Trigger 2</Accordion.Trigger>\n                  </Accordion.Header>\n                  <Accordion.Panel>2</Accordion.Panel>\n                </Accordion.Item>\n                <Accordion.Item>\n                  <Accordion.Header>\n                    <Accordion.Trigger>Trigger 3</Accordion.Trigger>\n                  </Accordion.Header>\n                  <Accordion.Panel>This is the contents of Accordion.Panel 3</Accordion.Panel>\n                </Accordion.Item>\n                <Accordion.Item>\n                  <Accordion.Header>\n                    <Accordion.Trigger>Trigger 4</Accordion.Trigger>\n                  </Accordion.Header>\n                  <Accordion.Panel>This is the contents of Accordion.Panel 4</Accordion.Panel>\n                </Accordion.Item>\n              </Accordion.Root>,\n            );\n\n            const [trigger1, , , trigger4] = screen.getAllByRole('button');\n\n            await user.pointer({ keys: '[MouseLeft]', target: trigger4 });\n            expect(trigger4).toHaveFocus();\n\n            await user.keyboard('[Home]');\n            expect(trigger1).toHaveFocus();\n          });\n        });\n      });\n    });\n\n    it('does not affect composite keys on interactive elements in the panel', async () => {\n      const { user } = await render(\n        <Accordion.Root defaultValue={[0]}>\n          <Accordion.Item value={0}>\n            <Accordion.Header>\n              <Accordion.Trigger>Trigger 1</Accordion.Trigger>\n            </Accordion.Header>\n            <Accordion.Panel>\n              <input type=\"text\" defaultValue=\"abcd\" />\n            </Accordion.Panel>\n          </Accordion.Item>\n          <Accordion.Item value={1}>\n            <Accordion.Header>\n              <Accordion.Trigger>Trigger 2</Accordion.Trigger>\n            </Accordion.Header>\n            <Accordion.Panel>2</Accordion.Panel>\n          </Accordion.Item>\n        </Accordion.Root>,\n      );\n\n      const input = screen.getByRole('textbox') as HTMLInputElement;\n\n      await user.keyboard('[Tab]');\n      await user.keyboard('[Tab]');\n      expect(input).toHaveFocus();\n\n      // Firefox doesn't support document.getSelection() in inputs\n      expect(input.selectionStart).toBe(0);\n      expect(input.selectionEnd).toBe(4);\n\n      await user.keyboard('[ArrowLeft]');\n      expect(input.selectionStart).toBe(0);\n      expect(input.selectionEnd).toBe(0);\n    });\n\n    describe('prop: loopFocus', () => {\n      it('can disable focus looping between triggers', async () => {\n        const { user } = await render(\n          <Accordion.Root loopFocus={false}>\n            <Accordion.Item>\n              <Accordion.Header>\n                <Accordion.Trigger>Trigger 1</Accordion.Trigger>\n              </Accordion.Header>\n              <Accordion.Panel>1</Accordion.Panel>\n            </Accordion.Item>\n            <Accordion.Item>\n              <Accordion.Header>\n                <Accordion.Trigger>Trigger 2</Accordion.Trigger>\n              </Accordion.Header>\n              <Accordion.Panel>2</Accordion.Panel>\n            </Accordion.Item>\n          </Accordion.Root>,\n        );\n\n        const [trigger1, trigger2] = screen.getAllByRole('button');\n\n        await user.keyboard('[Tab]');\n        expect(trigger1).toHaveFocus();\n\n        await user.keyboard('[ArrowDown]');\n        expect(trigger2).toHaveFocus();\n\n        await user.keyboard('[ArrowDown]');\n        expect(trigger2).toHaveFocus();\n      });\n    });\n  });\n\n  describe('keyboard activation timing', () => {\n    [true, false].forEach((isNativeButton) => {\n      it(`opens and closes on Space keydown when rendering ${\n        isNativeButton ? 'interactive' : 'non-interactive'\n      } triggers`, async () => {\n        const onOpenChange = vi.fn();\n\n        const { user } = await render(\n          <Accordion.Root>\n            <Accordion.Item onOpenChange={onOpenChange}>\n              <Accordion.Header>\n                <Accordion.Trigger\n                  nativeButton={isNativeButton}\n                  render={isNativeButton ? undefined : <span />}\n                >\n                  Trigger 1\n                </Accordion.Trigger>\n              </Accordion.Header>\n              <Accordion.Panel>{PANEL_CONTENT_1}</Accordion.Panel>\n            </Accordion.Item>\n          </Accordion.Root>,\n        );\n\n        const trigger = screen.getByRole('button');\n\n        await user.keyboard('[Tab]');\n        expect(trigger).toHaveFocus();\n\n        fireEvent.keyDown(trigger, { key: ' ' });\n        expect(trigger).toHaveAttribute('aria-expanded', 'true');\n        expect(screen.queryByText(PANEL_CONTENT_1)).not.toBe(null);\n        expect(onOpenChange.mock.calls.length).toBe(1);\n        expect(onOpenChange.mock.calls[0][0]).toBe(true);\n\n        fireEvent.keyUp(trigger, { key: ' ' });\n        expect(trigger).toHaveAttribute('aria-expanded', 'true');\n        expect(onOpenChange.mock.calls.length).toBe(1);\n\n        fireEvent.keyDown(trigger, { key: ' ' });\n        expect(trigger).toHaveAttribute('aria-expanded', 'false');\n        expect(screen.queryByText(PANEL_CONTENT_1)).toBe(null);\n        expect(onOpenChange.mock.calls.length).toBe(2);\n        expect(onOpenChange.mock.calls[1][0]).toBe(false);\n\n        fireEvent.keyUp(trigger, { key: ' ' });\n        expect(trigger).toHaveAttribute('aria-expanded', 'false');\n        expect(onOpenChange.mock.calls.length).toBe(2);\n      });\n    });\n  });\n\n  describe.skipIf(isJSDOM)('prop: multiple', () => {\n    it('multiple items can be open when `multiple = true`', async () => {\n      const { user } = await render(\n        <Accordion.Root multiple>\n          <Accordion.Item>\n            <Accordion.Header>\n              <Accordion.Trigger>Trigger 1</Accordion.Trigger>\n            </Accordion.Header>\n            <Accordion.Panel>{PANEL_CONTENT_1}</Accordion.Panel>\n          </Accordion.Item>\n          <Accordion.Item>\n            <Accordion.Header>\n              <Accordion.Trigger>Trigger 2</Accordion.Trigger>\n            </Accordion.Header>\n            <Accordion.Panel>{PANEL_CONTENT_2}</Accordion.Panel>\n          </Accordion.Item>\n        </Accordion.Root>,\n      );\n\n      const [trigger1, trigger2] = screen.getAllByRole('button');\n\n      expect(trigger1).not.toHaveAttribute('data-panel-open');\n      expect(trigger2).not.toHaveAttribute('data-panel-open');\n      expect(screen.queryByText(PANEL_CONTENT_1)).toBe(null);\n      expect(screen.queryByText(PANEL_CONTENT_2)).toBe(null);\n\n      await user.pointer({ keys: '[MouseLeft]', target: trigger1 });\n      await user.pointer({ keys: '[MouseLeft]', target: trigger2 });\n\n      expect(screen.queryByText(PANEL_CONTENT_1)).toHaveAttribute('data-open');\n      expect(screen.queryByText(PANEL_CONTENT_2)).toHaveAttribute('data-open');\n      expect(trigger1).toHaveAttribute('data-panel-open');\n      expect(trigger2).toHaveAttribute('data-panel-open');\n    });\n\n    it('when false only one item can be open', async () => {\n      const { user } = await render(\n        <Accordion.Root multiple={false}>\n          <Accordion.Item>\n            <Accordion.Header>\n              <Accordion.Trigger>Trigger 1</Accordion.Trigger>\n            </Accordion.Header>\n            <Accordion.Panel>{PANEL_CONTENT_1}</Accordion.Panel>\n          </Accordion.Item>\n          <Accordion.Item>\n            <Accordion.Header>\n              <Accordion.Trigger>Trigger 2</Accordion.Trigger>\n            </Accordion.Header>\n            <Accordion.Panel>{PANEL_CONTENT_2}</Accordion.Panel>\n          </Accordion.Item>\n        </Accordion.Root>,\n      );\n\n      const [trigger1, trigger2] = screen.getAllByRole('button');\n\n      expect(screen.queryByText(PANEL_CONTENT_1)).toBe(null);\n      expect(screen.queryByText(PANEL_CONTENT_2)).toBe(null);\n      expect(trigger1).not.toHaveAttribute('data-panel-open');\n      expect(trigger2).not.toHaveAttribute('data-panel-open');\n\n      await user.pointer({ keys: '[MouseLeft]', target: trigger1 });\n\n      expect(screen.queryByText(PANEL_CONTENT_1)).toHaveAttribute('data-open');\n      expect(trigger1).toHaveAttribute('data-panel-open');\n\n      await user.pointer({ keys: '[MouseLeft]', target: trigger2 });\n\n      expect(screen.queryByText(PANEL_CONTENT_2)).toHaveAttribute('data-open');\n      expect(trigger2).toHaveAttribute('data-panel-open');\n      expect(screen.queryByText(PANEL_CONTENT_1)).toBe(null);\n      expect(trigger1).not.toHaveAttribute('data-panel-open');\n    });\n  });\n\n  describe.skipIf(isJSDOM)('horizontal orientation', () => {\n    it('ArrowLeft/Right moves focus in horizontal orientation', async () => {\n      const { user } = await render(\n        <Accordion.Root orientation=\"horizontal\">\n          <Accordion.Item>\n            <Accordion.Header>\n              <Accordion.Trigger>Trigger 1</Accordion.Trigger>\n            </Accordion.Header>\n            <Accordion.Panel>1</Accordion.Panel>\n          </Accordion.Item>\n          <Accordion.Item>\n            <Accordion.Header>\n              <Accordion.Trigger>Trigger 2</Accordion.Trigger>\n            </Accordion.Header>\n            <Accordion.Panel>2</Accordion.Panel>\n          </Accordion.Item>\n        </Accordion.Root>,\n      );\n\n      const [trigger1, trigger2] = screen.getAllByRole('button');\n\n      await user.keyboard('[Tab]');\n      expect(trigger1).toHaveFocus();\n\n      await user.keyboard('[ArrowRight]');\n      expect(trigger2).toHaveFocus();\n\n      await user.keyboard('[ArrowLeft]');\n      expect(trigger1).toHaveFocus();\n\n      await user.keyboard('[ArrowRight]');\n      expect(trigger2).toHaveFocus();\n\n      await user.keyboard('[ArrowRight]');\n      expect(trigger1).toHaveFocus();\n    });\n\n    describe.skipIf(isJSDOM)('RTL', () => {\n      it('ArrowLeft/Right is reversed for horizontal accordions in RTL mode', async () => {\n        const { user } = await render(\n          <DirectionProvider direction=\"rtl\">\n            <Accordion.Root orientation=\"horizontal\">\n              <Accordion.Item>\n                <Accordion.Header>\n                  <Accordion.Trigger>Trigger 1</Accordion.Trigger>\n                </Accordion.Header>\n                <Accordion.Panel>1</Accordion.Panel>\n              </Accordion.Item>\n              <Accordion.Item>\n                <Accordion.Header>\n                  <Accordion.Trigger>Trigger 2</Accordion.Trigger>\n                </Accordion.Header>\n                <Accordion.Panel>2</Accordion.Panel>\n              </Accordion.Item>\n            </Accordion.Root>\n          </DirectionProvider>,\n        );\n\n        const [trigger1, trigger2] = screen.getAllByRole('button');\n\n        await user.keyboard('[Tab]');\n        expect(trigger1).toHaveFocus();\n\n        await user.keyboard('[ArrowLeft]');\n        expect(trigger2).toHaveFocus();\n\n        await user.keyboard('[ArrowRight]');\n        expect(trigger1).toHaveFocus();\n\n        await user.keyboard('[ArrowLeft]');\n        expect(trigger2).toHaveFocus();\n\n        await user.keyboard('[ArrowLeft]');\n        expect(trigger1).toHaveFocus();\n      });\n    });\n  });\n\n  describe.skipIf(isJSDOM)('prop: onValueChange', () => {\n    it('default item value', async () => {\n      const onValueChange = vi.fn();\n\n      const { user } = await render(\n        <Accordion.Root onValueChange={onValueChange} multiple>\n          <Accordion.Item value={0}>\n            <Accordion.Header>\n              <Accordion.Trigger>Trigger 1</Accordion.Trigger>\n            </Accordion.Header>\n            <Accordion.Panel>1</Accordion.Panel>\n          </Accordion.Item>\n          <Accordion.Item value={1}>\n            <Accordion.Header>\n              <Accordion.Trigger>Trigger 2</Accordion.Trigger>\n            </Accordion.Header>\n            <Accordion.Panel>2</Accordion.Panel>\n          </Accordion.Item>\n        </Accordion.Root>,\n      );\n\n      const [trigger1, trigger2] = screen.getAllByRole('button');\n\n      expect(onValueChange.mock.calls.length).toBe(0);\n\n      await user.pointer({ keys: '[MouseLeft]', target: trigger1 });\n\n      expect(onValueChange.mock.calls.length).toBe(1);\n      expect(onValueChange.mock.lastCall?.[0]).toEqual([0]);\n\n      await user.pointer({ keys: '[MouseLeft]', target: trigger2 });\n\n      expect(onValueChange.mock.calls.length).toBe(2);\n      expect(onValueChange.mock.lastCall?.[0]).toEqual([0, 1]);\n    });\n\n    it('custom item value', async () => {\n      const onValueChange = vi.fn();\n\n      const { user } = await render(\n        <Accordion.Root onValueChange={onValueChange} multiple>\n          <Accordion.Item value=\"one\">\n            <Accordion.Header>\n              <Accordion.Trigger>Trigger 1</Accordion.Trigger>\n            </Accordion.Header>\n            <Accordion.Panel>1</Accordion.Panel>\n          </Accordion.Item>\n          <Accordion.Item value=\"two\">\n            <Accordion.Header>\n              <Accordion.Trigger>Trigger 2</Accordion.Trigger>\n            </Accordion.Header>\n            <Accordion.Panel>2</Accordion.Panel>\n          </Accordion.Item>\n        </Accordion.Root>,\n      );\n\n      const [trigger1, trigger2] = screen.getAllByRole('button');\n\n      expect(onValueChange.mock.calls.length).toBe(0);\n\n      await user.pointer({ keys: '[MouseLeft]', target: trigger2 });\n\n      expect(onValueChange.mock.calls.length).toBe(1);\n      expect(onValueChange.mock.calls[0][0]).toEqual(['two']);\n\n      await user.pointer({ keys: '[MouseLeft]', target: trigger1 });\n\n      expect(onValueChange.mock.calls.length).toBe(2);\n      expect(onValueChange.mock.calls[1][0]).toEqual(['two', 'one']);\n    });\n\n    it('`multiple` is false', async () => {\n      const onValueChange = vi.fn();\n\n      const { user } = await render(\n        <Accordion.Root onValueChange={onValueChange} multiple={false}>\n          <Accordion.Item value=\"one\">\n            <Accordion.Header>\n              <Accordion.Trigger>Trigger 1</Accordion.Trigger>\n            </Accordion.Header>\n            <Accordion.Panel>1</Accordion.Panel>\n          </Accordion.Item>\n          <Accordion.Item value=\"two\">\n            <Accordion.Header>\n              <Accordion.Trigger>Trigger 2</Accordion.Trigger>\n            </Accordion.Header>\n            <Accordion.Panel>2</Accordion.Panel>\n          </Accordion.Item>\n        </Accordion.Root>,\n      );\n\n      const [trigger1, trigger2] = screen.getAllByRole('button');\n\n      expect(onValueChange.mock.calls.length).toBe(0);\n\n      await user.pointer({ keys: '[MouseLeft]', target: trigger1 });\n\n      expect(onValueChange.mock.calls.length).toBe(1);\n      expect(onValueChange.mock.calls[0][0]).toEqual(['one']);\n\n      await user.pointer({ keys: '[MouseLeft]', target: trigger2 });\n\n      expect(onValueChange.mock.calls.length).toBe(2);\n      expect(onValueChange.mock.calls[1][0]).toEqual(['two']);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/accordion/root/AccordionRoot.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useControlled } from '@base-ui/utils/useControlled';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { warn } from '@base-ui/utils/warn';\nimport { BaseUIComponentProps, Orientation } from '../../utils/types';\nimport { CompositeList } from '../../composite/list/CompositeList';\nimport { useDirection } from '../../direction-provider/DirectionContext';\nimport { AccordionRootContext } from './AccordionRootContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport {\n  createChangeEventDetails,\n  type BaseUIChangeEventDetails,\n} from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\n\nconst rootStateAttributesMapping = {\n  value: () => null,\n};\n\n/**\n * Groups all parts of the accordion.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Accordion](https://base-ui.com/react/components/accordion)\n */\nexport const AccordionRoot = React.forwardRef(function AccordionRoot<Value = any>(\n  componentProps: AccordionRoot.Props<Value>,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const {\n    render,\n    className,\n    disabled = false,\n    hiddenUntilFound: hiddenUntilFoundProp,\n    keepMounted: keepMountedProp,\n    loopFocus = true,\n    onValueChange: onValueChangeProp,\n    multiple = false,\n    orientation = 'vertical',\n    value: valueProp,\n    defaultValue: defaultValueProp,\n    ...elementProps\n  } = componentProps;\n\n  const direction = useDirection();\n\n  if (process.env.NODE_ENV !== 'production') {\n    // eslint-disable-next-line react-hooks/rules-of-hooks\n    useIsoLayoutEffect(() => {\n      if (hiddenUntilFoundProp && keepMountedProp === false) {\n        warn(\n          'The `keepMounted={false}` prop on a Accordion.Root will be ignored when using `hiddenUntilFound` since it requires Panels to remain mounted when closed.',\n        );\n      }\n    }, [hiddenUntilFoundProp, keepMountedProp]);\n  }\n\n  // memoized to allow omitting both defaultValue and value\n  // which would otherwise trigger a warning in useControlled\n  const defaultValue = React.useMemo(() => {\n    if (valueProp === undefined) {\n      return defaultValueProp ?? [];\n    }\n\n    return undefined;\n  }, [valueProp, defaultValueProp]);\n\n  const onValueChange = useStableCallback(onValueChangeProp);\n\n  const accordionItemRefs = React.useRef<(HTMLElement | null)[]>([]);\n\n  const [value, setValue] = useControlled({\n    controlled: valueProp,\n    default: defaultValue,\n    name: 'Accordion',\n    state: 'value',\n  });\n\n  const handleValueChange = useStableCallback(\n    (newValue: AccordionRoot.Value<Value>[number], nextOpen: boolean) => {\n      const details = createChangeEventDetails(REASONS.none);\n      if (!multiple) {\n        const nextValue = value[0] === newValue ? [] : [newValue];\n        onValueChange(nextValue, details);\n        if (details.isCanceled) {\n          return;\n        }\n        setValue(nextValue);\n      } else if (nextOpen) {\n        const nextOpenValues = value.slice();\n        nextOpenValues.push(newValue);\n        onValueChange(nextOpenValues, details);\n        if (details.isCanceled) {\n          return;\n        }\n        setValue(nextOpenValues);\n      } else {\n        const nextOpenValues = value.filter((v) => v !== newValue);\n        onValueChange(nextOpenValues, details);\n        if (details.isCanceled) {\n          return;\n        }\n        setValue(nextOpenValues);\n      }\n    },\n  );\n\n  const state: AccordionRoot.State<Value> = React.useMemo(\n    () => ({\n      value,\n      disabled,\n      orientation,\n    }),\n    [value, disabled, orientation],\n  );\n\n  const contextValue: AccordionRootContext<Value> = React.useMemo(\n    () => ({\n      accordionItemRefs,\n      direction,\n      disabled,\n      handleValueChange,\n      hiddenUntilFound: hiddenUntilFoundProp ?? false,\n      keepMounted: keepMountedProp ?? false,\n      loopFocus,\n      orientation,\n      state,\n      value,\n    }),\n    [\n      direction,\n      disabled,\n      handleValueChange,\n      hiddenUntilFoundProp,\n      keepMountedProp,\n      loopFocus,\n      orientation,\n      state,\n      value,\n    ],\n  );\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: forwardedRef,\n    props: [\n      {\n        dir: direction,\n        role: 'region',\n      },\n      elementProps,\n    ],\n    stateAttributesMapping: rootStateAttributesMapping,\n  });\n\n  return (\n    <AccordionRootContext.Provider value={contextValue}>\n      <CompositeList elementsRef={accordionItemRefs}>{element}</CompositeList>\n    </AccordionRootContext.Provider>\n  );\n}) as {\n  <Value = any>(props: AccordionRoot.Props<Value>): React.JSX.Element;\n};\n\nexport type AccordionValue<Value = any> = Value[];\n\nexport interface AccordionRootState<Value = any> {\n  /**\n   * The current value.\n   */\n  value: AccordionValue<Value>;\n  /**\n   * Whether the component should ignore user interaction.\n   */\n  disabled: boolean;\n  /**\n   * The component orientation.\n   */\n  orientation: Orientation;\n}\n\nexport interface AccordionRootProps<Value = any> extends BaseUIComponentProps<\n  'div',\n  AccordionRoot.State<Value>\n> {\n  /**\n   * The controlled value of the item(s) that should be expanded.\n   *\n   * To render an uncontrolled accordion, use the `defaultValue` prop instead.\n   */\n  value?: AccordionValue<Value> | undefined;\n  /**\n   * The uncontrolled value of the item(s) that should be initially expanded.\n   *\n   * To render a controlled accordion, use the `value` prop instead.\n   */\n  defaultValue?: AccordionValue<Value> | undefined;\n  /**\n   * Whether the component should ignore user interaction.\n   * @default false\n   */\n  disabled?: boolean | undefined;\n  /**\n   * Allows the browser’s built-in page search to find and expand the panel contents.\n   *\n   * Overrides the `keepMounted` prop and uses `hidden=\"until-found\"`\n   * to hide the element without removing it from the DOM.\n   * @default false\n   */\n  hiddenUntilFound?: boolean | undefined;\n  /**\n   * Whether to keep the element in the DOM while the panel is closed.\n   * This prop is ignored when `hiddenUntilFound` is used.\n   * @default false\n   */\n  keepMounted?: boolean | undefined;\n  /**\n   * Whether to loop keyboard focus back to the first item\n   * when the end of the list is reached while using the arrow keys.\n   * @default true\n   */\n  loopFocus?: boolean | undefined;\n  /**\n   * Event handler called when an accordion item is expanded or collapsed.\n   * Provides the new value as an argument.\n   */\n  onValueChange?:\n    | ((value: AccordionValue<Value>, eventDetails: AccordionRootChangeEventDetails) => void)\n    | undefined;\n  /**\n   * Whether multiple items can be open at the same time.\n   * @default false\n   */\n  multiple?: boolean | undefined;\n  /**\n   * The visual orientation of the accordion.\n   * Controls whether roving focus uses left/right or up/down arrow keys.\n   * @default 'vertical'\n   */\n  orientation?: Orientation | undefined;\n}\n\nexport type AccordionRootChangeEventReason = typeof REASONS.triggerPress | typeof REASONS.none;\n\nexport type AccordionRootChangeEventDetails =\n  BaseUIChangeEventDetails<AccordionRoot.ChangeEventReason>;\n\nexport namespace AccordionRoot {\n  export type Value<TValue = any> = AccordionValue<TValue>;\n  export type State<TValue = any> = AccordionRootState<TValue>;\n  export type Props<TValue = any> = AccordionRootProps<TValue>;\n  export type ChangeEventReason = AccordionRootChangeEventReason;\n  export type ChangeEventDetails = AccordionRootChangeEventDetails;\n}\n"
  },
  {
    "path": "packages/react/src/accordion/root/AccordionRootContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport type { Orientation } from '../../utils/types';\nimport type { TextDirection } from '../../direction-provider';\nimport type { AccordionRoot } from './AccordionRoot';\n\nexport interface AccordionRootContext<Value = any> {\n  accordionItemRefs: React.RefObject<(HTMLElement | null)[]>;\n  direction: TextDirection;\n  disabled: boolean;\n  handleValueChange: (newValue: AccordionRoot.Value<Value>[number], nextOpen: boolean) => void;\n  hiddenUntilFound: boolean;\n  keepMounted: boolean;\n  loopFocus: boolean;\n  orientation: Orientation;\n  state: AccordionRoot.State<Value>;\n  value: AccordionRoot.Value<Value>;\n}\n\nexport const AccordionRootContext = React.createContext<AccordionRootContext<any> | undefined>(\n  undefined,\n);\n\nexport function useAccordionRootContext<Value = any>() {\n  const context = React.useContext<AccordionRootContext<Value> | undefined>(AccordionRootContext);\n  if (context === undefined) {\n    throw new Error(\n      'Base UI: AccordionRootContext is missing. Accordion parts must be placed within <Accordion.Root>.',\n    );\n  }\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/accordion/root/AccordionRootDataAttributes.ts",
    "content": "export enum AccordionRootDataAttributes {\n  /**\n   * Present when the accordion is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Indicates the orientation of the accordion.\n   */\n  orientation = 'data-orientation',\n}\n"
  },
  {
    "path": "packages/react/src/accordion/trigger/AccordionTrigger.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { Accordion } from '@base-ui/react/accordion';\nimport { screen } from '@mui/internal-test-utils';\nimport { describeConformance, createRenderer } from '#test-utils';\n\ndescribe('<Accordion.Trigger />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Accordion.Trigger />, () => ({\n    refInstanceof: window.HTMLButtonElement,\n    testComponentPropWith: 'button',\n    button: true,\n    render: (node) =>\n      render(\n        <Accordion.Root>\n          <Accordion.Item>{node}</Accordion.Item>\n        </Accordion.Root>,\n      ),\n  }));\n\n  it('keeps a non-native trigger tabbable', async () => {\n    await render(\n      <Accordion.Root>\n        <Accordion.Item>\n          <Accordion.Header>\n            <Accordion.Trigger nativeButton={false} render={<span />}>\n              Trigger\n            </Accordion.Trigger>\n          </Accordion.Header>\n          <Accordion.Panel>Panel</Accordion.Panel>\n        </Accordion.Item>\n      </Accordion.Root>,\n    );\n\n    const trigger = screen.getByRole('button', { name: 'Trigger' });\n    expect(trigger).toHaveAttribute('tabindex', '0');\n  });\n});\n"
  },
  {
    "path": "packages/react/src/accordion/trigger/AccordionTrigger.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { isElementDisabled } from '@base-ui/utils/isElementDisabled';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { triggerOpenStateMapping } from '../../utils/collapsibleOpenStateMapping';\nimport { BaseUIComponentProps, NativeButtonProps } from '../../utils/types';\nimport { useButton } from '../../use-button';\nimport { useCollapsibleRootContext } from '../../collapsible/root/CollapsibleRootContext';\nimport {\n  ARROW_DOWN,\n  ARROW_UP,\n  ARROW_RIGHT,\n  ARROW_LEFT,\n  HOME,\n  END,\n  stopEvent,\n} from '../../composite/composite';\nimport { useAccordionRootContext } from '../root/AccordionRootContext';\nimport type { AccordionItemState } from '../item/AccordionItem';\nimport { useAccordionItemContext } from '../item/AccordionItemContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\n\nconst SUPPORTED_KEYS = new Set([ARROW_DOWN, ARROW_UP, ARROW_RIGHT, ARROW_LEFT, HOME, END]);\n\nfunction getActiveTriggers(accordionItemRefs: { current: (HTMLElement | null)[] }): HTMLElement[] {\n  const { current: accordionItemElements } = accordionItemRefs;\n\n  const output: HTMLElement[] = [];\n\n  for (let i = 0; i < accordionItemElements.length; i += 1) {\n    const section = accordionItemElements[i];\n    if (!isElementDisabled(section)) {\n      const trigger = section?.querySelector<HTMLElement>('[type=\"button\"], [role=\"button\"]');\n      if (trigger && !isElementDisabled(trigger)) {\n        output.push(trigger);\n      }\n    }\n  }\n\n  return output;\n}\n\n/**\n * A button that opens and closes the corresponding panel.\n * Renders a `<button>` element.\n *\n * Documentation: [Base UI Accordion](https://base-ui.com/react/components/accordion)\n */\n\nexport const AccordionTrigger = React.forwardRef(function AccordionTrigger(\n  componentProps: AccordionTrigger.Props,\n  forwardedRef: React.ForwardedRef<HTMLElement>,\n) {\n  const {\n    disabled: disabledProp,\n    className,\n    id: idProp,\n    render,\n    nativeButton = true,\n    ...elementProps\n  } = componentProps;\n\n  const { panelId, open, handleTrigger, disabled: contextDisabled } = useCollapsibleRootContext();\n\n  const disabled = disabledProp ?? contextDisabled;\n\n  const { getButtonProps, buttonRef } = useButton({\n    disabled,\n    focusableWhenDisabled: true,\n    native: nativeButton,\n    composite: true,\n  });\n\n  const { accordionItemRefs, direction, loopFocus, orientation } = useAccordionRootContext();\n\n  const isRtl = direction === 'rtl';\n  const isHorizontal = orientation === 'horizontal';\n\n  const { state, setTriggerId, triggerId: id } = useAccordionItemContext();\n\n  useIsoLayoutEffect(() => {\n    if (idProp) {\n      setTriggerId(idProp);\n    }\n    return () => {\n      setTriggerId(undefined);\n    };\n  }, [idProp, setTriggerId]);\n\n  const props = React.useMemo(\n    () => ({\n      'aria-controls': open ? panelId : undefined,\n      'aria-expanded': open,\n      id,\n      tabIndex: 0,\n      onClick: handleTrigger,\n      onKeyDown(event: React.KeyboardEvent) {\n        if (!SUPPORTED_KEYS.has(event.key)) {\n          return;\n        }\n\n        stopEvent(event);\n\n        const triggers = getActiveTriggers(accordionItemRefs);\n\n        const numOfEnabledTriggers = triggers.length;\n        const lastIndex = numOfEnabledTriggers - 1;\n\n        let nextIndex = -1;\n\n        const thisIndex = triggers.indexOf(event.target as HTMLButtonElement);\n\n        function toNext() {\n          if (loopFocus) {\n            nextIndex = thisIndex + 1 > lastIndex ? 0 : thisIndex + 1;\n          } else {\n            nextIndex = Math.min(thisIndex + 1, lastIndex);\n          }\n        }\n\n        function toPrev() {\n          if (loopFocus) {\n            nextIndex = thisIndex === 0 ? lastIndex : thisIndex - 1;\n          } else {\n            nextIndex = thisIndex - 1;\n          }\n        }\n\n        switch (event.key) {\n          case ARROW_DOWN:\n            if (!isHorizontal) {\n              toNext();\n            }\n            break;\n          case ARROW_UP:\n            if (!isHorizontal) {\n              toPrev();\n            }\n            break;\n          case ARROW_RIGHT:\n            if (isHorizontal) {\n              if (isRtl) {\n                toPrev();\n              } else {\n                toNext();\n              }\n            }\n            break;\n          case ARROW_LEFT:\n            if (isHorizontal) {\n              if (isRtl) {\n                toNext();\n              } else {\n                toPrev();\n              }\n            }\n            break;\n          case 'Home':\n            nextIndex = 0;\n            break;\n          case 'End':\n            nextIndex = lastIndex;\n            break;\n          default:\n            break;\n        }\n\n        if (nextIndex > -1) {\n          triggers[nextIndex].focus();\n        }\n      },\n    }),\n    [accordionItemRefs, handleTrigger, id, isHorizontal, isRtl, loopFocus, open, panelId],\n  );\n\n  const element = useRenderElement('button', componentProps, {\n    state,\n    ref: [forwardedRef, buttonRef],\n    props: [props, elementProps, getButtonProps],\n    stateAttributesMapping: triggerOpenStateMapping,\n  });\n\n  return element;\n});\n\nexport interface AccordionTriggerState extends AccordionItemState {}\n\nexport interface AccordionTriggerProps\n  extends NativeButtonProps, BaseUIComponentProps<'button', AccordionTriggerState> {}\n\nexport namespace AccordionTrigger {\n  export type State = AccordionTriggerState;\n  export type Props = AccordionTriggerProps;\n}\n"
  },
  {
    "path": "packages/react/src/accordion/trigger/AccordionTriggerDataAttributes.ts",
    "content": "export enum AccordionTriggerDataAttributes {\n  /**\n   * Present when the accordion panel is open.\n   */\n  panelOpen = 'data-panel-open',\n  /**\n   * Present when the accordion item is disabled.\n   */\n  disabled = 'data-disabled',\n}\n"
  },
  {
    "path": "packages/react/src/alert-dialog/handle.ts",
    "content": "import { DialogHandle } from '../dialog/store/DialogHandle';\nimport { DialogStore } from '../dialog/store/DialogStore';\n\nexport function createAlertDialogHandle<Payload>(): DialogHandle<Payload> {\n  return new DialogHandle<Payload>(\n    new DialogStore<Payload>({\n      modal: true,\n      disablePointerDismissal: true,\n      role: 'alertdialog',\n    }),\n  );\n}\n"
  },
  {
    "path": "packages/react/src/alert-dialog/index.parts.ts",
    "content": "export { AlertDialogRoot as Root } from './root/AlertDialogRoot';\nexport { DialogBackdrop as Backdrop } from '../dialog/backdrop/DialogBackdrop';\nexport { DialogClose as Close } from '../dialog/close/DialogClose';\nexport { DialogDescription as Description } from '../dialog/description/DialogDescription';\nexport { DialogPopup as Popup } from '../dialog/popup/DialogPopup';\nexport { DialogPortal as Portal } from '../dialog/portal/DialogPortal';\nexport { DialogTitle as Title } from '../dialog/title/DialogTitle';\nexport { DialogTrigger as Trigger } from '../dialog/trigger/DialogTrigger';\nexport { DialogViewport as Viewport } from '../dialog/viewport/DialogViewport';\nexport { createAlertDialogHandle as createHandle } from './handle';\nexport { DialogHandle as Handle } from '../dialog/store/DialogHandle';\n"
  },
  {
    "path": "packages/react/src/alert-dialog/index.ts",
    "content": "export * as AlertDialog from './index.parts';\n\nexport type * from './root/AlertDialogRoot';\n"
  },
  {
    "path": "packages/react/src/alert-dialog/root/AlertDialogRoot.spec.tsx",
    "content": "import * as React from 'react';\nimport { expectType } from '#test-utils';\nimport { AlertDialog } from '@base-ui/react/alert-dialog';\n\nconst numberPayloadHandle = AlertDialog.createHandle<number>();\n\nconst rootWithDirectChildren = (\n  <AlertDialog.Root handle={numberPayloadHandle}>\n    <AlertDialog.Portal />\n  </AlertDialog.Root>\n);\n\nconst rootWithFunctionChildren = (\n  <AlertDialog.Root handle={numberPayloadHandle}>\n    {({ payload }) => {\n      expectType<number | undefined, typeof payload>(payload);\n      return null;\n    }}\n  </AlertDialog.Root>\n);\n\nconst triggerWithPayload = <AlertDialog.Trigger handle={numberPayloadHandle} payload={42} />;\nconst triggerWithoutPayload = <AlertDialog.Trigger handle={numberPayloadHandle} />;\n\nconst triggerWithInvalidPayload = (\n  // @ts-expect-error\n  <AlertDialog.Trigger handle={numberPayloadHandle} payload={'invalid'} />\n);\n"
  },
  {
    "path": "packages/react/src/alert-dialog/root/AlertDialogRoot.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { act, screen, waitFor } from '@mui/internal-test-utils';\nimport { AlertDialog } from '@base-ui/react/alert-dialog';\nimport { createRenderer, isJSDOM, popupConformanceTests } from '#test-utils';\nimport { REASONS } from '../../utils/reasons';\n\ndescribe('<AlertDialog.Root />', () => {\n  const { render } = createRenderer();\n\n  beforeEach(() => {\n    globalThis.BASE_UI_ANIMATIONS_DISABLED = true;\n  });\n\n  popupConformanceTests({\n    createComponent: (props) => (\n      <AlertDialog.Root {...props.root}>\n        <AlertDialog.Trigger {...props.trigger}>Open dialog</AlertDialog.Trigger>\n        <AlertDialog.Portal {...props.portal}>\n          <AlertDialog.Popup {...props.popup}>Dialog</AlertDialog.Popup>\n        </AlertDialog.Portal>\n      </AlertDialog.Root>\n    ),\n    render,\n    triggerMouseAction: 'click',\n    expectedPopupRole: 'alertdialog',\n    expectedAriaHasPopupValue: 'dialog',\n  });\n\n  it('ARIA attributes', async () => {\n    await render(\n      <AlertDialog.Root open>\n        <AlertDialog.Trigger />\n        <AlertDialog.Portal>\n          <AlertDialog.Backdrop />\n          <AlertDialog.Popup>\n            <AlertDialog.Title>title text</AlertDialog.Title>\n            <AlertDialog.Description>description text</AlertDialog.Description>\n          </AlertDialog.Popup>\n        </AlertDialog.Portal>\n      </AlertDialog.Root>,\n    );\n\n    const popup = screen.queryByRole('alertdialog');\n    expect(popup).not.toBe(null);\n\n    expect(screen.getByText('title text').getAttribute('id')).toBe(\n      popup?.getAttribute('aria-labelledby'),\n    );\n    expect(screen.getByText('description text').getAttribute('id')).toBe(\n      popup?.getAttribute('aria-describedby'),\n    );\n  });\n\n  describe('prop: onOpenChange', () => {\n    it('calls onOpenChange with the new open state', async () => {\n      const handleOpenChange = vi.fn();\n\n      const { user } = await render(\n        <AlertDialog.Root onOpenChange={handleOpenChange}>\n          <AlertDialog.Trigger>Open</AlertDialog.Trigger>\n          <AlertDialog.Portal>\n            <AlertDialog.Popup>\n              <AlertDialog.Close>Close</AlertDialog.Close>\n            </AlertDialog.Popup>\n          </AlertDialog.Portal>\n        </AlertDialog.Root>,\n      );\n\n      expect(handleOpenChange.mock.calls.length).toBe(0);\n\n      const openButton = screen.getByText('Open');\n      await user.click(openButton);\n\n      expect(handleOpenChange.mock.calls.length).toBe(1);\n      expect(handleOpenChange.mock.calls[0][0]).toBe(true);\n\n      const closeButton = screen.getByText('Close');\n      await user.click(closeButton);\n\n      expect(handleOpenChange.mock.calls.length).toBe(2);\n      expect(handleOpenChange.mock.calls[1][0]).toBe(false);\n    });\n\n    it('calls onOpenChange with the reason for change when clicked on trigger and close button', async () => {\n      const handleOpenChange = vi.fn();\n\n      const { user } = await render(\n        <AlertDialog.Root onOpenChange={handleOpenChange}>\n          <AlertDialog.Trigger>Open</AlertDialog.Trigger>\n          <AlertDialog.Portal>\n            <AlertDialog.Popup>\n              <AlertDialog.Close>Close</AlertDialog.Close>\n            </AlertDialog.Popup>\n          </AlertDialog.Portal>\n        </AlertDialog.Root>,\n      );\n\n      const openButton = screen.getByText('Open');\n      await user.click(openButton);\n\n      expect(handleOpenChange.mock.calls.length).toBe(1);\n      expect(handleOpenChange.mock.calls[0][1].reason).toBe(REASONS.triggerPress);\n\n      const closeButton = screen.getByText('Close');\n      await user.click(closeButton);\n\n      expect(handleOpenChange.mock.calls.length).toBe(2);\n      expect(handleOpenChange.mock.calls[1][1].reason).toBe(REASONS.closePress);\n    });\n\n    it('calls onOpenChange with the reason for change when pressed Esc while the dialog is open', async () => {\n      const handleOpenChange = vi.fn();\n\n      const { user } = await render(\n        <AlertDialog.Root defaultOpen onOpenChange={handleOpenChange}>\n          <AlertDialog.Trigger>Open</AlertDialog.Trigger>\n          <AlertDialog.Portal>\n            <AlertDialog.Popup>\n              <AlertDialog.Close>Close</AlertDialog.Close>\n            </AlertDialog.Popup>\n          </AlertDialog.Portal>\n        </AlertDialog.Root>,\n      );\n\n      await user.keyboard('[Escape]');\n\n      expect(handleOpenChange.mock.calls.length).toBe(1);\n      expect(handleOpenChange.mock.calls[0][1].reason).toBe(REASONS.escapeKey);\n    });\n\n    it('does not close when the backdrop is clicked', async () => {\n      const handleOpenChange = vi.fn();\n\n      const { user } = await render(\n        <AlertDialog.Root defaultOpen onOpenChange={handleOpenChange}>\n          <AlertDialog.Trigger>Open</AlertDialog.Trigger>\n          <AlertDialog.Portal>\n            <AlertDialog.Popup>\n              <AlertDialog.Close>Close</AlertDialog.Close>\n            </AlertDialog.Popup>\n          </AlertDialog.Portal>\n        </AlertDialog.Root>,\n      );\n\n      await user.click(screen.getByRole('presentation', { hidden: true }));\n\n      expect(handleOpenChange.mock.calls.length).toBe(0);\n      expect(screen.queryByRole('alertdialog')).not.toBe(null);\n    });\n  });\n\n  describe('prop: actionsRef', () => {\n    it('unmounts the alert dialog when the `unmount` method is called', async () => {\n      const actionsRef = {\n        current: {\n          unmount: vi.fn(),\n          close: vi.fn(),\n        },\n      };\n\n      const { user } = await render(\n        <AlertDialog.Root\n          actionsRef={actionsRef}\n          onOpenChange={(open, details) => {\n            details.preventUnmountOnClose();\n          }}\n        >\n          <AlertDialog.Trigger>Open</AlertDialog.Trigger>\n          <AlertDialog.Portal>\n            <AlertDialog.Popup />\n          </AlertDialog.Portal>\n        </AlertDialog.Root>,\n      );\n\n      const trigger = screen.getByText('Open');\n      await user.click(trigger);\n\n      await waitFor(() => {\n        expect(screen.queryByRole('alertdialog')).not.toBe(null);\n      });\n\n      await user.click(trigger);\n\n      await waitFor(() => {\n        expect(screen.queryByRole('alertdialog')).not.toBe(null);\n      });\n\n      await act(async () => actionsRef.current.unmount());\n\n      await waitFor(() => {\n        expect(screen.queryByRole('alertdialog')).toBe(null);\n      });\n    });\n  });\n\n  describe.skipIf(isJSDOM)('multiple triggers within Root', () => {\n    type NumberPayload = { payload: number | undefined };\n\n    it('opens the alert dialog with any trigger', async () => {\n      const { user } = await render(\n        <AlertDialog.Root>\n          <AlertDialog.Trigger>Trigger 1</AlertDialog.Trigger>\n          <AlertDialog.Trigger>Trigger 2</AlertDialog.Trigger>\n          <AlertDialog.Trigger>Trigger 3</AlertDialog.Trigger>\n\n          <AlertDialog.Portal>\n            <AlertDialog.Popup>\n              Alert dialog content\n              <AlertDialog.Close>Close</AlertDialog.Close>\n            </AlertDialog.Popup>\n          </AlertDialog.Portal>\n        </AlertDialog.Root>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n      const trigger3 = screen.getByRole('button', { name: 'Trigger 3' });\n\n      expect(screen.queryByText('Alert dialog content')).toBe(null);\n\n      await user.click(trigger1);\n      await waitFor(() => {\n        expect(screen.queryByText('Alert dialog content')).not.toBe(null);\n      });\n      await user.click(screen.getByText('Close'));\n      await waitFor(() => {\n        expect(screen.queryByText('Alert dialog content')).toBe(null);\n      });\n\n      await user.click(trigger2);\n      await waitFor(() => {\n        expect(screen.queryByText('Alert dialog content')).not.toBe(null);\n      });\n      await user.click(screen.getByText('Close'));\n      await waitFor(() => {\n        expect(screen.queryByText('Alert dialog content')).toBe(null);\n      });\n\n      await user.click(trigger3);\n      await waitFor(() => {\n        expect(screen.queryByText('Alert dialog content')).not.toBe(null);\n      });\n    });\n\n    it('sets the payload and renders content based on its value', async () => {\n      const { user } = await render(\n        <AlertDialog.Root>\n          {({ payload }: NumberPayload) => (\n            <React.Fragment>\n              <AlertDialog.Trigger payload={1}>Trigger 1</AlertDialog.Trigger>\n              <AlertDialog.Trigger payload={2}>Trigger 2</AlertDialog.Trigger>\n\n              <AlertDialog.Portal>\n                <AlertDialog.Popup>\n                  <span data-testid=\"content\">{payload}</span>\n                  <AlertDialog.Close>Close</AlertDialog.Close>\n                </AlertDialog.Popup>\n              </AlertDialog.Portal>\n            </React.Fragment>\n          )}\n        </AlertDialog.Root>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n\n      await user.click(trigger1);\n      await waitFor(() => {\n        expect(screen.getByTestId('content').textContent).toBe('1');\n      });\n\n      await user.click(trigger2);\n      await waitFor(() => {\n        expect(screen.getByTestId('content').textContent).toBe('2');\n      });\n    });\n\n    it('reuses the popup DOM node when switching triggers', async () => {\n      const { user } = await render(\n        <AlertDialog.Root>\n          {({ payload }: NumberPayload) => (\n            <React.Fragment>\n              <AlertDialog.Trigger payload={1}>Trigger 1</AlertDialog.Trigger>\n              <AlertDialog.Trigger payload={2}>Trigger 2</AlertDialog.Trigger>\n\n              <AlertDialog.Portal>\n                <AlertDialog.Popup data-testid=\"alert-dialog-popup\">\n                  <span>{payload}</span>\n                </AlertDialog.Popup>\n              </AlertDialog.Portal>\n            </React.Fragment>\n          )}\n        </AlertDialog.Root>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n\n      await user.click(trigger1);\n      const popupElement = screen.getByTestId('alert-dialog-popup');\n\n      await user.click(trigger2);\n      expect(screen.getByTestId('alert-dialog-popup')).toBe(popupElement);\n    });\n\n    it('synchronizes ARIA attributes on the active trigger', async () => {\n      const { user } = await render(\n        <AlertDialog.Root>\n          <AlertDialog.Trigger>Trigger 1</AlertDialog.Trigger>\n          <AlertDialog.Trigger>Trigger 2</AlertDialog.Trigger>\n\n          <AlertDialog.Portal>\n            <AlertDialog.Popup data-testid=\"alert-dialog-popup\">\n              Alert dialog content\n            </AlertDialog.Popup>\n          </AlertDialog.Portal>\n        </AlertDialog.Root>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n\n      expect(trigger1).toHaveAttribute('aria-expanded', 'false');\n      expect(trigger2).toHaveAttribute('aria-expanded', 'false');\n\n      await user.click(trigger1);\n\n      const dialog = await screen.findByRole('alertdialog');\n      const trigger1Controls = trigger1.getAttribute('aria-controls');\n      expect(trigger1Controls).not.toBe(null);\n      expect(dialog.getAttribute('id')).toBe(trigger1Controls);\n      await waitFor(() => {\n        expect(trigger1).toHaveAttribute('aria-expanded', 'true');\n      });\n      expect(trigger2).toHaveAttribute('aria-expanded', 'false');\n    });\n  });\n\n  describe.skipIf(isJSDOM)('multiple detached triggers', () => {\n    type NumberPayload = { payload: number | undefined };\n\n    it('opens the alert dialog with any trigger', async () => {\n      const testDialog = AlertDialog.createHandle();\n      const { user } = await render(\n        <div>\n          <AlertDialog.Trigger handle={testDialog}>Trigger 1</AlertDialog.Trigger>\n          <AlertDialog.Trigger handle={testDialog}>Trigger 2</AlertDialog.Trigger>\n          <AlertDialog.Trigger handle={testDialog}>Trigger 3</AlertDialog.Trigger>\n\n          <AlertDialog.Root handle={testDialog}>\n            <AlertDialog.Portal>\n              <AlertDialog.Popup>\n                Alert dialog content\n                <AlertDialog.Close>Close</AlertDialog.Close>\n              </AlertDialog.Popup>\n            </AlertDialog.Portal>\n          </AlertDialog.Root>\n        </div>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n      const trigger3 = screen.getByRole('button', { name: 'Trigger 3' });\n\n      expect(screen.queryByText('Alert dialog content')).toBe(null);\n\n      await user.click(trigger1);\n      await waitFor(() => {\n        expect(screen.queryByText('Alert dialog content')).not.toBe(null);\n      });\n      await user.click(screen.getByText('Close'));\n      await waitFor(() => {\n        expect(screen.queryByText('Alert dialog content')).toBe(null);\n      });\n\n      await user.click(trigger2);\n      await waitFor(() => {\n        expect(screen.queryByText('Alert dialog content')).not.toBe(null);\n      });\n      await user.click(screen.getByText('Close'));\n      await waitFor(() => {\n        expect(screen.queryByText('Alert dialog content')).toBe(null);\n      });\n\n      await user.click(trigger3);\n      await waitFor(() => {\n        expect(screen.queryByText('Alert dialog content')).not.toBe(null);\n      });\n    });\n\n    it('sets the payload and renders content based on its value', async () => {\n      const testDialog = AlertDialog.createHandle<number>();\n      const { user } = await render(\n        <div>\n          <AlertDialog.Trigger handle={testDialog} payload={1}>\n            Trigger 1\n          </AlertDialog.Trigger>\n          <AlertDialog.Trigger handle={testDialog} payload={2}>\n            Trigger 2\n          </AlertDialog.Trigger>\n\n          <AlertDialog.Root handle={testDialog}>\n            {({ payload }: NumberPayload) => (\n              <AlertDialog.Portal>\n                <AlertDialog.Popup>\n                  <span data-testid=\"content\">{payload}</span>\n                  <AlertDialog.Close>Close</AlertDialog.Close>\n                </AlertDialog.Popup>\n              </AlertDialog.Portal>\n            )}\n          </AlertDialog.Root>\n        </div>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n\n      await user.click(trigger1);\n      await waitFor(() => {\n        expect(screen.getByTestId('content').textContent).toBe('1');\n      });\n\n      await user.click(trigger2);\n      await waitFor(() => {\n        expect(screen.getByTestId('content').textContent).toBe('2');\n      });\n    });\n\n    it('reuses the popup DOM node when switching triggers', async () => {\n      const testDialog = AlertDialog.createHandle<number>();\n      const { user } = await render(\n        <React.Fragment>\n          <AlertDialog.Trigger handle={testDialog} payload={1}>\n            Trigger 1\n          </AlertDialog.Trigger>\n          <AlertDialog.Trigger handle={testDialog} payload={2}>\n            Trigger 2\n          </AlertDialog.Trigger>\n\n          <AlertDialog.Root handle={testDialog}>\n            {({ payload }: NumberPayload) => (\n              <AlertDialog.Portal>\n                <AlertDialog.Popup data-testid=\"alert-dialog-popup\">\n                  <span>{payload}</span>\n                </AlertDialog.Popup>\n              </AlertDialog.Portal>\n            )}\n          </AlertDialog.Root>\n        </React.Fragment>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n\n      await user.click(trigger1);\n      const popupElement = screen.getByTestId('alert-dialog-popup');\n\n      await user.click(trigger2);\n      expect(screen.getByTestId('alert-dialog-popup')).toBe(popupElement);\n    });\n  });\n\n  describe('imperative actions on the handle', () => {\n    it('keeps the alert dialog open when the backdrop is clicked', async () => {\n      const handle = AlertDialog.createHandle();\n\n      const { user } = await render(\n        <React.Fragment>\n          <AlertDialog.Trigger handle={handle}>Open</AlertDialog.Trigger>\n          <AlertDialog.Root handle={handle}>\n            <AlertDialog.Portal>\n              <AlertDialog.Popup>\n                <AlertDialog.Close>Close</AlertDialog.Close>\n              </AlertDialog.Popup>\n            </AlertDialog.Portal>\n          </AlertDialog.Root>\n        </React.Fragment>,\n      );\n\n      const trigger = screen.getByRole('button', { name: 'Open' });\n      await user.click(trigger);\n\n      expect(await screen.findByRole('alertdialog')).not.toBe(null);\n\n      const backdrop = await screen.findByRole('presentation', { hidden: true });\n      await user.click(backdrop);\n\n      await waitFor(() => {\n        expect(screen.queryByRole('alertdialog')).not.toBe(null);\n      });\n    });\n\n    it('opens and closes the dialog', async () => {\n      const dialog = AlertDialog.createHandle();\n      await render(\n        <div>\n          <AlertDialog.Trigger handle={dialog} id=\"trigger\">\n            Trigger\n          </AlertDialog.Trigger>\n          <AlertDialog.Root handle={dialog}>\n            <AlertDialog.Portal>\n              <AlertDialog.Popup data-testid=\"content\">Content</AlertDialog.Popup>\n            </AlertDialog.Portal>\n          </AlertDialog.Root>\n        </div>,\n      );\n\n      const trigger = screen.getByRole('button', { name: 'Trigger' });\n      expect(screen.queryByRole('alertdialog')).toBe(null);\n\n      await act(() => dialog.open('trigger'));\n      await waitFor(() => {\n        expect(screen.queryByRole('alertdialog')).not.toBe(null);\n      });\n\n      expect(screen.getByTestId('content').textContent).toBe('Content');\n      expect(trigger).toHaveAttribute('aria-expanded', 'true');\n\n      await act(() => dialog.close());\n      await waitFor(() => {\n        expect(screen.queryByRole('alertdialog')).toBe(null);\n      });\n\n      expect(trigger).toHaveAttribute('aria-expanded', 'false');\n    });\n\n    it('sets the payload associated with the trigger', async () => {\n      const dialog = AlertDialog.createHandle<number>();\n      await render(\n        <div>\n          <AlertDialog.Trigger handle={dialog} id=\"trigger1\" payload={1}>\n            Trigger 1\n          </AlertDialog.Trigger>\n          <AlertDialog.Trigger handle={dialog} id=\"trigger2\" payload={2}>\n            Trigger 2\n          </AlertDialog.Trigger>\n          <AlertDialog.Root handle={dialog}>\n            {({ payload }: { payload: number | undefined }) => (\n              <AlertDialog.Portal>\n                <AlertDialog.Popup data-testid=\"content\">{payload}</AlertDialog.Popup>\n              </AlertDialog.Portal>\n            )}\n          </AlertDialog.Root>\n        </div>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n      expect(screen.queryByRole('alertdialog')).toBe(null);\n\n      await act(() => dialog.open('trigger2'));\n      await waitFor(() => {\n        expect(screen.queryByRole('alertdialog')).not.toBe(null);\n      });\n\n      expect(screen.getByTestId('content').textContent).toBe('2');\n      expect(trigger2).toHaveAttribute('aria-expanded', 'true');\n      expect(trigger1).not.toHaveAttribute('aria-expanded', 'true');\n\n      await act(() => dialog.close());\n      await waitFor(() => {\n        expect(screen.queryByRole('alertdialog')).toBe(null);\n      });\n\n      expect(trigger2).toHaveAttribute('aria-expanded', 'false');\n    });\n\n    it('sets the payload programmatically', async () => {\n      const dialog = AlertDialog.createHandle<number>();\n      await render(\n        <div>\n          <AlertDialog.Trigger handle={dialog} id=\"trigger1\" payload={1}>\n            Trigger 1\n          </AlertDialog.Trigger>\n          <AlertDialog.Trigger handle={dialog} id=\"trigger2\" payload={2}>\n            Trigger 2\n          </AlertDialog.Trigger>\n          <AlertDialog.Root handle={dialog}>\n            {({ payload }: { payload: number | undefined }) => (\n              <AlertDialog.Portal>\n                <AlertDialog.Popup data-testid=\"content\">{payload}</AlertDialog.Popup>\n              </AlertDialog.Portal>\n            )}\n          </AlertDialog.Root>\n        </div>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n      expect(screen.queryByRole('alertdialog')).toBe(null);\n\n      await act(() => dialog.openWithPayload(8));\n      await waitFor(() => {\n        expect(screen.queryByRole('alertdialog')).not.toBe(null);\n      });\n\n      expect(screen.getByTestId('content').textContent).toBe('8');\n      expect(trigger1).not.toHaveAttribute('aria-expanded', 'true');\n      expect(trigger2).not.toHaveAttribute('aria-expanded', 'true');\n\n      await act(() => dialog.close());\n      await waitFor(() => {\n        expect(screen.queryByRole('alertdialog')).toBe(null);\n      });\n    });\n  });\n\n  describe.skipIf(isJSDOM)('modality', () => {\n    it('makes other interactive elements on the page inert when a modal dialog is open', async () => {\n      await render(\n        <AlertDialog.Root defaultOpen>\n          <AlertDialog.Trigger>Open Dialog</AlertDialog.Trigger>\n          <AlertDialog.Portal>\n            <AlertDialog.Popup>\n              <AlertDialog.Close>Close Dialog</AlertDialog.Close>\n            </AlertDialog.Popup>\n          </AlertDialog.Portal>\n        </AlertDialog.Root>,\n      );\n\n      expect(screen.getByRole('presentation', { hidden: true })).not.toBe(null);\n    });\n  });\n\n  describe.skipIf(isJSDOM)('prop: onOpenChangeComplete', () => {\n    it('is called on close when there is no exit animation defined', async () => {\n      const onOpenChangeComplete = vi.fn();\n\n      function Test() {\n        const [open, setOpen] = React.useState(true);\n        return (\n          <div>\n            <button onClick={() => setOpen(false)}>Close</button>\n            <AlertDialog.Root open={open} onOpenChangeComplete={onOpenChangeComplete}>\n              <AlertDialog.Portal>\n                <AlertDialog.Popup data-testid=\"popup\" />\n              </AlertDialog.Portal>\n            </AlertDialog.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<Test />);\n\n      const closeButton = screen.getByText('Close');\n      await user.click(closeButton);\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('popup')).toBe(null);\n      });\n\n      expect(onOpenChangeComplete.mock.calls[0][0]).toBe(true);\n      expect(onOpenChangeComplete.mock.lastCall?.[0]).toBe(false);\n    });\n\n    it('is called on close when the exit animation finishes', async () => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n      const onOpenChangeComplete = vi.fn();\n\n      function Test() {\n        const style = `\n          @keyframes test-anim {\n            to {\n              opacity: 0;\n            }\n          }\n\n          .animation-test-indicator[data-ending-style] {\n            animation: test-anim 1ms;\n          }\n        `;\n\n        const [open, setOpen] = React.useState(true);\n\n        return (\n          <div>\n            {/* eslint-disable-next-line react/no-danger */}\n            <style dangerouslySetInnerHTML={{ __html: style }} />\n            <button onClick={() => setOpen(false)}>Close</button>\n            <AlertDialog.Root open={open} onOpenChangeComplete={onOpenChangeComplete}>\n              <AlertDialog.Portal>\n                <AlertDialog.Popup className=\"animation-test-indicator\" data-testid=\"popup\" />\n              </AlertDialog.Portal>\n            </AlertDialog.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<Test />);\n\n      expect(screen.getByTestId('popup')).not.toBe(null);\n\n      // Wait for open animation to finish\n      await waitFor(() => {\n        expect(onOpenChangeComplete.mock.calls[0][0]).toBe(true);\n      });\n\n      const closeButton = screen.getByText('Close');\n      await user.click(closeButton);\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('popup')).toBe(null);\n      });\n\n      expect(onOpenChangeComplete.mock.lastCall?.[0]).toBe(false);\n    });\n\n    it('is called on open when there is no enter animation defined', async () => {\n      const onOpenChangeComplete = vi.fn();\n\n      function Test() {\n        const [open, setOpen] = React.useState(false);\n        return (\n          <div>\n            <button onClick={() => setOpen(true)}>Open</button>\n            <AlertDialog.Root open={open} onOpenChangeComplete={onOpenChangeComplete}>\n              <AlertDialog.Portal>\n                <AlertDialog.Popup data-testid=\"popup\" />\n              </AlertDialog.Portal>\n            </AlertDialog.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<Test />);\n\n      const openButton = screen.getByText('Open');\n      await user.click(openButton);\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('popup')).not.toBe(null);\n      });\n\n      expect(onOpenChangeComplete.mock.calls.length).toBe(2);\n      expect(onOpenChangeComplete.mock.calls[0][0]).toBe(true);\n    });\n\n    it('is called on open when the enter animation finishes', async () => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n      const onOpenChangeComplete = vi.fn();\n\n      function Test() {\n        const style = `\n          @keyframes test-anim {\n            from {\n              opacity: 0;\n            }\n          }\n\n          .animation-test-indicator[data-starting-style] {\n            animation: test-anim 1ms;\n          }\n        `;\n\n        const [open, setOpen] = React.useState(false);\n\n        return (\n          <div>\n            {/* eslint-disable-next-line react/no-danger */}\n            <style dangerouslySetInnerHTML={{ __html: style }} />\n            <button onClick={() => setOpen(true)}>Open</button>\n            <AlertDialog.Root\n              open={open}\n              onOpenChange={setOpen}\n              onOpenChangeComplete={onOpenChangeComplete}\n            >\n              <AlertDialog.Portal>\n                <AlertDialog.Popup className=\"animation-test-indicator\" data-testid=\"popup\" />\n              </AlertDialog.Portal>\n            </AlertDialog.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<Test />);\n\n      const openButton = screen.getByText('Open');\n      await user.click(openButton);\n\n      // Wait for open animation to finish\n      await waitFor(() => {\n        expect(onOpenChangeComplete.mock.calls[0][0]).toBe(true);\n      });\n\n      expect(screen.queryByTestId('popup')).not.toBe(null);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/alert-dialog/root/AlertDialogRoot.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useRefWithInit } from '@base-ui/utils/useRefWithInit';\nimport { useDialogRoot } from '../../dialog/root/useDialogRoot';\nimport { DialogRootContext, useDialogRootContext } from '../../dialog/root/DialogRootContext';\nimport { BaseUIChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { DialogStore } from '../../dialog/store/DialogStore';\nimport { DialogHandle } from '../../dialog/store/DialogHandle';\nimport type { DialogRoot } from '../../dialog/root/DialogRoot';\n\n/**\n * Groups all parts of the alert dialog.\n * Doesn’t render its own HTML element.\n *\n * Documentation: [Base UI Alert Dialog](https://base-ui.com/react/components/alert-dialog)\n */\nexport function AlertDialogRoot<Payload>(props: AlertDialogRoot.Props<Payload>) {\n  const {\n    children,\n    open: openProp,\n    defaultOpen = false,\n    onOpenChange,\n    onOpenChangeComplete,\n    actionsRef,\n    handle,\n    triggerId: triggerIdProp,\n    defaultTriggerId: defaultTriggerIdProp = null,\n  } = props;\n\n  const parentDialogRootContext = useDialogRootContext();\n  const nested = Boolean(parentDialogRootContext);\n\n  const store = useRefWithInit(() => {\n    return (\n      handle?.store ??\n      new DialogStore<Payload>({\n        open: defaultOpen,\n        openProp,\n        activeTriggerId: defaultTriggerIdProp,\n        triggerIdProp,\n        modal: true,\n        disablePointerDismissal: true,\n        nested,\n        role: 'alertdialog',\n      })\n    );\n  }).current;\n\n  store.useControlledProp('openProp', openProp);\n  store.useControlledProp('triggerIdProp', triggerIdProp);\n  store.useSyncedValue('nested', nested);\n  store.useContextCallback('onOpenChange', onOpenChange);\n  store.useContextCallback('onOpenChangeComplete', onOpenChangeComplete);\n\n  const payload = store.useState('payload') as Payload | undefined;\n\n  useDialogRoot({\n    store,\n    actionsRef,\n    parentContext: parentDialogRootContext?.store.context,\n    onOpenChange,\n    triggerIdProp,\n  });\n\n  const contextValue: DialogRootContext<Payload> = React.useMemo(() => ({ store }), [store]);\n\n  return (\n    <DialogRootContext.Provider value={contextValue as DialogRootContext}>\n      {typeof children === 'function' ? children({ payload }) : children}\n    </DialogRootContext.Provider>\n  );\n}\n\nexport interface AlertDialogRootState {}\n\nexport interface AlertDialogRootProps<Payload = unknown> extends Omit<\n  DialogRoot.Props<Payload>,\n  'modal' | 'disablePointerDismissal' | 'onOpenChange' | 'actionsRef' | 'handle'\n> {\n  /**\n   * Event handler called when the dialog is opened or closed.\n   */\n  onOpenChange?:\n    | ((open: boolean, eventDetails: AlertDialogRoot.ChangeEventDetails) => void)\n    | undefined;\n  /**\n   * A ref to imperative actions.\n   * - `unmount`: When specified, the dialog will not be unmounted when closed.\n   * Instead, the `unmount` function must be called to unmount the dialog manually.\n   * Useful when the dialog's animation is controlled by an external library.\n   * - `close`: Closes the dialog imperatively when called.\n   */\n  actionsRef?: React.RefObject<AlertDialogRoot.Actions | null> | undefined;\n  /**\n   * A handle to associate the alert dialog with a trigger.\n   * If specified, allows external triggers to control the alert dialog's open state.\n   * Can be created with the AlertDialog.createHandle() method.\n   */\n  handle?: DialogHandle<Payload> | undefined;\n}\n\nexport type AlertDialogRootActions = DialogRoot.Actions;\n\nexport type AlertDialogRootChangeEventReason = DialogRoot.ChangeEventReason;\nexport type AlertDialogRootChangeEventDetails =\n  BaseUIChangeEventDetails<AlertDialogRoot.ChangeEventReason> & {\n    preventUnmountOnClose(): void;\n  };\n\nexport namespace AlertDialogRoot {\n  export type State = AlertDialogRootState;\n  export type Props<Payload = unknown> = AlertDialogRootProps<Payload>;\n  export type Actions = AlertDialogRootActions;\n  export type ChangeEventReason = AlertDialogRootChangeEventReason;\n  export type ChangeEventDetails = AlertDialogRootChangeEventDetails;\n}\n"
  },
  {
    "path": "packages/react/src/autocomplete/index.parts.ts",
    "content": "export { AutocompleteRoot as Root } from './root/AutocompleteRoot';\nexport { AutocompleteValue as Value } from './value/AutocompleteValue';\n\nexport { ComboboxTrigger as Trigger } from '../combobox/trigger/ComboboxTrigger';\nexport { ComboboxInput as Input } from '../combobox/input/ComboboxInput';\nexport { ComboboxInputGroup as InputGroup } from '../combobox/input-group/ComboboxInputGroup';\nexport { ComboboxIcon as Icon } from '../combobox/icon/ComboboxIcon';\nexport { ComboboxClear as Clear } from '../combobox/clear/ComboboxClear';\nexport { ComboboxList as List } from '../combobox/list/ComboboxList';\nexport { ComboboxStatus as Status } from '../combobox/status/ComboboxStatus';\nexport { ComboboxPortal as Portal } from '../combobox/portal/ComboboxPortal';\nexport { ComboboxBackdrop as Backdrop } from '../combobox/backdrop/ComboboxBackdrop';\nexport { ComboboxPositioner as Positioner } from '../combobox/positioner/ComboboxPositioner';\nexport { ComboboxPopup as Popup } from '../combobox/popup/ComboboxPopup';\nexport { ComboboxArrow as Arrow } from '../combobox/arrow/ComboboxArrow';\nexport { ComboboxGroup as Group } from '../combobox/group/ComboboxGroup';\nexport { ComboboxGroupLabel as GroupLabel } from '../combobox/group-label/ComboboxGroupLabel';\nexport { ComboboxItem as Item } from '../combobox/item/ComboboxItem';\nexport { ComboboxRow as Row } from '../combobox/row/ComboboxRow';\nexport { ComboboxCollection as Collection } from '../combobox/collection/ComboboxCollection';\nexport { ComboboxEmpty as Empty } from '../combobox/empty/ComboboxEmpty';\n\nexport { Separator } from '../separator/Separator';\n\nexport { useCoreFilter as useFilter } from '../combobox/root/utils/useFilter';\nexport { useFilteredItems } from '../combobox/root/utils/useFilteredItems';\n"
  },
  {
    "path": "packages/react/src/autocomplete/index.ts",
    "content": "export * as Autocomplete from './index.parts';\n\nexport type * from './root/AutocompleteRoot';\nexport type * from './value/AutocompleteValue';\n\nexport type {\n  ComboboxTriggerProps as AutocompleteTriggerProps,\n  ComboboxTriggerState as AutocompleteTriggerState,\n} from '../combobox/trigger/ComboboxTrigger';\nexport type {\n  ComboboxInputProps as AutocompleteInputProps,\n  ComboboxInputState as AutocompleteInputState,\n} from '../combobox/input/ComboboxInput';\nexport type {\n  ComboboxInputGroupProps as AutocompleteInputGroupProps,\n  ComboboxInputGroupState as AutocompleteInputGroupState,\n} from '../combobox/input-group/ComboboxInputGroup';\nexport type {\n  ComboboxPopupProps as AutocompletePopupProps,\n  ComboboxPopupState as AutocompletePopupState,\n} from '../combobox/popup/ComboboxPopup';\nexport type {\n  ComboboxPositionerProps as AutocompletePositionerProps,\n  ComboboxPositionerState as AutocompletePositionerState,\n} from '../combobox/positioner/ComboboxPositioner';\nexport type {\n  ComboboxListProps as AutocompleteListProps,\n  ComboboxListState as AutocompleteListState,\n} from '../combobox/list/ComboboxList';\nexport type {\n  ComboboxItemProps as AutocompleteItemProps,\n  ComboboxItemState as AutocompleteItemState,\n} from '../combobox/item/ComboboxItem';\nexport type {\n  ComboboxArrowProps as AutocompleteArrowProps,\n  ComboboxArrowState as AutocompleteArrowState,\n} from '../combobox/arrow/ComboboxArrow';\nexport type {\n  ComboboxBackdropProps as AutocompleteBackdropProps,\n  ComboboxBackdropState as AutocompleteBackdropState,\n} from '../combobox/backdrop/ComboboxBackdrop';\nexport type { ComboboxPortalProps as AutocompletePortalProps } from '../combobox/portal/ComboboxPortal';\nexport type {\n  ComboboxGroupProps as AutocompleteGroupProps,\n  ComboboxGroupState as AutocompleteGroupState,\n} from '../combobox/group/ComboboxGroup';\nexport type {\n  ComboboxGroupLabelProps as AutocompleteGroupLabelProps,\n  ComboboxGroupLabelState as AutocompleteGroupLabelState,\n} from '../combobox/group-label/ComboboxGroupLabel';\nexport type {\n  ComboboxEmptyProps as AutocompleteEmptyProps,\n  ComboboxEmptyState as AutocompleteEmptyState,\n} from '../combobox/empty/ComboboxEmpty';\nexport type {\n  ComboboxStatusProps as AutocompleteStatusProps,\n  ComboboxStatusState as AutocompleteStatusState,\n} from '../combobox/status/ComboboxStatus';\nexport type { ComboboxCollectionProps as AutocompleteCollectionProps } from '../combobox/collection/ComboboxCollection';\n\nexport type {\n  Filter as AutocompleteFilter,\n  UseFilterOptions as AutocompleteFilterOptions,\n} from '../combobox/root/utils/useFilter';\n"
  },
  {
    "path": "packages/react/src/autocomplete/item/AutocompleteItem.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport { screen, waitFor } from '@mui/internal-test-utils';\nimport { createRenderer } from '#test-utils';\nimport { Autocomplete } from '@base-ui/react/autocomplete';\n\ndescribe('<Autocomplete.Item />', () => {\n  const { render } = createRenderer();\n\n  describe('prop: onClick', () => {\n    it('calls onClick when clicked with a pointer', async () => {\n      const handleClick = vi.fn();\n      const { user } = await render(\n        <Autocomplete.Root items={['apple', 'banana']} openOnInputClick>\n          <Autocomplete.Input data-testid=\"input\" />\n          <Autocomplete.Portal>\n            <Autocomplete.Positioner>\n              <Autocomplete.Popup>\n                <Autocomplete.List>\n                  {(item: string) => (\n                    <Autocomplete.Item key={item} value={item} onClick={handleClick}>\n                      {item}\n                    </Autocomplete.Item>\n                  )}\n                </Autocomplete.List>\n              </Autocomplete.Popup>\n            </Autocomplete.Positioner>\n          </Autocomplete.Portal>\n        </Autocomplete.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      await user.click(input);\n\n      const option = screen.getByRole('option', { name: 'banana' });\n      await user.click(option);\n\n      expect(handleClick.mock.calls.length).toBe(1);\n    });\n\n    it('calls onClick when selected with Enter key (via root interaction)', async () => {\n      const handleClick = vi.fn();\n      const { user } = await render(\n        <Autocomplete.Root items={['one', 'two']} openOnInputClick>\n          <Autocomplete.Input data-testid=\"input\" />\n          <Autocomplete.Portal>\n            <Autocomplete.Positioner>\n              <Autocomplete.Popup>\n                <Autocomplete.List>\n                  {(item: string) => (\n                    <Autocomplete.Item key={item} value={item} onClick={handleClick}>\n                      {item}\n                    </Autocomplete.Item>\n                  )}\n                </Autocomplete.List>\n              </Autocomplete.Popup>\n            </Autocomplete.Positioner>\n          </Autocomplete.Portal>\n        </Autocomplete.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      await user.click(input);\n      await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n\n      // Move highlight to an option then press Enter to select it\n      await user.keyboard('{ArrowDown}');\n      await user.keyboard('{ArrowDown}');\n      await user.keyboard('{Enter}');\n\n      expect(handleClick.mock.calls.length).toBe(1);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/autocomplete/root/AutocompleteRoot.spec.tsx",
    "content": "import * as React from 'react';\nimport { Autocomplete } from '@base-ui/react/autocomplete';\n\nconst objectItems = [\n  { value: 'a', label: 'apple' },\n  { value: 'b', label: 'banana' },\n  { value: 'c', label: 'cherry' },\n];\n\nconst objectItemsReadonly = [\n  { value: 'a', label: 'apple' },\n  { value: 'b', label: 'banana' },\n  { value: 'c', label: 'cherry' },\n] as const;\n\nconst groupItemsReadonly = [\n  {\n    value: 'fruits',\n    items: [\n      { value: 'a', label: 'apple' },\n      { value: 'b', label: 'banana' },\n      { value: 'c', label: 'cherry' },\n    ],\n  },\n  {\n    value: 'vegetables',\n    items: [\n      { value: 'd', label: 'daikon' },\n      { value: 'e', label: 'endive' },\n      { value: 'f', label: 'fennel' },\n    ],\n  },\n] as const;\n\n<Autocomplete.Root\n  items={objectItems}\n  itemToStringValue={(item) => {\n    return item.value;\n  }}\n/>;\n\n<Autocomplete.Root\n  items={groupItemsReadonly}\n  itemToStringValue={(item) => {\n    return item.label;\n  }}\n/>;\n\n<Autocomplete.Root\n  items={groupItemsReadonly}\n  itemToStringValue={(item) => {\n    // @ts-expect-error - item is the nested item from groups, not the group itself\n    return item.items;\n  }}\n/>;\n\n<Autocomplete.Root\n  items={objectItems}\n  defaultValue=\"a\"\n  onValueChange={(value) => {\n    value.startsWith('a');\n  }}\n/>;\n\n<Autocomplete.Root\n  items={objectItemsReadonly}\n  defaultValue=\"a\"\n  onValueChange={(value) => {\n    value.startsWith('a');\n  }}\n/>;\n\n<Autocomplete.Root\n  items={objectItems}\n  value=\"a\"\n  onValueChange={(value) => {\n    value.startsWith('a');\n  }}\n/>;\n\n// @ts-expect-error value refers to the input value, not the item object\n<Autocomplete.Root items={objectItems} value={objectItems[0]} />;\n\n<Autocomplete.Root\n  items={objectItems}\n  defaultValue=\"a\"\n  itemToStringValue={(item) => {\n    return item.value;\n  }}\n/>;\n\n<Autocomplete.Root\n  defaultValue=\"javascript\"\n  onValueChange={(value) => {\n    // @ts-expect-error\n    value.pop();\n  }}\n/>;\n\n<Autocomplete.Root\n  defaultValue=\"test\"\n  onValueChange={(value) => {\n    value.length;\n  }}\n/>;\n\nfunction App2() {\n  const [value, setValue] = React.useState('a');\n  return (\n    <Autocomplete.Root\n      value={value}\n      onValueChange={(newValue) => {\n        newValue.length;\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "packages/react/src/autocomplete/root/AutocompleteRoot.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { act, fireEvent, flushMicrotasks, screen, waitFor } from '@mui/internal-test-utils';\nimport { createRenderer, isJSDOM } from '#test-utils';\nimport { Autocomplete } from '@base-ui/react/autocomplete';\nimport { Field } from '@base-ui/react/field';\nimport { Form } from '@base-ui/react/form';\n\ndescribe('<Autocomplete.Root />', () => {\n  beforeEach(() => {\n    globalThis.BASE_UI_ANIMATIONS_DISABLED = true;\n  });\n\n  const { render } = createRenderer();\n\n  describe('keyboard interactions', () => {\n    it('closes popup on Tab after selecting with Enter and typing again', async () => {\n      const { user } = await render(\n        <Autocomplete.Root items={['alpha', 'alpine', 'beta']} autoHighlight>\n          <Autocomplete.Input />\n          <Autocomplete.Portal>\n            <Autocomplete.Positioner>\n              <Autocomplete.Popup>\n                <Autocomplete.List>\n                  {(item: string) => (\n                    <Autocomplete.Item key={item} value={item}>\n                      {item}\n                    </Autocomplete.Item>\n                  )}\n                </Autocomplete.List>\n              </Autocomplete.Popup>\n            </Autocomplete.Positioner>\n          </Autocomplete.Portal>\n          <button />\n        </Autocomplete.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n\n      await user.click(input);\n      await user.type(input, 'al');\n\n      const firstOption = await screen.findByRole('option', { name: 'alpha' });\n      expect(firstOption).toHaveAttribute('data-highlighted');\n\n      await user.keyboard('{Enter}');\n      expect(input.value).toBe('alpha');\n\n      await waitFor(() => {\n        expect(screen.queryByRole('listbox')).toBe(null);\n      });\n\n      await user.clear(input);\n      await user.type(input, 'a');\n\n      await waitFor(() => {\n        expect(screen.queryByRole('listbox')).not.toBe(null);\n      });\n\n      await user.tab();\n\n      await waitFor(() => {\n        expect(screen.queryByRole('listbox')).toBe(null);\n      });\n    });\n  });\n\n  it('should handle browser autofill', async () => {\n    await render(\n      <Field.Root name=\"auto\">\n        <Autocomplete.Root defaultValue=\"\">\n          <Autocomplete.Input data-testid=\"input\" />\n          <Autocomplete.Portal>\n            <Autocomplete.Positioner>\n              <Autocomplete.Popup>\n                <Autocomplete.List>\n                  <Autocomplete.Item value=\"alpha\">alpha</Autocomplete.Item>\n                  <Autocomplete.Item value=\"beta\">beta</Autocomplete.Item>\n                </Autocomplete.List>\n              </Autocomplete.Popup>\n            </Autocomplete.Positioner>\n          </Autocomplete.Portal>\n        </Autocomplete.Root>\n      </Field.Root>,\n    );\n\n    // Hidden inputs are rendered without a name for selectionMode='none', but Field provides the form input.\n    // Simulate browser autofill by changing the hidden field control input for this Field.\n    const hidden = screen.getByRole('textbox', {\n      hidden: true,\n    });\n    fireEvent.change(hidden!, { target: { value: 'beta' } });\n    await flushMicrotasks();\n\n    const input = screen.getByTestId<HTMLInputElement>('input');\n    expect(input.value).toBe('beta');\n  });\n\n  it('should pass autoComplete to the visible input', async () => {\n    await render(\n      <Autocomplete.Root name=\"search\">\n        <Autocomplete.Input autoComplete=\"on\" />\n        <Autocomplete.Portal>\n          <Autocomplete.Positioner>\n            <Autocomplete.Popup>\n              <Autocomplete.List>\n                <Autocomplete.Item value=\"alpha\">alpha</Autocomplete.Item>\n                <Autocomplete.Item value=\"beta\">beta</Autocomplete.Item>\n              </Autocomplete.List>\n            </Autocomplete.Popup>\n          </Autocomplete.Positioner>\n        </Autocomplete.Portal>\n      </Autocomplete.Root>,\n    );\n\n    const input = screen.getByRole('combobox');\n    const hiddenInput = screen.getByRole('textbox', { hidden: true });\n\n    expect(input).toHaveAttribute('name', 'search');\n    expect(input).toHaveAttribute('autocomplete', 'on');\n    expect(hiddenInput).not.toHaveAttribute('name');\n    expect(hiddenInput).toHaveAttribute('id');\n    expect(hiddenInput).not.toHaveAttribute('autocomplete');\n  });\n\n  describe('prop: autoHighlight', () => {\n    it('calls onItemHighlighted when the popup auto highlights on open', async () => {\n      const onItemHighlighted = vi.fn();\n\n      const { user } = await render(\n        <Autocomplete.Root\n          items={['alpha', 'alpine', 'beta']}\n          autoHighlight\n          onItemHighlighted={onItemHighlighted}\n        >\n          <Autocomplete.Input data-testid=\"input\" />\n          <Autocomplete.Portal>\n            <Autocomplete.Positioner>\n              <Autocomplete.Popup>\n                <Autocomplete.List>\n                  {(item: string) => (\n                    <Autocomplete.Item key={item} value={item}>\n                      {item}\n                    </Autocomplete.Item>\n                  )}\n                </Autocomplete.List>\n              </Autocomplete.Popup>\n            </Autocomplete.Positioner>\n          </Autocomplete.Portal>\n        </Autocomplete.Root>,\n      );\n\n      const input = screen.getByTestId<HTMLInputElement>('input');\n      await user.type(input, 'a');\n\n      const firstOption = await screen.findByRole('option', { name: 'alpha' });\n      expect(onItemHighlighted.mock.calls.length).toBeGreaterThan(0);\n\n      const [value, eventDetails] = onItemHighlighted.mock.lastCall ?? [];\n      expect(value).toBe('alpha');\n      expect(eventDetails.reason).toBe('none');\n\n      await waitFor(() => {\n        expect(firstOption).toHaveAttribute('data-highlighted');\n      });\n    });\n\n    it('highlights the first item when typing and keeps it during filtering', async () => {\n      const { user } = await render(\n        <Autocomplete.Root autoHighlight>\n          <Autocomplete.Input />\n          <Autocomplete.Portal>\n            <Autocomplete.Positioner>\n              <Autocomplete.Popup>\n                <Autocomplete.List>\n                  <Autocomplete.Item value=\"new york\">new york</Autocomplete.Item>\n                  <Autocomplete.Item value=\"new york city\">new york city</Autocomplete.Item>\n                  <Autocomplete.Item value=\"newcastle\">newcastle</Autocomplete.Item>\n                </Autocomplete.List>\n              </Autocomplete.Popup>\n            </Autocomplete.Positioner>\n          </Autocomplete.Portal>\n        </Autocomplete.Root>,\n      );\n\n      const input = screen.getByRole('combobox');\n      await user.click(input);\n      await user.type(input, 'new');\n\n      const newYorkOption = screen.getByRole('option', { name: 'new york' });\n      expect(newYorkOption).toHaveAttribute('data-highlighted');\n      expect(input.getAttribute('aria-activedescendant')).toBe(newYorkOption.id);\n\n      // Trailing space should not clear highlight if matches remain\n      await user.type(input, ' ');\n\n      expect(newYorkOption).toHaveAttribute('data-highlighted');\n      expect(input.getAttribute('aria-activedescendant')).toBe(newYorkOption.id);\n    });\n\n    it('does not highlight on open via click or when pressing arrow keys initially', async () => {\n      const { user } = await render(\n        <Autocomplete.Root items={['apple', 'banana']} autoHighlight openOnInputClick>\n          <Autocomplete.Input />\n          <Autocomplete.Portal>\n            <Autocomplete.Positioner>\n              <Autocomplete.Popup>\n                <Autocomplete.List>\n                  {(item: string) => (\n                    <Autocomplete.Item key={item} value={item}>\n                      {item}\n                    </Autocomplete.Item>\n                  )}\n                </Autocomplete.List>\n              </Autocomplete.Popup>\n            </Autocomplete.Positioner>\n          </Autocomplete.Portal>\n        </Autocomplete.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n      fireEvent.click(input);\n\n      await waitFor(() => {\n        expect(input).not.toHaveAttribute('aria-activedescendant');\n      });\n\n      await user.keyboard('{ArrowDown}');\n      await waitFor(() => {\n        expect(input).not.toHaveAttribute('aria-activedescendant');\n      });\n\n      await user.keyboard('{ArrowDown}');\n      await waitFor(() => {\n        expect(input).not.toHaveAttribute('aria-activedescendant');\n      });\n\n      await user.keyboard('{Escape}');\n      await user.click(input);\n\n      expect(input).not.toHaveAttribute('aria-activedescendant');\n\n      await user.keyboard('{ArrowUp}');\n      await waitFor(() => {\n        expect(input).toHaveAttribute('aria-activedescendant');\n      });\n    });\n\n    it('highlights the first item when opening via ArrowDown', async () => {\n      const { user } = await render(\n        <Autocomplete.Root items={['alpha', 'beta', 'gamma']} autoHighlight>\n          <Autocomplete.Input />\n          <Autocomplete.Portal>\n            <Autocomplete.Positioner>\n              <Autocomplete.Popup>\n                <Autocomplete.List>\n                  {(item: string) => (\n                    <Autocomplete.Item key={item} value={item}>\n                      {item}\n                    </Autocomplete.Item>\n                  )}\n                </Autocomplete.List>\n              </Autocomplete.Popup>\n            </Autocomplete.Positioner>\n          </Autocomplete.Portal>\n        </Autocomplete.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n\n      await user.click(input);\n      await user.keyboard('{ArrowDown}');\n\n      const firstOption = await screen.findByRole('option', { name: 'alpha' });\n      expect(firstOption).toHaveAttribute('data-highlighted');\n      expect(input.getAttribute('aria-activedescendant')).toBe(firstOption.id);\n    });\n\n    it('links aria-activedescendant to the highlighted item after filtering', async () => {\n      const { user } = await render(\n        <Autocomplete.Root items={['feature', 'fix']} autoHighlight>\n          <Autocomplete.Input />\n          <Autocomplete.Portal>\n            <Autocomplete.Positioner>\n              <Autocomplete.Popup>\n                <Autocomplete.List>\n                  {(item) => (\n                    <Autocomplete.Item key={item} value={item}>\n                      {item}\n                    </Autocomplete.Item>\n                  )}\n                </Autocomplete.List>\n              </Autocomplete.Popup>\n            </Autocomplete.Positioner>\n          </Autocomplete.Portal>\n        </Autocomplete.Root>,\n      );\n\n      const input = screen.getByRole('combobox');\n      await user.click(input);\n\n      // Type 'f' — both items remain, first should be highlighted\n      await user.type(input, 'f');\n      const firstOption = screen.getByRole('option', { name: 'feature' });\n      expect(firstOption).toHaveAttribute('data-highlighted');\n      expect(input.getAttribute('aria-activedescendant')).toBe(firstOption.id);\n\n      // Type 'i' — filters to \"fix\" and highlight should follow, with ids stable\n      await user.type(input, 'i');\n      const fixOption = screen.getByRole('option', { name: 'fix' });\n      expect(fixOption).toHaveAttribute('data-highlighted');\n      expect(input.getAttribute('aria-activedescendant')).toBe(fixOption.id);\n    });\n\n    it('does not highlight first/last item when pressing ArrowDown/ArrowUp initially', async () => {\n      const { user } = await render(\n        <Autocomplete.Root items={['alpha', 'beta', 'gamma']}>\n          <Autocomplete.Input data-testid=\"input\" />\n          <Autocomplete.Portal>\n            <Autocomplete.Positioner>\n              <Autocomplete.Popup>\n                <Autocomplete.List>\n                  {(item: string) => (\n                    <Autocomplete.Item key={item} value={item}>\n                      {item}\n                    </Autocomplete.Item>\n                  )}\n                </Autocomplete.List>\n              </Autocomplete.Popup>\n            </Autocomplete.Positioner>\n          </Autocomplete.Portal>\n        </Autocomplete.Root>,\n      );\n\n      const input = screen.getByTestId<HTMLInputElement>('input');\n\n      await user.click(input);\n      expect(input).not.toHaveAttribute('aria-activedescendant');\n\n      await user.keyboard('{ArrowDown}');\n      await waitFor(() => {\n        expect(input).not.toHaveAttribute('aria-activedescendant');\n      });\n\n      await user.keyboard('{ArrowDown}');\n      await waitFor(() => {\n        expect(input).toHaveAttribute('aria-activedescendant');\n      });\n    });\n\n    it('retains highlight when clearing the query with autoHighlight enabled', async () => {\n      const { user } = await render(\n        <Autocomplete.Root items={['apple', 'banana', 'cherry']} autoHighlight openOnInputClick>\n          <Autocomplete.Input />\n          <Autocomplete.Portal>\n            <Autocomplete.Positioner>\n              <Autocomplete.Popup>\n                <Autocomplete.List>\n                  {(item: string) => (\n                    <Autocomplete.Item key={item} value={item}>\n                      {item}\n                    </Autocomplete.Item>\n                  )}\n                </Autocomplete.List>\n              </Autocomplete.Popup>\n            </Autocomplete.Positioner>\n          </Autocomplete.Portal>\n        </Autocomplete.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n\n      await user.click(input);\n      await user.type(input, 'ban');\n\n      await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n      expect(input).toHaveAttribute('aria-activedescendant');\n\n      const highlightedBefore = input.getAttribute('aria-activedescendant');\n      expect(highlightedBefore).not.toBe(null);\n\n      await user.clear(input);\n\n      await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n      expect(input.getAttribute('aria-activedescendant')).toBe(highlightedBefore);\n    });\n\n    it('highlights the first item immediately when behavior is \"always\"', async () => {\n      await render(\n        <Autocomplete.Root items={['alpha', 'beta', 'gamma']} autoHighlight=\"always\" defaultOpen>\n          <Autocomplete.Input />\n          <Autocomplete.Portal>\n            <Autocomplete.Positioner>\n              <Autocomplete.Popup>\n                <Autocomplete.List>\n                  {(item: string) => (\n                    <Autocomplete.Item key={item} value={item}>\n                      {item}\n                    </Autocomplete.Item>\n                  )}\n                </Autocomplete.List>\n              </Autocomplete.Popup>\n            </Autocomplete.Positioner>\n          </Autocomplete.Portal>\n        </Autocomplete.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n      const firstOption = screen.getByRole('option', { name: 'alpha' });\n\n      expect(input).toHaveAttribute('aria-activedescendant', firstOption.id);\n      expect(firstOption).toHaveAttribute('data-highlighted');\n    });\n\n    it('keeps the latest pointer highlight on outside blur when behavior is \"always\"', async () => {\n      const { user } = await render(\n        <React.Fragment>\n          <Autocomplete.Root\n            items={['apple', 'banana', 'cherry']}\n            autoHighlight=\"always\"\n            keepHighlight\n            open\n            inline\n          >\n            <Autocomplete.Input data-testid=\"input\" />\n            <Autocomplete.List>\n              {(item: string) => (\n                <Autocomplete.Item key={item} value={item}>\n                  {item}\n                </Autocomplete.Item>\n              )}\n            </Autocomplete.List>\n          </Autocomplete.Root>\n          <button data-testid=\"outside\">outside</button>\n        </React.Fragment>,\n      );\n\n      const input = screen.getByTestId<HTMLInputElement>('input');\n      const banana = screen.getByRole('option', { name: 'banana' });\n\n      await act(async () => {\n        input.focus();\n      });\n\n      await user.hover(banana);\n\n      await waitFor(() => {\n        expect(input).toHaveAttribute('aria-activedescendant', banana.id);\n        expect(banana).toHaveAttribute('data-highlighted');\n      });\n\n      const outside = screen.getByTestId('outside');\n      fireEvent.pointerDown(outside);\n      fireEvent.blur(input, { relatedTarget: outside });\n      fireEvent.focus(outside);\n\n      await waitFor(() => {\n        expect(input).toHaveAttribute('aria-activedescendant', banana.id);\n        expect(banana).toHaveAttribute('data-highlighted');\n      });\n\n      expect(screen.getByRole('option', { name: 'apple' })).not.toHaveAttribute('data-highlighted');\n    });\n  });\n\n  describe('prop: keepHighlight', () => {\n    it('keeps the current highlight when the pointer leaves the list', async () => {\n      const { user } = await render(\n        <Autocomplete.Root items={['apple', 'banana']} autoHighlight keepHighlight>\n          <Autocomplete.Input data-testid=\"input\" />\n          <Autocomplete.Portal>\n            <Autocomplete.Positioner>\n              <Autocomplete.Popup>\n                <Autocomplete.List>\n                  {(item: string) => (\n                    <Autocomplete.Item key={item} value={item}>\n                      {item}\n                    </Autocomplete.Item>\n                  )}\n                </Autocomplete.List>\n              </Autocomplete.Popup>\n            </Autocomplete.Positioner>\n          </Autocomplete.Portal>\n        </Autocomplete.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n      await user.click(input);\n      await user.type(input, 'ap');\n\n      const apple = await screen.findByRole('option', { name: 'apple' });\n      await waitFor(() => expect(apple).toHaveAttribute('data-highlighted'));\n\n      const outside = document.createElement('div');\n      document.body.appendChild(outside);\n      fireEvent.pointerLeave(apple, { pointerType: 'mouse', relatedTarget: outside });\n\n      await waitFor(() => expect(apple).toHaveAttribute('data-highlighted'));\n      outside.remove();\n    });\n\n    it('continues keyboard navigation from the kept highlight after pointer leave', async () => {\n      const { user } = await render(\n        <Autocomplete.Root items={['apple', 'banana', 'carrot']} autoHighlight keepHighlight>\n          <Autocomplete.Input />\n          <Autocomplete.Portal>\n            <Autocomplete.Positioner>\n              <Autocomplete.Popup>\n                <Autocomplete.List>\n                  {(item: string) => (\n                    <Autocomplete.Item key={item} value={item}>\n                      {item}\n                    </Autocomplete.Item>\n                  )}\n                </Autocomplete.List>\n              </Autocomplete.Popup>\n            </Autocomplete.Positioner>\n          </Autocomplete.Portal>\n        </Autocomplete.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n      await user.click(input);\n      await user.type(input, 'a');\n\n      const apple = await screen.findByRole('option', { name: 'apple' });\n      await waitFor(() => expect(apple).toHaveAttribute('data-highlighted'));\n\n      const outside = document.createElement('div');\n      document.body.appendChild(outside);\n      fireEvent.pointerLeave(apple, { pointerType: 'mouse', relatedTarget: outside });\n\n      await user.keyboard('{ArrowDown}');\n\n      const banana = screen.getByRole('option', { name: 'banana' });\n      await waitFor(() => expect(banana).toHaveAttribute('data-highlighted'));\n      outside.remove();\n    });\n  });\n\n  describe('prop: mode', () => {\n    it('mode=\"list\" (default): no inline overlay, consumer handles filtering', async () => {\n      const items = ['apple', 'banana', 'cherry'];\n\n      const { user } = await render(\n        <Autocomplete.Root mode=\"list\" items={items}>\n          <Autocomplete.Input data-testid=\"input\" />\n          <Autocomplete.Portal>\n            <Autocomplete.Positioner>\n              <Autocomplete.Popup>\n                <Autocomplete.List>\n                  {(item) => (\n                    <Autocomplete.Item key={item} value={item}>\n                      {item}\n                    </Autocomplete.Item>\n                  )}\n                </Autocomplete.List>\n              </Autocomplete.Popup>\n            </Autocomplete.Positioner>\n          </Autocomplete.Portal>\n        </Autocomplete.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n\n      await user.click(input);\n      await user.type(input, 'a');\n\n      expect(screen.getAllByRole('option')).toHaveLength(2); // apple, banana\n\n      await user.keyboard('{ArrowDown}');\n\n      expect(input.value).toBe('a');\n      expect(screen.getAllByRole('option')).toHaveLength(2);\n    });\n\n    it('mode=\"both\": inline overlay + autocomplete handles filtering', async () => {\n      const items = ['apple', 'banana', 'cherry'];\n\n      const { user } = await render(\n        <Autocomplete.Root mode=\"both\" items={items}>\n          <Autocomplete.Input data-testid=\"input\" />\n          <Autocomplete.Portal>\n            <Autocomplete.Positioner>\n              <Autocomplete.Popup>\n                <Autocomplete.List>\n                  {(item) => (\n                    <Autocomplete.Item key={item} value={item}>\n                      {item}\n                    </Autocomplete.Item>\n                  )}\n                </Autocomplete.List>\n              </Autocomplete.Popup>\n            </Autocomplete.Positioner>\n          </Autocomplete.Portal>\n        </Autocomplete.Root>,\n      );\n\n      const input = screen.getByTestId<HTMLInputElement>('input');\n\n      await user.click(input);\n      await user.type(input, 'a');\n\n      expect(screen.getAllByRole('option')).toHaveLength(2); // apple, banana\n\n      await user.keyboard('{ArrowDown}');\n\n      expect(input.value).toBe('apple');\n      expect(screen.getAllByRole('option')).toHaveLength(2);\n    });\n\n    it('mode=\"both\": hovering items should not change the inline overlay (preserve temporary value)', async () => {\n      const items = ['alpha', 'alpine', 'beta'];\n\n      const { user } = await render(\n        <Autocomplete.Root mode=\"both\" items={items}>\n          <Autocomplete.Input data-testid=\"input\" />\n          <Autocomplete.Portal>\n            <Autocomplete.Positioner>\n              <Autocomplete.Popup>\n                <Autocomplete.List>\n                  {(item) => (\n                    <Autocomplete.Item key={item} value={item}>\n                      {item}\n                    </Autocomplete.Item>\n                  )}\n                </Autocomplete.List>\n              </Autocomplete.Popup>\n            </Autocomplete.Positioner>\n          </Autocomplete.Portal>\n        </Autocomplete.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n      await user.click(input);\n      await user.type(input, 'al');\n\n      await user.keyboard('{ArrowDown}');\n      expect(input.value).toBe('alpha');\n\n      await user.hover(screen.getByRole('option', { name: 'alpine' }));\n      expect(input.value).toBe('alpha');\n    });\n\n    it('mode=\"inline\": static items with inline overlay', async () => {\n      const { user } = await render(\n        <Autocomplete.Root mode=\"inline\" openOnInputClick>\n          <Autocomplete.Input data-testid=\"input\" />\n          <Autocomplete.Portal>\n            <Autocomplete.Positioner>\n              <Autocomplete.Popup>\n                <Autocomplete.List>\n                  <Autocomplete.Item value=\"apple\">apple</Autocomplete.Item>\n                  <Autocomplete.Item value=\"banana\">banana</Autocomplete.Item>\n                  <Autocomplete.Item value=\"cherry\">cherry</Autocomplete.Item>\n                </Autocomplete.List>\n              </Autocomplete.Popup>\n            </Autocomplete.Positioner>\n          </Autocomplete.Portal>\n        </Autocomplete.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n\n      await user.click(input);\n\n      await waitFor(() => {\n        expect(screen.getAllByRole('option')).toHaveLength(3);\n      });\n\n      await user.keyboard('{ArrowDown}');\n\n      await waitFor(() => {\n        expect(input.value).toBe('apple');\n      });\n\n      await user.type(input, 'b');\n\n      expect(input.value).toBe('appleb');\n      expect(screen.getAllByRole('option')).toHaveLength(3);\n    });\n\n    it('mode=\"none\": static items without inline overlay', async () => {\n      const { user } = await render(\n        <Autocomplete.Root mode=\"none\" openOnInputClick>\n          <Autocomplete.Input data-testid=\"input\" />\n          <Autocomplete.Portal>\n            <Autocomplete.Positioner>\n              <Autocomplete.Popup>\n                <Autocomplete.List>\n                  <Autocomplete.Item value=\"apple\">apple</Autocomplete.Item>\n                  <Autocomplete.Item value=\"banana\">banana</Autocomplete.Item>\n                  <Autocomplete.Item value=\"cherry\">cherry</Autocomplete.Item>\n                </Autocomplete.List>\n              </Autocomplete.Popup>\n            </Autocomplete.Positioner>\n          </Autocomplete.Portal>\n        </Autocomplete.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n\n      await user.click(input);\n      await user.keyboard('{ArrowDown}');\n\n      expect(input.value).toBe('');\n      expect(screen.getAllByRole('option')).toHaveLength(3);\n\n      await user.type(input, 'x');\n      await user.keyboard('{ArrowDown}');\n\n      expect(input.value).toBe('x');\n      expect(screen.getAllByRole('option')).toHaveLength(3);\n    });\n  });\n\n  describe('prop: filter', () => {\n    it('does not apply default filtering when filter is null', async () => {\n      interface Movie {\n        id: string;\n        title: string;\n        year: number;\n      }\n\n      const asyncResults: Movie[] = [\n        { id: '1', title: 'Pulp Fiction', year: 1994 },\n        { id: '2', title: 'The Godfather', year: 1972 },\n        { id: '3', title: 'The Dark Knight', year: 2008 },\n      ];\n\n      const { user } = await render(\n        <Autocomplete.Root\n          items={asyncResults}\n          filter={null}\n          itemToStringValue={(movie: Movie) => movie.title}\n        >\n          <Autocomplete.Input data-testid=\"input\" />\n          <Autocomplete.Portal>\n            <Autocomplete.Positioner>\n              <Autocomplete.Popup>\n                <Autocomplete.List>\n                  {(movie: Movie) => (\n                    <Autocomplete.Item key={movie.id} value={movie}>\n                      {movie.title}\n                    </Autocomplete.Item>\n                  )}\n                </Autocomplete.List>\n              </Autocomplete.Popup>\n            </Autocomplete.Positioner>\n          </Autocomplete.Portal>\n        </Autocomplete.Root>,\n      );\n\n      const input = screen.getByTestId<HTMLInputElement>('input');\n      await user.type(input, '1994');\n\n      await waitFor(() => {\n        expect(screen.getAllByRole('option')).toHaveLength(3);\n      });\n      expect(screen.getByRole('option', { name: 'Pulp Fiction' })).not.toBe(null);\n      expect(screen.getByRole('option', { name: 'The Godfather' })).not.toBe(null);\n      expect(screen.getByRole('option', { name: 'The Dark Knight' })).not.toBe(null);\n    });\n  });\n\n  describe('prop: submitOnItemClick', () => {\n    it('prevents submit on Enter when an item is highlighted by default (false)', async () => {\n      let submitted = 0;\n\n      const handleSubmit: React.FormEventHandler<HTMLFormElement> = (event) => {\n        event.preventDefault();\n        submitted += 1;\n      };\n\n      const { user } = await render(\n        <form onSubmit={handleSubmit}>\n          <Field.Root name=\"search\">\n            <Autocomplete.Root items={['apple', 'banana']} autoHighlight>\n              <Autocomplete.Input />\n              <Autocomplete.Portal>\n                <Autocomplete.Positioner>\n                  <Autocomplete.Popup>\n                    <Autocomplete.List>\n                      {(item) => (\n                        <Autocomplete.Item key={item} value={item}>\n                          {item}\n                        </Autocomplete.Item>\n                      )}\n                    </Autocomplete.List>\n                  </Autocomplete.Popup>\n                </Autocomplete.Positioner>\n              </Autocomplete.Portal>\n            </Autocomplete.Root>\n          </Field.Root>\n        </form>,\n      );\n\n      const input = screen.getByRole('combobox');\n      await user.click(screen.getByRole('combobox'));\n      await user.type(input, 'a'); // open and highlight first\n      await user.keyboard('{Enter}');\n\n      expect(submitted).toBe(0);\n    });\n\n    it('when true, clicking with pointer submits the owning form', async () => {\n      let submitValue: string | null = null;\n      let submitCount = 0;\n\n      const handleSubmit: React.FormEventHandler<HTMLFormElement> = (event) => {\n        event.preventDefault();\n        const data = new FormData(event.currentTarget);\n        submitValue = (data.get('q') as string) ?? null;\n        submitCount += 1;\n      };\n\n      const { user } = await render(\n        <form onSubmit={handleSubmit}>\n          <Field.Root name=\"q\">\n            <Autocomplete.Root items={['alpha', 'alpine']} submitOnItemClick>\n              <Autocomplete.Input />\n              <Autocomplete.Portal>\n                <Autocomplete.Positioner>\n                  <Autocomplete.Popup>\n                    <Autocomplete.List>\n                      {(item) => (\n                        <Autocomplete.Item key={item} value={item}>\n                          {item}\n                        </Autocomplete.Item>\n                      )}\n                    </Autocomplete.List>\n                  </Autocomplete.Popup>\n                </Autocomplete.Positioner>\n              </Autocomplete.Portal>\n            </Autocomplete.Root>\n          </Field.Root>\n        </form>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n      await user.type(input, 'al');\n\n      const alphaButton = screen.getByRole('option', { name: 'alpha' });\n      await user.click(alphaButton);\n\n      expect(submitValue).toBe('alpha');\n      expect(submitCount).toBe(1);\n    });\n\n    it('when true, pressing Enter in the Input submits the owning form when an item is highlighted', async () => {\n      let submitValue: string | null = null;\n      let submitCount = 0;\n\n      const handleSubmit: React.FormEventHandler<HTMLFormElement> = (event) => {\n        event.preventDefault();\n        const data = new FormData(event.currentTarget);\n        submitValue = (data.get('q') as string) ?? null;\n        submitCount += 1;\n      };\n\n      const { user } = await render(\n        <form onSubmit={handleSubmit}>\n          <Field.Root name=\"q\">\n            <Autocomplete.Root items={['alpha', 'alpine']} submitOnItemClick>\n              <Autocomplete.Input />\n              <Autocomplete.Portal>\n                <Autocomplete.Positioner>\n                  <Autocomplete.Popup>\n                    <Autocomplete.List>\n                      {(item) => (\n                        <Autocomplete.Item key={item} value={item}>\n                          {item}\n                        </Autocomplete.Item>\n                      )}\n                    </Autocomplete.List>\n                  </Autocomplete.Popup>\n                </Autocomplete.Positioner>\n              </Autocomplete.Portal>\n            </Autocomplete.Root>\n          </Field.Root>\n        </form>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n      await user.type(input, 'al');\n      await user.keyboard('{ArrowDown}');\n\n      const alphaButton = screen.getByRole('option', { name: 'alpha' });\n      expect(alphaButton).toHaveAttribute('data-highlighted');\n\n      await user.keyboard('{Enter}');\n\n      expect(submitValue).toBe('alpha');\n      expect(submitCount).toBe(1);\n    });\n\n    it('focusing the listbox should keep the input focused and maintain functionality', async () => {\n      let submitValue: string | null = null;\n      let submitCount = 0;\n\n      const handleSubmit: React.FormEventHandler<HTMLFormElement> = (event) => {\n        event.preventDefault();\n        const data = new FormData(event.currentTarget);\n        submitValue = (data.get('q') as string) ?? null;\n        submitCount += 1;\n      };\n\n      const { user } = await render(\n        <form onSubmit={handleSubmit}>\n          <Field.Root name=\"q\">\n            <Autocomplete.Root items={['alpha', 'alpine']} submitOnItemClick>\n              <Autocomplete.Input />\n              <Autocomplete.Portal>\n                <Autocomplete.Positioner>\n                  <Autocomplete.Popup>\n                    <Autocomplete.List data-testid=\"listbox\">\n                      {(item) => (\n                        <Autocomplete.Item key={item} value={item}>\n                          {item}\n                        </Autocomplete.Item>\n                      )}\n                    </Autocomplete.List>\n                  </Autocomplete.Popup>\n                </Autocomplete.Positioner>\n              </Autocomplete.Portal>\n            </Autocomplete.Root>\n          </Field.Root>\n        </form>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n      await user.type(input, 'al');\n      await user.keyboard('{ArrowDown}');\n\n      const listbox = screen.getByRole('listbox');\n      const alphaOption = screen.getByRole('option', { name: 'alpha' });\n      await waitFor(() => {\n        expect(alphaOption).toHaveAttribute('data-highlighted');\n      });\n\n      await act(() => {\n        listbox.focus();\n      });\n      expect(input).toHaveFocus();\n\n      await user.keyboard('{Enter}');\n\n      expect(submitValue).toBe('alpha');\n      expect(submitCount).toBe(1);\n    });\n  });\n\n  describe('Form', () => {\n    const { render: renderFakeTimers, clock } = createRenderer({\n      clockOptions: {\n        shouldAdvanceTime: true,\n      },\n    });\n\n    clock.withFakeTimers();\n\n    it('submits the typed input value when wrapped in Field.Root', async () => {\n      let submitted: string | null = null;\n\n      const handleSubmit: React.FormEventHandler<HTMLFormElement> = (event) => {\n        event.preventDefault();\n        const data = new FormData(event.currentTarget);\n        submitted = (data.get('search') as string) ?? null;\n      };\n\n      const { user } = await render(\n        <Form onSubmit={handleSubmit}>\n          <Field.Root name=\"search\">\n            <Autocomplete.Root>\n              <Autocomplete.Input data-testid=\"input\" />\n            </Autocomplete.Root>\n          </Field.Root>\n          <button type=\"submit\">Submit</button>\n        </Form>,\n      );\n\n      const input = screen.getByTestId('input');\n      await user.type(input, 'hello world');\n      await user.click(screen.getByText('Submit'));\n\n      expect(submitted).toBe('hello world');\n    });\n\n    it('submits the typed input value when name is provided on Autocomplete.Root', async () => {\n      let submitted: FormDataEntryValue | null = null;\n\n      const handleSubmit: React.FormEventHandler<HTMLFormElement> = (event) => {\n        event.preventDefault();\n        const data = new FormData(event.currentTarget);\n        submitted = data.get('query');\n      };\n\n      const { user } = await render(\n        <Form onSubmit={handleSubmit}>\n          <Field.Root name=\"query\">\n            <Autocomplete.Root>\n              <Autocomplete.Input data-testid=\"input\" />\n              <Autocomplete.Portal>\n                <Autocomplete.Positioner>\n                  <Autocomplete.Popup>\n                    <Autocomplete.List>\n                      <Autocomplete.Item value=\"base-ui\">base-ui</Autocomplete.Item>\n                    </Autocomplete.List>\n                  </Autocomplete.Popup>\n                </Autocomplete.Positioner>\n              </Autocomplete.Portal>\n            </Autocomplete.Root>\n          </Field.Root>\n          <button type=\"submit\" data-testid=\"submit-btn\">\n            Submit\n          </button>\n        </Form>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n      await user.type(input, 'base ui');\n\n      expect(input.getAttribute('name'), 'input should have name attribute').toBe('query');\n      expect(input.value, 'input should have typed value').toBe('base ui');\n\n      await user.click(screen.getByText('Submit'));\n\n      expect(submitted).toBe('base ui');\n    });\n\n    it('triggers native validation when required and empty', async () => {\n      const { user } = await render(\n        <Form>\n          <Field.Root name=\"auto\" data-testid=\"field\">\n            <Autocomplete.Root required>\n              <Autocomplete.Input data-testid=\"input\" />\n            </Autocomplete.Root>\n            <Field.Error match=\"valueMissing\" data-testid=\"error\">\n              required\n            </Field.Error>\n          </Field.Root>\n          <button type=\"submit\">Submit</button>\n        </Form>,\n      );\n\n      expect(screen.queryByTestId('error')).toBe(null);\n\n      await user.click(screen.getByText('Submit'));\n\n      const error = screen.getByTestId('error');\n      expect(error).toHaveTextContent('required');\n    });\n\n    it('clears external errors on change', async () => {\n      const { user } = await renderFakeTimers(\n        <Form\n          errors={{\n            autocomplete: 'test',\n          }}\n        >\n          <Field.Root name=\"autocomplete\">\n            <Autocomplete.Root>\n              <Autocomplete.Input data-testid=\"input\" />\n              <Autocomplete.Portal>\n                <Autocomplete.Positioner>\n                  <Autocomplete.Popup>\n                    <Autocomplete.List>\n                      <Autocomplete.Item value=\"a\">a</Autocomplete.Item>\n                      <Autocomplete.Item value=\"b\">b</Autocomplete.Item>\n                    </Autocomplete.List>\n                  </Autocomplete.Popup>\n                </Autocomplete.Positioner>\n              </Autocomplete.Portal>\n            </Autocomplete.Root>\n            <Field.Error data-testid=\"error\" />\n          </Field.Root>\n        </Form>,\n      );\n\n      expect(screen.getByTestId('error')).toHaveTextContent('test');\n\n      const input = screen.getByTestId('input');\n      expect(input).toHaveAttribute('aria-invalid', 'true');\n\n      await user.type(input, 'test input');\n      await flushMicrotasks();\n\n      expect(screen.queryByTestId('error')).toBe(null);\n      expect(input).not.toHaveAttribute('aria-invalid');\n    });\n\n    it('submits the input value directly (not selection value)', async () => {\n      let submitted: string | null = null;\n\n      const handleSubmit: React.FormEventHandler<HTMLFormElement> = (event) => {\n        event.preventDefault();\n        const data = new FormData(event.currentTarget);\n        submitted = data.get('search') as string;\n      };\n\n      const { user } = await render(\n        <Form onSubmit={handleSubmit}>\n          <Field.Root name=\"search\">\n            <Autocomplete.Root>\n              <Autocomplete.Input data-testid=\"input\" />\n              <Autocomplete.Portal>\n                <Autocomplete.Positioner>\n                  <Autocomplete.Popup>\n                    <Autocomplete.List>\n                      <Autocomplete.Item value=\"apple\">apple</Autocomplete.Item>\n                      <Autocomplete.Item value=\"banana\">banana</Autocomplete.Item>\n                    </Autocomplete.List>\n                  </Autocomplete.Popup>\n                </Autocomplete.Positioner>\n              </Autocomplete.Portal>\n            </Autocomplete.Root>\n          </Field.Root>\n          <button type=\"submit\">Submit</button>\n        </Form>,\n      );\n\n      const input = screen.getByTestId('input');\n      // Type something that doesn't exactly match any option\n      await user.type(input, 'appl');\n      await user.click(screen.getByText('Submit'));\n\n      expect(submitted).toBe('appl');\n    });\n\n    it('Enter submits when no item is highlighted', async () => {\n      let submitted = 0;\n\n      const handleSubmit: React.FormEventHandler<HTMLFormElement> = (event) => {\n        event.preventDefault();\n        submitted += 1;\n      };\n\n      const { user } = await render(\n        <Form onSubmit={handleSubmit}>\n          <Field.Root name=\"search\">\n            <Autocomplete.Root items={['alpha', 'beta']} openOnInputClick>\n              <Autocomplete.Input />\n              <Autocomplete.Portal>\n                <Autocomplete.Positioner>\n                  <Autocomplete.Popup>\n                    <Autocomplete.List>\n                      {(item) => (\n                        <Autocomplete.Item key={item} value={item}>\n                          {item}\n                        </Autocomplete.Item>\n                      )}\n                    </Autocomplete.List>\n                  </Autocomplete.Popup>\n                </Autocomplete.Positioner>\n              </Autocomplete.Portal>\n            </Autocomplete.Root>\n          </Field.Root>\n          <button type=\"submit\">Submit</button>\n        </Form>,\n      );\n\n      await user.click(screen.getByRole('combobox'));\n      await user.keyboard('{Enter}');\n\n      expect(submitted).toBe(1);\n    });\n\n    it('pressing Enter in the Input submits the owning form when no item is highlighted', async () => {\n      let submitValue: string | null = null;\n      let submitCount = 0;\n\n      const handleSubmit: React.FormEventHandler<HTMLFormElement> = (event) => {\n        event.preventDefault();\n        const data = new FormData(event.currentTarget);\n        submitValue = (data.get('q') as string) ?? null;\n        submitCount += 1;\n      };\n\n      const { user } = await render(\n        <Form onSubmit={handleSubmit}>\n          <Field.Root name=\"q\">\n            <Autocomplete.Root items={['alpha', 'alpine']} submitOnItemClick>\n              <Autocomplete.Input />\n              <Autocomplete.Portal>\n                <Autocomplete.Positioner>\n                  <Autocomplete.Popup>\n                    <Autocomplete.List>\n                      {(item) => (\n                        <Autocomplete.Item key={item} value={item}>\n                          {item}\n                        </Autocomplete.Item>\n                      )}\n                    </Autocomplete.List>\n                  </Autocomplete.Popup>\n                </Autocomplete.Positioner>\n              </Autocomplete.Portal>\n            </Autocomplete.Root>\n          </Field.Root>\n          <button type=\"submit\">Submit</button>\n        </Form>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n\n      await user.type(input, 'xyz');\n      await user.keyboard('{Enter}');\n\n      expect(submitValue).toBe('xyz');\n      expect(submitCount).toBe(1);\n    });\n\n    it('pressing Enter in the List when it has focus submits the owning form', async () => {\n      let submitValue: string | null = null;\n      let submitCount = 0;\n\n      const handleSubmit: React.FormEventHandler<HTMLFormElement> = (event) => {\n        event.preventDefault();\n        const data = new FormData(event.currentTarget);\n        submitValue = (data.get('q') as string) ?? null;\n        submitCount += 1;\n      };\n\n      const { user } = await render(\n        <Form onSubmit={handleSubmit}>\n          <Field.Root name=\"q\">\n            <Autocomplete.Root items={['alpha', 'alpine']} submitOnItemClick>\n              <Autocomplete.Input />\n              <Autocomplete.Portal>\n                <Autocomplete.Positioner>\n                  <Autocomplete.Popup>\n                    <Autocomplete.List>\n                      {(item) => (\n                        <Autocomplete.Item key={item} value={item}>\n                          {item}\n                        </Autocomplete.Item>\n                      )}\n                    </Autocomplete.List>\n                  </Autocomplete.Popup>\n                </Autocomplete.Positioner>\n              </Autocomplete.Portal>\n            </Autocomplete.Root>\n          </Field.Root>\n        </Form>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n      await user.type(input, 'al');\n      await user.keyboard('{ArrowDown}');\n\n      const alphaButton = screen.getByRole('option', { name: 'alpha' });\n      expect(alphaButton).toHaveAttribute('data-highlighted');\n\n      const list = screen.getByRole('listbox');\n      act(() => {\n        list.focus();\n      });\n\n      await user.keyboard('{Enter}');\n\n      expect(submitValue).toBe('alpha');\n      expect(submitCount).toBe(1);\n    });\n  });\n\n  describe('object item stringification', () => {\n    it('filters and displays using label for {label} objects', async () => {\n      const items = [{ label: 'United States' }, { label: 'Canada' }, { label: 'Australia' }];\n\n      const { user } = await render(\n        <Autocomplete.Root items={items}>\n          <Autocomplete.Input data-testid=\"input\" />\n          <Autocomplete.Portal>\n            <Autocomplete.Positioner>\n              <Autocomplete.Popup>\n                <Autocomplete.List>\n                  {(item: { label: string }) => (\n                    <Autocomplete.Item key={item.label} value={item}>\n                      {item.label}\n                    </Autocomplete.Item>\n                  )}\n                </Autocomplete.List>\n              </Autocomplete.Popup>\n            </Autocomplete.Positioner>\n          </Autocomplete.Portal>\n        </Autocomplete.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      await user.click(input);\n      await user.type(input, 'can');\n\n      // Should match the item by its label, not its value\n      await waitFor(() => {\n        expect(screen.getAllByRole('option')).toHaveLength(1);\n      });\n      expect(screen.getByRole('option', { name: 'Canada' })).not.toBe(null);\n    });\n\n    it('uses itemToStringValue when object lacks label', async () => {\n      const items = [{ country: 'United States' }, { country: 'Canada' }, { country: 'Australia' }];\n\n      const { user } = await render(\n        <Autocomplete.Root items={items} itemToStringValue={(i) => i.country}>\n          <Autocomplete.Input data-testid=\"input\" />\n          <Autocomplete.Portal>\n            <Autocomplete.Positioner>\n              <Autocomplete.Popup>\n                <Autocomplete.List>\n                  {(item: { country: string }) => (\n                    <Autocomplete.Item key={item.country} value={item}>\n                      {item.country}\n                    </Autocomplete.Item>\n                  )}\n                </Autocomplete.List>\n              </Autocomplete.Popup>\n            </Autocomplete.Positioner>\n          </Autocomplete.Portal>\n        </Autocomplete.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      await user.click(input);\n      await user.type(input, 'can');\n\n      // Should match by the provided itemToStringValue mapping\n      await waitFor(() => {\n        expect(screen.getAllByRole('option')).toHaveLength(1);\n      });\n      expect(screen.getByRole('option', { name: 'Canada' })).not.toBe(null);\n    });\n\n    it('filters and displays using value for {value} objects', async () => {\n      const items = [{ value: 'United States' }, { value: 'Canada' }, { value: 'Australia' }];\n\n      const { user } = await render(\n        <Autocomplete.Root items={items}>\n          <Autocomplete.Input data-testid=\"input\" />\n          <Autocomplete.Portal>\n            <Autocomplete.Positioner>\n              <Autocomplete.Popup>\n                <Autocomplete.List>\n                  {(item: { value: string }) => (\n                    <Autocomplete.Item key={item.value} value={item}>\n                      {item.value}\n                    </Autocomplete.Item>\n                  )}\n                </Autocomplete.List>\n              </Autocomplete.Popup>\n            </Autocomplete.Positioner>\n          </Autocomplete.Portal>\n        </Autocomplete.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      await user.type(input, 'can');\n\n      await waitFor(() => {\n        expect(screen.getAllByRole('option')).toHaveLength(1);\n      });\n      expect(screen.getByRole('option', { name: 'Canada' })).not.toBe(null);\n    });\n  });\n\n  describe('Field', () => {\n    const { render: renderFakeTimers, clock } = createRenderer({\n      clockOptions: {\n        shouldAdvanceTime: true,\n      },\n    });\n\n    clock.withFakeTimers();\n\n    it('sets `required` on the visible input', async () => {\n      await render(\n        <Field.Root>\n          <Autocomplete.Root required>\n            <Autocomplete.Input data-testid=\"input\" />\n            <Autocomplete.Portal>\n              <Autocomplete.Positioner />\n            </Autocomplete.Portal>\n          </Autocomplete.Root>\n        </Field.Root>,\n      );\n\n      expect(screen.getByTestId('input')).toHaveAttribute('required');\n    });\n\n    it('[data-touched]', async () => {\n      await render(\n        <Field.Root>\n          <Autocomplete.Root>\n            <Autocomplete.Input data-testid=\"input\" />\n            <Autocomplete.Portal>\n              <Autocomplete.Positioner>\n                <Autocomplete.Popup>\n                  <Autocomplete.List>\n                    <Autocomplete.Item value=\"\">Select</Autocomplete.Item>\n                    <Autocomplete.Item value=\"1\">Option 1</Autocomplete.Item>\n                  </Autocomplete.List>\n                </Autocomplete.Popup>\n              </Autocomplete.Positioner>\n            </Autocomplete.Portal>\n          </Autocomplete.Root>\n        </Field.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n\n      expect(input).not.toHaveAttribute('data-touched');\n\n      fireEvent.focus(input);\n      fireEvent.blur(input);\n\n      await flushMicrotasks();\n\n      expect(input).toHaveAttribute('data-touched', '');\n    });\n\n    it('[data-dirty]', async () => {\n      const { user } = await renderFakeTimers(\n        <Field.Root>\n          <Autocomplete.Root>\n            <Autocomplete.Input data-testid=\"input\" />\n            <Autocomplete.Portal>\n              <Autocomplete.Positioner>\n                <Autocomplete.Popup>\n                  <Autocomplete.List>\n                    <Autocomplete.Item value=\"\">Select</Autocomplete.Item>\n                    <Autocomplete.Item value=\"1\">Option 1</Autocomplete.Item>\n                  </Autocomplete.List>\n                </Autocomplete.Popup>\n              </Autocomplete.Positioner>\n            </Autocomplete.Portal>\n          </Autocomplete.Root>\n        </Field.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n\n      expect(input).not.toHaveAttribute('data-dirty');\n\n      await user.type(input, 'test');\n      await flushMicrotasks();\n\n      expect(input).toHaveAttribute('data-dirty', '');\n    });\n\n    describe('[data-filled]', () => {\n      it('adds [data-filled] attribute when input has content', async () => {\n        const { user } = await renderFakeTimers(\n          <Field.Root>\n            <Autocomplete.Root>\n              <Autocomplete.Input data-testid=\"input\" />\n              <Autocomplete.Portal>\n                <Autocomplete.Positioner>\n                  <Autocomplete.Popup>\n                    <Autocomplete.List>\n                      <Autocomplete.Item value=\"\">Select</Autocomplete.Item>\n                      <Autocomplete.Item value=\"1\">Option 1</Autocomplete.Item>\n                    </Autocomplete.List>\n                  </Autocomplete.Popup>\n                </Autocomplete.Positioner>\n              </Autocomplete.Portal>\n            </Autocomplete.Root>\n          </Field.Root>,\n        );\n\n        const input = screen.getByTestId('input');\n\n        expect(input).not.toHaveAttribute('data-filled');\n\n        await user.type(input, 'test input');\n        await flushMicrotasks();\n\n        expect(input).toHaveAttribute('data-filled', '');\n      });\n\n      it('adds [data-filled] attribute when already filled with defaultValue', async () => {\n        await render(\n          <Field.Root>\n            <Autocomplete.Root defaultValue=\"initial value\">\n              <Autocomplete.Input data-testid=\"input\" />\n              <Autocomplete.Portal>\n                <Autocomplete.Positioner>\n                  <Autocomplete.Popup>\n                    <Autocomplete.List>\n                      <Autocomplete.Item value=\"1\">Option 1</Autocomplete.Item>\n                    </Autocomplete.List>\n                  </Autocomplete.Popup>\n                </Autocomplete.Positioner>\n              </Autocomplete.Portal>\n            </Autocomplete.Root>\n          </Field.Root>,\n        );\n\n        const input = screen.getByTestId('input');\n\n        expect(input).toHaveAttribute('data-filled');\n      });\n    });\n\n    it('[data-focused]', async () => {\n      await render(\n        <Field.Root>\n          <Autocomplete.Root>\n            <Autocomplete.Input data-testid=\"input\" />\n            <Autocomplete.Portal>\n              <Autocomplete.Positioner>\n                <Autocomplete.Popup>\n                  <Autocomplete.List>\n                    <Autocomplete.Item value=\"\">Select</Autocomplete.Item>\n                    <Autocomplete.Item value=\"1\">Option 1</Autocomplete.Item>\n                  </Autocomplete.List>\n                </Autocomplete.Popup>\n              </Autocomplete.Positioner>\n            </Autocomplete.Portal>\n          </Autocomplete.Root>\n        </Field.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n\n      expect(input).not.toHaveAttribute('data-focused');\n\n      fireEvent.focus(input);\n\n      expect(input).toHaveAttribute('data-focused', '');\n\n      fireEvent.blur(input);\n\n      expect(input).not.toHaveAttribute('data-focused');\n    });\n\n    it('[data-invalid]', async () => {\n      await render(\n        <Field.Root invalid>\n          <Autocomplete.Root>\n            <Autocomplete.Input data-testid=\"input\" />\n            <Autocomplete.Portal>\n              <Autocomplete.Positioner>\n                <Autocomplete.Popup>\n                  <Autocomplete.List>\n                    <Autocomplete.Item value=\"1\">Option 1</Autocomplete.Item>\n                  </Autocomplete.List>\n                </Autocomplete.Popup>\n              </Autocomplete.Positioner>\n            </Autocomplete.Portal>\n          </Autocomplete.Root>\n        </Field.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n\n      expect(input).toHaveAttribute('data-invalid', '');\n    });\n\n    it('[data-valid]', async () => {\n      const { user } = await render(\n        <Field.Root validationMode=\"onBlur\">\n          <Autocomplete.Root required>\n            <Autocomplete.Input data-testid=\"input\" />\n            <Autocomplete.Portal>\n              <Autocomplete.Positioner>\n                <Autocomplete.Popup>\n                  <Autocomplete.List>\n                    <Autocomplete.Item value=\"1\">Option 1</Autocomplete.Item>\n                  </Autocomplete.List>\n                </Autocomplete.Popup>\n              </Autocomplete.Positioner>\n            </Autocomplete.Portal>\n          </Autocomplete.Root>\n        </Field.Root>,\n      );\n\n      const input = screen.getByRole('combobox');\n      expect(input).not.toHaveAttribute('data-valid');\n      expect(input).not.toHaveAttribute('data-invalid');\n\n      await user.type(input, 'ok');\n      await act(async () => input.blur());\n      await flushMicrotasks();\n\n      expect(input).toHaveAttribute('data-valid', '');\n      expect(input).not.toHaveAttribute('data-invalid');\n    });\n\n    it('prop: validate', async () => {\n      await render(\n        <Field.Root validationMode=\"onBlur\" validate={() => 'error'}>\n          <Autocomplete.Root>\n            <Autocomplete.Input data-testid=\"input\" />\n            <Autocomplete.Portal>\n              <Autocomplete.Positioner />\n            </Autocomplete.Portal>\n          </Autocomplete.Root>\n        </Field.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      expect(input).not.toHaveAttribute('aria-invalid');\n\n      fireEvent.focus(input);\n      fireEvent.blur(input);\n      await flushMicrotasks();\n      expect(input).toHaveAttribute('aria-invalid', 'true');\n    });\n\n    it('prop: validationMode=onSubmit', async () => {\n      const { user } = await render(\n        <Form>\n          <Field.Root\n            validate={(value) => {\n              return value === 'one' ? 'error' : null;\n            }}\n          >\n            <Autocomplete.Root required>\n              <Autocomplete.Input data-testid=\"input\" />\n              <Autocomplete.Portal>\n                <Autocomplete.Positioner>\n                  <Autocomplete.Popup>\n                    <Autocomplete.List>\n                      <Autocomplete.Item value=\"one\">Option 1</Autocomplete.Item>\n                      <Autocomplete.Item value=\"two\">Option 2</Autocomplete.Item>\n                    </Autocomplete.List>\n                  </Autocomplete.Popup>\n                </Autocomplete.Positioner>\n              </Autocomplete.Portal>\n            </Autocomplete.Root>\n          </Field.Root>\n          <button type=\"submit\">submit</button>\n        </Form>,\n      );\n\n      const input = screen.getByTestId('input');\n      expect(input).not.toHaveAttribute('aria-invalid');\n\n      await user.click(screen.getByText('submit'));\n      expect(input).toHaveAttribute('aria-invalid', 'true');\n\n      await user.type(input, 'two');\n      expect(input).not.toHaveAttribute('aria-invalid');\n\n      await user.clear(input);\n      await user.type(input, 'one');\n      expect(input).toHaveAttribute('aria-invalid', 'true');\n\n      await user.clear(input);\n      await user.type(input, 'three');\n      expect(input).not.toHaveAttribute('aria-invalid');\n    });\n\n    // flaky in real browser\n    it.skipIf(!isJSDOM)('prop: validationMode=onChange', async () => {\n      const { user } = await render(\n        <Field.Root\n          validationMode=\"onChange\"\n          validate={(value) => {\n            return value === 'invalid' ? 'error' : null;\n          }}\n        >\n          <Autocomplete.Root>\n            <Autocomplete.Input data-testid=\"input\" />\n            <Autocomplete.Portal>\n              <Autocomplete.Positioner>\n                <Autocomplete.Popup>\n                  <Autocomplete.List>\n                    <Autocomplete.Item value=\"valid\">Valid Option</Autocomplete.Item>\n                  </Autocomplete.List>\n                </Autocomplete.Popup>\n              </Autocomplete.Positioner>\n            </Autocomplete.Portal>\n          </Autocomplete.Root>\n        </Field.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n\n      expect(input).not.toHaveAttribute('aria-invalid');\n\n      await user.type(input, 'invalid');\n\n      expect(input).toHaveAttribute('aria-invalid', 'true');\n    });\n\n    // flaky in real browser\n    it.skipIf(!isJSDOM)('prop: validationMode=onBlur', async () => {\n      const { user } = await render(\n        <Field.Root\n          validationMode=\"onBlur\"\n          validate={(value) => {\n            return value === 'invalid' ? 'error' : null;\n          }}\n        >\n          <Autocomplete.Root>\n            <Autocomplete.Input data-testid=\"input\" />\n            <Autocomplete.Portal>\n              <Autocomplete.Positioner>\n                <Autocomplete.Popup>\n                  <Autocomplete.List>\n                    <Autocomplete.Item value=\"valid\">Valid Option</Autocomplete.Item>\n                  </Autocomplete.List>\n                </Autocomplete.Popup>\n              </Autocomplete.Positioner>\n            </Autocomplete.Portal>\n          </Autocomplete.Root>\n          <Field.Error data-testid=\"error\" />\n        </Field.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n\n      expect(input).not.toHaveAttribute('aria-invalid');\n\n      await user.type(input, 'invalid');\n\n      fireEvent.blur(input);\n\n      await waitFor(() => {\n        expect(input).toHaveAttribute('aria-invalid', 'true');\n      });\n    });\n\n    it('Field.Label', async () => {\n      await render(\n        <Field.Root>\n          <Autocomplete.Root>\n            <Autocomplete.Input data-testid=\"input\" />\n            <Autocomplete.Portal>\n              <Autocomplete.Positioner />\n            </Autocomplete.Portal>\n          </Autocomplete.Root>\n          <Field.Label data-testid=\"label\" render={<span />} nativeLabel={false} />\n        </Field.Root>,\n      );\n\n      expect(screen.getByTestId('input')).toHaveAttribute(\n        'aria-labelledby',\n        screen.getByTestId('label').id,\n      );\n    });\n\n    it('Field.Description', async () => {\n      await render(\n        <Field.Root>\n          <Autocomplete.Root>\n            <Autocomplete.Input data-testid=\"input\" />\n            <Autocomplete.Portal>\n              <Autocomplete.Positioner />\n            </Autocomplete.Portal>\n          </Autocomplete.Root>\n          <Field.Description data-testid=\"description\" />\n        </Field.Root>,\n      );\n\n      expect(screen.getByTestId('input')).toHaveAttribute(\n        'aria-describedby',\n        screen.getByTestId('description').id,\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/autocomplete/root/AutocompleteRoot.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { AriaCombobox, type AriaComboboxState } from '../../combobox/root/AriaCombobox';\nimport { useCoreFilter } from '../../combobox/root/utils/useFilter';\nimport { stringifyAsLabel } from '../../utils/resolveValueLabel';\nimport { REASONS } from '../../utils/reasons';\n\n/**\n * Groups all parts of the autocomplete.\n * Doesn't render its own HTML element.\n *\n * Documentation: [Base UI Autocomplete](https://base-ui.com/react/components/autocomplete)\n */\nexport function AutocompleteRoot<Items extends readonly { items: readonly any[] }[]>(\n  props: Omit<AutocompleteRoot.Props<Items[number]['items'][number]>, 'items'> & {\n    /**\n     * The items to be displayed in the list.\n     * Can be either a flat array of items or an array of groups with items.\n     */\n    items: Items;\n  },\n): React.JSX.Element;\nexport function AutocompleteRoot<ItemValue>(\n  props: Omit<AutocompleteRoot.Props<ItemValue>, 'items'> & {\n    /**\n     * The items to be displayed in the list.\n     * Can be either a flat array of items or an array of groups with items.\n     */\n    items?: readonly ItemValue[] | undefined;\n  },\n): React.JSX.Element;\nexport function AutocompleteRoot<ItemValue>(\n  props: AutocompleteRoot.Props<ItemValue>,\n): React.JSX.Element {\n  const {\n    openOnInputClick = false,\n    value,\n    defaultValue,\n    onValueChange,\n    mode = 'list',\n    itemToStringValue,\n    ...other\n  } = props;\n\n  const enableInline = mode === 'inline' || mode === 'both';\n  const staticItems = mode === 'inline' || mode === 'none';\n\n  // Mirror the typed value for uncontrolled usage so we can compose the temporary\n  // inline input value.\n  const isControlled = value !== undefined;\n  const [internalValue, setInternalValue] = React.useState(defaultValue ?? '');\n  const [inlineInputValue, setInlineInputValue] = React.useState('');\n\n  React.useEffect(() => {\n    if (isControlled) {\n      setInlineInputValue('');\n    }\n  }, [value, isControlled]);\n\n  // Compose the input value shown to the user: inline value takes precedence when present.\n  let resolvedInputValue: typeof value;\n  if (enableInline && inlineInputValue !== '') {\n    resolvedInputValue = inlineInputValue;\n  } else if (isControlled) {\n    resolvedInputValue = value ?? '';\n  } else {\n    resolvedInputValue = internalValue;\n  }\n\n  const handleValueChange = useStableCallback(\n    (nextValue: string, eventDetails: AutocompleteRoot.ChangeEventDetails) => {\n      setInlineInputValue('');\n      if (!isControlled) {\n        setInternalValue(nextValue);\n      }\n      onValueChange?.(nextValue, eventDetails);\n    },\n  );\n\n  const collator = useCoreFilter();\n\n  const baseFilter = React.useMemo<Exclude<typeof other.filter, undefined>>(() => {\n    if (other.filter !== undefined) {\n      return other.filter;\n    }\n    return collator.contains;\n  }, [other.filter, collator]);\n\n  const resolvedQuery = String(isControlled ? value : internalValue).trim();\n\n  // In \"both\", wrap filtering to use only the typed value, ignoring the inline value.\n  const resolvedFilter: typeof other.filter = React.useMemo(() => {\n    if (mode !== 'both') {\n      return staticItems ? null : baseFilter;\n    }\n    if (baseFilter === null) {\n      return null;\n    }\n    return (item, _query, toString) => {\n      return baseFilter(item, resolvedQuery, toString);\n    };\n  }, [baseFilter, mode, resolvedQuery, staticItems]);\n\n  const handleItemHighlighted = useStableCallback(\n    (highlightedValue: any, eventDetails: AriaCombobox.HighlightEventDetails) => {\n      props.onItemHighlighted?.(highlightedValue, eventDetails);\n\n      if (eventDetails.reason === REASONS.pointer) {\n        return;\n      }\n\n      if (enableInline) {\n        if (highlightedValue == null) {\n          setInlineInputValue('');\n        } else {\n          setInlineInputValue(stringifyAsLabel(highlightedValue, itemToStringValue));\n        }\n      } else {\n        setInlineInputValue('');\n      }\n    },\n  );\n\n  return (\n    <AriaCombobox\n      {...other}\n      itemToStringLabel={itemToStringValue}\n      openOnInputClick={openOnInputClick}\n      selectionMode=\"none\"\n      fillInputOnItemPress\n      filter={resolvedFilter}\n      autoComplete={mode}\n      inputValue={resolvedInputValue}\n      defaultInputValue={defaultValue}\n      onInputValueChange={handleValueChange}\n      onItemHighlighted={handleItemHighlighted}\n    />\n  );\n}\n\nexport interface AutocompleteRootState extends AriaComboboxState {}\n\nexport interface AutocompleteRootActions {\n  unmount: () => void;\n}\n\nexport type AutocompleteRootChangeEventReason = AriaCombobox.ChangeEventReason;\nexport type AutocompleteRootChangeEventDetails = AriaCombobox.ChangeEventDetails;\n\nexport type AutocompleteRootHighlightEventReason = AriaCombobox.HighlightEventReason;\nexport type AutocompleteRootHighlightEventDetails = AriaCombobox.HighlightEventDetails;\n\nexport interface AutocompleteRootProps<ItemValue> extends Omit<\n  AriaCombobox.Props<ItemValue, 'none'>,\n  | 'selectionMode'\n  | 'selectedValue'\n  | 'defaultSelectedValue'\n  | 'onSelectedValueChange'\n  | 'fillInputOnItemPress'\n  | 'itemToStringValue'\n  | 'isItemEqualToValue'\n  // Different names\n  | 'inputValue' // value\n  | 'defaultInputValue' // defaultValue\n  | 'onInputValueChange' // onValueChange\n  | 'autoComplete' // mode\n  | 'formAutoComplete'\n  | 'itemToStringLabel' // itemToStringValue\n  // Custom JSDoc\n  | 'autoHighlight'\n  | 'keepHighlight'\n  | 'highlightItemOnHover'\n  | 'actionsRef'\n  | 'onOpenChange'\n  | 'openOnInputClick'\n> {\n  /**\n   * Controls how the autocomplete behaves with respect to list filtering and inline autocompletion.\n   * - `list` (default): items are dynamically filtered based on the input value. The input value does not change based on the active item.\n   * - `both`: items are dynamically filtered based on the input value, which will temporarily change based on the active item (inline autocompletion).\n   * - `inline`: items are static (not filtered), and the input value will temporarily change based on the active item (inline autocompletion).\n   * - `none`: items are static (not filtered), and the input value will not change based on the active item.\n   * @default 'list'\n   */\n  mode?: 'list' | 'both' | 'inline' | 'none' | undefined;\n  /**\n   * Whether the first matching item is highlighted automatically.\n   * - `true`: highlight after the user types and keep the highlight while the query changes.\n   * - `'always'`: always highlight the first item.\n   * @default false\n   */\n  autoHighlight?: boolean | 'always' | undefined;\n  /**\n   * Whether the highlighted item should be preserved when the pointer leaves the list.\n   * @default false\n   */\n  keepHighlight?: boolean | undefined;\n  /**\n   * Whether moving the pointer over items should highlight them.\n   * Disabling this prop allows CSS `:hover` to be differentiated from the `:focus` (`data-highlighted`) state.\n   * @default true\n   */\n  highlightItemOnHover?: boolean | undefined;\n  /**\n   * The uncontrolled input value of the autocomplete when it's initially rendered.\n   *\n   * To render a controlled autocomplete, use the `value` prop instead.\n   */\n  defaultValue?:\n    | AriaCombobox.Props<React.ComponentProps<'input'>['defaultValue'], 'none'>['defaultInputValue']\n    | undefined;\n  /**\n   * The input value of the autocomplete. Use when controlled.\n   */\n  value?:\n    | AriaCombobox.Props<React.ComponentProps<'input'>['value'], 'none'>['inputValue']\n    | undefined;\n  /**\n   * Event handler called when the input value of the autocomplete changes.\n   */\n  onValueChange?:\n    | ((value: string, eventDetails: AutocompleteRootChangeEventDetails) => void)\n    | undefined;\n  /**\n   * Whether clicking an item should submit the autocomplete's owning form.\n   * By default, clicking an item via a pointer or <kbd>Enter</kbd> key does not submit the owning form.\n   * Useful when the autocomplete is used as a single-field form search input.\n   * @default false\n   */\n  submitOnItemClick?: AriaCombobox.Props<ItemValue, 'none'>['submitOnItemClick'] | undefined;\n  /**\n   * When the item values are objects (`<Autocomplete.Item value={object}>`), this function converts the object value to a string representation for both display in the input and form submission.\n   * If the shape of the object is `{ value, label }`, the label will be used automatically without needing to specify this prop.\n   */\n  itemToStringValue?: ((itemValue: ItemValue) => string) | undefined;\n  /**\n   * A ref to imperative actions.\n   * - `unmount`: When specified, the autocomplete will not be unmounted when closed.\n   * Instead, the `unmount` function must be called to unmount the autocomplete manually.\n   * Useful when the autocomplete's animation is controlled by an external library.\n   */\n  actionsRef?: React.RefObject<AutocompleteRootActions | null> | undefined;\n  /**\n   * Event handler called when the popup is opened or closed.\n   */\n  onOpenChange?:\n    | ((open: boolean, eventDetails: AutocompleteRootChangeEventDetails) => void)\n    | undefined;\n  /**\n   * Callback fired when an item is highlighted or unhighlighted.\n   * Receives the highlighted item value (or `undefined` if no item is highlighted) and event details with a `reason` property describing why the highlight changed.\n   * The `reason` can be:\n   * - `'keyboard'`: the highlight changed due to keyboard navigation.\n   * - `'pointer'`: the highlight changed due to pointer hovering.\n   * - `'none'`: the highlight changed programmatically.\n   */\n  onItemHighlighted?:\n    | ((\n        highlightedValue: ItemValue | undefined,\n        eventDetails: AutocompleteRootHighlightEventDetails,\n      ) => void)\n    | undefined;\n  /**\n   * Whether the popup opens when clicking the input.\n   * @default false\n   */\n  openOnInputClick?: boolean | undefined;\n}\n\nexport namespace AutocompleteRoot {\n  export type Props<ItemValue> = AutocompleteRootProps<ItemValue>;\n  export type State = AutocompleteRootState;\n  export type Actions = AutocompleteRootActions;\n  export type ChangeEventReason = AutocompleteRootChangeEventReason;\n  export type ChangeEventDetails = AutocompleteRootChangeEventDetails;\n  export type HighlightEventReason = AutocompleteRootHighlightEventReason;\n  export type HighlightEventDetails = AutocompleteRootHighlightEventDetails;\n}\n"
  },
  {
    "path": "packages/react/src/autocomplete/value/AutocompleteValue.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { screen } from '@mui/internal-test-utils';\nimport { Autocomplete } from '@base-ui/react/autocomplete';\nimport { createRenderer } from '#test-utils';\n\ndescribe('<Autocomplete.Value />', () => {\n  const { render } = createRenderer();\n\n  describe('prop: children', () => {\n    it('renders current input value via function child', async () => {\n      await render(\n        <Autocomplete.Root defaultValue=\"hel\">\n          <Autocomplete.Trigger>\n            <Autocomplete.Value>{(val) => <div data-testid=\"value\">{val}</div>}</Autocomplete.Value>\n          </Autocomplete.Trigger>\n          <Autocomplete.Portal>\n            <Autocomplete.Positioner>\n              <Autocomplete.Popup>\n                <Autocomplete.List>\n                  <Autocomplete.Item value=\"hello\">hello</Autocomplete.Item>\n                  <Autocomplete.Item value=\"help\">help</Autocomplete.Item>\n                </Autocomplete.List>\n              </Autocomplete.Popup>\n            </Autocomplete.Positioner>\n          </Autocomplete.Portal>\n        </Autocomplete.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('hel');\n    });\n\n    it('renders function child with empty string when no value typed', async () => {\n      await render(\n        <Autocomplete.Root>\n          <Autocomplete.Value>\n            {(val) => <div data-testid=\"value\">{val === '' ? 'empty' : String(val)}</div>}\n          </Autocomplete.Value>\n          <Autocomplete.Portal>\n            <Autocomplete.Positioner>\n              <Autocomplete.Popup>\n                <Autocomplete.List>\n                  <Autocomplete.Item value=\"a\">a</Autocomplete.Item>\n                </Autocomplete.List>\n              </Autocomplete.Popup>\n            </Autocomplete.Positioner>\n          </Autocomplete.Portal>\n        </Autocomplete.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('empty');\n    });\n\n    it('overrides the display when children is a static ReactNode', async () => {\n      await render(\n        <Autocomplete.Root defaultValue=\"test-value\">\n          <Autocomplete.Value>Custom Display Text</Autocomplete.Value>\n          <Autocomplete.Portal>\n            <Autocomplete.Positioner>\n              <Autocomplete.Popup>\n                <Autocomplete.List>\n                  <Autocomplete.Item value=\"test-value\">Test</Autocomplete.Item>\n                </Autocomplete.List>\n              </Autocomplete.Popup>\n            </Autocomplete.Positioner>\n          </Autocomplete.Portal>\n        </Autocomplete.Root>,\n      );\n\n      expect(screen.getByText('Custom Display Text')).not.toBe(null);\n    });\n\n    it('renders complex ReactNode children', async () => {\n      await render(\n        <Autocomplete.Root defaultValue=\"test\">\n          <Autocomplete.Value>\n            <span data-testid=\"complex\">\n              <strong>Bold</strong> and <em>italic</em> text\n            </span>\n          </Autocomplete.Value>\n          <Autocomplete.Portal>\n            <Autocomplete.Positioner>\n              <Autocomplete.Popup>\n                <Autocomplete.List>\n                  <Autocomplete.Item value=\"test\">Test</Autocomplete.Item>\n                </Autocomplete.List>\n              </Autocomplete.Popup>\n            </Autocomplete.Positioner>\n          </Autocomplete.Portal>\n        </Autocomplete.Root>,\n      );\n\n      const element = screen.getByTestId('complex');\n      expect(element.querySelector('strong')).toHaveTextContent('Bold');\n      expect(element.querySelector('em')).toHaveTextContent('italic');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/autocomplete/value/AutocompleteValue.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useComboboxInputValueContext } from '../../combobox/root/ComboboxRootContext';\n\n/**\n * The current value of the autocomplete.\n * Doesn't render its own HTML element.\n *\n * Documentation: [Base UI Autocomplete](https://base-ui.com/react/components/autocomplete)\n */\nexport function AutocompleteValue(props: AutocompleteValue.Props): React.ReactElement {\n  const { children } = props;\n\n  const inputValue = useComboboxInputValueContext();\n\n  let returnValue = null;\n  if (typeof children === 'function') {\n    returnValue = children(String(inputValue));\n  } else if (children != null) {\n    returnValue = children;\n  } else {\n    returnValue = inputValue;\n  }\n\n  return <React.Fragment>{returnValue}</React.Fragment>;\n}\n\nexport interface AutocompleteValueState {}\n\nexport interface AutocompleteValueProps {\n  children?: React.ReactNode | ((value: string) => React.ReactNode);\n}\n\nexport namespace AutocompleteValue {\n  export type State = AutocompleteValueState;\n  export type Props = AutocompleteValueProps;\n}\n"
  },
  {
    "path": "packages/react/src/avatar/fallback/AvatarFallback.test.tsx",
    "content": "import { Mock, vi, expect } from 'vitest';\nimport * as React from 'react';\nimport { Avatar } from '@base-ui/react/avatar';\nimport { waitFor, screen } from '@mui/internal-test-utils';\nimport { describeConformance, createRenderer, isJSDOM } from '#test-utils';\nimport { useImageLoadingStatus } from '../image/useImageLoadingStatus';\n\nvi.mock('../image/useImageLoadingStatus');\n\ndescribe('<Avatar.Fallback />', () => {\n  const { render } = createRenderer();\n\n  afterEach(() => {\n    vi.clearAllMocks();\n  });\n\n  describeConformance(<Avatar.Fallback />, () => ({\n    render: (node) => {\n      return render(<Avatar.Root>{node}</Avatar.Root>);\n    },\n    refInstanceof: window.HTMLSpanElement,\n  }));\n\n  it.skipIf(!isJSDOM)('should not render the children if the image loaded', async () => {\n    (useImageLoadingStatus as Mock).mockReturnValue('loaded');\n\n    await render(\n      <Avatar.Root>\n        <Avatar.Image />\n        <Avatar.Fallback data-testid=\"fallback\" />\n      </Avatar.Root>,\n    );\n\n    await waitFor(() => {\n      expect(screen.queryByTestId('fallback')).toBe(null);\n    });\n  });\n\n  it.skipIf(!isJSDOM)('should render the fallback if the image fails to load', async () => {\n    (useImageLoadingStatus as Mock).mockReturnValue('error');\n\n    await render(\n      <Avatar.Root>\n        <Avatar.Image />\n        <Avatar.Fallback>AC</Avatar.Fallback>\n      </Avatar.Root>,\n    );\n\n    await waitFor(() => {\n      expect(screen.queryByText('AC')).not.toBe(null);\n    });\n  });\n\n  describe.skipIf(!isJSDOM)('prop: delay', () => {\n    const { clock, render: renderFakeTimers } = createRenderer();\n\n    clock.withFakeTimers();\n\n    it('shows the fallback when the delay has elapsed', async () => {\n      await renderFakeTimers(\n        <Avatar.Root>\n          <Avatar.Image />\n          <Avatar.Fallback delay={100}>AC</Avatar.Fallback>\n        </Avatar.Root>,\n      );\n\n      expect(screen.queryByText('AC')).toBe(null);\n\n      clock.tick(100);\n\n      expect(screen.queryByText('AC')).not.toBe(null);\n    });\n  });\n\n  it.skipIf(!isJSDOM)(\n    'keeps fallback mounted and image unmounted while the image is loading',\n    async () => {\n      const useImageLoadingStatusMock = useImageLoadingStatus as Mock;\n      useImageLoadingStatusMock.mockImplementation((src) => (src ? 'loading' : 'error'));\n\n      function Test() {\n        const [showImage, setShowImage] = React.useState(false);\n\n        function handleShowImage() {\n          setShowImage(true);\n        }\n\n        return (\n          <div>\n            <button onClick={handleShowImage}>Show image</button>\n            <Avatar.Root>\n              <Avatar.Image data-testid=\"image\" src={showImage ? 'avatar.png' : undefined} />\n              <Avatar.Fallback data-testid=\"fallback\">AC</Avatar.Fallback>\n            </Avatar.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<Test />);\n\n      expect(screen.queryByTestId('image')).toBe(null);\n      expect(screen.getByTestId('fallback')).not.toBe(null);\n\n      await user.click(screen.getByText('Show image'));\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('image')).toBe(null);\n        expect(screen.getByTestId('fallback')).not.toBe(null);\n      });\n    },\n  );\n\n  describe.skipIf(isJSDOM)('regression', () => {\n    afterEach(() => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = true;\n    });\n\n    it('keeps only one of image or fallback mounted when switching to image', async () => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n      const useImageLoadingStatusMock = useImageLoadingStatus as Mock;\n      useImageLoadingStatusMock.mockImplementation((src) => (src ? 'loaded' : 'error'));\n\n      const style = `\n        @keyframes test-exit {\n          to {\n            opacity: 0;\n          }\n        }\n\n        .animation-test-fallback[data-ending-style] {\n          animation: test-exit 2s;\n        }\n      `;\n\n      function Test() {\n        const [showImage, setShowImage] = React.useState(false);\n\n        function handleShowImage() {\n          setShowImage(true);\n        }\n\n        return (\n          <div>\n            {/* eslint-disable-next-line react/no-danger */}\n            <style dangerouslySetInnerHTML={{ __html: style }} />\n            <button onClick={handleShowImage}>Show image</button>\n            <Avatar.Root>\n              <Avatar.Image data-testid=\"image\" src={showImage ? 'avatar.png' : undefined} />\n              <Avatar.Fallback className=\"animation-test-fallback\" data-testid=\"fallback\">\n                AC\n              </Avatar.Fallback>\n            </Avatar.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<Test />);\n\n      expect(screen.queryByTestId('image')).toBe(null);\n      expect(screen.getByTestId('fallback')).not.toBe(null);\n\n      await user.click(screen.getByText('Show image'));\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('image')).not.toBe(null);\n        expect(screen.queryByTestId('fallback')).toBe(null);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/avatar/fallback/AvatarFallback.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useTimeout } from '@base-ui/utils/useTimeout';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useAvatarRootContext } from '../root/AvatarRootContext';\nimport type { AvatarRootState } from '../root/AvatarRoot';\nimport { avatarStateAttributesMapping } from '../root/stateAttributesMapping';\n\n/**\n * Rendered when the image fails to load or when no image is provided.\n * Renders a `<span>` element.\n *\n * Documentation: [Base UI Avatar](https://base-ui.com/react/components/avatar)\n */\nexport const AvatarFallback = React.forwardRef(function AvatarFallback(\n  componentProps: AvatarFallback.Props,\n  forwardedRef: React.ForwardedRef<HTMLSpanElement>,\n) {\n  const { className, render, delay, ...elementProps } = componentProps;\n\n  const { imageLoadingStatus } = useAvatarRootContext();\n  const [delayPassed, setDelayPassed] = React.useState(delay === undefined);\n  const timeout = useTimeout();\n\n  React.useEffect(() => {\n    if (delay !== undefined) {\n      timeout.start(delay, () => setDelayPassed(true));\n    }\n    return timeout.clear;\n  }, [timeout, delay]);\n\n  const state: AvatarFallbackState = {\n    imageLoadingStatus,\n  };\n\n  const element = useRenderElement('span', componentProps, {\n    state,\n    ref: forwardedRef,\n    props: elementProps,\n    stateAttributesMapping: avatarStateAttributesMapping,\n    enabled: imageLoadingStatus !== 'loaded' && delayPassed,\n  });\n\n  return element;\n});\n\nexport interface AvatarFallbackState extends AvatarRootState {}\n\nexport interface AvatarFallbackProps extends BaseUIComponentProps<'span', AvatarFallbackState> {\n  /**\n   * How long to wait before showing the fallback. Specified in milliseconds.\n   */\n  delay?: number | undefined;\n}\n\nexport namespace AvatarFallback {\n  export type State = AvatarFallbackState;\n  export type Props = AvatarFallbackProps;\n}\n"
  },
  {
    "path": "packages/react/src/avatar/image/AvatarImage.test.tsx",
    "content": "import { Mock, vi, expect } from 'vitest';\nimport * as React from 'react';\nimport { Avatar } from '@base-ui/react/avatar';\nimport { screen, waitFor } from '@mui/internal-test-utils';\nimport { describeConformance, createRenderer, isJSDOM } from '#test-utils';\nimport { useImageLoadingStatus } from './useImageLoadingStatus';\n\nvi.mock('./useImageLoadingStatus');\n\ndescribe('<Avatar.Image />', () => {\n  const { render } = createRenderer();\n\n  const useImageLoadingStatusMock = useImageLoadingStatus as Mock;\n\n  beforeEach(() => {\n    useImageLoadingStatusMock.mockReturnValue('loaded');\n  });\n\n  afterEach(() => {\n    vi.clearAllMocks();\n  });\n\n  describeConformance(<Avatar.Image />, () => ({\n    render: (node) => {\n      return render(<Avatar.Root>{node}</Avatar.Root>);\n    },\n    refInstanceof: window.HTMLImageElement,\n  }));\n\n  describe.skipIf(isJSDOM)('animations', () => {\n    afterEach(() => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = true;\n    });\n\n    it('triggers enter animation via data-starting-style when mounting', async () => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n      useImageLoadingStatusMock.mockImplementation((src) => (src ? 'loaded' : 'idle'));\n\n      let transitionFinished = false;\n      function notifyTransitionFinished() {\n        transitionFinished = true;\n      }\n\n      const style = `\n        .animation-test-image {\n          transition: opacity 1ms;\n        }\n\n        .animation-test-image[data-starting-style],\n        .animation-test-image[data-ending-style] {\n          opacity: 0;\n        }\n      `;\n\n      function Test() {\n        const [showImage, setShowImage] = React.useState(false);\n\n        function handleShowImage() {\n          setShowImage(true);\n        }\n\n        return (\n          <div>\n            {/* eslint-disable-next-line react/no-danger */}\n            <style dangerouslySetInnerHTML={{ __html: style }} />\n            <button onClick={handleShowImage}>Show image</button>\n            <Avatar.Root>\n              <Avatar.Image\n                className=\"animation-test-image\"\n                data-testid=\"image\"\n                onTransitionEnd={notifyTransitionFinished}\n                src={showImage ? 'avatar.png' : undefined}\n              />\n            </Avatar.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<Test />);\n      expect(screen.queryByTestId('image')).toBe(null);\n\n      await user.click(screen.getByText('Show image'));\n\n      await waitFor(() => {\n        expect(transitionFinished).toBe(true);\n      });\n\n      expect(screen.getByTestId('image')).not.toBe(null);\n    });\n\n    it('applies data-ending-style before unmount', async () => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n      useImageLoadingStatusMock.mockImplementation((src) => (src ? 'loaded' : 'idle'));\n\n      const style = `\n        @keyframes test-anim {\n          to {\n            opacity: 0;\n          }\n        }\n\n        .animation-test-image[data-ending-style] {\n          animation: test-anim 1ms;\n        }\n      `;\n\n      function Test() {\n        const [showImage, setShowImage] = React.useState(true);\n\n        function handleHideImage() {\n          setShowImage(false);\n        }\n\n        return (\n          <div>\n            {/* eslint-disable-next-line react/no-danger */}\n            <style dangerouslySetInnerHTML={{ __html: style }} />\n            <button onClick={handleHideImage}>Hide image</button>\n            <Avatar.Root>\n              <Avatar.Image\n                className=\"animation-test-image\"\n                data-testid=\"image\"\n                src={showImage ? 'avatar.png' : undefined}\n              />\n            </Avatar.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<Test />);\n      expect(screen.getByTestId('image')).not.toBe(null);\n\n      await user.click(screen.getByText('Hide image'));\n\n      await waitFor(() => {\n        const image = screen.queryByTestId('image');\n        expect(image).not.toBe(null);\n        expect(image).toHaveAttribute('data-ending-style');\n      });\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('image')).toBe(null);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/avatar/image/AvatarImage.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport type { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useAvatarRootContext } from '../root/AvatarRootContext';\nimport type { AvatarRootState } from '../root/AvatarRoot';\nimport { avatarStateAttributesMapping } from '../root/stateAttributesMapping';\nimport { useOpenChangeComplete } from '../../utils/useOpenChangeComplete';\nimport { transitionStatusMapping } from '../../utils/stateAttributesMapping';\nimport { type TransitionStatus, useTransitionStatus } from '../../utils/useTransitionStatus';\nimport { useImageLoadingStatus, ImageLoadingStatus } from './useImageLoadingStatus';\n\nconst stateAttributesMapping: StateAttributesMapping<AvatarImageState> = {\n  ...avatarStateAttributesMapping,\n  ...transitionStatusMapping,\n};\n\n/**\n * The image to be displayed in the avatar.\n * Renders an `<img>` element.\n *\n * Documentation: [Base UI Avatar](https://base-ui.com/react/components/avatar)\n */\nexport const AvatarImage = React.forwardRef(function AvatarImage(\n  componentProps: AvatarImage.Props,\n  forwardedRef: React.ForwardedRef<HTMLImageElement>,\n) {\n  const {\n    className,\n    render,\n    onLoadingStatusChange: onLoadingStatusChangeProp,\n    referrerPolicy,\n    crossOrigin,\n    ...elementProps\n  } = componentProps;\n\n  const context = useAvatarRootContext();\n  const imageLoadingStatus = useImageLoadingStatus(componentProps.src, {\n    referrerPolicy,\n    crossOrigin,\n  });\n\n  const isVisible = imageLoadingStatus === 'loaded';\n  const { mounted, transitionStatus, setMounted } = useTransitionStatus(isVisible);\n\n  const imageRef = React.useRef<HTMLImageElement | null>(null);\n\n  const handleLoadingStatusChange = useStableCallback((status: ImageLoadingStatus) => {\n    onLoadingStatusChangeProp?.(status);\n    context.setImageLoadingStatus(status);\n  });\n\n  useIsoLayoutEffect(() => {\n    if (imageLoadingStatus !== 'idle') {\n      handleLoadingStatusChange(imageLoadingStatus);\n    }\n  }, [imageLoadingStatus, handleLoadingStatusChange]);\n\n  const state: AvatarImageState = {\n    imageLoadingStatus,\n    transitionStatus,\n  };\n\n  useOpenChangeComplete({\n    open: isVisible,\n    ref: imageRef,\n    onComplete() {\n      if (!isVisible) {\n        setMounted(false);\n      }\n    },\n  });\n\n  const element = useRenderElement('img', componentProps, {\n    state,\n    ref: [forwardedRef, imageRef],\n    props: elementProps,\n    stateAttributesMapping,\n    enabled: mounted,\n  });\n\n  if (!mounted) {\n    return null;\n  }\n\n  return element;\n});\n\nexport interface AvatarImageState extends AvatarRootState {\n  /**\n   * The transition status of the component.\n   */\n  transitionStatus: TransitionStatus;\n}\n\nexport interface AvatarImageProps extends BaseUIComponentProps<'img', AvatarImageState> {\n  /**\n   * Callback fired when the loading status changes.\n   */\n  onLoadingStatusChange?: ((status: ImageLoadingStatus) => void) | undefined;\n}\n\nexport namespace AvatarImage {\n  export type State = AvatarImageState;\n  export type Props = AvatarImageProps;\n}\n"
  },
  {
    "path": "packages/react/src/avatar/image/AvatarImageDataAttributes.ts",
    "content": "import { TransitionStatusDataAttributes } from '../../utils/stateAttributesMapping';\n\nexport enum AvatarImageDataAttributes {\n  /**\n   * Present when the image is animating in.\n   */\n  startingStyle = TransitionStatusDataAttributes.startingStyle,\n  /**\n   * Present when the image is animating out.\n   */\n  endingStyle = TransitionStatusDataAttributes.endingStyle,\n}\n"
  },
  {
    "path": "packages/react/src/avatar/image/useImageLoadingStatus.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { NOOP } from '../../utils/noop';\n\nexport type ImageLoadingStatus = 'idle' | 'loading' | 'loaded' | 'error';\n\ninterface UseImageLoadingStatusOptions {\n  referrerPolicy?: React.HTMLAttributeReferrerPolicy | undefined;\n  crossOrigin?: React.ImgHTMLAttributes<HTMLImageElement>['crossOrigin'] | undefined;\n}\n\nexport function useImageLoadingStatus(\n  src: string | undefined,\n  { referrerPolicy, crossOrigin }: UseImageLoadingStatusOptions,\n): ImageLoadingStatus {\n  const [loadingStatus, setLoadingStatus] = React.useState<ImageLoadingStatus>('idle');\n\n  useIsoLayoutEffect(() => {\n    if (!src) {\n      setLoadingStatus('error');\n      return NOOP;\n    }\n\n    let isMounted = true;\n    const image = new window.Image();\n\n    const updateStatus = (status: ImageLoadingStatus) => () => {\n      if (!isMounted) {\n        return;\n      }\n\n      setLoadingStatus(status);\n    };\n\n    setLoadingStatus('loading');\n    image.onload = updateStatus('loaded');\n    image.onerror = updateStatus('error');\n    if (referrerPolicy) {\n      image.referrerPolicy = referrerPolicy;\n    }\n    image.crossOrigin = crossOrigin ?? null;\n    image.src = src;\n\n    return () => {\n      isMounted = false;\n    };\n  }, [src, crossOrigin, referrerPolicy]);\n\n  return loadingStatus;\n}\n"
  },
  {
    "path": "packages/react/src/avatar/index.parts.ts",
    "content": "export { AvatarRoot as Root } from './root/AvatarRoot';\nexport { AvatarImage as Image } from './image/AvatarImage';\nexport { AvatarFallback as Fallback } from './fallback/AvatarFallback';\n"
  },
  {
    "path": "packages/react/src/avatar/index.ts",
    "content": "export * as Avatar from './index.parts';\n\nexport type * from './root/AvatarRoot';\nexport type * from './image/AvatarImage';\nexport type * from './fallback/AvatarFallback';\n"
  },
  {
    "path": "packages/react/src/avatar/root/AvatarRoot.test.tsx",
    "content": "import { Avatar } from '@base-ui/react/avatar';\nimport { describeConformance, createRenderer } from '#test-utils';\n\ndescribe('<Avatar.Root />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Avatar.Root />, () => ({\n    render,\n    refInstanceof: window.HTMLSpanElement,\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/avatar/root/AvatarRoot.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { AvatarRootContext } from './AvatarRootContext';\nimport { avatarStateAttributesMapping } from './stateAttributesMapping';\n\n/**\n * Displays a user's profile picture, initials, or fallback icon.\n * Renders a `<span>` element.\n *\n * Documentation: [Base UI Avatar](https://base-ui.com/react/components/avatar)\n */\nexport const AvatarRoot = React.forwardRef(function AvatarRoot(\n  componentProps: AvatarRoot.Props,\n  forwardedRef: React.ForwardedRef<HTMLSpanElement>,\n) {\n  const { className, render, ...elementProps } = componentProps;\n\n  const [imageLoadingStatus, setImageLoadingStatus] = React.useState<ImageLoadingStatus>('idle');\n\n  const state: AvatarRootState = {\n    imageLoadingStatus,\n  };\n\n  const contextValue = React.useMemo(\n    () => ({\n      imageLoadingStatus,\n      setImageLoadingStatus,\n    }),\n    [imageLoadingStatus, setImageLoadingStatus],\n  );\n\n  const element = useRenderElement('span', componentProps, {\n    state,\n    ref: forwardedRef,\n    props: elementProps,\n    stateAttributesMapping: avatarStateAttributesMapping,\n  });\n\n  return <AvatarRootContext.Provider value={contextValue}>{element}</AvatarRootContext.Provider>;\n});\n\nexport type ImageLoadingStatus = 'idle' | 'loading' | 'loaded' | 'error';\n\nexport interface AvatarRootState {\n  /**\n   * The image loading status.\n   */\n  imageLoadingStatus: ImageLoadingStatus;\n}\n\nexport interface AvatarRootProps extends BaseUIComponentProps<'span', AvatarRootState> {}\n\nexport namespace AvatarRoot {\n  export type State = AvatarRootState;\n  export type Props = AvatarRootProps;\n}\n"
  },
  {
    "path": "packages/react/src/avatar/root/AvatarRootContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport type { ImageLoadingStatus } from './AvatarRoot';\n\nexport interface AvatarRootContext {\n  imageLoadingStatus: ImageLoadingStatus;\n  setImageLoadingStatus: React.Dispatch<React.SetStateAction<ImageLoadingStatus>>;\n}\n\nexport const AvatarRootContext = React.createContext<AvatarRootContext | undefined>(undefined);\n\nexport function useAvatarRootContext() {\n  const context = React.useContext(AvatarRootContext);\n  if (context === undefined) {\n    throw new Error(\n      'Base UI: AvatarRootContext is missing. Avatar parts must be placed within <Avatar.Root>.',\n    );\n  }\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/avatar/root/stateAttributesMapping.ts",
    "content": "export const avatarStateAttributesMapping = {\n  imageLoadingStatus: () => null,\n};\n"
  },
  {
    "path": "packages/react/src/button/Button.spec.tsx",
    "content": "import { Button } from '@base-ui/react/button';\n\n<Button />;\n<Button type=\"submit\" form=\"form-id\" name=\"action\" />;\n\n<Button nativeButton={false} render={<span />} />;\n<Button nativeButton={false} render={(props) => <div {...props} />} />;\n<Button nativeButton={false} disabled render={<span />} />;\n\n<Button nativeButton={false} type=\"submit\" />;\n"
  },
  {
    "path": "packages/react/src/button/Button.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport { Button } from '@base-ui/react/button';\nimport { screen } from '@mui/internal-test-utils';\nimport { describeConformance, createRenderer } from '#test-utils';\n\ndescribe('<Button />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Button />, () => ({\n    render,\n    refInstanceof: window.HTMLButtonElement,\n    button: true,\n  }));\n\n  describe('prop: disabled', () => {\n    it('native button: uses the disabled attribute and is not focusable', async () => {\n      const handleClick = vi.fn();\n      const handleMouseDown = vi.fn();\n      const handlePointerDown = vi.fn();\n      const handleKeyDown = vi.fn();\n\n      const { user } = await render(\n        <Button\n          disabled\n          onClick={handleClick}\n          onMouseDown={handleMouseDown}\n          onPointerDown={handlePointerDown}\n          onKeyDown={handleKeyDown}\n        />,\n      );\n\n      const button = screen.getByRole('button');\n\n      expect(button).toHaveAttribute('disabled');\n      expect(button).toHaveAttribute('data-disabled');\n      expect(button).not.toHaveAttribute('aria-disabled');\n\n      await user.keyboard('[Tab]');\n      expect(button).not.toHaveFocus();\n\n      await user.click(button);\n      await user.keyboard('[Space]');\n      await user.keyboard('[Enter]');\n\n      expect(handleClick.mock.calls.length).toBe(0);\n      expect(handleMouseDown.mock.calls.length).toBe(0);\n      expect(handlePointerDown.mock.calls.length).toBe(0);\n      expect(handleKeyDown.mock.calls.length).toBe(0);\n    });\n\n    it('custom element: applies aria-disabled and is not focusable', async () => {\n      const handleClick = vi.fn();\n      const handleMouseDown = vi.fn();\n      const handlePointerDown = vi.fn();\n      const handleKeyDown = vi.fn();\n\n      const { user } = await render(\n        <Button\n          disabled\n          nativeButton={false}\n          render={<span />}\n          onClick={handleClick}\n          onMouseDown={handleMouseDown}\n          onPointerDown={handlePointerDown}\n          onKeyDown={handleKeyDown}\n        />,\n      );\n\n      const button = screen.getByRole('button');\n\n      expect(button).not.toHaveAttribute('disabled');\n      expect(button).toHaveAttribute('data-disabled');\n      expect(button).toHaveAttribute('aria-disabled', 'true');\n      expect(button).toHaveAttribute('tabindex', '-1');\n\n      await user.keyboard('[Tab]');\n      expect(button).not.toHaveFocus();\n\n      await user.click(button);\n      await user.keyboard('[Space]');\n      await user.keyboard('[Enter]');\n\n      expect(handleClick.mock.calls.length).toBe(0);\n      expect(handleMouseDown.mock.calls.length).toBe(0);\n      expect(handlePointerDown.mock.calls.length).toBe(0);\n      expect(handleKeyDown.mock.calls.length).toBe(0);\n    });\n  });\n\n  describe('prop: focusableWhenDisabled', () => {\n    it('native button: prevents interactions but remains focusable', async () => {\n      const handleClick = vi.fn();\n      const handleMouseDown = vi.fn();\n      const handlePointerDown = vi.fn();\n      const handleKeyDown = vi.fn();\n\n      const { user } = await render(\n        <Button\n          disabled\n          focusableWhenDisabled\n          onClick={handleClick}\n          onMouseDown={handleMouseDown}\n          onPointerDown={handlePointerDown}\n          onKeyDown={handleKeyDown}\n        />,\n      );\n\n      const button = screen.getByRole('button');\n\n      expect(button).not.toHaveAttribute('disabled');\n      expect(button).toHaveAttribute('data-disabled');\n      expect(button).toHaveAttribute('aria-disabled', 'true');\n      expect(button).toHaveAttribute('tabindex', '0');\n\n      await user.keyboard('[Tab]');\n      expect(button).toHaveFocus();\n\n      await user.click(button);\n      await user.keyboard('[Space]');\n      await user.keyboard('[Enter]');\n\n      expect(handleClick.mock.calls.length).toBe(0);\n      expect(handleMouseDown.mock.calls.length).toBe(0);\n      expect(handlePointerDown.mock.calls.length).toBe(0);\n      expect(handleKeyDown.mock.calls.length).toBe(0);\n    });\n\n    it('custom element: prevents interactions but remains focusable', async () => {\n      const handleClick = vi.fn();\n      const handleMouseDown = vi.fn();\n      const handlePointerDown = vi.fn();\n      const handleKeyDown = vi.fn();\n\n      const { user } = await render(\n        <Button\n          disabled\n          focusableWhenDisabled\n          nativeButton={false}\n          render={<span />}\n          onClick={handleClick}\n          onMouseDown={handleMouseDown}\n          onPointerDown={handlePointerDown}\n          onKeyDown={handleKeyDown}\n        />,\n      );\n\n      const button = screen.getByRole('button');\n\n      expect(button).not.toHaveAttribute('disabled');\n      expect(button).toHaveAttribute('data-disabled');\n      expect(button).toHaveAttribute('aria-disabled', 'true');\n      expect(button).toHaveAttribute('tabindex', '0');\n\n      await user.keyboard('[Tab]');\n      expect(button).toHaveFocus();\n\n      await user.click(button);\n      await user.keyboard('[Space]');\n      await user.keyboard('[Enter]');\n\n      expect(handleClick.mock.calls.length).toBe(0);\n      expect(handleMouseDown.mock.calls.length).toBe(0);\n      expect(handlePointerDown.mock.calls.length).toBe(0);\n      expect(handleKeyDown.mock.calls.length).toBe(0);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/button/Button.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useButton } from '../use-button/useButton';\nimport { useRenderElement } from '../utils/useRenderElement';\nimport type { BaseUIComponentProps, NativeButtonProps } from '../utils/types';\n\n/**\n * A button component that can be used to trigger actions.\n * Renders a `<button>` element.\n *\n * Documentation: [Base UI Button](https://base-ui.com/react/components/button)\n */\nexport const Button = React.forwardRef(function Button(\n  componentProps: Button.Props,\n  forwardedRef: React.ForwardedRef<HTMLElement>,\n) {\n  const {\n    render,\n    className,\n    disabled = false,\n    focusableWhenDisabled = false,\n    nativeButton = true,\n    ...elementProps\n  } = componentProps;\n\n  const { getButtonProps, buttonRef } = useButton({\n    disabled,\n    focusableWhenDisabled,\n    native: nativeButton,\n  });\n\n  const state: ButtonState = {\n    disabled,\n  };\n\n  return useRenderElement('button', componentProps, {\n    state,\n    ref: [forwardedRef, buttonRef],\n    props: [elementProps, getButtonProps],\n  });\n});\n\nexport interface ButtonState {\n  /**\n   * Whether the button should ignore user interaction.\n   */\n  disabled: boolean;\n}\n\nexport interface ButtonProps\n  extends NativeButtonProps, BaseUIComponentProps<'button', ButtonState> {\n  /**\n   * Whether the button should be focusable when disabled.\n   * @default false\n   */\n  focusableWhenDisabled?: boolean | undefined;\n}\n\nexport namespace Button {\n  export type State = ButtonState;\n  export type Props = ButtonProps;\n}\n"
  },
  {
    "path": "packages/react/src/button/ButtonDataAttributes.tsx",
    "content": "export enum ButtonDataAttributes {\n  /**\n   * Present when the button is disabled.\n   */\n  disabled = 'data-disabled',\n}\n"
  },
  {
    "path": "packages/react/src/button/index.ts",
    "content": "export { Button } from './Button';\n\nexport type * from './Button';\n"
  },
  {
    "path": "packages/react/src/calendar/day-button/CalendarDayButton.test.tsx",
    "content": "import * as React from 'react';\nimport { screen } from '@mui/internal-test-utils';\nimport { expect, vi } from 'vitest';\nimport { Calendar } from '@base-ui/react/calendar';\nimport { createTemporalRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Calendar.DayButton />', () => {\n  const { render, adapter } = createTemporalRenderer();\n\n  describeConformance(<Calendar.DayButton />, () => ({\n    refInstanceof: window.HTMLButtonElement,\n    button: true,\n    render(node) {\n      const date = adapter.now('default');\n\n      return render(\n        <Calendar.Root visibleDate={adapter.startOfMonth(date)}>\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>\n              <Calendar.DayGridRow value={adapter.startOfWeek(date)}>\n                <Calendar.DayGridCell value={date}>{node}</Calendar.DayGridCell>\n              </Calendar.DayGridRow>\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n    },\n  }));\n\n  describe('value update', () => {\n    it('should update the value when clicked', async () => {\n      const onValueChange = vi.fn();\n      const date = adapter.date('2025-02-04', 'default');\n\n      const { user } = render(\n        <Calendar.Root visibleDate={adapter.startOfMonth(date)} onValueChange={onValueChange}>\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>\n              <Calendar.DayGridRow value={adapter.startOfWeek(date)}>\n                <Calendar.DayGridCell value={date}>\n                  <Calendar.DayButton />\n                </Calendar.DayGridCell>\n              </Calendar.DayGridRow>\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n\n      await user.click(button);\n      expect(onValueChange.mock.calls.length).toBe(1);\n      expect(onValueChange.mock.calls[0][0]).toEqualDateTime('2025-02-04T00:00:00.000Z');\n    });\n\n    it('should keep the time of the previous value when clicked', async () => {\n      const onValueChange = vi.fn();\n      const date = adapter.date('2025-02-04', 'default');\n\n      const { user } = render(\n        <Calendar.Root\n          visibleDate={adapter.startOfMonth(date)}\n          onValueChange={onValueChange}\n          value={adapter.date('2025-02-01T12:01:02.003Z', 'default')}\n        >\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>\n              <Calendar.DayGridRow value={adapter.startOfWeek(date)}>\n                <Calendar.DayGridCell value={date}>\n                  <Calendar.DayButton />\n                </Calendar.DayGridCell>\n              </Calendar.DayGridRow>\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n\n      await user.click(button);\n      expect(onValueChange.mock.calls.length).toBe(1);\n      expect(onValueChange.mock.calls[0][0]).toEqualDateTime('2025-02-04T12:01:02.003Z');\n    });\n\n    it('should keep the time of the default value when clicked', async () => {\n      const onValueChange = vi.fn();\n      const date = adapter.date('2025-02-04', 'default');\n\n      const { user } = render(\n        <Calendar.Root\n          visibleDate={adapter.startOfMonth(date)}\n          onValueChange={onValueChange}\n          defaultValue={adapter.date('2025-02-01T12:01:02.003Z', 'default')}\n        >\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>\n              <Calendar.DayGridRow value={adapter.startOfWeek(date)}>\n                <Calendar.DayGridCell value={date}>\n                  <Calendar.DayButton />\n                </Calendar.DayGridCell>\n              </Calendar.DayGridRow>\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n\n      await user.click(button);\n      expect(onValueChange.mock.calls.length).toBe(1);\n      expect(onValueChange.mock.calls[0][0]).toEqualDateTime('2025-02-04T12:01:02.003Z');\n    });\n\n    it('should keep the time of the reference date when clicked', async () => {\n      const onValueChange = vi.fn();\n      const date = adapter.date('2025-02-04', 'default');\n\n      const { user } = render(\n        <Calendar.Root\n          visibleDate={adapter.startOfMonth(date)}\n          onValueChange={onValueChange}\n          referenceDate={adapter.date('2025-02-01T12:01:02.003Z', 'default')}\n        >\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>\n              <Calendar.DayGridRow value={adapter.startOfWeek(date)}>\n                <Calendar.DayGridCell value={date}>\n                  <Calendar.DayButton />\n                </Calendar.DayGridCell>\n              </Calendar.DayGridRow>\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n\n      await user.click(button);\n      expect(onValueChange.mock.calls.length).toBe(1);\n      expect(onValueChange.mock.calls[0][0]).toEqualDateTime('2025-02-04T12:01:02.003Z');\n    });\n  });\n\n  describe('selected state', () => {\n    it('should be selected when the date is in the same day as the value', () => {\n      const date = adapter.date('2025-02-04', 'default');\n\n      render(\n        <Calendar.Root\n          visibleDate={adapter.startOfMonth(date)}\n          value={adapter.date('2025-02-04T12:01:02.003Z', 'default')}\n        >\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>\n              <Calendar.DayGridRow value={adapter.startOfWeek(date)}>\n                <Calendar.DayGridCell value={date}>\n                  <Calendar.DayButton />\n                </Calendar.DayGridCell>\n              </Calendar.DayGridRow>\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      expect(button).toHaveAttribute('aria-selected', 'true');\n      expect(button).toHaveAttribute('data-selected');\n    });\n\n    it('should be selected when the date is in the same day as the default value', () => {\n      const date = adapter.date('2025-02-04', 'default');\n\n      render(\n        <Calendar.Root\n          visibleDate={adapter.startOfMonth(date)}\n          defaultValue={adapter.date('2025-02-04T12:01:02.003Z', 'default')}\n        >\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>\n              <Calendar.DayGridRow value={adapter.startOfWeek(date)}>\n                <Calendar.DayGridCell value={date}>\n                  <Calendar.DayButton />\n                </Calendar.DayGridCell>\n              </Calendar.DayGridRow>\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      expect(button).toHaveAttribute('aria-selected', 'true');\n      expect(button).toHaveAttribute('data-selected');\n    });\n\n    it('should not be selected when the date is in the same day as the reference date', () => {\n      const date = adapter.date('2025-02-04', 'default');\n\n      render(\n        <Calendar.Root\n          visibleDate={adapter.startOfMonth(date)}\n          referenceDate={adapter.date('2025-02-04T12:01:02.003Z', 'default')}\n        >\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>\n              <Calendar.DayGridRow value={adapter.startOfWeek(date)}>\n                <Calendar.DayGridCell value={date}>\n                  <Calendar.DayButton />\n                </Calendar.DayGridCell>\n              </Calendar.DayGridRow>\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      expect(button).not.toHaveAttribute('aria-selected', 'true');\n      expect(button).not.toHaveAttribute('data-selected');\n    });\n\n    it('should update the selected state when the controlled value changes', () => {\n      const dateA = adapter.date('2025-02-04', 'default');\n      const dateB = adapter.date('2025-02-07', 'default');\n\n      const { rerender } = render(\n        <Calendar.Root visibleDate={adapter.startOfMonth(dateA)} value={dateA}>\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>\n              <Calendar.DayGridRow value={adapter.startOfWeek(dateA)}>\n                <Calendar.DayGridCell value={dateA}>\n                  <Calendar.DayButton />\n                </Calendar.DayGridCell>\n                <Calendar.DayGridCell value={dateB}>\n                  <Calendar.DayButton />\n                </Calendar.DayGridCell>\n              </Calendar.DayGridRow>\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const buttons = screen.getAllByRole('button');\n      expect(buttons[0]).toHaveAttribute('aria-selected', 'true');\n      expect(buttons[1]).not.toHaveAttribute('aria-selected', 'true');\n\n      rerender(\n        <Calendar.Root visibleDate={adapter.startOfMonth(dateA)} value={dateB}>\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>\n              <Calendar.DayGridRow value={adapter.startOfWeek(dateA)}>\n                <Calendar.DayGridCell value={dateA}>\n                  <Calendar.DayButton />\n                </Calendar.DayGridCell>\n                <Calendar.DayGridCell value={dateB}>\n                  <Calendar.DayButton />\n                </Calendar.DayGridCell>\n              </Calendar.DayGridRow>\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      expect(buttons[0]).not.toHaveAttribute('aria-selected', 'true');\n      expect(buttons[1]).toHaveAttribute('aria-selected', 'true');\n    });\n  });\n\n  describe('disabled state', () => {\n    it('should have aria-disabled=\"true\" and data-disabled when props.disabled is true', () => {\n      const date = adapter.date('2025-02-04', 'default');\n\n      render(\n        <Calendar.Root visibleDate={adapter.startOfMonth(date)}>\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>\n              <Calendar.DayGridRow value={adapter.startOfWeek(date)}>\n                <Calendar.DayGridCell value={date}>\n                  <Calendar.DayButton disabled />\n                </Calendar.DayGridCell>\n              </Calendar.DayGridRow>\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      expect(button).toHaveAttribute('aria-disabled', 'true');\n      expect(button).toHaveAttribute('data-disabled');\n    });\n\n    it('should have aria-disabled=\"true\" and data-disabled when the date is before the minDate', () => {\n      const date = adapter.date('2025-02-04', 'default');\n\n      render(\n        <Calendar.Root\n          visibleDate={adapter.startOfMonth(date)}\n          minDate={adapter.date('2025-02-10', 'default')}\n        >\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>\n              <Calendar.DayGridRow value={adapter.startOfWeek(date)}>\n                <Calendar.DayGridCell value={date}>\n                  <Calendar.DayButton />\n                </Calendar.DayGridCell>\n              </Calendar.DayGridRow>\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      expect(button).toHaveAttribute('aria-disabled', 'true');\n      expect(button).toHaveAttribute('data-disabled');\n    });\n\n    it('should have aria-disabled=\"true\" and data-disabled when the date is after the maxDate', () => {\n      const date = adapter.date('2025-02-04', 'default');\n\n      render(\n        <Calendar.Root\n          visibleDate={adapter.startOfMonth(date)}\n          maxDate={adapter.date('2025-02-02', 'default')}\n        >\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>\n              <Calendar.DayGridRow value={adapter.startOfWeek(date)}>\n                <Calendar.DayGridCell value={date}>\n                  <Calendar.DayButton />\n                </Calendar.DayGridCell>\n              </Calendar.DayGridRow>\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      expect(button).toHaveAttribute('aria-disabled', 'true');\n      expect(button).toHaveAttribute('data-disabled');\n    });\n\n    it('should not have aria-disabled or data-disabled when the date is not disabled', () => {\n      const date = adapter.date('2025-02-04', 'default');\n\n      render(\n        <Calendar.Root visibleDate={adapter.startOfMonth(date)}>\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>\n              <Calendar.DayGridRow value={adapter.startOfWeek(date)}>\n                <Calendar.DayGridCell value={date}>\n                  <Calendar.DayButton />\n                </Calendar.DayGridCell>\n              </Calendar.DayGridRow>\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      expect(button).not.toHaveAttribute('aria-disabled');\n      expect(button).not.toHaveAttribute('data-disabled');\n    });\n  });\n\n  describe('current state', () => {\n    it('should have aria-current=\"date\" and data-current when the date is today', () => {\n      const date = adapter.now('default');\n\n      render(\n        <Calendar.Root visibleDate={adapter.startOfMonth(date)}>\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>\n              <Calendar.DayGridRow value={adapter.startOfWeek(date)}>\n                <Calendar.DayGridCell value={date}>\n                  <Calendar.DayButton />\n                </Calendar.DayGridCell>\n              </Calendar.DayGridRow>\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      expect(button).toHaveAttribute('aria-current', 'date');\n      expect(button).toHaveAttribute('data-current');\n    });\n\n    it('should not have aria-current or data-current when the date is not today', () => {\n      const date = adapter.date('2025-02-04', 'default');\n\n      render(\n        <Calendar.Root visibleDate={adapter.startOfMonth(date)}>\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>\n              <Calendar.DayGridRow value={adapter.startOfWeek(date)}>\n                <Calendar.DayGridCell value={date}>\n                  <Calendar.DayButton />\n                </Calendar.DayGridCell>\n              </Calendar.DayGridRow>\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      expect(button).not.toHaveAttribute('aria-current');\n      expect(button).not.toHaveAttribute('data-current');\n    });\n  });\n\n  describe('outside month state', () => {\n    it('should not have data-outside-month attribute when the date is inside the current month', () => {\n      const date = adapter.date('2025-01-31', 'default');\n\n      render(\n        <Calendar.Root visibleDate={adapter.startOfMonth(date)}>\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>\n              <Calendar.DayGridRow value={adapter.startOfWeek(date)}>\n                <Calendar.DayGridCell value={date}>\n                  <Calendar.DayButton />\n                </Calendar.DayGridCell>\n              </Calendar.DayGridRow>\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const button = document.querySelector('button');\n      expect(button).not.toHaveAttribute('data-outside-month');\n    });\n\n    it('should have data-outside-month attribute and aria-disabled when the date is outside the current month', () => {\n      const date = adapter.date('2025-01-31', 'default');\n\n      render(\n        <Calendar.Root visibleDate={adapter.addMonths(adapter.startOfMonth(date), 1)}>\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>\n              <Calendar.DayGridRow value={adapter.startOfWeek(date)}>\n                <Calendar.DayGridCell value={date}>\n                  <Calendar.DayButton />\n                </Calendar.DayGridCell>\n              </Calendar.DayGridRow>\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      expect(button).toHaveAttribute('data-outside-month');\n      expect(button).toHaveAttribute('aria-disabled', 'true');\n    });\n\n    it('should not call `onValueChange` or `onClick` on a day when an outside-month day is clicked', async () => {\n      const onValueChange = vi.fn();\n      const onButtonClick = vi.fn();\n      const date = adapter.date('2025-01-31', 'default');\n\n      const { user } = render(\n        <Calendar.Root\n          visibleDate={adapter.addMonths(adapter.startOfMonth(date), 1)}\n          onValueChange={onValueChange}\n        >\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>\n              <Calendar.DayGridRow value={adapter.startOfWeek(date)}>\n                <Calendar.DayGridCell value={date}>\n                  <Calendar.DayButton onClick={onButtonClick} />\n                </Calendar.DayGridCell>\n              </Calendar.DayGridRow>\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      await user.click(button);\n      expect(onValueChange.mock.calls.length).toBe(0);\n      expect(onButtonClick.mock.calls.length).toBe(0);\n    });\n  });\n\n  describe('unavailable state', () => {\n    it('should not have data-unavailable or aria-disabled when the date is available', () => {\n      const date = adapter.date('2025-02-04', 'default');\n\n      render(\n        <Calendar.Root visibleDate={adapter.startOfMonth(date)} isDateUnavailable={() => false}>\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>\n              <Calendar.DayGridRow value={adapter.startOfWeek(date)}>\n                <Calendar.DayGridCell value={date}>\n                  <Calendar.DayButton />\n                </Calendar.DayGridCell>\n              </Calendar.DayGridRow>\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      expect(button).not.toHaveAttribute('data-unavailable');\n      expect(button).not.toHaveAttribute('aria-disabled');\n    });\n\n    it('should have data-unavailable and aria-disabled when the date is unavailable', () => {\n      const date = adapter.date('2025-02-04', 'default');\n\n      render(\n        <Calendar.Root visibleDate={adapter.startOfMonth(date)} isDateUnavailable={() => true}>\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>\n              <Calendar.DayGridRow value={adapter.startOfWeek(date)}>\n                <Calendar.DayGridCell value={date}>\n                  <Calendar.DayButton />\n                </Calendar.DayGridCell>\n              </Calendar.DayGridRow>\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      expect(button).toHaveAttribute('data-unavailable');\n      expect(button).toHaveAttribute('aria-disabled', 'true');\n    });\n  });\n\n  describe('format prop', () => {\n    it('should use the non-padded day of month format by default', () => {\n      const date = adapter.date('2025-02-04', 'default');\n\n      render(\n        <Calendar.Root visibleDate={adapter.startOfMonth(date)}>\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>\n              <Calendar.DayGridRow value={adapter.startOfWeek(date)}>\n                <Calendar.DayGridCell value={date}>\n                  <Calendar.DayButton />\n                </Calendar.DayGridCell>\n              </Calendar.DayGridRow>\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      expect(button.textContent).toBe('4');\n    });\n\n    it('should use the provided format', () => {\n      const date = adapter.date('2025-02-04', 'default');\n\n      render(\n        <Calendar.Root visibleDate={adapter.startOfMonth(date)}>\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>\n              <Calendar.DayGridRow value={adapter.startOfWeek(date)}>\n                <Calendar.DayGridCell value={date}>\n                  <Calendar.DayButton format={adapter.formats.dayOfMonthPadded} />\n                </Calendar.DayGridCell>\n              </Calendar.DayGridRow>\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      expect(button.textContent).toBe('04');\n    });\n  });\n\n  describe('accessibility attributes', () => {\n    it('should have the full date format as aria-label', () => {\n      const date = adapter.date('2025-02-04', 'default');\n\n      render(\n        <Calendar.Root visibleDate={adapter.startOfMonth(date)}>\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>\n              <Calendar.DayGridRow value={adapter.startOfWeek(date)}>\n                <Calendar.DayGridCell value={date}>\n                  <Calendar.DayButton format={adapter.formats.dayOfMonthPadded} />\n                </Calendar.DayGridCell>\n              </Calendar.DayGridRow>\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      expect(button).toHaveAccessibleName(\n        adapter.format(date, 'localizedDateWithFullMonthAndWeekDay'),\n      );\n    });\n  });\n\n  describe('tabIndex', () => {\n    it('should have tabIndex={0} when the date is the first day of the month that contains no selected date or reference date', () => {\n      const date = adapter.date('2025-02-01', 'default');\n\n      render(\n        <Calendar.Root visibleDate={adapter.startOfMonth(date)}>\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>\n              <Calendar.DayGridRow value={adapter.startOfWeek(date)}>\n                <Calendar.DayGridCell value={date}>\n                  <Calendar.DayButton />\n                </Calendar.DayGridCell>\n              </Calendar.DayGridRow>\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      expect(button).toHaveAttribute('tabindex', '0');\n    });\n\n    it('should have tabIndex={-1} when the date is not the first day of the month that contains no selected date or reference date', () => {\n      const date = adapter.date('2025-02-04', 'default');\n\n      render(\n        <Calendar.Root visibleDate={adapter.startOfMonth(date)}>\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>\n              <Calendar.DayGridRow value={adapter.startOfWeek(date)}>\n                <Calendar.DayGridCell value={date}>\n                  <Calendar.DayButton />\n                </Calendar.DayGridCell>\n              </Calendar.DayGridRow>\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      expect(button).toHaveAttribute('tabindex', '-1');\n    });\n\n    it('should have tabIndex={0} when the date is selected', () => {\n      const date = adapter.date('2025-02-04', 'default');\n\n      render(\n        <Calendar.Root visibleDate={adapter.startOfMonth(date)} value={date}>\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>\n              <Calendar.DayGridRow value={adapter.startOfWeek(date)}>\n                <Calendar.DayGridCell value={date}>\n                  <Calendar.DayButton />\n                </Calendar.DayGridCell>\n              </Calendar.DayGridRow>\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      expect(button).toHaveAttribute('tabindex', '0');\n    });\n\n    it('should have tabIndex={0} when the date is the reference date and no date is selected', () => {\n      const date = adapter.date('2025-02-04', 'default');\n\n      render(\n        <Calendar.Root visibleDate={adapter.startOfMonth(date)} referenceDate={date}>\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>\n              <Calendar.DayGridRow value={adapter.startOfWeek(date)}>\n                <Calendar.DayGridCell value={date}>\n                  <Calendar.DayButton />\n                </Calendar.DayGridCell>\n              </Calendar.DayGridRow>\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      expect(button).toHaveAttribute('tabindex', '0');\n    });\n\n    it('should have tabIndex={-1} when the date is the reference date but a date is selected in the same month', () => {\n      const date = adapter.date('2025-02-04', 'default');\n      const selectedDate = adapter.date('2025-02-10', 'default');\n\n      render(\n        <Calendar.Root\n          visibleDate={adapter.startOfMonth(date)}\n          referenceDate={date}\n          value={selectedDate}\n        >\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>\n              <Calendar.DayGridRow value={adapter.startOfWeek(date)}>\n                <Calendar.DayGridCell value={date}>\n                  <Calendar.DayButton />\n                </Calendar.DayGridCell>\n              </Calendar.DayGridRow>\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      expect(button).toHaveAttribute('tabindex', '-1');\n    });\n\n    it('should have tabIndex={-1} when the date is outside the current month event if its the first day of the month', () => {\n      const date = adapter.date('2025-01-01', 'default');\n\n      render(\n        <Calendar.Root visibleDate={adapter.addMonths(adapter.startOfMonth(date), -1)}>\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>\n              <Calendar.DayGridRow value={adapter.startOfWeek(date)}>\n                <Calendar.DayGridCell value={date}>\n                  <Calendar.DayButton />\n                </Calendar.DayGridCell>\n              </Calendar.DayGridRow>\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      expect(button).toHaveAttribute('tabindex', '-1');\n    });\n\n    it('should have tabIndex={-1} when the date is outside the current month even if it is the selected value', () => {\n      const date = adapter.date('2025-01-31', 'default');\n\n      render(\n        <Calendar.Root visibleDate={adapter.addMonths(adapter.startOfMonth(date), 1)} value={date}>\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>\n              <Calendar.DayGridRow value={adapter.startOfWeek(date)}>\n                <Calendar.DayGridCell value={date}>\n                  <Calendar.DayButton />\n                </Calendar.DayGridCell>\n              </Calendar.DayGridRow>\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      expect(button).toHaveAttribute('tabindex', '-1');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/calendar/day-button/CalendarDayButton.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStore } from '@base-ui/utils/store';\nimport { BaseUIComponentProps, NativeButtonProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { CalendarDayButtonDataAttributes } from './CalendarDayButtonDataAttributes';\nimport { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport { useButton } from '../../use-button';\nimport { useSharedCalendarRootContext } from '../root/SharedCalendarRootContext';\nimport { useSharedCalendarDayGridBodyContext } from '../day-grid-body/SharedCalendarDayGridBodyContext';\nimport { useTemporalAdapter } from '../../temporal-adapter-provider/TemporalAdapterContext';\nimport { useCalendarDayGridCellContext } from '../day-grid-cell/SharedCalendarDayGridCellContext';\nimport { CompositeItem } from '../../composite/item/CompositeItem';\nimport { selectors } from '../store';\nimport { UseSharedCalendarDayGridBodyItemMetadata } from '../day-grid-body/useSharedCalendarDayGridBody';\n\nconst stateAttributesMapping: StateAttributesMapping<CalendarDayButton.State> = {\n  selected(value) {\n    return value ? { [CalendarDayButtonDataAttributes.selected]: '' } : null;\n  },\n  disabled(value) {\n    return value ? { [CalendarDayButtonDataAttributes.disabled]: '' } : null;\n  },\n  unavailable(value) {\n    return value ? { [CalendarDayButtonDataAttributes.unavailable]: '' } : null;\n  },\n  current(value) {\n    return value ? { [CalendarDayButtonDataAttributes.current]: '' } : null;\n  },\n  outsideMonth(value) {\n    return value ? { [CalendarDayButtonDataAttributes.outsideMonth]: '' } : null;\n  },\n};\n\nconst InnerCalendarDayButton = React.forwardRef(function InnerCalendarDayButton(\n  componentProps: CalendarDayButton.Props,\n  forwardedRef: React.ForwardedRef<HTMLButtonElement>,\n) {\n  const adapter = useTemporalAdapter();\n\n  const {\n    className,\n    render,\n    nativeButton,\n    format = adapter.formats.dayOfMonth,\n    disabled,\n    focusableWhenDisabled = false,\n    ...elementProps\n  } = componentProps;\n\n  const store = useSharedCalendarRootContext();\n  const { month, today } = useSharedCalendarDayGridBodyContext();\n  const {\n    isDisabled: isCellDisabled,\n    isUnavailable,\n    isOutsideCurrentMonth,\n    value,\n  } = useCalendarDayGridCellContext();\n  const isSelected = useStore(store, selectors.isDayButtonSelected, value);\n  const isTabbable = useStore(store, selectors.isDayButtonTabbable, value, month);\n\n  const isCurrent = adapter.isSameDay(value, today);\n\n  const formattedDate = React.useMemo(\n    () => adapter.format(value, 'localizedDateWithFullMonthAndWeekDay'),\n    [adapter, value],\n  );\n\n  const isDisabled = disabled ?? isCellDisabled;\n  const isInteractionDisabled = isDisabled || isOutsideCurrentMonth;\n\n  const { getButtonProps, buttonRef } = useButton({\n    disabled: isInteractionDisabled,\n    native: nativeButton,\n    focusableWhenDisabled,\n  });\n\n  const formattedValue = React.useMemo(\n    () => adapter.formatByString(value, format),\n    [adapter, value, format],\n  );\n\n  const itemMetadata = React.useMemo<UseSharedCalendarDayGridBodyItemMetadata>(\n    () => ({\n      focusable: (focusableWhenDisabled || !isDisabled) && !isOutsideCurrentMonth,\n    }),\n    [focusableWhenDisabled, isDisabled, isOutsideCurrentMonth],\n  );\n\n  const props: React.ButtonHTMLAttributes<HTMLButtonElement> = {\n    'aria-label': formattedDate,\n    'aria-selected': isSelected ? true : undefined,\n    'aria-current': isCurrent ? 'date' : undefined,\n    'aria-disabled': isDisabled || isOutsideCurrentMonth || isUnavailable ? true : undefined,\n    children: formattedValue,\n    tabIndex: isTabbable ? 0 : -1,\n    onClick(event) {\n      if (isUnavailable || isInteractionDisabled) {\n        return;\n      }\n      store.selectDate(value, event);\n    },\n  };\n\n  const state: CalendarDayButton.State = React.useMemo(\n    () => ({\n      selected: isSelected,\n      disabled: isDisabled,\n      unavailable: isUnavailable,\n      current: isCurrent,\n      outsideMonth: isOutsideCurrentMonth,\n    }),\n    [isSelected, isDisabled, isUnavailable, isCurrent, isOutsideCurrentMonth],\n  );\n\n  const element = useRenderElement('button', componentProps, {\n    state,\n    ref: [buttonRef, forwardedRef],\n    props: [props, elementProps, getButtonProps],\n    stateAttributesMapping,\n  });\n\n  return <CompositeItem metadata={itemMetadata} render={element} />;\n});\n\n/**\n * An individual interactive day button in the calendar.\n * Renders a `<button>` element.\n *\n * Documentation: [Base UI Calendar](https://base-ui.com/react/components/calendar)\n */\nexport const CalendarDayButton = React.memo(InnerCalendarDayButton);\n\nexport interface CalendarDayButtonState {\n  /**\n   * Whether the day is selected.\n   */\n  selected: boolean;\n  /**\n   * Whether the day is disabled.\n   */\n  disabled: boolean;\n  /**\n   * Whether the day is not available.\n   */\n  unavailable: boolean;\n  /**\n   * Whether the day contains the current date.\n   */\n  current: boolean;\n  /**\n   * Whether the day is outside the month rendered by the day grid wrapping it.\n   */\n  outsideMonth: boolean;\n}\n\nexport interface CalendarDayButtonProps\n  extends Omit<BaseUIComponentProps<'button', CalendarDayButtonState>, 'value'>, NativeButtonProps {\n  /**\n   * The format used to display the day.\n   * @default adapter.formats.dayOfMonth\n   */\n  format?: string | undefined;\n  /**\n   * When `true` the item remains focusable when disabled.\n   * @default false\n   */\n  focusableWhenDisabled?: boolean | undefined;\n}\n\nexport namespace CalendarDayButton {\n  export type State = CalendarDayButtonState;\n  export type Props = CalendarDayButtonProps;\n}\n"
  },
  {
    "path": "packages/react/src/calendar/day-button/CalendarDayButtonDataAttributes.ts",
    "content": "export enum CalendarDayButtonDataAttributes {\n  /**\n   * Present when the day is selected.\n   */\n  selected = 'data-selected',\n  /**\n   * Present when the day is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the day is unavailable.\n   */\n  unavailable = 'data-unavailable',\n  /**\n   * Present when the day is the current date.\n   */\n  current = 'data-current',\n  /**\n   * Present when the day is outside the month rendered by the day grid wrapping it.\n   */\n  outsideMonth = 'data-outside-month',\n}\n"
  },
  {
    "path": "packages/react/src/calendar/day-grid/CalendarDayGrid.test.tsx",
    "content": "import * as React from 'react';\nimport { Calendar } from '@base-ui/react/calendar';\nimport { createTemporalRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Calendar.DayGrid />', () => {\n  const { render } = createTemporalRenderer();\n\n  describeConformance(<Calendar.DayGrid />, () => ({\n    refInstanceof: window.HTMLTableElement,\n    render(node) {\n      return render(<Calendar.Root>{node}</Calendar.Root>);\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/calendar/day-grid/CalendarDayGrid.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\n\n/**\n * Groups all the parts of the calendar's day grid.\n * Renders a `<table>` element.\n *\n * Documentation: [Base UI Calendar](https://base-ui.com/react/components/calendar)\n */\nexport const CalendarDayGrid = React.forwardRef(function CalendarDayGrid(\n  componentProps: CalendarDayGrid.Props,\n  forwardedRef: React.ForwardedRef<HTMLTableElement>,\n) {\n  const { className, render, ...elementProps } = componentProps;\n\n  const element = useRenderElement('table', componentProps, {\n    ref: forwardedRef,\n    props: [{ role: 'grid' }, elementProps],\n  });\n\n  return element;\n});\n\nexport interface CalendarDayGridState {}\n\nexport interface CalendarDayGridProps extends BaseUIComponentProps<'table', CalendarDayGridState> {}\n\nexport namespace CalendarDayGrid {\n  export type State = CalendarDayGridState;\n  export type Props = CalendarDayGridProps;\n}\n"
  },
  {
    "path": "packages/react/src/calendar/day-grid-body/CalendarDayGridBody.keyboard.test.tsx",
    "content": "import * as React from 'react';\nimport { vi } from 'vitest';\nimport { Calendar } from '@base-ui/react/calendar';\nimport type { CalendarRoot } from '@base-ui/react/calendar';\nimport { act, screen } from '@mui/internal-test-utils';\nimport { createTemporalRenderer } from '#test-utils';\n\ndescribe('<Calendar.DayGridBody /> - keyboard navigation', () => {\n  const { render, adapter } = createTemporalRenderer();\n\n  function renderCalendar(\n    defaultDate: ReturnType<ReturnType<typeof createTemporalRenderer>['adapter']['date']>,\n    options?: {\n      minDate?: ReturnType<ReturnType<typeof createTemporalRenderer>['adapter']['date']>;\n      maxDate?: ReturnType<ReturnType<typeof createTemporalRenderer>['adapter']['date']>;\n      onVisibleDateChange?: CalendarRoot.Props['onVisibleDateChange'];\n      onValueChange?: CalendarRoot.Props['onValueChange'];\n      focusableWhenDisabled?: boolean;\n    },\n  ) {\n    return render(\n      <Calendar.Root\n        defaultVisibleDate={defaultDate}\n        onVisibleDateChange={options?.onVisibleDateChange}\n        onValueChange={options?.onValueChange}\n        minDate={options?.minDate}\n        maxDate={options?.maxDate}\n      >\n        <Calendar.DayGrid>\n          <Calendar.DayGridBody>\n            {(week) => (\n              <Calendar.DayGridRow value={week} key={week.getTime()}>\n                {(day) => (\n                  <Calendar.DayGridCell value={day} key={day.getTime()}>\n                    <Calendar.DayButton focusableWhenDisabled={options?.focusableWhenDisabled} />\n                  </Calendar.DayGridCell>\n                )}\n              </Calendar.DayGridRow>\n            )}\n          </Calendar.DayGridBody>\n        </Calendar.DayGrid>\n      </Calendar.Root>,\n    );\n  }\n\n  /** Queries a day button by its full accessible name (e.g. \"Saturday, February 15, 2025\"). */\n  function getDayButton(\n    date: ReturnType<ReturnType<typeof createTemporalRenderer>['adapter']['date']>,\n  ) {\n    return screen.getByRole('button', {\n      name: adapter.format(date, 'localizedDateWithFullMonthAndWeekDay'),\n    });\n  }\n\n  // Common date constants\n  const feb1 = adapter.date('2025-02-01', 'default');\n  const feb8 = adapter.date('2025-02-08', 'default');\n  const feb9 = adapter.date('2025-02-09', 'default');\n  const feb14 = adapter.date('2025-02-14', 'default');\n  const feb15 = adapter.date('2025-02-15', 'default');\n  const feb16 = adapter.date('2025-02-16', 'default');\n  const feb28 = adapter.date('2025-02-28', 'default');\n  const mar31 = adapter.date('2025-03-31', 'default');\n  const apr7 = adapter.date('2025-04-07', 'default');\n  const jul25 = adapter.date('2021-07-25', 'default');\n  const jul31 = adapter.date('2021-07-31', 'default');\n  const aug1 = adapter.date('2021-08-01', 'default');\n  const aug7 = adapter.date('2021-08-07', 'default');\n\n  // ---------------------------------------------------------------------------\n  // PageDown / PageUp\n  // ---------------------------------------------------------------------------\n\n  describe('PageDown', () => {\n    it('should move focus to the same day in the next month when pressing PageDown', async () => {\n      const onVisibleDateChange = vi.fn();\n\n      const { user } = renderCalendar(feb15, { onVisibleDateChange });\n\n      await act(async () => {\n        getDayButton(feb15).focus();\n      });\n      await user.keyboard('[PageDown]');\n\n      expect(onVisibleDateChange.mock.calls.length).toBe(1);\n      expect(onVisibleDateChange.mock.calls[0][1].reason).toBe('keyboard');\n      const newDate = adapter.addMonths(feb15, 1);\n      expect(onVisibleDateChange.mock.calls[0][0]).toEqual(adapter.startOfMonth(newDate));\n      expect(getDayButton(newDate)).toHaveFocus();\n    });\n\n    it('should find the nearest day to focus in the next month when same day does not exist', async () => {\n      const jan31 = adapter.date('2025-01-31', 'default');\n      const onVisibleDateChange = vi.fn();\n\n      const { user } = renderCalendar(jan31, { onVisibleDateChange });\n\n      await act(async () => {\n        getDayButton(jan31).focus();\n      });\n      await user.keyboard('[PageDown]');\n\n      expect(onVisibleDateChange.mock.calls.length).toBe(1);\n      expect(onVisibleDateChange.mock.calls[0][1].reason).toBe('keyboard');\n      const newDate = adapter.addMonths(jan31, 1);\n      expect(onVisibleDateChange.mock.calls[0][0]).toEqual(adapter.startOfMonth(newDate));\n      expect(getDayButton(newDate)).toHaveFocus();\n    });\n  });\n\n  describe('PageUp', () => {\n    it('should move focus to the same day in the previous month when pressing PageUp', async () => {\n      const onVisibleDateChange = vi.fn();\n\n      const { user } = renderCalendar(feb15, { onVisibleDateChange });\n\n      await act(async () => {\n        getDayButton(feb15).focus();\n      });\n      await user.keyboard('[PageUp]');\n\n      expect(onVisibleDateChange.mock.calls.length).toBe(1);\n      expect(onVisibleDateChange.mock.calls[0][1].reason).toBe('keyboard');\n      const newDate = adapter.addMonths(feb15, -1);\n      expect(onVisibleDateChange.mock.calls[0][0]).toEqual(adapter.startOfMonth(newDate));\n      expect(getDayButton(newDate)).toHaveFocus();\n    });\n\n    it('should find the nearest day to focus in the previous month when same day does not exist', async () => {\n      const onVisibleDateChange = vi.fn();\n\n      const { user } = renderCalendar(mar31, { onVisibleDateChange });\n\n      await act(async () => {\n        getDayButton(mar31).focus();\n      });\n      await user.keyboard('[PageUp]');\n\n      expect(onVisibleDateChange.mock.calls.length).toBe(1);\n      expect(onVisibleDateChange.mock.calls[0][1].reason).toBe('keyboard');\n      const newDate = adapter.addMonths(mar31, -1);\n      expect(onVisibleDateChange.mock.calls[0][0]).toEqual(adapter.startOfMonth(newDate));\n      expect(getDayButton(newDate)).toHaveFocus();\n    });\n  });\n\n  describe('Shift+PageDown', () => {\n    it('should move focus to the same day 12 months forward when pressing Shift+PageDown', async () => {\n      const onVisibleDateChange = vi.fn();\n\n      const { user } = renderCalendar(feb15, { onVisibleDateChange });\n\n      await act(async () => {\n        getDayButton(feb15).focus();\n      });\n      await user.keyboard('[ShiftLeft>][PageDown][/ShiftLeft]');\n\n      expect(onVisibleDateChange.mock.calls.length).toBe(1);\n      expect(onVisibleDateChange.mock.calls[0][1].reason).toBe('keyboard');\n      const newDate = adapter.addMonths(feb15, 12);\n      expect(onVisibleDateChange.mock.calls[0][0]).toEqual(adapter.startOfMonth(newDate));\n      expect(getDayButton(newDate)).toHaveFocus();\n    });\n  });\n\n  describe('Shift+PageUp', () => {\n    it('should move focus to the same day 12 months backward when pressing Shift+PageUp', async () => {\n      const onVisibleDateChange = vi.fn();\n\n      const { user } = renderCalendar(feb15, { onVisibleDateChange });\n\n      await act(async () => {\n        getDayButton(feb15).focus();\n      });\n      await user.keyboard('[ShiftLeft>][PageUp][/ShiftLeft]');\n\n      expect(onVisibleDateChange.mock.calls.length).toBe(1);\n      expect(onVisibleDateChange.mock.calls[0][1].reason).toBe('keyboard');\n      const newDate = adapter.addMonths(feb15, -12);\n      expect(onVisibleDateChange.mock.calls[0][0]).toEqual(adapter.startOfMonth(newDate));\n      expect(getDayButton(newDate)).toHaveFocus();\n    });\n  });\n\n  // ---------------------------------------------------------------------------\n  // Arrow navigation - within the same month\n  //\n  // February 2025 grid (en-US locale, weeks start Sunday):\n  //   Week 1: Jan 26(disabled) … Feb 1(Sat, index 6)\n  //   Week 2: Feb 2(Sun) … Feb 8(Sat, index 13)\n  //   Week 3: Feb 9(Sun) … Feb 15(Sat, index 20)\n  //   Week 4: Feb 16(Sun) … Feb 22(Sat, index 27)\n  //   Week 5: Feb 23(Sun) … Feb 28(Fri, index 33), Mar 1(disabled, index 34)\n  // ---------------------------------------------------------------------------\n\n  describe('ArrowRight', () => {\n    it('should move focus to the next day', async () => {\n      const { user } = renderCalendar(adapter.startOfMonth(feb14));\n\n      await act(async () => {\n        getDayButton(feb14).focus();\n      });\n      await user.keyboard('{ArrowRight}');\n\n      expect(getDayButton(feb15)).toHaveFocus();\n    });\n\n    // July 2021 ends on Saturday: Jul 31 is the very last cell of the 5-week\n    // grid (index 34). There is no trailing outside-month day, so the onLoop\n    // guard does not block navigation and the month wraps to August 2021.\n    // handleItemLooping(ArrowRight, 34) → newHighlightedIndex = 0\n    // August 2021 starts on Sunday → index 0 = Aug 1 (in-month, enabled).\n    it('should wrap to the next month and focus the first day when on the last day', async () => {\n      const onVisibleDateChange = vi.fn();\n\n      const { user } = renderCalendar(adapter.startOfMonth(jul31), { onVisibleDateChange });\n\n      await act(async () => {\n        getDayButton(jul31).focus();\n      });\n      await user.keyboard('{ArrowRight}');\n\n      expect(onVisibleDateChange.mock.calls.length).toBe(1);\n      expect(onVisibleDateChange.mock.calls[0][1].reason).toBe('keyboard');\n      expect(onVisibleDateChange.mock.calls[0][0]).toEqual(adapter.startOfMonth(aug1));\n      expect(getDayButton(aug1)).toHaveFocus();\n    });\n  });\n\n  describe('ArrowLeft', () => {\n    it('should move focus to the previous day', async () => {\n      const { user } = renderCalendar(adapter.startOfMonth(feb15));\n\n      await act(async () => {\n        getDayButton(feb15).focus();\n      });\n      await user.keyboard('{ArrowLeft}');\n\n      expect(getDayButton(feb14)).toHaveFocus();\n    });\n\n    // August 2021 starts on Sunday: Aug 1 is the very first cell of the grid\n    // (index 0). There is no leading outside-month day, so the onLoop guard\n    // does not block navigation and the month wraps to July 2021.\n    // handleItemLooping(ArrowLeft, 0) → newHighlightedIndex = 5×7-1 = 34\n    // July 2021: index 34 = Jul 31 (Saturday, in-month, enabled).\n    it('should wrap to the previous month and focus the last day when on the first day', async () => {\n      const onVisibleDateChange = vi.fn();\n\n      const { user } = renderCalendar(adapter.startOfMonth(aug1), { onVisibleDateChange });\n\n      await act(async () => {\n        getDayButton(aug1).focus();\n      });\n      await user.keyboard('{ArrowLeft}');\n\n      expect(onVisibleDateChange.mock.calls.length).toBe(1);\n      expect(onVisibleDateChange.mock.calls[0][1].reason).toBe('keyboard');\n      expect(onVisibleDateChange.mock.calls[0][0]).toEqual(adapter.startOfMonth(jul31));\n      expect(getDayButton(jul31)).toHaveFocus();\n    });\n  });\n\n  describe('ArrowDown', () => {\n    it('should move focus down one week to the same weekday', async () => {\n      const { user } = renderCalendar(adapter.startOfMonth(feb8));\n\n      await act(async () => {\n        getDayButton(feb8).focus();\n      });\n      await user.keyboard('{ArrowDown}');\n\n      expect(getDayButton(feb15)).toHaveFocus();\n    });\n\n    // Jul 31 (Sat, index 34) is in the last row of July 2021's 5-week grid.\n    // elementsRef.current[34 + 7] is undefined (no row below), so the onLoop\n    // guard does not block navigation.\n    // handleItemLooping(ArrowDown, 34) → newHighlightedIndex = 34 % 7 = 6\n    // August 2021: index 6 = Aug 7 (Saturday, in-month, enabled).\n    it('should wrap to the next month and focus the same weekday when on the last week', async () => {\n      const onVisibleDateChange = vi.fn();\n\n      const { user } = renderCalendar(adapter.startOfMonth(jul31), { onVisibleDateChange });\n\n      await act(async () => {\n        getDayButton(jul31).focus();\n      });\n      await user.keyboard('{ArrowDown}');\n\n      expect(onVisibleDateChange.mock.calls.length).toBe(1);\n      expect(onVisibleDateChange.mock.calls[0][1].reason).toBe('keyboard');\n      expect(onVisibleDateChange.mock.calls[0][0]).toEqual(adapter.startOfMonth(aug7));\n      expect(getDayButton(aug7)).toHaveFocus();\n    });\n\n    // Mar 31, 2025 (Mon) is at index 36 in March 2025's 6-week grid.\n    // handleItemLooping(ArrowDown, 36) → newHighlightedIndex = 36 % 7 = 1\n    // April 2025: index 1 = Mar 31 (outside-month, disabled), so the algorithm\n    // skips by +7 to index 8, which is Apr 7 (Monday, in-month, enabled).\n    it('should skip disabled previous-month day in the next month when wrapping down', async () => {\n      const onVisibleDateChange = vi.fn();\n\n      const { user } = renderCalendar(adapter.startOfMonth(mar31), { onVisibleDateChange });\n\n      await act(async () => {\n        getDayButton(mar31).focus();\n      });\n      await user.keyboard('{ArrowDown}');\n\n      expect(onVisibleDateChange.mock.calls.length).toBe(1);\n      expect(onVisibleDateChange.mock.calls[0][1].reason).toBe('keyboard');\n      expect(onVisibleDateChange.mock.calls[0][0]).toEqual(adapter.startOfMonth(apr7));\n      expect(getDayButton(apr7)).toHaveFocus();\n    });\n  });\n\n  // ---------------------------------------------------------------------------\n  // Home / End\n  //\n  // February 2025 grid (en-US locale, weeks start Sunday):\n  //   Week 1: Jan 26(disabled, idx 0) … Jan 31(disabled, idx 5), Feb 1(Sat, idx 6)\n  //   Week 2: Feb 2(Sun, idx 7)  … Feb 8(Sat, idx 13)\n  //   Week 3: Feb 9(Sun, idx 14) … Feb 14(Fri, idx 19), Feb 15(Sat, idx 20)\n  //   Week 4: Feb 16(Sun, idx 21) … Feb 22(Sat, idx 27)\n  //   Week 5: Feb 23(Sun, idx 28) … Feb 28(Fri, idx 33), Mar 1(disabled, idx 34)\n  // ---------------------------------------------------------------------------\n\n  describe('Home', () => {\n    it('should move focus to the first day of the week when pressing Home from a mid-week day', async () => {\n      const { user } = renderCalendar(adapter.startOfMonth(feb14));\n\n      await act(async () => {\n        getDayButton(feb14).focus();\n      });\n      await user.keyboard('{Home}');\n\n      expect(getDayButton(feb9)).toHaveFocus();\n\n      await user.keyboard('{Home}');\n\n      expect(getDayButton(feb1)).toHaveFocus();\n    });\n\n    it('should move focus to the first day of the month when pressing Home from the first day of the week', async () => {\n      const { user } = renderCalendar(adapter.startOfMonth(feb9));\n\n      await act(async () => {\n        getDayButton(feb9).focus();\n      });\n      await user.keyboard('{Home}');\n\n      expect(getDayButton(feb1)).toHaveFocus();\n    });\n  });\n\n  describe('End', () => {\n    it('should move focus to the last day of the week when pressing End from a mid-week day', async () => {\n      const { user } = renderCalendar(adapter.startOfMonth(feb14));\n\n      await act(async () => {\n        getDayButton(feb14).focus();\n      });\n      await user.keyboard('{End}');\n\n      expect(getDayButton(feb15)).toHaveFocus();\n\n      await user.keyboard('{End}');\n\n      expect(getDayButton(feb28)).toHaveFocus();\n    });\n\n    it('should move focus to the last day of the month when pressing End from the last day of the week', async () => {\n      const { user } = renderCalendar(adapter.startOfMonth(feb15));\n\n      await act(async () => {\n        getDayButton(feb15).focus();\n      });\n      await user.keyboard('{End}');\n\n      expect(getDayButton(feb28)).toHaveFocus();\n    });\n  });\n\n  describe('ArrowUp', () => {\n    it('should move focus up one week to the same weekday', async () => {\n      const { user } = renderCalendar(adapter.startOfMonth(feb15));\n\n      await act(async () => {\n        getDayButton(feb15).focus();\n      });\n      await user.keyboard('{ArrowUp}');\n\n      expect(getDayButton(feb8)).toHaveFocus();\n    });\n\n    // Aug 1 (Sun, index 0) is in the first row of August 2021's 5-week grid.\n    // elementsRef.current[0 - 7] is undefined (no row above), so the onLoop\n    // guard does not block navigation.\n    // handleItemLooping(ArrowUp, 0) → newHighlightedIndex = 5×7-(7-0) = 28\n    // July 2021: index 28 = Jul 25 (Sunday, in-month, enabled).\n    it('should wrap to the previous month and focus the same weekday when on the first week', async () => {\n      const onVisibleDateChange = vi.fn();\n\n      const { user } = renderCalendar(adapter.startOfMonth(aug1), { onVisibleDateChange });\n\n      await act(async () => {\n        getDayButton(aug1).focus();\n      });\n      await user.keyboard('{ArrowUp}');\n\n      expect(onVisibleDateChange.mock.calls.length).toBe(1);\n      expect(onVisibleDateChange.mock.calls[0][1].reason).toBe('keyboard');\n      expect(onVisibleDateChange.mock.calls[0][0]).toEqual(adapter.startOfMonth(jul25));\n      expect(getDayButton(jul25)).toHaveFocus();\n    });\n\n    // Apr 7 (Mon, index 8 in a Sun-first 6-week April grid) is in the second row.\n    // ArrowUp targets index 1 (Mar 31, outside-month, disabled) → onLoop fires.\n    // onLoop: setVisibleDate(March) + executeAfterItemMapUpdate\n    // March grid: guessedIndex = 42 - 7 + (8 % 7) = 36 = Mar 31 (Mon, in-month, enabled).\n    it('should change to previous month and focus same weekday when navigating up from the second week', async () => {\n      const onVisibleDateChange = vi.fn();\n\n      const { user } = renderCalendar(adapter.startOfMonth(apr7), { onVisibleDateChange });\n\n      await act(async () => {\n        getDayButton(apr7).focus();\n      });\n      await user.keyboard('{ArrowUp}');\n\n      expect(onVisibleDateChange.mock.calls.length).toBe(1);\n      expect(onVisibleDateChange.mock.calls[0][1].reason).toBe('keyboard');\n      expect(onVisibleDateChange.mock.calls[0][0]).toEqual(adapter.startOfMonth(mar31));\n      expect(getDayButton(mar31)).toHaveFocus();\n    });\n  });\n\n  // ---------------------------------------------------------------------------\n  // Keyboard navigation should NOT trigger when the target day is disabled\n  // (outside minDate/maxDate bounds).\n  // ---------------------------------------------------------------------------\n\n  // ---------------------------------------------------------------------------\n  // focusableWhenDisabled\n  //\n  // February 2025 grid (en-US locale, weeks start Sunday), maxDate = Feb 14:\n  //   Feb 1–14  → enabled  (index 6–19)\n  //   Feb 15–28 → disabled but focusable (index 20–33)\n  //   Mar 1     → outside-month → always skipped (index 34)\n  // ---------------------------------------------------------------------------\n\n  describe('focusableWhenDisabled', () => {\n    const maxDate = feb14;\n\n    describe('ArrowRight', () => {\n      it('should land on a disabled day when focusableWhenDisabled is true', async () => {\n        const { user } = renderCalendar(adapter.startOfMonth(feb14), {\n          focusableWhenDisabled: true,\n          maxDate,\n        });\n\n        await act(async () => {\n          getDayButton(feb14).focus();\n        });\n        await user.keyboard('{ArrowRight}');\n\n        expect(getDayButton(feb15)).toHaveFocus();\n      });\n\n      it('should continue navigating from a focused disabled day', async () => {\n        const { user } = renderCalendar(adapter.startOfMonth(feb14), {\n          focusableWhenDisabled: true,\n          maxDate,\n        });\n\n        await act(async () => {\n          getDayButton(feb14).focus();\n        });\n        await user.keyboard('{ArrowRight}');\n\n        expect(getDayButton(feb15)).toHaveFocus();\n\n        await user.keyboard('{ArrowRight}');\n\n        expect(getDayButton(feb16)).toHaveFocus();\n      });\n    });\n\n    describe('ArrowLeft', () => {\n      it('should land on a disabled day when navigating left', async () => {\n        const { user } = renderCalendar(adapter.startOfMonth(feb16), {\n          focusableWhenDisabled: true,\n          maxDate,\n        });\n\n        await act(async () => {\n          getDayButton(feb16).focus();\n        });\n        await user.keyboard('{ArrowLeft}');\n\n        expect(getDayButton(feb15)).toHaveFocus();\n      });\n    });\n\n    describe('ArrowDown', () => {\n      it('should land on a disabled day when navigating down', async () => {\n        const feb21 = adapter.date('2025-02-21', 'default');\n\n        const { user } = renderCalendar(adapter.startOfMonth(feb14), {\n          focusableWhenDisabled: true,\n          maxDate,\n        });\n\n        await act(async () => {\n          getDayButton(feb14).focus();\n        });\n        await user.keyboard('{ArrowDown}');\n\n        expect(getDayButton(feb21)).toHaveFocus();\n      });\n    });\n\n    it('should not select a day when Enter is pressed on a focused disabled day', async () => {\n      const onValueChange = vi.fn();\n\n      const { user } = renderCalendar(adapter.startOfMonth(feb14), {\n        focusableWhenDisabled: true,\n        maxDate,\n        onValueChange,\n      });\n\n      await act(async () => {\n        getDayButton(feb14).focus();\n      });\n      await user.keyboard('{ArrowRight}');\n\n      expect(getDayButton(feb15)).toHaveFocus();\n\n      await user.keyboard('{Enter}');\n\n      expect(onValueChange.mock.calls.length).toBe(0);\n    });\n\n    it('should skip disabled days when focusableWhenDisabled is false (default)', async () => {\n      // Default renderCalendar does not set focusableWhenDisabled on DayButton.\n      // With maxDate = Feb 14, ArrowRight from Feb 14 should not move focus since there\n      // are no more enabled in-month days ahead and the outside-month trailing day is\n      // also skipped.\n      const { user } = renderCalendar(adapter.startOfMonth(feb14), {\n        maxDate,\n      });\n\n      await act(async () => {\n        getDayButton(feb14).focus();\n      });\n      await user.keyboard('{ArrowRight}');\n\n      expect(getDayButton(feb14)).toHaveFocus();\n    });\n  });\n\n  describe('disabled day boundary (minDate / maxDate)', () => {\n    describe('PageDown', () => {\n      it('should not navigate to the next month if the same day would be after maxDate', async () => {\n        const onVisibleDateChange = vi.fn();\n\n        const { user } = renderCalendar(adapter.startOfMonth(feb15), {\n          onVisibleDateChange,\n          maxDate: feb28,\n        });\n\n        await act(async () => {\n          getDayButton(feb15).focus();\n        });\n        await user.keyboard('[PageDown]');\n\n        expect(onVisibleDateChange.mock.calls.length).toBe(0);\n        expect(getDayButton(feb15)).toHaveFocus();\n      });\n    });\n\n    it('should find the closest day to navigate to the next month if the same day would be after maxDate', async () => {\n      const onVisibleDateChange = vi.fn();\n\n      const { user } = renderCalendar(adapter.startOfMonth(mar31), {\n        onVisibleDateChange,\n        maxDate: apr7,\n      });\n\n      await act(async () => {\n        getDayButton(mar31).focus();\n      });\n      await user.keyboard('[PageDown]');\n\n      expect(onVisibleDateChange.mock.calls.length).toBe(1);\n      expect(onVisibleDateChange.mock.calls[0][1].reason).toBe('keyboard');\n      expect(onVisibleDateChange.mock.calls[0][0]).toEqual(adapter.startOfMonth(apr7));\n      expect(getDayButton(apr7)).toHaveFocus();\n    });\n\n    describe('PageUp', () => {\n      it('should not navigate to the previous month if the same day would be before minDate', async () => {\n        const onVisibleDateChange = vi.fn();\n\n        const { user } = renderCalendar(adapter.startOfMonth(feb15), {\n          onVisibleDateChange,\n          minDate: feb1,\n        });\n\n        await act(async () => {\n          getDayButton(feb15).focus();\n        });\n        await user.keyboard('[PageUp]');\n\n        expect(onVisibleDateChange.mock.calls.length).toBe(0);\n        expect(getDayButton(feb15)).toHaveFocus();\n      });\n\n      it('should find the closest day to navigate to the previous month if the same day would be before minDate', async () => {\n        const onVisibleDateChange = vi.fn();\n\n        const { user } = renderCalendar(adapter.startOfMonth(aug7), {\n          onVisibleDateChange,\n          minDate: jul25,\n        });\n\n        await act(async () => {\n          getDayButton(aug7).focus();\n        });\n        await user.keyboard('[PageUp]');\n\n        expect(onVisibleDateChange.mock.calls.length).toBe(1);\n        expect(onVisibleDateChange.mock.calls[0][1].reason).toBe('keyboard');\n        expect(onVisibleDateChange.mock.calls[0][0]).toEqual(adapter.startOfMonth(jul25));\n        expect(getDayButton(jul25)).toHaveFocus();\n      });\n    });\n\n    describe('Shift+PageDown', () => {\n      it('should not navigate 12 months forward if the same day would be after maxDate', async () => {\n        const maxDate = adapter.date('2025-12-31', 'default');\n        const onVisibleDateChange = vi.fn();\n\n        const { user } = renderCalendar(adapter.startOfMonth(feb15), {\n          onVisibleDateChange,\n          maxDate,\n        });\n\n        await act(async () => {\n          getDayButton(feb15).focus();\n        });\n        await user.keyboard('[ShiftLeft>][PageDown][/ShiftLeft]');\n\n        expect(onVisibleDateChange.mock.calls.length).toBe(0);\n        expect(getDayButton(feb15)).toHaveFocus();\n      });\n    });\n\n    describe('Shift+PageUp', () => {\n      it('should not navigate 12 months backward if the same day would be before minDate', async () => {\n        const onVisibleDateChange = vi.fn();\n        const minDate = adapter.date('2025-01-01', 'default');\n\n        const { user } = renderCalendar(adapter.startOfMonth(feb15), {\n          onVisibleDateChange,\n          minDate,\n        });\n\n        await act(async () => {\n          getDayButton(feb15).focus();\n        });\n        await user.keyboard('[ShiftLeft>][PageUp][/ShiftLeft]');\n\n        expect(onVisibleDateChange.mock.calls.length).toBe(0);\n        expect(getDayButton(feb15)).toHaveFocus();\n      });\n    });\n\n    describe('ArrowRight', () => {\n      it('should not wrap to the next month when the last day is at maxDate', async () => {\n        const onVisibleDateChange = vi.fn();\n\n        const { user } = renderCalendar(adapter.startOfMonth(feb28), {\n          onVisibleDateChange,\n          maxDate: feb28,\n        });\n\n        await act(async () => {\n          getDayButton(feb28).focus();\n        });\n        await user.keyboard('{ArrowRight}');\n\n        expect(onVisibleDateChange.mock.calls.length).toBe(0);\n        expect(getDayButton(feb28)).toHaveFocus();\n      });\n    });\n\n    describe('ArrowLeft', () => {\n      it('should not wrap to the previous month when the first day is at minDate', async () => {\n        const onVisibleDateChange = vi.fn();\n\n        const { user } = renderCalendar(adapter.startOfMonth(feb1), {\n          onVisibleDateChange,\n          minDate: feb1,\n        });\n\n        await act(async () => {\n          getDayButton(feb1).focus();\n        });\n        await user.keyboard('{ArrowLeft}');\n\n        expect(onVisibleDateChange.mock.calls.length).toBe(0);\n        expect(getDayButton(feb1)).toHaveFocus();\n      });\n    });\n\n    describe('ArrowDown', () => {\n      it('should not wrap to the next month when the day in the same weekday column would be after maxDate', async () => {\n        const onVisibleDateChange = vi.fn();\n\n        const { user } = renderCalendar(adapter.startOfMonth(feb28), {\n          onVisibleDateChange,\n          maxDate: feb28,\n        });\n\n        await act(async () => {\n          getDayButton(feb28).focus();\n        });\n        await user.keyboard('{ArrowDown}');\n\n        expect(onVisibleDateChange.mock.calls.length).toBe(0);\n        expect(getDayButton(feb28)).toHaveFocus();\n      });\n    });\n\n    describe('ArrowUp', () => {\n      it('should not wrap to the previous month when the day in the same weekday column would be before minDate', async () => {\n        const onVisibleDateChange = vi.fn();\n\n        const { user } = renderCalendar(adapter.startOfMonth(feb1), {\n          onVisibleDateChange,\n          minDate: feb1,\n        });\n\n        await act(async () => {\n          getDayButton(feb1).focus();\n        });\n        await user.keyboard('{ArrowUp}');\n\n        expect(onVisibleDateChange.mock.calls.length).toBe(0);\n        expect(getDayButton(feb1)).toHaveFocus();\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/calendar/day-grid-body/CalendarDayGridBody.test.tsx",
    "content": "import * as React from 'react';\nimport { screen } from '@mui/internal-test-utils';\nimport { expect } from 'vitest';\nimport { Calendar } from '@base-ui/react/calendar';\nimport { createTemporalRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Calendar.DayGridBody />', () => {\n  const { render, adapter } = createTemporalRenderer();\n\n  describeConformance(<Calendar.DayGridBody />, () => ({\n    refInstanceof: window.HTMLTableSectionElement,\n    testRenderPropWith: 'tbody',\n    wrappingAllowed: false,\n    render(node) {\n      return render(\n        <Calendar.Root>\n          <Calendar.DayGrid>{node}</Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n    },\n  }));\n\n  describe('fixedWeekNumber prop', () => {\n    it('should render the correct number of weeks without fixedWeekNumber for a month with 4 weeks', () => {\n      // February 2026 starts on Sunday and ends on Saturday (4 weeks exactly for en-US locale)\n      const date = adapter.date('2026-02-01', 'default');\n\n      render(\n        <Calendar.Root visibleDate={date}>\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>\n              {(week) => (\n                <Calendar.DayGridRow value={week} key={week.getTime()} data-testid=\"week-row\" />\n              )}\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const rows = screen.getAllByTestId('week-row');\n      expect(rows.length).toBe(4);\n    });\n\n    it('should render the correct number of weeks without fixedWeekNumber for a month with 5 weeks', () => {\n      // February 2025 has 5 weeks (starts on Saturday, ends on Friday in en-US locale)\n      const date = adapter.date('2025-02-01', 'default');\n\n      render(\n        <Calendar.Root visibleDate={date}>\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>\n              {(week) => (\n                <Calendar.DayGridRow value={week} key={week.getTime()} data-testid=\"week-row\" />\n              )}\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const rows = screen.getAllByTestId('week-row');\n      expect(rows.length).toBe(5);\n    });\n\n    it('should render the correct number of weeks without fixedWeekNumber for a month with 6 weeks', () => {\n      // March 2025 has 6 weeks (starts on Saturday, ends on Monday in en-US locale)\n      const date = adapter.date('2025-03-01', 'default');\n\n      render(\n        <Calendar.Root visibleDate={date}>\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>\n              {(week) => (\n                <Calendar.DayGridRow value={week} key={week.getTime()} data-testid=\"week-row\" />\n              )}\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const rows = screen.getAllByTestId('week-row');\n      expect(rows.length).toBe(6);\n    });\n\n    it('should render exactly 6 weeks when fixedWeekNumber is 6 for a month with 4 weeks', () => {\n      // February 2026 has 4 weeks but we force 6\n      const date = adapter.date('2026-02-01', 'default');\n\n      render(\n        <Calendar.Root visibleDate={date}>\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody fixedWeekNumber={6}>\n              {(week) => (\n                <Calendar.DayGridRow value={week} key={week.getTime()} data-testid=\"week-row\" />\n              )}\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const rows = screen.getAllByTestId('week-row');\n      expect(rows.length).toBe(6);\n    });\n\n    it('should render exactly 6 weeks when fixedWeekNumber is 6 for a month with 5 weeks', () => {\n      // February 2025 has 5 weeks but we force 6\n      const date = adapter.date('2025-02-01', 'default');\n\n      render(\n        <Calendar.Root visibleDate={date}>\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody fixedWeekNumber={6}>\n              {(week) => (\n                <Calendar.DayGridRow value={week} key={week.getTime()} data-testid=\"week-row\" />\n              )}\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const rows = screen.getAllByTestId('week-row');\n      expect(rows.length).toBe(6);\n    });\n\n    it('should render exactly 6 weeks when fixedWeekNumber is 6 for a month with 6 weeks', () => {\n      // March 2025 has 6 weeks\n      const date = adapter.date('2025-03-01', 'default');\n\n      render(\n        <Calendar.Root visibleDate={date}>\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody fixedWeekNumber={6}>\n              {(week) => (\n                <Calendar.DayGridRow value={week} key={week.getTime()} data-testid=\"week-row\" />\n              )}\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const rows = screen.getAllByTestId('week-row');\n      expect(rows.length).toBe(6);\n    });\n\n    it('should render exactly 5 weeks when fixedWeekNumber is 5', () => {\n      // Use a month that would normally have 4 or 6 weeks\n      const date = adapter.date('2026-02-01', 'default');\n\n      render(\n        <Calendar.Root visibleDate={date}>\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody fixedWeekNumber={5}>\n              {(week) => (\n                <Calendar.DayGridRow value={week} key={week.getTime()} data-testid=\"week-row\" />\n              )}\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const rows = screen.getAllByTestId('week-row');\n      expect(rows.length).toBe(5);\n    });\n  });\n\n  describe('offset prop', () => {\n    it('should render the current month when offset is 0', () => {\n      const date = adapter.date('2025-02-15', 'default');\n\n      render(\n        <Calendar.Root visibleDate={date}>\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody offset={0}>\n              {(week) => (\n                <Calendar.DayGridRow value={week} key={week.getTime()}>\n                  {(day) => (\n                    <Calendar.DayGridCell value={day} key={day.getTime()}>\n                      <Calendar.DayButton\n                        data-testid={`month-${adapter.format(day, 'monthPadded')}`}\n                      />\n                    </Calendar.DayGridCell>\n                  )}\n                </Calendar.DayGridRow>\n              )}\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      // Check that February (02) days are rendered - February 2025 has 28 days\n      expect(screen.getAllByTestId('month-02').length).toBe(28);\n    });\n\n    it('should render the next month when offset is 1', () => {\n      const date = adapter.date('2025-02-15', 'default');\n\n      render(\n        <Calendar.Root visibleDate={date}>\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody offset={1}>\n              {(week) => (\n                <Calendar.DayGridRow value={week} key={week.getTime()}>\n                  {(day) => (\n                    <Calendar.DayGridCell value={day} key={day.getTime()}>\n                      <Calendar.DayButton\n                        data-testid={`month-${adapter.format(day, 'monthPadded')}`}\n                      />\n                    </Calendar.DayGridCell>\n                  )}\n                </Calendar.DayGridRow>\n              )}\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      // Check that March (03) days are rendered - March has 31 days\n      expect(screen.getAllByTestId('month-03').length).toBe(31);\n    });\n\n    it('should render the previous month when offset is -1', () => {\n      const date = adapter.date('2025-02-15', 'default');\n\n      render(\n        <Calendar.Root visibleDate={date}>\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody offset={-1}>\n              {(week) => (\n                <Calendar.DayGridRow value={week} key={week.getTime()}>\n                  {(day) => (\n                    <Calendar.DayGridCell value={day} key={day.getTime()}>\n                      <Calendar.DayButton\n                        data-testid={`month-${adapter.format(day, 'monthPadded')}`}\n                      />\n                    </Calendar.DayGridCell>\n                  )}\n                </Calendar.DayGridRow>\n              )}\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      // Check that January (01) days are rendered - January has 31 days\n      expect(screen.getAllByTestId('month-01').length).toBe(31);\n    });\n\n    it('should render multiple months with different offsets', () => {\n      const date = adapter.date('2025-02-15', 'default');\n\n      render(\n        <Calendar.Root visibleDate={date}>\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody offset={0}>\n              {(week) => (\n                <Calendar.DayGridRow value={week} key={week.getTime()}>\n                  {(day) => (\n                    <Calendar.DayGridCell value={day} key={day.getTime()}>\n                      <Calendar.DayButton\n                        data-testid={`current-month-${adapter.format(day, 'monthPadded')}`}\n                      />\n                    </Calendar.DayGridCell>\n                  )}\n                </Calendar.DayGridRow>\n              )}\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody offset={1}>\n              {(week) => (\n                <Calendar.DayGridRow value={week} key={week.getTime()}>\n                  {(day) => (\n                    <Calendar.DayGridCell value={day} key={day.getTime()}>\n                      <Calendar.DayButton\n                        data-testid={`next-month-${adapter.format(day, 'monthPadded')}`}\n                      />\n                    </Calendar.DayGridCell>\n                  )}\n                </Calendar.DayGridRow>\n              )}\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      // Current month (February - 02) has 28 days\n      expect(screen.getAllByTestId('current-month-02').length).toBe(28);\n\n      // Next month (March - 03) has 31 days\n      expect(screen.getAllByTestId('next-month-03').length).toBe(31);\n    });\n\n    it('should default to offset 0 when not provided', () => {\n      const date = adapter.date('2025-02-15', 'default');\n\n      render(\n        <Calendar.Root visibleDate={date}>\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>\n              {(week) => (\n                <Calendar.DayGridRow value={week} key={week.getTime()}>\n                  {(day) => (\n                    <Calendar.DayGridCell value={day} key={day.getTime()}>\n                      <Calendar.DayButton\n                        data-testid={`month-${adapter.format(day, 'monthPadded')}`}\n                      />\n                    </Calendar.DayGridCell>\n                  )}\n                </Calendar.DayGridRow>\n              )}\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      // Check that February (02) days are rendered - February 2025 has 28 days\n      expect(screen.getAllByTestId('month-02').length).toBe(28);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/calendar/day-grid-body/CalendarDayGridBody.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { SharedCalendarDayGridBodyContext } from './SharedCalendarDayGridBodyContext';\nimport {\n  useSharedCalendarDayGridBody,\n  UseSharedCalendarDayGridBodyParameters,\n} from './useSharedCalendarDayGridBody';\nimport { CompositeRoot } from '../../composite/root/CompositeRoot';\n\n/**\n * Groups all parts of the calendar's day grid.\n * Renders a `<tbody>` element.\n *\n * Documentation: [Base UI Calendar](https://base-ui.com/react/components/calendar)\n */\nexport const CalendarDayGridBody = React.forwardRef(function CalendarDayGridBody(\n  componentProps: CalendarDayGridBody.Props,\n  forwardedRef: React.ForwardedRef<HTMLTableSectionElement>,\n) {\n  const { className, render, children, fixedWeekNumber, offset, ...elementProps } = componentProps;\n\n  const { props, compositeRootProps, context, ref } = useSharedCalendarDayGridBody({\n    children,\n    fixedWeekNumber,\n    offset,\n  });\n\n  const element = useRenderElement('tbody', componentProps, {\n    ref: [forwardedRef, ref],\n    props: [props, elementProps],\n  });\n\n  return (\n    <SharedCalendarDayGridBodyContext.Provider value={context}>\n      <CompositeRoot {...compositeRootProps} render={element} />\n    </SharedCalendarDayGridBodyContext.Provider>\n  );\n});\n\nexport interface CalendarDayGridBodyState {}\n\nexport interface CalendarDayGridBodyProps\n  extends\n    Omit<BaseUIComponentProps<'tbody', CalendarDayGridBodyState>, 'children'>,\n    UseSharedCalendarDayGridBodyParameters {}\n\nexport namespace CalendarDayGridBody {\n  export type State = CalendarDayGridBodyState;\n  export type Props = CalendarDayGridBodyProps;\n}\n"
  },
  {
    "path": "packages/react/src/calendar/day-grid-body/SharedCalendarDayGridBodyContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { TemporalSupportedObject } from '../../types/temporal';\n\nexport interface SharedCalendarDayGridBodyContext {\n  /**\n   * The month of this component.\n   */\n  month: TemporalSupportedObject;\n  /**\n   * Today's date, computed once at the grid body level.\n   */\n  today: TemporalSupportedObject;\n}\n\nexport const SharedCalendarDayGridBodyContext = React.createContext<\n  SharedCalendarDayGridBodyContext | undefined\n>(undefined);\n\nexport function useSharedCalendarDayGridBodyContext() {\n  const context = React.useContext(SharedCalendarDayGridBodyContext);\n  if (context === undefined) {\n    throw new Error(\n      'Base UI: SharedCalendarDayGridBodyContext is missing. <Calendar.DayGridRow /> must be placed within <Calendar.DayGridBody /> and <RangeCalendar.DayGridRow /> must be placed within <RangeCalendar.DayGridBody />.',\n    );\n  }\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/calendar/day-grid-body/useSharedCalendarDayGridBody.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStore } from '@base-ui/utils/store';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { REASONS } from '../../utils/reasons';\nimport { useSharedCalendarRootContext } from '../root/SharedCalendarRootContext';\nimport { SharedCalendarDayGridBodyContext } from './SharedCalendarDayGridBodyContext';\nimport { BaseUIEvent, HTMLProps } from '../../utils/types';\nimport { useTemporalAdapter } from '../../temporal-adapter-provider/TemporalAdapterContext';\nimport { TemporalSupportedObject } from '../../types/temporal';\nimport { useCalendarWeekList } from '../use-week-list/useCalendarWeekList';\nimport { selectors } from '../store';\nimport { computeMonthDayGrid } from '../utils/computeMonthDayGrid';\nimport { areArraysEqual } from '../../utils/areArraysEqual';\nimport { CompositeMetadata } from '../../composite/list/CompositeList';\nimport { CompositeRoot } from '../../composite/root/CompositeRoot';\nimport { findNonDisabledListIndex, isListIndexDisabled } from '../../floating-ui-react/utils';\nimport {\n  ARROW_DOWN,\n  ARROW_LEFT,\n  ARROW_RIGHT,\n  ARROW_UP,\n  HORIZONTAL_KEYS,\n  HOME,\n  END,\n  PAGE_UP,\n  PAGE_DOWN,\n} from '../../composite/composite';\nimport { validateDate } from '../../utils/temporal/validateDate';\n\nconst BACKWARD_KEYS = new Set([ARROW_UP, ARROW_LEFT]);\nconst CUSTOM_NAVIGATION_KEYS = new Set([HOME, END, PAGE_UP, PAGE_DOWN]);\n\nexport function useSharedCalendarDayGridBody(\n  parameters: UseSharedCalendarDayGridBodyParameters,\n): UseSharedCalendarDayGridBodyReturnValue {\n  const { fixedWeekNumber, children, offset = 0 } = parameters;\n\n  const adapter = useTemporalAdapter();\n  const store = useSharedCalendarRootContext();\n  const visibleMonth = useStore(store, selectors.visibleMonth);\n  const timezone = useStore(store, selectors.timezoneToRender);\n  const ref = React.useRef<HTMLTableSectionElement>(null);\n  const [highlightedIndex, setHighlightedIndex] = React.useState(0);\n  const executeAfterItemMapUpdate = React.useRef<(newMap: any) => void>(null);\n\n  const nowValue = adapter.now(timezone);\n  const todayRef = React.useRef(nowValue);\n  if (!adapter.isSameDay(todayRef.current, nowValue)) {\n    todayRef.current = nowValue;\n  }\n  const today = todayRef.current;\n\n  const month = React.useMemo(() => {\n    return offset === 0 ? visibleMonth : adapter.addMonths(visibleMonth, offset);\n  }, [adapter, visibleMonth, offset]);\n\n  const getWeekList = useCalendarWeekList();\n  const weeks = React.useMemo(\n    () => getWeekList({ date: month, amount: fixedWeekNumber ?? 'end-of-month' }),\n    [getWeekList, month, fixedWeekNumber],\n  );\n\n  const resolvedChildren = React.useMemo(() => {\n    if (!React.isValidElement(children) && typeof children === 'function') {\n      return weeks.map(children);\n    }\n\n    return children;\n  }, [children, weeks]);\n\n  const [itemMap, setItemMap] = React.useState(\n    () => new Map<Node, CompositeMetadata<UseSharedCalendarDayGridBodyItemMetadata> | null>(),\n  );\n\n  const items = React.useMemo(() => Array.from(itemMap.keys()) as HTMLElement[], [itemMap]);\n\n  const prevDisabledIndicesRef = React.useRef<number[]>([]);\n\n  const disabledIndices = React.useMemo(() => {\n    const output: number[] = [];\n    for (const itemMetadata of itemMap.values()) {\n      if (itemMetadata?.index != null && !itemMetadata.focusable) {\n        output.push(itemMetadata.index);\n      }\n    }\n    if (areArraysEqual(prevDisabledIndicesRef.current, output)) {\n      return prevDisabledIndicesRef.current;\n    }\n    prevDisabledIndicesRef.current = output;\n    return output;\n  }, [itemMap]);\n\n  const handleItemMapUpdate = (newMap: typeof itemMap) => {\n    setItemMap(newMap);\n  };\n\n  // Execute pending focus callback after React has committed the new item map to the DOM.\n  // This replaces a queueMicrotask approach that could fire before React's commit phase.\n  useIsoLayoutEffect(() => {\n    if (executeAfterItemMapUpdate.current) {\n      executeAfterItemMapUpdate.current(itemMap);\n      executeAfterItemMapUpdate.current = null;\n    }\n  }, [itemMap]);\n\n  const focusNonDisabledItem = (\n    elements: Array<HTMLElement | null>,\n    itemDisabledIndices: number[],\n    guessedIndex: number,\n    decrement: boolean,\n    amount: number,\n  ) => {\n    if (elements.length === 0) {\n      return;\n    }\n    let idx = guessedIndex;\n    if (isListIndexDisabled(elements, idx, itemDisabledIndices)) {\n      idx = findNonDisabledListIndex(elements, {\n        startingIndex: idx,\n        decrement,\n        disabledIndices: itemDisabledIndices,\n        amount,\n      });\n    }\n    if (idx >= 0 && idx < elements.length) {\n      setHighlightedIndex(idx);\n      elements[idx]?.focus();\n    }\n  };\n\n  const focusNextNonDisabledElement = ({\n    elements = items,\n    newHighlightedIndex,\n    decrement,\n    amount,\n  }: {\n    elements?: Array<HTMLElement | null> | undefined;\n    newHighlightedIndex: number;\n    decrement: boolean;\n    amount: number;\n  }) => {\n    focusNonDisabledItem(elements, disabledIndices, newHighlightedIndex, decrement, amount);\n  };\n\n  // Focuses the correct item after a cross-month navigation (PageUp/PageDown or arrow-key\n  // loop). Uses the new month's item map directly because the `disabledIndices` state\n  // variable still reflects the old month at this point.\n  const focusItemFromMap = (\n    newMap: typeof itemMap,\n    guessedIndex: number,\n    decrement: boolean,\n    amount: number,\n  ) => {\n    const newItems = Array.from(newMap.keys()) as HTMLElement[];\n    const newDisabledIndices: number[] = [];\n    for (const meta of newMap.values()) {\n      if (meta?.index != null && !meta.focusable) {\n        newDisabledIndices.push(meta.index);\n      }\n    }\n    focusNonDisabledItem(newItems, newDisabledIndices, guessedIndex, decrement, amount);\n  };\n\n  const handleKeyboardNavigation = (event: BaseUIEvent<React.KeyboardEvent>) => {\n    const eventKey = event.key;\n\n    if (!CUSTOM_NAVIGATION_KEYS.has(eventKey)) {\n      return;\n    }\n    switch (eventKey) {\n      case HOME:\n      case END: {\n        const colIndex = highlightedIndex % 7;\n        // allow for default composite navigation in case we are on the first or last day of the week\n        if ((eventKey === HOME && colIndex === 0) || (eventKey === END && colIndex === 6)) {\n          return;\n        }\n        // prevent default composite navigation and handle it ourselves\n        event.preventDefault();\n        event.preventBaseUIHandler();\n        const currentWeekStartIndex = Math.floor(highlightedIndex / 7) * 7;\n        const newHighlightedIndex =\n          eventKey === HOME ? currentWeekStartIndex : currentWeekStartIndex + 6;\n        focusNextNonDisabledElement({\n          elements: items,\n          newHighlightedIndex,\n          decrement: eventKey === HOME,\n          amount: 1,\n        });\n        break;\n      }\n\n      case PAGE_UP:\n      case PAGE_DOWN: {\n        event.preventDefault();\n        // Without knowing the current day we can not move to next month and focus the same day\n        const decrement = eventKey === PAGE_UP;\n        let amount = 1;\n        if (event.shiftKey) {\n          amount = 12;\n        }\n        const currentDay = computeMonthDayGrid(adapter, month, fixedWeekNumber, weeks)[\n          highlightedIndex\n        ];\n        if (!currentDay) {\n          return;\n        }\n\n        const targetDate = adapter.addMonths(currentDay, decrement ? -amount : amount);\n        const targetMonth = adapter.addMonths(visibleMonth, decrement ? -amount : amount);\n\n        const { minDate, maxDate } = store.state;\n        // Check if the target date would be within min/max bounds\n        let dateValidationError: ReturnType<typeof validateDate> = null;\n        if (minDate != null || maxDate != null) {\n          dateValidationError = validateDate({\n            adapter,\n            value: targetDate,\n            validationProps: { minDate, maxDate },\n          });\n          if (dateValidationError != null) {\n            // Block navigation only if the entire target month is outside the valid range.\n            // If the month has some valid days, navigate and let focusItemFromMap find the nearest one.\n            if (\n              (maxDate != null && adapter.isAfter(adapter.startOfMonth(targetMonth), maxDate)) ||\n              (minDate != null && adapter.isBefore(adapter.endOfMonth(targetMonth), minDate))\n            ) {\n              return;\n            }\n          }\n        }\n\n        store.setVisibleDate(\n          targetMonth,\n          event.nativeEvent,\n          event.currentTarget as HTMLElement,\n          REASONS.keyboard,\n        );\n        executeAfterItemMapUpdate.current = (newMap: typeof itemMap) => {\n          const newMonth = adapter.addMonths(month, decrement ? -amount : amount);\n          const newGridDays = computeMonthDayGrid(adapter, newMonth, fixedWeekNumber);\n          // Find the target date in the new month's grid. Use targetDate (already clamped\n          // by addMonths) so that e.g. Jan 31 + 1 month correctly finds Feb 28.\n          const targetDayOfMonth = adapter.getDate(targetDate);\n          const targetMonthValue = adapter.getMonth(targetDate);\n          const targetYearValue = adapter.getYear(targetDate);\n          const sameDayInNewMonthIndex = newGridDays.findIndex(\n            (day) =>\n              adapter.getDate(day) === targetDayOfMonth &&\n              adapter.getMonth(day) === targetMonthValue &&\n              adapter.getYear(day) === targetYearValue,\n          );\n          // When the target day is disabled, find the nearest valid day in the right direction:\n          // beyond maxDate → search backward for the last valid day;\n          // before minDate → search forward for the first valid day.\n          let searchDecrement = eventKey === PAGE_UP;\n          if (dateValidationError === 'after-max-date') {\n            searchDecrement = true;\n          } else if (dateValidationError === 'before-min-date') {\n            searchDecrement = false;\n          }\n          focusItemFromMap(newMap, sameDayInNewMonthIndex, searchDecrement, 1);\n        };\n\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  };\n\n  const handleLooping: CompositeRoot.Props<\n    UseSharedCalendarDayGridBodyItemMetadata,\n    any\n  >['onLoop'] = (event, prevIndex) => {\n    event.preventDefault();\n    const eventKey = event.key;\n    const decrement = BACKWARD_KEYS.has(eventKey);\n\n    const targetMonth = adapter.addMonths(visibleMonth, decrement ? -1 : 1);\n\n    const { minDate, maxDate } = store.state;\n    if (minDate != null || maxDate != null) {\n      // Check if the target month has any valid (non-disabled) days within min/max bounds.\n      if (\n        (minDate != null && adapter.isBefore(adapter.endOfMonth(targetMonth), minDate)) ||\n        (maxDate != null && adapter.isAfter(adapter.startOfMonth(targetMonth), maxDate))\n      ) {\n        // The entire target month is outside the valid range; stay put.\n        return prevIndex;\n      }\n    }\n\n    // Change the visible month and focus the equivalent day once the new month's\n    // DOM has been committed. This covers every arrow-key loop scenario, including\n    // cases where an outside-month day is visible as the first/last row of the\n    // current grid — the visible date must always update when crossing a month boundary.\n    store.setVisibleDate(\n      targetMonth,\n      event.nativeEvent,\n      event.currentTarget as HTMLElement,\n      REASONS.keyboard,\n    );\n\n    executeAfterItemMapUpdate.current = (newMap: typeof itemMap) => {\n      const newItems = Array.from(newMap.keys()) as HTMLElement[];\n\n      let guessedIndex: number;\n      if (eventKey === ARROW_LEFT) {\n        guessedIndex = newItems.length - 1;\n      } else if (eventKey === ARROW_RIGHT) {\n        guessedIndex = 0;\n      } else if (eventKey === ARROW_DOWN) {\n        guessedIndex = prevIndex % 7;\n      } else {\n        // ARROW_UP: same weekday in the last row of the previous month\n        guessedIndex = newItems.length - 7 + (prevIndex % 7);\n      }\n\n      focusItemFromMap(newMap, guessedIndex, decrement, HORIZONTAL_KEYS.has(eventKey) ? 1 : 7);\n    };\n\n    // Return the current index so the composite does not move focus before the new month renders.\n    return prevIndex;\n  };\n\n  const compositeRootProps: CompositeRoot.Props<UseSharedCalendarDayGridBodyItemMetadata, any> = {\n    cols: 7,\n    disabledIndices,\n    orientation: 'horizontal',\n    enableHomeAndEndKeys: true,\n    onMapChange: handleItemMapUpdate,\n    highlightedIndex,\n    onKeyDown: handleKeyboardNavigation,\n    onHighlightedIndexChange: setHighlightedIndex,\n    onLoop: handleLooping,\n  };\n\n  const props: HTMLProps = {\n    children: resolvedChildren,\n  };\n\n  const context: SharedCalendarDayGridBodyContext = React.useMemo(\n    () => ({ month, today }),\n    [month, today],\n  );\n\n  return { props, compositeRootProps, context, ref };\n}\n\nexport interface UseSharedCalendarDayGridBodyParameters {\n  /**\n   * The children of the component.\n   * If a function is provided, it will be called for each week to render as its parameter.\n   */\n  children?:\n    | React.ReactNode\n    | ((\n        week: TemporalSupportedObject,\n        index: number,\n        weeks: TemporalSupportedObject[],\n      ) => React.ReactNode);\n  /**\n   * Will render the requested amount of weeks by adding weeks of the next month if needed.\n   * Set it to 6 to create a Gregorian calendar where all months have the same number of weeks.\n   */\n  fixedWeekNumber?: number | undefined;\n  /**\n   * The offset to apply to the rendered month compared to the current month.\n   * This is mostly useful when displaying multiple day grids.\n   * @default 0\n   */\n  offset?: number | undefined;\n}\n\nexport interface UseSharedCalendarDayGridBodyItemMetadata {\n  focusable?: boolean | undefined;\n}\n\nexport interface UseSharedCalendarDayGridBodyReturnValue {\n  /**\n   * The props to apply to the element.\n   */\n  props: HTMLProps;\n  /**\n   * The props to apply to the composite root.\n   */\n  compositeRootProps: CompositeRoot.Props<UseSharedCalendarDayGridBodyItemMetadata, any>;\n  /**\n   * The context to provide to the children of the component.\n   */\n  context: SharedCalendarDayGridBodyContext;\n  /**\n   * The ref to apply to the element.\n   */\n  ref: React.RefObject<HTMLTableSectionElement | null>;\n}\n"
  },
  {
    "path": "packages/react/src/calendar/day-grid-cell/CalendarDayGridCell.test.tsx",
    "content": "import * as React from 'react';\nimport { Calendar } from '@base-ui/react/calendar';\nimport { createTemporalRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Calendar.DayGridCell />', () => {\n  const { render, adapter } = createTemporalRenderer();\n\n  const date = adapter.now('default');\n  const startOfWeek = adapter.startOfWeek(date);\n\n  describeConformance(<Calendar.DayGridCell value={date} />, () => ({\n    refInstanceof: window.HTMLTableCellElement,\n    testRenderPropWith: 'td',\n    wrappingAllowed: false,\n    render(node) {\n      return render(\n        <Calendar.Root>\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>\n              <Calendar.DayGridRow value={startOfWeek}>{node}</Calendar.DayGridRow>\n            </Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/calendar/day-grid-cell/CalendarDayGridCell.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport {\n  useSharedCalendarDayGridCell,\n  UseSharedCalendarDayGridCellParameters,\n} from './useSharedCalendarDayGridCell';\nimport { SharedCalendarDayGridCellContext } from './SharedCalendarDayGridCellContext';\n\nconst InnerCalendarDayGridCell = React.forwardRef(function InnerCalendarDayGridCell(\n  componentProps: CalendarDayGridCell.Props,\n  forwardedRef: React.ForwardedRef<HTMLTableCellElement>,\n) {\n  const { className, render, value, ...elementProps } = componentProps;\n  const { props, context } = useSharedCalendarDayGridCell({ value });\n\n  const element = useRenderElement('td', componentProps, {\n    ref: [forwardedRef],\n    props: [props, elementProps],\n  });\n\n  return (\n    <SharedCalendarDayGridCellContext.Provider value={context}>\n      {element}\n    </SharedCalendarDayGridCellContext.Provider>\n  );\n});\n\n/**\n * An individual day cell in the calendar.\n * Renders a `<td>` element.\n *\n * Documentation: [Base UI Calendar](https://base-ui.com/react/components/calendar)\n */\nexport const CalendarDayGridCell = React.memo(InnerCalendarDayGridCell);\n\nexport interface CalendarDayGridCellState {}\n\nexport interface CalendarDayGridCellProps\n  extends\n    BaseUIComponentProps<'td', CalendarDayGridCellState>,\n    UseSharedCalendarDayGridCellParameters {}\n\nexport namespace CalendarDayGridCell {\n  export type State = CalendarDayGridCellState;\n  export type Props = CalendarDayGridCellProps;\n}\n"
  },
  {
    "path": "packages/react/src/calendar/day-grid-cell/SharedCalendarDayGridCellContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { TemporalSupportedObject } from '../../types/temporal';\n\nexport interface SharedCalendarDayGridCellContext {\n  /**\n   * The value to select when this cell is clicked.\n   */\n  value: TemporalSupportedObject;\n  /**\n   * Whether the cell is disabled.\n   */\n  isDisabled: boolean;\n  /**\n   * Whether the cell is unavailable.\n   */\n  isUnavailable: boolean;\n  /**\n   * Whether the cell is outside the current month.\n   */\n  isOutsideCurrentMonth: boolean;\n}\n\nexport const SharedCalendarDayGridCellContext = React.createContext<\n  SharedCalendarDayGridCellContext | undefined\n>(undefined);\n\nexport function useCalendarDayGridCellContext() {\n  const context = React.useContext(SharedCalendarDayGridCellContext);\n  if (context === undefined) {\n    throw new Error(\n      'Base UI: SharedCalendarDayGridCellContext is missing. <Calendar.DayButton /> must be placed within <Calendar.DayGridCell /> and <RangeCalendar.DayButton /> must be placed within <RangeCalendar.DayGridCell />.',\n    );\n  }\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/calendar/day-grid-cell/useSharedCalendarDayGridCell.ts",
    "content": "import * as React from 'react';\nimport { useStore } from '@base-ui/utils/store';\nimport { TemporalSupportedObject } from '../../types/temporal';\nimport { SharedCalendarDayGridCellContext } from './SharedCalendarDayGridCellContext';\nimport { HTMLProps } from '../../utils/types';\nimport { useTemporalAdapter } from '../../temporal-adapter-provider/TemporalAdapterContext';\nimport { useSharedCalendarDayGridBodyContext } from '../day-grid-body/SharedCalendarDayGridBodyContext';\nimport { useSharedCalendarRootContext } from '../root/SharedCalendarRootContext';\nimport { selectors } from '../store';\n\nexport function useSharedCalendarDayGridCell(\n  parameters: UseSharedCalendarDayGridCellParameters,\n): UseSharedCalendarDayGridCellReturnValue {\n  const { value } = parameters;\n  const adapter = useTemporalAdapter();\n  const store = useSharedCalendarRootContext();\n  const { month } = useSharedCalendarDayGridBodyContext();\n\n  const isDisabled = useStore(store, selectors.isDayCellDisabled, value);\n  const isUnavailable = useStore(store, selectors.isDayCellUnavailable, value);\n\n  const isOutsideCurrentMonth = React.useMemo(\n    () => (month == null ? false : !adapter.isSameMonth(month, value)),\n    [month, value, adapter],\n  );\n\n  const props: HTMLProps = {\n    role: 'gridcell',\n    'aria-disabled': isDisabled || isOutsideCurrentMonth || isUnavailable ? true : undefined,\n  };\n\n  const context: SharedCalendarDayGridCellContext = React.useMemo(\n    () => ({\n      value,\n      isDisabled,\n      isUnavailable,\n      isOutsideCurrentMonth,\n    }),\n    [isDisabled, isUnavailable, isOutsideCurrentMonth, value],\n  );\n\n  return { props, context };\n}\n\nexport interface UseSharedCalendarDayGridCellParameters {\n  /**\n   * The value to select when this cell is clicked.\n   */\n  value: TemporalSupportedObject;\n}\n\nexport interface UseSharedCalendarDayGridCellReturnValue {\n  props: HTMLProps;\n  /**\n   * The context to expose to the children components.\n   */\n  context: SharedCalendarDayGridCellContext;\n}\n"
  },
  {
    "path": "packages/react/src/calendar/day-grid-header/CalendarDayGridHeader.test.tsx",
    "content": "import * as React from 'react';\nimport { Calendar } from '@base-ui/react/calendar';\nimport { createTemporalRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Calendar.DayGridHeader />', () => {\n  const { render } = createTemporalRenderer();\n\n  describeConformance(<Calendar.DayGridHeader />, () => ({\n    refInstanceof: window.HTMLTableSectionElement,\n    testRenderPropWith: 'thead',\n    wrappingAllowed: false,\n    render(node) {\n      return render(\n        <Calendar.Root>\n          <Calendar.DayGrid>{node}</Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/calendar/day-grid-header/CalendarDayGridHeader.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\n\n/**\n * Groups all parts of the calendar's day grid header.\n * Renders a `<thead>` element.\n *\n * Documentation: [Base UI Calendar](https://base-ui.com/react/components/calendar)\n */\nexport const CalendarDayGridHeader = React.forwardRef(function CalendarDayGridHeader(\n  componentProps: CalendarDayGridHeader.Props,\n  forwardedRef: React.ForwardedRef<HTMLTableSectionElement>,\n) {\n  const { className, render, ...elementProps } = componentProps;\n\n  const element = useRenderElement('thead', componentProps, {\n    ref: forwardedRef,\n    props: [{ 'aria-hidden': true }, elementProps],\n  });\n\n  return element;\n});\n\nexport interface CalendarDayGridHeaderState {}\n\nexport interface CalendarDayGridHeaderProps extends BaseUIComponentProps<\n  'thead',\n  CalendarDayGridHeaderState\n> {}\n\nexport namespace CalendarDayGridHeader {\n  export type State = CalendarDayGridHeaderState;\n  export type Props = CalendarDayGridHeaderProps;\n}\n"
  },
  {
    "path": "packages/react/src/calendar/day-grid-header-cell/CalendarDayGridHeaderCell.test.tsx",
    "content": "import * as React from 'react';\nimport { expect } from 'vitest';\nimport { Calendar } from '@base-ui/react/calendar';\nimport { createTemporalRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Calendar.DayGridHeaderCell />', () => {\n  const { render, adapter } = createTemporalRenderer();\n\n  const date = adapter.now('default');\n\n  describeConformance(<Calendar.DayGridHeaderCell value={date} />, () => ({\n    refInstanceof: window.HTMLTableCellElement,\n    testRenderPropWith: 'th',\n    wrappingAllowed: false,\n    render(node) {\n      return render(\n        <Calendar.Root>\n          <Calendar.DayGrid>\n            <Calendar.DayGridHeader>\n              <Calendar.DayGridHeaderRow>{node}</Calendar.DayGridHeaderRow>\n            </Calendar.DayGridHeader>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n    },\n  }));\n\n  describe('formatter prop', () => {\n    it('should use the first letter of the week day format by default', () => {\n      render(\n        <Calendar.Root>\n          <Calendar.DayGrid>\n            <Calendar.DayGridHeader>\n              <Calendar.DayGridHeaderRow>\n                <Calendar.DayGridHeaderCell value={adapter.date('2025-02-04', 'default')} />\n              </Calendar.DayGridHeaderRow>\n            </Calendar.DayGridHeader>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const cell = document.querySelector('th')!;\n      expect(cell.textContent).toBe('T');\n    });\n\n    it('should use the value returned by the provided formatter', () => {\n      render(\n        <Calendar.Root>\n          <Calendar.DayGrid>\n            <Calendar.DayGridHeader>\n              <Calendar.DayGridHeaderRow>\n                <Calendar.DayGridHeaderCell\n                  value={adapter.date('2025-02-04', 'default')}\n                  formatter={() => 'Test'}\n                />\n              </Calendar.DayGridHeaderRow>\n            </Calendar.DayGridHeader>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n\n      const cell = document.querySelector('th')!;\n      expect(cell.textContent).toBe('Test');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/calendar/day-grid-header-cell/CalendarDayGridHeaderCell.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { TemporalSupportedObject } from '../../types/temporal';\nimport { useTemporalAdapter } from '../../temporal-adapter-provider/TemporalAdapterContext';\n\nconst InnerCalendarDayGridHeaderCell = React.forwardRef(function InnerCalendarDayGridHeaderCell(\n  componentProps: CalendarDayGridHeaderCell.Props,\n  forwardedRef: React.ForwardedRef<HTMLTableCellElement>,\n) {\n  const adapter = useTemporalAdapter();\n  const defaultFormatter = React.useCallback(\n    (date: TemporalSupportedObject) =>\n      adapter.format(date, 'weekday3Letters').charAt(0).toUpperCase(),\n    [adapter],\n  );\n\n  const { className, render, value, formatter = defaultFormatter, ...otherProps } = componentProps;\n\n  const formattedValue = React.useMemo(() => formatter(value), [formatter, value]);\n\n  const element = useRenderElement('th', componentProps, {\n    ref: forwardedRef,\n    props: [{ children: formattedValue }, otherProps],\n  });\n\n  return element;\n});\n\n/**\n * An individual day header cell in the calendar.\n * Renders a `<th>` element.\n *\n * Documentation: [Base UI Calendar](https://base-ui.com/react/components/calendar)\n */\nexport const CalendarDayGridHeaderCell = React.memo(InnerCalendarDayGridHeaderCell);\n\nexport interface CalendarDayGridHeaderCellState {}\n\nexport interface CalendarDayGridHeaderCellProps extends BaseUIComponentProps<\n  'th',\n  CalendarDayGridHeaderCellState\n> {\n  value: TemporalSupportedObject;\n  /**\n   * The formatter function used to display the day of the week.\n   * @param {TemporalSupportedObject} date The date to format.\n   * @returns {string} The formatted date.\n   * @default (date) => adapter.format(date, 'EEE').charAt(0).toUpperCase()\n   */\n  formatter?: ((date: TemporalSupportedObject) => string) | undefined;\n}\n\nexport namespace CalendarDayGridHeaderCell {\n  export type State = CalendarDayGridHeaderCellState;\n  export type Props = CalendarDayGridHeaderCellProps;\n}\n"
  },
  {
    "path": "packages/react/src/calendar/day-grid-header-row/CalendarDayGridHeaderRow.test.tsx",
    "content": "import * as React from 'react';\nimport { Calendar } from '@base-ui/react/calendar';\nimport { createTemporalRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Calendar.DayGridHeaderRow />', () => {\n  const { render } = createTemporalRenderer();\n\n  describeConformance(<Calendar.DayGridHeaderRow />, () => ({\n    refInstanceof: window.HTMLTableRowElement,\n    testRenderPropWith: 'tr',\n    wrappingAllowed: false,\n    render(node) {\n      return render(\n        <Calendar.Root>\n          <Calendar.DayGrid>\n            <Calendar.DayGridHeader>{node}</Calendar.DayGridHeader>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/calendar/day-grid-header-row/CalendarDayGridHeaderRow.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { TemporalSupportedObject } from '../../types/temporal';\nimport { useTemporalAdapter } from '../../temporal-adapter-provider/TemporalAdapterContext';\nimport { useCalendarDayList } from '../use-day-list/useCalendarDayList';\n\n/**\n * Groups all cells of the calendar's day grid header row.\n * Renders a `<tr>` element.\n *\n * Documentation: [Base UI Calendar](https://base-ui.com/react/components/calendar)\n */\nexport const CalendarDayGridHeaderRow = React.forwardRef(function CalendarDayGridHeaderRow(\n  componentProps: CalendarDayGridHeaderRow.Props,\n  forwardedRef: React.ForwardedRef<HTMLTableRowElement>,\n) {\n  const { className, render, children, ...elementProps } = componentProps;\n\n  const adapter = useTemporalAdapter();\n\n  const getDayList = useCalendarDayList();\n  const days = React.useMemo(\n    () => getDayList({ date: adapter.startOfWeek(adapter.now('default')), amount: 7 }),\n    [adapter, getDayList],\n  );\n\n  const resolvedChildren = React.useMemo(() => {\n    if (!React.isValidElement(children) && typeof children === 'function') {\n      return days.map(children);\n    }\n\n    return children;\n  }, [children, days]);\n\n  const element = useRenderElement('tr', componentProps, {\n    ref: forwardedRef,\n    props: [{ children: resolvedChildren }, elementProps],\n  });\n\n  return element;\n});\n\nexport interface CalendarDayGridHeaderRowState {}\n\nexport interface CalendarDayGridHeaderRowProps extends Omit<\n  BaseUIComponentProps<'tr', CalendarDayGridHeaderRowState>,\n  'children'\n> {\n  /**\n   * The children of the component.\n   * If a function is provided, it will be called for each day of the week as its parameter.\n   */\n  children?:\n    | React.ReactNode\n    | ((\n        day: TemporalSupportedObject,\n        index: number,\n        days: TemporalSupportedObject[],\n      ) => React.ReactNode);\n}\n\nexport namespace CalendarDayGridHeaderRow {\n  export type State = CalendarDayGridHeaderRowState;\n  export type Props = CalendarDayGridHeaderRowProps;\n}\n"
  },
  {
    "path": "packages/react/src/calendar/day-grid-row/CalendarDayGridRow.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { TemporalSupportedObject } from '../../types/temporal';\nimport { useCalendarDayList } from '../use-day-list/useCalendarDayList';\n\n/**\n * Groups all cells of a given calendar's day grid row.\n * Renders a `<tr>` element.\n *\n * Documentation: [Base UI Calendar](https://base-ui.com/react/components/calendar)\n */\nexport const CalendarDayGridRow = React.forwardRef(function CalendarDayGridRow(\n  componentProps: CalendarDayGridRow.Props,\n  forwardedRef: React.ForwardedRef<HTMLTableRowElement>,\n) {\n  const { className, render, value, children, ...elementProps } = componentProps;\n\n  const getDayList = useCalendarDayList();\n  const days = React.useMemo(() => getDayList({ date: value, amount: 7 }), [getDayList, value]);\n\n  const resolvedChildren = React.useMemo(() => {\n    if (!React.isValidElement(children) && typeof children === 'function') {\n      return days.map(children);\n    }\n\n    return children;\n  }, [children, days]);\n\n  const element = useRenderElement('tr', componentProps, {\n    ref: forwardedRef,\n    props: [{ children: resolvedChildren }, elementProps],\n  });\n\n  return element;\n});\n\nexport interface CalendarDayGridRowState {}\n\nexport interface CalendarDayGridRowProps extends Omit<\n  BaseUIComponentProps<'tr', CalendarDayGridRowState>,\n  'children'\n> {\n  /**\n   * The date object representing the week.\n   */\n  value: TemporalSupportedObject;\n  /**\n   * The children of the component.\n   * If a function is provided, it will be called for each day of the week as its parameter.\n   */\n  children?:\n    | React.ReactNode\n    | ((\n        day: TemporalSupportedObject,\n        index: number,\n        days: TemporalSupportedObject[],\n      ) => React.ReactNode);\n}\n\nexport namespace CalendarDayGridRow {\n  export type State = CalendarDayGridRowState;\n  export type Props = CalendarDayGridRowProps;\n}\n"
  },
  {
    "path": "packages/react/src/calendar/day-grid-row/CalendarDayRow.test.tsx",
    "content": "import * as React from 'react';\nimport { Calendar } from '@base-ui/react/calendar';\nimport { createTemporalRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Calendar.DayGridRow />', () => {\n  const { render, adapter } = createTemporalRenderer();\n\n  const date = adapter.now('default');\n  const startOfWeek = adapter.startOfWeek(date);\n\n  describeConformance(<Calendar.DayGridRow value={startOfWeek} />, () => ({\n    refInstanceof: window.HTMLTableRowElement,\n    testRenderPropWith: 'tr',\n    wrappingAllowed: false,\n    render(node) {\n      return render(\n        <Calendar.Root>\n          <Calendar.DayGrid>\n            <Calendar.DayGridBody>{node}</Calendar.DayGridBody>\n          </Calendar.DayGrid>\n        </Calendar.Root>,\n      );\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/calendar/decrement-month/CalendarDecrementMonth.test.tsx",
    "content": "import * as React from 'react';\nimport { screen, fireEvent } from '@mui/internal-test-utils';\nimport { expect, vi } from 'vitest';\nimport { Calendar } from '@base-ui/react/calendar';\nimport { LocalizationProvider } from '@base-ui/react/localization-provider';\nimport { createRenderer, createTemporalRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Calendar.DecrementMonth />', () => {\n  const { render, adapter } = createTemporalRenderer();\n\n  describeConformance(<Calendar.DecrementMonth />, () => ({\n    refInstanceof: window.HTMLButtonElement,\n    button: true,\n    render(node) {\n      return render(<Calendar.Root>{node}</Calendar.Root>);\n    },\n  }));\n\n  describe('visible date update', () => {\n    it('should keep the day and time of the previous visible date when clicked', async () => {\n      const onVisibleDateChange = vi.fn();\n\n      const { user } = render(\n        <Calendar.Root\n          onVisibleDateChange={onVisibleDateChange}\n          visibleDate={adapter.date('2025-02-05T12:01:02.003Z', 'default')}\n        >\n          <Calendar.DecrementMonth />\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n\n      await user.click(button);\n      expect(onVisibleDateChange.mock.calls.length).toBe(1);\n      expect(onVisibleDateChange.mock.calls[0][0]).toEqualDateTime('2025-01-05T12:01:02.003Z');\n    });\n\n    it(\"should call onVisibleDateChange with reason 'month-change' when clicked\", async () => {\n      const onVisibleDateChange = vi.fn();\n\n      const { user } = render(\n        <Calendar.Root\n          onVisibleDateChange={onVisibleDateChange}\n          visibleDate={adapter.date('2025-02-05', 'default')}\n        >\n          <Calendar.DecrementMonth />\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n\n      await user.click(button);\n      expect(onVisibleDateChange.mock.calls.length).toBe(1);\n      expect(onVisibleDateChange.mock.calls[0][1].reason).toBe('month-change');\n    });\n  });\n\n  describe('disabled state', () => {\n    it('should be disabled when props.disabled is true', () => {\n      render(\n        <Calendar.Root>\n          <Calendar.DecrementMonth disabled />\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      expect(button).not.toHaveAttribute('disabled');\n      expect(button).toHaveAttribute('data-disabled');\n      expect(button).toHaveAttribute('aria-disabled', 'true');\n    });\n\n    it('should be disabled when the calendar is disabled', () => {\n      render(\n        <Calendar.Root disabled>\n          <Calendar.DecrementMonth />\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      expect(button).not.toHaveAttribute('disabled');\n      expect(button).toHaveAttribute('data-disabled');\n      expect(button).toHaveAttribute('aria-disabled', 'true');\n    });\n\n    it('should be disabled when the target month is before the minDate month', () => {\n      render(\n        <Calendar.Root\n          minDate={adapter.date('2025-01-10', 'default')}\n          visibleDate={adapter.date('2025-01-01', 'default')}\n        >\n          <Calendar.DecrementMonth />\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      expect(button).not.toHaveAttribute('disabled');\n      expect(button).toHaveAttribute('data-disabled');\n      expect(button).toHaveAttribute('aria-disabled', 'true');\n    });\n\n    it('should not be disabled when the target month is equal to the minDate month', () => {\n      render(\n        <Calendar.Root\n          minDate={adapter.date('2025-01-10', 'default')}\n          visibleDate={adapter.date('2025-02-01', 'default')}\n        >\n          <Calendar.DecrementMonth />\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      expect(button).not.toHaveAttribute('disabled');\n      expect(button).not.toHaveAttribute('data-disabled');\n    });\n\n    it('should not be disabled when disabled is false and the target month is before the minDate month', () => {\n      render(\n        <Calendar.Root\n          minDate={adapter.date('2025-01-10', 'default')}\n          visibleDate={adapter.date('2025-01-01', 'default')}\n        >\n          <Calendar.DecrementMonth disabled={false} />\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      expect(button).not.toHaveAttribute('disabled');\n      expect(button).toHaveAttribute('aria-disabled', 'false');\n    });\n\n    it('should navigate to the previous month when disabled is false even if the target month is before the minDate month', async () => {\n      const onVisibleDateChange = vi.fn();\n\n      const { user } = render(\n        <Calendar.Root\n          minDate={adapter.date('2025-01-10', 'default')}\n          visibleDate={adapter.date('2025-01-01', 'default')}\n          onVisibleDateChange={onVisibleDateChange}\n        >\n          <Calendar.DecrementMonth disabled={false} />\n        </Calendar.Root>,\n      );\n\n      await user.click(screen.getByRole('button'));\n      expect(onVisibleDateChange.mock.calls.length).toBe(1);\n    });\n  });\n\n  describe('press and hold', () => {\n    const { render: renderWithClock, clock } = createRenderer();\n    clock.withFakeTimers();\n\n    function renderCalendar(\n      props: Record<string, any> = {},\n      buttonProps: Record<string, any> = {},\n    ) {\n      return renderWithClock(\n        <LocalizationProvider>\n          <Calendar.Root defaultVisibleDate={adapter.date('2025-06-15', 'default')} {...props}>\n            <Calendar.DecrementMonth data-testid=\"decrement\" {...buttonProps} />\n          </Calendar.Root>\n        </LocalizationProvider>,\n      );\n    }\n\n    it('should navigate continuously when holding pointerdown', async () => {\n      const onVisibleDateChange = vi.fn();\n      await renderCalendar({ onVisibleDateChange });\n\n      const button = screen.getByTestId('decrement');\n\n      fireEvent.pointerDown(button);\n\n      // First tick fires immediately\n      expect(onVisibleDateChange.mock.calls.length).toBe(1);\n\n      // Wait for initial delay (400ms)\n      clock.tick(400);\n\n      // Continuous ticks at 100ms intervals\n      clock.tick(100);\n      expect(onVisibleDateChange.mock.calls.length).toBe(2);\n\n      clock.tick(100);\n      expect(onVisibleDateChange.mock.calls.length).toBe(3);\n\n      clock.tick(100);\n      expect(onVisibleDateChange.mock.calls.length).toBe(4);\n\n      fireEvent.pointerUp(button);\n\n      // Should stop after pointer up\n      clock.tick(100);\n      expect(onVisibleDateChange.mock.calls.length).toBe(4);\n    });\n\n    it(\"should call onVisibleDateChange with reason 'month-change' when holding pointerdown\", async () => {\n      const onVisibleDateChange = vi.fn();\n      await renderCalendar({ onVisibleDateChange });\n\n      const button = screen.getByTestId('decrement');\n\n      fireEvent.pointerDown(button);\n\n      expect(onVisibleDateChange.mock.calls.length).toBe(1);\n      expect(onVisibleDateChange.mock.calls[0][1].reason).toBe('month-change');\n\n      fireEvent.pointerUp(button);\n    });\n\n    it('should stop at minDate boundary', async () => {\n      const onVisibleDateChange = vi.fn();\n      await renderCalendar({\n        defaultVisibleDate: adapter.date('2025-03-15', 'default'),\n        minDate: adapter.date('2025-01-01', 'default'),\n        onVisibleDateChange,\n      });\n\n      const button = screen.getByTestId('decrement');\n\n      fireEvent.pointerDown(button);\n\n      // First tick: Mar -> Feb\n      expect(onVisibleDateChange.mock.calls.length).toBe(1);\n\n      clock.tick(400);\n\n      // Second tick: Feb -> Jan\n      clock.tick(100);\n      expect(onVisibleDateChange.mock.calls.length).toBe(2);\n\n      // Third tick would go to Dec 2024 which is before minDate, should stop\n      clock.tick(100);\n      expect(onVisibleDateChange.mock.calls.length).toBe(2);\n\n      fireEvent.pointerUp(button);\n    });\n\n    it('should continue navigating past the minDate boundary when disabled is false', async () => {\n      const onVisibleDateChange = vi.fn();\n      await renderCalendar(\n        {\n          defaultVisibleDate: adapter.date('2025-03-15', 'default'),\n          minDate: adapter.date('2025-01-01', 'default'),\n          onVisibleDateChange,\n        },\n        { disabled: false },\n      );\n\n      const button = screen.getByRole('button', { name: /previous month/i });\n\n      fireEvent.pointerDown(button);\n\n      // First tick: Mar → Feb\n      expect(onVisibleDateChange.mock.calls.length).toBe(1);\n\n      clock.tick(400);\n\n      // Second tick: Feb → Jan\n      clock.tick(100);\n      expect(onVisibleDateChange.mock.calls.length).toBe(2);\n\n      // Third tick: Jan → Dec 2024 — would stop at boundary without the fix\n      clock.tick(100);\n      expect(onVisibleDateChange.mock.calls.length).toBe(3);\n\n      fireEvent.pointerUp(button);\n    });\n\n    it('should use the controlled visibleDate for each tick', async () => {\n      const onVisibleDateChange = vi.fn();\n\n      function ControlledCalendar() {\n        const [visibleDate, setVisibleDate] = React.useState(() =>\n          adapter.date('2025-06-15', 'default'),\n        );\n        return (\n          <LocalizationProvider>\n            <Calendar.Root\n              visibleDate={visibleDate}\n              onVisibleDateChange={(date, details) => {\n                setVisibleDate(date);\n                onVisibleDateChange(date, details);\n              }}\n            >\n              <Calendar.DecrementMonth data-testid=\"decrement\" />\n            </Calendar.Root>\n          </LocalizationProvider>\n        );\n      }\n\n      await renderWithClock(<ControlledCalendar />);\n      const button = screen.getByTestId('decrement');\n\n      fireEvent.pointerDown(button);\n\n      // First tick fires immediately: Jun -> May\n      expect(onVisibleDateChange.mock.calls.length).toBe(1);\n      expect(onVisibleDateChange.mock.calls[0][0]).toEqualDateTime('2025-05-15');\n\n      clock.tick(400);\n\n      // Second tick: May -> Apr\n      clock.tick(100);\n      expect(onVisibleDateChange.mock.calls.length).toBe(2);\n      expect(onVisibleDateChange.mock.calls[1][0]).toEqualDateTime('2025-04-15');\n\n      // Third tick: Apr -> Mar\n      clock.tick(100);\n      expect(onVisibleDateChange.mock.calls.length).toBe(3);\n      expect(onVisibleDateChange.mock.calls[2][0]).toEqualDateTime('2025-03-15');\n\n      fireEvent.pointerUp(button);\n    });\n\n    describe('touch (Android)', () => {\n      it('should navigate once on a quick touch tap', async () => {\n        const onVisibleDateChange = vi.fn();\n        await renderCalendar({ onVisibleDateChange });\n\n        const button = screen.getByTestId('decrement');\n\n        // Simulate a quick touch tap: pointerdown then immediate pointerup\n        fireEvent.pointerDown(button, { pointerType: 'touch' });\n        fireEvent.pointerUp(button, { pointerType: 'touch' });\n\n        // Quick tap: auto-change should not have started (released before 50ms)\n        expect(onVisibleDateChange.mock.calls.length).toBe(0);\n\n        // The browser fires a synthesized click after touch up; this should trigger navigation\n        fireEvent.click(button, { detail: 1 });\n        expect(onVisibleDateChange.mock.calls.length).toBe(1);\n      });\n\n      it('should navigate continuously on touch hold and ignore the synthesized click after release', async () => {\n        const onVisibleDateChange = vi.fn();\n        await renderCalendar({ onVisibleDateChange });\n\n        const button = screen.getByTestId('decrement');\n\n        fireEvent.pointerDown(button, { pointerType: 'touch' });\n\n        // Wait past the TOUCH_TIMEOUT (50ms) to confirm it's an intentional hold\n        clock.tick(50);\n\n        // First tick fires immediately after intent confirmed\n        expect(onVisibleDateChange.mock.calls.length).toBe(1);\n\n        // Wait for initial delay (400ms) and one tick\n        clock.tick(400);\n        clock.tick(100);\n        expect(onVisibleDateChange.mock.calls.length).toBe(2);\n\n        fireEvent.pointerUp(button, { pointerType: 'touch' });\n\n        // The browser fires a synthesized click after touch hold; it must be suppressed\n        fireEvent.click(button, { detail: 1 });\n        expect(onVisibleDateChange.mock.calls.length).toBe(2);\n      });\n\n      it('should not start auto-change when the touch moves more than 8px (scroll gesture)', async () => {\n        const onVisibleDateChange = vi.fn();\n        await renderCalendar({ onVisibleDateChange });\n\n        const button = screen.getByTestId('decrement');\n\n        fireEvent.pointerDown(button, { pointerType: 'touch', clientX: 0, clientY: 0 });\n\n        // Move pointer by 9px — exceeds the 8px scroll threshold\n        fireEvent.pointerMove(button, { pointerType: 'touch', clientX: 9, clientY: 0 });\n\n        clock.tick(50);\n\n        // Auto-change should not have started due to scroll detection\n        expect(onVisibleDateChange.mock.calls.length).toBe(0);\n\n        fireEvent.pointerUp(button, { pointerType: 'touch' });\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/calendar/decrement-month/CalendarDecrementMonth.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStore } from '@base-ui/utils/store';\nimport { useSharedCalendarRootContext } from '../root/SharedCalendarRootContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { BaseUIComponentProps, NativeButtonProps } from '../../utils/types';\nimport { useButton } from '../../use-button';\nimport { useTemporalAdapter } from '../../temporal-adapter-provider/TemporalAdapterContext';\nimport { selectors } from '../store';\nimport { useCalendarMonthButton } from '../utils/useCalendarMonthButton';\nimport { REASONS } from '../../utils/reasons';\n\n/**\n * Displays an element to navigate to the previous month in the calendar.\n * Renders a `<button>` element.\n *\n * Documentation: [Base UI Calendar](https://base-ui.com/react/components/calendar)\n */\n\nexport const CalendarDecrementMonth = React.forwardRef(function CalendarDecrementMonth(\n  componentProps: CalendarDecrementMonth.Props,\n  forwardedRef: React.ForwardedRef<HTMLButtonElement>,\n) {\n  const {\n    className,\n    render,\n    nativeButton,\n    disabled: disabledProp,\n    ...elementProps\n  } = componentProps;\n\n  const store = useSharedCalendarRootContext();\n  const adapter = useTemporalAdapter();\n  const monthPageSize = useStore(store, selectors.monthPageSize);\n  const visibleDate = useStore(store, selectors.visibleDate);\n\n  const targetDate = React.useMemo(\n    () => adapter.addMonths(visibleDate, -monthPageSize),\n    [visibleDate, monthPageSize, adapter],\n  );\n\n  const isDisabled = useStore(store, selectors.isSetMonthButtonDisabled, targetDate, disabledProp);\n\n  const { getButtonProps, buttonRef } = useButton({\n    disabled: isDisabled,\n    native: nativeButton,\n    focusableWhenDisabled: true,\n  });\n\n  const { pointerHandlers, autoChangeButtonRef, shouldSkipClick } = useCalendarMonthButton({\n    direction: -1,\n    disabled: isDisabled,\n    disabledProp,\n    store,\n    adapter,\n    monthPageSize,\n  });\n\n  const state: CalendarDecrementMonth.State = React.useMemo(\n    () => ({ disabled: isDisabled }),\n    [isDisabled],\n  );\n\n  const element = useRenderElement('button', componentProps, {\n    state,\n    ref: [buttonRef, autoChangeButtonRef, forwardedRef],\n    props: [\n      {\n        tabIndex: 0,\n        'aria-label': monthPageSize > 1 ? 'Previous months' : 'Previous month',\n        onClick(event) {\n          if (isDisabled || shouldSkipClick(event)) {\n            return;\n          }\n          store.setVisibleDate(\n            targetDate,\n            event.nativeEvent,\n            event.currentTarget as HTMLElement,\n            REASONS.monthChange,\n          );\n        },\n        ...pointerHandlers,\n      },\n      elementProps,\n      getButtonProps,\n    ],\n  });\n\n  return element;\n});\n\nexport interface CalendarDecrementMonthState {\n  /**\n   * Whether the button is disabled.\n   */\n  disabled: boolean;\n}\n\nexport interface CalendarDecrementMonthProps\n  extends BaseUIComponentProps<'button', CalendarDecrementMonthState>, NativeButtonProps {}\n\nexport namespace CalendarDecrementMonth {\n  export type State = CalendarDecrementMonthState;\n  export type Props = CalendarDecrementMonthProps;\n}\n"
  },
  {
    "path": "packages/react/src/calendar/decrement-month/CalendarDecrementMonthDataAttributes.ts",
    "content": "export enum CalendarDecrementMonthDataAttributes {\n  /**\n   * Present when the button is disabled.\n   */\n  disabled = 'data-disabled',\n}\n"
  },
  {
    "path": "packages/react/src/calendar/increment-month/CalendarIncrementMonth.test.tsx",
    "content": "import * as React from 'react';\nimport { screen, fireEvent } from '@mui/internal-test-utils';\nimport { expect, vi } from 'vitest';\nimport { Calendar } from '@base-ui/react/calendar';\nimport { LocalizationProvider } from '@base-ui/react/localization-provider';\nimport { createRenderer, createTemporalRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Calendar.IncrementMonth />', () => {\n  const { render, adapter } = createTemporalRenderer();\n\n  describeConformance(<Calendar.IncrementMonth />, () => ({\n    refInstanceof: window.HTMLButtonElement,\n    button: true,\n    render(node) {\n      return render(<Calendar.Root>{node}</Calendar.Root>);\n    },\n  }));\n\n  describe('visible date update', () => {\n    it('should keep the day and time of the previous visible date when clicked', async () => {\n      const onVisibleDateChange = vi.fn();\n\n      const { user } = render(\n        <Calendar.Root\n          onVisibleDateChange={onVisibleDateChange}\n          visibleDate={adapter.date('2025-02-05T12:01:02.003Z', 'default')}\n        >\n          <Calendar.IncrementMonth />\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n\n      await user.click(button);\n      expect(onVisibleDateChange.mock.calls.length).toBe(1);\n      expect(onVisibleDateChange.mock.calls[0][0]).toEqualDateTime('2025-03-05T12:01:02.003Z');\n    });\n\n    it(\"should call onVisibleDateChange with reason 'month-change' when clicked\", async () => {\n      const onVisibleDateChange = vi.fn();\n\n      const { user } = render(\n        <Calendar.Root\n          onVisibleDateChange={onVisibleDateChange}\n          visibleDate={adapter.date('2025-02-05', 'default')}\n        >\n          <Calendar.IncrementMonth />\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n\n      await user.click(button);\n      expect(onVisibleDateChange.mock.calls.length).toBe(1);\n      expect(onVisibleDateChange.mock.calls[0][1].reason).toBe('month-change');\n    });\n  });\n\n  describe('disabled state', () => {\n    it('should be disabled when props.disabled is true', () => {\n      render(\n        <Calendar.Root>\n          <Calendar.IncrementMonth disabled />\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      expect(button).not.toHaveAttribute('disabled');\n      expect(button).toHaveAttribute('data-disabled');\n      expect(button).toHaveAttribute('aria-disabled', 'true');\n    });\n\n    it('should be disabled when the Calendar is disabled', () => {\n      render(\n        <Calendar.Root disabled>\n          <Calendar.IncrementMonth />\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      expect(button).not.toHaveAttribute('disabled');\n      expect(button).toHaveAttribute('data-disabled');\n      expect(button).toHaveAttribute('aria-disabled', 'true');\n    });\n\n    it('should be disabled when the target month is after the maxDate month', () => {\n      render(\n        <Calendar.Root\n          maxDate={adapter.date('2025-01-10', 'default')}\n          visibleDate={adapter.date('2025-01-01', 'default')}\n        >\n          <Calendar.IncrementMonth />\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      expect(button).not.toHaveAttribute('disabled');\n      expect(button).toHaveAttribute('data-disabled');\n      expect(button).toHaveAttribute('aria-disabled', 'true');\n    });\n\n    it('should not be disabled when the target month is equal to the maxDate month', () => {\n      render(\n        <Calendar.Root\n          maxDate={adapter.date('2025-01-10', 'default')}\n          visibleDate={adapter.date('2024-12-01', 'default')}\n        >\n          <Calendar.IncrementMonth />\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      expect(button).not.toHaveAttribute('disabled');\n      expect(button).not.toHaveAttribute('data-disabled');\n    });\n\n    it('should not be disabled when disabled is false and the target month is after the maxDate month', () => {\n      render(\n        <Calendar.Root\n          maxDate={adapter.date('2025-01-10', 'default')}\n          visibleDate={adapter.date('2025-01-01', 'default')}\n        >\n          <Calendar.IncrementMonth disabled={false} />\n        </Calendar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      expect(button).not.toHaveAttribute('disabled');\n      expect(button).not.toHaveAttribute('data-disabled');\n    });\n\n    it('should navigate to the next month when disabled is false even if the target month is after the maxDate month', async () => {\n      const onVisibleDateChange = vi.fn();\n\n      const { user } = render(\n        <Calendar.Root\n          maxDate={adapter.date('2025-01-10', 'default')}\n          visibleDate={adapter.date('2025-01-01', 'default')}\n          onVisibleDateChange={onVisibleDateChange}\n        >\n          <Calendar.IncrementMonth disabled={false} />\n        </Calendar.Root>,\n      );\n\n      await user.click(screen.getByRole('button'));\n      expect(onVisibleDateChange.mock.calls.length).toBe(1);\n    });\n  });\n\n  describe('press and hold', () => {\n    const { render: renderWithClock, clock } = createRenderer();\n    clock.withFakeTimers();\n\n    function renderCalendar(props: Record<string, any> = {}) {\n      return renderWithClock(\n        <LocalizationProvider>\n          <Calendar.Root defaultVisibleDate={adapter.date('2025-01-15', 'default')} {...props}>\n            <Calendar.IncrementMonth data-testid=\"increment\" />\n          </Calendar.Root>\n        </LocalizationProvider>,\n      );\n    }\n\n    it('should navigate continuously when holding pointerdown', async () => {\n      const onVisibleDateChange = vi.fn();\n      await renderCalendar({ onVisibleDateChange });\n\n      const button = screen.getByTestId('increment');\n\n      fireEvent.pointerDown(button);\n\n      // First tick fires immediately\n      expect(onVisibleDateChange.mock.calls.length).toBe(1);\n\n      // Wait for initial delay (400ms)\n      clock.tick(400);\n\n      // Continuous ticks at 100ms intervals\n      clock.tick(100);\n      expect(onVisibleDateChange.mock.calls.length).toBe(2);\n\n      clock.tick(100);\n      expect(onVisibleDateChange.mock.calls.length).toBe(3);\n\n      clock.tick(100);\n      expect(onVisibleDateChange.mock.calls.length).toBe(4);\n\n      fireEvent.pointerUp(button);\n\n      // Should stop after pointer up\n      clock.tick(100);\n      expect(onVisibleDateChange.mock.calls.length).toBe(4);\n    });\n\n    it(\"should call onVisibleDateChange with reason 'month-change' when holding pointerdown\", async () => {\n      const onVisibleDateChange = vi.fn();\n      await renderCalendar({ onVisibleDateChange });\n\n      const button = screen.getByTestId('increment');\n\n      fireEvent.pointerDown(button);\n\n      expect(onVisibleDateChange.mock.calls.length).toBe(1);\n      expect(onVisibleDateChange.mock.calls[0][1].reason).toBe('month-change');\n\n      fireEvent.pointerUp(button);\n    });\n\n    it('should stop at maxDate boundary', async () => {\n      const onVisibleDateChange = vi.fn();\n      await renderCalendar({\n        defaultVisibleDate: adapter.date('2025-01-15', 'default'),\n        maxDate: adapter.date('2025-03-31', 'default'),\n        onVisibleDateChange,\n      });\n\n      const button = screen.getByTestId('increment');\n\n      fireEvent.pointerDown(button);\n\n      // First tick: Jan -> Feb\n      expect(onVisibleDateChange.mock.calls.length).toBe(1);\n\n      clock.tick(400);\n\n      // Second tick: Feb -> Mar\n      clock.tick(100);\n      expect(onVisibleDateChange.mock.calls.length).toBe(2);\n\n      // Third tick would go to Apr which is after maxDate, should stop\n      clock.tick(100);\n      expect(onVisibleDateChange.mock.calls.length).toBe(2);\n\n      fireEvent.pointerUp(button);\n    });\n\n    it('should continue navigating past the maxDate boundary when disabled is false', async () => {\n      const onVisibleDateChange = vi.fn();\n      await renderWithClock(\n        <LocalizationProvider>\n          <Calendar.Root\n            defaultVisibleDate={adapter.date('2025-01-15', 'default')}\n            maxDate={adapter.date('2025-03-31', 'default')}\n            onVisibleDateChange={onVisibleDateChange}\n          >\n            <Calendar.IncrementMonth data-testid=\"increment\" disabled={false} />\n          </Calendar.Root>\n        </LocalizationProvider>,\n      );\n\n      const button = screen.getByTestId('increment');\n\n      fireEvent.pointerDown(button);\n\n      // First tick: Jan → Feb\n      expect(onVisibleDateChange.mock.calls.length).toBe(1);\n\n      clock.tick(400);\n\n      // Second tick: Feb → Mar\n      clock.tick(100);\n      expect(onVisibleDateChange.mock.calls.length).toBe(2);\n\n      // Third tick: Mar → Apr — would stop at boundary without the fix\n      clock.tick(100);\n      expect(onVisibleDateChange.mock.calls.length).toBe(3);\n\n      fireEvent.pointerUp(button);\n    });\n\n    it('should use the controlled visibleDate for each tick', async () => {\n      const onVisibleDateChange = vi.fn();\n\n      function ControlledCalendar() {\n        const [visibleDate, setVisibleDate] = React.useState(() =>\n          adapter.date('2025-01-15', 'default'),\n        );\n        return (\n          <LocalizationProvider>\n            <Calendar.Root\n              visibleDate={visibleDate}\n              onVisibleDateChange={(date, details) => {\n                setVisibleDate(date);\n                onVisibleDateChange(date, details);\n              }}\n            >\n              <Calendar.IncrementMonth data-testid=\"increment\" />\n            </Calendar.Root>\n          </LocalizationProvider>\n        );\n      }\n\n      await renderWithClock(<ControlledCalendar />);\n      const button = screen.getByTestId('increment');\n\n      fireEvent.pointerDown(button);\n\n      // First tick fires immediately: Jan -> Feb\n      expect(onVisibleDateChange.mock.calls.length).toBe(1);\n      expect(onVisibleDateChange.mock.calls[0][0]).toEqualDateTime('2025-02-15');\n\n      clock.tick(400);\n\n      // Second tick: Feb -> Mar\n      clock.tick(100);\n      expect(onVisibleDateChange.mock.calls.length).toBe(2);\n      expect(onVisibleDateChange.mock.calls[1][0]).toEqualDateTime('2025-03-15');\n\n      // Third tick: Mar -> Apr\n      clock.tick(100);\n      expect(onVisibleDateChange.mock.calls.length).toBe(3);\n      expect(onVisibleDateChange.mock.calls[2][0]).toEqualDateTime('2025-04-15');\n\n      fireEvent.pointerUp(button);\n    });\n\n    describe('touch (Android)', () => {\n      it('should navigate once on a quick touch tap', async () => {\n        const onVisibleDateChange = vi.fn();\n        await renderCalendar({ onVisibleDateChange });\n\n        const button = screen.getByTestId('increment');\n\n        // Simulate a quick touch tap: pointerdown then immediate pointerup\n        fireEvent.pointerDown(button, { pointerType: 'touch' });\n        fireEvent.pointerUp(button, { pointerType: 'touch' });\n\n        // Quick tap: auto-change should not have started (released before 50ms)\n        expect(onVisibleDateChange.mock.calls.length).toBe(0);\n\n        // The browser fires a synthesized click after touch up; this should trigger navigation\n        fireEvent.click(button, { detail: 1 });\n        expect(onVisibleDateChange.mock.calls.length).toBe(1);\n      });\n\n      it('should navigate continuously on touch hold and ignore the synthesized click after release', async () => {\n        const onVisibleDateChange = vi.fn();\n        await renderCalendar({ onVisibleDateChange });\n\n        const button = screen.getByTestId('increment');\n\n        fireEvent.pointerDown(button, { pointerType: 'touch' });\n\n        // Wait past the TOUCH_TIMEOUT (50ms) to confirm it's an intentional hold\n        clock.tick(50);\n\n        // First tick fires immediately after intent confirmed\n        expect(onVisibleDateChange.mock.calls.length).toBe(1);\n\n        // Wait for initial delay (400ms) and one tick\n        clock.tick(400);\n        clock.tick(100);\n        expect(onVisibleDateChange.mock.calls.length).toBe(2);\n\n        fireEvent.pointerUp(button, { pointerType: 'touch' });\n\n        // The browser fires a synthesized click after touch hold; it must be suppressed\n        fireEvent.click(button, { detail: 1 });\n        expect(onVisibleDateChange.mock.calls.length).toBe(2);\n      });\n\n      it('should not start auto-change when the touch moves more than 8px (scroll gesture)', async () => {\n        const onVisibleDateChange = vi.fn();\n        await renderCalendar({ onVisibleDateChange });\n\n        const button = screen.getByTestId('increment');\n\n        fireEvent.pointerDown(button, { pointerType: 'touch', clientX: 0, clientY: 0 });\n\n        // Move pointer by 9px — exceeds the 8px scroll threshold\n        fireEvent.pointerMove(button, { pointerType: 'touch', clientX: 9, clientY: 0 });\n\n        clock.tick(50);\n\n        // Auto-change should not have started due to scroll detection\n        expect(onVisibleDateChange.mock.calls.length).toBe(0);\n\n        fireEvent.pointerUp(button, { pointerType: 'touch' });\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/calendar/increment-month/CalendarIncrementMonth.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStore } from '@base-ui/utils/store';\nimport { useSharedCalendarRootContext } from '../root/SharedCalendarRootContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { BaseUIComponentProps, NativeButtonProps } from '../../utils/types';\nimport { useButton } from '../../use-button';\nimport { useTemporalAdapter } from '../../temporal-adapter-provider/TemporalAdapterContext';\nimport { selectors } from '../store';\nimport { useCalendarMonthButton } from '../utils/useCalendarMonthButton';\nimport { REASONS } from '../../utils/reasons';\n\n/**\n * Displays an element to navigate to the next month in the calendar.\n * Renders a `<button>` element.\n *\n * Documentation: [Base UI Calendar](https://base-ui.com/react/components/calendar)\n */\nexport const CalendarIncrementMonth = React.forwardRef(function CalendarIncrementMonth(\n  componentProps: CalendarIncrementMonth.Props,\n  forwardedRef: React.ForwardedRef<HTMLButtonElement>,\n) {\n  const {\n    className,\n    render,\n    nativeButton,\n    disabled: disabledProp,\n    ...elementProps\n  } = componentProps;\n\n  const store = useSharedCalendarRootContext();\n  const adapter = useTemporalAdapter();\n  const monthPageSize = useStore(store, selectors.monthPageSize);\n  const visibleDate = useStore(store, selectors.visibleDate);\n\n  const targetDate = React.useMemo(\n    () => adapter.addMonths(visibleDate, monthPageSize),\n    [visibleDate, monthPageSize, adapter],\n  );\n\n  const isDisabled = useStore(store, selectors.isSetMonthButtonDisabled, targetDate, disabledProp);\n\n  const { getButtonProps, buttonRef } = useButton({\n    disabled: isDisabled,\n    native: nativeButton,\n    focusableWhenDisabled: true,\n  });\n\n  const { pointerHandlers, autoChangeButtonRef, shouldSkipClick } = useCalendarMonthButton({\n    direction: 1,\n    disabled: isDisabled,\n    disabledProp,\n    store,\n    adapter,\n    monthPageSize,\n  });\n\n  const state: CalendarIncrementMonth.State = React.useMemo(\n    () => ({ disabled: isDisabled }),\n    [isDisabled],\n  );\n\n  const element = useRenderElement('button', componentProps, {\n    state,\n    ref: [buttonRef, autoChangeButtonRef, forwardedRef],\n    props: [\n      {\n        tabIndex: 0,\n        'aria-label': monthPageSize > 1 ? 'Next months' : 'Next month',\n        onClick(event) {\n          if (isDisabled || shouldSkipClick(event)) {\n            return;\n          }\n          store.setVisibleDate(\n            targetDate,\n            event.nativeEvent,\n            event.currentTarget as HTMLElement,\n            REASONS.monthChange,\n          );\n        },\n        ...pointerHandlers,\n      },\n      elementProps,\n      getButtonProps,\n    ],\n  });\n\n  return element;\n});\n\nexport interface CalendarIncrementMonthState {\n  /**\n   * Whether the button is disabled.\n   */\n  disabled: boolean;\n}\n\nexport interface CalendarIncrementMonthProps\n  extends BaseUIComponentProps<'button', CalendarIncrementMonthState>, NativeButtonProps {}\n\nexport namespace CalendarIncrementMonth {\n  export type State = CalendarIncrementMonthState;\n  export type Props = CalendarIncrementMonthProps;\n}\n"
  },
  {
    "path": "packages/react/src/calendar/increment-month/CalendarIncrementMonthDataAttributes.ts",
    "content": "export enum CalendarIncrementMonthDataAttributes {\n  /**\n   * Present when the button is disabled.\n   */\n  disabled = 'data-disabled',\n}\n"
  },
  {
    "path": "packages/react/src/calendar/index.parts.ts",
    "content": "export { CalendarRoot as Root } from './root/CalendarRoot';\n\n// Days\nexport { CalendarDayGrid as DayGrid } from './day-grid/CalendarDayGrid';\nexport { CalendarDayGridHeader as DayGridHeader } from './day-grid-header/CalendarDayGridHeader';\nexport { CalendarDayGridHeaderRow as DayGridHeaderRow } from './day-grid-header-row/CalendarDayGridHeaderRow';\nexport { CalendarDayGridHeaderCell as DayGridHeaderCell } from './day-grid-header-cell/CalendarDayGridHeaderCell';\nexport { CalendarDayGridBody as DayGridBody } from './day-grid-body/CalendarDayGridBody';\nexport { CalendarDayGridRow as DayGridRow } from './day-grid-row/CalendarDayGridRow';\nexport { CalendarDayGridCell as DayGridCell } from './day-grid-cell/CalendarDayGridCell';\nexport { CalendarDayButton as DayButton } from './day-button/CalendarDayButton';\n\n// Navigation\nexport { CalendarDecrementMonth as DecrementMonth } from './decrement-month/CalendarDecrementMonth';\nexport { CalendarIncrementMonth as IncrementMonth } from './increment-month/CalendarIncrementMonth';\n\n// Context\nexport { useCalendarContext as useContext } from './use-context/CalendarContext';\nexport { useCalendarWeekList as useWeekList } from './use-week-list/useCalendarWeekList';\nexport { useCalendarDayList as useDayList } from './use-day-list/useCalendarDayList';\n\nexport { CalendarViewport as Viewport } from './viewport/CalendarViewport';\n"
  },
  {
    "path": "packages/react/src/calendar/index.ts",
    "content": "export * as Calendar from './index.parts';\n\nexport type * from './root/CalendarRoot';\nexport type * from './day-grid/CalendarDayGrid';\nexport type * from './day-grid-body/CalendarDayGridBody';\nexport type * from './day-grid-cell/CalendarDayGridCell';\nexport type * from './day-grid-header/CalendarDayGridHeader';\nexport type * from './day-grid-header-cell/CalendarDayGridHeaderCell';\nexport type * from './day-grid-header-row/CalendarDayGridHeaderRow';\nexport type * from './day-grid-row/CalendarDayGridRow';\nexport type * from './day-button/CalendarDayButton';\nexport type * from './increment-month/CalendarIncrementMonth';\nexport type * from './decrement-month/CalendarDecrementMonth';\nexport type * from './viewport/CalendarViewport';\nexport type * from './use-context/CalendarContext';\n"
  },
  {
    "path": "packages/react/src/calendar/root/CalendarRoot.test.tsx",
    "content": "import * as React from 'react';\nimport { expect } from 'vitest';\nimport { screen } from '@mui/internal-test-utils';\nimport { Calendar } from '@base-ui/react/calendar';\nimport { createTemporalRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Calendar.Root />', () => {\n  const { render, adapter } = createTemporalRenderer();\n\n  describeConformance(<Calendar.Root />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render,\n  }));\n\n  describe('aria-label', () => {\n    it('should have an aria-label with only the visible month and year when no aria-label prop is provided', () => {\n      const visibleDate = adapter.date('2025-03-15', 'default');\n\n      render(<Calendar.Root data-testid=\"calendar\" visibleDate={visibleDate} />);\n\n      const calendar = screen.getByTestId('calendar');\n      expect(calendar).toHaveAttribute('aria-label', 'March 2025');\n    });\n\n    it('should prepend custom aria-label to the visible month', () => {\n      const visibleDate = adapter.date('2025-03-15', 'default');\n\n      render(\n        <Calendar.Root data-testid=\"calendar\" visibleDate={visibleDate} aria-label=\"Choose date\" />,\n      );\n\n      const calendar = screen.getByTestId('calendar');\n      expect(calendar).toHaveAttribute('aria-label', 'Choose date, March 2025');\n    });\n\n    it('should update aria-label when visible month changes', () => {\n      const initialDate = adapter.date('2025-03-15', 'default');\n\n      const { rerender } = render(\n        <Calendar.Root data-testid=\"calendar\" visibleDate={initialDate} />,\n      );\n\n      const calendar = screen.getByTestId('calendar');\n      expect(calendar).toHaveAttribute('aria-label', 'March 2025');\n\n      const newDate = adapter.date('2025-06-20', 'default');\n      rerender(<Calendar.Root data-testid=\"calendar\" visibleDate={newDate} />);\n\n      expect(calendar).toHaveAttribute('aria-label', 'June 2025');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/calendar/root/CalendarRoot.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStore } from '@base-ui/utils/store';\nimport { useRefWithInit } from '@base-ui/utils/useRefWithInit';\nimport { TemporalValue } from '../../types/temporal';\nimport { SharedCalendarRootContext } from './SharedCalendarRootContext';\nimport { getDateManager } from '../../utils/temporal/getDateManager';\nimport { CalendarContext } from '../use-context/CalendarContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { formatMonthFullLetterAndYear } from '../../utils/temporal/date-helpers';\nimport { useTemporalAdapter } from '../../temporal-adapter-provider/TemporalAdapterContext';\nimport { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport {\n  CalendarNavigationDirection,\n  CalendarValueChangeHandlerContext,\n  selectors,\n  CalendarRootElementState,\n  ValueManager,\n  SharedCalendarStore,\n  SharedCalendarStoreParameters,\n  CalendarChangeEventReason,\n  CalendarValueChangeEventDetails,\n  CalendarVisibleDateChangeEventDetails,\n} from '../store';\nimport { CalendarRootDataAttributes } from './CalendarRootDataAttributes';\nimport { ValidateDateReturnValue } from '../../utils/temporal/validateDate';\n\nconst stateAttributesMapping: StateAttributesMapping<CalendarRoot.State> = {\n  navigationDirection: (direction) => {\n    if (direction === 'none') {\n      return null;\n    }\n    return {\n      [CalendarRootDataAttributes.navigationDirection]: direction,\n    };\n  },\n};\n\nexport const calendarValueManager: ValueManager<TemporalValue> = {\n  getDateToUseForReferenceDate: (value) => value,\n  onSelectDate: ({ setValue, selectedDate }) => setValue(selectedDate),\n  getActiveDateFromValue: (value) => value,\n};\n\n/**\n * Groups all parts of the calendar.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Calendar](https://base-ui.com/react/components/calendar)\n */\nexport const CalendarRoot = React.forwardRef(function CalendarRoot(\n  componentProps: CalendarRoot.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const {\n    // Rendering props\n    className,\n    render,\n    // Form props\n    readOnly,\n    disabled,\n    invalid,\n    // Focus and navigation props\n    monthPageSize,\n    // Value props\n    onValueChange,\n    defaultValue,\n    value,\n    timezone,\n    referenceDate,\n    // Visible date props\n    onVisibleDateChange,\n    visibleDate,\n    defaultVisibleDate,\n    // Children\n    children,\n    // Validation props\n    minDate,\n    maxDate,\n    isDateUnavailable,\n    // Accessibility props\n    'aria-label': ariaLabelProp,\n    // Props forwarded to the DOM element\n    ...elementProps\n  } = componentProps;\n\n  const adapter = useTemporalAdapter();\n  const manager = React.useMemo(() => getDateManager(adapter), [adapter]);\n\n  const store = useRefWithInit(\n    () =>\n      new SharedCalendarStore(\n        {\n          readOnly,\n          disabled,\n          invalid,\n          monthPageSize,\n          onValueChange,\n          defaultValue,\n          value,\n          timezone,\n          referenceDate,\n          onVisibleDateChange,\n          visibleDate,\n          defaultVisibleDate,\n          isDateUnavailable,\n          minDate,\n          maxDate,\n        },\n        adapter,\n        manager,\n        calendarValueManager,\n      ),\n  ).current;\n\n  store.useControlledProp('valueProp', value);\n  store.useControlledProp('visibleDateProp', visibleDate);\n  store.useContextCallback('onValueChange', onValueChange);\n  store.useContextCallback('onVisibleDateChange', onVisibleDateChange);\n  store.useSyncedValues({\n    adapter,\n    manager,\n    timezoneProp: timezone,\n    referenceDateProp: referenceDate ?? null,\n    minDate,\n    maxDate,\n    isDateUnavailable,\n    disabled: disabled ?? false,\n    readOnly: readOnly ?? false,\n    invalidProp: invalid,\n    monthPageSize: monthPageSize ?? 1,\n  });\n\n  const visibleMonth = useStore(store, selectors.visibleMonth);\n  const state: CalendarRoot.State = useStore(store, selectors.rootElementState);\n  const publicContext = useStore(store, selectors.publicContext);\n\n  const resolvedChildren = React.useMemo(() => {\n    if (!React.isValidElement(children) && typeof children === 'function') {\n      return children({ ...publicContext, setVisibleDate: store.setVisibleDate });\n    }\n\n    return children;\n  }, [children, publicContext, store.setVisibleDate]);\n\n  // TODO: Improve localization support (right now it doesn't work well with RTL languages)\n  const ariaLabel = React.useMemo(() => {\n    const formattedVisibleMonth = formatMonthFullLetterAndYear(adapter, visibleMonth);\n    const prefix = ariaLabelProp ? `${ariaLabelProp}, ` : '';\n\n    return `${prefix}${formattedVisibleMonth}`;\n  }, [adapter, ariaLabelProp, visibleMonth]);\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: forwardedRef,\n    props: [{ children: resolvedChildren, 'aria-label': ariaLabel }, elementProps],\n    stateAttributesMapping,\n  });\n\n  return (\n    <SharedCalendarRootContext.Provider value={store}>{element}</SharedCalendarRootContext.Provider>\n  );\n});\n\nexport type CalendarRootNavigationDirection = CalendarNavigationDirection;\n\nexport interface CalendarRootState extends CalendarRootElementState {}\n\nexport interface CalendarRootProps\n  extends\n    Omit<BaseUIComponentProps<'div', CalendarRootState>, 'children'>,\n    SharedCalendarStoreParameters<TemporalValue, ValidateDateReturnValue> {\n  /**\n   * The children of the component.\n   * If a function is provided, it will be called with the public context as its parameter.\n   */\n  children?: React.ReactNode | ((parameters: CalendarContext) => React.ReactNode);\n}\n\nexport interface CalendarRootValueChangeHandlerContext extends CalendarValueChangeHandlerContext<ValidateDateReturnValue> {}\n\nexport type CalendarRootChangeEventReason = CalendarChangeEventReason;\n\nexport type CalendarRootValueChangeEventDetails =\n  CalendarValueChangeEventDetails<ValidateDateReturnValue>;\n\nexport type CalendarRootVisibleDateChangeEventDetails = CalendarVisibleDateChangeEventDetails;\n\nexport namespace CalendarRoot {\n  export type NavigationDirection = CalendarRootNavigationDirection;\n  export type State = CalendarRootState;\n  export type Props = CalendarRootProps;\n  export type ValueChangeHandlerContext = CalendarRootValueChangeHandlerContext;\n  export type ChangeEventReason = CalendarRootChangeEventReason;\n  export type ValueChangeEventDetails = CalendarRootValueChangeEventDetails;\n  export type VisibleDateChangeEventDetails = CalendarRootVisibleDateChangeEventDetails;\n}\n"
  },
  {
    "path": "packages/react/src/calendar/root/CalendarRootDataAttributes.ts",
    "content": "export enum CalendarRootDataAttributes {\n  /**\n   * Present when the current value is empty.\n   */\n  empty = 'data-empty',\n  /**\n   * Present when the current value is invalid (fails validation).\n   */\n  invalid = 'data-invalid',\n  /**\n   * Present when the calendar is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the calendar is readonly.\n   */\n  readonly = 'data-readonly',\n  /**\n   * Indicates the direction of the navigation (based on the month navigating to).\n   * @type {'previous' | 'next' | 'none'}\n   */\n  navigationDirection = 'data-navigation-direction',\n}\n"
  },
  {
    "path": "packages/react/src/calendar/root/SharedCalendarRootContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { SharedCalendarStore } from '../store';\n\nexport type SharedCalendarRootContext = SharedCalendarStore<any, any>;\n\nexport const SharedCalendarRootContext = React.createContext<SharedCalendarRootContext | undefined>(\n  undefined,\n);\n\nexport function useSharedCalendarRootContext() {\n  const context = React.useContext(SharedCalendarRootContext);\n  if (context === undefined) {\n    throw new Error(\n      'Base UI: SharedCalendarRootContext is missing. Calendar parts must be placed within <Calendar.Root /> and Range Calendar parts must be placed within <RangeCalendar.Root />.',\n    );\n  }\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/calendar/store/SharedCalendarState.ts",
    "content": "import { TemporalManager } from '../../utils/temporal/types';\nimport {\n  TemporalAdapter,\n  TemporalSupportedObject,\n  TemporalSupportedValue,\n  TemporalTimezone,\n} from '../../types/temporal';\n\nexport type CalendarNavigationDirection = 'previous' | 'next' | 'none';\n\nexport interface SharedCalendarState<TValue extends TemporalSupportedValue = any> {\n  /**\n   * The value of the calendar, as passed to `props.value` or `props.defaultValue`.\n   */\n  value: TValue;\n  /**\n   * The controlled value prop.\n   * `undefined` means the value is uncontrolled.\n   */\n  readonly valueProp: TValue | undefined;\n  /**\n   * The date that is currently visible in the calendar.\n   */\n  visibleDate: TemporalSupportedObject;\n  /**\n   * The controlled visible date prop.\n   * `undefined` means the visible date is uncontrolled.\n   */\n  readonly visibleDateProp: TemporalSupportedObject | undefined;\n  /**\n   * The direction the calendar is currently navigating in.\n   */\n  navigationDirection: CalendarNavigationDirection;\n  /**\n   * The initial date used to generate the reference date if any.\n   */\n  initialReferenceDateFromValue: TemporalSupportedObject | null;\n  /**\n   * The timezone as passed to `props.timezone`.\n   */\n  timezoneProp: TemporalTimezone | undefined;\n  /**\n   * The reference date as passed to `props.referenceDate`.\n   */\n  referenceDateProp: TemporalSupportedObject | null;\n  /**\n   * Whether the calendar is disabled.\n   */\n  disabled: boolean;\n  /**\n   * Whether the calendar is readonly.\n   */\n  readOnly: boolean;\n  /**\n   * Whether the calendar is forcefully marked as invalid.\n   */\n  invalidProp: boolean | undefined;\n  /**\n   * Minimal selectable date.\n   */\n  minDate: TemporalSupportedObject | undefined;\n  /**\n   * Maximal selectable date.\n   */\n  maxDate: TemporalSupportedObject | undefined;\n  /**\n   * Mark specific dates as unavailable.\n   * Those dates will not be selectable but they will still be focusable with the keyboard.\n   */\n  isDateUnavailable: ((day: TemporalSupportedObject) => boolean) | undefined;\n  /**\n   * The amount of months to navigate by when pressing `<Calendar.IncrementMonth>`, `<Calendar.DecrementMonth>`.\n   */\n  monthPageSize: number;\n  /**\n   * The manager of the calendar (uses `getDateManager` for Calendar and `getDateRangeManager` for RangeCalendar).\n   * Not publicly exposed, is only set in state to avoid passing it to the selectors.\n   */\n  manager: TemporalManager<TValue, any, any>;\n  /**\n   * The adapter of the date library.\n   * Not publicly exposed, is only set in state to avoid passing it to the selectors.\n   */\n  adapter: TemporalAdapter;\n}\n"
  },
  {
    "path": "packages/react/src/calendar/store/SharedCalendarStore.ts",
    "content": "import { ReactStore } from '@base-ui/utils/store';\nimport {\n  TemporalSupportedObject,\n  TemporalSupportedValue,\n  TemporalAdapter,\n} from '../../types/temporal';\nimport { ValidateDateValidationProps } from '../../utils/temporal/validateDate';\nimport { getInitialReferenceDate } from '../../utils/temporal/getInitialReferenceDate';\nimport { TemporalManager, TemporalTimezoneProps } from '../../utils/temporal/types';\nimport {\n  BaseUIChangeEventDetails,\n  createChangeEventDetails,\n} from '../../utils/createBaseUIEventDetails';\nimport { mergeDateAndTime } from '../../utils/temporal/date-helpers';\nimport { CalendarNavigationDirection, SharedCalendarState as State } from './SharedCalendarState';\nimport { selectors } from './selectors';\nimport { BaseUIEventReasons, REASONS } from '../../utils/reasons';\n\nexport interface SharedCalendarStoreContext<TValue extends TemporalSupportedValue, TError> {\n  onValueChange?:\n    | ((value: TValue, eventDetails: CalendarValueChangeEventDetails<TError>) => void)\n    | undefined;\n  onVisibleDateChange?:\n    | ((\n        visibleDate: TemporalSupportedObject,\n        eventDetails: CalendarVisibleDateChangeEventDetails,\n      ) => void)\n    | undefined;\n}\n\n/**\n * Store managing the state of the Calendar and the Range Calendar components.\n */\nexport class SharedCalendarStore<TValue extends TemporalSupportedValue, TError> extends ReactStore<\n  State<TValue>,\n  SharedCalendarStoreContext<TValue, TError>\n> {\n  private valueManager: ValueManager<TValue>;\n\n  constructor(\n    parameters: SharedCalendarStoreParameters<TValue, TError>,\n    adapter: TemporalAdapter,\n    manager: TemporalManager<TValue, TError, any>,\n    valueManager: ValueManager<TValue>,\n  ) {\n    const value = parameters.value ?? parameters.defaultValue ?? manager.emptyValue;\n    const initialReferenceDateFromValue = valueManager.getDateToUseForReferenceDate(value);\n\n    let initialVisibleDate: TemporalSupportedObject;\n    if (parameters.visibleDate) {\n      initialVisibleDate = parameters.visibleDate;\n    } else if (parameters.defaultVisibleDate) {\n      initialVisibleDate = parameters.defaultVisibleDate;\n    } else {\n      initialVisibleDate = getInitialReferenceDate({\n        adapter,\n        timezone: parameters.timezone ?? 'default',\n        validationProps: { minDate: parameters.minDate, maxDate: parameters.maxDate },\n        externalReferenceDate: parameters.referenceDate ?? null,\n        externalDate: initialReferenceDateFromValue,\n      });\n    }\n\n    super(\n      {\n        adapter,\n        manager,\n        timezoneProp: parameters.timezone,\n        referenceDateProp: parameters.referenceDate ?? null,\n        minDate: parameters.minDate,\n        maxDate: parameters.maxDate,\n        isDateUnavailable: parameters.isDateUnavailable,\n        disabled: parameters.disabled ?? false,\n        readOnly: parameters.readOnly ?? false,\n        invalidProp: parameters.invalid,\n        monthPageSize: parameters.monthPageSize ?? 1,\n        visibleDate: initialVisibleDate,\n        visibleDateProp: parameters.visibleDate,\n        initialReferenceDateFromValue,\n        value,\n        valueProp: parameters.value,\n        navigationDirection: 'none',\n      },\n      {\n        onValueChange: parameters.onValueChange,\n        onVisibleDateChange: parameters.onVisibleDateChange,\n      },\n    );\n\n    this.valueManager = valueManager;\n\n    // When the controlled value prop changes, sync the internal value\n    // and auto-update visibleDate to the active date.\n    this.observe(\n      (state) => state.valueProp,\n      (newValueProp, oldValueProp) => {\n        if (\n          newValueProp !== undefined &&\n          oldValueProp !== undefined &&\n          !this.state.adapter.isEqual(newValueProp, oldValueProp)\n        ) {\n          this.set('value', newValueProp);\n          const visibleDate = this.valueManager.getActiveDateFromValue(newValueProp);\n          if (\n            this.state.adapter.isValid(visibleDate) &&\n            !this.state.adapter.isSameMonth(visibleDate, this.state.visibleDate)\n          ) {\n            this.setVisibleDate(visibleDate, undefined, undefined, REASONS.valuePropChange);\n          }\n        }\n      },\n    );\n\n    // When the controlled visible date prop changes, update the navigation direction.\n    this.observe(\n      (state) => state.visibleDateProp,\n      (newVisibleDateProp, oldVisibleDateProp) => {\n        if (\n          newVisibleDateProp !== undefined &&\n          oldVisibleDateProp !== undefined &&\n          !this.state.adapter.isEqual(newVisibleDateProp, oldVisibleDateProp)\n        ) {\n          this.set(\n            'navigationDirection',\n            this.getNavigationDirectionFromVisibleDateChange(\n              newVisibleDateProp,\n              oldVisibleDateProp,\n            ),\n          );\n        }\n      },\n    );\n  }\n\n  /**\n   * Sets the visible date.\n   */\n  public setVisibleDate = (\n    visibleDate: TemporalSupportedObject,\n    nativeEvent?: Event,\n    trigger?: HTMLElement,\n    reason?: CalendarChangeEventReason,\n  ) => {\n    const eventDetails = createChangeEventDetails(reason ?? REASONS.dayPress, nativeEvent, trigger);\n\n    this.context.onVisibleDateChange?.(visibleDate, eventDetails);\n    if (!eventDetails.isCanceled && this.state.visibleDateProp === undefined) {\n      this.update({\n        visibleDate,\n        navigationDirection: this.getNavigationDirectionFromVisibleDateChange(\n          visibleDate,\n          this.state.visibleDate,\n        ),\n      });\n    }\n  };\n\n  /**\n   * Selects a date.\n   */\n  public selectDate = (\n    selectedDate: TemporalSupportedObject,\n    event: React.MouseEvent<HTMLButtonElement>,\n  ) => {\n    if (this.state.readOnly) {\n      return;\n    }\n\n    const referenceDate = selectors.referenceDate(this.state);\n    const activeDate = this.valueManager.getActiveDateFromValue(this.state.value) ?? referenceDate;\n    const cleanSelectedDate = mergeDateAndTime(this.state.adapter, selectedDate, activeDate);\n\n    this.valueManager.onSelectDate({\n      setValue: (newValue) => this.setValue(newValue, event),\n      prevValue: this.state.value,\n      selectedDate: cleanSelectedDate,\n      referenceDate,\n    });\n  };\n\n  /**\n   * Sets the value.\n   * Should only be used internally through `selectDate` method.\n   */\n  private setValue(newValue: TValue, event: React.MouseEvent<HTMLButtonElement>) {\n    const inputTimezone = this.state.manager.getTimezone(this.state.value);\n    const newValueWithInputTimezone =\n      inputTimezone == null ? newValue : this.state.manager.setTimezone(newValue, inputTimezone);\n\n    const eventDetails = createChangeEventDetails(\n      REASONS.dayPress,\n      event.nativeEvent,\n      event.currentTarget,\n      {\n        getValidationError: () =>\n          this.state.manager.getValidationError(\n            newValueWithInputTimezone,\n            selectors.validationProps(this.state),\n          ),\n      },\n    );\n\n    this.context.onValueChange?.(newValueWithInputTimezone, eventDetails);\n    if (!eventDetails.isCanceled && this.state.valueProp === undefined) {\n      this.set('value', newValueWithInputTimezone);\n    }\n  }\n\n  /**\n   * Determines the navigation direction based on the new and the previous visible date.\n   */\n  private getNavigationDirectionFromVisibleDateChange(\n    visibleDate: TemporalSupportedObject,\n    previousVisibleDate: TemporalSupportedObject,\n  ) {\n    const prevVisibleDateTimestamp = this.state.adapter.getTime(previousVisibleDate);\n    const visibleDateTimestamp = this.state.adapter.getTime(visibleDate);\n\n    let newNavigationDirection: CalendarNavigationDirection = 'none';\n    if (visibleDateTimestamp < prevVisibleDateTimestamp) {\n      newNavigationDirection = 'previous';\n    } else if (visibleDateTimestamp > prevVisibleDateTimestamp) {\n      newNavigationDirection = 'next';\n    }\n    return newNavigationDirection;\n  }\n}\n\nexport interface SharedCalendarStoreParameters<TValue extends TemporalSupportedValue, TError>\n  extends TemporalTimezoneProps, ValidateDateValidationProps {\n  /**\n   * The controlled value that should be selected.\n   * To render an uncontrolled (Range)Calendar, use the `defaultValue` prop instead.\n   */\n  value?: TValue | undefined;\n  /**\n   * The uncontrolled value that should be initially selected.\n   * To render a controlled (Range)Calendar, use the `value` prop instead.\n   */\n  defaultValue?: TValue | undefined;\n  /**\n   * Event handler called when the selected value changes.\n   * Provides the new value as an argument.\n   * Has `getValidationError()` in the `eventDetails` to retrieve the validation error associated to the new value.\n   */\n  onValueChange?:\n    | ((value: TValue, eventDetails: CalendarValueChangeEventDetails<TError>) => void)\n    | undefined;\n  /**\n   * Whether the component should ignore user interaction.\n   * @default false\n   */\n  disabled?: boolean | undefined;\n  /**\n   * Whether the user should be unable to select a date in the calendar.\n   * @default false\n   */\n  readOnly?: boolean | undefined;\n  /**\n   * Whether the calendar is forcefully marked as invalid.\n   * A calendar can be invalid when the selected date fails validation (i.e., is outside of the allowed `minDate` and `maxDate` range).\n   * @default false\n   */\n  invalid?: boolean | undefined;\n  /**\n   * Mark specific dates as unavailable.\n   * Those dates will not be selectable but they will still be focusable with the keyboard.\n   */\n  isDateUnavailable?: ((day: TemporalSupportedObject) => boolean) | undefined;\n  /**\n   * The date used to decide which month should be displayed in the Day Grid.\n   * To render an uncontrolled Calendar, use the `defaultVisibleDate` prop instead.\n   */\n  visibleDate?: TemporalSupportedObject | undefined;\n  /**\n   * The date used to decide which month should be initially displayed in the Day Grid.\n   * To render a controlled Calendar, use the `visibleDate` prop instead.\n   */\n  defaultVisibleDate?: TemporalSupportedObject | undefined;\n  /**\n   * Event handler called when the visible date changes.\n   * Provides the new date as an argument.\n   * Has the change reason in the `eventDetails`.\n   */\n  onVisibleDateChange?:\n    | ((\n        visibleDate: TemporalSupportedObject,\n        eventDetails: CalendarVisibleDateChangeEventDetails,\n      ) => void)\n    | undefined;\n  /**\n   * The date used to generate the new value when both `value` and `defaultValue` are empty.\n   * It can be used to:\n   * - set a desired time on the selected date;\n   * - set a desired default year or month;\n   * @default 'The closest valid date using the validation props.'\n   */\n  referenceDate?: TemporalSupportedObject | undefined;\n  /**\n   * The amount of months to move by when navigating.\n   * This is mostly useful when displaying multiple day grids.\n   * @default 1\n   */\n  monthPageSize?: number | undefined;\n}\n\nexport interface ValueManager<TValue extends TemporalSupportedValue> {\n  /**\n   * Returns the date to use for the reference date.\n   */\n  getDateToUseForReferenceDate: (value: TValue) => TemporalSupportedObject | null;\n  /**\n   * Runs logic when a date is selected.\n   * This is used to correctly update the value on the Range Calendar.\n   */\n  onSelectDate: (parameters: OnSelectDateParameters<TValue>) => void;\n  /**\n   * Returns the active date from the value.\n   * This is used to determine which date is being edited in the Range Calendar (start of end date).\n   */\n  getActiveDateFromValue: (value: TValue) => TemporalSupportedObject | null;\n}\n\nexport interface OnSelectDateParameters<TValue extends TemporalSupportedValue> {\n  setValue: (value: TValue) => void;\n  /**\n   * The value before the change.\n   */\n  prevValue: TValue;\n  /**\n   * The date to select.\n   */\n  selectedDate: TemporalSupportedObject;\n  /**\n   * The reference date.\n   */\n  referenceDate: TemporalSupportedObject;\n}\n\nexport interface CalendarValueChangeHandlerContext<TError> {\n  /**\n   * The validation error associated to the new value.\n   */\n  getValidationError: () => TError;\n}\n\nexport type CalendarChangeEventReason =\n  | BaseUIEventReasons['monthChange']\n  | BaseUIEventReasons['valuePropChange']\n  | BaseUIEventReasons['dayPress']\n  | BaseUIEventReasons['keyboard'];\n\nexport type CalendarValueChangeEventDetails<TError> = BaseUIChangeEventDetails<\n  CalendarChangeEventReason,\n  CalendarValueChangeHandlerContext<TError>\n>;\n\nexport type CalendarVisibleDateChangeEventDetails = BaseUIChangeEventDetails<\n  CalendarChangeEventReason,\n  {}\n>;\n"
  },
  {
    "path": "packages/react/src/calendar/store/index.ts",
    "content": "export * from './SharedCalendarStore';\nexport * from './SharedCalendarState';\nexport * from './selectors';\n"
  },
  {
    "path": "packages/react/src/calendar/store/selectors.ts",
    "content": "import { createSelector, createSelectorMemoized } from '@base-ui/utils/store';\nimport {\n  TemporalSupportedObject,\n  TemporalSupportedValue,\n  TemporalAdapter,\n} from '../../types/temporal';\nimport { validateDate } from '../../utils/temporal/validateDate';\nimport { getInitialReferenceDate } from '../../utils/temporal/getInitialReferenceDate';\nimport { CalendarNavigationDirection, SharedCalendarState as State } from './SharedCalendarState';\n\nconst timezoneToRenderSelector = createSelectorMemoized(\n  (state: State) => state.adapter,\n  (state: State) => state.manager,\n  (state: State) => state.value,\n  (state: State) => state.timezoneProp,\n  (state: State) => state.referenceDateProp,\n  (adapter, manager, value, timezoneProp, referenceDateProp) => {\n    if (timezoneProp != null) {\n      return timezoneProp;\n    }\n\n    const valueTimezone = manager.getTimezone(value);\n    if (valueTimezone) {\n      return valueTimezone;\n    }\n\n    if (referenceDateProp) {\n      return adapter.getTimezone(referenceDateProp);\n    }\n    return 'default';\n  },\n);\n\nconst validationPropsSelector = createSelectorMemoized(\n  (state: State) => state.minDate,\n  (state: State) => state.maxDate,\n  (minDate, maxDate) => ({ minDate, maxDate }),\n);\n\nconst referenceDateSelector = createSelectorMemoized(\n  (state: State) => state.adapter,\n  timezoneToRenderSelector,\n  (state: State) => state.initialReferenceDateFromValue,\n  validationPropsSelector,\n  (state: State) => state.referenceDateProp,\n  (adapter, timezone, initialReferenceDateFromValue, validationProps, referenceDateProp) =>\n    getInitialReferenceDate({\n      adapter,\n      timezone,\n      validationProps,\n      externalReferenceDate: referenceDateProp,\n      externalDate: initialReferenceDateFromValue,\n    }),\n);\n\nconst valueWithTimezoneToRenderSelector = createSelectorMemoized(\n  timezoneToRenderSelector,\n  (state: State) => state.manager,\n  (state: State) => state.value,\n  (timezone, manager, value) => manager.setTimezone(value, timezone),\n) as <TValue extends TemporalSupportedValue>(state: State<TValue>) => TValue;\n\nconst selectedDatesSelector = createSelectorMemoized(\n  (state: State) => state.manager,\n  (state: State) => state.value,\n  timezoneToRenderSelector,\n  (manager, value, timezone) =>\n    manager.getDatesFromValue(value).map((date) => {\n      if (manager.getTimezone(date) === timezone) {\n        return date;\n      }\n      return manager.setTimezone(date, timezone);\n    }),\n);\n\nconst isDayCellDisabledSelector = createSelector((state: State, value: TemporalSupportedObject) => {\n  if (state.disabled) {\n    return true;\n  }\n\n  const validationError = validateDate({\n    adapter: state.adapter,\n    value,\n    validationProps: validationPropsSelector(state),\n  });\n\n  return validationError != null;\n});\n\nconst isDayCellUnavailableSelector = createSelector(\n  (state: State, value: TemporalSupportedObject) => state.isDateUnavailable?.(value) ?? false,\n);\n\nconst isDayButtonSelectedSelector = createSelector(\n  (state: State) => state.adapter,\n  selectedDatesSelector,\n  (adapter, selectedDates, cellValue: TemporalSupportedObject) => {\n    return selectedDates.some((date) => adapter.isSameDay(cellValue, date));\n  },\n);\n\nconst isSetMonthButtonDisabledSelector = createSelector(\n  (state: State) => state.adapter,\n  validationPropsSelector,\n  (state: State) => state.disabled,\n  (\n    adapter,\n    validationProps,\n    isCalendarDisabled,\n    targetDate: TemporalSupportedObject,\n    disabledProp?: boolean | undefined,\n  ) => {\n    // short-circuit if the disabled prop is explicitly provided.\n    if (disabledProp !== undefined) {\n      return disabledProp;\n    }\n\n    if (isCalendarDisabled) {\n      return true;\n    }\n\n    // The month targeted and all the months before are fully disabled, we disable the button.\n    if (\n      validationProps.minDate != null &&\n      adapter.isBefore(adapter.endOfMonth(targetDate), validationProps.minDate)\n    ) {\n      return true;\n    }\n\n    // The month targeted and all the months after are fully disabled, we disable the button.\n    return (\n      validationProps.maxDate != null &&\n      adapter.isAfter(adapter.startOfMonth(targetDate), validationProps.maxDate)\n    );\n  },\n);\n\nconst visibleDateSelector = createSelectorMemoized(\n  (state: State) => state.visibleDateProp,\n  (state: State) => state.visibleDate,\n  (state: State) => state.adapter,\n  timezoneToRenderSelector,\n  (visibleDateProp, visibleDate, adapter, timezone) =>\n    adapter.setTimezone(visibleDateProp ?? visibleDate, timezone),\n);\n\nconst visibleMonthSelector = createSelectorMemoized(\n  (state: State) => state.adapter,\n  visibleDateSelector,\n  (adapter, date: TemporalSupportedObject) => adapter.startOfMonth(date),\n);\n\nconst isValueInvalidSelector = createSelectorMemoized(\n  (state: State) => state.manager,\n  (state: State) => state.invalidProp,\n  valueWithTimezoneToRenderSelector,\n  validationPropsSelector,\n  (manager, invalidProp, value, validationProps) => {\n    if (invalidProp != null) {\n      return invalidProp;\n    }\n\n    const error = manager.getValidationError(value, validationProps);\n    return !manager.isValidationErrorEmpty(error);\n  },\n);\n\nconst rootElementStateSelector = createSelectorMemoized(\n  (state: State) => state.manager,\n  (state: State) => state.readOnly,\n  (state: State) => state.disabled,\n  (state: State) => state.navigationDirection,\n  isValueInvalidSelector,\n  valueWithTimezoneToRenderSelector,\n  (manager, readOnly, disabled, navigationDirection, invalid, value) => ({\n    empty: manager.areValuesEqual(value, manager.emptyValue),\n    invalid,\n    disabled,\n    readOnly,\n    navigationDirection,\n  }),\n);\n\nconst publicContextSelector = createSelectorMemoized(visibleDateSelector, (visibleDate) => ({\n  visibleDate,\n}));\n\nconst getMonthKey = (adapter: TemporalAdapter, date: TemporalSupportedObject) =>\n  `${adapter.getYear(date)}-${adapter.getMonth(date)}`;\n\nconst getDateKey = (adapter: TemporalAdapter, date: TemporalSupportedObject) =>\n  adapter.getTime(date);\n\nconst tabbableCellsPerMonthSelector = createSelectorMemoized(\n  (state: State) => state.adapter,\n  selectedDatesSelector,\n  referenceDateSelector,\n  (adapter, selectedDates, referenceDate) => {\n    const months = new Map<string, Set<number>>();\n\n    // Each month that contains selected dates has these selected dates as tabbable cells.\n    for (const date of selectedDates) {\n      const monthKey = getMonthKey(adapter, date);\n      if (!months.has(monthKey)) {\n        months.set(monthKey, new Set());\n      }\n      months.get(monthKey)!.add(getDateKey(adapter, date));\n    }\n\n    // If the month containing the reference dates has no selected dates, then the reference date will be tabbable in this month.\n    const referenceDateMonthKey = getMonthKey(adapter, referenceDate);\n    if (!months.has(referenceDateMonthKey)) {\n      months.set(referenceDateMonthKey, new Set([getDateKey(adapter, referenceDate)]));\n    }\n\n    return months;\n  },\n);\n\nconst isDayButtonTabbableSelector = createSelector(\n  tabbableCellsPerMonthSelector,\n  (state: State) => state.adapter,\n  (\n    tabbableCellsPerMonth,\n    adapter,\n    date: TemporalSupportedObject,\n    month: TemporalSupportedObject,\n  ) => {\n    // If the date is not in the current month, it cannot be tabbable.\n    if (!adapter.isSameMonth(date, month)) {\n      return false;\n    }\n\n    const monthKey = getMonthKey(adapter, date);\n\n    // If the month has registered tabbable cells, we check if the date is one of them.\n    if (tabbableCellsPerMonth.has(monthKey)) {\n      const dateKey = getDateKey(adapter, date);\n      return tabbableCellsPerMonth.get(monthKey)!.has(dateKey);\n    }\n\n    // Otherwise, only the first day of the month is tabbable.\n    const firstDayOfMonth = adapter.startOfMonth(date);\n    return adapter.isSameDay(date, firstDayOfMonth);\n  },\n);\n\nexport const selectors = {\n  /**\n   * Returns the timezone to use for rendering.\n   */\n  timezoneToRender: timezoneToRenderSelector,\n  /**\n   * Returns the state of the root element.\n   */\n  rootElementState: rootElementStateSelector,\n  /**\n   * Returns the context to publicly expose in render functions and hooks.\n   */\n  publicContext: publicContextSelector,\n  /**\n   * Returns the props to check if a date is valid or not.\n   */\n  validationProps: validationPropsSelector,\n  /**\n   * Returns the amount of months to navigate by when pressing `<Calendar.IncrementMonth>` or `<Calendar.DecrementMonth>`.\n   */\n  monthPageSize: createSelector((state: State) => state.monthPageSize),\n  /**\n   * Returns the date currently visible.\n   */\n  visibleDate: visibleDateSelector,\n  /**\n   * Returns the current visible month.\n   */\n  visibleMonth: visibleMonthSelector,\n  /**\n   * Returns the navigation direction.\n   */\n  navigationDirection: createSelector((state: State) => state.navigationDirection),\n  /**\n   * Returns the current value with the timezone to render applied.\n   */\n  valueWithTimezoneToRender: valueWithTimezoneToRenderSelector,\n  /**\n   * Returns the reference date.\n   */\n  referenceDate: referenceDateSelector,\n  /**\n   * Returns the list of currently selected dates.\n   * When used inside the Calendar component, it contains the current value if not null.\n   * When used inside the RangeCalendar component, it contains the selected start and/or end dates if not null.\n   */\n  selectedDates: selectedDatesSelector,\n  /**\n   * Checks if a day cell should be disabled.\n   */\n  isDayCellDisabled: isDayCellDisabledSelector,\n  /**\n   * Checks if a day cell should be selected.\n   */\n  isDayButtonSelected: isDayButtonSelectedSelector,\n  /**\n   * Checks if a specific dates is unavailable.\n   * If so, this date should not be selectable but should still be focusable with the keyboard.\n   */\n  isDayCellUnavailable: isDayCellUnavailableSelector,\n  /**\n   * Checks if a month navigation button should be disabled.\n   */\n  isSetMonthButtonDisabled: isSetMonthButtonDisabledSelector,\n  /**\n   * Checks if a day should be reachable using tab navigation.\n   */\n  isDayButtonTabbable: isDayButtonTabbableSelector,\n};\n\nexport interface CalendarRootElementState {\n  /**\n   * Whether the current value is empty.\n   */\n  empty: boolean;\n  /**\n   * Whether the current value is invalid.\n   */\n  invalid: boolean;\n  /**\n   * Whether the calendar is disabled.\n   */\n  disabled: boolean;\n  /**\n   * Whether the calendar is readonly.\n   */\n  readOnly?: boolean | undefined;\n  /**\n   * The direction of the navigation (based on the month navigating to).\n   */\n  navigationDirection: CalendarNavigationDirection;\n}\n"
  },
  {
    "path": "packages/react/src/calendar/store/test/value.SharedCalendarStore.test.ts",
    "content": "import { expect, vi } from 'vitest';\nimport { TemporalAdapterDateFns } from '../../../temporal-adapter-date-fns/TemporalAdapterDateFns';\nimport { TemporalValue } from '../../../types/temporal';\nimport { ValidateDateReturnValue } from '../../../utils/temporal/validateDate';\nimport { getDateManager } from '../../../utils/temporal/getDateManager';\nimport {\n  SharedCalendarStore,\n  SharedCalendarStoreParameters,\n  CalendarValueChangeEventDetails,\n} from '../SharedCalendarStore';\nimport { calendarValueManager } from '../../root/CalendarRoot';\n\n/**\n * Creates a mock MouseEvent for testing selectDate.\n */\nfunction createMockMouseEvent(): React.MouseEvent<HTMLButtonElement> {\n  const nativeEvent = new MouseEvent('click');\n  const currentTarget = document.createElement('button');\n\n  return {\n    nativeEvent,\n    currentTarget,\n    type: 'click',\n    preventDefault: () => {},\n    stopPropagation: () => {},\n  } as unknown as React.MouseEvent<HTMLButtonElement>;\n}\n\n/**\n * Helper to create a SharedCalendarStore with sensible defaults.\n */\nfunction createStore(\n  adapter: TemporalAdapterDateFns,\n  parameters: Partial<SharedCalendarStoreParameters<TemporalValue, ValidateDateReturnValue>> = {},\n) {\n  const manager = getDateManager(adapter);\n\n  const fullParameters: SharedCalendarStoreParameters<TemporalValue, ValidateDateReturnValue> = {\n    visibleDate: parameters.visibleDate ?? adapter.date('2025-02-01', 'default'),\n    ...parameters,\n  };\n\n  return new SharedCalendarStore(fullParameters, adapter, manager, calendarValueManager);\n}\n\ndescribe('SharedCalendarStore - value', () => {\n  const adapter = new TemporalAdapterDateFns();\n\n  describe('initialization', () => {\n    it('should initialize with null value when no value or defaultValue is provided', () => {\n      const store = createStore(adapter);\n\n      expect(store.state.value).toBe(null);\n    });\n\n    it('should initialize with defaultValue when provided', () => {\n      const defaultValue = adapter.date('2025-02-15', 'default');\n      const store = createStore(adapter, { defaultValue });\n\n      expect(adapter.isEqual(store.state.value, defaultValue)).toBe(true);\n    });\n\n    it('should initialize with value when provided (controlled mode)', () => {\n      const value = adapter.date('2025-02-20', 'default');\n      const store = createStore(adapter, { value });\n\n      expect(adapter.isEqual(store.state.value, value)).toBe(true);\n    });\n\n    it('should prefer value over defaultValue when both are provided', () => {\n      const value = adapter.date('2025-02-20', 'default');\n      const store = createStore(adapter, {\n        value,\n        defaultValue: adapter.date('2025-02-15', 'default'),\n      });\n\n      expect(adapter.isEqual(store.state.value, value)).toBe(true);\n    });\n  });\n\n  describe('uncontrolled mode (defaultValue)', () => {\n    it('should update internal value when selectDate is called', () => {\n      const store = createStore(adapter, { defaultValue: adapter.date('2025-02-15', 'default') });\n      const selectedDate = adapter.date('2025-02-20', 'default');\n\n      store.selectDate(selectedDate, createMockMouseEvent());\n\n      expect(adapter.isEqual(store.state.value, selectedDate)).toBe(true);\n    });\n\n    it('should call onValueChange when selectDate is called', () => {\n      const onValueChange = vi.fn();\n      const store = createStore(adapter, {\n        defaultValue: adapter.date('2025-02-15', 'default'),\n        onValueChange,\n      });\n      const selectedDate = adapter.date('2025-02-20', 'default');\n\n      store.selectDate(selectedDate, createMockMouseEvent());\n\n      expect(onValueChange.mock.calls.length).toBe(1);\n      expect(adapter.isEqual(onValueChange.mock.calls[0][0], selectedDate)).toBe(true);\n    });\n\n    it('should allow multiple value changes', () => {\n      const onValueChange = vi.fn();\n      const store = createStore(adapter, {\n        defaultValue: adapter.date('2025-02-15', 'default'),\n        onValueChange,\n      });\n\n      const date1 = adapter.date('2025-02-18', 'default');\n      const date2 = adapter.date('2025-02-22', 'default');\n\n      store.selectDate(date1, createMockMouseEvent());\n      expect(adapter.isEqual(store.state.value, date1)).toBe(true);\n\n      store.selectDate(date2, createMockMouseEvent());\n      expect(adapter.isEqual(store.state.value, date2)).toBe(true);\n\n      expect(onValueChange.mock.calls.length).toBe(2);\n    });\n  });\n\n  describe('controlled mode (value prop)', () => {\n    it('should not update internal value when selectDate is called', () => {\n      const value = adapter.date('2025-02-15', 'default');\n      const onValueChange = vi.fn();\n      const store = createStore(adapter, { value, onValueChange });\n      const selectedDate = adapter.date('2025-02-20', 'default');\n\n      store.selectDate(selectedDate, createMockMouseEvent());\n\n      // Value should remain unchanged (controlled mode)\n      expect(adapter.isEqual(store.state.value, value)).toBe(true);\n    });\n\n    it('should call onValueChange when selectDate is called', () => {\n      const value = adapter.date('2025-02-15', 'default');\n      const onValueChange = vi.fn();\n      const store = createStore(adapter, { value, onValueChange });\n      const selectedDate = adapter.date('2025-02-20', 'default');\n\n      store.selectDate(selectedDate, createMockMouseEvent());\n\n      expect(onValueChange.mock.calls.length).toBe(1);\n      expect(adapter.isEqual(onValueChange.mock.calls[0][0], selectedDate)).toBe(true);\n    });\n  });\n\n  describe('onValueChange callback', () => {\n    it('should return the validation error for invalid dates', () => {\n      const onValueChange = vi.fn();\n      const minDate = adapter.date('2025-02-25', 'default');\n      const store = createStore(adapter, { onValueChange, minDate });\n\n      // Select a date before minDate\n      const selectedDate = adapter.date('2025-02-20', 'default');\n      store.selectDate(selectedDate, createMockMouseEvent());\n\n      expect(onValueChange.mock.calls.length).toBe(1);\n\n      const eventDetails = onValueChange.mock\n        .calls[0][1] as CalendarValueChangeEventDetails<ValidateDateReturnValue>;\n      const validationError = eventDetails.getValidationError();\n      expect(validationError).toBe('before-min-date');\n    });\n\n    it('should return null validation error for valid dates', () => {\n      const onValueChange = vi.fn();\n      const minDate = adapter.date('2025-02-10', 'default');\n      const store = createStore(adapter, { onValueChange, minDate });\n\n      // Select a date after minDate\n      const selectedDate = adapter.date('2025-02-20', 'default');\n      store.selectDate(selectedDate, createMockMouseEvent());\n\n      expect(onValueChange.mock.calls.length).toBe(1);\n\n      const eventDetails = onValueChange.mock\n        .calls[0][1] as CalendarValueChangeEventDetails<ValidateDateReturnValue>;\n      const validationError = eventDetails.getValidationError();\n      expect(validationError).toBe(null);\n    });\n\n    it('should support eventDetails.cancel() when uncontrolled', () => {\n      const defaultValue = adapter.date('2025-02-15', 'default');\n\n      const store = createStore(adapter, {\n        defaultValue,\n        onValueChange: (_, eventDetails) => eventDetails.cancel(),\n      });\n\n      store.selectDate(adapter.date('2025-02-20', 'default'), createMockMouseEvent());\n\n      // But value should remain unchanged\n      expect(adapter.isEqual(store.state.value, defaultValue)).toBe(true);\n    });\n\n    it('should support conditionally calling eventDetails.cancel() based on validation when uncontrolled', () => {\n      const defaultValue = adapter.date('2025-02-15', 'default');\n      const store = createStore(adapter, {\n        defaultValue,\n        onValueChange: (_, eventDetails) => {\n          // Cancel if there's a validation error\n          const error = eventDetails.getValidationError();\n          if (error) {\n            eventDetails.cancel();\n          }\n        },\n        minDate: adapter.date('2025-02-18', 'default'),\n      });\n\n      // Try to select a date before minDate - should be canceled\n      const invalidDate = adapter.date('2025-02-16', 'default');\n      store.selectDate(invalidDate, createMockMouseEvent());\n\n      // Value should remain unchanged\n      expect(adapter.isEqual(store.state.value, defaultValue)).toBe(true);\n\n      // Select a valid date - should succeed\n      const validDate = adapter.date('2025-02-20', 'default');\n      store.selectDate(validDate, createMockMouseEvent());\n\n      // Value should be updated\n      expect(adapter.isEqual(store.state.value, validDate)).toBe(true);\n    });\n  });\n\n  describe('readOnly mode', () => {\n    it('should not update value when readOnly is true', () => {\n      const defaultValue = adapter.date('2025-02-15', 'default');\n      const onValueChange = vi.fn();\n      const store = createStore(adapter, { defaultValue, onValueChange, readOnly: true });\n      const selectedDate = adapter.date('2025-02-20', 'default');\n\n      store.selectDate(selectedDate, createMockMouseEvent());\n\n      // onValueChange should not be called\n      expect(onValueChange.mock.calls.length).toBe(0);\n\n      // Value should remain unchanged\n      expect(adapter.isEqual(store.state.value, defaultValue)).toBe(true);\n    });\n\n    it('should not call onValueChange when readOnly is true', () => {\n      const value = adapter.date('2025-02-15', 'default');\n      const onValueChange = vi.fn();\n      const store = createStore(adapter, { value, onValueChange, readOnly: true });\n      const selectedDate = adapter.date('2025-02-20', 'default');\n\n      store.selectDate(selectedDate, createMockMouseEvent());\n\n      expect(onValueChange.mock.calls.length).toBe(0);\n    });\n  });\n\n  describe('empty value handling', () => {\n    it('should handle null value (controlled with empty)', () => {\n      const store = createStore(adapter, { value: null });\n\n      expect(store.state.value).toBe(null);\n    });\n\n    it('should transition from null to a selected value in uncontrolled mode', () => {\n      const onValueChange = vi.fn();\n      const store = createStore(adapter, { onValueChange });\n      const selectedDate = adapter.date('2025-02-20', 'default');\n\n      expect(store.state.value).toBe(null);\n\n      store.selectDate(selectedDate, createMockMouseEvent());\n\n      expect(adapter.isEqual(store.state.value, selectedDate)).toBe(true);\n      expect(onValueChange.mock.calls.length).toBe(1);\n    });\n\n    it('should call onValueChange when transitioning from null (controlled)', () => {\n      const onValueChange = vi.fn();\n      const store = createStore(adapter, { value: null, onValueChange });\n      const selectedDate = adapter.date('2025-02-20', 'default');\n\n      store.selectDate(selectedDate, createMockMouseEvent());\n\n      // Value remains null in controlled mode\n      expect(store.state.value).toBe(null);\n\n      // But onValueChange should be called\n      expect(onValueChange.mock.calls.length).toBe(1);\n      expect(adapter.isEqual(onValueChange.mock.calls[0][0], selectedDate)).toBe(true);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/calendar/store/test/visibleDate.SharedCalendarStore.test.ts",
    "content": "import { expect, vi } from 'vitest';\nimport { TemporalAdapterDateFns } from '../../../temporal-adapter-date-fns/TemporalAdapterDateFns';\nimport { TemporalValue } from '../../../types/temporal';\nimport { ValidateDateReturnValue } from '../../../utils/temporal/validateDate';\nimport { getDateManager } from '../../../utils/temporal/getDateManager';\nimport {\n  SharedCalendarStore,\n  SharedCalendarStoreParameters,\n  CalendarVisibleDateChangeEventDetails,\n} from '../SharedCalendarStore';\nimport { calendarValueManager } from '../../root/CalendarRoot';\n\n/**\n * Helper to create a SharedCalendarStore with sensible defaults.\n */\nfunction createStore(\n  adapter: TemporalAdapterDateFns,\n  parameters: Partial<SharedCalendarStoreParameters<TemporalValue, ValidateDateReturnValue>> = {},\n) {\n  const manager = getDateManager(adapter);\n\n  const fullParameters: SharedCalendarStoreParameters<TemporalValue, ValidateDateReturnValue> = {\n    ...parameters,\n  };\n\n  return new SharedCalendarStore(fullParameters, adapter, manager, calendarValueManager);\n}\n\ndescribe('SharedCalendarStore - visibleDate', () => {\n  const adapter = new TemporalAdapterDateFns();\n\n  describe('initialization', () => {\n    it('should initialize with visibleDate when provided', () => {\n      const visibleDate = adapter.date('2025-03-15', 'default');\n      const store = createStore(adapter, { visibleDate });\n\n      expect(adapter.isEqual(store.state.visibleDate, visibleDate)).toBe(true);\n    });\n\n    it('should initialize with defaultVisibleDate when provided', () => {\n      const defaultVisibleDate = adapter.date('2025-04-20', 'default');\n      const store = createStore(adapter, { defaultVisibleDate });\n\n      expect(adapter.isEqual(store.state.visibleDate, defaultVisibleDate)).toBe(true);\n    });\n\n    it('should prefer visibleDate over defaultVisibleDate when both are provided', () => {\n      const visibleDate = adapter.date('2025-03-15', 'default');\n      const store = createStore(adapter, {\n        visibleDate,\n        defaultVisibleDate: adapter.date('2025-04-20', 'default'),\n      });\n\n      expect(adapter.isEqual(store.state.visibleDate, visibleDate)).toBe(true);\n    });\n\n    it('should derive visibleDate from value when no visibleDate props are provided', () => {\n      const value = adapter.date('2025-05-10', 'default');\n      const store = createStore(adapter, { value });\n\n      // Should use the value as the initial visible date\n      expect(adapter.isEqual(store.state.visibleDate, value)).toBe(true);\n    });\n\n    it('should derive visibleDate from defaultValue when no visibleDate props are provided', () => {\n      const defaultValue = adapter.date('2025-06-15', 'default');\n      const store = createStore(adapter, { defaultValue });\n\n      // Should use the defaultValue as the initial visible date\n      expect(adapter.isEqual(store.state.visibleDate, defaultValue)).toBe(true);\n    });\n\n    it('should derive visibleDate from referenceDate when no value or visibleDate props are provided', () => {\n      const referenceDate = adapter.date('2025-07-20', 'default');\n      const store = createStore(adapter, { referenceDate });\n\n      // Should use the referenceDate as the initial visible date\n      expect(adapter.isEqual(store.state.visibleDate, referenceDate)).toBe(true);\n    });\n  });\n\n  describe('uncontrolled mode (defaultVisibleDate)', () => {\n    it('should update internal visibleDate when setVisibleDate is called', () => {\n      const store = createStore(adapter, {\n        defaultVisibleDate: adapter.date('2025-02-01', 'default'),\n      });\n      const newVisibleDate = adapter.date('2025-03-01', 'default');\n\n      store.setVisibleDate(newVisibleDate);\n\n      expect(adapter.isEqual(store.state.visibleDate, newVisibleDate)).toBe(true);\n    });\n\n    it('should call onVisibleDateChange when setVisibleDate is called', () => {\n      const onVisibleDateChange = vi.fn();\n      const store = createStore(adapter, {\n        defaultVisibleDate: adapter.date('2025-02-01', 'default'),\n        onVisibleDateChange,\n      });\n      const newVisibleDate = adapter.date('2025-03-01', 'default');\n\n      store.setVisibleDate(newVisibleDate);\n\n      expect(onVisibleDateChange.mock.calls.length).toBe(1);\n      expect(adapter.isEqual(onVisibleDateChange.mock.calls[0][0], newVisibleDate)).toBe(true);\n    });\n\n    it('should allow multiple visibleDate changes', () => {\n      const onVisibleDateChange = vi.fn();\n      const store = createStore(adapter, {\n        defaultVisibleDate: adapter.date('2025-02-01', 'default'),\n        onVisibleDateChange,\n      });\n\n      const date1 = adapter.date('2025-03-01', 'default');\n      const date2 = adapter.date('2025-04-01', 'default');\n\n      store.setVisibleDate(date1);\n      expect(adapter.isEqual(store.state.visibleDate, date1)).toBe(true);\n\n      store.setVisibleDate(date2);\n      expect(adapter.isEqual(store.state.visibleDate, date2)).toBe(true);\n\n      expect(onVisibleDateChange.mock.calls.length).toBe(2);\n    });\n  });\n\n  describe('controlled mode (visibleDate prop)', () => {\n    it('should not update internal visibleDate when setVisibleDate is called', () => {\n      const visibleDate = adapter.date('2025-02-01', 'default');\n      const onVisibleDateChange = vi.fn();\n      const store = createStore(adapter, { visibleDate, onVisibleDateChange });\n      const newVisibleDate = adapter.date('2025-03-01', 'default');\n\n      store.setVisibleDate(newVisibleDate);\n\n      // visibleDate should remain unchanged (controlled mode)\n      expect(adapter.isEqual(store.state.visibleDate, visibleDate)).toBe(true);\n    });\n\n    it('should call onVisibleDateChange when setVisibleDate is called', () => {\n      const visibleDate = adapter.date('2025-02-01', 'default');\n      const onVisibleDateChange = vi.fn();\n      const store = createStore(adapter, { visibleDate, onVisibleDateChange });\n      const newVisibleDate = adapter.date('2025-03-01', 'default');\n\n      store.setVisibleDate(newVisibleDate);\n\n      expect(onVisibleDateChange.mock.calls.length).toBe(1);\n      expect(adapter.isEqual(onVisibleDateChange.mock.calls[0][0], newVisibleDate)).toBe(true);\n    });\n  });\n\n  describe('onVisibleDateChange callback', () => {\n    it('should pass eventDetails as second argument', () => {\n      const onVisibleDateChange = vi.fn();\n      const store = createStore(adapter, {\n        defaultVisibleDate: adapter.date('2025-02-01', 'default'),\n        onVisibleDateChange,\n      });\n      const newVisibleDate = adapter.date('2025-03-01', 'default');\n\n      store.setVisibleDate(newVisibleDate);\n\n      expect(onVisibleDateChange.mock.calls.length).toBe(1);\n\n      const eventDetails = onVisibleDateChange.mock\n        .calls[0][1] as CalendarVisibleDateChangeEventDetails;\n      expect(eventDetails).not.toBe(undefined);\n      expect(eventDetails.reason).toBe('day-press');\n      expect(eventDetails.event).toBeInstanceOf(Event);\n      expect(eventDetails.isCanceled).toBe(false);\n      expect(typeof eventDetails.cancel).toBe('function');\n    });\n\n    it('should support eventDetails.cancel() when uncontrolled', () => {\n      const defaultVisibleDate = adapter.date('2025-02-01', 'default');\n      const store = createStore(adapter, {\n        defaultVisibleDate,\n        onVisibleDateChange: (_, eventDetails) => eventDetails.cancel(),\n      });\n      const newVisibleDate = adapter.date('2025-03-01', 'default');\n\n      store.setVisibleDate(newVisibleDate);\n\n      // visibleDate should remain unchanged because cancel() was called\n      expect(adapter.isEqual(store.state.visibleDate, defaultVisibleDate)).toBe(true);\n    });\n\n    it(\"should pass reason 'month-change' in eventDetails when reason is 'month-change'\", () => {\n      const onVisibleDateChange = vi.fn();\n      const store = createStore(adapter, {\n        defaultVisibleDate: adapter.date('2025-02-01', 'default'),\n        onVisibleDateChange,\n      });\n      const newVisibleDate = adapter.date('2025-03-01', 'default');\n\n      store.setVisibleDate(newVisibleDate, undefined, undefined, 'month-change');\n\n      const eventDetails = onVisibleDateChange.mock\n        .calls[0][1] as CalendarVisibleDateChangeEventDetails;\n      expect(eventDetails.reason).toBe('month-change');\n    });\n\n    it(\"should pass reason 'keyboard' in eventDetails when reason is 'keyboard'\", () => {\n      const onVisibleDateChange = vi.fn();\n      const store = createStore(adapter, {\n        defaultVisibleDate: adapter.date('2025-02-01', 'default'),\n        onVisibleDateChange,\n      });\n      const newVisibleDate = adapter.date('2025-03-01', 'default');\n\n      store.setVisibleDate(newVisibleDate, undefined, undefined, 'keyboard');\n\n      const eventDetails = onVisibleDateChange.mock\n        .calls[0][1] as CalendarVisibleDateChangeEventDetails;\n      expect(eventDetails.reason).toBe('keyboard');\n    });\n\n    it('should support conditionally calling eventDetails.cancel() when uncontrolled', () => {\n      const defaultVisibleDate = adapter.date('2025-02-01', 'default');\n      const store = createStore(adapter, {\n        defaultVisibleDate,\n        onVisibleDateChange: (newDate, eventDetails) => {\n          // Only allow navigating forward in time\n          if (adapter.isBefore(newDate, defaultVisibleDate)) {\n            eventDetails.cancel();\n          }\n        },\n      });\n\n      // Try to navigate backward - should be canceled\n      const pastDate = adapter.date('2025-01-01', 'default');\n      store.setVisibleDate(pastDate);\n      expect(adapter.isEqual(store.state.visibleDate, defaultVisibleDate)).toBe(true);\n\n      // Navigate forward - should succeed\n      const futureDate = adapter.date('2025-03-01', 'default');\n      store.setVisibleDate(futureDate);\n      expect(adapter.isEqual(store.state.visibleDate, futureDate)).toBe(true);\n    });\n  });\n\n  describe('navigationDirection', () => {\n    it('should set navigationDirection to \"next\" when navigating forward', () => {\n      const store = createStore(adapter, {\n        defaultVisibleDate: adapter.date('2025-02-01', 'default'),\n      });\n      const newVisibleDate = adapter.date('2025-03-01', 'default');\n\n      store.setVisibleDate(newVisibleDate);\n\n      expect(store.state.navigationDirection).toBe('next');\n    });\n\n    it('should set navigationDirection to \"previous\" when navigating backward', () => {\n      const store = createStore(adapter, {\n        defaultVisibleDate: adapter.date('2025-02-01', 'default'),\n      });\n      const newVisibleDate = adapter.date('2025-01-01', 'default');\n\n      store.setVisibleDate(newVisibleDate);\n\n      expect(store.state.navigationDirection).toBe('previous');\n    });\n\n    it('should set navigationDirection to \"none\" when setting the same date', () => {\n      const visibleDate = adapter.date('2025-02-01', 'default');\n      const store = createStore(adapter, { defaultVisibleDate: visibleDate });\n\n      // First set a different date to change direction\n      store.setVisibleDate(adapter.date('2025-03-01', 'default'));\n      expect(store.state.navigationDirection).toBe('next');\n\n      // Now set back to the same date\n      store.setVisibleDate(adapter.date('2025-03-01', 'default'));\n      expect(store.state.navigationDirection).toBe('none');\n    });\n\n    it('should initialize with navigationDirection \"none\"', () => {\n      const store = createStore(adapter, {\n        defaultVisibleDate: adapter.date('2025-02-01', 'default'),\n      });\n\n      expect(store.state.navigationDirection).toBe('none');\n    });\n  });\n\n  describe('visibleDate synchronization with value', () => {\n    it('should update visibleDate when controlled value changes to a valid date', () => {\n      const onVisibleDateChange = vi.fn();\n      const initialValue = adapter.date('2025-02-15', 'default');\n      const store = createStore(adapter, { value: initialValue, onVisibleDateChange });\n\n      // Change the controlled value to a different month\n      const newValue = adapter.date('2025-05-20', 'default');\n      store.set('valueProp', newValue);\n\n      // visibleDate should be updated to match the new value\n      expect(adapter.isEqual(store.state.visibleDate, newValue)).toBe(true);\n\n      // onVisibleDateChange should be called with the new visible date\n      expect(onVisibleDateChange.mock.calls.length).toBe(1);\n      expect(adapter.isEqual(onVisibleDateChange.mock.calls[0][0], newValue)).toBe(true);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/calendar/use-context/CalendarContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStore } from '@base-ui/utils/store';\nimport { useSharedCalendarRootContext } from '../root/SharedCalendarRootContext';\nimport { selectors } from '../store';\n\nexport type CalendarContext = ReturnType<typeof selectors.publicContext> & {\n  setVisibleDate: ReturnType<typeof useSharedCalendarRootContext>['setVisibleDate'];\n};\n\nexport function useCalendarContext(): CalendarContext {\n  const store = useSharedCalendarRootContext();\n  const calendarPublicContext = useStore(store, selectors.publicContext);\n\n  return React.useMemo(\n    () => ({ ...calendarPublicContext, setVisibleDate: store.setVisibleDate }),\n    [calendarPublicContext, store.setVisibleDate],\n  );\n}\n"
  },
  {
    "path": "packages/react/src/calendar/use-day-list/getDayList.ts",
    "content": "import { TemporalAdapter, TemporalSupportedObject } from '../../types/temporal';\n\n/**\n * Computes a list of consecutive days starting from the given date.\n * This is a pure function — no React hooks required.\n */\nexport function getDayList(\n  adapter: TemporalAdapter,\n  params: { date: TemporalSupportedObject; amount: number },\n): TemporalSupportedObject[] {\n  const { date, amount } = params;\n\n  if (process.env.NODE_ENV !== 'production') {\n    if (amount <= 0) {\n      throw new Error(\n        `getDayList: The 'amount' parameter must be a positive number, but received ${amount}.`,\n      );\n    }\n  }\n\n  const start = adapter.startOfDay(date);\n  const end = adapter.endOfDay(adapter.addDays(start, amount - 1));\n\n  let current = start;\n  let currentDayNumber = adapter.getDayOfWeek(current);\n  const days: TemporalSupportedObject[] = [];\n\n  while (adapter.isBefore(current, end)) {\n    days.push(current);\n\n    const prevDayNumber = currentDayNumber;\n    current = adapter.addDays(current, 1);\n    currentDayNumber = adapter.getDayOfWeek(current);\n\n    // If there is a TZ change at midnight, adding 1 day may only increase the date by 23 hours to 11pm\n    // To fix, bump the date into the next day (add 12 hours) and then revert to the start of the day\n    // See https://github.com/moment/moment/issues/4743#issuecomment-811306874 for context.\n    if (prevDayNumber === currentDayNumber) {\n      current = adapter.startOfDay(adapter.addHours(current, 12));\n    }\n  }\n\n  return days;\n}\n"
  },
  {
    "path": "packages/react/src/calendar/use-day-list/useCalendarDayList.ts",
    "content": "import * as React from 'react';\nimport { useTemporalAdapter } from '../../temporal-adapter-provider/TemporalAdapterContext';\nimport { TemporalSupportedObject } from '../../types/temporal';\nimport { getDayList } from './getDayList';\n\nexport function useCalendarDayList(): UseCalendarDayListReturnValue {\n  const adapter = useTemporalAdapter();\n\n  return React.useCallback((params) => getDayList(adapter, params), [adapter]);\n}\n\nexport type UseCalendarDayListReturnValue = (parameters: {\n  /**\n   * The date to get the weeks in month for.\n   */\n  date: TemporalSupportedObject;\n  /**\n   * The amount of days to return.\n   */\n  amount: number;\n}) => TemporalSupportedObject[];\n"
  },
  {
    "path": "packages/react/src/calendar/use-week-list/getWeekList.ts",
    "content": "import { TemporalSupportedObject, TemporalAdapter } from '../../types/temporal';\n\n/**\n * Computes a list of week-start dates for the given month/date.\n * This is a pure function — no React hooks required.\n */\nexport function getWeekList(\n  adapter: TemporalAdapter,\n  params: { date: TemporalSupportedObject; amount: number | 'end-of-month' },\n): TemporalSupportedObject[] {\n  const { date, amount } = params;\n\n  if (process.env.NODE_ENV !== 'production') {\n    if (typeof amount === 'number' && amount <= 0) {\n      throw new Error(\n        `getWeekList: The 'amount' parameter must be a positive number, but received ${amount}.`,\n      );\n    }\n  }\n\n  const start = adapter.startOfWeek(date);\n  const end =\n    amount === 'end-of-month'\n      ? adapter.endOfWeek(adapter.endOfMonth(date))\n      : adapter.endOfWeek(adapter.addWeeks(start, amount - 1));\n\n  let current = start;\n  let currentWeekNumber = adapter.getWeekNumber(current);\n  const weeks: TemporalSupportedObject[] = [];\n\n  while (adapter.isBefore(current, end)) {\n    weeks.push(current);\n\n    const prevWeekNumber = currentWeekNumber;\n    current = adapter.addWeeks(current, 1);\n    currentWeekNumber = adapter.getWeekNumber(current);\n\n    // If there is a TZ change at midnight, adding 1 week may only increase the date by 6 days and 23 hours to 11pm\n    // To fix, bump the date into the next day (add 12 hours) and then revert to the start of the day\n    // See https://github.com/moment/moment/issues/4743#issuecomment-811306874 for context.\n    if (prevWeekNumber === currentWeekNumber) {\n      current = adapter.startOfDay(adapter.addHours(current, 12));\n    }\n  }\n\n  return weeks;\n}\n"
  },
  {
    "path": "packages/react/src/calendar/use-week-list/useCalendarWeekList.ts",
    "content": "import * as React from 'react';\nimport { useTemporalAdapter } from '../../temporal-adapter-provider/TemporalAdapterContext';\nimport { TemporalSupportedObject } from '../../types/temporal/temporal';\nimport { getWeekList } from './getWeekList';\n\nexport function useCalendarWeekList(): UseWeekListReturnValue {\n  const adapter = useTemporalAdapter();\n\n  return React.useCallback((params) => getWeekList(adapter, params), [adapter]);\n}\n\nexport type UseWeekListReturnValue = (\n  parameters: UseWeekListReturnValueParameters,\n) => TemporalSupportedObject[];\n\ninterface UseWeekListReturnValueParameters {\n  /**\n   * The date to get the weeks in month for.\n   */\n  date: TemporalSupportedObject;\n  /**\n   * The amount of weeks to return.\n   * When equal to \"end-of-month\", the method will return all the weeks until the end of the month.\n   * When equal to a number, the method will return that many weeks.\n   * Set it to 6 to create a Gregorian calendar where all months have the same number of weeks.\n   */\n  amount: number | 'end-of-month';\n}\n"
  },
  {
    "path": "packages/react/src/calendar/utils/computeMonthDayGrid.ts",
    "content": "import { TemporalSupportedObject, TemporalAdapter } from '../../types/temporal';\nimport { getDayList } from '../use-day-list/getDayList';\nimport { getWeekList } from '../use-week-list/getWeekList';\n\n/**\n * Computes a flat, chronologically ordered array of all days in a month's grid.\n * Composes `getWeekList` and `getDayList` to produce the same result as the rendered grid.\n */\nexport function computeMonthDayGrid(\n  adapter: TemporalAdapter,\n  month: TemporalSupportedObject,\n  fixedWeekNumber?: number,\n  weeks?: TemporalSupportedObject[],\n): TemporalSupportedObject[] {\n  const weeksList =\n    weeks ?? getWeekList(adapter, { date: month, amount: fixedWeekNumber ?? 'end-of-month' });\n  return weeksList.flatMap((week) => getDayList(adapter, { date: week, amount: 7 }));\n}\n"
  },
  {
    "path": "packages/react/src/calendar/utils/useCalendarMonthButton.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { usePressAndHold } from '../../utils/usePressAndHold';\nimport { REASONS } from '../../utils/reasons';\nimport type { SharedCalendarStore } from '../store/SharedCalendarStore';\nimport type { TemporalSupportedValue, TemporalAdapter } from '../../types/temporal';\nimport { selectors } from '../store';\n\nconst CHANGE_MONTH_TICK_DELAY = 100;\n\ninterface UseCalendarMonthButtonParameters {\n  direction: 1 | -1;\n  disabled: boolean;\n  disabledProp?: boolean | undefined;\n  store: SharedCalendarStore<TemporalSupportedValue, unknown>;\n  adapter: TemporalAdapter;\n  monthPageSize: number;\n}\n\n/**\n * Adds press-and-hold behavior to Calendar month navigation buttons.\n * On pointer down, performs one navigation immediately, then after a delay\n * starts continuous navigation at a fixed interval.\n */\nexport function useCalendarMonthButton(params: UseCalendarMonthButtonParameters) {\n  const { direction, disabled, disabledProp, store, adapter, monthPageSize } = params;\n\n  const autoChangeButtonRef = React.useRef<HTMLElement | null>(null);\n\n  const { pointerHandlers, shouldSkipClick } = usePressAndHold({\n    disabled,\n    elementRef: autoChangeButtonRef,\n    tickDelay: CHANGE_MONTH_TICK_DELAY,\n    tick(triggerNativeEvent) {\n      const button = autoChangeButtonRef.current;\n      const currentVisibleDate = selectors.visibleDate(store.state);\n      const targetDate = adapter.addMonths(currentVisibleDate, direction * monthPageSize);\n\n      const wouldBeDisabled = selectors.isSetMonthButtonDisabled(\n        store.state,\n        targetDate,\n        disabledProp,\n      );\n      if (wouldBeDisabled) {\n        return false;\n      }\n\n      store.setVisibleDate(\n        targetDate,\n        triggerNativeEvent,\n        button ?? undefined,\n        REASONS.monthChange,\n      );\n      return true;\n    },\n  });\n\n  return { pointerHandlers, autoChangeButtonRef, shouldSkipClick };\n}\n"
  },
  {
    "path": "packages/react/src/calendar/viewport/CalendarViewport.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { inertValue } from '@base-ui/utils/inertValue';\nimport { useAnimationFrame } from '@base-ui/utils/useAnimationFrame';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useStore } from '@base-ui/utils/store';\nimport { TemporalSupportedObject } from '../../types/temporal';\nimport { useAnimationsFinished } from '../../utils/useAnimationsFinished';\nimport { CalendarViewportDataAttributes } from './CalendarViewportDataAttributes';\nimport { useSharedCalendarRootContext } from '../root/SharedCalendarRootContext';\nimport { CalendarNavigationDirection, selectors } from '../store';\nimport { useTemporalAdapter } from '../../temporal-adapter-provider/TemporalAdapterContext';\n\nconst getNavigationDirectionAttribute = (navigationDirection: CalendarNavigationDirection) => {\n  switch (navigationDirection) {\n    case 'none':\n      return null;\n    default:\n      return {\n        [CalendarViewportDataAttributes.navigationDirection]: navigationDirection,\n      };\n  }\n};\n\nconst ALLOWED_TAGS = new Set(['div', 'section', 'span', 'tbody']);\n\nfunction getSafeTag(localName: string): string {\n  return ALLOWED_TAGS.has(localName) ? localName : 'div';\n}\n\nconst DATA_ATTRIBUTES = [\n  CalendarViewportDataAttributes.current,\n  CalendarViewportDataAttributes.startingStyle,\n  CalendarViewportDataAttributes.endingStyle,\n  CalendarViewportDataAttributes.navigationDirection,\n];\n\n/**\n * A viewport for displaying calendar month transitions.\n * This component is only required if you want to animate certain part of a calendar when navigating between months.\n * The first rendered child element has to handle a ref.\n * Passes `data-current` to the currently visible content and `data-previous` to the previous content when animating between two.\n * Doesn't render its own HTML element.\n *\n * Documentation: [Base UI Calendar](https://base-ui.com/react/components/calendar)\n */\nexport function CalendarViewport({ children }: CalendarViewport.Props): React.JSX.Element {\n  const store = useSharedCalendarRootContext();\n  const adapter = useTemporalAdapter();\n\n  const navigationDirection = useStore(store, selectors.navigationDirection);\n  const visibleMonth = useStore(store, selectors.visibleMonth);\n  const lastHandledVisibleMonth = React.useRef<TemporalSupportedObject | null>(visibleMonth);\n\n  const capturedNodeRef = React.useRef<HTMLElement | null>(null);\n  const [previousContentNode, setPreviousContentNode] = React.useState<HTMLElement | null>(null);\n\n  const currentContainerRef = React.useRef<HTMLElement>(null);\n  const previousContainerRef = React.useRef<HTMLElement>(null);\n\n  const onAnimationsFinished = useAnimationsFinished(currentContainerRef, true, false);\n  const cleanupTimeout = useAnimationFrame();\n  const abortControllerRef = React.useRef<AbortController | null>(null);\n\n  const [showStartingStyleAttribute, setShowStartingStyleAttribute] = React.useState(false);\n\n  useIsoLayoutEffect(() => {\n    // When the visible month changes,\n    // set the captured children HTML to state,\n    // so we can render both new and old month when transitioning.\n    if (\n      visibleMonth &&\n      lastHandledVisibleMonth.current &&\n      !adapter.isEqual(lastHandledVisibleMonth.current, visibleMonth) &&\n      capturedNodeRef.current\n    ) {\n      // Cancel the previous transition's pending animation-finished callback\n      abortControllerRef.current?.abort();\n      const abortController = new AbortController();\n      abortControllerRef.current = abortController;\n\n      setPreviousContentNode(capturedNodeRef.current);\n      setShowStartingStyleAttribute(true);\n\n      cleanupTimeout.request(() => {\n        setShowStartingStyleAttribute(false);\n        onAnimationsFinished(() => {\n          setPreviousContentNode(null);\n          capturedNodeRef.current = null;\n        }, abortController.signal);\n      });\n\n      lastHandledVisibleMonth.current = visibleMonth;\n    }\n  }, [adapter, cleanupTimeout, onAnimationsFinished, visibleMonth]);\n\n  // Capture a clone of the current content DOM subtree when not transitioning.\n  // We can't store previous React nodes as they may be stateful; instead we capture DOM clones for visual continuity.\n  useIsoLayoutEffect(() => {\n    // When a transition is in progress, we store the next content in capturedNodeRef.\n    // This handles the case where the trigger changes multiple times before the transition finishes.\n    // We want to always capture the latest content for the previous snapshot.\n    // So clicking quickly on T1, T2, T3 will result in the following sequence:\n    // 1. T1 -> T2: previousContent = T1, currentContent = T2\n    // 2. T2 -> T3: previousContent = T2, currentContent = T3\n    const source = currentContainerRef.current;\n    if (!source) {\n      return;\n    }\n\n    // Create the wrapper element of the same type as the source element.\n    // It has to be an element of the same tag, especially if it's the calendar body (`tbody`).\n    const wrapper = document.createElement(getSafeTag(source.localName));\n    for (const child of Array.from(source.childNodes)) {\n      wrapper.appendChild(child.cloneNode(true));\n    }\n\n    capturedNodeRef.current = wrapper;\n  });\n\n  const currentChildren = React.cloneElement(children, {\n    ref: currentContainerRef,\n    key: 'current',\n  });\n\n  const isTransitioning = previousContentNode != null;\n  let childrenToRender: React.ReactNode;\n  if (!isTransitioning) {\n    childrenToRender = currentChildren;\n  } else {\n    childrenToRender = (\n      <React.Fragment>\n        {navigationDirection === 'previous' && currentChildren}\n        {React.createElement(getSafeTag(previousContentNode.localName), {\n          className: currentContainerRef?.current?.className,\n          key: 'previous',\n          ref: previousContainerRef,\n          'data-previous': '',\n          'data-starting-style': showStartingStyleAttribute ? '' : undefined,\n          'data-ending-style': showStartingStyleAttribute ? undefined : '',\n          inert: inertValue(true),\n          ...getNavigationDirectionAttribute(navigationDirection),\n        })}\n        {navigationDirection === 'next' && currentChildren}\n      </React.Fragment>\n    );\n  }\n\n  // Avoids remounting the current month after transition ends.\n  useIsoLayoutEffect(() => {\n    if (!currentContainerRef.current) {\n      return;\n    }\n    if (isTransitioning) {\n      currentContainerRef.current.setAttribute('data-current', '');\n      if (showStartingStyleAttribute) {\n        currentContainerRef.current.setAttribute('data-starting-style', '');\n      } else {\n        currentContainerRef.current.setAttribute('data-ending-style', '');\n        currentContainerRef.current.removeAttribute('data-starting-style');\n      }\n      const navigationDirectionAttribute = getNavigationDirectionAttribute(navigationDirection);\n      if (navigationDirectionAttribute) {\n        currentContainerRef.current.setAttribute(\n          ...Object.entries(navigationDirectionAttribute)[0],\n        );\n      }\n    } else {\n      for (const attribute of DATA_ATTRIBUTES) {\n        currentContainerRef.current.removeAttribute(attribute);\n      }\n    }\n  }, [isTransitioning, showStartingStyleAttribute, navigationDirection]);\n\n  // When previousContentNode is present, imperatively populate the previous container with the cloned children.\n  useIsoLayoutEffect(() => {\n    const container = previousContainerRef.current;\n    if (!container || !previousContentNode) {\n      return;\n    }\n\n    container.replaceChildren(...Array.from(previousContentNode.childNodes));\n  }, [previousContentNode]);\n\n  return childrenToRender;\n}\n\nexport interface CalendarViewportProps {\n  /**\n   * The content to render inside the transition container.\n   */\n  children: React.JSX.Element;\n}\n\nexport interface CalendarViewportState {\n  /**\n   * Indicates the direction of the navigation (based on the month navigating to).\n   */\n  navigationDirection: CalendarNavigationDirection;\n}\n\nexport namespace CalendarViewport {\n  export type Props = CalendarViewportProps;\n  export type State = CalendarViewportState;\n}\n"
  },
  {
    "path": "packages/react/src/calendar/viewport/CalendarViewportDataAttributes.ts",
    "content": "import { TransitionStatusDataAttributes } from '../../utils/stateAttributesMapping';\n\nexport enum CalendarViewportDataAttributes {\n  /**\n   * Applied to the direct child of the viewport when no transitions are present or the new content when it's entering.\n   */\n  current = 'data-current',\n  /**\n   * Applied to the direct child of the viewport that contains the exiting content when transitions are present.\n   */\n  previous = 'data-previous',\n  /**\n   * Indicates the direction of the navigation (based on the month navigating to).\n   * @type {'previous' | 'next' | 'none'}\n   */\n  navigationDirection = 'data-navigation-direction',\n  /**\n   * Present when the day grid body is animating in.\n   */\n  startingStyle = TransitionStatusDataAttributes.startingStyle,\n  /**\n   * Present when the day grid body is animating out.\n   */\n  endingStyle = TransitionStatusDataAttributes.endingStyle,\n}\n"
  },
  {
    "path": "packages/react/src/checkbox/index.parts.ts",
    "content": "export { CheckboxRoot as Root } from './root/CheckboxRoot';\nexport { CheckboxIndicator as Indicator } from './indicator/CheckboxIndicator';\n"
  },
  {
    "path": "packages/react/src/checkbox/index.ts",
    "content": "export * as Checkbox from './index.parts';\n\nexport type * from './root/CheckboxRoot';\nexport type * from './indicator/CheckboxIndicator';\n"
  },
  {
    "path": "packages/react/src/checkbox/indicator/CheckboxIndicator.test.tsx",
    "content": "import { expect } from 'vitest';\nimport * as React from 'react';\nimport { Checkbox } from '@base-ui/react/checkbox';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\nimport { screen, waitFor } from '@mui/internal-test-utils';\nimport { CheckboxRootContext } from '../root/CheckboxRootContext';\n\nconst testContext = {\n  checked: true,\n  disabled: false,\n  readOnly: false,\n  required: false,\n  indeterminate: false,\n  dirty: false,\n  touched: false,\n  valid: null,\n  filled: false,\n  focused: false,\n};\n\ndescribe('<Checkbox.Indicator />', () => {\n  beforeEach(() => {\n    globalThis.BASE_UI_ANIMATIONS_DISABLED = true;\n  });\n\n  const { render } = createRenderer();\n\n  describeConformance(<Checkbox.Indicator />, () => ({\n    refInstanceof: window.HTMLSpanElement,\n    render(node) {\n      return render(\n        <CheckboxRootContext.Provider value={testContext}>{node}</CheckboxRootContext.Provider>,\n      );\n    },\n  }));\n\n  it('should not render indicator by default', async () => {\n    await render(\n      <Checkbox.Root>\n        <Checkbox.Indicator data-testid=\"indicator\" />\n      </Checkbox.Root>,\n    );\n    const indicator = screen.queryByTestId('indicator');\n    expect(indicator).toBe(null);\n  });\n\n  it('should render indicator when checked', async () => {\n    await render(\n      <Checkbox.Root checked>\n        <Checkbox.Indicator data-testid=\"indicator\" />\n      </Checkbox.Root>,\n    );\n    const indicator = screen.getByTestId('indicator');\n    expect(indicator).not.toBe(null);\n  });\n\n  it('should spread extra props', async () => {\n    await render(\n      <Checkbox.Root defaultChecked>\n        <Checkbox.Indicator data-testid=\"indicator\" data-extra-prop=\"Lorem ipsum\" />\n      </Checkbox.Root>,\n    );\n    const indicator = screen.getByTestId('indicator');\n    expect(indicator).toHaveAttribute('data-extra-prop', 'Lorem ipsum');\n  });\n\n  describe('prop: keepMounted', () => {\n    it('should keep indicator mounted when unchecked', async () => {\n      await render(\n        <Checkbox.Root>\n          <Checkbox.Indicator data-testid=\"indicator\" keepMounted />\n        </Checkbox.Root>,\n      );\n      const indicator = screen.getByTestId('indicator');\n      expect(indicator).not.toBe(null);\n    });\n\n    it('should keep indicator mounted when checked', async () => {\n      await render(\n        <Checkbox.Root checked>\n          <Checkbox.Indicator data-testid=\"indicator\" keepMounted />\n        </Checkbox.Root>,\n      );\n      const indicator = screen.getByTestId('indicator');\n      expect(indicator).not.toBe(null);\n    });\n\n    it('should keep indicator mounted when indeterminate', async () => {\n      await render(\n        <Checkbox.Root indeterminate>\n          <Checkbox.Indicator data-testid=\"indicator\" keepMounted />\n        </Checkbox.Root>,\n      );\n      const indicator = screen.getByTestId('indicator');\n      expect(indicator).not.toBe(null);\n    });\n  });\n\n  it('should remove the indicator when there is no exit animation defined', async ({ skip }) => {\n    if (isJSDOM) {\n      skip();\n    }\n\n    function Test() {\n      const [checked, setChecked] = React.useState(true);\n      return (\n        <div>\n          <button onClick={() => setChecked(false)}>Close</button>\n          <Checkbox.Root checked={checked}>\n            <Checkbox.Indicator data-testid=\"indicator\" />\n          </Checkbox.Root>\n        </div>\n      );\n    }\n\n    const { user } = await render(<Test />);\n\n    expect(screen.getByTestId('indicator')).not.toBe(null);\n\n    const closeButton = screen.getByText('Close');\n\n    await user.click(closeButton);\n\n    await waitFor(() => {\n      expect(screen.queryByTestId('indicator')).toBe(null);\n    });\n  });\n\n  it('should remove the indicator when the animation finishes', async ({ skip }) => {\n    if (isJSDOM) {\n      skip();\n    }\n\n    globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n    let animationFinished = false;\n    const notifyAnimationFinished = () => {\n      animationFinished = true;\n    };\n\n    function Test() {\n      const style = `\n        @keyframes test-anim {\n          to {\n            opacity: 0;\n          }\n        }\n\n        .animation-test-indicator[data-ending-style] {\n          animation: test-anim 1ms;\n        }\n      `;\n\n      const [checked, setChecked] = React.useState(true);\n\n      return (\n        <div>\n          {/* eslint-disable-next-line react/no-danger */}\n          <style dangerouslySetInnerHTML={{ __html: style }} />\n          <button onClick={() => setChecked(false)}>Close</button>\n          <Checkbox.Root checked={checked}>\n            <Checkbox.Indicator\n              className=\"animation-test-indicator\"\n              data-testid=\"indicator\"\n              onAnimationEnd={notifyAnimationFinished}\n              keepMounted\n            />\n          </Checkbox.Root>\n        </div>\n      );\n    }\n\n    const { user } = await render(<Test />);\n    expect(screen.getByTestId('indicator')).not.toBe(null);\n\n    const closeButton = screen.getByText('Close');\n    await user.click(closeButton);\n\n    await waitFor(() => {\n      expect(animationFinished).toBe(true);\n    });\n  });\n\n  describe.skipIf(isJSDOM)('animations', () => {\n    afterEach(() => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = true;\n    });\n\n    it('triggers enter animation via data-starting-style when mounting', async () => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n      let transitionFinished = false;\n      function notifyTransitionFinished() {\n        transitionFinished = true;\n      }\n\n      const style = `\n        .animation-test-indicator {\n          transition: opacity 1ms;\n        }\n\n        .animation-test-indicator[data-starting-style],\n        .animation-test-indicator[data-ending-style] {\n          opacity: 0;\n        }\n      `;\n\n      function Test() {\n        const [checked, setChecked] = React.useState(false);\n\n        function handleCheck() {\n          setChecked(true);\n        }\n\n        return (\n          <div>\n            {/* eslint-disable-next-line react/no-danger */}\n            <style dangerouslySetInnerHTML={{ __html: style }} />\n            <button onClick={handleCheck}>Check</button>\n            <Checkbox.Root checked={checked}>\n              <Checkbox.Indicator\n                className=\"animation-test-indicator\"\n                data-testid=\"indicator\"\n                onTransitionEnd={notifyTransitionFinished}\n              />\n            </Checkbox.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<Test />);\n      expect(screen.queryByTestId('indicator')).toBe(null);\n\n      await user.click(screen.getByText('Check'));\n\n      await waitFor(() => {\n        expect(transitionFinished).toBe(true);\n      });\n\n      expect(screen.getByTestId('indicator')).not.toBe(null);\n    });\n\n    it('applies data-ending-style before unmount', async () => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n      const style = `\n        @keyframes test-anim {\n          to {\n            opacity: 0;\n          }\n        }\n\n        .animation-test-indicator[data-ending-style] {\n          animation: test-anim 1ms;\n        }\n      `;\n\n      function Test() {\n        const [checked, setChecked] = React.useState(true);\n\n        function handleUncheck() {\n          setChecked(false);\n        }\n\n        return (\n          <div>\n            {/* eslint-disable-next-line react/no-danger */}\n            <style dangerouslySetInnerHTML={{ __html: style }} />\n            <button onClick={handleUncheck}>Uncheck</button>\n            <Checkbox.Root checked={checked}>\n              <Checkbox.Indicator className=\"animation-test-indicator\" data-testid=\"indicator\" />\n            </Checkbox.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<Test />);\n      expect(screen.getByTestId('indicator')).not.toBe(null);\n\n      await user.click(screen.getByText('Uncheck'));\n\n      await waitFor(() => {\n        const indicator = screen.queryByTestId('indicator');\n        expect(indicator).not.toBe(null);\n        expect(indicator).toHaveAttribute('data-ending-style');\n      });\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('indicator')).toBe(null);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/checkbox/indicator/CheckboxIndicator.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useCheckboxRootContext } from '../root/CheckboxRootContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useStateAttributesMapping } from '../utils/useStateAttributesMapping';\nimport type { CheckboxRootState } from '../root/CheckboxRoot';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useOpenChangeComplete } from '../../utils/useOpenChangeComplete';\nimport { type TransitionStatus, useTransitionStatus } from '../../utils/useTransitionStatus';\nimport type { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport { transitionStatusMapping } from '../../utils/stateAttributesMapping';\nimport { fieldValidityMapping } from '../../field/utils/constants';\n\n/**\n * Indicates whether the checkbox is ticked.\n * Renders a `<span>` element.\n *\n * Documentation: [Base UI Checkbox](https://base-ui.com/react/components/checkbox)\n */\nexport const CheckboxIndicator = React.forwardRef(function CheckboxIndicator(\n  componentProps: CheckboxIndicator.Props,\n  forwardedRef: React.ForwardedRef<HTMLSpanElement>,\n) {\n  const { render, className, keepMounted = false, ...elementProps } = componentProps;\n\n  const rootState = useCheckboxRootContext();\n\n  const rendered = rootState.checked || rootState.indeterminate;\n\n  const { mounted, transitionStatus, setMounted } = useTransitionStatus(rendered);\n\n  const indicatorRef = React.useRef<HTMLSpanElement | null>(null);\n\n  const state: CheckboxIndicatorState = {\n    ...rootState,\n    transitionStatus,\n  };\n\n  useOpenChangeComplete({\n    open: rendered,\n    ref: indicatorRef,\n    onComplete() {\n      if (!rendered) {\n        setMounted(false);\n      }\n    },\n  });\n\n  const baseStateAttributesMapping = useStateAttributesMapping(rootState);\n\n  const stateAttributesMapping: StateAttributesMapping<CheckboxIndicatorState> = React.useMemo(\n    () => ({\n      ...baseStateAttributesMapping,\n      ...transitionStatusMapping,\n      ...fieldValidityMapping,\n    }),\n    [baseStateAttributesMapping],\n  );\n\n  const shouldRender = keepMounted || mounted;\n\n  const element = useRenderElement('span', componentProps, {\n    ref: [forwardedRef, indicatorRef],\n    state,\n    stateAttributesMapping,\n    props: elementProps,\n  });\n\n  if (!shouldRender) {\n    return null;\n  }\n\n  return element;\n});\n\nexport interface CheckboxIndicatorState extends CheckboxRootState {\n  /**\n   * The transition status of the component.\n   */\n  transitionStatus: TransitionStatus;\n}\n\nexport interface CheckboxIndicatorProps extends BaseUIComponentProps<\n  'span',\n  CheckboxIndicatorState\n> {\n  /**\n   * Whether to keep the element in the DOM when the checkbox is not checked.\n   * @default false\n   */\n  keepMounted?: boolean | undefined;\n}\n\nexport namespace CheckboxIndicator {\n  export type State = CheckboxIndicatorState;\n  export type Props = CheckboxIndicatorProps;\n}\n"
  },
  {
    "path": "packages/react/src/checkbox/indicator/CheckboxIndicatorDataAttributes.ts",
    "content": "import { TransitionStatusDataAttributes } from '../../utils/stateAttributesMapping';\n\nexport enum CheckboxIndicatorDataAttributes {\n  /**\n   * Present when the checkbox is checked.\n   */\n  checked = 'data-checked',\n  /**\n   * Present when the checkbox is not checked.\n   */\n  unchecked = 'data-unchecked',\n  /**\n   * Present when the checkbox is in an indeterminate state.\n   */\n  indeterminate = 'data-indeterminate',\n  /**\n   * Present when the checkbox is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the checkbox is readonly.\n   */\n  readonly = 'data-readonly',\n  /**\n   * Present when the checkbox is required.\n   */\n  required = 'data-required',\n  /**\n   * Present when the checkbox indicator is animating in.\n   */\n  startingStyle = TransitionStatusDataAttributes.startingStyle,\n  /**\n   * Present when the checkbox indicator is animating out.\n   */\n  endingStyle = TransitionStatusDataAttributes.endingStyle,\n  /**\n   * Present when the checkbox is in valid state (when wrapped in Field.Root).\n   */\n  valid = 'data-valid',\n  /**\n   * Present when the checkbox is in invalid state (when wrapped in Field.Root).\n   */\n  invalid = 'data-invalid',\n  /**\n   * Present when the checkbox has been touched (when wrapped in Field.Root).\n   */\n  touched = 'data-touched',\n  /**\n   * Present when the checkbox's value has changed (when wrapped in Field.Root).\n   */\n  dirty = 'data-dirty',\n  /**\n   * Present when the checkbox is checked (when wrapped in Field.Root).\n   */\n  filled = 'data-filled',\n  /**\n   * Present when the checkbox is focused (when wrapped in Field.Root).\n   */\n  focused = 'data-focused',\n}\n"
  },
  {
    "path": "packages/react/src/checkbox/root/CheckboxRoot.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { act, fireEvent, screen, waitFor } from '@mui/internal-test-utils';\nimport { Checkbox } from '@base-ui/react/checkbox';\nimport { CheckboxGroup } from '@base-ui/react/checkbox-group';\nimport { Field } from '@base-ui/react/field';\nimport { Form } from '@base-ui/react/form';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\n\ndescribe('<Checkbox.Root />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Checkbox.Root />, () => ({\n    refInstanceof: window.HTMLSpanElement,\n    testComponentPropWith: 'span',\n    button: true,\n    render,\n  }));\n\n  describe('ARIA attributes', () => {\n    it('sets the correct aria attributes', async () => {\n      const { setProps } = await render(<Checkbox.Root data-testid=\"test\" required={false} />);\n\n      expect(screen.getByRole('checkbox')).toBe(screen.getByTestId('test'));\n      expect(screen.getByRole('checkbox')).toHaveAttribute('aria-checked');\n      await setProps({ required: true });\n      expect(screen.getByRole('checkbox')).toHaveAttribute('aria-required', 'true');\n    });\n  });\n\n  describe('extra props', () => {\n    it('can override the built-in attributes', async () => {\n      const { container } = await render(<Checkbox.Root role=\"switch\" />);\n      expect(container.firstElementChild as HTMLElement).toHaveAttribute('role', 'switch');\n    });\n  });\n\n  describe('interactions', () => {\n    it('should change its state when clicked', async () => {\n      await render(<Checkbox.Root />);\n      const [checkbox] = screen.getAllByRole('checkbox');\n      // querying it separately since hidden true returns both button and input.\n      // without hidden it only returns the button (in the above query)\n      const [, input] = screen.getAllByRole<HTMLInputElement>('checkbox', {\n        hidden: true,\n      });\n\n      expect(checkbox).toHaveAttribute('aria-checked', 'false');\n      expect(input.checked).toBe(false);\n\n      await act(async () => {\n        checkbox.click();\n      });\n\n      expect(checkbox).toHaveAttribute('aria-checked', 'true');\n      expect(input.checked).toBe(true);\n\n      await act(async () => {\n        checkbox.click();\n      });\n\n      expect(checkbox).toHaveAttribute('aria-checked', 'false');\n      expect(input.checked).toBe(false);\n    });\n\n    it('should update its state when changed from outside', async () => {\n      function Test() {\n        const [checked, setChecked] = React.useState(false);\n        return (\n          <div>\n            <button onClick={() => setChecked((c) => !c)}>Toggle</button>\n            <Checkbox.Root checked={checked} />;\n          </div>\n        );\n      }\n\n      await render(<Test />);\n      const [checkbox] = screen.getAllByRole('checkbox');\n      const button = screen.getByText('Toggle');\n\n      expect(checkbox).toHaveAttribute('aria-checked', 'false');\n      await act(async () => {\n        button.click();\n      });\n\n      expect(checkbox).toHaveAttribute('aria-checked', 'true');\n\n      await act(async () => {\n        button.click();\n      });\n\n      expect(checkbox).toHaveAttribute('aria-checked', 'false');\n    });\n\n    it('should call onCheckedChange when clicked', async () => {\n      const handleChange = vi.fn();\n      await render(<Checkbox.Root onCheckedChange={handleChange} />);\n      const [checkbox] = screen.getAllByRole('checkbox');\n\n      await act(async () => {\n        checkbox.click();\n      });\n\n      expect(handleChange.mock.calls.length).toBe(1);\n      expect(handleChange.mock.calls[0][0]).toBe(true);\n    });\n\n    it('should report keyboard modifier event properties when calling onCheckedChange', async () => {\n      const handleChange = vi.fn((checked, eventDetails) => eventDetails);\n      const { user } = await render(<Checkbox.Root onCheckedChange={handleChange} />);\n      const [checkbox] = screen.getAllByRole('checkbox');\n\n      await user.keyboard('{Shift>}');\n      await user.click(checkbox);\n      await user.keyboard('{/Shift}');\n\n      expect(handleChange.mock.calls.length).toBe(1);\n      expect(handleChange.mock.results[0]?.value.event.shiftKey).toBe(true);\n    });\n\n    it('should update its state if the underlying input is toggled', async () => {\n      await render(<Checkbox.Root />);\n      const checkbox = screen.getByRole('checkbox');\n      const internalInput = document.querySelector<HTMLInputElement>('input[type=\"checkbox\"]');\n\n      await act(async () => {\n        internalInput?.click();\n      });\n\n      expect(checkbox).toHaveAttribute('aria-checked', 'true');\n    });\n\n    ['Enter', 'Space'].forEach((key) => {\n      it(`can be activated with ${key} key`, async () => {\n        const { user } = await render(<Checkbox.Root />);\n\n        const checkbox = screen.getByRole('checkbox');\n        expect(checkbox).toHaveAttribute('aria-checked', 'false');\n\n        await user.keyboard('[Tab]');\n        expect(checkbox).toHaveFocus();\n\n        await user.keyboard(`[${key}]`);\n        expect(checkbox).toHaveAttribute('aria-checked', 'true');\n      });\n    });\n  });\n\n  describe('prop: disabled', () => {\n    it('uses aria-disabled instead of HTML disabled', async () => {\n      await render(<Checkbox.Root disabled />);\n      expect(screen.getByRole('checkbox')).not.toHaveAttribute('disabled');\n      expect(screen.getByRole('checkbox')).toHaveAttribute('aria-disabled', 'true');\n    });\n\n    it('should not change its state when clicked', async () => {\n      await render(<Checkbox.Root disabled />);\n      const [checkbox] = screen.getAllByRole('checkbox');\n\n      expect(checkbox).toHaveAttribute('aria-checked', 'false');\n\n      await act(async () => {\n        checkbox.click();\n      });\n\n      expect(checkbox).toHaveAttribute('aria-checked', 'false');\n    });\n  });\n\n  describe('prop: readOnly', () => {\n    it('should have the `aria-readonly` attribute', async () => {\n      await render(<Checkbox.Root readOnly />);\n      expect(screen.getAllByRole('checkbox')[0]).toHaveAttribute('aria-readonly', 'true');\n    });\n\n    it('should not have the aria attribute when `readOnly` is not set', async () => {\n      await render(<Checkbox.Root />);\n      expect(screen.getAllByRole('checkbox')[0]).not.toHaveAttribute('aria-readonly');\n    });\n\n    it('should not change its state when clicked', async () => {\n      await render(<Checkbox.Root readOnly />);\n      const [checkbox] = screen.getAllByRole('checkbox');\n\n      expect(checkbox).toHaveAttribute('aria-checked', 'false');\n\n      await act(async () => {\n        checkbox.click();\n      });\n\n      expect(checkbox).toHaveAttribute('aria-checked', 'false');\n    });\n  });\n\n  describe('prop: indeterminate', () => {\n    it('should set the `aria-checked` attribute as \"mixed\"', async () => {\n      await render(<Checkbox.Root indeterminate />);\n      expect(screen.getAllByRole('checkbox')[0]).toHaveAttribute('aria-checked', 'mixed');\n    });\n\n    it('should not change its state when clicked', async () => {\n      await render(<Checkbox.Root indeterminate />);\n      const [checkbox] = screen.getAllByRole('checkbox');\n\n      expect(checkbox).toHaveAttribute('aria-checked', 'mixed');\n\n      await act(async () => {\n        checkbox.click();\n      });\n\n      expect(checkbox).toHaveAttribute('aria-checked', 'mixed');\n    });\n\n    it('should not have the aria attribute when `indeterminate` is not set', async () => {\n      await render(<Checkbox.Root />);\n      expect(screen.getAllByRole('checkbox')[0]).not.toHaveAttribute('aria-checked', 'mixed');\n    });\n\n    it('should not be overridden by `checked` prop', async () => {\n      await render(<Checkbox.Root indeterminate checked />);\n      expect(screen.getAllByRole('checkbox')[0]).toHaveAttribute('aria-checked', 'mixed');\n    });\n  });\n\n  it('should update its state if the underlying input is toggled', async () => {\n    await render(<Checkbox.Root />);\n    const [checkbox] = screen.getAllByRole('checkbox');\n    const [, input] = screen.getAllByRole<HTMLInputElement>('checkbox', {\n      hidden: true,\n    });\n\n    await act(async () => {\n      input.click();\n    });\n\n    expect(checkbox).toHaveAttribute('aria-checked', 'true');\n  });\n\n  it('should place the style hooks on the root and the indicator', async () => {\n    const { setProps } = await render(\n      <Checkbox.Root defaultChecked disabled readOnly required>\n        <Checkbox.Indicator />\n      </Checkbox.Root>,\n    );\n\n    const [checkbox] = screen.getAllByRole('checkbox');\n    const indicator = checkbox.querySelector('span');\n\n    expect(checkbox).toHaveAttribute('data-checked', '');\n    expect(checkbox).not.toHaveAttribute('data-unchecked');\n\n    expect(checkbox).toHaveAttribute('data-disabled', '');\n    expect(checkbox).toHaveAttribute('data-readonly', '');\n    expect(checkbox).toHaveAttribute('data-required', '');\n\n    expect(indicator).toHaveAttribute('data-checked', '');\n    expect(indicator).not.toHaveAttribute('data-unchecked');\n\n    expect(indicator).toHaveAttribute('data-disabled', '');\n    expect(indicator).toHaveAttribute('data-readonly', '');\n    expect(indicator).toHaveAttribute('data-required', '');\n\n    await setProps({ disabled: false, readOnly: false });\n    fireEvent.click(checkbox);\n\n    expect(checkbox).toHaveAttribute('data-unchecked', '');\n    expect(checkbox).not.toHaveAttribute('data-checked');\n  });\n\n  it('should set the name attribute only on the input', async () => {\n    await render(<Checkbox.Root name=\"checkbox-name\" />);\n\n    const [, input] = screen.getAllByRole<HTMLInputElement>('checkbox', {\n      hidden: true,\n    });\n    expect(input).toHaveAttribute('name', 'checkbox-name');\n    expect(screen.getByRole('checkbox')).not.toHaveAttribute('name');\n  });\n\n  // flaky with user.click\n  describe('with native <label>', () => {\n    it('should toggle the checkbox when a wrapping <label> is clicked', async () => {\n      await render(\n        <label data-testid=\"label\">\n          <Checkbox.Root />\n          Toggle\n        </label>,\n      );\n\n      const checkbox = screen.getByRole('checkbox');\n      expect(checkbox).toHaveAttribute('aria-checked', 'false');\n\n      fireEvent.click(screen.getByTestId('label'));\n      expect(checkbox).toHaveAttribute('aria-checked', 'true');\n    });\n\n    it('should toggle the checkbox when a explicitly linked <label> is clicked', async () => {\n      await render(\n        <div>\n          <label data-testid=\"label\" htmlFor=\"myCheckbox\">\n            Toggle\n          </label>\n\n          <Checkbox.Root id=\"myCheckbox\" />\n        </div>,\n      );\n\n      const checkbox = screen.getByRole('checkbox');\n      expect(checkbox).toHaveAttribute('aria-checked', 'false');\n\n      fireEvent.click(screen.getByTestId('label'));\n      expect(checkbox).toHaveAttribute('aria-checked', 'true');\n    });\n\n    it('should associate `id` with the native button when `nativeButton=true`', async () => {\n      await render(\n        <div>\n          <label data-testid=\"label\" htmlFor=\"myCheckbox\">\n            Toggle\n          </label>\n\n          <Checkbox.Root id=\"myCheckbox\" nativeButton render={<button />} />\n        </div>,\n      );\n\n      const checkbox = screen.getByRole('checkbox');\n      expect(checkbox).toHaveAttribute('id', 'myCheckbox');\n\n      const hiddenInputs = screen.getAllByRole<HTMLInputElement>('checkbox', { hidden: true });\n      const hiddenInput = hiddenInputs.find((input) => input !== checkbox);\n      expect(hiddenInput).not.toBe(undefined);\n      expect(hiddenInput).not.toHaveAttribute('id', 'myCheckbox');\n\n      expect(checkbox).toHaveAttribute('aria-checked', 'false');\n      fireEvent.click(screen.getByTestId('label'));\n      expect(checkbox).toHaveAttribute('aria-checked', 'true');\n    });\n  });\n\n  describe('Form', () => {\n    it('triggers native HTML validation on submit', async () => {\n      const { user } = await render(\n        <Form>\n          <Field.Root name=\"test\" data-testid=\"field\">\n            <Checkbox.Root required />\n            <Field.Error match=\"valueMissing\" data-testid=\"error\">\n              required\n            </Field.Error>\n          </Field.Root>\n          <button type=\"submit\">Submit</button>\n        </Form>,\n      );\n\n      const submit = screen.getByText('Submit');\n\n      expect(screen.queryByTestId('error')).toBe(null);\n\n      await user.click(submit);\n\n      const error = screen.getByTestId('error');\n      expect(error).toHaveTextContent('required');\n    });\n\n    it('clears external errors on change', async () => {\n      await render(\n        <Form\n          errors={{\n            test: 'test',\n          }}\n        >\n          <Field.Root name=\"test\" data-testid=\"field\">\n            <Checkbox.Root data-testid=\"checkbox\" />\n            <Field.Error data-testid=\"error\" />\n          </Field.Root>\n        </Form>,\n      );\n\n      const checkbox = screen.getByTestId('checkbox');\n\n      expect(checkbox).toHaveAttribute('aria-invalid', 'true');\n      expect(screen.queryByTestId('error')).toHaveTextContent('test');\n\n      fireEvent.click(checkbox);\n\n      expect(checkbox).not.toHaveAttribute('aria-invalid');\n      expect(screen.queryByTestId('error')).toBe(null);\n    });\n\n    it.skipIf(isJSDOM)(\n      'should include the checkbox value in form submission, matching native checkbox behavior',\n      async () => {\n        const submitSpy = vi.fn((event) => {\n          event.preventDefault();\n          const formData = new FormData(event.currentTarget);\n          return formData.get('test-checkbox');\n        });\n\n        await render(\n          <Form onSubmit={submitSpy}>\n            <Field.Root name=\"test-checkbox\">\n              <Checkbox.Root />\n            </Field.Root>\n            <button type=\"submit\">Submit</button>\n          </Form>,\n        );\n\n        const checkbox = screen.getByRole('checkbox');\n        const submitButton = screen.getByRole('button')!;\n\n        await act(async () => {\n          submitButton.click();\n        });\n\n        expect(submitSpy.mock.calls.length).toBe(1);\n        expect(submitSpy.mock.results.at(-1)?.value).toBe(null);\n\n        await act(async () => {\n          checkbox.click();\n        });\n\n        await act(async () => {\n          submitButton.click();\n        });\n\n        expect(submitSpy.mock.calls.length).toBe(2);\n        expect(submitSpy.mock.results.at(-1)?.value).toBe('on');\n      },\n    );\n\n    it.skipIf(isJSDOM)(\n      'should include the custom checkbox value in form submission, matching native checkbox behavior',\n      async () => {\n        const submitSpy = vi.fn((event) => {\n          event.preventDefault();\n          const formData = new FormData(event.currentTarget);\n          return formData.get('test-checkbox');\n        });\n\n        await render(\n          <Form onSubmit={submitSpy}>\n            <Field.Root name=\"test-checkbox\">\n              <Checkbox.Root value=\"test-value\" />\n            </Field.Root>\n            <button type=\"submit\">Submit</button>\n          </Form>,\n        );\n\n        const checkbox = screen.getByRole('checkbox');\n        const submitButton = screen.getByRole('button')!;\n\n        await act(async () => {\n          submitButton.click();\n        });\n\n        expect(submitSpy.mock.calls.length).toBe(1);\n        expect(submitSpy.mock.results.at(-1)?.value).toBe(null);\n\n        await act(async () => {\n          checkbox.click();\n        });\n\n        await act(async () => {\n          submitButton.click();\n        });\n\n        expect(submitSpy.mock.calls.length).toBe(2);\n        expect(submitSpy.mock.results.at(-1)?.value).toBe('test-value');\n      },\n    );\n\n    it.skipIf(isJSDOM)('matches native checkbox form submission behavior', async () => {\n      const nativeSubmitSpy = vi.fn((event) => {\n        event.preventDefault();\n        const formData = new FormData(event.currentTarget);\n        return {\n          get: formData.get('native'),\n          getAll: formData.getAll('native'),\n        };\n      });\n\n      const customSubmitSpy = vi.fn((event) => {\n        event.preventDefault();\n        const formData = new FormData(event.currentTarget);\n        return {\n          get: formData.get('custom'),\n          getAll: formData.getAll('custom'),\n        };\n      });\n\n      const { user: nativeUser } = await render(\n        <form onSubmit={nativeSubmitSpy}>\n          <input type=\"checkbox\" name=\"native\" />\n          <button type=\"submit\">Submit</button>\n        </form>,\n      );\n\n      const nativeCheckbox = screen.getByRole('checkbox');\n      const nativeSubmitButton = screen.getByRole('button')!;\n\n      await nativeUser.click(nativeSubmitButton);\n      expect(nativeSubmitSpy.mock.results.at(-1)?.value.get).toBe(null);\n      expect(nativeSubmitSpy.mock.results.at(-1)?.value.getAll).toEqual([]);\n\n      await nativeUser.click(nativeCheckbox);\n      await nativeUser.click(nativeSubmitButton);\n      expect(nativeSubmitSpy.mock.results.at(-1)?.value.get).toBe('on');\n\n      const { user: customUser } = await render(\n        <Form onSubmit={customSubmitSpy}>\n          <Field.Root name=\"custom\">\n            <Checkbox.Root />\n          </Field.Root>\n          <button type=\"submit\">Submit</button>\n        </Form>,\n      );\n\n      const customCheckbox = screen.getAllByRole('checkbox')[1];\n      const customSubmitButton = screen.getAllByRole('button')[1]!;\n\n      await customUser.click(customSubmitButton);\n      expect(customSubmitSpy.mock.results.at(-1)?.value.get).toBe(null);\n      expect(customSubmitSpy.mock.results.at(-1)?.value.getAll).toEqual([]);\n\n      await customUser.click(customCheckbox);\n      await customUser.click(customSubmitButton);\n      expect(customSubmitSpy.mock.results.at(-1)?.value.get).toBe('on');\n    });\n\n    it.skipIf(isJSDOM)(\n      'should submit uncheckedValue when checkbox is unchecked and uncheckedValue is specified',\n      async () => {\n        const submitSpy = vi.fn((event) => {\n          event.preventDefault();\n          const formData = new FormData(event.currentTarget);\n          return formData.get('test-checkbox');\n        });\n\n        await render(\n          <Form onSubmit={submitSpy}>\n            <Field.Root name=\"test-checkbox\">\n              <Checkbox.Root uncheckedValue=\"off\" />\n            </Field.Root>\n            <button type=\"submit\">Submit</button>\n          </Form>,\n        );\n\n        const checkbox = screen.getByRole('checkbox');\n        const submitButton = screen.getByRole('button')!;\n\n        await act(async () => {\n          submitButton.click();\n        });\n\n        expect(submitSpy.mock.calls.length).toBe(1);\n        expect(submitSpy.mock.results.at(-1)?.value).toBe('off');\n\n        await act(async () => {\n          checkbox.click();\n        });\n\n        await act(async () => {\n          submitButton.click();\n        });\n\n        expect(submitSpy.mock.calls.length).toBe(2);\n        expect(submitSpy.mock.results.at(-1)?.value).toBe('on');\n\n        await act(async () => {\n          checkbox.click();\n        });\n\n        await act(async () => {\n          submitButton.click();\n        });\n\n        expect(submitSpy.mock.calls.length).toBe(3);\n        expect(submitSpy.mock.results.at(-1)?.value).toBe('off');\n      },\n    );\n\n    it.skipIf(isJSDOM)(\n      'should submit custom uncheckedValue when checkbox is unchecked',\n      async () => {\n        const submitSpy = vi.fn((event) => {\n          event.preventDefault();\n          const formData = new FormData(event.currentTarget);\n          return formData.get('test-checkbox');\n        });\n\n        await render(\n          <Form onSubmit={submitSpy}>\n            <Field.Root name=\"test-checkbox\">\n              <Checkbox.Root uncheckedValue=\"false\" value=\"true\" />\n            </Field.Root>\n            <button type=\"submit\">Submit</button>\n          </Form>,\n        );\n\n        const checkbox = screen.getByRole('checkbox');\n        const submitButton = screen.getByRole('button')!;\n\n        await act(async () => {\n          submitButton.click();\n        });\n\n        expect(submitSpy.mock.calls.length).toBe(1);\n        expect(submitSpy.mock.results.at(-1)?.value).toBe('false');\n\n        await act(async () => {\n          checkbox.click();\n        });\n\n        await act(async () => {\n          submitButton.click();\n        });\n\n        expect(submitSpy.mock.calls.length).toBe(2);\n        expect(submitSpy.mock.results.at(-1)?.value).toBe('true');\n      },\n    );\n  });\n\n  describe('Field', () => {\n    it('should receive disabled prop from Field.Root', async () => {\n      await render(\n        <Field.Root disabled>\n          <Checkbox.Root />\n        </Field.Root>,\n      );\n\n      const [checkbox] = screen.getAllByRole('checkbox');\n      expect(checkbox).toHaveAttribute('aria-disabled', 'true');\n    });\n\n    it('should receive name prop from Field.Root', async () => {\n      await render(\n        <Field.Root name=\"field-checkbox\">\n          <Checkbox.Root />\n        </Field.Root>,\n      );\n\n      const [, input] = screen.getAllByRole<HTMLInputElement>('checkbox', {\n        hidden: true,\n      });\n      expect(input).toHaveAttribute('name', 'field-checkbox');\n    });\n\n    it('[data-touched]', async () => {\n      await render(\n        <Field.Root>\n          <Checkbox.Root data-testid=\"button\" />\n        </Field.Root>,\n      );\n\n      const button = screen.getByTestId('button');\n\n      fireEvent.focus(button);\n      fireEvent.blur(button);\n\n      expect(button).toHaveAttribute('data-touched', '');\n    });\n\n    it('[data-dirty]', async () => {\n      await render(\n        <Field.Root>\n          <Checkbox.Root data-testid=\"button\" />\n        </Field.Root>,\n      );\n\n      const button = screen.getByTestId('button');\n\n      expect(button).not.toHaveAttribute('data-dirty');\n\n      fireEvent.click(button);\n\n      expect(button).toHaveAttribute('data-dirty', '');\n    });\n\n    describe('[data-filled]', () => {\n      it('adds [data-filled] attribute when checked after being initially unchecked', async () => {\n        await render(\n          <Field.Root>\n            <Checkbox.Root data-testid=\"button\" />\n          </Field.Root>,\n        );\n\n        const button = screen.getByTestId('button');\n\n        expect(button).not.toHaveAttribute('data-filled');\n\n        fireEvent.click(button);\n\n        expect(button).toHaveAttribute('data-filled', '');\n\n        fireEvent.click(button);\n\n        expect(button).not.toHaveAttribute('data-filled');\n      });\n\n      it('removes [data-filled] attribute when unchecked after being initially checked', async () => {\n        await render(\n          <Field.Root>\n            <Checkbox.Root data-testid=\"button\" defaultChecked />\n          </Field.Root>,\n        );\n\n        const button = screen.getByTestId('button');\n\n        expect(button).toHaveAttribute('data-filled');\n\n        fireEvent.click(button);\n\n        expect(button).not.toHaveAttribute('data-filled', '');\n      });\n\n      it('adds [data-filled] attribute when any checkbox is filled when inside a group', async () => {\n        await render(\n          <Field.Root>\n            <CheckboxGroup defaultValue={['1', '2']}>\n              <Checkbox.Root name=\"1\" data-testid=\"button-1\" />\n              <Checkbox.Root name=\"2\" data-testid=\"button-2\" />\n            </CheckboxGroup>\n          </Field.Root>,\n        );\n\n        const button1 = screen.getByTestId('button-1');\n        const button2 = screen.getByTestId('button-2');\n\n        expect(button1).toHaveAttribute('data-filled');\n        expect(button2).toHaveAttribute('data-filled');\n\n        fireEvent.click(button1);\n\n        expect(button1).toHaveAttribute('data-filled');\n        expect(button2).toHaveAttribute('data-filled');\n\n        fireEvent.click(button2);\n\n        expect(button1).not.toHaveAttribute('data-filled');\n        expect(button2).not.toHaveAttribute('data-filled');\n      });\n    });\n\n    it('[data-focused]', async () => {\n      await render(\n        <Field.Root>\n          <Checkbox.Root data-testid=\"button\" />\n        </Field.Root>,\n      );\n\n      const button = screen.getByTestId('button');\n\n      expect(button).not.toHaveAttribute('data-focused');\n\n      fireEvent.focus(button);\n\n      expect(button).toHaveAttribute('data-focused', '');\n\n      fireEvent.blur(button);\n\n      expect(button).not.toHaveAttribute('data-focused');\n    });\n\n    it('[data-invalid]', async () => {\n      await render(\n        <Field.Root invalid>\n          <Checkbox.Root data-testid=\"button\" />\n        </Field.Root>,\n      );\n\n      const button = screen.getByTestId('button');\n\n      expect(button).toHaveAttribute('data-invalid', '');\n    });\n\n    it('[data-valid]', async () => {\n      await render(\n        <Field.Root validationMode=\"onBlur\">\n          <Checkbox.Root data-testid=\"button\" required />\n        </Field.Root>,\n      );\n\n      const button = screen.getByTestId('button');\n\n      expect(button).not.toHaveAttribute('data-valid');\n      expect(button).not.toHaveAttribute('data-invalid');\n\n      // Check the checkbox and trigger validation\n      fireEvent.click(button);\n      fireEvent.focus(button);\n      fireEvent.blur(button);\n\n      expect(button).toHaveAttribute('data-valid', '');\n      expect(button).not.toHaveAttribute('data-invalid');\n    });\n\n    it('prop: validationMode=onSubmit', async () => {\n      await render(\n        <Form>\n          <Field.Root>\n            <Checkbox.Root required />\n            <Field.Error data-testid=\"error\" />\n          </Field.Root>\n          <button type=\"submit\">submit</button>\n        </Form>,\n      );\n\n      const checkbox = screen.getByRole('checkbox');\n      expect(checkbox).not.toHaveAttribute('aria-invalid');\n\n      fireEvent.click(checkbox);\n      expect(checkbox).toHaveAttribute('data-checked', '');\n      fireEvent.click(checkbox);\n      expect(checkbox).toHaveAttribute('data-unchecked', '');\n      expect(checkbox).not.toHaveAttribute('aria-invalid');\n\n      fireEvent.click(screen.getByText('submit'));\n      expect(checkbox).toHaveAttribute('aria-invalid', 'true');\n\n      fireEvent.click(checkbox);\n      expect(checkbox).toHaveAttribute('data-checked', '');\n      expect(checkbox).not.toHaveAttribute('aria-invalid');\n\n      fireEvent.click(checkbox);\n      expect(checkbox).toHaveAttribute('data-unchecked', '');\n      expect(checkbox).toHaveAttribute('aria-invalid');\n\n      fireEvent.click(checkbox);\n      expect(checkbox).toHaveAttribute('data-checked', '');\n      expect(checkbox).not.toHaveAttribute('aria-invalid');\n    });\n\n    it('props: validationMode=onChange', async () => {\n      await render(\n        <Field.Root\n          validationMode=\"onChange\"\n          validate={(value) => {\n            const checked = value as boolean;\n            return checked ? 'error' : null;\n          }}\n        >\n          <Checkbox.Root data-testid=\"button\" />\n        </Field.Root>,\n      );\n\n      const button = screen.getByTestId('button');\n\n      expect(button).not.toHaveAttribute('aria-invalid');\n\n      fireEvent.click(button);\n\n      expect(button).toHaveAttribute('aria-invalid', 'true');\n    });\n\n    it('revalidates when a controlled value changes externally', async () => {\n      const validateSpy = vi.fn((value: unknown) => ((value as boolean) ? 'error' : null));\n\n      function App() {\n        const [checked, setChecked] = React.useState(false);\n\n        return (\n          <React.Fragment>\n            <Field.Root validationMode=\"onChange\" validate={validateSpy} name=\"terms\">\n              <Checkbox.Root data-testid=\"button\" checked={checked} onCheckedChange={setChecked} />\n            </Field.Root>\n            <button type=\"button\" onClick={() => setChecked((prev) => !prev)}>\n              Toggle externally\n            </button>\n          </React.Fragment>\n        );\n      }\n\n      await render(<App />);\n\n      const button = screen.getByTestId('button');\n      const toggle = screen.getByText('Toggle externally');\n\n      expect(button).not.toHaveAttribute('aria-invalid');\n      const initialCallCount = validateSpy.mock.calls.length;\n\n      fireEvent.click(toggle);\n\n      expect(validateSpy.mock.calls.length).toBe(initialCallCount + 1);\n      expect(validateSpy.mock.lastCall?.[0]).toBe(true);\n      expect(button).toHaveAttribute('aria-invalid', 'true');\n    });\n\n    it('prop: validationMode=onBlur', async () => {\n      await render(\n        <Field.Root\n          validationMode=\"onBlur\"\n          validate={(value) => {\n            const checked = value as boolean;\n            return checked ? 'error' : null;\n          }}\n        >\n          <Checkbox.Root data-testid=\"button\" />\n          <Field.Error data-testid=\"error\" />\n        </Field.Root>,\n      );\n\n      const button = screen.getByTestId('button');\n\n      expect(button).not.toHaveAttribute('aria-invalid');\n\n      fireEvent.click(button);\n      fireEvent.blur(button);\n\n      expect(button).toHaveAttribute('aria-invalid', 'true');\n    });\n\n    describe('Field.Label', () => {\n      describe('explicit association', () => {\n        it('when label and checkbox are siblings', async () => {\n          await render(\n            <Field.Root>\n              <Field.Label>Label</Field.Label>\n              <Checkbox.Root />\n            </Field.Root>,\n          );\n\n          const label = screen.getByText('Label');\n          expect(label.getAttribute('id')).not.toBe(null);\n\n          const input = document.querySelector('input[type=\"checkbox\"]');\n          expect(label.getAttribute('for')).toBe(input?.getAttribute('id'));\n\n          const checkbox = screen.getByRole('checkbox');\n          expect(checkbox.getAttribute('aria-labelledby')).toBe(label.getAttribute('id'));\n          expect(checkbox).toHaveAttribute('aria-checked', 'false');\n\n          fireEvent.click(label);\n          expect(checkbox).toHaveAttribute('aria-checked', 'true');\n        });\n      });\n\n      describe('implicit association', () => {\n        it('sets `for` on the label', async () => {\n          await render(\n            <Field.Root>\n              <Field.Label data-testid=\"label\">\n                <Checkbox.Root />\n                OK\n              </Field.Label>\n            </Field.Root>,\n          );\n\n          const label = screen.getByTestId('label');\n          const input = document.querySelector('input[type=\"checkbox\"]');\n          expect(label.getAttribute('for')).not.toBe(null);\n          expect(label.getAttribute('for')).toBe(input?.getAttribute('id'));\n\n          const checkbox = screen.getByRole('checkbox');\n          expect(label.getAttribute('id')).not.toBe(null);\n          expect(checkbox.getAttribute('aria-labelledby')).toBe(label.getAttribute('id'));\n\n          expect(checkbox).toHaveAttribute('aria-checked', 'false');\n          fireEvent.click(screen.getByText('OK'));\n          expect(checkbox).toHaveAttribute('aria-checked', 'true');\n        });\n      });\n    });\n\n    it('Field.Description', async () => {\n      await render(\n        <Field.Root>\n          <Checkbox.Root data-testid=\"button\" />\n          <Field.Description data-testid=\"description\" />\n        </Field.Root>,\n      );\n\n      const internalInput = screen.getByRole<HTMLInputElement>('checkbox');\n\n      expect(internalInput).toHaveAttribute(\n        'aria-describedby',\n        screen.getByTestId('description').id,\n      );\n    });\n  });\n\n  it('should change state when clicking the checkbox if it has a wrapping label', async () => {\n    await render(\n      <label data-testid=\"label\">\n        <Checkbox.Root />\n        Toggle\n      </label>,\n    );\n\n    const [checkbox] = screen.getAllByRole('checkbox');\n\n    expect(checkbox).toHaveAttribute('aria-checked', 'false');\n\n    fireEvent.click(checkbox);\n\n    expect(checkbox).toHaveAttribute('aria-checked', 'true');\n\n    fireEvent.click(checkbox);\n\n    expect(checkbox).toHaveAttribute('aria-checked', 'false');\n  });\n\n  it('sets `aria-labelledby` from a sibling label associated with the hidden input', async () => {\n    await render(\n      <div>\n        <label htmlFor=\"checkbox-input\">Label</label>\n        <Checkbox.Root id=\"checkbox-input\" />\n      </div>,\n    );\n\n    const label = screen.getByText('Label');\n    expect(label.id).not.toBe('');\n    expect(screen.getByRole('checkbox')).toHaveAttribute('aria-labelledby', label.id);\n  });\n\n  it('updates fallback `aria-labelledby` when the hidden input id changes', async () => {\n    function TestCase() {\n      const [id, setId] = React.useState('checkbox-input-a');\n\n      return (\n        <React.Fragment>\n          <label htmlFor=\"checkbox-input-a\">Label A</label>\n          <label htmlFor=\"checkbox-input-b\">Label B</label>\n          <Checkbox.Root id={id} />\n          <button type=\"button\" onClick={() => setId('checkbox-input-b')}>\n            Toggle\n          </button>\n        </React.Fragment>\n      );\n    }\n\n    await render(<TestCase />);\n\n    const checkbox = screen.getByRole('checkbox');\n    const labelA = screen.getByText('Label A');\n\n    expect(labelA.id).not.toBe('');\n    expect(checkbox).toHaveAttribute('aria-labelledby', labelA.id);\n\n    fireEvent.click(screen.getByRole('button', { name: 'Toggle' }));\n\n    await waitFor(() => {\n      const labelB = screen.getByText('Label B');\n\n      expect(labelB.id).not.toBe('');\n      expect(labelA.id).not.toBe(labelB.id);\n      expect(checkbox).toHaveAttribute('aria-labelledby', labelB.id);\n    });\n  });\n\n  it('can render a native button', async () => {\n    const { container, user } = await render(<Checkbox.Root render={<button />} nativeButton />);\n\n    const checkbox = screen.getByRole('checkbox');\n    expect(checkbox).toHaveAttribute('aria-checked', 'false');\n    // eslint-disable-next-line testing-library/no-container\n    expect(container.querySelector('button')).toBe(checkbox);\n\n    await user.keyboard('[Tab]');\n    expect(checkbox).toHaveFocus();\n\n    await user.keyboard('[Enter]');\n    expect(checkbox).toHaveAttribute('aria-checked', 'true');\n\n    await user.keyboard('[Space]');\n    expect(checkbox).toHaveAttribute('aria-checked', 'false');\n\n    await user.click(checkbox);\n    expect(checkbox).toHaveAttribute('aria-checked', 'true');\n  });\n});\n"
  },
  {
    "path": "packages/react/src/checkbox/root/CheckboxRoot.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { EMPTY_OBJECT } from '@base-ui/utils/empty';\nimport { useControlled } from '@base-ui/utils/useControlled';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useMergedRefs } from '@base-ui/utils/useMergedRefs';\nimport { useRefWithInit } from '@base-ui/utils/useRefWithInit';\nimport { visuallyHidden, visuallyHiddenInput } from '@base-ui/utils/visuallyHidden';\nimport { NOOP } from '../../utils/noop';\nimport { useStateAttributesMapping } from '../utils/useStateAttributesMapping';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useBaseUiId } from '../../utils/useBaseUiId';\nimport type { BaseUIComponentProps, NonNativeButtonProps } from '../../utils/types';\nimport { mergeProps } from '../../merge-props';\nimport { useButton } from '../../use-button/useButton';\nimport type { FieldRootState } from '../../field/root/FieldRoot';\nimport { useFieldRootContext } from '../../field/root/FieldRootContext';\nimport { useFieldItemContext } from '../../field/item/FieldItemContext';\nimport { useField } from '../../field/useField';\nimport { useFormContext } from '../../form/FormContext';\nimport { useLabelableContext } from '../../labelable-provider/LabelableContext';\nimport { useAriaLabelledBy } from '../../labelable-provider/useAriaLabelledBy';\nimport { useCheckboxGroupContext } from '../../checkbox-group/CheckboxGroupContext';\nimport { CheckboxRootContext } from './CheckboxRootContext';\nimport {\n  BaseUIChangeEventDetails,\n  createChangeEventDetails,\n} from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport { useValueChanged } from '../../utils/useValueChanged';\n\nexport const PARENT_CHECKBOX = 'data-parent';\n\n/**\n * Represents the checkbox itself.\n * Renders a `<span>` element and a hidden `<input>` beside.\n *\n * Documentation: [Base UI Checkbox](https://base-ui.com/react/components/checkbox)\n */\nexport const CheckboxRoot = React.forwardRef(function CheckboxRoot(\n  componentProps: CheckboxRoot.Props,\n  forwardedRef: React.ForwardedRef<HTMLElement>,\n) {\n  const {\n    checked: checkedProp,\n    className,\n    defaultChecked = false,\n    'aria-labelledby': ariaLabelledByProp,\n    disabled: disabledProp = false,\n    id: idProp,\n    indeterminate = false,\n    inputRef: inputRefProp,\n    name: nameProp,\n    onCheckedChange: onCheckedChangeProp,\n    parent = false,\n    readOnly = false,\n    render,\n    required = false,\n    uncheckedValue,\n    value: valueProp,\n    nativeButton = false,\n    ...elementProps\n  } = componentProps;\n\n  const { clearErrors } = useFormContext();\n  const {\n    disabled: rootDisabled,\n    name: fieldName,\n    setDirty,\n    setFilled,\n    setFocused,\n    setTouched,\n    state: fieldState,\n    validationMode,\n    validityData,\n    shouldValidateOnChange,\n    validation: localValidation,\n  } = useFieldRootContext();\n  const fieldItemContext = useFieldItemContext();\n  const { labelId, controlId, registerControlId, getDescriptionProps } = useLabelableContext();\n\n  const groupContext = useCheckboxGroupContext();\n  const parentContext = groupContext?.parent;\n  const isGroupedWithParent = parentContext && groupContext.allValues;\n\n  const disabled =\n    rootDisabled || fieldItemContext.disabled || groupContext?.disabled || disabledProp;\n  const name = fieldName ?? nameProp;\n  const value = valueProp ?? name;\n\n  const id = useBaseUiId();\n\n  const parentId = useBaseUiId();\n  let inputId = controlId;\n  if (isGroupedWithParent) {\n    inputId = parent ? parentId : `${parentContext.id}-${value}`;\n  } else if (idProp) {\n    inputId = idProp;\n  }\n\n  let groupProps: Partial<Omit<CheckboxRoot.Props, 'className'>> = {};\n  if (isGroupedWithParent) {\n    if (parent) {\n      groupProps = groupContext.parent.getParentProps();\n    } else if (value) {\n      groupProps = groupContext.parent.getChildProps(value);\n    }\n  }\n\n  const onCheckedChange = useStableCallback(onCheckedChangeProp);\n\n  const {\n    checked: groupChecked = checkedProp,\n    indeterminate: groupIndeterminate = indeterminate,\n    onCheckedChange: groupOnChange,\n    ...otherGroupProps\n  } = groupProps;\n\n  const groupValue = groupContext?.value;\n  const setGroupValue = groupContext?.setValue;\n  const defaultGroupValue = groupContext?.defaultValue;\n\n  const controlRef = React.useRef<HTMLButtonElement>(null);\n  const controlSourceRef = useRefWithInit(() => Symbol('checkbox-control'));\n  const hasRegisteredRef = React.useRef(false);\n\n  const { getButtonProps, buttonRef } = useButton({\n    disabled,\n    native: nativeButton,\n  });\n\n  const validation = groupContext?.validation ?? localValidation;\n\n  const [checked, setCheckedState] = useControlled({\n    controlled: value && groupValue && !parent ? groupValue.includes(value) : groupChecked,\n    default:\n      value && defaultGroupValue && !parent ? defaultGroupValue.includes(value) : defaultChecked,\n    name: 'Checkbox',\n    state: 'checked',\n  });\n\n  // can't use useLabelableId because of optional groupContext and/or parent\n  useIsoLayoutEffect(() => {\n    if (registerControlId === NOOP) {\n      return undefined;\n    }\n\n    hasRegisteredRef.current = true;\n    registerControlId(controlSourceRef.current, inputId);\n\n    return undefined;\n  }, [inputId, groupContext, registerControlId, parent, controlSourceRef]);\n\n  React.useEffect(() => {\n    const controlSource = controlSourceRef.current;\n\n    return () => {\n      if (!hasRegisteredRef.current || registerControlId === NOOP) {\n        return;\n      }\n\n      hasRegisteredRef.current = false;\n      registerControlId(controlSource, undefined);\n    };\n  }, [registerControlId, controlSourceRef]);\n\n  useField({\n    enabled: !groupContext,\n    id,\n    commit: validation.commit,\n    value: checked,\n    controlRef,\n    name,\n    getValue: () => checked,\n  });\n\n  const inputRef = React.useRef<HTMLInputElement>(null);\n  const mergedInputRef = useMergedRefs(inputRefProp, inputRef, validation.inputRef);\n  const ariaLabelledBy = useAriaLabelledBy(\n    ariaLabelledByProp,\n    labelId,\n    inputRef,\n    !nativeButton,\n    inputId ?? undefined,\n  );\n\n  useIsoLayoutEffect(() => {\n    if (inputRef.current) {\n      inputRef.current.indeterminate = groupIndeterminate;\n      if (checked) {\n        setFilled(true);\n      }\n    }\n  }, [checked, groupIndeterminate, setFilled]);\n\n  useValueChanged(checked, () => {\n    if (groupContext && !parent) {\n      return;\n    }\n\n    clearErrors(name);\n    setFilled(checked);\n    setDirty(checked !== validityData.initialValue);\n\n    if (shouldValidateOnChange()) {\n      validation.commit(checked);\n    } else {\n      validation.commit(checked, true);\n    }\n  });\n\n  const inputProps = mergeProps<'input'>(\n    {\n      checked,\n      disabled,\n      // parent checkboxes unset `name` to be excluded from form submission\n      name: parent ? undefined : name,\n      // Set `id` to stop Chrome warning about an unassociated input.\n      // When using a native button, the `id` is applied to the button instead.\n      id: nativeButton ? undefined : (inputId ?? undefined),\n      required,\n      ref: mergedInputRef,\n      style: name ? visuallyHiddenInput : visuallyHidden,\n      tabIndex: -1,\n      type: 'checkbox',\n      'aria-hidden': true,\n      onChange(event) {\n        // Workaround for https://github.com/facebook/react/issues/9023\n        if (event.nativeEvent.defaultPrevented) {\n          return;\n        }\n\n        const nextChecked = event.target.checked;\n        const details = createChangeEventDetails(REASONS.none, event.nativeEvent);\n\n        groupOnChange?.(nextChecked, details);\n        onCheckedChange(nextChecked, details);\n\n        if (details.isCanceled) {\n          return;\n        }\n\n        setCheckedState(nextChecked);\n\n        if (value && groupValue && setGroupValue && !parent) {\n          const nextGroupValue = nextChecked\n            ? [...groupValue, value]\n            : groupValue.filter((item) => item !== value);\n\n          setGroupValue(nextGroupValue, details);\n        }\n      },\n      onFocus() {\n        controlRef.current?.focus();\n      },\n    },\n    // React <19 sets an empty value if `undefined` is passed explicitly\n    // To avoid this, we only set the value if it's defined\n    valueProp !== undefined\n      ? { value: (groupContext ? checked && valueProp : valueProp) || '' }\n      : EMPTY_OBJECT,\n    getDescriptionProps,\n    groupContext ? validation.getValidationProps : validation.getInputValidationProps,\n  );\n\n  const computedChecked = isGroupedWithParent ? Boolean(groupChecked) : checked;\n  const computedIndeterminate = isGroupedWithParent\n    ? groupIndeterminate || indeterminate\n    : indeterminate;\n\n  React.useEffect(() => {\n    if (!parentContext || !value) {\n      return undefined;\n    }\n\n    const disabledStates = parentContext.disabledStatesRef.current;\n    disabledStates.set(value, disabled);\n\n    return () => {\n      disabledStates.delete(value);\n    };\n  }, [parentContext, disabled, value]);\n\n  const state: CheckboxRootState = React.useMemo(\n    () => ({\n      ...fieldState,\n      checked: computedChecked,\n      disabled,\n      readOnly,\n      required,\n      indeterminate: computedIndeterminate,\n    }),\n    [fieldState, computedChecked, disabled, readOnly, required, computedIndeterminate],\n  );\n\n  const stateAttributesMapping = useStateAttributesMapping(state);\n\n  const element = useRenderElement('span', componentProps, {\n    state,\n    ref: [buttonRef, controlRef, forwardedRef, groupContext?.registerControlRef],\n    props: [\n      {\n        id: nativeButton ? (inputId ?? undefined) : id,\n        role: 'checkbox',\n        'aria-checked': groupIndeterminate ? 'mixed' : checked,\n        'aria-readonly': readOnly || undefined,\n        'aria-required': required || undefined,\n        'aria-labelledby': ariaLabelledBy,\n        [PARENT_CHECKBOX as string]: parent ? '' : undefined,\n        onFocus() {\n          setFocused(true);\n        },\n        onBlur() {\n          const inputEl = inputRef.current;\n          if (!inputEl) {\n            return;\n          }\n\n          setTouched(true);\n          setFocused(false);\n\n          if (validationMode === 'onBlur') {\n            validation.commit(groupContext ? groupValue : inputEl.checked);\n          }\n        },\n        onClick(event: React.MouseEvent) {\n          if (readOnly || disabled) {\n            return;\n          }\n\n          event.preventDefault();\n\n          inputRef.current?.dispatchEvent(\n            new PointerEvent('click', {\n              bubbles: true,\n              shiftKey: event.shiftKey,\n              ctrlKey: event.ctrlKey,\n              altKey: event.altKey,\n              metaKey: event.metaKey,\n            }),\n          );\n        },\n      },\n      getDescriptionProps,\n      validation.getValidationProps,\n      elementProps,\n      otherGroupProps,\n      getButtonProps,\n    ],\n    stateAttributesMapping,\n  });\n\n  return (\n    <CheckboxRootContext.Provider value={state}>\n      {element}\n      {!checked && !groupContext && name && !parent && uncheckedValue !== undefined && (\n        <input type=\"hidden\" name={name} value={uncheckedValue} />\n      )}\n      <input {...inputProps} />\n    </CheckboxRootContext.Provider>\n  );\n});\n\nexport interface CheckboxRootState extends FieldRootState {\n  /**\n   * Whether the checkbox is currently ticked.\n   */\n  checked: boolean;\n  /**\n   * Whether the component should ignore user interaction.\n   */\n  disabled: boolean;\n  /**\n   * Whether the user should be unable to tick or untick the checkbox.\n   */\n  readOnly: boolean;\n  /**\n   * Whether the user must tick the checkbox before submitting a form.\n   */\n  required: boolean;\n  /**\n   * Whether the checkbox is in a mixed state: neither ticked, nor unticked.\n   */\n  indeterminate: boolean;\n}\n\nexport interface CheckboxRootProps\n  extends\n    NonNativeButtonProps,\n    Omit<BaseUIComponentProps<'span', CheckboxRootState>, 'onChange' | 'value'> {\n  /**\n   * The id of the input element.\n   */\n  id?: string | undefined;\n  /**\n   * Identifies the field when a form is submitted.\n   * @default undefined\n   */\n  name?: string | undefined;\n  /**\n   * Whether the checkbox is currently ticked.\n   *\n   * To render an uncontrolled checkbox, use the `defaultChecked` prop instead.\n   * @default undefined\n   */\n  checked?: boolean | undefined;\n  /**\n   * Whether the checkbox is initially ticked.\n   *\n   * To render a controlled checkbox, use the `checked` prop instead.\n   * @default false\n   */\n  defaultChecked?: boolean | undefined;\n  /**\n   * Whether the component should ignore user interaction.\n   * @default false\n   */\n  disabled?: boolean | undefined;\n  /**\n   * Event handler called when the checkbox is ticked or unticked.\n   */\n  onCheckedChange?:\n    | ((checked: boolean, eventDetails: CheckboxRootChangeEventDetails) => void)\n    | undefined;\n  /**\n   * Whether the user should be unable to tick or untick the checkbox.\n   * @default false\n   */\n  readOnly?: boolean | undefined;\n  /**\n   * Whether the user must tick the checkbox before submitting a form.\n   * @default false\n   */\n  required?: boolean | undefined;\n  /**\n   * Whether the checkbox is in a mixed state: neither ticked, nor unticked.\n   * @default false\n   */\n  indeterminate?: boolean | undefined;\n  /**\n   * A ref to access the hidden `<input>` element.\n   */\n  inputRef?: React.Ref<HTMLInputElement> | undefined;\n  /**\n   * Whether the checkbox controls a group of child checkboxes.\n   *\n   * Must be used in a [Checkbox Group](https://base-ui.com/react/components/checkbox-group).\n   * @default false\n   */\n  parent?: boolean | undefined;\n  /**\n   * The value submitted with the form when the checkbox is unchecked.\n   * By default, unchecked checkboxes do not submit any value, matching native checkbox behavior.\n   */\n  uncheckedValue?: string | undefined;\n  /**\n   * The value of the selected checkbox.\n   */\n  value?: string | undefined;\n}\n\nexport type CheckboxRootChangeEventReason = typeof REASONS.none;\nexport type CheckboxRootChangeEventDetails =\n  BaseUIChangeEventDetails<CheckboxRoot.ChangeEventReason>;\n\nexport namespace CheckboxRoot {\n  export type State = CheckboxRootState;\n  export type Props = CheckboxRootProps;\n  export type ChangeEventReason = CheckboxRootChangeEventReason;\n  export type ChangeEventDetails = CheckboxRootChangeEventDetails;\n}\n"
  },
  {
    "path": "packages/react/src/checkbox/root/CheckboxRootContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport type { CheckboxRootState } from './CheckboxRoot';\n\nexport type CheckboxRootContext = CheckboxRootState;\n\nexport const CheckboxRootContext = React.createContext<CheckboxRootContext | undefined>(undefined);\n\nexport function useCheckboxRootContext() {\n  const context = React.useContext(CheckboxRootContext);\n  if (context === undefined) {\n    throw new Error(\n      'Base UI: CheckboxRootContext is missing. Checkbox parts must be placed within <Checkbox.Root>.',\n    );\n  }\n\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/checkbox/root/CheckboxRootDataAttributes.ts",
    "content": "export enum CheckboxRootDataAttributes {\n  /**\n   * Present when the checkbox is checked.\n   */\n  checked = 'data-checked',\n  /**\n   * Present when the checkbox is not checked.\n   */\n  unchecked = 'data-unchecked',\n  /**\n   * Present when the checkbox is in an indeterminate state.\n   */\n  indeterminate = 'data-indeterminate',\n  /**\n   * Present when the checkbox is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the checkbox is readonly.\n   */\n  readonly = 'data-readonly',\n  /**\n   * Present when the checkbox is required.\n   */\n  required = 'data-required',\n  /**\n   * Present when the checkbox is in valid state (when wrapped in Field.Root).\n   */\n  valid = 'data-valid',\n  /**\n   * Present when the checkbox is in invalid state (when wrapped in Field.Root).\n   */\n  invalid = 'data-invalid',\n  /**\n   * Present when the checkbox has been touched (when wrapped in Field.Root).\n   */\n  touched = 'data-touched',\n  /**\n   * Present when the checkbox's value has changed (when wrapped in Field.Root).\n   */\n  dirty = 'data-dirty',\n  /**\n   * Present when the checkbox is checked (when wrapped in Field.Root).\n   */\n  filled = 'data-filled',\n  /**\n   * Present when the checkbox is focused (when wrapped in Field.Root).\n   */\n  focused = 'data-focused',\n}\n"
  },
  {
    "path": "packages/react/src/checkbox/utils/useStateAttributesMapping.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport type { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport type { CheckboxRootState } from '../root/CheckboxRoot';\nimport { CheckboxRootDataAttributes } from '../root/CheckboxRootDataAttributes';\nimport { fieldValidityMapping } from '../../field/utils/constants';\n\nexport function useStateAttributesMapping(state: CheckboxRootState) {\n  return React.useMemo<StateAttributesMapping<typeof state>>(\n    () => ({\n      checked(value): Record<string, string> {\n        if (state.indeterminate) {\n          // `data-indeterminate` is already handled by the `indeterminate` prop.\n          return {};\n        }\n\n        if (value) {\n          return {\n            [CheckboxRootDataAttributes.checked]: '',\n          };\n        }\n\n        return {\n          [CheckboxRootDataAttributes.unchecked]: '',\n        };\n      },\n      ...fieldValidityMapping,\n    }),\n    [state.indeterminate],\n  );\n}\n"
  },
  {
    "path": "packages/react/src/checkbox-group/CheckboxGroup.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { createRenderer, screen, fireEvent } from '@mui/internal-test-utils';\nimport { CheckboxGroup } from '@base-ui/react/checkbox-group';\nimport { Checkbox } from '@base-ui/react/checkbox';\nimport { Field } from '@base-ui/react/field';\nimport { Form } from '@base-ui/react/form';\nimport { describeConformance, isJSDOM } from '#test-utils';\n\ndescribe('<CheckboxGroup />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<CheckboxGroup />, () => ({\n    inheritComponent: 'div',\n    refInstanceof: window.HTMLDivElement,\n    render,\n  }));\n\n  describe('prop: value', () => {\n    it('should control the value', () => {\n      function App() {\n        const [value, setValue] = React.useState(['red']);\n        return (\n          <CheckboxGroup value={value} onValueChange={setValue}>\n            <Checkbox.Root name=\"red\" data-testid=\"red\" />\n            <Checkbox.Root name=\"green\" data-testid=\"green\" />\n            <Checkbox.Root name=\"blue\" data-testid=\"blue\" />\n          </CheckboxGroup>\n        );\n      }\n\n      render(<App />);\n\n      const red = screen.getByTestId('red');\n      const green = screen.getByTestId('green');\n      const blue = screen.getByTestId('blue');\n\n      expect(red).toHaveAttribute('aria-checked', 'true');\n      expect(green).toHaveAttribute('aria-checked', 'false');\n      expect(blue).toHaveAttribute('aria-checked', 'false');\n\n      fireEvent.click(green);\n\n      expect(red).toHaveAttribute('aria-checked', 'true');\n      expect(green).toHaveAttribute('aria-checked', 'true');\n      expect(blue).toHaveAttribute('aria-checked', 'false');\n\n      fireEvent.click(blue);\n\n      expect(red).toHaveAttribute('aria-checked', 'true');\n      expect(green).toHaveAttribute('aria-checked', 'true');\n      expect(blue).toHaveAttribute('aria-checked', 'true');\n\n      fireEvent.click(green);\n\n      expect(red).toHaveAttribute('aria-checked', 'true');\n      expect(green).toHaveAttribute('aria-checked', 'false');\n      expect(blue).toHaveAttribute('aria-checked', 'true');\n    });\n  });\n\n  describe('prop: onValueChange', () => {\n    it('should be called when the value changes', () => {\n      const handleValueChange = vi.fn();\n\n      function App() {\n        const [value, setValue] = React.useState<string[]>([]);\n        return (\n          <CheckboxGroup\n            value={value}\n            onValueChange={(nextValue) => {\n              setValue(nextValue);\n              handleValueChange(nextValue);\n            }}\n          >\n            <Checkbox.Root name=\"red\" data-testid=\"red\" />\n            <Checkbox.Root name=\"green\" data-testid=\"green\" />\n            <Checkbox.Root name=\"blue\" data-testid=\"blue\" />\n          </CheckboxGroup>\n        );\n      }\n\n      render(<App />);\n\n      const red = screen.getByTestId('red');\n      const green = screen.getByTestId('green');\n      const blue = screen.getByTestId('blue');\n\n      fireEvent.click(red);\n\n      expect(handleValueChange.mock.calls.length).toBe(1);\n      expect(handleValueChange.mock.calls[0][0]).toEqual(['red']);\n\n      fireEvent.click(green);\n\n      expect(handleValueChange.mock.calls.length).toBe(2);\n      expect(handleValueChange.mock.calls[1][0]).toEqual(['red', 'green']);\n\n      fireEvent.click(blue);\n\n      expect(handleValueChange.mock.calls.length).toBe(3);\n      expect(handleValueChange.mock.calls[2][0]).toEqual(['red', 'green', 'blue']);\n    });\n  });\n\n  describe('prop: defaultValue', () => {\n    it('should set the initial value', () => {\n      function App() {\n        return (\n          <CheckboxGroup defaultValue={['red']}>\n            <Checkbox.Root name=\"red\" data-testid=\"red\" />\n            <Checkbox.Root name=\"green\" data-testid=\"green\" />\n            <Checkbox.Root name=\"blue\" data-testid=\"blue\" />\n          </CheckboxGroup>\n        );\n      }\n\n      render(<App />);\n\n      const red = screen.getByTestId('red');\n      const green = screen.getByTestId('green');\n      const blue = screen.getByTestId('blue');\n\n      expect(red).toHaveAttribute('aria-checked', 'true');\n      expect(green).toHaveAttribute('aria-checked', 'false');\n      expect(blue).toHaveAttribute('aria-checked', 'false');\n\n      fireEvent.click(green);\n\n      expect(red).toHaveAttribute('aria-checked', 'true');\n      expect(green).toHaveAttribute('aria-checked', 'true');\n      expect(blue).toHaveAttribute('aria-checked', 'false');\n    });\n  });\n\n  describe('prop: disabled', () => {\n    it('disables all checkboxes when `true`', () => {\n      function App() {\n        return (\n          <CheckboxGroup disabled>\n            <Checkbox.Root name=\"red\" data-testid=\"red\" />\n            <Checkbox.Root name=\"green\" data-testid=\"green\" />\n            <Checkbox.Root name=\"blue\" data-testid=\"blue\" />\n          </CheckboxGroup>\n        );\n      }\n\n      render(<App />);\n\n      const red = screen.getByTestId('red');\n      const green = screen.getByTestId('green');\n      const blue = screen.getByTestId('blue');\n\n      expect(red).toHaveAttribute('aria-disabled', 'true');\n      expect(green).toHaveAttribute('aria-disabled', 'true');\n      expect(blue).toHaveAttribute('aria-disabled', 'true');\n    });\n\n    it('does not disable all checkboxes when `false`', () => {\n      function App() {\n        return (\n          <CheckboxGroup disabled={false}>\n            <Checkbox.Root name=\"red\" data-testid=\"red\" />\n            <Checkbox.Root name=\"green\" data-testid=\"green\" />\n            <Checkbox.Root name=\"blue\" data-testid=\"blue\" />\n          </CheckboxGroup>\n        );\n      }\n\n      render(<App />);\n\n      const red = screen.getByTestId('red');\n      const green = screen.getByTestId('green');\n      const blue = screen.getByTestId('blue');\n\n      expect(red).not.toHaveAttribute('aria-disabled', 'true');\n      expect(green).not.toHaveAttribute('aria-disabled', 'true');\n      expect(blue).not.toHaveAttribute('aria-disabled', 'true');\n    });\n\n    it('takes precedence over individual checkboxes', () => {\n      function App() {\n        return (\n          <CheckboxGroup disabled>\n            <Checkbox.Root name=\"red\" data-testid=\"red\" disabled={false} />\n            <Checkbox.Root name=\"green\" data-testid=\"green\" />\n            <Checkbox.Root name=\"blue\" data-testid=\"blue\" />\n          </CheckboxGroup>\n        );\n      }\n\n      render(<App />);\n\n      const red = screen.getByTestId('red');\n      const green = screen.getByTestId('green');\n      const blue = screen.getByTestId('blue');\n\n      expect(red).toHaveAttribute('aria-disabled', 'true');\n      expect(green).toHaveAttribute('aria-disabled', 'true');\n      expect(blue).toHaveAttribute('aria-disabled', 'true');\n    });\n  });\n\n  describe('Field', () => {\n    it('prop: validationMode=onSubmit', async () => {\n      const validateSpy = vi.fn((value) => {\n        const v = value as string[];\n        if (v.length === 0) {\n          return 'custom error 1';\n        }\n        if (v.length < 2) {\n          return 'custom error 2';\n        }\n        if (v.includes('two')) {\n          return 'custom error 3';\n        }\n        return null;\n      });\n      const { user } = render(\n        <Form>\n          <Field.Root validate={validateSpy} name=\"test\">\n            <CheckboxGroup defaultValue={[]}>\n              <Field.Item>\n                <Checkbox.Root value=\"one\" data-testid=\"checkbox\" />\n              </Field.Item>\n              <Field.Item>\n                <Checkbox.Root value=\"two\" data-testid=\"checkbox\" />\n              </Field.Item>\n              <Field.Item>\n                <Checkbox.Root value=\"three\" data-testid=\"checkbox\" />\n              </Field.Item>\n            </CheckboxGroup>\n          </Field.Root>\n          <button type=\"submit\">submit</button>\n        </Form>,\n      );\n\n      const checkboxes = screen.getAllByTestId('checkbox');\n      const [checkbox1, checkbox2, checkbox3] = checkboxes;\n      checkboxes.forEach((checkbox) => expect(checkbox).not.toHaveAttribute('aria-invalid'));\n\n      await user.click(checkbox2);\n      checkboxes.forEach((checkbox) => expect(checkbox).not.toHaveAttribute('aria-invalid'));\n\n      await user.click(screen.getByText('submit'));\n      checkboxes.forEach((checkbox) => expect(checkbox).toHaveAttribute('aria-invalid'));\n\n      await user.click(checkbox1);\n      expect(validateSpy.mock.lastCall?.[0]).toEqual(['two', 'one']);\n      checkboxes.forEach((checkbox) => expect(checkbox).toHaveAttribute('aria-invalid'));\n      await user.click(checkbox2);\n      await user.click(checkbox3);\n      expect(validateSpy.mock.lastCall?.[0]).toEqual(['one', 'three']);\n      checkboxes.forEach((checkbox) => expect(checkbox).not.toHaveAttribute('aria-invalid'));\n    });\n\n    it('prop: validationMode=onChange', async () => {\n      const validateSpy = vi.fn((value) => {\n        const v = value as string[];\n        return v.includes('one') ? 'error' : null;\n      });\n      render(\n        <Field.Root validationMode=\"onChange\" validate={validateSpy} name=\"apple\">\n          <CheckboxGroup defaultValue={['one']}>\n            <Field.Item>\n              <Checkbox.Root value=\"one\" data-testid=\"checkbox\" />\n            </Field.Item>\n            <Field.Item>\n              <Checkbox.Root value=\"two\" data-testid=\"checkbox\" />\n            </Field.Item>\n            <Field.Item>\n              <Checkbox.Root value=\"three\" data-testid=\"checkbox\" />\n            </Field.Item>\n          </CheckboxGroup>\n        </Field.Root>,\n      );\n\n      const checkboxes = screen.getAllByTestId('checkbox');\n      const [checkbox1, checkbox2, checkbox3] = checkboxes;\n\n      checkboxes.forEach((checkbox) => expect(checkbox).not.toHaveAttribute('aria-invalid'));\n\n      fireEvent.click(checkbox1);\n      checkboxes.forEach((checkbox) => expect(checkbox).not.toHaveAttribute('aria-invalid'));\n      expect(validateSpy.mock.calls.length).toBe(1);\n      expect(validateSpy.mock.lastCall?.[0]).toEqual([]);\n\n      fireEvent.click(checkbox2);\n      checkboxes.forEach((checkbox) => expect(checkbox).not.toHaveAttribute('aria-invalid'));\n      expect(validateSpy.mock.calls.length).toBe(2);\n      expect(validateSpy.mock.lastCall?.[0]).toEqual(['two']);\n\n      fireEvent.click(checkbox1);\n      checkboxes.forEach((checkbox) => expect(checkbox).toHaveAttribute('aria-invalid', 'true'));\n      expect(validateSpy.mock.calls.length).toBe(3);\n      expect(validateSpy.mock.lastCall?.[0]).toEqual(['two', 'one']);\n\n      fireEvent.click(checkbox3);\n      checkboxes.forEach((checkbox) => expect(checkbox).toHaveAttribute('aria-invalid', 'true'));\n    });\n\n    it('revalidates when the controlled value changes externally', async () => {\n      const validateSpy = vi.fn((value: unknown) => {\n        const values = value as string[];\n        return values.includes('one') ? 'error' : null;\n      });\n\n      function App() {\n        const [selected, setSelected] = React.useState<string[]>([]);\n\n        return (\n          <React.Fragment>\n            <Field.Root validationMode=\"onChange\" validate={validateSpy} name=\"apple\">\n              <CheckboxGroup value={selected}>\n                <Field.Item>\n                  <Checkbox.Root value=\"one\" data-testid=\"checkbox\" />\n                </Field.Item>\n                <Field.Item>\n                  <Checkbox.Root value=\"two\" data-testid=\"checkbox\" />\n                </Field.Item>\n              </CheckboxGroup>\n            </Field.Root>\n            <button type=\"button\" onClick={() => setSelected(['one'])}>\n              Select externally\n            </button>\n          </React.Fragment>\n        );\n      }\n\n      render(<App />);\n\n      const checkboxes = screen.getAllByTestId('checkbox');\n      const toggle = screen.getByText('Select externally');\n\n      checkboxes.forEach((checkbox) => expect(checkbox).not.toHaveAttribute('aria-invalid'));\n      const initialCallCount = validateSpy.mock.calls.length;\n\n      fireEvent.click(toggle);\n\n      expect(validateSpy.mock.calls.length).toBe(initialCallCount + 1);\n      expect(validateSpy.mock.lastCall?.[0]).toEqual(['one']);\n      checkboxes.forEach((checkbox) => expect(checkbox).toHaveAttribute('aria-invalid', 'true'));\n    });\n\n    it('prop: validationMode=onBlur', async () => {\n      const validateSpy = vi.fn((value) => {\n        const v = value as string[];\n        return v.includes('one') ? 'error' : null;\n      });\n      render(\n        <Field.Root validationMode=\"onBlur\" validate={validateSpy} name=\"apple\">\n          <CheckboxGroup defaultValue={['one']}>\n            <Field.Item>\n              <Checkbox.Root value=\"one\" data-testid=\"checkbox\" />\n            </Field.Item>\n            <Field.Item>\n              <Checkbox.Root value=\"two\" data-testid=\"checkbox\" />\n            </Field.Item>\n            <Field.Item>\n              <Checkbox.Root value=\"three\" data-testid=\"checkbox\" />\n            </Field.Item>\n          </CheckboxGroup>\n          <Field.Error data-testid=\"error\" />\n        </Field.Root>,\n      );\n\n      const checkboxes = screen.getAllByTestId('checkbox');\n      const [checkbox1, , checkbox3] = checkboxes;\n\n      checkboxes.forEach((checkbox) => expect(checkbox).not.toHaveAttribute('aria-invalid'));\n\n      fireEvent.click(checkbox1);\n      expect(validateSpy.mock.calls.length).toBe(0);\n      fireEvent.blur(checkbox1);\n      expect(validateSpy.mock.calls.length).toBe(1);\n      expect(validateSpy.mock.lastCall?.[0]).toEqual([]);\n\n      checkboxes.forEach((checkbox) => expect(checkbox).not.toHaveAttribute('aria-invalid'));\n\n      fireEvent.click(checkbox3);\n      expect(validateSpy.mock.calls.length).toBe(1);\n      fireEvent.blur(checkbox3);\n      expect(validateSpy.mock.calls.length).toBe(2);\n      expect(validateSpy.mock.lastCall?.[0]).toEqual(['three']);\n\n      checkboxes.forEach((checkbox) => expect(checkbox).not.toHaveAttribute('aria-invalid'));\n\n      fireEvent.click(checkbox1);\n      expect(validateSpy.mock.calls.length).toBe(2);\n      fireEvent.blur(checkbox1);\n      expect(validateSpy.mock.calls.length).toBe(3);\n      expect(validateSpy.mock.lastCall?.[0]).toEqual(['three', 'one']);\n\n      checkboxes.forEach((checkbox) => expect(checkbox).toHaveAttribute('aria-invalid', 'true'));\n    });\n  });\n\n  describe('Field.Label', () => {\n    it('implicit association', async () => {\n      const changeSpy = vi.fn();\n      render(\n        <Field.Root name=\"apple\">\n          <CheckboxGroup defaultValue={['fuji-apple', 'gala-apple']}>\n            <Field.Item>\n              <Field.Label data-testid=\"label\">\n                <Checkbox.Root value=\"fuji-apple\" />\n                Fuji\n              </Field.Label>\n            </Field.Item>\n            <Field.Item>\n              <Field.Label data-testid=\"label\">\n                <Checkbox.Root value=\"gala-apple\" />\n                Gala\n              </Field.Label>\n            </Field.Item>\n            <Field.Item>\n              <Field.Label data-testid=\"label\">\n                <Checkbox.Root value=\"granny-smith-apple\" onCheckedChange={changeSpy} />\n                Granny Smith\n              </Field.Label>\n            </Field.Item>\n          </CheckboxGroup>\n        </Field.Root>,\n      );\n\n      const checkboxes = screen.getAllByRole('checkbox');\n      const labels = screen.getAllByTestId('label');\n      const inputs = document.querySelectorAll('input[type=\"checkbox\"]');\n\n      checkboxes.forEach((checkbox, index) => {\n        const label = labels[index];\n        const input = inputs[index];\n\n        expect(label.getAttribute('for')).not.toBe(null);\n        expect(label.getAttribute('for')).toBe(input.getAttribute('id'));\n        expect(label.getAttribute('id')).not.toBe(null);\n        expect(label.getAttribute('id')).toBe(checkbox.getAttribute('aria-labelledby'));\n      });\n\n      fireEvent.click(labels[2]);\n      expect(changeSpy.mock.calls.length).toBe(1);\n    });\n\n    it('explicit association', async () => {\n      const changeSpy = vi.fn();\n\n      await render(\n        <Field.Root name=\"apple\">\n          <CheckboxGroup defaultValue={['fuji-apple', 'gala-apple']}>\n            <Field.Item>\n              <Checkbox.Root value=\"fuji-apple\" />\n              <Field.Label data-testid=\"label\">Fuji</Field.Label>\n              <Field.Description data-testid=\"description\">\n                A fuji apple is the round, edible fruit of an apple tree\n              </Field.Description>\n            </Field.Item>\n            <Field.Item>\n              <Checkbox.Root value=\"gala-apple\" onCheckedChange={changeSpy} />\n              <Field.Label data-testid=\"label\">Gala</Field.Label>\n              <Field.Description data-testid=\"description\">\n                A gala apple is the round, edible fruit of an apple tree\n              </Field.Description>\n            </Field.Item>\n          </CheckboxGroup>\n        </Field.Root>,\n      );\n\n      const checkboxes = screen.getAllByRole('checkbox');\n      const labels = screen.getAllByTestId('label');\n      const descriptions = screen.getAllByTestId('description');\n      const inputs = document.querySelectorAll('input[type=\"checkbox\"]');\n\n      checkboxes.forEach((checkbox, index) => {\n        const label = labels[index];\n        const description = descriptions[index];\n        const input = inputs[index];\n\n        expect(label.getAttribute('for')).not.toBe(null);\n        expect(label.getAttribute('for')).toBe(input.getAttribute('id'));\n        expect(label.getAttribute('id')).not.toBe(null);\n        expect(label.getAttribute('id')).toBe(checkbox.getAttribute('aria-labelledby'));\n        expect(description.getAttribute('id')).not.toBe(null);\n        expect(description.getAttribute('id')).toBe(checkbox.getAttribute('aria-describedby'));\n      });\n\n      fireEvent.click(screen.getByText('Gala'));\n      expect(changeSpy.mock.calls.length).toBe(1);\n    });\n  });\n\n  describe('Field.Description', () => {\n    it('links the group and individual checkboxes', async () => {\n      await render(\n        <Field.Root name=\"apple\">\n          <CheckboxGroup defaultValue={[]}>\n            <Field.Description data-testid=\"group-description\">Group description</Field.Description>\n            <Field.Item>\n              <Field.Label>\n                <Checkbox.Root value=\"fuji-apple\" />\n                Fuji\n              </Field.Label>\n            </Field.Item>\n          </CheckboxGroup>\n        </Field.Root>,\n      );\n\n      const groupDescription = screen.getByTestId('group-description');\n      const groupDescriptionId = groupDescription.getAttribute('id');\n      expect(groupDescriptionId).not.toBe(null);\n      expect(screen.getByRole('group').getAttribute('aria-describedby')).toContain(\n        groupDescriptionId,\n      );\n      expect(screen.getByRole('checkbox').getAttribute('aria-describedby')).toContain(\n        groupDescriptionId,\n      );\n    });\n  });\n\n  describe.skipIf(isJSDOM)('Form', () => {\n    it('includes the checkbox group value in form submission', async () => {\n      render(\n        <Form\n          onSubmit={(event) => {\n            event.preventDefault();\n            const formData = new FormData(event.currentTarget);\n            expect(formData.getAll('apple')).toEqual(['fuji-apple', 'gala-apple']);\n          }}\n        >\n          <Field.Root name=\"apple\">\n            <CheckboxGroup defaultValue={['fuji-apple', 'gala-apple']}>\n              <Field.Item>\n                <Checkbox.Root value=\"fuji-apple\" data-testid=\"button-1\" />\n              </Field.Item>\n              <Field.Item>\n                <Checkbox.Root value=\"gala-apple\" data-testid=\"button-2\" />\n              </Field.Item>\n              <Field.Item>\n                <Checkbox.Root value=\"granny-smith-apple\" data-testid=\"button-3\" />\n              </Field.Item>\n            </CheckboxGroup>\n          </Field.Root>\n          <button type=\"submit\">Submit</button>\n        </Form>,\n      );\n\n      const submit = screen.getByRole('button');\n      fireEvent.click(submit);\n    });\n\n    it('is validated as a group upon form submission', async () => {\n      const validateSpy = vi.fn();\n\n      render(\n        <Form\n          onSubmit={(event) => {\n            event.preventDefault();\n          }}\n        >\n          <Field.Root name=\"apple\" validate={validateSpy}>\n            <CheckboxGroup defaultValue={['fuji-apple', 'gala-apple']}>\n              <Field.Item>\n                <Checkbox.Root value=\"fuji-apple\" data-testid=\"button-1\" />\n              </Field.Item>\n              <Field.Item>\n                <Checkbox.Root value=\"gala-apple\" data-testid=\"button-2\" />\n              </Field.Item>\n              <Field.Item>\n                <Checkbox.Root value=\"granny-smith-apple\" data-testid=\"button-3\" />\n              </Field.Item>\n            </CheckboxGroup>\n          </Field.Root>\n          <button type=\"submit\">Submit</button>\n        </Form>,\n      );\n\n      const submit = screen.getByRole('button');\n      fireEvent.click(submit);\n      expect(validateSpy.mock.calls.length).toBe(1);\n      expect(validateSpy.mock.calls[0][0]).toEqual(['fuji-apple', 'gala-apple']);\n    });\n\n    it('focuses the first checkbox when the field receives an error from Form', async () => {\n      function App() {\n        const [errors, setErrors] = React.useState<Form.Props['errors']>({});\n        return (\n          <Form\n            errors={errors}\n            onSubmit={(event) => {\n              event.preventDefault();\n              setErrors({ group: 'server error' });\n            }}\n          >\n            <Field.Root name=\"group\" data-testid=\"field\">\n              <CheckboxGroup defaultValue={['one']}>\n                <Field.Item>\n                  <Checkbox.Root value=\"one\" />\n                </Field.Item>\n                <Field.Item>\n                  <Checkbox.Root value=\"two\" />\n                </Field.Item>\n              </CheckboxGroup>\n              <Field.Error data-testid=\"error\" />\n            </Field.Root>\n            <button type=\"submit\">Submit</button>\n          </Form>\n        );\n      }\n\n      const { user } = render(<App />);\n      expect(screen.queryByTestId('error')).toBe(null);\n      const submit = screen.getByText('Submit');\n      await user.click(submit);\n\n      const [checkbox1] = screen.getAllByRole('checkbox');\n      expect(checkbox1).toHaveFocus();\n      expect(checkbox1).toHaveAttribute('aria-invalid', 'true');\n      expect(screen.queryByTestId('error')).toHaveTextContent('server error');\n    });\n\n    it('excludes parent checkboxes from form submission', async () => {\n      const allValues = ['fuji-apple', 'gala-apple', 'granny-smith'];\n\n      function App() {\n        const [value, setValue] = React.useState<string[]>(['fuji-apple', 'gala-apple']);\n        return (\n          <Form\n            onSubmit={(event) => {\n              event.preventDefault();\n              const formData = new FormData(event.currentTarget);\n              expect(formData.getAll('apple')).toEqual([\n                'fuji-apple',\n                'gala-apple',\n                'granny-smith-apple',\n              ]);\n            }}\n          >\n            <Field.Root name=\"apple\">\n              <CheckboxGroup value={value} onValueChange={setValue} allValues={allValues}>\n                <Field.Item>\n                  <Checkbox.Root parent />\n                </Field.Item>\n                <Field.Item>\n                  <Checkbox.Root value=\"fuji-apple\" />\n                </Field.Item>\n                <Field.Item>\n                  <Checkbox.Root value=\"gala-apple\" />\n                </Field.Item>\n                <Field.Item>\n                  <Checkbox.Root value=\"granny-smith-apple\" />\n                </Field.Item>\n              </CheckboxGroup>\n            </Field.Root>\n            <button type=\"submit\">Submit</button>\n          </Form>\n        );\n      }\n\n      const { user } = render(<App />);\n\n      const [parentCheckbox, , , checkbox3] = screen.getAllByRole('checkbox');\n\n      expect(parentCheckbox).toHaveAttribute('aria-checked', 'mixed');\n\n      await user.click(checkbox3);\n\n      expect(parentCheckbox).toHaveAttribute('aria-checked', 'true');\n\n      const submit = screen.getByText('Submit');\n      fireEvent.click(submit);\n    });\n\n    it('appends the id attribute of the error to aria-describedby of individual checkboxes', async () => {\n      await render(\n        <Form errors={{ group: 'error' }}>\n          <Field.Root name=\"group\">\n            <CheckboxGroup defaultValue={['one']}>\n              <Field.Item>\n                <Checkbox.Root value=\"one\" />\n                <Field.Description>Description</Field.Description>\n              </Field.Item>\n              <Field.Item>\n                <Checkbox.Root value=\"two\" />\n              </Field.Item>\n            </CheckboxGroup>\n            <Field.Error data-testid=\"error\" />\n          </Field.Root>\n          <button type=\"submit\">Submit</button>\n        </Form>,\n      );\n      const error = screen.getByTestId('error');\n      expect(error).not.toBe(null);\n\n      const [checkbox1] = screen.getAllByRole('checkbox');\n      expect(checkbox1.getAttribute('aria-describedby')).toContain(error.getAttribute('id'));\n      expect(checkbox1.getAttribute('aria-describedby')).toContain(\n        screen.getByText('Description').getAttribute('id'),\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/checkbox-group/CheckboxGroup.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useControlled } from '@base-ui/utils/useControlled';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useBaseUiId } from '../utils/useBaseUiId';\nimport { useRenderElement } from '../utils/useRenderElement';\nimport { CheckboxGroupContext } from './CheckboxGroupContext';\nimport type { FieldRootState } from '../field/root/FieldRoot';\nimport { useFieldRootContext } from '../field/root/FieldRootContext';\nimport { useLabelableContext } from '../labelable-provider/LabelableContext';\nimport type { BaseUIComponentProps } from '../utils/types';\nimport { fieldValidityMapping } from '../field/utils/constants';\nimport { useField } from '../field/useField';\nimport { PARENT_CHECKBOX } from '../checkbox/root/CheckboxRoot';\nimport { useCheckboxGroupParent } from './useCheckboxGroupParent';\nimport type { BaseUIChangeEventDetails } from '../utils/createBaseUIEventDetails';\nimport { REASONS } from '../utils/reasons';\nimport { useFormContext } from '../form/FormContext';\nimport { useValueChanged } from '../utils/useValueChanged';\nimport { areArraysEqual } from '../utils/areArraysEqual';\nimport { EMPTY_ARRAY } from '../utils/constants';\n\n/**\n * Provides a shared state to a series of checkboxes.\n *\n * Documentation: [Base UI Checkbox Group](https://base-ui.com/react/components/checkbox-group)\n */\nexport const CheckboxGroup = React.forwardRef(function CheckboxGroup(\n  componentProps: CheckboxGroup.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const {\n    allValues,\n    className,\n    defaultValue,\n    disabled: disabledProp = false,\n    id: idProp,\n    onValueChange,\n    render,\n    value: externalValue,\n    ...elementProps\n  } = componentProps;\n\n  const {\n    disabled: fieldDisabled,\n    name: fieldName,\n    state: fieldState,\n    validation,\n    setFilled,\n    setDirty,\n    shouldValidateOnChange,\n    validityData,\n  } = useFieldRootContext();\n  const { labelId, getDescriptionProps } = useLabelableContext();\n  const { clearErrors } = useFormContext();\n\n  const disabled = fieldDisabled || disabledProp;\n\n  const [value, setValueUnwrapped] = useControlled({\n    controlled: externalValue,\n    default: defaultValue,\n    name: 'CheckboxGroup',\n    state: 'value',\n  });\n\n  const setValue = useStableCallback(\n    (v: string[], eventDetails: CheckboxGroup.ChangeEventDetails) => {\n      onValueChange?.(v, eventDetails);\n\n      if (eventDetails.isCanceled) {\n        return;\n      }\n\n      setValueUnwrapped(v);\n    },\n  );\n\n  const parent = useCheckboxGroupParent({\n    allValues,\n    value: externalValue,\n    onValueChange,\n  });\n\n  const id = useBaseUiId(idProp);\n\n  const controlRef = React.useRef<HTMLButtonElement>(null);\n\n  const registerControlRef = React.useCallback((element: HTMLButtonElement | null) => {\n    if (controlRef.current == null && element != null && !element.hasAttribute(PARENT_CHECKBOX)) {\n      controlRef.current = element;\n    }\n  }, []);\n\n  useField({\n    enabled: !!fieldName,\n    id,\n    commit: validation.commit,\n    value,\n    controlRef,\n    name: fieldName,\n    getValue: () => value,\n  });\n\n  const resolvedValue = value ?? EMPTY_ARRAY;\n\n  useValueChanged(resolvedValue, () => {\n    if (fieldName) {\n      clearErrors(fieldName);\n    }\n\n    const initialValue = Array.isArray(validityData.initialValue)\n      ? (validityData.initialValue as readonly string[])\n      : EMPTY_ARRAY;\n\n    setFilled(resolvedValue.length > 0);\n    setDirty(!areArraysEqual(resolvedValue, initialValue));\n\n    if (shouldValidateOnChange()) {\n      validation.commit(resolvedValue);\n    } else {\n      validation.commit(resolvedValue, true);\n    }\n  });\n\n  const state: CheckboxGroupState = {\n    ...fieldState,\n    disabled,\n  };\n\n  const contextValue: CheckboxGroupContext = React.useMemo(\n    () => ({\n      allValues,\n      value,\n      defaultValue,\n      setValue,\n      parent,\n      disabled,\n      validation,\n      registerControlRef,\n    }),\n    [allValues, value, defaultValue, setValue, parent, disabled, validation, registerControlRef],\n  );\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: forwardedRef,\n    props: [\n      {\n        role: 'group',\n        'aria-labelledby': labelId,\n      },\n      getDescriptionProps,\n      elementProps,\n    ],\n    stateAttributesMapping: fieldValidityMapping,\n  });\n\n  return (\n    <CheckboxGroupContext.Provider value={contextValue}>{element}</CheckboxGroupContext.Provider>\n  );\n});\n\nexport interface CheckboxGroupState extends FieldRootState {\n  /**\n   * Whether the component should ignore user interaction.\n   */\n  disabled: boolean;\n}\n\nexport interface CheckboxGroupProps extends BaseUIComponentProps<'div', CheckboxGroupState> {\n  /**\n   * Names of the checkboxes in the group that should be ticked.\n   *\n   * To render an uncontrolled checkbox group, use the `defaultValue` prop instead.\n   */\n  value?: string[] | undefined;\n  /**\n   * Names of the checkboxes in the group that should be initially ticked.\n   *\n   * To render a controlled checkbox group, use the `value` prop instead.\n   */\n  defaultValue?: string[] | undefined;\n  /**\n   * Event handler called when a checkbox in the group is ticked or unticked.\n   * Provides the new value as an argument.\n   */\n  onValueChange?:\n    | ((value: string[], eventDetails: CheckboxGroupChangeEventDetails) => void)\n    | undefined;\n  /**\n   * Names of all checkboxes in the group. Use this when creating a parent checkbox.\n   */\n  allValues?: string[] | undefined;\n  /**\n   * Whether the component should ignore user interaction.\n   * @default false\n   */\n  disabled?: boolean | undefined;\n}\n\nexport type CheckboxGroupChangeEventReason = typeof REASONS.none;\nexport type CheckboxGroupChangeEventDetails =\n  BaseUIChangeEventDetails<CheckboxGroup.ChangeEventReason>;\n\nexport namespace CheckboxGroup {\n  export type State = CheckboxGroupState;\n  export type Props = CheckboxGroupProps;\n  export type ChangeEventReason = CheckboxGroupChangeEventReason;\n  export type ChangeEventDetails = CheckboxGroupChangeEventDetails;\n}\n"
  },
  {
    "path": "packages/react/src/checkbox-group/CheckboxGroupContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport type { UseFieldValidationReturnValue } from '../field/root/useFieldValidation';\nimport type { UseCheckboxGroupParentReturnValue } from './useCheckboxGroupParent';\nimport type { BaseUIChangeEventDetails } from '../utils/createBaseUIEventDetails';\nimport type { BaseUIEventReasons } from '../utils/reasons';\n\nexport interface CheckboxGroupContext {\n  value: string[] | undefined;\n  defaultValue: string[] | undefined;\n  setValue: (\n    value: string[],\n    eventDetails: BaseUIChangeEventDetails<BaseUIEventReasons['none']>,\n  ) => void;\n  allValues: string[] | undefined;\n  parent: UseCheckboxGroupParentReturnValue;\n  disabled: boolean;\n  validation: UseFieldValidationReturnValue;\n  registerControlRef: (element: HTMLButtonElement | null) => void;\n}\n\nexport const CheckboxGroupContext = React.createContext<CheckboxGroupContext | undefined>(\n  undefined,\n);\n\nexport function useCheckboxGroupContext(optional: false): CheckboxGroupContext;\nexport function useCheckboxGroupContext(optional?: true): CheckboxGroupContext | undefined;\nexport function useCheckboxGroupContext(optional = true) {\n  const context = React.useContext(CheckboxGroupContext);\n  if (context === undefined && !optional) {\n    throw new Error(\n      'Base UI: CheckboxGroupContext is missing. CheckboxGroup parts must be placed within <CheckboxGroup>.',\n    );\n  }\n\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/checkbox-group/CheckboxGroupDataAttributes.ts",
    "content": "export enum CheckboxGroupDataAttributes {\n  /**\n   * Present when the checkbox group is disabled.\n   */\n  disabled = 'data-disabled',\n}\n"
  },
  {
    "path": "packages/react/src/checkbox-group/index.parts.ts",
    "content": "export { CheckboxGroup } from './CheckboxGroup';\n"
  },
  {
    "path": "packages/react/src/checkbox-group/index.ts",
    "content": "export { CheckboxGroup } from './CheckboxGroup';\nexport type * from './CheckboxGroup';\n"
  },
  {
    "path": "packages/react/src/checkbox-group/useCheckboxGroupParent.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { createRenderer, fireEvent, screen } from '@mui/internal-test-utils';\nimport { CheckboxGroup } from '@base-ui/react/checkbox-group';\nimport { Checkbox } from '@base-ui/react/checkbox';\n\ndescribe('useCheckboxGroupParent', () => {\n  const { render } = createRenderer();\n  const allValues = ['a', 'b', 'c'];\n\n  it('should control child checkboxes', () => {\n    const parentCheckedChange = vi.fn();\n    const childCheckedChange = vi.fn();\n    function App() {\n      const [value, setValue] = React.useState<string[]>([]);\n      return (\n        <CheckboxGroup value={value} onValueChange={setValue} allValues={allValues}>\n          <Checkbox.Root parent data-testid=\"parent\" onCheckedChange={parentCheckedChange} />\n          <Checkbox.Root value=\"a\" />\n          <Checkbox.Root value=\"b\" onCheckedChange={childCheckedChange} />\n          <Checkbox.Root value=\"c\" />\n        </CheckboxGroup>\n      );\n    }\n\n    render(<App />);\n\n    const checkboxes = screen\n      .getAllByRole('checkbox')\n      .filter((v) => v.getAttribute('value') && v.tagName === 'BUTTON');\n    const parent = screen.getByTestId('parent');\n\n    checkboxes.forEach((checkbox) => {\n      expect(checkbox).toHaveAttribute('aria-checked', 'false');\n    });\n\n    fireEvent.click(parent);\n    expect(parent).toHaveAttribute('aria-checked', 'true');\n\n    checkboxes.forEach((checkbox) => {\n      expect(checkbox).toHaveAttribute('aria-checked', 'true');\n    });\n\n    expect(parentCheckedChange.mock.calls.length).toBe(1);\n    expect(childCheckedChange.mock.calls.length).toBe(0);\n\n    fireEvent.click(parent);\n    expect(parent).toHaveAttribute('aria-checked', 'false');\n\n    checkboxes.forEach((checkbox) => {\n      expect(checkbox).toHaveAttribute('aria-checked', 'false');\n    });\n\n    expect(parentCheckedChange.mock.calls.length).toBe(2);\n    expect(childCheckedChange.mock.calls.length).toBe(0);\n  });\n\n  it('parent should be marked as mixed if some children are checked', () => {\n    const childCheckedChange = vi.fn();\n    function App() {\n      const [value, setValue] = React.useState<string[]>([]);\n      return (\n        <CheckboxGroup value={value} onValueChange={setValue} allValues={allValues}>\n          <Checkbox.Root parent data-testid=\"parent\" />\n          <Checkbox.Root value=\"a\" onCheckedChange={childCheckedChange} />\n          <Checkbox.Root value=\"b\" />\n          <Checkbox.Root value=\"c\" />\n        </CheckboxGroup>\n      );\n    }\n\n    render(<App />);\n\n    const checkboxes = screen\n      .getAllByRole('checkbox')\n      .filter((v) => v.getAttribute('data-parent') == null);\n\n    checkboxes.forEach((checkbox) => {\n      expect(checkbox).toHaveAttribute('aria-checked', 'false');\n    });\n    fireEvent.click(checkboxes[0]);\n    expect(childCheckedChange.mock.calls.length).toBe(1);\n\n    expect(screen.getByTestId('parent')).toHaveAttribute('aria-checked', 'mixed');\n  });\n\n  it('should correctly initialize the values array', () => {\n    function App() {\n      const [value, setValue] = React.useState<string[]>(['a']);\n      return (\n        <CheckboxGroup value={value} onValueChange={setValue} allValues={allValues}>\n          <Checkbox.Root parent data-testid=\"parent\" />\n          <Checkbox.Root value=\"a\" data-testid=\"checkboxA\" />\n          <Checkbox.Root value=\"b\" />\n          <Checkbox.Root value=\"c\" />\n        </CheckboxGroup>\n      );\n    }\n\n    render(<App />);\n\n    expect(screen.getByTestId('parent')).toHaveAttribute('aria-checked', 'mixed');\n\n    expect(screen.getByTestId('checkboxA')).toHaveAttribute('aria-checked', 'true');\n  });\n\n  it('should update the values array when a child checkbox is clicked', () => {\n    function App() {\n      const [value, setValue] = React.useState<string[]>(['a']);\n      return (\n        <CheckboxGroup value={value} onValueChange={setValue} allValues={allValues}>\n          <Checkbox.Root parent data-testid=\"parent\" />\n          <Checkbox.Root value=\"a\" data-testid=\"checkboxA\" />\n          <Checkbox.Root value=\"b\" />\n          <Checkbox.Root value=\"c\" />\n        </CheckboxGroup>\n      );\n    }\n\n    render(<App />);\n\n    expect(screen.getByTestId('parent')).toHaveAttribute('aria-checked', 'mixed');\n\n    const checkboxes = screen\n      .getAllByRole('checkbox')\n      .filter((v) => v.getAttribute('data-parent') == null);\n\n    const checkboxA = screen.getByTestId('checkboxA');\n    expect(checkboxA).toHaveAttribute('aria-checked', 'true');\n\n    checkboxes.forEach((checkbox) => {\n      if (checkbox !== checkboxA) {\n        fireEvent.click(checkbox);\n      }\n    });\n\n    expect(screen.getByTestId('parent')).toHaveAttribute('aria-checked', 'true');\n  });\n\n  it('should apply space-separated aria-controls attribute with child names', () => {\n    function App() {\n      const [value, setValue] = React.useState<string[]>([]);\n      return (\n        <CheckboxGroup value={value} onValueChange={setValue} allValues={allValues}>\n          <Checkbox.Root parent data-testid=\"parent\" />\n          <Checkbox.Root value=\"a\" />\n          <Checkbox.Root value=\"b\" />\n          <Checkbox.Root value=\"c\" />\n        </CheckboxGroup>\n      );\n    }\n\n    render(<App />);\n\n    const parent = screen.getByTestId('parent');\n    const id = parent.getAttribute('id');\n\n    expect(parent).toHaveAttribute('aria-controls', allValues.map((v) => `${id}-${v}`).join(' '));\n  });\n\n  it('preserves initial state if mixed when parent is clicked', () => {\n    function App() {\n      const [value, setValue] = React.useState<string[]>([]);\n      return (\n        <CheckboxGroup value={value} onValueChange={setValue} allValues={allValues}>\n          <Checkbox.Root parent data-testid=\"parent\" />\n          <Checkbox.Root value=\"a\" data-testid=\"checkboxA\" />\n          <Checkbox.Root value=\"b\" />\n          <Checkbox.Root value=\"c\" />\n        </CheckboxGroup>\n      );\n    }\n\n    render(<App />);\n\n    const checkboxes = screen\n      .getAllByRole('checkbox')\n      .filter((v) => v.getAttribute('data-parent') == null && v.tagName === 'BUTTON');\n    const checkboxA = screen.getByTestId('checkboxA');\n    const parent = screen.getByTestId('parent');\n\n    fireEvent.click(checkboxA);\n\n    expect(screen.getByTestId('parent')).toHaveAttribute('aria-checked', 'mixed');\n\n    fireEvent.click(parent);\n\n    checkboxes.forEach((checkbox) => {\n      expect(checkbox).toHaveAttribute('aria-checked', 'true');\n    });\n\n    fireEvent.click(parent);\n\n    checkboxes.forEach((checkbox) => {\n      expect(checkbox).toHaveAttribute('aria-checked', 'false');\n    });\n\n    fireEvent.click(parent);\n\n    expect(parent).toHaveAttribute('aria-checked', 'mixed');\n    expect(checkboxA).toHaveAttribute('aria-checked', 'true');\n    checkboxes.forEach((checkbox) => {\n      if (checkbox !== checkboxA) {\n        expect(checkbox).toHaveAttribute('aria-checked', 'false');\n      }\n    });\n  });\n\n  it('handles unchecked disabled checkboxes', () => {\n    function App() {\n      const [value, setValue] = React.useState<string[]>([]);\n      return (\n        <CheckboxGroup value={value} onValueChange={setValue} allValues={allValues}>\n          <Checkbox.Root parent data-testid=\"parent\" />\n          <Checkbox.Root value=\"a\" disabled data-testid=\"checkboxA\" />\n          <Checkbox.Root value=\"b\" />\n          <Checkbox.Root value=\"c\" />\n        </CheckboxGroup>\n      );\n    }\n\n    render(<App />);\n\n    const parent = screen.getByTestId('parent');\n    fireEvent.click(parent);\n\n    expect(parent).toHaveAttribute('aria-checked', 'mixed');\n    expect(screen.getByTestId('checkboxA')).toHaveAttribute('aria-checked', 'false');\n  });\n\n  it('handles checked disabled checkboxes', () => {\n    function App() {\n      const [value, setValue] = React.useState<string[]>(['a']);\n      return (\n        <CheckboxGroup value={value} onValueChange={setValue} allValues={allValues}>\n          <Checkbox.Root parent data-testid=\"parent\" />\n          <Checkbox.Root value=\"a\" data-testid=\"checkboxA\" disabled />\n          <Checkbox.Root value=\"b\" data-testid=\"checkboxB\" />\n          <Checkbox.Root value=\"c\" />\n        </CheckboxGroup>\n      );\n    }\n\n    render(<App />);\n\n    const checkboxA = screen.getByTestId('checkboxA');\n    const checkboxB = screen.getByTestId('checkboxB');\n    const parent = screen.getByTestId('parent');\n\n    fireEvent.click(parent);\n    expect(checkboxA).toHaveAttribute('aria-checked', 'true');\n    expect(checkboxB).toHaveAttribute('aria-checked', 'true');\n\n    fireEvent.click(parent);\n    expect(checkboxA).toHaveAttribute('aria-checked', 'true');\n    expect(checkboxB).toHaveAttribute('aria-checked', 'false');\n  });\n});\n"
  },
  {
    "path": "packages/react/src/checkbox-group/useCheckboxGroupParent.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useBaseUiId } from '../utils/useBaseUiId';\nimport type { BaseUIChangeEventDetails } from '../utils/createBaseUIEventDetails';\nimport type { BaseUIEventReasons } from '../utils/reasons';\n\nconst EMPTY: string[] = [];\n\nexport function useCheckboxGroupParent(\n  params: UseCheckboxGroupParentParameters,\n): UseCheckboxGroupParentReturnValue {\n  const { allValues = EMPTY, value = EMPTY, onValueChange: onValueChangeProp } = params;\n\n  const uncontrolledStateRef = React.useRef(value);\n  const disabledStatesRef = React.useRef(new Map<string, boolean>());\n\n  const [status, setStatus] = React.useState<'on' | 'off' | 'mixed'>('mixed');\n\n  const id = useBaseUiId();\n  const checked = value.length === allValues.length;\n  const indeterminate = value.length !== allValues.length && value.length > 0;\n\n  const onValueChange = useStableCallback(onValueChangeProp);\n\n  const getParentProps: UseCheckboxGroupParentReturnValue['getParentProps'] = React.useCallback(\n    () => ({\n      id,\n      indeterminate,\n      checked,\n      // TODO: custom `id` on child checkboxes breaks this\n      // https://github.com/mui/base-ui/issues/2691\n      'aria-controls': allValues.map((v) => `${id}-${v}`).join(' '),\n      onCheckedChange(_, eventDetails) {\n        const uncontrolledState = uncontrolledStateRef.current;\n\n        // None except the disabled ones that are checked, which can't be changed.\n        const none = allValues.filter(\n          (v) => disabledStatesRef.current.get(v) && uncontrolledState.includes(v),\n        );\n        // \"All\" that are valid:\n        // - any that aren't disabled\n        // - disabled ones that are checked\n        const all = allValues.filter(\n          (v) =>\n            !disabledStatesRef.current.get(v) ||\n            (disabledStatesRef.current.get(v) && uncontrolledState.includes(v)),\n        );\n\n        const allOnOrOff =\n          uncontrolledState.length === all.length || uncontrolledState.length === 0;\n\n        if (allOnOrOff) {\n          if (value.length === all.length) {\n            onValueChange(none, eventDetails);\n          } else {\n            onValueChange(all, eventDetails);\n          }\n          return;\n        }\n\n        if (status === 'mixed') {\n          onValueChange(all, eventDetails);\n          setStatus('on');\n        } else if (status === 'on') {\n          onValueChange(none, eventDetails);\n          setStatus('off');\n        } else if (status === 'off') {\n          onValueChange(uncontrolledState, eventDetails);\n          setStatus('mixed');\n        }\n      },\n    }),\n    [allValues, checked, id, indeterminate, onValueChange, status, value.length],\n  );\n\n  const getChildProps: UseCheckboxGroupParentReturnValue['getChildProps'] = React.useCallback(\n    (childValue: string) => ({\n      checked: value.includes(childValue),\n      onCheckedChange(nextChecked, eventDetails) {\n        const newValue = value.slice();\n        if (nextChecked) {\n          newValue.push(childValue);\n        } else {\n          newValue.splice(newValue.indexOf(childValue), 1);\n        }\n        uncontrolledStateRef.current = newValue;\n        onValueChange(newValue, eventDetails);\n        setStatus('mixed');\n      },\n    }),\n    [onValueChange, value],\n  );\n\n  return React.useMemo(\n    () => ({\n      id,\n      indeterminate,\n      getParentProps,\n      getChildProps,\n      disabledStatesRef,\n    }),\n    [id, indeterminate, getParentProps, getChildProps],\n  );\n}\n\nexport interface UseCheckboxGroupParentParameters {\n  allValues?: string[] | undefined;\n  value?: string[] | undefined;\n  onValueChange?:\n    | ((\n        value: string[],\n        eventDetails: BaseUIChangeEventDetails<BaseUIEventReasons['none']>,\n      ) => void)\n    | undefined;\n}\n\nexport interface UseCheckboxGroupParentReturnValue {\n  id: string | undefined;\n  indeterminate: boolean;\n  disabledStatesRef: React.RefObject<Map<string, boolean>>;\n  getParentProps: () => {\n    id: string | undefined;\n    indeterminate: boolean;\n    checked: boolean;\n    'aria-controls': string;\n    onCheckedChange: (\n      checked: boolean,\n      eventDetails: BaseUIChangeEventDetails<BaseUIEventReasons['none']>,\n    ) => void;\n  };\n  getChildProps: (value: string) => {\n    checked: boolean;\n    onCheckedChange: (\n      checked: boolean,\n      eventDetails: BaseUIChangeEventDetails<BaseUIEventReasons['none']>,\n    ) => void;\n  };\n}\n\nexport interface UseCheckboxGroupParentState {}\n"
  },
  {
    "path": "packages/react/src/collapsible/index.parts.ts",
    "content": "export { CollapsibleRoot as Root } from './root/CollapsibleRoot';\nexport { CollapsibleTrigger as Trigger } from './trigger/CollapsibleTrigger';\nexport { CollapsiblePanel as Panel } from './panel/CollapsiblePanel';\n"
  },
  {
    "path": "packages/react/src/collapsible/index.ts",
    "content": "export * as Collapsible from './index.parts';\n\nexport type * from './root/CollapsibleRoot';\nexport type * from './trigger/CollapsibleTrigger';\nexport type * from './panel/CollapsiblePanel';\n"
  },
  {
    "path": "packages/react/src/collapsible/panel/CollapsiblePanel.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { act, fireEvent, flushMicrotasks, screen } from '@mui/internal-test-utils';\nimport { Collapsible } from '@base-ui/react/collapsible';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\n\nconst PANEL_CONTENT = 'This is panel content';\n\ndescribe('<Collapsible.Panel />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Collapsible.Panel />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render: (node) => {\n      return render(<Collapsible.Root defaultOpen>{node}</Collapsible.Root>);\n    },\n  }));\n\n  describe('prop: keepMounted', () => {\n    it('does not unmount the panel when true', async () => {\n      function App() {\n        const [open, setOpen] = React.useState(false);\n        return (\n          <Collapsible.Root open={open} onOpenChange={setOpen}>\n            <Collapsible.Trigger />\n            <Collapsible.Panel keepMounted>{PANEL_CONTENT}</Collapsible.Panel>\n          </Collapsible.Root>\n        );\n      }\n\n      await render(<App />);\n\n      const trigger = screen.getByRole('button');\n\n      expect(trigger).toHaveAttribute('aria-expanded', 'false');\n      expect(screen.queryByText(PANEL_CONTENT)).not.toBe(null);\n      expect(screen.queryByText(PANEL_CONTENT)).not.toBeVisible();\n      expect(screen.queryByText(PANEL_CONTENT)).toHaveAttribute('data-closed');\n\n      fireEvent.click(trigger);\n      await flushMicrotasks();\n\n      expect(trigger).toHaveAttribute('aria-expanded', 'true');\n      expect(trigger.getAttribute('aria-controls')).toBe(\n        screen.queryByText(PANEL_CONTENT)?.getAttribute('id'),\n      );\n      expect(screen.queryByText(PANEL_CONTENT)).toBeVisible();\n      expect(screen.queryByText(PANEL_CONTENT)).toHaveAttribute('data-open');\n      expect(trigger).toHaveAttribute('data-panel-open');\n\n      fireEvent.click(trigger);\n      await flushMicrotasks();\n\n      expect(trigger).toHaveAttribute('aria-expanded', 'false');\n      expect(trigger.getAttribute('aria-controls')).toBe(null);\n      expect(screen.queryByText(PANEL_CONTENT)).not.toBeVisible();\n      expect(screen.queryByText(PANEL_CONTENT)).toHaveAttribute('data-closed');\n    });\n  });\n\n  // we test firefox in browserstack which does not support this yet\n  describe.skipIf(!('onbeforematch' in window) || isJSDOM)('prop: hiddenUntilFound', () => {\n    it('uses `hidden=\"until-found\" to hide panel when true', async () => {\n      const handleOpenChange = vi.fn();\n\n      await render(\n        <Collapsible.Root defaultOpen={false} onOpenChange={handleOpenChange}>\n          <Collapsible.Trigger />\n          <Collapsible.Panel hiddenUntilFound keepMounted>\n            {PANEL_CONTENT}\n          </Collapsible.Panel>\n        </Collapsible.Root>,\n      );\n\n      const panel = screen.queryByText(PANEL_CONTENT);\n\n      act(() => {\n        const event = new window.Event('beforematch', {\n          bubbles: true,\n          cancelable: false,\n        });\n        panel?.dispatchEvent(event);\n      });\n\n      expect(handleOpenChange.mock.calls.length).toBe(1);\n      expect(panel).toHaveAttribute('data-open');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/collapsible/panel/CollapsiblePanel.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { warn } from '@base-ui/utils/warn';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useCollapsibleRootContext } from '../root/CollapsibleRootContext';\nimport type { CollapsibleRootState } from '../root/CollapsibleRoot';\nimport { collapsibleStateAttributesMapping } from '../root/stateAttributesMapping';\nimport { useCollapsiblePanel } from './useCollapsiblePanel';\nimport { CollapsiblePanelCssVars } from './CollapsiblePanelCssVars';\nimport { useOpenChangeComplete } from '../../utils/useOpenChangeComplete';\nimport type { TransitionStatus } from '../../utils/useTransitionStatus';\n\n/**\n * A panel with the collapsible contents.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Collapsible](https://base-ui.com/react/components/collapsible)\n */\nexport const CollapsiblePanel = React.forwardRef(function CollapsiblePanel(\n  componentProps: CollapsiblePanel.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const {\n    className,\n    hiddenUntilFound: hiddenUntilFoundProp,\n    keepMounted: keepMountedProp,\n    render,\n    id: idProp,\n    ...elementProps\n  } = componentProps;\n\n  if (process.env.NODE_ENV !== 'production') {\n    // eslint-disable-next-line react-hooks/rules-of-hooks\n    useIsoLayoutEffect(() => {\n      if (hiddenUntilFoundProp && keepMountedProp === false) {\n        warn(\n          'The `keepMounted={false}` prop on a Collapsible will be ignored when using `hiddenUntilFound` since it requires the Panel to remain mounted even when closed.',\n        );\n      }\n    }, [hiddenUntilFoundProp, keepMountedProp]);\n  }\n\n  const {\n    abortControllerRef,\n    animationTypeRef,\n    height,\n    mounted,\n    onOpenChange,\n    open,\n    panelId,\n    panelRef,\n    runOnceAnimationsFinish,\n    setDimensions,\n    setHiddenUntilFound,\n    setKeepMounted,\n    setMounted,\n    setPanelIdState,\n    setOpen,\n    setVisible,\n    state,\n    transitionDimensionRef,\n    visible,\n    width,\n    transitionStatus,\n  } = useCollapsibleRootContext();\n\n  const hiddenUntilFound = hiddenUntilFoundProp ?? false;\n  const keepMounted = keepMountedProp ?? false;\n\n  useIsoLayoutEffect(() => {\n    if (idProp) {\n      setPanelIdState(idProp);\n      return () => {\n        setPanelIdState(undefined);\n      };\n    }\n    return undefined;\n  }, [idProp, setPanelIdState]);\n\n  useIsoLayoutEffect(() => {\n    setHiddenUntilFound(hiddenUntilFound);\n  }, [setHiddenUntilFound, hiddenUntilFound]);\n\n  useIsoLayoutEffect(() => {\n    setKeepMounted(keepMounted);\n  }, [setKeepMounted, keepMounted]);\n\n  const { props } = useCollapsiblePanel({\n    abortControllerRef,\n    animationTypeRef,\n    externalRef: forwardedRef,\n    height,\n    hiddenUntilFound,\n    id: panelId,\n    keepMounted,\n    mounted,\n    onOpenChange,\n    open,\n    panelRef,\n    runOnceAnimationsFinish,\n    setDimensions,\n    setMounted,\n    setOpen,\n    setVisible,\n    transitionDimensionRef,\n    visible,\n    width,\n  });\n\n  useOpenChangeComplete({\n    open: open && transitionStatus === 'idle',\n    ref: panelRef,\n    onComplete() {\n      if (!open) {\n        return;\n      }\n\n      setDimensions({ height: undefined, width: undefined });\n    },\n  });\n\n  const panelState: CollapsiblePanelState = React.useMemo(\n    () => ({\n      ...state,\n      transitionStatus,\n    }),\n    [state, transitionStatus],\n  );\n\n  const element = useRenderElement('div', componentProps, {\n    state: panelState,\n    ref: [forwardedRef, panelRef],\n    props: [\n      props,\n      {\n        style: {\n          [CollapsiblePanelCssVars.collapsiblePanelHeight as string]:\n            height === undefined ? 'auto' : `${height}px`,\n          [CollapsiblePanelCssVars.collapsiblePanelWidth as string]:\n            width === undefined ? 'auto' : `${width}px`,\n        },\n      },\n      elementProps,\n    ],\n    stateAttributesMapping: collapsibleStateAttributesMapping,\n  });\n\n  const shouldRender = keepMounted || hiddenUntilFound || mounted;\n\n  if (!shouldRender) {\n    return null;\n  }\n\n  return element;\n});\n\nexport interface CollapsiblePanelState extends CollapsibleRootState {\n  /**\n   * The transition status of the component.\n   */\n  transitionStatus: TransitionStatus;\n}\n\nexport interface CollapsiblePanelProps extends BaseUIComponentProps<'div', CollapsiblePanelState> {\n  /**\n   * Allows the browser’s built-in page search to find and expand the panel contents.\n   *\n   * Overrides the `keepMounted` prop and uses `hidden=\"until-found\"`\n   * to hide the element without removing it from the DOM.\n   *\n   * @default false\n   */\n  hiddenUntilFound?: boolean | undefined;\n  /**\n   * Whether to keep the element in the DOM while the panel is hidden.\n   * This prop is ignored when `hiddenUntilFound` is used.\n   * @default false\n   */\n  keepMounted?: boolean | undefined;\n}\n\nexport namespace CollapsiblePanel {\n  export type State = CollapsiblePanelState;\n  export type Props = CollapsiblePanelProps;\n}\n"
  },
  {
    "path": "packages/react/src/collapsible/panel/CollapsiblePanelCssVars.ts",
    "content": "export enum CollapsiblePanelCssVars {\n  /**\n   * The collapsible panel's height.\n   * @type {number}\n   */\n  collapsiblePanelHeight = '--collapsible-panel-height',\n  /**\n   * The collapsible panel's width.\n   * @type {number}\n   */\n  collapsiblePanelWidth = '--collapsible-panel-width',\n}\n"
  },
  {
    "path": "packages/react/src/collapsible/panel/CollapsiblePanelDataAttributes.ts",
    "content": "import { TransitionStatusDataAttributes } from '../../utils/stateAttributesMapping';\n\nexport enum CollapsiblePanelDataAttributes {\n  /**\n   * Present when the collapsible panel is open.\n   */\n  open = 'data-open',\n  /**\n   * Present when the collapsible panel is closed.\n   */\n  closed = 'data-closed',\n  /**\n   * Present when the panel is animating in.\n   */\n  startingStyle = TransitionStatusDataAttributes.startingStyle,\n  /**\n   * Present when the panel is animating out.\n   */\n  endingStyle = TransitionStatusDataAttributes.endingStyle,\n}\n"
  },
  {
    "path": "packages/react/src/collapsible/panel/useCollapsiblePanel.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useMergedRefs } from '@base-ui/utils/useMergedRefs';\nimport { useOnMount } from '@base-ui/utils/useOnMount';\nimport { AnimationFrame, useAnimationFrame } from '@base-ui/utils/useAnimationFrame';\nimport { warn } from '@base-ui/utils/warn';\nimport { HTMLProps } from '../../utils/types';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport type { AnimationType, Dimensions } from '../root/useCollapsibleRoot';\nimport { CollapsiblePanelDataAttributes } from './CollapsiblePanelDataAttributes';\nimport { AccordionRootDataAttributes } from '../../accordion/root/AccordionRootDataAttributes';\nimport type { CollapsibleRoot } from '../root/CollapsibleRoot';\n\nexport function useCollapsiblePanel(\n  parameters: UseCollapsiblePanelParameters,\n): UseCollapsiblePanelReturnValue {\n  const {\n    abortControllerRef,\n    animationTypeRef,\n    externalRef,\n    height,\n    hiddenUntilFound,\n    keepMounted,\n    id: idParam,\n    mounted,\n    onOpenChange,\n    open,\n    panelRef,\n    runOnceAnimationsFinish,\n    setDimensions,\n    setMounted,\n    setOpen,\n    setVisible,\n    transitionDimensionRef,\n    visible,\n    width,\n  } = parameters;\n\n  const isBeforeMatchRef = React.useRef(false);\n  const latestAnimationNameRef = React.useRef<string>(null);\n  const shouldCancelInitialOpenAnimationRef = React.useRef(open);\n  const shouldCancelInitialOpenTransitionRef = React.useRef(open);\n\n  const endingStyleFrame = useAnimationFrame();\n\n  /**\n   * When opening, the `hidden` attribute is removed immediately.\n   * When closing, the `hidden` attribute is set after any exit animations runs.\n   */\n  const hidden = React.useMemo(() => {\n    if (animationTypeRef.current === 'css-animation') {\n      return !visible;\n    }\n\n    return !open && !mounted;\n  }, [open, mounted, visible, animationTypeRef]);\n\n  /**\n   * When `keepMounted` is `true` this runs once as soon as it exists in the DOM\n   * regardless of initial open state.\n   *\n   * When `keepMounted` is `false` this runs on every mount, typically every\n   * time it opens. If the panel is in the middle of a close transition that is\n   * interrupted and re-opens, this won't run as the panel was not unmounted.\n   */\n  const handlePanelRef = useStableCallback((element: HTMLElement) => {\n    if (!element) {\n      return undefined;\n    }\n    if (animationTypeRef.current == null || transitionDimensionRef.current == null) {\n      const panelStyles = getComputedStyle(element);\n\n      const hasAnimation = panelStyles.animationName !== 'none' && panelStyles.animationName !== '';\n      const hasTransition =\n        panelStyles.transitionDuration !== '0s' && panelStyles.transitionDuration !== '';\n\n      /**\n       * animationTypeRef is safe to read in render because it's only ever set\n       * once here during the first render and never again.\n       * https://react.dev/learn/referencing-values-with-refs#best-practices-for-refs\n       */\n      if (hasAnimation && hasTransition) {\n        if (process.env.NODE_ENV !== 'production') {\n          warn(\n            'CSS transitions and CSS animations both detected on Collapsible or Accordion panel.',\n            'Only one of either animation type should be used.',\n          );\n        }\n      } else if (panelStyles.animationName === 'none' && panelStyles.transitionDuration !== '0s') {\n        animationTypeRef.current = 'css-transition';\n      } else if (panelStyles.animationName !== 'none' && panelStyles.transitionDuration === '0s') {\n        animationTypeRef.current = 'css-animation';\n      } else {\n        animationTypeRef.current = 'none';\n      }\n\n      /**\n       * We need to know in advance which side is being collapsed when using CSS\n       * transitions in order to set the value of width/height to `0px` momentarily.\n       * Setting both to `0px` will break layout.\n       */\n      if (\n        element.getAttribute(AccordionRootDataAttributes.orientation) === 'horizontal' ||\n        panelStyles.transitionProperty.indexOf('width') > -1\n      ) {\n        transitionDimensionRef.current = 'width';\n      } else {\n        transitionDimensionRef.current = 'height';\n      }\n    }\n\n    if (animationTypeRef.current !== 'css-transition') {\n      return undefined;\n    }\n\n    if (height === undefined || width === undefined) {\n      setDimensions({ height: element.scrollHeight, width: element.scrollWidth });\n\n      if (shouldCancelInitialOpenTransitionRef.current) {\n        element.style.setProperty('transition-duration', '0s');\n      }\n    }\n\n    let frame = -1;\n    let nextFrame = -1;\n\n    frame = AnimationFrame.request(() => {\n      shouldCancelInitialOpenTransitionRef.current = false;\n      nextFrame = AnimationFrame.request(() => {\n        /**\n         * This is slightly faster than another RAF and is the earliest\n         * opportunity to remove the temporary `transition-duration: 0s` that\n         * was applied to cancel opening transitions of initially open panels.\n         * https://nolanlawson.com/2018/09/25/accurately-measuring-layout-on-the-web/\n         */\n        setTimeout(() => {\n          element.style.removeProperty('transition-duration');\n        });\n      });\n    });\n\n    return () => {\n      AnimationFrame.cancel(frame);\n      AnimationFrame.cancel(nextFrame);\n    };\n  });\n\n  const mergedPanelRef = useMergedRefs(externalRef, panelRef, handlePanelRef);\n\n  useIsoLayoutEffect(() => {\n    if (animationTypeRef.current !== 'css-transition') {\n      return undefined;\n    }\n\n    const panel = panelRef.current;\n\n    if (!panel) {\n      return undefined;\n    }\n\n    let resizeFrame = -1;\n\n    if (abortControllerRef.current != null) {\n      abortControllerRef.current.abort();\n      abortControllerRef.current = null;\n    }\n\n    if (open) {\n      const originalLayoutStyles = {\n        'justify-content': panel.style.justifyContent,\n        'align-items': panel.style.alignItems,\n        'align-content': panel.style.alignContent,\n        'justify-items': panel.style.justifyItems,\n      };\n\n      /* opening */\n      Object.keys(originalLayoutStyles).forEach((key) => {\n        panel.style.setProperty(key, 'initial', 'important');\n      });\n\n      /**\n       * When `keepMounted={false}` and the panel is initially closed, the very\n       * first time it opens (not any subsequent opens) `data-starting-style` is\n       * off or missing by a frame so we need to set it manually. Otherwise any\n       * CSS properties expected to transition using [data-starting-style] may\n       * be mis-timed and appear to be complete skipped.\n       */\n      if (!shouldCancelInitialOpenTransitionRef.current && !keepMounted) {\n        panel.setAttribute(CollapsiblePanelDataAttributes.startingStyle, '');\n      }\n\n      setDimensions({ height: panel.scrollHeight, width: panel.scrollWidth });\n\n      resizeFrame = AnimationFrame.request(() => {\n        Object.entries(originalLayoutStyles).forEach(([key, value]) => {\n          if (value === '') {\n            panel.style.removeProperty(key);\n          } else {\n            panel.style.setProperty(key, value);\n          }\n        });\n      });\n    } else {\n      if (panel.scrollHeight === 0 && panel.scrollWidth === 0) {\n        return undefined;\n      }\n\n      /* closing */\n      setDimensions({ height: panel.scrollHeight, width: panel.scrollWidth });\n\n      const abortController = new AbortController();\n      abortControllerRef.current = abortController;\n      const signal = abortController.signal;\n\n      let attributeObserver: MutationObserver | null = null;\n\n      const endingStyleAttribute = CollapsiblePanelDataAttributes.endingStyle;\n\n      // Wait for `[data-ending-style]` to be applied.\n      attributeObserver = new MutationObserver((mutationList) => {\n        const hasEndingStyle = mutationList.some(\n          (mutation) =>\n            mutation.type === 'attributes' && mutation.attributeName === endingStyleAttribute,\n        );\n\n        if (hasEndingStyle) {\n          attributeObserver?.disconnect();\n          attributeObserver = null;\n          runOnceAnimationsFinish(() => {\n            setDimensions({ height: 0, width: 0 });\n            panel.style.removeProperty('content-visibility');\n            setMounted(false);\n            if (abortControllerRef.current === abortController) {\n              abortControllerRef.current = null;\n            }\n          }, signal);\n        }\n      });\n\n      attributeObserver.observe(panel, {\n        attributes: true,\n        attributeFilter: [endingStyleAttribute],\n      });\n\n      return () => {\n        attributeObserver?.disconnect();\n        endingStyleFrame.cancel();\n        if (abortControllerRef.current === abortController) {\n          abortController.abort();\n          abortControllerRef.current = null;\n        }\n      };\n    }\n\n    return () => {\n      AnimationFrame.cancel(resizeFrame);\n    };\n  }, [\n    abortControllerRef,\n    animationTypeRef,\n    endingStyleFrame,\n    hiddenUntilFound,\n    keepMounted,\n    mounted,\n    open,\n    panelRef,\n    runOnceAnimationsFinish,\n    setDimensions,\n    setMounted,\n  ]);\n\n  useIsoLayoutEffect(() => {\n    if (animationTypeRef.current !== 'css-animation') {\n      return;\n    }\n\n    const panel = panelRef.current;\n    if (!panel) {\n      return;\n    }\n\n    latestAnimationNameRef.current = panel.style.animationName || latestAnimationNameRef.current;\n\n    panel.style.setProperty('animation-name', 'none');\n\n    setDimensions({ height: panel.scrollHeight, width: panel.scrollWidth });\n\n    if (!shouldCancelInitialOpenAnimationRef.current && !isBeforeMatchRef.current) {\n      panel.style.removeProperty('animation-name');\n    }\n\n    if (open) {\n      if (abortControllerRef.current != null) {\n        abortControllerRef.current.abort();\n        abortControllerRef.current = null;\n      }\n      setMounted(true);\n      setVisible(true);\n    } else {\n      abortControllerRef.current = new AbortController();\n      runOnceAnimationsFinish(() => {\n        setMounted(false);\n        setVisible(false);\n        abortControllerRef.current = null;\n      }, abortControllerRef.current.signal);\n    }\n  }, [\n    abortControllerRef,\n    animationTypeRef,\n    open,\n    panelRef,\n    runOnceAnimationsFinish,\n    setDimensions,\n    setMounted,\n    setVisible,\n    visible,\n  ]);\n\n  useOnMount(() => {\n    const frame = AnimationFrame.request(() => {\n      shouldCancelInitialOpenAnimationRef.current = false;\n    });\n    return () => AnimationFrame.cancel(frame);\n  });\n\n  useIsoLayoutEffect(() => {\n    if (!hiddenUntilFound) {\n      return undefined;\n    }\n\n    const panel = panelRef.current;\n    if (!panel) {\n      return undefined;\n    }\n\n    let frame = -1;\n    let nextFrame = -1;\n\n    if (open && isBeforeMatchRef.current) {\n      panel.style.transitionDuration = '0s';\n      setDimensions({ height: panel.scrollHeight, width: panel.scrollWidth });\n      frame = AnimationFrame.request(() => {\n        isBeforeMatchRef.current = false;\n        nextFrame = AnimationFrame.request(() => {\n          setTimeout(() => {\n            panel.style.removeProperty('transition-duration');\n          });\n        });\n      });\n    }\n\n    return () => {\n      AnimationFrame.cancel(frame);\n      AnimationFrame.cancel(nextFrame);\n    };\n  }, [hiddenUntilFound, open, panelRef, setDimensions]);\n\n  useIsoLayoutEffect(() => {\n    const panel = panelRef.current;\n\n    if (panel && hiddenUntilFound && hidden) {\n      /**\n       * React only supports a boolean for the `hidden` attribute and forces\n       * legit string values to booleans so we have to force it back in the DOM\n       * when necessary: https://github.com/facebook/react/issues/24740\n       */\n      panel.setAttribute('hidden', 'until-found');\n      /**\n       * Set data-starting-style here to persist the closed styles, this is to\n       * prevent transitions from starting when the `hidden` attribute changes\n       * to `'until-found'` as they could have different `display` properties:\n       * https://github.com/tailwindlabs/tailwindcss/pull/14625\n       */\n      if (animationTypeRef.current === 'css-transition') {\n        panel.setAttribute(CollapsiblePanelDataAttributes.startingStyle, '');\n      }\n    }\n  }, [hiddenUntilFound, hidden, animationTypeRef, panelRef]);\n\n  React.useEffect(\n    function registerBeforeMatchListener() {\n      const panel = panelRef.current;\n      if (!panel) {\n        return undefined;\n      }\n\n      function handleBeforeMatch(event: Event) {\n        isBeforeMatchRef.current = true;\n        setOpen(true);\n        onOpenChange(true, createChangeEventDetails(REASONS.none, event));\n      }\n\n      panel.addEventListener('beforematch', handleBeforeMatch);\n\n      return () => {\n        panel.removeEventListener('beforematch', handleBeforeMatch);\n      };\n    },\n    [onOpenChange, panelRef, setOpen],\n  );\n\n  return React.useMemo(\n    () => ({\n      props: {\n        hidden,\n        id: idParam,\n        ref: mergedPanelRef,\n      },\n    }),\n    [hidden, idParam, mergedPanelRef],\n  );\n}\n\nexport interface UseCollapsiblePanelParameters {\n  abortControllerRef: React.RefObject<AbortController | null>;\n  animationTypeRef: React.RefObject<AnimationType>;\n  externalRef: React.ForwardedRef<HTMLDivElement>;\n  /**\n   * The height of the panel.\n   */\n  height: number | undefined;\n  /**\n   * Allows the browser’s built-in page search to find and expand the panel contents.\n   *\n   * Overrides the `keepMounted` prop and uses `hidden=\"until-found\"`\n   * to hide the element without removing it from the DOM.\n   */\n  hiddenUntilFound: boolean;\n  /**\n   * The `id` attribute of the panel.\n   */\n  id: React.HTMLAttributes<Element>['id'];\n  /**\n   * Whether to keep the element in the DOM while the panel is closed.\n   * This prop is ignored when `hiddenUntilFound` is used.\n   */\n  keepMounted: boolean;\n  /**\n   * Whether the collapsible panel is currently mounted.\n   */\n  mounted: boolean;\n  onOpenChange: (open: boolean, eventDetails: CollapsibleRoot.ChangeEventDetails) => void;\n  /**\n   * Whether the collapsible panel is currently open.\n   */\n  open: boolean;\n  panelRef: React.RefObject<HTMLElement | null>;\n  runOnceAnimationsFinish: (fnToExecute: () => void, signal?: AbortSignal | null) => void;\n  setDimensions: React.Dispatch<React.SetStateAction<Dimensions>>;\n  setMounted: (nextMounted: boolean) => void;\n  setOpen: (nextOpen: boolean) => void;\n  setVisible: React.Dispatch<React.SetStateAction<boolean>>;\n  transitionDimensionRef: React.RefObject<'height' | 'width' | null>;\n  /**\n   * The visible state of the panel used to determine the `[hidden]` attribute\n   * only when CSS keyframe animations are used.\n   */\n  visible: boolean;\n  /**\n   * The width of the panel.\n   */\n  width: number | undefined;\n}\n\nexport interface UseCollapsiblePanelReturnValue {\n  props: HTMLProps;\n}\n\nexport interface UseCollapsiblePanelState {}\n"
  },
  {
    "path": "packages/react/src/collapsible/root/CollapsibleRoot.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { screen } from '@mui/internal-test-utils';\nimport { Collapsible } from '@base-ui/react/collapsible';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\nimport { REASONS } from '../../utils/reasons';\n\nconst PANEL_CONTENT = 'This is panel content';\n\ndescribe('<Collapsible.Root />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Collapsible.Root />, () => ({\n    render,\n    refInstanceof: window.HTMLDivElement,\n  }));\n\n  describe('ARIA attributes', () => {\n    it('sets ARIA attributes', async () => {\n      await render(\n        <Collapsible.Root defaultOpen>\n          <Collapsible.Trigger />\n          <Collapsible.Panel data-testid=\"panel\" />\n        </Collapsible.Root>,\n      );\n\n      const trigger = screen.getByRole('button');\n      const panel = screen.getByTestId('panel');\n\n      expect(trigger).toHaveAttribute('aria-expanded');\n      expect(trigger).toHaveAttribute('aria-controls');\n      expect(trigger.getAttribute('aria-controls')).toBe(panel.getAttribute('id'));\n    });\n\n    it('references manual panel id in trigger aria-controls', async () => {\n      await render(\n        <Collapsible.Root defaultOpen>\n          <Collapsible.Trigger />\n          <Collapsible.Panel id=\"custom-panel-id\" data-testid=\"panel\" />\n        </Collapsible.Root>,\n      );\n\n      const trigger = screen.getByRole('button');\n      const panel = screen.getByTestId('panel');\n\n      expect(trigger).toHaveAttribute('aria-controls', 'custom-panel-id');\n      expect(panel).toHaveAttribute('id', 'custom-panel-id');\n    });\n  });\n\n  describe('collapsible status', () => {\n    it('disabled status', async () => {\n      await render(\n        <Collapsible.Root disabled>\n          <Collapsible.Trigger />\n          <Collapsible.Panel data-testid=\"panel\" />\n        </Collapsible.Root>,\n      );\n\n      const trigger = screen.getByRole('button');\n\n      expect(trigger).toHaveAttribute('data-disabled');\n    });\n  });\n\n  describe('BaseUIChangeEventDetails', () => {\n    it('calls onOpenChange with eventDetails', async () => {\n      const handleOpenChange = vi.fn();\n\n      const { user } = await render(\n        <Collapsible.Root onOpenChange={handleOpenChange}>\n          <Collapsible.Trigger>Toggle</Collapsible.Trigger>\n          <Collapsible.Panel>{PANEL_CONTENT}</Collapsible.Panel>\n        </Collapsible.Root>,\n      );\n\n      const trigger = screen.getByRole('button', { name: 'Toggle' });\n      await user.click(trigger);\n\n      expect(handleOpenChange.mock.calls.length).toBe(1);\n      const [openArg, details] = handleOpenChange.mock.calls[0] as [boolean, any];\n      expect(openArg).toBe(true);\n      expect(details).not.toBe(undefined);\n      expect(details.reason).toBe(REASONS.triggerPress);\n      expect(details.event).toBeInstanceOf(MouseEvent);\n      expect(details.isCanceled).toBe(false);\n      expect(typeof details.cancel).toBe('function');\n      expect(typeof details.allowPropagation).toBe('function');\n    });\n  });\n\n  describe.skipIf(isJSDOM)('open state', () => {\n    it('controlled mode', async () => {\n      function App() {\n        const [open, setOpen] = React.useState(false);\n        return (\n          <React.Fragment>\n            <Collapsible.Root open={open}>\n              <Collapsible.Trigger>trigger</Collapsible.Trigger>\n              <Collapsible.Panel>This is panel content</Collapsible.Panel>\n            </Collapsible.Root>\n            <button type=\"button\" onClick={() => setOpen(!open)}>\n              toggle\n            </button>\n          </React.Fragment>\n        );\n      }\n      const { user } = await render(<App />);\n\n      const externalTrigger = screen.getByRole('button', { name: 'toggle' });\n      const trigger = screen.getByRole('button', { name: 'trigger' });\n\n      expect(trigger).not.toHaveAttribute('aria-controls');\n      expect(trigger).toHaveAttribute('aria-expanded', 'false');\n      expect(screen.queryByText(PANEL_CONTENT)).toBe(null);\n\n      await user.click(externalTrigger);\n\n      expect(trigger).toHaveAttribute('aria-expanded', 'true');\n      expect(trigger).toHaveAttribute('aria-controls');\n\n      expect(screen.queryByText(PANEL_CONTENT)).not.toBe(null);\n      expect(screen.queryByText(PANEL_CONTENT)).toBeVisible();\n      expect(screen.queryByText(PANEL_CONTENT)).toHaveAttribute('data-open');\n      expect(trigger).toHaveAttribute('data-panel-open');\n\n      await user.click(externalTrigger);\n\n      expect(trigger).not.toHaveAttribute('aria-controls');\n      expect(trigger).toHaveAttribute('aria-expanded', 'false');\n      expect(screen.queryByText(PANEL_CONTENT)).toBe(null);\n    });\n\n    it('uncontrolled mode', async () => {\n      const { user } = await render(\n        <Collapsible.Root defaultOpen={false}>\n          <Collapsible.Trigger />\n          <Collapsible.Panel>This is panel content</Collapsible.Panel>\n        </Collapsible.Root>,\n      );\n\n      const trigger = screen.getByRole('button');\n\n      expect(trigger).not.toHaveAttribute('aria-controls');\n      expect(trigger).toHaveAttribute('aria-expanded', 'false');\n      expect(screen.queryByText(PANEL_CONTENT)).toBe(null);\n\n      await user.pointer({ keys: '[MouseLeft]', target: trigger });\n\n      expect(trigger).toHaveAttribute('aria-expanded', 'true');\n      expect(trigger).toHaveAttribute('aria-controls');\n      expect(screen.queryByText(PANEL_CONTENT)).not.toBe(null);\n      expect(screen.queryByText(PANEL_CONTENT)).toBeVisible();\n      expect(screen.queryByText(PANEL_CONTENT)).toHaveAttribute('data-open');\n      expect(trigger).toHaveAttribute('data-panel-open');\n\n      await user.pointer({ keys: '[MouseLeft]', target: trigger });\n\n      expect(trigger).toHaveAttribute('aria-expanded', 'false');\n      expect(trigger).not.toHaveAttribute('aria-controls');\n      expect(trigger).not.toHaveAttribute('data-panel-open');\n      expect(screen.queryByText(PANEL_CONTENT)).toBe(null);\n    });\n  });\n\n  describe.skipIf(isJSDOM)('keyboard interactions', () => {\n    ['Enter', 'Space'].forEach((key) => {\n      it(`key: ${key} should toggle the Collapsible`, async () => {\n        const { user } = await render(\n          <Collapsible.Root defaultOpen={false}>\n            <Collapsible.Trigger>Trigger</Collapsible.Trigger>\n            <Collapsible.Panel>This is panel content</Collapsible.Panel>\n          </Collapsible.Root>,\n        );\n\n        const trigger = screen.getByRole('button');\n\n        expect(trigger).not.toHaveAttribute('aria-controls');\n        expect(trigger).toHaveAttribute('aria-expanded', 'false');\n        expect(screen.queryByText(PANEL_CONTENT)).toBe(null);\n\n        await user.keyboard('[Tab]');\n        expect(trigger).toHaveFocus();\n        await user.keyboard(`[${key}]`);\n\n        expect(trigger).toHaveAttribute('aria-controls');\n        expect(trigger).toHaveAttribute('aria-expanded', 'true');\n        expect(trigger).toHaveAttribute('data-panel-open');\n        expect(screen.queryByText(PANEL_CONTENT)).toBeVisible();\n        expect(screen.queryByText(PANEL_CONTENT)).not.toBe(null);\n        expect(screen.queryByText(PANEL_CONTENT)).toHaveAttribute('data-open');\n\n        await user.keyboard(`[${key}]`);\n\n        expect(trigger).not.toHaveAttribute('aria-controls');\n        expect(trigger).toHaveAttribute('aria-expanded', 'false');\n        expect(trigger).not.toHaveAttribute('data-panel-open');\n        expect(screen.queryByText(PANEL_CONTENT)).toBe(null);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/collapsible/root/CollapsibleRoot.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useCollapsibleRoot, type UseCollapsibleRootReturnValue } from './useCollapsibleRoot';\nimport { CollapsibleRootContext } from './CollapsibleRootContext';\nimport { collapsibleStateAttributesMapping } from './stateAttributesMapping';\nimport type { BaseUIChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\n\n/**\n * Groups all parts of the collapsible.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Collapsible](https://base-ui.com/react/components/collapsible)\n */\nexport const CollapsibleRoot = React.forwardRef(function CollapsibleRoot(\n  componentProps: CollapsibleRoot.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const {\n    render,\n    className,\n    defaultOpen = false,\n    disabled = false,\n    onOpenChange: onOpenChangeProp,\n    open,\n    ...elementProps\n  } = componentProps;\n\n  const onOpenChange = useStableCallback(onOpenChangeProp);\n\n  const collapsible = useCollapsibleRoot({\n    open,\n    defaultOpen,\n    onOpenChange,\n    disabled,\n  });\n\n  const state: CollapsibleRootState = React.useMemo(\n    () => ({\n      open: collapsible.open,\n      disabled: collapsible.disabled,\n      transitionStatus: collapsible.transitionStatus,\n    }),\n    [collapsible.open, collapsible.disabled, collapsible.transitionStatus],\n  );\n\n  const contextValue: CollapsibleRootContext = React.useMemo(\n    () => ({\n      ...collapsible,\n      onOpenChange,\n      state,\n    }),\n    [collapsible, onOpenChange, state],\n  );\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: forwardedRef,\n    props: elementProps,\n    stateAttributesMapping: collapsibleStateAttributesMapping,\n  });\n\n  return (\n    <CollapsibleRootContext.Provider value={contextValue}>\n      {element}\n    </CollapsibleRootContext.Provider>\n  );\n});\n\nexport interface CollapsibleRootState extends Pick<\n  UseCollapsibleRootReturnValue,\n  'open' | 'disabled'\n> {}\n\nexport interface CollapsibleRootProps extends BaseUIComponentProps<'div', CollapsibleRootState> {\n  /**\n   * Whether the collapsible panel is currently open.\n   *\n   * To render an uncontrolled collapsible, use the `defaultOpen` prop instead.\n   */\n  open?: boolean | undefined;\n  /**\n   * Whether the collapsible panel is initially open.\n   *\n   * To render a controlled collapsible, use the `open` prop instead.\n   * @default false\n   */\n  defaultOpen?: boolean | undefined;\n  /**\n   * Event handler called when the panel is opened or closed.\n   */\n  onOpenChange?:\n    | ((open: boolean, eventDetails: CollapsibleRootChangeEventDetails) => void)\n    | undefined;\n  /**\n   * Whether the component should ignore user interaction.\n   * @default false\n   */\n  disabled?: boolean | undefined;\n}\n\nexport type CollapsibleRootChangeEventReason = typeof REASONS.triggerPress | typeof REASONS.none;\nexport type CollapsibleRootChangeEventDetails =\n  BaseUIChangeEventDetails<CollapsibleRootChangeEventReason>;\n\nexport namespace CollapsibleRoot {\n  export type State = CollapsibleRootState;\n  export type Props = CollapsibleRootProps;\n  export type ChangeEventReason = CollapsibleRootChangeEventReason;\n  export type ChangeEventDetails = CollapsibleRootChangeEventDetails;\n}\n"
  },
  {
    "path": "packages/react/src/collapsible/root/CollapsibleRootContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport type { UseCollapsibleRootReturnValue } from './useCollapsibleRoot';\nimport type { CollapsibleRoot, CollapsibleRootState } from './CollapsibleRoot';\nimport type { TransitionStatus } from '../../utils/useTransitionStatus';\n\nexport interface CollapsibleRootContext extends UseCollapsibleRootReturnValue {\n  onOpenChange: (open: boolean, eventDetails: CollapsibleRoot.ChangeEventDetails) => void;\n  state: CollapsibleRootState;\n  transitionStatus: TransitionStatus;\n}\n\nexport const CollapsibleRootContext = React.createContext<CollapsibleRootContext | undefined>(\n  undefined,\n);\n\nexport function useCollapsibleRootContext() {\n  const context = React.useContext(CollapsibleRootContext);\n  if (context === undefined) {\n    throw new Error(\n      'Base UI: CollapsibleRootContext is missing. Collapsible parts must be placed within <Collapsible.Root>.',\n    );\n  }\n\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/collapsible/root/stateAttributesMapping.ts",
    "content": "import type { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport { collapsibleOpenStateMapping as baseMapping } from '../../utils/collapsibleOpenStateMapping';\nimport type { CollapsibleRootState } from './CollapsibleRoot';\nimport { transitionStatusMapping } from '../../utils/stateAttributesMapping';\n\nexport const collapsibleStateAttributesMapping: StateAttributesMapping<CollapsibleRootState> = {\n  ...baseMapping,\n  ...transitionStatusMapping,\n};\n"
  },
  {
    "path": "packages/react/src/collapsible/root/useCollapsibleRoot.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { useControlled } from '@base-ui/utils/useControlled';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useBaseUiId } from '../../utils/useBaseUiId';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport { useAnimationsFinished } from '../../utils/useAnimationsFinished';\nimport { useTransitionStatus, TransitionStatus } from '../../utils/useTransitionStatus';\nimport type { CollapsibleRoot } from './CollapsibleRoot';\n\nexport type AnimationType = 'css-transition' | 'css-animation' | 'none' | null;\n\nexport interface Dimensions {\n  height: number | undefined;\n  width: number | undefined;\n}\n\nexport function useCollapsibleRoot(\n  parameters: UseCollapsibleRootParameters,\n): UseCollapsibleRootReturnValue {\n  const { open: openParam, defaultOpen, onOpenChange, disabled } = parameters;\n\n  const isControlled = openParam !== undefined;\n\n  const [open, setOpen] = useControlled({\n    controlled: openParam,\n    default: defaultOpen,\n    name: 'Collapsible',\n    state: 'open',\n  });\n\n  const { mounted, setMounted, transitionStatus } = useTransitionStatus(open, true, true);\n  const [visible, setVisible] = React.useState(open);\n  const [{ height, width }, setDimensions] = React.useState<Dimensions>({\n    height: undefined,\n    width: undefined,\n  });\n\n  const defaultPanelId = useBaseUiId();\n  const [panelIdState, setPanelIdState] = React.useState<string | undefined>();\n  const panelId = panelIdState ?? defaultPanelId;\n\n  const [hiddenUntilFound, setHiddenUntilFound] = React.useState(false);\n  const [keepMounted, setKeepMounted] = React.useState(false);\n\n  const abortControllerRef = React.useRef<AbortController | null>(null);\n  const animationTypeRef = React.useRef<AnimationType>(null);\n  const transitionDimensionRef = React.useRef<'width' | 'height' | null>(null);\n  const panelRef: React.RefObject<HTMLElement | null> = React.useRef(null);\n\n  const runOnceAnimationsFinish = useAnimationsFinished(panelRef, false);\n\n  const handleTrigger = useStableCallback((event: React.MouseEvent | React.KeyboardEvent) => {\n    const nextOpen = !open;\n    const eventDetails = createChangeEventDetails(REASONS.triggerPress, event.nativeEvent);\n\n    onOpenChange(nextOpen, eventDetails);\n\n    if (eventDetails.isCanceled) {\n      return;\n    }\n\n    const panel = panelRef.current;\n\n    if (animationTypeRef.current === 'css-animation' && panel != null) {\n      panel.style.removeProperty('animation-name');\n    }\n\n    if (!hiddenUntilFound && !keepMounted) {\n      if (animationTypeRef.current != null && animationTypeRef.current !== 'css-animation') {\n        if (!mounted && nextOpen) {\n          setMounted(true);\n        }\n      }\n\n      if (animationTypeRef.current === 'css-animation') {\n        if (!visible && nextOpen) {\n          setVisible(true);\n        }\n        if (!mounted && nextOpen) {\n          setMounted(true);\n        }\n      }\n    }\n\n    setOpen(nextOpen);\n\n    if (animationTypeRef.current === 'none' && mounted && !nextOpen) {\n      setMounted(false);\n    }\n  });\n\n  useIsoLayoutEffect(() => {\n    /**\n     * Unmount immediately when closing in controlled mode and keepMounted={false}\n     * and no CSS animations or transitions are applied\n     */\n    if (isControlled && animationTypeRef.current === 'none' && !keepMounted && !open) {\n      setMounted(false);\n    }\n  }, [isControlled, keepMounted, open, openParam, setMounted]);\n\n  return React.useMemo(\n    () => ({\n      abortControllerRef,\n      animationTypeRef,\n      disabled,\n      handleTrigger,\n      height,\n      mounted,\n      open,\n      panelId,\n      panelRef,\n      runOnceAnimationsFinish,\n      setDimensions,\n      setHiddenUntilFound,\n      setKeepMounted,\n      setMounted,\n      setOpen,\n      setPanelIdState,\n      setVisible,\n      transitionDimensionRef,\n      transitionStatus,\n      visible,\n      width,\n    }),\n    [\n      abortControllerRef,\n      animationTypeRef,\n      disabled,\n      handleTrigger,\n      height,\n      mounted,\n      open,\n      panelId,\n      panelRef,\n      runOnceAnimationsFinish,\n      setDimensions,\n      setHiddenUntilFound,\n      setKeepMounted,\n      setMounted,\n      setOpen,\n      setVisible,\n      transitionDimensionRef,\n      transitionStatus,\n      visible,\n      width,\n    ],\n  );\n}\n\nexport interface UseCollapsibleRootParameters {\n  /**\n   * Whether the collapsible panel is currently open.\n   *\n   * To render an uncontrolled collapsible, use the `defaultOpen` prop instead.\n   */\n  open?: boolean | undefined;\n  /**\n   * Whether the collapsible panel is initially open.\n   *\n   * To render a controlled collapsible, use the `open` prop instead.\n   * @default false\n   */\n  defaultOpen?: boolean | undefined;\n  /**\n   * Event handler called when the panel is opened or closed.\n   */\n  onOpenChange: (open: boolean, eventDetails: CollapsibleRoot.ChangeEventDetails) => void;\n  /**\n   * Whether the component should ignore user interaction.\n   * @default false\n   */\n  disabled: boolean;\n}\n\nexport interface UseCollapsibleRootReturnValue {\n  abortControllerRef: React.RefObject<AbortController | null>;\n  animationTypeRef: React.RefObject<AnimationType>;\n  /**\n   * Whether the component should ignore user interaction.\n   */\n  disabled: boolean;\n  handleTrigger: (event: React.MouseEvent | React.KeyboardEvent) => void;\n  /**\n   * The height of the panel.\n   */\n  height: number | undefined;\n  /**\n   * Whether the collapsible panel is currently mounted.\n   */\n  mounted: boolean;\n  /**\n   * Whether the collapsible panel is currently open.\n   */\n  open: boolean;\n  panelId: React.HTMLAttributes<Element>['id'];\n  panelRef: React.RefObject<HTMLElement | null>;\n  runOnceAnimationsFinish: (fnToExecute: () => void, signal?: AbortSignal | null) => void;\n  setDimensions: React.Dispatch<React.SetStateAction<Dimensions>>;\n  setHiddenUntilFound: React.Dispatch<React.SetStateAction<boolean>>;\n  setKeepMounted: React.Dispatch<React.SetStateAction<boolean>>;\n  setMounted: (open: boolean) => void;\n  setOpen: (open: boolean) => void;\n  setPanelIdState: (id: string | undefined) => void;\n  setVisible: React.Dispatch<React.SetStateAction<boolean>>;\n  transitionDimensionRef: React.RefObject<'height' | 'width' | null>;\n  transitionStatus: TransitionStatus;\n  /**\n   * The visible state of the panel used to determine the `[hidden]` attribute\n   * only when CSS keyframe animations are used.\n   */\n  visible: boolean;\n  /**\n   * The width of the panel.\n   */\n  width: number | undefined;\n}\n\nexport interface UseCollapsibleRootState {}\n"
  },
  {
    "path": "packages/react/src/collapsible/trigger/CollapsibleTrigger.test.tsx",
    "content": "import { createRenderer } from '@mui/internal-test-utils';\nimport { Collapsible } from '@base-ui/react/collapsible';\nimport { describeConformance } from '../../../test/describeConformance';\n\ndescribe('<Collapsible.Trigger />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Collapsible.Trigger />, () => ({\n    refInstanceof: window.HTMLButtonElement,\n    testComponentPropWith: 'button',\n    button: true,\n    render: (node) => {\n      const { container, ...other } = render(<Collapsible.Root>{node}</Collapsible.Root>);\n\n      return { container, ...other };\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/collapsible/trigger/CollapsibleTrigger.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { triggerOpenStateMapping } from '../../utils/collapsibleOpenStateMapping';\nimport type { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport { transitionStatusMapping } from '../../utils/stateAttributesMapping';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { BaseUIComponentProps, NativeButtonProps } from '../../utils/types';\nimport { useButton } from '../../use-button';\nimport { useCollapsibleRootContext } from '../root/CollapsibleRootContext';\nimport { type CollapsibleRootState } from '../root/CollapsibleRoot';\n\nconst stateAttributesMapping: StateAttributesMapping<CollapsibleRootState> = {\n  ...triggerOpenStateMapping,\n  ...transitionStatusMapping,\n};\n\n/**\n * A button that opens and closes the collapsible panel.\n * Renders a `<button>` element.\n *\n * Documentation: [Base UI Collapsible](https://base-ui.com/react/components/collapsible)\n */\nexport const CollapsibleTrigger = React.forwardRef(function CollapsibleTrigger(\n  componentProps: CollapsibleTrigger.Props,\n  forwardedRef: React.ForwardedRef<HTMLButtonElement>,\n) {\n  const {\n    panelId,\n    open,\n    handleTrigger,\n    state,\n    disabled: contextDisabled,\n  } = useCollapsibleRootContext();\n\n  const {\n    className,\n    disabled = contextDisabled,\n    id,\n    render,\n    nativeButton = true,\n    ...elementProps\n  } = componentProps;\n\n  const { getButtonProps, buttonRef } = useButton({\n    disabled,\n    focusableWhenDisabled: true,\n    native: nativeButton,\n  });\n\n  const props = React.useMemo(\n    () => ({\n      'aria-controls': open ? panelId : undefined,\n      'aria-expanded': open,\n      onClick: handleTrigger,\n    }),\n    [panelId, open, handleTrigger],\n  );\n\n  const element = useRenderElement('button', componentProps, {\n    state,\n    ref: [forwardedRef, buttonRef],\n    props: [props, elementProps, getButtonProps],\n    stateAttributesMapping,\n  });\n\n  return element;\n});\n\nexport interface CollapsibleTriggerState extends CollapsibleRootState {}\n\nexport interface CollapsibleTriggerProps\n  extends NativeButtonProps, BaseUIComponentProps<'button', CollapsibleTriggerState> {}\n\nexport namespace CollapsibleTrigger {\n  export type State = CollapsibleTriggerState;\n  export type Props = CollapsibleTriggerProps;\n}\n"
  },
  {
    "path": "packages/react/src/collapsible/trigger/CollapsibleTriggerDataAttributes.ts",
    "content": "export enum CollapsibleTriggerDataAttributes {\n  /**\n   * Present when the collapsible panel is open.\n   */\n  panelOpen = 'data-panel-open',\n}\n"
  },
  {
    "path": "packages/react/src/combobox/arrow/ComboboxArrow.test.tsx",
    "content": "import { Combobox } from '@base-ui/react/combobox';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Combobox.Arrow />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Combobox.Arrow />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <Combobox.Root defaultOpen>\n          <Combobox.Portal>\n            <Combobox.Positioner>{node}</Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/combobox/arrow/ComboboxArrow.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStore } from '@base-ui/utils/store';\nimport { useComboboxPositionerContext } from '../positioner/ComboboxPositionerContext';\nimport { useComboboxRootContext } from '../root/ComboboxRootContext';\nimport { selectors } from '../store';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport type { Side, Align } from '../../utils/useAnchorPositioning';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { popupStateMapping } from '../../utils/popupStateMapping';\n\n/**\n * Displays an element positioned against the anchor.\n * Renders a `<div>` element.\n */\nexport const ComboboxArrow = React.forwardRef(function ComboboxArrow(\n  componentProps: ComboboxArrow.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { className, render, ...elementProps } = componentProps;\n\n  const store = useComboboxRootContext();\n  const { arrowRef, side, align, arrowUncentered, arrowStyles } = useComboboxPositionerContext();\n\n  const open = useStore(store, selectors.open);\n\n  const state: ComboboxArrowState = {\n    open,\n    side,\n    align,\n    uncentered: arrowUncentered,\n  };\n\n  return useRenderElement('div', componentProps, {\n    ref: [arrowRef, forwardedRef],\n    stateAttributesMapping: popupStateMapping,\n    state,\n    props: {\n      style: arrowStyles,\n      'aria-hidden': true,\n      ...elementProps,\n    },\n  });\n});\n\nexport interface ComboboxArrowState {\n  /**\n   * Whether the popup is currently open.\n   */\n  open: boolean;\n  /**\n   * The side of the anchor the component is placed on.\n   */\n  side: Side;\n  /**\n   * The alignment of the component relative to the anchor.\n   */\n  align: Align;\n  /**\n   * Whether the arrow cannot be centered on the anchor.\n   */\n  uncentered: boolean;\n}\n\nexport interface ComboboxArrowProps extends BaseUIComponentProps<'div', ComboboxArrowState> {}\n\nexport namespace ComboboxArrow {\n  export type State = ComboboxArrowState;\n  export type Props = ComboboxArrowProps;\n}\n"
  },
  {
    "path": "packages/react/src/combobox/arrow/ComboboxArrowDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum ComboboxArrowDataAttributes {\n  /**\n   * Present when the popup is open.\n   */\n  open = CommonPopupDataAttributes.open,\n  /**\n   * Present when the popup is closed.\n   */\n  closed = CommonPopupDataAttributes.closed,\n  /**\n   * Indicates which side the popup is positioned relative to the trigger.\n   * @type {'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'}\n   */\n  side = CommonPopupDataAttributes.side,\n  /**\n   * Indicates how the popup is aligned relative to specified side.\n   * @type {'start' | 'center' | 'end'}\n   */\n  align = CommonPopupDataAttributes.align,\n  /**\n   * Present when the arrow is uncentered.\n   */\n  uncentered = 'data-uncentered',\n}\n"
  },
  {
    "path": "packages/react/src/combobox/backdrop/ComboboxBackdrop.test.tsx",
    "content": "import { Combobox } from '@base-ui/react/combobox';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Combobox.Backdrop />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Combobox.Backdrop />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <Combobox.Root defaultOpen>\n          <Combobox.Portal>{node}</Combobox.Portal>\n        </Combobox.Root>,\n      );\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/combobox/backdrop/ComboboxBackdrop.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStore } from '@base-ui/utils/store';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useComboboxRootContext } from '../root/ComboboxRootContext';\nimport { popupStateMapping } from '../../utils/popupStateMapping';\nimport type { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport type { TransitionStatus } from '../../utils/useTransitionStatus';\nimport { transitionStatusMapping } from '../../utils/stateAttributesMapping';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { selectors } from '../store';\n\nconst stateAttributesMapping: StateAttributesMapping<ComboboxBackdropState> = {\n  ...popupStateMapping,\n  ...transitionStatusMapping,\n};\n\n/**\n * An overlay displayed beneath the popup.\n * Renders a `<div>` element.\n */\nexport const ComboboxBackdrop = React.forwardRef(function ComboboxBackdrop(\n  componentProps: ComboboxBackdrop.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { className, render, ...elementProps } = componentProps;\n\n  const store = useComboboxRootContext();\n\n  const open = useStore(store, selectors.open);\n  const mounted = useStore(store, selectors.mounted);\n  const transitionStatus = useStore(store, selectors.transitionStatus);\n\n  const state: ComboboxBackdropState = {\n    open,\n    transitionStatus,\n  };\n\n  return useRenderElement('div', componentProps, {\n    state,\n    ref: forwardedRef,\n    stateAttributesMapping,\n    props: [\n      {\n        role: 'presentation',\n        hidden: !mounted,\n        style: {\n          userSelect: 'none',\n          WebkitUserSelect: 'none',\n        },\n      },\n      elementProps,\n    ],\n  });\n});\n\nexport interface ComboboxBackdropProps extends BaseUIComponentProps<'div', ComboboxBackdropState> {}\n\nexport interface ComboboxBackdropState {\n  /**\n   * Whether the popup is currently open.\n   */\n  open: boolean;\n  /**\n   * The transition status of the component.\n   */\n  transitionStatus: TransitionStatus;\n}\n\nexport namespace ComboboxBackdrop {\n  export type Props = ComboboxBackdropProps;\n  export type State = ComboboxBackdropState;\n}\n"
  },
  {
    "path": "packages/react/src/combobox/backdrop/ComboboxBackdropDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum ComboboxBackdropDataAttributes {\n  /**\n   * Present when the popup is open.\n   */\n  open = CommonPopupDataAttributes.open,\n  /**\n   * Present when the popup is closed.\n   */\n  closed = CommonPopupDataAttributes.closed,\n  /**\n   * Present when the popup is animating in.\n   */\n  startingStyle = CommonPopupDataAttributes.startingStyle,\n  /**\n   * Present when the popup is animating out.\n   */\n  endingStyle = CommonPopupDataAttributes.endingStyle,\n}\n"
  },
  {
    "path": "packages/react/src/combobox/chip/ComboboxChip.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport { Combobox } from '@base-ui/react/combobox';\nimport { createRenderer, describeConformance } from '#test-utils';\nimport { act, screen, waitFor } from '@mui/internal-test-utils';\n\ndescribe('<Combobox.Chip />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Combobox.Chip />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <Combobox.Root multiple>\n          <Combobox.Chips>{node}</Combobox.Chips>\n        </Combobox.Root>,\n      );\n    },\n  }));\n\n  describe('prop: disabled', () => {\n    it('should render aria-disabled attribute when disabled', async () => {\n      await render(\n        <Combobox.Root multiple disabled>\n          <Combobox.Chips>\n            <Combobox.Chip data-testid=\"chip\">apple</Combobox.Chip>\n          </Combobox.Chips>\n        </Combobox.Root>,\n      );\n\n      const chip = screen.getByTestId('chip');\n      expect(chip).toHaveAttribute('aria-disabled', 'true');\n    });\n\n    it('should prevent keyboard navigation when disabled', async () => {\n      const { user } = await render(\n        <Combobox.Root multiple disabled defaultValue={['apple', 'banana']}>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Chips>\n            <Combobox.Chip data-testid=\"chip-apple\">apple</Combobox.Chip>\n            <Combobox.Chip data-testid=\"chip-banana\">banana</Combobox.Chip>\n          </Combobox.Chips>\n        </Combobox.Root>,\n      );\n\n      const chipApple = screen.getByTestId('chip-apple');\n\n      // Focus the chip manually (simulating navigation)\n      await act(async () => {\n        chipApple.focus();\n      });\n\n      // Try to navigate with arrow keys\n      await user.keyboard('{ArrowRight}');\n\n      // The focus should remain on the same chip when disabled\n      expect(chipApple).toHaveFocus();\n    });\n\n    it('should prevent deletion when disabled', async () => {\n      const handleValueChange = vi.fn();\n      const { user } = await render(\n        <Combobox.Root\n          multiple\n          disabled\n          defaultValue={['apple', 'banana']}\n          onValueChange={handleValueChange}\n        >\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Chips>\n            <Combobox.Chip data-testid=\"chip-apple\">apple</Combobox.Chip>\n            <Combobox.Chip data-testid=\"chip-banana\">banana</Combobox.Chip>\n          </Combobox.Chips>\n        </Combobox.Root>,\n      );\n\n      const chipApple = screen.getByTestId('chip-apple');\n\n      // Focus the chip manually\n      await act(async () => {\n        chipApple.focus();\n      });\n\n      // Try to delete with backspace\n      await user.keyboard('{Backspace}');\n\n      expect(handleValueChange.mock.calls.length).toBe(0);\n      expect(screen.getByTestId('chip-apple')).not.toBe(null);\n    });\n\n    it('should prevent mouse interactions when disabled', async () => {\n      const { user } = await render(\n        <Combobox.Root multiple disabled>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Chips>\n            <Combobox.Chip data-testid=\"chip\">apple</Combobox.Chip>\n          </Combobox.Chips>\n        </Combobox.Root>,\n      );\n\n      const chip = screen.getByTestId('chip');\n      const input = screen.getByTestId('input');\n\n      // Chip should not focus input on mouse down when disabled\n      await user.click(chip);\n      expect(input).not.toHaveFocus();\n    });\n\n    it('should prevent focus when disabled', async () => {\n      const { user } = await render(\n        <Combobox.Root multiple disabled>\n          <Combobox.Chips>\n            <Combobox.Chip data-testid=\"chip\">apple</Combobox.Chip>\n          </Combobox.Chips>\n        </Combobox.Root>,\n      );\n\n      const chip = screen.getByTestId('chip');\n      await user.click(chip);\n\n      expect(chip).not.toHaveFocus();\n    });\n  });\n\n  describe('prop: readOnly', () => {\n    it('should render aria-readonly attribute when readOnly', async () => {\n      await render(\n        <Combobox.Root multiple readOnly>\n          <Combobox.Chips>\n            <Combobox.Chip data-testid=\"chip\">apple</Combobox.Chip>\n          </Combobox.Chips>\n        </Combobox.Root>,\n      );\n\n      const chip = screen.getByTestId('chip');\n      expect(chip).toHaveAttribute('aria-readonly', 'true');\n    });\n\n    it('should prevent deletion when readOnly', async () => {\n      const handleValueChange = vi.fn();\n      const { user } = await render(\n        <Combobox.Root\n          multiple\n          readOnly\n          defaultValue={['apple', 'banana']}\n          onValueChange={handleValueChange}\n        >\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Chips>\n            <Combobox.Chip data-testid=\"chip-apple\">apple</Combobox.Chip>\n            <Combobox.Chip data-testid=\"chip-banana\">banana</Combobox.Chip>\n          </Combobox.Chips>\n        </Combobox.Root>,\n      );\n\n      const chipApple = screen.getByTestId('chip-apple');\n\n      // Focus the chip manually\n      await act(async () => {\n        chipApple.focus();\n      });\n\n      // Try to delete with backspace\n      await user.keyboard('{Backspace}');\n\n      expect(handleValueChange.mock.calls.length).toBe(0);\n      expect(screen.getByTestId('chip-apple')).not.toBe(null);\n    });\n\n    it('should prevent navigation when readOnly and also prevent deletion', async () => {\n      const { user } = await render(\n        <Combobox.Root multiple readOnly defaultValue={['apple', 'banana']}>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Chips>\n            <Combobox.Chip data-testid=\"chip-apple\">apple</Combobox.Chip>\n            <Combobox.Chip data-testid=\"chip-banana\">banana</Combobox.Chip>\n          </Combobox.Chips>\n        </Combobox.Root>,\n      );\n\n      const chipApple = screen.getByTestId('chip-apple');\n\n      // Focus the first chip\n      await act(async () => {\n        chipApple.focus();\n      });\n\n      // Navigation should be blocked\n      await user.keyboard('{ArrowRight}');\n      expect(chipApple).toHaveFocus();\n\n      // Deletion should be blocked\n      await user.keyboard('{Delete}');\n      expect(screen.getByTestId('chip-banana')).not.toBe(null);\n    });\n\n    it('should focus when readOnly', async () => {\n      const { user } = await render(\n        <Combobox.Root multiple readOnly>\n          <Combobox.Chips>\n            <Combobox.Chip data-testid=\"chip\">apple</Combobox.Chip>\n          </Combobox.Chips>\n        </Combobox.Root>,\n      );\n\n      const chip = screen.getByTestId('chip');\n      await user.click(chip);\n\n      expect(chip).toHaveFocus();\n    });\n  });\n\n  describe('interaction behavior', () => {\n    it('does not dismiss the popup when clicking a chip (outsidePress is blocked)', async () => {\n      const { user } = await render(\n        <Combobox.Root multiple defaultOpen defaultValue={['apple', 'banana']}>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                  <Combobox.Item value=\"b\">b</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n          <Combobox.Chips>\n            <Combobox.Chip data-testid=\"chip-apple\">apple</Combobox.Chip>\n            <Combobox.Chip data-testid=\"chip-banana\">banana</Combobox.Chip>\n          </Combobox.Chips>\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByRole('listbox')).not.toBe(null);\n\n      await user.click(screen.getByTestId('chip-apple'));\n\n      expect(screen.getByRole('listbox')).not.toBe(null);\n    });\n\n    it('closes the popup when a chip receives focus', async () => {\n      const { user } = await render(\n        <Combobox.Root multiple defaultOpen defaultValue={['apple', 'banana']}>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                  <Combobox.Item value=\"b\">b</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n          <Combobox.Chips>\n            <Combobox.Chip data-testid=\"chip-apple\">apple</Combobox.Chip>\n            <Combobox.Chip data-testid=\"chip-banana\">banana</Combobox.Chip>\n          </Combobox.Chips>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      expect(screen.getByRole('listbox')).not.toBe(null);\n\n      await user.click(input);\n      const chipApple = screen.getByTestId('chip-apple');\n      await act(async () => {\n        chipApple.focus();\n      });\n\n      await waitFor(() => expect(screen.queryByRole('listbox')).toBe(null));\n    });\n\n    it('should handle keyboard navigation when enabled', async () => {\n      const { user } = await render(\n        <Combobox.Root multiple defaultValue={['apple', 'banana', 'cherry']}>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Chips>\n            <Combobox.Chip data-testid=\"chip-apple\">apple</Combobox.Chip>\n            <Combobox.Chip data-testid=\"chip-banana\">banana</Combobox.Chip>\n            <Combobox.Chip data-testid=\"chip-cherry\">cherry</Combobox.Chip>\n          </Combobox.Chips>\n        </Combobox.Root>,\n      );\n\n      const chipApple = screen.getByTestId('chip-apple');\n      const chipBanana = screen.getByTestId('chip-banana');\n      const chipCherry = screen.getByTestId('chip-cherry');\n      const input = screen.getByTestId('input');\n\n      // Focus the first chip\n      await act(async () => {\n        chipApple.focus();\n      });\n\n      // Navigate right\n      await user.keyboard('{ArrowRight}');\n      expect(chipBanana).toHaveFocus();\n\n      // Navigate right again\n      await user.keyboard('{ArrowRight}');\n      expect(chipCherry).toHaveFocus();\n\n      // Navigate right at the end should focus input\n      await user.keyboard('{ArrowRight}');\n      expect(input).toHaveFocus();\n\n      // Navigate left from first chip should also focus input\n      await act(async () => {\n        chipApple.focus();\n      });\n      await user.keyboard('{ArrowLeft}');\n      expect(input).toHaveFocus();\n    });\n\n    it('should handle deletion when enabled', async () => {\n      const handleValueChange = vi.fn();\n      const { user } = await render(\n        <Combobox.Root\n          multiple\n          defaultValue={['apple', 'banana']}\n          onValueChange={handleValueChange}\n        >\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Chips>\n            <Combobox.Chip data-testid=\"chip-apple\">apple</Combobox.Chip>\n            <Combobox.Chip data-testid=\"chip-banana\">banana</Combobox.Chip>\n          </Combobox.Chips>\n        </Combobox.Root>,\n      );\n\n      const chipApple = screen.getByTestId('chip-apple');\n\n      // Focus the chip and delete it\n      await act(async () => {\n        chipApple.focus();\n      });\n      await user.keyboard('{Backspace}');\n\n      expect(handleValueChange.mock.calls.length).toBe(1);\n      expect(handleValueChange.mock.calls[0][0]).toEqual(['banana']);\n    });\n\n    it('should focus input on mouse down when enabled', async () => {\n      const { user } = await render(\n        <Combobox.Root multiple>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Chips>\n            <Combobox.Chip data-testid=\"chip\">apple</Combobox.Chip>\n          </Combobox.Chips>\n        </Combobox.Root>,\n      );\n\n      const chip = screen.getByTestId('chip');\n      const input = screen.getByTestId('input');\n\n      await user.click(chip);\n      expect(input).toHaveFocus();\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/combobox/chip/ComboboxChip.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport { useStore } from '@base-ui/utils/store';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { useComboboxChipsContext } from '../chips/ComboboxChipsContext';\nimport { useComboboxRootContext } from '../root/ComboboxRootContext';\nimport { useCompositeListItem } from '../../composite/list/useCompositeListItem';\nimport { ComboboxChipContext } from './ComboboxChipContext';\nimport { stopEvent } from '../../floating-ui-react/utils';\nimport { selectors } from '../store';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\n\n/**\n * An individual chip that represents a value in a multiselectable input.\n * Renders a `<div>` element.\n */\nexport const ComboboxChip = React.forwardRef(function ComboboxChip(\n  componentProps: ComboboxChip.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, ...elementProps } = componentProps;\n\n  const store = useComboboxRootContext();\n  const { setHighlightedChipIndex, chipsRef } = useComboboxChipsContext()!;\n\n  const disabled = useStore(store, selectors.disabled);\n  const readOnly = useStore(store, selectors.readOnly);\n  const selectedValue = useStore(store, selectors.selectedValue);\n\n  const { ref, index } = useCompositeListItem();\n\n  function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {\n    let nextIndex: number | undefined = index;\n\n    if (event.key === 'ArrowLeft') {\n      event.preventDefault();\n      if (index > 0) {\n        nextIndex = index - 1;\n      } else {\n        nextIndex = undefined;\n      }\n    } else if (event.key === 'ArrowRight') {\n      event.preventDefault();\n      if (index < selectedValue.length - 1) {\n        nextIndex = index + 1;\n      } else {\n        nextIndex = undefined;\n      }\n    } else if (event.key === 'Backspace' || event.key === 'Delete') {\n      const computedNextIndex =\n        index >= selectedValue.length - 1 ? selectedValue.length - 2 : index;\n      nextIndex = computedNextIndex >= 0 ? computedNextIndex : undefined;\n\n      stopEvent(event);\n\n      store.state.setIndices({ activeIndex: null, selectedIndex: null, type: 'keyboard' });\n      store.state.setSelectedValue(\n        selectedValue.filter((_: any, i: number) => i !== index),\n        createChangeEventDetails(REASONS.none, event.nativeEvent),\n      );\n    } else if (event.key === 'Enter' || event.key === ' ') {\n      stopEvent(event);\n      nextIndex = undefined;\n    } else if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {\n      stopEvent(event);\n      store.state.setOpen(\n        true,\n        createChangeEventDetails(REASONS.listNavigation, event.nativeEvent),\n      );\n      nextIndex = undefined;\n    } else if (\n      // Check for printable characters (letters, numbers, symbols)\n      event.key.length === 1 &&\n      !event.ctrlKey &&\n      !event.metaKey &&\n      !event.altKey\n    ) {\n      nextIndex = undefined;\n    }\n\n    return nextIndex;\n  }\n\n  const state: ComboboxChipState = {\n    disabled,\n  };\n\n  const element = useRenderElement('div', componentProps, {\n    ref: [forwardedRef, ref],\n    state,\n    props: [\n      {\n        tabIndex: -1,\n        'aria-disabled': disabled || undefined,\n        'aria-readonly': readOnly || undefined,\n        onKeyDown(event) {\n          if (disabled || readOnly) {\n            return;\n          }\n\n          const nextIndex = handleKeyDown(event);\n\n          ReactDOM.flushSync(() => {\n            setHighlightedChipIndex(nextIndex);\n          });\n\n          if (nextIndex === undefined) {\n            store.state.inputRef.current?.focus();\n          } else {\n            chipsRef.current[nextIndex]?.focus();\n          }\n        },\n      },\n      elementProps,\n    ],\n  });\n\n  const contextValue: ComboboxChipContext = React.useMemo(\n    () => ({\n      index,\n    }),\n    [index],\n  );\n\n  return (\n    <ComboboxChipContext.Provider value={contextValue}>{element}</ComboboxChipContext.Provider>\n  );\n});\n\nexport interface ComboboxChipState {\n  /**\n   * Whether the component should ignore user interaction.\n   */\n  disabled: boolean;\n}\n\nexport interface ComboboxChipProps extends BaseUIComponentProps<'div', ComboboxChipState> {}\n\nexport namespace ComboboxChip {\n  export type State = ComboboxChipState;\n  export type Props = ComboboxChipProps;\n}\n"
  },
  {
    "path": "packages/react/src/combobox/chip/ComboboxChipContext.ts",
    "content": "'use client';\nimport * as React from 'react';\n\nexport interface ComboboxChipContext {\n  index: number;\n}\n\nexport const ComboboxChipContext = React.createContext<ComboboxChipContext | undefined>(undefined);\n\nexport function useComboboxChipContext() {\n  const context = React.useContext(ComboboxChipContext);\n  if (!context) {\n    throw new Error('useComboboxChipContext must be used within a ComboboxChip');\n  }\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/combobox/chip-remove/ComboboxChipRemove.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport { Combobox } from '@base-ui/react/combobox';\nimport { createRenderer, describeConformance } from '#test-utils';\nimport { act, screen } from '@mui/internal-test-utils';\n\ndescribe('<Combobox.ChipRemove />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Combobox.ChipRemove />, () => ({\n    refInstanceof: window.HTMLButtonElement,\n    button: true,\n    render(node) {\n      return render(\n        <Combobox.Root multiple>\n          <Combobox.Chips>\n            <Combobox.Chip>{node}</Combobox.Chip>\n          </Combobox.Chips>\n        </Combobox.Root>,\n      );\n    },\n  }));\n\n  describe('prop: disabled', () => {\n    it('should render disabled attribute when disabled', async () => {\n      await render(\n        <Combobox.Root multiple disabled>\n          <Combobox.Chips>\n            <Combobox.Chip>\n              apple\n              <Combobox.ChipRemove data-testid=\"remove\">×</Combobox.ChipRemove>\n            </Combobox.Chip>\n          </Combobox.Chips>\n        </Combobox.Root>,\n      );\n\n      const remove = screen.getByTestId('remove');\n      expect(remove).toHaveAttribute('aria-disabled', 'true');\n    });\n\n    it('should not remove chip when disabled', async () => {\n      const handleValueChange = vi.fn();\n      const { user } = await render(\n        <Combobox.Root\n          multiple\n          disabled\n          defaultValue={['apple', 'banana']}\n          onValueChange={handleValueChange}\n        >\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Chips>\n            <Combobox.Chip data-testid=\"chip-apple\">\n              apple\n              <Combobox.ChipRemove data-testid=\"remove-apple\" />\n            </Combobox.Chip>\n            <Combobox.Chip data-testid=\"chip-banana\">\n              banana\n              <Combobox.ChipRemove data-testid=\"remove-banana\" />\n            </Combobox.Chip>\n          </Combobox.Chips>\n        </Combobox.Root>,\n      );\n\n      const removeApple = screen.getByTestId('remove-apple');\n\n      await user.click(removeApple);\n\n      expect(handleValueChange.mock.calls.length).toBe(0);\n      expect(screen.getByTestId('chip-apple')).not.toBe(null);\n    });\n  });\n\n  describe('prop: readOnly', () => {\n    it('should not remove chip when readOnly', async () => {\n      const handleValueChange = vi.fn();\n      const { user } = await render(\n        <Combobox.Root\n          multiple\n          readOnly\n          defaultValue={['apple', 'banana']}\n          onValueChange={handleValueChange}\n        >\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Chips>\n            <Combobox.Chip data-testid=\"chip-apple\">\n              apple\n              <Combobox.ChipRemove data-testid=\"remove-apple\" />\n            </Combobox.Chip>\n            <Combobox.Chip data-testid=\"chip-banana\">\n              banana\n              <Combobox.ChipRemove data-testid=\"remove-banana\" />\n            </Combobox.Chip>\n          </Combobox.Chips>\n        </Combobox.Root>,\n      );\n\n      const removeApple = screen.getByTestId('remove-apple');\n\n      await user.click(removeApple);\n\n      expect(handleValueChange.mock.calls.length).toBe(0);\n      expect(screen.getByTestId('chip-apple')).not.toBe(null);\n    });\n\n    it('should be focusable but not functional when readOnly', async () => {\n      const { user } = await render(\n        <Combobox.Root multiple readOnly>\n          <Combobox.Chips>\n            <Combobox.Chip>\n              apple\n              <Combobox.ChipRemove data-testid=\"remove\" />\n            </Combobox.Chip>\n          </Combobox.Chips>\n        </Combobox.Root>,\n      );\n\n      const remove = screen.getByTestId('remove');\n\n      // Should be focusable\n      await act(async () => {\n        remove.focus();\n      });\n      expect(remove).toHaveFocus();\n\n      // But should not trigger action\n      await user.keyboard('{Enter}');\n      expect(screen.getByTestId('remove')).not.toBe(null);\n    });\n  });\n\n  describe('interaction behavior', () => {\n    it('should remove chip on click when enabled', async () => {\n      const handleValueChange = vi.fn();\n      const { user } = await render(\n        <Combobox.Root\n          multiple\n          defaultValue={['apple', 'banana']}\n          onValueChange={handleValueChange}\n        >\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Chips>\n            <Combobox.Chip data-testid=\"chip-apple\">\n              apple\n              <Combobox.ChipRemove data-testid=\"remove-apple\" />\n            </Combobox.Chip>\n            <Combobox.Chip data-testid=\"chip-banana\">\n              banana\n              <Combobox.ChipRemove data-testid=\"remove-banana\" />\n            </Combobox.Chip>\n          </Combobox.Chips>\n        </Combobox.Root>,\n      );\n\n      const removeApple = screen.getByTestId('remove-apple');\n\n      await user.click(removeApple);\n\n      expect(handleValueChange.mock.calls.length).toBe(1);\n      expect(handleValueChange.mock.calls[0][0]).toEqual(['banana']);\n    });\n\n    it('should focus input after removing chip', async () => {\n      const { user } = await render(\n        <Combobox.Root multiple defaultValue={['apple']}>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Chips>\n            <Combobox.Chip>\n              apple\n              <Combobox.ChipRemove data-testid=\"remove\" />\n            </Combobox.Chip>\n          </Combobox.Chips>\n        </Combobox.Root>,\n      );\n\n      const remove = screen.getByTestId('remove');\n      const input = screen.getByTestId('input');\n\n      await user.click(remove);\n\n      expect(input).toHaveFocus();\n    });\n\n    it('should keep the popup open while removing a chip', async () => {\n      const { user } = await render(\n        <Combobox.Root items={['apple', 'banana']} multiple defaultValue={['apple']}>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Trigger>Open</Combobox.Trigger>\n          <Combobox.Chips>\n            <Combobox.Chip>\n              apple\n              <Combobox.ChipRemove data-testid=\"remove\" />\n            </Combobox.Chip>\n          </Combobox.Chips>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"apple\">apple</Combobox.Item>\n                  <Combobox.Item value=\"banana\">banana</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      await user.click(screen.getByRole('button', { name: 'Open' }));\n      await screen.findByRole('listbox');\n\n      await user.click(screen.getByTestId('remove'));\n\n      await screen.findByRole('listbox');\n      expect(screen.getByTestId('input')).toHaveFocus();\n    });\n\n    it('should prevent event propagation', async () => {\n      const handleChipClick = vi.fn();\n      const handleRemoveClick = vi.fn();\n      const { user } = await render(\n        <Combobox.Root multiple defaultValue={['apple']}>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Chips>\n            <Combobox.Chip onClick={handleChipClick}>\n              apple\n              <Combobox.ChipRemove data-testid=\"remove\" onClick={handleRemoveClick} />\n            </Combobox.Chip>\n          </Combobox.Chips>\n        </Combobox.Root>,\n      );\n\n      const remove = screen.getByTestId('remove');\n\n      await user.click(remove);\n\n      // Remove click should happen but not propagate to chip\n      expect(handleRemoveClick.mock.calls.length).toBe(1);\n      expect(handleChipClick.mock.calls.length).toBe(0);\n    });\n\n    it('should handle keyboard activation', async () => {\n      const handleValueChange = vi.fn();\n      const { user } = await render(\n        <Combobox.Root\n          multiple\n          defaultValue={['apple', 'banana']}\n          onValueChange={handleValueChange}\n        >\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Chips>\n            <Combobox.Chip>\n              apple\n              <Combobox.ChipRemove data-testid=\"remove\">×</Combobox.ChipRemove>\n            </Combobox.Chip>\n            <Combobox.Chip>banana</Combobox.Chip>\n          </Combobox.Chips>\n        </Combobox.Root>,\n      );\n\n      const remove = screen.getByTestId('remove');\n\n      await act(async () => {\n        remove.focus();\n      });\n      await user.keyboard('{Enter}');\n\n      expect(handleValueChange.mock.calls.length).toBe(1);\n      expect(handleValueChange.mock.calls[0][0]).toEqual(['banana']);\n    });\n\n    it('should have proper tab index', async () => {\n      await render(\n        <Combobox.Root multiple>\n          <Combobox.Chips>\n            <Combobox.Chip>\n              apple\n              <Combobox.ChipRemove data-testid=\"remove\">×</Combobox.ChipRemove>\n            </Combobox.Chip>\n          </Combobox.Chips>\n        </Combobox.Root>,\n      );\n\n      const remove = screen.getByTestId('remove');\n      expect(remove).toHaveAttribute('tabindex', '-1');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/combobox/chip-remove/ComboboxChipRemove.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStore } from '@base-ui/utils/store';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { BaseUIComponentProps, NativeButtonProps } from '../../utils/types';\nimport { useComboboxRootContext } from '../root/ComboboxRootContext';\nimport { useComboboxChipContext } from '../chip/ComboboxChipContext';\nimport { useButton } from '../../use-button';\nimport { stopEvent } from '../../floating-ui-react/utils';\nimport { selectors } from '../store';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport { findItemIndex } from '../../utils/itemEquality';\n\n/**\n * A button to remove a chip.\n * Renders a `<button>` element.\n */\nexport const ComboboxChipRemove = React.forwardRef(function ComboboxChipRemove(\n  componentProps: ComboboxChipRemove.Props,\n  forwardedRef: React.ForwardedRef<HTMLButtonElement>,\n) {\n  const {\n    render,\n    className,\n    disabled: disabledProp = false,\n    nativeButton = true,\n    ...elementProps\n  } = componentProps;\n\n  const store = useComboboxRootContext();\n  const { index } = useComboboxChipContext();\n\n  const comboboxDisabled = useStore(store, selectors.disabled);\n  const readOnly = useStore(store, selectors.readOnly);\n  const selectedValue = useStore(store, selectors.selectedValue);\n  const isItemEqualToValue = useStore(store, selectors.isItemEqualToValue);\n\n  const disabled = comboboxDisabled || disabledProp;\n\n  const { buttonRef, getButtonProps } = useButton({\n    native: nativeButton,\n    disabled: disabled || readOnly,\n    focusableWhenDisabled: true,\n  });\n\n  const state: ComboboxChipRemoveState = {\n    disabled,\n  };\n\n  function clearActiveIndexForRemovedItem(removedItem: any) {\n    const activeIndex = store.state.activeIndex;\n\n    if (activeIndex == null) {\n      return;\n    }\n\n    // Try current visible list first; if not found, it's filtered out.\n    // No need to clear highlight in that case since it can't equal activeIndex.\n    const removedIndex = findItemIndex(\n      store.state.valuesRef.current,\n      removedItem,\n      isItemEqualToValue,\n    );\n    if (removedIndex !== -1 && activeIndex === removedIndex) {\n      store.state.setIndices({\n        activeIndex: null,\n        type: store.state.keyboardActiveRef.current ? 'keyboard' : 'pointer',\n      });\n    }\n  }\n\n  function removeChip(\n    event: React.MouseEvent<HTMLButtonElement> | React.KeyboardEvent<HTMLButtonElement>,\n  ) {\n    const eventDetails = createChangeEventDetails(REASONS.chipRemovePress, event.nativeEvent);\n    const removedItem = selectedValue[index];\n\n    clearActiveIndexForRemovedItem(removedItem);\n\n    store.state.setSelectedValue(\n      selectedValue.filter((_: any, i: number) => i !== index),\n      eventDetails,\n    );\n\n    store.state.inputRef.current?.focus();\n    return eventDetails;\n  }\n\n  const element = useRenderElement('button', componentProps, {\n    ref: [forwardedRef, buttonRef],\n    state,\n    props: [\n      {\n        tabIndex: -1,\n        onMouseDown(event) {\n          event.preventDefault();\n        },\n        onClick(event) {\n          if (disabled || readOnly) {\n            return;\n          }\n\n          const eventDetails = removeChip(event);\n          if (!eventDetails.isPropagationAllowed) {\n            event.stopPropagation();\n          }\n        },\n        onKeyDown(event) {\n          if (disabled || readOnly) {\n            return;\n          }\n\n          if (event.key === 'Enter' || event.key === ' ') {\n            const eventDetails = removeChip(event);\n            if (!eventDetails.isPropagationAllowed) {\n              stopEvent(event);\n            }\n          }\n        },\n      },\n      elementProps,\n      getButtonProps,\n    ],\n  });\n\n  return element;\n});\n\nexport interface ComboboxChipRemoveState {\n  /**\n   * Whether the component should ignore user interaction.\n   */\n  disabled: boolean;\n}\n\nexport interface ComboboxChipRemoveProps\n  extends NativeButtonProps, BaseUIComponentProps<'button', ComboboxChipRemoveState> {}\n\nexport namespace ComboboxChipRemove {\n  export type State = ComboboxChipRemoveState;\n  export type Props = ComboboxChipRemoveProps;\n}\n"
  },
  {
    "path": "packages/react/src/combobox/chips/ComboboxChips.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport { Combobox } from '@base-ui/react/combobox';\nimport { fireEvent, screen } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance } from '#test-utils';\nimport { Field } from '@base-ui/react/field';\n\ndescribe('<Combobox.Chips />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Combobox.Chips />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(<Combobox.Root>{node}</Combobox.Root>);\n    },\n  }));\n\n  it('does not set role=\"toolbar\" when there are no chips', async () => {\n    await render(\n      <Combobox.Root multiple>\n        <Combobox.Chips data-testid=\"chips\" />\n      </Combobox.Root>,\n    );\n\n    const chips = screen.getByTestId('chips');\n    expect(chips).not.toHaveAttribute('role');\n  });\n\n  it('sets role=\"toolbar\" when there is at least one chip', async () => {\n    await render(\n      <Combobox.Root multiple defaultValue={['apple']}>\n        <Combobox.Chips data-testid=\"chips\">\n          <Combobox.Chip>apple</Combobox.Chip>\n        </Combobox.Chips>\n      </Combobox.Root>,\n    );\n\n    const chips = screen.getByTestId('chips');\n    expect(chips).toHaveAttribute('role', 'toolbar');\n  });\n\n  it('focuses the input when clicking anywhere in the Chips area', async () => {\n    await render(\n      <Combobox.Root multiple defaultValue={['apple']}>\n        <Combobox.Chips data-testid=\"chips\">\n          <Combobox.Chip data-testid=\"chip\">apple</Combobox.Chip>\n          <Combobox.Input data-testid=\"input\" />\n        </Combobox.Chips>\n      </Combobox.Root>,\n    );\n\n    const chips = screen.getByTestId('chips');\n    const chip = screen.getByTestId('chip');\n    const input = screen.getByTestId('input');\n\n    expect(document.activeElement).not.toBe(input);\n\n    fireEvent.mouseDown(chips);\n    expect(input).toHaveFocus();\n\n    // Blur and click on a chip: input should still receive focus.\n    input.blur();\n    expect(document.activeElement).not.toBe(input);\n    fireEvent.mouseDown(chip);\n    expect(input).toHaveFocus();\n  });\n\n  it('lets onMouseDown prevent the built-in focus and open behavior', async () => {\n    const handleMouseDown = vi.fn();\n\n    await render(\n      <Combobox.Root items={['apple', 'banana']} multiple defaultValue={['apple']}>\n        <Combobox.Chips\n          data-testid=\"chips\"\n          onMouseDown={(event) => {\n            handleMouseDown(event);\n            event.preventBaseUIHandler();\n          }}\n        >\n          <Combobox.Chip>apple</Combobox.Chip>\n          <Combobox.Input data-testid=\"input\" />\n        </Combobox.Chips>\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup>\n              <Combobox.List>\n                <Combobox.Item value=\"apple\">apple</Combobox.Item>\n                <Combobox.Item value=\"banana\">banana</Combobox.Item>\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    const chips = screen.getByTestId('chips');\n    const input = screen.getByTestId('input');\n\n    fireEvent.mouseDown(chips);\n\n    expect(handleMouseDown).toHaveBeenCalledTimes(1);\n    expect(input).not.toHaveFocus();\n    expect(screen.queryByRole('listbox')).toBe(null);\n  });\n\n  it('lets nested chip onMouseDown prevent the built-in focus and open behavior', async () => {\n    const handleMouseDown = vi.fn();\n\n    await render(\n      <Combobox.Root items={['apple', 'banana']} multiple defaultValue={['apple']}>\n        <Combobox.Chips data-testid=\"chips\">\n          <Combobox.Chip\n            data-testid=\"chip\"\n            onMouseDown={(event) => {\n              handleMouseDown(event);\n              event.preventBaseUIHandler();\n            }}\n          >\n            apple\n          </Combobox.Chip>\n          <Combobox.Input data-testid=\"input\" />\n        </Combobox.Chips>\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup>\n              <Combobox.List>\n                <Combobox.Item value=\"apple\">apple</Combobox.Item>\n                <Combobox.Item value=\"banana\">banana</Combobox.Item>\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    fireEvent.mouseDown(screen.getByTestId('chip'));\n\n    expect(handleMouseDown).toHaveBeenCalledTimes(1);\n    expect(screen.getByTestId('input')).not.toHaveFocus();\n    expect(screen.queryByRole('listbox')).toBe(null);\n  });\n\n  it('lets nested chip onContextMenu call preventBaseUIHandler', async () => {\n    const handleContextMenu = vi.fn();\n\n    await render(\n      <Combobox.Root items={['apple', 'banana']} multiple defaultValue={['apple']}>\n        <Combobox.Chips>\n          <Combobox.Chip\n            data-testid=\"chip\"\n            onContextMenu={(event) => {\n              handleContextMenu(event);\n              event.preventBaseUIHandler();\n            }}\n          >\n            apple\n          </Combobox.Chip>\n          <Combobox.Input />\n        </Combobox.Chips>\n      </Combobox.Root>,\n    );\n\n    expect(() => fireEvent.contextMenu(screen.getByTestId('chip'))).not.toThrow();\n    expect(handleContextMenu).toHaveBeenCalledTimes(1);\n  });\n\n  it('does not treat chip remove presses as chips-area presses', async () => {\n    await render(\n      <Combobox.Root items={['apple', 'banana']} multiple defaultValue={['apple']}>\n        <Combobox.Chips>\n          <Combobox.Chip>\n            apple\n            <Combobox.ChipRemove data-testid=\"remove\" />\n          </Combobox.Chip>\n          <Combobox.Input />\n        </Combobox.Chips>\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup>\n              <Combobox.List>\n                <Combobox.Item value=\"apple\">apple</Combobox.Item>\n                <Combobox.Item value=\"banana\">banana</Combobox.Item>\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    fireEvent.mouseDown(screen.getByTestId('remove'));\n    expect(screen.queryByRole('listbox')).toBe(null);\n  });\n\n  it('does not focus or open when disabled by Field.Root', async () => {\n    await render(\n      <Field.Root disabled>\n        <Combobox.Root items={['apple', 'banana']} multiple defaultValue={['apple']}>\n          <Combobox.Chips data-testid=\"chips\">\n            <Combobox.Chip>apple</Combobox.Chip>\n            <Combobox.Input data-testid=\"input\" />\n          </Combobox.Chips>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"apple\">apple</Combobox.Item>\n                  <Combobox.Item value=\"banana\">banana</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>\n      </Field.Root>,\n    );\n\n    fireEvent.mouseDown(screen.getByTestId('chips'));\n\n    expect(screen.getByTestId('input')).not.toHaveFocus();\n    expect(screen.queryByRole('listbox')).toBe(null);\n  });\n\n  it('does not focus or open when readOnly', async () => {\n    await render(\n      <Combobox.Root items={['apple', 'banana']} multiple readOnly defaultValue={['apple']}>\n        <Combobox.Chips data-testid=\"chips\">\n          <Combobox.Chip>apple</Combobox.Chip>\n          <Combobox.Input data-testid=\"input\" />\n        </Combobox.Chips>\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup>\n              <Combobox.List>\n                <Combobox.Item value=\"apple\">apple</Combobox.Item>\n                <Combobox.Item value=\"banana\">banana</Combobox.Item>\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    fireEvent.mouseDown(screen.getByTestId('chips'));\n\n    expect(screen.getByTestId('input')).not.toHaveFocus();\n    expect(screen.queryByRole('listbox')).toBe(null);\n  });\n});\n"
  },
  {
    "path": "packages/react/src/combobox/chips/ComboboxChips.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStore } from '@base-ui/utils/store';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { ComboboxChipsContext } from './ComboboxChipsContext';\nimport { CompositeList } from '../../composite/list/CompositeList';\nimport { useComboboxRootContext } from '../root/ComboboxRootContext';\nimport { selectors } from '../store';\nimport { EMPTY_OBJECT } from '../../utils/constants';\nimport { handleInputPress } from '../utils/handleInputPress';\n\n/**\n * A container for the chips in a multiselectable input.\n * Renders a `<div>` element.\n */\nexport const ComboboxChips = React.forwardRef(function ComboboxChips(\n  componentProps: ComboboxChips.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, ...elementProps } = componentProps;\n\n  const store = useComboboxRootContext();\n\n  const open = useStore(store, selectors.open);\n  const hasSelectionChips = useStore(store, selectors.hasSelectionChips);\n\n  const [highlightedChipIndex, setHighlightedChipIndex] = React.useState<number | undefined>(\n    undefined,\n  );\n\n  if (open && highlightedChipIndex !== undefined) {\n    setHighlightedChipIndex(undefined);\n  }\n\n  const chipsRef = React.useRef<Array<HTMLButtonElement | null>>([]);\n\n  const element = useRenderElement('div', componentProps, {\n    ref: [forwardedRef, store.state.chipsContainerRef],\n    // NVDA enters browse mode instead of staying in focus mode when navigating with\n    // arrow keys inside a container unless it has a toolbar role.\n    props: [\n      hasSelectionChips ? { role: 'toolbar' } : EMPTY_OBJECT,\n      {\n        onMouseDown(event) {\n          handleInputPress(event, store, store.state.disabled, store.state.readOnly);\n        },\n      },\n      elementProps,\n    ],\n  });\n\n  const contextValue: ComboboxChipsContext = React.useMemo(\n    () => ({\n      highlightedChipIndex,\n      setHighlightedChipIndex,\n      chipsRef,\n    }),\n    [highlightedChipIndex, setHighlightedChipIndex, chipsRef],\n  );\n\n  return (\n    <ComboboxChipsContext.Provider value={contextValue}>\n      <CompositeList elementsRef={chipsRef}>{element}</CompositeList>\n    </ComboboxChipsContext.Provider>\n  );\n});\n\nexport interface ComboboxChipsState {}\n\nexport interface ComboboxChipsProps extends BaseUIComponentProps<'div', ComboboxChipsState> {}\n\nexport namespace ComboboxChips {\n  export type State = ComboboxChipsState;\n  export type Props = ComboboxChipsProps;\n}\n"
  },
  {
    "path": "packages/react/src/combobox/chips/ComboboxChipsContext.ts",
    "content": "'use client';\nimport * as React from 'react';\n\nexport interface ComboboxChipsContext {\n  highlightedChipIndex: number | undefined;\n  setHighlightedChipIndex: React.Dispatch<React.SetStateAction<number | undefined>>;\n  chipsRef: React.RefObject<Array<HTMLButtonElement | null>>;\n}\n\nexport const ComboboxChipsContext = React.createContext<ComboboxChipsContext | undefined>(\n  undefined,\n);\n\nexport function useComboboxChipsContext() {\n  return React.useContext(ComboboxChipsContext);\n}\n"
  },
  {
    "path": "packages/react/src/combobox/clear/ComboboxClear.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { Combobox } from '@base-ui/react/combobox';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\nimport { fireEvent, screen, waitFor } from '@mui/internal-test-utils';\n\ndescribe('<Combobox.Clear />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Combobox.Clear />, () => ({\n    refInstanceof: window.HTMLButtonElement,\n    button: true,\n    render(node) {\n      return render(\n        <Combobox.Root defaultValue=\"a\">\n          <Combobox.Input />\n          {node}\n        </Combobox.Root>,\n      );\n    },\n  }));\n\n  it('renders in single mode when a value is selected', async () => {\n    await render(\n      <Combobox.Root defaultValue=\"a\">\n        <Combobox.Input />\n        <Combobox.Clear data-testid=\"clear\" />\n      </Combobox.Root>,\n    );\n    expect(screen.getByTestId('clear')).not.toBe(null);\n  });\n\n  it('click clears selected value and focuses input', async () => {\n    const { user } = await render(\n      <Combobox.Root defaultValue=\"a\">\n        <Combobox.Input data-testid=\"input\" />\n        <Combobox.Clear data-testid=\"clear\" />\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup>\n              <Combobox.List>\n                <Combobox.Item value=\"a\">a</Combobox.Item>\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    const input = screen.getByRole('combobox');\n    await user.click(screen.getByTestId('clear'));\n\n    expect(screen.queryByTestId('clear')).toBe(null);\n    expect(document.activeElement).toBe(input);\n  });\n\n  it('does not dismiss the popup on click (outsidePress is blocked)', async () => {\n    const { user } = await render(\n      <Combobox.Root defaultOpen defaultValue=\"a\">\n        <Combobox.Input data-testid=\"input\" />\n        <Combobox.Clear data-testid=\"clear\" />\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup>\n              <Combobox.List>\n                <Combobox.Item value=\"a\">a</Combobox.Item>\n                <Combobox.Item value=\"b\">b</Combobox.Item>\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    expect(screen.getByRole('listbox')).not.toBe(null);\n\n    await user.click(screen.getByTestId('clear'));\n\n    expect(screen.getByRole('listbox')).not.toBe(null);\n  });\n\n  it('is disabled when root disabled and does nothing on click', async () => {\n    await render(\n      <Combobox.Root defaultValue=\"a\" disabled>\n        <Combobox.Input data-testid=\"input\" />\n        <Combobox.Clear data-testid=\"clear\" />\n      </Combobox.Root>,\n    );\n\n    const clear = screen.getByTestId('clear');\n    expect(clear).toHaveAttribute('disabled');\n\n    fireEvent.click(clear);\n    expect(screen.getByTestId('clear')).not.toBe(null);\n  });\n\n  it('when root is readOnly it does nothing on click', async () => {\n    await render(\n      <Combobox.Root defaultValue=\"a\" readOnly>\n        <Combobox.Input data-testid=\"input\" />\n        <Combobox.Clear data-testid=\"clear\" />\n      </Combobox.Root>,\n    );\n\n    const clear = screen.getByTestId('clear');\n\n    fireEvent.click(clear);\n    expect(screen.getByTestId('clear')).not.toBe(null);\n  });\n\n  describe.skipIf(isJSDOM)('animations', () => {\n    it('triggers enter animation via data-starting-style when becoming visible', async () => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n      let animationFinished = false;\n      const notifyAnimationFinished = () => {\n        animationFinished = true;\n      };\n\n      const style = `\n        .animation-test-indicator {\n          transition: opacity 1ms;\n        }\n\n        .animation-test-indicator[data-starting-style],\n        .animation-test-indicator[data-ending-style] {\n          opacity: 0;\n        }\n      `;\n\n      const { user } = await render(\n        <div>\n          {/* eslint-disable-next-line react/no-danger */}\n          <style dangerouslySetInnerHTML={{ __html: style }} />\n          <Combobox.Root>\n            <Combobox.Input data-testid=\"input\" />\n            <Combobox.Clear\n              className=\"animation-test-indicator\"\n              data-testid=\"clear\"\n              onTransitionEnd={notifyAnimationFinished}\n            />\n            <Combobox.Portal>\n              <Combobox.Positioner>\n                <Combobox.Popup>\n                  <Combobox.List>\n                    <Combobox.Item value=\"a\">a</Combobox.Item>\n                  </Combobox.List>\n                </Combobox.Popup>\n              </Combobox.Positioner>\n            </Combobox.Portal>\n          </Combobox.Root>\n        </div>,\n      );\n\n      expect(screen.queryByTestId('clear')).toBe(null);\n\n      const input = screen.getByTestId('input');\n      await user.click(input);\n      await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n      await user.click(screen.getByRole('option', { name: 'a' }));\n\n      await waitFor(() => {\n        expect(animationFinished).toBe(true);\n      });\n      expect(screen.getByTestId('clear')).not.toBe(null);\n    });\n\n    it('triggers exit animation via data-ending-style before unmount', async () => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n      let animationFinished = false;\n      const notifyAnimationFinished = () => {\n        animationFinished = true;\n      };\n\n      const style = `\n        .animation-test-indicator {\n          transition: opacity 1ms;\n        }\n\n        .animation-test-indicator[data-starting-style],\n        .animation-test-indicator[data-ending-style] {\n          opacity: 0;\n        }\n      `;\n\n      const { user } = await render(\n        <div>\n          {/* eslint-disable-next-line react/no-danger */}\n          <style dangerouslySetInnerHTML={{ __html: style }} />\n          <Combobox.Root defaultValue=\"a\">\n            <Combobox.Input data-testid=\"input\" />\n            <Combobox.Clear\n              className=\"animation-test-indicator\"\n              data-testid=\"clear\"\n              keepMounted\n              onTransitionEnd={notifyAnimationFinished}\n            />\n          </Combobox.Root>\n        </div>,\n      );\n\n      const clear = screen.getByTestId('clear');\n      expect(clear).not.toBe(null);\n\n      await user.click(clear);\n\n      await waitFor(() => {\n        expect(animationFinished).toBe(true);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/combobox/clear/ComboboxClear.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStore } from '@base-ui/utils/store';\nimport { useComboboxInputValueContext, useComboboxRootContext } from '../root/ComboboxRootContext';\nimport type { BaseUIComponentProps, NativeButtonProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { selectors } from '../store';\nimport { useButton } from '../../use-button';\nimport { useFieldRootContext } from '../../field/root/FieldRootContext';\nimport { TransitionStatus, useTransitionStatus } from '../../utils/useTransitionStatus';\nimport { transitionStatusMapping } from '../../utils/stateAttributesMapping';\nimport { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport { useOpenChangeComplete } from '../../utils/useOpenChangeComplete';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport { triggerOpenStateMapping } from '../../utils/popupStateMapping';\n\nconst stateAttributesMapping: StateAttributesMapping<ComboboxClearState> = {\n  ...transitionStatusMapping,\n  ...triggerOpenStateMapping,\n};\n\n/**\n * Clears the value when clicked.\n * Renders a `<button>` element.\n */\nexport const ComboboxClear = React.forwardRef(function ComboboxClear(\n  componentProps: ComboboxClear.Props,\n  forwardedRef: React.ForwardedRef<HTMLButtonElement>,\n) {\n  const {\n    render,\n    className,\n    disabled: disabledProp = false,\n    nativeButton = true,\n    keepMounted = false,\n    ...elementProps\n  } = componentProps;\n\n  const { disabled: fieldDisabled } = useFieldRootContext();\n  const store = useComboboxRootContext();\n\n  const selectionMode = useStore(store, selectors.selectionMode);\n  const comboboxDisabled = useStore(store, selectors.disabled);\n  const readOnly = useStore(store, selectors.readOnly);\n  const open = useStore(store, selectors.open);\n  const selectedValue = useStore(store, selectors.selectedValue);\n  const hasSelectionChips = useStore(store, selectors.hasSelectionChips);\n\n  const inputValue = useComboboxInputValueContext();\n\n  let visible = false;\n  if (selectionMode === 'none') {\n    visible = inputValue !== '';\n  } else if (selectionMode === 'single') {\n    visible = selectedValue != null;\n  } else {\n    visible = hasSelectionChips;\n  }\n\n  const disabled = fieldDisabled || comboboxDisabled || disabledProp;\n\n  const { buttonRef, getButtonProps } = useButton({\n    native: nativeButton,\n    disabled,\n  });\n\n  const { mounted, transitionStatus, setMounted } = useTransitionStatus(visible);\n\n  const state: ComboboxClearState = {\n    disabled,\n    open,\n    transitionStatus,\n  };\n\n  useOpenChangeComplete({\n    open: visible,\n    ref: store.state.clearRef,\n    onComplete() {\n      if (!visible) {\n        setMounted(false);\n      }\n    },\n  });\n\n  const element = useRenderElement('button', componentProps, {\n    state,\n    ref: [forwardedRef, buttonRef, store.state.clearRef],\n    props: [\n      {\n        tabIndex: -1,\n        children: 'x',\n        // Avoid stealing focus from the input.\n        onMouseDown(event) {\n          event.preventDefault();\n        },\n        onClick(event) {\n          if (disabled || readOnly) {\n            return;\n          }\n\n          const keyboardActiveRef = store.state.keyboardActiveRef;\n\n          store.state.setInputValue(\n            '',\n            createChangeEventDetails(REASONS.clearPress, event.nativeEvent),\n          );\n\n          if (selectionMode !== 'none') {\n            store.state.setSelectedValue(\n              Array.isArray(selectedValue) ? [] : null,\n              createChangeEventDetails(REASONS.clearPress, event.nativeEvent),\n            );\n            store.state.setIndices({\n              activeIndex: null,\n              selectedIndex: null,\n              type: keyboardActiveRef.current ? 'keyboard' : 'pointer',\n            });\n          } else {\n            store.state.setIndices({\n              activeIndex: null,\n              type: keyboardActiveRef.current ? 'keyboard' : 'pointer',\n            });\n          }\n\n          store.state.inputRef.current?.focus();\n        },\n      },\n      elementProps,\n      getButtonProps,\n    ],\n    stateAttributesMapping,\n  });\n\n  const shouldRender = keepMounted || mounted;\n  if (!shouldRender) {\n    return null;\n  }\n\n  return element;\n});\n\nexport interface ComboboxClearState {\n  /**\n   * Whether the popup is open.\n   */\n  open: boolean;\n  /**\n   * Whether the component should ignore user interaction.\n   */\n  disabled: boolean;\n  /**\n   * The transition status of the component.\n   */\n  transitionStatus: TransitionStatus;\n}\n\nexport interface ComboboxClearProps\n  extends NativeButtonProps, BaseUIComponentProps<'button', ComboboxClearState> {\n  /**\n   * Whether the component should ignore user interaction.\n   * @default false\n   */\n  disabled?: boolean | undefined;\n  /**\n   * Whether the component should remain mounted in the DOM when not visible.\n   * @default false\n   */\n  keepMounted?: boolean | undefined;\n}\n\nexport namespace ComboboxClear {\n  export type State = ComboboxClearState;\n  export type Props = ComboboxClearProps;\n}\n"
  },
  {
    "path": "packages/react/src/combobox/clear/ComboboxClearDataAtributes.ts",
    "content": "import {\n  CommonPopupDataAttributes,\n  CommonTriggerDataAttributes,\n} from '../../utils/popupStateMapping';\n\nexport enum ComboboxClearDataAttributes {\n  /**\n   * Present when the corresponding popup is open.\n   */\n  popupOpen = CommonTriggerDataAttributes.popupOpen,\n  /**\n   * Present when the button is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the button is animating in.\n   */\n  startingStyle = CommonPopupDataAttributes.startingStyle,\n  /**\n   * Present when the button is animating out.\n   */\n  endingStyle = CommonPopupDataAttributes.endingStyle,\n}\n"
  },
  {
    "path": "packages/react/src/combobox/collection/ComboboxCollection.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { Combobox } from '@base-ui/react/combobox';\nimport { createRenderer } from '#test-utils';\nimport { screen } from '@mui/internal-test-utils';\n\ndescribe('<Combobox.Collection />', () => {\n  const { render } = createRenderer();\n\n  it('renders filtered items', async () => {\n    await render(\n      <Combobox.Root items={['alpha', 'beta', 'alpine']} defaultOpen>\n        <Combobox.Input />\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup>\n              <Combobox.List>\n                <Combobox.Collection>\n                  {(item) => (\n                    <Combobox.Item key={item} value={item} data-testid={`item-${item}`}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.Collection>\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    expect(screen.getByTestId('item-alpha')).not.toBe(null);\n    expect(screen.getByTestId('item-beta')).not.toBe(null);\n    expect(screen.getByTestId('item-alpine')).not.toBe(null);\n  });\n});\n"
  },
  {
    "path": "packages/react/src/combobox/collection/ComboboxCollection.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useComboboxDerivedItemsContext } from '../root/ComboboxRootContext';\nimport { useGroupCollectionContext } from './GroupCollectionContext';\n\n/**\n * Renders filtered list items.\n * Doesn't render its own HTML element.\n *\n * If rendering a flat list, pass a function child to the `List` component instead, which implicitly wraps it.\n */\nexport function ComboboxCollection(props: ComboboxCollection.Props): React.JSX.Element | null {\n  const { children } = props;\n\n  const { filteredItems } = useComboboxDerivedItemsContext();\n  const groupContext = useGroupCollectionContext();\n\n  const itemsToRender = groupContext ? groupContext.items : filteredItems;\n\n  if (!itemsToRender) {\n    return null;\n  }\n\n  return <React.Fragment>{itemsToRender.map(children)}</React.Fragment>;\n}\n\nexport interface ComboboxCollectionState {}\n\nexport interface ComboboxCollectionProps {\n  children: (item: any, index: number) => React.ReactNode;\n}\n\nexport namespace ComboboxCollection {\n  export type State = ComboboxCollectionState;\n  export type Props = ComboboxCollectionProps;\n}\n"
  },
  {
    "path": "packages/react/src/combobox/collection/GroupCollectionContext.tsx",
    "content": "'use client';\nimport * as React from 'react';\n\ninterface GroupCollectionContext {\n  items: readonly any[];\n}\n\nconst GroupCollectionContext = React.createContext<GroupCollectionContext | null>(null);\n\nexport function useGroupCollectionContext() {\n  return React.useContext(GroupCollectionContext);\n}\n\nexport function GroupCollectionProvider(props: GroupCollectionProvider.Props) {\n  const { children, items } = props;\n\n  const contextValue = React.useMemo(() => ({ items }), [items]);\n\n  return (\n    <GroupCollectionContext.Provider value={contextValue}>\n      {children}\n    </GroupCollectionContext.Provider>\n  );\n}\n\nnamespace GroupCollectionProvider {\n  export interface Props {\n    children: React.ReactNode;\n    items: readonly any[];\n  }\n}\n"
  },
  {
    "path": "packages/react/src/combobox/empty/ComboboxEmpty.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { Combobox } from '@base-ui/react/combobox';\nimport { createRenderer, describeConformance } from '#test-utils';\nimport { screen } from '@mui/internal-test-utils';\n\ndescribe('<Combobox.Empty />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Combobox.Empty />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <Combobox.Root defaultOpen>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                {node}\n                <Combobox.List />\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n    },\n  }));\n\n  it('renders when there are no filtered items', async () => {\n    await render(\n      <Combobox.Root items={[]} defaultOpen>\n        <Combobox.Input />\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup>\n              <Combobox.Empty data-testid=\"empty\">No results</Combobox.Empty>\n              <Combobox.List>\n                {(item) => (\n                  <Combobox.Item key={item} value={item}>\n                    {item}\n                  </Combobox.Item>\n                )}\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    expect(screen.getByTestId('empty')).toHaveTextContent('No results');\n    expect(screen.getByTestId('empty')).toHaveAttribute('role', 'status');\n  });\n\n  it('does not render when there are items', async () => {\n    await render(\n      <Combobox.Root items={['a']} defaultOpen>\n        <Combobox.Input />\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup>\n              <Combobox.Empty>No results</Combobox.Empty>\n              <Combobox.List>\n                {(item) => (\n                  <Combobox.Item key={item} value={item}>\n                    {item}\n                  </Combobox.Item>\n                )}\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    expect(screen.queryByText('No results')).toBe(null);\n  });\n\n  it('renders when the search query matches no items', async () => {\n    await render(\n      <Combobox.Root items={['a', 'b', 'c']} defaultInputValue=\"d\" defaultOpen>\n        <Combobox.Input />\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup>\n              <Combobox.Empty data-testid=\"empty\">No results</Combobox.Empty>\n              <Combobox.List>\n                {(item) => (\n                  <Combobox.Item key={item} value={item}>\n                    {item}\n                  </Combobox.Item>\n                )}\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    expect(screen.queryByText('No results')).not.toBe(null);\n  });\n\n  it('does not render when the search query matches an item', async () => {\n    await render(\n      <Combobox.Root items={['a', 'b', 'c']} defaultInputValue=\"c\" defaultOpen>\n        <Combobox.Input />\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup>\n              <Combobox.Empty data-testid=\"empty\">No results</Combobox.Empty>\n              <Combobox.List>\n                {(item) => (\n                  <Combobox.Item key={item} value={item}>\n                    {item}\n                  </Combobox.Item>\n                )}\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    expect(screen.queryByText('No results')).toBe(null);\n  });\n});\n"
  },
  {
    "path": "packages/react/src/combobox/empty/ComboboxEmpty.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport {\n  useComboboxDerivedItemsContext,\n  useComboboxRootContext,\n} from '../root/ComboboxRootContext';\n\n/**\n * Renders its children only when the list is empty.\n * Requires the `items` prop on the root component.\n * Announces changes politely to screen readers.\n * Renders a `<div>` element.\n */\nexport const ComboboxEmpty = React.forwardRef(function ComboboxEmpty(\n  componentProps: ComboboxEmpty.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, children: childrenProp, ...elementProps } = componentProps;\n\n  const { filteredItems } = useComboboxDerivedItemsContext();\n  const store = useComboboxRootContext();\n\n  const children = filteredItems.length === 0 ? childrenProp : null;\n\n  return useRenderElement('div', componentProps, {\n    ref: [forwardedRef, store.state.emptyRef],\n    props: [\n      {\n        children,\n        role: 'status',\n        'aria-live': 'polite',\n        'aria-atomic': true,\n      },\n      elementProps,\n    ],\n  });\n});\n\nexport interface ComboboxEmptyState {}\n\nexport interface ComboboxEmptyProps extends BaseUIComponentProps<'div', ComboboxEmptyState> {}\n\nexport namespace ComboboxEmpty {\n  export type State = ComboboxEmptyState;\n  export type Props = ComboboxEmptyProps;\n}\n"
  },
  {
    "path": "packages/react/src/combobox/group/ComboboxGroup.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { Combobox } from '@base-ui/react/combobox';\nimport { createRenderer, describeConformance } from '#test-utils';\nimport { screen } from '@mui/internal-test-utils';\n\ndescribe('<Combobox.Group />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Combobox.Group />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(<Combobox.Root open>{node}</Combobox.Root>);\n    },\n  }));\n\n  it('should render group with label', async () => {\n    await render(\n      <Combobox.Root open>\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Group>\n              <Combobox.GroupLabel>Fruits</Combobox.GroupLabel>\n              <Combobox.Item value=\"apple\">Apple</Combobox.Item>\n              <Combobox.Item value=\"banana\">Banana</Combobox.Item>\n            </Combobox.Group>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    expect(screen.getByRole('group')).toHaveAttribute('aria-labelledby');\n    expect(screen.getByText('Fruits')).toBeVisible();\n  });\n\n  it('should associate label with group', async () => {\n    await render(\n      <Combobox.Root open>\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Group>\n              <Combobox.GroupLabel>Vegetables</Combobox.GroupLabel>\n              <Combobox.Item value=\"carrot\">Carrot</Combobox.Item>\n              <Combobox.Item value=\"lettuce\">Lettuce</Combobox.Item>\n            </Combobox.Group>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    const Group = screen.getByRole('group');\n    const label = screen.getByText('Vegetables');\n    expect(Group).toHaveAttribute('aria-labelledby', label.id);\n  });\n});\n"
  },
  {
    "path": "packages/react/src/combobox/group/ComboboxGroup.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { ComboboxGroupContext } from './ComboboxGroupContext';\nimport { GroupCollectionProvider } from '../collection/GroupCollectionContext';\n\n/**\n * Groups related items with the corresponding label.\n * Renders a `<div>` element.\n */\nexport const ComboboxGroup = React.forwardRef(function ComboboxGroup(\n  componentProps: ComboboxGroup.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, items, ...elementProps } = componentProps;\n\n  const [labelId, setLabelId] = React.useState<string | undefined>();\n\n  const contextValue = React.useMemo(\n    () => ({\n      labelId,\n      setLabelId,\n      items,\n    }),\n    [labelId, setLabelId, items],\n  );\n\n  const element = useRenderElement('div', componentProps, {\n    ref: forwardedRef,\n    props: [\n      {\n        role: 'group',\n        'aria-labelledby': labelId,\n      },\n      elementProps,\n    ],\n  });\n\n  const wrappedElement = (\n    <ComboboxGroupContext.Provider value={contextValue}>{element}</ComboboxGroupContext.Provider>\n  );\n\n  if (items) {\n    return <GroupCollectionProvider items={items}>{wrappedElement}</GroupCollectionProvider>;\n  }\n\n  return wrappedElement;\n});\n\nexport interface ComboboxGroupState {}\n\nexport interface ComboboxGroupProps extends BaseUIComponentProps<'div', ComboboxGroupState> {\n  /**\n   * Items to be rendered within this group.\n   * When provided, child `Collection` components will use these items.\n   */\n  items?: readonly any[] | undefined;\n}\n\nexport namespace ComboboxGroup {\n  export type State = ComboboxGroupState;\n  export type Props = ComboboxGroupProps;\n}\n"
  },
  {
    "path": "packages/react/src/combobox/group/ComboboxGroupContext.ts",
    "content": "'use client';\nimport * as React from 'react';\n\nexport interface ComboboxGroupContext {\n  labelId: string | undefined;\n  setLabelId: React.Dispatch<React.SetStateAction<string | undefined>>;\n  /**\n   * Optional list of items that belong to this group. Used by nested\n   * collections to render group-specific items.\n   */\n  items?: readonly any[] | undefined;\n}\n\nexport const ComboboxGroupContext = React.createContext<ComboboxGroupContext | undefined>(\n  undefined,\n);\n\nexport function useComboboxGroupContext() {\n  const context = React.useContext(ComboboxGroupContext);\n  if (context === undefined) {\n    throw new Error(\n      'Base UI: ComboboxGroupContext is missing. ComboboxGroup parts must be placed within <Combobox.Group>.',\n    );\n  }\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/combobox/group-label/ComboboxGroupLabel.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { Combobox } from '@base-ui/react/combobox';\nimport { createRenderer, describeConformance } from '#test-utils';\nimport { screen } from '@mui/internal-test-utils';\n\ndescribe('<Combobox.GroupLabel />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Combobox.GroupLabel />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <Combobox.Root open>\n          <Combobox.Group>{node}</Combobox.Group>\n        </Combobox.Root>,\n      );\n    },\n  }));\n\n  describe('a11y attributes', () => {\n    it('wires to group aria-labelledby', async () => {\n      await render(\n        <Combobox.Root open>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.Group>\n                  <Combobox.GroupLabel>Label</Combobox.GroupLabel>\n                </Combobox.Group>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const group = screen.getByRole('group');\n      const label = screen.getByText('Label');\n      expect(group).toHaveAttribute('aria-labelledby', label.id);\n    });\n\n    it('uses provided id in aria-labelledby', async () => {\n      await render(\n        <Combobox.Root open>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.Group>\n                  <Combobox.GroupLabel id=\"test-group\">Label</Combobox.GroupLabel>\n                </Combobox.Group>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const group = screen.getByRole('group');\n      expect(group).toHaveAttribute('aria-labelledby', 'test-group');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/combobox/group-label/ComboboxGroupLabel.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { useBaseUiId } from '../../utils/useBaseUiId';\nimport { useComboboxGroupContext } from '../group/ComboboxGroupContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\n\n/**\n * An accessible label that is automatically associated with its parent group.\n * Renders a `<div>` element.\n */\nexport const ComboboxGroupLabel = React.forwardRef(function ComboboxGroupLabel(\n  componentProps: ComboboxGroupLabel.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, id: idProp, ...elementProps } = componentProps;\n\n  const { setLabelId } = useComboboxGroupContext();\n\n  const id = useBaseUiId(idProp);\n\n  useIsoLayoutEffect(() => {\n    setLabelId(id);\n    return () => {\n      setLabelId(undefined);\n    };\n  }, [id, setLabelId]);\n\n  const element = useRenderElement('div', componentProps, {\n    ref: forwardedRef,\n    props: [{ id }, elementProps],\n  });\n\n  return element;\n});\n\nexport interface ComboboxGroupLabelState {}\n\nexport interface ComboboxGroupLabelProps extends BaseUIComponentProps<\n  'div',\n  ComboboxGroupLabelState\n> {}\n\nexport namespace ComboboxGroupLabel {\n  export type State = ComboboxGroupLabelState;\n  export type Props = ComboboxGroupLabelProps;\n}\n"
  },
  {
    "path": "packages/react/src/combobox/icon/ComboboxIcon.test.tsx",
    "content": "import { Combobox } from '@base-ui/react/combobox';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Combobox.Icon />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Combobox.Icon />, () => ({\n    refInstanceof: window.HTMLSpanElement,\n    render(node) {\n      return render(<Combobox.Root open>{node}</Combobox.Root>);\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/combobox/icon/ComboboxIcon.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\n\n/**\n * An icon that indicates that the trigger button opens the popup.\n * Renders a `<span>` element.\n */\nexport const ComboboxIcon = React.forwardRef(function ComboboxIcon(\n  componentProps: ComboboxIcon.Props,\n  forwardedRef: React.ForwardedRef<HTMLSpanElement>,\n) {\n  const { className, render, ...elementProps } = componentProps;\n\n  const element = useRenderElement('span', componentProps, {\n    ref: forwardedRef,\n    props: [\n      {\n        'aria-hidden': true,\n        children: '▼',\n      },\n      elementProps,\n    ],\n  });\n\n  return element;\n});\n\nexport interface ComboboxIconState {}\n\nexport interface ComboboxIconProps extends BaseUIComponentProps<'span', ComboboxIconState> {}\n\nexport namespace ComboboxIcon {\n  export type State = ComboboxIconState;\n  export type Props = ComboboxIconProps;\n}\n"
  },
  {
    "path": "packages/react/src/combobox/index.parts.ts",
    "content": "export { ComboboxRoot as Root } from './root/ComboboxRoot';\nexport { ComboboxLabel as Label } from './label/ComboboxLabel';\nexport { ComboboxValue as Value } from './value/ComboboxValue';\nexport { ComboboxInput as Input } from './input/ComboboxInput';\nexport { ComboboxInputGroup as InputGroup } from './input-group/ComboboxInputGroup';\nexport { ComboboxTrigger as Trigger } from './trigger/ComboboxTrigger';\nexport { ComboboxList as List } from './list/ComboboxList';\nexport { ComboboxStatus as Status } from './status/ComboboxStatus';\nexport { ComboboxPortal as Portal } from './portal/ComboboxPortal';\nexport { ComboboxBackdrop as Backdrop } from './backdrop/ComboboxBackdrop';\nexport { ComboboxPositioner as Positioner } from './positioner/ComboboxPositioner';\nexport { ComboboxPopup as Popup } from './popup/ComboboxPopup';\nexport { ComboboxArrow as Arrow } from './arrow/ComboboxArrow';\nexport { ComboboxIcon as Icon } from './icon/ComboboxIcon';\nexport { ComboboxGroup as Group } from './group/ComboboxGroup';\nexport { ComboboxGroupLabel as GroupLabel } from './group-label/ComboboxGroupLabel';\nexport { ComboboxItem as Item } from './item/ComboboxItem';\nexport { ComboboxItemIndicator as ItemIndicator } from './item-indicator/ComboboxItemIndicator';\nexport { ComboboxChips as Chips } from './chips/ComboboxChips';\nexport { ComboboxChip as Chip } from './chip/ComboboxChip';\nexport { ComboboxChipRemove as ChipRemove } from './chip-remove/ComboboxChipRemove';\nexport { ComboboxRow as Row } from './row/ComboboxRow';\nexport { ComboboxCollection as Collection } from './collection/ComboboxCollection';\nexport { ComboboxEmpty as Empty } from './empty/ComboboxEmpty';\nexport { ComboboxClear as Clear } from './clear/ComboboxClear';\n\nexport { Separator } from '../separator';\n\nexport { useComboboxFilter as useFilter } from './root/utils/useFilter';\nexport { useFilteredItems } from './root/utils/useFilteredItems';\n"
  },
  {
    "path": "packages/react/src/combobox/index.ts",
    "content": "export * as Combobox from './index.parts';\n\nexport type * from './root/ComboboxRoot';\nexport type * from './label/ComboboxLabel';\nexport type * from './trigger/ComboboxTrigger';\nexport type * from './input/ComboboxInput';\nexport type * from './input-group/ComboboxInputGroup';\nexport type * from './popup/ComboboxPopup';\nexport type * from './positioner/ComboboxPositioner';\nexport type * from './list/ComboboxList';\nexport type * from './item/ComboboxItem';\nexport type * from './item-indicator/ComboboxItemIndicator';\nexport type * from './value/ComboboxValue';\nexport type * from './icon/ComboboxIcon';\nexport type * from './arrow/ComboboxArrow';\nexport type * from './backdrop/ComboboxBackdrop';\nexport type * from './portal/ComboboxPortal';\nexport type * from './empty/ComboboxEmpty';\nexport type * from './group/ComboboxGroup';\nexport type * from './group-label/ComboboxGroupLabel';\nexport type * from './row/ComboboxRow';\nexport type * from './chips/ComboboxChips';\nexport type * from './chip/ComboboxChip';\nexport type * from './chip-remove/ComboboxChipRemove';\nexport type * from './clear/ComboboxClear';\nexport type * from './status/ComboboxStatus';\nexport type * from './collection/ComboboxCollection';\n\nexport type {\n  Filter as ComboboxFilter,\n  UseComboboxFilterOptions as ComboboxFilterOptions,\n} from '../combobox/root/utils/useFilter';\n"
  },
  {
    "path": "packages/react/src/combobox/input/ComboboxInput.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { Combobox } from '@base-ui/react/combobox';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\nimport { screen, waitFor } from '@mui/internal-test-utils';\nimport { Field } from '@base-ui/react/field';\nimport { REASONS } from '../../utils/reasons';\n\ndescribe('<Combobox.Input />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Combobox.Input />, () => ({\n    refInstanceof: window.HTMLInputElement,\n    render(node) {\n      return render(<Combobox.Root>{node}</Combobox.Root>);\n    },\n  }));\n\n  describe('prop: disabled', () => {\n    it('should render aria-disabled attribute when disabled', async () => {\n      await render(\n        <Combobox.Root>\n          <Combobox.Input disabled data-testid=\"input\" />\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      expect(input).toHaveAttribute('disabled');\n    });\n\n    it('should inherit disabled state from ComboboxRoot', async () => {\n      await render(\n        <Combobox.Root disabled>\n          <Combobox.Input data-testid=\"input\" />\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      expect(input).toHaveAttribute('disabled');\n    });\n\n    it('should inherit disabled state from Field.Root', async () => {\n      await render(\n        <Field.Root disabled>\n          <Combobox.Root>\n            <Combobox.Input data-testid=\"input\" />\n          </Combobox.Root>\n        </Field.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      expect(input).toHaveAttribute('disabled');\n    });\n\n    it('should not open popup when disabled', async () => {\n      const { user } = await render(\n        <Combobox.Root>\n          <Combobox.Input disabled data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                  <Combobox.Item value=\"b\">b</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      await user.click(input);\n\n      expect(screen.queryByRole('listbox')).toBe(null);\n    });\n\n    it('should prioritize local disabled over root disabled', async () => {\n      await render(\n        <Combobox.Root disabled={false}>\n          <Combobox.Input disabled data-testid=\"input\" />\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      expect(input).toHaveAttribute('disabled');\n    });\n  });\n\n  describe('prop: readOnly', () => {\n    it('should render aria-readonly and readonly attributes when readOnly', async () => {\n      await render(\n        <Combobox.Root readOnly>\n          <Combobox.Input data-testid=\"input\" />\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      expect(input).toHaveAttribute('aria-readonly', 'true');\n      expect(input).toHaveAttribute('readonly');\n    });\n\n    it('should not open popup when readOnly', async () => {\n      const { user } = await render(\n        <Combobox.Root readOnly>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                  <Combobox.Item value=\"b\">b</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      await user.click(input);\n\n      expect(screen.queryByRole('listbox')).toBe(null);\n    });\n\n    it('should prevent keyboard interactions when readOnly', async () => {\n      const { user } = await render(\n        <Combobox.Root readOnly>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                  <Combobox.Item value=\"b\">b</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n\n      await user.type(input, 'a');\n      expect(screen.queryByRole('listbox')).toBe(null);\n    });\n\n    it('allows interactions when readOnly={false}', async () => {\n      const { user } = await render(\n        <Combobox.Root readOnly={false}>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      await user.click(input);\n      await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n\n      await waitFor(() => {\n        screen.getByRole('option', { name: 'a' });\n      });\n      await user.click(screen.getByRole('option', { name: 'a' }));\n      expect(input).toHaveValue('a');\n    });\n  });\n\n  describe('prop: required', () => {\n    it('sets aria-required attribute when required', async () => {\n      await render(\n        <Combobox.Root required>\n          <Combobox.Input data-testid=\"input\" />\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      expect(input).toHaveAttribute('aria-required', 'true');\n    });\n  });\n\n  describe('onOpenChange reason', () => {\n    it('fires with reason input-press when Input is clicked', async () => {\n      const onOpenChange = vi.fn();\n\n      const { user } = await render(\n        <Combobox.Root items={['apple', 'banana']} onOpenChange={onOpenChange}>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: string) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      await user.click(input);\n\n      expect(onOpenChange.mock.calls.length).toBeGreaterThan(0);\n      expect(onOpenChange.mock.lastCall?.[0]).toBe(true);\n      expect(onOpenChange.mock.lastCall?.[1].reason).toBe(REASONS.inputPress);\n    });\n  });\n\n  describe('interaction behavior', () => {\n    it('clears selected value when input text is cleared (single selection)', async () => {\n      const { user } = await render(\n        <Combobox.Root items={['apple', 'banana']} defaultValue=\"apple\">\n          <Combobox.Input />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: string) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n\n      expect(input.value).toBe('apple');\n\n      await user.clear(input);\n\n      expect(input.value).toBe('');\n\n      await user.type(input, 'a');\n      await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n\n      const options = screen.getAllByRole('option');\n      options.forEach((opt) => {\n        expect(opt).not.toHaveAttribute('aria-selected', 'true');\n      });\n    });\n\n    it('should open popup on typing when enabled', async () => {\n      const { user } = await render(\n        <Combobox.Root>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"apple\">apple</Combobox.Item>\n                  <Combobox.Item value=\"banana\">banana</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      await user.type(input, 'a');\n\n      expect(screen.getByRole('listbox')).not.toBe(null);\n    });\n\n    it('should handle multiple selection with chips when disabled', async () => {\n      const { user } = await render(\n        <Combobox.Root multiple disabled defaultValue={['apple']}>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Chips>\n            <Combobox.Chip data-testid=\"chip\">\n              apple\n              <Combobox.ChipRemove data-testid=\"remove\" />\n            </Combobox.Chip>\n          </Combobox.Chips>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"apple\">apple</Combobox.Item>\n                  <Combobox.Item value=\"banana\">banana</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      const chip = screen.getByTestId('chip');\n      const remove = screen.getByTestId('remove');\n\n      expect(input).toHaveAttribute('disabled');\n      expect(chip).toHaveAttribute('aria-disabled', 'true');\n      expect(remove).toHaveAttribute('aria-disabled', 'true');\n\n      await user.type(input, '{backspace}');\n      expect(screen.getByTestId('chip')).not.toBe(null);\n    });\n\n    it('should handle multiple selection with chips when readOnly', async () => {\n      const { user } = await render(\n        <Combobox.Root multiple readOnly defaultValue={['apple']}>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Chips>\n            <Combobox.Chip data-testid=\"chip\">\n              apple\n              <Combobox.ChipRemove data-testid=\"remove\" />\n            </Combobox.Chip>\n          </Combobox.Chips>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"apple\">apple</Combobox.Item>\n                  <Combobox.Item value=\"banana\">banana</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      const chip = screen.getByTestId('chip');\n\n      expect(input).toHaveAttribute('aria-readonly', 'true');\n      expect(chip).toHaveAttribute('aria-readonly', 'true');\n\n      await user.type(input, '{backspace}');\n      expect(screen.getByTestId('chip')).not.toBe(null);\n    });\n\n    it('should move focus to clear button when pressing Escape and popup is closed', async () => {\n      const { user } = await render(\n        <Combobox.Root items={['apple', 'banana']} defaultValue=\"apple\">\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Clear data-testid=\"clear\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"apple\">apple</Combobox.Item>\n                  <Combobox.Item value=\"banana\">banana</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n\n      input.focus();\n      await user.keyboard('{Escape}');\n\n      await waitFor(() => {\n        expect(input.value).toBe('');\n      });\n\n      await user.type(input, 'a');\n\n      await waitFor(() => {\n        expect(screen.getByRole('listbox')).not.toBe(null);\n      });\n      await user.click(screen.getByRole('option', { name: 'apple' }));\n\n      await user.type(input, 'a');\n      await waitFor(() => {\n        expect(screen.getByRole('listbox')).not.toBe(null);\n      });\n\n      await user.keyboard('{Escape}');\n\n      await waitFor(() => {\n        expect(input.value).toBe('apple');\n      });\n      expect(screen.queryByRole('listbox')).toBe(null);\n\n      await user.keyboard('{Escape}');\n\n      await waitFor(() => {\n        expect(input.value).toBe('');\n      });\n    });\n\n    it('pressing Home moves caret to start', async () => {\n      const { user } = await render(\n        <Combobox.Root>\n          <Combobox.Input />\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n\n      input.focus();\n      await user.type(input, 'banana');\n      expect(input.value).toBe('banana');\n\n      await user.keyboard('{Home}');\n\n      expect(input.selectionStart).toBe(0);\n      expect(input.selectionEnd).toBe(0);\n    });\n\n    it('pressing End moves caret to end', async () => {\n      const { user } = await render(\n        <Combobox.Root>\n          <Combobox.Input />\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n\n      input.focus();\n      await user.type(input, 'apple');\n      expect(input.value).toBe('apple');\n\n      await user.keyboard('{End}');\n\n      expect(input.selectionStart).toBe(input.value.length);\n      expect(input.selectionEnd).toBe(input.value.length);\n    });\n\n    it.skipIf(isJSDOM)(\n      'scrolls to the start and end when pressing Home/End on overflowing input',\n      async () => {\n        const { user } = await render(\n          <Combobox.Root>\n            <Combobox.Input style={{ width: 64, fontSize: 20 }} />\n          </Combobox.Root>,\n        );\n\n        const input = screen.getByRole<HTMLInputElement>('combobox');\n        input.focus();\n\n        await user.type(input, 'this is a very long combobox value');\n\n        expect(input.scrollWidth).toBeGreaterThan(input.clientWidth);\n\n        const expectedScroll = input.scrollWidth - input.clientWidth;\n\n        expect(expectedScroll).toBeGreaterThan(0);\n\n        input.scrollLeft = expectedScroll;\n        input.setSelectionRange(input.value.length, input.value.length);\n\n        await user.keyboard('{Home}');\n        expect(input.selectionStart).toBe(0);\n        expect(input.selectionEnd).toBe(0);\n        expect(input.scrollLeft).toBe(0);\n\n        await user.keyboard('{End}');\n        expect(input.selectionStart).toBe(input.value.length);\n        expect(input.selectionEnd).toBe(input.value.length);\n        expect(Math.abs(input.scrollLeft - expectedScroll)).toBeLessThanOrEqual(2);\n      },\n    );\n\n    it('preserves caret position when controlled and inserting in the middle', async () => {\n      function Controlled() {\n        const [value, setValue] = React.useState('');\n        return (\n          <Combobox.Root inputValue={value} onInputValueChange={setValue}>\n            <Combobox.Input />\n          </Combobox.Root>\n        );\n      }\n\n      const { user } = await render(<Controlled />);\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n\n      await user.type(input, 'abcd');\n      expect(input.value).toBe('abcd');\n\n      // Move caret left twice to position after \"ab\"\n      await user.keyboard('{ArrowLeft}{ArrowLeft}');\n      expect(input.selectionStart).toBe(2);\n      expect(input.selectionEnd).toBe(2);\n\n      await user.keyboard('xxx');\n      expect(input.value).toBe('abxxxcd');\n      expect(input.selectionStart).toBe(5);\n      expect(input.selectionEnd).toBe(5);\n\n      await user.keyboard('y');\n      expect(input.value).toBe('abxxxycd');\n      expect(input.selectionStart).toBe(6);\n      expect(input.selectionEnd).toBe(6);\n    });\n\n    it('closes the popup when tabbing out', async () => {\n      const { user } = await render(\n        <div>\n          <Combobox.Root>\n            <Combobox.Input />\n            <Combobox.Portal>\n              <Combobox.Positioner>\n                <Combobox.Popup>\n                  <Combobox.List>\n                    <Combobox.Item value=\"apple\">apple</Combobox.Item>\n                    <Combobox.Item value=\"banana\">banana</Combobox.Item>\n                  </Combobox.List>\n                </Combobox.Popup>\n              </Combobox.Positioner>\n            </Combobox.Portal>\n          </Combobox.Root>\n          <button type=\"button\" data-testid=\"button\">\n            button\n          </button>\n        </div>,\n      );\n\n      const input = screen.getByRole('combobox');\n      const button = screen.getByTestId('button');\n\n      await user.click(input);\n\n      await waitFor(() => {\n        expect(screen.getByRole('listbox')).not.toBe(null);\n      });\n\n      await user.tab();\n\n      await waitFor(() => {\n        expect(button).toHaveFocus();\n      });\n      expect(screen.queryByRole('listbox')).toBe(null);\n\n      await user.click(input);\n\n      await waitFor(() => {\n        expect(screen.getByRole('listbox')).not.toBe(null);\n      });\n\n      await user.tab();\n\n      await waitFor(() => {\n        expect(button).toHaveFocus();\n      });\n      expect(screen.queryByRole('listbox')).toBe(null);\n    });\n  });\n\n  describe('data state attributes', () => {\n    it.skipIf(isJSDOM)('sets data-popup-side to the current popup side', async () => {\n      const { user } = await render(\n        <Combobox.Root items={['apple']}>\n          <Combobox.Input />\n          <Combobox.Portal>\n            <Combobox.Positioner side=\"right\">\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: string) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole('combobox');\n      expect(input).not.toHaveAttribute('data-popup-side');\n\n      await user.click(input);\n\n      await waitFor(() => expect(screen.queryByRole('listbox')).not.toBe(null));\n      expect(input).toHaveAttribute('data-popup-side', 'right');\n\n      await user.click(document.body);\n\n      await waitFor(() => expect(screen.queryByRole('listbox')).toBe(null));\n      expect(input).not.toHaveAttribute('data-popup-side');\n    });\n\n    it('toggles data-empty when the filtered list is empty', async () => {\n      const { user } = await render(\n        <Combobox.Root items={[]}>\n          <Combobox.Input />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List />\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole('combobox');\n\n      await user.click(input);\n\n      await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n      expect(input).toHaveAttribute('data-list-empty');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/combobox/input/ComboboxInput.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStore } from '@base-ui/utils/store';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { isAndroid, isFirefox } from '@base-ui/utils/detectBrowser';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { useBaseUiId } from '../../utils/useBaseUiId';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport {\n  useComboboxDerivedItemsContext,\n  useComboboxInputValueContext,\n  useComboboxRootContext,\n} from '../root/ComboboxRootContext';\nimport { triggerStateAttributesMapping } from '../utils/stateAttributesMapping';\nimport { selectors } from '../store';\nimport type { FieldRootState } from '../../field/root/FieldRoot';\nimport { useFieldRootContext } from '../../field/root/FieldRootContext';\nimport { DEFAULT_FIELD_STATE_ATTRIBUTES } from '../../field/utils/constants';\nimport { useLabelableContext } from '../../labelable-provider/LabelableContext';\nimport { useComboboxChipsContext } from '../chips/ComboboxChipsContext';\nimport { stopEvent } from '../../floating-ui-react/utils';\nimport { useComboboxPositionerContext } from '../positioner/ComboboxPositionerContext';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport type { Side } from '../../utils/useAnchorPositioning';\nimport { useDirection } from '../../direction-provider/DirectionContext';\nimport { resolveAriaLabelledBy } from '../../utils/resolveAriaLabelledBy';\nimport { ComboboxInternalDismissButton } from '../utils/ComboboxInternalDismissButton';\n\n/**\n * A text input to search for items in the list.\n * Renders an `<input>` element.\n */\nexport const ComboboxInput = React.forwardRef(function ComboboxInput(\n  componentProps: ComboboxInput.Props,\n  forwardedRef: React.ForwardedRef<HTMLInputElement>,\n) {\n  const {\n    render,\n    className,\n    disabled: disabledProp = false,\n    id: idProp,\n    ...elementProps\n  } = componentProps;\n\n  const {\n    state: fieldState,\n    disabled: fieldDisabled,\n    setTouched,\n    setFocused,\n    validationMode,\n    validation,\n  } = useFieldRootContext();\n  const { labelId: fieldLabelId } = useLabelableContext();\n  const comboboxChipsContext = useComboboxChipsContext();\n  const positioning = useComboboxPositionerContext(true);\n  const hasPositionerParent = Boolean(positioning);\n  const store = useComboboxRootContext();\n  const { filteredItems } = useComboboxDerivedItemsContext();\n  // `inputValue` can't be placed in the store.\n  // https://github.com/mui/base-ui/issues/2703\n  const inputValue = useComboboxInputValueContext();\n  const direction = useDirection();\n\n  const required = useStore(store, selectors.required);\n  const comboboxDisabled = useStore(store, selectors.disabled);\n  const readOnly = useStore(store, selectors.readOnly);\n  const name = useStore(store, selectors.name);\n  const selectionMode = useStore(store, selectors.selectionMode);\n  const autoHighlightMode = useStore(store, selectors.autoHighlight);\n  const inputProps = useStore(store, selectors.inputProps);\n  const triggerProps = useStore(store, selectors.triggerProps);\n  const open = useStore(store, selectors.open);\n  const mounted = useStore(store, selectors.mounted);\n  const selectedValue = useStore(store, selectors.selectedValue);\n  const popupSideValue = useStore(store, selectors.popupSide);\n  const positionerElement = useStore(store, selectors.positionerElement);\n  const rootId = useStore(store, selectors.id);\n  const inline = useStore(store, selectors.inline);\n  const modal = useStore(store, selectors.modal);\n\n  const autoHighlightEnabled = Boolean(autoHighlightMode);\n  const popupSide = mounted && positionerElement ? popupSideValue : null;\n  const disabled = fieldDisabled || comboboxDisabled || disabledProp;\n  const listEmpty = filteredItems.length === 0;\n\n  const isInsidePopup = hasPositionerParent || inline;\n  const focusManagerModal = !isInsidePopup || modal;\n  const id = useBaseUiId(idProp ?? (!isInsidePopup ? rootId : undefined));\n  const ariaLabelledBy = resolveAriaLabelledBy(fieldLabelId, undefined);\n  const fieldStateForInput = hasPositionerParent ? DEFAULT_FIELD_STATE_ATTRIBUTES : fieldState;\n\n  const [composingValue, setComposingValue] = React.useState<string | null>(null);\n  const isComposingRef = React.useRef(false);\n  const lastActiveIndexRef = React.useRef<number | null>(null);\n  const shouldRestoreActiveIndexRef = React.useRef(false);\n\n  const setInputElement = useStableCallback((element: HTMLInputElement | null) => {\n    const nextIsInsidePopup = hasPositionerParent || store.state.inline;\n\n    if (nextIsInsidePopup && !store.state.hasInputValue) {\n      store.state.setInputValue('', createChangeEventDetails(REASONS.none));\n    }\n\n    store.update({\n      inputElement: element,\n      inputInsidePopup: nextIsInsidePopup,\n    });\n  });\n\n  const validationProps =\n    hasPositionerParent || !validation ? elementProps : validation.getValidationProps(elementProps);\n\n  const state: ComboboxInputState = {\n    ...fieldStateForInput,\n    open,\n    disabled,\n    readOnly,\n    popupSide,\n    listEmpty,\n  };\n\n  function handleKeyDown(event: React.KeyboardEvent<HTMLInputElement>) {\n    if (!comboboxChipsContext) {\n      return undefined;\n    }\n\n    let nextIndex: number | undefined;\n\n    const { highlightedChipIndex } = comboboxChipsContext;\n\n    if (highlightedChipIndex !== undefined) {\n      if (event.key === 'ArrowLeft') {\n        event.preventDefault();\n        if (highlightedChipIndex > 0) {\n          nextIndex = highlightedChipIndex - 1;\n        } else {\n          nextIndex = undefined;\n        }\n      } else if (event.key === 'ArrowRight') {\n        event.preventDefault();\n        if (highlightedChipIndex < selectedValue.length - 1) {\n          nextIndex = highlightedChipIndex + 1;\n        } else {\n          nextIndex = undefined;\n        }\n      } else if (event.key === 'Backspace' || event.key === 'Delete') {\n        event.preventDefault();\n        // Move highlight appropriately after removal.\n        const computedNextIndex =\n          highlightedChipIndex >= selectedValue.length - 1\n            ? selectedValue.length - 2\n            : highlightedChipIndex;\n        // If the computed index is negative, treat it as no highlight.\n        nextIndex = computedNextIndex >= 0 ? computedNextIndex : undefined;\n        store.state.setIndices({ activeIndex: null, selectedIndex: null, type: 'keyboard' });\n      }\n      return nextIndex;\n    }\n\n    // Handle navigation when no chip is highlighted\n    if (\n      event.key === 'ArrowLeft' &&\n      (event.currentTarget.selectionStart ?? 0) === 0 &&\n      selectedValue.length > 0\n    ) {\n      event.preventDefault();\n      const lastChipIndex = Math.max(selectedValue.length - 1, 0);\n      nextIndex = lastChipIndex;\n    } else if (\n      event.key === 'Backspace' &&\n      event.currentTarget.value === '' &&\n      selectedValue.length > 0\n    ) {\n      store.state.setIndices({ activeIndex: null, selectedIndex: null, type: 'keyboard' });\n      event.preventDefault();\n    }\n\n    return nextIndex;\n  }\n\n  const element = useRenderElement('input', componentProps, {\n    state,\n    ref: [forwardedRef, store.state.inputRef, setInputElement],\n    props: [\n      inputProps,\n      triggerProps,\n      {\n        type: 'text',\n        value: componentProps.value ?? composingValue ?? inputValue,\n        'aria-readonly': readOnly || undefined,\n        'aria-required': required || undefined,\n        'aria-labelledby': ariaLabelledBy,\n        disabled,\n        readOnly,\n        required: selectionMode === 'none' ? required : undefined,\n        ...(selectionMode === 'none' && name && { name }),\n        id,\n        onFocus() {\n          setFocused(true);\n\n          if (!inline || !shouldRestoreActiveIndexRef.current) {\n            return;\n          }\n\n          shouldRestoreActiveIndexRef.current = false;\n          const nextActiveIndex = lastActiveIndexRef.current;\n\n          if (\n            nextActiveIndex == null ||\n            // `valuesRef` can be sparse, so guard against restoring a removed slot.\n            !Object.hasOwn(store.state.valuesRef.current, nextActiveIndex)\n          ) {\n            return;\n          }\n\n          store.state.setIndices({ activeIndex: nextActiveIndex });\n        },\n        onBlur() {\n          setTouched(true);\n          setFocused(false);\n\n          const activeIndex = store.state.activeIndex;\n          if (inline && activeIndex !== null && autoHighlightMode !== 'always') {\n            lastActiveIndexRef.current = activeIndex;\n            shouldRestoreActiveIndexRef.current = true;\n            store.state.setIndices({ activeIndex: null });\n          }\n\n          if (validationMode === 'onBlur') {\n            const valueToValidate = selectionMode === 'none' ? inputValue : selectedValue;\n            validation.commit(valueToValidate);\n          }\n        },\n        onCompositionStart(event) {\n          if (isAndroid) {\n            return;\n          }\n          isComposingRef.current = true;\n          setComposingValue(event.currentTarget.value);\n        },\n        onCompositionEnd(event) {\n          isComposingRef.current = false;\n          const next = event.currentTarget.value;\n          setComposingValue(null);\n          store.state.setInputValue(\n            next,\n            createChangeEventDetails(REASONS.inputChange, event.nativeEvent),\n          );\n        },\n        onChange(event) {\n          // Autofill may not provide `inputType` (Chrome) or may report\n          // `insertReplacementText` (Firefox).\n          const inputType = (event.nativeEvent as InputEvent).inputType;\n          const autofillLikeInput = !inputType || inputType === 'insertReplacementText';\n          const shouldOpenOnInput = isComposingRef.current || !autofillLikeInput;\n\n          // During IME composition, avoid propagating controlled updates to prevent\n          // filtering the options prematurely so `Empty` won't show incorrectly.\n          // We can't rely on this check for Android due to how it handles composition\n          // events with some keyboards (e.g. Samsung keyboard with predictive text on\n          // treats all text as always-composing).\n          // https://github.com/mui/base-ui/issues/2942\n          if (isComposingRef.current) {\n            const nextVal = event.currentTarget.value;\n            setComposingValue(nextVal);\n\n            if (nextVal === '' && !store.state.openOnInputClick && !store.state.inputInsidePopup) {\n              store.state.setOpen(\n                false,\n                createChangeEventDetails(REASONS.inputClear, event.nativeEvent),\n              );\n            }\n\n            const trimmed = nextVal.trim();\n            const shouldMaintainHighlight = autoHighlightEnabled && trimmed !== '';\n\n            if (!readOnly && !disabled && trimmed) {\n              if (shouldOpenOnInput) {\n                store.state.setOpen(\n                  true,\n                  createChangeEventDetails(REASONS.inputChange, event.nativeEvent),\n                );\n                if (!autoHighlightEnabled) {\n                  store.state.setIndices({\n                    activeIndex: null,\n                    selectedIndex: null,\n                    type: store.state.keyboardActiveRef.current ? 'keyboard' : 'pointer',\n                  });\n                }\n              }\n            }\n\n            if (open && store.state.activeIndex !== null && !shouldMaintainHighlight) {\n              store.state.setIndices({\n                activeIndex: null,\n                selectedIndex: null,\n                type: store.state.keyboardActiveRef.current ? 'keyboard' : 'pointer',\n              });\n            }\n\n            return;\n          }\n\n          store.state.setInputValue(\n            event.currentTarget.value,\n            createChangeEventDetails(REASONS.inputChange, event.nativeEvent),\n          );\n\n          const empty = event.currentTarget.value === '';\n          const clearDetails = createChangeEventDetails(REASONS.inputClear, event.nativeEvent);\n\n          if (empty && !store.state.inputInsidePopup) {\n            if (selectionMode === 'single') {\n              store.state.setSelectedValue(null, clearDetails);\n            }\n\n            if (!store.state.openOnInputClick) {\n              store.state.setOpen(false, clearDetails);\n            }\n          }\n\n          const trimmed = event.currentTarget.value.trim();\n          if (!readOnly && !disabled && trimmed) {\n            if (shouldOpenOnInput) {\n              store.state.setOpen(\n                true,\n                createChangeEventDetails(REASONS.inputChange, event.nativeEvent),\n              );\n              // When autoHighlight is enabled, keep the highlight (will be set to 0 in root).\n              if (!autoHighlightEnabled) {\n                store.state.setIndices({\n                  activeIndex: null,\n                  selectedIndex: null,\n                  type: store.state.keyboardActiveRef.current ? 'keyboard' : 'pointer',\n                });\n              }\n            }\n          }\n\n          // When the user types, ensure the list resets its highlight so that\n          // virtual focus returns to the input (aria-activedescendant is\n          // cleared).\n          if (open && store.state.activeIndex !== null && !autoHighlightEnabled) {\n            store.state.setIndices({\n              activeIndex: null,\n              selectedIndex: null,\n              type: store.state.keyboardActiveRef.current ? 'keyboard' : 'pointer',\n            });\n          }\n        },\n        onKeyDown(event) {\n          if (disabled || readOnly) {\n            return;\n          }\n\n          if (event.ctrlKey || event.shiftKey || event.altKey || event.metaKey) {\n            return;\n          }\n\n          store.state.keyboardActiveRef.current = true;\n          const input = event.currentTarget;\n          const scrollAmount = input.scrollWidth - input.clientWidth;\n          const isRTL = direction === 'rtl';\n\n          if (event.key === 'Home') {\n            stopEvent(event);\n            const cursor = isFirefox && isRTL ? input.value.length : 0;\n            input.setSelectionRange(cursor, cursor);\n            input.scrollLeft = 0;\n            return;\n          }\n\n          if (event.key === 'End') {\n            stopEvent(event);\n            const cursor = isFirefox && isRTL ? 0 : input.value.length;\n            input.setSelectionRange(cursor, cursor);\n            input.scrollLeft = isRTL ? -scrollAmount : scrollAmount;\n            return;\n          }\n\n          if (!mounted && event.key === 'Escape') {\n            const isClear =\n              selectionMode === 'multiple' && Array.isArray(selectedValue)\n                ? selectedValue.length === 0\n                : selectedValue === null;\n\n            const details = createChangeEventDetails(REASONS.escapeKey, event.nativeEvent);\n            const value = selectionMode === 'multiple' ? [] : null;\n            store.state.setInputValue('', details);\n            store.state.setSelectedValue(value, details);\n\n            if (!isClear && !store.state.inline && !details.isPropagationAllowed) {\n              event.stopPropagation();\n            }\n\n            return;\n          }\n\n          // Handle deletion when no chip is highlighted and the input is empty.\n          if (\n            comboboxChipsContext &&\n            event.key === 'Backspace' &&\n            input.value === '' &&\n            comboboxChipsContext.highlightedChipIndex === undefined &&\n            Array.isArray(selectedValue) &&\n            selectedValue.length > 0\n          ) {\n            const newValue = selectedValue.slice(0, -1);\n            // If the removed item was also the active (highlighted) item, clear highlight\n            store.state.setIndices({\n              activeIndex: null,\n              selectedIndex: null,\n              type: store.state.keyboardActiveRef.current ? 'keyboard' : 'pointer',\n            });\n            store.state.setSelectedValue(\n              newValue,\n              createChangeEventDetails(REASONS.none, event.nativeEvent),\n            );\n            return;\n          }\n\n          const hadHighlightedChip = comboboxChipsContext?.highlightedChipIndex !== undefined;\n          const nextIndex = handleKeyDown(event);\n\n          comboboxChipsContext?.setHighlightedChipIndex(nextIndex);\n\n          if (nextIndex !== undefined) {\n            comboboxChipsContext?.chipsRef.current[nextIndex]?.focus();\n          } else if (hadHighlightedChip) {\n            store.state.inputRef.current?.focus();\n          }\n\n          // event.isComposing\n          if (event.which === 229) {\n            return;\n          }\n\n          if (event.key === 'Enter' && open) {\n            const activeIndex = store.state.activeIndex;\n            const nativeEvent = event.nativeEvent;\n\n            if (activeIndex === null) {\n              if (inline) {\n                return;\n              }\n\n              // Allow form submission when no item is highlighted.\n              store.state.setOpen(false, createChangeEventDetails(REASONS.none, nativeEvent));\n              return;\n            }\n\n            stopEvent(event);\n\n            const listItem = store.state.listRef.current[activeIndex];\n\n            if (listItem) {\n              store.state.selectionEventRef.current = nativeEvent;\n              listItem.click();\n              store.state.selectionEventRef.current = null;\n            }\n          }\n        },\n        onPointerMove() {\n          store.state.keyboardActiveRef.current = false;\n        },\n        onPointerDown() {\n          store.state.keyboardActiveRef.current = false;\n        },\n      },\n      validationProps,\n    ],\n    stateAttributesMapping: triggerStateAttributesMapping,\n  });\n\n  return (\n    <React.Fragment>\n      {open && focusManagerModal && (\n        <ComboboxInternalDismissButton ref={store.state.startDismissRef} />\n      )}\n      {element}\n    </React.Fragment>\n  );\n});\n\nexport interface ComboboxInputState extends FieldRootState {\n  /**\n   * Whether the corresponding popup is open.\n   */\n  open: boolean;\n  /**\n   * Indicates which side the corresponding popup is positioned relative to its anchor.\n   */\n  popupSide: Side | null;\n  /**\n   * Present when the corresponding items list is empty.\n   */\n  listEmpty: boolean;\n  /**\n   * Whether the component should ignore user edits.\n   */\n  readOnly: boolean;\n}\n\nexport interface ComboboxInputProps extends BaseUIComponentProps<'input', ComboboxInputState> {\n  /**\n   * Whether the component should ignore user interaction.\n   * @default false\n   */\n  disabled?: boolean | undefined;\n}\n\nexport namespace ComboboxInput {\n  export type State = ComboboxInputState;\n  export type Props = ComboboxInputProps;\n}\n"
  },
  {
    "path": "packages/react/src/combobox/input/ComboboxInputDataAttributes.ts",
    "content": "import { CommonTriggerDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum ComboboxInputDataAttributes {\n  /**\n   * Present when the corresponding popup is open.\n   */\n  popupOpen = CommonTriggerDataAttributes.popupOpen,\n  /**\n   * Present when the input is pressed.\n   */\n  pressed = CommonTriggerDataAttributes.pressed,\n  /**\n   * Present when the component is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the component is readonly.\n   */\n  readonly = 'data-readonly',\n  /**\n   * Indicates which side the corresponding popup is positioned relative to its anchor.\n   * @type {'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start' | null}\n   */\n  popupSide = 'data-popup-side',\n  /**\n   * Present when the component is required.\n   */\n  required = 'data-required',\n  /**\n   * Present when the component is in valid state (when wrapped in Field.Root).\n   */\n  valid = 'data-valid',\n  /**\n   * Present when the component is in invalid state (when wrapped in Field.Root).\n   */\n  invalid = 'data-invalid',\n  /**\n   * Present when the component has been touched (when wrapped in Field.Root).\n   */\n  touched = 'data-touched',\n  /**\n   * Present when the component's value has changed (when wrapped in Field.Root).\n   */\n  dirty = 'data-dirty',\n  /**\n   * Present when the component has a value (when wrapped in Field.Root).\n   */\n  filled = 'data-filled',\n  /**\n   * Present when the input is focused (when wrapped in Field.Root).\n   */\n  focused = 'data-focused',\n  /**\n   * Present when the corresponding items list is empty.\n   */\n  listEmpty = 'data-list-empty',\n}\n"
  },
  {
    "path": "packages/react/src/combobox/input-group/ComboboxInputGroup.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { Combobox } from '@base-ui/react/combobox';\nimport { Field } from '@base-ui/react/field';\nimport { createRenderer, describeConformance } from '#test-utils';\nimport { fireEvent, screen } from '@mui/internal-test-utils';\n\ndescribe('<Combobox.InputGroup />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Combobox.InputGroup />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(<Combobox.Root items={['a']}>{node}</Combobox.Root>);\n    },\n  }));\n\n  it('should not dismiss the popup when clicking inside the input group', async () => {\n    const { user } = await render(\n      <Combobox.Root items={['a', 'b']}>\n        <Combobox.InputGroup style={{ padding: 10 }}>\n          <span data-testid=\"pad\">padding</span>\n          <Combobox.Input />\n          <Combobox.Trigger>Open</Combobox.Trigger>\n        </Combobox.InputGroup>\n\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup>\n              <Combobox.List>\n                <Combobox.Item value=\"a\">a</Combobox.Item>\n                <Combobox.Item value=\"b\">b</Combobox.Item>\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    await user.click(screen.getByRole('button', { name: 'Open' }));\n    expect(await screen.findByRole('listbox')).not.toBe(null);\n\n    await user.click(screen.getByTestId('pad'));\n    expect(screen.queryByRole('listbox')).not.toBe(null);\n  });\n\n  it('focuses the input and opens when clicking input-group padding around chips', async () => {\n    await render(\n      <Combobox.Root items={['a', 'b']} multiple defaultValue={['a']}>\n        <Combobox.InputGroup data-testid=\"group\" style={{ padding: 10 }}>\n          <Combobox.Chips>\n            <Combobox.Chip>a</Combobox.Chip>\n            <Combobox.Input data-testid=\"input\" />\n          </Combobox.Chips>\n        </Combobox.InputGroup>\n\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup>\n              <Combobox.List>\n                <Combobox.Item value=\"a\">a</Combobox.Item>\n                <Combobox.Item value=\"b\">b</Combobox.Item>\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    const group = screen.getByTestId('group');\n    const input = screen.getByTestId('input');\n\n    fireEvent.mouseDown(group);\n\n    expect(input).toHaveFocus();\n    expect(screen.queryByRole('listbox')).not.toBe(null);\n  });\n\n  it('does not handle chip presses a second time when chips are nested inside the input group', async () => {\n    const onOpenChange = vi.fn();\n\n    await render(\n      <Combobox.Root items={['a', 'b']} multiple defaultValue={['a']} onOpenChange={onOpenChange}>\n        <Combobox.InputGroup>\n          <Combobox.Chips>\n            <Combobox.Chip data-testid=\"chip\">a</Combobox.Chip>\n            <Combobox.Input />\n          </Combobox.Chips>\n        </Combobox.InputGroup>\n\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup>\n              <Combobox.List>\n                <Combobox.Item value=\"a\">a</Combobox.Item>\n                <Combobox.Item value=\"b\">b</Combobox.Item>\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    fireEvent.mouseDown(screen.getByTestId('chip'));\n    expect(onOpenChange).toHaveBeenCalledTimes(1);\n  });\n\n  it('focuses the input without opening when openOnInputClick is false', async () => {\n    await render(\n      <Combobox.Root items={['a', 'b']} multiple openOnInputClick={false} defaultValue={['a']}>\n        <Combobox.InputGroup data-testid=\"group\" style={{ padding: 10 }}>\n          <Combobox.Chips>\n            <Combobox.Chip>a</Combobox.Chip>\n            <Combobox.Input data-testid=\"input\" />\n          </Combobox.Chips>\n        </Combobox.InputGroup>\n\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup>\n              <Combobox.List>\n                <Combobox.Item value=\"a\">a</Combobox.Item>\n                <Combobox.Item value=\"b\">b</Combobox.Item>\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    fireEvent.mouseDown(screen.getByTestId('group'));\n\n    expect(screen.getByTestId('input')).toHaveFocus();\n    expect(screen.queryByRole('listbox')).toBe(null);\n  });\n\n  it('does not focus or open when disabled by Field.Root', async () => {\n    await render(\n      <Field.Root disabled>\n        <Combobox.Root items={['a', 'b']} multiple defaultValue={['a']}>\n          <Combobox.InputGroup data-testid=\"group\" style={{ padding: 10 }}>\n            <Combobox.Chips>\n              <Combobox.Chip>a</Combobox.Chip>\n              <Combobox.Input data-testid=\"input\" />\n            </Combobox.Chips>\n          </Combobox.InputGroup>\n\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                  <Combobox.Item value=\"b\">b</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>\n      </Field.Root>,\n    );\n\n    fireEvent.mouseDown(screen.getByTestId('group'));\n\n    expect(screen.getByTestId('input')).not.toHaveFocus();\n    expect(screen.queryByRole('listbox')).toBe(null);\n  });\n\n  it('does not focus or open when readOnly', async () => {\n    await render(\n      <Combobox.Root items={['a', 'b']} multiple readOnly defaultValue={['a']}>\n        <Combobox.InputGroup data-testid=\"group\" style={{ padding: 10 }}>\n          <Combobox.Chips>\n            <Combobox.Chip>a</Combobox.Chip>\n            <Combobox.Input data-testid=\"input\" />\n          </Combobox.Chips>\n        </Combobox.InputGroup>\n\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup>\n              <Combobox.List>\n                <Combobox.Item value=\"a\">a</Combobox.Item>\n                <Combobox.Item value=\"b\">b</Combobox.Item>\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    fireEvent.mouseDown(screen.getByTestId('group'));\n\n    expect(screen.getByTestId('input')).not.toHaveFocus();\n    expect(screen.queryByRole('listbox')).toBe(null);\n  });\n\n  it('has role prop', async () => {\n    await render(\n      <Combobox.Root items={['a']}>\n        <Combobox.InputGroup />\n      </Combobox.Root>,\n    );\n\n    expect(screen.queryByRole('group')).not.toBe(null);\n  });\n});\n"
  },
  {
    "path": "packages/react/src/combobox/input-group/ComboboxInputGroup.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useStore } from '@base-ui/utils/store';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useFieldRootContext } from '../../field/root/FieldRootContext';\nimport type { FieldRoot } from '../../field/root/FieldRoot';\nimport {\n  useComboboxDerivedItemsContext,\n  useComboboxRootContext,\n} from '../root/ComboboxRootContext';\nimport { selectors } from '../store';\nimport type { Side } from '../../utils/useAnchorPositioning';\nimport { triggerStateAttributesMapping } from '../utils/stateAttributesMapping';\nimport { handleInputPress } from '../utils/handleInputPress';\nimport { contains } from '../../floating-ui-react/utils/element';\n\n/**\n * A wrapper for the input and its associated controls.\n * Renders a `<div>` element.\n */\nexport const ComboboxInputGroup = React.forwardRef(function ComboboxInputGroup(\n  componentProps: ComboboxInputGroup.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, ...elementProps } = componentProps;\n\n  const { state: fieldState } = useFieldRootContext();\n  const store = useComboboxRootContext();\n  const { filteredItems } = useComboboxDerivedItemsContext();\n\n  const open = useStore(store, selectors.open);\n  const mounted = useStore(store, selectors.mounted);\n  const popupSideValue = useStore(store, selectors.popupSide);\n  const positionerElement = useStore(store, selectors.positionerElement);\n  const comboboxDisabled = useStore(store, selectors.disabled);\n  const readOnly = useStore(store, selectors.readOnly);\n  const hasSelectedValue = useStore(store, selectors.hasSelectedValue);\n  const selectionMode = useStore(store, selectors.selectionMode);\n\n  const popupSide = mounted && positionerElement ? popupSideValue : null;\n  const disabled = comboboxDisabled;\n  const listEmpty = filteredItems.length === 0;\n  const placeholder = selectionMode === 'none' ? false : !hasSelectedValue;\n\n  const state: ComboboxInputGroup.State = {\n    ...fieldState,\n    open,\n    disabled,\n    readOnly,\n    popupSide,\n    listEmpty,\n    placeholder,\n  };\n\n  const setInputGroupElement = useStableCallback((element: HTMLDivElement | null) => {\n    store.set('inputGroupElement', element);\n  });\n\n  return useRenderElement('div', componentProps, {\n    ref: [forwardedRef, setInputGroupElement],\n    props: [\n      {\n        role: 'group',\n        onMouseDown(event) {\n          handleInputPress(event, store, disabled, readOnly, (target) => {\n            return contains(store.state.chipsContainerRef.current, target);\n          });\n        },\n      },\n      elementProps,\n    ],\n    state,\n    stateAttributesMapping: triggerStateAttributesMapping,\n  });\n});\n\nexport interface ComboboxInputGroupState extends FieldRoot.State {\n  /**\n   * Whether the corresponding popup is open.\n   */\n  open: boolean;\n  /**\n   * Whether the component should ignore user interaction.\n   */\n  disabled: boolean;\n  /**\n   * Whether the component should ignore user edits.\n   */\n  readOnly: boolean;\n  /**\n   * Indicates which side the corresponding popup is positioned relative to its anchor.\n   */\n  popupSide: Side | null;\n  /**\n   * Present when the corresponding items list is empty.\n   */\n  listEmpty: boolean;\n  /**\n   * Whether the combobox doesn't have a value.\n   */\n  placeholder: boolean;\n}\n\nexport interface ComboboxInputGroupProps extends BaseUIComponentProps<\n  'div',\n  ComboboxInputGroup.State\n> {}\n\nexport namespace ComboboxInputGroup {\n  export type State = ComboboxInputGroupState;\n  export type Props = ComboboxInputGroupProps;\n}\n"
  },
  {
    "path": "packages/react/src/combobox/input-group/ComboboxInputGroupDataAttributes.ts",
    "content": "export enum ComboboxInputGroupDataAttributes {\n  /**\n   * Present when the corresponding popup is open.\n   */\n  popupOpen = 'data-popup-open',\n  /**\n   * Present when the input group is pressed.\n   */\n  pressed = 'data-pressed',\n  /**\n   * Present when the component is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the component is readonly.\n   */\n  readonly = 'data-readonly',\n  /**\n   * Indicates which side the corresponding popup is positioned relative to its anchor.\n   * @type {'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start' | null}\n   */\n  popupSide = 'data-popup-side',\n  /**\n   * Present when the component is in valid state (when wrapped in Field.Root).\n   */\n  valid = 'data-valid',\n  /**\n   * Present when the component is in invalid state (when wrapped in Field.Root).\n   */\n  invalid = 'data-invalid',\n  /**\n   * Present when the component has been touched (when wrapped in Field.Root).\n   */\n  touched = 'data-touched',\n  /**\n   * Present when the component's value has changed (when wrapped in Field.Root).\n   */\n  dirty = 'data-dirty',\n  /**\n   * Present when the component has a value (when wrapped in Field.Root).\n   */\n  filled = 'data-filled',\n  /**\n   * Present when the component is focused (when wrapped in Field.Root).\n   */\n  focused = 'data-focused',\n  /**\n   * Present when the corresponding items list is empty.\n   */\n  listEmpty = 'data-list-empty',\n  /**\n   * Present when the combobox doesn't have a value.\n   */\n  placeholder = 'data-placeholder',\n}\n"
  },
  {
    "path": "packages/react/src/combobox/item/ComboboxItem.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport { Combobox } from '@base-ui/react/combobox';\nimport { fireEvent, flushMicrotasks, screen, waitFor } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\n\ndescribe('<Combobox.Item />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Combobox.Item />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(<Combobox.Root>{node}</Combobox.Root>);\n    },\n  }));\n\n  it('selects item and closes in single mode', async () => {\n    await render(\n      <Combobox.Root defaultOpen>\n        <Combobox.Input data-testid=\"input\" />\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup>\n              <Combobox.List>\n                <Combobox.Item value=\"one\">one</Combobox.Item>\n                <Combobox.Item value=\"two\">two</Combobox.Item>\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    const input = screen.getByTestId('input');\n    fireEvent.click(screen.getByRole('option', { name: 'two' }));\n    await flushMicrotasks();\n\n    expect(input).toHaveValue('two');\n    expect(screen.queryByRole('listbox')).toBe(null);\n  });\n\n  describe('prop: onClick', () => {\n    it('calls onClick when clicked with a pointer', async () => {\n      const handleClick = vi.fn();\n      const { user } = await render(\n        <Combobox.Root items={['apple', 'banana']} openOnInputClick>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: string) => (\n                    <Combobox.Item key={item} value={item} onClick={handleClick}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      await user.click(input);\n\n      const option = screen.getByRole('option', { name: 'banana' });\n      await user.click(option);\n\n      expect(handleClick.mock.calls.length).toBe(1);\n    });\n\n    it('calls onClick when selected with Enter key (via root interaction)', async () => {\n      const handleClick = vi.fn();\n      const { user } = await render(\n        <Combobox.Root items={['one', 'two']} openOnInputClick>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: string) => (\n                    <Combobox.Item key={item} value={item} onClick={handleClick}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      await user.click(input);\n      await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n\n      await user.keyboard('{ArrowDown}');\n      await user.keyboard('{Enter}');\n\n      expect(handleClick.mock.calls.length).toBe(1);\n    });\n\n    it('does not select the item when onClick prevents Base UI handler', async () => {\n      const handleClick = vi.fn((event) => event.preventBaseUIHandler());\n      const { user } = await render(\n        <Combobox.Root defaultOpen>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"one\" onClick={handleClick}>\n                    one\n                  </Combobox.Item>\n                  <Combobox.Item value=\"two\">two</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const option = screen.getByRole('option', { name: 'one' });\n      await user.click(option);\n      await flushMicrotasks();\n\n      const input = screen.getByTestId('input');\n      expect(handleClick.mock.calls.length).toBe(1);\n      expect(input).toHaveValue('');\n      expect(screen.queryByRole('listbox')).not.toBe(null);\n    });\n  });\n\n  it('does not select disabled item', async () => {\n    await render(\n      <Combobox.Root defaultOpen>\n        <Combobox.Input data-testid=\"input\" />\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup>\n              <Combobox.List>\n                <Combobox.Item value=\"one\">one</Combobox.Item>\n                <Combobox.Item value=\"two\" disabled>\n                  two\n                </Combobox.Item>\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    const input = screen.getByTestId('input');\n    fireEvent.click(screen.getByRole('option', { name: 'two' }));\n    await flushMicrotasks();\n\n    expect(input).toHaveValue('');\n  });\n\n  it('Enter selects highlighted item', async () => {\n    const { user } = await render(\n      <Combobox.Root>\n        <Combobox.Input data-testid=\"input\" />\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup>\n              <Combobox.List>\n                <Combobox.Item value=\"one\">one</Combobox.Item>\n                <Combobox.Item value=\"two\">two</Combobox.Item>\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    const input = screen.getByTestId('input');\n    await user.click(input);\n    await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n    await user.keyboard('{ArrowDown}');\n    await user.keyboard('{Enter}');\n\n    await waitFor(() => expect(input).toHaveValue('one'));\n    expect(screen.queryByRole('listbox')).toBe(null);\n  });\n\n  it('multiple mode toggles selection and stays open', async () => {\n    const { user } = await render(\n      <Combobox.Root multiple>\n        <Combobox.Input data-testid=\"input\" />\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup>\n              <Combobox.List>\n                <Combobox.Item value=\"a\">a</Combobox.Item>\n                <Combobox.Item value=\"b\">b</Combobox.Item>\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    const input = screen.getByTestId('input');\n    await user.click(input);\n    await waitFor(() => {\n      expect(screen.getByRole('listbox')).not.toBe(null);\n    });\n\n    const a = screen.getByRole('option', { name: 'a' });\n    await user.click(a);\n    expect(a).toHaveAttribute('aria-selected', 'true');\n    expect(screen.getByRole('listbox')).not.toBe(null);\n\n    await user.click(a);\n    expect(a).not.toHaveAttribute('aria-selected', 'true');\n  });\n\n  it('reflects selected value with aria-selected when reopening', async () => {\n    const { user } = await render(\n      <Combobox.Root defaultValue=\"two\">\n        <Combobox.Input data-testid=\"input\" />\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup>\n              <Combobox.List>\n                <Combobox.Item value=\"one\">one</Combobox.Item>\n                <Combobox.Item value=\"two\">two</Combobox.Item>\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    const input = screen.getByTestId('input');\n    await user.click(input);\n    await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n    await waitFor(() =>\n      expect(screen.getByRole('option', { name: 'two' })).toHaveAttribute('aria-selected', 'true'),\n    );\n  });\n\n  describe.skipIf(!isJSDOM)('link handling', () => {\n    it('clicking a link inside an item does not select or close (anchor with href)', async () => {\n      const { user } = await render(\n        <Combobox.Root defaultOpen>\n          <Combobox.Input />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item\n                    value=\"one\"\n                    render={<a href=\"/somewhere\" />}\n                    data-testid=\"link-one\"\n                  >\n                    one\n                  </Combobox.Item>\n                  <Combobox.Item value=\"two\">two</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n      const link = screen.getByTestId('link-one');\n\n      await user.click(link);\n\n      await waitFor(() => expect(input.value).toBe(''));\n      expect(screen.queryByRole('listbox')).not.toBe(null);\n    });\n\n    it('clicking a hash link inside an item does not select and closes popup (anchor with #hash)', async () => {\n      const { user } = await render(\n        <Combobox.Root defaultOpen>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"one\" render={<a href=\"#section\" />} data-testid=\"link-hash\">\n                    one\n                  </Combobox.Item>\n                  <Combobox.Item value=\"two\">two</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n      const link = screen.getByTestId('link-hash');\n\n      await user.click(link);\n\n      await waitFor(() => expect(input.value).toBe(''));\n      expect(screen.queryByRole('listbox')).toBe(null);\n    });\n\n    it('clicking an anchor without href behaves like normal item (selects and closes)', async () => {\n      const { user } = await render(\n        <Combobox.Root defaultOpen>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"one\" render={<a />} data-testid=\"anchor-no-href\">\n                    one\n                  </Combobox.Item>\n                  <Combobox.Item value=\"two\">two</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n\n      await user.click(screen.getByTestId('anchor-no-href'));\n\n      await waitFor(() => expect(input.value).toBe('one'));\n      expect(screen.queryByRole('listbox')).toBe(null);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/combobox/item/ComboboxItem.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport { useStore } from '@base-ui/utils/store';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport {\n  useComboboxRootContext,\n  useComboboxDerivedItemsContext,\n} from '../root/ComboboxRootContext';\nimport {\n  useCompositeListItem,\n  IndexGuessBehavior,\n} from '../../composite/list/useCompositeListItem';\nimport type { BaseUIComponentProps, HTMLProps, NonNativeButtonProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { ComboboxItemContext } from './ComboboxItemContext';\nimport { selectors } from '../store';\nimport { useButton } from '../../use-button';\nimport { useComboboxRowContext } from '../row/ComboboxRowContext';\nimport { compareItemEquality, findItemIndex } from '../../utils/itemEquality';\n\n/**\n * An individual item in the list.\n * Renders a `<div>` element.\n */\nexport const ComboboxItem = React.memo(\n  React.forwardRef(function ComboboxItem(\n    componentProps: ComboboxItem.Props,\n    forwardedRef: React.ForwardedRef<HTMLDivElement>,\n  ) {\n    const {\n      render,\n      className,\n      value: itemValue = null,\n      index: indexProp,\n      disabled = false,\n      nativeButton = false,\n      ...elementProps\n    } = componentProps;\n\n    const didPointerDownRef = React.useRef(false);\n    const textRef = React.useRef<HTMLElement | null>(null);\n    const listItem = useCompositeListItem({\n      index: indexProp,\n      textRef,\n      indexGuessBehavior: IndexGuessBehavior.GuessFromOrder,\n    });\n\n    const store = useComboboxRootContext();\n    const isRow = useComboboxRowContext();\n    const { flatFilteredItems, hasItems } = useComboboxDerivedItemsContext();\n\n    const open = useStore(store, selectors.open);\n    const selectionMode = useStore(store, selectors.selectionMode);\n    const readOnly = useStore(store, selectors.readOnly);\n    const virtualized = useStore(store, selectors.virtualized);\n    const isItemEqualToValue = useStore(store, selectors.isItemEqualToValue);\n\n    const selectable = selectionMode !== 'none';\n    const index =\n      indexProp ??\n      (virtualized\n        ? findItemIndex(flatFilteredItems, itemValue, isItemEqualToValue)\n        : listItem.index);\n    const hasRegistered = listItem.index !== -1;\n\n    const rootId = useStore(store, selectors.id);\n    const highlighted = useStore(store, selectors.isActive, index);\n    const matchesSelectedValue = useStore(store, selectors.isSelected, itemValue);\n    const getItemProps = useStore(store, selectors.getItemProps);\n\n    const itemRef = React.useRef<HTMLDivElement | null>(null);\n\n    const id = rootId != null && hasRegistered ? `${rootId}-${index}` : undefined;\n    const selected = matchesSelectedValue && selectable;\n\n    useIsoLayoutEffect(() => {\n      const shouldRun = hasRegistered && (virtualized || indexProp != null);\n      if (!shouldRun) {\n        return undefined;\n      }\n\n      const list = store.state.listRef.current;\n      list[index] = itemRef.current;\n\n      return () => {\n        delete list[index];\n      };\n    }, [hasRegistered, virtualized, index, indexProp, store]);\n\n    useIsoLayoutEffect(() => {\n      if (!hasRegistered || hasItems) {\n        return undefined;\n      }\n\n      const visibleMap = store.state.valuesRef.current;\n      visibleMap[index] = itemValue;\n\n      // Stable registry that doesn't depend on filtering. Assume that no\n      // filtering had occurred at this point; otherwise, an `items` prop is\n      // required.\n      if (selectionMode !== 'none') {\n        store.state.allValuesRef.current.push(itemValue);\n      }\n\n      return () => {\n        delete visibleMap[index];\n      };\n    }, [hasRegistered, hasItems, index, itemValue, store, selectionMode]);\n\n    useIsoLayoutEffect(() => {\n      if (!open) {\n        didPointerDownRef.current = false;\n        return;\n      }\n\n      if (!hasRegistered || hasItems) {\n        return;\n      }\n\n      const selectedValue = store.state.selectedValue;\n      const lastSelectedValue = Array.isArray(selectedValue)\n        ? selectedValue[selectedValue.length - 1]\n        : selectedValue;\n\n      if (compareItemEquality(itemValue, lastSelectedValue, isItemEqualToValue)) {\n        store.set('selectedIndex', index);\n      }\n    }, [hasRegistered, hasItems, open, store, index, itemValue, isItemEqualToValue]);\n\n    const state: ComboboxItemState = {\n      disabled,\n      selected,\n      highlighted,\n    };\n\n    const rootProps = getItemProps({ active: highlighted, selected });\n    rootProps.id = undefined;\n    rootProps.onFocus = undefined;\n\n    const { getButtonProps, buttonRef } = useButton({\n      disabled,\n      focusableWhenDisabled: true,\n      native: nativeButton,\n      composite: true,\n    });\n\n    function commitSelection(nativeEvent: MouseEvent) {\n      function selectItem() {\n        store.state.handleSelection(nativeEvent, itemValue);\n      }\n\n      if (store.state.submitOnItemClick) {\n        ReactDOM.flushSync(selectItem);\n        store.state.requestSubmit();\n      } else {\n        selectItem();\n      }\n    }\n\n    const defaultProps: HTMLProps = {\n      id,\n      role: isRow ? 'gridcell' : 'option',\n      'aria-selected': selectable ? selected : undefined,\n      // Focusable items steal focus from the input upon mouseup.\n      // Warn if the user renders a natively focusable element like `<button>`,\n      // as it should be a `<div>` instead.\n      tabIndex: undefined,\n      onPointerDownCapture(event) {\n        didPointerDownRef.current = true;\n        event.preventDefault();\n      },\n      onClick(event) {\n        if (disabled || readOnly) {\n          return;\n        }\n\n        commitSelection(event.nativeEvent);\n      },\n      onMouseUp(event) {\n        const pointerStartedOnItem = didPointerDownRef.current;\n        didPointerDownRef.current = false;\n\n        if (disabled || readOnly || event.button !== 0 || pointerStartedOnItem || !highlighted) {\n          return;\n        }\n\n        commitSelection(event.nativeEvent);\n      },\n    };\n\n    const element = useRenderElement('div', componentProps, {\n      ref: [buttonRef, forwardedRef, listItem.ref, itemRef],\n      state,\n      props: [rootProps, defaultProps, elementProps, getButtonProps],\n    });\n\n    const contextValue: ComboboxItemContext = React.useMemo(\n      () => ({\n        selected,\n        textRef,\n      }),\n      [selected, textRef],\n    );\n\n    return (\n      <ComboboxItemContext.Provider value={contextValue}>{element}</ComboboxItemContext.Provider>\n    );\n  }),\n);\n\nexport interface ComboboxItemState {\n  /**\n   * Whether the item should ignore user interaction.\n   */\n  disabled: boolean;\n  /**\n   * Whether the item is selected.\n   */\n  selected: boolean;\n  /**\n   * Whether the item is highlighted.\n   */\n  highlighted: boolean;\n}\n\nexport interface ComboboxItemProps\n  extends NonNativeButtonProps, Omit<BaseUIComponentProps<'div', ComboboxItemState>, 'id'> {\n  children?: React.ReactNode;\n  /**\n   * An optional click handler for the item when selected.\n   * It fires when clicking the item with the pointer, as well as when pressing `Enter` with the keyboard if the item is highlighted when the `Input` or `List` element has focus.\n   */\n  onClick?: BaseUIComponentProps<'div', ComboboxItemState>['onClick'] | undefined;\n  /**\n   * The index of the item in the list. Improves performance when specified by avoiding the need to calculate the index automatically from the DOM.\n   */\n  index?: number | undefined;\n  /**\n   * A unique value that identifies this item.\n   * @default null\n   */\n  value?: any;\n  /**\n   * Whether the component should ignore user interaction.\n   * @default false\n   */\n  disabled?: boolean | undefined;\n}\n\nexport namespace ComboboxItem {\n  export type State = ComboboxItemState;\n  export type Props = ComboboxItemProps;\n}\n"
  },
  {
    "path": "packages/react/src/combobox/item/ComboboxItemContext.ts",
    "content": "'use client';\nimport * as React from 'react';\n\nexport interface ComboboxItemContext {\n  selected: boolean;\n  textRef: React.RefObject<HTMLElement | null>;\n}\n\nexport const ComboboxItemContext = React.createContext<ComboboxItemContext | undefined>(undefined);\n\nexport function useComboboxItemContext() {\n  const context = React.useContext(ComboboxItemContext);\n  if (!context) {\n    throw new Error(\n      'Base UI: ComboboxItemContext is missing. ComboboxItem parts must be placed within <Combobox.Item>.',\n    );\n  }\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/combobox/item/ComboboxItemDataAttributes.ts",
    "content": "export enum ComboboxItemDataAttributes {\n  /**\n   * Present when the item is selected.\n   */\n  selected = 'data-selected',\n  /**\n   * Present when the item is highlighted.\n   */\n  highlighted = 'data-highlighted',\n  /**\n   * Present when the item is disabled.\n   */\n  disabled = 'data-disabled',\n}\n"
  },
  {
    "path": "packages/react/src/combobox/item-indicator/ComboboxItemIndicator.test.tsx",
    "content": "import { Combobox } from '@base-ui/react/combobox';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Combobox.ItemIndicator />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Combobox.ItemIndicator keepMounted />, () => ({\n    refInstanceof: window.HTMLSpanElement,\n    render(node) {\n      return render(\n        <Combobox.Root>\n          <Combobox.Item>{node}</Combobox.Item>\n        </Combobox.Root>,\n      );\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/combobox/item-indicator/ComboboxItemIndicator.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useComboboxItemContext } from '../item/ComboboxItemContext';\nimport { type TransitionStatus, useTransitionStatus } from '../../utils/useTransitionStatus';\nimport { useOpenChangeComplete } from '../../utils/useOpenChangeComplete';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { transitionStatusMapping } from '../../utils/stateAttributesMapping';\n\n/**\n * Indicates whether the item is selected.\n * Renders a `<span>` element.\n */\nexport const ComboboxItemIndicator = React.forwardRef(function ComboboxItemIndicator(\n  componentProps: ComboboxItemIndicator.Props,\n  forwardedRef: React.ForwardedRef<HTMLSpanElement>,\n) {\n  const keepMounted = componentProps.keepMounted ?? false;\n\n  const { selected } = useComboboxItemContext();\n\n  const shouldRender = keepMounted || selected;\n  if (!shouldRender) {\n    return null;\n  }\n\n  // eslint-disable-next-line @typescript-eslint/no-use-before-define\n  return <Inner {...componentProps} ref={forwardedRef} />;\n});\n\n/** The core implementation of the indicator is split here to avoid paying the hooks\n * costs unless the element needs to be mounted. */\nconst Inner = React.memo(\n  React.forwardRef(\n    (\n      componentProps: ComboboxItemIndicator.Props,\n      forwardedRef: React.ForwardedRef<HTMLSpanElement>,\n    ) => {\n      const { render, className, keepMounted, ...elementProps } = componentProps;\n\n      const { selected } = useComboboxItemContext();\n\n      const indicatorRef = React.useRef<HTMLSpanElement | null>(null);\n\n      const { transitionStatus, setMounted } = useTransitionStatus(selected);\n\n      const state: ComboboxItemIndicatorState = {\n        selected,\n        transitionStatus,\n      };\n\n      const element = useRenderElement('span', componentProps, {\n        ref: [forwardedRef, indicatorRef],\n        state,\n        props: [\n          {\n            'aria-hidden': true,\n            children: '✔️',\n          },\n          elementProps,\n        ],\n        stateAttributesMapping: transitionStatusMapping,\n      });\n\n      useOpenChangeComplete({\n        open: selected,\n        ref: indicatorRef,\n        onComplete() {\n          if (!selected) {\n            setMounted(false);\n          }\n        },\n      });\n\n      return element;\n    },\n  ),\n);\n\nexport interface ComboboxItemIndicatorProps extends BaseUIComponentProps<\n  'span',\n  ComboboxItemIndicatorState\n> {\n  children?: React.ReactNode;\n  /**\n   * Whether to keep the HTML element in the DOM when the item is not selected.\n   * @default false\n   */\n  keepMounted?: boolean | undefined;\n}\n\nexport interface ComboboxItemIndicatorState {\n  /**\n   * Whether the item is selected.\n   */\n  selected: boolean;\n  /**\n   * The transition status of the component.\n   */\n  transitionStatus: TransitionStatus;\n}\n\nexport namespace ComboboxItemIndicator {\n  export type Props = ComboboxItemIndicatorProps;\n  export type State = ComboboxItemIndicatorState;\n}\n"
  },
  {
    "path": "packages/react/src/combobox/item-indicator/ComboboxItemIndicatorDataAttributes.ts",
    "content": "import { TransitionStatusDataAttributes } from '../../utils/stateAttributesMapping';\n\nexport enum ComboboxItemIndicatorDataAttributes {\n  /**\n   * Present when the indicator is animating in.\n   */\n  startingStyle = TransitionStatusDataAttributes.startingStyle,\n  /**\n   * Present when the indicator is animating out.\n   */\n  endingStyle = TransitionStatusDataAttributes.endingStyle,\n}\n"
  },
  {
    "path": "packages/react/src/combobox/label/ComboboxLabel.test.tsx",
    "content": "import { Combobox } from '@base-ui/react/combobox';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Combobox.Label />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Combobox.Label />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <Combobox.Root>\n          {node}\n          <Combobox.Trigger>Open</Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.Input />\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/combobox/label/ComboboxLabel.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { error } from '@base-ui/utils/error';\nimport { SafeReact } from '@base-ui/utils/safeReact';\nimport { useStore } from '@base-ui/utils/store';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport type { FieldRoot } from '../../field/root/FieldRoot';\nimport { useFieldRootContext } from '../../field/root/FieldRootContext';\nimport { fieldValidityMapping } from '../../field/utils/constants';\nimport { useLabel } from '../../labelable-provider/useLabel';\nimport { getDefaultLabelId } from '../../utils/resolveAriaLabelledBy';\nimport { useComboboxRootContext } from '../root/ComboboxRootContext';\nimport { selectors } from '../store';\n\n/**\n * An accessible label that is automatically associated with the combobox trigger.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Combobox](https://base-ui.com/react/components/combobox)\n */\nexport const ComboboxLabel = React.forwardRef(function ComboboxLabel(\n  componentProps: ComboboxLabel.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, ...elementProps } = componentProps;\n  // Keep label id derived from the root and ignore runtime `id` overrides from untyped consumers.\n  const elementPropsWithoutId = elementProps as typeof elementProps & { id?: string | undefined };\n  delete elementPropsWithoutId.id;\n\n  const fieldRootContext = useFieldRootContext();\n  const store = useComboboxRootContext();\n\n  const inputInsidePopup = useStore(store, selectors.inputInsidePopup);\n  const triggerElement = useStore(store, selectors.triggerElement);\n  const inputElement = useStore(store, selectors.inputElement);\n  const rootId = useStore(store, selectors.id);\n  const defaultLabelId = getDefaultLabelId(rootId);\n\n  const localControlId = triggerElement?.id ?? (inputInsidePopup ? rootId : undefined);\n\n  if (process.env.NODE_ENV !== 'production') {\n    // eslint-disable-next-line react-hooks/rules-of-hooks\n    React.useEffect(() => {\n      if (!inputElement || inputInsidePopup) {\n        return;\n      }\n\n      const ownerStackMessage = SafeReact.captureOwnerStack?.() || '';\n      const message =\n        '<Combobox.Label> labels <Combobox.Trigger> only. ' +\n        'When <Combobox.Input> is the form control, use a native <label> or <Field.Label> instead.';\n      error(`${message}${ownerStackMessage}`);\n    }, [inputElement, inputInsidePopup]);\n  }\n\n  const labelProps = useLabel({\n    id: defaultLabelId,\n    fallbackControlId: localControlId,\n    setLabelId(nextLabelId) {\n      store.set('labelId', nextLabelId);\n    },\n  });\n\n  return useRenderElement('div', componentProps, {\n    ref: forwardedRef,\n    state: fieldRootContext.state,\n    props: [labelProps, elementProps],\n    stateAttributesMapping: fieldValidityMapping,\n  });\n});\n\nexport type ComboboxLabelState = FieldRoot.State;\n\nexport interface ComboboxLabelProps extends Omit<\n  BaseUIComponentProps<'div', ComboboxLabel.State>,\n  'id'\n> {}\n\nexport namespace ComboboxLabel {\n  export type State = ComboboxLabelState;\n  export type Props = ComboboxLabelProps;\n}\n"
  },
  {
    "path": "packages/react/src/combobox/list/ComboboxList.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { Combobox } from '@base-ui/react/combobox';\nimport { createRenderer, describeConformance } from '#test-utils';\nimport { screen } from '@mui/internal-test-utils';\n\ndescribe('<Combobox.List />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Combobox.List />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(<Combobox.Root>{node}</Combobox.Root>);\n    },\n  }));\n\n  it('sets role=listbox and aria-multiselectable in multiple mode', async () => {\n    await render(\n      <Combobox.Root multiple defaultOpen>\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup>\n              <Combobox.List>\n                <Combobox.Item value=\"a\">a</Combobox.Item>\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    const list = screen.getByRole('listbox');\n    expect(list).toHaveAttribute('aria-multiselectable', 'true');\n  });\n});\n"
  },
  {
    "path": "packages/react/src/combobox/list/ComboboxList.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStore } from '@base-ui/utils/store';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport {\n  useComboboxDerivedItemsContext,\n  useComboboxFloatingContext,\n  useComboboxRootContext,\n} from '../root/ComboboxRootContext';\nimport { useComboboxPositionerContext } from '../positioner/ComboboxPositionerContext';\nimport { selectors } from '../store';\nimport { ComboboxCollection } from '../collection/ComboboxCollection';\nimport { CompositeList } from '../../composite/list/CompositeList';\nimport { stopEvent } from '../../floating-ui-react/utils';\n\n/**\n * A list container for the items.\n * Renders a `<div>` element.\n */\nexport const ComboboxList = React.forwardRef(function ComboboxList(\n  componentProps: ComboboxList.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, children, ...elementProps } = componentProps;\n\n  const store = useComboboxRootContext();\n  const floatingRootContext = useComboboxFloatingContext();\n  const hasPositionerContext = Boolean(useComboboxPositionerContext(true));\n  const { filteredItems } = useComboboxDerivedItemsContext();\n\n  const items = useStore(store, selectors.items);\n  const labelsRef = useStore(store, selectors.labelsRef);\n  const listRef = useStore(store, selectors.listRef);\n  const selectionMode = useStore(store, selectors.selectionMode);\n  const grid = useStore(store, selectors.grid);\n  const popupProps = useStore(store, selectors.popupProps);\n  const disabled = useStore(store, selectors.disabled);\n  const readOnly = useStore(store, selectors.readOnly);\n  const virtualized = useStore(store, selectors.virtualized);\n\n  const multiple = selectionMode === 'multiple';\n  const empty = filteredItems.length === 0;\n\n  const setPositionerElement = useStableCallback((element) => {\n    store.set('positionerElement', element);\n  });\n\n  const setListElement = useStableCallback((element) => {\n    store.set('listElement', element);\n  });\n\n  // Support \"closed template\" API: if children is a function, implicitly wrap it\n  // with a Combobox.Collection that reads items from context/root.\n  // Ensures this component's `popupProps` subscription does not cause <Combobox.Item>\n  // to re-render on every active index change.\n  const resolvedChildren = React.useMemo(() => {\n    if (typeof children === 'function') {\n      return <ComboboxCollection>{children}</ComboboxCollection>;\n    }\n    return children;\n  }, [children]);\n\n  const state: ComboboxListState = {\n    empty,\n  };\n\n  const floatingId = floatingRootContext.useState('floatingId');\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: [forwardedRef, setListElement, hasPositionerContext ? null : setPositionerElement],\n    props: [\n      popupProps,\n      {\n        children: resolvedChildren,\n        tabIndex: -1,\n        id: floatingId,\n        role: grid ? 'grid' : 'listbox',\n        'aria-multiselectable': multiple ? 'true' : undefined,\n        onKeyDown(event) {\n          if (disabled || readOnly) {\n            return;\n          }\n\n          if (event.key === 'Enter') {\n            const activeIndex = store.state.activeIndex;\n\n            if (activeIndex == null) {\n              // Allow form submission when no item is highlighted.\n              return;\n            }\n\n            stopEvent(event);\n\n            const nativeEvent = event.nativeEvent;\n            const listItem = store.state.listRef.current[activeIndex];\n\n            if (listItem) {\n              store.state.selectionEventRef.current = nativeEvent;\n              listItem.click();\n              store.state.selectionEventRef.current = null;\n            }\n          }\n        },\n        onKeyDownCapture() {\n          store.state.keyboardActiveRef.current = true;\n        },\n        onPointerMoveCapture() {\n          store.state.keyboardActiveRef.current = false;\n        },\n      },\n      elementProps,\n    ],\n  });\n\n  if (virtualized) {\n    return element;\n  }\n\n  return (\n    <CompositeList elementsRef={listRef} labelsRef={items ? undefined : labelsRef}>\n      {element}\n    </CompositeList>\n  );\n});\n\nexport interface ComboboxListState {\n  /**\n   * Whether the list is empty.\n   */\n  empty: boolean;\n}\n\nexport interface ComboboxListProps extends Omit<\n  BaseUIComponentProps<'div', ComboboxListState>,\n  'children'\n> {\n  children?: React.ReactNode | ((item: any, index: number) => React.ReactNode);\n}\n\nexport namespace ComboboxList {\n  export type State = ComboboxListState;\n  export type Props = ComboboxListProps;\n}\n"
  },
  {
    "path": "packages/react/src/combobox/popup/ComboboxPopup.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { Combobox } from '@base-ui/react/combobox';\nimport { createRenderer, describeConformance } from '#test-utils';\nimport { screen, waitFor } from '@mui/internal-test-utils';\n\ndescribe('<Combobox.Popup />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Combobox.Popup />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <Combobox.Root open>\n          <Combobox.Portal>\n            <Combobox.Positioner>{node}</Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n    },\n  }));\n\n  it('exposes open state via data attributes mapping', async () => {\n    await render(\n      <Combobox.Root defaultOpen>\n        <Combobox.Input />\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup data-testid=\"popup\" />\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    const popup = await screen.findByTestId('popup');\n    expect(popup).toHaveAttribute('data-open');\n  });\n\n  it('sets role to presentation when input renders outside the popup', async () => {\n    await render(\n      <Combobox.Root defaultOpen items={['Apple']}>\n        <Combobox.Input />\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup data-testid=\"popup\" />\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    const popup = await screen.findByTestId('popup');\n    await waitFor(() => {\n      expect(popup).toHaveAttribute('role', 'presentation');\n    });\n  });\n\n  it('sets role to dialog when input renders inside the popup', async () => {\n    await render(\n      <Combobox.Root defaultOpen items={['Apple']}>\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup data-testid=\"popup\">\n              <Combobox.Input />\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    const popup = await screen.findByTestId('popup');\n    await waitFor(() => {\n      expect(popup).toHaveAttribute('role', 'dialog');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/combobox/popup/ComboboxPopup.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { InteractionType } from '@base-ui/utils/useEnhancedClickHandler';\nimport { useStore } from '@base-ui/utils/store';\nimport { FloatingFocusManager } from '../../floating-ui-react';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport {\n  useComboboxFloatingContext,\n  useComboboxRootContext,\n  useComboboxDerivedItemsContext,\n} from '../root/ComboboxRootContext';\nimport { selectors } from '../store';\nimport { popupStateMapping } from '../../utils/popupStateMapping';\nimport { useComboboxPositionerContext } from '../positioner/ComboboxPositionerContext';\nimport type { Side, Align } from '../../utils/useAnchorPositioning';\nimport { useOpenChangeComplete } from '../../utils/useOpenChangeComplete';\nimport type { TransitionStatus } from '../../utils/useTransitionStatus';\nimport { transitionStatusMapping } from '../../utils/stateAttributesMapping';\nimport { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport { contains, getTarget } from '../../floating-ui-react/utils';\nimport { getDisabledMountTransitionStyles } from '../../utils/getDisabledMountTransitionStyles';\nimport { ComboboxInternalDismissButton } from '../utils/ComboboxInternalDismissButton';\n\nconst stateAttributesMapping: StateAttributesMapping<ComboboxPopupState> = {\n  ...popupStateMapping,\n  ...transitionStatusMapping,\n};\n\n/**\n * A container for the list.\n * Renders a `<div>` element.\n */\nexport const ComboboxPopup = React.forwardRef(function ComboboxPopup(\n  componentProps: ComboboxPopup.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, initialFocus, finalFocus, ...elementProps } = componentProps;\n\n  const store = useComboboxRootContext();\n  const positioning = useComboboxPositionerContext();\n  const floatingRootContext = useComboboxFloatingContext();\n  const { filteredItems } = useComboboxDerivedItemsContext();\n\n  const mounted = useStore(store, selectors.mounted);\n  const open = useStore(store, selectors.open);\n  const openMethod = useStore(store, selectors.openMethod);\n  const transitionStatus = useStore(store, selectors.transitionStatus);\n  const inputInsidePopup = useStore(store, selectors.inputInsidePopup);\n  const inputElement = useStore(store, selectors.inputElement);\n  const modal = useStore(store, selectors.modal);\n\n  const empty = filteredItems.length === 0;\n\n  useOpenChangeComplete({\n    open,\n    ref: store.state.popupRef,\n    onComplete() {\n      if (open) {\n        store.state.onOpenChangeComplete(true);\n      }\n    },\n  });\n\n  const state: ComboboxPopupState = {\n    open,\n    side: positioning.side,\n    align: positioning.align,\n    anchorHidden: positioning.anchorHidden,\n    transitionStatus,\n    empty,\n  };\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: [forwardedRef, store.state.popupRef],\n    props: [\n      {\n        role: inputInsidePopup ? 'dialog' : 'presentation',\n        tabIndex: -1,\n        onFocus(event) {\n          const target = getTarget(event.nativeEvent) as Element | null;\n          if (\n            openMethod !== 'touch' &&\n            (contains(store.state.listElement, target) || target === event.currentTarget)\n          ) {\n            store.state.inputRef.current?.focus();\n          }\n        },\n      },\n      getDisabledMountTransitionStyles(transitionStatus),\n      elementProps,\n    ],\n    stateAttributesMapping,\n  });\n\n  // Default initial focus logic:\n  // If opened by touch, focus the popup element to prevent the virtual keyboard from opening\n  // (this is required for Android specifically as iOS handles this automatically).\n  const computedDefaultInitialFocus = inputInsidePopup\n    ? (interactionType: InteractionType) =>\n        interactionType === 'touch' ? store.state.popupRef.current : inputElement\n    : false;\n\n  const resolvedInitialFocus =\n    initialFocus === undefined ? computedDefaultInitialFocus : initialFocus;\n\n  let resolvedFinalFocus: ComboboxPopup.Props['finalFocus'] | boolean | undefined;\n  if (finalFocus != null) {\n    resolvedFinalFocus = finalFocus;\n  } else {\n    resolvedFinalFocus = inputInsidePopup ? undefined : false;\n  }\n\n  const focusManagerModal = !inputInsidePopup || modal;\n\n  return (\n    <FloatingFocusManager\n      context={floatingRootContext}\n      disabled={!mounted}\n      modal={focusManagerModal}\n      openInteractionType={openMethod}\n      initialFocus={resolvedInitialFocus}\n      returnFocus={resolvedFinalFocus}\n      getInsideElements={() => [\n        store.state.startDismissRef.current,\n        store.state.endDismissRef.current,\n      ]}\n    >\n      <React.Fragment>\n        {element}\n        {focusManagerModal && <ComboboxInternalDismissButton ref={store.state.endDismissRef} />}\n      </React.Fragment>\n    </FloatingFocusManager>\n  );\n});\n\nexport interface ComboboxPopupState {\n  /**\n   * Whether the component is open.\n   */\n  open: boolean;\n  /**\n   * The side of the anchor the component is placed on.\n   */\n  side: Side;\n  /**\n   * The alignment of the component relative to the anchor.\n   */\n  align: Align;\n  /**\n   * Whether the anchor element is hidden.\n   */\n  anchorHidden: boolean;\n  /**\n   * The transition status of the component.\n   */\n  transitionStatus: TransitionStatus;\n  /**\n   * Whether there are no items to display.\n   */\n  empty: boolean;\n}\n\nexport interface ComboboxPopupProps extends BaseUIComponentProps<'div', ComboboxPopupState> {\n  /**\n   * Determines the element to focus when the popup is opened.\n   *\n   * - `false`: Do not move focus.\n   * - `true`: Move focus based on the default behavior (first tabbable element or popup).\n   * - `RefObject`: Move focus to the ref element.\n   * - `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`).\n   *   Return an element to focus, `true` to use the default behavior, or `false`/`undefined` to do nothing.\n   */\n  initialFocus?:\n    | boolean\n    | React.RefObject<HTMLElement | null>\n    | ((openType: InteractionType) => void | boolean | HTMLElement | null)\n    | undefined;\n  /**\n   * Determines the element to focus when the popup is closed.\n   *\n   * - `false`: Do not move focus.\n   * - `true`: Move focus based on the default behavior (trigger or previously focused element).\n   * - `RefObject`: Move focus to the ref element.\n   * - `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`).\n   *   Return an element to focus, `true` to use the default behavior, or `false`/`undefined` to do nothing.\n   */\n  finalFocus?:\n    | boolean\n    | React.RefObject<HTMLElement | null>\n    | ((closeType: InteractionType) => void | boolean | HTMLElement | null)\n    | undefined;\n}\n\nexport namespace ComboboxPopup {\n  export type State = ComboboxPopupState;\n  export type Props = ComboboxPopupProps;\n}\n"
  },
  {
    "path": "packages/react/src/combobox/popup/ComboboxPopupDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum ComboboxPopupDataAttributes {\n  /**\n   * Present when the popup is open.\n   */\n  open = CommonPopupDataAttributes.open,\n  /**\n   * Present when the popup is closed.\n   */\n  closed = CommonPopupDataAttributes.closed,\n  /**\n   * Present when the popup is animating in.\n   */\n  startingStyle = CommonPopupDataAttributes.startingStyle,\n  /**\n   * Present when the popup is animating out.\n   */\n  endingStyle = CommonPopupDataAttributes.endingStyle,\n  /**\n   * Indicates which side the popup is positioned relative to the trigger.\n   * @type {'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'}\n   */\n  side = CommonPopupDataAttributes.side,\n  /**\n   * Indicates how the popup is aligned relative to specified side.\n   * @type {'start' | 'center' | 'end'}\n   */\n  align = CommonPopupDataAttributes.align,\n  /**\n   * Present if animations should be instant.\n   * @type {'click' | 'dismiss'}\n   */\n  instant = 'data-instant',\n  /**\n   * Present when the items list is empty.\n   */\n  empty = 'data-empty',\n}\n"
  },
  {
    "path": "packages/react/src/combobox/portal/ComboboxPortal.test.tsx",
    "content": "import * as React from 'react';\nimport { Combobox } from '@base-ui/react/combobox';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Combobox.Portal />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Combobox.Portal />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(<Combobox.Root open>{node}</Combobox.Root>);\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/combobox/portal/ComboboxPortal.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStore } from '@base-ui/utils/store';\nimport { FloatingPortal } from '../../floating-ui-react';\nimport { useComboboxRootContext } from '../root/ComboboxRootContext';\nimport { ComboboxPortalContext } from './ComboboxPortalContext';\nimport { selectors } from '../store';\n\n/**\n * A portal element that moves the popup to a different part of the DOM.\n * By default, the portal element is appended to `<body>`.\n * Renders a `<div>` element.\n */\nexport const ComboboxPortal = React.forwardRef(function ComboboxPortal(\n  props: ComboboxPortal.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { keepMounted = false, ...portalProps } = props;\n\n  const store = useComboboxRootContext();\n\n  const mounted = useStore(store, selectors.mounted);\n  const forceMounted = useStore(store, selectors.forceMounted);\n\n  const shouldRender = mounted || keepMounted || forceMounted;\n  if (!shouldRender) {\n    return null;\n  }\n\n  return (\n    <ComboboxPortalContext.Provider value={keepMounted}>\n      <FloatingPortal ref={forwardedRef} {...portalProps} />\n    </ComboboxPortalContext.Provider>\n  );\n});\n\nexport interface ComboboxPortalState {}\n\nexport interface ComboboxPortalProps extends FloatingPortal.Props<ComboboxPortalState> {\n  /**\n   * Whether to keep the portal mounted in the DOM while the popup is hidden.\n   * @default false\n   */\n  keepMounted?: boolean | undefined;\n}\n\nexport namespace ComboboxPortal {\n  export type State = ComboboxPortalState;\n  export type Props = ComboboxPortalProps;\n}\n"
  },
  {
    "path": "packages/react/src/combobox/portal/ComboboxPortalContext.tsx",
    "content": "'use client';\nimport * as React from 'react';\n\nexport const ComboboxPortalContext = React.createContext<boolean | undefined>(undefined);\n\nexport function useComboboxPortalContext() {\n  const context = React.useContext(ComboboxPortalContext);\n  if (context === undefined) {\n    throw new Error('Base UI: <Combobox.Portal> is missing.');\n  }\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/combobox/positioner/ComboboxPositioner.test.tsx",
    "content": "import { expect } from 'vitest';\nimport * as React from 'react';\nimport { waitFor } from '@mui/internal-test-utils';\nimport { Combobox } from '@base-ui/react/combobox';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\n\ndescribe('<Combobox.Positioner />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Combobox.Positioner />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <Combobox.Root open>\n          <Combobox.Portal>{node}</Combobox.Portal>\n        </Combobox.Root>,\n      );\n    },\n  }));\n\n  describe.skipIf(isJSDOM)('default anchor', () => {\n    it('uses the input when input group is absent', async () => {\n      const inputWidth = 120;\n      const triggerWidth = 240;\n      let anchorWidth = 0;\n      const inputRef = React.createRef<HTMLInputElement>();\n\n      await render(\n        <Combobox.Root open>\n          <Combobox.Input ref={inputRef} style={{ width: inputWidth }} />\n          <Combobox.Trigger style={{ width: triggerWidth }}>Open</Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner\n              sideOffset={(data) => {\n                anchorWidth = data.anchor.width;\n                return 0;\n              }}\n            >\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"One\">One</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      await waitFor(() => {\n        expect(anchorWidth).toBeCloseTo(inputRef.current!.getBoundingClientRect().width, 0);\n        expect(anchorWidth).not.toBeCloseTo(triggerWidth, 0);\n      });\n    });\n\n    it('uses the input group when present', async () => {\n      const inputGroupWidth = 240;\n      const inputWidth = 120;\n      let anchorWidth = 0;\n\n      await render(\n        <Combobox.Root open>\n          <Combobox.InputGroup style={{ width: inputGroupWidth }}>\n            <Combobox.Input style={{ width: inputWidth }} />\n            <Combobox.Trigger>Open</Combobox.Trigger>\n          </Combobox.InputGroup>\n          <Combobox.Portal>\n            <Combobox.Positioner\n              sideOffset={(data) => {\n                anchorWidth = data.anchor.width;\n                return 0;\n              }}\n            >\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"One\">One</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      await waitFor(() => {\n        expect(anchorWidth).toBeCloseTo(inputGroupWidth, 0);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/combobox/positioner/ComboboxPositioner.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStore } from '@base-ui/utils/store';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { inertValue } from '@base-ui/utils/inertValue';\nimport { useScrollLock } from '@base-ui/utils/useScrollLock';\nimport {\n  useComboboxFloatingContext,\n  useComboboxRootContext,\n  useComboboxDerivedItemsContext,\n} from '../root/ComboboxRootContext';\nimport { ComboboxPositionerContext } from './ComboboxPositionerContext';\nimport {\n  type Side,\n  type Align,\n  useAnchorPositioning,\n  type UseAnchorPositioningSharedParameters,\n} from '../../utils/useAnchorPositioning';\nimport type { BaseUIComponentProps, HTMLProps } from '../../utils/types';\nimport { popupStateMapping } from '../../utils/popupStateMapping';\nimport { useComboboxPortalContext } from '../portal/ComboboxPortalContext';\nimport { DROPDOWN_COLLISION_AVOIDANCE } from '../../utils/constants';\nimport { getDisabledMountTransitionStyles } from '../../utils/getDisabledMountTransitionStyles';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { selectors } from '../store';\nimport { InternalBackdrop } from '../../utils/InternalBackdrop';\n\n/**\n * Positions the popup against the trigger.\n * Renders a `<div>` element.\n */\nexport const ComboboxPositioner = React.forwardRef(function ComboboxPositioner(\n  componentProps: ComboboxPositioner.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const {\n    render,\n    className,\n    anchor,\n    positionMethod = 'absolute',\n    side = 'bottom',\n    align = 'center',\n    sideOffset = 0,\n    alignOffset = 0,\n    collisionBoundary = 'clipping-ancestors',\n    collisionPadding = 5,\n    arrowPadding = 5,\n    sticky = false,\n    disableAnchorTracking = false,\n    collisionAvoidance = DROPDOWN_COLLISION_AVOIDANCE,\n    ...elementProps\n  } = componentProps;\n\n  const store = useComboboxRootContext();\n  const { filteredItems } = useComboboxDerivedItemsContext();\n  const floatingRootContext = useComboboxFloatingContext();\n  const keepMounted = useComboboxPortalContext();\n\n  const modal = useStore(store, selectors.modal);\n  const open = useStore(store, selectors.open);\n  const mounted = useStore(store, selectors.mounted);\n  const openMethod = useStore(store, selectors.openMethod);\n  const triggerElement = useStore(store, selectors.triggerElement);\n  const inputElement = useStore(store, selectors.inputElement);\n  const inputGroupElement = useStore(store, selectors.inputGroupElement);\n  const inputInsidePopup = useStore(store, selectors.inputInsidePopup);\n  const transitionStatus = useStore(store, selectors.transitionStatus);\n\n  const empty = filteredItems.length === 0;\n  const resolvedAnchor =\n    anchor ?? (inputInsidePopup ? triggerElement : (inputGroupElement ?? inputElement));\n\n  const positioning = useAnchorPositioning({\n    anchor: resolvedAnchor,\n    floatingRootContext,\n    positionMethod,\n    mounted,\n    side,\n    sideOffset,\n    align,\n    alignOffset,\n    arrowPadding,\n    collisionBoundary,\n    collisionPadding,\n    sticky,\n    disableAnchorTracking,\n    keepMounted,\n    collisionAvoidance,\n    lazyFlip: true,\n  });\n\n  useScrollLock(open && modal && openMethod !== 'touch', triggerElement);\n\n  const defaultProps: HTMLProps = React.useMemo(() => {\n    const style: React.CSSProperties = {\n      ...positioning.positionerStyles,\n    };\n\n    if (!open) {\n      style.pointerEvents = 'none';\n    }\n\n    return {\n      role: 'presentation',\n      hidden: !mounted,\n      style,\n    };\n  }, [open, mounted, positioning.positionerStyles]);\n\n  const state: ComboboxPositionerState = {\n    open,\n    side: positioning.side,\n    align: positioning.align,\n    anchorHidden: positioning.anchorHidden,\n    empty,\n  };\n\n  useIsoLayoutEffect(() => {\n    store.set('popupSide', positioning.side);\n  }, [store, positioning.side]);\n\n  const contextValue: ComboboxPositionerContext = React.useMemo(\n    () => ({\n      side: positioning.side,\n      align: positioning.align,\n      arrowRef: positioning.arrowRef,\n      arrowUncentered: positioning.arrowUncentered,\n      arrowStyles: positioning.arrowStyles,\n      anchorHidden: positioning.anchorHidden,\n      isPositioned: positioning.isPositioned,\n    }),\n    [\n      positioning.side,\n      positioning.align,\n      positioning.arrowRef,\n      positioning.arrowUncentered,\n      positioning.arrowStyles,\n      positioning.anchorHidden,\n      positioning.isPositioned,\n    ],\n  );\n\n  const setPositionerElement = useStableCallback((element) => {\n    store.set('positionerElement', element);\n  });\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: [forwardedRef, setPositionerElement],\n    props: [defaultProps, getDisabledMountTransitionStyles(transitionStatus), elementProps],\n    stateAttributesMapping: popupStateMapping,\n  });\n\n  return (\n    <ComboboxPositionerContext.Provider value={contextValue}>\n      {mounted && modal && (\n        <InternalBackdrop\n          inert={inertValue(!open)}\n          cutout={inputGroupElement ?? inputElement ?? triggerElement}\n        />\n      )}\n      {element}\n    </ComboboxPositionerContext.Provider>\n  );\n});\n\nexport interface ComboboxPositionerState {\n  /**\n   * Whether the popup is currently open.\n   */\n  open: boolean;\n  /**\n   * The side of the anchor the component is placed on.\n   */\n  side: Side;\n  /**\n   * The alignment of the component relative to the anchor.\n   */\n  align: Align;\n  /**\n   * Whether the anchor element is hidden.\n   */\n  anchorHidden: boolean;\n  /**\n   * Whether there are no items to display.\n   */\n  empty: boolean;\n}\n\nexport interface ComboboxPositionerProps\n  extends\n    UseAnchorPositioningSharedParameters,\n    BaseUIComponentProps<'div', ComboboxPositionerState> {}\n\nexport namespace ComboboxPositioner {\n  export type State = ComboboxPositionerState;\n  export type Props = ComboboxPositionerProps;\n}\n"
  },
  {
    "path": "packages/react/src/combobox/positioner/ComboboxPositionerContext.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport type { Side, Align } from '../../utils/useAnchorPositioning';\n\nexport interface ComboboxPositionerContext {\n  side: Side;\n  align: Align;\n  arrowRef: React.RefObject<Element | null>;\n  arrowUncentered: boolean;\n  arrowStyles: React.CSSProperties;\n  anchorHidden: boolean;\n  isPositioned: boolean;\n}\n\nexport const ComboboxPositionerContext = React.createContext<ComboboxPositionerContext | undefined>(\n  undefined,\n);\n\nexport function useComboboxPositionerContext(optional?: false): ComboboxPositionerContext;\nexport function useComboboxPositionerContext(optional: true): ComboboxPositionerContext | undefined;\nexport function useComboboxPositionerContext(optional?: boolean) {\n  const context = React.useContext(ComboboxPositionerContext);\n  if (context === undefined && !optional) {\n    throw new Error(\n      'Base UI: <Combobox.Popup> and <Combobox.Arrow> must be used within the <Combobox.Positioner> component',\n    );\n  }\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/combobox/positioner/ComboboxPositionerCssVars.ts",
    "content": "export enum ComboboxPositionerCssVars {\n  /**\n   * The available width between the trigger and the edge of the viewport.\n   * @type {number}\n   */\n  availableWidth = '--available-width',\n  /**\n   * The available height between the trigger and the edge of the viewport.\n   * @type {number}\n   */\n  availableHeight = '--available-height',\n  /**\n   * The anchor's width.\n   * @type {number}\n   */\n  anchorWidth = '--anchor-width',\n  /**\n   * The anchor's height.\n   * @type {number}\n   */\n  anchorHeight = '--anchor-height',\n  /**\n   * The coordinates that this element is anchored to. Used for animations and transitions.\n   * @type {string}\n   */\n  transformOrigin = '--transform-origin',\n}\n"
  },
  {
    "path": "packages/react/src/combobox/positioner/ComboboxPositionerDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum ComboboxPositionerDataAttributes {\n  /**\n   * Present when the popup is open.\n   */\n  open = CommonPopupDataAttributes.open,\n  /**\n   * Present when the popup is closed.\n   */\n  closed = CommonPopupDataAttributes.closed,\n  /**\n   * Present when the anchor is hidden.\n   */\n  anchorHidden = CommonPopupDataAttributes.anchorHidden,\n  /**\n   * Indicates which side the popup is positioned relative to the trigger.\n   * @type {'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'}\n   */\n  side = CommonPopupDataAttributes.side,\n  /**\n   * Indicates how the popup is aligned relative to specified side.\n   * @type {'start' | 'center' | 'end'}\n   */\n  align = CommonPopupDataAttributes.align,\n  /**\n   * Present when the items list is empty.\n   */\n  empty = 'data-empty',\n}\n"
  },
  {
    "path": "packages/react/src/combobox/root/AriaCombobox.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useControlled } from '@base-ui/utils/useControlled';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useOnFirstRender } from '@base-ui/utils/useOnFirstRender';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useMergedRefs } from '@base-ui/utils/useMergedRefs';\nimport { visuallyHidden, visuallyHiddenInput } from '@base-ui/utils/visuallyHidden';\nimport { useRefWithInit } from '@base-ui/utils/useRefWithInit';\nimport { Store, useStore } from '@base-ui/utils/store';\nimport { useValueAsRef } from '@base-ui/utils/useValueAsRef';\nimport {\n  ElementProps,\n  useDismiss,\n  useFloatingRootContext,\n  useInteractions,\n  useListNavigation,\n  useClick,\n} from '../../floating-ui-react';\nimport { contains, getTarget } from '../../floating-ui-react/utils';\nimport {\n  createChangeEventDetails,\n  createGenericEventDetails,\n  type BaseUIChangeEventDetails,\n  type BaseUIGenericEventDetails,\n} from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport {\n  ComboboxFloatingContext,\n  ComboboxDerivedItemsContext,\n  ComboboxRootContext,\n  ComboboxInputValueContext,\n} from './ComboboxRootContext';\nimport { selectors, type State as StoreState } from '../store';\nimport { useOpenChangeComplete } from '../../utils/useOpenChangeComplete';\nimport { useFieldRootContext } from '../../field/root/FieldRootContext';\nimport { useField } from '../../field/useField';\nimport { useFormContext } from '../../form/FormContext';\nimport { useLabelableId } from '../../labelable-provider/useLabelableId';\nimport { createCollatorItemFilter, createSingleSelectionCollatorFilter } from './utils';\nimport { useCoreFilter } from './utils/useFilter';\nimport { useTransitionStatus } from '../../utils/useTransitionStatus';\nimport { EMPTY_ARRAY, EMPTY_OBJECT } from '../../utils/constants';\nimport { useOpenInteractionType } from '../../utils/useOpenInteractionType';\nimport { HTMLProps } from '../../utils/types';\nimport { useValueChanged } from '../../utils/useValueChanged';\nimport { NOOP } from '../../utils/noop';\nimport {\n  stringifyAsLabel,\n  stringifyAsValue,\n  Group,\n  isGroupedItems,\n} from '../../utils/resolveValueLabel';\nimport {\n  compareItemEquality,\n  defaultItemEquality,\n  findItemIndex,\n  removeItem,\n  selectedValueIncludes,\n} from '../../utils/itemEquality';\nimport { INITIAL_LAST_HIGHLIGHT, NO_ACTIVE_VALUE } from './utils/constants';\n\n/**\n * @internal\n */\nexport function AriaCombobox<Value, Mode extends SelectionMode = 'none'>(\n  props: Omit<AriaComboboxProps<Value, Mode>, 'items'> & {\n    items: readonly Group<any>[];\n  },\n): React.JSX.Element;\nexport function AriaCombobox<Value, Mode extends SelectionMode = 'none'>(\n  props: Omit<AriaComboboxProps<Value, Mode>, 'items'> & {\n    items?: readonly any[] | undefined;\n  },\n): React.JSX.Element;\nexport function AriaCombobox<Value = any, Mode extends SelectionMode = 'none'>(\n  props: AriaComboboxProps<Value, Mode>,\n): React.JSX.Element {\n  const {\n    id: idProp,\n    onOpenChangeComplete: onOpenChangeCompleteProp,\n    defaultSelectedValue = null,\n    selectedValue: selectedValueProp,\n    onSelectedValueChange,\n    defaultInputValue: defaultInputValueProp,\n    inputValue: inputValueProp,\n    selectionMode = 'none',\n    onItemHighlighted: onItemHighlightedProp,\n    name: nameProp,\n    disabled: disabledProp = false,\n    readOnly = false,\n    required = false,\n    inputRef: inputRefProp,\n    grid = false,\n    items,\n    filteredItems: filteredItemsProp,\n    filter: filterProp,\n    openOnInputClick = true,\n    autoHighlight = false,\n    keepHighlight = false,\n    highlightItemOnHover = true,\n    loopFocus = true,\n    itemToStringLabel,\n    itemToStringValue,\n    isItemEqualToValue = defaultItemEquality,\n    virtualized = false,\n    inline: inlineProp = false,\n    fillInputOnItemPress = true,\n    modal = false,\n    limit = -1,\n    autoComplete = 'list',\n    formAutoComplete,\n    locale,\n    submitOnItemClick = false,\n  } = props;\n\n  const { clearErrors } = useFormContext();\n  const {\n    setDirty,\n    validityData,\n    shouldValidateOnChange,\n    setFilled,\n    name: fieldName,\n    disabled: fieldDisabled,\n    setTouched,\n    setFocused,\n    validationMode,\n    validation,\n  } = useFieldRootContext();\n\n  const id = useLabelableId({ id: idProp });\n  const collatorFilter = useCoreFilter({ locale });\n\n  const [queryChangedAfterOpen, setQueryChangedAfterOpen] = React.useState(false);\n  const [closeQuery, setCloseQuery] = React.useState<string | null>(null);\n\n  const listRef = React.useRef<Array<HTMLElement | null>>([]);\n  const labelsRef = React.useRef<Array<string | null>>([]);\n  const popupRef = React.useRef<HTMLDivElement | null>(null);\n  const inputRef = React.useRef<HTMLInputElement | null>(null);\n  const startDismissRef = React.useRef<HTMLSpanElement | null>(null);\n  const endDismissRef = React.useRef<HTMLSpanElement | null>(null);\n  const emptyRef = React.useRef<HTMLDivElement | null>(null);\n  const keyboardActiveRef = React.useRef(true);\n  const hadInputClearRef = React.useRef(false);\n  const chipsContainerRef = React.useRef<HTMLDivElement | null>(null);\n  const clearRef = React.useRef<HTMLButtonElement | null>(null);\n  const selectionEventRef = React.useRef<MouseEvent | PointerEvent | KeyboardEvent | null>(null);\n  const lastHighlightRef = React.useRef(INITIAL_LAST_HIGHLIGHT);\n  const pendingQueryHighlightRef = React.useRef<null | { hasQuery: boolean }>(null);\n\n  /**\n   * Contains the currently visible list of item values post-filtering.\n   */\n  const valuesRef = React.useRef<any[]>([]);\n  /**\n   * Contains all item values in a stable, unfiltered order.\n   * This is only used when `items` prop is not provided.\n   * It accumulates values on first mount and does not remove them on unmount due to\n   * filtering, providing a stable index for selected value tracking.\n   */\n  const allValuesRef = React.useRef<any[]>([]);\n\n  const disabled = fieldDisabled || disabledProp;\n  const name = fieldName ?? nameProp;\n  const multiple = selectionMode === 'multiple';\n  const single = selectionMode === 'single';\n  const hasInputValue = inputValueProp !== undefined || defaultInputValueProp !== undefined;\n  const hasItems = items !== undefined;\n  const hasFilteredItemsProp = filteredItemsProp !== undefined;\n\n  let autoHighlightMode: false | 'input-change' | 'always';\n  if (autoHighlight === 'always') {\n    autoHighlightMode = 'always';\n  } else {\n    autoHighlightMode = autoHighlight ? 'input-change' : false;\n  }\n\n  const [selectedValue, setSelectedValueUnwrapped] = useControlled<any>({\n    controlled: selectedValueProp,\n    default: multiple ? (defaultSelectedValue ?? EMPTY_ARRAY) : defaultSelectedValue,\n    name: 'Combobox',\n    state: 'selectedValue',\n  });\n\n  const filter = React.useMemo(() => {\n    if (filterProp === null) {\n      return () => true;\n    }\n    if (filterProp !== undefined) {\n      return filterProp;\n    }\n    if (single && !queryChangedAfterOpen) {\n      return createSingleSelectionCollatorFilter(collatorFilter, itemToStringLabel, selectedValue);\n    }\n    return createCollatorItemFilter(collatorFilter, itemToStringLabel);\n  }, [filterProp, single, selectedValue, queryChangedAfterOpen, collatorFilter, itemToStringLabel]);\n\n  // If neither inputValue nor defaultInputValue are provided, derive it from the\n  // selected value for single mode so the input reflects the selection on mount.\n  const initialDefaultInputValue = useRefWithInit<React.ComponentProps<'input'>['defaultValue']>(\n    () => {\n      if (hasInputValue) {\n        return defaultInputValueProp ?? '';\n      }\n      if (single) {\n        return stringifyAsLabel(selectedValue, itemToStringLabel);\n      }\n      return '';\n    },\n  ).current;\n\n  const [inputValue, setInputValueUnwrapped] = useControlled({\n    controlled: inputValueProp,\n    default: initialDefaultInputValue,\n    name: 'Combobox',\n    state: 'inputValue',\n  });\n\n  const [open, setOpenUnwrapped] = useControlled({\n    controlled: props.open,\n    default: props.defaultOpen,\n    name: 'Combobox',\n    state: 'open',\n  });\n\n  const isGrouped = isGroupedItems(items);\n  const query = closeQuery ?? (inputValue === '' ? '' : String(inputValue).trim());\n\n  const selectedLabelString = single ? stringifyAsLabel(selectedValue, itemToStringLabel) : '';\n\n  const shouldBypassFiltering =\n    single &&\n    !queryChangedAfterOpen &&\n    query !== '' &&\n    selectedLabelString !== '' &&\n    selectedLabelString.length === query.length &&\n    collatorFilter.contains(selectedLabelString, query);\n\n  const filterQuery = shouldBypassFiltering ? '' : query;\n  const shouldIgnoreExternalFiltering = hasItems && hasFilteredItemsProp && shouldBypassFiltering;\n\n  const flatItems: readonly any[] = React.useMemo(() => {\n    if (!items) {\n      return EMPTY_ARRAY;\n    }\n\n    if (isGrouped) {\n      return items.flatMap((group) => group.items);\n    }\n\n    return items;\n  }, [items, isGrouped]);\n\n  const filteredItems: Value[] | Group<Value>[] = React.useMemo(() => {\n    if (filteredItemsProp && !shouldIgnoreExternalFiltering) {\n      return filteredItemsProp as Value[] | Group<Value>[];\n    }\n\n    if (!items) {\n      return EMPTY_ARRAY as Value[];\n    }\n\n    if (isGrouped) {\n      const groupedItems = items;\n      const resultingGroups: Group<Value>[] = [];\n      let currentCount = 0;\n\n      for (const group of groupedItems) {\n        if (limit > -1 && currentCount >= limit) {\n          break;\n        }\n\n        const candidateItems =\n          filterQuery === ''\n            ? group.items\n            : group.items.filter((item) => filter(item, filterQuery, itemToStringLabel));\n\n        if (candidateItems.length === 0) {\n          continue;\n        }\n\n        const remainingLimit = limit > -1 ? limit - currentCount : Infinity;\n        const itemsToTake = candidateItems.slice(0, remainingLimit);\n\n        if (itemsToTake.length > 0) {\n          const newGroup = { ...group, items: itemsToTake };\n          resultingGroups.push(newGroup);\n          currentCount += itemsToTake.length;\n        }\n      }\n\n      return resultingGroups;\n    }\n\n    if (filterQuery === '') {\n      return limit > -1\n        ? flatItems.slice(0, limit)\n        : // The cast here is done as `flatItems` is readonly.\n          // valuesRef.current, a mutable ref, can be set to `flatFilteredItems`, which may\n          // reference this exact readonly value, creating a mutation risk.\n          // However, <Combobox.Item> can never mutate this value as the mutating effect\n          // bails early when `items` is provided, and this is only ever returned\n          // when `items` is provided due to the early return at the top of this hook.\n          (flatItems as Value[]);\n    }\n\n    const limitedItems: Value[] = [];\n    for (const item of flatItems) {\n      if (limit > -1 && limitedItems.length >= limit) {\n        break;\n      }\n      if (filter(item, filterQuery, itemToStringLabel)) {\n        limitedItems.push(item);\n      }\n    }\n\n    return limitedItems;\n  }, [\n    filteredItemsProp,\n    shouldIgnoreExternalFiltering,\n    items,\n    isGrouped,\n    filterQuery,\n    limit,\n    filter,\n    itemToStringLabel,\n    flatItems,\n  ]);\n\n  const flatFilteredItems: Value[] = React.useMemo(() => {\n    if (isGrouped) {\n      const groups = filteredItems as Group<Value>[];\n      return groups.flatMap((g) => g.items);\n    }\n    return filteredItems as Value[];\n  }, [filteredItems, isGrouped]);\n\n  const store = useRefWithInit(\n    () =>\n      new Store<StoreState>({\n        id,\n        labelId: undefined,\n        selectedValue,\n        open,\n        filter,\n        query,\n        items,\n        selectionMode,\n        listRef,\n        labelsRef,\n        popupRef,\n        emptyRef,\n        inputRef,\n        startDismissRef,\n        endDismissRef,\n        keyboardActiveRef,\n        chipsContainerRef,\n        clearRef,\n        valuesRef,\n        allValuesRef,\n        selectionEventRef,\n        name,\n        disabled,\n        readOnly,\n        required,\n        grid,\n        isGrouped,\n        virtualized,\n        openOnInputClick,\n        itemToStringLabel,\n        isItemEqualToValue,\n        modal,\n        autoHighlight: autoHighlightMode,\n        submitOnItemClick,\n        hasInputValue,\n        mounted: false,\n        forceMounted: false,\n        transitionStatus: 'idle',\n        inline: inlineProp,\n        activeIndex: null,\n        selectedIndex: null,\n        popupProps: {},\n        inputProps: {},\n        triggerProps: {},\n        positionerElement: null,\n        listElement: null,\n        triggerElement: null,\n        inputElement: null,\n        inputGroupElement: null,\n        popupSide: null,\n        openMethod: null,\n        inputInsidePopup: true,\n        onOpenChangeComplete: onOpenChangeCompleteProp || NOOP,\n        // Placeholder callbacks replaced on first render\n        setOpen: NOOP,\n        setInputValue: NOOP,\n        setSelectedValue: NOOP,\n        setIndices: NOOP,\n        onItemHighlighted: NOOP,\n        handleSelection: NOOP,\n        getItemProps: () => EMPTY_OBJECT,\n        forceMount: NOOP,\n        requestSubmit: NOOP,\n      }),\n  ).current;\n\n  const fieldRawValue = selectionMode === 'none' ? inputValue : selectedValue;\n  const fieldStringValue = React.useMemo(() => {\n    if (selectionMode === 'none') {\n      return fieldRawValue;\n    }\n    if (Array.isArray(selectedValue)) {\n      return selectedValue.map((value) => stringifyAsValue(value, itemToStringValue));\n    }\n    return stringifyAsValue(selectedValue, itemToStringValue);\n  }, [fieldRawValue, itemToStringValue, selectionMode, selectedValue]);\n\n  const onItemHighlighted = useStableCallback(onItemHighlightedProp);\n  const onOpenChangeComplete = useStableCallback(onOpenChangeCompleteProp);\n\n  const activeIndex = useStore(store, selectors.activeIndex);\n  const selectedIndex = useStore(store, selectors.selectedIndex);\n  const positionerElement = useStore(store, selectors.positionerElement);\n  const listElement = useStore(store, selectors.listElement);\n  const triggerElement = useStore(store, selectors.triggerElement);\n  const inputElement = useStore(store, selectors.inputElement);\n  const inputGroupElement = useStore(store, selectors.inputGroupElement);\n  const inline = useStore(store, selectors.inline);\n  const inputInsidePopup = useStore(store, selectors.inputInsidePopup);\n\n  const triggerRef = useValueAsRef(triggerElement);\n\n  const { mounted, setMounted, transitionStatus } = useTransitionStatus(open);\n  const { openMethod, triggerProps } = useOpenInteractionType(open);\n\n  useField({\n    id,\n    name,\n    commit: validation.commit,\n    value: fieldRawValue,\n    controlRef: inputInsidePopup ? triggerRef : inputRef,\n    getValue: () => fieldStringValue,\n  });\n\n  const forceMount = useStableCallback(() => {\n    if (items) {\n      // Ensure typeahead works on a closed list.\n      labelsRef.current = flatFilteredItems.map((item) =>\n        stringifyAsLabel(item, itemToStringLabel),\n      );\n    } else {\n      store.set('forceMounted', true);\n    }\n  });\n\n  const initialSelectedValueRef = React.useRef(selectedValue);\n  useIsoLayoutEffect(() => {\n    // Ensure the values and labels are registered for programmatic value changes.\n    if (selectedValue !== initialSelectedValueRef.current) {\n      forceMount();\n    }\n  }, [forceMount, selectedValue]);\n\n  const setIndices = useStableCallback(\n    (options: {\n      activeIndex?: number | null | undefined;\n      selectedIndex?: number | null | undefined;\n      type?: 'none' | 'keyboard' | 'pointer' | undefined;\n    }) => {\n      store.update(options);\n      const type: AriaCombobox.HighlightEventReason = options.type || 'none';\n      if (options.activeIndex === undefined) {\n        return;\n      }\n\n      if (options.activeIndex === null) {\n        if (lastHighlightRef.current !== INITIAL_LAST_HIGHLIGHT) {\n          lastHighlightRef.current = INITIAL_LAST_HIGHLIGHT;\n          onItemHighlighted(undefined, createGenericEventDetails(type, undefined, { index: -1 }));\n        }\n      } else {\n        const activeValue = valuesRef.current[options.activeIndex];\n        lastHighlightRef.current = { value: activeValue, index: options.activeIndex };\n        onItemHighlighted(\n          activeValue,\n          createGenericEventDetails(type, undefined, {\n            index: options.activeIndex,\n          }),\n        );\n      }\n    },\n  );\n\n  const setInputValue = useStableCallback(\n    (next: string, eventDetails: AriaCombobox.ChangeEventDetails) => {\n      hadInputClearRef.current = eventDetails.reason === REASONS.inputClear;\n\n      props.onInputValueChange?.(next, eventDetails);\n\n      if (eventDetails.isCanceled) {\n        return;\n      }\n\n      // If user is typing, ensure we don't auto-highlight on open due to a race\n      // with the post-open effect that sets this flag.\n      if (eventDetails.reason === REASONS.inputChange) {\n        const event = eventDetails.event as Event;\n        const inputType = (event as InputEvent).inputType;\n        // Treat composition commits as typed input; autofill may omit `inputType` or\n        // report `insertReplacementText`.\n        const isTypedInput =\n          event.type === 'compositionend' ||\n          (inputType != null && inputType !== '' && inputType !== 'insertReplacementText');\n        if (isTypedInput) {\n          const hasQuery = next.trim() !== '';\n          if (hasQuery) {\n            setQueryChangedAfterOpen(true);\n          }\n          // Defer index updates until after the filtered items have been derived to ensure\n          // `onItemHighlighted` receives the latest item.\n          pendingQueryHighlightRef.current = { hasQuery };\n          if (hasQuery && autoHighlightMode && store.state.activeIndex == null) {\n            store.set('activeIndex', 0);\n          }\n        }\n      }\n\n      setInputValueUnwrapped(next);\n    },\n  );\n\n  const setOpen = useStableCallback(\n    (nextOpen: boolean, eventDetails: AriaCombobox.ChangeEventDetails) => {\n      if (open === nextOpen) {\n        return;\n      }\n\n      // If the `Empty` component is not used, the positioner or popup should be hidden\n      // with CSS. In this case, allow the Escape key to bubble to close a parent popup\n      // if there are no items to show.\n      if (\n        eventDetails.reason === 'escape-key' &&\n        hasItems &&\n        flatFilteredItems.length === 0 &&\n        !store.state.emptyRef.current\n      ) {\n        eventDetails.allowPropagation();\n      }\n\n      props.onOpenChange?.(nextOpen, eventDetails);\n\n      if (eventDetails.isCanceled) {\n        return;\n      }\n\n      if (!nextOpen && queryChangedAfterOpen) {\n        if (single) {\n          if (!inline) {\n            setCloseQuery(query);\n          }\n          // Avoid a flicker when closing the popup with an empty query.\n          if (query === '') {\n            setQueryChangedAfterOpen(false);\n          }\n        } else if (multiple) {\n          if (inline || inputInsidePopup) {\n            setIndices({ activeIndex: null });\n          } else {\n            // Freeze the current query so filtering remains stable while exiting.\n            setCloseQuery(query);\n          }\n          // Clear the input immediately on close while retaining filtering via closeQuery for exit animations\n          // if the input is outside the popup.\n          setInputValue('', createChangeEventDetails(REASONS.inputClear, eventDetails.event));\n        }\n      }\n\n      setOpenUnwrapped(nextOpen);\n\n      if (\n        !nextOpen &&\n        inputInsidePopup &&\n        (eventDetails.reason === REASONS.focusOut || eventDetails.reason === REASONS.outsidePress)\n      ) {\n        setTouched(true);\n        setFocused(false);\n\n        if (validationMode === 'onBlur') {\n          const valueToValidate = selectionMode === 'none' ? inputValue : selectedValue;\n          validation.commit(valueToValidate);\n        }\n      }\n    },\n  );\n\n  const setSelectedValue = useStableCallback(\n    (nextValue: Value | Value[] | null, eventDetails: AriaCombobox.ChangeEventDetails) => {\n      // Cast to `any` due to conditional value type (single vs. multiple).\n      // The runtime implementation already ensures the correct value shape.\n      onSelectedValueChange?.(nextValue as any, eventDetails);\n\n      if (eventDetails.isCanceled) {\n        return;\n      }\n\n      setSelectedValueUnwrapped(nextValue);\n\n      const shouldFillInput =\n        (selectionMode === 'none' && popupRef.current && fillInputOnItemPress) ||\n        (single && !store.state.inputInsidePopup);\n\n      if (shouldFillInput) {\n        setInputValue(\n          stringifyAsLabel(nextValue, itemToStringLabel),\n          createChangeEventDetails(eventDetails.reason, eventDetails.event),\n        );\n      }\n\n      if (\n        single &&\n        nextValue != null &&\n        eventDetails.reason !== REASONS.inputChange &&\n        queryChangedAfterOpen &&\n        !inline\n      ) {\n        setCloseQuery(query);\n      }\n    },\n  );\n\n  const handleSelection = useStableCallback(\n    (event: MouseEvent | PointerEvent | KeyboardEvent, passedValue?: any) => {\n      let itemValue = passedValue;\n      if (itemValue === undefined) {\n        if (activeIndex === null) {\n          return;\n        }\n        itemValue = valuesRef.current[activeIndex];\n      }\n\n      const targetEl = getTarget(event) as HTMLElement | null;\n      const overrideEvent = selectionEventRef.current ?? event;\n      selectionEventRef.current = null;\n      const eventDetails = createChangeEventDetails(REASONS.itemPress, overrideEvent);\n\n      // Let the link handle the click.\n      const href = targetEl?.closest('a')?.getAttribute('href');\n      if (href) {\n        if (href.startsWith('#')) {\n          setOpen(false, eventDetails);\n        }\n        return;\n      }\n\n      if (multiple) {\n        const currentSelectedValue = Array.isArray(selectedValue) ? selectedValue : [];\n        const isCurrentlySelected = selectedValueIncludes(\n          currentSelectedValue,\n          itemValue,\n          store.state.isItemEqualToValue,\n        );\n        const nextValue = isCurrentlySelected\n          ? removeItem(currentSelectedValue, itemValue, store.state.isItemEqualToValue)\n          : [...currentSelectedValue, itemValue];\n\n        setSelectedValue(nextValue, eventDetails);\n\n        const wasFiltering = inputRef.current ? inputRef.current.value.trim() !== '' : false;\n        if (!wasFiltering) {\n          return;\n        }\n\n        if (store.state.inputInsidePopup) {\n          setInputValue('', createChangeEventDetails(REASONS.inputClear, eventDetails.event));\n        } else {\n          setOpen(false, eventDetails);\n        }\n      } else {\n        setSelectedValue(itemValue, eventDetails);\n        setOpen(false, eventDetails);\n      }\n    },\n  );\n\n  const requestSubmit = useStableCallback(() => {\n    if (!store.state.submitOnItemClick) {\n      return;\n    }\n\n    const form = store.state.inputElement?.form;\n    if (form && typeof form.requestSubmit === 'function') {\n      form.requestSubmit();\n    }\n  });\n\n  const handleUnmount = useStableCallback(() => {\n    setMounted(false);\n    onOpenChangeComplete?.(false);\n    setQueryChangedAfterOpen(false);\n    setCloseQuery(null);\n\n    if (selectionMode === 'none') {\n      setIndices({ activeIndex: null, selectedIndex: null });\n    } else {\n      setIndices({ activeIndex: null });\n    }\n\n    // Multiple selection mode:\n    // If the user typed a filter and didn't select in multiple mode, clear the input\n    // after close completes to avoid mid-exit flicker and start fresh on next open.\n    if (\n      multiple &&\n      inputRef.current &&\n      inputRef.current.value !== '' &&\n      !hadInputClearRef.current\n    ) {\n      setInputValue('', createChangeEventDetails(REASONS.inputClear));\n    }\n\n    // Single selection mode:\n    // - If input is rendered inside the popup, clear it so the next open is blank\n    // - If input is outside the popup, sync it to the selected value\n    if (single) {\n      if (store.state.inputInsidePopup) {\n        if (inputRef.current && inputRef.current.value !== '') {\n          setInputValue('', createChangeEventDetails(REASONS.inputClear));\n        }\n      } else {\n        const stringVal = stringifyAsLabel(selectedValue, itemToStringLabel);\n        if (inputRef.current && inputRef.current.value !== stringVal) {\n          // If no selection was made, treat this as clearing the typed filter.\n          const reason = stringVal === '' ? REASONS.inputClear : REASONS.none;\n          setInputValue(stringVal, createChangeEventDetails(reason));\n        }\n      }\n    }\n  });\n\n  // Support composing the Dialog component around an inline combobox.\n  // `[role=\"dialog\"]` is more interoperable than using a context, e.g. it can work\n  // with third-party modal libraries, though the limitation is that the closest\n  // `role=dialog` part must be the animated element.\n  const resolvedPopupRef: React.RefObject<HTMLElement | null> = React.useMemo(() => {\n    if (inline && positionerElement) {\n      return { current: positionerElement.closest('[role=\"dialog\"]') };\n    }\n    return popupRef;\n  }, [inline, positionerElement]);\n\n  useOpenChangeComplete({\n    enabled: !props.actionsRef,\n    open,\n    ref: resolvedPopupRef,\n    onComplete() {\n      if (!open) {\n        handleUnmount();\n      }\n    },\n  });\n\n  React.useImperativeHandle(props.actionsRef, () => ({ unmount: handleUnmount }), [handleUnmount]);\n\n  useIsoLayoutEffect(\n    function syncSelectedIndex() {\n      if (open || selectionMode === 'none') {\n        return;\n      }\n\n      const registry = items ? flatItems : allValuesRef.current;\n\n      if (multiple) {\n        const currentValue = Array.isArray(selectedValue) ? selectedValue : [];\n        const lastValue = currentValue[currentValue.length - 1];\n        const lastIndex = findItemIndex(registry, lastValue, isItemEqualToValue);\n        setIndices({ selectedIndex: lastIndex === -1 ? null : lastIndex });\n      } else {\n        const index = findItemIndex(registry, selectedValue, isItemEqualToValue);\n        setIndices({ selectedIndex: index === -1 ? null : index });\n      }\n    },\n    [\n      open,\n      selectedValue,\n      items,\n      selectionMode,\n      flatItems,\n      multiple,\n      isItemEqualToValue,\n      setIndices,\n    ],\n  );\n\n  useIsoLayoutEffect(() => {\n    if (items) {\n      valuesRef.current = flatFilteredItems;\n      listRef.current.length = flatFilteredItems.length;\n    }\n  }, [items, flatFilteredItems]);\n\n  useIsoLayoutEffect(() => {\n    const pendingHighlight = pendingQueryHighlightRef.current;\n    if (pendingHighlight) {\n      if (pendingHighlight.hasQuery) {\n        if (autoHighlightMode) {\n          store.set('activeIndex', 0);\n        }\n      } else if (autoHighlightMode === 'always') {\n        store.set('activeIndex', 0);\n      }\n      pendingQueryHighlightRef.current = null;\n    }\n\n    if (!open && !inline) {\n      return;\n    }\n\n    const shouldUseFlatFilteredItems = hasItems || hasFilteredItemsProp;\n    const candidateItems = shouldUseFlatFilteredItems ? flatFilteredItems : valuesRef.current;\n    const storeActiveIndex = store.state.activeIndex;\n\n    if (storeActiveIndex == null) {\n      if (autoHighlightMode === 'always' && candidateItems.length > 0) {\n        store.set('activeIndex', 0);\n        return;\n      }\n      if (lastHighlightRef.current !== INITIAL_LAST_HIGHLIGHT) {\n        lastHighlightRef.current = INITIAL_LAST_HIGHLIGHT;\n        store.state.onItemHighlighted(\n          undefined,\n          createGenericEventDetails(REASONS.none, undefined, { index: -1 }),\n        );\n      }\n      return;\n    }\n\n    if (storeActiveIndex >= candidateItems.length) {\n      if (lastHighlightRef.current !== INITIAL_LAST_HIGHLIGHT) {\n        lastHighlightRef.current = INITIAL_LAST_HIGHLIGHT;\n        store.state.onItemHighlighted(\n          undefined,\n          createGenericEventDetails(REASONS.none, undefined, { index: -1 }),\n        );\n      }\n      store.set('activeIndex', null);\n      return;\n    }\n\n    const itemValue = candidateItems[storeActiveIndex];\n    const previouslyHighlightedItemValue = lastHighlightRef.current.value;\n    const isSameItem =\n      previouslyHighlightedItemValue !== NO_ACTIVE_VALUE &&\n      compareItemEquality(\n        itemValue,\n        previouslyHighlightedItemValue,\n        store.state.isItemEqualToValue,\n      );\n\n    if (lastHighlightRef.current.index !== storeActiveIndex || !isSameItem) {\n      lastHighlightRef.current = { value: itemValue, index: storeActiveIndex };\n      store.state.onItemHighlighted(\n        itemValue,\n        createGenericEventDetails(REASONS.none, undefined, { index: storeActiveIndex }),\n      );\n    }\n  }, [\n    activeIndex,\n    autoHighlightMode,\n    hasFilteredItemsProp,\n    hasItems,\n    flatFilteredItems,\n    inline,\n    open,\n    store,\n  ]);\n\n  useIsoLayoutEffect(() => {\n    if (selectionMode === 'none') {\n      setFilled(String(inputValue) !== '');\n      return;\n    }\n    setFilled(\n      multiple ? Array.isArray(selectedValue) && selectedValue.length > 0 : selectedValue != null,\n    );\n  }, [setFilled, selectionMode, inputValue, selectedValue, multiple]);\n\n  // Ensures that the active index is not set to 0 when the list is empty.\n  // This avoids needing to press ArrowDown twice under certain conditions.\n  React.useEffect(() => {\n    if (hasItems && autoHighlightMode && flatFilteredItems.length === 0) {\n      setIndices({ activeIndex: null });\n    }\n  }, [hasItems, autoHighlightMode, flatFilteredItems.length, setIndices]);\n\n  useValueChanged(query, () => {\n    if (!open || query === '' || query === String(initialDefaultInputValue)) {\n      return;\n    }\n    setQueryChangedAfterOpen(true);\n  });\n\n  useValueChanged(selectedValue, () => {\n    if (selectionMode === 'none') {\n      return;\n    }\n\n    clearErrors(name);\n    setDirty(selectedValue !== validityData.initialValue);\n\n    if (shouldValidateOnChange()) {\n      validation.commit(selectedValue);\n    } else {\n      validation.commit(selectedValue, true);\n    }\n\n    if (single && !hasInputValue && !inputInsidePopup) {\n      const nextInputValue = stringifyAsLabel(selectedValue, itemToStringLabel);\n\n      if (inputValue !== nextInputValue) {\n        setInputValue(nextInputValue, createChangeEventDetails(REASONS.none));\n      }\n    }\n  });\n\n  useValueChanged(inputValue, () => {\n    if (selectionMode !== 'none') {\n      return;\n    }\n\n    clearErrors(name);\n    setDirty(inputValue !== validityData.initialValue);\n\n    if (shouldValidateOnChange()) {\n      validation.commit(inputValue);\n    } else {\n      validation.commit(inputValue, true);\n    }\n  });\n\n  useValueChanged(items, () => {\n    if (!single || hasInputValue || inputInsidePopup || queryChangedAfterOpen) {\n      return;\n    }\n\n    const nextInputValue = stringifyAsLabel(selectedValue, itemToStringLabel);\n\n    if (inputValue !== nextInputValue) {\n      setInputValue(nextInputValue, createChangeEventDetails(REASONS.none));\n    }\n  });\n\n  const floatingRootContext = useFloatingRootContext({\n    open: inline ? true : open,\n    onOpenChange: setOpen,\n    elements: {\n      reference: inputInsidePopup ? triggerElement : inputElement,\n      floating: positionerElement,\n    },\n  });\n\n  let ariaHasPopup: 'grid' | 'listbox' | undefined;\n  let ariaExpanded: 'true' | 'false' | undefined;\n  if (!inline) {\n    ariaHasPopup = grid ? 'grid' : 'listbox';\n    ariaExpanded = open ? 'true' : 'false';\n  }\n\n  const role: ElementProps = React.useMemo(() => {\n    const isPlainInput = inputElement?.tagName === 'INPUT';\n    // During SSR and initial hydration, the input ref is not available yet.\n    // Assume an input-like control so combobox ARIA attributes are present.\n    const shouldTreatAsInput = inputElement == null || isPlainInput;\n    const shouldApplyAria = shouldTreatAsInput || open;\n\n    const reference = shouldTreatAsInput\n      ? ({\n          autoComplete: 'off',\n          spellCheck: 'false',\n          autoCorrect: 'off',\n          autoCapitalize: 'none',\n        } as HTMLProps<HTMLInputElement>)\n      : {};\n\n    if (shouldApplyAria) {\n      reference.role = 'combobox';\n      reference['aria-expanded'] = ariaExpanded;\n      reference['aria-haspopup'] = ariaHasPopup;\n      reference['aria-controls'] = open ? listElement?.id : undefined;\n      reference['aria-autocomplete'] = autoComplete;\n    }\n\n    return {\n      reference,\n      floating: { role: 'presentation' },\n    };\n  }, [inputElement, open, ariaExpanded, ariaHasPopup, listElement?.id, autoComplete]);\n\n  const click = useClick(floatingRootContext, {\n    enabled: !readOnly && !disabled && openOnInputClick,\n    event: 'mousedown-only',\n    toggle: false,\n    // Apply a small delay for touch to let mobile viewport/keyboard positioning settle.\n    // This avoids top-bottom flip flickers if the preferred position is \"top\" when first tapping.\n    touchOpenDelay: inputInsidePopup ? 0 : 100,\n    reason: REASONS.inputPress,\n  });\n\n  const dismiss = useDismiss(floatingRootContext, {\n    enabled: !readOnly && !disabled && !inline,\n    outsidePressEvent: {\n      mouse: 'sloppy',\n      // The visual viewport (affected by the mobile software keyboard) can be\n      // somewhat small. The user may want to scroll the screen to see more of\n      // the popup.\n      touch: 'intentional',\n    },\n    // Without a popup, let the Escape key bubble the event up to other popups' handlers.\n    bubbles: inline ? true : undefined,\n    outsidePress(event) {\n      const target = getTarget(event) as Element | null;\n      return (\n        !contains(triggerElement, target) &&\n        !contains(clearRef.current, target) &&\n        !contains(chipsContainerRef.current, target) &&\n        !contains(inputGroupElement, target)\n      );\n    },\n  });\n\n  const listNavigation = useListNavigation(floatingRootContext, {\n    enabled: !readOnly && !disabled,\n    id,\n    listRef,\n    activeIndex,\n    selectedIndex,\n    virtual: true,\n    loopFocus,\n    allowEscape: loopFocus && !autoHighlightMode,\n    focusItemOnOpen:\n      queryChangedAfterOpen || (selectionMode === 'none' && !autoHighlightMode) ? false : 'auto',\n    focusItemOnHover: highlightItemOnHover,\n    resetOnPointerLeave: !keepHighlight,\n    // `cols` > 1 enables grid navigation.\n    // Since <Combobox.Row> infers column sizes (and is required when building a grid),\n    // it works correctly even with a value of `2`.\n    // Floating UI tests don't require `role=\"row\"` wrappers, so retains the number API.\n    cols: grid ? 2 : 1,\n    orientation: grid ? 'horizontal' : undefined,\n    disabledIndices: EMPTY_ARRAY as number[],\n    onNavigate(nextActiveIndex, event) {\n      // Retain the highlight only while actually transitioning out or closed.\n      if ((!event && !open) || transitionStatus === 'ending') {\n        return;\n      }\n\n      if (!event) {\n        setIndices({\n          activeIndex: nextActiveIndex,\n        });\n      } else {\n        setIndices({\n          activeIndex: nextActiveIndex,\n          type: keyboardActiveRef.current ? 'keyboard' : 'pointer',\n        });\n      }\n    },\n  });\n\n  const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions([\n    role,\n    click,\n    dismiss,\n    listNavigation,\n  ]);\n\n  useOnFirstRender(() => {\n    store.update({\n      inline: inlineProp,\n      popupProps: getFloatingProps(),\n      inputProps: getReferenceProps(),\n      triggerProps,\n      getItemProps,\n      setOpen,\n      setInputValue,\n      setSelectedValue,\n      setIndices,\n      onItemHighlighted,\n      handleSelection,\n      forceMount,\n      requestSubmit,\n    });\n  });\n\n  useIsoLayoutEffect(() => {\n    store.update({\n      id,\n      selectedValue,\n      open,\n      mounted,\n      transitionStatus,\n      items,\n      inline: inlineProp,\n      popupProps: getFloatingProps(),\n      inputProps: getReferenceProps(),\n      triggerProps,\n      openMethod,\n      getItemProps,\n      selectionMode,\n      name,\n      disabled,\n      readOnly,\n      required,\n      grid,\n      isGrouped,\n      virtualized,\n      onOpenChangeComplete,\n      openOnInputClick,\n      itemToStringLabel,\n      modal,\n      autoHighlight: autoHighlightMode,\n      isItemEqualToValue,\n      submitOnItemClick,\n      hasInputValue,\n      requestSubmit,\n    });\n  }, [\n    store,\n    id,\n    selectedValue,\n    open,\n    mounted,\n    transitionStatus,\n    items,\n    getFloatingProps,\n    getReferenceProps,\n    getItemProps,\n    openMethod,\n    triggerProps,\n    selectionMode,\n    name,\n    disabled,\n    readOnly,\n    required,\n    validation,\n    grid,\n    isGrouped,\n    virtualized,\n    onOpenChangeComplete,\n    openOnInputClick,\n    itemToStringLabel,\n    modal,\n    isItemEqualToValue,\n    submitOnItemClick,\n    hasInputValue,\n    inlineProp,\n    requestSubmit,\n    autoHighlightMode,\n  ]);\n\n  const hiddenInputRef = useMergedRefs(inputRefProp, validation.inputRef);\n\n  const itemsContextValue: ComboboxDerivedItemsContext = React.useMemo(\n    () => ({\n      query,\n      hasItems,\n      filteredItems,\n      flatFilteredItems,\n    }),\n    [query, hasItems, filteredItems, flatFilteredItems],\n  );\n\n  const serializedValue = React.useMemo(() => {\n    if (Array.isArray(fieldRawValue)) {\n      return '';\n    }\n    return stringifyAsValue(fieldRawValue, itemToStringValue);\n  }, [fieldRawValue, itemToStringValue]);\n\n  const hasMultipleSelection = multiple && Array.isArray(selectedValue) && selectedValue.length > 0;\n  const hiddenInputName = multiple || selectionMode === 'none' ? undefined : name;\n\n  const hiddenInputs = React.useMemo(() => {\n    if (!multiple || !Array.isArray(selectedValue) || !name) {\n      return null;\n    }\n\n    return selectedValue.map((value: Value) => {\n      const currentSerializedValue = stringifyAsValue(value, itemToStringValue);\n      return (\n        <input\n          key={currentSerializedValue}\n          type=\"hidden\"\n          name={name}\n          value={currentSerializedValue}\n        />\n      );\n    });\n  }, [multiple, selectedValue, name, itemToStringValue]);\n\n  const children = (\n    <React.Fragment>\n      {props.children}\n      <input\n        {...validation.getInputValidationProps({\n          // Move focus when the hidden input is focused.\n          onFocus() {\n            if (inputInsidePopup) {\n              triggerElement?.focus();\n              return;\n            }\n\n            (inputRef.current || triggerElement)?.focus();\n          },\n          // Handle browser autofill.\n          onChange(event: React.ChangeEvent<HTMLInputElement>) {\n            // Workaround for https://github.com/facebook/react/issues/9023\n            if (event.nativeEvent.defaultPrevented) {\n              return;\n            }\n\n            const nextValue = event.target.value;\n            const details = createChangeEventDetails(REASONS.none, event.nativeEvent);\n\n            function handleChange() {\n              // Browser autofill only writes a single scalar value.\n              if (multiple) {\n                return;\n              }\n\n              if (selectionMode === 'none') {\n                setDirty(nextValue !== validityData.initialValue);\n                setInputValue(nextValue, details);\n\n                if (shouldValidateOnChange()) {\n                  validation.commit(nextValue);\n                }\n                return;\n              }\n\n              const matchingValue = valuesRef.current.find((v) => {\n                const candidate = stringifyAsValue(v, itemToStringValue);\n                if (candidate.toLowerCase() === nextValue.toLowerCase()) {\n                  return true;\n                }\n                return false;\n              });\n\n              if (matchingValue != null) {\n                setDirty(matchingValue !== validityData.initialValue);\n                setSelectedValue?.(matchingValue, details);\n\n                if (shouldValidateOnChange()) {\n                  validation.commit(matchingValue);\n                }\n              }\n            }\n\n            if (items) {\n              handleChange();\n            } else {\n              forceMount();\n              queueMicrotask(handleChange);\n            }\n          },\n        })}\n        id={id && hiddenInputName == null ? `${id}-hidden-input` : undefined}\n        name={hiddenInputName}\n        autoComplete={formAutoComplete}\n        disabled={disabled}\n        required={required && !hasMultipleSelection}\n        readOnly={readOnly}\n        value={serializedValue}\n        ref={hiddenInputRef}\n        style={hiddenInputName ? visuallyHiddenInput : visuallyHidden}\n        tabIndex={-1}\n        aria-hidden\n      />\n      {hiddenInputs}\n    </React.Fragment>\n  );\n\n  return (\n    <ComboboxRootContext.Provider value={store}>\n      <ComboboxFloatingContext.Provider value={floatingRootContext}>\n        <ComboboxDerivedItemsContext.Provider value={itemsContextValue}>\n          <ComboboxInputValueContext.Provider value={inputValue}>\n            {children}\n          </ComboboxInputValueContext.Provider>\n        </ComboboxDerivedItemsContext.Provider>\n      </ComboboxFloatingContext.Provider>\n    </ComboboxRootContext.Provider>\n  );\n}\n\ntype SelectionMode = 'single' | 'multiple' | 'none';\n\ntype ComboboxItemValueType<ItemValue, Mode extends SelectionMode> = Mode extends 'multiple'\n  ? ItemValue[]\n  : ItemValue;\n\ninterface ComboboxRootProps<ItemValue> {\n  children?: React.ReactNode;\n  /**\n   * Identifies the field when a form is submitted.\n   */\n  name?: string | undefined;\n  /**\n   * The id of the component.\n   */\n  id?: string | undefined;\n  /**\n   * Whether the user must choose a value before submitting a form.\n   * @default false\n   */\n  required?: boolean | undefined;\n  /**\n   * Whether the user should be unable to choose a different option from the popup.\n   * @default false\n   */\n  readOnly?: boolean | undefined;\n  /**\n   * Whether the component should ignore user interaction.\n   * @default false\n   */\n  disabled?: boolean | undefined;\n  /**\n   * Whether the popup is initially open.\n   *\n   * To render a controlled popup, use the `open` prop instead.\n   * @default false\n   */\n  defaultOpen?: boolean | undefined;\n  /**\n   * Whether the popup is currently open. Use when controlled.\n   */\n  open?: boolean | undefined;\n  /**\n   * Event handler called when the popup is opened or closed.\n   */\n  onOpenChange?:\n    | ((open: boolean, eventDetails: AriaCombobox.ChangeEventDetails) => void)\n    | undefined;\n  /**\n   * Event handler called after any animations complete when the popup is opened or closed.\n   */\n  onOpenChangeComplete?: ((open: boolean) => void) | undefined;\n  /**\n   * Whether the popup opens when clicking the input.\n   * @default true\n   */\n  openOnInputClick?: boolean | undefined;\n  /**\n   * Whether the first matching item is highlighted automatically.\n   * - `false`: do not highlight automatically.\n   * - `true`: highlight after the user types and keep the highlight while the query changes.\n   * - `'always'`: highlight the first item as soon as the list opens.\n   * @default false\n   */\n  autoHighlight?: boolean | 'always' | undefined;\n  /**\n   * Whether the highlighted item should be preserved when the pointer leaves the list.\n   * @default false\n   */\n  keepHighlight?: boolean | undefined;\n  /**\n   * Whether moving the pointer over items should highlight them.\n   * Disabling this prop allows CSS `:hover` to be differentiated from the `:focus` (`data-highlighted`) state.\n   * @default true\n   */\n  highlightItemOnHover?: boolean | undefined;\n  /**\n   * Whether to loop keyboard focus back to the input when the end of the list is reached while using the arrow keys. The first item can then be reached by pressing <kbd>ArrowDown</kbd> again from the input, or the last item can be reached by pressing <kbd>ArrowUp</kbd> from the input.\n   * The input is always included in the focus loop per [ARIA Authoring Practices](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/).\n   * When disabled, focus does not move when on the last element and the user presses <kbd>ArrowDown</kbd>, or when on the first element and the user presses <kbd>ArrowUp</kbd>.\n   * @default true\n   */\n  loopFocus?: boolean | undefined;\n  /**\n   * The input value of the combobox. Use when controlled.\n   */\n  inputValue?: React.ComponentProps<'input'>['value'] | undefined;\n  /**\n   * Callback fired when the input value of the combobox changes.\n   */\n  onInputValueChange?:\n    | ((value: string, eventDetails: AriaCombobox.ChangeEventDetails) => void)\n    | undefined;\n  /**\n   * The uncontrolled input value when initially rendered.\n   *\n   * To render a controlled input, use the `inputValue` prop instead.\n   */\n  defaultInputValue?: React.ComponentProps<'input'>['defaultValue'] | undefined;\n  /**\n   * A ref to imperative actions.\n   * - `unmount`: When specified, the combobox will not be unmounted when closed.\n   * Instead, the `unmount` function must be called to unmount the combobox manually.\n   * Useful when the combobox's animation is controlled by an external library.\n   */\n  actionsRef?: React.RefObject<AriaCombobox.Actions | null> | undefined;\n  /**\n   * Callback fired when an item is highlighted or unhighlighted.\n   * Receives the highlighted item value (or `undefined` if no item is highlighted) and event details with a `reason` property describing why the highlight changed.\n   * The `reason` can be:\n   * - `'keyboard'`: the highlight changed due to keyboard navigation.\n   * - `'pointer'`: the highlight changed due to pointer hovering.\n   * - `'none'`: the highlight changed programmatically.\n   */\n  onItemHighlighted?:\n    | ((itemValue: ItemValue | undefined, eventDetails: AriaCombobox.HighlightEventDetails) => void)\n    | undefined;\n  /**\n   * A ref to the hidden input element.\n   */\n  inputRef?: React.Ref<HTMLInputElement> | undefined;\n  /**\n   * Whether list items are presented in a grid layout.\n   * When enabled, arrow keys navigate across rows and columns inferred from DOM rows.\n   * @default false\n   */\n  grid?: boolean | undefined;\n  /**\n   * The items to be displayed in the list.\n   * Can be either a flat array of items or an array of groups with items.\n   */\n  items?: readonly any[] | readonly Group<any>[] | undefined;\n  /**\n   * Filtered items to display in the list.\n   * When provided, the list will use these items instead of filtering the `items` prop internally.\n   * Use when you want to control filtering logic externally with the `useFilter()` hook.\n   */\n  filteredItems?: readonly any[] | readonly Group<any>[] | undefined;\n  /**\n   * Filter function used to match items vs input query.\n   */\n  filter?:\n    | null\n    | ((\n        itemValue: ItemValue,\n        query: string,\n        itemToString?: (itemValue: ItemValue) => string,\n      ) => boolean)\n    | undefined;\n  /**\n   * When the item values are objects (`<Combobox.Item value={object}>`), this function converts the object value to a string representation for display in the input.\n   * If the shape of the object is `{ value, label }`, the label will be used automatically without needing to specify this prop.\n   */\n  itemToStringLabel?: ((itemValue: ItemValue) => string) | undefined;\n  /**\n   * When the item values are objects (`<Combobox.Item value={object}>`), this function converts the object value to a string representation for form submission.\n   * If the shape of the object is `{ value, label }`, the value will be used automatically without needing to specify this prop.\n   */\n  itemToStringValue?: ((itemValue: ItemValue) => string) | undefined;\n  /**\n   * Custom comparison logic used to determine if a combobox item value matches the current selected value. Useful when item values are objects without matching referentially.\n   * Defaults to `Object.is` comparison.\n   */\n  isItemEqualToValue?: ((itemValue: ItemValue, value: ItemValue) => boolean) | undefined;\n  /**\n   * Whether the items are being externally virtualized.\n   * @default false\n   */\n  virtualized?: boolean | undefined;\n  /**\n   * Whether the list is rendered inline without using the popup.\n   * @default false\n   */\n  inline?: boolean | undefined;\n  /**\n   * Determines if the popup enters a modal state when open.\n   * - `true`: user interaction is limited to the popup: document page scroll is locked and pointer interactions on outside elements are disabled.\n   * - `false`: user interaction with the rest of the document is allowed.\n   * @default false\n   */\n  modal?: boolean | undefined;\n  /**\n   * The maximum number of items to display in the list.\n   * @default -1\n   */\n  limit?: number | undefined;\n  /**\n   * Controls how the component behaves with respect to list filtering and inline autocompletion.\n   * - `list` (default): items are dynamically filtered based on the input value. The input value does not change based on the active item.\n   * - `both`: items are dynamically filtered based on the input value, which will temporarily change based on the active item (inline autocompletion).\n   * - `inline`: items are static (not filtered), and the input value will temporarily change based on the active item (inline autocompletion).\n   * - `none`: items are static (not filtered), and the input value will not change based on the active item.\n   * @default 'list'\n   */\n  autoComplete?: 'list' | 'both' | 'inline' | 'none' | undefined;\n  /**\n   * Provides a hint to the browser for autofill on the hidden input element.\n   * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/autocomplete\n   */\n  formAutoComplete?: string | undefined;\n  /**\n   * The locale to use for string comparison.\n   * Defaults to the user's runtime locale.\n   */\n  locale?: Intl.LocalesArgument | undefined;\n  /**\n   * Whether clicking an item should submit the owning form.\n   * @default false\n   */\n  submitOnItemClick?: boolean | undefined;\n  /**\n   * INTERNAL: When `selectionMode` is `none`, controls whether selecting an item fills the input.\n   */\n  fillInputOnItemPress?: boolean | undefined;\n}\n\nexport interface AriaComboboxState {}\n\nexport type AriaComboboxProps<\n  Value,\n  Mode extends SelectionMode = 'none',\n> = ComboboxRootProps<Value> & {\n  /**\n   * How the combobox should remember the selected value.\n   * - `single`: Remembers the last selected value.\n   * - `multiple`: Remember all selected values.\n   * - `none`: Do not remember the selected value.\n   * @default 'none'\n   */\n  selectionMode?: Mode | undefined;\n  /**\n   * The selected value of the combobox. Use when controlled.\n   */\n  selectedValue?: ComboboxItemValueType<Value, Mode> | undefined;\n  /**\n   * The uncontrolled selected value of the combobox when it's initially rendered.\n   *\n   * To render a controlled combobox, use the `selectedValue` prop instead.\n   */\n  defaultSelectedValue?: ComboboxItemValueType<Value, Mode> | null | undefined;\n  /**\n   * Callback fired when the selected value of the combobox changes.\n   */\n  onSelectedValueChange?:\n    | ((\n        value: ComboboxItemValueType<Value, Mode>,\n        eventDetails: AriaCombobox.ChangeEventDetails,\n      ) => void)\n    | undefined;\n};\n\nexport namespace AriaCombobox {\n  export type Props<Value, Mode extends SelectionMode = 'none'> = AriaComboboxProps<Value, Mode>;\n  export type State = AriaComboboxState;\n\n  export interface Actions {\n    unmount: () => void;\n  }\n\n  export type HighlightEventReason = 'keyboard' | 'pointer' | 'none';\n  export type HighlightEventDetails = BaseUIGenericEventDetails<\n    HighlightEventReason,\n    { index: number }\n  >;\n\n  export type ChangeEventReason =\n    | typeof REASONS.triggerPress\n    | typeof REASONS.outsidePress\n    | typeof REASONS.itemPress\n    | typeof REASONS.closePress\n    | typeof REASONS.escapeKey\n    | typeof REASONS.listNavigation\n    | typeof REASONS.focusOut\n    | typeof REASONS.inputChange\n    | typeof REASONS.inputClear\n    | typeof REASONS.clearPress\n    | typeof REASONS.chipRemovePress\n    | typeof REASONS.none;\n  export type ChangeEventDetails = BaseUIChangeEventDetails<ChangeEventReason>;\n}\n"
  },
  {
    "path": "packages/react/src/combobox/root/ComboboxRoot.spec.tsx",
    "content": "import * as React from 'react';\nimport { Combobox } from '@base-ui/react/combobox';\nimport { mergeProps } from '../../merge-props';\n\nconst objectItems = [\n  { value: 'a', label: 'apple' },\n  { value: 'b', label: 'banana' },\n  { value: 'c', label: 'cherry' },\n];\n\nconst objectItemsReadonly = [\n  { value: 'a', label: 'apple' },\n  { value: 'b', label: 'banana' },\n  { value: 'c', label: 'cherry' },\n] as const;\n\nconst groupItemsReadonly = [\n  {\n    value: 'fruits',\n    items: [\n      { value: 'a', label: 'apple' },\n      { value: 'b', label: 'banana' },\n      { value: 'c', label: 'cherry' },\n    ],\n  },\n  {\n    value: 'vegetables',\n    items: [\n      { value: 'd', label: 'daikon' },\n      { value: 'e', label: 'endive' },\n      { value: 'f', label: 'fennel' },\n    ],\n  },\n] as const;\n\n<Combobox.Root\n  items={objectItems}\n  itemToStringValue={(item) => {\n    // @ts-expect-error - inference always comes from `value`/`defaultValue`\n    return item.value;\n  }}\n/>;\n\n<Combobox.Root\n  items={groupItemsReadonly}\n  itemToStringValue={(item) => {\n    // @ts-expect-error - inference always comes from `value`/`defaultValue`\n    return item.value;\n  }}\n/>;\n\n<Combobox.Root\n  items={groupItemsReadonly}\n  defaultValue={groupItemsReadonly[0].items[0]}\n  itemToStringValue={(item) => {\n    return item.label;\n  }}\n/>;\n\n<Combobox.Root\n  items={objectItems}\n  defaultValue=\"a\"\n  onValueChange={(value) => {\n    // @ts-expect-error - possibly null\n    value.startsWith('a');\n    value?.startsWith('a');\n  }}\n/>;\n\n<Combobox.Root\n  items={objectItemsReadonly}\n  defaultValue=\"a\"\n  onValueChange={(value) => {\n    // @ts-expect-error - possibly null\n    value.startsWith('a');\n    value?.startsWith('a');\n  }}\n/>;\n\n<Combobox.Root\n  items={objectItems}\n  value=\"a\"\n  onValueChange={(value) => {\n    // @ts-expect-error - possibly null\n    value.startsWith('a');\n  }}\n/>;\n\n<Combobox.Root\n  items={objectItems}\n  value={objectItems[0]}\n  onValueChange={(value) => {\n    // @ts-expect-error - possibly null\n    value.label;\n  }}\n/>;\n\n<Combobox.Root\n  items={objectItems}\n  defaultValue={objectItems[0]}\n  itemToStringLabel={(item) => {\n    return item.label;\n  }}\n  itemToStringValue={(item) => {\n    return item.value;\n  }}\n/>;\n\n<Combobox.Root\n  items={objectItems}\n  defaultValue={objectItems[0]}\n  itemToStringLabel={(item) => {\n    // @ts-expect-error\n    item.x;\n    return item.label;\n  }}\n  itemToStringValue={(item) => {\n    // @ts-expect-error\n    item.x;\n    return item.value;\n  }}\n  isItemEqualToValue={(a, b) => {\n    // @ts-expect-error\n    a.x === b.x;\n    return a.value === b.value;\n  }}\n/>;\n\n<Combobox.Root\n  defaultValue=\"a\"\n  itemToStringLabel={(item) => {\n    return item;\n  }}\n  itemToStringValue={(item) => {\n    return item;\n  }}\n  isItemEqualToValue={(a, b) => {\n    // @ts-expect-error\n    a.x === b.x;\n    return a === b;\n  }}\n/>;\n\n<Combobox.Root\n  multiple\n  // @ts-expect-error\n  defaultValue=\"javascript\"\n  onValueChange={(value) => {\n    value.pop();\n  }}\n/>;\n\n<Combobox.Root\n  multiple\n  defaultValue={['javascript', 'typescript']}\n  onValueChange={(value) => {\n    value.pop();\n  }}\n/>;\n\n<Combobox.Root\n  multiple={false}\n  // @ts-expect-error\n  defaultValue={['javascript', 'typescript']}\n  onValueChange={(value) => {\n    // @ts-expect-error\n    value.pop();\n  }}\n/>;\n\n<Combobox.Root\n  defaultValue=\"javascript\"\n  onValueChange={(value) => {\n    // @ts-expect-error\n    value.pop();\n  }}\n/>;\n\n<Combobox.Root\n  multiple\n  onValueChange={(value) => {\n    value.pop();\n  }}\n/>;\n\nfunction App() {\n  const [multiple, setMultiple] = React.useState(false);\n  return (\n    <Combobox.Root\n      multiple={multiple}\n      onValueChange={(value) => {\n        // @ts-expect-error\n        value.pop();\n      }}\n    />\n  );\n}\n\n<Combobox.Root\n  items={['a', 'b', 'c']}\n  onValueChange={(value) => {\n    // @ts-expect-error\n    value.length;\n  }}\n/>;\n\n<Combobox.Root\n  items={['a', 'b', 'c']}\n  defaultValue=\"test\"\n  onValueChange={(value) => {\n    // @ts-expect-error\n    value.length;\n  }}\n/>;\n\n<Combobox.Root\n  items={[\n    { id: 1, name: 'Alice' },\n    { id: 2, name: 'Bob' },\n  ]}\n  defaultValue={[{ id: 2, name: 'Bob' }]}\n  itemToStringLabel={(item) => item.name}\n  itemToStringValue={(item) => String(item.id)}\n  isItemEqualToValue={(item, value) => item.id === value.id}\n  defaultOpen\n  multiple\n/>;\n\n// Should accept null value\n<Combobox.Root\n  items={[\n    { id: 1, name: 'Alice' },\n    { id: 2, name: 'Bob' },\n  ]}\n  value={null}\n/>;\n\nfunction App2() {\n  const [value, setValue] = React.useState('a');\n  return (\n    <Combobox.Root\n      value={value}\n      onValueChange={(newValue) => {\n        // @ts-expect-error\n        newValue.length;\n        // @ts-expect-error - user is forced to type useState with null\n        // even if they don't want to allow null\n        setValue(newValue);\n      }}\n    />\n  );\n}\n\nfunction App3() {\n  const [value, setValue] = React.useState<string | null>('a');\n  return (\n    <Combobox.Root\n      value={value}\n      onValueChange={(newValue) => {\n        // @ts-expect-error\n        newValue.length;\n        setValue(newValue);\n      }}\n    />\n  );\n}\n\nmergeProps<typeof Combobox.Root<any>>(\n  {\n    value: '',\n  },\n  {},\n);\n\nexport function Wrapper<Value, Multiple extends boolean | undefined = false>(\n  props: Combobox.Root.Props<Value, Multiple>,\n) {\n  return <Combobox.Root {...props} />;\n}\n"
  },
  {
    "path": "packages/react/src/combobox/root/ComboboxRoot.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport {\n  act,\n  fireEvent,\n  flushMicrotasks,\n  screen,\n  waitFor,\n  ignoreActWarnings,\n  reactMajor,\n} from '@mui/internal-test-utils';\nimport { createRenderer, isJSDOM, popupConformanceTests } from '#test-utils';\nimport { Combobox } from '@base-ui/react/combobox';\nimport { Dialog } from '@base-ui/react/dialog';\nimport { Field } from '@base-ui/react/field';\nimport { Form } from '@base-ui/react/form';\nimport { useStore } from '@base-ui/utils/store';\nimport { CompositeRoot } from '../../composite/root/CompositeRoot';\nimport { CompositeItem } from '../../composite/item/CompositeItem';\nimport { REASONS } from '../../utils/reasons';\nimport { useComboboxRootContext } from './ComboboxRootContext';\nimport { selectors } from '../store';\n\nfunction AsyncItemsCombobox() {\n  const [items, setItems] = React.useState(['Apple', 'Banana', 'Cherry']);\n  const [selectedValue, setSelectedValue] = React.useState<string | null>(null);\n\n  return (\n    <Combobox.Root\n      items={items}\n      onValueChange={(value: string | null) => {\n        setSelectedValue(value);\n      }}\n      onOpenChangeComplete={(open) => {\n        if (!open && selectedValue) {\n          setItems([selectedValue]);\n        }\n      }}\n    >\n      <Combobox.Input data-testid=\"input\" />\n      <Combobox.Portal>\n        <Combobox.Positioner>\n          <Combobox.Popup>\n            <Combobox.List>\n              {(item: string) => (\n                <Combobox.Item key={item} value={item}>\n                  {item}\n                </Combobox.Item>\n              )}\n            </Combobox.List>\n          </Combobox.Popup>\n        </Combobox.Positioner>\n      </Combobox.Portal>\n    </Combobox.Root>\n  );\n}\n\nfunction SelectedIndexProbe() {\n  const store = useComboboxRootContext();\n  const selectedIndex = useStore(store, selectors.selectedIndex);\n\n  return (\n    <div data-testid=\"selected-index\">{selectedIndex === null ? 'null' : `${selectedIndex}`}</div>\n  );\n}\n\nfunction isElementOrAncestorInert(element: HTMLElement) {\n  let current: HTMLElement | null = element;\n  while (current) {\n    if (\n      current.getAttribute('aria-hidden') === 'true' ||\n      current.hasAttribute('inert') ||\n      current.hasAttribute('data-base-ui-inert')\n    ) {\n      return true;\n    }\n    current = current.parentElement;\n  }\n  return false;\n}\n\ndescribe('<Combobox.Root />', () => {\n  beforeEach(() => {\n    globalThis.BASE_UI_ANIMATIONS_DISABLED = true;\n  });\n\n  const { render, renderToString } = createRenderer();\n\n  popupConformanceTests({\n    createComponent: (props) => (\n      <Combobox.Root {...props.root}>\n        <Combobox.Input data-testid=\"trigger\" />\n        <Combobox.Portal {...props.portal}>\n          <Combobox.Positioner>\n            <Combobox.Popup>\n              <Combobox.List {...props.popup}>\n                <Combobox.Item value=\"item\">Item</Combobox.Item>\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>\n    ),\n    render,\n    triggerMouseAction: 'click',\n    expectedPopupRole: 'listbox',\n    combobox: true,\n  });\n\n  describe('server-side rendering', () => {\n    it('sets combobox aria attributes on the input', () => {\n      renderToString(\n        <Combobox.Root>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner />\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n\n      expect(input).toHaveAttribute('role', 'combobox');\n      expect(input).toHaveAttribute('aria-expanded', 'false');\n      expect(input).toHaveAttribute('aria-autocomplete', 'list');\n      expect(input).toHaveAttribute('aria-haspopup', 'listbox');\n    });\n\n    it('sets combobox aria attributes on the trigger when input is inside popup', () => {\n      renderToString(\n        <Combobox.Root>\n          <Combobox.Trigger data-testid=\"trigger\" />\n          <Combobox.Portal>\n            <Combobox.Positioner />\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n\n      expect(trigger).toHaveAttribute('role', 'combobox');\n      expect(trigger).toHaveAttribute('aria-expanded', 'false');\n      expect(trigger).toHaveAttribute('aria-haspopup', 'dialog');\n    });\n\n    it('does not link Combobox.Label to trigger before hydration', () => {\n      renderToString(\n        <Combobox.Root inline>\n          <Combobox.Label data-testid=\"label\">Food</Combobox.Label>\n          <Combobox.Trigger data-testid=\"trigger\">Open</Combobox.Trigger>\n          <Combobox.Input data-testid=\"input\" />\n        </Combobox.Root>,\n      );\n\n      const label = screen.getByTestId('label');\n      const trigger = screen.getByTestId('trigger');\n\n      expect(label.id).not.toBe('');\n      expect(trigger.id).not.toBe('');\n      expect(trigger).not.toHaveAttribute('aria-labelledby');\n    });\n  });\n\n  it('does not focus input when closing via trigger click (input inside popup)', async () => {\n    const { user } = await render(\n      <Combobox.Root items={['One', 'Two', 'Three']}>\n        <Combobox.Trigger data-testid=\"trigger\">\n          <Combobox.Value />\n        </Combobox.Trigger>\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup aria-label=\"Demo\">\n              <Combobox.Input data-testid=\"input\" aria-label=\"combobox-input\" />\n              <Combobox.List>\n                {(item: string) => (\n                  <Combobox.Item key={item} value={item}>\n                    {item}\n                  </Combobox.Item>\n                )}\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    const trigger = screen.getByTestId('trigger');\n    await user.click(trigger);\n\n    expect(await screen.findByRole('listbox')).not.toBe(null);\n\n    const input = await screen.findByRole('combobox', { name: 'combobox-input' });\n    await waitFor(() => expect(input).toHaveFocus());\n\n    await user.click(trigger);\n    await waitFor(() => {\n      expect(trigger).toHaveFocus();\n    });\n  });\n\n  it('does not aria-hide the input group when the input is outside the popup', async () => {\n    const { user } = await render(\n      <Combobox.Root items={['apple', 'banana']}>\n        <Combobox.InputGroup data-testid=\"group\">\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Trigger>Open</Combobox.Trigger>\n        </Combobox.InputGroup>\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup>\n              <Combobox.List>\n                <Combobox.Item value=\"apple\">apple</Combobox.Item>\n                <Combobox.Item value=\"banana\">banana</Combobox.Item>\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    const input = screen.getByTestId('input');\n    const group = screen.getByTestId('group');\n\n    await user.click(input);\n    expect(await screen.findByRole('listbox')).not.toBe(null);\n    await flushMicrotasks();\n\n    expect(input).toHaveFocus();\n    expect(group).not.toHaveAttribute('aria-hidden', 'true');\n  });\n\n  it('dismisses the popup when clicking a plain wrapper around the input', async () => {\n    const { user } = await render(\n      <Combobox.Root items={['apple', 'banana']}>\n        <div style={{ padding: 10 }}>\n          <span data-testid=\"pad\">padding</span>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Trigger>Open</Combobox.Trigger>\n        </div>\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup>\n              <Combobox.List>\n                <Combobox.Item value=\"apple\">apple</Combobox.Item>\n                <Combobox.Item value=\"banana\">banana</Combobox.Item>\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    await user.click(screen.getByRole('button', { name: 'Open' }));\n    expect(await screen.findByRole('listbox')).not.toBe(null);\n\n    await user.click(screen.getByTestId('pad'));\n\n    await waitFor(() => {\n      expect(screen.queryByRole('listbox')).toBe(null);\n    });\n  });\n\n  it('does not cause infinite re-renders when items becomes undefined', async () => {\n    const { rerender } = await render(\n      <Combobox.Root items={[]} defaultOpen>\n        <Combobox.Input />\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup>\n              <Combobox.List />\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    rerender(\n      <Combobox.Root items={undefined} defaultOpen>\n        <Combobox.Input />\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup>\n              <Combobox.List />\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n  });\n\n  it('hides the trigger when popup is open with input outside the popup', async () => {\n    const { user } = await render(\n      <div>\n        <button data-testid=\"outside\">Outside</button>\n        <Combobox.Root items={['Apple', 'Banana']}>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Trigger data-testid=\"trigger\">Open</Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: string) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>\n      </div>,\n    );\n\n    await user.click(screen.getByTestId('input'));\n\n    await waitFor(() => {\n      expect(screen.queryByRole('listbox')).not.toBe(null);\n    });\n\n    const outside = screen.getByTestId('outside');\n    const trigger = screen.getByTestId('trigger');\n\n    await waitFor(() => {\n      expect(isElementOrAncestorInert(outside)).toBe(true);\n    });\n\n    expect(isElementOrAncestorInert(trigger)).toBe(true);\n  });\n\n  it('does not render the start dismiss button while closed', async () => {\n    await render(\n      <Combobox.Root items={['Apple', 'Banana']}>\n        <Combobox.Input data-testid=\"input\" />\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup data-testid=\"popup\">\n              <Combobox.List>\n                {(item: string) => (\n                  <Combobox.Item key={item} value={item}>\n                    {item}\n                  </Combobox.Item>\n                )}\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    expect(screen.queryByRole('button', { name: 'Dismiss' })).toBe(null);\n  });\n\n  it('renders internal dismiss buttons before the input and after the popup', async () => {\n    const { user } = await render(\n      <div>\n        <button data-testid=\"outside\">Outside</button>\n        <Combobox.Root defaultOpen modal items={['Apple', 'Banana']}>\n          <Combobox.Trigger>Open</Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup data-testid=\"popup\" aria-label=\"Demo\">\n                <Combobox.Input aria-label=\"Combobox input\" />\n                <Combobox.List>\n                  {(item: string) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>\n      </div>,\n    );\n\n    const popup = screen.getByTestId('popup');\n    const input = screen.getByRole('combobox', { name: 'Combobox input' });\n    const [startDismissButton, endDismissButton] = screen.getAllByRole('button', {\n      name: 'Dismiss',\n    });\n    const outside = screen.getByTestId('outside');\n\n    expect(input.previousElementSibling).toBe(startDismissButton);\n    expect(popup.nextElementSibling).toBe(endDismissButton);\n    expect(startDismissButton).not.toHaveAttribute('tabindex');\n    expect(endDismissButton).not.toHaveAttribute('tabindex');\n\n    await waitFor(() => {\n      expect(isElementOrAncestorInert(outside)).toBe(true);\n    });\n\n    await user.click(endDismissButton);\n\n    await waitFor(() => {\n      expect(screen.queryByRole('listbox')).toBe(null);\n    });\n  });\n\n  it('renders an internal dismiss button for the input-outside-popup pattern', async () => {\n    const { user } = await render(\n      <Combobox.Root items={['Apple', 'Banana']}>\n        <Combobox.Input data-testid=\"input\" />\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup data-testid=\"popup\">\n              <Combobox.List>\n                {(item: string) => (\n                  <Combobox.Item key={item} value={item}>\n                    {item}\n                  </Combobox.Item>\n                )}\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    await user.click(screen.getByTestId('input'));\n\n    const popup = await screen.findByTestId('popup');\n    const input = screen.getByTestId('input');\n    const [startDismissButton, endDismissButton] = screen.getAllByRole('button', {\n      name: 'Dismiss',\n    });\n\n    expect(input.previousElementSibling).toBe(startDismissButton);\n    expect(popup.nextElementSibling).toBe(endDismissButton);\n    expect(startDismissButton).not.toHaveAttribute('tabindex');\n    expect(endDismissButton).not.toHaveAttribute('tabindex');\n\n    await user.click(startDismissButton);\n\n    await waitFor(() => {\n      expect(screen.queryByRole('listbox')).toBe(null);\n    });\n  });\n\n  describe('selection behavior', () => {\n    describe('single', () => {\n      it('fires onOpenChange once with reason item-press on mouse click', async () => {\n        const items = ['apple', 'banana'];\n        const onOpenChange = vi.fn();\n\n        const { user } = await render(\n          <Combobox.Root items={items} onOpenChange={onOpenChange}>\n            <Combobox.Input />\n            <Combobox.Portal>\n              <Combobox.Positioner>\n                <Combobox.Popup>\n                  <Combobox.List>\n                    {(item: string) => (\n                      <Combobox.Item key={item} value={item}>\n                        {item}\n                      </Combobox.Item>\n                    )}\n                  </Combobox.List>\n                </Combobox.Popup>\n              </Combobox.Positioner>\n            </Combobox.Portal>\n          </Combobox.Root>,\n        );\n\n        const input = screen.getByRole('combobox');\n        await user.click(input);\n        expect(screen.getByRole('listbox')).not.toBe(null);\n\n        onOpenChange.mockClear();\n        await user.click(screen.getByRole('option', { name: 'apple' }));\n\n        await waitFor(() => {\n          expect(screen.queryByRole('listbox')).toBe(null);\n        });\n\n        expect(onOpenChange.mock.calls.length).toBe(1);\n        expect(onOpenChange.mock.lastCall?.[0]).toBe(false);\n        expect(onOpenChange.mock.lastCall?.[1].reason).toBe(REASONS.itemPress);\n        expect(onOpenChange.mock.lastCall?.[1].event instanceof MouseEvent).toBe(true);\n      });\n\n      it('fires onOpenChange once with reason item-press on Enter selection', async () => {\n        const items = ['apple', 'banana'];\n        const onOpenChange = vi.fn();\n\n        const { user } = await render(\n          <Combobox.Root items={items} onOpenChange={onOpenChange}>\n            <Combobox.Input />\n            <Combobox.Portal>\n              <Combobox.Positioner>\n                <Combobox.Popup>\n                  <Combobox.List>\n                    {(item: string) => (\n                      <Combobox.Item key={item} value={item}>\n                        {item}\n                      </Combobox.Item>\n                    )}\n                  </Combobox.List>\n                </Combobox.Popup>\n              </Combobox.Positioner>\n            </Combobox.Portal>\n          </Combobox.Root>,\n        );\n\n        const input = screen.getByRole('combobox');\n        await user.click(input);\n        await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n\n        await user.keyboard('{ArrowDown}');\n        onOpenChange.mockClear();\n        await user.keyboard('{Enter}');\n\n        await waitFor(() => {\n          expect(screen.queryByRole('listbox')).toBe(null);\n        });\n\n        expect(onOpenChange.mock.calls.length).toBe(1);\n        expect(onOpenChange.mock.lastCall?.[0]).toBe(false);\n        expect(onOpenChange.mock.lastCall?.[1].reason).toBe(REASONS.itemPress);\n        expect(onOpenChange.mock.lastCall?.[1].event instanceof KeyboardEvent).toBe(true);\n      });\n\n      it('should auto-close popup after selection when open state is uncontrolled', async () => {\n        const items = ['apple', 'banana', 'cherry'];\n\n        const { user } = await render(\n          <Combobox.Root items={items}>\n            <Combobox.Input data-testid=\"input\" />\n            <Combobox.Trigger data-testid=\"trigger\">Open</Combobox.Trigger>\n            <Combobox.Portal>\n              <Combobox.Positioner>\n                <Combobox.Popup>\n                  <Combobox.List>\n                    {(item: string) => (\n                      <Combobox.Item key={item} value={item}>\n                        {item}\n                      </Combobox.Item>\n                    )}\n                  </Combobox.List>\n                </Combobox.Popup>\n              </Combobox.Positioner>\n            </Combobox.Portal>\n          </Combobox.Root>,\n        );\n\n        const input = screen.getByTestId('input');\n        const trigger = screen.getByTestId('trigger');\n        await user.click(trigger);\n\n        expect(await screen.findByRole('listbox')).not.toBe(null);\n        expect(input).toHaveAttribute('aria-expanded', 'true');\n\n        const appleOption = await screen.findByText('apple');\n        await user.click(appleOption);\n\n        await waitFor(() => {\n          expect(screen.queryByRole('listbox')).toBe(null);\n        });\n        expect(input).toHaveAttribute('aria-expanded', 'false');\n      });\n\n      it('syncs selected index when items change after close', async () => {\n        const { user } = await render(<AsyncItemsCombobox />);\n\n        const input = screen.getByTestId('input');\n        await user.click(input);\n        await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n\n        await user.click(screen.getByRole('option', { name: 'Cherry' }));\n        await waitFor(() => expect(screen.queryByRole('listbox')).toBe(null));\n\n        await user.click(input);\n        await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n\n        const cherryOption = screen.getByRole('option', { name: 'Cherry' });\n        expect(cherryOption).toHaveAttribute('data-selected', '');\n      });\n\n      it('should not auto-close popup when open state is controlled', async () => {\n        const items = ['apple', 'banana', 'cherry'];\n\n        const { user } = await render(\n          <Combobox.Root items={items} open>\n            <Combobox.Input data-testid=\"input\" />\n            <Combobox.Trigger data-testid=\"trigger\">Open</Combobox.Trigger>\n            <Combobox.Portal>\n              <Combobox.Positioner>\n                <Combobox.Popup>\n                  <Combobox.List>\n                    {(item: string) => (\n                      <Combobox.Item key={item} value={item}>\n                        {item}\n                      </Combobox.Item>\n                    )}\n                  </Combobox.List>\n                </Combobox.Popup>\n              </Combobox.Positioner>\n            </Combobox.Portal>\n          </Combobox.Root>,\n        );\n\n        await user.click(screen.getByText('apple'));\n        expect(screen.getByRole('listbox')).not.toBe(null);\n      });\n\n      it('should show all items when query is empty with enhanced filter', async () => {\n        const items = ['apple', 'banana', 'cherry'];\n\n        await render(\n          <Combobox.Root items={items} defaultOpen>\n            <Combobox.Input data-testid=\"input\" />\n            <Combobox.Portal>\n              <Combobox.Positioner>\n                <Combobox.Popup>\n                  <Combobox.List>\n                    {(item: string) => (\n                      <Combobox.Item key={item} value={item}>\n                        {item}\n                      </Combobox.Item>\n                    )}\n                  </Combobox.List>\n                </Combobox.Popup>\n              </Combobox.Positioner>\n            </Combobox.Portal>\n          </Combobox.Root>,\n        );\n\n        expect(screen.queryByText('apple')).not.toBe(null);\n        expect(screen.queryByText('banana')).not.toBe(null);\n        expect(screen.queryByText('cherry')).not.toBe(null);\n      });\n\n      it('should show all items when query matches current selection', async () => {\n        const items = ['apple', 'banana', 'cherry'];\n\n        const { user } = await render(\n          <Combobox.Root items={items} defaultValue=\"apple\">\n            <Combobox.Input data-testid=\"input\" />\n            <Combobox.Trigger data-testid=\"trigger\">Open</Combobox.Trigger>\n            <Combobox.Portal>\n              <Combobox.Positioner>\n                <Combobox.Popup>\n                  <Combobox.List>\n                    {(item) => (\n                      <Combobox.Item key={item} value={item}>\n                        {item}\n                      </Combobox.Item>\n                    )}\n                  </Combobox.List>\n                </Combobox.Popup>\n              </Combobox.Positioner>\n            </Combobox.Portal>\n          </Combobox.Root>,\n        );\n\n        const input = screen.getByTestId('input');\n        const trigger = screen.getByTestId('trigger');\n\n        await user.click(trigger);\n\n        const appleOption = await screen.findByText('apple');\n        await user.click(appleOption);\n\n        expect(input).toHaveValue('apple');\n\n        await user.click(trigger);\n\n        expect(await screen.findByText('apple')).not.toBe(null);\n        expect(await screen.findByText('banana')).not.toBe(null);\n        expect(await screen.findByText('cherry')).not.toBe(null);\n      });\n\n      it('should reset input value to selected value when popup closes without selection', async () => {\n        const items = ['apple', 'banana', 'cherry'];\n        const onInputValueChange = vi.fn();\n\n        const { user } = await render(\n          <Combobox.Root items={items} defaultValue=\"apple\" onInputValueChange={onInputValueChange}>\n            <Combobox.Input data-testid=\"input\" />\n            <Combobox.Trigger data-testid=\"trigger\">Open</Combobox.Trigger>\n            <Combobox.Portal>\n              <Combobox.Positioner>\n                <Combobox.Popup>\n                  <Combobox.List>\n                    {(item) => (\n                      <Combobox.Item key={item} value={item}>\n                        {item}\n                      </Combobox.Item>\n                    )}\n                  </Combobox.List>\n                </Combobox.Popup>\n              </Combobox.Positioner>\n            </Combobox.Portal>\n          </Combobox.Root>,\n        );\n\n        const input = screen.getByTestId('input');\n        const trigger = screen.getByTestId('trigger');\n\n        await user.click(trigger);\n        const appleOption = await screen.findByText('apple');\n        await user.click(appleOption);\n\n        expect(input).toHaveValue('apple');\n\n        await user.click(trigger);\n        await user.type(input, 'xyz');\n        expect(input).toHaveValue('applexyz');\n\n        await user.click(document.body);\n\n        await waitFor(() => expect(input).toHaveValue('apple'));\n        expect(onInputValueChange.mock.lastCall?.[0]).toBe('apple');\n        expect(onInputValueChange.mock.lastCall?.[1].reason).toBe('none');\n      });\n\n      it('should not auto-close during browser autofill', async () => {\n        const items = ['apple', 'banana', 'cherry'];\n\n        await render(\n          <Combobox.Root items={items} name=\"test\" defaultOpen>\n            <Combobox.Input data-testid=\"input\" />\n            <Combobox.Portal>\n              <Combobox.Positioner>\n                <Combobox.Popup>\n                  <Combobox.List>\n                    {(item: string) => (\n                      <Combobox.Item key={item} value={item}>\n                        {item}\n                      </Combobox.Item>\n                    )}\n                  </Combobox.List>\n                </Combobox.Popup>\n              </Combobox.Positioner>\n            </Combobox.Portal>\n          </Combobox.Root>,\n        );\n\n        expect(screen.getByRole('listbox')).not.toBe(null);\n\n        const hiddenInput = screen.queryByRole('textbox', { hidden: true });\n        fireEvent.change(hiddenInput!, { target: { value: 'apple' } });\n\n        await flushMicrotasks();\n\n        expect(screen.getByRole('listbox')).not.toBe(null);\n      });\n    });\n\n    describe('multiple', () => {\n      it('should handle multiple selection', async () => {\n        const handleValueChange = vi.fn();\n\n        const { user } = await render(\n          <Combobox.Root multiple onValueChange={handleValueChange}>\n            <Combobox.Input />\n            <Combobox.List>\n              <Combobox.Item value=\"a\">a</Combobox.Item>\n              <Combobox.Item value=\"b\">b</Combobox.Item>\n              <Combobox.Item value=\"c\">c</Combobox.Item>\n            </Combobox.List>\n          </Combobox.Root>,\n        );\n\n        const optionA = screen.getByRole('option', { name: 'a' });\n        await user.click(optionA);\n\n        expect(handleValueChange.mock.calls.length).toBe(1);\n        expect(handleValueChange.mock.calls[0][0]).toEqual(['a']);\n\n        const optionB = screen.getByRole('option', { name: 'b' });\n        await user.click(optionB);\n\n        expect(handleValueChange.mock.calls.length).toBe(2);\n        expect(handleValueChange.mock.calls[1][0]).toEqual(['a', 'b']);\n      });\n\n      it('resets selectedIndex when clearing all selections while open', async () => {\n        const items = ['apple', 'banana', 'cherry'];\n\n        function App() {\n          const [value, setValue] = React.useState(items.slice(0, 2));\n\n          return (\n            <Combobox.Root items={items} multiple value={value} onValueChange={setValue}>\n              <Combobox.Input data-testid=\"input\" />\n              <SelectedIndexProbe />\n              <button type=\"button\" data-testid=\"clear\" onClick={() => setValue([])}>\n                Clear\n              </button>\n              <Combobox.Portal>\n                <Combobox.Positioner>\n                  <Combobox.Popup>\n                    <Combobox.List>\n                      {(item: string) => (\n                        <Combobox.Item key={item} value={item}>\n                          {item}\n                        </Combobox.Item>\n                      )}\n                    </Combobox.List>\n                  </Combobox.Popup>\n                </Combobox.Positioner>\n              </Combobox.Portal>\n            </Combobox.Root>\n          );\n        }\n\n        const { user } = await render(<App />);\n\n        expect(screen.queryByRole('listbox')).toBe(null);\n\n        await user.click(screen.getByTestId('input'));\n\n        expect(await screen.findByRole('listbox')).not.toBe(null);\n        expect(screen.getByTestId('selected-index').textContent).toBe('1');\n\n        await user.click(screen.getByTestId('clear'));\n\n        await waitFor(() => {\n          expect(screen.getByTestId('selected-index').textContent).toBe('null');\n        });\n      });\n\n      it('re-syncs selectedIndex after an external controlled update when closing', async () => {\n        const items = ['apple', 'banana', 'cherry'];\n\n        function App() {\n          const [value, setValue] = React.useState([items[0]]);\n\n          return (\n            <Combobox.Root items={items} multiple value={value} onValueChange={setValue}>\n              <Combobox.Input data-testid=\"input\" />\n              <SelectedIndexProbe />\n              <button type=\"button\" data-testid=\"set-external\" onClick={() => setValue([items[2]])}>\n                Set external\n              </button>\n              <Combobox.Portal>\n                <Combobox.Positioner>\n                  <Combobox.Popup>\n                    <Combobox.List>\n                      {(item: string) => (\n                        <Combobox.Item key={item} value={item}>\n                          {item}\n                        </Combobox.Item>\n                      )}\n                    </Combobox.List>\n                  </Combobox.Popup>\n                </Combobox.Positioner>\n              </Combobox.Portal>\n            </Combobox.Root>\n          );\n        }\n\n        const { user } = await render(<App />);\n\n        const input = screen.getByTestId('input');\n        await user.click(input);\n        expect(await screen.findByRole('listbox')).not.toBe(null);\n\n        await waitFor(() => {\n          expect(screen.getByTestId('selected-index').textContent).toBe('0');\n        });\n\n        await user.click(screen.getByTestId('set-external'));\n        await waitFor(() => {\n          expect(screen.queryByRole('listbox')).toBe(null);\n          expect(screen.getByTestId('selected-index').textContent).toBe('2');\n        });\n\n        await user.click(input);\n        expect(await screen.findByRole('listbox')).not.toBe(null);\n\n        await waitFor(() => {\n          expect(screen.getByTestId('selected-index').textContent).toBe('2');\n        });\n      });\n\n      it('should create multiple hidden inputs for form submission', async () => {\n        const items = ['a', 'b', 'c'];\n        await render(\n          <Combobox.Root multiple value={items} name=\"languages\">\n            <Combobox.Input />\n            <Combobox.Portal>\n              <Combobox.Positioner>\n                <Combobox.Popup>\n                  <Combobox.List>\n                    <Combobox.Item value=\"a\">a</Combobox.Item>\n                    <Combobox.Item value=\"b\">b</Combobox.Item>\n                  </Combobox.List>\n                </Combobox.Popup>\n              </Combobox.Positioner>\n            </Combobox.Portal>\n          </Combobox.Root>,\n        );\n\n        items.forEach((item) => {\n          const input = screen.getByDisplayValue(item);\n          expect(input).toHaveAttribute('type', 'hidden');\n          expect(input.tagName).toBe('INPUT');\n        });\n      });\n\n      it('should handle disabled state with chips', async () => {\n        const { user } = await render(\n          <Combobox.Root multiple disabled defaultValue={['a', 'b']}>\n            <Combobox.Input data-testid=\"input\" />\n            <Combobox.Chips>\n              <Combobox.Chip data-testid=\"chip-a\">\n                <Combobox.ChipRemove data-testid=\"remove-a\" />\n              </Combobox.Chip>\n              <Combobox.Chip data-testid=\"chip-b\">\n                <Combobox.ChipRemove data-testid=\"remove-b\" />\n              </Combobox.Chip>\n            </Combobox.Chips>\n            <Combobox.Portal>\n              <Combobox.Positioner>\n                <Combobox.Popup>\n                  <Combobox.List>\n                    <Combobox.Item value=\"a\">a</Combobox.Item>\n                    <Combobox.Item value=\"b\">b</Combobox.Item>\n                    <Combobox.Item value=\"c\">c</Combobox.Item>\n                  </Combobox.List>\n                </Combobox.Popup>\n              </Combobox.Positioner>\n            </Combobox.Portal>\n          </Combobox.Root>,\n        );\n\n        const chipA = screen.getByTestId('chip-a');\n        const removeA = screen.getByTestId('remove-a');\n\n        expect(chipA).toHaveAttribute('aria-disabled', 'true');\n        expect(removeA).toHaveAttribute('aria-disabled', 'true');\n\n        await user.click(removeA);\n        expect(screen.getByTestId('chip-a')).not.toBe(null);\n      });\n\n      it('should handle readOnly state with chips', async () => {\n        const { user } = await render(\n          <Combobox.Root multiple readOnly defaultValue={['a', 'b']}>\n            <Combobox.Input data-testid=\"input\" />\n            <Combobox.Chips>\n              <Combobox.Chip data-testid=\"chip-a\">\n                <Combobox.ChipRemove data-testid=\"remove-a\" />\n              </Combobox.Chip>\n              <Combobox.Chip data-testid=\"chip-b\">\n                <Combobox.ChipRemove data-testid=\"remove-b\" />\n              </Combobox.Chip>\n            </Combobox.Chips>\n            <Combobox.Portal>\n              <Combobox.Positioner>\n                <Combobox.Popup>\n                  <Combobox.List>\n                    <Combobox.Item value=\"a\">a</Combobox.Item>\n                    <Combobox.Item value=\"b\">b</Combobox.Item>\n                    <Combobox.Item value=\"c\">c</Combobox.Item>\n                  </Combobox.List>\n                </Combobox.Popup>\n              </Combobox.Positioner>\n            </Combobox.Portal>\n          </Combobox.Root>,\n        );\n\n        const chipA = screen.getByTestId('chip-a');\n        const removeA = screen.getByTestId('remove-a');\n\n        expect(chipA).toHaveAttribute('aria-readonly', 'true');\n\n        await user.click(removeA);\n        expect(screen.getByTestId('chip-a')).not.toBe(null);\n      });\n    });\n  });\n\n  describe('keyboard interaction', () => {\n    it('focuses first item on ArrowDown and last item on ArrowUp', async () => {\n      const { user } = await render(\n        <Combobox.Root>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"apple\">apple</Combobox.Item>\n                  <Combobox.Item value=\"banana\">banana</Combobox.Item>\n                  <Combobox.Item value=\"cherry\">cherry</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n\n      await act(async () => input.focus());\n\n      await user.keyboard('{ArrowDown}');\n      await waitFor(() => {\n        const first = screen.getByRole('option', { name: 'apple' });\n        expect(input).toHaveAttribute('aria-activedescendant', first.id);\n      });\n\n      await user.keyboard('{Escape}');\n\n      await waitFor(() => {\n        expect(screen.queryByRole('listbox')).toBe(null);\n      });\n\n      await user.keyboard('{ArrowUp}');\n\n      await waitFor(() => {\n        const last = screen.getByRole('option', { name: 'cherry' });\n        expect(input).toHaveAttribute('aria-activedescendant', last.id);\n      });\n    });\n\n    it('opens, navigates with ArrowDown, and Enter selects', async () => {\n      const items = ['apple', 'banana', 'cherry'];\n\n      const { user } = await render(\n        <Combobox.Root items={items}>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: string) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n\n      await user.click(input);\n      await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n\n      // Highlight first item and select it\n      await user.keyboard('{ArrowDown}');\n      await user.keyboard('{Enter}');\n\n      expect(screen.queryByRole('listbox')).toBe(null);\n      expect(input).toHaveValue('apple');\n    });\n\n    it('Enter selects with manual indices provided to items', async () => {\n      const items = ['apple', 'banana', 'cherry'];\n\n      const { user } = await render(\n        <Combobox.Root items={items}>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: string, index: number) => (\n                    <Combobox.Item key={item} value={item} index={index}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n\n      await user.click(input);\n      await waitFor(() => {\n        expect(screen.getByRole('listbox')).not.toBe(null);\n      });\n\n      await user.type(input, 'c'); // filter to \"cherry\"\n      await user.keyboard('{ArrowDown}');\n      await user.keyboard('{Enter}');\n\n      await waitFor(() => {\n        expect(input).toHaveValue('cherry');\n      });\n    });\n\n    it('clicking on \"listbox\" keeps the focus on the input', async () => {\n      const items = ['apple', 'banana', 'cherry'];\n\n      const { user } = await render(\n        <Combobox.Root items={items}>\n          <Combobox.Input />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: string) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole('combobox');\n\n      await user.click(input);\n      await waitFor(() => {\n        expect(screen.getByRole('listbox')).not.toBe(null);\n      });\n\n      const listbox = screen.getByRole('listbox');\n      await user.click(listbox);\n      expect(input).toHaveFocus();\n\n      await user.keyboard('{ArrowDown}');\n      await user.keyboard('{Enter}');\n\n      await waitFor(() => {\n        expect(input).toHaveValue('apple');\n      });\n    });\n\n    it('Escape closes the popup without committing when nothing highlighted', async () => {\n      const { user } = await render(\n        <Combobox.Root defaultOpen items={['a', 'b']}>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                  <Combobox.Item value=\"b\">b</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      expect(screen.queryByRole('listbox')).not.toBe(null);\n\n      await user.keyboard('{Escape}');\n      expect(screen.queryByRole('listbox')).toBe(null);\n      expect(input).toHaveValue('');\n    });\n\n    it('bubbles Escape key when rendered inline without Positioner/Popup', async () => {\n      const onOuterKeyDown = vi.fn();\n\n      const { user } = await render(\n        <div\n          data-testid=\"outer\"\n          onKeyDown={(event) => {\n            if (event.key === 'Escape') {\n              onOuterKeyDown();\n            }\n          }}\n        >\n          <Combobox.Root inline defaultOpen>\n            <Combobox.Input data-testid=\"input\" />\n            <Combobox.List>\n              <Combobox.Item value=\"a\">a</Combobox.Item>\n              <Combobox.Item value=\"b\">b</Combobox.Item>\n            </Combobox.List>\n          </Combobox.Root>\n        </div>,\n      );\n\n      const input = screen.getByTestId('input');\n      await user.click(input);\n\n      await waitFor(() => {\n        expect(screen.getByRole('listbox')).not.toBe(null);\n      });\n\n      await user.keyboard('{Escape}');\n\n      expect(onOuterKeyDown.mock.calls.length).toBe(1);\n    });\n\n    it('keeps input value on Enter when inline and no item is highlighted', async () => {\n      const { user } = await render(\n        <Combobox.Root inline items={['Apple', 'Banana']}>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.List>\n            {(item: string) => (\n              <Combobox.Item key={item} value={item}>\n                {item}\n              </Combobox.Item>\n            )}\n          </Combobox.List>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n\n      await user.click(input);\n      await user.type(input, 'Ba');\n\n      expect(input).not.toHaveAttribute('aria-activedescendant');\n      expect(input).toHaveValue('Ba');\n\n      await user.keyboard('{Enter}');\n\n      expect(input).toHaveValue('Ba');\n    });\n\n    it('bubbles Escape key when list is empty and popup hidden with CSS', async () => {\n      const onOuterKeyDown = vi.fn();\n\n      const { user } = await render(\n        <div\n          onKeyDown={(event) => {\n            if (event.key === 'Escape') {\n              onOuterKeyDown();\n            }\n          }}\n        >\n          <Combobox.Root defaultOpen items={[]}>\n            <Combobox.Input />\n            <Combobox.Portal>\n              <Combobox.Positioner data-testid=\"positioner\">\n                <Combobox.Popup>\n                  <Combobox.List>\n                    {(item: string) => (\n                      <Combobox.Item key={item} value={item}>\n                        {item}\n                      </Combobox.Item>\n                    )}\n                  </Combobox.List>\n                </Combobox.Popup>\n              </Combobox.Positioner>\n            </Combobox.Portal>\n          </Combobox.Root>\n        </div>,\n      );\n\n      const positioner = await screen.findByTestId('positioner');\n      positioner.style.display = 'none';\n\n      const input = screen.getByRole('combobox');\n      await user.click(input);\n      await user.keyboard('{Escape}');\n\n      expect(onOuterKeyDown.mock.calls.length).toBe(1);\n    });\n\n    it('does not bubble Escape key when Empty component is present', async () => {\n      const onOuterKeyDown = vi.fn();\n\n      const { user } = await render(\n        <div\n          onKeyDown={(event) => {\n            if (event.key === 'Escape') {\n              onOuterKeyDown();\n            }\n          }}\n        >\n          <Combobox.Root defaultOpen items={[]}>\n            <Combobox.Input />\n            <Combobox.Portal>\n              <Combobox.Positioner data-testid=\"positioner\">\n                <Combobox.Popup>\n                  <Combobox.List>\n                    {(item: string) => (\n                      <Combobox.Item key={item} value={item}>\n                        {item}\n                      </Combobox.Item>\n                    )}\n                  </Combobox.List>\n                  <Combobox.Empty>No results.</Combobox.Empty>\n                </Combobox.Popup>\n              </Combobox.Positioner>\n            </Combobox.Portal>\n          </Combobox.Root>\n        </div>,\n      );\n\n      const positioner = await screen.findByTestId('positioner');\n      positioner.style.display = 'none';\n\n      const input = screen.getByRole('combobox');\n      await user.click(input);\n      await user.keyboard('{Escape}');\n\n      expect(onOuterKeyDown.mock.calls.length).toBe(0);\n    });\n  });\n\n  describe('aria attributes', () => {\n    it('sets all aria attributes on the input when closed', async () => {\n      await render(\n        <Combobox.Root>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner />\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n\n      expect(input).toHaveAttribute('role', 'combobox');\n      expect(input).toHaveAttribute('aria-expanded', 'false');\n      expect(input).toHaveAttribute('aria-autocomplete', 'list');\n      expect(input).toHaveAttribute('aria-haspopup', 'listbox');\n      expect(input).not.toHaveAttribute('aria-controls');\n      expect(input).not.toHaveAttribute('aria-activedescendant');\n    });\n\n    it('sets all aria attributes on the input when open', async () => {\n      await render(\n        <Combobox.Root defaultOpen>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List />\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      const listbox = screen.getByRole('listbox');\n\n      expect(input).toHaveAttribute('role', 'combobox');\n      expect(input).toHaveAttribute('aria-expanded', 'true');\n      expect(input).toHaveAttribute('aria-autocomplete', 'list');\n      expect(input).toHaveAttribute('aria-haspopup', 'listbox');\n      expect(input).toHaveAttribute('aria-controls', listbox.id);\n      expect(input).not.toHaveAttribute('aria-activedescendant');\n    });\n\n    it('sets correct attributes on the item when highlighted', async () => {\n      const { user } = await render(\n        <Combobox.Root defaultOpen>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                  <Combobox.Item value=\"b\">b</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole('combobox');\n\n      await user.click(input);\n\n      expect(input).not.toHaveAttribute('aria-activedescendant');\n\n      await user.keyboard('{ArrowDown}');\n\n      await waitFor(() => {\n        expect(screen.getByRole('option', { name: 'a' })).toHaveAttribute('aria-selected', 'false');\n      });\n      expect(screen.getByRole('option', { name: 'b' })).toHaveAttribute('aria-selected', 'false');\n      expect(input).toHaveAttribute(\n        'aria-activedescendant',\n        screen.getByRole('option', { name: 'a' }).id,\n      );\n\n      await user.keyboard('{Enter}');\n      await user.click(input);\n\n      await waitFor(() => {\n        expect(screen.getByRole('option', { name: 'a' })).toHaveAttribute('aria-selected', 'true');\n      });\n      expect(screen.getByRole('option', { name: 'b' })).toHaveAttribute('aria-selected', 'false');\n    });\n\n    it('sets aria-controls=\"dialog\" attribute on trigger', async () => {\n      const { user } = await render(\n        <Combobox.Root defaultOpen>\n          <Combobox.Trigger>trigger</Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.Input data-testid=\"input\" />\n                <Combobox.List />\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const trigger = screen\n        .getAllByRole('combobox')\n        .find((element) => element.tagName === 'BUTTON')!;\n      const listbox = screen.getByRole('listbox');\n\n      expect(trigger).toHaveAttribute('aria-controls', listbox.id);\n      expect(trigger).toHaveAttribute('aria-expanded', 'true');\n\n      await user.click(trigger);\n\n      await waitFor(() => {\n        expect(trigger).toHaveAttribute('aria-expanded', 'false');\n      });\n      expect(trigger).not.toHaveAttribute('aria-controls');\n    });\n  });\n\n  it('should handle browser autofill', async () => {\n    const onInputValueChange = vi.fn();\n    const { user } = await render(\n      <Combobox.Root name=\"test\" onInputValueChange={onInputValueChange}>\n        <Combobox.Input />\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup>\n              <Combobox.List>\n                <Combobox.Item value=\"a\">a</Combobox.Item>\n                <Combobox.Item value=\"b\">b</Combobox.Item>\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    const input = screen.getByRole('combobox');\n\n    fireEvent.change(\n      screen.getAllByDisplayValue('').find((el) => el.getAttribute('name') === 'test')!,\n      { target: { value: 'b' } },\n    );\n    await flushMicrotasks();\n    expect(onInputValueChange.mock.lastCall?.[0]).toBe('b');\n    expect(onInputValueChange.mock.lastCall?.[1].reason).toBe(REASONS.none);\n\n    await user.click(input);\n\n    await waitFor(() => {\n      expect(screen.getByRole('option', { name: 'b' })).toHaveAttribute('aria-selected', 'true');\n    });\n  });\n\n  it('shows all items when opening after browser autofill', async () => {\n    const items = ['a', 'b', 'c'];\n    const { user } = await render(\n      <Combobox.Root name=\"test\" items={items}>\n        <Combobox.Input />\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup>\n              <Combobox.List>\n                {(item: string) => (\n                  <Combobox.Item key={item} value={item}>\n                    {item}\n                  </Combobox.Item>\n                )}\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    const input = screen.getByRole('combobox');\n\n    fireEvent.change(\n      screen.getAllByDisplayValue('').find((el) => el.getAttribute('name') === 'test')!,\n      { target: { value: 'b' } },\n    );\n    await flushMicrotasks();\n\n    await user.click(input);\n\n    await waitFor(() => {\n      expect(screen.getByRole('listbox')).not.toBe(null);\n    });\n    expect(screen.getByRole('option', { name: 'a' })).not.toBe(null);\n    expect(screen.getByRole('option', { name: 'b' })).not.toBe(null);\n    expect(screen.getByRole('option', { name: 'c' })).not.toBe(null);\n  });\n\n  it('shows all items when opening after browser autofill with insertReplacementText', async () => {\n    const items = ['a', 'b', 'c'];\n    const { user } = await render(\n      <Combobox.Root name=\"test\" items={items}>\n        <Combobox.Input />\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup>\n              <Combobox.List>\n                {(item: string) => (\n                  <Combobox.Item key={item} value={item}>\n                    {item}\n                  </Combobox.Item>\n                )}\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    const input = screen.getByRole('combobox');\n\n    fireEvent.input(\n      screen.getAllByDisplayValue('').find((el) => el.getAttribute('name') === 'test')!,\n      { target: { value: 'b' }, inputType: 'insertReplacementText' },\n    );\n    await flushMicrotasks();\n\n    await user.click(input);\n\n    await waitFor(() => {\n      expect(screen.getByRole('listbox')).not.toBe(null);\n    });\n    expect(screen.getByRole('option', { name: 'a' })).not.toBe(null);\n    expect(screen.getByRole('option', { name: 'b' })).not.toBe(null);\n    expect(screen.getByRole('option', { name: 'c' })).not.toBe(null);\n  });\n\n  it('should handle browser autofill with object values', async () => {\n    const items = [\n      { country: 'United States', code: 'US' },\n      { country: 'Canada', code: 'CA' },\n    ];\n\n    await render(\n      <Combobox.Root\n        name=\"country\"\n        items={items}\n        itemToStringLabel={(item: (typeof items)[number]) => item.country}\n        itemToStringValue={(item: (typeof items)[number]) => item.code}\n        defaultOpen\n      >\n        <Combobox.Input />\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup>\n              <Combobox.List>\n                {(item: (typeof items)[1]) => (\n                  <Combobox.Item key={item.code} value={item}>\n                    {item.country}\n                  </Combobox.Item>\n                )}\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    const input = screen.getByRole('combobox');\n\n    fireEvent.change(\n      // getByRole('textbox', { hidden: true, name: 'country' }) does not work\n      screen.getAllByDisplayValue('').find((el) => el.getAttribute('name') === 'country')!,\n      { target: { value: 'CA' } },\n    );\n    await flushMicrotasks();\n\n    fireEvent.click(input);\n\n    await waitFor(() => {\n      expect(screen.getByRole('option', { name: 'Canada' })).toHaveAttribute(\n        'aria-selected',\n        'true',\n      );\n    });\n  });\n\n  it('should pass autoComplete to the hidden input', async () => {\n    await render(\n      <Combobox.Root name=\"country\" autoComplete=\"country\">\n        <Combobox.Input />\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup>\n              <Combobox.List>\n                <Combobox.Item value=\"US\">United States</Combobox.Item>\n                <Combobox.Item value=\"CA\">Canada</Combobox.Item>\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    const input = screen.getByRole('combobox');\n    const hiddenInput = screen.getByRole('textbox', { hidden: true });\n\n    expect(input).toHaveAttribute('autocomplete', 'off');\n    expect(input).not.toHaveAttribute('name');\n    expect(hiddenInput).toHaveAttribute('name', 'country');\n    expect(hiddenInput).not.toHaveAttribute('id');\n    expect(hiddenInput).toHaveAttribute('autocomplete', 'country');\n  });\n\n  it('does not open on programmatic input events', async () => {\n    await render(\n      <Combobox.Root>\n        <Combobox.Input />\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup>\n              <Combobox.List>\n                <Combobox.Item value=\"Darlinghurst\">Darlinghurst</Combobox.Item>\n                <Combobox.Item value=\"Sydney\">Sydney</Combobox.Item>\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    const input = screen.getByRole('combobox');\n    fireEvent.change(input, { target: { value: 'Darlinghurst' } });\n    await flushMicrotasks();\n\n    expect(screen.queryByRole('listbox')).toBe(null);\n  });\n\n  it('opens on paste input events', async () => {\n    await render(\n      <Combobox.Root>\n        <Combobox.Input />\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup>\n              <Combobox.List>\n                <Combobox.Item value=\"Darlinghurst\">Darlinghurst</Combobox.Item>\n                <Combobox.Item value=\"Sydney\">Sydney</Combobox.Item>\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    const input = screen.getByRole('combobox');\n    fireEvent.input(input, {\n      target: { value: 'Darlinghurst' },\n      inputType: 'insertFromPaste',\n    });\n\n    await waitFor(() => {\n      expect(screen.getByRole('listbox')).not.toBe(null);\n    });\n  });\n\n  describe('prop: id', () => {\n    it('sets the id on the input when it is outside the popup', async () => {\n      await render(\n        <Combobox.Root id=\"test-id\">\n          <Combobox.Input />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                  <Combobox.Item value=\"b\">b</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole('combobox');\n      await waitFor(() => {\n        expect(input).toHaveAttribute('id', 'test-id');\n      });\n    });\n\n    it('sets the id on the trigger when the input is inside the popup', async () => {\n      await render(\n        <Combobox.Root id=\"test-id\" defaultOpen>\n          <Combobox.Trigger data-testid=\"trigger\">Open</Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.Input data-testid=\"input\" />\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                  <Combobox.Item value=\"b\">b</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      const input = screen.getByTestId('input');\n\n      expect(trigger).toHaveAttribute('id', 'test-id');\n      expect(input).not.toHaveAttribute('id', 'test-id');\n    });\n  });\n\n  describe('prop: disabled', () => {\n    it('should render disabled state on all interactive components', async () => {\n      const { user } = await render(\n        <Combobox.Root disabled>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Trigger data-testid=\"trigger\">Open</Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\" data-testid=\"item-a\">\n                    a\n                  </Combobox.Item>\n                  <Combobox.Item value=\"b\" data-testid=\"item-b\">\n                    b\n                  </Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      const trigger = screen.getByTestId('trigger');\n\n      expect(input).toHaveAttribute('disabled');\n      expect(trigger).toHaveAttribute('disabled');\n\n      // Verify interactions are disabled\n      await user.click(trigger);\n      expect(screen.queryByRole('listbox')).toBe(null);\n    });\n\n    it('should not open popup when disabled', async () => {\n      const { user } = await render(\n        <Combobox.Root disabled>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Trigger data-testid=\"trigger\">Open</Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                  <Combobox.Item value=\"b\">b</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      const trigger = screen.getByTestId('trigger');\n\n      await user.click(input);\n      expect(screen.queryByRole('listbox')).toBe(null);\n\n      await user.click(trigger);\n      expect(screen.queryByRole('listbox')).toBe(null);\n    });\n\n    it('should prevent keyboard interactions when disabled', async () => {\n      const { user } = await render(\n        <Combobox.Root disabled>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                  <Combobox.Item value=\"b\">b</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n\n      await user.type(input, 'a');\n      expect(screen.queryByRole('listbox')).toBe(null);\n    });\n\n    it('should set disabled attribute on hidden input', async () => {\n      await render(\n        <Combobox.Root disabled name=\"test\">\n          <Combobox.Input />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const hiddenInput = screen.getByRole('textbox', { hidden: true });\n      expect(hiddenInput).toHaveAttribute('disabled');\n    });\n  });\n\n  describe('prop: required', () => {\n    it('does not mark the hidden input as required when selection exists in multiple mode', async () => {\n      await render(\n        <Combobox.Root multiple required name=\"languages\" value={['a']}>\n          <Combobox.Input />\n        </Combobox.Root>,\n      );\n\n      const hiddenInput = screen.getByRole('textbox', { hidden: true });\n      expect(hiddenInput).not.toBe(null);\n      expect(hiddenInput).not.toHaveAttribute('required');\n    });\n\n    it('keeps the hidden input required when no selection exists in multiple mode', async () => {\n      await render(\n        <Combobox.Root multiple required name=\"languages\" value={[]}>\n          <Combobox.Input />\n        </Combobox.Root>,\n      );\n\n      const hiddenInput = screen.getByRole('textbox', { hidden: true });\n      expect(hiddenInput).not.toBe(null);\n      expect(hiddenInput).toHaveAttribute('required');\n    });\n  });\n\n  describe('prop: readOnly', () => {\n    it('should render readOnly state on the input and disable interactions', async () => {\n      const { user } = await render(\n        <Combobox.Root readOnly>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Trigger data-testid=\"trigger\">Open</Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\" data-testid=\"item-a\">\n                    a\n                  </Combobox.Item>\n                  <Combobox.Item value=\"b\" data-testid=\"item-b\">\n                    b\n                  </Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      const trigger = screen.getByTestId('trigger');\n\n      expect(input).toHaveAttribute('aria-readonly', 'true');\n      expect(input).toHaveAttribute('readonly');\n\n      // Verify interactions are disabled\n      await user.click(trigger);\n      expect(screen.queryByRole('listbox')).toBe(null);\n    });\n\n    it('should not open popup when readOnly', async () => {\n      const { user } = await render(\n        <Combobox.Root readOnly>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Trigger data-testid=\"trigger\">Open</Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                  <Combobox.Item value=\"b\">b</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      const trigger = screen.getByTestId('trigger');\n\n      await user.click(input);\n      expect(screen.queryByRole('listbox')).toBe(null);\n\n      await user.click(trigger);\n      expect(screen.queryByRole('listbox')).toBe(null);\n    });\n\n    it('should prevent keyboard interactions when readOnly', async () => {\n      const { user } = await render(\n        <Combobox.Root readOnly>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                  <Combobox.Item value=\"b\">b</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n\n      await user.type(input, 'a');\n      expect(screen.queryByRole('listbox')).toBe(null);\n    });\n\n    it('should set readOnly attribute on hidden input', async () => {\n      await render(\n        <Combobox.Root readOnly name=\"test\">\n          <Combobox.Input />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const hiddenInput = screen.getByRole('textbox', { hidden: true });\n      expect(hiddenInput).toHaveAttribute('readonly');\n    });\n\n    it('should prevent value changes when readOnly with items', async () => {\n      const handleValueChange = vi.fn();\n      const { user } = await render(\n        <Combobox.Root readOnly onValueChange={handleValueChange} defaultOpen>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\" data-testid=\"item-a\">\n                    a\n                  </Combobox.Item>\n                  <Combobox.Item value=\"b\" data-testid=\"item-b\">\n                    b\n                  </Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const itemA = screen.getByTestId('item-a');\n      await user.click(itemA);\n\n      expect(handleValueChange.mock.calls.length).toBe(0);\n    });\n  });\n\n  describe('prop: itemToStringLabel', () => {\n    const items = [\n      { country: 'United States', code: 'US' },\n      { country: 'Canada', code: 'CA' },\n      { country: 'Australia', code: 'AU' },\n    ];\n\n    it('uses itemToStringLabel for input value synchronization', async () => {\n      const { user } = await render(\n        <Combobox.Root\n          items={items}\n          itemToStringLabel={(item: (typeof items)[number]) => item.country}\n          itemToStringValue={(item: (typeof items)[number]) => item.code}\n          defaultOpen\n        >\n          <Combobox.Input />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: { country: string; code: string }) => (\n                    <Combobox.Item key={item.code} value={item}>\n                      {item.country}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole('combobox');\n      await user.click(screen.getByText('Canada'));\n      expect(input).toHaveValue('Canada');\n    });\n\n    it('shows the label for a controlled object value not in items', async () => {\n      const value = { country: 'Japan', code: 'JP' };\n\n      await render(\n        <Combobox.Root\n          items={items}\n          value={value}\n          itemToStringLabel={(item) => item.country}\n          itemToStringValue={(item) => item.code}\n        >\n          <Combobox.Input />\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole('combobox');\n      expect(input).toHaveValue('Japan');\n    });\n  });\n\n  describe('prop: itemToStringValue', () => {\n    const items = [\n      { country: 'United States', code: 'US' },\n      { country: 'Canada', code: 'CA' },\n      { country: 'Australia', code: 'AU' },\n    ];\n\n    it('uses itemToStringValue for form submission', async () => {\n      await render(\n        <Combobox.Root\n          name=\"country\"\n          items={items}\n          itemToStringLabel={(item) => item.country}\n          itemToStringValue={(item) => item.code}\n          defaultValue={items[0]}\n        >\n          <Combobox.Input />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: string) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const hiddenInput = screen.getByDisplayValue('US'); // input[name=\"country\"]\n      expect(hiddenInput.tagName).toBe('INPUT');\n      expect(hiddenInput).toHaveAttribute('name', 'country');\n    });\n\n    it('uses itemToStringValue for multiple selection form submission', async () => {\n      const values = [items[0], items[1]];\n      await render(\n        <Combobox.Root\n          name=\"countries\"\n          items={items}\n          itemToStringLabel={(item) => item.country}\n          itemToStringValue={(item) => item.code}\n          multiple\n          defaultValue={values}\n        >\n          <Combobox.Input />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: string) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      values.forEach((value) => {\n        const input = screen.getByDisplayValue(value.code);\n        expect(input.tagName).toBe('INPUT');\n        expect(input).toHaveAttribute('name', 'countries');\n      });\n    });\n  });\n\n  describe('initial input value derivation', () => {\n    it('derives input from defaultValue on first mount when unspecified', async () => {\n      await render(\n        <Combobox.Root defaultValue=\"apple\">\n          <Combobox.Input />\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByRole('combobox')).toHaveValue('apple');\n    });\n\n    it('derives input from defaultValue on first mount with items prop', async () => {\n      const items = [{ value: 'apple', label: 'Apple' }];\n      await render(\n        <Combobox.Root items={items} defaultValue={items[0]}>\n          <Combobox.Input />\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByRole('combobox')).toHaveValue('Apple');\n    });\n\n    it('derives input from controlled value on first mount when unspecified', async () => {\n      await render(\n        <Combobox.Root value=\"banana\">\n          <Combobox.Input />\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByRole('combobox')).toHaveValue('banana');\n    });\n\n    it('defaultInputValue overrides derivation when provided', async () => {\n      await render(\n        <Combobox.Root defaultValue=\"apple\" defaultInputValue=\"x\">\n          <Combobox.Input />\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByRole('combobox')).toHaveValue('x');\n    });\n\n    it('inputValue overrides derivation when provided', async () => {\n      await render(\n        <Combobox.Root value=\"apple\" inputValue=\"x\">\n          <Combobox.Input />\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByRole('combobox')).toHaveValue('x');\n    });\n\n    it('multiple mode initial input remains empty', async () => {\n      const items = [\n        { value: 'a', label: 'A' },\n        { value: 'b', label: 'B' },\n      ];\n      await render(\n        <Combobox.Root multiple items={items}>\n          <Combobox.Input />\n        </Combobox.Root>,\n      );\n\n      const input = screen.getAllByRole('combobox').find((element) => element.tagName === 'INPUT');\n\n      expect(input).toHaveValue('');\n    });\n\n    it('does not set input value for input-inside-popup pattern', async () => {\n      await render(\n        <Combobox.Root defaultOpen defaultValue=\"apple\">\n          <Combobox.Trigger>Trigger</Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.Input />\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getAllByRole('combobox').find((element) => element.tagName === 'INPUT');\n\n      expect(input).toHaveValue('');\n    });\n  });\n\n  describe('input value synchronization', () => {\n    it('updates derived input when controlled value changes externally', async () => {\n      const items = [\n        { value: 'apple', label: 'Apple' },\n        { value: 'banana', label: 'Banana' },\n      ];\n\n      const { setProps } = await render(\n        <Combobox.Root items={items} value={items[0]}>\n          <Combobox.Input />\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n      expect(input).toHaveValue('Apple');\n\n      await setProps({ value: items[1] });\n\n      expect(input).toHaveValue('Banana');\n    });\n\n    it('re-derives input when items array changes', async () => {\n      const initialItems = [\n        { value: 'a', label: 'Apple' },\n        { value: 'b', label: 'Banana' },\n      ];\n\n      const { setProps } = await render(\n        <Combobox.Root items={initialItems} value={initialItems[0]}>\n          <Combobox.Input />\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n      expect(input).toHaveValue('Apple');\n\n      const nextItems = [\n        { value: 'a', label: 'Apricot' },\n        { value: 'b', label: 'Banana' },\n        { value: 'c', label: 'Cherry' },\n      ];\n\n      await setProps({ items: nextItems, value: nextItems[0] });\n      expect(input).toHaveValue('Apricot');\n\n      const sameLengthDifferentItems = [\n        { value: 'a', label: 'Ambrosia' },\n        { value: 'b', label: 'Blue Java' },\n        { value: 'c', label: 'Clementine' },\n      ];\n\n      await setProps({ items: sameLengthDifferentItems, value: sameLengthDifferentItems[0] });\n      expect(input).toHaveValue('Ambrosia');\n    });\n\n    it('restores derived input after items load asynchronously', async () => {\n      const { setProps } = await render(\n        <Combobox.Root items={[]} value=\"banana\">\n          <Combobox.Input />\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n      expect(input).toHaveValue('banana');\n\n      await setProps({ items: ['apple', 'banana', 'bread'] });\n\n      expect(input).toHaveValue('banana');\n\n      await setProps({ items: ['banana'] });\n\n      expect(input).toHaveValue('banana');\n    });\n  });\n\n  describe('prop: grid', () => {\n    it('sets grid roles when grid is enabled and rows are used', async () => {\n      await render(\n        <Combobox.Root grid defaultOpen>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Row>\n                    <Combobox.Item value=\"1\">1</Combobox.Item>\n                    <Combobox.Item value=\"2\">2</Combobox.Item>\n                    <Combobox.Item value=\"3\">3</Combobox.Item>\n                  </Combobox.Row>\n                  <Combobox.Row>\n                    <Combobox.Item value=\"4\">4</Combobox.Item>\n                    <Combobox.Item value=\"5\">5</Combobox.Item>\n                    <Combobox.Item value=\"6\">6</Combobox.Item>\n                  </Combobox.Row>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const grid = screen.getByRole('grid');\n      expect(grid).not.toBe(null);\n      const cells = screen.getAllByRole('gridcell');\n      expect(cells).toHaveLength(6);\n    });\n\n    it('arrow keys navigate across rows and columns in grid mode', async () => {\n      const onItemHighlighted = vi.fn();\n      const { user } = await render(\n        <Combobox.Root grid onItemHighlighted={onItemHighlighted} defaultOpen>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Row>\n                    <Combobox.Item value=\"1\">1</Combobox.Item>\n                    <Combobox.Item value=\"2\">2</Combobox.Item>\n                    <Combobox.Item value=\"3\">3</Combobox.Item>\n                  </Combobox.Row>\n                  <Combobox.Row>\n                    <Combobox.Item value=\"4\">4</Combobox.Item>\n                    <Combobox.Item value=\"5\">5</Combobox.Item>\n                    <Combobox.Item value=\"6\">6</Combobox.Item>\n                  </Combobox.Row>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      await user.click(input);\n      await waitFor(() => expect(screen.getByRole('grid')).not.toBe(null));\n\n      await user.keyboard('{ArrowDown}');\n      await waitFor(() => expect(onItemHighlighted.mock.lastCall?.[0]).toBe('1'));\n\n      await user.keyboard('{ArrowRight}');\n      await waitFor(() => expect(onItemHighlighted.mock.lastCall?.[0]).toBe('2'));\n\n      await user.keyboard('{ArrowRight}');\n      await waitFor(() => expect(onItemHighlighted.mock.lastCall?.[0]).toBe('3'));\n\n      await user.keyboard('{ArrowDown}');\n      await waitFor(() => expect(onItemHighlighted.mock.lastCall?.[0]).toBe('6'));\n\n      await user.keyboard('{ArrowLeft}');\n      await waitFor(() => expect(onItemHighlighted.mock.lastCall?.[0]).toBe('5'));\n\n      await user.keyboard('{ArrowUp}');\n      await waitFor(() => expect(onItemHighlighted.mock.lastCall?.[0]).toBe('2'));\n    });\n\n    it('supports uneven rows navigation', async () => {\n      const onItemHighlighted = vi.fn();\n      const { user } = await render(\n        <Combobox.Root grid onItemHighlighted={onItemHighlighted} defaultOpen>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Row>\n                    <Combobox.Item value=\"1\">1</Combobox.Item>\n                    <Combobox.Item value=\"2\">2</Combobox.Item>\n                    <Combobox.Item value=\"3\">3</Combobox.Item>\n                  </Combobox.Row>\n                  <Combobox.Row>\n                    <Combobox.Item value=\"4\">4</Combobox.Item>\n                    <Combobox.Item value=\"5\">5</Combobox.Item>\n                  </Combobox.Row>\n                  <Combobox.Row>\n                    <Combobox.Item value=\"6\">6</Combobox.Item>\n                    <Combobox.Item value=\"7\">7</Combobox.Item>\n                    <Combobox.Item value=\"8\">8</Combobox.Item>\n                    <Combobox.Item value=\"9\">9</Combobox.Item>\n                    <Combobox.Item value=\"10\">10</Combobox.Item>\n                  </Combobox.Row>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      await user.click(input);\n      await waitFor(() => expect(screen.getByRole('grid')).not.toBe(null));\n\n      await user.keyboard('{ArrowDown}');\n      await waitFor(() => expect(onItemHighlighted.mock.lastCall?.[0]).toBe('1'));\n\n      await user.keyboard('{ArrowRight}');\n      await waitFor(() => expect(onItemHighlighted.mock.lastCall?.[0]).toBe('2'));\n\n      await user.keyboard('{ArrowRight}');\n      await waitFor(() => expect(onItemHighlighted.mock.lastCall?.[0]).toBe('3'));\n\n      // Down from last col (3) to shorter row should clamp to last item (5)\n      await user.keyboard('{ArrowDown}');\n      await waitFor(() => expect(onItemHighlighted.mock.lastCall?.[0]).toBe('5'));\n\n      // Up from clamped item (5) should return to same column in previous row (2)\n      await user.keyboard('{ArrowUp}');\n      await waitFor(() => expect(onItemHighlighted.mock.lastCall?.[0]).toBe('2'));\n\n      // From 2, move down to 5 (same column), then down to 7 in the longer row\n      await user.keyboard('{ArrowDown}');\n      await waitFor(() => expect(onItemHighlighted.mock.lastCall?.[0]).toBe('5'));\n\n      await user.keyboard('{ArrowDown}');\n      await waitFor(() => expect(onItemHighlighted.mock.lastCall?.[0]).toBe('7'));\n\n      // Left within last row goes to 6, up to first col in previous row (4)\n      await user.keyboard('{ArrowLeft}');\n      await waitFor(() => expect(onItemHighlighted.mock.lastCall?.[0]).toBe('6'));\n\n      await user.keyboard('{ArrowUp}');\n      await waitFor(() => expect(onItemHighlighted.mock.lastCall?.[0]).toBe('4'));\n    });\n\n    it('supports uneven rows navigation within groups', async () => {\n      const onItemHighlighted = vi.fn();\n      const { user } = await render(\n        <Combobox.Root grid onItemHighlighted={onItemHighlighted} defaultOpen>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Group>\n                    <Combobox.Row>\n                      <Combobox.Item value=\"1\">1</Combobox.Item>\n                      <Combobox.Item value=\"2\">2</Combobox.Item>\n                      <Combobox.Item value=\"3\">3</Combobox.Item>\n                    </Combobox.Row>\n                  </Combobox.Group>\n                  <Combobox.Group>\n                    <Combobox.Row>\n                      <Combobox.Item value=\"4\">4</Combobox.Item>\n                      <Combobox.Item value=\"5\">5</Combobox.Item>\n                    </Combobox.Row>\n                  </Combobox.Group>\n                  <Combobox.Group>\n                    <Combobox.Row>\n                      <Combobox.Item value=\"6\">6</Combobox.Item>\n                      <Combobox.Item value=\"7\">7</Combobox.Item>\n                      <Combobox.Item value=\"8\">8</Combobox.Item>\n                      <Combobox.Item value=\"9\">9</Combobox.Item>\n                      <Combobox.Item value=\"10\">10</Combobox.Item>\n                    </Combobox.Row>\n                  </Combobox.Group>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      await user.click(input);\n      await waitFor(() => expect(screen.getByRole('grid')).not.toBe(null));\n\n      await user.keyboard('{ArrowDown}');\n      await waitFor(() => expect(onItemHighlighted.mock.lastCall?.[0]).toBe('1'));\n\n      await user.keyboard('{ArrowRight}');\n      await waitFor(() => expect(onItemHighlighted.mock.lastCall?.[0]).toBe('2'));\n\n      await user.keyboard('{ArrowRight}');\n      await waitFor(() => expect(onItemHighlighted.mock.lastCall?.[0]).toBe('3'));\n\n      await user.keyboard('{ArrowDown}');\n      await waitFor(() => expect(onItemHighlighted.mock.lastCall?.[0]).toBe('5'));\n\n      await user.keyboard('{ArrowUp}');\n      await waitFor(() => expect(onItemHighlighted.mock.lastCall?.[0]).toBe('2'));\n\n      await user.keyboard('{ArrowDown}');\n      await waitFor(() => expect(onItemHighlighted.mock.lastCall?.[0]).toBe('5'));\n\n      await user.keyboard('{ArrowDown}');\n      await waitFor(() => expect(onItemHighlighted.mock.lastCall?.[0]).toBe('7'));\n    });\n  });\n\n  describe('prop: multiple', () => {\n    it('\"single\" selects and closes, then reopens with selection focused', async () => {\n      const { user } = await render(\n        <Combobox.Root defaultOpen>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                  <Combobox.Item value=\"b\">b</Combobox.Item>\n                  <Combobox.Item value=\"c\">c</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      await user.click(screen.getByRole('option', { name: 'b' }));\n      expect(screen.queryByRole('listbox')).toBe(null);\n      expect(input).toHaveValue('b');\n\n      await user.click(input);\n      await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n      expect(screen.getByRole('option', { name: 'b' })).toHaveAttribute('aria-selected', 'true');\n    });\n\n    it('\"multiple\" clears uncontrolled input after select when filtering', async () => {\n      const { user } = await render(\n        <Combobox.Root multiple>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"apple\">apple</Combobox.Item>\n                  <Combobox.Item value=\"banana\">banana</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      await user.type(input, 'app');\n      await flushMicrotasks();\n      await user.click(screen.getByRole('option', { name: 'apple' }));\n      await flushMicrotasks();\n\n      // After selecting while filtering, uncontrolled input clears\n      expect(input).toHaveValue('');\n    });\n\n    it('does not close popup when filtering with input inside popup in multiple mode', async () => {\n      const items = ['apple', 'apricot', 'banana'];\n      const { user } = await render(\n        <Combobox.Root multiple items={items}>\n          <Combobox.Trigger data-testid=\"trigger\">\n            <Combobox.Value />\n          </Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.Input data-testid=\"input\" />\n                <Combobox.List>\n                  {(item: string) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      await user.click(trigger);\n\n      const input = await screen.findByTestId('input');\n      await user.type(input, 'app');\n      await user.click(screen.getByRole('option', { name: 'apple' }));\n\n      await waitFor(() => {\n        expect(screen.queryByRole('listbox')).not.toBe(null);\n      });\n      expect(input).toHaveValue('');\n    });\n\n    it('\"multiple\" clears typed input on close when no selection made', async () => {\n      const onInput = vi.fn();\n      const { user } = await render(\n        <Combobox.Root multiple defaultOpen onInputValueChange={onInput}>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"apple\">apple</Combobox.Item>\n                  <Combobox.Item value=\"banana\">banana</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      await user.type(input, 'app');\n      await flushMicrotasks();\n\n      // Close without selecting\n      await user.keyboard('{Escape}');\n\n      expect(screen.queryByRole('listbox')).toBe(null);\n      expect(input).toHaveValue('');\n      expect(onInput.mock.lastCall?.[0]).toBe('');\n      expect(onInput.mock.lastCall?.[1].reason).toBe(REASONS.inputClear);\n    });\n\n    it('\"single\" clears typed input on close when no selection made (input outside popup)', async () => {\n      const onInput = vi.fn();\n      const { user } = await render(\n        <Combobox.Root defaultOpen onInputValueChange={onInput}>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"apple\">apple</Combobox.Item>\n                  <Combobox.Item value=\"banana\">banana</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      await user.type(input, 'zz');\n      await flushMicrotasks();\n\n      // Close without selecting\n      await user.keyboard('{Escape}');\n\n      await waitFor(() => expect(screen.queryByRole('listbox')).toBe(null));\n      expect(input).toHaveValue('');\n      expect(onInput.mock.lastCall?.[0]).toBe('');\n      expect(onInput.mock.lastCall?.[1].reason).toBe(REASONS.inputClear);\n    });\n  });\n\n  describe('prop: filter', () => {\n    it('uses custom filter to narrow results', async () => {\n      const items = ['alpha', 'beta', 'alphabet', 'alpine'];\n      const startsWith = (item: string, q: string) => item.toLowerCase().startsWith(q);\n\n      const { user } = await render(\n        <Combobox.Root items={items} filter={(item, q) => startsWith(String(item), q)}>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      await user.type(input, 'alp');\n      await flushMicrotasks();\n\n      // Only beta should be filtered out\n      expect(screen.queryByText('beta')).toBe(null);\n      expect(screen.queryByText('alpha')).not.toBe(null);\n      expect(screen.queryByText('alphabet')).not.toBe(null);\n      expect(screen.queryByText('alpine')).not.toBe(null);\n    });\n\n    it('resets filtered results after selecting when using a custom search stringifier', async () => {\n      type Movie = { id: number; english: string; romaji: string };\n      const movies: Movie[] = [\n        { id: 1, english: 'Spirited Away', romaji: 'Sen to Chihiro no Kamikakushi' },\n        { id: 2, english: 'My Neighbor Totoro', romaji: 'Tonari no Totoro' },\n        { id: 3, english: 'Princess Mononoke', romaji: 'Mononoke Hime' },\n      ];\n\n      const stringifyMovie = (movie: Movie | null) =>\n        movie ? `${movie.english} ${movie.romaji}` : '';\n\n      function MultilingualFilterCombobox() {\n        const [value, setValue] = React.useState<Movie | null>(null);\n        const { contains } = Combobox.useFilter({ value });\n\n        const filter = React.useCallback(\n          (item: Movie | null, query: string) => {\n            if (!item) {\n              return false;\n            }\n            return contains(item, query, stringifyMovie);\n          },\n          [contains],\n        );\n\n        return (\n          <Combobox.Root\n            items={movies}\n            value={value}\n            onValueChange={setValue}\n            filter={filter}\n            itemToStringLabel={(movie) => movie?.english ?? ''}\n          >\n            <Combobox.Input data-testid=\"input\" />\n            <Combobox.Portal>\n              <Combobox.Positioner>\n                <Combobox.Popup>\n                  <Combobox.List>\n                    {(movie: Movie) => (\n                      <Combobox.Item key={movie.id} value={movie}>\n                        {movie.english}\n                      </Combobox.Item>\n                    )}\n                  </Combobox.List>\n                </Combobox.Popup>\n              </Combobox.Positioner>\n            </Combobox.Portal>\n          </Combobox.Root>\n        );\n      }\n\n      const { user } = await render(<MultilingualFilterCombobox />);\n      const input = screen.getByRole('combobox');\n\n      await user.click(input);\n      await screen.findByRole('listbox');\n\n      await user.type(input, 'tonari');\n\n      await waitFor(() => {\n        expect(screen.queryByRole('option', { name: 'Spirited Away' })).toBe(null);\n      });\n\n      await user.click(screen.getByRole('option', { name: 'My Neighbor Totoro' }));\n\n      await waitFor(() => {\n        expect(screen.queryByRole('listbox')).toBe(null);\n      });\n\n      await user.click(input);\n      await screen.findByRole('listbox');\n\n      await waitFor(() => {\n        expect(screen.queryByRole('option', { name: 'Spirited Away' })).not.toBe(null);\n      });\n    });\n  });\n\n  describe('prop: filteredItems', () => {\n    it('resets external filteredItems when reopening after a selection', async () => {\n      interface TestItem {\n        id: number;\n        label: string;\n        label2: string;\n      }\n\n      const testItems: TestItem[] = [\n        {\n          id: 1,\n          label: 'apple',\n          label2: 'one',\n        },\n        {\n          id: 2,\n          label: 'orange',\n          label2: 'two',\n        },\n        {\n          id: 3,\n          label: 'banana',\n          label2: 'three',\n        },\n      ];\n\n      function getItemLabelToFilter(item: TestItem | null) {\n        return item ? `${item.label} ${item.label2}` : '';\n      }\n\n      function getItemLabelToDisplay(item: TestItem | null) {\n        return item ? item.label || item.label2 : '';\n      }\n\n      function FilteredItemsCombobox() {\n        const [searchValue, setSearchValue] = React.useState('');\n        const [value, setValue] = React.useState<TestItem | null>(null);\n\n        const deferredSearchValue = React.useDeferredValue(searchValue);\n\n        const { contains } = Combobox.useFilter({ value });\n\n        const resolvedSearchValue =\n          searchValue === '' || deferredSearchValue === '' ? searchValue : deferredSearchValue;\n\n        const filteredItems = React.useMemo(() => {\n          return testItems.filter((item) =>\n            contains(item, resolvedSearchValue, getItemLabelToFilter),\n          );\n        }, [contains, resolvedSearchValue]);\n\n        return (\n          <Combobox.Root\n            items={testItems}\n            filteredItems={filteredItems}\n            inputValue={searchValue}\n            onInputValueChange={setSearchValue}\n            value={value}\n            onValueChange={setValue}\n            itemToStringLabel={getItemLabelToDisplay}\n            isItemEqualToValue={(item, v) => item?.id === v?.id}\n          >\n            <Combobox.Input />\n            <Combobox.Portal>\n              <Combobox.Positioner sideOffset={4}>\n                <Combobox.Popup>\n                  <Combobox.Empty>No items found.</Combobox.Empty>\n                  <Combobox.List>\n                    {(item: TestItem) => (\n                      <Combobox.Item key={item.id} value={item}>\n                        <Combobox.ItemIndicator></Combobox.ItemIndicator>\n                        {item.label}\n                      </Combobox.Item>\n                    )}\n                  </Combobox.List>\n                </Combobox.Popup>\n              </Combobox.Positioner>\n            </Combobox.Portal>\n          </Combobox.Root>\n        );\n      }\n\n      const { user } = await render(<FilteredItemsCombobox />);\n      const input = screen.getByRole('combobox');\n\n      await user.click(input);\n      await user.type(input, 'one');\n\n      await waitFor(() => {\n        expect(screen.queryByRole('option', { name: 'orange' })).toBe(null);\n      });\n\n      await user.click(screen.getByRole('option', { name: 'apple' }));\n\n      await waitFor(() => {\n        expect(screen.queryByRole('listbox')).toBe(null);\n      });\n\n      await user.click(input);\n\n      await waitFor(() => {\n        expect(screen.queryByRole('option', { name: 'orange' })).not.toBe(null);\n      });\n    });\n\n    it('uses filteredItems when items prop is omitted', async () => {\n      const fruits = ['Apple', 'Banana', 'Cherry'];\n\n      function FilteredItemsOnlyCombobox() {\n        const [value, setValue] = React.useState<string | null>(null);\n\n        return (\n          <Combobox.Root filteredItems={fruits} value={value} onValueChange={setValue}>\n            <Combobox.Input data-testid=\"input\" />\n            <Combobox.Portal>\n              <Combobox.Positioner sideOffset={4}>\n                <Combobox.Popup>\n                  <Combobox.List>\n                    {(item: string) => (\n                      <Combobox.Item key={item} value={item}>\n                        {item}\n                      </Combobox.Item>\n                    )}\n                  </Combobox.List>\n                </Combobox.Popup>\n              </Combobox.Positioner>\n            </Combobox.Portal>\n          </Combobox.Root>\n        );\n      }\n\n      const { user } = await render(<FilteredItemsOnlyCombobox />);\n      const input = screen.getByTestId('input');\n\n      await user.click(input);\n      await screen.findByRole('listbox');\n      await user.click(screen.getByRole('option', { name: 'Apple' }));\n\n      await waitFor(() => {\n        expect(screen.queryByRole('listbox')).toBe(null);\n      });\n\n      await user.click(input);\n      await screen.findByRole('listbox');\n\n      await waitFor(() => {\n        expect(screen.queryByRole('option', { name: 'Banana' })).not.toBe(null);\n      });\n    });\n\n    it('highlights the externally filtered item order when filtering reorders items', async () => {\n      const fruits = ['Apple', 'Banana', 'Zucchini'];\n      const onItemHighlighted = vi.fn();\n\n      function ReorderingFilteredItemsCombobox() {\n        const [input, setInput] = React.useState('');\n        const filteredItems = React.useMemo(() => {\n          if (input.length > 0) {\n            return [...fruits].reverse();\n          }\n          return fruits;\n        }, [input]);\n\n        return (\n          <Combobox.Root\n            autoHighlight\n            filteredItems={filteredItems}\n            inputValue={input}\n            onInputValueChange={setInput}\n            onItemHighlighted={onItemHighlighted}\n          >\n            <Combobox.Input data-testid=\"input\" />\n            <Combobox.Portal>\n              <Combobox.Positioner sideOffset={4}>\n                <Combobox.Popup>\n                  <Combobox.List>\n                    {(item: string) => (\n                      <Combobox.Item key={item} value={item}>\n                        {item}\n                      </Combobox.Item>\n                    )}\n                  </Combobox.List>\n                </Combobox.Popup>\n              </Combobox.Positioner>\n            </Combobox.Portal>\n          </Combobox.Root>\n        );\n      }\n\n      const { user } = await render(<ReorderingFilteredItemsCombobox />);\n      const input = screen.getByTestId('input');\n\n      await user.click(input);\n      await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n\n      onItemHighlighted.mockClear();\n\n      await user.type(input, 'a');\n\n      await waitFor(() => {\n        expect(onItemHighlighted.mock.calls.length).toBeGreaterThan(0);\n      });\n\n      const [highlightedValue] = onItemHighlighted.mock.lastCall ?? [];\n      expect(highlightedValue).toBe('Zucchini');\n    });\n  });\n\n  describe('prop: openOnInputClick', () => {\n    it('opens on input click by default', async () => {\n      const { user } = await render(\n        <Combobox.Root>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      await user.click(input);\n      await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n\n      // Click input again should not toggle closed automatically\n      await user.click(input);\n      await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n    });\n\n    it('does not open on input click when false, but opens on typing', async () => {\n      const { user } = await render(\n        <Combobox.Root openOnInputClick={false}>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      await user.click(input);\n      expect(screen.queryByRole('listbox')).toBe(null);\n\n      await user.type(input, 'a');\n      expect(screen.queryByRole('listbox')).not.toBe(null);\n    });\n  });\n\n  describe('prop: autoHighlight', () => {\n    it('does not auto-highlight on initial open when no selection', async () => {\n      await render(\n        <Combobox.Root items={['apple', 'banana', 'cherry']} autoHighlight defaultOpen>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: string) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n      expect(screen.getByRole('listbox')).not.toBe(null);\n      expect(input).not.toHaveAttribute('aria-activedescendant');\n    });\n\n    it('shows the selected item as selected on initial open (no active highlight)', async () => {\n      await render(\n        <Combobox.Root\n          items={['apple', 'banana', 'cherry']}\n          defaultValue=\"banana\"\n          autoHighlight\n          defaultOpen\n        >\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: string) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n      const banana = screen.getByRole('option', { name: 'banana' });\n\n      expect(banana).toHaveAttribute('aria-selected', 'true');\n      // Highlight is applied only after filtering begins\n      expect(input).not.toHaveAttribute('aria-activedescendant');\n    });\n\n    it('highlights the first matching item after typing (single mode)', async () => {\n      const { user } = await render(\n        <Combobox.Root items={['apple', 'banana', 'cherry']} autoHighlight>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: string) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n      await user.type(input, 'ch');\n\n      const cherry = await screen.findByRole('option', { name: 'cherry' });\n      expect(input).toHaveAttribute('aria-activedescendant', cherry.id);\n    });\n\n    it('highlights the first matching item after IME composition', async () => {\n      await render(\n        <Combobox.Root items={['apple', 'banana', 'cherry']} autoHighlight openOnInputClick={false}>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: string) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n      fireEvent.compositionStart(input);\n      fireEvent.change(input, { target: { value: 'ch' } });\n      fireEvent.compositionEnd(input, { data: 'ch' });\n\n      await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n      const cherry = await screen.findByRole('option', { name: 'cherry' });\n      await waitFor(() => expect(input).toHaveAttribute('aria-activedescendant', cherry.id));\n    });\n\n    it('highlights the first matching item for a static list without the items prop', async () => {\n      const { user } = await render(\n        <Combobox.Root autoHighlight>\n          <Combobox.Input />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"alpha\">alpha</Combobox.Item>\n                  <Combobox.Item value=\"alphabet\">alphabet</Combobox.Item>\n                  <Combobox.Item value=\"beta\">beta</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole('combobox');\n      await user.type(input, 'al');\n\n      const alpha = screen.getByRole('option', { name: 'alpha' });\n      await waitFor(() => expect(alpha).toHaveAttribute('data-highlighted'));\n      expect(input).toHaveAttribute('aria-activedescendant', alpha.id);\n\n      await user.type(input, ' ');\n      expect(alpha).toHaveAttribute('data-highlighted');\n      expect(input).toHaveAttribute('aria-activedescendant', alpha.id);\n    });\n\n    it('keeps gridcell typeahead active across Space in row mode without selecting', async () => {\n      const onValueChange = vi.fn();\n      const { user } = await render(\n        <Combobox.Root autoHighlight grid onValueChange={onValueChange}>\n          <Combobox.Input />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Row>\n                    <Combobox.Item value=\"new-york\">new york</Combobox.Item>\n                    <Combobox.Item value=\"new-jersey\">new jersey</Combobox.Item>\n                  </Combobox.Row>\n                  <Combobox.Row>\n                    <Combobox.Item value=\"old-town\">old town</Combobox.Item>\n                    <Combobox.Item value=\"other\">other</Combobox.Item>\n                  </Combobox.Row>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole('combobox');\n      await user.type(input, 'new');\n\n      const newYork = screen.getByRole('gridcell', { name: 'new york' });\n      await waitFor(() => expect(newYork).toHaveAttribute('data-highlighted'));\n      expect(input).toHaveAttribute('aria-activedescendant', newYork.id);\n\n      await user.type(input, ' ');\n      expect(newYork).toHaveAttribute('data-highlighted');\n      expect(input).toHaveAttribute('aria-activedescendant', newYork.id);\n      expect(input).toHaveValue('new ');\n      expect(onValueChange.mock.calls.length > 0).toBe(false);\n    });\n\n    it('retains highlight when query is cleared back to empty', async () => {\n      const { user } = await render(\n        <Combobox.Root items={['apple', 'banana', 'cherry']} autoHighlight>\n          <Combobox.Input />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: string) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n\n      await user.type(input, 'a');\n      await waitFor(() => {\n        expect(screen.queryByRole('listbox')).not.toBe(null);\n      });\n      await waitFor(() => expect(input).toHaveAttribute('aria-activedescendant'));\n\n      await user.clear(input);\n      await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n      await waitFor(() => expect(input).toHaveAttribute('aria-activedescendant'));\n    });\n\n    it('retains highlight when clearing the query with input-change behavior', async () => {\n      const { user } = await render(\n        <Combobox.Root items={['apple', 'banana', 'cherry']} autoHighlight>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: string) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n      await user.click(input);\n      await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n\n      await user.type(input, 'ban');\n      await screen.findByRole('option', { name: 'banana' });\n      await waitFor(() => expect(input).toHaveAttribute('aria-activedescendant'));\n      const highlightedBefore = input.getAttribute('aria-activedescendant');\n      expect(highlightedBefore).not.toBe(null);\n\n      await user.clear(input);\n      await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n      await waitFor(() => expect(input).toHaveAttribute('aria-activedescendant'));\n    });\n\n    it('highlights the first matching item after typing (multiple mode)', async () => {\n      const { user } = await render(\n        <Combobox.Root items={['apple', 'banana', 'cherry']} multiple autoHighlight>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: string) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n\n      await user.type(input, 'ba');\n      await waitFor(() => {\n        expect(screen.queryByRole('listbox')).not.toBe(null);\n      });\n\n      const activeId = input.getAttribute('aria-activedescendant');\n      expect(activeId).not.toBe(null);\n      const activeEl = document.getElementById(activeId!);\n      expect(activeEl?.textContent).toBe('banana');\n    });\n\n    it('clears highlight after removing the highlighted chip while filtering (multiple mode)', async () => {\n      const { user } = await render(\n        <Combobox.Root\n          items={['apple', 'banana', 'cherry']}\n          multiple\n          autoHighlight\n          defaultOpen\n          defaultValue={['apple']}\n        >\n          <Combobox.Chips>\n            <Combobox.Value>\n              {(value: string[]) => (\n                <React.Fragment>\n                  {value.map((item) => (\n                    <Combobox.Chip key={item}>\n                      {item}\n                      <Combobox.ChipRemove aria-label={`Remove ${item}`} />\n                    </Combobox.Chip>\n                  ))}\n                  <Combobox.Input data-testid=\"input\" />\n                </React.Fragment>\n              )}\n            </Combobox.Value>\n          </Combobox.Chips>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: string) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n\n      await waitFor(() => {\n        expect(screen.getByRole('listbox')).not.toBe(null);\n      });\n\n      await user.type(input, 'a');\n      await waitFor(() => expect(input).toHaveAttribute('aria-activedescendant'));\n\n      await user.click(screen.getByRole('button', { name: 'Remove apple', hidden: true }));\n\n      await waitFor(() => expect(input.getAttribute('aria-activedescendant')).toBe(null));\n    });\n\n    it('keeps the active item highlighted after clearing the last selected value', async () => {\n      const items = [\n        { id: 'js', value: 'JavaScript' },\n        { id: 'ts', value: 'TypeScript' },\n        { id: 'py', value: 'Python' },\n        { id: 'rb', value: 'Ruby' },\n      ];\n\n      const { user } = await render(\n        <Combobox.Root items={items} multiple>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: (typeof items)[number]) => (\n                    <Combobox.Item key={item.id} value={item}>\n                      {item.value}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n\n      await user.click(input);\n      await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n\n      await user.click(screen.getByRole('option', { name: 'JavaScript' }));\n      await user.click(screen.getByRole('option', { name: 'TypeScript' }));\n\n      await user.type(input, 'pyth');\n      await user.click(screen.getByRole('option', { name: 'Python' }));\n\n      await waitFor(() => expect(screen.queryByRole('listbox')).toBe(null));\n\n      input.focus();\n      await user.keyboard('{ArrowDown}');\n      await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n\n      await user.hover(screen.getByRole('option', { name: 'JavaScript' }));\n      await user.click(screen.getByRole('option', { name: 'JavaScript' }));\n      await user.hover(screen.getByRole('option', { name: 'TypeScript' }));\n      await user.click(screen.getByRole('option', { name: 'TypeScript' }));\n\n      const pythonOption = screen.getByRole('option', { name: 'Python' });\n      await user.hover(pythonOption);\n      await user.click(pythonOption);\n\n      await waitFor(() => {\n        expect(input).toHaveAttribute('aria-activedescendant', pythonOption.id);\n      });\n\n      await user.keyboard('{ArrowDown}');\n      const rubyOption = screen.getByRole('option', { name: 'Ruby' });\n      await waitFor(() => {\n        expect(input).toHaveAttribute('aria-activedescendant', rubyOption.id);\n      });\n    });\n\n    it('does not shift highlight to the previous selected item on Enter deselect', async () => {\n      const items = [\n        { id: 'js', value: 'JavaScript' },\n        { id: 'ts', value: 'TypeScript' },\n        { id: 'py', value: 'Python' },\n      ];\n\n      const { user } = await render(\n        <Combobox.Root items={items} multiple defaultValue={[items[0], items[1]]}>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: (typeof items)[number]) => (\n                    <Combobox.Item key={item.id} value={item}>\n                      {item.value}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n\n      await user.click(input);\n      await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n\n      const typeScriptOption = screen.getByRole('option', { name: 'TypeScript' });\n      await user.hover(typeScriptOption);\n      await waitFor(() => {\n        expect(input).toHaveAttribute('aria-activedescendant', typeScriptOption.id);\n      });\n\n      await user.keyboard('{Enter}');\n\n      await waitFor(() => {\n        expect(typeScriptOption).toHaveAttribute('aria-selected', 'false');\n        expect(input).toHaveAttribute('aria-activedescendant', typeScriptOption.id);\n      });\n    });\n\n    it('continues ArrowDown navigation from the Enter-selected item (multiple mode)', async () => {\n      const items = [\n        { id: 'js', value: 'JavaScript' },\n        { id: 'ts', value: 'TypeScript' },\n        { id: 'py', value: 'Python' },\n      ];\n\n      const { user } = await render(\n        <Combobox.Root items={items} multiple>\n          <Combobox.Chips>\n            <Combobox.Value>\n              {(value: (typeof items)[number][]) => (\n                <React.Fragment>\n                  {value.map((item) => (\n                    <Combobox.Chip key={item.id} aria-label={item.value}>\n                      {item.value}\n                      <Combobox.ChipRemove aria-label={`Remove ${item.value}`} />\n                    </Combobox.Chip>\n                  ))}\n                  <Combobox.Input data-testid=\"input\" />\n                </React.Fragment>\n              )}\n            </Combobox.Value>\n          </Combobox.Chips>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: (typeof items)[number]) => (\n                    <Combobox.Item key={item.id} value={item}>\n                      {item.value}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      await user.click(input);\n      await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n\n      const typeScriptOption = screen.getByRole('option', { name: 'TypeScript' });\n      await user.hover(typeScriptOption);\n      await waitFor(() => {\n        expect(input).toHaveAttribute('aria-activedescendant', typeScriptOption.id);\n      });\n\n      await user.keyboard('{Enter}');\n      await waitFor(() => {\n        expect(input).toHaveAttribute('aria-activedescendant', typeScriptOption.id);\n      });\n\n      await user.keyboard('{ArrowDown}');\n\n      const pythonOption = screen.getByRole('option', { name: 'Python' });\n      await waitFor(() => {\n        expect(input).toHaveAttribute('aria-activedescendant', pythonOption.id);\n      });\n    });\n\n    it('clears active highlight when removing the highlighted chip item', async () => {\n      const items = [\n        { id: 'js', value: 'JavaScript' },\n        { id: 'ts', value: 'TypeScript' },\n        { id: 'py', value: 'Python' },\n      ];\n\n      const { user } = await render(\n        <Combobox.Root items={items} multiple defaultValue={[items[0], items[1]]}>\n          <Combobox.Chips>\n            <Combobox.Value>\n              {(value: (typeof items)[number][]) => (\n                <React.Fragment>\n                  {value.map((item) => (\n                    <Combobox.Chip key={item.id} aria-label={item.value}>\n                      {item.value}\n                      <Combobox.ChipRemove aria-label={`Remove ${item.value}`} />\n                    </Combobox.Chip>\n                  ))}\n                  <Combobox.Input data-testid=\"input\" />\n                </React.Fragment>\n              )}\n            </Combobox.Value>\n          </Combobox.Chips>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: (typeof items)[number]) => (\n                    <Combobox.Item key={item.id} value={item}>\n                      {item.value}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      await user.click(input);\n      await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n\n      await user.keyboard('{ArrowUp}');\n\n      await waitFor(() => {\n        expect(input).toHaveAttribute('aria-activedescendant');\n      });\n\n      const highlightedOption = document.getElementById(\n        input.getAttribute('aria-activedescendant')!,\n      );\n      const highlightedLabel = highlightedOption?.textContent;\n      expect(highlightedLabel).not.toBe(null);\n\n      await user.click(\n        screen.getByRole('button', { name: `Remove ${highlightedLabel as string}`, hidden: true }),\n      );\n\n      await waitFor(() => {\n        expect(input.getAttribute('aria-activedescendant')).toBe(null);\n      });\n    });\n\n    it('keeps highlight in sync after selecting then backspacing to a single match', async () => {\n      const items = ['alpha', 'beta', 'gamma', 'delta', 'epsilon'];\n      const { user } = await render(\n        <Combobox.Root items={items} autoHighlight>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: string) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n      await user.click(input);\n      await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n\n      // Select index 4\n      await user.click(screen.getByRole('option', { name: 'epsilon' }));\n      await waitFor(() => expect(screen.queryByRole('listbox')).toBe(null));\n\n      // Reopen and press Backspace to narrow to a single match\n      await user.click(input);\n      await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n\n      // Backspace once: from 'epsilon' -> 'epsilo', which should still only match 'epsilon'\n      await user.keyboard('{Backspace}');\n      const epsilon = await screen.findByRole('option', { name: 'epsilon' });\n      // With autoHighlight, the first (and only) item should be highlighted\n      await waitFor(() => expect(input).toHaveAttribute('aria-activedescendant', epsilon.id));\n    });\n\n    it('navigates on first ArrowDown after editing selection to a new matching query', async () => {\n      const { user } = await render(\n        <Combobox.Root items={['Apple', 'Grape', 'Grapefruit']} autoHighlight>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: string) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n\n      // Open and select Apple\n      await user.click(input);\n      await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n      await user.click(screen.getByRole('option', { name: 'Apple' }));\n\n      // Edit input to \"Ape\" (matches Grape and Grapefruit)\n      await user.click(input);\n      await user.clear(input);\n      await user.type(input, 'Ape');\n      await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n\n      const grape = screen.getByRole('option', { name: 'Grape' });\n      const grapefruit = screen.getByRole('option', { name: 'Grapefruit' });\n\n      // With autoHighlight, first match is highlighted immediately\n      await waitFor(() => expect(input).toHaveAttribute('aria-activedescendant', grape.id));\n\n      // One ArrowDown should move to the next match (no double keypress needed)\n      await user.keyboard('{ArrowDown}');\n      await waitFor(() => expect(input).toHaveAttribute('aria-activedescendant', grapefruit.id));\n    });\n\n    it('updates highlighted callback with newly filtered first item', async () => {\n      const onItemHighlighted = vi.fn();\n      const items = ['banana', 'apple', 'apricot'];\n\n      const { user } = await render(\n        <Combobox.Root\n          items={items}\n          autoHighlight\n          defaultOpen\n          onItemHighlighted={onItemHighlighted}\n        >\n          <Combobox.Input />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: string) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole('combobox');\n\n      await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n      await user.click(input);\n      await user.keyboard('{ArrowDown}');\n\n      await waitFor(() => {\n        expect(onItemHighlighted.mock.calls.length).toBeGreaterThan(0);\n      });\n      const [initialValue] = onItemHighlighted.mock.lastCall ?? [];\n      expect(initialValue).toBe('banana');\n\n      onItemHighlighted.mockClear();\n\n      await user.type(input, 'ap');\n\n      await waitFor(() => {\n        expect(onItemHighlighted.mock.calls.length).toBeGreaterThan(0);\n      });\n      const [nextValue, data] = onItemHighlighted.mock.lastCall ?? [];\n      expect(nextValue).toBe('apple');\n      expect(data.reason).toBe('none');\n      expect(data.index).toBe(0);\n    });\n\n    it('fires a single clearing highlight on Enter selection', async () => {\n      const onItemHighlighted = vi.fn();\n\n      const { user } = await render(\n        <Combobox.Root\n          items={['Apple', 'Apricot', 'Banana']}\n          autoHighlight\n          onItemHighlighted={onItemHighlighted}\n        >\n          <Combobox.Input />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: string) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole('combobox');\n\n      await user.click(input);\n      await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n      await user.type(input, 'app');\n\n      // Reset history to focus on close events only.\n      onItemHighlighted.mockClear();\n      await user.keyboard('{Enter}');\n      await flushMicrotasks();\n\n      const clearingCalls = onItemHighlighted.mock.calls.filter((call) => call[0] === undefined);\n      expect(clearingCalls.length).toBe(1);\n      const postClearCalls = onItemHighlighted.mock.calls.slice(\n        onItemHighlighted.mock.calls.indexOf(clearingCalls[0]) + 1,\n      );\n      expect(postClearCalls.every((c) => c[0] === undefined)).toBe(true);\n    });\n  });\n\n  describe('prop: onItemHighlighted', () => {\n    it('fires on keyboard navigation', async () => {\n      const items = ['a', 'b', 'c'];\n      const onItemHighlighted = vi.fn();\n\n      const { user } = await render(\n        <Combobox.Root items={items} onItemHighlighted={onItemHighlighted}>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: string) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      await user.click(input);\n      await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n      await user.keyboard('{ArrowDown}');\n\n      await waitFor(() => {\n        expect(onItemHighlighted.mock.calls.length).toBeGreaterThan(0);\n      });\n      const [value, eventDetails] = onItemHighlighted.mock.lastCall ?? [];\n      expect(value).toBe('a');\n      expect(eventDetails.reason).toBe('keyboard');\n      expect(eventDetails.index).toBe(0);\n    });\n  });\n\n  describe('prop: open', () => {\n    it('controls the open state', async () => {\n      const { setProps, user } = await render(\n        <Combobox.Root open={false}>\n          <Combobox.Input />\n          <Combobox.Trigger>Open</Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole('combobox');\n\n      await user.click(input);\n      await waitFor(() => {\n        expect(screen.queryByRole('listbox')).toBe(null);\n      });\n\n      await setProps({ open: true });\n      await waitFor(() => {\n        expect(screen.getByRole('listbox')).not.toBe(null);\n      });\n\n      await user.click(document.body);\n      await waitFor(() => {\n        expect(screen.getByRole('listbox')).not.toBe(null);\n      });\n    });\n\n    it('keeps filtering responsive after selection when inline and open is controlled', async () => {\n      const items = ['Apple', 'Apricot', 'Banana', 'Grape', 'Orange'];\n\n      const { user } = await render(\n        <Combobox.Root items={items} open inline>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.List>\n            {(item: string) => (\n              <Combobox.Item key={item} value={item}>\n                {item}\n              </Combobox.Item>\n            )}\n          </Combobox.List>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n\n      await user.type(input, 'ap');\n\n      await waitFor(() => {\n        expect(screen.queryByRole('option', { name: 'Banana' })).toBe(null);\n      });\n\n      await user.click(screen.getByRole('option', { name: 'Apple' }));\n\n      await user.clear(input);\n      await user.type(input, 'ba');\n\n      await waitFor(() => {\n        expect(screen.getByRole('option', { name: 'Banana' })).not.toBe(null);\n      });\n    });\n  });\n\n  describe('prop: onOpenChange', () => {\n    it('fires when opening and closing', async () => {\n      const onOpenChange = vi.fn();\n\n      const { user } = await render(\n        <Combobox.Root onOpenChange={onOpenChange}>\n          <Combobox.Input />\n          <Combobox.Trigger>Open</Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole('combobox');\n\n      await user.click(input);\n      await waitFor(() => {\n        expect(onOpenChange.mock.calls.length).toBeGreaterThan(0);\n      });\n      expect(onOpenChange.mock.lastCall?.[0]).toBe(true);\n\n      // Close by clicking outside\n      await user.click(document.body);\n      await waitFor(() => {\n        expect(onOpenChange.mock.lastCall?.[0]).toBe(false);\n      });\n    });\n  });\n\n  describe('prop: defaultOpen', () => {\n    it('opens by default', async () => {\n      await render(\n        <Combobox.Root defaultOpen>\n          <Combobox.Input />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByRole('listbox')).not.toBe(null);\n    });\n\n    it('remains uncontrolled (can be closed via interaction)', async () => {\n      const { user } = await render(\n        <Combobox.Root defaultOpen>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Trigger>Open</Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByRole('listbox')).not.toBe(null);\n\n      await user.click(document.body);\n\n      await waitFor(() => {\n        expect(screen.queryByRole('listbox')).toBe(null);\n      });\n    });\n\n    it('is overridden by controlled open={false}', async () => {\n      await render(\n        <Combobox.Root defaultOpen open={false}>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      expect(screen.queryByRole('listbox')).toBe(null);\n    });\n\n    it('respects controlled open={true}', async () => {\n      await render(\n        <Combobox.Root defaultOpen open>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByRole('listbox')).not.toBe(null);\n    });\n  });\n\n  describe('prop: limit', () => {\n    it('limits the number of items displayed when no groups are used', async () => {\n      const items = ['apple', 'banana', 'cherry', 'date', 'elderberry'];\n      await render(\n        <Combobox.Root items={items} limit={3} defaultOpen>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      // Should only show the first 3 items\n      expect(screen.getByRole('option', { name: 'apple' })).not.toBe(null);\n      expect(screen.getByRole('option', { name: 'banana' })).not.toBe(null);\n      expect(screen.getByRole('option', { name: 'cherry' })).not.toBe(null);\n      expect(screen.queryByRole('option', { name: 'date' })).toBe(null);\n      expect(screen.queryByRole('option', { name: 'elderberry' })).toBe(null);\n    });\n\n    it('limits the number of items displayed when groups are used', async () => {\n      const items = [\n        {\n          value: 'citrus',\n          items: ['orange', 'lemon', 'lime'],\n        },\n        {\n          value: 'berries',\n          items: ['strawberry', 'blueberry', 'raspberry'],\n        },\n      ];\n\n      await render(\n        <Combobox.Root items={items} limit={4} defaultOpen>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(group) => (\n                    <Combobox.Group key={group.value} items={group.items}>\n                      <Combobox.GroupLabel>{group.value}</Combobox.GroupLabel>\n                      <Combobox.Collection>\n                        {(item) => (\n                          <Combobox.Item key={item} value={item}>\n                            {item}\n                          </Combobox.Item>\n                        )}\n                      </Combobox.Collection>\n                    </Combobox.Group>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      // Should show first 4 items across groups\n      expect(screen.getByRole('option', { name: 'orange' })).not.toBe(null);\n      expect(screen.getByRole('option', { name: 'lemon' })).not.toBe(null);\n      expect(screen.getByRole('option', { name: 'lime' })).not.toBe(null);\n      expect(screen.getByRole('option', { name: 'strawberry' })).not.toBe(null);\n      // These should be limited out\n      expect(screen.queryByRole('option', { name: 'blueberry' })).toBe(null);\n      expect(screen.queryByRole('option', { name: 'raspberry' })).toBe(null);\n\n      // Group labels should still be visible\n      expect(screen.getByText('citrus')).not.toBe(null);\n      expect(screen.getByText('berries')).not.toBe(null);\n    });\n\n    it('respects limit when filtering items', async () => {\n      const items = ['apple', 'apricot', 'avocado', 'banana', 'blueberry'];\n      const { user } = await render(\n        <Combobox.Root items={items} limit={2} defaultOpen>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n\n      // Type 'a' to filter items starting with 'a'\n      await user.type(input, 'a');\n      await flushMicrotasks();\n\n      // Should only show first 2 filtered items\n      expect(screen.getByRole('option', { name: 'apple' })).not.toBe(null);\n      expect(screen.getByRole('option', { name: 'apricot' })).not.toBe(null);\n      expect(screen.queryByRole('option', { name: 'avocado' })).toBe(null);\n      expect(screen.queryByRole('option', { name: 'banana' })).toBe(null);\n      expect(screen.queryByRole('option', { name: 'blueberry' })).toBe(null);\n    });\n\n    it('shows all items when limit is -1 (default)', async () => {\n      const items = ['apple', 'banana', 'cherry', 'date', 'elderberry'];\n      await render(\n        <Combobox.Root items={items} limit={-1} defaultOpen>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      // Should show all items\n      expect(screen.getByRole('option', { name: 'apple' })).not.toBe(null);\n      expect(screen.getByRole('option', { name: 'banana' })).not.toBe(null);\n      expect(screen.getByRole('option', { name: 'cherry' })).not.toBe(null);\n      expect(screen.getByRole('option', { name: 'date' })).not.toBe(null);\n      expect(screen.getByRole('option', { name: 'elderberry' })).not.toBe(null);\n    });\n\n    it('handles limit of 0 gracefully', async () => {\n      const items = ['apple', 'banana', 'cherry'];\n      await render(\n        <Combobox.Root items={items} limit={0} defaultOpen>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      // Should show no items\n      expect(screen.queryByRole('option', { name: 'apple' })).toBe(null);\n      expect(screen.queryByRole('option', { name: 'banana' })).toBe(null);\n      expect(screen.queryByRole('option', { name: 'cherry' })).toBe(null);\n    });\n\n    it('preserves order of items when applying limit across groups', async () => {\n      const items = [\n        {\n          value: 'groupA',\n          items: ['A1', 'A2'],\n        },\n        {\n          value: 'groupB',\n          items: ['B1', 'B2', 'B3'],\n        },\n      ];\n\n      await render(\n        <Combobox.Root items={items} limit={3} defaultOpen>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(group) => (\n                    <Combobox.Group key={group.value} items={group.items}>\n                      <Combobox.GroupLabel>Group {group.value.slice(-1)}</Combobox.GroupLabel>\n                      <Combobox.Collection>\n                        {(item) => (\n                          <Combobox.Item key={item} value={item}>\n                            {item}\n                          </Combobox.Item>\n                        )}\n                      </Combobox.Collection>\n                    </Combobox.Group>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      // Should show first 3 items in order: A1, A2, B1\n      expect(screen.getByRole('option', { name: 'A1' })).not.toBe(null);\n      expect(screen.getByRole('option', { name: 'A2' })).not.toBe(null);\n      expect(screen.getByRole('option', { name: 'B1' })).not.toBe(null);\n      expect(screen.queryByRole('option', { name: 'B2' })).toBe(null);\n      expect(screen.queryByRole('option', { name: 'B3' })).toBe(null);\n    });\n\n    it('does not limit items when not using items prop', async () => {\n      await render(\n        <Combobox.Root limit={2} defaultOpen>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"apple\">apple</Combobox.Item>\n                  <Combobox.Item value=\"banana\">banana</Combobox.Item>\n                  <Combobox.Item value=\"cherry\">cherry</Combobox.Item>\n                  <Combobox.Item value=\"date\">date</Combobox.Item>\n                  <Combobox.Item value=\"elderberry\">elderberry</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      // Should show all items because limit only works with items prop\n      expect(screen.getByRole('option', { name: 'apple' })).not.toBe(null);\n      expect(screen.getByRole('option', { name: 'banana' })).not.toBe(null);\n      expect(screen.getByRole('option', { name: 'cherry' })).not.toBe(null);\n      expect(screen.getByRole('option', { name: 'date' })).not.toBe(null);\n      expect(screen.getByRole('option', { name: 'elderberry' })).not.toBe(null);\n    });\n\n    it('updates displayed items when limit changes', async () => {\n      const items = ['apple', 'banana', 'cherry', 'date'];\n      const { setProps } = await render(\n        <Combobox.Root items={items} limit={2} defaultOpen>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      // Initially shows 2 items\n      expect(screen.getByRole('option', { name: 'apple' })).not.toBe(null);\n      expect(screen.getByRole('option', { name: 'banana' })).not.toBe(null);\n      expect(screen.queryByRole('option', { name: 'cherry' })).toBe(null);\n      expect(screen.queryByRole('option', { name: 'date' })).toBe(null);\n\n      // Update limit to 3\n      await setProps({ limit: 3 });\n      await flushMicrotasks();\n\n      // Now shows 3 items\n      expect(screen.getByRole('option', { name: 'apple' })).not.toBe(null);\n      expect(screen.getByRole('option', { name: 'banana' })).not.toBe(null);\n      expect(screen.getByRole('option', { name: 'cherry' })).not.toBe(null);\n      expect(screen.queryByRole('option', { name: 'date' })).toBe(null);\n    });\n  });\n\n  describe('dialog pattern', () => {\n    const fruits = ['Apple', 'Apricot', 'Banana', 'Grape', 'Orange'];\n\n    function DialogMultipleCombobox({ defaultOpen = true }: { defaultOpen?: boolean }) {\n      const [open, setOpen] = React.useState(defaultOpen);\n      return (\n        <Combobox.Root multiple items={fruits} inline>\n          <Dialog.Root open={open} onOpenChange={setOpen}>\n            <Dialog.Trigger>Trigger</Dialog.Trigger>\n            <Dialog.Portal>\n              <Dialog.Popup aria-label=\"Fruit chooser\">\n                <Combobox.Chips>\n                  <Combobox.Input data-testid=\"dialog-input\" />\n                  <Combobox.List>\n                    {(item: string) => (\n                      <Combobox.Item key={item} value={item}>\n                        {item}\n                      </Combobox.Item>\n                    )}\n                  </Combobox.List>\n                </Combobox.Chips>\n              </Dialog.Popup>\n            </Dialog.Portal>\n          </Dialog.Root>\n        </Combobox.Root>\n      );\n    }\n\n    function DialogSingleCombobox({ defaultOpen = true }: { defaultOpen?: boolean }) {\n      const [open, setOpen] = React.useState(defaultOpen);\n      const inputId = React.useId();\n\n      return (\n        <Combobox.Root items={fruits} open={open} onOpenChange={setOpen} inline>\n          <Dialog.Root open={open} onOpenChange={setOpen}>\n            <Dialog.Trigger data-testid=\"dialog-trigger\">\n              <Combobox.Value>\n                {(value: string | null) => (value == null ? 'Select a fruit' : value)}\n              </Combobox.Value>\n            </Dialog.Trigger>\n            <Dialog.Portal>\n              <Dialog.Popup aria-label=\"Fruit chooser\">\n                <div>\n                  <label htmlFor={inputId}>Fruit</label>\n                  <Combobox.Input\n                    id={inputId}\n                    data-testid=\"dialog-input\"\n                    placeholder=\"e.g. Apple\"\n                  />\n                </div>\n                <Combobox.List>\n                  {(item: string) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n                <Dialog.Close>Done</Dialog.Close>\n              </Dialog.Popup>\n            </Dialog.Portal>\n          </Dialog.Root>\n        </Combobox.Root>\n      );\n    }\n\n    describe('multiple', () => {\n      it('clears input after filtering, removes filter and highlight', async () => {\n        const { user } = await render(<DialogMultipleCombobox />);\n\n        const input = await screen.findByTestId('dialog-input');\n\n        await user.type(input, 'ap');\n\n        await waitFor(() => {\n          expect(screen.queryByRole('option', { name: 'Banana' })).toBe(null);\n        });\n        expect(screen.getByRole('option', { name: 'Apple' })).not.toBe(null);\n        expect(screen.getByRole('option', { name: 'Apricot' })).not.toBe(null);\n\n        await user.click(screen.getByRole('option', { name: 'Apple' }));\n\n        expect(input).toHaveValue('');\n        await waitFor(() => {\n          expect(screen.queryByRole('option', { name: 'Banana' })).not.toBe(null);\n        });\n        expect(input).toHaveAttribute('aria-activedescendant');\n      });\n\n      it('still filters after selecting an item', async () => {\n        const { user } = await render(<DialogMultipleCombobox />);\n\n        const input = await screen.findByTestId('dialog-input');\n\n        await user.type(input, 'ap');\n\n        await waitFor(() => {\n          expect(screen.queryByRole('option', { name: 'Banana' })).toBe(null);\n        });\n        expect(screen.getByRole('option', { name: 'Apple' })).not.toBe(null);\n        expect(screen.getByRole('option', { name: 'Apricot' })).not.toBe(null);\n\n        await user.click(screen.getByRole('option', { name: 'Apple' }));\n\n        expect(input).toHaveValue('');\n        await waitFor(() => {\n          expect(screen.queryByRole('option', { name: 'Banana' })).not.toBe(null);\n        });\n        expect(input).toHaveAttribute('aria-activedescendant');\n\n        await user.type(input, 'ap');\n\n        await waitFor(() => {\n          expect(screen.queryByRole('option', { name: 'Banana' })).toBe(null);\n        });\n      });\n\n      it('retains highlight on selected item when not filtering', async () => {\n        const { user } = await render(<DialogMultipleCombobox />);\n\n        const input = await screen.findByTestId('dialog-input');\n\n        await act(async () => {\n          input.focus();\n        });\n\n        await user.keyboard('{ArrowDown}');\n        await waitFor(() => {\n          const apple = screen.getByRole('option', { name: 'Apple' });\n          expect(input).toHaveAttribute('aria-activedescendant', apple.id);\n        });\n\n        await user.keyboard('{Enter}');\n\n        await waitFor(() => {\n          const apple = screen.getByRole('option', { name: 'Apple' });\n          expect(input).toHaveAttribute('aria-activedescendant', apple.id);\n        });\n      });\n    });\n\n    describe('single', () => {\n      it('closes the dialog after selecting an item and updates the trigger value', async () => {\n        if (reactMajor <= 18) {\n          ignoreActWarnings();\n        }\n\n        const { user } = await render(<DialogSingleCombobox defaultOpen={false} />);\n\n        const trigger = screen.getByTestId('dialog-trigger');\n        await user.click(trigger);\n\n        await screen.findByRole('dialog', { name: 'Fruit chooser' });\n        const input = await screen.findByTestId('dialog-input');\n\n        await user.type(input, 'ap');\n        await user.click(screen.getByRole('option', { name: 'Apple' }));\n\n        await waitFor(() => {\n          expect(screen.queryByRole('dialog', { name: 'Fruit chooser' })).toBe(null);\n        });\n\n        await waitFor(() => {\n          expect(trigger).toHaveTextContent('Apple');\n        });\n      });\n\n      it('clears the filter input when re-opening after a selection', async () => {\n        if (reactMajor <= 18) {\n          ignoreActWarnings();\n        }\n\n        const { user } = await render(<DialogSingleCombobox defaultOpen={false} />);\n\n        const trigger = screen.getByTestId('dialog-trigger');\n        await user.click(trigger);\n\n        await screen.findByRole('dialog', { name: 'Fruit chooser' });\n        const input = await screen.findByTestId('dialog-input');\n\n        await user.type(input, 'ap');\n        await user.click(screen.getByRole('option', { name: 'Apple' }));\n\n        await waitFor(() => {\n          expect(screen.queryByRole('dialog', { name: 'Fruit chooser' })).toBe(null);\n        });\n\n        await user.click(trigger);\n\n        await screen.findByRole('dialog', { name: 'Fruit chooser' });\n        const reopenedInput = await screen.findByTestId('dialog-input');\n\n        expect(reopenedInput).toHaveValue('');\n        await screen.findByRole('option', { name: 'Banana' });\n      });\n\n      it('restores highlight when the input regains focus', async () => {\n        const { user } = await render(<DialogSingleCombobox />);\n\n        const input = await screen.findByTestId('dialog-input');\n\n        await act(async () => {\n          input.focus();\n        });\n\n        await user.keyboard('{ArrowDown}');\n\n        const apple = screen.getByRole('option', { name: 'Apple' });\n\n        await waitFor(() => {\n          expect(apple).toHaveAttribute('data-highlighted');\n          expect(input).toHaveAttribute('aria-activedescendant', apple.id);\n        });\n\n        const done = screen.getByRole('button', { name: 'Done' });\n        fireEvent.blur(input, { relatedTarget: done });\n        fireEvent.focus(done);\n\n        await waitFor(() => {\n          expect(input).not.toHaveAttribute('aria-activedescendant');\n          expect(apple).not.toHaveAttribute('data-highlighted');\n        });\n\n        fireEvent.blur(done, { relatedTarget: input });\n        fireEvent.focus(input);\n\n        await waitFor(() => {\n          expect(apple).toHaveAttribute('data-highlighted');\n          expect(input).toHaveAttribute('aria-activedescendant', apple.id);\n        });\n      });\n    });\n  });\n\n  describe('Form', () => {\n    const { render: renderFakeTimers, clock } = createRenderer({\n      clockOptions: {\n        shouldAdvanceTime: true,\n      },\n    });\n\n    clock.withFakeTimers();\n\n    it('submits stringified value to onFormSubmit when itemToStringValue is provided', async () => {\n      const items = [\n        { code: 'US', label: 'United States' },\n        { code: 'CA', label: 'Canada' },\n      ];\n      const handleFormSubmit = vi.fn();\n\n      const { user } = await renderFakeTimers(\n        <Form onFormSubmit={handleFormSubmit}>\n          <Field.Root name=\"country\">\n            <Combobox.Root\n              items={items}\n              itemToStringLabel={(item) => item.label}\n              itemToStringValue={(item) => item.code}\n              defaultValue={items[0]}\n            >\n              <Combobox.Input />\n              <Combobox.Portal>\n                <Combobox.Positioner>\n                  <Combobox.Popup>\n                    <Combobox.List>\n                      {(item: (typeof items)[number]) => (\n                        <Combobox.Item key={item.code} value={item}>\n                          {item.label}\n                        </Combobox.Item>\n                      )}\n                    </Combobox.List>\n                  </Combobox.Popup>\n                </Combobox.Positioner>\n              </Combobox.Portal>\n            </Combobox.Root>\n          </Field.Root>\n          <button type=\"submit\">Submit</button>\n        </Form>,\n      );\n\n      await user.click(screen.getByText('Submit'));\n\n      expect(handleFormSubmit.mock.calls.length).toBe(1);\n      expect(handleFormSubmit.mock.calls[0][0]).toEqual({ country: 'US' });\n    });\n\n    describe('serialization for object values', () => {\n      const items = [\n        { value: 'US', label: 'United States' },\n        { value: 'CA', label: 'Canada' },\n        { value: 'AU', label: 'Australia' },\n      ];\n\n      it('serializes {value,label} objects using their value field', async () => {\n        await render(\n          <Combobox.Root\n            name=\"country\"\n            items={items}\n            itemToStringLabel={(item) => item.label}\n            defaultValue={items[1]}\n          >\n            <Combobox.Input />\n            <Combobox.Portal>\n              <Combobox.Positioner>\n                <Combobox.Popup>\n                  <Combobox.List>\n                    {(item: { value: string; label: string }) => (\n                      <Combobox.Item key={item.value} value={item}>\n                        {item.label}\n                      </Combobox.Item>\n                    )}\n                  </Combobox.List>\n                </Combobox.Popup>\n              </Combobox.Positioner>\n            </Combobox.Portal>\n          </Combobox.Root>,\n        );\n\n        const hiddenInput = screen.getByDisplayValue('CA');\n        expect(hiddenInput.tagName).toBe('INPUT');\n        expect(hiddenInput).toHaveAttribute('name', 'country');\n      });\n\n      it('serializes multiple {value,label} objects into multiple hidden inputs', async () => {\n        const values = [items[0], items[2]];\n        const { container } = await render(\n          <Combobox.Root\n            name=\"countries\"\n            items={items}\n            itemToStringLabel={(item) => item.label}\n            multiple\n            defaultValue={values}\n          >\n            <Combobox.Input />\n            <Combobox.Portal>\n              <Combobox.Positioner>\n                <Combobox.Popup>\n                  <Combobox.List>\n                    {(item: { value: string; label: string }) => (\n                      <Combobox.Item key={item.value} value={item}>\n                        {item.label}\n                      </Combobox.Item>\n                    )}\n                  </Combobox.List>\n                </Combobox.Popup>\n              </Combobox.Positioner>\n            </Combobox.Portal>\n          </Combobox.Root>,\n        );\n\n        // eslint-disable-next-line testing-library/no-container -- Can't avoid container here. A better test would be checking form submission.\n        const hiddenInputs = container.querySelectorAll('input[name=\"countries\"]');\n        expect(hiddenInputs).toHaveLength(values.length);\n        values.forEach((item, index) => {\n          expect(hiddenInputs[index]).toHaveValue(item.value);\n        });\n      });\n\n      it('falls back to itemToStringValue when object lacks value', async () => {\n        const codeItems = [\n          { code: 'US', name: 'United States' },\n          { code: 'CA', name: 'Canada' },\n          { code: 'AU', name: 'Australia' },\n        ];\n\n        const { container } = await render(\n          <Combobox.Root\n            name=\"country\"\n            items={codeItems}\n            itemToStringLabel={(item) => item.name}\n            itemToStringValue={(item) => item.code}\n            defaultValue={codeItems[0]}\n          >\n            <Combobox.Input />\n            <Combobox.Portal>\n              <Combobox.Positioner>\n                <Combobox.Popup>\n                  <Combobox.List>\n                    {(item: { code: string; name: string }) => (\n                      <Combobox.Item key={item.code} value={item}>\n                        {item.name}\n                      </Combobox.Item>\n                    )}\n                  </Combobox.List>\n                </Combobox.Popup>\n              </Combobox.Positioner>\n            </Combobox.Portal>\n          </Combobox.Root>,\n        );\n\n        // eslint-disable-next-line testing-library/no-container -- Can't avoid container here. A better test would be checking form submission.\n        const hiddenInput = container.querySelector('input[name=\"country\"]');\n        expect(hiddenInput).toHaveValue('US');\n      });\n    });\n\n    it('triggers native HTML validation on submit', async () => {\n      const { user } = await render(\n        <Form>\n          <Field.Root name=\"test\" data-testid=\"field\">\n            <Combobox.Root required>\n              <Combobox.Input data-testid=\"input\" />\n              <Combobox.Portal>\n                <Combobox.Positioner />\n              </Combobox.Portal>\n            </Combobox.Root>\n            <Field.Error match=\"valueMissing\" data-testid=\"error\">\n              required\n            </Field.Error>\n          </Field.Root>\n          <button type=\"submit\">Submit</button>\n        </Form>,\n      );\n\n      const submit = screen.getByText('Submit');\n\n      expect(screen.queryByTestId('error')).toBe(null);\n\n      await user.click(submit);\n\n      const error = screen.getByTestId('error');\n      expect(error).toHaveTextContent('required');\n    });\n\n    it('focuses trigger and surfaces errors when input is inside popup', async () => {\n      ignoreActWarnings();\n\n      let submittedCalls = 0;\n\n      const handleSubmit: React.FormEventHandler<HTMLFormElement> = (event) => {\n        event.preventDefault();\n        submittedCalls += 1;\n      };\n\n      const { user } = await render(\n        <Form onSubmit={handleSubmit}>\n          <Field.Root name=\"combobox\">\n            <Combobox.Root required>\n              <Combobox.Trigger data-testid=\"trigger\">\n                <Combobox.Value />\n              </Combobox.Trigger>\n              <Combobox.Portal>\n                <Combobox.Positioner>\n                  <Combobox.Popup>\n                    <Combobox.Input data-testid=\"input\" />\n                    <Combobox.List>\n                      <Combobox.Item value=\"a\">a</Combobox.Item>\n                      <Combobox.Item value=\"b\">b</Combobox.Item>\n                    </Combobox.List>\n                  </Combobox.Popup>\n                </Combobox.Positioner>\n              </Combobox.Portal>\n            </Combobox.Root>\n            <Field.Error match=\"valueMissing\" data-testid=\"error\">\n              required\n            </Field.Error>\n          </Field.Root>\n          <button type=\"submit\">Submit</button>\n        </Form>,\n      );\n\n      expect(screen.queryByTestId('error')).toBe(null);\n\n      await user.click(screen.getByText('Submit'));\n\n      expect(submittedCalls).toBe(0);\n\n      const trigger = screen.getByTestId('trigger');\n\n      await waitFor(() => expect(trigger).toHaveFocus());\n      expect(trigger).toHaveAttribute('data-invalid', '');\n\n      const error = screen.getByTestId('error');\n      expect(error).toHaveTextContent('required');\n\n      await user.click(trigger);\n\n      const input = await screen.findByTestId('input');\n      expect(input).not.toHaveAttribute('data-invalid');\n    });\n\n    it('clears external errors on change', async () => {\n      const { user } = await renderFakeTimers(\n        <Form\n          errors={{\n            combobox: 'test',\n          }}\n        >\n          <Field.Root name=\"combobox\">\n            <Combobox.Root>\n              <Combobox.Input data-testid=\"input\" />\n              <Combobox.Portal>\n                <Combobox.Positioner>\n                  <Combobox.Popup>\n                    <Combobox.List>\n                      <Combobox.Item value=\"a\">a</Combobox.Item>\n                      <Combobox.Item value=\"b\">b</Combobox.Item>\n                    </Combobox.List>\n                  </Combobox.Popup>\n                </Combobox.Positioner>\n              </Combobox.Portal>\n            </Combobox.Root>\n            <Field.Error data-testid=\"error\" />\n          </Field.Root>\n        </Form>,\n      );\n\n      expect(screen.getByTestId('error')).toHaveTextContent('test');\n\n      const input = screen.getByTestId('input');\n      expect(input).toHaveAttribute('aria-invalid', 'true');\n\n      await user.click(input);\n      await flushMicrotasks();\n\n      const option = screen.getByRole('option', { name: 'b' });\n      clock.tick(200);\n      await user.click(option);\n\n      expect(screen.queryByTestId('error')).toBe(null);\n      expect(input).not.toHaveAttribute('aria-invalid');\n    });\n\n    it('submits on Enter when no item is highlighted (does not prevent)', async () => {\n      let submittedCalls = 0;\n\n      const handleSubmit: React.FormEventHandler<HTMLFormElement> = (event) => {\n        event.preventDefault();\n        submittedCalls += 1;\n      };\n\n      const { user } = await render(\n        <Form onSubmit={handleSubmit}>\n          <Field.Root name=\"q\">\n            <Combobox.Root items={['apple', 'banana']} openOnInputClick>\n              <Combobox.Input />\n              <Combobox.Portal>\n                <Combobox.Positioner>\n                  <Combobox.Popup>\n                    <Combobox.List>\n                      {(item) => (\n                        <Combobox.Item key={item} value={item}>\n                          {item}\n                        </Combobox.Item>\n                      )}\n                    </Combobox.List>\n                  </Combobox.Popup>\n                </Combobox.Positioner>\n              </Combobox.Portal>\n            </Combobox.Root>\n          </Field.Root>\n          <button type=\"submit\">Submit</button>\n        </Form>,\n      );\n\n      const input = screen.getByRole('combobox');\n      await user.click(input);\n      // No navigation, so nothing highlighted\n      await user.keyboard('{Enter}');\n\n      expect(submittedCalls).toBe(1);\n    });\n\n    it('prevents submit on Enter when an item is highlighted', async () => {\n      let submittedCalls = 0;\n\n      const handleSubmit: React.FormEventHandler<HTMLFormElement> = (event) => {\n        event.preventDefault();\n        submittedCalls += 1;\n      };\n\n      const { user } = await render(\n        <Form onSubmit={handleSubmit}>\n          <Field.Root name=\"q\">\n            <Combobox.Root items={['alpha', 'beta']} openOnInputClick>\n              <Combobox.Input />\n              <Combobox.Portal>\n                <Combobox.Positioner>\n                  <Combobox.Popup>\n                    <Combobox.List>\n                      {(item) => (\n                        <Combobox.Item key={item} value={item}>\n                          {item}\n                        </Combobox.Item>\n                      )}\n                    </Combobox.List>\n                  </Combobox.Popup>\n                </Combobox.Positioner>\n              </Combobox.Portal>\n            </Combobox.Root>\n          </Field.Root>\n          <button type=\"submit\">Submit</button>\n        </Form>,\n      );\n\n      const input = screen.getByRole('combobox');\n      await user.click(input);\n      await user.keyboard('{ArrowDown}');\n      await user.keyboard('{Enter}');\n\n      expect(submittedCalls).toBe(0);\n    });\n  });\n\n  describe('Field', () => {\n    const { render: renderFakeTimers, clock } = createRenderer({\n      clockOptions: {\n        shouldAdvanceTime: true,\n      },\n    });\n\n    clock.withFakeTimers();\n\n    it('should receive disabled prop from Field.Root', async () => {\n      await render(\n        <Field.Root disabled>\n          <Combobox.Root>\n            <Combobox.Input data-testid=\"input\" />\n            <Combobox.Trigger data-testid=\"trigger\">Open</Combobox.Trigger>\n            <Combobox.Portal>\n              <Combobox.Positioner>\n                <Combobox.Popup>\n                  <Combobox.List>\n                    <Combobox.Item value=\"a\">a</Combobox.Item>\n                    <Combobox.Item value=\"b\">b</Combobox.Item>\n                  </Combobox.List>\n                </Combobox.Popup>\n              </Combobox.Positioner>\n            </Combobox.Portal>\n          </Combobox.Root>\n        </Field.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      expect(input).toHaveAttribute('disabled');\n\n      const trigger = screen.getByTestId('trigger');\n      expect(trigger).toHaveAttribute('disabled');\n    });\n\n    it('should receive name prop from Field.Root', async () => {\n      await render(\n        <Field.Root name=\"field-combobox\">\n          <Combobox.Root>\n            <Combobox.Input data-testid=\"input\" />\n            <Combobox.Portal>\n              <Combobox.Positioner>\n                <Combobox.Popup>\n                  <Combobox.List>\n                    <Combobox.Item value=\"a\">a</Combobox.Item>\n                    <Combobox.Item value=\"b\">b</Combobox.Item>\n                  </Combobox.List>\n                </Combobox.Popup>\n              </Combobox.Positioner>\n            </Combobox.Portal>\n          </Combobox.Root>\n        </Field.Root>,\n      );\n\n      const hiddenInput = screen.getByRole('textbox', { hidden: true });\n      expect(hiddenInput).toHaveAttribute('name', 'field-combobox');\n    });\n\n    it('Field.Label links to Combobox.Trigger when input is inside popup and trigger has an explicit id', async () => {\n      await render(\n        <Field.Root>\n          <Field.Label data-testid=\"label\">Search</Field.Label>\n          <Combobox.Root>\n            <Combobox.Trigger data-testid=\"trigger\" id=\"x-id\">\n              Open\n            </Combobox.Trigger>\n            <Combobox.Portal>\n              <Combobox.Positioner>\n                <Combobox.Popup>\n                  <Combobox.Input />\n                  <Combobox.List>\n                    <Combobox.Item value=\"a\">a</Combobox.Item>\n                    <Combobox.Item value=\"b\">b</Combobox.Item>\n                  </Combobox.List>\n                </Combobox.Popup>\n              </Combobox.Positioner>\n            </Combobox.Portal>\n          </Combobox.Root>\n        </Field.Root>,\n      );\n\n      const label = screen.getByTestId<HTMLLabelElement>('label');\n      const trigger = screen.getByTestId('trigger');\n\n      /* eslint-disable testing-library/no-wait-for-multiple-assertions */\n      await waitFor(() => {\n        expect(trigger).toHaveAttribute('id', 'x-id');\n        expect(trigger).toHaveAttribute('aria-labelledby', label.id);\n      });\n      /* eslint-enable testing-library/no-wait-for-multiple-assertions */\n    });\n\n    it('does not apply validation ARIA attributes to input inside popup', async () => {\n      const { user } = await render(\n        <Field.Root invalid>\n          <Combobox.Root>\n            <Combobox.Trigger data-testid=\"trigger\">Open</Combobox.Trigger>\n            <Combobox.Portal>\n              <Combobox.Positioner>\n                <Combobox.Popup>\n                  <Combobox.Input data-testid=\"input\" />\n                  <Combobox.List>\n                    <Combobox.Item value=\"a\">a</Combobox.Item>\n                    <Combobox.Item value=\"b\">b</Combobox.Item>\n                  </Combobox.List>\n                </Combobox.Popup>\n              </Combobox.Positioner>\n            </Combobox.Portal>\n          </Combobox.Root>\n          <Field.Description data-testid=\"description\" />\n          <Field.Error data-testid=\"error\" match />\n        </Field.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      const description = screen.getByTestId('description');\n      const error = screen.getByTestId('error');\n      const triggerDescribedBy = trigger.getAttribute('aria-describedby');\n\n      expect(trigger).toHaveAttribute('aria-invalid', 'true');\n      expect(triggerDescribedBy).toContain(description.id);\n      expect(triggerDescribedBy).toContain(error.id);\n\n      await user.click(trigger);\n\n      const input = await screen.findByTestId('input');\n\n      expect(input).not.toHaveAttribute('aria-invalid');\n      expect(input).not.toHaveAttribute('aria-describedby');\n      expect(input).not.toHaveAttribute('data-valid');\n      expect(input).not.toHaveAttribute('data-invalid');\n      expect(input).not.toHaveAttribute('data-touched');\n      expect(input).not.toHaveAttribute('data-dirty');\n      expect(input).not.toHaveAttribute('data-filled');\n      expect(input).not.toHaveAttribute('data-focused');\n    });\n\n    it('Combobox.Label links to Combobox.Trigger when input is inside popup and trigger has an explicit id', async () => {\n      await render(\n        <Combobox.Root>\n          <Combobox.Label data-testid=\"label\">Search</Combobox.Label>\n          <Combobox.Trigger data-testid=\"trigger\" id=\"x-id\">\n            Open\n          </Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.Input />\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                  <Combobox.Item value=\"b\">b</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const label = screen.getByTestId<HTMLDivElement>('label');\n      const trigger = screen.getByTestId('trigger');\n\n      /* eslint-disable testing-library/no-wait-for-multiple-assertions */\n      await waitFor(() => {\n        expect(trigger).toHaveAttribute('id', 'x-id');\n        expect(trigger).toHaveAttribute('aria-labelledby', label.id);\n      });\n      /* eslint-enable testing-library/no-wait-for-multiple-assertions */\n    });\n\n    it('Combobox.Label focuses trigger without opening when input is inside popup', async () => {\n      const { user } = await render(\n        <Combobox.Root>\n          <Combobox.Label data-testid=\"label\">Search</Combobox.Label>\n          <Combobox.Trigger data-testid=\"trigger\">Open</Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.Input />\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                  <Combobox.Item value=\"b\">b</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      await user.click(screen.getByTestId('label'));\n\n      expect(screen.getByTestId('trigger')).toHaveFocus();\n      expect(screen.queryByRole('dialog')).toBe(null);\n    });\n\n    it('[data-touched]', async () => {\n      await render(\n        <Field.Root>\n          <Combobox.Root>\n            <Combobox.Input data-testid=\"input\" />\n            <Combobox.Trigger data-testid=\"trigger\" />\n            <Combobox.Portal>\n              <Combobox.Positioner>\n                <Combobox.Popup>\n                  <Combobox.List>\n                    <Combobox.Item value=\"\">Select</Combobox.Item>\n                    <Combobox.Item value=\"1\">Option 1</Combobox.Item>\n                  </Combobox.List>\n                </Combobox.Popup>\n              </Combobox.Positioner>\n            </Combobox.Portal>\n          </Combobox.Root>\n        </Field.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      const trigger = screen.getByTestId('trigger');\n\n      expect(input).not.toHaveAttribute('data-dirty');\n      expect(trigger).not.toHaveAttribute('data-dirty');\n\n      fireEvent.focus(input);\n      fireEvent.blur(input);\n\n      await flushMicrotasks();\n\n      expect(input).toHaveAttribute('data-touched', '');\n      expect(trigger).toHaveAttribute('data-touched', '');\n    });\n\n    it('[data-dirty]', async () => {\n      const { user } = await renderFakeTimers(\n        <Field.Root>\n          <Combobox.Root>\n            <Combobox.Input data-testid=\"input\" />\n            <Combobox.Trigger data-testid=\"trigger\" />\n            <Combobox.Portal>\n              <Combobox.Positioner>\n                <Combobox.Popup>\n                  <Combobox.List>\n                    <Combobox.Item value=\"\">Select</Combobox.Item>\n                    <Combobox.Item value=\"1\">Option 1</Combobox.Item>\n                  </Combobox.List>\n                </Combobox.Popup>\n              </Combobox.Positioner>\n            </Combobox.Portal>\n          </Combobox.Root>\n        </Field.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      const trigger = screen.getByTestId('trigger');\n\n      expect(input).not.toHaveAttribute('data-dirty');\n      expect(trigger).not.toHaveAttribute('data-dirty');\n\n      await user.click(input);\n      await flushMicrotasks();\n      clock.tick(200);\n\n      const option = screen.getByRole('option', { name: 'Option 1' });\n\n      // Arrow Down to focus the Option 1\n      await user.keyboard('{ArrowDown}');\n      await user.click(option);\n      await flushMicrotasks();\n\n      expect(input).toHaveAttribute('data-dirty', '');\n      expect(trigger).toHaveAttribute('data-dirty', '');\n    });\n\n    describe('[data-filled]', () => {\n      it('adds [data-filled] attribute when filled', async () => {\n        const { user } = await renderFakeTimers(\n          <Field.Root>\n            <Combobox.Root>\n              <Combobox.Input data-testid=\"input\" />\n              <Combobox.Trigger data-testid=\"trigger\" />\n              <Combobox.Portal>\n                <Combobox.Positioner>\n                  <Combobox.Popup>\n                    <Combobox.List>\n                      <Combobox.Item value=\"\">Select</Combobox.Item>\n                      <Combobox.Item value=\"1\">Option 1</Combobox.Item>\n                    </Combobox.List>\n                  </Combobox.Popup>\n                </Combobox.Positioner>\n              </Combobox.Portal>\n            </Combobox.Root>\n          </Field.Root>,\n        );\n\n        const input = screen.getByTestId('input');\n        const trigger = screen.getByTestId('trigger');\n\n        expect(input).not.toHaveAttribute('data-filled');\n        expect(trigger).not.toHaveAttribute('data-filled');\n\n        await user.click(input);\n        await flushMicrotasks();\n        clock.tick(200);\n\n        const option = screen.getByRole('option', { name: 'Option 1' });\n\n        // Arrow Down to focus the Option 1\n        await user.keyboard('{ArrowDown}');\n        await user.click(option);\n        await flushMicrotasks();\n\n        expect(input).toHaveAttribute('data-filled', '');\n        expect(trigger).toHaveAttribute('data-filled', '');\n\n        await user.click(input);\n\n        await flushMicrotasks();\n\n        const listbox = screen.getByRole('listbox');\n\n        expect(listbox).not.toHaveAttribute('data-filled');\n      });\n\n      it('adds [data-filled] attribute when already filled', async () => {\n        await render(\n          <Field.Root>\n            <Combobox.Root defaultValue=\"1\">\n              <Combobox.Input data-testid=\"input\" />\n              <Combobox.Trigger data-testid=\"trigger\" />\n              <Combobox.Portal>\n                <Combobox.Positioner>\n                  <Combobox.Popup>\n                    <Combobox.List>\n                      <Combobox.Item value=\"1\">Option 1</Combobox.Item>\n                    </Combobox.List>\n                  </Combobox.Popup>\n                </Combobox.Positioner>\n              </Combobox.Portal>\n            </Combobox.Root>\n          </Field.Root>,\n        );\n\n        const input = screen.getByTestId('input');\n        const trigger = screen.getByTestId('trigger');\n\n        expect(input).toHaveAttribute('data-filled');\n        expect(trigger).toHaveAttribute('data-filled');\n      });\n    });\n\n    it('[data-focused]', async () => {\n      await render(\n        <Field.Root>\n          <Combobox.Root>\n            <Combobox.Input data-testid=\"input\" />\n            <Combobox.Trigger data-testid=\"trigger\" />\n            <Combobox.Portal>\n              <Combobox.Positioner>\n                <Combobox.Popup>\n                  <Combobox.List>\n                    <Combobox.Item value=\"\">Select</Combobox.Item>\n                    <Combobox.Item value=\"1\">Option 1</Combobox.Item>\n                  </Combobox.List>\n                </Combobox.Popup>\n              </Combobox.Positioner>\n            </Combobox.Portal>\n          </Combobox.Root>\n        </Field.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      const trigger = screen.getByTestId('trigger');\n\n      expect(input).not.toHaveAttribute('data-focused');\n      expect(trigger).not.toHaveAttribute('data-focused');\n\n      fireEvent.focus(input);\n\n      expect(input).toHaveAttribute('data-focused', '');\n      expect(trigger).toHaveAttribute('data-focused', '');\n\n      fireEvent.blur(input);\n\n      expect(input).not.toHaveAttribute('data-focused');\n      expect(trigger).not.toHaveAttribute('data-focused');\n    });\n\n    it('does not mark as touched when focus moves into the popup', async () => {\n      const validateSpy = vi.fn(() => 'error');\n\n      await render(\n        <React.Fragment>\n          <Field.Root validationMode=\"onBlur\" validate={validateSpy}>\n            <Combobox.Root>\n              <Combobox.Trigger data-testid=\"trigger\" />\n              <Combobox.Portal>\n                <Combobox.Positioner>\n                  <Combobox.Popup>\n                    <Combobox.List>\n                      <Combobox.Item value=\"1\">Option 1</Combobox.Item>\n                    </Combobox.List>\n                  </Combobox.Popup>\n                </Combobox.Positioner>\n              </Combobox.Portal>\n            </Combobox.Root>\n          </Field.Root>\n          <button data-testid=\"outside\">Outside</button>\n        </React.Fragment>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n\n      fireEvent.focus(trigger);\n      fireEvent.click(trigger);\n\n      await flushMicrotasks();\n\n      const popup = screen.getByRole('dialog');\n\n      fireEvent.blur(trigger, { relatedTarget: popup });\n      fireEvent.focus(popup);\n\n      await flushMicrotasks();\n\n      expect(validateSpy.mock.calls.length).toBe(0);\n      expect(trigger).toHaveAttribute('data-focused', '');\n      expect(trigger).not.toHaveAttribute('data-touched');\n      expect(trigger).not.toHaveAttribute('aria-invalid');\n    });\n\n    it('validates when the popup is blurred', async () => {\n      const validateSpy = vi.fn(() => 'error');\n\n      await render(\n        <React.Fragment>\n          <Field.Root validationMode=\"onBlur\" validate={validateSpy}>\n            <Combobox.Root>\n              <Combobox.Trigger data-testid=\"trigger\" />\n              <Combobox.Portal>\n                <Combobox.Positioner>\n                  <Combobox.Popup>\n                    <Combobox.List>\n                      <Combobox.Item value=\"1\">Option 1</Combobox.Item>\n                    </Combobox.List>\n                  </Combobox.Popup>\n                </Combobox.Positioner>\n              </Combobox.Portal>\n            </Combobox.Root>\n          </Field.Root>\n          <button data-testid=\"outside\">Outside</button>\n        </React.Fragment>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      const outside = screen.getByTestId('outside');\n\n      fireEvent.focus(trigger);\n      fireEvent.click(trigger);\n\n      await flushMicrotasks();\n\n      const popup = screen.getByRole('dialog');\n\n      fireEvent.blur(trigger, { relatedTarget: popup });\n      fireEvent.focus(popup);\n\n      fireEvent.blur(popup, { relatedTarget: outside });\n      fireEvent.focus(outside);\n\n      await waitFor(() => {\n        expect(validateSpy.mock.calls.length).toBe(1);\n      });\n\n      expect(trigger).toHaveAttribute('data-touched', '');\n      expect(trigger).not.toHaveAttribute('data-focused');\n      expect(trigger).toHaveAttribute('aria-invalid', 'true');\n    });\n\n    it('[data-invalid]', async () => {\n      await render(\n        <Field.Root invalid>\n          <Combobox.Root>\n            <Combobox.Input data-testid=\"input\" />\n            <Combobox.Trigger data-testid=\"trigger\" />\n            <Combobox.Portal>\n              <Combobox.Positioner>\n                <Combobox.Popup>\n                  <Combobox.List>\n                    <Combobox.Item value=\"1\">Option 1</Combobox.Item>\n                  </Combobox.List>\n                </Combobox.Popup>\n              </Combobox.Positioner>\n            </Combobox.Portal>\n          </Combobox.Root>\n        </Field.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      const trigger = screen.getByTestId('trigger');\n\n      expect(input).toHaveAttribute('data-invalid', '');\n      expect(trigger).toHaveAttribute('data-invalid', '');\n    });\n\n    it('[data-valid]', async () => {\n      const { user } = await render(\n        <Field.Root validationMode=\"onBlur\">\n          <Combobox.Root>\n            <Combobox.Input data-testid=\"input\" required />\n            <Combobox.Trigger data-testid=\"trigger\" />\n            <Combobox.Portal>\n              <Combobox.Positioner>\n                <Combobox.Popup>\n                  <Combobox.List>\n                    <Combobox.Item value=\"1\">Option 1</Combobox.Item>\n                  </Combobox.List>\n                </Combobox.Popup>\n              </Combobox.Positioner>\n            </Combobox.Portal>\n          </Combobox.Root>\n        </Field.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n      const trigger = screen.getByTestId('trigger');\n\n      expect(input).not.toHaveAttribute('data-valid');\n      expect(input).not.toHaveAttribute('data-invalid');\n      expect(trigger).not.toHaveAttribute('data-valid');\n      expect(trigger).not.toHaveAttribute('data-invalid');\n\n      // Select an option to produce a valid value, then blur to commit\n      fireEvent.focus(input);\n      await user.click(input);\n      const option = await screen.findByRole('option', { name: 'Option 1' });\n\n      await user.click(option);\n      fireEvent.blur(input);\n\n      await waitFor(() => expect(input).toHaveAttribute('data-valid', ''));\n      expect(trigger).toHaveAttribute('data-valid', '');\n      expect(input).not.toHaveAttribute('data-invalid');\n      expect(trigger).not.toHaveAttribute('data-invalid');\n    });\n\n    it('prop: validate', async () => {\n      await render(\n        <Field.Root validationMode=\"onBlur\" validate={() => 'error'}>\n          <Combobox.Root>\n            <Combobox.Input data-testid=\"input\" />\n            <Combobox.Portal>\n              <Combobox.Positioner />\n            </Combobox.Portal>\n          </Combobox.Root>\n        </Field.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n\n      expect(input).not.toHaveAttribute('aria-invalid');\n\n      fireEvent.focus(input);\n      fireEvent.blur(input);\n\n      await flushMicrotasks();\n\n      expect(input).toHaveAttribute('aria-invalid', 'true');\n    });\n\n    it('passes raw value to validate when itemToStringValue is provided', async () => {\n      const items = [\n        { code: 'US', label: 'United States' },\n        { code: 'CA', label: 'Canada' },\n      ];\n      const validateSpy = vi.fn((value: unknown) => {\n        expect(value).toBe(items[0]);\n        return 'error';\n      });\n\n      await render(\n        <Field.Root validationMode=\"onBlur\" validate={validateSpy}>\n          <Combobox.Root\n            items={items}\n            defaultValue={items[0]}\n            itemToStringLabel={(item) => item.label}\n            itemToStringValue={(item) => item.code}\n          >\n            <Combobox.Input data-testid=\"input\" />\n            <Combobox.Portal>\n              <Combobox.Positioner />\n            </Combobox.Portal>\n          </Combobox.Root>\n        </Field.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n\n      fireEvent.focus(input);\n      fireEvent.blur(input);\n\n      await waitFor(() => {\n        expect(validateSpy.mock.calls.length).toBe(1);\n      });\n      expect(input).toHaveAttribute('aria-invalid', 'true');\n    });\n\n    it('prop: validationMode=onSubmit', async () => {\n      const { user } = await render(\n        <Form>\n          <Field.Root validate={(val) => (val === 'a' ? 'error' : null)}>\n            <Combobox.Root required>\n              <Combobox.Input data-testid=\"input\" />\n              <Combobox.Clear data-testid=\"clear\" />\n              <Combobox.Portal>\n                <Combobox.Positioner>\n                  <Combobox.Popup>\n                    <Combobox.List>\n                      <Combobox.Item value=\"a\">a</Combobox.Item>\n                      <Combobox.Item value=\"b\">b</Combobox.Item>\n                    </Combobox.List>\n                  </Combobox.Popup>\n                </Combobox.Positioner>\n              </Combobox.Portal>\n            </Combobox.Root>\n          </Field.Root>\n          <button type=\"submit\">submit</button>\n        </Form>,\n      );\n\n      const input = screen.getByTestId('input');\n      expect(input).not.toHaveAttribute('aria-invalid');\n\n      await user.click(screen.getByText('submit'));\n      expect(input).toHaveAttribute('aria-invalid', 'true');\n\n      await user.click(input);\n\n      await user.keyboard('{ArrowDown}');\n      await user.keyboard('{ArrowDown}');\n      await user.keyboard('{Enter}');\n\n      expect(input).not.toHaveAttribute('aria-invalid');\n\n      const clear = screen.getByTestId('clear');\n      await user.click(clear);\n\n      expect(document.activeElement).toBe(input);\n      await user.keyboard('{Tab}');\n\n      expect(input).toHaveAttribute('aria-invalid', 'true');\n    });\n\n    // flaky in real browser\n    it.skipIf(!isJSDOM)('prop: validationMode=onChange', async () => {\n      const { user } = await render(\n        <Field.Root\n          validationMode=\"onChange\"\n          validate={(value) => {\n            return value === '1' ? 'error' : null;\n          }}\n        >\n          <Combobox.Root>\n            <Combobox.Input data-testid=\"input\" />\n            <Combobox.Portal>\n              <Combobox.Positioner>\n                <Combobox.Popup>\n                  <Combobox.List>\n                    <Combobox.Item value=\"1\">Option 1</Combobox.Item>\n                  </Combobox.List>\n                </Combobox.Popup>\n              </Combobox.Positioner>\n            </Combobox.Portal>\n          </Combobox.Root>\n        </Field.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n\n      expect(input).not.toHaveAttribute('aria-invalid');\n\n      await user.click(input);\n\n      await flushMicrotasks();\n\n      // Arrow Down to focus the Option 1\n      await user.keyboard('{ArrowDown}');\n      await user.keyboard('{Enter}');\n\n      expect(input).toHaveAttribute('aria-invalid', 'true');\n    });\n\n    // flaky in real browser\n    it.skipIf(!isJSDOM)('prop: validationMode=onBlur', async () => {\n      const { user } = await render(\n        <Field.Root\n          validationMode=\"onBlur\"\n          validate={(value) => {\n            return value === '1' ? 'error' : null;\n          }}\n        >\n          <Combobox.Root>\n            <Combobox.Input data-testid=\"input\" />\n            <Combobox.Portal>\n              <Combobox.Positioner>\n                <Combobox.Popup>\n                  <Combobox.List>\n                    <Combobox.Item value=\"1\">Option 1</Combobox.Item>\n                  </Combobox.List>\n                </Combobox.Popup>\n              </Combobox.Positioner>\n            </Combobox.Portal>\n          </Combobox.Root>\n          <Field.Error data-testid=\"error\" />\n        </Field.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n\n      expect(input).not.toHaveAttribute('aria-invalid');\n\n      await user.click(input);\n\n      await flushMicrotasks();\n\n      // Arrow Down to focus the Option 1\n      await user.keyboard('{ArrowDown}');\n      await user.keyboard('{Enter}');\n\n      fireEvent.blur(input);\n\n      await flushMicrotasks();\n\n      await waitFor(() => {\n        expect(input).toHaveAttribute('aria-invalid', 'true');\n      });\n    });\n\n    it('Field.Label', async () => {\n      await render(\n        <Field.Root>\n          <Combobox.Root>\n            <Combobox.Input data-testid=\"input\" />\n            <Combobox.Portal>\n              <Combobox.Positioner />\n            </Combobox.Portal>\n          </Combobox.Root>\n          <Field.Label data-testid=\"label\" nativeLabel={false} render={<span />} />\n        </Field.Root>,\n      );\n\n      expect(screen.getByTestId('input')).toHaveAttribute(\n        'aria-labelledby',\n        screen.getByTestId('label').id,\n      );\n    });\n\n    it('Combobox.Label does not label Combobox.Input and warns when input is the form control', async () => {\n      const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});\n\n      await render(\n        <Combobox.Root>\n          <Combobox.Label data-testid=\"label\" />\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner />\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      await waitFor(() => {\n        expect(errorSpy.mock.calls.length).toBe(1);\n      });\n\n      expect(errorSpy.mock.calls[0][0]).toContain(\n        'Base UI: <Combobox.Label> labels <Combobox.Trigger> only.',\n      );\n      expect(screen.getByTestId('input')).not.toHaveAttribute('aria-labelledby');\n      errorSpy.mockRestore();\n    });\n\n    it('does not set fallback aria-labelledby when no label is rendered', async () => {\n      await render(\n        <Combobox.Root>\n          <Combobox.Input data-testid=\"input\" aria-label=\"Search\" />\n          <Combobox.Portal>\n            <Combobox.Positioner />\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      await waitFor(() => {\n        expect(screen.getByTestId('input')).not.toHaveAttribute('aria-labelledby');\n      });\n    });\n\n    it('updates Combobox.Label linkage when root id changes', async () => {\n      const { setProps } = await render(\n        <Combobox.Root id=\"first\">\n          <Combobox.Label data-testid=\"label\">Food</Combobox.Label>\n          <Combobox.Trigger data-testid=\"trigger\">Open</Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.Input />\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      await setProps({ id: 'second' });\n\n      /* eslint-disable testing-library/no-wait-for-multiple-assertions */\n      await waitFor(() => {\n        const label = screen.getByTestId('label');\n        const trigger = screen.getByTestId('trigger');\n        expect(trigger).toHaveAttribute('id', 'second');\n        expect(label.id).toBe('second-label');\n        expect(trigger).toHaveAttribute('aria-labelledby', label.id);\n      });\n      /* eslint-enable testing-library/no-wait-for-multiple-assertions */\n    });\n\n    it('Field.Description', async () => {\n      await render(\n        <Field.Root>\n          <Combobox.Root>\n            <Combobox.Input data-testid=\"input\" />\n            <Combobox.Portal>\n              <Combobox.Positioner />\n            </Combobox.Portal>\n          </Combobox.Root>\n          <Field.Description data-testid=\"description\" />\n        </Field.Root>,\n      );\n\n      expect(screen.getByTestId('input')).toHaveAttribute(\n        'aria-describedby',\n        screen.getByTestId('description').id,\n      );\n    });\n  });\n\n  describe('prop: isItemEqualToValue', () => {\n    it('matches object values using the provided comparator', async () => {\n      const users = [\n        { id: 1, name: 'Alice' },\n        { id: 2, name: 'Bob' },\n      ];\n\n      await render(\n        <Combobox.Root\n          items={users}\n          value={{ id: 2, name: 'Bob' }}\n          itemToStringLabel={(item) => item.name}\n          itemToStringValue={(item) => String(item.id)}\n          isItemEqualToValue={(item, value) => item.id === value.id}\n          defaultOpen\n        >\n          <Combobox.Input data-testid=\"input\" />\n          <span data-testid=\"value\">\n            <Combobox.Value />\n          </span>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item) => (\n                    <Combobox.Item key={item.id} value={item}>\n                      {item.name}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('Bob');\n      expect(screen.getByRole('option', { name: 'Bob' })).toHaveAttribute('aria-selected', 'true');\n    });\n\n    it('properly deselects object values using the provided comparator', async () => {\n      const users = [\n        { id: 1, name: 'Alice' },\n        { id: 2, name: 'Bob' },\n      ];\n\n      await render(\n        <Combobox.Root\n          items={users}\n          defaultValue={[{ id: 2, name: 'Bob' }]}\n          itemToStringLabel={(item) => item.name}\n          itemToStringValue={(item) => String(item.id)}\n          isItemEqualToValue={(item, value) => item.id === value.id}\n          defaultOpen\n          multiple\n        >\n          <Combobox.Input data-testid=\"input\" />\n          <span data-testid=\"value\">\n            <Combobox.Value />\n          </span>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item) => (\n                    <Combobox.Item key={item.id} value={item}>\n                      {item.name}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const option = screen.getByRole('option', { name: 'Bob' });\n\n      fireEvent.click(option);\n\n      await waitFor(() => {\n        expect(screen.getByRole('option', { name: 'Bob' })).toHaveAttribute(\n          'aria-selected',\n          'false',\n        );\n      });\n    });\n\n    it('passes item as the first comparator argument in multiple mode', async () => {\n      const users = [\n        { id: 1, name: 'Alice', source: 'item' },\n        { id: 2, name: 'Bob', source: 'item' },\n      ];\n\n      await render(\n        <Combobox.Root\n          items={users}\n          defaultValue={[{ id: 2, name: 'Bob', source: 'selected' }]}\n          itemToStringLabel={(item) => item.name}\n          itemToStringValue={(item) => String(item.id)}\n          isItemEqualToValue={(item, value) =>\n            item.id === value.id && item.source === 'item' && value.source === 'selected'\n          }\n          defaultOpen\n          multiple\n        >\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item) => (\n                    <Combobox.Item key={item.id} value={item}>\n                      {item.name}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const option = screen.getByRole('option', { name: 'Bob' });\n      expect(option).toHaveAttribute('aria-selected', 'true');\n\n      fireEvent.click(option);\n\n      await waitFor(() => {\n        expect(screen.getByRole('option', { name: 'Bob' })).toHaveAttribute(\n          'aria-selected',\n          'false',\n        );\n      });\n    });\n\n    it('does not call comparator with null when clearing the value', async () => {\n      const users = [\n        { id: 1, name: 'Alice' },\n        { id: 2, name: 'Bob' },\n      ];\n\n      const compare = vi.fn((item: any, value: any) => {\n        if (value == null) {\n          throw new Error('Compared against null');\n        }\n        return item.id === value.id;\n      });\n\n      const hiddenInputRef = React.createRef<HTMLInputElement>();\n\n      const { user } = await render(\n        <Combobox.Root\n          items={users}\n          defaultValue={users[0]}\n          itemToStringLabel={(item) => item.name}\n          itemToStringValue={(item) => String(item.id)}\n          isItemEqualToValue={compare}\n          inputRef={hiddenInputRef}\n        >\n          <Combobox.Trigger>\n            <Combobox.Value data-testid=\"value\" />\n          </Combobox.Trigger>\n          <Combobox.Clear data-testid=\"clear\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item) => (\n                    <Combobox.Item key={item.id} value={item}>\n                      {item.name}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const clear = await screen.findByTestId('clear');\n      await user.click(clear);\n\n      await waitFor(() => {\n        expect(hiddenInputRef.current?.value ?? '').toBe('');\n      });\n\n      expect(compare.mock.calls.length).toBeGreaterThan(0);\n      compare.mock.calls.forEach((call) => {\n        expect(call[1]).not.toBe(null);\n      });\n    });\n\n    it('does not call comparator with undefined when items load asynchronously after opening', async () => {\n      interface Country {\n        code: string;\n        label: string;\n      }\n\n      const loadedItems: Country[] = [\n        { code: 'ca', label: 'Canada' },\n        { code: 'us', label: 'United States' },\n      ];\n\n      const compare = vi.fn((item: Country, value: Country) => {\n        if (item == null || value == null) {\n          throw new Error('Compared against undefined');\n        }\n        return item.code === value.code;\n      });\n\n      const handleInputValueChange = vi.fn();\n      const handleValueChange = vi.fn();\n\n      const { user, setProps } = await render(\n        <Combobox.Root\n          items={undefined}\n          value={loadedItems[0]}\n          inputValue=\"\"\n          onInputValueChange={handleInputValueChange}\n          onValueChange={handleValueChange}\n          itemToStringLabel={(item: Country) => item.label}\n          isItemEqualToValue={compare}\n          filter={null}\n        >\n          <Combobox.Input />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: Country) => (\n                    <Combobox.Item key={item.code} value={item}>\n                      {item.label}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole('combobox');\n      await user.click(input);\n\n      await setProps({ items: loadedItems });\n\n      const canada = await screen.findByRole('option', { name: 'Canada' });\n      fireEvent.mouseMove(canada, { pointerType: 'mouse' });\n      await waitFor(() => expect(canada).toHaveAttribute('data-highlighted'));\n      await user.keyboard('{ArrowDown}');\n\n      await waitFor(() => {\n        expect(screen.getByRole('option', { name: 'United States' })).toHaveAttribute(\n          'data-highlighted',\n        );\n      });\n\n      expect(compare.mock.calls.length).toBeGreaterThan(0);\n      compare.mock.calls.forEach((call) => {\n        expect(call[0]).not.toBe(null);\n        expect(call[0]).not.toBe(undefined);\n        expect(call[1]).not.toBe(null);\n        expect(call[1]).not.toBe(undefined);\n      });\n    });\n\n    it('keeps showing items after selecting in controlled input-inside-popup async load flow', async () => {\n      interface Country {\n        code: string;\n        label: string;\n      }\n\n      const loadedItems: Country[] = [\n        { code: 'ca', label: 'Canada' },\n        { code: 'us', label: 'United States' },\n      ];\n\n      function AsyncControlledCombobox(props: { countries: Country[] | undefined }) {\n        const { countries } = props;\n        const [country, setCountry] = React.useState<Country | null>(null);\n        const [inputValue, setInputValue] = React.useState('');\n\n        return (\n          <Combobox.Root\n            items={countries}\n            filter={null}\n            value={country}\n            inputValue={inputValue}\n            onInputValueChange={setInputValue}\n            isItemEqualToValue={(item, selected) => item?.code === selected?.code}\n            onValueChange={(value) => {\n              if (country?.code === value?.code) {\n                setCountry(null);\n              } else {\n                setCountry(value);\n              }\n            }}\n          >\n            <Combobox.Trigger data-testid=\"trigger\">Open</Combobox.Trigger>\n            <Combobox.Portal>\n              <Combobox.Positioner>\n                <Combobox.Popup>\n                  <Combobox.Input />\n                  <Combobox.Empty data-testid=\"empty\">No countries found.</Combobox.Empty>\n                  <Combobox.List>\n                    {(item: Country) => (\n                      <Combobox.Item key={item.code} value={item}>\n                        {item.label}\n                      </Combobox.Item>\n                    )}\n                  </Combobox.List>\n                </Combobox.Popup>\n              </Combobox.Positioner>\n            </Combobox.Portal>\n          </Combobox.Root>\n        );\n      }\n\n      const { user, setProps } = await render(<AsyncControlledCombobox countries={undefined} />);\n\n      const trigger = screen.getByTestId('trigger');\n\n      await user.click(trigger);\n      await setProps({ countries: loadedItems });\n\n      const canada = await screen.findByRole('option', { name: 'Canada' });\n      fireEvent.mouseMove(canada, { pointerType: 'mouse' });\n      await waitFor(() => expect(canada).toHaveAttribute('data-highlighted'));\n\n      await user.click(canada);\n      await waitFor(() => expect(screen.queryByRole('listbox')).toBe(null));\n\n      await user.click(trigger);\n\n      await waitFor(() => {\n        expect(screen.getByRole('option', { name: 'Canada' })).not.toBe(null);\n        expect(screen.getByRole('option', { name: 'United States' })).not.toBe(null);\n      });\n    });\n  });\n\n  describe('prop: highlightItemOnHover', () => {\n    it('highlights an item on mouse move by default', async () => {\n      const { user } = await render(\n        <Combobox.Root items={['apple', 'banana', 'cherry']}>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: string) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n      await user.click(input);\n\n      const banana = screen.getByRole('option', { name: 'banana' });\n      fireEvent.mouseMove(banana, { pointerType: 'mouse' });\n\n      await waitFor(() => expect(banana).toHaveAttribute('data-highlighted'));\n      expect(input.getAttribute('aria-activedescendant')).toBe(banana.id);\n    });\n\n    it('does not highlight items from mouse movement when disabled', async () => {\n      const { user } = await render(\n        <Combobox.Root items={['apple', 'banana', 'cherry']} highlightItemOnHover={false}>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: string) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n      await user.click(input);\n\n      const banana = screen.getByRole('option', { name: 'banana' });\n      fireEvent.mouseMove(banana, { pointerType: 'mouse' });\n\n      await waitFor(() => expect(input).not.toHaveAttribute('aria-activedescendant'));\n      expect(banana).not.toHaveAttribute('data-highlighted');\n    });\n  });\n\n  describe('prop: loopFocus', () => {\n    it('loops focus from last to first item with ArrowDown by default', async () => {\n      const { user } = await render(\n        <Combobox.Root items={['apple', 'banana', 'cherry']}>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: string) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n      await act(async () => input.focus());\n\n      // ArrowUp opens and focuses last item\n      await user.keyboard('{ArrowUp}');\n\n      await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n\n      const options = screen.getAllByRole('option');\n\n      await waitFor(() => {\n        expect(input).toHaveAttribute('aria-activedescendant', options[2].id);\n      });\n\n      // Loop cycles through input\n      await user.keyboard('{ArrowDown}');\n\n      await waitFor(() => {\n        expect(input).not.toHaveAttribute('aria-activedescendant');\n      });\n\n      // Then to first item\n      await user.keyboard('{ArrowDown}');\n\n      await waitFor(() => {\n        expect(input).toHaveAttribute('aria-activedescendant', options[0].id);\n      });\n    });\n\n    it('loops focus from first to last item with ArrowUp by default', async () => {\n      const { user } = await render(\n        <Combobox.Root items={['apple', 'banana', 'cherry']}>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: string) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n      await act(async () => input.focus());\n\n      // ArrowDown opens and focuses first item\n      await user.keyboard('{ArrowDown}');\n\n      await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n\n      const options = screen.getAllByRole('option');\n\n      await waitFor(() => {\n        expect(input).toHaveAttribute('aria-activedescendant', options[0].id);\n      });\n\n      // Loop cycles through input\n      await user.keyboard('{ArrowUp}');\n\n      await waitFor(() => {\n        expect(input).not.toHaveAttribute('aria-activedescendant');\n      });\n\n      // Then to last item\n      await user.keyboard('{ArrowUp}');\n\n      await waitFor(() => {\n        expect(input).toHaveAttribute('aria-activedescendant', options[2].id);\n      });\n    });\n\n    it('does not loop focus from last to first with ArrowDown when loopFocus={false}', async () => {\n      const { user } = await render(\n        <Combobox.Root items={['apple', 'banana', 'cherry']} loopFocus={false}>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: string) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n      await act(async () => input.focus());\n\n      // ArrowUp opens and focuses last item\n      await user.keyboard('{ArrowUp}');\n\n      await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n\n      const options = screen.getAllByRole('option');\n\n      await waitFor(() => {\n        expect(input).toHaveAttribute('aria-activedescendant', options[2].id);\n      });\n\n      // Should stay at last item (no loop)\n      await user.keyboard('{ArrowDown}');\n\n      await waitFor(() => {\n        expect(input).toHaveAttribute('aria-activedescendant', options[2].id);\n      });\n    });\n\n    it('does not loop focus from first to last with ArrowUp when loopFocus={false}', async () => {\n      const { user } = await render(\n        <Combobox.Root items={['apple', 'banana', 'cherry']} loopFocus={false}>\n          <Combobox.Input data-testid=\"input\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: string) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('combobox');\n      await act(async () => input.focus());\n\n      // ArrowDown opens and focuses first item\n      await user.keyboard('{ArrowDown}');\n\n      await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n\n      const options = screen.getAllByRole('option');\n\n      await waitFor(() => {\n        expect(input).toHaveAttribute('aria-activedescendant', options[0].id);\n      });\n\n      // Should stay at first item (no loop)\n      await user.keyboard('{ArrowUp}');\n\n      await waitFor(() => {\n        expect(input).toHaveAttribute('aria-activedescendant', options[0].id);\n      });\n    });\n  });\n\n  describe('within Composite', () => {\n    it('should navigate between combobox and composite items', async () => {\n      const { user } = await render(\n        <CompositeRoot orientation=\"horizontal\">\n          <CompositeItem tag=\"button\">Item 1</CompositeItem>\n          <CompositeItem tag=\"button\">Item 2</CompositeItem>\n          <Combobox.Root>\n            <Combobox.Input render={(props) => <CompositeItem tag=\"input\" props={[props]} />} />\n          </Combobox.Root>\n        </CompositeRoot>,\n      );\n\n      const input = screen.getByRole('combobox');\n      await user.click(input);\n\n      await user.keyboard('{ArrowLeft}');\n      const button2 = screen.getByRole('button', { name: 'Item 2' });\n      expect(button2).toHaveFocus();\n\n      await user.keyboard('{ArrowRight}');\n      expect(input).toHaveFocus();\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/combobox/root/ComboboxRoot.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { AriaCombobox, type AriaComboboxState } from './AriaCombobox';\n\n/**\n * Groups all parts of the combobox.\n * Doesn't render its own HTML element.\n *\n * Documentation: [Base UI Combobox](https://base-ui.com/react/components/combobox)\n */\nexport function ComboboxRoot<Value, Multiple extends boolean | undefined = false>(\n  props: ComboboxRoot.Props<Value, Multiple>,\n): React.JSX.Element {\n  const {\n    multiple = false as Multiple,\n    defaultValue,\n    value,\n    onValueChange,\n    autoComplete,\n    ...other\n  } = props;\n\n  return (\n    <AriaCombobox\n      {...(other as any)}\n      selectionMode={multiple ? 'multiple' : 'single'}\n      selectedValue={value}\n      defaultSelectedValue={defaultValue}\n      onSelectedValueChange={onValueChange}\n      formAutoComplete={autoComplete}\n    />\n  );\n}\n\ntype ModeFromMultiple<Multiple extends boolean | undefined> = Multiple extends true\n  ? 'multiple'\n  : 'single';\n\ntype ComboboxValueType<Value, Multiple extends boolean | undefined> = Multiple extends true\n  ? Value[]\n  : Value;\n\nexport type ComboboxRootProps<Value, Multiple extends boolean | undefined = false> = Omit<\n  AriaCombobox.Props<Value, ModeFromMultiple<Multiple>>,\n  | 'fillInputOnItemPress'\n  | 'autoComplete'\n  | 'formAutoComplete'\n  | 'submitOnItemClick'\n  | 'autoHighlight'\n  | 'keepHighlight'\n  | 'highlightItemOnHover'\n  | 'itemToStringLabel'\n  | 'itemToStringValue'\n  | 'isItemEqualToValue'\n  // Different names\n  | 'selectionMode'\n  | 'defaultSelectedValue'\n  | 'selectedValue'\n  | 'onSelectedValueChange'\n  // Custom JSDoc\n  | 'actionsRef'\n  | 'onOpenChange'\n  | 'onInputValueChange'\n  | 'onItemHighlighted'\n> & {\n  /**\n   * Whether multiple items can be selected.\n   * @default false\n   */\n  multiple?: Multiple | undefined;\n  /**\n   * Provides a hint to the browser for autofill.\n   * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/autocomplete\n   */\n  autoComplete?: string | undefined;\n  /**\n   * Whether the first matching item is highlighted automatically while filtering.\n   * @default false\n   */\n  autoHighlight?: boolean | undefined;\n  /**\n   * Whether moving the pointer over items should highlight them.\n   * Disabling this prop allows CSS `:hover` to be differentiated from the `:focus` (`data-highlighted`) state.\n   * @default true\n   */\n  highlightItemOnHover?: boolean | undefined;\n  /**\n   * When the item values are objects (`<Combobox.Item value={object}>`), this function converts the object value to a string representation for display in the input.\n   * If the shape of the object is `{ value, label }`, the label will be used automatically without needing to specify this prop.\n   */\n  itemToStringLabel?: ((itemValue: Value) => string) | undefined;\n  /**\n   * When the item values are objects (`<Combobox.Item value={object}>`), this function converts the object value to a string representation for form submission.\n   * If the shape of the object is `{ value, label }`, the value will be used automatically without needing to specify this prop.\n   */\n  itemToStringValue?: ((itemValue: Value) => string) | undefined;\n  /**\n   * Custom comparison logic used to determine if a combobox item value matches the current selected value. Useful when item values are objects without matching referentially.\n   * Defaults to `Object.is` comparison.\n   */\n  isItemEqualToValue?: ((itemValue: Value, value: Value) => boolean) | undefined;\n  /**\n   * The uncontrolled selected value of the combobox when it's initially rendered.\n   *\n   * To render a controlled combobox, use the `value` prop instead.\n   */\n  defaultValue?: ComboboxValueType<Value, Multiple> | null | undefined;\n  /**\n   * A ref to imperative actions.\n   * - `unmount`: When specified, the combobox will not be unmounted when closed.\n   * Instead, the `unmount` function must be called to unmount the combobox manually.\n   * Useful when the combobox's animation is controlled by an external library.\n   */\n  actionsRef?: React.RefObject<ComboboxRoot.Actions | null> | undefined;\n  /**\n   * Event handler called when the popup is opened or closed.\n   */\n  onOpenChange?:\n    | ((open: boolean, eventDetails: ComboboxRoot.ChangeEventDetails) => void)\n    | undefined;\n  /**\n   * Event handler called when the input value changes.\n   */\n  onInputValueChange?:\n    | ((inputValue: string, eventDetails: ComboboxRoot.ChangeEventDetails) => void)\n    | undefined;\n  /**\n   * Callback fired when an item is highlighted or unhighlighted.\n   * Receives the highlighted item value (or `undefined` if no item is highlighted) and event details with a `reason` property describing why the highlight changed.\n   * The `reason` can be:\n   * - `'keyboard'`: the highlight changed due to keyboard navigation.\n   * - `'pointer'`: the highlight changed due to pointer hovering.\n   * - `'none'`: the highlight changed programmatically.\n   */\n  onItemHighlighted?:\n    | ((\n        highlightedValue: Value | undefined,\n        eventDetails: ComboboxRoot.HighlightEventDetails,\n      ) => void)\n    | undefined;\n  /**\n   * The selected value of the combobox. Use when controlled.\n   */\n  value?: ComboboxValueType<Value, Multiple> | null | undefined;\n  /**\n   * Event handler called when the selected value of the combobox changes.\n   */\n  onValueChange?:\n    | ((\n        value: ComboboxValueType<Value, Multiple> | (Multiple extends true ? never : null),\n        eventDetails: ComboboxRoot.ChangeEventDetails,\n      ) => void)\n    | undefined;\n};\n\nexport interface ComboboxRootState extends AriaComboboxState {}\n\nexport type ComboboxRootActions = AriaCombobox.Actions;\n\nexport type ComboboxRootChangeEventReason = AriaCombobox.ChangeEventReason;\nexport type ComboboxRootChangeEventDetails = AriaCombobox.ChangeEventDetails;\n\nexport type ComboboxRootHighlightEventReason = AriaCombobox.HighlightEventReason;\nexport type ComboboxRootHighlightEventDetails = AriaCombobox.HighlightEventDetails;\n\nexport namespace ComboboxRoot {\n  export type Props<Value, Multiple extends boolean | undefined = false> = ComboboxRootProps<\n    Value,\n    Multiple\n  >;\n  export type State = ComboboxRootState;\n  export type Actions = ComboboxRootActions;\n  export type ChangeEventReason = ComboboxRootChangeEventReason;\n  export type ChangeEventDetails = ComboboxRootChangeEventDetails;\n  export type HighlightEventReason = ComboboxRootHighlightEventReason;\n  export type HighlightEventDetails = ComboboxRootHighlightEventDetails;\n}\n"
  },
  {
    "path": "packages/react/src/combobox/root/ComboboxRootContext.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { ComboboxStore } from '../store';\nimport type { FloatingRootContext } from '../../floating-ui-react';\n\nexport interface ComboboxDerivedItemsContext {\n  query: string;\n  hasItems: boolean;\n  filteredItems: any[];\n  flatFilteredItems: any[];\n}\n\nexport const ComboboxRootContext = React.createContext<ComboboxStore | undefined>(undefined);\nexport const ComboboxFloatingContext = React.createContext<FloatingRootContext | undefined>(\n  undefined,\n);\nexport const ComboboxDerivedItemsContext = React.createContext<\n  ComboboxDerivedItemsContext | undefined\n>(undefined);\n// `inputValue` can't be placed in the store.\n// https://github.com/mui/base-ui/issues/2703\nexport const ComboboxInputValueContext =\n  React.createContext<React.ComponentProps<'input'>['value']>('');\n\nexport function useComboboxRootContext() {\n  const context = React.useContext(ComboboxRootContext) as ComboboxStore | undefined;\n  if (!context) {\n    throw new Error(\n      'Base UI: ComboboxRootContext is missing. Combobox parts must be placed within <Combobox.Root>.',\n    );\n  }\n  return context;\n}\n\nexport function useComboboxFloatingContext() {\n  const context = React.useContext(ComboboxFloatingContext);\n  if (!context) {\n    throw new Error(\n      'Base UI: ComboboxFloatingContext is missing. Combobox parts must be placed within <Combobox.Root>.',\n    );\n  }\n  return context;\n}\n\nexport function useComboboxDerivedItemsContext() {\n  const context = React.useContext(ComboboxDerivedItemsContext);\n  if (!context) {\n    throw new Error(\n      'Base UI: ComboboxItemsContext is missing. Combobox parts must be placed within <Combobox.Root>.',\n    );\n  }\n  return context;\n}\n\nexport function useComboboxInputValueContext() {\n  return React.useContext(ComboboxInputValueContext);\n}\n"
  },
  {
    "path": "packages/react/src/combobox/root/utils/constants.ts",
    "content": "export const NO_ACTIVE_VALUE = Symbol('none');\nexport const INITIAL_LAST_HIGHLIGHT: { value: any; index: number } = {\n  value: NO_ACTIVE_VALUE,\n  index: -1,\n} as const;\n"
  },
  {
    "path": "packages/react/src/combobox/root/utils/index.ts",
    "content": "import { stringifyAsLabel } from '../../../utils/resolveValueLabel';\nimport type { Filter } from './useFilter';\n\n/**\n * Enhanced filter using Intl.Collator for more robust string matching.\n * Uses the provided `itemToStringLabel` function if available, otherwise falls back to:\n * • When `item` is an object with a `value` property, that property is used.\n * • When `item` is a primitive (e.g. `string`), it is used directly.\n */\nexport function createCollatorItemFilter(\n  collatorFilter: Filter,\n  itemToStringLabel?: (item: any) => string,\n) {\n  return (item: any, query: string) => {\n    if (item == null) {\n      return false;\n    }\n    const itemString = stringifyAsLabel(item, itemToStringLabel);\n    return collatorFilter.contains(itemString, query);\n  };\n}\n\n/**\n * Enhanced filter for single selection mode using Intl.Collator that shows all items\n * when query is empty or matches the current selection, making it easier to browse options.\n */\nexport function createSingleSelectionCollatorFilter(\n  collatorFilter: Filter,\n  itemToStringLabel?: (item: any) => string,\n  selectedValue?: any,\n) {\n  return (item: any, query: string) => {\n    if (item == null) {\n      return false;\n    }\n    if (!query) {\n      return true;\n    }\n\n    const itemString = stringifyAsLabel(item, itemToStringLabel);\n    const selectedString =\n      selectedValue != null ? stringifyAsLabel(selectedValue, itemToStringLabel) : '';\n\n    // Handle case-insensitive matching consistently\n    if (\n      selectedString &&\n      collatorFilter.contains(selectedString, query) &&\n      selectedString.length === query.length\n    ) {\n      return true;\n    }\n\n    return collatorFilter.contains(itemString, query);\n  };\n}\n"
  },
  {
    "path": "packages/react/src/combobox/root/utils/useFilter.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { createCollatorItemFilter, createSingleSelectionCollatorFilter } from './index';\nimport { stringifyAsLabel } from '../../../utils/resolveValueLabel';\n\nexport interface UseFilterOptions extends Intl.CollatorOptions {\n  /**\n   * The locale to use for string comparison.\n   * Defaults to the user's runtime locale.\n   */\n  locale?: Intl.LocalesArgument | undefined;\n}\n\nexport interface Filter {\n  contains: <Item>(item: Item, query: string, itemToString?: (item: Item) => string) => boolean;\n  startsWith: <Item>(item: Item, query: string, itemToString?: (item: Item) => string) => boolean;\n  endsWith: <Item>(item: Item, query: string, itemToString?: (item: Item) => string) => boolean;\n}\n\nconst filterCache = new Map<string, Filter>();\n\nfunction stringifyLocale(locale?: Intl.LocalesArgument): string {\n  if (Array.isArray(locale)) {\n    return locale.map((value) => stringifyLocale(value)).join(',');\n  }\n  if (locale == null) {\n    return '';\n  }\n  return String(locale);\n}\n\nfunction getFilter(options: UseFilterOptions = {}): Filter {\n  const mergedOptions: Intl.CollatorOptions = {\n    usage: 'search',\n    sensitivity: 'base',\n    ignorePunctuation: true,\n    ...options,\n  };\n\n  const cacheKey = `${stringifyLocale(options.locale)}|${JSON.stringify(mergedOptions)}`;\n  const cachedFilter = filterCache.get(cacheKey);\n\n  if (cachedFilter) {\n    return cachedFilter;\n  }\n\n  const collator = new Intl.Collator(options.locale, mergedOptions);\n\n  const filter: Filter = {\n    contains<Item>(item: Item, query: string, itemToString?: (item: Item) => string) {\n      if (!query) {\n        return true;\n      }\n\n      const itemString = stringifyAsLabel(item, itemToString);\n\n      for (let i = 0; i <= itemString.length - query.length; i += 1) {\n        if (collator.compare(itemString.slice(i, i + query.length), query) === 0) {\n          return true;\n        }\n      }\n\n      return false;\n    },\n    startsWith<Item>(item: Item, query: string, itemToString?: (item: Item) => string) {\n      if (!query) {\n        return true;\n      }\n\n      const itemString = stringifyAsLabel(item, itemToString);\n\n      return collator.compare(itemString.slice(0, query.length), query) === 0;\n    },\n    endsWith<Item>(item: Item, query: string, itemToString?: (item: Item) => string) {\n      if (!query) {\n        return true;\n      }\n\n      const itemString = stringifyAsLabel(item, itemToString);\n      const queryLength = query.length;\n\n      return (\n        itemString.length >= queryLength &&\n        collator.compare(itemString.slice(itemString.length - queryLength), query) === 0\n      );\n    },\n  };\n\n  filterCache.set(cacheKey, filter);\n  return filter;\n}\n\n/**\n * Matches items against a query using `Intl.Collator` for robust string matching.\n */\nexport const useCoreFilter = getFilter;\n\nexport interface UseComboboxFilterOptions extends UseFilterOptions {\n  /**\n   * Whether the combobox is in multiple selection mode.\n   * @default false\n   */\n  multiple?: boolean | undefined;\n  /**\n   * The current value of the combobox.\n   */\n  value?: any;\n}\n\n/**\n * Matches items against a query using `Intl.Collator` for robust string matching.\n */\nexport function useComboboxFilter(options: UseComboboxFilterOptions = {}): Filter {\n  const { multiple = false, value, ...collatorOptions } = options;\n\n  const coreFilter = getFilter(collatorOptions);\n\n  const contains: Filter['contains'] = React.useCallback(\n    (item: any, query: string, itemToString?: (item: any) => string) => {\n      if (multiple) {\n        return createCollatorItemFilter(coreFilter, itemToString)(item, query);\n      }\n      return createSingleSelectionCollatorFilter(coreFilter, itemToString, value)(item, query);\n    },\n    [coreFilter, value, multiple],\n  );\n\n  return React.useMemo(\n    () => ({\n      contains,\n      startsWith: coreFilter.startsWith,\n      endsWith: coreFilter.endsWith,\n    }),\n    [contains, coreFilter],\n  );\n}\n"
  },
  {
    "path": "packages/react/src/combobox/root/utils/useFilteredItems.ts",
    "content": "import { useComboboxDerivedItemsContext } from '../ComboboxRootContext';\n\n/**\n * Returns the internally filtered items.\n */\nexport function useFilteredItems<T>() {\n  const items = useComboboxDerivedItemsContext();\n  return items.filteredItems as T[];\n}\n"
  },
  {
    "path": "packages/react/src/combobox/row/ComboboxRow.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { ComboboxRowContext } from './ComboboxRowContext';\n\n/**\n * Displays a single row of items in a grid list.\n * Enable `grid` on the root component to turn the listbox into a grid.\n * Renders a `<div>` element.\n */\nexport const ComboboxRow = React.forwardRef(function ComboboxRow(\n  componentProps: ComboboxRow.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, ...elementProps } = componentProps;\n\n  const element = useRenderElement('div', componentProps, {\n    ref: forwardedRef,\n    props: [{ role: 'row' }, elementProps],\n  });\n\n  return <ComboboxRowContext.Provider value>{element}</ComboboxRowContext.Provider>;\n});\n\nexport interface ComboboxRowState {}\n\nexport interface ComboboxRowProps extends BaseUIComponentProps<'div', ComboboxRowState> {}\n\nexport namespace ComboboxRow {\n  export type State = ComboboxRowState;\n  export type Props = ComboboxRowProps;\n}\n"
  },
  {
    "path": "packages/react/src/combobox/row/ComboboxRowContext.ts",
    "content": "'use client';\nimport * as React from 'react';\n\nexport const ComboboxRowContext = React.createContext(false);\n\nexport function useComboboxRowContext() {\n  return React.useContext(ComboboxRowContext);\n}\n"
  },
  {
    "path": "packages/react/src/combobox/status/ComboboxStatus.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { Combobox } from '@base-ui/react/combobox';\nimport { createRenderer, describeConformance } from '#test-utils';\nimport { screen, waitFor } from '@mui/internal-test-utils';\n\ndescribe('<Combobox.Status />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Combobox.Status />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(<Combobox.Root>{node}</Combobox.Root>);\n    },\n  }));\n\n  it('renders only when open', async () => {\n    const { user } = await render(\n      <Combobox.Root>\n        <Combobox.Input data-testid=\"input\" />\n        <Combobox.Portal>\n          <Combobox.Positioner>\n            <Combobox.Popup>\n              <Combobox.Status />\n              <Combobox.List>\n                <Combobox.Item value=\"a\">a</Combobox.Item>\n              </Combobox.List>\n            </Combobox.Popup>\n          </Combobox.Positioner>\n        </Combobox.Portal>\n      </Combobox.Root>,\n    );\n\n    expect(screen.queryByRole('status')).toBe(null);\n    await user.click(screen.getByTestId('input'));\n    await waitFor(() => expect(screen.getByRole('status')).not.toBe(null));\n  });\n});\n"
  },
  {
    "path": "packages/react/src/combobox/status/ComboboxStatus.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\n\n/**\n * Displays a status message whose content changes are announced politely to screen readers.\n * Useful for conveying the status of an asynchronously loaded list.\n * Renders a `<div>` element.\n */\nexport const ComboboxStatus = React.forwardRef(function ComboboxStatus(\n  componentProps: ComboboxStatus.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, ...elementProps } = componentProps;\n\n  return useRenderElement('div', componentProps, {\n    ref: forwardedRef,\n    props: [\n      {\n        role: 'status',\n        'aria-live': 'polite',\n        'aria-atomic': true,\n      },\n      elementProps,\n    ],\n  });\n});\n\nexport interface ComboboxStatusState {}\n\nexport interface ComboboxStatusProps extends BaseUIComponentProps<'div', ComboboxStatusState> {}\n\nexport namespace ComboboxStatus {\n  export type State = ComboboxStatusState;\n  export type Props = ComboboxStatusProps;\n}\n"
  },
  {
    "path": "packages/react/src/combobox/store.ts",
    "content": "import { Store, createSelector } from '@base-ui/utils/store';\nimport type { InteractionType } from '@base-ui/utils/useEnhancedClickHandler';\nimport type { TransitionStatus } from '../utils/useTransitionStatus';\nimport type { HTMLProps } from '../utils/types';\nimport type { Side } from '../utils/useAnchorPositioning';\nimport { compareItemEquality } from '../utils/itemEquality';\nimport { hasNullItemLabel } from '../utils/resolveValueLabel';\nimport type { AriaCombobox } from './root/AriaCombobox';\n\nexport type State = {\n  id: string | undefined;\n  labelId: string | undefined;\n\n  query: string;\n\n  filter: (item: any, query: string) => boolean;\n\n  items: readonly any[] | undefined;\n\n  selectedValue: any;\n\n  open: boolean;\n  mounted: boolean;\n  transitionStatus: TransitionStatus;\n  forceMounted: boolean;\n\n  inline: boolean;\n\n  activeIndex: number | null;\n  selectedIndex: number | null;\n\n  popupProps: HTMLProps;\n  inputProps: HTMLProps;\n  triggerProps: HTMLProps;\n\n  positionerElement: HTMLElement | null;\n  listElement: HTMLElement | null;\n  triggerElement: HTMLElement | null;\n  inputElement: HTMLInputElement | null;\n  inputGroupElement: HTMLDivElement | null;\n  popupSide: Side | null;\n\n  openMethod: InteractionType | null;\n\n  inputInsidePopup: boolean;\n\n  selectionMode: 'single' | 'multiple' | 'none';\n\n  listRef: React.RefObject<Array<HTMLElement | null>>;\n  labelsRef: React.RefObject<Array<string | null>>;\n  popupRef: React.RefObject<HTMLDivElement | null>;\n  emptyRef: React.RefObject<HTMLDivElement | null>;\n  inputRef: React.RefObject<HTMLInputElement | null>;\n  startDismissRef: React.RefObject<HTMLSpanElement | null>;\n  endDismissRef: React.RefObject<HTMLSpanElement | null>;\n  keyboardActiveRef: React.RefObject<boolean>;\n  chipsContainerRef: React.RefObject<HTMLDivElement | null>;\n  clearRef: React.RefObject<HTMLButtonElement | null>;\n  valuesRef: React.RefObject<Array<any>>;\n  allValuesRef: React.RefObject<Array<any>>;\n  selectionEventRef: React.RefObject<MouseEvent | PointerEvent | KeyboardEvent | null>;\n\n  setOpen: (open: boolean, eventDetails: AriaCombobox.ChangeEventDetails) => void;\n  setInputValue: (value: string, eventDetails: AriaCombobox.ChangeEventDetails) => void;\n  setSelectedValue: (value: any, eventDetails: AriaCombobox.ChangeEventDetails) => void;\n  setIndices: (indices: {\n    activeIndex?: number | null | undefined;\n    selectedIndex?: number | null | undefined;\n    type?: 'keyboard' | 'pointer' | 'none' | undefined;\n  }) => void;\n  onItemHighlighted: (item: any, eventDetails: AriaCombobox.HighlightEventDetails) => void;\n  forceMount: () => void;\n  handleSelection: (event: MouseEvent | PointerEvent | KeyboardEvent, passedValue?: any) => void;\n  getItemProps: (\n    props?: HTMLProps & { active?: boolean | undefined; selected?: boolean | undefined },\n  ) => Record<string, unknown>;\n  requestSubmit: () => void;\n\n  name: string | undefined;\n  disabled: boolean;\n  readOnly: boolean;\n  required: boolean;\n  grid: boolean;\n  isGrouped: boolean;\n  virtualized: boolean;\n  onOpenChangeComplete: (open: boolean) => void;\n  openOnInputClick: boolean;\n  itemToStringLabel?: ((item: any) => string) | undefined;\n  isItemEqualToValue: (itemValue: any, selectedValue: any) => boolean;\n  modal: boolean;\n  autoHighlight: false | 'always' | 'input-change';\n  submitOnItemClick: boolean;\n  hasInputValue: boolean;\n};\n\nexport type ComboboxStore = Store<State>;\n\nexport const selectors = {\n  id: createSelector((state: State) => state.id),\n  labelId: createSelector((state: State) => state.labelId),\n\n  query: createSelector((state: State) => state.query),\n\n  items: createSelector((state: State) => state.items),\n\n  selectedValue: createSelector((state: State) => state.selectedValue),\n  hasSelectionChips: createSelector((state: State) => {\n    const selectedValue = state.selectedValue;\n    return Array.isArray(selectedValue) && selectedValue.length > 0;\n  }),\n\n  hasSelectedValue: createSelector((state: State) => {\n    const { selectedValue, selectionMode } = state;\n    if (selectedValue == null) {\n      return false;\n    }\n    if (selectionMode === 'multiple' && Array.isArray(selectedValue)) {\n      return selectedValue.length > 0;\n    }\n    return true;\n  }),\n\n  hasNullItemLabel: createSelector((state: State, enabled: boolean) => {\n    return enabled ? hasNullItemLabel(state.items) : false;\n  }),\n\n  open: createSelector((state: State) => state.open),\n  mounted: createSelector((state: State) => state.mounted),\n  forceMounted: createSelector((state: State) => state.forceMounted),\n\n  inline: createSelector((state: State) => state.inline),\n\n  activeIndex: createSelector((state: State) => state.activeIndex),\n  selectedIndex: createSelector((state: State) => state.selectedIndex),\n  isActive: createSelector((state: State, index: number) => state.activeIndex === index),\n  isSelected: createSelector((state: State, itemValue: any) => {\n    const comparer = state.isItemEqualToValue;\n    const selectedValue = state.selectedValue;\n    if (Array.isArray(selectedValue)) {\n      return selectedValue.some((selectedItem) =>\n        compareItemEquality(itemValue, selectedItem, comparer),\n      );\n    }\n    return compareItemEquality(itemValue, selectedValue, comparer);\n  }),\n\n  transitionStatus: createSelector((state: State) => state.transitionStatus),\n\n  popupProps: createSelector((state: State) => state.popupProps),\n  inputProps: createSelector((state: State) => state.inputProps),\n  triggerProps: createSelector((state: State) => state.triggerProps),\n  getItemProps: createSelector((state: State) => state.getItemProps),\n\n  positionerElement: createSelector((state: State) => state.positionerElement),\n  listElement: createSelector((state: State) => state.listElement),\n  triggerElement: createSelector((state: State) => state.triggerElement),\n  inputElement: createSelector((state: State) => state.inputElement),\n  inputGroupElement: createSelector((state: State) => state.inputGroupElement),\n  popupSide: createSelector((state: State) => state.popupSide),\n\n  openMethod: createSelector((state: State) => state.openMethod),\n\n  inputInsidePopup: createSelector((state: State) => state.inputInsidePopup),\n\n  selectionMode: createSelector((state: State) => state.selectionMode),\n  listRef: createSelector((state: State) => state.listRef),\n  labelsRef: createSelector((state: State) => state.labelsRef),\n  popupRef: createSelector((state: State) => state.popupRef),\n  emptyRef: createSelector((state: State) => state.emptyRef),\n  inputRef: createSelector((state: State) => state.inputRef),\n  keyboardActiveRef: createSelector((state: State) => state.keyboardActiveRef),\n  chipsContainerRef: createSelector((state: State) => state.chipsContainerRef),\n  clearRef: createSelector((state: State) => state.clearRef),\n  valuesRef: createSelector((state: State) => state.valuesRef),\n  allValuesRef: createSelector((state: State) => state.allValuesRef),\n\n  name: createSelector((state: State) => state.name),\n  disabled: createSelector((state: State) => state.disabled),\n  readOnly: createSelector((state: State) => state.readOnly),\n  required: createSelector((state: State) => state.required),\n  grid: createSelector((state: State) => state.grid),\n  isGrouped: createSelector((state: State) => state.isGrouped),\n  virtualized: createSelector((state: State) => state.virtualized),\n  onOpenChangeComplete: createSelector((state: State) => state.onOpenChangeComplete),\n  openOnInputClick: createSelector((state: State) => state.openOnInputClick),\n  itemToStringLabel: createSelector((state: State) => state.itemToStringLabel),\n  isItemEqualToValue: createSelector((state: State) => state.isItemEqualToValue),\n  modal: createSelector((state: State) => state.modal),\n  autoHighlight: createSelector((state: State) => state.autoHighlight),\n  submitOnItemClick: createSelector((state: State) => state.submitOnItemClick),\n};\n"
  },
  {
    "path": "packages/react/src/combobox/trigger/ComboboxTrigger.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport { Combobox } from '@base-ui/react/combobox';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\nimport { act, fireEvent, screen, waitFor } from '@mui/internal-test-utils';\nimport { Field } from '@base-ui/react/field';\nimport { REASONS } from '../../utils/reasons';\n\ndescribe('<Combobox.Trigger />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Combobox.Trigger />, () => ({\n    refInstanceof: window.HTMLButtonElement,\n    button: true,\n    render(node) {\n      return render(<Combobox.Root>{node}</Combobox.Root>);\n    },\n  }));\n\n  it('sets tabIndex to -1 when not used as the main anchor', async () => {\n    const { user } = await render(\n      <Combobox.Root>\n        <Combobox.Input />\n        <Combobox.Trigger data-testid=\"trigger\" />\n      </Combobox.Root>,\n    );\n\n    const input = screen.getByRole('combobox');\n    const trigger = screen.getByTestId('trigger');\n\n    expect(trigger).toHaveAttribute('tabindex', '-1');\n\n    await user.click(input);\n    expect(trigger).toHaveAttribute('tabindex', '-1');\n\n    await user.click(trigger);\n    expect(trigger).toHaveAttribute('tabindex', '-1');\n  });\n\n  describe('prop: disabled', () => {\n    it('should render aria-disabled attribute when disabled', async () => {\n      await render(\n        <Combobox.Root>\n          <Combobox.Trigger disabled data-testid=\"trigger\">\n            Open\n          </Combobox.Trigger>\n        </Combobox.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      expect(trigger).toHaveAttribute('disabled');\n    });\n\n    it('should inherit disabled state from ComboboxRoot', async () => {\n      await render(\n        <Combobox.Root disabled>\n          <Combobox.Trigger data-testid=\"trigger\">Open</Combobox.Trigger>\n        </Combobox.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      expect(trigger).toHaveAttribute('disabled');\n    });\n\n    it('should inherit disabled state from Field.Root', async () => {\n      await render(\n        <Field.Root disabled>\n          <Combobox.Root>\n            <Combobox.Trigger data-testid=\"trigger\">Open</Combobox.Trigger>\n          </Combobox.Root>\n        </Field.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      expect(trigger).toHaveAttribute('disabled');\n    });\n\n    it('should not open popup when disabled', async () => {\n      const { user } = await render(\n        <Combobox.Root>\n          <Combobox.Trigger disabled data-testid=\"trigger\">\n            Open\n          </Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                  <Combobox.Item value=\"b\">b</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      await user.click(trigger);\n\n      expect(screen.queryByRole('listbox')).toBe(null);\n    });\n\n    it('should prioritize local disabled over root disabled', async () => {\n      await render(\n        <Combobox.Root disabled={false}>\n          <Combobox.Trigger disabled data-testid=\"trigger\">\n            Open\n          </Combobox.Trigger>\n        </Combobox.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      expect(trigger).toHaveAttribute('disabled');\n    });\n  });\n\n  describe('prop: readOnly', () => {\n    it('should not open popup when readOnly', async () => {\n      const { user } = await render(\n        <Combobox.Root readOnly>\n          <Combobox.Trigger data-testid=\"trigger\">Open</Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                  <Combobox.Item value=\"b\">b</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      await user.click(trigger);\n\n      expect(screen.queryByRole('listbox')).toBe(null);\n    });\n\n    it('should not toggle when readOnly=false (control)', async () => {\n      const { user } = await render(\n        <Combobox.Root readOnly={false}>\n          <Combobox.Trigger data-testid=\"trigger\">Open</Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      await user.click(trigger);\n      expect(await screen.findByRole('listbox')).not.toBe(null);\n    });\n  });\n\n  describe('interaction behavior', () => {\n    it('should toggle popup when enabled', async () => {\n      const { user } = await render(\n        <Combobox.Root>\n          <Combobox.Input />\n          <Combobox.Trigger data-testid=\"trigger\">Open</Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                  <Combobox.Item value=\"b\">b</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n\n      await user.click(trigger);\n      expect(await screen.findByRole('listbox')).not.toBe(null);\n\n      await user.click(trigger);\n      await waitFor(() => {\n        expect(screen.queryByRole('listbox')).toBe(null);\n      });\n    });\n\n    it('should call onOpenChange when toggling', async () => {\n      const handleOpenChange = vi.fn();\n      const { user } = await render(\n        <Combobox.Root onOpenChange={handleOpenChange}>\n          <Combobox.Trigger data-testid=\"trigger\">Open</Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                  <Combobox.Item value=\"b\">b</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      await user.click(trigger);\n\n      await waitFor(() => {\n        expect(handleOpenChange.mock.calls.length).toBe(1);\n      });\n      expect(handleOpenChange.mock.calls[0][0]).toBe(true);\n    });\n\n    it('opens popup when pressing ArrowDown or ArrowUp', async () => {\n      const { user } = await render(\n        <Combobox.Root>\n          <Combobox.Trigger data-testid=\"trigger\" />\n          <Combobox.Input />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List />\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      await act(async () => {\n        trigger.focus();\n      });\n\n      await user.keyboard('{ArrowDown}');\n      expect(screen.getByRole('listbox')).not.toBe(null);\n      expect(screen.getByRole('combobox')).toHaveFocus();\n\n      await user.keyboard('{Escape}');\n      expect(screen.queryByRole('listbox')).toBe(null);\n\n      await act(async () => {\n        trigger.focus();\n      });\n\n      await user.keyboard('{ArrowUp}');\n      expect(screen.queryByRole('listbox')).not.toBe(null);\n      expect(screen.getByRole('combobox')).toHaveFocus();\n    });\n\n    it('does not open on ArrowDown/ArrowUp when reference is a textarea', async () => {\n      const { user } = await render(\n        <Combobox.Root>\n          <Combobox.Trigger>Open</Combobox.Trigger>\n          <textarea aria-label=\"notes\" />\n        </Combobox.Root>,\n      );\n\n      const trigger = screen.getByRole('combobox');\n      await user.click(trigger);\n      await user.keyboard('{ArrowDown}');\n      expect(screen.queryByRole('listbox')).toBe(null);\n      await user.keyboard('{ArrowUp}');\n      expect(screen.queryByRole('listbox')).toBe(null);\n    });\n\n    it('fires with reason trigger-press when Trigger is clicked', async () => {\n      const onOpenChange = vi.fn();\n      const { user } = await render(\n        <Combobox.Root onOpenChange={onOpenChange}>\n          <Combobox.Trigger data-testid=\"trigger\">Open</Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"alpha\">Alpha</Combobox.Item>\n                  <Combobox.Item value=\"beta\">Beta</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      await user.click(trigger);\n\n      await waitFor(() => {\n        expect(onOpenChange.mock.calls.length).toBe(1);\n      });\n      expect(onOpenChange.mock.lastCall?.[0]).toBe(true);\n      expect(onOpenChange.mock.lastCall?.[1].reason).toBe(REASONS.triggerPress);\n    });\n  });\n\n  describe('drag selection', () => {\n    it('commits selection when the input is outside the popup', async () => {\n      const handleValueChange = vi.fn();\n\n      await render(\n        <Combobox.Root onValueChange={handleValueChange}>\n          <Combobox.Input />\n          <Combobox.Trigger data-testid=\"trigger\">Open</Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"alpha\">Alpha</Combobox.Item>\n                  <Combobox.Item value=\"beta\">Beta</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      fireEvent.pointerDown(trigger, { pointerType: 'mouse', button: 0 });\n      fireEvent.mouseDown(trigger, { button: 0 });\n\n      await screen.findByRole('listbox');\n      const option = await screen.findByRole('option', { name: 'Beta' });\n\n      fireEvent.mouseMove(option, { pointerType: 'mouse' });\n      await waitFor(() => expect(option).toHaveAttribute('data-highlighted'));\n\n      fireEvent.mouseUp(option, { button: 0 });\n\n      await waitFor(() => {\n        expect(handleValueChange.mock.calls.length).toBe(1);\n      });\n      expect(handleValueChange.mock.calls[0][0]).toBe('beta');\n    });\n\n    it('commits selection when the input is inside the popup and the pointer is released over an item', async () => {\n      const handleValueChange = vi.fn();\n\n      await render(\n        <Combobox.Root onValueChange={handleValueChange}>\n          <Combobox.Trigger data-testid=\"trigger\">Open</Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.Input />\n                <Combobox.List>\n                  <Combobox.Item value=\"alpha\">Alpha</Combobox.Item>\n                  <Combobox.Item value=\"beta\">Beta</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      fireEvent.pointerDown(trigger, { pointerType: 'mouse', button: 0 });\n      fireEvent.mouseDown(trigger, { button: 0 });\n\n      await screen.findByRole('listbox');\n      const option = await screen.findByRole('option', { name: 'Beta' });\n\n      fireEvent.mouseMove(option, { pointerType: 'mouse' });\n      await waitFor(() => expect(option).toHaveAttribute('data-highlighted'));\n\n      fireEvent.mouseUp(option, { button: 0 });\n\n      await waitFor(() => {\n        expect(handleValueChange.mock.calls.length).toBe(1);\n      });\n      expect(handleValueChange.mock.calls[0][0]).toBe('beta');\n    });\n\n    it('does not commit selection if the pointer never hovers the item', async () => {\n      const handleValueChange = vi.fn();\n\n      await render(\n        <Combobox.Root onValueChange={handleValueChange}>\n          <Combobox.Trigger data-testid=\"trigger\">Open</Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.Input />\n                <Combobox.List>\n                  <Combobox.Item value=\"alpha\">Alpha</Combobox.Item>\n                  <Combobox.Item value=\"beta\">Beta</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      fireEvent.pointerDown(trigger, { pointerType: 'mouse', button: 0 });\n      fireEvent.mouseDown(trigger, { button: 0 });\n\n      await screen.findByRole('listbox');\n      const option = await screen.findByRole('option', { name: 'Beta' });\n\n      fireEvent.mouseUp(option, { button: 0 });\n\n      await waitFor(() => {\n        expect(handleValueChange.mock.calls.length).toBe(0);\n      });\n    });\n  });\n\n  describe('cancel-open', () => {\n    it('closes the popup when mouseup occurs outside the trigger bounds', async () => {\n      await render(\n        <Combobox.Root>\n          <Combobox.Trigger data-testid=\"trigger\">Open</Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"alpha\">Alpha</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      fireEvent.pointerDown(trigger, { pointerType: 'mouse', button: 0 });\n      fireEvent.mouseDown(trigger, { button: 0 });\n\n      await screen.findByRole('listbox');\n\n      fireEvent.mouseUp(document.body, { button: 0, clientX: 999, clientY: 999 });\n\n      await waitFor(() => {\n        expect(screen.queryByRole('listbox')).toBe(null);\n      });\n    });\n\n    it('keeps the popup open when mouseup remains near the trigger bounds', async () => {\n      await render(\n        <Combobox.Root>\n          <Combobox.Trigger data-testid=\"trigger\">Open</Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"alpha\">Alpha</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      trigger.getBoundingClientRect = () =>\n        ({\n          left: 0,\n          top: 0,\n          right: 100,\n          bottom: 40,\n          width: 100,\n          height: 40,\n          x: 0,\n          y: 0,\n          toJSON() {\n            return {};\n          },\n        }) as DOMRect;\n\n      fireEvent.pointerDown(trigger, { pointerType: 'mouse', button: 0 });\n      fireEvent.mouseDown(trigger, { button: 0 });\n\n      const listbox = await screen.findByRole('listbox');\n\n      fireEvent.mouseUp(document.body, { button: 0, clientX: 1, clientY: 1 });\n\n      expect(listbox.isConnected).toBe(true);\n    });\n  });\n\n  describe('aria attributes', () => {\n    it('sets aria-required attribute when required (input inside popup)', async () => {\n      await render(\n        <Combobox.Root required>\n          <Combobox.Trigger data-testid=\"trigger\" />\n        </Combobox.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      expect(trigger).toHaveAttribute('aria-required', 'true');\n    });\n\n    it('does not set aria-required attribute when the input is outside the popup', async () => {\n      await render(\n        <Combobox.Root required>\n          <Combobox.Input />\n          <Combobox.Trigger data-testid=\"trigger\" />\n        </Combobox.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      expect(trigger).not.toHaveAttribute('aria-required');\n    });\n\n    it('sets all aria attributes on the input when closed', async () => {\n      await render(\n        <Combobox.Root>\n          <Combobox.Trigger data-testid=\"trigger\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List />\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n\n      expect(trigger).toHaveAttribute('tabindex', '0');\n      expect(trigger).toHaveAttribute('aria-expanded', 'false');\n      expect(trigger).toHaveAttribute('aria-haspopup', 'dialog');\n      expect(trigger).not.toHaveAttribute('aria-controls');\n    });\n\n    it('sets all aria attributes on the input when open', async () => {\n      const { user } = await render(\n        <Combobox.Root>\n          <Combobox.Trigger data-testid=\"trigger\" />\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List />\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      await user.click(trigger);\n\n      const listbox = await screen.findByRole('listbox');\n\n      expect(trigger).toHaveAttribute('tabindex', '0');\n      expect(trigger).toHaveAttribute('aria-expanded', 'true');\n      expect(trigger).toHaveAttribute('aria-haspopup', 'dialog');\n      expect(trigger).toHaveAttribute('aria-controls', listbox.id);\n    });\n  });\n\n  describe('typeahead', () => {\n    it('selects item when typing on focused trigger (input inside popup)', async () => {\n      const { user } = await render(\n        <Combobox.Root items={['apple', 'banana', 'cherry']}>\n          <Combobox.Trigger data-testid=\"trigger\">\n            <Combobox.Value data-testid=\"value\" />\n          </Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: string) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n\n      expect(trigger).not.toHaveTextContent('apple');\n\n      await act(async () => {\n        trigger.focus();\n      });\n      await user.keyboard('a');\n\n      expect(trigger).toHaveTextContent('apple');\n      expect(screen.queryByRole('listbox')).toBe(null);\n    });\n  });\n\n  describe('data state attributes', () => {\n    it.skipIf(isJSDOM)('sets data-popup-side to the current popup side', async () => {\n      const { user } = await render(\n        <Combobox.Root items={['apple']}>\n          <Combobox.Trigger data-testid=\"trigger\">Trigger</Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner side=\"right\">\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: string) => (\n                    <Combobox.Item key={item} value={item}>\n                      {item}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      expect(trigger).not.toHaveAttribute('data-popup-side');\n\n      await user.click(trigger);\n\n      await waitFor(() => expect(screen.queryByRole('listbox')).not.toBe(null));\n      expect(trigger).toHaveAttribute('data-popup-side', 'right');\n\n      await user.click(document.body);\n\n      await waitFor(() => expect(screen.queryByRole('listbox')).toBe(null));\n      expect(trigger).not.toHaveAttribute('data-popup-side');\n    });\n\n    it('toggles data-list-empty when the filtered list is empty', async () => {\n      const { user } = await render(\n        <Combobox.Root items={[]}>\n          <Combobox.Trigger data-testid=\"trigger\">Trigger</Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List />\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n\n      await user.click(trigger);\n\n      await waitFor(() => expect(screen.getByRole('listbox')).not.toBe(null));\n      expect(trigger).toHaveAttribute('data-list-empty');\n    });\n\n    it('has data-placeholder when no value is selected', async () => {\n      await render(\n        <Combobox.Root>\n          <Combobox.Trigger data-testid=\"trigger\">\n            <Combobox.Value placeholder=\"Select\" />\n          </Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      expect(trigger).toHaveAttribute('data-placeholder');\n    });\n\n    it('does not have data-placeholder when value is selected', async () => {\n      await render(\n        <Combobox.Root defaultValue=\"a\">\n          <Combobox.Trigger data-testid=\"trigger\">\n            <Combobox.Value placeholder=\"Select\" />\n          </Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      expect(trigger).not.toHaveAttribute('data-placeholder');\n    });\n\n    it('has data-placeholder when multiple mode has empty array', async () => {\n      await render(\n        <Combobox.Root multiple defaultValue={[]}>\n          <Combobox.Trigger data-testid=\"trigger\">\n            <Combobox.Value placeholder=\"Select\" />\n          </Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      expect(trigger).toHaveAttribute('data-placeholder');\n    });\n\n    it('does not have data-placeholder when multiple mode has a default value', async () => {\n      await render(\n        <Combobox.Root multiple defaultValue={['a']}>\n          <Combobox.Trigger data-testid=\"trigger\">\n            <Combobox.Value placeholder=\"Select\" />\n          </Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      expect(trigger).not.toHaveAttribute('data-placeholder');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/combobox/trigger/ComboboxTrigger.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStore } from '@base-ui/utils/store';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useTimeout } from '@base-ui/utils/useTimeout';\nimport { ownerDocument } from '@base-ui/utils/owner';\nimport { BaseUIComponentProps, NativeButtonProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useButton } from '../../use-button';\nimport {\n  useComboboxFloatingContext,\n  useComboboxDerivedItemsContext,\n  useComboboxInputValueContext,\n  useComboboxRootContext,\n} from '../root/ComboboxRootContext';\nimport { triggerStateAttributesMapping } from '../utils/stateAttributesMapping';\nimport { selectors } from '../store';\nimport { useFieldRootContext } from '../../field/root/FieldRootContext';\nimport { useLabelableContext } from '../../labelable-provider/LabelableContext';\nimport { stopEvent, contains, getTarget } from '../../floating-ui-react/utils';\nimport { getPseudoElementBounds } from '../../utils/getPseudoElementBounds';\nimport type { FieldRootState } from '../../field/root/FieldRoot';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport { useClick, useTypeahead } from '../../floating-ui-react';\nimport type { Side } from '../../utils/useAnchorPositioning';\nimport { useLabelableId } from '../../labelable-provider/useLabelableId';\nimport { resolveAriaLabelledBy } from '../../utils/resolveAriaLabelledBy';\n\nconst BOUNDARY_OFFSET = 2;\n\n/**\n * A button that opens the popup.\n * Renders a `<button>` element.\n */\nexport const ComboboxTrigger = React.forwardRef(function ComboboxTrigger(\n  componentProps: ComboboxTrigger.Props,\n  forwardedRef: React.ForwardedRef<HTMLButtonElement>,\n) {\n  const {\n    render,\n    className,\n    nativeButton = true,\n    disabled: disabledProp = false,\n    id: idProp,\n    ...elementProps\n  } = componentProps;\n\n  const {\n    state: fieldState,\n    disabled: fieldDisabled,\n    setTouched,\n    setFocused,\n    validationMode,\n    validation,\n  } = useFieldRootContext();\n  const { labelId: fieldLabelId } = useLabelableContext();\n  const store = useComboboxRootContext();\n  const { filteredItems } = useComboboxDerivedItemsContext();\n\n  const selectionMode = useStore(store, selectors.selectionMode);\n  const comboboxDisabled = useStore(store, selectors.disabled);\n  const readOnly = useStore(store, selectors.readOnly);\n  const required = useStore(store, selectors.required);\n  const mounted = useStore(store, selectors.mounted);\n  const popupSideValue = useStore(store, selectors.popupSide);\n  const positionerElement = useStore(store, selectors.positionerElement);\n  const listElement = useStore(store, selectors.listElement);\n  const triggerProps = useStore(store, selectors.triggerProps);\n  const triggerElement = useStore(store, selectors.triggerElement);\n  const inputInsidePopup = useStore(store, selectors.inputInsidePopup);\n  const rootId = useStore(store, selectors.id);\n  const comboboxLabelId = useStore(store, selectors.labelId);\n  const open = useStore(store, selectors.open);\n  const selectedValue = useStore(store, selectors.selectedValue);\n  const activeIndex = useStore(store, selectors.activeIndex);\n  const selectedIndex = useStore(store, selectors.selectedIndex);\n  const hasSelectedValue = useStore(store, selectors.hasSelectedValue);\n\n  const floatingRootContext = useComboboxFloatingContext();\n  const inputValue = useComboboxInputValueContext();\n\n  const focusTimeout = useTimeout();\n\n  const disabled = fieldDisabled || comboboxDisabled || disabledProp;\n  const listEmpty = filteredItems.length === 0;\n  const popupSide = mounted && positionerElement ? popupSideValue : null;\n\n  useLabelableId({ id: inputInsidePopup ? idProp : undefined });\n  const id = inputInsidePopup ? (idProp ?? rootId) : idProp;\n  const ariaLabelledBy = resolveAriaLabelledBy(fieldLabelId, comboboxLabelId);\n\n  const currentPointerTypeRef = React.useRef<PointerEvent['pointerType']>('');\n\n  function trackPointerType(event: React.PointerEvent) {\n    currentPointerTypeRef.current = event.pointerType;\n  }\n\n  const domReference = floatingRootContext.useState('domReferenceElement');\n\n  // Update the floating root context to use the trigger element when it differs from the current reference.\n  // This ensures useClick and useTypeahead attach handlers to the correct element.\n  React.useEffect(() => {\n    if (!inputInsidePopup) {\n      return;\n    }\n    if (triggerElement && triggerElement !== domReference) {\n      floatingRootContext.set('domReferenceElement', triggerElement);\n    }\n  }, [triggerElement, domReference, floatingRootContext, inputInsidePopup]);\n\n  const { reference: triggerTypeaheadProps } = useTypeahead(floatingRootContext, {\n    enabled: !open && !readOnly && !comboboxDisabled && selectionMode === 'single',\n    listRef: store.state.labelsRef,\n    activeIndex,\n    selectedIndex,\n    onMatch(index) {\n      const nextSelectedValue = store.state.valuesRef.current[index];\n      if (nextSelectedValue !== undefined) {\n        store.state.setSelectedValue(nextSelectedValue, createChangeEventDetails('none'));\n      }\n    },\n  });\n\n  const { reference: triggerClickProps } = useClick(floatingRootContext, {\n    enabled: !readOnly && !comboboxDisabled,\n    event: 'mousedown',\n  });\n\n  const { buttonRef, getButtonProps } = useButton({\n    native: nativeButton,\n    disabled,\n  });\n\n  const state: ComboboxTriggerState = {\n    ...fieldState,\n    open,\n    disabled,\n    popupSide,\n    listEmpty,\n    placeholder: !hasSelectedValue,\n  };\n\n  const setTriggerElement = useStableCallback((element) => {\n    store.set('triggerElement', element);\n  });\n\n  const element = useRenderElement('button', componentProps, {\n    ref: [forwardedRef, buttonRef, setTriggerElement],\n    state,\n    props: [\n      triggerProps,\n      triggerClickProps,\n      triggerTypeaheadProps,\n      {\n        id,\n        tabIndex: inputInsidePopup ? 0 : -1,\n        role: inputInsidePopup ? 'combobox' : undefined,\n        'aria-expanded': open ? 'true' : 'false',\n        'aria-haspopup': inputInsidePopup ? 'dialog' : 'listbox',\n        'aria-controls': open ? listElement?.id : undefined,\n        'aria-required': inputInsidePopup ? required || undefined : undefined,\n        'aria-labelledby': ariaLabelledBy,\n        onPointerDown: trackPointerType,\n        onPointerEnter: trackPointerType,\n        onFocus() {\n          setFocused(true);\n\n          if (disabled || readOnly) {\n            return;\n          }\n\n          focusTimeout.start(0, store.state.forceMount);\n        },\n        onBlur(event) {\n          // If focus is moving into the popup, don't count it as a blur.\n          if (contains(positionerElement, event.relatedTarget)) {\n            return;\n          }\n\n          setTouched(true);\n          setFocused(false);\n\n          if (validationMode === 'onBlur') {\n            const valueToValidate = selectionMode === 'none' ? inputValue : selectedValue;\n            validation.commit(valueToValidate);\n          }\n        },\n        onMouseDown(event) {\n          if (disabled || readOnly) {\n            return;\n          }\n\n          if (!inputInsidePopup) {\n            floatingRootContext.set('domReferenceElement', event.currentTarget);\n          }\n\n          // Ensure items are registered for initial selection highlight.\n          store.state.forceMount();\n\n          if (currentPointerTypeRef.current !== 'touch') {\n            store.state.inputRef.current?.focus();\n\n            if (!inputInsidePopup) {\n              event.preventDefault();\n            }\n          }\n\n          if (open) {\n            return;\n          }\n\n          const doc = ownerDocument(event.currentTarget);\n\n          function handleMouseUp(mouseEvent: MouseEvent) {\n            if (!triggerElement) {\n              return;\n            }\n\n            const mouseUpTarget = getTarget(mouseEvent) as Element | null;\n            const positioner = store.state.positionerElement;\n            const list = store.state.listElement;\n\n            if (\n              contains(triggerElement, mouseUpTarget) ||\n              contains(positioner, mouseUpTarget) ||\n              contains(list, mouseUpTarget) ||\n              mouseUpTarget === triggerElement\n            ) {\n              return;\n            }\n\n            const bounds = getPseudoElementBounds(triggerElement);\n\n            const withinHorizontal =\n              mouseEvent.clientX >= bounds.left - BOUNDARY_OFFSET &&\n              mouseEvent.clientX <= bounds.right + BOUNDARY_OFFSET;\n            const withinVertical =\n              mouseEvent.clientY >= bounds.top - BOUNDARY_OFFSET &&\n              mouseEvent.clientY <= bounds.bottom + BOUNDARY_OFFSET;\n\n            if (withinHorizontal && withinVertical) {\n              return;\n            }\n\n            store.state.setOpen(false, createChangeEventDetails('cancel-open', mouseEvent));\n          }\n\n          if (inputInsidePopup) {\n            doc.addEventListener('mouseup', handleMouseUp, { once: true });\n          }\n        },\n        onKeyDown(event) {\n          if (disabled || readOnly) {\n            return;\n          }\n\n          if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {\n            stopEvent(event);\n            store.state.setOpen(\n              true,\n              createChangeEventDetails(REASONS.listNavigation, event.nativeEvent),\n            );\n            store.state.inputRef.current?.focus();\n          }\n        },\n      },\n      validation ? validation.getValidationProps(elementProps) : elementProps,\n      getButtonProps,\n    ],\n    stateAttributesMapping: triggerStateAttributesMapping,\n  });\n\n  return element;\n});\n\nexport interface ComboboxTriggerState extends FieldRootState {\n  /**\n   * Whether the popup is open.\n   */\n  open: boolean;\n  /**\n   * Whether the component should ignore user interaction.\n   */\n  disabled: boolean;\n  /**\n   * Indicates which side the corresponding popup is positioned relative to its anchor.\n   */\n  popupSide: Side | null;\n  /**\n   * Present when the corresponding items list is empty.\n   */\n  listEmpty: boolean;\n  /**\n   * Whether the combobox doesn't have a value.\n   */\n  placeholder: boolean;\n}\n\nexport interface ComboboxTriggerProps\n  extends NativeButtonProps, BaseUIComponentProps<'button', ComboboxTriggerState> {\n  /**\n   * Whether the component should ignore user interaction.\n   * @default false\n   */\n  disabled?: boolean | undefined;\n}\n\nexport namespace ComboboxTrigger {\n  export type State = ComboboxTriggerState;\n  export type Props = ComboboxTriggerProps;\n}\n"
  },
  {
    "path": "packages/react/src/combobox/trigger/ComboboxTriggerDataAttributes.ts",
    "content": "import { CommonTriggerDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum ComboboxTriggerDataAttributes {\n  /**\n   * Present when the corresponding popup is open.\n   */\n  popupOpen = CommonTriggerDataAttributes.popupOpen,\n  /**\n   * Present when the trigger is pressed.\n   */\n  pressed = CommonTriggerDataAttributes.pressed,\n  /**\n   * Present when the component is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the component is readonly.\n   */\n  readonly = 'data-readonly',\n  /**\n   * Indicates which side the corresponding popup is positioned relative to its anchor.\n   * @type {'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start' | null}\n   */\n  popupSide = 'data-popup-side',\n  /**\n   * Present when the component is required.\n   */\n  required = 'data-required',\n  /**\n   * Present when the component is in valid state (when wrapped in Field.Root).\n   */\n  valid = 'data-valid',\n  /**\n   * Present when the component is in invalid state (when wrapped in Field.Root).\n   */\n  invalid = 'data-invalid',\n  /**\n   * Present when the component has been touched (when wrapped in Field.Root).\n   */\n  touched = 'data-touched',\n  /**\n   * Present when the component's value has changed (when wrapped in Field.Root).\n   */\n  dirty = 'data-dirty',\n  /**\n   * Present when the component has a value (when wrapped in Field.Root).\n   */\n  filled = 'data-filled',\n  /**\n   * Present when the trigger is focused (when wrapped in Field.Root).\n   */\n  focused = 'data-focused',\n  /**\n   * Present when the corresponding items list is empty.\n   */\n  listEmpty = 'data-list-empty',\n  /**\n   * Present when the combobox doesn't have a value.\n   */\n  placeholder = 'data-placeholder',\n}\n"
  },
  {
    "path": "packages/react/src/combobox/utils/ComboboxInternalDismissButton.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useMergedRefs } from '@base-ui/utils/useMergedRefs';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { visuallyHiddenInput } from '@base-ui/utils/visuallyHidden';\nimport { useButton } from '../../use-button';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport { useComboboxRootContext } from '../root/ComboboxRootContext';\n\ntype DismissEvent = React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>;\n\n/**\n * @internal\n */\nexport const ComboboxInternalDismissButton = React.forwardRef<HTMLSpanElement>(\n  function ComboboxInternalDismissButton(_, forwardedRef) {\n    const store = useComboboxRootContext();\n\n    const { buttonRef, getButtonProps } = useButton({\n      native: false,\n    });\n\n    const mergedRef = useMergedRefs(forwardedRef, buttonRef);\n\n    const handleDismiss = useStableCallback((event: DismissEvent) => {\n      store.state.setOpen(\n        false,\n        createChangeEventDetails(REASONS.closePress, event.nativeEvent, event.currentTarget),\n      );\n    });\n\n    const dismissProps = getButtonProps({\n      onClick: handleDismiss,\n    });\n\n    return (\n      <span\n        ref={mergedRef}\n        {...dismissProps}\n        aria-label=\"Dismiss\"\n        tabIndex={undefined}\n        style={visuallyHiddenInput}\n      />\n    );\n  },\n);\n"
  },
  {
    "path": "packages/react/src/combobox/utils/handleInputPress.ts",
    "content": "import { isElement } from '@floating-ui/utils/dom';\nimport type * as React from 'react';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport { getTarget, isInteractiveElement } from '../../floating-ui-react/utils/element';\nimport type { ComboboxStore } from '../store';\n\nexport function handleInputPress(\n  event: React.MouseEvent<HTMLElement> & { baseUIHandlerPrevented?: boolean | undefined },\n  store: ComboboxStore,\n  disabled: boolean,\n  readOnly: boolean,\n  shouldIgnoreTarget?: ((target: Element | null) => boolean) | undefined,\n) {\n  if (event.baseUIHandlerPrevented || readOnly) {\n    return;\n  }\n\n  const target = getTarget(event.nativeEvent);\n  const targetElement = isElement(target) ? target : null;\n  if (\n    targetElement !== event.currentTarget &&\n    (shouldIgnoreTarget?.(targetElement) || isInteractiveElement(targetElement))\n  ) {\n    return;\n  }\n\n  event.preventDefault();\n\n  if (disabled) {\n    return;\n  }\n\n  store.state.inputRef.current?.focus();\n\n  if (store.state.openOnInputClick) {\n    store.state.setOpen(true, createChangeEventDetails(REASONS.inputPress, event.nativeEvent));\n  }\n}\n"
  },
  {
    "path": "packages/react/src/combobox/utils/stateAttributesMapping.ts",
    "content": "import { pressableTriggerOpenStateMapping } from '../../utils/popupStateMapping';\nimport { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport type { Side } from '../../utils/useAnchorPositioning';\nimport { fieldValidityMapping } from '../../field/utils/constants';\n\nexport const triggerStateAttributesMapping = {\n  ...pressableTriggerOpenStateMapping,\n  ...fieldValidityMapping,\n  popupSide: (side: Side | null) => (side ? { 'data-popup-side': side } : null),\n  listEmpty: (empty: boolean) => (empty ? { 'data-list-empty': '' } : null),\n} satisfies StateAttributesMapping<{\n  open: boolean;\n  valid: boolean | null;\n  popupSide: Side | null;\n  listEmpty: boolean;\n  placeholder: boolean;\n}>;\n"
  },
  {
    "path": "packages/react/src/combobox/value/ComboboxValue.test.tsx",
    "content": "import { expect } from 'vitest';\nimport * as React from 'react';\nimport { screen } from '@mui/internal-test-utils';\nimport { Combobox } from '@base-ui/react/combobox';\nimport { createRenderer } from '#test-utils';\n\ndescribe('<Combobox.Value />', () => {\n  const { render } = createRenderer();\n\n  describe('prop: children', () => {\n    it('renders current selected value via function child', async () => {\n      await render(\n        <Combobox.Root defaultValue=\"b\">\n          <Combobox.Value>{(val) => <div data-testid=\"value\">{val}</div>}</Combobox.Value>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                  <Combobox.Item value=\"b\">b</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('b');\n    });\n\n    it('renders function child with null when no value selected', async () => {\n      await render(\n        <Combobox.Root>\n          <Combobox.Value>\n            {(val) => <div data-testid=\"value\">{val === null ? 'null' : String(val)}</div>}\n          </Combobox.Value>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"a\">a</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('null');\n    });\n\n    it('renders function child with complex objects', async () => {\n      const complexValue = { id: 1, name: 'Test', nested: { data: 'value' } };\n\n      await render(\n        <Combobox.Root defaultValue={complexValue}>\n          <Combobox.Value>\n            {(val) => <div data-testid=\"value\">{val ? `${val.name} (${val.id})` : 'No value'}</div>}\n          </Combobox.Value>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value={complexValue}>Test Item</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('Test (1)');\n    });\n\n    it('overrides the value display when children is a static ReactNode', async () => {\n      await render(\n        <Combobox.Root defaultValue=\"test-value\">\n          <Combobox.Value>Custom Display Text</Combobox.Value>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"test-value\">Test</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByText('Custom Display Text')).not.toBe(null);\n    });\n\n    it('renders complex ReactNode children', async () => {\n      await render(\n        <Combobox.Root defaultValue=\"test\">\n          <Combobox.Value>\n            <span data-testid=\"complex\">\n              <strong>Bold</strong> and <em>italic</em> text\n            </span>\n          </Combobox.Value>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"test\">Test</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const element = screen.getByTestId('complex');\n      expect(element.querySelector('strong')).toHaveTextContent('Bold');\n      expect(element.querySelector('em')).toHaveTextContent('italic');\n    });\n  });\n\n  describe('selected value with label property', () => {\n    it('renders label from selected value object', async () => {\n      const valueWithLabel = { value: 'test', label: 'Test Label' };\n\n      await render(\n        <Combobox.Root defaultValue={valueWithLabel}>\n          <Combobox.Trigger data-testid=\"value\">\n            <Combobox.Value />\n          </Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value={valueWithLabel}>Test Item</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('Test Label');\n    });\n\n    it('renders ReactNode label from selected value object', async () => {\n      const valueWithLabel = {\n        value: 'test',\n        label: <span data-testid=\"label-node\">Formatted Label</span>,\n      };\n\n      await render(\n        <Combobox.Root defaultValue={valueWithLabel}>\n          <Combobox.Trigger data-testid=\"value\">\n            <Combobox.Value />\n          </Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value={valueWithLabel}>Test Item</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByTestId('label-node')).toHaveTextContent('Formatted Label');\n    });\n\n    it('handles selected value with null label', async () => {\n      const valueWithNullLabel = { value: 'test', label: null };\n\n      await render(\n        <Combobox.Root defaultValue={valueWithNullLabel}>\n          <Combobox.Trigger data-testid=\"value\">\n            <Combobox.Value />\n          </Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value={valueWithNullLabel}>Test Item</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      // Should fall back to stringifyItem behavior\n      expect(screen.getByTestId('value')).toHaveTextContent('test');\n    });\n  });\n\n  describe('items array format', () => {\n    it('renders null item label from items array when no value selected', async () => {\n      const items = [\n        { value: null, label: 'Select item' },\n        { value: 'a', label: 'A' },\n      ];\n\n      await render(\n        <Combobox.Root items={items}>\n          <Combobox.Trigger data-testid=\"value\">\n            <Combobox.Value />\n          </Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value={items[0]}>Select item</Combobox.Item>\n                  <Combobox.Item value={items[1]}>A</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('Select item');\n    });\n\n    it('renders null item label when input is inside popup and no defaultValue', async () => {\n      const items = [\n        { value: null, label: 'Select country' },\n        { value: 'united-kingdom', label: 'United Kingdom' },\n      ];\n\n      await render(\n        <Combobox.Root items={items}>\n          <Combobox.Trigger data-testid=\"value\">\n            <Combobox.Value />\n          </Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup aria-label=\"Select country\">\n                <div>\n                  <Combobox.Input placeholder=\"e.g. United Kingdom\" />\n                </div>\n                <Combobox.Empty>No countries found.</Combobox.Empty>\n                <Combobox.List>\n                  <Combobox.Item value={items[0]}>Select country</Combobox.Item>\n                  <Combobox.Item value={items[1]}>United Kingdom</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('Select country');\n    });\n\n    it('displays the label from items array when value is selected', async () => {\n      const items = [\n        { value: 'sans', label: 'Sans-serif' },\n        { value: 'serif', label: 'Serif' },\n        { value: 'mono', label: 'Monospace' },\n      ];\n\n      await render(\n        <Combobox.Root defaultValue={items[1]} items={items}>\n          <Combobox.Trigger data-testid=\"value\">\n            <Combobox.Value />\n          </Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value={items[0]}>Sans-serif</Combobox.Item>\n                  <Combobox.Item value={items[1]}>Serif</Combobox.Item>\n                  <Combobox.Item value={items[2]}>Monospace</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('Serif');\n    });\n\n    it('updates the label when value changes with items array', async () => {\n      const items = [\n        { value: 'sans', label: 'Sans-serif' },\n        { value: 'serif', label: 'Serif' },\n        { value: 'mono', label: 'Monospace' },\n      ];\n\n      function App() {\n        const [value, setValue] = React.useState<(typeof items)[number] | null>(items[0]);\n        return (\n          <div>\n            <button onClick={() => setValue(items[1])}>serif</button>\n            <button onClick={() => setValue(items[2])}>mono</button>\n            <button onClick={() => setValue(null)}>clear</button>\n            <Combobox.Root value={value} onValueChange={setValue} items={items}>\n              <Combobox.Trigger data-testid=\"value\">\n                <Combobox.Value />\n              </Combobox.Trigger>\n              <Combobox.Portal>\n                <Combobox.Positioner>\n                  <Combobox.Popup>\n                    <Combobox.List>\n                      <Combobox.Item value={items[0]}>Sans-serif</Combobox.Item>\n                      <Combobox.Item value={items[1]}>Serif</Combobox.Item>\n                      <Combobox.Item value={items[2]}>Monospace</Combobox.Item>\n                    </Combobox.List>\n                  </Combobox.Popup>\n                </Combobox.Positioner>\n              </Combobox.Portal>\n            </Combobox.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<App />);\n\n      expect(screen.getByTestId('value')).toHaveTextContent('Sans-serif');\n\n      await user.click(screen.getByRole('button', { name: 'serif' }));\n      expect(screen.getByTestId('value')).toHaveTextContent('Serif');\n\n      await user.click(screen.getByRole('button', { name: 'mono' }));\n      expect(screen.getByTestId('value')).toHaveTextContent('Monospace');\n\n      await user.click(screen.getByRole('button', { name: 'clear' }));\n      expect(screen.getByTestId('value')).toHaveTextContent('');\n    });\n\n    it('supports ReactNode labels in items array', async () => {\n      const items = [\n        { value: 'bold', label: <strong>Bold Text</strong> },\n        { value: 'italic', label: <em>Italic Text</em> },\n      ];\n\n      await render(\n        <Combobox.Root defaultValue={items[0]} items={items}>\n          <Combobox.Trigger data-testid=\"value\">\n            <Combobox.Value />\n          </Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value={items[0]}>Bold Text</Combobox.Item>\n                  <Combobox.Item value={items[1]}>Italic Text</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByTestId('value').querySelector('strong')).toHaveTextContent('Bold Text');\n    });\n\n    it('handles duplicate values in items array (uses first match)', async () => {\n      const items = [\n        { value: 'test', label: 'First Label' },\n        { value: 'test', label: 'Second Label' },\n        { value: 'other', label: 'Other' },\n      ];\n\n      await render(\n        <Combobox.Root defaultValue={items[0]} items={items}>\n          <Combobox.Trigger data-testid=\"value\">\n            <Combobox.Value />\n          </Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value={items[0]}>First Label</Combobox.Item>\n                  <Combobox.Item value={items[1]}>Second Label</Combobox.Item>\n                  <Combobox.Item value={items[2]}>Other</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('First Label');\n    });\n\n    it('is not stale after items are updated', async () => {\n      function App() {\n        // Keep stable object identities for selected items\n        const aRef = React.useRef({ value: 'a', label: 'a' });\n        const bRef = React.useRef({ value: 'b', label: 'b' });\n        const cRef = React.useRef<{ value: 'c'; label: string } | null>(null);\n\n        const [value, setValue] = React.useState<typeof aRef.current | null>(aRef.current);\n        const [items, setItems] = React.useState([aRef.current, bRef.current]);\n\n        function updateItems() {\n          // mutate the label but keep the same object identity for `a`\n          aRef.current.label = 'a new';\n          // introduce a new item `c` and keep reference to it for selection later\n          cRef.current = { value: 'c', label: 'c' };\n          setItems([aRef.current, bRef.current, cRef.current]);\n        }\n\n        return (\n          <div>\n            <button onClick={updateItems}>update</button>\n            <button onClick={() => cRef.current && setValue(cRef.current)}>select c</button>\n            <Combobox.Root value={value} onValueChange={setValue} items={items}>\n              <Combobox.Trigger data-testid=\"value\">\n                <Combobox.Value />\n              </Combobox.Trigger>\n              <Combobox.Portal>\n                <Combobox.Positioner>\n                  <Combobox.Popup>\n                    <Combobox.List>\n                      {items.map((item) => (\n                        <Combobox.Item key={item.value} value={item}>\n                          {item.label}\n                        </Combobox.Item>\n                      ))}\n                    </Combobox.List>\n                  </Combobox.Popup>\n                </Combobox.Positioner>\n              </Combobox.Portal>\n            </Combobox.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<App />);\n\n      expect(screen.getByTestId('value')).toHaveTextContent('a');\n\n      await user.click(screen.getByRole('button', { name: 'update' }));\n      expect(screen.getByTestId('value')).toHaveTextContent('a new');\n\n      await user.click(screen.getByRole('button', { name: 'select c' }));\n      expect(screen.getByTestId('value')).toHaveTextContent('c');\n    });\n  });\n\n  describe('grouped items', () => {\n    it('handles grouped items correctly', async () => {\n      const items = [\n        {\n          value: 'fonts',\n          items: [\n            { value: 'sans', label: 'Sans-serif' },\n            { value: 'serif', label: 'Serif' },\n          ],\n        },\n        {\n          value: 'sizes',\n          items: [\n            { value: 'small', label: 'Small' },\n            { value: 'large', label: 'Large' },\n          ],\n        },\n      ];\n\n      await render(\n        <Combobox.Root defaultValue={items[0].items[1]} items={items}>\n          <Combobox.Trigger data-testid=\"value\">\n            <Combobox.Value />\n          </Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Group items={items[0].items}>\n                    <Combobox.Item value={items[0].items[0]}>Sans-serif</Combobox.Item>\n                    <Combobox.Item value={items[0].items[1]}>Serif</Combobox.Item>\n                  </Combobox.Group>\n                  <Combobox.Group items={items[1].items}>\n                    <Combobox.Item value={items[1].items[0]}>Small</Combobox.Item>\n                    <Combobox.Item value={items[1].items[1]}>Large</Combobox.Item>\n                  </Combobox.Group>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('Serif');\n    });\n\n    it('handles null items in grouped structure', async () => {\n      const items = [\n        {\n          value: 'options',\n          items: [\n            { value: null, label: 'None selected' },\n            { value: 'option1', label: 'Option 1' },\n          ],\n        },\n      ];\n\n      await render(\n        <Combobox.Root items={items}>\n          <Combobox.Trigger data-testid=\"value\">\n            <Combobox.Value />\n          </Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Group items={items[0].items}>\n                    <Combobox.Item value={items[0].items[0]}>None selected</Combobox.Item>\n                    <Combobox.Item value={items[0].items[1]}>Option 1</Combobox.Item>\n                  </Combobox.Group>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('None selected');\n    });\n  });\n\n  describe('multiple selection', () => {\n    it('displays comma-separated labels from items array', async () => {\n      const items = [\n        { value: 'sans', label: 'Sans-serif' },\n        { value: 'serif', label: 'Serif' },\n        { value: 'mono', label: 'Monospace' },\n      ];\n\n      await render(\n        <Combobox.Root defaultValue={[items[0], items[1]]} items={items} multiple>\n          <Combobox.Trigger>\n            <span data-testid=\"value\">\n              <Combobox.Value />\n            </span>\n          </Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.Input />\n                <Combobox.List>\n                  {(item: any) => (\n                    <Combobox.Item key={item.value} value={item}>\n                      {item.label}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('Sans-serif, Serif');\n    });\n\n    it('supports ReactNode labels for multiple selections', async () => {\n      const items = [\n        { value: 'bold', label: <strong>Bold Text</strong> },\n        { value: 'italic', label: <em>Italic Text</em> },\n      ];\n\n      await render(\n        <Combobox.Root defaultValue={items} items={items} multiple>\n          <Combobox.Trigger>\n            <span data-testid=\"value\">\n              <Combobox.Value />\n            </span>\n          </Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  {(item: any) => (\n                    <Combobox.Item key={item.value} value={item}>\n                      {item.label}\n                    </Combobox.Item>\n                  )}\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      const value = screen.getByTestId('value');\n      expect(value.querySelector('strong')).toHaveTextContent('Bold Text');\n      expect(value.querySelector('em')).toHaveTextContent('Italic Text');\n      expect(value).toHaveTextContent('Bold Text, Italic Text');\n    });\n\n    it('falls back to raw values when items are not provided', async () => {\n      await render(\n        <Combobox.Root defaultValue={['serif', 'mono']} multiple>\n          <Combobox.Trigger>\n            <span data-testid=\"value\">\n              <Combobox.Value />\n            </span>\n          </Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"serif\">Serif</Combobox.Item>\n                  <Combobox.Item value=\"mono\">Monospace</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('serif, mono');\n    });\n  });\n\n  describe('primitive values', () => {\n    it('handles string values correctly', async () => {\n      await render(\n        <Combobox.Root defaultValue=\"test-string\">\n          <Combobox.Trigger data-testid=\"value\">\n            <Combobox.Value />\n          </Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"test-string\">Test String</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('test-string');\n    });\n\n    it('handles number values correctly', async () => {\n      await render(\n        <Combobox.Root defaultValue={42}>\n          <Combobox.Trigger data-testid=\"value\">\n            <Combobox.Value />\n          </Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value={42}>Forty Two</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('42');\n    });\n\n    it('handles boolean values correctly', async () => {\n      await render(\n        <Combobox.Root defaultValue={true}>\n          <Combobox.Trigger data-testid=\"value\">\n            <Combobox.Value />\n          </Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value={true}>True</Combobox.Item>\n                  <Combobox.Item value={false}>False</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('true');\n    });\n  });\n\n  describe('prop: itemToStringLabel', () => {\n    it('uses custom itemToStringLabel function', async () => {\n      const customitemToStringLabel = (item: any) => {\n        if (item && typeof item === 'object' && 'name' in item) {\n          return `Custom: ${item.name}`;\n        }\n        return String(item);\n      };\n\n      const complexItem = { id: 1, name: 'Test Item' };\n\n      await render(\n        <Combobox.Root defaultValue={complexItem} itemToStringLabel={customitemToStringLabel}>\n          <Combobox.Trigger data-testid=\"value\">\n            <Combobox.Value />\n          </Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value={complexItem}>Test Item</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('Custom: Test Item');\n    });\n  });\n\n  describe('prop: placeholder', () => {\n    it('displays placeholder when no value is selected', async () => {\n      await render(\n        <Combobox.Root>\n          <Combobox.Trigger data-testid=\"value\">\n            <Combobox.Value placeholder=\"Select an option\" />\n          </Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"option1\">Option 1</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('Select an option');\n    });\n\n    it('does not display placeholder when value is selected', async () => {\n      await render(\n        <Combobox.Root defaultValue=\"option1\">\n          <Combobox.Trigger data-testid=\"value\">\n            <Combobox.Value placeholder=\"Select an option\" />\n          </Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"option1\">Option 1</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('option1');\n    });\n\n    it('children prop takes precedence over placeholder', async () => {\n      await render(\n        <Combobox.Root>\n          <Combobox.Trigger data-testid=\"value\">\n            <Combobox.Value placeholder=\"Select an option\">Custom Text</Combobox.Value>\n          </Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"option1\">Option 1</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('Custom Text');\n    });\n\n    it('children function takes precedence over placeholder', async () => {\n      await render(\n        <Combobox.Root>\n          <Combobox.Trigger data-testid=\"value\">\n            <Combobox.Value placeholder=\"Select an option\">\n              {(value) => value || 'Function fallback'}\n            </Combobox.Value>\n          </Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"option1\">Option 1</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('Function fallback');\n    });\n\n    it('null item label in items takes precedence over placeholder', async () => {\n      const items = [\n        { value: null, label: 'None' },\n        { value: 'option1', label: 'Option 1' },\n      ];\n\n      await render(\n        <Combobox.Root items={items}>\n          <Combobox.Trigger data-testid=\"value\">\n            <Combobox.Value placeholder=\"Select an option\" />\n          </Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value={null}>None</Combobox.Item>\n                  <Combobox.Item value=\"option1\">Option 1</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('None');\n    });\n\n    it('uses placeholder when items have null value without label', async () => {\n      const items = [\n        { value: null, label: null },\n        { value: 'option1', label: 'Option 1' },\n      ];\n\n      await render(\n        <Combobox.Root items={items}>\n          <Combobox.Trigger data-testid=\"value\">\n            <Combobox.Value placeholder=\"Select an option\" />\n          </Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value={null}>None</Combobox.Item>\n                  <Combobox.Item value=\"option1\">Option 1</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('Select an option');\n    });\n\n    it('supports ReactNode as placeholder', async () => {\n      await render(\n        <Combobox.Root>\n          <Combobox.Trigger>\n            <Combobox.Value placeholder={<span data-testid=\"placeholder\">Select an option</span>} />\n          </Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"option1\">Option 1</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByTestId('placeholder')).toHaveTextContent('Select an option');\n    });\n\n    it('displays placeholder when multiple mode has empty array', async () => {\n      await render(\n        <Combobox.Root multiple defaultValue={[]}>\n          <Combobox.Trigger data-testid=\"value\">\n            <Combobox.Value placeholder=\"Select options\" />\n          </Combobox.Trigger>\n          <Combobox.Portal>\n            <Combobox.Positioner>\n              <Combobox.Popup>\n                <Combobox.List>\n                  <Combobox.Item value=\"option1\">Option 1</Combobox.Item>\n                </Combobox.List>\n              </Combobox.Popup>\n            </Combobox.Positioner>\n          </Combobox.Portal>\n        </Combobox.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('Select options');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/combobox/value/ComboboxValue.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStore } from '@base-ui/utils/store';\nimport { useComboboxRootContext } from '../root/ComboboxRootContext';\nimport { resolveMultipleLabels, resolveSelectedLabel } from '../../utils/resolveValueLabel';\nimport { selectors } from '../store';\n\n/**\n * The current value of the combobox.\n * Doesn't render its own HTML element.\n *\n * Documentation: [Base UI Combobox](https://base-ui.com/react/components/combobox)\n */\nexport function ComboboxValue(props: ComboboxValue.Props): React.ReactElement {\n  const { children: childrenProp, placeholder } = props;\n\n  const store = useComboboxRootContext();\n\n  const itemToStringLabel = useStore(store, selectors.itemToStringLabel);\n  const selectedValue = useStore(store, selectors.selectedValue);\n  const items = useStore(store, selectors.items);\n  const multiple = useStore(store, selectors.selectionMode) === 'multiple';\n  const hasSelectedValue = useStore(store, selectors.hasSelectedValue);\n\n  const shouldCheckNullItemLabel = !hasSelectedValue && placeholder != null && childrenProp == null;\n  const hasNullLabel = useStore(store, selectors.hasNullItemLabel, shouldCheckNullItemLabel);\n\n  let children = null;\n  if (typeof childrenProp === 'function') {\n    children = childrenProp(selectedValue);\n  } else if (childrenProp != null) {\n    children = childrenProp;\n  } else if (!hasSelectedValue && placeholder != null && !hasNullLabel) {\n    children = placeholder;\n  } else if (multiple && Array.isArray(selectedValue)) {\n    children = resolveMultipleLabels(selectedValue, items, itemToStringLabel);\n  } else {\n    children = resolveSelectedLabel(selectedValue, items, itemToStringLabel);\n  }\n\n  return <React.Fragment>{children}</React.Fragment>;\n}\n\nexport interface ComboboxValueState {}\n\nexport interface ComboboxValueProps {\n  children?: React.ReactNode | ((selectedValue: any) => React.ReactNode);\n  /**\n   * The placeholder value to display when no value is selected.\n   * This is overridden by `children` if specified, or by a null item's label in `items`.\n   */\n  placeholder?: React.ReactNode;\n}\n\nexport namespace ComboboxValue {\n  export type State = ComboboxValueState;\n  export type Props = ComboboxValueProps;\n}\n"
  },
  {
    "path": "packages/react/src/composite/composite.ts",
    "content": "import { isHTMLElement } from '@floating-ui/utils/dom';\nimport type { TextDirection } from '../direction-provider/DirectionContext';\n\nexport {\n  stopEvent,\n  isIndexOutOfListBounds,\n  isListIndexDisabled,\n  createGridCellMap,\n  findNonDisabledListIndex,\n  getGridCellIndexOfCorner,\n  getGridCellIndices,\n  getGridNavigatedIndex,\n  getMaxListIndex,\n  getMinListIndex,\n} from '../floating-ui-react/utils';\n\nexport interface Dimensions {\n  width: number;\n  height: number;\n}\n\nexport const ARROW_UP = 'ArrowUp';\nexport const ARROW_DOWN = 'ArrowDown';\nexport const ARROW_LEFT = 'ArrowLeft';\nexport const ARROW_RIGHT = 'ArrowRight';\nexport const HOME = 'Home';\nexport const END = 'End';\nexport const PAGE_UP = 'PageUp';\nexport const PAGE_DOWN = 'PageDown';\n\nexport const HORIZONTAL_KEYS = new Set([ARROW_LEFT, ARROW_RIGHT]);\nexport const HORIZONTAL_KEYS_WITH_EXTRA_KEYS = new Set([ARROW_LEFT, ARROW_RIGHT, HOME, END]);\nexport const VERTICAL_KEYS = new Set([ARROW_UP, ARROW_DOWN]);\nexport const VERTICAL_KEYS_WITH_EXTRA_KEYS = new Set([ARROW_UP, ARROW_DOWN, HOME, END]);\nexport const ARROW_KEYS = new Set([...HORIZONTAL_KEYS, ...VERTICAL_KEYS]);\nexport const ALL_KEYS = new Set([...ARROW_KEYS, HOME, END]);\nexport const COMPOSITE_KEYS = new Set([ARROW_UP, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT, HOME, END]);\n\nexport const SHIFT = 'Shift' as const;\nexport const CONTROL = 'Control' as const;\nexport const ALT = 'Alt' as const;\nexport const META = 'Meta' as const;\nexport const MODIFIER_KEYS = new Set([SHIFT, CONTROL, ALT, META] as const);\nexport type ModifierKey = typeof MODIFIER_KEYS extends Set<infer Keys> ? Keys : never;\n\nfunction isInputElement(element: EventTarget): element is HTMLInputElement {\n  return isHTMLElement(element) && element.tagName === 'INPUT';\n}\n\nexport function isNativeInput(\n  element: EventTarget,\n): element is HTMLElement & (HTMLInputElement | HTMLTextAreaElement) {\n  if (isInputElement(element) && element.selectionStart != null) {\n    return true;\n  }\n  if (isHTMLElement(element) && element.tagName === 'TEXTAREA') {\n    return true;\n  }\n  return false;\n}\n\nexport function scrollIntoViewIfNeeded(\n  scrollContainer: HTMLElement | null,\n  element: HTMLElement | null,\n  direction: TextDirection,\n  orientation: 'horizontal' | 'vertical' | 'both',\n) {\n  if (!scrollContainer || !element || !element.scrollTo) {\n    return;\n  }\n\n  let targetX = scrollContainer.scrollLeft;\n  let targetY = scrollContainer.scrollTop;\n\n  const isOverflowingX = scrollContainer.clientWidth < scrollContainer.scrollWidth;\n  const isOverflowingY = scrollContainer.clientHeight < scrollContainer.scrollHeight;\n\n  if (isOverflowingX && orientation !== 'vertical') {\n    const elementOffsetLeft = getOffset(scrollContainer, element, 'left');\n    const containerStyles = getStyles(scrollContainer);\n    const elementStyles = getStyles(element);\n\n    if (direction === 'ltr') {\n      if (\n        elementOffsetLeft + element.offsetWidth + elementStyles.scrollMarginRight >\n        scrollContainer.scrollLeft +\n          scrollContainer.clientWidth -\n          containerStyles.scrollPaddingRight\n      ) {\n        // overflow to the right, scroll to align right edges\n        targetX =\n          elementOffsetLeft +\n          element.offsetWidth +\n          elementStyles.scrollMarginRight -\n          scrollContainer.clientWidth +\n          containerStyles.scrollPaddingRight;\n      } else if (\n        elementOffsetLeft - elementStyles.scrollMarginLeft <\n        scrollContainer.scrollLeft + containerStyles.scrollPaddingLeft\n      ) {\n        // overflow to the left, scroll to align left edges\n        targetX =\n          elementOffsetLeft - elementStyles.scrollMarginLeft - containerStyles.scrollPaddingLeft;\n      }\n    }\n\n    if (direction === 'rtl') {\n      if (\n        elementOffsetLeft - elementStyles.scrollMarginRight <\n        scrollContainer.scrollLeft + containerStyles.scrollPaddingLeft\n      ) {\n        // overflow to the left, scroll to align left edges\n        targetX =\n          elementOffsetLeft - elementStyles.scrollMarginLeft - containerStyles.scrollPaddingLeft;\n      } else if (\n        elementOffsetLeft + element.offsetWidth + elementStyles.scrollMarginRight >\n        scrollContainer.scrollLeft +\n          scrollContainer.clientWidth -\n          containerStyles.scrollPaddingRight\n      ) {\n        // overflow to the right, scroll to align right edges\n        targetX =\n          elementOffsetLeft +\n          element.offsetWidth +\n          elementStyles.scrollMarginRight -\n          scrollContainer.clientWidth +\n          containerStyles.scrollPaddingRight;\n      }\n    }\n  }\n\n  if (isOverflowingY && orientation !== 'horizontal') {\n    const elementOffsetTop = getOffset(scrollContainer, element, 'top');\n    const containerStyles = getStyles(scrollContainer);\n    const elementStyles = getStyles(element);\n\n    if (\n      elementOffsetTop - elementStyles.scrollMarginTop <\n      scrollContainer.scrollTop + containerStyles.scrollPaddingTop\n    ) {\n      // overflow upwards, align top edges\n      targetY = elementOffsetTop - elementStyles.scrollMarginTop - containerStyles.scrollPaddingTop;\n    } else if (\n      elementOffsetTop + element.offsetHeight + elementStyles.scrollMarginBottom >\n      scrollContainer.scrollTop + scrollContainer.clientHeight - containerStyles.scrollPaddingBottom\n    ) {\n      // overflow downwards, align bottom edges\n      targetY =\n        elementOffsetTop +\n        element.offsetHeight +\n        elementStyles.scrollMarginBottom -\n        scrollContainer.clientHeight +\n        containerStyles.scrollPaddingBottom;\n    }\n  }\n\n  scrollContainer.scrollTo({\n    left: targetX,\n    top: targetY,\n    behavior: 'auto',\n  });\n}\n\nfunction getOffset(ancestor: HTMLElement, element: HTMLElement, side: 'left' | 'top') {\n  const propName = side === 'left' ? 'offsetLeft' : 'offsetTop';\n\n  let result = 0;\n\n  while (element.offsetParent) {\n    result += element[propName];\n    if (element.offsetParent === ancestor) {\n      break;\n    }\n    element = element.offsetParent as HTMLElement;\n  }\n\n  return result;\n}\n\nfunction getStyles(element: HTMLElement) {\n  const styles = getComputedStyle(element);\n  return {\n    scrollMarginTop: parseFloat(styles.scrollMarginTop) || 0,\n    scrollMarginRight: parseFloat(styles.scrollMarginRight) || 0,\n    scrollMarginBottom: parseFloat(styles.scrollMarginBottom) || 0,\n    scrollMarginLeft: parseFloat(styles.scrollMarginLeft) || 0,\n    scrollPaddingTop: parseFloat(styles.scrollPaddingTop) || 0,\n    scrollPaddingRight: parseFloat(styles.scrollPaddingRight) || 0,\n    scrollPaddingBottom: parseFloat(styles.scrollPaddingBottom) || 0,\n    scrollPaddingLeft: parseFloat(styles.scrollPaddingLeft) || 0,\n  };\n}\n"
  },
  {
    "path": "packages/react/src/composite/constants.ts",
    "content": "export const ACTIVE_COMPOSITE_ITEM = 'data-composite-item-active';\n"
  },
  {
    "path": "packages/react/src/composite/item/CompositeItem.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useCompositeItem } from './useCompositeItem';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { EMPTY_OBJECT, EMPTY_ARRAY } from '../../utils/constants';\nimport { StateAttributesMapping } from '../../utils/getStateAttributesProps';\n\n/**\n * @internal\n */\nexport function CompositeItem<Metadata, State extends Record<string, any>>(\n  componentProps: CompositeItem.Props<Metadata, State>,\n) {\n  const {\n    render,\n    className,\n    state = EMPTY_OBJECT as State,\n    props = EMPTY_ARRAY,\n    refs = EMPTY_ARRAY,\n    metadata,\n    stateAttributesMapping,\n    tag = 'div',\n    ...elementProps\n  } = componentProps;\n\n  const { compositeProps, compositeRef } = useCompositeItem({ metadata });\n\n  return useRenderElement(tag, componentProps, {\n    state,\n    ref: [...refs, compositeRef],\n    props: [compositeProps, ...props, elementProps],\n    stateAttributesMapping,\n  });\n}\n\nexport interface CompositeItemState {}\n\nexport interface CompositeItemProps<Metadata, State extends Record<string, any>> extends Pick<\n  BaseUIComponentProps<any, State>,\n  'render' | 'className'\n> {\n  children?: React.ReactNode;\n  metadata?: Metadata | undefined;\n  refs?: React.Ref<HTMLElement | null>[] | undefined;\n  props?: Array<Record<string, any> | (() => Record<string, any>)> | undefined;\n  state?: State | undefined;\n  stateAttributesMapping?: StateAttributesMapping<State> | undefined;\n  tag?: keyof React.JSX.IntrinsicElements | undefined;\n}\n\nexport namespace CompositeItem {\n  export type State = CompositeItemState;\n  export type Props<Metadata, TState extends Record<string, any>> = CompositeItemProps<\n    Metadata,\n    TState\n  >;\n}\n"
  },
  {
    "path": "packages/react/src/composite/item/useCompositeItem.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { useMergedRefs } from '@base-ui/utils/useMergedRefs';\nimport { useCompositeRootContext } from '../root/CompositeRootContext';\nimport {\n  useCompositeListItem,\n  type UseCompositeListItemParameters,\n} from '../list/useCompositeListItem';\nimport { HTMLProps } from '../../utils/types';\n\nexport interface UseCompositeItemParameters<Metadata> extends Pick<\n  UseCompositeListItemParameters<Metadata>,\n  'metadata' | 'indexGuessBehavior'\n> {}\n\nexport function useCompositeItem<Metadata>(params: UseCompositeItemParameters<Metadata> = {}) {\n  const { highlightItemOnHover, highlightedIndex, onHighlightedIndexChange } =\n    useCompositeRootContext();\n  const { ref, index } = useCompositeListItem(params);\n\n  const isHighlighted = highlightedIndex === index;\n\n  const itemRef = React.useRef<HTMLElement | null>(null);\n  const mergedRef = useMergedRefs(ref, itemRef);\n\n  const compositeProps = React.useMemo<HTMLProps>(\n    () => ({\n      tabIndex: isHighlighted ? 0 : -1,\n      onFocus() {\n        onHighlightedIndexChange(index);\n      },\n      onMouseMove() {\n        const item = itemRef.current;\n        if (!highlightItemOnHover || !item) {\n          return;\n        }\n\n        const disabled = item.hasAttribute('disabled') || item.ariaDisabled === 'true';\n        if (!isHighlighted && !disabled) {\n          item.focus();\n        }\n      },\n    }),\n    [isHighlighted, onHighlightedIndexChange, index, highlightItemOnHover],\n  );\n\n  return {\n    compositeProps,\n    compositeRef: mergedRef as React.RefCallback<HTMLElement | null>,\n    index,\n  };\n}\n"
  },
  {
    "path": "packages/react/src/composite/list/CompositeList.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { createRenderer } from '#test-utils';\nimport { CompositeList } from './CompositeList';\nimport { useCompositeListItem } from './useCompositeListItem';\n\ndescribe('<CompositeList />', () => {\n  const { render } = createRenderer();\n\n  describe('prop: elementsRef', () => {\n    it('cleans up refs on unmount', async () => {\n      function Item() {\n        const { ref } = useCompositeListItem();\n        return <div ref={ref} />;\n      }\n      const elementsRef = {\n        current: [] as Array<HTMLElement | null>,\n      };\n      const labelsRef = {\n        current: [] as Array<string | null>,\n      };\n      const { unmount } = await render(\n        <CompositeList elementsRef={elementsRef} labelsRef={labelsRef}>\n          <Item />\n          <Item />\n          <Item />\n        </CompositeList>,\n      );\n\n      expect(elementsRef.current).toHaveLength(3);\n      expect(labelsRef.current).toHaveLength(3);\n\n      unmount();\n      expect(elementsRef.current).toHaveLength(0);\n      expect(labelsRef.current).toHaveLength(0);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/composite/list/CompositeList.tsx",
    "content": "/* eslint-disable no-bitwise */\n'use client';\nimport * as React from 'react';\nimport { useRefWithInit } from '@base-ui/utils/useRefWithInit';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { CompositeListContext } from './CompositeListContext';\n\nexport type CompositeMetadata<CustomMetadata> = {\n  index?: number | null | undefined;\n} & CustomMetadata;\n\n/**\n * Provides context for a list of items in a composite component.\n * @internal\n */\nexport function CompositeList<Metadata>(props: CompositeList.Props<Metadata>) {\n  const { children, elementsRef, labelsRef, onMapChange: onMapChangeProp } = props;\n\n  const onMapChange = useStableCallback(onMapChangeProp);\n\n  const nextIndexRef = React.useRef(0);\n  const listeners = useRefWithInit(createListeners).current;\n\n  // We use a stable `map` to avoid O(n^2) re-allocation costs for large lists.\n  // `mapTick` is our re-render trigger mechanism. We also need to update the\n  // elements and label refs, but there's a lot of async work going on and sometimes\n  // the effect that handles `onMapChange` gets called after those refs have been\n  // filled, and we don't want to lose those values by setting their lengths to `0`.\n  // We also need to have them at the proper length because floating-ui uses that\n  // information for list navigation.\n\n  const map = useRefWithInit(createMap<Metadata>).current;\n  // `mapTick` uses a counter rather than objects for low precision-loss risk and better memory efficiency\n  const [mapTick, setMapTick] = React.useState(0);\n  const lastTickRef = React.useRef(mapTick);\n\n  const register = useStableCallback((node: Element, metadata: Metadata) => {\n    map.set(node, metadata ?? null);\n    lastTickRef.current += 1;\n    setMapTick(lastTickRef.current);\n  });\n\n  const unregister = useStableCallback((node: Element) => {\n    map.delete(node);\n    lastTickRef.current += 1;\n    setMapTick(lastTickRef.current);\n  });\n\n  const sortedMap = React.useMemo(() => {\n    // `mapTick` is the `useMemo` trigger as `map` is stable.\n    disableEslintWarning(mapTick);\n\n    const newMap = new Map<Element, CompositeMetadata<Metadata>>();\n    // Filter out disconnected elements before sorting to avoid inconsistent\n    // compareDocumentPosition results when elements are detached from the DOM.\n    const sortedNodes = Array.from(map.keys())\n      .filter((node) => node.isConnected)\n      .sort(sortByDocumentPosition);\n\n    sortedNodes.forEach((node, index) => {\n      const metadata = map.get(node) ?? ({} as CompositeMetadata<Metadata>);\n      newMap.set(node, { ...metadata, index });\n    });\n\n    return newMap;\n  }, [map, mapTick]);\n\n  useIsoLayoutEffect(() => {\n    if (typeof MutationObserver !== 'function' || sortedMap.size === 0) {\n      return undefined;\n    }\n\n    const mutationObserver = new MutationObserver((entries) => {\n      const diff = new Set<Node>();\n      const updateDiff = (node: Node) => (diff.has(node) ? diff.delete(node) : diff.add(node));\n      entries.forEach((entry) => {\n        entry.removedNodes.forEach(updateDiff);\n        entry.addedNodes.forEach(updateDiff);\n      });\n      if (diff.size === 0) {\n        lastTickRef.current += 1;\n        setMapTick(lastTickRef.current);\n      }\n    });\n\n    sortedMap.forEach((_, node) => {\n      if (node.parentElement) {\n        mutationObserver.observe(node.parentElement, { childList: true });\n      }\n    });\n\n    return () => {\n      mutationObserver.disconnect();\n    };\n  }, [sortedMap]);\n\n  useIsoLayoutEffect(() => {\n    const shouldUpdateLengths = lastTickRef.current === mapTick;\n    if (shouldUpdateLengths) {\n      if (elementsRef.current.length !== sortedMap.size) {\n        elementsRef.current.length = sortedMap.size;\n      }\n      if (labelsRef && labelsRef.current.length !== sortedMap.size) {\n        labelsRef.current.length = sortedMap.size;\n      }\n      nextIndexRef.current = sortedMap.size;\n    }\n\n    onMapChange(sortedMap);\n  }, [onMapChange, sortedMap, elementsRef, labelsRef, mapTick]);\n\n  useIsoLayoutEffect(() => {\n    return () => {\n      elementsRef.current = [];\n    };\n  }, [elementsRef]);\n\n  useIsoLayoutEffect(() => {\n    return () => {\n      if (labelsRef) {\n        labelsRef.current = [];\n      }\n    };\n  }, [labelsRef]);\n\n  const subscribeMapChange = useStableCallback((fn) => {\n    listeners.add(fn);\n    return () => {\n      listeners.delete(fn);\n    };\n  });\n\n  useIsoLayoutEffect(() => {\n    listeners.forEach((l) => l(sortedMap));\n  }, [listeners, sortedMap]);\n\n  const contextValue = React.useMemo(\n    () => ({ register, unregister, subscribeMapChange, elementsRef, labelsRef, nextIndexRef }),\n    [register, unregister, subscribeMapChange, elementsRef, labelsRef, nextIndexRef],\n  );\n\n  return (\n    <CompositeListContext.Provider value={contextValue}>{children}</CompositeListContext.Provider>\n  );\n}\n\nfunction createMap<Metadata>() {\n  return new Map<Element, CompositeMetadata<Metadata> | null>();\n}\n\nfunction createListeners() {\n  return new Set<Function>();\n}\n\nfunction sortByDocumentPosition(a: Element, b: Element) {\n  const position = a.compareDocumentPosition(b);\n\n  if (\n    position & Node.DOCUMENT_POSITION_FOLLOWING ||\n    position & Node.DOCUMENT_POSITION_CONTAINED_BY\n  ) {\n    return -1;\n  }\n\n  if (position & Node.DOCUMENT_POSITION_PRECEDING || position & Node.DOCUMENT_POSITION_CONTAINS) {\n    return 1;\n  }\n\n  return 0;\n}\n\nfunction disableEslintWarning(_: any) {}\n\nexport interface CompositeListState {}\n\nexport interface CompositeListProps<Metadata> {\n  children: React.ReactNode;\n  /**\n   * A ref to the list of HTML elements, ordered by their index.\n   * `useListNavigation`'s `listRef` prop.\n   */\n  elementsRef: React.RefObject<Array<HTMLElement | null>>;\n  /**\n   * A ref to the list of element labels, ordered by their index.\n   * `useTypeahead`'s `listRef` prop.\n   */\n  labelsRef?: React.RefObject<Array<string | null>> | undefined;\n  onMapChange?: ((newMap: Map<Element, CompositeMetadata<Metadata> | null>) => void) | undefined;\n}\n\nexport namespace CompositeList {\n  export type State = CompositeListState;\n  export type Props<Metadata> = CompositeListProps<Metadata>;\n}\n"
  },
  {
    "path": "packages/react/src/composite/list/CompositeListContext.ts",
    "content": "'use client';\nimport * as React from 'react';\n\nexport interface CompositeListContextValue<Metadata> {\n  register: (node: Element, metadata: Metadata) => void;\n  unregister: (node: Element) => void;\n  subscribeMapChange: (fn: (map: Map<Element, Metadata | null>) => void) => () => void;\n  elementsRef: React.RefObject<Array<HTMLElement | null>>;\n  labelsRef?: React.RefObject<Array<string | null>> | undefined;\n  nextIndexRef: React.RefObject<number>;\n}\n\nexport const CompositeListContext = React.createContext<CompositeListContextValue<any>>({\n  register: () => {},\n  unregister: () => {},\n  subscribeMapChange: () => {\n    return () => {};\n  },\n  elementsRef: { current: [] },\n  nextIndexRef: { current: 0 },\n});\n\nexport function useCompositeListContext() {\n  return React.useContext(CompositeListContext);\n}\n"
  },
  {
    "path": "packages/react/src/composite/list/useCompositeListItem.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useCompositeListContext } from './CompositeListContext';\n\nexport interface UseCompositeListItemParameters<Metadata> {\n  index?: number | undefined;\n  label?: string | null | undefined;\n  metadata?: Metadata | undefined;\n  textRef?: React.RefObject<HTMLElement | null> | undefined;\n  /** Enables guessing the indexes. This avoids a re-render after mount, which is useful for\n   * large lists. This should be used for lists that are likely flat and vertical, other cases\n   * might trigger a re-render anyway. */\n  indexGuessBehavior?: IndexGuessBehavior | undefined;\n}\n\ninterface UseCompositeListItemReturnValue {\n  ref: (node: HTMLElement | null) => void;\n  index: number;\n}\n\nexport enum IndexGuessBehavior {\n  None,\n  GuessFromOrder,\n}\n\n/**\n * Used to register a list item and its index (DOM position) in the `CompositeList`.\n */\nexport function useCompositeListItem<Metadata>(\n  params: UseCompositeListItemParameters<Metadata> = {},\n): UseCompositeListItemReturnValue {\n  const { label, metadata, textRef, indexGuessBehavior, index: externalIndex } = params;\n\n  const { register, unregister, subscribeMapChange, elementsRef, labelsRef, nextIndexRef } =\n    useCompositeListContext();\n\n  const indexRef = React.useRef(-1);\n  const [index, setIndex] = React.useState<number>(\n    externalIndex ??\n      (indexGuessBehavior === IndexGuessBehavior.GuessFromOrder\n        ? () => {\n            if (indexRef.current === -1) {\n              const newIndex = nextIndexRef.current;\n              nextIndexRef.current += 1;\n              indexRef.current = newIndex;\n            }\n            return indexRef.current;\n          }\n        : -1),\n  );\n\n  const componentRef = React.useRef<Element | null>(null);\n\n  const ref = React.useCallback(\n    (node: HTMLElement | null) => {\n      componentRef.current = node;\n\n      if (index !== -1 && node !== null) {\n        elementsRef.current[index] = node;\n\n        if (labelsRef) {\n          const isLabelDefined = label !== undefined;\n          labelsRef.current[index] = isLabelDefined\n            ? label\n            : (textRef?.current?.textContent ?? node.textContent);\n        }\n      }\n    },\n    [index, elementsRef, labelsRef, label, textRef],\n  );\n\n  useIsoLayoutEffect(() => {\n    if (externalIndex != null) {\n      return undefined;\n    }\n\n    const node = componentRef.current;\n    if (node) {\n      register(node, metadata);\n      return () => {\n        unregister(node);\n      };\n    }\n    return undefined;\n  }, [externalIndex, register, unregister, metadata]);\n\n  useIsoLayoutEffect(() => {\n    if (externalIndex != null) {\n      return undefined;\n    }\n\n    return subscribeMapChange((map) => {\n      const i = componentRef.current ? map.get(componentRef.current)?.index : null;\n\n      if (i != null) {\n        setIndex(i);\n      }\n    });\n  }, [externalIndex, subscribeMapChange, setIndex]);\n\n  return React.useMemo(\n    () => ({\n      ref,\n      index,\n    }),\n    [index, ref],\n  );\n}\n"
  },
  {
    "path": "packages/react/src/composite/root/CompositeRoot.test.tsx",
    "content": "import { expect } from 'vitest';\nimport * as React from 'react';\nimport { act, createRenderer, fireEvent, flushMicrotasks, screen } from '@mui/internal-test-utils';\nimport { isJSDOM } from '#test-utils';\nimport { DirectionProvider } from '../../direction-provider';\nimport { CompositeItem } from '../item/CompositeItem';\nimport { CompositeRoot } from './CompositeRoot';\n\ndescribe('Composite', () => {\n  const { render } = createRenderer();\n\n  describe('list', () => {\n    it('controlled mode', async () => {\n      function App() {\n        const [highlightedIndex, setHighlightedIndex] = React.useState(0);\n        return (\n          <CompositeRoot\n            highlightedIndex={highlightedIndex}\n            onHighlightedIndexChange={setHighlightedIndex}\n          >\n            <CompositeItem data-testid=\"1\">1</CompositeItem>\n            <CompositeItem data-testid=\"2\">2</CompositeItem>\n            <CompositeItem data-testid=\"3\">3</CompositeItem>\n          </CompositeRoot>\n        );\n      }\n\n      render(<App />);\n\n      const item1 = screen.getByTestId('1');\n      const item2 = screen.getByTestId('2');\n      const item3 = screen.getByTestId('3');\n\n      act(() => item1.focus());\n\n      expect(item1).toHaveAttribute('tabindex', '0');\n\n      fireEvent.keyDown(item1, { key: 'ArrowDown' });\n      await flushMicrotasks();\n\n      expect(item2).toHaveAttribute('tabindex', '0');\n      expect(item2).toHaveFocus();\n\n      fireEvent.keyDown(item2, { key: 'ArrowDown' });\n      await flushMicrotasks();\n\n      expect(item3).toHaveAttribute('tabindex', '0');\n      expect(item3).toHaveFocus();\n\n      fireEvent.keyDown(item3, { key: 'ArrowUp' });\n      await flushMicrotasks();\n\n      expect(item2).toHaveAttribute('tabindex', '0');\n      expect(item2).toHaveFocus();\n\n      fireEvent.keyDown(item2, { key: 'ArrowUp' });\n      await flushMicrotasks();\n\n      expect(item1).toHaveAttribute('tabindex', '0');\n      expect(item1).toHaveFocus();\n    });\n\n    it('uncontrolled mode', async () => {\n      render(\n        <CompositeRoot>\n          <CompositeItem data-testid=\"1\">1</CompositeItem>\n          <CompositeItem data-testid=\"2\">2</CompositeItem>\n          <CompositeItem data-testid=\"3\">3</CompositeItem>\n        </CompositeRoot>,\n      );\n\n      const item1 = screen.getByTestId('1');\n      const item2 = screen.getByTestId('2');\n      const item3 = screen.getByTestId('3');\n\n      act(() => item1.focus());\n\n      fireEvent.keyDown(item1, { key: 'ArrowDown' });\n      await flushMicrotasks();\n\n      expect(item2).toHaveAttribute('tabindex', '0');\n      expect(item2).toHaveFocus();\n\n      fireEvent.keyDown(item2, { key: 'ArrowDown' });\n      await flushMicrotasks();\n\n      expect(item3).toHaveAttribute('tabindex', '0');\n      expect(item3).toHaveFocus();\n\n      fireEvent.keyDown(item3, { key: 'ArrowUp' });\n      await flushMicrotasks();\n\n      expect(item2).toHaveAttribute('tabindex', '0');\n      expect(item2).toHaveFocus();\n\n      fireEvent.keyDown(item2, { key: 'ArrowUp' });\n      await flushMicrotasks();\n\n      expect(item1).toHaveAttribute('tabindex', '0');\n      expect(item1).toHaveFocus();\n    });\n\n    it.skipIf(isJSDOM)('updates the order of items', async () => {\n      function App(props: { items: string[] }) {\n        return (\n          <CompositeRoot>\n            {props.items.map((item) => (\n              <CompositeItem key={item} data-testid={item}>\n                {item}\n              </CompositeItem>\n            ))}\n          </CompositeRoot>\n        );\n      }\n      const { user, rerender } = render(<App items={['1', '2', '3']} />);\n      rerender(<App items={['1', '3', '2']} />);\n\n      const item1 = screen.getByTestId('1');\n      const item3 = screen.getByTestId('3');\n\n      act(() => item1.focus());\n      await user.keyboard('{ArrowDown}');\n      expect(item3).toHaveFocus();\n    });\n\n    describe('Home and End keys', () => {\n      it('Home key moves focus to the first item', async () => {\n        render(\n          <CompositeRoot enableHomeAndEndKeys>\n            <CompositeItem data-testid=\"1\">1</CompositeItem>\n            <CompositeItem data-testid=\"2\">2</CompositeItem>\n            <CompositeItem data-testid=\"3\">3</CompositeItem>\n          </CompositeRoot>,\n        );\n\n        const item1 = screen.getByTestId('1');\n        const item3 = screen.getByTestId('3');\n\n        act(() => item3.focus());\n\n        fireEvent.keyDown(item3, { key: 'Home' });\n        await flushMicrotasks();\n\n        expect(item1).toHaveAttribute('tabindex', '0');\n        expect(item1).toHaveFocus();\n      });\n\n      it('End key moves focus to the last item', async () => {\n        render(\n          <CompositeRoot enableHomeAndEndKeys>\n            <CompositeItem data-testid=\"1\">1</CompositeItem>\n            <CompositeItem data-testid=\"2\">2</CompositeItem>\n            <CompositeItem data-testid=\"3\">3</CompositeItem>\n          </CompositeRoot>,\n        );\n\n        const item1 = screen.getByTestId('1');\n        const item3 = screen.getByTestId('3');\n\n        act(() => item1.focus());\n\n        fireEvent.keyDown(item1, { key: 'End' });\n        await flushMicrotasks();\n\n        expect(item3).toHaveAttribute('tabindex', '0');\n        expect(item3).toHaveFocus();\n      });\n    });\n\n    describe.skipIf(isJSDOM)('rtl', () => {\n      it('horizontal orientation', async () => {\n        render(\n          <div dir=\"rtl\">\n            <DirectionProvider direction=\"rtl\">\n              <CompositeRoot orientation=\"horizontal\">\n                <CompositeItem data-testid=\"1\">1</CompositeItem>\n                <CompositeItem data-testid=\"2\">2</CompositeItem>\n                <CompositeItem data-testid=\"3\">3</CompositeItem>\n              </CompositeRoot>\n            </DirectionProvider>\n          </div>,\n        );\n\n        const item1 = screen.getByTestId('1');\n        const item2 = screen.getByTestId('2');\n        const item3 = screen.getByTestId('3');\n\n        act(() => item1.focus());\n\n        fireEvent.keyDown(item1, { key: 'ArrowDown' });\n        await flushMicrotasks();\n\n        fireEvent.keyDown(item1, { key: 'ArrowLeft' });\n        await flushMicrotasks();\n\n        expect(item2).toHaveAttribute('tabindex', '0');\n        expect(item2).toHaveFocus();\n\n        fireEvent.keyDown(item2, { key: 'ArrowLeft' });\n        await flushMicrotasks();\n\n        expect(item3).toHaveAttribute('tabindex', '0');\n        expect(item3).toHaveFocus();\n\n        fireEvent.keyDown(item3, { key: 'ArrowRight' });\n        await flushMicrotasks();\n\n        expect(item2).toHaveAttribute('tabindex', '0');\n        expect(item2).toHaveFocus();\n\n        fireEvent.keyDown(item2, { key: 'ArrowRight' });\n        await flushMicrotasks();\n\n        expect(item1).toHaveAttribute('tabindex', '0');\n        expect(item1).toHaveFocus();\n\n        // loop backward\n        fireEvent.keyDown(item1, { key: 'ArrowRight' });\n        await flushMicrotasks();\n\n        expect(item3).toHaveAttribute('tabindex', '0');\n        expect(item3).toHaveFocus();\n      });\n\n      it('both horizontal and vertical orientation', async () => {\n        render(\n          <div dir=\"rtl\">\n            <DirectionProvider direction=\"rtl\">\n              <CompositeRoot orientation=\"both\">\n                <CompositeItem data-testid=\"1\">1</CompositeItem>\n                <CompositeItem data-testid=\"2\">2</CompositeItem>\n                <CompositeItem data-testid=\"3\">3</CompositeItem>\n              </CompositeRoot>\n            </DirectionProvider>\n          </div>,\n        );\n\n        const item1 = screen.getByTestId('1');\n        const item2 = screen.getByTestId('2');\n        const item3 = screen.getByTestId('3');\n\n        act(() => item1.focus());\n\n        fireEvent.keyDown(item1, { key: 'ArrowLeft' });\n        await flushMicrotasks();\n\n        expect(item2).toHaveAttribute('tabindex', '0');\n        expect(item2).toHaveFocus();\n\n        fireEvent.keyDown(item2, { key: 'ArrowLeft' });\n        await flushMicrotasks();\n\n        expect(item3).toHaveAttribute('tabindex', '0');\n        expect(item3).toHaveFocus();\n\n        fireEvent.keyDown(item3, { key: 'ArrowRight' });\n        await flushMicrotasks();\n\n        expect(item2).toHaveAttribute('tabindex', '0');\n        expect(item2).toHaveFocus();\n\n        fireEvent.keyDown(item2, { key: 'ArrowRight' });\n        await flushMicrotasks();\n\n        expect(item1).toHaveAttribute('tabindex', '0');\n        expect(item1).toHaveFocus();\n\n        fireEvent.keyDown(item1, { key: 'ArrowDown' });\n        await flushMicrotasks();\n\n        expect(item2).toHaveAttribute('tabindex', '0');\n        expect(item2).toHaveFocus();\n\n        fireEvent.keyDown(item2, { key: 'ArrowDown' });\n        await flushMicrotasks();\n\n        expect(item3).toHaveAttribute('tabindex', '0');\n        expect(item3).toHaveFocus();\n      });\n    });\n  });\n\n  describe('grid', () => {\n    it('uniform 1x1 items', async () => {\n      function App() {\n        return (\n          // 1 to 9 numpad\n          <CompositeRoot cols={3} enableHomeAndEndKeys>\n            {['1', '2', '3', '4', '5', '6', '7', '8', '9'].map((i) => (\n              <CompositeItem key={i} data-testid={i}>\n                {i}\n              </CompositeItem>\n            ))}\n          </CompositeRoot>\n        );\n      }\n\n      await render(<App />);\n\n      act(() => screen.getByTestId('1').focus());\n\n      fireEvent.keyDown(screen.getByTestId('1'), { key: 'ArrowDown' });\n      await flushMicrotasks();\n\n      expect(screen.getByTestId('4')).toHaveAttribute('tabindex', '0');\n      expect(screen.getByTestId('4')).toHaveFocus();\n\n      fireEvent.keyDown(screen.getByTestId('4'), { key: 'ArrowRight' });\n      await flushMicrotasks();\n\n      expect(screen.getByTestId('5')).toHaveAttribute('tabindex', '0');\n      expect(screen.getByTestId('5')).toHaveFocus();\n\n      fireEvent.keyDown(screen.getByTestId('5'), { key: 'ArrowDown' });\n      await flushMicrotasks();\n\n      expect(screen.getByTestId('8')).toHaveAttribute('tabindex', '0');\n      expect(screen.getByTestId('8')).toHaveFocus();\n\n      fireEvent.keyDown(screen.getByTestId('8'), { key: 'ArrowLeft' });\n      await flushMicrotasks();\n\n      expect(screen.getByTestId('7')).toHaveAttribute('tabindex', '0');\n      expect(screen.getByTestId('7')).toHaveFocus();\n\n      fireEvent.keyDown(screen.getByTestId('7'), { key: 'ArrowUp' });\n      await flushMicrotasks();\n\n      expect(screen.getByTestId('4')).toHaveAttribute('tabindex', '0');\n      expect(screen.getByTestId('4')).toHaveFocus();\n\n      act(() => screen.getByTestId('9').focus());\n      await flushMicrotasks();\n\n      expect(screen.getByTestId('9')).toHaveAttribute('tabindex', '0');\n\n      fireEvent.keyDown(screen.getByTestId('9'), { key: 'Home' });\n      await flushMicrotasks();\n\n      expect(screen.getByTestId('1')).toHaveAttribute('tabindex', '0');\n\n      fireEvent.keyDown(screen.getByTestId('1'), { key: 'End' });\n      await flushMicrotasks();\n\n      expect(screen.getByTestId('9')).toHaveAttribute('tabindex', '0');\n    });\n\n    describe.skipIf(isJSDOM)('rtl', () => {\n      it('horizontal orientation', async () => {\n        render(\n          <div dir=\"rtl\">\n            <DirectionProvider direction=\"rtl\">\n              <CompositeRoot cols={3} orientation=\"horizontal\" enableHomeAndEndKeys>\n                {['1', '2', '3', '4', '5', '6', '7', '8', '9'].map((i) => (\n                  <CompositeItem key={i} data-testid={i}>\n                    {i}\n                  </CompositeItem>\n                ))}\n              </CompositeRoot>\n            </DirectionProvider>\n          </div>,\n        );\n\n        act(() => screen.getByTestId('1').focus());\n\n        fireEvent.keyDown(screen.getByTestId('1'), { key: 'ArrowLeft' });\n        await flushMicrotasks();\n\n        expect(screen.getByTestId('2')).toHaveAttribute('tabindex', '0');\n        expect(screen.getByTestId('2')).toHaveFocus();\n\n        fireEvent.keyDown(screen.getByTestId('2'), { key: 'ArrowLeft' });\n        await flushMicrotasks();\n\n        expect(screen.getByTestId('3')).toHaveAttribute('tabindex', '0');\n        expect(screen.getByTestId('3')).toHaveFocus();\n\n        fireEvent.keyDown(screen.getByTestId('3'), { key: 'ArrowLeft' });\n        await flushMicrotasks();\n\n        expect(screen.getByTestId('4')).toHaveAttribute('tabindex', '0');\n        expect(screen.getByTestId('4')).toHaveFocus();\n\n        fireEvent.keyDown(screen.getByTestId('4'), { key: 'ArrowLeft' });\n        await flushMicrotasks();\n\n        expect(screen.getByTestId('5')).toHaveAttribute('tabindex', '0');\n        expect(screen.getByTestId('5')).toHaveFocus();\n\n        fireEvent.keyDown(screen.getByTestId('5'), { key: 'Home' });\n        await flushMicrotasks();\n\n        expect(screen.getByTestId('1')).toHaveAttribute('tabindex', '0');\n\n        fireEvent.keyDown(screen.getByTestId('1'), { key: 'End' });\n        await flushMicrotasks();\n\n        expect(screen.getByTestId('9')).toHaveAttribute('tabindex', '0');\n      });\n\n      it('both horizontal and vertical orientation', async () => {\n        await render(\n          <div dir=\"rtl\">\n            <DirectionProvider direction=\"rtl\">\n              <CompositeRoot cols={3} orientation=\"both\" enableHomeAndEndKeys>\n                {['1', '2', '3', '4', '5', '6', '7', '8', '9'].map((i) => (\n                  <CompositeItem key={i} data-testid={i}>\n                    {i}\n                  </CompositeItem>\n                ))}\n              </CompositeRoot>\n            </DirectionProvider>\n          </div>,\n        );\n\n        act(() => screen.getByTestId('1').focus());\n\n        fireEvent.keyDown(screen.getByTestId('1'), { key: 'ArrowDown' });\n        await flushMicrotasks();\n\n        expect(screen.getByTestId('4')).toHaveAttribute('tabindex', '0');\n        expect(screen.getByTestId('4')).toHaveFocus();\n\n        fireEvent.keyDown(screen.getByTestId('4'), { key: 'ArrowLeft' });\n        await flushMicrotasks();\n\n        expect(screen.getByTestId('5')).toHaveAttribute('tabindex', '0');\n        expect(screen.getByTestId('5')).toHaveFocus();\n\n        fireEvent.keyDown(screen.getByTestId('5'), { key: 'ArrowDown' });\n        await flushMicrotasks();\n\n        expect(screen.getByTestId('8')).toHaveAttribute('tabindex', '0');\n        expect(screen.getByTestId('8')).toHaveFocus();\n\n        fireEvent.keyDown(screen.getByTestId('8'), { key: 'ArrowRight' });\n        await flushMicrotasks();\n\n        expect(screen.getByTestId('7')).toHaveAttribute('tabindex', '0');\n        expect(screen.getByTestId('7')).toHaveFocus();\n\n        fireEvent.keyDown(screen.getByTestId('7'), { key: 'ArrowUp' });\n        await flushMicrotasks();\n\n        expect(screen.getByTestId('4')).toHaveAttribute('tabindex', '0');\n        expect(screen.getByTestId('4')).toHaveFocus();\n\n        fireEvent.keyDown(screen.getByTestId('4'), { key: 'End' });\n        await flushMicrotasks();\n\n        expect(screen.getByTestId('9')).toHaveAttribute('tabindex', '0');\n\n        fireEvent.keyDown(screen.getByTestId('9'), { key: 'Home' });\n        await flushMicrotasks();\n\n        expect(screen.getByTestId('1')).toHaveAttribute('tabindex', '0');\n      });\n    });\n\n    describe('prop: disabledIndices', () => {\n      it('disables navigating item when their index is included', async () => {\n        function App() {\n          const [highlightedIndex, setHighlightedIndex] = React.useState(0);\n          return (\n            <CompositeRoot\n              highlightedIndex={highlightedIndex}\n              onHighlightedIndexChange={setHighlightedIndex}\n              disabledIndices={[1]}\n            >\n              <CompositeItem data-testid=\"1\" />\n              <CompositeItem data-testid=\"2\" />\n              <CompositeItem data-testid=\"3\" />\n            </CompositeRoot>\n          );\n        }\n\n        render(<App />);\n\n        const item1 = screen.getByTestId('1');\n        const item3 = screen.getByTestId('3');\n\n        act(() => item1.focus());\n\n        fireEvent.keyDown(item1, { key: 'ArrowDown' });\n        await flushMicrotasks();\n\n        expect(item3).toHaveAttribute('tabindex', '0');\n        expect(item3).toHaveFocus();\n\n        fireEvent.keyDown(item3, { key: 'ArrowUp' });\n        await flushMicrotasks();\n\n        expect(item1).toHaveAttribute('tabindex', '0');\n        expect(item1).toHaveFocus();\n      });\n\n      it('allows navigating items disabled in the DOM when their index is excluded', async () => {\n        function App() {\n          const [highlightedIndex, setHighlightedIndex] = React.useState(0);\n          return (\n            <CompositeRoot\n              highlightedIndex={highlightedIndex}\n              onHighlightedIndexChange={setHighlightedIndex}\n              disabledIndices={[]}\n            >\n              <CompositeItem\n                data-testid=\"1\"\n                // TS doesn't like the disabled attribute on non-interactive elements\n                // but testing library refuses to focus disabled interactive elements\n                // @ts-ignore\n                render={<span data-disabled aria-disabled=\"true\" disabled />}\n              />\n              <CompositeItem\n                data-testid=\"2\"\n                // @ts-ignore\n                render={<span data-disabled aria-disabled=\"true\" disabled />}\n              />\n              <CompositeItem\n                data-testid=\"3\"\n                // @ts-ignore\n                render={<span data-disabled aria-disabled=\"true\" disabled />}\n              />\n            </CompositeRoot>\n          );\n        }\n\n        await render(<App />);\n\n        const item1 = screen.getByTestId('1');\n        const item2 = screen.getByTestId('2');\n        const item3 = screen.getByTestId('3');\n\n        act(() => item1.focus());\n\n        fireEvent.keyDown(item1, { key: 'ArrowDown' });\n        await flushMicrotasks();\n\n        expect(item2).toHaveAttribute('tabindex', '0');\n        expect(item2).toHaveFocus();\n\n        fireEvent.keyDown(item2, { key: 'ArrowDown' });\n        await flushMicrotasks();\n\n        expect(item3).toHaveAttribute('tabindex', '0');\n        expect(item3).toHaveFocus();\n\n        fireEvent.keyDown(item3, { key: 'ArrowDown' });\n        await flushMicrotasks();\n\n        expect(item1).toHaveAttribute('tabindex', '0');\n        expect(item1).toHaveFocus();\n\n        fireEvent.keyDown(item1, { key: 'ArrowUp' });\n        await flushMicrotasks();\n\n        expect(item3).toHaveAttribute('tabindex', '0');\n        expect(item3).toHaveFocus();\n      });\n    });\n  });\n\n  describe('prop: disabledIndices', () => {\n    it('disables navigating item when their index is included', async () => {\n      function App() {\n        const [highlightedIndex, setHighlightedIndex] = React.useState(0);\n        return (\n          <CompositeRoot\n            highlightedIndex={highlightedIndex}\n            onHighlightedIndexChange={setHighlightedIndex}\n            disabledIndices={[1]}\n          >\n            <CompositeItem data-testid=\"1\" />\n            <CompositeItem data-testid=\"2\" />\n            <CompositeItem data-testid=\"3\" />\n          </CompositeRoot>\n        );\n      }\n\n      render(<App />);\n\n      const item1 = screen.getByTestId('1');\n      const item3 = screen.getByTestId('3');\n\n      act(() => item1.focus());\n\n      fireEvent.keyDown(item1, { key: 'ArrowDown' });\n      await flushMicrotasks();\n\n      expect(item3).toHaveAttribute('tabindex', '0');\n      expect(item3).toHaveFocus();\n\n      fireEvent.keyDown(item3, { key: 'ArrowUp' });\n      await flushMicrotasks();\n\n      expect(item1).toHaveAttribute('tabindex', '0');\n      expect(item1).toHaveFocus();\n    });\n\n    it('allows navigating items disabled in the DOM when their index is excluded', async () => {\n      function App() {\n        const [highlightedIndex, setHighlightedIndex] = React.useState(0);\n        return (\n          <CompositeRoot\n            highlightedIndex={highlightedIndex}\n            onHighlightedIndexChange={setHighlightedIndex}\n            disabledIndices={[]}\n          >\n            <CompositeItem\n              data-testid=\"1\"\n              // TS doesn't like the disabled attribute on non-interactive elements\n              // but testing library refuses to focus disabled interactive elements\n              // @ts-ignore\n              render={<span data-disabled aria-disabled=\"true\" disabled />}\n            />\n            <CompositeItem\n              data-testid=\"2\"\n              // @ts-ignore\n              render={<span data-disabled aria-disabled=\"true\" disabled />}\n            />\n            <CompositeItem\n              data-testid=\"3\"\n              // @ts-ignore\n              render={<span data-disabled aria-disabled=\"true\" disabled />}\n            />\n          </CompositeRoot>\n        );\n      }\n\n      await render(<App />);\n\n      const item1 = screen.getByTestId('1');\n      const item2 = screen.getByTestId('2');\n      const item3 = screen.getByTestId('3');\n\n      act(() => item1.focus());\n\n      fireEvent.keyDown(item1, { key: 'ArrowDown' });\n      await flushMicrotasks();\n\n      expect(item2).toHaveAttribute('tabindex', '0');\n      expect(item2).toHaveFocus();\n\n      fireEvent.keyDown(item2, { key: 'ArrowDown' });\n      await flushMicrotasks();\n\n      expect(item3).toHaveAttribute('tabindex', '0');\n      expect(item3).toHaveFocus();\n\n      fireEvent.keyDown(item3, { key: 'ArrowDown' });\n      await flushMicrotasks();\n\n      expect(item1).toHaveAttribute('tabindex', '0');\n      expect(item1).toHaveFocus();\n\n      fireEvent.keyDown(item1, { key: 'ArrowUp' });\n      await flushMicrotasks();\n\n      expect(item3).toHaveAttribute('tabindex', '0');\n      expect(item3).toHaveFocus();\n    });\n  });\n\n  describe('prop: modifierKeys', () => {\n    it('prevents arrow key navigation when any modifier key is pressed by default', async () => {\n      render(\n        <CompositeRoot>\n          <CompositeItem data-testid=\"1\">1</CompositeItem>\n          <CompositeItem data-testid=\"2\">2</CompositeItem>\n        </CompositeRoot>,\n      );\n\n      const item1 = screen.getByTestId('1');\n\n      act(() => item1.focus());\n\n      expect(item1).toHaveFocus();\n\n      fireEvent.keyDown(item1, { key: 'ArrowDown', shiftKey: true });\n      await flushMicrotasks();\n      expect(item1).toHaveFocus();\n\n      fireEvent.keyDown(item1, { key: 'ArrowDown', ctrlKey: true });\n      await flushMicrotasks();\n      expect(item1).toHaveFocus();\n\n      fireEvent.keyDown(item1, { key: 'ArrowDown', altKey: true });\n      await flushMicrotasks();\n      expect(item1).toHaveFocus();\n\n      fireEvent.keyDown(item1, { key: 'ArrowDown', metaKey: true });\n      await flushMicrotasks();\n      expect(item1).toHaveFocus();\n    });\n\n    it('specifies allowed modifier keys that do not prevent arrow key navigation when pressed', async () => {\n      render(\n        <CompositeRoot modifierKeys={['Alt', 'Meta']}>\n          <CompositeItem data-testid=\"1\">1</CompositeItem>\n          <CompositeItem data-testid=\"2\">2</CompositeItem>\n          <CompositeItem data-testid=\"3\">3</CompositeItem>\n        </CompositeRoot>,\n      );\n\n      const item1 = screen.getByTestId('1');\n      const item2 = screen.getByTestId('2');\n      const item3 = screen.getByTestId('3');\n\n      act(() => item1.focus());\n\n      expect(item1).toHaveFocus();\n\n      fireEvent.keyDown(item1, { key: 'ArrowDown', shiftKey: true });\n      await flushMicrotasks();\n      expect(item1).toHaveFocus();\n\n      fireEvent.keyDown(item1, { key: 'ArrowDown', ctrlKey: true });\n      await flushMicrotasks();\n      expect(item1).toHaveFocus();\n\n      fireEvent.keyDown(item1, { key: 'ArrowDown', altKey: true });\n      await flushMicrotasks();\n      expect(item2).toHaveFocus();\n\n      fireEvent.keyDown(item2, { key: 'ArrowDown', metaKey: true });\n      await flushMicrotasks();\n      expect(item3).toHaveFocus();\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/composite/root/CompositeRoot.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { CompositeList, type CompositeMetadata } from '../list/CompositeList';\nimport { useCompositeRoot } from './useCompositeRoot';\nimport { CompositeRootContext } from './CompositeRootContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport type { BaseUIComponentProps, BaseUIEvent } from '../../utils/types';\nimport type { Dimensions, ModifierKey } from '../composite';\nimport { useDirection } from '../../direction-provider/DirectionContext';\nimport { EMPTY_ARRAY, EMPTY_OBJECT } from '../../utils/constants';\nimport { StateAttributesMapping } from '../../utils/getStateAttributesProps';\n\n/**\n * @internal\n */\nexport function CompositeRoot<Metadata extends {}, State extends Record<string, any>>(\n  componentProps: CompositeRoot.Props<Metadata, State>,\n) {\n  const {\n    render,\n    className,\n    refs = EMPTY_ARRAY as React.Ref<Element>[],\n    props = EMPTY_ARRAY,\n    state = EMPTY_OBJECT as State,\n    stateAttributesMapping,\n    highlightedIndex: highlightedIndexProp,\n    onHighlightedIndexChange: onHighlightedIndexChangeProp,\n    orientation,\n    dense,\n    itemSizes,\n    loopFocus,\n    onLoop,\n    cols,\n    enableHomeAndEndKeys,\n    onMapChange: onMapChangeProp,\n    stopEventPropagation = true,\n    rootRef,\n    disabledIndices,\n    modifierKeys,\n    highlightItemOnHover = false,\n    tag = 'div',\n    ...elementProps\n  } = componentProps;\n\n  const direction = useDirection();\n\n  const {\n    props: defaultProps,\n    highlightedIndex,\n    onHighlightedIndexChange,\n    elementsRef,\n    onMapChange: onMapChangeUnwrapped,\n    relayKeyboardEvent,\n  } = useCompositeRoot({\n    itemSizes,\n    cols,\n    loopFocus,\n    onLoop,\n    dense,\n    orientation,\n    highlightedIndex: highlightedIndexProp,\n    onHighlightedIndexChange: onHighlightedIndexChangeProp,\n    rootRef,\n    stopEventPropagation,\n    enableHomeAndEndKeys,\n    direction,\n    disabledIndices,\n    modifierKeys,\n  });\n\n  const element = useRenderElement(tag, componentProps, {\n    state,\n    ref: refs,\n    props: [defaultProps, ...props, elementProps],\n    stateAttributesMapping,\n  });\n\n  const contextValue: CompositeRootContext = React.useMemo(\n    () => ({\n      highlightedIndex,\n      onHighlightedIndexChange,\n      highlightItemOnHover,\n      relayKeyboardEvent,\n    }),\n    [highlightedIndex, onHighlightedIndexChange, highlightItemOnHover, relayKeyboardEvent],\n  );\n\n  return (\n    <CompositeRootContext.Provider value={contextValue}>\n      <CompositeList<Metadata>\n        elementsRef={elementsRef}\n        onMapChange={(newMap) => {\n          onMapChangeProp?.(newMap);\n          onMapChangeUnwrapped(newMap);\n        }}\n      >\n        {element}\n      </CompositeList>\n    </CompositeRootContext.Provider>\n  );\n}\n\nexport interface CompositeRootState {}\n\nexport interface CompositeRootProps<Metadata, State extends Record<string, any>> extends Pick<\n  BaseUIComponentProps<'div', State>,\n  'render' | 'className' | 'children'\n> {\n  props?: Array<Record<string, any> | (() => Record<string, any>)> | undefined;\n  state?: State | undefined;\n  stateAttributesMapping?: StateAttributesMapping<State> | undefined;\n  refs?: React.Ref<HTMLElement | null>[] | undefined;\n  tag?: keyof React.JSX.IntrinsicElements | undefined;\n  orientation?: 'horizontal' | 'vertical' | 'both' | undefined;\n  cols?: number | undefined;\n  loopFocus?: boolean | undefined;\n  onLoop?:\n    | ((\n        event: React.KeyboardEvent,\n        prevIndex: number,\n        nextIndex: number,\n        elementsRef: React.RefObject<(HTMLDivElement | null)[]>,\n      ) => number)\n    | undefined;\n  highlightedIndex?: number | undefined;\n  onHighlightedIndexChange?: ((index: number) => void) | undefined;\n  itemSizes?: Dimensions[] | undefined;\n  dense?: boolean | undefined;\n  enableHomeAndEndKeys?: boolean | undefined;\n  onMapChange?: ((newMap: Map<Node, CompositeMetadata<Metadata> | null>) => void) | undefined;\n  onKeyDown?: ((event: BaseUIEvent<React.KeyboardEvent>) => void) | undefined;\n  stopEventPropagation?: boolean | undefined;\n  rootRef?: React.RefObject<HTMLElement | null> | undefined;\n  disabledIndices?: number[] | undefined;\n  modifierKeys?: ModifierKey[] | undefined;\n  highlightItemOnHover?: boolean | undefined;\n}\n\nexport namespace CompositeRoot {\n  export type State = CompositeRootState;\n  export type Props<Metadata, TState extends Record<string, any>> = CompositeRootProps<\n    Metadata,\n    TState\n  >;\n}\n"
  },
  {
    "path": "packages/react/src/composite/root/CompositeRootContext.ts",
    "content": "'use client';\nimport * as React from 'react';\n\nexport interface CompositeRootContext {\n  highlightedIndex: number;\n  onHighlightedIndexChange: (index: number, shouldScrollIntoView?: boolean) => void;\n  highlightItemOnHover: boolean;\n  /**\n   * Makes it possible to control composite components using events that don't originate from their children.\n   * For example, a Menubar with detached triggers may define its Menu.Root outside of CompositeRoot.\n   * Keyboard events that occur within this menu won't normally be captured by the CompositeRoot,\n   * so they need to be forwarded manually using this function.\n   */\n  relayKeyboardEvent: (event: React.KeyboardEvent<any>) => void;\n}\n\nexport const CompositeRootContext = React.createContext<CompositeRootContext | undefined>(\n  undefined,\n);\n\nexport function useCompositeRootContext(optional: true): CompositeRootContext | undefined;\nexport function useCompositeRootContext(optional?: false): CompositeRootContext;\nexport function useCompositeRootContext(optional = false) {\n  const context = React.useContext(CompositeRootContext);\n  if (context === undefined && !optional) {\n    throw new Error(\n      'Base UI: CompositeRootContext is missing. Composite parts must be placed within <Composite.Root>.',\n    );\n  }\n\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/composite/root/useCompositeRoot.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { isElementDisabled } from '@base-ui/utils/isElementDisabled';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useMergedRefs } from '@base-ui/utils/useMergedRefs';\nimport type { TextDirection } from '../../direction-provider/DirectionContext';\nimport {\n  ALL_KEYS,\n  ARROW_DOWN,\n  ARROW_KEYS,\n  ARROW_LEFT,\n  ARROW_RIGHT,\n  ARROW_UP,\n  END,\n  HOME,\n  HORIZONTAL_KEYS,\n  HORIZONTAL_KEYS_WITH_EXTRA_KEYS,\n  MODIFIER_KEYS,\n  VERTICAL_KEYS,\n  VERTICAL_KEYS_WITH_EXTRA_KEYS,\n  createGridCellMap,\n  findNonDisabledListIndex,\n  getGridCellIndexOfCorner,\n  getGridCellIndices,\n  getGridNavigatedIndex,\n  getMaxListIndex,\n  getMinListIndex,\n  isListIndexDisabled,\n  isIndexOutOfListBounds,\n  isNativeInput,\n  scrollIntoViewIfNeeded,\n  type Dimensions,\n  type ModifierKey,\n} from '../composite';\nimport { ACTIVE_COMPOSITE_ITEM } from '../constants';\nimport { CompositeMetadata } from '../list/CompositeList';\nimport { HTMLProps } from '../../utils/types';\n\nexport interface UseCompositeRootParameters {\n  orientation?: 'horizontal' | 'vertical' | 'both' | undefined;\n  cols?: number | undefined;\n  loopFocus?: boolean | undefined;\n  onLoop?:\n    | ((\n        event: React.KeyboardEvent,\n        prevIndex: number,\n        nextIndex: number,\n        elementsRef: React.RefObject<(HTMLDivElement | null)[]>,\n      ) => number)\n    | undefined;\n  highlightedIndex?: number | undefined;\n  onHighlightedIndexChange?: ((index: number) => void) | undefined;\n  dense?: boolean | undefined;\n  direction: TextDirection;\n  itemSizes?: Array<Dimensions> | undefined;\n  rootRef?: React.Ref<Element> | undefined;\n  /**\n   * When `true`, pressing the Home key moves focus to the first item,\n   * and pressing the End key moves focus to the last item.\n   * @default false\n   */\n  enableHomeAndEndKeys?: boolean | undefined;\n  /**\n   * When `true`, keypress events on Composite's navigation keys\n   * be stopped with event.stopPropagation().\n   * @default false\n   */\n  stopEventPropagation?: boolean | undefined;\n  /**\n   * Array of item indices to be considered disabled.\n   * Used for composite items that are focusable when disabled.\n   */\n  disabledIndices?: number[] | undefined;\n  /**\n   * Array of [modifier key values](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values#modifier_keys) that should allow normal keyboard actions\n   * when pressed. By default, all modifier keys prevent normal actions.\n   * @default []\n   */\n  modifierKeys?: ModifierKey[] | undefined;\n}\n\nconst EMPTY_ARRAY: never[] = [];\n\nexport function useCompositeRoot(params: UseCompositeRootParameters) {\n  const {\n    itemSizes,\n    cols = 1,\n    loopFocus = true,\n    onLoop,\n    dense = false,\n    orientation = 'both',\n    direction,\n    highlightedIndex: externalHighlightedIndex,\n    onHighlightedIndexChange: externalSetHighlightedIndex,\n    rootRef: externalRef,\n    enableHomeAndEndKeys = false,\n    stopEventPropagation = false,\n    disabledIndices,\n    modifierKeys = EMPTY_ARRAY,\n  } = params;\n\n  const [internalHighlightedIndex, internalSetHighlightedIndex] = React.useState(0);\n\n  const isGrid = cols > 1;\n\n  const rootRef = React.useRef<HTMLElement | null>(null);\n  const mergedRef = useMergedRefs(rootRef, externalRef);\n\n  const elementsRef = React.useRef<Array<HTMLDivElement | null>>([]);\n  const hasSetDefaultIndexRef = React.useRef(false);\n\n  const highlightedIndex = externalHighlightedIndex ?? internalHighlightedIndex;\n  const onHighlightedIndexChange = useStableCallback((index, shouldScrollIntoView = false) => {\n    (externalSetHighlightedIndex ?? internalSetHighlightedIndex)(index);\n    if (shouldScrollIntoView) {\n      const newActiveItem = elementsRef.current[index];\n      scrollIntoViewIfNeeded(rootRef.current, newActiveItem, direction, orientation);\n    }\n  });\n\n  const onMapChange = useStableCallback((map: Map<Element, CompositeMetadata<any>>) => {\n    if (map.size === 0 || hasSetDefaultIndexRef.current) {\n      return;\n    }\n    hasSetDefaultIndexRef.current = true;\n    const sortedElements = Array.from(map.keys());\n    const activeItem = (sortedElements.find((compositeElement) =>\n      compositeElement?.hasAttribute(ACTIVE_COMPOSITE_ITEM),\n    ) ?? null) as HTMLElement | null;\n    // Set the default highlighted index of an arbitrary composite item.\n    const activeIndex = activeItem ? sortedElements.indexOf(activeItem) : -1;\n\n    if (activeIndex !== -1) {\n      onHighlightedIndexChange(activeIndex);\n    }\n\n    scrollIntoViewIfNeeded(rootRef.current, activeItem, direction, orientation);\n  });\n\n  const wrappedOnLoop = useStableCallback(\n    (event: React.KeyboardEvent, prevIndex: number, nextIndex: number) => {\n      if (!onLoop) {\n        return nextIndex;\n      }\n      return onLoop?.(event, prevIndex, nextIndex, elementsRef);\n    },\n  );\n\n  const props = React.useMemo<HTMLProps>(\n    () => ({\n      'aria-orientation': orientation === 'both' ? undefined : orientation,\n      ref: mergedRef,\n      onFocus(event) {\n        const element = rootRef.current;\n        if (!element || !isNativeInput(event.target)) {\n          return;\n        }\n        event.target.setSelectionRange(0, event.target.value.length ?? 0);\n      },\n      onKeyDown(event) {\n        const RELEVANT_KEYS = enableHomeAndEndKeys ? ALL_KEYS : ARROW_KEYS;\n        if (!RELEVANT_KEYS.has(event.key)) {\n          return;\n        }\n\n        if (isModifierKeySet(event, modifierKeys)) {\n          return;\n        }\n\n        const element = rootRef.current;\n        if (!element) {\n          return;\n        }\n        const isRtl = direction === 'rtl';\n\n        const horizontalForwardKey = isRtl ? ARROW_LEFT : ARROW_RIGHT;\n        const forwardKey = {\n          horizontal: horizontalForwardKey,\n          vertical: ARROW_DOWN,\n          both: horizontalForwardKey,\n        }[orientation];\n        const horizontalBackwardKey = isRtl ? ARROW_RIGHT : ARROW_LEFT;\n        const backwardKey = {\n          horizontal: horizontalBackwardKey,\n          vertical: ARROW_UP,\n          both: horizontalBackwardKey,\n        }[orientation];\n\n        if (isNativeInput(event.target) && !isElementDisabled(event.target)) {\n          const selectionStart = event.target.selectionStart;\n          const selectionEnd = event.target.selectionEnd;\n          const textContent = event.target.value ?? '';\n          // return to native textbox behavior when\n          // 1 - Shift is held to make a text selection, or if there already is a text selection\n          if (selectionStart == null || event.shiftKey || selectionStart !== selectionEnd) {\n            return;\n          }\n          // 2 - arrow-ing forward and not in the last position of the text\n          if (event.key !== backwardKey && selectionStart < textContent.length) {\n            return;\n          }\n          // 3 -arrow-ing backward and not in the first position of the text\n          if (event.key !== forwardKey && selectionStart > 0) {\n            return;\n          }\n        }\n\n        let nextIndex = highlightedIndex;\n        const minIndex = getMinListIndex(elementsRef, disabledIndices);\n        const maxIndex = getMaxListIndex(elementsRef, disabledIndices);\n\n        if (isGrid) {\n          const sizes =\n            itemSizes ||\n            Array.from({ length: elementsRef.current.length }, () => ({\n              width: 1,\n              height: 1,\n            }));\n          // To calculate movements on the grid, we use hypothetical cell indices\n          // as if every item was 1x1, then convert back to real indices.\n          const cellMap = createGridCellMap(sizes, cols, dense);\n          const minGridIndex = cellMap.findIndex(\n            (index) =>\n              index != null && !isListIndexDisabled(elementsRef.current, index, disabledIndices),\n          );\n          // last enabled index\n          const maxGridIndex = cellMap.reduce(\n            (foundIndex: number, index, cellIndex) =>\n              index != null && !isListIndexDisabled(elementsRef.current, index, disabledIndices)\n                ? cellIndex\n                : foundIndex,\n            -1,\n          );\n\n          nextIndex = cellMap[\n            getGridNavigatedIndex(\n              cellMap.map((itemIndex) =>\n                itemIndex != null ? elementsRef.current[itemIndex] : null,\n              ),\n              {\n                event,\n                orientation,\n                loopFocus,\n                onLoop: wrappedOnLoop,\n                cols,\n                // treat undefined (empty grid spaces) as disabled indices so we\n                // don't end up in them\n                disabledIndices: getGridCellIndices(\n                  [\n                    ...(disabledIndices ||\n                      elementsRef.current.map((_, index) =>\n                        isListIndexDisabled(elementsRef.current, index) ? index : undefined,\n                      )),\n                    undefined,\n                  ],\n                  cellMap,\n                ),\n                minIndex: minGridIndex,\n                maxIndex: maxGridIndex,\n                prevIndex: getGridCellIndexOfCorner(\n                  highlightedIndex > maxIndex ? minIndex : highlightedIndex,\n                  sizes,\n                  cellMap,\n                  cols,\n                  // use a corner matching the edge closest to the direction we're\n                  // moving in so we don't end up in the same item. Prefer\n                  // top/left over bottom/right.\n                  // eslint-disable-next-line no-nested-ternary\n                  event.key === ARROW_DOWN ? 'bl' : event.key === ARROW_RIGHT ? 'tr' : 'tl',\n                ),\n                rtl: isRtl,\n              },\n            )\n          ] as number; // navigated cell will never be nullish\n        }\n\n        const forwardKeys = {\n          horizontal: [horizontalForwardKey],\n          vertical: [ARROW_DOWN],\n          both: [horizontalForwardKey, ARROW_DOWN],\n        }[orientation];\n\n        const backwardKeys = {\n          horizontal: [horizontalBackwardKey],\n          vertical: [ARROW_UP],\n          both: [horizontalBackwardKey, ARROW_UP],\n        }[orientation];\n\n        const preventedKeys = isGrid\n          ? RELEVANT_KEYS\n          : {\n              horizontal: enableHomeAndEndKeys ? HORIZONTAL_KEYS_WITH_EXTRA_KEYS : HORIZONTAL_KEYS,\n              vertical: enableHomeAndEndKeys ? VERTICAL_KEYS_WITH_EXTRA_KEYS : VERTICAL_KEYS,\n              both: RELEVANT_KEYS,\n            }[orientation];\n\n        if (enableHomeAndEndKeys) {\n          if (event.key === HOME) {\n            nextIndex = minIndex;\n          } else if (event.key === END) {\n            nextIndex = maxIndex;\n          }\n        }\n\n        if (\n          nextIndex === highlightedIndex &&\n          (forwardKeys.includes(event.key) || backwardKeys.includes(event.key))\n        ) {\n          if (loopFocus && nextIndex === maxIndex && forwardKeys.includes(event.key)) {\n            nextIndex = minIndex;\n            if (onLoop) {\n              nextIndex = onLoop(event, highlightedIndex, nextIndex, elementsRef);\n            }\n          } else if (loopFocus && nextIndex === minIndex && backwardKeys.includes(event.key)) {\n            nextIndex = maxIndex;\n            if (onLoop) {\n              nextIndex = onLoop(event, highlightedIndex, nextIndex, elementsRef);\n            }\n          } else {\n            nextIndex = findNonDisabledListIndex(elementsRef.current, {\n              startingIndex: nextIndex,\n              decrement: backwardKeys.includes(event.key),\n              disabledIndices,\n            });\n          }\n        }\n\n        if (\n          nextIndex !== highlightedIndex &&\n          !isIndexOutOfListBounds(elementsRef.current, nextIndex)\n        ) {\n          if (stopEventPropagation) {\n            event.stopPropagation();\n          }\n\n          if (preventedKeys.has(event.key)) {\n            event.preventDefault();\n          }\n          onHighlightedIndexChange(nextIndex, true);\n\n          // Wait for FocusManager `returnFocus` to execute.\n          queueMicrotask(() => {\n            elementsRef.current[nextIndex]?.focus();\n          });\n        }\n      },\n    }),\n    [\n      cols,\n      dense,\n      direction,\n      disabledIndices,\n      elementsRef,\n      enableHomeAndEndKeys,\n      highlightedIndex,\n      isGrid,\n      itemSizes,\n      loopFocus,\n      onLoop,\n      wrappedOnLoop,\n      mergedRef,\n      modifierKeys,\n      onHighlightedIndexChange,\n      orientation,\n      stopEventPropagation,\n    ],\n  );\n\n  return React.useMemo(\n    () => ({\n      props,\n      highlightedIndex,\n      onHighlightedIndexChange,\n      elementsRef,\n      disabledIndices,\n      onMapChange,\n      relayKeyboardEvent: props.onKeyDown!,\n    }),\n    [props, highlightedIndex, onHighlightedIndexChange, elementsRef, disabledIndices, onMapChange],\n  );\n}\n\nfunction isModifierKeySet(event: React.KeyboardEvent, ignoredModifierKeys: ModifierKey[]) {\n  for (const key of MODIFIER_KEYS.values()) {\n    if (ignoredModifierKeys.includes(key)) {\n      continue;\n    }\n    if (event.getModifierState(key)) {\n      return true;\n    }\n  }\n  return false;\n}\n"
  },
  {
    "path": "packages/react/src/context-menu/index.parts.ts",
    "content": "export { ContextMenuRoot as Root } from './root/ContextMenuRoot';\nexport { ContextMenuTrigger as Trigger } from './trigger/ContextMenuTrigger';\n\nexport { MenuBackdrop as Backdrop } from '../menu/backdrop/MenuBackdrop';\nexport { MenuPortal as Portal } from '../menu/portal/MenuPortal';\nexport { MenuPositioner as Positioner } from '../menu/positioner/MenuPositioner';\nexport { MenuPopup as Popup } from '../menu/popup/MenuPopup';\nexport { MenuArrow as Arrow } from '../menu/arrow/MenuArrow';\nexport { MenuGroup as Group } from '../menu/group/MenuGroup';\nexport { MenuGroupLabel as GroupLabel } from '../menu/group-label/MenuGroupLabel';\nexport { MenuItem as Item } from '../menu/item/MenuItem';\nexport { MenuCheckboxItem as CheckboxItem } from '../menu/checkbox-item/MenuCheckboxItem';\nexport { MenuCheckboxItemIndicator as CheckboxItemIndicator } from '../menu/checkbox-item-indicator/MenuCheckboxItemIndicator';\nexport { MenuLinkItem as LinkItem } from '../menu/link-item/MenuLinkItem';\nexport { MenuRadioGroup as RadioGroup } from '../menu/radio-group/MenuRadioGroup';\nexport { MenuRadioItem as RadioItem } from '../menu/radio-item/MenuRadioItem';\nexport { MenuRadioItemIndicator as RadioItemIndicator } from '../menu/radio-item-indicator/MenuRadioItemIndicator';\nexport { MenuSubmenuRoot as SubmenuRoot } from '../menu/submenu-root/MenuSubmenuRoot';\nexport { MenuSubmenuTrigger as SubmenuTrigger } from '../menu/submenu-trigger/MenuSubmenuTrigger';\n\nexport { Separator } from '../separator/Separator';\n"
  },
  {
    "path": "packages/react/src/context-menu/index.ts",
    "content": "export * as ContextMenu from './index.parts';\n\nexport type * from './root/ContextMenuRoot';\nexport type * from './trigger/ContextMenuTrigger';\n\nexport type {\n  MenuBackdropProps as ContextMenuBackdropProps,\n  MenuBackdropState as ContextMenuBackdropState,\n} from '../menu/backdrop/MenuBackdrop';\nexport type { MenuPortalProps as ContextMenuPortalProps } from '../menu/portal/MenuPortal';\nexport type {\n  MenuPositionerProps as ContextMenuPositionerProps,\n  MenuPositionerState as ContextMenuPositionerState,\n} from '../menu/positioner/MenuPositioner';\nexport type {\n  MenuPopupProps as ContextMenuPopupProps,\n  MenuPopupState as ContextMenuPopupState,\n} from '../menu/popup/MenuPopup';\nexport type {\n  MenuArrowProps as ContextMenuArrowProps,\n  MenuArrowState as ContextMenuArrowState,\n} from '../menu/arrow/MenuArrow';\nexport type {\n  MenuGroupProps as ContextMenuGroupProps,\n  MenuGroupState as ContextMenuGroupState,\n} from '../menu/group/MenuGroup';\nexport type {\n  MenuGroupLabelProps as ContextMenuGroupLabelProps,\n  MenuGroupLabelState as ContextMenuGroupLabelState,\n} from '../menu/group-label/MenuGroupLabel';\nexport type {\n  MenuItemProps as ContextMenuItemProps,\n  MenuItemState as ContextMenuItemState,\n} from '../menu/item/MenuItem';\nexport type {\n  MenuLinkItemProps as ContextMenuLinkItemProps,\n  MenuLinkItemState as ContextMenuLinkItemState,\n} from '../menu/link-item/MenuLinkItem';\nexport type {\n  MenuCheckboxItemProps as ContextMenuCheckboxItemProps,\n  MenuCheckboxItemState as ContextMenuCheckboxItemState,\n} from '../menu/checkbox-item/MenuCheckboxItem';\nexport type {\n  MenuCheckboxItemIndicatorProps as ContextMenuCheckboxItemIndicatorProps,\n  MenuCheckboxItemIndicatorState as ContextMenuCheckboxItemIndicatorState,\n} from '../menu/checkbox-item-indicator/MenuCheckboxItemIndicator';\nexport type {\n  MenuRadioGroupProps as ContextMenuRadioGroupProps,\n  MenuRadioGroupState as ContextMenuRadioGroupState,\n} from '../menu/radio-group/MenuRadioGroup';\nexport type {\n  MenuRadioItemProps as ContextMenuRadioItemProps,\n  MenuRadioItemState as ContextMenuRadioItemState,\n} from '../menu/radio-item/MenuRadioItem';\nexport type {\n  MenuRadioItemIndicatorProps as ContextMenuRadioItemIndicatorProps,\n  MenuRadioItemIndicatorState as ContextMenuRadioItemIndicatorState,\n} from '../menu/radio-item-indicator/MenuRadioItemIndicator';\nexport type {\n  MenuSubmenuRootProps as ContextMenuSubmenuRootProps,\n  MenuSubmenuRootState as ContextMenuSubmenuRootState,\n} from '../menu/submenu-root/MenuSubmenuRoot';\nexport type {\n  MenuSubmenuTriggerProps as ContextMenuSubmenuTriggerProps,\n  MenuSubmenuTriggerState as ContextMenuSubmenuTriggerState,\n} from '../menu/submenu-trigger/MenuSubmenuTrigger';\n"
  },
  {
    "path": "packages/react/src/context-menu/root/ContextMenuRoot.non-mac.test.tsx",
    "content": "import { vi, expect } from 'vitest';\nimport {\n  fireEvent,\n  ignoreActWarnings,\n  reactMajor,\n  screen,\n  waitFor,\n} from '@mui/internal-test-utils';\nimport { ContextMenu } from '@base-ui/react/context-menu';\nimport { createRenderer } from '#test-utils';\n\nvi.mock('@base-ui/utils/detectBrowser', async () => {\n  const actual = await vi.importActual<typeof import('@base-ui/utils/detectBrowser')>(\n    '@base-ui/utils/detectBrowser',\n  );\n\n  return {\n    ...actual,\n    isMac: false,\n  };\n});\n\ndescribe('<ContextMenu.Root /> (non-Mac)', () => {\n  beforeEach(() => {\n    globalThis.BASE_UI_ANIMATIONS_DISABLED = true;\n  });\n\n  const { render, clock } = createRenderer({\n    clockOptions: {\n      shouldAdvanceTime: true,\n    },\n  });\n\n  describe('interactions', () => {\n    clock.withFakeTimers();\n\n    it('ignores context menu mouseup on non-Mac platforms', async () => {\n      if (reactMajor <= 18) {\n        ignoreActWarnings();\n      }\n\n      const onOpenChange = vi.fn();\n\n      await render(\n        <ContextMenu.Root onOpenChange={onOpenChange}>\n          <ContextMenu.Trigger data-testid=\"context-trigger\">Surface</ContextMenu.Trigger>\n          <ContextMenu.Portal>\n            <ContextMenu.Positioner alignOffset={0}>\n              <ContextMenu.Popup data-testid=\"context-popup\">\n                <ContextMenu.Item data-testid=\"context-item\">Action</ContextMenu.Item>\n              </ContextMenu.Popup>\n            </ContextMenu.Positioner>\n          </ContextMenu.Portal>\n        </ContextMenu.Root>,\n      );\n\n      const trigger = screen.getByTestId('context-trigger');\n\n      fireEvent.contextMenu(trigger, { clientX: 12, clientY: 12, button: 2 });\n\n      await screen.findByTestId('context-popup');\n      const item = screen.getByTestId('context-item');\n\n      fireEvent.pointerMove(document.body, { clientX: 24, clientY: 24 });\n      fireEvent.mouseUp(item, { button: 2, clientX: 24, clientY: 24 });\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('context-popup')).not.toBe(null);\n      });\n\n      expect(onOpenChange.mock.calls.length).toBe(1);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/context-menu/root/ContextMenuRoot.test.tsx",
    "content": "import { vi, expect } from 'vitest';\nimport {\n  fireEvent,\n  flushMicrotasks,\n  ignoreActWarnings,\n  reactMajor,\n  screen,\n  waitFor,\n} from '@mui/internal-test-utils';\nimport { ContextMenu } from '@base-ui/react/context-menu';\nimport { createRenderer, isJSDOM } from '#test-utils';\nimport { REASONS } from '../../utils/reasons';\n\nvi.mock('@base-ui/utils/detectBrowser', async () => {\n  const actual = await vi.importActual<typeof import('@base-ui/utils/detectBrowser')>(\n    '@base-ui/utils/detectBrowser',\n  );\n\n  return {\n    ...actual,\n    isMac: true,\n  };\n});\n\ndescribe('<ContextMenu.Root />', () => {\n  beforeEach(() => {\n    globalThis.BASE_UI_ANIMATIONS_DISABLED = true;\n  });\n\n  const { render, clock } = createRenderer({\n    clockOptions: {\n      shouldAdvanceTime: true,\n    },\n  });\n\n  describe('interactions', () => {\n    clock.withFakeTimers();\n\n    it('closes nested submenus when releasing the context menu pointer over an item', async () => {\n      const rootOnOpenChange = vi.fn();\n      const submenuOnOpenChange = vi.fn();\n\n      const { user } = await render(\n        <ContextMenu.Root onOpenChange={rootOnOpenChange}>\n          <ContextMenu.Trigger data-testid=\"context-trigger\">Surface</ContextMenu.Trigger>\n          <ContextMenu.Portal>\n            <ContextMenu.Positioner>\n              <ContextMenu.Popup data-testid=\"context-root-popup\">\n                <ContextMenu.SubmenuRoot defaultOpen onOpenChange={submenuOnOpenChange}>\n                  <ContextMenu.SubmenuTrigger delay={1} data-testid=\"context-submenu-trigger\">\n                    More options\n                  </ContextMenu.SubmenuTrigger>\n                  <ContextMenu.Portal>\n                    <ContextMenu.Positioner>\n                      <ContextMenu.Popup data-testid=\"context-submenu-popup\">\n                        <ContextMenu.Item data-testid=\"context-submenu-item\">\n                          Deep action\n                        </ContextMenu.Item>\n                      </ContextMenu.Popup>\n                    </ContextMenu.Positioner>\n                  </ContextMenu.Portal>\n                </ContextMenu.SubmenuRoot>\n              </ContextMenu.Popup>\n            </ContextMenu.Positioner>\n          </ContextMenu.Portal>\n        </ContextMenu.Root>,\n      );\n\n      const trigger = screen.getByTestId('context-trigger');\n\n      fireEvent.contextMenu(trigger, { clientX: 10, clientY: 10, button: 2 });\n      await flushMicrotasks();\n\n      await screen.findByTestId('context-root-popup');\n\n      const submenuTrigger = screen.getByTestId('context-submenu-trigger');\n      await user.hover(submenuTrigger);\n\n      await screen.findByTestId('context-submenu-popup');\n\n      const submenuItem = screen.getByTestId('context-submenu-item');\n      fireEvent.mouseUp(submenuItem, { button: 2 });\n      await flushMicrotasks();\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('context-submenu-popup')).toBe(null);\n      });\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('context-root-popup')).toBe(null);\n      });\n\n      expect(submenuOnOpenChange.mock.lastCall?.[0]).toBe(false);\n      expect(submenuOnOpenChange.mock.lastCall?.[1].reason).toBe(REASONS.itemPress);\n      expect(rootOnOpenChange.mock.lastCall?.[0]).toBe(false);\n      expect(rootOnOpenChange.mock.lastCall?.[1].reason).toBe(REASONS.itemPress);\n    });\n\n    it('ignores mouseup directly under the cursor when the context menu spawns there', async () => {\n      if (reactMajor <= 18) {\n        ignoreActWarnings();\n      }\n\n      const onOpenChange = vi.fn();\n\n      await render(\n        <ContextMenu.Root onOpenChange={onOpenChange}>\n          <ContextMenu.Trigger data-testid=\"context-trigger\">Surface</ContextMenu.Trigger>\n          <ContextMenu.Portal>\n            <ContextMenu.Positioner alignOffset={0}>\n              <ContextMenu.Popup data-testid=\"context-popup\">\n                <ContextMenu.Item data-testid=\"context-item\">Action</ContextMenu.Item>\n              </ContextMenu.Popup>\n            </ContextMenu.Positioner>\n          </ContextMenu.Portal>\n        </ContextMenu.Root>,\n      );\n\n      const trigger = screen.getByTestId('context-trigger');\n\n      fireEvent.contextMenu(trigger, { clientX: 12, clientY: 12, button: 2 });\n\n      await screen.findByTestId('context-popup');\n      const item = screen.getByTestId('context-item');\n\n      fireEvent.mouseUp(item, { button: 2, clientX: 12, clientY: 12 });\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('context-popup')).not.toBe(null);\n      });\n\n      expect(onOpenChange.mock.calls.length).toBe(1);\n    });\n\n    it('ignores mouseup directly under the cursor when alignOffset is negative', async () => {\n      if (reactMajor <= 18) {\n        ignoreActWarnings();\n      }\n\n      const onOpenChange = vi.fn();\n\n      await render(\n        <ContextMenu.Root onOpenChange={onOpenChange}>\n          <ContextMenu.Trigger data-testid=\"context-trigger\">Surface</ContextMenu.Trigger>\n          <ContextMenu.Portal>\n            <ContextMenu.Positioner alignOffset={-5}>\n              <ContextMenu.Popup data-testid=\"context-popup\">\n                <ContextMenu.Item data-testid=\"context-item\">Action</ContextMenu.Item>\n              </ContextMenu.Popup>\n            </ContextMenu.Positioner>\n          </ContextMenu.Portal>\n        </ContextMenu.Root>,\n      );\n\n      const trigger = screen.getByTestId('context-trigger');\n\n      fireEvent.contextMenu(trigger, { clientX: 18, clientY: 18, button: 2 });\n\n      await screen.findByTestId('context-popup');\n      const item = screen.getByTestId('context-item');\n\n      fireEvent.mouseUp(item, { button: 2, clientX: 18, clientY: 18 });\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('context-popup')).not.toBe(null);\n      });\n\n      expect(onOpenChange.mock.calls.length).toBe(1);\n    });\n\n    it('allows mouseup after leaving the initial cursor point', async () => {\n      if (reactMajor <= 18) {\n        ignoreActWarnings();\n      }\n\n      const onOpenChange = vi.fn();\n\n      await render(\n        <ContextMenu.Root onOpenChange={onOpenChange}>\n          <ContextMenu.Trigger data-testid=\"context-trigger\">Surface</ContextMenu.Trigger>\n          <ContextMenu.Portal>\n            <ContextMenu.Positioner alignOffset={0}>\n              <ContextMenu.Popup data-testid=\"context-popup\">\n                <ContextMenu.Item data-testid=\"context-item\">Action</ContextMenu.Item>\n              </ContextMenu.Popup>\n            </ContextMenu.Positioner>\n          </ContextMenu.Portal>\n        </ContextMenu.Root>,\n      );\n\n      const trigger = screen.getByTestId('context-trigger');\n\n      fireEvent.contextMenu(trigger, { clientX: 20, clientY: 20, button: 2 });\n\n      await screen.findByTestId('context-popup');\n      const item = screen.getByTestId('context-item');\n\n      fireEvent.pointerMove(document.body, { clientX: 24, clientY: 24 });\n      fireEvent.mouseUp(item, { button: 2, clientX: 24, clientY: 24 });\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('context-popup')).toBe(null);\n      });\n\n      expect(onOpenChange.mock.lastCall?.[0]).toBe(false);\n    });\n\n    it('does not open when disabled', async () => {\n      const onOpenChange = vi.fn();\n\n      await render(\n        <ContextMenu.Root disabled onOpenChange={onOpenChange}>\n          <ContextMenu.Trigger data-testid=\"context-trigger\">Surface</ContextMenu.Trigger>\n          <ContextMenu.Portal>\n            <ContextMenu.Positioner>\n              <ContextMenu.Popup data-testid=\"context-popup\">\n                <ContextMenu.Item>Action</ContextMenu.Item>\n              </ContextMenu.Popup>\n            </ContextMenu.Positioner>\n          </ContextMenu.Portal>\n        </ContextMenu.Root>,\n      );\n\n      const trigger = screen.getByTestId('context-trigger');\n\n      fireEvent.contextMenu(trigger, { clientX: 10, clientY: 10, button: 2 });\n      await flushMicrotasks();\n\n      expect(screen.queryByTestId('context-popup')).toBe(null);\n      expect(onOpenChange.mock.calls.length).toBe(0);\n    });\n  });\n\n  describe.skipIf(isJSDOM)('prop: collisionAvoidance', () => {\n    const popupHeight = 100;\n    const popupWidth = 150;\n    const popupStyle = { width: popupWidth, height: popupHeight };\n\n    it('flips to the opposite side when side: flip is set and there is no space', async () => {\n      const viewportHeight = window.innerHeight;\n\n      await render(\n        <div style={{ position: 'fixed', bottom: 0, left: 0, right: 0, height: 50 }}>\n          <ContextMenu.Root open>\n            <ContextMenu.Trigger data-testid=\"context-trigger\">Surface</ContextMenu.Trigger>\n            <ContextMenu.Portal>\n              <ContextMenu.Positioner\n                data-testid=\"positioner\"\n                collisionAvoidance={{ side: 'flip' }}\n                // Anchor near the bottom of the viewport so there's no space below\n                anchor={{\n                  getBoundingClientRect: () =>\n                    DOMRect.fromRect({\n                      width: 0,\n                      height: 0,\n                      x: 100,\n                      y: viewportHeight - 20,\n                    }),\n                }}\n              >\n                <ContextMenu.Popup data-testid=\"context-popup\" style={popupStyle}>\n                  <ContextMenu.Item>Action 1</ContextMenu.Item>\n                  <ContextMenu.Item>Action 2</ContextMenu.Item>\n                  <ContextMenu.Item>Action 3</ContextMenu.Item>\n                </ContextMenu.Popup>\n              </ContextMenu.Positioner>\n            </ContextMenu.Portal>\n          </ContextMenu.Root>\n        </div>,\n      );\n\n      const positioner = screen.getByTestId('positioner');\n\n      await waitFor(() => {\n        // When collisionAvoidance={{ side: 'flip' }} is set and there's no space below,\n        // the menu should flip to the top\n        expect(positioner.getAttribute('data-side')).toBe('top');\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/context-menu/root/ContextMenuRoot.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useId } from '@base-ui/utils/useId';\nimport { ContextMenuRootContext } from './ContextMenuRootContext';\nimport { Menu } from '../../menu';\nimport { MenuRootContext } from '../../menu/root/MenuRootContext';\nimport type { BaseUIChangeEventDetails } from '../../types';\nimport type { MenuRoot } from '../../menu/root/MenuRoot';\n\n/**\n * A component that creates a context menu activated by right clicking or long pressing.\n * Doesn’t render its own HTML element.\n *\n * Documentation: [Base UI Context Menu](https://base-ui.com/react/components/context-menu)\n */\nexport function ContextMenuRoot(props: ContextMenuRoot.Props) {\n  const [anchor, setAnchor] = React.useState<ContextMenuRootContext['anchor']>({\n    getBoundingClientRect() {\n      return DOMRect.fromRect({ width: 0, height: 0, x: 0, y: 0 });\n    },\n  });\n\n  const backdropRef = React.useRef<HTMLDivElement | null>(null);\n  const internalBackdropRef = React.useRef<HTMLDivElement | null>(null);\n  const actionsRef: ContextMenuRootContext['actionsRef'] = React.useRef(null);\n  const positionerRef = React.useRef<HTMLElement | null>(null);\n  const allowMouseUpTriggerRef = React.useRef(true);\n  const initialCursorPointRef = React.useRef<{ x: number; y: number } | null>(null);\n  const id = useId();\n\n  const contextValue: ContextMenuRootContext = React.useMemo(\n    () => ({\n      anchor,\n      setAnchor,\n      actionsRef,\n      backdropRef,\n      internalBackdropRef,\n      positionerRef,\n      allowMouseUpTriggerRef,\n      initialCursorPointRef,\n      rootId: id,\n    }),\n    [anchor, id],\n  );\n\n  return (\n    <ContextMenuRootContext.Provider value={contextValue}>\n      <MenuRootContext.Provider value={undefined}>\n        <Menu.Root {...props} />\n      </MenuRootContext.Provider>\n    </ContextMenuRootContext.Provider>\n  );\n}\n\nexport interface ContextMenuRootState {}\n\nexport interface ContextMenuRootProps extends Omit<\n  Menu.Root.Props,\n  'modal' | 'openOnHover' | 'delay' | 'closeDelay' | 'onOpenChange'\n> {\n  /**\n   * Event handler called when the menu is opened or closed.\n   */\n  onOpenChange?:\n    | ((open: boolean, eventDetails: ContextMenuRoot.ChangeEventDetails) => void)\n    | undefined;\n}\n\nexport type ContextMenuRootChangeEventReason = MenuRoot.ChangeEventReason;\nexport type ContextMenuRootChangeEventDetails =\n  BaseUIChangeEventDetails<ContextMenuRoot.ChangeEventReason>;\n\nexport namespace ContextMenuRoot {\n  export type State = ContextMenuRootState;\n  export type Props = ContextMenuRootProps;\n  export type ChangeEventReason = ContextMenuRootChangeEventReason;\n  export type ChangeEventDetails = ContextMenuRootChangeEventDetails;\n}\n"
  },
  {
    "path": "packages/react/src/context-menu/root/ContextMenuRootContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport type { ContextMenuRoot } from './ContextMenuRoot';\n\nexport interface ContextMenuRootContext {\n  anchor: { getBoundingClientRect: () => DOMRect };\n  setAnchor: React.Dispatch<React.SetStateAction<ContextMenuRootContext['anchor']>>;\n  backdropRef: React.RefObject<HTMLDivElement | null>;\n  internalBackdropRef: React.RefObject<HTMLDivElement | null>;\n  actionsRef: React.RefObject<{\n    setOpen: (nextOpen: boolean, eventDetails: ContextMenuRoot.ChangeEventDetails) => void;\n  } | null>;\n  positionerRef: React.RefObject<HTMLElement | null>;\n  allowMouseUpTriggerRef: React.RefObject<boolean>;\n  initialCursorPointRef: React.RefObject<{ x: number; y: number } | null>;\n  rootId: string | undefined;\n}\n\nexport const ContextMenuRootContext = React.createContext<ContextMenuRootContext | undefined>(\n  undefined,\n);\n\nexport function useContextMenuRootContext(optional: false): ContextMenuRootContext;\nexport function useContextMenuRootContext(optional?: true): ContextMenuRootContext | undefined;\nexport function useContextMenuRootContext(optional = true) {\n  const context = React.useContext(ContextMenuRootContext);\n  if (context === undefined && !optional) {\n    throw new Error(\n      'Base UI: ContextMenuRootContext is missing. ContextMenu parts must be placed within <ContextMenu.Root>.',\n    );\n  }\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/context-menu/trigger/ContextMenuTrigger.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport { fireEvent, flushMicrotasks, screen } from '@mui/internal-test-utils';\nimport { ContextMenu } from '@base-ui/react/context-menu';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\n\ndescribe('<ContextMenu.Trigger />', () => {\n  const { render, clock } = createRenderer({\n    clockOptions: {\n      shouldAdvanceTime: true,\n    },\n  });\n\n  clock.withFakeTimers();\n\n  describeConformance(<ContextMenu.Trigger />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(<ContextMenu.Root>{node}</ContextMenu.Root>);\n    },\n  }));\n\n  it('should open menu on right click (context menu event)', async () => {\n    await render(\n      <ContextMenu.Root>\n        <ContextMenu.Trigger data-testid=\"trigger\">Right click me</ContextMenu.Trigger>\n        <ContextMenu.Portal>\n          <ContextMenu.Positioner>\n            <ContextMenu.Popup />\n          </ContextMenu.Positioner>\n        </ContextMenu.Portal>\n      </ContextMenu.Root>,\n    );\n\n    const trigger = screen.getByTestId('trigger');\n    fireEvent.contextMenu(trigger);\n    await flushMicrotasks();\n\n    expect(screen.queryByRole('menu')).not.toBe(null);\n  });\n\n  it('adds open state attributes', async () => {\n    const { user } = await render(\n      <ContextMenu.Root defaultOpen>\n        <ContextMenu.Trigger data-testid=\"trigger\">Right click me</ContextMenu.Trigger>\n        <ContextMenu.Portal>\n          <ContextMenu.Positioner>\n            <ContextMenu.Popup />\n          </ContextMenu.Positioner>\n        </ContextMenu.Portal>\n      </ContextMenu.Root>,\n    );\n\n    const trigger = screen.getByTestId('trigger');\n    expect(trigger).toHaveAttribute('data-popup-open', '');\n\n    await user.keyboard('{Escape}');\n    expect(trigger).not.toHaveAttribute('data-popup-open');\n  });\n\n  it('should call onOpenChange when menu is opened via right click', async () => {\n    const onOpenChange = vi.fn();\n\n    await render(\n      <ContextMenu.Root onOpenChange={onOpenChange}>\n        <ContextMenu.Trigger data-testid=\"trigger\">Right click me</ContextMenu.Trigger>\n        <ContextMenu.Portal>\n          <ContextMenu.Positioner>\n            <ContextMenu.Popup />\n          </ContextMenu.Positioner>\n        </ContextMenu.Portal>\n      </ContextMenu.Root>,\n    );\n\n    const trigger = screen.getByTestId('trigger');\n    fireEvent.contextMenu(trigger);\n    await flushMicrotasks();\n\n    expect(onOpenChange.mock.lastCall?.[0]).toBe(true);\n  });\n\n  it('does not cancel opening menu on mouseup after mousedown outside before 500ms', async () => {\n    const onOpenChange = vi.fn();\n\n    await render(\n      <ContextMenu.Root onOpenChange={onOpenChange}>\n        <ContextMenu.Trigger data-testid=\"trigger\">Right click me</ContextMenu.Trigger>\n        <ContextMenu.Portal>\n          <ContextMenu.Positioner>\n            <ContextMenu.Popup />\n          </ContextMenu.Positioner>\n        </ContextMenu.Portal>\n      </ContextMenu.Root>,\n    );\n\n    const trigger = screen.getByTestId('trigger');\n    fireEvent.mouseDown(trigger);\n    fireEvent.contextMenu(trigger);\n\n    clock.tick(499);\n\n    expect(onOpenChange.mock.calls.length).toBe(1);\n    expect(onOpenChange.mock.lastCall?.[0]).toBe(true);\n\n    fireEvent.mouseUp(document.body);\n\n    clock.tick(1);\n\n    expect(onOpenChange.mock.calls.length).toBe(1);\n  });\n\n  it('cancels opening menu on mouseup after mousedown outside after 500ms', async () => {\n    const onOpenChange = vi.fn();\n\n    await render(\n      <ContextMenu.Root onOpenChange={onOpenChange}>\n        <ContextMenu.Trigger data-testid=\"trigger\">Right click me</ContextMenu.Trigger>\n        <ContextMenu.Portal>\n          <ContextMenu.Positioner>\n            <ContextMenu.Popup />\n          </ContextMenu.Positioner>\n        </ContextMenu.Portal>\n      </ContextMenu.Root>,\n    );\n\n    const trigger = screen.getByTestId('trigger');\n    fireEvent.mouseDown(trigger);\n    fireEvent.contextMenu(trigger);\n\n    clock.tick(501);\n\n    fireEvent.mouseUp(document.body);\n\n    expect(onOpenChange.mock.calls.length).toBe(2);\n    expect(onOpenChange.mock.lastCall?.[0]).toBe(false);\n  });\n\n  describe('prop: disabled', () => {\n    it('does not open on right-click when disabled', async () => {\n      const onOpenChange = vi.fn();\n\n      await render(\n        <ContextMenu.Root disabled onOpenChange={onOpenChange}>\n          <ContextMenu.Trigger data-testid=\"trigger\">Right click me</ContextMenu.Trigger>\n          <ContextMenu.Portal>\n            <ContextMenu.Positioner>\n              <ContextMenu.Popup data-testid=\"popup\" />\n            </ContextMenu.Positioner>\n          </ContextMenu.Portal>\n        </ContextMenu.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      fireEvent.contextMenu(trigger);\n      await flushMicrotasks();\n\n      expect(screen.queryByTestId('popup')).toBe(null);\n      expect(onOpenChange.mock.calls.length).toBe(0);\n    });\n\n    it('does not block the native context menu when disabled', async () => {\n      await render(\n        <ContextMenu.Root disabled>\n          <ContextMenu.Trigger data-testid=\"trigger\">Right click me</ContextMenu.Trigger>\n          <ContextMenu.Portal>\n            <ContextMenu.Positioner>\n              <ContextMenu.Popup data-testid=\"popup\" />\n            </ContextMenu.Positioner>\n          </ContextMenu.Portal>\n        </ContextMenu.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n\n      let defaultPrevented = false;\n      trigger.addEventListener(\n        'contextmenu',\n        (event) => {\n          defaultPrevented = event.defaultPrevented;\n        },\n        { capture: false },\n      );\n\n      fireEvent.contextMenu(trigger);\n      await flushMicrotasks();\n\n      expect(defaultPrevented).toBe(false);\n    });\n  });\n\n  describe.skipIf(isJSDOM)('long press', () => {\n    it('should open menu on long press on touchscreen devices', async () => {\n      await render(\n        <ContextMenu.Root>\n          <ContextMenu.Trigger data-testid=\"trigger\">Long press me</ContextMenu.Trigger>\n          <ContextMenu.Portal>\n            <ContextMenu.Positioner>\n              <ContextMenu.Popup />\n            </ContextMenu.Positioner>\n          </ContextMenu.Portal>\n        </ContextMenu.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n\n      const touchObj = new Touch({\n        identifier: 0,\n        target: trigger,\n        clientX: 100,\n        clientY: 100,\n      });\n\n      fireEvent.touchStart(trigger, {\n        touches: [touchObj],\n      });\n\n      clock.tick(500);\n\n      expect(screen.queryByRole('menu')).not.toBe(null);\n    });\n\n    it('should cancel long press when touch moves beyond threshold', async () => {\n      const onOpenChange = vi.fn();\n\n      await render(\n        <ContextMenu.Root onOpenChange={onOpenChange}>\n          <ContextMenu.Trigger data-testid=\"trigger\">Long press me</ContextMenu.Trigger>\n          <ContextMenu.Portal>\n            <ContextMenu.Positioner>\n              <ContextMenu.Popup />\n            </ContextMenu.Positioner>\n          </ContextMenu.Portal>\n        </ContextMenu.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n\n      const touchStartObj = new Touch({\n        identifier: 0,\n        target: trigger,\n        clientX: 100,\n        clientY: 100,\n      });\n\n      fireEvent.touchStart(trigger, {\n        touches: [touchStartObj],\n      });\n\n      // Simulate touch move (more than 10px movement)\n      // This should cancel the long press\n      const touchMoveObj = new Touch({\n        identifier: 0,\n        target: trigger,\n        clientX: 120,\n        clientY: 100,\n      });\n\n      fireEvent.touchMove(trigger, {\n        touches: [touchMoveObj],\n      });\n\n      clock.tick(500);\n\n      expect(screen.queryByRole('menu')).toBe(null);\n      expect(onOpenChange.mock.calls.length).toBe(0);\n    });\n\n    it('does not open on long press when disabled', async () => {\n      const onOpenChange = vi.fn();\n\n      await render(\n        <ContextMenu.Root disabled onOpenChange={onOpenChange}>\n          <ContextMenu.Trigger data-testid=\"trigger\">Long press me</ContextMenu.Trigger>\n          <ContextMenu.Portal>\n            <ContextMenu.Positioner>\n              <ContextMenu.Popup data-testid=\"popup\" />\n            </ContextMenu.Positioner>\n          </ContextMenu.Portal>\n        </ContextMenu.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n\n      const touchObj = new Touch({\n        identifier: 0,\n        target: trigger,\n        clientX: 100,\n        clientY: 100,\n      });\n\n      fireEvent.touchStart(trigger, {\n        touches: [touchObj],\n      });\n\n      clock.tick(500);\n\n      expect(screen.queryByTestId('popup')).toBe(null);\n      expect(onOpenChange.mock.calls.length).toBe(0);\n    });\n  });\n\n  it('should handle nested context menus correctly', async () => {\n    await render(\n      <ContextMenu.Root>\n        <ContextMenu.Trigger data-testid=\"outer-trigger\">\n          outer\n          <ContextMenu.Root>\n            <ContextMenu.Trigger>inner</ContextMenu.Trigger>\n            <ContextMenu.Portal>\n              <ContextMenu.Positioner>\n                <ContextMenu.Popup data-testid=\"inner-menu\" />\n              </ContextMenu.Positioner>\n            </ContextMenu.Portal>\n          </ContextMenu.Root>\n        </ContextMenu.Trigger>\n        <ContextMenu.Portal>\n          <ContextMenu.Positioner>\n            <ContextMenu.Popup data-testid=\"outer-menu\" />\n          </ContextMenu.Positioner>\n        </ContextMenu.Portal>\n      </ContextMenu.Root>,\n    );\n\n    const innerTrigger = screen.getByText('inner');\n    const outerTrigger = screen.getByText('outer');\n\n    fireEvent.contextMenu(innerTrigger);\n    await flushMicrotasks();\n\n    expect(screen.queryByTestId('inner-menu')).not.toBe(null);\n    expect(screen.queryByTestId('outer-menu')).toBe(null);\n\n    fireEvent.pointerDown(document.body, { pointerType: 'mouse' });\n    await flushMicrotasks();\n\n    expect(screen.queryByTestId('inner-menu')).toBe(null);\n\n    fireEvent.contextMenu(outerTrigger);\n    await flushMicrotasks();\n\n    expect(screen.queryByTestId('outer-menu')).not.toBe(null);\n    expect(screen.queryByTestId('inner-menu')).toBe(null);\n  });\n});\n"
  },
  {
    "path": "packages/react/src/context-menu/trigger/ContextMenuTrigger.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { ownerDocument } from '@base-ui/utils/owner';\nimport { useTimeout } from '@base-ui/utils/useTimeout';\nimport { contains, getTarget, stopEvent } from '../../floating-ui-react/utils';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useContextMenuRootContext } from '../root/ContextMenuRootContext';\nimport { useMenuRootContext } from '../../menu/root/MenuRootContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { pressableTriggerOpenStateMapping } from '../../utils/popupStateMapping';\nimport { REASONS } from '../../utils/reasons';\nimport { findRootOwnerId } from '../../menu/utils/findRootOwnerId';\n\nconst LONG_PRESS_DELAY = 500;\n\n/**\n * An area that opens the menu on right click or long press.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Context Menu](https://base-ui.com/react/components/context-menu)\n */\nexport const ContextMenuTrigger = React.forwardRef(function ContextMenuTrigger(\n  componentProps: ContextMenuTrigger.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, ...elementProps } = componentProps;\n\n  const {\n    setAnchor,\n    actionsRef,\n    internalBackdropRef,\n    backdropRef,\n    positionerRef,\n    allowMouseUpTriggerRef,\n    initialCursorPointRef,\n    rootId,\n  } = useContextMenuRootContext(false);\n\n  const { store } = useMenuRootContext(false);\n  const open = store.useState('open');\n  const disabled = store.useState('disabled');\n\n  const triggerRef = React.useRef<HTMLDivElement | null>(null);\n  const touchPositionRef = React.useRef<{ x: number; y: number } | null>(null);\n  const longPressTimeout = useTimeout();\n  const allowMouseUpTimeout = useTimeout();\n  const allowMouseUpRef = React.useRef(false);\n\n  function handleLongPress(x: number, y: number, event: MouseEvent | TouchEvent) {\n    const isTouchEvent = event.type.startsWith('touch');\n\n    initialCursorPointRef.current = { x, y };\n\n    setAnchor({\n      getBoundingClientRect() {\n        return DOMRect.fromRect({\n          width: isTouchEvent ? 10 : 0,\n          height: isTouchEvent ? 10 : 0,\n          x,\n          y,\n        });\n      },\n    });\n\n    allowMouseUpRef.current = false;\n    actionsRef.current?.setOpen(true, createChangeEventDetails(REASONS.triggerPress, event));\n\n    allowMouseUpTimeout.start(LONG_PRESS_DELAY, () => {\n      allowMouseUpRef.current = true;\n    });\n  }\n\n  function handleContextMenu(event: React.MouseEvent) {\n    if (disabled) {\n      return;\n    }\n    allowMouseUpTriggerRef.current = true;\n    stopEvent(event);\n    handleLongPress(event.clientX, event.clientY, event.nativeEvent);\n    const doc = ownerDocument(triggerRef.current);\n\n    doc.addEventListener(\n      'mouseup',\n      (mouseEvent: MouseEvent) => {\n        allowMouseUpTriggerRef.current = false;\n\n        if (!allowMouseUpRef.current) {\n          return;\n        }\n\n        allowMouseUpTimeout.clear();\n        allowMouseUpRef.current = false;\n\n        const mouseUpTarget = getTarget(mouseEvent) as Element | null;\n\n        if (contains(positionerRef.current, mouseUpTarget)) {\n          return;\n        }\n\n        if (rootId && mouseUpTarget && findRootOwnerId(mouseUpTarget) === rootId) {\n          return;\n        }\n\n        actionsRef.current?.setOpen(\n          false,\n          createChangeEventDetails(REASONS.cancelOpen, mouseEvent),\n        );\n      },\n      { once: true },\n    );\n  }\n\n  function handleTouchStart(event: React.TouchEvent) {\n    if (disabled) {\n      return;\n    }\n    allowMouseUpTriggerRef.current = false;\n    if (event.touches.length === 1) {\n      event.stopPropagation();\n      const touch = event.touches[0];\n      touchPositionRef.current = { x: touch.clientX, y: touch.clientY };\n      longPressTimeout.start(LONG_PRESS_DELAY, () => {\n        if (touchPositionRef.current) {\n          handleLongPress(\n            touchPositionRef.current.x,\n            touchPositionRef.current.y,\n            event.nativeEvent,\n          );\n        }\n      });\n    }\n  }\n\n  function handleTouchMove(event: React.TouchEvent) {\n    if (longPressTimeout.isStarted() && touchPositionRef.current && event.touches.length === 1) {\n      const touch = event.touches[0];\n      const moveThreshold = 10;\n\n      const deltaX = Math.abs(touch.clientX - touchPositionRef.current.x);\n      const deltaY = Math.abs(touch.clientY - touchPositionRef.current.y);\n\n      if (deltaX > moveThreshold || deltaY > moveThreshold) {\n        longPressTimeout.clear();\n      }\n    }\n  }\n\n  function handleTouchEnd() {\n    longPressTimeout.clear();\n    touchPositionRef.current = null;\n  }\n\n  React.useEffect(() => {\n    function handleDocumentContextMenu(event: MouseEvent) {\n      if (disabled) {\n        return;\n      }\n      const target = getTarget(event);\n      const targetElement = target as HTMLElement | null;\n      if (\n        contains(triggerRef.current, targetElement) ||\n        contains(internalBackdropRef.current, targetElement) ||\n        contains(backdropRef.current, targetElement)\n      ) {\n        event.preventDefault();\n      }\n    }\n\n    const doc = ownerDocument(triggerRef.current);\n    doc.addEventListener('contextmenu', handleDocumentContextMenu);\n    return () => {\n      doc.removeEventListener('contextmenu', handleDocumentContextMenu);\n    };\n  }, [backdropRef, disabled, internalBackdropRef]);\n\n  const state: ContextMenuTriggerState = {\n    open,\n  };\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: [triggerRef, forwardedRef],\n    props: [\n      {\n        onContextMenu: handleContextMenu,\n        onTouchStart: handleTouchStart,\n        onTouchMove: handleTouchMove,\n        onTouchEnd: handleTouchEnd,\n        onTouchCancel: handleTouchEnd,\n        style: {\n          WebkitTouchCallout: 'none',\n        },\n      },\n      elementProps,\n    ],\n    stateAttributesMapping: pressableTriggerOpenStateMapping,\n  });\n\n  return element;\n});\n\nexport interface ContextMenuTriggerState {\n  /**\n   * Whether the context menu is currently open.\n   */\n  open: boolean;\n}\n\nexport interface ContextMenuTriggerProps extends BaseUIComponentProps<\n  'div',\n  ContextMenuTriggerState\n> {}\n\nexport namespace ContextMenuTrigger {\n  export type State = ContextMenuTriggerState;\n  export type Props = ContextMenuTriggerProps;\n}\n"
  },
  {
    "path": "packages/react/src/context-menu/trigger/ContextMenuTriggerDataAttributes.ts",
    "content": "import { CommonTriggerDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum ContextMenuTriggerDataAttributes {\n  /**\n   * Present when the corresponding context menu is open.\n   */\n  popupOpen = CommonTriggerDataAttributes.popupOpen,\n  /**\n   * Present when the trigger is pressed.\n   */\n  pressed = CommonTriggerDataAttributes.pressed,\n}\n"
  },
  {
    "path": "packages/react/src/csp-provider/CSPContext.tsx",
    "content": "'use client';\nimport * as React from 'react';\n\nexport interface CSPContextValue {\n  nonce?: string | undefined;\n  disableStyleElements?: boolean | undefined;\n}\n\n/**\n * @internal\n */\nexport const CSPContext = React.createContext<CSPContextValue | undefined>(undefined);\n\nconst DEFAULT_CSP_CONTEXT_VALUE: CSPContextValue = {\n  disableStyleElements: false,\n};\n\n/**\n * @internal\n */\nexport function useCSPContext(): CSPContextValue {\n  return React.useContext(CSPContext) ?? DEFAULT_CSP_CONTEXT_VALUE;\n}\n"
  },
  {
    "path": "packages/react/src/csp-provider/CSPProvider.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { ScrollArea } from '@base-ui/react/scroll-area';\nimport { Select } from '@base-ui/react/select';\nimport { CSPProvider } from '@base-ui/react/csp-provider';\nimport { createRenderer } from '#test-utils';\n\nfunction queryDisableScrollbarStyle() {\n  const styles = Array.from(document.querySelectorAll('style'));\n  return (\n    styles.find((element) => element.textContent?.includes('.base-ui-disable-scrollbar')) ?? null\n  );\n}\n\ndescribe('<CSPProvider />', () => {\n  const { render } = createRenderer();\n\n  it('does not render inline style tags when disableStyleElements is true', async () => {\n    await render(\n      <CSPProvider disableStyleElements>\n        <ScrollArea.Root>\n          <ScrollArea.Viewport />\n        </ScrollArea.Root>\n      </CSPProvider>,\n    );\n\n    expect(queryDisableScrollbarStyle()).toBeNull();\n  });\n\n  it('does not render Select inline style tags when disableStyleElements is true', async () => {\n    await render(\n      <CSPProvider disableStyleElements>\n        <Select.Root defaultOpen>\n          <Select.Trigger>\n            <Select.Value />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"a\">a</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>\n      </CSPProvider>,\n    );\n\n    expect(queryDisableScrollbarStyle()).toBeNull();\n  });\n\n  it('applies nonce to inline style tags', async () => {\n    await render(\n      <CSPProvider nonce=\"test-nonce\">\n        <ScrollArea.Root>\n          <ScrollArea.Viewport />\n        </ScrollArea.Root>\n      </CSPProvider>,\n    );\n\n    const style = queryDisableScrollbarStyle();\n    expect(style).not.toBeNull();\n    expect(style).toHaveAttribute('nonce', 'test-nonce');\n  });\n\n  it('renders inline style tags by default', async () => {\n    await render(\n      <ScrollArea.Root>\n        <ScrollArea.Viewport />\n      </ScrollArea.Root>,\n    );\n\n    // Style already exists from previous test due to React 19's hoisting,\n    // but we can still verify it's present\n    const style = queryDisableScrollbarStyle();\n    expect(style).not.toBeNull();\n  });\n});\n"
  },
  {
    "path": "packages/react/src/csp-provider/CSPProvider.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { CSPContext, type CSPContextValue } from './CSPContext';\n\n/**\n * Provides a default Content Security Policy (CSP) configuration for Base UI components that\n * require inline `<style>` or `<script>` tags.\n *\n * Documentation: [Base UI CSP Provider](https://base-ui.com/react/utils/csp-provider)\n */\nexport function CSPProvider(props: CSPProvider.Props) {\n  const { children, nonce, disableStyleElements } = props;\n\n  const contextValue: CSPContextValue = React.useMemo(\n    () => ({\n      nonce,\n      disableStyleElements,\n    }),\n    [nonce, disableStyleElements],\n  );\n\n  return <CSPContext.Provider value={contextValue}>{children}</CSPContext.Provider>;\n}\n\nexport interface CSPProviderState {}\n\nexport interface CSPProviderProps {\n  children?: React.ReactNode;\n  /**\n   * The nonce value to apply to inline `<style>` and `<script>` tags.\n   */\n  nonce?: string | undefined;\n  /**\n   * Whether inline `<style>` elements created by Base UI components should not be rendered. Instead, components must specify the CSS styles via custom class names or other methods.\n   * @default false\n   */\n  disableStyleElements?: boolean | undefined;\n}\n\nexport namespace CSPProvider {\n  export type State = CSPProviderState;\n  export type Props = CSPProviderProps;\n}\n"
  },
  {
    "path": "packages/react/src/csp-provider/index.parts.ts",
    "content": "export { CSPProvider } from './CSPProvider';\n"
  },
  {
    "path": "packages/react/src/csp-provider/index.ts",
    "content": "export { CSPProvider } from './index.parts';\n\nexport type * from './CSPProvider';\n"
  },
  {
    "path": "packages/react/src/dialog/backdrop/DialogBackdrop.test.tsx",
    "content": "import { expect } from 'vitest';\nimport * as React from 'react';\nimport { Dialog } from '@base-ui/react/dialog';\nimport { createRenderer, describeConformance } from '#test-utils';\nimport { screen } from '@mui/internal-test-utils';\n\ndescribe('<Dialog.Backdrop />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Dialog.Backdrop />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render: (node) => {\n      return render(\n        <Dialog.Root open modal={false}>\n          {node}\n        </Dialog.Root>,\n      );\n    },\n  }));\n\n  it('has role=\"presentation\"', async () => {\n    await render(\n      <Dialog.Root open>\n        <Dialog.Backdrop data-testid=\"backdrop\" />\n      </Dialog.Root>,\n    );\n\n    expect(screen.getByTestId('backdrop')).toHaveAttribute('role', 'presentation');\n  });\n\n  describe('prop: forceRender', () => {\n    it('renders only the root backdrop by default', async () => {\n      function App() {\n        const [nestedOpen, setNestedOpen] = React.useState(true);\n\n        return (\n          <Dialog.Root open>\n            <Dialog.Backdrop data-testid=\"root-backdrop\" />\n            <Dialog.Portal>\n              <Dialog.Popup>\n                Root dialog\n                <Dialog.Root open={nestedOpen} onOpenChange={setNestedOpen}>\n                  <Dialog.Backdrop data-testid=\"nested-backdrop\" />\n                  <Dialog.Portal>\n                    <Dialog.Popup>Nested dialog</Dialog.Popup>\n                  </Dialog.Portal>\n                </Dialog.Root>\n              </Dialog.Popup>\n            </Dialog.Portal>\n          </Dialog.Root>\n        );\n      }\n\n      await render(<App />);\n\n      expect(screen.getByTestId('root-backdrop')).not.toBe(null);\n      expect(screen.queryByTestId('nested-backdrop')).toBe(null);\n    });\n\n    it('always renders by default when not nested', async () => {\n      await render(\n        <Dialog.Root open>\n          <Dialog.Backdrop data-testid=\"backdrop\" />\n          <Dialog.Portal>\n            <Dialog.Popup>Content</Dialog.Popup>\n          </Dialog.Portal>\n        </Dialog.Root>,\n      );\n\n      expect(screen.getByTestId('backdrop')).not.toBe(null);\n    });\n\n    it('renders only the root backdrop with multiple nesting levels', async () => {\n      function App() {\n        const [level2Open, setLevel2Open] = React.useState(true);\n        const [level3Open, setLevel3Open] = React.useState(true);\n\n        return (\n          <Dialog.Root open>\n            <Dialog.Backdrop data-testid=\"level-1-backdrop\" />\n            <Dialog.Portal>\n              <Dialog.Popup>\n                Level 1 dialog\n                <Dialog.Root open={level2Open} onOpenChange={setLevel2Open}>\n                  <Dialog.Backdrop data-testid=\"level-2-backdrop\" />\n                  <Dialog.Portal>\n                    <Dialog.Popup>\n                      Level 2 dialog\n                      <Dialog.Root open={level3Open} onOpenChange={setLevel3Open}>\n                        <Dialog.Backdrop data-testid=\"level-3-backdrop\" />\n                        <Dialog.Portal>\n                          <Dialog.Popup>Level 3 dialog</Dialog.Popup>\n                        </Dialog.Portal>\n                      </Dialog.Root>\n                    </Dialog.Popup>\n                  </Dialog.Portal>\n                </Dialog.Root>\n              </Dialog.Popup>\n            </Dialog.Portal>\n          </Dialog.Root>\n        );\n      }\n\n      await render(<App />);\n\n      expect(screen.getByTestId('level-1-backdrop')).not.toBe(null);\n      expect(screen.queryByTestId('level-2-backdrop')).toBe(null);\n      expect(screen.queryByTestId('level-3-backdrop')).toBe(null);\n    });\n\n    it('always renders when true', async () => {\n      function App() {\n        const [level2Open, setLevel2Open] = React.useState(true);\n        const [level3Open, setLevel3Open] = React.useState(true);\n\n        return (\n          <Dialog.Root open>\n            <Dialog.Backdrop data-testid=\"level-1-backdrop\" forceRender />\n            <Dialog.Portal>\n              <Dialog.Popup>\n                Level 1 dialog\n                <Dialog.Root open={level2Open} onOpenChange={setLevel2Open}>\n                  <Dialog.Backdrop data-testid=\"level-2-backdrop\" forceRender />\n                  <Dialog.Portal>\n                    <Dialog.Popup>\n                      Level 2 dialog\n                      <Dialog.Root open={level3Open} onOpenChange={setLevel3Open}>\n                        <Dialog.Backdrop data-testid=\"level-3-backdrop\" forceRender />\n                        <Dialog.Portal>\n                          <Dialog.Popup>Level 3 dialog</Dialog.Popup>\n                        </Dialog.Portal>\n                      </Dialog.Root>\n                    </Dialog.Popup>\n                  </Dialog.Portal>\n                </Dialog.Root>\n              </Dialog.Popup>\n            </Dialog.Portal>\n          </Dialog.Root>\n        );\n      }\n\n      await render(<App />);\n\n      expect(screen.getByTestId('level-1-backdrop')).not.toBe(null);\n      expect(screen.getByTestId('level-2-backdrop')).not.toBe(null);\n      expect(screen.getByTestId('level-3-backdrop')).not.toBe(null);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/dialog/backdrop/DialogBackdrop.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useDialogRootContext } from '../root/DialogRootContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { type TransitionStatus } from '../../utils/useTransitionStatus';\nimport { type BaseUIComponentProps } from '../../utils/types';\nimport { type StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport { popupStateMapping as baseMapping } from '../../utils/popupStateMapping';\nimport { transitionStatusMapping } from '../../utils/stateAttributesMapping';\n\nconst stateAttributesMapping: StateAttributesMapping<DialogBackdropState> = {\n  ...baseMapping,\n  ...transitionStatusMapping,\n};\n\n/**\n * An overlay displayed beneath the popup.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Dialog](https://base-ui.com/react/components/dialog)\n */\nexport const DialogBackdrop = React.forwardRef(function DialogBackdrop(\n  componentProps: DialogBackdrop.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, forceRender = false, ...elementProps } = componentProps;\n  const { store } = useDialogRootContext();\n\n  const open = store.useState('open');\n  const nested = store.useState('nested');\n  const mounted = store.useState('mounted');\n  const transitionStatus = store.useState('transitionStatus');\n\n  const state: DialogBackdropState = {\n    open,\n    transitionStatus,\n  };\n\n  return useRenderElement('div', componentProps, {\n    state,\n    ref: [store.context.backdropRef, forwardedRef],\n    stateAttributesMapping,\n    props: [\n      {\n        role: 'presentation',\n        hidden: !mounted,\n        style: {\n          userSelect: 'none',\n          WebkitUserSelect: 'none',\n        },\n      },\n      elementProps,\n    ],\n    enabled: forceRender || !nested,\n  });\n});\n\nexport interface DialogBackdropProps extends BaseUIComponentProps<'div', DialogBackdropState> {\n  /**\n   * Whether the backdrop is forced to render even when nested.\n   * @default false\n   */\n  forceRender?: boolean | undefined;\n}\n\nexport interface DialogBackdropState {\n  /**\n   * Whether the dialog is currently open.\n   */\n  open: boolean;\n  /**\n   * The transition status of the component.\n   */\n  transitionStatus: TransitionStatus;\n}\n\nexport namespace DialogBackdrop {\n  export type Props = DialogBackdropProps;\n  export type State = DialogBackdropState;\n}\n"
  },
  {
    "path": "packages/react/src/dialog/backdrop/DialogBackdropDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum DialogBackdropDataAttributes {\n  /**\n   * Present when the dialog is open.\n   */\n  open = CommonPopupDataAttributes.open,\n  /**\n   * Present when the dialog is closed.\n   */\n  closed = CommonPopupDataAttributes.closed,\n  /**\n   * Present when the dialog is animating in.\n   */\n  startingStyle = CommonPopupDataAttributes.startingStyle,\n  /**\n   * Present when the dialog is animating out.\n   */\n  endingStyle = CommonPopupDataAttributes.endingStyle,\n}\n"
  },
  {
    "path": "packages/react/src/dialog/close/DialogClose.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport { Dialog } from '@base-ui/react/dialog';\nimport { screen } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Dialog.Close />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Dialog.Close />, () => ({\n    refInstanceof: window.HTMLButtonElement,\n    testComponentPropWith: 'button',\n    button: true,\n    render: (node) => {\n      return render(\n        <Dialog.Root open modal={false}>\n          <Dialog.Portal>\n            <Dialog.Popup>{node}</Dialog.Popup>\n          </Dialog.Portal>\n        </Dialog.Root>,\n      );\n    },\n  }));\n\n  describe('prop: disabled', () => {\n    it('disables the button', async () => {\n      const handleOpenChange = vi.fn();\n\n      const { user } = await render(\n        <Dialog.Root onOpenChange={handleOpenChange}>\n          <Dialog.Trigger>Open</Dialog.Trigger>\n          <Dialog.Portal>\n            <Dialog.Popup>\n              <Dialog.Close disabled>Close</Dialog.Close>\n            </Dialog.Popup>\n          </Dialog.Portal>\n        </Dialog.Root>,\n      );\n\n      expect(handleOpenChange.mock.calls.length).toBe(0);\n\n      const openButton = screen.getByText('Open');\n      await user.click(openButton);\n\n      expect(handleOpenChange.mock.calls.length).toBe(1);\n      expect(handleOpenChange.mock.calls[0][0]).toBe(true);\n\n      const closeButton = screen.getByText('Close');\n      expect(closeButton).toHaveAttribute('disabled');\n      expect(closeButton).toHaveAttribute('data-disabled');\n      await user.click(closeButton);\n\n      expect(handleOpenChange.mock.calls.length).toBe(1);\n    });\n\n    it('custom element', async () => {\n      const handleOpenChange = vi.fn();\n\n      const { user } = await render(\n        <Dialog.Root onOpenChange={handleOpenChange}>\n          <Dialog.Trigger>Open</Dialog.Trigger>\n          <Dialog.Portal>\n            <Dialog.Popup>\n              <Dialog.Close disabled render={<span />} nativeButton={false}>\n                Close\n              </Dialog.Close>\n            </Dialog.Popup>\n          </Dialog.Portal>\n        </Dialog.Root>,\n      );\n\n      expect(handleOpenChange.mock.calls.length).toBe(0);\n\n      const openButton = screen.getByText('Open');\n      await user.click(openButton);\n\n      expect(handleOpenChange.mock.calls.length).toBe(1);\n      expect(handleOpenChange.mock.calls[0][0]).toBe(true);\n\n      const closeButton = screen.getByText('Close');\n      expect(closeButton).not.toHaveAttribute('disabled');\n      expect(closeButton).toHaveAttribute('data-disabled');\n      expect(closeButton).toHaveAttribute('aria-disabled', 'true');\n      await user.click(closeButton);\n\n      expect(handleOpenChange.mock.calls.length).toBe(1);\n    });\n  });\n\n  it('closes the dialog when undefined is passed to the `onClick` prop', async () => {\n    const handleOpenChange = vi.fn();\n\n    const { user } = await render(\n      <Dialog.Root onOpenChange={handleOpenChange}>\n        <Dialog.Trigger>Open</Dialog.Trigger>\n        <Dialog.Portal>\n          <Dialog.Popup>\n            <Dialog.Close onClick={undefined}>Close</Dialog.Close>\n          </Dialog.Popup>\n        </Dialog.Portal>\n      </Dialog.Root>,\n    );\n\n    expect(handleOpenChange.mock.calls.length).toBe(0);\n\n    const openButton = screen.getByText('Open');\n    await user.click(openButton);\n\n    expect(handleOpenChange.mock.calls.length).toBe(1);\n    expect(handleOpenChange.mock.calls[0][0]).toBe(true);\n\n    const closeButton = screen.getByText('Close');\n    await user.click(closeButton);\n\n    expect(handleOpenChange.mock.calls.length).toBe(2);\n    expect(handleOpenChange.mock.calls[1][0]).toBe(false);\n  });\n});\n"
  },
  {
    "path": "packages/react/src/dialog/close/DialogClose.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useDialogRootContext } from '../root/DialogRootContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport type { BaseUIComponentProps, NativeButtonProps } from '../../utils/types';\nimport { useButton } from '../../use-button';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\n\n/**\n * A button that closes the dialog.\n * Renders a `<button>` element.\n *\n * Documentation: [Base UI Dialog](https://base-ui.com/react/components/dialog)\n */\nexport const DialogClose = React.forwardRef(function DialogClose(\n  componentProps: DialogClose.Props,\n  forwardedRef: React.ForwardedRef<HTMLButtonElement>,\n) {\n  const {\n    render,\n    className,\n    disabled = false,\n    nativeButton = true,\n    ...elementProps\n  } = componentProps;\n\n  const { store } = useDialogRootContext();\n  const open = store.useState('open');\n\n  function handleClick(event: React.MouseEvent) {\n    if (open) {\n      store.setOpen(false, createChangeEventDetails(REASONS.closePress, event.nativeEvent));\n    }\n  }\n\n  const { getButtonProps, buttonRef } = useButton({\n    disabled,\n    native: nativeButton,\n  });\n\n  const state: DialogCloseState = { disabled };\n\n  return useRenderElement('button', componentProps, {\n    state,\n    ref: [forwardedRef, buttonRef],\n    props: [{ onClick: handleClick }, elementProps, getButtonProps],\n  });\n});\n\nexport interface DialogCloseProps\n  extends NativeButtonProps, BaseUIComponentProps<'button', DialogCloseState> {}\n\nexport interface DialogCloseState {\n  /**\n   * Whether the button is currently disabled.\n   */\n  disabled: boolean;\n}\n\nexport namespace DialogClose {\n  export type Props = DialogCloseProps;\n  export type State = DialogCloseState;\n}\n"
  },
  {
    "path": "packages/react/src/dialog/close/DialogCloseDataAttributes.ts",
    "content": "export enum DialogCloseDataAttributes {\n  /**\n   * Present when the button is disabled.\n   */\n  disabled = 'data-disabled',\n}\n"
  },
  {
    "path": "packages/react/src/dialog/description/DialogDescription.test.tsx",
    "content": "import { Dialog } from '@base-ui/react/dialog';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Dialog.Description />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Dialog.Description />, () => ({\n    refInstanceof: window.HTMLParagraphElement,\n    render: (node) => {\n      return render(\n        <Dialog.Root open modal={false}>\n          <Dialog.Portal>\n            <Dialog.Popup>{node}</Dialog.Popup>\n          </Dialog.Portal>\n        </Dialog.Root>,\n      );\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/dialog/description/DialogDescription.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useDialogRootContext } from '../root/DialogRootContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useBaseUiId } from '../../utils/useBaseUiId';\nimport type { BaseUIComponentProps } from '../../utils/types';\n\n/**\n * A paragraph with additional information about the dialog.\n * Renders a `<p>` element.\n *\n * Documentation: [Base UI Dialog](https://base-ui.com/react/components/dialog)\n */\nexport const DialogDescription = React.forwardRef(function DialogDescription(\n  componentProps: DialogDescription.Props,\n  forwardedRef: React.ForwardedRef<HTMLParagraphElement>,\n) {\n  const { render, className, id: idProp, ...elementProps } = componentProps;\n  const { store } = useDialogRootContext();\n\n  const id = useBaseUiId(idProp);\n\n  store.useSyncedValueWithCleanup('descriptionElementId', id);\n\n  return useRenderElement('p', componentProps, {\n    ref: forwardedRef,\n    props: [{ id }, elementProps],\n  });\n});\n\nexport interface DialogDescriptionProps extends BaseUIComponentProps<'p', DialogDescriptionState> {}\n\nexport interface DialogDescriptionState {}\n\nexport namespace DialogDescription {\n  export type Props = DialogDescriptionProps;\n  export type State = DialogDescriptionState;\n}\n"
  },
  {
    "path": "packages/react/src/dialog/index.parts.ts",
    "content": "export { DialogBackdrop as Backdrop } from './backdrop/DialogBackdrop';\nexport { DialogClose as Close } from './close/DialogClose';\nexport { DialogDescription as Description } from './description/DialogDescription';\nexport { DialogPopup as Popup } from './popup/DialogPopup';\nexport { DialogPortal as Portal } from './portal/DialogPortal';\nexport { DialogRoot as Root } from './root/DialogRoot';\nexport { DialogViewport as Viewport } from './viewport/DialogViewport';\nexport { DialogTitle as Title } from './title/DialogTitle';\nexport { DialogTrigger as Trigger } from './trigger/DialogTrigger';\nexport { createDialogHandle as createHandle, DialogHandle as Handle } from './store/DialogHandle';\n"
  },
  {
    "path": "packages/react/src/dialog/index.ts",
    "content": "export * as Dialog from './index.parts';\n\nexport type * from './root/DialogRoot';\nexport type * from './trigger/DialogTrigger';\nexport type * from './portal/DialogPortal';\nexport type * from './popup/DialogPopup';\nexport type * from './backdrop/DialogBackdrop';\nexport type * from './title/DialogTitle';\nexport type * from './description/DialogDescription';\nexport type * from './close/DialogClose';\nexport type * from './viewport/DialogViewport';\n"
  },
  {
    "path": "packages/react/src/dialog/popup/DialogPopup.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { Dialog } from '@base-ui/react/dialog';\nimport { AlertDialog } from '@base-ui/react/alert-dialog';\nimport { act, waitFor, screen } from '@mui/internal-test-utils';\nimport { describeConformance, createRenderer, isJSDOM, waitSingleFrame } from '#test-utils';\n\ndescribe('<Dialog.Popup />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Dialog.Popup />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render: (node) => {\n      return render(\n        <Dialog.Root open modal={false}>\n          <Dialog.Portal>{node}</Dialog.Portal>\n        </Dialog.Root>,\n      );\n    },\n  }));\n\n  describe('prop: keepMounted', () => {\n    [\n      [true, true],\n      [false, false],\n      [undefined, false],\n    ].forEach(([keepMounted, expectedIsMounted]) => {\n      it(`should ${!expectedIsMounted ? 'not ' : ''}keep the dialog mounted when keepMounted=${keepMounted}`, async () => {\n        await render(\n          <Dialog.Root open={false} modal={false}>\n            <Dialog.Portal keepMounted={keepMounted}>\n              <Dialog.Popup />\n            </Dialog.Portal>\n          </Dialog.Root>,\n        );\n\n        const dialog = screen.queryByRole('dialog', { hidden: true });\n        if (expectedIsMounted) {\n          expect(dialog).not.toBe(null);\n          expect(dialog).toBeInaccessible();\n        } else {\n          expect(dialog).toBe(null);\n        }\n      });\n    });\n  });\n\n  describe('prop: initialFocus', () => {\n    it('should focus the first focusable element within the popup', async () => {\n      await render(\n        <div>\n          <input />\n          <Dialog.Root modal={false}>\n            <Dialog.Trigger>Open</Dialog.Trigger>\n            <Dialog.Portal>\n              <Dialog.Popup data-testid=\"dialog\">\n                <input data-testid=\"dialog-input\" />\n                <button>Close</button>\n              </Dialog.Popup>\n            </Dialog.Portal>\n          </Dialog.Root>\n          <input />\n        </div>,\n      );\n\n      const trigger = screen.getByText('Open');\n      await act(async () => {\n        trigger.click();\n      });\n\n      await waitFor(() => {\n        const dialogInput = screen.getByTestId('dialog-input');\n        expect(dialogInput).to.toHaveFocus();\n      });\n    });\n\n    it('should focus the element provided to `initialFocus` as a ref when open', async () => {\n      function TestComponent() {\n        const input2Ref = React.useRef<HTMLInputElement>(null);\n        return (\n          <div>\n            <input />\n            <Dialog.Root modal={false}>\n              <Dialog.Trigger>Open</Dialog.Trigger>\n              <Dialog.Portal>\n                <Dialog.Popup data-testid=\"dialog\" initialFocus={input2Ref}>\n                  <input data-testid=\"input-1\" />\n                  <input data-testid=\"input-2\" ref={input2Ref} />\n                  <input data-testid=\"input-3\" />\n                  <button>Close</button>\n                </Dialog.Popup>\n              </Dialog.Portal>\n            </Dialog.Root>\n            <input />\n          </div>\n        );\n      }\n\n      await render(<TestComponent />);\n\n      const trigger = screen.getByText('Open');\n      await act(async () => {\n        trigger.click();\n      });\n\n      await waitFor(() => {\n        const input2 = screen.getByTestId('input-2');\n        expect(input2).to.toHaveFocus();\n      });\n    });\n\n    it('should focus the element provided to `initialFocus` as a function when open', async () => {\n      function TestComponent() {\n        const input2Ref = React.useRef<HTMLInputElement>(null);\n\n        const getRef = React.useCallback(() => input2Ref.current, []);\n\n        return (\n          <div>\n            <input />\n            <Dialog.Root modal={false}>\n              <Dialog.Trigger>Open</Dialog.Trigger>\n              <Dialog.Portal>\n                <Dialog.Popup data-testid=\"dialog\" initialFocus={getRef}>\n                  <input data-testid=\"input-1\" />\n                  <input data-testid=\"input-2\" ref={input2Ref} />\n                  <input data-testid=\"input-3\" />\n                  <button>Close</button>\n                </Dialog.Popup>\n              </Dialog.Portal>\n            </Dialog.Root>\n            <input />\n          </div>\n        );\n      }\n\n      await render(<TestComponent />);\n\n      const trigger = screen.getByText('Open');\n      await act(async () => {\n        trigger.click();\n      });\n\n      await waitFor(() => {\n        const input2 = screen.getByTestId('input-2');\n        expect(input2).to.toHaveFocus();\n      });\n    });\n\n    it('should support element-returning function and no-op via false/void for initialFocus', async () => {\n      function TestComponent() {\n        const input2Ref = React.useRef<HTMLInputElement>(null);\n        const getEl = React.useCallback((type: string) => {\n          if (type === 'keyboard') {\n            return input2Ref.current;\n          }\n          return undefined;\n        }, []);\n\n        return (\n          <div>\n            <Dialog.Root modal={false}>\n              <Dialog.Trigger>Open</Dialog.Trigger>\n              <Dialog.Portal>\n                <Dialog.Popup data-testid=\"dialog\" initialFocus={getEl}>\n                  <input data-testid=\"input-1\" />\n                  <input data-testid=\"input-2\" ref={input2Ref} />\n                </Dialog.Popup>\n              </Dialog.Portal>\n            </Dialog.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<TestComponent />);\n\n      const trigger = screen.getByText('Open');\n      await user.click(trigger);\n\n      await waitFor(() => {\n        expect(trigger).toHaveFocus();\n      });\n\n      await user.keyboard('{Escape}');\n      await user.keyboard('{Enter}');\n\n      await waitFor(() => {\n        expect(screen.getByTestId('input-2')).toHaveFocus();\n      });\n    });\n\n    it('should not move focus when initialFocus is false', async () => {\n      function TestComponent() {\n        return (\n          <div>\n            <Dialog.Root modal={false}>\n              <Dialog.Trigger>Open</Dialog.Trigger>\n              <Dialog.Portal>\n                <Dialog.Popup data-testid=\"dialog\" initialFocus={false}>\n                  <input data-testid=\"input-1\" />\n                </Dialog.Popup>\n              </Dialog.Portal>\n            </Dialog.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<TestComponent />);\n      const trigger = screen.getByText('Open');\n      await user.click(trigger);\n      await waitFor(() => {\n        expect(trigger).toHaveFocus();\n      });\n    });\n\n    it('should default focus when initialFocus returns true', async () => {\n      function TestComponent() {\n        return (\n          <div>\n            <Dialog.Root modal={false}>\n              <Dialog.Trigger>Open</Dialog.Trigger>\n              <Dialog.Portal>\n                <Dialog.Popup data-testid=\"dialog\" initialFocus={() => true}>\n                  <input data-testid=\"input-1\" />\n                </Dialog.Popup>\n              </Dialog.Portal>\n            </Dialog.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<TestComponent />);\n      await user.click(screen.getByText('Open'));\n      await waitFor(() => {\n        expect(screen.getByTestId('input-1')).toHaveFocus();\n      });\n    });\n\n    it('uses default behavior when initialFocus returns null', async () => {\n      function TestComponent() {\n        return (\n          <div>\n            <Dialog.Root modal={false}>\n              <Dialog.Trigger>Open</Dialog.Trigger>\n              <Dialog.Portal>\n                <Dialog.Popup data-testid=\"dialog\" initialFocus={() => null}>\n                  <input data-testid=\"input-1\" />\n                </Dialog.Popup>\n              </Dialog.Portal>\n            </Dialog.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<TestComponent />);\n      await user.click(screen.getByText('Open'));\n      await waitFor(() => {\n        expect(screen.getByTestId('input-1')).toHaveFocus();\n      });\n    });\n\n    it('should not call initialFocus function when closing the dialog', async () => {\n      const initialFocusSpy = vi.fn();\n\n      function TestComponent() {\n        const input2Ref = React.useRef<HTMLInputElement>(null);\n\n        const getRef = React.useCallback(() => {\n          initialFocusSpy();\n          return input2Ref.current;\n        }, []);\n\n        return (\n          <div>\n            <Dialog.Root modal={false}>\n              <Dialog.Trigger>Open</Dialog.Trigger>\n              <Dialog.Portal>\n                <Dialog.Popup data-testid=\"dialog\" initialFocus={getRef}>\n                  <input data-testid=\"input-1\" />\n                  <input data-testid=\"input-2\" ref={input2Ref} />\n                  <Dialog.Close>Close</Dialog.Close>\n                </Dialog.Popup>\n              </Dialog.Portal>\n            </Dialog.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<TestComponent />);\n\n      const trigger = screen.getByText('Open');\n      await user.click(trigger);\n\n      await waitFor(() => {\n        const input2 = screen.getByTestId('input-2');\n        expect(input2).toHaveFocus();\n      });\n\n      expect(initialFocusSpy.mock.calls.length).toBe(1);\n\n      const closeButton = screen.getByText('Close');\n      await user.click(closeButton);\n\n      await waitFor(() => {\n        expect(trigger).toHaveFocus();\n      });\n\n      expect(initialFocusSpy.mock.calls.length).toBe(1);\n    });\n  });\n\n  describe('prop: finalFocus', () => {\n    it('should focus the trigger by default when closed', async () => {\n      const { user } = await render(\n        <div>\n          <input />\n          <Dialog.Root>\n            <Dialog.Backdrop />\n            <Dialog.Trigger>Open</Dialog.Trigger>\n            <Dialog.Portal>\n              <Dialog.Popup>\n                <Dialog.Close>Close</Dialog.Close>\n              </Dialog.Popup>\n            </Dialog.Portal>\n          </Dialog.Root>\n          <input />\n        </div>,\n      );\n\n      const trigger = screen.getByText('Open');\n      await user.click(trigger);\n\n      const closeButton = screen.getByText('Close');\n      await user.click(closeButton);\n\n      await waitFor(() => {\n        expect(trigger).toHaveFocus();\n      });\n    });\n\n    it('should focus the element provided to the prop when closed', async () => {\n      function TestComponent() {\n        const inputRef = React.useRef<HTMLInputElement>(null);\n        return (\n          <div>\n            <input />\n            <Dialog.Root>\n              <Dialog.Backdrop />\n              <Dialog.Trigger>Open</Dialog.Trigger>\n              <Dialog.Portal>\n                <Dialog.Popup finalFocus={inputRef}>\n                  <Dialog.Close>Close</Dialog.Close>\n                </Dialog.Popup>\n              </Dialog.Portal>\n            </Dialog.Root>\n            <input />\n            <input data-testid=\"input-to-focus\" ref={inputRef} />\n            <input />\n          </div>\n        );\n      }\n\n      const { user } = await render(<TestComponent />);\n\n      const trigger = screen.getByText('Open');\n      await user.click(trigger);\n\n      const closeButton = screen.getByText('Close');\n      await user.click(closeButton);\n\n      const inputToFocus = screen.getByTestId('input-to-focus');\n\n      await waitFor(() => {\n        expect(inputToFocus).toHaveFocus();\n      });\n    });\n\n    it('should support function returning element for finalFocus when closed', async () => {\n      function TestComponent() {\n        const inputRef = React.useRef<HTMLInputElement>(null);\n        const getEl = React.useCallback(() => inputRef.current, []);\n        return (\n          <div>\n            <Dialog.Root>\n              <Dialog.Backdrop />\n              <Dialog.Trigger>Open</Dialog.Trigger>\n              <Dialog.Portal>\n                <Dialog.Popup finalFocus={getEl}>\n                  <Dialog.Close>Close</Dialog.Close>\n                </Dialog.Popup>\n              </Dialog.Portal>\n            </Dialog.Root>\n            <input data-testid=\"input-to-focus\" ref={inputRef} />\n          </div>\n        );\n      }\n\n      const { user } = await render(<TestComponent />);\n      await user.click(screen.getByText('Open'));\n      await user.click(screen.getByText('Close'));\n      await waitFor(() => {\n        expect(screen.getByTestId('input-to-focus')).toHaveFocus();\n      });\n    });\n\n    it('should not move focus when finalFocus is false', async () => {\n      function TestComponent() {\n        return (\n          <div>\n            <Dialog.Root>\n              <Dialog.Backdrop />\n              <Dialog.Trigger>Open</Dialog.Trigger>\n              <Dialog.Portal>\n                <Dialog.Popup finalFocus={false}>\n                  <Dialog.Close>Close</Dialog.Close>\n                </Dialog.Popup>\n              </Dialog.Portal>\n            </Dialog.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<TestComponent />);\n      const trigger = screen.getByText('Open');\n      await user.click(trigger);\n      await user.click(screen.getByText('Close'));\n      await waitFor(() => {\n        expect(trigger).not.toHaveFocus();\n      });\n    });\n\n    it('should move focus to the trigger when finalFocus returns true', async () => {\n      function TestComponent() {\n        return (\n          <div>\n            <Dialog.Root>\n              <Dialog.Backdrop />\n              <Dialog.Trigger>Open</Dialog.Trigger>\n              <Dialog.Portal>\n                <Dialog.Popup finalFocus={() => true}>\n                  <Dialog.Close>Close</Dialog.Close>\n                </Dialog.Popup>\n              </Dialog.Portal>\n            </Dialog.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<TestComponent />);\n      const trigger = screen.getByText('Open');\n      await user.click(trigger);\n      await user.click(screen.getByText('Close'));\n      await waitFor(() => {\n        expect(trigger).toHaveFocus();\n      });\n    });\n\n    it('should support element-returning function and default via true + no-op via void for finalFocus based on closeType', async () => {\n      function TestComponent() {\n        const inputRef = React.useRef<HTMLInputElement>(null);\n        const getEl = React.useCallback((type: string) => {\n          if (type === 'keyboard') {\n            return inputRef.current;\n          }\n          return true; // default to trigger\n        }, []);\n\n        return (\n          <div>\n            <Dialog.Root>\n              <Dialog.Backdrop />\n              <Dialog.Trigger>Open</Dialog.Trigger>\n              <Dialog.Portal>\n                <Dialog.Popup finalFocus={getEl}>\n                  <Dialog.Close>Close</Dialog.Close>\n                </Dialog.Popup>\n              </Dialog.Portal>\n            </Dialog.Root>\n            <input data-testid=\"final-input\" ref={inputRef} />\n          </div>\n        );\n      }\n\n      const { user } = await render(<TestComponent />);\n\n      const trigger = screen.getByText('Open');\n\n      // Close via pointer: true => default, should move focus to trigger\n      await user.click(trigger);\n      await user.click(screen.getByText('Close'));\n      await waitFor(() => {\n        expect(trigger).toHaveFocus();\n      });\n\n      // Close via keyboard: should move focus to final-input\n      await user.click(trigger);\n      await waitSingleFrame();\n      await user.keyboard('{Escape}');\n      await waitFor(() => {\n        expect(screen.getByTestId('final-input')).toHaveFocus();\n      });\n    });\n\n    it('respects finalFocus when initialFocus points outside the popup', async () => {\n      function TestComponent() {\n        const initialRef = React.useRef<HTMLInputElement>(null);\n        const finalRef = React.useRef<HTMLInputElement>(null);\n        return (\n          <div>\n            <input data-testid=\"initial-outside\" ref={initialRef} />\n            <Dialog.Root>\n              <Dialog.Backdrop />\n              <Dialog.Trigger>Open</Dialog.Trigger>\n              <Dialog.Portal>\n                <Dialog.Popup initialFocus={initialRef} finalFocus={finalRef}>\n                  <Dialog.Close>Close</Dialog.Close>\n                </Dialog.Popup>\n              </Dialog.Portal>\n            </Dialog.Root>\n            <input data-testid=\"final-outside\" ref={finalRef} />\n          </div>\n        );\n      }\n\n      const { user } = await render(<TestComponent />);\n\n      await user.click(screen.getByText('Open'));\n\n      await waitFor(() => {\n        expect(screen.getByTestId('initial-outside')).toHaveFocus();\n      });\n\n      await user.click(screen.getByText('Close'));\n\n      await waitFor(() => {\n        expect(screen.getByTestId('final-outside')).toHaveFocus();\n      });\n    });\n\n    it('moves final focus to trigger if initialFocus points outside the popup and finalFocus is not specified', async () => {\n      function TestComponent() {\n        const initialRef = React.useRef<HTMLInputElement>(null);\n        const finalRef = React.useRef<HTMLInputElement>(null);\n        return (\n          <div>\n            <input data-testid=\"initial-outside\" ref={initialRef} />\n            <Dialog.Root>\n              <Dialog.Backdrop />\n              <Dialog.Trigger>Open</Dialog.Trigger>\n              <Dialog.Portal>\n                <Dialog.Popup initialFocus={initialRef}>\n                  <Dialog.Close>Close</Dialog.Close>\n                </Dialog.Popup>\n              </Dialog.Portal>\n            </Dialog.Root>\n            <input data-testid=\"final-outside\" ref={finalRef} />\n          </div>\n        );\n      }\n\n      const { user } = await render(<TestComponent />);\n\n      await user.click(screen.getByText('Open'));\n\n      await waitFor(() => {\n        expect(screen.getByTestId('initial-outside')).toHaveFocus();\n      });\n\n      await user.click(screen.getByText('Close'));\n\n      await waitFor(() => {\n        expect(screen.getByTestId('final-outside')).not.toHaveFocus();\n      });\n    });\n\n    it('uses default behavior when finalFocus returns null', async () => {\n      function TestComponent() {\n        return (\n          <div>\n            <Dialog.Root>\n              <Dialog.Backdrop />\n              <Dialog.Trigger>Open</Dialog.Trigger>\n              <Dialog.Portal>\n                <Dialog.Popup finalFocus={() => null}>\n                  <Dialog.Close>Close</Dialog.Close>\n                </Dialog.Popup>\n              </Dialog.Portal>\n            </Dialog.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<TestComponent />);\n      const trigger = screen.getByText('Open');\n      await user.click(trigger);\n      await user.click(screen.getByText('Close'));\n      await waitFor(() => {\n        expect(trigger).toHaveFocus();\n      });\n    });\n  });\n\n  describe.skipIf(isJSDOM)('nested dialog count', () => {\n    it('provides the number of open nested dialogs as a CSS variable', async () => {\n      const { user } = await render(\n        <Dialog.Root>\n          <Dialog.Trigger>Trigger 0</Dialog.Trigger>\n          <Dialog.Portal>\n            <Dialog.Popup data-testid=\"popup0\">\n              <Dialog.Root>\n                <Dialog.Trigger>Trigger 1</Dialog.Trigger>\n                <Dialog.Portal>\n                  <Dialog.Popup data-testid=\"popup1\">\n                    <Dialog.Root>\n                      <Dialog.Trigger>Trigger 2</Dialog.Trigger>\n                      <Dialog.Portal>\n                        <Dialog.Popup data-testid=\"popup2\">\n                          <Dialog.Close>Close 2</Dialog.Close>\n                        </Dialog.Popup>\n                      </Dialog.Portal>\n                    </Dialog.Root>\n                    <Dialog.Close>Close 1</Dialog.Close>\n                  </Dialog.Popup>\n                </Dialog.Portal>\n              </Dialog.Root>\n            </Dialog.Popup>\n          </Dialog.Portal>\n        </Dialog.Root>,\n      );\n\n      await user.click(screen.getByRole('button', { name: 'Trigger 0' }));\n\n      await waitFor(() => {\n        expect(screen.getByTestId('popup0')).not.toBe(null);\n      });\n\n      const computedStyles = getComputedStyle(screen.getByTestId('popup0'));\n\n      expect(computedStyles.getPropertyValue('--nested-dialogs')).toBe('0');\n\n      await user.click(screen.getByRole('button', { name: 'Trigger 1' }));\n\n      await waitFor(() => {\n        expect(screen.getByTestId('popup1')).not.toBe(null);\n      });\n\n      expect(computedStyles.getPropertyValue('--nested-dialogs')).toBe('1');\n\n      await user.click(screen.getByRole('button', { name: 'Trigger 2' }));\n\n      await waitFor(() => {\n        expect(screen.getByTestId('popup2')).not.toBe(null);\n      });\n\n      expect(computedStyles.getPropertyValue('--nested-dialogs')).toBe('2');\n\n      await user.click(screen.getByRole('button', { name: 'Close 2' }));\n\n      expect(computedStyles.getPropertyValue('--nested-dialogs')).toBe('1');\n\n      await user.click(screen.getByRole('button', { name: 'Close 1' }));\n\n      expect(computedStyles.getPropertyValue('--nested-dialogs')).toBe('0');\n    });\n\n    it('decrements the count when an open nested dialog is unmounted', async () => {\n      function App() {\n        const [showNested, setShowNested] = React.useState(true);\n        return (\n          <React.Fragment>\n            <button onClick={() => setShowNested(!showNested)}>toggle</button>\n            <Dialog.Root>\n              <Dialog.Trigger>Trigger 0</Dialog.Trigger>\n              <Dialog.Portal>\n                <Dialog.Popup data-testid=\"popup0\">\n                  {showNested && (\n                    <Dialog.Root>\n                      <Dialog.Trigger>Trigger 1</Dialog.Trigger>\n                      <Dialog.Portal>\n                        <Dialog.Popup data-testid=\"popup1\">\n                          <Dialog.Close>Close 1</Dialog.Close>\n                        </Dialog.Popup>\n                      </Dialog.Portal>\n                    </Dialog.Root>\n                  )}\n                  <Dialog.Close>Close 0</Dialog.Close>\n                </Dialog.Popup>\n              </Dialog.Portal>\n            </Dialog.Root>\n          </React.Fragment>\n        );\n      }\n\n      const { user } = await render(<App />);\n\n      await user.click(screen.getByRole('button', { name: 'Trigger 0' }));\n\n      await waitFor(() => {\n        expect(screen.getByTestId('popup0')).not.toBe(null);\n      });\n\n      const computedStyles = getComputedStyle(screen.getByTestId('popup0'));\n\n      expect(computedStyles.getPropertyValue('--nested-dialogs')).toBe('0');\n\n      await user.click(screen.getByRole('button', { name: 'Trigger 1' }));\n\n      await waitFor(() => {\n        expect(screen.getByTestId('popup1')).not.toBe(null);\n      });\n\n      expect(computedStyles.getPropertyValue('--nested-dialogs')).toBe('1');\n\n      await user.click(screen.getByRole('button', { name: 'toggle', hidden: true }));\n\n      expect(computedStyles.getPropertyValue('--nested-dialogs')).toBe('0');\n    });\n\n    it('does not change the count when a closed nested dialog is unmounted', async () => {\n      function App() {\n        const [showNested, setShowNested] = React.useState(true);\n        return (\n          <Dialog.Root>\n            <Dialog.Trigger>Trigger 0</Dialog.Trigger>\n            <Dialog.Portal>\n              <Dialog.Popup data-testid=\"popup0\">\n                {showNested && (\n                  <Dialog.Root>\n                    <Dialog.Trigger />\n                    <Dialog.Portal>\n                      <Dialog.Popup />\n                    </Dialog.Portal>\n                  </Dialog.Root>\n                )}\n                <button onClick={() => setShowNested(!showNested)}>toggle</button>\n                <Dialog.Close>Close 0</Dialog.Close>\n              </Dialog.Popup>\n            </Dialog.Portal>\n          </Dialog.Root>\n        );\n      }\n\n      const { user } = await render(<App />);\n\n      await user.click(screen.getByRole('button', { name: 'Trigger 0' }));\n\n      await waitFor(() => {\n        expect(screen.getByTestId('popup0')).not.toBe(null);\n      });\n\n      const computedStyles = getComputedStyle(screen.getByTestId('popup0'));\n\n      expect(computedStyles.getPropertyValue('--nested-dialogs')).toBe('0');\n\n      await user.click(screen.getByRole('button', { name: 'toggle' }));\n\n      expect(computedStyles.getPropertyValue('--nested-dialogs')).toBe('0');\n    });\n\n    it('increments for nested alert dialog and decrements on close (cross-type)', async () => {\n      const { user } = await render(\n        <Dialog.Root>\n          <Dialog.Trigger>Open Dialog</Dialog.Trigger>\n          <Dialog.Portal>\n            <Dialog.Popup data-testid=\"parent-dialog\">\n              <AlertDialog.Root>\n                <AlertDialog.Trigger>Open Alert</AlertDialog.Trigger>\n                <AlertDialog.Portal>\n                  <AlertDialog.Popup data-testid=\"nested-alert\">\n                    <AlertDialog.Close>Close Alert</AlertDialog.Close>\n                  </AlertDialog.Popup>\n                </AlertDialog.Portal>\n              </AlertDialog.Root>\n              <Dialog.Close>Close Dialog</Dialog.Close>\n            </Dialog.Popup>\n          </Dialog.Portal>\n        </Dialog.Root>,\n      );\n\n      await user.click(screen.getByRole('button', { name: 'Open Dialog' }));\n      await waitFor(() => expect(screen.getByTestId('parent-dialog')).not.toBe(null));\n\n      const parent = screen.getByTestId('parent-dialog');\n      expect(getComputedStyle(parent).getPropertyValue('--nested-dialogs')).toBe('0');\n\n      await user.click(screen.getByRole('button', { name: 'Open Alert' }));\n      await waitFor(() => expect(screen.getByTestId('nested-alert')).not.toBe(null));\n      await waitFor(() => {\n        expect(getComputedStyle(parent).getPropertyValue('--nested-dialogs')).toBe('1');\n      });\n\n      await user.click(screen.getByRole('button', { name: 'Close Alert' }));\n      await waitFor(() => {\n        expect(getComputedStyle(parent).getPropertyValue('--nested-dialogs')).toBe('0');\n      });\n    });\n  });\n\n  describe('style hooks', () => {\n    it('adds the `nested` and `nested-dialog-open` style hooks if a dialog has a parent dialog', async () => {\n      await render(\n        <Dialog.Root open>\n          <Dialog.Portal>\n            <Dialog.Popup data-testid=\"parent-dialog\" />\n            <Dialog.Root open>\n              <Dialog.Portal>\n                <Dialog.Popup data-testid=\"nested-dialog\">\n                  <Dialog.Root>\n                    <Dialog.Portal>\n                      <Dialog.Popup />\n                    </Dialog.Portal>\n                  </Dialog.Root>\n                </Dialog.Popup>\n              </Dialog.Portal>\n            </Dialog.Root>\n          </Dialog.Portal>\n        </Dialog.Root>,\n      );\n\n      const parentDialog = screen.getByTestId('parent-dialog');\n      const nestedDialog = screen.getByTestId('nested-dialog');\n\n      expect(parentDialog).not.toHaveAttribute('data-nested');\n      expect(nestedDialog).toHaveAttribute('data-nested');\n\n      expect(parentDialog).toHaveAttribute('data-nested-dialog-open');\n      expect(nestedDialog).not.toHaveAttribute('data-nested-dialog-open');\n    });\n\n    it('adds the `nested` and `nested-dialog-open` style hooks if a dialog has a parent alert dialog', async () => {\n      await render(\n        <AlertDialog.Root open>\n          <AlertDialog.Portal>\n            <AlertDialog.Popup data-testid=\"parent-dialog\" />\n            <Dialog.Root open>\n              <Dialog.Portal>\n                <Dialog.Popup data-testid=\"nested-dialog\">\n                  <Dialog.Root>\n                    <Dialog.Portal>\n                      <Dialog.Popup />\n                    </Dialog.Portal>\n                  </Dialog.Root>\n                </Dialog.Popup>\n              </Dialog.Portal>\n            </Dialog.Root>\n          </AlertDialog.Portal>\n        </AlertDialog.Root>,\n      );\n\n      const parentDialog = screen.getByTestId('parent-dialog');\n      const nestedDialog = screen.getByTestId('nested-dialog');\n\n      expect(parentDialog).not.toHaveAttribute('data-nested');\n      expect(nestedDialog).toHaveAttribute('data-nested');\n\n      expect(parentDialog).toHaveAttribute('data-nested-dialog-open');\n      expect(nestedDialog).not.toHaveAttribute('data-nested-dialog-open');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/dialog/popup/DialogPopup.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { InteractionType } from '@base-ui/utils/useEnhancedClickHandler';\nimport { FloatingFocusManager } from '../../floating-ui-react';\nimport { useDialogRootContext } from '../root/DialogRootContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { type BaseUIComponentProps } from '../../utils/types';\nimport { type TransitionStatus } from '../../utils/useTransitionStatus';\nimport { type StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport { popupStateMapping as baseMapping } from '../../utils/popupStateMapping';\nimport { transitionStatusMapping } from '../../utils/stateAttributesMapping';\nimport { DialogPopupCssVars } from './DialogPopupCssVars';\nimport { DialogPopupDataAttributes } from './DialogPopupDataAttributes';\nimport { useDialogPortalContext } from '../portal/DialogPortalContext';\nimport { useOpenChangeComplete } from '../../utils/useOpenChangeComplete';\nimport { COMPOSITE_KEYS } from '../../composite/composite';\n\nconst stateAttributesMapping: StateAttributesMapping<DialogPopupState> = {\n  ...baseMapping,\n  ...transitionStatusMapping,\n  nestedDialogOpen(value) {\n    return value ? { [DialogPopupDataAttributes.nestedDialogOpen]: '' } : null;\n  },\n};\n\n/**\n * A container for the dialog contents.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Dialog](https://base-ui.com/react/components/dialog)\n */\nexport const DialogPopup = React.forwardRef(function DialogPopup(\n  componentProps: DialogPopup.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { className, finalFocus, initialFocus, render, ...elementProps } = componentProps;\n\n  const { store } = useDialogRootContext();\n\n  const descriptionElementId = store.useState('descriptionElementId');\n  const disablePointerDismissal = store.useState('disablePointerDismissal');\n  const floatingRootContext = store.useState('floatingRootContext');\n  const rootPopupProps = store.useState('popupProps');\n  const modal = store.useState('modal');\n  const mounted = store.useState('mounted');\n  const nested = store.useState('nested');\n  const nestedOpenDialogCount = store.useState('nestedOpenDialogCount');\n  const open = store.useState('open');\n  const openMethod = store.useState('openMethod');\n  const titleElementId = store.useState('titleElementId');\n  const transitionStatus = store.useState('transitionStatus');\n  const role = store.useState('role');\n\n  useDialogPortalContext();\n\n  useOpenChangeComplete({\n    open,\n    ref: store.context.popupRef,\n    onComplete() {\n      if (open) {\n        store.context.onOpenChangeComplete?.(true);\n      }\n    },\n  });\n\n  // Default initial focus logic:\n  // If opened by touch, focus the popup element to prevent the virtual keyboard from opening\n  // (this is required for Android specifically as iOS handles this automatically).\n  function defaultInitialFocus(interactionType: InteractionType) {\n    if (interactionType === 'touch') {\n      return store.context.popupRef.current;\n    }\n    return true;\n  }\n\n  const resolvedInitialFocus = initialFocus === undefined ? defaultInitialFocus : initialFocus;\n\n  const nestedDialogOpen = nestedOpenDialogCount > 0;\n\n  const state: DialogPopupState = {\n    open,\n    nested,\n    transitionStatus,\n    nestedDialogOpen,\n  };\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    props: [\n      rootPopupProps,\n      {\n        'aria-labelledby': titleElementId ?? undefined,\n        'aria-describedby': descriptionElementId ?? undefined,\n        role,\n        tabIndex: -1,\n        hidden: !mounted,\n        onKeyDown(event: React.KeyboardEvent) {\n          if (COMPOSITE_KEYS.has(event.key)) {\n            event.stopPropagation();\n          }\n        },\n        style: {\n          [DialogPopupCssVars.nestedDialogs]: nestedOpenDialogCount,\n        } as React.CSSProperties,\n      },\n      elementProps,\n    ],\n    ref: [forwardedRef, store.context.popupRef, store.useStateSetter('popupElement')],\n    stateAttributesMapping,\n  });\n\n  return (\n    <FloatingFocusManager\n      context={floatingRootContext}\n      openInteractionType={openMethod}\n      disabled={!mounted}\n      closeOnFocusOut={!disablePointerDismissal}\n      initialFocus={resolvedInitialFocus}\n      returnFocus={finalFocus}\n      modal={modal !== false}\n      restoreFocus=\"popup\"\n    >\n      {element}\n    </FloatingFocusManager>\n  );\n});\n\nexport interface DialogPopupProps extends BaseUIComponentProps<'div', DialogPopupState> {\n  /**\n   * Determines the element to focus when the dialog is opened.\n   *\n   * - `false`: Do not move focus.\n   * - `true`: Move focus based on the default behavior (first tabbable element or popup).\n   * - `RefObject`: Move focus to the ref element.\n   * - `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`).\n   *   Return an element to focus, `true` to use the default behavior, or `false`/`undefined` to do nothing.\n   */\n  initialFocus?:\n    | boolean\n    | React.RefObject<HTMLElement | null>\n    | ((openType: InteractionType) => boolean | HTMLElement | null | void)\n    | undefined;\n  /**\n   * Determines the element to focus when the dialog is closed.\n   *\n   * - `false`: Do not move focus.\n   * - `true`: Move focus based on the default behavior (trigger or previously focused element).\n   * - `RefObject`: Move focus to the ref element.\n   * - `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`).\n   *   Return an element to focus, `true` to use the default behavior, or `false`/`undefined` to do nothing.\n   */\n  finalFocus?:\n    | boolean\n    | React.RefObject<HTMLElement | null>\n    | ((closeType: InteractionType) => boolean | HTMLElement | null | void)\n    | undefined;\n}\n\nexport interface DialogPopupState {\n  /**\n   * Whether the dialog is currently open.\n   */\n  open: boolean;\n  /**\n   * The transition status of the component.\n   */\n  transitionStatus: TransitionStatus;\n  /**\n   * Whether the dialog is nested within a parent dialog.\n   */\n  nested: boolean;\n  /**\n   * Whether the dialog has nested dialogs open.\n   */\n  nestedDialogOpen: boolean;\n}\n\nexport namespace DialogPopup {\n  export type Props = DialogPopupProps;\n  export type State = DialogPopupState;\n}\n"
  },
  {
    "path": "packages/react/src/dialog/popup/DialogPopupCssVars.ts",
    "content": "export enum DialogPopupCssVars {\n  /**\n   * Indicates how many dialogs are nested within.\n   * @type {number}\n   */\n  nestedDialogs = '--nested-dialogs',\n}\n"
  },
  {
    "path": "packages/react/src/dialog/popup/DialogPopupDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum DialogPopupDataAttributes {\n  /**\n   * Present when the dialog is open.\n   */\n  open = CommonPopupDataAttributes.open,\n  /**\n   * Present when the dialog is closed.\n   */\n  closed = CommonPopupDataAttributes.closed,\n  /**\n   * Present when the dialog is animating in.\n   */\n  startingStyle = CommonPopupDataAttributes.startingStyle,\n  /**\n   * Present when the dialog is animating out.\n   */\n  endingStyle = CommonPopupDataAttributes.endingStyle,\n  /**\n   * Present when the dialog is nested within another dialog.\n   */\n  nested = 'data-nested',\n  /**\n   * Present when the dialog has other open dialogs nested within it.\n   */\n  nestedDialogOpen = 'data-nested-dialog-open',\n}\n"
  },
  {
    "path": "packages/react/src/dialog/portal/DialogPortal.test.tsx",
    "content": "import { expect } from 'vitest';\nimport * as React from 'react';\nimport { Dialog } from '@base-ui/react/dialog';\nimport { createRenderer, describeConformance } from '#test-utils';\nimport { screen } from '@mui/internal-test-utils';\n\ndescribe('<Dialog.Portal />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Dialog.Portal />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(<Dialog.Root open>{node}</Dialog.Root>);\n    },\n  }));\n\n  describe('Suspense integration', () => {\n    // Issue #3695\n    it('should not throw \"Maximum update depth exceeded\" when Suspense boundary is outside Portal', async () => {\n      function createLazyComponent() {\n        let resolvePromise: ((value: { default: React.ComponentType }) => void) | null = null;\n        const promise = new Promise<{ default: React.ComponentType }>((resolve) => {\n          resolvePromise = resolve;\n        });\n\n        return {\n          LazyComponent: React.lazy(() => promise),\n          resolve(value: { default: React.ComponentType }) {\n            if (!resolvePromise) {\n              throw new Error('Lazy message resolver not initialized.');\n            }\n            resolvePromise(value);\n          },\n        };\n      }\n\n      const { LazyComponent, resolve } = createLazyComponent();\n\n      await render(\n        <React.Suspense fallback=\"Loading...\">\n          <Dialog.Root open>\n            <Dialog.Portal>\n              <Dialog.Popup>\n                <LazyComponent />\n              </Dialog.Popup>\n            </Dialog.Portal>\n          </Dialog.Root>\n        </React.Suspense>,\n      );\n\n      expect(await screen.findByText('Loading...')).not.toBe(null);\n      resolve({ default: () => <p>Greetings</p> });\n      expect(await screen.findByText('Greetings')).not.toBe(null);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/dialog/portal/DialogPortal.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { inertValue } from '@base-ui/utils/inertValue';\nimport { FloatingPortal } from '../../floating-ui-react';\nimport { useDialogRootContext } from '../root/DialogRootContext';\nimport { DialogPortalContext } from './DialogPortalContext';\nimport { InternalBackdrop } from '../../utils/InternalBackdrop';\n\n/**\n * A portal element that moves the popup to a different part of the DOM.\n * By default, the portal element is appended to `<body>`.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Dialog](https://base-ui.com/react/components/dialog)\n */\nexport const DialogPortal = React.forwardRef(function DialogPortal(\n  props: DialogPortal.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { keepMounted = false, ...portalProps } = props;\n\n  const { store } = useDialogRootContext();\n  const mounted = store.useState('mounted');\n  const modal = store.useState('modal');\n  const open = store.useState('open');\n\n  const shouldRender = mounted || keepMounted;\n  if (!shouldRender) {\n    return null;\n  }\n\n  return (\n    <DialogPortalContext.Provider value={keepMounted}>\n      <FloatingPortal ref={forwardedRef} {...portalProps}>\n        {mounted && modal === true && (\n          <InternalBackdrop ref={store.context.internalBackdropRef} inert={inertValue(!open)} />\n        )}\n        {props.children}\n      </FloatingPortal>\n    </DialogPortalContext.Provider>\n  );\n});\n\nexport interface DialogPortalState {}\n\nexport interface DialogPortalProps extends FloatingPortal.Props<DialogPortalState> {\n  /**\n   * Whether to keep the portal mounted in the DOM while the popup is hidden.\n   * @default false\n   */\n  keepMounted?: boolean | undefined;\n  /**\n   * A parent element to render the portal element into.\n   */\n  container?: FloatingPortal.Props<DialogPortalState>['container'] | undefined;\n}\n\nexport namespace DialogPortal {\n  export type State = DialogPortalState;\n  export type Props = DialogPortalProps;\n}\n"
  },
  {
    "path": "packages/react/src/dialog/portal/DialogPortalContext.ts",
    "content": "'use client';\nimport * as React from 'react';\n\nexport const DialogPortalContext = React.createContext<boolean | undefined>(undefined);\n\nexport function useDialogPortalContext() {\n  const value = React.useContext(DialogPortalContext);\n  if (value === undefined) {\n    throw new Error('Base UI: <Dialog.Portal> is missing.');\n  }\n  return value;\n}\n"
  },
  {
    "path": "packages/react/src/dialog/root/DialogRoot.detached-triggers.test.tsx",
    "content": "import { expect } from 'vitest';\nimport * as React from 'react';\nimport { act, screen, waitFor } from '@mui/internal-test-utils';\nimport { Dialog } from '@base-ui/react/dialog';\nimport { createRenderer, isJSDOM } from '#test-utils';\n\ndescribe('<Dialog.Root />', () => {\n  const { render } = createRenderer();\n\n  beforeEach(() => {\n    globalThis.BASE_UI_ANIMATIONS_DISABLED = true;\n  });\n\n  describe.skipIf(isJSDOM)('multiple triggers within Root', () => {\n    type NumberPayload = { payload: number | undefined };\n\n    it('opens the dialog with any trigger', async () => {\n      const { user } = await render(\n        <Dialog.Root>\n          <Dialog.Trigger>Trigger 1</Dialog.Trigger>\n          <Dialog.Trigger>Trigger 2</Dialog.Trigger>\n          <Dialog.Trigger>Trigger 3</Dialog.Trigger>\n\n          <Dialog.Portal>\n            <Dialog.Popup>\n              Dialog Content\n              <Dialog.Close>Close</Dialog.Close>\n            </Dialog.Popup>\n          </Dialog.Portal>\n        </Dialog.Root>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n      const trigger3 = screen.getByRole('button', { name: 'Trigger 3' });\n\n      expect(screen.queryByText('Dialog Content')).toBe(null);\n\n      await user.click(trigger1);\n      await waitFor(() => {\n        expect(screen.queryByText('Dialog Content')).not.toBe(null);\n      });\n\n      await user.click(screen.getByText('Close'));\n      await waitFor(() => {\n        expect(screen.queryByText('Dialog Content')).toBe(null);\n      });\n\n      await user.click(trigger2);\n      await waitFor(() => {\n        expect(screen.queryByText('Dialog Content')).not.toBe(null);\n      });\n\n      await user.click(screen.getByText('Close'));\n      await waitFor(() => {\n        expect(screen.queryByText('Dialog Content')).toBe(null);\n      });\n\n      await user.click(trigger3);\n      await waitFor(() => {\n        expect(screen.queryByText('Dialog Content')).not.toBe(null);\n      });\n    });\n\n    it('sets the payload and renders content based on its value', async () => {\n      const { user } = await render(\n        <Dialog.Root>\n          {({ payload }: NumberPayload) => (\n            <React.Fragment>\n              <Dialog.Trigger payload={1}>Trigger 1</Dialog.Trigger>\n              <Dialog.Trigger payload={2}>Trigger 2</Dialog.Trigger>\n\n              <Dialog.Portal>\n                <Dialog.Popup>\n                  <span data-testid=\"content\">{payload}</span>\n                  <Dialog.Close>Close</Dialog.Close>\n                </Dialog.Popup>\n              </Dialog.Portal>\n            </React.Fragment>\n          )}\n        </Dialog.Root>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n\n      await user.click(trigger1);\n      await waitFor(() => {\n        expect(screen.getByTestId('content').textContent).toBe('1');\n      });\n\n      await user.click(trigger2);\n      await waitFor(() => {\n        expect(screen.getByTestId('content').textContent).toBe('2');\n      });\n    });\n\n    it('reuses the popup DOM node when switching triggers', async () => {\n      const { user } = await render(\n        <Dialog.Root>\n          {({ payload }: NumberPayload) => (\n            <React.Fragment>\n              <Dialog.Trigger payload={1}>Trigger 1</Dialog.Trigger>\n              <Dialog.Trigger payload={2}>Trigger 2</Dialog.Trigger>\n\n              <Dialog.Portal>\n                <Dialog.Popup data-testid=\"dialog-popup\">\n                  <span>{payload}</span>\n                </Dialog.Popup>\n              </Dialog.Portal>\n            </React.Fragment>\n          )}\n        </Dialog.Root>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n\n      await user.click(trigger1);\n      const popupElement = screen.getByTestId('dialog-popup');\n\n      await user.click(trigger2);\n      expect(screen.getByTestId('dialog-popup')).toBe(popupElement);\n    });\n\n    it('synchronizes ARIA attributes on the active trigger', async () => {\n      const { user } = await render(\n        <Dialog.Root>\n          <Dialog.Trigger>Trigger 1</Dialog.Trigger>\n          <Dialog.Trigger>Trigger 2</Dialog.Trigger>\n\n          <Dialog.Portal>\n            <Dialog.Popup data-testid=\"dialog-popup\">Dialog Content</Dialog.Popup>\n          </Dialog.Portal>\n        </Dialog.Root>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n\n      expect(trigger1).toHaveAttribute('aria-expanded', 'false');\n      expect(trigger2).toHaveAttribute('aria-expanded', 'false');\n\n      await user.click(trigger1);\n\n      const dialog = await screen.findByRole('dialog');\n      const trigger1Controls = trigger1.getAttribute('aria-controls');\n      expect(trigger1Controls).not.toBe(null);\n      expect(dialog.getAttribute('id')).toBe(trigger1Controls);\n      await waitFor(() => {\n        expect(trigger1).toHaveAttribute('aria-expanded', 'true');\n      });\n      expect(trigger2).toHaveAttribute('aria-expanded', 'false');\n    });\n\n    it('sets the payload when opening programmatically with a controlled triggerId', async () => {\n      function App() {\n        const [open, setOpen] = React.useState(false);\n        const [triggerId, setTriggerId] = React.useState<string | null>(null);\n\n        return (\n          <div>\n            <Dialog.Root open={open} triggerId={triggerId}>\n              {({ payload }: NumberPayload) => (\n                <React.Fragment>\n                  <Dialog.Trigger id=\"trigger-1\" payload={1}>\n                    One\n                  </Dialog.Trigger>\n                  <Dialog.Trigger id=\"trigger-2\" payload={2}>\n                    Two\n                  </Dialog.Trigger>\n\n                  <Dialog.Portal>\n                    <Dialog.Popup>\n                      <span data-testid=\"content\">{payload}</span>\n                    </Dialog.Popup>\n                  </Dialog.Portal>\n                </React.Fragment>\n              )}\n            </Dialog.Root>\n\n            <button\n              type=\"button\"\n              onClick={() => {\n                setTriggerId('trigger-2');\n                setOpen(true);\n              }}\n            >\n              Open programmatically\n            </button>\n          </div>\n        );\n      }\n\n      const { user } = await render(<App />);\n\n      const openButton = screen.getByRole('button', { name: 'Open programmatically' });\n      await user.click(openButton);\n\n      await waitFor(() => {\n        expect(screen.getByTestId('content').textContent).toBe('2');\n      });\n    });\n\n    it('keeps the payload reactive', async () => {\n      function App() {\n        const [payloads, setPayloads] = React.useState([1, 2]);\n\n        return (\n          <div>\n            <Dialog.Root>\n              {({ payload }: NumberPayload) => (\n                <React.Fragment>\n                  <Dialog.Trigger id=\"trigger-1\" payload={payloads[0]}>\n                    Dialog 1\n                  </Dialog.Trigger>\n                  <Dialog.Trigger id=\"trigger-2\" payload={payloads[1]}>\n                    Dialog 2\n                  </Dialog.Trigger>\n\n                  <Dialog.Portal>\n                    <Dialog.Popup>\n                      <span data-testid=\"content\">{payload}</span>\n                      <button type=\"button\" onClick={() => setPayloads([8, 16])}>\n                        Update payloads\n                      </button>\n                    </Dialog.Popup>\n                  </Dialog.Portal>\n                </React.Fragment>\n              )}\n            </Dialog.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<App />);\n\n      const trigger1 = screen.getByRole('button', { name: 'Dialog 1' });\n      await user.click(trigger1);\n      await waitFor(() => {\n        expect(screen.getByTestId('content').textContent).toBe('1');\n      });\n\n      const updateButton = screen.getByRole('button', { name: 'Update payloads' });\n      await user.click(updateButton);\n      await waitFor(() => {\n        expect(screen.getByTestId('content').textContent).toBe('8');\n      });\n    });\n  });\n\n  describe.skipIf(isJSDOM)('multiple detached triggers', () => {\n    type NumberPayload = { payload: number | undefined };\n\n    it('opens the dialog with any trigger', async () => {\n      const testDialog = Dialog.createHandle();\n      const { user } = await render(\n        <div>\n          <Dialog.Trigger handle={testDialog}>Trigger 1</Dialog.Trigger>\n          <Dialog.Trigger handle={testDialog}>Trigger 2</Dialog.Trigger>\n          <Dialog.Trigger handle={testDialog}>Trigger 3</Dialog.Trigger>\n\n          <Dialog.Root handle={testDialog}>\n            <Dialog.Portal>\n              <Dialog.Popup>\n                Dialog Content\n                <Dialog.Close>Close</Dialog.Close>\n              </Dialog.Popup>\n            </Dialog.Portal>\n          </Dialog.Root>\n        </div>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n      const trigger3 = screen.getByRole('button', { name: 'Trigger 3' });\n\n      expect(screen.queryByText('Dialog Content')).toBe(null);\n\n      await user.click(trigger1);\n      await waitFor(() => {\n        expect(screen.queryByText('Dialog Content')).not.toBe(null);\n      });\n      await user.click(screen.getByText('Close'));\n      await waitFor(() => {\n        expect(screen.queryByText('Dialog Content')).toBe(null);\n      });\n\n      await user.click(trigger2);\n      await waitFor(() => {\n        expect(screen.queryByText('Dialog Content')).not.toBe(null);\n      });\n      await user.click(screen.getByText('Close'));\n      await waitFor(() => {\n        expect(screen.queryByText('Dialog Content')).toBe(null);\n      });\n\n      await user.click(trigger3);\n      await waitFor(() => {\n        expect(screen.queryByText('Dialog Content')).not.toBe(null);\n      });\n    });\n\n    it('sets the payload and renders content based on its value', async () => {\n      const testDialog = Dialog.createHandle<number>();\n      const { user } = await render(\n        <div>\n          <Dialog.Trigger handle={testDialog} payload={1}>\n            Trigger 1\n          </Dialog.Trigger>\n          <Dialog.Trigger handle={testDialog} payload={2}>\n            Trigger 2\n          </Dialog.Trigger>\n\n          <Dialog.Root handle={testDialog}>\n            {({ payload }: NumberPayload) => (\n              <Dialog.Portal>\n                <Dialog.Popup>\n                  <span data-testid=\"content\">{payload}</span>\n                  <Dialog.Close>Close</Dialog.Close>\n                </Dialog.Popup>\n              </Dialog.Portal>\n            )}\n          </Dialog.Root>\n        </div>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n\n      await user.click(trigger1);\n      await waitFor(() => {\n        expect(screen.getByTestId('content').textContent).toBe('1');\n      });\n\n      await user.click(trigger2);\n      await waitFor(() => {\n        expect(screen.getByTestId('content').textContent).toBe('2');\n      });\n    });\n\n    it('reuses the popup DOM node when switching triggers', async () => {\n      const testDialog = Dialog.createHandle<number>();\n      const { user } = await render(\n        <React.Fragment>\n          <Dialog.Trigger handle={testDialog} payload={1}>\n            Trigger 1\n          </Dialog.Trigger>\n          <Dialog.Trigger handle={testDialog} payload={2}>\n            Trigger 2\n          </Dialog.Trigger>\n\n          <Dialog.Root handle={testDialog}>\n            {({ payload }: NumberPayload) => (\n              <Dialog.Portal>\n                <Dialog.Popup data-testid=\"dialog-popup\">\n                  <span>{payload}</span>\n                </Dialog.Popup>\n              </Dialog.Portal>\n            )}\n          </Dialog.Root>\n        </React.Fragment>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n\n      await user.click(trigger1);\n      const popupElement = screen.getByTestId('dialog-popup');\n\n      await user.click(trigger2);\n      expect(screen.getByTestId('dialog-popup')).toBe(popupElement);\n    });\n\n    it('keeps the payload reactive', async () => {\n      type NumberAccessorPayload = { payload: (() => number) | undefined };\n      const testDialog = Dialog.createHandle<() => number>();\n      function Triggers() {\n        // Setting up triggers in a separate component so payload is in their local state\n        // and updating it does not cause the Dialog.Root to re-render automatically.\n        // This verifies that the payload is reactive and not only set on mount or on trigger click.\n        const [payloads, setPayloads] = React.useState([1, 2]);\n\n        return (\n          <div>\n            <Dialog.Trigger id=\"trigger-1\" payload={() => payloads[0]} handle={testDialog}>\n              Dialog 1\n            </Dialog.Trigger>\n            <Dialog.Trigger id=\"trigger-2\" payload={() => payloads[1]} handle={testDialog}>\n              Dialog 2\n            </Dialog.Trigger>\n            <button type=\"button\" onClick={() => setPayloads([8, 16])}>\n              Update payloads\n            </button>\n          </div>\n        );\n      }\n\n      function App() {\n        return (\n          <div>\n            <Triggers />\n            <Dialog.Root modal={false} disablePointerDismissal={true} handle={testDialog}>\n              {({ payload }: NumberAccessorPayload) => (\n                <Dialog.Portal>\n                  <Dialog.Popup>\n                    <span data-testid=\"content\">{payload?.()}</span>\n                  </Dialog.Popup>\n                </Dialog.Portal>\n              )}\n            </Dialog.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<App />);\n\n      const trigger1 = screen.getByRole('button', { name: 'Dialog 1' });\n      await user.click(trigger1);\n      await waitFor(() => {\n        expect(screen.getByTestId('content').textContent).toBe('1');\n      });\n\n      const updateButton = screen.getByRole('button', { name: 'Update payloads' });\n      await user.click(updateButton);\n      await waitFor(() => {\n        expect(screen.getByTestId('content').textContent).toBe('8');\n      });\n    });\n  });\n\n  describe('imperative actions on the handle', () => {\n    it('opens and closes the dialog', async () => {\n      const dialog = Dialog.createHandle();\n      await render(\n        <div>\n          <Dialog.Trigger handle={dialog} id=\"trigger\">\n            Trigger\n          </Dialog.Trigger>\n          <Dialog.Root handle={dialog}>\n            <Dialog.Portal>\n              <Dialog.Popup data-testid=\"content\">Content</Dialog.Popup>\n            </Dialog.Portal>\n          </Dialog.Root>\n        </div>,\n      );\n\n      const trigger = screen.getByRole('button', { name: 'Trigger' });\n      expect(screen.queryByRole('dialog')).toBe(null);\n\n      await act(() => dialog.open('trigger'));\n      await waitFor(() => {\n        expect(screen.queryByRole('dialog')).not.toBe(null);\n      });\n\n      expect(screen.getByTestId('content').textContent).toBe('Content');\n      expect(trigger).toHaveAttribute('aria-expanded', 'true');\n\n      await act(() => dialog.close());\n      await waitFor(() => {\n        expect(screen.queryByRole('dialog')).toBe(null);\n      });\n\n      expect(trigger).toHaveAttribute('aria-expanded', 'false');\n    });\n\n    it('sets the payload assosiated with the trigger', async () => {\n      const dialog = Dialog.createHandle<number>();\n      await render(\n        <div>\n          <Dialog.Trigger handle={dialog} id=\"trigger1\" payload={1}>\n            Trigger 1\n          </Dialog.Trigger>\n          <Dialog.Trigger handle={dialog} id=\"trigger2\" payload={2}>\n            Trigger 2\n          </Dialog.Trigger>\n          <Dialog.Root handle={dialog}>\n            {({ payload }: { payload: number | undefined }) => (\n              <Dialog.Portal>\n                <Dialog.Popup data-testid=\"content\">{payload}</Dialog.Popup>\n              </Dialog.Portal>\n            )}\n          </Dialog.Root>\n        </div>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n      expect(screen.queryByRole('dialog')).toBe(null);\n\n      await act(() => dialog.open('trigger2'));\n      await waitFor(() => {\n        expect(screen.queryByRole('dialog')).not.toBe(null);\n      });\n\n      expect(screen.getByTestId('content').textContent).toBe('2');\n      expect(trigger2).toHaveAttribute('aria-expanded', 'true');\n      expect(trigger1).not.toHaveAttribute('aria-expanded', 'true');\n\n      await act(() => dialog.close());\n      await waitFor(() => {\n        expect(screen.queryByRole('dialog')).toBe(null);\n      });\n\n      expect(trigger2).toHaveAttribute('aria-expanded', 'false');\n    });\n\n    it('sets the payload programmatically', async () => {\n      const dialog = Dialog.createHandle<number>();\n      await render(\n        <div>\n          <Dialog.Trigger handle={dialog} id=\"trigger1\" payload={1}>\n            Trigger 1\n          </Dialog.Trigger>\n          <Dialog.Trigger handle={dialog} id=\"trigger2\" payload={2}>\n            Trigger 2\n          </Dialog.Trigger>\n          <Dialog.Root handle={dialog}>\n            {({ payload }: { payload: number | undefined }) => (\n              <Dialog.Portal>\n                <Dialog.Popup data-testid=\"content\">{payload}</Dialog.Popup>\n              </Dialog.Portal>\n            )}\n          </Dialog.Root>\n        </div>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n      expect(screen.queryByRole('dialog')).toBe(null);\n\n      await act(() => dialog.openWithPayload(8));\n      await waitFor(() => {\n        expect(screen.queryByRole('dialog')).not.toBe(null);\n      });\n\n      expect(screen.getByTestId('content').textContent).toBe('8');\n      expect(trigger1).not.toHaveAttribute('aria-expanded', 'true');\n      expect(trigger2).not.toHaveAttribute('aria-expanded', 'true');\n\n      await act(() => dialog.close());\n      await waitFor(() => {\n        expect(screen.queryByRole('dialog')).toBe(null);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/dialog/root/DialogRoot.spec.tsx",
    "content": "import * as React from 'react';\nimport { expectType } from '#test-utils';\nimport { Dialog } from '@base-ui/react/dialog';\n\nconst numberPayloadHandle = Dialog.createHandle<number>();\n\nconst rootWithDirectChildren = (\n  <Dialog.Root handle={numberPayloadHandle}>\n    <Dialog.Portal />\n  </Dialog.Root>\n);\n\nconst rootWithFunctionChildren = (\n  <Dialog.Root handle={numberPayloadHandle}>\n    {({ payload }) => {\n      expectType<number | undefined, typeof payload>(payload);\n      return null;\n    }}\n  </Dialog.Root>\n);\n\nconst triggerWithPayload = <Dialog.Trigger handle={numberPayloadHandle} payload={42} />;\nconst triggerWithoutPayload = <Dialog.Trigger handle={numberPayloadHandle} />;\n\nconst triggerWithInvalidPayload = (\n  // @ts-expect-error\n  <Dialog.Trigger handle={numberPayloadHandle} payload={'invalid'} />\n);\n"
  },
  {
    "path": "packages/react/src/dialog/root/DialogRoot.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { act, fireEvent, screen, waitFor, flushMicrotasks } from '@mui/internal-test-utils';\nimport { Dialog } from '@base-ui/react/dialog';\nimport { createRenderer, isJSDOM, popupConformanceTests } from '#test-utils';\nimport { Menu } from '@base-ui/react/menu';\nimport { Select } from '@base-ui/react/select';\nimport { NumberField } from '@base-ui/react/number-field';\nimport { ScrollArea } from '@base-ui/react/scroll-area';\nimport { REASONS } from '../../utils/reasons';\n\ndescribe('<Dialog.Root />', () => {\n  const { render } = createRenderer();\n\n  beforeEach(() => {\n    globalThis.BASE_UI_ANIMATIONS_DISABLED = true;\n  });\n\n  popupConformanceTests({\n    createComponent: (props) => (\n      <Dialog.Root {...props.root}>\n        <Dialog.Trigger {...props.trigger}>Open dialog</Dialog.Trigger>\n        <Dialog.Portal {...props.portal}>\n          <Dialog.Popup {...props.popup}>Dialog</Dialog.Popup>\n        </Dialog.Portal>\n      </Dialog.Root>\n    ),\n    render,\n    triggerMouseAction: 'click',\n    expectedPopupRole: 'dialog',\n  });\n\n  describe.for([\n    { name: 'contained triggers', Component: ContainedTriggerDialog },\n    { name: 'detached triggers', Component: DetachedTriggerDialog },\n    { name: 'multiple detached triggers', Component: MultipleDetachedTriggersDialog },\n  ])('when using $name', ({ Component: TestDialog }) => {\n    it('ARIA attributes', async () => {\n      await render(\n        <TestDialog\n          rootProps={{ modal: false, open: true }}\n          popupProps={{\n            children: (\n              <React.Fragment>\n                <Dialog.Title>title text</Dialog.Title>\n                <Dialog.Description>description text</Dialog.Description>\n              </React.Fragment>\n            ),\n          }}\n          includeBackdrop\n        />,\n      );\n\n      const popup = screen.queryByRole('dialog');\n      expect(popup).not.toBe(null);\n\n      expect(screen.getByText('title text').getAttribute('id')).toBe(\n        popup?.getAttribute('aria-labelledby'),\n      );\n      expect(screen.getByText('description text').getAttribute('id')).toBe(\n        popup?.getAttribute('aria-describedby'),\n      );\n    });\n\n    describe('prop: onOpenChange', () => {\n      it('calls onOpenChange with the new open state', async () => {\n        const handleOpenChange = vi.fn();\n\n        const { user } = await render(\n          <TestDialog rootProps={{ onOpenChange: handleOpenChange }} />,\n        );\n\n        expect(handleOpenChange.mock.calls.length).toBe(0);\n\n        const openButton = screen.getByText('Open');\n        await user.click(openButton);\n\n        expect(handleOpenChange.mock.calls.length).toBe(1);\n        expect(handleOpenChange.mock.calls[0][0]).toBe(true);\n\n        const closeButton = screen.getByText('Close');\n        await user.click(closeButton);\n\n        expect(handleOpenChange.mock.calls.length).toBe(2);\n        expect(handleOpenChange.mock.calls[1][0]).toBe(false);\n      });\n\n      it('calls onOpenChange with the reason for change when clicked on trigger and close button', async () => {\n        const handleOpenChange = vi.fn();\n\n        const { user } = await render(\n          <TestDialog rootProps={{ onOpenChange: handleOpenChange }} />,\n        );\n\n        const openButton = screen.getByText('Open');\n        await user.click(openButton);\n\n        expect(handleOpenChange.mock.calls.length).toBe(1);\n        expect(handleOpenChange.mock.calls[0][1].reason).toBe(REASONS.triggerPress);\n\n        const closeButton = screen.getByText('Close');\n        await user.click(closeButton);\n\n        expect(handleOpenChange.mock.calls.length).toBe(2);\n        expect(handleOpenChange.mock.calls[1][1].reason).toBe(REASONS.closePress);\n      });\n\n      it('calls onOpenChange with the reason for change when pressed Esc while the dialog is open', async () => {\n        const handleOpenChange = vi.fn();\n\n        const { user } = await render(\n          <TestDialog rootProps={{ defaultOpen: true, onOpenChange: handleOpenChange }} />,\n        );\n\n        await user.keyboard('[Escape]');\n\n        expect(handleOpenChange.mock.calls.length).toBe(1);\n        expect(handleOpenChange.mock.calls[0][1].reason).toBe(REASONS.escapeKey);\n      });\n\n      it('calls onOpenChange with the reason for change when user clicks backdrop while the modal dialog is open', async () => {\n        const handleOpenChange = vi.fn();\n\n        const { user } = await render(\n          <TestDialog rootProps={{ defaultOpen: true, onOpenChange: handleOpenChange }} />,\n        );\n\n        await user.click(screen.getByRole('presentation', { hidden: true }));\n\n        expect(handleOpenChange.mock.calls.length).toBe(1);\n        expect(handleOpenChange.mock.calls[0][1].reason).toBe(REASONS.outsidePress);\n      });\n\n      it('calls onOpenChange with the reason for change when user clicks outside while the non-modal dialog is open', async () => {\n        const handleOpenChange = vi.fn();\n\n        const { user } = await render(\n          <TestDialog\n            rootProps={{ defaultOpen: true, onOpenChange: handleOpenChange, modal: false }}\n          />,\n        );\n\n        await user.click(document.body);\n\n        expect(handleOpenChange.mock.calls.length).toBe(1);\n        expect(handleOpenChange.mock.calls[0][1].reason).toBe(REASONS.outsidePress);\n      });\n\n      describe.skipIf(isJSDOM)('clicks on user backdrop', () => {\n        it('detects clicks on user backdrop', async () => {\n          const handleOpenChange = vi.fn();\n\n          const { user } = await render(\n            <TestDialog\n              rootProps={{ defaultOpen: true, onOpenChange: handleOpenChange }}\n              popupProps={{ style: { position: 'fixed', zIndex: 10 } }}\n              includeBackdrop\n            />,\n          );\n\n          await user.click(screen.getByTestId('backdrop'));\n\n          expect(handleOpenChange.mock.calls.length).toBe(1);\n          expect(handleOpenChange.mock.calls[0][1].reason).toBe(REASONS.outsidePress);\n        });\n\n        it('does not change open state on non-main button clicks', async () => {\n          const handleOpenChange = vi.fn();\n\n          const { user } = await render(\n            <TestDialog\n              rootProps={{ defaultOpen: true, onOpenChange: handleOpenChange }}\n              includeBackdrop\n            />,\n          );\n\n          const backdrop = screen.getByTestId('backdrop');\n          await user.pointer([{ target: backdrop }, { keys: '[MouseRight]', target: backdrop }]);\n\n          expect(handleOpenChange.mock.calls.length).toBe(0);\n        });\n      });\n\n      it('cancel() prevents opening while uncontrolled', async () => {\n        const { user } = await render(\n          <TestDialog\n            rootProps={{\n              onOpenChange: (nextOpen, eventDetails) => {\n                if (nextOpen) {\n                  eventDetails.cancel();\n                }\n              },\n            }}\n          />,\n        );\n\n        const openButton = screen.getByText('Open');\n        await user.click(openButton);\n        await flushMicrotasks();\n\n        expect(screen.queryByRole('dialog')).toBe(null);\n      });\n    });\n\n    describe('prop: modal', () => {\n      it('makes other interactive elements on the page inert when a modal dialog is open', async () => {\n        await render(<TestDialog rootProps={{ defaultOpen: true, modal: true }} />);\n\n        expect(screen.getByRole('presentation', { hidden: true })).not.toBe(null);\n      });\n\n      it('does not make other interactive elements on the page inert when a non-modal dialog is open', async () => {\n        await render(<TestDialog rootProps={{ defaultOpen: true, modal: false }} />);\n\n        expect(screen.queryByRole('presentation')).toBe(null);\n      });\n    });\n\n    describe('prop: disablePointerDismissal', () => {\n      (\n        [\n          [true, false],\n          [false, true],\n          [undefined, true],\n        ] as const\n      ).forEach(([disablePointerDismissal, expectDismissed]) => {\n        it(`${expectDismissed ? 'closes' : 'does not close'} the dialog when clicking outside if disablePointerDismissal=${disablePointerDismissal}`, async () => {\n          const handleOpenChange = vi.fn();\n\n          await render(\n            <div data-testid=\"outside\">\n              <TestDialog\n                rootProps={{\n                  defaultOpen: true,\n                  onOpenChange: handleOpenChange,\n                  disablePointerDismissal,\n                  modal: false,\n                }}\n              />\n            </div>,\n          );\n\n          const outside = screen.getByTestId('outside');\n\n          fireEvent.mouseDown(outside);\n          fireEvent.click(outside);\n          expect(handleOpenChange.mock.calls.length === 1).toBe(expectDismissed);\n\n          if (expectDismissed) {\n            expect(screen.queryByRole('dialog')).toBe(null);\n          } else {\n            expect(screen.queryByRole('dialog')).not.toBe(null);\n          }\n        });\n      });\n    });\n\n    describe('outside press event with backdrops', () => {\n      it('uses intentional outside press with user backdrop (mouse): closes on click, not on mousedown', async () => {\n        const handleOpenChange = vi.fn();\n\n        await render(\n          <TestDialog\n            rootProps={{ defaultOpen: true, onOpenChange: handleOpenChange, modal: false }}\n            includeBackdrop\n          />,\n        );\n\n        const backdrop = screen.getByTestId('backdrop');\n\n        fireEvent.mouseDown(backdrop);\n        expect(screen.queryByRole('dialog')).not.toBe(null);\n        expect(handleOpenChange.mock.calls.length).toBe(0);\n\n        fireEvent.click(backdrop);\n        await waitFor(() => {\n          expect(screen.queryByRole('dialog')).toBe(null);\n        });\n        expect(handleOpenChange.mock.calls.length).toBe(1);\n      });\n\n      it('uses intentional outside press with internal backdrop (modal=true): closes on click, not on mousedown', async () => {\n        const handleOpenChange = vi.fn();\n\n        await render(\n          <TestDialog\n            rootProps={{ defaultOpen: true, onOpenChange: handleOpenChange, modal: true }}\n          />,\n        );\n\n        const internalBackdrop = screen.getByRole('presentation', { hidden: true });\n\n        fireEvent.mouseDown(internalBackdrop);\n        expect(screen.queryByRole('dialog')).not.toBe(null);\n        expect(handleOpenChange.mock.calls.length).toBe(0);\n\n        fireEvent.click(internalBackdrop);\n        await waitFor(() => {\n          expect(screen.queryByRole('dialog')).toBe(null);\n        });\n        expect(handleOpenChange.mock.calls.length).toBe(1);\n      });\n\n      it('closing via intentional outside press with user backdrop (modal=true): works when portaled into a shadow DOM', async () => {\n        const handleOpenChange = vi.fn();\n\n        const container = document.body.appendChild(document.createElement('div'));\n        const shadowRoot = container.attachShadow({ mode: 'open' });\n\n        await render(\n          <TestDialog\n            rootProps={{ defaultOpen: true, onOpenChange: handleOpenChange, modal: true }}\n            portalProps={{ container: shadowRoot }}\n            includeBackdrop\n          />,\n        );\n\n        const backdrop = shadowRoot.querySelector('[data-testid=\"backdrop\"]') as HTMLElement;\n\n        fireEvent.click(backdrop);\n        await waitFor(() => {\n          expect(shadowRoot.querySelector('[role=\"dialog\"]')).toBe(null);\n        });\n        expect(handleOpenChange.mock.calls.length).toBe(1);\n      });\n\n      it('closing via outside press: works when clicking another element inside the same shadow root', async () => {\n        const handleOpenChange = vi.fn();\n\n        const host = document.body.appendChild(document.createElement('div'));\n        const shadowRoot = host.attachShadow({ mode: 'open' });\n        const container = document.createElement('div');\n        shadowRoot.appendChild(container);\n\n        try {\n          await render(\n            <React.Fragment>\n              <button data-testid=\"outside\">Outside</button>\n              <TestDialog\n                rootProps={{ defaultOpen: true, onOpenChange: handleOpenChange, modal: false }}\n                portalProps={{ container: shadowRoot }}\n              />\n            </React.Fragment>,\n            { container },\n          );\n\n          const outsideButton = shadowRoot.querySelector('[data-testid=\"outside\"]') as HTMLElement;\n\n          fireEvent.click(outsideButton);\n\n          await waitFor(() => {\n            expect(shadowRoot.querySelector('[role=\"dialog\"]')).toBe(null);\n          });\n\n          expect(handleOpenChange.mock.calls.length).toBe(1);\n          expect(handleOpenChange.mock.calls[0][1].reason).toBe(REASONS.outsidePress);\n        } finally {\n          await act(async () => {\n            host.remove();\n          });\n        }\n      });\n\n      it('closing via outside press: works when clicking outside the shadow root', async () => {\n        const handleOpenChange = vi.fn();\n\n        const host = document.body.appendChild(document.createElement('div'));\n        const shadowRoot = host.attachShadow({ mode: 'open' });\n        const container = document.createElement('div');\n        shadowRoot.appendChild(container);\n\n        try {\n          await render(\n            <TestDialog\n              rootProps={{ defaultOpen: true, onOpenChange: handleOpenChange, modal: false }}\n              portalProps={{ container: shadowRoot }}\n            />,\n            { container },\n          );\n\n          fireEvent.click(document.body);\n\n          await waitFor(() => {\n            expect(shadowRoot.querySelector('[role=\"dialog\"]')).toBe(null);\n          });\n\n          expect(handleOpenChange.mock.calls.length).toBe(1);\n          expect(handleOpenChange.mock.calls[0][1].reason).toBe(REASONS.outsidePress);\n        } finally {\n          await act(async () => {\n            host.remove();\n          });\n        }\n      });\n\n      it('closing via outside press: works for a modal dialog when clicking outside the shadow root', async () => {\n        const handleOpenChange = vi.fn();\n\n        const host = document.body.appendChild(document.createElement('div'));\n        const shadowRoot = host.attachShadow({ mode: 'open' });\n        const container = document.createElement('div');\n        shadowRoot.appendChild(container);\n\n        try {\n          await render(\n            <TestDialog\n              rootProps={{ defaultOpen: true, onOpenChange: handleOpenChange, modal: true }}\n              portalProps={{ container: shadowRoot }}\n            />,\n            { container },\n          );\n\n          fireEvent.click(document.body);\n\n          await waitFor(() => {\n            expect(shadowRoot.querySelector('[role=\"dialog\"]')).toBe(null);\n          });\n\n          expect(handleOpenChange.mock.calls.length).toBe(1);\n          expect(handleOpenChange.mock.calls[0][1].reason).toBe(REASONS.outsidePress);\n        } finally {\n          await act(async () => {\n            host.remove();\n          });\n        }\n      });\n    });\n\n    it.skipIf(isJSDOM)('waits for the exit transition to finish before unmounting', async () => {\n      const css = `\n    .dialog {\n      opacity: 0;\n      transition: opacity 200ms;\n    }\n    .dialog[data-open] {\n      opacity: 1;\n    }\n  `;\n\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n      const notifyTransitionEnd = vi.fn();\n\n      function TransitionTest(props: { open: boolean }) {\n        return (\n          <React.Fragment>\n            {/* eslint-disable-next-line react/no-danger */}\n            <style dangerouslySetInnerHTML={{ __html: css }} />\n            <TestDialog\n              rootProps={{ open: props.open, modal: false }}\n              portalProps={{ keepMounted: true }}\n              popupProps={{\n                className: 'dialog',\n                onTransitionEnd: notifyTransitionEnd,\n                children: null,\n              }}\n            />\n          </React.Fragment>\n        );\n      }\n\n      const { setProps } = await render(<TransitionTest open />);\n\n      await setProps({ open: false });\n      expect(screen.queryByRole('dialog')).not.toBe(null);\n\n      await waitFor(() => {\n        expect(screen.queryByRole('dialog')).toBe(null);\n      });\n\n      expect(notifyTransitionEnd.mock.calls.length).toBe(1);\n    });\n\n    describe('prop: modal', () => {\n      it('should render an internal backdrop when `true`', async () => {\n        const { user } = await render(\n          <div>\n            <TestDialog rootProps={{ modal: true }} />\n            <button>Outside</button>\n          </div>,\n        );\n\n        const trigger = screen.getByTestId('trigger');\n\n        await user.click(trigger);\n\n        await waitFor(() => {\n          expect(screen.queryByRole('dialog')).not.toBe(null);\n        });\n\n        const popup = screen.getByRole('dialog');\n\n        // focus guard -> internal backdrop\n        expect(popup.previousElementSibling?.previousElementSibling).toHaveAttribute(\n          'role',\n          'presentation',\n        );\n      });\n\n      it('should not render an internal backdrop when `false`', async () => {\n        const { user } = await render(\n          <div>\n            <TestDialog rootProps={{ modal: false }} />\n            <button>Outside</button>\n          </div>,\n        );\n\n        const trigger = screen.getByTestId('trigger');\n\n        await user.click(trigger);\n\n        await waitFor(() => {\n          expect(screen.queryByRole('dialog')).not.toBe(null);\n        });\n\n        const popup = screen.getByRole('dialog');\n\n        // focus guard -> internal backdrop\n        expect(popup.previousElementSibling?.previousElementSibling).toBe(null);\n      });\n    });\n\n    it('does not dismiss previous modal dialog when clicking new modal dialog', async () => {\n      function App() {\n        const [openNested, setOpenNested] = React.useState(false);\n        const [openNested2, setOpenNested2] = React.useState(false);\n\n        return (\n          <div>\n            <TestDialog\n              triggerProps={{ children: 'Open base' }}\n              popupProps={{\n                children: <button onClick={() => setOpenNested(true)}>Open nested 1</button>,\n              }}\n            />\n            <TestDialog\n              rootProps={{ open: openNested, onOpenChange: setOpenNested }}\n              popupProps={{\n                children: <button onClick={() => setOpenNested2(true)}>Open nested 2</button>,\n              }}\n            />\n            <TestDialog\n              rootProps={{ open: openNested2, onOpenChange: setOpenNested2 }}\n              popupProps={{ children: 'Final nested' }}\n            />\n          </div>\n        );\n      }\n\n      const { user } = await render(<App />);\n\n      const trigger = screen.getByRole('button', { name: 'Open base' });\n      await user.click(trigger);\n\n      const nestedButton1 = screen.getByRole('button', { name: 'Open nested 1' });\n      await user.click(nestedButton1);\n\n      const nestedButton2 = screen.getByRole('button', { name: 'Open nested 2' });\n      await user.click(nestedButton2);\n\n      const finalDialog = screen.getByText('Final nested');\n\n      expect(finalDialog).not.toBe(null);\n    });\n\n    it('dismisses non-nested dialogs one by one', async () => {\n      function App() {\n        const [openNested, setOpenNested] = React.useState(false);\n        const [openNested2, setOpenNested2] = React.useState(false);\n\n        return (\n          <div>\n            <TestDialog\n              triggerProps={{ children: 'Open base' }}\n              popupProps={\n                {\n                  'data-testid': 'level-1',\n                  children: <button onClick={() => setOpenNested(true)}>Open nested 1</button>,\n                } as Dialog.Popup.Props\n              }\n            />\n            <TestDialog\n              rootProps={{ open: openNested, onOpenChange: setOpenNested }}\n              popupProps={\n                {\n                  'data-testid': 'level-2',\n                  children: <button onClick={() => setOpenNested2(true)}>Open nested 2</button>,\n                } as Dialog.Popup.Props\n              }\n            />\n            <TestDialog\n              rootProps={{ open: openNested2, onOpenChange: setOpenNested2 }}\n              popupProps={\n                { 'data-testid': 'level-3', children: 'Final nested' } as Dialog.Popup.Props\n              }\n            />\n          </div>\n        );\n      }\n\n      await render(<App />);\n\n      const trigger = screen.getByRole('button', { name: 'Open base' });\n      fireEvent.click(trigger);\n\n      const nestedButton1 = screen.getByRole('button', { name: 'Open nested 1' });\n      fireEvent.click(nestedButton1);\n\n      const nestedButton2 = screen.getByRole('button', { name: 'Open nested 2' });\n      fireEvent.click(nestedButton2);\n\n      const backdrops = Array.from(document.querySelectorAll('[role=\"presentation\"]'));\n      fireEvent.click(backdrops[backdrops.length - 1]);\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('level-3')).toBe(null);\n      });\n\n      fireEvent.click(backdrops[backdrops.length - 2]);\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('level-2')).toBe(null);\n      });\n\n      fireEvent.click(backdrops[backdrops.length - 3]);\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('level-1')).toBe(null);\n      });\n    });\n\n    describe.skipIf(isJSDOM)('nested popups', () => {\n      it('should not dismiss the dialog when dismissing outside a nested modal menu', async () => {\n        const { user } = await render(\n          <TestDialog\n            popupProps={{\n              children: (\n                <Menu.Root>\n                  <Menu.Trigger>Open menu</Menu.Trigger>\n                  <Menu.Portal>\n                    <Menu.Positioner data-testid=\"menu-positioner\">\n                      <Menu.Popup>\n                        <Menu.Item>Item</Menu.Item>\n                      </Menu.Popup>\n                    </Menu.Positioner>\n                  </Menu.Portal>\n                </Menu.Root>\n              ),\n            }}\n          />,\n        );\n\n        const dialogTrigger = screen.getByRole('button', { name: 'Open' });\n        await user.click(dialogTrigger);\n\n        await waitFor(() => {\n          expect(screen.queryByRole('dialog')).not.toBe(null);\n        });\n\n        const menuTrigger = screen.getByRole('button', { name: 'Open menu' });\n\n        await user.click(menuTrigger);\n\n        await waitFor(() => {\n          expect(screen.queryByRole('menu')).not.toBe(null);\n        });\n\n        const menuPositioner = screen.getByTestId('menu-positioner');\n        const menuInternalBackdrop = menuPositioner.previousElementSibling as HTMLElement;\n\n        await user.click(menuInternalBackdrop);\n\n        await waitFor(() => {\n          expect(screen.queryByRole('menu')).toBe(null);\n        });\n        await waitFor(() => {\n          expect(screen.queryByRole('dialog')).not.toBe(null);\n        });\n\n        const dialogPopup = screen.getByTestId('dialog-popup');\n        const dialogInternalBackdrop = dialogPopup.previousElementSibling\n          ?.previousElementSibling as HTMLElement;\n\n        await user.click(dialogInternalBackdrop);\n\n        await waitFor(() => {\n          expect(screen.queryByRole('dialog')).toBe(null);\n        });\n      });\n\n      it('should not dismiss the dialog when dismissing outside a nested select popup', async () => {\n        const { user } = await render(\n          <TestDialog\n            popupProps={{\n              children: (\n                <Select.Root>\n                  <Select.Trigger data-testid=\"select-trigger\">Open select</Select.Trigger>\n                  <Select.Portal>\n                    <Select.Positioner data-testid=\"select-positioner\">\n                      <Select.Popup>\n                        <Select.Item>Item</Select.Item>\n                      </Select.Popup>\n                    </Select.Positioner>\n                  </Select.Portal>\n                </Select.Root>\n              ),\n            }}\n          />,\n        );\n\n        const dialogTrigger = screen.getByRole('button', { name: 'Open' });\n        await user.click(dialogTrigger);\n\n        await waitFor(() => {\n          expect(screen.queryByRole('dialog')).not.toBe(null);\n        });\n\n        const selectTrigger = screen.getByTestId('select-trigger');\n\n        await user.click(selectTrigger);\n\n        await waitFor(() => {\n          expect(screen.queryByRole('listbox')).not.toBe(null);\n        });\n\n        const selectPositioner = screen.getByTestId('select-positioner');\n        const selectInternalBackdrop = selectPositioner.previousElementSibling as HTMLElement;\n\n        await user.click(selectInternalBackdrop);\n\n        await waitFor(() => {\n          expect(screen.queryByRole('listbox')).toBe(null);\n        });\n        await waitFor(() => {\n          expect(screen.queryByRole('dialog')).not.toBe(null);\n        });\n\n        const dialogPopup = screen.getByTestId('dialog-popup');\n        const dialogInternalBackdrop = dialogPopup.previousElementSibling\n          ?.previousElementSibling as HTMLElement;\n\n        await user.click(dialogInternalBackdrop);\n\n        await waitFor(() => {\n          expect(screen.queryByRole('dialog')).toBe(null);\n        });\n      });\n\n      it('should not close the parent menu when Escape is pressed in a nested dialog', async () => {\n        const { user } = await render(\n          <Menu.Root>\n            <Menu.Trigger>Open menu</Menu.Trigger>\n            <Menu.Portal>\n              <Menu.Positioner>\n                <Menu.Popup>\n                  <TestDialog\n                    triggerProps={{ children: 'Open dialog' }}\n                    triggerWrapper={(trigger) => (\n                      <Menu.Item closeOnClick={false} render={trigger} nativeButton />\n                    )}\n                  ></TestDialog>\n                </Menu.Popup>\n              </Menu.Positioner>\n            </Menu.Portal>\n          </Menu.Root>,\n        );\n\n        const menuTrigger = screen.getByRole('button', { name: 'Open menu' });\n        await user.click(menuTrigger);\n\n        await waitFor(() => {\n          expect(screen.queryByRole('menu')).not.toBe(null);\n        });\n\n        const dialogTrigger = screen.getByRole('menuitem', { name: 'Open dialog' });\n        await user.click(dialogTrigger);\n\n        await waitFor(() => {\n          expect(screen.queryByRole('dialog')).not.toBe(null);\n        });\n\n        await user.keyboard('[Escape]');\n\n        await waitFor(() => {\n          expect(screen.queryByRole('dialog')).toBe(null);\n        });\n        await waitFor(() => {\n          expect(screen.queryByRole('menu')).not.toBe(null);\n        });\n      });\n    });\n\n    describe('prop: actionsRef', () => {\n      it('unmounts the dialog when the `unmount` method is called', async () => {\n        const actionsRef = {\n          current: {\n            unmount: vi.fn(),\n            close: vi.fn(),\n          },\n        };\n\n        const { user } = await render(\n          <TestDialog\n            rootProps={{\n              actionsRef,\n              onOpenChange: (open, details) => {\n                details.preventUnmountOnClose();\n              },\n            }}\n          />,\n        );\n\n        const trigger = screen.getByRole('button', { name: 'Open' });\n        await user.click(trigger);\n\n        await waitFor(() => {\n          expect(screen.queryByRole('dialog')).not.toBe(null);\n        });\n\n        await user.click(trigger);\n\n        await waitFor(() => {\n          expect(screen.queryByRole('dialog')).not.toBe(null);\n        });\n\n        await act(async () => actionsRef.current.unmount());\n\n        await waitFor(() => {\n          expect(screen.queryByRole('dialog')).toBe(null);\n        });\n      });\n    });\n\n    describe.skipIf(isJSDOM)('pointerdown removal', () => {\n      it('moves focus to the popup when a focused child is removed on pointerdown and outside press still dismisses', async () => {\n        function Test() {\n          const [showButton, setShowButton] = React.useState(true);\n          return (\n            <TestDialog\n              rootProps={{ defaultOpen: true, modal: 'trap-focus' }}\n              popupProps={{\n                children: showButton && (\n                  <button data-testid=\"remove\" onPointerDown={() => setShowButton(false)}>\n                    Remove on pointer down\n                  </button>\n                ),\n              }}\n            />\n          );\n        }\n\n        const { user } = await render(<Test />);\n\n        const removeButton = screen.getByTestId('remove');\n        await waitFor(() => {\n          expect(removeButton).toHaveFocus();\n        });\n        fireEvent.pointerDown(removeButton);\n\n        const popup = screen.getByTestId('dialog-popup');\n        await waitFor(() => {\n          expect(popup).toHaveFocus();\n        });\n\n        await user.click(document.body);\n\n        await waitFor(() => {\n          expect(screen.queryByRole('dialog')).toBe(null);\n        });\n      });\n\n      it('dismisses on first outside click after NumberField scrub interaction (pointer lock path)', async () => {\n        const originalRequestPointerLock = Element.prototype.requestPointerLock;\n        const requestPointerLockSpy = vi.fn(() => Promise.resolve());\n\n        try {\n          Element.prototype.requestPointerLock =\n            requestPointerLockSpy as typeof originalRequestPointerLock;\n\n          await render(\n            <ContainedTriggerDialog\n              rootProps={{ defaultOpen: true, modal: false }}\n              popupProps={{\n                children: (\n                  <NumberField.Root defaultValue={100}>\n                    <NumberField.ScrubArea data-testid=\"scrub-area\">\n                      <span>Amount</span>\n                    </NumberField.ScrubArea>\n                    <NumberField.Input aria-label=\"Amount\" />\n                  </NumberField.Root>\n                ),\n              }}\n            />,\n          );\n\n          const scrubArea = screen.getByTestId('scrub-area');\n\n          fireEvent.pointerDown(scrubArea, { pointerType: 'mouse', button: 0 });\n          fireEvent.mouseDown(scrubArea, { button: 0 });\n          fireEvent.pointerUp(document.body, { pointerType: 'mouse', button: 0 });\n          fireEvent.mouseUp(document.body, { button: 0 });\n          await flushMicrotasks();\n\n          fireEvent.click(document.body);\n\n          await waitFor(() => {\n            expect(screen.queryByRole('dialog')).toBe(null);\n          });\n          expect(requestPointerLockSpy.mock.calls.length).toBe(1);\n        } finally {\n          Element.prototype.requestPointerLock = originalRequestPointerLock;\n        }\n      });\n    });\n\n    describe.skipIf(isJSDOM)('prop: onOpenChangeComplete', () => {\n      it('is called on close when there is no exit animation defined', async () => {\n        const onOpenChangeComplete = vi.fn();\n\n        function Test() {\n          const [open, setOpen] = React.useState(true);\n          return (\n            <div>\n              <button onClick={() => setOpen(false)}>Close externally</button>\n              <TestDialog rootProps={{ open, onOpenChangeComplete }} />\n            </div>\n          );\n        }\n\n        const { user } = await render(<Test />);\n\n        const closeButton = screen.getByText('Close externally');\n        await user.click(closeButton);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('dialog-popup')).toBe(null);\n        });\n\n        expect(onOpenChangeComplete.mock.calls[0][0]).toBe(true);\n        expect(onOpenChangeComplete.mock.lastCall?.[0]).toBe(false);\n      });\n\n      it('is called on close when the exit animation finishes', async () => {\n        globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n        const onOpenChangeComplete = vi.fn();\n\n        function Test() {\n          const style = `\n        @keyframes test-anim {\n          to {\n            opacity: 0;\n          }\n        }\n\n        .animation-test-indicator[data-ending-style] {\n          animation: test-anim 1ms;\n        }\n      `;\n\n          const [open, setOpen] = React.useState(true);\n\n          return (\n            <div>\n              {/* eslint-disable-next-line react/no-danger */}\n              <style dangerouslySetInnerHTML={{ __html: style }} />\n              <button onClick={() => setOpen(false)}>Close externally</button>\n              <TestDialog\n                rootProps={{ open, onOpenChangeComplete }}\n                popupProps={{\n                  className: 'animation-test-indicator',\n                }}\n              />\n            </div>\n          );\n        }\n\n        const { user } = await render(<Test />);\n\n        expect(screen.getByTestId('dialog-popup')).not.toBe(null);\n\n        // Wait for open animation to finish\n        await waitFor(() => {\n          expect(onOpenChangeComplete.mock.calls[0][0]).toBe(true);\n        });\n\n        const closeButton = screen.getByText('Close externally');\n        await user.click(closeButton);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('dialog-popup')).toBe(null);\n        });\n\n        expect(onOpenChangeComplete.mock.lastCall?.[0]).toBe(false);\n      });\n\n      it('is called on open when there is no enter animation defined', async () => {\n        const onOpenChangeComplete = vi.fn();\n\n        function Test() {\n          const [open, setOpen] = React.useState(false);\n          return (\n            <div>\n              <button onClick={() => setOpen(true)}>Open externally</button>\n              <TestDialog rootProps={{ open, onOpenChangeComplete }} />\n            </div>\n          );\n        }\n\n        const { user } = await render(<Test />);\n\n        const openButton = screen.getByText('Open externally');\n        await user.click(openButton);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('dialog-popup')).not.toBe(null);\n        });\n\n        expect(onOpenChangeComplete.mock.calls.length).toBe(2);\n        expect(onOpenChangeComplete.mock.calls[0][0]).toBe(true);\n      });\n\n      it('is called on open when the enter animation finishes', async () => {\n        globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n        const onOpenChangeComplete = vi.fn();\n\n        function Test() {\n          const style = `\n          @keyframes test-anim {\n            from {\n              opacity: 0;\n            }\n          }\n\n          .animation-test-indicator[data-starting-style] {\n            animation: test-anim 1ms;\n          }\n        `;\n\n          const [open, setOpen] = React.useState(false);\n\n          return (\n            <div>\n              {/* eslint-disable-next-line react/no-danger */}\n              <style dangerouslySetInnerHTML={{ __html: style }} />\n              <button onClick={() => setOpen(true)}>Open externally</button>\n              <TestDialog\n                rootProps={{ open, onOpenChange: setOpen, onOpenChangeComplete }}\n                popupProps={{\n                  className: 'animation-test-indicator',\n                }}\n              />\n            </div>\n          );\n        }\n\n        const { user } = await render(<Test />);\n\n        const openButton = screen.getByText('Open externally');\n        await user.click(openButton);\n\n        // Wait for open animation to finish\n        await waitFor(() => {\n          expect(onOpenChangeComplete.mock.calls[0][0]).toBe(true);\n        });\n\n        expect(screen.queryByTestId('dialog-popup')).not.toBe(null);\n      });\n\n      it('waits for a restarted enter animation to finish', async () => {\n        globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n        const onOpenChangeComplete = vi.fn();\n\n        function Test() {\n          const style = `\n            @keyframes test-enter-a {\n              from {\n                opacity: 0;\n              }\n            }\n\n            @keyframes test-enter-b {\n              from {\n                opacity: 0;\n              }\n            }\n\n            .animation-test-indicator.animation-a[data-open] {\n              animation: test-enter-a 50ms linear;\n            }\n\n            .animation-test-indicator.animation-b[data-open] {\n              animation: test-enter-b 50ms linear;\n            }\n          `;\n\n          const [open, setOpen] = React.useState(false);\n          const [variant, setVariant] = React.useState<'a' | 'b'>('a');\n\n          return (\n            <div>\n              {/* eslint-disable-next-line react/no-danger */}\n              <style dangerouslySetInnerHTML={{ __html: style }} />\n              <button onClick={() => setOpen(true)}>Open externally</button>\n              <button onClick={() => setVariant((v) => (v === 'a' ? 'b' : 'a'))}>\n                Swap animation\n              </button>\n              <TestDialog\n                rootProps={{ open, onOpenChange: setOpen, onOpenChangeComplete }}\n                popupProps={{\n                  className: `animation-test-indicator animation-${variant}`,\n                }}\n              />\n            </div>\n          );\n        }\n\n        const { user } = await render(<Test />);\n\n        const openButton = screen.getByText('Open externally');\n        await user.click(openButton);\n\n        const popup = screen.getByTestId('dialog-popup');\n        await waitFor(() => {\n          expect(popup.getAnimations().length).not.toBe(0);\n        });\n\n        const swapButton = screen.getByText('Swap animation');\n        await user.click(swapButton);\n\n        await flushMicrotasks();\n        expect(onOpenChangeComplete.mock.calls.length).toBe(0);\n\n        await waitFor(() => {\n          expect(onOpenChangeComplete.mock.calls.length).toBe(1);\n          expect(onOpenChangeComplete.mock.calls[0][0]).toBe(true);\n        });\n      });\n\n      it('does not get called on open when dismissed during the enter animation', async () => {\n        globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n        const onOpenChangeComplete = vi.fn();\n\n        function Test() {\n          const style = `\n            .animation-test-indicator {\n              opacity: 0;\n              transition: opacity 200ms linear;\n            }\n\n            .animation-test-indicator[data-open] {\n              opacity: 1;\n            }\n\n            .animation-test-indicator[data-open][data-starting-style] {\n              opacity: 0;\n            }\n\n            .animation-test-indicator[data-ending-style] {\n              opacity: 0;\n            }\n          `;\n\n          const [open, setOpen] = React.useState(false);\n\n          return (\n            <div>\n              {/* eslint-disable-next-line react/no-danger */}\n              <style dangerouslySetInnerHTML={{ __html: style }} />\n              <button onClick={() => setOpen(true)}>Open externally</button>\n              <TestDialog\n                rootProps={{ open, onOpenChange: setOpen, onOpenChangeComplete }}\n                popupProps={{\n                  className: 'animation-test-indicator',\n                }}\n              />\n            </div>\n          );\n        }\n\n        const { user } = await render(<Test />);\n\n        const openButton = screen.getByText('Open externally');\n        await user.click(openButton);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('dialog-popup')).not.toBe(null);\n        });\n\n        const popup = screen.getByTestId('dialog-popup');\n        await waitFor(() => {\n          const animations = popup.getAnimations();\n          expect(animations.length).not.toBe(0);\n          expect(animations.some((anim) => anim.playState !== 'finished')).toBe(true);\n        });\n\n        await user.click(document.body);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('dialog-popup')).toBe(null);\n        });\n\n        expect(onOpenChangeComplete.mock.calls.length).toBe(1);\n        expect(onOpenChangeComplete.mock.calls[0][0]).toBe(false);\n      });\n\n      it('does not get called on mount when not open', async () => {\n        const onOpenChangeComplete = vi.fn();\n\n        await render(<TestDialog rootProps={{ onOpenChangeComplete }} />);\n\n        expect(onOpenChangeComplete.mock.calls.length).toBe(0);\n      });\n    });\n  });\n\n  it.skipIf(isJSDOM)(\n    'keeps focus trapped when dialog content contains a non-scrollable scroll area',\n    async () => {\n      const { user } = await render(\n        <div>\n          <button data-testid=\"outside-before\">Outside before</button>\n          <ContainedTriggerDialog\n            rootProps={{ defaultOpen: true, modal: 'trap-focus' }}\n            popupProps={{\n              children: (\n                <ScrollArea.Root style={{ width: 200, height: 200 }}>\n                  <ScrollArea.Viewport\n                    data-testid=\"viewport\"\n                    style={{ width: '100%', height: '100%' }}\n                  >\n                    <div style={{ width: 100, height: 100 }}>Non-scrollable content</div>\n                  </ScrollArea.Viewport>\n                </ScrollArea.Root>\n              ),\n            }}\n            omitTrigger\n          />\n          <button data-testid=\"outside-after\">Outside after</button>\n        </div>,\n      );\n\n      const popup = screen.getByRole('dialog');\n      const outsideBefore = screen.getByTestId('outside-before');\n      const outsideAfter = screen.getByTestId('outside-after');\n\n      await waitFor(() => {\n        expect(popup.contains(document.activeElement)).toBe(true);\n      });\n\n      await user.keyboard('[Tab]');\n      expect(popup.contains(document.activeElement)).toBe(true);\n\n      await user.keyboard('[Tab]');\n      expect(popup.contains(document.activeElement)).toBe(true);\n\n      await user.keyboard('[ShiftLeft>][Tab][/ShiftLeft]');\n      expect(popup.contains(document.activeElement)).toBe(true);\n\n      expect(outsideBefore).not.toHaveFocus();\n      expect(outsideAfter).not.toHaveFocus();\n    },\n  );\n});\n\ntype TestDialogProps = {\n  rootProps?: Omit<Dialog.Root.Props, 'children'>;\n  triggerProps?: Dialog.Trigger.Props;\n  portalProps?: Dialog.Portal.Props;\n  popupProps?: Dialog.Popup.Props;\n  omitTrigger?: boolean;\n  includeBackdrop?: boolean;\n  triggerWrapper?: (trigger: React.ReactElement) => React.ReactElement;\n};\n\nfunction ContainedTriggerDialog(props: TestDialogProps) {\n  const {\n    rootProps,\n    triggerProps,\n    portalProps,\n    popupProps,\n    omitTrigger = false,\n    includeBackdrop = false,\n    triggerWrapper = (trigger) => trigger,\n  } = props;\n\n  const { children: triggerChildren, ...restTriggerProps } = triggerProps ?? {};\n  const { children: popupChildren, ...restPopupProps } = popupProps ?? {};\n  const { children: portalChildren, ...restPortalProps } = portalProps ?? {};\n\n  return (\n    <Dialog.Root {...rootProps}>\n      {!omitTrigger\n        ? triggerWrapper(\n            <Dialog.Trigger data-testid=\"trigger\" {...restTriggerProps}>\n              {triggerChildren ?? 'Open'}\n            </Dialog.Trigger>,\n          )\n        : null}\n      <Dialog.Portal {...restPortalProps}>\n        {portalChildren ?? (\n          <React.Fragment>\n            {includeBackdrop ? (\n              <Dialog.Backdrop\n                data-testid=\"backdrop\"\n                style={{ position: 'fixed', zIndex: 10, inset: 0 }}\n              />\n            ) : null}\n            <Dialog.Popup\n              data-testid=\"dialog-popup\"\n              style={{ position: 'fixed', zIndex: 10 }}\n              {...restPopupProps}\n            >\n              {popupChildren ?? (\n                <React.Fragment>\n                  <p>Dialog content</p>\n                  <Dialog.Close>Close</Dialog.Close>\n                </React.Fragment>\n              )}\n            </Dialog.Popup>\n          </React.Fragment>\n        )}\n      </Dialog.Portal>\n    </Dialog.Root>\n  );\n}\n\nfunction DetachedTriggerDialog(props: Omit<TestDialogProps, 'omitTrigger'>) {\n  const { triggerProps, triggerWrapper = (trigger) => trigger } = props;\n\n  const { children: triggerChildren, ...restTriggerProps } = triggerProps ?? {};\n  const dialogHandle = Dialog.createHandle();\n\n  return (\n    <React.Fragment>\n      {triggerWrapper(\n        <Dialog.Trigger data-testid=\"trigger\" {...restTriggerProps} handle={dialogHandle}>\n          {triggerChildren ?? 'Open'}\n        </Dialog.Trigger>,\n      )}\n      <ContainedTriggerDialog\n        {...props}\n        rootProps={{ ...props.rootProps, handle: dialogHandle }}\n        omitTrigger\n      />\n    </React.Fragment>\n  );\n}\n\nfunction MultipleDetachedTriggersDialog(props: Omit<TestDialogProps, 'omitTrigger'>) {\n  const { triggerProps, triggerWrapper = (trigger) => trigger } = props;\n\n  const { children: triggerChildren, ...restTriggerProps } = triggerProps ?? {};\n  const dialogHandle = Dialog.createHandle();\n\n  return (\n    <React.Fragment>\n      {triggerWrapper(\n        <Dialog.Trigger data-testid=\"trigger\" {...restTriggerProps} handle={dialogHandle}>\n          {triggerChildren ?? 'Open'}\n        </Dialog.Trigger>,\n      )}\n      <Dialog.Trigger data-testid=\"trigger-2\" handle={dialogHandle}>\n        Open another\n      </Dialog.Trigger>\n      <ContainedTriggerDialog\n        {...props}\n        rootProps={{ ...props.rootProps, handle: dialogHandle }}\n        omitTrigger\n      />\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "packages/react/src/dialog/root/DialogRoot.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useRefWithInit } from '@base-ui/utils/useRefWithInit';\nimport { useOnFirstRender } from '@base-ui/utils/useOnFirstRender';\nimport { useDialogRoot } from './useDialogRoot';\nimport { DialogRootContext, useDialogRootContext } from './DialogRootContext';\nimport type { BaseUIChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport { DialogStore } from '../store/DialogStore';\nimport { DialogHandle } from '../store/DialogHandle';\nimport { type PayloadChildRenderFunction } from '../../utils/popups';\n\n/**\n * Groups all parts of the dialog.\n * Doesn’t render its own HTML element.\n *\n * Documentation: [Base UI Dialog](https://base-ui.com/react/components/dialog)\n */\nexport function DialogRoot<Payload>(props: DialogRoot.Props<Payload>) {\n  const {\n    children,\n    open: openProp,\n    defaultOpen = false,\n    onOpenChange,\n    onOpenChangeComplete,\n    disablePointerDismissal = false,\n    modal = true,\n    actionsRef,\n    handle,\n    triggerId: triggerIdProp,\n    defaultTriggerId: defaultTriggerIdProp = null,\n  } = props;\n\n  const parentDialogRootContext = useDialogRootContext(true);\n  const nested = Boolean(parentDialogRootContext);\n\n  const store = useRefWithInit(() => {\n    return (\n      handle?.store ??\n      new DialogStore<Payload>({\n        open: defaultOpen,\n        openProp,\n        activeTriggerId: defaultTriggerIdProp,\n        triggerIdProp,\n        modal,\n        disablePointerDismissal,\n        nested,\n      })\n    );\n  }).current;\n\n  // Support initially open state when uncontrolled\n  useOnFirstRender(() => {\n    if (openProp === undefined && store.state.open === false && defaultOpen === true) {\n      store.update({\n        open: true,\n        activeTriggerId: defaultTriggerIdProp,\n      });\n    }\n  });\n\n  store.useControlledProp('openProp', openProp);\n  store.useControlledProp('triggerIdProp', triggerIdProp);\n\n  store.useSyncedValues({ disablePointerDismissal, nested, modal });\n  store.useContextCallback('onOpenChange', onOpenChange);\n  store.useContextCallback('onOpenChangeComplete', onOpenChangeComplete);\n\n  const payload = store.useState('payload') as Payload | undefined;\n\n  useDialogRoot({\n    store,\n    actionsRef,\n    parentContext: parentDialogRootContext?.store.context,\n    onOpenChange,\n    triggerIdProp,\n  });\n\n  const contextValue: DialogRootContext<Payload> = React.useMemo(() => ({ store }), [store]);\n\n  return (\n    <DialogRootContext.Provider value={contextValue as DialogRootContext}>\n      {typeof children === 'function' ? children({ payload }) : children}\n    </DialogRootContext.Provider>\n  );\n}\n\nexport interface DialogRootState {}\n\nexport interface DialogRootProps<Payload = unknown> {\n  /**\n   * Whether the dialog is currently open.\n   */\n  open?: boolean | undefined;\n  /**\n   * Whether the dialog is initially open.\n   *\n   * To render a controlled dialog, use the `open` prop instead.\n   * @default false\n   */\n  defaultOpen?: boolean | undefined;\n  /**\n   * Determines if the dialog enters a modal state when open.\n   * - `true`: user interaction is limited to just the dialog: focus is trapped, document page scroll is locked, and pointer interactions on outside elements are disabled.\n   * - `false`: user interaction with the rest of the document is allowed.\n   * - `'trap-focus'`: focus is trapped inside the dialog, but document page scroll is not locked and pointer interactions outside of it remain enabled.\n   *\n   * When `modal` is `true` or `'trap-focus'`, render `<Dialog.Close>` inside `<Dialog.Popup>` so\n   * touch screen readers can escape the popup.\n   * @default true\n   */\n  modal?: boolean | 'trap-focus' | undefined;\n  /**\n   * Event handler called when the dialog is opened or closed.\n   */\n  onOpenChange?: ((open: boolean, eventDetails: DialogRoot.ChangeEventDetails) => void) | undefined;\n  /**\n   * Event handler called after any animations complete when the dialog is opened or closed.\n   */\n  onOpenChangeComplete?: ((open: boolean) => void) | undefined;\n  /**\n   * Determines whether the dialog should close on outside clicks.\n   * @default false\n   */\n  disablePointerDismissal?: boolean | undefined;\n  /**\n   * A ref to imperative actions.\n   * - `unmount`: When specified, the dialog will not be unmounted when closed.\n   * Instead, the `unmount` function must be called to unmount the dialog manually.\n   * Useful when the dialog's animation is controlled by an external library.\n   * - `close`: Closes the dialog imperatively when called.\n   */\n  actionsRef?: React.RefObject<DialogRoot.Actions | null> | undefined;\n  /**\n   * A handle to associate the dialog with a trigger.\n   * If specified, allows external triggers to control the dialog's open state.\n   * Can be created with the Dialog.createHandle() method.\n   */\n  handle?: DialogHandle<Payload> | undefined;\n  /**\n   * The content of the dialog.\n   * This can be a regular React node or a render function that receives the `payload` of the active trigger.\n   */\n  children?: React.ReactNode | PayloadChildRenderFunction<Payload>;\n  /**\n   * ID of the trigger that the dialog is associated with.\n   * This is useful in conjunction with the `open` prop to create a controlled dialog.\n   * There's no need to specify this prop when the popover is uncontrolled (i.e. when the `open` prop is not set).\n   */\n  triggerId?: string | null | undefined;\n  /**\n   * ID of the trigger that the dialog is associated with.\n   * This is useful in conjunction with the `defaultOpen` prop to create an initially open dialog.\n   */\n  defaultTriggerId?: string | null | undefined;\n}\n\nexport interface DialogRootActions {\n  unmount: () => void;\n  close: () => void;\n}\n\nexport type DialogRootChangeEventReason =\n  | typeof REASONS.triggerPress\n  | typeof REASONS.outsidePress\n  | typeof REASONS.escapeKey\n  | typeof REASONS.closePress\n  | typeof REASONS.focusOut\n  | typeof REASONS.imperativeAction\n  | typeof REASONS.none;\n\nexport type DialogRootChangeEventDetails =\n  BaseUIChangeEventDetails<DialogRoot.ChangeEventReason> & {\n    preventUnmountOnClose(): void;\n  };\n\nexport namespace DialogRoot {\n  export type State = DialogRootState;\n  export type Props<Payload = unknown> = DialogRootProps<Payload>;\n  export type Actions = DialogRootActions;\n  export type ChangeEventReason = DialogRootChangeEventReason;\n  export type ChangeEventDetails = DialogRootChangeEventDetails;\n}\n"
  },
  {
    "path": "packages/react/src/dialog/root/DialogRootContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { DialogStore } from '../store/DialogStore';\n\nexport interface DialogRootContext<Payload = unknown> {\n  store: DialogStore<Payload>;\n}\n\nexport const DialogRootContext = React.createContext<DialogRootContext | undefined>(undefined);\n\nexport function useDialogRootContext(optional?: false): DialogRootContext;\nexport function useDialogRootContext(optional: true): DialogRootContext | undefined;\nexport function useDialogRootContext(optional?: boolean) {\n  const dialogRootContext = React.useContext(DialogRootContext);\n\n  if (optional === false && dialogRootContext === undefined) {\n    throw new Error(\n      'Base UI: DialogRootContext is missing. Dialog parts must be placed within <Dialog.Root>.',\n    );\n  }\n\n  return dialogRootContext;\n}\n"
  },
  {
    "path": "packages/react/src/dialog/root/useDialogRoot.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useScrollLock } from '@base-ui/utils/useScrollLock';\nimport {\n  useDismiss,\n  useInteractions,\n  useRole,\n  useSyncedFloatingRootContext,\n} from '../../floating-ui-react';\nimport { contains, getTarget } from '../../floating-ui-react/utils';\nimport { useOpenInteractionType } from '../../utils/useOpenInteractionType';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport { type DialogRoot } from './DialogRoot';\nimport { DialogStore } from '../store/DialogStore';\nimport { useImplicitActiveTrigger, useOpenStateTransitions } from '../../utils/popups';\n\nexport function useDialogRoot(params: UseDialogRootParameters): UseDialogRootReturnValue {\n  const { store, parentContext, actionsRef } = params;\n\n  const open = store.useState('open');\n  const disablePointerDismissal = store.useState('disablePointerDismissal');\n  const modal = store.useState('modal');\n  const popupElement = store.useState('popupElement');\n\n  const { openMethod, triggerProps } = useOpenInteractionType(open);\n\n  useImplicitActiveTrigger(store);\n  const { forceUnmount } = useOpenStateTransitions(open, store);\n\n  const createDialogEventDetails = useStableCallback((reason: DialogRoot.ChangeEventReason) => {\n    const details: DialogRoot.ChangeEventDetails =\n      createChangeEventDetails<DialogRoot.ChangeEventReason>(\n        reason,\n      ) as DialogRoot.ChangeEventDetails;\n    details.preventUnmountOnClose = () => {\n      store.set('preventUnmountingOnClose', true);\n    };\n\n    return details;\n  });\n\n  const handleImperativeClose = React.useCallback(() => {\n    store.setOpen(false, createDialogEventDetails(REASONS.imperativeAction));\n  }, [store, createDialogEventDetails]);\n\n  React.useImperativeHandle(\n    actionsRef,\n    () => ({ unmount: forceUnmount, close: handleImperativeClose }),\n    [forceUnmount, handleImperativeClose],\n  );\n\n  const floatingRootContext = useSyncedFloatingRootContext({\n    popupStore: store,\n    onOpenChange: store.setOpen,\n    treatPopupAsFloatingElement: true,\n    noEmit: true,\n  });\n\n  const [ownNestedOpenDialogs, setOwnNestedOpenDialogs] = React.useState(0);\n  const isTopmost = ownNestedOpenDialogs === 0;\n\n  const role = useRole(floatingRootContext);\n  const dismiss = useDismiss(floatingRootContext, {\n    outsidePressEvent() {\n      if (store.context.internalBackdropRef.current || store.context.backdropRef.current) {\n        return 'intentional';\n      }\n      // Ensure `aria-hidden` on outside elements is removed immediately\n      // on outside press when trapping focus.\n      return {\n        mouse: modal === 'trap-focus' ? 'sloppy' : 'intentional',\n        touch: 'sloppy',\n      };\n    },\n    outsidePress(event) {\n      if (!store.context.outsidePressEnabledRef.current) {\n        return false;\n      }\n\n      // For mouse events, only accept left button (button 0)\n      // For touch events, a single touch is equivalent to left button\n      if ('button' in event && event.button !== 0) {\n        return false;\n      }\n      if ('touches' in event && event.touches.length !== 1) {\n        return false;\n      }\n      const target = getTarget(event) as Element | null;\n      if (isTopmost && !disablePointerDismissal) {\n        const eventTarget = target as Element | null;\n        // Only close if the click occurred on the dialog's owning backdrop.\n        // This supports multiple modal dialogs that aren't nested in the React tree:\n        // https://github.com/mui/base-ui/issues/1320\n        if (modal) {\n          return store.context.internalBackdropRef.current || store.context.backdropRef.current\n            ? store.context.internalBackdropRef.current === eventTarget ||\n                store.context.backdropRef.current === eventTarget ||\n                (contains(eventTarget, popupElement) &&\n                  !eventTarget?.hasAttribute('data-base-ui-portal'))\n            : true;\n        }\n        return true;\n      }\n      return false;\n    },\n    escapeKey: isTopmost,\n  });\n\n  useScrollLock(open && modal === true, popupElement);\n\n  const { getReferenceProps, getFloatingProps, getTriggerProps } = useInteractions([role, dismiss]);\n\n  // Listen for nested open/close events on this store to maintain the count\n  store.useContextCallback('onNestedDialogOpen', (ownChildrenCount) => {\n    setOwnNestedOpenDialogs(ownChildrenCount + 1);\n  });\n\n  store.useContextCallback('onNestedDialogClose', () => {\n    setOwnNestedOpenDialogs(0);\n  });\n\n  // Notify parent of our open/close state using parent callbacks, if any\n  React.useEffect(() => {\n    if (parentContext?.onNestedDialogOpen && open) {\n      parentContext.onNestedDialogOpen(ownNestedOpenDialogs);\n    }\n    if (parentContext?.onNestedDialogClose && !open) {\n      parentContext.onNestedDialogClose();\n    }\n    return () => {\n      if (parentContext?.onNestedDialogClose && open) {\n        parentContext.onNestedDialogClose();\n      }\n    };\n  }, [open, parentContext, ownNestedOpenDialogs]);\n\n  const activeTriggerProps = React.useMemo(\n    () => getReferenceProps(triggerProps),\n    [getReferenceProps, triggerProps],\n  );\n\n  const inactiveTriggerProps = React.useMemo(\n    () => getTriggerProps(triggerProps),\n    [getTriggerProps, triggerProps],\n  );\n\n  const popupProps = React.useMemo(() => getFloatingProps(), [getFloatingProps]);\n\n  store.useSyncedValues({\n    openMethod,\n    activeTriggerProps,\n    inactiveTriggerProps,\n    popupProps,\n    floatingRootContext,\n    nestedOpenDialogCount: ownNestedOpenDialogs,\n  });\n}\n\nexport interface UseDialogRootSharedParameters {}\n\nexport interface UseDialogRootParameters {\n  store: DialogStore<any>;\n  actionsRef?: DialogRoot.Props['actionsRef'] | undefined;\n  parentContext?: DialogStore<unknown>['context'] | undefined;\n  onOpenChange: DialogRoot.Props['onOpenChange'];\n  triggerIdProp?: string | null | undefined;\n}\n\nexport type UseDialogRootReturnValue = void;\n\nexport interface UseDialogRootState {}\n"
  },
  {
    "path": "packages/react/src/dialog/store/DialogHandle.ts",
    "content": "import { DialogStore } from './DialogStore';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\n\n/**\n * A handle to control a Dialog imperatively and to associate detached triggers with it.\n */\nexport class DialogHandle<Payload> {\n  /**\n   * Internal store holding the dialog state.\n   * @internal\n   */\n  public readonly store: DialogStore<Payload>;\n\n  constructor(store?: DialogStore<Payload>) {\n    this.store = store ?? new DialogStore<Payload>();\n  }\n\n  /**\n   * Opens the dialog and associates it with the trigger with the given id.\n   * The trigger, if provided, must be a Dialog.Trigger component with this handle passed as a prop.\n   *\n   * This method should only be called in an event handler or an effect (not during rendering).\n   *\n   * @param triggerId ID of the trigger to associate with the dialog. If null, the dialog will open without a trigger association.\n   */\n  open(triggerId: string | null) {\n    const triggerElement = triggerId\n      ? (this.store.context.triggerElements.getById(triggerId) as HTMLElement | undefined)\n      : undefined;\n\n    if (process.env.NODE_ENV !== 'production') {\n      if (triggerId && !triggerElement) {\n        console.warn(\n          `Base UI: DialogHandle.open: No trigger found with id \"${triggerId}\". The dialog will open, but the trigger will not be associated with the dialog.`,\n        );\n      }\n    }\n\n    this.store.setOpen(\n      true,\n      createChangeEventDetails(REASONS.imperativeAction, undefined, triggerElement),\n    );\n  }\n\n  /**\n   * Opens the dialog and sets the payload.\n   * Does not associate the dialog with any trigger.\n   *\n   * @param payload Payload to set when opening the dialog.\n   */\n  openWithPayload(payload: Payload) {\n    this.store.set('payload', payload);\n    this.store.setOpen(\n      true,\n      createChangeEventDetails(REASONS.imperativeAction, undefined, undefined),\n    );\n  }\n\n  /**\n   * Closes the dialog.\n   */\n  close() {\n    this.store.setOpen(\n      false,\n      createChangeEventDetails(REASONS.imperativeAction, undefined, undefined),\n    );\n  }\n\n  /**\n   * Indicates whether the dialog is currently open.\n   */\n  get isOpen() {\n    return this.store.state.open;\n  }\n}\n\n/**\n * Creates a new handle to connect a Dialog.Root with detached Dialog.Trigger components.\n */\nexport function createDialogHandle<Payload>(): DialogHandle<Payload> {\n  return new DialogHandle<Payload>();\n}\n"
  },
  {
    "path": "packages/react/src/dialog/store/DialogStore.ts",
    "content": "import * as React from 'react';\nimport { createSelector, ReactStore } from '@base-ui/utils/store';\nimport { type InteractionType } from '@base-ui/utils/useEnhancedClickHandler';\nimport { type DialogRoot } from '../root/DialogRoot';\nimport type { FloatingUIOpenChangeDetails } from '../../utils/types';\nimport {\n  createInitialPopupStoreState,\n  PopupStoreContext,\n  popupStoreSelectors,\n  PopupStoreState,\n  PopupTriggerMap,\n} from '../../utils/popups';\n\nexport type State<Payload> = PopupStoreState<Payload> & {\n  modal: boolean | 'trap-focus';\n  disablePointerDismissal: boolean;\n  openMethod: InteractionType | null;\n  nested: boolean;\n  nestedOpenDialogCount: number;\n  titleElementId: string | undefined;\n  descriptionElementId: string | undefined;\n  viewportElement: HTMLElement | null;\n  role: 'dialog' | 'alertdialog';\n};\n\ntype Context = PopupStoreContext<DialogRoot.ChangeEventDetails> & {\n  readonly popupRef: React.RefObject<HTMLElement | null>;\n  readonly backdropRef: React.RefObject<HTMLDivElement | null>;\n  readonly internalBackdropRef: React.RefObject<HTMLDivElement | null>;\n  readonly outsidePressEnabledRef: React.MutableRefObject<boolean>;\n  readonly onNestedDialogOpen?: ((ownChildrenCount: number) => void) | undefined;\n  readonly onNestedDialogClose?: (() => void) | undefined;\n};\n\nconst selectors = {\n  ...popupStoreSelectors,\n  modal: createSelector((state: State<unknown>) => state.modal),\n  nested: createSelector((state: State<unknown>) => state.nested),\n  nestedOpenDialogCount: createSelector((state: State<unknown>) => state.nestedOpenDialogCount),\n  disablePointerDismissal: createSelector((state: State<unknown>) => state.disablePointerDismissal),\n  openMethod: createSelector((state: State<unknown>) => state.openMethod),\n  descriptionElementId: createSelector((state: State<unknown>) => state.descriptionElementId),\n  titleElementId: createSelector((state: State<unknown>) => state.titleElementId),\n  viewportElement: createSelector((state: State<unknown>) => state.viewportElement),\n  role: createSelector((state: State<unknown>) => state.role),\n};\n\nexport class DialogStore<Payload> extends ReactStore<\n  Readonly<State<Payload>>,\n  Context,\n  typeof selectors\n> {\n  constructor(initialState?: Partial<State<Payload>>) {\n    super(\n      createInitialState<Payload>(initialState),\n      {\n        popupRef: React.createRef<HTMLElement>(),\n        backdropRef: React.createRef<HTMLDivElement>(),\n        internalBackdropRef: React.createRef<HTMLDivElement>(),\n        outsidePressEnabledRef: { current: true },\n        triggerElements: new PopupTriggerMap(),\n        onOpenChange: undefined,\n        onOpenChangeComplete: undefined,\n      },\n      selectors,\n    );\n  }\n\n  public setOpen = (\n    nextOpen: boolean,\n    eventDetails: Omit<DialogRoot.ChangeEventDetails, 'preventUnmountOnClose'>,\n  ) => {\n    (eventDetails as DialogRoot.ChangeEventDetails).preventUnmountOnClose = () => {\n      this.set('preventUnmountingOnClose', true);\n    };\n\n    if (!nextOpen && eventDetails.trigger == null && this.state.activeTriggerId != null) {\n      // When closing the dialog, pass the old trigger to the onOpenChange event\n      // so it's not reset too early (potentially causing focus issues in controlled scenarios).\n      eventDetails.trigger = this.state.activeTriggerElement ?? undefined;\n    }\n\n    this.context.onOpenChange?.(nextOpen, eventDetails as DialogRoot.ChangeEventDetails);\n\n    if (eventDetails.isCanceled) {\n      return;\n    }\n\n    const details: FloatingUIOpenChangeDetails = {\n      open: nextOpen,\n      nativeEvent: eventDetails.event,\n      reason: eventDetails.reason,\n      nested: this.state.nested,\n    };\n\n    this.state.floatingRootContext.context.events?.emit('openchange', details);\n\n    const updatedState: Partial<State<Payload>> = {\n      open: nextOpen,\n    };\n\n    // If a popup is closing, the `trigger` may be null.\n    // We want to keep the previous value so that exit animations are played and focus is returned correctly.\n    const newTriggerId = eventDetails.trigger?.id ?? null;\n    if (newTriggerId || nextOpen) {\n      updatedState.activeTriggerId = newTriggerId;\n      updatedState.activeTriggerElement = eventDetails.trigger ?? null;\n    }\n\n    this.update(updatedState);\n  };\n}\n\nfunction createInitialState<Payload>(initialState: Partial<State<Payload>> = {}): State<Payload> {\n  return {\n    ...createInitialPopupStoreState<Payload>(),\n    modal: true,\n    disablePointerDismissal: false,\n    popupElement: null,\n    viewportElement: null,\n    descriptionElementId: undefined,\n    titleElementId: undefined,\n    openMethod: null,\n    nested: false,\n    nestedOpenDialogCount: 0,\n    role: 'dialog',\n    ...initialState,\n  };\n}\n"
  },
  {
    "path": "packages/react/src/dialog/title/DialogTitle.test.tsx",
    "content": "import { Dialog } from '@base-ui/react/dialog';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Dialog.Title />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Dialog.Title />, () => ({\n    refInstanceof: window.HTMLHeadingElement,\n    render: (node) => {\n      return render(\n        <Dialog.Root open modal={false}>\n          <Dialog.Portal>\n            <Dialog.Popup>{node}</Dialog.Popup>\n          </Dialog.Portal>\n        </Dialog.Root>,\n      );\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/dialog/title/DialogTitle.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useDialogRootContext } from '../root/DialogRootContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useBaseUiId } from '../../utils/useBaseUiId';\nimport { type BaseUIComponentProps } from '../../utils/types';\n\n/**\n * A heading that labels the dialog.\n * Renders an `<h2>` element.\n *\n * Documentation: [Base UI Dialog](https://base-ui.com/react/components/dialog)\n */\nexport const DialogTitle = React.forwardRef(function DialogTitle(\n  componentProps: DialogTitle.Props,\n  forwardedRef: React.ForwardedRef<HTMLHeadingElement>,\n) {\n  const { render, className, id: idProp, ...elementProps } = componentProps;\n  const { store } = useDialogRootContext();\n\n  const id = useBaseUiId(idProp);\n\n  store.useSyncedValueWithCleanup('titleElementId', id);\n\n  return useRenderElement('h2', componentProps, {\n    ref: forwardedRef,\n    props: [{ id }, elementProps],\n  });\n});\n\nexport interface DialogTitleProps extends BaseUIComponentProps<'h2', DialogTitleState> {}\n\nexport interface DialogTitleState {}\n\nexport namespace DialogTitle {\n  export type Props = DialogTitleProps;\n  export type State = DialogTitleState;\n}\n"
  },
  {
    "path": "packages/react/src/dialog/trigger/DialogTrigger.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { Dialog } from '@base-ui/react/dialog';\nimport { screen } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Dialog.Trigger />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Dialog.Trigger />, () => ({\n    refInstanceof: window.HTMLButtonElement,\n    testComponentPropWith: 'button',\n    button: true,\n    render: (node) => {\n      return render(\n        <Dialog.Root open modal={false}>\n          {node}\n        </Dialog.Root>,\n      );\n    },\n  }));\n\n  describe('prop: disabled', () => {\n    it('disables the dialog', async () => {\n      const { user } = await render(\n        <Dialog.Root modal={false}>\n          <Dialog.Trigger disabled />\n          <Dialog.Portal>\n            <Dialog.Backdrop />\n            <Dialog.Popup>\n              <Dialog.Title>title text</Dialog.Title>\n            </Dialog.Popup>\n          </Dialog.Portal>\n        </Dialog.Root>,\n      );\n\n      const trigger = screen.getByRole('button');\n      expect(trigger).toHaveAttribute('disabled');\n      expect(trigger).toHaveAttribute('data-disabled');\n\n      await user.click(trigger);\n      expect(screen.queryByText('title text')).toBe(null);\n\n      await user.keyboard('[Tab]');\n      expect(document.activeElement).not.toBe(trigger);\n    });\n\n    it('custom element', async () => {\n      const { user } = await render(\n        <Dialog.Root modal={false}>\n          <Dialog.Trigger disabled render={<span />} nativeButton={false} />\n          <Dialog.Portal>\n            <Dialog.Backdrop />\n            <Dialog.Popup>\n              <Dialog.Title>title text</Dialog.Title>\n            </Dialog.Popup>\n          </Dialog.Portal>\n        </Dialog.Root>,\n      );\n\n      const trigger = screen.getByRole('button');\n      expect(trigger).not.toHaveAttribute('disabled');\n      expect(trigger).toHaveAttribute('data-disabled');\n      expect(trigger).toHaveAttribute('aria-disabled', 'true');\n\n      await user.click(trigger);\n      expect(screen.queryByText('title text')).toBe(null);\n\n      await user.keyboard('[Tab]');\n      expect(document.activeElement).not.toBe(trigger);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/dialog/trigger/DialogTrigger.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useDialogRootContext } from '../root/DialogRootContext';\nimport { useButton } from '../../use-button/useButton';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport type { BaseUIComponentProps, NativeButtonProps } from '../../utils/types';\nimport { triggerOpenStateMapping } from '../../utils/popupStateMapping';\nimport { CLICK_TRIGGER_IDENTIFIER } from '../../utils/constants';\nimport { DialogHandle } from '../store/DialogHandle';\nimport { useTriggerDataForwarding } from '../../utils/popups';\nimport { useBaseUiId } from '../../utils/useBaseUiId';\nimport { useClick, useInteractions } from '../../floating-ui-react';\n\n/**\n * A button that opens the dialog.\n * Renders a `<button>` element.\n *\n * Documentation: [Base UI Dialog](https://base-ui.com/react/components/dialog)\n */\nexport const DialogTrigger = React.forwardRef(function DialogTrigger(\n  componentProps: DialogTrigger.Props,\n  forwardedRef: React.ForwardedRef<HTMLButtonElement>,\n) {\n  const {\n    render,\n    className,\n    disabled = false,\n    nativeButton = true,\n    id: idProp,\n    payload,\n    handle,\n    ...elementProps\n  } = componentProps;\n\n  const dialogRootContext = useDialogRootContext(true);\n  const store = handle?.store ?? dialogRootContext?.store;\n  if (!store) {\n    throw new Error(\n      'Base UI: <Dialog.Trigger> must be used within <Dialog.Root> or provided with a handle.',\n    );\n  }\n\n  const thisTriggerId = useBaseUiId(idProp);\n  const floatingContext = store.useState('floatingRootContext');\n  const isOpenedByThisTrigger = store.useState('isOpenedByTrigger', thisTriggerId);\n\n  const triggerElementRef = React.useRef<HTMLElement | null>(null);\n\n  const { registerTrigger, isMountedByThisTrigger } = useTriggerDataForwarding(\n    thisTriggerId,\n    triggerElementRef,\n    store,\n    {\n      payload,\n    },\n  );\n\n  const { getButtonProps, buttonRef } = useButton({\n    disabled,\n    native: nativeButton,\n  });\n\n  const click = useClick(floatingContext, { enabled: floatingContext != null });\n\n  const localInteractionProps = useInteractions([click]);\n\n  const state: DialogTriggerState = {\n    disabled,\n    open: isOpenedByThisTrigger,\n  };\n\n  const rootTriggerProps = store.useState('triggerProps', isMountedByThisTrigger);\n\n  return useRenderElement('button', componentProps, {\n    state,\n    ref: [buttonRef, forwardedRef, registerTrigger, triggerElementRef],\n    props: [\n      localInteractionProps.getReferenceProps(),\n      rootTriggerProps,\n      { [CLICK_TRIGGER_IDENTIFIER as string]: '', id: thisTriggerId },\n      elementProps,\n      getButtonProps,\n    ],\n    stateAttributesMapping: triggerOpenStateMapping,\n  });\n}) as DialogTrigger;\n\nexport interface DialogTrigger {\n  <Payload>(\n    componentProps: DialogTriggerProps<Payload> & React.RefAttributes<HTMLElement>,\n  ): React.JSX.Element;\n}\n\nexport interface DialogTriggerProps<Payload = unknown>\n  extends NativeButtonProps, BaseUIComponentProps<'button', DialogTriggerState> {\n  /**\n   * A handle to associate the trigger with a dialog.\n   * Can be created with the Dialog.createHandle() method.\n   */\n  handle?: DialogHandle<Payload> | undefined;\n  /**\n   * A payload to pass to the dialog when it is opened.\n   */\n  payload?: Payload | undefined;\n  /**\n   * ID of the trigger. In addition to being forwarded to the rendered element,\n   * it is also used to specify the active trigger for the dialogs in controlled mode (with the DialogRoot `triggerId` prop).\n   */\n  id?: string | undefined;\n}\n\nexport interface DialogTriggerState {\n  /**\n   * Whether the dialog is currently disabled.\n   */\n  disabled: boolean;\n  /**\n   * Whether the dialog is currently open.\n   */\n  open: boolean;\n}\n\nexport namespace DialogTrigger {\n  export type Props<Payload = unknown> = DialogTriggerProps<Payload>;\n  export type State = DialogTriggerState;\n}\n"
  },
  {
    "path": "packages/react/src/dialog/trigger/DialogTriggerDataAttributes.ts",
    "content": "import { CommonTriggerDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum DialogTriggerDataAttributes {\n  /**\n   * Present when the trigger is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the corresponding dialog is open.\n   */\n  popupOpen = CommonTriggerDataAttributes.popupOpen,\n}\n"
  },
  {
    "path": "packages/react/src/dialog/viewport/DialogViewport.test.tsx",
    "content": "import { expect } from 'vitest';\nimport * as React from 'react';\nimport { Dialog } from '@base-ui/react/dialog';\nimport { createRenderer, describeConformance } from '#test-utils';\nimport { screen } from '@mui/internal-test-utils';\n\ndescribe('<Dialog.Viewport />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Dialog.Viewport />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render: (node) => {\n      return render(\n        <Dialog.Root open modal={false}>\n          <Dialog.Portal>\n            {node}\n            <Dialog.Popup />\n          </Dialog.Portal>\n        </Dialog.Root>,\n      );\n    },\n  }));\n\n  it('renders only when the dialog is mounted by default', async () => {\n    function App() {\n      const [open, setOpen] = React.useState(false);\n      return (\n        <Dialog.Root open={open} onOpenChange={setOpen} modal={false}>\n          <Dialog.Trigger>Open</Dialog.Trigger>\n          <Dialog.Portal>\n            <Dialog.Viewport data-testid=\"viewport\">\n              <Dialog.Popup data-testid=\"popup\">Content</Dialog.Popup>\n            </Dialog.Viewport>\n          </Dialog.Portal>\n        </Dialog.Root>\n      );\n    }\n\n    const { user } = await render(<App />);\n\n    expect(screen.queryByTestId('viewport')).toBe(null);\n\n    await user.click(screen.getByText('Open'));\n\n    expect(screen.getByTestId('viewport')).not.toBe(null);\n    expect(screen.getByTestId('viewport')).toContain(screen.getByTestId('popup'));\n  });\n\n  it('stays mounted when used within a keepMounted portal', async () => {\n    const { setProps } = await render(\n      <Dialog.Root open modal={false}>\n        <Dialog.Portal keepMounted>\n          <Dialog.Viewport data-testid=\"viewport\">\n            <Dialog.Popup>Content</Dialog.Popup>\n          </Dialog.Viewport>\n        </Dialog.Portal>\n      </Dialog.Root>,\n    );\n\n    expect(screen.getByTestId('viewport')).not.toBe(null);\n\n    await setProps({ open: false });\n\n    expect(screen.getByTestId('viewport')).not.toBe(null);\n  });\n});\n"
  },
  {
    "path": "packages/react/src/dialog/viewport/DialogViewport.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { type BaseUIComponentProps } from '../../utils/types';\nimport { type TransitionStatus } from '../../utils/useTransitionStatus';\nimport { type StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport { popupStateMapping as baseMapping } from '../../utils/popupStateMapping';\nimport { transitionStatusMapping } from '../../utils/stateAttributesMapping';\nimport { useDialogRootContext } from '../root/DialogRootContext';\nimport { useDialogPortalContext } from '../portal/DialogPortalContext';\nimport { DialogViewportDataAttributes } from './DialogViewportDataAttributes';\n\nconst stateAttributesMapping: StateAttributesMapping<DialogViewportState> = {\n  ...baseMapping,\n  ...transitionStatusMapping,\n  nested(value) {\n    return value ? { [DialogViewportDataAttributes.nested]: '' } : null;\n  },\n  nestedDialogOpen(value) {\n    return value ? { [DialogViewportDataAttributes.nestedDialogOpen]: '' } : null;\n  },\n};\n\n/**\n * A positioning container for the dialog popup that can be made scrollable.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Dialog](https://base-ui.com/react/components/dialog)\n */\nexport const DialogViewport = React.forwardRef(function DialogViewport(\n  componentProps: DialogViewport.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { className, render, children, ...elementProps } = componentProps;\n\n  const keepMounted = useDialogPortalContext();\n  const { store } = useDialogRootContext();\n\n  const open = store.useState('open');\n  const nested = store.useState('nested');\n  const transitionStatus = store.useState('transitionStatus');\n  const nestedOpenDialogCount = store.useState('nestedOpenDialogCount');\n  const mounted = store.useState('mounted');\n\n  const nestedDialogOpen = nestedOpenDialogCount > 0;\n\n  const state: DialogViewportState = {\n    open,\n    nested,\n    transitionStatus,\n    nestedDialogOpen,\n  };\n\n  const shouldRender = keepMounted || mounted;\n\n  return useRenderElement('div', componentProps, {\n    enabled: shouldRender,\n    state,\n    ref: [forwardedRef, store.useStateSetter('viewportElement')],\n    stateAttributesMapping,\n    props: [\n      {\n        role: 'presentation',\n        hidden: !mounted,\n        style: {\n          pointerEvents: !open ? 'none' : undefined,\n        },\n        children,\n      },\n      elementProps,\n    ],\n  });\n});\n\nexport interface DialogViewportState {\n  /**\n   * Whether the dialog is currently open.\n   */\n  open: boolean;\n  /**\n   * The transition status of the component.\n   */\n  transitionStatus: TransitionStatus;\n  /**\n   * Whether the dialog is nested within another dialog.\n   */\n  nested: boolean;\n  /**\n   * Whether the dialog has nested dialogs open.\n   */\n  nestedDialogOpen: boolean;\n}\n\nexport interface DialogViewportProps extends BaseUIComponentProps<'div', DialogViewportState> {}\n\nexport namespace DialogViewport {\n  export type State = DialogViewportState;\n  export type Props = DialogViewportProps;\n}\n"
  },
  {
    "path": "packages/react/src/dialog/viewport/DialogViewportDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum DialogViewportDataAttributes {\n  /**\n   * Present when the dialog is open.\n   */\n  open = CommonPopupDataAttributes.open,\n  /**\n   * Present when the dialog is closed.\n   */\n  closed = CommonPopupDataAttributes.closed,\n  /**\n   * Present when the dialog is animating in.\n   */\n  startingStyle = CommonPopupDataAttributes.startingStyle,\n  /**\n   * Present when the dialog is animating out.\n   */\n  endingStyle = CommonPopupDataAttributes.endingStyle,\n  /**\n   * Present when the dialog is nested within another dialog.\n   */\n  nested = 'data-nested',\n  /**\n   * Present when the dialog has other open dialogs nested within it.\n   */\n  nestedDialogOpen = 'data-nested-dialog-open',\n}\n"
  },
  {
    "path": "packages/react/src/direction-provider/DirectionContext.tsx",
    "content": "'use client';\nimport * as React from 'react';\n\nexport type TextDirection = 'ltr' | 'rtl';\n\nexport type DirectionContext = {\n  direction: TextDirection;\n};\n\n/**\n * @internal\n */\nexport const DirectionContext = React.createContext<DirectionContext | undefined>(undefined);\n\nexport function useDirection() {\n  const context = React.useContext(DirectionContext);\n\n  return context?.direction ?? 'ltr';\n}\n"
  },
  {
    "path": "packages/react/src/direction-provider/DirectionProvider.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { DirectionContext, type TextDirection } from './DirectionContext';\n\n/**\n * Enables RTL behavior for Base UI components.\n *\n * Documentation: [Base UI Direction Provider](https://base-ui.com/react/utils/direction-provider)\n */\nexport const DirectionProvider: React.FC<DirectionProvider.Props> = function DirectionProvider(\n  props,\n) {\n  const { direction = 'ltr' } = props;\n  const contextValue = React.useMemo(() => ({ direction }), [direction]);\n  return (\n    <DirectionContext.Provider value={contextValue}>{props.children}</DirectionContext.Provider>\n  );\n};\n\nexport interface DirectionProviderState {}\n\nexport interface DirectionProviderProps {\n  children?: React.ReactNode;\n  /**\n   * The reading direction of the text\n   * @default 'ltr'\n   */\n  direction?: TextDirection | undefined;\n}\n\nexport namespace DirectionProvider {\n  export type State = DirectionProviderState;\n  export type Props = DirectionProviderProps;\n}\n"
  },
  {
    "path": "packages/react/src/direction-provider/index.parts.ts",
    "content": "export { DirectionProvider as Provider } from './DirectionProvider';\n\nexport { useDirection } from './DirectionContext';\n\nexport type { TextDirection } from './DirectionContext';\n"
  },
  {
    "path": "packages/react/src/direction-provider/index.ts",
    "content": "export { Provider as DirectionProvider, useDirection, type TextDirection } from './index.parts';\nexport type { DirectionProviderProps } from './DirectionProvider';\n"
  },
  {
    "path": "packages/react/src/drawer/backdrop/DrawerBackdrop.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useDialogRootContext } from '../../dialog/root/DialogRootContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { type TransitionStatus } from '../../utils/useTransitionStatus';\nimport { type BaseUIComponentProps } from '../../utils/types';\nimport { type StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport { popupStateMapping as baseMapping } from '../../utils/popupStateMapping';\nimport { transitionStatusMapping } from '../../utils/stateAttributesMapping';\nimport { DrawerPopupCssVars } from '../popup/DrawerPopupCssVars';\nimport { DrawerBackdropCssVars } from './DrawerBackdropCssVars';\n\nconst stateAttributesMapping: StateAttributesMapping<DrawerBackdropState> = {\n  ...baseMapping,\n  ...transitionStatusMapping,\n};\n\n/**\n * An overlay displayed beneath the popup.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)\n */\nexport const DrawerBackdrop = React.forwardRef(function DrawerBackdrop(\n  componentProps: DrawerBackdrop.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, forceRender = false, ...elementProps } = componentProps;\n  const { store } = useDialogRootContext();\n\n  const open = store.useState('open');\n  const nested = store.useState('nested');\n  const mounted = store.useState('mounted');\n  const transitionStatus = store.useState('transitionStatus');\n\n  const state: DrawerBackdropState = {\n    open,\n    transitionStatus,\n  };\n\n  return useRenderElement('div', componentProps, {\n    state,\n    ref: [store.context.backdropRef, forwardedRef],\n    stateAttributesMapping,\n    props: [\n      {\n        role: 'presentation',\n        hidden: !mounted,\n        style: {\n          pointerEvents: !open ? 'none' : undefined,\n          userSelect: 'none',\n          WebkitUserSelect: 'none',\n          [DrawerBackdropCssVars.swipeProgress]: '0',\n          [DrawerPopupCssVars.swipeStrength]: '1',\n        } as React.CSSProperties,\n      },\n      elementProps,\n    ],\n    enabled: forceRender || !nested,\n  });\n});\n\nexport interface DrawerBackdropProps extends BaseUIComponentProps<'div', DrawerBackdropState> {\n  /**\n   * Whether the backdrop is forced to render even when nested.\n   * @default false\n   */\n  forceRender?: boolean | undefined;\n}\n\nexport interface DrawerBackdropState {\n  /**\n   * Whether the drawer is currently open.\n   */\n  open: boolean;\n  /**\n   * The transition status of the component.\n   */\n  transitionStatus: TransitionStatus;\n}\n\nexport namespace DrawerBackdrop {\n  export type Props = DrawerBackdropProps;\n  export type State = DrawerBackdropState;\n}\n"
  },
  {
    "path": "packages/react/src/drawer/backdrop/DrawerBackdropCssVars.ts",
    "content": "export enum DrawerBackdropCssVars {\n  /**\n   * The swipe progress of the drawer gesture.\n   * @type {number}\n   */\n  swipeProgress = '--drawer-swipe-progress',\n}\n"
  },
  {
    "path": "packages/react/src/drawer/backdrop/DrawerBackdropDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum DrawerBackdropDataAttributes {\n  /**\n   * Present when the drawer is open.\n   */\n  open = CommonPopupDataAttributes.open,\n  /**\n   * Present when the drawer is closed.\n   */\n  closed = CommonPopupDataAttributes.closed,\n  /**\n   * Present when the drawer is animating in.\n   */\n  startingStyle = CommonPopupDataAttributes.startingStyle,\n  /**\n   * Present when the drawer is animating out.\n   */\n  endingStyle = CommonPopupDataAttributes.endingStyle,\n}\n"
  },
  {
    "path": "packages/react/src/drawer/close/DrawerClose.tsx",
    "content": "'use client';\nimport type * as React from 'react';\nimport { DialogClose } from '../../dialog/close/DialogClose';\nimport type { BaseUIComponentProps, NativeButtonProps } from '../../utils/types';\n\n/**\n * A button that closes the drawer.\n * Renders a `<button>` element.\n *\n * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)\n */\nexport const DrawerClose = DialogClose as DrawerClose;\n\nexport interface DrawerCloseProps\n  extends NativeButtonProps, BaseUIComponentProps<'button', DrawerCloseState> {}\n\nexport interface DrawerCloseState {\n  /**\n   * Whether the button is currently disabled.\n   */\n  disabled: boolean;\n}\n\nexport interface DrawerClose {\n  (componentProps: DrawerCloseProps): React.JSX.Element;\n}\n\nexport namespace DrawerClose {\n  export type Props = DrawerCloseProps;\n  export type State = DrawerCloseState;\n}\n"
  },
  {
    "path": "packages/react/src/drawer/content/DrawerContent.test.tsx",
    "content": "import { describe, expect, it } from 'vitest';\nimport { Drawer } from '@base-ui/react/drawer';\nimport { screen } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Drawer.Content />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Drawer.Content />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <Drawer.Root open>\n          <Drawer.Portal>\n            <Drawer.Viewport>\n              <Drawer.Popup>{node}</Drawer.Popup>\n            </Drawer.Viewport>\n          </Drawer.Portal>\n        </Drawer.Root>,\n      );\n    },\n  }));\n\n  it('does not add public swipe-ignore attributes', async () => {\n    await render(\n      <Drawer.Root open>\n        <Drawer.Portal>\n          <Drawer.Viewport>\n            <Drawer.Popup>\n              <Drawer.Content data-testid=\"content\">Content</Drawer.Content>\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    expect(screen.getByTestId('content')).not.toHaveAttribute('data-swipe-ignore');\n    expect(screen.getByTestId('content')).not.toHaveAttribute('data-base-ui-swipe-ignore');\n  });\n});\n"
  },
  {
    "path": "packages/react/src/drawer/content/DrawerContent.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useDialogRootContext } from '../../dialog/root/DialogRootContext';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { DRAWER_CONTENT_ATTRIBUTE } from './DrawerContentDataAttributes';\n\n/**\n * A container for the drawer contents.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)\n */\nexport const DrawerContent = React.forwardRef(function DrawerContent(\n  componentProps: DrawerContent.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, ...elementProps } = componentProps;\n\n  useDialogRootContext();\n\n  return useRenderElement('div', componentProps, {\n    ref: forwardedRef,\n    props: [{ [DRAWER_CONTENT_ATTRIBUTE as string]: '' }, elementProps],\n  });\n});\n\nexport interface DrawerContentProps extends BaseUIComponentProps<'div', DrawerContentState> {}\nexport interface DrawerContentState {}\n\nexport namespace DrawerContent {\n  export type Props = DrawerContentProps;\n  export type State = DrawerContentState;\n}\n"
  },
  {
    "path": "packages/react/src/drawer/content/DrawerContentDataAttributes.ts",
    "content": "export const DRAWER_CONTENT_ATTRIBUTE = 'data-drawer-content';\n"
  },
  {
    "path": "packages/react/src/drawer/description/DrawerDescription.tsx",
    "content": "'use client';\nimport type * as React from 'react';\nimport { DialogDescription } from '../../dialog/description/DialogDescription';\nimport type { BaseUIComponentProps } from '../../utils/types';\n\n/**\n * A paragraph with additional information about the drawer.\n * Renders a `<p>` element.\n *\n * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)\n */\nexport const DrawerDescription = DialogDescription as DrawerDescription;\n\nexport interface DrawerDescriptionProps extends BaseUIComponentProps<'p', DrawerDescriptionState> {}\n\nexport interface DrawerDescriptionState {}\n\nexport interface DrawerDescription {\n  (componentProps: DrawerDescriptionProps): React.JSX.Element;\n}\n\nexport namespace DrawerDescription {\n  export type Props = DrawerDescriptionProps;\n  export type State = DrawerDescriptionState;\n}\n"
  },
  {
    "path": "packages/react/src/drawer/indent/DrawerIndent.test.tsx",
    "content": "import { describe, expect, it } from 'vitest';\nimport { Drawer } from '@base-ui/react/drawer';\nimport { screen } from '@mui/internal-test-utils';\nimport { createRenderer } from '#test-utils';\n\ninterface TestCaseProps {\n  open: boolean;\n}\n\nfunction TestCase(props: TestCaseProps) {\n  const { open } = props;\n\n  return (\n    <Drawer.Provider>\n      <Drawer.IndentBackground data-testid=\"bg\" />\n      <Drawer.Indent data-testid=\"indent\">\n        <Drawer.Root open={open}>\n          <Drawer.Trigger>Open</Drawer.Trigger>\n        </Drawer.Root>\n      </Drawer.Indent>\n    </Drawer.Provider>\n  );\n}\n\ndescribe('<Drawer.Indent />', () => {\n  const { render } = createRenderer();\n\n  it('sets data-active when any drawer is open', async () => {\n    const { setProps } = await render(<TestCase open={false} />);\n\n    expect(screen.getByTestId('indent')).toHaveAttribute('data-inactive', '');\n    expect(screen.getByTestId('indent')).not.toHaveAttribute('data-active');\n\n    await setProps({ open: true });\n\n    expect(screen.getByTestId('indent')).toHaveAttribute('data-active', '');\n    expect(screen.getByTestId('indent')).not.toHaveAttribute('data-inactive');\n    expect(screen.getByTestId('bg')).toHaveAttribute('data-active', '');\n  });\n});\n"
  },
  {
    "path": "packages/react/src/drawer/indent/DrawerIndent.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport type { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useDrawerProviderContext } from '../provider/DrawerProviderContext';\nimport { DrawerBackdropCssVars } from '../backdrop/DrawerBackdropCssVars';\nimport { DrawerPopupCssVars } from '../popup/DrawerPopupCssVars';\n\nconst stateAttributesMapping: StateAttributesMapping<DrawerIndentState> = {\n  active(value): Record<string, string> | null {\n    if (value) {\n      return { 'data-active': '' };\n    }\n    return { 'data-inactive': '' };\n  },\n};\n\n/**\n * A wrapper element intended to contain your app's main UI.\n * Applies `data-active` when any drawer within the nearest `<Drawer.Provider>` is open.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)\n */\nexport const DrawerIndent = React.forwardRef(function DrawerIndent(\n  componentProps: DrawerIndent.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, ...elementProps } = componentProps;\n\n  const providerContext = useDrawerProviderContext(true);\n\n  const active = providerContext?.active ?? false;\n  const visualStateStore = providerContext?.visualStateStore;\n\n  const indentRef = React.useRef<HTMLDivElement | null>(null);\n\n  useIsoLayoutEffect(() => {\n    const element = indentRef.current;\n    if (!element || !visualStateStore) {\n      return undefined;\n    }\n\n    const syncVisualState = () => {\n      const { swipeProgress, frontmostHeight } = visualStateStore.getSnapshot();\n      if (swipeProgress <= 0) {\n        element.style.setProperty(DrawerBackdropCssVars.swipeProgress, '0');\n      } else {\n        element.style.setProperty(DrawerBackdropCssVars.swipeProgress, `${swipeProgress}`);\n      }\n\n      if (frontmostHeight <= 0) {\n        element.style.removeProperty(DrawerPopupCssVars.height);\n      } else {\n        element.style.setProperty(DrawerPopupCssVars.height, `${frontmostHeight}px`);\n      }\n    };\n\n    syncVisualState();\n\n    const unsubscribe = visualStateStore.subscribe(syncVisualState);\n    return () => {\n      unsubscribe();\n      element.style.setProperty(DrawerBackdropCssVars.swipeProgress, '0');\n      element.style.removeProperty(DrawerPopupCssVars.height);\n    };\n  }, [visualStateStore]);\n\n  const state: DrawerIndentState = {\n    active,\n  };\n\n  return useRenderElement('div', componentProps, {\n    ref: [forwardedRef, indentRef],\n    state,\n    props: [\n      {\n        style: {\n          [DrawerBackdropCssVars.swipeProgress]: '0',\n        } as React.CSSProperties,\n      },\n      elementProps,\n    ],\n    stateAttributesMapping,\n  });\n});\n\nexport interface DrawerIndentState {\n  /**\n   * Whether any drawer within the nearest <Drawer.Provider> is open.\n   */\n  active: boolean;\n}\n\nexport interface DrawerIndentProps extends BaseUIComponentProps<'div', DrawerIndentState> {}\n\nexport namespace DrawerIndent {\n  export type State = DrawerIndentState;\n  export type Props = DrawerIndentProps;\n}\n"
  },
  {
    "path": "packages/react/src/drawer/indent-background/DrawerIndentBackground.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { Drawer } from '@base-ui/react/drawer';\nimport { screen } from '@mui/internal-test-utils';\nimport { createRenderer } from '#test-utils';\n\ninterface TestCaseProps {\n  open: boolean;\n}\n\nfunction TestCase(props: TestCaseProps) {\n  const { open } = props;\n\n  return (\n    <Drawer.Provider>\n      <Drawer.IndentBackground data-testid=\"bg\" />\n      <Drawer.Root open={open}>\n        <Drawer.Trigger>Open</Drawer.Trigger>\n      </Drawer.Root>\n    </Drawer.Provider>\n  );\n}\n\ndescribe('<Drawer.IndentBackground />', () => {\n  const { render } = createRenderer();\n\n  it('sets data-active when any drawer is open', async () => {\n    const { setProps } = await render(<TestCase open={false} />);\n\n    const background = screen.getByTestId('bg');\n\n    expect(background.getAttribute('data-inactive')).toBe('');\n    expect(background.getAttribute('data-active')).toBeNull();\n\n    await setProps({ open: true });\n\n    expect(background.getAttribute('data-active')).toBe('');\n    expect(background.getAttribute('data-inactive')).toBeNull();\n  });\n});\n"
  },
  {
    "path": "packages/react/src/drawer/indent-background/DrawerIndentBackground.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport type { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useDrawerProviderContext } from '../provider/DrawerProviderContext';\n\nconst stateAttributesMapping: StateAttributesMapping<DrawerIndentBackgroundState> = {\n  active(value): Record<string, string> | null {\n    if (value) {\n      return { 'data-active': '' };\n    }\n    return { 'data-inactive': '' };\n  },\n};\n\n/**\n * An element placed before `<Drawer.Indent>` to render a background layer that can be styled based on whether any drawer is open.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)\n */\nexport const DrawerIndentBackground = React.forwardRef(function DrawerIndentBackground(\n  componentProps: DrawerIndentBackground.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, ...elementProps } = componentProps;\n\n  const providerContext = useDrawerProviderContext(true);\n  const active = providerContext?.active ?? false;\n\n  const state: DrawerIndentBackgroundState = {\n    active,\n  };\n\n  return useRenderElement('div', componentProps, {\n    ref: forwardedRef,\n    state,\n    props: elementProps,\n    stateAttributesMapping,\n  });\n});\n\nexport interface DrawerIndentBackgroundState {\n  /**\n   * Whether any drawer within the nearest <Drawer.Provider> is open.\n   */\n  active: boolean;\n}\n\nexport interface DrawerIndentBackgroundProps extends BaseUIComponentProps<\n  'div',\n  DrawerIndentBackgroundState\n> {}\n\nexport namespace DrawerIndentBackground {\n  export type State = DrawerIndentBackgroundState;\n  export type Props = DrawerIndentBackgroundProps;\n}\n"
  },
  {
    "path": "packages/react/src/drawer/index.parts.ts",
    "content": "export { DrawerBackdrop as Backdrop } from './backdrop/DrawerBackdrop';\nexport { DrawerClose as Close } from './close/DrawerClose';\nexport { DrawerContent as Content } from './content/DrawerContent';\nexport { DrawerDescription as Description } from './description/DrawerDescription';\nexport { DrawerIndent as Indent } from './indent/DrawerIndent';\nexport { DrawerIndentBackground as IndentBackground } from './indent-background/DrawerIndentBackground';\nexport { DrawerPopup as Popup } from './popup/DrawerPopup';\nexport { DrawerPortal as Portal } from './portal/DrawerPortal';\nexport { DrawerProvider as Provider } from './provider/DrawerProvider';\nexport { DrawerRoot as Root } from './root/DrawerRoot';\nexport { DrawerSwipeArea as SwipeArea } from './swipe-area/DrawerSwipeArea';\nexport { DrawerTitle as Title } from './title/DrawerTitle';\nexport { DrawerTrigger as Trigger } from './trigger/DrawerTrigger';\nexport { DrawerViewport as Viewport } from './viewport/DrawerViewport';\nexport {\n  createDialogHandle as createHandle,\n  DialogHandle as Handle,\n} from '../dialog/store/DialogHandle';\n"
  },
  {
    "path": "packages/react/src/drawer/index.ts",
    "content": "export * as Drawer from './index.parts';\n\nexport type * from './root/DrawerRoot';\nexport type * from './provider/DrawerProvider';\nexport type * from './indent/DrawerIndent';\nexport type * from './indent-background/DrawerIndentBackground';\nexport type * from './trigger/DrawerTrigger';\nexport type * from './portal/DrawerPortal';\nexport type * from './popup/DrawerPopup';\nexport type * from './swipe-area/DrawerSwipeArea';\nexport type * from './content/DrawerContent';\nexport type * from './backdrop/DrawerBackdrop';\nexport type * from './viewport/DrawerViewport';\nexport type * from './title/DrawerTitle';\nexport type * from './description/DrawerDescription';\nexport type * from './close/DrawerClose';\n"
  },
  {
    "path": "packages/react/src/drawer/popup/DrawerPopup.test.tsx",
    "content": "import { describe, expect, it } from 'vitest';\nimport * as React from 'react';\nimport { Drawer } from '@base-ui/react/drawer';\nimport { act, screen, waitFor } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\n\ndescribe('<Drawer.Popup />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Drawer.Popup />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <Drawer.Root open>\n          <Drawer.Portal>{node}</Drawer.Portal>\n        </Drawer.Root>,\n      );\n    },\n  }));\n\n  it('defaults initial focus to the popup element', async () => {\n    await render(\n      <div>\n        <input />\n        <Drawer.Root modal={false}>\n          <Drawer.Trigger>Open</Drawer.Trigger>\n          <Drawer.Portal>\n            <Drawer.Viewport>\n              <Drawer.Popup data-testid=\"popup\">\n                <input data-testid=\"popup-input\" />\n              </Drawer.Popup>\n            </Drawer.Viewport>\n          </Drawer.Portal>\n        </Drawer.Root>\n      </div>,\n    );\n\n    await act(async () => {\n      screen.getByRole('button', { name: 'Open' }).click();\n    });\n\n    await waitFor(() => {\n      expect(screen.getByTestId('popup')).toHaveFocus();\n      expect(screen.getByTestId('popup-input')).not.toHaveFocus();\n    });\n  });\n\n  it.skipIf(isJSDOM)(\n    'includes border size in frontmost height CSS variable for nested drawers',\n    async () => {\n      await render(\n        <Drawer.Root open>\n          <Drawer.Portal>\n            <Drawer.Viewport>\n              <Drawer.Popup data-testid=\"parent-popup\">\n                <Drawer.Root open>\n                  <Drawer.Portal>\n                    <Drawer.Viewport>\n                      <Drawer.Popup\n                        data-testid=\"child-popup\"\n                        style={{\n                          height: 100,\n                          borderTop: '2px solid transparent',\n                          borderBottom: '2px solid transparent',\n                        }}\n                      >\n                        <div style={{ height: 10 }}>Child drawer</div>\n                      </Drawer.Popup>\n                    </Drawer.Viewport>\n                  </Drawer.Portal>\n                </Drawer.Root>\n              </Drawer.Popup>\n            </Drawer.Viewport>\n          </Drawer.Portal>\n        </Drawer.Root>,\n      );\n\n      const parentPopup = screen.getByTestId('parent-popup');\n      const childPopup = screen.getByTestId('child-popup');\n\n      await waitFor(() => {\n        expect(childPopup.offsetHeight).toBeGreaterThan(childPopup.scrollHeight);\n        expect(parentPopup.style.getPropertyValue('--drawer-frontmost-height')).toBe(\n          `${childPopup.offsetHeight}px`,\n        );\n      });\n    },\n  );\n});\n"
  },
  {
    "path": "packages/react/src/drawer/popup/DrawerPopup.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport type { InteractionType } from '@base-ui/utils/useEnhancedClickHandler';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { FloatingFocusManager } from '../../floating-ui-react';\nimport { useDialogRootContext } from '../../dialog/root/DialogRootContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport type { TransitionStatus } from '../../utils/useTransitionStatus';\nimport type { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport { popupStateMapping as baseMapping } from '../../utils/popupStateMapping';\nimport { transitionStatusMapping } from '../../utils/stateAttributesMapping';\nimport { DrawerBackdropCssVars } from '../backdrop/DrawerBackdropCssVars';\nimport { DrawerPopupCssVars } from './DrawerPopupCssVars';\nimport { DrawerPopupDataAttributes } from './DrawerPopupDataAttributes';\nimport { useDialogPortalContext } from '../../dialog/portal/DialogPortalContext';\nimport { useOpenChangeComplete } from '../../utils/useOpenChangeComplete';\nimport { COMPOSITE_KEYS } from '../../composite/composite';\nimport { useDrawerRootContext, type DrawerSwipeDirection } from '../root/DrawerRootContext';\nimport { useDrawerSnapPoints } from '../root/useDrawerSnapPoints';\nimport { useDrawerViewportContext } from '../viewport/DrawerViewportContext';\nimport { EMPTY_OBJECT } from '../../utils/constants';\n// Module-level flag to ensure we only register the CSS properties once,\n// regardless of how many Drawer components are mounted.\nlet drawerSwipeVarsRegistered = false;\n\n/**\n * Removes inheritance of high-frequency drawer swipe CSS variables, which\n * reduces style recalculation cost in complex drawers with deep subtrees.\n * Child elements that need these values can still opt-in by using `inherit`.\n * See https://motion.dev/blog/web-animation-performance-tier-list\n * under the \"Improving CSS variable performance\" section.\n */\nfunction removeCSSVariableInheritance() {\n  if (drawerSwipeVarsRegistered) {\n    return;\n  }\n\n  // Intentionally keep inheritance disabled on WebKit as well. Safari doesn't support\n  // opting descendants back in via `--var: inherit` for custom properties registered\n  // with `inherits: false`, but Drawer does not rely on descendant access to these vars\n  // (unlike ScrollArea), so we keep the performance optimization enabled.\n  if (typeof CSS !== 'undefined' && 'registerProperty' in CSS) {\n    [\n      DrawerPopupCssVars.swipeMovementX,\n      DrawerPopupCssVars.swipeMovementY,\n      DrawerPopupCssVars.snapPointOffset,\n    ].forEach((name) => {\n      try {\n        CSS.registerProperty({\n          name,\n          syntax: '<length>',\n          inherits: false,\n          initialValue: '0px',\n        });\n      } catch {\n        /* ignore already-registered */\n      }\n    });\n\n    [\n      {\n        name: DrawerBackdropCssVars.swipeProgress,\n        initialValue: '0',\n      },\n      {\n        name: DrawerPopupCssVars.swipeStrength,\n        initialValue: '1',\n      },\n    ].forEach(({ name, initialValue }) => {\n      try {\n        CSS.registerProperty({\n          name,\n          syntax: '<number>',\n          inherits: false,\n          initialValue,\n        });\n      } catch {\n        /* ignore already-registered */\n      }\n    });\n  }\n\n  drawerSwipeVarsRegistered = true;\n}\n\nconst stateAttributesMapping: StateAttributesMapping<DrawerPopupState> = {\n  ...baseMapping,\n  ...transitionStatusMapping,\n  expanded(value) {\n    return value ? { [DrawerPopupDataAttributes.expanded]: '' } : null;\n  },\n  nestedDrawerOpen(value) {\n    return value ? { [DrawerPopupDataAttributes.nestedDrawerOpen]: '' } : null;\n  },\n  nestedDrawerSwiping(value) {\n    return value ? { [DrawerPopupDataAttributes.nestedDrawerSwiping]: '' } : null;\n  },\n  swipeDirection(value) {\n    return value ? { [DrawerPopupDataAttributes.swipeDirection]: value } : null;\n  },\n  swiping(value) {\n    return value ? { [DrawerPopupDataAttributes.swiping]: '' } : null;\n  },\n};\n\n/**\n * A container for the drawer contents.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)\n */\nexport const DrawerPopup = React.forwardRef(function DrawerPopup(\n  componentProps: DrawerPopup.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { className, finalFocus, initialFocus, render, ...elementProps } = componentProps;\n\n  const { store } = useDialogRootContext();\n\n  const {\n    swipeDirection,\n    frontmostHeight,\n    hasNestedDrawer,\n    nestedSwiping,\n    nestedSwipeProgressStore,\n    onPopupHeightChange,\n    notifyParentFrontmostHeight,\n    notifyParentHasNestedDrawer,\n  } = useDrawerRootContext();\n\n  const descriptionElementId = store.useState('descriptionElementId');\n  const disablePointerDismissal = store.useState('disablePointerDismissal');\n  const floatingRootContext = store.useState('floatingRootContext');\n  const rootPopupProps = store.useState('popupProps');\n  const modal = store.useState('modal');\n  const mounted = store.useState('mounted');\n  const nested = store.useState('nested');\n  const nestedOpenDialogCount = store.useState('nestedOpenDialogCount');\n  const transitionStatus = store.useState('transitionStatus');\n  const open = store.useState('open');\n  const openMethod = store.useState('openMethod');\n  const titleElementId = store.useState('titleElementId');\n  const role = store.useState('role');\n\n  const nestedDrawerOpen = nestedOpenDialogCount > 0;\n\n  const swipe = useDrawerViewportContext(true);\n  const swiping = swipe?.swiping ?? false;\n  const swipeStrength = swipe?.swipeStrength ?? null;\n  const { snapPoints, activeSnapPoint, activeSnapPointOffset } = useDrawerSnapPoints();\n\n  useDialogPortalContext();\n\n  const [popupHeight, setPopupHeight] = React.useState(0);\n\n  const popupHeightRef = React.useRef(0);\n\n  const measureHeight = useStableCallback(() => {\n    const popupElement = store.context.popupRef.current;\n    if (!popupElement) {\n      return;\n    }\n\n    const offsetHeight = popupElement.offsetHeight;\n\n    // Only skip while the element is still actually stretched beyond its last measured height.\n    if (\n      popupHeightRef.current > 0 &&\n      frontmostHeight > popupHeightRef.current &&\n      offsetHeight > popupHeightRef.current\n    ) {\n      return;\n    }\n\n    const keepHeightWhileNested = popupHeightRef.current > 0 && hasNestedDrawer;\n    if (keepHeightWhileNested) {\n      const oldHeight = popupHeightRef.current;\n      setPopupHeight(oldHeight);\n      onPopupHeightChange(oldHeight);\n      return;\n    }\n\n    const nextHeight = offsetHeight;\n    if (nextHeight === popupHeightRef.current) {\n      return;\n    }\n\n    popupHeightRef.current = nextHeight;\n    setPopupHeight(nextHeight);\n    onPopupHeightChange(nextHeight);\n  });\n\n  useIsoLayoutEffect(() => {\n    if (!mounted) {\n      popupHeightRef.current = 0;\n      setPopupHeight(0);\n      onPopupHeightChange(0);\n      return undefined;\n    }\n\n    const popupElement = store.context.popupRef.current;\n    if (!popupElement) {\n      return undefined;\n    }\n\n    removeCSSVariableInheritance();\n    measureHeight();\n\n    if (typeof ResizeObserver !== 'function') {\n      return undefined;\n    }\n\n    const resizeObserver = new ResizeObserver(measureHeight);\n\n    resizeObserver.observe(popupElement);\n    return () => {\n      resizeObserver.disconnect();\n    };\n  }, [measureHeight, mounted, nestedDrawerOpen, onPopupHeightChange, store.context.popupRef]);\n\n  useIsoLayoutEffect(() => {\n    const popupRef = store.context.popupRef;\n\n    const syncNestedSwipeProgress = () => {\n      const popupElement = popupRef.current;\n      if (!popupElement) {\n        return;\n      }\n\n      const progress = nestedSwipeProgressStore.getSnapshot();\n      if (progress > 0) {\n        popupElement.style.setProperty(DrawerBackdropCssVars.swipeProgress, `${progress}`);\n      } else {\n        popupElement.style.setProperty(DrawerBackdropCssVars.swipeProgress, '0');\n      }\n    };\n\n    syncNestedSwipeProgress();\n    const unsubscribe = nestedSwipeProgressStore.subscribe(syncNestedSwipeProgress);\n\n    return () => {\n      unsubscribe();\n      const popupElement = popupRef.current;\n      if (popupElement) {\n        popupElement.style.setProperty(DrawerBackdropCssVars.swipeProgress, '0');\n      }\n    };\n  }, [nestedSwipeProgressStore, store.context.popupRef]);\n\n  React.useEffect(() => {\n    if (!open) {\n      return undefined;\n    }\n\n    notifyParentFrontmostHeight?.(frontmostHeight);\n\n    return () => {\n      notifyParentFrontmostHeight?.(0);\n    };\n  }, [frontmostHeight, open, notifyParentFrontmostHeight]);\n\n  React.useEffect(() => {\n    if (!notifyParentHasNestedDrawer) {\n      return undefined;\n    }\n\n    const present = open || transitionStatus === 'ending';\n    notifyParentHasNestedDrawer(present);\n\n    return () => {\n      notifyParentHasNestedDrawer(false);\n    };\n  }, [notifyParentHasNestedDrawer, open, transitionStatus]);\n\n  useOpenChangeComplete({\n    open,\n    ref: store.context.popupRef,\n    onComplete() {\n      if (open) {\n        store.context.onOpenChangeComplete?.(true);\n      }\n    },\n  });\n\n  const resolvedInitialFocus = initialFocus === undefined ? store.context.popupRef : initialFocus;\n\n  const state: DrawerPopupState = {\n    open,\n    nested,\n    transitionStatus,\n    expanded: activeSnapPoint === 1,\n    nestedDrawerOpen,\n    nestedDrawerSwiping: nestedSwiping,\n    swipeDirection,\n    swiping,\n  };\n\n  let popupHeightCssVarValue: string | undefined;\n  const shouldUseAutoHeight = !hasNestedDrawer && transitionStatus !== 'ending';\n  if (popupHeight && !shouldUseAutoHeight) {\n    popupHeightCssVarValue = `${popupHeight}px`;\n  }\n\n  const shouldApplySnapPoints =\n    snapPoints && snapPoints.length > 0 && (swipeDirection === 'down' || swipeDirection === 'up');\n  let snapPointOffsetValue: number | null = null;\n  if (shouldApplySnapPoints && activeSnapPointOffset !== null) {\n    snapPointOffsetValue = swipeDirection === 'up' ? -activeSnapPointOffset : activeSnapPointOffset;\n  }\n\n  let dragStyles = swipe ? swipe.getDragStyles() : EMPTY_OBJECT;\n  if (shouldApplySnapPoints && swipeDirection === 'down') {\n    const baseOffset = activeSnapPointOffset ?? 0;\n    const movementValue = Number.parseFloat(\n      String((dragStyles as Record<string, string>)[DrawerPopupCssVars.swipeMovementY] ?? 0),\n    );\n    const nextOffset = Number.isFinite(movementValue) ? baseOffset + movementValue : baseOffset;\n    const shouldDamp = nextOffset < 0;\n\n    if (swiping && shouldDamp && Number.isFinite(movementValue)) {\n      const overshoot = Math.abs(nextOffset);\n      const dampedOffset = -Math.sqrt(overshoot);\n      const dampedMovement = dampedOffset - baseOffset;\n      dragStyles = {\n        ...dragStyles,\n        transform: undefined,\n        [DrawerPopupCssVars.swipeMovementY]: `${dampedMovement}px`,\n      } as React.CSSProperties;\n    } else {\n      dragStyles = {\n        ...dragStyles,\n        transform: undefined,\n      };\n    }\n  }\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    props: [\n      rootPopupProps,\n      {\n        'aria-labelledby': titleElementId,\n        'aria-describedby': descriptionElementId,\n        role,\n        tabIndex: -1,\n        hidden: !mounted,\n        onKeyDown(event: React.KeyboardEvent) {\n          if (COMPOSITE_KEYS.has(event.key)) {\n            event.stopPropagation();\n          }\n        },\n        style: {\n          ...dragStyles,\n          [DrawerBackdropCssVars.swipeProgress]: '0',\n          [DrawerPopupCssVars.nestedDrawers]: nestedOpenDialogCount,\n          [DrawerPopupCssVars.height]: popupHeightCssVarValue,\n          [DrawerPopupCssVars.snapPointOffset]:\n            typeof snapPointOffsetValue === 'number' ? `${snapPointOffsetValue}px` : '0px',\n          [DrawerPopupCssVars.frontmostHeight]: frontmostHeight\n            ? `${frontmostHeight}px`\n            : undefined,\n          [DrawerPopupCssVars.swipeStrength]:\n            typeof swipeStrength === 'number' && Number.isFinite(swipeStrength) && swipeStrength > 0\n              ? `${swipeStrength}`\n              : '1',\n        } as React.CSSProperties,\n      },\n      elementProps,\n    ],\n    ref: [forwardedRef, store.context.popupRef, store.useStateSetter('popupElement')],\n    stateAttributesMapping,\n  });\n\n  return (\n    <FloatingFocusManager\n      context={floatingRootContext}\n      openInteractionType={openMethod}\n      disabled={!mounted}\n      closeOnFocusOut={!disablePointerDismissal}\n      initialFocus={resolvedInitialFocus}\n      returnFocus={finalFocus}\n      modal={modal !== false}\n      restoreFocus=\"popup\"\n    >\n      {element}\n    </FloatingFocusManager>\n  );\n});\n\nexport interface DrawerPopupProps extends BaseUIComponentProps<'div', DrawerPopupState> {\n  /**\n   * Determines the element to focus when the drawer is opened.\n   *\n   * - `false`: Do not move focus.\n   * - `true`: Move focus based on the default behavior (first tabbable element or popup).\n   * - `RefObject`: Move focus to the ref element.\n   * - `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`).\n   *   Return an element to focus, `true` to use the default behavior, or `false`/`undefined` to do nothing.\n   */\n  initialFocus?:\n    | boolean\n    | React.RefObject<HTMLElement | null>\n    | ((openType: InteractionType) => boolean | HTMLElement | null | void)\n    | undefined;\n  /**\n   * Determines the element to focus when the drawer is closed.\n   *\n   * - `false`: Do not move focus.\n   * - `true`: Move focus based on the default behavior (trigger or previously focused element).\n   * - `RefObject`: Move focus to the ref element.\n   * - `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`).\n   *   Return an element to focus, `true` to use the default behavior, or `false`/`undefined` to do nothing.\n   */\n  finalFocus?:\n    | boolean\n    | React.RefObject<HTMLElement | null>\n    | ((closeType: InteractionType) => boolean | HTMLElement | null | void)\n    | undefined;\n}\n\nexport interface DrawerPopupState {\n  /**\n   * Whether the drawer is currently open.\n   */\n  open: boolean;\n  /**\n   * The transition status of the component.\n   */\n  transitionStatus: TransitionStatus;\n  /**\n   * Whether the active snap point is the full-height expanded state.\n   */\n  expanded: boolean;\n  /**\n   * Whether the drawer is nested within a parent drawer.\n   */\n  nested: boolean;\n  /**\n   * Whether the drawer has nested drawers open.\n   */\n  nestedDrawerOpen: boolean;\n  /**\n   * Whether a nested drawer is currently being swiped.\n   */\n  nestedDrawerSwiping: boolean;\n  /**\n   * The swipe direction used to dismiss the drawer.\n   */\n  swipeDirection: DrawerSwipeDirection;\n  /**\n   * Whether the drawer is being swiped.\n   */\n  swiping: boolean;\n}\n\nexport namespace DrawerPopup {\n  export type Props = DrawerPopupProps;\n  export type State = DrawerPopupState;\n}\n"
  },
  {
    "path": "packages/react/src/drawer/popup/DrawerPopupCssVars.ts",
    "content": "export enum DrawerPopupCssVars {\n  /**\n   * The number of nested drawers that are currently open.\n   * @type {number}\n   */\n  nestedDrawers = '--nested-drawers',\n  /**\n   * The height of the drawer popup.\n   * @type {CSS length}\n   */\n  height = '--drawer-height',\n  /**\n   * The height of the frontmost open drawer in the current nested drawer stack.\n   * @type {CSS length}\n   */\n  frontmostHeight = '--drawer-frontmost-height',\n  /**\n   * The swipe movement on the X axis.\n   * @type {CSS length}\n   */\n  swipeMovementX = '--drawer-swipe-movement-x',\n  /**\n   * The swipe movement on the Y axis.\n   * @type {CSS length}\n   */\n  swipeMovementY = '--drawer-swipe-movement-y',\n  /**\n   * The snap point offset used for translating the drawer.\n   * @type {CSS length}\n   */\n  snapPointOffset = '--drawer-snap-point-offset',\n  /**\n   * A scalar (0.1-1) used to scale the swipe release transition duration in CSS.\n   * @type {number}\n   */\n  swipeStrength = '--drawer-swipe-strength',\n}\n"
  },
  {
    "path": "packages/react/src/drawer/popup/DrawerPopupDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum DrawerPopupDataAttributes {\n  /**\n   * Present when the drawer is open.\n   */\n  open = CommonPopupDataAttributes.open,\n  /**\n   * Present when the drawer is closed.\n   */\n  closed = CommonPopupDataAttributes.closed,\n  /**\n   * Present when the drawer is animating in.\n   */\n  startingStyle = CommonPopupDataAttributes.startingStyle,\n  /**\n   * Present when the drawer is animating out.\n   */\n  endingStyle = CommonPopupDataAttributes.endingStyle,\n  /**\n   * Present when the drawer is at the expanded (full-height) snap point.\n   */\n  expanded = 'data-expanded',\n  /**\n   * Present when a nested drawer is open.\n   */\n  nestedDrawerOpen = 'data-nested-drawer-open',\n  /**\n   * Present when a nested drawer is being swiped.\n   */\n  nestedDrawerSwiping = 'data-nested-drawer-swiping',\n  /**\n   * Present when the drawer is dismissed by swiping.\n   */\n  swipeDismiss = 'data-swipe-dismiss',\n  /**\n   * Indicates the swipe direction.\n   * @type {'up' | 'down' | 'left' | 'right'}\n   */\n  swipeDirection = 'data-swipe-direction',\n  /**\n   * Present when the drawer is being swiped.\n   */\n  swiping = 'data-swiping',\n}\n"
  },
  {
    "path": "packages/react/src/drawer/portal/DrawerPortal.tsx",
    "content": "'use client';\nimport type * as React from 'react';\nimport { DialogPortal } from '../../dialog/portal/DialogPortal';\nimport type { FloatingPortal } from '../../floating-ui-react';\n\n/**\n * A portal element that moves the popup to a different part of the DOM.\n * By default, the portal element is appended to `<body>`.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)\n */\nexport const DrawerPortal = DialogPortal as DrawerPortal;\n\nexport interface DrawerPortalState {}\n\nexport interface DrawerPortalProps extends FloatingPortal.Props<DrawerPortalState> {\n  /**\n   * Whether to keep the portal mounted in the DOM while the popup is hidden.\n   * @default false\n   */\n  keepMounted?: boolean | undefined;\n  /**\n   * A parent element to render the portal element into.\n   */\n  container?: FloatingPortal.Props<DrawerPortalState>['container'] | undefined;\n}\n\nexport interface DrawerPortal {\n  (\n    componentProps: DrawerPortalProps & React.RefAttributes<HTMLDivElement>,\n  ): React.JSX.Element | null;\n}\n\nexport namespace DrawerPortal {\n  export type Props = DrawerPortalProps;\n  export type State = DrawerPortalState;\n}\n"
  },
  {
    "path": "packages/react/src/drawer/provider/DrawerProvider.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport {\n  DrawerProviderContext,\n  type DrawerVisualState,\n  type DrawerVisualStateStore,\n} from './DrawerProviderContext';\n\n/**\n * Provides a shared context for coordinating global Drawer UI, such as indent/background effects based on whether any Drawer is open.\n * Doesn't render its own HTML element.\n *\n * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)\n */\nexport function DrawerProvider(props: DrawerProvider.Props) {\n  const { children } = props;\n\n  const [openById, setOpenById] = React.useState(() => new Map<string, boolean>());\n  const [visualStateStore] = React.useState(createVisualStateStore);\n\n  const setDrawerOpen = useStableCallback((drawerId: string, open: boolean) => {\n    setOpenById((prev) => {\n      const prevOpen = prev.get(drawerId);\n      if (prevOpen === open) {\n        return prev;\n      }\n      const next = new Map(prev);\n      next.set(drawerId, open);\n      return next;\n    });\n  });\n\n  const removeDrawer = useStableCallback((drawerId: string) => {\n    setOpenById((prev) => {\n      if (!prev.has(drawerId)) {\n        return prev;\n      }\n      const next = new Map(prev);\n      next.delete(drawerId);\n      return next;\n    });\n  });\n\n  const active = React.useMemo(() => {\n    for (const open of openById.values()) {\n      if (open) {\n        return true;\n      }\n    }\n    return false;\n  }, [openById]);\n\n  const contextValue = React.useMemo(\n    () => ({\n      setDrawerOpen,\n      removeDrawer,\n      active,\n      visualStateStore,\n    }),\n    [active, removeDrawer, setDrawerOpen, visualStateStore],\n  );\n\n  return (\n    <DrawerProviderContext.Provider value={contextValue}>{children}</DrawerProviderContext.Provider>\n  );\n}\n\nexport interface DrawerProviderState {}\n\nexport interface DrawerProviderProps {\n  children?: React.ReactNode;\n}\n\nexport namespace DrawerProvider {\n  export type State = DrawerProviderState;\n  export type Props = DrawerProviderProps;\n}\n\ntype VisualStateListener = () => void;\n\nfunction createVisualStateStore(): DrawerVisualStateStore {\n  let state: DrawerVisualState = {\n    swipeProgress: 0,\n    frontmostHeight: 0,\n  };\n  const listeners = new Set<VisualStateListener>();\n\n  return {\n    getSnapshot: () => state,\n    set(nextState) {\n      let nextSwipeProgress = state.swipeProgress;\n      if (nextState.swipeProgress !== undefined) {\n        nextSwipeProgress = Number.isFinite(nextState.swipeProgress) ? nextState.swipeProgress : 0;\n      }\n\n      let nextFrontmostHeight = state.frontmostHeight;\n      if (nextState.frontmostHeight !== undefined) {\n        nextFrontmostHeight = Number.isFinite(nextState.frontmostHeight)\n          ? nextState.frontmostHeight\n          : 0;\n      }\n\n      if (\n        nextSwipeProgress === state.swipeProgress &&\n        nextFrontmostHeight === state.frontmostHeight\n      ) {\n        return;\n      }\n\n      state = {\n        swipeProgress: nextSwipeProgress,\n        frontmostHeight: nextFrontmostHeight,\n      };\n\n      listeners.forEach((listener) => {\n        listener();\n      });\n    },\n    subscribe(listener) {\n      listeners.add(listener);\n      return () => {\n        listeners.delete(listener);\n      };\n    },\n  };\n}\n"
  },
  {
    "path": "packages/react/src/drawer/provider/DrawerProviderContext.ts",
    "content": "'use client';\nimport * as React from 'react';\n\nexport interface DrawerProviderContext {\n  setDrawerOpen: (drawerId: string, open: boolean) => void;\n  removeDrawer: (drawerId: string) => void;\n  active: boolean;\n  visualStateStore: DrawerVisualStateStore;\n}\n\nexport const DrawerProviderContext = React.createContext<DrawerProviderContext | undefined>(\n  undefined,\n);\n\nexport interface DrawerVisualState {\n  swipeProgress: number;\n  frontmostHeight: number;\n}\n\nexport interface DrawerVisualStateStore {\n  getSnapshot: () => DrawerVisualState;\n  subscribe: (listener: () => void) => () => void;\n  set: (state: Partial<DrawerVisualState>) => void;\n}\n\nexport function useDrawerProviderContext(optional?: false): DrawerProviderContext;\nexport function useDrawerProviderContext(optional: true): DrawerProviderContext | undefined;\nexport function useDrawerProviderContext(optional?: boolean) {\n  const context = React.useContext(DrawerProviderContext);\n\n  if (optional === false && context === undefined) {\n    throw new Error('Base UI: DrawerProviderContext is missing. Use <Drawer.Provider>.');\n  }\n\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/drawer/root/DrawerRoot.spec.tsx",
    "content": "import * as React from 'react';\nimport { expectType } from '#test-utils';\nimport { Drawer } from '@base-ui/react/drawer';\nimport { REASONS } from '../../utils/reasons';\n\ntype DrawerChangeHandler = NonNullable<Drawer.Root.Props['onOpenChange']>;\ntype DrawerChangeDetails = Parameters<DrawerChangeHandler>[1];\ntype DrawerSwipeEvent = Extract<DrawerChangeDetails, { reason: typeof REASONS.swipe }>['event'];\n\nexpectType<PointerEvent | TouchEvent, DrawerSwipeEvent>(null as unknown as DrawerSwipeEvent);\n\nfunction assertDrawerChange(details: DrawerChangeDetails) {\n  if (details.reason === REASONS.swipe) {\n    const event: PointerEvent | TouchEvent = details.event;\n    void event;\n    // @ts-expect-error swipe details should not expose keyboard events\n    const keyboardEvent: KeyboardEvent = details.event;\n    void keyboardEvent;\n  }\n\n  if (details.reason === REASONS.escapeKey) {\n    const event: KeyboardEvent = details.event;\n    void event;\n  }\n}\n\nconst handleDrawerChange: DrawerChangeHandler = (open, details) => {\n  expectType<boolean, typeof open>(open);\n  assertDrawerChange(details);\n};\n\nconst drawerOpenChangeReasonNarrowing = (\n  <Drawer.Root onOpenChange={handleDrawerChange}>\n    <Drawer.Trigger />\n    <Drawer.Portal />\n  </Drawer.Root>\n);\n\nvoid drawerOpenChangeReasonNarrowing;\n"
  },
  {
    "path": "packages/react/src/drawer/root/DrawerRoot.test.tsx",
    "content": "import { beforeAll, describe, expect, it, vi } from 'vitest';\nimport * as React from 'react';\nimport { Drawer } from '@base-ui/react/drawer';\nimport { act, fireEvent, flushMicrotasks, screen } from '@mui/internal-test-utils';\nimport { createRenderer, isJSDOM, waitSingleFrame } from '#test-utils';\nimport { REASONS } from '../../utils/reasons';\nimport { useDrawerRootContext } from './DrawerRootContext';\n\nvi.mock('@base-ui/utils/detectBrowser', async () => {\n  const actual = await vi.importActual<typeof import('@base-ui/utils/detectBrowser')>(\n    '@base-ui/utils/detectBrowser',\n  );\n  return { ...actual, isAndroid: true };\n});\n\nfunction TestCase({ onOpenChange }: { onOpenChange: (open: boolean) => void }) {\n  const [open, setOpen] = React.useState(true);\n\n  return (\n    <Drawer.Root\n      open={open}\n      onOpenChange={(nextOpen) => {\n        setOpen(nextOpen);\n        onOpenChange(nextOpen);\n      }}\n      swipeDirection=\"right\"\n    >\n      <Drawer.Portal>\n        <Drawer.Viewport data-testid=\"viewport\">\n          <Drawer.Popup data-testid=\"popup\">Drawer</Drawer.Popup>\n        </Drawer.Viewport>\n      </Drawer.Portal>\n    </Drawer.Root>\n  );\n}\n\nasync function simulateTimedRightSwipe(\n  element: HTMLElement,\n  startX: number,\n  endX: number,\n  startTime: number,\n  moveTime: number,\n  endTime: number,\n) {\n  if (isJSDOM) {\n    vi.setSystemTime(new Date(startTime));\n    fireEvent.pointerDown(element, {\n      button: 0,\n      buttons: 1,\n      pointerId: 1,\n      clientX: startX,\n      clientY: 100,\n      bubbles: true,\n      pointerType: 'mouse',\n    });\n\n    await flushMicrotasks();\n\n    vi.setSystemTime(new Date(moveTime));\n    fireEvent.pointerMove(element, {\n      pointerId: 1,\n      clientX: startX + 1,\n      clientY: 100,\n      bubbles: true,\n      pointerType: 'mouse',\n    });\n\n    await flushMicrotasks();\n\n    vi.setSystemTime(new Date(endTime - 1));\n    fireEvent.pointerMove(element, {\n      pointerId: 1,\n      clientX: endX,\n      clientY: 100,\n      bubbles: true,\n      pointerType: 'mouse',\n    });\n\n    await flushMicrotasks();\n\n    vi.setSystemTime(new Date(endTime));\n    fireEvent.pointerUp(element, {\n      pointerId: 1,\n      clientX: endX,\n      clientY: 100,\n      bubbles: true,\n      pointerType: 'mouse',\n    });\n\n    await flushMicrotasks();\n    return;\n  }\n\n  const moveDelay = Math.max(0, moveTime - startTime);\n  const endDelay = Math.max(0, endTime - moveTime);\n\n  fireEvent.pointerDown(element, {\n    button: 0,\n    buttons: 1,\n    pointerId: 1,\n    clientX: startX,\n    clientY: 100,\n    bubbles: true,\n    pointerType: 'mouse',\n  });\n\n  await flushMicrotasks();\n  await new Promise((resolve) => {\n    setTimeout(resolve, moveDelay);\n  });\n\n  fireEvent.pointerMove(element, {\n    pointerId: 1,\n    clientX: startX + 1,\n    clientY: 100,\n    bubbles: true,\n    pointerType: 'mouse',\n  });\n\n  await flushMicrotasks();\n  await new Promise((resolve) => {\n    setTimeout(resolve, endDelay);\n  });\n\n  fireEvent.pointerMove(element, {\n    pointerId: 1,\n    clientX: endX,\n    clientY: 100,\n    bubbles: true,\n    pointerType: 'mouse',\n  });\n\n  await flushMicrotasks();\n\n  fireEvent.pointerUp(element, {\n    pointerId: 1,\n    clientX: endX,\n    clientY: 100,\n    bubbles: true,\n    pointerType: 'mouse',\n  });\n\n  await flushMicrotasks();\n}\n\nasync function simulateTimedDownSwipe(\n  element: HTMLElement,\n  startY: number,\n  endY: number,\n  startTime: number,\n  moveTime: number,\n  endTime: number,\n  settleTime?: number,\n) {\n  const resolvedSettleTime =\n    typeof settleTime === 'number' && Number.isFinite(settleTime) ? settleTime : null;\n  const settleY = endY - 1;\n\n  if (isJSDOM) {\n    vi.setSystemTime(new Date(startTime));\n    fireEvent.pointerDown(element, {\n      button: 0,\n      buttons: 1,\n      pointerId: 1,\n      clientX: 100,\n      clientY: startY,\n      bubbles: true,\n      pointerType: 'mouse',\n    });\n\n    await flushMicrotasks();\n\n    vi.setSystemTime(new Date(moveTime));\n    fireEvent.pointerMove(element, {\n      pointerId: 1,\n      clientX: 100,\n      clientY: startY + 1,\n      bubbles: true,\n      pointerType: 'mouse',\n    });\n\n    await flushMicrotasks();\n\n    if (resolvedSettleTime !== null) {\n      vi.setSystemTime(new Date(resolvedSettleTime));\n      fireEvent.pointerMove(element, {\n        pointerId: 1,\n        clientX: 100,\n        clientY: settleY,\n        bubbles: true,\n        pointerType: 'mouse',\n      });\n\n      await flushMicrotasks();\n    }\n\n    vi.setSystemTime(new Date(endTime - 1));\n    fireEvent.pointerMove(element, {\n      pointerId: 1,\n      clientX: 100,\n      clientY: endY,\n      bubbles: true,\n      pointerType: 'mouse',\n    });\n\n    await flushMicrotasks();\n\n    vi.setSystemTime(new Date(endTime));\n    fireEvent.pointerUp(element, {\n      pointerId: 1,\n      clientX: 100,\n      clientY: endY,\n      bubbles: true,\n      pointerType: 'mouse',\n    });\n\n    await flushMicrotasks();\n    return;\n  }\n\n  const moveDelay = Math.max(0, moveTime - startTime);\n  const settleDelay =\n    resolvedSettleTime !== null ? Math.max(0, resolvedSettleTime - moveTime) : null;\n  const endDelay =\n    resolvedSettleTime !== null\n      ? Math.max(0, endTime - resolvedSettleTime)\n      : Math.max(0, endTime - moveTime);\n\n  fireEvent.pointerDown(element, {\n    button: 0,\n    buttons: 1,\n    pointerId: 1,\n    clientX: 100,\n    clientY: startY,\n    bubbles: true,\n    pointerType: 'mouse',\n  });\n\n  await flushMicrotasks();\n  await new Promise((resolve) => {\n    setTimeout(resolve, moveDelay);\n  });\n\n  fireEvent.pointerMove(element, {\n    pointerId: 1,\n    clientX: 100,\n    clientY: startY + 1,\n    bubbles: true,\n    pointerType: 'mouse',\n  });\n\n  await flushMicrotasks();\n\n  if (settleDelay !== null) {\n    await new Promise((resolve) => {\n      setTimeout(resolve, settleDelay);\n    });\n\n    fireEvent.pointerMove(element, {\n      pointerId: 1,\n      clientX: 100,\n      clientY: settleY,\n      bubbles: true,\n      pointerType: 'mouse',\n    });\n\n    await flushMicrotasks();\n  }\n\n  await new Promise((resolve) => {\n    setTimeout(resolve, endDelay);\n  });\n\n  fireEvent.pointerMove(element, {\n    pointerId: 1,\n    clientX: 100,\n    clientY: endY,\n    bubbles: true,\n    pointerType: 'mouse',\n  });\n\n  await flushMicrotasks();\n\n  fireEvent.pointerUp(element, {\n    pointerId: 1,\n    clientX: 100,\n    clientY: endY,\n    bubbles: true,\n    pointerType: 'mouse',\n  });\n\n  await flushMicrotasks();\n}\n\ntype TimedSwipeStep = {\n  type: 'down' | 'move' | 'up';\n  x: number;\n  y: number;\n  time: number;\n};\n\nasync function simulateTimedSwipe(element: HTMLElement, steps: TimedSwipeStep[]) {\n  if (steps.length === 0) {\n    return;\n  }\n\n  function fireStep(step: TimedSwipeStep) {\n    const baseEvent = {\n      pointerId: 1,\n      clientX: step.x,\n      clientY: step.y,\n      bubbles: true,\n      pointerType: 'mouse',\n    };\n\n    if (step.type === 'down') {\n      fireEvent.pointerDown(element, { ...baseEvent, button: 0, buttons: 1 });\n      return;\n    }\n\n    if (step.type === 'move') {\n      fireEvent.pointerMove(element, baseEvent);\n      return;\n    }\n\n    fireEvent.pointerUp(element, baseEvent);\n  }\n\n  if (isJSDOM) {\n    await steps.reduce(async (previous, step) => {\n      await previous;\n      vi.setSystemTime(new Date(step.time));\n      fireStep(step);\n      await flushMicrotasks();\n    }, Promise.resolve());\n    return;\n  }\n\n  await steps.reduce(async (previous, step, index) => {\n    await previous;\n    const previousTime = steps[index - 1]?.time ?? step.time;\n    const delay = index === 0 ? 0 : Math.max(0, step.time - previousTime);\n    if (delay > 0) {\n      await new Promise((resolve) => {\n        setTimeout(resolve, delay);\n      });\n    }\n\n    fireStep(step);\n    await flushMicrotasks();\n  }, Promise.resolve());\n}\n\nfunction mockResizeObserver() {\n  const original = globalThis.ResizeObserver;\n  if (typeof original === 'function') {\n    globalThis.ResizeObserver = class {\n      observe() {}\n      unobserve() {}\n      disconnect() {}\n    } as typeof ResizeObserver;\n  }\n  return () => {\n    if (typeof original === 'function') {\n      globalThis.ResizeObserver = original;\n    }\n  };\n}\n\n/**\n * Sets up `elementFromPoint` and `ResizeObserver` mocks for swipe-dismiss tests.\n * Returns a cleanup function that restores originals.\n */\nfunction setupSwipeTestEnv() {\n  const originalElementFromPoint = document.elementFromPoint;\n  const restoreResizeObserver = mockResizeObserver();\n  return {\n    /** Call after rendering to point `elementFromPoint` at the given element. */\n    pointAt(element: Element) {\n      document.elementFromPoint = () => element;\n    },\n    cleanup() {\n      document.elementFromPoint = originalElementFromPoint;\n      restoreResizeObserver();\n    },\n  };\n}\n\nfunction SnapPointResetCase() {\n  const snapPoints = ['100px', '300px', 1];\n  const [open, setOpen] = React.useState(true);\n  const [snapPoint, setSnapPoint] = React.useState<Drawer.Root.SnapPoint | null>(snapPoints[2]);\n\n  return (\n    <div>\n      <div data-testid=\"active-snap\">{String(snapPoint)}</div>\n      <Drawer.Root\n        open={open}\n        onOpenChange={setOpen}\n        snapPoints={snapPoints}\n        snapPoint={snapPoint}\n        onSnapPointChange={setSnapPoint}\n      >\n        <Drawer.Portal>\n          <Drawer.Viewport data-testid=\"viewport\">\n            <Drawer.Popup data-testid=\"popup\">\n              Drawer\n              <Drawer.Close data-testid=\"close\">Close</Drawer.Close>\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>\n    </div>\n  );\n}\n\nfunction ActiveSnapPointDisplay() {\n  const { activeSnapPoint } = useDrawerRootContext();\n  return <div data-testid=\"active-snap\">{String(activeSnapPoint)}</div>;\n}\n\nfunction DefaultSnapPointResetCase() {\n  const snapPoints = ['100px', '300px', 1];\n  const [open, setOpen] = React.useState(true);\n\n  return (\n    <Drawer.Root\n      defaultSnapPoint={snapPoints[1]}\n      open={open}\n      onOpenChange={setOpen}\n      snapPoints={snapPoints}\n    >\n      <ActiveSnapPointDisplay />\n      <Drawer.Portal>\n        <Drawer.Viewport data-testid=\"viewport\">\n          <Drawer.Popup data-testid=\"popup\">\n            Drawer\n            <Drawer.Close data-testid=\"close\">Close</Drawer.Close>\n          </Drawer.Popup>\n        </Drawer.Viewport>\n      </Drawer.Portal>\n    </Drawer.Root>\n  );\n}\n\nfunction SnapPointChangeDetailsCase({\n  onSnapPointChange,\n}: {\n  onSnapPointChange: (\n    snapPoint: Drawer.Root.SnapPoint | null,\n    eventDetails: Drawer.Root.SnapPointChangeEventDetails,\n  ) => void;\n}) {\n  const snapPoints = ['100px', '300px', 1];\n  const [open, setOpen] = React.useState(true);\n  const [snapPoint, setSnapPoint] = React.useState<Drawer.Root.SnapPoint | null>(snapPoints[2]);\n\n  return (\n    <Drawer.Root\n      open={open}\n      onOpenChange={setOpen}\n      snapPoints={snapPoints}\n      snapPoint={snapPoint}\n      onSnapPointChange={(nextSnapPoint, eventDetails) => {\n        setSnapPoint(nextSnapPoint);\n        onSnapPointChange(nextSnapPoint, eventDetails);\n      }}\n    >\n      <Drawer.Portal>\n        <Drawer.Viewport data-testid=\"viewport\">\n          <Drawer.Popup data-testid=\"popup\">\n            Drawer\n            <Drawer.Close data-testid=\"close\">Close</Drawer.Close>\n          </Drawer.Popup>\n        </Drawer.Viewport>\n      </Drawer.Portal>\n    </Drawer.Root>\n  );\n}\n\nfunction CanceledCloseSnapPointResetCase() {\n  const snapPoints = ['100px', '300px', 1];\n  const [open, setOpen] = React.useState(true);\n  const [snapPoint, setSnapPoint] = React.useState<Drawer.Root.SnapPoint | null>(snapPoints[2]);\n\n  return (\n    <div>\n      <div data-testid=\"active-snap\">{String(snapPoint)}</div>\n      <Drawer.Root\n        open={open}\n        onOpenChange={(nextOpen, eventDetails) => {\n          if (!nextOpen) {\n            eventDetails.cancel();\n          } else {\n            setOpen(nextOpen);\n          }\n        }}\n        snapPoints={snapPoints}\n        snapPoint={snapPoint}\n        onSnapPointChange={setSnapPoint}\n      >\n        <Drawer.Portal>\n          <Drawer.Viewport data-testid=\"viewport\">\n            <Drawer.Popup data-testid=\"popup\">\n              Drawer\n              <Drawer.Close data-testid=\"close\">Close</Drawer.Close>\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>\n    </div>\n  );\n}\n\nfunction ControlledAlwaysOpenCase({\n  onOpenChange,\n}: {\n  onOpenChange?: (open: boolean, eventDetails: Drawer.Root.ChangeEventDetails) => void;\n}) {\n  return (\n    <Drawer.Root open onOpenChange={onOpenChange} swipeDirection=\"down\">\n      <Drawer.Portal>\n        <Drawer.Backdrop data-testid=\"backdrop\" />\n        <Drawer.Viewport data-testid=\"viewport\" style={{ height: 300 }}>\n          <Drawer.Popup data-testid=\"popup\" style={{ height: 200 }}>\n            Drawer\n          </Drawer.Popup>\n        </Drawer.Viewport>\n      </Drawer.Portal>\n    </Drawer.Root>\n  );\n}\n\nfunction ControlledSwipeCloseSnapPointCase() {\n  const snapPoints = ['100px', '300px', 1];\n  const [open, setOpen] = React.useState(true);\n  // Start at '300px' (non-default) so we can distinguish correct reset to\n  // the default ('100px') from incorrect restoration to the pre-swipe value.\n  const [snapPoint, setSnapPoint] = React.useState<Drawer.Root.SnapPoint | null>(snapPoints[1]);\n\n  return (\n    <div>\n      <div data-testid=\"active-snap\">{String(snapPoint)}</div>\n      <Drawer.Root\n        open={open}\n        onOpenChange={setOpen}\n        snapPoints={snapPoints}\n        snapPoint={snapPoint}\n        onSnapPointChange={setSnapPoint}\n        swipeDirection=\"down\"\n      >\n        <Drawer.Portal>\n          <Drawer.Backdrop data-testid=\"backdrop\" />\n          <Drawer.Viewport data-testid=\"viewport\" style={{ height: 600 }}>\n            <Drawer.Popup data-testid=\"popup\" style={{ height: 600 }}>\n              Drawer\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>\n    </div>\n  );\n}\n\nfunction CanceledSwipeCloseCase() {\n  const [open, setOpen] = React.useState(true);\n\n  return (\n    <Drawer.Root\n      open={open}\n      onOpenChange={(nextOpen, eventDetails) => {\n        if (!nextOpen && eventDetails.reason === REASONS.swipe) {\n          eventDetails.cancel();\n          return;\n        }\n\n        setOpen(nextOpen);\n      }}\n      swipeDirection=\"down\"\n    >\n      <Drawer.Portal>\n        <Drawer.Backdrop data-testid=\"backdrop\" />\n        <Drawer.Viewport data-testid=\"viewport\" style={{ height: 300 }}>\n          <Drawer.Popup data-testid=\"popup\" style={{ height: 200 }}>\n            Drawer\n          </Drawer.Popup>\n        </Drawer.Viewport>\n      </Drawer.Portal>\n    </Drawer.Root>\n  );\n}\n\nfunction CanceledSwipeCloseSnapPointCase() {\n  const snapPoints = ['100px', '300px', 1];\n  const [open, setOpen] = React.useState(true);\n  const [snapPoint, setSnapPoint] = React.useState<Drawer.Root.SnapPoint | null>(snapPoints[0]);\n\n  return (\n    <div>\n      <div data-testid=\"active-snap\">{String(snapPoint)}</div>\n      <Drawer.Root\n        open={open}\n        onOpenChange={(nextOpen, eventDetails) => {\n          if (!nextOpen && eventDetails.reason === REASONS.swipe) {\n            eventDetails.cancel();\n            return;\n          }\n\n          setOpen(nextOpen);\n        }}\n        snapPoints={snapPoints}\n        snapPoint={snapPoint}\n        onSnapPointChange={setSnapPoint}\n        swipeDirection=\"down\"\n      >\n        <Drawer.Portal>\n          <Drawer.Backdrop data-testid=\"backdrop\" />\n          <Drawer.Viewport data-testid=\"viewport\" style={{ height: 600 }}>\n            <Drawer.Popup data-testid=\"popup\" style={{ height: 600 }}>\n              Drawer\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>\n    </div>\n  );\n}\n\nfunction SnapPointSwipeCase({ onOpenChange }: { onOpenChange: (open: boolean) => void }) {\n  const snapPoints = ['100px', '300px', 1];\n  const [open, setOpen] = React.useState(true);\n  const [snapPoint, setSnapPoint] = React.useState<Drawer.Root.SnapPoint | null>(snapPoints[0]);\n\n  return (\n    <div>\n      <div data-testid=\"active-snap\">{String(snapPoint)}</div>\n      <Drawer.Root\n        open={open}\n        onOpenChange={(nextOpen) => {\n          setOpen(nextOpen);\n          onOpenChange(nextOpen);\n        }}\n        snapPoints={snapPoints}\n        snapPoint={snapPoint}\n        onSnapPointChange={setSnapPoint}\n        swipeDirection=\"down\"\n      >\n        <Drawer.Portal>\n          <Drawer.Viewport data-testid=\"viewport\" style={{ height: 600 }}>\n            <Drawer.Popup data-testid=\"popup\" style={{ height: 600 }}>\n              Drawer\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>\n    </div>\n  );\n}\n\nfunction SnapPointSequentialSkipCase() {\n  const snapPoints = ['100px', '300px', 1];\n  const [snapPoint, setSnapPoint] = React.useState<Drawer.Root.SnapPoint | null>(snapPoints[0]);\n\n  return (\n    <div>\n      <div data-testid=\"active-snap\">{String(snapPoint)}</div>\n      <Drawer.Root\n        open\n        snapPoints={snapPoints}\n        snapPoint={snapPoint}\n        onSnapPointChange={setSnapPoint}\n        swipeDirection=\"down\"\n        snapToSequentialPoints\n      >\n        <Drawer.Portal>\n          <Drawer.Viewport data-testid=\"viewport\" style={{ height: 600 }}>\n            <Drawer.Popup data-testid=\"popup\" style={{ height: 600 }}>\n              Drawer\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>\n    </div>\n  );\n}\n\ndescribe('<Drawer.Root />', () => {\n  const { render } = createRenderer();\n\n  beforeAll(function beforeHook() {\n    // PointerEvent not fully implemented in jsdom, causing fireEvent.pointer* to ignore options.\n    // https://github.com/jsdom/jsdom/issues/2527\n    (window as any).PointerEvent = window.MouseEvent;\n  });\n\n  it.skipIf(isJSDOM)('uses a size-based swipe threshold', async () => {\n    const handleOpenChange = vi.fn();\n    await render(<TestCase onOpenChange={handleOpenChange} />);\n\n    await flushMicrotasks();\n\n    const viewport = screen.getByTestId('viewport');\n    const popup = screen.getByTestId('popup');\n\n    popup.style.width = '200px';\n    await flushMicrotasks();\n\n    const originalElementFromPoint = document.elementFromPoint;\n    document.elementFromPoint = () => popup;\n\n    const useFakeTimers = isJSDOM;\n    if (useFakeTimers) {\n      vi.useFakeTimers();\n    }\n\n    try {\n      const startTime = 1000;\n      const moveTime = 1100;\n      const endTime = 1600;\n\n      await simulateTimedRightSwipe(viewport, 100, 190, startTime, moveTime, endTime);\n      expect(handleOpenChange).not.toHaveBeenCalled();\n\n      await simulateTimedRightSwipe(\n        viewport,\n        100,\n        220,\n        startTime + 1000,\n        moveTime + 1000,\n        endTime + 1000,\n      );\n      expect(handleOpenChange).toHaveBeenCalledWith(false);\n    } finally {\n      if (useFakeTimers) {\n        vi.useRealTimers();\n      }\n      document.elementFromPoint = originalElementFromPoint;\n    }\n  });\n\n  it('supports detached triggers with handles', async () => {\n    const handle = Drawer.createHandle<number>();\n\n    await render(\n      <div>\n        <Drawer.Trigger handle={handle} payload={1}>\n          Trigger 1\n        </Drawer.Trigger>\n        <Drawer.Trigger handle={handle} payload={2}>\n          Trigger 2\n        </Drawer.Trigger>\n        <Drawer.Root handle={handle}>\n          {({ payload }: { payload: number | undefined }) => (\n            <Drawer.Portal>\n              <Drawer.Viewport>\n                <Drawer.Popup>\n                  <span data-testid=\"payload\">{payload}</span>\n                  <Drawer.Close>Close</Drawer.Close>\n                </Drawer.Popup>\n              </Drawer.Viewport>\n            </Drawer.Portal>\n          )}\n        </Drawer.Root>\n      </div>,\n    );\n\n    await flushMicrotasks();\n    expect(screen.queryByTestId('payload')).toBeNull();\n\n    fireEvent.click(screen.getByRole('button', { name: 'Trigger 1' }));\n    await flushMicrotasks();\n    expect(screen.getByTestId('payload').textContent).toBe('1');\n\n    fireEvent.click(screen.getByText('Close'));\n    await flushMicrotasks();\n    expect(screen.queryByTestId('payload')).toBeNull();\n\n    fireEvent.click(screen.getByRole('button', { name: 'Trigger 2' }));\n    await flushMicrotasks();\n    expect(screen.getByTestId('payload').textContent).toBe('2');\n  });\n\n  it('resets the active snap point when closing', async () => {\n    await render(<SnapPointResetCase />);\n    await flushMicrotasks();\n\n    const closeButton = screen.getByTestId('close');\n    fireEvent.click(closeButton);\n\n    await flushMicrotasks();\n\n    expect(screen.getByTestId('active-snap').textContent).toBe('100px');\n  });\n\n  it('resets to the default snap point when provided', async () => {\n    await render(<DefaultSnapPointResetCase />);\n    await flushMicrotasks();\n\n    expect(screen.getByTestId('active-snap').textContent).toBe('300px');\n\n    const closeButton = screen.getByTestId('close');\n    fireEvent.click(closeButton);\n\n    await flushMicrotasks();\n\n    expect(screen.getByTestId('active-snap').textContent).toBe('300px');\n  });\n\n  it('provides event details when snap point changes', async () => {\n    const handleSnapPointChange = vi.fn();\n    await render(<SnapPointChangeDetailsCase onSnapPointChange={handleSnapPointChange} />);\n    await flushMicrotasks();\n\n    const closeButton = screen.getByTestId('close');\n    fireEvent.click(closeButton);\n\n    await flushMicrotasks();\n\n    expect(handleSnapPointChange).toHaveBeenCalled();\n    const [, eventDetails] = handleSnapPointChange.mock.calls[0];\n    expect(eventDetails.reason).toBe(REASONS.closePress);\n  });\n\n  it('does not reset snap point when a close is canceled', async () => {\n    await render(<CanceledCloseSnapPointResetCase />);\n    await flushMicrotasks();\n\n    expect(screen.getByTestId('active-snap').textContent).toBe('1');\n\n    fireEvent.click(screen.getByTestId('close'));\n    await flushMicrotasks();\n\n    expect(screen.getByTestId('active-snap').textContent).toBe('1');\n  });\n\n  it.skipIf(isJSDOM)('clears swipe-dismiss styles when swipe close is canceled', async () => {\n    const env = setupSwipeTestEnv();\n\n    try {\n      await render(<CanceledSwipeCloseCase />);\n      await flushMicrotasks();\n\n      const viewport = screen.getByTestId('viewport');\n      const popup = screen.getByTestId('popup');\n      const backdrop = screen.getByTestId('backdrop');\n\n      Object.defineProperty(popup, 'offsetHeight', { value: 200, configurable: true });\n\n      env.pointAt(popup);\n\n      await simulateTimedDownSwipe(viewport, 100, 250, 1000, 1010, 1040);\n\n      expect(popup).not.toHaveAttribute('data-swipe-dismiss');\n      expect(backdrop).not.toHaveAttribute('data-swipe-dismiss');\n      expect(popup).not.toHaveAttribute('data-ending-style');\n      expect(backdrop).not.toHaveAttribute('data-swiping');\n      expect(popup).toHaveAttribute('data-open', '');\n      expect(popup.style.getPropertyValue('--drawer-swipe-movement-y')).toBe('0px');\n    } finally {\n      env.cleanup();\n    }\n  });\n\n  it.skipIf(isJSDOM)(\n    'does not dismiss a controlled drawer via swipe when open is always true',\n    async () => {\n      const handleOpenChange = vi.fn();\n      const env = setupSwipeTestEnv();\n\n      try {\n        await render(<ControlledAlwaysOpenCase onOpenChange={handleOpenChange} />);\n        await flushMicrotasks();\n\n        const viewport = screen.getByTestId('viewport');\n        const popup = screen.getByTestId('popup');\n        const backdrop = screen.getByTestId('backdrop');\n\n        Object.defineProperty(popup, 'offsetHeight', { value: 200, configurable: true });\n\n        env.pointAt(popup);\n\n        await simulateTimedDownSwipe(viewport, 100, 250, 1000, 1010, 1040);\n\n        // onOpenChange should still be called so the parent knows about the dismiss intent\n        expect(handleOpenChange).toHaveBeenCalledWith(false, expect.anything());\n\n        // The controlled reopen happens in rAF outside fireEvent's implicit act scope.\n        // Wrap the frame wait in act to avoid React act warnings.\n        await act(async () => {\n          await waitSingleFrame();\n        });\n\n        // The drawer should remain open without data-swipe-dismiss\n        expect(popup).not.toHaveAttribute('data-swipe-dismiss');\n        expect(backdrop).not.toHaveAttribute('data-swipe-dismiss');\n        expect(popup).not.toHaveAttribute('data-ending-style');\n        expect(popup).toHaveAttribute('data-open', '');\n      } finally {\n        env.cleanup();\n      }\n    },\n  );\n\n  it.skipIf(isJSDOM)(\n    'does not restore snap point when a controlled swipe close is accepted by the parent',\n    async () => {\n      const env = setupSwipeTestEnv();\n\n      try {\n        await render(<ControlledSwipeCloseSnapPointCase />);\n        await flushMicrotasks();\n\n        const viewport = screen.getByTestId('viewport');\n        const popup = screen.getByTestId('popup');\n\n        env.pointAt(popup);\n\n        await simulateTimedDownSwipe(viewport, 100, 260, 1000, 1010, 1040);\n        expect(screen.getByTestId('active-snap').textContent).toBe('100px');\n      } finally {\n        env.cleanup();\n      }\n    },\n  );\n\n  it.skipIf(isJSDOM)(\n    'restores snap point and swipe offsets when swipe close is canceled',\n    async () => {\n      const env = setupSwipeTestEnv();\n\n      try {\n        await render(<CanceledSwipeCloseSnapPointCase />);\n        await flushMicrotasks();\n\n        const viewport = screen.getByTestId('viewport');\n        const popup = screen.getByTestId('popup');\n\n        env.pointAt(popup);\n\n        await simulateTimedDownSwipe(viewport, 100, 260, 1000, 1010, 1040);\n\n        expect(screen.getByTestId('active-snap').textContent).toBe('100px');\n        expect(popup).toHaveAttribute('data-open', '');\n        expect(popup).not.toHaveAttribute('data-swipe-dismiss');\n        expect(popup).not.toHaveAttribute('data-ending-style');\n        expect(popup.style.getPropertyValue('--drawer-swipe-movement-y')).toBe('0px');\n      } finally {\n        env.cleanup();\n      }\n    },\n  );\n\n  it.skipIf(isJSDOM)(\n    'allows dragging past a snap point when snapToSequentialPoints is enabled',\n    async () => {\n      const env = setupSwipeTestEnv();\n\n      const useFakeTimers = isJSDOM;\n      if (useFakeTimers) {\n        vi.useFakeTimers();\n      }\n\n      try {\n        await render(<SnapPointSequentialSkipCase />);\n        await flushMicrotasks();\n\n        const viewport = screen.getByTestId('viewport');\n        const popup = screen.getByTestId('popup');\n\n        env.pointAt(popup);\n\n        const startTime = 1000;\n        const moveTime = 1010;\n        const endTime = 1040;\n\n        await simulateTimedDownSwipe(viewport, 500, 50, startTime, moveTime, endTime);\n\n        expect(screen.getByTestId('active-snap').textContent).toBe('1');\n      } finally {\n        if (useFakeTimers) {\n          vi.useRealTimers();\n        }\n        env.cleanup();\n      }\n    },\n  );\n\n  it.skipIf(isJSDOM)(\n    'advances to the next snap point on fast flicks when snapToSequentialPoints is enabled',\n    async () => {\n      const env = setupSwipeTestEnv();\n\n      const useFakeTimers = isJSDOM;\n      if (useFakeTimers) {\n        vi.useFakeTimers();\n      }\n\n      try {\n        await render(<SnapPointSequentialSkipCase />);\n        await flushMicrotasks();\n\n        const viewport = screen.getByTestId('viewport');\n        const popup = screen.getByTestId('popup');\n\n        env.pointAt(popup);\n\n        const startTime = 2000;\n        const moveTime = 2010;\n        const endTime = 2050;\n\n        await simulateTimedDownSwipe(viewport, 500, 460, startTime, moveTime, endTime);\n\n        expect(screen.getByTestId('active-snap').textContent).toBe('300px');\n      } finally {\n        if (useFakeTimers) {\n          vi.useRealTimers();\n        }\n        env.cleanup();\n      }\n    },\n  );\n\n  it.skipIf(isJSDOM)('keeps the drawer open on low-velocity swipes near a snap point', async () => {\n    const handleOpenChange = vi.fn();\n    const env = setupSwipeTestEnv();\n\n    const useFakeTimers = isJSDOM;\n    if (useFakeTimers) {\n      vi.useFakeTimers();\n    }\n\n    try {\n      await render(<SnapPointSwipeCase onOpenChange={handleOpenChange} />);\n      await flushMicrotasks();\n\n      const viewport = screen.getByTestId('viewport');\n      const popup = screen.getByTestId('popup');\n\n      env.pointAt(popup);\n\n      const startTime = 1000;\n      const moveTime = 1005;\n      const settleTime = 1015;\n      const endTime = 1035;\n\n      await simulateTimedDownSwipe(viewport, 100, 120, startTime, moveTime, endTime, settleTime);\n\n      expect(handleOpenChange).not.toHaveBeenCalledWith(false);\n      expect(screen.getByTestId('active-snap').textContent).toBe('100px');\n    } finally {\n      if (useFakeTimers) {\n        vi.useRealTimers();\n      }\n      env.cleanup();\n    }\n  });\n\n  it.skipIf(isJSDOM)(\n    'keeps the drawer open when the release velocity reverses during an upward swipe',\n    async () => {\n      const handleOpenChange = vi.fn();\n      const env = setupSwipeTestEnv();\n\n      const useFakeTimers = isJSDOM;\n      if (useFakeTimers) {\n        vi.useFakeTimers();\n      }\n\n      try {\n        await render(<SnapPointSwipeCase onOpenChange={handleOpenChange} />);\n        await flushMicrotasks();\n\n        const viewport = screen.getByTestId('viewport');\n        const popup = screen.getByTestId('popup');\n\n        env.pointAt(popup);\n\n        const startTime = 1000;\n        const nudgeTime = 1003;\n        const peakTime = 1010;\n        const reversalTime = 1015;\n        const endTime = 1025;\n\n        await simulateTimedSwipe(viewport, [\n          { type: 'down', x: 100, y: 300, time: startTime },\n          { type: 'move', x: 100, y: 299, time: nudgeTime },\n          { type: 'move', x: 100, y: 120, time: peakTime },\n          { type: 'move', x: 100, y: 140, time: reversalTime },\n          { type: 'up', x: 100, y: 140, time: endTime },\n        ]);\n\n        expect(handleOpenChange).not.toHaveBeenCalledWith(false);\n      } finally {\n        if (useFakeTimers) {\n          vi.useRealTimers();\n        }\n        env.cleanup();\n      }\n    },\n  );\n\n  it('closes when CloseWatcher emits a close event', async () => {\n    const handleOpenChange = vi.fn();\n\n    class CloseWatcherStub extends EventTarget {\n      static instances: CloseWatcherStub[] = [];\n      onclose: ((this: CloseWatcherStub, ev: Event) => void) | null = null;\n      oncancel: ((this: CloseWatcherStub, ev: Event) => void) | null = null;\n      destroy = vi.fn();\n      close = vi.fn();\n      requestClose = vi.fn();\n      constructor() {\n        super();\n        CloseWatcherStub.instances.push(this);\n      }\n    }\n\n    const originalCloseWatcher = (window as Window & { CloseWatcher?: unknown | undefined })\n      .CloseWatcher;\n    (window as Window & { CloseWatcher?: typeof CloseWatcherStub | undefined }).CloseWatcher =\n      CloseWatcherStub;\n\n    try {\n      await render(\n        <Drawer.Root defaultOpen onOpenChange={handleOpenChange}>\n          <Drawer.Portal>\n            <Drawer.Viewport>\n              <Drawer.Popup>Drawer</Drawer.Popup>\n            </Drawer.Viewport>\n          </Drawer.Portal>\n        </Drawer.Root>,\n      );\n\n      await flushMicrotasks();\n\n      const instance = CloseWatcherStub.instances[CloseWatcherStub.instances.length - 1];\n      expect(instance).not.toBeUndefined();\n\n      await act(async () => {\n        instance.dispatchEvent(new Event('close'));\n        await flushMicrotasks();\n      });\n\n      expect(handleOpenChange).toHaveBeenCalled();\n      const lastCall = handleOpenChange.mock.calls[handleOpenChange.mock.calls.length - 1];\n      expect(lastCall?.[0]).toBe(false);\n      expect(lastCall?.[1]?.reason).toBe(REASONS.closeWatcher);\n    } finally {\n      (window as Window & { CloseWatcher?: unknown | undefined }).CloseWatcher =\n        originalCloseWatcher;\n    }\n  });\n});\n"
  },
  {
    "path": "packages/react/src/drawer/root/DrawerRoot.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useControlled } from '@base-ui/utils/useControlled';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { ownerWindow } from '@base-ui/utils/owner';\nimport { isAndroid } from '@base-ui/utils/detectBrowser';\nimport { useId } from '@base-ui/utils/useId';\nimport {\n  DrawerRootContext,\n  type DrawerNestedSwipeProgressStore,\n  type DrawerSwipeDirection,\n  useDrawerRootContext,\n  type DrawerSnapPoint,\n} from './DrawerRootContext';\nimport { Dialog } from '../../dialog';\nimport {\n  createChangeEventDetails,\n  type BaseUIChangeEventDetails,\n} from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport { useDialogRootContext } from '../../dialog/root/DialogRootContext';\nimport { useDrawerProviderContext } from '../provider/DrawerProviderContext';\nimport type { DialogHandle } from '../../dialog/store/DialogHandle';\nimport type { PayloadChildRenderFunction } from '../../utils/popups';\n\n/**\n * Groups all parts of the drawer.\n * Doesn't render its own HTML element.\n *\n * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)\n */\nexport function DrawerRoot<Payload = unknown>(props: DrawerRoot.Props<Payload>) {\n  const {\n    children,\n    open: openProp,\n    defaultOpen = false,\n    onOpenChange,\n    onOpenChangeComplete,\n    disablePointerDismissal = false,\n    modal = true,\n    actionsRef,\n    handle,\n    triggerId: triggerIdProp,\n    defaultTriggerId: defaultTriggerIdProp = null,\n    swipeDirection = 'down',\n    snapToSequentialPoints = false,\n    snapPoints,\n    snapPoint: snapPointProp,\n    defaultSnapPoint,\n    onSnapPointChange: onSnapPointChangeProp,\n  } = props;\n\n  const onSnapPointChange = useStableCallback(onSnapPointChangeProp);\n\n  const parentDrawerRootContext = useDrawerRootContext(true);\n\n  const notifyParentSwipeProgressChange = parentDrawerRootContext?.onNestedSwipeProgressChange;\n  const notifyParentFrontmostHeight = parentDrawerRootContext?.onNestedFrontmostHeightChange;\n  const notifyParentSwipingChange = parentDrawerRootContext?.onNestedSwipingChange;\n  const notifyParentHasNestedDrawer = parentDrawerRootContext?.onNestedDrawerPresenceChange;\n\n  const [popupHeight, setPopupHeight] = React.useState(0);\n  const [frontmostHeight, setFrontmostHeight] = React.useState(0);\n  const [hasNestedDrawer, setHasNestedDrawer] = React.useState(false);\n  const [nestedSwiping, setNestedSwiping] = React.useState(false);\n  const [nestedSwipeProgressStore] = React.useState(createNestedSwipeProgressStore);\n\n  const resolvedDefaultSnapPoint =\n    defaultSnapPoint !== undefined ? defaultSnapPoint : (snapPoints?.[0] ?? null);\n  const isSnapPointControlled = snapPointProp !== undefined;\n\n  const [activeSnapPoint, setActiveSnapPointUnwrapped] = useControlled({\n    controlled: snapPointProp,\n    default: resolvedDefaultSnapPoint,\n    name: 'Drawer',\n    state: 'snapPoint',\n  });\n\n  const isNestedDrawerOpenRef = React.useRef(false);\n\n  const setActiveSnapPoint = useStableCallback(\n    (\n      nextSnapPoint: DrawerSnapPoint | null,\n      eventDetails?: DrawerRoot.SnapPointChangeEventDetails,\n    ) => {\n      const resolvedEventDetails = eventDetails ?? createChangeEventDetails(REASONS.none);\n\n      onSnapPointChange?.(nextSnapPoint, resolvedEventDetails);\n\n      if (resolvedEventDetails.isCanceled) {\n        return;\n      }\n\n      setActiveSnapPointUnwrapped(nextSnapPoint);\n    },\n  );\n\n  const resolvedActiveSnapPoint = React.useMemo(() => {\n    if (isSnapPointControlled) {\n      return activeSnapPoint;\n    }\n\n    if (!snapPoints || snapPoints.length === 0) {\n      return activeSnapPoint;\n    }\n\n    if (\n      activeSnapPoint === null ||\n      !snapPoints.some((snapPoint) => Object.is(snapPoint, activeSnapPoint))\n    ) {\n      return resolvedDefaultSnapPoint;\n    }\n\n    return activeSnapPoint;\n  }, [activeSnapPoint, isSnapPointControlled, resolvedDefaultSnapPoint, snapPoints]);\n\n  const onPopupHeightChange = useStableCallback((height: number) => {\n    setPopupHeight(height);\n\n    if (!isNestedDrawerOpenRef.current && height > 0) {\n      setFrontmostHeight(height);\n    }\n  });\n\n  const onNestedFrontmostHeightChange = useStableCallback((height: number) => {\n    if (height > 0) {\n      isNestedDrawerOpenRef.current = true;\n      setFrontmostHeight(height);\n      return;\n    }\n\n    isNestedDrawerOpenRef.current = false;\n    if (popupHeight > 0) {\n      setFrontmostHeight(popupHeight);\n    }\n  });\n\n  const onNestedDrawerPresenceChange = useStableCallback((present: boolean) => {\n    setHasNestedDrawer(present);\n  });\n\n  const onNestedSwipeProgressChange = useStableCallback((progress: number) => {\n    nestedSwipeProgressStore.set(progress);\n    notifyParentSwipeProgressChange?.(progress);\n  });\n\n  const onNestedSwipingChange = useStableCallback((swiping: boolean) => {\n    setNestedSwiping(swiping);\n    notifyParentSwipingChange?.(swiping);\n  });\n\n  const handleOpenChange = useStableCallback(\n    (nextOpen: boolean, eventDetails: DrawerRoot.ChangeEventDetails) => {\n      onOpenChange?.(nextOpen, eventDetails);\n\n      if (eventDetails.isCanceled) {\n        return;\n      }\n\n      if (!nextOpen && snapPoints && snapPoints.length > 0) {\n        setActiveSnapPoint(\n          resolvedDefaultSnapPoint,\n          createChangeEventDetails(\n            eventDetails.reason,\n            eventDetails.event,\n            eventDetails.trigger as HTMLElement | undefined,\n          ),\n        );\n      }\n    },\n  );\n\n  const contextValue: DrawerRootContext = React.useMemo(\n    () => ({\n      swipeDirection,\n      snapToSequentialPoints,\n      snapPoints,\n      activeSnapPoint: resolvedActiveSnapPoint,\n      setActiveSnapPoint,\n      frontmostHeight,\n      popupHeight,\n      hasNestedDrawer,\n      nestedSwiping,\n      nestedSwipeProgressStore,\n      onNestedDrawerPresenceChange,\n      onPopupHeightChange,\n      onNestedFrontmostHeightChange,\n      onNestedSwipingChange,\n      onNestedSwipeProgressChange,\n      notifyParentFrontmostHeight,\n      notifyParentSwipingChange,\n      notifyParentSwipeProgressChange,\n      notifyParentHasNestedDrawer,\n    }),\n    [\n      resolvedActiveSnapPoint,\n      frontmostHeight,\n      hasNestedDrawer,\n      nestedSwiping,\n      nestedSwipeProgressStore,\n      notifyParentHasNestedDrawer,\n      notifyParentSwipeProgressChange,\n      notifyParentSwipingChange,\n      notifyParentFrontmostHeight,\n      onNestedDrawerPresenceChange,\n      onNestedFrontmostHeightChange,\n      onNestedSwipeProgressChange,\n      onNestedSwipingChange,\n      onPopupHeightChange,\n      popupHeight,\n      setActiveSnapPoint,\n      snapPoints,\n      snapToSequentialPoints,\n      swipeDirection,\n    ],\n  );\n\n  const resolvedChildren: React.ReactNode | PayloadChildRenderFunction<Payload> =\n    typeof children === 'function' ? (\n      (payload) => (\n        <React.Fragment>\n          <DrawerProviderReporter />\n          {children(payload)}\n        </React.Fragment>\n      )\n    ) : (\n      <React.Fragment>\n        <DrawerProviderReporter />\n        {children}\n      </React.Fragment>\n    );\n\n  return (\n    <DrawerRootContext.Provider value={contextValue}>\n      <Dialog.Root\n        open={openProp}\n        defaultOpen={defaultOpen}\n        onOpenChange={handleOpenChange}\n        onOpenChangeComplete={onOpenChangeComplete}\n        disablePointerDismissal={disablePointerDismissal}\n        modal={modal}\n        actionsRef={actionsRef}\n        handle={handle}\n        triggerId={triggerIdProp}\n        defaultTriggerId={defaultTriggerIdProp}\n      >\n        {resolvedChildren}\n      </Dialog.Root>\n    </DrawerRootContext.Provider>\n  );\n}\n\nexport interface DrawerRootState {}\n\nexport interface DrawerRootProps<Payload = unknown> {\n  /**\n   * Whether the drawer is currently open.\n   */\n  open?: boolean | undefined;\n  /**\n   * Whether the drawer is initially open.\n   *\n   * To render a controlled drawer, use the `open` prop instead.\n   * @default false\n   */\n  defaultOpen?: boolean | undefined;\n  /**\n   * Determines if the drawer enters a modal state when open.\n   * - `true`: user interaction is limited to just the drawer: focus is trapped, document page scroll is locked, and pointer interactions on outside elements are disabled.\n   * - `false`: user interaction with the rest of the document is allowed.\n   * - `'trap-focus'`: focus is trapped inside the drawer, but document page scroll is not locked and pointer interactions outside of it remain enabled.\n   * @default true\n   */\n  modal?: boolean | 'trap-focus' | undefined;\n  /**\n   * Event handler called when the drawer is opened or closed.\n   */\n  onOpenChange?: ((open: boolean, eventDetails: DrawerRoot.ChangeEventDetails) => void) | undefined;\n  /**\n   * Event handler called after any animations complete when the drawer is opened or closed.\n   */\n  onOpenChangeComplete?: ((open: boolean) => void) | undefined;\n  /**\n   * Determines whether the drawer should close on outside clicks.\n   * @default false\n   */\n  disablePointerDismissal?: boolean | undefined;\n  /**\n   * A ref to imperative actions.\n   * - `unmount`: When specified, the drawer will not be unmounted when closed.\n   * Instead, the `unmount` function must be called to unmount the drawer manually.\n   * Useful when the drawer's animation is controlled by an external library.\n   * - `close`: Closes the drawer imperatively when called.\n   */\n  actionsRef?: React.RefObject<DrawerRoot.Actions | null> | undefined;\n  /**\n   * A handle to associate the drawer with a trigger.\n   * If specified, allows detached triggers to control the drawer's open state.\n   * Can be created with the Drawer.createHandle() method.\n   */\n  handle?: DialogHandle<Payload> | undefined;\n  /**\n   * ID of the trigger that the drawer is associated with.\n   * This is useful in conjunction with the `open` prop to create a controlled drawer.\n   * There's no need to specify this prop when the drawer is uncontrolled (i.e. when the `open` prop is not set).\n   */\n  triggerId?: string | null | undefined;\n  /**\n   * ID of the trigger that the drawer is associated with.\n   * This is useful in conjunction with the `defaultOpen` prop to create an initially open drawer.\n   */\n  defaultTriggerId?: string | null | undefined;\n  /**\n   * The content of the drawer.\n   */\n  children?: React.ReactNode | PayloadChildRenderFunction<Payload>;\n  /**\n   * The swipe direction used to dismiss the drawer.\n   * @default 'down'\n   */\n  swipeDirection?: DrawerSwipeDirection | undefined;\n  /**\n   * Snap points used to position the drawer.\n   * Use numbers between 0 and 1 to represent fractions of the viewport height,\n   * numbers greater than 1 as pixel values, or strings in `px`/`rem` units\n   * (for example, `'148px'` or `'30rem'`).\n   */\n  snapPoints?: DrawerSnapPoint[] | undefined;\n  /**\n   * Disables velocity-based snap skipping so drag distance determines the next snap point.\n   * @default false\n   */\n  snapToSequentialPoints?: boolean | undefined;\n  /**\n   * The currently active snap point. Use with `onSnapPointChange` to control the snap point.\n   */\n  snapPoint?: DrawerSnapPoint | null | undefined;\n  /**\n   * The initial snap point value when uncontrolled.\n   */\n  defaultSnapPoint?: DrawerSnapPoint | null | undefined;\n  /**\n   * Callback fired when the snap point changes.\n   */\n  onSnapPointChange?:\n    | ((\n        snapPoint: DrawerSnapPoint | null,\n        eventDetails: DrawerRoot.SnapPointChangeEventDetails,\n      ) => void)\n    | undefined;\n}\n\nexport interface DrawerRootActions {\n  unmount: () => void;\n  close: () => void;\n}\n\nexport type DrawerRootChangeEventReason =\n  | typeof REASONS.triggerPress\n  | typeof REASONS.outsidePress\n  | typeof REASONS.escapeKey\n  | typeof REASONS.closeWatcher\n  | typeof REASONS.closePress\n  | typeof REASONS.focusOut\n  | typeof REASONS.imperativeAction\n  | typeof REASONS.swipe\n  | typeof REASONS.none;\n\nexport type DrawerRootChangeEventDetails =\n  BaseUIChangeEventDetails<DrawerRoot.ChangeEventReason> & {\n    preventUnmountOnClose(): void;\n  };\n\nexport type DrawerRootSnapPointChangeEventReason = DrawerRootChangeEventReason;\n\nexport type DrawerRootSnapPointChangeEventDetails =\n  BaseUIChangeEventDetails<DrawerRootSnapPointChangeEventReason>;\n\nexport namespace DrawerRoot {\n  export type State = DrawerRootState;\n  export type Props<Payload = unknown> = DrawerRootProps<Payload>;\n  export type Actions = DrawerRootActions;\n  export type ChangeEventReason = DrawerRootChangeEventReason;\n  export type ChangeEventDetails = DrawerRootChangeEventDetails;\n  export type SnapPointChangeEventReason = DrawerRootSnapPointChangeEventReason;\n  export type SnapPointChangeEventDetails = DrawerRootSnapPointChangeEventDetails;\n  export type SnapPoint = DrawerSnapPoint;\n}\n\ninterface NestedSwipeProgressStore extends DrawerNestedSwipeProgressStore {\n  set: (progress: number) => void;\n}\n\nfunction createNestedSwipeProgressStore(): NestedSwipeProgressStore {\n  let progress = 0;\n  const listeners = new Set<() => void>();\n\n  return {\n    getSnapshot: () => progress,\n    set(nextProgress) {\n      const resolved = Number.isFinite(nextProgress) ? nextProgress : 0;\n      if (resolved === progress) {\n        return;\n      }\n\n      progress = resolved;\n      listeners.forEach((listener) => {\n        listener();\n      });\n    },\n    subscribe(listener) {\n      listeners.add(listener);\n      return () => {\n        listeners.delete(listener);\n      };\n    },\n  };\n}\n\nfunction DrawerProviderReporter() {\n  const drawerId = useId();\n\n  const providerContext = useDrawerProviderContext(true);\n  const dialogRootContext = useDialogRootContext(false);\n\n  const open = dialogRootContext.store.useState('open');\n  const nestedOpenDialogCount = dialogRootContext.store.useState('nestedOpenDialogCount');\n  const popupElement = dialogRootContext.store.useState('popupElement');\n\n  const isTopmost = nestedOpenDialogCount === 0;\n\n  React.useEffect(() => {\n    if (!providerContext || drawerId == null) {\n      return undefined;\n    }\n\n    return () => {\n      providerContext.removeDrawer(drawerId);\n    };\n  }, [drawerId, providerContext]);\n\n  React.useEffect(() => {\n    if (drawerId == null) {\n      return;\n    }\n\n    providerContext?.setDrawerOpen(drawerId, open);\n  }, [drawerId, open, providerContext]);\n\n  React.useEffect(() => {\n    // CloseWatcher enables the Android back gesture (Chromium-only).\n    // Keep this Android-only for now to avoid interfering with Escape/nesting semantics on desktop due to `useDismiss`.\n    if (!open || !isTopmost || !isAndroid) {\n      return undefined;\n    }\n\n    const win = ownerWindow(popupElement);\n\n    const CloseWatcherCtor = (win as Window & { CloseWatcher?: (new () => any) | undefined })\n      .CloseWatcher;\n    if (!CloseWatcherCtor) {\n      return undefined;\n    }\n\n    function handleCloseWatcher(event: Event) {\n      if (!dialogRootContext.store.select('open')) {\n        return;\n      }\n      dialogRootContext.store.setOpen(false, createChangeEventDetails(REASONS.closeWatcher, event));\n    }\n\n    const closeWatcher = new CloseWatcherCtor();\n\n    closeWatcher.addEventListener('close', handleCloseWatcher);\n\n    return () => {\n      closeWatcher.removeEventListener('close', handleCloseWatcher);\n      closeWatcher.destroy();\n    };\n  }, [dialogRootContext.store, isTopmost, open, popupElement]);\n\n  return null;\n}\n"
  },
  {
    "path": "packages/react/src/drawer/root/DrawerRootContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport type { SwipeDirection } from '../../utils/useSwipeDismiss';\nimport type { DrawerRootSnapPointChangeEventDetails } from './DrawerRoot';\n\nexport type DrawerSwipeDirection = SwipeDirection;\nexport type DrawerSnapPoint = number | string;\n\nexport interface DrawerNestedSwipeProgressStore {\n  getSnapshot: () => number;\n  subscribe: (listener: () => void) => () => void;\n}\n\nexport interface DrawerRootContext {\n  swipeDirection: DrawerSwipeDirection;\n  /**\n   * Whether snap points can be skipped based on swipe velocity.\n   */\n  snapToSequentialPoints: boolean;\n  /**\n   * Snap points used to size/position the drawer.\n   */\n  snapPoints?: DrawerSnapPoint[] | undefined;\n  /**\n   * The currently active snap point.\n   */\n  activeSnapPoint?: DrawerSnapPoint | null | undefined;\n  /**\n   * Updates the currently active snap point.\n   */\n  setActiveSnapPoint?:\n    | ((\n        snapPoint: DrawerSnapPoint | null,\n        eventDetails?: DrawerRootSnapPointChangeEventDetails,\n      ) => void)\n    | undefined;\n  /**\n   * The measured height of the frontmost open drawer within the current nested drawer stack.\n   */\n  frontmostHeight: number;\n  /**\n   * The measured height of the drawer popup element.\n   */\n  popupHeight: number;\n  /**\n   * Whether the current drawer has a nested drawer mounted in its stack (including while it is\n   * transitioning out).\n   */\n  hasNestedDrawer: boolean;\n  /**\n   * Whether a nested drawer is currently being swiped.\n   */\n  nestedSwiping: boolean;\n  /**\n   * Provides nested swipe progress without re-rendering the drawer tree.\n   */\n  nestedSwipeProgressStore: DrawerNestedSwipeProgressStore;\n  /**\n   * Called by a nested drawer to report whether it is still present (open or transitioning out).\n   */\n  onNestedDrawerPresenceChange: (present: boolean) => void;\n  /**\n   * Called by the drawer popup to report its own measured height.\n   */\n  onPopupHeightChange: (height: number) => void;\n  /**\n   * Called by a nested drawer to report the frontmost height of its own stack.\n   */\n  onNestedFrontmostHeightChange: (height: number) => void;\n  /**\n   * Called by a nested drawer to report whether it's being swiped.\n   */\n  onNestedSwipingChange: (swiping: boolean) => void;\n  /**\n   * Called by a nested drawer to report its swipe progress.\n   */\n  onNestedSwipeProgressChange: (progress: number) => void;\n  /**\n   * Provided to nested drawers so they can report their frontmost height to the parent drawer.\n   */\n  notifyParentFrontmostHeight?: ((height: number) => void) | undefined;\n  /**\n   * Provided to nested drawers so they can report swiping to the parent drawer.\n   */\n  notifyParentSwipingChange?: ((swiping: boolean) => void) | undefined;\n  /**\n   * Provided to nested drawers so they can report swipe progress to the parent drawer.\n   */\n  notifyParentSwipeProgressChange?: ((progress: number) => void) | undefined;\n  /**\n   * Provided to nested drawers so they can report whether they are still present (open or\n   * transitioning out) to the parent drawer.\n   */\n  notifyParentHasNestedDrawer?: ((present: boolean) => void) | undefined;\n}\n\nexport const DrawerRootContext = React.createContext<DrawerRootContext | undefined>(undefined);\n\nexport function useDrawerRootContext(optional?: false): DrawerRootContext;\nexport function useDrawerRootContext(optional: true): DrawerRootContext | undefined;\nexport function useDrawerRootContext(optional?: boolean) {\n  const drawerRootContext = React.useContext(DrawerRootContext);\n\n  if (optional === false && drawerRootContext === undefined) {\n    throw new Error(\n      'Base UI: DrawerRootContext is missing. Drawer parts must be placed within <Drawer.Root>.',\n    );\n  }\n\n  return drawerRootContext;\n}\n"
  },
  {
    "path": "packages/react/src/drawer/root/useDrawerSnapPoints.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { ownerDocument } from '@base-ui/utils/owner';\nimport { clamp } from '../../utils/clamp';\nimport { useDialogRootContext } from '../../dialog/root/DialogRootContext';\nimport { useDrawerRootContext } from './DrawerRootContext';\nimport type { DrawerSnapPoint } from './DrawerRootContext';\n\nexport interface ResolvedDrawerSnapPoint {\n  value: DrawerSnapPoint;\n  height: number;\n  offset: number;\n}\n\nfunction resolveSnapPointValue(\n  snapPoint: DrawerSnapPoint,\n  viewportHeight: number,\n  rootFontSize: number,\n) {\n  if (!Number.isFinite(viewportHeight) || viewportHeight <= 0) {\n    return null;\n  }\n\n  if (typeof snapPoint === 'number') {\n    if (!Number.isFinite(snapPoint)) {\n      return null;\n    }\n\n    if (snapPoint <= 1) {\n      return clamp(snapPoint, 0, 1) * viewportHeight;\n    }\n\n    return snapPoint;\n  }\n\n  const trimmed = snapPoint.trim();\n\n  if (trimmed.endsWith('px')) {\n    const value = Number.parseFloat(trimmed);\n    return Number.isFinite(value) ? value : null;\n  }\n\n  if (trimmed.endsWith('rem')) {\n    const value = Number.parseFloat(trimmed);\n    return Number.isFinite(value) ? value * rootFontSize : null;\n  }\n\n  return null;\n}\n\nfunction findClosestSnapPoint(\n  height: number,\n  points: ResolvedDrawerSnapPoint[],\n): ResolvedDrawerSnapPoint | null {\n  let closest: ResolvedDrawerSnapPoint | null = null;\n  let closestDistance = Infinity;\n\n  for (const point of points) {\n    const distance = Math.abs(point.height - height);\n    if (distance < closestDistance) {\n      closestDistance = distance;\n      closest = point;\n    }\n  }\n\n  return closest;\n}\n\nexport function useDrawerSnapPoints() {\n  const { store } = useDialogRootContext();\n  const { snapPoints, activeSnapPoint, setActiveSnapPoint, popupHeight } = useDrawerRootContext();\n  const viewportElement = store.useState('viewportElement');\n\n  const [viewportHeight, setViewportHeight] = React.useState(0);\n  const [rootFontSize, setRootFontSize] = React.useState(16);\n\n  const measureViewportHeight = useStableCallback(() => {\n    const doc = ownerDocument(viewportElement);\n    const html = doc.documentElement;\n\n    if (viewportElement) {\n      setViewportHeight(viewportElement.offsetHeight);\n    }\n\n    if (!viewportElement) {\n      setViewportHeight(html.clientHeight);\n    }\n\n    const fontSize = parseFloat(getComputedStyle(html).fontSize);\n    if (Number.isFinite(fontSize)) {\n      setRootFontSize(fontSize);\n    }\n  });\n\n  useIsoLayoutEffect(() => {\n    measureViewportHeight();\n\n    if (!viewportElement || typeof ResizeObserver !== 'function') {\n      return undefined;\n    }\n\n    const resizeObserver = new ResizeObserver(measureViewportHeight);\n    resizeObserver.observe(viewportElement);\n    return () => {\n      resizeObserver.disconnect();\n    };\n  }, [measureViewportHeight, viewportElement]);\n\n  const resolvedSnapPoints = React.useMemo<ResolvedDrawerSnapPoint[]>(() => {\n    if (!snapPoints || snapPoints.length === 0 || viewportHeight <= 0 || popupHeight <= 0) {\n      return [];\n    }\n\n    const maxHeight = Math.min(popupHeight, viewportHeight);\n    if (!Number.isFinite(maxHeight) || maxHeight <= 0) {\n      return [];\n    }\n\n    const resolved = snapPoints\n      .map((value): ResolvedDrawerSnapPoint | null => {\n        const resolvedHeight = resolveSnapPointValue(value, viewportHeight, rootFontSize);\n        if (resolvedHeight === null || !Number.isFinite(resolvedHeight)) {\n          return null;\n        }\n\n        const clampedHeight = clamp(resolvedHeight, 0, maxHeight);\n        return {\n          value,\n          height: clampedHeight,\n          offset: Math.max(0, popupHeight - clampedHeight),\n        };\n      })\n      .filter((point): point is ResolvedDrawerSnapPoint => Boolean(point));\n\n    if (resolved.length <= 1) {\n      return resolved;\n    }\n\n    const deduped: ResolvedDrawerSnapPoint[] = [];\n    const seenHeights: number[] = [];\n\n    for (let index = resolved.length - 1; index >= 0; index -= 1) {\n      const point = resolved[index];\n      const isDuplicate = seenHeights.some((height) => Math.abs(height - point.height) <= 1);\n      if (isDuplicate) {\n        continue;\n      }\n\n      seenHeights.push(point.height);\n      deduped.push(point);\n    }\n\n    deduped.reverse();\n    return deduped;\n  }, [popupHeight, rootFontSize, snapPoints, viewportHeight]);\n\n  const resolvedActiveSnapPoint = React.useMemo(() => {\n    if (activeSnapPoint === undefined) {\n      return resolvedSnapPoints[0];\n    }\n\n    if (activeSnapPoint === null) {\n      return undefined;\n    }\n\n    const exactMatch = resolvedSnapPoints.find((point) => Object.is(point.value, activeSnapPoint));\n    if (exactMatch) {\n      return exactMatch;\n    }\n\n    const maxHeight = Math.min(popupHeight, viewportHeight);\n    const resolvedHeight = resolveSnapPointValue(activeSnapPoint, viewportHeight, rootFontSize);\n    if (resolvedHeight === null || !Number.isFinite(resolvedHeight)) {\n      return undefined;\n    }\n\n    const clampedHeight = clamp(resolvedHeight, 0, maxHeight);\n    return findClosestSnapPoint(clampedHeight, resolvedSnapPoints) ?? undefined;\n  }, [activeSnapPoint, popupHeight, resolvedSnapPoints, rootFontSize, viewportHeight]);\n\n  return {\n    snapPoints,\n    activeSnapPoint,\n    setActiveSnapPoint,\n    popupHeight,\n    viewportHeight,\n    resolvedSnapPoints,\n    activeSnapPointOffset: resolvedActiveSnapPoint?.offset ?? null,\n  };\n}\n"
  },
  {
    "path": "packages/react/src/drawer/swipe-area/DrawerSwipeArea.test.tsx",
    "content": "import { beforeAll, describe, expect, it, vi } from 'vitest';\nimport { Drawer } from '@base-ui/react/drawer';\nimport { act, fireEvent, flushMicrotasks, screen, waitFor } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\n\ntype Point = {\n  x: number;\n  y: number;\n};\n\ntype SwipeInput = 'pointer' | 'touch';\n\ntype SwipeOptions = {\n  beforeRelease?: (() => Promise<unknown>) | (() => unknown);\n  input?: SwipeInput;\n  timeStepMs?: number;\n  startTimeMs?: number;\n};\n\nfunction createTouch(target: EventTarget, point: { clientX: number; clientY: number }) {\n  if (typeof Touch === 'function') {\n    return new Touch({\n      identifier: 1,\n      target,\n      ...point,\n    });\n  }\n\n  return point;\n}\n\nasync function swipe(element: HTMLElement, start: Point, end: Point, options: SwipeOptions = {}) {\n  const stepX = start.x + (end.x === start.x ? 0 : Math.sign(end.x - start.x));\n  const stepY = start.y + (end.y === start.y ? 0 : Math.sign(end.y - start.y));\n  const { beforeRelease, input = 'pointer', timeStepMs, startTimeMs = 0 } = options;\n  const useTimeStamp = input === 'pointer' && typeof timeStepMs === 'number';\n  let timeStamp = startTimeMs;\n\n  if (input === 'touch') {\n    fireEvent.touchStart(element, {\n      bubbles: true,\n      touches: [\n        createTouch(element, {\n          clientX: start.x,\n          clientY: start.y,\n        }),\n      ],\n    });\n\n    await flushMicrotasks();\n\n    fireEvent.touchMove(element, {\n      bubbles: true,\n      touches: [\n        createTouch(element, {\n          clientX: stepX,\n          clientY: stepY,\n        }),\n      ],\n    });\n\n    await flushMicrotasks();\n\n    fireEvent.touchMove(element, {\n      bubbles: true,\n      touches: [\n        createTouch(element, {\n          clientX: end.x,\n          clientY: end.y,\n        }),\n      ],\n    });\n\n    await flushMicrotasks();\n\n    if (beforeRelease) {\n      await beforeRelease();\n      await flushMicrotasks();\n    }\n\n    fireEvent.touchEnd(element, {\n      bubbles: true,\n      changedTouches: [\n        createTouch(element, {\n          clientX: end.x,\n          clientY: end.y,\n        }),\n      ],\n    });\n\n    await flushMicrotasks();\n    return;\n  }\n\n  fireEvent.pointerDown(element, {\n    button: 0,\n    buttons: 1,\n    pointerId: 1,\n    clientX: start.x,\n    clientY: start.y,\n    pointerType: 'mouse',\n    ...(useTimeStamp ? { timeStamp } : null),\n  });\n\n  await flushMicrotasks();\n\n  if (useTimeStamp) {\n    timeStamp += timeStepMs;\n  }\n\n  fireEvent.pointerMove(element, {\n    pointerId: 1,\n    clientX: stepX,\n    clientY: stepY,\n    pointerType: 'mouse',\n    ...(useTimeStamp ? { timeStamp } : null),\n  });\n\n  await flushMicrotasks();\n\n  if (useTimeStamp) {\n    timeStamp += timeStepMs;\n  }\n\n  fireEvent.pointerMove(element, {\n    pointerId: 1,\n    clientX: end.x,\n    clientY: end.y,\n    pointerType: 'mouse',\n    ...(useTimeStamp ? { timeStamp } : null),\n  });\n\n  await flushMicrotasks();\n\n  if (beforeRelease) {\n    await beforeRelease();\n    await flushMicrotasks();\n  }\n\n  if (useTimeStamp) {\n    timeStamp += timeStepMs;\n  }\n\n  fireEvent.pointerUp(element, {\n    pointerId: 1,\n    clientX: end.x,\n    clientY: end.y,\n    pointerType: 'mouse',\n    ...(useTimeStamp ? { timeStamp } : null),\n  });\n\n  await flushMicrotasks();\n}\n\nfunction wait(ms: number) {\n  return new Promise<void>((resolve) => {\n    setTimeout(resolve, ms);\n  });\n}\n\nfunction nextMacrotask() {\n  return wait(0);\n}\n\nasync function swipeUp(element: HTMLElement, startY: number, endY: number, options?: SwipeOptions) {\n  return swipe(element, { x: 10, y: startY }, { x: 10, y: endY }, options);\n}\n\nasync function swipeLeft(\n  element: HTMLElement,\n  startX: number,\n  endX: number,\n  options?: SwipeOptions,\n) {\n  return swipe(element, { x: startX, y: 10 }, { x: endX, y: 10 }, options);\n}\n\ndescribe('<Drawer.SwipeArea />', () => {\n  beforeAll(function beforeHook() {\n    // PointerEvent not fully implemented in jsdom, causing fireEvent.pointer* to ignore options.\n    // https://github.com/jsdom/jsdom/issues/2527\n    (window as any).PointerEvent = window.MouseEvent;\n  });\n\n  const { render } = createRenderer();\n\n  describeConformance(<Drawer.SwipeArea />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(<Drawer.Root>{node}</Drawer.Root>);\n    },\n  }));\n\n  it('opens the drawer when swiped in the open direction', async () => {\n    await render(\n      <Drawer.Root>\n        <Drawer.SwipeArea data-testid=\"swipe-area\" />\n      </Drawer.Root>,\n    );\n\n    const swipeArea = screen.getByTestId('swipe-area');\n\n    expect(swipeArea).toHaveAttribute('data-closed', '');\n\n    await swipeUp(swipeArea, 120, 40);\n\n    expect(swipeArea).toHaveAttribute('data-open', '');\n  });\n\n  it('does not open when the swipe direction never locks to the open direction', async () => {\n    const handleOpenChange = vi.fn();\n\n    await render(\n      <Drawer.Root onOpenChange={handleOpenChange}>\n        <Drawer.SwipeArea data-testid=\"swipe-area\" />\n      </Drawer.Root>,\n    );\n\n    const swipeArea = screen.getByTestId('swipe-area');\n\n    await swipe(swipeArea, { x: 10, y: 120 }, { x: 70, y: 118 });\n\n    expect(swipeArea).toHaveAttribute('data-closed', '');\n    expect(handleOpenChange).not.toHaveBeenCalled();\n  });\n\n  it('prevents default pointer down for non-touch swipes', async () => {\n    await render(\n      <Drawer.Root>\n        <Drawer.SwipeArea data-testid=\"swipe-area\" />\n      </Drawer.Root>,\n    );\n\n    const notCancelled = fireEvent.pointerDown(screen.getByTestId('swipe-area'), {\n      button: 0,\n      buttons: 1,\n      pointerId: 1,\n      clientX: 10,\n      clientY: 120,\n      pointerType: 'mouse',\n    });\n\n    expect(notCancelled).toBe(false);\n  });\n\n  it('does not open the drawer when disabled', async () => {\n    await render(\n      <Drawer.Root>\n        <Drawer.SwipeArea data-testid=\"swipe-area\" disabled />\n      </Drawer.Root>,\n    );\n\n    const swipeArea = screen.getByTestId('swipe-area');\n\n    await swipeUp(swipeArea, 120, 40);\n\n    expect(swipeArea).toHaveAttribute('data-closed', '');\n  });\n\n  it('respects custom swipeDirection', async () => {\n    await render(\n      <Drawer.Root>\n        <Drawer.SwipeArea data-testid=\"swipe-area\" swipeDirection=\"left\" />\n      </Drawer.Root>,\n    );\n\n    const swipeArea = screen.getByTestId('swipe-area');\n\n    await swipeLeft(swipeArea, 120, 40);\n\n    expect(swipeArea).toHaveAttribute('data-open', '');\n  });\n\n  it('opens the drawer when swiped with touch events', async () => {\n    await render(\n      <Drawer.Root>\n        <Drawer.SwipeArea data-testid=\"swipe-area\" />\n        <Drawer.Portal>\n          <Drawer.Viewport>\n            <Drawer.Popup data-testid=\"popup\">Drawer</Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const swipeArea = screen.getByTestId('swipe-area');\n\n    await swipeUp(swipeArea, 120, 40, { input: 'touch' });\n\n    expect(swipeArea).toHaveAttribute('data-open', '');\n    expect(screen.getByTestId('popup')).toHaveAttribute('data-open', '');\n  });\n\n  it('applies data-swiping during an active swipe gesture', async () => {\n    await render(\n      <Drawer.Root>\n        <Drawer.SwipeArea data-testid=\"swipe-area\" />\n      </Drawer.Root>,\n    );\n\n    const swipeArea = screen.getByTestId('swipe-area');\n\n    await swipeUp(swipeArea, 120, 40, {\n      beforeRelease() {\n        expect(swipeArea).toHaveAttribute('data-swiping', '');\n      },\n    });\n\n    expect(swipeArea).not.toHaveAttribute('data-swiping');\n  });\n\n  it('re-enables outside press dismissal after opening by swipe', async () => {\n    await render(\n      <Drawer.Root>\n        <Drawer.SwipeArea data-testid=\"swipe-area\" />\n        <Drawer.Portal>\n          <Drawer.Viewport>\n            <Drawer.Popup data-testid=\"popup\">Drawer</Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const swipeArea = screen.getByTestId('swipe-area');\n\n    await swipeUp(swipeArea, 120, 40, { input: 'touch' });\n\n    expect(screen.getByTestId('popup')).toHaveAttribute('data-open', '');\n\n    await act(async () => {\n      await nextMacrotask();\n    });\n\n    fireEvent.click(document.body);\n\n    await waitFor(() => {\n      expect(screen.queryByTestId('popup')).toBe(null);\n    });\n\n    expect(swipeArea).toHaveAttribute('data-closed', '');\n  });\n\n  it.skipIf(isJSDOM)('uses a size-based swipe threshold by default', async () => {\n    await render(\n      <Drawer.Root>\n        <Drawer.SwipeArea data-testid=\"swipe-area\" />\n        <Drawer.Portal>\n          <Drawer.Viewport>\n            <Drawer.Popup data-testid=\"popup\" style={{ height: 200 }}>\n              Drawer\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const swipeArea = screen.getByTestId('swipe-area');\n    const slowSwipe = {\n      async beforeRelease() {\n        const popup = await screen.findByTestId('popup');\n        Object.defineProperty(popup, 'offsetHeight', { value: 200, configurable: true });\n        // Age the last drag sample past the flick-velocity window so distance decides the outcome.\n        await act(async () => {\n          await wait(81);\n        });\n      },\n    };\n\n    await swipeUp(swipeArea, 200, 130, slowSwipe);\n\n    expect(swipeArea).toHaveAttribute('data-closed', '');\n\n    await swipeUp(swipeArea, 200, 80, slowSwipe);\n\n    expect(swipeArea).toHaveAttribute('data-open', '');\n  });\n});\n"
  },
  {
    "path": "packages/react/src/drawer/swipe-area/DrawerSwipeArea.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useTimeout } from '@base-ui/utils/useTimeout';\nimport { useDialogRootContext } from '../../dialog/root/DialogRootContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport type { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport {\n  getDisplacement,\n  getElementTransform,\n  useSwipeDismiss,\n  type SwipeDirection,\n} from '../../utils/useSwipeDismiss';\nimport { DrawerPopupCssVars } from '../popup/DrawerPopupCssVars';\nimport { DrawerPopupDataAttributes } from '../popup/DrawerPopupDataAttributes';\nimport { DrawerBackdropCssVars } from '../backdrop/DrawerBackdropCssVars';\nimport { useDrawerRootContext, type DrawerSwipeDirection } from '../root/DrawerRootContext';\nimport { useBaseUiId } from '../../utils/useBaseUiId';\nimport { useTriggerRegistration } from '../../utils/popups';\nimport { useDrawerProviderContext } from '../provider/DrawerProviderContext';\nimport { DrawerSwipeAreaDataAttributes } from './DrawerSwipeAreaDataAttributes';\n\nconst DEFAULT_SWIPE_OPEN_RATIO = 0.5;\nconst MIN_SWIPE_START_DISTANCE = 1;\nconst VELOCITY_THRESHOLD = 0.1;\nconst FALLBACK_SWIPE_OPEN_THRESHOLD = 40;\n\nconst SWIPE_AREA_OPEN_HOOK: Record<string, string> = {\n  [DrawerSwipeAreaDataAttributes.open]: '',\n};\n\nconst SWIPE_AREA_CLOSED_HOOK: Record<string, string> = {\n  [DrawerSwipeAreaDataAttributes.closed]: '',\n};\n\nconst SWIPE_AREA_SWIPING_HOOK: Record<string, string> = {\n  [DrawerSwipeAreaDataAttributes.swiping]: '',\n};\n\nconst SWIPE_AREA_DISABLED_HOOK: Record<string, string> = {\n  [DrawerSwipeAreaDataAttributes.disabled]: '',\n};\n\nconst stateAttributesMapping: StateAttributesMapping<DrawerSwipeAreaState> = {\n  open(value) {\n    return value ? SWIPE_AREA_OPEN_HOOK : SWIPE_AREA_CLOSED_HOOK;\n  },\n  swiping(value) {\n    return value ? SWIPE_AREA_SWIPING_HOOK : null;\n  },\n  swipeDirection(value) {\n    return value ? { [DrawerSwipeAreaDataAttributes.swipeDirection]: value } : null;\n  },\n  disabled(value) {\n    return value ? SWIPE_AREA_DISABLED_HOOK : null;\n  },\n};\n\nconst oppositeSwipeDirection: Record<DrawerSwipeDirection, DrawerSwipeDirection> = {\n  up: 'down',\n  down: 'up',\n  left: 'right',\n  right: 'left',\n};\n\nfunction resolveTouchAction(direction: DrawerSwipeDirection) {\n  return direction === 'left' || direction === 'right' ? 'pan-y' : 'pan-x';\n}\n\n/**\n * An invisible area that listens for swipe gestures to open the drawer.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)\n */\nexport const DrawerSwipeArea = React.forwardRef(function DrawerSwipeArea(\n  componentProps: DrawerSwipeArea.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const {\n    className,\n    render,\n    disabled = false,\n    swipeDirection: swipeDirectionProp,\n    ...elementProps\n  } = componentProps;\n\n  const { store } = useDialogRootContext();\n  const { swipeDirection, frontmostHeight } = useDrawerRootContext();\n  const providerContext = useDrawerProviderContext(true);\n\n  const [swipeActive, setSwipeActive] = React.useState(false);\n\n  const releaseDismissTimeout = useTimeout();\n  const swipeAreaRef = React.useRef<HTMLDivElement>(null);\n  const swipeStartEventRef = React.useRef<PointerEvent | TouchEvent | null>(null);\n  const openedBySwipeRef = React.useRef(false);\n  const dragDeltaRef = React.useRef({ x: 0, y: 0 });\n  const closedOffsetRef = React.useRef<number | null>(null);\n  const appliedSwipeStylesRef = React.useRef(false);\n  const popupTransitionRef = React.useRef<string | null>(null);\n\n  const swipeAreaId = useBaseUiId(componentProps.id);\n  const registerTrigger = useTriggerRegistration(swipeAreaId, store);\n\n  const open = store.useState('open');\n\n  const resolvedSwipeDirection = swipeDirectionProp ?? oppositeSwipeDirection[swipeDirection];\n  const dismissDirection = oppositeSwipeDirection[resolvedSwipeDirection];\n  const enabled = !disabled && (!open || swipeActive);\n\n  const resetDragDelta = useStableCallback(() => {\n    dragDeltaRef.current.x = 0;\n    dragDeltaRef.current.y = 0;\n  });\n\n  function disableDismissForSwipe() {\n    releaseDismissTimeout.clear();\n    store.context.outsidePressEnabledRef.current = false;\n  }\n\n  function enableDismissAfterRelease() {\n    // Safari can dispatch outside-press for the same swipe-open gesture\n    // after release, so defer re-enabling dismissal to the next macrotask.\n    releaseDismissTimeout.start(0, () => {\n      store.context.outsidePressEnabledRef.current = true;\n    });\n  }\n\n  function resolvePopupSize() {\n    const popupElement = store.context.popupRef.current;\n    if (!popupElement) {\n      return null;\n    }\n\n    const isHorizontal = dismissDirection === 'left' || dismissDirection === 'right';\n    const size = isHorizontal ? popupElement.offsetWidth : popupElement.offsetHeight;\n    if (size <= 0) {\n      return null;\n    }\n\n    return size;\n  }\n\n  function resolveClosedOffset() {\n    const offset = resolvePopupSize();\n    if (offset == null) {\n      return null;\n    }\n\n    const popupElement = store.context.popupRef.current;\n    if (!popupElement) {\n      return offset;\n    }\n\n    const isHorizontal = dismissDirection === 'left' || dismissDirection === 'right';\n    const transform = getElementTransform(popupElement);\n    const transformOffset = isHorizontal ? transform.x : transform.y;\n    if (Number.isFinite(transformOffset) && Math.abs(transformOffset) > 0.5) {\n      return Math.min(offset, Math.abs(transformOffset));\n    }\n\n    return offset;\n  }\n\n  function resolveSwipeOpenThreshold() {\n    const popupSize = resolvePopupSize();\n    if (popupSize == null) {\n      return FALLBACK_SWIPE_OPEN_THRESHOLD;\n    }\n\n    return popupSize * DEFAULT_SWIPE_OPEN_RATIO;\n  }\n\n  function applySwipeMovement() {\n    if (!swipeActive) {\n      return;\n    }\n\n    const popupElement = store.context.popupRef.current;\n    if (!popupElement) {\n      return;\n    }\n\n    if (!store.select('open') || !store.select('mounted')) {\n      return;\n    }\n\n    if (closedOffsetRef.current == null) {\n      closedOffsetRef.current = resolveClosedOffset();\n    }\n\n    const closedOffset = closedOffsetRef.current;\n    if (!closedOffset || !Number.isFinite(closedOffset) || closedOffset <= 0) {\n      return;\n    }\n\n    const { x, y } = dragDeltaRef.current;\n    const displacement = getDisplacement(resolvedSwipeDirection, x, y);\n    const clampedDisplacement = Math.max(0, displacement);\n    const dampedDisplacement =\n      clampedDisplacement > closedOffset\n        ? closedOffset + Math.sqrt(clampedDisplacement - closedOffset)\n        : clampedDisplacement;\n    const remaining = closedOffset - dampedDisplacement;\n    const directionSign = dismissDirection === 'left' || dismissDirection === 'up' ? -1 : 1;\n    const movement = remaining * directionSign;\n    const isHorizontal = dismissDirection === 'left' || dismissDirection === 'right';\n    const movementX = isHorizontal ? movement : 0;\n    const movementY = isHorizontal ? 0 : movement;\n    const openProgress = Math.max(0, Math.min(1, clampedDisplacement / closedOffset));\n    const backdropProgress = Math.max(0, Math.min(1, 1 - openProgress));\n\n    popupElement.style.setProperty(DrawerPopupCssVars.swipeMovementX, `${movementX}px`);\n    popupElement.style.setProperty(DrawerPopupCssVars.swipeMovementY, `${movementY}px`);\n    popupElement.setAttribute(DrawerPopupDataAttributes.swiping, '');\n    if (popupTransitionRef.current === null) {\n      popupTransitionRef.current = popupElement.style.transition;\n    }\n    popupElement.style.transition = 'none';\n\n    const backdropElement = store.context.backdropRef.current;\n    if (backdropElement) {\n      backdropElement.setAttribute(DrawerPopupDataAttributes.swiping, '');\n      backdropElement.style.setProperty(DrawerBackdropCssVars.swipeProgress, `${backdropProgress}`);\n      if (openProgress > 0 && frontmostHeight > 0) {\n        backdropElement.style.setProperty(DrawerPopupCssVars.height, `${frontmostHeight}px`);\n      } else {\n        backdropElement.style.removeProperty(DrawerPopupCssVars.height);\n      }\n    }\n\n    providerContext?.visualStateStore.set({\n      swipeProgress: openProgress,\n      frontmostHeight: openProgress > 0 ? frontmostHeight : 0,\n    });\n    appliedSwipeStylesRef.current = true;\n  }\n\n  const clearSwipeStyles = useStableCallback(() => {\n    const popupElement = store.context.popupRef.current;\n    if (popupElement && appliedSwipeStylesRef.current) {\n      popupElement.style.removeProperty(DrawerPopupCssVars.swipeMovementX);\n      popupElement.style.removeProperty(DrawerPopupCssVars.swipeMovementY);\n      popupElement.removeAttribute(DrawerPopupDataAttributes.swiping);\n    }\n\n    if (popupElement && popupTransitionRef.current !== null) {\n      popupElement.style.transition = popupTransitionRef.current;\n      popupTransitionRef.current = null;\n    }\n\n    const backdropElement = store.context.backdropRef.current;\n    if (backdropElement) {\n      backdropElement.removeAttribute(DrawerPopupDataAttributes.swiping);\n      backdropElement.style.setProperty(DrawerBackdropCssVars.swipeProgress, '0');\n      backdropElement.style.removeProperty(DrawerPopupCssVars.height);\n    }\n\n    providerContext?.visualStateStore.set({ swipeProgress: 0, frontmostHeight: 0 });\n    appliedSwipeStylesRef.current = false;\n  });\n\n  function openDrawer(event?: PointerEvent | TouchEvent) {\n    if (store.select('open')) {\n      return;\n    }\n    openedBySwipeRef.current = true;\n    store.setOpen(\n      true,\n      createChangeEventDetails(REASONS.swipe, event, swipeAreaRef.current ?? undefined),\n    );\n  }\n\n  function closeDrawer(event?: PointerEvent | TouchEvent) {\n    if (!store.select('open')) {\n      return;\n    }\n    store.setOpen(\n      false,\n      createChangeEventDetails(REASONS.swipe, event, swipeAreaRef.current ?? undefined),\n    );\n  }\n\n  const swipe = useSwipeDismiss({\n    enabled,\n    directions: [resolvedSwipeDirection],\n    elementRef: swipeAreaRef,\n    trackDrag: false,\n    movementCssVars: {\n      x: DrawerPopupCssVars.swipeMovementX,\n      y: DrawerPopupCssVars.swipeMovementY,\n    },\n    onSwipeStart(event) {\n      disableDismissForSwipe();\n      swipeStartEventRef.current = event;\n      openedBySwipeRef.current = false;\n      setSwipeActive(true);\n      resetDragDelta();\n    },\n    onProgress(_progress, details) {\n      if (!details) {\n        return;\n      }\n\n      if (!swipeStartEventRef.current) {\n        return;\n      }\n\n      dragDeltaRef.current.x = details.deltaX;\n      dragDeltaRef.current.y = details.deltaY;\n\n      if (details.direction !== resolvedSwipeDirection) {\n        return;\n      }\n\n      const displacement = getDisplacement(resolvedSwipeDirection, details.deltaX, details.deltaY);\n\n      if (displacement < MIN_SWIPE_START_DISTANCE && !openedBySwipeRef.current) {\n        return;\n      }\n\n      if (!openedBySwipeRef.current) {\n        openDrawer(swipeStartEventRef.current);\n      }\n\n      applySwipeMovement();\n    },\n    onRelease({ event, direction, deltaX, deltaY, releaseVelocityX, releaseVelocityY }) {\n      const displacement = getDisplacement(resolvedSwipeDirection, deltaX, deltaY);\n      const releaseVelocity = getDisplacement(\n        resolvedSwipeDirection,\n        releaseVelocityX,\n        releaseVelocityY,\n      );\n      const threshold = resolveSwipeOpenThreshold();\n      const hasEnoughDistance = threshold != null && displacement >= threshold;\n      const hasEnoughVelocity = releaseVelocity >= VELOCITY_THRESHOLD;\n      const shouldOpen =\n        threshold != null &&\n        direction === resolvedSwipeDirection &&\n        (hasEnoughDistance || hasEnoughVelocity) &&\n        !disabled;\n\n      if (shouldOpen) {\n        if (!store.select('open')) {\n          openDrawer(event);\n        }\n      } else if (openedBySwipeRef.current) {\n        closeDrawer(event);\n      }\n\n      swipeStartEventRef.current = null;\n      openedBySwipeRef.current = false;\n      setSwipeActive(false);\n      closedOffsetRef.current = null;\n\n      enableDismissAfterRelease();\n      resetDragDelta();\n      clearSwipeStyles();\n\n      return false;\n    },\n  });\n\n  const swipePointerProps = swipe.getPointerProps();\n  const swipeTouchProps = swipe.getTouchProps();\n  const resetSwipe = swipe.reset;\n\n  React.useEffect(() => {\n    if (!enabled) {\n      resetSwipe();\n      resetDragDelta();\n      clearSwipeStyles();\n      setSwipeActive(false);\n      openedBySwipeRef.current = false;\n      swipeStartEventRef.current = null;\n      closedOffsetRef.current = null;\n    }\n  }, [clearSwipeStyles, enabled, resetDragDelta, resetSwipe]);\n\n  React.useEffect(() => {\n    return () => {\n      store.context.outsidePressEnabledRef.current = true;\n    };\n  }, [store]);\n\n  const state: DrawerSwipeAreaState = {\n    open,\n    swiping: swipe.swiping,\n    swipeDirection: resolvedSwipeDirection,\n    disabled,\n  };\n\n  return useRenderElement('div', componentProps, {\n    state,\n    ref: [forwardedRef, swipeAreaRef, registerTrigger],\n    stateAttributesMapping,\n    props: [\n      {\n        role: 'presentation',\n        'aria-hidden': true,\n        style: {\n          pointerEvents: !enabled ? 'none' : undefined,\n          touchAction: resolveTouchAction(resolvedSwipeDirection),\n        },\n        onPointerDown(event: React.PointerEvent<HTMLDivElement>) {\n          if (event.pointerType === 'touch') {\n            return;\n          }\n          swipePointerProps.onPointerDown?.(event);\n          // Prevent native text selection/drag gestures from competing with swipe-open dragging.\n          if (event.cancelable) {\n            event.preventDefault();\n          }\n        },\n        onPointerMove(event: React.PointerEvent<HTMLDivElement>) {\n          if (event.pointerType === 'touch') {\n            return;\n          }\n          swipePointerProps.onPointerMove?.(event);\n        },\n        onPointerUp(event: React.PointerEvent<HTMLDivElement>) {\n          if (event.pointerType === 'touch') {\n            return;\n          }\n          swipePointerProps.onPointerUp?.(event);\n        },\n        onPointerCancel(event: React.PointerEvent<HTMLDivElement>) {\n          if (event.pointerType === 'touch') {\n            return;\n          }\n          swipePointerProps.onPointerCancel?.(event);\n        },\n      },\n      swipeTouchProps,\n      swipeAreaId ? { id: swipeAreaId } : undefined,\n      elementProps,\n    ],\n  });\n});\n\nexport interface DrawerSwipeAreaProps extends BaseUIComponentProps<'div', DrawerSwipeAreaState> {\n  /**\n   * Whether the swipe area is disabled.\n   * @default false\n   */\n  disabled?: boolean | undefined;\n  /**\n   * The swipe direction that opens the drawer.\n   * Defaults to the opposite of `Drawer.Root` `swipeDirection`.\n   */\n  swipeDirection?: DrawerSwipeDirection | undefined;\n}\n\nexport interface DrawerSwipeAreaState {\n  /**\n   * Whether the drawer is currently open.\n   */\n  open: boolean;\n  /**\n   * Whether the swipe area is currently being swiped.\n   */\n  swiping: boolean;\n  /**\n   * The swipe direction that opens the drawer.\n   */\n  swipeDirection: SwipeDirection;\n  /**\n   * Whether the swipe area is disabled.\n   */\n  disabled: boolean;\n}\n\nexport namespace DrawerSwipeArea {\n  export type Props = DrawerSwipeAreaProps;\n  export type State = DrawerSwipeAreaState;\n}\n"
  },
  {
    "path": "packages/react/src/drawer/swipe-area/DrawerSwipeAreaDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum DrawerSwipeAreaDataAttributes {\n  /**\n   * Present when the drawer is open.\n   */\n  open = CommonPopupDataAttributes.open,\n  /**\n   * Present when the drawer is closed.\n   */\n  closed = CommonPopupDataAttributes.closed,\n  /**\n   * Present when the swipe area is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Indicates the swipe direction.\n   * @type {'up' | 'down' | 'left' | 'right'}\n   */\n  swipeDirection = 'data-swipe-direction',\n  /**\n   * Present when the drawer is being swiped.\n   */\n  swiping = 'data-swiping',\n}\n"
  },
  {
    "path": "packages/react/src/drawer/title/DrawerTitle.tsx",
    "content": "'use client';\nimport type * as React from 'react';\nimport { DialogTitle } from '../../dialog/title/DialogTitle';\nimport type { BaseUIComponentProps } from '../../utils/types';\n\n/**\n * A heading that labels the drawer.\n * Renders an `<h2>` element.\n *\n * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)\n */\nexport const DrawerTitle = DialogTitle as DrawerTitle;\n\nexport interface DrawerTitleProps extends BaseUIComponentProps<'h2', DrawerTitleState> {}\n\nexport interface DrawerTitleState {}\n\nexport interface DrawerTitle {\n  (componentProps: DrawerTitleProps): React.JSX.Element;\n}\n\nexport namespace DrawerTitle {\n  export type Props = DrawerTitleProps;\n  export type State = DrawerTitleState;\n}\n"
  },
  {
    "path": "packages/react/src/drawer/trigger/DrawerTrigger.tsx",
    "content": "'use client';\nimport type * as React from 'react';\nimport { DialogTrigger } from '../../dialog/trigger/DialogTrigger';\nimport type { DialogHandle as DrawerHandle } from '../../dialog/store/DialogHandle';\nimport type { BaseUIComponentProps, NativeButtonProps } from '../../utils/types';\n\n/**\n * A button that opens the drawer.\n * Renders a `<button>` element.\n *\n * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)\n */\nexport const DrawerTrigger = DialogTrigger as DrawerTrigger;\n\nexport interface DrawerTrigger {\n  <Payload>(\n    componentProps: DrawerTriggerProps<Payload> & React.RefAttributes<HTMLElement>,\n  ): React.JSX.Element;\n}\n\nexport interface DrawerTriggerProps<Payload = unknown>\n  extends NativeButtonProps, BaseUIComponentProps<'button', DrawerTriggerState> {\n  /**\n   * A handle to associate the trigger with a drawer.\n   * Can be created with the Drawer.createHandle() method.\n   */\n  handle?: DrawerHandle<Payload> | undefined;\n  /**\n   * A payload to pass to the drawer when it is opened.\n   */\n  payload?: Payload | undefined;\n  /**\n   * ID of the trigger. In addition to being forwarded to the rendered element,\n   * it is also used to specify the active trigger for drawers in controlled mode (with the Drawer.Root `triggerId` prop).\n   */\n  id?: string | undefined;\n}\n\nexport interface DrawerTriggerState {\n  /**\n   * Whether the drawer is currently disabled.\n   */\n  disabled: boolean;\n  /**\n   * Whether the drawer is currently open.\n   */\n  open: boolean;\n}\n\nexport namespace DrawerTrigger {\n  export type Props<Payload = unknown> = DrawerTriggerProps<Payload>;\n  export type State = DrawerTriggerState;\n}\n"
  },
  {
    "path": "packages/react/src/drawer/viewport/DrawerViewport.test.tsx",
    "content": "import { beforeAll, describe, expect, it, vi } from 'vitest';\nimport * as ReactDOM from 'react-dom';\nimport { Combobox } from '@base-ui/react/combobox';\nimport { Drawer } from '@base-ui/react/drawer';\nimport { Slider } from '@base-ui/react/slider';\nimport { fireEvent, flushMicrotasks, screen, waitFor } from '@mui/internal-test-utils';\nimport { createRenderer, isJSDOM } from '#test-utils';\n\ndescribe('<Drawer.Viewport />', () => {\n  beforeAll(function beforeHook() {\n    // PointerEvent not fully implemented in jsdom, causing fireEvent.pointer* to ignore options.\n    // https://github.com/jsdom/jsdom/issues/2527\n    (window as any).PointerEvent = window.MouseEvent;\n  });\n\n  const { render } = createRenderer();\n\n  function createTouch(target: EventTarget, point: { clientX: number; clientY: number }) {\n    if (typeof Touch === 'function') {\n      return new Touch({\n        identifier: 1,\n        target,\n        ...point,\n      });\n    }\n\n    return point;\n  }\n\n  function createNativeTouchMove(target: EventTarget, point: { clientX: number; clientY: number }) {\n    const touchMove = new Event('touchmove', { bubbles: true, cancelable: true });\n    Object.defineProperty(touchMove, 'touches', {\n      value: [createTouch(target, point)],\n      configurable: true,\n    });\n    return touchMove;\n  }\n\n  it('clears text selection on swipe start', async () => {\n    await render(\n      <Drawer.Root open>\n        <Drawer.Portal>\n          <Drawer.Viewport data-testid=\"viewport\">\n            <Drawer.Popup data-testid=\"popup\">\n              <Drawer.Content>\n                <span data-testid=\"text\">Selectable</span>\n              </Drawer.Content>\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const text = screen.getByTestId('text');\n    expect(text.firstChild).toBeTruthy();\n\n    const selection = window.getSelection();\n    expect(selection).not.toBeNull();\n    if (!selection || !text.firstChild) {\n      return;\n    }\n\n    const range = document.createRange();\n    range.setStart(text.firstChild, 0);\n    range.setEnd(text.firstChild, 5);\n    selection.removeAllRanges();\n    selection.addRange(range);\n    expect(selection.isCollapsed).toBe(false);\n\n    const popup = screen.getByTestId('popup');\n    const viewport = screen.getByTestId('viewport');\n\n    const originalElementFromPoint = document.elementFromPoint;\n    document.elementFromPoint = () => popup;\n\n    try {\n      fireEvent.pointerDown(viewport, {\n        button: 0,\n        buttons: 1,\n        pointerId: 1,\n        clientX: 0,\n        clientY: 0,\n        pointerType: 'mouse',\n      });\n    } finally {\n      document.elementFromPoint = originalElementFromPoint;\n    }\n\n    expect(selection.rangeCount).toBe(0);\n  });\n\n  it('does not clear text selection on touch swipe start', async () => {\n    await render(\n      <Drawer.Root open>\n        <Drawer.Portal>\n          <Drawer.Viewport data-testid=\"viewport\">\n            <Drawer.Popup data-testid=\"popup\">\n              <Drawer.Content>\n                <span data-testid=\"text\">Selectable</span>\n              </Drawer.Content>\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const text = screen.getByTestId('text');\n    expect(text.firstChild).toBeTruthy();\n\n    const selection = window.getSelection();\n    expect(selection).not.toBeNull();\n    if (!selection || !text.firstChild) {\n      return;\n    }\n\n    const range = document.createRange();\n    range.setStart(text.firstChild, 0);\n    range.setEnd(text.firstChild, 5);\n    selection.removeAllRanges();\n    selection.addRange(range);\n    expect(selection.isCollapsed).toBe(false);\n\n    const popup = screen.getByTestId('popup');\n    const viewport = screen.getByTestId('viewport');\n\n    const originalElementFromPoint = document.elementFromPoint;\n    document.elementFromPoint = () => popup;\n\n    try {\n      fireEvent.touchStart(viewport, {\n        touches: [\n          createTouch(viewport, {\n            clientX: 0,\n            clientY: 0,\n          }),\n        ],\n      });\n    } finally {\n      document.elementFromPoint = originalElementFromPoint;\n    }\n\n    expect(selection.rangeCount).toBe(1);\n  });\n\n  it('starts touch swipes from interactive elements', async () => {\n    await render(\n      <Drawer.Root open>\n        <Drawer.Portal>\n          <Drawer.Backdrop data-testid=\"backdrop\" />\n          <Drawer.Viewport data-testid=\"viewport\">\n            <Drawer.Popup data-testid=\"popup\">\n              <button type=\"button\" data-testid=\"button\">\n                Action\n              </button>\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const button = screen.getByTestId('button');\n    const backdrop = screen.getByTestId('backdrop');\n\n    const originalElementFromPoint = document.elementFromPoint;\n    document.elementFromPoint = () => button;\n\n    try {\n      fireEvent.touchStart(button, {\n        touches: [\n          createTouch(button, {\n            clientX: 0,\n            clientY: 0,\n          }),\n        ],\n      });\n\n      await flushMicrotasks();\n\n      expect(backdrop).toHaveAttribute('data-swiping', '');\n    } finally {\n      document.elementFromPoint = originalElementFromPoint;\n    }\n  });\n\n  it('allows clicks on non-interactive elements without data-base-ui-swipe-ignore', async () => {\n    const handleClick = vi.fn();\n    const handleOpenChange = vi.fn();\n\n    await render(\n      <Drawer.Root open onOpenChange={handleOpenChange}>\n        <Drawer.Portal>\n          <Drawer.Backdrop data-testid=\"backdrop\" />\n          <Drawer.Viewport>\n            <Drawer.Popup>\n              <Drawer.Content>\n                <div data-testid=\"target\" onClick={handleClick}>\n                  Action\n                </div>\n              </Drawer.Content>\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const target = screen.getByTestId('target');\n    const backdrop = screen.getByTestId('backdrop');\n    const originalElementFromPoint = document.elementFromPoint;\n    document.elementFromPoint = () => target;\n\n    try {\n      fireEvent.touchStart(target, {\n        touches: [\n          createTouch(target, {\n            clientX: 0,\n            clientY: 0,\n          }),\n        ],\n      });\n      fireEvent.pointerDown(target, { pointerType: 'touch' });\n      fireEvent.touchEnd(target, {\n        changedTouches: [\n          createTouch(target, {\n            clientX: 0,\n            clientY: 0,\n          }),\n        ],\n      });\n      fireEvent.click(target, { detail: 1 });\n\n      await flushMicrotasks();\n    } finally {\n      document.elementFromPoint = originalElementFromPoint;\n    }\n\n    expect(handleClick).toHaveBeenCalledTimes(1);\n    expect(handleOpenChange).not.toHaveBeenCalled();\n    expect(backdrop).not.toHaveAttribute('data-swiping');\n  });\n\n  it('does not start touch swipes from elements with data-base-ui-swipe-ignore', async () => {\n    const handleOpenChange = vi.fn();\n\n    await render(\n      <Drawer.Root open onOpenChange={handleOpenChange}>\n        <Drawer.Portal>\n          <Drawer.Backdrop data-testid=\"backdrop\" />\n          <Drawer.Viewport data-testid=\"viewport\">\n            <Drawer.Popup>\n              <Drawer.Content>\n                <div data-testid=\"target\" data-base-ui-swipe-ignore>\n                  Action\n                </div>\n              </Drawer.Content>\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const target = screen.getByTestId('target');\n    const backdrop = screen.getByTestId('backdrop');\n\n    fireEvent.touchStart(target, {\n      touches: [\n        createTouch(target, {\n          clientX: 0,\n          clientY: 0,\n        }),\n      ],\n    });\n\n    fireEvent.touchMove(target, {\n      touches: [\n        createTouch(target, {\n          clientX: 0,\n          clientY: 40,\n        }),\n      ],\n    });\n\n    fireEvent.touchEnd(target, {\n      changedTouches: [\n        createTouch(target, {\n          clientX: 0,\n          clientY: 40,\n        }),\n      ],\n    });\n\n    await flushMicrotasks();\n\n    expect(backdrop).not.toHaveAttribute('data-swiping');\n    expect(handleOpenChange).not.toHaveBeenCalled();\n  });\n\n  it('does not prevent native touch scrolling in portaled descendants', async () => {\n    const portalContainer = document.createElement('div');\n    document.body.append(portalContainer);\n\n    function PortaledPopup() {\n      return ReactDOM.createPortal(\n        <div data-testid=\"portaled-popup\">Portaled popup</div>,\n        portalContainer,\n      );\n    }\n\n    await render(\n      <Drawer.Root open>\n        <Drawer.Portal>\n          <Drawer.Viewport>\n            <Drawer.Popup>\n              <Drawer.Content>Content</Drawer.Content>\n              <PortaledPopup />\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const portaledPopup = screen.getByTestId('portaled-popup');\n    const originalElementFromPoint = document.elementFromPoint;\n    document.elementFromPoint = () => portaledPopup;\n\n    try {\n      fireEvent.touchStart(portaledPopup, {\n        touches: [\n          createTouch(portaledPopup, {\n            clientX: 0,\n            clientY: 0,\n          }),\n        ],\n      });\n\n      const touchMove = createNativeTouchMove(portaledPopup, {\n        clientX: 0,\n        clientY: 40,\n      });\n      portaledPopup.dispatchEvent(touchMove);\n\n      expect(touchMove.defaultPrevented).toBe(false);\n    } finally {\n      document.elementFromPoint = originalElementFromPoint;\n      portalContainer.remove();\n    }\n  });\n\n  it.skipIf(isJSDOM)(\n    'allows touch gestures on a portaled combobox popup without starting drawer swipe',\n    async () => {\n      const handleOpenChange = vi.fn();\n      const { user } = await render(\n        <Drawer.Root open onOpenChange={handleOpenChange}>\n          <Drawer.Portal>\n            <Drawer.Backdrop data-testid=\"backdrop\" />\n            <Drawer.Viewport>\n              <Drawer.Popup>\n                <Drawer.Content>\n                  <Combobox.Root\n                    defaultOpen\n                    items={[\n                      'Apple',\n                      'Banana',\n                      'Cherry',\n                      'Date',\n                      'Elderberry',\n                      'Fig',\n                      'Grape',\n                      'Honeydew',\n                      'Kiwi',\n                      'Lime',\n                    ]}\n                  >\n                    <Combobox.Input />\n                    <Combobox.Portal>\n                      <Combobox.Positioner>\n                        <Combobox.Popup>\n                          <Combobox.List style={{ maxHeight: 40, overflow: 'auto' }}>\n                            {(item: string) => (\n                              <Combobox.Item key={item} value={item}>\n                                {item}\n                              </Combobox.Item>\n                            )}\n                          </Combobox.List>\n                        </Combobox.Popup>\n                      </Combobox.Positioner>\n                    </Combobox.Portal>\n                  </Combobox.Root>\n                </Drawer.Content>\n              </Drawer.Popup>\n            </Drawer.Viewport>\n          </Drawer.Portal>\n        </Drawer.Root>,\n      );\n\n      const listbox = await screen.findByRole('listbox');\n      const backdrop = screen.getByTestId('backdrop');\n      await waitFor(() => {\n        expect(listbox.scrollHeight).toBeGreaterThan(listbox.clientHeight);\n      });\n      expect(listbox.scrollHeight).toBeGreaterThan(listbox.clientHeight);\n\n      const originalElementFromPoint = document.elementFromPoint;\n      document.elementFromPoint = () => listbox;\n\n      try {\n        const rect = listbox.getBoundingClientRect();\n\n        await user.pointer([\n          {\n            target: listbox,\n            coords: {\n              clientX: rect.left + rect.width / 2,\n              clientY: rect.top + rect.height - 8,\n            },\n            keys: '[TouchA>]',\n          },\n          {\n            target: listbox,\n            coords: {\n              clientX: rect.left + rect.width / 2,\n              clientY: rect.top + rect.height / 2,\n            },\n            pointerName: 'TouchA',\n          },\n          {\n            target: listbox,\n            coords: {\n              clientX: rect.left + rect.width / 2,\n              clientY: rect.top + 8,\n            },\n            pointerName: 'TouchA',\n          },\n          { keys: '[/TouchA]' },\n        ]);\n\n        expect(backdrop).not.toHaveAttribute('data-swiping');\n        expect(handleOpenChange).not.toHaveBeenCalled();\n        expect(listbox).toBeVisible();\n      } finally {\n        document.elementFromPoint = originalElementFromPoint;\n      }\n    },\n  );\n\n  it('still allows touch swipes from elements with legacy data-swipe-ignore', async () => {\n    const handleOpenChange = vi.fn();\n\n    await render(\n      <Drawer.Root open onOpenChange={handleOpenChange} swipeDirection=\"down\">\n        <Drawer.Portal>\n          <Drawer.Backdrop data-testid=\"backdrop\" />\n          <Drawer.Viewport data-testid=\"viewport\">\n            <Drawer.Popup>\n              <div data-testid=\"target\" data-swipe-ignore>\n                Action\n              </div>\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const target = screen.getByTestId('target');\n    const backdrop = screen.getByTestId('backdrop');\n    const originalElementFromPoint = document.elementFromPoint;\n    document.elementFromPoint = () => target;\n\n    try {\n      fireEvent.touchStart(target, {\n        touches: [\n          createTouch(target, {\n            clientX: 0,\n            clientY: 0,\n          }),\n        ],\n      });\n\n      fireEvent.touchMove(target, {\n        touches: [\n          createTouch(target, {\n            clientX: 0,\n            clientY: 40,\n          }),\n        ],\n      });\n\n      await flushMicrotasks();\n\n      expect(backdrop).toHaveAttribute('data-swiping', '');\n\n      fireEvent.touchEnd(target, {\n        changedTouches: [\n          createTouch(target, {\n            clientX: 0,\n            clientY: 80,\n          }),\n        ],\n      });\n\n      await flushMicrotasks();\n    } finally {\n      document.elementFromPoint = originalElementFromPoint;\n    }\n    expect(handleOpenChange).not.toHaveBeenCalled();\n  });\n\n  it('does not start non-touch swipes from Drawer.Content', async () => {\n    await render(\n      <Drawer.Root open>\n        <Drawer.Portal>\n          <Drawer.Backdrop data-testid=\"backdrop\" />\n          <Drawer.Viewport>\n            <Drawer.Popup>\n              <Drawer.Content>\n                <div data-testid=\"target\">Action</div>\n              </Drawer.Content>\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const target = screen.getByTestId('target');\n    const backdrop = screen.getByTestId('backdrop');\n\n    const originalElementFromPoint = document.elementFromPoint;\n    document.elementFromPoint = () => target;\n\n    try {\n      fireEvent.pointerDown(target, {\n        button: 0,\n        buttons: 1,\n        pointerId: 1,\n        clientX: 0,\n        clientY: 0,\n        pointerType: 'mouse',\n      });\n\n      await flushMicrotasks();\n\n      expect(backdrop).not.toHaveAttribute('data-swiping');\n    } finally {\n      document.elementFromPoint = originalElementFromPoint;\n    }\n  });\n\n  it('does not jump when touch starts outside the popup and then enters it', async () => {\n    await render(\n      <Drawer.Root open swipeDirection=\"down\">\n        <Drawer.Portal>\n          <Drawer.Backdrop data-testid=\"backdrop\" />\n          <Drawer.Viewport data-testid=\"viewport\">\n            <Drawer.Popup data-testid=\"popup\">\n              <Drawer.Content>Content</Drawer.Content>\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const viewport = screen.getByTestId('viewport');\n    const popup = screen.getByTestId('popup');\n    const backdrop = screen.getByTestId('backdrop');\n    Object.defineProperty(popup, 'offsetHeight', { value: 200, configurable: true });\n\n    const originalElementFromPoint = document.elementFromPoint;\n    document.elementFromPoint = (_x, y) => (y < 100 ? viewport : popup);\n\n    try {\n      fireEvent.touchStart(viewport, {\n        touches: [\n          createTouch(viewport, {\n            clientX: 0,\n            clientY: 0,\n          }),\n        ],\n      });\n\n      fireEvent.touchMove(viewport, {\n        touches: [\n          createTouch(viewport, {\n            clientX: 0,\n            clientY: 120,\n          }),\n        ],\n      });\n\n      await flushMicrotasks();\n\n      expect(backdrop).toHaveAttribute('data-swiping', '');\n      expect(Number.parseFloat(popup.style.getPropertyValue('--drawer-swipe-movement-y'))).toBe(0);\n    } finally {\n      document.elementFromPoint = originalElementFromPoint;\n    }\n  });\n\n  it('dismisses when touch starts outside the popup, then continues swiping down inside it', async () => {\n    const handleOpenChange = vi.fn();\n\n    await render(\n      <Drawer.Root open onOpenChange={handleOpenChange} swipeDirection=\"down\">\n        <Drawer.Portal>\n          <Drawer.Backdrop data-testid=\"backdrop\" />\n          <Drawer.Viewport data-testid=\"viewport\">\n            <Drawer.Popup data-testid=\"popup\">\n              <Drawer.Content>Content</Drawer.Content>\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const viewport = screen.getByTestId('viewport');\n    const popup = screen.getByTestId('popup');\n    Object.defineProperty(popup, 'offsetHeight', { value: 200, configurable: true });\n\n    const originalElementFromPoint = document.elementFromPoint;\n    document.elementFromPoint = (_x, y) => (y < 100 ? viewport : popup);\n\n    try {\n      fireEvent.touchStart(viewport, {\n        touches: [\n          createTouch(viewport, {\n            clientX: 0,\n            clientY: 0,\n          }),\n        ],\n      });\n\n      fireEvent.touchMove(viewport, {\n        touches: [\n          createTouch(viewport, {\n            clientX: 0,\n            clientY: 120,\n          }),\n        ],\n      });\n\n      fireEvent.touchMove(viewport, {\n        touches: [\n          createTouch(viewport, {\n            clientX: 0,\n            clientY: 170,\n          }),\n        ],\n      });\n\n      fireEvent.touchEnd(viewport, {\n        changedTouches: [\n          createTouch(viewport, {\n            clientX: 0,\n            clientY: 170,\n          }),\n        ],\n      });\n\n      await flushMicrotasks();\n    } finally {\n      document.elementFromPoint = originalElementFromPoint;\n    }\n\n    expect(handleOpenChange).toHaveBeenCalledWith(\n      false,\n      expect.objectContaining({ reason: 'swipe' }),\n    );\n  });\n\n  it('treats pen interactions on Drawer.Content as non-touch swipes', async () => {\n    await render(\n      <Drawer.Root open swipeDirection=\"down\">\n        <Drawer.Portal>\n          <Drawer.Backdrop data-testid=\"backdrop\" />\n          <Drawer.Viewport data-testid=\"viewport\">\n            <Drawer.Popup>\n              <Drawer.Content>\n                <button type=\"button\" data-testid=\"button\">\n                  Action\n                </button>\n              </Drawer.Content>\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const button = screen.getByTestId('button');\n    const backdrop = screen.getByTestId('backdrop');\n\n    const originalElementFromPoint = document.elementFromPoint;\n    document.elementFromPoint = () => button;\n\n    try {\n      const pointerDownEvent = new Event('pointerdown', {\n        bubbles: true,\n        cancelable: true,\n      }) as PointerEvent;\n\n      Object.defineProperties(pointerDownEvent, {\n        button: { value: 0 },\n        buttons: { value: 1 },\n        pointerId: { value: 1 },\n        pointerType: { value: 'pen' },\n        clientX: { value: 0 },\n        clientY: { value: 0 },\n      });\n\n      fireEvent(button, pointerDownEvent);\n\n      fireEvent.touchStart(button, {\n        touches: [\n          createTouch(button, {\n            clientX: 0,\n            clientY: 0,\n          }),\n        ],\n      });\n\n      await flushMicrotasks();\n\n      expect(backdrop).not.toHaveAttribute('data-swiping');\n\n      const prevented = fireEvent.touchMove(button, {\n        touches: [\n          createTouch(button, {\n            clientX: 0,\n            clientY: 10,\n          }),\n        ],\n      });\n\n      expect(prevented).toBe(true);\n    } finally {\n      document.elementFromPoint = originalElementFromPoint;\n    }\n  });\n\n  it('does not mark nested drawers as swiping until movement passes the threshold', async () => {\n    await render(\n      <Drawer.Root open swipeDirection=\"down\">\n        <Drawer.Portal>\n          <Drawer.Viewport data-testid=\"parent-viewport\">\n            <Drawer.Popup data-testid=\"parent-popup\">\n              <Drawer.Root open swipeDirection=\"down\">\n                <Drawer.Portal>\n                  <Drawer.Viewport data-testid=\"child-viewport\">\n                    <Drawer.Popup data-testid=\"child-popup\">\n                      <button type=\"button\" data-testid=\"child-button\">\n                        Action\n                      </button>\n                    </Drawer.Popup>\n                  </Drawer.Viewport>\n                </Drawer.Portal>\n              </Drawer.Root>\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const parentPopup = screen.getByTestId('parent-popup');\n    const childPopup = screen.getByTestId('child-popup');\n    const parentViewport = screen.getByTestId('parent-viewport');\n    const childViewport = screen.getByTestId('child-viewport');\n    const button = screen.getByTestId('child-button');\n    Object.defineProperty(childPopup, 'offsetHeight', { value: 200, configurable: true });\n\n    const originalElementFromPoint = document.elementFromPoint;\n    document.elementFromPoint = () => childPopup;\n\n    try {\n      fireEvent.touchStart(button, {\n        touches: [\n          createTouch(button, {\n            clientX: 0,\n            clientY: 0,\n          }),\n        ],\n      });\n\n      await flushMicrotasks();\n\n      expect(parentViewport).not.toHaveAttribute('data-nested-dialog-open');\n      expect(childViewport).not.toHaveAttribute('data-nested-dialog-open');\n      expect(parentPopup).not.toHaveAttribute('data-nested-drawer-swiping');\n\n      fireEvent.touchMove(button, {\n        touches: [\n          createTouch(button, {\n            clientX: 0,\n            clientY: 5,\n          }),\n        ],\n      });\n\n      fireEvent.touchMove(button, {\n        touches: [\n          createTouch(button, {\n            clientX: 0,\n            clientY: 20,\n          }),\n        ],\n      });\n\n      await flushMicrotasks();\n\n      expect(parentPopup).toHaveAttribute('data-nested-drawer-swiping', '');\n    } finally {\n      document.elementFromPoint = originalElementFromPoint;\n    }\n  });\n\n  it('prevents touchmove at scroll top when swiping down on scrollable content', async () => {\n    await render(\n      <Drawer.Root open swipeDirection=\"down\">\n        <Drawer.Portal>\n          <Drawer.Viewport>\n            <Drawer.Popup>\n              <div data-testid=\"scroll\" style={{ overflowY: 'auto', maxHeight: 40 }}>\n                <div style={{ height: 120 }}>Scrollable content</div>\n              </div>\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const scroll = screen.getByTestId('scroll');\n    Object.defineProperty(scroll, 'scrollHeight', { value: 120, configurable: true });\n    Object.defineProperty(scroll, 'clientHeight', { value: 40, configurable: true });\n    scroll.scrollTop = 0;\n\n    fireEvent.touchStart(scroll, {\n      touches: [\n        createTouch(scroll, {\n          clientX: 0,\n          clientY: 0,\n        }),\n      ],\n    });\n\n    const prevented = fireEvent.touchMove(scroll, {\n      touches: [\n        createTouch(scroll, {\n          clientX: 0,\n          clientY: 10,\n        }),\n      ],\n    });\n\n    expect(prevented).toBe(false);\n  });\n\n  it('prevents touchmove at scroll bottom when swiping up on scrollable content', async () => {\n    await render(\n      <Drawer.Root open swipeDirection=\"up\">\n        <Drawer.Portal>\n          <Drawer.Viewport>\n            <Drawer.Popup>\n              <div data-testid=\"scroll\" style={{ overflowY: 'auto', maxHeight: 40 }}>\n                <div style={{ height: 120 }}>Scrollable content</div>\n              </div>\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const scroll = screen.getByTestId('scroll');\n    Object.defineProperty(scroll, 'scrollHeight', { value: 120, configurable: true });\n    Object.defineProperty(scroll, 'clientHeight', { value: 40, configurable: true });\n    scroll.scrollTop = 80;\n\n    fireEvent.touchStart(scroll, {\n      touches: [\n        createTouch(scroll, {\n          clientX: 0,\n          clientY: 20,\n        }),\n      ],\n    });\n\n    const prevented = fireEvent.touchMove(scroll, {\n      touches: [\n        createTouch(scroll, {\n          clientX: 0,\n          clientY: 10,\n        }),\n      ],\n    });\n\n    expect(prevented).toBe(false);\n  });\n\n  it('prevents touchmove when a scrollable ancestor wraps the popup at the top', async () => {\n    await render(\n      <Drawer.Root open swipeDirection=\"down\">\n        <Drawer.Portal>\n          <Drawer.Viewport>\n            <div data-testid=\"scroll\" style={{ overflowY: 'auto', maxHeight: 40 }}>\n              <Drawer.Popup>\n                <Drawer.Content>\n                  <span data-testid=\"item\">Scrollable content</span>\n                </Drawer.Content>\n              </Drawer.Popup>\n            </div>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const scroll = screen.getByTestId('scroll');\n    Object.defineProperty(scroll, 'scrollHeight', { value: 120, configurable: true });\n    Object.defineProperty(scroll, 'clientHeight', { value: 40, configurable: true });\n    scroll.scrollTop = 0;\n\n    const item = screen.getByTestId('item');\n\n    fireEvent.touchStart(item, {\n      touches: [\n        createTouch(item, {\n          clientX: 0,\n          clientY: 0,\n        }),\n      ],\n    });\n\n    const prevented = fireEvent.touchMove(item, {\n      touches: [\n        createTouch(item, {\n          clientX: 0,\n          clientY: 10,\n        }),\n      ],\n    });\n\n    expect(prevented).toBe(false);\n  });\n\n  it('prevents touchmove when there is no scroll container', async () => {\n    await render(\n      <Drawer.Root open swipeDirection=\"down\">\n        <Drawer.Portal>\n          <Drawer.Viewport>\n            <Drawer.Popup data-testid=\"popup\">\n              <Drawer.Content>Content</Drawer.Content>\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const popup = screen.getByTestId('popup');\n\n    fireEvent.touchStart(popup, {\n      touches: [\n        createTouch(popup, {\n          clientX: 0,\n          clientY: 0,\n        }),\n      ],\n    });\n\n    const prevented = fireEvent.touchMove(popup, {\n      touches: [\n        createTouch(popup, {\n          clientX: 0,\n          clientY: 10,\n        }),\n      ],\n    });\n\n    expect(prevented).toBe(false);\n  });\n\n  it('does not block touchmove on native range inputs', async () => {\n    await render(\n      <Drawer.Root open swipeDirection=\"down\">\n        <Drawer.Portal>\n          <Drawer.Backdrop data-testid=\"backdrop\" />\n          <Drawer.Viewport>\n            <Drawer.Popup>\n              <input type=\"range\" data-testid=\"range\" />\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const range = screen.getByTestId('range');\n    const backdrop = screen.getByTestId('backdrop');\n\n    fireEvent.touchStart(range, {\n      touches: [\n        createTouch(range, {\n          clientX: 0,\n          clientY: 0,\n        }),\n      ],\n    });\n\n    const dispatched = fireEvent.touchMove(range, {\n      touches: [\n        createTouch(range, {\n          clientX: 20,\n          clientY: 0,\n        }),\n      ],\n    });\n\n    await waitFor(() => {\n      expect(dispatched).toBe(true);\n      expect(backdrop).not.toHaveAttribute('data-swiping');\n    });\n  });\n\n  it('does not block touchmove on slider thumb range inputs', async () => {\n    await render(\n      <Drawer.Root open swipeDirection=\"down\">\n        <Drawer.Portal>\n          <Drawer.Backdrop data-testid=\"backdrop\" />\n          <Drawer.Viewport>\n            <Drawer.Popup>\n              <Slider.Root defaultValue={50}>\n                <Slider.Control>\n                  <Slider.Track>\n                    <Slider.Indicator />\n                    <Slider.Thumb />\n                  </Slider.Track>\n                </Slider.Control>\n              </Slider.Root>\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const sliderInput = screen.getByRole('slider');\n    const backdrop = screen.getByTestId('backdrop');\n\n    fireEvent.touchStart(sliderInput, {\n      touches: [\n        createTouch(sliderInput, {\n          clientX: 0,\n          clientY: 0,\n        }),\n      ],\n    });\n\n    const dispatched = fireEvent.touchMove(sliderInput, {\n      touches: [\n        createTouch(sliderInput, {\n          clientX: 20,\n          clientY: 0,\n        }),\n      ],\n    });\n\n    await flushMicrotasks();\n\n    expect(dispatched).toBe(true);\n    expect(backdrop).not.toHaveAttribute('data-swiping');\n  });\n\n  it('does not start swiping when adjusting input selection handles', async () => {\n    await render(\n      <Drawer.Root open swipeDirection=\"down\">\n        <Drawer.Portal>\n          <Drawer.Backdrop data-testid=\"backdrop\" />\n          <Drawer.Viewport>\n            <Drawer.Popup data-testid=\"popup\">\n              <input data-testid=\"input\" defaultValue=\"Selectable text\" />\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const input = screen.getByTestId('input') as HTMLInputElement;\n    const popup = screen.getByTestId('popup');\n    const backdrop = screen.getByTestId('backdrop');\n\n    input.focus();\n    input.setSelectionRange(0, 5);\n\n    const originalElementFromPoint = document.elementFromPoint;\n    document.elementFromPoint = () => popup;\n\n    try {\n      fireEvent.touchStart(popup, {\n        touches: [\n          createTouch(popup, {\n            clientX: 0,\n            clientY: 0,\n          }),\n        ],\n      });\n\n      expect(backdrop).not.toHaveAttribute('data-swiping');\n\n      const dispatched = fireEvent.touchMove(popup, {\n        touches: [\n          createTouch(popup, {\n            clientX: 0,\n            clientY: 10,\n          }),\n        ],\n      });\n\n      await waitFor(() => {\n        expect(dispatched).toBe(true);\n        expect(backdrop).not.toHaveAttribute('data-swiping');\n      });\n    } finally {\n      document.elementFromPoint = originalElementFromPoint;\n    }\n  });\n\n  it('does not start swiping when adjusting textarea selection handles', async () => {\n    await render(\n      <Drawer.Root open swipeDirection=\"down\">\n        <Drawer.Portal>\n          <Drawer.Backdrop data-testid=\"backdrop\" />\n          <Drawer.Viewport>\n            <Drawer.Popup data-testid=\"popup\">\n              <textarea data-testid=\"textarea\" defaultValue=\"Selectable text\" />\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const textarea = screen.getByTestId('textarea') as HTMLTextAreaElement;\n    const popup = screen.getByTestId('popup');\n    const backdrop = screen.getByTestId('backdrop');\n\n    textarea.focus();\n    textarea.setSelectionRange(0, 5);\n\n    const originalElementFromPoint = document.elementFromPoint;\n    document.elementFromPoint = () => popup;\n\n    try {\n      fireEvent.touchStart(popup, {\n        touches: [\n          createTouch(popup, {\n            clientX: 0,\n            clientY: 0,\n          }),\n        ],\n      });\n\n      expect(backdrop).not.toHaveAttribute('data-swiping');\n\n      const dispatched = fireEvent.touchMove(popup, {\n        touches: [\n          createTouch(popup, {\n            clientX: 0,\n            clientY: 10,\n          }),\n        ],\n      });\n\n      await waitFor(() => {\n        expect(dispatched).toBe(true);\n        expect(backdrop).not.toHaveAttribute('data-swiping');\n      });\n    } finally {\n      document.elementFromPoint = originalElementFromPoint;\n    }\n  });\n\n  it('does not start swiping when adjusting contenteditable selection handles', async () => {\n    await render(\n      <Drawer.Root open swipeDirection=\"down\">\n        <Drawer.Portal>\n          <Drawer.Backdrop data-testid=\"backdrop\" />\n          <Drawer.Viewport>\n            <Drawer.Popup data-testid=\"popup\">\n              <div contentEditable suppressContentEditableWarning data-testid=\"editable\">\n                Selectable text\n              </div>\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const editable = screen.getByTestId('editable');\n    const popup = screen.getByTestId('popup');\n    const backdrop = screen.getByTestId('backdrop');\n    const selection = window.getSelection();\n    expect(selection).not.toBeNull();\n    expect(editable.firstChild).toBeTruthy();\n    if (!selection || !editable.firstChild) {\n      return;\n    }\n\n    editable.focus();\n    const range = document.createRange();\n    range.setStart(editable.firstChild, 0);\n    range.setEnd(editable.firstChild, 5);\n    selection.removeAllRanges();\n    selection.addRange(range);\n\n    const originalElementFromPoint = document.elementFromPoint;\n    document.elementFromPoint = () => popup;\n\n    try {\n      fireEvent.touchStart(popup, {\n        touches: [\n          createTouch(popup, {\n            clientX: 0,\n            clientY: 0,\n          }),\n        ],\n      });\n\n      expect(backdrop).not.toHaveAttribute('data-swiping');\n\n      const dispatched = fireEvent.touchMove(popup, {\n        touches: [\n          createTouch(popup, {\n            clientX: 0,\n            clientY: 10,\n          }),\n        ],\n      });\n\n      await waitFor(() => {\n        expect(dispatched).toBe(true);\n        expect(backdrop).not.toHaveAttribute('data-swiping');\n      });\n    } finally {\n      document.elementFromPoint = originalElementFromPoint;\n      selection.removeAllRanges();\n    }\n  });\n\n  it('does not start swiping when adjusting regular text selection handles', async () => {\n    await render(\n      <Drawer.Root open swipeDirection=\"down\">\n        <Drawer.Portal>\n          <Drawer.Backdrop data-testid=\"backdrop\" />\n          <Drawer.Viewport>\n            <Drawer.Popup data-testid=\"popup\">\n              <span data-testid=\"text\">Selectable text</span>\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const text = screen.getByTestId('text');\n    const popup = screen.getByTestId('popup');\n    const backdrop = screen.getByTestId('backdrop');\n    const selection = window.getSelection();\n    expect(selection).not.toBeNull();\n    expect(text.firstChild).toBeTruthy();\n    if (!selection || !text.firstChild) {\n      return;\n    }\n\n    const range = document.createRange();\n    range.setStart(text.firstChild, 0);\n    range.setEnd(text.firstChild, 5);\n    selection.removeAllRanges();\n    selection.addRange(range);\n\n    const originalElementFromPoint = document.elementFromPoint;\n    document.elementFromPoint = () => popup;\n\n    try {\n      fireEvent.touchStart(popup, {\n        touches: [\n          createTouch(popup, {\n            clientX: 0,\n            clientY: 0,\n          }),\n        ],\n      });\n\n      expect(backdrop).not.toHaveAttribute('data-swiping');\n\n      const dispatched = fireEvent.touchMove(popup, {\n        touches: [\n          createTouch(popup, {\n            clientX: 0,\n            clientY: 10,\n          }),\n        ],\n      });\n\n      await waitFor(() => {\n        expect(dispatched).toBe(true);\n        expect(backdrop).not.toHaveAttribute('data-swiping');\n      });\n    } finally {\n      document.elementFromPoint = originalElementFromPoint;\n      selection.removeAllRanges();\n    }\n  });\n\n  it('allows touchmove when scrolling down from scroll top', async () => {\n    await render(\n      <Drawer.Root open swipeDirection=\"down\">\n        <Drawer.Portal>\n          <Drawer.Viewport>\n            <Drawer.Popup>\n              <div data-testid=\"scroll\" style={{ overflowY: 'auto', maxHeight: 40 }}>\n                <div style={{ height: 120 }}>Scrollable content</div>\n              </div>\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const scroll = screen.getByTestId('scroll');\n    Object.defineProperty(scroll, 'scrollHeight', { value: 120, configurable: true });\n    Object.defineProperty(scroll, 'clientHeight', { value: 40, configurable: true });\n    scroll.scrollTop = 0;\n\n    fireEvent.touchStart(scroll, {\n      touches: [\n        createTouch(scroll, {\n          clientX: 0,\n          clientY: 0,\n        }),\n      ],\n    });\n\n    const prevented = fireEvent.touchMove(scroll, {\n      touches: [\n        createTouch(scroll, {\n          clientX: 0,\n          clientY: -10,\n        }),\n      ],\n    });\n\n    expect(prevented).toBe(true);\n  });\n\n  it('does not start an opposite-direction swipe from scroll bottom for down drawers with snap points', async () => {\n    await render(\n      <Drawer.Root open swipeDirection=\"down\" snapPoints={['100px', 1]}>\n        <Drawer.Portal>\n          <Drawer.Backdrop data-testid=\"backdrop\" />\n          <Drawer.Viewport>\n            <Drawer.Popup>\n              <div data-testid=\"scroll\" style={{ overflowY: 'auto', maxHeight: 40 }}>\n                <div style={{ height: 120 }}>Scrollable content</div>\n              </div>\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const scroll = screen.getByTestId('scroll');\n    const backdrop = screen.getByTestId('backdrop');\n    Object.defineProperty(scroll, 'scrollHeight', { value: 120, configurable: true });\n    Object.defineProperty(scroll, 'clientHeight', { value: 40, configurable: true });\n    scroll.scrollTop = 80;\n\n    fireEvent.touchStart(scroll, {\n      touches: [\n        createTouch(scroll, {\n          clientX: 0,\n          clientY: 40,\n        }),\n      ],\n    });\n\n    const moveAllowed = fireEvent.touchMove(scroll, {\n      touches: [\n        createTouch(scroll, {\n          clientX: 0,\n          clientY: 20,\n        }),\n      ],\n    });\n\n    await flushMicrotasks();\n\n    expect(moveAllowed).toBe(true);\n    expect(backdrop).not.toHaveAttribute('data-swiping');\n  });\n\n  it('does not start an opposite-direction swipe from scroll right edge for right drawers', async () => {\n    await render(\n      <Drawer.Root open swipeDirection=\"right\">\n        <Drawer.Portal>\n          <Drawer.Backdrop data-testid=\"backdrop\" />\n          <Drawer.Viewport>\n            <Drawer.Popup>\n              <div data-testid=\"scroll\" style={{ overflowX: 'auto', maxWidth: 40 }}>\n                <div style={{ width: 120, height: 40 }}>Scrollable content</div>\n              </div>\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const scroll = screen.getByTestId('scroll');\n    const backdrop = screen.getByTestId('backdrop');\n    Object.defineProperty(scroll, 'scrollWidth', { value: 120, configurable: true });\n    Object.defineProperty(scroll, 'clientWidth', { value: 40, configurable: true });\n    scroll.scrollLeft = 80;\n\n    fireEvent.touchStart(scroll, {\n      touches: [\n        createTouch(scroll, {\n          clientX: 40,\n          clientY: 0,\n        }),\n      ],\n    });\n\n    const moveAllowed = fireEvent.touchMove(scroll, {\n      touches: [\n        createTouch(scroll, {\n          clientX: 20,\n          clientY: 0,\n        }),\n      ],\n    });\n\n    await flushMicrotasks();\n\n    expect(moveAllowed).toBe(true);\n    expect(backdrop).not.toHaveAttribute('data-swiping');\n  });\n\n  it('starts swipe-to-dismiss after a scrollable container reaches the dismiss edge', async () => {\n    await render(\n      <Drawer.Root open swipeDirection=\"down\">\n        <Drawer.Portal>\n          <Drawer.Backdrop data-testid=\"backdrop\" />\n          <Drawer.Viewport>\n            <Drawer.Popup>\n              <div data-testid=\"scroll\" style={{ overflowY: 'auto', maxHeight: 40 }}>\n                <div style={{ height: 120 }}>Scrollable content</div>\n              </div>\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const scroll = screen.getByTestId('scroll');\n    const backdrop = screen.getByTestId('backdrop');\n    Object.defineProperty(scroll, 'scrollHeight', { value: 120, configurable: true });\n    Object.defineProperty(scroll, 'clientHeight', { value: 40, configurable: true });\n    scroll.scrollTop = 30;\n\n    const originalElementFromPoint = document.elementFromPoint;\n    document.elementFromPoint = () => scroll;\n\n    try {\n      fireEvent.touchStart(scroll, {\n        touches: [\n          createTouch(scroll, {\n            clientX: 0,\n            clientY: 30,\n          }),\n        ],\n      });\n\n      const firstMovePrevented = fireEvent.touchMove(scroll, {\n        touches: [\n          createTouch(scroll, {\n            clientX: 0,\n            clientY: 40,\n          }),\n        ],\n      });\n\n      expect(firstMovePrevented).toBe(true);\n      expect(backdrop).not.toHaveAttribute('data-swiping');\n\n      scroll.scrollTop = 0;\n\n      const secondMovePrevented = fireEvent.touchMove(scroll, {\n        touches: [\n          createTouch(scroll, {\n            clientX: 0,\n            clientY: 50,\n          }),\n        ],\n      });\n\n      expect(secondMovePrevented).toBe(false);\n\n      await flushMicrotasks();\n\n      expect(backdrop).toHaveAttribute('data-swiping', '');\n    } finally {\n      document.elementFromPoint = originalElementFromPoint;\n    }\n  });\n\n  it('dismisses from a top-edge scroll container with a touch swipe down', async () => {\n    const handleOpenChange = vi.fn();\n\n    await render(\n      <Drawer.Root open onOpenChange={handleOpenChange} swipeDirection=\"down\">\n        <Drawer.Portal>\n          <Drawer.Backdrop data-testid=\"backdrop\" />\n          <Drawer.Viewport>\n            <Drawer.Popup data-testid=\"popup\">\n              <div data-testid=\"scroll\" style={{ overflowY: 'auto', maxHeight: 40 }}>\n                <div style={{ height: 120 }}>Scrollable content</div>\n              </div>\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const scroll = screen.getByTestId('scroll');\n    const backdrop = screen.getByTestId('backdrop');\n    const popup = screen.getByTestId('popup');\n    Object.defineProperty(scroll, 'scrollHeight', { value: 120, configurable: true });\n    Object.defineProperty(scroll, 'clientHeight', { value: 40, configurable: true });\n    scroll.scrollTop = 0;\n\n    Object.defineProperty(popup, 'offsetHeight', { value: 200, configurable: true });\n\n    const originalElementFromPoint = document.elementFromPoint;\n    document.elementFromPoint = () => scroll;\n\n    try {\n      fireEvent.touchStart(scroll, {\n        touches: [\n          createTouch(scroll, {\n            clientX: 0,\n            clientY: 0,\n          }),\n        ],\n      });\n\n      fireEvent.touchMove(scroll, {\n        touches: [\n          createTouch(scroll, {\n            clientX: 0,\n            clientY: 140,\n          }),\n        ],\n      });\n\n      expect(backdrop).toHaveAttribute('data-swiping', '');\n\n      fireEvent.touchEnd(scroll, {\n        changedTouches: [\n          createTouch(scroll, {\n            clientX: 0,\n            clientY: 140,\n          }),\n        ],\n      });\n\n      await flushMicrotasks();\n    } finally {\n      document.elementFromPoint = originalElementFromPoint;\n    }\n\n    expect(handleOpenChange).toHaveBeenCalledWith(\n      false,\n      expect.objectContaining({ reason: 'swipe' }),\n    );\n  });\n\n  it('dismisses from a bottom-edge scroll container with a touch swipe up', async () => {\n    const handleOpenChange = vi.fn();\n\n    await render(\n      <Drawer.Root open onOpenChange={handleOpenChange} swipeDirection=\"up\">\n        <Drawer.Portal>\n          <Drawer.Backdrop data-testid=\"backdrop\" />\n          <Drawer.Viewport>\n            <Drawer.Popup data-testid=\"popup\">\n              <div data-testid=\"scroll\" style={{ overflowY: 'auto', maxHeight: 40 }}>\n                <div style={{ height: 120 }}>Scrollable content</div>\n              </div>\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const scroll = screen.getByTestId('scroll');\n    const backdrop = screen.getByTestId('backdrop');\n    const popup = screen.getByTestId('popup');\n    Object.defineProperty(scroll, 'scrollHeight', { value: 120, configurable: true });\n    Object.defineProperty(scroll, 'clientHeight', { value: 40, configurable: true });\n    scroll.scrollTop = 80;\n\n    Object.defineProperty(popup, 'offsetHeight', { value: 200, configurable: true });\n\n    const originalElementFromPoint = document.elementFromPoint;\n    document.elementFromPoint = () => scroll;\n\n    try {\n      fireEvent.touchStart(scroll, {\n        touches: [\n          createTouch(scroll, {\n            clientX: 0,\n            clientY: 140,\n          }),\n        ],\n      });\n\n      fireEvent.touchMove(scroll, {\n        touches: [\n          createTouch(scroll, {\n            clientX: 0,\n            clientY: 0,\n          }),\n        ],\n      });\n\n      expect(backdrop).toHaveAttribute('data-swiping', '');\n\n      fireEvent.touchEnd(scroll, {\n        changedTouches: [\n          createTouch(scroll, {\n            clientX: 0,\n            clientY: 0,\n          }),\n        ],\n      });\n\n      await flushMicrotasks();\n    } finally {\n      document.elementFromPoint = originalElementFromPoint;\n    }\n\n    expect(handleOpenChange).toHaveBeenCalledWith(\n      false,\n      expect.objectContaining({ reason: 'swipe' }),\n    );\n  });\n\n  it('dismisses from a left-edge horizontal scroll container with a touch swipe right', async () => {\n    const handleOpenChange = vi.fn();\n\n    await render(\n      <Drawer.Root open onOpenChange={handleOpenChange} swipeDirection=\"right\">\n        <Drawer.Portal>\n          <Drawer.Backdrop data-testid=\"backdrop\" />\n          <Drawer.Viewport>\n            <Drawer.Popup data-testid=\"popup\">\n              <div data-testid=\"scroll\" style={{ overflowX: 'auto', maxWidth: 40 }}>\n                <div style={{ width: 120, height: 40 }}>Scrollable content</div>\n              </div>\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const scroll = screen.getByTestId('scroll');\n    const backdrop = screen.getByTestId('backdrop');\n    const popup = screen.getByTestId('popup');\n    Object.defineProperty(scroll, 'scrollWidth', { value: 120, configurable: true });\n    Object.defineProperty(scroll, 'clientWidth', { value: 40, configurable: true });\n    scroll.scrollLeft = 0;\n\n    Object.defineProperty(popup, 'offsetWidth', { value: 200, configurable: true });\n\n    const originalElementFromPoint = document.elementFromPoint;\n    document.elementFromPoint = () => scroll;\n\n    try {\n      fireEvent.touchStart(scroll, {\n        touches: [\n          createTouch(scroll, {\n            clientX: 0,\n            clientY: 0,\n          }),\n        ],\n      });\n\n      fireEvent.touchMove(scroll, {\n        touches: [\n          createTouch(scroll, {\n            clientX: 140,\n            clientY: 0,\n          }),\n        ],\n      });\n\n      expect(backdrop).toHaveAttribute('data-swiping', '');\n\n      fireEvent.touchEnd(scroll, {\n        changedTouches: [\n          createTouch(scroll, {\n            clientX: 140,\n            clientY: 0,\n          }),\n        ],\n      });\n\n      await flushMicrotasks();\n    } finally {\n      document.elementFromPoint = originalElementFromPoint;\n    }\n\n    expect(handleOpenChange).toHaveBeenCalledWith(\n      false,\n      expect.objectContaining({ reason: 'swipe' }),\n    );\n  });\n\n  it('dismisses from a right-edge horizontal scroll container with a touch swipe left', async () => {\n    const handleOpenChange = vi.fn();\n\n    await render(\n      <Drawer.Root open onOpenChange={handleOpenChange} swipeDirection=\"left\">\n        <Drawer.Portal>\n          <Drawer.Backdrop data-testid=\"backdrop\" />\n          <Drawer.Viewport>\n            <Drawer.Popup data-testid=\"popup\">\n              <div data-testid=\"scroll\" style={{ overflowX: 'auto', maxWidth: 40 }}>\n                <div style={{ width: 120, height: 40 }}>Scrollable content</div>\n              </div>\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const scroll = screen.getByTestId('scroll');\n    const backdrop = screen.getByTestId('backdrop');\n    const popup = screen.getByTestId('popup');\n    Object.defineProperty(scroll, 'scrollWidth', { value: 120, configurable: true });\n    Object.defineProperty(scroll, 'clientWidth', { value: 40, configurable: true });\n    scroll.scrollLeft = 80;\n\n    Object.defineProperty(popup, 'offsetWidth', { value: 200, configurable: true });\n\n    const originalElementFromPoint = document.elementFromPoint;\n    document.elementFromPoint = () => scroll;\n\n    try {\n      fireEvent.touchStart(scroll, {\n        touches: [\n          createTouch(scroll, {\n            clientX: 140,\n            clientY: 0,\n          }),\n        ],\n      });\n\n      fireEvent.touchMove(scroll, {\n        touches: [\n          createTouch(scroll, {\n            clientX: 0,\n            clientY: 0,\n          }),\n        ],\n      });\n\n      expect(backdrop).toHaveAttribute('data-swiping', '');\n\n      fireEvent.touchEnd(scroll, {\n        changedTouches: [\n          createTouch(scroll, {\n            clientX: 0,\n            clientY: 0,\n          }),\n        ],\n      });\n\n      await flushMicrotasks();\n    } finally {\n      document.elementFromPoint = originalElementFromPoint;\n    }\n\n    expect(handleOpenChange).toHaveBeenCalledWith(\n      false,\n      expect.objectContaining({ reason: 'swipe' }),\n    );\n  });\n\n  it('allows horizontal swipe dismiss from a vertical scroll container', async () => {\n    await render(\n      <Drawer.Root open swipeDirection=\"right\">\n        <Drawer.Portal>\n          <Drawer.Backdrop data-testid=\"backdrop\" />\n          <Drawer.Viewport>\n            <Drawer.Popup>\n              <div data-testid=\"scroll\" style={{ overflowY: 'auto', maxHeight: 40 }}>\n                <div style={{ height: 120 }}>Scrollable content</div>\n              </div>\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const scroll = screen.getByTestId('scroll');\n    const backdrop = screen.getByTestId('backdrop');\n    Object.defineProperty(scroll, 'scrollHeight', { value: 120, configurable: true });\n    Object.defineProperty(scroll, 'clientHeight', { value: 40, configurable: true });\n    scroll.scrollTop = 20;\n\n    const originalElementFromPoint = document.elementFromPoint;\n    document.elementFromPoint = () => scroll;\n\n    try {\n      fireEvent.touchStart(scroll, {\n        touches: [\n          createTouch(scroll, {\n            clientX: 0,\n            clientY: 20,\n          }),\n        ],\n      });\n\n      fireEvent.touchMove(scroll, {\n        touches: [\n          createTouch(scroll, {\n            clientX: 20,\n            clientY: 20,\n          }),\n        ],\n      });\n\n      await flushMicrotasks();\n\n      expect(backdrop).toHaveAttribute('data-swiping', '');\n    } finally {\n      document.elementFromPoint = originalElementFromPoint;\n    }\n  });\n\n  it('does not lock vertical swipe after minor cross-axis jitter in down drawers', async () => {\n    await render(\n      <Drawer.Root open swipeDirection=\"down\">\n        <Drawer.Portal>\n          <Drawer.Backdrop data-testid=\"backdrop\" />\n          <Drawer.Viewport>\n            <Drawer.Popup>\n              <div data-testid=\"scroll\" style={{ overflowX: 'auto', width: 40 }}>\n                <div style={{ width: 120, height: 40 }}>Scrollable content</div>\n              </div>\n            </Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const scroll = screen.getByTestId('scroll');\n    const backdrop = screen.getByTestId('backdrop');\n    Object.defineProperty(scroll, 'scrollWidth', { value: 120, configurable: true });\n    Object.defineProperty(scroll, 'clientWidth', { value: 40, configurable: true });\n    scroll.scrollLeft = 0;\n\n    const originalElementFromPoint = document.elementFromPoint;\n    document.elementFromPoint = () => scroll;\n\n    try {\n      fireEvent.touchStart(scroll, {\n        touches: [\n          createTouch(scroll, {\n            clientX: 0,\n            clientY: 0,\n          }),\n        ],\n      });\n\n      fireEvent.touchMove(scroll, {\n        touches: [\n          createTouch(scroll, {\n            clientX: 4,\n            clientY: 3,\n          }),\n        ],\n      });\n\n      fireEvent.touchMove(scroll, {\n        touches: [\n          createTouch(scroll, {\n            clientX: 4,\n            clientY: 28,\n          }),\n        ],\n      });\n\n      await flushMicrotasks();\n\n      expect(backdrop).toHaveAttribute('data-swiping', '');\n    } finally {\n      document.elementFromPoint = originalElementFromPoint;\n    }\n  });\n\n  it.skipIf(isJSDOM)(\n    'does not hijack cross-axis gestures from mixed-axis scroll containers',\n    async () => {\n      await render(\n        <Drawer.Root open swipeDirection=\"down\">\n          <Drawer.Portal>\n            <Drawer.Backdrop data-testid=\"backdrop\" />\n            <Drawer.Viewport>\n              <Drawer.Popup>\n                <div data-testid=\"scroll\" style={{ overflow: 'auto', width: 40, height: 40 }}>\n                  <div style={{ width: 120, height: 120 }}>Scrollable content</div>\n                </div>\n              </Drawer.Popup>\n            </Drawer.Viewport>\n          </Drawer.Portal>\n        </Drawer.Root>,\n      );\n\n      const scroll = screen.getByTestId('scroll');\n      const backdrop = screen.getByTestId('backdrop');\n      Object.defineProperty(scroll, 'scrollHeight', { value: 120, configurable: true });\n      Object.defineProperty(scroll, 'clientHeight', { value: 40, configurable: true });\n      Object.defineProperty(scroll, 'scrollWidth', { value: 120, configurable: true });\n      Object.defineProperty(scroll, 'clientWidth', { value: 40, configurable: true });\n      scroll.scrollTop = 0;\n      scroll.scrollLeft = 40;\n\n      const originalElementFromPoint = document.elementFromPoint;\n      document.elementFromPoint = () => scroll;\n\n      try {\n        fireEvent.touchStart(scroll, {\n          touches: [\n            createTouch(scroll, {\n              clientX: 40,\n              clientY: 0,\n            }),\n          ],\n        });\n\n        fireEvent.touchMove(scroll, {\n          touches: [\n            createTouch(scroll, {\n              clientX: 10,\n              clientY: 20,\n            }),\n          ],\n        });\n\n        await flushMicrotasks();\n\n        expect(backdrop).not.toHaveAttribute('data-swiping');\n      } finally {\n        document.elementFromPoint = originalElementFromPoint;\n      }\n    },\n  );\n\n  it.skipIf(isJSDOM)(\n    'does not block vertical scrolling in right drawers when only vertical overflow exists',\n    async () => {\n      await render(\n        <Drawer.Root open swipeDirection=\"right\">\n          <Drawer.Portal>\n            <Drawer.Backdrop data-testid=\"backdrop\" />\n            <Drawer.Viewport>\n              <Drawer.Popup>\n                <div data-testid=\"scroll\" style={{ overflowY: 'auto', height: 40 }}>\n                  <div style={{ height: 120 }}>Scrollable content</div>\n                </div>\n              </Drawer.Popup>\n            </Drawer.Viewport>\n          </Drawer.Portal>\n        </Drawer.Root>,\n      );\n\n      const scroll = screen.getByTestId('scroll');\n      const backdrop = screen.getByTestId('backdrop');\n\n      fireEvent.touchStart(scroll, {\n        touches: [\n          createTouch(scroll, {\n            clientX: 0,\n            clientY: 20,\n          }),\n        ],\n      });\n\n      const dispatched = fireEvent.touchMove(scroll, {\n        touches: [\n          createTouch(scroll, {\n            clientX: 0,\n            clientY: 0,\n          }),\n        ],\n      });\n\n      await flushMicrotasks();\n\n      expect(dispatched).toBe(true);\n      expect(backdrop).not.toHaveAttribute('data-swiping');\n    },\n  );\n\n  it.skipIf(isJSDOM)(\n    'does not block vertical scrolling in left drawers when only vertical overflow exists',\n    async () => {\n      await render(\n        <Drawer.Root open swipeDirection=\"left\">\n          <Drawer.Portal>\n            <Drawer.Backdrop data-testid=\"backdrop\" />\n            <Drawer.Viewport>\n              <Drawer.Popup>\n                <div data-testid=\"scroll\" style={{ overflowY: 'auto', height: 40 }}>\n                  <div style={{ height: 120 }}>Scrollable content</div>\n                </div>\n              </Drawer.Popup>\n            </Drawer.Viewport>\n          </Drawer.Portal>\n        </Drawer.Root>,\n      );\n\n      const scroll = screen.getByTestId('scroll');\n      const backdrop = screen.getByTestId('backdrop');\n\n      fireEvent.touchStart(scroll, {\n        touches: [\n          createTouch(scroll, {\n            clientX: 0,\n            clientY: 20,\n          }),\n        ],\n      });\n\n      const dispatched = fireEvent.touchMove(scroll, {\n        touches: [\n          createTouch(scroll, {\n            clientX: 0,\n            clientY: 0,\n          }),\n        ],\n      });\n\n      await flushMicrotasks();\n\n      expect(dispatched).toBe(true);\n      expect(backdrop).not.toHaveAttribute('data-swiping');\n    },\n  );\n\n  it.skipIf(isJSDOM)(\n    'does not block horizontal scrolling in down drawers when only horizontal overflow exists',\n    async () => {\n      await render(\n        <Drawer.Root open swipeDirection=\"down\">\n          <Drawer.Portal>\n            <Drawer.Backdrop data-testid=\"backdrop\" />\n            <Drawer.Viewport>\n              <Drawer.Popup>\n                <div data-testid=\"scroll\" style={{ overflowX: 'auto', width: 40 }}>\n                  <div style={{ width: 120, height: 40 }}>Scrollable content</div>\n                </div>\n              </Drawer.Popup>\n            </Drawer.Viewport>\n          </Drawer.Portal>\n        </Drawer.Root>,\n      );\n\n      const scroll = screen.getByTestId('scroll');\n      const backdrop = screen.getByTestId('backdrop');\n\n      fireEvent.touchStart(scroll, {\n        touches: [\n          createTouch(scroll, {\n            clientX: 20,\n            clientY: 0,\n          }),\n        ],\n      });\n\n      const dispatched = fireEvent.touchMove(scroll, {\n        touches: [\n          createTouch(scroll, {\n            clientX: 0,\n            clientY: 0,\n          }),\n        ],\n      });\n\n      await flushMicrotasks();\n\n      expect(dispatched).toBe(true);\n      expect(backdrop).not.toHaveAttribute('data-swiping');\n    },\n  );\n\n  it.skipIf(isJSDOM)(\n    'does not block horizontal scrolling in up drawers when only horizontal overflow exists',\n    async () => {\n      await render(\n        <Drawer.Root open swipeDirection=\"up\">\n          <Drawer.Portal>\n            <Drawer.Backdrop data-testid=\"backdrop\" />\n            <Drawer.Viewport>\n              <Drawer.Popup>\n                <div data-testid=\"scroll\" style={{ overflowX: 'auto', width: 40 }}>\n                  <div style={{ width: 120, height: 40 }}>Scrollable content</div>\n                </div>\n              </Drawer.Popup>\n            </Drawer.Viewport>\n          </Drawer.Portal>\n        </Drawer.Root>,\n      );\n\n      const scroll = screen.getByTestId('scroll');\n      const backdrop = screen.getByTestId('backdrop');\n\n      fireEvent.touchStart(scroll, {\n        touches: [\n          createTouch(scroll, {\n            clientX: 20,\n            clientY: 0,\n          }),\n        ],\n      });\n\n      const dispatched = fireEvent.touchMove(scroll, {\n        touches: [\n          createTouch(scroll, {\n            clientX: 0,\n            clientY: 0,\n          }),\n        ],\n      });\n\n      await flushMicrotasks();\n\n      expect(dispatched).toBe(true);\n      expect(backdrop).not.toHaveAttribute('data-swiping');\n    },\n  );\n\n  it('toggles data-swiping on the backdrop while swiping', async () => {\n    await render(\n      <Drawer.Root open>\n        <Drawer.Portal>\n          <Drawer.Backdrop data-testid=\"backdrop\" />\n          <Drawer.Viewport data-testid=\"viewport\">\n            <Drawer.Popup data-testid=\"popup\">Drawer</Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const viewport = screen.getByTestId('viewport');\n    const popup = screen.getByTestId('popup');\n    const backdrop = screen.getByTestId('backdrop');\n\n    const originalElementFromPoint = document.elementFromPoint;\n    document.elementFromPoint = () => popup;\n\n    try {\n      fireEvent.pointerDown(viewport, {\n        button: 0,\n        buttons: 1,\n        pointerId: 1,\n        clientX: 0,\n        clientY: 0,\n        pointerType: 'mouse',\n      });\n\n      await flushMicrotasks();\n\n      fireEvent.pointerMove(viewport, {\n        pointerId: 1,\n        clientX: 0,\n        clientY: 8,\n        pointerType: 'mouse',\n      });\n\n      await flushMicrotasks();\n\n      expect(backdrop).toHaveAttribute('data-swiping', '');\n\n      fireEvent.pointerUp(viewport, {\n        pointerId: 1,\n        clientX: 0,\n        clientY: 8,\n        pointerType: 'mouse',\n      });\n\n      await flushMicrotasks();\n\n      expect(backdrop).not.toHaveAttribute('data-swiping');\n    } finally {\n      document.elementFromPoint = originalElementFromPoint;\n    }\n  });\n\n  it('ends swipe drag when the primary mouse button is released mid-gesture', async () => {\n    await render(\n      <Drawer.Root open>\n        <Drawer.Portal>\n          <Drawer.Backdrop data-testid=\"backdrop\" />\n          <Drawer.Viewport data-testid=\"viewport\">\n            <Drawer.Popup data-testid=\"popup\">Drawer</Drawer.Popup>\n          </Drawer.Viewport>\n        </Drawer.Portal>\n      </Drawer.Root>,\n    );\n\n    const viewport = screen.getByTestId('viewport');\n    const popup = screen.getByTestId('popup');\n    const backdrop = screen.getByTestId('backdrop');\n\n    const originalElementFromPoint = document.elementFromPoint;\n    document.elementFromPoint = () => popup;\n\n    try {\n      fireEvent.pointerDown(viewport, {\n        button: 0,\n        buttons: 1,\n        pointerId: 1,\n        clientX: 0,\n        clientY: 0,\n        pointerType: 'mouse',\n      });\n\n      await flushMicrotasks();\n\n      fireEvent.pointerMove(viewport, {\n        pointerId: 1,\n        clientX: 0,\n        clientY: 8,\n        buttons: 1,\n        pointerType: 'mouse',\n      });\n\n      await flushMicrotasks();\n\n      expect(backdrop).toHaveAttribute('data-swiping', '');\n\n      // Simulate a right-click interruption where the primary button is no longer pressed.\n      fireEvent.pointerMove(viewport, {\n        pointerId: 1,\n        clientX: 0,\n        clientY: 12,\n        buttons: 2,\n        pointerType: 'mouse',\n      });\n\n      await flushMicrotasks();\n\n      expect(backdrop).not.toHaveAttribute('data-swiping');\n\n      fireEvent.pointerMove(viewport, {\n        pointerId: 1,\n        clientX: 0,\n        clientY: 30,\n        buttons: 0,\n        pointerType: 'mouse',\n      });\n\n      await flushMicrotasks();\n\n      expect(backdrop).not.toHaveAttribute('data-swiping');\n    } finally {\n      document.elementFromPoint = originalElementFromPoint;\n    }\n  });\n});\n"
  },
  {
    "path": "packages/react/src/drawer/viewport/DrawerViewport.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport { isElement } from '@floating-ui/utils/dom';\nimport { ownerDocument, ownerWindow } from '@base-ui/utils/owner';\nimport { useAnimationFrame } from '@base-ui/utils/useAnimationFrame';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useDialogRootContext } from '../../dialog/root/DialogRootContext';\nimport { DialogViewport } from '../../dialog/viewport/DialogViewport';\nimport { mergeProps } from '../../merge-props';\nimport { useDrawerRootContext } from '../root/DrawerRootContext';\nimport { useDrawerSnapPoints } from '../root/useDrawerSnapPoints';\nimport { useDrawerProviderContext } from '../provider/DrawerProviderContext';\nimport { clamp } from '../../utils/clamp';\nimport {\n  useSwipeDismiss,\n  type SwipeDirection,\n  type UseSwipeDismissProgressDetails,\n} from '../../utils/useSwipeDismiss';\nimport { DrawerPopupCssVars } from '../popup/DrawerPopupCssVars';\nimport { DrawerPopupDataAttributes } from '../popup/DrawerPopupDataAttributes';\nimport { DrawerBackdropCssVars } from '../backdrop/DrawerBackdropCssVars';\nimport { DRAWER_CONTENT_ATTRIBUTE } from '../content/DrawerContentDataAttributes';\nimport { REASONS } from '../../utils/reasons';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { contains } from '../../floating-ui-react/utils';\nimport { DrawerViewportContext } from './DrawerViewportContext';\nimport { TransitionStatusDataAttributes } from '../../utils/stateAttributesMapping';\nimport { findScrollableTouchTarget, type ScrollAxis } from '../../utils/scrollable';\nimport { BASE_UI_SWIPE_IGNORE_SELECTOR } from '../../utils/constants';\nimport { getElementAtPoint } from '../../utils/getElementAtPoint';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport type { TransitionStatus } from '../../utils/useTransitionStatus';\n\nconst MIN_SWIPE_THRESHOLD = 10;\nconst FAST_SWIPE_VELOCITY = 0.5;\nconst SNAP_VELOCITY_THRESHOLD = 0.5;\nconst SNAP_VELOCITY_MULTIPLIER = 300;\nconst MAX_SNAP_VELOCITY = 4;\nconst MIN_SWIPE_RELEASE_VELOCITY = 0.2;\nconst MAX_SWIPE_RELEASE_VELOCITY = 4;\nconst MIN_SWIPE_RELEASE_DURATION_MS = 80;\nconst MAX_SWIPE_RELEASE_DURATION_MS = 360;\nconst MIN_SWIPE_RELEASE_SCALAR = 0.1;\nconst MAX_SWIPE_RELEASE_SCALAR = 1;\nconst DRAWER_CONTENT_SELECTOR = `[${DRAWER_CONTENT_ATTRIBUTE}]`;\n\ninterface TouchScrollState {\n  startX: number;\n  startY: number;\n  lastX: number;\n  lastY: number;\n  scrollTarget: HTMLElement | null;\n  hasCrossAxisScrollableContent: boolean;\n  allowSwipe: boolean | null;\n  preserveNativeCrossAxisScroll: boolean;\n}\n\n/**\n * A positioning container for the drawer popup that can be made scrollable.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)\n */\nexport const DrawerViewport = React.forwardRef(function DrawerViewport(\n  props: DrawerViewport.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { className, render, children, ...elementProps } = props;\n\n  const { store } = useDialogRootContext();\n  const {\n    swipeDirection,\n    notifyParentSwipingChange,\n    notifyParentSwipeProgressChange,\n    frontmostHeight,\n    snapToSequentialPoints,\n  } = useDrawerRootContext();\n  const providerContext = useDrawerProviderContext(true);\n  const visualStateStore = providerContext?.visualStateStore;\n\n  const open = store.useState('open');\n  const mounted = store.useState('mounted');\n  const nested = store.useState('nested');\n  const nestedOpenDialogCount = store.useState('nestedOpenDialogCount');\n  const viewportElement = store.useState('viewportElement');\n  const popupElementState = store.useState('popupElement');\n\n  const nestedDrawerOpen = nestedOpenDialogCount > 0;\n  const scrollAxis =\n    swipeDirection === 'left' || swipeDirection === 'right' ? 'horizontal' : 'vertical';\n  const isVerticalScrollAxis = scrollAxis === 'vertical';\n  const crossScrollAxis: ScrollAxis = isVerticalScrollAxis ? 'horizontal' : 'vertical';\n\n  const {\n    snapPoints,\n    resolvedSnapPoints,\n    activeSnapPoint,\n    activeSnapPointOffset,\n    setActiveSnapPoint,\n    popupHeight,\n  } = useDrawerSnapPoints();\n\n  const [swipeRelease, setSwipeRelease] = React.useState<number | null>(null);\n  const pendingSwipeCloseSnapPointRef = React.useRef<typeof activeSnapPoint>(undefined);\n  const resetSwipeRef = React.useRef<(() => void) | null>(null);\n  const controlledDismissFrame = useAnimationFrame();\n\n  const nestedSwipeActiveRef = React.useRef(false);\n  const lastPointerTypeRef = React.useRef<React.PointerEvent['pointerType'] | ''>('');\n  const ignoreNextTouchStartFromPenRef = React.useRef(false);\n  const ignoreTouchSwipeRef = React.useRef(false);\n  const touchScrollStateRef = React.useRef<TouchScrollState | null>(null);\n\n  const snapPointRange = React.useMemo(() => {\n    if (!snapPoints || snapPoints.length < 2) {\n      return null;\n    }\n\n    if (swipeDirection !== 'down' && swipeDirection !== 'up') {\n      return null;\n    }\n\n    if (resolvedSnapPoints.length < 2) {\n      return null;\n    }\n\n    const offsets = resolvedSnapPoints\n      .map((point) => point.offset)\n      .filter((offset) => Number.isFinite(offset))\n      .sort((a, b) => a - b);\n\n    if (offsets.length < 2) {\n      return null;\n    }\n\n    const minOffset = offsets[0];\n    const nextOffset = offsets[1];\n    const maxOffset = offsets[offsets.length - 1];\n    let range = nextOffset - minOffset;\n    if (!Number.isFinite(range) || range <= 0) {\n      const fallbackRange = maxOffset - minOffset;\n      if (!Number.isFinite(fallbackRange) || fallbackRange <= 0) {\n        return null;\n      }\n      range = fallbackRange;\n    }\n\n    return { minOffset, range };\n  }, [resolvedSnapPoints, snapPoints, swipeDirection]);\n\n  const snapPointProgress = React.useMemo(() => {\n    if (!snapPointRange || activeSnapPointOffset === null) {\n      return null;\n    }\n\n    return clamp((activeSnapPointOffset - snapPointRange.minOffset) / snapPointRange.range, 0, 1);\n  }, [activeSnapPointOffset, snapPointRange]);\n\n  const swipeDirections = React.useMemo<SwipeDirection[]>(() => {\n    if (\n      snapPoints &&\n      snapPoints.length > 0 &&\n      (swipeDirection === 'down' || swipeDirection === 'up')\n    ) {\n      return swipeDirection === 'down' ? ['down', 'up'] : ['up', 'down'];\n    }\n\n    return [swipeDirection];\n  }, [snapPoints, swipeDirection]);\n\n  const setSwipeDismissed = useStableCallback((dismissed: boolean) => {\n    setSwipeDismissedElements(\n      store.context.popupRef.current,\n      store.context.backdropRef.current,\n      dismissed,\n    );\n  });\n\n  const clearSwipeRelease = useStableCallback(() => {\n    setSwipeDismissed(false);\n    store.context.popupRef.current?.removeAttribute(TransitionStatusDataAttributes.endingStyle);\n    setSwipeRelease(null);\n  });\n\n  const applySwipeProgress = useStableCallback(\n    ({\n      resolvedProgress,\n      shouldTrackProgress,\n      notifyParent,\n    }: {\n      resolvedProgress: number;\n      shouldTrackProgress: boolean;\n      notifyParent: boolean;\n    }) => {\n      const isActive = open && !nested && shouldTrackProgress;\n      const swipeProgress = isActive ? resolvedProgress : 0;\n\n      if (notifyParent && notifyParentSwipeProgressChange) {\n        const nestedSwipeProgress = open && shouldTrackProgress ? resolvedProgress : 0;\n        notifyParentSwipeProgressChange(nestedSwipeProgress);\n      }\n\n      visualStateStore?.set({\n        swipeProgress,\n        frontmostHeight: swipeProgress > 0 ? frontmostHeight : 0,\n      });\n\n      const backdropElement = store.context.backdropRef.current;\n      if (!backdropElement) {\n        return;\n      }\n\n      if (!isActive || swipeProgress <= 0) {\n        backdropElement.style.setProperty(DrawerBackdropCssVars.swipeProgress, '0');\n        backdropElement.style.removeProperty(DrawerPopupCssVars.height);\n        return;\n      }\n\n      backdropElement.style.setProperty(DrawerBackdropCssVars.swipeProgress, `${swipeProgress}`);\n      if (frontmostHeight > 0) {\n        backdropElement.style.setProperty(DrawerPopupCssVars.height, `${frontmostHeight}px`);\n      } else {\n        backdropElement.style.removeProperty(DrawerPopupCssVars.height);\n      }\n    },\n  );\n\n  function resolveSwipeRelease({\n    direction,\n    deltaX,\n    deltaY,\n    velocityX,\n    velocityY,\n    releaseVelocityX,\n    releaseVelocityY,\n  }: {\n    direction: SwipeDirection | undefined;\n    deltaX: number;\n    deltaY: number;\n    velocityX: number;\n    velocityY: number;\n    releaseVelocityX: number;\n    releaseVelocityY: number;\n  }): number | null {\n    if (!direction) {\n      return null;\n    }\n\n    const popupElement = store.context.popupRef.current;\n    if (!popupElement) {\n      return null;\n    }\n\n    const size =\n      direction === 'left' || direction === 'right'\n        ? popupElement.offsetWidth\n        : popupElement.offsetHeight;\n    if (!Number.isFinite(size) || size <= 0) {\n      return null;\n    }\n\n    const axisDelta = direction === 'left' || direction === 'right' ? deltaX : deltaY;\n    const snapPointBaseOffset =\n      snapPoints && snapPoints.length > 0 ? (activeSnapPointOffset ?? 0) : 0;\n    let baseOffset = 0;\n    if (direction === 'down') {\n      baseOffset = snapPointBaseOffset;\n    } else if (direction === 'up') {\n      baseOffset = -snapPointBaseOffset;\n    }\n\n    const translation = baseOffset + axisDelta;\n    const translationAlongDirection =\n      direction === 'left' || direction === 'up' ? -translation : translation;\n    const remainingDistance = Math.max(0, size - translationAlongDirection);\n    if (!Number.isFinite(remainingDistance) || remainingDistance <= 0) {\n      return null;\n    }\n\n    const axisVelocity =\n      direction === 'left' || direction === 'right' ? releaseVelocityX : releaseVelocityY;\n    const fallbackVelocity = direction === 'left' || direction === 'right' ? velocityX : velocityY;\n    const resolvedVelocity =\n      Math.abs(axisVelocity) > 0 && Number.isFinite(axisVelocity) ? axisVelocity : fallbackVelocity;\n    const directionalVelocity =\n      direction === 'left' || direction === 'up' ? -resolvedVelocity : resolvedVelocity;\n    if (\n      !Number.isFinite(directionalVelocity) ||\n      directionalVelocity <= MIN_SWIPE_RELEASE_VELOCITY\n    ) {\n      return null;\n    }\n\n    const clampedVelocity = clamp(\n      directionalVelocity,\n      MIN_SWIPE_RELEASE_VELOCITY,\n      MAX_SWIPE_RELEASE_VELOCITY,\n    );\n    const durationMs = clamp(\n      remainingDistance / clampedVelocity,\n      MIN_SWIPE_RELEASE_DURATION_MS,\n      MAX_SWIPE_RELEASE_DURATION_MS,\n    );\n    if (!Number.isFinite(durationMs)) {\n      return null;\n    }\n\n    const normalizedDuration =\n      (durationMs - MIN_SWIPE_RELEASE_DURATION_MS) /\n      (MAX_SWIPE_RELEASE_DURATION_MS - MIN_SWIPE_RELEASE_DURATION_MS);\n    const durationScalar = clamp(\n      MIN_SWIPE_RELEASE_SCALAR +\n        normalizedDuration * (MAX_SWIPE_RELEASE_SCALAR - MIN_SWIPE_RELEASE_SCALAR),\n      MIN_SWIPE_RELEASE_SCALAR,\n      MAX_SWIPE_RELEASE_SCALAR,\n    );\n    if (!Number.isFinite(durationScalar) || durationScalar <= 0) {\n      return null;\n    }\n\n    return durationScalar;\n  }\n\n  function updateNestedSwipeActive(details?: UseSwipeDismissProgressDetails) {\n    if (nestedSwipeActiveRef.current || !details) {\n      return;\n    }\n\n    const direction = details.direction ?? swipeDirection;\n    const delta = direction === 'left' || direction === 'right' ? details.deltaX : details.deltaY;\n    if (!Number.isFinite(delta) || Math.abs(delta) < MIN_SWIPE_THRESHOLD) {\n      return;\n    }\n\n    nestedSwipeActiveRef.current = true;\n    notifyParentSwipingChange?.(true);\n  }\n\n  const swipe = useSwipeDismiss({\n    enabled: mounted && !nestedDrawerOpen,\n    directions: swipeDirections,\n    elementRef: store.context.popupRef,\n    ignoreSelectorWhenTouch: false,\n    ignoreScrollableAncestors: true,\n    movementCssVars: {\n      x: DrawerPopupCssVars.swipeMovementX,\n      y: DrawerPopupCssVars.swipeMovementY,\n    },\n    onSwipeStart(event) {\n      if ('touches' in event || ('pointerType' in event && event.pointerType === 'touch')) {\n        return;\n      }\n\n      const popupElement = store.context.popupRef.current;\n      if (!popupElement) {\n        return;\n      }\n\n      const doc = ownerDocument(popupElement);\n      const selection = doc.getSelection?.();\n      if (!selection || selection.isCollapsed) {\n        return;\n      }\n\n      const anchorElement = isElement(selection.anchorNode)\n        ? selection.anchorNode\n        : selection.anchorNode?.parentElement;\n      const focusElement = isElement(selection.focusNode)\n        ? selection.focusNode\n        : selection.focusNode?.parentElement;\n\n      if (!contains(popupElement, anchorElement) && !contains(popupElement, focusElement)) {\n        return;\n      }\n\n      selection.removeAllRanges();\n    },\n    onSwipingChange(swiping) {\n      setBackdropSwipingAttribute(store.context.backdropRef.current, swiping);\n\n      if (!swiping) {\n        nestedSwipeActiveRef.current = false;\n        notifyParentSwipingChange?.(false);\n      }\n    },\n    swipeThreshold({ element, direction }) {\n      return getBaseSwipeThreshold(element, direction);\n    },\n    canStart(position, details) {\n      const popupElement = store.context.popupRef.current;\n      if (!popupElement) {\n        return false;\n      }\n\n      const doc = popupElement.ownerDocument;\n      const elementAtPoint = getElementAtPoint(doc, position.x, position.y);\n      if (!elementAtPoint || !contains(popupElement, elementAtPoint)) {\n        return false;\n      }\n\n      const nativeEvent = details.nativeEvent;\n      const touchLike =\n        'touches' in nativeEvent ||\n        ('pointerType' in nativeEvent && nativeEvent.pointerType === 'touch');\n      if (touchLike && shouldIgnoreSwipeForTextSelection(doc, popupElement)) {\n        return false;\n      }\n\n      if (nativeEvent.type === 'touchstart' && isSwipeIgnoredTarget(elementAtPoint)) {\n        return false;\n      }\n\n      return true;\n    },\n    onProgress(progress, details) {\n      updateNestedSwipeActive(details);\n\n      const currentDirection = details?.direction ?? swipe.swipeDirection;\n      const isDismissSwipe = currentDirection === undefined || currentDirection === swipeDirection;\n      const hasSnapPoints = Boolean(snapPoints && snapPoints.length > 0);\n      const isVerticalSwipe = swipeDirection === 'down' || swipeDirection === 'up';\n      const shouldTrackProgress =\n        (hasSnapPoints && isVerticalSwipe) ||\n        !hasSnapPoints ||\n        swipeDirection === 'left' ||\n        swipeDirection === 'right' ||\n        isDismissSwipe;\n\n      let resolvedProgress = progress;\n      if (snapPointRange && popupHeight > 0) {\n        if (details && Number.isFinite(details.deltaY)) {\n          const baseOffset = activeSnapPointOffset ?? snapPointRange.minOffset;\n          const nextOffset = clamp(baseOffset + details.deltaY, 0, popupHeight);\n          resolvedProgress = clamp(\n            (nextOffset - snapPointRange.minOffset) / snapPointRange.range,\n            0,\n            1,\n          );\n        } else if (snapPointProgress !== null) {\n          resolvedProgress = snapPointProgress;\n        } else if (currentDirection === 'down' || currentDirection === 'up') {\n          const displacement = progress * popupHeight;\n          const baseOffset = activeSnapPointOffset ?? snapPointRange.minOffset;\n          const nextOffset =\n            currentDirection === 'down' ? baseOffset + displacement : baseOffset - displacement;\n          resolvedProgress = clamp(\n            (nextOffset - snapPointRange.minOffset) / snapPointRange.range,\n            0,\n            1,\n          );\n        }\n      }\n\n      applySwipeProgress({\n        resolvedProgress,\n        shouldTrackProgress,\n        notifyParent: true,\n      });\n    },\n    onRelease({\n      event,\n      deltaX,\n      deltaY,\n      direction,\n      velocityX,\n      velocityY,\n      releaseVelocityX,\n      releaseVelocityY,\n    }: {\n      event: PointerEvent | TouchEvent;\n      deltaX: number;\n      deltaY: number;\n      direction: SwipeDirection | undefined;\n      velocityX: number;\n      velocityY: number;\n      releaseVelocityX: number;\n      releaseVelocityY: number;\n    }) {\n      const swipeReleasePayload = {\n        deltaX,\n        deltaY,\n        velocityX,\n        velocityY,\n        releaseVelocityX,\n        releaseVelocityY,\n      };\n\n      function startSwipeRelease(resolvedDirection: SwipeDirection) {\n        // Start ending transition styles earlier and synchronously to prevent a period where\n        // the popup appears stuck on release before the actual closing animation starts.\n        const popupElement = store.context.popupRef.current;\n        if (!popupElement) {\n          return;\n        }\n\n        notifyParentSwipingChange?.(false);\n        setSwipeDismissed(true);\n\n        popupElement.style.removeProperty('transition');\n        popupElement.setAttribute(TransitionStatusDataAttributes.endingStyle, '');\n        ReactDOM.flushSync(() => {\n          setSwipeRelease(\n            resolveSwipeRelease({\n              direction: resolvedDirection,\n              ...swipeReleasePayload,\n            }),\n          );\n        });\n      }\n\n      if (!snapPoints || snapPoints.length === 0) {\n        if (!direction) {\n          clearSwipeRelease();\n          return undefined;\n        }\n\n        const element = store.context.popupRef.current;\n        if (!element) {\n          clearSwipeRelease();\n          return undefined;\n        }\n\n        const baseThreshold = getBaseSwipeThreshold(element, direction);\n        const delta = direction === 'left' || direction === 'right' ? deltaX : deltaY;\n        if (!Number.isFinite(delta)) {\n          clearSwipeRelease();\n          return undefined;\n        }\n\n        const directionalDelta = direction === 'left' || direction === 'up' ? -delta : delta;\n        if (directionalDelta <= 0) {\n          clearSwipeRelease();\n          return false;\n        }\n\n        const velocity = direction === 'left' || direction === 'right' ? velocityX : velocityY;\n        const directionalVelocity =\n          direction === 'left' || direction === 'up' ? -velocity : velocity;\n        if (directionalVelocity >= FAST_SWIPE_VELOCITY && directionalDelta > 0) {\n          startSwipeRelease(direction);\n          return true;\n        }\n\n        const shouldClose = directionalDelta > baseThreshold;\n        if (shouldClose) {\n          startSwipeRelease(direction);\n        } else {\n          clearSwipeRelease();\n        }\n        return shouldClose;\n      }\n\n      if (swipeDirection !== 'down' && swipeDirection !== 'up') {\n        clearSwipeRelease();\n        return undefined;\n      }\n\n      if (!popupHeight || resolvedSnapPoints.length === 0) {\n        clearSwipeRelease();\n        return undefined;\n      }\n\n      const dragDelta = swipeDirection === 'down' ? deltaY : -deltaY;\n      if (!Number.isFinite(dragDelta)) {\n        clearSwipeRelease();\n        return undefined;\n      }\n\n      const dragDirection = Math.sign(dragDelta);\n      const releaseDirectionalVelocity =\n        swipeDirection === 'down' ? releaseVelocityY : -releaseVelocityY;\n      const fallbackDirectionalVelocity = swipeDirection === 'down' ? velocityY : -velocityY;\n      let resolvedDirectionalVelocity = Number.isFinite(releaseDirectionalVelocity)\n        ? releaseDirectionalVelocity\n        : fallbackDirectionalVelocity;\n      if (\n        dragDirection !== 0 &&\n        Math.abs(dragDelta) >= MIN_SWIPE_THRESHOLD &&\n        Number.isFinite(resolvedDirectionalVelocity)\n      ) {\n        const velocityDirection = Math.sign(resolvedDirectionalVelocity);\n        if (velocityDirection !== 0 && velocityDirection !== dragDirection) {\n          // Ignore touch reversals that would otherwise flip the snap decision.\n          resolvedDirectionalVelocity = fallbackDirectionalVelocity;\n        }\n      }\n\n      const currentOffset = activeSnapPointOffset ?? 0;\n      const dragTargetOffset = clamp(currentOffset + dragDelta, 0, popupHeight);\n      const velocityOffset =\n        Number.isFinite(resolvedDirectionalVelocity) &&\n        Math.abs(resolvedDirectionalVelocity) >= SNAP_VELOCITY_THRESHOLD\n          ? clamp(resolvedDirectionalVelocity, -MAX_SNAP_VELOCITY, MAX_SNAP_VELOCITY) *\n            SNAP_VELOCITY_MULTIPLIER\n          : 0;\n      const targetOffset = snapToSequentialPoints\n        ? dragTargetOffset\n        : clamp(dragTargetOffset + velocityOffset, 0, popupHeight);\n      const snapPointEventDetails = createChangeEventDetails(REASONS.swipe, event);\n      const closeFromSnapPoints = () => {\n        pendingSwipeCloseSnapPointRef.current = activeSnapPoint;\n        setActiveSnapPoint?.(null, snapPointEventDetails);\n        startSwipeRelease(swipeDirection);\n        return true;\n      };\n\n      if (snapToSequentialPoints) {\n        const orderedSnapPoints = [...resolvedSnapPoints].sort(\n          (first, second) => first.offset - second.offset,\n        );\n        if (orderedSnapPoints.length === 0) {\n          clearSwipeRelease();\n          return false;\n        }\n\n        let currentIndex = 0;\n        let closestDistance = Math.abs(currentOffset - orderedSnapPoints[0].offset);\n        for (let index = 1; index < orderedSnapPoints.length; index += 1) {\n          const distance = Math.abs(currentOffset - orderedSnapPoints[index].offset);\n          if (distance < closestDistance) {\n            closestDistance = distance;\n            currentIndex = index;\n          }\n        }\n\n        let targetSnapPoint = orderedSnapPoints[0];\n        closestDistance = Math.abs(targetOffset - targetSnapPoint.offset);\n        for (const snapPoint of orderedSnapPoints) {\n          const distance = Math.abs(targetOffset - snapPoint.offset);\n          if (distance < closestDistance) {\n            closestDistance = distance;\n            targetSnapPoint = snapPoint;\n          }\n        }\n\n        const velocityDirection = Math.sign(resolvedDirectionalVelocity);\n        const shouldAdvance =\n          dragDirection !== 0 &&\n          velocityDirection !== 0 &&\n          velocityDirection === dragDirection &&\n          Math.abs(resolvedDirectionalVelocity) >= SNAP_VELOCITY_THRESHOLD;\n        let effectiveTargetOffset = targetOffset;\n\n        if (shouldAdvance) {\n          const adjacentIndex = clamp(\n            currentIndex + dragDirection,\n            0,\n            orderedSnapPoints.length - 1,\n          );\n          if (adjacentIndex !== currentIndex) {\n            const adjacentPoint = orderedSnapPoints[adjacentIndex];\n            const shouldForceAdjacent =\n              dragDirection > 0\n                ? targetOffset < adjacentPoint.offset\n                : targetOffset > adjacentPoint.offset;\n            if (shouldForceAdjacent) {\n              targetSnapPoint = adjacentPoint;\n              effectiveTargetOffset = adjacentPoint.offset;\n            }\n          } else if (dragDirection > 0) {\n            return closeFromSnapPoints();\n          }\n        }\n\n        const closeOffset = popupHeight;\n        const closeDistance = Math.abs(effectiveTargetOffset - closeOffset);\n        const snapDistance = Math.abs(effectiveTargetOffset - targetSnapPoint.offset);\n        if (closeDistance < snapDistance) {\n          return closeFromSnapPoints();\n        }\n\n        setActiveSnapPoint?.(targetSnapPoint.value, snapPointEventDetails);\n        clearSwipeRelease();\n        return false;\n      }\n\n      if (resolvedDirectionalVelocity >= FAST_SWIPE_VELOCITY && dragDelta > 0) {\n        return closeFromSnapPoints();\n      }\n\n      let closestSnapPoint = resolvedSnapPoints[0];\n      let closestDistance = Math.abs(targetOffset - closestSnapPoint.offset);\n\n      for (const snapPoint of resolvedSnapPoints) {\n        const distance = Math.abs(targetOffset - snapPoint.offset);\n        if (distance < closestDistance) {\n          closestDistance = distance;\n          closestSnapPoint = snapPoint;\n        }\n      }\n\n      const closeOffset = popupHeight;\n      const closeDistance = Math.abs(targetOffset - closeOffset);\n      if (closeDistance < closestDistance) {\n        return closeFromSnapPoints();\n      }\n\n      setActiveSnapPoint?.(closestSnapPoint.value, snapPointEventDetails);\n      clearSwipeRelease();\n      return false;\n    },\n    onDismiss(event) {\n      visualStateStore?.set({ swipeProgress: 0, frontmostHeight: 0 });\n\n      const backdropElement = store.context.backdropRef.current;\n      if (backdropElement) {\n        backdropElement.style.setProperty(DrawerBackdropCssVars.swipeProgress, '0');\n        backdropElement.style.removeProperty(DrawerPopupCssVars.height);\n      }\n\n      const dismissEventDetails: Parameters<typeof store.setOpen>[1] = createChangeEventDetails(\n        REASONS.swipe,\n        event,\n      );\n      store.setOpen(false, dismissEventDetails);\n\n      if (dismissEventDetails.isCanceled) {\n        const pendingSnapPoint = pendingSwipeCloseSnapPointRef.current;\n        if (pendingSnapPoint !== undefined) {\n          setActiveSnapPoint?.(pendingSnapPoint, createChangeEventDetails(REASONS.swipe, event));\n        }\n\n        pendingSwipeCloseSnapPointRef.current = undefined;\n        resetSwipeRef.current?.();\n        clearSwipeRelease();\n        return;\n      }\n\n      // In controlled mode, the effective open state may not have changed yet\n      // (openProp takes precedence over state.open). Proceed optimistically with the\n      // dismiss animation — React's Scheduler flushes before the next rAF, so we can\n      // reliably check whether the parent accepted or rejected the close.\n      // Note: if onOpenChange is asynchronous (e.g., closes the drawer after a network\n      // call), the rAF check will see open === true, revert the animation, and the\n      // drawer will close without animation when the parent eventually sets open={false}.\n      if (store.select('open')) {\n        const savedEvent = event;\n        controlledDismissFrame.request(() => {\n          if (store.select('open')) {\n            // Parent rejected: revert animation and restore snap point.\n            const pendingSnapPoint = pendingSwipeCloseSnapPointRef.current;\n            if (pendingSnapPoint !== undefined) {\n              setActiveSnapPoint?.(\n                pendingSnapPoint,\n                createChangeEventDetails(REASONS.swipe, savedEvent),\n              );\n            }\n            pendingSwipeCloseSnapPointRef.current = undefined;\n            clearSwipeRelease();\n            resetSwipeRef.current?.();\n          } else {\n            // Parent accepted: clean up the ref.\n            pendingSwipeCloseSnapPointRef.current = undefined;\n          }\n        });\n        return;\n      }\n\n      pendingSwipeCloseSnapPointRef.current = undefined;\n      setSwipeDismissed(true);\n    },\n  });\n\n  const swipePointerProps = swipe.getPointerProps();\n  const swipeTouchProps = swipe.getTouchProps();\n  const resetSwipe = swipe.reset;\n  resetSwipeRef.current = resetSwipe;\n\n  React.useEffect(() => {\n    const rootElement = viewportElement ?? popupElementState;\n    if (!rootElement) {\n      return undefined;\n    }\n    const resolvedRootElement: HTMLElement = rootElement;\n\n    const doc = ownerDocument(resolvedRootElement);\n    const win = ownerWindow(doc);\n\n    function handleNativeTouchMove(event: TouchEvent) {\n      if (ignoreTouchSwipeRef.current) {\n        return;\n      }\n\n      const touchState = touchScrollStateRef.current;\n      const touch = event.touches[0];\n      if (!touch || !touchState) {\n        return;\n      }\n\n      const drawerAxisDelta = isVerticalScrollAxis\n        ? touch.clientY - touchState.lastY\n        : touch.clientX - touchState.lastX;\n\n      // Preserve native range interaction by never locking touchmove for range inputs.\n      if (isEventOnRangeInput(event, win)) {\n        touchState.allowSwipe = false;\n        updateTouchScrollPosition(touchState, touch);\n        return;\n      }\n\n      // Avoid blocking pinch zoom or text selection adjustments on iOS Safari.\n      if (event.touches.length === 2) {\n        updateTouchScrollPosition(touchState, touch);\n        return;\n      }\n\n      const allowTouchMove = shouldIgnoreSwipeForTextSelection(doc, resolvedRootElement);\n\n      if (allowTouchMove || !open || !mounted || nestedDrawerOpen) {\n        updateTouchScrollPosition(touchState, touch);\n        return;\n      }\n\n      if (preserveNativeCrossAxisScrollOnMove(touchState, touch, isVerticalScrollAxis)) {\n        updateTouchScrollPosition(touchState, touch);\n        return;\n      }\n\n      const scrollTarget = touchState.scrollTarget;\n      if (!scrollTarget || scrollTarget === doc.documentElement || scrollTarget === doc.body) {\n        if (event.cancelable) {\n          event.preventDefault();\n        }\n        updateTouchScrollPosition(touchState, touch);\n        return;\n      }\n\n      const hasScrollableContent = hasScrollableContentOnAxis(scrollTarget, scrollAxis);\n      if (!hasScrollableContent) {\n        // If the scroll container doesn't overflow on the drawer axis, prevent the window from\n        // scrolling instead.\n        if (event.cancelable) {\n          event.preventDefault();\n        }\n        updateTouchScrollPosition(touchState, touch);\n        return;\n      }\n\n      const delta = drawerAxisDelta;\n      if (delta !== 0) {\n        const canSwipeFromScrollEdge = canSwipeFromScrollEdgeOnMove(\n          scrollTarget,\n          scrollAxis,\n          swipeDirection,\n          delta,\n        );\n\n        if (!touchState.allowSwipe) {\n          if (!event.cancelable) {\n            touchState.allowSwipe = false;\n          } else if (canSwipeFromScrollEdge) {\n            touchState.allowSwipe = true;\n            event.preventDefault();\n          } else {\n            touchState.allowSwipe = false;\n          }\n        } else if (event.cancelable) {\n          event.preventDefault();\n        }\n      }\n\n      updateTouchScrollPosition(touchState, touch);\n    }\n\n    doc.addEventListener('touchmove', handleNativeTouchMove, { passive: false, capture: true });\n\n    return () => {\n      doc.removeEventListener('touchmove', handleNativeTouchMove, { capture: true });\n    };\n  }, [\n    mounted,\n    nestedDrawerOpen,\n    open,\n    popupElementState,\n    isVerticalScrollAxis,\n    scrollAxis,\n    swipeDirection,\n    viewportElement,\n  ]);\n\n  React.useEffect(() => {\n    if (!snapPointRange || swipe.swiping) {\n      return;\n    }\n\n    const resolvedProgress = !open || nested ? 0 : (snapPointProgress ?? 0);\n    applySwipeProgress({\n      resolvedProgress,\n      shouldTrackProgress: true,\n      notifyParent: false,\n    });\n  }, [\n    applySwipeProgress,\n    frontmostHeight,\n    nested,\n    notifyParentSwipeProgressChange,\n    open,\n    snapPointProgress,\n    snapPointRange,\n    swipe.swiping,\n    store,\n    visualStateStore,\n  ]);\n\n  React.useEffect(() => {\n    if (!notifyParentSwipeProgressChange) {\n      return undefined;\n    }\n\n    if (!open) {\n      notifyParentSwipeProgressChange(0);\n    }\n\n    return () => {\n      notifyParentSwipeProgressChange(0);\n    };\n  }, [notifyParentSwipeProgressChange, open]);\n\n  React.useEffect(() => {\n    if (open) {\n      resetSwipe();\n      clearSwipeRelease();\n    }\n  }, [clearSwipeRelease, open, resetSwipe]);\n\n  React.useEffect(() => {\n    return () => {\n      visualStateStore?.set({ swipeProgress: 0, frontmostHeight: 0 });\n      setBackdropSwipingAttribute(store.context.backdropRef.current, false);\n      notifyParentSwipingChange?.(false);\n    };\n  }, [notifyParentSwipingChange, store, visualStateStore]);\n\n  const swipeProviderValue = React.useMemo(\n    () => ({\n      swiping: swipe.swiping,\n      getDragStyles: swipe.getDragStyles,\n      swipeStrength: swipeRelease ?? null,\n      setSwipeDismissed(dismissed: boolean) {\n        setSwipeDismissedElements(\n          store.context.popupRef.current,\n          store.context.backdropRef.current,\n          dismissed,\n        );\n      },\n    }),\n    [store, swipe.getDragStyles, swipe.swiping, swipeRelease],\n  );\n\n  function resetTouchTrackingState() {\n    ignoreTouchSwipeRef.current = false;\n    touchScrollStateRef.current = null;\n    lastPointerTypeRef.current = '';\n    ignoreNextTouchStartFromPenRef.current = false;\n  }\n\n  return (\n    <DialogViewport\n      ref={forwardedRef}\n      className={className}\n      render={render}\n      {...mergeProps<'div'>(elementProps, {\n        onPointerDown(event) {\n          lastPointerTypeRef.current = event.pointerType;\n          ignoreNextTouchStartFromPenRef.current = event.pointerType === 'pen';\n\n          if (!open || !mounted || nestedDrawerOpen) {\n            return;\n          }\n\n          const doc = ownerDocument(event.currentTarget);\n          const elementAtPoint = getElementAtPoint(doc, event.clientX, event.clientY);\n          if (isSwipeIgnoredTarget(elementAtPoint) || isDrawerContentTarget(elementAtPoint)) {\n            return;\n          }\n\n          if (event.pointerType === 'touch') {\n            return;\n          }\n\n          swipePointerProps.onPointerDown?.(event);\n        },\n        onPointerMove(event) {\n          if (event.pointerType === 'touch') {\n            return;\n          }\n\n          swipePointerProps.onPointerMove?.(event);\n        },\n        onPointerUp(event) {\n          if (lastPointerTypeRef.current === event.pointerType) {\n            lastPointerTypeRef.current = '';\n          }\n\n          if (event.pointerType === 'touch') {\n            return;\n          }\n\n          swipePointerProps.onPointerUp?.(event);\n        },\n        onPointerCancel(event) {\n          if (lastPointerTypeRef.current === event.pointerType) {\n            lastPointerTypeRef.current = '';\n          }\n\n          if (event.pointerType === 'touch') {\n            return;\n          }\n\n          swipePointerProps.onPointerCancel?.(event);\n        },\n        onTouchStart(event) {\n          const startedFromPenPointerDown =\n            lastPointerTypeRef.current === 'pen' && ignoreNextTouchStartFromPenRef.current;\n          if (startedFromPenPointerDown) {\n            ignoreNextTouchStartFromPenRef.current = false;\n            ignoreTouchSwipeRef.current = false;\n            touchScrollStateRef.current = null;\n            return;\n          }\n\n          if (!open || !mounted || nestedDrawerOpen) {\n            ignoreTouchSwipeRef.current = false;\n            touchScrollStateRef.current = null;\n            return;\n          }\n\n          const touch = event.touches[0];\n          if (!touch) {\n            return;\n          }\n\n          if (isReactTouchEventOnRangeInput(event)) {\n            ignoreTouchSwipeRef.current = false;\n            touchScrollStateRef.current = null;\n            return;\n          }\n\n          const doc = ownerDocument(event.currentTarget);\n          const elementAtPoint = getElementAtPoint(doc, touch.clientX, touch.clientY);\n          ignoreTouchSwipeRef.current = isSwipeIgnoredTarget(elementAtPoint);\n          if (ignoreTouchSwipeRef.current) {\n            touchScrollStateRef.current = null;\n            return;\n          }\n\n          const rootElement = viewportElement ?? popupElementState;\n          const target = isElement(event.target) ? event.target : null;\n          if (rootElement && target && !contains(rootElement, target)) {\n            ignoreTouchSwipeRef.current = true;\n            touchScrollStateRef.current = null;\n            return;\n          }\n\n          let scrollTarget: HTMLElement | null = null;\n          let hasCrossAxisScrollableContent = false;\n          if (rootElement && target) {\n            scrollTarget = findScrollableTouchTarget(target, rootElement, scrollAxis);\n            hasCrossAxisScrollableContent =\n              findScrollableTouchTarget(target, rootElement, crossScrollAxis) != null;\n          }\n\n          let allowSwipe: boolean | null = null;\n          if (scrollTarget) {\n            const canSwipeFromEdge = isAtSwipeStartEdge(scrollTarget, scrollAxis, swipeDirection);\n            allowSwipe = canSwipeFromEdge ? null : false;\n          }\n\n          touchScrollStateRef.current = {\n            startX: touch.clientX,\n            startY: touch.clientY,\n            lastX: touch.clientX,\n            lastY: touch.clientY,\n            scrollTarget,\n            hasCrossAxisScrollableContent,\n            allowSwipe,\n            preserveNativeCrossAxisScroll: false,\n          };\n\n          swipeTouchProps.onTouchStart?.(event);\n        },\n        onTouchMove(event) {\n          if (ignoreTouchSwipeRef.current) {\n            return;\n          }\n\n          if (isReactTouchEventOnRangeInput(event)) {\n            return;\n          }\n\n          const touchState = touchScrollStateRef.current;\n          if (touchState?.preserveNativeCrossAxisScroll) {\n            return;\n          }\n\n          if (\n            touchState?.allowSwipe === false ||\n            (touchState?.scrollTarget != null && !touchState.allowSwipe)\n          ) {\n            return;\n          }\n\n          swipeTouchProps.onTouchMove?.(event);\n        },\n        onTouchEnd(event) {\n          resetTouchTrackingState();\n          swipeTouchProps.onTouchEnd?.(event);\n        },\n        onTouchCancel(event) {\n          resetTouchTrackingState();\n          swipeTouchProps.onTouchCancel?.(event);\n        },\n        // Drawer popups use drawer-specific nested state attributes.\n        // Suppress DialogViewport's generic nested dialog attribute.\n        ['data-nested-dialog-open' as string]: undefined,\n      })}\n    >\n      <DrawerViewportContext.Provider value={swipeProviderValue}>\n        {children}\n      </DrawerViewportContext.Provider>\n    </DialogViewport>\n  );\n});\n\nexport interface DrawerViewportState {\n  /**\n   * Whether the drawer is currently open.\n   */\n  open: boolean;\n  /**\n   * The transition status of the component.\n   */\n  transitionStatus: TransitionStatus;\n  /**\n   * Whether the drawer is nested within another drawer.\n   */\n  nested: boolean;\n  /**\n   * Whether the drawer has nested drawers open.\n   */\n  nestedDialogOpen: boolean;\n}\n\nexport interface DrawerViewportProps extends BaseUIComponentProps<'div', DrawerViewportState> {}\n\nexport namespace DrawerViewport {\n  export type Props = DrawerViewportProps;\n  export type State = DrawerViewportState;\n}\n\nfunction setSwipeDismissedElements(\n  popupElement: HTMLElement | null,\n  backdropElement: HTMLElement | null,\n  dismissed: boolean,\n) {\n  if (dismissed) {\n    popupElement?.setAttribute(DrawerPopupDataAttributes.swipeDismiss, '');\n    backdropElement?.setAttribute(DrawerPopupDataAttributes.swipeDismiss, '');\n    return;\n  }\n\n  popupElement?.removeAttribute(DrawerPopupDataAttributes.swipeDismiss);\n  backdropElement?.removeAttribute(DrawerPopupDataAttributes.swipeDismiss);\n}\n\nfunction setBackdropSwipingAttribute(backdropElement: HTMLElement | null, swiping: boolean) {\n  if (!backdropElement) {\n    return;\n  }\n\n  if (swiping) {\n    backdropElement.setAttribute(DrawerPopupDataAttributes.swiping, '');\n    return;\n  }\n\n  backdropElement.removeAttribute(DrawerPopupDataAttributes.swiping);\n}\n\nfunction isSwipeIgnoredTarget(target: Element | null): boolean {\n  return Boolean(target?.closest(BASE_UI_SWIPE_IGNORE_SELECTOR));\n}\n\nfunction isDrawerContentTarget(target: Element | null): boolean {\n  return Boolean(target?.closest(DRAWER_CONTENT_SELECTOR));\n}\n\nfunction getBaseSwipeThreshold(element: HTMLElement, direction: SwipeDirection): number {\n  const size =\n    direction === 'left' || direction === 'right' ? element.offsetWidth : element.offsetHeight;\n  return Math.max(size * 0.5, MIN_SWIPE_THRESHOLD);\n}\n\nfunction isRangeInput(\n  target: EventTarget | null,\n  win: ReturnType<typeof ownerWindow>,\n): target is HTMLInputElement {\n  return target instanceof win.HTMLInputElement && target.type === 'range';\n}\n\nfunction isTextSelectionControl(\n  target: EventTarget | null,\n): target is HTMLInputElement | HTMLTextAreaElement {\n  if (!isElement(target)) {\n    return false;\n  }\n\n  return target.tagName === 'INPUT' || target.tagName === 'TEXTAREA';\n}\n\nfunction hasExpandedSelectionWithinTarget(selection: Selection, target: Element): boolean {\n  const anchorElement = isElement(selection.anchorNode)\n    ? selection.anchorNode\n    : selection.anchorNode?.parentElement;\n  const focusElement = isElement(selection.focusNode)\n    ? selection.focusNode\n    : selection.focusNode?.parentElement;\n\n  return (\n    selection.containsNode(target, true) ||\n    contains(target, anchorElement) ||\n    contains(target, focusElement)\n  );\n}\n\nfunction shouldIgnoreSwipeForTextSelection(doc: Document, rootElement: HTMLElement): boolean {\n  const activeElement = doc.activeElement;\n  const activeElementWithinRoot = Boolean(activeElement && contains(rootElement, activeElement));\n\n  if (activeElementWithinRoot && isTextSelectionControl(activeElement)) {\n    const { selectionStart, selectionEnd } = activeElement;\n    if (selectionStart != null && selectionEnd != null && selectionStart < selectionEnd) {\n      return true;\n    }\n  }\n\n  const selection = doc.getSelection?.();\n  if (!selection || selection.isCollapsed) {\n    return false;\n  }\n\n  return hasExpandedSelectionWithinTarget(selection, rootElement);\n}\n\nfunction isEventOnRangeInput(event: TouchEvent, win: ReturnType<typeof ownerWindow>): boolean {\n  const composedPath = event.composedPath();\n  if (composedPath) {\n    return composedPath.some((pathTarget) => isRangeInput(pathTarget, win));\n  }\n\n  return isRangeInput(event.target, win);\n}\n\nfunction isReactTouchEventOnRangeInput(event: React.TouchEvent<Element>): boolean {\n  return isEventOnRangeInput(event.nativeEvent, ownerWindow(event.currentTarget));\n}\n\nfunction updateTouchScrollPosition(touchState: TouchScrollState, touch: Touch): void {\n  touchState.lastX = touch.clientX;\n  touchState.lastY = touch.clientY;\n}\n\nfunction preserveNativeCrossAxisScrollOnMove(\n  touchState: TouchScrollState,\n  touch: Touch,\n  isVerticalScrollAxis: boolean,\n): boolean {\n  if (touchState.preserveNativeCrossAxisScroll) {\n    return true;\n  }\n\n  if (touchState.allowSwipe === true || !touchState.hasCrossAxisScrollableContent) {\n    return false;\n  }\n\n  const drawerAxisGestureDelta = isVerticalScrollAxis\n    ? touch.clientY - touchState.startY\n    : touch.clientX - touchState.startX;\n  const crossAxisGestureDelta = isVerticalScrollAxis\n    ? touch.clientX - touchState.startX\n    : touch.clientY - touchState.startY;\n  const absDrawerAxisGestureDelta = Math.abs(drawerAxisGestureDelta);\n  const absCrossAxisGestureDelta = Math.abs(crossAxisGestureDelta);\n\n  if (absCrossAxisGestureDelta < 6 || absCrossAxisGestureDelta <= absDrawerAxisGestureDelta + 2) {\n    return false;\n  }\n\n  touchState.preserveNativeCrossAxisScroll = true;\n  return true;\n}\n\nfunction hasScrollableContentOnAxis(scrollTarget: HTMLElement, axis: ScrollAxis): boolean {\n  return axis === 'vertical'\n    ? scrollTarget.scrollHeight > scrollTarget.clientHeight\n    : scrollTarget.scrollWidth > scrollTarget.clientWidth;\n}\n\nfunction getScrollMetrics(scrollTarget: HTMLElement, axis: ScrollAxis) {\n  if (axis === 'vertical') {\n    const max = Math.max(0, scrollTarget.scrollHeight - scrollTarget.clientHeight);\n    return { offset: scrollTarget.scrollTop, max };\n  }\n\n  const max = Math.max(0, scrollTarget.scrollWidth - scrollTarget.clientWidth);\n  return { offset: scrollTarget.scrollLeft, max };\n}\n\nfunction isAtSwipeStartEdge(\n  scrollTarget: HTMLElement,\n  axis: ScrollAxis,\n  direction: SwipeDirection,\n): boolean {\n  const { offset, max } = getScrollMetrics(scrollTarget, axis);\n  const dismissFromStartEdge = shouldDismissFromStartEdge(direction, axis);\n  if (dismissFromStartEdge === null) {\n    return false;\n  }\n\n  return dismissFromStartEdge ? offset <= 0 : offset >= max;\n}\n\nfunction canSwipeFromScrollEdgeOnMove(\n  scrollTarget: HTMLElement,\n  axis: ScrollAxis,\n  direction: SwipeDirection,\n  delta: number,\n): boolean {\n  const { offset, max } = getScrollMetrics(scrollTarget, axis);\n  const dismissFromStartEdge = shouldDismissFromStartEdge(direction, axis);\n  if (dismissFromStartEdge === null) {\n    return false;\n  }\n\n  const movingTowardDismiss = dismissFromStartEdge ? delta > 0 : delta < 0;\n  if (!movingTowardDismiss) {\n    return false;\n  }\n\n  return dismissFromStartEdge ? offset <= 0 : offset >= max;\n}\n\nfunction shouldDismissFromStartEdge(direction: SwipeDirection, axis: ScrollAxis): boolean | null {\n  if (axis === 'vertical') {\n    if (direction === 'down') {\n      return true;\n    }\n    if (direction === 'up') {\n      return false;\n    }\n    return null;\n  }\n\n  if (direction === 'right') {\n    return true;\n  }\n  if (direction === 'left') {\n    return false;\n  }\n\n  return null;\n}\n"
  },
  {
    "path": "packages/react/src/drawer/viewport/DrawerViewportContext.tsx",
    "content": "'use client';\nimport * as React from 'react';\n\ninterface DrawerViewportContextValue {\n  swiping: boolean;\n  getDragStyles: () => React.CSSProperties;\n  swipeStrength: number | null;\n  setSwipeDismissed: (dismissed: boolean) => void;\n}\n\nexport const DrawerViewportContext = React.createContext<DrawerViewportContextValue | null>(null);\n\nexport function useDrawerViewportContext(optional?: false): DrawerViewportContextValue;\nexport function useDrawerViewportContext(optional: true): DrawerViewportContextValue | null;\nexport function useDrawerViewportContext(optional?: boolean) {\n  const context = React.useContext(DrawerViewportContext);\n\n  if (optional === false && context === null) {\n    throw new Error(\n      'Base UI: DrawerViewportContext is missing. Drawer parts must be placed within <Drawer.Viewport>.',\n    );\n  }\n\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/drawer/viewport/DrawerViewportDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum DrawerViewportDataAttributes {\n  /**\n   * Present when the drawer is open.\n   */\n  open = CommonPopupDataAttributes.open,\n  /**\n   * Present when the drawer is closed.\n   */\n  closed = CommonPopupDataAttributes.closed,\n  /**\n   * Present when the drawer is animating in.\n   */\n  startingStyle = CommonPopupDataAttributes.startingStyle,\n  /**\n   * Present when the drawer is animating out.\n   */\n  endingStyle = CommonPopupDataAttributes.endingStyle,\n  /**\n   * Present when the drawer is nested within another drawer.\n   */\n  nested = 'data-nested',\n}\n"
  },
  {
    "path": "packages/react/src/field/control/FieldControl.spec.tsx",
    "content": "import * as React from 'react';\nimport { Field } from '@base-ui/react/field';\n\nfunction App() {\n  const ref = React.useRef<HTMLTextAreaElement>(null);\n  return <Field.Control ref={ref} render={<textarea />} />;\n}\n"
  },
  {
    "path": "packages/react/src/field/control/FieldControl.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport { createRenderer, fireEvent, screen } from '@mui/internal-test-utils';\nimport { Field } from '@base-ui/react/field';\nimport { describeConformance, isJSDOM } from '#test-utils';\n\ndescribe('<Field.Control />', () => {\n  const { render, renderToString } = createRenderer();\n  const { render: renderNonStrict } = createRenderer({ strict: false });\n\n  describeConformance(<Field.Control />, () => ({\n    refInstanceof: window.HTMLInputElement,\n    render(node) {\n      return render(<Field.Root>{node}</Field.Root>);\n    },\n  }));\n\n  it('avoids rerendering for uncontrolled input changes', async () => {\n    const renderCountRef = { current: 0 };\n\n    function RenderCountedControl() {\n      renderCountRef.current += 1;\n      return <Field.Control data-testid=\"control\" />;\n    }\n\n    renderNonStrict(\n      <Field.Root>\n        <RenderCountedControl />\n      </Field.Root>,\n    );\n\n    const control = screen.getByTestId('control');\n    const initialRenderCount = renderCountRef.current;\n\n    fireEvent.change(control, { target: { value: 'a' } });\n    const afterFirstChange = renderCountRef.current;\n\n    fireEvent.change(control, { target: { value: 'ab' } });\n    fireEvent.change(control, { target: { value: 'abc' } });\n\n    expect(renderCountRef.current).toBe(afterFirstChange);\n    expect(afterFirstChange).toBeLessThanOrEqual(initialRenderCount + 1);\n  });\n\n  it.skipIf(isJSDOM)('should sync focused state when autoFocus is used with SSR', async () => {\n    vi.spyOn(console, 'error')\n      .mockName('console.error')\n      .mockImplementation(() => {});\n\n    function App() {\n      return (\n        <Field.Root data-testid=\"root\">\n          <Field.Label data-testid=\"label\">Name</Field.Label>\n          <Field.Control autoFocus />\n        </Field.Root>\n      );\n    }\n\n    const { hydrate } = renderToString(<App />);\n\n    const control = screen.getByRole('textbox');\n    expect(control).toHaveAttribute('autofocus');\n\n    // Simulate focused by browser before hydration\n    control.focus();\n    expect(control).toBe(document.activeElement);\n\n    hydrate();\n\n    expect(screen.getByTestId('root')).toHaveAttribute('data-focused', '');\n    expect(control).toHaveAttribute('data-focused', '');\n    expect(screen.getByText('Name')).toHaveAttribute('data-focused', '');\n  });\n});\n"
  },
  {
    "path": "packages/react/src/field/control/FieldControl.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useControlled } from '@base-ui/utils/useControlled';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { ownerDocument } from '@base-ui/utils/owner';\nimport { type FieldRootState } from '../root/FieldRoot';\nimport { useFieldRootContext } from '../root/FieldRootContext';\nimport { useLabelableContext } from '../../labelable-provider/LabelableContext';\nimport { useLabelableId } from '../../labelable-provider/useLabelableId';\nimport { fieldValidityMapping } from '../utils/constants';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useField } from '../useField';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport type { BaseUIChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { activeElement } from '../../floating-ui-react/utils';\n\n/**\n * The form control to label and validate.\n * Renders an `<input>` element.\n *\n * You can omit this part and use any Base UI input component instead. For example,\n * [Input](https://base-ui.com/react/components/input), [Checkbox](https://base-ui.com/react/components/checkbox),\n * or [Select](https://base-ui.com/react/components/select), among others, will work with Field out of the box.\n *\n * Documentation: [Base UI Field](https://base-ui.com/react/components/field)\n */\nexport const FieldControl = React.forwardRef(function FieldControl(\n  componentProps: FieldControl.Props,\n  forwardedRef: React.ForwardedRef<HTMLElement>,\n) {\n  const {\n    render,\n    className,\n    id: idProp,\n    name: nameProp,\n    value: valueProp,\n    disabled: disabledProp = false,\n    onValueChange,\n    defaultValue,\n    autoFocus = false,\n    ...elementProps\n  } = componentProps;\n\n  const {\n    state: fieldState,\n    name: fieldName,\n    disabled: fieldDisabled,\n    setTouched,\n    setDirty,\n    validityData,\n    setFocused,\n    setFilled,\n    validationMode,\n    validation,\n  } = useFieldRootContext();\n\n  const disabled = fieldDisabled || disabledProp;\n  const name = fieldName ?? nameProp;\n\n  const state: FieldControlState = {\n    ...fieldState,\n    disabled,\n  };\n\n  const { labelId } = useLabelableContext();\n\n  const id = useLabelableId({ id: idProp });\n\n  useIsoLayoutEffect(() => {\n    const hasExternalValue = valueProp != null;\n    if (validation.inputRef.current?.value || (hasExternalValue && valueProp !== '')) {\n      setFilled(true);\n    } else if (hasExternalValue && valueProp === '') {\n      setFilled(false);\n    }\n  }, [validation.inputRef, setFilled, valueProp]);\n\n  const inputRef = React.useRef<HTMLElement>(null);\n\n  useIsoLayoutEffect(() => {\n    if (autoFocus && inputRef.current === activeElement(ownerDocument(inputRef.current))) {\n      setFocused(true);\n    }\n  }, [autoFocus, setFocused]);\n\n  const [valueUnwrapped] = useControlled({\n    controlled: valueProp,\n    default: defaultValue,\n    name: 'FieldControl',\n    state: 'value',\n  });\n\n  const isControlled = valueProp !== undefined;\n  const value = isControlled ? valueUnwrapped : undefined;\n\n  useField({\n    id,\n    name,\n    commit: validation.commit,\n    value,\n    getValue: () => validation.inputRef.current?.value,\n    controlRef: validation.inputRef,\n  });\n\n  const element = useRenderElement('input', componentProps, {\n    ref: [forwardedRef, inputRef],\n    state,\n    props: [\n      {\n        id,\n        disabled,\n        name,\n        ref: validation.inputRef,\n        'aria-labelledby': labelId,\n        autoFocus,\n        ...(isControlled ? { value } : { defaultValue }),\n        onChange(event) {\n          const inputValue = event.currentTarget.value;\n          onValueChange?.(inputValue, createChangeEventDetails(REASONS.none, event.nativeEvent));\n          setDirty(inputValue !== validityData.initialValue);\n          setFilled(inputValue !== '');\n        },\n        onFocus() {\n          setFocused(true);\n        },\n        onBlur(event) {\n          setTouched(true);\n          setFocused(false);\n\n          if (validationMode === 'onBlur') {\n            validation.commit(event.currentTarget.value);\n          }\n        },\n        onKeyDown(event) {\n          if (event.currentTarget.tagName === 'INPUT' && event.key === 'Enter') {\n            setTouched(true);\n            validation.commit(event.currentTarget.value);\n          }\n        },\n      },\n      validation.getInputValidationProps(),\n      elementProps,\n    ],\n    stateAttributesMapping: fieldValidityMapping,\n  });\n\n  return element;\n});\n\nexport interface FieldControlState extends FieldRootState {}\n\nexport interface FieldControlProps extends BaseUIComponentProps<'input', FieldControlState> {\n  /**\n   * Callback fired when the `value` changes. Use when controlled.\n   */\n  onValueChange?:\n    | ((value: string, eventDetails: FieldControl.ChangeEventDetails) => void)\n    | undefined;\n  defaultValue?: React.ComponentProps<'input'>['defaultValue'] | undefined;\n}\n\nexport type FieldControlChangeEventReason = typeof REASONS.none;\n\nexport type FieldControlChangeEventDetails =\n  BaseUIChangeEventDetails<FieldControl.ChangeEventReason>;\n\nexport namespace FieldControl {\n  export type State = FieldControlState;\n  export type Props = FieldControlProps;\n  export type ChangeEventReason = FieldControlChangeEventReason;\n  export type ChangeEventDetails = FieldControlChangeEventDetails;\n}\n"
  },
  {
    "path": "packages/react/src/field/control/FieldControlDataAttributes.ts",
    "content": "export enum FieldControlDataAttributes {\n  /**\n   * Present when the field is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the field is in valid state.\n   */\n  valid = 'data-valid',\n  /**\n   * Present when the field is in invalid state.\n   */\n  invalid = 'data-invalid',\n  /**\n   * Present when the field has been touched.\n   */\n  touched = 'data-touched',\n  /**\n   * Present when the field's value has changed.\n   */\n  dirty = 'data-dirty',\n  /**\n   * Present when the field is filled.\n   */\n  filled = 'data-filled',\n  /**\n   * Present when the field control is focused.\n   */\n  focused = 'data-focused',\n}\n"
  },
  {
    "path": "packages/react/src/field/description/FieldDescription.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { Field } from '@base-ui/react/field';\nimport { createRenderer, screen } from '@mui/internal-test-utils';\nimport { describeConformance } from '../../../test/describeConformance';\n\ndescribe('<Field.Description />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Field.Description />, () => ({\n    refInstanceof: window.HTMLParagraphElement,\n    render(node) {\n      return render(<Field.Root>{node}</Field.Root>);\n    },\n  }));\n\n  it('should set aria-describedby on the control automatically', () => {\n    render(\n      <Field.Root>\n        <Field.Control />\n        <Field.Description>Message</Field.Description>\n      </Field.Root>,\n    );\n\n    expect(screen.getByRole('textbox')).toHaveAttribute(\n      'aria-describedby',\n      screen.getByText('Message').id,\n    );\n  });\n});\n"
  },
  {
    "path": "packages/react/src/field/description/FieldDescription.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { type FieldRootState } from '../root/FieldRoot';\nimport { useFieldRootContext } from '../root/FieldRootContext';\nimport { useLabelableContext } from '../../labelable-provider/LabelableContext';\nimport { fieldValidityMapping } from '../utils/constants';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useBaseUiId } from '../../utils/useBaseUiId';\nimport { useRenderElement } from '../../utils/useRenderElement';\n\n/**\n * A paragraph with additional information about the field.\n * Renders a `<p>` element.\n *\n * Documentation: [Base UI Field](https://base-ui.com/react/components/field)\n */\nexport const FieldDescription = React.forwardRef(function FieldDescription(\n  componentProps: FieldDescription.Props,\n  forwardedRef: React.ForwardedRef<HTMLParagraphElement>,\n) {\n  const { render, id: idProp, className, ...elementProps } = componentProps;\n\n  const id = useBaseUiId(idProp);\n\n  const fieldRootContext = useFieldRootContext(false);\n  const { setMessageIds } = useLabelableContext();\n\n  useIsoLayoutEffect(() => {\n    if (!id) {\n      return undefined;\n    }\n\n    setMessageIds((v) => v.concat(id));\n\n    return () => {\n      setMessageIds((v) => v.filter((item) => item !== id));\n    };\n  }, [id, setMessageIds]);\n\n  const element = useRenderElement('p', componentProps, {\n    ref: forwardedRef,\n    state: fieldRootContext.state,\n    props: [{ id }, elementProps],\n    stateAttributesMapping: fieldValidityMapping,\n  });\n\n  return element;\n});\n\nexport interface FieldDescriptionState extends FieldRootState {}\n\nexport interface FieldDescriptionProps extends BaseUIComponentProps<'p', FieldDescriptionState> {}\n\nexport namespace FieldDescription {\n  export type State = FieldDescriptionState;\n  export type Props = FieldDescriptionProps;\n}\n"
  },
  {
    "path": "packages/react/src/field/description/FieldDescriptionDataAttributes.ts",
    "content": "export enum FieldDescriptionDataAttributes {\n  /**\n   * Present when the field is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the field is in valid state.\n   */\n  valid = 'data-valid',\n  /**\n   * Present when the field is in invalid state.\n   */\n  invalid = 'data-invalid',\n  /**\n   * Present when the field has been touched.\n   */\n  touched = 'data-touched',\n  /**\n   * Present when the field's value has changed.\n   */\n  dirty = 'data-dirty',\n  /**\n   * Present when the field is filled.\n   */\n  filled = 'data-filled',\n  /**\n   * Present when the field control is focused.\n   */\n  focused = 'data-focused',\n}\n"
  },
  {
    "path": "packages/react/src/field/error/FieldError.test.tsx",
    "content": "import { expect } from 'vitest';\nimport * as React from 'react';\nimport { fireEvent, screen, waitFor } from '@mui/internal-test-utils';\nimport { Field } from '@base-ui/react/field';\nimport { Form } from '@base-ui/react/form';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\n\ndescribe('<Field.Error />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Field.Error match />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(<Field.Root invalid>{node}</Field.Root>);\n    },\n  }));\n\n  it('should set aria-describedby on the control automatically', async () => {\n    await render(\n      <Field.Root invalid>\n        <Field.Control />\n        <Field.Error match>Message</Field.Error>\n      </Field.Root>,\n    );\n\n    expect(screen.getByRole('textbox')).toHaveAttribute(\n      'aria-describedby',\n      screen.getByText('Message').id,\n    );\n  });\n\n  it('should show error messages by default', async () => {\n    await render(\n      <Form>\n        <Field.Root>\n          <Field.Control required />\n          <Field.Error>Message</Field.Error>\n        </Field.Root>\n        <button type=\"submit\">submit</button>\n      </Form>,\n    );\n\n    expect(screen.queryByText('Message')).toBe(null);\n\n    const input = screen.getByRole<HTMLInputElement>('textbox');\n\n    fireEvent.focus(input);\n    fireEvent.change(input, { target: { value: 'a' } });\n    fireEvent.change(input, { target: { value: '' } });\n    fireEvent.blur(input);\n    expect(screen.queryByText('Message')).toBe(null);\n\n    fireEvent.click(screen.getByText('submit'));\n    expect(screen.queryByText('Message')).not.toBe(null);\n  });\n\n  describe('prop: match', () => {\n    it('should only render when `match` matches constraint validation', async () => {\n      await render(\n        <Form>\n          <Field.Root>\n            <Field.Control required minLength={2} />\n            <Field.Error match=\"valueMissing\">Message</Field.Error>\n          </Field.Root>\n          <button type=\"submit\">submit</button>\n        </Form>,\n      );\n\n      expect(screen.queryByText('Message')).toBe(null);\n\n      fireEvent.click(screen.getByText('submit'));\n      expect(screen.queryByText('Message')).not.toBe(null);\n\n      const input = screen.getByRole<HTMLInputElement>('textbox');\n\n      fireEvent.focus(input);\n      fireEvent.change(input, { target: { value: 'a' } });\n      expect(screen.queryByText('Message')).toBe(null);\n\n      fireEvent.change(input, { target: { value: '' } });\n      expect(screen.queryByText('Message')).not.toBe(null);\n    });\n\n    it('should show custom errors', async () => {\n      await render(\n        <Form>\n          <Field.Root validate={() => 'error'}>\n            <Field.Control />\n            <Field.Error match=\"customError\">Message</Field.Error>\n          </Field.Root>\n          <button type=\"submit\">submit</button>\n        </Form>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('textbox');\n\n      fireEvent.focus(input);\n      fireEvent.change(input, { target: { value: 'a' } });\n      fireEvent.blur(input);\n      expect(screen.queryByText('Message')).toBe(null);\n\n      fireEvent.click(screen.getByText('submit'));\n      expect(screen.queryByText('Message')).not.toBe(null);\n    });\n\n    it('always renders the error message when `match` is true', async () => {\n      await render(\n        <Field.Root>\n          <Field.Control required />\n          <Field.Error match>Message</Field.Error>\n        </Field.Root>,\n      );\n\n      expect(screen.queryByText('Message')).not.toBe(null);\n    });\n  });\n\n  describe.skipIf(isJSDOM)('animations', () => {\n    afterEach(() => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = true;\n    });\n\n    it('triggers enter animation via data-starting-style when mounting', async () => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n      let transitionFinished = false;\n      function notifyTransitionFinished() {\n        transitionFinished = true;\n      }\n\n      const style = `\n        .animation-test-error {\n          transition: opacity 1ms;\n        }\n\n        .animation-test-error[data-starting-style],\n        .animation-test-error[data-ending-style] {\n          opacity: 0;\n        }\n      `;\n\n      function Test() {\n        const [showError, setShowError] = React.useState(false);\n\n        function handleShowError() {\n          setShowError(true);\n        }\n\n        return (\n          <div>\n            {/* eslint-disable-next-line react/no-danger */}\n            <style dangerouslySetInnerHTML={{ __html: style }} />\n            <button onClick={handleShowError}>Show</button>\n            <Field.Root>\n              <Field.Control required />\n              <Field.Error\n                className=\"animation-test-error\"\n                data-testid=\"error\"\n                match={showError}\n                onTransitionEnd={notifyTransitionFinished}\n              >\n                Message\n              </Field.Error>\n            </Field.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<Test />);\n      expect(screen.queryByTestId('error')).toBe(null);\n\n      await user.click(screen.getByText('Show'));\n\n      await waitFor(() => {\n        expect(transitionFinished).toBe(true);\n      });\n\n      expect(screen.getByTestId('error')).not.toBe(null);\n    });\n\n    it('applies data-ending-style before unmount', async () => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n      const style = `\n        @keyframes test-anim {\n          to {\n            opacity: 0;\n          }\n        }\n\n        .animation-test-error[data-ending-style] {\n          animation: test-anim 1ms;\n        }\n      `;\n\n      function Test() {\n        const [showError, setShowError] = React.useState(true);\n\n        function handleHideError() {\n          setShowError(false);\n        }\n\n        return (\n          <div>\n            {/* eslint-disable-next-line react/no-danger */}\n            <style dangerouslySetInnerHTML={{ __html: style }} />\n            <button onClick={handleHideError}>Hide</button>\n            <Field.Root>\n              <Field.Control required />\n              <Field.Error className=\"animation-test-error\" data-testid=\"error\" match={showError}>\n                Message\n              </Field.Error>\n            </Field.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<Test />);\n      expect(screen.getByTestId('error')).not.toBe(null);\n\n      await user.click(screen.getByText('Hide'));\n\n      await waitFor(() => {\n        const error = screen.queryByTestId('error');\n        expect(error).not.toBe(null);\n        expect(error).toHaveAttribute('data-ending-style');\n      });\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('error')).toBe(null);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/field/error/FieldError.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { type FieldRootState } from '../root/FieldRoot';\nimport { useFieldRootContext } from '../root/FieldRootContext';\nimport { useLabelableContext } from '../../labelable-provider/LabelableContext';\nimport { fieldValidityMapping } from '../utils/constants';\nimport { useFormContext } from '../../form/FormContext';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport type { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useBaseUiId } from '../../utils/useBaseUiId';\nimport { useOpenChangeComplete } from '../../utils/useOpenChangeComplete';\nimport { transitionStatusMapping } from '../../utils/stateAttributesMapping';\nimport { type TransitionStatus, useTransitionStatus } from '../../utils/useTransitionStatus';\n\nconst stateAttributesMapping: StateAttributesMapping<FieldErrorState> = {\n  ...fieldValidityMapping,\n  ...transitionStatusMapping,\n};\n\n/**\n * An error message displayed if the field control fails validation.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Field](https://base-ui.com/react/components/field)\n */\nexport const FieldError = React.forwardRef(function FieldError(\n  componentProps: FieldError.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, id: idProp, className, match, ...elementProps } = componentProps;\n\n  const id = useBaseUiId(idProp);\n\n  const { validityData, state: fieldState, name } = useFieldRootContext(false);\n  const { setMessageIds } = useLabelableContext();\n\n  const { errors } = useFormContext();\n\n  const formError = name ? errors[name] : null;\n\n  let rendered = false;\n  if (formError || match === true) {\n    rendered = true;\n  } else if (match) {\n    rendered = Boolean(validityData.state[match]);\n  } else {\n    rendered = validityData.state.valid === false;\n  }\n\n  const { mounted, transitionStatus, setMounted } = useTransitionStatus(rendered);\n\n  useIsoLayoutEffect(() => {\n    if (!rendered || !id) {\n      return undefined;\n    }\n\n    setMessageIds((v) => v.concat(id));\n\n    return () => {\n      setMessageIds((v) => v.filter((item) => item !== id));\n    };\n  }, [rendered, id, setMessageIds]);\n\n  const errorRef = React.useRef<HTMLDivElement | null>(null);\n  const [lastRenderedMessage, setLastRenderedMessage] = React.useState<React.ReactNode>(null);\n  const [lastRenderedMessageKey, setLastRenderedMessageKey] = React.useState<string | null>(null);\n\n  const errorMessage =\n    formError ||\n    (validityData.errors.length > 1 ? (\n      <ul>\n        {validityData.errors.map((message) => (\n          <li key={message}>{message}</li>\n        ))}\n      </ul>\n    ) : (\n      validityData.error\n    ));\n\n  let errorKey = validityData.error;\n  if (formError != null) {\n    errorKey = Array.isArray(formError) ? JSON.stringify(formError) : formError;\n  } else if (validityData.errors.length > 1) {\n    errorKey = JSON.stringify(validityData.errors);\n  }\n\n  if (rendered && errorKey !== lastRenderedMessageKey) {\n    setLastRenderedMessageKey(errorKey);\n    setLastRenderedMessage(errorMessage);\n  }\n\n  useOpenChangeComplete({\n    open: rendered,\n    ref: errorRef,\n    onComplete() {\n      if (!rendered) {\n        setMounted(false);\n      }\n    },\n  });\n\n  const state: FieldErrorState = {\n    ...fieldState,\n    transitionStatus,\n  };\n\n  const element = useRenderElement('div', componentProps, {\n    ref: [forwardedRef, errorRef],\n    state,\n    props: [\n      {\n        id,\n        children: rendered ? errorMessage : lastRenderedMessage,\n      },\n      elementProps,\n    ],\n    stateAttributesMapping,\n    enabled: mounted,\n  });\n\n  if (!mounted) {\n    return null;\n  }\n\n  return element;\n});\n\nexport interface FieldErrorState extends FieldRootState {\n  /**\n   * The transition status of the component.\n   */\n  transitionStatus: TransitionStatus;\n}\n\nexport interface FieldErrorProps extends BaseUIComponentProps<'div', FieldErrorState> {\n  /**\n   * Determines whether to show the error message according to the field’s\n   * [ValidityState](https://developer.mozilla.org/en-US/docs/Web/API/ValidityState).\n   * Specifying `true` will always show the error message, and lets external libraries\n   * control the visibility.\n   */\n  match?: boolean | keyof ValidityState | undefined;\n}\n\nexport namespace FieldError {\n  export type State = FieldErrorState;\n  export type Props = FieldErrorProps;\n}\n"
  },
  {
    "path": "packages/react/src/field/error/FieldErrorDataAttributes.ts",
    "content": "import { TransitionStatusDataAttributes } from '../../utils/stateAttributesMapping';\n\nexport enum FieldErrorDataAttributes {\n  /**\n   * Present when the field is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the field is in valid state.\n   */\n  valid = 'data-valid',\n  /**\n   * Present when the field is in invalid state.\n   */\n  invalid = 'data-invalid',\n  /**\n   * Present when the field has been touched.\n   */\n  touched = 'data-touched',\n  /**\n   * Present when the field's value has changed.\n   */\n  dirty = 'data-dirty',\n  /**\n   * Present when the field is filled.\n   */\n  filled = 'data-filled',\n  /**\n   * Present when the field control is focused.\n   */\n  focused = 'data-focused',\n  /**\n   * Present when the error message is animating in.\n   */\n  startingStyle = TransitionStatusDataAttributes.startingStyle,\n  /**\n   * Present when the error message is animating out.\n   */\n  endingStyle = TransitionStatusDataAttributes.endingStyle,\n}\n"
  },
  {
    "path": "packages/react/src/field/index.parts.ts",
    "content": "export { FieldRoot as Root } from './root/FieldRoot';\nexport { FieldLabel as Label } from './label/FieldLabel';\nexport { FieldError as Error } from './error/FieldError';\nexport { FieldDescription as Description } from './description/FieldDescription';\nexport { FieldControl as Control } from './control/FieldControl';\nexport { FieldValidity as Validity } from './validity/FieldValidity';\nexport { FieldItem as Item } from './item/FieldItem';\n\nexport type { FieldValidityData as ValidityData } from './root/FieldRoot';\n"
  },
  {
    "path": "packages/react/src/field/index.ts",
    "content": "export * as Field from './index.parts';\n\nexport type * from './root/FieldRoot';\nexport type * from './label/FieldLabel';\nexport type * from './description/FieldDescription';\nexport type * from './error/FieldError';\nexport type * from './control/FieldControl';\nexport type * from './validity/FieldValidity';\nexport type * from './item/FieldItem';\n"
  },
  {
    "path": "packages/react/src/field/item/FieldItem.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { Field } from '@base-ui/react/field';\nimport { Checkbox } from '@base-ui/react/checkbox';\nimport { CheckboxGroup } from '@base-ui/react/checkbox-group';\nimport { Radio } from '@base-ui/react/radio';\nimport { RadioGroup } from '@base-ui/react/radio-group';\nimport { createRenderer, screen } from '@mui/internal-test-utils';\nimport { describeConformance } from '../../../test/describeConformance';\n\ndescribe('<Field.Item />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Field.Item />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(<Field.Root>{node}</Field.Root>);\n    },\n  }));\n\n  describe('prop: disabled', () => {\n    it('disables a wrapped checkbox', async () => {\n      const onValueChange = vi.fn();\n      const { user } = await render(\n        <Field.Root name=\"apple\">\n          <CheckboxGroup defaultValue={[]} onValueChange={onValueChange}>\n            <Field.Item disabled>\n              <Checkbox.Root value=\"fuji-apple\" />\n            </Field.Item>\n            <Field.Item>\n              <Checkbox.Root value=\"gala-apple\" />\n            </Field.Item>\n          </CheckboxGroup>\n        </Field.Root>,\n      );\n      const [checkbox1, checkbox2] = screen.getAllByRole('checkbox');\n      await user.click(checkbox1);\n      expect(onValueChange.mock.calls.length).toBe(0);\n      await user.click(checkbox2);\n      expect(onValueChange.mock.calls.length).toBe(1);\n    });\n\n    it('disables a wrapped radio', async () => {\n      const onValueChange = vi.fn();\n      const { user } = await render(\n        <Field.Root name=\"apple\">\n          <RadioGroup defaultValue=\"\" onValueChange={onValueChange}>\n            <Field.Item disabled>\n              <Radio.Root value=\"fuji-apple\" />\n            </Field.Item>\n            <Field.Item>\n              <Radio.Root value=\"gala-apple\" />\n            </Field.Item>\n          </RadioGroup>\n        </Field.Root>,\n      );\n      const [radio1, radio2] = screen.getAllByRole('radio');\n      await user.click(radio1);\n      expect(onValueChange.mock.calls.length).toBe(0);\n      await user.click(radio2);\n      expect(onValueChange.mock.calls.length).toBe(1);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/field/item/FieldItem.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { type FieldRootState } from '../root/FieldRoot';\nimport { useFieldRootContext } from '../root/FieldRootContext';\nimport { fieldValidityMapping } from '../utils/constants';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { FieldItemContext } from './FieldItemContext';\nimport { LabelableProvider } from '../../labelable-provider';\nimport { useCheckboxGroupContext } from '../../checkbox-group/CheckboxGroupContext';\n\n/**\n * Groups individual items in a checkbox group or radio group with a label and description.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Field](https://base-ui.com/react/components/field)\n */\nexport const FieldItem = React.forwardRef(function FieldItem(\n  componentProps: FieldItem.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, disabled: disabledProp = false, ...elementProps } = componentProps;\n\n  const { state, disabled: rootDisabled } = useFieldRootContext(false);\n\n  const disabled = rootDisabled || disabledProp;\n\n  const checkboxGroupContext = useCheckboxGroupContext();\n  // checkboxGroupContext.parent is truthy even if no parent checkbox is involved\n  const parentId = checkboxGroupContext?.parent.id;\n  // this a more reliable check\n  const hasParentCheckbox = checkboxGroupContext?.allValues !== undefined;\n\n  const controlId = hasParentCheckbox ? parentId : undefined;\n\n  const fieldItemContext: FieldItemContext = React.useMemo(() => ({ disabled }), [disabled]);\n\n  const element = useRenderElement('div', componentProps, {\n    ref: forwardedRef,\n    state,\n    props: elementProps,\n    stateAttributesMapping: fieldValidityMapping,\n  });\n\n  return (\n    <LabelableProvider controlId={controlId}>\n      <FieldItemContext.Provider value={fieldItemContext}>{element}</FieldItemContext.Provider>\n    </LabelableProvider>\n  );\n});\n\nexport interface FieldItemState extends FieldRootState {}\n\nexport interface FieldItemProps extends BaseUIComponentProps<'div', FieldItemState> {\n  /**\n   * Whether the wrapped control should ignore user interaction.\n   * The `disabled` prop on `<Field.Root>` takes precedence over this.\n   * @default false\n   */\n  disabled?: boolean | undefined;\n}\n\nexport namespace FieldItem {\n  export type State = FieldItemState;\n  export type Props = FieldItemProps;\n}\n"
  },
  {
    "path": "packages/react/src/field/item/FieldItemContext.ts",
    "content": "'use client';\nimport * as React from 'react';\n\nexport interface FieldItemContext {\n  disabled: boolean;\n}\n\nexport const FieldItemContext = React.createContext<FieldItemContext>({ disabled: false });\n\nexport function useFieldItemContext() {\n  const context = React.useContext(FieldItemContext);\n\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/field/label/FieldLabel.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { Field } from '@base-ui/react/field';\nimport { screen } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Field.Label />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Field.Label />, () => ({\n    refInstanceof: window.HTMLLabelElement,\n    testRenderPropWith: 'label',\n    render(node) {\n      return render(<Field.Root>{node}</Field.Root>);\n    },\n  }));\n\n  it('should set htmlFor referencing the control automatically', async () => {\n    await render(\n      <Field.Root data-testid=\"field\">\n        <Field.Control />\n        <Field.Label data-testid=\"label\">Label</Field.Label>\n      </Field.Root>,\n    );\n\n    expect(screen.getByTestId('label')).toHaveAttribute('for', screen.getByRole('textbox').id);\n  });\n\n  it('when nativeLabel={false}, clicking focuses the associated control', async () => {\n    const { user } = await render(\n      <Field.Root>\n        <Field.Control data-testid=\"control\" />\n        <Field.Label nativeLabel={false} render={<div />} data-testid=\"label\">\n          Label\n        </Field.Label>\n      </Field.Root>,\n    );\n\n    const label = screen.getByTestId('label');\n    const control = screen.getByTestId('control');\n\n    expect(label).not.toHaveAttribute('for');\n\n    await user.click(label);\n    expect(control).toHaveFocus();\n  });\n\n  describe('dev warnings', () => {\n    it('does not warn by default', async () => {\n      const errorSpy = vi\n        .spyOn(console, 'error')\n        .mockName('console.error')\n        .mockImplementation(() => {});\n\n      await render(\n        <Field.Root>\n          <Field.Control />\n          <Field.Label>Label</Field.Label>\n        </Field.Root>,\n      );\n\n      expect(errorSpy).not.toHaveBeenCalled();\n      errorSpy.mockRestore();\n    });\n\n    it('errors if nativeLabel=true but ref is not a label', async () => {\n      const errorSpy = vi\n        .spyOn(console, 'error')\n        .mockName('console.error')\n        .mockImplementation(() => {});\n\n      await render(\n        <Field.Root>\n          <Field.Control />\n          <Field.Label nativeLabel render={<div />}>\n            Label\n          </Field.Label>\n        </Field.Root>,\n      );\n\n      expect(errorSpy).toHaveBeenCalledTimes(1);\n      expect(errorSpy).toHaveBeenCalledWith(\n        expect.stringContaining(\n          'Base UI: <Field.Label> expected a <label> element because the `nativeLabel` prop is true. ' +\n            'Rendering a non-<label> disables native label association, so `htmlFor` will not ' +\n            'work. Use a real <label> in the `render` prop, or set `nativeLabel` to `false`.',\n        ),\n      );\n      errorSpy.mockRestore();\n    });\n\n    it('errors if nativeLabel=false but ref is a label', async () => {\n      const errorSpy = vi\n        .spyOn(console, 'error')\n        .mockName('console.error')\n        .mockImplementation(() => {});\n\n      await render(\n        <Field.Root>\n          <Field.Control />\n          <Field.Label nativeLabel={false}>Label</Field.Label>\n        </Field.Root>,\n      );\n\n      expect(errorSpy).toHaveBeenCalledTimes(1);\n      expect(errorSpy).toHaveBeenCalledWith(\n        expect.stringContaining(\n          'Base UI: <Field.Label> expected a non-<label> element because the `nativeLabel` prop is false. ' +\n            'Rendering a <label> assumes native label behavior while Base UI treats it as ' +\n            'non-native, which can cause unexpected pointer behavior. Use a non-<label> in the ' +\n            '`render` prop, or set `nativeLabel` to `true`.',\n        ),\n      );\n      errorSpy.mockRestore();\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/field/label/FieldLabel.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { error } from '@base-ui/utils/error';\nimport { SafeReact } from '@base-ui/utils/safeReact';\nimport type { FieldRootState } from '../root/FieldRoot';\nimport { useFieldRootContext } from '../root/FieldRootContext';\nimport { fieldValidityMapping } from '../utils/constants';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useLabelableContext } from '../../labelable-provider/LabelableContext';\nimport { useLabel } from '../../labelable-provider/useLabel';\n\n/**\n * An accessible label that is automatically associated with the field control.\n * Renders a `<label>` element.\n *\n * Documentation: [Base UI Field](https://base-ui.com/react/components/field)\n */\nexport const FieldLabel = React.forwardRef(function FieldLabel(\n  componentProps: FieldLabel.Props,\n  forwardedRef: React.ForwardedRef<HTMLElement>,\n) {\n  const { render, className, id: idProp, nativeLabel = true, ...elementProps } = componentProps;\n\n  const fieldRootContext = useFieldRootContext(false);\n  const { labelId } = useLabelableContext();\n\n  const labelRef = React.useRef<HTMLElement | null>(null);\n  const labelProps = useLabel({\n    id: labelId ?? idProp,\n    native: nativeLabel,\n  });\n\n  if (process.env.NODE_ENV !== 'production') {\n    // eslint-disable-next-line react-hooks/rules-of-hooks\n    React.useEffect(() => {\n      if (!labelRef.current) {\n        return;\n      }\n\n      const isLabelTag = labelRef.current.tagName === 'LABEL';\n\n      if (nativeLabel) {\n        if (!isLabelTag) {\n          const ownerStackMessage = SafeReact.captureOwnerStack?.() || '';\n          const message =\n            '<Field.Label> expected a <label> element because the `nativeLabel` prop is true. ' +\n            'Rendering a non-<label> disables native label association, so `htmlFor` will not ' +\n            'work. Use a real <label> in the `render` prop, or set `nativeLabel` to `false`.';\n          error(`${message}${ownerStackMessage}`);\n        }\n      } else if (isLabelTag) {\n        const ownerStackMessage = SafeReact.captureOwnerStack?.() || '';\n        const message =\n          '<Field.Label> expected a non-<label> element because the `nativeLabel` prop is false. ' +\n          'Rendering a <label> assumes native label behavior while Base UI treats it as ' +\n          'non-native, which can cause unexpected pointer behavior. Use a non-<label> in the ' +\n          '`render` prop, or set `nativeLabel` to `true`.';\n        error(`${message}${ownerStackMessage}`);\n      }\n    }, [nativeLabel]);\n  }\n\n  const element = useRenderElement('label', componentProps, {\n    ref: [forwardedRef, labelRef],\n    state: fieldRootContext.state,\n    props: [labelProps, elementProps],\n    stateAttributesMapping: fieldValidityMapping,\n  });\n\n  return element;\n});\n\nexport interface FieldLabelState extends FieldRootState {}\n\nexport interface FieldLabelProps extends BaseUIComponentProps<'label', FieldLabelState> {\n  /**\n   * Whether the component renders a native `<label>` element when replacing it via the `render` prop.\n   * Set to `false` if the rendered element is not a label (e.g. `<div>`).\n   *\n   * This is useful to avoid inheriting label behaviors on `<button>` controls (such as `<Select.Trigger>` and `<Combobox.Trigger>`), including avoiding `:hover` on the button when hovering the label, and preventing clicks on the label from firing on the button.\n   * @default true\n   */\n  nativeLabel?: boolean | undefined;\n}\n\nexport namespace FieldLabel {\n  export type State = FieldLabelState;\n  export type Props = FieldLabelProps;\n}\n"
  },
  {
    "path": "packages/react/src/field/label/FieldLabelDataAttributes.ts",
    "content": "export enum FieldLabelDataAttributes {\n  /**\n   * Present when the field is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the field is in valid state.\n   */\n  valid = 'data-valid',\n  /**\n   * Present when the field is in invalid state.\n   */\n  invalid = 'data-invalid',\n  /**\n   * Present when the field has been touched.\n   */\n  touched = 'data-touched',\n  /**\n   * Present when the field's value has changed.\n   */\n  dirty = 'data-dirty',\n  /**\n   * Present when the field is filled.\n   */\n  filled = 'data-filled',\n  /**\n   * Present when the field control is focused.\n   */\n  focused = 'data-focused',\n}\n"
  },
  {
    "path": "packages/react/src/field/root/FieldRoot.test.tsx",
    "content": "import { vi, expect } from 'vitest';\nimport * as React from 'react';\nimport { Form } from '@base-ui/react/form';\nimport { NumberField } from '@base-ui/react/number-field';\nimport { Radio } from '@base-ui/react/radio';\nimport { RadioGroup } from '@base-ui/react/radio-group';\nimport { Select } from '@base-ui/react/select';\nimport { Checkbox } from '@base-ui/react/checkbox';\nimport { CheckboxGroup } from '@base-ui/react/checkbox-group';\nimport { Switch } from '@base-ui/react/switch';\nimport { Slider } from '@base-ui/react/slider';\nimport { Field } from '@base-ui/react/field';\nimport {\n  act,\n  fireEvent,\n  flushMicrotasks,\n  reactMajor,\n  screen,\n  waitFor,\n} from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\nimport { LabelableProvider } from '../../labelable-provider';\n\ndescribe('<Field.Root />', () => {\n  const { render, renderToString } = createRenderer();\n  const { render: renderStrict } = createRenderer({ strict: true });\n\n  describeConformance(<Field.Root />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render,\n  }));\n\n  it('updates label association when replacing one control with another', async () => {\n    function TestCase() {\n      const [showB, setShowB] = React.useState(false);\n\n      return (\n        <React.Fragment>\n          <Field.Root>\n            <Field.Label>Label</Field.Label>\n            {showB ? (\n              <Field.Control key=\"b\" id=\"control-b\" />\n            ) : (\n              <Field.Control key=\"a\" id=\"control-a\" />\n            )}\n          </Field.Root>\n          <button type=\"button\" onClick={() => setShowB(true)}>\n            Toggle\n          </button>\n        </React.Fragment>\n      );\n    }\n\n    await render(<TestCase />);\n\n    const label = screen.getByText('Label');\n    expect(label).toHaveAttribute('for', 'control-a');\n\n    fireEvent.click(screen.getByRole('button', { name: 'Toggle' }));\n\n    await waitFor(() => {\n      expect(label).toHaveAttribute('for', 'control-b');\n    });\n  });\n\n  it('preserves null initial control ids', async () => {\n    await render(\n      <Field.Root>\n        <LabelableProvider controlId={null}>\n          <Field.Label>Label</Field.Label>\n          <Field.Control data-testid=\"control\" />\n        </LabelableProvider>\n      </Field.Root>,\n    );\n\n    const label = screen.getByText('Label');\n    const control = screen.getByTestId('control');\n\n    expect(label).not.toHaveAttribute('for');\n    expect(control.getAttribute('id')).not.toBe(null);\n  });\n\n  it('updates label associations when the control id changes', async () => {\n    function TestCase() {\n      const [controlId, setControlId] = React.useState('control-a');\n\n      return (\n        <React.Fragment>\n          <Field.Root>\n            <Field.Label>Label</Field.Label>\n            <Field.Control id={controlId} />\n          </Field.Root>\n          <button type=\"button\" onClick={() => setControlId('control-b')}>\n            Change\n          </button>\n        </React.Fragment>\n      );\n    }\n\n    await render(<TestCase />);\n\n    const label = screen.getByText('Label');\n\n    expect(label).toHaveAttribute('for', 'control-a');\n\n    fireEvent.click(screen.getByRole('button', { name: 'Change' }));\n\n    await waitFor(() => {\n      expect(label).toHaveAttribute('for', 'control-b');\n    });\n  });\n\n  it('falls back to a generated id when the control id is removed', async () => {\n    function TestCase() {\n      const [controlId, setControlId] = React.useState<string | undefined>('control-a');\n\n      return (\n        <React.Fragment>\n          <Field.Root>\n            <Field.Label>Label</Field.Label>\n            <Field.Control id={controlId} />\n          </Field.Root>\n          <button type=\"button\" onClick={() => setControlId(undefined)}>\n            Clear\n          </button>\n        </React.Fragment>\n      );\n    }\n\n    await render(<TestCase />);\n\n    const label = screen.getByText('Label');\n    const control = screen.getByRole('textbox');\n\n    expect(label).toHaveAttribute('for', 'control-a');\n    expect(control).toHaveAttribute('id', 'control-a');\n\n    fireEvent.click(screen.getByRole('button', { name: 'Clear' }));\n\n    await waitFor(() => {\n      const updatedControl = screen.getByRole('textbox');\n      const updatedId = updatedControl.getAttribute('id') ?? '';\n\n      expect(updatedId).not.toBe('');\n      expect(updatedId).not.toBe('control-a');\n      expect(label).toHaveAttribute('for', updatedId);\n    });\n  });\n\n  it.skipIf(isJSDOM)('does not set `aria-labelledby` during SSR when Field.Label is absent', () => {\n    renderToString(\n      <Field.Root>\n        <Select.Root>\n          <Select.Trigger data-testid=\"trigger\">\n            <Select.Value placeholder=\"Pick one\" />\n          </Select.Trigger>\n        </Select.Root>\n      </Field.Root>,\n    );\n\n    expect(screen.getByTestId('trigger')).not.toHaveAttribute('aria-labelledby');\n  });\n\n  it.skipIf(isJSDOM)(\n    'keeps `aria-labelledby` valid when toggling from Checkbox.Root to Select.Root after hydration',\n    async () => {\n      function TestCase() {\n        const [showSelect, setShowSelect] = React.useState(false);\n\n        return (\n          <React.Fragment>\n            <Field.Root>\n              <Field.Label nativeLabel={false} render={<div />} data-testid=\"label\">\n                Label\n              </Field.Label>\n              {showSelect ? (\n                <Select.Root>\n                  <Select.Trigger data-testid=\"trigger\">\n                    <Select.Value placeholder=\"Pick one\" />\n                  </Select.Trigger>\n                </Select.Root>\n              ) : (\n                <Checkbox.Root data-testid=\"checkbox\" />\n              )}\n            </Field.Root>\n            <button type=\"button\" onClick={() => setShowSelect((prev) => !prev)}>\n              Toggle\n            </button>\n          </React.Fragment>\n        );\n      }\n\n      const { hydrate } = renderToString(<TestCase />);\n      const label = screen.getByTestId('label');\n      const checkbox = screen.getByTestId('checkbox');\n\n      expect(label.id).not.toBe('');\n      expect(checkbox).not.toHaveAttribute('aria-labelledby');\n\n      hydrate();\n      await waitFor(() => {\n        expect(screen.getByTestId('checkbox')).toHaveAttribute('aria-labelledby', label.id);\n      });\n      fireEvent.click(screen.getByRole('button', { name: 'Toggle' }));\n\n      const trigger = screen.getByTestId('trigger');\n      expect(trigger).toHaveAttribute('aria-labelledby', label.id);\n\n      fireEvent.click(screen.getByRole('button', { name: 'Toggle' }));\n\n      const checkboxAfterToggle = screen.getByTestId('checkbox');\n      expect(checkboxAfterToggle).toHaveAttribute('aria-labelledby', label.id);\n    },\n  );\n\n  it.skipIf(isJSDOM)(\n    'removes `aria-labelledby` when Field.Label is removed after hydration',\n    async () => {\n      function TestCase() {\n        const [showLabel, setShowLabel] = React.useState(true);\n\n        return (\n          <React.Fragment>\n            <Field.Root>\n              {showLabel ? (\n                <Field.Label nativeLabel={false} render={<div />} data-testid=\"label\">\n                  Label\n                </Field.Label>\n              ) : null}\n              <Select.Root>\n                <Select.Trigger data-testid=\"trigger\">\n                  <Select.Value placeholder=\"Pick one\" />\n                </Select.Trigger>\n              </Select.Root>\n            </Field.Root>\n            <button type=\"button\" onClick={() => setShowLabel(false)}>\n              Remove Label\n            </button>\n          </React.Fragment>\n        );\n      }\n\n      const { hydrate } = renderToString(<TestCase />);\n      const label = screen.getByTestId('label');\n      const trigger = screen.getByTestId('trigger');\n\n      expect(trigger).not.toHaveAttribute('aria-labelledby');\n\n      hydrate();\n      await waitFor(() => {\n        expect(screen.getByTestId('trigger')).toHaveAttribute('aria-labelledby', label.id);\n      });\n      fireEvent.click(screen.getByRole('button', { name: 'Remove Label' }));\n\n      expect(screen.queryByTestId('label')).toBe(null);\n      expect(screen.getByTestId('trigger')).not.toHaveAttribute('aria-labelledby');\n    },\n  );\n\n  it.skipIf(reactMajor < 19)(\n    'does not loop when a control is unmounted and remounted',\n    async () => {\n      const errorSpy = vi\n        .spyOn(console, 'error')\n        .mockName('console.error')\n        .mockImplementation(() => {});\n\n      try {\n        type ActivityProps = {\n          mode: 'visible' | 'hidden';\n          children: React.ReactNode;\n        };\n\n        const Activity = (React as typeof React & { Activity: React.ComponentType<ActivityProps> })\n          .Activity;\n\n        function TestCase() {\n          const [showSelect, setShowSelect] = React.useState(true);\n\n          return (\n            <React.Fragment>\n              <Field.Root>\n                <Field.Label nativeLabel={false} render={<div />}>\n                  Label\n                </Field.Label>\n                <Activity mode={showSelect ? 'visible' : 'hidden'}>\n                  <Select.Root>\n                    <Select.Trigger>\n                      <Select.Value placeholder=\"Select a model\" />\n                    </Select.Trigger>\n                    <Select.Portal>\n                      <Select.Positioner>\n                        <Select.Popup>\n                          <Select.Item value=\"model\">Model</Select.Item>\n                        </Select.Popup>\n                      </Select.Positioner>\n                    </Select.Portal>\n                  </Select.Root>\n                </Activity>\n              </Field.Root>\n              <Checkbox.Root\n                checked={!showSelect}\n                onCheckedChange={(checked) => {\n                  setShowSelect(!checked);\n                }}\n              />\n            </React.Fragment>\n          );\n        }\n\n        await renderStrict(<TestCase />);\n\n        const checkbox = screen.getByRole('checkbox');\n\n        fireEvent.click(checkbox);\n        fireEvent.click(checkbox);\n\n        expect(errorSpy.mock.calls.length).toBe(0);\n      } finally {\n        errorSpy.mockRestore();\n      }\n    },\n  );\n\n  describe('prop: disabled', () => {\n    it('should add data-disabled style hook to all components', async () => {\n      await render(\n        <Field.Root data-testid=\"field\" disabled>\n          <Field.Control data-testid=\"control\" />\n          <Field.Label data-testid=\"label\" />\n          <Field.Description data-testid=\"message\" />\n        </Field.Root>,\n      );\n\n      const field = screen.getByTestId('field');\n      const control = screen.getByTestId('control');\n      const label = screen.getByTestId('label');\n      const message = screen.getByTestId('message');\n\n      expect(field).toHaveAttribute('data-disabled', '');\n      expect(control).toHaveAttribute('data-disabled', '');\n      expect(label).toHaveAttribute('data-disabled', '');\n      expect(message).toHaveAttribute('data-disabled', '');\n    });\n  });\n\n  describe('prop: validate', () => {\n    it('when not in <Form> the function does not run by default', async () => {\n      const validateSpy = vi.fn(() => 'error');\n      await render(\n        <Field.Root validate={validateSpy}>\n          <Field.Control />\n          <Field.Error />\n        </Field.Root>,\n      );\n\n      const control = screen.getByRole('textbox');\n      const message = screen.queryByText('error');\n\n      expect(message).toBe(null);\n\n      fireEvent.focus(control);\n      fireEvent.change(control, { target: { value: 'abc' } });\n      expect(validateSpy.mock.calls.length).toBe(0);\n      expect(screen.queryByText('error')).toBe(null);\n\n      fireEvent.blur(control);\n      expect(validateSpy.mock.calls.length).toBe(0);\n      expect(screen.queryByText('error')).toBe(null);\n    });\n\n    it('runs after native validations', async () => {\n      await render(\n        <Form>\n          <Field.Root validate={(val) => (val === 'ab' ? 'custom error' : null)}>\n            <Field.Control required />\n            <Field.Error match=\"valueMissing\">value missing</Field.Error>\n            <Field.Error match=\"customError\" />\n          </Field.Root>\n          <button type=\"submit\">submit</button>\n        </Form>,\n      );\n\n      expect(screen.queryByText('value missing')).toBe(null);\n      expect(screen.queryByText('custom error')).toBe(null);\n\n      const input = screen.getByRole<HTMLInputElement>('textbox');\n\n      // submit\n      fireEvent.click(screen.getByText('submit'));\n      expect(screen.queryByText('value missing')).not.toBe(null);\n      expect(screen.queryByText('custom error')).toBe(null);\n\n      fireEvent.focus(input);\n      // revalidate\n      fireEvent.change(input, { target: { value: 'ab' } });\n      expect(screen.queryByText('value missing')).toBe(null);\n      expect(screen.queryByText('custom error')).not.toBe(null);\n\n      fireEvent.change(input, { target: { value: '' } });\n      expect(screen.queryByText('value missing')).not.toBe(null);\n      // expect(screen.queryByText('custom error')).toBe(null);\n    });\n\n    it('should apply aria-invalid prop to control once validation finishes', async () => {\n      await render(\n        <Form>\n          <Field.Root validate={() => 'error'}>\n            <Field.Control />\n            <Field.Error />\n          </Field.Root>\n          <button type=\"submit\">submit</button>\n        </Form>,\n      );\n\n      const control = screen.getByRole('textbox');\n      expect(control).not.toHaveAttribute('aria-invalid');\n\n      fireEvent.click(screen.getByText('submit'));\n      expect(control).toHaveAttribute('aria-invalid', 'true');\n    });\n\n    it('receives all form values as the 2nd argument', async () => {\n      const validateSpy = vi.fn();\n\n      await render(\n        <Form>\n          <Field.Root name=\"checkbox\">\n            <Checkbox.Root defaultChecked />\n          </Field.Root>\n\n          <Field.Root name=\"checkbox-group\">\n            <CheckboxGroup defaultValue={['apple', 'banana']}>\n              <Field.Item>\n                <Checkbox.Root value=\"apple\" />\n              </Field.Item>\n              <Field.Item>\n                <Checkbox.Root value=\"banana\" />\n              </Field.Item>\n            </CheckboxGroup>\n          </Field.Root>\n\n          <Field.Root name=\"input\" validate={validateSpy}>\n            <Field.Control data-testid=\"input\" type=\"url\" defaultValue=\"https://base-ui.com\" />\n          </Field.Root>\n\n          <Field.Root name=\"number-field\">\n            <NumberField.Root defaultValue={13}>\n              <NumberField.Input />\n            </NumberField.Root>\n          </Field.Root>\n\n          <Field.Root name=\"radio-group\">\n            <RadioGroup defaultValue=\"cats\">\n              <Radio.Root value=\"cats\" />\n            </RadioGroup>\n          </Field.Root>\n\n          <Field.Root name=\"select\">\n            <Select.Root defaultValue=\"sans\">\n              <Select.Trigger />\n              <Select.Portal>\n                <Select.Positioner>\n                  <Select.Popup>\n                    <Select.Item value=\"sans\" />\n                  </Select.Popup>\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n          </Field.Root>\n\n          <Field.Root name=\"slider\">\n            <Slider.Root defaultValue={12}>\n              <Slider.Control />\n            </Slider.Root>\n          </Field.Root>\n\n          <Field.Root name=\"range-slider\">\n            <Slider.Root defaultValue={[25, 70]}>\n              <Slider.Control />\n            </Slider.Root>\n          </Field.Root>\n\n          <Field.Root name=\"switch\">\n            <Switch.Root defaultChecked={false} />\n          </Field.Root>\n\n          <button type=\"submit\">submit</button>\n        </Form>,\n      );\n\n      fireEvent.click(screen.getByText('submit'));\n\n      expect(validateSpy.mock.calls.length).toBe(1);\n      expect(validateSpy.mock.calls[0][1]).toEqual({\n        checkbox: true,\n        'checkbox-group': ['apple', 'banana'],\n        input: 'https://base-ui.com',\n        'number-field': 13,\n        'radio-group': 'cats',\n        select: 'sans',\n        slider: 12,\n        'range-slider': [25, 70],\n        switch: false,\n      });\n    });\n\n    it('unmounted fields are excluded from the validate fn', async () => {\n      const validateSpy = vi.fn();\n      function App() {\n        const [checked, setChecked] = React.useState(true);\n\n        return (\n          <Form>\n            <input type=\"checkbox\" checked={checked} onChange={() => setChecked(!checked)} />\n            {checked && (\n              <Field.Root name=\"input1\">\n                <Field.Control defaultValue=\"one\" />\n              </Field.Root>\n            )}\n            <Field.Root name=\"input2\" validate={validateSpy}>\n              <Field.Control defaultValue=\"two\" />\n            </Field.Root>\n            <button type=\"submit\">submit</button>\n          </Form>\n        );\n      }\n      await render(<App />);\n\n      fireEvent.click(screen.getByText('submit'));\n\n      expect(validateSpy.mock.calls.length).toBe(1);\n      expect(validateSpy.mock.calls[0][1]).toEqual({\n        input1: 'one',\n        input2: 'two',\n      });\n\n      fireEvent.click(screen.getByRole('checkbox'));\n      fireEvent.click(screen.getByText('submit'));\n\n      expect(validateSpy.mock.calls.length).toBe(2);\n      expect(validateSpy.mock.lastCall?.[1]).toEqual({\n        input2: 'two',\n      });\n    });\n  });\n\n  describe('prop: validationMode', () => {\n    describe('onSubmit', () => {\n      it('should validate the field on submit', async () => {\n        await render(\n          <Form>\n            <Field.Root validate={() => 'error'}>\n              <Field.Control />\n              <Field.Error />\n            </Field.Root>\n            <button type=\"submit\">submit</button>\n          </Form>,\n        );\n\n        const message = screen.queryByText('error');\n\n        expect(message).toBe(null);\n\n        fireEvent.click(screen.getByText('submit'));\n\n        expect(screen.queryByText('error')).not.toBe(null);\n      });\n\n      it('revalidates on change', async () => {\n        await render(\n          <Form>\n            <Field.Root>\n              <Field.Control type=\"url\" required defaultValue=\"\" />\n              <Field.Error data-testid=\"error\" />\n            </Field.Root>\n            <button type=\"submit\">submit</button>\n          </Form>,\n        );\n\n        const control = screen.getByRole<HTMLInputElement>('textbox');\n\n        expect(screen.queryByTestId('error')).toBe(null);\n\n        fireEvent.click(screen.getByText('submit'));\n        expect(screen.queryByTestId('error')).not.toBe(null);\n\n        fireEvent.change(control, { target: { value: 'http://example' } });\n        expect(screen.queryByTestId('error')).toBe(null);\n      });\n    });\n\n    describe('onChange', () => {\n      it('validates the field on change', async () => {\n        await render(\n          <Field.Root\n            validationMode=\"onChange\"\n            validate={(value) => {\n              const str = value as string;\n              return str.length < 3 ? 'error' : null;\n            }}\n          >\n            <Field.Control />\n            <Field.Error />\n          </Field.Root>,\n        );\n\n        const control = screen.getByRole<HTMLInputElement>('textbox');\n        const message = screen.queryByText('error');\n\n        expect(message).toBe(null);\n\n        fireEvent.change(control, { target: { value: 't' } });\n\n        expect(control).toHaveAttribute('data-invalid', '');\n        expect(control).toHaveAttribute('aria-invalid', 'true');\n      });\n    });\n\n    describe('onBlur', () => {\n      it('validates the field on blur', async () => {\n        await render(\n          <Field.Root\n            validationMode=\"onBlur\"\n            validate={(value) => {\n              const str = value as string;\n              return str.length < 3 ? 'error' : null;\n            }}\n          >\n            <Field.Control />\n            <Field.Error />\n          </Field.Root>,\n        );\n\n        const control = screen.getByRole<HTMLInputElement>('textbox');\n        const message = screen.queryByText('error');\n\n        expect(message).toBe(null);\n\n        fireEvent.change(control, { target: { value: 't' } });\n\n        expect(control).not.toHaveAttribute('data-invalid');\n\n        fireEvent.blur(control);\n\n        expect(control).toHaveAttribute('data-invalid', '');\n        expect(control).toHaveAttribute('aria-invalid', 'true');\n      });\n\n      it('should not mark invalid if `valueMissing` is the only error and not yet dirtied', async () => {\n        await render(\n          <Field.Root validationMode=\"onBlur\">\n            <Field.Control data-testid=\"control\" required />\n          </Field.Root>,\n        );\n\n        const control = screen.getByTestId('control');\n\n        fireEvent.focus(control);\n        fireEvent.blur(control);\n\n        expect(control).not.toHaveAttribute('data-invalid');\n        expect(control).not.toHaveAttribute('aria-invalid');\n      });\n\n      it('should mark invalid if `valueMissing` is the only error and dirtied', async () => {\n        await render(\n          <Field.Root validationMode=\"onBlur\">\n            <Field.Control data-testid=\"control\" required />\n          </Field.Root>,\n        );\n\n        const control = screen.getByTestId('control');\n\n        fireEvent.focus(control);\n        fireEvent.change(control, { target: { value: 'a' } });\n        fireEvent.change(control, { target: { value: '' } });\n        fireEvent.blur(control);\n\n        expect(control).toHaveAttribute('data-invalid', '');\n        expect(control).toHaveAttribute('aria-invalid', 'true');\n      });\n\n      it('supports async validation', async () => {\n        await render(\n          <Field.Root validationMode=\"onBlur\" validate={() => Promise.resolve('error')}>\n            <Field.Control />\n            <Field.Error />\n          </Field.Root>,\n        );\n\n        const control = screen.getByRole('textbox');\n        const message = screen.queryByText('error');\n\n        expect(message).toBe(null);\n\n        fireEvent.focus(control);\n        fireEvent.blur(control);\n\n        await flushMicrotasks();\n\n        await waitFor(() => {\n          expect(screen.queryByText('error')).not.toBe(null);\n        });\n      });\n\n      it('should apply [data-field] style hooks to field components', async () => {\n        await render(\n          <Field.Root validationMode=\"onBlur\">\n            <Field.Label data-testid=\"label\">Label</Field.Label>\n            <Field.Description data-testid=\"description\">Description</Field.Description>\n            <Field.Error data-testid=\"error\" />\n            <Field.Control data-testid=\"control\" required />\n          </Field.Root>,\n        );\n\n        const control = screen.getByTestId<HTMLInputElement>('control');\n        const label = screen.getByTestId('label');\n        const description = screen.getByTestId('description');\n        let error = screen.queryByTestId('error');\n\n        expect(control).not.toHaveAttribute('data-valid');\n        expect(label).not.toHaveAttribute('data-valid');\n        expect(description).not.toHaveAttribute('data-valid');\n        expect(error).toBe(null);\n\n        fireEvent.focus(control);\n        fireEvent.change(control, { target: { value: 'a' } });\n        fireEvent.change(control, { target: { value: '' } });\n        fireEvent.blur(control);\n\n        error = screen.getByTestId('error');\n\n        expect(control).toHaveAttribute('data-invalid', '');\n        expect(label).toHaveAttribute('data-invalid', '');\n        expect(description).toHaveAttribute('data-invalid', '');\n        expect(error).toHaveAttribute('data-invalid', '');\n\n        act(() => {\n          control.value = 'value';\n          control.focus();\n          control.blur();\n        });\n\n        error = screen.queryByTestId('error');\n\n        expect(control).toHaveAttribute('data-valid', '');\n        expect(label).toHaveAttribute('data-valid', '');\n        expect(description).toHaveAttribute('data-valid', '');\n        expect(error).toBe(null);\n      });\n\n      describe('revalidation', () => {\n        it('revalidates on change for `valueMissing`', async () => {\n          await render(\n            <Field.Root validationMode=\"onBlur\">\n              <Field.Control required />\n              <Field.Error />\n            </Field.Root>,\n          );\n\n          const control = screen.getByRole('textbox');\n          const message = screen.queryByText('error');\n\n          expect(message).toBe(null);\n\n          fireEvent.focus(control);\n          fireEvent.change(control, { target: { value: 't' } });\n          fireEvent.blur(control);\n\n          expect(control).not.toHaveAttribute('aria-invalid', 'true');\n\n          fireEvent.focus(control);\n          fireEvent.change(control, { target: { value: '' } });\n          fireEvent.blur(control);\n\n          expect(control).toHaveAttribute('aria-invalid');\n        });\n\n        it('handles both `required` and `typeMismatch`', async () => {\n          await render(\n            <Field.Root validationMode=\"onBlur\">\n              <Field.Control type=\"email\" required />\n              <Field.Error data-testid=\"error\" />\n            </Field.Root>,\n          );\n\n          const control = screen.getByRole('textbox');\n          const message = screen.queryByTestId('error');\n\n          expect(message).toBe(null);\n\n          fireEvent.focus(control);\n          fireEvent.blur(control);\n\n          expect(control).not.toHaveAttribute('aria-invalid');\n\n          fireEvent.focus(control);\n          fireEvent.change(control, { target: { value: 'tt' } });\n          fireEvent.blur(control);\n\n          expect(control).toHaveAttribute('aria-invalid', 'true');\n\n          fireEvent.focus(control);\n          fireEvent.change(control, { target: { value: '' } });\n          fireEvent.blur(control);\n\n          expect(control).toHaveAttribute('aria-invalid', 'true');\n\n          fireEvent.focus(control);\n          fireEvent.change(control, { target: { value: 'email@email.com' } });\n          fireEvent.blur(control);\n\n          expect(control).not.toHaveAttribute('aria-invalid');\n        });\n\n        it('clears valueMissing on change but defers other native errors like typeMismatch until blur when both are active', async () => {\n          await render(\n            <Field.Root validationMode=\"onBlur\">\n              <Field.Control type=\"email\" required data-testid=\"control\" />\n              <Field.Error data-testid=\"error\" />\n            </Field.Root>,\n          );\n\n          const control = screen.getByTestId('control');\n\n          fireEvent.focus(control);\n          fireEvent.blur(control);\n          expect(control).not.toHaveAttribute('aria-invalid', 'true');\n          expect(screen.queryByTestId('error')).toBe(null);\n\n          fireEvent.focus(control);\n          fireEvent.change(control, { target: { value: 'a' } });\n          fireEvent.change(control, { target: { value: '' } });\n          fireEvent.blur(control);\n\n          expect(control).toHaveAttribute('aria-invalid', 'true');\n          expect(screen.getByTestId('error')).not.toBe(null);\n\n          fireEvent.focus(control);\n          fireEvent.change(control, { target: { value: 't' } });\n\n          // The field becomes temporarily valid because only 'valueMissing' is checked for immediate clearing.\n          // Other errors like 'typeMismatch' are deferred to the next blur/submit.\n          expect(control).not.toHaveAttribute('aria-invalid', 'true');\n          expect(screen.queryByTestId('error')).toBe(null);\n\n          fireEvent.blur(control);\n\n          expect(control).toHaveAttribute('aria-invalid', 'true');\n          expect(screen.getByTestId('error')).not.toBe(null);\n          expect(screen.getByTestId('error').textContent).not.toBe('');\n\n          fireEvent.focus(control);\n          fireEvent.change(control, { target: { value: 'test@example.com' } });\n\n          expect(control).not.toHaveAttribute('aria-invalid', 'true');\n          expect(screen.queryByTestId('error')).toBe(null);\n\n          fireEvent.blur(control);\n\n          expect(control).not.toHaveAttribute('aria-invalid', 'true');\n          expect(screen.queryByTestId('error')).toBe(null);\n        });\n      });\n\n      describe('computed validity state', () => {\n        it('should not mark field as invalid for valueMissing if not dirty', async () => {\n          await render(\n            <Field.Root validationMode=\"onBlur\">\n              <Field.Control data-testid=\"control\" required />\n            </Field.Root>,\n          );\n\n          const control = screen.getByTestId('control');\n\n          fireEvent.focus(control);\n          fireEvent.blur(control);\n\n          expect(control).not.toHaveAttribute('data-invalid');\n          expect(control).not.toHaveAttribute('aria-invalid');\n        });\n\n        it('should mark field as invalid for valueMissing if dirty', async () => {\n          await render(\n            <Field.Root validationMode=\"onBlur\">\n              <Field.Control data-testid=\"control\" required />\n            </Field.Root>,\n          );\n\n          const control = screen.getByTestId('control');\n\n          // Mark as touched and dirtied\n          fireEvent.focus(control);\n          fireEvent.change(control, { target: { value: 'a' } });\n          fireEvent.change(control, { target: { value: '' } });\n          fireEvent.blur(control);\n\n          // valueMissing is true, and markedDirtyRef is true, so valid should be false\n          expect(control).toHaveAttribute('data-invalid', '');\n          expect(control).toHaveAttribute('aria-invalid', 'true');\n        });\n\n        it('should mark field as invalid for other errors (e.g., typeMismatch) even if not dirty', async () => {\n          await render(\n            <Field.Root validationMode=\"onBlur\">\n              <Field.Control data-testid=\"control\" type=\"email\" defaultValue=\"not_an_email@\" />\n            </Field.Root>,\n          );\n\n          const control = screen.getByTestId('control');\n\n          // Mark as touched but not dirty\n          fireEvent.focus(control);\n          fireEvent.blur(control);\n\n          // typeMismatch is true, so valid should be false regardless of dirty state\n          expect(control).toHaveAttribute('data-invalid', '');\n          expect(control).toHaveAttribute('aria-invalid', 'true');\n        });\n      });\n    });\n  });\n\n  describe('prop: validateDebounceTime', () => {\n    const { clock, render: renderFakeTimers } = createRenderer();\n\n    clock.withFakeTimers();\n\n    it('should debounce validation', async () => {\n      await renderFakeTimers(\n        <Field.Root\n          validationDebounceTime={100}\n          validationMode=\"onChange\"\n          validate={(value) => {\n            const str = value as string;\n            return str.length < 3 ? 'error' : null;\n          }}\n        >\n          <Field.Control />\n          <Field.Error />\n        </Field.Root>,\n      );\n\n      const control = screen.getByRole<HTMLInputElement>('textbox');\n      const message = screen.queryByText('error');\n\n      expect(message).toBe(null);\n\n      fireEvent.change(control, { target: { value: 't' } });\n\n      expect(control).not.toHaveAttribute('aria-invalid');\n\n      clock.tick(99);\n\n      fireEvent.change(control, { target: { value: 'te' } });\n\n      clock.tick(99);\n\n      expect(control).not.toHaveAttribute('aria-invalid');\n\n      clock.tick(1);\n\n      expect(control).toHaveAttribute('aria-invalid', 'true');\n      expect(screen.queryByText('error')).not.toBe(null);\n    });\n  });\n\n  describe('style hooks', () => {\n    describe('touched', () => {\n      it('should apply [data-touched] style hook to all components when touched', async () => {\n        await render(\n          <Field.Root data-testid=\"root\">\n            <Field.Control data-testid=\"control\" />\n            <Field.Label data-testid=\"label\" />\n            <Field.Description data-testid=\"description\" />\n            <Field.Error data-testid=\"error\" />\n          </Field.Root>,\n        );\n\n        const root = screen.getByTestId('root');\n        const control = screen.getByTestId('control');\n        const label = screen.getByTestId('label');\n        const description = screen.getByTestId('description');\n        const error = screen.queryByTestId('error');\n\n        expect(root).not.toHaveAttribute('data-touched');\n        expect(control).not.toHaveAttribute('data-touched');\n        expect(label).not.toHaveAttribute('data-touched');\n        expect(description).not.toHaveAttribute('data-touched');\n        expect(error).toBe(null);\n\n        fireEvent.focus(control);\n        fireEvent.blur(control);\n\n        expect(root).toHaveAttribute('data-touched', '');\n        expect(control).toHaveAttribute('data-touched', '');\n        expect(label).toHaveAttribute('data-touched', '');\n        expect(description).toHaveAttribute('data-touched', '');\n        expect(error).toBe(null);\n      });\n    });\n\n    describe('dirty', () => {\n      it('should apply [data-dirty] style hook to all components when dirty', async () => {\n        await render(\n          <Field.Root data-testid=\"root\">\n            <Field.Control data-testid=\"control\" />\n            <Field.Label data-testid=\"label\" />\n            <Field.Description data-testid=\"description\" />\n            <Field.Error data-testid=\"error\" />\n          </Field.Root>,\n        );\n\n        const root = screen.getByTestId('root');\n        const control = screen.getByTestId('control');\n        const label = screen.getByTestId('label');\n        const description = screen.getByTestId('description');\n\n        expect(root).not.toHaveAttribute('data-dirty');\n        expect(control).not.toHaveAttribute('data-dirty');\n        expect(label).not.toHaveAttribute('data-dirty');\n        expect(description).not.toHaveAttribute('data-dirty');\n\n        fireEvent.change(control, { target: { value: 'value' } });\n\n        expect(root).toHaveAttribute('data-dirty', '');\n        expect(control).toHaveAttribute('data-dirty', '');\n        expect(label).toHaveAttribute('data-dirty', '');\n        expect(description).toHaveAttribute('data-dirty', '');\n\n        fireEvent.change(control, { target: { value: '' } });\n\n        expect(root).not.toHaveAttribute('data-dirty');\n        expect(control).not.toHaveAttribute('data-dirty');\n        expect(label).not.toHaveAttribute('data-dirty');\n        expect(description).not.toHaveAttribute('data-dirty');\n      });\n    });\n\n    describe('filled', () => {\n      it('should apply [data-filled] style hook to all components when filled', async () => {\n        await render(\n          <Field.Root data-testid=\"root\">\n            <Field.Control data-testid=\"control\" />\n            <Field.Label data-testid=\"label\" />\n            <Field.Description data-testid=\"description\" />\n            <Field.Error data-testid=\"error\" />\n          </Field.Root>,\n        );\n\n        const root = screen.getByTestId('root');\n        const control = screen.getByTestId('control');\n        const label = screen.getByTestId('label');\n        const description = screen.getByTestId('description');\n\n        expect(root).not.toHaveAttribute('data-filled');\n        expect(control).not.toHaveAttribute('data-filled');\n        expect(label).not.toHaveAttribute('data-filled');\n        expect(description).not.toHaveAttribute('data-filled');\n\n        fireEvent.change(control, { target: { value: 'value' } });\n\n        expect(root).toHaveAttribute('data-filled', '');\n        expect(control).toHaveAttribute('data-filled', '');\n        expect(label).toHaveAttribute('data-filled', '');\n        expect(description).toHaveAttribute('data-filled', '');\n\n        fireEvent.change(control, { target: { value: '' } });\n\n        expect(root).not.toHaveAttribute('data-filled');\n        expect(control).not.toHaveAttribute('data-filled');\n        expect(label).not.toHaveAttribute('data-filled');\n        expect(description).not.toHaveAttribute('data-filled');\n      });\n\n      it('changes [data-filled] when the value is changed externally', async () => {\n        function App() {\n          const [value, setValue] = React.useState('');\n          return (\n            <div>\n              <Field.Root>\n                <Field.Control value={value} onChange={(event) => setValue(event.target.value)} />\n              </Field.Root>\n              <button onClick={() => setValue('test')}>change</button>\n              <button onClick={() => setValue('')}>reset</button>\n            </div>\n          );\n        }\n\n        const { user } = await render(<App />);\n\n        expect(screen.getByRole('textbox')).not.toHaveAttribute('data-filled', '');\n\n        await user.click(screen.getByRole('button', { name: 'change' }));\n        expect(screen.getByRole('textbox')).toHaveAttribute('data-filled', '');\n\n        await user.click(screen.getByRole('button', { name: 'reset' }));\n        expect(screen.getByRole('textbox')).not.toHaveAttribute('data-filled', '');\n      });\n    });\n\n    describe('focused', () => {\n      it('should apply [data-focused] style hook to all components when focused', async () => {\n        await render(\n          <Field.Root data-testid=\"root\">\n            <Field.Control data-testid=\"control\" />\n            <Field.Label data-testid=\"label\" />\n            <Field.Description data-testid=\"description\" />\n            <Field.Error data-testid=\"error\" />\n          </Field.Root>,\n        );\n\n        const root = screen.getByTestId('root');\n        const control = screen.getByTestId('control');\n        const label = screen.getByTestId('label');\n        const description = screen.getByTestId('description');\n\n        expect(root).not.toHaveAttribute('data-focused');\n        expect(control).not.toHaveAttribute('data-focused');\n        expect(label).not.toHaveAttribute('data-focused');\n        expect(description).not.toHaveAttribute('data-focused');\n\n        fireEvent.focus(control);\n\n        expect(root).toHaveAttribute('data-focused', '');\n        expect(control).toHaveAttribute('data-focused', '');\n        expect(label).toHaveAttribute('data-focused', '');\n        expect(description).toHaveAttribute('data-focused', '');\n\n        fireEvent.blur(control);\n\n        expect(root).not.toHaveAttribute('data-focused');\n        expect(control).not.toHaveAttribute('data-focused');\n        expect(label).not.toHaveAttribute('data-focused');\n        expect(description).not.toHaveAttribute('data-focused');\n      });\n    });\n  });\n\n  describe('defaultValue behavior', () => {\n    it('should not reset to defaultValue when input value is programmatically changed and then focused', async () => {\n      const inputRef = React.createRef<HTMLInputElement>();\n\n      await render(\n        <Field.Root>\n          <Field.Control ref={inputRef} defaultValue=\"foo\" data-testid=\"input\" />\n        </Field.Root>,\n      );\n\n      const input = screen.getByTestId('input') as HTMLInputElement;\n\n      expect(input.value).toBe('foo');\n\n      if (inputRef.current) {\n        inputRef.current.value = '';\n      }\n\n      expect(input.value).toBe('');\n\n      fireEvent.focus(input);\n\n      expect(input.value).toBe('');\n    });\n\n    it('should not reset to defaultValue when input value is programmatically changed to non-empty value and then focused', async () => {\n      const inputRef = React.createRef<HTMLInputElement>();\n\n      await render(\n        <Field.Root>\n          <Field.Control ref={inputRef} defaultValue=\"foo\" data-testid=\"input\" />\n        </Field.Root>,\n      );\n\n      const input = screen.getByTestId('input') as HTMLInputElement;\n\n      expect(input.value).toBe('foo');\n\n      if (inputRef.current) {\n        inputRef.current.value = 'abc';\n      }\n\n      expect(input.value).toBe('abc');\n\n      fireEvent.focus(input);\n\n      expect(input.value).toBe('abc');\n    });\n  });\n\n  describe('prop: dirty', () => {\n    it('controls the dirty state', async () => {\n      await render(\n        <Field.Root data-testid=\"root\" dirty>\n          <Field.Control data-testid=\"control\" />\n          <Field.Label data-testid=\"label\" />\n          <Field.Description data-testid=\"description\" />\n          <Field.Error data-testid=\"error\" />\n        </Field.Root>,\n      );\n\n      ['root', 'control', 'label', 'description'].forEach((part) => {\n        expect(screen.getByTestId(part)).toHaveAttribute('data-dirty');\n      });\n    });\n  });\n\n  describe('prop: touched', () => {\n    it('controls the touched state', async () => {\n      await render(\n        <Field.Root data-testid=\"root\" touched>\n          <Field.Control data-testid=\"control\" />\n          <Field.Label data-testid=\"label\" />\n          <Field.Description data-testid=\"description\" />\n          <Field.Error data-testid=\"error\" />\n        </Field.Root>,\n      );\n\n      ['root', 'control', 'label', 'description'].forEach((part) => {\n        expect(screen.getByTestId(part)).toHaveAttribute('data-touched');\n      });\n    });\n  });\n\n  describe('prop: actionsRef', () => {\n    it('validates the field when the `validate` method is called', async () => {\n      function App() {\n        const actionsRef = React.useRef<Field.Root.Actions>(null);\n        return (\n          <div>\n            <Field.Root name=\"username\" actionsRef={actionsRef}>\n              <Field.Control defaultValue=\"\" required />\n              <Field.Error data-testid=\"error\" />\n            </Field.Root>\n            <button type=\"button\" onClick={() => actionsRef.current?.validate()}>\n              validate\n            </button>\n          </div>\n        );\n      }\n\n      const { user } = await render(<App />);\n\n      expect(screen.queryByTestId('error')).toBe(null);\n\n      await user.click(screen.getByText('validate'));\n\n      expect(screen.queryByTestId('error')).not.toBe(null);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/field/root/FieldRoot.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { FieldRootContext } from './FieldRootContext';\nimport { DEFAULT_VALIDITY_STATE, fieldValidityMapping } from '../utils/constants';\nimport { useFieldsetRootContext } from '../../fieldset/root/FieldsetRootContext';\nimport type { Form } from '../../form';\nimport { useFormContext } from '../../form/FormContext';\nimport { LabelableProvider } from '../../labelable-provider';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useFieldValidation } from './useFieldValidation';\n\n/**\n * @internal\n */\nconst FieldRootInner = React.forwardRef(function FieldRootInner(\n  componentProps: FieldRoot.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { errors, validationMode: formValidationMode, submitAttemptedRef } = useFormContext();\n\n  const {\n    render,\n    className,\n    validate: validateProp,\n    validationDebounceTime = 0,\n    validationMode = formValidationMode,\n    name,\n    disabled: disabledProp = false,\n    invalid: invalidProp,\n    dirty: dirtyProp,\n    touched: touchedProp,\n    actionsRef,\n    ...elementProps\n  } = componentProps;\n\n  const { disabled: disabledFieldset } = useFieldsetRootContext();\n\n  const validate = useStableCallback(validateProp || (() => null));\n\n  const disabled = disabledFieldset || disabledProp;\n\n  const [touchedState, setTouchedUnwrapped] = React.useState(false);\n  const [dirtyState, setDirtyUnwrapped] = React.useState(false);\n  const [filled, setFilled] = React.useState(false);\n  const [focused, setFocused] = React.useState(false);\n\n  const dirty = dirtyProp ?? dirtyState;\n  const touched = touchedProp ?? touchedState;\n\n  const markedDirtyRef = React.useRef(false);\n\n  const setDirty: typeof setDirtyUnwrapped = useStableCallback((value) => {\n    if (dirtyProp !== undefined) {\n      return;\n    }\n\n    if (value) {\n      markedDirtyRef.current = true;\n    }\n    setDirtyUnwrapped(value);\n  });\n\n  const setTouched: typeof setTouchedUnwrapped = useStableCallback((value) => {\n    if (touchedProp !== undefined) {\n      return;\n    }\n    setTouchedUnwrapped(value);\n  });\n\n  const shouldValidateOnChange = useStableCallback(\n    () =>\n      validationMode === 'onChange' ||\n      (validationMode === 'onSubmit' && submitAttemptedRef.current),\n  );\n\n  const hasFormError = !!name && Object.hasOwn(errors, name) && errors[name] !== undefined;\n  const invalid = invalidProp === true || hasFormError;\n\n  const [validityData, setValidityData] = React.useState<FieldValidityData>({\n    state: DEFAULT_VALIDITY_STATE,\n    error: '',\n    errors: [],\n    value: null,\n    initialValue: null,\n  });\n\n  const valid = !invalid && validityData.state.valid;\n\n  const state: FieldRootState = React.useMemo(\n    () => ({\n      disabled,\n      touched,\n      dirty,\n      valid,\n      filled,\n      focused,\n    }),\n    [disabled, touched, dirty, valid, filled, focused],\n  );\n\n  const validation = useFieldValidation({\n    setValidityData,\n    validate,\n    validityData,\n    validationDebounceTime,\n    invalid,\n    markedDirtyRef,\n    state,\n    name,\n    shouldValidateOnChange,\n  });\n\n  const handleImperativeValidate = React.useCallback(() => {\n    markedDirtyRef.current = true;\n    validation.commit(validityData.value);\n  }, [validation, validityData]);\n\n  React.useImperativeHandle(actionsRef, () => ({ validate: handleImperativeValidate }), [\n    handleImperativeValidate,\n  ]);\n\n  const contextValue: FieldRootContext = React.useMemo(\n    () => ({\n      invalid,\n      name,\n      validityData,\n      setValidityData,\n      disabled,\n      touched,\n      setTouched,\n      dirty,\n      setDirty,\n      filled,\n      setFilled,\n      focused,\n      setFocused,\n      validate,\n      validationMode,\n      validationDebounceTime,\n      shouldValidateOnChange,\n      state,\n      markedDirtyRef,\n      validation,\n    }),\n    [\n      invalid,\n      name,\n      validityData,\n      disabled,\n      touched,\n      setTouched,\n      dirty,\n      setDirty,\n      filled,\n      setFilled,\n      focused,\n      setFocused,\n      validate,\n      validationMode,\n      validationDebounceTime,\n      shouldValidateOnChange,\n      state,\n      validation,\n    ],\n  );\n\n  const element = useRenderElement('div', componentProps, {\n    ref: forwardedRef,\n    state,\n    props: elementProps,\n    stateAttributesMapping: fieldValidityMapping,\n  });\n\n  return <FieldRootContext.Provider value={contextValue}>{element}</FieldRootContext.Provider>;\n});\n\n/**\n * Groups all parts of the field.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Field](https://base-ui.com/react/components/field)\n */\nexport const FieldRoot = React.forwardRef(function FieldRoot(\n  componentProps: FieldRoot.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  return (\n    <LabelableProvider>\n      <FieldRootInner {...componentProps} ref={forwardedRef} />\n    </LabelableProvider>\n  );\n});\n\nexport interface FieldValidityData {\n  state: {\n    badInput: boolean;\n    customError: boolean;\n    patternMismatch: boolean;\n    rangeOverflow: boolean;\n    rangeUnderflow: boolean;\n    stepMismatch: boolean;\n    tooLong: boolean;\n    tooShort: boolean;\n    typeMismatch: boolean;\n    valueMissing: boolean;\n    valid: boolean | null;\n  };\n  error: string;\n  errors: string[];\n  value: unknown;\n  initialValue: unknown;\n}\n\nexport interface FieldRootActions {\n  validate: () => void;\n}\n\nexport interface FieldRootState {\n  /**\n   * Whether the component should ignore user interaction.\n   */\n  disabled: boolean;\n  /**\n   * Whether the field has been touched.\n   */\n  touched: boolean;\n  /**\n   * Whether the field value has changed from its initial value.\n   */\n  dirty: boolean;\n  /**\n   * Whether the field is valid.\n   */\n  valid: boolean | null;\n  /**\n   * Whether the field has a value.\n   */\n  filled: boolean;\n  /**\n   * Whether the field is focused.\n   */\n  focused: boolean;\n}\n\nexport interface FieldRootProps extends BaseUIComponentProps<'div', FieldRootState> {\n  /**\n   * Whether the component should ignore user interaction.\n   * Takes precedence over the `disabled` prop on the `<Field.Control>` component.\n   * @default false\n   */\n  disabled?: boolean | undefined;\n  /**\n   * Identifies the field when a form is submitted.\n   * Takes precedence over the `name` prop on the `<Field.Control>` component.\n   */\n  name?: string | undefined;\n  /**\n   * A function for custom validation. Return a string or an array of strings with\n   * the error message(s) if the value is invalid, or `null` if the value is valid.\n   * Asynchronous functions are supported, but they do not prevent form submission\n   * when using `validationMode=\"onSubmit\"`.\n   */\n  validate?:\n    | ((\n        value: unknown,\n        formValues: Form.Values,\n      ) => string | string[] | null | Promise<string | string[] | null>)\n    | undefined;\n  /**\n   * Determines when the field should be validated.\n   * This takes precedence over the `validationMode` prop on `<Form>`.\n   *\n   * - `onSubmit`: triggers validation when the form is submitted, and re-validates on change after submission.\n   * - `onBlur`: triggers validation when the control loses focus.\n   * - `onChange`: triggers validation on every change to the control value.\n   *\n   * @default 'onSubmit'\n   */\n  validationMode?: Form.ValidationMode | undefined;\n  /**\n   * How long to wait between `validate` callbacks if\n   * `validationMode=\"onChange\"` is used. Specified in milliseconds.\n   * @default 0\n   */\n  validationDebounceTime?: number | undefined;\n  /**\n   * Whether the field is invalid.\n   * Useful when the field state is controlled by an external library.\n   */\n  invalid?: boolean | undefined;\n  /**\n   * Whether the field's value has been changed from its initial value.\n   * Useful when the field state is controlled by an external library.\n   */\n  dirty?: boolean | undefined;\n  /**\n   * Whether the field has been touched.\n   * Useful when the field state is controlled by an external library.\n   */\n  touched?: boolean | undefined;\n  /**\n   * A ref to imperative actions.\n   * - `validate`: Validates the field when called.\n   */\n  actionsRef?: React.RefObject<FieldRoot.Actions | null> | undefined;\n}\n\nexport namespace FieldRoot {\n  export type State = FieldRootState;\n  export type Props = FieldRootProps;\n  export type Actions = FieldRootActions;\n}\n"
  },
  {
    "path": "packages/react/src/field/root/FieldRootContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { NOOP } from '../../utils/noop';\nimport {\n  DEFAULT_FIELD_ROOT_STATE,\n  DEFAULT_FIELD_STATE_ATTRIBUTES,\n  DEFAULT_VALIDITY_STATE,\n} from '../utils/constants';\nimport type { FieldValidityData, FieldRootState } from './FieldRoot';\nimport type { Form } from '../../form';\nimport type { UseFieldValidationReturnValue } from './useFieldValidation';\nimport type { HTMLProps } from '../../utils/types';\nimport { EMPTY_OBJECT } from '../../utils/constants';\n\nexport interface FieldRootContext {\n  invalid: boolean | undefined;\n  name: string | undefined;\n  validityData: FieldValidityData;\n  setValidityData: React.Dispatch<React.SetStateAction<FieldValidityData>>;\n  disabled: boolean | undefined;\n  touched: boolean;\n  setTouched: React.Dispatch<React.SetStateAction<boolean>>;\n  dirty: boolean;\n  setDirty: React.Dispatch<React.SetStateAction<boolean>>;\n  filled: boolean;\n  setFilled: React.Dispatch<React.SetStateAction<boolean>>;\n  focused: boolean;\n  setFocused: React.Dispatch<React.SetStateAction<boolean>>;\n  validate: (\n    value: unknown,\n    formValues: Record<string, unknown>,\n  ) => string | string[] | null | Promise<string | string[] | null>;\n  validationMode: Form.ValidationMode;\n  validationDebounceTime: number;\n  shouldValidateOnChange: () => boolean;\n  state: FieldRootState;\n  markedDirtyRef: React.RefObject<boolean>;\n  validation: UseFieldValidationReturnValue;\n}\n\nexport const FieldRootContext = React.createContext<FieldRootContext>({\n  invalid: undefined,\n  name: undefined,\n  validityData: {\n    state: DEFAULT_VALIDITY_STATE,\n    errors: [],\n    error: '',\n    value: '',\n    initialValue: null,\n  },\n  setValidityData: NOOP,\n  disabled: undefined,\n  touched: DEFAULT_FIELD_STATE_ATTRIBUTES.touched,\n  setTouched: NOOP,\n  dirty: DEFAULT_FIELD_STATE_ATTRIBUTES.dirty,\n  setDirty: NOOP,\n  filled: DEFAULT_FIELD_STATE_ATTRIBUTES.filled,\n  setFilled: NOOP,\n  focused: DEFAULT_FIELD_STATE_ATTRIBUTES.focused,\n  setFocused: NOOP,\n  validate: () => null,\n  validationMode: 'onSubmit',\n  validationDebounceTime: 0,\n  shouldValidateOnChange: () => false,\n  state: DEFAULT_FIELD_ROOT_STATE,\n  markedDirtyRef: { current: false },\n  validation: {\n    getValidationProps: (props: HTMLProps = EMPTY_OBJECT) => props,\n    getInputValidationProps: (props: HTMLProps = EMPTY_OBJECT) => props,\n    inputRef: { current: null },\n    commit: async () => {},\n  },\n});\n\nexport function useFieldRootContext(optional = true) {\n  const context = React.useContext(FieldRootContext);\n\n  if (context.setValidityData === NOOP && !optional) {\n    throw new Error(\n      'Base UI: FieldRootContext is missing. Field parts must be placed within <Field.Root>.',\n    );\n  }\n\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/field/root/FieldRootDataAttributes.ts",
    "content": "export enum FieldRootDataAttributes {\n  /**\n   * Present when the field is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the field has been touched.\n   */\n  touched = 'data-touched',\n  /**\n   * Present when the field's value has changed.\n   */\n  dirty = 'data-dirty',\n  /**\n   * Present when the field is valid.\n   */\n  valid = 'data-valid',\n  /**\n   * Present when the field is invalid.\n   */\n  invalid = 'data-invalid',\n  /**\n   * Present when the field is filled.\n   */\n  filled = 'data-filled',\n  /**\n   * Present when the field control is focused.\n   */\n  focused = 'data-focused',\n}\n"
  },
  {
    "path": "packages/react/src/field/root/useFieldValidation.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { EMPTY_OBJECT } from '@base-ui/utils/empty';\nimport { useTimeout } from '@base-ui/utils/useTimeout';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useLabelableContext } from '../../labelable-provider/LabelableContext';\nimport { mergeProps } from '../../merge-props';\nimport { DEFAULT_VALIDITY_STATE } from '../utils/constants';\nimport { useFormContext } from '../../form/FormContext';\nimport type { Form } from '../../form';\nimport { getCombinedFieldValidityData } from '../utils/getCombinedFieldValidityData';\nimport type { HTMLProps } from '../../utils/types';\nimport type { FieldValidityData, FieldRootState } from './FieldRoot';\n\nconst validityKeys = Object.keys(DEFAULT_VALIDITY_STATE) as Array<keyof ValidityState>;\n\nfunction isOnlyValueMissing(state: Record<keyof ValidityState, boolean> | undefined) {\n  if (!state || state.valid || !state.valueMissing) {\n    return false;\n  }\n\n  let onlyValueMissing = false;\n\n  for (const key of validityKeys) {\n    if (key === 'valid') {\n      continue;\n    }\n    if (key === 'valueMissing') {\n      onlyValueMissing = state[key];\n    }\n    if (state[key]) {\n      onlyValueMissing = false;\n    }\n  }\n\n  return onlyValueMissing;\n}\n\nexport function useFieldValidation(\n  params: UseFieldValidationParameters,\n): UseFieldValidationReturnValue {\n  const { formRef, clearErrors } = useFormContext();\n\n  const {\n    setValidityData,\n    validate,\n    validityData,\n    validationDebounceTime,\n    invalid,\n    markedDirtyRef,\n    state,\n    name,\n    shouldValidateOnChange,\n  } = params;\n\n  const { controlId, getDescriptionProps } = useLabelableContext();\n\n  const timeout = useTimeout();\n  const inputRef = React.useRef<HTMLInputElement | null>(null);\n\n  const commit = useStableCallback(async (value: unknown, revalidate = false) => {\n    const element = inputRef.current;\n    if (!element) {\n      return;\n    }\n\n    if (revalidate) {\n      if (state.valid !== false) {\n        return;\n      }\n\n      const currentNativeValidity = element.validity;\n\n      if (!currentNativeValidity.valueMissing) {\n        // The 'valueMissing' (required) condition has been resolved by the user typing.\n        // Temporarily mark the field as valid for this onChange event.\n        // Other native errors (e.g., typeMismatch) will be caught by full validation on blur or submit.\n        const nextValidityData = {\n          value,\n          state: { ...DEFAULT_VALIDITY_STATE, valid: true },\n          error: '',\n          errors: [],\n          initialValue: validityData.initialValue,\n        };\n        element.setCustomValidity('');\n\n        if (controlId) {\n          const currentFieldData = formRef.current.fields.get(controlId);\n          if (currentFieldData) {\n            formRef.current.fields.set(controlId, {\n              ...currentFieldData,\n              ...getCombinedFieldValidityData(nextValidityData, false), // invalid = false\n            });\n          }\n        }\n        setValidityData(nextValidityData);\n        return;\n      }\n\n      // Value is still missing, or other conditions apply.\n      // Let's use a representation of current validity for isOnlyValueMissing.\n      const currentNativeValidityObject = validityKeys.reduce(\n        (acc, key) => {\n          acc[key] = currentNativeValidity[key];\n          return acc;\n        },\n        {} as Record<keyof ValidityState, boolean>,\n      );\n\n      // If it's (still) natively invalid due to something other than just valueMissing,\n      // then bail from this revalidation on change to avoid \"scolding\" for other errors.\n      if (!currentNativeValidityObject.valid && !isOnlyValueMissing(currentNativeValidityObject)) {\n        return;\n      }\n\n      // If valueMissing is still true AND it's the only issue, or if the field is now natively valid,\n      // let it fall through to the main validation logic below.\n    }\n\n    function getState(el: HTMLInputElement) {\n      const computedState = validityKeys.reduce(\n        (acc, key) => {\n          acc[key] = el.validity[key];\n          return acc;\n        },\n        {} as Record<keyof ValidityState, boolean>,\n      );\n\n      let hasOnlyValueMissingError = false;\n\n      for (const key of validityKeys) {\n        if (key === 'valid') {\n          continue;\n        }\n        if (key === 'valueMissing' && computedState[key]) {\n          hasOnlyValueMissingError = true;\n        } else if (computedState[key]) {\n          return computedState;\n        }\n      }\n\n      // Only make `valueMissing` mark the field invalid if it's been changed\n      // to reduce error noise.\n      if (hasOnlyValueMissingError && !markedDirtyRef.current) {\n        computedState.valid = true;\n        computedState.valueMissing = false;\n      }\n      return computedState;\n    }\n\n    timeout.clear();\n\n    let result: null | string | string[] = null;\n    let validationErrors: string[] = [];\n\n    const nextState = getState(element);\n\n    let defaultValidationMessage;\n    const validateOnChange = shouldValidateOnChange();\n\n    if (element.validationMessage && !validateOnChange) {\n      // not validating on change, if there is a `validationMessage` from\n      // native validity, set errors and skip calling the custom validate fn\n      defaultValidationMessage = element.validationMessage;\n      validationErrors = [element.validationMessage];\n    } else {\n      // call the validate function because either\n      // - validating on change, or\n      // - native constraint validations passed, custom validity check is next\n      const formValues = Array.from(formRef.current.fields.values()).reduce((acc, field) => {\n        if (field.name) {\n          acc[field.name] = field.getValue();\n        }\n        return acc;\n      }, {} as Form.Values);\n\n      const resultOrPromise = validate(value, formValues);\n      if (\n        typeof resultOrPromise === 'object' &&\n        resultOrPromise !== null &&\n        'then' in resultOrPromise\n      ) {\n        result = await resultOrPromise;\n      } else {\n        result = resultOrPromise;\n      }\n\n      if (result !== null) {\n        nextState.valid = false;\n        nextState.customError = true;\n\n        if (Array.isArray(result)) {\n          validationErrors = result;\n          element.setCustomValidity(result.join('\\n'));\n        } else if (result) {\n          validationErrors = [result];\n          element.setCustomValidity(result);\n        }\n      } else if (validateOnChange) {\n        // validate function returned no errors, if validating on change\n        // we need to clear the custom validity state\n        element.setCustomValidity('');\n        nextState.customError = false;\n\n        if (element.validationMessage) {\n          defaultValidationMessage = element.validationMessage;\n          validationErrors = [element.validationMessage];\n        } else if (element.validity.valid && !nextState.valid) {\n          nextState.valid = true;\n        }\n      }\n    }\n\n    const nextValidityData = {\n      value,\n      state: nextState,\n      error: defaultValidationMessage ?? (Array.isArray(result) ? result[0] : (result ?? '')),\n      errors: validationErrors,\n      initialValue: validityData.initialValue,\n    };\n\n    if (controlId) {\n      const currentFieldData = formRef.current.fields.get(controlId);\n      if (currentFieldData) {\n        formRef.current.fields.set(controlId, {\n          ...currentFieldData,\n          // Keep Form-level errors part of overall field validity for submit blocking/focus logic.\n          ...getCombinedFieldValidityData(nextValidityData, invalid),\n        });\n      }\n    }\n\n    setValidityData(nextValidityData);\n  });\n\n  const getValidationProps = React.useCallback(\n    (externalProps = {}) =>\n      mergeProps<any>(\n        getDescriptionProps,\n        state.valid === false ? { 'aria-invalid': true } : EMPTY_OBJECT,\n        externalProps,\n      ),\n    [getDescriptionProps, state.valid],\n  );\n\n  const getInputValidationProps = React.useCallback(\n    (externalProps = {}) =>\n      mergeProps<'input'>(\n        {\n          onChange(event) {\n            // Workaround for https://github.com/facebook/react/issues/9023\n            if (event.nativeEvent.defaultPrevented) {\n              return;\n            }\n\n            clearErrors(name);\n\n            if (!shouldValidateOnChange()) {\n              commit(event.currentTarget.value, true);\n              return;\n            }\n\n            // When validating on change, run client-side validation even if\n            // externally invalid\n            const element = event.currentTarget;\n\n            if (element.value === '') {\n              // Ignore the debounce time for empty values.\n              commit(element.value);\n              return;\n            }\n\n            timeout.clear();\n\n            if (validationDebounceTime) {\n              timeout.start(validationDebounceTime, () => {\n                commit(element.value);\n              });\n            } else {\n              commit(element.value);\n            }\n          },\n        },\n        getValidationProps(externalProps),\n      ),\n    [\n      getValidationProps,\n      clearErrors,\n      name,\n      timeout,\n      commit,\n      validationDebounceTime,\n      shouldValidateOnChange,\n    ],\n  );\n\n  return React.useMemo(\n    () => ({\n      getValidationProps,\n      getInputValidationProps,\n      inputRef,\n      commit,\n    }),\n    [getValidationProps, getInputValidationProps, commit],\n  );\n}\n\nexport interface UseFieldValidationParameters {\n  setValidityData: (data: FieldValidityData) => void;\n  validate: (\n    value: unknown,\n    formValues: Form.Values,\n  ) => string | string[] | null | Promise<string | string[] | null>;\n  validityData: FieldValidityData;\n  validationDebounceTime: number;\n  invalid: boolean;\n  markedDirtyRef: React.RefObject<boolean>;\n  state: FieldRootState;\n  name: string | undefined;\n  shouldValidateOnChange: () => boolean;\n}\n\nexport interface UseFieldValidationReturnValue {\n  getValidationProps: (props?: HTMLProps) => HTMLProps;\n  getInputValidationProps: (props?: HTMLProps) => HTMLProps;\n  inputRef: React.RefObject<HTMLInputElement | null>;\n  commit: (value: unknown, revalidate?: boolean) => Promise<void>;\n}\n"
  },
  {
    "path": "packages/react/src/field/useField.ts",
    "content": "'use client';\nimport * as ReactDOM from 'react-dom';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { getCombinedFieldValidityData } from './utils/getCombinedFieldValidityData';\nimport { useFormContext } from '../form/FormContext';\nimport { useFieldRootContext } from './root/FieldRootContext';\n\nexport function useField(params: UseFieldParameters) {\n  const { enabled = true, value, id, name, controlRef, commit } = params;\n\n  const { formRef } = useFormContext();\n  const { invalid, markedDirtyRef, validityData, setValidityData } = useFieldRootContext();\n\n  const getValue = useStableCallback(params.getValue);\n\n  useIsoLayoutEffect(() => {\n    if (!enabled) {\n      return;\n    }\n\n    let initialValue = value;\n    if (initialValue === undefined) {\n      initialValue = getValue();\n    }\n\n    if (validityData.initialValue === null && initialValue !== null) {\n      setValidityData((prev) => ({ ...prev, initialValue }));\n    }\n  }, [enabled, setValidityData, value, validityData.initialValue, getValue]);\n\n  useIsoLayoutEffect(() => {\n    if (!enabled || !id) {\n      return;\n    }\n\n    formRef.current.fields.set(id, {\n      getValue,\n      name,\n      controlRef,\n      validityData: getCombinedFieldValidityData(validityData, invalid),\n      validate(flushSync = true) {\n        let nextValue = value;\n        if (nextValue === undefined) {\n          nextValue = getValue();\n        }\n\n        markedDirtyRef.current = true;\n\n        if (!flushSync) {\n          commit(nextValue);\n        } else {\n          // Synchronously update the validity state so the submit event can be prevented.\n          ReactDOM.flushSync(() => commit(nextValue));\n        }\n      },\n    });\n  }, [\n    commit,\n    controlRef,\n    enabled,\n    formRef,\n    getValue,\n    id,\n    invalid,\n    markedDirtyRef,\n    name,\n    validityData,\n    value,\n  ]);\n\n  useIsoLayoutEffect(() => {\n    const fields = formRef.current.fields;\n    return () => {\n      if (id) {\n        fields.delete(id);\n      }\n    };\n  }, [formRef, id]);\n}\n\nexport interface UseFieldParameters {\n  enabled?: boolean | undefined;\n  value: unknown;\n  getValue?: (() => unknown) | undefined;\n  id: string | undefined;\n  name?: string | undefined;\n  commit: (value: unknown) => void;\n  /**\n   * A ref to a focusable element that receives focus when the field fails\n   * validation during form submission.\n   */\n  controlRef: React.RefObject<any>;\n}\n"
  },
  {
    "path": "packages/react/src/field/utils/constants.ts",
    "content": "import { FieldControlDataAttributes } from '../control/FieldControlDataAttributes';\nimport type { FieldRootState } from '../root/FieldRoot';\n\nexport const DEFAULT_VALIDITY_STATE = {\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: null,\n  valueMissing: false,\n};\n\nexport const DEFAULT_FIELD_STATE_ATTRIBUTES: Pick<\n  FieldRootState,\n  'valid' | 'touched' | 'dirty' | 'filled' | 'focused'\n> = {\n  valid: null,\n  touched: false,\n  dirty: false,\n  filled: false,\n  focused: false,\n};\n\nexport const DEFAULT_FIELD_ROOT_STATE: FieldRootState = {\n  disabled: false,\n  ...DEFAULT_FIELD_STATE_ATTRIBUTES,\n};\n\nexport const fieldValidityMapping = {\n  valid(value: boolean | null): Record<string, string> | null {\n    if (value === null) {\n      return null;\n    }\n    if (value) {\n      return {\n        [FieldControlDataAttributes.valid]: '',\n      };\n    }\n    return {\n      [FieldControlDataAttributes.invalid]: '',\n    };\n  },\n};\n"
  },
  {
    "path": "packages/react/src/field/utils/getCombinedFieldValidityData.ts",
    "content": "import { FieldValidityData } from '../root/FieldRoot';\n\n/**\n * Combines the field's client-side, stateful validity data with the external invalid state to\n * determine the field's true validity.\n */\nexport function getCombinedFieldValidityData(\n  validityData: FieldValidityData,\n  invalid: boolean | undefined,\n) {\n  return {\n    ...validityData,\n    state: {\n      ...validityData.state,\n      valid: !invalid && validityData.state.valid,\n    },\n  };\n}\n"
  },
  {
    "path": "packages/react/src/field/validity/FieldValidity.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport { createRenderer, fireEvent, screen } from '@mui/internal-test-utils';\nimport { Field } from '@base-ui/react/field';\nimport { Form } from '@base-ui/react/form';\n\ndescribe('<Field.Validity />', () => {\n  const { render } = createRenderer();\n\n  describe('validationMode=onSubmit', () => {\n    it('should pass validity data', () => {\n      const handleValidity = vi.fn();\n\n      render(\n        <Form>\n          <Field.Root>\n            <Field.Control required />\n            <Field.Validity>{handleValidity}</Field.Validity>\n          </Field.Root>\n          <button type=\"submit\">submit</button>\n        </Form>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('textbox');\n\n      expect(handleValidity.mock.lastCall?.[0].validity.valid).toBe(null);\n\n      fireEvent.click(screen.getByText('submit'));\n\n      expect(handleValidity.mock.lastCall?.[0].validity.valid).toBe(false);\n      expect(handleValidity.mock.lastCall?.[0].validity.valueMissing).toBe(true);\n      expect(handleValidity.mock.lastCall?.[0]).toHaveProperty('transitionStatus');\n\n      fireEvent.focus(input);\n      fireEvent.change(input, { target: { value: 'test' } });\n\n      expect(handleValidity.mock.lastCall?.[0].value).toBe('test');\n      expect(handleValidity.mock.lastCall?.[0].validity.valid).toBe(true);\n      expect(handleValidity.mock.lastCall?.[0].validity.valueMissing).toBe(false);\n    });\n  });\n\n  describe('validationMode=onBlur', () => {\n    it('should pass validity data', () => {\n      const handleValidity = vi.fn();\n\n      render(\n        <Field.Root validationMode=\"onBlur\">\n          <Field.Control required />\n          <Field.Validity>{handleValidity}</Field.Validity>\n        </Field.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('textbox');\n\n      expect(handleValidity.mock.lastCall?.[0].validity.valid).toBe(null);\n\n      fireEvent.focus(input);\n      fireEvent.change(input, { target: { value: 'test' } });\n      fireEvent.blur(input);\n\n      expect(handleValidity.mock.lastCall?.[0].value).toBe('test');\n      expect(handleValidity.mock.lastCall?.[0].validity.valid).toBe(true);\n      expect(handleValidity.mock.lastCall?.[0].validity.valueMissing).toBe(false);\n    });\n\n    it('should correctly pass errors when validate function returns a string', () => {\n      const handleValidity = vi.fn();\n\n      render(\n        <Field.Root validationMode=\"onBlur\" validate={() => 'error'}>\n          <Field.Control />\n          <Field.Validity>{handleValidity}</Field.Validity>\n        </Field.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('textbox');\n\n      fireEvent.focus(input);\n      fireEvent.blur(input);\n\n      expect(handleValidity.mock.lastCall?.[0].error).toBe('error');\n      expect(handleValidity.mock.lastCall?.[0].errors).toEqual(['error']);\n    });\n\n    it('should correctly pass errors when validate function returns an array of strings', () => {\n      const handleValidity = vi.fn();\n\n      render(\n        <Field.Root validationMode=\"onBlur\" validate={() => ['1', '2']}>\n          <Field.Control />\n          <Field.Validity>{handleValidity}</Field.Validity>\n        </Field.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('textbox');\n\n      fireEvent.focus(input);\n      fireEvent.blur(input);\n\n      expect(handleValidity.mock.lastCall?.[0].error).toBe('1');\n      expect(handleValidity.mock.lastCall?.[0].errors).toEqual(['1', '2']);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/field/validity/FieldValidity.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useFieldRootContext } from '../root/FieldRootContext';\nimport { getCombinedFieldValidityData } from '../utils/getCombinedFieldValidityData';\nimport { FieldValidityData } from '../root/FieldRoot';\nimport { type TransitionStatus, useTransitionStatus } from '../../utils/useTransitionStatus';\n\n/**\n * Used to display a custom message based on the field’s validity.\n * Requires `children` to be a function that accepts field validity state as an argument.\n *\n * Documentation: [Base UI Field](https://base-ui.com/react/components/field)\n */\nexport const FieldValidity: React.FC<FieldValidity.Props> = function FieldValidity(props) {\n  const { children } = props;\n  const { validityData, invalid } = useFieldRootContext(false);\n\n  const combinedFieldValidityData = React.useMemo(\n    () => getCombinedFieldValidityData(validityData, invalid),\n    [validityData, invalid],\n  );\n  const isInvalid = combinedFieldValidityData.state.valid === false;\n  const { transitionStatus } = useTransitionStatus(isInvalid);\n\n  const fieldValidityState: FieldValidityState = React.useMemo(() => {\n    return {\n      ...combinedFieldValidityData,\n      validity: combinedFieldValidityData.state,\n      transitionStatus,\n    };\n  }, [combinedFieldValidityData, transitionStatus]);\n\n  return <React.Fragment>{children(fieldValidityState)}</React.Fragment>;\n};\n\nexport interface FieldValidityState extends Omit<FieldValidityData, 'state'> {\n  /**\n   * The validity state.\n   */\n  validity: FieldValidityData['state'];\n  /**\n   * The transition status of the component.\n   */\n  transitionStatus: TransitionStatus;\n}\n\nexport interface FieldValidityProps {\n  /**\n   * A function that accepts the field validity state as an argument.\n   *\n   * ```jsx\n   * <Field.Validity>\n   *   {(validity) => {\n   *     return <div>...</div>\n   *   }}\n   * </Field.Validity>\n   * ```\n   */\n  children: (state: FieldValidityState) => React.ReactNode;\n}\n\nexport namespace FieldValidity {\n  export type State = FieldValidityState;\n  export type Props = FieldValidityProps;\n}\n"
  },
  {
    "path": "packages/react/src/fieldset/index.parts.ts",
    "content": "export { FieldsetRoot as Root } from './root/FieldsetRoot';\nexport { FieldsetLegend as Legend } from './legend/FieldsetLegend';\n"
  },
  {
    "path": "packages/react/src/fieldset/index.ts",
    "content": "export * as Fieldset from './index.parts';\n\nexport type * from './root/FieldsetRoot';\nexport type * from './legend/FieldsetLegend';\n"
  },
  {
    "path": "packages/react/src/fieldset/legend/FieldsetLegend.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { createRenderer, screen, waitFor } from '@mui/internal-test-utils';\nimport { Fieldset } from '@base-ui/react/fieldset';\nimport { describeConformance, isJSDOM } from '#test-utils';\n\ndescribe('<Fieldset.Legend />', () => {\n  const { render, renderToString } = createRenderer();\n\n  describeConformance(<Fieldset.Legend />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(<Fieldset.Root>{node}</Fieldset.Root>);\n    },\n  }));\n\n  it('should set aria-labelledby on the fieldset automatically', () => {\n    render(\n      <Fieldset.Root>\n        <Fieldset.Legend data-testid=\"legend\">Legend</Fieldset.Legend>\n      </Fieldset.Root>,\n    );\n\n    expect(screen.getByRole('group')).toHaveAttribute(\n      'aria-labelledby',\n      screen.getByTestId('legend').id,\n    );\n  });\n\n  it('should set aria-labelledby on the fieldset with custom id', () => {\n    render(\n      <Fieldset.Root>\n        <Fieldset.Legend id=\"legend-id\" />\n      </Fieldset.Root>,\n    );\n\n    expect(screen.getByRole('group')).toHaveAttribute('aria-labelledby', 'legend-id');\n  });\n\n  it.skipIf(isJSDOM)('does not set `aria-labelledby` during SSR when legend is absent', () => {\n    renderToString(<Fieldset.Root data-testid=\"fieldset\" />);\n\n    expect(screen.getByTestId('fieldset')).not.toHaveAttribute('aria-labelledby');\n  });\n\n  it.skipIf(isJSDOM)(\n    'sets `aria-labelledby` after hydration without a custom legend id',\n    async () => {\n      const { hydrate } = renderToString(\n        <Fieldset.Root data-testid=\"fieldset\">\n          <Fieldset.Legend data-testid=\"legend\">Legend</Fieldset.Legend>\n        </Fieldset.Root>,\n      );\n\n      const fieldset = screen.getByTestId('fieldset');\n      const legend = screen.getByTestId('legend');\n\n      expect(legend.id).not.toBe('');\n      expect(fieldset).not.toHaveAttribute('aria-labelledby');\n\n      hydrate();\n\n      await waitFor(() => {\n        expect(screen.getByTestId('fieldset')).toHaveAttribute('aria-labelledby', legend.id);\n      });\n    },\n  );\n});\n"
  },
  {
    "path": "packages/react/src/fieldset/legend/FieldsetLegend.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useBaseUiId } from '../../utils/useBaseUiId';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useFieldsetRootContext } from '../root/FieldsetRootContext';\nimport type { BaseUIComponentProps } from '../../utils/types';\n\n/**\n * An accessible label that is automatically associated with the fieldset.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Fieldset](https://base-ui.com/react/components/fieldset)\n */\nexport const FieldsetLegend = React.forwardRef(function FieldsetLegend(\n  componentProps: FieldsetLegend.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, id: idProp, ...elementProps } = componentProps;\n\n  const { disabled, setLegendId } = useFieldsetRootContext();\n\n  const id = useBaseUiId(idProp);\n\n  useIsoLayoutEffect(() => {\n    setLegendId(id);\n    return () => {\n      setLegendId(undefined);\n    };\n  }, [setLegendId, id]);\n\n  const state: FieldsetLegendState = {\n    disabled: disabled ?? false,\n  };\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: forwardedRef,\n    props: [{ id }, elementProps],\n  });\n\n  return element;\n});\n\nexport interface FieldsetLegendState {\n  /**\n   * Whether the component should ignore user interaction.\n   */\n  disabled: boolean;\n}\n\nexport interface FieldsetLegendProps extends BaseUIComponentProps<'div', FieldsetLegendState> {}\n\nexport namespace FieldsetLegend {\n  export type State = FieldsetLegendState;\n  export type Props = FieldsetLegendProps;\n}\n"
  },
  {
    "path": "packages/react/src/fieldset/root/FieldsetRoot.test.tsx",
    "content": "import { createRenderer } from '@mui/internal-test-utils';\nimport { Fieldset } from '@base-ui/react/fieldset';\nimport { describeConformance } from '../../../test/describeConformance';\n\ndescribe('<Fieldset.Root />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Fieldset.Root />, () => ({\n    inheritComponent: 'fieldset',\n    refInstanceof: window.HTMLFieldSetElement,\n    render,\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/fieldset/root/FieldsetRoot.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { FieldsetRootContext } from './FieldsetRootContext';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\n\n/**\n * Groups a shared legend with related controls.\n * Renders a `<fieldset>` element.\n *\n * Documentation: [Base UI Fieldset](https://base-ui.com/react/components/fieldset)\n */\nexport const FieldsetRoot = React.forwardRef(function FieldsetRoot(\n  componentProps: FieldsetRoot.Props,\n  forwardedRef: React.ForwardedRef<HTMLElement>,\n) {\n  const { render, className, disabled = false, ...elementProps } = componentProps;\n  const [legendId, setLegendId] = React.useState<string | undefined>(undefined);\n\n  const state: FieldsetRootState = {\n    disabled,\n  };\n\n  const element = useRenderElement('fieldset', componentProps, {\n    ref: forwardedRef,\n    state,\n    props: [\n      {\n        'aria-labelledby': legendId,\n      },\n      elementProps,\n    ],\n  });\n\n  const contextValue: FieldsetRootContext = React.useMemo(\n    () => ({\n      legendId,\n      setLegendId,\n      disabled,\n    }),\n    [legendId, setLegendId, disabled],\n  );\n\n  return (\n    <FieldsetRootContext.Provider value={contextValue}>{element}</FieldsetRootContext.Provider>\n  );\n});\n\nexport interface FieldsetRootState {\n  /**\n   * Whether the component should ignore user interaction.\n   */\n  disabled: boolean;\n}\nexport interface FieldsetRootProps extends BaseUIComponentProps<'fieldset', FieldsetRootState> {}\n\nexport namespace FieldsetRoot {\n  export type State = FieldsetRootState;\n  export type Props = FieldsetRootProps;\n}\n"
  },
  {
    "path": "packages/react/src/fieldset/root/FieldsetRootContext.ts",
    "content": "'use client';\nimport * as React from 'react';\n\nexport interface FieldsetRootContext {\n  legendId: string | undefined;\n  setLegendId: React.Dispatch<React.SetStateAction<string | undefined>>;\n  disabled: boolean | undefined;\n}\n\nexport const FieldsetRootContext = React.createContext<FieldsetRootContext>({\n  legendId: undefined,\n  setLegendId: () => {},\n  disabled: undefined,\n});\n\nexport function useFieldsetRootContext(optional: true): FieldsetRootContext | undefined;\nexport function useFieldsetRootContext(optional?: false): FieldsetRootContext;\nexport function useFieldsetRootContext(optional = false) {\n  const context = React.useContext(FieldsetRootContext);\n  if (!context && !optional) {\n    throw new Error(\n      'Base UI: FieldsetRootContext is missing. Fieldset parts must be placed within <Fieldset.Root>.',\n    );\n  }\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/components/FloatingDelayGroup.test.tsx",
    "content": "import { vi, expect } from 'vitest';\n/* eslint-disable @typescript-eslint/no-shadow */\n/* eslint-disable react/jsx-fragments */\nimport * as React from 'react';\nimport { act, fireEvent, render, screen } from '@mui/internal-test-utils';\n\nimport { isJSDOM } from '@base-ui/utils/detectBrowser';\nimport {\n  FloatingDelayGroup,\n  useDelayGroup,\n  useFloating,\n  useHover,\n  useInteractions,\n} from '../index';\n\ninterface Props {\n  label: string;\n  children: React.JSX.Element;\n}\n\nfunction Tooltip({ children, label }: Props) {\n  const [open, setOpen] = React.useState(false);\n\n  const { x, y, refs, strategy, context } = useFloating({\n    open,\n    onOpenChange: setOpen,\n  });\n\n  const { delayRef } = useDelayGroup(context, { open });\n  const hover = useHover(context, { delay: () => delayRef.current });\n  const { getReferenceProps } = useInteractions([hover]);\n\n  const renderCount = React.useRef(0);\n  const renderCountRef = React.useRef<HTMLSpanElement | null>(null);\n\n  React.useEffect(() => {\n    renderCount.current += 1;\n    if (renderCountRef.current) {\n      renderCountRef.current.textContent = String(renderCount.current);\n    }\n  });\n\n  return (\n    <>\n      {React.cloneElement(\n        children,\n        getReferenceProps({\n          ref: refs.setReference,\n          ...children.props,\n        }),\n      )}\n      <span data-testid={`render-count-${label}`} ref={renderCountRef} />\n      {open && (\n        <div\n          data-testid={`floating-${label}`}\n          ref={refs.setFloating}\n          style={{\n            position: strategy,\n            top: y ?? '',\n            left: x ?? '',\n          }}\n        >\n          {label}\n        </div>\n      )}\n    </>\n  );\n}\n\nfunction App() {\n  return (\n    <FloatingDelayGroup delay={{ open: 1000, close: 200 }}>\n      <Tooltip label=\"one\">\n        <button data-testid=\"reference-one\" />\n      </Tooltip>\n      <Tooltip label=\"two\">\n        <button data-testid=\"reference-two\" />\n      </Tooltip>\n      <Tooltip label=\"three\">\n        <button data-testid=\"reference-three\" />\n      </Tooltip>\n    </FloatingDelayGroup>\n  );\n}\n\ndescribe.skipIf(!isJSDOM)('FloatingDelayGroup', () => {\n  beforeEach(() => {\n    vi.useFakeTimers();\n  });\n\n  test('groups delays correctly', async () => {\n    render(<App />);\n\n    fireEvent.mouseEnter(screen.getByTestId('reference-one'));\n\n    await act(async () => {\n      vi.advanceTimersByTime(1);\n    });\n\n    expect(screen.queryByTestId('floating-one')).not.toBeInTheDocument();\n\n    await act(async () => {\n      vi.advanceTimersByTime(999);\n    });\n\n    expect(screen.getByTestId('floating-one')).toBeInTheDocument();\n\n    fireEvent.mouseEnter(screen.getByTestId('reference-two'));\n\n    await act(async () => {\n      vi.advanceTimersByTime(1);\n    });\n\n    expect(screen.queryByTestId('floating-one')).not.toBeInTheDocument();\n    expect(screen.getByTestId('floating-two')).toBeInTheDocument();\n\n    fireEvent.mouseEnter(screen.getByTestId('reference-three'));\n\n    await act(async () => {\n      vi.advanceTimersByTime(1);\n    });\n\n    expect(screen.queryByTestId('floating-two')).not.toBeInTheDocument();\n    expect(screen.getByTestId('floating-three')).toBeInTheDocument();\n\n    fireEvent.mouseLeave(screen.getByTestId('reference-three'));\n\n    await act(async () => {\n      vi.advanceTimersByTime(1);\n    });\n\n    expect(screen.getByTestId('floating-three')).toBeInTheDocument();\n\n    await act(async () => {\n      vi.advanceTimersByTime(199);\n    });\n\n    expect(screen.queryByTestId('floating-three')).not.toBeInTheDocument();\n  });\n\n  test('timeoutMs', async () => {\n    function App() {\n      return (\n        <FloatingDelayGroup delay={{ open: 1000, close: 100 }} timeoutMs={500}>\n          <Tooltip label=\"one\">\n            <button data-testid=\"reference-one\" />\n          </Tooltip>\n          <Tooltip label=\"two\">\n            <button data-testid=\"reference-two\" />\n          </Tooltip>\n          <Tooltip label=\"three\">\n            <button data-testid=\"reference-three\" />\n          </Tooltip>\n        </FloatingDelayGroup>\n      );\n    }\n\n    render(<App />);\n\n    fireEvent.mouseEnter(screen.getByTestId('reference-one'));\n\n    await act(async () => {\n      vi.advanceTimersByTime(1000);\n    });\n\n    fireEvent.mouseLeave(screen.getByTestId('reference-one'));\n\n    expect(screen.getByTestId('floating-one')).toBeInTheDocument();\n\n    await act(async () => {\n      vi.advanceTimersByTime(499);\n    });\n\n    expect(screen.queryByTestId('floating-one')).not.toBeInTheDocument();\n\n    fireEvent.mouseEnter(screen.getByTestId('reference-two'));\n\n    await act(async () => {\n      vi.advanceTimersByTime(1);\n    });\n\n    expect(screen.getByTestId('floating-two')).toBeInTheDocument();\n\n    fireEvent.mouseEnter(screen.getByTestId('reference-three'));\n\n    await act(async () => {\n      vi.advanceTimersByTime(1);\n    });\n\n    expect(screen.queryByTestId('floating-two')).not.toBeInTheDocument();\n    expect(screen.getByTestId('floating-three')).toBeInTheDocument();\n\n    fireEvent.mouseLeave(screen.getByTestId('reference-three'));\n\n    await act(async () => {\n      vi.advanceTimersByTime(1);\n    });\n\n    expect(screen.getByTestId('floating-three')).toBeInTheDocument();\n\n    await act(async () => {\n      vi.advanceTimersByTime(99);\n    });\n\n    expect(screen.queryByTestId('floating-three')).not.toBeInTheDocument();\n  });\n\n  it('does not re-render unrelated consumers', async () => {\n    function App() {\n      return (\n        <FloatingDelayGroup delay={{ open: 1000, close: 100 }} timeoutMs={500}>\n          <Tooltip label=\"one\">\n            <button data-testid=\"reference-one\" />\n          </Tooltip>\n          <Tooltip label=\"two\">\n            <button data-testid=\"reference-two\" />\n          </Tooltip>\n          <Tooltip label=\"three\">\n            <button data-testid=\"reference-three\" />\n          </Tooltip>\n        </FloatingDelayGroup>\n      );\n    }\n\n    render(<App />);\n\n    fireEvent.mouseEnter(screen.getByTestId('reference-one'));\n\n    await act(async () => {\n      vi.advanceTimersByTime(1000);\n    });\n\n    fireEvent.mouseLeave(screen.getByTestId('reference-one'));\n\n    expect(screen.getByTestId('floating-one')).toBeInTheDocument();\n\n    await act(async () => {\n      vi.advanceTimersByTime(499);\n    });\n\n    expect(screen.queryByTestId('floating-one')).not.toBeInTheDocument();\n\n    fireEvent.mouseEnter(screen.getByTestId('reference-two'));\n\n    await act(async () => {\n      vi.advanceTimersByTime(1);\n    });\n\n    expect(screen.getByTestId('floating-two')).toBeInTheDocument();\n    expect(screen.queryByTestId('render-count-one')).toHaveTextContent('11');\n    expect(screen.queryByTestId('render-count-two')).toHaveTextContent('7');\n    expect(screen.queryByTestId('render-count-three')).toHaveTextContent('3');\n  });\n});\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/components/FloatingDelayGroup.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useTimeout, Timeout } from '@base-ui/utils/useTimeout';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\n\nimport { getDelay } from '../hooks/useHoverShared';\nimport type { FloatingRootContext, Delay, FloatingContext } from '../types';\nimport {\n  BaseUIChangeEventDetails,\n  createChangeEventDetails,\n} from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\n\ninterface ContextValue {\n  hasProvider: boolean;\n  timeoutMs: number;\n  delayRef: React.RefObject<Delay>;\n  initialDelayRef: React.RefObject<Delay>;\n  timeout: Timeout;\n  currentIdRef: React.RefObject<any>;\n  currentContextRef: React.RefObject<{\n    onOpenChange: (open: boolean, eventDetails: BaseUIChangeEventDetails<any>) => void;\n    setIsInstantPhase: (value: boolean) => void;\n  } | null>;\n}\n\nconst FloatingDelayGroupContext = React.createContext<ContextValue>({\n  hasProvider: false,\n  timeoutMs: 0,\n  delayRef: { current: 0 },\n  initialDelayRef: { current: 0 },\n  timeout: new Timeout(),\n  currentIdRef: { current: null },\n  currentContextRef: { current: null },\n});\n\nexport interface FloatingDelayGroupProps {\n  children?: React.ReactNode;\n  /**\n   * The delay to use for the group when it's not in the instant phase.\n   */\n  delay: Delay;\n  /**\n   * An optional explicit timeout to use for the group, which represents when\n   * grouping logic will no longer be active after the close delay completes.\n   * This is useful if you want grouping to “last” longer than the close delay,\n   * for example if there is no close delay at all.\n   */\n  timeoutMs?: number | undefined;\n}\n\n/**\n * Experimental next version of `FloatingDelayGroup` to become the default\n * in the future. This component is not yet stable.\n * Provides context for a group of floating elements that should share a\n * `delay`. Unlike `FloatingDelayGroup`, `useDelayGroup` with this\n * component does not cause a re-render of unrelated consumers of the\n * context when the delay changes.\n * @see https://floating-ui.com/docs/FloatingDelayGroup\n * @internal\n */\nexport function FloatingDelayGroup(props: FloatingDelayGroupProps): React.JSX.Element {\n  const { children, delay, timeoutMs = 0 } = props;\n\n  const delayRef = React.useRef(delay);\n  const initialDelayRef = React.useRef(delay);\n  const currentIdRef = React.useRef<string | null>(null);\n  const currentContextRef = React.useRef(null);\n  const timeout = useTimeout();\n\n  return (\n    <FloatingDelayGroupContext.Provider\n      value={React.useMemo(\n        () => ({\n          hasProvider: true,\n          delayRef,\n          initialDelayRef,\n          currentIdRef,\n          timeoutMs,\n          currentContextRef,\n          timeout,\n        }),\n        [timeoutMs, timeout],\n      )}\n    >\n      {children}\n    </FloatingDelayGroupContext.Provider>\n  );\n}\n\ninterface UseDelayGroupOptions {\n  /**\n   * Whether the trigger this hook is used in has opened the tooltip.\n   */\n  open: boolean;\n}\n\ninterface UseDelayGroupReturn {\n  /**\n   * The delay reference object.\n   */\n  delayRef: React.RefObject<Delay>;\n  /**\n   * Whether animations should be removed.\n   */\n  isInstantPhase: boolean;\n  /**\n   * Whether a `<FloatingDelayGroup>` provider is present.\n   */\n  hasProvider: boolean;\n}\n\n/**\n * Enables grouping when called inside a component that's a child of a\n * `FloatingDelayGroup`.\n * @see https://floating-ui.com/docs/FloatingDelayGroup\n * @internal\n */\nexport function useDelayGroup(\n  context: FloatingRootContext | FloatingContext,\n  options: UseDelayGroupOptions = { open: false },\n): UseDelayGroupReturn {\n  const store = 'rootStore' in context ? context.rootStore : context;\n  const floatingId = store.useState('floatingId');\n  const { open } = options;\n\n  const groupContext = React.useContext(FloatingDelayGroupContext);\n  const {\n    currentIdRef,\n    delayRef,\n    timeoutMs,\n    initialDelayRef,\n    currentContextRef,\n    hasProvider,\n    timeout,\n  } = groupContext;\n\n  const [isInstantPhase, setIsInstantPhase] = React.useState(false);\n\n  useIsoLayoutEffect(() => {\n    function unset() {\n      setIsInstantPhase(false);\n      currentContextRef.current?.setIsInstantPhase(false);\n      currentIdRef.current = null;\n      currentContextRef.current = null;\n      delayRef.current = initialDelayRef.current;\n    }\n\n    if (!currentIdRef.current) {\n      return undefined;\n    }\n\n    if (!open && currentIdRef.current === floatingId) {\n      setIsInstantPhase(false);\n\n      if (timeoutMs) {\n        const closingId = floatingId;\n        timeout.start(timeoutMs, () => {\n          // If another tooltip has taken over the group, skip resetting.\n          if (\n            store.select('open') ||\n            (currentIdRef.current && currentIdRef.current !== closingId)\n          ) {\n            return;\n          }\n          unset();\n        });\n        return () => {\n          timeout.clear();\n        };\n      }\n\n      unset();\n    }\n    return undefined;\n  }, [\n    open,\n    floatingId,\n    currentIdRef,\n    delayRef,\n    timeoutMs,\n    initialDelayRef,\n    currentContextRef,\n    timeout,\n    store,\n  ]);\n\n  useIsoLayoutEffect(() => {\n    if (!open) {\n      return;\n    }\n\n    const prevContext = currentContextRef.current;\n    const prevId = currentIdRef.current;\n\n    // A new tooltip is opening, so cancel any pending timeout that would reset\n    // the group's delay back to the initial value.\n    timeout.clear();\n    currentContextRef.current = { onOpenChange: store.setOpen, setIsInstantPhase };\n    currentIdRef.current = floatingId;\n    delayRef.current = {\n      open: 0,\n      close: getDelay(initialDelayRef.current, 'close'),\n    };\n\n    if (prevId !== null && prevId !== floatingId) {\n      setIsInstantPhase(true);\n      prevContext?.setIsInstantPhase(true);\n      prevContext?.onOpenChange(false, createChangeEventDetails(REASONS.none));\n    } else {\n      setIsInstantPhase(false);\n      prevContext?.setIsInstantPhase(false);\n    }\n  }, [\n    open,\n    floatingId,\n    store,\n    currentIdRef,\n    delayRef,\n    timeoutMs,\n    initialDelayRef,\n    currentContextRef,\n    timeout,\n  ]);\n\n  useIsoLayoutEffect(() => {\n    return () => {\n      currentContextRef.current = null;\n    };\n  }, [currentContextRef]);\n\n  return React.useMemo(\n    () => ({\n      hasProvider,\n      delayRef,\n      isInstantPhase,\n    }),\n    [hasProvider, delayRef, isInstantPhase],\n  );\n}\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/components/FloatingFocusManager.test.tsx",
    "content": "import { test, vi, expect } from 'vitest';\n/* eslint-disable jsx-a11y/role-has-required-aria-props */\n/* eslint-disable no-promise-executor-return */\n/* eslint-disable react/function-component-definition */\n/* eslint-disable @typescript-eslint/no-shadow */\n/* eslint-disable react/jsx-fragments */\nimport userEvent from '@testing-library/user-event';\nimport {\n  flushMicrotasks,\n  act,\n  fireEvent,\n  render,\n  screen,\n  waitFor,\n  within,\n} from '@mui/internal-test-utils';\nimport * as React from 'react';\nimport * as ReactDOMClient from 'react-dom/client';\nimport { isJSDOM } from '@base-ui/utils/detectBrowser';\nimport {\n  FloatingFocusManager,\n  FloatingNode,\n  FloatingPortal,\n  FloatingTree,\n  useClick,\n  useDismiss,\n  useFloating,\n  useFloatingNodeId,\n  useFloatingParentNodeId,\n  useHover,\n  useInteractions,\n  useRole,\n} from '../index';\nimport type { FloatingFocusManagerProps } from './FloatingFocusManager';\nimport { Main as Navigation } from '../../../test/floating-ui-tests/Navigation';\n\n// TODO (@Janpot) It looks like the toHaveFocus assertion from @mui/internal-test-utils\n// is not working correctly with iframes and nested documents. Helper as a workaround\n// until fixed.\nfunction isFocused(element: Element): boolean {\n  let doc = element.ownerDocument;\n  let current: Element = element;\n\n  while (doc) {\n    if (doc.activeElement !== current) {\n      return false;\n    }\n\n    // Move up to the parent document\n    const frame = doc.defaultView?.frameElement; // the <iframe> hosting this doc\n    if (!frame) {\n      return true;\n    }\n\n    current = frame;\n    doc = frame.ownerDocument;\n  }\n\n  return true;\n}\n\nbeforeEach(() => {\n  vi.spyOn(window, 'requestAnimationFrame').mockImplementation(\n    (callback: FrameRequestCallback): number => {\n      callback(0);\n      return 0;\n    },\n  );\n\n  Object.defineProperty(HTMLElement.prototype, 'inert', {\n    configurable: true,\n    enumerable: false,\n    writable: true,\n    value: true,\n  });\n});\n\nfunction App(\n  props: Partial<\n    Omit<FloatingFocusManagerProps, 'initialFocus'> & {\n      initialFocus?: 'two' | boolean;\n    }\n  >,\n) {\n  const ref = React.useRef<HTMLButtonElement | null>(null);\n  const [open, setOpen] = React.useState(false);\n  const { refs, context } = useFloating({\n    open,\n    onOpenChange: setOpen,\n  });\n\n  return (\n    <>\n      <button data-testid=\"reference\" ref={refs.setReference} onClick={() => setOpen(!open)} />\n      {open && (\n        <FloatingFocusManager\n          {...props}\n          initialFocus={props.initialFocus === 'two' ? ref : props.initialFocus}\n          context={context}\n        >\n          <div role=\"dialog\" ref={refs.setFloating} data-testid=\"floating\">\n            <button data-testid=\"one\">close</button>\n            <button data-testid=\"two\" ref={ref}>\n              confirm\n            </button>\n            <button data-testid=\"three\" onClick={() => setOpen(false)}>\n              x\n            </button>\n            {props.children}\n          </div>\n        </FloatingFocusManager>\n      )}\n      <div tabIndex={0} data-testid=\"last\">\n        outside\n      </div>\n    </>\n  );\n}\n\ninterface DialogProps {\n  open?: boolean;\n  render: (props: { close: () => void }) => React.ReactNode;\n  children: React.JSX.Element;\n}\n\nfunction Dialog({ render, open: passedOpen = false, children }: DialogProps) {\n  const [open, setOpen] = React.useState(passedOpen);\n  const nodeId = useFloatingNodeId();\n\n  const { refs, context } = useFloating({\n    open,\n    onOpenChange: setOpen,\n    nodeId,\n  });\n\n  const { getReferenceProps, getFloatingProps } = useInteractions([\n    useClick(context),\n    useDismiss(context, { bubbles: false }),\n  ]);\n\n  return (\n    <FloatingNode id={nodeId}>\n      {React.cloneElement(\n        children,\n        getReferenceProps({ ref: refs.setReference, ...children.props }),\n      )}\n      <FloatingPortal>\n        {open && (\n          <FloatingFocusManager context={context}>\n            <div {...getFloatingProps({ ref: refs.setFloating })}>\n              {render({\n                close: () => setOpen(false),\n              })}\n            </div>\n          </FloatingFocusManager>\n        )}\n      </FloatingPortal>\n    </FloatingNode>\n  );\n}\n\ndescribe.skipIf(!isJSDOM)('FloatingFocusManager', () => {\n  describe('prop: initialFocus', () => {\n    test('default behavior focuses first tabbable element', async () => {\n      render(<App />);\n\n      fireEvent.click(screen.getByTestId('reference'));\n      await flushMicrotasks();\n\n      expect(screen.getByTestId('one')).toHaveFocus();\n    });\n\n    test('ref', async () => {\n      render(<App initialFocus=\"two\" />);\n      fireEvent.click(screen.getByTestId('reference'));\n      await flushMicrotasks();\n\n      expect(screen.getByTestId('two')).toHaveFocus();\n    });\n\n    test('respects autoFocus', async () => {\n      render(\n        <App>\n          <input autoFocus data-testid=\"input\" />\n        </App>,\n      );\n      fireEvent.click(screen.getByTestId('reference'));\n      await flushMicrotasks();\n      expect(screen.getByTestId('input')).toHaveFocus();\n    });\n  });\n\n  describe('prop: returnFocus', () => {\n    test('when true', async () => {\n      const { rerender } = render(<App />);\n\n      screen.getByTestId('reference').focus();\n      fireEvent.click(screen.getByTestId('reference'));\n      await flushMicrotasks();\n\n      expect(screen.getByTestId('one')).toHaveFocus();\n\n      act(() => screen.getByTestId('two').focus());\n\n      rerender(<App returnFocus={false} />);\n\n      expect(screen.getByTestId('two')).toHaveFocus();\n\n      fireEvent.click(screen.getByTestId('three'));\n      expect(screen.getByTestId('reference')).not.toHaveFocus();\n    });\n\n    test('when false', async () => {\n      render(<App returnFocus={false} />);\n\n      screen.getByTestId('reference').focus();\n      fireEvent.click(screen.getByTestId('reference'));\n      await flushMicrotasks();\n\n      expect(screen.getByTestId('one')).toHaveFocus();\n\n      fireEvent.click(screen.getByTestId('three'));\n      expect(screen.getByTestId('reference')).not.toHaveFocus();\n    });\n\n    test('ref', async () => {\n      function Test() {\n        const ref = React.useRef<HTMLInputElement | null>(null);\n        return (\n          <div>\n            <input />\n            <input data-testid=\"focus-target\" ref={ref} />\n            <input />\n            <App returnFocus={ref} />\n          </div>\n        );\n      }\n\n      render(<Test />);\n      screen.getByTestId('reference').focus();\n      fireEvent.click(screen.getByTestId('reference'));\n      await flushMicrotasks();\n\n      fireEvent.click(screen.getByTestId('three'));\n      await flushMicrotasks();\n      expect(screen.getByTestId('focus-target')).toHaveFocus();\n    });\n\n    test('always returns to the reference for nested elements', async () => {\n      const NestedDialog: React.FC<DialogProps> = (props) => {\n        const parentId = useFloatingParentNodeId();\n\n        if (parentId == null) {\n          return (\n            <FloatingTree>\n              <Dialog {...props} />\n            </FloatingTree>\n          );\n        }\n\n        return <Dialog {...props} />;\n      };\n\n      render(\n        <NestedDialog\n          render={({ close }) => (\n            <>\n              <NestedDialog\n                render={({ close }) => <button onClick={close} data-testid=\"close-nested-dialog\" />}\n              >\n                <button data-testid=\"open-nested-dialog\" />\n              </NestedDialog>\n              <button onClick={close} data-testid=\"close-dialog\" />\n            </>\n          )}\n        >\n          <button data-testid=\"open-dialog\" />\n        </NestedDialog>,\n      );\n\n      await userEvent.click(screen.getByTestId('open-dialog'));\n      await userEvent.click(screen.getByTestId('open-nested-dialog'));\n\n      expect(screen.getByTestId('close-nested-dialog')).toBeInTheDocument();\n\n      fireEvent.pointerDown(document.body);\n\n      expect(screen.queryByTestId('close-nested-dialog')).not.toBeInTheDocument();\n\n      fireEvent.pointerDown(document.body);\n\n      expect(screen.queryByTestId('close-dialog')).not.toBeInTheDocument();\n    });\n\n    test('return to the first focusable descendent of the reference, if the reference is not focusable', async () => {\n      render(\n        <Dialog render={({ close }) => <button onClick={close} data-testid=\"close-dialog\" />}>\n          <div data-testid=\"non-focusable-reference\">\n            <button data-testid=\"open-dialog\" />\n          </div>\n        </Dialog>,\n      );\n      screen.getByTestId('open-dialog').focus();\n      await userEvent.keyboard('{Enter}');\n\n      expect(screen.getByTestId('close-dialog')).toBeInTheDocument();\n\n      await userEvent.keyboard('{Esc}');\n\n      expect(screen.queryByTestId('close-dialog')).not.toBeInTheDocument();\n\n      expect(screen.getByTestId('open-dialog')).toHaveFocus();\n    });\n\n    test('preserves tabbable context next to reference element if removed (modal)', async () => {\n      function App() {\n        const [isOpen, setIsOpen] = React.useState(false);\n        const [removed, setRemoved] = React.useState(false);\n\n        const { refs, context } = useFloating({\n          open: isOpen,\n          onOpenChange: setIsOpen,\n        });\n\n        const click = useClick(context);\n\n        const { getReferenceProps, getFloatingProps } = useInteractions([click]);\n\n        return (\n          <>\n            {!removed && (\n              <button ref={refs.setReference} {...getReferenceProps()} data-testid=\"reference\" />\n            )}\n            {isOpen && (\n              <FloatingPortal>\n                <FloatingFocusManager context={context}>\n                  <div ref={refs.setFloating} {...getFloatingProps()}>\n                    <button\n                      data-testid=\"remove\"\n                      onClick={() => {\n                        setRemoved(true);\n                        setIsOpen(false);\n                      }}\n                    >\n                      remove\n                    </button>\n                  </div>\n                </FloatingFocusManager>\n              </FloatingPortal>\n            )}\n            <button data-testid=\"fallback\" />\n          </>\n        );\n      }\n\n      render(<App />);\n\n      fireEvent.click(screen.getByTestId('reference'));\n      await flushMicrotasks();\n\n      fireEvent.click(screen.getByTestId('remove'));\n      await flushMicrotasks();\n\n      await userEvent.tab();\n\n      expect(screen.getByTestId('fallback')).toHaveFocus();\n    });\n\n    test('preserves tabbable context next to reference element if removed (non-modal)', async () => {\n      function App() {\n        const [isOpen, setIsOpen] = React.useState(false);\n        const [removed, setRemoved] = React.useState(false);\n\n        const { refs, context } = useFloating({\n          open: isOpen,\n          onOpenChange: setIsOpen,\n        });\n\n        const click = useClick(context);\n\n        const { getReferenceProps, getFloatingProps } = useInteractions([click]);\n\n        return (\n          <>\n            {!removed && (\n              <button ref={refs.setReference} {...getReferenceProps()} data-testid=\"reference\" />\n            )}\n            {isOpen && (\n              <FloatingPortal>\n                <FloatingFocusManager context={context} modal={false}>\n                  <div ref={refs.setFloating} {...getFloatingProps()}>\n                    <button\n                      data-testid=\"remove\"\n                      onClick={() => {\n                        setRemoved(true);\n                        setIsOpen(false);\n                      }}\n                    >\n                      remove\n                    </button>\n                  </div>\n                </FloatingFocusManager>\n              </FloatingPortal>\n            )}\n            <button data-testid=\"fallback\" />\n          </>\n        );\n      }\n\n      render(<App />);\n\n      fireEvent.click(screen.getByTestId('reference'));\n      await flushMicrotasks();\n\n      fireEvent.click(screen.getByTestId('remove'));\n      await flushMicrotasks();\n\n      await userEvent.tab();\n\n      expect(screen.getByTestId('fallback')).toHaveFocus();\n    });\n\n    test.skipIf(!isJSDOM)(\n      'does not return focus to reference on outside press when preventScroll is not supported',\n      async () => {\n        function App() {\n          const [isOpen, setIsOpen] = React.useState(false);\n\n          const { refs, context } = useFloating({\n            open: isOpen,\n            onOpenChange: setIsOpen,\n          });\n\n          const click = useClick(context);\n          const dismiss = useDismiss(context);\n\n          const { getReferenceProps, getFloatingProps } = useInteractions([click, dismiss]);\n\n          return (\n            <>\n              <button ref={refs.setReference} {...getReferenceProps()}>\n                reference\n              </button>\n              {isOpen && (\n                <FloatingFocusManager context={context}>\n                  <div ref={refs.setFloating} {...getFloatingProps()} data-testid=\"floating\" />\n                </FloatingFocusManager>\n              )}\n            </>\n          );\n        }\n\n        render(<App />);\n\n        await userEvent.click(screen.getByText('reference'));\n        await flushMicrotasks();\n\n        expect(screen.getByTestId('floating')).toHaveFocus();\n\n        await userEvent.click(document.body);\n        await flushMicrotasks();\n\n        expect(screen.getByText('reference')).not.toHaveFocus();\n      },\n    );\n\n    test('returns focus to reference on outside press when preventScroll is supported', async () => {\n      const originalFocus = HTMLElement.prototype.focus;\n      Object.defineProperty(HTMLElement.prototype, 'focus', {\n        configurable: true,\n        writable: true,\n        value(options: any) {\n          // eslint-disable-next-line @typescript-eslint/no-unused-expressions\n          options && options.preventScroll;\n          return originalFocus.call(this, options);\n        },\n      });\n\n      function App() {\n        const [isOpen, setIsOpen] = React.useState(false);\n\n        const { refs, context } = useFloating({\n          open: isOpen,\n          onOpenChange: setIsOpen,\n        });\n\n        const click = useClick(context);\n        const dismiss = useDismiss(context);\n\n        const { getReferenceProps, getFloatingProps } = useInteractions([click, dismiss]);\n\n        return (\n          <>\n            <button ref={refs.setReference} {...getReferenceProps()}>\n              reference\n            </button>\n            {isOpen && (\n              <FloatingFocusManager context={context}>\n                <div ref={refs.setFloating} {...getFloatingProps()} data-testid=\"floating\" />\n              </FloatingFocusManager>\n            )}\n          </>\n        );\n      }\n\n      render(<App />);\n\n      await userEvent.click(screen.getByText('reference'));\n      await flushMicrotasks();\n\n      expect(screen.getByTestId('floating')).toHaveFocus();\n\n      await userEvent.click(document.body);\n      await flushMicrotasks();\n\n      expect(screen.getByText('reference')).toHaveFocus();\n\n      HTMLElement.prototype.focus = originalFocus;\n    });\n\n    test('does not insert fallback element when return element is falsy', async () => {\n      function App() {\n        const [isOpen, setIsOpen] = React.useState(false);\n\n        const { refs, context } = useFloating({ open: isOpen, onOpenChange: setIsOpen });\n\n        const click = useClick(context);\n        const { getReferenceProps, getFloatingProps } = useInteractions([click]);\n\n        return (\n          <>\n            <button data-testid=\"reference\" ref={refs.setReference} {...getReferenceProps()} />\n            <FloatingPortal>\n              {isOpen && (\n                <FloatingFocusManager context={context} returnFocus={() => undefined}>\n                  <div ref={refs.setFloating} {...getFloatingProps()}>\n                    <button data-testid=\"close\" onClick={() => setIsOpen(false)} />\n                  </div>\n                </FloatingFocusManager>\n              )}\n            </FloatingPortal>\n          </>\n        );\n      }\n\n      render(<App />);\n\n      const reference = screen.getByTestId('reference');\n      await userEvent.click(reference);\n      await flushMicrotasks();\n\n      expect(reference.nextElementSibling).toBeNull();\n\n      await userEvent.click(screen.getByTestId('close'));\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('close')).toBeNull();\n      });\n\n      expect(reference.nextElementSibling).toBeNull();\n    });\n  });\n\n  describe('iframe focus navigation', () => {\n    function App({ iframe }: { iframe: HTMLElement }) {\n      return (\n        <div>\n          <a href=\"#\">prev iframe link</a>\n          <Popover\n            portalRef={iframe}\n            render={() => (\n              <div data-testid=\"popover\">\n                <a href=\"#\">popover link 1</a>\n                <a href=\"#\">popover link 2</a>\n              </div>\n            )}\n          >\n            <button>Open</button>\n          </Popover>\n          <a href=\"#\">next iframe link</a>\n        </div>\n      );\n    }\n\n    function Popover({\n      children,\n      render,\n      portalRef,\n    }: {\n      children: React.ReactElement;\n      render: () => React.ReactNode;\n      portalRef?: HTMLElement;\n    }) {\n      const [open, setOpen] = React.useState(false);\n\n      const { floatingStyles, refs, context } = useFloating({\n        open,\n        onOpenChange: setOpen,\n      });\n\n      const click = useClick(context);\n      const dismiss = useDismiss(context);\n\n      const { getReferenceProps, getFloatingProps } = useInteractions([click, dismiss]);\n\n      return (\n        <>\n          {React.cloneElement(children, getReferenceProps({ ref: refs.setReference }))}\n          {open && (\n            <FloatingPortal container={portalRef}>\n              <FloatingFocusManager context={context} modal={false}>\n                <div ref={refs.setFloating} style={floatingStyles} {...getFloatingProps()}>\n                  {render()}\n                </div>\n              </FloatingFocusManager>\n            </FloatingPortal>\n          )}\n        </>\n      );\n    }\n\n    function IframeApp() {\n      React.useEffect(() => {\n        function createIframe() {\n          const innerRoot = document.querySelector('#innerRoot');\n          const iframe = document.createElement('iframe');\n          iframe.setAttribute('data-testid', 'iframe');\n          iframe.src = 'about:blank';\n          iframe.style.height = '300px';\n\n          innerRoot?.appendChild(iframe);\n\n          // Properly open, write, and close the iframe document.\n          const iframeDoc = iframe.contentWindow?.document;\n          if (iframeDoc) {\n            iframeDoc.open();\n            iframeDoc.write(`<div id=\"rootIframe\"></div>`);\n            iframeDoc.close();\n          }\n\n          const rootIframe = iframe.contentWindow?.document.getElementById('rootIframe');\n          return rootIframe;\n        }\n\n        const root = createIframe();\n        if (root) {\n          ReactDOMClient.createRoot(root).render(<App iframe={root} />);\n        }\n      }, []);\n\n      return (\n        <>\n          <a href=\"#\">Outside link 1</a>\n          <div id=\"innerRoot\" />\n          <a href=\"#\">Outside link 2</a>\n        </>\n      );\n    }\n\n    /* eslint-disable testing-library/prefer-screen-queries */\n    // \"Should not already be working\"(?) when trying to click within the iframe\n    // https://github.com/facebook/react/pull/32441\n    test.skipIf(!isJSDOM)('tabs from the popover to the next element in the iframe', async () => {\n      render(<IframeApp />);\n\n      const iframe: HTMLIFrameElement = await screen.findByTestId('iframe');\n      const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document;\n      const iframeWithin = iframeDoc ? within(iframeDoc.body) : screen;\n\n      const user = userEvent.setup({ document: iframeDoc });\n\n      await user.click(iframeWithin.getByRole('button', { name: 'Open' }));\n\n      expect(iframeWithin.getByTestId('popover')).toBeInTheDocument();\n\n      await user.tab();\n      await user.tab();\n\n      expect(isFocused(iframeWithin.getByText('next iframe link'))).toBe(true);\n    });\n\n    // \"Should not already be working\"(?) when trying to click within the iframe\n    // https://github.com/facebook/react/pull/32441\n    test.skipIf(!isJSDOM)(\n      'shift+tab from the popover to the previous element in the iframe',\n      async () => {\n        render(<IframeApp />);\n\n        const iframe: HTMLIFrameElement = await screen.findByTestId('iframe');\n        const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document;\n        const iframeWithin = iframeDoc ? within(iframeDoc.body) : screen;\n\n        const user = userEvent.setup({ document: iframeDoc });\n\n        await user.click(iframeWithin.getByRole('button', { name: 'Open' }));\n\n        expect(iframeWithin.getByTestId('popover')).toBeInTheDocument();\n\n        await user.tab({ shift: true });\n\n        expect(isFocused(iframeWithin.getByRole('button', { name: 'Open' }))).toBe(true);\n      },\n    );\n  });\n  /* eslint-enable testing-library/prefer-screen-queries */\n\n  describe('prop: modal', () => {\n    test('when true', async () => {\n      render(<App modal />);\n\n      fireEvent.click(screen.getByTestId('reference'));\n      await flushMicrotasks();\n\n      await userEvent.tab();\n      expect(screen.getByTestId('two')).toHaveFocus();\n\n      await userEvent.tab();\n      expect(screen.getByTestId('three')).toHaveFocus();\n\n      await userEvent.tab();\n      expect(screen.getByTestId('one')).toHaveFocus();\n\n      await userEvent.tab({ shift: true });\n      expect(screen.getByTestId('three')).toHaveFocus();\n\n      await userEvent.tab({ shift: true });\n      expect(screen.getByTestId('two')).toHaveFocus();\n\n      await userEvent.tab({ shift: true });\n      expect(screen.getByTestId('one')).toHaveFocus();\n\n      await userEvent.tab({ shift: true });\n      expect(screen.getByTestId('three')).toHaveFocus();\n\n      await userEvent.tab();\n      expect(screen.getByTestId('one')).toHaveFocus();\n    });\n\n    test('when false', async () => {\n      render(<App modal={false} />);\n\n      fireEvent.click(screen.getByTestId('reference'));\n      await flushMicrotasks();\n\n      await userEvent.tab();\n      expect(screen.getByTestId('two')).toHaveFocus();\n\n      await userEvent.tab();\n      expect(screen.getByTestId('three')).toHaveFocus();\n\n      await userEvent.tab();\n\n      // Wait for the setTimeout that wraps onOpenChange(false).\n      await act(() => new Promise((resolve) => setTimeout(resolve)));\n\n      // Focus leaving the floating element closes it.\n      expect(screen.queryByRole('dialog')).not.toBeInTheDocument();\n\n      expect(screen.getByTestId('last')).toHaveFocus();\n    });\n\n    test('false - comboboxes do not hide all other nodes', async () => {\n      function App() {\n        const [open, setOpen] = React.useState(false);\n        const { refs, context } = useFloating({\n          open,\n          onOpenChange: setOpen,\n        });\n\n        return (\n          <>\n            <input\n              role=\"combobox\"\n              data-testid=\"reference\"\n              ref={refs.setReference}\n              onFocus={() => setOpen(true)}\n            />\n            <button data-testid=\"btn-1\" />\n            <button data-testid=\"btn-2\" />\n            {open && (\n              <FloatingFocusManager context={context} modal={false}>\n                <div role=\"listbox\" ref={refs.setFloating} data-testid=\"floating\" />\n              </FloatingFocusManager>\n            )}\n          </>\n        );\n      }\n\n      render(<App />);\n\n      fireEvent.focus(screen.getByTestId('reference'));\n      await flushMicrotasks();\n\n      expect(screen.getByTestId('reference')).not.toHaveAttribute('inert');\n      expect(screen.getByTestId('floating')).not.toHaveAttribute('inert');\n      expect(screen.getByTestId('btn-1')).not.toHaveAttribute('inert');\n      expect(screen.getByTestId('btn-2')).not.toHaveAttribute('inert');\n    });\n\n    test('fallback to floating element when it has no tabbable content', async () => {\n      function App() {\n        const { refs, context } = useFloating({ open: true });\n        return (\n          <>\n            <button data-testid=\"reference\" ref={refs.setReference} />\n            <FloatingFocusManager context={context} modal>\n              <div ref={refs.setFloating} data-testid=\"floating\" tabIndex={-1} />\n            </FloatingFocusManager>\n          </>\n        );\n      }\n\n      render(<App />);\n      await flushMicrotasks();\n\n      await waitFor(() => {\n        expect(screen.getByTestId('floating')).toHaveFocus();\n      });\n      await userEvent.tab();\n      expect(screen.getByTestId('floating')).toHaveFocus();\n      await userEvent.tab({ shift: true });\n      expect(screen.getByTestId('floating')).toHaveFocus();\n    });\n\n    test('mixed modality and nesting', async () => {\n      interface Props {\n        open?: boolean;\n        modal?: boolean;\n        render: (props: { close: () => void }) => React.ReactNode;\n        children?: React.JSX.Element;\n        sideChildren?: React.JSX.Element;\n      }\n\n      const Dialog = ({\n        render,\n        open: controlledOpen,\n        modal = true,\n        children,\n        sideChildren,\n      }: Props) => {\n        const [internalOpen, setOpen] = React.useState(false);\n        const nodeId = useFloatingNodeId();\n        const open = controlledOpen !== undefined ? controlledOpen : internalOpen;\n\n        const { refs, context } = useFloating({\n          open,\n          onOpenChange: setOpen,\n          nodeId,\n        });\n\n        const { getReferenceProps, getFloatingProps } = useInteractions([\n          useClick(context),\n          useDismiss(context, { bubbles: false }),\n        ]);\n\n        return (\n          <FloatingNode id={nodeId}>\n            {children &&\n              React.cloneElement(\n                children,\n                getReferenceProps({ ref: refs.setReference, ...children.props }),\n              )}\n            <FloatingPortal>\n              {open && (\n                <FloatingFocusManager context={context} modal={modal}>\n                  <div {...getFloatingProps({ ref: refs.setFloating })}>\n                    {render({\n                      close: () => setOpen(false),\n                    })}\n                  </div>\n                </FloatingFocusManager>\n              )}\n            </FloatingPortal>\n            {sideChildren}\n          </FloatingNode>\n        );\n      };\n\n      const NestedDialog: React.FC<Props> = (props) => {\n        const parentId = useFloatingParentNodeId();\n\n        if (parentId == null) {\n          return (\n            <FloatingTree>\n              <Dialog {...props} />\n            </FloatingTree>\n          );\n        }\n\n        return <Dialog {...props} />;\n      };\n\n      const App = () => {\n        const [sideDialogOpen, setSideDialogOpen] = React.useState(false);\n        return (\n          <NestedDialog\n            modal={false}\n            render={({ close }) => (\n              <>\n                <button onClick={close} data-testid=\"close-dialog\" />\n                <button onClick={() => setSideDialogOpen(true)} data-testid=\"open-nested-dialog\" />\n              </>\n            )}\n            sideChildren={\n              <NestedDialog\n                modal\n                open={sideDialogOpen}\n                render={({ close }) => <button onClick={close} data-testid=\"close-nested-dialog\" />}\n              />\n            }\n          >\n            <button data-testid=\"open-dialog\" />\n          </NestedDialog>\n        );\n      };\n\n      render(<App />);\n\n      await userEvent.click(screen.getByTestId('open-dialog'));\n      await userEvent.click(screen.getByTestId('open-nested-dialog'));\n\n      expect(screen.getByTestId('close-dialog')).toBeInTheDocument();\n      expect(screen.getByTestId('close-nested-dialog')).toBeInTheDocument();\n    });\n\n    test('true - applies aria-hidden to outside nodes', async () => {\n      function App() {\n        const [isOpen, setIsOpen] = React.useState(false);\n        const { refs, context } = useFloating({\n          open: isOpen,\n          onOpenChange: setIsOpen,\n        });\n\n        return (\n          <>\n            <input\n              data-testid=\"reference\"\n              ref={refs.setReference}\n              onClick={() => setIsOpen((v) => !v)}\n            />\n            <div data-testid=\"outside-wrapper\">\n              <div data-testid=\"aria-live\" aria-live=\"polite\" />\n              <button data-testid=\"btn-1\" />\n              <button data-testid=\"btn-2\" />\n            </div>\n            {isOpen && (\n              <FloatingFocusManager context={context}>\n                <div ref={refs.setFloating} data-testid=\"floating\" />\n              </FloatingFocusManager>\n            )}\n          </>\n        );\n      }\n\n      render(<App />);\n\n      fireEvent.click(screen.getByTestId('reference'));\n      await flushMicrotasks();\n\n      expect(screen.getByTestId('reference')).toHaveAttribute('aria-hidden', 'true');\n      expect(screen.getByTestId('floating')).not.toHaveAttribute('aria-hidden');\n      expect(screen.getByTestId('aria-live')).not.toHaveAttribute('aria-hidden');\n      expect(screen.getByTestId('btn-1')).toHaveAttribute('aria-hidden', 'true');\n      expect(screen.getByTestId('btn-2')).toHaveAttribute('aria-hidden', 'true');\n\n      fireEvent.click(screen.getByTestId('reference'));\n\n      expect(screen.getByTestId('reference')).not.toHaveAttribute('aria-hidden');\n      expect(screen.getByTestId('aria-live')).not.toHaveAttribute('aria-hidden');\n      expect(screen.getByTestId('btn-1')).not.toHaveAttribute('aria-hidden');\n      expect(screen.getByTestId('btn-2')).not.toHaveAttribute('aria-hidden');\n    });\n\n    test('true - keeps supplied inside elements outside the floating node exposed to assistive tech', async () => {\n      function App() {\n        const [isOpen, setIsOpen] = React.useState(false);\n        const dismissRef = React.useRef<HTMLButtonElement | null>(null);\n        const { refs, context } = useFloating({\n          open: isOpen,\n          onOpenChange: setIsOpen,\n        });\n\n        return (\n          <>\n            <input\n              data-testid=\"reference\"\n              ref={refs.setReference}\n              onClick={() => setIsOpen((v) => !v)}\n            />\n            <div data-testid=\"outside-wrapper\">\n              <button data-testid=\"outside-button\" />\n            </div>\n            {isOpen && (\n              <FloatingFocusManager\n                context={context}\n                getInsideElements={() => [dismissRef.current]}\n              >\n                <>\n                  <div ref={refs.setFloating} data-testid=\"floating\" />\n                  <button ref={dismissRef} data-testid=\"dismiss\" />\n                </>\n              </FloatingFocusManager>\n            )}\n          </>\n        );\n      }\n\n      render(<App />);\n\n      fireEvent.click(screen.getByTestId('reference'));\n      await flushMicrotasks();\n\n      expect(screen.getByTestId('floating')).not.toHaveAttribute('aria-hidden');\n      expect(screen.getByTestId('dismiss')).not.toHaveAttribute('aria-hidden');\n      expect(screen.getByTestId('outside-wrapper')).toHaveAttribute('aria-hidden', 'true');\n    });\n\n    test('false - does not apply inert to outside nodes', async () => {\n      function App() {\n        const [isOpen, setIsOpen] = React.useState(false);\n        const { refs, context } = useFloating({\n          open: isOpen,\n          onOpenChange: setIsOpen,\n        });\n\n        return (\n          <>\n            <input\n              data-testid=\"reference\"\n              ref={refs.setReference}\n              onClick={() => setIsOpen((v) => !v)}\n            />\n            <div data-testid=\"outside-wrapper\">\n              <div data-testid=\"aria-live\" aria-live=\"polite\" />\n              <button data-testid=\"btn-1\" />\n              <button data-testid=\"btn-2\" />\n            </div>\n            {isOpen && (\n              <FloatingFocusManager context={context} modal={false}>\n                <div role=\"listbox\" ref={refs.setFloating} data-testid=\"floating\" />\n              </FloatingFocusManager>\n            )}\n          </>\n        );\n      }\n\n      render(<App />);\n\n      fireEvent.click(screen.getByTestId('reference'));\n      await flushMicrotasks();\n\n      expect(screen.getByTestId('floating')).not.toHaveAttribute('inert');\n      expect(screen.getByTestId('aria-live')).not.toHaveAttribute('inert');\n      expect(screen.getByTestId('btn-1')).not.toHaveAttribute('inert');\n      expect(screen.getByTestId('btn-2')).not.toHaveAttribute('inert');\n      expect(screen.getByTestId('reference')).toHaveAttribute('data-base-ui-inert');\n      expect(screen.getByTestId('outside-wrapper')).toHaveAttribute('data-base-ui-inert');\n      expect(screen.getByTestId('btn-1')).not.toHaveAttribute('data-base-ui-inert');\n      expect(screen.getByTestId('btn-2')).not.toHaveAttribute('data-base-ui-inert');\n\n      fireEvent.click(screen.getByTestId('reference'));\n\n      expect(screen.getByTestId('reference')).not.toHaveAttribute('data-base-ui-inert');\n      expect(screen.getByTestId('outside-wrapper')).not.toHaveAttribute('data-base-ui-inert');\n      expect(screen.getByTestId('btn-1')).not.toHaveAttribute('data-base-ui-inert');\n      expect(screen.getByTestId('btn-2')).not.toHaveAttribute('data-base-ui-inert');\n    });\n\n    test('false - keeps marker on top-level outside ancestor when reference has siblings', async () => {\n      function App() {\n        const [isOpen, setIsOpen] = React.useState(false);\n        const { refs, context } = useFloating({\n          open: isOpen,\n          onOpenChange: setIsOpen,\n        });\n\n        return (\n          <>\n            <div data-testid=\"outside-wrapper\">\n              <input\n                data-testid=\"reference\"\n                ref={refs.setReference}\n                onClick={() => setIsOpen((v) => !v)}\n              />\n              <button data-testid=\"btn-1\" />\n              <button data-testid=\"btn-2\" />\n              <div data-testid=\"nested-wrapper\">\n                <button data-testid=\"nested-btn\" />\n              </div>\n            </div>\n            <div data-testid=\"outside-sibling\" />\n            {isOpen && (\n              <FloatingFocusManager context={context} modal={false}>\n                <div role=\"listbox\" ref={refs.setFloating} data-testid=\"floating\" />\n              </FloatingFocusManager>\n            )}\n          </>\n        );\n      }\n\n      render(<App />);\n\n      fireEvent.click(screen.getByTestId('reference'));\n      await flushMicrotasks();\n\n      expect(screen.getByTestId('floating')).not.toHaveAttribute('inert');\n      expect(screen.getByTestId('outside-wrapper')).toHaveAttribute('data-base-ui-inert');\n      expect(screen.getByTestId('outside-sibling')).toHaveAttribute('data-base-ui-inert');\n      expect(screen.getByTestId('reference')).not.toHaveAttribute('data-base-ui-inert');\n      expect(screen.getByTestId('btn-1')).not.toHaveAttribute('data-base-ui-inert');\n      expect(screen.getByTestId('btn-2')).not.toHaveAttribute('data-base-ui-inert');\n      expect(screen.getByTestId('nested-wrapper')).not.toHaveAttribute('data-base-ui-inert');\n      expect(screen.getByTestId('nested-btn')).not.toHaveAttribute('data-base-ui-inert');\n\n      fireEvent.click(screen.getByTestId('reference'));\n\n      expect(screen.getByTestId('outside-wrapper')).not.toHaveAttribute('data-base-ui-inert');\n      expect(screen.getByTestId('outside-sibling')).not.toHaveAttribute('data-base-ui-inert');\n      expect(screen.getByTestId('reference')).not.toHaveAttribute('data-base-ui-inert');\n      expect(screen.getByTestId('btn-1')).not.toHaveAttribute('data-base-ui-inert');\n      expect(screen.getByTestId('btn-2')).not.toHaveAttribute('data-base-ui-inert');\n      expect(screen.getByTestId('nested-wrapper')).not.toHaveAttribute('data-base-ui-inert');\n      expect(screen.getByTestId('nested-btn')).not.toHaveAttribute('data-base-ui-inert');\n    });\n  });\n\n  describe('prop: disabled', () => {\n    test('true -> false', async () => {\n      function App() {\n        const [isOpen, setIsOpen] = React.useState(false);\n        const [disabled, setDisabled] = React.useState(true);\n\n        const { refs, context } = useFloating({\n          open: isOpen,\n          onOpenChange: setIsOpen,\n        });\n\n        return (\n          <>\n            <button\n              data-testid=\"reference\"\n              ref={refs.setReference}\n              onClick={() => setIsOpen((v) => !v)}\n            />\n            <button data-testid=\"toggle\" onClick={() => setDisabled((v) => !v)} />\n            {isOpen && (\n              <FloatingFocusManager context={context} disabled={disabled}>\n                <div ref={refs.setFloating} data-testid=\"floating\" role=\"dialog\" />\n              </FloatingFocusManager>\n            )}\n          </>\n        );\n      }\n\n      render(<App />);\n\n      fireEvent.click(screen.getByTestId('reference'));\n      await flushMicrotasks();\n      expect(screen.getByTestId('floating')).not.toHaveFocus();\n      fireEvent.click(screen.getByTestId('toggle'));\n      await flushMicrotasks();\n      await waitFor(() => {\n        expect(screen.getByTestId('floating')).toHaveFocus();\n      });\n    });\n\n    test('when false', async () => {\n      function App() {\n        const [isOpen, setIsOpen] = React.useState(false);\n        const [disabled, setDisabled] = React.useState(false);\n\n        const { refs, context } = useFloating({\n          open: isOpen,\n          onOpenChange: setIsOpen,\n        });\n\n        const click = useClick(context);\n\n        const { getReferenceProps, getFloatingProps } = useInteractions([click]);\n\n        return (\n          <>\n            <button data-testid=\"reference\" ref={refs.setReference} {...getReferenceProps()} />\n            <button data-testid=\"toggle\" onClick={() => setDisabled((v) => !v)} />\n            {isOpen && (\n              <FloatingFocusManager context={context} disabled={disabled}>\n                <div ref={refs.setFloating} data-testid=\"floating\" {...getFloatingProps()} />\n              </FloatingFocusManager>\n            )}\n          </>\n        );\n      }\n\n      render(<App />);\n\n      fireEvent.click(screen.getByTestId('reference'));\n      await flushMicrotasks();\n      expect(screen.getByTestId('floating')).toHaveFocus();\n    });\n\n    test('supports keepMounted behavior', async () => {\n      function App() {\n        const [isOpen, setIsOpen] = React.useState(false);\n\n        const { refs, context } = useFloating({\n          open: isOpen,\n          onOpenChange: setIsOpen,\n        });\n\n        const click = useClick(context);\n        const dismiss = useDismiss(context);\n\n        const { getReferenceProps, getFloatingProps } = useInteractions([click, dismiss]);\n\n        return (\n          <>\n            <button data-testid=\"reference\" ref={refs.setReference} {...getReferenceProps()} />\n            <FloatingFocusManager context={context} disabled={!isOpen} modal={false}>\n              <div ref={refs.setFloating} data-testid=\"floating\" {...getFloatingProps()}>\n                <button data-testid=\"child\" />\n              </div>\n            </FloatingFocusManager>\n            <button data-testid=\"after\" />\n          </>\n        );\n      }\n\n      render(<App />);\n\n      await flushMicrotasks();\n\n      expect(screen.getByTestId('floating')).not.toHaveFocus();\n\n      fireEvent.click(screen.getByTestId('reference'));\n\n      await flushMicrotasks();\n\n      await waitFor(() => {\n        expect(screen.getByTestId('child')).toHaveFocus();\n      });\n\n      await userEvent.tab();\n\n      expect(screen.getByTestId('after')).toHaveFocus();\n\n      await userEvent.tab({ shift: true });\n\n      fireEvent.click(screen.getByTestId('reference'));\n\n      expect(screen.getByTestId('child')).toHaveFocus();\n\n      await userEvent.keyboard('{Escape}');\n\n      expect(screen.getByTestId('reference')).toHaveFocus();\n    });\n  });\n\n  describe('non-modal + FloatingPortal', () => {\n    test('focuses inside element, tabbing out focuses last document element', async () => {\n      function App() {\n        const [open, setOpen] = React.useState(false);\n        const { refs, context } = useFloating({\n          open,\n          onOpenChange: setOpen,\n        });\n\n        return (\n          <>\n            <span tabIndex={0} data-testid=\"first\" />\n            <button data-testid=\"reference\" ref={refs.setReference} onClick={() => setOpen(true)} />\n            <FloatingPortal>\n              {open && (\n                <FloatingFocusManager context={context} modal={false}>\n                  <div data-testid=\"floating\" ref={refs.setFloating}>\n                    <span tabIndex={0} data-testid=\"inside\" />\n                  </div>\n                </FloatingFocusManager>\n              )}\n            </FloatingPortal>\n            <span tabIndex={0} data-testid=\"last\" />\n          </>\n        );\n      }\n\n      render(<App />);\n\n      await userEvent.click(screen.getByTestId('reference'));\n      await flushMicrotasks();\n\n      expect(screen.getByTestId('inside')).toHaveFocus();\n\n      await userEvent.tab();\n\n      expect(screen.queryByTestId('floating')).not.toBeInTheDocument();\n      expect(screen.getByTestId('last')).toHaveFocus();\n    });\n\n    test('does not mark reference siblings due to outside focus guards', async () => {\n      function App() {\n        const [open, setOpen] = React.useState(false);\n        const { refs, context } = useFloating({\n          open,\n          onOpenChange: setOpen,\n        });\n\n        return (\n          <>\n            <div data-testid=\"reference-wrapper\">\n              <button\n                data-testid=\"reference\"\n                ref={refs.setReference}\n                onClick={() => setOpen(true)}\n              />\n              <span data-testid=\"reference-sibling-1\" />\n              <span data-testid=\"reference-sibling-2\" />\n            </div>\n            <FloatingPortal>\n              {open && (\n                <FloatingFocusManager context={context} modal={false}>\n                  <div data-testid=\"floating\" ref={refs.setFloating}>\n                    <span tabIndex={0} data-testid=\"inside\" />\n                  </div>\n                </FloatingFocusManager>\n              )}\n            </FloatingPortal>\n          </>\n        );\n      }\n\n      render(<App />);\n\n      await userEvent.click(screen.getByTestId('reference'));\n      await flushMicrotasks();\n\n      expect(screen.getByTestId('floating')).toBeInTheDocument();\n      expect(screen.getByTestId('reference')).not.toHaveAttribute('data-base-ui-inert');\n      expect(screen.getByTestId('reference-sibling-1')).not.toHaveAttribute('data-base-ui-inert');\n      expect(screen.getByTestId('reference-sibling-2')).not.toHaveAttribute('data-base-ui-inert');\n    });\n\n    test('shift+tab', async () => {\n      function App() {\n        const [open, setOpen] = React.useState(false);\n        const { refs, context } = useFloating({\n          open,\n          onOpenChange: setOpen,\n        });\n\n        return (\n          <>\n            <span tabIndex={0} data-testid=\"first\" />\n            <button data-testid=\"reference\" ref={refs.setReference} onClick={() => setOpen(true)} />\n            <FloatingPortal>\n              {open && (\n                <FloatingFocusManager context={context} modal={false}>\n                  <div data-testid=\"floating\" ref={refs.setFloating}>\n                    <span tabIndex={0} data-testid=\"inside\" />\n                  </div>\n                </FloatingFocusManager>\n              )}\n            </FloatingPortal>\n            <span tabIndex={0} data-testid=\"last\" />\n          </>\n        );\n      }\n\n      render(<App />);\n\n      await userEvent.click(screen.getByTestId('reference'));\n      await flushMicrotasks();\n\n      await userEvent.tab({ shift: true });\n\n      expect(screen.getByTestId('floating')).toBeInTheDocument();\n\n      await userEvent.tab({ shift: true });\n\n      expect(screen.queryByTestId('floating')).not.toBeInTheDocument();\n    });\n  });\n\n  describe('Navigation', () => {\n    test('does not focus reference when hovering it', async () => {\n      render(<Navigation />);\n      await userEvent.hover(screen.getByText('Product'));\n      await userEvent.unhover(screen.getByText('Product'));\n      expect(screen.getByText('Product')).not.toHaveFocus();\n    });\n\n    test('returns focus to reference when floating element was opened by hover but is closed by esc key', async () => {\n      render(<Navigation />);\n      await userEvent.hover(screen.getByText('Product'));\n      await flushMicrotasks();\n      await userEvent.keyboard('{Escape}');\n      expect(screen.getByText('Product')).toHaveFocus();\n    });\n\n    test('returns focus to reference when floating element was opened by hover but is closed by an explicit close button', async () => {\n      render(<Navigation />);\n      await userEvent.hover(screen.getByText('Product'));\n      await flushMicrotasks();\n      await userEvent.click(screen.getByText('Close').parentElement!);\n      await userEvent.keyboard('{Tab}');\n      expect(screen.getByText('Close')).toHaveFocus();\n      await userEvent.keyboard('{Enter}');\n      expect(screen.getByText('Product')).toHaveFocus();\n    });\n\n    test('does not re-open after closing via escape key', async () => {\n      render(<Navigation />);\n      await userEvent.hover(screen.getByText('Product'));\n      await userEvent.keyboard('{Escape}');\n      expect(screen.queryByText('Link 1')).not.toBeInTheDocument();\n    });\n\n    test('closes when unhovering floating element even when focus is inside it', async () => {\n      render(<Navigation />);\n      await userEvent.hover(screen.getByText('Product'));\n      await userEvent.click(screen.getByTestId('subnavigation'));\n      await userEvent.unhover(screen.getByTestId('subnavigation'));\n      await userEvent.hover(screen.getByText('Product'));\n      await userEvent.unhover(screen.getByText('Product'));\n      expect(screen.queryByTestId('subnavigation')).not.toBeInTheDocument();\n    });\n  });\n\n  describe('prop: restoreFocus', () => {\n    function App({ restoreFocus = true }: { restoreFocus?: boolean }) {\n      const [isOpen, setIsOpen] = React.useState(false);\n      const [removed, setRemoved] = React.useState(false);\n      const twoRef = React.useRef<HTMLButtonElement | null>(null);\n\n      const { refs, context } = useFloating({\n        open: isOpen,\n        onOpenChange: setIsOpen,\n      });\n\n      const click = useClick(context);\n      const { getReferenceProps, getFloatingProps } = useInteractions([click]);\n\n      return (\n        <>\n          <button onClick={() => setRemoved(true)}>remove</button>\n          <button ref={refs.setReference} {...getReferenceProps()} data-testid=\"reference\" />\n          {isOpen && (\n            <FloatingFocusManager\n              context={context}\n              restoreFocus={restoreFocus}\n              initialFocus={twoRef}\n            >\n              <div ref={refs.setFloating} {...getFloatingProps()} data-testid=\"floating\">\n                <button>one</button>\n                {!removed && <button ref={twoRef}>two</button>}\n                <button>three</button>\n              </div>\n            </FloatingFocusManager>\n          )}\n        </>\n      );\n    }\n\n    test.skipIf(isJSDOM)(\n      'true: restores focus to nearest tabbable element if currently focused element is removed',\n      async () => {\n        render(<App />);\n\n        await userEvent.click(screen.getByTestId('reference'));\n        await flushMicrotasks();\n\n        const two = screen.getByRole('button', { name: 'two' });\n        const three = screen.getByRole('button', { name: 'three' });\n        const remove = screen.getByText('remove');\n\n        expect(two).toHaveFocus();\n\n        fireEvent.click(remove);\n\n        await waitFor(() => {\n          expect(three).toHaveFocus();\n        });\n      },\n    );\n\n    test.skipIf(isJSDOM)(\n      'false: does not restore focus to nearest tabbable element if currently focused element is removed',\n      async () => {\n        render(<App restoreFocus={false} />);\n\n        await userEvent.click(screen.getByTestId('reference'));\n        await flushMicrotasks();\n\n        const two = screen.getByRole('button', { name: 'two' });\n        const remove = screen.getByText('remove');\n\n        expect(two).toHaveFocus();\n\n        fireEvent.click(remove);\n        await flushMicrotasks();\n\n        await waitFor(() => {\n          expect(document.body).toHaveFocus();\n        });\n      },\n    );\n  });\n\n  test('trapped combobox prevents focus moving outside floating element', async () => {\n    function App() {\n      const [isOpen, setIsOpen] = React.useState(false);\n\n      const { refs, floatingStyles, context } = useFloating({\n        open: isOpen,\n        onOpenChange: setIsOpen,\n      });\n\n      const role = useRole(context);\n      const dismiss = useDismiss(context);\n      const click = useClick(context);\n\n      const { getReferenceProps, getFloatingProps } = useInteractions([role, dismiss, click]);\n\n      return (\n        <div className=\"App\">\n          <input\n            ref={refs.setReference}\n            {...getReferenceProps()}\n            data-testid=\"input\"\n            role=\"combobox\"\n          />\n          {isOpen && (\n            <FloatingFocusManager context={context}>\n              <div ref={refs.setFloating} style={floatingStyles} {...getFloatingProps()}>\n                <button>one</button>\n                <button>two</button>\n              </div>\n            </FloatingFocusManager>\n          )}\n        </div>\n      );\n    }\n\n    render(<App />);\n    await userEvent.click(screen.getByTestId('input'));\n    await flushMicrotasks();\n    expect(screen.getByTestId('input')).not.toHaveFocus();\n    expect(screen.getByRole('button', { name: 'one' })).toHaveFocus();\n    await userEvent.tab();\n    expect(screen.getByRole('button', { name: 'two' })).toHaveFocus();\n    await userEvent.tab();\n    expect(screen.getByRole('button', { name: 'one' })).toHaveFocus();\n    await flushMicrotasks();\n  });\n\n  test('untrapped combobox creates non-modal focus management', async () => {\n    function App() {\n      const [isOpen, setIsOpen] = React.useState(false);\n\n      const { refs, floatingStyles, context } = useFloating({\n        open: isOpen,\n        onOpenChange: setIsOpen,\n      });\n\n      const role = useRole(context);\n      const dismiss = useDismiss(context);\n      const click = useClick(context);\n\n      const { getReferenceProps, getFloatingProps } = useInteractions([role, dismiss, click]);\n\n      return (\n        <>\n          <input\n            ref={refs.setReference}\n            {...getReferenceProps()}\n            data-testid=\"input\"\n            role=\"combobox\"\n          />\n          {isOpen && (\n            <FloatingPortal>\n              <FloatingFocusManager context={context} initialFocus={false} modal={false}>\n                <div ref={refs.setFloating} style={floatingStyles} {...getFloatingProps()}>\n                  <button>one</button>\n                  <button>two</button>\n                </div>\n              </FloatingFocusManager>\n            </FloatingPortal>\n          )}\n          <button>outside</button>\n        </>\n      );\n    }\n\n    render(<App />);\n    await userEvent.click(screen.getByTestId('input'));\n    await flushMicrotasks();\n    expect(screen.getByTestId('input')).toHaveFocus();\n    await userEvent.tab();\n    expect(screen.getByRole('button', { name: 'one' })).toHaveFocus();\n    await userEvent.tab({ shift: true });\n    expect(screen.getByTestId('input')).toHaveFocus();\n  });\n\n  test('returns focus to last connected element', async () => {\n    function Drawer({\n      open,\n      onOpenChange,\n    }: {\n      open: boolean;\n      onOpenChange: (open: boolean) => void;\n    }) {\n      const { refs, context } = useFloating({ open, onOpenChange });\n      const dismiss = useDismiss(context);\n      const { getFloatingProps } = useInteractions([dismiss]);\n\n      return (\n        <FloatingFocusManager context={context}>\n          <div ref={refs.setFloating} {...getFloatingProps()}>\n            <button data-testid=\"child-reference\" />\n          </div>\n        </FloatingFocusManager>\n      );\n    }\n\n    function Parent() {\n      const [isOpen, setIsOpen] = React.useState(false);\n      const [isDrawerOpen, setIsDrawerOpen] = React.useState(false);\n\n      const { refs, context } = useFloating({\n        open: isOpen,\n        onOpenChange: setIsOpen,\n      });\n\n      const dismiss = useDismiss(context);\n      const click = useClick(context);\n\n      const { getReferenceProps, getFloatingProps } = useInteractions([click, dismiss]);\n\n      return (\n        <>\n          <button ref={refs.setReference} data-testid=\"parent-reference\" {...getReferenceProps()} />\n          {isOpen && (\n            <FloatingFocusManager context={context}>\n              <div ref={refs.setFloating} {...getFloatingProps()}>\n                Parent Floating\n                <button\n                  data-testid=\"parent-floating-reference\"\n                  onClick={() => {\n                    setIsDrawerOpen(true);\n                    setIsOpen(false);\n                  }}\n                />\n              </div>\n            </FloatingFocusManager>\n          )}\n          {isDrawerOpen && <Drawer open={isDrawerOpen} onOpenChange={setIsDrawerOpen} />}\n        </>\n      );\n    }\n\n    render(<Parent />);\n    await userEvent.click(screen.getByTestId('parent-reference'));\n    await flushMicrotasks();\n    expect(screen.getByTestId('parent-floating-reference')).toHaveFocus();\n    await userEvent.click(screen.getByTestId('parent-floating-reference'));\n    await flushMicrotasks();\n    expect(screen.getByTestId('child-reference')).toHaveFocus();\n    await userEvent.keyboard('{Escape}');\n    expect(screen.getByTestId('parent-reference')).toHaveFocus();\n  });\n\n  test('focus is placed on element with floating props when floating element is a wrapper', async () => {\n    function App() {\n      const [isOpen, setIsOpen] = React.useState(false);\n\n      const { refs, context } = useFloating({\n        open: isOpen,\n        onOpenChange: setIsOpen,\n      });\n\n      const role = useRole(context);\n\n      const { getReferenceProps, getFloatingProps } = useInteractions([role]);\n\n      return (\n        <>\n          <button\n            ref={refs.setReference}\n            {...getReferenceProps({\n              onClick: () => setIsOpen((v) => !v),\n            })}\n          />\n          {isOpen && (\n            <FloatingFocusManager context={context}>\n              <div ref={refs.setFloating} data-testid=\"outer\">\n                <div {...getFloatingProps()} data-testid=\"inner\" />\n              </div>\n            </FloatingFocusManager>\n          )}\n        </>\n      );\n    }\n\n    render(<App />);\n\n    await userEvent.click(screen.getByRole('button'));\n    await flushMicrotasks();\n\n    expect(screen.getByTestId('inner')).toHaveFocus();\n  });\n\n  test('floating element closes upon tabbing out of modal combobox', async () => {\n    function App() {\n      const [isOpen, setIsOpen] = React.useState(false);\n\n      const { refs, context } = useFloating({\n        open: isOpen,\n        onOpenChange: setIsOpen,\n      });\n\n      const click = useClick(context);\n\n      const { getReferenceProps, getFloatingProps } = useInteractions([click]);\n\n      return (\n        <>\n          <input\n            ref={refs.setReference}\n            {...getReferenceProps()}\n            data-testid=\"input\"\n            role=\"combobox\"\n          />\n          {isOpen && (\n            <FloatingFocusManager context={context} initialFocus={false}>\n              <div ref={refs.setFloating} {...getFloatingProps()} data-testid=\"floating\">\n                <button tabIndex={-1}>one</button>\n              </div>\n            </FloatingFocusManager>\n          )}\n          <button data-testid=\"after\" />\n        </>\n      );\n    }\n\n    render(<App />);\n    await userEvent.click(screen.getByTestId('input'));\n    await flushMicrotasks();\n    expect(screen.getByTestId('input')).toHaveFocus();\n    await userEvent.tab();\n    await flushMicrotasks();\n    expect(screen.getByTestId('after')).toHaveFocus();\n  });\n\n  test('untrapped typeable combobox closes on second tab sequence (click -> tab -> click -> tab)', async () => {\n    function App() {\n      const [isOpen, setIsOpen] = React.useState(false);\n\n      const { refs, context } = useFloating({\n        open: isOpen,\n        onOpenChange: setIsOpen,\n      });\n\n      const click = useClick(context);\n      const { getReferenceProps, getFloatingProps } = useInteractions([click]);\n\n      return (\n        <>\n          <input\n            ref={refs.setReference}\n            {...getReferenceProps()}\n            data-testid=\"input\"\n            role=\"combobox\"\n          />\n          {isOpen && (\n            <FloatingFocusManager context={context} initialFocus={false} modal>\n              <div ref={refs.setFloating} {...getFloatingProps()} data-testid=\"floating\">\n                <button tabIndex={-1}>one</button>\n              </div>\n            </FloatingFocusManager>\n          )}\n          <button data-testid=\"after\" />\n        </>\n      );\n    }\n\n    render(<App />);\n\n    await userEvent.click(screen.getByTestId('input'));\n    await flushMicrotasks();\n\n    expect(screen.getByTestId('input')).toHaveFocus();\n\n    await userEvent.tab();\n    await flushMicrotasks();\n\n    expect(screen.getByTestId('after')).toHaveFocus();\n    expect(screen.queryByTestId('floating')).not.toBeInTheDocument();\n\n    await userEvent.click(screen.getByTestId('input'));\n    await flushMicrotasks();\n\n    expect(screen.getByTestId('input')).toHaveFocus();\n\n    await userEvent.tab();\n    await flushMicrotasks();\n\n    expect(screen.getByTestId('after')).toHaveFocus();\n    expect(screen.queryByTestId('floating')).not.toBeInTheDocument();\n  });\n\n  test('focus does not return to reference when floating element is triggered by hover', async () => {\n    function App() {\n      const [isOpen, setIsOpen] = React.useState(false);\n\n      const { refs, context } = useFloating({\n        open: isOpen,\n        onOpenChange: setIsOpen,\n      });\n\n      const hover = useHover(context);\n\n      const { getReferenceProps, getFloatingProps } = useInteractions([hover]);\n\n      return (\n        <>\n          <button ref={refs.setReference} {...getReferenceProps()} data-testid=\"reference\" />\n          {isOpen && (\n            <FloatingFocusManager context={context}>\n              <div ref={refs.setFloating} {...getFloatingProps()} data-testid=\"floating\" />\n            </FloatingFocusManager>\n          )}\n        </>\n      );\n    }\n\n    render(<App />);\n\n    const reference = screen.getByTestId('reference');\n\n    act(() => reference.focus());\n\n    await userEvent.hover(reference);\n    await flushMicrotasks();\n\n    expect(screen.getByTestId('floating')).toHaveFocus();\n\n    await userEvent.unhover(screen.getByTestId('floating'));\n\n    expect(screen.getByTestId('reference')).not.toHaveFocus();\n  });\n\n  test('uses aria-hidden instead of inert on outside nodes if opened with hover and modal=true', async () => {\n    function App() {\n      const [isOpen, setIsOpen] = React.useState(false);\n\n      const { refs, context } = useFloating({\n        open: isOpen,\n        onOpenChange: setIsOpen,\n      });\n\n      const hover = useHover(context);\n\n      const { getReferenceProps, getFloatingProps } = useInteractions([hover]);\n\n      return (\n        <>\n          <button ref={refs.setReference} {...getReferenceProps()} data-testid=\"reference\" />\n          {isOpen && (\n            <FloatingFocusManager context={context}>\n              <div ref={refs.setFloating} {...getFloatingProps()} data-testid=\"floating\" />\n            </FloatingFocusManager>\n          )}\n          <button>outside</button>\n        </>\n      );\n    }\n\n    render(<App />);\n\n    await userEvent.hover(screen.getByTestId('reference'));\n    await flushMicrotasks();\n\n    expect(screen.getByText('outside')).not.toHaveAttribute('inert');\n    expect(screen.getByText('outside')).toHaveAttribute('aria-hidden', 'true');\n  });\n\n  test('floating element with no focusable elements and no listbox role gets tabIndex=0 when initialFocus is -1', async () => {\n    function App() {\n      const [isOpen, setIsOpen] = React.useState(false);\n\n      const { refs, context } = useFloating({\n        open: isOpen,\n        onOpenChange: setIsOpen,\n      });\n\n      return (\n        <>\n          <button data-testid=\"reference\" ref={refs.setReference} onClick={() => setIsOpen(true)} />\n          {isOpen && (\n            <FloatingFocusManager context={context} initialFocus={false} modal={false}>\n              <div ref={refs.setFloating} data-testid=\"floating\" role=\"dialog\" />\n            </FloatingFocusManager>\n          )}\n        </>\n      );\n    }\n\n    render(<App />);\n\n    const reference = screen.getByTestId('reference');\n    await userEvent.click(reference);\n    await flushMicrotasks();\n    fireEvent.focusOut(reference);\n    await flushMicrotasks();\n\n    expect(screen.getByTestId('floating')).toHaveAttribute('tabindex', '0');\n  });\n\n  test('floating element with listbox role ignores tabIndex setting', async () => {\n    function App() {\n      const [isOpen, setIsOpen] = React.useState(false);\n\n      const { refs, context } = useFloating({\n        open: isOpen,\n        onOpenChange: setIsOpen,\n      });\n\n      const click = useClick(context);\n      const { getReferenceProps, getFloatingProps } = useInteractions([click]);\n\n      return (\n        <>\n          <button\n            data-testid=\"reference\"\n            ref={refs.setReference}\n            onClick={() => setIsOpen(true)}\n            {...getReferenceProps()}\n          >\n            ref\n          </button>\n          {isOpen && (\n            <FloatingFocusManager context={context} initialFocus={false} modal={false}>\n              <div\n                ref={refs.setFloating}\n                role=\"listbox\"\n                data-testid=\"floating\"\n                {...getFloatingProps()}\n              >\n                floating\n              </div>\n            </FloatingFocusManager>\n          )}\n        </>\n      );\n    }\n\n    render(<App />);\n    await userEvent.click(screen.getByTestId('reference'));\n    await flushMicrotasks();\n\n    expect(screen.getByTestId('floating')).toHaveAttribute('tabindex', '-1');\n  });\n\n  test('handles manual tabindex on dialog floating element', async () => {\n    function App() {\n      const [isOpen, setIsOpen] = React.useState(false);\n\n      const { refs, context } = useFloating({\n        open: isOpen,\n        onOpenChange: setIsOpen,\n      });\n\n      return (\n        <>\n          <button data-testid=\"reference\" ref={refs.setReference} onClick={() => setIsOpen(true)} />\n          {isOpen && (\n            <FloatingFocusManager context={context} modal={false}>\n              <div ref={refs.setFloating} data-testid=\"floating\" role=\"dialog\" />\n            </FloatingFocusManager>\n          )}\n        </>\n      );\n    }\n\n    render(<App />);\n\n    await userEvent.click(screen.getByTestId('reference'));\n    await flushMicrotasks();\n\n    expect(screen.getByTestId('floating')).toHaveAttribute('tabindex', '0');\n    await userEvent.tab({ shift: true });\n    expect(screen.getByTestId('reference')).toHaveFocus();\n    await userEvent.tab();\n    expect(screen.getByTestId('floating')).toHaveFocus();\n  });\n\n  test('standard tabbing back and forth of a non-modal floating element', async () => {\n    function App() {\n      const [isOpen, setIsOpen] = React.useState(false);\n\n      const { refs, context } = useFloating({\n        open: isOpen,\n        onOpenChange: setIsOpen,\n      });\n\n      const click = useClick(context);\n      const { getReferenceProps, getFloatingProps } = useInteractions([click]);\n\n      return (\n        <>\n          <button data-testid=\"reference\" ref={refs.setReference} {...getReferenceProps()} />\n          {isOpen && (\n            <FloatingPortal>\n              <FloatingFocusManager context={context} modal={false}>\n                <div\n                  ref={refs.setFloating}\n                  data-testid=\"floating\"\n                  role=\"dialog\"\n                  {...getFloatingProps()}\n                >\n                  <button data-testid=\"inner\">inner</button>\n                </div>\n              </FloatingFocusManager>\n            </FloatingPortal>\n          )}\n        </>\n      );\n    }\n    render(<App />);\n\n    await userEvent.click(screen.getByTestId('reference'));\n    await flushMicrotasks();\n\n    expect(screen.getByTestId('inner')).toHaveFocus();\n    await userEvent.tab({ shift: true });\n    expect(screen.getByTestId('reference')).toHaveFocus();\n    await userEvent.tab();\n    expect(screen.getByTestId('inner')).toHaveFocus();\n  });\n});\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/components/FloatingFocusManager.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { tabbable, isTabbable, focusable, type FocusableElement } from 'tabbable';\nimport { getNodeName, isHTMLElement } from '@floating-ui/utils/dom';\nimport { useMergedRefs } from '@base-ui/utils/useMergedRefs';\nimport { useValueAsRef } from '@base-ui/utils/useValueAsRef';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useTimeout } from '@base-ui/utils/useTimeout';\nimport { isWebKit } from '@base-ui/utils/detectBrowser';\nimport type { InteractionType } from '@base-ui/utils/useEnhancedClickHandler';\nimport { useAnimationFrame } from '@base-ui/utils/useAnimationFrame';\nimport { ownerDocument, ownerWindow } from '@base-ui/utils/owner';\nimport { FocusGuard } from '../../utils/FocusGuard';\nimport {\n  activeElement,\n  contains,\n  getTarget,\n  isTypeableCombobox,\n  isVirtualClick,\n  isVirtualPointerEvent,\n  stopEvent,\n  getNodeAncestors,\n  getNodeChildren,\n  getFloatingFocusElement,\n  getTabbableOptions,\n  isOutsideEvent,\n  getNextTabbable,\n  getPreviousTabbable,\n  isElementVisible,\n  isTypeableElement,\n} from '../utils';\nimport type { FloatingContext, FloatingRootContext } from '../types';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport { createAttribute } from '../utils/createAttribute';\nimport { enqueueFocus } from '../utils/enqueueFocus';\nimport { markOthers } from '../utils/markOthers';\nimport { usePortalContext } from './FloatingPortal';\nimport { useFloatingTree } from './FloatingTree';\nimport { FloatingTreeStore } from '../components/FloatingTreeStore';\nimport { CLICK_TRIGGER_IDENTIFIER } from '../../utils/constants';\nimport { FloatingUIOpenChangeDetails } from '../../utils/types';\nimport { resolveRef } from '../../utils/resolveRef';\n\nfunction getEventType(event: Event, lastInteractionType?: InteractionType): InteractionType {\n  const win = ownerWindow(event.target);\n  if (event instanceof win.KeyboardEvent) {\n    return 'keyboard';\n  }\n  if (event instanceof win.FocusEvent) {\n    // Focus events can be caused by a preceding pointer interaction (e.g., focusout on outside press).\n    // Prefer the last known pointer type if provided, else treat as keyboard.\n    return lastInteractionType || 'keyboard';\n  }\n  if ('pointerType' in event) {\n    return (event.pointerType as React.PointerEvent['pointerType']) || 'keyboard';\n  }\n  if ('touches' in event) {\n    return 'touch';\n  }\n  if (event instanceof win.MouseEvent) {\n    // onClick events may not contain pointer events, and will fall through to here\n    return lastInteractionType || (event.detail === 0 ? 'keyboard' : 'mouse');\n  }\n  return '';\n}\n\nconst LIST_LIMIT = 20;\nlet previouslyFocusedElements: WeakRef<Element>[] = [];\n\nfunction clearDisconnectedPreviouslyFocusedElements() {\n  previouslyFocusedElements = previouslyFocusedElements.filter((entry) => {\n    return entry.deref()?.isConnected;\n  });\n}\n\nfunction addPreviouslyFocusedElement(element: Element | null | undefined) {\n  clearDisconnectedPreviouslyFocusedElements();\n  if (element && getNodeName(element) !== 'body') {\n    previouslyFocusedElements.push(new WeakRef(element));\n    if (previouslyFocusedElements.length > LIST_LIMIT) {\n      previouslyFocusedElements = previouslyFocusedElements.slice(-LIST_LIMIT);\n    }\n  }\n}\n\nfunction getPreviouslyFocusedElement() {\n  clearDisconnectedPreviouslyFocusedElements();\n  return previouslyFocusedElements[previouslyFocusedElements.length - 1]?.deref();\n}\n\nfunction getFirstTabbableElement(container: Element | null) {\n  if (!container) {\n    return null;\n  }\n\n  const tabbableOptions = getTabbableOptions();\n  if (isTabbable(container, tabbableOptions)) {\n    return container;\n  }\n\n  return tabbable(container, tabbableOptions)[0] || container;\n}\n\nfunction isFocusable(element: Element | null) {\n  if (!element || !element.isConnected) {\n    return false;\n  }\n\n  if (typeof element.checkVisibility === 'function') {\n    return element.checkVisibility();\n  }\n\n  return isElementVisible(element);\n}\n\nfunction handleTabIndex(\n  floatingFocusElement: HTMLElement,\n  orderRef: React.RefObject<Array<'reference' | 'floating' | 'content'>>,\n) {\n  if (\n    !orderRef.current.includes('floating') &&\n    !floatingFocusElement.getAttribute('role')?.includes('dialog')\n  ) {\n    return;\n  }\n\n  const options = getTabbableOptions();\n  const focusableElements = focusable(floatingFocusElement, options);\n  const tabbableContent = focusableElements.filter((element) => {\n    const dataTabIndex = element.getAttribute('data-tabindex') || '';\n    return (\n      isTabbable(element, options) ||\n      (element.hasAttribute('data-tabindex') && !dataTabIndex.startsWith('-'))\n    );\n  });\n  const tabIndex = floatingFocusElement.getAttribute('tabindex');\n\n  if (orderRef.current.includes('floating') || tabbableContent.length === 0) {\n    if (tabIndex !== '0') {\n      floatingFocusElement.setAttribute('tabindex', '0');\n    }\n  } else if (\n    tabIndex !== '-1' ||\n    (floatingFocusElement.hasAttribute('data-tabindex') &&\n      floatingFocusElement.getAttribute('data-tabindex') !== '-1')\n  ) {\n    floatingFocusElement.setAttribute('tabindex', '-1');\n    floatingFocusElement.setAttribute('data-tabindex', '-1');\n  }\n}\n\nexport interface FloatingFocusManagerProps {\n  children: React.JSX.Element;\n  /**\n   * The floating context returned from `useFloatingRootContext`.\n   */\n  context: FloatingRootContext | FloatingContext;\n  /**\n   * The interaction type used to open the floating element.\n   */\n  openInteractionType?: InteractionType | null | undefined;\n  /**\n   * Whether or not the focus manager should be disabled. Useful to delay focus\n   * management until after a transition completes or some other conditional\n   * state.\n   * @default false\n   */\n  disabled?: boolean | undefined;\n  /**\n   * Determines the element to focus when the floating element is opened.\n   *\n   * - `false`: Do not move focus.\n   * - `true`: Move focus based on the default behavior (first tabbable element or floating element).\n   * - `RefObject`: Move focus to the ref element.\n   * - `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`).\n   *   Return an element to focus, `true` to use default behavior, `null` to fallback to default behavior,\n   *   or `false`/`undefined` to do nothing.\n   * @default true\n   */\n  initialFocus?:\n    | boolean\n    | React.RefObject<HTMLElement | null>\n    | ((openType: InteractionType) => boolean | HTMLElement | null | void)\n    | undefined;\n  /**\n   * Determines the element to focus when the floating element is closed.\n   *\n   * - `false`: Do not move focus.\n   * - `true`: Move focus based on the default behavior (reference or previously focused element).\n   * - `RefObject`: Move focus to the ref element.\n   * - `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`).\n   *   Return an element to focus, `true` to use the default behavior, `null` to fallback to default behavior,\n   *   or `false`/`undefined` to do nothing.\n   * @default true\n   */\n  returnFocus?:\n    | boolean\n    | React.RefObject<HTMLElement | null>\n    | ((closeType: InteractionType) => boolean | HTMLElement | null | void)\n    | undefined;\n  /**\n   * Determines where focus should be restored if focus inside the floating element is lost\n   * (such as due to the removal of the currently focused element from the DOM).\n   *\n   * - `true`: restore to the nearest tabbable element inside the floating tree (previous\n   *   tabbable if possible, otherwise the last tabbable, then the floating element itself)\n   * - `'popup'`: restore directly to the floating element (container) itself\n   * - `false`: do not restore focus\n   * @default false\n   */\n  restoreFocus?: boolean | 'popup' | undefined;\n  /**\n   * Determines if focus is “modal”, meaning focus is fully trapped inside the\n   * floating element and outside content cannot be accessed. This includes\n   * screen reader virtual cursors.\n   * @default true\n   */\n  modal?: boolean | undefined;\n  /**\n   * Determines whether `focusout` event listeners that control whether the\n   * floating element should be closed if the focus moves outside of it are\n   * attached to the reference and floating elements. This affects non-modal\n   * focus management.\n   * @default true\n   */\n  closeOnFocusOut?: boolean | undefined;\n  /**\n   * Overrides the element to focus when tabbing forward out of the floating element.\n   */\n  nextFocusableElement?: HTMLElement | React.RefObject<HTMLElement | null> | null | undefined;\n  /**\n   * Overrides the element to focus when tabbing backward out of the floating element.\n   */\n  previousFocusableElement?: HTMLElement | React.RefObject<HTMLElement | null> | null | undefined;\n  /**\n   * Ref to the focus guard preceding the floating element content.\n   * Can be useful to focus the popup programmatically.\n   */\n  beforeContentFocusGuardRef?: React.RefObject<HTMLSpanElement | null> | undefined;\n  /**\n   * External FlatingTree to use when the one provided by context can't be used.\n   */\n  externalTree?: FloatingTreeStore | undefined;\n  /**\n   * Additional elements that should be treated as part of the floating subtree\n   * even if they are rendered outside the floating element itself.\n   */\n  getInsideElements?: (() => Array<Element | null | undefined>) | undefined;\n}\n\n/**\n * Provides focus management for the floating element.\n * @see https://floating-ui.com/docs/FloatingFocusManager\n * @internal\n */\nexport function FloatingFocusManager(props: FloatingFocusManagerProps): React.JSX.Element {\n  const {\n    context,\n    children,\n    disabled = false,\n    initialFocus = true,\n    returnFocus = true,\n    restoreFocus = false,\n    modal = true,\n    closeOnFocusOut = true,\n    openInteractionType = '',\n    nextFocusableElement,\n    previousFocusableElement,\n    beforeContentFocusGuardRef,\n    externalTree,\n    getInsideElements,\n  } = props;\n\n  const store = 'rootStore' in context ? context.rootStore : context;\n\n  const open = store.useState('open');\n  const domReference = store.useState('domReferenceElement');\n  const floating = store.useState('floatingElement');\n  const { events, dataRef } = store.context;\n\n  const getNodeId = useStableCallback(() => dataRef.current.floatingContext?.nodeId);\n\n  const ignoreInitialFocus = initialFocus === false;\n  // If the reference is a combobox and is typeable (e.g. input/textarea),\n  // there are different focus semantics. The guards should not be rendered, but\n  // aria-hidden should be applied to all nodes still. Further, the visually\n  // hidden dismiss button should only appear at the end of the list, not the\n  // start.\n  const isUntrappedTypeableCombobox = isTypeableCombobox(domReference) && ignoreInitialFocus;\n\n  const orderRef = React.useRef<Array<'reference' | 'floating' | 'content'>>(['content']);\n  const initialFocusRef = useValueAsRef(initialFocus);\n  const returnFocusRef = useValueAsRef(returnFocus);\n  const openInteractionTypeRef = useValueAsRef(openInteractionType);\n\n  const tree = useFloatingTree(externalTree);\n  const portalContext = usePortalContext();\n\n  const preventReturnFocusRef = React.useRef(false);\n  const isPointerDownRef = React.useRef(false);\n  const pointerDownOutsideRef = React.useRef(false);\n  const tabbableIndexRef = React.useRef(-1);\n  const closeTypeRef = React.useRef<InteractionType>('');\n  const lastInteractionTypeRef = React.useRef<InteractionType>('');\n\n  const beforeGuardRef = React.useRef<HTMLSpanElement | null>(null);\n  const afterGuardRef = React.useRef<HTMLSpanElement | null>(null);\n\n  const mergedBeforeGuardRef = useMergedRefs(\n    beforeGuardRef,\n    beforeContentFocusGuardRef,\n    portalContext?.beforeInsideRef,\n  );\n  const mergedAfterGuardRef = useMergedRefs(afterGuardRef, portalContext?.afterInsideRef);\n\n  const blurTimeout = useTimeout();\n  const pointerDownTimeout = useTimeout();\n  const restoreFocusFrame = useAnimationFrame();\n\n  const isInsidePortal = portalContext != null;\n  const floatingFocusElement = getFloatingFocusElement(floating);\n\n  const getTabbableContent = useStableCallback(\n    (container: Element | null = floatingFocusElement) => {\n      return container ? tabbable(container, getTabbableOptions()) : [];\n    },\n  );\n\n  const getResolvedInsideElements = useStableCallback(\n    () => getInsideElements?.().filter((element): element is Element => element != null) ?? [],\n  );\n\n  const getTabbableElements = useStableCallback((container?: Element) => {\n    const content = getTabbableContent(container);\n\n    return orderRef.current\n      .map(() => content)\n      .filter(Boolean)\n      .flat() as Array<FocusableElement>;\n  });\n\n  // Prevent Tab from escaping the modal when there are no tabbable elements.\n  React.useEffect(() => {\n    if (disabled || !modal) {\n      return undefined;\n    }\n\n    function onKeyDown(event: KeyboardEvent) {\n      if (event.key === 'Tab') {\n        // The focus guards have nothing to focus, so we need to stop the event.\n        if (\n          contains(floatingFocusElement, activeElement(ownerDocument(floatingFocusElement))) &&\n          getTabbableContent().length === 0 &&\n          !isUntrappedTypeableCombobox\n        ) {\n          stopEvent(event);\n        }\n      }\n    }\n\n    const doc = ownerDocument(floatingFocusElement);\n    doc.addEventListener('keydown', onKeyDown);\n    return () => {\n      doc.removeEventListener('keydown', onKeyDown);\n    };\n  }, [\n    disabled,\n    domReference,\n    floatingFocusElement,\n    modal,\n    orderRef,\n    isUntrappedTypeableCombobox,\n    getTabbableContent,\n    getTabbableElements,\n  ]);\n\n  // Track pointer/keyboard interactions to disambiguate focus and outside presses.\n  React.useEffect(() => {\n    if (disabled || !open) {\n      return undefined;\n    }\n\n    const doc = ownerDocument(floatingFocusElement);\n\n    function clearPointerDownOutside() {\n      pointerDownOutsideRef.current = false;\n    }\n\n    function onPointerDown(event: PointerEvent) {\n      const target = getTarget(event) as Element | null;\n      const insideElements = getResolvedInsideElements();\n      const pointerTargetInside =\n        contains(floating, target) ||\n        contains(domReference, target) ||\n        contains(portalContext?.portalNode, target) ||\n        insideElements.some((element) => element === target || contains(element, target));\n      pointerDownOutsideRef.current = !pointerTargetInside;\n      lastInteractionTypeRef.current =\n        (event.pointerType as React.PointerEvent['pointerType']) || 'keyboard';\n\n      if (target?.closest(`[${CLICK_TRIGGER_IDENTIFIER}]`)) {\n        isPointerDownRef.current = true;\n      }\n    }\n\n    function onKeyDown() {\n      lastInteractionTypeRef.current = 'keyboard';\n    }\n\n    doc.addEventListener('pointerdown', onPointerDown, true);\n    doc.addEventListener('pointerup', clearPointerDownOutside, true);\n    doc.addEventListener('pointercancel', clearPointerDownOutside, true);\n    doc.addEventListener('keydown', onKeyDown, true);\n    return () => {\n      doc.removeEventListener('pointerdown', onPointerDown, true);\n      doc.removeEventListener('pointerup', clearPointerDownOutside, true);\n      doc.removeEventListener('pointercancel', clearPointerDownOutside, true);\n      doc.removeEventListener('keydown', onKeyDown, true);\n    };\n  }, [\n    disabled,\n    floating,\n    domReference,\n    floatingFocusElement,\n    open,\n    portalContext,\n    getResolvedInsideElements,\n  ]);\n\n  // Close on focus out and restore focus within the floating tree when needed.\n  React.useEffect(() => {\n    if (disabled || !closeOnFocusOut) {\n      return undefined;\n    }\n\n    const doc = ownerDocument(floatingFocusElement);\n\n    // In Safari, buttons lose focus when pressing them.\n    function handlePointerDown() {\n      isPointerDownRef.current = true;\n      pointerDownTimeout.start(0, () => {\n        isPointerDownRef.current = false;\n      });\n    }\n\n    function handleFocusIn(event: FocusEvent) {\n      const target = getTarget(event) as Element | null;\n      const tabbableContent = getTabbableContent() as Array<Element | null>;\n      const tabbableIndex = tabbableContent.indexOf(target);\n      if (tabbableIndex !== -1) {\n        tabbableIndexRef.current = tabbableIndex;\n      }\n    }\n\n    function handleFocusOutside(event: FocusEvent) {\n      const relatedTarget = event.relatedTarget as HTMLElement | null;\n      const currentTarget = event.currentTarget;\n      const target = getTarget(event) as HTMLElement | null;\n\n      queueMicrotask(() => {\n        const nodeId = getNodeId();\n        const triggers = store.context.triggerElements;\n        const insideElements = getResolvedInsideElements();\n        const isRelatedFocusGuard =\n          relatedTarget?.hasAttribute(createAttribute('focus-guard')) &&\n          [\n            beforeGuardRef.current,\n            afterGuardRef.current,\n            portalContext?.beforeInsideRef.current,\n            portalContext?.afterInsideRef.current,\n            portalContext?.beforeOutsideRef.current,\n            portalContext?.afterOutsideRef.current,\n            resolveRef(previousFocusableElement),\n            resolveRef(nextFocusableElement),\n          ].includes(relatedTarget);\n\n        const movedToUnrelatedNode = !(\n          contains(domReference, relatedTarget) ||\n          contains(floating, relatedTarget) ||\n          contains(relatedTarget, floating) ||\n          contains(portalContext?.portalNode, relatedTarget) ||\n          insideElements.some(\n            (element) => element === relatedTarget || contains(element, relatedTarget),\n          ) ||\n          (relatedTarget != null && triggers.hasElement(relatedTarget)) ||\n          triggers.hasMatchingElement((trigger) => contains(trigger, relatedTarget)) ||\n          isRelatedFocusGuard ||\n          (tree &&\n            (getNodeChildren(tree.nodesRef.current, nodeId).find(\n              (node) =>\n                contains(node.context?.elements.floating, relatedTarget) ||\n                contains(node.context?.elements.domReference, relatedTarget),\n            ) ||\n              getNodeAncestors(tree.nodesRef.current, nodeId).find(\n                (node) =>\n                  [\n                    node.context?.elements.floating,\n                    getFloatingFocusElement(node.context?.elements.floating),\n                  ].includes(relatedTarget) ||\n                  node.context?.elements.domReference === relatedTarget,\n              )))\n        );\n\n        if (currentTarget === domReference && floatingFocusElement) {\n          handleTabIndex(floatingFocusElement, orderRef);\n        }\n\n        // Restore focus to the previous tabbable element index to prevent\n        // focus from being lost outside the floating tree.\n        if (\n          restoreFocus &&\n          currentTarget !== domReference &&\n          !isFocusable(target) &&\n          activeElement(doc) === doc.body\n        ) {\n          // Let `FloatingPortal` effect knows that focus is still inside the\n          // floating tree.\n          if (isHTMLElement(floatingFocusElement)) {\n            floatingFocusElement.focus();\n            // If explicitly requested to restore focus to the popup container, do not search\n            // for the next/previous tabbable element.\n            if (restoreFocus === 'popup') {\n              // If the element is removed on pointerdown, focus tries to move it,\n              // but since it's removed at the same time, focus gets lost as it\n              // happens after the .focus() call above.\n              // In this case, focus needs to be moved asynchronously.\n              restoreFocusFrame.request(() => {\n                floatingFocusElement.focus();\n              });\n              return;\n            }\n          }\n\n          const prevTabbableIndex = tabbableIndexRef.current;\n          const tabbableContent = getTabbableContent() as Array<Element | null>;\n          const nodeToFocus =\n            tabbableContent[prevTabbableIndex] ||\n            tabbableContent[tabbableContent.length - 1] ||\n            floatingFocusElement;\n\n          if (isHTMLElement(nodeToFocus)) {\n            nodeToFocus.focus();\n          }\n        }\n\n        // https://github.com/floating-ui/floating-ui/issues/3060\n        if (dataRef.current.insideReactTree) {\n          dataRef.current.insideReactTree = false;\n          return;\n        }\n\n        // Focus did not move inside the floating tree, and there are no tabbable\n        // portal guards to handle closing.\n        if (\n          (isUntrappedTypeableCombobox ? true : !modal) &&\n          relatedTarget &&\n          movedToUnrelatedNode &&\n          !isPointerDownRef.current &&\n          // Fix React 18 Strict Mode returnFocus due to double rendering.\n          // For an \"untrapped\" typeable combobox (input role=combobox with\n          // initialFocus=false), re-opening the popup and tabbing out should still close it even\n          // when the previously focused element (e.g. the next tabbable outside the popup) is\n          // focused again. Otherwise, the popup remains open on the second Tab sequence:\n          // click input -> Tab (closes) -> click input -> Tab.\n          // Allow closing when `isUntrappedTypeableCombobox` regardless of the previously focused element.\n          (isUntrappedTypeableCombobox || relatedTarget !== getPreviouslyFocusedElement())\n        ) {\n          preventReturnFocusRef.current = true;\n          store.setOpen(false, createChangeEventDetails(REASONS.focusOut, event));\n        }\n      });\n    }\n\n    function markInsideReactTree() {\n      if (pointerDownOutsideRef.current) {\n        return;\n      }\n      dataRef.current.insideReactTree = true;\n      blurTimeout.start(0, () => {\n        dataRef.current.insideReactTree = false;\n      });\n    }\n\n    const domReferenceElement = isHTMLElement(domReference) ? domReference : null;\n    const cleanups: Array<() => void> = [];\n\n    if (!floating && !domReferenceElement) {\n      return undefined;\n    }\n\n    if (domReferenceElement) {\n      domReferenceElement.addEventListener('focusout', handleFocusOutside);\n      domReferenceElement.addEventListener('pointerdown', handlePointerDown);\n      cleanups.push(() => {\n        domReferenceElement.removeEventListener('focusout', handleFocusOutside);\n        domReferenceElement.removeEventListener('pointerdown', handlePointerDown);\n      });\n    }\n\n    if (floating) {\n      floating.addEventListener('focusin', handleFocusIn);\n      floating.addEventListener('focusout', handleFocusOutside);\n\n      if (portalContext) {\n        floating.addEventListener('focusout', markInsideReactTree, true);\n        cleanups.push(() => {\n          floating.removeEventListener('focusout', markInsideReactTree, true);\n        });\n      }\n\n      cleanups.push(() => {\n        floating.removeEventListener('focusin', handleFocusIn);\n        floating.removeEventListener('focusout', handleFocusOutside);\n      });\n    }\n\n    return () => {\n      cleanups.forEach((cleanup) => {\n        cleanup();\n      });\n    };\n  }, [\n    disabled,\n    domReference,\n    floating,\n    floatingFocusElement,\n    modal,\n    tree,\n    portalContext,\n    store,\n    closeOnFocusOut,\n    restoreFocus,\n    getTabbableContent,\n    isUntrappedTypeableCombobox,\n    getNodeId,\n    orderRef,\n    dataRef,\n    blurTimeout,\n    pointerDownTimeout,\n    restoreFocusFrame,\n    nextFocusableElement,\n    previousFocusableElement,\n    getResolvedInsideElements,\n  ]);\n\n  // Hide everything outside the floating tree from assistive tech while open.\n  React.useEffect(() => {\n    if (disabled || !floating || !open) {\n      return undefined;\n    }\n\n    // Don't hide portals nested within the parent portal.\n    const portalNodes = Array.from(\n      portalContext?.portalNode?.querySelectorAll(`[${createAttribute('portal')}]`) || [],\n    );\n\n    const ancestors = tree ? getNodeAncestors(tree.nodesRef.current, getNodeId()) : [];\n    const rootAncestorComboboxDomReference = ancestors.find((node) =>\n      isTypeableCombobox(node.context?.elements.domReference || null),\n    )?.context?.elements.domReference;\n\n    const controlInsideElements = [\n      floating,\n      ...portalNodes,\n      beforeGuardRef.current,\n      afterGuardRef.current,\n      portalContext?.beforeOutsideRef.current,\n      portalContext?.afterOutsideRef.current,\n      ...getResolvedInsideElements(),\n    ];\n    const insideElements = [\n      ...controlInsideElements,\n      rootAncestorComboboxDomReference,\n      resolveRef(previousFocusableElement),\n      resolveRef(nextFocusableElement),\n      isUntrappedTypeableCombobox ? domReference : null,\n    ].filter((x): x is Element => x != null);\n\n    const ariaHiddenCleanup = markOthers(insideElements, {\n      ariaHidden: modal || isUntrappedTypeableCombobox,\n      mark: false,\n    });\n\n    const markerInsideElements = [floating, ...portalNodes].filter((x): x is Element => x != null);\n    const markerCleanup = markOthers(markerInsideElements);\n\n    return () => {\n      markerCleanup();\n      ariaHiddenCleanup();\n    };\n  }, [\n    open,\n    disabled,\n    domReference,\n    floating,\n    modal,\n    orderRef,\n    portalContext,\n    isUntrappedTypeableCombobox,\n    tree,\n    getNodeId,\n    nextFocusableElement,\n    previousFocusableElement,\n    getResolvedInsideElements,\n  ]);\n\n  // Focus the initial element when the floating element opens.\n  useIsoLayoutEffect(() => {\n    if (!open || disabled || !isHTMLElement(floatingFocusElement)) {\n      return;\n    }\n\n    const doc = ownerDocument(floatingFocusElement);\n    const previouslyFocusedElement = activeElement(doc);\n\n    // Wait for any layout effect state setters to execute to set `tabIndex`.\n    queueMicrotask(() => {\n      const focusableElements = getTabbableElements(floatingFocusElement);\n      const initialFocusValueOrFn = initialFocusRef.current;\n      const resolvedInitialFocus =\n        typeof initialFocusValueOrFn === 'function'\n          ? initialFocusValueOrFn(openInteractionTypeRef.current || '')\n          : initialFocusValueOrFn;\n\n      // `null` should fallback to default behavior in case of an empty ref.\n      if (resolvedInitialFocus === undefined || resolvedInitialFocus === false) {\n        return;\n      }\n\n      let elToFocus: FocusableElement | null | undefined;\n      if (resolvedInitialFocus === true || resolvedInitialFocus === null) {\n        elToFocus = focusableElements[0] || floatingFocusElement;\n      } else {\n        elToFocus = resolveRef(resolvedInitialFocus);\n      }\n      elToFocus = elToFocus || focusableElements[0] || floatingFocusElement;\n\n      const focusAlreadyInsideFloatingEl = contains(floatingFocusElement, previouslyFocusedElement);\n\n      if (focusAlreadyInsideFloatingEl) {\n        return;\n      }\n\n      enqueueFocus(elToFocus, {\n        preventScroll: elToFocus === floatingFocusElement,\n      });\n    });\n  }, [\n    disabled,\n    open,\n    floatingFocusElement,\n    ignoreInitialFocus,\n    getTabbableElements,\n    initialFocusRef,\n    openInteractionTypeRef,\n  ]);\n\n  // Track return focus targets and restore focus on unmount/close.\n  useIsoLayoutEffect(() => {\n    if (disabled || !floatingFocusElement) {\n      return undefined;\n    }\n\n    const doc = ownerDocument(floatingFocusElement);\n    const previouslyFocusedElement = activeElement(doc);\n\n    addPreviouslyFocusedElement(previouslyFocusedElement);\n\n    // Dismissing via outside press should always ignore `returnFocus` to\n    // prevent unwanted scrolling.\n    function onOpenChangeLocal(details: FloatingUIOpenChangeDetails) {\n      if (!details.open) {\n        closeTypeRef.current = getEventType(details.nativeEvent, lastInteractionTypeRef.current);\n      }\n\n      if (details.reason === REASONS.triggerHover && details.nativeEvent.type === 'mouseleave') {\n        preventReturnFocusRef.current = true;\n      }\n\n      if (details.reason !== REASONS.outsidePress) {\n        return;\n      }\n\n      if (details.nested) {\n        preventReturnFocusRef.current = false;\n      } else if (\n        isVirtualClick(details.nativeEvent as MouseEvent) ||\n        isVirtualPointerEvent(details.nativeEvent as PointerEvent)\n      ) {\n        preventReturnFocusRef.current = false;\n      } else {\n        let isPreventScrollSupported = false;\n        document.createElement('div').focus({\n          get preventScroll() {\n            isPreventScrollSupported = true;\n            return false;\n          },\n        });\n\n        if (isPreventScrollSupported) {\n          preventReturnFocusRef.current = false;\n        } else {\n          preventReturnFocusRef.current = true;\n        }\n      }\n    }\n\n    events.on('openchange', onOpenChangeLocal);\n\n    function getReturnElement() {\n      const returnFocusValueOrFn = returnFocusRef.current;\n      let resolvedReturnFocusValue =\n        typeof returnFocusValueOrFn === 'function'\n          ? returnFocusValueOrFn(closeTypeRef.current)\n          : returnFocusValueOrFn;\n\n      // `null` should fallback to default behavior in case of an empty ref.\n      if (resolvedReturnFocusValue === undefined || resolvedReturnFocusValue === false) {\n        return null;\n      }\n\n      if (resolvedReturnFocusValue === null) {\n        resolvedReturnFocusValue = true;\n      }\n\n      if (typeof resolvedReturnFocusValue === 'boolean') {\n        const el = domReference || getPreviouslyFocusedElement();\n        return el && el.isConnected ? el : null;\n      }\n\n      const fallback = domReference || getPreviouslyFocusedElement();\n\n      return resolveRef(resolvedReturnFocusValue) || fallback || null;\n    }\n\n    return () => {\n      events.off('openchange', onOpenChangeLocal);\n\n      const activeEl = activeElement(doc);\n      const insideElements = getResolvedInsideElements();\n      const isFocusInsideFloatingTree =\n        contains(floating, activeEl) ||\n        insideElements.some((element) => element === activeEl || contains(element, activeEl)) ||\n        (tree &&\n          getNodeChildren(tree.nodesRef.current, getNodeId(), false).some((node) =>\n            contains(node.context?.elements.floating, activeEl),\n          ));\n\n      const returnElement = getReturnElement();\n\n      queueMicrotask(() => {\n        // This is `returnElement`, if it's tabbable, or its first tabbable child.\n        const tabbableReturnElement = getFirstTabbableElement(returnElement);\n        const hasExplicitReturnFocus = typeof returnFocusRef.current !== 'boolean';\n\n        if (\n          // eslint-disable-next-line react-hooks/exhaustive-deps\n          returnFocusRef.current &&\n          !preventReturnFocusRef.current &&\n          isHTMLElement(tabbableReturnElement) &&\n          // If the focus moved somewhere else after mount, avoid returning focus\n          // since it likely entered a different element which should be\n          // respected: https://github.com/floating-ui/floating-ui/issues/2607\n          (!hasExplicitReturnFocus && tabbableReturnElement !== activeEl && activeEl !== doc.body\n            ? isFocusInsideFloatingTree\n            : true)\n        ) {\n          tabbableReturnElement.focus({ preventScroll: true });\n        }\n\n        preventReturnFocusRef.current = false;\n      });\n    };\n  }, [\n    disabled,\n    floating,\n    floatingFocusElement,\n    returnFocusRef,\n    dataRef,\n    events,\n    tree,\n    domReference,\n    getNodeId,\n    getResolvedInsideElements,\n  ]);\n\n  // Safari may randomly scroll to the bottom of the page if an input inside a popup has focus\n  // when the popup unmounts from the DOM.\n  // By blurring it before the popup unmounts, we can prevent this behavior.\n  useIsoLayoutEffect(() => {\n    if (!isWebKit || open || !floating) {\n      return;\n    }\n\n    const activeEl = activeElement(ownerDocument(floating));\n    if (!isHTMLElement(activeEl) || !isTypeableElement(activeEl)) {\n      return;\n    }\n\n    if (contains(floating, activeEl)) {\n      activeEl.blur();\n    }\n  }, [open, floating]);\n  // Synchronize the `context` & `modal` value to the FloatingPortal context.\n  // It will decide whether or not it needs to render its own guards.\n  useIsoLayoutEffect(() => {\n    if (disabled || !portalContext) {\n      return undefined;\n    }\n\n    portalContext.setFocusManagerState({\n      modal,\n      closeOnFocusOut,\n      open,\n      onOpenChange: store.setOpen,\n      domReference,\n    });\n\n    return () => {\n      portalContext.setFocusManagerState(null);\n    };\n  }, [disabled, portalContext, modal, open, store, closeOnFocusOut, domReference]);\n\n  // Keep the floating element tabIndex in sync and clear stale focus records.\n  useIsoLayoutEffect(() => {\n    if (disabled || !floatingFocusElement) {\n      return undefined;\n    }\n    handleTabIndex(floatingFocusElement, orderRef);\n    return () => {\n      queueMicrotask(clearDisconnectedPreviouslyFocusedElements);\n    };\n  }, [disabled, floatingFocusElement, orderRef]);\n\n  const shouldRenderGuards =\n    !disabled && (modal ? !isUntrappedTypeableCombobox : true) && (isInsidePortal || modal);\n\n  return (\n    <React.Fragment>\n      {shouldRenderGuards && (\n        <FocusGuard\n          data-type=\"inside\"\n          ref={mergedBeforeGuardRef}\n          onFocus={(event) => {\n            if (modal) {\n              const els = getTabbableElements();\n              enqueueFocus(els[els.length - 1]);\n            } else if (portalContext?.portalNode) {\n              preventReturnFocusRef.current = false;\n              if (isOutsideEvent(event, portalContext.portalNode)) {\n                const nextTabbable = getNextTabbable(domReference);\n                nextTabbable?.focus();\n              } else {\n                resolveRef(previousFocusableElement ?? portalContext.beforeOutsideRef)?.focus();\n              }\n            }\n          }}\n        />\n      )}\n      {children}\n      {shouldRenderGuards && (\n        <FocusGuard\n          data-type=\"inside\"\n          ref={mergedAfterGuardRef}\n          onFocus={(event) => {\n            if (modal) {\n              enqueueFocus(getTabbableElements()[0]);\n            } else if (portalContext?.portalNode) {\n              if (closeOnFocusOut) {\n                preventReturnFocusRef.current = true;\n              }\n\n              if (isOutsideEvent(event, portalContext.portalNode)) {\n                const prevTabbable = getPreviousTabbable(domReference);\n                prevTabbable?.focus();\n              } else {\n                resolveRef(nextFocusableElement ?? portalContext.afterOutsideRef)?.focus();\n              }\n            }\n          }}\n        />\n      )}\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/components/FloatingPortal.test.tsx",
    "content": "import { expect } from 'vitest';\nimport * as React from 'react';\nimport { fireEvent, flushMicrotasks, render, screen } from '@mui/internal-test-utils';\nimport { isJSDOM } from '@base-ui/utils/detectBrowser';\nimport { FloatingPortal, useFloating } from '../index';\nimport { FloatingPortalLite } from '../../utils/FloatingPortalLite';\nimport type { UseFloatingPortalNodeProps } from './FloatingPortal';\n\ninterface AppProps {\n  container?: UseFloatingPortalNodeProps['container'];\n}\n\nfunction App(props: AppProps) {\n  const [open, setOpen] = React.useState(false);\n  const { refs } = useFloating({\n    open,\n    onOpenChange: setOpen,\n  });\n\n  return (\n    <React.Fragment>\n      <button data-testid=\"reference\" ref={refs.setReference} onClick={() => setOpen(!open)} />\n      <FloatingPortal {...props}>\n        {open && <div ref={refs.setFloating} data-testid=\"floating\" />}\n      </FloatingPortal>\n    </React.Fragment>\n  );\n}\n\ndescribe.skipIf(!isJSDOM)('FloatingPortal', () => {\n  test('allows custom containers', async () => {\n    const customRoot = document.createElement('div');\n    customRoot.id = 'custom-root';\n    document.body.appendChild(customRoot);\n    render(<App container={customRoot} />);\n    fireEvent.click(screen.getByTestId('reference'));\n\n    await flushMicrotasks();\n\n    const parent = screen.getByTestId('floating').parentElement;\n    expect(parent?.hasAttribute('data-base-ui-portal')).toBe(true);\n    expect(parent?.parentElement).toBe(customRoot);\n    customRoot.remove();\n  });\n\n  test('allows refs as containers', async () => {\n    const el = document.createElement('div');\n    document.body.appendChild(el);\n    const ref = { current: el };\n    render(<App container={ref} />);\n    fireEvent.click(screen.getByTestId('reference'));\n    await flushMicrotasks();\n    const parent = screen.getByTestId('floating').parentElement;\n    expect(parent?.hasAttribute('data-base-ui-portal')).toBe(true);\n    expect(parent?.parentElement).toBe(el);\n    document.body.removeChild(el);\n  });\n\n  test('allows containers to be initially null', async () => {\n    function RootApp() {\n      const [container, setContainer] = React.useState<HTMLElement | null>(null);\n      const [renderContainer, setRenderContainer] = React.useState(false);\n\n      React.useEffect(() => {\n        setRenderContainer(true);\n      }, []);\n\n      return (\n        <React.Fragment>\n          {renderContainer && <div ref={setContainer} data-testid=\"root\" />}\n          <App container={container} />\n        </React.Fragment>\n      );\n    }\n\n    render(<RootApp />);\n\n    fireEvent.click(screen.getByTestId('reference'));\n    await flushMicrotasks();\n\n    const subRoot = screen.getByTestId('floating').parentElement;\n    const root = screen.getByTestId('root');\n    expect(root).toBe(subRoot?.parentElement);\n  });\n\n  test('reattaches the portal when the container changes', async () => {\n    const customRoot = document.createElement('div');\n    document.body.appendChild(customRoot);\n\n    try {\n      function RootSwitcher() {\n        const [container, setContainer] =\n          React.useState<UseFloatingPortalNodeProps['container']>(undefined);\n\n        return (\n          <React.Fragment>\n            <App container={container} />\n            <button onClick={() => setContainer(undefined)} data-testid=\"use-undefined\" />\n            <button onClick={() => setContainer(customRoot)} data-testid=\"use-element\" />\n          </React.Fragment>\n        );\n      }\n\n      render(<RootSwitcher />);\n\n      fireEvent.click(screen.getByTestId('reference'));\n\n      expect((await screen.findByTestId('floating')).parentElement?.parentElement).toBe(\n        document.body,\n      );\n\n      fireEvent.click(screen.getByTestId('use-element'));\n\n      expect((await screen.findByTestId('floating')).parentElement?.parentElement).toBe(customRoot);\n\n      fireEvent.click(screen.getByTestId('use-undefined'));\n\n      const floatingInBodyAgain = await screen.findByTestId('floating');\n      expect(floatingInBodyAgain.parentElement?.parentElement).toBe(document.body);\n      expect(customRoot.contains(floatingInBodyAgain)).toBe(false);\n    } finally {\n      customRoot.remove();\n    }\n  });\n\n  test('forwards HTML props to the portal element', async () => {\n    render(\n      <FloatingPortal data-testid=\"portal-element\" className=\"closed\">\n        <div />\n      </FloatingPortal>,\n    );\n\n    await flushMicrotasks();\n\n    const portal = document.querySelector('[data-testid=\"portal-element\"]') as HTMLElement | null;\n    expect(portal).not.toBeNull();\n    expect(portal).toHaveClass('closed');\n    expect(portal).toHaveAttribute('data-base-ui-portal');\n  });\n\n  test('FloatingPortalLite forwards HTML props to the portal element', async () => {\n    render(\n      <FloatingPortalLite data-testid=\"lite-portal\">\n        <div />\n      </FloatingPortalLite>,\n    );\n\n    await flushMicrotasks();\n\n    const portal = document.querySelector('[data-testid=\"lite-portal\"]');\n    expect(portal).not.toBeNull();\n  });\n});\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/components/FloatingPortal.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport { isNode } from '@floating-ui/utils/dom';\nimport { useId } from '@base-ui/utils/useId';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { FocusGuard } from '../../utils/FocusGuard';\nimport {\n  enableFocusInside,\n  disableFocusInside,\n  getPreviousTabbable,\n  getNextTabbable,\n  isOutsideEvent,\n} from '../utils';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport { createAttribute } from '../utils/createAttribute';\nimport {\n  useRenderElement,\n  type UseRenderElementComponentProps,\n} from '../../utils/useRenderElement';\nimport { EMPTY_OBJECT, ownerVisuallyHidden } from '../../utils/constants';\nimport type { BaseUIComponentProps } from '../../utils/types';\n\ntype FocusManagerState = null | {\n  modal: boolean;\n  open: boolean;\n  onOpenChange(\n    open: boolean,\n    data?: { reason?: string | undefined; event?: Event | undefined },\n  ): void;\n  domReference: Element | null;\n  closeOnFocusOut: boolean;\n};\n\nconst PortalContext = React.createContext<null | {\n  portalNode: HTMLElement | null;\n  setFocusManagerState: React.Dispatch<React.SetStateAction<FocusManagerState>>;\n  beforeInsideRef: React.RefObject<HTMLSpanElement | null>;\n  afterInsideRef: React.RefObject<HTMLSpanElement | null>;\n  beforeOutsideRef: React.RefObject<HTMLSpanElement | null>;\n  afterOutsideRef: React.RefObject<HTMLSpanElement | null>;\n}>(null);\n\nexport const usePortalContext = () => React.useContext(PortalContext);\n\nconst attr = createAttribute('portal');\n\nexport interface UseFloatingPortalNodeProps {\n  ref?: React.Ref<HTMLDivElement> | undefined;\n  container?:\n    | HTMLElement\n    | ShadowRoot\n    | null\n    | React.RefObject<HTMLElement | ShadowRoot | null>\n    | undefined;\n  componentProps?: UseRenderElementComponentProps<any> | undefined;\n  elementProps?: React.HTMLAttributes<HTMLDivElement> | undefined;\n}\n\nexport interface UseFloatingPortalNodeResult {\n  portalNode: HTMLElement | null;\n  portalSubtree: React.ReactPortal | null;\n}\n\nexport function useFloatingPortalNode(\n  props: UseFloatingPortalNodeProps = {},\n): UseFloatingPortalNodeResult {\n  const { ref, container: containerProp, componentProps = EMPTY_OBJECT, elementProps } = props;\n\n  const uniqueId = useId();\n  const portalContext = usePortalContext();\n  const parentPortalNode = portalContext?.portalNode;\n\n  const [containerElement, setContainerElement] = React.useState<HTMLElement | ShadowRoot | null>(\n    null,\n  );\n  const [portalNode, setPortalNode] = React.useState<HTMLElement | null>(null);\n  const setPortalNodeRef = useStableCallback((node: HTMLElement | null) => {\n    if (node !== null) {\n      // the useIsoLayoutEffect below watching containerProp / parentPortalNode\n      // sets setPortalNode(null) when the container becomes null or changes.\n      // So even though the ref callback now ignores null, the portal node still gets cleared.\n      setPortalNode(node);\n    }\n  });\n\n  const containerRef = React.useRef<HTMLElement | ShadowRoot | null>(null);\n\n  useIsoLayoutEffect(() => {\n    // Wait for the container to be resolved if explicitly `null`.\n    if (containerProp === null) {\n      if (containerRef.current) {\n        containerRef.current = null;\n        setPortalNode(null);\n        setContainerElement(null);\n      }\n      return;\n    }\n\n    // React 17 does not use React.useId().\n    if (uniqueId == null) {\n      return;\n    }\n\n    const resolvedContainer =\n      (containerProp && (isNode(containerProp) ? containerProp : containerProp.current)) ??\n      parentPortalNode ??\n      document.body;\n\n    if (resolvedContainer == null) {\n      if (containerRef.current) {\n        containerRef.current = null;\n        setPortalNode(null);\n        setContainerElement(null);\n      }\n      return;\n    }\n\n    if (containerRef.current !== resolvedContainer) {\n      containerRef.current = resolvedContainer;\n      setPortalNode(null);\n      setContainerElement(resolvedContainer);\n    }\n  }, [containerProp, parentPortalNode, uniqueId]);\n\n  const portalElement = useRenderElement('div', componentProps, {\n    ref: [ref, setPortalNodeRef],\n    props: [\n      {\n        id: uniqueId,\n        [attr]: '',\n      },\n      elementProps,\n    ],\n  });\n\n  // This `createPortal` call injects `portalElement` into the `container`.\n  // Another call inside `FloatingPortal`/`FloatingPortalLite` then injects the children into `portalElement`.\n  const portalSubtree =\n    containerElement && portalElement\n      ? ReactDOM.createPortal(portalElement, containerElement)\n      : null;\n\n  return {\n    portalNode,\n    portalSubtree,\n  };\n}\n\n/**\n * Portals the floating element into a given container element — by default,\n * outside of the app root and into the body.\n * This is necessary to ensure the floating element can appear outside any\n * potential parent containers that cause clipping (such as `overflow: hidden`),\n * while retaining its location in the React tree.\n * @see https://floating-ui.com/docs/FloatingPortal\n * @internal\n */\nexport const FloatingPortal = React.forwardRef(function FloatingPortal(\n  componentProps: FloatingPortal.Props<any> & { renderGuards?: boolean | undefined },\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { children, container, className, render, renderGuards, ...elementProps } = componentProps;\n\n  const { portalNode, portalSubtree } = useFloatingPortalNode({\n    container,\n    ref: forwardedRef,\n    componentProps,\n    elementProps,\n  });\n\n  const beforeOutsideRef = React.useRef<HTMLSpanElement>(null);\n  const afterOutsideRef = React.useRef<HTMLSpanElement>(null);\n  const beforeInsideRef = React.useRef<HTMLSpanElement>(null);\n  const afterInsideRef = React.useRef<HTMLSpanElement>(null);\n\n  const [focusManagerState, setFocusManagerState] = React.useState<FocusManagerState>(null);\n\n  const modal = focusManagerState?.modal;\n  const open = focusManagerState?.open;\n\n  const shouldRenderGuards =\n    typeof renderGuards === 'boolean'\n      ? renderGuards\n      : !!focusManagerState && !focusManagerState.modal && focusManagerState.open && !!portalNode;\n\n  // https://codesandbox.io/s/tabbable-portal-f4tng?file=/src/TabbablePortal.tsx\n  React.useEffect(() => {\n    if (!portalNode || modal) {\n      return undefined;\n    }\n\n    // Make sure elements inside the portal element are tabbable only when the\n    // portal has already been focused, either by tabbing into a focus trap\n    // element outside or using the mouse.\n    function onFocus(event: FocusEvent) {\n      if (portalNode && event.relatedTarget && isOutsideEvent(event)) {\n        const focusing = event.type === 'focusin';\n        const manageFocus = focusing ? enableFocusInside : disableFocusInside;\n        manageFocus(portalNode);\n      }\n    }\n\n    // Listen to the event on the capture phase so they run before the focus\n    // trap elements onFocus prop is called.\n    portalNode.addEventListener('focusin', onFocus, true);\n    portalNode.addEventListener('focusout', onFocus, true);\n    return () => {\n      portalNode.removeEventListener('focusin', onFocus, true);\n      portalNode.removeEventListener('focusout', onFocus, true);\n    };\n  }, [portalNode, modal]);\n\n  React.useEffect(() => {\n    if (!portalNode || open) {\n      return;\n    }\n    enableFocusInside(portalNode);\n  }, [open, portalNode]);\n\n  const portalContextValue = React.useMemo(\n    () => ({\n      beforeOutsideRef,\n      afterOutsideRef,\n      beforeInsideRef,\n      afterInsideRef,\n      portalNode,\n      setFocusManagerState,\n    }),\n    [portalNode],\n  );\n\n  return (\n    <React.Fragment>\n      {portalSubtree}\n      <PortalContext.Provider value={portalContextValue}>\n        {shouldRenderGuards && portalNode && (\n          <FocusGuard\n            data-type=\"outside\"\n            ref={beforeOutsideRef}\n            onFocus={(event) => {\n              if (isOutsideEvent(event, portalNode)) {\n                beforeInsideRef.current?.focus();\n              } else {\n                const domReference = focusManagerState ? focusManagerState.domReference : null;\n                const prevTabbable = getPreviousTabbable(domReference);\n                prevTabbable?.focus();\n              }\n            }}\n          />\n        )}\n        {shouldRenderGuards && portalNode && (\n          <span aria-owns={portalNode.id} style={ownerVisuallyHidden} />\n        )}\n        {portalNode && ReactDOM.createPortal(children, portalNode)}\n        {shouldRenderGuards && portalNode && (\n          <FocusGuard\n            data-type=\"outside\"\n            ref={afterOutsideRef}\n            onFocus={(event) => {\n              if (isOutsideEvent(event, portalNode)) {\n                afterInsideRef.current?.focus();\n              } else {\n                const domReference = focusManagerState ? focusManagerState.domReference : null;\n                const nextTabbable = getNextTabbable(domReference);\n                nextTabbable?.focus();\n\n                if (focusManagerState?.closeOnFocusOut) {\n                  focusManagerState?.onOpenChange(\n                    false,\n                    createChangeEventDetails(REASONS.focusOut, event.nativeEvent),\n                  );\n                }\n              }\n            }}\n          />\n        )}\n      </PortalContext.Provider>\n    </React.Fragment>\n  );\n});\n\nexport interface FloatingPortalState {}\n\nexport namespace FloatingPortal {\n  export type State = FloatingPortalState;\n  export interface Props<TState> extends BaseUIComponentProps<'div', TState> {\n    /**\n     * A parent element to render the portal element into.\n     */\n    container?: UseFloatingPortalNodeProps['container'] | undefined;\n  }\n}\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/components/FloatingRootStore.ts",
    "content": "import * as React from 'react';\nimport { createSelector, ReactStore } from '@base-ui/utils/store';\nimport type { FloatingEvents, ContextData, ReferenceType } from '../types';\nimport { type BaseUIChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { createEventEmitter } from '../utils/createEventEmitter';\nimport { type FloatingUIOpenChangeDetails } from '../../utils/types';\nimport { type PopupTriggerMap } from '../../utils/popups';\nimport { isClickLikeEvent } from '../utils';\n\nexport interface FloatingRootState {\n  open: boolean;\n  domReferenceElement: Element | null;\n  referenceElement: ReferenceType | null;\n  floatingElement: HTMLElement | null;\n  positionReference: ReferenceType | null;\n  /**\n   * The ID of the floating element.\n   */\n  floatingId: string | undefined;\n}\n\nexport interface FloatingRootStoreContext {\n  onOpenChange:\n    | ((open: boolean, eventDetails: BaseUIChangeEventDetails<string>) => void)\n    | undefined;\n  readonly dataRef: React.RefObject<ContextData>;\n  readonly events: FloatingEvents;\n  nested: boolean;\n  noEmit: boolean;\n  readonly triggerElements: PopupTriggerMap;\n}\n\nconst selectors = {\n  open: createSelector((state: FloatingRootState) => state.open),\n  domReferenceElement: createSelector((state: FloatingRootState) => state.domReferenceElement),\n  referenceElement: createSelector(\n    (state: FloatingRootState) => state.positionReference ?? state.referenceElement,\n  ),\n  floatingElement: createSelector((state: FloatingRootState) => state.floatingElement),\n  floatingId: createSelector((state: FloatingRootState) => state.floatingId),\n};\n\ninterface FloatingRootStoreOptions {\n  open: boolean;\n  referenceElement: ReferenceType | null;\n  floatingElement: HTMLElement | null;\n  triggerElements: PopupTriggerMap;\n  floatingId: string | undefined;\n  nested: boolean;\n  noEmit: boolean;\n  onOpenChange:\n    | ((open: boolean, eventDetails: BaseUIChangeEventDetails<string>) => void)\n    | undefined;\n}\n\nexport class FloatingRootStore extends ReactStore<\n  Readonly<FloatingRootState>,\n  FloatingRootStoreContext,\n  typeof selectors\n> {\n  constructor(options: FloatingRootStoreOptions) {\n    const { nested, noEmit, onOpenChange, triggerElements, ...initialState } = options;\n\n    super(\n      {\n        ...initialState,\n        positionReference: initialState.referenceElement,\n        domReferenceElement: initialState.referenceElement as Element | null,\n      },\n      {\n        onOpenChange,\n        dataRef: { current: {} },\n        events: createEventEmitter(),\n        nested,\n        noEmit,\n        triggerElements,\n      },\n      selectors,\n    );\n  }\n\n  /**\n   * Emits the `openchange` event through the internal event emitter and calls the `onOpenChange` handler with the provided arguments.\n   *\n   * @param newOpen The new open state.\n   * @param eventDetails Details about the event that triggered the open state change.\n   */\n  setOpen = (newOpen: boolean, eventDetails: BaseUIChangeEventDetails<string>) => {\n    if (\n      !newOpen ||\n      !this.state.open ||\n      // Prevent a pending hover-open from overwriting a click-open event, while allowing\n      // click events to upgrade a hover-open.\n      isClickLikeEvent(eventDetails.event)\n    ) {\n      this.context.dataRef.current.openEvent = newOpen ? eventDetails.event : undefined;\n    }\n    if (!this.context.noEmit) {\n      const details: FloatingUIOpenChangeDetails = {\n        open: newOpen,\n        reason: eventDetails.reason,\n        nativeEvent: eventDetails.event,\n        nested: this.context.nested,\n        triggerElement: eventDetails.trigger,\n      };\n\n      this.context.events.emit('openchange', details);\n    }\n\n    this.context.onOpenChange?.(newOpen, eventDetails);\n  };\n}\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/components/FloatingTree.tsx",
    "content": "'use client';\nimport * as React from 'react';\n\nimport { useId } from '@base-ui/utils/useId';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useRefWithInit } from '@base-ui/utils/useRefWithInit';\nimport type { FloatingNodeType, FloatingTreeType } from '../types';\nimport { FloatingTreeStore } from './FloatingTreeStore';\n\nconst FloatingNodeContext = React.createContext<FloatingNodeType | null>(null);\nconst FloatingTreeContext = React.createContext<FloatingTreeType | null>(null);\n\n/**\n * Returns the parent node id for nested floating elements, if available.\n * Returns `null` for top-level floating elements.\n */\nexport const useFloatingParentNodeId = (): string | null =>\n  React.useContext(FloatingNodeContext)?.id || null;\n\n/**\n * Returns the nearest floating tree context, if available.\n */\nexport const useFloatingTree = (externalTree?: FloatingTreeStore): FloatingTreeType | null => {\n  const contextTree = React.useContext(FloatingTreeContext) as FloatingTreeType | null;\n  return externalTree ?? contextTree;\n};\n\n/**\n * Registers a node into the `FloatingTree`, returning its id.\n * @see https://floating-ui.com/docs/FloatingTree\n */\nexport function useFloatingNodeId(externalTree?: FloatingTreeStore): string | undefined {\n  const id = useId();\n  const tree = useFloatingTree(externalTree);\n  const parentId = useFloatingParentNodeId();\n\n  useIsoLayoutEffect(() => {\n    if (!id) {\n      return undefined;\n    }\n    const node = { id, parentId };\n    tree?.addNode(node);\n    return () => {\n      tree?.removeNode(node);\n    };\n  }, [tree, id, parentId]);\n\n  return id;\n}\n\nexport interface FloatingNodeProps {\n  children?: React.ReactNode;\n  id: string | undefined;\n}\n\n/**\n * Provides parent node context for nested floating elements.\n * @see https://floating-ui.com/docs/FloatingTree\n * @internal\n */\nexport function FloatingNode(props: FloatingNodeProps): React.JSX.Element {\n  const { children, id } = props;\n\n  const parentId = useFloatingParentNodeId();\n\n  return (\n    <FloatingNodeContext.Provider value={React.useMemo(() => ({ id, parentId }), [id, parentId])}>\n      {children}\n    </FloatingNodeContext.Provider>\n  );\n}\n\nexport interface FloatingTreeProps {\n  children?: React.ReactNode;\n  externalTree?: FloatingTreeStore | undefined;\n}\n\n/**\n * Provides context for nested floating elements when they are not children of\n * each other on the DOM.\n * This is not necessary in all cases, except when there must be explicit communication between parent and child floating elements. It is necessary for:\n * - The `bubbles` option in the `useDismiss()` Hook\n * - Nested virtual list navigation\n * - Nested floating elements that each open on hover\n * - Custom communication between parent and child floating elements\n * @see https://floating-ui.com/docs/FloatingTree\n * @internal\n */\nexport function FloatingTree(props: FloatingTreeProps): React.JSX.Element {\n  const { children, externalTree } = props;\n\n  const tree = useRefWithInit(() => externalTree ?? new FloatingTreeStore()).current;\n  return <FloatingTreeContext.Provider value={tree}>{children}</FloatingTreeContext.Provider>;\n}\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/components/FloatingTreeStore.ts",
    "content": "import type { FloatingNodeType, FloatingEvents } from '../types';\nimport { createEventEmitter } from '../utils/createEventEmitter';\n\n/**\n * Stores and manages floating elements in a tree structure.\n * This is a backing store for the `FloatingTree` component.\n */\nexport class FloatingTreeStore {\n  public readonly nodesRef: React.RefObject<Array<FloatingNodeType>> = { current: [] };\n\n  public readonly events: FloatingEvents = createEventEmitter();\n\n  public addNode(node: FloatingNodeType) {\n    this.nodesRef.current.push(node);\n  }\n\n  public removeNode(node: FloatingNodeType) {\n    const index = this.nodesRef.current.findIndex((n) => n === node);\n    if (index !== -1) {\n      this.nodesRef.current.splice(index, 1);\n    }\n  }\n}\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/hooks/useClick.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { useAnimationFrame } from '@base-ui/utils/useAnimationFrame';\nimport { useTimeout } from '@base-ui/utils/useTimeout';\nimport { EMPTY_OBJECT } from '../../utils/constants';\nimport type { ElementProps, FloatingContext, FloatingRootContext } from '../types';\nimport { isClickLikeEvent, isMouseLikePointerType, isTypeableElement } from '../utils';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\n\nexport interface UseClickProps {\n  /**\n   * Whether the Hook is enabled, including all internal Effects and event\n   * handlers.\n   * @default true\n   */\n  enabled?: boolean | undefined;\n  /**\n   * The type of event to use to determine a “click” with mouse input.\n   * Keyboard clicks work as normal.\n   * @default 'click'\n   */\n  event?: 'click' | 'mousedown' | 'mousedown-only' | undefined;\n  /**\n   * Whether to toggle the open state with repeated clicks.\n   * @default true\n   */\n  toggle?: boolean | undefined;\n  /**\n   * Whether to ignore the logic for mouse input (for example, if `useHover()`\n   * is also being used).\n   * @default false\n   */\n  ignoreMouse?: boolean | undefined;\n  /**\n   * If already open from another event such as the `useHover()` Hook,\n   * determines whether to keep the floating element open when clicking the\n   * reference element for the first time.\n   * @default true\n   */\n  stickIfOpen?: boolean | undefined;\n  /**\n   * Touch-only delay (ms) before opening. Useful to allow mobile viewport/keyboard to settle.\n   * @default 0\n   */\n  touchOpenDelay?: number | undefined;\n  /**\n   * The reason for the click.\n   * @default REASONS.triggerPress\n   */\n  reason?: typeof REASONS.triggerPress | typeof REASONS.inputPress | undefined;\n}\n\n/**\n * Opens or closes the floating element when clicking the reference element.\n * @see https://floating-ui.com/docs/useClick\n */\nexport function useClick(\n  context: FloatingRootContext | FloatingContext,\n  props: UseClickProps = {},\n): ElementProps {\n  const store = 'rootStore' in context ? context.rootStore : context;\n  const dataRef = store.context.dataRef;\n\n  const {\n    enabled = true,\n    event: eventOption = 'click',\n    toggle = true,\n    ignoreMouse = false,\n    stickIfOpen = true,\n    touchOpenDelay = 0,\n    reason = REASONS.triggerPress,\n  } = props;\n\n  const pointerTypeRef = React.useRef<'mouse' | 'pen' | 'touch'>(undefined);\n  const frame = useAnimationFrame();\n  const touchOpenTimeout = useTimeout();\n\n  const reference: ElementProps['reference'] = React.useMemo(\n    () => ({\n      onPointerDown(event) {\n        pointerTypeRef.current = event.pointerType;\n      },\n      onMouseDown(event) {\n        const pointerType = pointerTypeRef.current;\n        const nativeEvent = event.nativeEvent;\n        const open = store.select('open');\n\n        // Ignore all buttons except for the \"main\" button.\n        // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button\n        if (\n          event.button !== 0 ||\n          eventOption === 'click' ||\n          (isMouseLikePointerType(pointerType, true) && ignoreMouse)\n        ) {\n          return;\n        }\n\n        const openEvent = dataRef.current.openEvent;\n        const openEventType = openEvent?.type;\n        const hasClickedOnInactiveTrigger =\n          store.select('domReferenceElement') !== event.currentTarget;\n        const nextOpen =\n          (open && hasClickedOnInactiveTrigger) ||\n          !(\n            open &&\n            toggle &&\n            (openEvent && stickIfOpen\n              ? openEventType === 'click' || openEventType === 'mousedown'\n              : true)\n          );\n\n        // Animations sometimes won't run on a typeable element if using a rAF.\n        // Focus is always set on these elements. For touch, we may delay opening.\n        if (isTypeableElement(nativeEvent.target)) {\n          const details = createChangeEventDetails(\n            reason,\n            nativeEvent,\n            nativeEvent.target as HTMLElement,\n          );\n          if (nextOpen && pointerType === 'touch' && touchOpenDelay > 0) {\n            touchOpenTimeout.start(touchOpenDelay, () => {\n              store.setOpen(true, details);\n            });\n          } else {\n            store.setOpen(nextOpen, details);\n          }\n          return;\n        }\n\n        // Capture the currentTarget before the rAF.\n        // as React sets it to null after the event handler completes.\n        const eventCurrentTarget = event.currentTarget as HTMLElement;\n\n        // Wait until focus is set on the element. This is an alternative to\n        // `event.preventDefault()` to avoid :focus-visible from appearing when using a pointer.\n        frame.request(() => {\n          const details = createChangeEventDetails(reason, nativeEvent, eventCurrentTarget);\n          if (nextOpen && pointerType === 'touch' && touchOpenDelay > 0) {\n            touchOpenTimeout.start(touchOpenDelay, () => {\n              store.setOpen(true, details);\n            });\n          } else {\n            store.setOpen(nextOpen, details);\n          }\n        });\n      },\n      onClick(event) {\n        if (eventOption === 'mousedown-only') {\n          return;\n        }\n\n        const pointerType = pointerTypeRef.current;\n\n        if (eventOption === 'mousedown' && pointerType) {\n          pointerTypeRef.current = undefined;\n          return;\n        }\n\n        if (isMouseLikePointerType(pointerType, true) && ignoreMouse) {\n          return;\n        }\n\n        const open = store.select('open');\n        const openEvent = dataRef.current.openEvent;\n        const hasClickedOnInactiveTrigger =\n          store.select('domReferenceElement') !== event.currentTarget;\n        const nextOpen =\n          (open && hasClickedOnInactiveTrigger) ||\n          !(open && toggle && (openEvent && stickIfOpen ? isClickLikeEvent(openEvent) : true));\n        const details = createChangeEventDetails(\n          reason,\n          event.nativeEvent,\n          event.currentTarget as HTMLElement,\n        );\n\n        if (nextOpen && pointerType === 'touch' && touchOpenDelay > 0) {\n          touchOpenTimeout.start(touchOpenDelay, () => {\n            store.setOpen(true, details);\n          });\n        } else {\n          store.setOpen(nextOpen, details);\n        }\n      },\n      onKeyDown() {\n        pointerTypeRef.current = undefined;\n      },\n    }),\n    [\n      dataRef,\n      eventOption,\n      ignoreMouse,\n      store,\n      stickIfOpen,\n      toggle,\n      frame,\n      touchOpenTimeout,\n      touchOpenDelay,\n      reason,\n    ],\n  );\n\n  return React.useMemo(() => (enabled ? { reference } : EMPTY_OBJECT), [enabled, reference]);\n}\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/hooks/useClientPoint.test.tsx",
    "content": "import { test, expect } from 'vitest';\nimport * as React from 'react';\nimport type { Coords } from '@floating-ui/react-dom';\nimport { flushMicrotasks } from '@mui/internal-test-utils';\nimport { fireEvent, render, screen } from '@testing-library/react';\nimport { useClientPoint, useFloating, useInteractions } from '../index';\n\nfunction expectLocation({ x, y }: Coords) {\n  expect(Number(screen.getByTestId('x')?.textContent)).toBe(x);\n  expect(Number(screen.getByTestId('y')?.textContent)).toBe(y);\n  expect(Number(screen.getByTestId('width')?.textContent)).toBe(0);\n  expect(Number(screen.getByTestId('height')?.textContent)).toBe(0);\n}\n\nfunction App({\n  enabled = true,\n  axis,\n  useTriggerProps = false,\n}: {\n  enabled?: boolean;\n  axis?: 'both' | 'x' | 'y';\n  useTriggerProps?: boolean;\n}) {\n  const [isOpen, setIsOpen] = React.useState(false);\n  const { refs, elements, context } = useFloating({\n    open: isOpen,\n    onOpenChange: setIsOpen,\n  });\n  const clientPoint = useClientPoint(context, {\n    enabled,\n    axis,\n  });\n  const { getReferenceProps, getTriggerProps, getFloatingProps } = useInteractions([clientPoint]);\n\n  const rect = elements.reference?.getBoundingClientRect();\n  const referenceProps = useTriggerProps ? getTriggerProps() : getReferenceProps();\n\n  return (\n    <React.Fragment>\n      <div\n        data-testid=\"reference\"\n        ref={refs.setReference}\n        {...referenceProps}\n        style={{ width: 0, height: 0 }}\n      >\n        Reference\n      </div>\n      {isOpen && (\n        <div data-testid=\"floating\" ref={refs.setFloating} {...getFloatingProps()}>\n          Floating\n        </div>\n      )}\n      <button onClick={() => setIsOpen((v) => !v)} />\n      <span data-testid=\"x\">{rect?.x}</span>\n      <span data-testid=\"y\">{rect?.y}</span>\n      <span data-testid=\"width\">{rect?.width}</span>\n      <span data-testid=\"height\">{rect?.height}</span>\n    </React.Fragment>\n  );\n}\n\ntest('updates position from trigger props', async () => {\n  render(<App useTriggerProps />);\n\n  await flushMicrotasks();\n\n  fireEvent(\n    screen.getByTestId('reference'),\n    new MouseEvent('mousemove', {\n      bubbles: true,\n      clientX: 400,\n      clientY: 200,\n    }),\n  );\n\n  await flushMicrotasks();\n\n  expectLocation({ x: 400, y: 200 });\n});\n\ntest('uses trigger element when dom reference is missing', async () => {\n  render(<App axis=\"x\" />);\n\n  const reference = screen.getByTestId('reference');\n  reference.getBoundingClientRect = () => ({\n    x: 10,\n    y: 50,\n    width: 0,\n    height: 0,\n    top: 50,\n    left: 10,\n    right: 10,\n    bottom: 50,\n    toJSON: () => {},\n  });\n\n  await flushMicrotasks();\n\n  fireEvent.mouseMove(reference, {\n    clientX: 200,\n    clientY: 300,\n  });\n\n  await flushMicrotasks();\n\n  expectLocation({ x: 200, y: 50 });\n});\n\ntest('renders at mouse event coords', async () => {\n  render(<App />);\n\n  await flushMicrotasks();\n\n  fireEvent(\n    screen.getByTestId('reference'),\n    new MouseEvent('mousemove', {\n      bubbles: true,\n      clientX: 500,\n      clientY: 500,\n    }),\n  );\n\n  await flushMicrotasks();\n\n  expectLocation({ x: 500, y: 500 });\n\n  fireEvent(\n    screen.getByTestId('reference'),\n    new MouseEvent('mousemove', {\n      bubbles: true,\n      clientX: 1000,\n      clientY: 1000,\n    }),\n  );\n\n  await flushMicrotasks();\n\n  expectLocation({ x: 1000, y: 1000 });\n\n  // Window listener isn't registered unless the floating element is open.\n  fireEvent(\n    window,\n    new MouseEvent('mousemove', {\n      bubbles: true,\n      clientX: 700,\n      clientY: 700,\n    }),\n  );\n\n  await flushMicrotasks();\n\n  expectLocation({ x: 1000, y: 1000 });\n\n  fireEvent.click(screen.getByRole('button'));\n  await flushMicrotasks();\n\n  fireEvent(\n    screen.getByTestId('reference'),\n    new MouseEvent('mousemove', {\n      bubbles: true,\n      clientX: 700,\n      clientY: 700,\n    }),\n  );\n  await flushMicrotasks();\n\n  expectLocation({ x: 700, y: 700 });\n\n  fireEvent(\n    document.body,\n    new MouseEvent('mousemove', {\n      bubbles: true,\n      clientX: 0,\n      clientY: 0,\n    }),\n  );\n  await flushMicrotasks();\n\n  expectLocation({ x: 0, y: 0 });\n});\n\ntest('cleans up window listener when closing or disabling', async () => {\n  const { rerender } = render(<App />);\n\n  fireEvent.click(screen.getByRole('button'));\n\n  fireEvent(\n    screen.getByTestId('reference'),\n    new MouseEvent('mousemove', {\n      bubbles: true,\n      clientX: 500,\n      clientY: 500,\n    }),\n  );\n  await flushMicrotasks();\n\n  fireEvent.click(screen.getByRole('button'));\n\n  fireEvent(\n    document.body,\n    new MouseEvent('mousemove', {\n      bubbles: true,\n      clientX: 0,\n      clientY: 0,\n    }),\n  );\n  await flushMicrotasks();\n\n  expectLocation({ x: 500, y: 500 });\n\n  fireEvent.click(screen.getByRole('button'));\n\n  fireEvent(\n    document.body,\n    new MouseEvent('mousemove', {\n      bubbles: true,\n      clientX: 500,\n      clientY: 500,\n    }),\n  );\n  await flushMicrotasks();\n\n  expectLocation({ x: 500, y: 500 });\n\n  rerender(<App enabled={false} />);\n\n  fireEvent(\n    document.body,\n    new MouseEvent('mousemove', {\n      bubbles: true,\n      clientX: 0,\n      clientY: 0,\n    }),\n  );\n  await flushMicrotasks();\n\n  expectLocation({ x: 500, y: 500 });\n});\n\ntest('axis x', async () => {\n  render(<App axis=\"x\" />);\n\n  fireEvent.click(screen.getByRole('button'));\n\n  fireEvent(\n    screen.getByTestId('reference'),\n    new MouseEvent('mousemove', {\n      bubbles: true,\n      clientX: 500,\n      clientY: 500,\n    }),\n  );\n  await flushMicrotasks();\n\n  expectLocation({ x: 500, y: 0 });\n});\n\ntest('axis y', async () => {\n  render(<App axis=\"y\" />);\n\n  fireEvent.click(screen.getByRole('button'));\n\n  fireEvent(\n    screen.getByTestId('reference'),\n    new MouseEvent('mousemove', {\n      bubbles: true,\n      clientX: 500,\n      clientY: 500,\n    }),\n  );\n  await flushMicrotasks();\n\n  expectLocation({ x: 0, y: 500 });\n});\n\ntest('removes window listener when cursor lands on floating element', async () => {\n  render(<App />);\n\n  fireEvent.click(screen.getByRole('button'));\n\n  fireEvent(\n    screen.getByTestId('reference'),\n    new MouseEvent('mousemove', {\n      bubbles: true,\n      clientX: 500,\n      clientY: 500,\n    }),\n  );\n\n  fireEvent(\n    screen.getByTestId('floating'),\n    new MouseEvent('mousemove', {\n      bubbles: true,\n      clientX: 500,\n      clientY: 500,\n    }),\n  );\n\n  fireEvent(\n    document.body,\n    new MouseEvent('mousemove', {\n      bubbles: true,\n      clientX: 0,\n      clientY: 0,\n    }),\n  );\n  await flushMicrotasks();\n\n  expectLocation({ x: 500, y: 500 });\n});\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/hooks/useClientPoint.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { getWindow } from '@floating-ui/utils/dom';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { contains, getTarget, isMouseLikePointerType } from '../utils';\n\nimport type { ContextData, ElementProps, FloatingContext, FloatingRootContext } from '../types';\n\nfunction createVirtualElement(\n  domElement: Element | null | undefined,\n  data: {\n    axis: 'x' | 'y' | 'both';\n    dataRef: React.RefObject<ContextData>;\n    pointerType: string | undefined;\n    x: number | null;\n    y: number | null;\n  },\n) {\n  let offsetX: number | null = null;\n  let offsetY: number | null = null;\n  let isAutoUpdateEvent = false;\n\n  return {\n    contextElement: domElement || undefined,\n    getBoundingClientRect() {\n      const domRect = domElement?.getBoundingClientRect() || {\n        width: 0,\n        height: 0,\n        x: 0,\n        y: 0,\n      };\n\n      const isXAxis = data.axis === 'x' || data.axis === 'both';\n      const isYAxis = data.axis === 'y' || data.axis === 'both';\n      const canTrackCursorOnAutoUpdate =\n        ['mouseenter', 'mousemove'].includes(data.dataRef.current.openEvent?.type || '') &&\n        data.pointerType !== 'touch';\n\n      let width = domRect.width;\n      let height = domRect.height;\n      let x = domRect.x;\n      let y = domRect.y;\n\n      if (offsetX == null && data.x && isXAxis) {\n        offsetX = domRect.x - data.x;\n      }\n\n      if (offsetY == null && data.y && isYAxis) {\n        offsetY = domRect.y - data.y;\n      }\n\n      x -= offsetX || 0;\n      y -= offsetY || 0;\n      width = 0;\n      height = 0;\n\n      if (!isAutoUpdateEvent || canTrackCursorOnAutoUpdate) {\n        width = data.axis === 'y' ? domRect.width : 0;\n        height = data.axis === 'x' ? domRect.height : 0;\n        x = isXAxis && data.x != null ? data.x : x;\n        y = isYAxis && data.y != null ? data.y : y;\n      } else if (isAutoUpdateEvent && !canTrackCursorOnAutoUpdate) {\n        height = data.axis === 'x' ? domRect.height : height;\n        width = data.axis === 'y' ? domRect.width : width;\n      }\n\n      isAutoUpdateEvent = true;\n\n      return {\n        width,\n        height,\n        x,\n        y,\n        top: y,\n        right: x + width,\n        bottom: y + height,\n        left: x,\n      };\n    },\n  };\n}\n\nfunction isMouseBasedEvent(event: Event | undefined): event is MouseEvent {\n  return event != null && (event as MouseEvent).clientX != null;\n}\n\nexport interface UseClientPointProps {\n  /**\n   * Whether the Hook is enabled, including all internal Effects and event\n   * handlers.\n   * @default true\n   */\n  enabled?: boolean | undefined;\n  /**\n   * Whether to restrict the client point to an axis and use the reference\n   * element (if it exists) as the other axis. This can be useful if the\n   * floating element is also interactive.\n   * @default 'both'\n   */\n  axis?: 'x' | 'y' | 'both' | undefined;\n}\n\n/**\n * Positions the floating element relative to a client point (in the viewport),\n * such as the mouse position. By default, it follows the mouse cursor.\n * @see https://floating-ui.com/docs/useClientPoint\n */\nexport function useClientPoint(\n  context: FloatingRootContext | FloatingContext,\n  props: UseClientPointProps = {},\n): ElementProps {\n  const store = 'rootStore' in context ? context.rootStore : context;\n\n  const open = store.useState('open');\n  const floating = store.useState('floatingElement');\n  const domReference = store.useState('domReferenceElement');\n  const dataRef = store.context.dataRef;\n\n  const { enabled = true, axis = 'both' } = props;\n\n  const initialRef = React.useRef(false);\n  const cleanupListenerRef = React.useRef<null | (() => void)>(null);\n\n  const [pointerType, setPointerType] = React.useState<string | undefined>();\n  const [reactive, setReactive] = React.useState([]);\n\n  const setReference = useStableCallback(\n    (newX: number | null, newY: number | null, referenceElement?: Element | null) => {\n      if (initialRef.current) {\n        return;\n      }\n\n      // Prevent setting if the open event was not a mouse-like one\n      // (e.g. focus to open, then hover over the reference element).\n      // Only apply if the event exists.\n      if (dataRef.current.openEvent && !isMouseBasedEvent(dataRef.current.openEvent)) {\n        return;\n      }\n\n      store.set(\n        'positionReference',\n        createVirtualElement(referenceElement ?? domReference, {\n          x: newX,\n          y: newY,\n          axis,\n          dataRef,\n          pointerType,\n        }),\n      );\n    },\n  );\n\n  const handleReferenceEnterOrMove = useStableCallback((event: React.MouseEvent<Element>) => {\n    if (!open) {\n      setReference(event.clientX, event.clientY, event.currentTarget as Element);\n    } else if (!cleanupListenerRef.current) {\n      // If there's no cleanup, there's no listener, but we want to ensure\n      // we add the listener if the cursor landed on the floating element and\n      // then back on the reference (i.e. it's interactive).\n      setReactive([]);\n    }\n  });\n\n  // If the pointer is a mouse-like pointer, we want to continue following the\n  // mouse even if the floating element is transitioning out. On touch\n  // devices, this is undesirable because the floating element will move to\n  // the dismissal touch point.\n  const openCheck = isMouseLikePointerType(pointerType) ? floating : open;\n\n  const addListener = React.useCallback(() => {\n    if (!openCheck || !enabled) {\n      return undefined;\n    }\n\n    const win = getWindow(floating);\n\n    function handleMouseMove(event: MouseEvent) {\n      const target = getTarget(event) as Element | null;\n\n      if (!contains(floating, target)) {\n        setReference(event.clientX, event.clientY);\n      } else {\n        win.removeEventListener('mousemove', handleMouseMove);\n        cleanupListenerRef.current = null;\n      }\n    }\n\n    if (!dataRef.current.openEvent || isMouseBasedEvent(dataRef.current.openEvent)) {\n      win.addEventListener('mousemove', handleMouseMove);\n      const cleanup = () => {\n        win.removeEventListener('mousemove', handleMouseMove);\n        cleanupListenerRef.current = null;\n      };\n      cleanupListenerRef.current = cleanup;\n      return cleanup;\n    }\n\n    store.set('positionReference', domReference);\n    return undefined;\n  }, [openCheck, enabled, floating, dataRef, domReference, store, setReference]);\n\n  React.useEffect(() => {\n    return addListener();\n  }, [addListener, reactive]);\n\n  React.useEffect(() => {\n    if (enabled && !floating) {\n      initialRef.current = false;\n    }\n  }, [enabled, floating]);\n\n  React.useEffect(() => {\n    if (!enabled && open) {\n      initialRef.current = true;\n    }\n  }, [enabled, open]);\n\n  const reference: ElementProps['reference'] = React.useMemo(() => {\n    function setPointerTypeRef(event: React.PointerEvent) {\n      setPointerType(event.pointerType);\n    }\n\n    return {\n      onPointerDown: setPointerTypeRef,\n      onPointerEnter: setPointerTypeRef,\n      onMouseMove: handleReferenceEnterOrMove,\n      onMouseEnter: handleReferenceEnterOrMove,\n    };\n  }, [handleReferenceEnterOrMove]);\n\n  return React.useMemo(\n    () => (enabled ? { reference, trigger: reference } : {}),\n    [enabled, reference],\n  );\n}\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/hooks/useDismiss.test.tsx",
    "content": "import { vi, expect } from 'vitest';\n/* eslint-disable @typescript-eslint/no-shadow */\nimport { act, fireEvent, flushMicrotasks, render, screen, waitFor } from '@mui/internal-test-utils';\nimport userEvent from '@testing-library/user-event';\nimport * as React from 'react';\n\nimport { isJSDOM } from '@base-ui/utils/detectBrowser';\nimport {\n  FloatingFocusManager,\n  FloatingNode,\n  FloatingPortal,\n  FloatingTree,\n  useDismiss,\n  useFloating,\n  useFloatingNodeId,\n  useFloatingParentNodeId,\n  useFocus,\n  useInteractions,\n  useClick,\n} from '../index';\nimport { REASONS } from '../../utils/reasons';\nimport type { UseDismissProps } from './useDismiss';\nimport { normalizeProp } from './useDismiss';\n\nbeforeEach(() => {\n  vi.spyOn(window, 'requestAnimationFrame').mockImplementation(\n    (callback: FrameRequestCallback): number => {\n      callback(0);\n      return 0;\n    },\n  );\n});\n\nfunction App(\n  props: UseDismissProps & {\n    onClose?: () => void;\n  },\n) {\n  const [open, setOpen] = React.useState(true);\n  const { refs, context } = useFloating({\n    open,\n    onOpenChange(openArg, data) {\n      setOpen(openArg);\n      const reason = data?.reason;\n      if (props.outsidePress) {\n        expect(reason).toBe(REASONS.outsidePress);\n      } else if (props.escapeKey) {\n        expect(reason).toBe(REASONS.escapeKey);\n        if (!openArg) {\n          props.onClose?.();\n        }\n      } else if (props.referencePress?.()) {\n        expect(reason).toBe(REASONS.triggerPress);\n      }\n    },\n  });\n  const { getReferenceProps, getFloatingProps } = useInteractions([useDismiss(context, props)]);\n\n  return (\n    <React.Fragment>\n      <button {...getReferenceProps({ ref: refs.setReference })} />\n      {open && (\n        <div role=\"tooltip\" {...getFloatingProps({ ref: refs.setFloating })}>\n          <input />\n        </div>\n      )}\n    </React.Fragment>\n  );\n}\n\ndescribe.skipIf(!isJSDOM)('useDismiss', () => {\n  describe('default options', () => {\n    test('dismisses with escape key', async () => {\n      render(<App />);\n      fireEvent.keyDown(document.body, { key: 'Escape' });\n      expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();\n      await flushMicrotasks();\n    });\n\n    test('does not dismiss with escape key if IME is active', async () => {\n      const onClose = vi.fn();\n\n      render(<App onClose={onClose} escapeKey />);\n\n      const textbox = screen.getByRole('textbox');\n\n      await act(async () => {\n        textbox.focus();\n      });\n\n      await flushMicrotasks();\n\n      // Simulate behavior when \"あ\" (Japanese) is entered and Esc is pressed for IME\n      // cancellation.\n      fireEvent.change(textbox, { target: { value: 'あ' } });\n      fireEvent.compositionStart(textbox);\n      fireEvent.keyDown(textbox, { key: 'Escape' });\n      fireEvent.compositionEnd(textbox);\n\n      // Wait for the compositionend timeout tick due to Safari\n      await new Promise((resolve) => {\n        setTimeout(resolve, 0);\n      });\n\n      expect(onClose).toHaveBeenCalledTimes(0);\n\n      fireEvent.keyDown(textbox, { key: 'Escape' });\n\n      expect(onClose).toHaveBeenCalledTimes(1);\n    });\n\n    test('dismisses with outside pointer press', async () => {\n      render(<App />);\n      await userEvent.click(document.body);\n      expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();\n    });\n\n    test('dismisses with reference press', async () => {\n      render(<App referencePress={() => true} />);\n      await userEvent.click(screen.getByRole('button'));\n      expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();\n    });\n\n    test('dismisses with native click', async () => {\n      render(<App referencePress={() => true} />);\n      fireEvent.click(screen.getByRole('button'));\n      expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();\n    });\n\n    test('outsidePress function guard', async () => {\n      render(<App outsidePress={() => false} />);\n      await userEvent.click(document.body);\n      expect(screen.getByRole('tooltip')).toBeInTheDocument();\n    });\n\n    test('outsidePress ignored for third party elements', async () => {\n      function App() {\n        const [isOpen, setIsOpen] = React.useState(true);\n\n        const { context, refs } = useFloating({\n          open: isOpen,\n          onOpenChange: setIsOpen,\n        });\n\n        const dismiss = useDismiss(context);\n\n        const { getReferenceProps, getFloatingProps } = useInteractions([dismiss]);\n\n        return (\n          <React.Fragment>\n            <button {...getReferenceProps({ ref: refs.setReference })} />\n            {isOpen && (\n              <FloatingFocusManager context={context}>\n                <div role=\"dialog\" {...getFloatingProps({ ref: refs.setFloating })} />\n              </FloatingFocusManager>\n            )}\n          </React.Fragment>\n        );\n      }\n\n      render(<App />);\n      await flushMicrotasks();\n\n      const thirdParty = document.createElement('div');\n      thirdParty.setAttribute('data-testid', 'third-party');\n      document.body.append(thirdParty);\n      await userEvent.click(thirdParty);\n      expect(screen.getByRole('dialog')).toBeInTheDocument();\n      thirdParty.remove();\n    });\n\n    test('dismisses when clicking outside a shared shadow root', async () => {\n      function App({ shadowRoot }: { shadowRoot: ShadowRoot }) {\n        const [isOpen, setIsOpen] = React.useState(true);\n\n        const { context, refs } = useFloating({\n          open: isOpen,\n          onOpenChange: setIsOpen,\n        });\n\n        const dismiss = useDismiss(context);\n        const { getReferenceProps, getFloatingProps } = useInteractions([dismiss]);\n\n        return (\n          <React.Fragment>\n            <button {...getReferenceProps({ ref: refs.setReference })} />\n            {isOpen && (\n              <FloatingPortal container={shadowRoot}>\n                <div role=\"dialog\" {...getFloatingProps({ ref: refs.setFloating })} />\n              </FloatingPortal>\n            )}\n          </React.Fragment>\n        );\n      }\n\n      const host = document.body.appendChild(document.createElement('div'));\n      const shadowRoot = host.attachShadow({ mode: 'open' });\n      const container = document.createElement('div');\n      shadowRoot.appendChild(container);\n\n      try {\n        render(<App shadowRoot={shadowRoot} />, { container });\n\n        await userEvent.click(document.body);\n\n        expect(shadowRoot.querySelector('[role=\"dialog\"]')).toBe(null);\n      } finally {\n        host.remove();\n      }\n    });\n\n    test('dismisses when clicking outside a shared shadow root while focus is managed', async () => {\n      function App({ shadowRoot }: { shadowRoot: ShadowRoot }) {\n        const [isOpen, setIsOpen] = React.useState(true);\n\n        const { context, refs } = useFloating({\n          open: isOpen,\n          onOpenChange: setIsOpen,\n        });\n\n        const dismiss = useDismiss(context);\n        const { getReferenceProps, getFloatingProps } = useInteractions([dismiss]);\n\n        return (\n          <React.Fragment>\n            <button {...getReferenceProps({ ref: refs.setReference })} />\n            {isOpen && (\n              <FloatingPortal container={shadowRoot}>\n                <FloatingFocusManager context={context}>\n                  <div role=\"dialog\" {...getFloatingProps({ ref: refs.setFloating })} />\n                </FloatingFocusManager>\n              </FloatingPortal>\n            )}\n          </React.Fragment>\n        );\n      }\n\n      const host = document.body.appendChild(document.createElement('div'));\n      const shadowRoot = host.attachShadow({ mode: 'open' });\n      const container = document.createElement('div');\n      shadowRoot.appendChild(container);\n\n      try {\n        render(<App shadowRoot={shadowRoot} />, { container });\n        await flushMicrotasks();\n\n        await userEvent.click(document.body);\n\n        expect(shadowRoot.querySelector('[role=\"dialog\"]')).toBe(null);\n      } finally {\n        host.remove();\n      }\n    });\n\n    test('outsidePress not ignored for nested floating elements', async () => {\n      function Popover({\n        children,\n        id,\n        modal,\n      }: {\n        children?: React.ReactNode;\n        id: string;\n        modal?: boolean | null;\n      }) {\n        const [isOpen, setIsOpen] = React.useState(true);\n\n        const { context, refs } = useFloating({\n          open: isOpen,\n          onOpenChange: setIsOpen,\n        });\n\n        const dismiss = useDismiss(context);\n\n        const { getReferenceProps, getFloatingProps } = useInteractions([dismiss]);\n\n        const dialogJsx = (\n          <div role=\"dialog\" data-testid={id} {...getFloatingProps({ ref: refs.setFloating })}>\n            {children}\n          </div>\n        );\n\n        return (\n          <React.Fragment>\n            <button {...getReferenceProps({ ref: refs.setReference })} />\n            {isOpen && (\n              <React.Fragment>\n                {modal == null ? (\n                  dialogJsx\n                ) : (\n                  <FloatingFocusManager context={context} modal={modal}>\n                    {dialogJsx}\n                  </FloatingFocusManager>\n                )}\n              </React.Fragment>\n            )}\n          </React.Fragment>\n        );\n      }\n\n      function App({ modal }: { modal: [boolean, boolean] | null }) {\n        return (\n          <Popover id=\"popover-1\" modal={modal ? modal[0] : true}>\n            <Popover id=\"popover-2\" modal={modal ? modal[1] : null} />\n          </Popover>\n        );\n      }\n\n      const { unmount } = render(<App modal={[true, true]} />);\n      await flushMicrotasks();\n\n      let popover1 = screen.getByTestId('popover-1');\n      let popover2 = screen.getByTestId('popover-2');\n      await userEvent.click(popover2);\n      expect(popover1).toBeInTheDocument();\n      expect(popover2).toBeInTheDocument();\n      await userEvent.click(popover1);\n      expect(popover2).not.toBeInTheDocument();\n\n      unmount();\n\n      const { unmount: unmount2 } = render(<App modal={[true, false]} />);\n      await flushMicrotasks();\n\n      popover1 = screen.getByTestId('popover-1');\n      popover2 = screen.getByTestId('popover-2');\n\n      await userEvent.click(popover2);\n      expect(popover1).toBeInTheDocument();\n      expect(popover2).toBeInTheDocument();\n      await userEvent.click(popover1);\n      expect(popover2).not.toBeInTheDocument();\n\n      unmount2();\n\n      const { unmount: unmount3 } = render(<App modal={[false, true]} />);\n      await flushMicrotasks();\n\n      popover1 = screen.getByTestId('popover-1');\n      popover2 = screen.getByTestId('popover-2');\n\n      await userEvent.click(popover2);\n      expect(popover1).toBeInTheDocument();\n      expect(popover2).toBeInTheDocument();\n      await userEvent.click(popover1);\n      expect(popover2).not.toBeInTheDocument();\n\n      unmount3();\n\n      render(<App modal={null} />);\n      await flushMicrotasks();\n\n      popover1 = screen.getByTestId('popover-1');\n      popover2 = screen.getByTestId('popover-2');\n\n      await userEvent.click(popover2);\n      expect(popover1).toBeInTheDocument();\n      expect(popover2).toBeInTheDocument();\n      await userEvent.click(popover1);\n      expect(popover2).not.toBeInTheDocument();\n    });\n  });\n\n  describe('options set to false', () => {\n    test('does not dismiss with escape key', async () => {\n      render(<App escapeKey={false} />);\n      fireEvent.keyDown(document.body, { key: 'Escape' });\n      expect(screen.getByRole('tooltip')).toBeInTheDocument();\n      await flushMicrotasks();\n    });\n\n    test('does not dismiss with outside press', async () => {\n      render(<App outsidePress={false} />);\n      await userEvent.click(document.body);\n      expect(screen.getByRole('tooltip')).toBeInTheDocument();\n    });\n\n    test('does not dismiss with reference pointer down', async () => {\n      render(<App referencePress={() => false} />);\n      await userEvent.click(screen.getByRole('button'));\n      expect(screen.getByRole('tooltip')).toBeInTheDocument();\n    });\n\n    test('does not dismiss when clicking portaled children', async () => {\n      function App() {\n        const [open, setOpen] = React.useState(true);\n        const { refs, context } = useFloating({\n          open,\n          onOpenChange: setOpen,\n        });\n\n        const { getReferenceProps, getFloatingProps } = useInteractions([useDismiss(context)]);\n\n        return (\n          <React.Fragment>\n            <button ref={refs.setReference} {...getReferenceProps()} />\n            {open && (\n              <div ref={refs.setFloating} {...getFloatingProps()}>\n                <FloatingPortal>\n                  <button data-testid=\"portaled-button\" />\n                </FloatingPortal>\n              </div>\n            )}\n          </React.Fragment>\n        );\n      }\n\n      render(<App />);\n\n      fireEvent.pointerDown(screen.getByTestId('portaled-button'), {\n        bubbles: true,\n      });\n      await flushMicrotasks();\n\n      expect(screen.getByTestId('portaled-button')).toBeInTheDocument();\n    });\n\n    test('outsidePress function guard', async () => {\n      render(<App outsidePress={() => true} />);\n      await userEvent.click(document.body);\n      expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();\n    });\n  });\n\n  describe('prop: bubbles', () => {\n    function Dialog({\n      testId,\n      children,\n      ...props\n    }: UseDismissProps & { testId: string; children: React.ReactNode }) {\n      const [open, setOpen] = React.useState(true);\n      const nodeId = useFloatingNodeId();\n\n      const { refs, context } = useFloating({\n        open,\n        onOpenChange: setOpen,\n        nodeId,\n      });\n\n      const { getReferenceProps, getFloatingProps } = useInteractions([useDismiss(context, props)]);\n\n      return (\n        <FloatingNode id={nodeId}>\n          <button {...getReferenceProps({ ref: refs.setReference })} />\n          {open && (\n            <FloatingFocusManager context={context}>\n              <div {...getFloatingProps({ ref: refs.setFloating })} data-testid={testId}>\n                {children}\n              </div>\n            </FloatingFocusManager>\n          )}\n        </FloatingNode>\n      );\n    }\n\n    function NestedDialog(props: UseDismissProps & { testId: string; children: React.ReactNode }) {\n      const parentId = useFloatingParentNodeId();\n\n      if (parentId == null) {\n        return (\n          <FloatingTree>\n            <Dialog {...props} />\n          </FloatingTree>\n        );\n      }\n\n      return <Dialog {...props} />;\n    }\n\n    describe('normalizeProp', () => {\n      test('undefined', () => {\n        const { escapeKey: escapeKeyBubbles, outsidePress: outsidePressBubbles } = normalizeProp();\n\n        expect(escapeKeyBubbles).toBe(false);\n        expect(outsidePressBubbles).toBe(true);\n      });\n\n      test('when false', () => {\n        const { escapeKey: escapeKeyBubbles, outsidePress: outsidePressBubbles } =\n          normalizeProp(false);\n\n        expect(escapeKeyBubbles).toBe(false);\n        expect(outsidePressBubbles).toBe(false);\n      });\n\n      test('{}', () => {\n        const { escapeKey: escapeKeyBubbles, outsidePress: outsidePressBubbles } = normalizeProp(\n          {},\n        );\n\n        expect(escapeKeyBubbles).toBe(false);\n        expect(outsidePressBubbles).toBe(true);\n      });\n\n      test('{ escapeKey: false }', () => {\n        const { escapeKey: escapeKeyBubbles, outsidePress: outsidePressBubbles } = normalizeProp({\n          escapeKey: false,\n        });\n\n        expect(escapeKeyBubbles).toBe(false);\n        expect(outsidePressBubbles).toBe(true);\n      });\n\n      test('{ outsidePress: false }', () => {\n        const { escapeKey: escapeKeyBubbles, outsidePress: outsidePressBubbles } = normalizeProp({\n          outsidePress: false,\n        });\n\n        expect(escapeKeyBubbles).toBe(false);\n        expect(outsidePressBubbles).toBe(false);\n      });\n    });\n\n    describe('prop: bubbles.outsidePress', () => {\n      test('when true', async () => {\n        render(\n          <NestedDialog testId=\"outer\">\n            <NestedDialog testId=\"inner\">\n              <button>test button</button>\n            </NestedDialog>\n          </NestedDialog>,\n        );\n\n        expect(screen.getByTestId('outer')).toBeInTheDocument();\n        expect(screen.getByTestId('inner')).toBeInTheDocument();\n\n        fireEvent.pointerDown(document.body);\n\n        expect(screen.queryByTestId('outer')).not.toBeInTheDocument();\n        expect(screen.queryByTestId('inner')).not.toBeInTheDocument();\n      });\n\n      test('when false', async () => {\n        render(\n          <NestedDialog testId=\"outer\" bubbles={{ outsidePress: false }}>\n            <NestedDialog testId=\"inner\" bubbles={{ outsidePress: false }}>\n              <button>test button</button>\n            </NestedDialog>\n          </NestedDialog>,\n        );\n\n        expect(screen.getByTestId('outer')).toBeInTheDocument();\n        expect(screen.getByTestId('inner')).toBeInTheDocument();\n\n        fireEvent.pointerDown(document.body);\n\n        expect(screen.getByTestId('outer')).toBeInTheDocument();\n        expect(screen.queryByTestId('inner')).not.toBeInTheDocument();\n\n        fireEvent.pointerDown(document.body);\n\n        expect(screen.queryByTestId('outer')).not.toBeInTheDocument();\n        expect(screen.queryByTestId('inner')).not.toBeInTheDocument();\n      });\n\n      test('mixed', async () => {\n        render(\n          <NestedDialog testId=\"outer\" bubbles={{ outsidePress: true }}>\n            <NestedDialog testId=\"inner\" bubbles={{ outsidePress: false }}>\n              <button>test button</button>\n            </NestedDialog>\n          </NestedDialog>,\n        );\n\n        expect(screen.getByTestId('outer')).toBeInTheDocument();\n        expect(screen.getByTestId('inner')).toBeInTheDocument();\n\n        fireEvent.pointerDown(document.body);\n\n        expect(screen.getByTestId('outer')).toBeInTheDocument();\n        expect(screen.queryByTestId('inner')).not.toBeInTheDocument();\n\n        fireEvent.pointerDown(document.body);\n\n        expect(screen.queryByTestId('outer')).not.toBeInTheDocument();\n        expect(screen.queryByTestId('inner')).not.toBeInTheDocument();\n      });\n    });\n\n    describe('prop: bubbles.escapeKey', () => {\n      test('without FloatingTree', async () => {\n        function App() {\n          const [popoverOpen, setPopoverOpen] = React.useState(true);\n          const [tooltipOpen, setTooltipOpen] = React.useState(false);\n\n          const popover = useFloating({\n            open: popoverOpen,\n            onOpenChange: setPopoverOpen,\n          });\n          const tooltip = useFloating({\n            open: tooltipOpen,\n            onOpenChange: setTooltipOpen,\n          });\n\n          const popoverInteractions = useInteractions([useDismiss(popover.context)]);\n          const tooltipInteractions = useInteractions([\n            useFocus(tooltip.context),\n            useDismiss(tooltip.context),\n          ]);\n\n          return (\n            <React.Fragment>\n              <button\n                ref={popover.refs.setReference}\n                {...popoverInteractions.getReferenceProps()}\n              />\n              {popoverOpen && (\n                <div\n                  role=\"dialog\"\n                  ref={popover.refs.setFloating}\n                  {...popoverInteractions.getFloatingProps()}\n                >\n                  <button\n                    data-testid=\"focus-button\"\n                    ref={tooltip.refs.setReference}\n                    {...tooltipInteractions.getReferenceProps()}\n                  />\n                </div>\n              )}\n              {tooltipOpen && (\n                <div\n                  role=\"tooltip\"\n                  ref={tooltip.refs.setFloating}\n                  {...tooltipInteractions.getFloatingProps()}\n                />\n              )}\n            </React.Fragment>\n          );\n        }\n\n        render(<App />);\n\n        await flushMicrotasks();\n        await act(async () => {\n          screen.getByTestId('focus-button').focus();\n        });\n\n        await waitFor(() => {\n          expect(screen.getByRole('tooltip')).toBeInTheDocument();\n        });\n\n        await userEvent.keyboard('{Escape}');\n\n        await waitFor(() => {\n          expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();\n        });\n        expect(screen.getByRole('dialog')).toBeInTheDocument();\n      });\n\n      test('when true', async () => {\n        render(\n          <NestedDialog testId=\"outer\" bubbles>\n            <NestedDialog testId=\"inner\" bubbles>\n              <button>test button</button>\n            </NestedDialog>\n          </NestedDialog>,\n        );\n\n        expect(screen.getByTestId('outer')).toBeInTheDocument();\n        expect(screen.getByTestId('inner')).toBeInTheDocument();\n\n        await userEvent.keyboard('{Escape}');\n\n        expect(screen.queryByTestId('outer')).not.toBeInTheDocument();\n        expect(screen.queryByTestId('inner')).not.toBeInTheDocument();\n      });\n\n      test('when false', async () => {\n        render(\n          <NestedDialog testId=\"outer\" bubbles={{ escapeKey: false }}>\n            <NestedDialog testId=\"inner\" bubbles={{ escapeKey: false }}>\n              <button>test button</button>\n            </NestedDialog>\n          </NestedDialog>,\n        );\n\n        expect(screen.getByTestId('outer')).toBeInTheDocument();\n        expect(screen.getByTestId('inner')).toBeInTheDocument();\n\n        await userEvent.keyboard('{Escape}');\n\n        expect(screen.getByTestId('outer')).toBeInTheDocument();\n        expect(screen.queryByTestId('inner')).not.toBeInTheDocument();\n\n        await userEvent.keyboard('{Escape}');\n\n        expect(screen.queryByTestId('outer')).not.toBeInTheDocument();\n        expect(screen.queryByTestId('inner')).not.toBeInTheDocument();\n      });\n\n      test('mixed', async () => {\n        render(\n          <NestedDialog testId=\"outer\" bubbles={{ escapeKey: true }}>\n            <NestedDialog testId=\"inner\" bubbles={{ escapeKey: false }}>\n              <button>test button</button>\n            </NestedDialog>\n          </NestedDialog>,\n        );\n\n        expect(screen.getByTestId('outer')).toBeInTheDocument();\n        expect(screen.getByTestId('inner')).toBeInTheDocument();\n\n        await userEvent.keyboard('{Escape}');\n\n        expect(screen.getByTestId('outer')).toBeInTheDocument();\n        expect(screen.queryByTestId('inner')).not.toBeInTheDocument();\n\n        await userEvent.keyboard('{Escape}');\n\n        expect(screen.queryByTestId('outer')).not.toBeInTheDocument();\n        expect(screen.queryByTestId('inner')).not.toBeInTheDocument();\n      });\n    });\n  });\n\n  describe('prop: capture', () => {\n    describe('normalizeProp', () => {\n      test('undefined', () => {\n        const { escapeKey: escapeKeyCapture, outsidePress: outsidePressCapture } = normalizeProp();\n\n        expect(escapeKeyCapture).toBe(false);\n        expect(outsidePressCapture).toBe(true);\n      });\n\n      test('{}', () => {\n        const { escapeKey: escapeKeyCapture, outsidePress: outsidePressCapture } = normalizeProp(\n          {},\n        );\n\n        expect(escapeKeyCapture).toBe(false);\n        expect(outsidePressCapture).toBe(true);\n      });\n\n      test('when true', () => {\n        const { escapeKey: escapeKeyCapture, outsidePress: outsidePressCapture } =\n          normalizeProp(true);\n\n        expect(escapeKeyCapture).toBe(true);\n        expect(outsidePressCapture).toBe(true);\n      });\n\n      test('when false', () => {\n        const { escapeKey: escapeKeyCapture, outsidePress: outsidePressCapture } =\n          normalizeProp(false);\n\n        expect(escapeKeyCapture).toBe(false);\n        expect(outsidePressCapture).toBe(false);\n      });\n\n      test('{ escapeKey: true }', () => {\n        const { escapeKey: escapeKeyCapture, outsidePress: outsidePressCapture } = normalizeProp({\n          escapeKey: true,\n        });\n\n        expect(escapeKeyCapture).toBe(true);\n        expect(outsidePressCapture).toBe(true);\n      });\n\n      test('{ outsidePress: false }', () => {\n        const { escapeKey: escapeKeyCapture, outsidePress: outsidePressCapture } = normalizeProp({\n          outsidePress: false,\n        });\n\n        expect(escapeKeyCapture).toBe(false);\n        expect(outsidePressCapture).toBe(false);\n      });\n    });\n\n    function Overlay({ children }: { children: React.ReactNode }) {\n      return (\n        <div\n          style={{ width: '100vw', height: '100vh' }}\n          onPointerDown={(event) => event.stopPropagation()}\n          onKeyDown={(event) => {\n            if (event.key === 'Escape') {\n              event.stopPropagation();\n            }\n          }}\n        >\n          <span>outside</span>\n          {children}\n        </div>\n      );\n    }\n\n    function Dialog({\n      id,\n      children,\n      ...props\n    }: UseDismissProps & { id: string; children: React.ReactNode }) {\n      const [open, setOpen] = React.useState(true);\n      const nodeId = useFloatingNodeId();\n\n      const { refs, context } = useFloating({\n        open,\n        onOpenChange: setOpen,\n        nodeId,\n      });\n\n      const { getReferenceProps, getFloatingProps } = useInteractions([useDismiss(context, props)]);\n\n      return (\n        <FloatingNode id={nodeId}>\n          <button {...getReferenceProps({ ref: refs.setReference })} />\n          {open && (\n            <FloatingPortal>\n              <FloatingFocusManager context={context}>\n                <div {...getFloatingProps({ ref: refs.setFloating })}>\n                  <span>{id}</span>\n                  {children}\n                </div>\n              </FloatingFocusManager>\n            </FloatingPortal>\n          )}\n        </FloatingNode>\n      );\n    }\n\n    function NestedDialog(props: UseDismissProps & { id: string; children: React.ReactNode }) {\n      const parentId = useFloatingParentNodeId();\n\n      if (parentId == null) {\n        return (\n          <FloatingTree>\n            <Dialog {...props} />\n          </FloatingTree>\n        );\n      }\n\n      return <Dialog {...props} />;\n    }\n\n    describe('prop: capture.outsidePress', () => {\n      test('when true', async () => {\n        const user = userEvent.setup();\n\n        render(\n          <Overlay>\n            <NestedDialog id=\"outer\">\n              <NestedDialog id=\"inner\">{null}</NestedDialog>\n            </NestedDialog>\n          </Overlay>,\n        );\n\n        expect(screen.getByText('outer')).toBeInTheDocument();\n        expect(screen.getByText('inner')).toBeInTheDocument();\n\n        await user.click(screen.getByText('outer'));\n\n        expect(screen.getByText('outer')).toBeInTheDocument();\n        expect(screen.queryByText('inner')).not.toBeInTheDocument();\n\n        await user.click(screen.getByText('outside'));\n\n        expect(screen.queryByText('outer')).not.toBeInTheDocument();\n        expect(screen.queryByText('inner')).not.toBeInTheDocument();\n      });\n    });\n\n    describe('prop: capture.escapeKey', () => {\n      test('when false', async () => {\n        const user = userEvent.setup();\n\n        render(\n          <Overlay>\n            <NestedDialog id=\"outer\">\n              <NestedDialog id=\"inner\">{null}</NestedDialog>\n            </NestedDialog>\n          </Overlay>,\n        );\n\n        expect(screen.getByText('outer')).toBeInTheDocument();\n        expect(screen.getByText('inner')).toBeInTheDocument();\n\n        await user.keyboard('{Escape}');\n\n        expect(screen.getByText('outer')).toBeInTheDocument();\n        expect(screen.queryByText('inner')).not.toBeInTheDocument();\n\n        await user.keyboard('{Escape}');\n\n        expect(screen.queryByText('outer')).not.toBeInTheDocument();\n        expect(screen.queryByText('inner')).not.toBeInTheDocument();\n      });\n    });\n  });\n\n  describe('outsidePressEvent: intentional', () => {\n    test('dragging outside the floating element does not close', async () => {\n      render(<App outsidePressEvent=\"intentional\" />);\n      const floatingEl = screen.getByRole('tooltip');\n      fireEvent.mouseDown(floatingEl);\n      fireEvent.mouseUp(document.body);\n      expect(screen.getByRole('tooltip')).toBeInTheDocument();\n      await flushMicrotasks();\n    });\n\n    test('dragging inside the floating element does not close', async () => {\n      render(<App outsidePressEvent=\"intentional\" />);\n      const floatingEl = screen.getByRole('tooltip');\n      fireEvent.mouseDown(document.body);\n      fireEvent.mouseUp(floatingEl);\n      expect(screen.getByRole('tooltip')).toBeInTheDocument();\n      await flushMicrotasks();\n    });\n\n    test('dragging outside the floating element then clicking outside closes', async () => {\n      render(<App outsidePressEvent=\"intentional\" />);\n      const floatingEl = screen.getByRole('tooltip');\n      fireEvent.mouseDown(floatingEl);\n      fireEvent.mouseUp(document.body);\n      // A click event will have fired before the proper outside click.\n      fireEvent.click(document.body);\n      fireEvent.click(document.body);\n      expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();\n    });\n\n    test('inside click then programmatic outside click closes', async () => {\n      render(<App outsidePressEvent=\"intentional\" />);\n      const insideInput = screen.getByRole('textbox');\n\n      fireEvent.mouseDown(insideInput);\n      fireEvent.mouseUp(insideInput);\n      fireEvent.click(insideInput);\n      expect(screen.getByRole('tooltip')).toBeInTheDocument();\n\n      fireEvent.click(document.body);\n      expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();\n    });\n\n    test('inside click after drag does not cause immediate close on first outside click', async () => {\n      render(<App outsidePressEvent=\"intentional\" />);\n      const floatingEl = screen.getByRole('tooltip');\n      const insideInput = screen.getByRole('textbox');\n\n      fireEvent.mouseDown(floatingEl);\n      fireEvent.mouseUp(document.body);\n\n      // Inside clicks should never dismiss, and they should not consume the\n      // one-shot outside click suppression from the drag that started inside.\n      fireEvent.click(insideInput);\n      expect(screen.getByRole('tooltip')).toBeInTheDocument();\n\n      // First true outside click after that drag is still ignored once.\n      fireEvent.click(document.body);\n      expect(screen.getByRole('tooltip')).toBeInTheDocument();\n\n      // The next outside click is a deliberate outside press and dismisses.\n      fireEvent.click(document.body);\n      expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();\n    });\n\n    test('drag ending on outsidePress-ignored target does not consume next outside click', async () => {\n      render(\n        <App\n          outsidePressEvent=\"intentional\"\n          outsidePress={(event) => !(event.target as Element)?.closest('[data-testid=\"ignore\"]')}\n        />,\n      );\n      const floatingEl = screen.getByRole('tooltip');\n      const ignored = document.createElement('div');\n      ignored.setAttribute('data-testid', 'ignore');\n      document.body.append(ignored);\n\n      fireEvent.mouseDown(floatingEl);\n      fireEvent.mouseUp(ignored);\n\n      fireEvent.click(document.body);\n      expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();\n    });\n\n    test('press start prevented inside does not require double outside click', async () => {\n      function AppWithPreventedPressStart() {\n        const [open, setOpen] = React.useState(true);\n        const { refs, context } = useFloating({ open, onOpenChange: setOpen });\n        const { getReferenceProps, getFloatingProps } = useInteractions([\n          useDismiss(context, { outsidePressEvent: 'intentional' }),\n        ]);\n\n        return (\n          <React.Fragment>\n            <button {...getReferenceProps({ ref: refs.setReference })} />\n            {open && (\n              <div role=\"tooltip\" {...getFloatingProps({ ref: refs.setFloating })}>\n                <div data-testid=\"scrubber\" onPointerDown={(event) => event.preventDefault()} />\n              </div>\n            )}\n          </React.Fragment>\n        );\n      }\n\n      render(<AppWithPreventedPressStart />);\n      const scrubber = screen.getByTestId('scrubber');\n\n      fireEvent.pointerDown(scrubber, { pointerType: 'mouse', button: 0 });\n      fireEvent.mouseDown(scrubber, { button: 0 });\n      fireEvent.pointerUp(document.body, { pointerType: 'mouse', button: 0 });\n      fireEvent.mouseUp(document.body, { button: 0 });\n\n      // Wait a tick: if no immediate synthetic click occurred after pointerup,\n      // the next user click should still dismiss.\n      await act(async () => {\n        await new Promise((resolve) => {\n          setTimeout(resolve, 0);\n        });\n      });\n\n      fireEvent.pointerDown(document.body, { pointerType: 'mouse', button: 0 });\n      fireEvent.mouseDown(document.body, { button: 0 });\n      fireEvent.pointerUp(document.body, { pointerType: 'mouse', button: 0 });\n      fireEvent.mouseUp(document.body, { button: 0 });\n      fireEvent.click(document.body);\n      expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();\n    });\n\n    test('press start prevented inside suppresses only immediate outside click', async () => {\n      function AppWithPreventedPressStart() {\n        const [open, setOpen] = React.useState(true);\n        const { refs, context } = useFloating({ open, onOpenChange: setOpen });\n        const { getReferenceProps, getFloatingProps } = useInteractions([\n          useDismiss(context, { outsidePressEvent: 'intentional' }),\n        ]);\n\n        return (\n          <React.Fragment>\n            <button {...getReferenceProps({ ref: refs.setReference })} />\n            {open && (\n              <div role=\"tooltip\" {...getFloatingProps({ ref: refs.setFloating })}>\n                <div data-testid=\"scrubber\" onPointerDown={(event) => event.preventDefault()} />\n              </div>\n            )}\n          </React.Fragment>\n        );\n      }\n\n      render(<AppWithPreventedPressStart />);\n      const scrubber = screen.getByTestId('scrubber');\n\n      fireEvent.pointerDown(scrubber, { pointerType: 'mouse', button: 0 });\n      fireEvent.mouseDown(scrubber, { button: 0 });\n      fireEvent.pointerUp(document.body, { pointerType: 'mouse', button: 0 });\n      fireEvent.mouseUp(document.body, { button: 0 });\n\n      fireEvent.click(document.body);\n      expect(screen.getByRole('tooltip')).toBeInTheDocument();\n\n      await act(async () => {\n        await new Promise((resolve) => {\n          setTimeout(resolve, 0);\n        });\n      });\n\n      fireEvent.click(document.body);\n      expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();\n    });\n\n    test('pointercancel after prevented press start suppresses immediate outside click', async () => {\n      function AppWithPreventedPressStart() {\n        const [open, setOpen] = React.useState(true);\n        const { refs, context } = useFloating({ open, onOpenChange: setOpen });\n        const { getReferenceProps, getFloatingProps } = useInteractions([\n          useDismiss(context, { outsidePressEvent: 'intentional' }),\n        ]);\n\n        return (\n          <React.Fragment>\n            <button {...getReferenceProps({ ref: refs.setReference })} />\n            {open && (\n              <div role=\"tooltip\" {...getFloatingProps({ ref: refs.setFloating })}>\n                <div data-testid=\"scrubber\" onPointerDown={(event) => event.preventDefault()} />\n              </div>\n            )}\n          </React.Fragment>\n        );\n      }\n\n      render(<AppWithPreventedPressStart />);\n      const scrubber = screen.getByTestId('scrubber');\n\n      fireEvent.pointerDown(scrubber, { pointerType: 'mouse', button: 0 });\n      fireEvent.mouseDown(scrubber, { button: 0 });\n      fireEvent.pointerCancel(document.body, { pointerType: 'mouse' });\n\n      fireEvent.click(document.body);\n      expect(screen.getByRole('tooltip')).toBeInTheDocument();\n\n      await act(async () => {\n        await new Promise((resolve) => {\n          setTimeout(resolve, 0);\n        });\n      });\n\n      fireEvent.click(document.body);\n      expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();\n    });\n  });\n\n  test('nested floating elements with different portal containers', async () => {\n    function ButtonWithFloating({\n      children,\n      portalContainer,\n      triggerText,\n    }: {\n      children?: React.ReactNode;\n      portalContainer?: HTMLElement | null;\n      triggerText: string;\n    }) {\n      const [open, setOpen] = React.useState(false);\n      const { refs, floatingStyles, context } = useFloating({\n        open,\n        onOpenChange: setOpen,\n      });\n\n      const click = useClick(context);\n      const dismiss = useDismiss(context);\n\n      const { getReferenceProps, getFloatingProps } = useInteractions([click, dismiss]);\n\n      return (\n        <React.Fragment>\n          <button ref={refs.setReference} {...getReferenceProps()}>\n            {triggerText}\n          </button>\n          {open && (\n            <FloatingPortal container={portalContainer}>\n              <FloatingFocusManager context={context} modal={false}>\n                <div ref={refs.setFloating} style={floatingStyles} {...getFloatingProps()}>\n                  {children}\n                </div>\n              </FloatingFocusManager>\n            </FloatingPortal>\n          )}\n        </React.Fragment>\n      );\n    }\n\n    function App() {\n      const [otherContainer, setOtherContainer] = React.useState<HTMLDivElement | null>();\n\n      const portal1 = undefined;\n      const portal2 = otherContainer;\n\n      return (\n        <React.Fragment>\n          <ButtonWithFloating portalContainer={portal1} triggerText=\"open 1\">\n            <ButtonWithFloating portalContainer={portal2} triggerText=\"open 2\">\n              <button>nested</button>\n            </ButtonWithFloating>\n          </ButtonWithFloating>\n          <div ref={setOtherContainer} />\n        </React.Fragment>\n      );\n    }\n\n    render(<App />);\n\n    await userEvent.click(screen.getByText('open 1'));\n    expect(screen.getByText('open 2')).toBeInTheDocument();\n\n    await userEvent.click(screen.getByText('open 2'));\n    await flushMicrotasks();\n\n    expect(screen.getByText('open 1')).toBeInTheDocument();\n    expect(screen.getByText('open 2')).toBeInTheDocument();\n    expect(screen.getByText('nested')).toBeInTheDocument();\n  });\n});\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/hooks/useDismiss.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport {\n  getComputedStyle,\n  getParentNode,\n  isElement,\n  isHTMLElement,\n  isLastTraversableNode,\n  isShadowRoot,\n  isWebKit,\n} from '@floating-ui/utils/dom';\nimport { Timeout, useTimeout } from '@base-ui/utils/useTimeout';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { ownerDocument } from '@base-ui/utils/owner';\nimport {\n  contains,\n  getTarget,\n  isEventTargetWithin,\n  isReactEvent,\n  isRootElement,\n  getNodeChildren,\n} from '../utils';\n\n/* eslint-disable no-underscore-dangle */\n\nimport { useFloatingTree } from '../components/FloatingTree';\nimport { FloatingTreeStore } from '../components/FloatingTreeStore';\nimport type { ElementProps, FloatingContext, FloatingRootContext } from '../types';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport { createAttribute } from '../utils/createAttribute';\n\ntype PressType = 'intentional' | 'sloppy';\n\nconst bubbleHandlerKeys = {\n  intentional: 'onClick',\n  sloppy: 'onPointerDown',\n} as const;\n\nfunction alwaysFalse() {\n  return false;\n}\n\nexport function normalizeProp(\n  normalizable?: boolean | { escapeKey?: boolean | undefined; outsidePress?: boolean | undefined },\n) {\n  return {\n    escapeKey:\n      typeof normalizable === 'boolean' ? normalizable : (normalizable?.escapeKey ?? false),\n    outsidePress:\n      typeof normalizable === 'boolean' ? normalizable : (normalizable?.outsidePress ?? true),\n  };\n}\n\nexport interface UseDismissProps {\n  /**\n   * Whether the Hook is enabled, including all internal Effects and event\n   * handlers.\n   * @default true\n   */\n  enabled?: boolean | undefined;\n  /**\n   * Whether to dismiss the floating element upon pressing the `esc` key.\n   * @default true\n   */\n  escapeKey?: boolean | undefined;\n  /**\n   * Whether to dismiss the floating element upon pressing the reference\n   * element. You likely want to ensure the `move` option in the `useHover()`\n   * Hook has been disabled when this is in use.\n   *\n   * A lazy getter invoked when handling reference press events.\n   * @default false\n   */\n  referencePress?: (() => boolean) | undefined;\n  /**\n   * The type of event to use to determine a \"press\".\n   * - `down` is `pointerdown` on mouse input, but special iOS-like touch handling on touch input.\n   * - `up` is lazy on both mouse + touch input (equivalent to `click`).\n   * @default 'down'\n   */\n  referencePressEvent?: PressType | undefined;\n  /**\n   * Whether to dismiss the floating element upon pressing outside of the\n   * floating element.\n   * If you have another element, like a toast, that is rendered outside the\n   * floating element's React tree and don't want the floating element to close\n   * when pressing it, you can guard the check like so:\n   * ```jsx\n   * useDismiss(context, {\n   *   outsidePress: (event) => !event.target.closest('.toast'),\n   * });\n   * ```\n   * @default true\n   */\n  outsidePress?: boolean | ((event: MouseEvent | TouchEvent) => boolean) | undefined;\n  /**\n   * The type of event to use to determine an outside \"press\".\n   * - `intentional` requires the user to click outside intentionally, firing on `pointerup` for mouse, and requiring minimal `touchmove`s for touch.\n   * - `sloppy` fires on `pointerdown` for mouse, while for touch it fires on `touchend` (within 1 second) or while scrolling away after `touchstart`.\n   */\n  outsidePressEvent?:\n    | PressType\n    | {\n        mouse: PressType;\n        touch: PressType;\n      }\n    | (() =>\n        | PressType\n        | {\n            mouse: PressType;\n            touch: PressType;\n          })\n    | undefined;\n  /**\n   * Determines whether event listeners bubble upwards through a tree of\n   * floating elements.\n   */\n  bubbles?:\n    | boolean\n    | { escapeKey?: boolean | undefined; outsidePress?: boolean | undefined }\n    | undefined;\n  /**\n   * External FlatingTree to use when the one provided by context can't be used.\n   */\n  externalTree?: FloatingTreeStore | undefined;\n}\n\n/**\n * Closes the floating element when a dismissal is requested — by default, when\n * the user presses the `escape` key or outside of the floating element.\n * @see https://floating-ui.com/docs/useDismiss\n */\nexport function useDismiss(\n  context: FloatingRootContext | FloatingContext,\n  props: UseDismissProps = {},\n): ElementProps {\n  const store = 'rootStore' in context ? context.rootStore : context;\n  const open = store.useState('open');\n  const floatingElement = store.useState('floatingElement');\n\n  const { dataRef } = store.context;\n\n  const {\n    enabled = true,\n    escapeKey = true,\n    outsidePress: outsidePressProp = true,\n    outsidePressEvent = 'sloppy',\n    referencePress = alwaysFalse,\n    referencePressEvent = 'sloppy',\n    bubbles,\n    externalTree,\n  } = props;\n\n  const tree = useFloatingTree(externalTree);\n  const outsidePressFn = useStableCallback(\n    typeof outsidePressProp === 'function' ? outsidePressProp : () => false,\n  );\n  const outsidePress = typeof outsidePressProp === 'function' ? outsidePressFn : outsidePressProp;\n  const outsidePressEnabled = outsidePress !== false;\n  const getOutsidePressEventProp = useStableCallback(() => outsidePressEvent);\n\n  const pressStartedInsideRef = React.useRef(false);\n  const pressStartPreventedRef = React.useRef(false);\n  // Ignore only the very next outside click after dragging from inside to outside.\n  const suppressNextOutsideClickRef = React.useRef(false);\n\n  const { escapeKey: escapeKeyBubbles, outsidePress: outsidePressBubbles } = normalizeProp(bubbles);\n\n  const touchStateRef = React.useRef<{\n    startTime: number;\n    startX: number;\n    startY: number;\n    dismissOnTouchEnd: boolean;\n    dismissOnMouseDown: boolean;\n  } | null>(null);\n\n  const cancelDismissOnEndTimeout = useTimeout();\n  const clearInsideReactTreeTimeout = useTimeout();\n\n  const clearInsideReactTree = useStableCallback(() => {\n    clearInsideReactTreeTimeout.clear();\n    dataRef.current.insideReactTree = false;\n  });\n\n  const isComposingRef = React.useRef(false);\n  const currentPointerTypeRef = React.useRef<PointerEvent['pointerType']>('');\n\n  const isReferencePressEnabled = useStableCallback(referencePress);\n\n  const closeOnEscapeKeyDown = useStableCallback(\n    (event: React.KeyboardEvent<Element> | KeyboardEvent) => {\n      if (!open || !enabled || !escapeKey || event.key !== 'Escape') {\n        return;\n      }\n\n      // Wait until IME is settled. Pressing `Escape` while composing should\n      // close the compose menu, but not the floating element.\n      if (isComposingRef.current) {\n        return;\n      }\n\n      const nodeId = dataRef.current.floatingContext?.nodeId;\n\n      const children = tree ? getNodeChildren(tree.nodesRef.current, nodeId) : [];\n\n      if (!escapeKeyBubbles) {\n        if (children.length > 0) {\n          let shouldDismiss = true;\n\n          children.forEach((child) => {\n            if (child.context?.open && !child.context.dataRef.current.__escapeKeyBubbles) {\n              shouldDismiss = false;\n            }\n          });\n\n          if (!shouldDismiss) {\n            return;\n          }\n        }\n      }\n\n      const native = isReactEvent(event) ? event.nativeEvent : event;\n      const eventDetails = createChangeEventDetails(REASONS.escapeKey, native);\n\n      store.setOpen(false, eventDetails);\n\n      if (!escapeKeyBubbles && !eventDetails.isPropagationAllowed) {\n        event.stopPropagation();\n      }\n    },\n  );\n\n  const markInsideReactTree = useStableCallback(() => {\n    dataRef.current.insideReactTree = true;\n    clearInsideReactTreeTimeout.start(0, clearInsideReactTree);\n  });\n\n  React.useEffect(() => {\n    if (!open || !enabled) {\n      return undefined;\n    }\n\n    dataRef.current.__escapeKeyBubbles = escapeKeyBubbles;\n    dataRef.current.__outsidePressBubbles = outsidePressBubbles;\n\n    const compositionTimeout = new Timeout();\n    const preventedPressSuppressionTimeout = new Timeout();\n\n    function handleCompositionStart() {\n      compositionTimeout.clear();\n      isComposingRef.current = true;\n    }\n\n    function handleCompositionEnd() {\n      // Safari fires `compositionend` before `keydown`, so we need to wait\n      // until the next tick to set `isComposing` to `false`.\n      // https://bugs.webkit.org/show_bug.cgi?id=165004\n      compositionTimeout.start(\n        // 0ms or 1ms don't work in Safari. 5ms appears to consistently work.\n        // Only apply to WebKit for the test to remain 0ms.\n        isWebKit() ? 5 : 0,\n        () => {\n          isComposingRef.current = false;\n        },\n      );\n    }\n\n    function suppressImmediateOutsideClickAfterPreventedStart() {\n      suppressNextOutsideClickRef.current = true;\n      // Firefox can emit the synthetic outside click in a later task after\n      // pointer lock exit, so microtask clearing is too early here.\n      preventedPressSuppressionTimeout.start(0, () => {\n        suppressNextOutsideClickRef.current = false;\n      });\n    }\n\n    function resetPressStartState() {\n      pressStartedInsideRef.current = false;\n      pressStartPreventedRef.current = false;\n    }\n\n    function getOutsidePressEvent(): PressType {\n      const type = currentPointerTypeRef.current as 'pen' | 'mouse' | 'touch' | '';\n      const computedType = type === 'pen' || !type ? 'mouse' : type;\n\n      const outsidePressEventValue = getOutsidePressEventProp();\n      const resolved =\n        typeof outsidePressEventValue === 'function'\n          ? outsidePressEventValue()\n          : outsidePressEventValue;\n\n      if (typeof resolved === 'string') {\n        return resolved;\n      }\n\n      return resolved[computedType];\n    }\n\n    function shouldIgnoreEvent(event: Event) {\n      const computedOutsidePressEvent = getOutsidePressEvent();\n      return (\n        (computedOutsidePressEvent === 'intentional' && event.type !== 'click') ||\n        (computedOutsidePressEvent === 'sloppy' && event.type === 'click')\n      );\n    }\n\n    function isEventWithinFloatingTree(event: Event) {\n      const nodeId = dataRef.current.floatingContext?.nodeId;\n      const targetIsInsideChildren =\n        tree &&\n        getNodeChildren(tree.nodesRef.current, nodeId).some((node) =>\n          isEventTargetWithin(event, node.context?.elements.floating),\n        );\n\n      return (\n        isEventTargetWithin(event, store.select('floatingElement')) ||\n        isEventTargetWithin(event, store.select('domReferenceElement')) ||\n        targetIsInsideChildren\n      );\n    }\n\n    function closeOnPressOutside(event: MouseEvent | PointerEvent | TouchEvent) {\n      if (shouldIgnoreEvent(event)) {\n        clearInsideReactTree();\n        return;\n      }\n\n      if (dataRef.current.insideReactTree) {\n        clearInsideReactTree();\n        return;\n      }\n\n      const target = getTarget(event);\n      const inertSelector = `[${createAttribute('inert')}]`;\n      const targetRoot = isElement(target) ? target.getRootNode() : null;\n      const markers = Array.from(\n        (isShadowRoot(targetRoot)\n          ? targetRoot\n          : ownerDocument(store.select('floatingElement'))\n        ).querySelectorAll(inertSelector),\n      );\n\n      const triggers = store.context.triggerElements;\n\n      // If another trigger is clicked, don't close the floating element.\n      if (\n        target &&\n        (triggers.hasElement(target as Element) ||\n          triggers.hasMatchingElement((trigger) => contains(trigger, target as Element)))\n      ) {\n        return;\n      }\n\n      let targetRootAncestor = isElement(target) ? target : null;\n      while (targetRootAncestor && !isLastTraversableNode(targetRootAncestor)) {\n        const nextParent = getParentNode(targetRootAncestor);\n        if (isLastTraversableNode(nextParent) || !isElement(nextParent)) {\n          break;\n        }\n\n        targetRootAncestor = nextParent;\n      }\n\n      // Check if the click occurred on a third-party element injected after the\n      // floating element rendered.\n      if (\n        markers.length &&\n        isElement(target) &&\n        !isRootElement(target) &&\n        // Clicked on a direct ancestor (e.g. FloatingOverlay).\n        !contains(target, store.select('floatingElement')) &&\n        // If the target root element contains none of the markers, then the\n        // element was injected after the floating element rendered.\n        markers.every((marker) => !contains(targetRootAncestor, marker))\n      ) {\n        return;\n      }\n\n      // Check if the click occurred on the scrollbar\n      // Skip for touch events: scrollbars don't receive touch events on most platforms\n      if (isHTMLElement(target) && !('touches' in event)) {\n        const lastTraversableNode = isLastTraversableNode(target);\n        const style = getComputedStyle(target);\n        const scrollRe = /auto|scroll/;\n        const isScrollableX = lastTraversableNode || scrollRe.test(style.overflowX);\n        const isScrollableY = lastTraversableNode || scrollRe.test(style.overflowY);\n\n        const canScrollX =\n          isScrollableX && target.clientWidth > 0 && target.scrollWidth > target.clientWidth;\n        const canScrollY =\n          isScrollableY && target.clientHeight > 0 && target.scrollHeight > target.clientHeight;\n\n        const isRTL = style.direction === 'rtl';\n\n        // Check click position relative to scrollbar.\n        // In some browsers it is possible to change the <body> (or window)\n        // scrollbar to the left side, but is very rare and is difficult to\n        // check for. Plus, for modal dialogs with backdrops, it is more\n        // important that the backdrop is checked but not so much the window.\n        const pressedVerticalScrollbar =\n          canScrollY &&\n          (isRTL\n            ? event.offsetX <= target.offsetWidth - target.clientWidth\n            : event.offsetX > target.clientWidth);\n\n        const pressedHorizontalScrollbar = canScrollX && event.offsetY > target.clientHeight;\n\n        if (pressedVerticalScrollbar || pressedHorizontalScrollbar) {\n          return;\n        }\n      }\n\n      if (isEventWithinFloatingTree(event)) {\n        return;\n      }\n\n      // In intentional mode, a press that starts inside and ends outside gets\n      // one suppressed outside click. Run this after inside-target checks so\n      // inside clicks don't consume the one-shot suppression.\n      if (getOutsidePressEvent() === 'intentional' && suppressNextOutsideClickRef.current) {\n        preventedPressSuppressionTimeout.clear();\n        suppressNextOutsideClickRef.current = false;\n        return;\n      }\n\n      if (typeof outsidePress === 'function' && !outsidePress(event)) {\n        return;\n      }\n\n      const nodeId = dataRef.current.floatingContext?.nodeId;\n      const children = tree ? getNodeChildren(tree.nodesRef.current, nodeId) : [];\n      if (children.length > 0) {\n        let shouldDismiss = true;\n\n        children.forEach((child) => {\n          if (child.context?.open && !child.context.dataRef.current.__outsidePressBubbles) {\n            shouldDismiss = false;\n          }\n        });\n\n        if (!shouldDismiss) {\n          return;\n        }\n      }\n\n      store.setOpen(false, createChangeEventDetails(REASONS.outsidePress, event));\n      clearInsideReactTree();\n    }\n\n    function handlePointerDown(event: PointerEvent) {\n      if (\n        getOutsidePressEvent() !== 'sloppy' ||\n        event.pointerType === 'touch' ||\n        !store.select('open') ||\n        !enabled ||\n        isEventTargetWithin(event, store.select('floatingElement')) ||\n        isEventTargetWithin(event, store.select('domReferenceElement'))\n      ) {\n        return;\n      }\n\n      closeOnPressOutside(event);\n    }\n\n    function handleTouchStart(event: TouchEvent) {\n      if (\n        getOutsidePressEvent() !== 'sloppy' ||\n        !store.select('open') ||\n        !enabled ||\n        isEventTargetWithin(event, store.select('floatingElement')) ||\n        isEventTargetWithin(event, store.select('domReferenceElement'))\n      ) {\n        return;\n      }\n\n      const touch = event.touches[0];\n      if (touch) {\n        touchStateRef.current = {\n          startTime: Date.now(),\n          startX: touch.clientX,\n          startY: touch.clientY,\n          dismissOnTouchEnd: false,\n          dismissOnMouseDown: true,\n        };\n\n        cancelDismissOnEndTimeout.start(1000, () => {\n          if (touchStateRef.current) {\n            touchStateRef.current.dismissOnTouchEnd = false;\n            touchStateRef.current.dismissOnMouseDown = false;\n          }\n        });\n      }\n    }\n\n    function handleTouchStartCapture(event: TouchEvent) {\n      currentPointerTypeRef.current = 'touch';\n      const target = getTarget(event);\n      function callback() {\n        handleTouchStart(event);\n        target?.removeEventListener(event.type, callback);\n      }\n      target?.addEventListener(event.type, callback);\n    }\n\n    function closeOnPressOutsideCapture(event: PointerEvent | MouseEvent) {\n      cancelDismissOnEndTimeout.clear();\n\n      if (event.type === 'pointerdown') {\n        currentPointerTypeRef.current = (event as PointerEvent).pointerType;\n      }\n\n      if (\n        event.type === 'mousedown' &&\n        touchStateRef.current &&\n        !touchStateRef.current.dismissOnMouseDown\n      ) {\n        return;\n      }\n\n      const target = getTarget(event);\n\n      function callback() {\n        if (event.type === 'pointerdown') {\n          handlePointerDown(event as PointerEvent);\n        } else {\n          closeOnPressOutside(event as MouseEvent);\n        }\n        target?.removeEventListener(event.type, callback);\n      }\n      target?.addEventListener(event.type, callback);\n    }\n\n    function handlePressEndCapture(event: PointerEvent | MouseEvent) {\n      if (!pressStartedInsideRef.current) {\n        return;\n      }\n\n      const pressStartedInsideDefaultPrevented = pressStartPreventedRef.current;\n      resetPressStartState();\n\n      if (getOutsidePressEvent() !== 'intentional') {\n        return;\n      }\n\n      if (event.type === 'pointercancel') {\n        if (pressStartedInsideDefaultPrevented) {\n          suppressImmediateOutsideClickAfterPreventedStart();\n        }\n        return;\n      }\n\n      if (isEventWithinFloatingTree(event)) {\n        return;\n      }\n\n      // If pointerdown was prevented, no click may be generated for that\n      // interaction. However, Firefox may still emit an immediate click after\n      // pointerup (e.g. NumberField scrub with pointer lock), so suppress for\n      // one tick to absorb that synthetic click only.\n      if (pressStartedInsideDefaultPrevented) {\n        suppressImmediateOutsideClickAfterPreventedStart();\n        return;\n      }\n\n      // Avoid suppressing when outsidePress explicitly ignores this target.\n      if (typeof outsidePress === 'function' && !outsidePress(event as MouseEvent)) {\n        return;\n      }\n\n      preventedPressSuppressionTimeout.clear();\n      suppressNextOutsideClickRef.current = true;\n      clearInsideReactTree();\n    }\n\n    function handleTouchMove(event: TouchEvent) {\n      if (\n        getOutsidePressEvent() !== 'sloppy' ||\n        !touchStateRef.current ||\n        isEventTargetWithin(event, store.select('floatingElement')) ||\n        isEventTargetWithin(event, store.select('domReferenceElement'))\n      ) {\n        return;\n      }\n\n      const touch = event.touches[0];\n      if (!touch) {\n        return;\n      }\n\n      const deltaX = Math.abs(touch.clientX - touchStateRef.current.startX);\n      const deltaY = Math.abs(touch.clientY - touchStateRef.current.startY);\n      const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);\n\n      if (distance > 5) {\n        touchStateRef.current.dismissOnTouchEnd = true;\n      }\n\n      if (distance > 10) {\n        closeOnPressOutside(event);\n        cancelDismissOnEndTimeout.clear();\n        touchStateRef.current = null;\n      }\n    }\n\n    function handleTouchMoveCapture(event: TouchEvent) {\n      const target = getTarget(event);\n      function callback() {\n        handleTouchMove(event);\n        target?.removeEventListener(event.type, callback);\n      }\n      target?.addEventListener(event.type, callback);\n    }\n\n    function handleTouchEnd(event: TouchEvent) {\n      if (\n        getOutsidePressEvent() !== 'sloppy' ||\n        !touchStateRef.current ||\n        isEventTargetWithin(event, store.select('floatingElement')) ||\n        isEventTargetWithin(event, store.select('domReferenceElement'))\n      ) {\n        return;\n      }\n\n      if (touchStateRef.current.dismissOnTouchEnd) {\n        closeOnPressOutside(event);\n      }\n\n      cancelDismissOnEndTimeout.clear();\n      touchStateRef.current = null;\n    }\n\n    function handleTouchEndCapture(event: TouchEvent) {\n      const target = getTarget(event);\n      function callback() {\n        handleTouchEnd(event);\n        target?.removeEventListener(event.type, callback);\n      }\n      target?.addEventListener(event.type, callback);\n    }\n\n    const doc = ownerDocument(floatingElement);\n\n    if (escapeKey) {\n      doc.addEventListener('keydown', closeOnEscapeKeyDown);\n      doc.addEventListener('compositionstart', handleCompositionStart);\n      doc.addEventListener('compositionend', handleCompositionEnd);\n    }\n\n    if (outsidePressEnabled) {\n      doc.addEventListener('click', closeOnPressOutsideCapture, true);\n      doc.addEventListener('pointerdown', closeOnPressOutsideCapture, true);\n      doc.addEventListener('pointerup', handlePressEndCapture, true);\n      doc.addEventListener('pointercancel', handlePressEndCapture, true);\n      doc.addEventListener('mousedown', closeOnPressOutsideCapture, true);\n      doc.addEventListener('mouseup', handlePressEndCapture, true);\n      doc.addEventListener('touchstart', handleTouchStartCapture, true);\n      doc.addEventListener('touchmove', handleTouchMoveCapture, true);\n      doc.addEventListener('touchend', handleTouchEndCapture, true);\n    }\n\n    return () => {\n      if (escapeKey) {\n        doc.removeEventListener('keydown', closeOnEscapeKeyDown);\n        doc.removeEventListener('compositionstart', handleCompositionStart);\n        doc.removeEventListener('compositionend', handleCompositionEnd);\n      }\n\n      if (outsidePressEnabled) {\n        doc.removeEventListener('click', closeOnPressOutsideCapture, true);\n        doc.removeEventListener('pointerdown', closeOnPressOutsideCapture, true);\n        doc.removeEventListener('pointerup', handlePressEndCapture, true);\n        doc.removeEventListener('pointercancel', handlePressEndCapture, true);\n        doc.removeEventListener('mousedown', closeOnPressOutsideCapture, true);\n        doc.removeEventListener('mouseup', handlePressEndCapture, true);\n        doc.removeEventListener('touchstart', handleTouchStartCapture, true);\n        doc.removeEventListener('touchmove', handleTouchMoveCapture, true);\n        doc.removeEventListener('touchend', handleTouchEndCapture, true);\n      }\n\n      compositionTimeout.clear();\n      preventedPressSuppressionTimeout.clear();\n      resetPressStartState();\n      suppressNextOutsideClickRef.current = false;\n    };\n  }, [\n    dataRef,\n    floatingElement,\n    escapeKey,\n    outsidePressEnabled,\n    outsidePress,\n    open,\n    enabled,\n    escapeKeyBubbles,\n    outsidePressBubbles,\n    closeOnEscapeKeyDown,\n    clearInsideReactTree,\n    getOutsidePressEventProp,\n    tree,\n    store,\n    cancelDismissOnEndTimeout,\n  ]);\n\n  React.useEffect(clearInsideReactTree, [outsidePress, clearInsideReactTree]);\n\n  const reference: ElementProps['reference'] = React.useMemo(\n    () => ({\n      onKeyDown: closeOnEscapeKeyDown,\n      [bubbleHandlerKeys[referencePressEvent]]: (event: React.SyntheticEvent) => {\n        if (!isReferencePressEnabled()) {\n          return;\n        }\n\n        store.setOpen(\n          false,\n          createChangeEventDetails(REASONS.triggerPress, event.nativeEvent as any),\n        );\n      },\n      ...(referencePressEvent !== 'intentional' && {\n        onClick(event) {\n          if (!isReferencePressEnabled()) {\n            return;\n          }\n\n          store.setOpen(false, createChangeEventDetails(REASONS.triggerPress, event.nativeEvent));\n        },\n      }),\n    }),\n    [closeOnEscapeKeyDown, store, referencePressEvent, isReferencePressEnabled],\n  );\n\n  const markPressStartedInsideReactTree = useStableCallback(\n    (event: React.PointerEvent | React.MouseEvent) => {\n      if (!open || !enabled || event.button !== 0) {\n        return;\n      }\n      const target = getTarget(event.nativeEvent) as Element | null;\n      // Only treat presses that start within the floating DOM subtree as inside.\n      // This avoids suppressing parent dismissal when interacting with nested portals.\n      if (!contains(store.select('floatingElement'), target)) {\n        return;\n      }\n\n      if (!pressStartedInsideRef.current) {\n        pressStartedInsideRef.current = true;\n        pressStartPreventedRef.current = false;\n      }\n    },\n  );\n\n  const markInsidePressStartPrevented = useStableCallback(\n    (event: React.PointerEvent | React.MouseEvent) => {\n      if (!open || !enabled) {\n        return;\n      }\n\n      if (!(event.defaultPrevented || event.nativeEvent.defaultPrevented)) {\n        return;\n      }\n\n      if (pressStartedInsideRef.current) {\n        pressStartPreventedRef.current = true;\n      }\n    },\n  );\n\n  const floating: ElementProps['floating'] = React.useMemo(\n    () => ({\n      onKeyDown: closeOnEscapeKeyDown,\n      // `onMouseDown` may be blocked if `event.preventDefault()` is called in\n      // `onPointerDown`, such as with <NumberField.ScrubArea>.\n      // See https://github.com/mui/base-ui/pull/3379\n      onPointerDown: markInsidePressStartPrevented,\n      onMouseDown: markInsidePressStartPrevented,\n      onClickCapture: markInsideReactTree,\n      onMouseDownCapture(event) {\n        markInsideReactTree();\n        markPressStartedInsideReactTree(event);\n      },\n      onPointerDownCapture(event) {\n        markInsideReactTree();\n        markPressStartedInsideReactTree(event);\n      },\n      onMouseUpCapture: markInsideReactTree,\n      onTouchEndCapture: markInsideReactTree,\n      onTouchMoveCapture: markInsideReactTree,\n    }),\n    [\n      closeOnEscapeKeyDown,\n      markInsideReactTree,\n      markPressStartedInsideReactTree,\n      markInsidePressStartPrevented,\n    ],\n  );\n\n  return React.useMemo(\n    () => (enabled ? { reference, floating, trigger: reference } : {}),\n    [enabled, reference, floating],\n  );\n}\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/hooks/useFloating.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { useFloating as usePosition, type VirtualElement } from '@floating-ui/react-dom';\nimport { isElement } from '@floating-ui/utils/dom';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\n\nimport { useFloatingTree } from '../components/FloatingTree';\nimport type {\n  FloatingContext,\n  NarrowedElement,\n  ReferenceType,\n  UseFloatingOptions,\n  UseFloatingReturn,\n} from '../types';\nimport { useFloatingRootContext } from './useFloatingRootContext';\nimport { FloatingRootStore } from '../components/FloatingRootStore';\n\n/**\n * Provides data to position a floating element and context to add interactions.\n * @see https://floating-ui.com/docs/useFloating\n */\nexport function useFloating(options: UseFloatingOptions = {}): UseFloatingReturn {\n  const { nodeId, externalTree } = options;\n\n  const internalRootStore = useFloatingRootContext(options);\n\n  const rootContext = options.rootContext || internalRootStore;\n  const rootContextElements = {\n    reference: rootContext.useState('referenceElement'),\n    floating: rootContext.useState('floatingElement'),\n    domReference: rootContext.useState('domReferenceElement'),\n  };\n\n  const [positionReference, setPositionReferenceRaw] = React.useState<ReferenceType | null>(null);\n\n  const domReferenceRef = React.useRef<NarrowedElement<ReferenceType> | null>(null);\n\n  const tree = useFloatingTree(externalTree);\n\n  useIsoLayoutEffect(() => {\n    if (rootContextElements.domReference) {\n      domReferenceRef.current =\n        rootContextElements.domReference as NarrowedElement<ReferenceType> | null;\n    }\n  }, [rootContextElements.domReference]);\n\n  const position = usePosition({\n    ...options,\n    elements: {\n      ...rootContextElements,\n      ...(positionReference && { reference: positionReference }),\n    },\n  });\n\n  const setPositionReference = React.useCallback(\n    (node: ReferenceType | null) => {\n      const computedPositionReference = isElement(node)\n        ? ({\n            getBoundingClientRect: () => node.getBoundingClientRect(),\n            getClientRects: () => node.getClientRects(),\n            contextElement: node,\n          } satisfies VirtualElement)\n        : node;\n      // Store the positionReference in state if the DOM reference is specified externally via the\n      // `elements.reference` option. This ensures that it won't be overridden on future renders.\n      setPositionReferenceRaw(computedPositionReference);\n      position.refs.setReference(computedPositionReference);\n    },\n    [position.refs],\n  );\n\n  const [localDomReference, setLocalDomReference] =\n    React.useState<NarrowedElement<ReferenceType> | null>(null);\n  const [localFloatingElement, setLocalFloatingElement] = React.useState<HTMLElement | null>(null);\n  rootContext.useSyncedValue('referenceElement', localDomReference);\n  rootContext.useSyncedValue(\n    'domReferenceElement',\n    isElement(localDomReference) ? (localDomReference as Element) : null,\n  );\n  rootContext.useSyncedValue('floatingElement', localFloatingElement);\n\n  const setReference = React.useCallback(\n    (node: ReferenceType | null) => {\n      if (isElement(node) || node === null) {\n        (domReferenceRef as React.RefObject<Element | null>).current = node;\n        setLocalDomReference(node as NarrowedElement<ReferenceType> | null);\n      }\n\n      // Backwards-compatibility for passing a virtual element to `reference`\n      // after it has set the DOM reference.\n      if (\n        isElement(position.refs.reference.current) ||\n        position.refs.reference.current === null ||\n        // Don't allow setting virtual elements using the old technique back to\n        // `null` to support `positionReference` + an unstable `reference`\n        // callback ref.\n        (node !== null && !isElement(node))\n      ) {\n        position.refs.setReference(node);\n      }\n    },\n    [position.refs, setLocalDomReference],\n  );\n\n  const setFloating = React.useCallback(\n    (node: HTMLElement | null) => {\n      setLocalFloatingElement(node);\n      position.refs.setFloating(node);\n    },\n    [position.refs],\n  );\n\n  const refs = React.useMemo(\n    () => ({\n      ...position.refs,\n      setReference,\n      setFloating,\n      setPositionReference,\n      domReference: domReferenceRef,\n    }),\n    [position.refs, setReference, setFloating, setPositionReference],\n  );\n\n  const elements = React.useMemo(\n    () => ({\n      ...position.elements,\n      domReference: rootContextElements.domReference,\n    }),\n    [position.elements, rootContextElements.domReference],\n  );\n\n  const open = rootContext.useState('open');\n  const floatingId = rootContext.useState('floatingId');\n\n  const context = React.useMemo<FloatingContext>(\n    () => ({\n      ...position,\n      dataRef: rootContext.context.dataRef,\n      open,\n      onOpenChange: rootContext.setOpen,\n      events: rootContext.context.events,\n      floatingId,\n      refs,\n      elements,\n      nodeId,\n      rootStore: rootContext,\n    }),\n    [position, refs, elements, nodeId, rootContext, open, floatingId],\n  );\n\n  useIsoLayoutEffect(() => {\n    rootContext.context.dataRef.current.floatingContext = context;\n\n    const node = tree?.nodesRef.current.find((n) => n.id === nodeId);\n    if (node) {\n      node.context = context;\n    }\n  });\n\n  return React.useMemo(\n    () => ({\n      ...position,\n      context,\n      refs,\n      elements,\n      rootStore: rootContext as unknown as FloatingRootStore,\n    }),\n    [position, refs, elements, context, rootContext],\n  ) as UseFloatingReturn;\n}\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/hooks/useFloatingRootContext.ts",
    "content": "'use client';\nimport { isElement } from '@floating-ui/utils/dom';\nimport { useId } from '@base-ui/utils/useId';\nimport { useRefWithInit } from '@base-ui/utils/useRefWithInit';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport type { ReferenceType } from '../types';\nimport type { BaseUIChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { useFloatingParentNodeId } from '../components/FloatingTree';\nimport { FloatingRootStore, type FloatingRootState } from '../components/FloatingRootStore';\nimport { PopupTriggerMap } from '../../utils/popups';\n\nexport interface UseFloatingRootContextOptions {\n  open?: boolean | undefined;\n  onOpenChange?(open: boolean, eventDetails: BaseUIChangeEventDetails<string>): void;\n  elements?:\n    | {\n        reference?: ReferenceType | null | undefined;\n        floating?: HTMLElement | null | undefined;\n      }\n    | undefined;\n}\n\nexport function useFloatingRootContext(options: UseFloatingRootContextOptions): FloatingRootStore {\n  const { open = false, onOpenChange, elements = {} } = options;\n\n  const floatingId = useId();\n  const nested = useFloatingParentNodeId() != null;\n\n  if (process.env.NODE_ENV !== 'production') {\n    const optionDomReference = elements.reference;\n    if (optionDomReference && !isElement(optionDomReference)) {\n      console.error(\n        'Cannot pass a virtual element to the `elements.reference` option,',\n        'as it must be a real DOM element. Use `context.setPositionReference()`',\n        'instead.',\n      );\n    }\n  }\n\n  const store = useRefWithInit(\n    () =>\n      new FloatingRootStore({\n        open,\n        onOpenChange,\n        referenceElement: elements.reference ?? null,\n        floatingElement: elements.floating ?? null,\n        triggerElements: new PopupTriggerMap(),\n        floatingId,\n        nested,\n        noEmit: false,\n      }),\n  ).current;\n\n  useIsoLayoutEffect(() => {\n    const valuesToSync: Writeable<Partial<FloatingRootState>> = {\n      open,\n      floatingId,\n    };\n\n    // Only sync elements that are defined to avoid overwriting existing ones\n    if (elements.reference !== undefined) {\n      valuesToSync.referenceElement = elements.reference;\n      valuesToSync.domReferenceElement = isElement(elements.reference) ? elements.reference : null;\n    }\n\n    if (elements.floating !== undefined) {\n      valuesToSync.floatingElement = elements.floating;\n    }\n\n    store.update(valuesToSync);\n  }, [open, floatingId, elements.reference, elements.floating, store]);\n\n  store.context.onOpenChange = onOpenChange;\n  store.context.nested = nested;\n  store.context.noEmit = false;\n\n  return store;\n}\n\ntype Writeable<T> = { -readonly [P in keyof T]: T[P] };\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/hooks/useFocus.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { getWindow, isElement, isHTMLElement } from '@floating-ui/utils/dom';\nimport { isMac, isSafari } from '@base-ui/utils/detectBrowser';\nimport { useTimeout } from '@base-ui/utils/useTimeout';\nimport { ownerDocument } from '@base-ui/utils/owner';\nimport {\n  activeElement,\n  contains,\n  getTarget,\n  isTargetInsideEnabledTrigger,\n  isTypeableElement,\n  matchesFocusVisible,\n} from '../utils';\n\nimport type { ElementProps, FloatingContext, FloatingRootContext } from '../types';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport { createAttribute } from '../utils/createAttribute';\nimport { FloatingUIOpenChangeDetails } from '../../utils/types';\n\nconst isMacSafari = isMac && isSafari;\n\nexport interface UseFocusProps {\n  /**\n   * Whether the Hook is enabled, including all internal Effects and event\n   * handlers.\n   * @default true\n   */\n  enabled?: boolean | undefined;\n  /**\n   * Waits for the specified time before opening.\n   * @default undefined\n   */\n  delay?: number | (() => number | undefined) | undefined;\n}\n\n/**\n * Opens the floating element while the reference element has focus, like CSS\n * `:focus`.\n * @see https://floating-ui.com/docs/useFocus\n */\nexport function useFocus(\n  context: FloatingRootContext | FloatingContext,\n  props: UseFocusProps = {},\n): ElementProps {\n  const store = 'rootStore' in context ? context.rootStore : context;\n\n  const { events, dataRef } = store.context;\n  const { enabled = true, delay } = props;\n\n  const blockFocusRef = React.useRef(false);\n  // Track which reference should be blocked from re-opening after Escape/press dismissal.\n  const blockedReferenceRef = React.useRef<Element | null>(null);\n  const timeout = useTimeout();\n  const keyboardModalityRef = React.useRef(true);\n\n  React.useEffect(() => {\n    const domReference = store.select('domReferenceElement');\n    if (!enabled) {\n      return undefined;\n    }\n\n    const win = getWindow(domReference);\n\n    // If the reference was focused and the user left the tab/window, and the\n    // floating element was not open, the focus should be blocked when they\n    // return to the tab/window.\n    function onBlur() {\n      const currentDomReference = store.select('domReferenceElement');\n      if (\n        !store.select('open') &&\n        isHTMLElement(currentDomReference) &&\n        currentDomReference === activeElement(ownerDocument(currentDomReference))\n      ) {\n        blockFocusRef.current = true;\n      }\n    }\n\n    function onKeyDown() {\n      keyboardModalityRef.current = true;\n    }\n\n    function onPointerDown() {\n      keyboardModalityRef.current = false;\n    }\n\n    win.addEventListener('blur', onBlur);\n\n    if (isMacSafari) {\n      win.addEventListener('keydown', onKeyDown, true);\n      win.addEventListener('pointerdown', onPointerDown, true);\n    }\n\n    return () => {\n      win.removeEventListener('blur', onBlur);\n\n      if (isMacSafari) {\n        win.removeEventListener('keydown', onKeyDown, true);\n        win.removeEventListener('pointerdown', onPointerDown, true);\n      }\n    };\n  }, [store, enabled]);\n\n  React.useEffect(() => {\n    if (!enabled) {\n      return undefined;\n    }\n\n    function onOpenChangeLocal(details: FloatingUIOpenChangeDetails) {\n      if (details.reason === REASONS.triggerPress || details.reason === REASONS.escapeKey) {\n        const referenceElement = store.select('domReferenceElement');\n        if (isElement(referenceElement)) {\n          blockedReferenceRef.current = referenceElement;\n          blockFocusRef.current = true;\n        }\n      }\n    }\n\n    events.on('openchange', onOpenChangeLocal);\n    return () => {\n      events.off('openchange', onOpenChangeLocal);\n    };\n  }, [events, enabled, store]);\n\n  const reference: ElementProps['reference'] = React.useMemo(\n    () => ({\n      onMouseLeave() {\n        blockFocusRef.current = false;\n        blockedReferenceRef.current = null;\n      },\n      onFocus(event) {\n        const focusTarget = event.currentTarget as Element;\n        if (blockFocusRef.current) {\n          if (blockedReferenceRef.current === focusTarget) {\n            return;\n          }\n\n          blockFocusRef.current = false;\n          blockedReferenceRef.current = null;\n        }\n\n        const target = getTarget(event.nativeEvent);\n\n        if (isElement(target)) {\n          // Safari fails to match `:focus-visible` if focus was initially\n          // outside the document.\n          if (isMacSafari && !event.relatedTarget) {\n            if (!keyboardModalityRef.current && !isTypeableElement(target)) {\n              return;\n            }\n          } else if (!matchesFocusVisible(target)) {\n            return;\n          }\n        }\n\n        const movedFromOtherEnabledTrigger = isTargetInsideEnabledTrigger(\n          event.relatedTarget,\n          store.context.triggerElements,\n        );\n\n        const { nativeEvent, currentTarget } = event;\n        const delayValue = typeof delay === 'function' ? delay() : delay;\n\n        if (\n          (store.select('open') && movedFromOtherEnabledTrigger) ||\n          delayValue === 0 ||\n          delayValue === undefined\n        ) {\n          store.setOpen(\n            true,\n            createChangeEventDetails(\n              REASONS.triggerFocus,\n              nativeEvent,\n              currentTarget as HTMLElement,\n            ),\n          );\n          return;\n        }\n\n        timeout.start(delayValue, () => {\n          if (blockFocusRef.current) {\n            return;\n          }\n\n          store.setOpen(\n            true,\n            createChangeEventDetails(\n              REASONS.triggerFocus,\n              nativeEvent,\n              currentTarget as HTMLElement,\n            ),\n          );\n        });\n      },\n      onBlur(event) {\n        blockFocusRef.current = false;\n        blockedReferenceRef.current = null;\n        const relatedTarget = event.relatedTarget;\n        const nativeEvent = event.nativeEvent;\n\n        // Hit the non-modal focus management portal guard. Focus will be\n        // moved into the floating element immediately after.\n        const movedToFocusGuard =\n          isElement(relatedTarget) &&\n          relatedTarget.hasAttribute(createAttribute('focus-guard')) &&\n          relatedTarget.getAttribute('data-type') === 'outside';\n\n        // Wait for the window blur listener to fire.\n        timeout.start(0, () => {\n          const domReference = store.select('domReferenceElement');\n          const activeEl = activeElement(domReference ? domReference.ownerDocument : document);\n\n          // Focus left the page, keep it open.\n          if (!relatedTarget && activeEl === domReference) {\n            return;\n          }\n\n          // When focusing the reference element (e.g. regular click), then\n          // clicking into the floating element, prevent it from hiding.\n          // Note: it must be focusable, e.g. `tabindex=\"-1\"`.\n          // We can not rely on relatedTarget to point to the correct element\n          // as it will only point to the shadow host of the newly focused element\n          // and not the element that actually has received focus if it is located\n          // inside a shadow root.\n          if (\n            contains(dataRef.current.floatingContext?.refs.floating.current, activeEl) ||\n            contains(domReference, activeEl) ||\n            movedToFocusGuard\n          ) {\n            return;\n          }\n\n          // If the next focused element is one of the triggers, do not close\n          // the floating element. The focus handler of that trigger will\n          // handle the open state.\n          const nextFocusedElement = relatedTarget ?? activeEl;\n          if (isTargetInsideEnabledTrigger(nextFocusedElement, store.context.triggerElements)) {\n            return;\n          }\n\n          store.setOpen(false, createChangeEventDetails(REASONS.triggerFocus, nativeEvent));\n        });\n      },\n    }),\n    [dataRef, store, timeout, delay],\n  );\n\n  return React.useMemo(\n    () => (enabled ? { reference, trigger: reference } : {}),\n    [enabled, reference],\n  );\n}\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/hooks/useHover.test.tsx",
    "content": "import { vi, test, expect } from 'vitest';\n/* eslint-disable @typescript-eslint/no-shadow */\nimport { act, fireEvent, flushMicrotasks, render, screen, waitFor } from '@mui/internal-test-utils';\nimport * as React from 'react';\nimport userEvent from '@testing-library/user-event';\nimport { isJSDOM } from '@base-ui/utils/detectBrowser';\nimport { useFloating, useHover, useInteractions } from '../index';\nimport type { UseHoverProps } from './useHover';\nimport { Popover } from '../../../test/floating-ui-tests/Popover';\nimport { REASONS } from '../../utils/reasons';\n\nfunction App({ showReference = true, ...props }: UseHoverProps & { showReference?: boolean }) {\n  const [open, setOpen] = React.useState(false);\n  const { refs, context } = useFloating({\n    open,\n    onOpenChange: setOpen,\n  });\n  const { getReferenceProps, getFloatingProps } = useInteractions([useHover(context, props)]);\n\n  return (\n    <React.Fragment>\n      {showReference && <button {...getReferenceProps({ ref: refs.setReference })} />}\n      {open && <div role=\"tooltip\" {...getFloatingProps({ ref: refs.setFloating })} />}\n    </React.Fragment>\n  );\n}\n\ndescribe.skipIf(!isJSDOM)('useHover', () => {\n  beforeEach(() => {\n    vi.useFakeTimers();\n  });\n\n  test('opens on mouseenter', async () => {\n    render(<App />);\n\n    fireEvent.mouseEnter(screen.getByRole('button'));\n    expect(screen.getByRole('tooltip')).toBeInTheDocument();\n    await flushMicrotasks();\n  });\n\n  test('closes on mouseleave', () => {\n    render(<App />);\n\n    fireEvent.mouseEnter(screen.getByRole('button'));\n    fireEvent.mouseLeave(screen.getByRole('button'));\n    expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();\n  });\n\n  describe('prop: delay', () => {\n    test('symmetric number', async () => {\n      render(<App delay={1000} />);\n\n      fireEvent.mouseEnter(screen.getByRole('button'));\n\n      await act(async () => {\n        vi.advanceTimersByTime(999);\n      });\n\n      expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();\n\n      await act(async () => {\n        vi.advanceTimersByTime(1);\n      });\n\n      expect(screen.getByRole('tooltip')).toBeInTheDocument();\n    });\n\n    test('open', async () => {\n      render(<App delay={{ open: 500 }} />);\n\n      fireEvent.mouseEnter(screen.getByRole('button'));\n\n      await act(async () => {\n        vi.advanceTimersByTime(499);\n      });\n\n      expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();\n\n      await act(async () => {\n        vi.advanceTimersByTime(1);\n      });\n\n      expect(screen.getByRole('tooltip')).toBeInTheDocument();\n    });\n\n    test('close', async () => {\n      render(<App delay={{ close: 500 }} />);\n\n      fireEvent.mouseEnter(screen.getByRole('button'));\n      fireEvent.mouseLeave(screen.getByRole('button'));\n\n      await act(async () => {\n        vi.advanceTimersByTime(499);\n      });\n\n      expect(screen.getByRole('tooltip')).toBeInTheDocument();\n\n      await act(async () => {\n        vi.advanceTimersByTime(1);\n      });\n\n      expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();\n    });\n\n    test('open with close 0', async () => {\n      render(<App delay={{ open: 500 }} />);\n\n      fireEvent.mouseEnter(screen.getByRole('button'));\n\n      await act(async () => {\n        vi.advanceTimersByTime(499);\n      });\n\n      fireEvent.mouseLeave(screen.getByRole('button'));\n\n      await act(async () => {\n        vi.advanceTimersByTime(1);\n      });\n\n      expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();\n    });\n\n    test('restMs + nullish open delay should respect restMs', async () => {\n      render(<App restMs={100} delay={{ close: 100 }} />);\n\n      fireEvent.mouseEnter(screen.getByRole('button'));\n\n      await act(async () => {\n        vi.advanceTimersByTime(99);\n      });\n\n      expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();\n    });\n  });\n\n  test('restMs', async () => {\n    render(<App restMs={100} />);\n\n    const button = screen.getByRole('button');\n\n    const originalDispatchEvent = button.dispatchEvent;\n    const spy = vi.spyOn(button, 'dispatchEvent').mockImplementation((event) => {\n      Object.defineProperty(event, 'movementX', { value: 10 });\n      Object.defineProperty(event, 'movementY', { value: 10 });\n      return originalDispatchEvent.call(button, event);\n    });\n\n    fireEvent.mouseMove(button);\n\n    await act(async () => {\n      vi.advanceTimersByTime(99);\n    });\n\n    fireEvent.mouseMove(button);\n\n    await act(async () => {\n      vi.advanceTimersByTime(1);\n    });\n\n    expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();\n\n    fireEvent.mouseMove(button);\n\n    await act(async () => {\n      vi.advanceTimersByTime(100);\n    });\n\n    expect(screen.getByRole('tooltip')).toBeInTheDocument();\n\n    spy.mockRestore();\n  });\n\n  test.skip('restMs is always 0 for touch input', async () => {\n    render(<App restMs={100} />);\n\n    fireEvent.pointerDown(screen.getByRole('button'), { pointerType: 'touch' });\n    fireEvent.mouseMove(screen.getByRole('button'));\n\n    await flushMicrotasks();\n\n    await waitFor(() => {\n      expect(screen.getByRole('tooltip')).toBeInTheDocument();\n    });\n  });\n\n  test('restMs does not reset timer for minor mouse movement', async () => {\n    render(<App restMs={100} />);\n\n    const button = screen.getByRole('button');\n\n    const originalDispatchEvent = button.dispatchEvent;\n    const spy = vi.spyOn(button, 'dispatchEvent').mockImplementation((event) => {\n      Object.defineProperty(event, 'movementX', { value: 1 });\n      Object.defineProperty(event, 'movementY', { value: 0 });\n      return originalDispatchEvent.call(button, event);\n    });\n\n    fireEvent.mouseMove(button);\n\n    await act(async () => {\n      vi.advanceTimersByTime(99);\n    });\n\n    fireEvent.mouseMove(button);\n\n    await act(async () => {\n      vi.advanceTimersByTime(1);\n    });\n\n    expect(screen.getByRole('tooltip')).toBeInTheDocument();\n\n    spy.mockRestore();\n  });\n\n  test('mouseleave on the floating element closes it (mouse)', async () => {\n    render(<App />);\n\n    fireEvent.mouseEnter(screen.getByRole('button'));\n    await flushMicrotasks();\n\n    fireEvent(\n      screen.getByRole('button'),\n      new MouseEvent('mouseleave', {\n        relatedTarget: screen.getByRole('tooltip'),\n      }),\n    );\n\n    expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();\n  });\n\n  test('does not show after delay if domReference changes', async () => {\n    const { rerender } = render(<App delay={1000} />);\n\n    fireEvent.mouseEnter(screen.getByRole('button'));\n\n    await act(async () => {\n      vi.advanceTimersByTime(1);\n    });\n\n    rerender(<App showReference={false} />);\n\n    await act(async () => {\n      vi.advanceTimersByTime(999);\n    });\n\n    expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();\n  });\n\n  test('reason string', async () => {\n    function App() {\n      const [isOpen, setIsOpen] = React.useState(false);\n      const { refs, context } = useFloating({\n        open: isOpen,\n        onOpenChange(nextOpen, data) {\n          setIsOpen(nextOpen);\n          expect(data?.reason).toBe(REASONS.triggerHover);\n        },\n      });\n\n      const hover = useHover(context);\n      const { getReferenceProps, getFloatingProps } = useInteractions([hover]);\n\n      return (\n        <React.Fragment>\n          <button ref={refs.setReference} {...getReferenceProps()} />\n          {isOpen && <div role=\"tooltip\" ref={refs.setFloating} {...getFloatingProps()} />}\n        </React.Fragment>\n      );\n    }\n\n    render(<App />);\n    const button = screen.getByRole('button');\n    fireEvent.mouseEnter(button);\n    await flushMicrotasks();\n    fireEvent.mouseLeave(button);\n  });\n\n  test('cleans up blockPointerEvents if trigger changes', async () => {\n    vi.useRealTimers();\n    const user = userEvent.setup();\n    render(\n      <Popover\n        hover={false}\n        modal={false}\n        bubbles\n        render={({ labelId, descriptionId, close }) => (\n          <React.Fragment>\n            <h2 id={labelId}>Parent title</h2>\n            <p id={descriptionId}>Description</p>\n            <Popover\n              hover\n              modal={false}\n              bubbles\n              render={({ labelId, descriptionId, close }) => (\n                <React.Fragment>\n                  <h2 id={labelId}>Child title</h2>\n                  <p id={descriptionId}>Description</p>\n                  <button onClick={close}>Close</button>\n                </React.Fragment>\n              )}\n            >\n              <button type=\"button\">Open child</button>\n            </Popover>\n            <button onClick={close}>Close</button>\n          </React.Fragment>\n        )}\n      >\n        <button type=\"button\">Open parent</button>\n      </Popover>,\n    );\n\n    await user.click(screen.getByText('Open parent'));\n    expect(screen.getByText('Parent title')).toBeInTheDocument();\n    await user.click(screen.getByText('Open child'));\n    expect(screen.getByText('Child title')).toBeInTheDocument();\n    await user.click(screen.getByText('Child title'));\n    // clean up blockPointerEvents\n    // userEvent.unhover does not work because of the pointer-events\n    fireEvent.mouseLeave(screen.getByRole('dialog', { name: 'Child title' }));\n    expect(screen.getByText('Child title')).toBeInTheDocument();\n    await user.click(screen.getByText('Parent title'));\n    // screen.debug();\n    expect(screen.getByText('Parent title')).toBeInTheDocument();\n  });\n});\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/hooks/useHover.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { isElement } from '@floating-ui/utils/dom';\nimport { useTimeout } from '@base-ui/utils/useTimeout';\nimport { useValueAsRef } from '@base-ui/utils/useValueAsRef';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { ownerDocument } from '@base-ui/utils/owner';\nimport { contains, getTarget, isInteractiveElement } from '../utils';\n\nimport { useFloatingParentNodeId, useFloatingTree } from '../components/FloatingTree';\nimport type { Delay, ElementProps, FloatingContext, FloatingRootContext } from '../types';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport { FloatingUIOpenChangeDetails } from '../../utils/types';\nimport type { HandleClose } from './useHoverShared';\nimport { getDelay, getRestMs } from './useHoverShared';\n\nexport type { HandleCloseContext, HandleClose } from './useHoverShared';\n\nexport interface UseHoverProps {\n  /**\n   * Accepts an event handler that runs on `mousemove` to control when the\n   * floating element closes once the cursor leaves the reference element.\n   * @default null\n   */\n  handleClose?: HandleClose | null | undefined;\n  /**\n   * Waits until the user’s cursor is at “rest” over the reference element\n   * before changing the `open` state.\n   * @default 0\n   */\n  restMs?: number | (() => number) | undefined;\n  /**\n   * Waits for the specified time when the event listener runs before changing\n   * the `open` state.\n   * @default 0\n   */\n  delay?: Delay | (() => Delay) | undefined;\n  /**\n   * Whether moving the cursor over the floating element will open it, without a\n   * regular hover event required.\n   * @default true\n   */\n  move?: boolean | undefined;\n}\n\n/**\n * Opens the floating element while hovering over the reference element, like\n * CSS `:hover`.\n * @see https://floating-ui.com/docs/useHover\n */\nexport function useHover(\n  context: FloatingRootContext | FloatingContext,\n  props: UseHoverProps = {},\n): ElementProps {\n  const store = 'rootStore' in context ? context.rootStore : context;\n  const open = store.useState('open');\n  const floatingElement = store.useState('floatingElement');\n  const domReferenceElement = store.useState('domReferenceElement');\n  const { dataRef, events } = store.context;\n  const { delay = 0, handleClose = null, restMs = 0, move = true } = props;\n\n  const tree = useFloatingTree();\n  const parentId = useFloatingParentNodeId();\n  const handleCloseRef = useValueAsRef(handleClose);\n  const delayRef = useValueAsRef(delay);\n  const restMsRef = useValueAsRef(restMs);\n\n  const pointerTypeRef = React.useRef<string>(undefined);\n  const interactedInsideRef = React.useRef(false);\n  const timeout = useTimeout();\n  const handlerRef = React.useRef<(event: MouseEvent) => void>(undefined);\n  const restTimeout = useTimeout();\n  const blockMouseMoveRef = React.useRef(true);\n  const performedPointerEventsMutationRef = React.useRef(false);\n  const unbindMouseMoveRef = React.useRef(() => {});\n  const restTimeoutPendingRef = React.useRef(false);\n\n  const isHoverOpen = useStableCallback(() => {\n    const type = dataRef.current.openEvent?.type;\n    return type?.includes('mouse') && type !== 'mousedown';\n  });\n\n  const isClickLikeOpenEvent = useStableCallback(() => {\n    if (interactedInsideRef.current) {\n      return true;\n    }\n\n    return dataRef.current.openEvent\n      ? ['click', 'mousedown'].includes(dataRef.current.openEvent.type)\n      : false;\n  });\n\n  // When closing before opening, clear the delay timeouts to cancel it\n  // from showing.\n  React.useEffect(() => {\n    function onOpenChangeLocal(details: FloatingUIOpenChangeDetails) {\n      if (!details.open) {\n        timeout.clear();\n        restTimeout.clear();\n        blockMouseMoveRef.current = true;\n        restTimeoutPendingRef.current = false;\n      }\n    }\n\n    events.on('openchange', onOpenChangeLocal);\n    return () => {\n      events.off('openchange', onOpenChangeLocal);\n    };\n  }, [events, timeout, restTimeout]);\n\n  React.useEffect(() => {\n    if (!handleCloseRef.current) {\n      return undefined;\n    }\n    if (!open) {\n      return undefined;\n    }\n\n    function onLeave(event: MouseEvent) {\n      if (isClickLikeOpenEvent()) {\n        return;\n      }\n\n      if (isHoverOpen()) {\n        store.setOpen(\n          false,\n          createChangeEventDetails(\n            REASONS.triggerHover,\n            event,\n            (event.currentTarget as HTMLElement) ?? undefined,\n          ),\n        );\n      }\n    }\n\n    const html = ownerDocument(floatingElement).documentElement;\n    html.addEventListener('mouseleave', onLeave);\n    return () => {\n      html.removeEventListener('mouseleave', onLeave);\n    };\n  }, [floatingElement, open, store, handleCloseRef, isHoverOpen, isClickLikeOpenEvent]);\n\n  const closeWithDelay = React.useCallback(\n    (event: MouseEvent, runElseBranch = true) => {\n      const closeDelay = getDelay(delayRef.current, 'close', pointerTypeRef.current);\n      if (closeDelay && !handlerRef.current) {\n        timeout.start(closeDelay, () =>\n          store.setOpen(false, createChangeEventDetails(REASONS.triggerHover, event)),\n        );\n      } else if (runElseBranch) {\n        timeout.clear();\n        store.setOpen(false, createChangeEventDetails(REASONS.triggerHover, event));\n      }\n    },\n    [delayRef, store, timeout],\n  );\n\n  const cleanupMouseMoveHandler = useStableCallback(() => {\n    unbindMouseMoveRef.current();\n    handlerRef.current = undefined;\n  });\n\n  const clearPointerEvents = useStableCallback(() => {\n    if (performedPointerEventsMutationRef.current) {\n      const body = ownerDocument(floatingElement).body;\n      body.style.pointerEvents = '';\n      performedPointerEventsMutationRef.current = false;\n    }\n  });\n\n  const handleInteractInside = useStableCallback((event: PointerEvent) => {\n    const target = getTarget(event) as Element | null;\n    if (!isInteractiveElement(target)) {\n      interactedInsideRef.current = false;\n      return;\n    }\n\n    interactedInsideRef.current = true;\n  });\n\n  // Registering the mouse events on the reference directly to bypass React's\n  // delegation system. If the cursor was on a disabled element and then entered\n  // the reference (no gap), `mouseenter` doesn't fire in the delegation system.\n  React.useEffect(() => {\n    function onReferenceMouseEnter(event: MouseEvent) {\n      timeout.clear();\n      blockMouseMoveRef.current = false;\n\n      if (getRestMs(restMsRef.current) > 0 && !getDelay(delayRef.current, 'open')) {\n        return;\n      }\n\n      const openDelay = getDelay(delayRef.current, 'open', pointerTypeRef.current);\n      const trigger = (event.currentTarget as HTMLElement) ?? undefined;\n\n      const domReference = store.select('domReferenceElement');\n\n      const isOverInactiveTrigger = domReference && trigger && !contains(domReference, trigger);\n\n      if (openDelay) {\n        timeout.start(openDelay, () => {\n          if (!store.select('open')) {\n            store.setOpen(true, createChangeEventDetails(REASONS.triggerHover, event, trigger));\n          }\n        });\n      } else if (!open || isOverInactiveTrigger) {\n        store.setOpen(true, createChangeEventDetails(REASONS.triggerHover, event, trigger));\n      }\n    }\n\n    function onReferenceMouseLeave(event: MouseEvent) {\n      if (isClickLikeOpenEvent()) {\n        clearPointerEvents();\n        return;\n      }\n\n      unbindMouseMoveRef.current();\n\n      const doc = ownerDocument(floatingElement);\n      restTimeout.clear();\n      restTimeoutPendingRef.current = false;\n\n      const triggers = store.context.triggerElements;\n\n      if (event.relatedTarget && triggers.hasElement(event.relatedTarget as Element)) {\n        // If the mouse is leaving the reference element to another trigger, don't explicitly close the popup\n        // as it will be moved.\n        return;\n      }\n\n      if (handleCloseRef.current && dataRef.current.floatingContext) {\n        // Prevent clearing `onScrollMouseLeave` timeout.\n        if (!open) {\n          timeout.clear();\n        }\n\n        handlerRef.current = handleCloseRef.current({\n          ...dataRef.current.floatingContext,\n          tree,\n          x: event.clientX,\n          y: event.clientY,\n          onClose() {\n            clearPointerEvents();\n            cleanupMouseMoveHandler();\n            if (!isClickLikeOpenEvent()) {\n              closeWithDelay(event, true);\n            }\n          },\n        });\n\n        const handler = handlerRef.current;\n\n        doc.addEventListener('mousemove', handler);\n        unbindMouseMoveRef.current = () => {\n          doc.removeEventListener('mousemove', handler);\n        };\n\n        return;\n      }\n\n      // Allow interactivity without `safePolygon` on touch devices. With a\n      // pointer, a short close delay is an alternative, so it should work\n      // consistently.\n      const shouldClose =\n        pointerTypeRef.current === 'touch'\n          ? !contains(floatingElement, event.relatedTarget as Element | null)\n          : true;\n      if (shouldClose) {\n        closeWithDelay(event);\n      }\n    }\n\n    // Ensure the floating element closes after scrolling even if the pointer\n    // did not move.\n    // https://github.com/floating-ui/floating-ui/discussions/1692\n    function onScrollMouseLeave(event: MouseEvent) {\n      if (isClickLikeOpenEvent() || !dataRef.current.floatingContext || !store.select('open')) {\n        return;\n      }\n\n      const triggers = store.context.triggerElements;\n\n      if (event.relatedTarget && triggers.hasElement(event.relatedTarget as Element)) {\n        // If the mouse is leaving the reference element to another trigger, don't explicitly close the popup\n        // as it will be moved.\n        return;\n      }\n\n      handleCloseRef.current?.({\n        ...dataRef.current.floatingContext,\n        tree,\n        x: event.clientX,\n        y: event.clientY,\n        onClose() {\n          clearPointerEvents();\n          cleanupMouseMoveHandler();\n          if (!isClickLikeOpenEvent()) {\n            closeWithDelay(event);\n          }\n        },\n      })(event);\n    }\n\n    function onFloatingMouseEnter() {\n      timeout.clear();\n      clearPointerEvents();\n    }\n\n    function onFloatingMouseLeave(event: MouseEvent) {\n      if (!isClickLikeOpenEvent()) {\n        closeWithDelay(event, false);\n      }\n    }\n\n    const trigger = domReferenceElement as HTMLElement | null;\n\n    if (isElement(trigger)) {\n      const floating = floatingElement;\n\n      if (open) {\n        trigger.addEventListener('mouseleave', onScrollMouseLeave);\n      }\n\n      if (move) {\n        trigger.addEventListener('mousemove', onReferenceMouseEnter, {\n          once: true,\n        });\n      }\n\n      trigger.addEventListener('mouseenter', onReferenceMouseEnter);\n      trigger.addEventListener('mouseleave', onReferenceMouseLeave);\n\n      if (floating) {\n        floating.addEventListener('mouseleave', onScrollMouseLeave);\n        floating.addEventListener('mouseenter', onFloatingMouseEnter);\n        floating.addEventListener('mouseleave', onFloatingMouseLeave);\n        floating.addEventListener('pointerdown', handleInteractInside, true);\n      }\n\n      return () => {\n        if (open) {\n          trigger.removeEventListener('mouseleave', onScrollMouseLeave);\n        }\n\n        if (move) {\n          trigger.removeEventListener('mousemove', onReferenceMouseEnter);\n        }\n\n        trigger.removeEventListener('mouseenter', onReferenceMouseEnter);\n        trigger.removeEventListener('mouseleave', onReferenceMouseLeave);\n\n        if (floating) {\n          floating.removeEventListener('mouseleave', onScrollMouseLeave);\n          floating.removeEventListener('mouseenter', onFloatingMouseEnter);\n          floating.removeEventListener('mouseleave', onFloatingMouseLeave);\n          floating.removeEventListener('pointerdown', handleInteractInside, true);\n        }\n      };\n    }\n\n    return undefined;\n  }, [\n    move,\n    domReferenceElement,\n    floatingElement,\n    store,\n    closeWithDelay,\n    cleanupMouseMoveHandler,\n    clearPointerEvents,\n    open,\n    tree,\n    delayRef,\n    handleCloseRef,\n    dataRef,\n    isClickLikeOpenEvent,\n    restMsRef,\n    timeout,\n    restTimeout,\n    handleInteractInside,\n  ]);\n\n  // Block pointer-events of every element other than the reference and floating\n  // while the floating element is open and has a `handleClose` handler. Also\n  // handles nested floating elements.\n  // https://github.com/floating-ui/floating-ui/issues/1722\n  useIsoLayoutEffect(() => {\n    // eslint-disable-next-line no-underscore-dangle\n    if (open && handleCloseRef.current?.__options?.blockPointerEvents && isHoverOpen()) {\n      performedPointerEventsMutationRef.current = true;\n      const floatingEl = floatingElement;\n\n      if (isElement(domReferenceElement) && floatingEl) {\n        const body = ownerDocument(floatingElement).body;\n\n        const ref = domReferenceElement as HTMLElement | SVGSVGElement;\n\n        const parentFloating = tree?.nodesRef.current.find((node) => node.id === parentId)?.context\n          ?.elements.floating;\n\n        if (parentFloating) {\n          parentFloating.style.pointerEvents = '';\n        }\n\n        body.style.pointerEvents = 'none';\n        ref.style.pointerEvents = 'auto';\n        floatingEl.style.pointerEvents = 'auto';\n\n        return () => {\n          body.style.pointerEvents = '';\n          ref.style.pointerEvents = '';\n          floatingEl.style.pointerEvents = '';\n        };\n      }\n    }\n\n    return undefined;\n  }, [open, parentId, tree, handleCloseRef, isHoverOpen, domReferenceElement, floatingElement]);\n\n  useIsoLayoutEffect(() => {\n    if (!open) {\n      pointerTypeRef.current = undefined;\n      restTimeoutPendingRef.current = false;\n      interactedInsideRef.current = false;\n      cleanupMouseMoveHandler();\n      clearPointerEvents();\n    }\n  }, [open, cleanupMouseMoveHandler, clearPointerEvents]);\n\n  React.useEffect(() => {\n    return () => {\n      cleanupMouseMoveHandler();\n      timeout.clear();\n      restTimeout.clear();\n      interactedInsideRef.current = false;\n    };\n  }, [domReferenceElement, cleanupMouseMoveHandler, timeout, restTimeout]);\n\n  React.useEffect(() => {\n    return clearPointerEvents;\n  }, [clearPointerEvents]);\n\n  const reference: ElementProps['reference'] = React.useMemo(() => {\n    function setPointerRef(event: React.PointerEvent) {\n      pointerTypeRef.current = event.pointerType;\n    }\n\n    return {\n      onPointerDown: setPointerRef,\n      onPointerEnter: setPointerRef,\n      onMouseMove(event) {\n        const { nativeEvent } = event;\n        const trigger = event.currentTarget as HTMLElement;\n\n        // `true` when there are multiple triggers per floating element and user hovers over the one that\n        // wasn't used to open the floating element.\n        const isOverInactiveTrigger =\n          store.select('domReferenceElement') &&\n          !contains(store.select('domReferenceElement'), event.target as Element);\n\n        function handleMouseMove() {\n          if (!blockMouseMoveRef.current && (!store.select('open') || isOverInactiveTrigger)) {\n            store.setOpen(\n              true,\n              createChangeEventDetails(REASONS.triggerHover, nativeEvent, trigger),\n            );\n          }\n        }\n\n        if (\n          (store.select('open') && !isOverInactiveTrigger) ||\n          getRestMs(restMsRef.current) === 0\n        ) {\n          return;\n        }\n\n        // Ignore insignificant movements to account for tremors.\n        if (\n          !isOverInactiveTrigger &&\n          restTimeoutPendingRef.current &&\n          event.movementX ** 2 + event.movementY ** 2 < 2\n        ) {\n          return;\n        }\n\n        restTimeout.clear();\n\n        if (pointerTypeRef.current === 'touch') {\n          handleMouseMove();\n        } else if (isOverInactiveTrigger) {\n          handleMouseMove();\n        } else {\n          restTimeoutPendingRef.current = true;\n          restTimeout.start(getRestMs(restMsRef.current), handleMouseMove);\n        }\n      },\n    };\n  }, [store, restMsRef, restTimeout]);\n\n  return React.useMemo(() => ({ reference }), [reference]);\n}\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/hooks/useHoverFloatingInteraction.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { isElement } from '@floating-ui/utils/dom';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useTimeout } from '@base-ui/utils/useTimeout';\nimport { ownerDocument } from '@base-ui/utils/owner';\n\nimport type { FloatingContext, FloatingRootContext } from '../types';\nimport { getNodeChildren, getTarget, isTargetInsideEnabledTrigger } from '../utils';\n\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport { useFloatingParentNodeId, useFloatingTree } from '../components/FloatingTree';\nimport {\n  applySafePolygonPointerEventsMutation,\n  clearSafePolygonPointerEventsMutation,\n  isInteractiveElement,\n  useHoverInteractionSharedState,\n} from './useHoverInteractionSharedState';\nimport { getDelay, isClickLikeOpenEvent as isClickLikeOpenEventShared } from './useHoverShared';\n\nexport type UseHoverFloatingInteractionProps = {\n  /**\n   * Whether the Hook is enabled, including all internal Effects and event\n   * handlers.\n   * @default true\n   */\n  enabled?: boolean | undefined;\n  /**\n   * Waits for the specified time when the event listener runs before changing\n   * the `open` state.\n   * @default 0\n   */\n  closeDelay?: number | (() => number) | undefined;\n};\n\n/**\n * Provides hover interactions that should be attached to the floating element.\n */\nexport function useHoverFloatingInteraction(\n  context: FloatingRootContext | FloatingContext,\n  parameters: UseHoverFloatingInteractionProps = {},\n): void {\n  const store = 'rootStore' in context ? context.rootStore : context;\n  const open = store.useState('open');\n  const floatingElement = store.useState('floatingElement');\n  const domReferenceElement = store.useState('domReferenceElement');\n  const { dataRef } = store.context;\n\n  const { enabled = true, closeDelay: closeDelayProp = 0 } = parameters;\n\n  const instance = useHoverInteractionSharedState(store);\n\n  const tree = useFloatingTree();\n  const parentId = useFloatingParentNodeId();\n\n  const isClickLikeOpenEvent = useStableCallback(() => {\n    return isClickLikeOpenEventShared(dataRef.current.openEvent?.type, instance.interactedInside);\n  });\n\n  const isHoverOpen = useStableCallback(() => {\n    const type = dataRef.current.openEvent?.type;\n    return type?.includes('mouse') && type !== 'mousedown';\n  });\n\n  const isRelatedTargetInsideEnabledTrigger = useStableCallback((target: EventTarget | null) => {\n    return isTargetInsideEnabledTrigger(target, store.context.triggerElements);\n  });\n\n  const closeWithDelay = React.useCallback(\n    (event: MouseEvent) => {\n      const closeDelay = getDelay(closeDelayProp, 'close', instance.pointerType);\n      const close = () => {\n        store.setOpen(false, createChangeEventDetails(REASONS.triggerHover, event));\n        tree?.events.emit('floating.closed', event);\n      };\n      if (closeDelay) {\n        instance.openChangeTimeout.start(closeDelay, close);\n      } else {\n        instance.openChangeTimeout.clear();\n        close();\n      }\n    },\n    [closeDelayProp, store, instance, tree],\n  );\n\n  const clearPointerEvents = useStableCallback(() => {\n    clearSafePolygonPointerEventsMutation(instance);\n  });\n\n  const handleInteractInside = useStableCallback((event: PointerEvent) => {\n    const target = getTarget(event) as Element | null;\n    if (!isInteractiveElement(target)) {\n      instance.interactedInside = false;\n      return;\n    }\n\n    instance.interactedInside = target?.closest('[aria-haspopup]') != null;\n  });\n\n  useIsoLayoutEffect(() => {\n    if (!open) {\n      instance.pointerType = undefined;\n      instance.restTimeoutPending = false;\n      instance.interactedInside = false;\n      clearPointerEvents();\n    }\n  }, [open, instance, clearPointerEvents]);\n\n  React.useEffect(() => {\n    return clearPointerEvents;\n  }, [clearPointerEvents]);\n\n  useIsoLayoutEffect(() => {\n    if (!enabled) {\n      return undefined;\n    }\n\n    if (\n      open &&\n      instance.handleCloseOptions?.blockPointerEvents &&\n      isHoverOpen() &&\n      isElement(domReferenceElement) &&\n      floatingElement\n    ) {\n      const ref = domReferenceElement as HTMLElement | SVGSVGElement;\n      const floatingEl = floatingElement;\n      const doc = ownerDocument(floatingElement);\n\n      const parentFloating = tree?.nodesRef.current.find((node) => node.id === parentId)?.context\n        ?.elements.floating as HTMLElement | null;\n\n      if (parentFloating) {\n        parentFloating.style.pointerEvents = '';\n      }\n\n      const scopeElement =\n        instance.handleCloseOptions?.getScope?.() ??\n        instance.pointerEventsScopeElement ??\n        parentFloating ??\n        (ref.closest('[data-rootownerid]') as HTMLElement | SVGSVGElement | null) ??\n        doc.body;\n\n      applySafePolygonPointerEventsMutation(instance, {\n        scopeElement,\n        referenceElement: ref,\n        floatingElement: floatingEl,\n      });\n\n      return () => {\n        clearPointerEvents();\n      };\n    }\n\n    return undefined;\n  }, [\n    enabled,\n    open,\n    domReferenceElement,\n    floatingElement,\n    instance,\n    isHoverOpen,\n    tree,\n    parentId,\n    clearPointerEvents,\n  ]);\n\n  const childClosedTimeout = useTimeout();\n\n  React.useEffect(() => {\n    if (!enabled) {\n      return undefined;\n    }\n\n    function onFloatingMouseEnter() {\n      instance.openChangeTimeout.clear();\n      childClosedTimeout.clear();\n      tree?.events.off('floating.closed', onNodeClosed);\n      clearPointerEvents();\n    }\n\n    function onFloatingMouseLeave(event: MouseEvent) {\n      if (tree && parentId && getNodeChildren(tree.nodesRef.current, parentId).length > 0) {\n        tree.events.on('floating.closed', onNodeClosed);\n        return;\n      }\n\n      if (isRelatedTargetInsideEnabledTrigger(event.relatedTarget)) {\n        // If the mouse is leaving the reference element to another trigger, don't explicitly close the popup\n        // as it will be moved.\n        return;\n      }\n\n      // If the safePolygon handler is active, let it handle the close logic.\n      if (instance.handler) {\n        instance.handler(event);\n        return;\n      }\n\n      clearPointerEvents();\n      if (!isClickLikeOpenEvent()) {\n        closeWithDelay(event);\n      }\n    }\n\n    function onNodeClosed(event: MouseEvent) {\n      if (!tree || !parentId || getNodeChildren(tree.nodesRef.current, parentId).length > 0) {\n        return;\n      }\n      // Allow the mouseenter event to fire in case child was closed because mouse moved into parent.\n      childClosedTimeout.start(0, () => {\n        tree.events.off('floating.closed', onNodeClosed);\n        store.setOpen(false, createChangeEventDetails(REASONS.triggerHover, event));\n        tree.events.emit('floating.closed', event);\n      });\n    }\n\n    const floating = floatingElement;\n    if (floating) {\n      floating.addEventListener('mouseenter', onFloatingMouseEnter);\n      floating.addEventListener('mouseleave', onFloatingMouseLeave);\n      floating.addEventListener('pointerdown', handleInteractInside, true);\n    }\n\n    return () => {\n      if (floating) {\n        floating.removeEventListener('mouseenter', onFloatingMouseEnter);\n        floating.removeEventListener('mouseleave', onFloatingMouseLeave);\n        floating.removeEventListener('pointerdown', handleInteractInside, true);\n      }\n      tree?.events.off('floating.closed', onNodeClosed);\n    };\n  }, [\n    enabled,\n    floatingElement,\n    store,\n    dataRef,\n    isClickLikeOpenEvent,\n    isRelatedTargetInsideEnabledTrigger,\n    closeWithDelay,\n    clearPointerEvents,\n    handleInteractInside,\n    instance,\n    tree,\n    parentId,\n    childClosedTimeout,\n  ]);\n}\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/hooks/useHoverInteractionSharedState.ts",
    "content": "'use client';\nimport { useOnMount } from '@base-ui/utils/useOnMount';\nimport { useRefWithInit } from '@base-ui/utils/useRefWithInit';\nimport { Timeout } from '@base-ui/utils/useTimeout';\n\nimport type { ContextData, FloatingRootContext, SafePolygonOptions } from '../types';\nimport { isInteractiveElement } from '../utils';\n\nexport { isInteractiveElement };\n\nexport class HoverInteraction {\n  pointerType: string | undefined;\n  interactedInside: boolean;\n  handler: ((event: MouseEvent) => void) | undefined;\n  blockMouseMove: boolean;\n  performedPointerEventsMutation: boolean;\n  pointerEventsScopeElement: HTMLElement | SVGSVGElement | null;\n  pointerEventsReferenceElement: HTMLElement | SVGSVGElement | null;\n  pointerEventsFloatingElement: HTMLElement | null;\n  restTimeoutPending: boolean;\n  openChangeTimeout: Timeout;\n  restTimeout: Timeout;\n  handleCloseOptions: SafePolygonOptions | undefined;\n\n  constructor() {\n    this.pointerType = undefined;\n    this.interactedInside = false;\n    this.handler = undefined;\n    this.blockMouseMove = true;\n    this.performedPointerEventsMutation = false;\n    this.pointerEventsScopeElement = null;\n    this.pointerEventsReferenceElement = null;\n    this.pointerEventsFloatingElement = null;\n    this.restTimeoutPending = false;\n    this.openChangeTimeout = new Timeout();\n    this.restTimeout = new Timeout();\n    this.handleCloseOptions = undefined;\n  }\n\n  static create(): HoverInteraction {\n    return new HoverInteraction();\n  }\n\n  dispose = () => {\n    this.openChangeTimeout.clear();\n    this.restTimeout.clear();\n  };\n\n  disposeEffect = () => {\n    return this.dispose;\n  };\n}\n\ntype PointerEventsMutationState = Pick<\n  HoverInteraction,\n  | 'performedPointerEventsMutation'\n  | 'pointerEventsScopeElement'\n  | 'pointerEventsReferenceElement'\n  | 'pointerEventsFloatingElement'\n>;\n\nexport function clearSafePolygonPointerEventsMutation(instance: PointerEventsMutationState) {\n  if (!instance.performedPointerEventsMutation) {\n    return;\n  }\n\n  instance.pointerEventsScopeElement?.style.removeProperty('pointer-events');\n  instance.pointerEventsReferenceElement?.style.removeProperty('pointer-events');\n  instance.pointerEventsFloatingElement?.style.removeProperty('pointer-events');\n  instance.performedPointerEventsMutation = false;\n  instance.pointerEventsScopeElement = null;\n  instance.pointerEventsReferenceElement = null;\n  instance.pointerEventsFloatingElement = null;\n}\n\nexport function applySafePolygonPointerEventsMutation(\n  instance: PointerEventsMutationState,\n  options: {\n    scopeElement: HTMLElement | SVGSVGElement;\n    referenceElement: HTMLElement | SVGSVGElement;\n    floatingElement: HTMLElement;\n  },\n) {\n  const { scopeElement, referenceElement, floatingElement } = options;\n\n  clearSafePolygonPointerEventsMutation(instance);\n  instance.performedPointerEventsMutation = true;\n  instance.pointerEventsScopeElement = scopeElement;\n  instance.pointerEventsReferenceElement = referenceElement;\n  instance.pointerEventsFloatingElement = floatingElement;\n\n  scopeElement.style.pointerEvents = 'none';\n  referenceElement.style.pointerEvents = 'auto';\n  floatingElement.style.pointerEvents = 'auto';\n}\n\ntype HoverContextData = ContextData & {\n  hoverInteractionState?: HoverInteraction | undefined;\n};\n\nexport function useHoverInteractionSharedState(store: FloatingRootContext): HoverInteraction {\n  const instance = useRefWithInit(HoverInteraction.create).current;\n\n  const data = store.context.dataRef.current as HoverContextData;\n  if (!data.hoverInteractionState) {\n    data.hoverInteractionState = instance;\n  }\n\n  useOnMount(data.hoverInteractionState.disposeEffect);\n\n  return data.hoverInteractionState;\n}\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/hooks/useHoverReferenceInteraction.test.tsx",
    "content": "import { vi, expect } from 'vitest';\nimport { fireEvent, flushMicrotasks, render, screen } from '@mui/internal-test-utils';\nimport * as React from 'react';\nimport { isJSDOM } from '@base-ui/utils/detectBrowser';\nimport { useFloating } from './useFloating';\nimport { useHoverReferenceInteraction } from './useHoverReferenceInteraction';\n\ndescribe.skipIf(!isJSDOM)('useHoverReferenceInteraction', () => {\n  it('does not treat child target as inactive when handlers are on a wrapper', async () => {\n    const onOpenChange = vi.fn();\n\n    function App() {\n      const [open, setOpen] = React.useState(true);\n      const triggerElementRef = React.useRef<Element | null>(null);\n      const { refs, context } = useFloating({\n        open,\n        onOpenChange(nextOpen, details) {\n          onOpenChange(nextOpen, details);\n          setOpen(nextOpen);\n        },\n      });\n\n      const hoverProps = useHoverReferenceInteraction(context, {\n        mouseOnly: true,\n        restMs: 100,\n        delay: { close: 0 },\n        move: false,\n        triggerElementRef,\n      });\n\n      return (\n        <React.Fragment>\n          <div data-testid=\"wrapper\" {...hoverProps}>\n            <button\n              data-testid=\"trigger\"\n              ref={(node) => {\n                refs.setReference(node);\n                triggerElementRef.current = node;\n              }}\n            />\n          </div>\n          {open && <div role=\"tooltip\" ref={refs.setFloating} />}\n        </React.Fragment>\n      );\n    }\n\n    render(<App />);\n\n    const wrapper = screen.getByTestId('wrapper');\n    const trigger = screen.getByTestId('trigger');\n\n    fireEvent.pointerEnter(wrapper, { pointerType: 'mouse' });\n    fireEvent.mouseEnter(wrapper);\n    fireEvent.mouseMove(trigger, { movementX: 10, movementY: 0 });\n\n    await flushMicrotasks();\n\n    // Moving over the active trigger should not emit a redundant openchange.\n    expect(onOpenChange).toHaveBeenCalledTimes(0);\n    expect(screen.queryByRole('tooltip')).not.toBe(null);\n  });\n\n  it('treats disabled child trigger as inactive in wrapper fallback mode', async () => {\n    const onOpenChange = vi.fn();\n\n    function App() {\n      const [open, setOpen] = React.useState(true);\n      const triggerElementRef = React.useRef<Element | null>(null);\n      const { refs, context } = useFloating({\n        open,\n        onOpenChange(nextOpen, details) {\n          onOpenChange(nextOpen, details);\n          setOpen(nextOpen);\n        },\n      });\n\n      const hoverProps = useHoverReferenceInteraction(context, {\n        mouseOnly: true,\n        restMs: 100,\n        delay: { close: 0 },\n        move: false,\n        triggerElementRef,\n      });\n\n      return (\n        <React.Fragment>\n          <button\n            data-testid=\"active-trigger\"\n            ref={(node) => {\n              refs.setReference(node);\n              triggerElementRef.current = node;\n            }}\n          />\n          <div data-testid=\"inactive-wrapper\" {...hoverProps}>\n            <button\n              data-testid=\"disabled-trigger\"\n              data-trigger-disabled\n              ref={(node) => {\n                if (node) {\n                  context.rootStore.context.triggerElements.add('disabled-trigger', node);\n                }\n              }}\n            />\n          </div>\n          {open && <div role=\"tooltip\" ref={refs.setFloating} />}\n        </React.Fragment>\n      );\n    }\n\n    render(<App />);\n\n    const activeTrigger = screen.getByTestId('active-trigger');\n    const wrapper = screen.getByTestId('inactive-wrapper');\n    const disabledTrigger = screen.getByTestId('disabled-trigger');\n\n    fireEvent.pointerEnter(activeTrigger, { pointerType: 'mouse' });\n    fireEvent.mouseEnter(activeTrigger);\n    fireEvent.pointerEnter(wrapper, { pointerType: 'mouse' });\n    fireEvent.mouseMove(disabledTrigger, { movementX: 10, movementY: 0 });\n\n    await flushMicrotasks();\n\n    expect(onOpenChange).toHaveBeenCalledTimes(1);\n    expect(screen.queryByRole('tooltip')).not.toBe(null);\n  });\n});\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/hooks/useHoverReferenceInteraction.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport { isElement } from '@floating-ui/utils/dom';\nimport { useValueAsRef } from '@base-ui/utils/useValueAsRef';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { ownerDocument } from '@base-ui/utils/owner';\nimport type { Delay, FloatingContext, FloatingRootContext } from '../types';\nimport { contains, isMouseLikePointerType, isTargetInsideEnabledTrigger } from '../utils';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport { useFloatingTree } from '../components/FloatingTree';\nimport type { FloatingTreeStore } from '../components/FloatingTreeStore';\nimport {\n  clearSafePolygonPointerEventsMutation,\n  useHoverInteractionSharedState,\n} from './useHoverInteractionSharedState';\nimport type { HandleClose, HandleCloseContextBase } from './useHoverShared';\nimport {\n  getDelay,\n  getRestMs,\n  isClickLikeOpenEvent as isClickLikeOpenEventShared,\n} from './useHoverShared';\nimport { FloatingUIOpenChangeDetails, HTMLProps } from '../../utils/types';\n\nexport interface UseHoverReferenceInteractionProps {\n  enabled?: boolean | undefined;\n  handleClose?: HandleClose | null | undefined;\n  restMs?: number | (() => number) | undefined;\n  delay?: Delay | (() => Delay) | undefined;\n  move?: boolean | undefined;\n  mouseOnly?: boolean | undefined;\n  externalTree?: FloatingTreeStore | undefined;\n  /**\n   * Whether the hook controls the active trigger. When false, the props are\n   * returned under the `trigger` key so they can be applied to inactive\n   * triggers via `getTriggerProps`.\n   * @default true\n   */\n  isActiveTrigger?: boolean | undefined;\n  triggerElementRef?: Readonly<React.RefObject<Element | null>> | undefined;\n  getHandleCloseContext?: (() => HandleCloseContextBase | null) | undefined;\n}\n\nconst EMPTY_REF: Readonly<React.RefObject<Element | null>> = { current: null };\n\n/**\n * Provides hover interactions that should be attached to reference or trigger\n * elements.\n */\nexport function useHoverReferenceInteraction(\n  context: FloatingRootContext | FloatingContext,\n  props: UseHoverReferenceInteractionProps = {},\n): HTMLProps | undefined {\n  const store = 'rootStore' in context ? context.rootStore : context;\n  const { dataRef, events } = store.context;\n\n  const {\n    enabled = true,\n    delay = 0,\n    handleClose = null,\n    mouseOnly = false,\n    restMs = 0,\n    move = true,\n    triggerElementRef = EMPTY_REF,\n    externalTree,\n    isActiveTrigger = true,\n    getHandleCloseContext,\n  } = props;\n\n  const tree = useFloatingTree(externalTree);\n\n  const instance = useHoverInteractionSharedState(store);\n\n  const handleCloseRef = useValueAsRef(handleClose);\n  const delayRef = useValueAsRef(delay);\n  const restMsRef = useValueAsRef(restMs);\n  const enabledRef = useValueAsRef(enabled);\n\n  if (isActiveTrigger) {\n    // eslint-disable-next-line no-underscore-dangle\n    instance.handleCloseOptions = handleCloseRef.current?.__options;\n  }\n\n  const isClickLikeOpenEvent = useStableCallback(() => {\n    return isClickLikeOpenEventShared(dataRef.current.openEvent?.type, instance.interactedInside);\n  });\n\n  const isRelatedTargetInsideEnabledTrigger = useStableCallback((target: EventTarget | null) => {\n    return isTargetInsideEnabledTrigger(target, store.context.triggerElements);\n  });\n\n  const isOverInactiveTrigger = useStableCallback(\n    (\n      currentDomReference: Element | null,\n      currentTarget: Element,\n      target: EventTarget | null,\n    ): boolean => {\n      const allTriggers = store.context.triggerElements;\n\n      // Fast path for normal usage where handlers are attached directly to triggers.\n      if (allTriggers.hasElement(currentTarget)) {\n        return !currentDomReference || !contains(currentDomReference, currentTarget);\n      }\n\n      // Fallback for delegated/wrapper usage where currentTarget may be outside the trigger map.\n      if (!isElement(target)) {\n        return false;\n      }\n\n      const targetElement = target as Element;\n      return (\n        allTriggers.hasMatchingElement((trigger) => contains(trigger, targetElement)) &&\n        (!currentDomReference || !contains(currentDomReference, targetElement))\n      );\n    },\n  );\n\n  const closeWithDelay = React.useCallback(\n    (event: MouseEvent, runElseBranch = true) => {\n      const closeDelay = getDelay(delayRef.current, 'close', instance.pointerType);\n      if (closeDelay) {\n        instance.openChangeTimeout.start(closeDelay, () => {\n          store.setOpen(false, createChangeEventDetails(REASONS.triggerHover, event));\n          tree?.events.emit('floating.closed', event);\n        });\n      } else if (runElseBranch) {\n        instance.openChangeTimeout.clear();\n        store.setOpen(false, createChangeEventDetails(REASONS.triggerHover, event));\n        tree?.events.emit('floating.closed', event);\n      }\n    },\n    [delayRef, store, instance, tree],\n  );\n\n  const cleanupMouseMoveHandler = useStableCallback(() => {\n    if (!instance.handler) {\n      return;\n    }\n    const doc = ownerDocument(store.select('domReferenceElement'));\n    doc.removeEventListener('mousemove', instance.handler);\n    instance.handler = undefined;\n  });\n  React.useEffect(() => cleanupMouseMoveHandler, [cleanupMouseMoveHandler]);\n\n  const clearPointerEvents = useStableCallback(() => {\n    clearSafePolygonPointerEventsMutation(instance);\n  });\n\n  // When closing before opening, clear the delay timeouts to cancel it\n  // from showing.\n  React.useEffect(() => {\n    if (!enabled) {\n      return undefined;\n    }\n\n    function onOpenChangeLocal(details: FloatingUIOpenChangeDetails) {\n      if (!details.open) {\n        cleanupMouseMoveHandler();\n        instance.openChangeTimeout.clear();\n        instance.restTimeout.clear();\n        instance.blockMouseMove = true;\n        instance.restTimeoutPending = false;\n      }\n    }\n\n    events.on('openchange', onOpenChangeLocal);\n    return () => {\n      events.off('openchange', onOpenChangeLocal);\n    };\n  }, [enabled, events, instance, cleanupMouseMoveHandler]);\n\n  React.useEffect(() => {\n    if (!enabled) {\n      return undefined;\n    }\n\n    const trigger =\n      (triggerElementRef.current as HTMLElement | null) ??\n      (isActiveTrigger ? (store.select('domReferenceElement') as HTMLElement | null) : null);\n\n    if (!isElement(trigger)) {\n      return undefined;\n    }\n\n    function onMouseEnter(event: MouseEvent) {\n      instance.openChangeTimeout.clear();\n      instance.blockMouseMove = false;\n\n      if (mouseOnly && !isMouseLikePointerType(instance.pointerType)) {\n        return;\n      }\n\n      // Only rest delay is set; there's no fallback delay.\n      // This will be handled by `onMouseMove`.\n      const restMsValue = getRestMs(restMsRef.current);\n      if (restMsValue > 0 && !getDelay(delayRef.current, 'open')) {\n        return;\n      }\n\n      const openDelay = getDelay(delayRef.current, 'open', instance.pointerType);\n      const triggerNode = (event.currentTarget as HTMLElement) ?? null;\n      const currentDomReference = store.select('domReferenceElement');\n      const isOverInactive =\n        triggerNode == null\n          ? false\n          : isOverInactiveTrigger(currentDomReference, triggerNode, event.target);\n\n      const isOpen = store.select('open');\n      const shouldOpen = !isOpen || isOverInactive;\n\n      // When moving between triggers while already open, open immediately without delay\n      if (isOverInactive && isOpen) {\n        store.setOpen(true, createChangeEventDetails(REASONS.triggerHover, event, triggerNode));\n      } else if (openDelay) {\n        instance.openChangeTimeout.start(openDelay, () => {\n          if (shouldOpen) {\n            store.setOpen(true, createChangeEventDetails(REASONS.triggerHover, event, triggerNode));\n          }\n        });\n      } else if (shouldOpen) {\n        store.setOpen(true, createChangeEventDetails(REASONS.triggerHover, event, triggerNode));\n      }\n    }\n\n    function onMouseLeave(event: MouseEvent) {\n      if (isClickLikeOpenEvent()) {\n        clearPointerEvents();\n        return;\n      }\n\n      cleanupMouseMoveHandler();\n\n      const domReferenceElement = store.select('domReferenceElement');\n      const doc = ownerDocument(domReferenceElement);\n      instance.restTimeout.clear();\n      instance.restTimeoutPending = false;\n\n      const handleCloseContextBase = dataRef.current.floatingContext ?? getHandleCloseContext?.();\n\n      const ignoreRelatedTargetTrigger = isRelatedTargetInsideEnabledTrigger(event.relatedTarget);\n\n      if (ignoreRelatedTargetTrigger) {\n        return;\n      }\n\n      if (handleCloseRef.current && handleCloseContextBase) {\n        if (!store.select('open')) {\n          instance.openChangeTimeout.clear();\n        }\n\n        const currentTrigger = triggerElementRef.current;\n\n        instance.handler = handleCloseRef.current({\n          ...handleCloseContextBase,\n          tree,\n          x: event.clientX,\n          y: event.clientY,\n          onClose() {\n            clearPointerEvents();\n            cleanupMouseMoveHandler();\n            if (\n              enabledRef.current &&\n              !isClickLikeOpenEvent() &&\n              currentTrigger === store.select('domReferenceElement')\n            ) {\n              closeWithDelay(event, true);\n            }\n          },\n        });\n\n        doc.addEventListener('mousemove', instance.handler);\n        instance.handler(event);\n\n        return;\n      }\n\n      const shouldClose =\n        instance.pointerType === 'touch'\n          ? !contains(store.select('floatingElement'), event.relatedTarget as Element | null)\n          : true;\n\n      if (shouldClose) {\n        closeWithDelay(event);\n      }\n    }\n\n    if (move) {\n      trigger.addEventListener('mousemove', onMouseEnter, {\n        once: true,\n      });\n    }\n\n    trigger.addEventListener('mouseenter', onMouseEnter);\n    trigger.addEventListener('mouseleave', onMouseLeave);\n\n    return () => {\n      if (move) {\n        trigger.removeEventListener('mousemove', onMouseEnter);\n      }\n\n      trigger.removeEventListener('mouseenter', onMouseEnter);\n      trigger.removeEventListener('mouseleave', onMouseLeave);\n    };\n  }, [\n    cleanupMouseMoveHandler,\n    clearPointerEvents,\n    dataRef,\n    delayRef,\n    closeWithDelay,\n    store,\n    enabled,\n    handleCloseRef,\n    instance,\n    isActiveTrigger,\n    isOverInactiveTrigger,\n    isClickLikeOpenEvent,\n    isRelatedTargetInsideEnabledTrigger,\n    mouseOnly,\n    move,\n    restMsRef,\n    triggerElementRef,\n    tree,\n    enabledRef,\n    getHandleCloseContext,\n  ]);\n\n  return React.useMemo<HTMLProps | undefined>(() => {\n    if (!enabled) {\n      return undefined;\n    }\n\n    function setPointerRef(event: React.PointerEvent) {\n      instance.pointerType = event.pointerType;\n    }\n\n    return {\n      onPointerDown: setPointerRef,\n      onPointerEnter: setPointerRef,\n      onMouseMove(event) {\n        const { nativeEvent } = event;\n        const trigger = event.currentTarget as HTMLElement;\n\n        const currentDomReference = store.select('domReferenceElement');\n        const currentOpen = store.select('open');\n        const isOverInactive = isOverInactiveTrigger(currentDomReference, trigger, event.target);\n\n        if (mouseOnly && !isMouseLikePointerType(instance.pointerType)) {\n          return;\n        }\n\n        const restMsValue = getRestMs(restMsRef.current);\n        if ((currentOpen && !isOverInactive) || restMsValue === 0) {\n          return;\n        }\n\n        if (\n          !isOverInactive &&\n          instance.restTimeoutPending &&\n          event.movementX ** 2 + event.movementY ** 2 < 2\n        ) {\n          return;\n        }\n\n        instance.restTimeout.clear();\n\n        function handleMouseMove() {\n          instance.restTimeoutPending = false;\n\n          // A delayed hover open should not override a click-like open that happened\n          // while the hover delay was pending.\n          if (isClickLikeOpenEvent()) {\n            return;\n          }\n\n          const latestOpen = store.select('open');\n\n          if (!instance.blockMouseMove && (!latestOpen || isOverInactive)) {\n            store.setOpen(\n              true,\n              createChangeEventDetails(REASONS.triggerHover, nativeEvent, trigger),\n            );\n          }\n        }\n\n        if (instance.pointerType === 'touch') {\n          ReactDOM.flushSync(() => {\n            handleMouseMove();\n          });\n        } else if (isOverInactive && currentOpen) {\n          handleMouseMove();\n        } else {\n          instance.restTimeoutPending = true;\n          instance.restTimeout.start(restMsValue, handleMouseMove);\n        }\n      },\n    };\n  }, [enabled, instance, isClickLikeOpenEvent, isOverInactiveTrigger, mouseOnly, store, restMsRef]);\n}\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/hooks/useHoverShared.ts",
    "content": "import { isMouseLikePointerType } from '../utils';\nimport type { ExtendedElements, FloatingTreeType, Placement } from '../types';\n\nexport interface HandleCloseOptions {\n  blockPointerEvents?: boolean | undefined;\n  getScope?: (() => HTMLElement | SVGSVGElement | null) | undefined;\n}\n\nexport interface HandleCloseContext {\n  x: number | null;\n  y: number | null;\n  placement: Placement | null;\n  elements: Pick<ExtendedElements, 'domReference' | 'floating'>;\n  onClose: () => void;\n  nodeId?: string | undefined;\n  tree?: FloatingTreeType | null | undefined;\n  leave?: boolean | undefined;\n}\n\nexport type HandleCloseContextBase = Omit<HandleCloseContext, 'onClose' | 'tree' | 'x' | 'y'>;\n\nexport interface HandleClose {\n  (context: HandleCloseContext): (event: MouseEvent) => void;\n  __options?: HandleCloseOptions | undefined;\n}\n\ntype HoverDelay = number | Partial<{ open: number; close: number }>;\n\nfunction resolveValue<T>(\n  value: T | (() => T) | undefined,\n  pointerType?: PointerEvent['pointerType'],\n): T | 0 | undefined {\n  if (pointerType != null && !isMouseLikePointerType(pointerType)) {\n    return 0;\n  }\n\n  if (typeof value === 'function') {\n    return (value as () => T)();\n  }\n\n  return value;\n}\n\nexport function getDelay(\n  value: HoverDelay | (() => HoverDelay) | undefined,\n  prop: 'open' | 'close',\n  pointerType?: PointerEvent['pointerType'],\n) {\n  const result = resolveValue(value, pointerType);\n  if (typeof result === 'number') {\n    return result;\n  }\n\n  return result?.[prop];\n}\n\nexport function getRestMs(value: number | (() => number)) {\n  if (typeof value === 'function') {\n    return value();\n  }\n  return value;\n}\n\nexport function isClickLikeOpenEvent(openEventType: string | undefined, interactedInside: boolean) {\n  return interactedInside || openEventType === 'click' || openEventType === 'mousedown';\n}\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/hooks/useInteractions.test.tsx",
    "content": "import { vi, expect } from 'vitest';\nimport * as React from 'react';\nimport { render } from '@testing-library/react';\n\nimport {\n  useClick,\n  useDismiss,\n  useFloating,\n  useFocus,\n  useHover,\n  useInteractions,\n  useListNavigation,\n  useRole,\n  useTypeahead,\n} from '../index';\n\ndescribe('useInteractions', () => {\n  it('correctly merges functions', () => {\n    const firstInteractionOnClick = vi.fn();\n    const secondInteractionOnClick = vi.fn();\n    const secondInteractionOnKeyDown = vi.fn();\n    const userOnClick = vi.fn();\n\n    function App() {\n      const { getReferenceProps } = useInteractions([\n        { reference: { onClick: firstInteractionOnClick } },\n        {\n          reference: {\n            onClick: secondInteractionOnClick,\n            onKeyDown: secondInteractionOnKeyDown,\n          },\n        },\n      ]);\n\n      const { onClick, onKeyDown } = getReferenceProps({ onClick: userOnClick });\n\n      // @ts-expect-error\n      onClick();\n      // @ts-expect-error\n      onKeyDown();\n\n      return null;\n    }\n\n    render(<App />);\n\n    expect(firstInteractionOnClick).toHaveBeenCalledTimes(1);\n    expect(secondInteractionOnClick).toHaveBeenCalledTimes(1);\n    expect(userOnClick).toHaveBeenCalledTimes(1);\n    expect(secondInteractionOnKeyDown).toHaveBeenCalledTimes(1);\n  });\n\n  it('does not error with undefined user supplied functions', () => {\n    function App() {\n      const { getReferenceProps } = useInteractions([{ reference: { onClick() {} } }]);\n      expect(() =>\n        // @ts-expect-error\n        getReferenceProps({ onClick: undefined }).onClick(),\n      ).not.toThrowError();\n      return null;\n    }\n\n    render(<App />);\n  });\n\n  it('does not break props that start with `on`', () => {\n    function App() {\n      const { getReferenceProps } = useInteractions([]);\n\n      const props = getReferenceProps({\n        // @ts-expect-error\n        onlyShowVotes: true,\n        onyx: () => {},\n      });\n\n      expect(props.onlyShowVotes).toBe(true);\n      expect(typeof props.onyx).toBe('function');\n\n      return null;\n    }\n\n    render(<App />);\n  });\n\n  it('does not break props that return values', () => {\n    function App() {\n      const { getReferenceProps } = useInteractions([]);\n\n      const props = getReferenceProps({\n        // @ts-expect-error\n        onyx: () => 'returned value',\n      });\n\n      // @ts-expect-error\n      expect(props.onyx()).toBe('returned value');\n\n      return null;\n    }\n\n    render(<App />);\n  });\n\n  it('prop getters are memoized', () => {\n    function App() {\n      const [open, setOpen] = React.useState(false);\n      const [, setCount] = React.useState(0);\n\n      const handleClose = () => () => {};\n      // eslint-disable-next-line\n      handleClose.__options = { blockPointerEvents: true };\n\n      const listRef = React.useRef([]);\n      const { context } = useFloating({ open, onOpenChange: setOpen });\n\n      // NOTE: if `ref`-related props are not memoized, this will cause\n      // an infinite loop as they must be memoized externally (as done by React).\n      // Other non-primitives like functions and arrays get memoized by the hooks.\n      const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions([\n        useHover(context, { handleClose }),\n        useFocus(context),\n        useClick(context),\n        useRole(context),\n        useDismiss(context),\n        useListNavigation(context, {\n          listRef,\n          activeIndex: 0,\n          onNavigate: () => {},\n          disabledIndices: [],\n        }),\n        useTypeahead(context, {\n          listRef,\n          activeIndex: 0,\n          onMatch: () => {},\n        }),\n      ]);\n\n      React.useEffect(() => {\n        // Should NOT cause an infinite loop as the prop getters are memoized.\n        setCount((c) => c + 1);\n      }, [getReferenceProps, getFloatingProps, getItemProps]);\n\n      return null;\n    }\n\n    render(<App />);\n  });\n});\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/hooks/useInteractions.ts",
    "content": "import * as React from 'react';\n\nimport type { ElementProps } from '../types';\nimport { ACTIVE_KEY, FOCUSABLE_ATTRIBUTE, SELECTED_KEY } from '../utils/constants';\n\nexport type ExtendedUserProps = {\n  [ACTIVE_KEY]?: boolean | undefined;\n  [SELECTED_KEY]?: boolean | undefined;\n};\n\nexport interface UseInteractionsReturn {\n  getReferenceProps: (userProps?: React.HTMLProps<Element>) => Record<string, unknown>;\n  getFloatingProps: (userProps?: React.HTMLProps<HTMLElement>) => Record<string, unknown>;\n  getItemProps: (\n    userProps?: Omit<React.HTMLProps<HTMLElement>, 'selected' | 'active'> & ExtendedUserProps,\n  ) => Record<string, unknown>;\n  getTriggerProps: (userProps?: React.HTMLProps<Element>) => Record<string, unknown>;\n}\n\n/**\n * Merges an array of interaction hooks' props into prop getters, allowing\n * event handler functions to be composed together without overwriting one\n * another.\n * @see https://floating-ui.com/docs/useInteractions\n */\nexport function useInteractions(propsList: Array<ElementProps | void> = []): UseInteractionsReturn {\n  const referenceDeps = propsList.map((key) => key?.reference);\n  const floatingDeps = propsList.map((key) => key?.floating);\n  const itemDeps = propsList.map((key) => key?.item);\n  const triggerDeps = propsList.map((key) => key?.trigger);\n\n  const getReferenceProps = React.useCallback(\n    (userProps?: React.HTMLProps<Element>) => mergeProps(userProps, propsList, 'reference'),\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n    referenceDeps,\n  );\n\n  const getFloatingProps = React.useCallback(\n    (userProps?: React.HTMLProps<HTMLElement>) => mergeProps(userProps, propsList, 'floating'),\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n    floatingDeps,\n  );\n\n  const getItemProps = React.useCallback(\n    (userProps?: Omit<React.HTMLProps<HTMLElement>, 'selected' | 'active'> & ExtendedUserProps) =>\n      mergeProps(userProps, propsList, 'item'),\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n    itemDeps,\n  );\n\n  const getTriggerProps = React.useCallback(\n    (userProps?: React.HTMLProps<Element>) => mergeProps(userProps, propsList, 'trigger'),\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n    triggerDeps,\n  );\n\n  return React.useMemo(\n    () => ({ getReferenceProps, getFloatingProps, getItemProps, getTriggerProps }),\n    [getReferenceProps, getFloatingProps, getItemProps, getTriggerProps],\n  );\n}\n\n/* eslint-disable guard-for-in */\n\nfunction mergeProps<Key extends keyof ElementProps>(\n  userProps: (React.HTMLProps<Element> & ExtendedUserProps) | undefined,\n  propsList: Array<ElementProps | void>,\n  elementKey: Key,\n): Record<string, unknown> {\n  const eventHandlers = new Map<string, Array<(...args: unknown[]) => void>>();\n  const isItem = elementKey === 'item';\n\n  const outputProps = {} as Record<string, unknown>;\n\n  if (elementKey === 'floating') {\n    outputProps.tabIndex = -1;\n    outputProps[FOCUSABLE_ATTRIBUTE] = '';\n  }\n\n  for (const key in userProps) {\n    if (isItem && userProps) {\n      if (key === ACTIVE_KEY || key === SELECTED_KEY) {\n        continue;\n      }\n    }\n    outputProps[key] = (userProps as any)[key];\n  }\n\n  for (let i = 0; i < propsList.length; i += 1) {\n    let props;\n\n    const propsOrGetProps = propsList[i]?.[elementKey];\n    if (typeof propsOrGetProps === 'function') {\n      props = userProps ? propsOrGetProps(userProps) : null;\n    } else {\n      props = propsOrGetProps;\n    }\n    if (!props) {\n      continue;\n    }\n\n    mutablyMergeProps(outputProps, props, isItem, eventHandlers);\n  }\n\n  mutablyMergeProps(outputProps, userProps, isItem, eventHandlers);\n\n  return outputProps;\n}\n\nfunction mutablyMergeProps(\n  outputProps: Record<string, unknown>,\n  props: any,\n  isItem: boolean,\n  eventHandlers: Map<string, Array<(...args: unknown[]) => void>>,\n) {\n  for (const key in props) {\n    const value = (props as any)[key];\n\n    if (isItem && (key === ACTIVE_KEY || key === SELECTED_KEY)) {\n      continue;\n    }\n\n    if (!key.startsWith('on')) {\n      outputProps[key] = value;\n    } else {\n      if (!eventHandlers.has(key)) {\n        eventHandlers.set(key, []);\n      }\n\n      if (typeof value === 'function') {\n        eventHandlers.get(key)?.push(value);\n\n        outputProps[key] = (...args: unknown[]) => {\n          return eventHandlers\n            .get(key)\n            ?.map((fn) => fn(...args))\n            .find((val) => val !== undefined);\n        };\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/hooks/useListNavigation.test.tsx",
    "content": "import { vi, it, describe, expect } from 'vitest';\nimport * as React from 'react';\nimport { act, fireEvent, render, screen, waitFor } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { flushMicrotasks } from '@mui/internal-test-utils';\nimport { isJSDOM } from '@base-ui/utils/detectBrowser';\nimport { useClick, useDismiss, useFloating, useInteractions, useListNavigation } from '../index';\nimport type { UseListNavigationProps } from '../types';\nimport { Main as ComplexGrid } from '../../../test/floating-ui-tests/ComplexGrid';\nimport { Main as Grid } from '../../../test/floating-ui-tests/Grid';\nimport { Main as EmojiPicker } from '../../../test/floating-ui-tests/EmojiPicker';\nimport { Main as ListboxFocus } from '../../../test/floating-ui-tests/ListboxFocus';\nimport { Main as NestedMenu } from '../../../test/floating-ui-tests/Menu';\nimport { HorizontalMenu } from '../../../test/floating-ui-tests/MenuOrientation';\n\n/* eslint-disable testing-library/no-unnecessary-act */\n\nfunction App(\n  inProps: Omit<Partial<UseListNavigationProps>, 'listRef'> & {\n    disableFirstItem?: boolean;\n    hideFirstItem?: boolean;\n  } = {},\n) {\n  const { disableFirstItem, hideFirstItem, ...props } = inProps;\n  const [open, setOpen] = React.useState(false);\n  const listRef = React.useRef<Array<HTMLLIElement | null>>([]);\n  const [activeIndex, setActiveIndex] = React.useState<null | number>(null);\n  const { refs, context } = useFloating({\n    open,\n    onOpenChange: setOpen,\n  });\n  const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions([\n    useClick(context),\n    useListNavigation(context, {\n      ...props,\n      listRef,\n      activeIndex,\n      onNavigate(index) {\n        setActiveIndex(index);\n        props.onNavigate?.(index, undefined);\n      },\n    }),\n  ]);\n\n  return (\n    <React.Fragment>\n      <button {...getReferenceProps({ ref: refs.setReference })} />\n      {open && (\n        <div role=\"menu\" {...getFloatingProps({ ref: refs.setFloating })}>\n          <ul>\n            {['one', 'two', 'three'].map((string, index) => (\n              // eslint-disable-next-line\n              <li\n                data-testid={`item-${index}`}\n                aria-selected={activeIndex === index}\n                key={string}\n                style={hideFirstItem && index === 0 ? { display: 'none' } : undefined}\n                tabIndex={-1}\n                aria-disabled={\n                  (disableFirstItem && index === 0) ||\n                  (typeof props.disabledIndices === 'function'\n                    ? props.disabledIndices?.(index)\n                    : props.disabledIndices?.includes(index))\n                }\n                {...getItemProps({\n                  ref(node: HTMLLIElement) {\n                    listRef.current[index] = node;\n                  },\n                })}\n              >\n                {string}\n              </li>\n            ))}\n          </ul>\n        </div>\n      )}\n    </React.Fragment>\n  );\n}\n\nfunction VirtualizedGridRows({\n  totalItems = 100,\n  initialActiveIndex = 0,\n  loopFocus = true,\n  disabledIndices,\n  hiddenIndices,\n}: {\n  totalItems?: number;\n  initialActiveIndex?: number;\n  loopFocus?: boolean;\n  disabledIndices?: UseListNavigationProps['disabledIndices'];\n  hiddenIndices?: number[];\n}) {\n  const COLUMNS = 5;\n  const VISIBLE_ROWS = 3;\n\n  const [open, setOpen] = React.useState(true);\n  const [activeIndex, setActiveIndex] = React.useState<number | null>(initialActiveIndex);\n  const listRef = React.useRef<Array<HTMLButtonElement | null>>([]);\n\n  const { refs, context } = useFloating({\n    open,\n    onOpenChange: setOpen,\n  });\n\n  const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions([\n    useListNavigation(context, {\n      listRef,\n      activeIndex,\n      onNavigate: setActiveIndex,\n      virtual: true,\n      loopFocus,\n      cols: 2,\n      orientation: 'horizontal',\n      disabledIndices,\n    }),\n  ]);\n\n  React.useEffect(() => {\n    listRef.current.length = totalItems;\n  }, [totalItems]);\n\n  return (\n    <React.Fragment>\n      <input\n        data-testid=\"virtual-grid-reference\"\n        {...getReferenceProps({ ref: refs.setReference })}\n      />\n      {open && (\n        <div\n          role=\"grid\"\n          data-testid=\"virtual-grid-floating\"\n          {...getFloatingProps({ ref: refs.setFloating })}\n        >\n          {Array.from({ length: VISIBLE_ROWS }, (_row, rowIndex) => (\n            <div key={rowIndex} role=\"row\">\n              {Array.from({ length: COLUMNS }, (_column, columnIndex) => {\n                const itemIndex = rowIndex * COLUMNS + columnIndex;\n                if (itemIndex >= totalItems) {\n                  return null;\n                }\n\n                return (\n                  <button\n                    key={itemIndex}\n                    type=\"button\"\n                    role=\"gridcell\"\n                    style={hiddenIndices?.includes(itemIndex) ? { display: 'none' } : undefined}\n                    data-active={activeIndex === itemIndex ? '' : undefined}\n                    {...getItemProps({\n                      ref(node: HTMLButtonElement | null) {\n                        listRef.current[itemIndex] = node;\n                      },\n                    })}\n                  >\n                    {itemIndex}\n                  </button>\n                );\n              })}\n            </div>\n          ))}\n        </div>\n      )}\n      <span data-testid=\"virtual-grid-active-index\" data-active-index={activeIndex ?? ''} />\n    </React.Fragment>\n  );\n}\n\ndescribe('useListNavigation', () => {\n  it('opens on ArrowDown and focuses first item', async () => {\n    render(<App />);\n\n    fireEvent.keyDown(screen.getByRole('button'), { key: 'ArrowDown' });\n    expect(screen.getByRole('menu')).toBeInTheDocument();\n    await waitFor(() => {\n      expect(screen.getByTestId('item-0')).toHaveFocus();\n    });\n  });\n\n  it('opens on ArrowUp and focuses last item', async () => {\n    render(<App />);\n\n    fireEvent.keyDown(screen.getByRole('button'), { key: 'ArrowUp' });\n    expect(screen.getByRole('menu')).toBeInTheDocument();\n    await waitFor(() => {\n      expect(screen.getByTestId('item-2')).toHaveFocus();\n    });\n  });\n\n  it('navigates down on ArrowDown', async () => {\n    render(<App />);\n\n    fireEvent.keyDown(screen.getByRole('button'), { key: 'ArrowDown' });\n    expect(screen.getByRole('menu')).toBeInTheDocument();\n    await waitFor(() => {\n      expect(screen.getByTestId('item-0')).toHaveFocus();\n    });\n\n    fireEvent.keyDown(screen.getByRole('menu'), { key: 'ArrowDown' });\n    await waitFor(() => {\n      expect(screen.getByTestId('item-1')).toHaveFocus();\n    });\n\n    fireEvent.keyDown(screen.getByRole('menu'), { key: 'ArrowDown' });\n    await waitFor(() => {\n      expect(screen.getByTestId('item-2')).toHaveFocus();\n    });\n\n    // Reached the end of the list.\n    fireEvent.keyDown(screen.getByRole('menu'), { key: 'ArrowDown' });\n    await waitFor(() => {\n      expect(screen.getByTestId('item-2')).toHaveFocus();\n    });\n  });\n\n  it('navigates up on ArrowUp', async () => {\n    render(<App />);\n\n    fireEvent.keyDown(screen.getByRole('button'), { key: 'ArrowUp' });\n    expect(screen.getByRole('menu')).toBeInTheDocument();\n    await waitFor(() => {\n      expect(screen.getByTestId('item-2')).toHaveFocus();\n    });\n\n    fireEvent.keyDown(screen.getByRole('menu'), { key: 'ArrowUp' });\n    await waitFor(() => {\n      expect(screen.getByTestId('item-1')).toHaveFocus();\n    });\n\n    fireEvent.keyDown(screen.getByRole('menu'), { key: 'ArrowUp' });\n    await waitFor(() => {\n      expect(screen.getByTestId('item-0')).toHaveFocus();\n    });\n\n    // Reached the end of the list.\n    fireEvent.keyDown(screen.getByRole('menu'), { key: 'ArrowUp' });\n    await waitFor(() => {\n      expect(screen.getByTestId('item-0')).toHaveFocus();\n    });\n  });\n\n  it('skips disabled item on initial navigation', async () => {\n    render(<App disableFirstItem loopFocus disabledIndices={[]} />);\n\n    fireEvent.keyDown(screen.getByRole('button'), { key: 'ArrowDown' });\n    expect(screen.getByRole('menu')).toBeInTheDocument();\n    await waitFor(() => {\n      expect(screen.getByTestId('item-1')).toHaveFocus();\n    });\n\n    fireEvent.keyDown(screen.getByRole('menu'), { key: 'ArrowDown' });\n    await waitFor(() => {\n      expect(screen.getByTestId('item-2')).toHaveFocus();\n    });\n\n    fireEvent.keyDown(screen.getByRole('menu'), { key: 'ArrowUp' });\n    await waitFor(() => {\n      expect(screen.getByTestId('item-1')).toHaveFocus();\n    });\n\n    fireEvent.keyDown(screen.getByRole('menu'), { key: 'ArrowUp' });\n    await waitFor(() => {\n      expect(screen.getByTestId('item-0')).toHaveFocus();\n    });\n  });\n\n  it('skips items hidden with CSS in navigation', async () => {\n    render(<App hideFirstItem loopFocus disabledIndices={[]} />);\n\n    fireEvent.keyDown(screen.getByRole('button'), { key: 'ArrowDown' });\n    expect(screen.getByRole('menu')).toBeInTheDocument();\n    await waitFor(() => {\n      expect(screen.getByTestId('item-1')).toHaveFocus();\n    });\n\n    fireEvent.keyDown(screen.getByRole('menu'), { key: 'ArrowUp' });\n    await waitFor(() => {\n      expect(screen.getByTestId('item-2')).toHaveFocus();\n    });\n  });\n\n  it('resets indexRef to -1 upon close', async () => {\n    const data = ['a', 'ab', 'abc', 'abcd'];\n\n    function Autocomplete() {\n      const [open, setOpen] = React.useState(false);\n      const [inputValue, setInputValue] = React.useState('');\n      const [activeIndex, setActiveIndex] = React.useState<number | null>(null);\n\n      const listRef = React.useRef<Array<HTMLElement | null>>([]);\n\n      const { x, y, strategy, context, refs } = useFloating({\n        open,\n        onOpenChange: setOpen,\n      });\n\n      const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions([\n        useDismiss(context),\n        useListNavigation(context, {\n          listRef,\n          activeIndex,\n          onNavigate: setActiveIndex,\n          virtual: true,\n          loopFocus: true,\n        }),\n      ]);\n\n      function onChange(event: React.ChangeEvent<HTMLInputElement>) {\n        const value = event.target.value;\n        setInputValue(value);\n\n        if (value) {\n          setActiveIndex(null);\n          setOpen(true);\n        } else {\n          setOpen(false);\n        }\n      }\n\n      const items = data.filter((item) => item.toLowerCase().startsWith(inputValue.toLowerCase()));\n\n      return (\n        <React.Fragment>\n          <input\n            {...getReferenceProps({\n              ref: refs.setReference,\n              onChange,\n              value: inputValue,\n              placeholder: 'Enter fruit',\n              'aria-autocomplete': 'list',\n            })}\n            data-testid=\"reference\"\n          />\n          {open && (\n            <div\n              {...getFloatingProps({\n                ref: refs.setFloating,\n                style: {\n                  position: strategy,\n                  left: x ?? '',\n                  top: y ?? '',\n                  background: '#eee',\n                  color: 'black',\n                  overflowY: 'auto',\n                },\n              })}\n              data-testid=\"floating\"\n            >\n              <ul>\n                {items.map((item, index) => (\n                  <li\n                    key={item}\n                    {...getItemProps({\n                      ref(node) {\n                        listRef.current[index] = node;\n                      },\n                      onClick() {\n                        setInputValue(item);\n                        setOpen(false);\n                        (refs.domReference.current as HTMLElement | null)?.focus();\n                      },\n                    })}\n                  >\n                    {item}\n                  </li>\n                ))}\n              </ul>\n            </div>\n          )}\n          <div data-testid=\"active-index\">{activeIndex}</div>\n        </React.Fragment>\n      );\n    }\n\n    render(<Autocomplete />);\n\n    await act(async () => screen.getByTestId('reference').focus());\n    await userEvent.keyboard('a');\n\n    expect(screen.getByTestId('floating')).toBeInTheDocument();\n    expect(screen.getByTestId('active-index').textContent).toBe('');\n\n    await userEvent.keyboard('{ArrowDown}');\n    await userEvent.keyboard('{ArrowDown}');\n    await userEvent.keyboard('{ArrowDown}');\n\n    expect(screen.getByTestId('active-index').textContent).toBe('2');\n\n    await userEvent.keyboard('{Escape}');\n\n    expect(screen.getByTestId('active-index').textContent).toBe('');\n\n    await userEvent.keyboard('{Backspace}');\n    await userEvent.keyboard('a');\n\n    expect(screen.getByTestId('floating')).toBeInTheDocument();\n    expect(screen.getByTestId('active-index').textContent).toBe('');\n\n    await userEvent.keyboard('{ArrowDown}');\n\n    expect(screen.getByTestId('active-index').textContent).toBe('0');\n  });\n\n  describe('prop: loopFocus', () => {\n    it('ArrowDown looping', async () => {\n      render(<App loopFocus />);\n\n      fireEvent.keyDown(screen.getByRole('button'), { key: 'ArrowDown' });\n      expect(screen.getByRole('menu')).toBeInTheDocument();\n      await waitFor(() => {\n        expect(screen.getByTestId('item-0')).toHaveFocus();\n      });\n\n      fireEvent.keyDown(screen.getByRole('menu'), { key: 'ArrowDown' });\n      await waitFor(() => {\n        expect(screen.getByTestId('item-1')).toHaveFocus();\n      });\n\n      fireEvent.keyDown(screen.getByRole('menu'), { key: 'ArrowDown' });\n      await waitFor(() => {\n        expect(screen.getByTestId('item-2')).toHaveFocus();\n      });\n\n      // Reached the end of the list and loops.\n      fireEvent.keyDown(screen.getByRole('menu'), { key: 'ArrowDown' });\n      await waitFor(() => {\n        expect(screen.getByTestId('item-0')).toHaveFocus();\n      });\n    });\n\n    it('ArrowUp looping', async () => {\n      render(<App loopFocus />);\n\n      fireEvent.keyDown(screen.getByRole('button'), { key: 'ArrowUp' });\n      expect(screen.getByRole('menu')).toBeInTheDocument();\n      await waitFor(() => {\n        expect(screen.getByTestId('item-2')).toHaveFocus();\n      });\n\n      fireEvent.keyDown(screen.getByRole('menu'), { key: 'ArrowUp' });\n      await waitFor(() => {\n        expect(screen.getByTestId('item-1')).toHaveFocus();\n      });\n\n      fireEvent.keyDown(screen.getByRole('menu'), { key: 'ArrowUp' });\n      await waitFor(() => {\n        expect(screen.getByTestId('item-0')).toHaveFocus();\n      });\n\n      // Reached the end of the list and loops.\n      fireEvent.keyDown(screen.getByRole('menu'), { key: 'ArrowUp' });\n      await waitFor(() => {\n        expect(screen.getByTestId('item-2')).toHaveFocus();\n      });\n    });\n  });\n\n  describe('prop: orientation', () => {\n    it('navigates down on ArrowRight', async () => {\n      render(<App orientation=\"horizontal\" />);\n\n      fireEvent.keyDown(screen.getByRole('button'), { key: 'ArrowRight' });\n      expect(screen.getByRole('menu')).toBeInTheDocument();\n      await waitFor(() => {\n        expect(screen.getByTestId('item-0')).toHaveFocus();\n      });\n\n      fireEvent.keyDown(screen.getByRole('menu'), { key: 'ArrowRight' });\n      await waitFor(() => {\n        expect(screen.getByTestId('item-1')).toHaveFocus();\n      });\n\n      fireEvent.keyDown(screen.getByRole('menu'), { key: 'ArrowRight' });\n      await waitFor(() => {\n        expect(screen.getByTestId('item-2')).toHaveFocus();\n      });\n\n      // Reached the end of the list.\n      fireEvent.keyDown(screen.getByRole('menu'), { key: 'ArrowRight' });\n      await waitFor(() => {\n        expect(screen.getByTestId('item-2')).toHaveFocus();\n      });\n    });\n\n    it('navigates up on ArrowLeft', async () => {\n      render(<App orientation=\"horizontal\" />);\n\n      fireEvent.keyDown(screen.getByRole('button'), { key: 'ArrowLeft' });\n      expect(screen.getByRole('menu')).toBeInTheDocument();\n      await waitFor(() => {\n        expect(screen.getByTestId('item-2')).toHaveFocus();\n      });\n\n      fireEvent.keyDown(screen.getByRole('menu'), { key: 'ArrowLeft' });\n      await waitFor(() => {\n        expect(screen.getByTestId('item-1')).toHaveFocus();\n      });\n\n      fireEvent.keyDown(screen.getByRole('menu'), { key: 'ArrowLeft' });\n      await waitFor(() => {\n        expect(screen.getByTestId('item-0')).toHaveFocus();\n      });\n\n      // Reached the end of the list.\n      fireEvent.keyDown(screen.getByRole('menu'), { key: 'ArrowLeft' });\n      await waitFor(() => {\n        expect(screen.getByTestId('item-0')).toHaveFocus();\n      });\n    });\n  });\n\n  describe('prop: rtl', () => {\n    it('navigates down on ArrowLeft', async () => {\n      render(<App rtl orientation=\"horizontal\" />);\n\n      fireEvent.keyDown(screen.getByRole('button'), { key: 'ArrowLeft' });\n      expect(screen.getByRole('menu')).toBeInTheDocument();\n      await waitFor(() => {\n        expect(screen.getByTestId('item-0')).toHaveFocus();\n      });\n\n      fireEvent.keyDown(screen.getByRole('menu'), { key: 'ArrowLeft' });\n      await waitFor(() => {\n        expect(screen.getByTestId('item-1')).toHaveFocus();\n      });\n\n      fireEvent.keyDown(screen.getByRole('menu'), { key: 'ArrowLeft' });\n      await waitFor(() => {\n        expect(screen.getByTestId('item-2')).toHaveFocus();\n      });\n\n      // Reached the end of the list.\n      fireEvent.keyDown(screen.getByRole('menu'), { key: 'ArrowLeft' });\n      await waitFor(() => {\n        expect(screen.getByTestId('item-2')).toHaveFocus();\n      });\n    });\n\n    it('navigates up on ArrowRight', async () => {\n      render(<App rtl orientation=\"horizontal\" />);\n\n      fireEvent.keyDown(screen.getByRole('button'), { key: 'ArrowRight' });\n      expect(screen.getByRole('menu')).toBeInTheDocument();\n      await waitFor(() => {\n        expect(screen.getByTestId('item-2')).toHaveFocus();\n      });\n\n      fireEvent.keyDown(screen.getByRole('menu'), { key: 'ArrowRight' });\n      await waitFor(() => {\n        expect(screen.getByTestId('item-1')).toHaveFocus();\n      });\n\n      fireEvent.keyDown(screen.getByRole('menu'), { key: 'ArrowRight' });\n      await waitFor(() => {\n        expect(screen.getByTestId('item-0')).toHaveFocus();\n      });\n\n      // Reached the end of the list.\n      fireEvent.keyDown(screen.getByRole('menu'), { key: 'ArrowRight' });\n      await waitFor(() => {\n        expect(screen.getByTestId('item-0')).toHaveFocus();\n      });\n    });\n  });\n\n  describe('prop: focusItemOnOpen', () => {\n    it('focuses the first item on click when true', async () => {\n      render(<App focusItemOnOpen />);\n      fireEvent.click(screen.getByRole('button'));\n      await waitFor(() => {\n        expect(screen.getByTestId('item-0')).toHaveFocus();\n      });\n    });\n\n    it('does not focus the first item on click when false', async () => {\n      render(<App focusItemOnOpen={false} />);\n      fireEvent.click(screen.getByRole('button'));\n      await waitFor(() => {\n        expect(screen.getByTestId('item-0')).not.toHaveFocus();\n      });\n    });\n  });\n\n  describe('prop: selectedIndex', () => {\n    it('scrolls the selected item into view on open', async ({ onTestFinished }) => {\n      const requestAnimationFrame = vi\n        .spyOn(window, 'requestAnimationFrame')\n        .mockImplementation(() => 0);\n      const scrollIntoView = vi.fn();\n      const originalScrollIntoView = HTMLElement.prototype.scrollIntoView;\n      HTMLElement.prototype.scrollIntoView = scrollIntoView;\n\n      onTestFinished(() => {\n        requestAnimationFrame.mockRestore();\n        HTMLElement.prototype.scrollIntoView = originalScrollIntoView;\n      });\n\n      render(<App selectedIndex={0} />);\n      fireEvent.click(screen.getByRole('button'));\n      await flushMicrotasks();\n      expect(requestAnimationFrame).toHaveBeenCalled();\n      // Run the timer\n      requestAnimationFrame.mock.calls.forEach((call) => call[0](0));\n      expect(scrollIntoView).toHaveBeenCalled();\n    });\n  });\n\n  describe('allowEscape + virtual', () => {\n    it('when true', async () => {\n      render(<App allowEscape virtual loopFocus />);\n      fireEvent.keyDown(screen.getByRole('button'), { key: 'ArrowDown' });\n      expect(screen.getByTestId('item-0').getAttribute('aria-selected')).toBe('true');\n      fireEvent.keyDown(screen.getByRole('button'), { key: 'ArrowUp' });\n      expect(screen.getByTestId('item-0').getAttribute('aria-selected')).toBe('false');\n      fireEvent.keyDown(screen.getByRole('button'), { key: 'ArrowDown' });\n      expect(screen.getByTestId('item-0').getAttribute('aria-selected')).toBe('true');\n      fireEvent.keyDown(screen.getByRole('button'), { key: 'ArrowDown' });\n      expect(screen.getByTestId('item-1').getAttribute('aria-selected')).toBe('true');\n      fireEvent.keyDown(screen.getByRole('button'), { key: 'ArrowDown' });\n      expect(screen.getByTestId('item-2').getAttribute('aria-selected')).toBe('true');\n      fireEvent.keyDown(screen.getByRole('button'), { key: 'ArrowDown' });\n      expect(screen.getByTestId('item-2').getAttribute('aria-selected')).toBe('false');\n      await flushMicrotasks();\n    });\n\n    it('when false', async () => {\n      render(<App allowEscape={false} virtual loopFocus />);\n      fireEvent.keyDown(screen.getByRole('button'), { key: 'ArrowDown' });\n      expect(screen.getByTestId('item-0').getAttribute('aria-selected')).toBe('true');\n      fireEvent.keyDown(screen.getByRole('button'), { key: 'ArrowDown' });\n      expect(screen.getByTestId('item-1').getAttribute('aria-selected')).toBe('true');\n      await flushMicrotasks();\n    });\n\n    it('true - onNavigate is called with `null` when escaped', async () => {\n      const spy = vi.fn();\n      render(<App allowEscape virtual loopFocus onNavigate={spy} />);\n      fireEvent.keyDown(screen.getByRole('button'), { key: 'ArrowDown' });\n      fireEvent.keyDown(screen.getByRole('button'), { key: 'ArrowUp' });\n      expect(spy).toHaveBeenCalledTimes(2);\n      expect(spy.mock.calls.some((args) => args[0] === null)).toBe(true);\n      await flushMicrotasks();\n    });\n  });\n\n  describe('prop: openOnArrowKeyDown', () => {\n    it('opens on ArrowDown when true', async () => {\n      render(<App openOnArrowKeyDown />);\n      fireEvent.keyDown(screen.getByRole('button'), { key: 'ArrowDown' });\n      expect(screen.getByRole('menu')).toBeInTheDocument();\n      await flushMicrotasks();\n    });\n\n    it('opens on ArrowUp when true', async () => {\n      render(<App openOnArrowKeyDown />);\n      fireEvent.keyDown(screen.getByRole('button'), { key: 'ArrowUp' });\n      expect(screen.getByRole('menu')).toBeInTheDocument();\n      await flushMicrotasks();\n    });\n\n    it('does not open on ArrowDown when false', () => {\n      render(<App openOnArrowKeyDown={false} />);\n      fireEvent.keyDown(screen.getByRole('button'), { key: 'ArrowDown' });\n      expect(screen.queryByRole('menu')).not.toBeInTheDocument();\n    });\n\n    it('does not open on ArrowUp when false', () => {\n      render(<App openOnArrowKeyDown={false} />);\n      fireEvent.keyDown(screen.getByRole('button'), { key: 'ArrowUp' });\n      expect(screen.queryByRole('menu')).not.toBeInTheDocument();\n    });\n  });\n\n  describe('prop: disabledIndices', () => {\n    it('indices are skipped in focus order', async () => {\n      render(<App disabledIndices={[0]} />);\n      fireEvent.keyDown(screen.getByRole('button'), { key: 'ArrowDown' });\n      await waitFor(() => {\n        expect(screen.getByTestId('item-1')).toHaveFocus();\n      });\n      fireEvent.keyDown(screen.getByRole('menu'), { key: 'ArrowUp' });\n      await waitFor(() => {\n        expect(screen.getByTestId('item-1')).toHaveFocus();\n      });\n    });\n  });\n\n  describe('prop: focusItemOnHover', () => {\n    it('true - focuses item on hover and syncs the active index', async () => {\n      const spy = vi.fn();\n      render(<App onNavigate={spy} />);\n      fireEvent.click(screen.getByRole('button'));\n      fireEvent.mouseMove(screen.getByTestId('item-1'));\n      expect(screen.getByTestId('item-1')).toHaveFocus();\n      fireEvent.pointerLeave(screen.getByTestId('item-1'));\n      expect(screen.getByRole('menu')).toHaveFocus();\n      expect(spy.mock.calls.some((args) => args[0] === 1)).toBe(true);\n      await flushMicrotasks();\n    });\n\n    it('false - does not focus item on hover and does not sync the active index', async () => {\n      const spy = vi.fn();\n      render(<App onNavigate={spy} focusItemOnOpen={false} focusItemOnHover={false} />);\n      fireEvent.click(screen.getByRole('button'));\n      fireEvent.mouseMove(screen.getByTestId('item-1'));\n      expect(screen.getByTestId('item-1')).not.toHaveFocus();\n      expect(spy).toHaveBeenCalledTimes(0);\n      await flushMicrotasks();\n    });\n  });\n\n  describe('grid navigation', () => {\n    it('ArrowDown focuses first item', async () => {\n      render(<Grid />);\n\n      fireEvent.click(screen.getByRole('button'));\n      expect(screen.getByRole('menu')).toBeInTheDocument();\n      fireEvent.keyDown(document, { key: 'ArrowDown' });\n      await waitFor(() => {\n        expect(screen.getAllByRole('option')[8]).toHaveFocus();\n      });\n    });\n\n    it('focuses first non-disabled item in grid', async () => {\n      render(<Grid />);\n      fireEvent.keyDown(screen.getByRole('button'), { key: 'Enter' });\n      fireEvent.click(screen.getByRole('button'));\n      await waitFor(() => {\n        expect(screen.getAllByRole('option')[8]).toHaveFocus();\n      });\n    });\n\n    it('focuses next item using ArrowRight key, skipping disabled items', async () => {\n      render(<Grid />);\n      fireEvent.keyDown(screen.getByRole('button'), { key: 'Enter' });\n      fireEvent.click(screen.getByRole('button'));\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowRight' });\n      expect(screen.getAllByRole('option')[9]).toHaveFocus();\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowRight' });\n      expect(screen.getAllByRole('option')[11]).toHaveFocus();\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowRight' });\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowRight' });\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowRight' });\n      expect(screen.getAllByRole('option')[14]).toHaveFocus();\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowRight' });\n      expect(screen.getAllByRole('option')[16]).toHaveFocus();\n      await flushMicrotasks();\n    });\n\n    it('focuses previous item using ArrowLeft key, skipping disabled items', async () => {\n      render(<Grid />);\n      fireEvent.keyDown(screen.getByRole('button'), { key: 'Enter' });\n      fireEvent.click(screen.getByRole('button'));\n\n      act(() => screen.getAllByRole('option')[47].focus());\n\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowLeft' });\n      expect(screen.getAllByRole('option')[46]).toHaveFocus();\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowLeft' });\n      expect(screen.getAllByRole('option')[44]).toHaveFocus();\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowLeft' });\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowLeft' });\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowLeft' });\n      expect(screen.getAllByRole('option')[41]).toHaveFocus();\n      await flushMicrotasks();\n    });\n\n    it('skips row and remains on same column when pressing ArrowDown', async () => {\n      render(<Grid />);\n      fireEvent.keyDown(screen.getByRole('button'), { key: 'Enter' });\n      fireEvent.click(screen.getByRole('button'));\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowDown' });\n      expect(screen.getAllByRole('option')[13]).toHaveFocus();\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowDown' });\n      expect(screen.getAllByRole('option')[18]).toHaveFocus();\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowDown' });\n      expect(screen.getAllByRole('option')[23]).toHaveFocus();\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowDown' });\n      expect(screen.getAllByRole('option')[28]).toHaveFocus();\n      await flushMicrotasks();\n    });\n\n    it('skips row and remains on same column when pressing ArrowUp', async () => {\n      render(<Grid />);\n      fireEvent.keyDown(screen.getByRole('button'), { key: 'Enter' });\n      fireEvent.click(screen.getByRole('button'));\n\n      act(() => screen.getAllByRole('option')[47].focus());\n\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowUp' });\n      expect(screen.getAllByRole('option')[42]).toHaveFocus();\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowUp' });\n      expect(screen.getAllByRole('option')[37]).toHaveFocus();\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowUp' });\n      expect(screen.getAllByRole('option')[32]).toHaveFocus();\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowUp' });\n      expect(screen.getAllByRole('option')[27]).toHaveFocus();\n      await flushMicrotasks();\n    });\n\n    it('loops on the same column with ArrowDown', async () => {\n      render(<Grid loopFocus />);\n      fireEvent.keyDown(screen.getByRole('button'), { key: 'Enter' });\n      fireEvent.click(screen.getByRole('button'));\n\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowDown' });\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowDown' });\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowDown' });\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowDown' });\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowDown' });\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowDown' });\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowDown' });\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowDown' });\n\n      expect(screen.getAllByRole('option')[8]).toHaveFocus();\n      await flushMicrotasks();\n    });\n\n    it('loops on the same column with ArrowUp', async () => {\n      render(<Grid loopFocus />);\n      fireEvent.keyDown(screen.getByRole('button'), { key: 'Enter' });\n      fireEvent.click(screen.getByRole('button'));\n\n      act(() => screen.getAllByRole('option')[43].focus());\n\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowUp' });\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowUp' });\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowUp' });\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowUp' });\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowUp' });\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowUp' });\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowUp' });\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowUp' });\n\n      expect(screen.getAllByRole('option')[43]).toHaveFocus();\n      await flushMicrotasks();\n    });\n\n    it('does not leave row with \"both\" orientation while looping', async () => {\n      render(<Grid orientation=\"both\" loopFocus />);\n      fireEvent.keyDown(screen.getByRole('button'), { key: 'Enter' });\n      fireEvent.click(screen.getByRole('button'));\n\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowRight' });\n      expect(screen.getAllByRole('option')[9]).toHaveFocus();\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowRight' });\n      expect(screen.getAllByRole('option')[8]).toHaveFocus();\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowLeft' });\n      expect(screen.getAllByRole('option')[9]).toHaveFocus();\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowLeft' });\n      expect(screen.getAllByRole('option')[8]).toHaveFocus();\n\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowDown' });\n      expect(screen.getAllByRole('option')[13]).toHaveFocus();\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowRight' });\n      expect(screen.getAllByRole('option')[14]).toHaveFocus();\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowRight' });\n      expect(screen.getAllByRole('option')[11]).toHaveFocus();\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowLeft' });\n      expect(screen.getAllByRole('option')[14]).toHaveFocus();\n      await flushMicrotasks();\n    });\n\n    it('looping works on last row', async () => {\n      render(<Grid orientation=\"both\" loopFocus />);\n      fireEvent.keyDown(screen.getByRole('button'), { key: 'Enter' });\n      fireEvent.click(screen.getByRole('button'));\n\n      act(() => screen.getAllByRole('option')[46].focus());\n\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowRight' });\n      expect(screen.getAllByRole('option')[47]).toHaveFocus();\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowRight' });\n      expect(screen.getAllByRole('option')[46]).toHaveFocus();\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowLeft' });\n      expect(screen.getAllByRole('option')[47]).toHaveFocus();\n      fireEvent.keyDown(screen.getByTestId('floating'), { key: 'ArrowLeft' });\n      expect(screen.getAllByRole('option')[46]).toHaveFocus();\n      await flushMicrotasks();\n    });\n\n    it('wraps ArrowUp to the last row in the full list for virtualized rows', async () => {\n      render(<VirtualizedGridRows />);\n\n      const reference = screen.getByTestId('virtual-grid-reference');\n      await act(async () => {\n        reference.focus();\n      });\n\n      await userEvent.keyboard('{ArrowUp}');\n\n      await waitFor(() => {\n        expect(screen.getByTestId('virtual-grid-active-index')).toHaveAttribute(\n          'data-active-index',\n          '95',\n        );\n      });\n    });\n\n    it('clamps ArrowUp to the last item in a partial last row for virtualized rows', async () => {\n      render(<VirtualizedGridRows totalItems={98} initialActiveIndex={4} />);\n\n      const reference = screen.getByTestId('virtual-grid-reference');\n      await act(async () => {\n        reference.focus();\n      });\n\n      await userEvent.keyboard('{ArrowUp}');\n\n      await waitFor(() => {\n        expect(screen.getByTestId('virtual-grid-active-index')).toHaveAttribute(\n          'data-active-index',\n          '97',\n        );\n      });\n    });\n\n    it('clamps ArrowDown into a partial last row for virtualized rows', async () => {\n      render(<VirtualizedGridRows totalItems={98} initialActiveIndex={93} />);\n\n      const reference = screen.getByTestId('virtual-grid-reference');\n      await act(async () => {\n        reference.focus();\n      });\n\n      await userEvent.keyboard('{ArrowDown}');\n\n      await waitFor(() => {\n        expect(screen.getByTestId('virtual-grid-active-index')).toHaveAttribute(\n          'data-active-index',\n          '97',\n        );\n      });\n    });\n\n    it('does not wrap ArrowUp when loopFocus is false for virtualized rows', async () => {\n      render(<VirtualizedGridRows totalItems={98} initialActiveIndex={4} loopFocus={false} />);\n\n      const reference = screen.getByTestId('virtual-grid-reference');\n      await act(async () => {\n        reference.focus();\n      });\n\n      await userEvent.keyboard('{ArrowUp}');\n\n      await waitFor(() => {\n        expect(screen.getByTestId('virtual-grid-active-index')).toHaveAttribute(\n          'data-active-index',\n          '4',\n        );\n      });\n    });\n\n    it('still clamps ArrowDown into a partial last row when loopFocus is false', async () => {\n      render(<VirtualizedGridRows totalItems={98} initialActiveIndex={93} loopFocus={false} />);\n\n      const reference = screen.getByTestId('virtual-grid-reference');\n      await act(async () => {\n        reference.focus();\n      });\n\n      await userEvent.keyboard('{ArrowDown}');\n\n      await waitFor(() => {\n        expect(screen.getByTestId('virtual-grid-active-index')).toHaveAttribute(\n          'data-active-index',\n          '97',\n        );\n      });\n    });\n\n    it('falls back left in a partial last row when the preferred candidate is disabled', async () => {\n      render(\n        <VirtualizedGridRows totalItems={98} initialActiveIndex={93} disabledIndices={[97]} />,\n      );\n\n      const reference = screen.getByTestId('virtual-grid-reference');\n      await act(async () => {\n        reference.focus();\n      });\n\n      await userEvent.keyboard('{ArrowDown}');\n\n      await waitFor(() => {\n        expect(screen.getByTestId('virtual-grid-active-index')).toHaveAttribute(\n          'data-active-index',\n          '96',\n        );\n      });\n    });\n\n    it('falls back left when the preferred candidate is hidden', async () => {\n      render(<VirtualizedGridRows initialActiveIndex={9} hiddenIndices={[14]} />);\n\n      const reference = screen.getByTestId('virtual-grid-reference');\n      await act(async () => {\n        reference.focus();\n      });\n\n      await userEvent.keyboard('{ArrowDown}');\n\n      await waitFor(() => {\n        expect(screen.getByTestId('virtual-grid-active-index')).toHaveAttribute(\n          'data-active-index',\n          '13',\n        );\n      });\n    });\n  });\n\n  describe('grid navigation when items have different sizes', () => {\n    it('focuses first non-disabled item in grid', async () => {\n      render(<ComplexGrid />);\n      fireEvent.keyDown(screen.getByRole('button'), { key: 'Enter' });\n      fireEvent.click(screen.getByRole('button'));\n      await waitFor(() => {\n        expect(screen.getAllByRole('option')[7]).toHaveFocus();\n      });\n    });\n\n    describe.each([\n      { rtl: false, arrowToStart: 'ArrowLeft', arrowToEnd: 'ArrowRight' },\n      { rtl: true, arrowToStart: 'ArrowRight', arrowToEnd: 'ArrowLeft' },\n    ])('with rtl $rtl', ({ rtl, arrowToStart, arrowToEnd }) => {\n      it(`focuses next item using ${arrowToEnd} key, skipping disabled items`, async () => {\n        render(<ComplexGrid rtl={rtl} />);\n        fireEvent.keyDown(screen.getByRole('button'), { key: 'Enter' });\n        fireEvent.click(screen.getByRole('button'));\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToEnd });\n        expect(screen.getAllByRole('option')[8]).toHaveFocus();\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToEnd });\n        expect(screen.getAllByRole('option')[10]).toHaveFocus();\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToEnd });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToEnd });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToEnd });\n        expect(screen.getAllByRole('option')[13]).toHaveFocus();\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToEnd });\n        expect(screen.getAllByRole('option')[15]).toHaveFocus();\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToEnd });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToEnd });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToEnd });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToEnd });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToEnd });\n        expect(screen.getAllByRole('option')[20]).toHaveFocus();\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToEnd });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToEnd });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToEnd });\n        expect(screen.getAllByRole('option')[24]).toHaveFocus();\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToEnd });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToEnd });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToEnd });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToEnd });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToEnd });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToEnd });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToEnd });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToEnd });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToEnd });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToEnd });\n        expect(screen.getAllByRole('option')[34]).toHaveFocus();\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToEnd });\n        expect(screen.getAllByRole('option')[36]).toHaveFocus();\n        await flushMicrotasks();\n      });\n\n      it(`focuses previous item using ${arrowToStart} key, skipping disabled items`, async () => {\n        render(<ComplexGrid rtl={rtl} />);\n        fireEvent.keyDown(screen.getByRole('button'), { key: 'Enter' });\n        fireEvent.click(screen.getByRole('button'));\n\n        act(() => screen.getAllByRole('option')[36].focus());\n\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToStart });\n        await waitFor(() => {\n          expect(screen.getAllByRole('option')[34]).toHaveFocus();\n        });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToStart });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToStart });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToStart });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToStart });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToStart });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToStart });\n        await waitFor(() => {\n          expect(screen.getAllByRole('option')[28]).toHaveFocus();\n        });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToStart });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToStart });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToStart });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToStart });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToStart });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToStart });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToStart });\n        await waitFor(() => {\n          expect(screen.getAllByRole('option')[20]).toHaveFocus();\n        });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToStart });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToStart });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToStart });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToStart });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToStart });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToStart });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToStart });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToStart });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToStart });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToStart });\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToStart });\n        await waitFor(() => {\n          expect(screen.getAllByRole('option')[7]).toHaveFocus();\n        });\n      });\n\n      it('looping works on last row', async () => {\n        render(<ComplexGrid rtl={rtl} orientation=\"both\" loopFocus />);\n        fireEvent.keyDown(screen.getByRole('button'), { key: 'Enter' });\n        fireEvent.click(screen.getByRole('button'));\n\n        act(() => screen.getAllByRole('option')[36].focus());\n\n        fireEvent.keyDown(screen.getByTestId('floating'), { key: arrowToEnd });\n        expect(screen.getAllByRole('option')[36]).toHaveFocus();\n        await flushMicrotasks();\n      });\n    });\n  });\n\n  it('grid navigation with changing list items', async () => {\n    render(<EmojiPicker />);\n\n    fireEvent.click(screen.getByRole('button'));\n\n    await flushMicrotasks();\n\n    const input = screen.getByRole('textbox');\n    const activeIndicator = screen.getByTestId('emoji-picker-active-index');\n    await waitFor(() => {\n      expect(input).toHaveFocus();\n    });\n\n    await userEvent.keyboard('appl');\n    const initialActiveIndex = activeIndicator.getAttribute('data-active-index');\n    await userEvent.keyboard('{ArrowDown}');\n\n    await waitFor(() => {\n      expect(activeIndicator.getAttribute('data-active-index')).not.toBe(initialActiveIndex);\n    });\n\n    await userEvent.keyboard('{ArrowDown}');\n\n    await waitFor(() => {\n      expect(activeIndicator.getAttribute('data-active-index')).not.toBe(initialActiveIndex);\n    });\n\n    expect(activeIndicator.getAttribute('data-active-index')).not.toBeNull();\n  });\n\n  it('grid navigation with disabled list items', async () => {\n    const { unmount } = render(<EmojiPicker />);\n\n    fireEvent.click(screen.getByRole('button'));\n\n    await flushMicrotasks();\n\n    const input = screen.getByRole('textbox');\n    const activeIndicator = screen.getByTestId('emoji-picker-active-index');\n    await waitFor(() => {\n      expect(input).toHaveFocus();\n    });\n\n    await userEvent.keyboard('o');\n    const initialActiveIndex = activeIndicator.getAttribute('data-active-index');\n    await userEvent.keyboard('{ArrowDown}');\n\n    expect(screen.getByLabelText('orange')).not.toHaveAttribute('data-active');\n    await waitFor(() => {\n      expect(activeIndicator.getAttribute('data-active-index')).not.toBe(initialActiveIndex);\n    });\n\n    await userEvent.keyboard('{ArrowDown}');\n\n    await waitFor(() => {\n      expect(activeIndicator.getAttribute('data-active-index')).not.toBe(initialActiveIndex);\n    });\n\n    expect(activeIndicator.getAttribute('data-active-index')).not.toBeNull();\n\n    unmount();\n\n    render(<EmojiPicker />);\n\n    fireEvent.click(screen.getByRole('button'));\n\n    await flushMicrotasks();\n\n    const nextInput = screen.getByRole('textbox');\n    const nextActiveIndicator = screen.getByTestId('emoji-picker-active-index');\n    await waitFor(() => {\n      expect(nextInput).toHaveFocus();\n    });\n\n    const nextInitialActiveIndex = nextActiveIndicator.getAttribute('data-active-index');\n    await userEvent.keyboard('{ArrowDown}');\n    await userEvent.keyboard('{ArrowDown}');\n    await userEvent.keyboard('{ArrowRight}');\n    await userEvent.keyboard('{ArrowUp}');\n\n    await waitFor(() => {\n      expect(nextActiveIndicator.getAttribute('data-active-index')).not.toBe(\n        nextInitialActiveIndex,\n      );\n    });\n    expect(screen.getByLabelText('cherry')).toHaveAttribute('data-active');\n  });\n\n  it('selectedIndex changing does not steal focus', async () => {\n    render(<ListboxFocus />);\n\n    // TODO: This feels like a bug. It's the animation frame callback from `enqueueFocus` sometimes\n    // kicking in after the click instead before, which causes flakeyness in this test as the wrong\n    // element will be focused.\n    await waitFor(() => {\n      expect(document.activeElement).toHaveRole('option');\n    });\n\n    await userEvent.click(screen.getByTestId('reference'));\n\n    await waitFor(() => {\n      expect(screen.getByTestId('reference')).toHaveFocus();\n    });\n  });\n\n  // In JSDOM it will not focus the first item, but will in the browser\n  it.skipIf(!isJSDOM)('focus management in nested lists', async () => {\n    render(<NestedMenu />);\n    await userEvent.click(screen.getByRole('button', { name: 'Edit' }));\n    await userEvent.keyboard('{ArrowDown}');\n    await userEvent.keyboard('{ArrowDown}');\n    await userEvent.keyboard('{ArrowDown}');\n    await userEvent.keyboard('{ArrowRight}');\n\n    expect(screen.getByText('Text')).toHaveFocus();\n  });\n\n  // In JSDOM it will not focus the first item, but will in the browser\n  it.skipIf(!isJSDOM)('keyboard navigation in nested menus lists', async () => {\n    render(<NestedMenu />);\n\n    await userEvent.click(screen.getByRole('button', { name: 'Edit' }));\n    await flushMicrotasks();\n    await userEvent.keyboard('{ArrowDown}');\n    await userEvent.keyboard('{ArrowDown}');\n    await userEvent.keyboard('{ArrowDown}');\n    await userEvent.keyboard('{ArrowRight}'); // opens first submenu\n    await flushMicrotasks();\n\n    await userEvent.keyboard('{ArrowDown}');\n    await userEvent.keyboard('{ArrowDown}');\n    await userEvent.keyboard('{ArrowRight}'); // opens second submenu\n    await flushMicrotasks();\n\n    expect(screen.getByText('.png')).toHaveFocus();\n\n    // it navigation with orientation = 'both'\n    await userEvent.keyboard('{ArrowRight}');\n    expect(screen.getByText('.jpg')).toHaveFocus();\n\n    await userEvent.keyboard('{ArrowDown}');\n    expect(screen.getByText('.gif')).toHaveFocus();\n\n    await userEvent.keyboard('{ArrowLeft}');\n    expect(screen.getByText('.svg')).toHaveFocus();\n\n    await userEvent.keyboard('{ArrowUp}');\n    expect(screen.getByText('.png')).toHaveFocus();\n\n    // escape closes the submenu\n    await userEvent.keyboard('{Escape}');\n    expect(screen.getByText('Image')).toHaveFocus();\n  });\n\n  // In JSDOM it will not focus the first item, but will in the browser\n  it.skipIf(!isJSDOM)(\n    'keyboard navigation in nested menus with different orientation',\n    async () => {\n      render(<HorizontalMenu />);\n\n      await userEvent.click(screen.getByRole('button', { name: 'Edit' }));\n      await act(async () => {});\n      await userEvent.keyboard('{ArrowRight}');\n      await userEvent.keyboard('{ArrowRight}');\n      await userEvent.keyboard('{ArrowRight}');\n      await userEvent.keyboard('{ArrowDown}'); // opens the Copy as submenu\n      await act(async () => {});\n\n      await userEvent.keyboard('{ArrowRight}');\n      await userEvent.keyboard('{ArrowDown}'); // opens the Share submenu\n      await act(async () => {});\n\n      expect(screen.getByText('Mail')).toHaveFocus();\n\n      await userEvent.keyboard('{ArrowLeft}');\n      expect(screen.getByText('Copy as')).toHaveFocus();\n    },\n  );\n\n  it('Home or End key press is ignored for typeable combobox reference', async () => {\n    // eslint-disable-next-line @typescript-eslint/no-shadow\n    function App() {\n      const [open, setOpen] = React.useState(false);\n      const listRef = React.useRef<Array<HTMLLIElement | null>>([]);\n      const [activeIndex, setActiveIndex] = React.useState<null | number>(null);\n      const { refs, context } = useFloating({\n        open,\n        onOpenChange: setOpen,\n      });\n      const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions([\n        useClick(context),\n        useListNavigation(context, {\n          listRef,\n          activeIndex,\n          onNavigate: setActiveIndex,\n        }),\n      ]);\n\n      return (\n        /* eslint-disable jsx-a11y/role-has-required-aria-props */\n        <React.Fragment>\n          <input role=\"combobox\" ref={refs.setReference} {...getReferenceProps()} />\n          {open && (\n            <div role=\"menu\" {...getFloatingProps({ ref: refs.setFloating })}>\n              <ul>\n                {['one', 'two', 'three'].map((string, index) => (\n                  // eslint-disable-next-line jsx-a11y/role-supports-aria-props\n                  <li\n                    data-testid={`item-${index}`}\n                    aria-selected={activeIndex === index}\n                    key={string}\n                    tabIndex={-1}\n                    {...getItemProps({\n                      ref(node: HTMLLIElement) {\n                        listRef.current[index] = node;\n                      },\n                    })}\n                  >\n                    {string}\n                  </li>\n                ))}\n              </ul>\n            </div>\n          )}\n        </React.Fragment>\n      );\n    }\n\n    render(<App />);\n\n    await act(async () => {\n      screen.getByRole('combobox').focus();\n    });\n\n    await userEvent.keyboard('{ArrowDown}');\n\n    await waitFor(() => {\n      expect(screen.getByTestId('item-0')).toHaveFocus();\n    });\n\n    await userEvent.keyboard('{End}');\n\n    expect(screen.getByTestId('item-0')).toHaveFocus();\n\n    await userEvent.keyboard('{ArrowDown}');\n    await userEvent.keyboard('{Home}');\n\n    await waitFor(() => {\n      expect(screen.getByTestId('item-1')).toHaveFocus();\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/hooks/useListNavigation.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { isHTMLElement } from '@floating-ui/utils/dom';\nimport { useValueAsRef } from '@base-ui/utils/useValueAsRef';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { ownerDocument } from '@base-ui/utils/owner';\nimport {\n  activeElement,\n  contains,\n  getTarget,\n  isTypeableCombobox,\n  isVirtualClick,\n  isVirtualPointerEvent,\n  stopEvent,\n  getFloatingFocusElement,\n  isIndexOutOfListBounds,\n  getMinListIndex,\n  getMaxListIndex,\n  getGridNavigatedIndex,\n  isListIndexDisabled,\n  createGridCellMap,\n  getGridCellIndices,\n  getGridCellIndexOfCorner,\n  findNonDisabledListIndex,\n} from '../utils';\nimport { useFloatingParentNodeId, useFloatingTree } from '../components/FloatingTree';\nimport { FloatingTreeStore } from '../components/FloatingTreeStore';\nimport type { ElementProps, FloatingContext, FloatingRootContext } from '../types';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport { enqueueFocus } from '../utils/enqueueFocus';\nimport { ARROW_UP, ARROW_DOWN, ARROW_RIGHT, ARROW_LEFT } from '../utils/constants';\n\nexport const ESCAPE = 'Escape';\n\nfunction doSwitch(\n  orientation: UseListNavigationProps['orientation'],\n  vertical: boolean,\n  horizontal: boolean,\n) {\n  switch (orientation) {\n    case 'vertical':\n      return vertical;\n    case 'horizontal':\n      return horizontal;\n    default:\n      return vertical || horizontal;\n  }\n}\n\nfunction isMainOrientationKey(key: string, orientation: UseListNavigationProps['orientation']) {\n  const vertical = key === ARROW_UP || key === ARROW_DOWN;\n  const horizontal = key === ARROW_LEFT || key === ARROW_RIGHT;\n  return doSwitch(orientation, vertical, horizontal);\n}\n\nfunction isMainOrientationToEndKey(\n  key: string,\n  orientation: UseListNavigationProps['orientation'],\n  rtl: boolean,\n) {\n  const vertical = key === ARROW_DOWN;\n  const horizontal = rtl ? key === ARROW_LEFT : key === ARROW_RIGHT;\n  return (\n    doSwitch(orientation, vertical, horizontal) || key === 'Enter' || key === ' ' || key === ''\n  );\n}\n\nfunction isCrossOrientationOpenKey(\n  key: string,\n  orientation: UseListNavigationProps['orientation'],\n  rtl: boolean,\n) {\n  const vertical = rtl ? key === ARROW_LEFT : key === ARROW_RIGHT;\n  const horizontal = key === ARROW_DOWN;\n  return doSwitch(orientation, vertical, horizontal);\n}\n\nfunction isCrossOrientationCloseKey(\n  key: string,\n  orientation: UseListNavigationProps['orientation'],\n  rtl: boolean,\n  cols?: number,\n) {\n  const vertical = rtl ? key === ARROW_RIGHT : key === ARROW_LEFT;\n  const horizontal = key === ARROW_UP;\n  if (orientation === 'both' || (orientation === 'horizontal' && cols && cols > 1)) {\n    return key === ESCAPE;\n  }\n  return doSwitch(orientation, vertical, horizontal);\n}\n\nexport interface UseListNavigationProps {\n  /**\n   * A ref that holds an array of list items.\n   * @default empty list\n   */\n  listRef: React.RefObject<Array<HTMLElement | null>>;\n  /**\n   * The index of the currently active (focused or highlighted) item, which may\n   * or may not be selected.\n   * @default null\n   */\n  activeIndex: number | null;\n  /**\n   * A callback that is called when the user navigates to a new active item,\n   * passed in a new `activeIndex`.\n   */\n  onNavigate?:\n    | ((activeIndex: number | null, event: React.SyntheticEvent | undefined) => void)\n    | undefined;\n  /**\n   * Whether the Hook is enabled, including all internal Effects and event\n   * handlers.\n   * @default true\n   */\n  enabled?: boolean | undefined;\n  /**\n   * The currently selected item index, which may or may not be active.\n   * @default null\n   */\n  selectedIndex?: number | null | undefined;\n  /**\n   * Whether to focus the item upon opening the floating element. 'auto' infers\n   * what to do based on the input type (keyboard vs. pointer), while a boolean\n   * value will force the value.\n   * @default 'auto'\n   */\n  focusItemOnOpen?: boolean | 'auto' | undefined;\n  /**\n   * Whether hovering an item synchronizes the focus.\n   * @default true\n   */\n  focusItemOnHover?: boolean | undefined;\n  /**\n   * Whether pressing an arrow key on the navigation’s main axis opens the\n   * floating element.\n   * @default true\n   */\n  openOnArrowKeyDown?: boolean | undefined;\n  /**\n   * By default elements with either a `disabled` or `aria-disabled` attribute\n   * are skipped in the list navigation — however, this requires the items to\n   * be rendered.\n   * This prop allows you to manually specify indices which should be disabled,\n   * overriding the default logic.\n   * For Windows-style select popups, where the menu does not open when\n   * navigating via arrow keys, specify an empty array.\n   * @default undefined\n   */\n  disabledIndices?: ReadonlyArray<number> | ((index: number) => boolean) | undefined;\n  /**\n   * Determines whether focus can escape the list, such that nothing is selected\n   * after navigating beyond the boundary of the list. In some\n   * autocomplete/combobox components, this may be desired, as screen\n   * readers will return to the input.\n   * `loopFocus` must be `true`.\n   * @default false\n   */\n  allowEscape?: boolean | undefined;\n  /**\n   * Determines whether focus should loop around when navigating past the first\n   * or last item.\n   * @default false\n   */\n  loopFocus?: boolean | undefined;\n  /**\n   * If the list is nested within another one (e.g. a nested submenu), the\n   * navigation semantics change.\n   * @default false\n   */\n  nested?: boolean | undefined;\n  /**\n   * Allows to specify the orientation of the parent list, which is used to\n   * determine the direction of the navigation.\n   * This is useful when list navigation is used within a Composite,\n   * as the hook can't determine the orientation of the parent list automatically.\n   */\n  parentOrientation?: UseListNavigationProps['orientation'] | undefined;\n  /**\n   * Whether the direction of the floating element’s navigation is in RTL\n   * layout.\n   * @default false\n   */\n  rtl?: boolean | undefined;\n  /**\n   * Whether the focus is virtual (using `aria-activedescendant`).\n   * Use this if you need focus to remain on the reference element\n   * (such as an input), but allow arrow keys to navigate list items.\n   * This is common in autocomplete listbox components.\n   * Your virtually-focused list items must have a unique `id` set on them.\n   * If you’re using a component role with the `useRole()` Hook, then an `id` is\n   * generated automatically.\n   * @default false\n   */\n  virtual?: boolean | undefined;\n  /**\n   * The orientation in which navigation occurs.\n   * @default 'vertical'\n   */\n  orientation?: 'vertical' | 'horizontal' | 'both' | undefined;\n  /**\n   * Specifies how many columns the list has (i.e., it’s a grid). Use an\n   * orientation of 'horizontal' (e.g. for an emoji picker/date picker, where\n   * pressing ArrowRight or ArrowLeft can change rows), or 'both' (where the\n   * current row cannot be escaped with ArrowRight or ArrowLeft, only ArrowUp\n   * and ArrowDown).\n   * @default 1\n   */\n  cols?: number | undefined;\n  /**\n   * The id of the root component.\n   */\n  id?: string | undefined;\n  /**\n   * Whether to clear the active index when the pointer leaves an item.\n   * @default true\n   */\n  resetOnPointerLeave?: boolean | undefined;\n  /**\n   * External FlatingTree to use when the one provided by context can't be used.\n   */\n  externalTree?: FloatingTreeStore | undefined;\n}\n\n/**\n * Adds arrow key-based navigation of a list of items, either using real DOM\n * focus or virtual focus.\n * @see https://floating-ui.com/docs/useListNavigation\n */\nexport function useListNavigation(\n  context: FloatingRootContext | FloatingContext,\n  props: UseListNavigationProps,\n): ElementProps {\n  const store = 'rootStore' in context ? context.rootStore : context;\n  const open = store.useState('open');\n  const floatingElement = store.useState('floatingElement');\n  const domReferenceElement = store.useState('domReferenceElement');\n  const dataRef = store.context.dataRef;\n\n  const {\n    listRef,\n    activeIndex,\n    onNavigate: onNavigateProp = () => {},\n    enabled = true,\n    selectedIndex = null,\n    allowEscape = false,\n    loopFocus = false,\n    nested = false,\n    rtl = false,\n    virtual = false,\n    focusItemOnOpen = 'auto',\n    focusItemOnHover = true,\n    openOnArrowKeyDown = true,\n    disabledIndices = undefined,\n    orientation = 'vertical',\n    parentOrientation,\n    cols = 1,\n    id,\n    resetOnPointerLeave = true,\n    externalTree,\n  } = props;\n\n  if (process.env.NODE_ENV !== 'production') {\n    if (allowEscape) {\n      if (!loopFocus) {\n        console.warn('`useListNavigation` looping must be enabled to allow escaping.');\n      }\n\n      if (!virtual) {\n        console.warn('`useListNavigation` must be virtual to allow escaping.');\n      }\n    }\n\n    if (orientation === 'vertical' && cols > 1) {\n      console.warn(\n        'In grid list navigation mode (`cols` > 1), the `orientation` should',\n        'be either \"horizontal\" or \"both\".',\n      );\n    }\n  }\n\n  const floatingFocusElement = getFloatingFocusElement(floatingElement);\n  const floatingFocusElementRef = useValueAsRef(floatingFocusElement);\n\n  const parentId = useFloatingParentNodeId();\n  const tree = useFloatingTree(externalTree);\n\n  useIsoLayoutEffect(() => {\n    dataRef.current.orientation = orientation;\n  }, [dataRef, orientation]);\n\n  const typeableComboboxReference = isTypeableCombobox(domReferenceElement);\n\n  const focusItemOnOpenRef = React.useRef(focusItemOnOpen);\n  const indexRef = React.useRef(selectedIndex ?? -1);\n  const keyRef = React.useRef<null | string>(null);\n  const isPointerModalityRef = React.useRef(true);\n\n  const onNavigate = useStableCallback((event?: React.SyntheticEvent) => {\n    onNavigateProp(indexRef.current === -1 ? null : indexRef.current, event);\n  });\n\n  const previousOnNavigateRef = React.useRef(onNavigate);\n  const previousMountedRef = React.useRef(!!floatingElement);\n  const previousOpenRef = React.useRef(open);\n  const forceSyncFocusRef = React.useRef(false);\n  const forceScrollIntoViewRef = React.useRef(false);\n\n  const disabledIndicesRef = useValueAsRef(disabledIndices);\n  const latestOpenRef = useValueAsRef(open);\n  const selectedIndexRef = useValueAsRef(selectedIndex);\n  const resetOnPointerLeaveRef = useValueAsRef(resetOnPointerLeave);\n\n  const focusItem = useStableCallback(() => {\n    function runFocus(item: HTMLElement) {\n      if (virtual) {\n        tree?.events.emit('virtualfocus', item);\n      } else {\n        enqueueFocus(item, {\n          sync: forceSyncFocusRef.current,\n          preventScroll: true,\n        });\n      }\n    }\n\n    const initialItem = listRef.current[indexRef.current];\n    const forceScrollIntoView = forceScrollIntoViewRef.current;\n\n    if (initialItem) {\n      runFocus(initialItem);\n    }\n\n    const scheduler = forceSyncFocusRef.current ? (v: () => void) => v() : requestAnimationFrame;\n\n    scheduler(() => {\n      const waitedItem = listRef.current[indexRef.current] || initialItem;\n\n      if (!waitedItem) {\n        return;\n      }\n\n      if (!initialItem) {\n        runFocus(waitedItem);\n      }\n\n      const shouldScrollIntoView =\n        // eslint-disable-next-line @typescript-eslint/no-use-before-define\n        item && (forceScrollIntoView || !isPointerModalityRef.current);\n\n      if (shouldScrollIntoView) {\n        // JSDOM doesn't support `.scrollIntoView()` but it's widely supported\n        // by all browsers.\n        waitedItem.scrollIntoView?.({ block: 'nearest', inline: 'nearest' });\n      }\n    });\n  });\n\n  // Sync `selectedIndex` to be the `activeIndex` upon opening the floating\n  // element. Also, reset `activeIndex` upon closing the floating element.\n  useIsoLayoutEffect(() => {\n    if (!enabled) {\n      return;\n    }\n\n    if (open && floatingElement) {\n      indexRef.current = selectedIndex ?? -1;\n      if (focusItemOnOpenRef.current && selectedIndex != null) {\n        // Regardless of the pointer modality, we want to ensure the selected\n        // item comes into view when the floating element is opened.\n        forceScrollIntoViewRef.current = true;\n        onNavigate();\n      }\n    } else if (previousMountedRef.current) {\n      // Since the user can specify `onNavigate` conditionally\n      // (onNavigate: open ? setActiveIndex : setSelectedIndex),\n      // we store and call the previous function.\n      indexRef.current = -1;\n      previousOnNavigateRef.current();\n    }\n  }, [enabled, open, floatingElement, selectedIndex, onNavigate]);\n\n  // Sync `activeIndex` to be the focused item while the floating element is\n  // open.\n  useIsoLayoutEffect(() => {\n    if (!enabled) {\n      return;\n    }\n    if (!open) {\n      forceSyncFocusRef.current = false;\n      return;\n    }\n    if (!floatingElement) {\n      return;\n    }\n\n    if (activeIndex == null) {\n      forceSyncFocusRef.current = false;\n\n      if (selectedIndexRef.current != null) {\n        return;\n      }\n\n      // Reset while the floating element was open (e.g. the list changed).\n      if (previousMountedRef.current) {\n        indexRef.current = -1;\n        focusItem();\n      }\n\n      // Initial sync.\n      if (\n        (!previousOpenRef.current || !previousMountedRef.current) &&\n        focusItemOnOpenRef.current &&\n        (keyRef.current != null || (focusItemOnOpenRef.current === true && keyRef.current == null))\n      ) {\n        let runs = 0;\n        const waitForListPopulated = () => {\n          if (listRef.current[0] == null) {\n            // Avoid letting the browser paint if possible on the first try,\n            // otherwise use rAF. Don't try more than twice, since something\n            // is wrong otherwise.\n            if (runs < 2) {\n              const scheduler = runs ? requestAnimationFrame : queueMicrotask;\n              scheduler(waitForListPopulated);\n            }\n            runs += 1;\n          } else {\n            // initially focus the first non-disabled item\n            indexRef.current =\n              keyRef.current == null ||\n              isMainOrientationToEndKey(keyRef.current, orientation, rtl) ||\n              nested\n                ? getMinListIndex(listRef)\n                : getMaxListIndex(listRef);\n            keyRef.current = null;\n            onNavigate();\n          }\n        };\n\n        waitForListPopulated();\n      }\n    } else if (!isIndexOutOfListBounds(listRef.current, activeIndex)) {\n      indexRef.current = activeIndex;\n      focusItem();\n      forceScrollIntoViewRef.current = false;\n    }\n  }, [\n    enabled,\n    open,\n    floatingElement,\n    activeIndex,\n    selectedIndexRef,\n    nested,\n    listRef,\n    orientation,\n    rtl,\n    onNavigate,\n    focusItem,\n    disabledIndicesRef,\n  ]);\n\n  // Ensure the parent floating element has focus when a nested child closes\n  // to allow arrow key navigation to work after the pointer leaves the child.\n  useIsoLayoutEffect(() => {\n    if (!enabled || floatingElement || !tree || virtual || !previousMountedRef.current) {\n      return;\n    }\n\n    const nodes = tree.nodesRef.current;\n    const parent = nodes.find((node) => node.id === parentId)?.context?.elements.floating;\n    const activeEl = activeElement(ownerDocument(floatingElement));\n    const treeContainsActiveEl = nodes.some(\n      (node) => node.context && contains(node.context.elements.floating, activeEl),\n    );\n\n    if (parent && !treeContainsActiveEl && isPointerModalityRef.current) {\n      parent.focus({ preventScroll: true });\n    }\n  }, [enabled, floatingElement, tree, parentId, virtual]);\n\n  useIsoLayoutEffect(() => {\n    previousOnNavigateRef.current = onNavigate;\n    previousOpenRef.current = open;\n    previousMountedRef.current = !!floatingElement;\n  });\n\n  useIsoLayoutEffect(() => {\n    if (!open) {\n      keyRef.current = null;\n      focusItemOnOpenRef.current = focusItemOnOpen;\n    }\n  }, [open, focusItemOnOpen]);\n\n  const hasActiveIndex = activeIndex != null;\n\n  const item = React.useMemo(() => {\n    function syncCurrentTarget(event: React.SyntheticEvent<any>) {\n      if (!latestOpenRef.current) {\n        return;\n      }\n      const index = listRef.current.indexOf(event.currentTarget);\n      if (index !== -1 && indexRef.current !== index) {\n        indexRef.current = index;\n        onNavigate(event);\n      }\n    }\n\n    const itemProps: ElementProps['item'] = {\n      onFocus(event) {\n        forceSyncFocusRef.current = true;\n        syncCurrentTarget(event);\n      },\n      onClick: ({ currentTarget }) => currentTarget.focus({ preventScroll: true }), // Safari\n      onMouseMove(event) {\n        forceSyncFocusRef.current = true;\n        forceScrollIntoViewRef.current = false;\n        if (focusItemOnHover) {\n          syncCurrentTarget(event);\n        }\n      },\n      onPointerLeave(event) {\n        if (\n          !latestOpenRef.current ||\n          !isPointerModalityRef.current ||\n          event.pointerType === 'touch'\n        ) {\n          return;\n        }\n\n        forceSyncFocusRef.current = true;\n\n        const relatedTarget = event.relatedTarget as HTMLElement | null;\n\n        if (!focusItemOnHover || listRef.current.includes(relatedTarget)) {\n          return;\n        }\n\n        if (!resetOnPointerLeaveRef.current) {\n          return;\n        }\n\n        enqueueFocus(null, { sync: true });\n\n        indexRef.current = -1;\n        onNavigate(event);\n\n        if (!virtual) {\n          const floatingFocusEl = floatingFocusElementRef.current;\n          const activeEl = activeElement(ownerDocument(floatingFocusEl));\n          if (floatingFocusEl && contains(floatingFocusEl, activeEl)) {\n            floatingFocusEl.focus({ preventScroll: true });\n          }\n        }\n      },\n    };\n\n    return itemProps;\n  }, [\n    latestOpenRef,\n    floatingFocusElementRef,\n    focusItemOnHover,\n    listRef,\n    onNavigate,\n    resetOnPointerLeaveRef,\n    virtual,\n  ]);\n\n  const getParentOrientation = React.useCallback(() => {\n    return (\n      parentOrientation ??\n      (tree?.nodesRef.current.find((node) => node.id === parentId)?.context?.dataRef?.current\n        .orientation as UseListNavigationProps['orientation'])\n    );\n  }, [parentId, tree, parentOrientation]);\n\n  const commonOnKeyDown = useStableCallback((event: React.KeyboardEvent) => {\n    isPointerModalityRef.current = false;\n    forceSyncFocusRef.current = true;\n\n    // When composing a character, Chrome fires ArrowDown twice. Firefox/Safari\n    // don't appear to suffer from this. `event.isComposing` is avoided due to\n    // Safari not supporting it properly (although it's not needed in the first\n    // place for Safari, just avoiding any possible issues).\n    if (event.which === 229) {\n      return;\n    }\n\n    // If the floating element is animating out, ignore navigation. Otherwise,\n    // the `activeIndex` gets set to 0 despite not being open so the next time\n    // the user ArrowDowns, the first item won't be focused.\n    if (!latestOpenRef.current && event.currentTarget === floatingFocusElementRef.current) {\n      return;\n    }\n\n    if (nested && isCrossOrientationCloseKey(event.key, orientation, rtl, cols)) {\n      // If the nested list's close key is also the parent navigation key,\n      // let the parent navigate. Otherwise, stop propagating the event.\n      if (!isMainOrientationKey(event.key, getParentOrientation())) {\n        stopEvent(event);\n      }\n\n      store.setOpen(false, createChangeEventDetails(REASONS.listNavigation, event.nativeEvent));\n\n      if (isHTMLElement(domReferenceElement)) {\n        if (virtual) {\n          tree?.events.emit('virtualfocus', domReferenceElement);\n        } else {\n          domReferenceElement.focus();\n        }\n      }\n\n      return;\n    }\n\n    const currentIndex = indexRef.current;\n    const minIndex = getMinListIndex(listRef, disabledIndices);\n    const maxIndex = getMaxListIndex(listRef, disabledIndices);\n\n    if (!typeableComboboxReference) {\n      if (event.key === 'Home') {\n        stopEvent(event);\n        indexRef.current = minIndex;\n        onNavigate(event);\n      }\n\n      if (event.key === 'End') {\n        stopEvent(event);\n        indexRef.current = maxIndex;\n        onNavigate(event);\n      }\n    }\n\n    // Grid navigation.\n    if (cols > 1) {\n      const sizes = Array.from({ length: listRef.current.length }, () => ({\n        width: 1,\n        height: 1,\n      }));\n      // To calculate movements on the grid, we use hypothetical cell indices\n      // as if every item was 1x1, then convert back to real indices.\n      const cellMap = createGridCellMap(sizes, cols, false);\n      const minGridIndex = cellMap.findIndex(\n        (index) => index != null && !isListIndexDisabled(listRef.current, index, disabledIndices),\n      );\n      // last enabled index\n      const maxGridIndex = cellMap.reduce(\n        (foundIndex: number, index, cellIndex) =>\n          index != null && !isListIndexDisabled(listRef.current, index, disabledIndices)\n            ? cellIndex\n            : foundIndex,\n        -1,\n      );\n\n      const index =\n        cellMap[\n          getGridNavigatedIndex(\n            cellMap.map((itemIndex) => (itemIndex != null ? listRef.current[itemIndex] : null)),\n            {\n              event,\n              orientation,\n              loopFocus,\n              rtl,\n              cols,\n              // treat undefined (empty grid spaces) as disabled indices so we\n              // don't end up in them\n              disabledIndices: getGridCellIndices(\n                [\n                  ...((typeof disabledIndices !== 'function' ? disabledIndices : null) ||\n                    listRef.current.map((_, listIndex) =>\n                      isListIndexDisabled(listRef.current, listIndex, disabledIndices)\n                        ? listIndex\n                        : undefined,\n                    )),\n                  undefined,\n                ],\n                cellMap,\n              ),\n              minIndex: minGridIndex,\n              maxIndex: maxGridIndex,\n              prevIndex: getGridCellIndexOfCorner(\n                indexRef.current > maxIndex ? minIndex : indexRef.current,\n                sizes,\n                cellMap,\n                cols,\n                // use a corner matching the edge closest to the direction\n                // we're moving in so we don't end up in the same item. Prefer\n                // top/left over bottom/right.\n                // eslint-disable-next-line no-nested-ternary\n                event.key === ARROW_DOWN\n                  ? 'bl'\n                  : event.key === (rtl ? ARROW_LEFT : ARROW_RIGHT)\n                    ? 'tr'\n                    : 'tl',\n              ),\n              stopEvent: true,\n            },\n          )\n        ];\n\n      if (index != null) {\n        indexRef.current = index;\n        onNavigate(event);\n      }\n\n      if (orientation === 'both') {\n        return;\n      }\n    }\n\n    if (isMainOrientationKey(event.key, orientation)) {\n      stopEvent(event);\n\n      // Reset the index if no item is focused.\n      if (\n        open &&\n        !virtual &&\n        activeElement(event.currentTarget.ownerDocument) === event.currentTarget\n      ) {\n        indexRef.current = isMainOrientationToEndKey(event.key, orientation, rtl)\n          ? minIndex\n          : maxIndex;\n        onNavigate(event);\n        return;\n      }\n\n      if (isMainOrientationToEndKey(event.key, orientation, rtl)) {\n        if (loopFocus) {\n          if (currentIndex >= maxIndex) {\n            if (allowEscape && currentIndex !== listRef.current.length) {\n              indexRef.current = -1;\n            } else {\n              // Give time for virtualizers to update the listRef.\n              forceSyncFocusRef.current = false;\n              indexRef.current = minIndex;\n            }\n          } else {\n            indexRef.current = findNonDisabledListIndex(listRef.current, {\n              startingIndex: currentIndex,\n              disabledIndices,\n            });\n          }\n        } else {\n          indexRef.current = Math.min(\n            maxIndex,\n            findNonDisabledListIndex(listRef.current, {\n              startingIndex: currentIndex,\n              disabledIndices,\n            }),\n          );\n        }\n      } else if (loopFocus) {\n        if (currentIndex <= minIndex) {\n          if (allowEscape && currentIndex !== -1) {\n            indexRef.current = listRef.current.length;\n          } else {\n            // Give time for virtualizers to update the listRef.\n            forceSyncFocusRef.current = false;\n            indexRef.current = maxIndex;\n          }\n        } else {\n          indexRef.current = findNonDisabledListIndex(listRef.current, {\n            startingIndex: currentIndex,\n            decrement: true,\n            disabledIndices,\n          });\n        }\n      } else {\n        indexRef.current = Math.max(\n          minIndex,\n          findNonDisabledListIndex(listRef.current, {\n            startingIndex: currentIndex,\n            decrement: true,\n            disabledIndices,\n          }),\n        );\n      }\n\n      if (isIndexOutOfListBounds(listRef.current, indexRef.current)) {\n        indexRef.current = -1;\n      }\n\n      onNavigate(event);\n    }\n  });\n\n  const ariaActiveDescendantProp = React.useMemo(() => {\n    return (\n      virtual &&\n      open &&\n      hasActiveIndex && {\n        'aria-activedescendant': `${id}-${activeIndex}`,\n      }\n    );\n  }, [virtual, open, hasActiveIndex, id, activeIndex]);\n\n  const floating: ElementProps['floating'] = React.useMemo(() => {\n    return {\n      'aria-orientation': orientation === 'both' ? undefined : orientation,\n      ...(!typeableComboboxReference ? ariaActiveDescendantProp : {}),\n      onKeyDown(event: React.KeyboardEvent) {\n        // Close submenu on Shift+Tab\n        if (event.key === 'Tab' && event.shiftKey && open && !virtual) {\n          // If the event originated from within a nested element (e.g., a Dialog opened from\n          // within the menu), don't close the menu. The nested element has its own focus\n          // management and should handle the Tab key.\n          const target = getTarget(event.nativeEvent) as Element | null;\n          if (target && !contains(floatingFocusElementRef.current, target)) {\n            return;\n          }\n\n          stopEvent(event);\n          store.setOpen(false, createChangeEventDetails(REASONS.focusOut, event.nativeEvent));\n\n          if (isHTMLElement(domReferenceElement)) {\n            domReferenceElement.focus();\n          }\n\n          return;\n        }\n\n        commonOnKeyDown(event);\n      },\n      onPointerMove() {\n        isPointerModalityRef.current = true;\n      },\n    };\n  }, [\n    ariaActiveDescendantProp,\n    commonOnKeyDown,\n    floatingFocusElementRef,\n    orientation,\n    typeableComboboxReference,\n    store,\n    open,\n    virtual,\n    domReferenceElement,\n  ]);\n\n  const trigger: ElementProps['trigger'] = React.useMemo(() => {\n    function checkVirtualMouse(event: React.PointerEvent) {\n      if (focusItemOnOpen === 'auto' && isVirtualClick(event.nativeEvent)) {\n        focusItemOnOpenRef.current = !virtual;\n      }\n    }\n\n    function checkVirtualPointer(event: React.PointerEvent) {\n      // `pointerdown` fires first, reset the state then perform the checks.\n      focusItemOnOpenRef.current = focusItemOnOpen;\n      if (focusItemOnOpen === 'auto' && isVirtualPointerEvent(event.nativeEvent)) {\n        focusItemOnOpenRef.current = true;\n      }\n    }\n\n    return {\n      onKeyDown(event) {\n        // non-reactive open state (to prevent re-creation of the handler)\n        const currentOpen = store.select('open');\n        isPointerModalityRef.current = false;\n\n        const isArrowKey = event.key.startsWith('Arrow');\n        const isParentCrossOpenKey = isCrossOrientationOpenKey(\n          event.key,\n          getParentOrientation(),\n          rtl,\n        );\n        const isMainKey = isMainOrientationKey(event.key, orientation);\n        const isNavigationKey =\n          (nested ? isParentCrossOpenKey : isMainKey) ||\n          event.key === 'Enter' ||\n          event.key.trim() === '';\n\n        if (virtual && currentOpen) {\n          return commonOnKeyDown(event);\n        }\n\n        // If a floating element should not open on arrow key down, avoid\n        // setting `activeIndex` while it's closed.\n        if (!currentOpen && !openOnArrowKeyDown && isArrowKey) {\n          return undefined;\n        }\n\n        if (isNavigationKey) {\n          const isParentMainKey = isMainOrientationKey(event.key, getParentOrientation());\n          keyRef.current = nested && isParentMainKey ? null : event.key;\n        }\n\n        if (nested) {\n          if (isParentCrossOpenKey) {\n            stopEvent(event);\n\n            if (currentOpen) {\n              indexRef.current = getMinListIndex(listRef, disabledIndicesRef.current);\n              onNavigate(event);\n            } else {\n              store.setOpen(\n                true,\n                createChangeEventDetails(\n                  REASONS.listNavigation,\n                  event.nativeEvent,\n                  event.currentTarget as HTMLElement,\n                ),\n              );\n            }\n          }\n\n          return undefined;\n        }\n\n        if (isMainKey) {\n          if (selectedIndexRef.current != null) {\n            indexRef.current = selectedIndexRef.current;\n          }\n\n          stopEvent(event);\n\n          if (!currentOpen && openOnArrowKeyDown) {\n            store.setOpen(\n              true,\n              createChangeEventDetails(\n                REASONS.listNavigation,\n                event.nativeEvent,\n                event.currentTarget as HTMLElement,\n              ),\n            );\n          } else {\n            commonOnKeyDown(event);\n          }\n\n          if (currentOpen) {\n            onNavigate(event);\n          }\n        }\n\n        return undefined;\n      },\n      onFocus(event) {\n        if (store.select('open') && !virtual) {\n          indexRef.current = -1;\n          onNavigate(event);\n        }\n      },\n      onPointerDown: checkVirtualPointer,\n      onPointerEnter: checkVirtualPointer,\n      onMouseDown: checkVirtualMouse,\n      onClick: checkVirtualMouse,\n    };\n  }, [\n    commonOnKeyDown,\n    disabledIndicesRef,\n    focusItemOnOpen,\n    listRef,\n    nested,\n    onNavigate,\n    store,\n    openOnArrowKeyDown,\n    orientation,\n    getParentOrientation,\n    rtl,\n    selectedIndexRef,\n    virtual,\n  ]);\n\n  const reference: ElementProps['reference'] = React.useMemo(() => {\n    return {\n      ...ariaActiveDescendantProp,\n      ...trigger,\n    };\n  }, [ariaActiveDescendantProp, trigger]);\n\n  return React.useMemo(\n    () => (enabled ? { reference, floating, item, trigger } : {}),\n    [enabled, reference, floating, trigger, item],\n  );\n}\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/hooks/useRole.ts",
    "content": "import * as React from 'react';\nimport { useId } from '@base-ui/utils/useId';\nimport { getFloatingFocusElement } from '../utils';\nimport { useFloatingParentNodeId } from '../components/FloatingTree';\nimport type { ElementProps, FloatingContext, FloatingRootContext } from '../types';\nimport type { ExtendedUserProps } from './useInteractions';\nimport { EMPTY_OBJECT } from '../../utils/constants';\n\ntype AriaRole = 'tooltip' | 'dialog' | 'alertdialog' | 'menu' | 'listbox' | 'grid' | 'tree';\ntype ComponentRole = 'select' | 'label' | 'combobox';\n\nexport interface UseRoleProps {\n  /**\n   * The role of the floating element.\n   * @default 'dialog'\n   */\n  role?: AriaRole | ComponentRole | undefined;\n}\n\nconst componentRoleToAriaRoleMap = new Map<AriaRole | ComponentRole, AriaRole | false>([\n  ['select', 'listbox'],\n  ['combobox', 'listbox'],\n  ['label', false],\n]);\n\n/**\n * Adds base screen reader props to the reference and floating elements for a\n * given floating element `role`.\n * @see https://floating-ui.com/docs/useRole\n */\nexport function useRole(\n  context: FloatingRootContext | FloatingContext,\n  props: UseRoleProps = {},\n): ElementProps {\n  const store = 'rootStore' in context ? context.rootStore : context;\n  const open = store.useState('open');\n  const defaultFloatingId = store.useState('floatingId');\n  const domReference = store.useState('domReferenceElement');\n  const floatingElement = store.useState('floatingElement');\n\n  const { role = 'dialog' } = props;\n\n  const defaultReferenceId = useId();\n  const referenceId = domReference?.id || defaultReferenceId;\n  const floatingId = React.useMemo(\n    () => getFloatingFocusElement(floatingElement)?.id || defaultFloatingId,\n    [floatingElement, defaultFloatingId],\n  );\n\n  const ariaRole = (componentRoleToAriaRoleMap.get(role) ?? role) as AriaRole | false | undefined;\n\n  const parentId = useFloatingParentNodeId();\n  const isNested = parentId != null;\n\n  const trigger: ElementProps['trigger'] = React.useMemo(() => {\n    if (ariaRole === 'tooltip' || role === 'label') {\n      return EMPTY_OBJECT;\n    }\n\n    return {\n      'aria-haspopup': ariaRole === 'alertdialog' ? 'dialog' : ariaRole,\n      'aria-expanded': 'false',\n      ...(ariaRole === 'listbox' && { role: 'combobox' }),\n      ...(ariaRole === 'menu' && isNested && { role: 'menuitem' }),\n      ...(role === 'select' && { 'aria-autocomplete': 'none' }),\n      ...(role === 'combobox' && { 'aria-autocomplete': 'list' }),\n    };\n  }, [ariaRole, isNested, role]);\n\n  const reference: ElementProps['reference'] = React.useMemo(() => {\n    if (ariaRole === 'tooltip' || role === 'label') {\n      return {\n        [`aria-${role === 'label' ? 'labelledby' : 'describedby'}`]: open ? floatingId : undefined,\n      };\n    }\n\n    const triggerProps = trigger;\n    return {\n      ...triggerProps,\n      'aria-expanded': open ? 'true' : 'false',\n      'aria-controls': open ? floatingId : undefined,\n      ...(ariaRole === 'menu' && { id: referenceId }),\n    };\n  }, [ariaRole, floatingId, open, referenceId, role, trigger]);\n\n  const floating: ElementProps['floating'] = React.useMemo(() => {\n    const floatingProps = {\n      id: floatingId,\n      ...(ariaRole && { role: ariaRole }),\n    };\n\n    if (ariaRole === 'tooltip' || role === 'label') {\n      return floatingProps;\n    }\n\n    return {\n      ...floatingProps,\n      ...(ariaRole === 'menu' && {\n        'aria-labelledby': referenceId,\n      }),\n    };\n  }, [ariaRole, floatingId, referenceId, role]);\n\n  const item: ElementProps['item'] = React.useCallback(\n    ({ active, selected }: ExtendedUserProps) => {\n      const commonProps = {\n        role: 'option',\n        ...(active && { id: `${floatingId}-fui-option` }),\n      };\n\n      // For `menu`, we are unable to tell if the item is a `menuitemradio`\n      // or `menuitemcheckbox`. For backwards-compatibility reasons, also\n      // avoid defaulting to `menuitem` as it may overwrite custom role props.\n      switch (role) {\n        case 'select':\n        case 'combobox':\n          return {\n            ...commonProps,\n            'aria-selected': selected,\n          };\n\n        default:\n      }\n\n      return {};\n    },\n    [floatingId, role],\n  );\n\n  return React.useMemo(\n    () => ({ reference, floating, item, trigger }),\n    [reference, floating, trigger, item],\n  );\n}\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/hooks/useSyncedFloatingRootContext.ts",
    "content": "'use client';\nimport { useId } from '@base-ui/utils/useId';\nimport { useRefWithInit } from '@base-ui/utils/useRefWithInit';\nimport { ReactStore } from '@base-ui/utils/store';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { isElement } from '@floating-ui/utils/dom';\nimport { BaseUIChangeEventDetails } from '../../types';\nimport { useFloatingParentNodeId } from '../components/FloatingTree';\nimport { PopupStoreContext, PopupStoreSelectors, PopupStoreState } from '../../utils/popups';\nimport { FloatingRootState, FloatingRootStore } from '../components/FloatingRootStore';\n\nexport interface UseSyncedFloatingRootContextOptions<State extends PopupStoreState<any>> {\n  popupStore: ReactStore<State, PopupStoreContext<any>, PopupStoreSelectors>;\n  /**\n   * Whether to prevent the auto-emitted `openchange` event.\n   */\n  noEmit?: boolean | undefined;\n  /**\n   * Whether the Popup element is passed to Floating UI as the floating element instead of the default Positioner.\n   */\n  treatPopupAsFloatingElement?: boolean | undefined;\n  onOpenChange(open: boolean, eventDetails: BaseUIChangeEventDetails<string>): void;\n}\n\n/**\n * Initializes a FloatingRootStore that is kept in sync with the provided PopupStore.\n * The new instance is created only once and updated on every render.\n */\nexport function useSyncedFloatingRootContext<State extends PopupStoreState<any>>(\n  options: UseSyncedFloatingRootContextOptions<State>,\n): FloatingRootStore {\n  const { popupStore, noEmit = false, treatPopupAsFloatingElement = false, onOpenChange } = options;\n\n  const floatingId = useId();\n  const nested = useFloatingParentNodeId() != null;\n\n  const open = popupStore.useState('open');\n  const referenceElement = popupStore.useState('activeTriggerElement');\n  const floatingElement = popupStore.useState(\n    treatPopupAsFloatingElement ? 'popupElement' : 'positionerElement',\n  );\n  const triggerElements = popupStore.context.triggerElements;\n\n  const store = useRefWithInit(\n    () =>\n      new FloatingRootStore({\n        open,\n        referenceElement,\n        floatingElement,\n        triggerElements,\n        onOpenChange,\n        floatingId,\n        nested,\n        noEmit,\n      }),\n  ).current;\n\n  useIsoLayoutEffect(() => {\n    const valuesToSync: Partial<FloatingRootState> = {\n      open,\n      floatingId,\n      referenceElement,\n      floatingElement,\n    };\n\n    if (isElement(referenceElement)) {\n      valuesToSync.domReferenceElement = referenceElement;\n    }\n\n    if (store.state.positionReference === store.state.referenceElement) {\n      valuesToSync.positionReference = referenceElement;\n    }\n\n    store.update(valuesToSync);\n  }, [open, floatingId, referenceElement, floatingElement, store]);\n\n  // TODO: When `setOpen` is a part of the PopupStore API, we don't need to sync it.\n  store.context.onOpenChange = onOpenChange;\n  store.context.nested = nested;\n  store.context.noEmit = noEmit;\n\n  return store;\n}\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/hooks/useTypeahead.test.tsx",
    "content": "import { vi, expect } from 'vitest';\nimport * as React from 'react';\nimport { act, render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\n\nimport { useClick, useFloating, useInteractions, useTypeahead } from '../index';\nimport type { UseTypeaheadProps } from './useTypeahead';\n\nbeforeEach(() => {\n  vi.useFakeTimers({ shouldAdvanceTime: true });\n});\n\nconst useImpl = ({\n  addUseClick = false,\n  ...props\n}: Pick<UseTypeaheadProps, 'onMatch' | 'onTypingChange'> & {\n  list?: Array<string>;\n  open?: boolean;\n  onOpenChange?: (open: boolean) => void;\n  addUseClick?: boolean;\n}) => {\n  const [open, setOpen] = React.useState(true);\n  const [activeIndex, setActiveIndex] = React.useState<null | number>(null);\n  const { refs, context } = useFloating({\n    open: props.open ?? open,\n    onOpenChange: props.onOpenChange ?? setOpen,\n  });\n  const listRef = React.useRef(props.list ?? ['one', 'two', 'three']);\n  const typeahead = useTypeahead(context, {\n    listRef,\n    activeIndex,\n    onMatch(index) {\n      setActiveIndex(index);\n      props.onMatch?.(index);\n    },\n    onTypingChange: props.onTypingChange,\n  });\n  const click = useClick(context, {\n    enabled: addUseClick,\n  });\n\n  const { getReferenceProps, getFloatingProps } = useInteractions([typeahead, click]);\n\n  return {\n    activeIndex,\n    open,\n    getReferenceProps: (userProps?: React.HTMLProps<Element>) =>\n      getReferenceProps({\n        role: 'combobox',\n        ...userProps,\n        ref: refs.setReference,\n      }),\n    getFloatingProps: () =>\n      getFloatingProps({\n        role: 'listbox',\n        ref: refs.setFloating,\n      }),\n  };\n};\n\nfunction Combobox(\n  props: Pick<UseTypeaheadProps, 'onMatch' | 'onTypingChange'> & {\n    list?: Array<string>;\n  },\n) {\n  const { getReferenceProps, getFloatingProps } = useImpl(props);\n  return (\n    <React.Fragment>\n      <input {...getReferenceProps()} />\n      <div {...getFloatingProps()} />\n    </React.Fragment>\n  );\n}\n\nfunction ComboboxWithElementsRef(\n  props: Pick<UseTypeaheadProps, 'onMatch'> & {\n    list?: Array<string>;\n    hiddenIndices?: Array<number>;\n  },\n) {\n  const [activeIndex, setActiveIndex] = React.useState<null | number>(null);\n  const [open, setOpen] = React.useState(true);\n  const { refs, context } = useFloating({\n    open,\n    onOpenChange: setOpen,\n  });\n  const listRef = React.useRef(props.list ?? ['apple', 'apricot', 'banana']);\n  const elementsRef = React.useRef<Array<HTMLElement | null>>([]);\n  const typeahead = useTypeahead(context, {\n    listRef,\n    elementsRef,\n    activeIndex,\n    onMatch(index) {\n      setActiveIndex(index);\n      props.onMatch?.(index);\n    },\n  });\n\n  const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions([typeahead]);\n\n  return (\n    <React.Fragment>\n      <input {...getReferenceProps({ role: 'combobox', ref: refs.setReference })} />\n      {open && (\n        <div {...getFloatingProps({ role: 'listbox', ref: refs.setFloating })}>\n          {listRef.current.map((value, index) => (\n            <div\n              key={value}\n              role=\"option\"\n              aria-selected={activeIndex === index}\n              style={props.hiddenIndices?.includes(index) ? { display: 'none' } : undefined}\n              {...getItemProps({\n                ref(node) {\n                  elementsRef.current[index] = node;\n                },\n              })}\n            >\n              {value}\n            </div>\n          ))}\n        </div>\n      )}\n    </React.Fragment>\n  );\n}\n\ndescribe('useTypeahead', () => {\n  it('rapidly focuses list items when they start with the same letter', async () => {\n    const spy = vi.fn();\n    render(<Combobox onMatch={spy} />);\n\n    await userEvent.click(screen.getByRole('combobox'));\n\n    await userEvent.keyboard('t');\n    expect(spy).toHaveBeenCalledWith(1);\n\n    await userEvent.keyboard('t');\n    expect(spy).toHaveBeenCalledWith(2);\n\n    await userEvent.keyboard('t');\n    expect(spy).toHaveBeenCalledWith(1);\n  });\n\n  it('bails out of rapid focus of first letter if the list contains a string that starts with two of the same letter', async () => {\n    const spy = vi.fn();\n    render(<Combobox onMatch={spy} list={['apple', 'aaron', 'apricot']} />);\n\n    await userEvent.click(screen.getByRole('combobox'));\n\n    await userEvent.keyboard('a');\n    expect(spy).toHaveBeenCalledWith(0);\n\n    await userEvent.keyboard('a');\n    expect(spy).toHaveBeenCalledWith(0);\n  });\n\n  it('starts from the current activeIndex and correctly loops', async () => {\n    const spy = vi.fn();\n    render(<Combobox onMatch={spy} list={['Toy Story 2', 'Toy Story 3', 'Toy Story 4']} />);\n\n    await userEvent.click(screen.getByRole('combobox'));\n\n    await userEvent.keyboard('t');\n    await userEvent.keyboard('o');\n    await userEvent.keyboard('y');\n    expect(spy).toHaveBeenCalledWith(0);\n\n    spy.mockReset();\n\n    await userEvent.keyboard('t');\n    await userEvent.keyboard('o');\n    await userEvent.keyboard('y');\n    expect(spy).not.toHaveBeenCalled();\n\n    vi.advanceTimersByTime(750);\n\n    await userEvent.keyboard('t');\n    await userEvent.keyboard('o');\n    await userEvent.keyboard('y');\n    expect(spy).toHaveBeenCalledWith(1);\n\n    vi.advanceTimersByTime(750);\n\n    await userEvent.keyboard('t');\n    await userEvent.keyboard('o');\n    await userEvent.keyboard('y');\n    expect(spy).toHaveBeenCalledWith(2);\n\n    vi.advanceTimersByTime(750);\n\n    await userEvent.keyboard('t');\n    await userEvent.keyboard('o');\n    await userEvent.keyboard('y');\n    expect(spy).toHaveBeenCalledWith(0);\n  });\n\n  it('capslock characters continue to match', async () => {\n    const spy = vi.fn();\n    render(<Combobox onMatch={spy} />);\n\n    await userEvent.click(screen.getByRole('combobox'));\n\n    await userEvent.keyboard('{CapsLock}t');\n    expect(spy).toHaveBeenCalledWith(1);\n  });\n\n  function App1(props: Pick<UseTypeaheadProps, 'onMatch'> & { list: Array<string> }) {\n    const { getReferenceProps, getFloatingProps, activeIndex, open } = useImpl(props);\n    const inputRef = React.useRef<HTMLInputElement | null>(null);\n\n    return (\n      <React.Fragment>\n        <div\n          {...getReferenceProps({\n            onClick: () => inputRef.current?.focus(),\n          })}\n        >\n          <input ref={inputRef} readOnly />\n        </div>\n        {open && (\n          <div {...getFloatingProps()}>\n            {props.list.map((value, i) => (\n              <div\n                key={value}\n                role=\"option\"\n                tabIndex={i === activeIndex ? 0 : -1}\n                aria-selected={i === activeIndex}\n              >\n                {value}\n              </div>\n            ))}\n          </div>\n        )}\n      </React.Fragment>\n    );\n  }\n\n  it('matches when focus is within reference', async () => {\n    const spy = vi.fn();\n    render(<App1 onMatch={spy} list={['one', 'two', 'three']} />);\n\n    await userEvent.click(screen.getByRole('combobox'));\n\n    await userEvent.keyboard('t');\n    expect(spy).toHaveBeenCalledWith(1);\n  });\n\n  it('matches when focus is within floating', async () => {\n    const spy = vi.fn();\n    render(<App1 onMatch={spy} list={['one', 'two', 'three']} />);\n\n    await userEvent.click(screen.getByRole('combobox'));\n\n    await userEvent.keyboard('t');\n    const option = await screen.findByRole('option', { selected: true });\n    expect(option.textContent).toBe('two');\n    option.focus();\n    expect(option).toHaveFocus();\n\n    await userEvent.keyboard('h');\n    expect((await screen.findByRole('option', { selected: true })).textContent).toBe('three');\n  });\n\n  it('onTypingChange is called when typing starts or stops', async () => {\n    const spy = vi.fn();\n    render(<Combobox onTypingChange={spy} list={['one', 'two', 'three']} />);\n\n    act(() => screen.getByRole('combobox').focus());\n\n    await userEvent.keyboard('t');\n    expect(spy).toHaveBeenCalledTimes(1);\n    expect(spy).toHaveBeenCalledWith(true);\n\n    vi.advanceTimersByTime(750);\n    expect(spy).toHaveBeenCalledTimes(2);\n    expect(spy).toHaveBeenCalledWith(false);\n  });\n\n  it('skips hidden items when matching with elementsRef', async () => {\n    const spy = vi.fn();\n    render(<ComboboxWithElementsRef onMatch={spy} hiddenIndices={[0]} />);\n\n    await userEvent.click(screen.getByRole('combobox'));\n\n    await userEvent.keyboard('a');\n    expect(spy).toHaveBeenCalledWith(1);\n  });\n});\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/hooks/useTypeahead.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useTimeout } from '@base-ui/utils/useTimeout';\nimport { contains, isElementVisible, stopEvent } from '../utils';\n\nimport type { ElementProps, FloatingContext, FloatingRootContext } from '../types';\n\nexport interface UseTypeaheadProps {\n  /**\n   * A ref which contains an array of strings whose indices match the HTML\n   * elements of the list.\n   * @default empty list\n   */\n  listRef: React.RefObject<Array<string | null>>;\n  /**\n   * The index of the active (focused or highlighted) item in the list.\n   * @default null\n   */\n  activeIndex: number | null;\n  /**\n   * Callback invoked with the matching index if found as the user types.\n   */\n  onMatch?: ((index: number) => void) | undefined;\n  /**\n   * Optional list of item elements that correspond to `listRef` indices.\n   * When an element exists for an index, typeahead skips it if it is hidden\n   * via CSS (`display: none`).\n   */\n  elementsRef?: React.RefObject<Array<HTMLElement | null>> | undefined;\n  /**\n   * Callback invoked with the typing state as the user types.\n   */\n  onTypingChange?: ((isTyping: boolean) => void) | undefined;\n  /**\n   * Whether the Hook is enabled, including all internal Effects and event\n   * handlers.\n   * @default true\n   */\n  enabled?: boolean | undefined;\n  /**\n   * The number of milliseconds to wait before resetting the typed string.\n   * @default 750\n   */\n  resetMs?: number | undefined;\n  /**\n   * The index of the selected item in the list, if available.\n   * @default null\n   */\n  selectedIndex?: number | null | undefined;\n}\n\n/**\n * Provides a matching callback that can be used to focus an item as the user\n * types, often used in tandem with `useListNavigation()`.\n * @see https://floating-ui.com/docs/useTypeahead\n */\nexport function useTypeahead(\n  context: FloatingRootContext | FloatingContext,\n  props: UseTypeaheadProps,\n): ElementProps {\n  const store = 'rootStore' in context ? context.rootStore : context;\n  const dataRef = store.context.dataRef;\n  const open = store.useState('open');\n  const {\n    listRef,\n    elementsRef,\n    activeIndex,\n    onMatch: onMatchProp,\n    onTypingChange,\n    enabled = true,\n    resetMs = 750,\n    selectedIndex = null,\n  } = props;\n\n  const timeout = useTimeout();\n  const stringRef = React.useRef('');\n  const prevIndexRef = React.useRef<number | null>(selectedIndex ?? activeIndex ?? -1);\n  const matchIndexRef = React.useRef<number | null>(null);\n\n  useIsoLayoutEffect(() => {\n    if (!open && selectedIndex !== null) {\n      return;\n    }\n\n    timeout.clear();\n    matchIndexRef.current = null;\n\n    if (stringRef.current !== '') {\n      stringRef.current = '';\n    }\n  }, [open, selectedIndex, timeout]);\n\n  useIsoLayoutEffect(() => {\n    // Sync arrow key navigation but not typeahead navigation.\n    if (open && stringRef.current === '') {\n      prevIndexRef.current = selectedIndex ?? activeIndex ?? -1;\n    }\n  }, [open, selectedIndex, activeIndex]);\n\n  const setTypingChange = useStableCallback((value: boolean) => {\n    if (value) {\n      if (!dataRef.current.typing) {\n        dataRef.current.typing = value;\n        onTypingChange?.(value);\n      }\n    } else if (dataRef.current.typing) {\n      dataRef.current.typing = value;\n      onTypingChange?.(value);\n    }\n  });\n\n  const onKeyDown = useStableCallback((event: React.KeyboardEvent) => {\n    function isVisible(index: number) {\n      const element = elementsRef?.current[index];\n      return !element || isElementVisible(element);\n    }\n\n    function getMatchingIndex(list: Array<string | null>, string: string, startIndex = 0) {\n      if (list.length === 0) {\n        return -1;\n      }\n\n      const normalizedStartIndex = ((startIndex % list.length) + list.length) % list.length;\n      const lowerString = string.toLocaleLowerCase();\n\n      for (let offset = 0; offset < list.length; offset += 1) {\n        const index = (normalizedStartIndex + offset) % list.length;\n        const text = list[index];\n        if (!text?.toLocaleLowerCase().startsWith(lowerString) || !isVisible(index)) {\n          continue;\n        }\n        return index;\n      }\n      return -1;\n    }\n\n    const listContent = listRef.current;\n\n    if (stringRef.current.length > 0 && event.key === ' ') {\n      // Space should continue the in-progress typeahead session.\n      stopEvent(event);\n      setTypingChange(true);\n    }\n\n    if (stringRef.current.length > 0 && stringRef.current[0] !== ' ') {\n      if (getMatchingIndex(listContent, stringRef.current) === -1 && event.key !== ' ') {\n        setTypingChange(false);\n      }\n    }\n\n    if (\n      listContent == null ||\n      // Character key.\n      event.key.length !== 1 ||\n      // Modifier key.\n      event.ctrlKey ||\n      event.metaKey ||\n      event.altKey\n    ) {\n      return;\n    }\n\n    if (open && event.key !== ' ') {\n      stopEvent(event);\n      setTypingChange(true);\n    }\n\n    // Capture whether this is a new typing session before mutating the string.\n    const isNewSession = stringRef.current === '';\n    if (isNewSession) {\n      prevIndexRef.current = selectedIndex ?? activeIndex ?? -1;\n    }\n\n    // Bail out if the list contains a word like \"llama\" or \"aaron\". TODO:\n    // allow it in this case, too.\n    const allowRapidSuccessionOfFirstLetter = listContent.every((text) =>\n      text ? text[0]?.toLocaleLowerCase() !== text[1]?.toLocaleLowerCase() : true,\n    );\n\n    // Allows the user to cycle through items that start with the same letter\n    // in rapid succession.\n    if (allowRapidSuccessionOfFirstLetter && stringRef.current === event.key) {\n      stringRef.current = '';\n      prevIndexRef.current = matchIndexRef.current;\n    }\n\n    stringRef.current += event.key;\n    timeout.start(resetMs, () => {\n      stringRef.current = '';\n      prevIndexRef.current = matchIndexRef.current;\n      setTypingChange(false);\n    });\n\n    // Compute the starting index for this search.\n    // If this is a new typing session (string is empty), base it on the current\n    // selection/active item; otherwise continue from the last matched index.\n    const prevIndex = isNewSession ? (selectedIndex ?? activeIndex ?? -1) : prevIndexRef.current;\n    const startIndex = (prevIndex ?? 0) + 1;\n\n    const index = getMatchingIndex(listContent, stringRef.current, startIndex);\n\n    if (index !== -1) {\n      onMatchProp?.(index);\n      matchIndexRef.current = index;\n    } else if (event.key !== ' ') {\n      stringRef.current = '';\n      setTypingChange(false);\n    }\n  });\n\n  const onBlur = useStableCallback((event: React.FocusEvent) => {\n    const next = event.relatedTarget as Element | null;\n    const currentDomReferenceElement = store.select('domReferenceElement');\n    const currentFloatingElement = store.select('floatingElement');\n    const withinReference = contains(currentDomReferenceElement, next);\n    const withinFloating = contains(currentFloatingElement, next);\n\n    // Keep the session if focus moves within the composite (reference <-> floating).\n    if (withinReference || withinFloating) {\n      return;\n    }\n\n    // End the current typing session when focus leaves the composite entirely.\n    timeout.clear();\n    stringRef.current = '';\n    prevIndexRef.current = matchIndexRef.current;\n    setTypingChange(false);\n  });\n\n  const reference: ElementProps['reference'] = React.useMemo(\n    () => ({ onKeyDown, onBlur }),\n    [onKeyDown, onBlur],\n  );\n\n  const floating: ElementProps['floating'] = React.useMemo(() => {\n    return {\n      onKeyDown,\n      onBlur,\n    };\n  }, [onKeyDown, onBlur]);\n\n  return React.useMemo(\n    () => (enabled ? { reference, floating } : {}),\n    [enabled, reference, floating],\n  );\n}\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/index.ts",
    "content": "export { FloatingDelayGroup, useDelayGroup } from './components/FloatingDelayGroup';\nexport { FloatingFocusManager } from './components/FloatingFocusManager';\nexport { FloatingPortal, useFloatingPortalNode } from './components/FloatingPortal';\nexport {\n  FloatingNode,\n  FloatingTree,\n  useFloatingNodeId,\n  useFloatingParentNodeId,\n  useFloatingTree,\n} from './components/FloatingTree';\nexport { FloatingTreeStore } from './components/FloatingTreeStore';\nexport { useClick } from './hooks/useClick';\nexport { useClientPoint } from './hooks/useClientPoint';\nexport { useDismiss } from './hooks/useDismiss';\nexport { useFloating } from './hooks/useFloating';\nexport { useFloatingRootContext } from './hooks/useFloatingRootContext';\nexport { useSyncedFloatingRootContext } from './hooks/useSyncedFloatingRootContext';\nexport { useFocus } from './hooks/useFocus';\nexport { useHoverFloatingInteraction } from './hooks/useHoverFloatingInteraction';\nexport { useHoverReferenceInteraction } from './hooks/useHoverReferenceInteraction';\nexport { useHover } from './hooks/useHover';\nexport { useInteractions } from './hooks/useInteractions';\nexport { useListNavigation } from './hooks/useListNavigation';\nexport { useRole } from './hooks/useRole';\nexport { useTypeahead } from './hooks/useTypeahead';\nexport { safePolygon } from './safePolygon';\nexport type * from './types';\nexport {\n  arrow,\n  autoPlacement,\n  autoUpdate,\n  computePosition,\n  detectOverflow,\n  flip,\n  getOverflowAncestors,\n  hide,\n  inline,\n  limitShift,\n  offset,\n  platform,\n  shift,\n  size,\n} from '@floating-ui/react-dom';\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/middleware/arrow.ts",
    "content": "import type { Derivable, Middleware, Padding } from '@floating-ui/react-dom';\nimport {\n  clamp,\n  evaluate,\n  getAlignment,\n  getAlignmentAxis,\n  getAxisLength,\n  getPaddingObject,\n} from '@floating-ui/utils';\n\nexport interface ArrowOptions {\n  /**\n   * The arrow element to be positioned.\n   * @default undefined\n   */\n  element: any;\n  /**\n   * The padding between the arrow element and the floating element edges.\n   * Useful when the floating element has rounded corners.\n   * @default 0\n   */\n  padding?: Padding | undefined;\n  /**\n   * Which element to use as the offset parent.\n   * @default 'real'\n   */\n  offsetParent: 'real' | 'floating';\n}\n\n/**\n * Fork of the original `arrow` middleware from Floating UI that allows\n * configuring the offset parent.\n */\nexport const baseArrow = (options: ArrowOptions | Derivable<ArrowOptions>): Middleware => ({\n  name: 'arrow',\n  options,\n  async fn(state) {\n    const { x, y, placement, rects, platform, elements, middlewareData } = state;\n    // Since `element` is required, we don't Partial<> the type.\n    const { element, padding = 0, offsetParent = 'real' } = evaluate(options, state) || {};\n\n    if (element == null) {\n      return {};\n    }\n\n    const paddingObject = getPaddingObject(padding);\n    const coords = { x, y };\n    const axis = getAlignmentAxis(placement);\n    const length = getAxisLength(axis);\n    const arrowDimensions = await platform.getDimensions(element);\n    const isYAxis = axis === 'y';\n    const minProp = isYAxis ? 'top' : 'left';\n    const maxProp = isYAxis ? 'bottom' : 'right';\n    const clientProp = isYAxis ? 'clientHeight' : 'clientWidth';\n\n    const endDiff =\n      rects.reference[length] + rects.reference[axis] - coords[axis] - rects.floating[length];\n    const startDiff = coords[axis] - rects.reference[axis];\n\n    const arrowOffsetParent =\n      offsetParent === 'real' ? await platform.getOffsetParent?.(element) : elements.floating;\n    let clientSize = elements.floating[clientProp] || rects.floating[length];\n\n    // DOM platform can return `window` as the `offsetParent`.\n    if (!clientSize || !(await platform.isElement?.(arrowOffsetParent))) {\n      clientSize = elements.floating[clientProp] || rects.floating[length];\n    }\n\n    const centerToReference = endDiff / 2 - startDiff / 2;\n\n    // If the padding is large enough that it causes the arrow to no longer be\n    // centered, modify the padding so that it is centered.\n    const largestPossiblePadding = clientSize / 2 - arrowDimensions[length] / 2 - 1;\n    const minPadding = Math.min(paddingObject[minProp], largestPossiblePadding);\n    const maxPadding = Math.min(paddingObject[maxProp], largestPossiblePadding);\n\n    // Make sure the arrow doesn't overflow the floating element if the center\n    // point is outside the floating element's bounds.\n    const min = minPadding;\n    const max = clientSize - arrowDimensions[length] - maxPadding;\n    const center = clientSize / 2 - arrowDimensions[length] / 2 + centerToReference;\n    const offset = clamp(min, center, max);\n\n    // If the reference is small enough that the arrow's padding causes it to\n    // to point to nothing for an aligned placement, adjust the offset of the\n    // floating element itself. To ensure `shift()` continues to take action,\n    // a single reset is performed when this is true.\n    const shouldAddOffset =\n      !middlewareData.arrow &&\n      getAlignment(placement) != null &&\n      center !== offset &&\n      rects.reference[length] / 2 -\n        (center < min ? minPadding : maxPadding) -\n        arrowDimensions[length] / 2 <\n        0;\n    // eslint-disable-next-line no-nested-ternary\n    const alignmentOffset = shouldAddOffset ? (center < min ? center - min : center - max) : 0;\n\n    return {\n      [axis]: coords[axis] + alignmentOffset,\n      data: {\n        [axis]: offset,\n        centerOffset: center - offset - alignmentOffset,\n        ...(shouldAddOffset && { alignmentOffset }),\n      },\n      reset: shouldAddOffset,\n    };\n  },\n});\n\n/**\n * Provides data to position an inner element of the floating element so that it\n * appears centered to the reference element.\n * This wraps the core `arrow` middleware to allow React refs as the element.\n * @see https://floating-ui.com/docs/arrow\n */\nexport const arrow = (\n  options: ArrowOptions | Derivable<ArrowOptions>,\n  deps?: React.DependencyList,\n): Middleware => ({\n  ...baseArrow(options),\n  options: [options, deps],\n});\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/safePolygon.test.ts",
    "content": "import { vi, expect } from 'vitest';\nimport { act } from '@mui/internal-test-utils';\nimport { FloatingTreeStore } from './components/FloatingTreeStore';\nimport type { HandleCloseContext } from './hooks/useHoverShared';\nimport type { FloatingContext } from './types';\nimport { safePolygon } from './safePolygon';\nimport { getEmptyRootContext } from './utils/getEmptyRootContext';\n\nfunction createRect(left: number, top: number, width: number, height: number) {\n  return {\n    x: left,\n    y: top,\n    left,\n    top,\n    width,\n    height,\n    right: left + width,\n    bottom: top + height,\n    toJSON() {\n      return this;\n    },\n  } satisfies DOMRect;\n}\n\nfunction createMouseMoveEvent(\n  clientX: number,\n  clientY: number,\n  target: EventTarget | null = null,\n): MouseEvent {\n  return {\n    type: 'mousemove',\n    clientX,\n    clientY,\n    relatedTarget: null,\n    composedPath: () => [target],\n  } as unknown as MouseEvent;\n}\n\nfunction createHandleCloseContext({\n  domReference,\n  floating,\n  onClose,\n  tree,\n  placement = 'right',\n  x = 2,\n  y = 0,\n}: {\n  domReference: Element;\n  floating: HTMLElement;\n  onClose: () => void;\n  tree: FloatingTreeStore;\n  placement?: 'top' | 'bottom' | 'left' | 'right';\n  x?: number;\n  y?: number;\n}): HandleCloseContext {\n  return {\n    x,\n    y,\n    placement,\n    elements: { domReference, floating },\n    nodeId: 'root',\n    onClose,\n    tree,\n  };\n}\n\nfunction createFloatingContext({\n  domReference,\n  floating,\n  open,\n}: {\n  domReference: Element;\n  floating: HTMLElement;\n  open: boolean;\n}): FloatingContext {\n  const refs: FloatingContext['refs'] = {\n    reference: { current: domReference },\n    floating: { current: floating },\n    domReference: { current: domReference },\n    setReference() {},\n    setFloating() {},\n    setPositionReference() {},\n  };\n\n  const events: FloatingContext['events'] = {\n    emit() {},\n    on() {},\n    off() {},\n  };\n\n  return {\n    x: 0,\n    y: 0,\n    strategy: 'absolute',\n    placement: 'right',\n    middlewareData: {},\n    isPositioned: true,\n    update: async () => {},\n    floatingStyles: {},\n    open,\n    onOpenChange() {},\n    events,\n    dataRef: { current: {} },\n    nodeId: 'root',\n    floatingId: 'floating-id',\n    refs,\n    elements: { reference: domReference, domReference, floating },\n    rootStore: getEmptyRootContext(),\n  };\n}\n\nfunction createPlacementScenario(placement: 'top' | 'bottom' | 'left' | 'right') {\n  const referenceRect = createRect(0, 0, 100, 100);\n\n  switch (placement) {\n    case 'top':\n      return {\n        referenceRect,\n        floatingRect: createRect(0, -120, 100, 100),\n        leavePoint: [50, 0] as const,\n        troughPoint: [50, -10] as const,\n        outsidePoint: [50, 150] as const,\n      };\n    case 'bottom':\n      return {\n        referenceRect,\n        floatingRect: createRect(0, 120, 100, 100),\n        leavePoint: [50, 100] as const,\n        troughPoint: [50, 110] as const,\n        outsidePoint: [50, -50] as const,\n      };\n    case 'left':\n      return {\n        referenceRect,\n        floatingRect: createRect(-120, 0, 100, 100),\n        leavePoint: [0, 50] as const,\n        troughPoint: [-10, 50] as const,\n        outsidePoint: [150, 50] as const,\n      };\n    case 'right':\n      return {\n        referenceRect,\n        floatingRect: createRect(120, 0, 100, 100),\n        leavePoint: [100, 50] as const,\n        troughPoint: [110, 50] as const,\n        outsidePoint: [-50, 50] as const,\n      };\n    default:\n      return {\n        referenceRect,\n        floatingRect: createRect(120, 0, 100, 100),\n        leavePoint: [100, 50] as const,\n        troughPoint: [110, 50] as const,\n        outsidePoint: [-50, 50] as const,\n      };\n  }\n}\n\ndescribe('safePolygon', () => {\n  beforeEach(() => {\n    vi.useFakeTimers();\n  });\n\n  afterEach(() => {\n    vi.useRealTimers();\n  });\n\n  it('does not close when a nested child is open', async () => {\n    const domReference = document.createElement('button');\n    const floating = document.createElement('div');\n    domReference.getBoundingClientRect = () => createRect(0, 0, 100, 100);\n    floating.getBoundingClientRect = () => createRect(120, 0, 100, 100);\n\n    const tree = new FloatingTreeStore();\n    const onClose = vi.fn();\n    const context = createHandleCloseContext({\n      domReference,\n      floating,\n      onClose,\n      tree,\n    });\n\n    const openChildContext = createFloatingContext({ domReference, floating, open: true });\n    tree.addNode({ id: 'child', parentId: 'root', context: openChildContext });\n\n    const handler = safePolygon()(context);\n    handler(createMouseMoveEvent(3, -1));\n\n    await act(async () => {\n      vi.advanceTimersByTime(50);\n    });\n\n    expect(onClose).toHaveBeenCalledTimes(0);\n  });\n\n  it('closes after intent timeout when no nested child is open', async () => {\n    const domReference = document.createElement('button');\n    const floating = document.createElement('div');\n    domReference.getBoundingClientRect = () => createRect(0, 0, 100, 100);\n    floating.getBoundingClientRect = () => createRect(120, 0, 100, 100);\n\n    const tree = new FloatingTreeStore();\n    const onClose = vi.fn();\n    const context = createHandleCloseContext({\n      domReference,\n      floating,\n      onClose,\n      tree,\n    });\n\n    const closedChildContext = createFloatingContext({ domReference, floating, open: false });\n    tree.addNode({ id: 'child', parentId: 'root', context: closedChildContext });\n\n    const handler = safePolygon()(context);\n    handler(createMouseMoveEvent(3, -1));\n\n    await act(async () => {\n      vi.advanceTimersByTime(50);\n    });\n\n    expect(onClose).toHaveBeenCalledTimes(1);\n  });\n\n  it.each(['top', 'bottom', 'left', 'right'] as const)(\n    'keeps open while moving through the trough on %s placement',\n    (placement) => {\n      const domReference = document.createElement('button');\n      const floating = document.createElement('div');\n      const scenario = createPlacementScenario(placement);\n\n      domReference.getBoundingClientRect = () => scenario.referenceRect;\n      floating.getBoundingClientRect = () => scenario.floatingRect;\n\n      const tree = new FloatingTreeStore();\n      const onClose = vi.fn();\n      const context = createHandleCloseContext({\n        domReference,\n        floating,\n        onClose,\n        tree,\n        placement,\n        x: scenario.leavePoint[0],\n        y: scenario.leavePoint[1],\n      });\n\n      const handler = safePolygon()(context);\n      handler(createMouseMoveEvent(scenario.troughPoint[0], scenario.troughPoint[1]));\n\n      expect(onClose).toHaveBeenCalledTimes(0);\n    },\n  );\n\n  it.each(['top', 'bottom', 'left', 'right'] as const)(\n    'closes when moving away from corridor on %s placement',\n    (placement) => {\n      const domReference = document.createElement('button');\n      const floating = document.createElement('div');\n      const scenario = createPlacementScenario(placement);\n\n      domReference.getBoundingClientRect = () => scenario.referenceRect;\n      floating.getBoundingClientRect = () => scenario.floatingRect;\n\n      const tree = new FloatingTreeStore();\n      const onClose = vi.fn();\n      const context = createHandleCloseContext({\n        domReference,\n        floating,\n        onClose,\n        tree,\n        placement,\n        x: scenario.leavePoint[0],\n        y: scenario.leavePoint[1],\n      });\n\n      const handler = safePolygon()(context);\n      handler(createMouseMoveEvent(scenario.outsidePoint[0], scenario.outsidePoint[1]));\n\n      expect(onClose).toHaveBeenCalledTimes(1);\n    },\n  );\n\n  it('resets traversal state for a new handler invocation', () => {\n    const domReference = document.createElement('button');\n    const floating = document.createElement('div');\n    const scenario = createPlacementScenario('right');\n\n    domReference.getBoundingClientRect = () => scenario.referenceRect;\n    floating.getBoundingClientRect = () => scenario.floatingRect;\n\n    const tree = new FloatingTreeStore();\n    const onClose = vi.fn();\n    const context = createHandleCloseContext({\n      domReference,\n      floating,\n      onClose,\n      tree,\n      placement: 'right',\n      x: scenario.leavePoint[0],\n      y: scenario.leavePoint[1],\n    });\n\n    const handleClose = safePolygon();\n    const firstHandler = handleClose(context);\n    firstHandler(createMouseMoveEvent(130, 50, floating));\n\n    const secondHandler = handleClose(context);\n    secondHandler(createMouseMoveEvent(scenario.troughPoint[0], scenario.troughPoint[1]));\n\n    expect(onClose).toHaveBeenCalledTimes(0);\n  });\n});\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/safePolygon.ts",
    "content": "import { isElement } from '@floating-ui/utils/dom';\nimport { Timeout } from '@base-ui/utils/useTimeout';\nimport type { Rect, Side } from './types';\nimport type { HandleClose, HandleCloseOptions } from './hooks/useHoverShared';\nimport { contains, getTarget } from './utils/element';\nimport { getNodeChildren } from './utils/nodes';\n\n/* eslint-disable no-nested-ternary */\n\nconst CURSOR_SPEED_THRESHOLD = 0.1;\nconst CURSOR_SPEED_THRESHOLD_SQUARED = CURSOR_SPEED_THRESHOLD * CURSOR_SPEED_THRESHOLD;\nconst POLYGON_BUFFER = 0.5;\n\nfunction hasIntersectingEdge(\n  pointX: number,\n  pointY: number,\n  xi: number,\n  yi: number,\n  xj: number,\n  yj: number,\n) {\n  return yi >= pointY !== yj >= pointY && pointX <= ((xj - xi) * (pointY - yi)) / (yj - yi) + xi;\n}\n\nfunction isPointInQuadrilateral(\n  pointX: number,\n  pointY: number,\n  x1: number,\n  y1: number,\n  x2: number,\n  y2: number,\n  x3: number,\n  y3: number,\n  x4: number,\n  y4: number,\n) {\n  let isInsideValue = false;\n\n  if (hasIntersectingEdge(pointX, pointY, x1, y1, x2, y2)) {\n    isInsideValue = !isInsideValue;\n  }\n\n  if (hasIntersectingEdge(pointX, pointY, x2, y2, x3, y3)) {\n    isInsideValue = !isInsideValue;\n  }\n\n  if (hasIntersectingEdge(pointX, pointY, x3, y3, x4, y4)) {\n    isInsideValue = !isInsideValue;\n  }\n\n  if (hasIntersectingEdge(pointX, pointY, x4, y4, x1, y1)) {\n    isInsideValue = !isInsideValue;\n  }\n\n  return isInsideValue;\n}\n\nfunction isInsideRect(pointX: number, pointY: number, rect: Rect) {\n  return (\n    pointX >= rect.x &&\n    pointX <= rect.x + rect.width &&\n    pointY >= rect.y &&\n    pointY <= rect.y + rect.height\n  );\n}\n\nfunction isInsideAxisAlignedRect(\n  pointX: number,\n  pointY: number,\n  x1: number,\n  y1: number,\n  x2: number,\n  y2: number,\n) {\n  const minX = Math.min(x1, x2);\n  const maxX = Math.max(x1, x2);\n  const minY = Math.min(y1, y2);\n  const maxY = Math.max(y1, y2);\n\n  return pointX >= minX && pointX <= maxX && pointY >= minY && pointY <= maxY;\n}\n\nexport interface SafePolygonOptions extends HandleCloseOptions {}\n\n/**\n * Generates a safe polygon area that the user can traverse without closing the\n * floating element once leaving the reference element.\n * @see https://floating-ui.com/docs/useHover#safepolygon\n */\nexport function safePolygon(options: SafePolygonOptions = {}) {\n  const { blockPointerEvents = false } = options;\n  const timeout = new Timeout();\n\n  const fn: HandleClose = ({ x, y, placement, elements, onClose, nodeId, tree }) => {\n    const side = placement?.split('-')[0] as Side | undefined;\n    let hasLanded = false;\n    let lastX: number | null = null;\n    let lastY: number | null = null;\n    let lastCursorTime = typeof performance !== 'undefined' ? performance.now() : 0;\n\n    function isCursorMovingSlowly(nextX: number, nextY: number) {\n      const currentTime = performance.now();\n      const elapsedTime = currentTime - lastCursorTime;\n\n      if (lastX === null || lastY === null || elapsedTime === 0) {\n        lastX = nextX;\n        lastY = nextY;\n        lastCursorTime = currentTime;\n        return false;\n      }\n\n      const deltaX = nextX - lastX;\n      const deltaY = nextY - lastY;\n      const distanceSquared = deltaX * deltaX + deltaY * deltaY;\n      const thresholdSquared = elapsedTime * elapsedTime * CURSOR_SPEED_THRESHOLD_SQUARED;\n\n      lastX = nextX;\n      lastY = nextY;\n      lastCursorTime = currentTime;\n\n      return distanceSquared < thresholdSquared;\n    }\n\n    function close() {\n      timeout.clear();\n      onClose();\n    }\n\n    return function onMouseMove(event: MouseEvent) {\n      timeout.clear();\n\n      const domReference = elements.domReference;\n      const floating = elements.floating;\n      if (!domReference || !floating || side == null || x == null || y == null) {\n        return undefined;\n      }\n\n      const { clientX, clientY } = event;\n      const target = getTarget(event) as Element | null;\n      const isLeave = event.type === 'mouseleave';\n      const isOverFloatingEl = contains(floating, target);\n      const isOverReferenceEl = contains(domReference, target);\n\n      if (isOverFloatingEl) {\n        hasLanded = true;\n\n        if (!isLeave) {\n          return undefined;\n        }\n      }\n\n      if (isOverReferenceEl) {\n        hasLanded = false;\n\n        if (!isLeave) {\n          hasLanded = true;\n          return undefined;\n        }\n      }\n\n      // Prevent overlapping floating element from being stuck in an open-close\n      // loop: https://github.com/floating-ui/floating-ui/issues/1910\n      if (isLeave && isElement(event.relatedTarget) && contains(floating, event.relatedTarget)) {\n        return undefined;\n      }\n\n      function hasOpenChildNode() {\n        return Boolean(tree && getNodeChildren(tree.nodesRef.current, nodeId).length > 0);\n      }\n\n      function closeIfNoOpenChild() {\n        if (!hasOpenChildNode()) {\n          close();\n        }\n      }\n\n      // If any nested child is open, abort.\n      if (hasOpenChildNode()) {\n        return undefined;\n      }\n\n      const refRect = domReference.getBoundingClientRect();\n      const rect = floating.getBoundingClientRect();\n      const cursorLeaveFromRight = x > rect.right - rect.width / 2;\n      const cursorLeaveFromBottom = y > rect.bottom - rect.height / 2;\n      const isFloatingWider = rect.width > refRect.width;\n      const isFloatingTaller = rect.height > refRect.height;\n      const left = (isFloatingWider ? refRect : rect).left;\n      const right = (isFloatingWider ? refRect : rect).right;\n      const top = (isFloatingTaller ? refRect : rect).top;\n      const bottom = (isFloatingTaller ? refRect : rect).bottom;\n\n      // If the pointer is leaving from the opposite side, the \"buffer\" logic\n      // creates a point where the floating element remains open, but should be\n      // ignored.\n      // A constant of 1 handles floating point rounding errors.\n      if (\n        (side === 'top' && y >= refRect.bottom - 1) ||\n        (side === 'bottom' && y <= refRect.top + 1) ||\n        (side === 'left' && x >= refRect.right - 1) ||\n        (side === 'right' && x <= refRect.left + 1)\n      ) {\n        closeIfNoOpenChild();\n        return undefined;\n      }\n\n      // Ignore when the cursor is within the rectangular trough between the\n      // two elements. Since the triangle is created from the cursor point,\n      // which can start beyond the ref element's edge, traversing back and\n      // forth from the ref to the floating element can cause it to close. This\n      // ensures it always remains open in that case.\n      let isInsideTroughRect = false;\n\n      switch (side) {\n        case 'top':\n          isInsideTroughRect = isInsideAxisAlignedRect(\n            clientX,\n            clientY,\n            left,\n            refRect.top + 1,\n            right,\n            rect.bottom - 1,\n          );\n          break;\n        case 'bottom':\n          isInsideTroughRect = isInsideAxisAlignedRect(\n            clientX,\n            clientY,\n            left,\n            rect.top + 1,\n            right,\n            refRect.bottom - 1,\n          );\n          break;\n        case 'left':\n          isInsideTroughRect = isInsideAxisAlignedRect(\n            clientX,\n            clientY,\n            rect.right - 1,\n            bottom,\n            refRect.left + 1,\n            top,\n          );\n          break;\n        case 'right':\n          isInsideTroughRect = isInsideAxisAlignedRect(\n            clientX,\n            clientY,\n            refRect.right - 1,\n            bottom,\n            rect.left + 1,\n            top,\n          );\n          break;\n        default:\n      }\n\n      if (isInsideTroughRect) {\n        return undefined;\n      }\n\n      if (hasLanded && !isInsideRect(clientX, clientY, refRect)) {\n        closeIfNoOpenChild();\n        return undefined;\n      }\n\n      if (!isLeave && isCursorMovingSlowly(clientX, clientY)) {\n        closeIfNoOpenChild();\n        return undefined;\n      }\n\n      let isInsidePolygon = false;\n\n      switch (side) {\n        case 'top': {\n          const cursorXOffset = isFloatingWider ? POLYGON_BUFFER / 2 : POLYGON_BUFFER * 4;\n          const cursorPointOneX = isFloatingWider\n            ? x + cursorXOffset\n            : cursorLeaveFromRight\n              ? x + cursorXOffset\n              : x - cursorXOffset;\n          const cursorPointTwoX = isFloatingWider\n            ? x - cursorXOffset\n            : cursorLeaveFromRight\n              ? x + cursorXOffset\n              : x - cursorXOffset;\n          const cursorPointY = y + POLYGON_BUFFER + 1;\n\n          const commonYLeft = cursorLeaveFromRight\n            ? rect.bottom - POLYGON_BUFFER\n            : isFloatingWider\n              ? rect.bottom - POLYGON_BUFFER\n              : rect.top;\n          const commonYRight = cursorLeaveFromRight\n            ? isFloatingWider\n              ? rect.bottom - POLYGON_BUFFER\n              : rect.top\n            : rect.bottom - POLYGON_BUFFER;\n\n          isInsidePolygon = isPointInQuadrilateral(\n            clientX,\n            clientY,\n            cursorPointOneX,\n            cursorPointY,\n            cursorPointTwoX,\n            cursorPointY,\n            rect.left,\n            commonYLeft,\n            rect.right,\n            commonYRight,\n          );\n          break;\n        }\n        case 'bottom': {\n          const cursorXOffset = isFloatingWider ? POLYGON_BUFFER / 2 : POLYGON_BUFFER * 4;\n          const cursorPointOneX = isFloatingWider\n            ? x + cursorXOffset\n            : cursorLeaveFromRight\n              ? x + cursorXOffset\n              : x - cursorXOffset;\n          const cursorPointTwoX = isFloatingWider\n            ? x - cursorXOffset\n            : cursorLeaveFromRight\n              ? x + cursorXOffset\n              : x - cursorXOffset;\n          const cursorPointY = y - POLYGON_BUFFER;\n\n          const commonYLeft = cursorLeaveFromRight\n            ? rect.top + POLYGON_BUFFER\n            : isFloatingWider\n              ? rect.top + POLYGON_BUFFER\n              : rect.bottom;\n          const commonYRight = cursorLeaveFromRight\n            ? isFloatingWider\n              ? rect.top + POLYGON_BUFFER\n              : rect.bottom\n            : rect.top + POLYGON_BUFFER;\n\n          isInsidePolygon = isPointInQuadrilateral(\n            clientX,\n            clientY,\n            cursorPointOneX,\n            cursorPointY,\n            cursorPointTwoX,\n            cursorPointY,\n            rect.left,\n            commonYLeft,\n            rect.right,\n            commonYRight,\n          );\n          break;\n        }\n        case 'left': {\n          const cursorYOffset = isFloatingTaller ? POLYGON_BUFFER / 2 : POLYGON_BUFFER * 4;\n          const cursorPointOneY = isFloatingTaller\n            ? y + cursorYOffset\n            : cursorLeaveFromBottom\n              ? y + cursorYOffset\n              : y - cursorYOffset;\n          const cursorPointTwoY = isFloatingTaller\n            ? y - cursorYOffset\n            : cursorLeaveFromBottom\n              ? y + cursorYOffset\n              : y - cursorYOffset;\n          const cursorPointX = x + POLYGON_BUFFER + 1;\n\n          const commonXTop = cursorLeaveFromBottom\n            ? rect.right - POLYGON_BUFFER\n            : isFloatingTaller\n              ? rect.right - POLYGON_BUFFER\n              : rect.left;\n          const commonXBottom = cursorLeaveFromBottom\n            ? isFloatingTaller\n              ? rect.right - POLYGON_BUFFER\n              : rect.left\n            : rect.right - POLYGON_BUFFER;\n\n          isInsidePolygon = isPointInQuadrilateral(\n            clientX,\n            clientY,\n            commonXTop,\n            rect.top,\n            commonXBottom,\n            rect.bottom,\n            cursorPointX,\n            cursorPointOneY,\n            cursorPointX,\n            cursorPointTwoY,\n          );\n          break;\n        }\n        case 'right': {\n          const cursorYOffset = isFloatingTaller ? POLYGON_BUFFER / 2 : POLYGON_BUFFER * 4;\n          const cursorPointOneY = isFloatingTaller\n            ? y + cursorYOffset\n            : cursorLeaveFromBottom\n              ? y + cursorYOffset\n              : y - cursorYOffset;\n          const cursorPointTwoY = isFloatingTaller\n            ? y - cursorYOffset\n            : cursorLeaveFromBottom\n              ? y + cursorYOffset\n              : y - cursorYOffset;\n          const cursorPointX = x - POLYGON_BUFFER;\n\n          const commonXTop = cursorLeaveFromBottom\n            ? rect.left + POLYGON_BUFFER\n            : isFloatingTaller\n              ? rect.left + POLYGON_BUFFER\n              : rect.right;\n          const commonXBottom = cursorLeaveFromBottom\n            ? isFloatingTaller\n              ? rect.left + POLYGON_BUFFER\n              : rect.right\n            : rect.left + POLYGON_BUFFER;\n\n          isInsidePolygon = isPointInQuadrilateral(\n            clientX,\n            clientY,\n            cursorPointX,\n            cursorPointOneY,\n            cursorPointX,\n            cursorPointTwoY,\n            commonXTop,\n            rect.top,\n            commonXBottom,\n            rect.bottom,\n          );\n          break;\n        }\n        default:\n      }\n\n      if (!isInsidePolygon) {\n        closeIfNoOpenChild();\n      } else if (!hasLanded) {\n        timeout.start(40, closeIfNoOpenChild);\n      }\n\n      return undefined;\n    };\n  };\n\n  // eslint-disable-next-line no-underscore-dangle\n  fn.__options = {\n    blockPointerEvents,\n  };\n\n  return fn;\n}\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/types.ts",
    "content": "import type {\n  UseFloatingOptions as UsePositionOptions,\n  UseFloatingReturn as UsePositionFloatingReturn,\n  VirtualElement,\n} from '@floating-ui/react-dom';\nimport type * as React from 'react';\nimport type { BaseUIChangeEventDetails } from '../utils/createBaseUIEventDetails';\n\nimport type { ExtendedUserProps } from './hooks/useInteractions';\nimport type { FloatingTreeStore } from './components/FloatingTreeStore';\nimport type { FloatingRootStore } from './components/FloatingRootStore';\n\nexport * from '.';\nexport type { FloatingDelayGroupProps } from './components/FloatingDelayGroup';\nexport type { FloatingFocusManagerProps } from './components/FloatingFocusManager';\nexport type { UseFloatingPortalNodeProps } from './components/FloatingPortal';\nexport type { UseClientPointProps } from './hooks/useClientPoint';\nexport type { UseDismissProps } from './hooks/useDismiss';\nexport type { UseFocusProps } from './hooks/useFocus';\nexport type { UseHoverProps } from './hooks/useHover';\nexport type { HandleCloseContext, HandleClose } from './hooks/useHoverShared';\nexport type { UseHoverFloatingInteractionProps } from './hooks/useHoverFloatingInteraction';\nexport type { UseHoverReferenceInteractionProps } from './hooks/useHoverReferenceInteraction';\nexport type { UseListNavigationProps } from './hooks/useListNavigation';\nexport type { UseRoleProps } from './hooks/useRole';\nexport type { UseTypeaheadProps } from './hooks/useTypeahead';\nexport type { UseFloatingRootContextOptions } from './hooks/useFloatingRootContext';\nexport type { UseInteractionsReturn } from './hooks/useInteractions';\nexport type { SafePolygonOptions } from './safePolygon';\nexport type { FloatingTreeProps, FloatingNodeProps } from './components/FloatingTree';\nexport type {\n  AlignedPlacement,\n  Alignment,\n  ArrowOptions,\n  AutoPlacementOptions,\n  AutoUpdateOptions,\n  Axis,\n  Boundary,\n  ClientRectObject,\n  ComputePositionConfig,\n  ComputePositionReturn,\n  Coords,\n  DetectOverflowOptions,\n  Dimensions,\n  ElementContext,\n  ElementRects,\n  Elements,\n  FlipOptions,\n  FloatingElement,\n  HideOptions,\n  InlineOptions,\n  Length,\n  Middleware,\n  MiddlewareArguments,\n  MiddlewareData,\n  MiddlewareReturn,\n  MiddlewareState,\n  NodeScroll,\n  OffsetOptions,\n  Padding,\n  Placement,\n  Platform,\n  Rect,\n  ReferenceElement,\n  RootBoundary,\n  ShiftOptions,\n  Side,\n  SideObject,\n  SizeOptions,\n  Strategy,\n  VirtualElement,\n} from '@floating-ui/react-dom';\nexport {\n  arrow,\n  autoPlacement,\n  autoUpdate,\n  computePosition,\n  detectOverflow,\n  flip,\n  getOverflowAncestors,\n  hide,\n  inline,\n  limitShift,\n  offset,\n  platform,\n  shift,\n  size,\n} from '@floating-ui/react-dom';\n\ntype Prettify<T> = {\n  [K in keyof T]: T[K];\n} & {};\n\nexport type Delay = number | Partial<{ open: number; close: number }>;\n\nexport type NarrowedElement<T> = T extends Element ? T : Element;\n\nexport interface ExtendedRefs {\n  reference: React.RefObject<ReferenceType | null>;\n  floating: React.RefObject<HTMLElement | null>;\n  domReference: React.RefObject<NarrowedElement<ReferenceType> | null>;\n  setReference(node: ReferenceType | null): void;\n  setFloating(node: HTMLElement | null): void;\n  setPositionReference(node: ReferenceType | null): void;\n}\n\nexport interface ExtendedElements {\n  reference: ReferenceType | null;\n  floating: HTMLElement | null;\n  domReference: NarrowedElement<ReferenceType> | null;\n}\n\nexport interface FloatingEvents {\n  emit<T extends string>(event: T, data?: any): void;\n  on(event: string, handler: (data: any) => void): void;\n  off(event: string, handler: (data: any) => void): void;\n}\n\nexport interface ContextData {\n  openEvent?: Event | undefined;\n  floatingContext?: FloatingContext | undefined;\n  /** @deprecated use `onTypingChange` prop in `useTypeahead` */\n  typing?: boolean | undefined;\n  [key: string]: any;\n}\n\nexport type FloatingRootContext = FloatingRootStore;\n\nexport type FloatingContext = Omit<\n  UsePositionFloatingReturn<ReferenceType>,\n  'refs' | 'elements'\n> & {\n  open: boolean;\n  onOpenChange(open: boolean, eventDetails: BaseUIChangeEventDetails<string>): void;\n  events: FloatingEvents;\n  dataRef: React.RefObject<ContextData>;\n  nodeId: string | undefined;\n  floatingId: string | undefined;\n  refs: ExtendedRefs;\n  elements: ExtendedElements;\n  rootStore: FloatingRootContext;\n};\n\nexport interface FloatingNodeType {\n  id: string | undefined;\n  parentId: string | null;\n  context?: FloatingContext | undefined;\n}\n\nexport type FloatingTreeType = FloatingTreeStore;\n\nexport interface ElementProps {\n  reference?: React.HTMLProps<Element> | undefined;\n  floating?: React.HTMLProps<HTMLElement> | undefined;\n  item?:\n    | React.HTMLProps<HTMLElement>\n    | ((props: ExtendedUserProps) => React.HTMLProps<HTMLElement>)\n    | undefined;\n  trigger?: React.HTMLProps<Element> | undefined;\n}\n\nexport type ReferenceType = Element | VirtualElement;\n\nexport type UseFloatingData = Prettify<UseFloatingReturn>;\n\nexport type UseFloatingReturn = Prettify<\n  UsePositionFloatingReturn & {\n    /**\n     * `FloatingContext`\n     */\n    context: Prettify<FloatingContext>;\n    /**\n     * Object containing the reference and floating refs and reactive setters.\n     */\n    refs: ExtendedRefs;\n    elements: ExtendedElements;\n  }\n>;\n\nexport interface UseFloatingOptions extends Omit<UsePositionOptions, 'elements'> {\n  rootContext?: FloatingRootContext | undefined;\n  /**\n   * Object of external elements as an alternative to the `refs` object setters.\n   */\n  elements?:\n    | {\n        /**\n         * Externally passed reference element. Store in state.\n         */\n        reference?: ReferenceType | null | undefined;\n        /**\n         * Externally passed floating element. Store in state.\n         */\n        floating?: HTMLElement | null | undefined;\n      }\n    | undefined;\n  /**\n   * An event callback that is invoked when the floating element is opened or\n   * closed.\n   */\n  onOpenChange?(open: boolean, eventDetails: BaseUIChangeEventDetails<string>): void;\n  /**\n   * Unique node id when using `FloatingTree`.\n   */\n  nodeId?: string | undefined;\n  /**\n   * External FlatingTree to use when the one provided by context can't be used.\n   */\n  externalTree?: FloatingTreeStore | undefined;\n}\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/utils/composite.ts",
    "content": "import { floor } from '@floating-ui/utils';\nimport { getComputedStyle } from '@floating-ui/utils/dom';\n\nimport type { Dimensions } from '../types';\nimport { stopEvent } from './event';\nimport { ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT, ARROW_UP } from './constants';\n\ntype DisabledIndices = ReadonlyArray<number> | ((index: number) => boolean);\n\nexport function isDifferentGridRow(index: number, cols: number, prevRow: number) {\n  return Math.floor(index / cols) !== prevRow;\n}\n\nexport function isIndexOutOfListBounds(list: Array<HTMLElement | null>, index: number) {\n  return index < 0 || index >= list.length;\n}\n\nexport function getMinListIndex(\n  listRef: React.RefObject<ReadonlyArray<HTMLElement | null>>,\n  disabledIndices?: DisabledIndices | undefined,\n) {\n  return findNonDisabledListIndex(listRef.current, { disabledIndices });\n}\n\nexport function getMaxListIndex(\n  listRef: React.RefObject<Array<HTMLElement | null>>,\n  disabledIndices?: DisabledIndices | undefined,\n) {\n  return findNonDisabledListIndex(listRef.current, {\n    decrement: true,\n    startingIndex: listRef.current.length,\n    disabledIndices,\n  });\n}\n\nexport function findNonDisabledListIndex(\n  list: ReadonlyArray<HTMLElement | null>,\n  {\n    startingIndex = -1,\n    decrement = false,\n    disabledIndices,\n    amount = 1,\n  }: {\n    startingIndex?: number | undefined;\n    decrement?: boolean | undefined;\n    disabledIndices?: DisabledIndices | undefined;\n    amount?: number | undefined;\n  } = {},\n): number {\n  let index = startingIndex;\n  do {\n    index += decrement ? -amount : amount;\n  } while (\n    index >= 0 &&\n    index <= list.length - 1 &&\n    isListIndexDisabled(list, index, disabledIndices)\n  );\n\n  return index;\n}\n\nexport function getGridNavigatedIndex(\n  list: Array<HTMLElement | null>,\n  {\n    event,\n    orientation,\n    loopFocus,\n    onLoop,\n    rtl,\n    cols,\n    disabledIndices,\n    minIndex,\n    maxIndex,\n    prevIndex,\n    stopEvent: stop = false,\n  }: {\n    event: React.KeyboardEvent;\n    orientation: 'horizontal' | 'vertical' | 'both';\n    loopFocus: boolean;\n    onLoop?:\n      | ((event: React.KeyboardEvent, prevIndex: number, nextIndex: number) => number)\n      | undefined;\n    rtl: boolean;\n    cols: number;\n    disabledIndices: DisabledIndices | undefined;\n    minIndex: number;\n    maxIndex: number;\n    prevIndex: number;\n    stopEvent?: boolean | undefined;\n  },\n) {\n  let nextIndex = prevIndex;\n\n  let verticalDirection: 'up' | 'down' | undefined;\n  if (event.key === ARROW_UP) {\n    verticalDirection = 'up';\n  } else if (event.key === ARROW_DOWN) {\n    verticalDirection = 'down';\n  }\n\n  if (verticalDirection) {\n    // -------------------------------------------------------------------------\n    // Detect row structure only when handling vertical navigation. This keeps\n    // the non-vertical key paths free from row inference work.\n    // -------------------------------------------------------------------------\n    const rows: number[][] = [];\n    const rowIndexMap: number[] = [];\n    let hasRoleRow = false;\n    let visibleItemCount = 0;\n    {\n      let currentRowEl: Element | null = null;\n      let currentRowIndex = -1;\n\n      list.forEach((el, idx) => {\n        if (el == null) {\n          return;\n        }\n\n        visibleItemCount += 1;\n\n        const rowEl = el.closest('[role=\"row\"]');\n        if (rowEl) {\n          hasRoleRow = true;\n        }\n\n        if (rowEl !== currentRowEl || currentRowIndex === -1) {\n          currentRowEl = rowEl;\n          currentRowIndex += 1;\n          rows[currentRowIndex] = [];\n        }\n        rows[currentRowIndex].push(idx);\n        rowIndexMap[idx] = currentRowIndex;\n      });\n    }\n\n    let hasDomRows = false;\n    let inferredDomCols = 0;\n\n    if (hasRoleRow) {\n      for (const row of rows) {\n        const rowLength = row.length;\n\n        if (rowLength > inferredDomCols) {\n          inferredDomCols = rowLength;\n        }\n\n        if (rowLength !== cols) {\n          hasDomRows = true;\n        }\n      }\n    }\n\n    const hasVirtualizedGaps = hasDomRows && visibleItemCount < list.length;\n    const verticalCols = inferredDomCols || cols;\n\n    const navigateVertically = (direction: 'up' | 'down') => {\n      if (!hasDomRows || prevIndex === -1) {\n        return undefined;\n      }\n\n      const currentRow = rowIndexMap[prevIndex];\n      if (currentRow == null) {\n        return undefined;\n      }\n\n      const colInRow = rows[currentRow].indexOf(prevIndex);\n      const step = direction === 'up' ? -1 : 1;\n\n      for (let nextRow = currentRow + step, i = 0; i < rows.length; i += 1, nextRow += step) {\n        if (nextRow < 0 || nextRow >= rows.length) {\n          if (!loopFocus || hasVirtualizedGaps) {\n            return undefined;\n          }\n          nextRow = nextRow < 0 ? rows.length - 1 : 0;\n          if (onLoop) {\n            const clampedCol = Math.min(colInRow, rows[nextRow].length - 1);\n            const targetItemIndex = rows[nextRow][clampedCol] ?? rows[nextRow][0];\n            const returnedItemIndex = onLoop(event, prevIndex, targetItemIndex);\n            nextRow = rowIndexMap[returnedItemIndex] ?? nextRow;\n          }\n        }\n\n        const targetRow = rows[nextRow];\n        for (let col = Math.min(colInRow, targetRow.length - 1); col >= 0; col -= 1) {\n          const candidate = targetRow[col];\n          if (!isListIndexDisabled(list, candidate, disabledIndices)) {\n            return candidate;\n          }\n        }\n      }\n\n      return undefined;\n    };\n\n    const navigateVerticallyWithInferredRows = (direction: 'up' | 'down') => {\n      if (!hasVirtualizedGaps || prevIndex === -1) {\n        return undefined;\n      }\n\n      const colInRow = prevIndex % verticalCols;\n      const rowStep = direction === 'up' ? -verticalCols : verticalCols;\n      const lastRowStart = maxIndex - (maxIndex % verticalCols);\n      const rowCount = floor(maxIndex / verticalCols) + 1;\n\n      for (\n        let rowStart = prevIndex - colInRow + rowStep, i = 0;\n        i < rowCount;\n        i += 1, rowStart += rowStep\n      ) {\n        if (rowStart < 0 || rowStart > maxIndex) {\n          if (!loopFocus) {\n            return undefined;\n          }\n          rowStart = rowStart < 0 ? lastRowStart : 0;\n        }\n\n        const rowEnd = Math.min(rowStart + verticalCols - 1, maxIndex);\n        for (\n          let candidate = Math.min(rowStart + colInRow, rowEnd);\n          candidate >= rowStart;\n          candidate -= 1\n        ) {\n          if (!isListIndexDisabled(list, candidate, disabledIndices)) {\n            return candidate;\n          }\n        }\n      }\n\n      return undefined;\n    };\n\n    if (stop) {\n      stopEvent(event);\n    }\n\n    const verticalCandidate =\n      navigateVertically(verticalDirection) ??\n      navigateVerticallyWithInferredRows(verticalDirection);\n\n    if (verticalCandidate !== undefined) {\n      nextIndex = verticalCandidate;\n    } else if (prevIndex === -1) {\n      nextIndex = verticalDirection === 'up' ? maxIndex : minIndex;\n    } else {\n      nextIndex = findNonDisabledListIndex(list, {\n        startingIndex: prevIndex,\n        amount: verticalCols,\n        decrement: verticalDirection === 'up',\n        disabledIndices,\n      });\n\n      if (loopFocus) {\n        if (verticalDirection === 'up' && (prevIndex - verticalCols < minIndex || nextIndex < 0)) {\n          const col = prevIndex % verticalCols;\n          const maxCol = maxIndex % verticalCols;\n          const offset = maxIndex - (maxCol - col);\n\n          if (maxCol === col) {\n            nextIndex = maxIndex;\n          } else {\n            nextIndex = maxCol > col ? offset : offset - verticalCols;\n          }\n          if (onLoop) {\n            nextIndex = onLoop(event, prevIndex, nextIndex);\n          }\n        }\n\n        if (verticalDirection === 'down' && prevIndex + verticalCols > maxIndex) {\n          nextIndex = findNonDisabledListIndex(list, {\n            startingIndex: (prevIndex % verticalCols) - verticalCols,\n            amount: verticalCols,\n            disabledIndices,\n          });\n          if (onLoop) {\n            nextIndex = onLoop(event, prevIndex, nextIndex);\n          }\n        }\n      }\n    }\n\n    if (isIndexOutOfListBounds(list, nextIndex)) {\n      nextIndex = prevIndex;\n    }\n  }\n\n  // Remains on the same row/column.\n  if (orientation === 'both') {\n    const prevRow = floor(prevIndex / cols);\n\n    if (event.key === (rtl ? ARROW_LEFT : ARROW_RIGHT)) {\n      if (stop) {\n        stopEvent(event);\n      }\n\n      if (prevIndex % cols !== cols - 1) {\n        nextIndex = findNonDisabledListIndex(list, {\n          startingIndex: prevIndex,\n          disabledIndices,\n        });\n\n        if (loopFocus && isDifferentGridRow(nextIndex, cols, prevRow)) {\n          nextIndex = findNonDisabledListIndex(list, {\n            startingIndex: prevIndex - (prevIndex % cols) - 1,\n            disabledIndices,\n          });\n          if (onLoop) {\n            nextIndex = onLoop(event, prevIndex, nextIndex);\n          }\n        }\n      } else if (loopFocus) {\n        nextIndex = findNonDisabledListIndex(list, {\n          startingIndex: prevIndex - (prevIndex % cols) - 1,\n          disabledIndices,\n        });\n        if (onLoop) {\n          nextIndex = onLoop(event, prevIndex, nextIndex);\n        }\n      }\n\n      if (isDifferentGridRow(nextIndex, cols, prevRow)) {\n        nextIndex = prevIndex;\n      }\n    }\n\n    if (event.key === (rtl ? ARROW_RIGHT : ARROW_LEFT)) {\n      if (stop) {\n        stopEvent(event);\n      }\n\n      if (prevIndex % cols !== 0) {\n        nextIndex = findNonDisabledListIndex(list, {\n          startingIndex: prevIndex,\n          decrement: true,\n          disabledIndices,\n        });\n\n        if (loopFocus && isDifferentGridRow(nextIndex, cols, prevRow)) {\n          nextIndex = findNonDisabledListIndex(list, {\n            startingIndex: prevIndex + (cols - (prevIndex % cols)),\n            decrement: true,\n            disabledIndices,\n          });\n          if (onLoop) {\n            nextIndex = onLoop(event, prevIndex, nextIndex);\n          }\n        }\n      } else if (loopFocus) {\n        nextIndex = findNonDisabledListIndex(list, {\n          startingIndex: prevIndex + (cols - (prevIndex % cols)),\n          decrement: true,\n          disabledIndices,\n        });\n        if (onLoop) {\n          nextIndex = onLoop(event, prevIndex, nextIndex);\n        }\n      }\n\n      if (isDifferentGridRow(nextIndex, cols, prevRow)) {\n        nextIndex = prevIndex;\n      }\n    }\n\n    const lastRow = floor(maxIndex / cols) === prevRow;\n\n    if (isIndexOutOfListBounds(list, nextIndex)) {\n      if (loopFocus && lastRow) {\n        nextIndex =\n          event.key === (rtl ? ARROW_RIGHT : ARROW_LEFT)\n            ? maxIndex\n            : findNonDisabledListIndex(list, {\n                startingIndex: prevIndex - (prevIndex % cols) - 1,\n                disabledIndices,\n              });\n        if (onLoop) {\n          nextIndex = onLoop(event, prevIndex, nextIndex);\n        }\n      } else {\n        nextIndex = prevIndex;\n      }\n    }\n  }\n\n  return nextIndex;\n}\n\n/** For each cell index, gets the item index that occupies that cell */\nexport function createGridCellMap(sizes: Dimensions[], cols: number, dense: boolean) {\n  const cellMap: (number | undefined)[] = [];\n  let startIndex = 0;\n  sizes.forEach(({ width, height }, index) => {\n    if (width > cols) {\n      if (process.env.NODE_ENV !== 'production') {\n        throw new Error(\n          `[Floating UI]: Invalid grid - item width at index ${index} is greater than grid columns`,\n        );\n      }\n    }\n    let itemPlaced = false;\n    if (dense) {\n      startIndex = 0;\n    }\n    while (!itemPlaced) {\n      const targetCells: number[] = [];\n      for (let i = 0; i < width; i += 1) {\n        for (let j = 0; j < height; j += 1) {\n          targetCells.push(startIndex + i + j * cols);\n        }\n      }\n      if (\n        (startIndex % cols) + width <= cols &&\n        targetCells.every((cell) => cellMap[cell] == null)\n      ) {\n        targetCells.forEach((cell) => {\n          cellMap[cell] = index;\n        });\n        itemPlaced = true;\n      } else {\n        startIndex += 1;\n      }\n    }\n  });\n\n  // convert into a non-sparse array\n  return [...cellMap];\n}\n\n/** Gets cell index of an item's corner or -1 when index is -1. */\nexport function getGridCellIndexOfCorner(\n  index: number,\n  sizes: Dimensions[],\n  cellMap: (number | undefined)[],\n  cols: number,\n  corner: 'tl' | 'tr' | 'bl' | 'br',\n) {\n  if (index === -1) {\n    return -1;\n  }\n\n  const firstCellIndex = cellMap.indexOf(index);\n  const sizeItem = sizes[index];\n\n  switch (corner) {\n    case 'tl':\n      return firstCellIndex;\n    case 'tr':\n      if (!sizeItem) {\n        return firstCellIndex;\n      }\n      return firstCellIndex + sizeItem.width - 1;\n    case 'bl':\n      if (!sizeItem) {\n        return firstCellIndex;\n      }\n      return firstCellIndex + (sizeItem.height - 1) * cols;\n    case 'br':\n      return cellMap.lastIndexOf(index);\n    default:\n      return -1;\n  }\n}\n\n/** Gets all cell indices that correspond to the specified indices */\nexport function getGridCellIndices(\n  indices: (number | undefined)[],\n  cellMap: (number | undefined)[],\n) {\n  return cellMap.flatMap((index, cellIndex) => (indices.includes(index) ? [cellIndex] : []));\n}\n\nexport function isListIndexDisabled(\n  list: ReadonlyArray<HTMLElement | null>,\n  index: number,\n  disabledIndices?: DisabledIndices,\n) {\n  const isExplicitlyDisabled =\n    typeof disabledIndices === 'function'\n      ? disabledIndices(index)\n      : (disabledIndices?.includes(index) ?? false);\n\n  if (isExplicitlyDisabled) {\n    return true;\n  }\n\n  const element = list[index];\n  if (!element) {\n    return false;\n  }\n\n  if (!isElementVisible(element)) {\n    return true;\n  }\n\n  return (\n    !disabledIndices &&\n    (element.hasAttribute('disabled') || element.getAttribute('aria-disabled') === 'true')\n  );\n}\n\nexport function isElementVisible(element: Element) {\n  return getComputedStyle(element).display !== 'none';\n}\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/utils/constants.ts",
    "content": "export const FOCUSABLE_ATTRIBUTE = 'data-base-ui-focusable';\nexport const ACTIVE_KEY = 'active';\nexport const SELECTED_KEY = 'selected';\nexport const TYPEABLE_SELECTOR =\n  \"input:not([type='hidden']):not([disabled]),\" +\n  \"[contenteditable]:not([contenteditable='false']),textarea:not([disabled])\";\nexport const ARROW_LEFT = 'ArrowLeft';\nexport const ARROW_RIGHT = 'ArrowRight';\nexport const ARROW_UP = 'ArrowUp';\nexport const ARROW_DOWN = 'ArrowDown';\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/utils/createAttribute.ts",
    "content": "export function createAttribute(name: string) {\n  return `data-base-ui-${name}`;\n}\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/utils/createEventEmitter.ts",
    "content": "import { FloatingEvents } from '../types';\n\nexport function createEventEmitter(): FloatingEvents {\n  const map = new Map<string, Set<(data: any) => void>>();\n  return {\n    emit(event: string, data: any) {\n      map.get(event)?.forEach((listener) => listener(data));\n    },\n    on(event: string, listener: (data: any) => void) {\n      if (!map.has(event)) {\n        map.set(event, new Set());\n      }\n      map.get(event)!.add(listener);\n    },\n    off(event: string, listener: (data: any) => void) {\n      map.get(event)?.delete(listener);\n    },\n  };\n}\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/utils/element.ts",
    "content": "import { isElement, isHTMLElement, isShadowRoot } from '@floating-ui/utils/dom';\nimport { isJSDOM } from '@base-ui/utils/detectBrowser';\nimport { FOCUSABLE_ATTRIBUTE, TYPEABLE_SELECTOR } from './constants';\nimport { type PopupTriggerMap } from '../../utils/popups';\n\nexport function activeElement(doc: Document) {\n  let element = doc.activeElement;\n\n  while (element?.shadowRoot?.activeElement != null) {\n    element = element.shadowRoot.activeElement;\n  }\n\n  return element;\n}\n\nexport function contains(parent?: Element | null, child?: Element | null) {\n  if (!parent || !child) {\n    return false;\n  }\n\n  const rootNode = child.getRootNode?.();\n\n  // First, attempt with faster native method\n  if (parent.contains(child)) {\n    return true;\n  }\n\n  // then fallback to custom implementation with Shadow DOM support\n  if (rootNode && isShadowRoot(rootNode)) {\n    let next = child;\n    while (next) {\n      if (parent === next) {\n        return true;\n      }\n      next = (next.parentNode as Element) || (next as unknown as ShadowRoot).host;\n    }\n  }\n\n  // Give up, the result is false\n  return false;\n}\n\nexport function isTargetInsideEnabledTrigger(\n  target: EventTarget | null,\n  triggerElements: PopupTriggerMap,\n) {\n  if (!isElement(target)) {\n    return false;\n  }\n\n  const targetElement = target as Element;\n\n  if (triggerElements.hasElement(targetElement)) {\n    return !targetElement.hasAttribute('data-trigger-disabled');\n  }\n\n  for (const [, trigger] of triggerElements.entries()) {\n    if (contains(trigger, targetElement)) {\n      return !trigger.hasAttribute('data-trigger-disabled');\n    }\n  }\n\n  return false;\n}\n\nexport function getTarget(event: Event) {\n  if ('composedPath' in event) {\n    return event.composedPath()[0];\n  }\n\n  // TS thinks `event` is of type never as it assumes all browsers support\n  // `composedPath()`, but browsers without shadow DOM don't.\n  return (event as Event).target;\n}\n\nexport function isEventTargetWithin(event: Event, node: Node | null | undefined) {\n  if (node == null) {\n    return false;\n  }\n\n  if ('composedPath' in event) {\n    return event.composedPath().includes(node);\n  }\n\n  // TS thinks `event` is of type never as it assumes all browsers support composedPath, but browsers without shadow dom don't\n  const eventAgain = event as Event;\n  return eventAgain.target != null && node.contains(eventAgain.target as Node);\n}\n\nexport function isRootElement(element: Element): boolean {\n  return element.matches('html,body');\n}\n\nexport function isTypeableElement(element: unknown): boolean {\n  return isHTMLElement(element) && element.matches(TYPEABLE_SELECTOR);\n}\n\nexport function isInteractiveElement(element: Element | null) {\n  return (\n    element?.closest(\n      `button,a[href],[role=\"button\"],select,[tabindex]:not([tabindex=\"-1\"]),${TYPEABLE_SELECTOR}`,\n    ) != null\n  );\n}\n\nexport function isTypeableCombobox(element: Element | null) {\n  if (!element) {\n    return false;\n  }\n  return element.getAttribute('role') === 'combobox' && isTypeableElement(element);\n}\n\nexport function matchesFocusVisible(element: Element | null) {\n  // We don't want to block focus from working with `visibleOnly`\n  // (JSDOM doesn't match `:focus-visible` when the element has `:focus`)\n  if (!element || isJSDOM) {\n    return true;\n  }\n  try {\n    return element.matches(':focus-visible');\n  } catch (_e) {\n    return true;\n  }\n}\n\nexport function getFloatingFocusElement(\n  floatingElement: HTMLElement | null | undefined,\n): HTMLElement | null {\n  if (!floatingElement) {\n    return null;\n  }\n  // Try to find the element that has `{...getFloatingProps()}` spread on it.\n  // This indicates the floating element is acting as a positioning wrapper, and\n  // so focus should be managed on the child element with the event handlers and\n  // aria props.\n  return floatingElement.hasAttribute(FOCUSABLE_ATTRIBUTE)\n    ? floatingElement\n    : floatingElement.querySelector(`[${FOCUSABLE_ATTRIBUTE}]`) || floatingElement;\n}\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/utils/enqueueFocus.ts",
    "content": "import type { FocusableElement } from 'tabbable';\n\ninterface Options {\n  preventScroll?: boolean | undefined;\n  cancelPrevious?: boolean | undefined;\n  sync?: boolean | undefined;\n}\n\nlet rafId = 0;\nexport function enqueueFocus(el: FocusableElement | null, options: Options = {}) {\n  const { preventScroll = false, cancelPrevious = true, sync = false } = options;\n  if (cancelPrevious) {\n    cancelAnimationFrame(rafId);\n  }\n  const exec = () => el?.focus({ preventScroll });\n  if (sync) {\n    exec();\n  } else {\n    rafId = requestAnimationFrame(exec);\n  }\n}\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/utils/event.ts",
    "content": "import { isAndroid, isJSDOM } from '@base-ui/utils/detectBrowser';\n\nexport function stopEvent(event: Event | React.SyntheticEvent) {\n  event.preventDefault();\n  event.stopPropagation();\n}\n\nexport function isReactEvent(event: any): event is React.SyntheticEvent {\n  return 'nativeEvent' in event;\n}\n\n// License: https://github.com/adobe/react-spectrum/blob/main/packages/@react-aria/utils/src/isVirtualEvent.ts\nexport function isVirtualClick(event: MouseEvent | PointerEvent): boolean {\n  if ((event as PointerEvent).pointerType === '' && event.isTrusted) {\n    return true;\n  }\n\n  if (isAndroid && (event as PointerEvent).pointerType) {\n    return event.type === 'click' && event.buttons === 1;\n  }\n\n  return event.detail === 0 && !(event as PointerEvent).pointerType;\n}\n\nexport function isVirtualPointerEvent(event: PointerEvent) {\n  if (isJSDOM) {\n    return false;\n  }\n  return (\n    (!isAndroid && event.width === 0 && event.height === 0) ||\n    (isAndroid &&\n      event.width === 1 &&\n      event.height === 1 &&\n      event.pressure === 0 &&\n      event.detail === 0 &&\n      event.pointerType === 'mouse') ||\n    // iOS VoiceOver returns 0.333• for width/height.\n    (event.width < 1 &&\n      event.height < 1 &&\n      event.pressure === 0 &&\n      event.detail === 0 &&\n      event.pointerType === 'touch')\n  );\n}\n\nexport function isMouseLikePointerType(pointerType: string | undefined, strict?: boolean) {\n  // On some Linux machines with Chromium, mouse inputs return a `pointerType`\n  // of \"pen\": https://github.com/floating-ui/floating-ui/issues/2015\n  const values: Array<string | undefined> = ['mouse', 'pen'];\n  if (!strict) {\n    values.push('', undefined);\n  }\n  return values.includes(pointerType);\n}\n\nexport function isClickLikeEvent(event: Event | React.SyntheticEvent) {\n  const type = event.type;\n  return type === 'click' || type === 'mousedown' || type === 'keydown' || type === 'keyup';\n}\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/utils/getEmptyRootContext.ts",
    "content": "import { PopupTriggerMap } from '../../utils/popups';\nimport { FloatingRootStore } from '../components/FloatingRootStore';\nimport type { FloatingRootContext } from '../types';\n\nexport function getEmptyRootContext(): FloatingRootContext {\n  return new FloatingRootStore({\n    open: false,\n    floatingElement: null,\n    referenceElement: null,\n    triggerElements: new PopupTriggerMap(),\n    floatingId: '',\n    nested: false,\n    noEmit: false,\n    onOpenChange: undefined,\n  });\n}\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/utils/markOthers.test.ts",
    "content": "import { expect } from 'vitest';\nimport { markOthers } from './markOthers';\n\nafterEach(() => {\n  document.body.innerHTML = '';\n});\n\ntest('single call', () => {\n  const other = document.createElement('div');\n  document.body.appendChild(other);\n  const target = document.createElement('div');\n  document.body.appendChild(target);\n\n  const cleanup = markOthers([target], { ariaHidden: true });\n\n  expect(other.getAttribute('aria-hidden')).toBe('true');\n\n  cleanup();\n\n  expect(other.getAttribute('aria-hidden')).toBe(null);\n});\n\ntest('multiple calls', () => {\n  const other = document.createElement('div');\n  document.body.appendChild(other);\n  const target = document.createElement('div');\n  document.body.appendChild(target);\n\n  const cleanup = markOthers([target], { ariaHidden: true });\n\n  expect(other.getAttribute('aria-hidden')).toBe('true');\n\n  const nextTarget = document.createElement('div');\n  document.body.appendChild(nextTarget);\n\n  const nextCleanup = markOthers([nextTarget], { ariaHidden: true });\n\n  expect(target.getAttribute('aria-hidden')).toBe('true');\n  expect(nextTarget.getAttribute('aria-hidden')).toBe(null);\n\n  document.body.removeChild(nextTarget);\n\n  nextCleanup();\n\n  expect(target.getAttribute('aria-hidden')).toBe(null);\n  expect(other.getAttribute('aria-hidden')).toBe('true');\n\n  cleanup();\n\n  expect(other.getAttribute('aria-hidden')).toBe(null);\n\n  document.body.appendChild(nextTarget);\n});\n\ntest('out of order cleanup', () => {\n  const other = document.createElement('div');\n  document.body.appendChild(other);\n  const target = document.createElement('div');\n  target.setAttribute('data-testid', '');\n  document.body.appendChild(target);\n\n  const cleanup = markOthers([target], { ariaHidden: true });\n\n  expect(other.getAttribute('aria-hidden')).toBe('true');\n\n  const nextTarget = document.createElement('div');\n  document.body.appendChild(nextTarget);\n\n  const nextCleanup = markOthers([nextTarget], { ariaHidden: true });\n\n  expect(target.getAttribute('aria-hidden')).toBe('true');\n  expect(nextTarget.getAttribute('aria-hidden')).toBe(null);\n\n  cleanup();\n\n  expect(nextTarget.getAttribute('aria-hidden')).toBe(null);\n  expect(target.getAttribute('aria-hidden')).toBe('true');\n  expect(other.getAttribute('aria-hidden')).toBe('true');\n\n  nextCleanup();\n\n  expect(nextTarget.getAttribute('aria-hidden')).toBe(null);\n  expect(other.getAttribute('aria-hidden')).toBe(null);\n  expect(target.getAttribute('aria-hidden')).toBe(null);\n});\n\ntest('multiple cleanups with differing controlAttribute', () => {\n  const other = document.createElement('div');\n  document.body.appendChild(other);\n  const target = document.createElement('div');\n  target.setAttribute('data-testid', '1');\n  document.body.appendChild(target);\n\n  const cleanup = markOthers([target], { ariaHidden: true });\n\n  expect(other.getAttribute('aria-hidden')).toBe('true');\n\n  const target2 = document.createElement('div');\n  target2.setAttribute('data-testid', '2');\n  document.body.appendChild(target2);\n\n  const cleanup2 = markOthers([target2]);\n\n  expect(target.getAttribute('aria-hidden')).not.toBe('true');\n  expect(target.getAttribute('data-base-ui-inert')).toBe('');\n\n  cleanup();\n\n  expect(other.getAttribute('aria-hidden')).toBe(null);\n\n  cleanup2();\n\n  expect(target.getAttribute('data-base-ui-inert')).toBe(null);\n});\n\ntest('mixed controlAttribute usage (aria-hidden/inert/none)', () => {\n  const other = document.createElement('div');\n  document.body.appendChild(other);\n\n  const A = document.createElement('div');\n  A.setAttribute('data-testid', 'A');\n  document.body.appendChild(A);\n\n  const B = document.createElement('div');\n  B.setAttribute('data-testid', 'B');\n  document.body.appendChild(B);\n\n  const C = document.createElement('div');\n  C.setAttribute('data-testid', 'C');\n  document.body.appendChild(C);\n\n  const cleanupA = markOthers([A], { ariaHidden: true });\n\n  expect(other.getAttribute('aria-hidden')).toBe('true');\n  expect(other.hasAttribute('inert')).toBe(false);\n  expect(other.getAttribute('data-base-ui-inert')).toBe('');\n\n  const cleanupB = markOthers([B], { inert: true });\n\n  expect(other.getAttribute('aria-hidden')).toBe('true');\n  expect(other.getAttribute('inert')).toBe('');\n  expect(other.getAttribute('data-base-ui-inert')).toBe('');\n\n  const cleanupC = markOthers([C]);\n\n  expect(other.getAttribute('aria-hidden')).toBe('true');\n  expect(other.getAttribute('inert')).toBe('');\n  expect(other.getAttribute('data-base-ui-inert')).toBe('');\n\n  cleanupC();\n\n  expect(other.getAttribute('aria-hidden')).toBe('true');\n  expect(other.getAttribute('inert')).toBe('');\n  expect(other.getAttribute('data-base-ui-inert')).toBe('');\n\n  cleanupB();\n\n  expect(other.getAttribute('aria-hidden')).toBe('true');\n  expect(other.hasAttribute('inert')).toBe(false);\n  expect(other.getAttribute('data-base-ui-inert')).toBe('');\n\n  cleanupA();\n\n  expect(other.hasAttribute('aria-hidden')).toBe(false);\n  expect(other.hasAttribute('inert')).toBe(false);\n  expect(other.hasAttribute('data-base-ui-inert')).toBe(false);\n});\n\ntest('tracks externally controlled attributes per control attribute', () => {\n  const container = document.createElement('div');\n  const keep = document.createElement('div');\n  const outside = document.createElement('div');\n\n  outside.setAttribute('inert', '');\n  container.append(keep, outside);\n  document.body.append(container);\n\n  let cleanupInert: (() => void) | undefined;\n  let cleanupAriaHidden: (() => void) | undefined;\n\n  try {\n    cleanupInert = markOthers([keep], { inert: true });\n    cleanupAriaHidden = markOthers([keep], { ariaHidden: true });\n\n    expect(outside).toHaveAttribute('inert');\n    expect(outside).toHaveAttribute('aria-hidden', 'true');\n\n    cleanupAriaHidden();\n    cleanupAriaHidden = undefined;\n\n    expect(outside).toHaveAttribute('inert');\n    expect(outside).not.toHaveAttribute('aria-hidden');\n\n    cleanupInert();\n    cleanupInert = undefined;\n\n    expect(outside).toHaveAttribute('inert');\n    expect(outside).not.toHaveAttribute('aria-hidden');\n  } finally {\n    cleanupAriaHidden?.();\n    cleanupInert?.();\n  }\n});\n\ntest('ignores marker elements without affecting aria-hidden', () => {\n  const floating = document.createElement('div');\n  document.body.appendChild(floating);\n\n  const reference = document.createElement('div');\n  document.body.appendChild(reference);\n\n  const outside = document.createElement('div');\n  document.body.appendChild(outside);\n\n  const cleanup = markOthers([floating], {\n    ariaHidden: true,\n    markerIgnoreElements: [reference],\n  });\n\n  expect(outside.getAttribute('data-base-ui-inert')).toBe('');\n  expect(reference.getAttribute('data-base-ui-inert')).toBe(null);\n  expect(reference.getAttribute('aria-hidden')).toBe('true');\n\n  cleanup();\n\n  expect(outside.getAttribute('data-base-ui-inert')).toBe(null);\n  expect(reference.getAttribute('aria-hidden')).toBe(null);\n});\n\ntest('keeps marker on top-level outside node with nested markerIgnoreElements', () => {\n  const floating = document.createElement('div');\n  document.body.appendChild(floating);\n\n  const outsideWrapper = document.createElement('div');\n  const ariaLive = document.createElement('div');\n  ariaLive.setAttribute('aria-live', 'polite');\n  const sibling = document.createElement('button');\n  outsideWrapper.append(ariaLive, sibling);\n  document.body.append(outsideWrapper);\n\n  const cleanup = markOthers([floating], {\n    ariaHidden: true,\n    markerIgnoreElements: [ariaLive],\n  });\n\n  expect(outsideWrapper).toHaveAttribute('data-base-ui-inert');\n  expect(ariaLive).not.toHaveAttribute('data-base-ui-inert');\n  expect(ariaLive).not.toHaveAttribute('aria-hidden');\n  expect(sibling).not.toHaveAttribute('data-base-ui-inert');\n  expect(sibling).toHaveAttribute('aria-hidden', 'true');\n\n  cleanup();\n\n  expect(outsideWrapper).not.toHaveAttribute('data-base-ui-inert');\n  expect(sibling).not.toHaveAttribute('aria-hidden');\n});\n\ntest('keeps marker top-level when ignored descendant is nested in a branch', () => {\n  const floating = document.createElement('div');\n  document.body.appendChild(floating);\n\n  const outsideRoot = document.createElement('div');\n  const ignoredBranch = document.createElement('div');\n  const ignoredParent = document.createElement('div');\n  const ignoredDescendant = document.createElement('div');\n  ignoredDescendant.setAttribute('aria-live', 'polite');\n  ignoredParent.append(ignoredDescendant);\n  ignoredBranch.append(ignoredParent);\n\n  const siblingBranch = document.createElement('div');\n  const siblingLeaf = document.createElement('button');\n  siblingBranch.append(siblingLeaf);\n\n  outsideRoot.append(ignoredBranch, siblingBranch);\n  document.body.append(outsideRoot);\n\n  const cleanup = markOthers([floating], {\n    ariaHidden: true,\n    markerIgnoreElements: [ignoredDescendant],\n  });\n\n  expect(outsideRoot).toHaveAttribute('data-base-ui-inert');\n  expect(ignoredBranch).not.toHaveAttribute('data-base-ui-inert');\n  expect(ignoredParent).not.toHaveAttribute('data-base-ui-inert');\n  expect(ignoredDescendant).not.toHaveAttribute('data-base-ui-inert');\n  expect(ignoredDescendant).not.toHaveAttribute('aria-hidden');\n  expect(siblingBranch).not.toHaveAttribute('data-base-ui-inert');\n  expect(siblingBranch).toHaveAttribute('aria-hidden', 'true');\n  expect(siblingLeaf).not.toHaveAttribute('data-base-ui-inert');\n\n  cleanup();\n\n  expect(outsideRoot).not.toHaveAttribute('data-base-ui-inert');\n  expect(siblingBranch).not.toHaveAttribute('aria-hidden');\n});\n\ntest('preserves externally owned aria-hidden during concurrent aria-hidden and inert overlaps', () => {\n  const keep = document.createElement('div');\n  const outside = document.createElement('div');\n  outside.setAttribute('aria-hidden', 'true');\n  document.body.append(keep, outside);\n\n  let cleanupAriaHidden: (() => void) | undefined;\n  let cleanupInert: (() => void) | undefined;\n\n  try {\n    cleanupAriaHidden = markOthers([keep], { ariaHidden: true, mark: false });\n    cleanupInert = markOthers([keep], { inert: true, mark: false });\n\n    expect(outside).toHaveAttribute('aria-hidden', 'true');\n    expect(outside).toHaveAttribute('inert');\n\n    cleanupInert();\n    cleanupInert = undefined;\n\n    expect(outside).toHaveAttribute('aria-hidden', 'true');\n    expect(outside).not.toHaveAttribute('inert');\n\n    cleanupAriaHidden();\n    cleanupAriaHidden = undefined;\n\n    expect(outside).toHaveAttribute('aria-hidden', 'true');\n    expect(outside).not.toHaveAttribute('inert');\n  } finally {\n    cleanupInert?.();\n    cleanupAriaHidden?.();\n  }\n});\n\ntest('does not let mark-only overlap disturb control cleanup bookkeeping', () => {\n  const keep = document.createElement('div');\n  const outside = document.createElement('div');\n  document.body.append(keep, outside);\n\n  let cleanupMarkOnly: (() => void) | undefined;\n  let cleanupControlOnly: (() => void) | undefined;\n\n  try {\n    cleanupMarkOnly = markOthers([keep], { mark: true });\n    cleanupControlOnly = markOthers([keep], { ariaHidden: true, mark: false });\n\n    expect(outside).toHaveAttribute('data-base-ui-inert');\n    expect(outside).toHaveAttribute('aria-hidden', 'true');\n\n    cleanupMarkOnly();\n    cleanupMarkOnly = undefined;\n\n    expect(outside).not.toHaveAttribute('data-base-ui-inert');\n    expect(outside).toHaveAttribute('aria-hidden', 'true');\n\n    cleanupControlOnly();\n    cleanupControlOnly = undefined;\n\n    expect(outside).not.toHaveAttribute('data-base-ui-inert');\n    expect(outside).not.toHaveAttribute('aria-hidden');\n  } finally {\n    cleanupControlOnly?.();\n    cleanupMarkOnly?.();\n  }\n});\n\ntest('does not recurse infinitely with target inside anchor in shadow root', () => {\n  const host = document.createElement('div');\n  document.body.appendChild(host);\n\n  const shadowRoot = host.attachShadow({ mode: 'open' });\n  const anchor = document.createElement('a');\n  anchor.href = 'https://floating-ui.com';\n\n  const target = document.createElement('button');\n  anchor.appendChild(target);\n  shadowRoot.appendChild(anchor);\n\n  const cleanup = markOthers([target], { ariaHidden: true });\n\n  cleanup();\n});\n\ntest('uses shadow root host as avoid element when parent chain includes anchor', () => {\n  const outside = document.createElement('div');\n  document.body.appendChild(outside);\n\n  const host = document.createElement('div');\n  document.body.appendChild(host);\n\n  const shadowRoot = host.attachShadow({ mode: 'open' });\n  const anchor = document.createElement('a');\n  anchor.href = 'https://floating-ui.com';\n\n  const target = document.createElement('button');\n  anchor.appendChild(target);\n  shadowRoot.appendChild(anchor);\n\n  const cleanup = markOthers([target], { ariaHidden: true });\n\n  expect(outside.getAttribute('aria-hidden')).toBe('true');\n  expect(host.getAttribute('aria-hidden')).toBe(null);\n\n  cleanup();\n\n  expect(outside.getAttribute('aria-hidden')).toBe(null);\n});\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/utils/markOthers.ts",
    "content": "// Modified to add conditional `aria-hidden` support:\n// https://github.com/theKashey/aria-hidden/blob/9220c8f4a4fd35f63bee5510a9f41a37264382d4/src/index.ts\nimport { getNodeName, isShadowRoot } from '@floating-ui/utils/dom';\nimport { ownerDocument } from '@base-ui/utils/owner';\n\ntype Undo = () => void;\n\ninterface MarkOthersOptions {\n  ariaHidden?: boolean | undefined;\n  inert?: boolean | undefined;\n  mark?: boolean | undefined;\n  markerIgnoreElements?: Element[] | undefined;\n}\n\nconst counters = {\n  inert: new WeakMap<Element, number>(),\n  'aria-hidden': new WeakMap<Element, number>(),\n};\n\nconst markerName = 'data-base-ui-inert';\ntype ControlAttribute = keyof typeof counters;\n\nconst uncontrolledElementsSets: Record<ControlAttribute, WeakSet<Element>> = {\n  inert: new WeakSet<Element>(),\n  'aria-hidden': new WeakSet<Element>(),\n};\nlet markerCounterMap = new WeakMap<Element, number>();\nlet lockCount = 0;\n\nfunction getUncontrolledElementsSet(controlAttribute: ControlAttribute) {\n  return uncontrolledElementsSets[controlAttribute];\n}\n\nexport const supportsInert = (): boolean =>\n  typeof HTMLElement !== 'undefined' && 'inert' in HTMLElement.prototype;\n\nfunction unwrapHost(node: Node | null): Element | null {\n  if (!node) {\n    return null;\n  }\n\n  return isShadowRoot(node) ? node.host : unwrapHost(node.parentNode);\n}\n\nconst correctElements = (parent: HTMLElement, targets: Element[]): Element[] =>\n  targets\n    .map((target) => {\n      if (parent.contains(target)) {\n        return target;\n      }\n\n      const correctedTarget = unwrapHost(target);\n\n      if (parent.contains(correctedTarget)) {\n        return correctedTarget;\n      }\n\n      return null;\n    })\n    .filter((x): x is Element => x != null);\n\nconst buildKeepSet = (targets: Element[]): Set<Node> => {\n  const keep = new Set<Node>();\n\n  targets.forEach((target) => {\n    let node: Node | null = target;\n    while (node && !keep.has(node)) {\n      keep.add(node);\n      node = node.parentNode;\n    }\n  });\n\n  return keep;\n};\n\nconst collectOutsideElements = (\n  root: HTMLElement,\n  keepElements: Set<Node>,\n  stopElements: Set<Node>,\n): Element[] => {\n  const outside: Element[] = [];\n\n  const walk = (parent: Element | null) => {\n    if (!parent || stopElements.has(parent)) {\n      return;\n    }\n\n    Array.from(parent.children).forEach((node: Element) => {\n      if (getNodeName(node) === 'script') {\n        return;\n      }\n\n      if (keepElements.has(node)) {\n        walk(node);\n      } else {\n        outside.push(node);\n      }\n    });\n  };\n\n  walk(root);\n\n  return outside;\n};\n\nfunction applyAttributeToOthers(\n  uncorrectedAvoidElements: Element[],\n  body: HTMLElement,\n  ariaHidden: boolean,\n  inert: boolean,\n  { mark = true, markerIgnoreElements = [] }: MarkOthersOptions,\n): Undo {\n  // eslint-disable-next-line no-nested-ternary\n  const controlAttribute = inert ? 'inert' : ariaHidden ? 'aria-hidden' : null;\n  let counterMap: WeakMap<Element, number> | null = null;\n  let uncontrolledElementsSet: WeakSet<Element> | null = null;\n  const avoidElements = correctElements(body, uncorrectedAvoidElements);\n  const markerIgnoreTargets = mark ? correctElements(body, markerIgnoreElements) : [];\n  const markerIgnoreSet = new Set<Node>(markerIgnoreTargets);\n  const markerTargets = mark\n    ? collectOutsideElements(\n        body,\n        buildKeepSet(avoidElements),\n        new Set<Node>(avoidElements),\n      ).filter((target) => !markerIgnoreSet.has(target))\n    : [];\n  const hiddenElements: Element[] = [];\n  const markedElements: Element[] = [];\n\n  if (controlAttribute) {\n    const map = counters[controlAttribute];\n    const currentUncontrolledElementsSet = getUncontrolledElementsSet(controlAttribute);\n    uncontrolledElementsSet = currentUncontrolledElementsSet;\n    counterMap = map;\n    const ariaLiveElements = correctElements(\n      body,\n      Array.from(body.querySelectorAll('[aria-live]')),\n    );\n    const controlElements = avoidElements.concat(ariaLiveElements);\n    const controlTargets = collectOutsideElements(\n      body,\n      buildKeepSet(controlElements),\n      new Set<Node>(controlElements),\n    );\n\n    controlTargets.forEach((node) => {\n      const attr = node.getAttribute(controlAttribute);\n      const alreadyHidden = attr !== null && attr !== 'false';\n      const counterValue = (map.get(node) || 0) + 1;\n\n      map.set(node, counterValue);\n      hiddenElements.push(node);\n\n      if (counterValue === 1 && alreadyHidden) {\n        currentUncontrolledElementsSet.add(node);\n      }\n\n      if (!alreadyHidden) {\n        node.setAttribute(controlAttribute, controlAttribute === 'inert' ? '' : 'true');\n      }\n    });\n  }\n\n  if (mark) {\n    markerTargets.forEach((node) => {\n      const markerValue = (markerCounterMap.get(node) || 0) + 1;\n\n      markerCounterMap.set(node, markerValue);\n      markedElements.push(node);\n\n      if (markerValue === 1) {\n        node.setAttribute(markerName, '');\n      }\n    });\n  }\n\n  lockCount += 1;\n\n  return () => {\n    if (counterMap) {\n      hiddenElements.forEach((element) => {\n        const currentCounterValue = counterMap.get(element) || 0;\n        const counterValue = currentCounterValue - 1;\n        counterMap.set(element, counterValue);\n\n        if (!counterValue) {\n          if (!uncontrolledElementsSet?.has(element) && controlAttribute) {\n            element.removeAttribute(controlAttribute);\n          }\n\n          uncontrolledElementsSet?.delete(element);\n        }\n      });\n    }\n\n    if (mark) {\n      markedElements.forEach((element) => {\n        const markerValue = (markerCounterMap.get(element) || 0) - 1;\n\n        markerCounterMap.set(element, markerValue);\n\n        if (!markerValue) {\n          element.removeAttribute(markerName);\n        }\n      });\n    }\n\n    lockCount -= 1;\n\n    if (!lockCount) {\n      counters.inert = new WeakMap();\n      counters['aria-hidden'] = new WeakMap();\n      uncontrolledElementsSets.inert = new WeakSet();\n      uncontrolledElementsSets['aria-hidden'] = new WeakSet();\n      markerCounterMap = new WeakMap();\n    }\n  };\n}\n\nexport function markOthers(avoidElements: Element[], options: MarkOthersOptions = {}): Undo {\n  const { ariaHidden = false, inert = false, mark = true, markerIgnoreElements = [] } = options;\n  const body = ownerDocument(avoidElements[0]).body;\n  return applyAttributeToOthers(avoidElements, body, ariaHidden, inert, {\n    mark,\n    markerIgnoreElements,\n  });\n}\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/utils/nodes.test.ts",
    "content": "import { expect } from 'vitest';\nimport type { FloatingContext } from '../types';\nimport { getNodeAncestors, getNodeChildren } from './nodes';\n\nconst contextOpen = { open: true } as FloatingContext;\nconst contextClosed = { open: false } as FloatingContext;\n\ntest('getNodeChildren returns an array of children, ignoring closed ones when onlyOpenChildren=true', () => {\n  expect(\n    getNodeChildren(\n      [\n        { id: '0', parentId: null, context: contextOpen },\n        { id: '1', parentId: '0', context: contextOpen },\n        { id: '2', parentId: '1', context: contextOpen },\n        { id: '3', parentId: '1', context: contextOpen },\n        { id: '4', parentId: '1', context: contextClosed },\n      ],\n      '0',\n      true,\n    ),\n  ).toEqual([\n    { id: '1', parentId: '0', context: contextOpen },\n    { id: '2', parentId: '1', context: contextOpen },\n    { id: '3', parentId: '1', context: contextOpen },\n  ]);\n});\n\ntest('getNodeChildren returns an array of children, including closed ones when onlyOpenChildren=false', () => {\n  expect(\n    getNodeChildren(\n      [\n        { id: '0', parentId: null, context: contextOpen },\n        { id: '1', parentId: '0', context: contextOpen },\n        { id: '2', parentId: '1', context: contextOpen },\n        { id: '3', parentId: '1', context: contextOpen },\n        { id: '4', parentId: '1', context: contextClosed },\n      ],\n      '0',\n      false,\n    ),\n  ).toEqual([\n    { id: '1', parentId: '0', context: contextOpen },\n    { id: '2', parentId: '1', context: contextOpen },\n    { id: '3', parentId: '1', context: contextOpen },\n    { id: '4', parentId: '1', context: contextClosed },\n  ]);\n});\n\ntest('getNodeChildren handles deep parent structures correctly (onlyOpenChildren=true)', () => {\n  const nodes = [\n    { id: '0', parentId: null, context: contextOpen },\n    { id: '1', parentId: '0', context: contextOpen },\n    { id: '2', parentId: '1', context: contextClosed },\n    { id: '3', parentId: '2', context: contextOpen },\n    { id: '4', parentId: '2', context: contextClosed },\n    { id: '5', parentId: '0', context: contextOpen },\n    { id: '6', parentId: '5', context: contextOpen },\n  ];\n\n  expect(getNodeChildren(nodes, '0', true)).toEqual([\n    { id: '1', parentId: '0', context: contextOpen },\n    { id: '5', parentId: '0', context: contextOpen },\n    { id: '6', parentId: '5', context: contextOpen },\n  ]);\n});\n\ntest('getNodeChildren handles deep parent structures correctly (onlyOpenChildren=false)', () => {\n  const nodes = [\n    { id: '0', parentId: null, context: contextOpen },\n    { id: '1', parentId: '0', context: contextOpen },\n    { id: '2', parentId: '1', context: contextClosed },\n    { id: '3', parentId: '2', context: contextOpen },\n    { id: '4', parentId: '2', context: contextClosed },\n    { id: '5', parentId: '0', context: contextOpen },\n    { id: '6', parentId: '5', context: contextOpen },\n  ];\n\n  expect(getNodeChildren(nodes, '0', false)).toEqual([\n    { id: '1', parentId: '0', context: contextOpen },\n    { id: '2', parentId: '1', context: contextClosed },\n    { id: '3', parentId: '2', context: contextOpen },\n    { id: '4', parentId: '2', context: contextClosed },\n    { id: '5', parentId: '0', context: contextOpen },\n    { id: '6', parentId: '5', context: contextOpen },\n  ]);\n});\n\ntest('getNodeAncestors returns an array of ancestors', () => {\n  expect(\n    getNodeAncestors(\n      [\n        { id: '0', parentId: null },\n        { id: '1', parentId: '0' },\n        { id: '2', parentId: '1' },\n      ],\n      '2',\n    ),\n  ).toEqual([\n    { id: '1', parentId: '0' },\n    { id: '0', parentId: null },\n  ]);\n});\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/utils/nodes.ts",
    "content": "import type { FloatingNodeType } from '../types';\n\n/* eslint-disable @typescript-eslint/no-loop-func */\n\nexport function getNodeChildren(\n  nodes: Array<FloatingNodeType>,\n  id: string | undefined,\n  onlyOpenChildren = true,\n): Array<FloatingNodeType> {\n  const directChildren = nodes.filter(\n    (node) => node.parentId === id && (!onlyOpenChildren || node.context?.open),\n  );\n  return directChildren.flatMap((child) => [\n    child,\n    ...getNodeChildren(nodes, child.id, onlyOpenChildren),\n  ]);\n}\n\nexport function getDeepestNode(nodes: Array<FloatingNodeType>, id: string | undefined) {\n  let deepestNodeId: string | undefined;\n  let maxDepth = -1;\n\n  function findDeepest(nodeId: string | undefined, depth: number) {\n    if (depth > maxDepth) {\n      deepestNodeId = nodeId;\n      maxDepth = depth;\n    }\n\n    const children = getNodeChildren(nodes, nodeId);\n\n    children.forEach((child) => {\n      findDeepest(child.id, depth + 1);\n    });\n  }\n\n  findDeepest(id, 0);\n\n  return nodes.find((node) => node.id === deepestNodeId);\n}\n\nexport function getNodeAncestors(nodes: Array<FloatingNodeType>, id: string | undefined) {\n  let allAncestors: Array<FloatingNodeType> = [];\n  let currentParentId = nodes.find((node) => node.id === id)?.parentId;\n\n  while (currentParentId) {\n    const currentNode = nodes.find((node) => node.id === currentParentId);\n    currentParentId = currentNode?.parentId;\n\n    if (currentNode) {\n      allAncestors = allAncestors.concat(currentNode);\n    }\n  }\n\n  return allAncestors;\n}\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/utils/tabbable.ts",
    "content": "import { tabbable, type FocusableElement } from 'tabbable';\nimport { ownerDocument } from '@base-ui/utils/owner';\nimport { activeElement, contains } from './element';\n\nexport const getTabbableOptions = () =>\n  ({\n    getShadowRoot: true,\n    displayCheck:\n      // JSDOM does not support the `tabbable` library. To solve this we can\n      // check if `ResizeObserver` is a real function (not polyfilled), which\n      // determines if the current environment is JSDOM-like.\n      typeof ResizeObserver === 'function' && ResizeObserver.toString().includes('[native code]')\n        ? 'full'\n        : 'none',\n  }) as const;\n\nfunction getTabbableIn(container: HTMLElement, dir: 1 | -1): FocusableElement | undefined {\n  const list = tabbable(container, getTabbableOptions());\n  const len = list.length;\n  if (len === 0) {\n    return undefined;\n  }\n\n  const active = activeElement(ownerDocument(container)) as FocusableElement;\n  const index = list.indexOf(active);\n  // eslint-disable-next-line no-nested-ternary\n  const nextIndex = index === -1 ? (dir === 1 ? 0 : len - 1) : index + dir;\n\n  return list[nextIndex];\n}\n\nexport function getNextTabbable(referenceElement: Element | null): FocusableElement | null {\n  return (\n    getTabbableIn(ownerDocument(referenceElement).body, 1) || (referenceElement as FocusableElement)\n  );\n}\n\nexport function getPreviousTabbable(referenceElement: Element | null): FocusableElement | null {\n  return (\n    getTabbableIn(ownerDocument(referenceElement).body, -1) ||\n    (referenceElement as FocusableElement)\n  );\n}\n\nfunction getTabbableNearElement(referenceElement: Element | null, dir: 1 | -1) {\n  if (!referenceElement) {\n    return null;\n  }\n\n  const list = tabbable(ownerDocument(referenceElement).body, getTabbableOptions());\n  const elementCount = list.length;\n  if (elementCount === 0) {\n    return null;\n  }\n\n  const index = list.indexOf(referenceElement as FocusableElement);\n  if (index === -1) {\n    return null;\n  }\n\n  const nextIndex = (index + dir + elementCount) % elementCount;\n  return list[nextIndex];\n}\n\nexport function getTabbableAfterElement(referenceElement: Element | null): FocusableElement | null {\n  return getTabbableNearElement(referenceElement, 1);\n}\n\nexport function getTabbableBeforeElement(\n  referenceElement: Element | null,\n): FocusableElement | null {\n  return getTabbableNearElement(referenceElement, -1);\n}\n\nexport function isOutsideEvent(event: FocusEvent | React.FocusEvent, container?: Element) {\n  const containerElement = container || (event.currentTarget as Element);\n  const relatedTarget = event.relatedTarget as HTMLElement | null;\n  return !relatedTarget || !contains(containerElement, relatedTarget);\n}\n\nexport function disableFocusInside(container: HTMLElement) {\n  const tabbableElements = tabbable(container, getTabbableOptions());\n  tabbableElements.forEach((element) => {\n    element.dataset.tabindex = element.getAttribute('tabindex') || '';\n    element.setAttribute('tabindex', '-1');\n  });\n}\n\nexport function enableFocusInside(container: HTMLElement) {\n  const elements = container.querySelectorAll<HTMLElement>('[data-tabindex]');\n  elements.forEach((element) => {\n    const tabindex = element.dataset.tabindex;\n    delete element.dataset.tabindex;\n    if (tabindex) {\n      element.setAttribute('tabindex', tabindex);\n    } else {\n      element.removeAttribute('tabindex');\n    }\n  });\n}\n"
  },
  {
    "path": "packages/react/src/floating-ui-react/utils.ts",
    "content": "export * from './utils/element';\nexport * from './utils/nodes';\nexport * from './utils/event';\nexport * from './utils/composite';\nexport * from './utils/tabbable';\n"
  },
  {
    "path": "packages/react/src/form/Form.spec.tsx",
    "content": "import * as React from 'react';\nimport { expectType } from '#test-utils';\nimport { Form } from '@base-ui/react/form';\n\ninterface Values {\n  name: string;\n  age: number;\n}\n\n<Form<Values>\n  onFormSubmit={(values) => {\n    expectType<string, typeof values.name>(values.name);\n    expectType<number, typeof values.age>(values.age);\n    // @ts-expect-error\n    values.email.startsWith('a');\n  }}\n/>;\n"
  },
  {
    "path": "packages/react/src/form/Form.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { Form } from '@base-ui/react/form';\nimport { Field } from '@base-ui/react/field';\nimport { NumberField } from '@base-ui/react/number-field';\nimport { createRenderer, fireEvent, screen } from '@mui/internal-test-utils';\nimport { describeConformance } from '../../test/describeConformance';\n\ndescribe('<Form />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Form />, () => ({\n    refInstanceof: window.HTMLFormElement,\n    render,\n  }));\n\n  it('does not submit if there are errors', async () => {\n    const onSubmit = vi.fn();\n\n    const { user } = render(\n      <Form onSubmit={onSubmit}>\n        <Field.Root>\n          <Field.Control required />\n          <Field.Error data-testid=\"error\" />\n        </Field.Root>\n        <button>Submit</button>\n      </Form>,\n    );\n\n    const submit = screen.getByRole('button');\n\n    await user.click(submit);\n\n    expect(screen.getByTestId('error')).not.toBe(null);\n    expect(onSubmit.mock.calls.length > 0).toBe(false);\n  });\n\n  it('unmounted fields should be removed from the form', async () => {\n    const submitSpy = vi.fn((event) => event.preventDefault());\n    function App() {\n      const [checked, setChecked] = React.useState(true);\n\n      return (\n        <Form onSubmit={submitSpy}>\n          <Field.Root name=\"name\">\n            <Field.Control defaultValue=\"Alice\" />\n          </Field.Root>\n\n          <input type=\"checkbox\" checked={checked} onChange={() => setChecked(!checked)} />\n\n          {checked && (\n            <Field.Root name=\"email\">\n              <Field.Control defaultValue=\"\" required data-testid=\"email\" />\n            </Field.Root>\n          )}\n\n          <button>Submit</button>\n        </Form>\n      );\n    }\n\n    const { user } = await render(<App />);\n\n    const submit = screen.getByText('Submit');\n\n    await user.click(submit);\n    expect(submitSpy.mock.calls.length).toBe(0);\n    expect(screen.getByTestId('email')).toHaveAttribute('aria-invalid', 'true');\n\n    await user.click(screen.getByRole('checkbox'));\n    await user.click(submit);\n    expect(submitSpy.mock.calls.length).toBe(1);\n  });\n\n  describe('prop: errors', () => {\n    it('should mark <Field.Control> as invalid and populate <Field.Error>', () => {\n      render(\n        <Form errors={{ foo: 'bar' }}>\n          <Field.Root name=\"foo\">\n            <Field.Control />\n            <Field.Error data-testid=\"error\" />\n          </Field.Root>\n        </Form>,\n      );\n\n      expect(screen.getByTestId('error')).toHaveTextContent('bar');\n      expect(screen.getByRole('textbox')).toHaveAttribute('aria-invalid', 'true');\n    });\n\n    it('should not mark <Field.Control> as invalid if no error is provided', () => {\n      render(\n        <Form>\n          <Field.Root name=\"foo\">\n            <Field.Control />\n            <Field.Error data-testid=\"error\" />\n          </Field.Root>\n        </Form>,\n      );\n\n      expect(screen.queryByTestId('error')).toBe(null);\n      expect(screen.getByRole('textbox')).not.toHaveAttribute('aria-invalid');\n    });\n\n    function App() {\n      const [errors, setErrors] = React.useState<Form.Props['errors']>({});\n\n      return (\n        <Form\n          errors={errors}\n          onSubmit={(event) => {\n            event.preventDefault();\n            const formData = new FormData(event.currentTarget);\n            const name = formData.get('name') as string;\n            const age = formData.get('age') as string;\n\n            setErrors({\n              ...(name === '' && { name: 'Name is required' }),\n              ...(age === '' && { age: 'Age is required' }),\n            });\n          }}\n        >\n          <Field.Root name=\"name\">\n            <Field.Control data-testid=\"name\" />\n            <Field.Error data-testid=\"name-error\" />\n          </Field.Root>\n          <Field.Root name=\"age\">\n            <Field.Control data-testid=\"age\" />\n            <Field.Error data-testid=\"age-error\" />\n          </Field.Root>\n          <button type=\"submit\">Submit</button>\n        </Form>\n      );\n    }\n\n    it('focuses the first invalid field only on submit', async () => {\n      const { user } = render(<App />);\n\n      const submit = screen.getByRole('button');\n      const name = screen.getByTestId('name');\n      const age = screen.getByTestId('age');\n\n      await user.click(submit);\n\n      expect(name).toHaveFocus();\n\n      fireEvent.change(name, { target: { value: 'John' } });\n\n      expect(age).not.toHaveFocus();\n\n      await user.click(submit);\n\n      expect(age).toHaveFocus();\n\n      fireEvent.change(age, { target: { value: '42' } });\n\n      await user.click(submit);\n\n      expect(age).not.toHaveFocus();\n    });\n\n    it('does not swap focus immediately on change after two submissions', async () => {\n      const { user } = render(<App />);\n\n      const submit = screen.getByRole('button');\n      const name = screen.getByTestId('name');\n      const age = screen.getByTestId('age');\n\n      await user.click(submit);\n\n      expect(name).toHaveFocus();\n\n      await user.click(submit);\n\n      fireEvent.change(name, { target: { value: 'John' } });\n\n      expect(age).not.toHaveFocus();\n    });\n\n    it('removes errors upon change', async () => {\n      render(<App />);\n\n      const name = screen.getByTestId('name');\n      const age = screen.getByTestId('age');\n\n      fireEvent.click(screen.getByText('Submit'));\n\n      expect(screen.queryByTestId('name-error')).not.toBe(null);\n      expect(screen.queryByTestId('age-error')).not.toBe(null);\n\n      fireEvent.change(name, { target: { value: 'John' } });\n      fireEvent.change(age, { target: { value: '42' } });\n      expect(screen.queryByTestId('name-error')).toBe(null);\n      expect(screen.queryByTestId('age-error')).toBe(null);\n    });\n\n    it('runs field validation on first change after Form error is set', async () => {\n      const validateSpy = vi.fn((value: unknown) => {\n        if (value === 'abcd') {\n          return 'field error';\n        }\n        return null;\n      });\n\n      function Test() {\n        const [errors, setErrors] = React.useState<Form.Props['errors']>({});\n\n        return (\n          <Form\n            errors={errors}\n            onSubmit={(event) => {\n              event.preventDefault();\n              const formData = new FormData(event.currentTarget);\n              const name = formData.get('name') as string;\n\n              if (name === 'abcde') {\n                setErrors({ name: 'submit error' });\n              } else {\n                setErrors({});\n              }\n            }}\n          >\n            <Field.Root name=\"name\" validate={validateSpy}>\n              <Field.Control data-testid=\"name\" />\n              <Field.Error data-testid=\"name-error\" />\n            </Field.Root>\n            <button type=\"submit\">Submit</button>\n          </Form>\n        );\n      }\n\n      const { user } = render(<Test />);\n\n      const input = screen.getByTestId('name');\n      await user.click(input);\n      await user.keyboard('abcde');\n      await user.click(screen.getByRole('button', { name: 'Submit' }));\n      expect(screen.queryByTestId('name-error')).not.toBe(null);\n      expect(screen.getByTestId('name-error')).toHaveTextContent('submit error');\n\n      validateSpy.mockClear();\n\n      await user.click(input);\n      // value changes from 'abcde' to 'abcd'\n      await user.keyboard('{Backspace}');\n      expect(validateSpy.mock.calls.length).toBe(1);\n      expect(screen.queryByTestId('name-error')).not.toBe(null);\n      expect(screen.getByTestId('name-error')).toHaveTextContent('field error');\n    });\n\n    it('runs field validation on change when invalid prop is true and validationMode is onChange', async () => {\n      const validateSpy = vi.fn(() => 'field error');\n\n      function Test() {\n        return (\n          <Form errors={{ name: 'server error' }}>\n            <Field.Root name=\"name\" invalid validate={validateSpy} validationMode=\"onChange\">\n              <Field.Control data-testid=\"name\" />\n              <Field.Error data-testid=\"name-error\" />\n            </Field.Root>\n          </Form>\n        );\n      }\n\n      const { user } = render(<Test />);\n\n      const input = screen.getByTestId('name');\n      expect(screen.getByTestId('name-error')).toHaveTextContent('server error');\n\n      await user.click(input);\n      await user.keyboard('a');\n\n      expect(validateSpy.mock.calls.length).toBe(1);\n      expect(screen.getByTestId('name-error')).toHaveTextContent('field error');\n      expect(input).toHaveAttribute('aria-invalid', 'true');\n    });\n\n    it('does not run field validation on change for onBlur mode when invalid prop is true', async () => {\n      const validateSpy = vi.fn(() => 'field error');\n\n      function Test() {\n        return (\n          <Form errors={{ name: 'server error' }}>\n            <Field.Root name=\"name\" invalid validate={validateSpy} validationMode=\"onBlur\">\n              <Field.Control data-testid=\"name\" />\n              <Field.Error data-testid=\"name-error\" />\n            </Field.Root>\n          </Form>\n        );\n      }\n\n      const { user } = render(<Test />);\n\n      const input = screen.getByTestId('name');\n      expect(screen.getByTestId('name-error')).toHaveTextContent('server error');\n\n      await user.click(input);\n      await user.keyboard('a');\n      expect(validateSpy.mock.calls.length).toBe(0);\n      expect(screen.queryByTestId('name-error')).toBe(null);\n\n      await user.tab();\n      expect(validateSpy.mock.calls.length).toBe(1);\n      expect(screen.getByTestId('name-error')).toHaveTextContent('field error');\n    });\n  });\n\n  describe('prop: onFormSubmit', () => {\n    it('runs when the form is submitted', async () => {\n      const submitSpy = vi.fn((formValues, eventDetails) => ({ formValues, eventDetails }));\n\n      function App() {\n        return (\n          <Form onFormSubmit={submitSpy}>\n            <Field.Root name=\"username\">\n              <Field.Control defaultValue=\"alice132\" />\n            </Field.Root>\n            <Field.Root name=\"quantity\">\n              <NumberField.Root defaultValue={5}>\n                <NumberField.Input />\n              </NumberField.Root>\n            </Field.Root>\n            <button type=\"submit\">submit</button>\n          </Form>\n        );\n      }\n\n      render(<App />);\n\n      fireEvent.click(screen.getByText('submit'));\n\n      expect(submitSpy.mock.calls.length).toBe(1);\n      expect(submitSpy.mock.results.at(-1)?.value.formValues).toEqual({\n        username: 'alice132',\n        quantity: 5,\n      });\n      expect(submitSpy.mock.results.at(-1)?.value.eventDetails.event.defaultPrevented).toBe(true);\n    });\n\n    it('does not run when the form is invalid', async () => {\n      const submitSpy = vi.fn();\n\n      function App() {\n        return (\n          <Form onFormSubmit={submitSpy}>\n            <Field.Root name=\"username\">\n              <Field.Control defaultValue=\"\" required />\n              <Field.Error data-testid=\"error\" />\n            </Field.Root>\n            <button type=\"submit\">submit</button>\n          </Form>\n        );\n      }\n      render(<App />);\n      expect(screen.queryByTestId('error')).toBe(null);\n      fireEvent.click(screen.getByText('submit'));\n      expect(submitSpy.mock.calls.length).toBe(0);\n      expect(screen.queryByTestId('error')).not.toBe(null);\n    });\n  });\n\n  it('does not submit when invalid prop remains true even if validate returns null', async () => {\n    const submitSpy = vi.fn();\n    const validateSpy = vi.fn(() => null);\n\n    const { user } = render(\n      <Form onSubmit={submitSpy}>\n        <Field.Root name=\"name\" invalid validate={validateSpy} validationMode=\"onChange\">\n          <Field.Control data-testid=\"name\" />\n          <Field.Error data-testid=\"name-error\" />\n        </Field.Root>\n        <button type=\"submit\">submit</button>\n      </Form>,\n    );\n\n    const input = screen.getByTestId('name');\n    await user.click(input);\n    await user.keyboard('o');\n\n    expect(validateSpy.mock.calls.length).toBe(1);\n\n    await user.click(screen.getByText('submit'));\n\n    expect(submitSpy.mock.calls.length).toBe(0);\n    expect(input).toHaveAttribute('aria-invalid', 'true');\n  });\n\n  describe('prop: noValidate', () => {\n    it('should disable native validation if set to true (default)', () => {\n      render(<Form data-testid=\"form\" />);\n      expect(screen.getByTestId('form')).toHaveAttribute('novalidate');\n    });\n\n    it('should enable native validation if set to false', () => {\n      render(<Form noValidate={false} data-testid=\"form\" />);\n      expect(screen.getByTestId('form')).not.toHaveAttribute('novalidate');\n    });\n  });\n\n  describe('prop: actionsRef', () => {\n    it('validates the form when the `validate` method is called', async () => {\n      function App() {\n        const actionsRef = React.useRef<Form.Actions>(null);\n        return (\n          <div>\n            <Form actionsRef={actionsRef}>\n              <Field.Root name=\"username\">\n                <Field.Control defaultValue=\"\" required />\n                <Field.Error data-testid=\"error\" />\n              </Field.Root>\n              <Field.Root name=\"quantity\" validate={() => 'error'}>\n                <NumberField.Root defaultValue={5}>\n                  <NumberField.Input />\n                </NumberField.Root>\n                <Field.Error data-testid=\"error\" />\n              </Field.Root>\n              <button type=\"submit\">submit</button>\n            </Form>\n            <button type=\"button\" onClick={() => actionsRef.current?.validate()}>\n              validate\n            </button>\n          </div>\n        );\n      }\n\n      const { user } = await render(<App />);\n\n      expect(screen.queryByTestId('error')).toBe(null);\n\n      await user.click(screen.getByText('validate'));\n\n      await expect(screen.queryAllByTestId('error').length).toBe(2);\n    });\n\n    it('validates a field when the `validate` method is called with the field name', async () => {\n      function App() {\n        const actionsRef = React.useRef<Form.Actions>(null);\n        return (\n          <div>\n            <Form actionsRef={actionsRef}>\n              <Field.Root name=\"username\">\n                <Field.Control defaultValue=\"\" required />\n                <Field.Error data-testid=\"error\" />\n              </Field.Root>\n              <Field.Root name=\"quantity\" validate={() => 'number field error'}>\n                <NumberField.Root defaultValue={5}>\n                  <NumberField.Input />\n                </NumberField.Root>\n                <Field.Error data-testid=\"error\" />\n              </Field.Root>\n              <button type=\"submit\">submit</button>\n            </Form>\n            <button type=\"button\" onClick={() => actionsRef.current?.validate('quantity')}>\n              validate\n            </button>\n          </div>\n        );\n      }\n\n      const { user } = await render(<App />);\n\n      expect(screen.queryByTestId('error')).toBe(null);\n\n      await user.click(screen.getByText('validate'));\n\n      await expect(screen.queryByTestId('error')).toHaveTextContent('number field error');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/form/Form.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport {\n  createGenericEventDetails,\n  type BaseUIGenericEventDetails,\n} from '../utils/createBaseUIEventDetails';\nimport { REASONS } from '../utils/reasons';\nimport type { BaseUIComponentProps } from '../utils/types';\nimport { FormContext } from './FormContext';\nimport { useRenderElement } from '../utils/useRenderElement';\nimport { EMPTY_OBJECT } from '../utils/constants';\nimport { useValueChanged } from '../utils/useValueChanged';\n\n/**\n * A native form element with consolidated error handling.\n * Renders a `<form>` element.\n *\n * Documentation: [Base UI Form](https://base-ui.com/react/components/form)\n */\nexport const Form = React.forwardRef(function Form<\n  FormValues extends Record<string, any> = Record<string, any>,\n>(componentProps: Form.Props<FormValues>, forwardedRef: React.ForwardedRef<HTMLFormElement>) {\n  const {\n    render,\n    className,\n    validationMode = 'onSubmit',\n    errors: externalErrors,\n    onSubmit,\n    onFormSubmit,\n    actionsRef,\n    ...elementProps\n  } = componentProps;\n\n  const formRef = React.useRef<FormContext['formRef']['current']>({\n    fields: new Map(),\n  });\n  const submittedRef = React.useRef(false);\n  const submitAttemptedRef = React.useRef(false);\n\n  const focusControl = useStableCallback((control: HTMLElement | null) => {\n    if (!control) {\n      return;\n    }\n    control.focus();\n    if (control.tagName === 'INPUT') {\n      (control as HTMLInputElement).select();\n    }\n  });\n\n  const [errors, setErrors] = React.useState(externalErrors);\n\n  useValueChanged(externalErrors, () => {\n    setErrors(externalErrors);\n  });\n\n  React.useEffect(() => {\n    if (!submittedRef.current) {\n      return;\n    }\n\n    submittedRef.current = false;\n\n    const invalidFields = Array.from(formRef.current.fields.values()).filter(\n      (field) => field.validityData.state.valid === false,\n    );\n\n    if (invalidFields.length) {\n      focusControl(invalidFields[0].controlRef.current);\n    }\n  }, [errors, focusControl]);\n\n  const handleImperativeValidate = React.useCallback((fieldName?: string | undefined) => {\n    const values = Array.from(formRef.current.fields.values());\n\n    if (fieldName) {\n      const namedField = values.find((field) => field.name === fieldName);\n      if (namedField) {\n        namedField.validate(false);\n      }\n    } else {\n      values.forEach((field) => {\n        field.validate(false);\n      });\n    }\n  }, []);\n\n  React.useImperativeHandle(actionsRef, () => ({ validate: handleImperativeValidate }), [\n    handleImperativeValidate,\n  ]);\n\n  const element = useRenderElement('form', componentProps, {\n    ref: forwardedRef,\n    props: [\n      {\n        noValidate: true,\n        onSubmit(event) {\n          submitAttemptedRef.current = true;\n\n          let values = Array.from(formRef.current.fields.values());\n\n          // Async validation isn't supported to stop the submit event.\n          values.forEach((field) => {\n            field.validate();\n          });\n\n          values = Array.from(formRef.current.fields.values());\n\n          const invalidFields = values.filter((field) => !field.validityData.state.valid);\n\n          if (invalidFields.length) {\n            event.preventDefault();\n            focusControl(invalidFields[0].controlRef.current);\n          } else {\n            submittedRef.current = true;\n            onSubmit?.(event as any);\n\n            if (onFormSubmit) {\n              event.preventDefault();\n\n              const formValues = values.reduce((acc, field) => {\n                if (field.name) {\n                  (acc as Record<string, any>)[field.name] = field.getValue();\n                }\n                return acc;\n              }, {} as FormValues);\n\n              onFormSubmit(formValues, createGenericEventDetails(REASONS.none, event.nativeEvent));\n            }\n          }\n        },\n      },\n      elementProps,\n    ],\n  });\n\n  const clearErrors = useStableCallback((name: string | undefined) => {\n    if (name && errors && EMPTY_OBJECT.hasOwnProperty.call(errors, name)) {\n      const nextErrors = { ...errors };\n      delete nextErrors[name];\n      setErrors(nextErrors);\n    }\n  });\n\n  const contextValue: FormContext = React.useMemo(\n    () => ({\n      formRef,\n      validationMode,\n      errors: errors ?? EMPTY_OBJECT,\n      clearErrors,\n      submitAttemptedRef,\n    }),\n    [formRef, validationMode, errors, clearErrors],\n  );\n\n  return <FormContext.Provider value={contextValue}>{element}</FormContext.Provider>;\n}) as {\n  <FormValues extends Record<string, any> = Record<string, any>>(\n    props: Form.Props<FormValues> & {\n      ref?: React.Ref<HTMLFormElement> | undefined;\n    },\n  ): React.JSX.Element;\n};\n\nexport type FormSubmitEventReason = typeof REASONS.none;\nexport type FormSubmitEventDetails = BaseUIGenericEventDetails<Form.SubmitEventReason>;\n\nexport type FormValidationMode = 'onSubmit' | 'onBlur' | 'onChange';\n\nexport interface FormActions {\n  validate: (fieldName?: string | undefined) => void;\n}\n\nexport interface FormState {}\n\nexport interface FormProps<\n  FormValues extends Record<string, any> = Record<string, any>,\n> extends BaseUIComponentProps<'form', FormState> {\n  /**\n   * Determines when the form should be validated.\n   * The `validationMode` prop on `<Field.Root>` takes precedence over this.\n   *\n   * - `onSubmit` (default): validates the field when the form is submitted, afterwards fields will re-validate on change.\n   * - `onBlur`: validates a field when it loses focus.\n   * - `onChange`: validates the field on every change to its value.\n   *\n   * @default 'onSubmit'\n   */\n  validationMode?: FormValidationMode | undefined;\n  /**\n   * Validation errors returned externally, typically after submission by a server or a form action.\n   * This should be an object where keys correspond to the `name` attribute on `<Field.Root>`,\n   * and values correspond to error(s) related to that field.\n   */\n  errors?: FormContext['errors'] | undefined;\n  /**\n   * Event handler called when the form is submitted.\n   * `preventDefault()` is called on the native submit event when used.\n   */\n  onFormSubmit?:\n    | ((formValues: FormValues, eventDetails: Form.SubmitEventDetails) => void)\n    | undefined;\n  /**\n   * A ref to imperative actions.\n   * - `validate`: Validates all fields when called. Optionally pass a field name to validate a single field.\n   * @example\n   * ```tsx\n   * // validate all fields\n   * actionsRef.current.validate();\n   *\n   * // validate one field\n   * actionsRef.current.validate('email');\n   * ```\n   */\n  actionsRef?: React.RefObject<Form.Actions | null> | undefined;\n}\n\nexport namespace Form {\n  export type Props<FormValues extends Record<string, any> = Record<string, any>> =\n    FormProps<FormValues>;\n  export type State = FormState;\n  export type Actions = FormActions;\n  export type ValidationMode = FormValidationMode;\n  export type SubmitEventReason = FormSubmitEventReason;\n  export type SubmitEventDetails = FormSubmitEventDetails;\n\n  export type Values<FormValues extends Record<string, any> = Record<string, any>> = FormValues;\n}\n"
  },
  {
    "path": "packages/react/src/form/FormContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport type { FieldValidityData } from '../field/root/FieldRoot';\nimport { NOOP } from '../utils/noop';\nimport type { Form } from './Form';\n\nexport type Errors = Record<string, string | string[]>;\n\nexport interface FormContext {\n  errors: Errors;\n  clearErrors: (name: string | undefined) => void;\n  formRef: React.RefObject<{\n    fields: Map<\n      string,\n      {\n        name: string | undefined;\n        validate: (flushSync?: boolean | undefined) => void;\n        validityData: FieldValidityData;\n        controlRef: React.RefObject<HTMLElement | null>;\n        getValue: () => unknown;\n      }\n    >;\n  }>;\n  validationMode: Form.ValidationMode;\n  submitAttemptedRef: React.RefObject<boolean>;\n}\n\nexport const FormContext = React.createContext<FormContext>({\n  formRef: {\n    current: {\n      fields: new Map(),\n    },\n  },\n  errors: {},\n  clearErrors: NOOP,\n  validationMode: 'onSubmit',\n  submitAttemptedRef: {\n    current: false,\n  },\n});\n\nexport function useFormContext() {\n  return React.useContext(FormContext);\n}\n"
  },
  {
    "path": "packages/react/src/form/index.ts",
    "content": "export { Form } from './Form';\n\nexport type * from './Form';\n"
  },
  {
    "path": "packages/react/src/global.d.ts",
    "content": "declare global {\n  /**\n   * When `true`, disables animation-related code, even if supported by the runtime environment.\n   */\n  // eslint-disable-next-line vars-on-top\n  var BASE_UI_ANIMATIONS_DISABLED: boolean;\n}\n\nexport type {};\n"
  },
  {
    "path": "packages/react/src/index.test.ts",
    "content": "import { describe, it, expect } from 'vitest';\n/**\n * Important: This test also serves as a point to\n * import the entire lib for coverage reporting\n */\nimport { isJSDOM } from '#test-utils';\nimport * as BaseUI from './index';\n\ndescribe('@base-ui/react', () => {\n  it('should have exports', () => {\n    expect(typeof BaseUI).toBe('object');\n  });\n\n  it('should not have undefined exports', () => {\n    Object.keys(BaseUI).forEach((exportKey) => {\n      const value = (BaseUI as Record<string, unknown>)[exportKey];\n      expect(Boolean(value)).toBe(true);\n    });\n  });\n\n  it.skipIf(!isJSDOM)('should have the correct root exports', async () => {\n    const packageJson = await import('../package.json');\n    const subpathExports = packageJson.exports;\n\n    await Promise.all(\n      Object.keys(subpathExports)\n        .filter(\n          (key) =>\n            ![\n              '.',\n              './utils',\n              './temporal-adapter-luxon',\n              './temporal-adapter-date-fns',\n              './types',\n            ].includes(key) && !key.startsWith('./unstable-'),\n        )\n        .map(async (subpath) => {\n          const importSpecifier = `@base-ui/react/${subpath.replace('./', '')}`;\n          const module = await import(/* @vite-ignore */ importSpecifier);\n\n          Object.keys(module).forEach((exportKey) => {\n            expect((BaseUI as Record<string, unknown>)[exportKey]).not.toBeUndefined();\n          });\n        }),\n    );\n  });\n});\n"
  },
  {
    "path": "packages/react/src/index.ts",
    "content": "export * from './accordion';\nexport * from './alert-dialog';\nexport * from './autocomplete';\nexport * from './avatar';\nexport * from './button';\n// TODO Temporal: enable before public release\n// export * from './calendar';\nexport * from './checkbox';\nexport * from './checkbox-group';\nexport * from './collapsible';\nexport * from './combobox';\nexport * from './context-menu';\nexport * from './csp-provider';\nexport * from './dialog';\nexport * from './direction-provider';\nexport * from './drawer';\nexport * from './field';\nexport * from './fieldset';\nexport * from './form';\nexport * from './input';\nexport * from './menu';\nexport * from './menubar';\nexport * from './merge-props';\nexport * from './meter';\nexport * from './navigation-menu';\nexport * from './number-field';\nexport * from './popover';\nexport * from './preview-card';\nexport * from './progress';\nexport * from './radio';\nexport * from './radio-group';\nexport * from './scroll-area';\nexport * from './select';\nexport * from './separator';\nexport * from './slider';\nexport * from './switch';\nexport * from './tabs';\n// TODO Temporal: enable before public release\n// export * from './localization-provider';\nexport * from './toast';\nexport * from './toggle';\nexport * from './toggle-group';\nexport * from './toolbar';\nexport * from './tooltip';\nexport * from './use-render';\n\nexport type * from './types';\n"
  },
  {
    "path": "packages/react/src/input/Input.spec.tsx",
    "content": "import * as React from 'react';\nimport { Input } from '@base-ui/react/input';\n\nfunction App() {\n  const ref = React.useRef<HTMLTextAreaElement>(null);\n  return <Input ref={ref} render={<textarea />} />;\n}\n"
  },
  {
    "path": "packages/react/src/input/Input.test.tsx",
    "content": "import { Input } from '@base-ui/react/input';\nimport { createRenderer } from '@mui/internal-test-utils';\nimport { describeConformance } from '../../test/describeConformance';\n\ndescribe('<Input />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Input />, () => ({\n    refInstanceof: window.HTMLInputElement,\n    render,\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/input/Input.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport type { BaseUIComponentProps } from '../utils/types';\nimport { Field, type FieldControlState } from '../field';\n\n/**\n * A native input element that automatically works with [Field](https://base-ui.com/react/components/field).\n * Renders an `<input>` element.\n *\n * Documentation: [Base UI Input](https://base-ui.com/react/components/input)\n */\nexport const Input = React.forwardRef(function Input(\n  props: Input.Props,\n  forwardedRef: React.ForwardedRef<HTMLElement>,\n) {\n  return <Field.Control ref={forwardedRef} {...props} />;\n});\n\nexport interface InputProps extends BaseUIComponentProps<'input', InputState> {\n  /**\n   * Callback fired when the `value` changes. Use when controlled.\n   */\n  onValueChange?: Field.Control.Props['onValueChange'] | undefined;\n  /**\n   * The default value of the input. Use when uncontrolled.\n   */\n  defaultValue?: Field.Control.Props['defaultValue'] | undefined;\n  /**\n   * The value of the input. Use when controlled.\n   */\n  value?: React.ComponentProps<'input'>['value'] | undefined;\n}\n\nexport interface InputState extends FieldControlState {}\n\nexport type InputChangeEventReason = Field.Control.ChangeEventReason;\nexport type InputChangeEventDetails = Field.Control.ChangeEventDetails;\n\nexport namespace Input {\n  export type Props = InputProps;\n  export type State = InputState;\n  export type ChangeEventReason = InputChangeEventReason;\n  export type ChangeEventDetails = InputChangeEventDetails;\n}\n"
  },
  {
    "path": "packages/react/src/input/InputDataAttributes.ts",
    "content": "export enum InputDataAttributes {\n  /**\n   * Present when the input is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the input is in valid state (when wrapped in Field.Root).\n   */\n  valid = 'data-valid',\n  /**\n   * Present when the input is in invalid state (when wrapped in Field.Root).\n   */\n  invalid = 'data-invalid',\n  /**\n   * Present when the input has been touched (when wrapped in Field.Root).\n   */\n  touched = 'data-touched',\n  /**\n   * Present when the input's value has changed (when wrapped in Field.Root).\n   */\n  dirty = 'data-dirty',\n  /**\n   * Present when the input is filled (when wrapped in Field.Root).\n   */\n  filled = 'data-filled',\n  /**\n   * Present when the input is focused (when wrapped in Field.Root).\n   */\n  focused = 'data-focused',\n}\n"
  },
  {
    "path": "packages/react/src/input/index.ts",
    "content": "export { Input } from './Input';\n\nexport type * from './Input';\n"
  },
  {
    "path": "packages/react/src/labelable-provider/LabelableContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { NOOP } from '../utils/noop';\nimport { HTMLProps } from '../utils/types';\n\nexport interface LabelableContext {\n  /**\n   * The `id` of the labelable element.\n   * When `null` the association is implicit.\n   */\n  controlId: string | null | undefined;\n  registerControlId: (source: symbol, id: string | null | undefined) => void;\n  /**\n   * The `id` of the label.\n   */\n  labelId: string | undefined;\n  setLabelId: React.Dispatch<React.SetStateAction<string | undefined>>;\n  /**\n   * An array of `id`s of elements that provide an accessible description.\n   */\n  messageIds: string[];\n  setMessageIds: React.Dispatch<React.SetStateAction<string[]>>;\n  getDescriptionProps: (externalProps: HTMLProps) => HTMLProps;\n}\n\n/**\n * A context for providing [labelable elements](https://html.spec.whatwg.org/multipage/forms.html#category-label)\\\n * with an accessible name (label) and description.\n */\nexport const LabelableContext = React.createContext<LabelableContext>({\n  controlId: undefined,\n  registerControlId: NOOP,\n  labelId: undefined,\n  setLabelId: NOOP,\n  messageIds: [],\n  setMessageIds: NOOP,\n  getDescriptionProps: (externalProps: HTMLProps) => externalProps,\n});\n\nexport function useLabelableContext() {\n  return React.useContext(LabelableContext);\n}\n"
  },
  {
    "path": "packages/react/src/labelable-provider/LabelableProvider.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useRefWithInit } from '@base-ui/utils/useRefWithInit';\nimport { mergeProps } from '../merge-props';\nimport { HTMLProps } from '../utils/types';\nimport { useBaseUiId } from '../utils/useBaseUiId';\nimport { LabelableContext, useLabelableContext } from './LabelableContext';\n\n/**\n * @internal\n */\nexport const LabelableProvider: React.FC<LabelableProvider.Props> = function LabelableProvider(\n  props,\n) {\n  const defaultId = useBaseUiId();\n  const initialControlId = props.controlId === undefined ? defaultId : props.controlId;\n\n  const [controlId, setControlIdState] = React.useState<string | null | undefined>(\n    initialControlId,\n  );\n  const [labelId, setLabelId] = React.useState<string | undefined>(props.labelId);\n  const [messageIds, setMessageIds] = React.useState<string[]>([]);\n\n  const registrationsRef = useRefWithInit(() => new Map<symbol, string | null>());\n\n  const { messageIds: parentMessageIds } = useLabelableContext();\n\n  const registerControlId = useStableCallback(\n    (source: symbol, nextId: string | null | undefined) => {\n      const registrations = registrationsRef.current;\n\n      if (nextId === undefined) {\n        registrations.delete(source);\n        return;\n      }\n\n      registrations.set(source, nextId);\n\n      // Only flush when registering, not when unregistering.\n      // This prevents loops during rapid unmount/remount cycles (e.g. React Activity).\n      // The next registration will pick up the correct state.\n      setControlIdState((prev) => {\n        if (registrations.size === 0) {\n          return undefined;\n        }\n\n        let nextControlId: string | null | undefined;\n\n        for (const id of registrations.values()) {\n          if (prev !== undefined && id === prev) {\n            return prev;\n          }\n\n          if (nextControlId === undefined) {\n            nextControlId = id;\n          }\n        }\n\n        return nextControlId;\n      });\n    },\n  );\n\n  const getDescriptionProps = React.useCallback(\n    (externalProps: HTMLProps) => {\n      return mergeProps(\n        { 'aria-describedby': parentMessageIds.concat(messageIds).join(' ') || undefined },\n        externalProps,\n      );\n    },\n    [parentMessageIds, messageIds],\n  );\n\n  const contextValue: LabelableContext = React.useMemo(\n    () => ({\n      controlId,\n      registerControlId,\n      labelId,\n      setLabelId,\n      messageIds,\n      setMessageIds,\n      getDescriptionProps,\n    }),\n    [\n      controlId,\n      registerControlId,\n      labelId,\n      setLabelId,\n      messageIds,\n      setMessageIds,\n      getDescriptionProps,\n    ],\n  );\n\n  return (\n    <LabelableContext.Provider value={contextValue}>{props.children}</LabelableContext.Provider>\n  );\n};\n\nexport interface LabelableProviderState {}\n\nexport interface LabelableProviderProps {\n  controlId?: string | null | undefined;\n  labelId?: string | undefined;\n  children?: React.ReactNode;\n}\n\nexport namespace LabelableProvider {\n  export type State = LabelableProviderState;\n  export type Props = LabelableProviderProps;\n}\n"
  },
  {
    "path": "packages/react/src/labelable-provider/index.ts",
    "content": "export * from './LabelableProvider';\n"
  },
  {
    "path": "packages/react/src/labelable-provider/useAriaLabelledBy.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useBaseUiId } from '../utils/useBaseUiId';\n\n/**\n * @internal\n */\nexport function useAriaLabelledBy(\n  explicitAriaLabelledBy: string | undefined,\n  labelId: string | undefined,\n  labelSourceRef: React.RefObject<LabelSource | null>,\n  enableFallback = true,\n  labelSourceId?: string,\n) {\n  const [fallbackAriaLabelledBy, setFallbackAriaLabelledBy] = React.useState<string | undefined>();\n\n  const generatedLabelId = useBaseUiId(labelSourceId ? `${labelSourceId}-label` : undefined);\n  const ariaLabelledBy = explicitAriaLabelledBy ?? labelId ?? fallbackAriaLabelledBy;\n\n  // Fallback for <span> controls labelled by wrapping/sibling native <label>.\n  // Run after every commit so DOM association changes (e.g. label mount/unmount)\n  // are reflected even when props/state deps are unchanged.\n  // eslint-disable-next-line react-hooks/exhaustive-deps\n  useIsoLayoutEffect(() => {\n    const nextAriaLabelledBy =\n      explicitAriaLabelledBy || labelId || !enableFallback\n        ? undefined\n        : getAriaLabelledBy(labelSourceRef.current, generatedLabelId);\n\n    if (fallbackAriaLabelledBy !== nextAriaLabelledBy) {\n      setFallbackAriaLabelledBy(nextAriaLabelledBy);\n    }\n  });\n\n  return ariaLabelledBy;\n}\n\nfunction getAriaLabelledBy(labelSource?: LabelSource | null, generatedLabelId?: string) {\n  const label = findAssociatedLabel(labelSource);\n  if (!label) {\n    return undefined;\n  }\n\n  if (!label.id && generatedLabelId) {\n    label.id = generatedLabelId;\n  }\n\n  return label.id || undefined;\n}\n\nfunction findAssociatedLabel(labelSource?: LabelSource | null) {\n  if (!labelSource) {\n    return undefined;\n  }\n\n  // Fast path before the expensive `.labels` read.\n  const parent = labelSource.parentElement;\n  if (parent && parent.tagName === 'LABEL') {\n    return parent as HTMLLabelElement;\n  }\n\n  const controlId = labelSource.id;\n  if (controlId) {\n    const nextSibling = labelSource.nextElementSibling as HTMLLabelElement | null;\n    if (nextSibling && nextSibling.htmlFor === controlId) {\n      return nextSibling;\n    }\n  }\n\n  const labels = labelSource.labels;\n  return labels && labels[0];\n}\n\ntype LabelSource = HTMLElement & { labels?: NodeListOf<HTMLLabelElement> | null | undefined };\n"
  },
  {
    "path": "packages/react/src/labelable-provider/useLabel.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { isHTMLElement } from '@floating-ui/utils/dom';\nimport { ownerDocument } from '@base-ui/utils/owner';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { getTarget } from '../floating-ui-react/utils';\nimport { useRegisteredLabelId } from '../utils/useRegisteredLabelId';\nimport { useLabelableContext } from './LabelableContext';\n\nexport function useLabel(params: UseLabelParameters = {}): UseLabelReturnValue {\n  const {\n    id: idProp,\n    fallbackControlId,\n    native = false,\n    setLabelId: setLabelIdProp,\n    focusControl: focusControlProp,\n  } = params;\n\n  const { controlId: contextControlId, setLabelId: setContextLabelId } = useLabelableContext();\n\n  const syncLabelId = useStableCallback((nextLabelId: string | undefined) => {\n    setContextLabelId(nextLabelId);\n    setLabelIdProp?.(nextLabelId);\n  });\n\n  const id = useRegisteredLabelId(idProp, syncLabelId);\n\n  const resolvedControlId = contextControlId ?? fallbackControlId;\n\n  function focusControl(event: React.MouseEvent) {\n    if (focusControlProp) {\n      focusControlProp(event, resolvedControlId);\n      return;\n    }\n\n    if (!resolvedControlId) {\n      return;\n    }\n\n    const controlElement = ownerDocument(event.currentTarget).getElementById(resolvedControlId);\n    if (isHTMLElement(controlElement)) {\n      focusElementWithVisible(controlElement);\n    }\n  }\n\n  function handleInteraction(event: React.MouseEvent) {\n    const target = getTarget(event.nativeEvent) as HTMLElement | null;\n    if (target?.closest('button,input,select,textarea')) {\n      return;\n    }\n\n    // Prevent text selection when double clicking label.\n    if (!event.defaultPrevented && event.detail > 1) {\n      event.preventDefault();\n    }\n\n    if (native) {\n      return;\n    }\n\n    focusControl(event);\n  }\n\n  return native\n    ? {\n        id,\n        htmlFor: resolvedControlId ?? undefined,\n        onMouseDown: handleInteraction,\n      }\n    : {\n        id,\n        onClick: handleInteraction,\n        onPointerDown(event: React.PointerEvent) {\n          event.preventDefault();\n        },\n      };\n}\n\nexport interface UseLabelParameters {\n  id?: string | undefined;\n  /**\n   * Control id used when no labelable context control id exists.\n   */\n  fallbackControlId?: string | null | undefined;\n  /**\n   * Whether the rendered element is a native `<label>`.\n   * @default false\n   */\n  native?: boolean | undefined;\n  /**\n   * Additional callback to sync the current label id with local component state/store.\n   */\n  setLabelId?: ((nextLabelId: string | undefined) => void) | undefined;\n  /**\n   * Custom focus handler for non-native labels.\n   * If omitted, focus behavior targets the resolved control id.\n   */\n  focusControl?:\n    | ((event: React.MouseEvent, controlId: string | null | undefined) => void)\n    | undefined;\n}\n\nexport type UseLabelReturnValue = React.HTMLAttributes<any> & React.LabelHTMLAttributes<any>;\n\nexport function focusElementWithVisible(element: HTMLElement) {\n  element.focus({\n    // Available from Chrome 144+ (January 2026).\n    // Safari and Firefox already support it.\n    // @ts-expect-error not available in types yet\n    focusVisible: true,\n  });\n}\n"
  },
  {
    "path": "packages/react/src/labelable-provider/useLabelableId.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useRefWithInit } from '@base-ui/utils/useRefWithInit';\nimport { isElement } from '@floating-ui/utils/dom';\nimport { NOOP } from '../utils/noop';\nimport { useBaseUiId } from '../utils/useBaseUiId';\nimport { useLabelableContext } from './LabelableContext';\n\nexport function useLabelableId(params: UseLabelableIdParameters = {}) {\n  const { id, implicit = false, controlRef } = params;\n\n  const { controlId, registerControlId } = useLabelableContext();\n\n  const defaultId = useBaseUiId(id);\n\n  const controlIdForEffect = implicit ? controlId : undefined;\n\n  const controlSourceRef = useRefWithInit(() => Symbol('labelable-control'));\n  const hasRegisteredRef = React.useRef(false);\n  const hadExplicitIdRef = React.useRef(id != null);\n\n  const unregisterControlId = useStableCallback(() => {\n    if (!hasRegisteredRef.current || registerControlId === NOOP) {\n      return;\n    }\n\n    hasRegisteredRef.current = false;\n    registerControlId(controlSourceRef.current, undefined);\n  });\n\n  useIsoLayoutEffect(() => {\n    if (registerControlId === NOOP) {\n      return undefined;\n    }\n\n    let nextId: string | null | undefined;\n\n    if (implicit) {\n      const elem = controlRef?.current;\n\n      if (isElement(elem) && elem.closest('label') != null) {\n        nextId = id ?? null;\n      } else {\n        nextId = controlIdForEffect ?? defaultId;\n      }\n    } else if (id != null) {\n      hadExplicitIdRef.current = true;\n      nextId = id;\n    } else if (hadExplicitIdRef.current) {\n      nextId = defaultId;\n    } else {\n      unregisterControlId();\n      return undefined;\n    }\n\n    if (nextId === undefined) {\n      unregisterControlId();\n      return undefined;\n    }\n\n    hasRegisteredRef.current = true;\n    registerControlId(controlSourceRef.current, nextId);\n\n    return undefined;\n  }, [\n    id,\n    controlRef,\n    controlIdForEffect,\n    registerControlId,\n    implicit,\n    defaultId,\n    controlSourceRef,\n    unregisterControlId,\n  ]);\n\n  React.useEffect(() => {\n    return unregisterControlId;\n  }, [unregisterControlId]);\n\n  return controlId ?? defaultId;\n}\n\nexport interface UseLabelableIdParameters {\n  id?: string | undefined;\n  /**\n   * Whether implicit labelling is supported.\n   * @default false\n   */\n  implicit?: boolean | undefined;\n  /**\n   * A ref to an element that can be implicitly labelled.\n   */\n  controlRef?: React.RefObject<HTMLElement | null> | undefined;\n}\n\nexport type UseLabelableIdReturnValue = string;\n\nexport interface UseLabelableIdState {}\n"
  },
  {
    "path": "packages/react/src/localization-provider/LocalizationContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { Locale } from 'date-fns/locale';\n\nexport type LocalizationContext = {\n  temporalLocale?: Locale | undefined;\n};\n\n/**\n * @internal\n */\nexport const LocalizationContext = React.createContext<LocalizationContext | undefined>(undefined);\n\nexport function useTemporalLocale() {\n  const context = React.useContext(LocalizationContext);\n  if (context === undefined) {\n    throw new Error(\n      'Base UI: LocalizationContext is missing. Temporal components must be place within <LocalizationProvider />',\n    );\n  }\n\n  return context.temporalLocale;\n}\n"
  },
  {
    "path": "packages/react/src/localization-provider/LocalizationProvider.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { Locale } from 'date-fns/locale';\nimport { LocalizationContext } from './LocalizationContext';\nimport { TemporalAdapterDateFns } from '../temporal-adapter-date-fns/TemporalAdapterDateFns';\nimport { TemporalAdapterContext } from '../temporal-adapter-provider/TemporalAdapterContext';\n\n/**\n * Defines the temporal locale provider for Base UI temporal components.\n *\n * Doesn't render its own HTML element.\n *\n * Documentation: [Base UI Localization Provider](https://base-ui.com/react/utils/localization-provider)\n */\nexport const LocalizationProvider: React.FC<LocalizationProvider.Props> =\n  function LocalizationProvider(props: LocalizationProvider.Props) {\n    const { children, temporalLocale } = props;\n\n    const contextValue = React.useMemo(() => ({ temporalLocale }), [temporalLocale]);\n    const adapterContextValue = React.useMemo(\n      () => ({ adapter: new TemporalAdapterDateFns({ locale: temporalLocale }) }),\n      [temporalLocale],\n    );\n\n    return (\n      <LocalizationContext.Provider value={contextValue}>\n        <TemporalAdapterContext.Provider value={adapterContextValue}>\n          {children}\n        </TemporalAdapterContext.Provider>\n      </LocalizationContext.Provider>\n    );\n  };\n\nexport namespace LocalizationProvider {\n  export interface Props {\n    children?: React.ReactNode;\n    /**\n     * The locale to use in temporal components.\n     * @default en-US\n     */\n    temporalLocale?: Locale | undefined;\n  }\n}\n"
  },
  {
    "path": "packages/react/src/localization-provider/index.ts",
    "content": "export { LocalizationProvider } from './LocalizationProvider';\nexport { useTemporalLocale } from './LocalizationContext';\n"
  },
  {
    "path": "packages/react/src/menu/arrow/MenuArrow.test.tsx",
    "content": "import { Menu } from '@base-ui/react/menu';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Menu.Arrow />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Menu.Arrow />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <Menu.Root open>\n          <Menu.Portal>\n            <Menu.Positioner>\n              <Menu.Popup>{node}</Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/menu/arrow/MenuArrow.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useMenuPositionerContext } from '../positioner/MenuPositionerContext';\nimport { useMenuRootContext } from '../root/MenuRootContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport type { Side, Align } from '../../utils/useAnchorPositioning';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { popupStateMapping } from '../../utils/popupStateMapping';\n\n/**\n * Displays an element positioned against the menu anchor.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)\n */\nexport const MenuArrow = React.forwardRef(function MenuArrow(\n  componentProps: MenuArrow.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { className, render, ...elementProps } = componentProps;\n\n  const { store } = useMenuRootContext();\n  const { arrowRef, side, align, arrowUncentered, arrowStyles } = useMenuPositionerContext();\n  const open = store.useState('open');\n\n  const state: MenuArrowState = {\n    open,\n    side,\n    align,\n    uncentered: arrowUncentered,\n  };\n\n  return useRenderElement('div', componentProps, {\n    ref: [arrowRef, forwardedRef],\n    stateAttributesMapping: popupStateMapping,\n    state,\n    props: {\n      style: arrowStyles,\n      'aria-hidden': true,\n      ...elementProps,\n    },\n  });\n});\n\nexport interface MenuArrowState {\n  /**\n   * Whether the menu is currently open.\n   */\n  open: boolean;\n  /**\n   * The side of the anchor the component is placed on.\n   */\n  side: Side;\n  /**\n   * The alignment of the component relative to the anchor.\n   */\n  align: Align;\n  /**\n   * Whether the arrow cannot be centered on the anchor.\n   */\n  uncentered: boolean;\n}\n\nexport interface MenuArrowProps extends BaseUIComponentProps<'div', MenuArrowState> {}\n\nexport namespace MenuArrow {\n  export type State = MenuArrowState;\n  export type Props = MenuArrowProps;\n}\n"
  },
  {
    "path": "packages/react/src/menu/arrow/MenuArrowDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum MenuArrowDataAttributes {\n  /**\n   * Present when the menu popup is open.\n   */\n  open = CommonPopupDataAttributes.open,\n  /**\n   * Present when the menu popup is closed.\n   */\n  closed = CommonPopupDataAttributes.closed,\n  /**\n   * Indicates which side the popup is positioned relative to the trigger.\n   * @type {'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'}\n   */\n  side = CommonPopupDataAttributes.side,\n  /**\n   * Indicates how the popup is aligned relative to specified side.\n   * @type {'start' | 'center' | 'end'}\n   */\n  align = CommonPopupDataAttributes.align,\n  /**\n   * Present when the menu arrow is uncentered.\n   */\n  uncentered = 'data-uncentered',\n}\n"
  },
  {
    "path": "packages/react/src/menu/backdrop/MenuBackdrop.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { Menu } from '@base-ui/react/menu';\nimport { createRenderer, describeConformance } from '#test-utils';\nimport { screen, waitFor } from '@mui/internal-test-utils';\n\ndescribe('<Menu.Backdrop />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Menu.Backdrop />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(<Menu.Root open>{node}</Menu.Root>);\n    },\n  }));\n\n  it('sets `pointer-events: none` style on backdrop if opened by hover', async () => {\n    const { user } = await render(\n      <Menu.Root>\n        <Menu.Trigger delay={0} openOnHover>\n          Open\n        </Menu.Trigger>\n        <Menu.Portal>\n          <Menu.Backdrop data-testid=\"backdrop\" />\n          <Menu.Positioner>\n            <Menu.Popup />\n          </Menu.Positioner>\n        </Menu.Portal>\n      </Menu.Root>,\n    );\n\n    await user.hover(screen.getByText('Open'));\n\n    expect(screen.getByTestId('backdrop').style.pointerEvents).toBe('none');\n  });\n\n  it('does not set `pointer-events: none` style on backdrop if opened by click', async () => {\n    const { user } = await render(\n      <Menu.Root>\n        <Menu.Trigger delay={0}>Open</Menu.Trigger>\n        <Menu.Portal>\n          <Menu.Backdrop data-testid=\"backdrop\" />\n          <Menu.Positioner>\n            <Menu.Popup />\n          </Menu.Positioner>\n        </Menu.Portal>\n      </Menu.Root>,\n    );\n\n    await user.click(screen.getByText('Open'));\n\n    await waitFor(() => {\n      expect(screen.getByTestId('backdrop').style.pointerEvents).not.toBe('none');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/menu/backdrop/MenuBackdrop.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useMenuRootContext } from '../root/MenuRootContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { type StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport { popupStateMapping as baseMapping } from '../../utils/popupStateMapping';\nimport type { TransitionStatus } from '../../utils/useTransitionStatus';\nimport { transitionStatusMapping } from '../../utils/stateAttributesMapping';\nimport { useContextMenuRootContext } from '../../context-menu/root/ContextMenuRootContext';\nimport { REASONS } from '../../utils/reasons';\n\nconst stateAttributesMapping: StateAttributesMapping<MenuBackdropState> = {\n  ...baseMapping,\n  ...transitionStatusMapping,\n};\n\n/**\n * An overlay displayed beneath the menu popup.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)\n */\nexport const MenuBackdrop = React.forwardRef(function MenuBackdrop(\n  componentProps: MenuBackdrop.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { className, render, ...elementProps } = componentProps;\n\n  const { store } = useMenuRootContext();\n  const open = store.useState('open');\n  const mounted = store.useState('mounted');\n  const transitionStatus = store.useState('transitionStatus');\n  const lastOpenChangeReason = store.useState('lastOpenChangeReason');\n\n  const contextMenuContext = useContextMenuRootContext();\n\n  const state: MenuBackdropState = {\n    open,\n    transitionStatus,\n  };\n\n  return useRenderElement('div', componentProps, {\n    ref: contextMenuContext?.backdropRef\n      ? [forwardedRef, contextMenuContext.backdropRef]\n      : forwardedRef,\n    state,\n    stateAttributesMapping,\n    props: [\n      {\n        role: 'presentation',\n        hidden: !mounted,\n        style: {\n          pointerEvents: lastOpenChangeReason === REASONS.triggerHover ? 'none' : undefined,\n          userSelect: 'none',\n          WebkitUserSelect: 'none',\n        },\n      },\n      elementProps,\n    ],\n  });\n});\n\nexport interface MenuBackdropState {\n  /**\n   * Whether the menu is currently open.\n   */\n  open: boolean;\n  /**\n   * The transition status of the component.\n   */\n  transitionStatus: TransitionStatus;\n}\n\nexport interface MenuBackdropProps extends BaseUIComponentProps<'div', MenuBackdropState> {}\n\nexport namespace MenuBackdrop {\n  export type State = MenuBackdropState;\n  export type Props = MenuBackdropProps;\n}\n"
  },
  {
    "path": "packages/react/src/menu/backdrop/MenuBackdropDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum MenuBackdropDataAttributes {\n  /**\n   * Present when the menu is open.\n   */\n  open = CommonPopupDataAttributes.open,\n  /**\n   * Present when the menu is closed.\n   */\n  closed = CommonPopupDataAttributes.closed,\n  /**\n   * Present when the menu is animating in.\n   */\n  startingStyle = CommonPopupDataAttributes.startingStyle,\n  /**\n   * Present when the menu is animating out.\n   */\n  endingStyle = CommonPopupDataAttributes.endingStyle,\n}\n"
  },
  {
    "path": "packages/react/src/menu/checkbox-item/MenuCheckboxItem.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { fireEvent, act, waitFor, screen } from '@mui/internal-test-utils';\nimport { Menu } from '@base-ui/react/menu';\nimport { describeConformance, createRenderer, isJSDOM } from '#test-utils';\n\ndescribe('<Menu.CheckboxItem />', () => {\n  const { render, clock } = createRenderer({\n    clockOptions: {\n      shouldAdvanceTime: true,\n    },\n  });\n\n  clock.withFakeTimers();\n\n  describeConformance(<Menu.CheckboxItem />, () => ({\n    render: (node) => {\n      return render(<Menu.Root open>{node}</Menu.Root>);\n    },\n    refInstanceof: window.HTMLDivElement,\n  }));\n\n  it('perf: does not rerender menu items unnecessarily', async ({ skip }) => {\n    if (isJSDOM) {\n      skip();\n    }\n\n    const renderItem1Spy = vi.fn();\n    const renderItem2Spy = vi.fn();\n    const renderItem3Spy = vi.fn();\n    const renderItem4Spy = vi.fn();\n\n    const LoggingRoot = React.forwardRef(function LoggingRoot(\n      props: any & { renderSpy: () => void },\n      ref: React.ForwardedRef<HTMLLIElement>,\n    ) {\n      const { renderSpy, state, ...other } = props;\n      renderSpy();\n      return <li {...other} ref={ref} />;\n    });\n\n    await render(\n      <Menu.Root open>\n        <Menu.Portal>\n          <Menu.Positioner>\n            <Menu.Popup>\n              <Menu.CheckboxItem render={<LoggingRoot renderSpy={renderItem1Spy} />} id=\"item-1\">\n                1\n              </Menu.CheckboxItem>\n              <Menu.CheckboxItem render={<LoggingRoot renderSpy={renderItem2Spy} />} id=\"item-2\">\n                2\n              </Menu.CheckboxItem>\n              <Menu.CheckboxItem render={<LoggingRoot renderSpy={renderItem3Spy} />} id=\"item-3\">\n                3\n              </Menu.CheckboxItem>\n              <Menu.CheckboxItem render={<LoggingRoot renderSpy={renderItem4Spy} />} id=\"item-4\">\n                4\n              </Menu.CheckboxItem>\n            </Menu.Popup>\n          </Menu.Positioner>\n        </Menu.Portal>\n      </Menu.Root>,\n    );\n\n    const menuItems = screen.getAllByRole('menuitemcheckbox');\n    await act(async () => {\n      menuItems[0].focus();\n    });\n\n    renderItem1Spy.mockClear();\n    renderItem2Spy.mockClear();\n    renderItem3Spy.mockClear();\n    renderItem4Spy.mockClear();\n\n    expect(renderItem1Spy.mock.calls.length).toBe(0);\n\n    fireEvent.keyDown(menuItems[0], { key: 'ArrowDown' }); // highlights '2'\n\n    // React renders twice in strict mode, so we expect twice the number of spy calls\n\n    await waitFor(\n      () => {\n        expect(renderItem1Spy.mock.calls.length).toBe(2); // '1' rerenders as it loses highlight\n      },\n      { timeout: 1000 },\n    );\n\n    await waitFor(\n      () => {\n        expect(renderItem2Spy.mock.calls.length).toBe(2); // '2' rerenders as it receives highlight\n      },\n      { timeout: 1000 },\n    );\n\n    // neither the highlighted nor the selected state of these options changed,\n    // so they don't need to rerender:\n    expect(renderItem3Spy.mock.calls.length).toBe(0);\n    expect(renderItem4Spy.mock.calls.length).toBe(0);\n  });\n\n  describe('state management', () => {\n    (\n      [\n        [true, 'true', 'checked'],\n        [false, 'false', 'unchecked'],\n      ] as const\n    ).forEach(([checked, ariaChecked, dataState]) =>\n      it('adds the state and ARIA attributes when checked', async () => {\n        const { user } = await render(\n          <Menu.Root>\n            <Menu.Trigger>Open</Menu.Trigger>\n            <Menu.Portal>\n              <Menu.Positioner>\n                <Menu.Popup>\n                  <Menu.CheckboxItem checked={checked}>Item</Menu.CheckboxItem>\n                </Menu.Popup>\n              </Menu.Positioner>\n            </Menu.Portal>\n          </Menu.Root>,\n        );\n\n        const trigger = screen.getByRole('button', { name: 'Open' });\n        await user.click(trigger);\n\n        const item = screen.getByRole('menuitemcheckbox');\n        expect(item).toHaveAttribute('aria-checked', ariaChecked);\n        expect(item).toHaveAttribute(`data-${dataState}`, '');\n      }),\n    );\n\n    it('toggles the checked state when clicked', async () => {\n      const { user } = await render(\n        <Menu.Root>\n          <Menu.Trigger>Open</Menu.Trigger>\n          <Menu.Portal>\n            <Menu.Positioner>\n              <Menu.Popup>\n                <Menu.CheckboxItem>Item</Menu.CheckboxItem>\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      const trigger = screen.getByRole('button', { name: 'Open' });\n      await user.click(trigger);\n\n      const item = screen.getByRole('menuitemcheckbox');\n      await user.click(item);\n\n      expect(item).toHaveAttribute('aria-checked', 'true');\n      expect(item).toHaveAttribute('data-checked', '');\n\n      await user.click(item);\n\n      expect(item).toHaveAttribute('aria-checked', 'false');\n      expect(item).toHaveAttribute('data-unchecked', '');\n    });\n\n    it(`toggles the checked state when Space is pressed`, async () => {\n      const { user } = await render(\n        <Menu.Root>\n          <Menu.Trigger>Open</Menu.Trigger>\n          <Menu.Portal>\n            <Menu.Positioner>\n              <Menu.Popup>\n                <Menu.CheckboxItem>Item</Menu.CheckboxItem>\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      const trigger = screen.getByRole('button', { name: 'Open' });\n      await act(async () => {\n        trigger.focus();\n      });\n      await user.keyboard('[ArrowDown]');\n      const item = screen.getByRole('menuitemcheckbox');\n\n      await waitFor(() => {\n        expect(item).toHaveFocus();\n      });\n\n      await user.keyboard(`[Space]`);\n      expect(item).toHaveAttribute('data-checked', '');\n\n      await user.keyboard(`[Space]`);\n      expect(item).toHaveAttribute('data-unchecked', '');\n    });\n\n    it.skipIf(isJSDOM)(\n      'does not toggle when Space is pressed during an active typeahead session',\n      async () => {\n        const onCheckedChange = vi.fn();\n        const { user } = await render(\n          <Menu.Root open>\n            <Menu.Portal>\n              <Menu.Positioner>\n                <Menu.Popup>\n                  <Menu.CheckboxItem onCheckedChange={onCheckedChange}>Item One</Menu.CheckboxItem>\n                  <Menu.CheckboxItem onCheckedChange={onCheckedChange}>Item Two</Menu.CheckboxItem>\n                </Menu.Popup>\n              </Menu.Positioner>\n            </Menu.Portal>\n          </Menu.Root>,\n        );\n\n        const [itemOne, itemTwo] = screen.getAllByRole('menuitemcheckbox');\n\n        await act(async () => {\n          itemOne.focus();\n        });\n\n        await user.keyboard('Item T');\n\n        await waitFor(() => {\n          expect(itemTwo).toHaveFocus();\n        });\n\n        await user.keyboard('[Space]');\n        await user.keyboard('[Space]');\n\n        expect(onCheckedChange.mock.calls.length > 0).toBe(false);\n        expect(itemTwo).toHaveAttribute('aria-checked', 'false');\n      },\n    );\n\n    it(`toggles the checked state when Enter is pressed`, async ({ skip }) => {\n      if (isJSDOM) {\n        skip();\n      }\n\n      const { user } = await render(\n        <Menu.Root>\n          <Menu.Trigger>Open</Menu.Trigger>\n          <Menu.Portal>\n            <Menu.Positioner>\n              <Menu.Popup>\n                <Menu.CheckboxItem>Item</Menu.CheckboxItem>\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      const trigger = screen.getByRole('button', { name: 'Open' });\n      await act(async () => {\n        trigger.focus();\n      });\n\n      await user.keyboard('[ArrowDown]');\n      const item = screen.getByRole('menuitemcheckbox');\n\n      await waitFor(() => {\n        expect(item).toHaveFocus();\n      });\n\n      await user.keyboard(`[Enter]`);\n      expect(item).toHaveAttribute('data-checked', '');\n    });\n\n    it('calls `onCheckedChange` when the item is clicked', async () => {\n      const onCheckedChange = vi.fn();\n      const { user } = await render(\n        <Menu.Root>\n          <Menu.Trigger>Open</Menu.Trigger>\n          <Menu.Portal>\n            <Menu.Positioner>\n              <Menu.Popup>\n                <Menu.CheckboxItem onCheckedChange={onCheckedChange}>Item</Menu.CheckboxItem>\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      const trigger = screen.getByRole('button', { name: 'Open' });\n      await user.click(trigger);\n\n      const item = screen.getByRole('menuitemcheckbox');\n      await user.click(item);\n\n      expect(onCheckedChange.mock.calls.length).toBe(1);\n      expect(onCheckedChange.mock.lastCall?.[0]).toBe(true);\n\n      await user.click(item);\n\n      expect(onCheckedChange.mock.calls.length).toBe(2);\n      expect(onCheckedChange.mock.lastCall?.[0]).toBe(false);\n    });\n\n    it('keeps the state when closed and reopened', async () => {\n      const { user } = await render(\n        <Menu.Root modal={false}>\n          <Menu.Trigger>Open</Menu.Trigger>\n          <Menu.Portal keepMounted>\n            <Menu.Positioner>\n              <Menu.Popup>\n                <Menu.CheckboxItem>Item</Menu.CheckboxItem>\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      const trigger = screen.getByRole('button', { name: 'Open' });\n      await act(() => {\n        trigger.focus();\n      });\n\n      await user.keyboard('{Enter}');\n\n      const item = screen.getByRole('menuitemcheckbox');\n      await user.click(item);\n\n      await user.keyboard('{Enter}');\n      await user.keyboard('{Enter}');\n\n      const itemAfterReopen = screen.getByRole('menuitemcheckbox');\n      expect(itemAfterReopen).toHaveAttribute('aria-checked', 'true');\n      expect(itemAfterReopen).toHaveAttribute('data-checked');\n    });\n  });\n\n  describe('prop: closeOnClick', () => {\n    it('when `closeOnClick=true`, closes the menu when the item is clicked', async () => {\n      const { user } = await render(\n        <Menu.Root>\n          <Menu.Trigger>Open</Menu.Trigger>\n          <Menu.Portal>\n            <Menu.Positioner>\n              <Menu.Popup>\n                <Menu.CheckboxItem closeOnClick>Item</Menu.CheckboxItem>\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      const trigger = screen.getByRole('button', { name: 'Open' });\n      await user.click(trigger);\n\n      const item = screen.getByRole('menuitemcheckbox');\n      await user.click(item);\n\n      expect(screen.queryByRole('menu')).toBe(null);\n    });\n\n    it('does not close the menu when the item is clicked by default', async () => {\n      const { user } = await render(\n        <Menu.Root>\n          <Menu.Trigger>Open</Menu.Trigger>\n          <Menu.Portal>\n            <Menu.Positioner>\n              <Menu.Popup>\n                <Menu.CheckboxItem>Item</Menu.CheckboxItem>\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      const trigger = screen.getByRole('button', { name: 'Open' });\n      await user.click(trigger);\n\n      const item = screen.getByRole('menuitemcheckbox');\n      await user.click(item);\n\n      expect(screen.queryByRole('menu')).not.toBe(null);\n    });\n  });\n\n  describe('prop: focusableWhenDisabled', () => {\n    it('can be focused but not interacted with when disabled', async () => {\n      const handleCheckedChange = vi.fn();\n      const handleClick = vi.fn();\n      const handleKeyDown = vi.fn();\n      const handleKeyUp = vi.fn();\n\n      await render(\n        <Menu.Root open>\n          <Menu.Portal>\n            <Menu.Positioner>\n              <Menu.Popup>\n                <Menu.CheckboxItem\n                  disabled\n                  onCheckedChange={handleCheckedChange}\n                  onClick={handleClick}\n                  onKeyDown={handleKeyDown}\n                  onKeyUp={handleKeyUp}\n                >\n                  Item\n                </Menu.CheckboxItem>\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      const item = screen.getByRole('menuitemcheckbox');\n      await act(() => item.focus());\n      expect(item).toHaveFocus();\n\n      fireEvent.keyDown(item, { key: 'Enter' });\n      expect(handleKeyDown.mock.calls.length).toBe(0);\n      expect(handleClick.mock.calls.length).toBe(0);\n      expect(handleCheckedChange.mock.calls.length).toBe(0);\n\n      fireEvent.keyUp(item, { key: 'Space' });\n      expect(handleKeyUp.mock.calls.length).toBe(0);\n      expect(handleClick.mock.calls.length).toBe(0);\n      expect(handleCheckedChange.mock.calls.length).toBe(0);\n\n      fireEvent.click(item);\n      expect(handleKeyDown.mock.calls.length).toBe(0);\n      expect(handleKeyUp.mock.calls.length).toBe(0);\n      expect(handleClick.mock.calls.length).toBe(0);\n      expect(handleCheckedChange.mock.calls.length).toBe(0);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/menu/checkbox-item/MenuCheckboxItem.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useControlled } from '@base-ui/utils/useControlled';\nimport { MenuCheckboxItemContext } from './MenuCheckboxItemContext';\nimport { REGULAR_ITEM, useMenuItem } from '../item/useMenuItem';\nimport { useCompositeListItem } from '../../composite/list/useCompositeListItem';\nimport { useMenuRootContext } from '../root/MenuRootContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useBaseUiId } from '../../utils/useBaseUiId';\nimport type { BaseUIComponentProps, NonNativeButtonProps } from '../../utils/types';\nimport { itemMapping } from '../utils/stateAttributesMapping';\nimport { useMenuPositionerContext } from '../positioner/MenuPositionerContext';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport type { MenuRoot } from '../root/MenuRoot';\n\n/**\n * A menu item that toggles a setting on or off.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)\n */\nexport const MenuCheckboxItem = React.forwardRef(function MenuCheckboxItem(\n  componentProps: MenuCheckboxItem.Props,\n  forwardedRef: React.ForwardedRef<HTMLElement>,\n) {\n  const {\n    render,\n    className,\n    id: idProp,\n    label,\n    nativeButton = false,\n    disabled = false,\n    closeOnClick = false,\n    checked: checkedProp,\n    defaultChecked,\n    onCheckedChange,\n    ...elementProps\n  } = componentProps;\n\n  const listItem = useCompositeListItem({ label });\n  const menuPositionerContext = useMenuPositionerContext(true);\n  const id = useBaseUiId(idProp);\n\n  const { store } = useMenuRootContext();\n  const highlighted = store.useState('isActive', listItem.index);\n  const itemProps = store.useState('itemProps');\n\n  const [checked, setChecked] = useControlled({\n    controlled: checkedProp,\n    default: defaultChecked ?? false,\n    name: 'MenuCheckboxItem',\n    state: 'checked',\n  });\n\n  const { getItemProps, itemRef } = useMenuItem({\n    closeOnClick,\n    disabled,\n    highlighted,\n    id,\n    store,\n    nativeButton,\n    nodeId: menuPositionerContext?.nodeId,\n    itemMetadata: REGULAR_ITEM,\n  });\n\n  const state: MenuCheckboxItemState = React.useMemo(\n    () => ({\n      disabled,\n      highlighted,\n      checked,\n    }),\n    [disabled, highlighted, checked],\n  );\n\n  const handleClick = useStableCallback((event: React.MouseEvent) => {\n    const details = {\n      ...createChangeEventDetails(REASONS.itemPress, event.nativeEvent),\n      preventUnmountOnClose: () => {},\n    };\n\n    onCheckedChange?.(!checked, details);\n\n    if (details.isCanceled) {\n      return;\n    }\n\n    setChecked((currentlyChecked) => !currentlyChecked);\n  });\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    stateAttributesMapping: itemMapping,\n    props: [\n      itemProps,\n      {\n        role: 'menuitemcheckbox',\n        'aria-checked': checked,\n        onClick: handleClick,\n      },\n      elementProps,\n      getItemProps,\n    ],\n    ref: [itemRef, forwardedRef, listItem.ref],\n  });\n\n  return (\n    <MenuCheckboxItemContext.Provider value={state}>{element}</MenuCheckboxItemContext.Provider>\n  );\n});\n\nexport interface MenuCheckboxItemState {\n  /**\n   * Whether the checkbox item should ignore user interaction.\n   */\n  disabled: boolean;\n  /**\n   * Whether the checkbox item is currently highlighted.\n   */\n  highlighted: boolean;\n  /**\n   * Whether the checkbox item is currently ticked.\n   */\n  checked: boolean;\n}\n\nexport interface MenuCheckboxItemProps\n  extends NonNativeButtonProps, BaseUIComponentProps<'div', MenuCheckboxItemState> {\n  /**\n   * Whether the checkbox item is currently ticked.\n   *\n   * To render an uncontrolled checkbox item, use the `defaultChecked` prop instead.\n   */\n  checked?: boolean | undefined;\n  /**\n   * Whether the checkbox item is initially ticked.\n   *\n   * To render a controlled checkbox item, use the `checked` prop instead.\n   * @default false\n   */\n  defaultChecked?: boolean | undefined;\n  /**\n   * Event handler called when the checkbox item is ticked or unticked.\n   */\n  onCheckedChange?:\n    | ((checked: boolean, eventDetails: MenuCheckboxItem.ChangeEventDetails) => void)\n    | undefined;\n  /**\n   * The click handler for the menu item.\n   */\n  onClick?: BaseUIComponentProps<'div', MenuCheckboxItemState>['onClick'] | undefined;\n  /**\n   * Whether the component should ignore user interaction.\n   * @default false\n   */\n  disabled?: boolean | undefined;\n  /**\n   * Overrides the text label to use when the item is matched during keyboard text navigation.\n   */\n  label?: string | undefined;\n  /**\n   * @ignore\n   */\n  id?: string | undefined;\n  /**\n   * Whether to close the menu when the item is clicked.\n   * @default false\n   */\n  closeOnClick?: boolean | undefined;\n}\n\nexport type MenuCheckboxItemChangeEventReason = MenuRoot.ChangeEventReason;\nexport type MenuCheckboxItemChangeEventDetails = MenuRoot.ChangeEventDetails;\n\nexport namespace MenuCheckboxItem {\n  export type State = MenuCheckboxItemState;\n  export type Props = MenuCheckboxItemProps;\n  export type ChangeEventReason = MenuCheckboxItemChangeEventReason;\n  export type ChangeEventDetails = MenuCheckboxItemChangeEventDetails;\n}\n"
  },
  {
    "path": "packages/react/src/menu/checkbox-item/MenuCheckboxItemContext.ts",
    "content": "'use client';\nimport * as React from 'react';\n\nexport interface MenuCheckboxItemContext {\n  checked: boolean;\n  highlighted: boolean;\n  disabled: boolean;\n}\n\nexport const MenuCheckboxItemContext = React.createContext<MenuCheckboxItemContext | undefined>(\n  undefined,\n);\n\nexport function useMenuCheckboxItemContext() {\n  const context = React.useContext(MenuCheckboxItemContext);\n  if (context === undefined) {\n    throw new Error(\n      'Base UI: MenuCheckboxItemContext is missing. MenuCheckboxItem parts must be placed within <Menu.CheckboxItem>.',\n    );\n  }\n\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/menu/checkbox-item/MenuCheckboxItemDataAttributes.ts",
    "content": "export enum MenuCheckboxItemDataAttributes {\n  /**\n   * Present when the menu checkbox item is checked.\n   */\n  checked = 'data-checked',\n  /**\n   * Present when the menu checkbox item is not checked.\n   */\n  unchecked = 'data-unchecked',\n  /**\n   * Present when the menu checkbox item is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the menu checkbox item is highlighted.\n   */\n  highlighted = 'data-highlighted',\n}\n"
  },
  {
    "path": "packages/react/src/menu/checkbox-item-indicator/MenuCheckboxItemIndicator.test.tsx",
    "content": "import { expect } from 'vitest';\nimport * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\nimport { screen, waitFor } from '@mui/internal-test-utils';\n\ndescribe('<Menu.CheckboxItemIndicator />', () => {\n  beforeEach(() => {\n    globalThis.BASE_UI_ANIMATIONS_DISABLED = true;\n  });\n\n  const { render } = createRenderer();\n\n  describeConformance(<Menu.CheckboxItemIndicator keepMounted />, () => ({\n    refInstanceof: window.HTMLSpanElement,\n    render(node) {\n      return render(\n        <Menu.Root open>\n          <Menu.Portal>\n            <Menu.Positioner>\n              <Menu.Popup>\n                <Menu.CheckboxItem>{node}</Menu.CheckboxItem>\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n    },\n  }));\n\n  it('should remove the indicator when there is no exit animation defined', async ({ skip }) => {\n    if (isJSDOM) {\n      skip();\n    }\n\n    function Test() {\n      const [checked, setChecked] = React.useState(true);\n      return (\n        <div>\n          <button onClick={() => setChecked(false)}>Close</button>\n          <Menu.Root open modal={false}>\n            <Menu.Portal>\n              <Menu.Positioner>\n                <Menu.Popup>\n                  <Menu.CheckboxItem checked={checked}>\n                    <Menu.CheckboxItemIndicator data-testid=\"indicator\" />\n                  </Menu.CheckboxItem>\n                </Menu.Popup>\n              </Menu.Positioner>\n            </Menu.Portal>\n          </Menu.Root>\n        </div>\n      );\n    }\n\n    const { user } = await render(<Test />);\n\n    expect(screen.queryByTestId('indicator')).not.toBe(null);\n\n    const closeButton = screen.getByText('Close');\n\n    await user.click(closeButton);\n\n    await waitFor(() => {\n      expect(screen.queryByTestId('indicator')).toBe(null);\n    });\n  });\n\n  it('should remove the indicator when the animation finishes', async ({ skip }) => {\n    if (isJSDOM) {\n      skip();\n    }\n\n    globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n    let animationFinished = false;\n    const notifyAnimationFinished = () => {\n      animationFinished = true;\n    };\n\n    function Test() {\n      const style = `\n        @keyframes test-anim {\n          to {\n            opacity: 0;\n          }\n        }\n        .animation-test-indicator[data-ending-style] {\n          animation: test-anim 1ms;\n        }\n      `;\n\n      const [checked, setChecked] = React.useState(true);\n\n      return (\n        <div>\n          {/* eslint-disable-next-line react/no-danger */}\n          <style dangerouslySetInnerHTML={{ __html: style }} />\n          <button onClick={() => setChecked(false)}>Close</button>\n          <Menu.Root open modal={false}>\n            <Menu.Portal>\n              <Menu.Positioner>\n                <Menu.Popup>\n                  <Menu.CheckboxItem checked={checked}>\n                    <Menu.CheckboxItemIndicator\n                      className=\"animation-test-indicator\"\n                      data-testid=\"indicator\"\n                      keepMounted\n                      onAnimationEnd={notifyAnimationFinished}\n                    />\n                  </Menu.CheckboxItem>\n                </Menu.Popup>\n              </Menu.Positioner>\n            </Menu.Portal>\n          </Menu.Root>\n        </div>\n      );\n    }\n\n    const { user } = await render(<Test />);\n\n    expect(screen.getByTestId('indicator')).not.toHaveAttribute('hidden');\n\n    const closeButton = screen.getByText('Close');\n    await user.click(closeButton);\n\n    await waitFor(() => {\n      expect(animationFinished).toBe(true);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/menu/checkbox-item-indicator/MenuCheckboxItemIndicator.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useMenuCheckboxItemContext } from '../checkbox-item/MenuCheckboxItemContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { itemMapping } from '../utils/stateAttributesMapping';\nimport { TransitionStatus, useTransitionStatus } from '../../utils/useTransitionStatus';\nimport { useOpenChangeComplete } from '../../utils/useOpenChangeComplete';\n\n/**\n * Indicates whether the checkbox item is ticked.\n * Renders a `<span>` element.\n *\n * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)\n */\nexport const MenuCheckboxItemIndicator = React.forwardRef(function MenuCheckboxItemIndicator(\n  componentProps: MenuCheckboxItemIndicator.Props,\n  forwardedRef: React.ForwardedRef<HTMLSpanElement>,\n) {\n  const { render, className, keepMounted = false, ...elementProps } = componentProps;\n\n  const item = useMenuCheckboxItemContext();\n\n  const indicatorRef = React.useRef<HTMLSpanElement | null>(null);\n\n  const { transitionStatus, setMounted } = useTransitionStatus(item.checked);\n\n  useOpenChangeComplete({\n    open: item.checked,\n    ref: indicatorRef,\n    onComplete() {\n      if (!item.checked) {\n        setMounted(false);\n      }\n    },\n  });\n\n  const state: MenuCheckboxItemIndicatorState = {\n    checked: item.checked,\n    disabled: item.disabled,\n    highlighted: item.highlighted,\n    transitionStatus,\n  };\n\n  const element = useRenderElement('span', componentProps, {\n    state,\n    ref: [forwardedRef, indicatorRef],\n    stateAttributesMapping: itemMapping,\n    props: {\n      'aria-hidden': true,\n      ...elementProps,\n    },\n    enabled: keepMounted || item.checked,\n  });\n\n  return element;\n});\n\nexport interface MenuCheckboxItemIndicatorProps extends BaseUIComponentProps<\n  'span',\n  MenuCheckboxItemIndicatorState\n> {\n  /**\n   * Whether to keep the HTML element in the DOM when the checkbox item is not checked.\n   * @default false\n   */\n  keepMounted?: boolean | undefined;\n}\n\nexport interface MenuCheckboxItemIndicatorState {\n  /**\n   * Whether the checkbox item is currently ticked.\n   */\n  checked: boolean;\n  /**\n   * Whether the component should ignore user interaction.\n   */\n  disabled: boolean;\n  /**\n   * Whether the item is highlighted.\n   */\n  highlighted: boolean;\n  /**\n   * The transition status of the component.\n   */\n  transitionStatus: TransitionStatus;\n}\n\nexport namespace MenuCheckboxItemIndicator {\n  export type Props = MenuCheckboxItemIndicatorProps;\n  export type State = MenuCheckboxItemIndicatorState;\n}\n"
  },
  {
    "path": "packages/react/src/menu/checkbox-item-indicator/MenuCheckboxItemIndicatorDataAttributes.ts",
    "content": "import { TransitionStatusDataAttributes } from '../../utils/stateAttributesMapping';\n\nexport enum MenuCheckboxItemIndicatorDataAttributes {\n  /**\n   * Present when the menu checkbox item is checked.\n   */\n  checked = 'data-checked',\n  /**\n   * Present when the menu checkbox item is not checked.\n   */\n  unchecked = 'data-unchecked',\n  /**\n   * Present when the menu checkbox item is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the indicator is animating in.\n   */\n  startingStyle = TransitionStatusDataAttributes.startingStyle,\n  /**\n   * Present when the indicator is animating out.\n   */\n  endingStyle = TransitionStatusDataAttributes.endingStyle,\n}\n"
  },
  {
    "path": "packages/react/src/menu/group/MenuGroup.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { screen } from '@mui/internal-test-utils';\nimport { Menu } from '@base-ui/react/menu';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Menu.Group />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Menu.Group />, () => ({\n    render,\n    refInstanceof: window.HTMLDivElement,\n  }));\n\n  it('renders a div with the `group` role', async () => {\n    await render(<Menu.Group />);\n    expect(screen.getByRole('group')).toBeVisible();\n  });\n});\n"
  },
  {
    "path": "packages/react/src/menu/group/MenuGroup.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { MenuGroupContext } from './MenuGroupContext';\n\n/**\n * Groups related menu items with the corresponding label.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)\n */\nexport const MenuGroup = React.forwardRef(function MenuGroup(\n  componentProps: MenuGroup.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, ...elementProps } = componentProps;\n\n  const [labelId, setLabelId] = React.useState<string | undefined>(undefined);\n\n  const context = React.useMemo(() => ({ setLabelId }), [setLabelId]);\n\n  const element = useRenderElement('div', componentProps, {\n    ref: forwardedRef,\n    props: {\n      role: 'group',\n      'aria-labelledby': labelId,\n      ...elementProps,\n    },\n  });\n\n  return <MenuGroupContext.Provider value={context}>{element}</MenuGroupContext.Provider>;\n});\n\nexport interface MenuGroupProps extends BaseUIComponentProps<'div', MenuGroupState> {\n  /**\n   * The content of the component.\n   */\n  children?: React.ReactNode;\n}\n\nexport interface MenuGroupState {}\n\nexport namespace MenuGroup {\n  export type Props = MenuGroupProps;\n  export type State = MenuGroupState;\n}\n"
  },
  {
    "path": "packages/react/src/menu/group/MenuGroupContext.ts",
    "content": "'use client';\nimport * as React from 'react';\n\nexport interface MenuGroupContext {\n  setLabelId: (id: string | undefined) => void;\n}\n\nexport const MenuGroupContext = React.createContext<MenuGroupContext | undefined>(undefined);\n\nexport function useMenuGroupRootContext() {\n  const context = React.useContext(MenuGroupContext);\n  if (context === undefined) {\n    throw new Error(\n      'Base UI: MenuGroupRootContext is missing. Menu group parts must be used within <Menu.Group>.',\n    );\n  }\n\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/menu/group-label/MenuGroupLabel.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { screen } from '@mui/internal-test-utils';\nimport { Menu } from '@base-ui/react/menu';\nimport { createRenderer, describeConformance } from '#test-utils';\nimport { MenuGroupContext } from '../group/MenuGroupContext';\n\nconst testContext: MenuGroupContext = {\n  setLabelId: () => {},\n};\n\ndescribe('<Menu.GroupLabel />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Menu.GroupLabel />, () => ({\n    render: (node) => {\n      return render(\n        <MenuGroupContext.Provider value={testContext}>{node}</MenuGroupContext.Provider>,\n      );\n    },\n    refInstanceof: window.HTMLDivElement,\n  }));\n\n  describe('a11y attributes', () => {\n    it('should have the role `presentation`', async () => {\n      await render(\n        <Menu.Root open>\n          <Menu.Portal>\n            <Menu.Positioner>\n              <Menu.Popup>\n                <Menu.Group>\n                  <Menu.GroupLabel>Test group</Menu.GroupLabel>\n                </Menu.Group>\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      const groupLabel = screen.getByText('Test group');\n      expect(groupLabel).toHaveAttribute('role', 'presentation');\n    });\n\n    it(\"should reference the generated id in Group's `aria-labelledby`\", async () => {\n      await render(\n        <Menu.Root open>\n          <Menu.Portal>\n            <Menu.Positioner>\n              <Menu.Popup>\n                <Menu.Group>\n                  <Menu.GroupLabel>Test group</Menu.GroupLabel>\n                </Menu.Group>\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      const group = screen.getByRole('group');\n      const groupLabel = screen.getByText('Test group');\n\n      expect(group).toHaveAttribute('aria-labelledby', groupLabel.id);\n    });\n\n    it(\"should reference the provided id in Group's `aria-labelledby`\", async () => {\n      await render(\n        <Menu.Root open>\n          <Menu.Portal>\n            <Menu.Positioner>\n              <Menu.Popup>\n                <Menu.Group>\n                  <Menu.GroupLabel id=\"test-group\">Test group</Menu.GroupLabel>\n                </Menu.Group>\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      const group = screen.getByRole('group');\n      expect(group).toHaveAttribute('aria-labelledby', 'test-group');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/menu/group-label/MenuGroupLabel.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useBaseUiId } from '../../utils/useBaseUiId';\nimport { useMenuGroupRootContext } from '../group/MenuGroupContext';\n\n/**\n * An accessible label that is automatically associated with its parent group.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)\n */\nexport const MenuGroupLabel = React.forwardRef(function MenuGroupLabelComponent(\n  componentProps: MenuGroupLabel.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { className, render, id: idProp, ...elementProps } = componentProps;\n\n  const id = useBaseUiId(idProp);\n\n  const { setLabelId } = useMenuGroupRootContext();\n\n  useIsoLayoutEffect(() => {\n    setLabelId(id);\n    return () => {\n      setLabelId(undefined);\n    };\n  }, [setLabelId, id]);\n\n  return useRenderElement('div', componentProps, {\n    ref: forwardedRef,\n    props: {\n      id,\n      role: 'presentation',\n      ...elementProps,\n    },\n  });\n});\n\nexport interface MenuGroupLabelProps extends BaseUIComponentProps<'div', MenuGroupLabelState> {}\n\nexport interface MenuGroupLabelState {}\n\nexport namespace MenuGroupLabel {\n  export type Props = MenuGroupLabelProps;\n  export type State = MenuGroupLabelState;\n}\n"
  },
  {
    "path": "packages/react/src/menu/index.parts.ts",
    "content": "export { MenuArrow as Arrow } from './arrow/MenuArrow';\nexport { MenuBackdrop as Backdrop } from './backdrop/MenuBackdrop';\nexport { MenuCheckboxItem as CheckboxItem } from './checkbox-item/MenuCheckboxItem';\nexport { MenuCheckboxItemIndicator as CheckboxItemIndicator } from './checkbox-item-indicator/MenuCheckboxItemIndicator';\nexport { MenuGroup as Group } from './group/MenuGroup';\nexport { MenuGroupLabel as GroupLabel } from './group-label/MenuGroupLabel';\nexport { MenuItem as Item } from './item/MenuItem';\nexport { MenuLinkItem as LinkItem } from './link-item/MenuLinkItem';\nexport { MenuPopup as Popup } from './popup/MenuPopup';\nexport { MenuPortal as Portal } from './portal/MenuPortal';\nexport { MenuPositioner as Positioner } from './positioner/MenuPositioner';\nexport { MenuRadioGroup as RadioGroup } from './radio-group/MenuRadioGroup';\nexport { MenuRadioItem as RadioItem } from './radio-item/MenuRadioItem';\nexport { MenuRadioItemIndicator as RadioItemIndicator } from './radio-item-indicator/MenuRadioItemIndicator';\nexport { MenuRoot as Root } from './root/MenuRoot';\nexport { MenuSubmenuRoot as SubmenuRoot } from './submenu-root/MenuSubmenuRoot';\nexport { MenuTrigger as Trigger } from './trigger/MenuTrigger';\nexport { MenuViewport as Viewport } from './viewport/MenuViewport';\nexport { Separator } from '../separator/Separator';\nexport { MenuSubmenuTrigger as SubmenuTrigger } from './submenu-trigger/MenuSubmenuTrigger';\nexport { MenuHandle as Handle, createMenuHandle as createHandle } from './store/MenuHandle';\n"
  },
  {
    "path": "packages/react/src/menu/index.ts",
    "content": "export * as Menu from './index.parts';\n\nexport type * from './root/MenuRoot';\nexport type * from './arrow/MenuArrow';\nexport type * from './backdrop/MenuBackdrop';\nexport type * from './checkbox-item/MenuCheckboxItem';\nexport type * from './checkbox-item-indicator/MenuCheckboxItemIndicator';\nexport type * from './group-label/MenuGroupLabel';\nexport type * from './group/MenuGroup';\nexport type * from './item/MenuItem';\nexport type * from './link-item/MenuLinkItem';\nexport type * from './popup/MenuPopup';\nexport type * from './portal/MenuPortal';\nexport type * from './positioner/MenuPositioner';\nexport type * from './radio-group/MenuRadioGroup';\nexport type * from './radio-item/MenuRadioItem';\nexport type * from './radio-item-indicator/MenuRadioItemIndicator';\nexport type * from './submenu-root/MenuSubmenuRoot';\nexport type * from './trigger/MenuTrigger';\nexport type * from './submenu-trigger/MenuSubmenuTrigger';\nexport type * from './viewport/MenuViewport';\n"
  },
  {
    "path": "packages/react/src/menu/item/MenuItem.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { act, fireEvent, screen, waitFor } from '@mui/internal-test-utils';\nimport { Menu } from '@base-ui/react/menu';\nimport { describeConformance, createRenderer, isJSDOM } from '#test-utils';\n\ndescribe('<Menu.Item />', () => {\n  const { render, clock } = createRenderer({\n    clockOptions: {\n      shouldAdvanceTime: true,\n    },\n  });\n\n  clock.withFakeTimers();\n\n  describeConformance(<Menu.Item />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    button: true,\n    render: (node) => {\n      return render(<Menu.Root open>{node}</Menu.Root>);\n    },\n  }));\n\n  it('calls the onClick handler when clicked', async () => {\n    const onClick = vi.fn();\n    const { user } = await render(\n      <Menu.Root open>\n        <Menu.Portal>\n          <Menu.Positioner>\n            <Menu.Popup>\n              <Menu.Item onClick={onClick} id=\"item\">\n                Item\n              </Menu.Item>\n            </Menu.Popup>\n          </Menu.Positioner>\n        </Menu.Portal>\n      </Menu.Root>,\n    );\n\n    const item = screen.getByRole('menuitem');\n    await user.click(item);\n\n    expect(onClick.mock.calls.length).toBe(1);\n  });\n\n  it('does not close the menu when onClick prevents Base UI handler', async () => {\n    const onClick = vi.fn((event) => event.preventBaseUIHandler());\n    const { user } = await render(\n      <Menu.Root>\n        <Menu.Trigger>Open</Menu.Trigger>\n        <Menu.Portal>\n          <Menu.Positioner>\n            <Menu.Popup>\n              <Menu.Item onClick={onClick}>Item</Menu.Item>\n            </Menu.Popup>\n          </Menu.Positioner>\n        </Menu.Portal>\n      </Menu.Root>,\n    );\n\n    const trigger = screen.getByRole('button', { name: 'Open' });\n    await user.click(trigger);\n\n    const item = screen.getByRole('menuitem');\n    await user.click(item);\n\n    expect(onClick.mock.calls.length).toBe(1);\n    expect(screen.queryByRole('menu')).not.toBe(null);\n  });\n\n  it('allows onMouseDown to call preventBaseUIHandler', async () => {\n    await render(\n      <Menu.Root open>\n        <Menu.Portal>\n          <Menu.Positioner>\n            <Menu.Popup>\n              <Menu.Item onMouseDown={(event) => event.preventBaseUIHandler()}>Item</Menu.Item>\n            </Menu.Popup>\n          </Menu.Positioner>\n        </Menu.Portal>\n      </Menu.Root>,\n    );\n\n    const item = screen.getByRole('menuitem');\n\n    expect(() => fireEvent.mouseDown(item)).not.toThrow();\n  });\n\n  it('perf: does not rerender menu items unnecessarily', async ({ skip }) => {\n    if (isJSDOM) {\n      skip();\n    }\n\n    const renderItem1Spy = vi.fn();\n    const renderItem2Spy = vi.fn();\n    const renderItem3Spy = vi.fn();\n    const renderItem4Spy = vi.fn();\n\n    const LoggingRoot = React.forwardRef(function LoggingRoot(\n      props: any & { renderSpy: () => void },\n      ref: React.ForwardedRef<HTMLLIElement>,\n    ) {\n      const { renderSpy, state, ...other } = props;\n      renderSpy();\n      return <li {...other} ref={ref} />;\n    });\n\n    const { user } = await render(\n      <Menu.Root open>\n        <Menu.Portal>\n          <Menu.Positioner>\n            <Menu.Popup>\n              <Menu.Item render={<LoggingRoot renderSpy={renderItem1Spy} />} id=\"item-1\">\n                1\n              </Menu.Item>\n              <Menu.Item render={<LoggingRoot renderSpy={renderItem2Spy} />} id=\"item-2\">\n                2\n              </Menu.Item>\n              <Menu.Item render={<LoggingRoot renderSpy={renderItem3Spy} />} id=\"item-3\">\n                3\n              </Menu.Item>\n              <Menu.Item render={<LoggingRoot renderSpy={renderItem4Spy} />} id=\"item-4\">\n                4\n              </Menu.Item>\n            </Menu.Popup>\n          </Menu.Positioner>\n        </Menu.Portal>\n      </Menu.Root>,\n    );\n\n    const menuItems = screen.getAllByRole('menuitem');\n    await act(async () => {\n      menuItems[0].focus();\n    });\n\n    renderItem1Spy.mockClear();\n    renderItem2Spy.mockClear();\n    renderItem3Spy.mockClear();\n    renderItem4Spy.mockClear();\n\n    expect(renderItem1Spy.mock.calls.length).toBe(0);\n\n    await user.keyboard('{ArrowDown}'); // highlights '2'\n\n    // React renders twice in strict mode, so we expect twice the number of spy calls\n\n    await waitFor(\n      () => {\n        expect(renderItem1Spy.mock.calls.length).toBe(2); // '1' rerenders as it loses highlight\n      },\n      { timeout: 1000 },\n    );\n    await waitFor(\n      () => {\n        expect(renderItem2Spy.mock.calls.length).toBe(2); // '2' rerenders as it receives highlight\n      },\n      { timeout: 1000 },\n    );\n\n    // neither the highlighted nor the selected state of these options changed,\n    // so they don't need to rerender:\n    expect(renderItem3Spy.mock.calls.length).toBe(0);\n    expect(renderItem4Spy.mock.calls.length).toBe(0);\n  });\n\n  describe('prop: closeOnClick', () => {\n    it('closes the menu when the item is clicked by default', async () => {\n      const { user } = await render(\n        <Menu.Root>\n          <Menu.Trigger>Open</Menu.Trigger>\n          <Menu.Portal>\n            <Menu.Positioner>\n              <Menu.Popup>\n                <Menu.Item>Item</Menu.Item>\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      const trigger = screen.getByRole('button', { name: 'Open' });\n      await user.click(trigger);\n\n      const item = screen.getByRole('menuitem');\n      await user.click(item);\n\n      expect(screen.queryByRole('menu')).toBe(null);\n    });\n\n    it('when `closeOnClick=false` does not close the menu when the item is clicked', async () => {\n      const { user } = await render(\n        <Menu.Root>\n          <Menu.Trigger>Open</Menu.Trigger>\n          <Menu.Portal>\n            <Menu.Positioner>\n              <Menu.Popup>\n                <Menu.Item closeOnClick={false}>Item</Menu.Item>\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      const trigger = screen.getByRole('button', { name: 'Open' });\n      await user.click(trigger);\n\n      const item = screen.getByRole('menuitem');\n      await user.click(item);\n\n      expect(screen.queryByRole('menu')).not.toBe(null);\n    });\n  });\n\n  describe('disabled state', () => {\n    it('can be focused but not interacted with when disabled', async () => {\n      const handleClick = vi.fn();\n      const handleKeyDown = vi.fn();\n      const handleKeyUp = vi.fn();\n\n      await render(\n        <Menu.Root open>\n          <Menu.Portal>\n            <Menu.Positioner>\n              <Menu.Popup>\n                <Menu.Item\n                  disabled\n                  onClick={handleClick}\n                  onKeyDown={handleKeyDown}\n                  onKeyUp={handleKeyUp}\n                >\n                  Item\n                </Menu.Item>\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      const item = screen.getByRole('menuitem');\n      await act(() => item.focus());\n      expect(item).toHaveFocus();\n\n      fireEvent.keyDown(item, { key: 'Enter' });\n      expect(handleKeyDown.mock.calls.length).toBe(0);\n      expect(handleClick.mock.calls.length).toBe(0);\n\n      fireEvent.keyUp(item, { key: 'Space' });\n      expect(handleKeyUp.mock.calls.length).toBe(0);\n      expect(handleClick.mock.calls.length).toBe(0);\n\n      fireEvent.click(item);\n      expect(handleKeyDown.mock.calls.length).toBe(0);\n      expect(handleKeyUp.mock.calls.length).toBe(0);\n      expect(handleClick.mock.calls.length).toBe(0);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/menu/item/MenuItem.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { REGULAR_ITEM, useMenuItem } from './useMenuItem';\nimport { useMenuRootContext } from '../root/MenuRootContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useBaseUiId } from '../../utils/useBaseUiId';\nimport type { BaseUIComponentProps, NonNativeButtonProps } from '../../utils/types';\nimport { useCompositeListItem } from '../../composite/list/useCompositeListItem';\nimport { useMenuPositionerContext } from '../positioner/MenuPositionerContext';\n\n/**\n * An individual interactive item in the menu.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)\n */\nexport const MenuItem = React.forwardRef(function MenuItem(\n  componentProps: MenuItem.Props,\n  forwardedRef: React.ForwardedRef<HTMLElement>,\n) {\n  const {\n    render,\n    className,\n    id: idProp,\n    label,\n    nativeButton = false,\n    disabled = false,\n    closeOnClick = true,\n    ...elementProps\n  } = componentProps;\n\n  const listItem = useCompositeListItem({ label });\n  const menuPositionerContext = useMenuPositionerContext(true);\n  const id = useBaseUiId(idProp);\n\n  const { store } = useMenuRootContext();\n  const highlighted = store.useState('isActive', listItem.index);\n  const itemProps = store.useState('itemProps');\n\n  const { getItemProps, itemRef } = useMenuItem({\n    closeOnClick,\n    disabled,\n    highlighted,\n    id,\n    store,\n    nativeButton,\n    nodeId: menuPositionerContext?.nodeId,\n    itemMetadata: REGULAR_ITEM,\n  });\n\n  const state: MenuItemState = {\n    disabled,\n    highlighted,\n  };\n\n  return useRenderElement('div', componentProps, {\n    state,\n    props: [itemProps, elementProps, getItemProps],\n    ref: [itemRef, forwardedRef, listItem.ref],\n  });\n});\n\nexport interface MenuItemState {\n  /**\n   * Whether the item should ignore user interaction.\n   */\n  disabled: boolean;\n  /**\n   * Whether the item is highlighted.\n   */\n  highlighted: boolean;\n}\n\nexport interface MenuItemProps\n  extends NonNativeButtonProps, BaseUIComponentProps<'div', MenuItemState> {\n  /**\n   * The click handler for the menu item.\n   */\n  onClick?: BaseUIComponentProps<'div', MenuItemState>['onClick'] | undefined;\n  /**\n   * Whether the component should ignore user interaction.\n   * @default false\n   */\n  disabled?: boolean | undefined;\n  /**\n   * Overrides the text label to use when the item is matched during keyboard text navigation.\n   */\n  label?: string | undefined;\n  /**\n   * @ignore\n   */\n  id?: string | undefined;\n  /**\n   * Whether to close the menu when the item is clicked.\n   *\n   * @default true\n   */\n  closeOnClick?: boolean | undefined;\n}\n\nexport namespace MenuItem {\n  export type State = MenuItemState;\n  export type Props = MenuItemProps;\n}\n"
  },
  {
    "path": "packages/react/src/menu/item/MenuItemDataAttributes.ts",
    "content": "export enum MenuItemDataAttributes {\n  /**\n   * Present when the menu item is highlighted.\n   */\n  highlighted = 'data-highlighted',\n  /**\n   * Present when the menu item is disabled.\n   */\n  disabled = 'data-disabled',\n}\n"
  },
  {
    "path": "packages/react/src/menu/item/useMenuItem.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { useMergedRefs } from '@base-ui/utils/useMergedRefs';\nimport { useButton } from '../../use-button';\nimport { mergeProps } from '../../merge-props';\nimport { HTMLProps } from '../../utils/types';\nimport { MenuStore } from '../store/MenuStore';\nimport { useMenuItemCommonProps } from './useMenuItemCommonProps';\n\nexport const REGULAR_ITEM = {\n  type: 'regular-item' as const,\n};\n\nexport function useMenuItem(params: UseMenuItemParameters): UseMenuItemReturnValue {\n  const {\n    closeOnClick,\n    disabled = false,\n    highlighted,\n    id,\n    store,\n    typingRef = store.context.typingRef,\n    nativeButton,\n    itemMetadata,\n    nodeId,\n  } = params;\n\n  const itemRef = React.useRef<HTMLElement | null>(null);\n\n  const { getButtonProps, buttonRef } = useButton({\n    disabled,\n    focusableWhenDisabled: true,\n    native: nativeButton,\n    composite: true,\n  });\n\n  const commonProps = useMenuItemCommonProps({\n    closeOnClick,\n    highlighted,\n    id,\n    nodeId,\n    store,\n    typingRef,\n    itemRef,\n    itemMetadata,\n  });\n\n  const getItemProps = React.useCallback(\n    (externalProps?: HTMLProps): HTMLProps => {\n      return mergeProps<'div'>(\n        commonProps,\n        {\n          onMouseEnter() {\n            if (itemMetadata.type !== 'submenu-trigger') {\n              return;\n            }\n\n            itemMetadata.setActive();\n          },\n        },\n        externalProps,\n        getButtonProps,\n      );\n    },\n    [commonProps, getButtonProps, itemMetadata],\n  );\n\n  const mergedRef = useMergedRefs(itemRef, buttonRef);\n\n  return React.useMemo(\n    () => ({\n      getItemProps,\n      itemRef: mergedRef,\n    }),\n    [getItemProps, mergedRef],\n  );\n}\n\nexport interface UseMenuItemParameters {\n  /**\n   * Whether to close the menu when the item is clicked.\n   */\n  closeOnClick: boolean;\n  /**\n   * Whether the component should ignore user interaction.\n   */\n  disabled: boolean;\n  /**\n   * Determines if the menu item is highlighted.\n   */\n  highlighted: boolean;\n  /**\n   * The id of the menu item.\n   */\n  id: string | undefined;\n  /**\n   * Whether the component renders a native `<button>` element when replacing it\n   * via the `render` prop.\n   * Set to `false` if the rendered element is not a button (e.g. `<div>`).\n   * @default false\n   */\n  nativeButton: boolean;\n  /**\n   * Additional data specific to the item type.\n   */\n  itemMetadata: UseMenuItemMetadata;\n  /**\n   * The node id of the menu positioner.\n   */\n  nodeId: string | undefined;\n  /**\n   * The menu store.\n   */\n  store: MenuStore<any>;\n  /**\n   * Whether a typeahead session is in progress.\n   * @default store.context.typingRef\n   */\n  typingRef?: React.RefObject<boolean> | undefined;\n}\n\nexport type UseMenuItemMetadata =\n  | typeof REGULAR_ITEM\n  | {\n      type: 'submenu-trigger';\n      setActive: () => void;\n    };\n\nexport interface UseMenuItemReturnValue {\n  /**\n   * Resolver for the root slot's props.\n   * @param externalProps event handlers for the root slot\n   * @returns props that should be spread on the root slot\n   */\n  getItemProps: (externalProps?: HTMLProps) => HTMLProps;\n  /**\n   * The ref to the component's root DOM element.\n   */\n  itemRef: React.RefCallback<HTMLElement> | null;\n}\n\nexport interface UseMenuItemState {}\n"
  },
  {
    "path": "packages/react/src/menu/item/useMenuItemCommonProps.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { isMac } from '@base-ui/utils/detectBrowser';\nimport { HTMLProps } from '../../utils/types';\nimport { MenuStore } from '../store/MenuStore';\nimport { REASONS } from '../../utils/reasons';\nimport { useContextMenuRootContext } from '../../context-menu/root/ContextMenuRootContext';\nimport type { UseMenuItemMetadata } from './useMenuItem';\n\nexport interface UseMenuItemCommonPropsParameters {\n  /**\n   * Whether to close the menu when the item is clicked.\n   */\n  closeOnClick: boolean;\n  /**\n   * Determines if the menu item is highlighted.\n   */\n  highlighted: boolean;\n  /**\n   * The id of the menu item.\n   */\n  id: string | undefined;\n  /**\n   * The node id of the menu positioner.\n   */\n  nodeId: string | undefined;\n  /**\n   * The menu store.\n   */\n  store: MenuStore<any>;\n  /**\n   * Whether a typeahead session is in progress.\n   */\n  typingRef?: React.RefObject<boolean> | undefined;\n  /**\n   * Ref to the item element.\n   */\n  itemRef: React.RefObject<HTMLElement | null>;\n  /**\n   * Optional metadata for checking item type before triggering click.\n   * If provided, click will only be triggered for 'regular-item' type.\n   */\n  itemMetadata?: UseMenuItemMetadata | undefined;\n}\n\n/**\n * Returns common props shared by all menu item types.\n * This hook extracts the shared logic for id, role, tabIndex, onKeyDown,\n * onMouseMove, onClick, and onMouseUp handlers.\n */\nexport function useMenuItemCommonProps(params: UseMenuItemCommonPropsParameters): HTMLProps {\n  const { closeOnClick, highlighted, id, nodeId, store, typingRef, itemRef, itemMetadata } = params;\n\n  const { events: menuEvents } = store.useState('floatingTreeRoot');\n  const contextMenuContext = useContextMenuRootContext(true);\n  const isContextMenu = contextMenuContext !== undefined;\n\n  return React.useMemo(\n    () => ({\n      id,\n      role: 'menuitem' as const,\n      tabIndex: highlighted ? 0 : -1,\n      onKeyDown(event: React.KeyboardEvent) {\n        if (event.key === ' ' && typingRef?.current) {\n          event.preventDefault();\n        }\n      },\n      onMouseMove(event: React.MouseEvent) {\n        if (!nodeId) {\n          return;\n        }\n\n        // Inform the floating tree that a menu item within this menu was hovered/moved over\n        // so unrelated descendant submenus can be closed.\n        menuEvents.emit('itemhover', {\n          nodeId,\n          target: event.currentTarget,\n        });\n      },\n      onClick(event: React.MouseEvent) {\n        if (closeOnClick) {\n          menuEvents.emit('close', { domEvent: event, reason: REASONS.itemPress });\n        }\n      },\n      onMouseUp(event: React.MouseEvent) {\n        if (contextMenuContext) {\n          const initialCursorPoint = contextMenuContext.initialCursorPointRef.current;\n          contextMenuContext.initialCursorPointRef.current = null;\n          if (\n            isContextMenu &&\n            initialCursorPoint &&\n            Math.abs(event.clientX - initialCursorPoint.x) <= 1 &&\n            Math.abs(event.clientY - initialCursorPoint.y) <= 1\n          ) {\n            return;\n          }\n\n          // On non-macOS platforms, this mouseup belongs to the right-click gesture\n          // that opened the context menu, so it must not activate an item.\n          if (isContextMenu && !isMac && event.button === 2) {\n            return;\n          }\n        }\n\n        if (\n          itemRef.current &&\n          store.context.allowMouseUpTriggerRef.current &&\n          (!isContextMenu || event.button === 2)\n        ) {\n          // This fires whenever the user clicks on the trigger, moves the cursor, and releases it over the item.\n          // We trigger the click and override the `closeOnClick` preference to always close the menu.\n          if (!itemMetadata || itemMetadata.type === 'regular-item') {\n            itemRef.current.click();\n          }\n        }\n      },\n    }),\n    [\n      closeOnClick,\n      highlighted,\n      id,\n      menuEvents,\n      nodeId,\n      store,\n      typingRef,\n      itemRef,\n      contextMenuContext,\n      isContextMenu,\n      itemMetadata,\n    ],\n  );\n}\n\nexport interface UseMenuItemCommonPropsState {}\n"
  },
  {
    "path": "packages/react/src/menu/link-item/MenuLinkItem.test.tsx",
    "content": "import { expect } from 'vitest';\nimport * as React from 'react';\nimport { MemoryRouter, Route, Routes, Link, useLocation } from 'react-router';\nimport { act, screen, waitFor } from '@mui/internal-test-utils';\nimport { Menu } from '@base-ui/react/menu';\nimport { describeConformance, createRenderer, isJSDOM } from '#test-utils';\n\ndescribe('<Menu.LinkItem />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Menu.LinkItem />, () => ({\n    refInstanceof: window.HTMLAnchorElement,\n    render: (node) => {\n      return render(<Menu.Root open>{node}</Menu.Root>);\n    },\n  }));\n\n  describe('rendering links', () => {\n    function One() {\n      return <div>page one</div>;\n    }\n    function Two() {\n      return <div>page two</div>;\n    }\n    function LocationDisplay() {\n      const location = useLocation();\n      return <div data-testid=\"location\">{location.pathname}</div>;\n    }\n\n    it.skipIf(isJSDOM)('react-router <Link> activates with Enter and Space', async () => {\n      const { user } = await render(\n        <MemoryRouter initialEntries={['/']}>\n          <Routes>\n            <Route path=\"/\" element={<One />} />\n            <Route path=\"/two\" element={<Two />} />\n          </Routes>\n\n          <LocationDisplay />\n\n          <Menu.Root open>\n            <Menu.Portal>\n              <Menu.Positioner>\n                <Menu.Popup>\n                  <Menu.LinkItem render={<Link to=\"/\" />}>link 1</Menu.LinkItem>\n                  <Menu.LinkItem render={<Link to=\"/two\" />}>link 2</Menu.LinkItem>\n                </Menu.Popup>\n              </Menu.Positioner>\n            </Menu.Portal>\n          </Menu.Root>\n        </MemoryRouter>,\n      );\n\n      const [link1, link2] = screen.getAllByRole('menuitem');\n\n      const locationDisplay = screen.getByTestId('location');\n\n      expect(screen.getByText(/page one/i)).not.toBe(null);\n\n      expect(locationDisplay).toHaveTextContent('/');\n\n      act(() => {\n        link2.focus();\n      });\n\n      await waitFor(() => {\n        expect(link2).toHaveFocus();\n      });\n\n      await user.keyboard('[Enter]');\n\n      expect(locationDisplay).toHaveTextContent('/two');\n\n      expect(screen.getByText(/page two/i)).not.toBe(null);\n\n      act(() => {\n        link1.focus();\n      });\n\n      await waitFor(() => {\n        expect(link1).toHaveFocus();\n      });\n\n      await user.keyboard('[Enter]');\n\n      expect(screen.getByText(/page one/i)).not.toBe(null);\n\n      expect(locationDisplay).toHaveTextContent('/');\n\n      act(() => {\n        link2.focus();\n      });\n\n      await waitFor(() => {\n        expect(link2).toHaveFocus();\n      });\n\n      await user.keyboard('[Space]');\n\n      expect(locationDisplay).toHaveTextContent('/two');\n\n      expect(screen.getByText(/page two/i)).not.toBe(null);\n\n      act(() => {\n        link1.focus();\n      });\n\n      await waitFor(() => {\n        expect(link1).toHaveFocus();\n      });\n\n      await user.keyboard('[Space]');\n\n      expect(screen.getByText(/page one/i)).not.toBe(null);\n\n      expect(locationDisplay).toHaveTextContent('/');\n    });\n\n    it.skipIf(isJSDOM)(\n      'does not navigate when Space is pressed during an active typeahead session',\n      async () => {\n        const { user } = await render(\n          <MemoryRouter initialEntries={['/']}>\n            <Routes>\n              <Route path=\"/\" element={<One />} />\n              <Route path=\"/two\" element={<Two />} />\n            </Routes>\n\n            <LocationDisplay />\n\n            <Menu.Root open>\n              <Menu.Portal>\n                <Menu.Positioner>\n                  <Menu.Popup>\n                    <Menu.LinkItem render={<Link to=\"/\" />}>Item One</Menu.LinkItem>\n                    <Menu.LinkItem render={<Link to=\"/two\" />}>Item Two</Menu.LinkItem>\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            </Menu.Root>\n          </MemoryRouter>,\n        );\n\n        const [link1, link2] = screen.getAllByRole('menuitem');\n        const locationDisplay = screen.getByTestId('location');\n\n        act(() => {\n          link1.focus();\n        });\n\n        await waitFor(() => {\n          expect(link1).toHaveFocus();\n        });\n\n        await user.keyboard('Item T');\n\n        await waitFor(() => {\n          expect(link2).toHaveFocus();\n        });\n\n        expect(locationDisplay).toHaveTextContent('/');\n\n        await user.keyboard('[Space]');\n        expect(locationDisplay).toHaveTextContent('/');\n      },\n    );\n  });\n});\n"
  },
  {
    "path": "packages/react/src/menu/link-item/MenuLinkItem.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useMenuRootContext } from '../root/MenuRootContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useBaseUiId } from '../../utils/useBaseUiId';\nimport type { BaseUIComponentProps, HTMLProps } from '../../utils/types';\nimport { useCompositeListItem } from '../../composite/list/useCompositeListItem';\nimport { useMenuPositionerContext } from '../positioner/MenuPositionerContext';\nimport { useMenuItemCommonProps } from '../item/useMenuItemCommonProps';\nimport { useButton } from '../../use-button';\nimport { mergeProps } from '../../merge-props';\n\n/**\n * A link in the menu that can be used to navigate to a different page or section.\n * Renders an `<a>` element.\n *\n * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)\n */\nexport const MenuLinkItem = React.forwardRef(function MenuLinkItem(\n  componentProps: MenuLinkItem.Props,\n  forwardedRef: React.ForwardedRef<Element>,\n) {\n  const {\n    render,\n    className,\n    id: idProp,\n    label,\n    closeOnClick = false,\n    ...elementProps\n  } = componentProps;\n\n  const linkRef = React.useRef<HTMLAnchorElement | null>(null);\n\n  const listItem = useCompositeListItem({ label });\n  const menuPositionerContext = useMenuPositionerContext(true);\n  const nodeId = menuPositionerContext?.nodeId;\n\n  const id = useBaseUiId(idProp);\n\n  const { store } = useMenuRootContext();\n  const highlighted = store.useState('isActive', listItem.index);\n  const itemProps = store.useState('itemProps');\n  const typingRef = store.context.typingRef;\n\n  const { getButtonProps, buttonRef } = useButton({\n    native: false,\n    composite: true,\n  });\n\n  const commonProps = useMenuItemCommonProps({\n    closeOnClick,\n    highlighted,\n    id,\n    nodeId,\n    store,\n    typingRef,\n    itemRef: linkRef,\n  });\n\n  function getItemProps(externalProps?: HTMLProps): HTMLProps {\n    return mergeProps<'a'>(commonProps, externalProps, getButtonProps);\n  }\n\n  const state: MenuLinkItemState = React.useMemo(\n    () => ({\n      highlighted,\n    }),\n    [highlighted],\n  );\n\n  return useRenderElement('a', componentProps, {\n    state,\n    props: [itemProps, elementProps, getItemProps],\n    ref: [linkRef, buttonRef, forwardedRef, listItem.ref],\n  });\n});\n\nexport interface MenuLinkItemState {\n  /**\n   * Whether the item is highlighted.\n   */\n  highlighted: boolean;\n}\n\nexport interface MenuLinkItemProps extends BaseUIComponentProps<'a', MenuLinkItemState> {\n  /**\n   * Overrides the text label to use when the item is matched during keyboard text navigation.\n   */\n  label?: string | undefined;\n  /**\n   * @ignore\n   */\n  id?: string | undefined;\n  /**\n   * Whether to close the menu when the item is clicked.\n   * @default false\n   */\n  closeOnClick?: boolean | undefined;\n}\n\nexport namespace MenuLinkItem {\n  export type State = MenuLinkItemState;\n  export type Props = MenuLinkItemProps;\n}\n"
  },
  {
    "path": "packages/react/src/menu/link-item/MenuLinkItemDataAttributes.ts",
    "content": "export enum MenuLinkItemDataAttributes {\n  /**\n   * Present when the link is highlighted.\n   */\n  highlighted = 'data-highlighted',\n}\n"
  },
  {
    "path": "packages/react/src/menu/popup/MenuPopup.test.tsx",
    "content": "import { expect } from 'vitest';\nimport * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\nimport { createRenderer, describeConformance } from '#test-utils';\nimport { act, waitFor, screen } from '@mui/internal-test-utils';\n\ndescribe('<Menu.Popup />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Menu.Popup />, () => ({\n    render: (node) => {\n      return render(\n        <Menu.Root open>\n          <Menu.Portal>\n            <Menu.Positioner>{node}</Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n    },\n    refInstanceof: window.HTMLDivElement,\n  }));\n\n  describe('prop: finalFocus', () => {\n    it('should focus the trigger by default when closed', async () => {\n      await render(\n        <div>\n          <input />\n          <Menu.Root>\n            <Menu.Trigger>Open</Menu.Trigger>\n            <Menu.Portal>\n              <Menu.Positioner>\n                <Menu.Popup>\n                  <Menu.Item>Close</Menu.Item>\n                </Menu.Popup>\n              </Menu.Positioner>\n            </Menu.Portal>\n          </Menu.Root>\n          <input />\n        </div>,\n      );\n\n      const trigger = screen.getByText('Open');\n      await act(async () => {\n        trigger.click();\n      });\n\n      const closeButton = screen.getByText('Close');\n      await act(async () => {\n        closeButton.click();\n      });\n\n      await waitFor(() => {\n        expect(trigger).toHaveFocus();\n      });\n    });\n\n    it('should focus the element provided to the prop when closed', async () => {\n      function TestComponent() {\n        const inputRef = React.useRef<HTMLInputElement | null>(null);\n        return (\n          <div>\n            <input />\n            <Menu.Root>\n              <Menu.Trigger>Open</Menu.Trigger>\n              <Menu.Portal>\n                <Menu.Positioner>\n                  <Menu.Popup finalFocus={inputRef}>\n                    <Menu.Item>Close</Menu.Item>\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            </Menu.Root>\n            <input />\n            <input data-testid=\"input-to-focus\" ref={inputRef} />\n            <input />\n          </div>\n        );\n      }\n\n      const { user } = await render(<TestComponent />);\n\n      const trigger = screen.getByText('Open');\n      await user.click(trigger);\n\n      const closeButton = await screen.findByText('Close');\n      await user.click(closeButton);\n\n      const inputToFocus = screen.getByTestId('input-to-focus');\n\n      await waitFor(() => {\n        expect(inputToFocus).toHaveFocus();\n      });\n    });\n\n    it('should focus the element provided to `finalFocus` as a function when closed', async () => {\n      function TestComponent() {\n        const ref = React.useRef<HTMLInputElement>(null);\n        const getRef = React.useCallback(() => ref.current, []);\n        return (\n          <div>\n            <Menu.Root>\n              <Menu.Trigger>Open</Menu.Trigger>\n              <Menu.Portal>\n                <Menu.Positioner>\n                  <Menu.Popup finalFocus={getRef}>\n                    <Menu.Item>Close</Menu.Item>\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            </Menu.Root>\n            <input data-testid=\"input-to-focus\" ref={ref} />\n          </div>\n        );\n      }\n\n      const { user } = await render(<TestComponent />);\n\n      const trigger = screen.getByText('Open');\n      await user.click(trigger);\n\n      const closeButton = await screen.findByText('Close');\n      await user.click(closeButton);\n\n      await waitFor(() => {\n        expect(screen.getByTestId('input-to-focus')).toHaveFocus();\n      });\n    });\n\n    it('should not move focus when finalFocus is false', async () => {\n      function TestComponent() {\n        return (\n          <div>\n            <Menu.Root>\n              <Menu.Trigger>Open</Menu.Trigger>\n              <Menu.Portal>\n                <Menu.Positioner>\n                  <Menu.Popup finalFocus={false}>\n                    <Menu.Item>Close</Menu.Item>\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            </Menu.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<TestComponent />);\n      const trigger = screen.getByText('Open');\n\n      await user.click(trigger);\n      await user.click(await screen.findByText('Close'));\n\n      await waitFor(() => {\n        expect(trigger).not.toHaveFocus();\n      });\n    });\n\n    it('should move focus to trigger when finalFocus returns true', async () => {\n      function TestComponent() {\n        return (\n          <div>\n            <Menu.Root>\n              <Menu.Trigger>Open</Menu.Trigger>\n              <Menu.Portal>\n                <Menu.Positioner>\n                  <Menu.Popup finalFocus={() => true}>\n                    <Menu.Item>Close</Menu.Item>\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            </Menu.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<TestComponent />);\n      const trigger = screen.getByText('Open');\n\n      await user.click(trigger);\n      await user.click(await screen.findByText('Close'));\n\n      await waitFor(() => {\n        expect(trigger).toHaveFocus();\n      });\n    });\n\n    it('uses default behavior when finalFocus returns null', async () => {\n      function TestComponent() {\n        return (\n          <div>\n            <Menu.Root>\n              <Menu.Trigger>Open</Menu.Trigger>\n              <Menu.Portal>\n                <Menu.Positioner>\n                  <Menu.Popup finalFocus={() => null}>\n                    <Menu.Item>Close</Menu.Item>\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            </Menu.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<TestComponent />);\n      const trigger = screen.getByText('Open');\n      await user.click(trigger);\n      await user.click(await screen.findByText('Close'));\n      await waitFor(() => {\n        expect(trigger).toHaveFocus();\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/menu/popup/MenuPopup.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport type { InteractionType } from '@base-ui/utils/useEnhancedClickHandler';\nimport { FloatingFocusManager, useHoverFloatingInteraction } from '../../floating-ui-react';\nimport { useMenuRootContext } from '../root/MenuRootContext';\nimport type { MenuRoot } from '../root/MenuRoot';\nimport { useMenuPositionerContext } from '../positioner/MenuPositionerContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport type { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport type { Side, Align } from '../../utils/useAnchorPositioning';\nimport type { TransitionStatus } from '../../utils/useTransitionStatus';\nimport { popupStateMapping as baseMapping } from '../../utils/popupStateMapping';\nimport { transitionStatusMapping } from '../../utils/stateAttributesMapping';\nimport { useOpenChangeComplete } from '../../utils/useOpenChangeComplete';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport { useToolbarRootContext } from '../../toolbar/root/ToolbarRootContext';\nimport { COMPOSITE_KEYS } from '../../composite/composite';\nimport { getDisabledMountTransitionStyles } from '../../utils/getDisabledMountTransitionStyles';\n\nconst stateAttributesMapping: StateAttributesMapping<MenuPopupState> = {\n  ...baseMapping,\n  ...transitionStatusMapping,\n};\n\n/**\n * A container for the menu items.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)\n */\nexport const MenuPopup = React.forwardRef(function MenuPopup(\n  componentProps: MenuPopup.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, finalFocus, ...elementProps } = componentProps;\n\n  const { store } = useMenuRootContext();\n  const { side, align } = useMenuPositionerContext();\n  const insideToolbar = useToolbarRootContext(true) != null;\n\n  const open = store.useState('open');\n  const transitionStatus = store.useState('transitionStatus');\n  const popupProps = store.useState('popupProps');\n  const mounted = store.useState('mounted');\n  const instantType = store.useState('instantType');\n  const triggerElement = store.useState('activeTriggerElement');\n  const parent = store.useState('parent');\n  const lastOpenChangeReason = store.useState('lastOpenChangeReason');\n  const rootId = store.useState('rootId');\n  const floatingContext = store.useState('floatingRootContext');\n  const floatingTreeRoot = store.useState('floatingTreeRoot');\n  const closeDelay = store.useState('closeDelay');\n  const activeTriggerElement = store.useState('activeTriggerElement');\n\n  const isContextMenu = parent.type === 'context-menu';\n\n  useOpenChangeComplete({\n    open,\n    ref: store.context.popupRef,\n    onComplete() {\n      if (open) {\n        store.context.onOpenChangeComplete?.(true);\n      }\n    },\n  });\n\n  React.useEffect(() => {\n    function handleClose(event: {\n      domEvent: Event | undefined;\n      reason: MenuRoot.ChangeEventReason;\n    }) {\n      store.setOpen(false, createChangeEventDetails(event.reason, event.domEvent));\n    }\n\n    floatingTreeRoot.events.on('close', handleClose);\n\n    return () => {\n      floatingTreeRoot.events.off('close', handleClose);\n    };\n  }, [floatingTreeRoot.events, store]);\n\n  const hoverEnabled = store.useState('hoverEnabled');\n  const disabled = store.useState('disabled');\n\n  useHoverFloatingInteraction(floatingContext, {\n    enabled: hoverEnabled && !disabled && !isContextMenu && parent.type !== 'menubar',\n    closeDelay,\n  });\n\n  const state: MenuPopupState = {\n    transitionStatus,\n    side,\n    align,\n    open,\n    nested: parent.type === 'menu',\n    instant: instantType,\n  };\n\n  const setPopupElement = React.useCallback(\n    (element: HTMLElement | null) => {\n      store.set('popupElement', element);\n    },\n    [store],\n  );\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: [forwardedRef, store.context.popupRef, setPopupElement],\n    stateAttributesMapping,\n    props: [\n      popupProps,\n      {\n        onKeyDown(event) {\n          if (insideToolbar && COMPOSITE_KEYS.has(event.key)) {\n            event.stopPropagation();\n          }\n        },\n      },\n      getDisabledMountTransitionStyles(transitionStatus),\n      elementProps,\n      { 'data-rootownerid': rootId } as Record<string, string>,\n    ],\n  });\n\n  let returnFocus = parent.type === undefined || isContextMenu;\n  if (\n    triggerElement ||\n    (parent.type === 'menubar' && lastOpenChangeReason !== REASONS.outsidePress)\n  ) {\n    returnFocus = true;\n  }\n\n  return (\n    <FloatingFocusManager\n      context={floatingContext}\n      modal={isContextMenu}\n      disabled={!mounted}\n      returnFocus={finalFocus === undefined ? returnFocus : finalFocus}\n      initialFocus={parent.type !== 'menu'}\n      restoreFocus\n      externalTree={parent.type !== 'menubar' ? floatingTreeRoot : undefined}\n      previousFocusableElement={activeTriggerElement as HTMLElement | null}\n      nextFocusableElement={\n        parent.type === undefined ? store.context.triggerFocusTargetRef : undefined\n      }\n      beforeContentFocusGuardRef={\n        parent.type === undefined ? store.context.beforeContentFocusGuardRef : undefined\n      }\n    >\n      {element}\n    </FloatingFocusManager>\n  );\n});\n\nexport interface MenuPopupProps extends BaseUIComponentProps<'div', MenuPopupState> {\n  children?: React.ReactNode;\n  /**\n   * @ignore\n   */\n  id?: string | undefined;\n  /**\n   * Determines the element to focus when the menu is closed.\n   *\n   * - `false`: Do not move focus.\n   * - `true`: Move focus based on the default behavior (trigger or previously focused element).\n   * - `RefObject`: Move focus to the ref element.\n   * - `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`).\n   *   Return an element to focus, `true` to use the default behavior, or `false`/`undefined` to do nothing.\n   */\n  finalFocus?:\n    | boolean\n    | React.RefObject<HTMLElement | null>\n    | ((closeType: InteractionType) => boolean | HTMLElement | null | void)\n    | undefined;\n}\n\nexport interface MenuPopupState {\n  /**\n   * The transition status of the component.\n   */\n  transitionStatus: TransitionStatus;\n  /**\n   * The side of the anchor the component is placed on.\n   */\n  side: Side;\n  /**\n   * The alignment of the component relative to the anchor.\n   */\n  align: Align;\n  /**\n   * Whether the menu is currently open.\n   */\n  open: boolean;\n  /**\n   * Whether the component is nested.\n   */\n  nested: boolean;\n  /**\n   * Whether transitions should be skipped.\n   */\n  instant: 'dismiss' | 'click' | 'group' | 'trigger-change' | undefined;\n}\n\nexport namespace MenuPopup {\n  export type Props = MenuPopupProps;\n  export type State = MenuPopupState;\n}\n"
  },
  {
    "path": "packages/react/src/menu/popup/MenuPopupDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum MenuPopupDataAttributes {\n  /**\n   * Present when the menu is open.\n   */\n  open = CommonPopupDataAttributes.open,\n  /**\n   * Present when the menu is closed.\n   */\n  closed = CommonPopupDataAttributes.closed,\n  /**\n   * Present when the menu is animating in.\n   */\n  startingStyle = CommonPopupDataAttributes.startingStyle,\n  /**\n   * Present when the menu is animating out.\n   */\n  endingStyle = CommonPopupDataAttributes.endingStyle,\n  /**\n   * Indicates which side the popup is positioned relative to the trigger.\n   * @type {'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'}\n   */\n  side = CommonPopupDataAttributes.side,\n  /**\n   * Indicates how the popup is aligned relative to specified side.\n   * @type {'start' | 'center' | 'end'}\n   */\n  align = CommonPopupDataAttributes.align,\n  /**\n   * Present if animations should be instant.\n   * @type {'click' | 'dismiss' | 'group' | 'trigger-change'}\n   */\n  instant = 'data-instant',\n}\n"
  },
  {
    "path": "packages/react/src/menu/portal/MenuPortal.test.tsx",
    "content": "import * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Menu.Portal />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Menu.Portal keepMounted />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(<Menu.Root>{node}</Menu.Root>);\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/menu/portal/MenuPortal.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { FloatingPortal } from '../../floating-ui-react';\nimport { useMenuRootContext } from '../root/MenuRootContext';\nimport { MenuPortalContext } from './MenuPortalContext';\n\n/**\n * A portal element that moves the popup to a different part of the DOM.\n * By default, the portal element is appended to `<body>`.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)\n */\nexport const MenuPortal = React.forwardRef(function MenuPortal(\n  props: MenuPortal.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { keepMounted = false, ...portalProps } = props;\n\n  const { store } = useMenuRootContext();\n  const mounted = store.useState('mounted');\n\n  const shouldRender = mounted || keepMounted;\n  if (!shouldRender) {\n    return null;\n  }\n\n  return (\n    <MenuPortalContext.Provider value={keepMounted}>\n      <FloatingPortal ref={forwardedRef} {...portalProps} />\n    </MenuPortalContext.Provider>\n  );\n});\n\nexport interface MenuPortalState {}\n\nexport interface MenuPortalProps extends FloatingPortal.Props<MenuPortalState> {\n  /**\n   * Whether to keep the portal mounted in the DOM while the popup is hidden.\n   * @default false\n   */\n  keepMounted?: boolean | undefined;\n}\n\nexport namespace MenuPortal {\n  export type State = MenuPortalState;\n  export type Props = MenuPortalProps;\n}\n"
  },
  {
    "path": "packages/react/src/menu/portal/MenuPortalContext.ts",
    "content": "'use client';\nimport * as React from 'react';\n\nexport const MenuPortalContext = React.createContext<boolean | undefined>(undefined);\n\nexport function useMenuPortalContext() {\n  const value = React.useContext(MenuPortalContext);\n  if (value === undefined) {\n    throw new Error('Base UI: <Menu.Portal> is missing.');\n  }\n  return value;\n}\n"
  },
  {
    "path": "packages/react/src/menu/positioner/MenuPositioner.spec.tsx",
    "content": "import { Menu } from '@base-ui/react';\n\n// @ts-expect-error - `keepMounted` should not be available\n<Menu.Positioner keepMounted />;\n"
  },
  {
    "path": "packages/react/src/menu/positioner/MenuPositioner.test.tsx",
    "content": "import { afterEach, expect } from 'vitest';\nimport * as React from 'react';\nimport userEvent from '@testing-library/user-event';\nimport { flushMicrotasks, screen, waitFor } from '@mui/internal-test-utils';\nimport { Menu } from '@base-ui/react/menu';\nimport { describeConformance, createRenderer, isJSDOM } from '#test-utils';\n\nconst Trigger = React.forwardRef(function Trigger(\n  props: Menu.Trigger.Props,\n  ref: React.ForwardedRef<any>,\n) {\n  return <Menu.Trigger {...props} ref={ref} render={<div />} nativeButton={false} />;\n});\n\ndescribe('<Menu.Positioner />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Menu.Positioner />, () => ({\n    render: (node) => {\n      return render(\n        <Menu.Root open>\n          <Menu.Portal>{node}</Menu.Portal>\n        </Menu.Root>,\n      );\n    },\n    refInstanceof: window.HTMLDivElement,\n  }));\n\n  describe.skipIf(isJSDOM)('prop: anchor', () => {\n    it('should be placed near the specified element when a ref is passed', async () => {\n      function TestComponent() {\n        const anchor = React.useRef<HTMLDivElement | null>(null);\n\n        return (\n          <div style={{ margin: '50px' }}>\n            <Menu.Root open>\n              <Menu.Portal>\n                <Menu.Positioner\n                  side=\"bottom\"\n                  align=\"start\"\n                  anchor={anchor}\n                  arrowPadding={0}\n                  data-testid=\"positioner\"\n                >\n                  <Menu.Popup>\n                    <Menu.Item>1</Menu.Item>\n                    <Menu.Item>2</Menu.Item>\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            </Menu.Root>\n            <div data-testid=\"anchor\" style={{ marginTop: '100px' }} ref={anchor} />\n          </div>\n        );\n      }\n\n      await render(<TestComponent />);\n\n      const positioner = screen.getByTestId('positioner');\n      const anchor = screen.getByTestId('anchor');\n\n      const anchorPosition = anchor.getBoundingClientRect();\n\n      await flushMicrotasks();\n\n      expect(positioner.style.getPropertyValue('transform')).toBe(\n        `translate(${anchorPosition.left}px, ${anchorPosition.bottom}px)`,\n      );\n    });\n\n    it('should be placed near the specified element when an element is passed', async () => {\n      function TestComponent() {\n        const [anchor, setAnchor] = React.useState<HTMLDivElement | null>(null);\n        const handleRef = React.useCallback((element: HTMLDivElement | null) => {\n          setAnchor(element);\n        }, []);\n\n        return (\n          <div style={{ margin: '50px' }}>\n            <Menu.Root open>\n              <Menu.Portal>\n                <Menu.Positioner\n                  side=\"bottom\"\n                  align=\"start\"\n                  anchor={anchor}\n                  arrowPadding={0}\n                  data-testid=\"positioner\"\n                >\n                  <Menu.Popup>\n                    <Menu.Item>1</Menu.Item>\n                    <Menu.Item>2</Menu.Item>\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            </Menu.Root>\n            <div data-testid=\"anchor\" style={{ marginTop: '100px' }} ref={handleRef} />\n          </div>\n        );\n      }\n\n      await render(<TestComponent />);\n\n      const positioner = screen.getByTestId('positioner');\n      const anchor = screen.getByTestId('anchor');\n\n      const anchorPosition = anchor.getBoundingClientRect();\n\n      await flushMicrotasks();\n\n      expect(positioner.style.getPropertyValue('transform')).toBe(\n        `translate(${anchorPosition.left}px, ${anchorPosition.bottom}px)`,\n      );\n    });\n\n    it('should be placed near the specified element when a function returning an element is passed', async () => {\n      function TestComponent() {\n        const [anchor, setAnchor] = React.useState<HTMLDivElement | null>(null);\n        const handleRef = React.useCallback((element: HTMLDivElement | null) => {\n          setAnchor(element);\n        }, []);\n\n        const getAnchor = React.useCallback(() => anchor, [anchor]);\n\n        return (\n          <div style={{ margin: '50px' }}>\n            <Menu.Root open>\n              <Menu.Portal>\n                <Menu.Positioner\n                  side=\"bottom\"\n                  align=\"start\"\n                  anchor={getAnchor}\n                  arrowPadding={0}\n                  data-testid=\"positioner\"\n                >\n                  <Menu.Popup>\n                    <Menu.Item>1</Menu.Item>\n                    <Menu.Item>2</Menu.Item>\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            </Menu.Root>\n            <div data-testid=\"anchor\" style={{ marginTop: '100px' }} ref={handleRef} />\n          </div>\n        );\n      }\n\n      await render(<TestComponent />);\n\n      const positioner = screen.getByTestId('positioner');\n      const anchor = screen.getByTestId('anchor');\n\n      const anchorPosition = anchor.getBoundingClientRect();\n\n      await flushMicrotasks();\n\n      expect(positioner.style.getPropertyValue('transform')).toBe(\n        `translate(${anchorPosition.left}px, ${anchorPosition.bottom}px)`,\n      );\n    });\n\n    it('should be placed at the specified position', async () => {\n      const boundingRect = {\n        x: 200,\n        y: 100,\n        top: 100,\n        left: 200,\n        bottom: 100,\n        right: 200,\n        height: 0,\n        width: 0,\n        toJSON: () => {},\n      };\n\n      const virtualElement = { getBoundingClientRect: () => boundingRect };\n\n      await render(\n        <Menu.Root open>\n          <Menu.Portal>\n            <Menu.Positioner\n              side=\"bottom\"\n              align=\"start\"\n              anchor={virtualElement}\n              arrowPadding={0}\n              data-testid=\"positioner\"\n            >\n              <Menu.Popup>\n                <Menu.Item>1</Menu.Item>\n                <Menu.Item>2</Menu.Item>\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      const positioner = screen.getByTestId('positioner');\n      expect(positioner.style.getPropertyValue('transform')).toBe(`translate(200px, 100px)`);\n    });\n\n    it('should accept a non-memoized function as an anchor', async () => {\n      function TestComponent() {\n        return (\n          <div style={{ margin: '50px' }}>\n            <Menu.Root open>\n              <Menu.Portal>\n                <Menu.Positioner\n                  side=\"bottom\"\n                  align=\"start\"\n                  anchor={() => null}\n                  arrowPadding={0}\n                  data-testid=\"positioner\"\n                >\n                  <Menu.Popup>\n                    <Menu.Item>1</Menu.Item>\n                    <Menu.Item>2</Menu.Item>\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            </Menu.Root>\n          </div>\n        );\n      }\n\n      await render(<TestComponent />);\n    });\n\n    it('should react to the anchor changing from a ref to undefined and back', async () => {\n      function TestComponent() {\n        const anchorRef = React.useRef<HTMLDivElement | null>(null);\n        const [currentAnchor, setCurrentAnchor] = React.useState<\n          React.RefObject<HTMLDivElement | null> | undefined\n        >(anchorRef);\n\n        return (\n          <div style={{ margin: '50px' }}>\n            <button type=\"button\" onClick={() => setCurrentAnchor(undefined)}>\n              undefined\n            </button>\n            <button type=\"button\" onClick={() => setCurrentAnchor(anchorRef)}>\n              ref\n            </button>\n            <Menu.Root open>\n              <Menu.Trigger>trigger</Menu.Trigger>\n              <Menu.Portal>\n                <Menu.Positioner\n                  side=\"bottom\"\n                  align=\"start\"\n                  anchor={currentAnchor}\n                  arrowPadding={0}\n                  data-testid=\"positioner\"\n                >\n                  <Menu.Popup>\n                    <Menu.Item>1</Menu.Item>\n                    <Menu.Item>2</Menu.Item>\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            </Menu.Root>\n            <div\n              data-testid=\"anchor\"\n              style={{ marginTop: '100px', width: 10, height: 10 }}\n              ref={anchorRef}\n            />\n          </div>\n        );\n      }\n\n      await render(<TestComponent />);\n\n      const positioner = screen.getByTestId('positioner');\n      const anchorElement = screen.getByTestId('anchor');\n\n      const setUndefinedButton = screen.getByRole('button', { name: 'undefined' });\n      const setRefButton = screen.getByRole('button', { name: 'ref' });\n      const trigger = screen.getByRole('button', { name: 'trigger' });\n\n      let anchorRect = anchorElement.getBoundingClientRect();\n      await flushMicrotasks();\n      expect(positioner.style.getPropertyValue('transform')).toBe(\n        `translate(${anchorRect.left}px, ${anchorRect.bottom}px)`,\n      );\n\n      await userEvent.click(setUndefinedButton);\n      await flushMicrotasks();\n\n      const triggerRect = trigger.getBoundingClientRect();\n      expect(positioner.style.getPropertyValue('transform')).toBe(\n        `translate(${Math.floor(triggerRect.left)}px, ${triggerRect.bottom}px)`,\n      );\n\n      await userEvent.click(setRefButton);\n      await flushMicrotasks();\n\n      anchorRect = anchorElement.getBoundingClientRect();\n      expect(positioner.style.getPropertyValue('transform')).toBe(\n        `translate(${anchorRect.left}px, ${anchorRect.bottom}px)`,\n      );\n    });\n  });\n\n  describe.skipIf(isJSDOM)('prop: keepMounted', () => {\n    afterEach(async () => {\n      const { cleanup } = await import('vitest-browser-react');\n      await cleanup();\n    });\n\n    it('when keepMounted=true, should keep the content mounted when closed', async () => {\n      const { userEvent: user } = await import('vitest/browser');\n      const { render: vbrRender } = await import('vitest-browser-react');\n\n      await vbrRender(\n        <Menu.Root modal={false}>\n          <Menu.Trigger>Toggle</Menu.Trigger>\n          <Menu.Portal keepMounted>\n            <Menu.Positioner>\n              <Menu.Popup>\n                <Menu.Item>1</Menu.Item>\n                <Menu.Item>2</Menu.Item>\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      const trigger = screen.getByRole('button', { name: 'Toggle' });\n\n      expect(screen.queryByRole('menu', { hidden: true })).not.toBe(null);\n      expect(screen.queryByRole('menu', { hidden: true })).toBeInaccessible();\n\n      await user.click(trigger, { delay: 20 });\n      await waitFor(() => {\n        expect(screen.queryByRole('menu', { hidden: false })).not.toBe(null);\n      });\n      expect(screen.queryByRole('menu', { hidden: false })).not.toBeInaccessible();\n\n      await user.click(trigger, { delay: 20 });\n      await waitFor(() => {\n        expect(screen.queryByRole('menu', { hidden: true })).not.toBe(null);\n      });\n      await waitFor(() => {\n        expect(screen.queryByRole('menu', { hidden: true })).toBeInaccessible();\n      });\n    });\n\n    it('when keepMounted=false, should unmount the content when closed', async () => {\n      const { userEvent: user } = await import('vitest/browser');\n      const { render: vbrRender } = await import('vitest-browser-react');\n\n      await vbrRender(\n        <Menu.Root modal={false}>\n          <Menu.Trigger>Toggle</Menu.Trigger>\n          <Menu.Portal keepMounted={false}>\n            <Menu.Positioner>\n              <Menu.Popup>\n                <Menu.Item>1</Menu.Item>\n                <Menu.Item>2</Menu.Item>\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      const trigger = screen.getByRole('button', { name: 'Toggle' });\n\n      expect(screen.queryByRole('menu', { hidden: true })).toBe(null);\n\n      await user.click(trigger, { delay: 20 });\n      await flushMicrotasks();\n      await waitFor(() => {\n        expect(screen.queryByRole('menu', { hidden: false })).not.toBe(null);\n      });\n      expect(screen.queryByRole('menu', { hidden: false })).not.toBeInaccessible();\n\n      await user.click(trigger, { delay: 20 });\n      await waitFor(() => {\n        expect(screen.queryByRole('menu', { hidden: true })).toBe(null);\n      });\n    });\n  });\n\n  const baselineX = 10;\n  const baselineY = 36;\n  const popupWidth = 52;\n  const popupHeight = 24;\n  const anchorWidth = 72;\n  const anchorHeight = 36;\n  const triggerStyle = { width: anchorWidth, height: anchorHeight };\n  const popupStyle = { width: popupWidth, height: popupHeight };\n\n  describe.skipIf(isJSDOM)('prop: sideOffset', () => {\n    it('offsets the side when a number is specified', async () => {\n      const sideOffset = 7;\n      await render(\n        <Menu.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Menu.Portal>\n            <Menu.Positioner data-testid=\"positioner\" sideOffset={sideOffset}>\n              <Menu.Popup style={popupStyle}>Popup</Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      expect(screen.getByTestId('positioner').style.transform).toBe(\n        `translate(${baselineX}px, ${baselineY + sideOffset}px)`,\n      );\n    });\n\n    it('offsets the side when a function is specified', async () => {\n      await render(\n        <Menu.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Menu.Portal>\n            <Menu.Positioner\n              data-testid=\"positioner\"\n              sideOffset={(data) => data.positioner.width + data.anchor.width}\n            >\n              <Menu.Popup style={popupStyle}>Popup</Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      expect(screen.getByTestId('positioner').style.transform).toBe(\n        `translate(${baselineX}px, ${baselineY + popupWidth + anchorWidth}px)`,\n      );\n    });\n\n    it('can read the latest side inside sideOffset', async () => {\n      let side = 'none';\n      await render(\n        <Menu.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Menu.Portal>\n            <Menu.Positioner\n              side=\"left\"\n              data-testid=\"positioner\"\n              sideOffset={(data) => {\n                side = data.side;\n                return 0;\n              }}\n            >\n              <Menu.Popup style={popupStyle}>Popup</Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      // correctly flips the side in the browser\n      expect(side).toBe('right');\n    });\n\n    it('can read the latest align inside sideOffset', async () => {\n      let align = 'none';\n      await render(\n        <Menu.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Menu.Portal>\n            <Menu.Positioner\n              side=\"right\"\n              align=\"start\"\n              data-testid=\"positioner\"\n              sideOffset={(data) => {\n                align = data.align;\n                return 0;\n              }}\n            >\n              <Menu.Popup style={popupStyle}>Popup</Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      // correctly flips the align in the browser\n      expect(align).toBe('end');\n    });\n\n    it('reads logical side inside sideOffset', async () => {\n      let side = 'none';\n      await render(\n        <Menu.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Menu.Portal>\n            <Menu.Positioner\n              side=\"inline-start\"\n              data-testid=\"positioner\"\n              sideOffset={(data) => {\n                side = data.side;\n                return 0;\n              }}\n            >\n              <Menu.Popup style={popupStyle}>Popup</Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      // correctly flips the side in the browser\n      expect(side).toBe('inline-end');\n    });\n  });\n\n  describe.skipIf(isJSDOM)('prop: alignOffset', () => {\n    it('offsets the align when a number is specified', async () => {\n      const alignOffset = 7;\n      await render(\n        <Menu.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Menu.Portal>\n            <Menu.Positioner data-testid=\"positioner\" alignOffset={alignOffset}>\n              <Menu.Popup style={popupStyle}>Popup</Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      expect(screen.getByTestId('positioner').style.transform).toBe(\n        `translate(${baselineX + alignOffset}px, ${baselineY}px)`,\n      );\n    });\n\n    it('offsets the align when a function is specified', async () => {\n      await render(\n        <Menu.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Menu.Portal>\n            <Menu.Positioner data-testid=\"positioner\" alignOffset={(data) => data.positioner.width}>\n              <Menu.Popup style={popupStyle}>Popup</Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      expect(screen.getByTestId('positioner').style.transform).toBe(\n        `translate(${baselineX + popupWidth}px, ${baselineY}px)`,\n      );\n    });\n\n    it('can read the latest side inside alignOffset', async () => {\n      let side = 'none';\n      await render(\n        <Menu.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Menu.Portal>\n            <Menu.Positioner\n              side=\"left\"\n              data-testid=\"positioner\"\n              alignOffset={(data) => {\n                side = data.side;\n                return 0;\n              }}\n            >\n              <Menu.Popup style={popupStyle}>Popup</Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      // correctly flips the side in the browser\n      expect(side).toBe('right');\n    });\n\n    it('can read the latest align inside alignOffset', async () => {\n      let align = 'none';\n      await render(\n        <Menu.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Menu.Portal>\n            <Menu.Positioner\n              side=\"right\"\n              align=\"start\"\n              data-testid=\"positioner\"\n              alignOffset={(data) => {\n                align = data.align;\n                return 0;\n              }}\n            >\n              <Menu.Popup style={popupStyle}>Popup</Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      // correctly flips the align in the browser\n      expect(align).toBe('end');\n    });\n\n    it('reads logical side inside alignOffset', async () => {\n      let side = 'none';\n      await render(\n        <Menu.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Menu.Portal>\n            <Menu.Positioner\n              side=\"inline-start\"\n              data-testid=\"positioner\"\n              alignOffset={(data) => {\n                side = data.side;\n                return 0;\n              }}\n            >\n              <Menu.Popup style={popupStyle}>Popup</Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      // correctly flips the side in the browser\n      expect(side).toBe('inline-end');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/menu/positioner/MenuPositioner.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { inertValue } from '@base-ui/utils/inertValue';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useTimeout } from '@base-ui/utils/useTimeout';\nimport { FloatingNode } from '../../floating-ui-react';\nimport { MenuPositionerContext } from './MenuPositionerContext';\nimport { useMenuRootContext } from '../root/MenuRootContext';\nimport type { MenuRoot } from '../root/MenuRoot';\nimport {\n  useAnchorPositioning,\n  type Align,\n  type Side,\n  type UseAnchorPositioningSharedParameters,\n} from '../../utils/useAnchorPositioning';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { popupStateMapping } from '../../utils/popupStateMapping';\nimport { CompositeList } from '../../composite/list/CompositeList';\nimport { InternalBackdrop } from '../../utils/InternalBackdrop';\nimport { useMenuPortalContext } from '../portal/MenuPortalContext';\nimport { DROPDOWN_COLLISION_AVOIDANCE, POPUP_COLLISION_AVOIDANCE } from '../../utils/constants';\nimport { getDisabledMountTransitionStyles } from '../../utils/getDisabledMountTransitionStyles';\nimport { useContextMenuRootContext } from '../../context-menu/root/ContextMenuRootContext';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport { MenuOpenEventDetails } from '../utils/types';\nimport { adaptiveOrigin } from '../../utils/adaptiveOriginMiddleware';\nimport { useAnimationsFinished } from '../../utils/useAnimationsFinished';\n\n/**\n * Positions the menu popup against the trigger.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)\n */\nexport const MenuPositioner = React.forwardRef(function MenuPositioner(\n  componentProps: MenuPositioner.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const {\n    anchor: anchorProp,\n    positionMethod: positionMethodProp = 'absolute',\n    className,\n    render,\n    side,\n    align: alignProp,\n    sideOffset: sideOffsetProp = 0,\n    alignOffset: alignOffsetProp = 0,\n    collisionBoundary = 'clipping-ancestors',\n    collisionPadding = 5,\n    arrowPadding = 5,\n    sticky = false,\n    disableAnchorTracking = false,\n    collisionAvoidance: collisionAvoidanceProp = DROPDOWN_COLLISION_AVOIDANCE,\n    ...elementProps\n  } = componentProps;\n\n  const { store } = useMenuRootContext();\n\n  const keepMounted = useMenuPortalContext();\n  const contextMenuContext = useContextMenuRootContext(true);\n\n  const parent = store.useState('parent');\n  const floatingRootContext = store.useState('floatingRootContext');\n  const floatingTreeRoot = store.useState('floatingTreeRoot');\n  const mounted = store.useState('mounted');\n  const open = store.useState('open');\n  const modal = store.useState('modal');\n  const triggerElement = store.useState('activeTriggerElement');\n  const transitionStatus = store.useState('transitionStatus');\n  const positionerElement = store.useState('positionerElement');\n  const instantType = store.useState('instantType');\n  const hasViewport = store.useState('hasViewport');\n  const lastOpenChangeReason = store.useState('lastOpenChangeReason');\n  const floatingNodeId = store.useState('floatingNodeId');\n  const floatingParentNodeId = store.useState('floatingParentNodeId');\n  const domReference = floatingRootContext.useState('domReferenceElement');\n\n  const previousTriggerRef = React.useRef<Element | null>(null);\n  const runOnceAnimationsFinish = useAnimationsFinished(positionerElement, false, false);\n\n  let anchor = anchorProp;\n  let sideOffset = sideOffsetProp;\n  let alignOffset = alignOffsetProp;\n  let align = alignProp;\n  let collisionAvoidance = collisionAvoidanceProp;\n  if (parent.type === 'context-menu') {\n    anchor = anchorProp ?? parent.context?.anchor;\n    align = align ?? 'start';\n    if (!side && align !== 'center') {\n      alignOffset = componentProps.alignOffset ?? 2;\n      sideOffset = componentProps.sideOffset ?? -5;\n    }\n  }\n\n  let computedSide = side;\n  let computedAlign = align;\n  if (parent.type === 'menu') {\n    computedSide = computedSide ?? 'inline-end';\n    computedAlign = computedAlign ?? 'start';\n    collisionAvoidance = componentProps.collisionAvoidance ?? POPUP_COLLISION_AVOIDANCE;\n  } else if (parent.type === 'menubar') {\n    computedSide = computedSide ?? 'bottom';\n    computedAlign = computedAlign ?? 'start';\n  }\n\n  const contextMenu = parent.type === 'context-menu';\n\n  const positioner = useAnchorPositioning({\n    anchor,\n    floatingRootContext,\n    positionMethod: contextMenuContext ? 'fixed' : positionMethodProp,\n    mounted,\n    side: computedSide,\n    sideOffset,\n    align: computedAlign,\n    alignOffset,\n    arrowPadding: contextMenu ? 0 : arrowPadding,\n    collisionBoundary,\n    collisionPadding,\n    sticky,\n    nodeId: floatingNodeId,\n    keepMounted,\n    disableAnchorTracking,\n    collisionAvoidance,\n    shiftCrossAxis:\n      contextMenu && !('side' in collisionAvoidance && collisionAvoidance.side === 'flip'),\n    externalTree: floatingTreeRoot,\n    adaptiveOrigin: hasViewport ? adaptiveOrigin : undefined,\n  });\n\n  const positionerProps = React.useMemo(() => {\n    const hiddenStyles: React.CSSProperties = {};\n\n    if (!open) {\n      hiddenStyles.pointerEvents = 'none';\n    }\n\n    return {\n      role: 'presentation',\n      hidden: !mounted,\n      style: {\n        ...positioner.positionerStyles,\n        ...hiddenStyles,\n      },\n    };\n  }, [open, mounted, positioner.positionerStyles]);\n\n  React.useEffect(() => {\n    function onMenuOpenChange(details: MenuOpenEventDetails) {\n      if (details.open) {\n        if (details.parentNodeId === floatingNodeId) {\n          store.set('hoverEnabled', false);\n        }\n        if (\n          details.nodeId !== floatingNodeId &&\n          details.parentNodeId === store.select('floatingParentNodeId')\n        ) {\n          store.setOpen(false, createChangeEventDetails(REASONS.siblingOpen));\n        }\n      }\n    }\n\n    floatingTreeRoot.events.on('menuopenchange', onMenuOpenChange);\n\n    return () => {\n      floatingTreeRoot.events.off('menuopenchange', onMenuOpenChange);\n    };\n  }, [store, floatingTreeRoot.events, floatingNodeId]);\n\n  React.useEffect(() => {\n    if (store.select('floatingParentNodeId') == null) {\n      return undefined;\n    }\n\n    function onParentClose(details: MenuOpenEventDetails) {\n      if (details.open || details.nodeId !== store.select('floatingParentNodeId')) {\n        return;\n      }\n\n      const reason: MenuRoot.ChangeEventReason = details.reason ?? REASONS.siblingOpen;\n      store.setOpen(false, createChangeEventDetails(reason));\n    }\n\n    floatingTreeRoot.events.on('menuopenchange', onParentClose);\n\n    return () => {\n      floatingTreeRoot.events.off('menuopenchange', onParentClose);\n    };\n  }, [floatingTreeRoot.events, store]);\n\n  const closeTimeout = useTimeout();\n\n  // Clear pending close timeout when the menu closes.\n  React.useEffect(() => {\n    if (!open) {\n      closeTimeout.clear();\n    }\n  }, [open, closeTimeout]);\n\n  // Close unrelated child submenus when hovering a different item in the parent menu.\n  React.useEffect(() => {\n    function onItemHover(event: { nodeId: string | undefined; target: Element | null }) {\n      // If an item within our parent menu is hovered, and this menu's trigger is not that item,\n      // close this submenu. This ensures hovering a different item in the parent closes other branches.\n      if (!open || event.nodeId !== store.select('floatingParentNodeId')) {\n        return;\n      }\n\n      if (event.target && triggerElement && triggerElement !== event.target) {\n        const delay = store.select('closeDelay');\n        if (delay > 0) {\n          if (!closeTimeout.isStarted()) {\n            closeTimeout.start(delay, () => {\n              store.setOpen(false, createChangeEventDetails(REASONS.siblingOpen));\n            });\n          }\n        } else {\n          store.setOpen(false, createChangeEventDetails(REASONS.siblingOpen));\n        }\n      } else {\n        // User re-hovered the submenu trigger, cancel pending close.\n        closeTimeout.clear();\n      }\n    }\n\n    floatingTreeRoot.events.on('itemhover', onItemHover);\n    return () => {\n      floatingTreeRoot.events.off('itemhover', onItemHover);\n    };\n  }, [floatingTreeRoot.events, open, triggerElement, store, closeTimeout]);\n\n  React.useEffect(() => {\n    const eventDetails: MenuOpenEventDetails = {\n      open,\n      nodeId: floatingNodeId,\n      parentNodeId: floatingParentNodeId,\n      reason: store.select('lastOpenChangeReason'),\n    };\n\n    floatingTreeRoot.events.emit('menuopenchange', eventDetails);\n  }, [floatingTreeRoot.events, open, store, floatingNodeId, floatingParentNodeId]);\n\n  // Keep positioner transition behavior aligned with Popover when switching detached triggers.\n  useIsoLayoutEffect(() => {\n    const currentTrigger = domReference;\n    const previousTrigger = previousTriggerRef.current;\n\n    if (currentTrigger) {\n      previousTriggerRef.current = currentTrigger;\n    }\n\n    if (previousTrigger && currentTrigger && currentTrigger !== previousTrigger) {\n      store.set('instantType', undefined);\n\n      const abortController = new AbortController();\n      runOnceAnimationsFinish(() => {\n        store.set('instantType', 'trigger-change');\n      }, abortController.signal);\n\n      return () => {\n        abortController.abort();\n      };\n    }\n\n    return undefined;\n  }, [domReference, runOnceAnimationsFinish, store]);\n\n  const state: MenuPositionerState = {\n    open,\n    side: positioner.side,\n    align: positioner.align,\n    anchorHidden: positioner.anchorHidden,\n    nested: parent.type === 'menu',\n    instant: instantType,\n  };\n\n  const contextValue: MenuPositionerContext = React.useMemo(\n    () => ({\n      side: positioner.side,\n      align: positioner.align,\n      arrowRef: positioner.arrowRef,\n      arrowUncentered: positioner.arrowUncentered,\n      arrowStyles: positioner.arrowStyles,\n      nodeId: positioner.context.nodeId,\n    }),\n    [\n      positioner.side,\n      positioner.align,\n      positioner.arrowRef,\n      positioner.arrowUncentered,\n      positioner.arrowStyles,\n      positioner.context.nodeId,\n    ],\n  );\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    stateAttributesMapping: popupStateMapping,\n    ref: [forwardedRef, store.useStateSetter('positionerElement')],\n    props: [positionerProps, getDisabledMountTransitionStyles(transitionStatus), elementProps],\n  });\n\n  const shouldRenderBackdrop =\n    mounted &&\n    parent.type !== 'menu' &&\n    ((parent.type !== 'menubar' && modal && lastOpenChangeReason !== REASONS.triggerHover) ||\n      (parent.type === 'menubar' && parent.context.modal));\n\n  // cuts a hole in the backdrop to allow pointer interaction with the menubar or dropdown menu trigger element\n  let backdropCutout: HTMLElement | null = null;\n  if (parent.type === 'menubar') {\n    backdropCutout = parent.context.contentElement;\n  } else if (parent.type === undefined) {\n    backdropCutout = triggerElement as HTMLElement | null;\n  }\n\n  return (\n    <MenuPositionerContext.Provider value={contextValue}>\n      {shouldRenderBackdrop && (\n        <InternalBackdrop\n          ref={\n            parent.type === 'context-menu' || parent.type === 'nested-context-menu'\n              ? parent.context.internalBackdropRef\n              : null\n          }\n          inert={inertValue(!open)}\n          cutout={backdropCutout}\n        />\n      )}\n      <FloatingNode id={floatingNodeId}>\n        <CompositeList\n          elementsRef={store.context.itemDomElements}\n          labelsRef={store.context.itemLabels}\n        >\n          {element}\n        </CompositeList>\n      </FloatingNode>\n    </MenuPositionerContext.Provider>\n  );\n});\n\nexport interface MenuPositionerState {\n  /**\n   * Whether the menu is currently open.\n   */\n  open: boolean;\n  /**\n   * The side of the anchor the component is placed on.\n   */\n  side: Side;\n  /**\n   * The alignment of the component relative to the anchor.\n   */\n  align: Align;\n  /**\n   * Whether the anchor element is hidden.\n   */\n  anchorHidden: boolean;\n  /**\n   * Whether the component is nested.\n   */\n  nested: boolean;\n  /**\n   * Whether CSS transitions should be disabled.\n   */\n  instant: string | undefined;\n}\n\nexport interface MenuPositionerProps\n  extends UseAnchorPositioningSharedParameters, BaseUIComponentProps<'div', MenuPositionerState> {}\n\nexport namespace MenuPositioner {\n  export type State = MenuPositionerState;\n  export type Props = MenuPositionerProps;\n}\n"
  },
  {
    "path": "packages/react/src/menu/positioner/MenuPositionerContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport type { Side, Align } from '../../utils/useAnchorPositioning';\n\nexport interface MenuPositionerContext {\n  /**\n   * The side of the anchor element the popup is positioned relative to.\n   */\n  side: Side;\n  /**\n   * How to align the popup relative to the specified side.\n   */\n  align: Align;\n  arrowRef: React.RefObject<Element | null>;\n  arrowUncentered: boolean;\n  arrowStyles: React.CSSProperties;\n  nodeId: string | undefined;\n}\n\nexport const MenuPositionerContext = React.createContext<MenuPositionerContext | undefined>(\n  undefined,\n);\n\nexport function useMenuPositionerContext(optional?: false): MenuPositionerContext;\nexport function useMenuPositionerContext(optional: true): MenuPositionerContext | undefined;\nexport function useMenuPositionerContext(optional?: boolean) {\n  const context = React.useContext(MenuPositionerContext);\n  if (context === undefined && !optional) {\n    throw new Error(\n      'Base UI: MenuPositionerContext is missing. MenuPositioner parts must be placed within <Menu.Positioner>.',\n    );\n  }\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/menu/positioner/MenuPositionerCssVars.ts",
    "content": "export enum MenuPositionerCssVars {\n  /**\n   * The available width between the trigger and the edge of the viewport.\n   * @type {number}\n   */\n  availableWidth = '--available-width',\n  /**\n   * The available height between the trigger and the edge of the viewport.\n   * @type {number}\n   */\n  availableHeight = '--available-height',\n  /**\n   * The anchor's width.\n   * @type {number}\n   */\n  anchorWidth = '--anchor-width',\n  /**\n   * The anchor's height.\n   * @type {number}\n   */\n  anchorHeight = '--anchor-height',\n  /**\n   * The coordinates that this element is anchored to. Used for animations and transitions.\n   * @type {string}\n   */\n  transformOrigin = '--transform-origin',\n}\n"
  },
  {
    "path": "packages/react/src/menu/positioner/MenuPositionerDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum MenuPositionerDataAttributes {\n  /**\n   * Present when the menu popup is open.\n   */\n  open = CommonPopupDataAttributes.open,\n  /**\n   * Present when the menu popup is closed.\n   */\n  closed = CommonPopupDataAttributes.closed,\n  /**\n   * Present when the anchor is hidden.\n   */\n  anchorHidden = CommonPopupDataAttributes.anchorHidden,\n  /**\n   * Indicates which side the popup is positioned relative to the trigger.\n   * @type {'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'}\n   */\n  side = CommonPopupDataAttributes.side,\n  /**\n   * Indicates how the popup is aligned relative to specified side.\n   * @type {'start' | 'center' | 'end'}\n   */\n  align = CommonPopupDataAttributes.align,\n}\n"
  },
  {
    "path": "packages/react/src/menu/radio-group/MenuRadioGroup.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { screen } from '@mui/internal-test-utils';\nimport { Menu } from '@base-ui/react/menu';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Menu.RadioGroup />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Menu.RadioGroup />, () => ({\n    render,\n    refInstanceof: window.HTMLDivElement,\n  }));\n\n  it('renders a div with the `group` role', async () => {\n    await render(<Menu.RadioGroup />);\n    expect(screen.getByRole('group')).toBeVisible();\n  });\n});\n"
  },
  {
    "path": "packages/react/src/menu/radio-group/MenuRadioGroup.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useControlled } from '@base-ui/utils/useControlled';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { MenuRadioGroupContext } from './MenuRadioGroupContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport type { MenuRoot } from '../root/MenuRoot';\n\n/**\n * Groups related radio items.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)\n */\nexport const MenuRadioGroup = React.memo(\n  React.forwardRef(function MenuRadioGroup(\n    componentProps: MenuRadioGroup.Props,\n    forwardedRef: React.ForwardedRef<HTMLDivElement>,\n  ) {\n    const {\n      render,\n      className,\n      value: valueProp,\n      defaultValue,\n      onValueChange: onValueChangeProp,\n      disabled = false,\n      ...elementProps\n    } = componentProps;\n\n    const [value, setValueUnwrapped] = useControlled({\n      controlled: valueProp,\n      default: defaultValue,\n      name: 'MenuRadioGroup',\n    });\n\n    const onValueChange = useStableCallback(onValueChangeProp);\n\n    const setValue = useStableCallback(\n      (newValue: any, eventDetails: MenuRadioGroup.ChangeEventDetails) => {\n        onValueChange?.(newValue, eventDetails);\n\n        if (eventDetails.isCanceled) {\n          return;\n        }\n\n        setValueUnwrapped(newValue);\n      },\n    );\n\n    const state: MenuRadioGroupState = { disabled };\n\n    const element = useRenderElement('div', componentProps, {\n      state,\n      ref: forwardedRef,\n      props: {\n        role: 'group',\n        'aria-disabled': disabled || undefined,\n        ...elementProps,\n      },\n    });\n\n    const context: MenuRadioGroupContext = React.useMemo(\n      () => ({\n        value,\n        setValue,\n        disabled,\n      }),\n      [value, setValue, disabled],\n    );\n\n    return (\n      <MenuRadioGroupContext.Provider value={context}>{element}</MenuRadioGroupContext.Provider>\n    );\n  }),\n);\n\nexport interface MenuRadioGroupProps extends BaseUIComponentProps<'div', MenuRadioGroupState> {\n  /**\n   * The content of the component.\n   */\n  children?: React.ReactNode;\n  /**\n   * The controlled value of the radio item that should be currently selected.\n   *\n   * To render an uncontrolled radio group, use the `defaultValue` prop instead.\n   */\n  value?: any;\n  /**\n   * The uncontrolled value of the radio item that should be initially selected.\n   *\n   * To render a controlled radio group, use the `value` prop instead.\n   */\n  defaultValue?: any;\n  /**\n   * Function called when the selected value changes.\n   */\n  onValueChange?:\n    | ((value: any, eventDetails: MenuRadioGroup.ChangeEventDetails) => void)\n    | undefined;\n  /**\n   * Whether the component should ignore user interaction.\n   *\n   * @default false\n   */\n  disabled?: boolean | undefined;\n}\n\nexport interface MenuRadioGroupState {\n  /**\n   * Whether the component is disabled.\n   */\n  disabled: boolean;\n}\n\nexport type MenuRadioGroupChangeEventReason = MenuRoot.ChangeEventReason;\nexport type MenuRadioGroupChangeEventDetails = MenuRoot.ChangeEventDetails;\n\nexport namespace MenuRadioGroup {\n  export type Props = MenuRadioGroupProps;\n  export type State = MenuRadioGroupState;\n  export type ChangeEventReason = MenuRadioGroupChangeEventReason;\n  export type ChangeEventDetails = MenuRadioGroupChangeEventDetails;\n}\n"
  },
  {
    "path": "packages/react/src/menu/radio-group/MenuRadioGroupContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport type { MenuRoot } from '../root/MenuRoot';\n\nexport interface MenuRadioGroupContext {\n  value: any;\n  setValue: (newValue: any, eventDetails: MenuRoot.ChangeEventDetails) => void;\n  disabled: boolean;\n}\n\nexport const MenuRadioGroupContext = React.createContext<MenuRadioGroupContext | undefined>(\n  undefined,\n);\n\nexport function useMenuRadioGroupContext() {\n  const context = React.useContext(MenuRadioGroupContext);\n  if (context === undefined) {\n    throw new Error(\n      'Base UI: MenuRadioGroupContext is missing. MenuRadioGroup parts must be placed within <Menu.RadioGroup>.',\n    );\n  }\n\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/menu/radio-item/MenuRadioItem.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { fireEvent, act, waitFor, screen } from '@mui/internal-test-utils';\nimport { Menu } from '@base-ui/react/menu';\nimport { describeConformance, createRenderer, isJSDOM } from '#test-utils';\nimport { MenuRadioGroupContext } from '../radio-group/MenuRadioGroupContext';\n\nconst testRadioGroupContext = {\n  value: '0',\n  setValue: () => {},\n  disabled: false,\n};\n\ndescribe('<Menu.RadioItem />', () => {\n  const { render, clock } = createRenderer({\n    clockOptions: {\n      shouldAdvanceTime: true,\n    },\n  });\n\n  clock.withFakeTimers();\n\n  describeConformance(<Menu.RadioItem value=\"0\" />, () => ({\n    render: (node) => {\n      return render(\n        <Menu.Root open>\n          <MenuRadioGroupContext.Provider value={testRadioGroupContext}>\n            {node}\n          </MenuRadioGroupContext.Provider>\n        </Menu.Root>,\n      );\n    },\n    refInstanceof: window.HTMLDivElement,\n  }));\n\n  it('perf: does not rerender menu items unnecessarily', async ({ skip }) => {\n    if (isJSDOM) {\n      skip();\n    }\n\n    const renderItem1Spy = vi.fn();\n    const renderItem2Spy = vi.fn();\n    const renderItem3Spy = vi.fn();\n    const renderItem4Spy = vi.fn();\n\n    const LoggingRoot = React.forwardRef(function LoggingRoot(\n      props: any & { renderSpy: () => void },\n      ref: React.ForwardedRef<HTMLLIElement>,\n    ) {\n      const { renderSpy, state, ...other } = props;\n      renderSpy();\n      return <li {...other} ref={ref} />;\n    });\n\n    await render(\n      <Menu.Root open>\n        <Menu.Portal>\n          <Menu.Positioner>\n            <Menu.Popup>\n              <Menu.RadioGroup>\n                <Menu.RadioItem\n                  value={1}\n                  render={<LoggingRoot renderSpy={renderItem1Spy} />}\n                  id=\"item-1\"\n                >\n                  1\n                </Menu.RadioItem>\n                <Menu.RadioItem\n                  value={2}\n                  render={<LoggingRoot renderSpy={renderItem2Spy} />}\n                  id=\"item-2\"\n                >\n                  2\n                </Menu.RadioItem>\n                <Menu.RadioItem\n                  value={3}\n                  render={<LoggingRoot renderSpy={renderItem3Spy} />}\n                  id=\"item-3\"\n                >\n                  3\n                </Menu.RadioItem>\n                <Menu.RadioItem\n                  value={4}\n                  render={<LoggingRoot renderSpy={renderItem4Spy} />}\n                  id=\"item-4\"\n                >\n                  4\n                </Menu.RadioItem>\n              </Menu.RadioGroup>\n            </Menu.Popup>\n          </Menu.Positioner>\n        </Menu.Portal>\n      </Menu.Root>,\n    );\n\n    const menuItems = screen.getAllByRole('menuitemradio');\n    await act(async () => {\n      menuItems[0].focus();\n    });\n\n    renderItem1Spy.mockClear();\n    renderItem2Spy.mockClear();\n    renderItem3Spy.mockClear();\n    renderItem4Spy.mockClear();\n\n    expect(renderItem1Spy.mock.calls.length).toBe(0);\n\n    fireEvent.keyDown(menuItems[0], { key: 'ArrowDown' }); // highlights '2'\n\n    // React renders twice in strict mode, so we expect twice the number of spy calls\n\n    await waitFor(\n      () => {\n        expect(renderItem1Spy.mock.calls.length).toBe(2); // '1' rerenders as it loses highlight\n      },\n      { timeout: 1000 },\n    );\n\n    await waitFor(\n      () => {\n        expect(renderItem2Spy.mock.calls.length).toBe(2); // '2' rerenders as it receives highlight\n      },\n      { timeout: 1000 },\n    );\n\n    // neither the highlighted nor the selected state of these options changed,\n    // so they don't need to rerender:\n    expect(renderItem3Spy.mock.calls.length).toBe(0);\n    expect(renderItem4Spy.mock.calls.length).toBe(0);\n  });\n\n  describe('state management', () => {\n    it('adds the state and ARIA attributes when selected', async () => {\n      const { user } = await render(\n        <Menu.Root>\n          <Menu.Trigger>Open</Menu.Trigger>\n          <Menu.Portal>\n            <Menu.Positioner>\n              <Menu.Popup>\n                <Menu.RadioGroup defaultValue={0}>\n                  <Menu.RadioItem value={1}>Item</Menu.RadioItem>\n                </Menu.RadioGroup>\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      const trigger = screen.getByRole('button', { name: 'Open' });\n      await user.click(trigger);\n\n      const item = screen.getByRole('menuitemradio');\n      await user.click(item);\n\n      expect(item).toHaveAttribute('aria-checked', 'true');\n      expect(item).toHaveAttribute('data-checked', '');\n    });\n\n    ['Space', 'Enter'].forEach((key) => {\n      it(`selects the item when ${key} is pressed`, async () => {\n        const { user } = await render(\n          <Menu.Root>\n            <Menu.Trigger>Open</Menu.Trigger>\n            <Menu.Portal>\n              <Menu.Positioner>\n                <Menu.RadioGroup defaultValue={0}>\n                  <Menu.RadioItem value={1}>Item</Menu.RadioItem>\n                </Menu.RadioGroup>\n              </Menu.Positioner>\n            </Menu.Portal>\n          </Menu.Root>,\n        );\n\n        const trigger = screen.getByRole('button', { name: 'Open' });\n        await act(async () => {\n          trigger.focus();\n        });\n        await user.keyboard('[ArrowDown]');\n        const item = screen.getByRole('menuitemradio');\n\n        await waitFor(() => {\n          expect(item).toHaveFocus();\n        });\n\n        await user.keyboard(`[${key}]`);\n        expect(item).toHaveAttribute('data-checked', '');\n      });\n    });\n\n    it.skipIf(isJSDOM)(\n      'does not select when Space is pressed during an active typeahead session',\n      async () => {\n        const onValueChange = vi.fn();\n        const { user } = await render(\n          <Menu.Root open>\n            <Menu.Portal>\n              <Menu.Positioner>\n                <Menu.Popup>\n                  <Menu.RadioGroup defaultValue={0} onValueChange={onValueChange}>\n                    <Menu.RadioItem value={1}>Item One</Menu.RadioItem>\n                    <Menu.RadioItem value={2}>Item Two</Menu.RadioItem>\n                  </Menu.RadioGroup>\n                </Menu.Popup>\n              </Menu.Positioner>\n            </Menu.Portal>\n          </Menu.Root>,\n        );\n\n        const [itemOne, itemTwo] = screen.getAllByRole('menuitemradio');\n\n        await act(async () => {\n          itemOne.focus();\n        });\n\n        await user.keyboard('Item T');\n\n        await waitFor(() => {\n          expect(itemTwo).toHaveFocus();\n        });\n\n        await user.keyboard('[Space]');\n        await user.keyboard('[Space]');\n\n        expect(onValueChange.mock.calls.length > 0).toBe(false);\n        expect(itemTwo).toHaveAttribute('aria-checked', 'false');\n      },\n    );\n\n    it('calls `onValueChange` when the item is clicked', async () => {\n      const onValueChange = vi.fn();\n      const { user } = await render(\n        <Menu.Root>\n          <Menu.Trigger>Open</Menu.Trigger>\n          <Menu.Portal>\n            <Menu.Positioner>\n              <Menu.Popup>\n                <Menu.RadioGroup defaultValue={0} onValueChange={onValueChange}>\n                  <Menu.RadioItem value={1}>Item</Menu.RadioItem>\n                </Menu.RadioGroup>\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      const trigger = screen.getByRole('button', { name: 'Open' });\n      await user.click(trigger);\n\n      const item = screen.getByRole('menuitemradio');\n      await user.click(item);\n\n      expect(onValueChange.mock.calls.length).toBe(1);\n      expect(onValueChange.mock.lastCall?.[0]).toBe(1);\n    });\n\n    it('keeps the state when closed and reopened', async () => {\n      const { user } = await render(\n        <Menu.Root modal={false}>\n          <Menu.Trigger>Open</Menu.Trigger>\n          <Menu.Portal keepMounted>\n            <Menu.Positioner>\n              <Menu.Popup>\n                <Menu.RadioGroup defaultValue={0}>\n                  <Menu.RadioItem value={1}>Item</Menu.RadioItem>\n                </Menu.RadioGroup>\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      const trigger = screen.getByRole('button', { name: 'Open' });\n      await user.click(trigger);\n\n      const item = screen.getByRole('menuitemradio');\n      await user.click(item);\n\n      await user.click(trigger); // close the menu\n\n      await waitFor(() => {\n        expect(screen.queryByRole('menu')).toBe(null);\n      });\n\n      await user.click(trigger); // reopen the menu\n\n      const itemAfterReopen = await screen.findByRole('menuitemradio');\n      expect(itemAfterReopen).toHaveAttribute('aria-checked', 'true');\n      expect(itemAfterReopen).toHaveAttribute('data-checked', '');\n    });\n  });\n\n  describe('prop: closeOnClick', () => {\n    it('when `closeOnClick=true`, closes the menu when the item is clicked', async () => {\n      const { user } = await render(\n        <Menu.Root>\n          <Menu.Trigger>Open</Menu.Trigger>\n          <Menu.Portal>\n            <Menu.Positioner>\n              <Menu.Popup>\n                <Menu.RadioGroup defaultValue={0}>\n                  <Menu.RadioItem closeOnClick value={1}>\n                    Item\n                  </Menu.RadioItem>\n                </Menu.RadioGroup>\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      const trigger = screen.getByRole('button', { name: 'Open' });\n      await user.click(trigger);\n\n      const item = screen.getByRole('menuitemradio');\n      await user.click(item);\n\n      expect(screen.queryByRole('menu')).toBe(null);\n    });\n\n    it('does not close the menu when the item is clicked by default', async () => {\n      const { user } = await render(\n        <Menu.Root>\n          <Menu.Trigger>Open</Menu.Trigger>\n          <Menu.Portal>\n            <Menu.Positioner>\n              <Menu.Popup>\n                <Menu.RadioGroup defaultValue={0}>\n                  <Menu.RadioItem value={1}>Item</Menu.RadioItem>\n                </Menu.RadioGroup>\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      const trigger = screen.getByRole('button', { name: 'Open' });\n      await user.click(trigger);\n\n      const item = screen.getByRole('menuitemradio');\n      await user.click(item);\n\n      expect(screen.queryByRole('menu')).not.toBe(null);\n    });\n  });\n\n  describe('prop: focusableWhenDisabled', () => {\n    it('can be focused but not interacted with when a radio group is disabled', async () => {\n      const handleClick = vi.fn();\n      const handleKeyDown = vi.fn();\n      const handleKeyUp = vi.fn();\n      const handleValueChange = vi.fn();\n\n      await render(\n        <Menu.Root open>\n          <Menu.Portal>\n            <Menu.Positioner>\n              <Menu.Popup>\n                <Menu.RadioGroup defaultValue={0} disabled onValueChange={handleValueChange}>\n                  <Menu.RadioItem\n                    value=\"one\"\n                    onClick={handleClick}\n                    onKeyDown={handleKeyDown}\n                    onKeyUp={handleKeyUp}\n                  >\n                    one\n                  </Menu.RadioItem>\n                  <Menu.RadioItem\n                    value=\"two\"\n                    onClick={handleClick}\n                    onKeyDown={handleKeyDown}\n                    onKeyUp={handleKeyUp}\n                  >\n                    two\n                  </Menu.RadioItem>\n                </Menu.RadioGroup>\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      const [item1, item2] = screen.getAllByRole('menuitemradio');\n\n      expect(item1).toHaveAttribute('data-disabled');\n      expect(item2).toHaveAttribute('data-disabled');\n\n      await act(() => item1.focus());\n      expect(item1).toHaveFocus();\n\n      fireEvent.keyDown(item1, { key: 'Enter' });\n      expect(handleKeyDown.mock.calls.length).toBe(0);\n      expect(handleClick.mock.calls.length).toBe(0);\n      expect(handleValueChange.mock.calls.length).toBe(0);\n\n      fireEvent.keyUp(item1, { key: 'Space' });\n      expect(handleKeyDown.mock.calls.length).toBe(0);\n      expect(handleClick.mock.calls.length).toBe(0);\n      expect(handleValueChange.mock.calls.length).toBe(0);\n\n      fireEvent.click(item1);\n      expect(handleClick.mock.calls.length).toBe(0);\n      expect(handleValueChange.mock.calls.length).toBe(0);\n\n      fireEvent.keyDown(item1, { key: 'ArrowDown' });\n      expect(handleKeyDown.mock.calls.length).toBe(0);\n      expect(item2).toHaveFocus();\n\n      fireEvent.keyDown(item2, { key: 'Enter' });\n      expect(handleKeyDown.mock.calls.length).toBe(0);\n      expect(handleClick.mock.calls.length).toBe(0);\n      expect(handleValueChange.mock.calls.length).toBe(0);\n\n      fireEvent.keyUp(item2, { key: 'Space' });\n      expect(handleKeyDown.mock.calls.length).toBe(0);\n      expect(handleClick.mock.calls.length).toBe(0);\n      expect(handleValueChange.mock.calls.length).toBe(0);\n\n      fireEvent.click(item2);\n      expect(handleClick.mock.calls.length).toBe(0);\n      expect(handleValueChange.mock.calls.length).toBe(0);\n    });\n  });\n\n  it('can be focused but not interacted with when individual items are disabled', async () => {\n    const handleClick = vi.fn();\n    const handleKeyDown = vi.fn();\n    const handleKeyUp = vi.fn();\n    const handleValueChange = vi.fn();\n\n    await render(\n      <Menu.Root open>\n        <Menu.Portal>\n          <Menu.Positioner>\n            <Menu.Popup>\n              <Menu.RadioGroup defaultValue={0} onValueChange={handleValueChange}>\n                <Menu.RadioItem\n                  value=\"one\"\n                  onClick={handleClick}\n                  onKeyDown={handleKeyDown}\n                  onKeyUp={handleKeyUp}\n                  disabled\n                >\n                  one\n                </Menu.RadioItem>\n                <Menu.RadioItem\n                  value=\"two\"\n                  onClick={handleClick}\n                  onKeyDown={handleKeyDown}\n                  onKeyUp={handleKeyUp}\n                >\n                  two\n                </Menu.RadioItem>\n              </Menu.RadioGroup>\n            </Menu.Popup>\n          </Menu.Positioner>\n        </Menu.Portal>\n      </Menu.Root>,\n    );\n\n    const [item1, item2] = screen.getAllByRole('menuitemradio');\n\n    expect(item1).toHaveAttribute('data-disabled');\n    expect(item2).not.toHaveAttribute('data-disabled');\n\n    await act(() => item1.focus());\n    expect(item1).toHaveFocus();\n\n    fireEvent.keyDown(item1, { key: 'Enter' });\n    expect(handleKeyDown.mock.calls.length).toBe(0);\n    expect(handleClick.mock.calls.length).toBe(0);\n    expect(handleValueChange.mock.calls.length).toBe(0);\n\n    fireEvent.keyUp(item1, { key: 'Space' });\n    expect(handleKeyDown.mock.calls.length).toBe(0);\n    expect(handleClick.mock.calls.length).toBe(0);\n    expect(handleValueChange.mock.calls.length).toBe(0);\n\n    fireEvent.click(item1);\n    expect(handleClick.mock.calls.length).toBe(0);\n    expect(handleValueChange.mock.calls.length).toBe(0);\n\n    fireEvent.keyDown(item1, { key: 'ArrowDown' });\n    expect(handleKeyDown.mock.calls.length).toBe(0);\n    expect(item2).toHaveFocus();\n\n    fireEvent.keyDown(item2, { key: 'Enter' });\n    expect(handleKeyDown.mock.calls.length).toBe(1);\n    expect(handleClick.mock.calls.length).toBe(1);\n    expect(handleValueChange.mock.calls.length).toBe(1);\n    expect(handleValueChange.mock.calls[0][0]).toBe('two');\n\n    fireEvent.keyDown(item2, { key: 'ArrowDown' });\n    await waitFor(() => {\n      expect(item1).toHaveFocus();\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/menu/radio-item/MenuRadioItem.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useMenuRootContext } from '../root/MenuRootContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useBaseUiId } from '../../utils/useBaseUiId';\nimport type { BaseUIComponentProps, NonNativeButtonProps } from '../../utils/types';\nimport { useMenuRadioGroupContext } from '../radio-group/MenuRadioGroupContext';\nimport { MenuRadioItemContext } from './MenuRadioItemContext';\nimport { itemMapping } from '../utils/stateAttributesMapping';\nimport { useCompositeListItem } from '../../composite/list/useCompositeListItem';\nimport { REGULAR_ITEM, useMenuItem } from '../item/useMenuItem';\nimport { useMenuPositionerContext } from '../positioner/MenuPositionerContext';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\n\n/**\n * A menu item that works like a radio button in a given group.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)\n */\nexport const MenuRadioItem = React.forwardRef(function MenuRadioItem(\n  componentProps: MenuRadioItem.Props,\n  forwardedRef: React.ForwardedRef<HTMLElement>,\n) {\n  const {\n    render,\n    className,\n    id: idProp,\n    label,\n    nativeButton = false,\n    disabled: disabledProp = false,\n    closeOnClick = false,\n    value,\n    ...elementProps\n  } = componentProps;\n\n  const listItem = useCompositeListItem({ label });\n  const menuPositionerContext = useMenuPositionerContext(true);\n  const id = useBaseUiId(idProp);\n\n  const { store } = useMenuRootContext();\n  const highlighted = store.useState('isActive', listItem.index);\n  const itemProps = store.useState('itemProps');\n\n  const {\n    value: selectedValue,\n    setValue: setSelectedValue,\n    disabled: groupDisabled,\n  } = useMenuRadioGroupContext();\n\n  const disabled = groupDisabled || disabledProp;\n  const checked = selectedValue === value;\n\n  const { getItemProps, itemRef } = useMenuItem({\n    closeOnClick,\n    disabled,\n    highlighted,\n    id,\n    store,\n    nativeButton,\n    nodeId: menuPositionerContext?.nodeId,\n    itemMetadata: REGULAR_ITEM,\n  });\n\n  const state: MenuRadioItemState = React.useMemo(\n    () => ({\n      disabled,\n      highlighted,\n      checked,\n    }),\n    [disabled, highlighted, checked],\n  );\n\n  const handleClick = useStableCallback((event: React.MouseEvent) => {\n    const details = {\n      ...createChangeEventDetails(REASONS.itemPress, event.nativeEvent),\n      preventUnmountOnClose: () => {},\n    };\n    setSelectedValue(value, details);\n  });\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    stateAttributesMapping: itemMapping,\n    props: [\n      itemProps,\n      {\n        role: 'menuitemradio',\n        'aria-checked': checked,\n        onClick: handleClick,\n      },\n      elementProps,\n      getItemProps,\n    ],\n    ref: [itemRef, forwardedRef, listItem.ref],\n  });\n\n  return <MenuRadioItemContext.Provider value={state}>{element}</MenuRadioItemContext.Provider>;\n});\n\nexport interface MenuRadioItemState {\n  /**\n   * Whether the radio item should ignore user interaction.\n   */\n  disabled: boolean;\n  /**\n   * Whether the radio item is currently highlighted.\n   */\n  highlighted: boolean;\n  /**\n   * Whether the radio item is currently selected.\n   */\n  checked: boolean;\n}\n\nexport interface MenuRadioItemProps\n  extends NonNativeButtonProps, BaseUIComponentProps<'div', MenuRadioItemState> {\n  /**\n   * Value of the radio item.\n   * This is the value that will be set in the MenuRadioGroup when the item is selected.\n   */\n  value: any;\n  /**\n   * The click handler for the menu item.\n   */\n  onClick?: BaseUIComponentProps<'div', MenuRadioItemState>['onClick'] | undefined;\n  /**\n   * Whether the component should ignore user interaction.\n   * @default false\n   */\n  disabled?: boolean | undefined;\n  /**\n   * Overrides the text label to use when the item is matched during keyboard text navigation.\n   */\n  label?: string | undefined;\n  /**\n   * @ignore\n   */\n  id?: string | undefined;\n  /**\n   * Whether to close the menu when the item is clicked.\n   * @default false\n   */\n  closeOnClick?: boolean | undefined;\n}\n\nexport namespace MenuRadioItem {\n  export type State = MenuRadioItemState;\n  export type Props = MenuRadioItemProps;\n}\n"
  },
  {
    "path": "packages/react/src/menu/radio-item/MenuRadioItemContext.ts",
    "content": "'use client';\nimport * as React from 'react';\n\nexport interface MenuRadioItemContext {\n  checked: boolean;\n  highlighted: boolean;\n  disabled: boolean;\n}\n\nexport const MenuRadioItemContext = React.createContext<MenuRadioItemContext | undefined>(\n  undefined,\n);\n\nexport function useMenuRadioItemContext() {\n  const context = React.useContext(MenuRadioItemContext);\n  if (context === undefined) {\n    throw new Error(\n      'Base UI: MenuRadioItemContext is missing. MenuRadioItem parts must be placed within <Menu.RadioItem>.',\n    );\n  }\n\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/menu/radio-item/MenuRadioItemDataAttributes.ts",
    "content": "export enum MenuRadioItemDataAttributes {\n  /**\n   * Present when the menu radio item is selected.\n   */\n  checked = 'data-checked',\n  /**\n   * Present when the menu radio item is not selected.\n   */\n  unchecked = 'data-unchecked',\n  /**\n   * Present when the menu radio item is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the menu radio item is highlighted.\n   */\n  highlighted = 'data-highlighted',\n}\n"
  },
  {
    "path": "packages/react/src/menu/radio-item-indicator/MenuRadioItemIndicator.test.tsx",
    "content": "import { expect } from 'vitest';\nimport * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\nimport { screen, waitFor } from '@mui/internal-test-utils';\n\ndescribe('<Menu.RadioItemIndicator />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Menu.RadioItemIndicator keepMounted />, () => ({\n    refInstanceof: window.HTMLSpanElement,\n    render(node) {\n      return render(\n        <Menu.Root open>\n          <Menu.Portal>\n            <Menu.Positioner>\n              <Menu.Popup>\n                <Menu.RadioGroup>\n                  <Menu.RadioItem value=\"\">{node}</Menu.RadioItem>\n                </Menu.RadioGroup>\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n    },\n  }));\n\n  it('should remove the indicator when there is no exit animation defined', async ({ skip }) => {\n    if (isJSDOM) {\n      skip();\n    }\n\n    function Test() {\n      const [value, setValue] = React.useState('a');\n      return (\n        <div>\n          <button onClick={() => setValue('b')}>Close</button>\n          <Menu.Root open modal={false}>\n            <Menu.Portal>\n              <Menu.Positioner>\n                <Menu.Popup>\n                  <Menu.Popup>\n                    <Menu.RadioGroup value={value}>\n                      <Menu.RadioItem value=\"a\">\n                        <Menu.RadioItemIndicator data-testid=\"indicator\" />\n                      </Menu.RadioItem>\n                      <Menu.RadioItem value=\"b\">\n                        <Menu.RadioItemIndicator keepMounted />\n                      </Menu.RadioItem>\n                    </Menu.RadioGroup>\n                  </Menu.Popup>\n                </Menu.Popup>\n              </Menu.Positioner>\n            </Menu.Portal>\n          </Menu.Root>\n        </div>\n      );\n    }\n\n    const { user } = await render(<Test />);\n\n    expect(screen.queryByTestId('indicator')).not.toBe(null);\n\n    const closeButton = screen.getByText('Close');\n\n    await user.click(closeButton);\n\n    await waitFor(() => {\n      expect(screen.queryByTestId('indicator')).toBe(null);\n    });\n  });\n\n  it('should remove the indicator when the animation finishes', async ({ skip }) => {\n    if (isJSDOM) {\n      skip();\n    }\n\n    globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n    let animationFinished = false;\n    const notifyAnimationFinished = () => {\n      animationFinished = true;\n    };\n\n    function Test() {\n      const style = `\n        @keyframes test-anim {\n          to {\n            opacity: 0;\n          }\n        }\n        .animation-test-indicator[data-ending-style] {\n          animation: test-anim 1ms;\n        }\n      `;\n\n      const [value, setValue] = React.useState('a');\n\n      return (\n        <div>\n          {/* eslint-disable-next-line react/no-danger */}\n          <style dangerouslySetInnerHTML={{ __html: style }} />\n          <button onClick={() => setValue('b')}>Close</button>\n          <Menu.Root open modal={false}>\n            <Menu.Portal>\n              <Menu.Positioner>\n                <Menu.Popup>\n                  <Menu.RadioGroup value={value}>\n                    <Menu.RadioItem value=\"a\">\n                      <Menu.RadioItemIndicator\n                        className=\"animation-test-indicator\"\n                        data-testid=\"indicator\"\n                        keepMounted\n                        onAnimationEnd={notifyAnimationFinished}\n                      />\n                    </Menu.RadioItem>\n                    <Menu.RadioItem value=\"b\">\n                      <Menu.RadioItemIndicator keepMounted />\n                    </Menu.RadioItem>\n                  </Menu.RadioGroup>\n                </Menu.Popup>\n              </Menu.Positioner>\n            </Menu.Portal>\n          </Menu.Root>\n        </div>\n      );\n    }\n\n    const { user } = await render(<Test />);\n\n    expect(screen.getByTestId('indicator')).not.toBe(null);\n\n    const closeButton = screen.getByText('Close');\n    await user.click(closeButton);\n\n    await waitFor(() => {\n      expect(animationFinished).toBe(true);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/menu/radio-item-indicator/MenuRadioItemIndicator.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useMenuRadioItemContext } from '../radio-item/MenuRadioItemContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { itemMapping } from '../utils/stateAttributesMapping';\nimport { TransitionStatus, useTransitionStatus } from '../../utils/useTransitionStatus';\nimport { useOpenChangeComplete } from '../../utils/useOpenChangeComplete';\n\n/**\n * Indicates whether the radio item is selected.\n * Renders a `<span>` element.\n *\n * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)\n */\nexport const MenuRadioItemIndicator = React.forwardRef(function MenuRadioItemIndicator(\n  componentProps: MenuRadioItemIndicator.Props,\n  forwardedRef: React.ForwardedRef<HTMLSpanElement>,\n) {\n  const { render, className, keepMounted = false, ...elementProps } = componentProps;\n\n  const item = useMenuRadioItemContext();\n\n  const indicatorRef = React.useRef<HTMLSpanElement | null>(null);\n\n  const { transitionStatus, setMounted } = useTransitionStatus(item.checked);\n\n  useOpenChangeComplete({\n    open: item.checked,\n    ref: indicatorRef,\n    onComplete() {\n      if (!item.checked) {\n        setMounted(false);\n      }\n    },\n  });\n\n  const state: MenuRadioItemIndicatorState = {\n    checked: item.checked,\n    disabled: item.disabled,\n    highlighted: item.highlighted,\n    transitionStatus,\n  };\n\n  const element = useRenderElement('span', componentProps, {\n    state,\n    stateAttributesMapping: itemMapping,\n    ref: [forwardedRef, indicatorRef],\n    props: {\n      'aria-hidden': true,\n      ...elementProps,\n    },\n    enabled: keepMounted || item.checked,\n  });\n\n  return element;\n});\n\nexport interface MenuRadioItemIndicatorProps extends BaseUIComponentProps<\n  'span',\n  MenuRadioItemIndicatorState\n> {\n  /**\n   * Whether to keep the HTML element in the DOM when the radio item is inactive.\n   * @default false\n   */\n  keepMounted?: boolean | undefined;\n}\n\nexport interface MenuRadioItemIndicatorState {\n  /**\n   * Whether the radio item is currently selected.\n   */\n  checked: boolean;\n  /**\n   * Whether the component should ignore user interaction.\n   */\n  disabled: boolean;\n  /**\n   * Whether the item is highlighted.\n   */\n  highlighted: boolean;\n  /**\n   * The transition status of the component.\n   */\n  transitionStatus: TransitionStatus;\n}\n\nexport namespace MenuRadioItemIndicator {\n  export type Props = MenuRadioItemIndicatorProps;\n  export type State = MenuRadioItemIndicatorState;\n}\n"
  },
  {
    "path": "packages/react/src/menu/radio-item-indicator/MenuRadioItemIndicatorDataAttributes.ts",
    "content": "import { TransitionStatusDataAttributes } from '../../utils/stateAttributesMapping';\n\nexport enum MenuRadioItemIndicatorDataAttributes {\n  /**\n   * Present when the menu radio item is selected.\n   */\n  checked = 'data-checked',\n  /**\n   * Present when the menu radio item is not selected.\n   */\n  unchecked = 'data-unchecked',\n  /**\n   * Present when the menu radio item is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the radio indicator is animating in.\n   */\n  startingStyle = TransitionStatusDataAttributes.startingStyle,\n  /**\n   * Present when the radio indicator is animating out.\n   */\n  endingStyle = TransitionStatusDataAttributes.endingStyle,\n}\n"
  },
  {
    "path": "packages/react/src/menu/root/MenuRoot.detached-triggers.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { act, fireEvent, ignoreActWarnings, screen, waitFor } from '@mui/internal-test-utils';\nimport { Menu } from '@base-ui/react/menu';\nimport { createRenderer, isJSDOM, wait } from '#test-utils';\n\ndescribe('<MenuRoot />', () => {\n  beforeEach(() => {\n    globalThis.BASE_UI_ANIMATIONS_DISABLED = true;\n  });\n\n  const { render } = createRenderer();\n\n  describe.skipIf(isJSDOM)('multiple triggers within Root', () => {\n    type NumberPayload = { payload: number | undefined };\n\n    it('should open the menu with any trigger', async () => {\n      const { user } = await render(\n        <Menu.Root>\n          <Menu.Trigger>Trigger 1</Menu.Trigger>\n          <Menu.Trigger>Trigger 2</Menu.Trigger>\n          <Menu.Trigger>Trigger 3</Menu.Trigger>\n\n          <Menu.Portal>\n            <Menu.Positioner>\n              <Menu.Popup>\n                <Menu.Item>Close</Menu.Item>\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n      const trigger3 = screen.getByRole('button', { name: 'Trigger 3' });\n\n      expect(screen.queryByRole('menu')).toBe(null);\n\n      await user.click(trigger1);\n      await screen.findByRole('menu');\n      await user.click(await screen.findByRole('menuitem', { name: 'Close' }));\n      await waitFor(() => {\n        expect(screen.queryByRole('menu')).toBe(null);\n      });\n\n      await user.click(trigger2);\n      await screen.findByRole('menu');\n      await user.click(await screen.findByRole('menuitem', { name: 'Close' }));\n      await waitFor(() => {\n        expect(screen.queryByRole('menu')).toBe(null);\n      });\n\n      await user.click(trigger3);\n      await screen.findByRole('menu');\n      await user.click(await screen.findByRole('menuitem', { name: 'Close' }));\n      await waitFor(() => {\n        expect(screen.queryByRole('menu')).toBe(null);\n      });\n    });\n\n    it('should set the payload and render content based on its value', async () => {\n      const { user } = await render(\n        <Menu.Root>\n          {({ payload }: NumberPayload) => (\n            <React.Fragment>\n              <Menu.Trigger payload={1}>Trigger 1</Menu.Trigger>\n              <Menu.Trigger payload={2}>Trigger 2</Menu.Trigger>\n\n              <Menu.Portal>\n                <Menu.Positioner>\n                  <Menu.Popup>\n                    <Menu.Item data-testid=\"content\">{payload}</Menu.Item>\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            </React.Fragment>\n          )}\n        </Menu.Root>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n\n      await user.click(trigger1);\n      await waitFor(() => {\n        expect(screen.getByTestId('content').textContent).toBe('1');\n      });\n\n      await user.click(screen.getByTestId('content'));\n      await waitFor(() => {\n        expect(screen.queryByRole('menu')).toBe(null);\n      });\n\n      await user.click(trigger2);\n      await waitFor(() => {\n        expect(screen.getByTestId('content').textContent).toBe('2');\n      });\n    });\n\n    it('should reuse the popup and positioner DOM nodes when switching triggers', async () => {\n      const { user } = await render(\n        <Menu.Root>\n          {({ payload }: NumberPayload) => (\n            <React.Fragment>\n              <Menu.Trigger payload={1}>Trigger 1</Menu.Trigger>\n              <Menu.Trigger payload={2}>Trigger 2</Menu.Trigger>\n\n              <Menu.Portal>\n                <Menu.Positioner data-testid=\"positioner\">\n                  <Menu.Popup data-testid=\"popup\">\n                    <span>{payload}</span>\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            </React.Fragment>\n          )}\n        </Menu.Root>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n\n      await user.click(trigger1);\n      await screen.findByRole('menu');\n      const popupElement = screen.getByTestId('popup');\n      const positionerElement = screen.getByTestId('positioner');\n\n      await user.click(trigger2);\n      await screen.findByRole('menu');\n\n      expect(screen.getByTestId('popup')).toBe(popupElement);\n      expect(screen.getByTestId('positioner')).toBe(positionerElement);\n    });\n\n    it('should allow controlling the menu state programmatically', async () => {\n      function Test() {\n        const [open, setOpen] = React.useState(false);\n        const [activeTrigger, setActiveTrigger] = React.useState<string | null>(null);\n\n        return (\n          <div>\n            <Menu.Root\n              open={open}\n              triggerId={activeTrigger}\n              onOpenChange={(nextOpen, details) => {\n                setActiveTrigger(details.trigger?.id ?? null);\n                setOpen(nextOpen);\n              }}\n            >\n              {({ payload }: NumberPayload) => (\n                <React.Fragment>\n                  <Menu.Trigger payload={1} id=\"trigger-1\">\n                    Trigger 1\n                  </Menu.Trigger>\n                  <Menu.Trigger payload={2} id=\"trigger-2\">\n                    Trigger 2\n                  </Menu.Trigger>\n\n                  <Menu.Portal>\n                    <Menu.Positioner>\n                      <Menu.Popup>\n                        <Menu.Item data-testid=\"content\">{payload}</Menu.Item>\n                      </Menu.Popup>\n                    </Menu.Positioner>\n                  </Menu.Portal>\n                </React.Fragment>\n              )}\n            </Menu.Root>\n            <button\n              onClick={() => {\n                setOpen(true);\n                setActiveTrigger('trigger-1');\n              }}\n            >\n              Open Trigger 1\n            </button>\n            <button\n              onClick={() => {\n                setOpen(true);\n                setActiveTrigger('trigger-2');\n              }}\n            >\n              Open Trigger 2\n            </button>\n            <button onClick={() => setOpen(false)}>Close</button>\n          </div>\n        );\n      }\n\n      const { user } = await render(<Test />);\n\n      await user.click(screen.getByRole('button', { name: 'Open Trigger 1' }));\n      await waitFor(() => {\n        expect(screen.getByTestId('content').textContent).toBe('1');\n      });\n\n      await user.click(screen.getByRole('button', { name: 'Open Trigger 2' }));\n      await waitFor(() => {\n        expect(screen.getByTestId('content').textContent).toBe('2');\n      });\n\n      await user.click(screen.getByRole('button', { name: 'Close' }));\n      await waitFor(() => {\n        expect(screen.queryByTestId('content')).toBe(null);\n      });\n    });\n\n    it('allows setting an initially open menu', async () => {\n      await render(\n        <Menu.Root defaultOpen defaultTriggerId=\"trigger-2\">\n          {({ payload }: NumberPayload) => (\n            <React.Fragment>\n              <Menu.Trigger payload={1} id=\"trigger-1\">\n                Trigger 1\n              </Menu.Trigger>\n              <Menu.Trigger payload={2} id=\"trigger-2\">\n                Trigger 2\n              </Menu.Trigger>\n              <Menu.Portal>\n                <Menu.Positioner>\n                  <Menu.Popup>\n                    <Menu.Item data-testid=\"popup-content\">{payload}</Menu.Item>\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            </React.Fragment>\n          )}\n        </Menu.Root>,\n      );\n\n      expect(screen.getByTestId('popup-content').textContent).toBe('2');\n    });\n\n    describe('nested menus', () => {\n      it('supports keyboard navigation from any trigger', async () => {\n        const { user } = await render(\n          <Menu.Root>\n            <Menu.Trigger>Trigger 1</Menu.Trigger>\n            <Menu.Trigger>Trigger 2</Menu.Trigger>\n\n            <Menu.Portal>\n              <Menu.Positioner data-testid=\"menu\">\n                <Menu.Popup>\n                  <Menu.Item>Standalone</Menu.Item>\n                  <Menu.SubmenuRoot>\n                    <Menu.SubmenuTrigger data-testid=\"submenu-trigger\">More</Menu.SubmenuTrigger>\n                    <Menu.Portal>\n                      <Menu.Positioner data-testid=\"submenu\">\n                        <Menu.Popup>\n                          <Menu.Item data-testid=\"submenu-item\">Nested</Menu.Item>\n                        </Menu.Popup>\n                      </Menu.Positioner>\n                    </Menu.Portal>\n                  </Menu.SubmenuRoot>\n                </Menu.Popup>\n              </Menu.Positioner>\n            </Menu.Portal>\n          </Menu.Root>,\n        );\n\n        const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n        const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n\n        await user.click(trigger1);\n        await screen.findByTestId('menu');\n\n        await user.keyboard('[ArrowDown]');\n        await user.keyboard('[ArrowDown]');\n\n        const submenuTrigger = await screen.findByTestId('submenu-trigger');\n        await waitFor(() => {\n          expect(submenuTrigger).toHaveFocus();\n        });\n\n        await user.keyboard('[ArrowRight]');\n\n        const submenuItem = await screen.findByTestId('submenu-item');\n        await waitFor(() => {\n          expect(submenuItem).toHaveFocus();\n        });\n\n        await user.keyboard('[ArrowLeft]');\n        await waitFor(() => {\n          expect(screen.queryByTestId('submenu')).toBe(null);\n        });\n        expect(submenuTrigger).toHaveFocus();\n\n        await user.keyboard('[Escape]');\n        await waitFor(() => {\n          expect(screen.queryByTestId('menu')).toBe(null);\n        });\n\n        await user.click(trigger2);\n        await screen.findByTestId('menu');\n      });\n\n      it('opens a submenu with the mouse when hover is disabled', async () => {\n        const { user } = await render(\n          <Menu.Root>\n            <Menu.Trigger>Trigger 1</Menu.Trigger>\n            <Menu.Trigger>Trigger 2</Menu.Trigger>\n\n            <Menu.Portal>\n              <Menu.Positioner data-testid=\"menu\">\n                <Menu.Popup>\n                  <Menu.Item>Standalone</Menu.Item>\n                  <Menu.SubmenuRoot>\n                    <Menu.SubmenuTrigger data-testid=\"submenu-trigger\" openOnHover={false}>\n                      More\n                    </Menu.SubmenuTrigger>\n                    <Menu.Portal>\n                      <Menu.Positioner data-testid=\"submenu\">\n                        <Menu.Popup>\n                          <Menu.Item data-testid=\"submenu-item\">Nested</Menu.Item>\n                        </Menu.Popup>\n                      </Menu.Positioner>\n                    </Menu.Portal>\n                  </Menu.SubmenuRoot>\n                </Menu.Popup>\n              </Menu.Positioner>\n            </Menu.Portal>\n          </Menu.Root>,\n        );\n\n        const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n        const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n\n        await user.click(trigger1);\n        await screen.findByTestId('menu');\n        expect(screen.queryByTestId('submenu')).toBe(null);\n\n        const submenuTrigger = screen.getByTestId('submenu-trigger');\n        await user.click(submenuTrigger);\n\n        const submenuItem = await screen.findByTestId('submenu-item');\n        expect(submenuItem.textContent).toBe('Nested');\n\n        await user.click(submenuItem);\n        await waitFor(() => {\n          expect(screen.queryByTestId('menu')).toBe(null);\n        });\n\n        await user.click(trigger2);\n        await screen.findByTestId('menu');\n        expect(screen.queryByTestId('submenu')).toBe(null);\n      });\n\n      it('closes every level when clicking outside the deepest submenu', async () => {\n        const { user } = await render(\n          <div>\n            <Menu.Root>\n              <Menu.Trigger>Trigger 1</Menu.Trigger>\n              <Menu.Trigger>Trigger 2</Menu.Trigger>\n              <Menu.Portal>\n                <Menu.Positioner data-testid=\"level-1\">\n                  <Menu.Popup>\n                    <Menu.Item>Item 1</Menu.Item>\n                    <Menu.SubmenuRoot>\n                      <Menu.SubmenuTrigger data-testid=\"submenu-trigger-1\">\n                        Level 2\n                      </Menu.SubmenuTrigger>\n                      <Menu.Portal>\n                        <Menu.Positioner data-testid=\"level-2\">\n                          <Menu.Popup>\n                            <Menu.Item>Item 2</Menu.Item>\n                            <Menu.SubmenuRoot>\n                              <Menu.SubmenuTrigger data-testid=\"submenu-trigger-2\">\n                                Level 3\n                              </Menu.SubmenuTrigger>\n                              <Menu.Portal>\n                                <Menu.Positioner data-testid=\"level-3\">\n                                  <Menu.Popup>\n                                    <Menu.Item>Deep Item</Menu.Item>\n                                  </Menu.Popup>\n                                </Menu.Positioner>\n                              </Menu.Portal>\n                            </Menu.SubmenuRoot>\n                          </Menu.Popup>\n                        </Menu.Positioner>\n                      </Menu.Portal>\n                    </Menu.SubmenuRoot>\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            </Menu.Root>\n            <button data-testid=\"outside\">Outside</button>\n          </div>,\n        );\n\n        const trigger = screen.getByRole('button', { name: 'Trigger 1' });\n        await user.click(trigger);\n        await screen.findByTestId('level-1');\n\n        await user.keyboard('[ArrowDown]');\n        await user.keyboard('[ArrowDown]');\n\n        const submenuTrigger1 = await screen.findByTestId('submenu-trigger-1');\n        await waitFor(() => {\n          expect(submenuTrigger1).toHaveFocus();\n        });\n\n        await user.keyboard('[ArrowRight]');\n        await screen.findByTestId('level-2');\n\n        await user.keyboard('[ArrowDown]');\n        const submenuTrigger2 = await screen.findByTestId('submenu-trigger-2');\n        await waitFor(() => {\n          expect(submenuTrigger2).toHaveFocus();\n        });\n\n        await user.keyboard('[ArrowRight]');\n        await screen.findByTestId('level-3');\n\n        await user.click(screen.getByTestId('outside'));\n        await waitFor(() => {\n          expect(screen.queryByTestId('level-1')).toBe(null);\n          expect(screen.queryByTestId('level-2')).toBe(null);\n          expect(screen.queryByTestId('level-3')).toBe(null);\n        });\n      });\n\n      it('allows selecting nested items via click, drag, release', async () => {\n        ignoreActWarnings();\n        const clickSpy = vi.fn();\n        const { user } = await render(\n          <Menu.Root>\n            <Menu.Trigger>Trigger 1</Menu.Trigger>\n            <Menu.Trigger>Trigger 2</Menu.Trigger>\n\n            <Menu.Portal>\n              <Menu.Positioner data-testid=\"menu\">\n                <Menu.Popup>\n                  <Menu.Item>Item 1</Menu.Item>\n                  <Menu.SubmenuRoot>\n                    <Menu.SubmenuTrigger data-testid=\"submenu-trigger\">More</Menu.SubmenuTrigger>\n                    <Menu.Portal>\n                      <Menu.Positioner data-testid=\"submenu\">\n                        <Menu.Popup>\n                          <Menu.Item data-testid=\"submenu-item\" onClick={clickSpy}>\n                            Nested Action\n                          </Menu.Item>\n                        </Menu.Popup>\n                      </Menu.Positioner>\n                    </Menu.Portal>\n                  </Menu.SubmenuRoot>\n                </Menu.Popup>\n              </Menu.Positioner>\n            </Menu.Portal>\n          </Menu.Root>,\n        );\n\n        const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n        fireEvent.mouseDown(trigger1);\n\n        await screen.findByTestId('menu');\n\n        const submenuTrigger = await screen.findByTestId('submenu-trigger');\n        await user.hover(submenuTrigger);\n        await screen.findByTestId('submenu');\n\n        // Wait 200ms to enable mouseup on menu items\n        await wait(200);\n\n        const submenuItem = await screen.findByTestId('submenu-item');\n        fireEvent.mouseUp(submenuItem);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('menu')).toBe(null);\n        });\n        expect(clickSpy.mock.calls.length).toBe(1);\n\n        const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n        await user.click(trigger2);\n        await screen.findByTestId('menu');\n      });\n    });\n  });\n\n  describe.skipIf(isJSDOM)('multiple detached triggers', () => {\n    type NumberPayload = { payload: number | undefined };\n\n    it('should open the menu with any trigger', async () => {\n      const testMenu = Menu.createHandle();\n      const { user } = await render(\n        <div>\n          <Menu.Trigger handle={testMenu}>Trigger 1</Menu.Trigger>\n          <Menu.Trigger handle={testMenu}>Trigger 2</Menu.Trigger>\n          <Menu.Trigger handle={testMenu}>Trigger 3</Menu.Trigger>\n\n          <Menu.Root handle={testMenu}>\n            <Menu.Portal>\n              <Menu.Positioner>\n                <Menu.Popup>\n                  <Menu.Item>Close</Menu.Item>\n                </Menu.Popup>\n              </Menu.Positioner>\n            </Menu.Portal>\n          </Menu.Root>\n        </div>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n      const trigger3 = screen.getByRole('button', { name: 'Trigger 3' });\n\n      expect(screen.queryByRole('menu')).toBe(null);\n\n      await user.click(trigger1);\n      await screen.findByRole('menu');\n      await user.click(await screen.findByRole('menuitem', { name: 'Close' }));\n      await waitFor(() => {\n        expect(screen.queryByRole('menu')).toBe(null);\n      });\n\n      await user.click(trigger2);\n      await screen.findByRole('menu');\n      await user.click(await screen.findByRole('menuitem', { name: 'Close' }));\n      await waitFor(() => {\n        expect(screen.queryByRole('menu')).toBe(null);\n      });\n\n      await user.click(trigger3);\n      await screen.findByRole('menu');\n      await user.click(await screen.findByRole('menuitem', { name: 'Close' }));\n      await waitFor(() => {\n        expect(screen.queryByRole('menu')).toBe(null);\n      });\n    });\n\n    it('should set the payload and render content based on its value', async () => {\n      const testMenu = Menu.createHandle<number>();\n      const { user } = await render(\n        <div>\n          <Menu.Trigger handle={testMenu} payload={1}>\n            Trigger 1\n          </Menu.Trigger>\n          <Menu.Trigger handle={testMenu} payload={2}>\n            Trigger 2\n          </Menu.Trigger>\n\n          <Menu.Root handle={testMenu}>\n            {({ payload }: NumberPayload) => (\n              <Menu.Portal>\n                <Menu.Positioner>\n                  <Menu.Popup>\n                    <Menu.Item data-testid=\"content\">{payload}</Menu.Item>\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            )}\n          </Menu.Root>\n        </div>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n\n      await user.click(trigger1);\n      await waitFor(() => {\n        expect(screen.getByTestId('content').textContent).toBe('1');\n      });\n\n      await user.click(screen.getByTestId('content'));\n      await waitFor(() => {\n        expect(screen.queryByRole('menu')).toBe(null);\n      });\n\n      await user.click(trigger2);\n      await waitFor(() => {\n        expect(screen.getByTestId('content').textContent).toBe('2');\n      });\n    });\n\n    it('should reuse the popup and positioner DOM nodes when switching triggers', async () => {\n      const testMenu = Menu.createHandle<number>();\n      const { user } = await render(\n        <React.Fragment>\n          <Menu.Trigger handle={testMenu} payload={1}>\n            Trigger 1\n          </Menu.Trigger>\n          <Menu.Trigger handle={testMenu} payload={2}>\n            Trigger 2\n          </Menu.Trigger>\n\n          <Menu.Root handle={testMenu}>\n            {({ payload }: NumberPayload) => (\n              <Menu.Portal>\n                <Menu.Positioner data-testid=\"positioner\">\n                  <Menu.Popup data-testid=\"popup\">\n                    <span>{payload}</span>\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            )}\n          </Menu.Root>\n        </React.Fragment>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n\n      await user.click(trigger1);\n      await screen.findByRole('menu');\n      const popupElement = screen.getByTestId('popup');\n      const positionerElement = screen.getByTestId('positioner');\n\n      await user.click(trigger2);\n      await screen.findByRole('menu');\n\n      expect(screen.getByTestId('popup')).toBe(popupElement);\n      expect(screen.getByTestId('positioner')).toBe(positionerElement);\n    });\n\n    it('should allow controlling the menu state programmatically', async () => {\n      const testMenu = Menu.createHandle<number>();\n\n      function Test() {\n        const [open, setOpen] = React.useState(false);\n        const [activeTrigger, setActiveTrigger] = React.useState<string | null>(null);\n\n        return (\n          <div style={{ margin: 50 }}>\n            <Menu.Trigger handle={testMenu} payload={1} id=\"trigger-1\">\n              Trigger 1\n            </Menu.Trigger>\n            <Menu.Trigger handle={testMenu} payload={2} id=\"trigger-2\">\n              Trigger 2\n            </Menu.Trigger>\n\n            <Menu.Root\n              open={open}\n              onOpenChange={(nextOpen, details) => {\n                setActiveTrigger(details.trigger?.id ?? null);\n                setOpen(nextOpen);\n              }}\n              triggerId={activeTrigger}\n              handle={testMenu}\n            >\n              {({ payload }: NumberPayload) => (\n                <Menu.Portal>\n                  <Menu.Positioner data-testid=\"positioner\" side=\"bottom\" align=\"start\">\n                    <Menu.Popup>\n                      <Menu.Item data-testid=\"content\">{payload}</Menu.Item>\n                    </Menu.Popup>\n                  </Menu.Positioner>\n                </Menu.Portal>\n              )}\n            </Menu.Root>\n\n            <button\n              onClick={() => {\n                setOpen(true);\n                setActiveTrigger('trigger-1');\n              }}\n            >\n              Open Trigger 1\n            </button>\n            <button\n              onClick={() => {\n                setOpen(true);\n                setActiveTrigger('trigger-2');\n              }}\n            >\n              Open Trigger 2\n            </button>\n            <button onClick={() => setOpen(false)}>Close</button>\n          </div>\n        );\n      }\n\n      const { user } = await render(<Test />);\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n\n      await user.click(screen.getByRole('button', { name: 'Open Trigger 1' }));\n      await waitFor(() => {\n        expect(screen.getByTestId('content').textContent).toBe('1');\n      });\n\n      await waitFor(() => {\n        const positionerLeft = screen.getByTestId('positioner').getBoundingClientRect().left;\n        expect(\n          Math.abs(positionerLeft - trigger1.getBoundingClientRect().left),\n        ).toBeLessThanOrEqual(1);\n      });\n\n      await user.click(screen.getByRole('button', { name: 'Open Trigger 2' }));\n      await waitFor(() => {\n        expect(screen.getByTestId('content').textContent).toBe('2');\n      });\n      await waitFor(() => {\n        const positionerLeft = screen.getByTestId('positioner').getBoundingClientRect().left;\n        expect(\n          Math.abs(positionerLeft - trigger2.getBoundingClientRect().left),\n        ).toBeLessThanOrEqual(1);\n      });\n\n      await user.click(screen.getByRole('button', { name: 'Close' }));\n      await waitFor(() => {\n        expect(screen.queryByTestId('content')).toBe(null);\n      });\n    });\n\n    it('allows setting an initially open menu', async () => {\n      const testMenu = Menu.createHandle<number>();\n      await render(\n        <Menu.Root handle={testMenu} defaultOpen defaultTriggerId=\"trigger-2\">\n          {({ payload }: NumberPayload) => (\n            <React.Fragment>\n              <Menu.Trigger handle={testMenu} payload={1} id=\"trigger-1\">\n                Trigger 1\n              </Menu.Trigger>\n              <Menu.Trigger handle={testMenu} payload={2} id=\"trigger-2\">\n                Trigger 2\n              </Menu.Trigger>\n              <Menu.Portal>\n                <Menu.Positioner>\n                  <Menu.Popup>\n                    <Menu.Item data-testid=\"popup-content\">{payload}</Menu.Item>\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            </React.Fragment>\n          )}\n        </Menu.Root>,\n      );\n\n      expect(screen.getByTestId('popup-content').textContent).toBe('2');\n    });\n\n    it('should not have inline scale style after switching triggers', async () => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n      const testMenu = Menu.createHandle<number>();\n\n      function Test() {\n        return (\n          <React.Fragment>\n            <Menu.Trigger handle={testMenu} payload={1}>\n              Trigger 1\n            </Menu.Trigger>\n            <Menu.Trigger handle={testMenu} payload={2}>\n              Trigger 2\n            </Menu.Trigger>\n\n            <Menu.Root handle={testMenu}>\n              {({ payload }: NumberPayload) => (\n                <Menu.Portal>\n                  <Menu.Positioner>\n                    <Menu.Popup data-testid=\"popup\">\n                      <Menu.Viewport>\n                        <Menu.Item data-testid=\"content\">{payload}</Menu.Item>\n                      </Menu.Viewport>\n                    </Menu.Popup>\n                  </Menu.Positioner>\n                </Menu.Portal>\n              )}\n            </Menu.Root>\n          </React.Fragment>\n        );\n      }\n\n      const { user } = await render(<Test />);\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n\n      await user.click(trigger1);\n      await waitFor(() => {\n        expect(screen.getByTestId('content').textContent).toBe('1');\n      });\n\n      await user.click(trigger2);\n      await waitFor(() => {\n        expect(screen.getByTestId('content').textContent).toBe('2');\n      });\n\n      const popup = screen.getByTestId('popup');\n      expect(popup.style.scale).toBe('');\n    });\n\n    describe('nested menus', () => {\n      it('supports keyboard navigation regardless of which trigger opened the menu', async () => {\n        const testMenu = Menu.createHandle();\n        const { user } = await render(\n          <div>\n            <Menu.Trigger handle={testMenu}>Trigger 1</Menu.Trigger>\n            <Menu.Trigger handle={testMenu}>Trigger 2</Menu.Trigger>\n\n            <Menu.Root handle={testMenu}>\n              <Menu.Portal>\n                <Menu.Positioner data-testid=\"menu\">\n                  <Menu.Popup>\n                    <Menu.Item>Standalone</Menu.Item>\n                    <Menu.SubmenuRoot>\n                      <Menu.SubmenuTrigger data-testid=\"submenu-trigger\">More</Menu.SubmenuTrigger>\n                      <Menu.Portal>\n                        <Menu.Positioner data-testid=\"submenu\">\n                          <Menu.Popup>\n                            <Menu.Item data-testid=\"submenu-item\">Nested</Menu.Item>\n                          </Menu.Popup>\n                        </Menu.Positioner>\n                      </Menu.Portal>\n                    </Menu.SubmenuRoot>\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            </Menu.Root>\n          </div>,\n        );\n\n        const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n        const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n\n        await user.click(trigger1);\n        await screen.findByTestId('menu');\n\n        await user.keyboard('[ArrowDown]');\n        await user.keyboard('[ArrowDown]');\n\n        const submenuTrigger = await screen.findByTestId('submenu-trigger');\n        await waitFor(() => {\n          expect(submenuTrigger).toHaveFocus();\n        });\n\n        await user.keyboard('[ArrowRight]');\n        const submenuItem = await screen.findByTestId('submenu-item');\n        await waitFor(() => expect(submenuItem).toHaveFocus());\n\n        await user.keyboard('[ArrowLeft]');\n        await waitFor(() => {\n          expect(screen.queryByTestId('submenu')).toBe(null);\n        });\n        expect(submenuTrigger).toHaveFocus();\n\n        await user.keyboard('[Escape]');\n        await waitFor(() => {\n          expect(screen.queryByTestId('menu')).toBe(null);\n        });\n\n        await user.click(trigger2);\n        await screen.findByTestId('menu');\n      });\n\n      it('opens submenus on click when hover is disabled', async () => {\n        const testMenu = Menu.createHandle();\n        const { user } = await render(\n          <div>\n            <Menu.Trigger handle={testMenu}>Trigger 1</Menu.Trigger>\n            <Menu.Trigger handle={testMenu}>Trigger 2</Menu.Trigger>\n\n            <Menu.Root handle={testMenu}>\n              <Menu.Portal>\n                <Menu.Positioner data-testid=\"menu\">\n                  <Menu.Popup>\n                    <Menu.Item>Standalone</Menu.Item>\n                    <Menu.SubmenuRoot>\n                      <Menu.SubmenuTrigger data-testid=\"submenu-trigger\" openOnHover={false}>\n                        More\n                      </Menu.SubmenuTrigger>\n                      <Menu.Portal>\n                        <Menu.Positioner data-testid=\"submenu\">\n                          <Menu.Popup>\n                            <Menu.Item data-testid=\"submenu-item\">Nested</Menu.Item>\n                          </Menu.Popup>\n                        </Menu.Positioner>\n                      </Menu.Portal>\n                    </Menu.SubmenuRoot>\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            </Menu.Root>\n          </div>,\n        );\n\n        const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n        const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n\n        await user.click(trigger1);\n        await screen.findByTestId('menu');\n        expect(screen.queryByTestId('submenu')).toBe(null);\n\n        const submenuTrigger = screen.getByTestId('submenu-trigger');\n        await user.click(submenuTrigger);\n\n        const submenuItem = await screen.findByTestId('submenu-item');\n        expect(submenuItem.textContent).toBe('Nested');\n\n        await user.click(submenuItem);\n        await waitFor(() => {\n          expect(screen.queryByTestId('menu')).toBe(null);\n        });\n\n        await user.click(trigger2);\n        await screen.findByTestId('menu');\n        expect(screen.queryByTestId('submenu')).toBe(null);\n      });\n\n      it('closes the nested tree on outside click', async () => {\n        const testMenu = Menu.createHandle();\n        const { user } = await render(\n          <div>\n            <Menu.Trigger handle={testMenu}>Trigger 1</Menu.Trigger>\n            <Menu.Trigger handle={testMenu}>Trigger 2</Menu.Trigger>\n\n            <Menu.Root handle={testMenu}>\n              <Menu.Portal>\n                <Menu.Positioner data-testid=\"level-1\">\n                  <Menu.Popup>\n                    <Menu.Item>Item 1</Menu.Item>\n                    <Menu.SubmenuRoot>\n                      <Menu.SubmenuTrigger data-testid=\"submenu-trigger-1\">\n                        Level 2\n                      </Menu.SubmenuTrigger>\n                      <Menu.Portal>\n                        <Menu.Positioner data-testid=\"level-2\">\n                          <Menu.Popup>\n                            <Menu.Item>Item 2</Menu.Item>\n                            <Menu.SubmenuRoot>\n                              <Menu.SubmenuTrigger data-testid=\"submenu-trigger-2\">\n                                Level 3\n                              </Menu.SubmenuTrigger>\n                              <Menu.Portal>\n                                <Menu.Positioner data-testid=\"level-3\">\n                                  <Menu.Popup>\n                                    <Menu.Item>Deep Item</Menu.Item>\n                                  </Menu.Popup>\n                                </Menu.Positioner>\n                              </Menu.Portal>\n                            </Menu.SubmenuRoot>\n                          </Menu.Popup>\n                        </Menu.Positioner>\n                      </Menu.Portal>\n                    </Menu.SubmenuRoot>\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            </Menu.Root>\n            <button data-testid=\"outside\">Outside</button>\n          </div>,\n        );\n\n        const trigger = screen.getByRole('button', { name: 'Trigger 1' });\n        await user.click(trigger);\n        await screen.findByTestId('level-1');\n\n        await user.keyboard('[ArrowDown]');\n        await user.keyboard('[ArrowDown]');\n\n        const submenuTrigger1 = await screen.findByTestId('submenu-trigger-1');\n        await waitFor(() => expect(submenuTrigger1).toHaveFocus());\n\n        await user.keyboard('[ArrowRight]');\n        await screen.findByTestId('level-2');\n\n        await user.keyboard('[ArrowDown]');\n        const submenuTrigger2 = await screen.findByTestId('submenu-trigger-2');\n        await waitFor(() => expect(submenuTrigger2).toHaveFocus());\n\n        await user.keyboard('[ArrowRight]');\n        await screen.findByTestId('level-3');\n\n        await user.click(screen.getByTestId('outside'));\n        await waitFor(() => {\n          expect(screen.queryByTestId('level-1')).toBe(null);\n          expect(screen.queryByTestId('level-2')).toBe(null);\n          expect(screen.queryByTestId('level-3')).toBe(null);\n        });\n      });\n\n      it('selects nested items with click, drag, release', async () => {\n        ignoreActWarnings();\n        const testMenu = Menu.createHandle();\n        const clickSpy = vi.fn();\n        const { user } = await render(\n          <div>\n            <Menu.Trigger handle={testMenu}>Trigger 1</Menu.Trigger>\n            <Menu.Trigger handle={testMenu}>Trigger 2</Menu.Trigger>\n\n            <Menu.Root handle={testMenu}>\n              <Menu.Portal>\n                <Menu.Positioner data-testid=\"menu\">\n                  <Menu.Popup>\n                    <Menu.Item>Item 1</Menu.Item>\n                    <Menu.SubmenuRoot>\n                      <Menu.SubmenuTrigger data-testid=\"submenu-trigger\">More</Menu.SubmenuTrigger>\n                      <Menu.Portal>\n                        <Menu.Positioner data-testid=\"submenu\">\n                          <Menu.Popup>\n                            <Menu.Item data-testid=\"submenu-item\" onClick={clickSpy}>\n                              Nested Action\n                            </Menu.Item>\n                          </Menu.Popup>\n                        </Menu.Positioner>\n                      </Menu.Portal>\n                    </Menu.SubmenuRoot>\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            </Menu.Root>\n          </div>,\n        );\n\n        const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n        fireEvent.mouseDown(trigger1);\n        await screen.findByTestId('menu');\n\n        const submenuTrigger = await screen.findByTestId('submenu-trigger');\n        await user.hover(submenuTrigger);\n        await screen.findByTestId('submenu');\n\n        // Wait 200ms to enable mouseup on menu items\n        await wait(200);\n\n        const submenuItem = await screen.findByTestId('submenu-item');\n        fireEvent.mouseUp(submenuItem);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('menu')).toBe(null);\n        });\n        expect(clickSpy.mock.calls.length).toBe(1);\n\n        const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n        await user.click(trigger2);\n        await screen.findByTestId('menu');\n      });\n    });\n  });\n\n  describe.skipIf(isJSDOM)('imperative actions on the handle', () => {\n    type NumberPayload = { payload: number | undefined };\n\n    it('opens and closes the menu', async () => {\n      const menuHandle = Menu.createHandle();\n      await render(\n        <div>\n          <Menu.Trigger handle={menuHandle} id=\"trigger\">\n            Trigger\n          </Menu.Trigger>\n          <Menu.Root handle={menuHandle}>\n            <Menu.Portal>\n              <Menu.Positioner>\n                <Menu.Popup data-testid=\"content\">\n                  <Menu.Item>Content</Menu.Item>\n                </Menu.Popup>\n              </Menu.Positioner>\n            </Menu.Portal>\n          </Menu.Root>\n        </div>,\n      );\n\n      const trigger = screen.getByRole('button', { name: 'Trigger' });\n      expect(screen.queryByRole('menu')).toBe(null);\n\n      await act(async () => {\n        menuHandle.open('trigger');\n      });\n      await waitFor(() => {\n        expect(screen.queryByRole('menu')).not.toBe(null);\n      });\n\n      expect(screen.getByTestId('content').textContent).toBe('Content');\n      expect(trigger).toHaveAttribute('aria-expanded', 'true');\n\n      await act(async () => {\n        menuHandle.close();\n      });\n      await waitFor(() => {\n        expect(screen.queryByRole('menu')).toBe(null);\n      });\n\n      expect(trigger).toHaveAttribute('aria-expanded', 'false');\n    });\n\n    it('sets the payload associated with the trigger', async () => {\n      const menuHandle = Menu.createHandle<number>();\n      await render(\n        <div>\n          <Menu.Trigger handle={menuHandle} id=\"trigger1\" payload={1}>\n            Trigger 1\n          </Menu.Trigger>\n          <Menu.Trigger handle={menuHandle} id=\"trigger2\" payload={2}>\n            Trigger 2\n          </Menu.Trigger>\n          <Menu.Root handle={menuHandle}>\n            {({ payload }: NumberPayload) => (\n              <Menu.Portal>\n                <Menu.Positioner>\n                  <Menu.Popup>\n                    <Menu.Item data-testid=\"content\">{payload}</Menu.Item>\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            )}\n          </Menu.Root>\n        </div>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n      expect(screen.queryByRole('menu')).toBe(null);\n\n      await act(async () => {\n        menuHandle.open('trigger2');\n      });\n      await waitFor(() => {\n        expect(screen.queryByRole('menu')).not.toBe(null);\n      });\n\n      expect(screen.getByTestId('content').textContent).toBe('2');\n      expect(trigger2).toHaveAttribute('aria-expanded', 'true');\n      expect(trigger1).not.toHaveAttribute('aria-expanded', 'true');\n\n      await act(async () => {\n        menuHandle.close();\n      });\n      await waitFor(() => {\n        expect(screen.queryByRole('menu')).toBe(null);\n      });\n\n      expect(trigger2).toHaveAttribute('aria-expanded', 'false');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/menu/root/MenuRoot.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport {\n  act,\n  fireEvent,\n  flushMicrotasks,\n  ignoreActWarnings,\n  screen,\n  waitFor,\n} from '@mui/internal-test-utils';\nimport { DirectionProvider } from '@base-ui/react/direction-provider';\nimport { useRefWithInit } from '@base-ui/utils/useRefWithInit';\nimport { Menu } from '@base-ui/react/menu';\nimport { Dialog } from '@base-ui/react/dialog';\nimport { AlertDialog } from '@base-ui/react/alert-dialog';\nimport userEvent from '@testing-library/user-event';\nimport { createRenderer, isJSDOM, popupConformanceTests, wait } from '#test-utils';\nimport { REASONS } from '../../utils/reasons';\nimport { PATIENT_CLICK_THRESHOLD } from '../../utils/constants';\n\ndescribe('<Menu.Root />', () => {\n  beforeEach(() => {\n    globalThis.BASE_UI_ANIMATIONS_DISABLED = true;\n  });\n\n  const { render } = createRenderer();\n\n  popupConformanceTests({\n    createComponent: (props) => (\n      <Menu.Root {...props.root}>\n        <Menu.Trigger {...props.trigger}>Open menu</Menu.Trigger>\n        <Menu.Portal {...props.portal}>\n          <Menu.Positioner>\n            <Menu.Popup {...props.popup}>\n              <Menu.Item>Item</Menu.Item>\n            </Menu.Popup>\n          </Menu.Positioner>\n        </Menu.Portal>\n      </Menu.Root>\n    ),\n    render,\n    triggerMouseAction: 'click',\n    expectedPopupRole: 'menu',\n  });\n\n  // All these tests run for contained and detached triggers.\n  // The rendered menubar has the same structure in most cases.\n  describe.for([\n    { name: 'contained triggers', Component: ContainedTriggerMenu },\n    { name: 'detached triggers', Component: DetachedTriggerMenu },\n  ])('when using $name', ({ Component: TestMenu }) => {\n    describe('keyboard navigation', () => {\n      it('changes the highlighted item using the arrow keys', async () => {\n        await render(<TestMenu />);\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n        await act(async () => {\n          trigger.focus();\n        });\n\n        await userEvent.keyboard('[Enter]');\n\n        const item1 = screen.getByTestId('item-1');\n        const item2 = screen.getByTestId('item-2');\n        const item3 = screen.getByTestId('item-3');\n\n        await waitFor(() => {\n          expect(item1).toHaveFocus();\n        });\n\n        await userEvent.keyboard('{ArrowDown}');\n        await waitFor(() => {\n          expect(item2).toHaveFocus();\n        });\n\n        await userEvent.keyboard('{ArrowDown}');\n        await waitFor(() => {\n          expect(item3).toHaveFocus();\n        });\n\n        await userEvent.keyboard('{ArrowUp}');\n        await waitFor(() => {\n          expect(item2).toHaveFocus();\n        });\n      });\n\n      it('changes the highlighted item using the Home and End keys', async () => {\n        await render(<TestMenu />);\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n        await act(async () => {\n          trigger.focus();\n        });\n\n        await userEvent.keyboard('[Enter]');\n        const item1 = screen.getByTestId('item-1');\n        const item5 = screen.getByTestId('item-5');\n\n        await waitFor(() => {\n          expect(item1).toHaveFocus();\n        });\n\n        await userEvent.keyboard('{End}');\n        await waitFor(() => {\n          expect(item5).toHaveFocus();\n        });\n\n        await userEvent.keyboard('{Home}');\n        await waitFor(() => {\n          expect(item1).toHaveFocus();\n        });\n      });\n\n      it('includes disabled items during keyboard navigation', async () => {\n        await render(<TestMenu />);\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n        await act(async () => {\n          trigger.focus();\n        });\n\n        await userEvent.keyboard('[Enter]');\n\n        const item1 = screen.getByTestId('item-1');\n        const item2 = screen.getByTestId('item-2');\n        const disabledItem3 = screen.getByTestId('item-3');\n\n        await waitFor(() => {\n          expect(item1).toHaveFocus();\n        });\n\n        await userEvent.keyboard('{ArrowDown}');\n\n        await waitFor(() => {\n          expect(item2).toHaveFocus();\n        });\n\n        await userEvent.keyboard('{ArrowDown}');\n\n        await waitFor(() => {\n          expect(disabledItem3).toHaveFocus();\n        });\n\n        expect(disabledItem3).toHaveAttribute('aria-disabled', 'true');\n      });\n\n      it.skipIf(isJSDOM)('skips items hidden with CSS during keyboard navigation', async () => {\n        await render(\n          <TestMenu\n            popupProps={{\n              children: (\n                <React.Fragment>\n                  <Menu.Item data-testid=\"item-1\" style={{ display: 'none' }}>\n                    Item 1\n                  </Menu.Item>\n                  <Menu.Item data-testid=\"item-2\">Item 2</Menu.Item>\n                  <Menu.Item data-testid=\"item-3\">Item 3</Menu.Item>\n                </React.Fragment>\n              ),\n            }}\n          />,\n        );\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n        await act(async () => {\n          trigger.focus();\n        });\n\n        await userEvent.keyboard('[Enter]');\n\n        const hiddenItem = screen.getByTestId('item-1');\n        const item2 = screen.getByTestId('item-2');\n        const item3 = screen.getByTestId('item-3');\n\n        await waitFor(() => {\n          expect(item2).toHaveFocus();\n        });\n        expect(hiddenItem).toHaveAttribute('tabindex', '-1');\n\n        await userEvent.keyboard('{ArrowDown}');\n        await waitFor(() => {\n          expect(item3).toHaveFocus();\n        });\n\n        await userEvent.keyboard('{ArrowUp}');\n        await waitFor(() => {\n          expect(item2).toHaveFocus();\n        });\n      });\n\n      describe('text navigation', () => {\n        it.skipIf(isJSDOM)('changes the highlighted item', async () => {\n          const itemElements = [\n            <Menu.Item key=\"aa\">Aa</Menu.Item>,\n            <Menu.Item key=\"ba\">Ba</Menu.Item>,\n            <Menu.Item key=\"bb\">Bb</Menu.Item>,\n            <Menu.Item key=\"ca\">Ca</Menu.Item>,\n            <Menu.Item key=\"cb\">Cb</Menu.Item>,\n            <Menu.Item key=\"cd\">Cd</Menu.Item>,\n          ];\n\n          const { user } = await render(\n            <TestMenu rootProps={{ open: true }} popupProps={{ children: itemElements }} />,\n          );\n\n          const items = screen.getAllByRole('menuitem');\n\n          await act(async () => {\n            items[0].focus();\n          });\n\n          await user.keyboard('c');\n          await waitFor(() => {\n            expect(screen.getByText('Ca')).toHaveFocus();\n          });\n\n          expect(screen.getByText('Ca')).toHaveAttribute('tabindex', '0');\n\n          await user.keyboard('d');\n          await waitFor(() => {\n            expect(screen.getByText('Cd')).toHaveFocus();\n          });\n\n          expect(screen.getByText('Cd')).toHaveAttribute('tabindex', '0');\n        });\n\n        it.skipIf(isJSDOM)('skips items hidden with CSS in text navigation', async () => {\n          const itemElements = [\n            <Menu.Item key=\"hidden\" data-testid=\"item-hidden\" style={{ display: 'none' }}>\n              Apple\n            </Menu.Item>,\n            <Menu.Item key=\"apricot\" data-testid=\"item-apricot\">\n              Apricot\n            </Menu.Item>,\n            <Menu.Item key=\"banana\" data-testid=\"item-banana\">\n              Banana\n            </Menu.Item>,\n          ];\n\n          const { user } = await render(\n            <TestMenu rootProps={{ open: true }} popupProps={{ children: itemElements }} />,\n          );\n\n          const hiddenItem = screen.getByTestId('item-hidden');\n          const apricotItem = screen.getByTestId('item-apricot');\n          const bananaItem = screen.getByTestId('item-banana');\n\n          await act(async () => {\n            bananaItem.focus();\n          });\n\n          await user.keyboard('a');\n          await waitFor(() => {\n            expect(apricotItem).toHaveFocus();\n          });\n\n          expect(hiddenItem).toHaveAttribute('tabindex', '-1');\n        });\n\n        it.skipIf(!isJSDOM)(\n          'changes the highlighted item using text navigation on label prop',\n          async () => {\n            const itemElements = [\n              <Menu.Item key=\"1\" label=\"Aa\">\n                1\n              </Menu.Item>,\n              <Menu.Item key=\"2\" label=\"Ba\">\n                2\n              </Menu.Item>,\n              <Menu.Item key=\"3\" label=\"Bb\">\n                3\n              </Menu.Item>,\n              <Menu.Item key=\"4\" label=\"Ca\">\n                4\n              </Menu.Item>,\n            ];\n\n            const { user } = await render(<TestMenu popupProps={{ children: itemElements }} />);\n\n            const trigger = screen.getByRole('button', { name: 'Toggle' });\n            await user.click(trigger);\n            const items = screen.getAllByRole('menuitem');\n            await flushMicrotasks();\n\n            await user.keyboard('b');\n            await waitFor(() => {\n              expect(items[1]).toHaveFocus();\n            });\n\n            await waitFor(() => {\n              expect(items[1]).toHaveAttribute('tabindex', '0');\n            });\n\n            await user.keyboard('b');\n            await waitFor(() => {\n              expect(items[2]).toHaveFocus();\n            });\n\n            await waitFor(() => {\n              expect(items[2]).toHaveAttribute('tabindex', '0');\n            });\n\n            await user.keyboard('b');\n            await waitFor(() => {\n              expect(items[2]).toHaveFocus();\n            });\n\n            await waitFor(() => {\n              expect(items[2]).toHaveAttribute('tabindex', '0');\n            });\n          },\n        );\n\n        it.skipIf(isJSDOM)('skips the non-stringifiable items', async () => {\n          const itemElements = [\n            <Menu.Item key=\"aa\">Aa</Menu.Item>,\n            <Menu.Item key=\"ba\">Ba</Menu.Item>,\n            <Menu.Item key=\"empty\" />,\n            <Menu.Item key=\"nested\">\n              <div>Nested Content</div>\n            </Menu.Item>,\n            <Menu.Item key=\"undefined\">{undefined}</Menu.Item>,\n            <Menu.Item key=\"null\">{null}</Menu.Item>,\n            <Menu.Item key=\"bc\">Bc</Menu.Item>,\n          ];\n\n          const { user } = await render(\n            <TestMenu rootProps={{ open: true }} popupProps={{ children: itemElements }} />,\n          );\n\n          const items = screen.getAllByRole('menuitem');\n\n          await act(async () => {\n            items[0].focus();\n          });\n\n          await user.keyboard('b');\n          await waitFor(() => {\n            expect(screen.getByText('Ba')).toHaveFocus();\n          });\n          expect(screen.getByText('Ba')).toHaveAttribute('tabindex', '0');\n\n          await user.keyboard('c');\n          await waitFor(() => {\n            expect(screen.getByText('Bc')).toHaveFocus();\n          });\n          expect(screen.getByText('Bc')).toHaveAttribute('tabindex', '0');\n        });\n\n        it.skipIf(isJSDOM)('navigate to options with diacritic characters', async () => {\n          const itemElements = [\n            <Menu.Item key=\"aa\">Aa</Menu.Item>,\n            <Menu.Item key=\"ba\">Ba</Menu.Item>,\n            <Menu.Item key=\"bb\">Bb</Menu.Item>,\n            <Menu.Item key=\"bą\">Bą</Menu.Item>,\n          ];\n\n          const { user } = await render(\n            <TestMenu rootProps={{ open: true }} popupProps={{ children: itemElements }} />,\n          );\n\n          const items = screen.getAllByRole('menuitem');\n\n          await act(async () => {\n            items[0].focus();\n          });\n\n          await user.keyboard('b');\n          await waitFor(() => {\n            expect(screen.getByText('Ba')).toHaveFocus();\n          });\n          expect(screen.getByText('Ba')).toHaveAttribute('tabindex', '0');\n\n          await user.keyboard('ą');\n          await waitFor(() => {\n            expect(screen.getByText('Bą')).toHaveFocus();\n          });\n          expect(screen.getByText('Bą')).toHaveAttribute('tabindex', '0');\n        });\n\n        it.skipIf(isJSDOM)(\n          'navigate to next options that begin with diacritic characters',\n          async () => {\n            const itemElements = [\n              <Menu.Item key=\"aa\">Aa</Menu.Item>,\n              <Menu.Item key=\"ąa\">ąa</Menu.Item>,\n              <Menu.Item key=\"ąb\">ąb</Menu.Item>,\n              <Menu.Item key=\"ąc\">ąc</Menu.Item>,\n            ];\n\n            const { user } = await render(\n              <TestMenu rootProps={{ open: true }} popupProps={{ children: itemElements }} />,\n            );\n\n            const items = screen.getAllByRole('menuitem');\n\n            await act(async () => {\n              items[0].focus();\n            });\n\n            await user.keyboard('ą');\n            await waitFor(() => {\n              expect(screen.getByText('ąa')).toHaveFocus();\n            });\n            expect(screen.getByText('ąa')).toHaveAttribute('tabindex', '0');\n          },\n        );\n\n        it.skipIf(isJSDOM)(\n          'does not trigger the onClick event when Space is pressed during text navigation',\n          async () => {\n            const handleClick = vi.fn();\n\n            const itemElements = [\n              <Menu.Item key=\"one\" onClick={() => handleClick()}>\n                Item One\n              </Menu.Item>,\n              <Menu.Item key=\"two\" onClick={() => handleClick()}>\n                Item Two\n              </Menu.Item>,\n              <Menu.Item key=\"three\" onClick={() => handleClick()}>\n                Item Three\n              </Menu.Item>,\n            ];\n\n            const { user } = await render(\n              <TestMenu rootProps={{ open: true }} popupProps={{ children: itemElements }} />,\n            );\n\n            const items = screen.getAllByRole('menuitem');\n\n            await act(async () => {\n              items[0].focus();\n            });\n\n            await user.keyboard('Item T');\n\n            expect(handleClick.mock.calls.length > 0).toBe(false);\n\n            await waitFor(() => {\n              expect(items[1]).toHaveFocus();\n            });\n          },\n        );\n\n        it.skipIf(isJSDOM)(\n          'does not open a submenu when pressing Space during a typeahead session',\n          async () => {\n            const { user } = await render(\n              <TestMenu\n                rootProps={{ open: true }}\n                popupProps={{\n                  children: (\n                    <Menu.SubmenuRoot>\n                      <Menu.SubmenuTrigger data-testid=\"submenu-trigger\">\n                        Add to Playlist\n                      </Menu.SubmenuTrigger>\n                      <Menu.Portal>\n                        <Menu.Positioner>\n                          <Menu.Popup data-testid=\"submenu\">\n                            <Menu.Item>Add now</Menu.Item>\n                          </Menu.Popup>\n                        </Menu.Positioner>\n                      </Menu.Portal>\n                    </Menu.SubmenuRoot>\n                  ),\n                }}\n              />,\n            );\n\n            const submenuTrigger = screen.getByTestId('submenu-trigger');\n\n            await act(async () => {\n              submenuTrigger.focus();\n            });\n\n            await user.keyboard('Add to p');\n\n            await waitFor(() => {\n              expect(submenuTrigger).toHaveFocus();\n            });\n\n            await user.keyboard('[Space]');\n            expect(screen.queryByTestId('submenu')).toBe(null);\n\n            await user.keyboard('[Space]');\n            expect(screen.queryByTestId('submenu')).toBe(null);\n          },\n        );\n\n        it('opens a focused submenu trigger with Space when not typing', async () => {\n          const { user } = await render(<TestMenu rootProps={{ open: true }} />);\n\n          const submenuTrigger = screen.getByTestId('submenu-trigger');\n\n          await act(async () => {\n            submenuTrigger.focus();\n          });\n\n          await user.keyboard('[Space]');\n          expect(screen.queryByTestId('submenu')).not.toBe(null);\n        });\n\n        it.skipIf(isJSDOM)(\n          'matches \"Item 2\" after \"Item \" currently matches \"Item 1\"',\n          async () => {\n            const { user } = await render(\n              <TestMenu\n                rootProps={{ open: true }}\n                popupProps={{\n                  children: (\n                    <React.Fragment>\n                      <Menu.Item>Item 1</Menu.Item>\n                      <Menu.Item data-testid=\"item-2\">Item 2</Menu.Item>\n                      <Menu.Item>Item 3</Menu.Item>\n                    </React.Fragment>\n                  ),\n                }}\n              />,\n            );\n\n            const item1 = screen.getByRole('menuitem', { name: 'Item 1' });\n            const item2 = screen.getByTestId('item-2');\n\n            await act(async () => {\n              item1.focus();\n            });\n\n            await user.keyboard('Item 2');\n            expect(item2).toHaveFocus();\n          },\n        );\n\n        it.skipIf(isJSDOM)(\n          'matches a submenu trigger label after a space + numeric suffix',\n          async () => {\n            const { user } = await render(\n              <TestMenu\n                rootProps={{ open: true }}\n                popupProps={{\n                  children: (\n                    <React.Fragment>\n                      <Menu.Item>Item 1</Menu.Item>\n                      <Menu.SubmenuRoot>\n                        <Menu.SubmenuTrigger data-testid=\"submenu-trigger\">\n                          Item 2\n                        </Menu.SubmenuTrigger>\n                        <Menu.Portal>\n                          <Menu.Positioner>\n                            <Menu.Popup data-testid=\"submenu\">\n                              <Menu.Item>Nested 2.1</Menu.Item>\n                            </Menu.Popup>\n                          </Menu.Positioner>\n                        </Menu.Portal>\n                      </Menu.SubmenuRoot>\n                      <Menu.Item>Item 3</Menu.Item>\n                    </React.Fragment>\n                  ),\n                }}\n              />,\n            );\n\n            const item1 = screen.getByRole('menuitem', { name: 'Item 1' });\n            const submenuTrigger = screen.getByTestId('submenu-trigger');\n\n            await act(async () => {\n              item1.focus();\n            });\n\n            await user.keyboard('Item 2');\n            expect(submenuTrigger).toHaveFocus();\n            expect(screen.queryByTestId('submenu')).toBe(null);\n          },\n        );\n      });\n    });\n\n    describe('nested menus', () => {\n      (\n        [\n          ['vertical', 'ltr', 'ArrowRight', 'ArrowLeft'],\n          ['vertical', 'rtl', 'ArrowLeft', 'ArrowRight'],\n          ['horizontal', 'ltr', 'ArrowDown', 'ArrowUp'],\n          ['horizontal', 'rtl', 'ArrowDown', 'ArrowUp'],\n        ] as const\n      ).forEach(([orientation, direction, openKey, closeKey]) => {\n        it.skipIf(isJSDOM)(\n          `opens a nested menu of a ${orientation} ${direction.toUpperCase()} menu with ${openKey} key and closes it with ${closeKey}`,\n\n          async () => {\n            const { user } = await render(\n              <DirectionProvider direction={direction}>\n                <TestMenu rootProps={{ open: true, orientation }} submenuProps={{ orientation }} />\n              </DirectionProvider>,\n            );\n\n            const submenuTrigger = screen.getByTestId('submenu-trigger');\n\n            await act(async () => {\n              submenuTrigger.focus();\n            });\n\n            // This check fails in JSDOM\n            await waitFor(() => {\n              expect(submenuTrigger).toHaveFocus();\n            });\n\n            await user.keyboard(`[${openKey}]`);\n\n            let submenu: HTMLElement | null = await screen.findByTestId('submenu');\n\n            const submenuItem1 = screen.queryByTestId('item-4_1');\n            expect(submenuItem1).not.toBe(null);\n            await waitFor(() => {\n              expect(submenuItem1).toHaveFocus();\n            });\n\n            await user.keyboard(`[${closeKey}]`);\n\n            submenu = screen.queryByTestId('submenu');\n            expect(submenu).toBe(null);\n\n            expect(submenuTrigger).toHaveFocus();\n          },\n        );\n      });\n\n      it('opens submenu on click when openOnHover is false', async () => {\n        const { user } = await render(<TestMenu submenuTriggerProps={{ openOnHover: false }} />);\n\n        const mainTrigger = screen.getByRole('button', { name: 'Toggle' });\n        await user.click(mainTrigger);\n\n        const menu = await screen.findByTestId('menu');\n        expect(screen.queryByTestId('submenu')).toBe(null);\n\n        const submenuTrigger = await screen.findByTestId('submenu-trigger');\n        await user.click(submenuTrigger);\n\n        expect(menu).not.toBe(null);\n        expect(await screen.findByTestId('item-4_1')).toHaveTextContent('Item 4.1');\n      });\n\n      it('closes submenus when focus is lost by shift-tabbing from a nested menu', async () => {\n        const { user } = await render(<TestMenu />);\n\n        const mainTrigger = screen.getByRole('button', { name: 'Toggle' });\n        await user.click(mainTrigger);\n\n        await screen.findByTestId('menu');\n        expect(screen.queryByTestId('submenu')).toBe(null);\n\n        const submenuTrigger = await screen.findByTestId('submenu-trigger');\n        await user.hover(submenuTrigger);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('submenu')).not.toBe(null);\n        });\n\n        const submenuItem = await screen.findByTestId('item-4_1');\n        await act(async () => {\n          submenuItem.focus();\n        });\n\n        await waitFor(() => {\n          expect(submenuItem).toHaveFocus();\n        });\n\n        // Shift+Tab should close the submenu and focus should return to the submenu trigger\n        await user.keyboard('{Shift>}{Tab}{/Shift}');\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('submenu')).toBe(null);\n        });\n\n        expect(submenuTrigger).toHaveFocus();\n      });\n\n      it('closes the entire tree when clicking outside the deepest submenu', async () => {\n        const { user } = await render(\n          <div>\n            <TestMenu />\n            <button data-testid=\"outside\">Outside</button>\n          </div>,\n        );\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n        await user.click(trigger);\n\n        await screen.findByTestId('menu');\n\n        await user.keyboard('[ArrowDown]');\n        await user.keyboard('[ArrowDown]');\n        await user.keyboard('[ArrowDown]');\n        await user.keyboard('[ArrowDown]');\n\n        const submenuTrigger1 = await screen.findByTestId('submenu-trigger');\n        await waitFor(() => {\n          expect(submenuTrigger1).toHaveFocus();\n        });\n\n        await user.keyboard('[ArrowRight]');\n        await screen.findByTestId('submenu');\n\n        await user.keyboard('[ArrowDown]');\n        await user.keyboard('[ArrowDown]');\n\n        const submenuTrigger2 = await screen.findByTestId('nested-submenu-trigger');\n        await waitFor(() => {\n          expect(submenuTrigger2).toHaveFocus();\n        });\n\n        await user.keyboard('[ArrowRight]');\n        await screen.findByTestId('nested-submenu');\n\n        const outside = screen.getByTestId('outside');\n        await user.click(outside);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('level-1')).toBe(null);\n          expect(screen.queryByTestId('level-2')).toBe(null);\n          expect(screen.queryByTestId('level-3')).toBe(null);\n        });\n      });\n    });\n\n    describe('nested popups', () => {\n      it('keeps the menu and dialog open when pressing Shift+Tab inside a nested dialog', async () => {\n        function MenuWithNestedDialog() {\n          return (\n            <Menu.Root>\n              <Menu.Trigger data-testid=\"menu-trigger\">Open Menu</Menu.Trigger>\n              <Menu.Portal>\n                <Menu.Positioner>\n                  <Menu.Popup data-testid=\"menu-popup\">\n                    <Menu.Item>Item 1</Menu.Item>\n                    <Dialog.Root>\n                      <Menu.Item\n                        render={<Dialog.Trigger />}\n                        closeOnClick={false}\n                        nativeButton\n                        data-testid=\"dialog-trigger\"\n                      >\n                        Open Dialog\n                      </Menu.Item>\n                      <Dialog.Portal>\n                        <Dialog.Popup data-testid=\"dialog-popup\">\n                          <button type=\"button\" data-testid=\"dialog-button\">\n                            Dialog Button\n                          </button>\n                        </Dialog.Popup>\n                      </Dialog.Portal>\n                    </Dialog.Root>\n                    <Menu.Item>Item 2</Menu.Item>\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            </Menu.Root>\n          );\n        }\n\n        const { user } = await render(<MenuWithNestedDialog />);\n\n        const menuTrigger = screen.getByTestId('menu-trigger');\n        await user.click(menuTrigger);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('menu-popup')).not.toBe(null);\n        });\n\n        const dialogTrigger = screen.getByTestId('dialog-trigger');\n        await user.click(dialogTrigger);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('dialog-popup')).not.toBe(null);\n        });\n\n        const dialogButton = screen.getByTestId('dialog-button');\n        await act(async () => {\n          dialogButton.focus();\n        });\n\n        await waitFor(() => {\n          expect(dialogButton).toHaveFocus();\n        });\n\n        // Shift+Tab inside the dialog should NOT close the menu or the dialog\n        await user.keyboard('{Shift>}{Tab}{/Shift}');\n\n        // Both menu and dialog should still be open\n        await waitFor(() => {\n          expect(screen.queryByTestId('menu-popup')).not.toBe(null);\n          expect(screen.queryByTestId('dialog-popup')).not.toBe(null);\n        });\n      });\n\n      it.skipIf(isJSDOM)(\n        'keeps focus in a nested alert dialog popup when the pointer leaves the triggering menu item',\n        async () => {\n          function MenuWithNestedAlertDialog() {\n            return (\n              <Menu.Root>\n                <Menu.Trigger data-testid=\"menu-trigger\">Open Menu</Menu.Trigger>\n                <Menu.Portal>\n                  <Menu.Positioner>\n                    <Menu.Popup data-testid=\"menu-popup\">\n                      <Menu.Item>Item 1</Menu.Item>\n                      <AlertDialog.Root>\n                        <Menu.Item\n                          render={<AlertDialog.Trigger />}\n                          closeOnClick={false}\n                          nativeButton\n                          data-testid=\"alert-dialog-trigger\"\n                        >\n                          Open Alert Dialog\n                        </Menu.Item>\n                        <AlertDialog.Portal>\n                          <AlertDialog.Backdrop data-testid=\"alert-dialog-backdrop\" />\n                          <AlertDialog.Popup data-testid=\"alert-dialog-popup\">\n                            <AlertDialog.Close data-testid=\"alert-dialog-close\">\n                              Close\n                            </AlertDialog.Close>\n                          </AlertDialog.Popup>\n                        </AlertDialog.Portal>\n                      </AlertDialog.Root>\n                      <Menu.Item>Item 2</Menu.Item>\n                    </Menu.Popup>\n                  </Menu.Positioner>\n                </Menu.Portal>\n              </Menu.Root>\n            );\n          }\n\n          const { user } = await render(<MenuWithNestedAlertDialog />);\n\n          await user.click(screen.getByTestId('menu-trigger'));\n\n          const alertDialogTrigger = await screen.findByTestId('alert-dialog-trigger');\n          await user.click(alertDialogTrigger);\n\n          const menuPopup = screen.getByTestId('menu-popup');\n          const alertDialogPopup = await screen.findByTestId('alert-dialog-popup');\n\n          await waitFor(() => {\n            expect(alertDialogPopup.contains(document.activeElement)).toBe(true);\n          });\n\n          fireEvent.pointerLeave(alertDialogTrigger, {\n            pointerType: 'mouse',\n            relatedTarget: document.body,\n          });\n\n          await waitFor(() => {\n            expect(alertDialogPopup.contains(document.activeElement)).toBe(true);\n          });\n          expect(menuPopup.contains(document.activeElement)).toBe(false);\n        },\n      );\n    });\n\n    describe('focus management', () => {\n      it('focuses the first item after the menu is opened by keyboard', async () => {\n        await render(<TestMenu />);\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n        await act(async () => {\n          trigger.focus();\n        });\n\n        await userEvent.keyboard('[Enter]');\n\n        const [firstItem, ...otherItems] = screen.getAllByRole('menuitem');\n        await waitFor(() => {\n          expect(firstItem.tabIndex).toBe(0);\n        });\n        otherItems.forEach((item) => {\n          expect(item.tabIndex).toBe(-1);\n        });\n      });\n\n      it('focuses the first item when down arrow key opens the menu', async () => {\n        const { user } = await render(<TestMenu />);\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n        await act(async () => {\n          trigger.focus();\n        });\n\n        await user.keyboard('[ArrowDown]');\n\n        const [firstItem, ...otherItems] = screen.getAllByRole('menuitem');\n        await waitFor(() => expect(firstItem).toHaveFocus());\n        expect(firstItem.tabIndex).toBe(0);\n        otherItems.forEach((item) => {\n          expect(item.tabIndex).toBe(-1);\n        });\n      });\n\n      it('focuses the last item when up arrow key opens the menu', async () => {\n        const { user } = await render(<TestMenu />);\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n\n        await act(async () => {\n          trigger.focus();\n        });\n\n        await user.keyboard('[ArrowUp]');\n\n        const items = screen.getAllByRole('menuitem');\n        await waitFor(() => {\n          expect(items[4]).toHaveFocus();\n        });\n\n        expect(items[4].tabIndex).toBe(0);\n        [items[0], items[1], items[2], items[3]].forEach((item) => {\n          expect(item.tabIndex).toBe(-1);\n        });\n      });\n\n      it('focuses the trigger after the menu is closed', async () => {\n        const { user } = await render(\n          <div>\n            <input type=\"text\" />\n            <TestMenu />\n            <input type=\"text\" />\n          </div>,\n        );\n\n        const button = screen.getByRole('button', { name: 'Toggle' });\n        await user.click(button);\n\n        const menuItem = await screen.findAllByRole('menuitem');\n        await user.click(menuItem[0]);\n\n        expect(button).toHaveFocus();\n      });\n\n      it.skipIf(isJSDOM)(\n        'focuses the trigger after the menu is closed but not unmounted',\n        async () => {\n          const { user } = await render(\n            <div>\n              <input type=\"text\" />\n              <TestMenu portalProps={{ keepMounted: true }} />\n              <input type=\"text\" />\n            </div>,\n          );\n\n          const button = screen.getByRole('button', { name: 'Toggle' });\n          await user.click(button);\n\n          const menuItem = await screen.findAllByRole('menuitem');\n          await user.click(menuItem[0]);\n\n          await waitFor(() => {\n            expect(button).toHaveFocus();\n          });\n        },\n      );\n    });\n\n    describe('prop: closeParentOnEsc', () => {\n      it('does not close the parent menu when the Escape key is pressed by default', async () => {\n        const { user } = await render(<TestMenu />);\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n        await act(async () => {\n          trigger.focus();\n        });\n\n        await user.keyboard('[ArrowDown]');\n        await waitFor(() => {\n          expect(screen.getByTestId('item-1')).toHaveFocus();\n        });\n\n        await user.keyboard('[ArrowDown]');\n        await waitFor(() => {\n          expect(screen.getByTestId('item-2')).toHaveFocus();\n        });\n\n        await user.keyboard('[ArrowDown]');\n        await waitFor(() => {\n          expect(screen.getByTestId('item-3')).toHaveFocus();\n        });\n\n        await user.keyboard('[ArrowDown]');\n        await waitFor(() => {\n          expect(screen.getByTestId('submenu-trigger')).toHaveFocus();\n        });\n\n        await user.keyboard('[ArrowRight]');\n        await waitFor(() => {\n          expect(screen.getByTestId('item-4_1')).toHaveFocus();\n        });\n\n        await user.keyboard('[Escape]');\n\n        const menus = screen.queryAllByRole('menu', { hidden: false });\n        await waitFor(() => {\n          expect(menus.length).toBe(1);\n        });\n\n        expect(menus[0].dataset.testid).toBe('menu');\n      });\n\n      it('closes the parent menu when the Escape key is pressed  if `closeParentOnEsc=true`', async () => {\n        const { user } = await render(<TestMenu submenuProps={{ closeParentOnEsc: true }} />);\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n        await act(async () => {\n          trigger.focus();\n        });\n\n        await user.keyboard('[ArrowDown]');\n        await waitFor(() => {\n          expect(screen.getByTestId('item-1')).toHaveFocus();\n        });\n\n        await user.keyboard('[ArrowDown]');\n        await waitFor(() => {\n          expect(screen.getByTestId('item-2')).toHaveFocus();\n        });\n\n        await user.keyboard('[ArrowDown]');\n        await waitFor(() => {\n          expect(screen.getByTestId('item-3')).toHaveFocus();\n        });\n\n        await user.keyboard('[ArrowDown]');\n        await waitFor(() => {\n          expect(screen.getByTestId('submenu-trigger')).toHaveFocus();\n        });\n\n        await user.keyboard('[ArrowRight]');\n        await waitFor(() => {\n          expect(screen.getByRole('menuitem', { name: 'Item 4.1' })).toHaveFocus();\n        });\n\n        await user.keyboard('[Escape]');\n        await flushMicrotasks();\n\n        expect(screen.queryByRole('menu', { hidden: false })).toBe(null);\n      });\n    });\n\n    describe('prop: modal', () => {\n      it('should render an internal backdrop when `true`', async () => {\n        const { user } = await render(\n          <div>\n            <TestMenu rootProps={{ modal: true }} />\n            <button>Outside</button>\n          </div>,\n        );\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n\n        await user.click(trigger);\n\n        await waitFor(() => {\n          expect(screen.queryByRole('menu')).not.toBe(null);\n        });\n\n        const positioner = screen.getByTestId('menu-positioner');\n\n        expect(positioner.previousElementSibling).toHaveAttribute('role', 'presentation');\n      });\n\n      it('should not render an internal backdrop when `false`', async () => {\n        const { user } = await render(\n          <div>\n            <TestMenu rootProps={{ modal: false }} />\n            <button>Outside</button>\n          </div>,\n        );\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n\n        await user.click(trigger);\n\n        await waitFor(() => {\n          expect(screen.queryByRole('menu')).not.toBe(null);\n        });\n\n        const positioner = screen.getByTestId('menu-positioner');\n\n        expect(positioner.previousElementSibling).toBe(null);\n      });\n    });\n\n    describe.skipIf(isJSDOM)('interaction type tracking (openMethod)', () => {\n      it('should not apply scroll lock when opened via touch', async () => {\n        await render(<TestMenu rootProps={{ modal: true }} />);\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n\n        fireEvent.pointerDown(trigger, { pointerType: 'touch' });\n        fireEvent.mouseDown(trigger);\n\n        const menu = await screen.findByRole('menu');\n\n        const doc = menu.ownerDocument;\n\n        const isScrollLocked =\n          doc.documentElement.style.overflow === 'hidden' ||\n          doc.documentElement.hasAttribute('data-base-ui-scroll-locked') ||\n          doc.body.style.overflow === 'hidden';\n\n        expect(isScrollLocked).toBe(false);\n      });\n\n      it('should apply scroll lock when opened via mouse', async () => {\n        const { user } = await render(<TestMenu rootProps={{ modal: true }} />);\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n        const doc = trigger.ownerDocument;\n\n        await user.click(trigger);\n        await screen.findByRole('menu');\n\n        const isScrollLocked =\n          doc.documentElement.style.overflow === 'hidden' ||\n          doc.documentElement.hasAttribute('data-base-ui-scroll-locked') ||\n          doc.body.style.overflow === 'hidden';\n\n        expect(isScrollLocked).toBe(true);\n      });\n    });\n\n    describe('prop: actionsRef', () => {\n      it('unmounts the menu when the `unmount` method is called', async () => {\n        const actionsRef = {\n          current: {\n            unmount: vi.fn(),\n            close: vi.fn(),\n          },\n        };\n\n        const { user } = await render(\n          <TestMenu\n            rootProps={{\n              actionsRef,\n              onOpenChange: (open, details) => {\n                details.preventUnmountOnClose();\n              },\n            }}\n          />,\n        );\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n        await act(() => {\n          trigger.focus();\n        });\n\n        await user.keyboard('{Enter}');\n\n        await waitFor(() => {\n          expect(screen.queryByRole('menu')).not.toBe(null);\n        });\n\n        await user.click(trigger);\n\n        await waitFor(() => {\n          expect(screen.queryByRole('menu')).not.toBe(null);\n        });\n\n        await act(async () => {\n          await new Promise((resolve) => {\n            requestAnimationFrame(resolve);\n          });\n\n          actionsRef.current.unmount();\n        });\n\n        await waitFor(() => {\n          expect(screen.queryByRole('menu')).toBe(null);\n        });\n      });\n    });\n\n    describe.skipIf(isJSDOM)('prop: onOpenChangeComplete', () => {\n      it('is called on close when there is no exit animation defined', async () => {\n        const onOpenChangeComplete = vi.fn();\n\n        function Test() {\n          const [open, setOpen] = React.useState(true);\n          return (\n            <div>\n              <button onClick={() => setOpen(false)}>Close</button>\n              <TestMenu rootProps={{ open, onOpenChangeComplete }} />\n            </div>\n          );\n        }\n\n        const { user } = await render(<Test />);\n\n        const closeButton = screen.getByText('Close');\n        await user.click(closeButton);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('menu')).toBe(null);\n        });\n\n        expect(onOpenChangeComplete.mock.calls[0][0]).toBe(true);\n        expect(onOpenChangeComplete.mock.lastCall?.[0]).toBe(false);\n      });\n\n      it('is called on close when the exit animation finishes', async () => {\n        globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n        const onOpenChangeComplete = vi.fn();\n\n        function Test() {\n          const style = `\n          @keyframes test-anim {\n            to {\n              opacity: 0;\n            }\n          }\n\n          .animation-test-indicator[data-ending-style] {\n            animation: test-anim 1ms;\n          }\n        `;\n\n          const [open, setOpen] = React.useState(true);\n\n          return (\n            <div>\n              {/* eslint-disable-next-line react/no-danger */}\n              <style dangerouslySetInnerHTML={{ __html: style }} />\n              <button onClick={() => setOpen(false)}>Close</button>\n              <TestMenu\n                rootProps={{ open, onOpenChangeComplete }}\n                popupProps={{ className: 'animation-test-indicator' }}\n              />\n            </div>\n          );\n        }\n\n        const { user } = await render(<Test />);\n\n        expect(screen.getByTestId('menu')).not.toBe(null);\n\n        // Wait for open animation to finish\n        await waitFor(() => {\n          expect(onOpenChangeComplete.mock.calls[0][0]).toBe(true);\n        });\n\n        const closeButton = screen.getByText('Close');\n        await user.click(closeButton);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('menu')).toBe(null);\n        });\n\n        expect(onOpenChangeComplete.mock.lastCall?.[0]).toBe(false);\n      });\n\n      it('is called on open when there is no enter animation defined', async () => {\n        const onOpenChangeComplete = vi.fn();\n\n        function Test() {\n          const [open, setOpen] = React.useState(false);\n          return (\n            <div>\n              <button onClick={() => setOpen(true)}>Open</button>\n              <TestMenu rootProps={{ open, onOpenChangeComplete }} />\n            </div>\n          );\n        }\n\n        const { user } = await render(<Test />);\n\n        const openButton = screen.getByText('Open');\n        await user.click(openButton);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('menu')).not.toBe(null);\n        });\n\n        expect(onOpenChangeComplete.mock.calls.length).toBe(2);\n        expect(onOpenChangeComplete.mock.calls[0][0]).toBe(true);\n      });\n\n      it('is called on open when the enter animation finishes', async () => {\n        globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n        const onOpenChangeComplete = vi.fn();\n\n        function Test() {\n          const style = `\n          @keyframes test-anim {\n            from {\n              opacity: 0;\n            }\n          }\n\n          .animation-test-indicator[data-starting-style] {\n            animation: test-anim 1ms;\n          }\n        `;\n\n          const [open, setOpen] = React.useState(false);\n\n          return (\n            <div>\n              {/* eslint-disable-next-line react/no-danger */}\n              <style dangerouslySetInnerHTML={{ __html: style }} />\n              <button onClick={() => setOpen(true)}>Open</button>\n              <TestMenu\n                rootProps={{ open, onOpenChange: setOpen, onOpenChangeComplete }}\n                popupProps={{ className: 'animation-test-indicator' }}\n              />\n            </div>\n          );\n        }\n\n        const { user } = await render(<Test />);\n\n        const openButton = screen.getByText('Open');\n        await user.click(openButton);\n\n        // Wait for open animation to finish\n        await waitFor(() => {\n          expect(onOpenChangeComplete.mock.calls[0][0]).toBe(true);\n        });\n\n        expect(screen.queryByTestId('menu')).not.toBe(null);\n      });\n\n      it('does not get called on mount when not open', async () => {\n        const onOpenChangeComplete = vi.fn();\n\n        await render(<TestMenu rootProps={{ onOpenChangeComplete }} />);\n\n        expect(onOpenChangeComplete.mock.calls.length).toBe(0);\n      });\n    });\n\n    describe('prop: openOnHover', () => {\n      it('should open the menu when the trigger is hovered', async () => {\n        await render(<TestMenu triggerProps={{ openOnHover: true, delay: 0 }} />);\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n\n        await act(async () => {\n          trigger.focus();\n        });\n\n        await userEvent.hover(trigger);\n\n        await waitFor(() => {\n          expect(screen.queryByRole('menu')).not.toBe(null);\n        });\n      });\n\n      it('should close the menu when the trigger is no longer hovered', async () => {\n        await render(\n          <TestMenu rootProps={{ modal: false }} triggerProps={{ openOnHover: true, delay: 0 }} />,\n        );\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n\n        await act(async () => {\n          trigger.focus();\n        });\n\n        await userEvent.hover(trigger);\n\n        await waitFor(() => {\n          expect(screen.queryByRole('menu')).not.toBe(null);\n        });\n\n        await userEvent.unhover(trigger);\n\n        await waitFor(() => {\n          expect(screen.queryByRole('menu')).toBe(null);\n        });\n      });\n\n      it('opens the submenu on hover with zero delay', async () => {\n        await render(\n          <ContainedTriggerMenu\n            rootProps={{ defaultOpen: true }}\n            submenuTriggerProps={{ delay: 0 }}\n          />,\n        );\n\n        const submenuTrigger = screen.getByTestId('submenu-trigger');\n\n        await userEvent.hover(submenuTrigger);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('submenu')).not.toBe(null);\n        });\n      });\n\n      it('does not clear body pointer-events styles when closing a scoped submenu', async () => {\n        await render(\n          <TestMenu\n            rootProps={{ defaultOpen: true }}\n            submenuTriggerProps={{ delay: 0, closeDelay: 0 }}\n          />,\n        );\n\n        const submenuTrigger = screen.getByTestId('submenu-trigger');\n        await userEvent.hover(submenuTrigger);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('submenu')).not.toBe(null);\n        });\n\n        const previousBodyPointerEvents = document.body.style.pointerEvents;\n        try {\n          document.body.style.pointerEvents = 'none';\n\n          const sibling = screen.getByTestId('item-2');\n          // Use fireEvent to bypass pointer-events checks during safe-polygon pointer events mutation\n          fireEvent.mouseMove(sibling);\n\n          await waitFor(() => {\n            expect(screen.queryByTestId('submenu')).toBe(null);\n          });\n\n          expect(document.body.style.pointerEvents).toBe('none');\n        } finally {\n          document.body.style.pointerEvents = previousBodyPointerEvents;\n        }\n      });\n\n      it('should not close when submenu is hovered after root menu is hovered', async () => {\n        await render(\n          <TestMenu\n            triggerProps={{ openOnHover: true, delay: 0 }}\n            submenuTriggerProps={{ delay: 0 }}\n          />,\n        );\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n\n        await act(async () => {\n          trigger.focus();\n        });\n\n        await userEvent.hover(trigger);\n\n        await waitFor(() => {\n          expect(screen.getByTestId('menu')).not.toBe(null);\n        });\n\n        const menu = screen.getByTestId('menu');\n\n        await userEvent.hover(menu);\n\n        const submenuTrigger = screen.getByRole('menuitem', { name: 'Item 4' });\n\n        await userEvent.hover(submenuTrigger);\n\n        await waitFor(() => {\n          expect(screen.getByTestId('menu')).not.toBe(null);\n        });\n        await waitFor(() => {\n          expect(screen.getByTestId('submenu')).not.toBe(null);\n        });\n\n        const submenu = screen.getByTestId('submenu');\n\n        // Use fireEvent to bypass pointer-events checks during safe-polygon pointer events mutation\n        fireEvent.mouseMove(menu);\n        fireEvent.mouseLeave(menu);\n        await userEvent.hover(submenu);\n\n        await waitFor(() => {\n          expect(screen.getByTestId('menu')).not.toBe(null);\n        });\n        await waitFor(() => {\n          expect(screen.getByTestId('submenu')).not.toBe(null);\n        });\n      });\n\n      it('keeps the parent submenu open after a third-level submenu closes due to sibling hover', async () => {\n        await render(\n          <ContainedTriggerMenu\n            triggerProps={{ openOnHover: true, delay: 0 }}\n            submenuTriggerProps={{ delay: 0 }}\n          />,\n        );\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n\n        await act(async () => {\n          trigger.focus();\n        });\n\n        await userEvent.hover(trigger);\n\n        await waitFor(() => {\n          expect(screen.getByTestId('menu')).not.toBe(null);\n        });\n\n        // Open first-level submenu\n        const level1Trigger = screen.getByRole('menuitem', { name: 'Item 4' });\n        await userEvent.hover(level1Trigger);\n\n        await waitFor(() => {\n          expect(screen.getByTestId('submenu')).not.toBe(null);\n        });\n\n        // Open second-level submenu\n        const level2Trigger = screen.getByRole('menuitem', { name: 'Item 4.3' });\n        await userEvent.hover(level2Trigger);\n\n        await waitFor(() => {\n          expect(screen.getByTestId('nested-submenu')).not.toBe(null);\n        });\n\n        // Hover a sibling item in the parent submenu to close the second-level submenu\n        const parentSibling = screen.getByRole('menuitem', { name: 'Item 4.2' });\n        // Use fireEvent to bypass pointer-events checks during safe-polygon pointer events mutation\n        fireEvent.mouseMove(parentSibling);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('nested-submenu')).toBe(null);\n        });\n\n        // Now unhover the parent submenu container; it should remain open\n        const submenu1 = screen.getByTestId('submenu');\n        fireEvent.mouseLeave(submenu1);\n\n        // Parent submenu should still be open\n        await waitFor(() => {\n          expect(screen.getByTestId('submenu')).not.toBe(null);\n        });\n      });\n\n      describe('modal behavior', () => {\n        const { render: renderFakeTimers, clock } = createRenderer();\n\n        clock.withFakeTimers();\n\n        it('treats hover-opened menus as modal after a click', async () => {\n          await renderFakeTimers(\n            <Menu.Root>\n              <Menu.Trigger openOnHover delay={0}>\n                Toggle\n              </Menu.Trigger>\n              <Menu.Portal>\n                <Menu.Positioner data-testid=\"positioner\">\n                  <Menu.Popup>\n                    <Menu.Item>Item 1</Menu.Item>\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            </Menu.Root>,\n          );\n\n          const trigger = screen.getByRole('button', { name: 'Toggle' });\n\n          fireEvent.mouseEnter(trigger);\n          fireEvent.mouseMove(trigger);\n\n          await flushMicrotasks();\n          expect(screen.queryByRole('menu')).not.toBe(null);\n\n          const positioner = screen.getByTestId('positioner');\n          expect(positioner.previousElementSibling).toBe(null);\n\n          clock.tick(PATIENT_CLICK_THRESHOLD - 1);\n          fireEvent.click(trigger);\n\n          await flushMicrotasks();\n          expect(positioner.previousElementSibling).toHaveAttribute('role', 'presentation');\n        });\n      });\n    });\n\n    describe('prop: closeDelay', () => {\n      const { render: renderFakeTimers, clock } = createRenderer();\n\n      clock.withFakeTimers();\n\n      it('should close after delay', async () => {\n        await renderFakeTimers(\n          <TestMenu triggerProps={{ openOnHover: true, delay: 0, closeDelay: 100 }} />,\n        );\n\n        const anchor = screen.getByRole('button');\n\n        fireEvent.mouseEnter(anchor);\n        fireEvent.mouseMove(anchor);\n\n        await flushMicrotasks();\n\n        expect(screen.getByText('Item 1')).not.toBe(null);\n\n        fireEvent.mouseLeave(anchor);\n\n        clock.tick(50);\n\n        expect(screen.getByText('Item 1')).not.toBe(null);\n\n        clock.tick(50);\n\n        expect(screen.queryByText('Item 1')).toBe(null);\n      });\n\n      it('should close submenu after delay when hovering a sibling item', async () => {\n        await renderFakeTimers(\n          <TestMenu\n            triggerProps={{ openOnHover: true, delay: 0 }}\n            submenuTriggerProps={{ delay: 0, closeDelay: 100 }}\n          />,\n        );\n\n        const trigger = screen.getByRole('button');\n\n        fireEvent.mouseEnter(trigger);\n        fireEvent.mouseMove(trigger);\n\n        await flushMicrotasks();\n\n        // Open the submenu by hovering its trigger\n        const submenuTrigger = screen.getByRole('menuitem', { name: 'Item 4' });\n        fireEvent.mouseEnter(submenuTrigger);\n        fireEvent.mouseMove(submenuTrigger);\n\n        await flushMicrotasks();\n\n        expect(screen.queryByTestId('submenu')).not.toBe(null);\n\n        // Hover a sibling item in the parent menu\n        const siblingItem = screen.getByRole('menuitem', { name: 'Item 1' });\n        fireEvent.mouseMove(siblingItem);\n\n        // Submenu should still be open after partial delay\n        clock.tick(50);\n        expect(screen.queryByTestId('submenu')).not.toBe(null);\n\n        // Submenu should close after the full delay\n        clock.tick(50);\n        expect(screen.queryByTestId('submenu')).toBe(null);\n      });\n\n      it('should not restart closeDelay on repeated mousemove over sibling items', async () => {\n        await renderFakeTimers(\n          <TestMenu\n            triggerProps={{ openOnHover: true, delay: 0 }}\n            submenuTriggerProps={{ delay: 0, closeDelay: 100 }}\n          />,\n        );\n\n        const trigger = screen.getByRole('button');\n\n        fireEvent.mouseEnter(trigger);\n        fireEvent.mouseMove(trigger);\n\n        await flushMicrotasks();\n\n        // Open the submenu by hovering its trigger\n        const submenuTrigger = screen.getByRole('menuitem', { name: 'Item 4' });\n        fireEvent.mouseEnter(submenuTrigger);\n        fireEvent.mouseMove(submenuTrigger);\n\n        await flushMicrotasks();\n\n        expect(screen.queryByTestId('submenu')).not.toBe(null);\n\n        // Hover a sibling item in the parent menu\n        const siblingItem = screen.getByRole('menuitem', { name: 'Item 1' });\n        fireEvent.mouseMove(siblingItem);\n\n        // Wait 80ms (most of the delay), then move again over the sibling\n        clock.tick(80);\n        expect(screen.queryByTestId('submenu')).not.toBe(null);\n\n        // Move again - this should NOT restart the timer\n        fireEvent.mouseMove(siblingItem);\n\n        // After 20 more ms (100ms total from first move), the submenu should close\n        clock.tick(20);\n        expect(screen.queryByTestId('submenu')).toBe(null);\n      });\n    });\n\n    describe.skipIf(isJSDOM)('mouse interaction', () => {\n      afterEach(async () => {\n        const { cleanup } = await import('vitest-browser-react');\n        await cleanup();\n      });\n\n      it('triggers a menu item and closes the menu on click, drag, release', async () => {\n        ignoreActWarnings();\n        const openChangeSpy = vi.fn();\n        const clickSpy = vi.fn();\n\n        const items = [\n          <Menu.Item key=\"1\" data-testid=\"item-1\">\n            1\n          </Menu.Item>,\n          <Menu.Item key=\"2\" data-testid=\"item-2\" onClick={clickSpy}>\n            2\n          </Menu.Item>,\n          <Menu.Item key=\"3\" data-testid=\"item-3\">\n            3\n          </Menu.Item>,\n        ];\n\n        await render(\n          <div>\n            <TestMenu\n              rootProps={{ onOpenChange: openChangeSpy }}\n              popupProps={{ children: items }}\n            />\n          </div>,\n        );\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n\n        fireEvent.mouseDown(trigger);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('menu')).not.toBe(null);\n        });\n\n        await wait(200);\n\n        const item2 = screen.getByTestId('item-2');\n        fireEvent.mouseUp(item2);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('menu')).toBe(null);\n        });\n\n        expect(clickSpy.mock.calls.length).toBe(1);\n\n        expect(openChangeSpy.mock.calls.length).toBe(2);\n        expect(openChangeSpy.mock.calls[0][0]).toBe(true);\n        expect(openChangeSpy.mock.lastCall?.[0]).toBe(false);\n        expect(openChangeSpy.mock.lastCall?.[1].reason).toBe(REASONS.itemPress);\n      });\n\n      it('closes the menu on click, drag outside, release', async () => {\n        const { userEvent: user } = await import('vitest/browser');\n        const { render: vbrRender } = await import('vitest-browser-react');\n\n        const openChangeSpy = vi.fn();\n\n        const items = [\n          <Menu.Item key=\"1\" data-testid=\"item-1\">\n            1\n          </Menu.Item>,\n          <Menu.Item key=\"2\" data-testid=\"item-2\">\n            2\n          </Menu.Item>,\n          <Menu.Item key=\"3\" data-testid=\"item-3\">\n            3\n          </Menu.Item>,\n        ];\n\n        await vbrRender(\n          <div>\n            <TestMenu\n              rootProps={{ onOpenChange: openChangeSpy }}\n              popupProps={{ children: items }}\n            />\n            <div data-testid=\"outside\">Outside</div>\n          </div>,\n        );\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n        const outsideElement = screen.getByTestId('outside');\n\n        await user.dragAndDrop(trigger, outsideElement);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('menu')).toBe(null);\n        });\n\n        expect(openChangeSpy.mock.calls.length).toBe(2);\n        expect(openChangeSpy.mock.calls[0][0]).toBe(true);\n        expect(openChangeSpy.mock.lastCall?.[0]).toBe(false);\n        expect(openChangeSpy.mock.lastCall?.[1].reason).toBe(REASONS.cancelOpen);\n      });\n    });\n\n    describe('BaseUIChangeEventDetails', () => {\n      it('onOpenChange cancel() prevents opening while uncontrolled', async () => {\n        await render(\n          <TestMenu\n            rootProps={{\n              onOpenChange: (nextOpen, eventDetails) => {\n                if (nextOpen) {\n                  eventDetails.cancel();\n                }\n              },\n            }}\n          />,\n        );\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n        await userEvent.click(trigger);\n\n        await waitFor(() => {\n          expect(screen.queryByRole('menu')).toBe(null);\n        });\n      });\n    });\n  });\n\n  describe('prop: highlightItemOnHover', () => {\n    it('highlights an item on mouse move by default', async () => {\n      await render(\n        <Menu.Root open>\n          <Menu.Portal>\n            <Menu.Positioner>\n              <Menu.Popup>\n                <Menu.Item data-testid=\"item-1\">Item 1</Menu.Item>\n                <Menu.Item data-testid=\"item-2\">Item 2</Menu.Item>\n                <Menu.Item data-testid=\"item-3\">Item 3</Menu.Item>\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      const item2 = screen.getByTestId('item-2');\n      fireEvent.mouseMove(item2);\n\n      await waitFor(() => {\n        expect(item2).toHaveFocus();\n      });\n    });\n\n    it('does not highlight items from mouse movement when disabled', async () => {\n      await render(\n        <Menu.Root open highlightItemOnHover={false}>\n          <Menu.Portal>\n            <Menu.Positioner>\n              <Menu.Popup>\n                <Menu.Item data-testid=\"item-1\">Item 1</Menu.Item>\n                <Menu.Item data-testid=\"item-2\">Item 2</Menu.Item>\n                <Menu.Item data-testid=\"item-3\">Item 3</Menu.Item>\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      const item2 = screen.getByTestId('item-2');\n      fireEvent.mouseMove(item2);\n\n      await flushMicrotasks();\n\n      expect(item2).not.toHaveFocus();\n    });\n  });\n\n  describe('dynamic items', () => {\n    const { render: renderFakeTimers, clock } = createRenderer({\n      clockOptions: {\n        shouldAdvanceTime: true,\n      },\n    });\n\n    clock.withFakeTimers();\n\n    it('skips null items when navigating', async () => {\n      function DynamicMenu() {\n        const [itemsFiltered, setItemsFiltered] = React.useState(false);\n\n        return (\n          <Menu.Root\n            onOpenChange={(newOpen) => {\n              if (newOpen) {\n                setTimeout(() => {\n                  setItemsFiltered(true);\n                }, 0);\n              }\n            }}\n            onOpenChangeComplete={(newOpen) => {\n              if (!newOpen) {\n                setItemsFiltered(false);\n              }\n            }}\n          >\n            <Menu.Trigger>Toggle</Menu.Trigger>\n            <Menu.Portal>\n              <Menu.Positioner>\n                <Menu.Popup>\n                  <Menu.Item>Add to Library</Menu.Item>\n                  {!itemsFiltered && (\n                    <React.Fragment>\n                      <Menu.Item>Add to Playlist</Menu.Item>\n                      <Menu.Item>Play Next</Menu.Item>\n                      <Menu.Item>Play Last</Menu.Item>\n                    </React.Fragment>\n                  )}\n                  <Menu.Item>Favorite</Menu.Item>\n                  <Menu.Item>Share</Menu.Item>\n                </Menu.Popup>\n              </Menu.Positioner>\n            </Menu.Portal>\n          </Menu.Root>\n        );\n      }\n\n      const { user } = await renderFakeTimers(<DynamicMenu />);\n\n      const trigger = screen.getByText('Toggle');\n\n      await act(async () => {\n        trigger.focus();\n      });\n\n      await user.keyboard('{ArrowDown}');\n\n      await waitFor(() => {\n        expect(screen.queryByRole('menu')).not.toBe(null);\n      });\n\n      await user.keyboard('{ArrowDown}');\n      await user.keyboard('{ArrowDown}'); // Share\n      await user.keyboard('{ArrowDown}'); // loops back to Add to Library\n\n      expect(screen.queryByRole('menuitem', { name: 'Add to Library' })).toHaveFocus();\n    });\n  });\n});\n\nfunction ContainedTriggerMenu(props: TestMenuProps) {\n  const { triggerProps, ...rest } = props;\n  return (\n    <TestMenuContents {...rest}>\n      <Menu.Trigger {...triggerProps}>Toggle</Menu.Trigger>\n    </TestMenuContents>\n  );\n}\n\nfunction DetachedTriggerMenu(props: TestMenuProps) {\n  const { triggerProps, ...rest } = props;\n  const menuHandle = useRefWithInit(() => new Menu.Handle()).current;\n\n  return (\n    <React.Fragment>\n      <TestMenuContents\n        {...rest}\n        rootProps={{ ...rest.rootProps, handle: menuHandle }}\n      ></TestMenuContents>\n      <Menu.Trigger handle={menuHandle} {...triggerProps}>\n        Toggle\n      </Menu.Trigger>\n    </React.Fragment>\n  );\n}\n\ntype TestMenuProps = {\n  rootProps?: Menu.Root.Props;\n  portalProps?: Menu.Portal.Props;\n  popupProps?: Menu.Popup.Props;\n  triggerProps?: Menu.Trigger.Props;\n  submenuProps?: Menu.SubmenuRoot.Props;\n  submenuTriggerProps?: Menu.SubmenuTrigger.Props;\n  children?: React.ReactNode;\n};\n\nfunction TestMenuContents(props: TestMenuProps) {\n  const { children, rootProps, portalProps, submenuProps, submenuTriggerProps, popupProps } = props;\n  return (\n    <Menu.Root {...rootProps}>\n      {children}\n      <Menu.Portal {...portalProps}>\n        <Menu.Positioner data-testid=\"menu-positioner\">\n          <Menu.Popup data-testid=\"menu\" {...popupProps}>\n            {popupProps?.children ?? (\n              <React.Fragment>\n                <Menu.Item data-testid=\"item-1\">Item 1</Menu.Item>\n                <Menu.Item data-testid=\"item-2\">Item 2</Menu.Item>\n                <Menu.Item data-testid=\"item-3\" disabled>\n                  Item 3\n                </Menu.Item>\n                <Menu.SubmenuRoot {...submenuProps}>\n                  <Menu.SubmenuTrigger data-testid=\"submenu-trigger\" {...submenuTriggerProps}>\n                    Item 4\n                  </Menu.SubmenuTrigger>\n                  <Menu.Portal>\n                    <Menu.Positioner>\n                      <Menu.Popup data-testid=\"submenu\">\n                        <Menu.Item data-testid=\"item-4_1\">Item 4.1</Menu.Item>\n                        <Menu.Item data-testid=\"item-4_2\">Item 4.2</Menu.Item>\n                        <Menu.SubmenuRoot {...submenuProps}>\n                          <Menu.SubmenuTrigger\n                            data-testid=\"nested-submenu-trigger\"\n                            {...submenuTriggerProps}\n                          >\n                            Item 4.3\n                          </Menu.SubmenuTrigger>\n                          <Menu.Portal>\n                            <Menu.Positioner>\n                              <Menu.Popup data-testid=\"nested-submenu\">\n                                <Menu.Item data-testid=\"item-4_3_1\">Item 4.3.1</Menu.Item>\n                                <Menu.Item data-testid=\"item-4_3_2\">Item 4.3.2</Menu.Item>\n                              </Menu.Popup>\n                            </Menu.Positioner>\n                          </Menu.Portal>\n                        </Menu.SubmenuRoot>\n                      </Menu.Popup>\n                    </Menu.Positioner>\n                  </Menu.Portal>\n                </Menu.SubmenuRoot>\n                <Menu.Item data-testid=\"item-5\">Item 5</Menu.Item>\n              </React.Fragment>\n            )}\n          </Menu.Popup>\n        </Menu.Positioner>\n      </Menu.Portal>\n    </Menu.Root>\n  );\n}\n"
  },
  {
    "path": "packages/react/src/menu/root/MenuRoot.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useTimeout } from '@base-ui/utils/useTimeout';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useId } from '@base-ui/utils/useId';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useOnFirstRender } from '@base-ui/utils/useOnFirstRender';\nimport { useScrollLock } from '@base-ui/utils/useScrollLock';\nimport { EMPTY_ARRAY } from '@base-ui/utils/empty';\nimport { fastComponent } from '@base-ui/utils/fastHooks';\nimport {\n  FloatingEvents,\n  FloatingTree,\n  useDismiss,\n  useFloatingNodeId,\n  useFloatingParentNodeId,\n  useInteractions,\n  useListNavigation,\n  useRole,\n  useTypeahead,\n  useSyncedFloatingRootContext,\n} from '../../floating-ui-react';\nimport { MenuRootContext, useMenuRootContext } from './MenuRootContext';\nimport { MenubarContext, useMenubarContext } from '../../menubar/MenubarContext';\nimport { TYPEAHEAD_RESET_MS } from '../../utils/constants';\nimport { useDirection } from '../../direction-provider/DirectionContext';\nimport { useOpenInteractionType } from '../../utils/useOpenInteractionType';\nimport type { FloatingUIOpenChangeDetails } from '../../utils/types';\nimport {\n  createChangeEventDetails,\n  type BaseUIChangeEventDetails,\n} from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport {\n  ContextMenuRootContext,\n  useContextMenuRootContext,\n} from '../../context-menu/root/ContextMenuRootContext';\nimport { mergeProps } from '../../merge-props';\nimport { MenuStore, type State as MenuStoreState } from '../store/MenuStore';\nimport { MenuHandle } from '../store/MenuHandle';\nimport {\n  PayloadChildRenderFunction,\n  useImplicitActiveTrigger,\n  useOpenStateTransitions,\n} from '../../utils/popups';\nimport { useMenuSubmenuRootContext } from '../submenu-root/MenuSubmenuRootContext';\n\n/**\n * Groups all parts of the menu.\n * Doesn’t render its own HTML element.\n *\n * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)\n */\nexport const MenuRoot = fastComponent(function MenuRoot<Payload>(props: MenuRoot.Props<Payload>) {\n  const {\n    children,\n    open: openProp,\n    onOpenChange,\n    onOpenChangeComplete,\n    defaultOpen = false,\n    disabled: disabledProp = false,\n    modal: modalProp,\n    loopFocus = true,\n    orientation = 'vertical',\n    actionsRef,\n    closeParentOnEsc = false,\n    handle,\n    triggerId: triggerIdProp,\n    defaultTriggerId: defaultTriggerIdProp = null,\n    highlightItemOnHover = true,\n  } = props;\n\n  const contextMenuContext = useContextMenuRootContext(true);\n  const parentMenuRootContext = useMenuRootContext(true);\n  const menubarContext = useMenubarContext(true);\n  const isSubmenu = useMenuSubmenuRootContext();\n\n  const parentFromContext: MenuParent = React.useMemo(() => {\n    if (isSubmenu && parentMenuRootContext) {\n      return {\n        type: 'menu',\n        store: parentMenuRootContext.store,\n      };\n    }\n\n    if (menubarContext) {\n      return {\n        type: 'menubar',\n        context: menubarContext,\n      };\n    }\n\n    // Ensure this is not a Menu nested inside ContextMenu.Trigger.\n    // ContextMenu parentContext is always undefined as ContextMenu.Root is instantiated with\n    // <MenuRootContext.Provider value={undefined}>\n    if (contextMenuContext && !parentMenuRootContext) {\n      return {\n        type: 'context-menu',\n        context: contextMenuContext,\n      };\n    }\n\n    return {\n      type: undefined,\n    };\n  }, [contextMenuContext, parentMenuRootContext, menubarContext, isSubmenu]);\n\n  const store = MenuStore.useStore(handle?.store, {\n    open: defaultOpen,\n    openProp,\n    activeTriggerId: defaultTriggerIdProp,\n    triggerIdProp,\n    parent: parentFromContext,\n  });\n\n  // Support initially open state when uncontrolled\n  useOnFirstRender(() => {\n    if (openProp === undefined && store.state.open === false && defaultOpen === true) {\n      store.update({\n        open: true,\n        activeTriggerId: defaultTriggerIdProp,\n      });\n    }\n  });\n\n  store.useControlledProp('openProp', openProp);\n  store.useControlledProp('triggerIdProp', triggerIdProp);\n\n  store.useContextCallback('onOpenChangeComplete', onOpenChangeComplete);\n\n  const floatingTreeRoot = store.useState('floatingTreeRoot');\n  const floatingNodeIdFromContext = useFloatingNodeId(floatingTreeRoot);\n  const floatingParentNodeIdFromContext = useFloatingParentNodeId();\n\n  useIsoLayoutEffect(() => {\n    if (contextMenuContext && !parentMenuRootContext) {\n      // This is a context menu root.\n      // It doesn't support detached triggers yet, so we have to sync the parent context manually.\n      store.update({\n        parent: {\n          type: 'context-menu',\n          context: contextMenuContext,\n        },\n        floatingNodeId: floatingNodeIdFromContext,\n        floatingParentNodeId: floatingParentNodeIdFromContext,\n      });\n    } else if (parentMenuRootContext) {\n      store.update({\n        floatingNodeId: floatingNodeIdFromContext,\n        floatingParentNodeId: floatingParentNodeIdFromContext,\n      });\n    }\n  }, [\n    contextMenuContext,\n    parentMenuRootContext,\n    floatingNodeIdFromContext,\n    floatingParentNodeIdFromContext,\n    store,\n  ]);\n\n  const open = store.useState('open');\n  const activeTriggerElement = store.useState('activeTriggerElement');\n  const positionerElement = store.useState('positionerElement');\n  const hoverEnabled = store.useState('hoverEnabled');\n  const modal = store.useState('modal');\n  const disabled = store.useState('disabled');\n  const lastOpenChangeReason = store.useState('lastOpenChangeReason');\n  const parent = store.useState('parent');\n\n  const activeIndex = store.useState('activeIndex');\n  const payload = store.useState('payload') as Payload | undefined;\n  const floatingParentNodeId = store.useState('floatingParentNodeId');\n\n  const openEventRef = React.useRef<Event | null>(null);\n\n  const nested = floatingParentNodeId != null;\n\n  let floatingEvents: FloatingEvents;\n\n  if (process.env.NODE_ENV !== 'production') {\n    if (parent.type !== undefined && modalProp !== undefined) {\n      console.warn(\n        'Base UI: The `modal` prop is not supported on nested menus. It will be ignored.',\n      );\n    }\n  }\n\n  store.useSyncedValues({\n    disabled: disabledProp,\n    modal: parent.type === undefined ? modalProp : undefined,\n    rootId: useId(),\n  });\n\n  const { openMethod, triggerProps: interactionTypeProps } = useOpenInteractionType(open);\n\n  useImplicitActiveTrigger(store);\n  const { forceUnmount } = useOpenStateTransitions(open, store, () => {\n    store.update({ allowMouseEnter: false, stickIfOpen: true });\n  });\n\n  const allowOutsidePressDismissalRef = React.useRef(parent.type !== 'context-menu');\n  const allowOutsidePressDismissalTimeout = useTimeout();\n\n  React.useEffect(() => {\n    if (!open) {\n      openEventRef.current = null;\n    }\n\n    if (parent.type !== 'context-menu') {\n      return;\n    }\n\n    if (!open) {\n      allowOutsidePressDismissalTimeout.clear();\n      allowOutsidePressDismissalRef.current = false;\n      return;\n    }\n\n    // With `mousedown` outside press events and long press touch input, there\n    // needs to be a grace period after opening to ensure the dismissal event\n    // doesn't fire immediately after open.\n    allowOutsidePressDismissalTimeout.start(500, () => {\n      allowOutsidePressDismissalRef.current = true;\n    });\n  }, [allowOutsidePressDismissalTimeout, open, parent.type]);\n\n  useScrollLock(\n    open && modal && lastOpenChangeReason !== REASONS.triggerHover && openMethod !== 'touch',\n    positionerElement,\n  );\n\n  useIsoLayoutEffect(() => {\n    if (!open && !hoverEnabled) {\n      store.set('hoverEnabled', true);\n    }\n  }, [open, hoverEnabled, store]);\n\n  const allowTouchToCloseRef = React.useRef(true);\n  const allowTouchToCloseTimeout = useTimeout();\n\n  const setOpen = useStableCallback(\n    (\n      nextOpen: boolean,\n      eventDetails: Omit<MenuRoot.ChangeEventDetails, 'preventUnmountOnClose'>,\n    ) => {\n      const reason = eventDetails.reason;\n\n      if (\n        open === nextOpen &&\n        eventDetails.trigger === activeTriggerElement &&\n        lastOpenChangeReason === reason\n      ) {\n        return;\n      }\n\n      (eventDetails as MenuRoot.ChangeEventDetails).preventUnmountOnClose = () => {\n        store.set('preventUnmountingOnClose', true);\n      };\n\n      // Do not immediately reset the activeTriggerId to allow\n      // exit animations to play and focus to be returned correctly.\n      if (!nextOpen && eventDetails.trigger == null) {\n        eventDetails.trigger = activeTriggerElement ?? undefined;\n      }\n\n      onOpenChange?.(nextOpen, eventDetails as MenuRoot.ChangeEventDetails);\n\n      if (eventDetails.isCanceled) {\n        return;\n      }\n\n      const details: FloatingUIOpenChangeDetails = {\n        open: nextOpen,\n        nativeEvent: eventDetails.event,\n        reason: eventDetails.reason,\n        nested,\n      };\n\n      floatingEvents?.emit('openchange', details);\n\n      const nativeEvent = eventDetails.event as Event;\n      if (\n        nextOpen === false &&\n        nativeEvent?.type === 'click' &&\n        (nativeEvent as PointerEvent).pointerType === 'touch' &&\n        !allowTouchToCloseRef.current\n      ) {\n        return;\n      }\n\n      // Workaround `enableFocusInside` in Floating UI setting `tabindex=0` of a non-highlighted\n      // option upon close when tabbing out due to `keepMounted=true`:\n      // https://github.com/floating-ui/floating-ui/pull/3004/files#diff-962a7439cdeb09ea98d4b622a45d517bce07ad8c3f866e089bda05f4b0bbd875R194-R199\n      // This otherwise causes options to retain `tabindex=0` incorrectly when the popup is closed\n      // when tabbing outside.\n      if (!nextOpen && activeIndex !== null) {\n        const activeOption = store.context.itemDomElements.current[activeIndex];\n        // Wait for Floating UI's focus effect to have fired\n        queueMicrotask(() => {\n          activeOption?.setAttribute('tabindex', '-1');\n        });\n      }\n\n      // Prevent the menu from closing on mobile devices that have a delayed click event.\n      // In some cases the menu, when tapped, will fire the focus event first and then the click event.\n      // Without this guard, the menu will close immediately after opening.\n      if (nextOpen && reason === REASONS.triggerFocus) {\n        allowTouchToCloseRef.current = false;\n        allowTouchToCloseTimeout.start(300, () => {\n          allowTouchToCloseRef.current = true;\n        });\n      } else {\n        allowTouchToCloseRef.current = true;\n        allowTouchToCloseTimeout.clear();\n      }\n\n      const isKeyboardClick =\n        (reason === REASONS.triggerPress || reason === REASONS.itemPress) &&\n        (nativeEvent as MouseEvent).detail === 0 &&\n        nativeEvent?.isTrusted;\n      const isDismissClose = !nextOpen && (reason === REASONS.escapeKey || reason == null);\n\n      const updatedState: Partial<MenuStoreState<Payload>> = {\n        open: nextOpen,\n        openChangeReason: reason,\n      };\n      openEventRef.current = eventDetails.event ?? null;\n\n      // If a popup is closing, the `trigger` may be null.\n      // We want to keep the previous value so that exit animations are played and focus is returned correctly.\n      const newTriggerId = eventDetails.trigger?.id ?? null;\n      if (newTriggerId || nextOpen) {\n        updatedState.activeTriggerId = newTriggerId;\n        updatedState.activeTriggerElement = eventDetails.trigger ?? null;\n      }\n\n      store.update(updatedState);\n\n      if (\n        parent.type === 'menubar' &&\n        (reason === REASONS.triggerFocus ||\n          reason === REASONS.focusOut ||\n          reason === REASONS.triggerHover ||\n          reason === REASONS.listNavigation ||\n          reason === REASONS.siblingOpen)\n      ) {\n        store.set('instantType', 'group');\n      } else if (isKeyboardClick || isDismissClose) {\n        store.set('instantType', isKeyboardClick ? 'click' : 'dismiss');\n      } else {\n        store.set('instantType', undefined);\n      }\n    },\n  );\n\n  const createMenuEventDetails = React.useCallback(\n    (reason: MenuRoot.ChangeEventReason) => {\n      const details: MenuRoot.ChangeEventDetails =\n        createChangeEventDetails<MenuRoot.ChangeEventReason>(reason) as MenuRoot.ChangeEventDetails;\n      details.preventUnmountOnClose = () => {\n        store.set('preventUnmountingOnClose', true);\n      };\n\n      return details;\n    },\n    [store],\n  );\n\n  const handleImperativeClose = React.useCallback(() => {\n    store.setOpen(false, createMenuEventDetails(REASONS.imperativeAction));\n  }, [store, createMenuEventDetails]);\n\n  React.useImperativeHandle(\n    actionsRef,\n    () => ({ unmount: forceUnmount, close: handleImperativeClose }),\n    [forceUnmount, handleImperativeClose],\n  );\n\n  let ctx: ContextMenuRootContext | undefined;\n  if (parent.type === 'context-menu') {\n    ctx = parent.context;\n  }\n\n  React.useImperativeHandle<HTMLElement | null, HTMLElement | null>(\n    ctx?.positionerRef,\n    () => positionerElement,\n    [positionerElement],\n  );\n\n  React.useImperativeHandle(ctx?.actionsRef, () => ({ setOpen }), [setOpen]);\n\n  const floatingRootContext = useSyncedFloatingRootContext({\n    popupStore: store,\n    onOpenChange: setOpen,\n  });\n\n  floatingEvents = floatingRootContext.context.events;\n\n  React.useEffect(() => {\n    const handleSetOpenEvent = ({\n      open: nextOpen,\n      eventDetails,\n    }: {\n      open: boolean;\n      eventDetails: MenuRoot.ChangeEventDetails;\n    }) => setOpen(nextOpen, eventDetails);\n\n    floatingEvents.on('setOpen', handleSetOpenEvent);\n\n    return () => {\n      floatingEvents?.off('setOpen', handleSetOpenEvent);\n    };\n  }, [floatingEvents, setOpen]);\n\n  const dismiss = useDismiss(floatingRootContext, {\n    enabled: !disabled,\n    bubbles: { escapeKey: closeParentOnEsc && parent.type === 'menu' },\n    outsidePress() {\n      if (parent.type !== 'context-menu' || openEventRef.current?.type === 'contextmenu') {\n        return true;\n      }\n\n      return allowOutsidePressDismissalRef.current;\n    },\n    externalTree: nested ? floatingTreeRoot : undefined,\n  });\n\n  const role = useRole(floatingRootContext, {\n    role: 'menu',\n  });\n\n  const direction = useDirection();\n\n  const setActiveIndex = React.useCallback(\n    (index: number | null) => {\n      if (store.select('activeIndex') === index) {\n        return;\n      }\n      store.set('activeIndex', index);\n    },\n    [store],\n  );\n\n  const listNavigation = useListNavigation(floatingRootContext, {\n    enabled: !disabled,\n    listRef: store.context.itemDomElements,\n    activeIndex,\n    nested: parent.type !== undefined,\n    loopFocus,\n    orientation,\n    parentOrientation: parent.type === 'menubar' ? parent.context.orientation : undefined,\n    rtl: direction === 'rtl',\n    disabledIndices: EMPTY_ARRAY,\n    onNavigate: setActiveIndex,\n    openOnArrowKeyDown: parent.type !== 'context-menu',\n    externalTree: nested ? floatingTreeRoot : undefined,\n    focusItemOnHover: highlightItemOnHover,\n  });\n\n  const onTypingChange = React.useCallback(\n    (nextTyping: boolean) => {\n      store.context.typingRef.current = nextTyping;\n    },\n    [store],\n  );\n\n  const typeahead = useTypeahead(floatingRootContext, {\n    listRef: store.context.itemLabels,\n    elementsRef: store.context.itemDomElements,\n    activeIndex,\n    resetMs: TYPEAHEAD_RESET_MS,\n    onMatch: (index) => {\n      if (open && index !== activeIndex) {\n        store.set('activeIndex', index);\n      }\n    },\n    onTypingChange,\n  });\n\n  const { getReferenceProps, getFloatingProps, getItemProps, getTriggerProps } = useInteractions([\n    dismiss,\n    role,\n    listNavigation,\n    typeahead,\n  ]);\n\n  const activeTriggerProps = React.useMemo(() => {\n    const mergedProps = mergeProps(\n      getReferenceProps(),\n      {\n        onMouseMove() {\n          store.set('allowMouseEnter', true);\n        },\n      },\n      interactionTypeProps,\n    );\n\n    delete mergedProps.role;\n    return mergedProps;\n  }, [getReferenceProps, store, interactionTypeProps]);\n\n  const inactiveTriggerProps = React.useMemo(() => {\n    const triggerProps = getTriggerProps();\n    if (!triggerProps) {\n      return triggerProps;\n    }\n\n    const mergedProps = mergeProps(triggerProps, interactionTypeProps);\n    delete mergedProps.role;\n    delete mergedProps['aria-controls'];\n    return mergedProps;\n  }, [getTriggerProps, interactionTypeProps]);\n\n  const popupProps = React.useMemo(\n    () =>\n      getFloatingProps({\n        onMouseMove() {\n          store.set('allowMouseEnter', true);\n          if (parent.type === 'menu') {\n            store.set('hoverEnabled', false);\n          }\n        },\n        onClick() {\n          if (store.select('hoverEnabled')) {\n            store.set('hoverEnabled', false);\n          }\n        },\n        onKeyDown(event) {\n          // The Menubar's CompositeRoot captures keyboard events via\n          // event delegation. This works well when Menu.Root is nested inside Menubar,\n          // but with detached triggers we need to manually forward the event to the CompositeRoot.\n          const relay = store.select('keyboardEventRelay');\n          if (relay && !event.isPropagationStopped()) {\n            relay(event);\n          }\n        },\n      }),\n    [getFloatingProps, parent.type, store],\n  );\n\n  const itemProps = React.useMemo(() => getItemProps(), [getItemProps]);\n\n  store.useSyncedValues({\n    floatingRootContext,\n    activeTriggerProps,\n    inactiveTriggerProps,\n    popupProps,\n    itemProps,\n  });\n\n  const context: MenuRootContext<Payload> = React.useMemo(\n    () => ({\n      store,\n      parent: parentFromContext,\n    }),\n    [store, parentFromContext],\n  );\n\n  const content = (\n    <MenuRootContext.Provider value={context as MenuRootContext}>\n      {typeof children === 'function' ? children({ payload }) : children}\n    </MenuRootContext.Provider>\n  );\n\n  if (parent.type === undefined || parent.type === 'context-menu') {\n    // set up a FloatingTree to provide the context to nested menus\n    return <FloatingTree externalTree={floatingTreeRoot}>{content}</FloatingTree>;\n  }\n\n  return content;\n});\n\nexport interface MenuRootState {}\n\nexport interface MenuRootProps<Payload = unknown> {\n  /**\n   * Whether the menu is initially open.\n   *\n   * To render a controlled menu, use the `open` prop instead.\n   * @default false\n   */\n  defaultOpen?: boolean | undefined;\n  /**\n   * Whether to loop keyboard focus back to the first item\n   * when the end of the list is reached while using the arrow keys.\n   * @default true\n   */\n  loopFocus?: boolean | undefined;\n  /**\n   * Whether moving the pointer over items should highlight them.\n   * Disabling this prop allows CSS `:hover` to be differentiated from the `:focus` (`data-highlighted`) state.\n   * @default true\n   */\n  highlightItemOnHover?: boolean | undefined;\n  /**\n   * Determines if the menu enters a modal state when open.\n   * - `true`: user interaction is limited to the menu: document page scroll is locked and pointer interactions on outside elements are disabled.\n   * - `false`: user interaction with the rest of the document is allowed.\n   * @default true\n   */\n  modal?: boolean | undefined;\n  /**\n   * Event handler called when the menu is opened or closed.\n   */\n  onOpenChange?: ((open: boolean, eventDetails: MenuRoot.ChangeEventDetails) => void) | undefined;\n  /**\n   * Event handler called after any animations complete when the menu is closed.\n   */\n  onOpenChangeComplete?: ((open: boolean) => void) | undefined;\n  /**\n   * Whether the menu is currently open.\n   */\n  open?: boolean | undefined;\n  /**\n   * The visual orientation of the menu.\n   * Controls whether roving focus uses up/down or left/right arrow keys.\n   * @default 'vertical'\n   */\n  orientation?: MenuRoot.Orientation | undefined;\n  /**\n   * Whether the component should ignore user interaction.\n   * @default false\n   */\n  disabled?: boolean | undefined;\n  /**\n   * When in a submenu, determines whether pressing the Escape key\n   * closes the entire menu, or only the current child menu.\n   * @default false\n   */\n  closeParentOnEsc?: boolean | undefined;\n  /**\n   * A ref to imperative actions.\n   * - `unmount`: When specified, the menu will not be unmounted when closed.\n   *    Instead, the `unmount` function must be called to unmount the menu manually.\n   *   Useful when the menu's animation is controlled by an external library.\n   * - `close`: When specified, the menu can be closed imperatively.\n   */\n  actionsRef?: React.RefObject<MenuRoot.Actions | null> | undefined;\n  /**\n   * ID of the trigger that the popover is associated with.\n   * This is useful in conjunction with the `open` prop to create a controlled popover.\n   * There's no need to specify this prop when the popover is uncontrolled (i.e. when the `open` prop is not set).\n   */\n  triggerId?: string | null | undefined;\n  /**\n   * ID of the trigger that the popover is associated with.\n   * This is useful in conjunction with the `defaultOpen` prop to create an initially open popover.\n   */\n  defaultTriggerId?: string | null | undefined;\n  /**\n   * A handle to associate the menu with a trigger.\n   * If specified, allows external triggers to control the menu's open state.\n   */\n  handle?: MenuHandle<Payload> | undefined;\n  /**\n   * The content of the popover.\n   * This can be a regular React node or a render function that receives the `payload` of the active trigger.\n   */\n  children?: React.ReactNode | PayloadChildRenderFunction<Payload>;\n}\n\nexport interface MenuRootActions {\n  unmount: () => void;\n  close: () => void;\n}\n\nexport type MenuRootChangeEventReason =\n  | typeof REASONS.triggerHover\n  | typeof REASONS.triggerFocus\n  | typeof REASONS.triggerPress\n  | typeof REASONS.outsidePress\n  | typeof REASONS.focusOut\n  | typeof REASONS.listNavigation\n  | typeof REASONS.escapeKey\n  | typeof REASONS.itemPress\n  | typeof REASONS.closePress\n  | typeof REASONS.siblingOpen\n  | typeof REASONS.cancelOpen\n  | typeof REASONS.imperativeAction\n  | typeof REASONS.none;\n\nexport type MenuRootChangeEventDetails = BaseUIChangeEventDetails<MenuRoot.ChangeEventReason> & {\n  preventUnmountOnClose(): void;\n};\n\nexport type MenuRootOrientation = 'horizontal' | 'vertical';\n\nexport type MenuParent =\n  | {\n      type: 'menu';\n      store: MenuStore<unknown>;\n    }\n  | {\n      type: 'menubar';\n      context: MenubarContext;\n    }\n  | {\n      type: 'context-menu';\n      context: ContextMenuRootContext;\n    }\n  | {\n      type: 'nested-context-menu';\n      context: ContextMenuRootContext;\n      menuContext: MenuRootContext;\n    }\n  | {\n      type: undefined;\n    };\n\nexport namespace MenuRoot {\n  export type State = MenuRootState;\n  export type Props<Payload = unknown> = MenuRootProps<Payload>;\n  export type Actions = MenuRootActions;\n  export type ChangeEventReason = MenuRootChangeEventReason;\n  export type ChangeEventDetails = MenuRootChangeEventDetails;\n  export type Orientation = MenuRootOrientation;\n}\n"
  },
  {
    "path": "packages/react/src/menu/root/MenuRootContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { type MenuStore } from '../store/MenuStore';\nimport { MenuParent } from './MenuRoot';\n\nexport interface MenuRootContext<Payload = unknown> {\n  store: MenuStore<Payload>;\n  parent: MenuParent;\n}\n\nexport const MenuRootContext = React.createContext<MenuRootContext | undefined>(undefined);\n\nexport function useMenuRootContext(optional?: false): MenuRootContext;\nexport function useMenuRootContext(optional: true): MenuRootContext | undefined;\nexport function useMenuRootContext(optional?: boolean) {\n  const context = React.useContext(MenuRootContext);\n  if (context === undefined && !optional) {\n    throw new Error(\n      'Base UI: MenuRootContext is missing. Menu parts must be placed within <Menu.Root>.',\n    );\n  }\n\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/menu/store/MenuHandle.ts",
    "content": "import { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { MenuStore } from './MenuStore';\n\nexport class MenuHandle<Payload> {\n  /**\n   * Internal store holding the menu's state.\n   * @internal\n   */\n  public readonly store: MenuStore<Payload>;\n\n  constructor() {\n    this.store = new MenuStore<Payload>();\n  }\n\n  /**\n   * Opens the menu and associates it with the trigger with the given id.\n   * The trigger must be a Menu.Trigger component with this handle passed as a prop.\n   *\n   * @param triggerId ID of the trigger to associate with the menu.\n   */\n  open(triggerId: string) {\n    const triggerElement = triggerId\n      ? (this.store.context.triggerElements.getById(triggerId) as HTMLElement | undefined)\n      : undefined;\n\n    if (triggerId && !triggerElement) {\n      throw new Error(`Base UI: MenuHandle.open: No trigger found with id \"${triggerId}\".`);\n    }\n\n    this.store.setOpen(\n      true,\n      createChangeEventDetails('imperative-action', undefined, triggerElement),\n    );\n  }\n\n  /**\n   * Closes the menu.\n   */\n  close() {\n    this.store.setOpen(false, createChangeEventDetails('imperative-action', undefined, undefined));\n  }\n\n  /**\n   * Indicates whether the menu is currently open.\n   */\n  get isOpen() {\n    return this.store.state.open;\n  }\n}\n\n/**\n * Creates a new handle to connect a Menu.Root with detached Menu.Trigger components.\n */\nexport function createMenuHandle<Payload>(): MenuHandle<Payload> {\n  return new MenuHandle<Payload>();\n}\n"
  },
  {
    "path": "packages/react/src/menu/store/MenuStore.ts",
    "content": "import * as React from 'react';\nimport { createSelector, ReactStore } from '@base-ui/utils/store';\nimport { EMPTY_OBJECT } from '@base-ui/utils/empty';\nimport { useRefWithInit } from '@base-ui/utils/useRefWithInit';\nimport { MenuParent, MenuRoot } from '../root/MenuRoot';\nimport { FloatingTreeStore } from '../../floating-ui-react/components/FloatingTreeStore';\nimport { HTMLProps } from '../../utils/types';\nimport {\n  createInitialPopupStoreState,\n  PopupStoreContext,\n  popupStoreSelectors,\n  PopupStoreState,\n  PopupTriggerMap,\n} from '../../utils/popups';\n\nexport type State<Payload> = PopupStoreState<Payload> & {\n  disabled: boolean;\n  modal: boolean;\n  allowMouseEnter: boolean;\n  parent: MenuParent;\n  rootId: string | undefined;\n  activeIndex: number | null;\n  hoverEnabled: boolean;\n  stickIfOpen: boolean;\n  instantType: 'dismiss' | 'click' | 'group' | 'trigger-change' | undefined;\n  openChangeReason: MenuRoot.ChangeEventReason | null;\n  floatingTreeRoot: FloatingTreeStore;\n  floatingNodeId: string | undefined;\n  floatingParentNodeId: string | null;\n  itemProps: HTMLProps;\n  closeDelay: number;\n  keyboardEventRelay: ((event: React.KeyboardEvent<any>) => void) | undefined;\n  hasViewport: boolean;\n};\n\ntype Context = PopupStoreContext<MenuRoot.ChangeEventDetails> & {\n  readonly positionerRef: React.RefObject<HTMLElement | null>;\n  readonly popupRef: React.RefObject<HTMLElement | null>;\n  readonly typingRef: React.RefObject<boolean>;\n  readonly itemDomElements: React.RefObject<(HTMLElement | null)[]>;\n  readonly itemLabels: React.RefObject<(string | null)[]>;\n  allowMouseUpTriggerRef: React.RefObject<boolean>;\n  readonly triggerFocusTargetRef: React.RefObject<HTMLElement | null>;\n  readonly beforeContentFocusGuardRef: React.RefObject<HTMLElement | null>;\n};\n\nconst selectors = {\n  ...popupStoreSelectors,\n  disabled: createSelector((state: State<unknown>) =>\n    state.parent.type === 'menubar'\n      ? state.parent.context.disabled || state.disabled\n      : state.disabled,\n  ),\n  modal: createSelector(\n    (state: State<unknown>) =>\n      (state.parent.type === undefined || state.parent.type === 'context-menu') &&\n      (state.modal ?? true),\n  ),\n\n  allowMouseEnter: createSelector((state: State<unknown>) => state.allowMouseEnter),\n  stickIfOpen: createSelector((state: State<unknown>) => state.stickIfOpen),\n  parent: createSelector((state: State<unknown>) => state.parent),\n  rootId: createSelector((state: State<unknown>): string | undefined => {\n    if (state.parent.type === 'menu') {\n      return state.parent.store.select('rootId');\n    }\n\n    return state.parent.type !== undefined ? state.parent.context.rootId : state.rootId;\n  }),\n  activeIndex: createSelector((state: State<unknown>) => state.activeIndex),\n  isActive: createSelector(\n    (state: State<unknown>, itemIndex: number) => state.activeIndex === itemIndex,\n  ),\n  hoverEnabled: createSelector((state: State<unknown>) => state.hoverEnabled),\n  instantType: createSelector((state: State<unknown>) => state.instantType),\n  lastOpenChangeReason: createSelector((state: State<unknown>) => state.openChangeReason),\n  floatingTreeRoot: createSelector((state: State<unknown>): FloatingTreeStore => {\n    if (state.parent.type === 'menu') {\n      return state.parent.store.select('floatingTreeRoot');\n    }\n\n    return state.floatingTreeRoot;\n  }),\n  floatingNodeId: createSelector((state: State<unknown>) => state.floatingNodeId),\n  floatingParentNodeId: createSelector((state: State<unknown>) => state.floatingParentNodeId),\n  itemProps: createSelector((state: State<unknown>) => state.itemProps),\n  closeDelay: createSelector((state: State<unknown>) => state.closeDelay),\n  hasViewport: createSelector((state: State<unknown>) => state.hasViewport),\n  keyboardEventRelay: createSelector(\n    (state: State<unknown>): React.KeyboardEventHandler<any> | undefined => {\n      if (state.keyboardEventRelay) {\n        return state.keyboardEventRelay;\n      }\n\n      if (state.parent.type === 'menu') {\n        return state.parent.store.select('keyboardEventRelay');\n      }\n\n      return undefined;\n    },\n  ),\n};\n\nexport class MenuStore<Payload> extends ReactStore<\n  Readonly<State<Payload>>,\n  Context,\n  typeof selectors\n> {\n  constructor(initialState?: Partial<State<Payload>>) {\n    super(\n      { ...createInitialState(), ...initialState },\n      {\n        positionerRef: React.createRef<HTMLElement | null>(),\n        popupRef: React.createRef<HTMLElement | null>(),\n        typingRef: { current: false },\n        itemDomElements: { current: [] },\n        itemLabels: { current: [] },\n        allowMouseUpTriggerRef: { current: false },\n        triggerFocusTargetRef: React.createRef<HTMLElement>(),\n        beforeContentFocusGuardRef: React.createRef<HTMLElement>(),\n        onOpenChangeComplete: undefined,\n        triggerElements: new PopupTriggerMap(),\n      },\n      selectors,\n    );\n\n    // Set up propagation of state from parent menu if applicable.\n    this.unsubscribeParentListener = this.observe('parent', (parent) => {\n      this.unsubscribeParentListener?.();\n\n      if (parent.type === 'menu') {\n        let rootId = parent.store.select('rootId');\n        let floatingTreeRoot = parent.store.select('floatingTreeRoot');\n        let keyboardEventRelay = parent.store.select('keyboardEventRelay');\n\n        this.unsubscribeParentListener = parent.store.subscribe(() => {\n          const nextRootId = parent.store.select('rootId');\n          const nextFloatingTreeRoot = parent.store.select('floatingTreeRoot');\n          const nextKeyboardEventRelay = parent.store.select('keyboardEventRelay');\n\n          if (\n            rootId === nextRootId &&\n            floatingTreeRoot === nextFloatingTreeRoot &&\n            keyboardEventRelay === nextKeyboardEventRelay\n          ) {\n            return;\n          }\n\n          rootId = nextRootId;\n          floatingTreeRoot = nextFloatingTreeRoot;\n          keyboardEventRelay = nextKeyboardEventRelay;\n          this.notifyAll();\n        });\n\n        this.context.allowMouseUpTriggerRef = parent.store.context.allowMouseUpTriggerRef;\n        return;\n      }\n\n      if (parent.type !== undefined) {\n        this.context.allowMouseUpTriggerRef = parent.context.allowMouseUpTriggerRef;\n      }\n\n      this.unsubscribeParentListener = null;\n    });\n  }\n\n  setOpen(open: boolean, eventDetails: Omit<MenuRoot.ChangeEventDetails, 'preventUnmountOnClose'>) {\n    this.state.floatingRootContext.context.events.emit('setOpen', { open, eventDetails });\n  }\n\n  public static useStore<Payload>(\n    externalStore: MenuStore<Payload> | undefined,\n    initialState: Partial<State<Payload>>,\n  ) {\n    // eslint-disable-next-line react-hooks/rules-of-hooks\n    const internalStore = useRefWithInit(() => {\n      return new MenuStore<Payload>(initialState);\n    }).current;\n\n    return externalStore ?? internalStore;\n  }\n\n  private unsubscribeParentListener: (() => void) | null = null;\n}\n\nfunction createInitialState<Payload>(): State<Payload> {\n  return {\n    ...createInitialPopupStoreState(),\n    disabled: false,\n    modal: true,\n    allowMouseEnter: false,\n    stickIfOpen: true,\n    parent: {\n      type: undefined,\n    },\n    rootId: undefined,\n    activeIndex: null,\n    hoverEnabled: true,\n    instantType: undefined,\n    openChangeReason: null,\n    floatingTreeRoot: new FloatingTreeStore(),\n    floatingNodeId: undefined,\n    floatingParentNodeId: null,\n    itemProps: EMPTY_OBJECT as HTMLProps,\n    keyboardEventRelay: undefined,\n    closeDelay: 0,\n    hasViewport: false,\n  };\n}\n"
  },
  {
    "path": "packages/react/src/menu/submenu-root/MenuSubmenuRoot.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { MenuRoot } from '../root/MenuRoot';\nimport { useMenuRootContext } from '../root/MenuRootContext';\nimport { MenuSubmenuRootContext } from './MenuSubmenuRootContext';\n\nexport { useMenuSubmenuRootContext } from './MenuSubmenuRootContext';\n\n/**\n * Groups all parts of a submenu.\n * Doesn’t render its own HTML element.\n *\n * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)\n */\nexport function MenuSubmenuRoot(props: MenuSubmenuRoot.Props) {\n  const parentMenu = useMenuRootContext().store;\n\n  const contextValue = React.useMemo(() => ({ parentMenu }), [parentMenu]);\n\n  return (\n    <MenuSubmenuRootContext.Provider value={contextValue}>\n      <MenuRoot {...props} />\n    </MenuSubmenuRootContext.Provider>\n  );\n}\n\nexport interface MenuSubmenuRootProps extends Omit<\n  MenuRoot.Props,\n  'modal' | 'openOnHover' | 'onOpenChange'\n> {\n  /**\n   * Event handler called when the menu is opened or closed.\n   */\n  onOpenChange?:\n    | ((open: boolean, eventDetails: MenuSubmenuRoot.ChangeEventDetails) => void)\n    | undefined;\n  /**\n   * When in a submenu, determines whether pressing the Escape key\n   * closes the entire menu, or only the current child menu.\n   * @default false\n   */\n  closeParentOnEsc?: boolean | undefined;\n}\n\nexport interface MenuSubmenuRootState {}\n\nexport type MenuSubmenuRootChangeEventReason = MenuRoot.ChangeEventReason;\nexport type MenuSubmenuRootChangeEventDetails = MenuRoot.ChangeEventDetails;\n\nexport namespace MenuSubmenuRoot {\n  export type Props = MenuSubmenuRootProps;\n  export type State = MenuSubmenuRootState;\n  export type ChangeEventReason = MenuSubmenuRootChangeEventReason;\n  export type ChangeEventDetails = MenuSubmenuRootChangeEventDetails;\n}\n"
  },
  {
    "path": "packages/react/src/menu/submenu-root/MenuSubmenuRootContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { MenuStore } from '../store/MenuStore';\n\nexport const MenuSubmenuRootContext = React.createContext<MenuSubmenuRootContext | undefined>(\n  undefined,\n);\n\nexport interface MenuSubmenuRootContext {\n  parentMenu: MenuStore<unknown>;\n}\n\nexport function useMenuSubmenuRootContext(): MenuSubmenuRootContext | undefined {\n  return React.useContext(MenuSubmenuRootContext);\n}\n"
  },
  {
    "path": "packages/react/src/menu/submenu-trigger/MenuSubmenuTrigger.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { fireEvent, waitFor, screen } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance } from '#test-utils';\nimport { DirectionProvider } from '@base-ui/react/direction-provider';\nimport { Menu } from '@base-ui/react/menu';\n\ntype TextDirection = 'ltr' | 'rtl';\n\ndescribe('<Menu.SubmenuTrigger />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Menu.Trigger />, () => ({\n    refInstanceof: window.HTMLButtonElement,\n    testComponentPropWith: 'button',\n    button: true,\n    render(node) {\n      return render(\n        <Menu.Root open>\n          <Menu.SubmenuRoot>{node}</Menu.SubmenuRoot>\n        </Menu.Root>,\n      );\n    },\n  }));\n\n  function TestComponent({ direction = 'ltr' }: { direction: TextDirection }) {\n    return (\n      <DirectionProvider direction={direction}>\n        <Menu.Root open>\n          <Menu.Trigger>Open menu</Menu.Trigger>\n          <Menu.Portal>\n            <Menu.Positioner>\n              <Menu.Popup>\n                <Menu.Item>1</Menu.Item>\n                <Menu.SubmenuRoot>\n                  <Menu.SubmenuTrigger>2</Menu.SubmenuTrigger>\n                  <Menu.Portal>\n                    <Menu.Positioner>\n                      <Menu.Popup>\n                        <Menu.Item>2.1</Menu.Item>\n                        <Menu.Item>2.2</Menu.Item>\n                      </Menu.Popup>\n                    </Menu.Positioner>\n                  </Menu.Portal>\n                </Menu.SubmenuRoot>\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>\n      </DirectionProvider>\n    );\n  }\n\n  const testCases = [\n    { direction: 'ltr', openKey: 'ArrowRight', closeKey: 'ArrowLeft' },\n    { direction: 'rtl', openKey: 'ArrowLeft', closeKey: 'ArrowRight' },\n  ];\n\n  testCases.forEach(({ direction, openKey }) => {\n    it(`opens the submenu with ${openKey} and highlights a single item in ${direction.toUpperCase()} direction`, async () => {\n      await render(<TestComponent direction={direction as TextDirection} />);\n      const submenuTrigger = screen.getByText('2');\n\n      fireEvent.focus(submenuTrigger);\n      fireEvent.keyDown(submenuTrigger, { key: openKey });\n\n      const submenuItems = await screen.findAllByRole('menuitem');\n      const submenuItem1 = submenuItems.find((item) => item.textContent === '2.1');\n\n      await waitFor(() => {\n        expect(submenuItem1).toHaveFocus();\n      });\n\n      submenuItems.forEach((item) => {\n        if (item === submenuItem1) {\n          expect(item).toHaveAttribute('data-highlighted');\n        } else {\n          expect(item).not.toHaveAttribute('data-highlighted');\n        }\n      });\n\n      // Check that parent menu items are not active\n      const parentMenuItems = screen\n        .getAllByRole('menuitem')\n        .filter((item) => item.textContent !== '2.1' && item.textContent !== '2.2');\n      parentMenuItems.forEach((item) => {\n        expect(item).not.toHaveAttribute('data-highlighted');\n      });\n    });\n  });\n\n  it('sets tabIndex to 0 on the submenu trigger after opening the submenu with a keydown event', async () => {\n    await render(<TestComponent direction=\"ltr\" />);\n    const submenuTrigger = screen.getByText('2');\n\n    fireEvent.focus(submenuTrigger);\n    fireEvent.keyDown(submenuTrigger, { key: 'ArrowRight' });\n\n    await waitFor(() => {\n      expect(submenuTrigger).toHaveAttribute('tabIndex', '0');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/menu/submenu-trigger/MenuSubmenuTrigger.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport {\n  safePolygon,\n  useClick,\n  useHoverReferenceInteraction,\n  useInteractions,\n} from '../../floating-ui-react';\nimport { BaseUIComponentProps, NonNativeButtonProps } from '../../utils/types';\nimport { useMenuRootContext } from '../root/MenuRootContext';\nimport { useBaseUiId } from '../../utils/useBaseUiId';\nimport { triggerOpenStateMapping } from '../../utils/popupStateMapping';\nimport { useCompositeListItem } from '../../composite/list/useCompositeListItem';\nimport { useMenuItem } from '../item/useMenuItem';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useMenuPositionerContext } from '../positioner/MenuPositionerContext';\nimport { useTriggerRegistration } from '../../utils/popups';\nimport { useMenuSubmenuRootContext } from '../submenu-root/MenuSubmenuRootContext';\n\n/**\n * A menu item that opens a submenu.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)\n */\nexport const MenuSubmenuTrigger = React.forwardRef(function SubmenuTriggerComponent(\n  componentProps: MenuSubmenuTrigger.Props,\n  forwardedRef: React.ForwardedRef<HTMLElement>,\n) {\n  const {\n    render,\n    className,\n    label,\n    id: idProp,\n    nativeButton = false,\n    openOnHover = true,\n    delay = 100,\n    closeDelay = 0,\n    disabled: disabledProp = false,\n    ...elementProps\n  } = componentProps;\n\n  const listItem = useCompositeListItem();\n  const menuPositionerContext = useMenuPositionerContext();\n\n  const { store } = useMenuRootContext();\n\n  const thisTriggerId = useBaseUiId(idProp);\n  const open = store.useState('open');\n  const floatingRootContext = store.useState('floatingRootContext');\n  const floatingTreeRoot = store.useState('floatingTreeRoot');\n\n  const baseRegisterTrigger = useTriggerRegistration(thisTriggerId, store);\n  const registerTrigger = React.useCallback(\n    (element: Element | null) => {\n      const cleanup = baseRegisterTrigger(element);\n\n      if (element !== null && store.select('open') && store.select('activeTriggerId') == null) {\n        store.update({\n          activeTriggerId: thisTriggerId,\n          activeTriggerElement: element,\n          closeDelay,\n        });\n      }\n\n      return cleanup;\n    },\n    [baseRegisterTrigger, closeDelay, store, thisTriggerId],\n  );\n\n  const triggerElementRef = React.useRef<HTMLElement | null>(null);\n  const handleTriggerElementRef = React.useCallback(\n    (el: HTMLElement | null) => {\n      triggerElementRef.current = el;\n      store.set('activeTriggerElement', el);\n    },\n    [store],\n  );\n\n  const submenuRootContext = useMenuSubmenuRootContext();\n  if (!submenuRootContext?.parentMenu) {\n    throw new Error('Base UI: <Menu.SubmenuTrigger> must be placed in <Menu.SubmenuRoot>.');\n  }\n\n  store.useSyncedValue('closeDelay', closeDelay);\n\n  const parentMenuStore = submenuRootContext.parentMenu;\n\n  const itemProps = parentMenuStore.useState('itemProps');\n  const highlighted = parentMenuStore.useState('isActive', listItem.index);\n\n  const itemMetadata = React.useMemo(\n    () => ({\n      type: 'submenu-trigger' as const,\n      setActive() {\n        parentMenuStore.set('activeIndex', listItem.index);\n      },\n    }),\n    [parentMenuStore, listItem.index],\n  );\n\n  const rootDisabled = store.useState('disabled');\n  const disabled = disabledProp || rootDisabled;\n\n  const { getItemProps, itemRef } = useMenuItem({\n    closeOnClick: false,\n    disabled,\n    highlighted,\n    id: thisTriggerId,\n    store,\n    typingRef: parentMenuStore.context.typingRef,\n    nativeButton,\n    itemMetadata,\n    nodeId: menuPositionerContext?.nodeId,\n  });\n\n  const hoverEnabled = store.useState('hoverEnabled');\n  const allowMouseEnter = parentMenuStore.useState('allowMouseEnter');\n\n  const hoverProps = useHoverReferenceInteraction(floatingRootContext, {\n    enabled: hoverEnabled && openOnHover && !disabled,\n    handleClose: safePolygon({ blockPointerEvents: true }),\n    mouseOnly: true,\n    move: true,\n    restMs: delay,\n    delay: allowMouseEnter ? { open: delay, close: closeDelay } : 0,\n    triggerElementRef,\n    externalTree: floatingTreeRoot,\n  });\n\n  const click = useClick(floatingRootContext, {\n    enabled: !disabled,\n    event: 'mousedown',\n    toggle: !openOnHover,\n    ignoreMouse: openOnHover,\n    stickIfOpen: false,\n  });\n\n  const localInteractionProps = useInteractions([click]);\n\n  const rootTriggerProps = store.useState('triggerProps', true);\n  delete rootTriggerProps.id;\n\n  const state: MenuSubmenuTriggerState = { disabled, highlighted, open };\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    stateAttributesMapping: triggerOpenStateMapping,\n    props: [\n      localInteractionProps.getReferenceProps(),\n      hoverProps,\n      rootTriggerProps,\n      itemProps,\n      {\n        tabIndex: open || highlighted ? 0 : -1,\n        onBlur() {\n          if (highlighted) {\n            parentMenuStore.set('activeIndex', null);\n          }\n        },\n      },\n      elementProps,\n      getItemProps,\n    ],\n    ref: [forwardedRef, listItem.ref, itemRef, registerTrigger, handleTriggerElementRef],\n  });\n\n  return element;\n});\n\nexport interface MenuSubmenuTriggerState {\n  /**\n   * Whether the component should ignore user interaction.\n   */\n  disabled: boolean;\n  /**\n   * Whether the item is highlighted.\n   */\n  highlighted: boolean;\n  /**\n   * Whether the menu is currently open.\n   */\n  open: boolean;\n}\n\nexport interface MenuSubmenuTriggerProps\n  extends NonNativeButtonProps, BaseUIComponentProps<'div', MenuSubmenuTriggerState> {\n  onClick?: BaseUIComponentProps<'div', MenuSubmenuTriggerState>['onClick'] | undefined;\n  /**\n   * Overrides the text label to use when the item is matched during keyboard text navigation.\n   */\n  label?: string | undefined;\n  /**\n   * @ignore\n   */\n  id?: string | undefined;\n  /**\n   * Whether the component should ignore user interaction.\n   * @default false\n   */\n  disabled?: boolean | undefined;\n  /**\n   * How long to wait before the menu may be opened on hover. Specified in milliseconds.\n   *\n   * Requires the `openOnHover` prop.\n   * @default 100\n   */\n  delay?: number | undefined;\n  /**\n   * How long to wait before closing the menu that was opened on hover.\n   * Specified in milliseconds.\n   *\n   * Requires the `openOnHover` prop.\n   * @default 0\n   */\n  closeDelay?: number | undefined;\n  /**\n   * Whether the menu should also open when the trigger is hovered.\n   */\n  openOnHover?: boolean | undefined;\n}\n\nexport namespace MenuSubmenuTrigger {\n  export type Props = MenuSubmenuTriggerProps;\n  export type State = MenuSubmenuTriggerState;\n}\n"
  },
  {
    "path": "packages/react/src/menu/submenu-trigger/MenuSubmenuTriggerDataAttributes.ts",
    "content": "import { CommonTriggerDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum MenuSubmenuTriggerDataAttributes {\n  /**\n   * Present when the corresponding submenu is open.\n   */\n  popupOpen = CommonTriggerDataAttributes.popupOpen,\n  /**\n   * Present when the submenu trigger is highlighted.\n   */\n  highlighted = 'data-highlighted',\n  /**\n   * Present when the submenu trigger is disabled.\n   */\n  disabled = 'data-disabled',\n}\n"
  },
  {
    "path": "packages/react/src/menu/trigger/MenuTrigger.test.tsx",
    "content": "import { expect } from 'vitest';\nimport userEvent from '@testing-library/user-event';\nimport { act, fireEvent, flushMicrotasks, screen } from '@mui/internal-test-utils';\nimport { Menu } from '@base-ui/react/menu';\nimport { Popover } from '@base-ui/react/popover';\nimport { describeConformance, createRenderer } from '#test-utils';\nimport { PATIENT_CLICK_THRESHOLD } from '../../utils/constants';\n\ndescribe('<Menu.Trigger />', () => {\n  const { render } = createRenderer();\n  const user = userEvent.setup();\n\n  describeConformance(<Menu.Trigger />, () => ({\n    refInstanceof: window.HTMLButtonElement,\n    testComponentPropWith: 'button',\n    button: true,\n    render: (node) => {\n      return render(<Menu.Root open>{node}</Menu.Root>);\n    },\n  }));\n\n  describe('prop: disabled', () => {\n    it('should render a disabled button', async () => {\n      await render(\n        <Menu.Root>\n          <Menu.Trigger disabled />\n        </Menu.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      expect(button).toHaveProperty('disabled', true);\n    });\n\n    it('should not open the menu when clicked', async () => {\n      await render(\n        <Menu.Root>\n          <Menu.Trigger disabled />\n          <Menu.Portal>\n            <Menu.Positioner>\n              <Menu.Popup />\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      await user.click(button);\n\n      expect(screen.queryByRole('menu', { hidden: false })).toBe(null);\n    });\n  });\n\n  it('toggles the menu state when clicked', async () => {\n    await render(\n      <Menu.Root>\n        <Menu.Trigger>Open</Menu.Trigger>\n        <Menu.Portal>\n          <Menu.Positioner>\n            <Menu.Popup />\n          </Menu.Positioner>\n        </Menu.Portal>\n      </Menu.Root>,\n    );\n\n    const button = screen.getByRole('button', { name: 'Open' });\n    await user.click(button);\n\n    const menuPopup = await screen.findByRole('menu', { hidden: false });\n    expect(menuPopup).not.toBe(null);\n    expect(menuPopup).toHaveAttribute('data-open', '');\n  });\n\n  describe('keyboard navigation', () => {\n    [\n      <Menu.Trigger>Open</Menu.Trigger>,\n      <Menu.Trigger render={<span />} nativeButton={false}>\n        Open\n      </Menu.Trigger>,\n    ].forEach((buttonComponent) => {\n      const buttonType = buttonComponent.props.slots?.root ? 'non-native' : 'native';\n      ['ArrowUp', 'ArrowDown', 'Enter', ' '].forEach((key) => {\n        if (buttonType === 'native' && (key === ' ' || key === 'Enter')) {\n          return;\n        }\n\n        it(`opens the menu when pressing \"${key}\" on a ${buttonType} button`, async () => {\n          await render(\n            <Menu.Root>\n              {buttonComponent}\n              <Menu.Portal>\n                <Menu.Positioner>\n                  <Menu.Popup>\n                    <Menu.Item>1</Menu.Item>\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            </Menu.Root>,\n          );\n\n          const button = screen.getByRole('button', { name: 'Open' });\n          await act(async () => {\n            button.focus();\n          });\n\n          await user.keyboard(`[${key}]`);\n\n          const menuPopup = screen.queryByRole('menu', { hidden: false });\n          expect(menuPopup).not.toBe(null);\n        });\n      });\n    });\n  });\n\n  describe('accessibility attributes', () => {\n    it('has the aria-haspopup attribute', async () => {\n      await render(\n        <Menu.Root>\n          <Menu.Trigger />\n        </Menu.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      expect(button).toHaveAttribute('aria-haspopup');\n    });\n\n    it('has the aria-expanded=false attribute when closed', async () => {\n      await render(\n        <Menu.Root>\n          <Menu.Trigger />\n        </Menu.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      expect(button).toHaveAttribute('aria-expanded', 'false');\n    });\n\n    it('has the aria-expanded=true attribute when open', async () => {\n      await render(\n        <Menu.Root open>\n          <Menu.Trigger>Toggle</Menu.Trigger>\n        </Menu.Root>,\n      );\n\n      const button = screen.getByRole('button', { name: 'Toggle' });\n      expect(button).toHaveAttribute('aria-expanded', 'true');\n    });\n  });\n\n  describe('style hooks', () => {\n    it('should have the data-popup-open and data-pressed attributes when open', async () => {\n      await render(\n        <Menu.Root>\n          <Menu.Trigger />\n        </Menu.Root>,\n      );\n\n      const trigger = screen.getByRole('button');\n\n      await act(async () => {\n        trigger.click();\n      });\n\n      expect(trigger).toHaveAttribute('data-popup-open');\n      expect(trigger).toHaveAttribute('data-pressed');\n    });\n  });\n\n  describe('impatient clicks with `openOnHover=true`', () => {\n    const { clock, render: renderFakeTimers } = createRenderer();\n\n    clock.withFakeTimers();\n\n    it('does not close the menu if the user clicks too quickly', async () => {\n      await renderFakeTimers(\n        <Menu.Root>\n          <Menu.Trigger delay={0} openOnHover />\n        </Menu.Root>,\n      );\n\n      const trigger = screen.getByRole('button');\n\n      fireEvent.mouseMove(trigger);\n\n      clock.tick(PATIENT_CLICK_THRESHOLD - 1);\n\n      fireEvent.click(trigger);\n\n      expect(trigger).toHaveAttribute('data-popup-open');\n    });\n\n    it('closes the menu if the user clicks patiently', async () => {\n      await renderFakeTimers(\n        <Menu.Root>\n          <Menu.Trigger delay={0} openOnHover />\n          <Menu.Portal>\n            <Menu.Positioner>\n              <Menu.Popup />\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      const trigger = screen.getByRole('button');\n\n      fireEvent.mouseEnter(trigger);\n\n      clock.tick(PATIENT_CLICK_THRESHOLD);\n\n      fireEvent.click(trigger);\n\n      expect(trigger).not.toHaveAttribute('data-popup-open');\n    });\n\n    it('sticks if the user clicks impatiently', async () => {\n      await renderFakeTimers(\n        <Menu.Root>\n          <Menu.Trigger delay={0} openOnHover />\n        </Menu.Root>,\n      );\n\n      const trigger = screen.getByRole('button');\n\n      fireEvent.mouseEnter(trigger);\n\n      clock.tick(PATIENT_CLICK_THRESHOLD - 1);\n\n      fireEvent.click(trigger);\n      fireEvent.mouseLeave(trigger);\n\n      expect(trigger).toHaveAttribute('data-popup-open');\n\n      clock.tick(1);\n\n      expect(trigger).toHaveAttribute('data-popup-open');\n    });\n\n    it('does not stick if the user clicks patiently', async () => {\n      await renderFakeTimers(\n        <Menu.Root>\n          <Menu.Trigger delay={0} openOnHover />\n          <Menu.Portal>\n            <Menu.Positioner>\n              <Menu.Popup />\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      const trigger = screen.getByRole('button');\n\n      fireEvent.mouseEnter(trigger);\n\n      clock.tick(PATIENT_CLICK_THRESHOLD);\n\n      fireEvent.click(trigger);\n      fireEvent.mouseLeave(trigger);\n\n      expect(trigger).not.toHaveAttribute('data-popup-open');\n    });\n\n    it('sticks when clicked before the hover delay completes', async () => {\n      await renderFakeTimers(\n        <Menu.Root>\n          <Menu.Trigger openOnHover delay={300}>\n            Open\n          </Menu.Trigger>\n          <Menu.Portal>\n            <Menu.Positioner>\n              <Menu.Popup>Content</Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      const trigger = screen.getByRole('button');\n\n      fireEvent.mouseEnter(trigger);\n      fireEvent.mouseMove(trigger);\n\n      clock.tick(100);\n\n      // User clicks impatiently to open\n      fireEvent.click(trigger);\n\n      expect(trigger).toHaveAttribute('data-popup-open');\n\n      fireEvent.mouseLeave(trigger);\n\n      expect(trigger).toHaveAttribute('data-popup-open');\n    });\n\n    it('should keep the menu open when re-hovered and clicked within the patient threshold', async () => {\n      await render(\n        <Menu.Root>\n          <Menu.Trigger openOnHover delay={100}>\n            Open\n          </Menu.Trigger>\n          <Menu.Portal>\n            <Menu.Positioner>\n              <Menu.Popup>Content</Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      const trigger = screen.getByRole('button');\n\n      fireEvent.mouseEnter(trigger);\n      fireEvent.mouseMove(trigger);\n\n      clock.tick(100);\n      await flushMicrotasks();\n\n      expect(screen.getByText('Content')).not.toBe(null);\n\n      clock.tick(PATIENT_CLICK_THRESHOLD);\n\n      fireEvent.mouseLeave(trigger);\n      fireEvent.mouseEnter(trigger);\n      fireEvent.mouseMove(trigger);\n\n      fireEvent.click(trigger);\n      expect(screen.getByText('Content')).not.toBe(null);\n    });\n  });\n\n  describe('preventBaseUIHandler', () => {\n    it('prevents opening the menu with a mouse when `preventBaseUIHandler` is called in onMouseDown', async () => {\n      await render(\n        <Menu.Root>\n          <Menu.Trigger onMouseDown={(event) => event.preventBaseUIHandler()} />\n          <Menu.Portal>\n            <Menu.Positioner>\n              <Menu.Popup />\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      await user.click(button);\n\n      expect(screen.queryByRole('menu', { hidden: false })).toBe(null);\n    });\n\n    it('prevents opening the menu with keyboard when `preventBaseUIHandler` is called in onClick', async () => {\n      await render(\n        <Menu.Root>\n          <Menu.Trigger onClick={(event) => event.preventBaseUIHandler()} />\n          <Menu.Portal>\n            <Menu.Positioner>\n              <Menu.Popup />\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      await act(async () => {\n        button.focus();\n      });\n\n      await user.keyboard('[Enter]');\n\n      expect(screen.queryByRole('menu', { hidden: false })).toBe(null);\n    });\n  });\n\n  it('does not have role prop inside a Popover', async () => {\n    await render(\n      <Popover.Root open>\n        <Popover.Trigger>Open</Popover.Trigger>\n        <Popover.Portal>\n          <Popover.Positioner>\n            <Popover.Popup>\n              <Menu.Root>\n                <Menu.Trigger data-testid=\"menu-trigger\" />\n              </Menu.Root>\n            </Popover.Popup>\n          </Popover.Positioner>\n        </Popover.Portal>\n      </Popover.Root>,\n    );\n\n    const button = screen.getByTestId('menu-trigger');\n    expect(button).not.toHaveAttribute('role');\n  });\n\n  it('has a role prop inside a Popover when not a native button', async () => {\n    await render(\n      <Popover.Root open>\n        <Popover.Trigger>Open</Popover.Trigger>\n        <Popover.Portal>\n          <Popover.Positioner>\n            <Popover.Popup>\n              <Menu.Root>\n                <Menu.Trigger data-testid=\"menu-trigger\" render={<span />} nativeButton={false} />\n              </Menu.Root>\n            </Popover.Popup>\n          </Popover.Positioner>\n        </Popover.Portal>\n      </Popover.Root>,\n    );\n\n    const button = screen.getByTestId('menu-trigger');\n    expect(button).toHaveAttribute('role', 'button');\n  });\n});\n"
  },
  {
    "path": "packages/react/src/menu/trigger/MenuTrigger.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport { FocusableElement } from 'tabbable';\nimport { useTimeout } from '@base-ui/utils/useTimeout';\nimport { ownerDocument } from '@base-ui/utils/owner';\nimport { fastComponentRef } from '@base-ui/utils/fastHooks';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { EMPTY_OBJECT } from '@base-ui/utils/empty';\nimport {\n  safePolygon,\n  useClick,\n  useFloatingTree,\n  useFocus,\n  useHoverReferenceInteraction,\n  useInteractions,\n  useFloatingNodeId,\n  useFloatingParentNodeId,\n} from '../../floating-ui-react';\nimport { FloatingTreeStore } from '../../floating-ui-react/components/FloatingTreeStore';\nimport {\n  contains,\n  getNextTabbable,\n  getTabbableAfterElement,\n  getTabbableBeforeElement,\n  isOutsideEvent,\n} from '../../floating-ui-react/utils';\nimport { useMenuRootContext } from '../root/MenuRootContext';\nimport { pressableTriggerOpenStateMapping } from '../../utils/popupStateMapping';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { BaseUIComponentProps, NativeButtonProps } from '../../utils/types';\nimport { useButton } from '../../use-button/useButton';\nimport { getPseudoElementBounds } from '../../utils/getPseudoElementBounds';\nimport { CompositeItem } from '../../composite/item/CompositeItem';\nimport { useCompositeRootContext } from '../../composite/root/CompositeRootContext';\nimport { findRootOwnerId } from '../utils/findRootOwnerId';\nimport { useTriggerDataForwarding } from '../../utils/popups';\nimport { useBaseUiId } from '../../utils/useBaseUiId';\nimport { REASONS } from '../../utils/reasons';\nimport { useMixedToggleClickHandler } from '../../utils/useMixedToggleClickHandler';\nimport { MenuHandle } from '../store/MenuHandle';\nimport { useContextMenuRootContext } from '../../context-menu/root/ContextMenuRootContext';\nimport { useMenubarContext } from '../../menubar/MenubarContext';\nimport { MenuParent } from '../root/MenuRoot';\nimport { PATIENT_CLICK_THRESHOLD } from '../../utils/constants';\nimport { FocusGuard } from '../../utils/FocusGuard';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\n\nconst BOUNDARY_OFFSET = 2;\n\n/**\n * A button that opens the menu.\n * Renders a `<button>` element.\n *\n * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)\n */\nexport const MenuTrigger = fastComponentRef(function MenuTrigger(\n  componentProps: MenuTrigger.Props,\n  forwardedRef: React.ForwardedRef<HTMLElement>,\n) {\n  const {\n    render,\n    className,\n    disabled: disabledProp = false,\n    nativeButton = true,\n    id: idProp,\n    openOnHover: openOnHoverProp,\n    delay = 100,\n    closeDelay = 0,\n    handle,\n    payload,\n    ...elementProps\n  } = componentProps;\n\n  const rootContext = useMenuRootContext(true);\n  const store = handle?.store ?? rootContext?.store;\n  if (!store) {\n    throw new Error(\n      'Base UI: <Menu.Trigger> must be either used within a <Menu.Root> component or provided with a handle.',\n    );\n  }\n\n  const thisTriggerId = useBaseUiId(idProp);\n  const isTriggerActive = store.useState('isTriggerActive', thisTriggerId);\n  const floatingRootContext = store.useState('floatingRootContext');\n  const isOpenedByThisTrigger = store.useState('isOpenedByTrigger', thisTriggerId);\n\n  const triggerElementRef = React.useRef<HTMLElement | null>(null);\n\n  const parent = useMenuParent();\n  const compositeRootContext = useCompositeRootContext(true);\n  const floatingTreeRootFromContext = useFloatingTree();\n  const floatingTreeRoot: FloatingTreeStore = React.useMemo(() => {\n    return floatingTreeRootFromContext ?? new FloatingTreeStore();\n  }, [floatingTreeRootFromContext]);\n\n  const floatingNodeId = useFloatingNodeId(floatingTreeRoot);\n  const floatingParentNodeId = useFloatingParentNodeId();\n\n  const { registerTrigger, isMountedByThisTrigger } = useTriggerDataForwarding(\n    thisTriggerId,\n    triggerElementRef,\n    store,\n    {\n      payload,\n      closeDelay,\n      parent,\n      floatingTreeRoot,\n      floatingNodeId,\n      floatingParentNodeId,\n      keyboardEventRelay: compositeRootContext?.relayKeyboardEvent,\n    },\n  );\n\n  const isInMenubar = parent.type === 'menubar';\n\n  const rootDisabled = store.useState('disabled');\n  const disabled = disabledProp || rootDisabled || (isInMenubar && parent.context.disabled);\n\n  const { getButtonProps, buttonRef } = useButton({\n    disabled,\n    native: nativeButton,\n  });\n\n  React.useEffect(() => {\n    if (!isOpenedByThisTrigger && parent.type === undefined) {\n      store.context.allowMouseUpTriggerRef.current = false;\n    }\n  }, [store, isOpenedByThisTrigger, parent.type]);\n\n  const triggerRef = React.useRef<HTMLElement | null>(null);\n  const allowMouseUpTriggerTimeout = useTimeout();\n\n  const handleDocumentMouseUp = useStableCallback((mouseEvent: MouseEvent) => {\n    if (!triggerRef.current) {\n      return;\n    }\n\n    allowMouseUpTriggerTimeout.clear();\n    store.context.allowMouseUpTriggerRef.current = false;\n\n    const mouseUpTarget = mouseEvent.target as Element | null;\n\n    if (\n      contains(triggerRef.current, mouseUpTarget) ||\n      contains(store.select('positionerElement'), mouseUpTarget) ||\n      mouseUpTarget === triggerRef.current\n    ) {\n      return;\n    }\n\n    if (mouseUpTarget != null && findRootOwnerId(mouseUpTarget) === store.select('rootId')) {\n      return;\n    }\n\n    const bounds = getPseudoElementBounds(triggerRef.current);\n\n    if (\n      mouseEvent.clientX >= bounds.left - BOUNDARY_OFFSET &&\n      mouseEvent.clientX <= bounds.right + BOUNDARY_OFFSET &&\n      mouseEvent.clientY >= bounds.top - BOUNDARY_OFFSET &&\n      mouseEvent.clientY <= bounds.bottom + BOUNDARY_OFFSET\n    ) {\n      return;\n    }\n\n    floatingTreeRoot.events.emit('close', { domEvent: mouseEvent, reason: REASONS.cancelOpen });\n  });\n\n  React.useEffect(() => {\n    if (isOpenedByThisTrigger && store.select('lastOpenChangeReason') === REASONS.triggerHover) {\n      const doc = ownerDocument(triggerRef.current);\n      doc.addEventListener('mouseup', handleDocumentMouseUp, { once: true });\n    }\n  }, [isOpenedByThisTrigger, handleDocumentMouseUp, store]);\n\n  const parentMenubarHasSubmenuOpen = isInMenubar && parent.context.hasSubmenuOpen;\n  const openOnHover = openOnHoverProp ?? parentMenubarHasSubmenuOpen;\n\n  const hoverProps = useHoverReferenceInteraction(floatingRootContext, {\n    enabled:\n      openOnHover &&\n      !disabled &&\n      parent.type !== 'context-menu' &&\n      (!isInMenubar || (parentMenubarHasSubmenuOpen && !isMountedByThisTrigger)),\n    handleClose: safePolygon({ blockPointerEvents: !isInMenubar }),\n    mouseOnly: true,\n    move: false,\n    restMs: parent.type === undefined ? delay : undefined,\n    delay: { close: closeDelay },\n    triggerElementRef,\n    externalTree: floatingTreeRoot,\n    isActiveTrigger: isTriggerActive,\n  });\n\n  // Whether to ignore clicks to open the menu.\n  // `lastOpenChangeReason` doesn't need to be reactive here, as we need to run this\n  // only when `isOpenedByThisTrigger` changes.\n  const stickIfOpen = useStickIfOpen(isOpenedByThisTrigger, store.select('lastOpenChangeReason'));\n\n  const click = useClick(floatingRootContext, {\n    enabled: !disabled && parent.type !== 'context-menu',\n    event: isOpenedByThisTrigger && isInMenubar ? 'click' : 'mousedown',\n    toggle: true,\n    ignoreMouse: false,\n    stickIfOpen: parent.type === undefined ? stickIfOpen : false,\n  });\n\n  const focus = useFocus(floatingRootContext, {\n    enabled: !disabled && parentMenubarHasSubmenuOpen,\n  });\n\n  const mixedToggleHandlers = useMixedToggleClickHandler({\n    open: isOpenedByThisTrigger,\n    enabled: isInMenubar,\n    mouseDownAction: 'open',\n  });\n\n  const localInteractionProps = useInteractions([click, focus]);\n\n  const state: MenuTriggerState = {\n    disabled,\n    open: isOpenedByThisTrigger,\n  };\n\n  const rootTriggerProps = store.useState('triggerProps', isMountedByThisTrigger);\n\n  const ref = [triggerRef, forwardedRef, buttonRef, registerTrigger, triggerElementRef];\n  const props = [\n    localInteractionProps.getReferenceProps(),\n    hoverProps ?? EMPTY_OBJECT,\n    rootTriggerProps,\n    {\n      'aria-haspopup': 'menu' as const,\n      id: thisTriggerId,\n      onMouseDown: (event: React.MouseEvent) => {\n        if (store.select('open')) {\n          return;\n        }\n\n        // mousedown -> mouseup on menu item should not trigger it within 200ms.\n        allowMouseUpTriggerTimeout.start(200, () => {\n          store.context.allowMouseUpTriggerRef.current = true;\n        });\n\n        const doc = ownerDocument(event.currentTarget);\n        doc.addEventListener('mouseup', handleDocumentMouseUp, { once: true });\n      },\n    },\n    isInMenubar ? { role: 'menuitem' } : {},\n    mixedToggleHandlers,\n    elementProps,\n    getButtonProps,\n  ];\n\n  const preFocusGuardRef = React.useRef<HTMLElement>(null);\n\n  const handlePreFocusGuardFocus = useStableCallback((event: React.FocusEvent) => {\n    ReactDOM.flushSync(() => {\n      store.setOpen(\n        false,\n        createChangeEventDetails(\n          REASONS.focusOut,\n          event.nativeEvent,\n          event.currentTarget as HTMLElement,\n        ),\n      );\n    });\n\n    const previousTabbable: FocusableElement | null = getTabbableBeforeElement(\n      preFocusGuardRef.current,\n    );\n    previousTabbable?.focus();\n  });\n\n  const handleFocusTargetFocus = useStableCallback((event: React.FocusEvent) => {\n    const currentPositionerElement = store.select('positionerElement');\n    if (currentPositionerElement && isOutsideEvent(event, currentPositionerElement)) {\n      store.context.beforeContentFocusGuardRef.current?.focus();\n    } else {\n      ReactDOM.flushSync(() => {\n        store.setOpen(\n          false,\n          createChangeEventDetails(\n            REASONS.focusOut,\n            event.nativeEvent,\n            event.currentTarget as HTMLElement,\n          ),\n        );\n      });\n\n      let nextTabbable = getTabbableAfterElement(\n        store.context.triggerFocusTargetRef.current || triggerElementRef.current,\n      );\n\n      while (nextTabbable !== null && contains(currentPositionerElement, nextTabbable)) {\n        const prevTabbable = nextTabbable;\n        nextTabbable = getNextTabbable(nextTabbable);\n        if (nextTabbable === prevTabbable) {\n          break;\n        }\n      }\n\n      nextTabbable?.focus();\n    }\n  });\n\n  const element = useRenderElement('button', componentProps, {\n    enabled: !isInMenubar,\n    stateAttributesMapping: pressableTriggerOpenStateMapping,\n    state,\n    ref,\n    props,\n  });\n\n  if (isInMenubar) {\n    return (\n      <CompositeItem\n        tag=\"button\"\n        render={render}\n        className={className}\n        state={state}\n        refs={ref}\n        props={props}\n        stateAttributesMapping={pressableTriggerOpenStateMapping}\n      />\n    );\n  }\n\n  // A fragment with key is required to ensure that the `element` is mounted to the same DOM node\n  // regardless of whether the focus guards are rendered or not.\n\n  if (isOpenedByThisTrigger) {\n    return (\n      <React.Fragment>\n        <FocusGuard\n          ref={preFocusGuardRef}\n          onFocus={handlePreFocusGuardFocus}\n          key={`${thisTriggerId}-pre-focus-guard`}\n        />\n        <React.Fragment key={thisTriggerId}>{element}</React.Fragment>\n        <FocusGuard\n          ref={store.context.triggerFocusTargetRef}\n          onFocus={handleFocusTargetFocus}\n          key={`${thisTriggerId}-post-focus-guard`}\n        />\n      </React.Fragment>\n    );\n  }\n\n  return <React.Fragment key={thisTriggerId}>{element}</React.Fragment>;\n}) as MenuTrigger;\n\nexport interface MenuTrigger {\n  <Payload>(\n    componentProps: MenuTriggerProps<Payload> & React.RefAttributes<HTMLElement>,\n  ): React.JSX.Element;\n}\n\nexport interface MenuTriggerProps<Payload = unknown>\n  extends NativeButtonProps, BaseUIComponentProps<'button', MenuTriggerState> {\n  children?: React.ReactNode;\n  /**\n   * Whether the component should ignore user interaction.\n   * @default false\n   */\n  disabled?: boolean | undefined;\n  /**\n   * A handle to associate the trigger with a menu.\n   */\n  handle?: MenuHandle<Payload> | undefined;\n  /**\n   * A payload to pass to the menu when it is opened.\n   */\n  payload?: Payload | undefined;\n  /**\n   * How long to wait before the menu may be opened on hover. Specified in milliseconds.\n   *\n   * Requires the `openOnHover` prop.\n   * @default 100\n   */\n  delay?: number | undefined;\n  /**\n   * How long to wait before closing the menu that was opened on hover.\n   * Specified in milliseconds.\n   *\n   * Requires the `openOnHover` prop.\n   * @default 0\n   */\n  closeDelay?: number | undefined;\n  /**\n   * Whether the menu should also open when the trigger is hovered.\n   */\n  openOnHover?: boolean | undefined;\n}\n\nexport interface MenuTriggerState {\n  /**\n   * Whether the menu is currently open.\n   */\n  open: boolean;\n  /**\n   * Whether the trigger is disabled.\n   */\n  disabled: boolean;\n}\n\nexport namespace MenuTrigger {\n  export type Props<Payload = unknown> = MenuTriggerProps<Payload>;\n  export type State = MenuTriggerState;\n}\n\n/**\n * Determines whether to ignore clicks after a hover-open.\n */\nfunction useStickIfOpen(open: boolean, openReason: string | null) {\n  const stickIfOpenTimeout = useTimeout();\n  const [stickIfOpen, setStickIfOpen] = React.useState(false);\n  useIsoLayoutEffect(() => {\n    if (open && openReason === 'trigger-hover') {\n      // Only allow \"patient\" clicks to close the menu if it's open.\n      // If they clicked within 500ms of the menu opening, keep it open.\n      setStickIfOpen(true);\n      stickIfOpenTimeout.start(PATIENT_CLICK_THRESHOLD, () => {\n        setStickIfOpen(false);\n      });\n    } else if (!open) {\n      stickIfOpenTimeout.clear();\n      setStickIfOpen(false);\n    }\n  }, [open, openReason, stickIfOpenTimeout]);\n\n  return stickIfOpen;\n}\n\nfunction useMenuParent() {\n  const contextMenuContext = useContextMenuRootContext(true);\n  const parentContext = useMenuRootContext(true);\n  const menubarContext = useMenubarContext(true);\n\n  const parent: MenuParent = React.useMemo(() => {\n    if (menubarContext) {\n      return {\n        type: 'menubar',\n        context: menubarContext,\n      };\n    }\n\n    // Ensure this is not a Menu nested inside ContextMenu.Trigger.\n    // ContextMenu parentContext is always undefined as ContextMenu.Root is instantiated with\n    // <MenuRootContext.Provider value={undefined}>\n    if (contextMenuContext && !parentContext) {\n      return {\n        type: 'context-menu',\n        context: contextMenuContext,\n      };\n    }\n\n    return {\n      type: undefined,\n    };\n  }, [contextMenuContext, parentContext, menubarContext]);\n\n  return parent;\n}\n"
  },
  {
    "path": "packages/react/src/menu/trigger/MenuTriggerDataAttributes.ts",
    "content": "import { CommonTriggerDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum MenuTriggerDataAttributes {\n  /**\n   * Present when the corresponding menu is open.\n   */\n  popupOpen = CommonTriggerDataAttributes.popupOpen,\n  /**\n   * Present when the trigger is pressed.\n   */\n  pressed = CommonTriggerDataAttributes.pressed,\n}\n"
  },
  {
    "path": "packages/react/src/menu/utils/findRootOwnerId.ts",
    "content": "import { getParentNode, isHTMLElement, isLastTraversableNode } from '@floating-ui/utils/dom';\n\nexport function findRootOwnerId(node: Node): string | undefined {\n  if (isHTMLElement(node) && node.hasAttribute('data-rootownerid')) {\n    return node.getAttribute('data-rootownerid') ?? undefined;\n  }\n\n  if (isLastTraversableNode(node)) {\n    return undefined;\n  }\n\n  return findRootOwnerId(getParentNode(node));\n}\n"
  },
  {
    "path": "packages/react/src/menu/utils/stateAttributesMapping.ts",
    "content": "import type { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport { transitionStatusMapping } from '../../utils/stateAttributesMapping';\nimport { MenuCheckboxItemDataAttributes } from '../checkbox-item/MenuCheckboxItemDataAttributes';\n\nexport const itemMapping: StateAttributesMapping<{ checked: boolean }> = {\n  checked(value): Record<string, string> {\n    if (value) {\n      return {\n        [MenuCheckboxItemDataAttributes.checked]: '',\n      };\n    }\n    return {\n      [MenuCheckboxItemDataAttributes.unchecked]: '',\n    };\n  },\n  ...transitionStatusMapping,\n};\n"
  },
  {
    "path": "packages/react/src/menu/utils/types.ts",
    "content": "import type { MenuRoot } from '../root/MenuRoot';\n\nexport interface MenuOpenEventDetails {\n  open: boolean;\n  reason: MenuRoot.ChangeEventReason | null;\n  nodeId: string | undefined;\n  parentNodeId: string | null;\n}\n"
  },
  {
    "path": "packages/react/src/menu/viewport/MenuViewport.test.tsx",
    "content": "import { expect } from 'vitest';\nimport * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\nimport { screen, waitFor } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\n\ndescribe('<Menu.Viewport />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Menu.Viewport />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <Menu.Root open>\n          <Menu.Trigger>Trigger</Menu.Trigger>\n          <Menu.Portal>\n            <Menu.Positioner>\n              <Menu.Popup>{node}</Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.Root>,\n      );\n    },\n  }));\n\n  it('should render children in the `current` container by default', async () => {\n    await render(\n      <Menu.Root open>\n        <Menu.Trigger>Trigger</Menu.Trigger>\n        <Menu.Portal>\n          <Menu.Positioner>\n            <Menu.Popup>\n              <Menu.Viewport>\n                <div data-testid=\"content\">Content</div>\n              </Menu.Viewport>\n            </Menu.Popup>\n          </Menu.Positioner>\n        </Menu.Portal>\n      </Menu.Root>,\n    );\n\n    const currentContainer = screen.getByTestId('content').closest('[data-current]');\n    expect(currentContainer).not.toBe(null);\n    expect(currentContainer!.textContent).toBe('Content');\n  });\n\n  it('should remount the `current` container when the active trigger changes', async () => {\n    const { user } = await render(\n      <Menu.Root>\n        {({ payload }) => (\n          <React.Fragment>\n            <Menu.Trigger payload=\"first\" data-testid=\"trigger1\">\n              Trigger 1\n            </Menu.Trigger>\n            <Menu.Trigger payload=\"second\" data-testid=\"trigger2\">\n              Trigger 2\n            </Menu.Trigger>\n            <Menu.Portal>\n              <Menu.Positioner>\n                <Menu.Popup>\n                  <Menu.Viewport>\n                    {payload === 'first' ? (\n                      <img data-testid=\"payload-image-1\" src=\"about:blank\" alt=\"Preview 1\" />\n                    ) : null}\n                    {payload === 'second' ? (\n                      <img data-testid=\"payload-image-2\" src=\"about:blank\" alt=\"Preview 2\" />\n                    ) : null}\n                  </Menu.Viewport>\n                </Menu.Popup>\n              </Menu.Positioner>\n            </Menu.Portal>\n          </React.Fragment>\n        )}\n      </Menu.Root>,\n    );\n\n    const trigger1 = screen.getByTestId('trigger1');\n    const trigger2 = screen.getByTestId('trigger2');\n\n    await user.click(trigger1);\n\n    const firstImage = await screen.findByTestId('payload-image-1');\n    const firstContainer = firstImage.closest('[data-current]');\n    expect(firstContainer).not.toBe(null);\n\n    await user.click(trigger2);\n\n    await waitFor(() => {\n      const secondImage = screen.getByTestId('payload-image-2');\n      const secondContainer = secondImage.closest('[data-current]');\n      expect(secondContainer).not.toBe(null);\n      expect(secondContainer).not.toBe(firstContainer);\n    });\n  });\n\n  describe.skipIf(isJSDOM)('morphing containers with multiple triggers and payloads', () => {\n    beforeEach(() => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n    });\n\n    afterEach(() => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = true;\n    });\n\n    it('should create morphing containers during transitions', async () => {\n      const { user } = await render(\n        <div>\n          <style>\n            {`\n              [data-transitioning] [data-previous] {\n                animation: slide-out 0.3s ease-out forwards;\n              }\n              [data-transitioning] [data-current] {\n                animation: slide-in 0.3s ease-out forwards;\n              }\n              @keyframes slide-out {\n                from { transform: translateX(0); opacity: 1; }\n                to { transform: translateX(-30%); opacity: 0; }\n              }\n              @keyframes slide-in {\n                from { transform: translateX(30%); opacity: 0; }\n                to { transform: translateX(0); opacity: 1; }\n              }\n            `}\n          </style>\n          <Menu.Root>\n            {({ payload }) => (\n              <React.Fragment>\n                <Menu.Trigger\n                  payload={0}\n                  data-testid=\"trigger1\"\n                  style={{\n                    position: 'absolute',\n                    top: '10px',\n                    left: '10px',\n                    width: '100px',\n                    height: '50px',\n                  }}\n                >\n                  Trigger 1\n                </Menu.Trigger>\n                <Menu.Trigger\n                  payload={1}\n                  data-testid=\"trigger2\"\n                  style={{\n                    position: 'absolute',\n                    top: '100px',\n                    left: '200px',\n                    width: '100px',\n                    height: '50px',\n                  }}\n                >\n                  Trigger 2\n                </Menu.Trigger>\n                <Menu.Portal>\n                  <Menu.Positioner>\n                    <Menu.Popup>\n                      <Menu.Viewport>\n                        <div data-testid=\"content\">Content {payload as number}</div>\n                      </Menu.Viewport>\n                    </Menu.Popup>\n                  </Menu.Positioner>\n                </Menu.Portal>\n              </React.Fragment>\n            )}\n          </Menu.Root>\n        </div>,\n      );\n\n      const trigger1 = screen.getByTestId('trigger1');\n      const trigger2 = screen.getByTestId('trigger2');\n\n      await user.click(trigger1);\n      await waitFor(() => {\n        expect(screen.getByText('Content 0')).toBeVisible();\n      });\n\n      // Click second trigger to trigger morphing\n      await user.click(trigger2);\n\n      // Check for morphing containers during transition\n      let previousContainer: HTMLElement | null = null;\n      await waitFor(() => {\n        previousContainer = document.querySelector('[data-previous]');\n        expect(previousContainer).not.toBe(null);\n      });\n\n      expect(previousContainer).toHaveAttribute('inert');\n      expect(previousContainer!.textContent).toBe('Content 0');\n      expect(previousContainer!.style.getPropertyValue('--popup-width')).toMatch(\n        /^\\d+(?:\\.\\d+)?px$/,\n      );\n      expect(previousContainer!.style.getPropertyValue('--popup-height')).toMatch(\n        /^\\d+(?:\\.\\d+)?px$/,\n      );\n\n      const nextContainer = document.querySelector('[data-current]');\n      expect(nextContainer).not.toBe(null);\n      expect(nextContainer!.textContent).toBe('Content 1');\n\n      // Verify they are cleaned up after animation\n      await waitFor(() => {\n        expect(document.querySelector('[data-previous]')).toBe(null);\n      });\n\n      expect(document.querySelector('[data-current]')).toBeVisible();\n      expect(screen.getByText('Content 1')).toBeVisible();\n    });\n\n    it('should handle rapid trigger changes', async () => {\n      function TestComponent() {\n        return (\n          <div>\n            <style>\n              {`\n              [data-transitioning] [data-previous] {\n                animation: slide-out 0.2s ease-out forwards;\n              }\n              [data-transitioning] [data-current] {\n                animation: slide-in 0.2s ease-out forwards;\n              }\n              @keyframes slide-out {\n                from { transform: translateX(0); opacity: 1; }\n                to { transform: translateX(-30%); opacity: 0; }\n              }\n              @keyframes slide-in {\n                from { transform: translateX(30%); opacity: 0; }\n                to { transform: translateX(0); opacity: 1; }\n              }\n            `}\n            </style>\n            <Menu.Root>\n              {({ payload }) => (\n                <React.Fragment>\n                  <Menu.Trigger payload={1} data-testid=\"trigger1\">\n                    Trigger 1\n                  </Menu.Trigger>\n                  <Menu.Trigger payload={2} data-testid=\"trigger2\">\n                    Trigger 2\n                  </Menu.Trigger>\n                  <Menu.Trigger payload={3} data-testid=\"trigger3\">\n                    Trigger 3\n                  </Menu.Trigger>\n                  <Menu.Portal>\n                    <Menu.Positioner>\n                      <Menu.Popup>\n                        <Menu.Viewport>Content {payload as number}</Menu.Viewport>\n                      </Menu.Popup>\n                    </Menu.Positioner>\n                  </Menu.Portal>\n                </React.Fragment>\n              )}\n            </Menu.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<TestComponent />);\n\n      const trigger1 = screen.getByTestId('trigger1');\n      const trigger2 = screen.getByTestId('trigger2');\n      const trigger3 = screen.getByTestId('trigger3');\n\n      await user.click(trigger1);\n      await user.click(trigger2);\n      await user.click(trigger3);\n      await user.click(trigger1);\n\n      const content = await screen.findByText('Content 1');\n      await waitFor(() => {\n        expect(content).toBeVisible();\n      });\n    });\n\n    it.each([\n      {\n        name: 'should calculate \"right down\" direction',\n        trigger1: { top: 10, left: 10 },\n        trigger2: { top: 100, left: 200 },\n        expectedDirection: ['right', 'down'],\n      },\n      {\n        name: 'should calculate \"left up\" direction',\n        trigger1: { top: 100, left: 200 },\n        trigger2: { top: 10, left: 10 },\n        expectedDirection: ['left', 'up'],\n      },\n      {\n        name: 'should calculate \"right\" direction (horizontal only)',\n        trigger1: { top: 50, left: 10 },\n        trigger2: { top: 52, left: 200 }, // 2px vertical difference within tolerance\n        expectedDirection: ['right'],\n      },\n      {\n        name: 'should calculate \"down\" direction (vertical only)',\n        trigger1: { top: 10, left: 50 },\n        trigger2: { top: 100, left: 52 }, // 2px horizontal difference within tolerance\n        expectedDirection: ['down'],\n      },\n      {\n        name: 'should handle tolerance for small differences',\n        trigger1: { top: 50, left: 50 },\n        trigger2: { top: 52, left: 52 }, // Both differences within 5px tolerance\n        expectedDirection: [],\n      },\n      {\n        name: 'should calculate \"left down\" direction',\n        trigger1: { top: 10, left: 200 },\n        trigger2: { top: 100, left: 10 },\n        expectedDirection: ['left', 'down'],\n      },\n      {\n        name: 'should calculate \"right up\" direction',\n        trigger1: { top: 100, left: 10 },\n        trigger2: { top: 10, left: 200 },\n        expectedDirection: ['right', 'up'],\n      },\n    ])('$name', async ({ trigger1, trigger2, expectedDirection }) => {\n      const { user } = await render(\n        <div>\n          <style>\n            {`\n              [data-transitioning] [data-previous] {\n                animation: slide-out 0.2s ease-out forwards;\n              }\n              [data-transitioning] [data-current] {\n                animation: slide-in 0.2s ease-out forwards;\n              }\n              @keyframes slide-out {\n                from { transform: translateX(0); opacity: 1; }\n                to { transform: translateX(-30%); opacity: 0; }\n              }\n              @keyframes slide-in {\n                from { transform: translateX(30%); opacity: 0; }\n                to { transform: translateX(0); opacity: 1; }\n              }\n            `}\n          </style>\n          <Menu.Root>\n            {({ payload }) => (\n              <React.Fragment>\n                <Menu.Trigger\n                  payload={0}\n                  data-testid=\"trigger1\"\n                  style={{\n                    position: 'absolute',\n                    top: `${trigger1.top}px`,\n                    left: `${trigger1.left}px`,\n                    width: '100px',\n                    height: '50px',\n                  }}\n                >\n                  Trigger 1\n                </Menu.Trigger>\n                <Menu.Trigger\n                  payload={1}\n                  data-testid=\"trigger2\"\n                  style={{\n                    position: 'absolute',\n                    top: `${trigger2.top}px`,\n                    left: `${trigger2.left}px`,\n                    width: '100px',\n                    height: '50px',\n                  }}\n                >\n                  Trigger 2\n                </Menu.Trigger>\n                <Menu.Portal>\n                  <Menu.Positioner>\n                    <Menu.Popup>\n                      <Menu.Viewport data-testid=\"viewport\">\n                        <div data-testid=\"content\">Content {payload as number}</div>\n                      </Menu.Viewport>\n                    </Menu.Popup>\n                  </Menu.Positioner>\n                </Menu.Portal>\n              </React.Fragment>\n            )}\n          </Menu.Root>\n        </div>,\n      );\n\n      const triggerElement1 = screen.getByTestId('trigger1');\n      const triggerElement2 = screen.getByTestId('trigger2');\n\n      await user.click(triggerElement1);\n\n      await waitFor(() => {\n        expect(screen.getByText('Content 0')).toBeVisible();\n      });\n\n      await user.click(triggerElement2);\n\n      const viewport = screen.getByTestId('viewport');\n      await waitFor(() => {\n        expect(viewport).toHaveAttribute('data-activation-direction');\n      });\n\n      const direction = viewport.getAttribute('data-activation-direction');\n\n      if (expectedDirection.length === 0) {\n        expect(direction?.trim()).toBe('');\n      } else {\n        expectedDirection.forEach((dir) => {\n          expect(direction).toContain(dir);\n        });\n      }\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/menu/viewport/MenuViewport.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useMenuRootContext } from '../root/MenuRootContext';\nimport { useMenuPositionerContext } from '../positioner/MenuPositionerContext';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport { usePopupViewport } from '../../utils/usePopupViewport';\nimport { MenuViewportCssVars } from './MenuViewportCssVars';\n\nconst stateAttributesMapping: StateAttributesMapping<MenuViewport.State> = {\n  activationDirection: (value) =>\n    value\n      ? {\n          'data-activation-direction': value,\n        }\n      : null,\n};\n\n/**\n * A viewport for displaying content transitions.\n * This component is only required if one popup can be opened by multiple triggers, its content change based on the trigger\n * and switching between them is animated.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)\n */\nexport const MenuViewport = React.forwardRef(function MenuViewport(\n  componentProps: MenuViewport.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, children, ...elementProps } = componentProps;\n\n  const { store } = useMenuRootContext();\n  const { side } = useMenuPositionerContext();\n\n  const instantType = store.useState('instantType');\n\n  const { children: childrenToRender, state: viewportState } = usePopupViewport({\n    store,\n    side,\n    cssVars: MenuViewportCssVars,\n    children,\n  });\n\n  const state: MenuViewport.State = {\n    activationDirection: viewportState.activationDirection,\n    transitioning: viewportState.transitioning,\n    instant: instantType,\n  };\n\n  return useRenderElement('div', componentProps, {\n    state,\n    ref: forwardedRef,\n    props: [elementProps, { children: childrenToRender }],\n    stateAttributesMapping,\n  });\n});\n\nexport namespace MenuViewport {\n  export interface Props extends BaseUIComponentProps<'div', State> {\n    /**\n     * The content to render inside the transition container.\n     */\n    children?: React.ReactNode;\n  }\n\n  export interface State {\n    activationDirection: string | undefined;\n    /**\n     * Whether the viewport is currently transitioning between contents.\n     */\n    transitioning: boolean;\n    /**\n     * Present if animations should be instant.\n     */\n    instant: 'dismiss' | 'click' | 'group' | 'trigger-change' | undefined;\n  }\n}\n"
  },
  {
    "path": "packages/react/src/menu/viewport/MenuViewportCssVars.ts",
    "content": "export enum MenuViewportCssVars {\n  /**\n   * The width of the parent popup.\n   * This variable is placed on the 'previous' container and stores the width of the popup when the previous content was rendered.\n   * It can be used to freeze the dimensions of the popup when animating between different content.\n   */\n  popupWidth = '--popup-width',\n  /**\n   * The height of the parent popup.\n   * This variable is placed on the 'previous' container and stores the height of the popup when the previous content was rendered.\n   * It can be used to freeze the dimensions of the popup when animating between different content.\n   */\n  popupHeight = '--popup-height',\n}\n"
  },
  {
    "path": "packages/react/src/menu/viewport/MenuViewportDataAttributes.ts",
    "content": "export enum MenuViewportDataAttributes {\n  /**\n   * Applied to the direct child of the viewport when no transitions are present or the new content when it's entering.\n   */\n  current = 'data-current',\n  /**\n   * Applied to the direct child of the viewport that contains the exiting content when transitions are present.\n   */\n  previous = 'data-previous',\n  /**\n   * Indicates the direction from which the popup was activated.\n   * This can be used to create directional animations based on how the popup was triggered.\n   * Contains space-separated values for both horizontal and vertical axes.\n   * @type {`${'left' | 'right'} {'down' | 'up'}`}\n   */\n  activationDirection = 'data-activation-direction',\n  /**\n   * Indicates that the viewport is currently transitioning between old and new content.\n   */\n  transitioning = 'data-transitioning',\n  /**\n   * Present if animations should be instant.\n   * @type {'click' | 'dismiss' | 'group' | 'trigger-change'}\n   */\n  instant = 'data-instant',\n}\n"
  },
  {
    "path": "packages/react/src/menubar/Menubar.test.tsx",
    "content": "import { afterEach, expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { act, fireEvent, screen, waitFor } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance, isJSDOM, wait } from '#test-utils';\nimport { Menubar } from '@base-ui/react/menubar';\nimport { Menu } from '@base-ui/react/menu';\nimport { useRefWithInit } from '@base-ui/utils/useRefWithInit';\n\ndescribe('<Menubar />', () => {\n  beforeEach(() => {\n    globalThis.BASE_UI_ANIMATIONS_DISABLED = true;\n  });\n\n  const { render } = createRenderer();\n\n  describeConformance(<Menubar />, () => ({\n    render: (node) => {\n      return render(<Menubar>{node}</Menubar>);\n    },\n    refInstanceof: window.HTMLDivElement,\n  }));\n\n  // All these tests run for contained, detached and multiple contained triggers.\n  // The rendered menubar has the same structure in most cases.\n  describe.for([\n    { name: 'contained triggers', Component: ContainedTriggerMenubar },\n    { name: 'detached triggers', Component: DetachedTriggerMenubar },\n    { name: 'multiple contained triggers', Component: MultipleContainedTriggersMenubar },\n  ])('when using $name', ({ Component: TestMenubar }) => {\n    describe.skipIf(isJSDOM)('click interactions', () => {\n      afterEach(async () => {\n        const { cleanup } = await import('vitest-browser-react');\n        await cleanup();\n      });\n\n      it('should open the menu after clicking on its trigger and close it when clicking again', async () => {\n        const { userEvent: user } = await import('vitest/browser');\n        const { render: vbrRender } = await import('vitest-browser-react');\n\n        await vbrRender(<TestMenubar />);\n\n        const fileTrigger = screen.getByTestId('file-trigger');\n\n        await user.click(fileTrigger, { delay: 30 });\n        await screen.findByTestId('file-menu');\n\n        // Click again to close the menu\n        await user.click(fileTrigger, { delay: 30 });\n        await waitFor(() => {\n          expect(screen.queryByTestId('file-menu')).toBe(null);\n        });\n      });\n    });\n\n    describe.skipIf(isJSDOM)('hover behavior', async () => {\n      afterEach(async () => {\n        const { cleanup } = await import('vitest-browser-react');\n        await cleanup();\n      });\n\n      it('should not open submenus on hover when no submenu is already open', async () => {\n        const { userEvent: user } = await import('vitest/browser');\n        const { render: vbrRender } = await import('vitest-browser-react');\n\n        await vbrRender(<TestMenubar />);\n\n        const fileTrigger = screen.getByTestId('file-trigger');\n\n        await user.hover(fileTrigger);\n\n        // The file menu should not be open because no submenu is already open\n        expect(screen.queryByTestId('file-menu')).toBe(null);\n      });\n\n      it('should open submenus on hover when another submenu is already open', async () => {\n        const { userEvent: user } = await import('vitest/browser');\n        const { render: vbrRender } = await import('vitest-browser-react');\n\n        await vbrRender(<TestMenubar />);\n\n        // First click to open the file menu\n        const fileTrigger = screen.getByTestId('file-trigger');\n        await user.click(fileTrigger);\n\n        await screen.findByTestId('file-menu');\n        await waitFor(() => {\n          expect(screen.getByRole('menubar')).toHaveAttribute('data-has-submenu-open', 'true');\n        });\n\n        // Now hover over the edit trigger, it should open because a submenu is already open\n        const editTrigger = screen.queryByTestId('edit-trigger');\n        expect(editTrigger).not.toBe(null);\n\n        await user.hover(editTrigger!);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('edit-menu')).not.toBe(null);\n        });\n\n        // The file menu should now be closed\n        expect(screen.queryByTestId('file-menu')).toBe(null);\n\n        // Continue hovering to the view trigger\n        const viewTrigger = screen.getByTestId('view-trigger');\n        await user.hover(viewTrigger);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('view-menu')).not.toBe(null);\n        });\n\n        // The edit menu should now be closed\n        expect(screen.queryByTestId('edit-menu')).toBe(null);\n      });\n\n      it('should open nested submenus on hover when parent menu is open', async () => {\n        const { userEvent: user } = await import('vitest/browser');\n        const { render: vbrRender } = await import('vitest-browser-react');\n\n        await vbrRender(<TestMenubar />);\n\n        // First click to open the file menu\n        const fileTrigger = screen.getByTestId('file-trigger');\n        await user.click(fileTrigger);\n\n        await waitFor(() => {\n          expect(screen.getByTestId('file-menu')).not.toBe(null);\n        });\n        await waitFor(() => {\n          expect(screen.getByRole('menubar')).toHaveAttribute('data-has-submenu-open', 'true');\n        });\n\n        await wait(50);\n\n        // Now hover over the share submenu trigger\n        const shareTrigger = await screen.findByTestId('share-trigger');\n        await user.hover(shareTrigger);\n\n        // The share submenu should open\n        await waitFor(() => {\n          expect(screen.queryByTestId('share-menu')).not.toBe(null);\n        });\n      });\n\n      it('should open another menu on hover when a nested submenu is open', async () => {\n        const { userEvent: user } = await import('vitest/browser');\n        const { render: vbrRender } = await import('vitest-browser-react');\n\n        await vbrRender(<TestMenubar />);\n\n        // First click to open the file menu\n        const fileTrigger = screen.getByTestId('file-trigger');\n        await user.click(fileTrigger);\n\n        await waitFor(() => {\n          expect(screen.getByTestId('file-menu')).not.toBe(null);\n        });\n        await waitFor(() => {\n          expect(screen.getByRole('menubar')).toHaveAttribute('data-has-submenu-open', 'true');\n        });\n\n        // Now hover over the share submenu trigger\n        const shareTrigger = await screen.findByTestId('share-trigger');\n        await user.hover(shareTrigger);\n\n        // The share submenu should open\n        await waitFor(() => {\n          expect(screen.queryByTestId('share-menu')).not.toBe(null);\n        });\n\n        // Hover over the first item in the share submenu\n        await user.hover(screen.getByTestId('share-item-1'));\n\n        // Now hover over the edit menubar trigger\n        const editTrigger = screen.getByTestId('edit-trigger');\n        await user.hover(editTrigger);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('edit-menu')).not.toBe(null);\n        });\n\n        expect(screen.queryByTestId('file-menu')).toBe(null);\n        expect(screen.queryByTestId('share-menu')).toBe(null);\n      });\n    });\n\n    describe('focus behavior', () => {\n      it('focuses a menubar item without immediately opening the menu', async () => {\n        const { user } = await render(<TestMenubar />);\n\n        await user.tab();\n\n        await waitFor(() => {\n          const fileTrigger = screen.getByTestId('file-trigger');\n          expect(fileTrigger).toHaveFocus();\n          expect(screen.queryByTestId('file-menu')).toBe(null);\n        });\n\n        await wait(50);\n\n        await user.keyboard('{Enter}');\n        await waitFor(() => {\n          expect(screen.queryByTestId('file-menu')).not.toBe(null);\n        });\n      });\n    });\n\n    describe.skipIf(isJSDOM)('closeOnClick on nested items behavior', () => {\n      afterEach(async () => {\n        const { cleanup } = await import('vitest-browser-react');\n        await cleanup();\n      });\n\n      it('should respect closeOnClick on nested items when the menu was opened on click', async () => {\n        const { userEvent: user } = await import('vitest/browser');\n        const { render: vbrRender } = await import('vitest-browser-react');\n\n        await vbrRender(<TestMenubar />);\n\n        const viewTrigger = screen.getByTestId('view-trigger');\n\n        await user.click(viewTrigger, { delay: 30 });\n        await screen.findByTestId('view-menu');\n\n        const layoutTrigger = screen.getByTestId('layout-trigger');\n        await user.hover(layoutTrigger);\n\n        await screen.findByTestId('layout-menu');\n\n        const layoutItem2 = screen.getByTestId('layout-item-2');\n        await user.click(layoutItem2, { delay: 30 });\n\n        // The layout menu should not close after clicking an item\n        await waitFor(() => {\n          expect(screen.queryByTestId('layout-menu')).not.toBe(null);\n        });\n      });\n\n      // https://github.com/mui/base-ui/issues/2092\n      it('should respect closeOnClick on nested items when the menu was opened on hover', async () => {\n        const { userEvent: user } = await import('vitest/browser');\n        const { render: vbrRender } = await import('vitest-browser-react');\n\n        await vbrRender(<TestMenubar />);\n\n        const fileTrigger = screen.getByTestId('file-trigger');\n        const viewTrigger = screen.getByTestId('view-trigger');\n\n        await user.click(fileTrigger, { delay: 30 });\n        await screen.findByTestId('file-menu');\n\n        await user.hover(viewTrigger);\n        await screen.findByTestId('view-menu');\n\n        const layoutTrigger = screen.getByTestId('layout-trigger');\n        await user.hover(layoutTrigger);\n\n        await screen.findByTestId('layout-menu');\n\n        const layoutItem2 = screen.getByTestId('layout-item-2');\n        await user.click(layoutItem2, { delay: 30 });\n\n        // The layout menu should not close after clicking an item\n        await waitFor(() => {\n          expect(screen.queryByTestId('layout-menu')).not.toBe(null);\n        });\n      });\n    });\n\n    describe('keyboard interactions', () => {\n      it('should navigate between menubar items with arrow keys', async () => {\n        const { user } = await render(<TestMenubar />);\n\n        const fileTrigger = screen.getByTestId('file-trigger');\n        const editTrigger = screen.getByTestId('edit-trigger');\n\n        // First focus the file trigger\n        await act(async () => {\n          fileTrigger.focus();\n        });\n\n        // Check that file trigger has focus\n        await waitFor(() => {\n          expect(fileTrigger).toHaveFocus();\n        });\n\n        // Use arrow right to navigate to edit trigger\n        await user.keyboard('{ArrowRight}');\n\n        // Wait for the edit trigger to get focus\n        await waitFor(() => {\n          expect(editTrigger).toHaveFocus();\n        });\n      });\n\n      it('should open the menu with Space key', async () => {\n        const { user } = await render(<TestMenubar />);\n        const fileTrigger = screen.getByTestId('file-trigger');\n\n        // Focus the file trigger\n        await act(async () => {\n          fileTrigger.focus();\n        });\n\n        // Press Space key to open menu\n        await user.keyboard(' ');\n\n        // Menu should be open\n        await waitFor(() => {\n          expect(screen.queryByTestId('file-menu')).not.toBe(null);\n        });\n      });\n\n      it('should navigate within the menu using arrow keys', async () => {\n        const { user } = await render(<TestMenubar />);\n        const fileTrigger = screen.getByTestId('file-trigger');\n\n        // Focus and open file menu\n        await act(async () => {\n          fileTrigger.focus();\n        });\n        await user.keyboard('{Enter}');\n\n        // File menu should be open\n        await waitFor(() => {\n          expect(screen.queryByTestId('file-menu')).not.toBe(null);\n        });\n\n        // First item should be focused automatically\n        const firstItem = screen.getByTestId('file-item-1');\n        await waitFor(() => {\n          expect(firstItem).toHaveFocus();\n        });\n\n        // Navigate down to second item\n        await user.keyboard('{ArrowDown}');\n        const secondItem = screen.getByTestId('file-item-2');\n        await waitFor(() => {\n          expect(secondItem).toHaveFocus();\n        });\n\n        // Navigate down to submenu trigger\n        await user.keyboard('{ArrowDown}');\n        const shareTrigger = screen.getByTestId('share-trigger');\n        await waitFor(() => {\n          expect(shareTrigger).toHaveFocus();\n        });\n      });\n\n      it('should open the submenu with right arrow key', async () => {\n        const { user } = await render(<TestMenubar />);\n        const fileTrigger = screen.getByTestId('file-trigger');\n\n        // Focus and open file menu\n        await act(async () => {\n          fileTrigger.focus();\n        });\n        await user.keyboard('{Enter}');\n        await waitFor(() => {\n          expect(screen.queryByTestId('file-menu')).not.toBe(null);\n        });\n\n        await waitFor(() => {\n          expect(screen.getByTestId('file-item-1')).toHaveFocus();\n        });\n\n        await user.keyboard('{ArrowDown}');\n        await waitFor(() => {\n          expect(screen.getByTestId('file-item-2')).toHaveFocus();\n        });\n\n        await user.keyboard('{ArrowDown}');\n        await waitFor(() => {\n          expect(screen.getByTestId('share-trigger')).toHaveFocus();\n        });\n\n        // Arrow right should open submenu\n        await user.keyboard('{ArrowRight}');\n\n        // Share submenu should be open\n        await waitFor(() => {\n          expect(screen.queryByTestId('share-menu')).not.toBe(null);\n        });\n\n        // First submenu item should be focused\n        const submenuItem = screen.getByTestId('share-item-1');\n        await waitFor(() => {\n          expect(submenuItem).toHaveFocus();\n        });\n      });\n\n      it.skipIf(isJSDOM)('should close the menu with Escape key', async () => {\n        const { user } = await render(<TestMenubar />);\n        const fileTrigger = screen.getByTestId('file-trigger');\n\n        // Focus and open file menu\n        await act(async () => {\n          fileTrigger.focus();\n        });\n\n        await user.keyboard('{Enter}');\n\n        // Menu should be open\n        await waitFor(() => {\n          expect(screen.queryByTestId('file-menu')).not.toBe(null);\n        });\n\n        // Press Escape to close\n        await user.keyboard('{Escape}');\n\n        // Menu should be closed\n        await waitFor(() => {\n          expect(screen.queryByTestId('file-menu')).toBe(null);\n        });\n      });\n\n      it('should close submenu with left arrow key and return focus to submenu trigger', async () => {\n        const { user } = await render(<TestMenubar />);\n        const fileTrigger = screen.getByTestId('file-trigger');\n\n        // Focus and open file menu\n        await act(async () => {\n          fileTrigger.focus();\n        });\n        await user.keyboard('{Enter}');\n        await waitFor(() => {\n          expect(screen.getByTestId('file-item-1')).toHaveFocus();\n        });\n\n        // Navigate to submenu trigger\n        await user.keyboard('{ArrowDown}');\n        await waitFor(() => {\n          expect(screen.getByTestId('file-item-2')).toHaveFocus();\n        });\n\n        await user.keyboard('{ArrowDown}');\n\n        const shareTrigger = screen.getByTestId('share-trigger');\n        await waitFor(() => {\n          expect(shareTrigger).toHaveFocus();\n        });\n\n        // Open submenu\n        await user.keyboard('{ArrowRight}');\n        await waitFor(() => {\n          expect(screen.queryByTestId('share-menu')).not.toBe(null);\n        });\n\n        // Close submenu with left arrow\n        await user.keyboard('{ArrowLeft}');\n\n        // Submenu should be closed\n        await waitFor(() => {\n          expect(screen.queryByTestId('share-menu')).toBe(null);\n        });\n\n        // Focus should return to submenu trigger\n        expect(shareTrigger).toHaveFocus();\n      });\n\n      it.skipIf(!isJSDOM)(\n        'closes open submenus when navigating to the next menubar item with ArrowRight',\n        async () => {\n          const rootOnOpenChange = vi.fn();\n          const submenuOnOpenChange = vi.fn();\n          const nextOnOpenChange = vi.fn();\n\n          function SpyMenubar() {\n            return (\n              <Menubar>\n                <Menu.Root onOpenChange={rootOnOpenChange}>\n                  <Menu.Trigger data-testid=\"menubar-file-trigger\">File</Menu.Trigger>\n                  <Menu.Portal>\n                    <Menu.Positioner data-testid=\"menubar-file-menu\">\n                      <Menu.Popup>\n                        <Menu.Item data-testid=\"menubar-file-item\">Item 1</Menu.Item>\n                        <Menu.SubmenuRoot onOpenChange={submenuOnOpenChange}>\n                          <Menu.SubmenuTrigger data-testid=\"menubar-submenu-trigger\">\n                            Share\n                          </Menu.SubmenuTrigger>\n                          <Menu.Portal>\n                            <Menu.Positioner data-testid=\"menubar-submenu-menu\">\n                              <Menu.Popup>\n                                <Menu.Item data-testid=\"menubar-submenu-item\">Email</Menu.Item>\n                              </Menu.Popup>\n                            </Menu.Positioner>\n                          </Menu.Portal>\n                        </Menu.SubmenuRoot>\n                      </Menu.Popup>\n                    </Menu.Positioner>\n                  </Menu.Portal>\n                </Menu.Root>\n                <Menu.Root onOpenChange={nextOnOpenChange}>\n                  <Menu.Trigger data-testid=\"menubar-next-trigger\">Edit</Menu.Trigger>\n                  <Menu.Portal>\n                    <Menu.Positioner data-testid=\"menubar-next-menu\">\n                      <Menu.Popup>\n                        <Menu.Item>Edit Item</Menu.Item>\n                      </Menu.Popup>\n                    </Menu.Positioner>\n                  </Menu.Portal>\n                </Menu.Root>\n              </Menubar>\n            );\n          }\n\n          const { user } = await render(<SpyMenubar />);\n          const fileTrigger = screen.getByTestId('menubar-file-trigger');\n\n          await act(async () => {\n            fileTrigger.focus();\n          });\n          await user.keyboard('{Enter}');\n          await screen.findByTestId('menubar-file-menu');\n\n          await waitFor(() => {\n            expect(screen.getByTestId('menubar-file-item')).toHaveFocus();\n          });\n\n          await user.keyboard('{ArrowDown}');\n          const submenuTrigger = screen.getByTestId('menubar-submenu-trigger');\n          await waitFor(() => {\n            expect(submenuTrigger).toHaveFocus();\n          });\n\n          await user.keyboard('{ArrowRight}');\n          await screen.findByTestId('menubar-submenu-menu');\n\n          await waitFor(() => {\n            expect(screen.getByTestId('menubar-submenu-item')).toHaveFocus();\n          });\n\n          await user.keyboard('{ArrowRight}');\n\n          await screen.findByTestId('menubar-next-menu');\n\n          await waitFor(() => {\n            expect(screen.queryByTestId('menubar-submenu-menu')).toBe(null);\n          });\n\n          await waitFor(() => {\n            expect(screen.queryByTestId('menubar-file-menu')).toBe(null);\n          });\n\n          expect(submenuOnOpenChange.mock.lastCall?.[0]).toBe(false);\n          expect(rootOnOpenChange.mock.lastCall?.[0]).toBe(false);\n          expect(nextOnOpenChange.mock.lastCall?.[0]).toBe(true);\n        },\n      );\n\n      // Doesn't work in headless mode.\n      it.skipIf(!isJSDOM)(\n        'should navigate between menus using left/right arrow keys when menus are open',\n        async () => {\n          const { user } = await render(<TestMenubar />);\n          const fileTrigger = screen.getByTestId('file-trigger');\n\n          // Focus and open file menu\n          await act(async () => {\n            fileTrigger.focus();\n          });\n          await user.keyboard('{Enter}');\n\n          // File menu should be open\n          await waitFor(() => {\n            expect(screen.queryByTestId('file-menu')).not.toBe(null);\n          });\n\n          // Navigate right to edit menu\n          await user.keyboard('{ArrowRight}');\n\n          // File menu should close, edit menu should open\n          await waitFor(() => {\n            expect(screen.queryByTestId('file-menu')).toBe(null);\n          });\n          await waitFor(() => {\n            expect(screen.queryByTestId('edit-menu')).not.toBe(null);\n          });\n        },\n      );\n    });\n\n    describe.skipIf(!isJSDOM)('mixed mouse and keyboard interactions', () => {\n      it('should allow keyboard navigation after opening a menu with mouse click', async () => {\n        const { user } = await render(<TestMenubar />);\n\n        // Open the menu with a mouse click\n        const fileTrigger = screen.getByTestId('file-trigger');\n        await user.click(fileTrigger);\n\n        // Menu should be open\n        await waitFor(() => {\n          expect(screen.queryByTestId('file-menu')).not.toBe(null);\n        });\n\n        // Navigate with keyboard\n        await user.keyboard('{ArrowDown}');\n        const firstItem = screen.getByTestId('file-item-1');\n        await waitFor(() => {\n          expect(firstItem).toHaveFocus();\n        });\n\n        // Continue navigation\n        await user.keyboard('{ArrowDown}');\n        const secondItem = screen.getByTestId('file-item-2');\n        await waitFor(() => {\n          expect(secondItem).toHaveFocus();\n        });\n      });\n\n      it('should allow clicking a menu trigger then navigating to another menu with keyboard', async () => {\n        const { user } = await render(<TestMenubar />);\n\n        // Open the file menu with a mouse click\n        const fileTrigger = screen.getByTestId('file-trigger');\n        await user.click(fileTrigger);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('file-menu')).not.toBe(null);\n        });\n\n        // Navigate to edit menu with keyboard\n        await user.keyboard('{ArrowRight}');\n\n        // File menu should close, edit menu should open\n        await waitFor(() => {\n          expect(screen.queryByTestId('file-menu')).toBe(null);\n        });\n        await waitFor(() => {\n          expect(screen.queryByTestId('edit-menu')).not.toBe(null);\n        });\n      });\n    });\n\n    describe.skipIf(!isJSDOM)('touch interactions', () => {\n      it('closes the entire tree on a single outside press after opening a submenu', async () => {\n        await render(\n          <div>\n            <TestMenubar />\n            <button data-testid=\"outside\">Outside</button>\n          </div>,\n        );\n\n        const fileTrigger = screen.getByTestId('file-trigger');\n\n        fireEvent.pointerDown(fileTrigger, { pointerType: 'touch' });\n        fireEvent.mouseDown(fileTrigger);\n\n        await screen.findByTestId('file-menu');\n\n        const shareTrigger = await screen.findByTestId('share-trigger');\n        fireEvent.pointerDown(shareTrigger, { pointerType: 'touch' });\n        fireEvent.mouseDown(shareTrigger);\n\n        await screen.findByTestId('share-menu');\n\n        const outside = screen.getByTestId('outside');\n        fireEvent.pointerDown(outside, { pointerType: 'touch' });\n        fireEvent.mouseDown(outside);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('share-menu')).toBe(null);\n          expect(screen.queryByTestId('file-menu')).toBe(null);\n        });\n      });\n    });\n\n    describe.skipIf(!isJSDOM)('prop: loopFocus', () => {\n      describe('when loopFocus == true', () => {\n        it('should loop around to the first item after the last one', async () => {\n          const { user } = await render(<TestMenubar loopFocus />);\n\n          const firstItem = screen.getByTestId('file-trigger');\n          await act(async () => {\n            firstItem.focus();\n          });\n\n          await user.keyboard('{ArrowRight}');\n          await user.keyboard('{ArrowRight}');\n\n          const lastItem = screen.getByTestId('view-trigger');\n          expect(lastItem).toHaveFocus();\n\n          await user.keyboard('{ArrowRight}');\n          expect(firstItem).toHaveFocus();\n        });\n\n        it('should loop around to the last item after the first one', async () => {\n          const { user } = await render(<TestMenubar loopFocus />);\n\n          const fileTrigger = screen.getByTestId('file-trigger');\n          await act(async () => {\n            fileTrigger.focus();\n          });\n\n          await waitFor(() => {\n            expect(fileTrigger).toHaveFocus();\n          });\n\n          await user.keyboard('{ArrowLeft}');\n          const lastItem = screen.getByTestId('view-trigger');\n          await waitFor(() => {\n            expect(lastItem).toHaveFocus();\n          });\n        });\n      });\n\n      describe('when loopFocus == false', () => {\n        it('should stay on the last item when navigating beyond it', async () => {\n          const { user } = await render(<TestMenubar loopFocus={false} />);\n\n          const fileTrigger = screen.getByTestId('file-trigger');\n          await act(async () => {\n            fileTrigger.focus();\n          });\n\n          await user.keyboard('{ArrowRight}');\n          await waitFor(() => {\n            expect(screen.queryByTestId('edit-trigger')).not.toBe(null);\n          });\n\n          await user.keyboard('{ArrowRight}');\n\n          const lastItem = screen.getByTestId('view-trigger');\n          await waitFor(() => {\n            expect(lastItem).toHaveFocus();\n          });\n\n          await user.keyboard('{ArrowRight}');\n          await waitFor(() => {\n            expect(lastItem).toHaveFocus();\n          });\n        });\n\n        it('should stay on the first item when navigating before it', async () => {\n          const { user } = await render(<TestMenubar loopFocus={false} />);\n\n          const firstItem = screen.getByTestId('file-trigger');\n          await act(async () => {\n            firstItem.focus();\n          });\n\n          await user.keyboard('{ArrowLeft}');\n          expect(firstItem).toHaveFocus();\n        });\n      });\n    });\n\n    describe('prop: disabled', () => {\n      it('disables child menus when menubar is disabled', async () => {\n        const { user } = await render(<TestMenubar disabled />);\n\n        const fileTrigger = screen.getByTestId('file-trigger');\n\n        // Trigger should be disabled\n        expect(fileTrigger).toHaveAttribute('disabled');\n\n        // It should not be reachable via Tab\n        await user.tab();\n        expect(fileTrigger).not.toHaveFocus();\n        expect(document.body).toHaveFocus();\n\n        // Clicking should not open the menu\n        await user.click(fileTrigger);\n        expect(screen.queryByTestId('file-menu')).toBe(null);\n      });\n    });\n\n    it.skipIf(isJSDOM)(\n      'correctly opens new menu on hover after clicking on its trigger and entering from hover (#2222)',\n      async () => {\n        const { user } = await render(<TestMenubar />);\n\n        const fileTrigger = screen.getByTestId('file-trigger');\n        const editTrigger = screen.getByTestId('edit-trigger');\n        await user.click(fileTrigger);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('file-menu')).not.toBe(null);\n        });\n        await waitFor(() => {\n          expect(screen.getByRole('menubar')).toHaveAttribute('data-has-submenu-open', 'true');\n        });\n\n        await user.hover(editTrigger);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('edit-menu')).not.toBe(null);\n        });\n\n        await user.click(editTrigger);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('edit-menu')).toBe(null);\n        });\n        await waitFor(() => {\n          expect(screen.getByRole('menubar')).toHaveAttribute('data-has-submenu-open', 'false');\n        });\n\n        await user.click(fileTrigger);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('file-menu')).not.toBe(null);\n        });\n        await waitFor(() => {\n          expect(screen.getByRole('menubar')).toHaveAttribute('data-has-submenu-open', 'true');\n        });\n\n        await user.hover(editTrigger);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('edit-menu')).not.toBe(null);\n        });\n      },\n    );\n\n    describe('role', () => {\n      it('sets role=\"menubar\" on the root element', async () => {\n        await render(<TestMenubar />);\n        expect(screen.queryByRole('menubar')).not.toBe(null);\n      });\n\n      it('sets role=\"menuitem\" on menu triggers', async () => {\n        await render(<TestMenubar />);\n        const menuItems = screen.getAllByRole('menuitem');\n        expect(menuItems).toHaveLength(3);\n      });\n    });\n  });\n});\n\nfunction ContainedTriggerMenubar(props: Menubar.Props) {\n  return (\n    <Menubar {...props} style={{ maxWidth: '25vw', display: 'flex' }}>\n      <Menu.Root>\n        <Menu.Trigger data-testid=\"file-trigger\">File</Menu.Trigger>\n        <Menu.Portal>\n          <Menu.Positioner data-testid=\"file-menu\">\n            <Menu.Popup>\n              <Menu.Item data-testid=\"file-item-1\">Open</Menu.Item>\n              <Menu.Item data-testid=\"file-item-2\">Save</Menu.Item>\n              <Menu.SubmenuRoot>\n                <Menu.SubmenuTrigger data-testid=\"share-trigger\">Share</Menu.SubmenuTrigger>\n                <Menu.Portal>\n                  <Menu.Positioner data-testid=\"share-menu\">\n                    <Menu.Popup>\n                      <Menu.Item data-testid=\"share-item-1\">Email</Menu.Item>\n                      <Menu.Item data-testid=\"share-item-2\">Print</Menu.Item>\n                    </Menu.Popup>\n                  </Menu.Positioner>\n                </Menu.Portal>\n              </Menu.SubmenuRoot>\n            </Menu.Popup>\n          </Menu.Positioner>\n        </Menu.Portal>\n      </Menu.Root>\n      <Menu.Root>\n        <Menu.Trigger data-testid=\"edit-trigger\">Edit</Menu.Trigger>\n        <Menu.Portal>\n          <Menu.Positioner data-testid=\"edit-menu\">\n            <Menu.Popup>\n              <Menu.Item data-testid=\"edit-item-1\">Copy</Menu.Item>\n              <Menu.Item data-testid=\"edit-item-2\">Paste</Menu.Item>\n            </Menu.Popup>\n          </Menu.Positioner>\n        </Menu.Portal>\n      </Menu.Root>\n      <Menu.Root>\n        <Menu.Trigger data-testid=\"view-trigger\">View</Menu.Trigger>\n        <Menu.Portal>\n          <Menu.Positioner data-testid=\"view-menu\">\n            <Menu.Popup>\n              <Menu.Item data-testid=\"view-item-1\">Zoom In</Menu.Item>\n              <Menu.Item data-testid=\"view-item-2\">Zoom Out</Menu.Item>\n              <Menu.SubmenuRoot>\n                <Menu.SubmenuTrigger data-testid=\"layout-trigger\">Layout</Menu.SubmenuTrigger>\n                <Menu.Portal>\n                  <Menu.Positioner data-testid=\"layout-menu\">\n                    <Menu.Popup>\n                      <Menu.RadioGroup defaultValue=\"single\">\n                        <Menu.RadioItem value=\"single\" data-testid=\"layout-item-1\">\n                          Single column\n                        </Menu.RadioItem>\n                        <Menu.RadioItem value=\"two\" data-testid=\"layout-item-2\">\n                          Two columns\n                        </Menu.RadioItem>\n                      </Menu.RadioGroup>\n                    </Menu.Popup>\n                  </Menu.Positioner>\n                </Menu.Portal>\n              </Menu.SubmenuRoot>\n            </Menu.Popup>\n          </Menu.Positioner>\n        </Menu.Portal>\n      </Menu.Root>\n    </Menubar>\n  );\n}\n\nfunction DynamicMenu(props: { handle?: Menu.Handle<MenuDefinition>; children?: React.ReactNode }) {\n  const { handle, children } = props;\n\n  return (\n    <Menu.Root handle={handle}>\n      {({ payload: menu }) => {\n        return (\n          <React.Fragment>\n            {children}\n            <Menu.Portal>\n              <Menu.Positioner data-testid={menu?.menuTestId}>\n                <Menu.Popup>\n                  {(menu?.items ?? []).map((item, index) =>\n                    renderMenuContentItem(item, `item-${index}`),\n                  )}\n                </Menu.Popup>\n              </Menu.Positioner>\n            </Menu.Portal>\n          </React.Fragment>\n        );\n      }}\n    </Menu.Root>\n  );\n}\n\nfunction renderMenuContentItem(item: ContentItem, key: string) {\n  switch (item.type) {\n    case 'item':\n      return (\n        <Menu.Item\n          disabled={item.disabled}\n          closeOnClick={item.closeOnClick}\n          onClick={item.onClick}\n          data-testid={item.testId}\n          key={key}\n        >\n          {item.label}\n        </Menu.Item>\n      );\n\n    case 'submenu':\n      return (\n        <Menu.SubmenuRoot key={key}>\n          <Menu.SubmenuTrigger data-testid={item.testId}>{item.label}</Menu.SubmenuTrigger>\n          <Menu.Portal>\n            <Menu.Positioner data-testid={item.menuTestId}>\n              <Menu.Popup>\n                {item.items.map((subItem, subIndex) =>\n                  renderMenuContentItem(subItem, `${key}.${subIndex}`),\n                )}\n              </Menu.Popup>\n            </Menu.Positioner>\n          </Menu.Portal>\n        </Menu.SubmenuRoot>\n      );\n    case 'radioGroup':\n      return (\n        <Menu.RadioGroup defaultValue={item.defaultValue} key={key}>\n          {item.items.map((radioItem, radioIndex) =>\n            renderMenuContentItem(radioItem, `${key}.${radioIndex}`),\n          )}\n        </Menu.RadioGroup>\n      );\n    case 'radioItem':\n      return (\n        <Menu.RadioItem\n          value={item.value}\n          data-testid={item.testId}\n          disabled={item.disabled}\n          key={key}\n        >\n          {item.label}\n        </Menu.RadioItem>\n      );\n    default:\n      return null;\n  }\n}\n\ntype RadioItem = {\n  type: 'radioItem';\n  label: string;\n  value: string;\n  testId?: string;\n  disabled?: boolean;\n};\n\ntype ContentItem =\n  | {\n      type: 'item';\n      label: string;\n      testId?: string;\n      disabled?: boolean;\n      closeOnClick?: boolean;\n      onClick?: () => void;\n    }\n  | {\n      type: 'submenu';\n      label: string;\n      testId?: string;\n      disabled?: boolean;\n      menuTestId?: string;\n      items: ContentItem[];\n    }\n  | {\n      type: 'radioGroup';\n      defaultValue?: string;\n      items: RadioItem[];\n    }\n  | RadioItem;\n\ntype MenuDefinition = {\n  label: string;\n  triggerTestId?: string;\n  menuTestId?: string;\n  items: ContentItem[];\n};\n\nconst menuContents: Record<string, MenuDefinition> = {\n  file: {\n    label: 'File',\n    triggerTestId: 'file-trigger',\n    menuTestId: 'file-menu',\n    items: [\n      { type: 'item', label: 'Open', testId: 'file-item-1' },\n      { type: 'item', label: 'Save', testId: 'file-item-2' },\n      {\n        type: 'submenu',\n        label: 'Share',\n        testId: 'share-trigger',\n        menuTestId: 'share-menu',\n        items: [\n          { type: 'item', label: 'Email', testId: 'share-item-1' },\n          { type: 'item', label: 'Print', testId: 'share-item-2' },\n        ],\n      },\n    ],\n  },\n  edit: {\n    label: 'Edit',\n    triggerTestId: 'edit-trigger',\n    menuTestId: 'edit-menu',\n    items: [\n      { type: 'item', label: 'Copy', testId: 'edit-item-1' },\n      { type: 'item', label: 'Paste', testId: 'edit-item-2' },\n    ],\n  },\n  view: {\n    label: 'View',\n    triggerTestId: 'view-trigger',\n    menuTestId: 'view-menu',\n    items: [\n      { type: 'item', label: 'Zoom In', testId: 'view-item-1' },\n      { type: 'item', label: 'Zoom Out', testId: 'view-item-2' },\n      {\n        type: 'submenu',\n        label: 'Layout',\n        testId: 'layout-trigger',\n        menuTestId: 'layout-menu',\n        items: [\n          {\n            type: 'radioGroup',\n            defaultValue: 'single',\n            items: [\n              {\n                type: 'radioItem',\n                value: 'single',\n                label: 'Single column',\n                testId: 'layout-item-1',\n              },\n              {\n                type: 'radioItem',\n                value: 'two',\n                label: 'Two columns',\n                testId: 'layout-item-2',\n              },\n            ],\n          },\n        ],\n      },\n    ],\n  },\n};\n\nfunction DetachedTriggerMenubar(props: Menubar.Props) {\n  const testMenuHandle = useRefWithInit(() => new Menu.Handle<MenuDefinition>()).current;\n\n  return (\n    <React.Fragment>\n      <Menubar {...props} style={{ maxWidth: '25vw', display: 'flex' }}>\n        {Object.entries(menuContents).map(([key, menuDef]) => (\n          <Menu.Trigger\n            handle={testMenuHandle}\n            payload={menuDef}\n            key={key}\n            data-testid={menuDef.triggerTestId}\n          >\n            {menuDef.label}\n          </Menu.Trigger>\n        ))}\n      </Menubar>\n      <DynamicMenu handle={testMenuHandle} />\n    </React.Fragment>\n  );\n}\n\nfunction MultipleContainedTriggersMenubar(props: Menubar.Props) {\n  return (\n    <Menubar {...props} style={{ maxWidth: '25vw', display: 'flex' }}>\n      <DynamicMenu>\n        {Object.entries(menuContents).map(([key, menuDef]) => (\n          <Menu.Trigger payload={menuDef} key={key} data-testid={menuDef.triggerTestId}>\n            {menuDef.label}\n          </Menu.Trigger>\n        ))}\n      </DynamicMenu>\n    </Menubar>\n  );\n}\n"
  },
  {
    "path": "packages/react/src/menubar/Menubar.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useScrollLock } from '@base-ui/utils/useScrollLock';\nimport {\n  FloatingNode,\n  FloatingTree,\n  useFloatingNodeId,\n  useFloatingTree,\n} from '../floating-ui-react';\nimport { type MenuRoot } from '../menu/root/MenuRoot';\nimport { BaseUIComponentProps } from '../utils/types';\nimport { MenubarContext, useMenubarContext } from './MenubarContext';\nimport { useOpenInteractionType } from '../utils/useOpenInteractionType';\nimport { CompositeRoot } from '../composite/root/CompositeRoot';\nimport { useBaseUiId } from '../utils/useBaseUiId';\nimport { MenuOpenEventDetails } from '../menu/utils/types';\nimport { StateAttributesMapping } from '../utils/getStateAttributesProps';\n\nconst menubarStateAttributesMapping: StateAttributesMapping<MenubarState> = {\n  hasSubmenuOpen(value) {\n    return {\n      'data-has-submenu-open': value ? 'true' : 'false',\n    };\n  },\n};\n\n/**\n * The container for menus.\n *\n * Documentation: [Base UI Menubar](https://base-ui.com/react/components/menubar)\n */\nexport const Menubar = React.forwardRef(function Menubar(\n  props: Menubar.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const {\n    orientation = 'horizontal',\n    loopFocus = true,\n    render,\n    className,\n    modal = true,\n    disabled = false,\n    id: idProp,\n    ...elementProps\n  } = props;\n\n  const [contentElement, setContentElement] = React.useState<HTMLElement | null>(null);\n  const [hasSubmenuOpen, setHasSubmenuOpen] = React.useState(false);\n\n  const { openMethod, triggerProps: interactionTypeProps } = useOpenInteractionType(hasSubmenuOpen);\n\n  useScrollLock(modal && hasSubmenuOpen && openMethod !== 'touch', contentElement);\n\n  const id = useBaseUiId(idProp);\n\n  const state: MenubarState = {\n    orientation,\n    modal,\n    hasSubmenuOpen,\n  };\n\n  const contentRef = React.useRef<HTMLDivElement>(null);\n  const allowMouseUpTriggerRef = React.useRef(false);\n\n  const context: MenubarContext = React.useMemo(\n    () => ({\n      contentElement,\n      setContentElement,\n      setHasSubmenuOpen,\n      hasSubmenuOpen,\n      modal,\n      disabled,\n      orientation,\n      allowMouseUpTriggerRef,\n      rootId: id,\n    }),\n    [contentElement, hasSubmenuOpen, modal, disabled, orientation, id],\n  );\n\n  return (\n    <MenubarContext.Provider value={context}>\n      <FloatingTree>\n        <MenubarContent>\n          <CompositeRoot\n            render={render}\n            className={className}\n            state={state}\n            stateAttributesMapping={menubarStateAttributesMapping}\n            refs={[forwardedRef, setContentElement, contentRef]}\n            props={[{ role: 'menubar', id }, interactionTypeProps, elementProps]}\n            orientation={orientation}\n            loopFocus={loopFocus}\n            highlightItemOnHover={hasSubmenuOpen}\n          />\n        </MenubarContent>\n      </FloatingTree>\n    </MenubarContext.Provider>\n  );\n});\n\nfunction MenubarContent(props: React.PropsWithChildren<{}>) {\n  const nodeId = useFloatingNodeId();\n  const { events: menuEvents } = useFloatingTree()!;\n  const rootContext = useMenubarContext();\n\n  React.useEffect(() => {\n    function onSubmenuOpenChange(details: MenuOpenEventDetails) {\n      if (!details.nodeId || details.parentNodeId !== nodeId) {\n        return;\n      }\n\n      if (details.open) {\n        if (!rootContext.hasSubmenuOpen) {\n          rootContext.setHasSubmenuOpen(true);\n        }\n      } else if (details.reason !== 'sibling-open' && details.reason !== 'list-navigation') {\n        rootContext.setHasSubmenuOpen(false);\n      }\n    }\n\n    menuEvents.on('menuopenchange', onSubmenuOpenChange);\n\n    return () => {\n      menuEvents.off('menuopenchange', onSubmenuOpenChange);\n    };\n  }, [menuEvents, nodeId, rootContext]);\n\n  return <FloatingNode id={nodeId}>{props.children}</FloatingNode>;\n}\n\nexport interface MenubarState {\n  /**\n   * The orientation of the menubar.\n   */\n  orientation: MenuRoot.Orientation;\n  /**\n   * Whether the menubar is modal.\n   */\n  modal: boolean;\n  /**\n   * Whether any submenu within the menubar is open.\n   */\n  hasSubmenuOpen: boolean;\n}\n\nexport interface MenubarProps extends BaseUIComponentProps<'div', MenubarState> {\n  /**\n   * Whether the menubar is modal.\n   * @default true\n   */\n  modal?: boolean | undefined;\n  /**\n   * Whether the whole menubar is disabled.\n   * @default false\n   */\n  disabled?: boolean | undefined;\n  /**\n   * The orientation of the menubar.\n   * @default 'horizontal'\n   */\n  orientation?: MenuRoot.Orientation | undefined;\n  /**\n   * Whether to loop keyboard focus back to the first item\n   * when the end of the list is reached while using the arrow keys.\n   * @default true\n   */\n  loopFocus?: boolean | undefined;\n}\n\nexport namespace Menubar {\n  export type State = MenubarState;\n  export type Props = MenubarProps;\n}\n"
  },
  {
    "path": "packages/react/src/menubar/MenubarContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { type MenuRoot } from '../menu/root/MenuRoot';\n\nexport interface MenubarContext {\n  modal: boolean;\n  disabled: boolean;\n  contentElement: HTMLElement | null;\n  setContentElement: (element: HTMLElement | null) => void;\n  hasSubmenuOpen: boolean;\n  setHasSubmenuOpen: (open: boolean) => void;\n  orientation: MenuRoot.Orientation;\n  allowMouseUpTriggerRef: React.RefObject<boolean>;\n  rootId: string | undefined;\n}\n\nexport const MenubarContext = React.createContext<MenubarContext | null>(null);\n\nexport function useMenubarContext(optional?: false): MenubarContext;\nexport function useMenubarContext(optional: true): MenubarContext | null;\nexport function useMenubarContext(optional?: boolean) {\n  const context = React.useContext(MenubarContext);\n  if (context === null && !optional) {\n    throw new Error(\n      'Base UI: MenubarContext is missing. Menubar parts must be placed within <Menubar>.',\n    );\n  }\n\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/menubar/MenubarDataAttributes.ts",
    "content": "export enum MenuTriggerDataAttributes {\n  /**\n   * Present when the corresponding menubar is modal.\n   */\n  modal = 'data-modal',\n  /**\n   * Determines the orientation of the menubar.\n   * @type 'horizontal' | 'vertical'\n   * @default 'horizontal'\n   */\n  orientation = 'data-orientation',\n}\n"
  },
  {
    "path": "packages/react/src/menubar/index.ts",
    "content": "export { Menubar } from './Menubar';\n\nexport type * from './Menubar';\n"
  },
  {
    "path": "packages/react/src/merge-props/index.ts",
    "content": "export * from './mergeProps';\n"
  },
  {
    "path": "packages/react/src/merge-props/mergeProps.test.ts",
    "content": "import { expect, vi } from 'vitest';\nimport { mergeProps, mergePropsN } from '@base-ui/react/merge-props';\nimport type { BaseUIEvent } from '../utils/types';\n\ndescribe('mergeProps', () => {\n  it('merges event handlers', () => {\n    const theirProps = {\n      onClick: vi.fn(),\n      onKeyDown: vi.fn(),\n    };\n    const ourProps = {\n      onClick: vi.fn(),\n      onPaste: vi.fn(),\n    };\n    const mergedProps = mergeProps<'button'>(ourProps, theirProps);\n\n    mergedProps.onClick?.({ nativeEvent: new MouseEvent('click') } as any);\n    mergedProps.onKeyDown?.({ nativeEvent: new KeyboardEvent('keydown') } as any);\n    mergedProps.onPaste?.({ nativeEvent: new Event('paste') } as any);\n\n    expect(theirProps.onClick.mock.invocationCallOrder[0]).toBeLessThan(\n      ourProps.onClick.mock.invocationCallOrder[0],\n    );\n    expect(theirProps.onClick.mock.calls.length).toBe(1);\n    expect(ourProps.onClick.mock.calls.length).toBe(1);\n    expect(theirProps.onKeyDown.mock.calls.length).toBe(1);\n    expect(ourProps.onPaste.mock.calls.length).toBe(1);\n  });\n\n  it('merges multiple event handlers', () => {\n    const log: string[] = [];\n\n    const mergedProps = mergeProps<'button'>(\n      {\n        onClick() {\n          log.push('3');\n        },\n      },\n      {\n        onClick() {\n          log.push('2');\n        },\n      },\n      {\n        onClick() {\n          log.push('1');\n        },\n      },\n    );\n\n    mergedProps.onClick?.({ nativeEvent: new MouseEvent('click') } as any);\n    expect(log).toEqual(['1', '2', '3']);\n  });\n\n  it('merges undefined event handlers', () => {\n    const log: string[] = [];\n\n    const mergedProps = mergeProps<'button'>(\n      {\n        onClick() {\n          log.push('3');\n        },\n      },\n      {\n        onClick: undefined,\n      },\n      {\n        onClick() {\n          log.push('1');\n        },\n      },\n    );\n\n    mergedProps.onClick?.({ nativeEvent: new MouseEvent('click') } as any);\n    expect(log).toEqual(['1', '3']);\n  });\n\n  it('makes a lone synthetic event handler preventable', () => {\n    let prevented = false;\n\n    const mergedProps = mergeProps<'button'>(\n      {},\n      {\n        onMouseDown(event) {\n          event.preventBaseUIHandler();\n          prevented = event.baseUIHandlerPrevented === true;\n        },\n      },\n    );\n\n    mergedProps.onMouseDown?.({ nativeEvent: new MouseEvent('mousedown') } as any);\n\n    expect(prevented).toBe(true);\n  });\n\n  it('makes a first-position synthetic event handler preventable', () => {\n    let prevented = false;\n\n    const mergedProps = mergeProps<'button'>(\n      {\n        onMouseDown(event) {\n          event.preventBaseUIHandler();\n          prevented = event.baseUIHandlerPrevented === true;\n        },\n      },\n      {\n        id: 'test-button',\n      },\n    );\n\n    mergedProps.onMouseDown?.({ nativeEvent: new MouseEvent('mousedown') } as any);\n\n    expect(prevented).toBe(true);\n  });\n\n  it('makes a first-position synthetic event handler preventable in mergePropsN', () => {\n    let prevented = false;\n\n    const mergedProps = mergePropsN<'button'>([\n      {\n        onMouseDown(event) {\n          event.preventBaseUIHandler();\n          prevented = event.baseUIHandlerPrevented === true;\n        },\n      },\n      {\n        id: 'test-button',\n      },\n    ]);\n\n    mergedProps.onMouseDown?.({ nativeEvent: new MouseEvent('mousedown') } as any);\n\n    expect(prevented).toBe(true);\n  });\n\n  it('makes a lone obscure synthetic event handler preventable', () => {\n    let prevented = false;\n\n    const mergedProps = mergeProps<'button'>(\n      {},\n      {\n        onContextMenu(event) {\n          event.preventBaseUIHandler();\n          prevented = event.baseUIHandlerPrevented === true;\n        },\n      },\n    );\n\n    mergedProps.onContextMenu?.({ nativeEvent: new MouseEvent('contextmenu') } as any);\n\n    expect(prevented).toBe(true);\n  });\n\n  it('merges styles', () => {\n    const theirProps = {\n      style: { color: 'red' },\n    };\n    const ourProps = {\n      style: { color: 'blue', backgroundColor: 'blue' },\n    };\n    const mergedProps = mergeProps<'div'>(ourProps, theirProps);\n\n    expect(mergedProps.style).toEqual({\n      color: 'red',\n      backgroundColor: 'blue',\n    });\n  });\n\n  it('merges styles with undefined', () => {\n    const theirProps = {\n      style: { color: 'red' },\n    };\n    const ourProps = {};\n\n    const mergedProps = mergeProps<'button'>(ourProps, theirProps);\n\n    expect(mergedProps.style).toEqual({\n      color: 'red',\n    });\n  });\n\n  it('does not merge styles if both are undefined', () => {\n    const theirProps = {};\n    const ourProps = {};\n    const mergedProps = mergeProps<'button'>(ourProps, theirProps);\n\n    expect(mergedProps.style).toBe(undefined);\n  });\n\n  it('merges classNames with rightmost first', () => {\n    const theirProps = {\n      className: 'external-class',\n    };\n    const ourProps = {\n      className: 'internal-class',\n    };\n    const mergedProps = mergeProps<'div'>(ourProps, theirProps);\n\n    expect(mergedProps.className).toBe('external-class internal-class');\n  });\n\n  it('merges multiple classNames', () => {\n    const mergedProps = mergeProps<'div'>(\n      {\n        className: 'class-1',\n      },\n      {\n        className: 'class-2',\n      },\n      {\n        className: 'class-3',\n      },\n    );\n\n    expect(mergedProps.className).toBe('class-3 class-2 class-1');\n  });\n\n  it('merges classNames with undefined', () => {\n    const theirProps = {\n      className: 'external-class',\n    };\n    const ourProps = {};\n\n    const mergedProps = mergeProps<'button'>(ourProps, theirProps);\n\n    expect(mergedProps.className).toBe('external-class');\n  });\n\n  it('does not merge classNames if both are undefined', () => {\n    const theirProps = {};\n    const ourProps = {};\n    const mergedProps = mergeProps<'button'>(ourProps, theirProps);\n\n    expect(mergedProps.className).toBe(undefined);\n  });\n\n  it('does not prevent internal handler if event.preventBaseUIHandler() is not called', () => {\n    let ran = false;\n\n    const mergedProps = mergeProps<'button'>(\n      {\n        onClick() {},\n      },\n      {\n        onClick() {\n          ran = true;\n        },\n      },\n    );\n\n    mergedProps.onClick?.({ nativeEvent: new MouseEvent('click') } as any);\n\n    expect(ran).toBe(true);\n  });\n\n  it('prevents internal handler if event.preventBaseUIHandler() is called', () => {\n    let ran = false;\n\n    const mergedProps = mergeProps<'button'>(\n      {\n        onClick: function onClick3() {\n          ran = true;\n        },\n      },\n      {\n        onClick: function onClick2() {\n          ran = true;\n        },\n      },\n      {\n        onClick: function onClick1(event) {\n          event.preventBaseUIHandler();\n        },\n      },\n    );\n\n    const event = { nativeEvent: new MouseEvent('click') } as any;\n    mergedProps.onClick?.(event);\n\n    expect(ran).toBe(false);\n  });\n\n  it('prevents handlers merged after event.preventBaseUIHandler() is called', () => {\n    const log: string[] = [];\n\n    const mergedProps = mergeProps<any>(\n      {\n        onClick() {\n          log.push('2');\n        },\n      },\n      {\n        onClick(event: BaseUIEvent<React.MouseEvent>) {\n          event.preventBaseUIHandler();\n          log.push('1');\n        },\n      },\n      {\n        onClick() {\n          log.push('0');\n        },\n      },\n    );\n\n    mergedProps.onClick?.({ nativeEvent: new MouseEvent('click') });\n\n    expect(log).toEqual(['0', '1']);\n  });\n\n  [true, 13, 'newValue', { key: 'value' }, ['value'], () => 'value'].forEach((eventArgument) => {\n    it('handles non-standard event handlers without error', () => {\n      const log: string[] = [];\n\n      const mergedProps = mergeProps<any>(\n        {\n          onValueChange() {\n            log.push('1');\n          },\n        },\n        {\n          onValueChange() {\n            log.push('0');\n          },\n        },\n      );\n\n      mergedProps.onValueChange(eventArgument);\n\n      expect(log).toEqual(['0', '1']);\n    });\n  });\n\n  it('merges internal props so that the ones defined first override the ones defined later', () => {\n    const mergedProps = mergeProps<'button'>(\n      {\n        title: 'internal title 2',\n      },\n      {\n        title: 'internal title 1',\n      },\n      {},\n    );\n\n    expect(mergedProps.title).toBe('internal title 1');\n  });\n\n  it('sets baseUIHandlerPrevented to true after calling preventBaseUIHandler()', () => {\n    let observedFlag: boolean | undefined;\n\n    const mergedProps = mergeProps<'button'>(\n      {\n        onClick() {},\n      },\n      {\n        onClick(event) {\n          event.preventBaseUIHandler();\n          observedFlag = event.baseUIHandlerPrevented;\n        },\n      },\n    );\n\n    mergedProps.onClick?.({ nativeEvent: new MouseEvent('click') } as any);\n\n    expect(observedFlag).toBe(true);\n  });\n\n  describe('props getters', () => {\n    it('calls the props getter with the props defined after it', () => {\n      let observedProps;\n      const propsGetter = vi.fn((props) => {\n        observedProps = { ...props };\n        return props;\n      });\n\n      mergeProps(\n        {\n          id: '2',\n          className: 'test-class',\n        },\n        propsGetter,\n        {\n          id: '1',\n          role: 'button',\n        },\n      );\n\n      expect(propsGetter.mock.calls.length === 1).toBe(true);\n      expect(observedProps).toEqual({ id: '2', className: 'test-class' });\n    });\n\n    it('calls the props getter with merged props defined after it', () => {\n      let observedProps;\n      const propsGetter = vi.fn((props) => {\n        observedProps = { ...props };\n        return props;\n      });\n\n      mergeProps(\n        {\n          role: 'button',\n          className: 'test-class',\n        },\n        {\n          role: 'tab',\n        },\n        propsGetter,\n        {\n          id: 'one',\n        },\n      );\n\n      expect(propsGetter.mock.calls.length === 1).toBe(true);\n      expect(observedProps).toEqual({\n        role: 'tab',\n        className: 'test-class',\n      });\n    });\n\n    it('calls the props getter with an empty object if no props are defined after it', () => {\n      let observedProps;\n      const propsGetter = vi.fn((props) => {\n        observedProps = { ...props };\n        return props;\n      });\n\n      mergeProps(propsGetter, { id: '1' });\n\n      expect(propsGetter.mock.calls.length === 1).toBe(true);\n      expect(observedProps).toEqual({});\n    });\n\n    it('accepts the result of the props getter', () => {\n      const propsGetter = () => ({ className: 'test-class' });\n      const result = mergeProps(\n        {\n          id: 'two',\n          role: 'tab',\n        },\n        {\n          id: 'one',\n        },\n        propsGetter,\n      );\n\n      expect(result).toEqual({\n        className: 'test-class',\n      });\n    });\n\n    it('does not automatically prevent handlers that are manually called by getter handlers', () => {\n      const log: string[] = [];\n\n      const mergedProps = mergeProps<'button'>(\n        {\n          onClick() {\n            log.push('first-handler');\n          },\n        },\n        (props) => ({\n          onClick(event: BaseUIEvent<React.MouseEvent>) {\n            // Call preventBaseUIHandler to signal prevention\n            event.preventBaseUIHandler();\n            log.push('getter-handler');\n            // Manually calling the previous handler - this bypasses automatic prevention!\n            props.onClick?.({ nativeEvent: new MouseEvent('click') } as any);\n          },\n        }),\n        {\n          onClick() {\n            // This handler does NOT call preventBaseUIHandler, so getter-handler runs\n            log.push('last-handler');\n          },\n        },\n      );\n\n      mergedProps.onClick?.({ nativeEvent: new MouseEvent('click') } as any);\n\n      // last-handler runs first, then getter-handler (not prevented), then getter-handler\n      // manually calls first-handler which runs despite preventBaseUIHandler being called\n      expect(log).toEqual(['last-handler', 'getter-handler', 'first-handler']);\n    });\n\n    it('allows props getter handlers to check baseUIHandlerPrevented manually', () => {\n      const log: string[] = [];\n\n      const mergedProps = mergeProps<'button'>(\n        {\n          onClick() {\n            log.push('first-handler');\n          },\n        },\n        (props) => ({\n          onClick(event: BaseUIEvent<React.MouseEvent>) {\n            // Call preventBaseUIHandler to signal prevention\n            event.preventBaseUIHandler();\n            log.push('getter-handler');\n            // Check the flag before manually calling previous handlers - this respects prevention\n            if (!event.baseUIHandlerPrevented) {\n              props.onClick?.({ nativeEvent: new MouseEvent('click') } as any);\n            }\n          },\n        }),\n        {\n          onClick() {\n            // This handler does NOT call preventBaseUIHandler, so getter-handler runs\n            log.push('last-handler');\n          },\n        },\n      );\n\n      mergedProps.onClick?.({ nativeEvent: new MouseEvent('click') } as any);\n\n      // first-handler does NOT run because getter-handler checks the flag before calling it\n      expect(log).toEqual(['last-handler', 'getter-handler']);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/merge-props/mergeProps.ts",
    "content": "import * as React from 'react';\nimport { mergeObjects } from '@base-ui/utils/mergeObjects';\nimport type { BaseUIEvent, WithBaseUIEvent } from '../utils/types';\n\ntype ElementType = React.ElementType;\ntype PropsOf<T extends React.ElementType> = WithBaseUIEvent<React.ComponentPropsWithRef<T>>;\ntype InputProps<T extends React.ElementType> =\n  | PropsOf<T>\n  | ((otherProps: PropsOf<T>) => PropsOf<T>)\n  | undefined;\n\nconst EMPTY_PROPS = {};\n\n/* eslint-disable id-denylist */\n/**\n * Merges multiple sets of React props. It follows the Object.assign pattern where the rightmost object's fields overwrite\n * the conflicting ones from others. This doesn't apply to event handlers, `className` and `style` props.\n *\n * Event handlers are merged and called in right-to-left order (rightmost handler executes first, leftmost last).\n * For React synthetic events, the rightmost handler can prevent prior (left-positioned) handlers from executing\n * by calling `event.preventBaseUIHandler()`. For non-synthetic events (custom events with primitive/object values),\n * all handlers always execute without prevention capability.\n *\n * The `className` prop is merged by concatenating classes in right-to-left order (rightmost class appears first in the string).\n * The `style` prop is merged with rightmost styles overwriting the prior ones.\n *\n * Props can either be provided as objects or as functions that take the previous props as an argument.\n * The function will receive the merged props up to that point (going from left to right):\n * so in the case of `(obj1, obj2, fn, obj3)`, `fn` will receive the merged props of `obj1` and `obj2`.\n * The function is responsible for chaining event handlers if needed (i.e. we don't run the merge logic).\n *\n * Event handlers returned by the functions are not automatically prevented when `preventBaseUIHandler` is called.\n * They must check `event.baseUIHandlerPrevented` themselves and bail out if it's true.\n *\n * @important **`ref` is not merged.**\n * @param a Props object to merge.\n * @param b Props object to merge. The function will overwrite conflicting props from `a`.\n * @param c Props object to merge. The function will overwrite conflicting props from previous parameters.\n * @param d Props object to merge. The function will overwrite conflicting props from previous parameters.\n * @param e Props object to merge. The function will overwrite conflicting props from previous parameters.\n * @returns The merged props.\n * @public\n */\nexport function mergeProps<T extends ElementType>(\n  a: InputProps<T>,\n  b: InputProps<T>,\n  c: InputProps<T>,\n  d: InputProps<T>,\n  e: InputProps<T>,\n): PropsOf<T>;\nexport function mergeProps<T extends ElementType>(\n  a: InputProps<T>,\n  b: InputProps<T>,\n  c: InputProps<T>,\n  d: InputProps<T>,\n): PropsOf<T>;\nexport function mergeProps<T extends ElementType>(\n  a: InputProps<T>,\n  b: InputProps<T>,\n  c: InputProps<T>,\n): PropsOf<T>;\nexport function mergeProps<T extends ElementType>(a: InputProps<T>, b: InputProps<T>): PropsOf<T>;\nexport function mergeProps(a: any, b: any, c?: any, d?: any, e?: any) {\n  if (!c && !d && !e && !a) {\n    return createInitialMergedProps(b);\n  }\n\n  let merged = createInitialMergedProps(a);\n\n  if (b) {\n    merged = mergeOne(merged, b);\n  }\n  if (c) {\n    merged = mergeOne(merged, c);\n  }\n  if (d) {\n    merged = mergeOne(merged, d);\n  }\n  if (e) {\n    merged = mergeOne(merged, e);\n  }\n\n  return merged;\n}\n/* eslint-enable id-denylist */\n\n/**\n * Merges an arbitrary number of React props using the same logic as {@link mergeProps}.\n * This function accepts an array of props instead of individual arguments.\n *\n * This has slightly lower performance than {@link mergeProps} due to accepting an array\n * instead of a fixed number of arguments. Prefer {@link mergeProps} when merging 5 or\n * fewer prop sets for better performance.\n *\n * @param props Array of props to merge.\n * @returns The merged props.\n * @see mergeProps\n * @public\n */\nexport function mergePropsN<T extends ElementType>(props: InputProps<T>[]): PropsOf<T> {\n  if (props.length === 0) {\n    return EMPTY_PROPS as PropsOf<T>;\n  }\n  if (props.length === 1) {\n    const firstProps = props[0];\n\n    if (isPropsGetter(firstProps)) {\n      return resolvePropsGetter(firstProps, EMPTY_PROPS) as PropsOf<T>;\n    }\n\n    return copyPropsWithWrappedEventHandlers(firstProps) as PropsOf<T>;\n  }\n\n  let merged = createInitialMergedProps(props[0]);\n\n  for (let i = 1; i < props.length; i += 1) {\n    merged = mergeOne(merged, props[i]);\n  }\n\n  return merged as PropsOf<T>;\n}\n\nfunction mergeOne<T extends ElementType>(merged: Record<string, any>, inputProps: InputProps<T>) {\n  if (isPropsGetter(inputProps)) {\n    return inputProps(merged);\n  }\n  return mutablyMergeInto(merged, inputProps);\n}\n\nfunction createInitialMergedProps<T extends ElementType>(inputProps: InputProps<T>) {\n  if (isPropsGetter(inputProps)) {\n    // Getter-returned handlers intentionally keep their existing semantics.\n    return { ...resolvePropsGetter(inputProps, EMPTY_PROPS) };\n  }\n\n  return copyPropsWithWrappedEventHandlers(inputProps);\n}\n\nfunction copyPropsWithWrappedEventHandlers<T extends ElementType>(\n  inputProps: React.ComponentPropsWithRef<T> | undefined,\n) {\n  const copiedProps = { ...inputProps } as Record<string, any>;\n\n  for (const propName in copiedProps) {\n    if (isEventHandler(propName, copiedProps[propName])) {\n      copiedProps[propName] = wrapEventHandler(copiedProps[propName]);\n    }\n  }\n\n  return copiedProps;\n}\n\n/**\n * Merges two sets of props. In case of conflicts, the external props take precedence.\n */\nfunction mutablyMergeInto<T extends ElementType>(\n  mergedProps: Record<string, any>,\n  externalProps: React.ComponentPropsWithRef<T> | undefined,\n) {\n  if (!externalProps) {\n    return mergedProps;\n  }\n\n  // eslint-disable-next-line guard-for-in\n  for (const propName in externalProps) {\n    const externalPropValue = externalProps[propName];\n\n    switch (propName) {\n      case 'style': {\n        mergedProps[propName] = mergeObjects(\n          mergedProps.style as React.CSSProperties | undefined,\n          externalPropValue as React.CSSProperties | undefined,\n        );\n        break;\n      }\n      case 'className': {\n        mergedProps[propName] = mergeClassNames(mergedProps.className, externalPropValue as string);\n        break;\n      }\n      default: {\n        if (isEventHandler(propName, externalPropValue)) {\n          mergedProps[propName] = mergeEventHandlers(mergedProps[propName], externalPropValue);\n        } else {\n          mergedProps[propName] = externalPropValue;\n        }\n      }\n    }\n  }\n\n  return mergedProps;\n}\n\nfunction isEventHandler(key: string, value: unknown) {\n  // This approach is more efficient than using a regex.\n  const code0 = key.charCodeAt(0);\n  const code1 = key.charCodeAt(1);\n  const code2 = key.charCodeAt(2);\n  return (\n    code0 === 111 /* o */ &&\n    code1 === 110 /* n */ &&\n    code2 >= 65 /* A */ &&\n    code2 <= 90 /* Z */ &&\n    (typeof value === 'function' || typeof value === 'undefined')\n  );\n}\n\nfunction isPropsGetter<T extends React.ComponentType>(\n  inputProps: InputProps<T>,\n): inputProps is (props: PropsOf<T>) => PropsOf<T> {\n  return typeof inputProps === 'function';\n}\n\nfunction resolvePropsGetter<T extends ElementType>(\n  inputProps: InputProps<ElementType>,\n  previousProps: PropsOf<T>,\n) {\n  if (isPropsGetter(inputProps)) {\n    return inputProps(previousProps);\n  }\n\n  return inputProps ?? (EMPTY_PROPS as PropsOf<T>);\n}\n\nfunction mergeEventHandlers(ourHandler: Function | undefined, theirHandler: Function | undefined) {\n  if (!theirHandler) {\n    return ourHandler;\n  }\n  if (!ourHandler) {\n    return wrapEventHandler(theirHandler);\n  }\n\n  return (event: unknown) => {\n    if (isSyntheticEvent(event)) {\n      const baseUIEvent = event as BaseUIEvent<typeof event>;\n\n      makeEventPreventable(baseUIEvent);\n\n      const result = theirHandler(baseUIEvent);\n\n      if (!baseUIEvent.baseUIHandlerPrevented) {\n        ourHandler?.(baseUIEvent);\n      }\n\n      return result;\n    }\n\n    const result = theirHandler(event);\n    ourHandler?.(event);\n    return result;\n  };\n}\n\nfunction wrapEventHandler(handler: Function | undefined) {\n  if (!handler) {\n    return handler;\n  }\n\n  return (event: unknown) => {\n    if (isSyntheticEvent(event)) {\n      makeEventPreventable(event as BaseUIEvent<typeof event>);\n    }\n\n    return handler(event);\n  };\n}\n\nexport function makeEventPreventable<T extends React.SyntheticEvent>(event: BaseUIEvent<T>) {\n  event.preventBaseUIHandler = () => {\n    (event.baseUIHandlerPrevented as boolean) = true;\n  };\n\n  return event;\n}\n\nexport function mergeClassNames(\n  ourClassName: string | undefined,\n  theirClassName: string | undefined,\n) {\n  if (theirClassName) {\n    if (ourClassName) {\n      // eslint-disable-next-line prefer-template\n      return theirClassName + ' ' + ourClassName;\n    }\n\n    return theirClassName;\n  }\n\n  return ourClassName;\n}\n\nfunction isSyntheticEvent(event: unknown): event is React.SyntheticEvent {\n  return event != null && typeof event === 'object' && 'nativeEvent' in event;\n}\n"
  },
  {
    "path": "packages/react/src/meter/index.parts.ts",
    "content": "export { MeterRoot as Root } from './root/MeterRoot';\nexport { MeterTrack as Track } from './track/MeterTrack';\nexport { MeterIndicator as Indicator } from './indicator/MeterIndicator';\nexport { MeterValue as Value } from './value/MeterValue';\nexport { MeterLabel as Label } from './label/MeterLabel';\n"
  },
  {
    "path": "packages/react/src/meter/index.ts",
    "content": "export * as Meter from './index.parts';\n\nexport type * from './root/MeterRoot';\nexport type * from './indicator/MeterIndicator';\nexport type * from './label/MeterLabel';\nexport type * from './track/MeterTrack';\nexport type * from './value/MeterValue';\n"
  },
  {
    "path": "packages/react/src/meter/indicator/MeterIndicator.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { screen } from '@mui/internal-test-utils';\nimport { Meter } from '@base-ui/react/meter';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\n\ndescribe('<Meter.Indicator />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Meter.Indicator />, () => ({\n    render: (node) => {\n      return render(<Meter.Root value={30}>{node}</Meter.Root>);\n    },\n    refInstanceof: window.HTMLDivElement,\n  }));\n\n  describe.skipIf(isJSDOM)('internal styles', () => {\n    it('sets positioning styles', async () => {\n      await render(\n        <Meter.Root value={33} style={{ width: '100px' }}>\n          <Meter.Track>\n            <Meter.Indicator data-testid=\"indicator\" />\n          </Meter.Track>\n        </Meter.Root>,\n      );\n\n      const indicator = screen.getByTestId('indicator');\n\n      expect(indicator).toHaveComputedStyle({\n        left: '0px',\n        width: '33px',\n      });\n    });\n\n    it('sets zero width when value is 0', async () => {\n      await render(\n        <Meter.Root value={0} style={{ width: '100px' }}>\n          <Meter.Track>\n            <Meter.Indicator data-testid=\"indicator\" />\n          </Meter.Track>\n        </Meter.Root>,\n      );\n\n      const indicator = screen.getByTestId('indicator');\n\n      expect(indicator).toHaveComputedStyle({\n        insetInlineStart: '0px',\n        width: '0px',\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/meter/indicator/MeterIndicator.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { valueToPercent } from '../../utils/valueToPercent';\nimport type { MeterRootState } from '../root/MeterRoot';\nimport { useMeterRootContext } from '../root/MeterRootContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\n\n/**\n * Visualizes the position of the value along the range.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Meter](https://base-ui.com/react/components/meter)\n */\nexport const MeterIndicator = React.forwardRef(function MeterIndicator(\n  componentProps: MeterIndicator.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, ...elementProps } = componentProps;\n\n  const context = useMeterRootContext();\n\n  const percentageWidth = valueToPercent(context.value, context.min, context.max);\n\n  return useRenderElement('div', componentProps, {\n    ref: forwardedRef,\n    props: [\n      {\n        style: {\n          insetInlineStart: 0,\n          height: 'inherit',\n          width: `${percentageWidth}%`,\n        },\n      },\n      elementProps,\n    ],\n  });\n});\n\nexport interface MeterIndicatorState extends MeterRootState {}\n\nexport interface MeterIndicatorProps extends BaseUIComponentProps<'div', MeterIndicatorState> {}\n\nexport namespace MeterIndicator {\n  export type State = MeterIndicatorState;\n  export type Props = MeterIndicatorProps;\n}\n"
  },
  {
    "path": "packages/react/src/meter/label/MeterLabel.test.tsx",
    "content": "import { Meter } from '@base-ui/react/meter';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Meter.Label />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Meter.Label />, () => ({\n    render: (node) => {\n      return render(<Meter.Root value={50}>{node}</Meter.Root>);\n    },\n    refInstanceof: window.HTMLSpanElement,\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/meter/label/MeterLabel.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useMeterRootContext } from '../root/MeterRootContext';\nimport type { MeterRootState } from '../root/MeterRoot';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useRegisteredLabelId } from '../../utils/useRegisteredLabelId';\n\n/**\n * An accessible label for the meter.\n * Renders a `<span>` element.\n *\n * Documentation: [Base UI Meter](https://base-ui.com/react/components/meter)\n */\nexport const MeterLabel = React.forwardRef(function MeterLabel(\n  componentProps: MeterLabel.Props,\n  forwardedRef: React.ForwardedRef<HTMLSpanElement>,\n) {\n  const { render, className, id: idProp, ...elementProps } = componentProps;\n\n  const { setLabelId } = useMeterRootContext();\n\n  const id = useRegisteredLabelId(idProp, setLabelId);\n\n  return useRenderElement('span', componentProps, {\n    ref: forwardedRef,\n    props: [\n      {\n        id,\n        role: 'presentation',\n      },\n      elementProps,\n    ],\n  });\n});\n\nexport interface MeterLabelState extends MeterRootState {}\n\nexport interface MeterLabelProps extends BaseUIComponentProps<'span', MeterLabelState> {}\n\nexport namespace MeterLabel {\n  export type State = MeterLabelState;\n  export type Props = MeterLabelProps;\n}\n"
  },
  {
    "path": "packages/react/src/meter/root/MeterRoot.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { Meter } from '@base-ui/react/meter';\nimport { screen } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Meter.Root />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Meter.Root value={50} />, () => ({\n    render,\n    refInstanceof: window.HTMLDivElement,\n  }));\n\n  describe('ARIA attributes', () => {\n    it('sets the correct aria attributes', async () => {\n      await render(\n        <Meter.Root value={30}>\n          <Meter.Label>Battery Level</Meter.Label>\n          <Meter.Track>\n            <Meter.Indicator />\n          </Meter.Track>\n        </Meter.Root>,\n      );\n\n      const meter = screen.getByRole('meter');\n\n      expect(meter).toHaveAttribute('aria-valuenow', '30');\n      expect(meter).toHaveAttribute('aria-valuemin', '0');\n      expect(meter).toHaveAttribute('aria-valuemax', '100');\n      expect(meter).toHaveAttribute('aria-valuetext', '30%');\n      expect(meter.getAttribute('aria-labelledby')).toBe(\n        screen.getByText('Battery Level').getAttribute('id'),\n      );\n    });\n\n    it('should update aria-valuenow when value changes', async () => {\n      const { setProps } = await render(\n        <Meter.Root value={50}>\n          <Meter.Track>\n            <Meter.Indicator />\n          </Meter.Track>\n        </Meter.Root>,\n      );\n      const meter = screen.getByRole('meter');\n      await setProps({ value: 77 });\n      expect(meter).toHaveAttribute('aria-valuenow', '77');\n    });\n  });\n\n  describe('prop: format', () => {\n    it('formats the value', async () => {\n      const format: Intl.NumberFormatOptions = {\n        style: 'currency',\n        currency: 'USD',\n      };\n      function formatValue(v: number) {\n        return new Intl.NumberFormat(undefined, format).format(v);\n      }\n\n      await render(\n        <Meter.Root value={30} format={format}>\n          <Meter.Value data-testid=\"value\" />\n          <Meter.Track>\n            <Meter.Indicator />\n          </Meter.Track>\n        </Meter.Root>,\n      );\n\n      const value = screen.getByTestId('value');\n      const meter = screen.getByRole('meter');\n      expect(value).toHaveTextContent(formatValue(30));\n      expect(meter).toHaveAttribute('aria-valuetext', formatValue(30));\n    });\n  });\n\n  describe('prop: locale', () => {\n    it('sets the locale when formatting the value', async () => {\n      // In German locale, numbers use dot as thousands separator and comma as decimal separator\n      const expectedValue = new Intl.NumberFormat('de-DE').format(86.49);\n\n      await render(\n        <Meter.Root\n          value={86.49}\n          format={{\n            style: 'decimal',\n            minimumFractionDigits: 2,\n            maximumFractionDigits: 2,\n          }}\n          locale=\"de-DE\"\n        >\n          <Meter.Value data-testid=\"value\" />\n        </Meter.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent(expectedValue);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/meter/root/MeterRoot.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { visuallyHidden } from '@base-ui/utils/visuallyHidden';\nimport { MeterRootContext } from './MeterRootContext';\nimport { BaseUIComponentProps, HTMLProps } from '../../utils/types';\nimport { formatNumberValue } from '../../utils/formatNumber';\nimport { useRenderElement } from '../../utils/useRenderElement';\n\n/**\n * Groups all parts of the meter and provides the value for screen readers.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Meter](https://base-ui.com/react/components/meter)\n */\nexport const MeterRoot = React.forwardRef(function MeterRoot(\n  componentProps: MeterRoot.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const {\n    format,\n    getAriaValueText,\n    locale,\n    max = 100,\n    min = 0,\n    value: valueProp,\n    render,\n    className,\n    children,\n    ...elementProps\n  } = componentProps;\n\n  const [labelId, setLabelId] = React.useState<string | undefined>();\n  const formattedValue = formatNumberValue(valueProp, locale, format);\n\n  let ariaValuetext = `${valueProp}%`;\n  if (getAriaValueText) {\n    ariaValuetext = getAriaValueText(formattedValue, valueProp);\n  } else if (format) {\n    ariaValuetext = formattedValue;\n  }\n\n  const defaultProps: HTMLProps = {\n    'aria-labelledby': labelId,\n    'aria-valuemax': max,\n    'aria-valuemin': min,\n    'aria-valuenow': valueProp,\n    'aria-valuetext': ariaValuetext,\n    role: 'meter',\n    children: (\n      <React.Fragment>\n        {children}\n        <span role=\"presentation\" style={visuallyHidden}>\n          {/* force NVDA to read the label https://github.com/mui/base-ui/issues/4184 */}x\n        </span>\n      </React.Fragment>\n    ),\n  };\n\n  const contextValue: MeterRootContext = React.useMemo(\n    () => ({\n      formattedValue,\n      max,\n      min,\n      setLabelId,\n      value: valueProp,\n    }),\n    [formattedValue, max, min, setLabelId, valueProp],\n  );\n\n  const element = useRenderElement('div', componentProps, {\n    ref: forwardedRef,\n    props: [defaultProps, elementProps],\n  });\n\n  return <MeterRootContext.Provider value={contextValue}>{element}</MeterRootContext.Provider>;\n});\nexport interface MeterRootState {}\nexport interface MeterRootProps extends BaseUIComponentProps<'div', MeterRootState> {\n  /**\n   * A string value that provides a user-friendly name for `aria-valuenow`, the current value of the meter.\n   */\n  'aria-valuetext'?: React.AriaAttributes['aria-valuetext'] | undefined;\n  /**\n   * Options to format the value.\n   */\n  format?: Intl.NumberFormatOptions | undefined;\n  /**\n   * A function that returns a string value that provides a human-readable text alternative for `aria-valuenow`, the current value of the meter.\n   */\n  getAriaValueText?: ((formattedValue: string, value: number) => string) | undefined;\n  /**\n   * The locale used by `Intl.NumberFormat` when formatting the value.\n   * Defaults to the user's runtime locale.\n   */\n  locale?: Intl.LocalesArgument | undefined;\n  /**\n   * The maximum value\n   * @default 100\n   */\n  max?: number | undefined;\n  /**\n   * The minimum value\n   * @default 0\n   */\n  min?: number | undefined;\n  /**\n   * The current value.\n   */\n  value: number;\n}\n\nexport namespace MeterRoot {\n  export type State = MeterRootState;\n  export type Props = MeterRootProps;\n}\n"
  },
  {
    "path": "packages/react/src/meter/root/MeterRootContext.ts",
    "content": "'use client';\nimport * as React from 'react';\n\nexport type MeterRootContext = {\n  formattedValue: string;\n  max: number;\n  min: number;\n  setLabelId: React.Dispatch<React.SetStateAction<string | undefined>>;\n  value: number;\n};\n\nexport const MeterRootContext = React.createContext<MeterRootContext | undefined>(undefined);\n\nexport function useMeterRootContext() {\n  const context = React.useContext(MeterRootContext);\n  if (context === undefined) {\n    throw new Error(\n      'Base UI: MeterRootContext is missing. Meter parts must be placed within <Meter.Root>.',\n    );\n  }\n\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/meter/track/MeterTrack.test.tsx",
    "content": "import { Meter } from '@base-ui/react/meter';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Meter.Track />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Meter.Track />, () => ({\n    render: (node) => {\n      return render(<Meter.Root value={30}>{node}</Meter.Root>);\n    },\n    refInstanceof: window.HTMLDivElement,\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/meter/track/MeterTrack.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport type { MeterRootState } from '../root/MeterRoot';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\n\n/**\n * Contains the meter indicator and represents the entire range of the meter.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Meter](https://base-ui.com/react/components/meter)\n */\nexport const MeterTrack = React.forwardRef(function MeterTrack(\n  componentProps: MeterTrack.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, ...elementProps } = componentProps;\n\n  return useRenderElement('div', componentProps, {\n    ref: forwardedRef,\n    props: elementProps,\n  });\n});\n\nexport interface MeterTrackState extends MeterRootState {}\n\nexport interface MeterTrackProps extends BaseUIComponentProps<'div', MeterTrackState> {}\n\nexport namespace MeterTrack {\n  export type State = MeterTrackState;\n  export type Props = MeterTrackProps;\n}\n"
  },
  {
    "path": "packages/react/src/meter/value/MeterValue.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport { screen } from '@mui/internal-test-utils';\nimport { Meter } from '@base-ui/react/meter';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Meter.Value />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Meter.Value />, () => ({\n    render: (node) => {\n      return render(<Meter.Root value={30}>{node}</Meter.Root>);\n    },\n    refInstanceof: window.HTMLSpanElement,\n  }));\n\n  describe('prop: children', () => {\n    it('renders the value when children is not provided', async () => {\n      await render(\n        <Meter.Root value={30}>\n          <Meter.Value data-testid=\"value\" />\n        </Meter.Root>,\n      );\n\n      const value = screen.getByTestId('value');\n      expect(value).toHaveTextContent((0.3).toLocaleString(undefined, { style: 'percent' }));\n    });\n\n    it('renders a formatted value when a format is provided', async () => {\n      const format: Intl.NumberFormatOptions = {\n        style: 'currency',\n        currency: 'USD',\n      };\n      function formatValue(v: number) {\n        return new Intl.NumberFormat(undefined, format).format(v);\n      }\n\n      await render(\n        <Meter.Root value={30} format={format}>\n          <Meter.Value data-testid=\"value\" />\n        </Meter.Root>,\n      );\n\n      const value = screen.getByTestId('value');\n      expect(value).toHaveTextContent(formatValue(30));\n    });\n\n    it('accepts a render function', async () => {\n      const renderSpy = vi.fn();\n      const format: Intl.NumberFormatOptions = {\n        style: 'currency',\n        currency: 'USD',\n      };\n      function formatValue(v: number) {\n        return new Intl.NumberFormat(undefined, format).format(v);\n      }\n      await render(\n        <Meter.Root value={30} format={format}>\n          <Meter.Value data-testid=\"value\">{renderSpy}</Meter.Value>\n        </Meter.Root>,\n      );\n      expect(renderSpy.mock.lastCall?.[0]).toEqual(formatValue(30));\n      expect(renderSpy.mock.lastCall?.[1]).toEqual(30);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/meter/value/MeterValue.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useMeterRootContext } from '../root/MeterRootContext';\nimport type { MeterRootState } from '../root/MeterRoot';\nimport { useRenderElement } from '../../utils/useRenderElement';\n\n/**\n * A text element displaying the current value.\n * Renders a `<span>` element.\n *\n * Documentation: [Base UI Meter](https://base-ui.com/react/components/meter)\n */\nexport const MeterValue = React.forwardRef(function MeterValue(\n  componentProps: MeterValue.Props,\n  forwardedRef: React.ForwardedRef<HTMLSpanElement>,\n) {\n  const { className, render, children, ...elementProps } = componentProps;\n\n  const { value, formattedValue } = useMeterRootContext();\n\n  return useRenderElement('span', componentProps, {\n    ref: forwardedRef,\n    props: [\n      {\n        'aria-hidden': true,\n        children:\n          typeof children === 'function'\n            ? children(formattedValue, value)\n            : ((formattedValue || value) ?? ''),\n      },\n      elementProps,\n    ],\n  });\n});\n\nexport interface MeterValueState extends MeterRootState {}\n\nexport interface MeterValueProps extends Omit<\n  BaseUIComponentProps<'span', MeterValueState>,\n  'children'\n> {\n  children?: null | ((formattedValue: string, value: number) => React.ReactNode) | undefined;\n}\n\nexport namespace MeterValue {\n  export type State = MeterValueState;\n  export type Props = MeterValueProps;\n}\n"
  },
  {
    "path": "packages/react/src/navigation-menu/arrow/NavigationMenuArrow.test.tsx",
    "content": "import { NavigationMenu } from '@base-ui/react/navigation-menu';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<NavigationMenu.Arrow />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<NavigationMenu.Arrow />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <NavigationMenu.Root value=\"test\">\n          <NavigationMenu.Portal>\n            <NavigationMenu.Positioner>{node}</NavigationMenu.Positioner>\n          </NavigationMenu.Portal>\n        </NavigationMenu.Root>,\n      );\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/navigation-menu/arrow/NavigationMenuArrow.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useNavigationMenuPositionerContext } from '../positioner/NavigationMenuPositionerContext';\nimport { useNavigationMenuRootContext } from '../root/NavigationMenuRootContext';\nimport type { Align, Side } from '../../utils/useAnchorPositioning';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { popupStateMapping } from '../../utils/popupStateMapping';\nimport { useRenderElement } from '../../utils/useRenderElement';\n\n/**\n * Displays an element pointing toward the navigation menu's current anchor.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Navigation Menu](https://base-ui.com/react/components/navigation-menu)\n */\nexport const NavigationMenuArrow = React.forwardRef(function NavigationMenuArrow(\n  componentProps: NavigationMenuArrow.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { className, render, ...elementProps } = componentProps;\n\n  const { open } = useNavigationMenuRootContext();\n  const { arrowRef, side, align, arrowUncentered, arrowStyles } =\n    useNavigationMenuPositionerContext();\n\n  const state: NavigationMenuArrowState = {\n    open,\n    side,\n    align,\n    uncentered: arrowUncentered,\n  };\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: [forwardedRef, arrowRef],\n    props: [{ style: arrowStyles, 'aria-hidden': true }, elementProps],\n    stateAttributesMapping: popupStateMapping,\n  });\n\n  return element;\n});\n\nexport interface NavigationMenuArrowState {\n  /**\n   * Whether the popup is currently open.\n   */\n  open: boolean;\n  /**\n   * The side of the anchor the component is placed on.\n   */\n  side: Side;\n  /**\n   * The alignment of the component relative to the anchor.\n   */\n  align: Align;\n  /**\n   * Whether the arrow cannot be centered on the anchor.\n   */\n  uncentered: boolean;\n}\n\nexport interface NavigationMenuArrowProps extends BaseUIComponentProps<\n  'div',\n  NavigationMenuArrowState\n> {}\n\nexport namespace NavigationMenuArrow {\n  export type State = NavigationMenuArrowState;\n  export type Props = NavigationMenuArrowProps;\n}\n"
  },
  {
    "path": "packages/react/src/navigation-menu/arrow/NavigationMenuArrowDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum NavigationMenuArrowDataAttributes {\n  /**\n   * Present when the popup is open.\n   */\n  open = CommonPopupDataAttributes.open,\n  /**\n   * Present when the popup is closed.\n   */\n  closed = CommonPopupDataAttributes.closed,\n  /**\n   * Indicates which side the popup is positioned relative to the trigger.\n   * @type {'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'}\n   */\n  side = CommonPopupDataAttributes.side,\n  /**\n   * Indicates how the popup is aligned relative to specified side.\n   * @type {'start' | 'center' | 'end'}\n   */\n  align = CommonPopupDataAttributes.align,\n  /**\n   * Present when the popup arrow is uncentered.\n   */\n  uncentered = 'data-uncentered',\n}\n"
  },
  {
    "path": "packages/react/src/navigation-menu/backdrop/NavigationMenuBackdrop.test.tsx",
    "content": "import { NavigationMenu } from '@base-ui/react/navigation-menu';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<NavigationMenu.Backdrop />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<NavigationMenu.Backdrop />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(<NavigationMenu.Root>{node}</NavigationMenu.Root>);\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/navigation-menu/backdrop/NavigationMenuBackdrop.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useNavigationMenuRootContext } from '../root/NavigationMenuRootContext';\nimport type { TransitionStatus } from '../../utils/useTransitionStatus';\nimport type { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport { transitionStatusMapping } from '../../utils/stateAttributesMapping';\nimport { popupStateMapping as baseMapping } from '../../utils/popupStateMapping';\n\nconst stateAttributesMapping: StateAttributesMapping<NavigationMenuBackdropState> = {\n  ...baseMapping,\n  ...transitionStatusMapping,\n};\n\n/**\n * A backdrop for the navigation menu popup.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Navigation Menu](https://base-ui.com/react/components/navigation-menu)\n */\nexport const NavigationMenuBackdrop = React.forwardRef(function NavigationMenuBackdrop(\n  componentProps: NavigationMenuBackdrop.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { className, render, ...elementProps } = componentProps;\n\n  const { open, mounted, transitionStatus } = useNavigationMenuRootContext();\n\n  const state: NavigationMenuBackdropState = {\n    open,\n    transitionStatus,\n  };\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: forwardedRef,\n    props: [\n      {\n        role: 'presentation',\n        hidden: !mounted,\n        style: {\n          userSelect: 'none',\n          WebkitUserSelect: 'none',\n        },\n      },\n      elementProps,\n    ],\n    stateAttributesMapping,\n  });\n\n  return element;\n});\n\nexport interface NavigationMenuBackdropState {\n  /**\n   * If `true`, the popup is open.\n   */\n  open: boolean;\n  /**\n   * The transition status of the popup.\n   */\n  transitionStatus: TransitionStatus;\n}\n\nexport interface NavigationMenuBackdropProps extends BaseUIComponentProps<\n  'div',\n  NavigationMenuBackdropState\n> {}\n\nexport namespace NavigationMenuBackdrop {\n  export type State = NavigationMenuBackdropState;\n  export type Props = NavigationMenuBackdropProps;\n}\n"
  },
  {
    "path": "packages/react/src/navigation-menu/backdrop/NavigationMenuBackdropDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum NavigationMenuBackdropDataAttributes {\n  /**\n   * Present when the popup is open.\n   */\n  open = CommonPopupDataAttributes.open,\n  /**\n   * Present when the popup is closed.\n   */\n  closed = CommonPopupDataAttributes.closed,\n  /**\n   * Present when the popup is animating in.\n   */\n  startingStyle = CommonPopupDataAttributes.startingStyle,\n  /**\n   * Present when the popup is animating out.\n   */\n  endingStyle = CommonPopupDataAttributes.endingStyle,\n}\n"
  },
  {
    "path": "packages/react/src/navigation-menu/content/NavigationMenuContent.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { NavigationMenu } from '@base-ui/react/navigation-menu';\nimport { fireEvent, screen, flushMicrotasks, waitFor } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<NavigationMenu.Content />', () => {\n  const { render } = createRenderer();\n\n  describeConformance.skip(<NavigationMenu.Content />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <NavigationMenu.Root value=\"test\">\n          <NavigationMenu.Item>{node}</NavigationMenu.Item>\n          <NavigationMenu.Portal>\n            <NavigationMenu.Positioner>\n              <NavigationMenu.Popup>\n                <NavigationMenu.Viewport />\n              </NavigationMenu.Popup>\n            </NavigationMenu.Positioner>\n          </NavigationMenu.Portal>\n        </NavigationMenu.Root>,\n      );\n    },\n  }));\n\n  describe('server-side rendering', () => {\n    const { renderToString } = createRenderer();\n\n    it('keeps the content mounted (hidden) in the DOM when keepMounted is true', async () => {\n      renderToString(\n        <NavigationMenu.Root>\n          <NavigationMenu.List>\n            <NavigationMenu.Item>\n              <NavigationMenu.Trigger>Item 1</NavigationMenu.Trigger>\n              <NavigationMenu.Content keepMounted data-testid=\"content-1\">\n                <NavigationMenu.Link href=\"#link-1\">Link 1</NavigationMenu.Link>\n              </NavigationMenu.Content>\n            </NavigationMenu.Item>\n          </NavigationMenu.List>\n          <NavigationMenu.Portal>\n            <NavigationMenu.Positioner>\n              <NavigationMenu.Popup>\n                <NavigationMenu.Viewport />\n              </NavigationMenu.Popup>\n            </NavigationMenu.Positioner>\n          </NavigationMenu.Portal>\n        </NavigationMenu.Root>,\n      );\n\n      const contents = screen.queryAllByTestId('content-1');\n      expect(contents.length).toBe(1);\n    });\n\n    it('does not keep the content mounted in the DOM when keepMounted is false', async () => {\n      renderToString(\n        <NavigationMenu.Root>\n          <NavigationMenu.List>\n            <NavigationMenu.Item>\n              <NavigationMenu.Trigger>Item 1</NavigationMenu.Trigger>\n              <NavigationMenu.Content data-testid=\"content-1\">\n                <NavigationMenu.Link href=\"#link-1\">Link 1</NavigationMenu.Link>\n              </NavigationMenu.Content>\n            </NavigationMenu.Item>\n          </NavigationMenu.List>\n          <NavigationMenu.Portal>\n            <NavigationMenu.Positioner>\n              <NavigationMenu.Popup>\n                <NavigationMenu.Viewport />\n              </NavigationMenu.Popup>\n            </NavigationMenu.Positioner>\n          </NavigationMenu.Portal>\n        </NavigationMenu.Root>,\n      );\n\n      const contents = screen.queryAllByTestId('content-1');\n      expect(contents.length).toBe(0);\n    });\n  });\n\n  it('keeps the content mounted (hidden) post-hydration when keepMounted is true', async () => {\n    await render(\n      <NavigationMenu.Root>\n        <NavigationMenu.List>\n          <NavigationMenu.Item>\n            <NavigationMenu.Trigger>Item 1</NavigationMenu.Trigger>\n            <NavigationMenu.Content keepMounted data-testid=\"content-1\">\n              <NavigationMenu.Link href=\"#link-1\">Link 1</NavigationMenu.Link>\n            </NavigationMenu.Content>\n          </NavigationMenu.Item>\n        </NavigationMenu.List>\n        <NavigationMenu.Portal>\n          <NavigationMenu.Positioner>\n            <NavigationMenu.Popup>\n              <NavigationMenu.Viewport />\n            </NavigationMenu.Popup>\n          </NavigationMenu.Positioner>\n        </NavigationMenu.Portal>\n      </NavigationMenu.Root>,\n    );\n\n    const contents = screen.queryAllByTestId('content-1');\n    expect(contents.length).toBe(1);\n    expect(contents[0]).toHaveAttribute('hidden');\n  });\n\n  it('does not keep the content mounted post-hydration when keepMounted is false', async () => {\n    await render(\n      <NavigationMenu.Root>\n        <NavigationMenu.List>\n          <NavigationMenu.Item>\n            <NavigationMenu.Trigger>Item 1</NavigationMenu.Trigger>\n            <NavigationMenu.Content data-testid=\"content-1\">\n              <NavigationMenu.Link href=\"#link-1\">Link 1</NavigationMenu.Link>\n            </NavigationMenu.Content>\n          </NavigationMenu.Item>\n        </NavigationMenu.List>\n        <NavigationMenu.Portal>\n          <NavigationMenu.Positioner>\n            <NavigationMenu.Popup>\n              <NavigationMenu.Viewport />\n            </NavigationMenu.Popup>\n          </NavigationMenu.Positioner>\n        </NavigationMenu.Portal>\n      </NavigationMenu.Root>,\n    );\n\n    const contents = screen.queryAllByTestId('content-1');\n    expect(contents.length).toBe(0);\n  });\n\n  it('moves content into the popup and keeps it there when switching triggers', async () => {\n    await render(\n      <NavigationMenu.Root>\n        <NavigationMenu.List data-testid=\"list\">\n          <NavigationMenu.Item value=\"item-1\">\n            <NavigationMenu.Trigger>Item 1</NavigationMenu.Trigger>\n            <NavigationMenu.Content keepMounted data-testid=\"content-1\">\n              <NavigationMenu.Link href=\"#link-1\">Link 1</NavigationMenu.Link>\n            </NavigationMenu.Content>\n          </NavigationMenu.Item>\n          <NavigationMenu.Item value=\"item-2\">\n            <NavigationMenu.Trigger>Item 2</NavigationMenu.Trigger>\n            <NavigationMenu.Content keepMounted data-testid=\"content-2\">\n              <NavigationMenu.Link href=\"#link-2\">Link 2</NavigationMenu.Link>\n            </NavigationMenu.Content>\n          </NavigationMenu.Item>\n        </NavigationMenu.List>\n        <NavigationMenu.Portal>\n          <NavigationMenu.Positioner>\n            <NavigationMenu.Popup>\n              <NavigationMenu.Viewport data-testid=\"viewport\" />\n            </NavigationMenu.Popup>\n          </NavigationMenu.Positioner>\n        </NavigationMenu.Portal>\n      </NavigationMenu.Root>,\n    );\n\n    const list = screen.getByTestId('list');\n\n    fireEvent.click(screen.getByRole('button', { name: 'Item 1' }));\n    await flushMicrotasks();\n\n    const viewport = screen.getByTestId('viewport');\n    const content1 = screen.getByTestId('content-1');\n    expect(viewport.contains(content1)).toBe(true);\n    expect(list.contains(content1)).toBe(false);\n\n    fireEvent.click(screen.getByRole('button', { name: 'Item 2' }));\n    await flushMicrotasks();\n\n    await waitFor(() => {\n      expect(screen.queryByTestId('content-2')).not.toBe(null);\n    });\n\n    const content1After = screen.queryByTestId('content-1');\n    const content2 = screen.queryByTestId('content-2');\n    if (!content1After || !content2) {\n      throw new Error('Expected both contents to remain mounted inside the viewport.');\n    }\n    expect(viewport.contains(content1After)).toBe(true);\n    expect(viewport.contains(content2)).toBe(true);\n  });\n\n  it('keeps content mounted inside the popup when closed if the portal is kept mounted', async () => {\n    const { user } = await render(\n      <NavigationMenu.Root>\n        <NavigationMenu.List>\n          <NavigationMenu.Item value=\"item-1\">\n            <NavigationMenu.Trigger>Item 1</NavigationMenu.Trigger>\n            <NavigationMenu.Content keepMounted data-testid=\"content-1\">\n              <NavigationMenu.Link href=\"#link-1\">Link 1</NavigationMenu.Link>\n            </NavigationMenu.Content>\n          </NavigationMenu.Item>\n        </NavigationMenu.List>\n        <NavigationMenu.Portal keepMounted>\n          <NavigationMenu.Positioner>\n            <NavigationMenu.Popup>\n              <NavigationMenu.Viewport data-testid=\"viewport\" />\n            </NavigationMenu.Popup>\n          </NavigationMenu.Positioner>\n        </NavigationMenu.Portal>\n      </NavigationMenu.Root>,\n    );\n\n    await user.click(screen.getByRole('button', { name: 'Item 1' }));\n    await flushMicrotasks();\n\n    const viewport = screen.getByTestId('viewport');\n    expect(viewport.contains(screen.getByTestId('content-1'))).toBe(true);\n\n    await user.keyboard('{Escape}');\n    await flushMicrotasks();\n\n    await waitFor(() => {\n      expect(screen.getByTestId('content-1')).toHaveAttribute('hidden');\n    });\n\n    expect(viewport.contains(screen.getByTestId('content-1'))).toBe(true);\n  });\n});\n"
  },
  {
    "path": "packages/react/src/navigation-menu/content/NavigationMenuContent.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { inertValue } from '@base-ui/utils/inertValue';\nimport { FloatingNode } from '../../floating-ui-react';\nimport { contains, getTarget } from '../../floating-ui-react/utils';\nimport type { BaseUIComponentProps, HTMLProps } from '../../utils/types';\nimport {\n  useNavigationMenuRootContext,\n  useNavigationMenuTreeContext,\n} from '../root/NavigationMenuRootContext';\nimport { useNavigationMenuItemContext } from '../item/NavigationMenuItemContext';\nimport { TransitionStatus, useTransitionStatus } from '../../utils/useTransitionStatus';\nimport { useOpenChangeComplete } from '../../utils/useOpenChangeComplete';\nimport { transitionStatusMapping } from '../../utils/stateAttributesMapping';\nimport { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport { CompositeRoot } from '../../composite/root/CompositeRoot';\nimport { popupStateMapping } from '../../utils/popupStateMapping';\nimport { EMPTY_OBJECT } from '../../utils/constants';\n\nconst stateAttributesMapping: StateAttributesMapping<NavigationMenuContentState> = {\n  ...popupStateMapping,\n  ...transitionStatusMapping,\n  activationDirection(value) {\n    if (!value) {\n      return null;\n    }\n    return {\n      'data-activation-direction': value,\n    };\n  },\n};\n\n/**\n * A container for the content of the navigation menu item that is moved into the popup\n * when the item is active.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Navigation Menu](https://base-ui.com/react/components/navigation-menu)\n */\nexport const NavigationMenuContent = React.forwardRef(function NavigationMenuContent(\n  componentProps: NavigationMenuContent.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { className, render, keepMounted = false, ...elementProps } = componentProps;\n\n  const {\n    mounted: popupMounted,\n    viewportElement,\n    value,\n    activationDirection,\n    currentContentRef,\n    viewportTargetElement,\n  } = useNavigationMenuRootContext();\n  const { value: itemValue } = useNavigationMenuItemContext();\n  const nodeId = useNavigationMenuTreeContext();\n\n  const open = popupMounted && value === itemValue;\n\n  const ref = React.useRef<HTMLDivElement | null>(null);\n\n  const [hasMountedInPortal, setHasMountedInPortal] = React.useState(false);\n  const [focusInside, setFocusInside] = React.useState(false);\n\n  const { mounted, setMounted, transitionStatus } = useTransitionStatus(open);\n\n  // If the popup unmounts before the content's exit animation completes, reset the internal\n  // mounted state so the next open can re-enter via `transitionStatus=\"starting\"`.\n  if (mounted && !popupMounted) {\n    setMounted(false);\n  }\n\n  useOpenChangeComplete({\n    ref,\n    open,\n    onComplete() {\n      if (!open) {\n        setMounted(false);\n      }\n    },\n  });\n\n  // When a content re-enters while still mounted (e.g. switching top-level triggers\n  // back before the exit animation completes), the DOM element hasn't changed so the\n  // callback ref won't fire again. Ensure the shared ref is updated so the\n  // MutationObserver in the trigger watches the correct content element.\n  useIsoLayoutEffect(() => {\n    if (open && ref.current) {\n      currentContentRef.current = ref.current;\n    }\n  }, [open, currentContentRef]);\n\n  const state: NavigationMenuContentState = {\n    open,\n    transitionStatus,\n    activationDirection,\n  };\n\n  const handleCurrentContentRef = React.useCallback(\n    (node: HTMLDivElement | null) => {\n      if (node) {\n        currentContentRef.current = node;\n      }\n    },\n    [currentContentRef],\n  );\n\n  const commonProps: HTMLProps<HTMLDivElement> = {\n    onFocus(event) {\n      const target = getTarget(event.nativeEvent) as Element | null;\n      if (target?.hasAttribute('data-base-ui-focus-guard')) {\n        return;\n      }\n      setFocusInside(true);\n    },\n    onBlur(event) {\n      if (!contains(event.currentTarget, event.relatedTarget)) {\n        setFocusInside(false);\n      }\n    },\n  };\n\n  const defaultProps: HTMLProps =\n    !open && mounted\n      ? {\n          style: { position: 'absolute', top: 0, left: 0 },\n          inert: inertValue(!focusInside),\n          ...commonProps,\n        }\n      : commonProps;\n\n  const portalContainer = viewportTargetElement || viewportElement;\n  const hidden = keepMounted && !mounted;\n  const shouldRenderInline = keepMounted && !portalContainer && !hasMountedInPortal;\n\n  if (keepMounted && portalContainer && !hasMountedInPortal) {\n    setHasMountedInPortal(true);\n  }\n\n  if (shouldRenderInline) {\n    return (\n      <CompositeRoot\n        render={render}\n        className={className}\n        state={state}\n        refs={[forwardedRef]}\n        props={[defaultProps, { hidden: true }, elementProps]}\n        stateAttributesMapping={stateAttributesMapping}\n      />\n    );\n  }\n\n  if (!portalContainer || (!mounted && !keepMounted)) {\n    return null;\n  }\n\n  return ReactDOM.createPortal(\n    <FloatingNode id={nodeId}>\n      <CompositeRoot\n        render={render}\n        className={className}\n        state={state}\n        refs={[forwardedRef, ref, handleCurrentContentRef]}\n        props={[defaultProps, hidden ? { hidden: true } : EMPTY_OBJECT, elementProps]}\n        stateAttributesMapping={stateAttributesMapping}\n      />\n    </FloatingNode>,\n    portalContainer,\n  );\n});\n\nexport interface NavigationMenuContentState {\n  /**\n   * If `true`, the component is open.\n   */\n  open: boolean;\n  /**\n   * The transition status of the component.\n   */\n  transitionStatus: TransitionStatus;\n  /**\n   * The direction of the activation.\n   */\n  activationDirection: 'left' | 'right' | 'up' | 'down' | null;\n}\n\nexport interface NavigationMenuContentProps extends BaseUIComponentProps<\n  'div',\n  NavigationMenuContentState\n> {\n  /**\n   * Whether to keep the content mounted in the DOM while the popup is closed.\n   * Ensures the content is present during server-side rendering for web crawlers.\n   * @default false\n   */\n  keepMounted?: boolean | undefined;\n}\n\nexport namespace NavigationMenuContent {\n  export type State = NavigationMenuContentState;\n  export type Props = NavigationMenuContentProps;\n}\n"
  },
  {
    "path": "packages/react/src/navigation-menu/content/NavigationMenuContentDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum NavigationMenuContentDataAttributes {\n  /**\n   * Present when the popup is open.\n   */\n  open = CommonPopupDataAttributes.open,\n  /**\n   * Present when the popup is closed.\n   */\n  closed = CommonPopupDataAttributes.closed,\n  /**\n   * Present when the content is animating in.\n   */\n  startingStyle = CommonPopupDataAttributes.startingStyle,\n  /**\n   * Present when the content is animating out.\n   */\n  endingStyle = CommonPopupDataAttributes.endingStyle,\n  /**\n   * Which direction another trigger was activated from.\n   */\n  activationDirection = 'data-activation-direction',\n}\n"
  },
  {
    "path": "packages/react/src/navigation-menu/icon/NavigationMenuIcon.test.tsx",
    "content": "import { NavigationMenu } from '@base-ui/react/navigation-menu';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<NavigationMenu.Icon />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<NavigationMenu.Icon />, () => ({\n    refInstanceof: window.HTMLSpanElement,\n    render(node) {\n      return render(\n        <NavigationMenu.Root>\n          <NavigationMenu.Item>{node}</NavigationMenu.Item>\n        </NavigationMenu.Root>,\n      );\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/navigation-menu/icon/NavigationMenuIcon.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useNavigationMenuRootContext } from '../root/NavigationMenuRootContext';\nimport { triggerOpenStateMapping } from '../../utils/popupStateMapping';\nimport { useNavigationMenuItemContext } from '../item/NavigationMenuItemContext';\n\n/**\n * An icon that indicates that the trigger button opens a menu.\n *\n * Documentation: [Base UI Navigation Menu](https://base-ui.com/react/components/navigation-menu)\n */\nexport const NavigationMenuIcon = React.forwardRef(function NavigationMenuIcon(\n  componentProps: NavigationMenuIcon.Props,\n  forwardedRef: React.ForwardedRef<HTMLSpanElement>,\n) {\n  const { className, render, ...elementProps } = componentProps;\n\n  const { value: itemValue } = useNavigationMenuItemContext();\n  const { open, value } = useNavigationMenuRootContext();\n\n  const isActiveItem = open && value === itemValue;\n\n  const state: NavigationMenuIconState = {\n    open: isActiveItem,\n  };\n\n  const element = useRenderElement('span', componentProps, {\n    state,\n    ref: forwardedRef,\n    props: [{ 'aria-hidden': true, children: '▼' }, elementProps],\n    stateAttributesMapping: triggerOpenStateMapping,\n  });\n\n  return element;\n});\n\nexport interface NavigationMenuIconState {\n  /**\n   * Whether the navigation menu is open and the item is active.\n   */\n  open: boolean;\n}\n\nexport interface NavigationMenuIconProps extends BaseUIComponentProps<\n  'span',\n  NavigationMenuIconState\n> {}\n\nexport namespace NavigationMenuIcon {\n  export type State = NavigationMenuIconState;\n  export type Props = NavigationMenuIconProps;\n}\n"
  },
  {
    "path": "packages/react/src/navigation-menu/index.parts.ts",
    "content": "export { NavigationMenuRoot as Root } from './root/NavigationMenuRoot';\nexport { NavigationMenuList as List } from './list/NavigationMenuList';\nexport { NavigationMenuItem as Item } from './item/NavigationMenuItem';\nexport { NavigationMenuContent as Content } from './content/NavigationMenuContent';\nexport { NavigationMenuTrigger as Trigger } from './trigger/NavigationMenuTrigger';\nexport { NavigationMenuPortal as Portal } from './portal/NavigationMenuPortal';\nexport { NavigationMenuPositioner as Positioner } from './positioner/NavigationMenuPositioner';\nexport { NavigationMenuViewport as Viewport } from './viewport/NavigationMenuViewport';\nexport { NavigationMenuBackdrop as Backdrop } from './backdrop/NavigationMenuBackdrop';\nexport { NavigationMenuPopup as Popup } from './popup/NavigationMenuPopup';\nexport { NavigationMenuArrow as Arrow } from './arrow/NavigationMenuArrow';\nexport { NavigationMenuLink as Link } from './link/NavigationMenuLink';\nexport { NavigationMenuIcon as Icon } from './icon/NavigationMenuIcon';\n"
  },
  {
    "path": "packages/react/src/navigation-menu/index.ts",
    "content": "export * as NavigationMenu from './index.parts';\n\nexport type * from './root/NavigationMenuRoot';\nexport type * from './trigger/NavigationMenuTrigger';\nexport type * from './portal/NavigationMenuPortal';\nexport type * from './positioner/NavigationMenuPositioner';\nexport type * from './viewport/NavigationMenuViewport';\nexport type * from './list/NavigationMenuList';\nexport type * from './item/NavigationMenuItem';\nexport type * from './content/NavigationMenuContent';\nexport type * from './popup/NavigationMenuPopup';\nexport type * from './backdrop/NavigationMenuBackdrop';\nexport type * from './arrow/NavigationMenuArrow';\nexport type * from './link/NavigationMenuLink';\nexport type * from './icon/NavigationMenuIcon';\n"
  },
  {
    "path": "packages/react/src/navigation-menu/item/NavigationMenuItem.test.tsx",
    "content": "import { NavigationMenu } from '@base-ui/react/navigation-menu';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<NavigationMenu.Item />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<NavigationMenu.Item />, () => ({\n    refInstanceof: window.HTMLLIElement,\n    render(node) {\n      return render(<NavigationMenu.Root>{node}</NavigationMenu.Root>);\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/navigation-menu/item/NavigationMenuItem.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport {\n  NavigationMenuItemContext,\n  NavigationMenuItemContextValue,\n} from './NavigationMenuItemContext';\nimport { useBaseUiId } from '../../utils/useBaseUiId';\n\n/**\n * An individual navigation menu item.\n * Renders a `<li>` element.\n *\n * Documentation: [Base UI Navigation Menu](https://base-ui.com/react/components/navigation-menu)\n */\nexport const NavigationMenuItem = React.forwardRef(function NavigationMenuItem(\n  componentProps: NavigationMenuItem.Props,\n  forwardedRef: React.ForwardedRef<HTMLLIElement>,\n) {\n  const { className, render, value: valueProp, ...elementProps } = componentProps;\n\n  const fallbackValue = useBaseUiId();\n  const value = valueProp ?? fallbackValue;\n\n  const element = useRenderElement('li', componentProps, {\n    ref: forwardedRef,\n    props: elementProps,\n  });\n\n  const contextValue: NavigationMenuItemContextValue = React.useMemo(() => ({ value }), [value]);\n\n  return (\n    <NavigationMenuItemContext.Provider value={contextValue}>\n      {element}\n    </NavigationMenuItemContext.Provider>\n  );\n});\n\nexport interface NavigationMenuItemState {}\n\nexport interface NavigationMenuItemProps extends BaseUIComponentProps<\n  'li',\n  NavigationMenuItemState\n> {\n  /**\n   * A unique value that identifies this navigation menu item.\n   * If no value is provided, a unique ID will be generated automatically.\n   * Use when controlling the navigation menu programmatically.\n   */\n  value?: any;\n}\n\nexport namespace NavigationMenuItem {\n  export type State = NavigationMenuItemState;\n  export type Props = NavigationMenuItemProps;\n}\n"
  },
  {
    "path": "packages/react/src/navigation-menu/item/NavigationMenuItemContext.ts",
    "content": "'use client';\nimport * as React from 'react';\n\nexport interface NavigationMenuItemContextValue {\n  value: any;\n}\n\nexport const NavigationMenuItemContext = React.createContext<\n  NavigationMenuItemContextValue | undefined\n>(undefined);\n\nexport function useNavigationMenuItemContext() {\n  const value = React.useContext(NavigationMenuItemContext);\n  if (!value) {\n    throw new Error(\n      'Base UI: NavigationMenuItem parts must be used within a <NavigationMenu.Item>.',\n    );\n  }\n  return value;\n}\n"
  },
  {
    "path": "packages/react/src/navigation-menu/link/NavigationMenuLink.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { screen, waitFor } from '@mui/internal-test-utils';\nimport { NavigationMenu } from '@base-ui/react/navigation-menu';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\n\ndescribe('<NavigationMenu.Link />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<NavigationMenu.Link />, () => ({\n    refInstanceof: window.HTMLAnchorElement,\n    render(node) {\n      return render(\n        <NavigationMenu.Root>\n          <NavigationMenu.List>{node}</NavigationMenu.List>\n        </NavigationMenu.Root>,\n      );\n    },\n  }));\n\n  describe.skipIf(!isJSDOM)('prop: closeOnClick', () => {\n    it('closes the menu when clicking a link when true', async () => {\n      const { user } = await render(\n        <NavigationMenu.Root>\n          <NavigationMenu.List>\n            <NavigationMenu.Item value=\"item-1\">\n              <NavigationMenu.Trigger data-testid=\"trigger-1\">Item 1</NavigationMenu.Trigger>\n              <NavigationMenu.Content data-testid=\"popup-1\">\n                <NavigationMenu.Link href=\"#link-1\" closeOnClick>\n                  Link 1\n                </NavigationMenu.Link>\n              </NavigationMenu.Content>\n            </NavigationMenu.Item>\n          </NavigationMenu.List>\n\n          <NavigationMenu.Portal>\n            <NavigationMenu.Positioner>\n              <NavigationMenu.Popup>\n                <NavigationMenu.Viewport />\n              </NavigationMenu.Popup>\n            </NavigationMenu.Positioner>\n          </NavigationMenu.Portal>\n        </NavigationMenu.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger-1');\n\n      await user.click(trigger);\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('popup-1')).not.toBe(null);\n      });\n      expect(trigger).toHaveAttribute('aria-expanded', 'true');\n\n      const link = screen.getByRole('link', { name: 'Link 1' });\n      await user.click(link);\n\n      await waitFor(() => expect(screen.queryByTestId('popup-1')).toBe(null));\n      expect(trigger).toHaveAttribute('aria-expanded', 'false');\n    });\n\n    it('does not close the menu when clicking a link when false', async () => {\n      const { user } = await render(\n        <NavigationMenu.Root>\n          <NavigationMenu.List>\n            <NavigationMenu.Item value=\"item-1\">\n              <NavigationMenu.Trigger data-testid=\"trigger-1\">Item 1</NavigationMenu.Trigger>\n              <NavigationMenu.Content data-testid=\"popup-1\">\n                <NavigationMenu.Link href=\"#link-1\" closeOnClick={false}>\n                  Link 1\n                </NavigationMenu.Link>\n              </NavigationMenu.Content>\n            </NavigationMenu.Item>\n          </NavigationMenu.List>\n\n          <NavigationMenu.Portal>\n            <NavigationMenu.Positioner>\n              <NavigationMenu.Popup>\n                <NavigationMenu.Viewport />\n              </NavigationMenu.Popup>\n            </NavigationMenu.Positioner>\n          </NavigationMenu.Portal>\n        </NavigationMenu.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger-1');\n\n      await user.click(trigger);\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('popup-1')).not.toBe(null);\n      });\n      expect(trigger).toHaveAttribute('aria-expanded', 'true');\n\n      const link = screen.getByRole('link', { name: 'Link 1' });\n      await user.click(link);\n\n      await waitFor(() => expect(screen.queryByTestId('popup-1')).not.toBe(null));\n      expect(trigger).toHaveAttribute('aria-expanded', 'true');\n    });\n  });\n\n  describe('prop: active', () => {\n    it('when `true`, renders with aria-current=\"page\"', async () => {\n      await render(\n        <NavigationMenu.Root>\n          <NavigationMenu.List>\n            <NavigationMenu.Item>\n              <NavigationMenu.Link href=\"#\" active>\n                active\n              </NavigationMenu.Link>\n            </NavigationMenu.Item>\n          </NavigationMenu.List>\n        </NavigationMenu.Root>,\n      );\n      expect(screen.getByRole('link', { name: 'active' })).toHaveAttribute('aria-current', 'page');\n    });\n\n    it('when `false`, does not render with aria-current=\"page\"', async () => {\n      await render(\n        <NavigationMenu.Root>\n          <NavigationMenu.List>\n            <NavigationMenu.Item>\n              <NavigationMenu.Link href=\"#\" active={false}>\n                inactive\n              </NavigationMenu.Link>\n            </NavigationMenu.Item>\n          </NavigationMenu.List>\n        </NavigationMenu.Root>,\n      );\n      expect(screen.getByRole('link', { name: 'inactive' })).not.toHaveAttribute('aria-current');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/navigation-menu/link/NavigationMenuLink.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useFloatingTree } from '../../floating-ui-react';\nimport type { BaseUIComponentProps, HTMLProps } from '../../utils/types';\nimport {\n  useNavigationMenuRootContext,\n  useNavigationMenuTreeContext,\n} from '../root/NavigationMenuRootContext';\nimport { isOutsideMenuEvent } from '../utils/isOutsideMenuEvent';\nimport { CompositeItem } from '../../composite/item/CompositeItem';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\n\n/**\n * A link in the navigation menu that can be used to navigate to a different page or section.\n * Renders an `<a>` element.\n *\n * Documentation: [Base UI Navigation Menu](https://base-ui.com/react/components/navigation-menu)\n */\nexport const NavigationMenuLink = React.forwardRef(function NavigationMenuLink(\n  componentProps: NavigationMenuLink.Props,\n  forwardedRef: React.ForwardedRef<HTMLAnchorElement>,\n) {\n  const {\n    className,\n    render,\n    active = false,\n    closeOnClick = false,\n    ...elementProps\n  } = componentProps;\n\n  const { setValue, popupElement, positionerElement, rootRef } = useNavigationMenuRootContext();\n  const nodeId = useNavigationMenuTreeContext();\n  const tree = useFloatingTree();\n\n  const state: NavigationMenuLinkState = {\n    active,\n  };\n\n  const defaultProps: HTMLProps = {\n    'aria-current': active ? 'page' : undefined,\n    tabIndex: undefined,\n    onClick(event) {\n      if (closeOnClick) {\n        setValue(null, createChangeEventDetails(REASONS.linkPress, event.nativeEvent));\n      }\n    },\n    onBlur(event) {\n      if (\n        positionerElement &&\n        popupElement &&\n        isOutsideMenuEvent(\n          {\n            currentTarget: event.currentTarget,\n            relatedTarget: event.relatedTarget as HTMLElement | null,\n          },\n          { popupElement, rootRef, tree, nodeId },\n        )\n      ) {\n        setValue(null, createChangeEventDetails(REASONS.focusOut, event.nativeEvent));\n      }\n    },\n  };\n\n  return (\n    <CompositeItem\n      tag=\"a\"\n      render={render}\n      className={className}\n      state={state}\n      refs={[forwardedRef]}\n      props={[defaultProps, elementProps]}\n    />\n  );\n});\n\nexport interface NavigationMenuLinkState {\n  /**\n   * Whether the link is the currently active page.\n   */\n  active: boolean;\n}\n\nexport interface NavigationMenuLinkProps extends BaseUIComponentProps<\n  'a',\n  NavigationMenuLinkState\n> {\n  /**\n   * Whether the link is the currently active page.\n   * @default false\n   */\n  active?: boolean | undefined;\n  /**\n   * Whether to close the navigation menu when the link is clicked.\n   * @default false\n   */\n  closeOnClick?: boolean | undefined;\n}\n\nexport namespace NavigationMenuLink {\n  export type State = NavigationMenuLinkState;\n  export type Props = NavigationMenuLinkProps;\n}\n"
  },
  {
    "path": "packages/react/src/navigation-menu/link/NavigationMenuLinkDataAttributes.ts",
    "content": "export enum NavigationMenuLinkDataAttributes {\n  /**\n   * Present when the link is the currently active page.\n   */\n  active = 'data-active',\n}\n"
  },
  {
    "path": "packages/react/src/navigation-menu/list/NavigationMenuDismissContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport type { ElementProps } from '../../floating-ui-react';\n\nexport const NavigationMenuDismissContext = React.createContext<ElementProps | undefined>(\n  undefined,\n);\n\nexport function useNavigationMenuDismissContext() {\n  return React.useContext(NavigationMenuDismissContext);\n}\n"
  },
  {
    "path": "packages/react/src/navigation-menu/list/NavigationMenuList.test.tsx",
    "content": "import { NavigationMenu } from '@base-ui/react/navigation-menu';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<NavigationMenu.List />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<NavigationMenu.List />, () => ({\n    refInstanceof: window.HTMLUListElement,\n    render(node) {\n      return render(<NavigationMenu.Root>{node}</NavigationMenu.Root>);\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/navigation-menu/list/NavigationMenuList.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useDismiss, useHoverFloatingInteraction } from '../../floating-ui-react';\nimport { getTarget } from '../../floating-ui-react/utils';\nimport type { BaseUIComponentProps, HTMLProps } from '../../utils/types';\nimport { CompositeRoot } from '../../composite/root/CompositeRoot';\nimport { useNavigationMenuRootContext } from '../root/NavigationMenuRootContext';\nimport { EMPTY_OBJECT } from '../../utils/constants';\nimport { NAVIGATION_MENU_TRIGGER_IDENTIFIER } from '../utils/constants';\nimport { NavigationMenuDismissContext } from './NavigationMenuDismissContext';\nimport { getEmptyRootContext } from '../../floating-ui-react/utils/getEmptyRootContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\n\n/**\n * Contains a list of navigation menu items.\n * Renders a `<ul>` element.\n *\n * Documentation: [Base UI Navigation Menu](https://base-ui.com/react/components/navigation-menu)\n */\nexport const NavigationMenuList = React.forwardRef(function NavigationMenuList(\n  componentProps: NavigationMenuList.Props,\n  forwardedRef: React.ForwardedRef<HTMLUListElement>,\n) {\n  const { className, render, ...elementProps } = componentProps;\n\n  const {\n    orientation,\n    open,\n    floatingRootContext,\n    positionerElement,\n    value,\n    closeDelay,\n    viewportElement,\n    nested,\n  } = useNavigationMenuRootContext();\n\n  const fallbackContext = React.useMemo(() => getEmptyRootContext(), []);\n  const context = floatingRootContext || fallbackContext;\n  const interactionsEnabled = positionerElement ? true : !value;\n  const hoverInteractionsEnabled = positionerElement || viewportElement ? true : !value;\n\n  useHoverFloatingInteraction(context, {\n    enabled: Boolean(floatingRootContext) && hoverInteractionsEnabled,\n    closeDelay,\n  });\n\n  const dismiss = useDismiss(context, {\n    enabled: interactionsEnabled,\n    outsidePressEvent: 'intentional',\n    outsidePress(event) {\n      const target = getTarget(event) as HTMLElement | null;\n      const closestNavigationMenuTrigger = target?.closest(\n        `[${NAVIGATION_MENU_TRIGGER_IDENTIFIER}]`,\n      );\n      return closestNavigationMenuTrigger === null;\n    },\n  });\n\n  const dismissProps = floatingRootContext ? dismiss : undefined;\n\n  const state: NavigationMenuListState = {\n    open,\n  };\n\n  // `stopEventPropagation` won't stop the propagation if the end of the list is reached,\n  // but we want to block it in this case.\n  // When nested, skip this handler so arrow keys can reach the parent CompositeRoot.\n  const defaultProps: HTMLProps = nested\n    ? EMPTY_OBJECT\n    : {\n        onKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {\n          const shouldStop =\n            (orientation === 'horizontal' &&\n              (event.key === 'ArrowLeft' || event.key === 'ArrowRight')) ||\n            (orientation === 'vertical' && (event.key === 'ArrowUp' || event.key === 'ArrowDown'));\n\n          if (shouldStop) {\n            event.stopPropagation();\n          }\n        },\n      };\n\n  const props = [\n    dismissProps?.floating || EMPTY_OBJECT,\n    defaultProps,\n    { 'aria-orientation': undefined },\n    elementProps,\n  ];\n\n  // When nested, skip the CompositeRoot wrapper so that triggers can participate\n  // in the parent Content's composite navigation context. Also skip the onKeyDown\n  // handler that blocks propagation so arrow keys can reach the parent CompositeRoot.\n  const element = useRenderElement('ul', componentProps, {\n    state,\n    ref: forwardedRef,\n    props,\n    enabled: nested,\n  });\n\n  if (nested) {\n    return (\n      <NavigationMenuDismissContext.Provider value={dismissProps}>\n        {element}\n      </NavigationMenuDismissContext.Provider>\n    );\n  }\n\n  return (\n    <NavigationMenuDismissContext.Provider value={dismissProps}>\n      <CompositeRoot\n        render={render}\n        className={className}\n        state={state}\n        refs={[forwardedRef]}\n        props={props}\n        loopFocus={false}\n        orientation={orientation}\n        tag=\"ul\"\n      />\n    </NavigationMenuDismissContext.Provider>\n  );\n});\n\nexport interface NavigationMenuListState {\n  /**\n   * If `true`, the popup is open.\n   */\n  open: boolean;\n}\n\nexport interface NavigationMenuListProps extends BaseUIComponentProps<\n  'ul',\n  NavigationMenuListState\n> {}\n\nexport namespace NavigationMenuList {\n  export type State = NavigationMenuListState;\n  export type Props = NavigationMenuListProps;\n}\n"
  },
  {
    "path": "packages/react/src/navigation-menu/popup/NavigationMenuPopup.test.tsx",
    "content": "import { NavigationMenu } from '@base-ui/react/navigation-menu';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<NavigationMenu.Popup />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<NavigationMenu.Popup />, () => ({\n    refInstanceof: window.HTMLElement,\n    render(node) {\n      return render(\n        <NavigationMenu.Root value=\"test\">\n          <NavigationMenu.Portal>\n            <NavigationMenu.Positioner>{node}</NavigationMenu.Positioner>\n          </NavigationMenu.Portal>\n        </NavigationMenu.Root>,\n      );\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/navigation-menu/popup/NavigationMenuPopup.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useNavigationMenuRootContext } from '../root/NavigationMenuRootContext';\nimport type { TransitionStatus } from '../../utils/useTransitionStatus';\nimport { transitionStatusMapping } from '../../utils/stateAttributesMapping';\nimport { useBaseUiId } from '../../utils/useBaseUiId';\nimport { useNavigationMenuPositionerContext } from '../positioner/NavigationMenuPositionerContext';\nimport { useDirection } from '../../direction-provider/DirectionContext';\nimport { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport { popupStateMapping as baseMapping } from '../../utils/popupStateMapping';\nimport { Align, Side } from '../../utils/useAnchorPositioning';\n\nconst stateAttributesMapping: StateAttributesMapping<NavigationMenuPopupState> = {\n  ...baseMapping,\n  ...transitionStatusMapping,\n};\n\n/**\n * A container for the navigation menu contents.\n * Renders a `<nav>` element.\n *\n * Documentation: [Base UI Navigation Menu](https://base-ui.com/react/components/navigation-menu)\n */\nexport const NavigationMenuPopup = React.forwardRef(function NavigationMenuPopup(\n  componentProps: NavigationMenuPopup.Props,\n  forwardedRef: React.ForwardedRef<HTMLElement>,\n) {\n  const { className, render, id: idProp, ...elementProps } = componentProps;\n\n  const { open, transitionStatus, setPopupElement } = useNavigationMenuRootContext();\n  const positioning = useNavigationMenuPositionerContext();\n  const direction = useDirection();\n\n  const id = useBaseUiId(idProp);\n\n  const state: NavigationMenuPopupState = {\n    open,\n    transitionStatus,\n    side: positioning.side,\n    align: positioning.align,\n    anchorHidden: positioning.anchorHidden,\n  };\n\n  // Ensure popup size transitions correctly when anchored to `bottom` (side=top) or `right` (side=left).\n  let isOriginSide = positioning.side === 'top';\n  let isPhysicalLeft = positioning.side === 'left';\n  if (direction === 'rtl') {\n    isOriginSide = isOriginSide || positioning.side === 'inline-end';\n    isPhysicalLeft = isPhysicalLeft || positioning.side === 'inline-end';\n  } else {\n    isOriginSide = isOriginSide || positioning.side === 'inline-start';\n    isPhysicalLeft = isPhysicalLeft || positioning.side === 'inline-start';\n  }\n\n  const element = useRenderElement('nav', componentProps, {\n    state,\n    ref: [forwardedRef, setPopupElement],\n    props: [\n      {\n        id,\n        tabIndex: -1,\n        style: isOriginSide\n          ? {\n              position: 'absolute',\n              [positioning.side === 'top' ? 'bottom' : 'top']: '0',\n              [isPhysicalLeft ? 'right' : 'left']: '0',\n            }\n          : {},\n      },\n      elementProps,\n    ],\n    stateAttributesMapping,\n  });\n\n  return element;\n});\n\nexport interface NavigationMenuPopupState {\n  /**\n   * If `true`, the popup is open.\n   */\n  open: boolean;\n  /**\n   * The transition status of the popup.\n   */\n  transitionStatus: TransitionStatus;\n  /**\n   * The side of the anchor the popup is positioned on.\n   */\n  side: Side;\n  /**\n   * The alignment of the popup relative to the anchor.\n   */\n  align: Align;\n  /**\n   * Whether the anchor element is hidden.\n   */\n  anchorHidden: boolean;\n}\n\nexport interface NavigationMenuPopupProps extends BaseUIComponentProps<\n  'nav',\n  NavigationMenuPopupState\n> {}\n\nexport namespace NavigationMenuPopup {\n  export type State = NavigationMenuPopupState;\n  export type Props = NavigationMenuPopupProps;\n}\n"
  },
  {
    "path": "packages/react/src/navigation-menu/popup/NavigationMenuPopupCssVars.ts",
    "content": "export enum NavigationMenuPopupCssVars {\n  /**\n   * The fixed width of the popup element.\n   * @type {number}\n   */\n  popupWidth = '--popup-width',\n  /**\n   * The fixed height of the popup element.\n   * @type {number}\n   */\n  popupHeight = '--popup-height',\n}\n"
  },
  {
    "path": "packages/react/src/navigation-menu/popup/NavigationMenuPopupDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum NavigationMenuPopupDataAttributes {\n  /**\n   * Present when the popup is open.\n   */\n  open = CommonPopupDataAttributes.open,\n  /**\n   * Present when the popup is closed.\n   */\n  closed = CommonPopupDataAttributes.closed,\n  /**\n   * Present when the popup is animating in.\n   */\n  startingStyle = CommonPopupDataAttributes.startingStyle,\n  /**\n   * Present when the popup is animating out.\n   */\n  endingStyle = CommonPopupDataAttributes.endingStyle,\n  /**\n   * Indicates which side the popup is positioned relative to the trigger.\n   * @type {'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'}\n   */\n  side = CommonPopupDataAttributes.side,\n  /**\n   * Indicates how the popup is aligned relative to the specified side.\n   * @type {'start' | 'center' | 'end'}\n   */\n  align = CommonPopupDataAttributes.align,\n}\n"
  },
  {
    "path": "packages/react/src/navigation-menu/portal/NavigationMenuPortal.test.tsx",
    "content": "import * as React from 'react';\nimport { NavigationMenu } from '@base-ui/react/navigation-menu';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<NavigationMenu.Portal />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<NavigationMenu.Portal />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(<NavigationMenu.Root value=\"item\">{node}</NavigationMenu.Root>);\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/navigation-menu/portal/NavigationMenuPortal.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { FloatingPortal } from '../../floating-ui-react';\nimport { useNavigationMenuRootContext } from '../root/NavigationMenuRootContext';\nimport { NavigationMenuPortalContext } from './NavigationMenuPortalContext';\n\n/**\n * A portal element that moves the popup to a different part of the DOM.\n * By default, the portal element is appended to `<body>`.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Navigation Menu](https://base-ui.com/react/components/navigation-menu)\n */\nexport const NavigationMenuPortal = React.forwardRef(function NavigationMenuPortal(\n  props: NavigationMenuPortal.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { keepMounted = false, ...portalProps } = props;\n\n  const { mounted } = useNavigationMenuRootContext();\n\n  const shouldRender = mounted || keepMounted;\n  if (!shouldRender) {\n    return null;\n  }\n\n  return (\n    <NavigationMenuPortalContext.Provider value={keepMounted}>\n      <FloatingPortal ref={forwardedRef} {...portalProps} />\n    </NavigationMenuPortalContext.Provider>\n  );\n});\n\nexport interface NavigationMenuPortalState {}\n\nexport interface NavigationMenuPortalProps extends FloatingPortal.Props<NavigationMenuPortalState> {\n  /**\n   * Whether to keep the portal mounted in the DOM while the popup is hidden.\n   * @default false\n   */\n  keepMounted?: boolean | undefined;\n  /**\n   * A parent element to render the portal element into.\n   */\n  container?: FloatingPortal.Props<NavigationMenuPortalState>['container'] | undefined;\n}\n\nexport namespace NavigationMenuPortal {\n  export type State = NavigationMenuPortalState;\n  export type Props = NavigationMenuPortalProps;\n}\n"
  },
  {
    "path": "packages/react/src/navigation-menu/portal/NavigationMenuPortalContext.ts",
    "content": "'use client';\nimport * as React from 'react';\n\nexport const NavigationMenuPortalContext = React.createContext<boolean | undefined>(undefined);\n\nexport function useNavigationMenuPortalContext() {\n  const value = React.useContext(NavigationMenuPortalContext);\n  if (value === undefined) {\n    throw new Error('Base UI: <NavigationMenu.Portal> is missing.');\n  }\n  return value;\n}\n"
  },
  {
    "path": "packages/react/src/navigation-menu/positioner/NavigationMenuPositioner.test.tsx",
    "content": "import { NavigationMenu } from '@base-ui/react/navigation-menu';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<NavigationMenu.Positioner />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<NavigationMenu.Positioner />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <NavigationMenu.Root value=\"test\">\n          <NavigationMenu.Portal>{node}</NavigationMenu.Portal>\n        </NavigationMenu.Root>,\n      );\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/navigation-menu/positioner/NavigationMenuPositioner.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport { ownerWindow } from '@base-ui/utils/owner';\nimport { useTimeout } from '@base-ui/utils/useTimeout';\nimport {\n  disableFocusInside,\n  enableFocusInside,\n  isOutsideEvent,\n} from '../../floating-ui-react/utils';\nimport { getEmptyRootContext } from '../../floating-ui-react/utils/getEmptyRootContext';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport {\n  useNavigationMenuRootContext,\n  useNavigationMenuTreeContext,\n} from '../root/NavigationMenuRootContext';\nimport { useNavigationMenuPortalContext } from '../portal/NavigationMenuPortalContext';\nimport {\n  useAnchorPositioning,\n  type Align,\n  type Side,\n  type UseAnchorPositioningSharedParameters,\n} from '../../utils/useAnchorPositioning';\nimport { NavigationMenuPositionerContext } from './NavigationMenuPositionerContext';\nimport { popupStateMapping } from '../../utils/popupStateMapping';\nimport { DROPDOWN_COLLISION_AVOIDANCE, POPUP_COLLISION_AVOIDANCE } from '../../utils/constants';\nimport { adaptiveOrigin } from '../../utils/adaptiveOriginMiddleware';\nimport { getDisabledMountTransitionStyles } from '../../utils/getDisabledMountTransitionStyles';\n\nconst EMPTY_ROOT_CONTEXT = getEmptyRootContext();\n\n/**\n * Positions the navigation menu against the currently active trigger.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Navigation Menu](https://base-ui.com/react/components/navigation-menu)\n */\nexport const NavigationMenuPositioner = React.forwardRef(function NavigationMenuPositioner(\n  componentProps: NavigationMenuPositioner.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const {\n    open,\n    mounted,\n    positionerElement,\n    setPositionerElement,\n    floatingRootContext,\n    nested,\n    transitionStatus,\n  } = useNavigationMenuRootContext();\n\n  const {\n    className,\n    render,\n    anchor,\n    positionMethod = 'absolute',\n    side = 'bottom',\n    align = 'center',\n    sideOffset = 0,\n    alignOffset = 0,\n    collisionBoundary = 'clipping-ancestors',\n    collisionPadding = 5,\n    collisionAvoidance = nested ? POPUP_COLLISION_AVOIDANCE : DROPDOWN_COLLISION_AVOIDANCE,\n    arrowPadding = 5,\n    sticky = false,\n    disableAnchorTracking = false,\n    ...elementProps\n  } = componentProps;\n\n  const keepMounted = useNavigationMenuPortalContext();\n  const nodeId = useNavigationMenuTreeContext();\n\n  const resizeTimeout = useTimeout();\n\n  const [instant, setInstant] = React.useState(false);\n\n  const positionerRef = React.useRef<HTMLDivElement | null>(null);\n  const prevTriggerElementRef = React.useRef<Element | null>(null);\n\n  // https://codesandbox.io/s/tabbable-portal-f4tng?file=/src/TabbablePortal.tsx\n  React.useEffect(() => {\n    if (!positionerElement) {\n      return undefined;\n    }\n\n    // Make sure elements inside the portal element are tabbable only when the\n    // portal has already been focused, either by tabbing into a focus trap\n    // element outside or using the mouse.\n    function onFocus(event: FocusEvent) {\n      if (positionerElement && isOutsideEvent(event)) {\n        const focusing = event.type === 'focusin';\n        const manageFocus = focusing ? enableFocusInside : disableFocusInside;\n        manageFocus(positionerElement);\n      }\n    }\n\n    // Listen to the event on the capture phase so they run before the focus\n    // trap elements onFocus prop is called.\n    positionerElement.addEventListener('focusin', onFocus, true);\n    positionerElement.addEventListener('focusout', onFocus, true);\n    return () => {\n      positionerElement.removeEventListener('focusin', onFocus, true);\n      positionerElement.removeEventListener('focusout', onFocus, true);\n    };\n  }, [positionerElement]);\n\n  const domReference = (floatingRootContext || EMPTY_ROOT_CONTEXT).useState('domReferenceElement');\n\n  const positioning = useAnchorPositioning({\n    anchor: anchor ?? domReference ?? prevTriggerElementRef,\n    positionMethod,\n    mounted,\n    side,\n    sideOffset,\n    align,\n    alignOffset,\n    arrowPadding,\n    collisionBoundary,\n    collisionPadding,\n    sticky,\n    disableAnchorTracking,\n    keepMounted,\n    floatingRootContext,\n    collisionAvoidance,\n    nodeId,\n    // Allows the menu to remain anchored without wobbling while its size\n    // and position transition simultaneously when side=top or side=left.\n    adaptiveOrigin,\n  });\n\n  const defaultProps: React.ComponentProps<'div'> = React.useMemo(() => {\n    const hiddenStyles: React.CSSProperties = {};\n\n    if (!open) {\n      hiddenStyles.pointerEvents = 'none';\n    }\n\n    return {\n      role: 'presentation',\n      hidden: !mounted,\n      style: {\n        ...positioning.positionerStyles,\n        ...hiddenStyles,\n      },\n    };\n  }, [open, mounted, positioning.positionerStyles]);\n\n  const state: NavigationMenuPositionerState = {\n    open,\n    side: positioning.side,\n    align: positioning.align,\n    anchorHidden: positioning.anchorHidden,\n    instant,\n  };\n\n  React.useEffect(() => {\n    if (!open) {\n      return undefined;\n    }\n\n    function handleResize() {\n      ReactDOM.flushSync(() => {\n        setInstant(true);\n      });\n\n      resizeTimeout.start(100, () => {\n        setInstant(false);\n      });\n    }\n\n    const win = ownerWindow(positionerElement);\n    win.addEventListener('resize', handleResize);\n    return () => {\n      win.removeEventListener('resize', handleResize);\n    };\n  }, [open, resizeTimeout, positionerElement]);\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: [forwardedRef, setPositionerElement, positionerRef],\n    props: [defaultProps, getDisabledMountTransitionStyles(transitionStatus), elementProps],\n    stateAttributesMapping: popupStateMapping,\n  });\n\n  return (\n    <NavigationMenuPositionerContext.Provider value={positioning}>\n      {element}\n    </NavigationMenuPositionerContext.Provider>\n  );\n});\n\nexport interface NavigationMenuPositionerState {\n  /**\n   * Whether the navigation menu is currently open.\n   */\n  open: boolean;\n  /**\n   * The side of the anchor the component is placed on.\n   */\n  side: Side;\n  /**\n   * The alignment of the component relative to the anchor.\n   */\n  align: Align;\n  /**\n   * Whether the anchor element is hidden.\n   */\n  anchorHidden: boolean;\n  /**\n   * Whether CSS transitions should be disabled.\n   */\n  instant: boolean;\n}\n\nexport interface NavigationMenuPositionerProps\n  extends\n    UseAnchorPositioningSharedParameters,\n    BaseUIComponentProps<'div', NavigationMenuPositionerState> {}\n\nexport namespace NavigationMenuPositioner {\n  export type State = NavigationMenuPositionerState;\n  export type Props = NavigationMenuPositionerProps;\n}\n"
  },
  {
    "path": "packages/react/src/navigation-menu/positioner/NavigationMenuPositionerContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { useAnchorPositioning } from '../../utils/useAnchorPositioning';\n\nexport type NavigationMenuPositionerContext = ReturnType<typeof useAnchorPositioning>;\n\nexport const NavigationMenuPositionerContext = React.createContext<\n  NavigationMenuPositionerContext | undefined\n>(undefined);\n\nexport function useNavigationMenuPositionerContext(\n  optional: true,\n): NavigationMenuPositionerContext | undefined;\nexport function useNavigationMenuPositionerContext(\n  optional?: false,\n): NavigationMenuPositionerContext;\nexport function useNavigationMenuPositionerContext(optional = false) {\n  const context = React.useContext(NavigationMenuPositionerContext);\n  if (!context && !optional) {\n    throw new Error(\n      'Base UI: NavigationMenuPositionerContext is missing. NavigationMenuPositioner parts must be placed within <NavigationMenu.Positioner>.',\n    );\n  }\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/navigation-menu/positioner/NavigationMenuPositionerCssVars.ts",
    "content": "export enum NavigationMenuPositionerCssVars {\n  /**\n   * The available width between the trigger and the edge of the viewport.\n   * @type {number}\n   */\n  availableWidth = '--available-width',\n  /**\n   * The available height between the trigger and the edge of the viewport.\n   * @type {number}\n   */\n  availableHeight = '--available-height',\n  /**\n   * The anchor's width.\n   * @type {number}\n   */\n  anchorWidth = '--anchor-width',\n  /**\n   * The anchor's height.\n   * @type {number}\n   */\n  anchorHeight = '--anchor-height',\n  /**\n   * The coordinates that this element is anchored to. Used for animations and transitions.\n   * @type {string}\n   */\n  transformOrigin = '--transform-origin',\n  /**\n   * The fixed width of the positioner element.\n   * @type {number}\n   */\n  positionerWidth = '--positioner-width',\n  /**\n   * The fixed height of the positioner element.\n   * @type {number}\n   */\n  positionerHeight = '--positioner-height',\n}\n"
  },
  {
    "path": "packages/react/src/navigation-menu/positioner/NavigationMenuPositionerDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum NavigationMenuPositionerDataAttributes {\n  /**\n   * Present when the popup is open.\n   */\n  open = CommonPopupDataAttributes.open,\n  /**\n   * Present when the popup is closed.\n   */\n  closed = CommonPopupDataAttributes.closed,\n  /**\n   * Present when the anchor is hidden.\n   */\n  anchorHidden = CommonPopupDataAttributes.anchorHidden,\n  /**\n   * Indicates which side the popup is positioned relative to the trigger.\n   * @type {'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'}\n   */\n  side = CommonPopupDataAttributes.side,\n  /**\n   * Indicates how the popup is aligned relative to the specified side.\n   * @type {'start' | 'center' | 'end'}\n   */\n  align = CommonPopupDataAttributes.align,\n  /**\n   * Present if animations should be instant.\n   */\n  instant = 'data-instant',\n}\n"
  },
  {
    "path": "packages/react/src/navigation-menu/root/NavigationMenuRoot.spec.tsx",
    "content": "import { expectType } from '#test-utils';\nimport { NavigationMenu } from '@base-ui/react/navigation-menu';\n\nconst stringValue = 'item-1';\nconst nullableValue: string | null = 'item-1';\n\n<NavigationMenu.Root\n  value={stringValue}\n  onValueChange={(value) => {\n    expectType<string | null, typeof value>(value);\n  }}\n/>;\n\n<NavigationMenu.Root\n  defaultValue={1}\n  onValueChange={(value) => {\n    expectType<number | null, typeof value>(value);\n  }}\n/>;\n\n<NavigationMenu.Root<'a' | 'b'> value=\"a\" />;\n\n<NavigationMenu.Root<'a' | 'b'>\n  onValueChange={(value) => {\n    expectType<'a' | 'b' | null, typeof value>(value);\n  }}\n/>;\n\n<NavigationMenu.Root<string | null>\n  value={nullableValue}\n  onValueChange={(value) => {\n    expectType<string | null, typeof value>(value);\n  }}\n/>;\n\n<NavigationMenu.Root\n  onValueChange={(value) => {\n    // Backward-compatible default: no explicit generic keeps permissive `any`.\n    expectType<any, typeof value>(value);\n  }}\n/>;\n\n// @ts-expect-error value must match explicit generic type\n<NavigationMenu.Root<'a' | 'b'> value=\"c\" />;\n\ntype NavigationMenuChangeHandler = NonNullable<NavigationMenu.Root.Props<'a'>['onValueChange']>;\ntype NavigationMenuDefaultChangeHandler = NonNullable<NavigationMenu.Root.Props['onValueChange']>;\n\nconst handleValueChange: NavigationMenuChangeHandler = (value) => {\n  expectType<'a' | null, typeof value>(value);\n};\n\n<NavigationMenu.Root<'a'> onValueChange={handleValueChange} />;\n\nconst handleDefaultValueChange: NavigationMenuDefaultChangeHandler = (value) => {\n  expectType<any, typeof value>(value);\n};\n\n<NavigationMenu.Root onValueChange={handleDefaultValueChange} />;\n\nexport function Wrapper<Value>(props: NavigationMenu.Root.Props<Value>) {\n  return <NavigationMenu.Root {...props} />;\n}\n"
  },
  {
    "path": "packages/react/src/navigation-menu/root/NavigationMenuRoot.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { fireEvent, screen, flushMicrotasks, act, within, waitFor } from '@mui/internal-test-utils';\nimport { NavigationMenu } from '@base-ui/react/navigation-menu';\nimport { Dialog } from '@base-ui/react/dialog';\nimport { Popover } from '@base-ui/react/popover';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\nimport { PATIENT_CLICK_THRESHOLD } from '../../utils/constants';\nimport { OPEN_DELAY } from '../utils/constants';\n\nfunction TestNavigationMenu(props: NavigationMenu.Root.Props) {\n  return (\n    <NavigationMenu.Root {...props}>\n      <NavigationMenu.List>\n        <NavigationMenu.Item value=\"item-1\">\n          <NavigationMenu.Trigger data-testid=\"trigger-1\">Item 1</NavigationMenu.Trigger>\n          <NavigationMenu.Content data-testid=\"popup-1\">\n            <NavigationMenu.Link href=\"#link-1\">Link 1</NavigationMenu.Link>\n            <NavigationMenu.Link href=\"#link-2\">Link 2</NavigationMenu.Link>\n          </NavigationMenu.Content>\n        </NavigationMenu.Item>\n        <NavigationMenu.Item value=\"item-2\">\n          <NavigationMenu.Trigger data-testid=\"trigger-2\">Item 2</NavigationMenu.Trigger>\n          <NavigationMenu.Content data-testid=\"popup-2\">\n            <NavigationMenu.Link href=\"#link-3\">Link 3</NavigationMenu.Link>\n            <NavigationMenu.Link href=\"#link-4\">Link 4</NavigationMenu.Link>\n          </NavigationMenu.Content>\n        </NavigationMenu.Item>\n      </NavigationMenu.List>\n\n      <NavigationMenu.Portal>\n        <NavigationMenu.Positioner data-testid=\"top-level-positioner\">\n          <NavigationMenu.Popup>\n            <NavigationMenu.Viewport />\n          </NavigationMenu.Popup>\n        </NavigationMenu.Positioner>\n      </NavigationMenu.Portal>\n    </NavigationMenu.Root>\n  );\n}\n\nfunction TestNestedNavigationMenu(props: NavigationMenu.Root.Props = {}) {\n  return (\n    <NavigationMenu.Root {...props}>\n      <NavigationMenu.List>\n        <NavigationMenu.Item value=\"item-1\">\n          <NavigationMenu.Trigger data-testid=\"trigger-1\">Item 1</NavigationMenu.Trigger>\n\n          <NavigationMenu.Content data-testid=\"popup-1\">\n            <NavigationMenu.Link href=\"#link-1\">Link 1</NavigationMenu.Link>\n            <NavigationMenu.Root>\n              <NavigationMenu.List>\n                <NavigationMenu.Item value=\"nested-item-1\">\n                  <NavigationMenu.Trigger data-testid=\"nested-trigger-1\">\n                    Nested Item 1\n                  </NavigationMenu.Trigger>\n                  <NavigationMenu.Content data-testid=\"nested-popup-1\">\n                    <NavigationMenu.Link href=\"#nested-link-1\">Nested Link 1</NavigationMenu.Link>\n                  </NavigationMenu.Content>\n                </NavigationMenu.Item>\n              </NavigationMenu.List>\n\n              <NavigationMenu.Portal>\n                <NavigationMenu.Positioner side=\"right\" data-testid=\"nested-positioner\">\n                  <NavigationMenu.Popup>\n                    <NavigationMenu.Viewport />\n                  </NavigationMenu.Popup>\n                </NavigationMenu.Positioner>\n              </NavigationMenu.Portal>\n            </NavigationMenu.Root>\n          </NavigationMenu.Content>\n        </NavigationMenu.Item>\n\n        <NavigationMenu.Item value=\"item-2\">\n          <NavigationMenu.Trigger data-testid=\"trigger-2\">Item 2</NavigationMenu.Trigger>\n          <NavigationMenu.Content data-testid=\"popup-2\">\n            <NavigationMenu.Link href=\"#link-3\">Link 3</NavigationMenu.Link>\n          </NavigationMenu.Content>\n        </NavigationMenu.Item>\n      </NavigationMenu.List>\n\n      <NavigationMenu.Portal>\n        <NavigationMenu.Positioner data-testid=\"top-level-positioner\">\n          <NavigationMenu.Popup>\n            <NavigationMenu.Viewport />\n          </NavigationMenu.Popup>\n        </NavigationMenu.Positioner>\n      </NavigationMenu.Portal>\n    </NavigationMenu.Root>\n  );\n}\n\nfunction TestNavigationMenuOrientationAttributes() {\n  return (\n    <NavigationMenu.Root data-testid=\"top-level-root\" defaultValue=\"item-1\" orientation=\"vertical\">\n      <NavigationMenu.List data-testid=\"top-level-list\">\n        <NavigationMenu.Item value=\"item-1\">\n          <NavigationMenu.Trigger>Item 1</NavigationMenu.Trigger>\n          <NavigationMenu.Content>\n            <NavigationMenu.Root\n              data-testid=\"nested-root\"\n              defaultValue=\"nested-item-1\"\n              orientation=\"vertical\"\n            >\n              <NavigationMenu.List data-testid=\"nested-list\">\n                <NavigationMenu.Item value=\"nested-item-1\">\n                  <NavigationMenu.Trigger>Nested Item 1</NavigationMenu.Trigger>\n                  <NavigationMenu.Content>\n                    <NavigationMenu.Link href=\"#nested-link-1\">Nested Link 1</NavigationMenu.Link>\n                  </NavigationMenu.Content>\n                </NavigationMenu.Item>\n              </NavigationMenu.List>\n\n              <NavigationMenu.Viewport />\n            </NavigationMenu.Root>\n          </NavigationMenu.Content>\n        </NavigationMenu.Item>\n      </NavigationMenu.List>\n\n      <NavigationMenu.Portal>\n        <NavigationMenu.Positioner>\n          <NavigationMenu.Popup>\n            <NavigationMenu.Viewport />\n          </NavigationMenu.Popup>\n        </NavigationMenu.Positioner>\n      </NavigationMenu.Portal>\n    </NavigationMenu.Root>\n  );\n}\n\nfunction TestInlineNestedNavigationMenu(props: { nestedDefaultValue?: string | null } = {}) {\n  const { nestedDefaultValue = 'nested-item-1' } = props;\n  const nestedRootProps =\n    nestedDefaultValue == null ? undefined : { defaultValue: nestedDefaultValue };\n\n  return (\n    <NavigationMenu.Root>\n      <NavigationMenu.List>\n        <NavigationMenu.Item value=\"item-1\">\n          <NavigationMenu.Trigger data-testid=\"trigger-1\">Item 1</NavigationMenu.Trigger>\n\n          <NavigationMenu.Content data-testid=\"popup-1\">\n            <NavigationMenu.Link href=\"#link-1\">Link 1</NavigationMenu.Link>\n            <NavigationMenu.Root {...nestedRootProps}>\n              <NavigationMenu.List data-testid=\"inline-nested-list\">\n                <NavigationMenu.Item value=\"nested-item-1\">\n                  <NavigationMenu.Trigger data-testid=\"nested-trigger-1\">\n                    Nested Item 1\n                  </NavigationMenu.Trigger>\n                  <NavigationMenu.Content data-testid=\"nested-popup-1\">\n                    <NavigationMenu.Link href=\"#nested-link-1\">Nested Link 1</NavigationMenu.Link>\n                  </NavigationMenu.Content>\n                </NavigationMenu.Item>\n                <NavigationMenu.Item value=\"nested-item-2\">\n                  <NavigationMenu.Trigger data-testid=\"nested-trigger-2\">\n                    Nested Item 2\n                  </NavigationMenu.Trigger>\n                  <NavigationMenu.Content data-testid=\"nested-popup-2\">\n                    <NavigationMenu.Link href=\"#nested-link-2\">Nested Link 2</NavigationMenu.Link>\n                  </NavigationMenu.Content>\n                </NavigationMenu.Item>\n              </NavigationMenu.List>\n\n              <NavigationMenu.Viewport data-testid=\"inline-nested-viewport\" />\n            </NavigationMenu.Root>\n          </NavigationMenu.Content>\n        </NavigationMenu.Item>\n\n        <NavigationMenu.Item value=\"item-2\">\n          <NavigationMenu.Trigger data-testid=\"trigger-2\">Item 2</NavigationMenu.Trigger>\n          <NavigationMenu.Content data-testid=\"popup-2\">\n            <NavigationMenu.Link href=\"#link-3\">Link 3</NavigationMenu.Link>\n          </NavigationMenu.Content>\n        </NavigationMenu.Item>\n      </NavigationMenu.List>\n\n      <NavigationMenu.Portal>\n        <NavigationMenu.Positioner data-testid=\"positioner\">\n          <NavigationMenu.Popup data-testid=\"popup-root\">\n            <NavigationMenu.Viewport />\n          </NavigationMenu.Popup>\n        </NavigationMenu.Positioner>\n      </NavigationMenu.Portal>\n    </NavigationMenu.Root>\n  );\n}\n\nfunction TestInlineNestedNavigationMenuWithDynamicContent({\n  initialContentStage = 0,\n}: {\n  initialContentStage?: number;\n} = {}) {\n  const [contentStage, setContentStage] = React.useState(initialContentStage);\n\n  return (\n    <NavigationMenu.Root>\n      <NavigationMenu.List>\n        <NavigationMenu.Item value=\"item-1\">\n          <NavigationMenu.Trigger data-testid=\"trigger-1\">Item 1</NavigationMenu.Trigger>\n\n          <NavigationMenu.Content data-testid=\"popup-1\">\n            <NavigationMenu.Link href=\"#link-1\">Link 1</NavigationMenu.Link>\n            <NavigationMenu.Root defaultValue=\"nested-item-1\">\n              <NavigationMenu.List>\n                <NavigationMenu.Item value=\"nested-item-1\">\n                  <NavigationMenu.Trigger data-testid=\"nested-trigger-1\">\n                    Nested Item 1\n                  </NavigationMenu.Trigger>\n                  <NavigationMenu.Content data-testid=\"nested-popup-1\">\n                    <button\n                      type=\"button\"\n                      data-testid=\"insert-content\"\n                      onClick={() => {\n                        setContentStage((prev) => Math.min(prev + 1, 2));\n                      }}\n                    >\n                      Insert content\n                    </button>\n                    {contentStage >= 1 && (\n                      <div data-testid=\"extra-content\">\n                        <NavigationMenu.Link href=\"#nested-link-1\">\n                          Nested Link 1\n                        </NavigationMenu.Link>\n                        <NavigationMenu.Link href=\"#nested-link-2\">\n                          Nested Link 2\n                        </NavigationMenu.Link>\n                        <NavigationMenu.Link href=\"#nested-link-3\">\n                          Nested Link 3\n                        </NavigationMenu.Link>\n                      </div>\n                    )}\n                    {contentStage >= 2 && (\n                      <div data-testid=\"extra-content-2\">\n                        <NavigationMenu.Link href=\"#nested-link-4\">\n                          Nested Link 4\n                        </NavigationMenu.Link>\n                        <NavigationMenu.Link href=\"#nested-link-5\">\n                          Nested Link 5\n                        </NavigationMenu.Link>\n                      </div>\n                    )}\n                  </NavigationMenu.Content>\n                </NavigationMenu.Item>\n              </NavigationMenu.List>\n\n              <NavigationMenu.Viewport />\n            </NavigationMenu.Root>\n          </NavigationMenu.Content>\n        </NavigationMenu.Item>\n      </NavigationMenu.List>\n\n      <NavigationMenu.Portal>\n        <NavigationMenu.Positioner data-testid=\"positioner\">\n          <NavigationMenu.Popup data-testid=\"popup-root\">\n            <NavigationMenu.Viewport />\n          </NavigationMenu.Popup>\n        </NavigationMenu.Positioner>\n      </NavigationMenu.Portal>\n    </NavigationMenu.Root>\n  );\n}\n\nfunction TestInlineNestedNavigationMenuTabForwardBoundary() {\n  return (\n    <NavigationMenu.Root>\n      <NavigationMenu.List>\n        <NavigationMenu.Item value=\"item-1\">\n          <NavigationMenu.Trigger data-testid=\"trigger-1\">Product</NavigationMenu.Trigger>\n\n          <NavigationMenu.Content data-testid=\"popup-1\">\n            <NavigationMenu.Root defaultValue=\"nested-item-2\">\n              <NavigationMenu.List>\n                <NavigationMenu.Item value=\"nested-item-1\">\n                  <NavigationMenu.Trigger data-testid=\"nested-trigger-1\">\n                    Engineering Leads\n                  </NavigationMenu.Trigger>\n                  <NavigationMenu.Content data-testid=\"nested-popup-1\">\n                    <NavigationMenu.Link href=\"#releases\">Releases</NavigationMenu.Link>\n                  </NavigationMenu.Content>\n                </NavigationMenu.Item>\n                <NavigationMenu.Item value=\"nested-item-2\">\n                  <NavigationMenu.Trigger data-testid=\"nested-trigger-2\">\n                    Startups\n                  </NavigationMenu.Trigger>\n                  <NavigationMenu.Content data-testid=\"nested-popup-2\">\n                    <NavigationMenu.Link href=\"#quick-start\">Quick start</NavigationMenu.Link>\n                    <NavigationMenu.Link href=\"#menu\">Menu</NavigationMenu.Link>\n                    <NavigationMenu.Link href=\"#select\" data-testid=\"nested-last-link\">\n                      Select\n                    </NavigationMenu.Link>\n                  </NavigationMenu.Content>\n                </NavigationMenu.Item>\n              </NavigationMenu.List>\n\n              <NavigationMenu.Viewport />\n            </NavigationMenu.Root>\n          </NavigationMenu.Content>\n        </NavigationMenu.Item>\n\n        <NavigationMenu.Item value=\"item-2\">\n          <NavigationMenu.Trigger data-testid=\"trigger-2\">Learn</NavigationMenu.Trigger>\n          <NavigationMenu.Content data-testid=\"popup-2\">\n            <NavigationMenu.Link href=\"#learn\">Learn link</NavigationMenu.Link>\n          </NavigationMenu.Content>\n        </NavigationMenu.Item>\n      </NavigationMenu.List>\n\n      <NavigationMenu.Portal>\n        <NavigationMenu.Positioner>\n          <NavigationMenu.Popup>\n            <NavigationMenu.Viewport />\n          </NavigationMenu.Popup>\n        </NavigationMenu.Positioner>\n      </NavigationMenu.Portal>\n    </NavigationMenu.Root>\n  );\n}\n\nfunction TestInlineNestedNavigationMenuTabFlow() {\n  return (\n    <NavigationMenu.Root>\n      <NavigationMenu.List>\n        <NavigationMenu.Item value=\"item-1\">\n          <NavigationMenu.Trigger data-testid=\"trigger-product\">Product</NavigationMenu.Trigger>\n\n          <NavigationMenu.Content data-testid=\"popup-product\">\n            <NavigationMenu.Root defaultValue=\"developers\" orientation=\"vertical\">\n              <NavigationMenu.List>\n                <NavigationMenu.Item value=\"developers\">\n                  <NavigationMenu.Trigger data-testid=\"nested-trigger-developers\">\n                    Developers\n                  </NavigationMenu.Trigger>\n                  <NavigationMenu.Content data-testid=\"nested-popup-developers\">\n                    <NavigationMenu.Link href=\"#get-started\" data-testid=\"nested-link-get-started\">\n                      Get started\n                    </NavigationMenu.Link>\n                    <NavigationMenu.Link href=\"#composition\" data-testid=\"nested-link-composition\">\n                      Composition\n                    </NavigationMenu.Link>\n                  </NavigationMenu.Content>\n                </NavigationMenu.Item>\n                <NavigationMenu.Item value=\"design-systems\">\n                  <NavigationMenu.Trigger data-testid=\"nested-trigger-design-systems\">\n                    Design Systems\n                  </NavigationMenu.Trigger>\n                  <NavigationMenu.Content data-testid=\"nested-popup-design-systems\">\n                    <NavigationMenu.Link\n                      href=\"#styling\"\n                      data-testid=\"nested-link-design-systems-styling\"\n                    >\n                      Styling\n                    </NavigationMenu.Link>\n                    <NavigationMenu.Link href=\"#accessibility\">Accessibility</NavigationMenu.Link>\n                  </NavigationMenu.Content>\n                </NavigationMenu.Item>\n                <NavigationMenu.Item value=\"engineering-leads\">\n                  <NavigationMenu.Trigger>Engineering Leads</NavigationMenu.Trigger>\n                  <NavigationMenu.Content>\n                    <NavigationMenu.Link href=\"#releases\">Releases</NavigationMenu.Link>\n                  </NavigationMenu.Content>\n                </NavigationMenu.Item>\n              </NavigationMenu.List>\n\n              <NavigationMenu.Viewport />\n            </NavigationMenu.Root>\n          </NavigationMenu.Content>\n        </NavigationMenu.Item>\n\n        <NavigationMenu.Item value=\"item-2\">\n          <NavigationMenu.Trigger data-testid=\"trigger-learn\">Learn</NavigationMenu.Trigger>\n          <NavigationMenu.Content>\n            <NavigationMenu.Link href=\"#learn\">Learn link</NavigationMenu.Link>\n          </NavigationMenu.Content>\n        </NavigationMenu.Item>\n      </NavigationMenu.List>\n\n      <NavigationMenu.Portal>\n        <NavigationMenu.Positioner>\n          <NavigationMenu.Popup>\n            <NavigationMenu.Viewport />\n          </NavigationMenu.Popup>\n        </NavigationMenu.Positioner>\n      </NavigationMenu.Portal>\n    </NavigationMenu.Root>\n  );\n}\n\nfunction TestNavigationMenuWithKeepMountedContent() {\n  return (\n    <NavigationMenu.Root defaultValue=\"item-1\">\n      <NavigationMenu.List>\n        <NavigationMenu.Item value=\"item-1\">\n          <NavigationMenu.Trigger data-testid=\"trigger-product\">Product</NavigationMenu.Trigger>\n          <NavigationMenu.Content keepMounted>\n            <div style={{ width: 675, height: 220 }}>Product panel</div>\n          </NavigationMenu.Content>\n        </NavigationMenu.Item>\n\n        <NavigationMenu.Item value=\"item-2\">\n          <NavigationMenu.Trigger data-testid=\"trigger-learn\">Learn</NavigationMenu.Trigger>\n          <NavigationMenu.Content keepMounted>\n            <div style={{ width: 500, height: 180 }}>Learn panel</div>\n          </NavigationMenu.Content>\n        </NavigationMenu.Item>\n      </NavigationMenu.List>\n\n      <NavigationMenu.Portal>\n        <NavigationMenu.Positioner data-testid=\"positioner\">\n          <NavigationMenu.Popup data-testid=\"popup-root\">\n            <NavigationMenu.Viewport />\n          </NavigationMenu.Popup>\n        </NavigationMenu.Positioner>\n      </NavigationMenu.Portal>\n    </NavigationMenu.Root>\n  );\n}\n\nfunction TestNavigationMenuWithKeepMountedContentClosed() {\n  return (\n    <NavigationMenu.Root>\n      <NavigationMenu.List>\n        <NavigationMenu.Item value=\"item-1\">\n          <NavigationMenu.Trigger data-testid=\"trigger-product\">Product</NavigationMenu.Trigger>\n          <NavigationMenu.Content keepMounted>\n            <div style={{ width: 675, height: 220 }}>Product panel</div>\n          </NavigationMenu.Content>\n        </NavigationMenu.Item>\n\n        <NavigationMenu.Item value=\"item-2\">\n          <NavigationMenu.Trigger data-testid=\"trigger-learn\">Learn</NavigationMenu.Trigger>\n          <NavigationMenu.Content keepMounted>\n            <div style={{ width: 500, height: 180 }}>Learn panel</div>\n          </NavigationMenu.Content>\n        </NavigationMenu.Item>\n      </NavigationMenu.List>\n\n      <NavigationMenu.Portal keepMounted>\n        <NavigationMenu.Positioner data-testid=\"positioner\">\n          <NavigationMenu.Popup data-testid=\"popup-root\">\n            <NavigationMenu.Viewport />\n          </NavigationMenu.Popup>\n        </NavigationMenu.Positioner>\n      </NavigationMenu.Portal>\n    </NavigationMenu.Root>\n  );\n}\n\nfunction TestNavigationMenuWithScopedPopupExitAnimation(\n  props: {\n    onOpenChangeComplete?: NavigationMenu.Root.Props['onOpenChangeComplete'];\n  } = {},\n) {\n  const { onOpenChangeComplete } = props;\n  const style = `\n    .test-navigation-menu-popup {\n      transition-property: opacity, transform, width, height;\n      transition-duration: 350ms;\n      transition-timing-function: cubic-bezier(0.22, 1, 0.36, 1);\n    }\n\n    .test-navigation-menu-popup[data-starting-style],\n    .test-navigation-menu-popup[data-ending-style] {\n      opacity: 0;\n      transform: scale(0.9);\n    }\n\n    .test-navigation-menu-popup[data-ending-style] {\n      transition-property: opacity, transform;\n      transition-duration: 150ms;\n      transition-timing-function: ease;\n    }\n\n    .test-navigation-menu-content {\n      transition:\n        opacity 175ms ease,\n        transform 350ms cubic-bezier(0.4, 0, 0.2, 1);\n    }\n\n    .test-navigation-menu-content[data-starting-style],\n    .test-navigation-menu-content[data-ending-style] {\n      opacity: 0;\n    }\n\n    .test-navigation-menu-content[data-starting-style][data-activation-direction='left'] {\n      transform: translateX(-2rem);\n    }\n\n    .test-navigation-menu-content[data-starting-style][data-activation-direction='right'] {\n      transform: translateX(2rem);\n    }\n\n    .test-navigation-menu-content[data-ending-style] {\n      transition-duration: 175ms;\n      transition-timing-function: ease;\n    }\n\n    .test-navigation-menu-content[data-ending-style][data-activation-direction='left'] {\n      transform: translateX(2rem);\n    }\n\n    .test-navigation-menu-content[data-ending-style][data-activation-direction='right'] {\n      transform: translateX(-2rem);\n    }\n  `;\n\n  return (\n    <NavigationMenu.Root onOpenChangeComplete={onOpenChangeComplete}>\n      {/* eslint-disable-next-line react/no-danger */}\n      <style dangerouslySetInnerHTML={{ __html: style }} />\n      <NavigationMenu.List>\n        <NavigationMenu.Item value=\"item-1\">\n          <NavigationMenu.Trigger data-testid=\"trigger-product\">Product</NavigationMenu.Trigger>\n          <NavigationMenu.Content className=\"test-navigation-menu-content\">\n            <div style={{ width: 675, height: 220 }}>Product panel</div>\n          </NavigationMenu.Content>\n        </NavigationMenu.Item>\n\n        <NavigationMenu.Item value=\"item-2\">\n          <NavigationMenu.Trigger data-testid=\"trigger-learn\">Learn</NavigationMenu.Trigger>\n          <NavigationMenu.Content className=\"test-navigation-menu-content\">\n            <div style={{ width: 500, height: 180 }}>Learn panel</div>\n          </NavigationMenu.Content>\n        </NavigationMenu.Item>\n      </NavigationMenu.List>\n\n      <NavigationMenu.Portal>\n        <NavigationMenu.Positioner>\n          <NavigationMenu.Popup className=\"test-navigation-menu-popup\" data-testid=\"popup-root\">\n            <NavigationMenu.Viewport />\n          </NavigationMenu.Popup>\n        </NavigationMenu.Positioner>\n      </NavigationMenu.Portal>\n    </NavigationMenu.Root>\n  );\n}\n\nfunction mockBoundingClientRect(\n  element: Element,\n  rect: { x: number; y: number; width: number; height: number },\n) {\n  const domRect = {\n    x: rect.x,\n    y: rect.y,\n    width: rect.width,\n    height: rect.height,\n    top: rect.y,\n    left: rect.x,\n    right: rect.x + rect.width,\n    bottom: rect.y + rect.height,\n    toJSON: () => ({}),\n  };\n\n  Object.defineProperty(element, 'getBoundingClientRect', {\n    configurable: true,\n    value: () => domRect,\n  });\n}\n\nfunction mockAnimations(element: HTMLElement) {\n  type MockAnimation = {\n    finished: Promise<void>;\n    resolveFinished: (() => void) | null;\n  };\n\n  function createAnimation(): MockAnimation {\n    let resolveFinished: (() => void) | null = null;\n\n    return {\n      finished: new Promise<void>((resolve) => {\n        resolveFinished = resolve;\n      }),\n      resolveFinished,\n    };\n  }\n\n  let currentAnimation = createAnimation();\n  let activeAnimations: MockAnimation[] = [];\n\n  Object.defineProperty(element, 'getAnimations', {\n    configurable: true,\n    value: () =>\n      activeAnimations.map((animation) => ({\n        finished: animation.finished,\n      })),\n  });\n\n  return {\n    start() {\n      currentAnimation = createAnimation();\n      activeAnimations.push(currentAnimation);\n      return currentAnimation;\n    },\n    finish(animation: MockAnimation = currentAnimation) {\n      const finished = animation.finished;\n      animation.resolveFinished?.();\n      animation.resolveFinished = null;\n      activeAnimations = activeAnimations.filter((item) => item !== animation);\n      return finished;\n    },\n  };\n}\n\nfunction mockResizeObserver() {\n  const originalResizeObserver = globalThis.ResizeObserver;\n\n  globalThis.ResizeObserver = class {\n    observe() {}\n    unobserve() {}\n    disconnect() {}\n  } as unknown as typeof ResizeObserver;\n\n  return () => {\n    globalThis.ResizeObserver = originalResizeObserver;\n  };\n}\n\nfunction TestDeeplyNestedNavigationMenu() {\n  return (\n    <NavigationMenu.Root>\n      <NavigationMenu.List>\n        <NavigationMenu.Item value=\"item-1\">\n          <NavigationMenu.Trigger data-testid=\"trigger-1\">Item 1</NavigationMenu.Trigger>\n\n          <NavigationMenu.Content data-testid=\"content-1\">\n            <NavigationMenu.Link href=\"#link-1\" data-testid=\"link-1\">\n              Link 1\n            </NavigationMenu.Link>\n            {/* Level 2 */}\n            <NavigationMenu.Root defaultValue=\"level2-item-1\">\n              <NavigationMenu.List>\n                <NavigationMenu.Item value=\"level2-item-1\">\n                  <NavigationMenu.Trigger data-testid=\"level2-trigger-1\">\n                    Level 2 Item 1\n                  </NavigationMenu.Trigger>\n                  <NavigationMenu.Content data-testid=\"level2-content-1\">\n                    <NavigationMenu.Link href=\"#level2-link-1\" data-testid=\"level2-link-1\">\n                      Level 2 Link 1\n                    </NavigationMenu.Link>\n                    {/* Level 3 */}\n                    <NavigationMenu.Root defaultValue=\"level3-item-1\">\n                      <NavigationMenu.List>\n                        <NavigationMenu.Item value=\"level3-item-1\">\n                          <NavigationMenu.Trigger data-testid=\"level3-trigger-1\">\n                            Level 3 Item 1\n                          </NavigationMenu.Trigger>\n                          <NavigationMenu.Content data-testid=\"level3-content-1\">\n                            <NavigationMenu.Link href=\"#level3-link-1\">\n                              Level 3 Link 1\n                            </NavigationMenu.Link>\n                          </NavigationMenu.Content>\n                        </NavigationMenu.Item>\n                        <NavigationMenu.Item value=\"level3-item-2\">\n                          <NavigationMenu.Trigger data-testid=\"level3-trigger-2\">\n                            Level 3 Item 2\n                          </NavigationMenu.Trigger>\n                          <NavigationMenu.Content data-testid=\"level3-content-2\">\n                            <NavigationMenu.Link href=\"#level3-link-2\">\n                              Level 3 Link 2\n                            </NavigationMenu.Link>\n                          </NavigationMenu.Content>\n                        </NavigationMenu.Item>\n                      </NavigationMenu.List>\n                      <NavigationMenu.Viewport />\n                    </NavigationMenu.Root>\n                  </NavigationMenu.Content>\n                </NavigationMenu.Item>\n                <NavigationMenu.Item value=\"level2-item-2\">\n                  <NavigationMenu.Trigger data-testid=\"level2-trigger-2\">\n                    Level 2 Item 2\n                  </NavigationMenu.Trigger>\n                  <NavigationMenu.Content data-testid=\"level2-content-2\">\n                    <NavigationMenu.Link href=\"#level2-link-2\">Level 2 Link 2</NavigationMenu.Link>\n                  </NavigationMenu.Content>\n                </NavigationMenu.Item>\n              </NavigationMenu.List>\n              <NavigationMenu.Viewport />\n            </NavigationMenu.Root>\n          </NavigationMenu.Content>\n        </NavigationMenu.Item>\n      </NavigationMenu.List>\n\n      <NavigationMenu.Portal>\n        <NavigationMenu.Positioner>\n          <NavigationMenu.Popup>\n            <NavigationMenu.Viewport />\n          </NavigationMenu.Popup>\n        </NavigationMenu.Positioner>\n      </NavigationMenu.Portal>\n    </NavigationMenu.Root>\n  );\n}\n\nfunction TestNestedNavigationMenuWithCloseOnClick(props: {\n  onValueChange?: NavigationMenu.Root.Props['onValueChange'];\n}) {\n  return (\n    <NavigationMenu.Root onValueChange={props.onValueChange}>\n      <NavigationMenu.List>\n        <NavigationMenu.Item value=\"item-1\">\n          <NavigationMenu.Trigger data-testid=\"trigger-1\">Item 1</NavigationMenu.Trigger>\n\n          <NavigationMenu.Content data-testid=\"popup-1\">\n            <NavigationMenu.Link href=\"#link-1\" closeOnClick>\n              Link 1\n            </NavigationMenu.Link>\n            <NavigationMenu.Root>\n              <NavigationMenu.List>\n                <NavigationMenu.Item value=\"nested-item-1\">\n                  <NavigationMenu.Trigger data-testid=\"nested-trigger-1\">\n                    Nested Item 1\n                  </NavigationMenu.Trigger>\n                  <NavigationMenu.Content data-testid=\"nested-popup-1\">\n                    <NavigationMenu.Link\n                      href=\"#nested-link-1\"\n                      closeOnClick\n                      data-testid=\"nested-link-1\"\n                    >\n                      Nested Link 1\n                    </NavigationMenu.Link>\n                  </NavigationMenu.Content>\n                </NavigationMenu.Item>\n              </NavigationMenu.List>\n\n              <NavigationMenu.Portal>\n                <NavigationMenu.Positioner side=\"right\">\n                  <NavigationMenu.Popup>\n                    <NavigationMenu.Viewport />\n                  </NavigationMenu.Popup>\n                </NavigationMenu.Positioner>\n              </NavigationMenu.Portal>\n            </NavigationMenu.Root>\n          </NavigationMenu.Content>\n        </NavigationMenu.Item>\n      </NavigationMenu.List>\n\n      <NavigationMenu.Portal>\n        <NavigationMenu.Positioner>\n          <NavigationMenu.Popup>\n            <NavigationMenu.Viewport />\n          </NavigationMenu.Popup>\n        </NavigationMenu.Positioner>\n      </NavigationMenu.Portal>\n    </NavigationMenu.Root>\n  );\n}\n\nfunction TestDeeplyNestedNavigationMenuWithCloseOnClick() {\n  return (\n    <NavigationMenu.Root>\n      <NavigationMenu.List>\n        <NavigationMenu.Item value=\"item-1\">\n          <NavigationMenu.Trigger data-testid=\"trigger-1\">Item 1</NavigationMenu.Trigger>\n\n          <NavigationMenu.Content data-testid=\"content-1\">\n            <NavigationMenu.Root defaultValue=\"level2-item-1\">\n              <NavigationMenu.List>\n                <NavigationMenu.Item value=\"level2-item-1\">\n                  <NavigationMenu.Trigger data-testid=\"level2-trigger-1\">\n                    Level 2 Item 1\n                  </NavigationMenu.Trigger>\n                  <NavigationMenu.Content data-testid=\"level2-content-1\">\n                    <NavigationMenu.Root defaultValue=\"level3-item-1\">\n                      <NavigationMenu.List>\n                        <NavigationMenu.Item value=\"level3-item-1\">\n                          <NavigationMenu.Trigger data-testid=\"level3-trigger-1\">\n                            Level 3 Item 1\n                          </NavigationMenu.Trigger>\n                          <NavigationMenu.Content data-testid=\"level3-content-1\">\n                            <NavigationMenu.Link\n                              href=\"#level3-link-1\"\n                              closeOnClick\n                              data-testid=\"level3-link-1\"\n                            >\n                              Level 3 Link 1\n                            </NavigationMenu.Link>\n                          </NavigationMenu.Content>\n                        </NavigationMenu.Item>\n                      </NavigationMenu.List>\n                      <NavigationMenu.Viewport />\n                    </NavigationMenu.Root>\n                  </NavigationMenu.Content>\n                </NavigationMenu.Item>\n              </NavigationMenu.List>\n              <NavigationMenu.Viewport />\n            </NavigationMenu.Root>\n          </NavigationMenu.Content>\n        </NavigationMenu.Item>\n      </NavigationMenu.List>\n\n      <NavigationMenu.Portal>\n        <NavigationMenu.Positioner>\n          <NavigationMenu.Popup>\n            <NavigationMenu.Viewport />\n          </NavigationMenu.Popup>\n        </NavigationMenu.Positioner>\n      </NavigationMenu.Portal>\n    </NavigationMenu.Root>\n  );\n}\n\nfunction TestNavigationMenuWithNestedPopup(props: { children: React.ReactNode }) {\n  const { children } = props;\n  return (\n    <NavigationMenu.Root>\n      <NavigationMenu.List>\n        <NavigationMenu.Item value=\"item-1\">\n          <NavigationMenu.Trigger data-testid=\"trigger-1\">Item 1</NavigationMenu.Trigger>\n\n          <NavigationMenu.Content data-testid=\"popup-1\">{children}</NavigationMenu.Content>\n        </NavigationMenu.Item>\n      </NavigationMenu.List>\n\n      <NavigationMenu.Portal>\n        <NavigationMenu.Positioner>\n          <NavigationMenu.Popup>\n            <NavigationMenu.Viewport />\n          </NavigationMenu.Popup>\n        </NavigationMenu.Positioner>\n      </NavigationMenu.Portal>\n    </NavigationMenu.Root>\n  );\n}\n\nfunction TestNavigationMenuWithDialog() {\n  return (\n    <TestNavigationMenuWithNestedPopup>\n      <Dialog.Root>\n        <Dialog.Trigger data-testid=\"dialog-trigger\">Open dialog</Dialog.Trigger>\n        <Dialog.Portal>\n          <Dialog.Popup\n            data-testid=\"dialog-popup\"\n            onClick={(event) => {\n              event.stopPropagation();\n            }}\n          >\n            <button type=\"button\" data-testid=\"dialog-button\">\n              Dialog button\n            </button>\n          </Dialog.Popup>\n        </Dialog.Portal>\n      </Dialog.Root>\n    </TestNavigationMenuWithNestedPopup>\n  );\n}\n\nfunction TestNavigationMenuWithPopover() {\n  return (\n    <TestNavigationMenuWithNestedPopup>\n      <Popover.Root>\n        <Popover.Trigger data-testid=\"popover-trigger\">Open popover</Popover.Trigger>\n        <Popover.Portal>\n          <Popover.Positioner>\n            <Popover.Popup\n              data-testid=\"popover-popup\"\n              onClick={(event) => {\n                event.stopPropagation();\n              }}\n            >\n              <button type=\"button\" data-testid=\"popover-button\">\n                Popover button\n              </button>\n            </Popover.Popup>\n          </Popover.Positioner>\n        </Popover.Portal>\n      </Popover.Root>\n    </TestNavigationMenuWithNestedPopup>\n  );\n}\n\ndescribe('<NavigationMenu.Root />', () => {\n  const { render, clock } = createRenderer({\n    clockOptions: {\n      shouldAdvanceTime: true,\n    },\n  });\n\n  clock.withFakeTimers();\n\n  describeConformance(<NavigationMenu.Root />, () => ({\n    refInstanceof: window.HTMLElement,\n    render(node) {\n      return render(node);\n    },\n  }));\n\n  it('does not apply aria-orientation to the top-level list or root element', async () => {\n    await render(<TestNavigationMenuOrientationAttributes />);\n\n    expect(screen.getByTestId('top-level-root')).not.toHaveAttribute('aria-orientation');\n    expect(screen.getByTestId('top-level-list')).not.toHaveAttribute('aria-orientation');\n  });\n\n  it('does not apply aria-orientation to nested lists or root elements', async () => {\n    await render(<TestNavigationMenuOrientationAttributes />);\n\n    expect(screen.getByTestId('nested-root')).not.toHaveAttribute('aria-orientation');\n    expect(screen.getByTestId('nested-list')).not.toHaveAttribute('aria-orientation');\n  });\n\n  describe('interactions', () => {\n    it('opens on hover with mouse input', async () => {\n      await render(<TestNavigationMenu />);\n      const trigger = screen.getByTestId('trigger-1');\n\n      fireEvent.mouseEnter(trigger);\n      fireEvent.mouseMove(trigger);\n      clock.tick(50);\n      await flushMicrotasks();\n\n      expect(screen.queryByTestId('popup-1')).not.toBe(null);\n      expect(trigger).toHaveAttribute('aria-expanded', 'true');\n    });\n\n    it('blocks pointer events on the body while traversing from a top-level trigger to the popup', async () => {\n      await render(<TestNavigationMenu />);\n      const trigger = screen.getByTestId('trigger-1');\n      const siblingTrigger = screen.getByTestId('trigger-2');\n\n      fireEvent.mouseEnter(trigger);\n      fireEvent.mouseMove(trigger);\n      clock.tick(50);\n      await flushMicrotasks();\n\n      expect(screen.queryByTestId('popup-1')).not.toBe(null);\n      expect(document.body.style.pointerEvents).toBe('none');\n      expect(getComputedStyle(siblingTrigger).pointerEvents).toBe('none');\n\n      fireEvent.mouseEnter(screen.getByTestId('top-level-positioner'));\n      await flushMicrotasks();\n\n      expect(document.body.style.pointerEvents).toBe('');\n    });\n\n    it.skipIf(isJSDOM)(\n      'blocks pointer events on sibling top-level triggers when opened through real hover',\n      async () => {\n        const { user } = await render(<TestNavigationMenu />);\n        const trigger = screen.getByTestId('trigger-1');\n        const siblingTrigger = screen.getByTestId('trigger-2');\n\n        await user.hover(trigger);\n\n        await waitFor(() => {\n          expect(document.body.style.pointerEvents).toBe('none');\n        });\n\n        expect(getComputedStyle(siblingTrigger).pointerEvents).toBe('none');\n      },\n    );\n\n    it('opens on click with mouse input', async () => {\n      await render(<TestNavigationMenu />);\n      const trigger = screen.getByTestId('trigger-1');\n\n      fireEvent.click(trigger);\n      await flushMicrotasks();\n\n      expect(screen.queryByTestId('popup-1')).not.toBe(null);\n      expect(trigger).toHaveAttribute('aria-expanded', 'true');\n    });\n\n    it('does not open on hover with touch input', async () => {\n      await render(<TestNavigationMenu />);\n      const trigger = screen.getByTestId('trigger-1');\n\n      fireEvent.pointerEnter(trigger, { pointerType: 'touch' });\n      await flushMicrotasks();\n\n      expect(screen.queryByTestId('popup-1')).toBe(null);\n      expect(trigger).toHaveAttribute('aria-expanded', 'false');\n    });\n\n    it('opens on click with touch input', async () => {\n      await render(<TestNavigationMenu />);\n      const trigger = screen.getByTestId('trigger-1');\n\n      fireEvent.pointerDown(trigger, { pointerType: 'touch' });\n      fireEvent.pointerUp(trigger, { pointerType: 'touch' });\n      fireEvent.click(trigger);\n      await flushMicrotasks();\n\n      expect(screen.queryByTestId('popup-1')).not.toBe(null);\n      expect(trigger).toHaveAttribute('aria-expanded', 'true');\n    });\n\n    it.skipIf(isJSDOM)('restores hover open after a touch click closes outside', async () => {\n      const { user } = await render(\n        <div>\n          <TestNavigationMenu />\n          <button data-testid=\"outside\" />\n        </div>,\n      );\n      const trigger1 = screen.getByTestId('trigger-1');\n      const trigger2 = screen.getByTestId('trigger-2');\n\n      fireEvent.pointerEnter(trigger1, { pointerType: 'touch' });\n      fireEvent.pointerDown(trigger1, { pointerType: 'touch' });\n      fireEvent.pointerUp(trigger1, { pointerType: 'touch' });\n      fireEvent.click(trigger1);\n      await flushMicrotasks();\n\n      expect(screen.queryByTestId('popup-1')).not.toBe(null);\n\n      await user.click(screen.getByTestId('outside'));\n      await flushMicrotasks();\n\n      expect(screen.queryByTestId('popup-1')).toBe(null);\n\n      await user.hover(trigger2);\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('popup-2')).not.toBe(null);\n      });\n    });\n\n    it.skipIf(isJSDOM)(\n      'restores hover open after touching a nested submenu trigger and closing outside',\n      async () => {\n        const { user } = await render(\n          <div>\n            <TestInlineNestedNavigationMenu />\n            <button data-testid=\"outside\" />\n          </div>,\n        );\n        const trigger = screen.getByTestId('trigger-1');\n\n        fireEvent.pointerEnter(trigger, { pointerType: 'touch' });\n        fireEvent.pointerDown(trigger, { pointerType: 'touch' });\n        fireEvent.pointerUp(trigger, { pointerType: 'touch' });\n        fireEvent.click(trigger);\n        await flushMicrotasks();\n\n        const nestedTrigger2 = screen.getByTestId('nested-trigger-2');\n        fireEvent.pointerEnter(nestedTrigger2, { pointerType: 'touch' });\n        fireEvent.pointerDown(nestedTrigger2, { pointerType: 'touch' });\n        fireEvent.pointerUp(nestedTrigger2, { pointerType: 'touch' });\n        fireEvent.click(nestedTrigger2);\n        await flushMicrotasks();\n\n        expect(screen.queryByTestId('nested-popup-2')).not.toBe(null);\n\n        await user.click(screen.getByTestId('outside'));\n        await flushMicrotasks();\n\n        expect(screen.queryByTestId('popup-1')).toBe(null);\n\n        await user.hover(trigger);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('popup-1')).not.toBe(null);\n        });\n      },\n    );\n\n    it('restores hover open after a quick click then trigger switch', async () => {\n      const { user } = await render(<TestNavigationMenu />);\n      const trigger1 = screen.getByTestId('trigger-1');\n      const trigger2 = screen.getByTestId('trigger-2');\n\n      await user.hover(trigger1);\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('popup-1')).not.toBe(null);\n      });\n\n      await user.click(trigger1);\n      await flushMicrotasks();\n\n      await user.hover(trigger2);\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('popup-2')).not.toBe(null);\n      });\n\n      await user.unhover(trigger2);\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('popup-2')).toBe(null);\n      });\n\n      await user.hover(trigger1);\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('popup-1')).not.toBe(null);\n      });\n    });\n\n    it('closes after pointerdown on a link in a hover-open popup when pointer leaves', async () => {\n      const { user } = await render(<TestNavigationMenu />);\n      const trigger1 = screen.getByTestId('trigger-1');\n\n      await user.hover(trigger1);\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('popup-1')).not.toBe(null);\n      });\n\n      const popup1 = screen.getByTestId('popup-1');\n      const link1 = within(popup1).getByText('Link 1');\n\n      await user.hover(link1);\n      fireEvent.pointerDown(link1, { pointerType: 'mouse' });\n      await user.unhover(link1);\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('popup-1')).toBe(null);\n      });\n    });\n\n    it('does not close menu when clicking a different trigger with mouse', async () => {\n      await render(<TestNavigationMenu />);\n      const trigger1 = screen.getByTestId('trigger-1');\n      const trigger2 = screen.getByTestId('trigger-2');\n\n      fireEvent.click(trigger1);\n      await flushMicrotasks();\n\n      expect(screen.queryByTestId('popup-1')).not.toBe(null);\n      expect(trigger1).toHaveAttribute('aria-expanded', 'true');\n\n      fireEvent.click(trigger2);\n      await flushMicrotasks();\n\n      expect(screen.queryByTestId('popup-1')).toBe(null);\n      expect(trigger1).toHaveAttribute('aria-expanded', 'false');\n      expect(screen.queryByTestId('popup-2')).not.toBe(null);\n      expect(trigger2).toHaveAttribute('aria-expanded', 'true');\n    });\n\n    it('does not close menu when clicking a different trigger on touch', async () => {\n      await render(<TestNavigationMenu />);\n      const trigger1 = screen.getByTestId('trigger-1');\n      const trigger2 = screen.getByTestId('trigger-2');\n\n      fireEvent.click(trigger1);\n      await flushMicrotasks();\n\n      expect(screen.queryByTestId('popup-1')).not.toBe(null);\n      expect(trigger1).toHaveAttribute('aria-expanded', 'true');\n\n      fireEvent.pointerDown(trigger2, { pointerType: 'touch' });\n      fireEvent.pointerUp(trigger2, { pointerType: 'touch' });\n      fireEvent.click(trigger2);\n      await flushMicrotasks();\n\n      expect(screen.queryByTestId('popup-1')).toBe(null);\n      expect(trigger1).toHaveAttribute('aria-expanded', 'false');\n      expect(screen.queryByTestId('popup-2')).not.toBe(null);\n      expect(trigger2).toHaveAttribute('aria-expanded', 'true');\n    });\n\n    it('returns focus to trigger when closing menu', async () => {\n      const { user } = await render(\n        <div>\n          <button data-testid=\"first\" />\n          <TestNavigationMenu />\n          <button data-testid=\"last\" />\n        </div>,\n      );\n\n      const trigger = screen.getByTestId('trigger-1');\n\n      await user.click(trigger);\n      await flushMicrotasks();\n\n      expect(screen.queryByTestId('popup-1')).not.toBe(null);\n      expect(trigger).toHaveFocus();\n\n      await user.keyboard('{Escape}');\n      await flushMicrotasks();\n\n      expect(screen.queryByTestId('popup-1')).toBe(null);\n      expect(trigger).toHaveFocus();\n    });\n\n    it('respects focus outside when clicking menu', async () => {\n      const { user } = await render(\n        <div>\n          <button data-testid=\"first\" />\n          <TestNavigationMenu />\n          <button data-testid=\"last\" />\n        </div>,\n      );\n\n      const trigger = screen.getByTestId('trigger-1');\n      const last = screen.getByTestId('last');\n\n      await user.click(trigger);\n      await flushMicrotasks();\n\n      expect(screen.queryByTestId('popup-1')).not.toBe(null);\n      expect(trigger).toHaveFocus();\n\n      await user.click(screen.getByTestId('last'));\n      await flushMicrotasks();\n\n      expect(screen.queryByTestId('popup-1')).toBe(null);\n      expect(last).toHaveFocus();\n    });\n\n    it('does not restore focus to the trigger when closed via hover', async () => {\n      await render(<TestNavigationMenu />);\n      const trigger = screen.getByTestId('trigger-1');\n\n      fireEvent.mouseEnter(trigger);\n      fireEvent.mouseMove(trigger);\n      clock.tick(OPEN_DELAY);\n\n      const popup = await screen.findByTestId('popup-1');\n      expect(trigger).toHaveAttribute('aria-expanded', 'true');\n\n      fireEvent.mouseLeave(trigger);\n      fireEvent.mouseLeave(popup);\n      clock.tick(50);\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('popup-1')).toBe(null);\n      });\n      expect(trigger).toHaveAttribute('aria-expanded', 'false');\n      expect(trigger).not.toHaveFocus();\n    });\n\n    it('does not restore focus to the trigger when focus moves outside', async () => {\n      const { user } = await render(\n        <div>\n          <button data-testid=\"first\" />\n          <TestNavigationMenu />\n          <button data-testid=\"last\" />\n        </div>,\n      );\n\n      const trigger = screen.getByTestId('trigger-1');\n      const last = screen.getByTestId('last');\n\n      await act(async () => trigger.focus());\n\n      await user.click(trigger);\n      await user.tab();\n      await user.tab();\n      await user.tab();\n      await user.tab();\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('popup-1')).toBe(null);\n      });\n      expect(last).toHaveFocus();\n      expect(trigger).not.toHaveFocus();\n    });\n  });\n\n  describe('patient click threshold', () => {\n    it('closes if hovered then clicked after the patient threshold', async () => {\n      await render(<TestNavigationMenu />);\n      const trigger = screen.getByTestId('trigger-1');\n\n      fireEvent.click(trigger);\n      clock.tick(OPEN_DELAY);\n      await flushMicrotasks();\n\n      expect(screen.queryByTestId('popup-1')).not.toBe(null);\n\n      clock.tick(PATIENT_CLICK_THRESHOLD);\n\n      fireEvent.click(trigger);\n      await flushMicrotasks();\n\n      expect(screen.queryByTestId('popup-1')).toBe(null);\n      expect(trigger).toHaveAttribute('aria-expanded', 'false');\n    });\n  });\n\n  describe('prop: defaultValue', () => {\n    it('should respect defaultValue', async () => {\n      await render(<TestNavigationMenu defaultValue=\"item-1\" />);\n      const trigger = screen.getByTestId('trigger-1');\n\n      fireEvent.mouseEnter(trigger);\n      fireEvent.mouseMove(trigger);\n      clock.tick(OPEN_DELAY);\n      await flushMicrotasks();\n      expect(trigger).toHaveAttribute('aria-expanded', 'true');\n    });\n  });\n\n  describe('prop: onValueChange', () => {\n    it('should call onValueChange when value changes', async () => {\n      const onValueChange = vi.fn();\n      await render(<TestNavigationMenu onValueChange={onValueChange} />);\n      const trigger1 = screen.getByTestId('trigger-1');\n      const trigger2 = screen.getByTestId('trigger-2');\n\n      fireEvent.click(trigger1);\n      await flushMicrotasks();\n      expect(onValueChange.mock.calls.length).toBe(1);\n      expect(onValueChange.mock.lastCall?.[0]).toBe('item-1');\n\n      fireEvent.click(trigger2);\n      await flushMicrotasks();\n      expect(onValueChange.mock.calls.length).toBe(2);\n      expect(onValueChange.mock.lastCall?.[0]).toBe('item-2');\n    });\n\n    it('should be controlled by value prop', async () => {\n      const { setProps } = await render(<TestNavigationMenu value=\"item-1\" />);\n\n      let trigger1 = screen.getByTestId('trigger-1');\n      fireEvent.mouseEnter(trigger1);\n      fireEvent.mouseMove(trigger1);\n      clock.tick(OPEN_DELAY);\n      await flushMicrotasks();\n      expect(trigger1).toHaveAttribute('aria-expanded', 'true');\n\n      await setProps({ value: 'item-2' });\n\n      const trigger2 = screen.getByTestId('trigger-2');\n      fireEvent.mouseLeave(trigger1);\n      fireEvent.mouseEnter(trigger2);\n      fireEvent.mouseMove(trigger2);\n      await flushMicrotasks();\n\n      trigger1 = screen.getByTestId('trigger-1');\n      expect(trigger1).toHaveAttribute('aria-expanded', 'false');\n      expect(trigger2).toHaveAttribute('aria-expanded', 'true');\n    });\n  });\n\n  describe('prop: delay', () => {\n    it('respects custom delay value', async () => {\n      const customDelay = 100;\n      await render(<TestNavigationMenu delay={customDelay} />);\n      const trigger = screen.getByTestId('trigger-1');\n\n      fireEvent.mouseEnter(trigger);\n      fireEvent.mouseMove(trigger);\n      clock.tick(customDelay - 25);\n      await flushMicrotasks();\n\n      // Menu shouldn't be open yet since we're before the delay\n      expect(screen.queryByTestId('popup-1')).toBe(null);\n\n      clock.tick(50);\n      await flushMicrotasks();\n\n      expect(screen.queryByTestId('popup-1')).not.toBe(null);\n      expect(trigger).toHaveAttribute('aria-expanded', 'true');\n    });\n  });\n\n  describe('prop: closeDelay', () => {\n    it('respects custom closeDelay value', async () => {\n      const customCloseDelay = 100;\n      await render(<TestNavigationMenu closeDelay={customCloseDelay} />);\n      const trigger = screen.getByTestId('trigger-1');\n\n      fireEvent.mouseEnter(trigger);\n      fireEvent.mouseMove(trigger);\n      clock.tick(OPEN_DELAY);\n      await flushMicrotasks();\n      expect(screen.queryByTestId('popup-1')).not.toBe(null);\n\n      fireEvent.mouseLeave(trigger);\n      clock.tick(customCloseDelay - 25);\n      await flushMicrotasks();\n\n      expect(screen.queryByTestId('popup-1')).not.toBe(null);\n\n      // Complete the closeDelay\n      clock.tick(50);\n      await flushMicrotasks();\n\n      expect(screen.queryByTestId('popup-1')).toBe(null);\n      expect(trigger).toHaveAttribute('aria-expanded', 'false');\n    });\n  });\n\n  describe('tabbing', () => {\n    it('moves focus through the menu correctly', async () => {\n      const { user } = await render(\n        <div>\n          <button data-testid=\"first\" />\n          <TestNavigationMenu />\n        </div>,\n      );\n      const trigger1 = screen.getByTestId('trigger-1');\n\n      await act(async () => trigger1.focus());\n\n      fireEvent.click(trigger1);\n      await flushMicrotasks();\n\n      expect(screen.getByTestId('popup-1')).not.toBe(null);\n      expect(trigger1).toHaveFocus();\n\n      await user.tab();\n      expect(screen.getByText('Link 1')).toHaveFocus();\n\n      await user.tab();\n      expect(screen.getByText('Link 2')).toHaveFocus();\n\n      await user.tab();\n      expect(screen.getByTestId('trigger-2')).toHaveFocus();\n\n      fireEvent.click(screen.getByTestId('trigger-2'));\n      await flushMicrotasks();\n\n      expect(screen.getByTestId('popup-2')).not.toBe(null);\n\n      await user.tab();\n      expect(screen.getByText('Link 3')).toHaveFocus();\n\n      await user.tab();\n      expect(screen.getByText('Link 4')).toHaveFocus();\n\n      await user.tab({ shift: true });\n      await user.tab({ shift: true });\n      await user.tab({ shift: true });\n\n      expect(trigger1).toHaveFocus();\n    });\n\n    it('closes the menu when tabbing forward out', async () => {\n      const { user } = await render(\n        <div>\n          <button data-testid=\"first\" />\n          <TestNavigationMenu />\n          <button data-testid=\"last\" />\n        </div>,\n      );\n      const trigger = screen.getByTestId('trigger-1');\n\n      await act(async () => trigger.focus());\n      fireEvent.click(trigger);\n      await flushMicrotasks();\n\n      expect(screen.getByTestId('popup-1')).not.toBe(null);\n      expect(trigger).toHaveFocus();\n\n      await user.tab(); // Link 1\n      await user.tab(); // Link 2\n      await user.tab(); // trigger 2\n      await user.tab(); // last\n\n      expect(screen.queryByTestId('popup-1')).toBe(null);\n    });\n\n    it('closes the menu when tabbing back out', async () => {\n      const { user } = await render(\n        <div>\n          <button data-testid=\"first\" />\n          <TestNavigationMenu />\n        </div>,\n      );\n      const trigger = screen.getByTestId('trigger-1');\n\n      await act(async () => trigger.focus());\n      fireEvent.click(trigger);\n      await flushMicrotasks();\n\n      expect(screen.getByTestId('popup-1')).not.toBe(null);\n      expect(trigger).toHaveFocus();\n\n      await user.tab();\n      expect(screen.getByText('Link 1')).toHaveFocus();\n\n      await user.tab({ shift: true }); // trigger 1\n      await user.tab({ shift: true }); // first\n\n      expect(screen.queryByTestId('popup-1')).toBe(null);\n    });\n  });\n\n  describe('nested popups', () => {\n    it('keeps a hover-open menu open when pointerdown happens on a nested dialog trigger', async () => {\n      const { user } = await render(<TestNavigationMenuWithDialog />);\n      const trigger = screen.getByTestId('trigger-1');\n\n      fireEvent.mouseEnter(trigger);\n      fireEvent.mouseMove(trigger);\n      clock.tick(OPEN_DELAY);\n      await flushMicrotasks();\n\n      const popup = screen.getByTestId('popup-1');\n      const dialogTrigger = screen.getByTestId('dialog-trigger');\n\n      fireEvent.pointerDown(dialogTrigger, { pointerType: 'mouse' });\n      fireEvent.mouseLeave(popup);\n\n      await user.click(dialogTrigger);\n\n      expect(await screen.findByTestId('dialog-popup')).not.toBe(null);\n      expect(screen.queryByTestId('popup-1')).not.toBe(null);\n      expect(trigger).toHaveAttribute('aria-expanded', 'true');\n    });\n\n    it('keeps the menu open when interacting with a nested dialog', async () => {\n      const { user } = await render(<TestNavigationMenuWithDialog />);\n      const trigger = screen.getByTestId('trigger-1');\n\n      await user.click(trigger);\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('popup-1')).not.toBe(null);\n      });\n      expect(trigger).toHaveAttribute('aria-expanded', 'true');\n\n      const dialogTrigger = screen.getByTestId('dialog-trigger');\n      await user.click(dialogTrigger);\n\n      expect(await screen.findByTestId('dialog-popup')).not.toBe(null);\n\n      await user.click(screen.getByTestId('dialog-button'));\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('popup-1')).not.toBe(null);\n      });\n      expect(trigger).toHaveAttribute('aria-expanded', 'true');\n    });\n\n    it('keeps the menu open when interacting with a nested popover', async () => {\n      const { user } = await render(<TestNavigationMenuWithPopover />);\n      const trigger = screen.getByTestId('trigger-1');\n\n      await user.click(trigger);\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('popup-1')).not.toBe(null);\n      });\n      expect(trigger).toHaveAttribute('aria-expanded', 'true');\n\n      const popoverTrigger = screen.getByTestId('popover-trigger');\n      await user.click(popoverTrigger);\n\n      expect(await screen.findByTestId('popover-popup')).not.toBe(null);\n\n      await user.click(screen.getByTestId('popover-button'));\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('popup-1')).not.toBe(null);\n      });\n      expect(trigger).toHaveAttribute('aria-expanded', 'true');\n    });\n  });\n\n  describe('nested menus', () => {\n    it('opens nested menu on hover and stays open when hovering over nested popup', async () => {\n      await render(<TestNestedNavigationMenu />);\n      const trigger1 = screen.getByTestId('trigger-1');\n\n      fireEvent.mouseEnter(trigger1);\n      fireEvent.mouseMove(trigger1);\n      clock.tick(OPEN_DELAY);\n      await flushMicrotasks();\n\n      const popup1 = screen.getByTestId('popup-1');\n      expect(popup1).not.toBe(null);\n      expect(trigger1).toHaveAttribute('aria-expanded', 'true');\n\n      const nestedTrigger1 = within(popup1).getByTestId('nested-trigger-1');\n\n      fireEvent.mouseEnter(nestedTrigger1);\n      fireEvent.mouseMove(nestedTrigger1);\n      clock.tick(OPEN_DELAY);\n      await flushMicrotasks();\n\n      const nestedPopup1 = screen.getByTestId('nested-popup-1');\n      expect(nestedPopup1).not.toBe(null);\n      expect(nestedTrigger1).toHaveAttribute('aria-expanded', 'true');\n\n      fireEvent.mouseEnter(nestedPopup1);\n      fireEvent.mouseMove(nestedPopup1);\n      await flushMicrotasks();\n\n      expect(screen.queryByTestId('popup-1')).not.toBe(null);\n      expect(screen.queryByTestId('nested-popup-1')).not.toBe(null);\n      expect(trigger1).toHaveAttribute('aria-expanded', 'true');\n      expect(nestedTrigger1).toHaveAttribute('aria-expanded', 'true');\n    });\n\n    it('handles inline nested menu without positioner/popup correctly', async () => {\n      await render(<TestInlineNestedNavigationMenu />);\n      const trigger1 = screen.getByTestId('trigger-1');\n\n      fireEvent.mouseEnter(trigger1);\n      fireEvent.mouseMove(trigger1);\n      clock.tick(OPEN_DELAY);\n      await flushMicrotasks();\n\n      const popup1 = screen.getByTestId('popup-1');\n      expect(popup1).not.toBe(null);\n      expect(trigger1).toHaveAttribute('aria-expanded', 'true');\n\n      const nestedTrigger1 = within(popup1).getByTestId('nested-trigger-1');\n\n      fireEvent.mouseEnter(nestedTrigger1);\n      fireEvent.mouseMove(nestedTrigger1);\n      clock.tick(OPEN_DELAY);\n      await flushMicrotasks();\n\n      const nestedPopup1 = screen.getByTestId('nested-popup-1');\n      expect(nestedPopup1).not.toBe(null);\n      expect(nestedTrigger1).toHaveAttribute('aria-expanded', 'true');\n\n      fireEvent.mouseEnter(nestedPopup1);\n      fireEvent.mouseMove(nestedPopup1);\n      await flushMicrotasks();\n\n      expect(screen.queryByTestId('popup-1')).not.toBe(null);\n      expect(screen.queryByTestId('nested-popup-1')).not.toBe(null);\n      expect(trigger1).toHaveAttribute('aria-expanded', 'true');\n      expect(nestedTrigger1).toHaveAttribute('aria-expanded', 'true');\n\n      const nestedTrigger2 = within(popup1).getByTestId('nested-trigger-2');\n      fireEvent.mouseEnter(nestedTrigger2);\n      fireEvent.mouseMove(nestedTrigger2);\n      clock.tick(OPEN_DELAY);\n      await flushMicrotasks();\n\n      const nestedPopup2 = screen.getByTestId('nested-popup-2');\n      expect(nestedPopup2).not.toBe(null);\n      expect(nestedTrigger2).toHaveAttribute('aria-expanded', 'true');\n      expect(nestedTrigger1).toHaveAttribute('aria-expanded', 'false');\n    });\n\n    it('keeps parent menu open when hovering inline nested triggers without defaultValue', async () => {\n      await render(<TestInlineNestedNavigationMenu nestedDefaultValue={null} />);\n      const trigger1 = screen.getByTestId('trigger-1');\n\n      fireEvent.mouseEnter(trigger1);\n      fireEvent.mouseMove(trigger1);\n      clock.tick(OPEN_DELAY);\n      await flushMicrotasks();\n\n      const popup1 = screen.getByTestId('popup-1');\n      const nestedTrigger1 = within(popup1).getByTestId('nested-trigger-1');\n      expect(nestedTrigger1).toHaveAttribute('aria-expanded', 'false');\n\n      fireEvent.mouseEnter(nestedTrigger1);\n      fireEvent.mouseMove(nestedTrigger1);\n      clock.tick(OPEN_DELAY);\n      await flushMicrotasks();\n\n      expect(screen.queryByTestId('popup-1')).not.toBe(null);\n      expect(trigger1).toHaveAttribute('aria-expanded', 'true');\n      expect(screen.getByTestId('nested-popup-1')).not.toBe(null);\n      expect(nestedTrigger1).toHaveAttribute('aria-expanded', 'true');\n\n      const nestedTrigger2 = within(popup1).getByTestId('nested-trigger-2');\n      fireEvent.mouseEnter(nestedTrigger2);\n      fireEvent.mouseMove(nestedTrigger2);\n      clock.tick(OPEN_DELAY);\n      await flushMicrotasks();\n\n      expect(screen.queryByTestId('popup-1')).not.toBe(null);\n      expect(trigger1).toHaveAttribute('aria-expanded', 'true');\n      expect(screen.getByTestId('nested-popup-2')).not.toBe(null);\n      expect(nestedTrigger2).toHaveAttribute('aria-expanded', 'true');\n    });\n\n    it('closes the parent menu after a nested submenu closes on delayed hover-out', async () => {\n      const closeDelay = 200;\n\n      await render(<TestNestedNavigationMenu closeDelay={closeDelay} />);\n      const trigger1 = screen.getByTestId('trigger-1');\n\n      fireEvent.mouseEnter(trigger1);\n      fireEvent.mouseMove(trigger1);\n      clock.tick(OPEN_DELAY);\n      await flushMicrotasks();\n\n      const popup1 = screen.getByTestId('popup-1');\n      const nestedTrigger1 = within(popup1).getByTestId('nested-trigger-1');\n      const topLevelPositioner = screen.getByTestId('top-level-positioner');\n\n      fireEvent.mouseEnter(nestedTrigger1);\n      fireEvent.mouseMove(nestedTrigger1);\n      clock.tick(OPEN_DELAY);\n      await flushMicrotasks();\n\n      const nestedPopup1 = screen.getByTestId('nested-popup-1');\n      const nestedPositioner = screen.getByTestId('nested-positioner');\n      expect(nestedPopup1).not.toBe(null);\n\n      fireEvent.mouseLeave(nestedTrigger1);\n      fireEvent.mouseLeave(nestedPositioner);\n      fireEvent.mouseLeave(topLevelPositioner);\n      clock.tick(closeDelay);\n      await flushMicrotasks();\n\n      expect(screen.queryByTestId('nested-popup-1')).toBe(null);\n      expect(screen.queryByTestId('popup-1')).toBe(null);\n      expect(trigger1).toHaveAttribute('aria-expanded', 'false');\n    });\n\n    it('closes the parent menu when a nested link with closeOnClick is clicked', async () => {\n      const { user } = await render(<TestNestedNavigationMenuWithCloseOnClick />);\n      const trigger1 = screen.getByTestId('trigger-1');\n\n      await user.click(trigger1);\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('popup-1')).not.toBe(null);\n      });\n\n      const popup1 = screen.getByTestId('popup-1');\n      const nestedTrigger1 = within(popup1).getByTestId('nested-trigger-1');\n\n      fireEvent.mouseEnter(nestedTrigger1);\n      fireEvent.mouseMove(nestedTrigger1);\n      clock.tick(OPEN_DELAY);\n      await flushMicrotasks();\n\n      expect(screen.queryByTestId('nested-popup-1')).not.toBe(null);\n\n      const nestedLink = screen.getByTestId('nested-link-1');\n      await user.click(nestedLink);\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('nested-popup-1')).toBe(null);\n      });\n      expect(screen.queryByTestId('popup-1')).toBe(null);\n      expect(trigger1).toHaveAttribute('aria-expanded', 'false');\n    });\n\n    it('calls onValueChange on the parent root when nested closeOnClick link is clicked', async () => {\n      const onValueChange = vi.fn();\n      const { user } = await render(\n        <TestNestedNavigationMenuWithCloseOnClick onValueChange={onValueChange} />,\n      );\n      const trigger1 = screen.getByTestId('trigger-1');\n\n      await user.click(trigger1);\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('popup-1')).not.toBe(null);\n      });\n\n      const popup1 = screen.getByTestId('popup-1');\n      const nestedTrigger1 = within(popup1).getByTestId('nested-trigger-1');\n\n      fireEvent.mouseEnter(nestedTrigger1);\n      fireEvent.mouseMove(nestedTrigger1);\n      clock.tick(OPEN_DELAY);\n      await flushMicrotasks();\n\n      const nestedLink = screen.getByTestId('nested-link-1');\n      await user.click(nestedLink);\n\n      await waitFor(() => {\n        expect(onValueChange.mock.lastCall?.[0]).toBe(null);\n      });\n    });\n\n    it('closes all levels when a deeply nested link with closeOnClick is clicked', async () => {\n      const { user } = await render(<TestDeeplyNestedNavigationMenuWithCloseOnClick />);\n      const trigger1 = screen.getByTestId('trigger-1');\n\n      await user.click(trigger1);\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('content-1')).not.toBe(null);\n      });\n\n      expect(screen.queryByTestId('level2-content-1')).not.toBe(null);\n      expect(screen.queryByTestId('level3-content-1')).not.toBe(null);\n\n      const level3Link = screen.getByTestId('level3-link-1');\n      await user.click(level3Link);\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('level3-content-1')).toBe(null);\n      });\n      expect(screen.queryByTestId('level2-content-1')).toBe(null);\n      expect(screen.queryByTestId('content-1')).toBe(null);\n      expect(trigger1).toHaveAttribute('aria-expanded', 'false');\n    });\n\n    describe('inline nested viewport', () => {\n      it('renders viewport content correctly for inline nested menu', async () => {\n        await render(<TestInlineNestedNavigationMenu />);\n        const trigger1 = screen.getByTestId('trigger-1');\n\n        fireEvent.mouseEnter(trigger1);\n        fireEvent.mouseMove(trigger1);\n        clock.tick(OPEN_DELAY);\n        await flushMicrotasks();\n\n        const popup1 = screen.getByTestId('popup-1');\n        expect(popup1).not.toBe(null);\n\n        const nestedTrigger1 = within(popup1).getByTestId('nested-trigger-1');\n        expect(nestedTrigger1).toHaveAttribute('aria-expanded', 'true');\n\n        const nestedPopup1 = screen.getByTestId('nested-popup-1');\n        expect(nestedPopup1).not.toBe(null);\n        expect(screen.getByText('Nested Link 1')).not.toBe(null);\n      });\n\n      it('switches content in viewport when hovering different nested triggers', async () => {\n        await render(<TestInlineNestedNavigationMenu />);\n        const trigger1 = screen.getByTestId('trigger-1');\n\n        fireEvent.mouseEnter(trigger1);\n        fireEvent.mouseMove(trigger1);\n        clock.tick(OPEN_DELAY);\n        await flushMicrotasks();\n\n        const popup1 = screen.getByTestId('popup-1');\n        const nestedTrigger1 = within(popup1).getByTestId('nested-trigger-1');\n        const nestedTrigger2 = within(popup1).getByTestId('nested-trigger-2');\n\n        expect(nestedTrigger1).toHaveAttribute('aria-expanded', 'true');\n        expect(screen.getByTestId('nested-popup-1')).not.toBe(null);\n\n        fireEvent.mouseEnter(nestedTrigger2);\n        fireEvent.mouseMove(nestedTrigger2);\n        clock.tick(OPEN_DELAY);\n        await flushMicrotasks();\n\n        expect(nestedTrigger2).toHaveAttribute('aria-expanded', 'true');\n        expect(nestedTrigger1).toHaveAttribute('aria-expanded', 'false');\n        expect(screen.getByTestId('nested-popup-2')).not.toBe(null);\n        expect(screen.queryByTestId('nested-popup-1')).toBe(null);\n\n        fireEvent.mouseEnter(nestedTrigger1);\n        fireEvent.mouseMove(nestedTrigger1);\n        clock.tick(OPEN_DELAY);\n        await flushMicrotasks();\n\n        expect(nestedTrigger1).toHaveAttribute('aria-expanded', 'true');\n        expect(nestedTrigger2).toHaveAttribute('aria-expanded', 'false');\n        expect(screen.getByTestId('nested-popup-1')).not.toBe(null);\n        expect(screen.queryByTestId('nested-popup-2')).toBe(null);\n      });\n\n      it('scopes inline safePolygon pointer events to the submenu list while traversing to the viewport', async () => {\n        await render(<TestInlineNestedNavigationMenu />);\n        const trigger1 = screen.getByTestId('trigger-1');\n\n        fireEvent.mouseEnter(trigger1);\n        fireEvent.mouseMove(trigger1);\n        clock.tick(OPEN_DELAY);\n        await flushMicrotasks();\n\n        const nestedList = screen.getByTestId('inline-nested-list');\n        const nestedTrigger1 = screen.getByTestId('nested-trigger-1');\n        const nestedViewport = screen.getByTestId('inline-nested-viewport');\n\n        mockBoundingClientRect(nestedTrigger1, { x: 0, y: 40, width: 100, height: 40 });\n        mockBoundingClientRect(nestedViewport, { x: 200, y: 0, width: 300, height: 300 });\n        fireEvent.mouseEnter(nestedTrigger1);\n\n        expect(nestedList.style.pointerEvents).toBe('none');\n\n        fireEvent.mouseLeave(nestedTrigger1, {\n          clientX: 98,\n          clientY: 60,\n        });\n\n        expect(nestedList.style.pointerEvents).toBe('none');\n        expect(document.body.style.pointerEvents).toBe('none');\n\n        fireEvent.mouseMove(document, { clientX: 150, clientY: 80 });\n        await flushMicrotasks();\n\n        expect(nestedTrigger1).toHaveAttribute('aria-expanded', 'true');\n        expect(screen.getByTestId('nested-popup-1')).not.toBe(null);\n        expect(nestedList.style.pointerEvents).toBe('none');\n\n        fireEvent.mouseEnter(nestedViewport);\n        await flushMicrotasks();\n\n        expect(nestedList.style.pointerEvents).toBe('');\n      });\n\n      it('clears inline safePolygon pointer events when the pointer leaves the traversal path', async () => {\n        await render(<TestInlineNestedNavigationMenu />);\n        const trigger1 = screen.getByTestId('trigger-1');\n\n        fireEvent.mouseEnter(trigger1);\n        fireEvent.mouseMove(trigger1);\n        clock.tick(OPEN_DELAY);\n        await flushMicrotasks();\n\n        const nestedList = screen.getByTestId('inline-nested-list');\n        const nestedTrigger1 = screen.getByTestId('nested-trigger-1');\n        const nestedViewport = screen.getByTestId('inline-nested-viewport');\n\n        mockBoundingClientRect(nestedTrigger1, { x: 0, y: 40, width: 100, height: 40 });\n        mockBoundingClientRect(nestedViewport, { x: 200, y: 0, width: 300, height: 300 });\n        fireEvent.mouseEnter(nestedTrigger1);\n\n        expect(nestedList.style.pointerEvents).toBe('none');\n\n        fireEvent.mouseLeave(nestedTrigger1, {\n          clientX: 98,\n          clientY: 60,\n        });\n        expect(nestedList.style.pointerEvents).toBe('none');\n\n        fireEvent.mouseMove(document, { clientX: 40, clientY: 220 });\n        await flushMicrotasks();\n\n        expect(nestedList.style.pointerEvents).toBe('');\n        expect(nestedTrigger1).toHaveAttribute('aria-expanded', 'true');\n        expect(screen.getByTestId('nested-popup-1')).not.toBe(null);\n      });\n\n      it('keeps inline safePolygon pointer events when returning to the original trigger before traversing again', async () => {\n        await render(<TestInlineNestedNavigationMenu />);\n        const trigger1 = screen.getByTestId('trigger-1');\n\n        fireEvent.mouseEnter(trigger1);\n        fireEvent.mouseMove(trigger1);\n        clock.tick(OPEN_DELAY);\n        await flushMicrotasks();\n\n        const nestedList = screen.getByTestId('inline-nested-list');\n        const nestedTrigger1 = screen.getByTestId('nested-trigger-1');\n        const nestedTrigger2 = screen.getByTestId('nested-trigger-2');\n        const nestedViewport = screen.getByTestId('inline-nested-viewport');\n\n        mockBoundingClientRect(nestedTrigger1, { x: 0, y: 40, width: 100, height: 40 });\n        mockBoundingClientRect(nestedTrigger2, { x: 0, y: 100, width: 100, height: 40 });\n        mockBoundingClientRect(nestedViewport, { x: 200, y: 0, width: 300, height: 300 });\n\n        fireEvent.mouseEnter(nestedTrigger1);\n        fireEvent.mouseLeave(nestedTrigger1, {\n          clientX: 98,\n          clientY: 60,\n        });\n        fireEvent.mouseMove(document, { clientX: 150, clientY: 80 });\n        await flushMicrotasks();\n        fireEvent.mouseEnter(nestedViewport);\n        await flushMicrotasks();\n\n        fireEvent.mouseEnter(nestedTrigger2);\n        fireEvent.mouseMove(nestedTrigger2);\n        clock.tick(OPEN_DELAY);\n        await flushMicrotasks();\n\n        fireEvent.mouseEnter(nestedTrigger1);\n        fireEvent.mouseMove(nestedTrigger1);\n        clock.tick(OPEN_DELAY);\n        await flushMicrotasks();\n\n        expect(nestedList.style.pointerEvents).toBe('none');\n\n        fireEvent.mouseLeave(nestedTrigger1, {\n          clientX: 98,\n          clientY: 60,\n        });\n        fireEvent.mouseMove(document, { clientX: 150, clientY: 80 });\n        await flushMicrotasks();\n\n        expect(nestedList.style.pointerEvents).toBe('none');\n        expect(nestedTrigger1).toHaveAttribute('aria-expanded', 'true');\n        expect(nestedTrigger2).toHaveAttribute('aria-expanded', 'false');\n        expect(screen.getByTestId('nested-popup-1')).not.toBe(null);\n        expect(screen.queryByTestId('nested-popup-2')).toBe(null);\n      });\n\n      it('closes inline nested viewport when parent menu closes', async () => {\n        await render(<TestInlineNestedNavigationMenu />);\n        const trigger1 = screen.getByTestId('trigger-1');\n\n        fireEvent.mouseEnter(trigger1);\n        fireEvent.mouseMove(trigger1);\n        clock.tick(OPEN_DELAY);\n        await flushMicrotasks();\n\n        const popup1 = screen.getByTestId('popup-1');\n        expect(popup1).not.toBe(null);\n\n        const nestedTrigger1 = within(popup1).getByTestId('nested-trigger-1');\n        expect(nestedTrigger1).toHaveAttribute('aria-expanded', 'true');\n        expect(screen.getByTestId('nested-popup-1')).not.toBe(null);\n\n        fireEvent.mouseLeave(trigger1);\n        fireEvent.mouseLeave(popup1);\n        clock.tick(50); // closeDelay\n        await flushMicrotasks();\n\n        expect(screen.queryByTestId('popup-1')).toBe(null);\n        expect(screen.queryByTestId('nested-popup-1')).toBe(null);\n        expect(trigger1).toHaveAttribute('aria-expanded', 'false');\n      });\n\n      it('maintains inline viewport state when hovering between triggers and content', async () => {\n        await render(<TestInlineNestedNavigationMenu />);\n        const trigger1 = screen.getByTestId('trigger-1');\n\n        fireEvent.mouseEnter(trigger1);\n        fireEvent.mouseMove(trigger1);\n        clock.tick(OPEN_DELAY);\n        await flushMicrotasks();\n\n        const popup1 = screen.getByTestId('popup-1');\n\n        const nestedTrigger2 = within(popup1).getByTestId('nested-trigger-2');\n        fireEvent.mouseEnter(nestedTrigger2);\n        fireEvent.mouseMove(nestedTrigger2);\n        clock.tick(OPEN_DELAY);\n        await flushMicrotasks();\n\n        expect(nestedTrigger2).toHaveAttribute('aria-expanded', 'true');\n        const nestedPopup2 = screen.getByTestId('nested-popup-2');\n        expect(nestedPopup2).not.toBe(null);\n\n        fireEvent.mouseEnter(nestedPopup2);\n        fireEvent.mouseMove(nestedPopup2);\n        await flushMicrotasks();\n\n        expect(nestedTrigger2).toHaveAttribute('aria-expanded', 'true');\n        expect(screen.getByTestId('nested-popup-2')).not.toBe(null);\n\n        fireEvent.mouseEnter(nestedTrigger2);\n        fireEvent.mouseMove(nestedTrigger2);\n        await flushMicrotasks();\n\n        expect(nestedTrigger2).toHaveAttribute('aria-expanded', 'true');\n        expect(screen.getByTestId('nested-popup-2')).not.toBe(null);\n      });\n\n      it('handles click interactions on inline nested menu triggers', async () => {\n        await render(<TestInlineNestedNavigationMenu />);\n        const trigger1 = screen.getByTestId('trigger-1');\n\n        fireEvent.click(trigger1);\n        await flushMicrotasks();\n\n        const popup1 = screen.getByTestId('popup-1');\n        expect(popup1).not.toBe(null);\n\n        const nestedTrigger1 = within(popup1).getByTestId('nested-trigger-1');\n        expect(nestedTrigger1).toHaveAttribute('aria-expanded', 'true');\n\n        const nestedTrigger2 = within(popup1).getByTestId('nested-trigger-2');\n        fireEvent.click(nestedTrigger2);\n        await flushMicrotasks();\n\n        expect(nestedTrigger2).toHaveAttribute('aria-expanded', 'true');\n        expect(nestedTrigger1).toHaveAttribute('aria-expanded', 'false');\n        expect(screen.getByTestId('nested-popup-2')).not.toBe(null);\n        expect(screen.queryByTestId('nested-popup-1')).toBe(null);\n\n        fireEvent.click(nestedTrigger2);\n        await flushMicrotasks();\n\n        expect(nestedTrigger2).toHaveAttribute('aria-expanded', 'true');\n        expect(nestedTrigger1).toHaveAttribute('aria-expanded', 'false');\n        expect(screen.queryByTestId('nested-popup-2')).not.toBe(null);\n        expect(screen.queryByTestId('nested-popup-1')).toBe(null);\n      });\n\n      it('allows arrow key navigation to submenu triggers', async () => {\n        const { user } = await render(<TestInlineNestedNavigationMenu />);\n        const trigger1 = screen.getByTestId('trigger-1');\n\n        fireEvent.click(trigger1);\n        await flushMicrotasks();\n\n        const popup1 = screen.getByTestId('popup-1');\n        expect(popup1).not.toBe(null);\n\n        const link1 = screen.getByText('Link 1');\n        await act(async () => link1.focus());\n\n        // Arrow down should move to nested-trigger-1\n        await user.keyboard('{ArrowDown}');\n\n        const nestedTrigger1 = within(popup1).getByTestId('nested-trigger-1');\n        expect(nestedTrigger1).toHaveFocus();\n\n        // Arrow down should move to nested-trigger-2\n        await user.keyboard('{ArrowDown}');\n\n        const nestedTrigger2 = within(popup1).getByTestId('nested-trigger-2');\n        expect(nestedTrigger2).toHaveFocus();\n\n        // Arrow up should move back to nested-trigger-1\n        await user.keyboard('{ArrowUp}');\n        expect(nestedTrigger1).toHaveFocus();\n\n        // Arrow up should move back to Link 1\n        await user.keyboard('{ArrowUp}');\n        expect(link1).toHaveFocus();\n      });\n\n      it('allows arrow key navigation with 3+ levels of nesting', async () => {\n        const { user } = await render(<TestDeeplyNestedNavigationMenu />);\n        const trigger1 = screen.getByTestId('trigger-1');\n\n        fireEvent.click(trigger1);\n        await flushMicrotasks();\n\n        const content1 = screen.getByTestId('content-1');\n        expect(content1).not.toBe(null);\n\n        // Level 1 content contains: Link 1, Level2-trigger-1, Level2-trigger-2\n        const link1 = screen.getByTestId('link-1');\n        await act(async () => link1.focus());\n\n        // Navigate through Level 1 content items\n        await user.keyboard('{ArrowDown}');\n        const level2Trigger1 = screen.getByTestId('level2-trigger-1');\n        expect(level2Trigger1).toHaveFocus();\n\n        await user.keyboard('{ArrowDown}');\n        const level2Trigger2 = screen.getByTestId('level2-trigger-2');\n        expect(level2Trigger2).toHaveFocus();\n\n        await user.keyboard('{ArrowUp}');\n        expect(level2Trigger1).toHaveFocus();\n\n        await user.keyboard('{ArrowUp}');\n        expect(link1).toHaveFocus();\n\n        // Now navigate into Level 2 content (which contains Level 3 triggers)\n        const level2Content1 = screen.getByTestId('level2-content-1');\n        const level2Link1 = within(level2Content1).getByTestId('level2-link-1');\n        await act(async () => level2Link1.focus());\n\n        // Navigate through Level 2 content items (includes Level 3 triggers)\n        await user.keyboard('{ArrowDown}');\n        const level3Trigger1 = screen.getByTestId('level3-trigger-1');\n        expect(level3Trigger1).toHaveFocus();\n\n        await user.keyboard('{ArrowDown}');\n        const level3Trigger2 = screen.getByTestId('level3-trigger-2');\n        expect(level3Trigger2).toHaveFocus();\n\n        await user.keyboard('{ArrowUp}');\n        expect(level3Trigger1).toHaveFocus();\n\n        await user.keyboard('{ArrowUp}');\n        expect(level2Link1).toHaveFocus();\n      });\n\n      it('updates popup sizing when inline nested content is inserted while active', async () => {\n        const originalResizeObserver = globalThis.ResizeObserver;\n        const previousAnimationsDisabled = globalThis.BASE_UI_ANIMATIONS_DISABLED;\n        globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n        if (typeof originalResizeObserver === 'function') {\n          globalThis.ResizeObserver = undefined as unknown as typeof ResizeObserver;\n        }\n\n        try {\n          await render(<TestInlineNestedNavigationMenuWithDynamicContent />);\n          const trigger1 = screen.getByTestId('trigger-1');\n\n          fireEvent.click(trigger1);\n          await flushMicrotasks();\n\n          const popupRoot = screen.getByTestId('popup-root');\n          const positioner = screen.getByTestId('positioner');\n          const animations = mockAnimations(popupRoot);\n\n          let popupWidth = 250;\n          let popupHeight = 120;\n\n          Object.defineProperty(popupRoot, 'offsetWidth', {\n            configurable: true,\n            get: () => popupWidth,\n          });\n          Object.defineProperty(popupRoot, 'offsetHeight', {\n            configurable: true,\n            get: () => popupHeight,\n          });\n\n          popupWidth = 250;\n          popupHeight = 220;\n          animations.start();\n          fireEvent.click(screen.getByTestId('insert-content'));\n          await flushMicrotasks();\n\n          expect(screen.getByTestId('extra-content')).not.toBe(null);\n          await waitFor(() => {\n            expect(\n              parseInt(getComputedStyle(positioner).getPropertyValue('--positioner-height'), 10),\n            ).toBe(220);\n          });\n\n          await act(async () => {\n            animations.finish();\n            await flushMicrotasks();\n          });\n\n          await waitFor(() => {\n            expect(popupRoot.style.getPropertyValue('--popup-width')).toBe('auto');\n            expect(popupRoot.style.getPropertyValue('--popup-height')).toBe('auto');\n            expect(positioner.style.getPropertyValue('--positioner-width')).toBe('250px');\n            expect(positioner.style.getPropertyValue('--positioner-height')).toBe('220px');\n          });\n        } finally {\n          globalThis.BASE_UI_ANIMATIONS_DISABLED = previousAnimationsDisabled;\n          if (typeof originalResizeObserver === 'function') {\n            globalThis.ResizeObserver = originalResizeObserver;\n          }\n        }\n      });\n\n      it('does not animate popup sizing when nested default content mounts during opening', async () => {\n        const previousAnimationsDisabled = globalThis.BASE_UI_ANIMATIONS_DISABLED;\n        globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n        try {\n          await render(<TestInlineNestedNavigationMenu />);\n          const trigger1 = screen.getByTestId('trigger-1');\n\n          fireEvent.click(trigger1);\n\n          const popupRoot = screen.getByTestId('popup-root');\n          const positioner = screen.getByTestId('positioner');\n\n          const popupWidthValues = [250, 250];\n          const popupHeightValues = [120, 220];\n          let popupWidth = 250;\n          let popupHeight = 220;\n\n          Object.defineProperty(popupRoot, 'offsetWidth', {\n            configurable: true,\n            get: () => {\n              const nextWidth = popupWidthValues.shift();\n              if (nextWidth != null) {\n                popupWidth = nextWidth;\n              }\n\n              return popupWidth;\n            },\n          });\n          Object.defineProperty(popupRoot, 'offsetHeight', {\n            configurable: true,\n            get: () => {\n              const nextHeight = popupHeightValues.shift();\n              if (nextHeight != null) {\n                popupHeight = nextHeight;\n              }\n\n              return popupHeight;\n            },\n          });\n\n          await flushMicrotasks();\n\n          expect(screen.getByTestId('nested-popup-1')).not.toBe(null);\n          await waitFor(() => {\n            expect(popupRoot.style.getPropertyValue('--popup-width')).toBe('auto');\n            expect(popupRoot.style.getPropertyValue('--popup-height')).toBe('auto');\n            expect(positioner.style.getPropertyValue('--positioner-width')).toBe('250px');\n            expect(positioner.style.getPropertyValue('--positioner-height')).toBe('220px');\n          });\n        } finally {\n          globalThis.BASE_UI_ANIMATIONS_DISABLED = previousAnimationsDisabled;\n        }\n      });\n\n      it('keeps inline mutation resize interruptible when content updates again mid-transition', async () => {\n        const previousAnimationsDisabled = globalThis.BASE_UI_ANIMATIONS_DISABLED;\n        globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n        try {\n          await render(\n            <TestInlineNestedNavigationMenuWithDynamicContent initialContentStage={1} />,\n          );\n          const trigger1 = screen.getByTestId('trigger-1');\n\n          fireEvent.click(trigger1);\n          await flushMicrotasks();\n\n          const popupRoot = screen.getByTestId('popup-root');\n          const positioner = screen.getByTestId('positioner');\n          const animations = mockAnimations(popupRoot);\n\n          const popupWidth = 250;\n          const popupHeightValues = [190, 260];\n          let popupHeight = 260;\n\n          Object.defineProperty(popupRoot, 'offsetWidth', {\n            configurable: true,\n            get: () => popupWidth,\n          });\n          Object.defineProperty(popupRoot, 'offsetHeight', {\n            configurable: true,\n            get: () => {\n              const nextHeight = popupHeightValues.shift();\n              if (nextHeight != null) {\n                popupHeight = nextHeight;\n              }\n\n              return popupHeight;\n            },\n          });\n\n          popupRoot.style.setProperty('--popup-width', '250px');\n          popupRoot.style.setProperty('--popup-height', '220px');\n          positioner.style.setProperty('--positioner-width', '250px');\n          positioner.style.setProperty('--positioner-height', '220px');\n\n          const setPropertySpy = vi.spyOn(positioner.style, 'setProperty');\n\n          animations.start();\n          fireEvent.click(screen.getByTestId('insert-content'));\n          await flushMicrotasks();\n\n          expect(screen.getByTestId('extra-content-2')).not.toBe(null);\n          expect(\n            setPropertySpy.mock.calls.some(\n              (call) => call[0] === '--positioner-height' && call[1] === '190px',\n            ),\n          ).toBe(true);\n\n          await act(async () => {\n            animations.finish();\n            await flushMicrotasks();\n          });\n\n          await waitFor(() => {\n            expect(positioner.style.getPropertyValue('--positioner-height')).toBe('260px');\n            expect(popupRoot.style.getPropertyValue('--popup-height')).toBe('auto');\n          });\n\n          setPropertySpy.mockRestore();\n        } finally {\n          globalThis.BASE_UI_ANIMATIONS_DISABLED = previousAnimationsDisabled;\n        }\n      });\n\n      it('updates popup sizing when the window is resized while the popup is open', async () => {\n        const restoreResizeObserver = mockResizeObserver();\n\n        try {\n          await render(<TestNavigationMenuWithKeepMountedContent />);\n\n          const popupRoot = screen.getByTestId('popup-root');\n          const positioner = screen.getByTestId('positioner');\n\n          let popupWidth = 675;\n          let popupHeight = 220;\n\n          Object.defineProperty(popupRoot, 'offsetWidth', {\n            configurable: true,\n            get: () => popupWidth,\n          });\n          Object.defineProperty(popupRoot, 'offsetHeight', {\n            configurable: true,\n            get: () => popupHeight,\n          });\n\n          popupRoot.style.setProperty('--popup-width', 'auto');\n          popupRoot.style.setProperty('--popup-height', 'auto');\n          positioner.style.setProperty('--positioner-width', '675px');\n          positioner.style.setProperty('--positioner-height', '220px');\n\n          popupWidth = 500;\n          popupHeight = 180;\n\n          fireEvent(window, new Event('resize'));\n          await flushMicrotasks();\n\n          await waitFor(() => {\n            expect(popupRoot.style.getPropertyValue('--popup-width')).toBe('auto');\n            expect(popupRoot.style.getPropertyValue('--popup-height')).toBe('auto');\n            expect(positioner.style.getPropertyValue('--positioner-width')).toBe('500px');\n            expect(positioner.style.getPropertyValue('--positioner-height')).toBe('180px');\n          });\n        } finally {\n          restoreResizeObserver();\n        }\n      });\n\n      it('updates popup sizing immediately when switching to a keepMounted trigger', async () => {\n        const restoreResizeObserver = mockResizeObserver();\n        const previousAnimationsDisabled = globalThis.BASE_UI_ANIMATIONS_DISABLED;\n        globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n        try {\n          await render(<TestNavigationMenuWithKeepMountedContent />);\n\n          const popupRoot = screen.getByTestId('popup-root');\n          const positioner = screen.getByTestId('positioner');\n          const animations = mockAnimations(popupRoot);\n\n          let popupWidth = 675;\n          let popupHeight = 220;\n\n          Object.defineProperty(popupRoot, 'offsetWidth', {\n            configurable: true,\n            get: () => popupWidth,\n          });\n          Object.defineProperty(popupRoot, 'offsetHeight', {\n            configurable: true,\n            get: () => popupHeight,\n          });\n\n          popupRoot.style.setProperty('--popup-width', 'auto');\n          popupRoot.style.setProperty('--popup-height', 'auto');\n          positioner.style.setProperty('--positioner-width', '675px');\n          positioner.style.setProperty('--positioner-height', '220px');\n\n          popupWidth = 500;\n          popupHeight = 180;\n          animations.start();\n          fireEvent.click(screen.getByTestId('trigger-learn'));\n          await flushMicrotasks();\n\n          expect(positioner.style.getPropertyValue('--positioner-width')).toBe('500px');\n          expect(positioner.style.getPropertyValue('--positioner-height')).toBe('180px');\n\n          await act(async () => {\n            animations.finish();\n            await flushMicrotasks();\n          });\n\n          await waitFor(() => {\n            expect(popupRoot.style.getPropertyValue('--popup-width')).toBe('auto');\n            expect(popupRoot.style.getPropertyValue('--popup-height')).toBe('auto');\n            expect(positioner.style.getPropertyValue('--positioner-width')).toBe('500px');\n            expect(positioner.style.getPropertyValue('--positioner-height')).toBe('180px');\n          });\n        } finally {\n          globalThis.BASE_UI_ANIMATIONS_DISABLED = previousAnimationsDisabled;\n          restoreResizeObserver();\n        }\n      });\n\n      it('ignores the initial open size reset once a trigger switch has started', async () => {\n        const restoreResizeObserver = mockResizeObserver();\n        const previousAnimationsDisabled = globalThis.BASE_UI_ANIMATIONS_DISABLED;\n        globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n        try {\n          function waitForAnimationFrame() {\n            return new Promise<void>((resolve) => {\n              requestAnimationFrame(() => {\n                resolve();\n              });\n            });\n          }\n\n          await render(<TestNavigationMenuWithKeepMountedContentClosed />);\n\n          const popupRoot = screen.getByTestId('popup-root');\n          const positioner = screen.getByTestId('positioner');\n          const animations = mockAnimations(popupRoot);\n\n          async function waitForSettledAnimations() {\n            await act(async () => {\n              await flushMicrotasks();\n              await waitForAnimationFrame();\n              await waitForAnimationFrame();\n            });\n          }\n\n          async function finishAnimation(animation: Parameters<typeof animations.finish>[0]) {\n            await act(async () => {\n              await animations.finish(animation);\n              await flushMicrotasks();\n              await waitForAnimationFrame();\n              await waitForAnimationFrame();\n            });\n          }\n\n          let popupWidth = 675;\n          let popupHeight = 220;\n\n          Object.defineProperty(popupRoot, 'offsetWidth', {\n            configurable: true,\n            get: () => popupWidth,\n          });\n          Object.defineProperty(popupRoot, 'offsetHeight', {\n            configurable: true,\n            get: () => popupHeight,\n          });\n\n          const openAnimation = animations.start();\n          fireEvent.click(screen.getByTestId('trigger-product'));\n          await waitForSettledAnimations();\n\n          popupWidth = 500;\n          popupHeight = 180;\n\n          const switchAnimation = animations.start();\n          fireEvent.click(screen.getByTestId('trigger-learn'));\n          await waitForSettledAnimations();\n\n          await waitFor(() => {\n            expect(positioner.style.getPropertyValue('--positioner-width')).toBe('500px');\n            expect(positioner.style.getPropertyValue('--positioner-height')).toBe('180px');\n          });\n\n          await finishAnimation(openAnimation);\n\n          expect(popupRoot.style.getPropertyValue('--popup-width')).toBe('500px');\n          expect(popupRoot.style.getPropertyValue('--popup-height')).toBe('180px');\n\n          await finishAnimation(switchAnimation);\n\n          await waitFor(() => {\n            expect(popupRoot.style.getPropertyValue('--popup-width')).toBe('auto');\n            expect(popupRoot.style.getPropertyValue('--popup-height')).toBe('auto');\n          });\n        } finally {\n          globalThis.BASE_UI_ANIMATIONS_DISABLED = previousAnimationsDisabled;\n          restoreResizeObserver();\n        }\n      });\n\n      it.skipIf(isJSDOM)('closes on the short exit path after switching content', async () => {\n        const animationsDisabled = globalThis.BASE_UI_ANIMATIONS_DISABLED;\n        globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n        try {\n          const onOpenChangeComplete = vi.fn();\n          const { user } = await render(\n            <TestNavigationMenuWithScopedPopupExitAnimation\n              onOpenChangeComplete={onOpenChangeComplete}\n            />,\n          );\n\n          await user.click(screen.getByTestId('trigger-product'));\n          await flushMicrotasks();\n\n          await user.click(screen.getByTestId('trigger-learn'));\n          await flushMicrotasks();\n\n          const popupRoot = screen.getByTestId('popup-root');\n\n          await waitFor(() => {\n            const hasRunningAnimations = popupRoot\n              .getAnimations()\n              .some((animation) => animation.playState !== 'finished');\n            expect(hasRunningAnimations).toBe(false);\n          });\n\n          const closeStart = performance.now();\n          fireEvent.keyDown(screen.getByTestId('trigger-learn'), { key: 'Escape' });\n          await flushMicrotasks();\n\n          await waitFor(() => {\n            expect(onOpenChangeComplete.mock.calls.length).toBe(1);\n            expect(onOpenChangeComplete.mock.calls[0][0]).toBe(false);\n          });\n\n          expect(performance.now() - closeStart).toBeLessThan(325);\n        } finally {\n          globalThis.BASE_UI_ANIMATIONS_DISABLED = animationsDisabled;\n        }\n      });\n\n      it('does not collapse popup size to zero on close if a measurement temporarily returns 0', async () => {\n        await render(<TestInlineNestedNavigationMenuWithDynamicContent />);\n        const trigger1 = screen.getByTestId('trigger-1');\n\n        fireEvent.click(trigger1);\n        await flushMicrotasks();\n\n        const popupRoot = screen.getByTestId('popup-root');\n        const positioner = screen.getByTestId('positioner');\n\n        popupRoot.style.setProperty('--popup-width', '250px');\n        popupRoot.style.setProperty('--popup-height', '120px');\n        positioner.style.setProperty('--positioner-width', '250px');\n        positioner.style.setProperty('--positioner-height', '120px');\n\n        Object.defineProperty(popupRoot, 'offsetWidth', {\n          configurable: true,\n          get: () => 0,\n        });\n        Object.defineProperty(popupRoot, 'offsetHeight', {\n          configurable: true,\n          get: () => 0,\n        });\n        Object.defineProperty(positioner, 'offsetWidth', {\n          configurable: true,\n          get: () => 0,\n        });\n        Object.defineProperty(positioner, 'offsetHeight', {\n          configurable: true,\n          get: () => 0,\n        });\n\n        fireEvent.blur(trigger1, { relatedTarget: document.body });\n        await flushMicrotasks();\n\n        expect(popupRoot.style.getPropertyValue('--popup-width')).toBe('250px');\n        expect(popupRoot.style.getPropertyValue('--popup-height')).toBe('120px');\n        expect(positioner.style.getPropertyValue('--positioner-width')).toBe('250px');\n        expect(positioner.style.getPropertyValue('--positioner-height')).toBe('120px');\n      });\n\n      it('tabs from the last link of the last nested panel to the next top-level trigger', async () => {\n        const { user } = await render(<TestInlineNestedNavigationMenuTabForwardBoundary />);\n        const trigger1 = screen.getByTestId('trigger-1');\n\n        await user.click(trigger1);\n        await flushMicrotasks();\n\n        const nestedLastLink = screen.getByTestId('nested-last-link');\n        await act(async () => nestedLastLink.focus());\n        expect(nestedLastLink).toHaveFocus();\n\n        await user.tab();\n\n        expect(screen.getByTestId('trigger-2')).toHaveFocus();\n        expect(screen.getByTestId('nested-popup-2')).not.toBe(null);\n        expect(screen.getByTestId('nested-trigger-2')).toHaveAttribute('aria-expanded', 'true');\n      });\n\n      it('tabs between nested triggers and links without opening inactive panels', async () => {\n        const { user } = await render(<TestInlineNestedNavigationMenuTabFlow />);\n        const triggerProduct = screen.getByTestId('trigger-product');\n\n        await user.click(triggerProduct);\n        await flushMicrotasks();\n\n        const nestedDevelopersTrigger = screen.getByTestId('nested-trigger-developers');\n        await act(async () => nestedDevelopersTrigger.focus());\n        expect(nestedDevelopersTrigger).toHaveFocus();\n\n        await user.tab();\n        expect(screen.getByTestId('nested-link-get-started')).toHaveFocus();\n\n        await user.tab({ shift: true });\n        expect(nestedDevelopersTrigger).toHaveFocus();\n\n        await user.tab();\n        expect(screen.getByTestId('nested-link-get-started')).toHaveFocus();\n\n        await user.tab();\n        expect(screen.getByTestId('nested-link-composition')).toHaveFocus();\n\n        await user.tab();\n        expect(screen.getByTestId('nested-trigger-design-systems')).toHaveFocus();\n        expect(screen.queryByTestId('nested-popup-design-systems')).toBe(null);\n\n        await user.tab();\n        expect(screen.getByText('Engineering Leads')).toHaveFocus();\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/navigation-menu/root/NavigationMenuRoot.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { isHTMLElement } from '@floating-ui/utils/dom';\nimport { useControlled } from '@base-ui/utils/useControlled';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { ownerDocument } from '@base-ui/utils/owner';\nimport {\n  FloatingNode,\n  FloatingTree,\n  useFloatingNodeId,\n  useFloatingParentNodeId,\n  type FloatingRootContext,\n} from '../../floating-ui-react';\nimport { activeElement, contains } from '../../floating-ui-react/utils';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport {\n  type NavigationMenuPopupAutoSizeResetState,\n  NavigationMenuRootContext,\n  NavigationMenuTreeContext,\n  useNavigationMenuRootContext,\n} from './NavigationMenuRootContext';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useOpenChangeComplete } from '../../utils/useOpenChangeComplete';\nimport { useTransitionStatus } from '../../utils/useTransitionStatus';\nimport { getCssDimensions } from '../../utils/getCssDimensions';\nimport { type BaseUIChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport { NavigationMenuPopupCssVars } from '../popup/NavigationMenuPopupCssVars';\nimport { NavigationMenuPositionerCssVars } from '../positioner/NavigationMenuPositionerCssVars';\n\nconst blockedReturnFocusReasons = new Set<string>([\n  REASONS.triggerHover,\n  REASONS.outsidePress,\n  REASONS.focusOut,\n]);\n\nfunction setSharedFixedSize(popupElement: HTMLElement, positionerElement: HTMLElement) {\n  const { width, height } = getCssDimensions(popupElement);\n\n  if (width === 0 || height === 0) {\n    return;\n  }\n\n  popupElement.style.setProperty(NavigationMenuPopupCssVars.popupWidth, `${width}px`);\n  popupElement.style.setProperty(NavigationMenuPopupCssVars.popupHeight, `${height}px`);\n  positionerElement.style.setProperty(\n    NavigationMenuPositionerCssVars.positionerWidth,\n    `${width}px`,\n  );\n  positionerElement.style.setProperty(\n    NavigationMenuPositionerCssVars.positionerHeight,\n    `${height}px`,\n  );\n}\n\n/**\n * Groups all parts of the navigation menu.\n * Renders a `<nav>` element at the root, or `<div>` element when nested.\n *\n * Documentation: [Base UI Navigation Menu](https://base-ui.com/react/components/navigation-menu)\n */\nexport const NavigationMenuRoot = React.forwardRef(function NavigationMenuRoot<Value = any>(\n  componentProps: NavigationMenuRoot.Props<Value>,\n  forwardedRef: React.ForwardedRef<HTMLElement>,\n) {\n  const {\n    defaultValue = null,\n    value: valueParam,\n    onValueChange,\n    actionsRef,\n    delay = 50,\n    closeDelay = 50,\n    orientation = 'horizontal',\n    onOpenChangeComplete,\n  } = componentProps;\n\n  const nested = useFloatingParentNodeId() != null;\n  const parentRootContext = useNavigationMenuRootContext(true);\n\n  const [value, setValueUnwrapped] = useControlled({\n    controlled: valueParam,\n    default: defaultValue,\n    name: 'NavigationMenu',\n    state: 'value',\n  });\n\n  // Derive open state from value being non-nullish\n  const open = value != null;\n\n  const closeReasonRef = React.useRef<NavigationMenuRoot.ChangeEventReason | undefined>(undefined);\n  const rootRef = React.useRef<HTMLDivElement | null>(null);\n\n  const [positionerElement, setPositionerElement] = React.useState<HTMLElement | null>(null);\n  const [popupElement, setPopupElement] = React.useState<HTMLElement | null>(null);\n  const [viewportElement, setViewportElement] = React.useState<HTMLElement | null>(null);\n  const [viewportTargetElement, setViewportTargetElement] = React.useState<HTMLElement | null>(\n    null,\n  );\n  const [activationDirection, setActivationDirection] =\n    React.useState<NavigationMenuRootContext['activationDirection']>(null);\n  const [floatingRootContext, setFloatingRootContext] = React.useState<\n    FloatingRootContext | undefined\n  >(undefined);\n  const [viewportInert, setViewportInert] = React.useState(false);\n\n  const prevTriggerElementRef = React.useRef<Element | null | undefined>(null);\n  const currentContentRef = React.useRef<HTMLDivElement | null>(null);\n  const beforeInsideRef = React.useRef<HTMLSpanElement | null>(null);\n  const afterInsideRef = React.useRef<HTMLSpanElement | null>(null);\n  const beforeOutsideRef = React.useRef<HTMLSpanElement | null>(null);\n  const afterOutsideRef = React.useRef<HTMLSpanElement | null>(null);\n  // Shared across triggers so a newly active trigger can cancel a stale\n  // popup auto-size reset scheduled by the previously active trigger.\n  const popupAutoSizeResetRef = React.useRef<NavigationMenuPopupAutoSizeResetState>({\n    abortController: null,\n    owner: null,\n  });\n\n  const { mounted, setMounted, transitionStatus } = useTransitionStatus(open);\n\n  React.useEffect(() => {\n    setViewportInert(false);\n  }, [value]);\n\n  const setValue = useStableCallback(\n    (\n      nextValue: NavigationMenuRoot.Value<Value>,\n      eventDetails: NavigationMenuRoot.ChangeEventDetails,\n    ) => {\n      if (!nextValue) {\n        closeReasonRef.current = eventDetails.reason;\n        setActivationDirection(null);\n        setFloatingRootContext(undefined);\n\n        if (positionerElement && popupElement) {\n          setSharedFixedSize(popupElement, positionerElement);\n        }\n      }\n\n      if (nextValue !== value) {\n        onValueChange?.(nextValue, eventDetails);\n      }\n\n      if (eventDetails.isCanceled) {\n        return;\n      }\n\n      setValueUnwrapped(nextValue);\n\n      if (nested && !nextValue && eventDetails.reason === REASONS.linkPress && parentRootContext) {\n        parentRootContext.setValue(null, eventDetails);\n      }\n    },\n  );\n\n  const handleUnmount = useStableCallback(() => {\n    const doc = ownerDocument(rootRef.current);\n    const activeEl = activeElement(doc);\n\n    const isReturnFocusBlocked = closeReasonRef.current\n      ? blockedReturnFocusReasons.has(closeReasonRef.current)\n      : false;\n\n    if (\n      !isReturnFocusBlocked &&\n      isHTMLElement(prevTriggerElementRef.current) &&\n      (activeEl === ownerDocument(popupElement).body || contains(popupElement, activeEl)) &&\n      popupElement\n    ) {\n      prevTriggerElementRef.current.focus({ preventScroll: true });\n      prevTriggerElementRef.current = undefined;\n    }\n\n    setMounted(false);\n    onOpenChangeComplete?.(false);\n    setActivationDirection(null);\n    setFloatingRootContext(undefined);\n\n    currentContentRef.current = null;\n    closeReasonRef.current = undefined;\n  });\n\n  useOpenChangeComplete({\n    enabled: !actionsRef,\n    open,\n    ref: { current: popupElement },\n    onComplete() {\n      if (!open) {\n        handleUnmount();\n      }\n    },\n  });\n\n  useOpenChangeComplete({\n    enabled: !actionsRef,\n    open,\n    ref: { current: viewportTargetElement },\n    onComplete() {\n      if (!open) {\n        handleUnmount();\n      }\n    },\n  });\n\n  const contextValue: NavigationMenuRootContext<Value> = React.useMemo(\n    () => ({\n      open,\n      value,\n      setValue,\n      mounted,\n      transitionStatus,\n      positionerElement,\n      setPositionerElement,\n      popupElement,\n      setPopupElement,\n      viewportElement,\n      setViewportElement,\n      viewportTargetElement,\n      setViewportTargetElement,\n      activationDirection,\n      setActivationDirection,\n      floatingRootContext,\n      setFloatingRootContext,\n      currentContentRef,\n      nested,\n      rootRef,\n      beforeInsideRef,\n      afterInsideRef,\n      beforeOutsideRef,\n      afterOutsideRef,\n      prevTriggerElementRef,\n      popupAutoSizeResetRef,\n      delay,\n      closeDelay,\n      orientation,\n      viewportInert,\n      setViewportInert,\n    }),\n    [\n      open,\n      value,\n      setValue,\n      mounted,\n      transitionStatus,\n      positionerElement,\n      popupElement,\n      viewportElement,\n      viewportTargetElement,\n      activationDirection,\n      floatingRootContext,\n      nested,\n      delay,\n      closeDelay,\n      orientation,\n      viewportInert,\n    ],\n  );\n\n  const jsx = (\n    <NavigationMenuRootContext.Provider value={contextValue}>\n      <TreeContext componentProps={componentProps} forwardedRef={forwardedRef}>\n        {componentProps.children}\n      </TreeContext>\n    </NavigationMenuRootContext.Provider>\n  );\n\n  if (!nested) {\n    // FloatingTree provides context to nested menus\n    return <FloatingTree>{jsx}</FloatingTree>;\n  }\n\n  return jsx;\n}) as {\n  <Value = any>(props: NavigationMenuRoot.Props<Value>): React.JSX.Element;\n};\n\nfunction TreeContext<Value>(props: {\n  componentProps: NavigationMenuRoot.Props<Value>;\n  forwardedRef: React.ForwardedRef<HTMLElement>;\n  children: React.ReactNode;\n}) {\n  const {\n    className,\n    render,\n    defaultValue,\n    value: valueParam,\n    onValueChange,\n    actionsRef,\n    delay,\n    closeDelay,\n    orientation,\n    onOpenChangeComplete,\n    ...elementProps\n  } = props.componentProps;\n\n  const nodeId = useFloatingNodeId();\n  const { rootRef, nested } = useNavigationMenuRootContext();\n\n  const { open } = useNavigationMenuRootContext();\n\n  const state: NavigationMenuRootState = {\n    open,\n    nested,\n  };\n\n  const element = useRenderElement(nested ? 'div' : 'nav', props.componentProps, {\n    state,\n    ref: [props.forwardedRef, rootRef],\n    props: elementProps,\n  });\n\n  return (\n    <NavigationMenuTreeContext.Provider value={nodeId}>\n      <FloatingNode id={nodeId}>{element}</FloatingNode>\n    </NavigationMenuTreeContext.Provider>\n  );\n}\n\nexport interface NavigationMenuRootState {\n  /**\n   * If `true`, the popup is open.\n   */\n  open: boolean;\n  /**\n   * Whether the navigation menu is nested.\n   */\n  nested: boolean;\n}\n\nexport interface NavigationMenuRootProps<Value = any> extends BaseUIComponentProps<\n  'nav',\n  NavigationMenuRootState\n> {\n  /**\n   * A ref to imperative actions.\n   */\n  actionsRef?: React.RefObject<NavigationMenuRoot.Actions | null> | undefined;\n  /**\n   * Event handler called after any animations complete when the navigation menu is closed.\n   */\n  onOpenChangeComplete?: ((open: boolean) => void) | undefined;\n  /**\n   * The controlled value of the navigation menu item that should be currently open.\n   * When non-nullish, the menu will be open. When nullish, the menu will be closed.\n   *\n   * To render an uncontrolled navigation menu, use the `defaultValue` prop instead.\n   * @default null\n   */\n  value?: Value | null | undefined;\n  /**\n   * The uncontrolled value of the item that should be initially selected.\n   *\n   * To render a controlled navigation menu, use the `value` prop instead.\n   * @default null\n   */\n  defaultValue?: Value | null | undefined;\n  /**\n   * Callback fired when the value changes.\n   */\n  onValueChange?:\n    | ((value: Value | null, eventDetails: NavigationMenuRoot.ChangeEventDetails) => void)\n    | undefined;\n  /**\n   * How long to wait before opening the navigation popup. Specified in milliseconds.\n   * @default 50\n   */\n  delay?: number | undefined;\n  /**\n   * How long to wait before closing the navigation popup. Specified in milliseconds.\n   * @default 50\n   */\n  closeDelay?: number | undefined;\n  /**\n   * The orientation of the navigation menu.\n   * @default 'horizontal'\n   */\n  orientation?: 'horizontal' | 'vertical' | undefined;\n}\n\nexport interface NavigationMenuRootActions {\n  unmount: () => void;\n}\n\nexport type NavigationMenuRootChangeEventReason =\n  | typeof REASONS.triggerPress\n  | typeof REASONS.triggerHover\n  | typeof REASONS.outsidePress\n  | typeof REASONS.listNavigation\n  | typeof REASONS.focusOut\n  | typeof REASONS.escapeKey\n  | typeof REASONS.linkPress\n  | typeof REASONS.none;\n\nexport type NavigationMenuRootChangeEventDetails =\n  BaseUIChangeEventDetails<NavigationMenuRoot.ChangeEventReason>;\n\nexport namespace NavigationMenuRoot {\n  export type State = NavigationMenuRootState;\n  export type Props<TValue = any> = NavigationMenuRootProps<TValue>;\n  export type Value<TValue = any> = TValue | null;\n  export type Actions = NavigationMenuRootActions;\n  export type ChangeEventReason = NavigationMenuRootChangeEventReason;\n  export type ChangeEventDetails = NavigationMenuRootChangeEventDetails;\n}\n"
  },
  {
    "path": "packages/react/src/navigation-menu/root/NavigationMenuRoot.webkit.test.tsx",
    "content": "import { vi, expect } from 'vitest';\nimport * as React from 'react';\nimport { fireEvent, flushMicrotasks, screen } from '@mui/internal-test-utils';\nimport { NavigationMenu } from '@base-ui/react/navigation-menu';\nimport { createRenderer } from '#test-utils';\nimport { OPEN_DELAY } from '../utils/constants';\n\nvi.mock('@base-ui/utils/detectBrowser', async () => {\n  const actual = await vi.importActual<typeof import('@base-ui/utils/detectBrowser')>(\n    '@base-ui/utils/detectBrowser',\n  );\n\n  return {\n    ...actual,\n    isWebKit: true,\n  };\n});\n\nfunction TestNavigationMenu() {\n  return (\n    <NavigationMenu.Root>\n      <NavigationMenu.List data-testid=\"top-level-list\">\n        <NavigationMenu.Item value=\"item-1\">\n          <NavigationMenu.Trigger data-testid=\"trigger-1\">Item 1</NavigationMenu.Trigger>\n          <NavigationMenu.Content data-testid=\"popup-1\">\n            <NavigationMenu.Link href=\"#link-1\">Link 1</NavigationMenu.Link>\n          </NavigationMenu.Content>\n        </NavigationMenu.Item>\n      </NavigationMenu.List>\n\n      <NavigationMenu.Portal>\n        <NavigationMenu.Positioner>\n          <NavigationMenu.Popup>\n            <NavigationMenu.Viewport />\n          </NavigationMenu.Popup>\n        </NavigationMenu.Positioner>\n      </NavigationMenu.Portal>\n    </NavigationMenu.Root>\n  );\n}\n\nfunction TestInlineNestedNavigationMenu() {\n  return (\n    <NavigationMenu.Root>\n      <NavigationMenu.List>\n        <NavigationMenu.Item value=\"item-1\">\n          <NavigationMenu.Trigger data-testid=\"trigger-1\">Item 1</NavigationMenu.Trigger>\n          <NavigationMenu.Content data-testid=\"popup-1\">\n            <NavigationMenu.Root defaultValue=\"nested-item-1\">\n              <NavigationMenu.List data-testid=\"inline-nested-list\">\n                <NavigationMenu.Item value=\"nested-item-1\">\n                  <NavigationMenu.Trigger data-testid=\"nested-trigger-1\">\n                    Nested Item 1\n                  </NavigationMenu.Trigger>\n                  <NavigationMenu.Content data-testid=\"nested-popup-1\">\n                    <NavigationMenu.Link href=\"#nested-link-1\">Nested Link 1</NavigationMenu.Link>\n                  </NavigationMenu.Content>\n                </NavigationMenu.Item>\n              </NavigationMenu.List>\n\n              <NavigationMenu.Viewport data-testid=\"inline-nested-viewport\" />\n            </NavigationMenu.Root>\n          </NavigationMenu.Content>\n        </NavigationMenu.Item>\n      </NavigationMenu.List>\n\n      <NavigationMenu.Portal>\n        <NavigationMenu.Positioner>\n          <NavigationMenu.Popup>\n            <NavigationMenu.Viewport />\n          </NavigationMenu.Popup>\n        </NavigationMenu.Positioner>\n      </NavigationMenu.Portal>\n    </NavigationMenu.Root>\n  );\n}\n\nfunction mockBoundingClientRect(\n  element: Element,\n  rect: { x: number; y: number; width: number; height: number },\n) {\n  const domRect = {\n    x: rect.x,\n    y: rect.y,\n    width: rect.width,\n    height: rect.height,\n    top: rect.y,\n    left: rect.x,\n    right: rect.x + rect.width,\n    bottom: rect.y + rect.height,\n    toJSON: () => ({}),\n  } satisfies DOMRect;\n\n  vi.spyOn(element, 'getBoundingClientRect').mockReturnValue(domRect);\n}\n\ndescribe('<NavigationMenu.Root /> (Safari)', () => {\n  const { render, clock } = createRenderer();\n\n  clock.withFakeTimers();\n\n  it('does not mutate top-level pointer events for hover-open menus', async () => {\n    await render(<TestNavigationMenu />);\n    const trigger = screen.getByTestId('trigger-1');\n\n    fireEvent.mouseEnter(trigger);\n    fireEvent.mouseMove(trigger);\n    clock.tick(OPEN_DELAY);\n    await flushMicrotasks();\n\n    const topLevelList = screen.getByTestId('top-level-list');\n\n    expect(screen.queryByTestId('popup-1')).not.toBe(null);\n    expect(topLevelList.style.pointerEvents).toBe('');\n    expect(document.body.style.pointerEvents).toBe('');\n  });\n\n  it('keeps nested safePolygon pointer events scoped on WebKit', async () => {\n    await render(<TestInlineNestedNavigationMenu />);\n    const trigger1 = screen.getByTestId('trigger-1');\n\n    fireEvent.mouseEnter(trigger1);\n    fireEvent.mouseMove(trigger1);\n    clock.tick(OPEN_DELAY);\n    await flushMicrotasks();\n\n    const nestedList = screen.getByTestId('inline-nested-list');\n    const nestedTrigger1 = screen.getByTestId('nested-trigger-1');\n    const nestedViewport = screen.getByTestId('inline-nested-viewport');\n\n    mockBoundingClientRect(nestedTrigger1, { x: 0, y: 40, width: 100, height: 40 });\n    mockBoundingClientRect(nestedViewport, { x: 200, y: 0, width: 300, height: 300 });\n    fireEvent.mouseEnter(nestedTrigger1);\n\n    expect(nestedList.style.pointerEvents).toBe('none');\n    expect(document.body.style.pointerEvents).toBe('');\n  });\n});\n"
  },
  {
    "path": "packages/react/src/navigation-menu/root/NavigationMenuRootContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport type { FloatingRootContext } from '../../floating-ui-react';\nimport type { TransitionStatus } from '../../utils/useTransitionStatus';\nimport type { NavigationMenuRoot } from './NavigationMenuRoot';\n\nexport type NavigationMenuPopupAutoSizeResetState = {\n  abortController: AbortController | null;\n  owner: any;\n};\n\nexport interface NavigationMenuRootContext<Value = any> {\n  open: boolean;\n  value: NavigationMenuRoot.Value<Value>;\n  setValue: (\n    value: NavigationMenuRoot.Value<Value>,\n    eventDetails: NavigationMenuRoot.ChangeEventDetails,\n  ) => void;\n  transitionStatus: TransitionStatus;\n  mounted: boolean;\n  popupElement: HTMLElement | null;\n  setPopupElement: React.Dispatch<React.SetStateAction<HTMLElement | null>>;\n  positionerElement: HTMLElement | null;\n  setPositionerElement: React.Dispatch<React.SetStateAction<HTMLElement | null>>;\n  viewportElement: HTMLElement | null;\n  setViewportElement: React.Dispatch<React.SetStateAction<HTMLElement | null>>;\n  viewportTargetElement: HTMLElement | null;\n  setViewportTargetElement: React.Dispatch<React.SetStateAction<HTMLElement | null>>;\n  activationDirection: 'left' | 'right' | 'up' | 'down' | null;\n  setActivationDirection: React.Dispatch<\n    React.SetStateAction<'left' | 'right' | 'up' | 'down' | null>\n  >;\n  floatingRootContext: FloatingRootContext | undefined;\n  setFloatingRootContext: React.Dispatch<React.SetStateAction<FloatingRootContext | undefined>>;\n  currentContentRef: React.RefObject<HTMLDivElement | null>;\n  nested: boolean;\n  rootRef: React.RefObject<HTMLDivElement | null>;\n  beforeInsideRef: React.RefObject<HTMLSpanElement | null>;\n  afterInsideRef: React.RefObject<HTMLSpanElement | null>;\n  beforeOutsideRef: React.RefObject<HTMLSpanElement | null>;\n  afterOutsideRef: React.RefObject<HTMLSpanElement | null>;\n  prevTriggerElementRef: React.RefObject<Element | null | undefined>;\n  popupAutoSizeResetRef: React.MutableRefObject<NavigationMenuPopupAutoSizeResetState>;\n  delay: number;\n  closeDelay: number;\n  orientation: 'horizontal' | 'vertical';\n  viewportInert: boolean;\n  setViewportInert: React.Dispatch<React.SetStateAction<boolean>>;\n}\n\nexport const NavigationMenuRootContext = React.createContext<\n  NavigationMenuRootContext<any> | undefined\n>(undefined);\n\nif (process.env.NODE_ENV !== 'production') {\n  NavigationMenuRootContext.displayName = 'NavigationMenuRootContext';\n}\n\nfunction useNavigationMenuRootContext<Value = any>(\n  optional?: false,\n): NavigationMenuRootContext<Value>;\nfunction useNavigationMenuRootContext<Value = any>(\n  optional: true,\n): NavigationMenuRootContext<Value> | undefined;\nfunction useNavigationMenuRootContext<Value = any>(optional?: boolean) {\n  const context = React.useContext<NavigationMenuRootContext<Value> | undefined>(\n    NavigationMenuRootContext,\n  );\n  if (context === undefined && !optional) {\n    throw new Error(\n      'Base UI: NavigationMenuRootContext is missing. Navigation Menu parts must be placed within <NavigationMenu.Root>.',\n    );\n  }\n  return context;\n}\n\nexport const NavigationMenuTreeContext = React.createContext<string | undefined>(undefined);\n\nfunction useNavigationMenuTreeContext() {\n  return React.useContext(NavigationMenuTreeContext);\n}\n\nexport { useNavigationMenuRootContext, useNavigationMenuTreeContext };\n"
  },
  {
    "path": "packages/react/src/navigation-menu/trigger/NavigationMenuTrigger.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { NavigationMenu } from '@base-ui/react/navigation-menu';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\nimport { screen, flushMicrotasks, waitFor, act } from '@mui/internal-test-utils';\nimport userEvent from '@testing-library/user-event';\n\ndescribe('<NavigationMenu.Trigger />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<NavigationMenu.Trigger />, () => ({\n    refInstanceof: window.HTMLButtonElement,\n    testComponentPropWith: 'button',\n    button: true,\n    render(node) {\n      return render(\n        <NavigationMenu.Root>\n          <NavigationMenu.List>\n            <NavigationMenu.Item>{node}</NavigationMenu.Item>\n          </NavigationMenu.List>\n        </NavigationMenu.Root>,\n      );\n    },\n  }));\n\n  it.skipIf(isJSDOM)('handles focus and positioner height', async () => {\n    await render(\n      <NavigationMenu.Root>\n        <NavigationMenu.List>\n          <NavigationMenu.Item>\n            <NavigationMenu.Trigger>Overview</NavigationMenu.Trigger>\n            <NavigationMenu.Content>\n              <NavigationMenu.Link href=\"#\">Quick Start</NavigationMenu.Link>\n            </NavigationMenu.Content>\n          </NavigationMenu.Item>\n          <NavigationMenu.Item>\n            <NavigationMenu.Trigger>Handbook</NavigationMenu.Trigger>\n            <NavigationMenu.Content>\n              <NavigationMenu.Link href=\"#\">Styling Base UI components</NavigationMenu.Link>\n            </NavigationMenu.Content>\n            <NavigationMenu.Content>\n              <NavigationMenu.Link href=\"#\">Second Link</NavigationMenu.Link>\n            </NavigationMenu.Content>\n          </NavigationMenu.Item>\n        </NavigationMenu.List>\n        <NavigationMenu.Portal>\n          <NavigationMenu.Positioner data-testid=\"positioner\">\n            <NavigationMenu.Popup>\n              <NavigationMenu.Viewport />\n            </NavigationMenu.Popup>\n          </NavigationMenu.Positioner>\n        </NavigationMenu.Portal>\n      </NavigationMenu.Root>,\n    );\n\n    const overviewButton = screen.getByRole('button', { name: 'Overview' });\n    await act(async () => {\n      overviewButton.focus();\n    });\n\n    await userEvent.keyboard('{ArrowDown}');\n    await flushMicrotasks();\n\n    const positioner = screen.getByTestId('positioner');\n    await waitFor(() => {\n      expect(\n        Math.abs(\n          parseInt(getComputedStyle(positioner).getPropertyValue('--positioner-height'), 10) - 18,\n        ),\n      ).toBeLessThanOrEqual(1);\n    });\n\n    const overviewLink = screen.getByRole('link', { name: 'Quick Start' });\n    await waitFor(() => {\n      expect(overviewLink).toHaveFocus();\n    });\n\n    await userEvent.tab({ shift: true });\n\n    await waitFor(() => {\n      expect(overviewButton).toHaveFocus();\n    });\n\n    await userEvent.keyboard('{ArrowRight}');\n    const handbookButton = screen.getByRole('button', { name: 'Handbook' });\n    await waitFor(() => {\n      expect(handbookButton).toHaveFocus();\n    });\n\n    await userEvent.keyboard('{ArrowDown}');\n    await flushMicrotasks();\n\n    await waitFor(() => {\n      expect(\n        Math.abs(\n          parseInt(getComputedStyle(positioner).getPropertyValue('--positioner-height'), 10) - 36,\n        ),\n      ).toBeLessThanOrEqual(1);\n    });\n\n    const handbookLink = screen.getByRole('link', { name: 'Styling Base UI components' });\n    await waitFor(() => {\n      expect(handbookLink).toHaveFocus();\n    });\n\n    await userEvent.tab({ shift: true });\n    await waitFor(() => {\n      expect(handbookButton).toHaveFocus();\n    });\n\n    await userEvent.keyboard('{ArrowLeft}');\n    await waitFor(() => {\n      expect(overviewButton).toHaveFocus();\n    });\n\n    await userEvent.keyboard('{ArrowDown}');\n    await flushMicrotasks();\n    await waitFor(() => {\n      expect(\n        Math.abs(\n          parseInt(getComputedStyle(positioner).getPropertyValue('--positioner-height'), 10) - 18,\n        ),\n      ).toBeLessThanOrEqual(1);\n    });\n  });\n\n  it.skipIf(isJSDOM)('handles positioner width correctly', async () => {\n    await render(\n      <NavigationMenu.Root>\n        <NavigationMenu.List>\n          <NavigationMenu.Item>\n            <NavigationMenu.Trigger>noContent</NavigationMenu.Trigger>\n          </NavigationMenu.Item>\n          <NavigationMenu.Item>\n            <NavigationMenu.Trigger>withContent</NavigationMenu.Trigger>\n            <NavigationMenu.Content>\n              <NavigationMenu.Link href=\"#\">Styling Base UI components</NavigationMenu.Link>\n            </NavigationMenu.Content>\n          </NavigationMenu.Item>\n        </NavigationMenu.List>\n        <NavigationMenu.Portal>\n          <NavigationMenu.Positioner data-testid=\"positioner\">\n            <NavigationMenu.Popup>\n              <NavigationMenu.Viewport />\n            </NavigationMenu.Popup>\n          </NavigationMenu.Positioner>\n        </NavigationMenu.Portal>\n      </NavigationMenu.Root>,\n    );\n\n    const noContentButton = screen.getByRole('button', { name: 'noContent' });\n    const withContentButton = screen.getByRole('button', { name: 'withContent' });\n\n    await userEvent.pointer([\n      { target: withContentButton },\n      { target: noContentButton, releasePrevious: true },\n      { target: withContentButton, releasePrevious: true },\n    ]);\n\n    await waitFor(() => {\n      const handbookLink = screen.getByRole('link', { name: 'Styling Base UI components' });\n\n      expect(handbookLink).toBeVisible();\n    });\n\n    const positioner = await screen.findByTestId('positioner');\n\n    await waitFor(() => {\n      expect(\n        Math.abs(\n          parseInt(getComputedStyle(positioner).getPropertyValue('--positioner-width'), 10) - 183,\n        ),\n      ).toBeLessThanOrEqual(1);\n    });\n  });\n\n  it.skipIf(isJSDOM)('repositions the positioner when switching triggers via hover', async () => {\n    const user = userEvent.setup({ pointerEventsCheck: 0 });\n\n    await render(\n      <NavigationMenu.Root>\n        <NavigationMenu.List style={{ display: 'flex' }}>\n          <NavigationMenu.Item>\n            <NavigationMenu.Trigger>Overview</NavigationMenu.Trigger>\n            <NavigationMenu.Content>\n              <NavigationMenu.Link href=\"#\">Overview Link</NavigationMenu.Link>\n            </NavigationMenu.Content>\n          </NavigationMenu.Item>\n          <NavigationMenu.Item>\n            <NavigationMenu.Trigger>Handbook</NavigationMenu.Trigger>\n            <NavigationMenu.Content>\n              <NavigationMenu.Link href=\"#\">Handbook Link</NavigationMenu.Link>\n            </NavigationMenu.Content>\n          </NavigationMenu.Item>\n        </NavigationMenu.List>\n        <NavigationMenu.Portal>\n          <NavigationMenu.Positioner data-testid=\"positioner\">\n            <NavigationMenu.Popup>\n              <NavigationMenu.Viewport />\n            </NavigationMenu.Popup>\n          </NavigationMenu.Positioner>\n        </NavigationMenu.Portal>\n      </NavigationMenu.Root>,\n    );\n\n    const overviewButton = screen.getByRole('button', { name: 'Overview' });\n    const handbookButton = screen.getByRole('button', { name: 'Handbook' });\n\n    await user.pointer([{ target: overviewButton }]);\n    await waitFor(() => {\n      expect(screen.getByRole('link', { name: 'Overview Link' })).toBeVisible();\n    });\n\n    const positioner = screen.getByTestId('positioner');\n    const firstLeft = positioner.getBoundingClientRect().left;\n\n    await user.pointer([{ target: handbookButton, releasePrevious: true }]);\n    await waitFor(() => {\n      expect(screen.getByRole('link', { name: 'Handbook Link' })).toBeVisible();\n    });\n\n    await waitFor(() => {\n      const secondLeft = positioner.getBoundingClientRect().left;\n      expect(Math.abs(secondLeft - firstLeft)).toBeGreaterThan(20);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/navigation-menu/trigger/NavigationMenuTrigger.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport { isHTMLElement } from '@floating-ui/utils/dom';\nimport { isTabbable } from 'tabbable';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { ownerWindow } from '@base-ui/utils/owner';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useTimeout } from '@base-ui/utils/useTimeout';\nimport { useAnimationFrame } from '@base-ui/utils/useAnimationFrame';\nimport { useValueAsRef } from '@base-ui/utils/useValueAsRef';\nimport { isWebKit } from '@base-ui/utils/detectBrowser';\nimport {\n  safePolygon,\n  useClick,\n  useFloatingRootContext,\n  useFloatingTree,\n  useHoverReferenceInteraction,\n  useInteractions,\n} from '../../floating-ui-react';\nimport {\n  applySafePolygonPointerEventsMutation,\n  clearSafePolygonPointerEventsMutation,\n  useHoverInteractionSharedState,\n} from '../../floating-ui-react/hooks/useHoverInteractionSharedState';\nimport {\n  contains,\n  getTabbableAfterElement,\n  getNextTabbable,\n  getPreviousTabbable,\n  isOutsideEvent,\n  stopEvent,\n} from '../../floating-ui-react/utils';\nimport type { HandleCloseContextBase } from '../../floating-ui-react/hooks/useHoverShared';\nimport type { BaseUIComponentProps, NativeButtonProps, HTMLProps } from '../../utils/types';\nimport { useNavigationMenuItemContext } from '../item/NavigationMenuItemContext';\nimport {\n  useNavigationMenuRootContext,\n  useNavigationMenuTreeContext,\n} from '../root/NavigationMenuRootContext';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport { EMPTY_ARRAY, ownerVisuallyHidden, PATIENT_CLICK_THRESHOLD } from '../../utils/constants';\nimport { FocusGuard } from '../../utils/FocusGuard';\nimport { pressableTriggerOpenStateMapping } from '../../utils/popupStateMapping';\nimport { TransitionStatusDataAttributes } from '../../utils/stateAttributesMapping';\nimport { isOutsideMenuEvent } from '../utils/isOutsideMenuEvent';\nimport { CompositeItem } from '../../composite/item/CompositeItem';\nimport { useButton } from '../../use-button';\nimport { useAnimationsFinished } from '../../utils/useAnimationsFinished';\nimport { getCssDimensions } from '../../utils/getCssDimensions';\nimport { NavigationMenuRoot } from '../root/NavigationMenuRoot';\nimport { NAVIGATION_MENU_TRIGGER_IDENTIFIER } from '../utils/constants';\nimport { useNavigationMenuDismissContext } from '../list/NavigationMenuDismissContext';\nimport { NavigationMenuPopupCssVars } from '../popup/NavigationMenuPopupCssVars';\nimport { NavigationMenuPositionerCssVars } from '../positioner/NavigationMenuPositionerCssVars';\n\nconst DEFAULT_SIZE = { width: 0, height: 0 };\n\n/**\n * Opens the navigation menu popup when hovered or clicked, revealing the\n * associated content.\n * Renders a `<button>` element.\n *\n * Documentation: [Base UI Navigation Menu](https://base-ui.com/react/components/navigation-menu)\n */\nexport const NavigationMenuTrigger = React.forwardRef(function NavigationMenuTrigger(\n  componentProps: NavigationMenuTrigger.Props,\n  forwardedRef: React.ForwardedRef<HTMLButtonElement>,\n) {\n  const { className, render, nativeButton = true, disabled, ...elementProps } = componentProps;\n\n  const {\n    value,\n    setValue,\n    mounted,\n    open,\n    positionerElement,\n    setActivationDirection,\n    setFloatingRootContext,\n    popupElement,\n    viewportElement,\n    transitionStatus,\n    rootRef,\n    beforeOutsideRef,\n    afterOutsideRef,\n    afterInsideRef,\n    beforeInsideRef,\n    prevTriggerElementRef,\n    popupAutoSizeResetRef,\n    currentContentRef,\n    delay,\n    closeDelay,\n    orientation,\n    setViewportInert,\n    nested,\n  } = useNavigationMenuRootContext();\n  const { value: itemValue } = useNavigationMenuItemContext();\n  const nodeId = useNavigationMenuTreeContext();\n  const tree = useFloatingTree();\n  const dismissProps = useNavigationMenuDismissContext();\n\n  const stickIfOpenTimeout = useTimeout();\n  const focusFrame = useAnimationFrame();\n  const mutationFrame = useAnimationFrame();\n  const resizeFrame = useAnimationFrame();\n  const sizeFrame = useAnimationFrame();\n\n  const [triggerElement, setTriggerElement] = React.useState<HTMLElement | null>(null);\n  const [stickIfOpen, setStickIfOpen] = React.useState(true);\n  const [pointerType, setPointerType] = React.useState<'mouse' | 'touch' | 'pen' | ''>('');\n\n  const triggerElementRef = React.useRef<HTMLElement | null>(null);\n  const allowFocusRef = React.useRef(false);\n  const prevSizeRef = React.useRef(DEFAULT_SIZE);\n  const skipAutoSizeSyncRef = React.useRef(false);\n\n  const isActiveItem = open && value === itemValue;\n  const isActiveItemRef = useValueAsRef(isActiveItem);\n  const interactionsEnabled = positionerElement ? true : !value;\n  const hoverFloatingElement = positionerElement || viewportElement;\n  const hoverInteractionsEnabled = hoverFloatingElement ? true : !value;\n\n  const runOnceAnimationsFinish = useAnimationsFinished(popupElement, false, false);\n\n  const handleTriggerElement = React.useCallback((element: HTMLElement | null) => {\n    triggerElementRef.current = element;\n    setTriggerElement(element);\n  }, []);\n\n  const cancelAutoSizeReset = useStableCallback((force = false) => {\n    if (!force && popupAutoSizeResetRef.current.owner !== itemValue) {\n      return;\n    }\n\n    popupAutoSizeResetRef.current.abortController?.abort();\n    popupAutoSizeResetRef.current.abortController = null;\n    popupAutoSizeResetRef.current.owner = null;\n  });\n\n  React.useEffect(cancelAutoSizeReset, [isActiveItem, cancelAutoSizeReset]);\n\n  function setAutoSizes() {\n    if (!popupElement) {\n      return;\n    }\n\n    popupElement.style.setProperty(NavigationMenuPopupCssVars.popupWidth, 'auto');\n    popupElement.style.setProperty(NavigationMenuPopupCssVars.popupHeight, 'auto');\n  }\n\n  function clearFixedSizes() {\n    if (!popupElement || !positionerElement) {\n      return;\n    }\n\n    popupElement.style.removeProperty(NavigationMenuPopupCssVars.popupWidth);\n    popupElement.style.removeProperty(NavigationMenuPopupCssVars.popupHeight);\n    positionerElement.style.removeProperty(NavigationMenuPositionerCssVars.positionerWidth);\n    positionerElement.style.removeProperty(NavigationMenuPositionerCssVars.positionerHeight);\n  }\n\n  function setSharedFixedSizes(width: number, height: number) {\n    if (!popupElement || !positionerElement) {\n      return;\n    }\n\n    popupElement.style.setProperty(NavigationMenuPopupCssVars.popupWidth, `${width}px`);\n    popupElement.style.setProperty(NavigationMenuPopupCssVars.popupHeight, `${height}px`);\n    positionerElement.style.setProperty(\n      NavigationMenuPositionerCssVars.positionerWidth,\n      `${width}px`,\n    );\n    positionerElement.style.setProperty(\n      NavigationMenuPositionerCssVars.positionerHeight,\n      `${height}px`,\n    );\n  }\n\n  function scheduleAutoSizeReset() {\n    cancelAutoSizeReset(true);\n\n    const abortController = new AbortController();\n    popupAutoSizeResetRef.current.abortController = abortController;\n    popupAutoSizeResetRef.current.owner = itemValue;\n\n    runOnceAnimationsFinish(() => {\n      if (\n        popupAutoSizeResetRef.current.abortController !== abortController ||\n        popupAutoSizeResetRef.current.owner !== itemValue\n      ) {\n        return;\n      }\n\n      popupAutoSizeResetRef.current.abortController = null;\n      popupAutoSizeResetRef.current.owner = null;\n      setAutoSizes();\n    }, abortController.signal);\n  }\n\n  const handleValueChange = useStableCallback(\n    (\n      currentWidth: number,\n      currentHeight: number,\n      options: {\n        syncPositioner?: boolean | undefined;\n      } = {},\n    ) => {\n      if (!popupElement || !positionerElement) {\n        return;\n      }\n\n      cancelAutoSizeReset(true);\n      const { syncPositioner = false } = options;\n\n      clearFixedSizes();\n\n      const { width, height } = getCssDimensions(popupElement);\n      const measuredWidth = width || prevSizeRef.current.width;\n      const measuredHeight = height || prevSizeRef.current.height;\n\n      if (currentHeight === 0 || currentWidth === 0) {\n        currentWidth = measuredWidth;\n        currentHeight = measuredHeight;\n      }\n\n      popupElement.style.setProperty(NavigationMenuPopupCssVars.popupWidth, `${currentWidth}px`);\n      popupElement.style.setProperty(NavigationMenuPopupCssVars.popupHeight, `${currentHeight}px`);\n      positionerElement.style.setProperty(\n        NavigationMenuPositionerCssVars.positionerWidth,\n        `${syncPositioner ? currentWidth : measuredWidth}px`,\n      );\n      positionerElement.style.setProperty(\n        NavigationMenuPositionerCssVars.positionerHeight,\n        `${syncPositioner ? currentHeight : measuredHeight}px`,\n      );\n\n      sizeFrame.request(() => {\n        popupElement.style.setProperty(NavigationMenuPopupCssVars.popupWidth, `${measuredWidth}px`);\n        popupElement.style.setProperty(\n          NavigationMenuPopupCssVars.popupHeight,\n          `${measuredHeight}px`,\n        );\n\n        if (syncPositioner) {\n          positionerElement.style.setProperty(\n            NavigationMenuPositionerCssVars.positionerWidth,\n            `${measuredWidth}px`,\n          );\n          positionerElement.style.setProperty(\n            NavigationMenuPositionerCssVars.positionerHeight,\n            `${measuredHeight}px`,\n          );\n        }\n\n        scheduleAutoSizeReset();\n      });\n    },\n  );\n\n  const handleInterruptedMutationResize = useStableCallback(\n    (currentWidth: number, currentHeight: number) => {\n      if (!popupElement || !positionerElement) {\n        return;\n      }\n\n      sizeFrame.cancel();\n      mutationFrame.cancel();\n      cancelAutoSizeReset(true);\n\n      if (currentWidth === 0 || currentHeight === 0) {\n        return;\n      }\n\n      setSharedFixedSizes(currentWidth, currentHeight);\n\n      mutationFrame.request(() => {\n        mutationFrame.request(() => {\n          clearFixedSizes();\n\n          const { width, height } = getCssDimensions(popupElement);\n          const measuredWidth = width || currentWidth || prevSizeRef.current.width;\n          const measuredHeight = height || currentHeight || prevSizeRef.current.height;\n\n          setSharedFixedSizes(currentWidth, currentHeight);\n\n          sizeFrame.request(() => {\n            setSharedFixedSizes(measuredWidth, measuredHeight);\n            scheduleAutoSizeReset();\n          });\n        });\n      });\n    },\n  );\n\n  const syncCurrentSize = useStableCallback(() => {\n    if (!popupElement || !positionerElement) {\n      return;\n    }\n\n    sizeFrame.cancel();\n    cancelAutoSizeReset(true);\n\n    clearFixedSizes();\n\n    const { width, height } = getCssDimensions(popupElement);\n\n    if (width === 0 || height === 0) {\n      return;\n    }\n\n    prevSizeRef.current = { width, height };\n    setAutoSizes();\n    positionerElement.style.setProperty(\n      NavigationMenuPositionerCssVars.positionerWidth,\n      `${width}px`,\n    );\n    positionerElement.style.setProperty(\n      NavigationMenuPositionerCssVars.positionerHeight,\n      `${height}px`,\n    );\n  });\n\n  const getMutationBaseline = useStableCallback(() => {\n    if (!popupElement) {\n      return { size: prevSizeRef.current, syncPositioner: false };\n    }\n\n    const popupWidth = popupElement.style.getPropertyValue(NavigationMenuPopupCssVars.popupWidth);\n    const popupHeight = popupElement.style.getPropertyValue(NavigationMenuPopupCssVars.popupHeight);\n    const isResizing =\n      popupWidth !== '' && popupWidth !== 'auto' && popupHeight !== '' && popupHeight !== 'auto';\n\n    if (!isResizing) {\n      return { size: prevSizeRef.current, syncPositioner: false };\n    }\n\n    return {\n      size: {\n        width: popupElement.offsetWidth || prevSizeRef.current.width,\n        height: popupElement.offsetHeight || prevSizeRef.current.height,\n      },\n      syncPositioner: true,\n    };\n  });\n\n  React.useEffect(() => {\n    if (!open) {\n      stickIfOpenTimeout.clear();\n      mutationFrame.cancel();\n      resizeFrame.cancel();\n      sizeFrame.cancel();\n      cancelAutoSizeReset(true);\n      skipAutoSizeSyncRef.current = false;\n      setPointerType('');\n    }\n  }, [stickIfOpenTimeout, open, mutationFrame, resizeFrame, sizeFrame, cancelAutoSizeReset]);\n\n  React.useEffect(() => {\n    if (!mounted) {\n      prevSizeRef.current = DEFAULT_SIZE;\n    }\n  }, [mounted]);\n\n  React.useEffect(() => {\n    if (!popupElement || typeof ResizeObserver !== 'function') {\n      return undefined;\n    }\n\n    const resizeObserver = new ResizeObserver(() => {\n      prevSizeRef.current = {\n        width: popupElement.offsetWidth,\n        height: popupElement.offsetHeight,\n      };\n    });\n\n    resizeObserver.observe(popupElement);\n\n    return () => {\n      resizeObserver.disconnect();\n    };\n  }, [popupElement]);\n\n  React.useEffect(() => {\n    if (!open || !isActiveItem || !popupElement || !positionerElement) {\n      return undefined;\n    }\n\n    const win = ownerWindow(positionerElement);\n    function handleResize() {\n      resizeFrame.cancel();\n      resizeFrame.request(syncCurrentSize);\n    }\n\n    win.addEventListener('resize', handleResize);\n\n    return () => {\n      resizeFrame.cancel();\n      win.removeEventListener('resize', handleResize);\n    };\n  }, [open, isActiveItem, popupElement, positionerElement, resizeFrame, syncCurrentSize]);\n\n  React.useEffect(() => {\n    const observedElement = currentContentRef.current;\n\n    if (\n      !observedElement ||\n      !popupElement ||\n      !isActiveItem ||\n      typeof MutationObserver !== 'function'\n    ) {\n      return undefined;\n    }\n\n    const mutationObserver = new MutationObserver(() => {\n      if (\n        transitionStatus === 'starting' ||\n        popupElement.hasAttribute(TransitionStatusDataAttributes.startingStyle)\n      ) {\n        syncCurrentSize();\n        return;\n      }\n\n      const { size, syncPositioner } = getMutationBaseline();\n\n      if (syncPositioner) {\n        handleInterruptedMutationResize(size.width, size.height);\n        return;\n      }\n\n      handleValueChange(size.width, size.height);\n    });\n\n    mutationObserver.observe(observedElement, {\n      childList: true,\n      subtree: true,\n      characterData: true,\n    });\n\n    return () => {\n      mutationObserver.disconnect();\n    };\n  }, [\n    currentContentRef,\n    popupElement,\n    isActiveItem,\n    transitionStatus,\n    getMutationBaseline,\n    handleInterruptedMutationResize,\n    handleValueChange,\n    syncCurrentSize,\n  ]);\n\n  React.useEffect(() => {\n    if (isActiveItem && open && popupElement && allowFocusRef.current) {\n      allowFocusRef.current = false;\n      focusFrame.request(() => {\n        beforeOutsideRef.current?.focus();\n      });\n    }\n\n    return () => {\n      focusFrame.cancel();\n    };\n  }, [beforeOutsideRef, focusFrame, isActiveItem, open, popupElement]);\n\n  useIsoLayoutEffect(() => {\n    if (isActiveItemRef.current && open && popupElement) {\n      if (transitionStatus === 'starting') {\n        const hasNestedMenu = currentContentRef.current?.querySelector('[data-nested]') != null;\n\n        if (hasNestedMenu) {\n          sizeFrame.request(syncCurrentSize);\n          return () => {\n            sizeFrame.cancel();\n          };\n        }\n      }\n\n      if (skipAutoSizeSyncRef.current) {\n        skipAutoSizeSyncRef.current = false;\n        return undefined;\n      }\n\n      handleValueChange(0, 0);\n    }\n    return undefined;\n  }, [\n    currentContentRef,\n    handleValueChange,\n    isActiveItemRef,\n    open,\n    popupElement,\n    sizeFrame,\n    syncCurrentSize,\n    transitionStatus,\n  ]);\n\n  function handleOpenChange(\n    nextOpen: boolean,\n    eventDetails: NavigationMenuRoot.ChangeEventDetails,\n  ) {\n    const isHover = eventDetails.reason === REASONS.triggerHover;\n\n    if (!interactionsEnabled) {\n      return;\n    }\n\n    if (pointerType === 'touch' && isHover) {\n      return;\n    }\n\n    if (!nextOpen && value !== itemValue) {\n      return;\n    }\n\n    function changeState() {\n      if (isHover) {\n        // Only allow \"patient\" clicks to close the popup if it's open.\n        // If they clicked within 500ms of the popup opening, keep it open.\n        setStickIfOpen(true);\n        stickIfOpenTimeout.clear();\n        stickIfOpenTimeout.start(PATIENT_CLICK_THRESHOLD, () => {\n          setStickIfOpen(false);\n        });\n      }\n\n      if (nextOpen) {\n        setValue(itemValue, eventDetails);\n      } else {\n        setValue(null, eventDetails);\n        setPointerType('');\n      }\n    }\n\n    if (isHover) {\n      ReactDOM.flushSync(changeState);\n    } else {\n      changeState();\n    }\n  }\n\n  const context = useFloatingRootContext({\n    open,\n    onOpenChange: handleOpenChange,\n    elements: {\n      reference: triggerElement,\n      floating: hoverFloatingElement,\n    },\n  });\n\n  const hoverInteractionState = useHoverInteractionSharedState(context);\n\n  React.useEffect(() => {\n    if (!open) {\n      context.context.dataRef.current.openEvent = undefined;\n      hoverInteractionState.pointerType = undefined;\n      hoverInteractionState.interactedInside = false;\n      hoverInteractionState.restTimeoutPending = false;\n      hoverInteractionState.openChangeTimeout.clear();\n      hoverInteractionState.restTimeout.clear();\n      clearSafePolygonPointerEventsMutation(hoverInteractionState);\n    }\n  }, [context, hoverInteractionState, open]);\n\n  const getInlineHandleCloseContext = useStableCallback(() => {\n    if (!nested || positionerElement || !triggerElementRef.current || !hoverFloatingElement) {\n      return null;\n    }\n\n    return getHandleCloseContext(triggerElementRef.current, hoverFloatingElement, nodeId);\n  });\n\n  function getScope() {\n    return triggerElementRef.current?.closest('ul') ?? null;\n  }\n\n  const hoverProps = useHoverReferenceInteraction(context, {\n    enabled: hoverInteractionsEnabled,\n    move: false,\n    handleClose: safePolygon({\n      blockPointerEvents: pointerType !== 'touch' && (!isWebKit || nested),\n      getScope,\n    }),\n    restMs: mounted && positionerElement ? 0 : delay,\n    delay: { close: closeDelay },\n    triggerElementRef,\n    getHandleCloseContext: getInlineHandleCloseContext,\n  });\n\n  const hover = React.useMemo(\n    () => (hoverProps ? { reference: hoverProps } : undefined),\n    [hoverProps],\n  );\n\n  const click = useClick(context, {\n    enabled: interactionsEnabled,\n    stickIfOpen,\n    toggle: isActiveItem,\n  });\n  const { getReferenceProps } = useInteractions([hover, click]);\n\n  useIsoLayoutEffect(() => {\n    if (isActiveItem) {\n      setFloatingRootContext(context);\n      prevTriggerElementRef.current = triggerElement;\n    }\n  }, [isActiveItem, context, setFloatingRootContext, prevTriggerElementRef, triggerElement]);\n\n  function handleActivation(event: React.MouseEvent | React.KeyboardEvent) {\n    ReactDOM.flushSync(() => {\n      const prevTriggerRect = prevTriggerElementRef.current?.getBoundingClientRect();\n\n      if (mounted && prevTriggerRect && triggerElement) {\n        const nextTriggerRect = triggerElement.getBoundingClientRect();\n        const isMovingRight = nextTriggerRect.left > prevTriggerRect.left;\n        const isMovingDown = nextTriggerRect.top > prevTriggerRect.top;\n\n        if (orientation === 'horizontal' && nextTriggerRect.left !== prevTriggerRect.left) {\n          setActivationDirection(isMovingRight ? 'right' : 'left');\n        } else if (orientation === 'vertical' && nextTriggerRect.top !== prevTriggerRect.top) {\n          setActivationDirection(isMovingDown ? 'down' : 'up');\n        }\n      }\n\n      // Reset the `openEvent` to `undefined` when the active item changes so that a\n      // `click` -> `hover` on new trigger -> `hover` back to old trigger doesn't unexpectedly\n      // cause the popup to remain stuck open when leaving the old trigger.\n      if (event.type !== 'click' && value != null) {\n        context.context.dataRef.current.openEvent = undefined;\n      }\n\n      if (pointerType === 'touch' && event.type !== 'click') {\n        return;\n      }\n\n      if (value != null) {\n        setValue(\n          itemValue,\n          createChangeEventDetails(\n            event.type === 'mouseenter' ? REASONS.triggerHover : REASONS.triggerPress,\n            event.nativeEvent,\n          ),\n        );\n      }\n\n      if (\n        event.type === 'mouseenter' &&\n        nested &&\n        !positionerElement &&\n        pointerType !== 'touch' &&\n        hoverFloatingElement &&\n        isHTMLElement(event.currentTarget)\n      ) {\n        const scopeElement = getScope();\n\n        if (!scopeElement) {\n          return;\n        }\n\n        applySafePolygonPointerEventsMutation(hoverInteractionState, {\n          scopeElement,\n          referenceElement: event.currentTarget,\n          floatingElement: hoverFloatingElement,\n        });\n      }\n    });\n  }\n\n  const handleOpenEvent = useStableCallback((event: React.MouseEvent | React.KeyboardEvent) => {\n    if (!popupElement || !positionerElement) {\n      handleActivation(event);\n      return;\n    }\n\n    const { width, height } = getCssDimensions(popupElement);\n    const shouldSkipAutoSizeSync =\n      value != null && value !== itemValue && (event.type === 'click' || pointerType !== 'touch');\n\n    handleActivation(event);\n\n    if (shouldSkipAutoSizeSync) {\n      skipAutoSizeSyncRef.current = true;\n    }\n\n    handleValueChange(width, height);\n  });\n\n  const state: NavigationMenuTriggerState = {\n    open: isActiveItem,\n  };\n\n  function handleSetPointerType(event: React.PointerEvent) {\n    setPointerType(event.pointerType);\n  }\n\n  const defaultProps: HTMLProps = {\n    tabIndex: 0,\n    onMouseEnter: handleOpenEvent,\n    onClick: handleOpenEvent,\n    onPointerEnter: handleSetPointerType,\n    onPointerDown: handleSetPointerType,\n    'aria-expanded': isActiveItem,\n    'aria-controls': isActiveItem ? popupElement?.id : undefined,\n    [NAVIGATION_MENU_TRIGGER_IDENTIFIER as string]: '',\n    onFocus() {\n      if (!isActiveItem) {\n        return;\n      }\n      setViewportInert(false);\n    },\n    onMouseMove() {\n      allowFocusRef.current = false;\n    },\n    onKeyDown(event) {\n      allowFocusRef.current = true;\n\n      // For nested (submenu) triggers, don't intercept arrow keys that are used for\n      // navigation in the parent content. The arrow keys should be handled by the\n      // parent's CompositeRoot for navigating between items.\n      if (nested) {\n        return;\n      }\n\n      const openHorizontal = orientation === 'horizontal' && event.key === 'ArrowDown';\n      const openVertical = orientation === 'vertical' && event.key === 'ArrowRight';\n\n      if (openHorizontal || openVertical) {\n        setValue(itemValue, createChangeEventDetails(REASONS.listNavigation, event.nativeEvent));\n        handleOpenEvent(event);\n        stopEvent(event);\n      }\n    },\n    onBlur(event) {\n      if (\n        positionerElement &&\n        popupElement &&\n        isOutsideMenuEvent(\n          {\n            currentTarget: event.currentTarget,\n            relatedTarget: event.relatedTarget as HTMLElement | null,\n          },\n          { popupElement, rootRef, tree, nodeId },\n        )\n      ) {\n        setValue(null, createChangeEventDetails(REASONS.focusOut, event.nativeEvent));\n      }\n    },\n  };\n\n  const { getButtonProps, buttonRef } = useButton({\n    disabled,\n    focusableWhenDisabled: true,\n    native: nativeButton,\n  });\n\n  const referenceElement = hoverFloatingElement;\n\n  return (\n    <React.Fragment>\n      <CompositeItem\n        tag=\"button\"\n        render={render}\n        className={className}\n        state={state}\n        stateAttributesMapping={pressableTriggerOpenStateMapping}\n        refs={[forwardedRef, handleTriggerElement, buttonRef]}\n        props={[\n          getReferenceProps,\n          dismissProps?.reference || EMPTY_ARRAY,\n          defaultProps,\n          elementProps,\n          getButtonProps,\n        ]}\n      />\n      {isActiveItem && (\n        <React.Fragment>\n          <FocusGuard\n            ref={beforeOutsideRef}\n            onFocus={(event) => {\n              if (referenceElement && isOutsideEvent(event, referenceElement)) {\n                beforeInsideRef.current?.focus();\n              } else {\n                const prevTabbable = getPreviousTabbable(triggerElement);\n                prevTabbable?.focus();\n              }\n            }}\n          />\n          <span aria-owns={viewportElement?.id} style={ownerVisuallyHidden} />\n          <FocusGuard\n            ref={afterOutsideRef}\n            onFocus={(event) => {\n              if (referenceElement && isOutsideEvent(event, referenceElement)) {\n                const elementToFocus =\n                  afterInsideRef.current && isTabbable(afterInsideRef.current)\n                    ? afterInsideRef.current\n                    : triggerElement;\n                elementToFocus?.focus();\n              } else {\n                let nextTabbable = getNextTabbable(triggerElement);\n\n                if (\n                  nested &&\n                  !positionerElement &&\n                  referenceElement &&\n                  nextTabbable &&\n                  contains(referenceElement, nextTabbable)\n                ) {\n                  nextTabbable = getTabbableAfterElement(afterInsideRef.current);\n                }\n\n                nextTabbable?.focus();\n\n                if ((!nested || positionerElement) && !contains(rootRef.current, nextTabbable)) {\n                  setValue(null, createChangeEventDetails(REASONS.focusOut, event.nativeEvent));\n                }\n              }\n            }}\n          />\n        </React.Fragment>\n      )}\n    </React.Fragment>\n  );\n});\n\nexport interface NavigationMenuTriggerState {\n  /**\n   * If `true`, the popup is open and the item is active.\n   */\n  open: boolean;\n}\n\nexport interface NavigationMenuTriggerProps\n  extends NativeButtonProps, BaseUIComponentProps<'button', NavigationMenuTriggerState> {}\n\nexport namespace NavigationMenuTrigger {\n  export type State = NavigationMenuTriggerState;\n  export type Props = NavigationMenuTriggerProps;\n}\n\nfunction getPlacementFromElements(\n  domReferenceElement: Element,\n  floatingElement: HTMLElement,\n): HandleCloseContextBase['placement'] {\n  const referenceRect = domReferenceElement.getBoundingClientRect();\n  const floatingRect = floatingElement.getBoundingClientRect();\n  const referenceCenterX = referenceRect.left + referenceRect.width / 2;\n  const referenceCenterY = referenceRect.top + referenceRect.height / 2;\n  const floatingCenterX = floatingRect.left + floatingRect.width / 2;\n  const floatingCenterY = floatingRect.top + floatingRect.height / 2;\n  const deltaX = floatingCenterX - referenceCenterX;\n  const deltaY = floatingCenterY - referenceCenterY;\n\n  if (Math.abs(deltaX) >= Math.abs(deltaY)) {\n    return deltaX >= 0 ? 'right' : 'left';\n  }\n\n  return deltaY >= 0 ? 'bottom' : 'top';\n}\n\nfunction getHandleCloseContext(\n  domReferenceElement: Element,\n  floatingElement: HTMLElement,\n  nodeId: string | undefined,\n): HandleCloseContextBase {\n  return {\n    placement: getPlacementFromElements(domReferenceElement, floatingElement),\n    elements: {\n      domReference: domReferenceElement,\n      floating: floatingElement,\n    },\n    nodeId,\n  };\n}\n"
  },
  {
    "path": "packages/react/src/navigation-menu/trigger/NavigationMenuTriggerDataAttributes.ts",
    "content": "import { CommonTriggerDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum NavigationMenuTriggerDataAttributes {\n  /**\n   * Present when the corresponding navigation menu is open.\n   */\n  popupOpen = CommonTriggerDataAttributes.popupOpen,\n  /**\n   * Present when the trigger is pressed.\n   */\n  pressed = CommonTriggerDataAttributes.pressed,\n}\n"
  },
  {
    "path": "packages/react/src/navigation-menu/utils/constants.ts",
    "content": "export const OPEN_DELAY = 50;\nexport const CLOSE_DELAY = 50;\nexport const NAVIGATION_MENU_TRIGGER_IDENTIFIER = 'data-base-ui-navigation-menu-trigger';\n"
  },
  {
    "path": "packages/react/src/navigation-menu/utils/isOutsideMenuEvent.ts",
    "content": "import { FloatingTreeType } from '../../floating-ui-react';\nimport { contains, getNodeChildren } from '../../floating-ui-react/utils';\n\ninterface Targets {\n  currentTarget: HTMLElement | null;\n  relatedTarget: HTMLElement | null;\n}\n\ninterface Params {\n  popupElement: HTMLElement | null;\n  viewportElement?: HTMLElement | null | undefined;\n  rootRef: React.RefObject<HTMLDivElement | null>;\n  tree: FloatingTreeType | null;\n  nodeId: string | undefined;\n}\n\nexport function isOutsideMenuEvent({ currentTarget, relatedTarget }: Targets, params: Params) {\n  const { popupElement, viewportElement, rootRef, tree, nodeId } = params;\n\n  const nodeChildrenContains = tree\n    ? getNodeChildren(tree.nodesRef.current, nodeId).some((node) =>\n        contains(node.context?.elements.floating, relatedTarget),\n      )\n    : [];\n\n  // For nested scenarios without popupElement, we need to be more lenient\n  // and only close if we're definitely outside the root\n  if (!popupElement) {\n    return !contains(rootRef.current, relatedTarget) && !nodeChildrenContains;\n  }\n\n  // Use popupElement as the primary floating element, but fall back to viewportElement if needed\n  const floatingElement = popupElement || viewportElement;\n\n  return (\n    !contains(floatingElement, currentTarget) &&\n    !contains(floatingElement, relatedTarget) &&\n    !contains(rootRef.current, relatedTarget) &&\n    !nodeChildrenContains &&\n    !(\n      contains(floatingElement, relatedTarget) &&\n      relatedTarget?.hasAttribute('data-base-ui-focus-guard')\n    )\n  );\n}\n"
  },
  {
    "path": "packages/react/src/navigation-menu/viewport/NavigationMenuViewport.test.tsx",
    "content": "import { NavigationMenu } from '@base-ui/react/navigation-menu';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<NavigationMenu.Viewport />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<NavigationMenu.Viewport />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(<NavigationMenu.Root>{node}</NavigationMenu.Root>);\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/navigation-menu/viewport/NavigationMenuViewport.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useId } from '@base-ui/utils/useId';\nimport { inertValue } from '@base-ui/utils/inertValue';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useNavigationMenuRootContext } from '../root/NavigationMenuRootContext';\nimport { FocusGuard } from '../../utils/FocusGuard';\nimport {\n  getNextTabbable,\n  getPreviousTabbable,\n  isOutsideEvent,\n  contains,\n} from '../../floating-ui-react/utils';\nimport { getEmptyRootContext } from '../../floating-ui-react/utils/getEmptyRootContext';\nimport { useNavigationMenuPositionerContext } from '../positioner/NavigationMenuPositionerContext';\n\nconst EMPTY_ROOT_CONTEXT = getEmptyRootContext();\n\nfunction Guards({ children }: { children: React.ReactNode }) {\n  const {\n    beforeInsideRef,\n    beforeOutsideRef,\n    afterInsideRef,\n    afterOutsideRef,\n    positionerElement,\n    viewportElement,\n    floatingRootContext,\n  } = useNavigationMenuRootContext();\n  const hasPositioner = Boolean(useNavigationMenuPositionerContext(true));\n\n  const referenceElement = positionerElement || viewportElement;\n\n  if (!floatingRootContext && !hasPositioner) {\n    return children;\n  }\n\n  return (\n    <React.Fragment>\n      <FocusGuard\n        ref={beforeInsideRef}\n        onFocus={(event) => {\n          if (referenceElement && isOutsideEvent(event, referenceElement)) {\n            getNextTabbable(referenceElement)?.focus();\n          } else {\n            beforeOutsideRef.current?.focus();\n          }\n        }}\n      />\n      {children}\n      <FocusGuard\n        ref={afterInsideRef}\n        onFocus={(event) => {\n          if (referenceElement && isOutsideEvent(event, referenceElement)) {\n            getPreviousTabbable(referenceElement)?.focus();\n          } else {\n            afterOutsideRef.current?.focus();\n          }\n        }}\n      />\n    </React.Fragment>\n  );\n}\n\n/**\n * The clipping viewport of the navigation menu's current content.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Navigation Menu](https://base-ui.com/react/components/navigation-menu)\n */\n\nexport const NavigationMenuViewport = React.forwardRef(function NavigationMenuViewport(\n  componentProps: NavigationMenuViewport.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { className, render, children, id: idProp, ...elementProps } = componentProps;\n\n  const id = useId(idProp);\n\n  const {\n    setViewportElement,\n    setViewportTargetElement,\n    floatingRootContext,\n    prevTriggerElementRef,\n    viewportInert,\n    setViewportInert,\n  } = useNavigationMenuRootContext();\n\n  const positioning = useNavigationMenuPositionerContext(true);\n  const hasPositioner = Boolean(positioning);\n  const domReference = (floatingRootContext || EMPTY_ROOT_CONTEXT).useState('domReferenceElement');\n\n  useIsoLayoutEffect(() => {\n    if (domReference) {\n      prevTriggerElementRef.current = domReference;\n    }\n  }, [domReference, prevTriggerElementRef]);\n\n  const element = useRenderElement('div', componentProps, {\n    ref: [forwardedRef, setViewportElement],\n    props: [\n      {\n        id,\n        onBlur(event) {\n          const relatedTarget = event.relatedTarget as Element | null;\n          const currentTarget = event.currentTarget as Element;\n\n          // If focus is leaving the viewport and not going to the trigger, make it inert\n          // to prevent a focus loop.\n          if (\n            relatedTarget &&\n            !contains(currentTarget, relatedTarget) &&\n            relatedTarget !== domReference\n          ) {\n            setViewportInert(true);\n          }\n        },\n        ...(!hasPositioner && viewportInert && { inert: inertValue(true) }),\n        children: hasPositioner ? (\n          children\n        ) : (\n          <Guards>\n            <div ref={setViewportTargetElement}>{children}</div>\n          </Guards>\n        ),\n      },\n      elementProps,\n    ],\n  });\n\n  return hasPositioner ? <Guards>{element}</Guards> : element;\n});\n\nexport interface NavigationMenuViewportState {}\n\nexport interface NavigationMenuViewportProps extends BaseUIComponentProps<\n  'div',\n  NavigationMenuViewportState\n> {}\n\nexport namespace NavigationMenuViewport {\n  export type State = NavigationMenuViewportState;\n  export type Props = NavigationMenuViewportProps;\n}\n"
  },
  {
    "path": "packages/react/src/number-field/decrement/NumberFieldDecrement.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { screen, fireEvent, act } from '@mui/internal-test-utils';\nimport { NumberField } from '@base-ui/react/number-field';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\nimport { CHANGE_VALUE_TICK_DELAY, START_AUTO_CHANGE_DELAY } from '../utils/constants';\n\ndescribe('<NumberField.Decrement />', () => {\n  const { render, clock } = createRenderer();\n\n  describeConformance(<NumberField.Decrement />, () => ({\n    refInstanceof: window.HTMLButtonElement,\n    testComponentPropWith: 'button',\n    button: true,\n    render(node) {\n      return render(<NumberField.Root>{node}</NumberField.Root>);\n    },\n  }));\n\n  it('has decrease label', async () => {\n    await render(\n      <NumberField.Root>\n        <NumberField.Decrement />\n      </NumberField.Root>,\n    );\n    expect(screen.queryByLabelText('Decrease')).not.toBe(null);\n  });\n\n  it('decrements starting from 0 click', async () => {\n    await render(\n      <NumberField.Root>\n        <NumberField.Decrement />\n        <NumberField.Input />\n      </NumberField.Root>,\n    );\n\n    const button = screen.getByRole('button');\n    fireEvent.click(button);\n    expect(screen.getByRole('textbox')).toHaveValue('0');\n  });\n\n  it('decrements to -1 starting from defaultValue=0 click', async () => {\n    await render(\n      <NumberField.Root defaultValue={0}>\n        <NumberField.Decrement />\n        <NumberField.Input />\n      </NumberField.Root>,\n    );\n\n    const button = screen.getByRole('button');\n    fireEvent.click(button);\n    expect(screen.getByRole('textbox')).toHaveValue('-1');\n  });\n\n  it('first decrement after external controlled update', async () => {\n    function Controlled() {\n      const [value, setValue] = React.useState<number | null>(null);\n      return (\n        <NumberField.Root value={value} onValueChange={setValue}>\n          <NumberField.Input />\n          <NumberField.Decrement />\n          <button onClick={() => setValue(1.23456)}>external</button>\n        </NumberField.Root>\n      );\n    }\n\n    const { user } = await render(<Controlled />);\n    const input = screen.getByRole('textbox');\n    const increase = screen.getByLabelText('Decrease');\n\n    await user.click(screen.getByText('external'));\n    expect(input).toHaveValue((1.23456).toLocaleString(undefined, { minimumFractionDigits: 5 }));\n\n    await user.click(increase);\n    expect(input).toHaveValue((0.235).toLocaleString(undefined, { minimumFractionDigits: 3 }));\n  });\n\n  it('only calls onValueChange once per decrement', async () => {\n    const handleValueChange = vi.fn();\n    const { user } = await render(\n      <NumberField.Root onValueChange={handleValueChange}>\n        <NumberField.Decrement />\n        <NumberField.Input />\n      </NumberField.Root>,\n    );\n\n    const button = screen.getByRole('button');\n\n    await user.click(button);\n    expect(handleValueChange.mock.calls.length).toBe(1);\n\n    await user.click(button);\n    expect(handleValueChange.mock.calls.length).toBe(2);\n  });\n\n  describe('press and hold', () => {\n    clock.withFakeTimers();\n\n    it('decrements continuously when holding pointerdown', async () => {\n      await render(\n        <NumberField.Root defaultValue={0}>\n          <NumberField.Decrement />\n          <NumberField.Input />\n        </NumberField.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      const input = screen.getByRole('textbox');\n\n      fireEvent.pointerDown(button); // onChange x1\n\n      expect(input).toHaveValue('-1');\n\n      clock.tick(START_AUTO_CHANGE_DELAY);\n\n      clock.tick(CHANGE_VALUE_TICK_DELAY); // onChange x2\n      clock.tick(CHANGE_VALUE_TICK_DELAY); // onChange x3\n      clock.tick(CHANGE_VALUE_TICK_DELAY); // onChange x4\n\n      expect(input).toHaveValue('-4');\n\n      fireEvent.pointerUp(button);\n\n      clock.tick(CHANGE_VALUE_TICK_DELAY);\n\n      expect(input).toHaveValue('-4');\n    });\n\n    it('stops calling onValueChange once min is reached', async () => {\n      const handleValueChange = vi.fn();\n      await render(\n        <NumberField.Root defaultValue={-9} min={-10} onValueChange={handleValueChange}>\n          <NumberField.Decrement />\n          <NumberField.Input />\n        </NumberField.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      const input = screen.getByRole('textbox');\n\n      fireEvent.pointerDown(button); // onChange x1\n\n      expect(input).toHaveValue('-10');\n      expect(handleValueChange.mock.calls.length).toBe(1);\n\n      clock.tick(START_AUTO_CHANGE_DELAY);\n\n      clock.tick(CHANGE_VALUE_TICK_DELAY);\n      clock.tick(CHANGE_VALUE_TICK_DELAY);\n\n      expect(input).toHaveValue('-10');\n      expect(handleValueChange.mock.calls.length).toBe(1);\n\n      fireEvent.pointerUp(button);\n    });\n\n    it('does not decrement twice with pointerdown and click', async () => {\n      await render(\n        <NumberField.Root defaultValue={0}>\n          <NumberField.Decrement />\n          <NumberField.Input />\n        </NumberField.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      const input = screen.getByRole('textbox');\n\n      fireEvent.pointerDown(button); // onChange x1\n      fireEvent.pointerUp(button);\n      fireEvent.click(button, { detail: 1 });\n\n      expect(input).toHaveValue('-1');\n    });\n\n    it('should stop decrementing after mouseleave', async () => {\n      await render(\n        <NumberField.Root defaultValue={0}>\n          <NumberField.Decrement />\n          <NumberField.Input />\n        </NumberField.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      const input = screen.getByRole('textbox');\n\n      fireEvent.pointerDown(button); // onChange x1\n\n      expect(input).toHaveValue('-1');\n\n      clock.tick(START_AUTO_CHANGE_DELAY);\n\n      clock.tick(CHANGE_VALUE_TICK_DELAY); // onChange x2\n      clock.tick(CHANGE_VALUE_TICK_DELAY); // onChange x3\n      clock.tick(CHANGE_VALUE_TICK_DELAY); // onChange x4\n\n      expect(input).toHaveValue('-4');\n\n      fireEvent.mouseLeave(button);\n\n      clock.tick(CHANGE_VALUE_TICK_DELAY);\n\n      expect(input).toHaveValue('-4');\n    });\n\n    it('should start decrementing again after mouseleave then mouseenter', async () => {\n      await render(\n        <NumberField.Root defaultValue={0}>\n          <NumberField.Decrement />\n          <NumberField.Input />\n        </NumberField.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      const input = screen.getByRole('textbox');\n\n      fireEvent.pointerDown(button); // onChange x1\n\n      expect(input).toHaveValue('-1');\n\n      clock.tick(START_AUTO_CHANGE_DELAY);\n\n      clock.tick(CHANGE_VALUE_TICK_DELAY); // onChange x2\n      clock.tick(CHANGE_VALUE_TICK_DELAY); // onChange x3\n      clock.tick(CHANGE_VALUE_TICK_DELAY); // onChange x4\n\n      expect(input).toHaveValue('-4');\n\n      fireEvent.mouseLeave(button);\n\n      clock.tick(CHANGE_VALUE_TICK_DELAY);\n\n      expect(input).toHaveValue('-4');\n\n      fireEvent.mouseEnter(button);\n\n      clock.tick(CHANGE_VALUE_TICK_DELAY); // onChange x5\n\n      expect(input).toHaveValue('-5');\n    });\n\n    it('should not start decrementing again after mouseleave then mouseenter after pointerup', async () => {\n      await render(\n        <NumberField.Root defaultValue={0}>\n          <NumberField.Decrement />\n          <NumberField.Input />\n        </NumberField.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      const input = screen.getByRole('textbox');\n\n      fireEvent.pointerDown(button); // onChange x1\n\n      expect(input).toHaveValue('-1');\n\n      clock.tick(START_AUTO_CHANGE_DELAY);\n\n      clock.tick(CHANGE_VALUE_TICK_DELAY); // onChange x2\n      clock.tick(CHANGE_VALUE_TICK_DELAY); // onChange x3\n      clock.tick(CHANGE_VALUE_TICK_DELAY); // onChange x4\n\n      expect(input).toHaveValue('-4');\n\n      fireEvent.pointerUp(button);\n\n      clock.tick(CHANGE_VALUE_TICK_DELAY);\n\n      expect(input).toHaveValue('-4');\n\n      fireEvent.mouseLeave(button);\n\n      clock.tick(CHANGE_VALUE_TICK_DELAY);\n\n      expect(input).toHaveValue('-4');\n\n      fireEvent.mouseEnter(button);\n\n      clock.tick(CHANGE_VALUE_TICK_DELAY);\n\n      expect(input).toHaveValue('-4');\n    });\n  });\n\n  it('should not decrement when readOnly', async () => {\n    await render(\n      <NumberField.Root readOnly>\n        <NumberField.Decrement />\n        <NumberField.Input />\n      </NumberField.Root>,\n    );\n\n    const button = screen.getByRole('button');\n    fireEvent.click(button);\n    expect(screen.getByRole('textbox')).toHaveValue('');\n  });\n\n  it('should decrement when input is dirty but not blurred (click)', async () => {\n    await render(\n      <NumberField.Root defaultValue={0}>\n        <NumberField.Decrement />\n        <NumberField.Input />\n      </NumberField.Root>,\n    );\n\n    const input = screen.getByRole('textbox');\n\n    await act(() => input.focus());\n\n    fireEvent.change(input, { target: { value: '100' } });\n    fireEvent.click(screen.getByRole('button'));\n\n    expect(input).toHaveValue('99');\n  });\n\n  it('should decrement when input is dirty but not blurred (pointerdown)', async () => {\n    await render(\n      <NumberField.Root defaultValue={0}>\n        <NumberField.Decrement />\n        <NumberField.Input />\n      </NumberField.Root>,\n    );\n\n    const input = screen.getByRole('textbox');\n\n    await act(() => input.focus());\n\n    fireEvent.change(input, { target: { value: '100' } });\n    fireEvent.pointerDown(screen.getByRole('button'));\n\n    expect(input).toHaveValue('99');\n  });\n\n  it('always decrements on quick touch (touchend that occurs before TOUCH_TIMEOUT)', async () => {\n    await render(\n      <NumberField.Root defaultValue={0}>\n        <NumberField.Decrement />\n        <NumberField.Input />\n      </NumberField.Root>,\n    );\n\n    const button = screen.getByRole('button');\n    const input = screen.getByRole('textbox');\n\n    fireEvent.touchStart(button);\n    fireEvent.mouseEnter(button);\n    fireEvent.pointerDown(button, { pointerType: 'touch' });\n    fireEvent.touchEnd(button);\n    fireEvent.click(button, { detail: 1 });\n\n    expect(input).toHaveValue('-1');\n\n    fireEvent.touchStart(button);\n    // No mouseenter occurs after the first focus\n    fireEvent.pointerDown(button, { pointerType: 'touch' });\n    fireEvent.touchEnd(button);\n    fireEvent.click(button, { detail: 1 });\n\n    expect(input).toHaveValue('-2');\n  });\n\n  it.skipIf(isJSDOM)('fires onValueCommitted once on first soft tap (touch)', async () => {\n    const onValueCommitted = vi.fn();\n    await render(\n      <NumberField.Root defaultValue={0} onValueCommitted={onValueCommitted}>\n        <NumberField.Decrement />\n        <NumberField.Input />\n      </NumberField.Root>,\n    );\n\n    const button = screen.getByLabelText('Decrease');\n\n    fireEvent.touchStart(button);\n    fireEvent.pointerDown(button, { pointerType: 'touch' });\n    fireEvent.touchEnd(button);\n    fireEvent.mouseEnter(button);\n    fireEvent.click(button, { detail: 1 });\n\n    expect(onValueCommitted.mock.calls.length).toBe(1);\n    expect(onValueCommitted.mock.calls[0][0]).toBe(-1);\n  });\n\n  describe('prop: snapOnStep', () => {\n    it('should decrement by exact step without rounding when snapOnStep is false', async () => {\n      await render(\n        <NumberField.Root defaultValue={2.7} step={2} snapOnStep={false}>\n          <NumberField.Decrement />\n          <NumberField.Input />\n        </NumberField.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      fireEvent.click(button);\n\n      expect(screen.getByRole('textbox')).toHaveValue((0.7).toLocaleString());\n    });\n\n    it('should snap on decrement when snapOnStep is true', async () => {\n      await render(\n        <NumberField.Root defaultValue={1.3} snapOnStep>\n          <NumberField.Decrement />\n          <NumberField.Input />\n        </NumberField.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      fireEvent.click(button);\n\n      expect(screen.getByRole('textbox')).toHaveValue('1');\n\n      fireEvent.change(screen.getByRole('textbox'), { target: { value: '1.9' } });\n      fireEvent.click(button);\n\n      expect(screen.getByRole('textbox')).toHaveValue('1');\n\n      fireEvent.change(screen.getByRole('textbox'), { target: { value: '-0.2' } });\n      fireEvent.click(button);\n\n      expect(screen.getByRole('textbox')).toHaveValue('-1');\n    });\n\n    it('should decrement with respect to the min value', async () => {\n      await render(\n        <NumberField.Root defaultValue={8} min={1} step={2} snapOnStep>\n          <NumberField.Decrement />\n          <NumberField.Input />\n        </NumberField.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      const input = screen.getByRole('textbox');\n\n      fireEvent.click(button);\n      expect(input).toHaveValue('7');\n\n      fireEvent.click(button);\n      expect(input).toHaveValue('5');\n\n      fireEvent.change(input, { target: { value: '9.112' } });\n      fireEvent.click(button);\n      expect(input).toHaveValue('9');\n\n      fireEvent.change(input, { target: { value: '1.112' } });\n      fireEvent.click(button);\n      expect(input).toHaveValue('1');\n    });\n  });\n\n  describe('disabled state', () => {\n    it('should not decrement when root is disabled', async () => {\n      const handleValueChange = vi.fn();\n      await render(\n        <NumberField.Root disabled onValueChange={handleValueChange}>\n          <NumberField.Decrement />\n          <NumberField.Input />\n        </NumberField.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      fireEvent.click(button);\n      expect(screen.getByRole('textbox')).toHaveValue('');\n      expect(handleValueChange.mock.calls.length).toBe(0);\n    });\n\n    it('should not decrement when button is disabled', async () => {\n      const handleValueChange = vi.fn();\n      await render(\n        <NumberField.Root defaultValue={0} onValueChange={handleValueChange}>\n          <NumberField.Decrement disabled />\n          <NumberField.Input />\n        </NumberField.Root>,\n      );\n      const input = screen.getByRole('textbox');\n      const button = screen.getByRole('button');\n      expect(button).toHaveAttribute('disabled');\n      expect(input).toHaveValue('0');\n\n      fireEvent.pointerDown(button);\n      expect(handleValueChange.mock.calls.length).toBe(0);\n      expect(input).toHaveValue('0');\n    });\n\n    describe('prop: className', () => {\n      it('when root is disabled', async () => {\n        const classNameSpy = vi.fn();\n        await render(\n          <NumberField.Root disabled>\n            <NumberField.Decrement className={classNameSpy} />\n            <NumberField.Input />\n          </NumberField.Root>,\n        );\n\n        expect(classNameSpy.mock.lastCall?.[0]).toHaveProperty('disabled', true);\n      });\n\n      it('when button is disabled', async () => {\n        const classNameSpy = vi.fn();\n        await render(\n          <NumberField.Root>\n            <NumberField.Decrement disabled className={classNameSpy} />\n            <NumberField.Input />\n          </NumberField.Root>,\n        );\n\n        expect(classNameSpy.mock.lastCall?.[0]).toHaveProperty('disabled', true);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/number-field/decrement/NumberFieldDecrement.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { BaseUIComponentProps, NativeButtonProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useButton } from '../../use-button';\nimport { useNumberFieldRootContext } from '../root/NumberFieldRootContext';\nimport type { NumberFieldRootState } from '../root/NumberFieldRoot';\nimport { useNumberFieldButton } from '../root/useNumberFieldButton';\nimport { stateAttributesMapping } from '../utils/stateAttributesMapping';\n\n/**\n * A stepper button that decreases the field value when clicked.\n * Renders an `<button>` element.\n *\n * Documentation: [Base UI Number Field](https://base-ui.com/react/components/number-field)\n */\nexport const NumberFieldDecrement = React.forwardRef(function NumberFieldDecrement(\n  componentProps: NumberFieldDecrement.Props,\n  forwardedRef: React.ForwardedRef<HTMLButtonElement>,\n) {\n  const {\n    render,\n    className,\n    disabled: disabledProp = false,\n    nativeButton = true,\n    ...elementProps\n  } = componentProps;\n\n  const {\n    allowInputSyncRef,\n    disabled: contextDisabled,\n    formatOptionsRef,\n    getStepAmount,\n    id,\n    incrementValue,\n    inputRef,\n    inputValue,\n    minWithDefault,\n    readOnly,\n    setValue,\n    state,\n    value,\n    valueRef,\n    locale,\n    lastChangedValueRef,\n    onValueCommitted,\n  } = useNumberFieldRootContext();\n\n  const isMin = value != null && value <= minWithDefault;\n  const disabled = disabledProp || contextDisabled || isMin;\n\n  const props = useNumberFieldButton({\n    isIncrement: false,\n    inputRef,\n    inputValue,\n    disabled,\n    readOnly,\n    id,\n    setValue,\n    getStepAmount,\n    incrementValue,\n    allowInputSyncRef,\n    formatOptionsRef,\n    valueRef,\n    locale,\n    lastChangedValueRef,\n    onValueCommitted,\n  });\n\n  const { getButtonProps, buttonRef } = useButton({\n    disabled,\n    native: nativeButton,\n    focusableWhenDisabled: true,\n  });\n\n  const buttonState = React.useMemo(\n    () => ({\n      ...state,\n      disabled,\n    }),\n    [state, disabled],\n  );\n\n  const element = useRenderElement('button', componentProps, {\n    ref: [forwardedRef, buttonRef],\n    state: buttonState,\n    props: [props, elementProps, getButtonProps],\n    stateAttributesMapping,\n  });\n\n  return element;\n});\n\nexport interface NumberFieldDecrementState extends NumberFieldRootState {}\n\nexport interface NumberFieldDecrementProps\n  extends NativeButtonProps, BaseUIComponentProps<'button', NumberFieldDecrementState> {}\n\nexport namespace NumberFieldDecrement {\n  export type State = NumberFieldDecrementState;\n  export type Props = NumberFieldDecrementProps;\n}\n"
  },
  {
    "path": "packages/react/src/number-field/decrement/NumberFieldDecrementDataAttributes.ts",
    "content": "export enum NumberFieldDecrementDataAttributes {\n  /**\n   * Present while scrubbing.\n   */\n  scrubbing = 'data-scrubbing',\n  /**\n   * Present when the number field is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the number field is readonly.\n   */\n  readonly = 'data-readonly',\n  /**\n   * Present when the number field is required.\n   */\n  required = 'data-required',\n  /**\n   * Present when the number field is in valid state (when wrapped in Field.Root).\n   */\n  valid = 'data-valid',\n  /**\n   * Present when the number field is in invalid state (when wrapped in Field.Root).\n   */\n  invalid = 'data-invalid',\n  /**\n   * Present when the number field has been touched (when wrapped in Field.Root).\n   */\n  touched = 'data-touched',\n  /**\n   * Present when the number field's value has changed (when wrapped in Field.Root).\n   */\n  dirty = 'data-dirty',\n  /**\n   * Present when the number field is filled (when wrapped in Field.Root).\n   */\n  filled = 'data-filled',\n  /**\n   * Present when the number field is focused (when wrapped in Field.Root).\n   */\n  focused = 'data-focused',\n}\n"
  },
  {
    "path": "packages/react/src/number-field/group/NumberFieldGroup.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { screen } from '@mui/internal-test-utils';\nimport { NumberField } from '@base-ui/react/number-field';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<NumberField.Group />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<NumberField.Group />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(<NumberField.Root>{node}</NumberField.Root>);\n    },\n  }));\n\n  it('has role prop', async () => {\n    await render(\n      <NumberField.Root>\n        <NumberField.Group />\n      </NumberField.Root>,\n    );\n    expect(screen.queryByRole('group')).not.toBe(null);\n  });\n});\n"
  },
  {
    "path": "packages/react/src/number-field/group/NumberFieldGroup.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useNumberFieldRootContext } from '../root/NumberFieldRootContext';\nimport type { NumberFieldRootState } from '../root/NumberFieldRoot';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { stateAttributesMapping } from '../utils/stateAttributesMapping';\nimport { useRenderElement } from '../../utils/useRenderElement';\n\n/**\n * Groups the input with the increment and decrement buttons.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Number Field](https://base-ui.com/react/components/number-field)\n */\nexport const NumberFieldGroup = React.forwardRef(function NumberFieldGroup(\n  componentProps: NumberFieldGroup.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, ...elementProps } = componentProps;\n\n  const { state } = useNumberFieldRootContext();\n\n  const element = useRenderElement('div', componentProps, {\n    ref: forwardedRef,\n    state,\n    props: [{ role: 'group' }, elementProps],\n    stateAttributesMapping,\n  });\n\n  return element;\n});\n\nexport interface NumberFieldGroupState extends NumberFieldRootState {}\n\nexport interface NumberFieldGroupProps extends BaseUIComponentProps<'div', NumberFieldGroupState> {}\n\nexport namespace NumberFieldGroup {\n  export type State = NumberFieldGroupState;\n  export type Props = NumberFieldGroupProps;\n}\n"
  },
  {
    "path": "packages/react/src/number-field/group/NumberFieldGroupDataAttributes.ts",
    "content": "export enum NumberFieldGroupDataAttributes {\n  /**\n   * Present while scrubbing.\n   */\n  scrubbing = 'data-scrubbing',\n  /**\n   * Present when the number field is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the number field is readonly.\n   */\n  readonly = 'data-readonly',\n  /**\n   * Present when the number field is required.\n   */\n  required = 'data-required',\n  /**\n   * Present when the number field is in valid state (when wrapped in Field.Root).\n   */\n  valid = 'data-valid',\n  /**\n   * Present when the number field is in invalid state (when wrapped in Field.Root).\n   */\n  invalid = 'data-invalid',\n  /**\n   * Present when the number field has been touched (when wrapped in Field.Root).\n   */\n  touched = 'data-touched',\n  /**\n   * Present when the number field's value has changed (when wrapped in Field.Root).\n   */\n  dirty = 'data-dirty',\n  /**\n   * Present when the number field is filled (when wrapped in Field.Root).\n   */\n  filled = 'data-filled',\n  /**\n   * Present when the number field is focused (when wrapped in Field.Root).\n   */\n  focused = 'data-focused',\n}\n"
  },
  {
    "path": "packages/react/src/number-field/increment/NumberFieldIncrement.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { screen, fireEvent, act } from '@mui/internal-test-utils';\nimport { NumberField } from '@base-ui/react/number-field';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\nimport { CHANGE_VALUE_TICK_DELAY, START_AUTO_CHANGE_DELAY } from '../utils/constants';\n\ndescribe('<NumberField.Increment />', () => {\n  const { render, clock } = createRenderer();\n\n  describeConformance(<NumberField.Increment />, () => ({\n    refInstanceof: window.HTMLButtonElement,\n    testComponentPropWith: 'button',\n    button: true,\n    render(node) {\n      return render(<NumberField.Root>{node}</NumberField.Root>);\n    },\n  }));\n\n  it('has increase label', async () => {\n    await render(\n      <NumberField.Root>\n        <NumberField.Increment />\n      </NumberField.Root>,\n    );\n    expect(screen.queryByLabelText('Increase')).not.toBe(null);\n  });\n\n  it('increments starting from 0 click', async () => {\n    await render(\n      <NumberField.Root>\n        <NumberField.Increment />\n        <NumberField.Input />\n      </NumberField.Root>,\n    );\n\n    const button = screen.getByRole('button');\n    fireEvent.click(button);\n    expect(screen.getByRole('textbox')).toHaveValue('0');\n  });\n\n  it('increments to 1 starting from defaultValue=0 click', async () => {\n    await render(\n      <NumberField.Root defaultValue={0}>\n        <NumberField.Increment />\n        <NumberField.Input />\n      </NumberField.Root>,\n    );\n\n    const button = screen.getByRole('button');\n    fireEvent.click(button);\n    expect(screen.getByRole('textbox')).toHaveValue('1');\n  });\n\n  it('first increment after external controlled update', async () => {\n    function Controlled() {\n      const [value, setValue] = React.useState<number | null>(null);\n      return (\n        <NumberField.Root value={value} onValueChange={setValue}>\n          <NumberField.Input />\n          <NumberField.Increment />\n          <button onClick={() => setValue(1.23456)}>external</button>\n        </NumberField.Root>\n      );\n    }\n\n    const { user } = await render(<Controlled />);\n    const input = screen.getByRole('textbox');\n    const increase = screen.getByLabelText('Increase');\n\n    await user.click(screen.getByText('external'));\n    expect(input).toHaveValue((1.23456).toLocaleString(undefined, { minimumFractionDigits: 5 }));\n\n    await user.click(increase);\n    expect(input).toHaveValue((2.235).toLocaleString(undefined, { minimumFractionDigits: 3 }));\n  });\n\n  it('only calls onValueChange once per increment', async () => {\n    const handleValueChange = vi.fn();\n    const { user } = await render(\n      <NumberField.Root onValueChange={handleValueChange}>\n        <NumberField.Increment />\n        <NumberField.Input />\n      </NumberField.Root>,\n    );\n\n    const button = screen.getByRole('button');\n\n    await user.click(button);\n    expect(handleValueChange.mock.calls.length).toBe(1);\n\n    await user.click(button);\n    expect(handleValueChange.mock.calls.length).toBe(2);\n  });\n\n  describe('press and hold', () => {\n    clock.withFakeTimers();\n\n    it('increments continuously when holding pointerdown', async () => {\n      await render(\n        <NumberField.Root defaultValue={0}>\n          <NumberField.Increment />\n          <NumberField.Input />\n        </NumberField.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      const input = screen.getByRole('textbox');\n\n      fireEvent.pointerDown(button); // onChange x1\n\n      expect(input).toHaveValue('1');\n\n      clock.tick(START_AUTO_CHANGE_DELAY);\n\n      clock.tick(CHANGE_VALUE_TICK_DELAY); // onChange x2\n      clock.tick(CHANGE_VALUE_TICK_DELAY); // onChange x3\n      clock.tick(CHANGE_VALUE_TICK_DELAY); // onChange x4\n\n      expect(input).toHaveValue('4');\n\n      fireEvent.pointerUp(button);\n\n      clock.tick(CHANGE_VALUE_TICK_DELAY);\n\n      expect(input).toHaveValue('4');\n    });\n\n    it('stops calling onValueChange once max is reached', async () => {\n      const handleValueChange = vi.fn();\n      await render(\n        <NumberField.Root defaultValue={9} max={10} onValueChange={handleValueChange}>\n          <NumberField.Increment />\n          <NumberField.Input />\n        </NumberField.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      const input = screen.getByRole('textbox');\n\n      fireEvent.pointerDown(button); // onChange x1\n\n      expect(input).toHaveValue('10');\n      expect(handleValueChange.mock.calls.length).toBe(1);\n\n      clock.tick(START_AUTO_CHANGE_DELAY);\n\n      clock.tick(CHANGE_VALUE_TICK_DELAY);\n      clock.tick(CHANGE_VALUE_TICK_DELAY);\n\n      expect(input).toHaveValue('10');\n      expect(handleValueChange.mock.calls.length).toBe(1);\n\n      fireEvent.pointerUp(button);\n    });\n\n    it('does not increment twice with pointerdown and click', async () => {\n      await render(\n        <NumberField.Root defaultValue={0}>\n          <NumberField.Increment />\n          <NumberField.Input />\n        </NumberField.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      const input = screen.getByRole('textbox');\n\n      fireEvent.pointerDown(button); // onChange x1\n      fireEvent.pointerUp(button);\n      fireEvent.click(button, { detail: 1 });\n\n      expect(input).toHaveValue('1');\n    });\n\n    it('should stop incrementing after mouseleave', async () => {\n      await render(\n        <NumberField.Root defaultValue={0}>\n          <NumberField.Increment />\n          <NumberField.Input />\n        </NumberField.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      const input = screen.getByRole('textbox');\n\n      fireEvent.pointerDown(button); // onChange x1\n\n      expect(input).toHaveValue('1');\n\n      clock.tick(START_AUTO_CHANGE_DELAY);\n\n      clock.tick(CHANGE_VALUE_TICK_DELAY); // onChange x2\n      clock.tick(CHANGE_VALUE_TICK_DELAY); // onChange x3\n      clock.tick(CHANGE_VALUE_TICK_DELAY); // onChange x4\n\n      expect(input).toHaveValue('4');\n\n      fireEvent.mouseLeave(button);\n\n      clock.tick(CHANGE_VALUE_TICK_DELAY);\n\n      expect(input).toHaveValue('4');\n    });\n\n    it('should start incrementing again after mouseleave then mouseenter', async () => {\n      await render(\n        <NumberField.Root defaultValue={0}>\n          <NumberField.Increment />\n          <NumberField.Input />\n        </NumberField.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      const input = screen.getByRole('textbox');\n\n      fireEvent.pointerDown(button); // onChange x1\n\n      expect(input).toHaveValue('1');\n\n      clock.tick(START_AUTO_CHANGE_DELAY);\n\n      clock.tick(CHANGE_VALUE_TICK_DELAY); // onChange x2\n      clock.tick(CHANGE_VALUE_TICK_DELAY); // onChange x3\n      clock.tick(CHANGE_VALUE_TICK_DELAY); // onChange x4\n\n      expect(input).toHaveValue('4');\n\n      fireEvent.mouseLeave(button);\n\n      clock.tick(CHANGE_VALUE_TICK_DELAY);\n\n      expect(input).toHaveValue('4');\n\n      fireEvent.mouseEnter(button);\n\n      clock.tick(CHANGE_VALUE_TICK_DELAY); // onChange x5\n\n      expect(input).toHaveValue('5');\n    });\n\n    it('should not start incrementing again after mouseleave then mouseenter after pointerup', async () => {\n      await render(\n        <NumberField.Root defaultValue={0}>\n          <NumberField.Increment />\n          <NumberField.Input />\n        </NumberField.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      const input = screen.getByRole('textbox');\n\n      fireEvent.pointerDown(button); // onChange x1\n\n      expect(input).toHaveValue('1');\n\n      clock.tick(START_AUTO_CHANGE_DELAY);\n\n      clock.tick(CHANGE_VALUE_TICK_DELAY); // onChange x2\n      clock.tick(CHANGE_VALUE_TICK_DELAY); // onChange x3\n      clock.tick(CHANGE_VALUE_TICK_DELAY); // onChange x4\n\n      expect(input).toHaveValue('4');\n\n      fireEvent.pointerUp(button);\n\n      clock.tick(CHANGE_VALUE_TICK_DELAY);\n\n      expect(input).toHaveValue('4');\n\n      fireEvent.mouseLeave(button);\n\n      clock.tick(CHANGE_VALUE_TICK_DELAY);\n\n      expect(input).toHaveValue('4');\n\n      fireEvent.mouseEnter(button);\n\n      clock.tick(CHANGE_VALUE_TICK_DELAY);\n\n      expect(input).toHaveValue('4');\n    });\n  });\n\n  it('should not increment when readOnly', async () => {\n    await render(\n      <NumberField.Root readOnly>\n        <NumberField.Increment />\n        <NumberField.Input />\n      </NumberField.Root>,\n    );\n\n    const button = screen.getByRole('button');\n    fireEvent.click(button);\n    expect(screen.getByRole('textbox')).toHaveValue('');\n  });\n\n  it('should increment when input is dirty but not blurred (click)', async () => {\n    await render(\n      <NumberField.Root defaultValue={0}>\n        <NumberField.Increment />\n        <NumberField.Input />\n      </NumberField.Root>,\n    );\n\n    const input = screen.getByRole('textbox');\n\n    await act(() => input.focus());\n\n    fireEvent.change(input, { target: { value: '100' } });\n    fireEvent.click(screen.getByRole('button'));\n\n    expect(input).toHaveValue('101');\n  });\n\n  it('should increment when input is dirty but not blurred (pointerdown)', async () => {\n    await render(\n      <NumberField.Root defaultValue={0}>\n        <NumberField.Increment />\n        <NumberField.Input />\n      </NumberField.Root>,\n    );\n\n    const input = screen.getByRole('textbox');\n\n    await act(() => input.focus());\n\n    fireEvent.change(input, { target: { value: '100' } });\n    fireEvent.pointerDown(screen.getByRole('button'));\n\n    expect(input).toHaveValue('101');\n  });\n\n  it('treats pen pointer as touch-like', async () => {\n    await render(\n      <NumberField.Root defaultValue={0}>\n        <NumberField.Increment />\n        <NumberField.Input />\n      </NumberField.Root>,\n    );\n\n    const button = screen.getByRole('button');\n    const input = screen.getByRole('textbox');\n\n    fireEvent.pointerDown(button, { pointerType: 'pen', button: 0 });\n\n    expect(document.activeElement).not.toBe(input);\n  });\n\n  it('always increments on quick touch (touchend that occurs before TOUCH_TIMEOUT)', async () => {\n    await render(\n      <NumberField.Root defaultValue={0}>\n        <NumberField.Increment />\n        <NumberField.Input />\n      </NumberField.Root>,\n    );\n\n    const button = screen.getByRole('button');\n    const input = screen.getByRole('textbox');\n\n    fireEvent.touchStart(button);\n    fireEvent.mouseEnter(button);\n    fireEvent.pointerDown(button, { pointerType: 'touch' });\n    fireEvent.click(button, { detail: 1 });\n    fireEvent.touchEnd(button);\n\n    expect(input).toHaveValue('1');\n\n    fireEvent.touchStart(button);\n    // No mouseenter occurs after the first focus\n    fireEvent.pointerDown(button, { pointerType: 'touch' });\n    fireEvent.click(button, { detail: 1 });\n    fireEvent.touchEnd(button);\n\n    expect(input).toHaveValue('2');\n  });\n\n  it.skipIf(isJSDOM)('fires onValueCommitted once on first soft tap (touch)', async () => {\n    const onValueCommitted = vi.fn();\n    await render(\n      <NumberField.Root defaultValue={0} onValueCommitted={onValueCommitted}>\n        <NumberField.Increment />\n        <NumberField.Input />\n      </NumberField.Root>,\n    );\n\n    const button = screen.getByLabelText('Increase');\n\n    // Simulate the typical sequence with a 300ms tap delay producing mouse compatibility events\n    fireEvent.touchStart(button);\n    fireEvent.pointerDown(button, { pointerType: 'touch' });\n    // No movement; quick tap\n    fireEvent.touchEnd(button);\n    // Compatibility mouse events and click\n    fireEvent.mouseEnter(button);\n    fireEvent.click(button, { detail: 1 });\n\n    expect(onValueCommitted.mock.calls.length).toBe(1);\n    expect(onValueCommitted.mock.calls[0][0]).toBe(1);\n  });\n\n  describe('prop: snapOnStep', () => {\n    it('should increment by exact step without rounding when snapOnStep is false', async () => {\n      await render(\n        <NumberField.Root defaultValue={2.7} step={2} snapOnStep={false}>\n          <NumberField.Increment />\n          <NumberField.Input />\n        </NumberField.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      fireEvent.click(button);\n\n      expect(screen.getByRole('textbox')).toHaveValue((4.7).toLocaleString());\n    });\n\n    it('should snap on increment when snapOnStep is true', async () => {\n      await render(\n        <NumberField.Root defaultValue={1.3} snapOnStep>\n          <NumberField.Increment />\n          <NumberField.Input />\n        </NumberField.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      fireEvent.click(button);\n\n      expect(screen.getByRole('textbox')).toHaveValue('2');\n\n      fireEvent.change(screen.getByRole('textbox'), { target: { value: '1.9' } });\n      fireEvent.click(button);\n\n      expect(screen.getByRole('textbox')).toHaveValue('2');\n\n      fireEvent.change(screen.getByRole('textbox'), { target: { value: '-0.2' } });\n      fireEvent.click(button);\n\n      expect(screen.getByRole('textbox')).toHaveValue('0');\n    });\n\n    it('should increment with respect to the min value', async () => {\n      await render(\n        <NumberField.Root defaultValue={1} min={1} step={2} snapOnStep>\n          <NumberField.Increment />\n          <NumberField.Input />\n        </NumberField.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      const input = screen.getByRole('textbox');\n\n      fireEvent.click(button);\n      expect(input).toHaveValue('3');\n\n      fireEvent.click(button);\n      expect(input).toHaveValue('5');\n\n      fireEvent.change(input, { target: { value: '1.112' } });\n      fireEvent.click(button);\n      expect(input).toHaveValue('3');\n\n      fireEvent.change(input, { target: { value: '0.999' } });\n      fireEvent.click(button);\n      expect(input).toHaveValue('1');\n    });\n  });\n\n  describe('disabled state', () => {\n    it('should not increment when root is disabled', async () => {\n      const handleValueChange = vi.fn();\n      await render(\n        <NumberField.Root disabled onValueChange={handleValueChange}>\n          <NumberField.Increment />\n          <NumberField.Input />\n        </NumberField.Root>,\n      );\n\n      const button = screen.getByRole('button');\n      fireEvent.click(button);\n      expect(screen.getByRole('textbox')).toHaveValue('');\n      expect(handleValueChange.mock.calls.length).toBe(0);\n    });\n\n    it('should not increment when button is disabled', async () => {\n      const handleValueChange = vi.fn();\n      await render(\n        <NumberField.Root defaultValue={0} onValueChange={handleValueChange}>\n          <NumberField.Increment disabled />\n          <NumberField.Input />\n        </NumberField.Root>,\n      );\n      const input = screen.getByRole('textbox');\n      const button = screen.getByRole('button');\n      expect(button).toHaveAttribute('disabled');\n      expect(input).toHaveValue('0');\n\n      fireEvent.pointerDown(button);\n      expect(handleValueChange.mock.calls.length).toBe(0);\n      expect(input).toHaveValue('0');\n    });\n\n    describe('prop: className', () => {\n      it('when root is disabled', async () => {\n        const classNameSpy = vi.fn();\n        await render(\n          <NumberField.Root disabled>\n            <NumberField.Increment className={classNameSpy} />\n            <NumberField.Input />\n          </NumberField.Root>,\n        );\n\n        expect(classNameSpy.mock.lastCall?.[0]).toHaveProperty('disabled', true);\n      });\n\n      it('when button is disabled', async () => {\n        const classNameSpy = vi.fn();\n        await render(\n          <NumberField.Root>\n            <NumberField.Increment disabled className={classNameSpy} />\n            <NumberField.Input />\n          </NumberField.Root>,\n        );\n\n        expect(classNameSpy.mock.lastCall?.[0]).toHaveProperty('disabled', true);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/number-field/increment/NumberFieldIncrement.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport type { BaseUIComponentProps, NativeButtonProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useButton } from '../../use-button';\nimport { useNumberFieldRootContext } from '../root/NumberFieldRootContext';\nimport { useNumberFieldButton } from '../root/useNumberFieldButton';\nimport type { NumberFieldRootState } from '../root/NumberFieldRoot';\nimport { stateAttributesMapping } from '../utils/stateAttributesMapping';\n\n/**\n * A stepper button that increases the field value when clicked.\n * Renders an `<button>` element.\n *\n * Documentation: [Base UI Number Field](https://base-ui.com/react/components/number-field)\n */\nexport const NumberFieldIncrement = React.forwardRef(function NumberFieldIncrement(\n  componentProps: NumberFieldIncrement.Props,\n  forwardedRef: React.ForwardedRef<HTMLButtonElement>,\n) {\n  const {\n    render,\n    className,\n    disabled: disabledProp = false,\n    nativeButton = true,\n    ...elementProps\n  } = componentProps;\n\n  const {\n    allowInputSyncRef,\n    disabled: contextDisabled,\n    formatOptionsRef,\n    getStepAmount,\n    id,\n    incrementValue,\n    inputRef,\n    inputValue,\n    locale,\n    maxWithDefault,\n    readOnly,\n    setValue,\n    state,\n    value,\n    valueRef,\n    lastChangedValueRef,\n    onValueCommitted,\n  } = useNumberFieldRootContext();\n\n  const isMax = value != null && value >= maxWithDefault;\n  const disabled = disabledProp || contextDisabled || isMax;\n\n  const props = useNumberFieldButton({\n    isIncrement: true,\n    inputRef,\n    inputValue,\n    disabled,\n    readOnly,\n    id,\n    setValue,\n    getStepAmount,\n    incrementValue,\n    allowInputSyncRef,\n    formatOptionsRef,\n    valueRef,\n    locale,\n    lastChangedValueRef,\n    onValueCommitted,\n  });\n\n  const { getButtonProps, buttonRef } = useButton({\n    disabled,\n    native: nativeButton,\n    focusableWhenDisabled: true,\n  });\n\n  const buttonState = React.useMemo(\n    () => ({\n      ...state,\n      disabled,\n    }),\n    [state, disabled],\n  );\n\n  const element = useRenderElement('button', componentProps, {\n    ref: [forwardedRef, buttonRef],\n    state: buttonState,\n    props: [props, elementProps, getButtonProps],\n    stateAttributesMapping,\n  });\n\n  return element;\n});\n\nexport interface NumberFieldIncrementState extends NumberFieldRootState {}\n\nexport interface NumberFieldIncrementProps\n  extends NativeButtonProps, BaseUIComponentProps<'button', NumberFieldIncrementState> {}\n\nexport namespace NumberFieldIncrement {\n  export type State = NumberFieldIncrementState;\n  export type Props = NumberFieldIncrementProps;\n}\n"
  },
  {
    "path": "packages/react/src/number-field/increment/NumberFieldIncrementDataAttributes.ts",
    "content": "export enum NumberFieldIncrementDataAttributes {\n  /**\n   * Present while scrubbing.\n   */\n  scrubbing = 'data-scrubbing',\n  /**\n   * Present when the number field is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the number field is readonly.\n   */\n  readonly = 'data-readonly',\n  /**\n   * Present when the number field is required.\n   */\n  required = 'data-required',\n  /**\n   * Present when the number field is in valid state (when wrapped in Field.Root).\n   */\n  valid = 'data-valid',\n  /**\n   * Present when the number field is in invalid state (when wrapped in Field.Root).\n   */\n  invalid = 'data-invalid',\n  /**\n   * Present when the number field has been touched (when wrapped in Field.Root).\n   */\n  touched = 'data-touched',\n  /**\n   * Present when the number field's value has changed (when wrapped in Field.Root).\n   */\n  dirty = 'data-dirty',\n  /**\n   * Present when the number field is filled (when wrapped in Field.Root).\n   */\n  filled = 'data-filled',\n  /**\n   * Present when the number field is focused (when wrapped in Field.Root).\n   */\n  focused = 'data-focused',\n}\n"
  },
  {
    "path": "packages/react/src/number-field/index.parts.ts",
    "content": "export { NumberFieldRoot as Root } from './root/NumberFieldRoot';\nexport { NumberFieldGroup as Group } from './group/NumberFieldGroup';\nexport { NumberFieldIncrement as Increment } from './increment/NumberFieldIncrement';\nexport { NumberFieldDecrement as Decrement } from './decrement/NumberFieldDecrement';\nexport { NumberFieldInput as Input } from './input/NumberFieldInput';\nexport { NumberFieldScrubArea as ScrubArea } from './scrub-area/NumberFieldScrubArea';\nexport { NumberFieldScrubAreaCursor as ScrubAreaCursor } from './scrub-area-cursor/NumberFieldScrubAreaCursor';\n"
  },
  {
    "path": "packages/react/src/number-field/index.ts",
    "content": "export * as NumberField from './index.parts';\n\nexport type * from './root/NumberFieldRoot';\nexport type * from './group/NumberFieldGroup';\nexport type * from './increment/NumberFieldIncrement';\nexport type * from './decrement/NumberFieldDecrement';\nexport type * from './input/NumberFieldInput';\nexport type * from './scrub-area/NumberFieldScrubArea';\nexport type * from './scrub-area-cursor/NumberFieldScrubAreaCursor';\n"
  },
  {
    "path": "packages/react/src/number-field/input/NumberFieldInput.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { act, screen, fireEvent } from '@mui/internal-test-utils';\nimport { NumberField } from '@base-ui/react/number-field';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<NumberField.Input />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<NumberField.Input />, () => ({\n    refInstanceof: window.HTMLInputElement,\n    render(node) {\n      return render(<NumberField.Root>{node}</NumberField.Root>);\n    },\n  }));\n\n  it('has textbox role', async () => {\n    await render(\n      <NumberField.Root>\n        <NumberField.Input />\n      </NumberField.Root>,\n    );\n    expect(screen.queryByRole('textbox')).not.toBe(null);\n  });\n\n  it('should not allow non-numeric characters on change', async () => {\n    await render(\n      <NumberField.Root>\n        <NumberField.Input />\n      </NumberField.Root>,\n    );\n    const input = screen.getByRole('textbox');\n    await act(async () => input.focus());\n    fireEvent.change(input, { target: { value: 'abc' } });\n    expect(input).toHaveValue('');\n  });\n\n  it('should not allow non-numeric characters on keydown', async () => {\n    await render(\n      <NumberField.Root>\n        <NumberField.Input />\n      </NumberField.Root>,\n    );\n    const input = screen.getByRole('textbox');\n    await act(async () => input.focus());\n    fireEvent.keyDown(input, { key: 'a' });\n    expect(input).toHaveValue('');\n  });\n\n  it('should allow numeric characters on change', async () => {\n    await render(\n      <NumberField.Root>\n        <NumberField.Input />\n      </NumberField.Root>,\n    );\n    const input = screen.getByRole('textbox');\n    await act(async () => input.focus());\n    fireEvent.change(input, { target: { value: '123' } });\n    expect(input).toHaveValue('123');\n  });\n\n  it('should increment on keydown ArrowUp', async () => {\n    await render(\n      <NumberField.Root defaultValue={0}>\n        <NumberField.Input />\n      </NumberField.Root>,\n    );\n    const input = screen.getByRole('textbox');\n    await act(async () => input.focus());\n    fireEvent.keyDown(input, { key: 'ArrowUp' });\n    expect(input).toHaveValue('1');\n  });\n\n  it('should decrement on keydown ArrowDown', async () => {\n    await render(\n      <NumberField.Root defaultValue={0}>\n        <NumberField.Input />\n      </NumberField.Root>,\n    );\n    const input = screen.getByRole('textbox');\n    await act(async () => input.focus());\n    fireEvent.keyDown(input, { key: 'ArrowDown' });\n    expect(input).toHaveValue('-1');\n  });\n\n  it('should increment to min on keydown Home', async () => {\n    await render(\n      <NumberField.Root min={-10} max={10}>\n        <NumberField.Input />\n      </NumberField.Root>,\n    );\n    const input = screen.getByRole('textbox');\n    await act(async () => input.focus());\n    fireEvent.keyDown(input, { key: 'Home' });\n    expect(input).toHaveValue('-10');\n  });\n\n  it('should decrement to max on keydown End', async () => {\n    await render(\n      <NumberField.Root min={-10} max={10}>\n        <NumberField.Input />\n      </NumberField.Root>,\n    );\n    const input = screen.getByRole('textbox');\n    await act(async () => input.focus());\n    fireEvent.keyDown(input, { key: 'End' });\n    expect(input).toHaveValue('10');\n  });\n\n  it('allows unicode plus/minus, permille and fullwidth digits on keydown when formatted as percent', async () => {\n    await render(\n      <NumberField.Root format={{ style: 'percent' }}>\n        <NumberField.Input />\n      </NumberField.Root>,\n    );\n\n    const input = screen.getByRole('textbox');\n    await act(async () => input.focus());\n\n    function dispatchKey(key: string) {\n      const evt = new window.KeyboardEvent('keydown', { key, bubbles: true, cancelable: true });\n      return input.dispatchEvent(evt);\n    }\n\n    expect(dispatchKey('−')).toBe(true); // MINUS SIGN U+2212\n    expect(dispatchKey('＋')).toBe(true); // FULLWIDTH PLUS SIGN U+FF0B\n    expect(dispatchKey('‰')).toBe(true);\n    expect(dispatchKey('１')).toBe(true);\n  });\n\n  it('blocks percent and permille symbols on keydown when not formatted as percent', async () => {\n    await render(\n      <NumberField.Root>\n        <NumberField.Input />\n      </NumberField.Root>,\n    );\n\n    const input = screen.getByRole('textbox');\n    await act(async () => input.focus());\n\n    function dispatchKey(key: string) {\n      const evt = new window.KeyboardEvent('keydown', { key, bubbles: true, cancelable: true });\n      return input.dispatchEvent(evt);\n    }\n\n    expect(dispatchKey('%')).toBe(false);\n    expect(dispatchKey('‰')).toBe(false);\n  });\n\n  it('applies locale-aware decimal/group gating (de-DE)', async () => {\n    await render(\n      <NumberField.Root locale=\"de-DE\">\n        <NumberField.Input />\n      </NumberField.Root>,\n    );\n\n    const input = screen.getByRole('textbox');\n    await act(async () => input.focus());\n\n    const dispatchKey = (key: string) => {\n      const evt = new window.KeyboardEvent('keydown', { key, bubbles: true, cancelable: true });\n      return input.dispatchEvent(evt);\n    };\n\n    // de-DE: decimal is ',' and group is '.'\n    // First comma is allowed\n    expect(dispatchKey(',')).toBe(true);\n    // Simulate a typical user value with a digit before decimal to let change handler accept it\n    fireEvent.change(input, { target: { value: '1,' } });\n    expect(input).toHaveValue('1,');\n\n    // Second comma should be blocked\n    expect(dispatchKey(',')).toBe(false);\n\n    // Grouping '.' should be allowed multiple times\n    expect(dispatchKey('.')).toBe(true);\n    fireEvent.change(input, { target: { value: '1.,' } });\n    expect(dispatchKey('.')).toBe(true);\n  });\n\n  it('allows space key when locale uses space-like grouping (pl-PL)', async () => {\n    await render(\n      <NumberField.Root locale=\"pl-PL\">\n        <NumberField.Input />\n      </NumberField.Root>,\n    );\n\n    const input = screen.getByRole('textbox');\n    await act(async () => input.focus());\n\n    const dispatchKey = (key: string) => {\n      const evt = new window.KeyboardEvent('keydown', { key, bubbles: true, cancelable: true });\n      return input.dispatchEvent(evt);\n    };\n\n    // pl-PL grouping is a space-like character; typing plain space from keyboard should be allowed\n    expect(dispatchKey(' ')).toBe(true);\n\n    // Simulate a typical user value using a regular space as group\n    fireEvent.change(input, { target: { value: '1 234' } });\n    expect(input).toHaveValue('1 234');\n  });\n\n  it('commits formatted value only on blur', async () => {\n    await render(\n      <NumberField.Root>\n        <NumberField.Input />\n      </NumberField.Root>,\n    );\n    const input = screen.getByRole('textbox');\n    await act(async () => input.focus());\n    fireEvent.change(input, { target: { value: '1234' } });\n    expect(input).toHaveValue('1234');\n    fireEvent.blur(input);\n    expect(input).toHaveValue((1234).toLocaleString());\n  });\n\n  it('should commit validated number on blur (min)', async () => {\n    await render(\n      <NumberField.Root min={0}>\n        <NumberField.Input />\n      </NumberField.Root>,\n    );\n    const input = screen.getByRole('textbox');\n    await act(async () => input.focus());\n    fireEvent.change(input, { target: { value: '-1' } });\n    expect(input).toHaveValue('-1');\n    fireEvent.blur(input);\n    expect(input).toHaveValue('0');\n  });\n\n  it('should commit validated number on blur (max)', async () => {\n    await render(\n      <NumberField.Root max={0}>\n        <NumberField.Input />\n      </NumberField.Root>,\n    );\n    const input = screen.getByRole('textbox');\n    await act(async () => input.focus());\n    fireEvent.change(input, { target: { value: '1' } });\n    expect(input).toHaveValue('1');\n    fireEvent.blur(input);\n    expect(input).toHaveValue('0');\n  });\n\n  it('should not snap number to step on blur', async () => {\n    await render(\n      <NumberField.Root step={0.5} snapOnStep>\n        <NumberField.Input />\n      </NumberField.Root>,\n    );\n    const input = screen.getByRole('textbox');\n    await act(async () => input.focus());\n    fireEvent.change(input, { target: { value: '1.5' } });\n    expect(input).toHaveValue('1.5');\n    fireEvent.blur(input);\n    expect(input).toHaveValue((1.5).toLocaleString());\n  });\n\n  it('should commit validated number on blur (step and min)', async () => {\n    await render(\n      <NumberField.Root min={2} step={2}>\n        <NumberField.Input />\n      </NumberField.Root>,\n    );\n    const input = screen.getByRole('textbox');\n    await act(async () => input.focus());\n    fireEvent.change(input, { target: { value: '3' } });\n    expect(input).toHaveValue('3');\n    fireEvent.blur(input);\n    expect(input).toHaveValue('3');\n  });\n\n  it('should preserve full precision on first blur after external value change', async () => {\n    const onValueChange = vi.fn();\n\n    function Controlled(props: { value: number | null }) {\n      return (\n        <NumberField.Root value={props.value} onValueChange={onValueChange}>\n          <NumberField.Input />\n        </NumberField.Root>\n      );\n    }\n\n    const { setProps } = await render(<Controlled value={null} />);\n    const input = screen.getByRole('textbox');\n\n    await act(async () => {\n      setProps({ value: 1.23456 });\n    });\n\n    expect(input).toHaveValue((1.23456).toLocaleString(undefined, { minimumFractionDigits: 5 }));\n\n    await act(async () => {\n      input.focus();\n      input.blur();\n    });\n\n    expect(input).toHaveValue((1.23456).toLocaleString(undefined, { minimumFractionDigits: 5 }));\n    expect(onValueChange.mock.calls.length).toBe(0);\n  });\n\n  it('should update input value after increment/decrement followed by external value change', async () => {\n    const onValueChange = vi.fn();\n\n    function Controlled() {\n      const [value, setValue] = React.useState<number | null>(0);\n      return (\n        <NumberField.Root\n          value={value}\n          onValueChange={(val) => {\n            onValueChange(val);\n            setValue(val);\n          }}\n        >\n          <NumberField.Input />\n          <NumberField.Increment />\n          <NumberField.Decrement />\n          <button onClick={() => setValue(1.23456)}>external</button>\n        </NumberField.Root>\n      );\n    }\n\n    const { user } = await render(<Controlled />);\n    const input = screen.getByRole('textbox');\n    const incrementButton = screen.getByLabelText('Increase');\n\n    expect(input).toHaveValue('0');\n\n    await user.click(incrementButton);\n\n    expect(input).toHaveValue('1');\n    expect(onValueChange.mock.calls.length).toBe(1);\n\n    await user.click(screen.getByText('external'));\n\n    expect(input).toHaveValue((1.23456).toLocaleString(undefined, { minimumFractionDigits: 5 }));\n  });\n\n  it('should update input value after decrement followed by external value change', async () => {\n    const onValueChange = vi.fn();\n\n    function Controlled() {\n      const [value, setValue] = React.useState<number | null>(5);\n      return (\n        <NumberField.Root\n          value={value}\n          onValueChange={(val) => {\n            onValueChange(val);\n            setValue(val);\n          }}\n        >\n          <NumberField.Input />\n          <NumberField.Increment />\n          <NumberField.Decrement />\n          <button onClick={() => setValue(2.98765)}>external</button>\n        </NumberField.Root>\n      );\n    }\n\n    const { user } = await render(<Controlled />);\n    const input = screen.getByRole('textbox');\n    const decrementButton = screen.getByLabelText('Decrease');\n\n    expect(input).toHaveValue('5');\n\n    await user.click(decrementButton);\n\n    expect(input).toHaveValue('4');\n    expect(onValueChange.mock.calls.length).toBe(1);\n\n    await user.click(screen.getByText('external'));\n\n    expect(input).toHaveValue((2.98765).toLocaleString(undefined, { minimumFractionDigits: 5 }));\n  });\n\n  it('should allow typing after precision is preserved on blur', async () => {\n    const onValueChange = vi.fn();\n\n    function Controlled(props: { value: number | null }) {\n      return (\n        <NumberField.Root value={props.value} onValueChange={onValueChange}>\n          <NumberField.Input />\n        </NumberField.Root>\n      );\n    }\n\n    const { setProps, user } = await render(<Controlled value={null} />);\n    const input = screen.getByRole('textbox');\n\n    await act(async () => {\n      setProps({ value: 1.23456 });\n    });\n\n    expect(input).toHaveValue((1.23456).toLocaleString(undefined, { minimumFractionDigits: 5 }));\n\n    await act(async () => {\n      input.focus();\n      input.blur();\n    });\n\n    expect(input).toHaveValue((1.23456).toLocaleString(undefined, { minimumFractionDigits: 5 }));\n\n    await act(async () => {\n      input.focus();\n    });\n\n    await user.clear(input);\n    await user.keyboard('1.234567');\n    expect(input).toHaveValue('1.234567');\n\n    fireEvent.blur(input);\n    expect(input).toHaveValue((1.23456).toLocaleString(undefined, { minimumFractionDigits: 5 }));\n  });\n\n  it('should format to canonical representation when input differs from max precision', async () => {\n    const onValueChange = vi.fn();\n\n    function Controlled(props: { value: number | null }) {\n      return (\n        <NumberField.Root value={props.value} onValueChange={onValueChange}>\n          <NumberField.Input />\n        </NumberField.Root>\n      );\n    }\n\n    const { setProps, user } = await render(<Controlled value={null} />);\n    const input = screen.getByRole('textbox');\n\n    await act(async () => {\n      setProps({ value: 1.23456 });\n    });\n\n    expect(input).toHaveValue((1.23456).toLocaleString(undefined, { minimumFractionDigits: 5 }));\n\n    await act(async () => {\n      input.focus();\n    });\n\n    await user.clear(input);\n    await user.keyboard('1.23456000');\n    expect(input).toHaveValue('1.23456000');\n\n    fireEvent.blur(input);\n    expect(input).toHaveValue((1.23456).toLocaleString(undefined, { minimumFractionDigits: 5 }));\n  });\n\n  it('should handle multiple blur cycles with precision preservation', async () => {\n    const onValueChange = vi.fn();\n\n    function Controlled(props: { value: number | null }) {\n      return (\n        <NumberField.Root value={props.value} onValueChange={onValueChange}>\n          <NumberField.Input />\n        </NumberField.Root>\n      );\n    }\n\n    const { setProps } = await render(<Controlled value={null} />);\n    const input = screen.getByRole('textbox');\n\n    await act(async () => {\n      setProps({ value: 1.23456789 });\n    });\n\n    expect(input).toHaveValue((1.23456789).toLocaleString(undefined, { minimumFractionDigits: 8 }));\n\n    await act(async () => {\n      input.focus();\n      input.blur();\n    });\n\n    expect(input).toHaveValue((1.23456789).toLocaleString(undefined, { minimumFractionDigits: 8 }));\n    expect(onValueChange.mock.calls.length).toBe(0);\n\n    await act(async () => {\n      input.focus();\n      input.blur();\n    });\n\n    expect(input).toHaveValue((1.23456789).toLocaleString(undefined, { minimumFractionDigits: 8 }));\n    expect(onValueChange.mock.calls.length).toBe(0);\n  });\n\n  it('should handle edge case where parsed value equals current value but input differs', async () => {\n    const onValueChange = vi.fn();\n\n    function Controlled(props: { value: number | null }) {\n      return (\n        <NumberField.Root value={props.value} onValueChange={onValueChange}>\n          <NumberField.Input />\n        </NumberField.Root>\n      );\n    }\n\n    const { setProps, user } = await render(<Controlled value={null} />);\n    const input = screen.getByRole('textbox');\n\n    await act(async () => {\n      setProps({ value: 1.5 });\n    });\n\n    expect(input).toHaveValue((1.5).toLocaleString());\n\n    await act(async () => {\n      input.focus();\n    });\n\n    await user.clear(input);\n    await user.keyboard('1.50');\n    expect(input).toHaveValue('1.50');\n\n    fireEvent.blur(input);\n    expect((input as HTMLInputElement).value).toMatch(/^1[.,]5/);\n  });\n\n  it('should preserve precision when value matches max precision after external change during typing', async () => {\n    const onValueChange = vi.fn();\n\n    function Controlled() {\n      const [value, setValue] = React.useState<number | null>(null);\n      return (\n        <NumberField.Root\n          value={value}\n          onValueChange={(val) => {\n            onValueChange(val);\n            setValue(val);\n          }}\n        >\n          <NumberField.Input />\n          <button onClick={() => setValue(3.14159265)}>set pi</button>\n        </NumberField.Root>\n      );\n    }\n\n    const { user } = await render(<Controlled />);\n    const input = screen.getByRole('textbox');\n\n    await act(async () => {\n      input.focus();\n    });\n\n    await user.keyboard('2.7');\n    expect(input).toHaveValue('2.7');\n\n    await user.click(screen.getByText('set pi'));\n\n    expect(input).toHaveValue((3.14159265).toLocaleString(undefined, { minimumFractionDigits: 8 }));\n\n    fireEvent.blur(input);\n    expect(input).toHaveValue((3.14159265).toLocaleString(undefined, { minimumFractionDigits: 8 }));\n  });\n\n  it('should round to explicit maximumFractionDigits on blur', async () => {\n    const onValueChange = vi.fn();\n\n    function Controlled(props: { value: number | null }) {\n      return (\n        <NumberField.Root\n          value={props.value}\n          onValueChange={onValueChange}\n          format={{ maximumFractionDigits: 2 }}\n        >\n          <NumberField.Input />\n        </NumberField.Root>\n      );\n    }\n\n    const { setProps } = await render(<Controlled value={null} />);\n    const input = screen.getByRole('textbox');\n\n    await act(async () => {\n      setProps({ value: 1.23456 });\n    });\n\n    expect(input).toHaveValue((1.23).toLocaleString());\n\n    await act(async () => {\n      input.focus();\n      input.blur();\n    });\n\n    expect(input).toHaveValue((1.23).toLocaleString());\n    expect(onValueChange.mock.calls.length).toBe(1);\n    expect(onValueChange.mock.calls[0][0]).toBe(1.23);\n  });\n\n  it('should round to step precision on blur when step implies precision constraints', async () => {\n    const onValueChange = vi.fn();\n\n    function Controlled() {\n      const [value, setValue] = React.useState<number | null>(null);\n      return (\n        <NumberField.Root\n          value={value}\n          onValueChange={(val) => {\n            onValueChange(val);\n            setValue(val);\n          }}\n          step={0.01}\n        >\n          <NumberField.Input />\n        </NumberField.Root>\n      );\n    }\n\n    const { user } = await render(<Controlled />);\n    const input = screen.getByRole('textbox');\n\n    await act(async () => {\n      input.focus();\n    });\n\n    await user.keyboard('1.23456');\n    expect(input).toHaveValue('1.23456');\n\n    // The stored value should be the full precision value\n    const valueBeforeBlur = onValueChange.mock.lastCall?.[0];\n    // The value gets processed through removeFloatingPointErrors during validation\n    // which applies some default precision constraints\n    expect(valueBeforeBlur).toBe(1.235);\n\n    const callCountBeforeBlur = onValueChange.mock.calls.length;\n\n    fireEvent.blur(input);\n\n    // Without explicit precision formatting, the behavior depends on the step\n    // The current implementation preserves full precision until it differs from canonical\n    expect(input).toHaveValue((1.235).toLocaleString(undefined, { minimumFractionDigits: 3 }));\n    expect(onValueChange.mock.calls.length).toBe(callCountBeforeBlur + 1);\n  });\n\n  it('commits parsed value on blur and normalizes display for fr-FR', async () => {\n    const onValueChange = vi.fn();\n\n    await render(\n      <NumberField.Root locale=\"fr-FR\" onValueChange={onValueChange}>\n        <NumberField.Input />\n      </NumberField.Root>,\n    );\n\n    const input = screen.getByRole<HTMLInputElement>('textbox');\n    await act(async () => input.focus());\n\n    fireEvent.change(input, { target: { value: '1234,5' } });\n    expect(input).toHaveValue('1234,5');\n\n    fireEvent.blur(input);\n\n    expect(onValueChange.mock.calls.length).toBe(1);\n    expect(onValueChange.mock.calls[0][0]).toBe(1234.5);\n\n    expect(input.value).toBe((1234.5).toLocaleString('fr-FR'));\n  });\n});\n"
  },
  {
    "path": "packages/react/src/number-field/input/NumberFieldInput.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { stopEvent } from '../../floating-ui-react/utils';\nimport { useNumberFieldRootContext } from '../root/NumberFieldRootContext';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useFieldRootContext } from '../../field/root/FieldRootContext';\nimport { fieldValidityMapping } from '../../field/utils/constants';\nimport { useField } from '../../field/useField';\nimport { useFormContext } from '../../form/FormContext';\nimport { useLabelableContext } from '../../labelable-provider/LabelableContext';\nimport { DEFAULT_STEP } from '../utils/constants';\nimport {\n  ARABIC_DETECT_RE,\n  PERSIAN_DETECT_RE,\n  HAN_DETECT_RE,\n  FULLWIDTH_DETECT_RE,\n  getNumberLocaleDetails,\n  parseNumber,\n  ANY_MINUS_RE,\n  ANY_PLUS_RE,\n  ANY_MINUS_DETECT_RE,\n  ANY_PLUS_DETECT_RE,\n} from '../utils/parse';\nimport type { NumberFieldRootState } from '../root/NumberFieldRoot';\nimport { stateAttributesMapping as numberFieldStateAttributesMapping } from '../utils/stateAttributesMapping';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport {\n  createChangeEventDetails,\n  createGenericEventDetails,\n} from '../../utils/createBaseUIEventDetails';\nimport { formatNumber, formatNumberMaxPrecision } from '../../utils/formatNumber';\nimport { useValueChanged } from '../../utils/useValueChanged';\nimport { REASONS } from '../../utils/reasons';\n\nconst stateAttributesMapping = {\n  ...fieldValidityMapping,\n  ...numberFieldStateAttributesMapping,\n};\n\nconst NAVIGATE_KEYS = new Set([\n  'Backspace',\n  'Delete',\n  'ArrowLeft',\n  'ArrowRight',\n  'Tab',\n  'Enter',\n  'Escape',\n]);\n\n/**\n * The native input control in the number field.\n * Renders an `<input>` element.\n *\n * Documentation: [Base UI Number Field](https://base-ui.com/react/components/number-field)\n */\nexport const NumberFieldInput = React.forwardRef(function NumberFieldInput(\n  componentProps: NumberFieldInput.Props,\n  forwardedRef: React.ForwardedRef<HTMLInputElement>,\n) {\n  const { render, className, ...elementProps } = componentProps;\n\n  const {\n    allowInputSyncRef,\n    disabled,\n    formatOptionsRef,\n    getAllowedNonNumericKeys,\n    getStepAmount,\n    id,\n    incrementValue,\n    inputMode,\n    inputValue,\n    max,\n    min,\n    name,\n    readOnly,\n    required,\n    setValue,\n    state,\n    setInputValue,\n    locale,\n    inputRef,\n    value,\n    onValueCommitted,\n    lastChangedValueRef,\n    hasPendingCommitRef,\n    valueRef,\n  } = useNumberFieldRootContext();\n\n  const { clearErrors } = useFormContext();\n  const { validationMode, setTouched, setFocused, invalid, shouldValidateOnChange, validation } =\n    useFieldRootContext();\n  const { labelId } = useLabelableContext();\n\n  const hasTouchedInputRef = React.useRef(false);\n  const blockRevalidationRef = React.useRef(false);\n\n  useField({\n    id,\n    commit: validation.commit,\n    value,\n    controlRef: inputRef,\n    name,\n    getValue: () => value ?? null,\n  });\n\n  useValueChanged(value, (previousValue) => {\n    const validateOnChange = shouldValidateOnChange();\n\n    clearErrors(name);\n\n    if (validateOnChange) {\n      validation.commit(value);\n    }\n\n    if (previousValue === value || validateOnChange) {\n      return;\n    }\n\n    if (blockRevalidationRef.current) {\n      blockRevalidationRef.current = false;\n      return;\n    }\n\n    validation.commit(value, true);\n  });\n\n  const inputProps: React.ComponentProps<'input'> = {\n    id,\n    required,\n    disabled,\n    readOnly,\n    inputMode,\n    value: inputValue,\n    type: 'text',\n    autoComplete: 'off',\n    autoCorrect: 'off',\n    spellCheck: 'false',\n    'aria-roledescription': 'Number field',\n    'aria-invalid': invalid || undefined,\n    'aria-labelledby': labelId,\n    // If the server's locale does not match the client's locale, the formatting may not match,\n    // causing a hydration mismatch.\n    suppressHydrationWarning: true,\n    onFocus(event) {\n      if (event.defaultPrevented || readOnly || disabled) {\n        return;\n      }\n\n      setFocused(true);\n\n      if (hasTouchedInputRef.current) {\n        return;\n      }\n\n      hasTouchedInputRef.current = true;\n\n      // Browsers set selection at the start of the input field by default. We want to set it at\n      // the end for the first focus.\n      const target = event.currentTarget;\n      const length = target.value.length;\n      target.setSelectionRange(length, length);\n    },\n    onBlur(event) {\n      if (event.defaultPrevented || readOnly || disabled) {\n        return;\n      }\n\n      setTouched(true);\n      setFocused(false);\n\n      const hadManualInput = !allowInputSyncRef.current;\n      const hadPendingProgrammaticChange = hasPendingCommitRef.current;\n\n      allowInputSyncRef.current = true;\n\n      if (inputValue.trim() === '') {\n        setValue(null, createChangeEventDetails(REASONS.inputClear, event.nativeEvent));\n        if (validationMode === 'onBlur') {\n          validation.commit(null);\n        }\n        onValueCommitted(null, createGenericEventDetails(REASONS.inputClear, event.nativeEvent));\n        return;\n      }\n\n      const formatOptions = formatOptionsRef.current;\n      const parsedValue = parseNumber(inputValue, locale, formatOptions);\n      if (parsedValue === null) {\n        return;\n      }\n\n      // If an explicit precision is requested, round the committed numeric value.\n      const hasExplicitPrecision =\n        formatOptions?.maximumFractionDigits != null ||\n        formatOptions?.minimumFractionDigits != null;\n\n      const maxFrac = formatOptions?.maximumFractionDigits;\n      const committed =\n        hasExplicitPrecision && typeof maxFrac === 'number'\n          ? Number(parsedValue.toFixed(maxFrac))\n          : parsedValue;\n\n      const nextEventDetails = createGenericEventDetails(REASONS.inputBlur, event.nativeEvent);\n      const shouldUpdateValue = value !== committed;\n      const shouldCommit = hadManualInput || shouldUpdateValue || hadPendingProgrammaticChange;\n\n      if (validationMode === 'onBlur') {\n        validation.commit(committed);\n      }\n      if (shouldUpdateValue) {\n        blockRevalidationRef.current = true;\n        setValue(committed, createChangeEventDetails(REASONS.inputBlur, event.nativeEvent));\n      }\n      if (shouldCommit) {\n        onValueCommitted(committed, nextEventDetails);\n      }\n\n      // Normalize only the displayed text\n      const canonicalText = formatNumber(committed, locale, formatOptions);\n      const maxPrecisionText = formatNumberMaxPrecision(parsedValue, locale, formatOptions);\n      const shouldPreserveFullPrecision =\n        !hasExplicitPrecision && parsedValue === value && inputValue === maxPrecisionText;\n\n      if (!shouldPreserveFullPrecision && inputValue !== canonicalText) {\n        setInputValue(canonicalText);\n      }\n    },\n    onChange(event) {\n      // Workaround for https://github.com/facebook/react/issues/9023\n      if (event.nativeEvent.defaultPrevented) {\n        return;\n      }\n\n      allowInputSyncRef.current = false;\n      const targetValue = event.target.value;\n\n      if (targetValue.trim() === '') {\n        setInputValue(targetValue);\n        setValue(null, createChangeEventDetails(REASONS.inputClear, event.nativeEvent));\n        return;\n      }\n\n      // Update the input text immediately and only fire onValueChange if the typed value is\n      // currently parseable into a number. This preserves good UX for IME\n      // composition/partial input while still providing live numeric updates when possible.\n      const allowedNonNumericKeys = getAllowedNonNumericKeys();\n      const isValidCharacterString = Array.from(targetValue).every((ch) => {\n        const isAsciiDigit = ch >= '0' && ch <= '9';\n        const isArabicNumeral = ARABIC_DETECT_RE.test(ch);\n        const isHanNumeral = HAN_DETECT_RE.test(ch);\n        const isPersianNumeral = PERSIAN_DETECT_RE.test(ch);\n        const isFullwidthNumeral = FULLWIDTH_DETECT_RE.test(ch);\n        const isMinus = ANY_MINUS_DETECT_RE.test(ch);\n        return (\n          isAsciiDigit ||\n          isArabicNumeral ||\n          isHanNumeral ||\n          isPersianNumeral ||\n          isFullwidthNumeral ||\n          isMinus ||\n          allowedNonNumericKeys.has(ch)\n        );\n      });\n\n      if (!isValidCharacterString) {\n        return;\n      }\n\n      const parsedValue = parseNumber(targetValue, locale, formatOptionsRef.current);\n\n      setInputValue(targetValue);\n\n      if (parsedValue !== null) {\n        setValue(parsedValue, createChangeEventDetails(REASONS.inputChange, event.nativeEvent));\n      }\n    },\n    onKeyDown(event) {\n      if (event.defaultPrevented || readOnly || disabled) {\n        return;\n      }\n\n      const nativeEvent = event.nativeEvent;\n\n      allowInputSyncRef.current = true;\n\n      const allowedNonNumericKeys = getAllowedNonNumericKeys();\n\n      let isAllowedNonNumericKey = allowedNonNumericKeys.has(event.key);\n\n      const { decimal, currency, percentSign } = getNumberLocaleDetails(\n        locale,\n        formatOptionsRef.current,\n      );\n\n      const selectionStart = event.currentTarget.selectionStart;\n      const selectionEnd = event.currentTarget.selectionEnd;\n      const isAllSelected = selectionStart === 0 && selectionEnd === inputValue.length;\n\n      // Normalize handling of plus/minus signs via precomputed regexes\n      const selectionContainsIndex = (index: number) =>\n        selectionStart != null &&\n        selectionEnd != null &&\n        index >= selectionStart &&\n        index < selectionEnd;\n\n      if (\n        ANY_MINUS_DETECT_RE.test(event.key) &&\n        Array.from(allowedNonNumericKeys).some((k) => ANY_MINUS_DETECT_RE.test(k || ''))\n      ) {\n        // Only allow one sign unless replacing the existing one or all text is selected\n        const existingIndex = inputValue.search(ANY_MINUS_RE);\n        const isReplacingExisting =\n          existingIndex != null && existingIndex !== -1 && selectionContainsIndex(existingIndex);\n        isAllowedNonNumericKey =\n          !(ANY_MINUS_DETECT_RE.test(inputValue) || ANY_PLUS_DETECT_RE.test(inputValue)) ||\n          isAllSelected ||\n          isReplacingExisting;\n      }\n      if (\n        ANY_PLUS_DETECT_RE.test(event.key) &&\n        Array.from(allowedNonNumericKeys).some((k) => ANY_PLUS_DETECT_RE.test(k || ''))\n      ) {\n        const existingIndex = inputValue.search(ANY_PLUS_RE);\n        const isReplacingExisting =\n          existingIndex != null && existingIndex !== -1 && selectionContainsIndex(existingIndex);\n        isAllowedNonNumericKey =\n          !(ANY_MINUS_DETECT_RE.test(inputValue) || ANY_PLUS_DETECT_RE.test(inputValue)) ||\n          isAllSelected ||\n          isReplacingExisting;\n      }\n\n      // Only allow one of each symbol.\n      [decimal, currency, percentSign].forEach((symbol) => {\n        if (event.key === symbol) {\n          const symbolIndex = inputValue.indexOf(symbol);\n          const isSymbolHighlighted = selectionContainsIndex(symbolIndex);\n          isAllowedNonNumericKey =\n            !inputValue.includes(symbol) || isAllSelected || isSymbolHighlighted;\n        }\n      });\n\n      const isAsciiDigit = event.key >= '0' && event.key <= '9';\n      const isArabicNumeral = ARABIC_DETECT_RE.test(event.key);\n      const isHanNumeral = HAN_DETECT_RE.test(event.key);\n      const isFullwidthNumeral = FULLWIDTH_DETECT_RE.test(event.key);\n      const isNavigateKey = NAVIGATE_KEYS.has(event.key);\n\n      if (\n        // Allow composition events (e.g., pinyin)\n        // event.nativeEvent.isComposing does not work in Safari:\n        // https://bugs.webkit.org/show_bug.cgi?id=165004\n        event.which === 229 ||\n        event.altKey ||\n        event.ctrlKey ||\n        event.metaKey ||\n        isAllowedNonNumericKey ||\n        isAsciiDigit ||\n        isArabicNumeral ||\n        isFullwidthNumeral ||\n        isHanNumeral ||\n        isNavigateKey\n      ) {\n        return;\n      }\n\n      // We need to commit the number at this point if the input hasn't been blurred.\n      const parsedValue = parseNumber(inputValue, locale, formatOptionsRef.current);\n\n      const amount = getStepAmount(event) ?? DEFAULT_STEP;\n\n      // Prevent insertion of text or caret from moving.\n      stopEvent(event);\n\n      const commitDetails = createGenericEventDetails(REASONS.keyboard, nativeEvent);\n\n      if (event.key === 'ArrowUp') {\n        incrementValue(amount, {\n          direction: 1,\n          currentValue: parsedValue,\n          event: nativeEvent,\n          reason: REASONS.keyboard,\n        });\n        onValueCommitted(lastChangedValueRef.current ?? valueRef.current, commitDetails);\n      } else if (event.key === 'ArrowDown') {\n        incrementValue(amount, {\n          direction: -1,\n          currentValue: parsedValue,\n          event: nativeEvent,\n          reason: REASONS.keyboard,\n        });\n        onValueCommitted(lastChangedValueRef.current ?? valueRef.current, commitDetails);\n      } else if (event.key === 'Home' && min != null) {\n        setValue(min, createChangeEventDetails(REASONS.keyboard, nativeEvent));\n        onValueCommitted(lastChangedValueRef.current ?? valueRef.current, commitDetails);\n      } else if (event.key === 'End' && max != null) {\n        setValue(max, createChangeEventDetails(REASONS.keyboard, nativeEvent));\n        onValueCommitted(lastChangedValueRef.current ?? valueRef.current, commitDetails);\n      }\n    },\n    onPaste(event) {\n      if (event.defaultPrevented || readOnly || disabled) {\n        return;\n      }\n\n      // Prevent `onChange` from being called.\n      event.preventDefault();\n\n      const clipboardData = event.clipboardData || window.Clipboard;\n      const pastedData = clipboardData.getData('text/plain');\n      const parsedValue = parseNumber(pastedData, locale, formatOptionsRef.current);\n\n      if (parsedValue !== null) {\n        allowInputSyncRef.current = false;\n        setValue(parsedValue, createChangeEventDetails(REASONS.inputPaste, event.nativeEvent));\n        setInputValue(pastedData);\n      }\n    },\n  };\n\n  const element = useRenderElement('input', componentProps, {\n    ref: [forwardedRef, inputRef],\n    state,\n    props: [inputProps, validation.getValidationProps(), elementProps],\n    stateAttributesMapping,\n  });\n\n  return element;\n});\n\nexport interface NumberFieldInputState extends NumberFieldRootState {}\n\nexport interface NumberFieldInputProps extends BaseUIComponentProps<\n  'input',\n  NumberFieldInputState\n> {\n  /**\n   * A string value that provides a user-friendly name for the role of the input.\n   * @default 'Number field'\n   */\n  'aria-roledescription'?: React.AriaAttributes['aria-roledescription'] | undefined;\n}\n\nexport namespace NumberFieldInput {\n  export type State = NumberFieldInputState;\n  export type Props = NumberFieldInputProps;\n}\n"
  },
  {
    "path": "packages/react/src/number-field/input/NumberFieldInputDataAttributes.ts",
    "content": "export enum NumberFieldInputDataAttributes {\n  /**\n   * Present while scrubbing.\n   */\n  scrubbing = 'data-scrubbing',\n  /**\n   * Present when the number field is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the number field is readonly.\n   */\n  readonly = 'data-readonly',\n  /**\n   * Present when the number field is required.\n   */\n  required = 'data-required',\n  /**\n   * Present when the number field is in valid state (when wrapped in Field.Root).\n   */\n  valid = 'data-valid',\n  /**\n   * Present when the number field is in invalid state (when wrapped in Field.Root).\n   */\n  invalid = 'data-invalid',\n  /**\n   * Present when the number field has been touched (when wrapped in Field.Root).\n   */\n  touched = 'data-touched',\n  /**\n   * Present when the number field's value has changed (when wrapped in Field.Root).\n   */\n  dirty = 'data-dirty',\n  /**\n   * Present when the number field is filled (when wrapped in Field.Root).\n   */\n  filled = 'data-filled',\n  /**\n   * Present when the number field is focused (when wrapped in Field.Root).\n   */\n  focused = 'data-focused',\n}\n"
  },
  {
    "path": "packages/react/src/number-field/root/NumberFieldRoot.spec.tsx",
    "content": "import { expectType } from '#test-utils';\nimport { NumberField } from '@base-ui/react/number-field';\nimport { REASONS } from '../../utils/reasons';\n\ntype NumberFieldChangeHandler = NonNullable<NumberField.Root.Props['onValueChange']>;\ntype NumberFieldCommitHandler = NonNullable<NumberField.Root.Props['onValueCommitted']>;\n\ntype NumberFieldChangeDetails = Parameters<NumberFieldChangeHandler>[1];\ntype NumberFieldWheelEvent = Extract<\n  NumberFieldChangeDetails,\n  { reason: typeof REASONS.wheel }\n>['event'];\n\nexpectType<WheelEvent, NumberFieldWheelEvent>(null as unknown as NumberFieldWheelEvent);\n\nfunction assertNumberFieldChange(details: NumberFieldChangeDetails) {\n  if (details.reason === REASONS.wheel) {\n    const wheelEvent: WheelEvent = details.event;\n    void wheelEvent;\n    // @ts-expect-error wheel details should not expose pointer events\n    const pointerEvent: PointerEvent = details.event;\n    void pointerEvent;\n  }\n\n  if (details.reason === REASONS.incrementPress) {\n    const event: PointerEvent | MouseEvent | TouchEvent = details.event;\n    void event;\n  }\n\n  if (details.reason === REASONS.inputClear) {\n    const event: InputEvent | FocusEvent | Event = details.event;\n    void event;\n    // @ts-expect-error keyboard events are not emitted for input-clear\n    const keyboardEvent: KeyboardEvent = details.event;\n    void keyboardEvent;\n  }\n}\n\ntype NumberFieldCommitDetails = Parameters<NumberFieldCommitHandler>[1];\n\nfunction assertNumberFieldCommit(details: NumberFieldCommitDetails) {\n  if (details.reason === REASONS.wheel) {\n    const event: WheelEvent = details.event;\n    void event;\n  }\n\n  if (details.reason === REASONS.scrub) {\n    const event: PointerEvent = details.event;\n    void event;\n  }\n\n  if (details.reason === REASONS.inputClear) {\n    const event: InputEvent | FocusEvent | Event = details.event;\n    void event;\n  }\n}\n\nconst handleNumberFieldChange: NumberFieldChangeHandler = (value, details) => {\n  expectType<number | null, typeof value>(value);\n  assertNumberFieldChange(details);\n};\n\nconst handleNumberFieldCommit: NumberFieldCommitHandler = (value, details) => {\n  expectType<number | null, typeof value>(value);\n  assertNumberFieldCommit(details);\n};\n\nconst numberFieldEventNarrowing = (\n  <NumberField.Root\n    defaultValue={0}\n    onValueChange={handleNumberFieldChange}\n    onValueCommitted={handleNumberFieldCommit}\n  />\n);\n"
  },
  {
    "path": "packages/react/src/number-field/root/NumberFieldRoot.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { act, screen, fireEvent } from '@mui/internal-test-utils';\nimport { NumberField as NumberFieldBase } from '@base-ui/react/number-field';\nimport { Field } from '@base-ui/react/field';\nimport { Form } from '@base-ui/react/form';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\nimport { REASONS } from '../../utils/reasons';\n\ndescribe('<NumberField />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<NumberFieldBase.Root />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render,\n  }));\n\n  function NumberField(props: NumberFieldBase.Root.Props) {\n    return (\n      <NumberFieldBase.Root {...props}>\n        <NumberFieldBase.Group>\n          <NumberFieldBase.Input />\n          <NumberFieldBase.Increment />\n          <NumberFieldBase.Decrement />\n          <NumberFieldBase.ScrubArea />\n        </NumberFieldBase.Group>\n      </NumberFieldBase.Root>\n    );\n  }\n\n  describe('prop: defaultValue', () => {\n    it('should accept a number value', async () => {\n      await render(<NumberField defaultValue={1} />);\n      const input = screen.getByRole('textbox');\n      expect(input).toHaveValue('1');\n    });\n\n    it('should accept an `undefined` value', async () => {\n      await render(<NumberField />);\n      const input = screen.getByRole('textbox');\n      expect(input).toHaveValue('');\n    });\n  });\n\n  describe('prop: value', () => {\n    it('should accept a number value that can change over time', async () => {\n      const { rerender } = await render(<NumberField value={1} />);\n      const input = screen.getByRole('textbox');\n      expect(input).toHaveValue('1');\n      await rerender(<NumberField value={2} />);\n      expect(input).toHaveValue('2');\n    });\n\n    it('should accept an `undefined` value', async () => {\n      await render(<NumberField />);\n      const input = screen.getByRole('textbox');\n      expect(input).toHaveValue('');\n    });\n\n    it('should accept a `null` value', async () => {\n      await render(<NumberField value={null} />);\n      const input = screen.getByRole('textbox');\n      expect(input).toHaveValue('');\n    });\n\n    it('should be `null` when the input is empty but not trimmed', async () => {\n      const onValueChange = vi.fn();\n      await render(<NumberField value={1} onValueChange={onValueChange} />);\n      const input = screen.getByRole('textbox');\n      fireEvent.change(input, { target: { value: '  ' } });\n      expect(onValueChange.mock.calls[0][0]).toBe(null);\n    });\n  });\n\n  it('blocks submission when step mismatch occurs', async () => {\n    await render(\n      <form data-testid=\"form\">\n        <NumberFieldBase.Root name=\"quantity\" min={0} step={0.1}>\n          <NumberFieldBase.Group>\n            <NumberFieldBase.Input />\n          </NumberFieldBase.Group>\n        </NumberFieldBase.Root>\n        <button type=\"submit\">Submit</button>\n      </form>,\n    );\n\n    const input = screen.getByRole('textbox');\n    fireEvent.change(input, { target: { value: '0.11' } });\n\n    const hiddenInput = document.querySelector(\n      'input[type=\"number\"][name=\"quantity\"]',\n    ) as HTMLInputElement;\n    expect(hiddenInput).not.toBe(null);\n    expect(hiddenInput.validity.stepMismatch).toBe(true);\n\n    const form = screen.getByTestId<HTMLFormElement>('form');\n    expect(form.checkValidity()).toBe(false);\n  });\n\n  it.skipIf(isJSDOM)('blocks submission when step mismatch occurs with default step', async () => {\n    await render(\n      <form data-testid=\"form\">\n        <NumberFieldBase.Root name=\"quantity\" min={0}>\n          <NumberFieldBase.Group>\n            <NumberFieldBase.Input />\n          </NumberFieldBase.Group>\n        </NumberFieldBase.Root>\n        <button type=\"submit\">Submit</button>\n      </form>,\n    );\n\n    const input = screen.getByRole('textbox');\n    fireEvent.change(input, { target: { value: '0.11' } });\n\n    const hiddenInput = document.querySelector(\n      'input[type=\"number\"][name=\"quantity\"]',\n    ) as HTMLInputElement;\n    expect(hiddenInput).not.toBe(null);\n    expect(hiddenInput.validity.stepMismatch).toBe(true);\n\n    const form = screen.getByTestId<HTMLFormElement>('form');\n    expect(form.checkValidity()).toBe(false);\n  });\n\n  it('does not block submission when step=\"any\"', async () => {\n    await render(\n      <form data-testid=\"form\">\n        <NumberFieldBase.Root name=\"quantity\" min={0} step=\"any\">\n          <NumberFieldBase.Group>\n            <NumberFieldBase.Input />\n          </NumberFieldBase.Group>\n        </NumberFieldBase.Root>\n        <button type=\"submit\">Submit</button>\n      </form>,\n    );\n\n    const input = screen.getByRole('textbox');\n    fireEvent.change(input, { target: { value: '0.11' } });\n\n    const hiddenInput = document.querySelector(\n      'input[type=\"number\"][name=\"quantity\"]',\n    ) as HTMLInputElement;\n    expect(hiddenInput).not.toBe(null);\n    expect(hiddenInput.validity.stepMismatch).toBe(false);\n\n    const form = screen.getByTestId<HTMLFormElement>('form');\n    expect(form.checkValidity()).toBe(true);\n  });\n\n  describe('prop: onValueChange', () => {\n    it('should be called when the value changes', async () => {\n      const onValueChange = vi.fn();\n      function App() {\n        const [value, setValue] = React.useState<number | null>(1);\n        return (\n          <NumberField\n            value={value}\n            onValueChange={(val) => {\n              onValueChange(val);\n              setValue(val);\n            }}\n          />\n        );\n      }\n      await render(<App />);\n      const input = screen.getByRole('textbox');\n      fireEvent.change(input, { target: { value: '2' } });\n      expect(onValueChange.mock.calls.length).toBe(1);\n      expect(onValueChange.mock.calls[0][0]).toBe(2);\n    });\n\n    it('should be called with a number when transitioning from `null`', async () => {\n      const onValueChange = vi.fn();\n      function App() {\n        const [value, setValue] = React.useState<number | null>(null);\n        return (\n          <NumberField\n            value={value}\n            onValueChange={(val) => {\n              onValueChange(val);\n              setValue(val);\n            }}\n          />\n        );\n      }\n      await render(<App />);\n      const input = screen.getByRole('textbox');\n      fireEvent.change(input, { target: { value: '5' } });\n      expect(onValueChange.mock.calls.length).toBe(1);\n      expect(onValueChange.mock.calls[0][0]).toBe(5);\n    });\n\n    it('should be called with `null` when empty and transitioning from a number', async () => {\n      const onValueChange = vi.fn();\n      function App() {\n        const [value, setValue] = React.useState<number | null>(5);\n        return (\n          <NumberField\n            value={value}\n            onValueChange={(val) => {\n              onValueChange(val);\n              setValue(val);\n            }}\n          />\n        );\n      }\n      await render(<App />);\n      const input = screen.getByRole('textbox');\n      fireEvent.change(input, { target: { value: '' } });\n      expect(onValueChange.mock.calls.length).toBe(1);\n      expect(onValueChange.mock.calls[0][0]).toBe(null);\n    });\n\n    it('includes the reason for parseable typing', async () => {\n      const onValueChange = vi.fn();\n      await render(<NumberField onValueChange={onValueChange} />);\n      const input = screen.getByRole('textbox');\n\n      fireEvent.change(input, { target: { value: '12' } });\n\n      expect(onValueChange).toHaveBeenCalledTimes(1);\n      const [, details] = onValueChange.mock.calls[0] as [\n        number | null,\n        NumberFieldBase.Root.ChangeEventDetails,\n      ];\n      expect(details.reason).toBe(REASONS.inputChange);\n    });\n\n    it('includes the reason when clearing the value', async () => {\n      const onValueChange = vi.fn();\n      await render(<NumberField defaultValue={5} onValueChange={onValueChange} />);\n      const input = screen.getByRole('textbox');\n\n      fireEvent.change(input, { target: { value: '' } });\n\n      expect(onValueChange).toHaveBeenCalledTimes(1);\n      const [, details] = onValueChange.mock.calls[0] as [\n        number | null,\n        NumberFieldBase.Root.ChangeEventDetails,\n      ];\n      expect(details.reason).toBe(REASONS.inputClear);\n    });\n\n    it('includes the reason for keyboard increments', async () => {\n      const onValueChange = vi.fn();\n      await render(<NumberField defaultValue={1} onValueChange={onValueChange} />);\n      const input = screen.getByRole('textbox');\n\n      await act(async () => {\n        input.focus();\n      });\n      fireEvent.keyDown(input, { key: 'ArrowUp' });\n\n      expect(onValueChange).toHaveBeenCalledTimes(1);\n      const [, details] = onValueChange.mock.calls[0] as [\n        number | null,\n        NumberFieldBase.Root.ChangeEventDetails,\n      ];\n      expect(details.reason).toBe('keyboard');\n    });\n\n    it('includes the reason for increment button presses', async () => {\n      const onValueChange = vi.fn();\n      await render(<NumberField defaultValue={1} onValueChange={onValueChange} />);\n      const incrementButton = screen.getByRole('button', { name: 'Increase' });\n\n      fireEvent.click(incrementButton);\n\n      expect(onValueChange.mock.calls.length).toBe(1);\n      const [, details] = onValueChange.mock.calls[0] as [\n        number | null,\n        NumberFieldBase.Root.ChangeEventDetails,\n      ];\n      expect(details.reason).toBe('increment-press');\n    });\n\n    it('includes the reason for decrement button presses', async () => {\n      const onValueChange = vi.fn();\n      await render(<NumberField defaultValue={1} onValueChange={onValueChange} />);\n      const decrementButton = screen.getByRole('button', { name: 'Decrease' });\n\n      fireEvent.click(decrementButton);\n\n      expect(onValueChange.mock.calls.length).toBe(1);\n      const [, details] = onValueChange.mock.calls[0] as [\n        number | null,\n        NumberFieldBase.Root.ChangeEventDetails,\n      ];\n      expect(details.reason).toBe('decrement-press');\n    });\n\n    it('includes the reason for wheel scrubbing', async () => {\n      const onValueChange = vi.fn();\n      await render(<NumberField allowWheelScrub defaultValue={4} onValueChange={onValueChange} />);\n      const input = screen.getByRole('textbox');\n\n      await act(async () => {\n        input.focus();\n      });\n      fireEvent.wheel(input, { deltaY: -100 });\n\n      expect(onValueChange.mock.calls.length).toBe(1);\n      const [, details] = onValueChange.mock.calls[0] as [\n        number | null,\n        NumberFieldBase.Root.ChangeEventDetails,\n      ];\n      expect(details.reason).toBe('wheel');\n    });\n  });\n\n  describe('typing behavior (parseable changes)', () => {\n    it('fires onValueChange for each parseable change while typing', async () => {\n      const onValueChange = vi.fn();\n      const onValueCommitted = vi.fn();\n      await render(\n        <NumberField onValueChange={onValueChange} onValueCommitted={onValueCommitted} />,\n      );\n      const input = screen.getByRole('textbox');\n\n      // Type '1' -> parseable\n      fireEvent.change(input, { target: { value: '1' } });\n      // Type '12' -> parseable\n      fireEvent.change(input, { target: { value: '12' } });\n      // Type '12.' -> parseable (treated as 12)\n      fireEvent.change(input, { target: { value: '12.' } });\n      // Type '12.a' -> not parseable, should not fire\n      fireEvent.change(input, { target: { value: '12.a' } });\n\n      expect(onValueChange.mock.calls.length).toBe(3);\n      expect(onValueChange.mock.calls[0][0]).toBe(1);\n      expect(onValueChange.mock.calls[1][0]).toBe(12);\n      expect(onValueChange.mock.calls[2][0]).toBe(12);\n\n      expect(onValueCommitted.mock.calls.length).toBe(0);\n    });\n\n    it('does not fire onValueChange for non-numeric composition/partial input', async () => {\n      const onValueChange = vi.fn();\n      const onValueCommitted = vi.fn();\n      await render(\n        <NumberField onValueChange={onValueChange} onValueCommitted={onValueCommitted} />,\n      );\n      const input = screen.getByRole('textbox');\n\n      // Simulate IME composition of non-numeric text; intermediate values like 'ni'\n      fireEvent.compositionStart(input);\n      fireEvent.change(input, { target: { value: 'n' } });\n      fireEvent.change(input, { target: { value: 'ni' } });\n      fireEvent.compositionEnd(input);\n\n      expect(onValueChange.mock.calls.length).toBe(0);\n\n      // Now enter a Han numeral which is parseable\n      fireEvent.change(input, { target: { value: '一' } });\n      expect(onValueChange.mock.calls.length).toBe(1);\n      expect(onValueChange.mock.calls[0][0]).toBe(1);\n\n      expect(onValueCommitted.mock.calls.length).toBe(0);\n      fireEvent.blur(input);\n      expect(onValueCommitted.mock.calls.length).toBe(1);\n      expect(onValueCommitted.mock.calls[0][0]).toBe(1);\n    });\n\n    it('handles sign and decimal partials vs. parseable numbers', async () => {\n      const onValueChange = vi.fn();\n      const onValueCommitted = vi.fn();\n      await render(\n        <NumberField onValueChange={onValueChange} onValueCommitted={onValueCommitted} min={-10} />,\n      );\n      const input = screen.getByRole('textbox');\n\n      // '-' or '.' alone aren't parseable\n      fireEvent.change(input, { target: { value: '-' } });\n      fireEvent.change(input, { target: { value: '.' } });\n      // '0.' is parseable (-> 0)\n      fireEvent.change(input, { target: { value: '0.' } });\n      fireEvent.change(input, { target: { value: '-1' } });\n      fireEvent.change(input, { target: { value: '-1.5' } });\n\n      expect(onValueChange.mock.calls.length).toBe(3);\n      expect(onValueChange.mock.calls[0][0]).toBe(0);\n      expect(onValueChange.mock.calls[1][0]).toBe(-1);\n      expect(onValueChange.mock.calls[2][0]).toBe(-1.5);\n\n      // No commit until blur\n      expect(onValueCommitted.mock.calls.length).toBe(0);\n\n      fireEvent.blur(input);\n      expect(onValueCommitted.mock.calls.length).toBe(1);\n      expect(onValueCommitted.mock.calls[0][0]).toBe(-1.5);\n    });\n\n    it('allows typing a decimal while replacing a selection', async () => {\n      await render(<NumberField defaultValue={12.3} locale=\"en-US\" />);\n      const input = screen.getByRole<HTMLInputElement>('textbox');\n\n      await act(async () => {\n        input.focus();\n      });\n\n      const decimalIndex = input.value.indexOf('.');\n      expect(decimalIndex).toBeGreaterThan(-1);\n      await act(async () => {\n        input.setSelectionRange(1, decimalIndex + 2);\n      });\n\n      const keydownResult = fireEvent.keyDown(input, { key: '.' });\n      expect(keydownResult).toBe(true);\n    });\n\n    it('accepts grouping while typing and parses progressively', async () => {\n      const onValueChange = vi.fn();\n      const onValueCommitted = vi.fn();\n      await render(\n        <NumberField onValueChange={onValueChange} onValueCommitted={onValueCommitted} />,\n      );\n      const input = screen.getByRole('textbox');\n\n      fireEvent.change(input, { target: { value: '1' } }); // 1\n      fireEvent.change(input, { target: { value: '1,' } }); // 1 (group symbol)\n      fireEvent.change(input, { target: { value: '1,2' } }); // 12\n      fireEvent.change(input, { target: { value: '1,23' } }); // 123\n      fireEvent.change(input, { target: { value: '1,234' } }); // 1234\n\n      expect(onValueChange.mock.calls.length).toBe(5);\n      expect(onValueChange.mock.calls[0][0]).toBe(1);\n      expect(onValueChange.mock.calls[1][0]).toBe(1);\n      expect(onValueChange.mock.calls[2][0]).toBe(12);\n      expect(onValueChange.mock.calls[3][0]).toBe(123);\n      expect(onValueChange.mock.calls[4][0]).toBe(1234);\n\n      expect(onValueCommitted.mock.calls.length).toBe(0);\n      fireEvent.blur(input);\n      expect(onValueCommitted.mock.calls.length).toBe(1);\n      expect(onValueCommitted.mock.calls[0][0]).toBe(1234);\n    });\n\n    it('respects locale decimal separator while typing (de-DE)', async () => {\n      const onValueChange = vi.fn();\n      const onValueCommitted = vi.fn();\n      await render(\n        <NumberField\n          onValueChange={onValueChange}\n          onValueCommitted={onValueCommitted}\n          locale=\"de-DE\"\n        />,\n      );\n      const input = screen.getByRole('textbox');\n\n      fireEvent.change(input, { target: { value: '1' } }); // 1\n      fireEvent.change(input, { target: { value: '1,' } }); // 1 (decimal separator typed)\n      fireEvent.change(input, { target: { value: '1,5' } }); // 1.5\n\n      expect(onValueChange.mock.calls.length).toBe(3);\n      expect(onValueChange.mock.calls[0][0]).toBe(1);\n      expect(onValueChange.mock.calls[1][0]).toBe(1);\n      expect(onValueChange.mock.calls[2][0]).toBe(1.5);\n\n      fireEvent.blur(input);\n      expect(onValueCommitted.mock.calls.length).toBe(1);\n      expect(onValueCommitted.mock.calls[0][0]).toBe(1.5);\n    });\n\n    it('parses percent while typing and commits canonical percent value', async () => {\n      const onValueChange = vi.fn();\n      const onValueCommitted = vi.fn();\n      await render(\n        <NumberField\n          onValueChange={onValueChange}\n          onValueCommitted={onValueCommitted}\n          format={{ style: 'percent' }}\n        />,\n      );\n      const input = screen.getByRole('textbox');\n\n      // Typing digits in percent style represents a fraction (12 -> 0.12)\n      fireEvent.change(input, { target: { value: '12' } });\n      // Typing with explicit percent sign also remains 0.12\n      fireEvent.change(input, { target: { value: '12%' } });\n\n      expect(onValueChange.mock.calls.length).toBe(2);\n      expect(onValueChange.mock.calls[0][0]).toBe(0.12);\n      expect(onValueChange.mock.calls[1][0]).toBe(0.12);\n      expect(onValueCommitted.mock.calls.length).toBe(0);\n\n      fireEvent.blur(input);\n      expect(onValueCommitted.mock.calls.length).toBe(1);\n      expect(onValueCommitted.mock.calls[0][0]).toBe(0.12);\n    });\n\n    it('accepts currency symbol while typing and parses numeric value', async () => {\n      const onValueChange = vi.fn();\n      await render(\n        <NumberField\n          onValueChange={onValueChange}\n          format={{ style: 'currency', currency: 'USD' }}\n        />,\n      );\n      const input = screen.getByRole('textbox');\n\n      fireEvent.change(input, { target: { value: '$1' } });\n      fireEvent.change(input, { target: { value: '$1,2' } });\n\n      expect(onValueChange.mock.calls.length).toBe(2);\n      expect(onValueChange.mock.calls[0][0]).toBe(1);\n      expect(onValueChange.mock.calls[1][0]).toBe(12);\n    });\n\n    it('allows deleting trailing currency symbols with locale literals', async () => {\n      const onValueChange = vi.fn();\n      const format: Intl.NumberFormatOptions = {\n        style: 'currency',\n        currency: 'EUR',\n        minimumFractionDigits: 2,\n        maximumFractionDigits: 2,\n      };\n      const formatter = new Intl.NumberFormat('de-DE', format);\n\n      await render(\n        <NumberField\n          defaultValue={12.34}\n          locale=\"de-DE\"\n          format={format}\n          onValueChange={onValueChange}\n        />,\n      );\n      const input = screen.getByRole('textbox');\n      const formatted = formatter.format(12.34);\n      const withoutCurrency = formatted.replace('€', '');\n\n      fireEvent.change(input, { target: { value: withoutCurrency } });\n\n      expect(input).toHaveValue(withoutCurrency);\n      expect(onValueChange.mock.calls.length).toBe(1);\n      expect(onValueChange.mock.calls[0][0]).toBe(12.34);\n    });\n\n    it('allows backspace to remove trailing currency symbol that follows a locale literal', async () => {\n      const onValueChange = vi.fn();\n      const format: Intl.NumberFormatOptions = {\n        style: 'currency',\n        currency: 'EUR',\n        minimumFractionDigits: 2,\n        maximumFractionDigits: 2,\n      };\n      const formatter = new Intl.NumberFormat('de-DE', format);\n\n      await render(\n        <NumberField\n          defaultValue={12.34}\n          locale=\"de-DE\"\n          format={format}\n          onValueChange={onValueChange}\n        />,\n      );\n\n      const input = screen.getByRole('textbox');\n      const formatted = formatter.format(12.34);\n      const afterBackspace = formatted.slice(0, -1);\n\n      await act(async () => {\n        input.focus();\n      });\n\n      const keydownResult = fireEvent.keyDown(input, { key: 'Backspace' });\n      expect(keydownResult).toBe(true);\n\n      fireEvent.change(input, { target: { value: afterBackspace } });\n\n      expect(input).toHaveValue(afterBackspace);\n      expect(onValueChange.mock.calls.length).toBe(1);\n      expect(onValueChange.mock.calls[0][0]).toBe(12.34);\n    });\n\n    it('does not commit on blur for invalid input', async () => {\n      const onValueCommitted = vi.fn();\n      await render(<NumberField onValueCommitted={onValueCommitted} />);\n      const input = screen.getByRole('textbox');\n\n      fireEvent.change(input, { target: { value: '.' } });\n      expect(input).toHaveValue('.');\n      fireEvent.blur(input);\n\n      expect(onValueCommitted.mock.calls.length).toBe(0);\n    });\n  });\n\n  describe('prop: onValueCommitted', () => {\n    it('fires on blur with committed numeric value', async () => {\n      const onValueCommitted = vi.fn();\n      await render(<NumberField onValueCommitted={onValueCommitted} />);\n      const input = screen.getByRole('textbox');\n\n      fireEvent.focus(input);\n      fireEvent.change(input, { target: { value: '123.' } });\n      fireEvent.blur(input);\n\n      expect(onValueCommitted.mock.calls.length).toBe(1);\n      // Canonicalizes to 123\n      expect(onValueCommitted.mock.calls[0][0]).toBe(123);\n    });\n\n    it('fires null on blur when input is cleared', async () => {\n      const onValueCommitted = vi.fn();\n      await render(<NumberField defaultValue={5} onValueCommitted={onValueCommitted} />);\n      const input = screen.getByRole('textbox');\n\n      fireEvent.focus(input);\n      fireEvent.change(input, { target: { value: '' } });\n      fireEvent.blur(input);\n\n      expect(onValueCommitted.mock.calls.length).toBe(1);\n      expect(onValueCommitted.mock.calls[0][0]).toBe(null);\n    });\n\n    it('fires on keyboard interactions (ArrowUp/Down/Home/End)', async () => {\n      const onValueCommitted = vi.fn();\n      await render(\n        <NumberField defaultValue={0} min={-10} max={10} onValueCommitted={onValueCommitted} />,\n      );\n\n      const input = screen.getByRole('textbox');\n      await act(async () => input.focus());\n\n      fireEvent.keyDown(input, { key: 'ArrowUp' });\n      expect(onValueCommitted.mock.calls.length).toBe(1);\n      expect(onValueCommitted.mock.lastCall?.[0]).toBe(1);\n\n      fireEvent.keyDown(input, { key: 'ArrowDown' });\n      expect(onValueCommitted.mock.calls.length).toBe(2);\n      expect(onValueCommitted.mock.lastCall?.[0]).toBe(0);\n\n      fireEvent.keyDown(input, { key: 'Home' });\n      expect(onValueCommitted.mock.calls.length).toBe(3);\n      expect(onValueCommitted.mock.lastCall?.[0]).toBe(-10);\n\n      fireEvent.keyDown(input, { key: 'End' });\n      expect(onValueCommitted.mock.calls.length).toBe(4);\n      expect(onValueCommitted.mock.lastCall?.[0]).toBe(10);\n    });\n\n    it('fires when using increment/decrement buttons', async () => {\n      const onValueCommitted = vi.fn();\n      await render(<NumberField defaultValue={0} onValueCommitted={onValueCommitted} />);\n\n      const input = screen.getByRole('textbox');\n      const inc = screen.getByLabelText('Increase');\n      const dec = screen.getByLabelText('Decrease');\n\n      fireEvent.click(inc);\n      expect(onValueCommitted.mock.calls.length).toBe(1);\n      expect(onValueCommitted.mock.lastCall?.[0]).toBe(1);\n      expect(input).toHaveValue('1');\n\n      fireEvent.click(dec);\n      expect(onValueCommitted.mock.calls.length).toBe(2);\n      expect(onValueCommitted.mock.lastCall?.[0]).toBe(0);\n      expect(input).toHaveValue('0');\n    });\n\n    it('includes the correct reason for increment and decrement button presses', async () => {\n      const onValueCommitted = vi.fn();\n      await render(<NumberField defaultValue={0} onValueCommitted={onValueCommitted} />);\n\n      const inc = screen.getByLabelText('Increase');\n      const dec = screen.getByLabelText('Decrease');\n\n      fireEvent.click(inc);\n      expect(onValueCommitted.mock.lastCall?.[1].reason).toBe(REASONS.incrementPress);\n\n      fireEvent.click(dec);\n      expect(onValueCommitted.mock.lastCall?.[1].reason).toBe(REASONS.decrementPress);\n    });\n  });\n\n  describe('prop: disabled', () => {\n    it('should disable the input', async () => {\n      await render(<NumberField disabled />);\n      const input = screen.getByRole('textbox');\n      expect(input).toHaveAttribute('disabled');\n    });\n  });\n\n  describe('prop: readOnly', () => {\n    it('should mark the input as readOnly', async () => {\n      await render(<NumberField readOnly />);\n      const input = screen.getByRole('textbox');\n      expect(input).toHaveAttribute('readonly');\n    });\n  });\n\n  describe('prop: required', () => {\n    it('should mark the input as required', async () => {\n      await render(<NumberField required />);\n      const input = screen.getByRole('textbox');\n      expect(input).toHaveAttribute('required');\n    });\n  });\n\n  describe('prop: name', () => {\n    it('should set the name attribute on the hidden input', async () => {\n      await render(<NumberField name=\"test\" />);\n      const hiddenInput = screen.getByText('', {\n        selector: 'input[aria-hidden][type=number]',\n      });\n      expect(hiddenInput).toHaveAttribute('name', 'test');\n    });\n  });\n\n  describe('prop: min', () => {\n    it('prevents the raw value from going below the `min` prop', async () => {\n      const fn = vi.fn();\n\n      function App() {\n        const [value, setValue] = React.useState<number | null>(5);\n        return (\n          <NumberField\n            value={value}\n            onValueChange={(v) => {\n              fn(v);\n              setValue(v);\n            }}\n            min={5}\n          />\n        );\n      }\n\n      await render(<App />);\n\n      const input = screen.getByRole('textbox');\n      fireEvent.change(input, { target: { value: '4' } });\n\n      expect(input).toHaveValue('4');\n      expect(fn.mock.calls[0][0]).toBe(5);\n    });\n\n    it('allows the value to go above the `min` prop', async () => {\n      const fn = vi.fn();\n\n      function App() {\n        const [value, setValue] = React.useState<number | null>(5);\n        return (\n          <NumberField\n            value={value}\n            onValueChange={(v) => {\n              fn(v);\n              setValue(v);\n            }}\n            min={5}\n          />\n        );\n      }\n\n      await render(<App />);\n\n      const input = screen.getByRole('textbox');\n      fireEvent.change(input, { target: { value: '6' } });\n\n      expect(input).toHaveValue('6');\n    });\n  });\n\n  describe('prop: max', () => {\n    it('prevents the value from going above the `max` prop', async () => {\n      const fn = vi.fn();\n\n      function App() {\n        const [value, setValue] = React.useState<number | null>(5);\n        return (\n          <NumberField\n            value={value}\n            onValueChange={(v) => {\n              fn(v);\n              setValue(v);\n            }}\n            max={5}\n          />\n        );\n      }\n\n      await render(<App />);\n\n      const input = screen.getByRole('textbox');\n      fireEvent.change(input, { target: { value: '6' } });\n\n      expect(input).toHaveValue('6');\n      expect(fn.mock.calls[0][0]).toBe(5);\n    });\n\n    it('allows the value to go below the `max` prop', async () => {\n      const fn = vi.fn();\n\n      function App() {\n        const [value, setValue] = React.useState<number | null>(5);\n        return (\n          <NumberField\n            value={value}\n            onValueChange={(v) => {\n              fn(v);\n              setValue(v);\n            }}\n            max={5}\n          />\n        );\n      }\n\n      await render(<App />);\n\n      const input = screen.getByRole('textbox');\n      fireEvent.change(input, { target: { value: '4' } });\n\n      expect(input).toHaveValue('4');\n      expect(fn.mock.calls[0][0]).toBe(4);\n    });\n  });\n\n  describe('prop: allowOutOfRange', () => {\n    it('allows range overflow validation when true', async () => {\n      await render(\n        <form data-testid=\"form\">\n          <NumberFieldBase.Root name=\"quantity\" max={5} allowOutOfRange>\n            <NumberFieldBase.Group>\n              <NumberFieldBase.Input />\n            </NumberFieldBase.Group>\n          </NumberFieldBase.Root>\n          <button type=\"submit\">Submit</button>\n        </form>,\n      );\n\n      const input = screen.getByRole('textbox');\n      fireEvent.change(input, { target: { value: '6' } });\n\n      const hiddenInput = document.querySelector(\n        'input[type=\"number\"][name=\"quantity\"]',\n      ) as HTMLInputElement;\n\n      expect(hiddenInput).not.toBe(null);\n      expect(hiddenInput.value).toBe('6');\n      expect(hiddenInput.validity.rangeOverflow).toBe(true);\n\n      const form = screen.getByTestId<HTMLFormElement>('form');\n      expect(form.checkValidity()).toBe(false);\n    });\n\n    it('still clamps step interactions when true', async () => {\n      await render(\n        <form data-testid=\"form\">\n          <NumberField defaultValue={5} max={5} allowOutOfRange name=\"quantity\" />\n          <button type=\"submit\">Submit</button>\n        </form>,\n      );\n\n      const input = screen.getByRole('textbox');\n      fireEvent.click(screen.getByLabelText('Increase'));\n\n      const hiddenInput = document.querySelector(\n        'input[type=\"number\"][name=\"quantity\"]',\n      ) as HTMLInputElement;\n\n      expect(input).toHaveValue('5');\n      expect(hiddenInput).not.toBe(null);\n      expect(hiddenInput.value).toBe('5');\n      expect(hiddenInput.validity.rangeOverflow).toBe(false);\n\n      const form = screen.getByTestId<HTMLFormElement>('form');\n      expect(form.checkValidity()).toBe(true);\n    });\n\n    it('clamps to range when false', async () => {\n      await render(\n        <form data-testid=\"form\">\n          <NumberFieldBase.Root name=\"quantity\" max={5} allowOutOfRange={false}>\n            <NumberFieldBase.Group>\n              <NumberFieldBase.Input />\n            </NumberFieldBase.Group>\n          </NumberFieldBase.Root>\n          <button type=\"submit\">Submit</button>\n        </form>,\n      );\n\n      const input = screen.getByRole('textbox');\n      fireEvent.change(input, { target: { value: '6' } });\n\n      const hiddenInput = document.querySelector(\n        'input[type=\"number\"][name=\"quantity\"]',\n      ) as HTMLInputElement;\n\n      expect(hiddenInput).not.toBe(null);\n      expect(hiddenInput.value).toBe('5');\n      expect(hiddenInput.validity.rangeOverflow).toBe(false);\n\n      const form = screen.getByTestId<HTMLFormElement>('form');\n      expect(form.checkValidity()).toBe(true);\n    });\n  });\n\n  describe('prop: step', () => {\n    it('defaults to 1', async () => {\n      await render(<NumberField defaultValue={5} />);\n      const input = screen.getByRole('textbox');\n      fireEvent.click(screen.getByLabelText('Increase'));\n      expect(input).toHaveValue('6');\n    });\n\n    it('should increment the value by the `step` prop', async () => {\n      await render(<NumberField defaultValue={4} step={2} />);\n      const input = screen.getByRole('textbox');\n      fireEvent.click(screen.getByLabelText('Increase'));\n      expect(input).toHaveValue('6');\n    });\n\n    it('should snap when incrementing to the nearest multiple of the `step` prop', async () => {\n      await render(<NumberField defaultValue={5} step={2} />);\n      const input = screen.getByRole('textbox');\n      fireEvent.change(input, { target: { value: '6' } });\n      fireEvent.blur(input);\n      expect(input).toHaveValue('6');\n    });\n\n    it('should decrement the value by the `step` prop', async () => {\n      await render(<NumberField defaultValue={6} step={2} />);\n      const input = screen.getByRole('textbox');\n      fireEvent.click(screen.getByLabelText('Decrease'));\n      expect(input).toHaveValue('4');\n    });\n\n    it('should snap when decrementing to the nearest multiple of the `step` prop', async () => {\n      await render(<NumberField defaultValue={5} step={2} />);\n      const input = screen.getByRole('textbox');\n      fireEvent.change(input, { target: { value: '4' } });\n      fireEvent.blur(input);\n      expect(input).toHaveValue('4');\n    });\n  });\n\n  describe.skipIf(isJSDOM)('prop: largeStep', () => {\n    it('should increment the value by the default `largeStep` prop of 10 while holding the shift key', async () => {\n      await render(<NumberField defaultValue={5} />);\n      const input = screen.getByRole('textbox');\n      fireEvent.pointerDown(screen.getByLabelText('Increase'), { shiftKey: true });\n      expect(input).toHaveValue('15');\n    });\n\n    it('should decrement the value by the default `largeStep` prop of 10 while holding the shift key', async () => {\n      await render(<NumberField defaultValue={6} />);\n      const input = screen.getByRole('textbox');\n      fireEvent.pointerDown(screen.getByLabelText('Decrease'), { shiftKey: true });\n      expect(input).toHaveValue('-4');\n    });\n\n    it('should use explicit `largeStep` value if provided while holding the shift key', async () => {\n      await render(<NumberField defaultValue={5} largeStep={5} />);\n      const input = screen.getByRole('textbox');\n      fireEvent.pointerDown(screen.getByLabelText('Increase'), { shiftKey: true });\n      expect(input).toHaveValue('10');\n    });\n\n    it('should not use the `largeStep` prop if no longer holding the shift key', async () => {\n      await render(<NumberField defaultValue={5} largeStep={5} />);\n      const input = screen.getByRole('textbox');\n      fireEvent.pointerDown(screen.getByLabelText('Increase'), { shiftKey: true });\n      expect(input).toHaveValue('10');\n      fireEvent.keyUp(input, { shiftKey: true });\n      fireEvent.pointerDown(screen.getByLabelText('Increase'), { shiftKey: true });\n      expect(input).toHaveValue('15');\n    });\n  });\n\n  describe.skipIf(isJSDOM)('prop: smallStep', () => {\n    it('should increment the value by the default `smallStep` prop of 0.1 while holding the alt key', async () => {\n      await render(<NumberField defaultValue={5} />);\n      const input = screen.getByRole('textbox');\n      fireEvent.pointerDown(screen.getByLabelText('Increase'), { altKey: true });\n      expect(input).toHaveValue((5.1).toLocaleString());\n    });\n\n    it('should decrement the value by the default `smallStep` prop of 0.1 while holding the alt key', async () => {\n      await render(<NumberField defaultValue={6} />);\n      const input = screen.getByRole('textbox');\n      fireEvent.pointerDown(screen.getByLabelText('Decrease'), { altKey: true });\n      expect(input).toHaveValue((5.9).toLocaleString());\n    });\n\n    it('should use explicit `smallStep` value if provided while holding the alt key', async () => {\n      await render(<NumberField defaultValue={5} smallStep={0.5} />);\n      const input = screen.getByRole('textbox');\n      fireEvent.keyDown(document.body, { altKey: true });\n      fireEvent.pointerDown(screen.getByLabelText('Increase'), { altKey: true });\n      expect(input).toHaveValue((5.5).toLocaleString());\n    });\n\n    it('should not use the `smallStep` prop if no longer holding the alt key', async () => {\n      await render(<NumberField defaultValue={5} smallStep={0.5} />);\n      const input = screen.getByRole('textbox');\n      const button = screen.getByLabelText('Increase');\n      fireEvent.pointerDown(button, { altKey: true });\n      expect(input).toHaveValue((5.5).toLocaleString());\n      fireEvent.keyUp(input, { altKey: false });\n      fireEvent.pointerDown(button);\n      expect(input).toHaveValue((6.5).toLocaleString());\n    });\n  });\n\n  describe('prop: format', () => {\n    it('should format the value using the provided options', async () => {\n      await render(\n        <NumberField defaultValue={1000} format={{ style: 'currency', currency: 'USD' }} />,\n      );\n      const input = screen.getByRole('textbox');\n      const expectedValue = new Intl.NumberFormat(undefined, {\n        style: 'currency',\n        currency: 'USD',\n      }).format(1000);\n      expect(input).toHaveValue(expectedValue);\n    });\n\n    it('reflects controlled value changes in the textbox', async () => {\n      function App() {\n        const [val, setVal] = React.useState<number | null>(1);\n        return (\n          <div>\n            <NumberField value={val} onValueChange={setVal} />\n            <button onClick={() => setVal(1234)}>set</button>\n          </div>\n        );\n      }\n\n      const { user } = await render(<App />);\n      const input = screen.getByRole('textbox');\n\n      expect(input).toHaveValue('1');\n\n      await user.click(screen.getByText('set'));\n      expect(input).toHaveValue((1234).toLocaleString());\n    });\n  });\n\n  describe('prop: allowWheelScrub', () => {\n    it('should allow the user to scrub the input value with the mouse wheel', async () => {\n      await render(<NumberField defaultValue={5} allowWheelScrub />);\n      const input = screen.getByRole('textbox');\n      await act(async () => input.focus());\n      fireEvent.wheel(input, { deltaY: 1 });\n      expect(input).toHaveValue('4');\n      fireEvent.wheel(input, { deltaY: -1 });\n      expect(input).toHaveValue('5');\n    });\n\n    it('should not allow the user to scrub the input value with the mouse wheel if `allowWheelScrub` is `false`', async () => {\n      await render(<NumberField defaultValue={5} allowWheelScrub={false} />);\n      const input = screen.getByRole('textbox');\n      await act(async () => input.focus());\n      fireEvent.wheel(input, { deltaY: 1 });\n      expect(input).toHaveValue('5');\n      fireEvent.wheel(input, { deltaY: -5 });\n      expect(input).toHaveValue('5');\n    });\n\n    it('calls onValueChange on wheel and commits on blur', async () => {\n      const onValueChange = vi.fn();\n      const onValueCommitted = vi.fn();\n      await render(\n        <NumberField\n          defaultValue={5}\n          allowWheelScrub\n          onValueChange={onValueChange}\n          onValueCommitted={onValueCommitted}\n        />,\n      );\n      const input = screen.getByRole('textbox');\n      await act(async () => input.focus());\n\n      fireEvent.wheel(input, { deltaY: 1 });\n      expect(onValueChange.mock.calls.length).toBe(1);\n      expect(onValueChange.mock.lastCall?.[0]).toBe(4);\n\n      fireEvent.wheel(input, { deltaY: -1 });\n      expect(onValueChange.mock.calls.length).toBe(2);\n      expect(onValueChange.mock.lastCall?.[0]).toBe(5);\n\n      // Wheel does not commit; blur commits current value\n      expect(onValueCommitted.mock.calls.length).toBe(0);\n\n      fireEvent.blur(input);\n      expect(onValueCommitted.mock.calls.length).toBe(1);\n      expect(onValueCommitted.mock.calls[0][0]).toBe(5);\n    });\n  });\n\n  describe('Form', () => {\n    it('should include the input value in the form submission', async ({ skip }) => {\n      if (isJSDOM) {\n        // FormData is not available in JSDOM\n        skip();\n      }\n\n      let fieldValue = '';\n\n      await render(\n        <Form\n          onSubmit={(event) => {\n            event.preventDefault();\n            const formData = new FormData(event.currentTarget);\n            fieldValue = formData.get('test') as string;\n          }}\n        >\n          <Field.Root name=\"test\">\n            <NumberFieldBase.Root defaultValue={undefined}>\n              <NumberFieldBase.Input />\n            </NumberFieldBase.Root>\n            <button type=\"submit\">Submit</button>\n          </Field.Root>\n        </Form>,\n      );\n\n      const submitButton = screen.getByText('Submit');\n      await act(async () => submitButton.click());\n      expect(fieldValue).toBe('');\n\n      fireEvent.change(screen.getByRole('textbox'), { target: { value: '50' } });\n      await act(async () => submitButton.click());\n      expect(fieldValue).toBe('50');\n    });\n\n    it('should not include formatting in the submitted value', async ({ skip }) => {\n      if (isJSDOM) {\n        // FormData is not available in JSDOM\n        skip();\n      }\n\n      const format: Intl.NumberFormatOptions = {\n        style: 'currency',\n        currency: 'EUR',\n        minimumFractionDigits: 2,\n        maximumFractionDigits: 2,\n      };\n\n      let fieldValue = '';\n\n      await render(\n        <Form\n          onSubmit={(event) => {\n            event.preventDefault();\n            const formData = new FormData(event.currentTarget);\n            fieldValue = formData.get('test') as string;\n          }}\n        >\n          <Field.Root name=\"test\">\n            <NumberFieldBase.Root defaultValue={54.5} format={format} locale=\"de-DE\">\n              <NumberFieldBase.Input />\n            </NumberFieldBase.Root>\n            <button type=\"submit\">Submit</button>\n          </Field.Root>\n        </Form>,\n      );\n\n      const input = screen.getByRole('textbox');\n      const expectedValue = new Intl.NumberFormat('de-DE', format).format(54.5);\n      expect(input).toHaveValue(expectedValue);\n\n      const submitButton = screen.getByText('Submit');\n\n      await act(async () => submitButton.click());\n\n      expect(fieldValue).toBe('54.5');\n    });\n\n    it('triggers native HTML validation on submit', async () => {\n      const { user } = await render(\n        <Form>\n          <Field.Root name=\"test\" data-testid=\"field\">\n            <NumberField required />\n            <Field.Error match=\"valueMissing\" data-testid=\"error\">\n              required\n            </Field.Error>\n          </Field.Root>\n          <button type=\"submit\">Submit</button>\n        </Form>,\n      );\n\n      const submit = screen.getByText('Submit');\n\n      expect(screen.queryByTestId('error')).toBe(null);\n\n      await user.click(submit);\n\n      const error = screen.getByTestId('error');\n      expect(error).toHaveTextContent('required');\n    });\n\n    it('focuses the input when the field receives an error from Form', async () => {\n      function App() {\n        const [errors, setErrors] = React.useState<Form.Props['errors']>({});\n        return (\n          <Form\n            errors={errors}\n            onSubmit={(event) => {\n              event.preventDefault();\n              setErrors({ quantity: 'server error' });\n            }}\n          >\n            <Field.Root name=\"quantity\" data-testid=\"field\">\n              <NumberField defaultValue={1} />\n              <Field.Error data-testid=\"error\" />\n            </Field.Root>\n            <button type=\"submit\">Submit</button>\n          </Form>\n        );\n      }\n\n      const { user } = await render(<App />);\n      expect(screen.queryByTestId('error')).toBe(null);\n      const submit = screen.getByText('Submit');\n      await user.click(submit);\n\n      const input = screen.getByRole('textbox');\n      expect(input).toHaveFocus();\n      expect(input).toHaveAttribute('aria-invalid', 'true');\n      expect(screen.queryByTestId('error')).toHaveTextContent('server error');\n    });\n\n    it('clears external errors on change', async () => {\n      await render(\n        <Form\n          errors={{\n            test: 'test',\n          }}\n        >\n          <Field.Root name=\"test\" data-testid=\"field\">\n            <NumberField defaultValue={1} />\n            <Field.Error data-testid=\"error\" />\n          </Field.Root>\n        </Form>,\n      );\n\n      const input = screen.getByRole('textbox');\n\n      expect(input).toHaveAttribute('aria-invalid', 'true');\n      expect(screen.queryByTestId('error')).toHaveTextContent('test');\n\n      fireEvent.change(input, { target: { value: '5' } });\n\n      expect(input).not.toHaveAttribute('aria-invalid');\n      expect(screen.queryByTestId('error')).toBe(null);\n    });\n\n    it('revalidates immediately after form submission errors using increment button', async () => {\n      const { user } = await render(\n        <Form>\n          <Field.Root name=\"quantity\">\n            <NumberField required />\n            <Field.Error match=\"valueMissing\" data-testid=\"error\">\n              required\n            </Field.Error>\n          </Field.Root>\n          <button type=\"submit\" data-testid=\"submit\">\n            Submit\n          </button>\n        </Form>,\n      );\n\n      const submit = screen.getByTestId('submit');\n      await user.click(submit);\n\n      expect(screen.getByTestId('error')).toHaveTextContent('required');\n      const input = screen.getByRole('textbox');\n      expect(input).toHaveAttribute('aria-invalid', 'true');\n\n      const incrementButton = screen.getByLabelText('Increase');\n      await user.click(incrementButton);\n\n      expect(screen.queryByTestId('error')).toBe(null);\n      expect(input).not.toHaveAttribute('aria-invalid');\n    });\n\n    it('should handle browser autofill', async () => {\n      const onValueChange = vi.fn();\n\n      await render(\n        <Field.Root name=\"quantity\">\n          <NumberFieldBase.Root onValueChange={onValueChange}>\n            <NumberFieldBase.Input />\n          </NumberFieldBase.Root>\n        </Field.Root>,\n      );\n\n      const input = screen.getByRole('textbox');\n      const hiddenInput = document.querySelector('input[type=\"number\"][name=\"quantity\"]');\n\n      expect(hiddenInput).not.toBe(null);\n      fireEvent.change(hiddenInput!, { target: { value: '42' } });\n\n      expect(onValueChange.mock.calls.length).toBe(1);\n      expect(onValueChange.mock.calls[0][0]).toBe(42);\n      expect(input).toHaveValue('42');\n    });\n  });\n\n  describe('Field', () => {\n    it('[data-touched]', async () => {\n      await render(\n        <Field.Root>\n          <NumberFieldBase.Root>\n            <NumberFieldBase.Input />\n          </NumberFieldBase.Root>\n        </Field.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('textbox');\n\n      fireEvent.focus(input);\n      fireEvent.blur(input);\n\n      expect(input).toHaveAttribute('data-touched', '');\n    });\n\n    it('[data-dirty]', async () => {\n      await render(\n        <Field.Root>\n          <NumberFieldBase.Root>\n            <NumberFieldBase.Input />\n          </NumberFieldBase.Root>\n        </Field.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('textbox');\n\n      expect(input).not.toHaveAttribute('data-dirty');\n\n      fireEvent.change(input, { target: { value: '1' } });\n\n      expect(input).toHaveAttribute('data-dirty', '');\n    });\n\n    describe('[data-filled]', () => {\n      it('adds [data-filled] attribute when filled', async () => {\n        await render(\n          <Field.Root>\n            <NumberFieldBase.Root>\n              <NumberFieldBase.Input data-testid=\"input\" />\n            </NumberFieldBase.Root>\n          </Field.Root>,\n        );\n\n        const input = screen.getByTestId('input');\n\n        expect(input).not.toHaveAttribute('data-filled');\n\n        fireEvent.change(input, { target: { value: '1' } });\n\n        expect(input).toHaveAttribute('data-filled', '');\n\n        fireEvent.change(input, { target: { value: '' } });\n\n        expect(input).not.toHaveAttribute('data-filled');\n      });\n\n      it('has [data-filled] attribute when already filled', async () => {\n        await render(\n          <Field.Root>\n            <NumberFieldBase.Root defaultValue={1}>\n              <NumberFieldBase.Input data-testid=\"input\" />\n            </NumberFieldBase.Root>\n          </Field.Root>,\n        );\n\n        const input = screen.getByTestId('input');\n\n        expect(input).toHaveAttribute('data-filled');\n\n        fireEvent.change(input, { target: { value: '' } });\n\n        expect(input).not.toHaveAttribute('data-filled');\n      });\n    });\n\n    it('[data-filled]', async () => {\n      await render(\n        <Field.Root>\n          <NumberFieldBase.Root>\n            <NumberFieldBase.Input data-testid=\"input\" />\n          </NumberFieldBase.Root>\n        </Field.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n\n      expect(input).not.toHaveAttribute('data-focused');\n\n      fireEvent.focus(input);\n\n      expect(input).toHaveAttribute('data-focused', '');\n\n      fireEvent.blur(input);\n\n      expect(input).not.toHaveAttribute('data-focused');\n    });\n\n    it('adds [data-focused] attribute on every focus', async () => {\n      await render(\n        <Field.Root>\n          <NumberFieldBase.Root>\n            <NumberFieldBase.Input data-testid=\"input\" />\n          </NumberFieldBase.Root>\n        </Field.Root>,\n      );\n\n      const input = screen.getByTestId('input');\n\n      fireEvent.focus(input);\n      expect(input).toHaveAttribute('data-focused', '');\n\n      fireEvent.blur(input);\n      expect(input).not.toHaveAttribute('data-focused');\n\n      fireEvent.focus(input);\n      expect(input).toHaveAttribute('data-focused', '');\n    });\n\n    it('prop: validate', async () => {\n      await render(\n        <Field.Root validationMode=\"onBlur\" validate={() => 'error'}>\n          <NumberFieldBase.Root>\n            <NumberFieldBase.Input />\n          </NumberFieldBase.Root>\n          <Field.Error data-testid=\"error\" />\n        </Field.Root>,\n      );\n\n      const input = screen.getByRole('textbox');\n\n      expect(input).not.toHaveAttribute('aria-invalid');\n\n      fireEvent.focus(input);\n      fireEvent.blur(input);\n\n      expect(input).toHaveAttribute('aria-invalid', 'true');\n    });\n\n    describe('prop: validationMode', () => {\n      it('onSubmit', async () => {\n        await render(\n          <Form>\n            <Field.Root validate={(value) => (value === 1 ? 'custom error' : null)}>\n              <NumberFieldBase.Root required data-testid=\"root\">\n                <NumberFieldBase.Input data-testid=\"input\" />\n              </NumberFieldBase.Root>\n              <Field.Error data-testid=\"error\" match=\"valueMissing\">\n                valueMissing error\n              </Field.Error>\n              <Field.Error data-testid=\"error\" match=\"customError\" />\n            </Field.Root>\n            <button type=\"submit\">submit</button>\n          </Form>,\n        );\n\n        const input = screen.getByRole('textbox');\n        expect(input).not.toHaveAttribute('aria-invalid');\n\n        fireEvent.change(input, { target: { value: '1' } });\n        fireEvent.blur(input);\n        expect(input).not.toHaveAttribute('aria-invalid');\n        expect(screen.queryByTestId('error')).toBe(null);\n\n        fireEvent.change(input, { target: { value: '' } });\n        fireEvent.blur(input);\n        expect(input).not.toHaveAttribute('aria-invalid');\n        expect(screen.queryByTestId('error')).toBe(null);\n\n        fireEvent.click(screen.getByText('submit'));\n        expect(input).toHaveAttribute('aria-invalid', 'true');\n        expect(screen.queryByTestId('error')).toHaveTextContent('valueMissing error');\n        expect(screen.getByTestId('root')).toHaveAttribute('data-invalid');\n        expect(input).toHaveAttribute('data-invalid');\n\n        fireEvent.change(input, { target: { value: '2' } });\n        expect(input).not.toHaveAttribute('aria-invalid');\n        expect(screen.queryByTestId('error')).toBe(null);\n        expect(screen.getByTestId('root')).not.toHaveAttribute('data-invalid');\n        expect(input).not.toHaveAttribute('data-invalid');\n        expect(screen.getByTestId('root')).toHaveAttribute('data-valid');\n        expect(input).toHaveAttribute('data-valid');\n        // re-invalidate the field value\n        fireEvent.change(input, { target: { value: '1' } });\n        expect(input).toHaveAttribute('aria-invalid', 'true');\n        expect(screen.queryByTestId('error')).toHaveTextContent('custom error');\n\n        fireEvent.change(input, { target: { value: '3' } });\n        expect(input).not.toHaveAttribute('aria-invalid');\n        expect(screen.queryByTestId('error')).toBe(null);\n\n        fireEvent.change(input, { target: { value: '' } });\n        expect(input).toHaveAttribute('aria-invalid', 'true');\n        expect(screen.queryByTestId('error')).toHaveTextContent('valueMissing error');\n      });\n\n      it('onChange', async () => {\n        await render(\n          <Field.Root\n            validationMode=\"onChange\"\n            validate={(value) => {\n              return value === 1 ? 'error' : null;\n            }}\n          >\n            <NumberFieldBase.Root>\n              <NumberFieldBase.Input data-testid=\"input\" />\n            </NumberFieldBase.Root>\n          </Field.Root>,\n        );\n\n        const input = screen.getByTestId('input');\n\n        expect(input).not.toHaveAttribute('aria-invalid');\n\n        fireEvent.change(input, { target: { value: '1' } });\n\n        expect(input).toHaveAttribute('aria-invalid', 'true');\n      });\n\n      it('revalidates when the controlled value changes externally', async () => {\n        const validateSpy = vi.fn((value: unknown) =>\n          (value as number | null) === 5 ? 'error' : null,\n        );\n\n        function App() {\n          const [value, setValue] = React.useState<number | null>(null);\n\n          return (\n            <React.Fragment>\n              <Field.Root validationMode=\"onChange\" validate={validateSpy} name=\"quantity\">\n                <NumberFieldBase.Root value={value} onValueChange={(next) => setValue(next)}>\n                  <NumberFieldBase.Input data-testid=\"input\" />\n                </NumberFieldBase.Root>\n              </Field.Root>\n              <button type=\"button\" onClick={() => setValue(5)}>\n                Set externally\n              </button>\n            </React.Fragment>\n          );\n        }\n\n        await render(<App />);\n\n        const input = screen.getByTestId('input');\n        const toggle = screen.getByText('Set externally');\n\n        expect(input).not.toHaveAttribute('aria-invalid');\n        const initialCallCount = validateSpy.mock.calls.length;\n\n        fireEvent.click(toggle);\n\n        expect(validateSpy.mock.calls.length).toBe(initialCallCount + 1);\n        expect(validateSpy.mock.lastCall?.[0]).toBe(5);\n        expect(input).toHaveAttribute('aria-invalid', 'true');\n      });\n\n      it('onBlur', async () => {\n        await render(\n          <Field.Root\n            validationMode=\"onBlur\"\n            validate={(value) => {\n              return value === 1 ? 'error' : null;\n            }}\n          >\n            <NumberFieldBase.Root required>\n              <NumberFieldBase.Input data-testid=\"input\" />\n            </NumberFieldBase.Root>\n            <Field.Error data-testid=\"error\" />\n          </Field.Root>,\n        );\n\n        const input = screen.getByTestId('input');\n        expect(input).not.toHaveAttribute('aria-invalid');\n\n        fireEvent.change(input, { target: { value: '1' } });\n        expect(input).not.toHaveAttribute('aria-invalid');\n        fireEvent.blur(input);\n        expect(input).toHaveAttribute('aria-invalid', 'true');\n        // revalidation\n        fireEvent.change(input, { target: { value: '2' } });\n        expect(input).not.toHaveAttribute('aria-invalid');\n        expect(screen.queryByTestId('error')).toBe(null);\n      });\n    });\n\n    // Chromium shows a native validation popup when stepMismatch occurs that blocks the test\n    it.skipIf(!isJSDOM)(\n      'prevents form submission when the value does not match the step',\n      async () => {\n        const handleSubmit = vi.fn();\n        await render(\n          <form onSubmit={handleSubmit}>\n            <NumberFieldBase.Root name=\"quantity\" defaultValue={0} min={0} step={0.1}>\n              <NumberFieldBase.Input data-testid=\"input\" />\n            </NumberFieldBase.Root>\n            <button type=\"submit\">submit</button>\n          </form>,\n        );\n\n        const input = screen.getByTestId('input');\n\n        await act(async () => {\n          input.focus();\n        });\n\n        fireEvent.change(input, { target: { value: '0.11' } });\n        fireEvent.click(screen.getByText('submit'));\n\n        expect(handleSubmit.mock.calls.length).toBe(0);\n\n        fireEvent.change(input, { target: { value: '0.1' } });\n        fireEvent.click(screen.getByText('submit'));\n\n        expect(handleSubmit.mock.calls.length).toBe(1);\n        expect(new FormData(handleSubmit.mock.calls[0][0].target).get('quantity')).toBe('0.1');\n      },\n    );\n\n    it('prevents Form/Field submission when the value does not match the step', async () => {\n      const handleSubmit = vi.fn();\n      await render(\n        <Form onFormSubmit={handleSubmit}>\n          <Field.Root name=\"quantity\">\n            <NumberFieldBase.Root defaultValue={0} min={0} step={0.1}>\n              <NumberFieldBase.Input data-testid=\"input\" />\n            </NumberFieldBase.Root>\n            <Field.Error match=\"stepMismatch\" data-testid=\"error\">\n              step mismatch\n            </Field.Error>\n          </Field.Root>\n          <button type=\"submit\">submit</button>\n        </Form>,\n      );\n\n      const input = screen.getByTestId('input');\n\n      await act(async () => {\n        input.focus();\n      });\n\n      expect(screen.queryByTestId('error')).toBe(null);\n\n      fireEvent.change(input, { target: { value: '0.11' } });\n      fireEvent.click(screen.getByText('submit'));\n\n      expect(handleSubmit.mock.calls.length).toBe(0);\n      expect(screen.getByTestId('error')).toHaveTextContent('step mismatch');\n\n      fireEvent.change(input, { target: { value: '0.1' } });\n      fireEvent.click(screen.getByText('submit'));\n\n      expect(handleSubmit.mock.calls.length).toBe(1);\n      expect(handleSubmit.mock.calls[0][0].quantity).toBe(0.1);\n    });\n\n    it('disables the input when disabled=true', async () => {\n      await render(\n        <Field.Root disabled>\n          <NumberFieldBase.Root>\n            <NumberFieldBase.Input />\n          </NumberFieldBase.Root>\n        </Field.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('textbox');\n\n      expect(input).toHaveAttribute('disabled', '');\n    });\n\n    it('does not disable the input when disabled=false', async () => {\n      await render(\n        <Field.Root disabled={false}>\n          <NumberFieldBase.Root>\n            <NumberFieldBase.Input />\n          </NumberFieldBase.Root>\n        </Field.Root>,\n      );\n\n      const input = screen.getByRole<HTMLInputElement>('textbox');\n\n      expect(input).not.toHaveAttribute('disabled');\n    });\n\n    it('is validated with latest value when validationMode=onBlur', async () => {\n      const validate = vi.fn(() => 'error');\n\n      await render(\n        <Form>\n          <Field.Root validationMode=\"onBlur\" validate={validate} name=\"quantity\">\n            <NumberFieldBase.Root defaultValue={undefined}>\n              <NumberFieldBase.Input />\n            </NumberFieldBase.Root>\n          </Field.Root>\n        </Form>,\n      );\n\n      const input = screen.getByRole('textbox');\n\n      fireEvent.focus(input);\n      fireEvent.change(input, { target: { value: '1' } });\n      fireEvent.blur(input);\n\n      expect(validate.mock.calls.length).toBe(1);\n      expect(validate.mock.calls[0]).toEqual([1, { quantity: 1 }]);\n    });\n\n    it('Field.Label', async () => {\n      await render(\n        <Field.Root>\n          <NumberFieldBase.Root>\n            <NumberFieldBase.Input />\n          </NumberFieldBase.Root>\n          <Field.Label data-testid=\"label\" />\n        </Field.Root>,\n      );\n\n      expect(screen.getByTestId('label')).toHaveAttribute('for', screen.getByRole('textbox').id);\n    });\n\n    it('Field.Description', async () => {\n      await render(\n        <Field.Root>\n          <NumberFieldBase.Root>\n            <NumberFieldBase.Input />\n          </NumberFieldBase.Root>\n          <Field.Description data-testid=\"description\" />\n        </Field.Root>,\n      );\n\n      expect(screen.getByRole('textbox')).toHaveAttribute(\n        'aria-describedby',\n        screen.getByTestId('description').id,\n      );\n    });\n  });\n\n  describe('prop: inputMode', () => {\n    it('should set the inputMode to numeric', async () => {\n      await render(<NumberField />);\n      const input = screen.getByRole('textbox');\n      expect(input).toHaveAttribute('inputmode', 'numeric');\n    });\n  });\n\n  describe('integration: exotic inputs and IME', () => {\n    it('parses Persian digits and separators via change events', async () => {\n      const onValueChange = vi.fn();\n      function App() {\n        const [value, setValue] = React.useState<number | null>(null);\n        return (\n          <NumberField\n            value={value}\n            onValueChange={(v) => {\n              onValueChange(v);\n              setValue(v);\n            }}\n          />\n        );\n      }\n      await render(<App />);\n\n      const input = screen.getByRole('textbox');\n      // ۱۲٫۳۴ => 12.34\n      fireEvent.change(input, { target: { value: '۱۲٫۳۴' } });\n\n      expect(onValueChange.mock.calls.length).toBe(1);\n      expect(onValueChange.mock.calls[0][0]).toBe(12.34);\n    });\n\n    it('parses Persian digits with Arabic group/decimal separators', async () => {\n      const onValueChange = vi.fn();\n      function App() {\n        const [value, setValue] = React.useState<number | null>(null);\n        return (\n          <NumberField\n            value={value}\n            onValueChange={(v) => {\n              onValueChange(v);\n              setValue(v);\n            }}\n          />\n        );\n      }\n      await render(<App />);\n\n      const input = screen.getByRole('textbox');\n      // ۱۲٬۳۴۵٫۶۷ => 12345.67\n      fireEvent.change(input, { target: { value: '۱۲٬۳۴۵٫۶۷' } });\n\n      expect(onValueChange.mock.calls.length).toBe(1);\n      expect(onValueChange.mock.calls[0][0]).toBe(12345.67);\n    });\n\n    it('parses fullwidth digits and punctuation', async () => {\n      const onValueChange = vi.fn();\n      function App() {\n        const [value, setValue] = React.useState<number | null>(null);\n        return (\n          <NumberField\n            value={value}\n            onValueChange={(v) => {\n              onValueChange(v);\n              setValue(v);\n            }}\n          />\n        );\n      }\n\n      await render(<App />);\n\n      const input = screen.getByRole('textbox');\n\n      fireEvent.change(input, { target: { value: '１，２３４．５６' } });\n\n      expect(onValueChange.mock.calls.length).toBe(1);\n      expect(onValueChange.mock.calls[0][0]).toBe(1234.56);\n    });\n\n    it('parses percent and permille signs in exotic forms when formatted as percent', async () => {\n      const onValueChange = vi.fn();\n      function App() {\n        const [value, setValue] = React.useState<number | null>(null);\n        return (\n          <NumberField\n            value={value}\n            format={{ style: 'percent' }}\n            onValueChange={(v) => {\n              onValueChange(v);\n              setValue(v);\n            }}\n          />\n        );\n      }\n\n      await render(<App />);\n\n      const input = screen.getByRole('textbox');\n      fireEvent.change(input, { target: { value: '١٢٪' } });\n\n      expect(onValueChange.mock.calls.length).toBe(1);\n      expect(onValueChange.mock.calls[0][0]).toBe(0.12);\n\n      // reset by typing again\n      fireEvent.change(input, { target: { value: '12؉' } });\n      expect(onValueChange.mock.calls.length).toBe(2);\n      expect(onValueChange.mock.calls[1][0]).toBe(0.012);\n    });\n\n    it('ignores percent and permille symbols when not formatted as percent', async () => {\n      const onValueChange = vi.fn();\n      await render(<NumberField onValueChange={onValueChange} />);\n\n      const input = screen.getByRole('textbox');\n      fireEvent.change(input, { target: { value: '12' } });\n      expect(onValueChange.mock.calls.length).toBe(1);\n      expect(onValueChange.mock.calls[0][0]).toBe(12);\n\n      fireEvent.change(input, { target: { value: '12%' } });\n      fireEvent.change(input, { target: { value: '12‰' } });\n\n      expect(onValueChange.mock.calls.length).toBe(1);\n      expect(input).toHaveValue('12');\n    });\n\n    it('parses trailing unicode minus', async () => {\n      const onValueChange = vi.fn();\n      function App() {\n        const [value, setValue] = React.useState<number | null>(null);\n        return (\n          <NumberField\n            value={value}\n            onValueChange={(v) => {\n              onValueChange(v);\n              setValue(v);\n            }}\n          />\n        );\n      }\n\n      await render(<App />);\n\n      const input = screen.getByRole('textbox');\n      fireEvent.change(input, { target: { value: '1234−' } });\n\n      expect(onValueChange.mock.calls.length).toBe(1);\n      expect(onValueChange.mock.calls[0][0]).toBe(-1234);\n    });\n\n    it('treats parentheses negatives as invalid input', async () => {\n      const onValueChange = vi.fn();\n      function App() {\n        const [value, setValue] = React.useState<number | null>(null);\n        return (\n          <NumberField\n            value={value}\n            onValueChange={(v) => {\n              onValueChange(v);\n              setValue(v);\n            }}\n          />\n        );\n      }\n\n      await render(<App />);\n\n      const input = screen.getByRole('textbox');\n      fireEvent.change(input, { target: { value: '(1,234.5)' } });\n\n      expect(onValueChange.mock.calls.length).toBe(0);\n      expect(input).toHaveValue('');\n    });\n\n    it('collapses extra dots from mixed-locale inputs', async () => {\n      const onValueChange = vi.fn();\n      function App() {\n        const [value, setValue] = React.useState<number | null>(null);\n        return (\n          <NumberField\n            value={value}\n            onValueChange={(v) => {\n              onValueChange(v);\n              setValue(v);\n            }}\n          />\n        );\n      }\n\n      await render(<App />);\n\n      const input = screen.getByRole('textbox');\n      fireEvent.change(input, { target: { value: '1.234.567.89' } });\n\n      expect(onValueChange.mock.calls.length).toBe(1);\n      expect(onValueChange.mock.calls[0][0]).toBe(1234567.89);\n    });\n\n    it('allows composition key events (IME) without preventing default', async () => {\n      await render(<NumberField />);\n\n      const input = screen.getByRole('textbox');\n\n      await act(async () => input.focus());\n\n      const preventDefaultSpy = vi.fn();\n\n      // 229 indicates a composition key event\n      fireEvent.keyDown(input, { which: 229, preventDefault: preventDefaultSpy });\n      expect(preventDefaultSpy).toHaveBeenCalledTimes(0);\n    });\n  });\n\n  describe.skipIf(isJSDOM)('pasting', () => {\n    it('should allow pasting a valid number', async () => {\n      await render(<NumberField />);\n      const input = screen.getByRole('textbox');\n\n      const dataTransfer = new DataTransfer();\n      dataTransfer.setData('text/plain', '123');\n\n      fireEvent.paste(input, { clipboardData: dataTransfer });\n      fireEvent.change(input, { target: { value: '123' } });\n      expect(input).toHaveValue('123');\n    });\n\n    it('should not allow pasting an invalid number', async () => {\n      await render(<NumberField />);\n      const input = screen.getByRole('textbox');\n\n      const dataTransfer = new DataTransfer();\n      dataTransfer.setData('text/plain', 'abc');\n\n      fireEvent.paste(input, { clipboardData: dataTransfer });\n      fireEvent.change(input, { target: { value: 'abc' } });\n      expect(input).toHaveValue('');\n      fireEvent.blur(input);\n      expect(input).toHaveValue('');\n    });\n  });\n\n  it('should allow navigation keys and not prevent their default behavior', async () => {\n    await render(<NumberField />);\n    const input = screen.getByRole('textbox') as HTMLInputElement;\n    input.focus();\n    fireEvent.change(input, { target: { value: '123' } });\n\n    const navigateKeys = ['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab', 'Enter'];\n    navigateKeys.forEach((key) => {\n      const preventDefaultSpy = vi.fn();\n      fireEvent.keyDown(input, { key, preventDefault: preventDefaultSpy });\n      expect(preventDefaultSpy).toHaveBeenCalledTimes(0);\n    });\n  });\n\n  describe('prop: locale', () => {\n    it('should set the locale of the input', async () => {\n      await render(<NumberField defaultValue={1000.5} locale=\"de-DE\" />);\n      const input = screen.getByRole('textbox');\n\n      // In German locale, numbers use dot as thousands separator and comma as decimal separator\n      const expectedValue = new Intl.NumberFormat('de-DE').format(1000.5);\n      expect(input).toHaveValue(expectedValue);\n    });\n\n    it('should use the default locale if no locale is provided', async () => {\n      await render(<NumberField defaultValue={1000.5} />);\n      const input = screen.getByRole('textbox');\n      const expectedValue = new Intl.NumberFormat().format(1000.5);\n      expect(input).toHaveValue(expectedValue);\n    });\n\n    it('should handle locales using space as the thousands separator', async () => {\n      await render(<NumberField defaultValue={12345.5} locale=\"pl\" />);\n\n      const input = screen.getByRole('textbox');\n      const expectedValue = new Intl.NumberFormat('pl').format(12345.5);\n      expect(input).toHaveValue(expectedValue);\n\n      const incrementButton = screen.getByLabelText('Increase');\n      fireEvent.click(incrementButton);\n\n      const newExpectedValue = new Intl.NumberFormat('pl').format(12346.5);\n      expect(input).toHaveValue(newExpectedValue);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/number-field/root/NumberFieldRoot.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useControlled } from '@base-ui/utils/useControlled';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useValueAsRef } from '@base-ui/utils/useValueAsRef';\nimport { useForcedRerendering } from '@base-ui/utils/useForcedRerendering';\nimport { useMergedRefs } from '@base-ui/utils/useMergedRefs';\nimport { visuallyHidden, visuallyHiddenInput } from '@base-ui/utils/visuallyHidden';\nimport { ownerDocument } from '@base-ui/utils/owner';\nimport { isIOS } from '@base-ui/utils/detectBrowser';\nimport { InputMode, NumberFieldRootContext } from './NumberFieldRootContext';\nimport { useFieldRootContext } from '../../field/root/FieldRootContext';\nimport type { FieldRootState } from '../../field/root/FieldRoot';\nimport { useLabelableId } from '../../labelable-provider/useLabelableId';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { stateAttributesMapping } from '../utils/stateAttributesMapping';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport {\n  getNumberLocaleDetails,\n  PERMILLE,\n  PERCENTAGES,\n  SPACE_SEPARATOR_RE,\n  BASE_NON_NUMERIC_SYMBOLS,\n  MINUS_SIGNS_WITH_ASCII,\n  PLUS_SIGNS_WITH_ASCII,\n} from '../utils/parse';\nimport { formatNumber, formatNumberMaxPrecision } from '../../utils/formatNumber';\nimport { DEFAULT_STEP } from '../utils/constants';\nimport { toValidatedNumber } from '../utils/validate';\nimport { EventWithOptionalKeyState } from '../utils/types';\nimport type { ChangeEventCustomProperties, IncrementValueParameters } from '../utils/types';\nimport {\n  createChangeEventDetails,\n  type BaseUIChangeEventDetails,\n  type BaseUIGenericEventDetails,\n  type ReasonToEvent,\n} from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\n\n/**\n * Groups all parts of the number field and manages its state.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Number Field](https://base-ui.com/react/components/number-field)\n */\nexport const NumberFieldRoot = React.forwardRef(function NumberFieldRoot(\n  componentProps: NumberFieldRoot.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const {\n    id: idProp,\n    min,\n    max,\n    smallStep = 0.1,\n    step: stepProp = 1,\n    largeStep = 10,\n    required = false,\n    disabled: disabledProp = false,\n    readOnly = false,\n    name: nameProp,\n    defaultValue,\n    value: valueProp,\n    onValueChange: onValueChangeProp,\n    onValueCommitted: onValueCommittedProp,\n    allowWheelScrub = false,\n    snapOnStep = false,\n    allowOutOfRange = false,\n    format,\n    locale,\n    render,\n    className,\n    inputRef: inputRefProp,\n    ...elementProps\n  } = componentProps;\n\n  const {\n    setDirty,\n    validityData,\n    disabled: fieldDisabled,\n    setFilled,\n    invalid,\n    name: fieldName,\n    state: fieldState,\n    validation,\n    shouldValidateOnChange,\n  } = useFieldRootContext();\n\n  const disabled = fieldDisabled || disabledProp;\n  const name = fieldName ?? nameProp;\n  const step = stepProp === 'any' ? 1 : stepProp;\n\n  const [isScrubbing, setIsScrubbing] = React.useState(false);\n\n  const minWithDefault = min ?? Number.MIN_SAFE_INTEGER;\n  const maxWithDefault = max ?? Number.MAX_SAFE_INTEGER;\n  const minWithZeroDefault = min ?? 0;\n  const formatStyle = format?.style;\n\n  const inputRef = React.useRef<HTMLInputElement>(null);\n  const hiddenInputRef = useMergedRefs(inputRefProp, validation.inputRef);\n\n  const id = useLabelableId({ id: idProp });\n\n  const [valueUnwrapped, setValueUnwrapped] = useControlled<number | null>({\n    controlled: valueProp,\n    default: defaultValue,\n    name: 'NumberField',\n    state: 'value',\n  });\n\n  const value = valueUnwrapped ?? null;\n  const valueRef = useValueAsRef(value);\n\n  useIsoLayoutEffect(() => {\n    setFilled(value !== null);\n  }, [setFilled, value]);\n\n  const forceRender = useForcedRerendering();\n\n  const formatOptionsRef = useValueAsRef(format);\n\n  const hasPendingCommitRef = React.useRef(false);\n\n  const onValueCommitted = useStableCallback(\n    (nextValue: number | null, eventDetails: NumberFieldRoot.CommitEventDetails) => {\n      hasPendingCommitRef.current = false;\n      onValueCommittedProp?.(nextValue, eventDetails);\n    },\n  );\n\n  const allowInputSyncRef = React.useRef(true);\n  const lastChangedValueRef = React.useRef<number | null>(null);\n\n  // During SSR, the value is formatted on the server, whose locale may differ from the client's\n  // locale. This causes a hydration mismatch, which we manually suppress. This is preferable to\n  // rendering an empty input field and then updating it with the formatted value, as the user\n  // can still see the value prior to hydration, even if it's not formatted correctly.\n  const [inputValue, setInputValue] = React.useState(() => {\n    if (valueProp !== undefined) {\n      return getControlledInputValue(value, locale, format);\n    }\n    return formatNumber(value, locale, format);\n  });\n  const [inputMode, setInputMode] = React.useState<InputMode>('numeric');\n\n  const getAllowedNonNumericKeys = useStableCallback(() => {\n    const { decimal, group, currency, literal } = getNumberLocaleDetails(locale, format);\n\n    const keys = new Set<string>();\n    BASE_NON_NUMERIC_SYMBOLS.forEach((symbol) => keys.add(symbol));\n    if (decimal) {\n      keys.add(decimal);\n    }\n    if (group) {\n      keys.add(group);\n      if (SPACE_SEPARATOR_RE.test(group)) {\n        keys.add(' ');\n      }\n    }\n\n    const allowPercentSymbols =\n      formatStyle === 'percent' || (formatStyle === 'unit' && format?.unit === 'percent');\n    const allowPermilleSymbols =\n      formatStyle === 'percent' || (formatStyle === 'unit' && format?.unit === 'permille');\n\n    if (allowPercentSymbols) {\n      PERCENTAGES.forEach((key) => keys.add(key));\n    }\n    if (allowPermilleSymbols) {\n      PERMILLE.forEach((key) => keys.add(key));\n    }\n\n    if (formatStyle === 'currency' && currency) {\n      keys.add(currency);\n    }\n\n    if (literal) {\n      // Some locales (e.g. de-DE) insert a literal space character between the number\n      // and the symbol, so allow those characters to be typed/removed.\n      Array.from(literal).forEach((char) => keys.add(char));\n      if (SPACE_SEPARATOR_RE.test(literal)) {\n        keys.add(' ');\n      }\n    }\n\n    // Allow plus sign in all cases; minus sign only when negatives are valid\n    PLUS_SIGNS_WITH_ASCII.forEach((key) => keys.add(key));\n    if (minWithDefault < 0) {\n      MINUS_SIGNS_WITH_ASCII.forEach((key) => keys.add(key));\n    }\n\n    return keys;\n  });\n\n  const getStepAmount = useStableCallback((event?: EventWithOptionalKeyState) => {\n    if (event?.altKey) {\n      return smallStep;\n    }\n    if (event?.shiftKey) {\n      return largeStep;\n    }\n    return step;\n  });\n\n  const setValue = useStableCallback(\n    (unvalidatedValue: number | null, details: NumberFieldRoot.ChangeEventDetails): boolean => {\n      const eventWithOptionalKeyState = details.event as EventWithOptionalKeyState;\n      const dir = details.direction;\n      const reason = details.reason;\n      // Only allow out-of-range values for direct text entry (native-like behavior).\n      // Step-based interactions (keyboard arrows, buttons, wheel, scrub) still clamp to min/max.\n      const shouldClampValue =\n        !allowOutOfRange ||\n        !(\n          reason === REASONS.inputChange ||\n          reason === REASONS.inputBlur ||\n          reason === REASONS.inputPaste ||\n          reason === REASONS.inputClear ||\n          reason === REASONS.none\n        );\n\n      const validatedValue = toValidatedNumber(unvalidatedValue, {\n        step: dir ? getStepAmount(eventWithOptionalKeyState) * dir : undefined,\n        format: formatOptionsRef.current,\n        minWithDefault,\n        maxWithDefault,\n        minWithZeroDefault,\n        snapOnStep,\n        small: eventWithOptionalKeyState?.altKey ?? false,\n        clamp: shouldClampValue,\n      });\n\n      // Determine whether we should notify about a change even if the numeric value is unchanged.\n      // This is needed when the user input is clamped/snapped to the same current value, or when\n      // the source value differs but validation normalizes to the existing value.\n      const isInputReason =\n        details.reason === REASONS.inputChange ||\n        details.reason === REASONS.inputClear ||\n        details.reason === REASONS.inputBlur ||\n        details.reason === REASONS.inputPaste ||\n        details.reason === REASONS.none;\n      const shouldFireChange =\n        validatedValue !== value ||\n        (isInputReason && (unvalidatedValue !== value || allowInputSyncRef.current === false));\n\n      if (shouldFireChange) {\n        lastChangedValueRef.current = validatedValue;\n        onValueChangeProp?.(validatedValue, details);\n\n        if (details.isCanceled) {\n          return shouldFireChange;\n        }\n\n        setValueUnwrapped(validatedValue);\n        setDirty(validatedValue !== validityData.initialValue);\n        hasPendingCommitRef.current = true;\n      }\n\n      // Keep the visible input in sync immediately when programmatic changes occur\n      // (increment/decrement, wheel, etc). During direct typing we don't want\n      // to overwrite the user-provided text until blur, so we gate on\n      // `allowInputSyncRef`.\n      if (allowInputSyncRef.current) {\n        setInputValue(formatNumber(validatedValue, locale, format));\n      }\n\n      // Formatting can change even if the numeric value hasn't, so ensure a re-render when needed.\n      forceRender();\n\n      return shouldFireChange;\n    },\n  );\n\n  const incrementValue = useStableCallback(\n    (amount: number, { direction, currentValue, event, reason }: IncrementValueParameters) => {\n      const prevValue = currentValue == null ? valueRef.current : currentValue;\n      const nextValue =\n        typeof prevValue === 'number' ? prevValue + amount * direction : Math.max(0, min ?? 0);\n      const nativeEvent = event as ReasonToEvent<IncrementValueParameters['reason']> | undefined;\n      return setValue(\n        nextValue,\n        createChangeEventDetails(reason, nativeEvent, undefined, {\n          direction,\n        }),\n      );\n    },\n  );\n\n  // We need to update the input value when the external `value` prop changes. This ends up acting\n  // as a single source of truth to update the input value, bypassing the need to manually set it in\n  // each event handler internally in this hook.\n  // This is done inside a layout effect as an alternative to the technique to set state during\n  // render as we're accessing a ref, which must be inside an effect.\n  // https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes\n  //\n  // ESLint is disabled because it needs to run even if the parsed value hasn't changed, since the\n  // value still can be formatted differently.\n  // eslint-disable-next-line react-hooks/exhaustive-deps\n  useIsoLayoutEffect(function syncFormattedInputValueOnValueChange() {\n    // This ensures the value is only updated on blur rather than every keystroke, but still\n    // allows the input value to be updated when the value is changed externally.\n    if (!allowInputSyncRef.current) {\n      return;\n    }\n\n    const nextInputValue =\n      valueProp !== undefined\n        ? getControlledInputValue(value, locale, format)\n        : formatNumber(value, locale, format);\n\n    if (nextInputValue !== inputValue) {\n      setInputValue(nextInputValue);\n    }\n  });\n\n  useIsoLayoutEffect(\n    function setDynamicInputModeForIOS() {\n      if (!isIOS) {\n        return;\n      }\n\n      // iOS numeric software keyboard doesn't have a minus key, so we need to use the default\n      // keyboard to let the user input a negative number.\n      let computedInputMode: typeof inputMode = 'text';\n\n      if (minWithDefault >= 0) {\n        // iOS numeric software keyboard doesn't have a decimal key for \"numeric\" input mode, but\n        // this is better than the \"text\" input if possible to use.\n        computedInputMode = 'decimal';\n      }\n\n      setInputMode(computedInputMode);\n    },\n    [minWithDefault, formatStyle],\n  );\n\n  // The `onWheel` prop can't be prevented, so we need to use a global event listener.\n  React.useEffect(\n    function registerElementWheelListener() {\n      const element = inputRef.current;\n      if (disabled || readOnly || !allowWheelScrub || !element) {\n        return undefined;\n      }\n\n      function handleWheel(event: WheelEvent) {\n        if (\n          // Allow pinch-zooming.\n          event.ctrlKey ||\n          ownerDocument(inputRef.current).activeElement !== inputRef.current\n        ) {\n          return;\n        }\n\n        // Prevent the default behavior to avoid scrolling the page.\n        event.preventDefault();\n\n        const amount = getStepAmount(event) ?? DEFAULT_STEP;\n\n        incrementValue(amount, {\n          direction: event.deltaY > 0 ? -1 : 1,\n          event,\n          reason: 'wheel',\n        });\n      }\n\n      element.addEventListener('wheel', handleWheel);\n\n      return () => {\n        element.removeEventListener('wheel', handleWheel);\n      };\n    },\n    [allowWheelScrub, incrementValue, disabled, readOnly, largeStep, step, getStepAmount],\n  );\n\n  const state: NumberFieldRootState = React.useMemo(\n    () => ({\n      ...fieldState,\n      disabled,\n      readOnly,\n      required,\n      value,\n      inputValue,\n      scrubbing: isScrubbing,\n    }),\n    [fieldState, disabled, readOnly, required, value, inputValue, isScrubbing],\n  );\n\n  const contextValue: NumberFieldRootContext = React.useMemo(\n    () => ({\n      inputRef,\n      inputValue,\n      value,\n      minWithDefault,\n      maxWithDefault,\n      disabled,\n      readOnly,\n      id,\n      setValue,\n      incrementValue,\n      getStepAmount,\n      allowInputSyncRef,\n      formatOptionsRef,\n      valueRef,\n      lastChangedValueRef,\n      hasPendingCommitRef,\n      name,\n      required,\n      invalid,\n      inputMode,\n      getAllowedNonNumericKeys,\n      min,\n      max,\n      setInputValue,\n      locale,\n      isScrubbing,\n      setIsScrubbing,\n      state,\n      onValueCommitted,\n    }),\n    [\n      inputRef,\n      inputValue,\n      value,\n      minWithDefault,\n      maxWithDefault,\n      disabled,\n      readOnly,\n      id,\n      setValue,\n      incrementValue,\n      getStepAmount,\n      formatOptionsRef,\n      valueRef,\n      name,\n      required,\n      invalid,\n      inputMode,\n      getAllowedNonNumericKeys,\n      min,\n      max,\n      setInputValue,\n      locale,\n      isScrubbing,\n      state,\n      onValueCommitted,\n    ],\n  );\n\n  const element = useRenderElement('div', componentProps, {\n    ref: forwardedRef,\n    state,\n    props: elementProps,\n    stateAttributesMapping,\n  });\n\n  return (\n    <NumberFieldRootContext.Provider value={contextValue}>\n      {element}\n      <input\n        {...validation.getInputValidationProps({\n          onFocus() {\n            inputRef.current?.focus();\n          },\n          onChange(event) {\n            // Workaround for https://github.com/facebook/react/issues/9023\n            if (event.nativeEvent.defaultPrevented) {\n              return;\n            }\n\n            // Handle browser autofill.\n            const nextValue = event.currentTarget.valueAsNumber;\n            const parsedValue = Number.isNaN(nextValue) ? null : nextValue;\n            const details = createChangeEventDetails(REASONS.none, event.nativeEvent);\n\n            setDirty(parsedValue !== validityData.initialValue);\n            setValue(parsedValue, details);\n\n            if (shouldValidateOnChange()) {\n              validation.commit(parsedValue);\n            }\n          },\n        })}\n        ref={hiddenInputRef}\n        type=\"number\"\n        name={name}\n        value={value ?? ''}\n        min={min}\n        max={max}\n        // stepMismatch validation is broken unless an explicit `min` is added.\n        // See https://github.com/facebook/react/issues/12334.\n        step={stepProp}\n        disabled={disabled}\n        required={required}\n        aria-hidden\n        tabIndex={-1}\n        style={name ? visuallyHiddenInput : visuallyHidden}\n      />\n    </NumberFieldRootContext.Provider>\n  );\n});\n\nexport interface NumberFieldRootProps extends Omit<\n  BaseUIComponentProps<'div', NumberFieldRootState>,\n  'onChange'\n> {\n  /**\n   * The id of the input element.\n   */\n  id?: string | undefined;\n  /**\n   * The minimum value of the input element.\n   */\n  min?: number | undefined;\n  /**\n   * The maximum value of the input element.\n   */\n  max?: number | undefined;\n  /**\n   * When true, direct text entry may be outside the `min`/`max` range without clamping,\n   * so native range underflow/overflow validation can occur.\n   * Step-based interactions (keyboard arrows, buttons, wheel, scrub) still clamp.\n   * @default false\n   */\n  allowOutOfRange?: boolean | undefined;\n  /**\n   * The small step value of the input element when incrementing while the meta key is held. Snaps\n   * to multiples of this value.\n   * @default 0.1\n   */\n  smallStep?: number | undefined;\n  /**\n   * Amount to increment and decrement with the buttons and arrow keys, or to scrub with pointer movement in the scrub area.\n   * To always enable step validation on form submission, specify the `min` prop explicitly in conjunction with this prop.\n   * Specify `step=\"any\"` to always disable step validation.\n   * @default 1\n   */\n  step?: number | 'any' | undefined;\n  /**\n   * The large step value of the input element when incrementing while the shift key is held. Snaps\n   * to multiples of this value.\n   * @default 10\n   */\n  largeStep?: number | undefined;\n  /**\n   * Whether the user must enter a value before submitting a form.\n   * @default false\n   */\n  required?: boolean | undefined;\n  /**\n   * Whether the component should ignore user interaction.\n   * @default false\n   */\n  disabled?: boolean | undefined;\n  /**\n   * Whether the user should be unable to change the field value.\n   * @default false\n   */\n  readOnly?: boolean | undefined;\n  /**\n   * Identifies the field when a form is submitted.\n   */\n  name?: string | undefined;\n  /**\n   * The raw numeric value of the field.\n   */\n  value?: number | null | undefined;\n  /**\n   * The uncontrolled value of the field when it’s initially rendered.\n   *\n   * To render a controlled number field, use the `value` prop instead.\n   */\n  defaultValue?: number | undefined;\n  /**\n   * Whether to allow the user to scrub the input value with the mouse wheel while focused and\n   * hovering over the input.\n   * @default false\n   */\n  allowWheelScrub?: boolean | undefined;\n  /**\n   * Whether the value should snap to the nearest step when incrementing or decrementing.\n   * @default false\n   */\n  snapOnStep?: boolean | undefined;\n  /**\n   * Options to format the input value.\n   */\n  format?: Intl.NumberFormatOptions | undefined;\n  /**\n   * Callback fired when the number value changes.\n   *\n   * The `eventDetails.reason` indicates what triggered the change:\n   * - `'input-change'` for parseable typing or programmatic text updates\n   * - `'input-clear'` when the field becomes empty\n   * - `'input-blur'` when formatting (and clamping, if enabled) occurs on blur\n   * - `'input-paste'` for paste interactions\n   * - `'keyboard'` for keyboard input\n   * - `'increment-press'` / `'decrement-press'` for button presses on the increment and decrement controls\n   * - `'wheel'` for wheel-based scrubbing\n   * - `'scrub'` for scrub area drags\n   */\n  onValueChange?:\n    | ((value: number | null, eventDetails: NumberFieldRoot.ChangeEventDetails) => void)\n    | undefined;\n  /**\n   * Callback function that is fired when the value is committed.\n   * It runs later than `onValueChange`, when:\n   * - The input is blurred after typing a value.\n   * - The pointer is released after scrubbing or pressing the increment/decrement buttons.\n   *\n   * It runs simultaneously with `onValueChange` when interacting with the keyboard.\n   *\n   * **Warning**: This is a generic event not a change event.\n   */\n  onValueCommitted?:\n    | ((value: number | null, eventDetails: NumberFieldRoot.CommitEventDetails) => void)\n    | undefined;\n  /**\n   * The locale of the input element.\n   * Defaults to the user's runtime locale.\n   */\n  locale?: Intl.LocalesArgument | undefined;\n  /**\n   * A ref to access the hidden input element.\n   */\n  inputRef?: React.Ref<HTMLInputElement> | undefined;\n}\n\nexport interface NumberFieldRootState extends FieldRootState {\n  /**\n   * The raw numeric value of the field.\n   */\n  value: number | null;\n  /**\n   * The formatted string value presented in the input element.\n   */\n  inputValue: string;\n  /**\n   * Whether the user must enter a value before submitting a form.\n   */\n  required: boolean;\n  /**\n   * Whether the component should ignore user interaction.\n   */\n  disabled: boolean;\n  /**\n   * Whether the user should be unable to change the field value.\n   */\n  readOnly: boolean;\n  /**\n   * Whether the user is currently scrubbing the field.\n   */\n  scrubbing: boolean;\n}\n\nexport type NumberFieldRootChangeEventReason =\n  | typeof REASONS.inputChange\n  | typeof REASONS.inputClear\n  | typeof REASONS.inputBlur\n  | typeof REASONS.inputPaste\n  | typeof REASONS.keyboard\n  | typeof REASONS.incrementPress\n  | typeof REASONS.decrementPress\n  | typeof REASONS.wheel\n  | typeof REASONS.scrub\n  | typeof REASONS.none;\nexport type NumberFieldRootChangeEventDetails = BaseUIChangeEventDetails<\n  NumberFieldRootChangeEventReason,\n  ChangeEventCustomProperties\n>;\n\nexport type NumberFieldRootCommitEventReason =\n  | typeof REASONS.inputBlur\n  | typeof REASONS.inputClear\n  | typeof REASONS.keyboard\n  | typeof REASONS.incrementPress\n  | typeof REASONS.decrementPress\n  | typeof REASONS.wheel\n  | typeof REASONS.scrub\n  | typeof REASONS.none;\nexport type NumberFieldRootCommitEventDetails =\n  BaseUIGenericEventDetails<NumberFieldRoot.CommitEventReason>;\n\nfunction getControlledInputValue(\n  value: number | null,\n  locale: Intl.LocalesArgument,\n  format: Intl.NumberFormatOptions | undefined,\n) {\n  const explicitPrecision =\n    format?.maximumFractionDigits != null || format?.minimumFractionDigits != null;\n  return explicitPrecision\n    ? formatNumber(value, locale, format)\n    : formatNumberMaxPrecision(value, locale, format);\n}\n\nexport namespace NumberFieldRoot {\n  export type State = NumberFieldRootState;\n  export type Props = NumberFieldRootProps;\n  export type ChangeEventReason = NumberFieldRootChangeEventReason;\n  export type ChangeEventDetails = NumberFieldRootChangeEventDetails;\n  export type CommitEventReason = NumberFieldRootCommitEventReason;\n  export type CommitEventDetails = NumberFieldRootCommitEventDetails;\n}\n"
  },
  {
    "path": "packages/react/src/number-field/root/NumberFieldRootContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport type { NumberFieldRoot, NumberFieldRootState } from './NumberFieldRoot';\nimport { EventWithOptionalKeyState } from '../utils/types';\nimport type { IncrementValueParameters } from '../utils/types';\n\nexport type InputMode = 'numeric' | 'decimal' | 'text';\n\nexport interface NumberFieldRootContext {\n  inputValue: string;\n  value: number | null;\n  minWithDefault: number;\n  maxWithDefault: number;\n  disabled: boolean;\n  readOnly: boolean;\n  id: string | undefined;\n  setValue: (value: number | null, details: NumberFieldRoot.ChangeEventDetails) => boolean;\n  getStepAmount: (event?: EventWithOptionalKeyState) => number | undefined;\n  incrementValue: (amount: number, params: IncrementValueParameters) => boolean;\n  inputRef: React.RefObject<HTMLInputElement | null>;\n  allowInputSyncRef: React.RefObject<boolean | null>;\n  formatOptionsRef: React.RefObject<Intl.NumberFormatOptions | undefined>;\n  valueRef: React.RefObject<number | null>;\n  lastChangedValueRef: React.RefObject<number | null>;\n  hasPendingCommitRef: React.RefObject<boolean>;\n  name: string | undefined;\n  required: boolean;\n  invalid: boolean | undefined;\n  inputMode: InputMode;\n  getAllowedNonNumericKeys: () => Set<string | undefined>;\n  min: number | undefined;\n  max: number | undefined;\n  setInputValue: React.Dispatch<React.SetStateAction<string>>;\n  locale: Intl.LocalesArgument;\n  isScrubbing: boolean;\n  setIsScrubbing: React.Dispatch<React.SetStateAction<boolean>>;\n  state: NumberFieldRootState;\n  onValueCommitted: (\n    value: number | null,\n    eventDetails: NumberFieldRoot.CommitEventDetails,\n  ) => void;\n}\n\nexport const NumberFieldRootContext = React.createContext<NumberFieldRootContext | undefined>(\n  undefined,\n);\n\nexport function useNumberFieldRootContext() {\n  const context = React.useContext(NumberFieldRootContext);\n  if (context === undefined) {\n    throw new Error(\n      'Base UI: NumberFieldRootContext is missing. NumberField parts must be placed within <NumberField.Root>.',\n    );\n  }\n\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/number-field/root/NumberFieldRootDataAttributes.ts",
    "content": "export enum NumberFieldRootDataAttributes {\n  /**\n   * Present while scrubbing.\n   */\n  scrubbing = 'data-scrubbing',\n  /**\n   * Present when the number field is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the number field is readonly.\n   */\n  readonly = 'data-readonly',\n  /**\n   * Present when the number field is required.\n   */\n  required = 'data-required',\n  /**\n   * Present when the number field is in valid state (when wrapped in Field.Root).\n   */\n  valid = 'data-valid',\n  /**\n   * Present when the number field is in invalid state (when wrapped in Field.Root).\n   */\n  invalid = 'data-invalid',\n  /**\n   * Present when the number field has been touched (when wrapped in Field.Root).\n   */\n  touched = 'data-touched',\n  /**\n   * Present when the number field's value has changed (when wrapped in Field.Root).\n   */\n  dirty = 'data-dirty',\n  /**\n   * Present when the number field is filled (when wrapped in Field.Root).\n   */\n  filled = 'data-filled',\n  /**\n   * Present when the number field is focused (when wrapped in Field.Root).\n   */\n  focused = 'data-focused',\n}\n"
  },
  {
    "path": "packages/react/src/number-field/root/useNumberFieldButton.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { usePressAndHold } from '../../utils/usePressAndHold';\nimport {\n  DEFAULT_STEP,\n  CHANGE_VALUE_TICK_DELAY,\n  START_AUTO_CHANGE_DELAY,\n  SCROLLING_POINTER_MOVE_DISTANCE,\n} from '../utils/constants';\nimport { parseNumber } from '../utils/parse';\nimport {\n  createChangeEventDetails,\n  createGenericEventDetails,\n} from '../../utils/createBaseUIEventDetails';\nimport type {\n  EventWithOptionalKeyState,\n  Direction,\n  IncrementValueParameters,\n} from '../utils/types';\nimport type { NumberFieldRoot } from './NumberFieldRoot';\nimport type { HTMLProps } from '../../utils/types';\nimport { REASONS } from '../../utils/reasons';\n\n// Treat pen as touch-like to avoid forcing the software keyboard on stylus taps.\n// Linux Chrome may emit \"pen\" historically for mouse usage due to a bug, but the touch path\n// still works with minor behavioral differences.\nfunction isTouchLikePointerType(pointerType: string) {\n  return pointerType === 'touch' || pointerType === 'pen';\n}\n\nexport function useNumberFieldButton(params: UseNumberFieldButtonParameters) {\n  const {\n    allowInputSyncRef,\n    disabled,\n    formatOptionsRef,\n    getStepAmount,\n    id,\n    incrementValue,\n    inputRef,\n    inputValue,\n    isIncrement,\n    locale,\n    readOnly,\n    setValue,\n    valueRef,\n    lastChangedValueRef,\n    onValueCommitted,\n  } = params;\n\n  const pressReason: NumberFieldRoot.ChangeEventReason = isIncrement\n    ? REASONS.incrementPress\n    : REASONS.decrementPress;\n\n  function commitValue(nativeEvent: MouseEvent) {\n    allowInputSyncRef.current = true;\n\n    // The input may be dirty but not yet blurred, so the value won't have been committed.\n    const parsedValue = parseNumber(inputValue, locale, formatOptionsRef.current);\n\n    if (parsedValue !== null) {\n      // The increment value function needs to know the current input value to increment it\n      // correctly.\n      valueRef.current = parsedValue;\n      setValue(\n        parsedValue,\n        createChangeEventDetails<\n          NumberFieldRoot.ChangeEventReason,\n          { direction?: Direction | undefined }\n        >(pressReason, nativeEvent, undefined, {\n          direction: isIncrement ? 1 : -1,\n        }),\n      );\n    }\n  }\n\n  const { pointerHandlers, shouldSkipClick } = usePressAndHold({\n    disabled: disabled || readOnly,\n    elementRef: inputRef,\n    tickDelay: CHANGE_VALUE_TICK_DELAY,\n    startDelay: START_AUTO_CHANGE_DELAY,\n    scrollDistance: SCROLLING_POINTER_MOVE_DISTANCE,\n    tick(triggerEvent) {\n      const amount = getStepAmount(triggerEvent as EventWithOptionalKeyState) ?? DEFAULT_STEP;\n      return incrementValue(amount, {\n        direction: isIncrement ? 1 : -1,\n        event: triggerEvent,\n        reason: pressReason,\n      });\n    },\n    onStop(nativeEvent: PointerEvent) {\n      const committed = lastChangedValueRef.current ?? valueRef.current;\n      onValueCommitted(committed, createGenericEventDetails(pressReason, nativeEvent));\n    },\n  });\n\n  const props: React.ComponentProps<'button'> = {\n    disabled,\n    'aria-readonly': readOnly || undefined,\n    'aria-label': isIncrement ? 'Increase' : 'Decrease',\n    'aria-controls': id,\n    // Keyboard users shouldn't have access to the buttons, since they can use the input element\n    // to change the value. On the other hand, `aria-hidden` is not applied because touch screen\n    // readers should be able to use the buttons.\n    tabIndex: -1,\n    style: {\n      WebkitUserSelect: 'none',\n      userSelect: 'none',\n    },\n    ...pointerHandlers,\n    onClick(event) {\n      const isDisabled = disabled || readOnly;\n      if (event.defaultPrevented || isDisabled || shouldSkipClick(event)) {\n        return;\n      }\n\n      commitValue(event.nativeEvent);\n\n      const amount = getStepAmount(event) ?? DEFAULT_STEP;\n\n      const prev = valueRef.current;\n\n      incrementValue(amount, {\n        direction: isIncrement ? 1 : -1,\n        event: event.nativeEvent,\n        reason: pressReason,\n      });\n\n      const committed = lastChangedValueRef.current ?? valueRef.current;\n      if (committed !== prev) {\n        onValueCommitted(committed, createGenericEventDetails(pressReason, event.nativeEvent));\n      }\n    },\n    onPointerDown(event) {\n      const isMainButton = !event.button || event.button === 0;\n      if (event.defaultPrevented || readOnly || !isMainButton || disabled) {\n        return;\n      }\n\n      // Sync dirty input value before starting the hold sequence.\n      commitValue(event.nativeEvent);\n\n      if (!isTouchLikePointerType(event.pointerType)) {\n        // Focus the input so the user can continue with keyboard interactions.\n        inputRef.current?.focus();\n      }\n\n      pointerHandlers.onPointerDown(event);\n    },\n  };\n\n  return props;\n}\n\nexport interface UseNumberFieldButtonParameters {\n  allowInputSyncRef: React.RefObject<boolean | null>;\n  disabled: boolean;\n  formatOptionsRef: React.RefObject<Intl.NumberFormatOptions | undefined>;\n  getStepAmount: (event?: EventWithOptionalKeyState) => number | undefined;\n  id: string | undefined;\n  incrementValue: (amount: number, params: IncrementValueParameters) => boolean;\n  inputRef: React.RefObject<HTMLInputElement | null>;\n  inputValue: string;\n  isIncrement: boolean;\n  locale?: Intl.LocalesArgument | undefined;\n  readOnly: boolean;\n  setValue: (value: number | null, details: NumberFieldRoot.ChangeEventDetails) => boolean;\n  valueRef: React.RefObject<number | null>;\n  lastChangedValueRef: React.RefObject<number | null>;\n  onValueCommitted: (\n    value: number | null,\n    eventDetails: NumberFieldRoot.CommitEventDetails,\n  ) => void;\n}\n\nexport interface UseNumberFieldButtonReturnValue {\n  props: HTMLProps;\n}\n\nexport interface UseNumberFieldButtonState {}\n"
  },
  {
    "path": "packages/react/src/number-field/scrub-area/NumberFieldScrubArea.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport { screen, act } from '@mui/internal-test-utils';\nimport { NumberField } from '@base-ui/react/number-field';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\nimport { isWebKit } from '@base-ui/utils/detectBrowser';\n\n// TODO (@Janpot): Contribute https://github.com/testing-library/user-event/issues/903 and\n// rely on `user.pointer()` instead.\nlet currentPos = { clientX: 0, clientY: 0 };\n\nfunction createPointerDownEvent(elm: HTMLElement) {\n  const box = elm.getBoundingClientRect();\n  const centerX = box.left + box.width / 2;\n  const centerY = box.top + box.height / 2;\n  currentPos = { clientX: centerX, clientY: centerY };\n  return new PointerEvent('pointerdown', {\n    bubbles: true,\n    ...currentPos,\n  });\n}\n\nfunction createPointerMoveEvent({ movementX = 0, movementY = 0 }) {\n  currentPos = {\n    clientX: currentPos.clientX + movementX,\n    clientY: currentPos.clientY + movementY,\n  };\n  return new PointerEvent('pointermove', {\n    bubbles: true,\n    ...currentPos,\n    movementX,\n    movementY,\n  });\n}\n\ndescribe('<NumberField.ScrubArea />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<NumberField.ScrubArea />, () => ({\n    refInstanceof: window.HTMLSpanElement,\n    render: async (node) => {\n      return render(<NumberField.Root>{node}</NumberField.Root>);\n    },\n  }));\n\n  it('has presentation role', async () => {\n    await render(\n      <NumberField.Root>\n        <NumberField.ScrubArea />\n      </NumberField.Root>,\n    );\n    expect(screen.queryByRole('presentation')).not.toBe(null);\n  });\n\n  // Only run the following tests in Chromium/Firefox.\n  if (isJSDOM || isWebKit) {\n    return;\n  }\n\n  it('should increment or decrement the value when scrubbing with the pointer', async () => {\n    await render(\n      <NumberField.Root defaultValue={0}>\n        <NumberField.Input />\n        <NumberField.ScrubArea data-testid=\"scrub-area\">\n          <NumberField.ScrubAreaCursor />\n        </NumberField.ScrubArea>\n      </NumberField.Root>,\n    );\n\n    const scrubArea = screen.getByTestId('scrub-area');\n    const input = screen.getByRole('textbox');\n\n    await act(async () => {\n      scrubArea.dispatchEvent(createPointerDownEvent(scrubArea));\n      scrubArea.dispatchEvent(createPointerMoveEvent({ movementX: -10 }));\n    });\n\n    expect(input).toHaveValue('-10');\n    await act(async () => {\n      scrubArea.dispatchEvent(createPointerMoveEvent({ movementX: 5 }));\n    });\n\n    expect(input).toHaveValue('-5');\n\n    await act(async () => {\n      scrubArea.dispatchEvent(createPointerMoveEvent({ movementX: -2 }));\n    });\n\n    expect(input).toHaveValue('-7');\n  });\n\n  it('calls onValueChange while scrubbing and onValueCommitted on pointerup', async () => {\n    const onValueChange = vi.fn();\n    const onValueCommitted = vi.fn();\n\n    await render(\n      <NumberField.Root\n        defaultValue={0}\n        onValueChange={onValueChange}\n        onValueCommitted={onValueCommitted}\n      >\n        <NumberField.Input />\n        <NumberField.ScrubArea data-testid=\"scrub-area\">\n          <NumberField.ScrubAreaCursor />\n        </NumberField.ScrubArea>\n      </NumberField.Root>,\n    );\n\n    const scrubArea = screen.getByTestId('scrub-area');\n\n    await act(async () => {\n      scrubArea.dispatchEvent(createPointerDownEvent(scrubArea));\n      scrubArea.dispatchEvent(createPointerMoveEvent({ movementX: 3 }));\n    });\n\n    // One or more changes depending on pixel sensitivity and environment\n    expect(onValueChange.mock.calls.length).toBeGreaterThan(0);\n\n    await act(async () => {\n      window.dispatchEvent(new PointerEvent('pointerup', { bubbles: true }));\n    });\n\n    expect(onValueCommitted.mock.calls.length).toBe(1);\n\n    const lastChange = onValueChange.mock.lastCall?.[0];\n    const committed = onValueCommitted.mock.calls[0][0];\n    expect(committed).toBe(lastChange);\n  });\n\n  describe('prop: pixelSensitivity', () => {\n    it('should only increment if the pointer movement was greater than or equal to the value', async () => {\n      await render(\n        <NumberField.Root defaultValue={0}>\n          <NumberField.Input />\n          <NumberField.ScrubArea data-testid=\"scrub-area\" pixelSensitivity={5}>\n            <NumberField.ScrubAreaCursor />\n          </NumberField.ScrubArea>\n        </NumberField.Root>,\n      );\n\n      const scrubArea = screen.getByTestId('scrub-area');\n      const input = screen.getByRole('textbox');\n\n      await act(async () => {\n        scrubArea.dispatchEvent(createPointerDownEvent(scrubArea));\n        scrubArea.dispatchEvent(createPointerMoveEvent({ movementX: -2 }));\n      });\n\n      expect(input).toHaveValue('0');\n\n      await act(async () => {\n        scrubArea.dispatchEvent(createPointerMoveEvent({ movementX: 2 }));\n      });\n\n      expect(input).toHaveValue('0');\n\n      await act(async () => {\n        scrubArea.dispatchEvent(createPointerMoveEvent({ movementX: 1 }));\n        scrubArea.dispatchEvent(createPointerMoveEvent({ movementX: 1 }));\n        scrubArea.dispatchEvent(createPointerMoveEvent({ movementX: 1 }));\n        scrubArea.dispatchEvent(createPointerMoveEvent({ movementX: 1 }));\n      });\n\n      expect(input).toHaveValue('0');\n\n      await act(async () => {\n        scrubArea.dispatchEvent(createPointerMoveEvent({ movementX: 1 }));\n      });\n\n      expect(input).toHaveValue('1');\n\n      await act(async () => {\n        scrubArea.dispatchEvent(createPointerMoveEvent({ movementX: 5 }));\n      });\n\n      expect(input).toHaveValue('6');\n\n      await act(async () => {\n        scrubArea.dispatchEvent(createPointerMoveEvent({ movementX: -4 }));\n      });\n\n      expect(input).toHaveValue('6');\n\n      await act(async () => {\n        scrubArea.dispatchEvent(createPointerMoveEvent({ movementX: -1 }));\n      });\n\n      expect(input).toHaveValue('5');\n\n      await act(async () => {\n        scrubArea.dispatchEvent(createPointerMoveEvent({ movementX: 5 }));\n      });\n\n      expect(input).toHaveValue('10');\n    });\n  });\n\n  describe('prop: direction', () => {\n    it('should only scrub if the pointer moved in the given direction', async () => {\n      await render(\n        <NumberField.Root defaultValue={0}>\n          <NumberField.Input />\n          <NumberField.ScrubArea data-testid=\"scrub-area\" direction=\"horizontal\">\n            <NumberField.ScrubAreaCursor />\n          </NumberField.ScrubArea>\n        </NumberField.Root>,\n      );\n\n      const scrubArea = screen.getByTestId('scrub-area');\n      const input = screen.getByRole('textbox');\n\n      await act(async () => {\n        scrubArea.dispatchEvent(createPointerDownEvent(scrubArea));\n        scrubArea.dispatchEvent(createPointerMoveEvent({ movementX: 10 }));\n      });\n\n      expect(input).toHaveValue('10');\n\n      await act(async () => {\n        scrubArea.dispatchEvent(createPointerMoveEvent({ movementY: 10 }));\n      });\n\n      expect(input).toHaveValue('10');\n    });\n  });\n\n  it('should fire onClick when clicked without scrubbing', async () => {\n    const handleClick = vi.fn();\n\n    const { user } = await render(\n      <NumberField.Root defaultValue={0}>\n        <NumberField.ScrubArea data-testid=\"scrub-area\" onClick={handleClick}>\n          <NumberField.ScrubAreaCursor />\n        </NumberField.ScrubArea>\n      </NumberField.Root>,\n    );\n\n    await user.click(screen.getByTestId('scrub-area'));\n\n    expect(handleClick.mock.calls.length).toBe(1);\n  });\n\n  it('should fire onClick on child elements', async () => {\n    const handleScrubAreaClick = vi.fn();\n    const handleLabelClick = vi.fn();\n\n    const { user } = await render(\n      <NumberField.Root defaultValue={0}>\n        <NumberField.ScrubArea onClick={handleScrubAreaClick}>\n          {/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions */}\n          <label onClick={handleLabelClick}>Amount</label>\n          <NumberField.ScrubAreaCursor />\n        </NumberField.ScrubArea>\n      </NumberField.Root>,\n    );\n\n    await user.click(screen.getByText('Amount'));\n\n    expect(handleLabelClick.mock.calls.length).toBe(1);\n    expect(handleScrubAreaClick.mock.calls.length).toBe(1);\n  });\n});\n"
  },
  {
    "path": "packages/react/src/number-field/scrub-area/NumberFieldScrubArea.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport { ownerWindow, ownerDocument } from '@base-ui/utils/owner';\nimport { isFirefox, isWebKit } from '@base-ui/utils/detectBrowser';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useTimeout } from '@base-ui/utils/useTimeout';\nimport type { BaseUIComponentProps, HTMLProps } from '../../utils/types';\nimport { useNumberFieldRootContext } from '../root/NumberFieldRootContext';\nimport type { NumberFieldRootState } from '../root/NumberFieldRoot';\nimport { stateAttributesMapping } from '../utils/stateAttributesMapping';\nimport { NumberFieldScrubAreaContext } from './NumberFieldScrubAreaContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { getViewportRect } from '../utils/getViewportRect';\nimport { subscribeToVisualViewportResize } from '../utils/subscribeToVisualViewportResize';\nimport { DEFAULT_STEP } from '../utils/constants';\nimport { createGenericEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\n\n/**\n * An interactive area where the user can click and drag to change the field value.\n * Renders a `<span>` element.\n *\n * Documentation: [Base UI Number Field](https://base-ui.com/react/components/number-field)\n */\nexport const NumberFieldScrubArea = React.forwardRef(function NumberFieldScrubArea(\n  componentProps: NumberFieldScrubArea.Props,\n  forwardedRef: React.ForwardedRef<HTMLSpanElement>,\n) {\n  const {\n    render,\n    className,\n    direction = 'horizontal',\n    pixelSensitivity = 2,\n    teleportDistance,\n    ...elementProps\n  } = componentProps;\n\n  const {\n    state,\n    setIsScrubbing: setRootScrubbing,\n    disabled,\n    readOnly,\n    inputRef,\n    incrementValue,\n    getStepAmount,\n    onValueCommitted,\n    lastChangedValueRef,\n    valueRef,\n  } = useNumberFieldRootContext();\n\n  const scrubAreaRef = React.useRef<HTMLSpanElement>(null);\n\n  const isScrubbingRef = React.useRef(false);\n  const didMoveRef = React.useRef(false);\n  const pointerDownTargetRef = React.useRef<EventTarget | null>(null);\n  const scrubAreaCursorRef = React.useRef<HTMLSpanElement>(null);\n  const virtualCursorCoords = React.useRef({ x: 0, y: 0 });\n  const visualScaleRef = React.useRef(1);\n\n  const exitPointerLockTimeout = useTimeout();\n\n  const [isTouchInput, setIsTouchInput] = React.useState(false);\n  const [isPointerLockDenied, setIsPointerLockDenied] = React.useState(false);\n  const [isScrubbing, setIsScrubbing] = React.useState(false);\n\n  React.useEffect(() => {\n    if (!isScrubbing || !scrubAreaCursorRef.current) {\n      return undefined;\n    }\n\n    return subscribeToVisualViewportResize(scrubAreaCursorRef.current, visualScaleRef);\n  }, [isScrubbing]);\n\n  function updateCursorTransform(x: number, y: number) {\n    if (scrubAreaCursorRef.current) {\n      scrubAreaCursorRef.current.style.transform = `translate3d(${x}px,${y}px,0) scale(${1 / visualScaleRef.current})`;\n    }\n  }\n\n  const onScrub = useStableCallback(({ movementX, movementY }: PointerEvent) => {\n    const virtualCursor = scrubAreaCursorRef.current;\n    const scrubAreaEl = scrubAreaRef.current;\n\n    if (!virtualCursor || !scrubAreaEl) {\n      return;\n    }\n\n    const rect = getViewportRect(teleportDistance, scrubAreaEl);\n\n    const coords = virtualCursorCoords.current;\n    const newCoords = {\n      x: Math.round(coords.x + movementX),\n      y: Math.round(coords.y + movementY),\n    };\n\n    const cursorWidth = virtualCursor.offsetWidth;\n    const cursorHeight = virtualCursor.offsetHeight;\n\n    if (newCoords.x + cursorWidth / 2 < rect.x) {\n      newCoords.x = rect.width - cursorWidth / 2;\n    } else if (newCoords.x + cursorWidth / 2 > rect.width) {\n      newCoords.x = rect.x - cursorWidth / 2;\n    }\n\n    if (newCoords.y + cursorHeight / 2 < rect.y) {\n      newCoords.y = rect.height - cursorHeight / 2;\n    } else if (newCoords.y + cursorHeight / 2 > rect.height) {\n      newCoords.y = rect.y - cursorHeight / 2;\n    }\n\n    virtualCursorCoords.current = newCoords;\n\n    updateCursorTransform(newCoords.x, newCoords.y);\n  });\n\n  const onScrubbingChange = useStableCallback(\n    (scrubbingValue: boolean, { clientX, clientY }: PointerEvent) => {\n      ReactDOM.flushSync(() => {\n        setIsScrubbing(scrubbingValue);\n        setRootScrubbing(scrubbingValue);\n      });\n\n      const virtualCursor = scrubAreaCursorRef.current;\n      if (!virtualCursor || !scrubbingValue) {\n        return;\n      }\n\n      const initialCoords = {\n        x: clientX - virtualCursor.offsetWidth / 2,\n        y: clientY - virtualCursor.offsetHeight / 2,\n      };\n\n      virtualCursorCoords.current = initialCoords;\n\n      updateCursorTransform(initialCoords.x, initialCoords.y);\n    },\n  );\n\n  React.useEffect(\n    function registerGlobalScrubbingEventListeners() {\n      // Only listen while actively scrubbing; avoids unrelated pointerup events committing.\n      if (!inputRef.current || disabled || readOnly || !isScrubbing) {\n        return undefined;\n      }\n\n      let cumulativeDelta = 0;\n\n      function handleScrubPointerUp(event: PointerEvent) {\n        function handler() {\n          try {\n            ownerDocument(scrubAreaRef.current).exitPointerLock();\n          } catch {\n            // Ignore errors.\n          } finally {\n            isScrubbingRef.current = false;\n            onScrubbingChange(false, event);\n            onValueCommitted(\n              lastChangedValueRef.current ?? valueRef.current,\n              createGenericEventDetails(REASONS.scrub, event),\n            );\n\n            // Manually dispatch a click event if no movement happened, since\n            // preventDefault on pointerdown prevents the browser click event.\n            if (!didMoveRef.current && pointerDownTargetRef.current != null) {\n              pointerDownTargetRef.current.dispatchEvent(\n                new MouseEvent('click', { bubbles: true, cancelable: true }),\n              );\n            }\n\n            didMoveRef.current = false;\n            pointerDownTargetRef.current = null;\n          }\n        }\n\n        if (isFirefox) {\n          // Firefox needs a small delay here when soft-clicking as the pointer\n          // lock will not release otherwise.\n          exitPointerLockTimeout.start(20, handler);\n        } else {\n          handler();\n        }\n      }\n\n      function handleScrubPointerMove(event: PointerEvent) {\n        if (!isScrubbingRef.current) {\n          return;\n        }\n\n        // Prevent text selection.\n        event.preventDefault();\n\n        onScrub(event);\n\n        const { movementX, movementY } = event;\n\n        cumulativeDelta += direction === 'vertical' ? movementY : movementX;\n\n        if (Math.abs(cumulativeDelta) >= pixelSensitivity) {\n          cumulativeDelta = 0;\n          didMoveRef.current = true;\n          const dValue = direction === 'vertical' ? -movementY : movementX;\n          const stepAmount = getStepAmount(event) ?? DEFAULT_STEP;\n          const rawAmount = dValue * stepAmount;\n\n          if (rawAmount !== 0) {\n            incrementValue(Math.abs(rawAmount), {\n              direction: rawAmount >= 0 ? 1 : -1,\n              event,\n              reason: REASONS.scrub,\n            });\n          }\n        }\n      }\n\n      const win = ownerWindow(inputRef.current);\n      win.addEventListener('pointerup', handleScrubPointerUp, true);\n      win.addEventListener('pointermove', handleScrubPointerMove, true);\n\n      return () => {\n        exitPointerLockTimeout.clear();\n        win.removeEventListener('pointerup', handleScrubPointerUp, true);\n        win.removeEventListener('pointermove', handleScrubPointerMove, true);\n      };\n    },\n    [\n      disabled,\n      readOnly,\n      incrementValue,\n      isScrubbing,\n      getStepAmount,\n      inputRef,\n      onScrubbingChange,\n      onScrub,\n      direction,\n      pixelSensitivity,\n      lastChangedValueRef,\n      onValueCommitted,\n      valueRef,\n      exitPointerLockTimeout,\n    ],\n  );\n\n  // Prevent scrolling using touch input when scrubbing.\n  React.useEffect(\n    function registerScrubberTouchPreventListener() {\n      const element = scrubAreaRef.current;\n      if (!element || disabled || readOnly) {\n        return undefined;\n      }\n\n      function handleTouchStart(event: TouchEvent) {\n        if (event.touches.length === 1) {\n          event.preventDefault();\n        }\n      }\n\n      element.addEventListener('touchstart', handleTouchStart);\n\n      return () => {\n        element.removeEventListener('touchstart', handleTouchStart);\n      };\n    },\n    [disabled, readOnly],\n  );\n\n  const defaultProps: HTMLProps = {\n    role: 'presentation',\n    style: {\n      touchAction: 'none',\n      WebkitUserSelect: 'none',\n      userSelect: 'none',\n    },\n    async onPointerDown(event) {\n      const isMainButton = !event.button || event.button === 0;\n      if (event.defaultPrevented || readOnly || !isMainButton || disabled) {\n        return;\n      }\n\n      const isTouch = event.pointerType === 'touch';\n      setIsTouchInput(isTouch);\n\n      if (event.pointerType === 'mouse') {\n        event.preventDefault();\n        inputRef.current?.focus();\n      }\n\n      isScrubbingRef.current = true;\n      didMoveRef.current = false;\n      pointerDownTargetRef.current = event.target;\n      onScrubbingChange(true, event.nativeEvent);\n\n      // WebKit causes significant layout shift with the native message, so we can't use it.\n      if (!isTouch && !isWebKit) {\n        try {\n          // Avoid non-deterministic errors in testing environments. This error sometimes\n          // appears:\n          // \"The root document of this element is not valid for pointer lock.\"\n          await ownerDocument(scrubAreaRef.current).body.requestPointerLock();\n          setIsPointerLockDenied(false);\n        } catch (error) {\n          setIsPointerLockDenied(true);\n        } finally {\n          if (isScrubbingRef.current) {\n            ReactDOM.flushSync(() => {\n              onScrubbingChange(true, event.nativeEvent);\n            });\n          }\n        }\n      }\n    },\n  };\n\n  const element = useRenderElement('span', componentProps, {\n    ref: [forwardedRef, scrubAreaRef],\n    state,\n    props: [defaultProps, elementProps],\n    stateAttributesMapping,\n  });\n\n  const contextValue: NumberFieldScrubAreaContext = React.useMemo(\n    () => ({\n      isScrubbing,\n      isTouchInput,\n      isPointerLockDenied,\n      scrubAreaCursorRef,\n      scrubAreaRef,\n      direction,\n      pixelSensitivity,\n      teleportDistance,\n    }),\n    [isScrubbing, isTouchInput, isPointerLockDenied, direction, pixelSensitivity, teleportDistance],\n  );\n\n  return (\n    <NumberFieldScrubAreaContext.Provider value={contextValue}>\n      {element}\n    </NumberFieldScrubAreaContext.Provider>\n  );\n});\n\nexport interface NumberFieldScrubAreaState extends NumberFieldRootState {}\n\nexport interface NumberFieldScrubAreaProps extends BaseUIComponentProps<\n  'span',\n  NumberFieldScrubAreaState\n> {\n  /**\n   * Cursor movement direction in the scrub area.\n   * @default 'horizontal'\n   */\n  direction?: 'horizontal' | 'vertical' | undefined;\n  /**\n   * Determines how many pixels the cursor must move before the value changes.\n   * A higher value will make scrubbing less sensitive.\n   * @default 2\n   */\n  pixelSensitivity?: number | undefined;\n  /**\n   * If specified, determines the distance that the cursor may move from the center\n   * of the scrub area before it will loop back around.\n   */\n  teleportDistance?: number | undefined;\n}\n\nexport namespace NumberFieldScrubArea {\n  export type State = NumberFieldScrubAreaState;\n  export type Props = NumberFieldScrubAreaProps;\n}\n"
  },
  {
    "path": "packages/react/src/number-field/scrub-area/NumberFieldScrubAreaContext.ts",
    "content": "'use client';\nimport * as React from 'react';\n\nexport interface NumberFieldScrubAreaContext {\n  isScrubbing: boolean;\n  isTouchInput: boolean;\n  isPointerLockDenied: boolean;\n  scrubAreaCursorRef: React.RefObject<HTMLSpanElement | null>;\n  scrubAreaRef: React.RefObject<HTMLSpanElement | null>;\n  direction: 'horizontal' | 'vertical';\n  pixelSensitivity: number;\n  teleportDistance: number | undefined;\n}\n\nexport const NumberFieldScrubAreaContext = React.createContext<\n  NumberFieldScrubAreaContext | undefined\n>(undefined);\n\nexport function useNumberFieldScrubAreaContext() {\n  const context = React.useContext(NumberFieldScrubAreaContext);\n  if (context === undefined) {\n    throw new Error(\n      'Base UI: NumberFieldScrubAreaContext is missing. NumberFieldScrubArea parts must be placed within <NumberField.ScrubArea>.',\n    );\n  }\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/number-field/scrub-area/NumberFieldScrubAreaDataAttributes.ts",
    "content": "export enum NumberFieldScrubAreaDataAttributes {\n  /**\n   * Present while scrubbing.\n   */\n  scrubbing = 'data-scrubbing',\n  /**\n   * Present when the number field is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the number field is readonly.\n   */\n  readonly = 'data-readonly',\n  /**\n   * Present when the number field is required.\n   */\n  required = 'data-required',\n  /**\n   * Present when the number field is in valid state (when wrapped in Field.Root).\n   */\n  valid = 'data-valid',\n  /**\n   * Present when the number field is in invalid state (when wrapped in Field.Root).\n   */\n  invalid = 'data-invalid',\n  /**\n   * Present when the number field has been touched (when wrapped in Field.Root).\n   */\n  touched = 'data-touched',\n  /**\n   * Present when the number field's value has changed (when wrapped in Field.Root).\n   */\n  dirty = 'data-dirty',\n  /**\n   * Present when the number field is filled (when wrapped in Field.Root).\n   */\n  filled = 'data-filled',\n  /**\n   * Present when the number field is focused (when wrapped in Field.Root).\n   */\n  focused = 'data-focused',\n}\n"
  },
  {
    "path": "packages/react/src/number-field/scrub-area-cursor/NumberFieldScrubAreaCursor.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { screen, act } from '@mui/internal-test-utils';\nimport { NumberField } from '@base-ui/react/number-field';\nimport { isWebKit } from '@base-ui/utils/detectBrowser';\nimport { createRenderer, describeConformance } from '#test-utils';\nimport { NumberFieldScrubAreaContext } from '../scrub-area/NumberFieldScrubAreaContext';\n\nconst defaultScrubAreaContext: NumberFieldScrubAreaContext = {\n  isScrubbing: true,\n  isTouchInput: false,\n  isPointerLockDenied: false,\n  direction: 'horizontal',\n  pixelSensitivity: 2,\n  teleportDistance: undefined,\n  scrubAreaCursorRef: React.createRef<HTMLSpanElement>(),\n  scrubAreaRef: React.createRef<HTMLDivElement>(),\n};\n\n// This component doesn't render on WebKit.\ndescribe.skipIf(isWebKit)('<NumberField.ScrubAreaCursor />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<NumberField.ScrubAreaCursor />, () => ({\n    refInstanceof: window.HTMLSpanElement,\n    render(node) {\n      return render(\n        <NumberField.Root>\n          <NumberField.ScrubArea>\n            <NumberFieldScrubAreaContext.Provider value={defaultScrubAreaContext}>\n              {node}\n            </NumberFieldScrubAreaContext.Provider>\n          </NumberField.ScrubArea>\n        </NumberField.Root>,\n      );\n    },\n  }));\n\n  it('has presentation role', async () => {\n    await render(\n      <NumberField.Root>\n        <NumberField.ScrubArea />\n      </NumberField.Root>,\n    );\n    expect(screen.queryByRole('presentation')).not.toBe(null);\n  });\n\n  it('renders when using mouse input', async () => {\n    const originalRequestPointerLock = Element.prototype.requestPointerLock;\n\n    try {\n      Element.prototype.requestPointerLock = vi.fn().mockResolvedValue(undefined);\n\n      const { user } = await render(\n        <NumberField.Root>\n          <NumberField.Input />\n          <NumberField.ScrubArea data-testid=\"scrub-area\">\n            <NumberField.ScrubAreaCursor data-testid=\"scrub-area-cursor\" />\n          </NumberField.ScrubArea>\n        </NumberField.Root>,\n      );\n\n      const scrubArea = screen.getByTestId('scrub-area');\n\n      await act(async () => {\n        await user.pointer({ target: scrubArea, keys: '[MouseLeft>]', pointerName: 'mouse' });\n        await new Promise((resolve) => {\n          setTimeout(resolve, 25);\n        });\n      });\n\n      expect(screen.queryByTestId('scrub-area-cursor')).not.toBe(null);\n    } finally {\n      Element.prototype.requestPointerLock = originalRequestPointerLock;\n    }\n  });\n\n  it('only renders a cursor for the active scrub area', async () => {\n    const originalRequestPointerLock = Element.prototype.requestPointerLock;\n\n    try {\n      Element.prototype.requestPointerLock = vi.fn().mockResolvedValue(undefined);\n\n      const { user } = await render(\n        <NumberField.Root>\n          <NumberField.Input />\n          <NumberField.ScrubArea data-testid=\"scrub-area-1\">\n            <NumberField.ScrubAreaCursor data-testid=\"scrub-area-cursor\" />\n          </NumberField.ScrubArea>\n          <NumberField.ScrubArea data-testid=\"scrub-area-2\">\n            <NumberField.ScrubAreaCursor data-testid=\"scrub-area-cursor\" />\n          </NumberField.ScrubArea>\n        </NumberField.Root>,\n      );\n\n      const firstScrubArea = screen.getByTestId('scrub-area-1');\n\n      await act(async () => {\n        await user.pointer({ target: firstScrubArea, keys: '[MouseLeft>]', pointerName: 'mouse' });\n        await new Promise((resolve) => {\n          setTimeout(resolve, 25);\n        });\n      });\n\n      expect(screen.queryAllByTestId('scrub-area-cursor')).toHaveLength(1);\n    } finally {\n      Element.prototype.requestPointerLock = originalRequestPointerLock;\n    }\n  });\n\n  it('does not render when using touch input', async () => {\n    const { user } = await render(\n      <NumberField.Root>\n        <NumberField.ScrubArea>\n          <NumberField.ScrubAreaCursor data-testid=\"scrub-area-cursor\" />\n        </NumberField.ScrubArea>\n      </NumberField.Root>,\n    );\n\n    const scrubArea = screen.getByRole('presentation');\n\n    await act(async () => {\n      await user.pointer({ target: scrubArea, keys: '[TouchA>]', pointerName: 'touch' });\n      await new Promise((resolve) => {\n        setTimeout(resolve, 25);\n      });\n    });\n\n    expect(screen.queryByTestId('scrub-area-cursor')).toBe(null);\n  });\n\n  it('handles pointer lock denial through requestPointerLock API', async () => {\n    const originalRequestPointerLock = Element.prototype.requestPointerLock;\n\n    try {\n      const requestLockStub = vi.fn(() => {\n        throw new Error('User denied pointer lock');\n      });\n      Element.prototype.requestPointerLock =\n        requestLockStub as typeof Element.prototype.requestPointerLock;\n\n      const { user } = await render(\n        <NumberField.Root>\n          <NumberField.ScrubArea>\n            <NumberField.ScrubAreaCursor data-testid=\"scrub-area-cursor\" />\n          </NumberField.ScrubArea>\n        </NumberField.Root>,\n      );\n\n      const scrubArea = screen.getByRole('presentation');\n\n      await act(async () => {\n        await user.pointer({ target: scrubArea, keys: '[MouseLeft>]', pointerName: 'mouse' });\n        await new Promise((resolve) => {\n          setTimeout(resolve, 25);\n        });\n      });\n\n      expect(screen.queryByTestId('scrub-area-cursor')).toBe(null);\n      expect(requestLockStub).toHaveBeenCalled();\n    } finally {\n      Element.prototype.requestPointerLock = originalRequestPointerLock;\n    }\n  });\n\n  it('does not render after a quick tap when pointer lock resolves later', async () => {\n    const originalRequestPointerLock = Element.prototype.requestPointerLock;\n\n    try {\n      // Simulate pointer lock resolving after the user already released the pointer (tap)\n      Element.prototype.requestPointerLock = vi.fn().mockReturnValue(\n        new Promise((resolve) => {\n          setTimeout(resolve, 30);\n        }),\n      );\n\n      const { user } = await render(\n        <NumberField.Root>\n          <NumberField.Input />\n          <NumberField.ScrubArea data-testid=\"scrub-area\">\n            <NumberField.ScrubAreaCursor data-testid=\"scrub-area-cursor\" />\n          </NumberField.ScrubArea>\n        </NumberField.Root>,\n      );\n\n      const scrubArea = screen.getByTestId('scrub-area');\n\n      await act(async () => {\n        // Quick press and release (tap)\n        await user.pointer({ target: scrubArea, keys: '[MouseLeft>]', pointerName: 'mouse' });\n        await user.pointer({ target: scrubArea, keys: '[/MouseLeft]', pointerName: 'mouse' });\n        window.dispatchEvent(new Event('pointerup'));\n        // Wait longer than the delayed pointer lock resolution\n        await new Promise((resolve) => {\n          setTimeout(resolve, 50);\n        });\n      });\n\n      // After a tap, the scrub cursor should not remain rendered\n      expect(screen.queryByTestId('scrub-area-cursor')).toBe(null);\n    } finally {\n      Element.prototype.requestPointerLock = originalRequestPointerLock;\n    }\n  });\n});\n"
  },
  {
    "path": "packages/react/src/number-field/scrub-area-cursor/NumberFieldScrubAreaCursor.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport { isWebKit } from '@base-ui/utils/detectBrowser';\nimport { ownerDocument } from '@base-ui/utils/owner';\nimport { useNumberFieldRootContext } from '../root/NumberFieldRootContext';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport type { NumberFieldRootState } from '../root/NumberFieldRoot';\nimport { stateAttributesMapping } from '../utils/stateAttributesMapping';\nimport { useNumberFieldScrubAreaContext } from '../scrub-area/NumberFieldScrubAreaContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\n\n/**\n * A custom element to display instead of the native cursor while using the scrub area.\n * Renders a `<span>` element.\n *\n * This component uses the [Pointer Lock API](https://developer.mozilla.org/en-US/docs/Web/API/Pointer_Lock_API), which may prompt the browser to display a related notification. It is disabled\n * in Safari to avoid a layout shift that this notification causes there.\n *\n * Documentation: [Base UI Number Field](https://base-ui.com/react/components/number-field)\n */\nexport const NumberFieldScrubAreaCursor = React.forwardRef(function NumberFieldScrubAreaCursor(\n  componentProps: NumberFieldScrubAreaCursor.Props,\n  forwardedRef: React.ForwardedRef<HTMLSpanElement>,\n) {\n  const { render, className, ...elementProps } = componentProps;\n\n  const { state } = useNumberFieldRootContext();\n  const { isScrubbing, isTouchInput, isPointerLockDenied, scrubAreaCursorRef } =\n    useNumberFieldScrubAreaContext();\n\n  const [domElement, setDomElement] = React.useState<Element | null>(null);\n\n  const shouldRender = isScrubbing && !isWebKit && !isTouchInput && !isPointerLockDenied;\n\n  const element = useRenderElement('span', componentProps, {\n    enabled: shouldRender,\n    ref: [forwardedRef, scrubAreaCursorRef, setDomElement],\n    state,\n    props: [\n      {\n        role: 'presentation',\n        style: {\n          position: 'fixed',\n          top: 0,\n          left: 0,\n          pointerEvents: 'none',\n        },\n      },\n      elementProps,\n    ],\n    stateAttributesMapping,\n  });\n\n  return element && ReactDOM.createPortal(element, ownerDocument(domElement).body);\n});\n\nexport interface NumberFieldScrubAreaCursorState extends NumberFieldRootState {}\n\nexport interface NumberFieldScrubAreaCursorProps extends BaseUIComponentProps<\n  'span',\n  NumberFieldScrubAreaCursorState\n> {}\n\nexport namespace NumberFieldScrubAreaCursor {\n  export type State = NumberFieldScrubAreaCursorState;\n  export type Props = NumberFieldScrubAreaCursorProps;\n}\n"
  },
  {
    "path": "packages/react/src/number-field/scrub-area-cursor/NumberFieldScrubAreaCursorDataAttributes.ts",
    "content": "export enum NumberFieldScrubAreaCursorDataAttributes {\n  /**\n   * Present while scrubbing.\n   */\n  scrubbing = 'data-scrubbing',\n  /**\n   * Present when the number field is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the number field is readonly.\n   */\n  readonly = 'data-readonly',\n  /**\n   * Present when the number field is required.\n   */\n  required = 'data-required',\n  /**\n   * Present when the number field is in valid state (when wrapped in Field.Root).\n   */\n  valid = 'data-valid',\n  /**\n   * Present when the number field is in invalid state (when wrapped in Field.Root).\n   */\n  invalid = 'data-invalid',\n  /**\n   * Present when the number field has been touched (when wrapped in Field.Root).\n   */\n  touched = 'data-touched',\n  /**\n   * Present when the number field's value has changed (when wrapped in Field.Root).\n   */\n  dirty = 'data-dirty',\n  /**\n   * Present when the number field is filled (when wrapped in Field.Root).\n   */\n  filled = 'data-filled',\n  /**\n   * Present when the number field is focused (when wrapped in Field.Root).\n   */\n  focused = 'data-focused',\n}\n"
  },
  {
    "path": "packages/react/src/number-field/utils/constants.ts",
    "content": "export const CHANGE_VALUE_TICK_DELAY = 60;\nexport const START_AUTO_CHANGE_DELAY = 400;\nexport const SCROLLING_POINTER_MOVE_DISTANCE = 8;\nexport const DEFAULT_STEP = 1;\n"
  },
  {
    "path": "packages/react/src/number-field/utils/getViewportRect.ts",
    "content": "import { ownerWindow } from '@base-ui/utils/owner';\n\n// Calculates the viewport rect for the virtual cursor.\nexport function getViewportRect(teleportDistance: number | undefined, scrubAreaEl: HTMLElement) {\n  const win = ownerWindow(scrubAreaEl);\n  const rect = scrubAreaEl.getBoundingClientRect();\n\n  if (rect && teleportDistance != null) {\n    return {\n      x: rect.left - teleportDistance / 2,\n      y: rect.top - teleportDistance / 2,\n      width: rect.right + teleportDistance / 2,\n      height: rect.bottom + teleportDistance / 2,\n    };\n  }\n\n  const vV = win.visualViewport;\n\n  if (vV) {\n    return {\n      x: vV.offsetLeft,\n      y: vV.offsetTop,\n      width: vV.offsetLeft + vV.width,\n      height: vV.offsetTop + vV.height,\n    };\n  }\n\n  return {\n    x: 0,\n    y: 0,\n    width: win.document.documentElement.clientWidth,\n    height: win.document.documentElement.clientHeight,\n  };\n}\n"
  },
  {
    "path": "packages/react/src/number-field/utils/parse.test.ts",
    "content": "import { expect } from 'vitest';\nimport { getNumberLocaleDetails, parseNumber } from './parse';\n\ndescribe('NumberField parse', () => {\n  describe('getNumberLocaleDetails', () => {\n    it('returns the number locale details', () => {\n      const details = getNumberLocaleDetails('en-US');\n      expect(details.decimal).toBe('.');\n      expect(details.group).toBe(',');\n      expect(details.currency).toBe(undefined);\n      expect(details.percent).toBe(undefined);\n      expect(details.unit).toBe(undefined);\n    });\n  });\n\n  describe('parseNumber', () => {\n    it('parses a number', () => {\n      const numberString = new Intl.NumberFormat().format(1234.56);\n      expect(parseNumber(numberString)).toBe(1234.56);\n    });\n\n    it('parses percentages by default', () => {\n      expect(parseNumber('12%')).toBe(0.12);\n    });\n\n    it('parses a number with Arabic numerals', ({ skip }) => {\n      // Skip in browser as it doesn't support Arabic numerals.\n      skip();\n      expect(parseNumber('١٬٢٣٤٫٥٦')).toBe(1234.56);\n    });\n\n    it('parses a number with Han numerals', () => {\n      expect(parseNumber('一,二三四.五六')).toBe(1234.56);\n    });\n\n    it('parses percentages with Arabic numerals', () => {\n      expect(parseNumber('١٢٪')).toBe(0.12);\n    });\n\n    it('parses percentages with Han numerals', () => {\n      expect(parseNumber('一二%')).toBe(0.12);\n    });\n\n    it('returns null for an invalid number', () => {\n      expect(parseNumber('invalid')).toBe(null);\n    });\n\n    it('handles percentages with style: \"percent\"', () => {\n      expect(parseNumber('12%', 'en-US', { style: 'percent' })).toBe(0.12);\n    });\n\n    it('handles percentages with style: \"unit\" and unit: \"percent\"', () => {\n      expect(parseNumber('12%', 'en-US', { style: 'unit', unit: 'percent' })).toBe(12);\n    });\n\n    it('parses fullwidth digits and punctuation', () => {\n      expect(parseNumber('１，２３４．５６')).toBe(1234.56);\n      expect(parseNumber('１２％')).toBe(0.12);\n    });\n\n    it('parses Persian digits', () => {\n      expect(parseNumber('۱۲۳۴')).toBe(1234);\n      expect(parseNumber('۱۲٫۳۴')).toBe(12.34);\n      expect(parseNumber('۱۲٪')).toBe(0.12);\n    });\n\n    it('parses Persian digits with Arabic thousands and decimal characters', () => {\n      // Uses Persian digits + Arabic separators (common in copied text)\n      expect(parseNumber('۱۲٬۳۴۵٫۶۷')).toBe(12345.67);\n    });\n\n    it('parses permille values', () => {\n      expect(parseNumber('12‰')).toBe(0.012);\n      expect(parseNumber('12؉')).toBe(0.012);\n    });\n\n    it('strips bidi/control characters', () => {\n      expect(parseNumber('1\\u200E234.56')).toBe(1234.56);\n      expect(parseNumber('\\u200E12\\u200F%')).toBe(0.12);\n    });\n\n    it('handles unicode minus and plus signs', () => {\n      expect(parseNumber('−1234')).toBe(-1234);\n      expect(parseNumber('1234−')).toBe(-1234);\n      expect(parseNumber('＋1234')).toBe(1234);\n      expect(parseNumber('1234＋')).toBe(1234);\n    });\n\n    it('parses french formatted numbers with narrow no-break space grouping', () => {\n      const fr = new Intl.NumberFormat('fr-FR').format(1234.5); // e.g., '1 234,5'\n      expect(parseNumber(fr, 'fr-FR')).toBe(1234.5);\n      expect(parseNumber(`${fr}−`, 'fr-FR')).toBe(-1234.5);\n    });\n\n    it('parses currency when options specify currency style', () => {\n      expect(parseNumber('$1,234.56', 'en-US', { style: 'currency', currency: 'USD' })).toBe(\n        1234.56,\n      );\n    });\n\n    it('parses units when options specify unit style', () => {\n      expect(parseNumber('12 kg', 'en-US', { style: 'unit', unit: 'kilogram' })).toBe(12);\n    });\n\n    it('returns null for Infinity-like inputs', () => {\n      expect(parseNumber('Infinity')).toBe(null);\n      expect(parseNumber('-Infinity')).toBe(null);\n      expect(parseNumber('∞')).toBe(null);\n    });\n\n    it('collapses extra dots from mixed-locale inputs', () => {\n      // Last '.' is decimal; previous '.' are removed\n      expect(parseNumber('1.234.567.89')).toBe(1234567.89);\n    });\n\n    it('parses number with mixed separators (FR)', () => {\n      expect(parseNumber('1.234.567,89', 'fr-FR')).toBe(1234567.89);\n    });\n\n    it('parses number with mixed separators (US)', () => {\n      expect(parseNumber('1.234.567,89', 'en-US')).toBe(1234.56789);\n    });\n\n    it('returns null for empty and whitespace-only input', () => {\n      expect(parseNumber('')).toBe(null);\n      expect(parseNumber('   ')).toBe(null);\n    });\n\n    it('returns null for just a sign', () => {\n      expect(parseNumber('-')).toBe(null);\n      expect(parseNumber('+')).toBe(null);\n    });\n\n    it('handles ASCII leading and trailing signs', () => {\n      expect(parseNumber('+1234')).toBe(1234);\n      expect(parseNumber('1234+')).toBe(1234);\n      expect(parseNumber('-1234')).toBe(-1234);\n      expect(parseNumber('1234-')).toBe(-1234);\n    });\n\n    it('supports multiple unicode minus variants', () => {\n      expect(parseNumber('‒123')).toBe(-123); // figure dash\n      expect(parseNumber('–123')).toBe(-123); // en dash\n      expect(parseNumber('—123')).toBe(-123); // em dash\n      expect(parseNumber('－123')).toBe(-123); // fullwidth hyphen-minus\n      expect(parseNumber('﹣123')).toBe(-123); // small hyphen-minus\n    });\n\n    it('supports additional unicode plus variants', () => {\n      expect(parseNumber('﹢123')).toBe(123); // small plus sign\n    });\n\n    it('handles additional percent symbol variants', () => {\n      expect(parseNumber('12％')).toBe(0.12); // fullwidth percent\n      expect(parseNumber('12﹪')).toBe(0.12); // small percent\n      expect(parseNumber('12٪')).toBe(0.12); // Arabic percent sign with ASCII digits\n    });\n\n    it('parses Arabic punctuation with ASCII digits', () => {\n      expect(parseNumber('1٬234٫56')).toBe(1234.56);\n    });\n\n    it('removes various Unicode space groupings (fr-FR)', () => {\n      expect(parseNumber('1\\u202F234,56', 'fr-FR')).toBe(1234.56); // narrow no-break space\n      expect(parseNumber('1\\u2009234,56', 'fr-FR')).toBe(1234.56); // thin space\n      expect(parseNumber('1\\u2007234,56', 'fr-FR')).toBe(1234.56); // figure space\n      expect(parseNumber('1\\u00A0234,56', 'fr-FR')).toBe(1234.56); // no-break space\n    });\n\n    it('handles Swiss grouping apostrophe', () => {\n      // de-CH uses RIGHT SINGLE QUOTATION MARK (U+2019) as group separator on some platforms and straight apostrophe on others\n      expect(parseNumber('1’234.56', 'de-CH')).toBe(1234.56);\n      expect(parseNumber(\"1'234.56\", 'de-CH')).toBe(1234.56);\n    });\n\n    it('parses de-DE formatted numbers', () => {\n      expect(parseNumber('1.234,56', 'de-DE')).toBe(1234.56);\n      expect(parseNumber('1.234.567,89', 'de-DE')).toBe(1234567.89);\n    });\n\n    it('parses currency prefix/suffix across locales', () => {\n      const en = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'EUR' }).format(\n        1234.56,\n      );\n      const fr = new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR' }).format(\n        1234.56,\n      );\n      expect(parseNumber(en, 'en-US', { style: 'currency', currency: 'EUR' })).toBe(1234.56);\n      expect(parseNumber(fr, 'fr-FR', { style: 'currency', currency: 'EUR' })).toBe(1234.56);\n    });\n\n    it('parses units with different formats', () => {\n      expect(parseNumber('12 km/h', 'en-US', { style: 'unit', unit: 'kilometer-per-hour' })).toBe(\n        12,\n      );\n      expect(parseNumber('12 m/s', 'en-US', { style: 'unit', unit: 'meter-per-second' })).toBe(12);\n    });\n\n    it('treats bidi/format controls as ignorable in the middle of input', () => {\n      expect(parseNumber('1\\u202A234\\u202C.56')).toBe(1234.56); // LRE...PDF\n      expect(parseNumber('\\u202A12\\u202C%')).toBe(0.12);\n    });\n\n    it('returns null for Infinity with explicit sign and surrounding spaces', () => {\n      expect(parseNumber(' +Infinity ')).toBe(null);\n      expect(parseNumber(' -∞ ')).toBe(null);\n      expect(parseNumber('+Infinity')).toBe(null);\n    });\n\n    it('collapses multiple consecutive dots keeping only the last as decimal', () => {\n      expect(parseNumber('1..5')).toBe(1.5);\n      expect(parseNumber('123..456..789.01')).toBe(123456789.01);\n      expect(parseNumber('....5')).toBe(0.5);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/number-field/utils/parse.ts",
    "content": "import { getFormatter } from '../../utils/formatNumber';\n\nexport const HAN_NUMERALS = ['零', '〇', '一', '二', '三', '四', '五', '六', '七', '八', '九'];\n// Map Han numeral characters to ASCII digits. Includes both forms of zero.\nexport const HAN_NUMERAL_TO_DIGIT: Record<string, string> = {\n  零: '0',\n  〇: '0',\n  一: '1',\n  二: '2',\n  三: '3',\n  四: '4',\n  五: '5',\n  六: '6',\n  七: '7',\n  八: '8',\n  九: '9',\n};\nexport const ARABIC_NUMERALS = ['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩'];\nexport const PERSIAN_NUMERALS = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'];\nexport const FULLWIDTH_NUMERALS = ['０', '１', '２', '３', '４', '５', '６', '７', '８', '９'];\n\nexport const PERCENTAGES = ['%', '٪', '％', '﹪'];\nexport const PERMILLE = ['‰', '؉'];\n\nexport const UNICODE_MINUS_SIGNS = ['−', '－', '‒', '–', '—', '﹣'];\nexport const UNICODE_PLUS_SIGNS = ['＋', '﹢'];\n\n// Fullwidth punctuation common in CJK inputs\nexport const FULLWIDTH_DECIMAL = '．'; // U+FF0E\nexport const FULLWIDTH_GROUP = '，'; // U+FF0C\n\nexport const ARABIC_RE = new RegExp(`[${ARABIC_NUMERALS.join('')}]`, 'g');\nexport const PERSIAN_RE = new RegExp(`[${PERSIAN_NUMERALS.join('')}]`, 'g');\nexport const FULLWIDTH_RE = new RegExp(`[${FULLWIDTH_NUMERALS.join('')}]`, 'g');\nexport const HAN_RE = new RegExp(`[${HAN_NUMERALS.join('')}]`, 'g');\nexport const PERCENT_RE = new RegExp(`[${PERCENTAGES.join('')}]`);\nexport const PERMILLE_RE = new RegExp(`[${PERMILLE.join('')}]`);\n\n// Detection regexes (non-global to avoid lastIndex side effects)\nexport const ARABIC_DETECT_RE = /[٠١٢٣٤٥٦٧٨٩]/;\nexport const PERSIAN_DETECT_RE = /[۰۱۲۳۴۵۶۷۸۹]/;\nexport const HAN_DETECT_RE = /[零〇一二三四五六七八九]/;\nexport const FULLWIDTH_DETECT_RE = new RegExp(`[${FULLWIDTH_NUMERALS.join('')}]`);\n\nexport const BASE_NON_NUMERIC_SYMBOLS = [\n  '.',\n  ',',\n  FULLWIDTH_DECIMAL,\n  FULLWIDTH_GROUP,\n  '٫',\n  '٬',\n] as const;\nexport const SPACE_SEPARATOR_RE = /\\p{Zs}/u;\nexport const PLUS_SIGNS_WITH_ASCII = ['+', ...UNICODE_PLUS_SIGNS];\nexport const MINUS_SIGNS_WITH_ASCII = ['-', ...UNICODE_MINUS_SIGNS];\n\nconst escapeRegExp = (s: string) => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\nconst escapeClassChar = (s: string) => s.replace(/[-\\\\\\]^]/g, (m) => `\\\\${m}`); // escape for use inside [...]\n\nconst charClassFrom = (chars: string[]) => `[${chars.map(escapeClassChar).join('')}]`;\n\nconst ANY_MINUS_CLASS = charClassFrom(['-'].concat(UNICODE_MINUS_SIGNS));\nconst ANY_PLUS_CLASS = charClassFrom(['+'].concat(UNICODE_PLUS_SIGNS));\n\nexport const ANY_MINUS_RE = new RegExp(ANY_MINUS_CLASS, 'gu');\nexport const ANY_PLUS_RE = new RegExp(ANY_PLUS_CLASS, 'gu');\nexport const ANY_MINUS_DETECT_RE = new RegExp(ANY_MINUS_CLASS);\nexport const ANY_PLUS_DETECT_RE = new RegExp(ANY_PLUS_CLASS);\n\nexport function getNumberLocaleDetails(\n  locale?: Intl.LocalesArgument,\n  options?: Intl.NumberFormatOptions,\n) {\n  const parts = getFormatter(locale, options).formatToParts(11111.1);\n  const result: Partial<Record<Intl.NumberFormatPartTypes, string | undefined>> = {};\n\n  parts.forEach((part) => {\n    result[part.type] = part.value;\n  });\n\n  // The formatting options may result in not returning a decimal.\n  getFormatter(locale)\n    .formatToParts(0.1)\n    .forEach((part) => {\n      if (part.type === 'decimal') {\n        result[part.type] = part.value;\n      }\n    });\n\n  return result;\n}\n\nexport function parseNumber(\n  formattedNumber: string,\n  locale?: Intl.LocalesArgument,\n  options?: Intl.NumberFormatOptions,\n) {\n  if (formattedNumber == null) {\n    return null;\n  }\n\n  // Normalize control characters and whitespace; remove bidi/format controls\n  let input = String(formattedNumber)\n    .replace(/\\p{Cf}/gu, '')\n    .trim();\n\n  // Normalize unicode minus/plus to ASCII, handle leading/trailing signs\n  input = input.replace(ANY_MINUS_RE, '-').replace(ANY_PLUS_RE, '+');\n\n  let isNegative = false;\n\n  // Trailing sign, e.g. \"1234-\" / \"1234+\"\n  const trailing = input.match(/([+-])\\s*$/);\n  if (trailing) {\n    if (trailing[1] === '-') {\n      isNegative = true;\n    }\n    input = input.replace(/([+-])\\s*$/, '');\n  }\n  // Leading sign\n  const leading = input.match(/^\\s*([+-])/);\n  if (leading) {\n    if (leading[1] === '-') {\n      isNegative = true;\n    }\n    input = input.replace(/^\\s*[+-]/, '');\n  }\n\n  // Heuristic locale detection\n  let computedLocale = locale;\n  if (computedLocale === undefined) {\n    if (ARABIC_DETECT_RE.test(input) || PERSIAN_DETECT_RE.test(input)) {\n      computedLocale = 'ar';\n    } else if (HAN_DETECT_RE.test(input)) {\n      computedLocale = 'zh';\n    }\n  }\n\n  const { group, decimal, currency } = getNumberLocaleDetails(computedLocale, options);\n\n  // Build robust unit regex from all unit parts (such as \"km/h\")\n  const unitParts = getFormatter(computedLocale, options)\n    .formatToParts(1)\n    .filter((p) => p.type === 'unit')\n    .map((p) => escapeRegExp(p.value));\n  const unitRegex = unitParts.length ? new RegExp(unitParts.join('|'), 'g') : null;\n\n  let groupRegex: RegExp | null = null;\n  if (group) {\n    const isSpaceGroup = /\\p{Zs}/u.test(group);\n    const isApostropheGroup = group === \"'\" || group === '’';\n\n    // Check if the group separator is a space-like character.\n    // If so, we'll replace all such characters with an empty string.\n    if (isSpaceGroup) {\n      groupRegex = /\\p{Zs}/gu;\n    } else if (isApostropheGroup) {\n      // Some environments format numbers with ASCII apostrophe and others with a curly apostrophe.\n      groupRegex = /['’]/g;\n    } else {\n      groupRegex = new RegExp(escapeRegExp(group), 'g');\n    }\n  }\n\n  const replacements: Array<{\n    regex: RegExp | null;\n    replacement: string | ((m: string) => string);\n  }> = [\n    { regex: group ? groupRegex : null, replacement: '' },\n    { regex: decimal ? new RegExp(escapeRegExp(decimal), 'g') : null, replacement: '.' },\n    // Fullwidth punctuation\n    { regex: /．/g, replacement: '.' }, // FULLWIDTH_DECIMAL\n    { regex: /，/g, replacement: '' }, // FULLWIDTH_GROUP\n    // Arabic punctuation\n    { regex: /٫/g, replacement: '.' }, // ARABIC DECIMAL SEPARATOR (U+066B)\n    { regex: /٬/g, replacement: '' }, // ARABIC THOUSANDS SEPARATOR (U+066C)\n    // Currency & unit labels\n    { regex: currency ? new RegExp(escapeRegExp(currency), 'g') : null, replacement: '' },\n    { regex: unitRegex, replacement: '' },\n    // Numeral systems to ASCII digits\n    { regex: ARABIC_RE, replacement: (ch) => String(ARABIC_NUMERALS.indexOf(ch)) },\n    { regex: PERSIAN_RE, replacement: (ch) => String(PERSIAN_NUMERALS.indexOf(ch)) },\n    { regex: FULLWIDTH_RE, replacement: (ch) => String(FULLWIDTH_NUMERALS.indexOf(ch)) },\n    { regex: HAN_RE, replacement: (ch) => HAN_NUMERAL_TO_DIGIT[ch] },\n  ];\n\n  let unformatted = replacements.reduce((acc, { regex, replacement }) => {\n    return regex ? acc.replace(regex, replacement as any) : acc;\n  }, input);\n\n  // Mixed-locale safety: keep only the last '.' as decimal\n  const lastDot = unformatted.lastIndexOf('.');\n  if (lastDot !== -1) {\n    unformatted = `${unformatted.slice(0, lastDot).replace(/\\./g, '')}.${unformatted.slice(lastDot + 1).replace(/\\./g, '')}`;\n  }\n\n  // Guard against Infinity inputs (ASCII and symbol)\n  if (/^[-+]?Infinity$/i.test(input) || /[∞]/.test(input)) {\n    return null;\n  }\n\n  const parseTarget = (isNegative ? '-' : '') + unformatted;\n\n  let num = parseFloat(parseTarget);\n\n  const style = options?.style;\n  const isUnitPercent = style === 'unit' && options?.unit === 'percent';\n  const hasPercentSymbol = PERCENT_RE.test(formattedNumber) || style === 'percent';\n  const hasPermilleSymbol = PERMILLE_RE.test(formattedNumber);\n\n  if (hasPermilleSymbol) {\n    num /= 1000;\n  } else if (!isUnitPercent && hasPercentSymbol) {\n    num /= 100;\n  }\n\n  if (Number.isNaN(num)) {\n    return null;\n  }\n\n  return num;\n}\n"
  },
  {
    "path": "packages/react/src/number-field/utils/stateAttributesMapping.ts",
    "content": "import { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport type { NumberFieldRootState } from '../root/NumberFieldRoot';\nimport { fieldValidityMapping } from '../../field/utils/constants';\n\nexport const stateAttributesMapping: StateAttributesMapping<NumberFieldRootState> = {\n  inputValue: () => null,\n  value: () => null,\n  ...fieldValidityMapping,\n};\n"
  },
  {
    "path": "packages/react/src/number-field/utils/subscribeToVisualViewportResize.ts",
    "content": "import { ownerWindow } from '@base-ui/utils/owner';\n\n// This lets us invert the scale of the cursor to match the OS scale, in which the cursor doesn't\n// scale with the content on pinch-zoom.\nexport function subscribeToVisualViewportResize(\n  element: Element,\n  visualScaleRef: React.RefObject<number>,\n) {\n  const vV = ownerWindow(element).visualViewport;\n\n  if (!vV) {\n    return () => {};\n  }\n\n  function handleVisualResize() {\n    if (vV) {\n      visualScaleRef.current = vV.scale;\n    }\n  }\n\n  handleVisualResize();\n\n  vV.addEventListener('resize', handleVisualResize);\n\n  return () => {\n    vV.removeEventListener('resize', handleVisualResize);\n  };\n}\n"
  },
  {
    "path": "packages/react/src/number-field/utils/types.ts",
    "content": "export type Direction = -1 | 1;\n\nexport type DirectionalChangeReason =\n  | 'increment-press'\n  | 'decrement-press'\n  | 'wheel'\n  | 'scrub'\n  | 'keyboard';\n\nexport interface ChangeEventCustomProperties {\n  direction?: Direction | undefined;\n}\n\nexport interface IncrementValueParameters {\n  direction: Direction;\n  event?: Event | React.SyntheticEvent | undefined;\n  reason: DirectionalChangeReason;\n  currentValue?: number | null | undefined;\n}\n\nexport interface EventWithOptionalKeyState {\n  altKey?: boolean | undefined;\n  shiftKey?: boolean | undefined;\n}\n"
  },
  {
    "path": "packages/react/src/number-field/utils/validate.test.ts",
    "content": "import { expect } from 'vitest';\nimport { toValidatedNumber, removeFloatingPointErrors } from './validate';\n\nconst min = Number.MIN_SAFE_INTEGER;\nconst max = Number.MAX_SAFE_INTEGER;\n\nconst defaultOptions = {\n  step: 1,\n  minWithDefault: min,\n  maxWithDefault: max,\n  minWithZeroDefault: 0,\n  format: undefined,\n  snapOnStep: true,\n  small: false,\n  clamp: true,\n} as const;\n\ndescribe('NumberField validate', () => {\n  describe('removeFloatingPointErrors', () => {\n    it('returns 0.3 for 0.2 + 0.1', () => {\n      expect(removeFloatingPointErrors(0.2 + 0.1)).toBe(0.3);\n    });\n\n    it('returns 0.3 for 0.2 + 0.1 with maximumFractionDigits', () => {\n      expect(removeFloatingPointErrors(0.2 + 0.1, { maximumFractionDigits: 1 })).toBe(0.3);\n    });\n\n    it('returns 1000 for 1000, ignoring grouping', () => {\n      expect(removeFloatingPointErrors(1000)).toBe(1000);\n    });\n\n    it('ignores formatting style', () => {\n      expect(removeFloatingPointErrors(1000, { style: 'currency', currency: 'USD' })).toBe(1000);\n    });\n\n    it('uses resolved maximumFractionDigits when only minimum is provided', () => {\n      expect(removeFloatingPointErrors(1.234567, { minimumFractionDigits: 5 })).toBe(1.23457);\n    });\n  });\n\n  describe('toValidatedNumber', () => {\n    it('returns null when value is null', () => {\n      expect(toValidatedNumber(null, defaultOptions)).toBe(null);\n    });\n\n    it('skips clamping when clamp is false', () => {\n      expect(\n        toValidatedNumber(12, {\n          ...defaultOptions,\n          minWithDefault: 0,\n          maxWithDefault: 10,\n          step: undefined,\n          snapOnStep: false,\n          clamp: false,\n        }),\n      ).toBe(12);\n    });\n\n    describe('incrementing', () => {\n      it('snaps 5 to 5 when step is 1', () => {\n        expect(toValidatedNumber(5, defaultOptions)).toBe(5);\n      });\n\n      it('snaps 5.5 to 5 when step is 1', () => {\n        expect(toValidatedNumber(5.5, defaultOptions)).toBe(5);\n      });\n\n      it('snaps -0.3 to -1 when step is 1', () => {\n        expect(toValidatedNumber(-0.3, defaultOptions)).toBe(-1);\n      });\n\n      it('be same value when step is undefined and within bounds', () => {\n        expect(\n          toValidatedNumber(5.5, {\n            ...defaultOptions,\n            step: undefined,\n          }),\n        ).toBe(5.5);\n      });\n\n      it('snaps 9 to 5 when step is 5 and within bounds', () => {\n        expect(\n          toValidatedNumber(9, {\n            ...defaultOptions,\n            step: 5,\n          }),\n        ).toBe(5);\n      });\n\n      it('snaps 12 to 10 when step is 5 and within bounds', () => {\n        expect(\n          toValidatedNumber(12, {\n            ...defaultOptions,\n            step: 5,\n          }),\n        ).toBe(10);\n      });\n\n      it('preserves exact value when snapOnStep is false', () => {\n        expect(\n          toValidatedNumber(9.7, {\n            ...defaultOptions,\n            step: 5,\n            snapOnStep: false,\n          }),\n        ).toBe(9.7);\n      });\n    });\n\n    describe('decrementing', () => {\n      it('snaps 5 to 5 when step is -1', () => {\n        expect(\n          toValidatedNumber(5, {\n            ...defaultOptions,\n            step: -1,\n          }),\n        ).toBe(5);\n      });\n\n      it('snaps 5.5 to 6 when step is -1', () => {\n        expect(\n          toValidatedNumber(5.5, {\n            ...defaultOptions,\n            step: -1,\n          }),\n        ).toBe(6);\n      });\n\n      it('snaps -0.3 to 0 when step is -1', () => {\n        expect(\n          toValidatedNumber(-0.3, {\n            ...defaultOptions,\n            step: -1,\n          }),\n        ).toBe(0);\n      });\n\n      it('be same value when step is undefined and within bounds', () => {\n        expect(\n          toValidatedNumber(5.5, {\n            ...defaultOptions,\n            step: undefined,\n          }),\n        ).toBe(5.5);\n      });\n\n      it('snaps 9 to 10 when step is -5', () => {\n        expect(\n          toValidatedNumber(9, {\n            ...defaultOptions,\n            step: -5,\n          }),\n        ).toBe(10);\n      });\n\n      it('snaps 12 to 15 when step is -5', () => {\n        expect(\n          toValidatedNumber(12, {\n            ...defaultOptions,\n            step: -5,\n          }),\n        ).toBe(15);\n      });\n\n      it('preserves exact value when snapOnStep is false', () => {\n        expect(\n          toValidatedNumber(12.3, {\n            ...defaultOptions,\n            step: -5,\n            snapOnStep: false,\n          }),\n        ).toBe(12.3);\n      });\n    });\n  });\n\n  it('removes floating point errors by default', () => {\n    expect(\n      toValidatedNumber(0.2 + 0.1, {\n        ...defaultOptions,\n        step: undefined,\n        snapOnStep: false,\n      }),\n    ).toBe(0.3);\n  });\n\n  describe('fractional step with snapOnStep', () => {\n    it('handles increment with step 0.1 without getting stuck', () => {\n      // Simulates incrementing 100.1 by 0.1: the raw value becomes 100.19999999999999\n      // which should snap to 100.2, not back to 100.1\n      expect(\n        toValidatedNumber(100.1 + 0.1, {\n          ...defaultOptions,\n          step: 0.1,\n          snapOnStep: true,\n        }),\n      ).toBe(100.2);\n    });\n\n    it('handles decrement with step -0.1 without getting stuck', () => {\n      // Simulates decrementing 100.1 by 0.1\n      expect(\n        toValidatedNumber(100.1 - 0.1, {\n          ...defaultOptions,\n          step: -0.1,\n          snapOnStep: true,\n        }),\n      ).toBe(100);\n    });\n\n    it('handles multiple increments with step 0.01', () => {\n      expect(\n        toValidatedNumber(0.01 + 0.01, {\n          ...defaultOptions,\n          step: 0.01,\n          snapOnStep: true,\n        }),\n      ).toBe(0.02);\n    });\n\n    it('handles fractional step when a minimum is set', () => {\n      expect(\n        toValidatedNumber(3 + 0.2 + 0.2, {\n          ...defaultOptions,\n          step: 0.2,\n          minWithDefault: 3,\n          minWithZeroDefault: 3,\n        }),\n      ).toBe(3.4);\n    });\n\n    it('rounds to the nearest value when using small step', () => {\n      expect(\n        toValidatedNumber(0.15, {\n          ...defaultOptions,\n          step: 0.1,\n          snapOnStep: true,\n          small: true,\n        }),\n      ).toBe(0.2);\n    });\n\n    it('rounds negative small steps to the nearest value', () => {\n      expect(\n        toValidatedNumber(-0.15, {\n          ...defaultOptions,\n          step: -0.1,\n          snapOnStep: true,\n          small: true,\n        }),\n      ).toBe(-0.2);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/number-field/utils/validate.ts",
    "content": "import { clamp } from '../../utils/clamp';\nimport { getFormatter } from '../../utils/formatNumber';\n\nconst STEP_EPSILON_FACTOR = 1e-10;\n\nfunction getFractionDigits(format?: Intl.NumberFormatOptions) {\n  const defaultOptions = getFormatter('en-US').resolvedOptions();\n  const minimumFractionDigits =\n    format?.minimumFractionDigits ?? defaultOptions.minimumFractionDigits ?? 0;\n  const maximumFractionDigits = Math.max(\n    format?.maximumFractionDigits ?? defaultOptions.maximumFractionDigits ?? 20,\n    minimumFractionDigits,\n  );\n  return { maximumFractionDigits, minimumFractionDigits };\n}\n\nfunction roundToFractionDigits(value: number, maximumFractionDigits: number) {\n  if (!Number.isFinite(value)) {\n    return value;\n  }\n  const digits = Math.min(Math.max(maximumFractionDigits, 0), 20);\n  return Number(value.toFixed(digits));\n}\n\nexport function removeFloatingPointErrors(value: number, format?: Intl.NumberFormatOptions) {\n  const { maximumFractionDigits } = getFractionDigits(format);\n  return roundToFractionDigits(value, maximumFractionDigits);\n}\n\nfunction snapToStep(\n  clampedValue: number,\n  base: number,\n  step: number,\n  mode: 'directional' | 'nearest' = 'directional',\n) {\n  if (step === 0) {\n    return clampedValue;\n  }\n\n  const stepSize = Math.abs(step);\n  const direction = Math.sign(step);\n  const tolerance = stepSize * STEP_EPSILON_FACTOR * direction;\n  const divisor = mode === 'nearest' ? step : stepSize;\n  const rawSteps = (clampedValue - base + tolerance) / divisor;\n\n  let snappedSteps: number;\n  if (mode === 'nearest') {\n    snappedSteps = Math.round(rawSteps);\n  } else if (direction > 0) {\n    snappedSteps = Math.floor(rawSteps);\n  } else {\n    snappedSteps = Math.ceil(rawSteps);\n  }\n\n  const stepForResult = mode === 'nearest' ? step : stepSize;\n\n  return base + snappedSteps * stepForResult;\n}\n\nexport function toValidatedNumber(\n  value: number | null,\n  {\n    step,\n    minWithDefault,\n    maxWithDefault,\n    minWithZeroDefault,\n    format,\n    snapOnStep,\n    small,\n    clamp: shouldClamp,\n  }: {\n    step: number | undefined;\n    minWithDefault: number;\n    maxWithDefault: number;\n    minWithZeroDefault: number;\n    format: Intl.NumberFormatOptions | undefined;\n    snapOnStep: boolean;\n    small: boolean;\n    clamp: boolean;\n  },\n) {\n  if (value === null) {\n    return value;\n  }\n\n  const clampedValue = shouldClamp ? clamp(value, minWithDefault, maxWithDefault) : value;\n\n  if (step != null && snapOnStep) {\n    if (step === 0) {\n      return removeFloatingPointErrors(clampedValue, format);\n    }\n\n    // If a real minimum is provided, use it\n    let base = minWithZeroDefault;\n    if (!small && minWithDefault !== Number.MIN_SAFE_INTEGER) {\n      base = minWithDefault;\n    }\n\n    const snappedValue = snapToStep(clampedValue, base, step, small ? 'nearest' : 'directional');\n    return removeFloatingPointErrors(snappedValue, format);\n  }\n\n  return removeFloatingPointErrors(clampedValue, format);\n}\n"
  },
  {
    "path": "packages/react/src/popover/arrow/PopoverArrow.test.tsx",
    "content": "import { Popover } from '@base-ui/react/popover';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Popover.Arrow />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Popover.Arrow />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <Popover.Root open>\n          <Popover.Trigger>Trigger</Popover.Trigger>\n          <Popover.Portal>\n            <Popover.Positioner>\n              <Popover.Popup>{node}</Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        </Popover.Root>,\n      );\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/popover/arrow/PopoverArrow.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { usePopoverPositionerContext } from '../positioner/PopoverPositionerContext';\nimport { usePopoverRootContext } from '../root/PopoverRootContext';\nimport type { Align, Side } from '../../utils/useAnchorPositioning';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { popupStateMapping } from '../../utils/popupStateMapping';\nimport { useRenderElement } from '../../utils/useRenderElement';\n\n/**\n * Displays an element positioned against the popover anchor.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Popover](https://base-ui.com/react/components/popover)\n */\nexport const PopoverArrow = React.forwardRef(function PopoverArrow(\n  componentProps: PopoverArrow.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { className, render, ...elementProps } = componentProps;\n\n  const { store } = usePopoverRootContext();\n  const open = store.useState('open');\n  const { arrowRef, side, align, arrowUncentered, arrowStyles } = usePopoverPositionerContext();\n\n  const state: PopoverArrowState = {\n    open,\n    side,\n    align,\n    uncentered: arrowUncentered,\n  };\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: [forwardedRef, arrowRef],\n    props: [{ style: arrowStyles, 'aria-hidden': true }, elementProps],\n    stateAttributesMapping: popupStateMapping,\n  });\n\n  return element;\n});\n\nexport interface PopoverArrowState {\n  /**\n   * Whether the popover is currently open.\n   */\n  open: boolean;\n  /**\n   * The side of the anchor the component is placed on.\n   */\n  side: Side;\n  /**\n   * The alignment of the component relative to the anchor.\n   */\n  align: Align;\n  /**\n   * Whether the arrow cannot be centered on the anchor.\n   */\n  uncentered: boolean;\n}\n\nexport interface PopoverArrowProps extends BaseUIComponentProps<'div', PopoverArrowState> {}\n\nexport namespace PopoverArrow {\n  export type State = PopoverArrowState;\n  export type Props = PopoverArrowProps;\n}\n"
  },
  {
    "path": "packages/react/src/popover/arrow/PopoverArrowDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum PopoverArrowDataAttributes {\n  /**\n   * Present when the popup is open.\n   */\n  open = CommonPopupDataAttributes.open,\n  /**\n   * Present when the popup is closed.\n   */\n  closed = CommonPopupDataAttributes.closed,\n  /**\n   * Indicates which side the popup is positioned relative to the trigger.\n   * @type {'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'}\n   */\n  side = CommonPopupDataAttributes.side,\n  /**\n   * Indicates how the popup is aligned relative to specified side.\n   * @type {'start' | 'center' | 'end'}\n   */\n  align = CommonPopupDataAttributes.align,\n  /**\n   * Present when the popover arrow is uncentered.\n   */\n  uncentered = 'data-uncentered',\n}\n"
  },
  {
    "path": "packages/react/src/popover/backdrop/PopoverBackdrop.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { Popover } from '@base-ui/react/popover';\nimport { createRenderer, describeConformance } from '#test-utils';\nimport { screen, waitFor } from '@mui/internal-test-utils';\n\ndescribe('<Popover.Backdrop />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Popover.Backdrop />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(<Popover.Root open>{node}</Popover.Root>);\n    },\n  }));\n\n  it('sets `pointer-events: none` style on backdrop if opened by hover', async () => {\n    const { user } = await render(\n      <Popover.Root>\n        <Popover.Trigger delay={0} openOnHover>\n          Open\n        </Popover.Trigger>\n        <Popover.Portal>\n          <Popover.Backdrop data-testid=\"backdrop\" />\n          <Popover.Positioner>\n            <Popover.Popup />\n          </Popover.Positioner>\n        </Popover.Portal>\n      </Popover.Root>,\n    );\n\n    await user.hover(screen.getByText('Open'));\n\n    expect(screen.getByTestId('backdrop').style.pointerEvents).toBe('none');\n  });\n\n  it('does not set `pointer-events: none` style on backdrop if opened by click', async () => {\n    const { user } = await render(\n      <Popover.Root>\n        <Popover.Trigger openOnHover>Open</Popover.Trigger>\n        <Popover.Portal>\n          <Popover.Backdrop data-testid=\"backdrop\" />\n          <Popover.Positioner>\n            <Popover.Popup />\n          </Popover.Positioner>\n        </Popover.Portal>\n      </Popover.Root>,\n    );\n\n    await user.click(screen.getByText('Open'));\n\n    await waitFor(() => {\n      expect(screen.getByTestId('backdrop').style.pointerEvents).not.toBe('none');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/popover/backdrop/PopoverBackdrop.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { usePopoverRootContext } from '../root/PopoverRootContext';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { type StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport { popupStateMapping as baseMapping } from '../../utils/popupStateMapping';\nimport type { TransitionStatus } from '../../utils/useTransitionStatus';\nimport { transitionStatusMapping } from '../../utils/stateAttributesMapping';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { REASONS } from '../../utils/reasons';\n\nconst stateAttributesMapping: StateAttributesMapping<PopoverBackdropState> = {\n  ...baseMapping,\n  ...transitionStatusMapping,\n};\n\n/**\n * An overlay displayed beneath the popover.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Popover](https://base-ui.com/react/components/popover)\n */\nexport const PopoverBackdrop = React.forwardRef(function PopoverBackdrop(\n  props: PopoverBackdrop.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { className, render, ...elementProps } = props;\n\n  const { store } = usePopoverRootContext();\n\n  const open = store.useState('open');\n  const mounted = store.useState('mounted');\n  const transitionStatus = store.useState('transitionStatus');\n  const openReason = store.useState('openChangeReason');\n\n  const state: PopoverBackdropState = {\n    open,\n    transitionStatus,\n  };\n\n  const element = useRenderElement('div', props, {\n    state,\n    ref: [store.context.backdropRef, forwardedRef],\n    props: [\n      {\n        role: 'presentation',\n        hidden: !mounted,\n        style: {\n          pointerEvents: openReason === REASONS.triggerHover ? 'none' : undefined,\n          userSelect: 'none',\n          WebkitUserSelect: 'none',\n        },\n      },\n      elementProps,\n    ],\n    stateAttributesMapping,\n  });\n\n  return element;\n});\n\nexport interface PopoverBackdropState {\n  /**\n   * Whether the popover is currently open.\n   */\n  open: boolean;\n  /**\n   * The transition status of the component.\n   */\n  transitionStatus: TransitionStatus;\n}\n\nexport interface PopoverBackdropProps extends BaseUIComponentProps<'div', PopoverBackdropState> {}\n\nexport namespace PopoverBackdrop {\n  export type State = PopoverBackdropState;\n  export type Props = PopoverBackdropProps;\n}\n"
  },
  {
    "path": "packages/react/src/popover/backdrop/PopoverBackdropDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum PopoverBackdropDataAttributes {\n  /**\n   * Present when the popup is open.\n   */\n  open = CommonPopupDataAttributes.open,\n  /**\n   * Present when the popup is closed.\n   */\n  closed = CommonPopupDataAttributes.closed,\n  /**\n   * Present when the popup is animating in.\n   */\n  startingStyle = CommonPopupDataAttributes.startingStyle,\n  /**\n   * Present when the popup is animating out.\n   */\n  endingStyle = CommonPopupDataAttributes.endingStyle,\n}\n"
  },
  {
    "path": "packages/react/src/popover/close/PopoverClose.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { Popover } from '@base-ui/react/popover';\nimport { fireEvent, screen, waitFor } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance } from '#test-utils';\n\nfunction isElementOrAncestorInert(element: HTMLElement) {\n  let current: HTMLElement | null = element;\n  while (current) {\n    if (\n      current.getAttribute('aria-hidden') === 'true' ||\n      current.hasAttribute('inert') ||\n      current.hasAttribute('data-base-ui-inert')\n    ) {\n      return true;\n    }\n    current = current.parentElement;\n  }\n  return false;\n}\n\ndescribe('<Popover.Close />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Popover.Close />, () => ({\n    refInstanceof: window.HTMLButtonElement,\n    testComponentPropWith: 'button',\n    button: true,\n\n    render(node) {\n      return render(\n        <Popover.Root open>\n          <Popover.Trigger>Trigger</Popover.Trigger>\n          <Popover.Portal>\n            <Popover.Positioner>\n              <Popover.Popup>{node}</Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        </Popover.Root>,\n      );\n    },\n  }));\n\n  it('renders when popover is closed', async () => {\n    await render(\n      <Popover.Root>\n        <Popover.Close aria-label=\"Close popover\" />\n      </Popover.Root>,\n    );\n\n    expect(screen.queryByRole('button', { name: 'Close popover' })).not.toBe(null);\n  });\n\n  it('should close popover when clicked', async () => {\n    await render(\n      <Popover.Root defaultOpen>\n        <Popover.Trigger>Trigger</Popover.Trigger>\n        <Popover.Portal>\n          <Popover.Positioner>\n            <Popover.Popup>\n              Content\n              <Popover.Close data-testid=\"close\" />\n            </Popover.Popup>\n          </Popover.Positioner>\n        </Popover.Portal>\n      </Popover.Root>,\n    );\n\n    expect(screen.queryByText('Content')).not.toBe(null);\n\n    fireEvent.click(screen.getByTestId('close'));\n\n    expect(screen.queryByText('Content')).toBe(null);\n  });\n\n  it('enables modal focus management when `modal=true` and close is rendered', async () => {\n    await render(\n      <div>\n        <button data-testid=\"outside\">Outside</button>\n        <Popover.Root defaultOpen modal>\n          <Popover.Trigger>Trigger</Popover.Trigger>\n          <Popover.Portal>\n            <Popover.Positioner>\n              <Popover.Popup>\n                <Popover.Close aria-label=\"Close popover\" />\n              </Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        </Popover.Root>\n      </div>,\n    );\n\n    await waitFor(() => {\n      expect(isElementOrAncestorInert(screen.getByTestId('outside'))).toBe(true);\n    });\n  });\n\n  it('enables modal focus management when `modal=\"trap-focus\"` and close is rendered', async () => {\n    await render(\n      <div>\n        <button data-testid=\"outside\">Outside</button>\n        <Popover.Root defaultOpen modal=\"trap-focus\">\n          <Popover.Trigger>Trigger</Popover.Trigger>\n          <Popover.Portal>\n            <Popover.Positioner>\n              <Popover.Popup>\n                <Popover.Close aria-label=\"Close popover\" />\n              </Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        </Popover.Root>\n      </div>,\n    );\n\n    await waitFor(() => {\n      expect(isElementOrAncestorInert(screen.getByTestId('outside'))).toBe(true);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/popover/close/PopoverClose.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport type { BaseUIComponentProps, NativeButtonProps } from '../../utils/types';\nimport { usePopoverRootContext } from '../root/PopoverRootContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useButton } from '../../use-button';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport { useClosePartRegistration } from '../../utils/closePart';\n\n/**\n * A button that closes the popover.\n * Renders a `<button>` element.\n *\n * Documentation: [Base UI Popover](https://base-ui.com/react/components/popover)\n */\nexport const PopoverClose = React.forwardRef(function PopoverClose(\n  componentProps: PopoverClose.Props,\n  forwardedRef: React.ForwardedRef<HTMLButtonElement>,\n) {\n  const {\n    render,\n    className,\n    disabled = false,\n    nativeButton = true,\n    ...elementProps\n  } = componentProps;\n\n  const { buttonRef, getButtonProps } = useButton({\n    disabled,\n    focusableWhenDisabled: false,\n    native: nativeButton,\n  });\n\n  const { store } = usePopoverRootContext();\n  useClosePartRegistration();\n\n  const element = useRenderElement('button', componentProps, {\n    ref: [forwardedRef, buttonRef],\n    props: [\n      {\n        onClick(event) {\n          store.setOpen(\n            false,\n            createChangeEventDetails(REASONS.closePress, event.nativeEvent, event.currentTarget),\n          );\n        },\n      },\n      elementProps,\n      getButtonProps,\n    ],\n  });\n\n  return element;\n});\n\nexport interface PopoverCloseState {}\n\nexport interface PopoverCloseProps\n  extends NativeButtonProps, BaseUIComponentProps<'button', PopoverCloseState> {}\n\nexport namespace PopoverClose {\n  export type State = PopoverCloseState;\n  export type Props = PopoverCloseProps;\n}\n"
  },
  {
    "path": "packages/react/src/popover/description/PopoverDescription.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { Popover } from '@base-ui/react/popover';\nimport { screen } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Popover.Description />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Popover.Description />, () => ({\n    refInstanceof: window.HTMLParagraphElement,\n    render(node) {\n      return render(\n        <Popover.Root open>\n          <Popover.Trigger>Trigger</Popover.Trigger>\n          <Popover.Portal>\n            <Popover.Positioner>\n              <Popover.Popup>{node}</Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        </Popover.Root>,\n      );\n    },\n  }));\n\n  it('describes the popup element with its id', async () => {\n    await render(\n      <Popover.Root open>\n        <Popover.Trigger>Trigger</Popover.Trigger>\n        <Popover.Portal>\n          <Popover.Positioner>\n            <Popover.Popup>\n              <Popover.Description>Title</Popover.Description>\n            </Popover.Popup>\n          </Popover.Positioner>\n        </Popover.Portal>\n      </Popover.Root>,\n    );\n\n    const id = document.querySelector('p')?.id;\n    expect(screen.getByRole('dialog')).toHaveAttribute('aria-describedby', id);\n  });\n});\n"
  },
  {
    "path": "packages/react/src/popover/description/PopoverDescription.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { usePopoverRootContext } from '../root/PopoverRootContext';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useBaseUiId } from '../../utils/useBaseUiId';\nimport { useRenderElement } from '../../utils/useRenderElement';\n\n/**\n * A paragraph with additional information about the popover.\n * Renders a `<p>` element.\n *\n * Documentation: [Base UI Popover](https://base-ui.com/react/components/popover)\n */\nexport const PopoverDescription = React.forwardRef(function PopoverDescription(\n  componentProps: PopoverDescription.Props,\n  forwardedRef: React.ForwardedRef<HTMLParagraphElement>,\n) {\n  const { render, className, ...elementProps } = componentProps;\n\n  const { store } = usePopoverRootContext();\n\n  const id = useBaseUiId(elementProps.id);\n\n  useIsoLayoutEffect(() => {\n    store.set('descriptionElementId', id);\n    return () => {\n      store.set('descriptionElementId', undefined);\n    };\n  }, [store, id]);\n\n  const element = useRenderElement('p', componentProps, {\n    ref: forwardedRef,\n    props: [{ id }, elementProps],\n  });\n\n  return element;\n});\n\nexport interface PopoverDescriptionState {}\n\nexport interface PopoverDescriptionProps extends BaseUIComponentProps<\n  'p',\n  PopoverDescriptionState\n> {}\n\nexport namespace PopoverDescription {\n  export type State = PopoverDescriptionState;\n  export type Props = PopoverDescriptionProps;\n}\n"
  },
  {
    "path": "packages/react/src/popover/index.parts.ts",
    "content": "export { PopoverRoot as Root } from './root/PopoverRoot';\nexport { PopoverTrigger as Trigger } from './trigger/PopoverTrigger';\nexport { PopoverPortal as Portal } from './portal/PopoverPortal';\nexport { PopoverPositioner as Positioner } from './positioner/PopoverPositioner';\nexport { PopoverPopup as Popup } from './popup/PopoverPopup';\nexport { PopoverArrow as Arrow } from './arrow/PopoverArrow';\nexport { PopoverBackdrop as Backdrop } from './backdrop/PopoverBackdrop';\nexport { PopoverTitle as Title } from './title/PopoverTitle';\nexport { PopoverDescription as Description } from './description/PopoverDescription';\nexport { PopoverClose as Close } from './close/PopoverClose';\nexport { PopoverViewport as Viewport } from './viewport/PopoverViewport';\nexport {\n  createPopoverHandle as createHandle,\n  PopoverHandle as Handle,\n} from './store/PopoverHandle';\n"
  },
  {
    "path": "packages/react/src/popover/index.ts",
    "content": "export * as Popover from './index.parts';\n\nexport type * from './root/PopoverRoot';\nexport type * from './trigger/PopoverTrigger';\nexport type * from './portal/PopoverPortal';\nexport type * from './positioner/PopoverPositioner';\nexport type * from './popup/PopoverPopup';\nexport type * from './arrow/PopoverArrow';\nexport type * from './backdrop/PopoverBackdrop';\nexport type * from './title/PopoverTitle';\nexport type * from './description/PopoverDescription';\nexport type * from './close/PopoverClose';\nexport type * from './viewport/PopoverViewport';\n"
  },
  {
    "path": "packages/react/src/popover/popup/PopoverPopup.test.tsx",
    "content": "import { expect } from 'vitest';\nimport * as React from 'react';\nimport { Popover } from '@base-ui/react/popover';\nimport { act, fireEvent, flushMicrotasks, screen, waitFor } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance, isJSDOM, waitSingleFrame } from '#test-utils';\n\ndescribe('<Popover.Popup />', () => {\n  const { render, clock } = createRenderer();\n\n  describeConformance(<Popover.Popup />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <Popover.Root open>\n          <Popover.Trigger>Trigger</Popover.Trigger>\n          <Popover.Portal>\n            <Popover.Positioner>{node}</Popover.Positioner>\n          </Popover.Portal>\n        </Popover.Root>,\n      );\n    },\n  }));\n\n  it('should render the children', async () => {\n    await render(\n      <Popover.Root open>\n        <Popover.Trigger>Trigger</Popover.Trigger>\n        <Popover.Portal>\n          <Popover.Positioner>\n            <Popover.Popup>Content</Popover.Popup>\n          </Popover.Positioner>\n        </Popover.Portal>\n      </Popover.Root>,\n    );\n\n    expect(screen.getByText('Content')).not.toBe(null);\n  });\n\n  describe('prop: initialFocus', () => {\n    it('should focus the first focusable element within the popup by default', async () => {\n      await render(\n        <div>\n          <input />\n          <Popover.Root>\n            <Popover.Trigger>Open</Popover.Trigger>\n            <Popover.Portal>\n              <Popover.Positioner>\n                <Popover.Popup data-testid=\"popover\">\n                  <input data-testid=\"popover-input\" />\n                  <button>Close</button>\n                </Popover.Popup>\n              </Popover.Positioner>\n            </Popover.Portal>\n          </Popover.Root>\n          <input />\n        </div>,\n      );\n\n      const trigger = screen.getByText('Open');\n      await act(async () => {\n        trigger.click();\n      });\n\n      await waitFor(() => {\n        const innerInput = screen.getByTestId('popover-input');\n        expect(innerInput).to.toHaveFocus();\n      });\n    });\n\n    it('should focus the element provided to `initialFocus` as a ref when open', async () => {\n      function TestComponent() {\n        const input2Ref = React.useRef<HTMLInputElement | null>(null);\n        return (\n          <div>\n            <input />\n            <Popover.Root>\n              <Popover.Trigger>Open</Popover.Trigger>\n              <Popover.Portal>\n                <Popover.Positioner>\n                  <Popover.Popup initialFocus={input2Ref}>\n                    <input data-testid=\"input-1\" />\n                    <input data-testid=\"input-2\" ref={input2Ref} />\n                    <input data-testid=\"input-3\" />\n                    <button>Close</button>\n                  </Popover.Popup>\n                </Popover.Positioner>\n              </Popover.Portal>\n            </Popover.Root>\n            <input />\n          </div>\n        );\n      }\n\n      await render(<TestComponent />);\n\n      const trigger = screen.getByText('Open');\n      await act(async () => {\n        trigger.click();\n      });\n\n      await waitFor(() => {\n        const input2 = screen.getByTestId('input-2');\n        expect(input2).to.toHaveFocus();\n      });\n    });\n\n    it('should focus the element provided to `initialFocus` as a function when open', async () => {\n      function TestComponent() {\n        const input2Ref = React.useRef<HTMLInputElement>(null);\n\n        const getRef = React.useCallback(() => input2Ref.current, []);\n\n        return (\n          <div>\n            <input />\n            <Popover.Root>\n              <Popover.Trigger>Open</Popover.Trigger>\n              <Popover.Portal>\n                <Popover.Positioner>\n                  <Popover.Popup initialFocus={getRef}>\n                    <input data-testid=\"input-1\" />\n                    <input data-testid=\"input-2\" ref={input2Ref} />\n                    <input data-testid=\"input-3\" />\n                    <button>Close</button>\n                  </Popover.Popup>\n                </Popover.Positioner>\n              </Popover.Portal>\n            </Popover.Root>\n            <input />\n          </div>\n        );\n      }\n\n      const { user } = await render(<TestComponent />);\n\n      const trigger = screen.getByText('Open');\n      await user.click(trigger);\n\n      await waitFor(() => {\n        const input2 = screen.getByTestId('input-2');\n        expect(input2).to.toHaveFocus();\n      });\n    });\n\n    it('should support element-returning function and no-op via false/void for initialFocus', async () => {\n      function TestComponent() {\n        const input2Ref = React.useRef<HTMLInputElement>(null);\n\n        const getEl = React.useCallback((type: string) => {\n          if (type === 'keyboard') {\n            return input2Ref.current;\n          }\n          return undefined;\n        }, []);\n\n        return (\n          <div>\n            <Popover.Root>\n              <Popover.Trigger>Open</Popover.Trigger>\n              <Popover.Portal>\n                <Popover.Positioner>\n                  <Popover.Popup data-testid=\"popover\" initialFocus={getEl}>\n                    <input data-testid=\"input-1\" />\n                    <input data-testid=\"input-2\" ref={input2Ref} />\n                  </Popover.Popup>\n                </Popover.Positioner>\n              </Popover.Portal>\n            </Popover.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<TestComponent />);\n\n      const trigger = screen.getByText('Open');\n      await user.click(trigger);\n\n      await waitFor(() => {\n        expect(trigger).toHaveFocus();\n      });\n\n      await user.keyboard('{Escape}');\n      await user.keyboard('{Enter}');\n\n      await waitFor(() => {\n        expect(screen.getByTestId('input-2')).toHaveFocus();\n      });\n    });\n\n    it('should not move focus when initialFocus is false', async () => {\n      function TestComponent() {\n        return (\n          <div>\n            <Popover.Root>\n              <Popover.Trigger>Open</Popover.Trigger>\n              <Popover.Portal>\n                <Popover.Positioner>\n                  <Popover.Popup data-testid=\"popover\" initialFocus={false}>\n                    <input data-testid=\"input-1\" />\n                  </Popover.Popup>\n                </Popover.Positioner>\n              </Popover.Portal>\n            </Popover.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<TestComponent />);\n      const trigger = screen.getByText('Open');\n      await user.click(trigger);\n      await waitFor(() => {\n        expect(trigger).toHaveFocus();\n      });\n    });\n\n    it('should default focus when initialFocus returns true', async () => {\n      function TestComponent() {\n        return (\n          <div>\n            <Popover.Root>\n              <Popover.Trigger>Open</Popover.Trigger>\n              <Popover.Portal>\n                <Popover.Positioner>\n                  <Popover.Popup data-testid=\"popover\" initialFocus={() => true}>\n                    <input data-testid=\"input-1\" />\n                  </Popover.Popup>\n                </Popover.Positioner>\n              </Popover.Portal>\n            </Popover.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<TestComponent />);\n      await user.click(screen.getByText('Open'));\n      await waitFor(() => {\n        expect(screen.getByTestId('input-1')).toHaveFocus();\n      });\n    });\n\n    it('uses default behavior when initialFocus returns null', async () => {\n      function TestComponent() {\n        return (\n          <div>\n            <Popover.Root>\n              <Popover.Trigger>Open</Popover.Trigger>\n              <Popover.Portal>\n                <Popover.Positioner>\n                  <Popover.Popup data-testid=\"popover\" initialFocus={() => null}>\n                    <input data-testid=\"input-1\" />\n                  </Popover.Popup>\n                </Popover.Positioner>\n              </Popover.Portal>\n            </Popover.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<TestComponent />);\n      await user.click(screen.getByText('Open'));\n      await waitFor(() => {\n        expect(screen.getByTestId('input-1')).toHaveFocus();\n      });\n    });\n  });\n\n  it.skipIf(isJSDOM)('focuses the popup when the active element becomes display:none', async () => {\n    function TestComponent() {\n      const [hidden, setHidden] = React.useState(false);\n\n      return (\n        <Popover.Root open>\n          <Popover.Trigger>Open</Popover.Trigger>\n          <Popover.Portal>\n            <Popover.Positioner>\n              <Popover.Popup data-testid=\"popup\">\n                <button\n                  data-testid=\"hide-button\"\n                  style={{ display: hidden ? 'none' : undefined }}\n                  onClick={() => setHidden(true)}\n                >\n                  Hide\n                </button>\n                <input />\n              </Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        </Popover.Root>\n      );\n    }\n\n    const { user } = await render(<TestComponent />);\n\n    await waitFor(() => {\n      expect(screen.getByTestId('hide-button')).toHaveFocus();\n    });\n\n    await user.click(screen.getByTestId('hide-button'));\n\n    await waitFor(() => {\n      expect(screen.getByTestId('popup')).toHaveFocus();\n    });\n  });\n\n  describe('openOnHover: delay + click', () => {\n    clock.withFakeTimers();\n\n    it('returns focus to the trigger if opened by click before the hover delay completes', async () => {\n      await render(\n        <Popover.Root>\n          <Popover.Trigger openOnHover delay={300}>\n            Open\n          </Popover.Trigger>\n          <Popover.Portal>\n            <Popover.Positioner>\n              <Popover.Popup>\n                <Popover.Close>Close</Popover.Close>\n              </Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        </Popover.Root>,\n      );\n\n      const trigger = screen.getByText('Open');\n\n      fireEvent.mouseEnter(trigger);\n      fireEvent.mouseMove(trigger);\n\n      clock.tick(100);\n\n      fireEvent.click(trigger);\n      await flushMicrotasks();\n\n      expect(screen.getByText('Close')).not.toBe(null);\n\n      clock.tick(1000);\n      await flushMicrotasks();\n\n      fireEvent.click(screen.getByText('Close'));\n      await flushMicrotasks();\n\n      expect(trigger).toHaveFocus();\n    });\n  });\n\n  describe('prop: finalFocus', () => {\n    it('should focus the trigger by default when closed', async () => {\n      await render(\n        <div>\n          <input />\n          <Popover.Root>\n            <Popover.Trigger>Open</Popover.Trigger>\n            <Popover.Portal>\n              <Popover.Positioner>\n                <Popover.Popup>\n                  <Popover.Close>Close</Popover.Close>\n                </Popover.Popup>\n              </Popover.Positioner>\n            </Popover.Portal>\n          </Popover.Root>\n          <input />\n        </div>,\n      );\n\n      const trigger = screen.getByText('Open');\n      await act(async () => {\n        trigger.click();\n      });\n\n      const closeButton = screen.getByText('Close');\n      await act(async () => {\n        closeButton.click();\n      });\n\n      await waitFor(() => {\n        expect(trigger).toHaveFocus();\n      });\n    });\n\n    it('should focus the element provided to the prop when closed', async () => {\n      function TestComponent() {\n        const inputRef = React.useRef<HTMLInputElement | null>(null);\n        return (\n          <div>\n            <input />\n            <Popover.Root>\n              <Popover.Trigger>Open</Popover.Trigger>\n              <Popover.Portal>\n                <Popover.Positioner>\n                  <Popover.Popup finalFocus={inputRef}>\n                    <Popover.Close>Close</Popover.Close>\n                  </Popover.Popup>\n                </Popover.Positioner>\n              </Popover.Portal>\n            </Popover.Root>\n            <input />\n            <input data-testid=\"input-to-focus\" ref={inputRef} />\n            <input />\n          </div>\n        );\n      }\n\n      await render(<TestComponent />);\n\n      const trigger = screen.getByText('Open');\n      await act(async () => {\n        trigger.click();\n      });\n\n      const closeButton = screen.getByText('Close');\n      await act(async () => {\n        closeButton.click();\n      });\n\n      const inputToFocus = screen.getByTestId('input-to-focus');\n\n      await waitFor(() => {\n        expect(inputToFocus).toHaveFocus();\n      });\n    });\n\n    it('should focus the element provided to `finalFocus` as a function when closed', async () => {\n      function TestComponent() {\n        const ref = React.useRef<HTMLInputElement>(null);\n        const getRef = React.useCallback(() => ref.current, []);\n        return (\n          <div>\n            <Popover.Root>\n              <Popover.Trigger>Open</Popover.Trigger>\n              <Popover.Portal>\n                <Popover.Positioner>\n                  <Popover.Popup finalFocus={getRef}>\n                    <Popover.Close>Close</Popover.Close>\n                  </Popover.Popup>\n                </Popover.Positioner>\n              </Popover.Portal>\n            </Popover.Root>\n            <input data-testid=\"input-to-focus\" ref={ref} />\n          </div>\n        );\n      }\n\n      const { user } = await render(<TestComponent />);\n\n      const trigger = screen.getByText('Open');\n      await user.click(trigger);\n\n      const closeButton = screen.getByText('Close');\n      await user.click(closeButton);\n\n      await waitFor(() => {\n        expect(screen.getByTestId('input-to-focus')).toHaveFocus();\n      });\n    });\n\n    it('should not move focus when finalFocus is false', async () => {\n      function TestComponent() {\n        return (\n          <div>\n            <Popover.Root>\n              <Popover.Trigger>Open</Popover.Trigger>\n              <Popover.Portal>\n                <Popover.Positioner>\n                  <Popover.Popup finalFocus={false}>\n                    <Popover.Close>Close</Popover.Close>\n                  </Popover.Popup>\n                </Popover.Positioner>\n              </Popover.Portal>\n            </Popover.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<TestComponent />);\n      const trigger = screen.getByText('Open');\n\n      await user.click(trigger);\n      await user.click(screen.getByText('Close'));\n\n      await waitFor(() => {\n        expect(trigger).not.toHaveFocus();\n      });\n    });\n\n    it('should move focus to the trigger when finalFocus returns true', async () => {\n      function TestComponent() {\n        return (\n          <div>\n            <Popover.Root>\n              <Popover.Trigger>Open</Popover.Trigger>\n              <Popover.Portal>\n                <Popover.Positioner>\n                  <Popover.Popup finalFocus={() => true}>\n                    <Popover.Close>Close</Popover.Close>\n                  </Popover.Popup>\n                </Popover.Positioner>\n              </Popover.Portal>\n            </Popover.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<TestComponent />);\n      const trigger = screen.getByText('Open');\n\n      await user.click(trigger);\n      await user.click(screen.getByText('Close'));\n\n      await waitFor(() => {\n        expect(trigger).toHaveFocus();\n      });\n    });\n\n    it('should support element-returning function and default via true + no-op via void for finalFocus based on closeType', async () => {\n      function TestComponent() {\n        const inputRef = React.useRef<HTMLInputElement>(null);\n        const getEl = React.useCallback((type: string) => {\n          if (type === 'keyboard') {\n            return inputRef.current;\n          }\n          return true;\n        }, []);\n\n        return (\n          <div>\n            <Popover.Root>\n              <Popover.Trigger>Open</Popover.Trigger>\n              <Popover.Portal>\n                <Popover.Positioner>\n                  <Popover.Popup finalFocus={getEl}>\n                    <Popover.Close>Close</Popover.Close>\n                  </Popover.Popup>\n                </Popover.Positioner>\n              </Popover.Portal>\n            </Popover.Root>\n            <input data-testid=\"final-input\" ref={inputRef} />\n          </div>\n        );\n      }\n\n      const { user } = await render(<TestComponent />);\n\n      const trigger = screen.getByText('Open');\n\n      // Close via pointer: true => default, should move focus to trigger\n      await user.click(trigger);\n      await user.click(screen.getByText('Close'));\n      await waitFor(() => {\n        expect(trigger).toHaveFocus();\n      });\n\n      // Close via keyboard: should move focus to final-input\n      await user.click(trigger);\n      await waitSingleFrame();\n      await user.keyboard('{Escape}');\n      await waitFor(() => {\n        expect(screen.getByTestId('final-input')).toHaveFocus();\n      });\n    });\n\n    it('uses default behavior when finalFocus returns null', async () => {\n      function TestComponent() {\n        return (\n          <div>\n            <Popover.Root>\n              <Popover.Trigger>Open</Popover.Trigger>\n              <Popover.Portal>\n                <Popover.Positioner>\n                  <Popover.Popup finalFocus={() => null}>\n                    <Popover.Close>Close</Popover.Close>\n                  </Popover.Popup>\n                </Popover.Positioner>\n              </Popover.Portal>\n            </Popover.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<TestComponent />);\n      const trigger = screen.getByText('Open');\n      await user.click(trigger);\n      await user.click(screen.getByText('Close'));\n      await waitFor(() => {\n        expect(trigger).toHaveFocus();\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/popover/popup/PopoverPopup.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { InteractionType } from '@base-ui/utils/useEnhancedClickHandler';\nimport { isHTMLElement } from '@floating-ui/utils/dom';\nimport { FloatingFocusManager, useHoverFloatingInteraction } from '../../floating-ui-react';\nimport { usePopoverRootContext } from '../root/PopoverRootContext';\nimport { usePopoverPositionerContext } from '../positioner/PopoverPositionerContext';\nimport type { Side, Align } from '../../utils/useAnchorPositioning';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport type { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport type { TransitionStatus } from '../../utils/useTransitionStatus';\nimport { popupStateMapping as baseMapping } from '../../utils/popupStateMapping';\nimport { transitionStatusMapping } from '../../utils/stateAttributesMapping';\nimport { useOpenChangeComplete } from '../../utils/useOpenChangeComplete';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { REASONS } from '../../utils/reasons';\nimport { COMPOSITE_KEYS } from '../../composite/composite';\nimport { useToolbarRootContext } from '../../toolbar/root/ToolbarRootContext';\nimport { getDisabledMountTransitionStyles } from '../../utils/getDisabledMountTransitionStyles';\nimport { ClosePartProvider, useClosePartCount } from '../../utils/closePart';\n\nconst stateAttributesMapping: StateAttributesMapping<PopoverPopupState> = {\n  ...baseMapping,\n  ...transitionStatusMapping,\n};\n\n/**\n * A container for the popover contents.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Popover](https://base-ui.com/react/components/popover)\n */\nexport const PopoverPopup = React.forwardRef(function PopoverPopup(\n  componentProps: PopoverPopup.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { className, render, initialFocus, finalFocus, ...elementProps } = componentProps;\n\n  const { store } = usePopoverRootContext();\n\n  const positioner = usePopoverPositionerContext();\n  const insideToolbar = useToolbarRootContext(true) != null;\n  const { context: closePartContext, hasClosePart } = useClosePartCount();\n\n  const open = store.useState('open');\n  const openMethod = store.useState('openMethod');\n  const instantType = store.useState('instantType');\n  const transitionStatus = store.useState('transitionStatus');\n  const popupProps = store.useState('popupProps');\n  const titleId = store.useState('titleElementId');\n  const descriptionId = store.useState('descriptionElementId');\n  const modal = store.useState('modal');\n  const mounted = store.useState('mounted');\n  const openReason = store.useState('openChangeReason');\n  const activeTriggerElement = store.useState('activeTriggerElement');\n  const floatingContext = store.useState('floatingRootContext');\n\n  useOpenChangeComplete({\n    open,\n    ref: store.context.popupRef,\n    onComplete() {\n      if (open) {\n        store.context.onOpenChangeComplete?.(true);\n      }\n    },\n  });\n\n  const disabled = store.useState('disabled');\n  const openOnHover = store.useState('openOnHover');\n  const closeDelay = store.useState('closeDelay');\n\n  useHoverFloatingInteraction(floatingContext, { enabled: openOnHover && !disabled, closeDelay });\n\n  // Default initial focus logic:\n  // If opened by touch, focus the popup element to prevent the virtual keyboard from opening\n  // (this is required for Android specifically as iOS handles this automatically).\n  function defaultInitialFocus(interactionType: InteractionType) {\n    if (interactionType === 'touch') {\n      return store.context.popupRef.current;\n    }\n    return true;\n  }\n\n  const resolvedInitialFocus = initialFocus === undefined ? defaultInitialFocus : initialFocus;\n\n  const state: PopoverPopupState = {\n    open,\n    side: positioner.side,\n    align: positioner.align,\n    instant: instantType,\n    transitionStatus,\n  };\n  const focusManagerModal = modal !== false && hasClosePart;\n  store.useSyncedValue('focusManagerModal', focusManagerModal);\n\n  const setPopupElement = React.useCallback(\n    (element: HTMLElement | null) => {\n      store.set('popupElement', element);\n    },\n    [store],\n  );\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: [forwardedRef, store.context.popupRef, setPopupElement],\n    props: [\n      popupProps,\n      {\n        'aria-labelledby': titleId,\n        'aria-describedby': descriptionId,\n        onKeyDown(event) {\n          if (insideToolbar && COMPOSITE_KEYS.has(event.key)) {\n            event.stopPropagation();\n          }\n        },\n      },\n      getDisabledMountTransitionStyles(transitionStatus),\n      elementProps,\n    ],\n    stateAttributesMapping,\n  });\n\n  return (\n    <FloatingFocusManager\n      context={floatingContext}\n      openInteractionType={openMethod}\n      modal={focusManagerModal}\n      disabled={!mounted || openReason === REASONS.triggerHover}\n      initialFocus={resolvedInitialFocus}\n      returnFocus={finalFocus}\n      restoreFocus=\"popup\"\n      previousFocusableElement={\n        isHTMLElement(activeTriggerElement) ? activeTriggerElement : undefined\n      }\n      nextFocusableElement={store.context.triggerFocusTargetRef}\n      beforeContentFocusGuardRef={store.context.beforeContentFocusGuardRef}\n    >\n      <ClosePartProvider value={closePartContext}>{element}</ClosePartProvider>\n    </FloatingFocusManager>\n  );\n});\n\nexport interface PopoverPopupState {\n  /**\n   * Whether the popover is currently open.\n   */\n  open: boolean;\n  /**\n   * The side of the anchor the component is placed on.\n   */\n  side: Side;\n  /**\n   * The alignment of the component relative to the anchor.\n   */\n  align: Align;\n  /**\n   * The transition status of the component.\n   */\n  transitionStatus: TransitionStatus;\n  /**\n   * Whether transitions should be skipped.\n   */\n  instant: 'dismiss' | 'click' | undefined;\n}\n\nexport interface PopoverPopupProps extends BaseUIComponentProps<'div', PopoverPopupState> {\n  /**\n   * Determines the element to focus when the popover is opened.\n   *\n   * - `false`: Do not move focus.\n   * - `true`: Move focus based on the default behavior (first tabbable element or popup).\n   * - `RefObject`: Move focus to the ref element.\n   * - `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`).\n   *   Return an element to focus, `true` to use the default behavior, or `false`/`undefined` to do nothing.\n   */\n  initialFocus?:\n    | boolean\n    | React.RefObject<HTMLElement | null>\n    | ((openType: InteractionType) => void | boolean | HTMLElement | null)\n    | undefined;\n  /**\n   * Determines the element to focus when the popover is closed.\n   *\n   * - `false`: Do not move focus.\n   * - `true`: Move focus based on the default behavior (trigger or previously focused element).\n   * - `RefObject`: Move focus to the ref element.\n   * - `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`).\n   *   Return an element to focus, `true` to use the default behavior, or `false`/`undefined` to do nothing.\n   */\n  finalFocus?:\n    | boolean\n    | React.RefObject<HTMLElement | null>\n    | ((closeType: InteractionType) => void | boolean | HTMLElement | null)\n    | undefined;\n}\n\nexport namespace PopoverPopup {\n  export type State = PopoverPopupState;\n  export type Props = PopoverPopupProps;\n}\n"
  },
  {
    "path": "packages/react/src/popover/popup/PopoverPopupCssVars.ts",
    "content": "export enum PopoverPopupCssVars {\n  /**\n   * The width of the popup.\n   */\n  popupWidth = '--popup-width',\n  /**\n   * The height of the popup.\n   */\n  popupHeight = '--popup-height',\n}\n"
  },
  {
    "path": "packages/react/src/popover/popup/PopoverPopupDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum PopoverPopupDataAttributes {\n  /**\n   * Present when the popup is open.\n   */\n  open = CommonPopupDataAttributes.open,\n  /**\n   * Present when the popup is closed.\n   */\n  closed = CommonPopupDataAttributes.closed,\n  /**\n   * Present when the popup is animating in.\n   */\n  startingStyle = CommonPopupDataAttributes.startingStyle,\n  /**\n   * Present when the popup is animating out.\n   */\n  endingStyle = CommonPopupDataAttributes.endingStyle,\n  /**\n   * Indicates which side the popup is positioned relative to the trigger.\n   * @type {'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'}\n   */\n  side = 'data-side',\n  /**\n   * Indicates how the popup is aligned relative to specified side.\n   * @type {'start' | 'center' | 'end'}\n   */\n  align = 'data-align',\n  /**\n   * Present if animations should be instant.\n   * @type {'click' | 'dismiss'}\n   */\n  instant = 'data-instant',\n}\n"
  },
  {
    "path": "packages/react/src/popover/portal/PopoverPortal.test.tsx",
    "content": "import * as React from 'react';\nimport { Popover } from '@base-ui/react/popover';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Popover.Portal />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Popover.Portal />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(<Popover.Root open>{node}</Popover.Root>);\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/popover/portal/PopoverPortal.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { FloatingPortal } from '../../floating-ui-react';\nimport { usePopoverRootContext } from '../root/PopoverRootContext';\nimport { PopoverPortalContext } from './PopoverPortalContext';\n\n/**\n * A portal element that moves the popup to a different part of the DOM.\n * By default, the portal element is appended to `<body>`.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Popover](https://base-ui.com/react/components/popover)\n */\nexport const PopoverPortal = React.forwardRef(function PopoverPortal(\n  props: PopoverPortal.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { keepMounted = false, ...portalProps } = props;\n\n  const { store } = usePopoverRootContext();\n  const mounted = store.useState('mounted');\n\n  const shouldRender = mounted || keepMounted;\n  if (!shouldRender) {\n    return null;\n  }\n\n  return (\n    <PopoverPortalContext.Provider value={keepMounted}>\n      <FloatingPortal ref={forwardedRef} {...portalProps} />\n    </PopoverPortalContext.Provider>\n  );\n});\n\nexport interface PopoverPortalState {}\n\nexport interface PopoverPortalProps extends FloatingPortal.Props<PopoverPortalState> {\n  /**\n   * Whether to keep the portal mounted in the DOM while the popup is hidden.\n   * @default false\n   */\n  keepMounted?: boolean | undefined;\n}\n\nexport namespace PopoverPortal {\n  export type State = PopoverPortalState;\n  export type Props = PopoverPortalProps;\n}\n"
  },
  {
    "path": "packages/react/src/popover/portal/PopoverPortalContext.ts",
    "content": "'use client';\nimport * as React from 'react';\n\nexport const PopoverPortalContext = React.createContext<boolean | undefined>(undefined);\n\nexport function usePopoverPortalContext() {\n  const value = React.useContext(PopoverPortalContext);\n  if (value === undefined) {\n    throw new Error('Base UI: <Popover.Portal> is missing.');\n  }\n  return value;\n}\n"
  },
  {
    "path": "packages/react/src/popover/positioner/PopoverPositioner.spec.tsx",
    "content": "import { Popover } from '@base-ui/react';\n\n// @ts-expect-error - `keepMounted` should not be available\n<Popover.Positioner keepMounted />;\n"
  },
  {
    "path": "packages/react/src/popover/positioner/PopoverPositioner.test.tsx",
    "content": "import { expect } from 'vitest';\nimport * as React from 'react';\nimport { Popover } from '@base-ui/react/popover';\nimport { screen, waitFor } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\n\nconst Trigger = React.forwardRef(function Trigger(\n  props: Popover.Trigger.Props,\n  ref: React.ForwardedRef<any>,\n) {\n  return <Popover.Trigger {...props} ref={ref} render={<div />} nativeButton={false} />;\n});\n\ndescribe('<Popover.Positioner />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Popover.Positioner />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <Popover.Root open>\n          <Popover.Trigger>Trigger</Popover.Trigger>\n          <Popover.Portal>{node}</Popover.Portal>\n        </Popover.Root>,\n      );\n    },\n  }));\n\n  const baselineX = 10;\n  const baselineY = 36;\n  const popupWidth = 52;\n  const popupHeight = 24;\n  const anchorWidth = 72;\n  const anchorHeight = 36;\n  const triggerStyle = { width: anchorWidth, height: anchorHeight };\n  const popupStyle = { width: popupWidth, height: popupHeight };\n\n  describe.skipIf(isJSDOM)('prop: sideOffset', () => {\n    it('offsets the side when a number is specified', async () => {\n      const sideOffset = 7;\n      await render(\n        <Popover.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Popover.Portal>\n            <Popover.Positioner data-testid=\"positioner\" sideOffset={sideOffset}>\n              <Popover.Popup style={popupStyle}>Popup</Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        </Popover.Root>,\n      );\n\n      expect(screen.getByTestId('positioner').getBoundingClientRect()).toMatchObject({\n        x: baselineX,\n        y: baselineY + sideOffset,\n      });\n    });\n\n    it('offsets the side when a function is specified', async () => {\n      await render(\n        <Popover.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Popover.Portal>\n            <Popover.Positioner\n              data-testid=\"positioner\"\n              sideOffset={(data) => data.positioner.width + data.anchor.width}\n            >\n              <Popover.Popup style={popupStyle}>Popup</Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        </Popover.Root>,\n      );\n\n      expect(screen.getByTestId('positioner').getBoundingClientRect()).toMatchObject({\n        x: baselineX,\n        y: baselineY + popupWidth + anchorWidth,\n      });\n    });\n\n    it('can read the latest side inside sideOffset', async () => {\n      let side = 'none';\n      await render(\n        <Popover.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Popover.Portal>\n            <Popover.Positioner\n              side=\"left\"\n              data-testid=\"positioner\"\n              sideOffset={(data) => {\n                side = data.side;\n                return 0;\n              }}\n            >\n              <Popover.Popup style={popupStyle}>Popup</Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        </Popover.Root>,\n      );\n\n      // correctly flips the side in the browser\n      expect(side).toBe('right');\n    });\n\n    it('can read the latest align inside sideOffset', async () => {\n      let align = 'none';\n      await render(\n        <Popover.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Popover.Portal>\n            <Popover.Positioner\n              side=\"right\"\n              align=\"start\"\n              data-testid=\"positioner\"\n              sideOffset={(data) => {\n                align = data.align;\n                return 0;\n              }}\n            >\n              <Popover.Popup style={popupStyle}>Popup</Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        </Popover.Root>,\n      );\n\n      // correctly flips the align in the browser\n      expect(align).toBe('end');\n    });\n\n    it('reads logical side inside sideOffset', async () => {\n      let side = 'none';\n      await render(\n        <Popover.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Popover.Portal>\n            <Popover.Positioner\n              side=\"inline-start\"\n              data-testid=\"positioner\"\n              sideOffset={(data) => {\n                side = data.side;\n                return 0;\n              }}\n            >\n              <Popover.Popup style={popupStyle}>Popup</Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        </Popover.Root>,\n      );\n\n      // correctly flips the side in the browser\n      expect(side).toBe('inline-end');\n    });\n  });\n\n  describe.skipIf(isJSDOM)('prop: alignOffset', () => {\n    it('offsets the align when a number is specified', async () => {\n      const alignOffset = 7;\n      await render(\n        <Popover.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Popover.Portal>\n            <Popover.Positioner data-testid=\"positioner\" alignOffset={alignOffset}>\n              <Popover.Popup style={popupStyle}>Popup</Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        </Popover.Root>,\n      );\n\n      expect(screen.getByTestId('positioner').getBoundingClientRect()).toMatchObject({\n        x: baselineX + alignOffset,\n        y: baselineY,\n      });\n    });\n\n    it('offsets the align when a function is specified', async () => {\n      await render(\n        <Popover.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Popover.Portal>\n            <Popover.Positioner\n              data-testid=\"positioner\"\n              alignOffset={(data) => data.positioner.width}\n            >\n              <Popover.Popup style={popupStyle}>Popup</Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        </Popover.Root>,\n      );\n\n      expect(screen.getByTestId('positioner').getBoundingClientRect()).toMatchObject({\n        x: baselineX + popupWidth,\n        y: baselineY,\n      });\n    });\n\n    it('can read the latest side inside alignOffset', async () => {\n      let side = 'none';\n      await render(\n        <Popover.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Popover.Portal>\n            <Popover.Positioner\n              side=\"left\"\n              data-testid=\"positioner\"\n              alignOffset={(data) => {\n                side = data.side;\n                return 0;\n              }}\n            >\n              <Popover.Popup style={popupStyle}>Popup</Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        </Popover.Root>,\n      );\n\n      // correctly flips the side in the browser\n      expect(side).toBe('right');\n    });\n\n    it('can read the latest align inside alignOffset', async () => {\n      let align = 'none';\n      await render(\n        <Popover.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Popover.Portal>\n            <Popover.Positioner\n              side=\"right\"\n              align=\"start\"\n              data-testid=\"positioner\"\n              alignOffset={(data) => {\n                align = data.align;\n                return 0;\n              }}\n            >\n              <Popover.Popup style={popupStyle}>Popup</Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        </Popover.Root>,\n      );\n\n      // correctly flips the align in the browser\n      expect(align).toBe('end');\n    });\n\n    it('reads logical side inside alignOffset', async () => {\n      let side = 'none';\n      await render(\n        <Popover.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Popover.Portal>\n            <Popover.Positioner\n              side=\"inline-start\"\n              data-testid=\"positioner\"\n              alignOffset={(data) => {\n                side = data.side;\n                return 0;\n              }}\n            >\n              <Popover.Popup style={popupStyle}>Popup</Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        </Popover.Root>,\n      );\n\n      // correctly flips the side in the browser\n      expect(side).toBe('inline-end');\n    });\n  });\n\n  it.skipIf(isJSDOM)('remains anchored if keepMounted=false', async () => {\n    function App({ top }: { top: number }) {\n      return (\n        <Popover.Root open>\n          <Trigger style={{ width: 100, height: 100, position: 'relative', top }}>Trigger</Trigger>\n          <Popover.Portal>\n            <Popover.Positioner data-testid=\"positioner\">\n              <Popover.Popup style={{ width: 100, height: 100 }}>Popup</Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        </Popover.Root>\n      );\n    }\n\n    const { setPropsAsync } = await render(<App top={0} />);\n    const positioner = screen.getByTestId('positioner');\n\n    const initial = { x: 5, y: 100 };\n    const final = { x: 5, y: 200 };\n\n    expect(positioner.getBoundingClientRect()).toMatchObject(initial);\n\n    await setPropsAsync({ top: 100 });\n\n    await waitFor(() => {\n      expect(positioner.getBoundingClientRect()).not.toMatchObject(initial);\n    });\n\n    expect(positioner.getBoundingClientRect()).toMatchObject(final);\n  });\n\n  it.skipIf(isJSDOM)('remains anchored if keepMounted=true', async () => {\n    function App({ top }: { top: number }) {\n      return (\n        <Popover.Root open>\n          <Trigger style={{ width: 100, height: 100, position: 'relative', top }}>Trigger</Trigger>\n          <Popover.Portal keepMounted>\n            <Popover.Positioner data-testid=\"positioner\">\n              <Popover.Popup style={{ width: 100, height: 100 }}>Popup</Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        </Popover.Root>\n      );\n    }\n\n    const { setPropsAsync } = await render(<App top={0} />);\n    const positioner = screen.getByTestId('positioner');\n\n    const initial = { x: 5, y: 100 };\n    const final = { x: 5, y: 200 };\n\n    expect(positioner.getBoundingClientRect()).toMatchObject(initial);\n\n    await setPropsAsync({ top: 100 });\n\n    await waitFor(() => {\n      expect(positioner.getBoundingClientRect()).not.toMatchObject(initial);\n    });\n\n    expect(positioner.getBoundingClientRect()).toMatchObject(final);\n  });\n});\n"
  },
  {
    "path": "packages/react/src/popover/positioner/PopoverPositioner.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { inertValue } from '@base-ui/utils/inertValue';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { FloatingNode, useFloatingNodeId } from '../../floating-ui-react';\nimport { usePopoverRootContext } from '../root/PopoverRootContext';\nimport { PopoverPositionerContext } from './PopoverPositionerContext';\nimport {\n  useAnchorPositioning,\n  type Side,\n  type Align,\n  type UseAnchorPositioningSharedParameters,\n} from '../../utils/useAnchorPositioning';\nimport type { BaseUIComponentProps, HTMLProps } from '../../utils/types';\nimport { popupStateMapping } from '../../utils/popupStateMapping';\nimport { usePopoverPortalContext } from '../portal/PopoverPortalContext';\nimport { InternalBackdrop } from '../../utils/InternalBackdrop';\nimport { REASONS } from '../../utils/reasons';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { POPUP_COLLISION_AVOIDANCE } from '../../utils/constants';\nimport { useAnimationsFinished } from '../../utils/useAnimationsFinished';\nimport { adaptiveOrigin } from '../../utils/adaptiveOriginMiddleware';\nimport { getDisabledMountTransitionStyles } from '../../utils/getDisabledMountTransitionStyles';\n\n/**\n * Positions the popover against the trigger.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Popover](https://base-ui.com/react/components/popover)\n */\nexport const PopoverPositioner = React.forwardRef(function PopoverPositioner(\n  componentProps: PopoverPositioner.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const {\n    render,\n    className,\n    anchor,\n    positionMethod = 'absolute',\n    side = 'bottom',\n    align = 'center',\n    sideOffset = 0,\n    alignOffset = 0,\n    collisionBoundary = 'clipping-ancestors',\n    collisionPadding = 5,\n    arrowPadding = 5,\n    sticky = false,\n    disableAnchorTracking = false,\n    collisionAvoidance = POPUP_COLLISION_AVOIDANCE,\n    ...elementProps\n  } = componentProps;\n\n  const { store } = usePopoverRootContext();\n  const keepMounted = usePopoverPortalContext();\n  const nodeId = useFloatingNodeId();\n\n  const floatingRootContext = store.useState('floatingRootContext');\n  const mounted = store.useState('mounted');\n  const open = store.useState('open');\n  const openReason = store.useState('openChangeReason');\n  const triggerElement = store.useState('activeTriggerElement');\n  const modal = store.useState('modal');\n  const positionerElement = store.useState('positionerElement');\n  const instantType = store.useState('instantType');\n  const transitionStatus = store.useState('transitionStatus');\n  const hasViewport = store.useState('hasViewport');\n\n  const prevTriggerElementRef = React.useRef<Element | null>(null);\n\n  const runOnceAnimationsFinish = useAnimationsFinished(positionerElement, false, false);\n\n  const positioning = useAnchorPositioning({\n    anchor,\n    floatingRootContext,\n    positionMethod,\n    mounted,\n    side,\n    sideOffset,\n    align,\n    alignOffset,\n    arrowPadding,\n    collisionBoundary,\n    collisionPadding,\n    sticky,\n    disableAnchorTracking,\n    keepMounted,\n    nodeId,\n    collisionAvoidance,\n    adaptiveOrigin: hasViewport ? adaptiveOrigin : undefined,\n  });\n\n  const defaultProps: HTMLProps = React.useMemo(() => {\n    const hiddenStyles: React.CSSProperties = {};\n\n    if (!open) {\n      hiddenStyles.pointerEvents = 'none';\n    }\n\n    return {\n      role: 'presentation',\n      hidden: !mounted,\n      style: {\n        ...positioning.positionerStyles,\n        ...hiddenStyles,\n      },\n    };\n  }, [open, mounted, positioning.positionerStyles]);\n\n  const positioner = React.useMemo(\n    () => ({\n      props: defaultProps,\n      ...positioning,\n    }),\n    [defaultProps, positioning],\n  );\n\n  const domReference = floatingRootContext.useState('domReferenceElement');\n\n  // When the current trigger element changes, enable transitions on the\n  // positioner temporarily\n  useIsoLayoutEffect(() => {\n    const currentTriggerElement = domReference;\n    const prevTriggerElement = prevTriggerElementRef.current;\n\n    if (currentTriggerElement) {\n      prevTriggerElementRef.current = currentTriggerElement;\n    }\n\n    if (\n      prevTriggerElement &&\n      currentTriggerElement &&\n      currentTriggerElement !== prevTriggerElement\n    ) {\n      store.set('instantType', undefined);\n      const ac = new AbortController();\n      runOnceAnimationsFinish(() => {\n        store.set('instantType', 'trigger-change');\n      }, ac.signal);\n\n      return () => {\n        ac.abort();\n      };\n    }\n\n    return undefined;\n  }, [domReference, runOnceAnimationsFinish, store]);\n\n  const state: PopoverPositionerState = {\n    open,\n    side: positioner.side,\n    align: positioner.align,\n    anchorHidden: positioner.anchorHidden,\n    instant: instantType,\n  };\n\n  const setPositionerElement = React.useCallback(\n    (element: HTMLElement | null) => {\n      store.set('positionerElement', element);\n    },\n    [store],\n  );\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    props: [positioner.props, getDisabledMountTransitionStyles(transitionStatus), elementProps],\n    ref: [forwardedRef, setPositionerElement],\n    stateAttributesMapping: popupStateMapping,\n  });\n\n  return (\n    <PopoverPositionerContext.Provider value={positioner}>\n      {mounted && modal === true && openReason !== REASONS.triggerHover && (\n        <InternalBackdrop\n          ref={store.context.internalBackdropRef}\n          inert={inertValue(!open)}\n          cutout={triggerElement}\n        />\n      )}\n      <FloatingNode id={nodeId}>{element}</FloatingNode>\n    </PopoverPositionerContext.Provider>\n  );\n});\n\nexport interface PopoverPositionerState {\n  /**\n   * Whether the popover is currently open.\n   */\n  open: boolean;\n  /**\n   * The side of the anchor the component is placed on.\n   */\n  side: Side;\n  /**\n   * The alignment of the component relative to the anchor.\n   */\n  align: Align;\n  /**\n   * Whether the anchor element is hidden.\n   */\n  anchorHidden: boolean;\n  /**\n   * Whether CSS transitions should be disabled.\n   */\n  instant: string | undefined;\n}\n\nexport interface PopoverPositionerProps\n  extends\n    UseAnchorPositioningSharedParameters,\n    BaseUIComponentProps<'div', PopoverPositionerState> {}\n\nexport namespace PopoverPositioner {\n  export type State = PopoverPositionerState;\n  export type Props = PopoverPositionerProps;\n}\n"
  },
  {
    "path": "packages/react/src/popover/positioner/PopoverPositionerContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport type { Side, Align } from '../../utils/useAnchorPositioning';\nimport type { FloatingContext } from '../../floating-ui-react';\n\nexport interface PopoverPositionerContext {\n  side: Side;\n  align: Align;\n  arrowRef: React.RefObject<Element | null>;\n  arrowUncentered: boolean;\n  arrowStyles: React.CSSProperties;\n  context: FloatingContext;\n}\n\nexport const PopoverPositionerContext = React.createContext<PopoverPositionerContext | undefined>(\n  undefined,\n);\n\nexport function usePopoverPositionerContext() {\n  const context = React.useContext(PopoverPositionerContext);\n  if (!context) {\n    throw new Error(\n      'Base UI: PopoverPositionerContext is missing. PopoverPositioner parts must be placed within <Popover.Positioner>.',\n    );\n  }\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/popover/positioner/PopoverPositionerCssVars.ts",
    "content": "export enum PopoverPositionerCssVars {\n  /**\n   * The available width between the trigger and the edge of the viewport.\n   * @type {number}\n   */\n  availableWidth = '--available-width',\n  /**\n   * The available height between the trigger and the edge of the viewport.\n   * @type {number}\n   */\n  availableHeight = '--available-height',\n  /**\n   * The anchor's width.\n   * @type {number}\n   */\n  anchorWidth = '--anchor-width',\n  /**\n   * The anchor's height.\n   * @type {number}\n   */\n  anchorHeight = '--anchor-height',\n  /**\n   * The coordinates that this element is anchored to. Used for animations and transitions.\n   * @type {string}\n   */\n  transformOrigin = '--transform-origin',\n  /**\n   * The width of the popover's positioner.\n   * It is important to set `width` to this value when using CSS to animate size changes.\n   */\n  positionerWidth = '--positioner-width',\n  /**\n   * The height of the popover's positioner.\n   * It is important to set `height` to this value when using CSS to animate size changes.\n   */\n  positionerHeight = '--positioner-height',\n}\n"
  },
  {
    "path": "packages/react/src/popover/positioner/PopoverPositionerDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum PopoverPositionerDataAttributes {\n  /**\n   * Present when the popup is open.\n   */\n  open = CommonPopupDataAttributes.open,\n  /**\n   * Present when the popup is closed.\n   */\n  closed = CommonPopupDataAttributes.closed,\n  /**\n   * Present when the anchor is hidden.\n   */\n  anchorHidden = CommonPopupDataAttributes.anchorHidden,\n  /**\n   * Indicates which side the popup is positioned relative to the trigger.\n   * @type {'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'}\n   */\n  side = CommonPopupDataAttributes.side,\n  /**\n   * Indicates how the popup is aligned relative to specified side.\n   * @type {'start' | 'center' | 'end'}\n   */\n  align = CommonPopupDataAttributes.align,\n}\n"
  },
  {
    "path": "packages/react/src/popover/root/PopoverRoot.detached-triggers.test.tsx",
    "content": "import { expect } from 'vitest';\nimport * as React from 'react';\nimport { createRenderer, isJSDOM } from '#test-utils';\nimport { act, screen, waitFor } from '@mui/internal-test-utils';\nimport { Popover } from '@base-ui/react/popover';\n\ndescribe('<Popover.Root />', () => {\n  beforeEach(() => {\n    globalThis.BASE_UI_ANIMATIONS_DISABLED = true;\n  });\n\n  const { render } = createRenderer();\n\n  describe.skipIf(isJSDOM)('multiple triggers within Root', () => {\n    type NumberPayload = { payload: number | undefined };\n\n    it('should open the popover with any trigger', async () => {\n      const { user } = await render(\n        <Popover.Root>\n          <Popover.Trigger>Trigger 1</Popover.Trigger>\n          <Popover.Trigger>Trigger 2</Popover.Trigger>\n          <Popover.Trigger>Trigger 3</Popover.Trigger>\n\n          <Popover.Portal>\n            <Popover.Positioner>\n              <Popover.Popup>\n                Popover Content\n                <Popover.Close>Close</Popover.Close>\n              </Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        </Popover.Root>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n      const trigger3 = screen.getByRole('button', { name: 'Trigger 3' });\n\n      expect(screen.queryByText('Popover Content')).toBe(null);\n\n      await user.click(trigger1);\n      expect(screen.getByText('Popover Content')).toBeVisible();\n      await user.click(screen.getByText('Close'));\n      expect(screen.queryByText('Popover Content')).toBe(null);\n\n      await user.click(trigger2);\n      expect(screen.getByText('Popover Content')).toBeVisible();\n      await user.click(screen.getByText('Close'));\n      expect(screen.queryByText('Popover Content')).toBe(null);\n\n      await user.click(trigger3);\n      expect(screen.getByText('Popover Content')).toBeVisible();\n      await user.click(screen.getByText('Close'));\n      expect(screen.queryByText('Popover Content')).toBe(null);\n    });\n\n    it('should open the popover with any trigger', async () => {\n      const { user } = await render(\n        <Popover.Root>\n          <Popover.Trigger>Trigger 1</Popover.Trigger>\n          <Popover.Trigger>Trigger 2</Popover.Trigger>\n          <Popover.Trigger>Trigger 3</Popover.Trigger>\n\n          <Popover.Portal>\n            <Popover.Positioner>\n              <Popover.Popup>\n                Popover Content\n                <Popover.Close>Close</Popover.Close>\n              </Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        </Popover.Root>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n      const trigger3 = screen.getByRole('button', { name: 'Trigger 3' });\n\n      expect(screen.queryByText('Popover Content')).toBe(null);\n\n      await user.click(trigger1);\n      expect(screen.getByText('Popover Content')).toBeVisible();\n      await user.click(screen.getByText('Close'));\n      expect(screen.queryByText('Popover Content')).toBe(null);\n\n      await user.click(trigger2);\n      expect(screen.getByText('Popover Content')).toBeVisible();\n      await user.click(screen.getByText('Close'));\n      expect(screen.queryByText('Popover Content')).toBe(null);\n\n      await user.click(trigger3);\n      expect(screen.getByText('Popover Content')).toBeVisible();\n      await user.click(screen.getByText('Close'));\n      expect(screen.queryByText('Popover Content')).toBe(null);\n    });\n\n    it('should set the payload and render content based on its value', async () => {\n      const { user } = await render(\n        <Popover.Root>\n          {({ payload }: NumberPayload) => (\n            <React.Fragment>\n              <Popover.Trigger payload={1}>Trigger 1</Popover.Trigger>\n              <Popover.Trigger payload={2}>Trigger 2</Popover.Trigger>\n\n              <Popover.Portal>\n                <Popover.Positioner>\n                  <Popover.Popup>\n                    <span data-testid=\"content\">{payload}</span>\n                  </Popover.Popup>\n                </Popover.Positioner>\n              </Popover.Portal>\n            </React.Fragment>\n          )}\n        </Popover.Root>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n\n      await user.click(trigger1);\n      expect(screen.getByTestId('content').textContent).toBe('1');\n\n      await user.click(trigger2);\n      expect(screen.getByTestId('content').textContent).toBe('2');\n    });\n\n    it('should reuse the popup and positioner DOM nodes when switching triggers', async () => {\n      const { user } = await render(\n        <Popover.Root>\n          {({ payload }: NumberPayload) => (\n            <React.Fragment>\n              <Popover.Trigger payload={1}>Trigger 1</Popover.Trigger>\n              <Popover.Trigger payload={2}>Trigger 2</Popover.Trigger>\n\n              <Popover.Portal>\n                <Popover.Positioner data-testid=\"positioner\">\n                  <Popover.Popup data-testid=\"popup\">\n                    <span>{payload}</span>\n                  </Popover.Popup>\n                </Popover.Positioner>\n              </Popover.Portal>\n            </React.Fragment>\n          )}\n        </Popover.Root>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n\n      await user.click(trigger1);\n      const popupElement = screen.getByTestId('popup');\n      const positionerElement = screen.getByTestId('positioner');\n\n      await user.click(trigger2);\n      expect(screen.getByTestId('popup')).toBe(popupElement);\n      expect(screen.getByTestId('positioner')).toBe(positionerElement);\n    });\n\n    it('should allow controlling the popover state programmatically', async () => {\n      function Test() {\n        const [open, setOpen] = React.useState(false);\n        const [activeTrigger, setActiveTrigger] = React.useState<string | null>(null);\n\n        return (\n          <div>\n            <Popover.Root\n              open={open}\n              triggerId={activeTrigger}\n              onOpenChange={(nextOpen, details) => {\n                setActiveTrigger(details.trigger?.id ?? null);\n                setOpen(nextOpen);\n              }}\n            >\n              {({ payload }: NumberPayload) => (\n                <React.Fragment>\n                  <Popover.Trigger payload={1} id=\"trigger-1\">\n                    Trigger 1\n                  </Popover.Trigger>\n                  <Popover.Trigger payload={2} id=\"trigger-2\">\n                    Trigger 2\n                  </Popover.Trigger>\n\n                  <Popover.Portal>\n                    <Popover.Positioner>\n                      <Popover.Popup>\n                        <span data-testid=\"content\">{payload as number}</span>\n                      </Popover.Popup>\n                    </Popover.Positioner>\n                  </Popover.Portal>\n                </React.Fragment>\n              )}\n            </Popover.Root>\n            <button\n              onClick={() => {\n                setOpen(true);\n                setActiveTrigger('trigger-1');\n              }}\n            >\n              Open Trigger 1\n            </button>\n            <button\n              onClick={() => {\n                setOpen(true);\n                setActiveTrigger('trigger-2');\n              }}\n            >\n              Open Trigger 2\n            </button>\n            <button onClick={() => setOpen(false)}>Close</button>\n          </div>\n        );\n      }\n\n      const { user } = await render(<Test />);\n      await user.click(screen.getByRole('button', { name: 'Open Trigger 1' }));\n      expect(screen.getByTestId('content').textContent).toBe('1');\n      await user.click(screen.getByRole('button', { name: 'Open Trigger 2' }));\n      expect(screen.getByTestId('content').textContent).toBe('2');\n      await user.click(screen.getByRole('button', { name: 'Close' }));\n      expect(screen.queryByTestId('content')).toBe(null);\n    });\n\n    it('allows setting an initially open popover', async () => {\n      const testPopover = Popover.createHandle<number>();\n      await render(\n        <Popover.Root handle={testPopover} defaultOpen defaultTriggerId=\"trigger-2\">\n          {({ payload }: NumberPayload) => (\n            <React.Fragment>\n              <Popover.Trigger handle={testPopover} payload={1} id=\"trigger-1\">\n                Trigger 1\n              </Popover.Trigger>\n              <Popover.Trigger handle={testPopover} payload={2} id=\"trigger-2\">\n                Trigger 2\n              </Popover.Trigger>\n              <Popover.Portal>\n                <Popover.Positioner>\n                  <Popover.Popup data-testid=\"popup\">\n                    <span>{payload}</span>\n                  </Popover.Popup>\n                </Popover.Positioner>\n              </Popover.Portal>\n            </React.Fragment>\n          )}\n        </Popover.Root>,\n      );\n\n      expect(screen.getByTestId('popup').textContent).toBe('2');\n    });\n  });\n\n  describe.skipIf(isJSDOM)('multiple detached triggers', () => {\n    type NumberPayload = { payload: number | undefined };\n\n    function TriggerWithNesting({\n      handle,\n      nesting,\n    }: {\n      handle: ReturnType<typeof Popover.createHandle>;\n      nesting: 0 | 1 | 2 | 3;\n    }) {\n      const trigger = (\n        <Popover.Trigger handle={handle} id=\"trigger\">\n          Trigger\n        </Popover.Trigger>\n      );\n\n      if (nesting === 0) {\n        return trigger;\n      }\n\n      if (nesting === 1) {\n        return <div>{trigger}</div>;\n      }\n\n      if (nesting === 2) {\n        return (\n          <div>\n            <div>{trigger}</div>\n          </div>\n        );\n      }\n\n      return (\n        <div>\n          <div>\n            <div>{trigger}</div>\n          </div>\n        </div>\n      );\n    }\n\n    function DetachedTriggerReparentingTest({\n      handle,\n      nesting,\n    }: {\n      handle: ReturnType<typeof Popover.createHandle>;\n      nesting: 0 | 1 | 2 | 3;\n    }) {\n      return (\n        <React.Fragment>\n          <TriggerWithNesting handle={handle} nesting={nesting} />\n          <Popover.Root handle={handle}>\n            <Popover.Portal>\n              <Popover.Positioner>\n                <Popover.Popup>\n                  Popover Content\n                  <Popover.Close>Close</Popover.Close>\n                </Popover.Popup>\n              </Popover.Positioner>\n            </Popover.Portal>\n          </Popover.Root>\n        </React.Fragment>\n      );\n    }\n\n    async function openAndClosePopover(user: any) {\n      await user.click(screen.getByRole('button', { name: 'Trigger' }));\n      await waitFor(() => {\n        expect(screen.getByText('Popover Content')).toBeVisible();\n      });\n      await user.click(screen.getByText('Close'));\n      await waitFor(() => {\n        expect(screen.queryByText('Popover Content')).toBe(null);\n      });\n    }\n\n    it('should open the popover with any trigger', async () => {\n      const testPopover = Popover.createHandle();\n      const { user } = await render(\n        <div>\n          <Popover.Trigger handle={testPopover}>Trigger 1</Popover.Trigger>\n          <Popover.Trigger handle={testPopover}>Trigger 2</Popover.Trigger>\n          <Popover.Trigger handle={testPopover}>Trigger 3</Popover.Trigger>\n\n          <Popover.Root handle={testPopover}>\n            <Popover.Portal>\n              <Popover.Positioner>\n                <Popover.Popup>\n                  Popover Content\n                  <Popover.Close>Close</Popover.Close>\n                </Popover.Popup>\n              </Popover.Positioner>\n            </Popover.Portal>\n          </Popover.Root>\n        </div>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n      const trigger3 = screen.getByRole('button', { name: 'Trigger 3' });\n\n      expect(screen.queryByText('Popover Content')).toBe(null);\n\n      await user.click(trigger1);\n      expect(screen.getByText('Popover Content')).toBeVisible();\n      await user.click(screen.getByText('Close'));\n      expect(screen.queryByText('Popover Content')).toBe(null);\n\n      await user.click(trigger2);\n      expect(screen.getByText('Popover Content')).toBeVisible();\n      await user.click(screen.getByText('Close'));\n      expect(screen.queryByText('Popover Content')).toBe(null);\n\n      await user.click(trigger3);\n      expect(screen.getByText('Popover Content')).toBeVisible();\n      await user.click(screen.getByText('Close'));\n      expect(screen.queryByText('Popover Content')).toBe(null);\n    });\n\n    it('should set the payload and render content based on its value', async () => {\n      const testPopover = Popover.createHandle<number>();\n      const { user } = await render(\n        <div>\n          <Popover.Trigger handle={testPopover} payload={1}>\n            Trigger 1\n          </Popover.Trigger>\n          <Popover.Trigger handle={testPopover} payload={2}>\n            Trigger 2\n          </Popover.Trigger>\n\n          <Popover.Root handle={testPopover}>\n            {({ payload }: NumberPayload) => (\n              <Popover.Portal>\n                <Popover.Positioner>\n                  <Popover.Popup>\n                    <span data-testid=\"content\">{payload}</span>\n                  </Popover.Popup>\n                </Popover.Positioner>\n              </Popover.Portal>\n            )}\n          </Popover.Root>\n        </div>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n\n      await user.click(trigger1);\n      expect(screen.getByTestId('content').textContent).toBe('1');\n\n      await user.click(trigger2);\n      expect(screen.getByTestId('content').textContent).toBe('2');\n    });\n\n    it('keeps detached triggers clickable when reparented (remove wrappers)', async () => {\n      const testPopover = Popover.createHandle();\n      const { user, setProps } = await render(\n        <DetachedTriggerReparentingTest handle={testPopover} nesting={3} />,\n      );\n\n      await openAndClosePopover(user);\n\n      await setProps({ nesting: 2 });\n      await openAndClosePopover(user);\n\n      await setProps({ nesting: 1 });\n      await openAndClosePopover(user);\n\n      await setProps({ nesting: 0 });\n      await openAndClosePopover(user);\n    });\n\n    it('keeps detached triggers clickable when reparented (add wrappers)', async () => {\n      const testPopover = Popover.createHandle();\n      const { user, setProps } = await render(\n        <DetachedTriggerReparentingTest handle={testPopover} nesting={0} />,\n      );\n\n      await openAndClosePopover(user);\n\n      await setProps({ nesting: 1 });\n      await openAndClosePopover(user);\n\n      await setProps({ nesting: 2 });\n      await openAndClosePopover(user);\n\n      await setProps({ nesting: 3 });\n      await openAndClosePopover(user);\n    });\n\n    it('keeps detached triggers clickable when reparented during Fast Refresh-like handle recreation', async () => {\n      const handleA = Popover.createHandle();\n      const { user, setProps } = await render(\n        <DetachedTriggerReparentingTest handle={handleA} nesting={3} />,\n      );\n\n      await openAndClosePopover(user);\n\n      await setProps({ handle: Popover.createHandle(), nesting: 2 });\n      await openAndClosePopover(user);\n\n      await setProps({ handle: Popover.createHandle(), nesting: 1 });\n      await openAndClosePopover(user);\n\n      await setProps({ handle: Popover.createHandle(), nesting: 0 });\n      await openAndClosePopover(user);\n    });\n\n    it('should reuse the popup and positioner DOM nodes when switching triggers', async () => {\n      const testPopover = Popover.createHandle<number>();\n      const { user } = await render(\n        <React.Fragment>\n          <Popover.Trigger handle={testPopover} payload={1}>\n            Trigger 1\n          </Popover.Trigger>\n          <Popover.Trigger handle={testPopover} payload={2}>\n            Trigger 2\n          </Popover.Trigger>\n\n          <Popover.Root handle={testPopover}>\n            {({ payload }: NumberPayload) => (\n              <Popover.Portal>\n                <Popover.Positioner data-testid=\"positioner\">\n                  <Popover.Popup data-testid=\"popup\">\n                    <span>{payload}</span>\n                  </Popover.Popup>\n                </Popover.Positioner>\n              </Popover.Portal>\n            )}\n          </Popover.Root>\n        </React.Fragment>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n\n      await user.click(trigger1);\n      const popupElement = screen.getByTestId('popup');\n      const positionerElement = screen.getByTestId('positioner');\n\n      await user.click(trigger2);\n      expect(screen.getByTestId('popup')).toBe(popupElement);\n      expect(screen.getByTestId('positioner')).toBe(positionerElement);\n    });\n\n    it('should allow controlling the popover state programmatically', async () => {\n      const testPopover = Popover.createHandle<number>();\n      function Test() {\n        const [open, setOpen] = React.useState(false);\n        const [activeTrigger, setActiveTrigger] = React.useState<string | null>(null);\n\n        return (\n          <div style={{ margin: 50 }}>\n            <Popover.Trigger handle={testPopover} payload={1} id=\"trigger-1\">\n              Trigger 1\n            </Popover.Trigger>\n            <Popover.Trigger handle={testPopover} payload={2} id=\"trigger-2\">\n              Trigger 2\n            </Popover.Trigger>\n\n            <Popover.Root\n              open={open}\n              onOpenChange={(nextOpen, details) => {\n                setActiveTrigger(details.trigger?.id ?? null);\n                setOpen(nextOpen);\n              }}\n              triggerId={activeTrigger}\n              handle={testPopover}\n            >\n              {({ payload }: NumberPayload) => (\n                <Popover.Portal>\n                  <Popover.Positioner data-testid=\"positioner\" side=\"bottom\" align=\"start\">\n                    <Popover.Popup>\n                      <span data-testid=\"content\">{payload}</span>\n                    </Popover.Popup>\n                  </Popover.Positioner>\n                </Popover.Portal>\n              )}\n            </Popover.Root>\n\n            <button\n              onClick={() => {\n                setOpen(true);\n                setActiveTrigger('trigger-1');\n              }}\n            >\n              Open Trigger 1\n            </button>\n            <button\n              onClick={() => {\n                setOpen(true);\n                setActiveTrigger('trigger-2');\n              }}\n            >\n              Open Trigger 2\n            </button>\n            <button onClick={() => setOpen(false)}>Close</button>\n          </div>\n        );\n      }\n\n      const { user } = await render(<Test />);\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n\n      await user.click(screen.getByRole('button', { name: 'Open Trigger 1' }));\n      expect(screen.getByTestId('content').textContent).toBe('1');\n\n      await waitFor(() => {\n        expect(\n          Math.abs(\n            screen.getByTestId('positioner').getBoundingClientRect().left -\n              trigger1.getBoundingClientRect().left,\n          ),\n        ).toBeLessThanOrEqual(1);\n      });\n\n      await user.click(screen.getByRole('button', { name: 'Open Trigger 2' }));\n      expect(screen.getByTestId('content').textContent).toBe('2');\n      await waitFor(() => {\n        expect(\n          Math.abs(\n            screen.getByTestId('positioner').getBoundingClientRect().left -\n              trigger2.getBoundingClientRect().left,\n          ),\n        ).toBeLessThanOrEqual(1);\n      });\n\n      await user.click(screen.getByRole('button', { name: 'Close' }));\n      expect(screen.queryByTestId('content')).toBe(null);\n    });\n\n    it('allows setting an initially open popover', async () => {\n      const testPopover = Popover.createHandle<number>();\n      await render(\n        <React.Fragment>\n          <Popover.Trigger handle={testPopover} payload={1} id=\"trigger-1\">\n            Trigger 1\n          </Popover.Trigger>\n          <Popover.Trigger handle={testPopover} payload={2} id=\"trigger-2\">\n            Trigger 2\n          </Popover.Trigger>\n\n          <Popover.Root handle={testPopover} defaultOpen defaultTriggerId=\"trigger-2\">\n            {({ payload }: NumberPayload) => (\n              <Popover.Portal>\n                <Popover.Positioner>\n                  <Popover.Popup data-testid=\"popup\">\n                    <span>{payload}</span>\n                  </Popover.Popup>\n                </Popover.Positioner>\n              </Popover.Portal>\n            )}\n          </Popover.Root>\n        </React.Fragment>,\n      );\n\n      expect(screen.getByTestId('popup').textContent).toBe('2');\n    });\n\n    it('should not have inline scale style after switching triggers', async () => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n      const testPopover = Popover.createHandle<number>();\n\n      function Test() {\n        return (\n          <React.Fragment>\n            <Popover.Trigger handle={testPopover} payload={1}>\n              Trigger 1\n            </Popover.Trigger>\n            <Popover.Trigger handle={testPopover} payload={2}>\n              Trigger 2\n            </Popover.Trigger>\n\n            <Popover.Root handle={testPopover}>\n              {({ payload }: NumberPayload) => (\n                <Popover.Portal>\n                  <Popover.Positioner>\n                    <Popover.Popup data-testid=\"popup\">\n                      <Popover.Viewport>\n                        <span data-testid=\"content\">{payload}</span>\n                      </Popover.Viewport>\n                    </Popover.Popup>\n                  </Popover.Positioner>\n                </Popover.Portal>\n              )}\n            </Popover.Root>\n          </React.Fragment>\n        );\n      }\n\n      const { user } = await render(<Test />);\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n\n      // Open with Trigger 1\n      await user.click(trigger1);\n      await waitFor(() => {\n        expect(screen.getByTestId('content').textContent).toBe('1');\n      });\n\n      // Switch to Trigger 2\n      await user.click(trigger2);\n      await waitFor(() => {\n        expect(screen.getByTestId('content').textContent).toBe('2');\n      });\n\n      // The popup should not have an inline scale style that would override CSS transitions\n      const popup = screen.getByTestId('popup');\n      expect(popup.style.scale).toBe('');\n    });\n\n    it('keeps positioning correct when conditional triggers unmount and the tree remounts', async () => {\n      const testPopover = Popover.createHandle();\n\n      function Test() {\n        const [key, setKey] = React.useState(1);\n        const [showErrorDemo, setShowErrorDemo] = React.useState(true);\n\n        return (\n          <React.Fragment key={key}>\n            <button\n              onClick={() => {\n                setShowErrorDemo((prev) => !prev);\n                setKey((prev) => prev + 1);\n              }}\n            >\n              Toggle\n            </button>\n            <div\n              style={{\n                display: 'flex',\n                flexDirection: 'column',\n                alignItems: 'flex-start',\n                gap: 48,\n                margin: 50,\n              }}\n            >\n              <Popover.Trigger handle={testPopover} id=\"trigger-0\">\n                Trigger 0\n              </Popover.Trigger>\n              {showErrorDemo && (\n                <Popover.Trigger handle={testPopover} id=\"trigger-1\">\n                  Trigger 1\n                </Popover.Trigger>\n              )}\n            </div>\n\n            <Popover.Root handle={testPopover} triggerId=\"trigger-0\" open>\n              <Popover.Portal>\n                <Popover.Positioner data-testid=\"positioner\" sideOffset={4} align=\"start\">\n                  <Popover.Popup>Content</Popover.Popup>\n                </Popover.Positioner>\n              </Popover.Portal>\n            </Popover.Root>\n          </React.Fragment>\n        );\n      }\n\n      const { user } = await render(<Test />);\n\n      const trigger0 = screen.getByRole('button', { name: 'Trigger 0' });\n      await waitFor(() => {\n        expect(\n          Math.abs(\n            screen.getByTestId('positioner').getBoundingClientRect().left -\n              trigger0.getBoundingClientRect().left,\n          ),\n        ).toBeLessThanOrEqual(1);\n      });\n\n      await user.click(screen.getByRole('button', { name: 'Toggle' }));\n      const trigger0After = screen.getByRole('button', { name: 'Trigger 0' });\n      await waitFor(() => {\n        expect(\n          Math.abs(\n            screen.getByTestId('positioner').getBoundingClientRect().left -\n              trigger0After.getBoundingClientRect().left,\n          ),\n        ).toBeLessThanOrEqual(1);\n      });\n    });\n  });\n\n  describe.skipIf(isJSDOM)('imperative actions on the handle', () => {\n    it('opens and closes the dialog', async () => {\n      const popover = Popover.createHandle();\n      await render(\n        <div>\n          <Popover.Trigger handle={popover} id=\"trigger\">\n            Trigger\n          </Popover.Trigger>\n          <Popover.Root handle={popover}>\n            <Popover.Portal>\n              <Popover.Positioner>\n                <Popover.Popup data-testid=\"content\">Content</Popover.Popup>\n              </Popover.Positioner>\n            </Popover.Portal>\n          </Popover.Root>\n        </div>,\n      );\n\n      const trigger = screen.getByRole('button', { name: 'Trigger' });\n      expect(screen.queryByRole('dialog')).toBe(null);\n\n      await act(() => popover.open('trigger'));\n      await waitFor(() => {\n        expect(screen.queryByRole('dialog')).not.toBe(null);\n      });\n\n      expect(screen.getByTestId('content').textContent).toBe('Content');\n      expect(trigger).toHaveAttribute('aria-expanded', 'true');\n\n      await act(() => popover.close());\n      await waitFor(() => {\n        expect(screen.queryByRole('dialog')).toBe(null);\n      });\n\n      expect(trigger).toHaveAttribute('aria-expanded', 'false');\n    });\n\n    it('sets the payload assosiated with the trigger', async () => {\n      const popover = Popover.createHandle<number>();\n      await render(\n        <div>\n          <Popover.Trigger handle={popover} id=\"trigger1\" payload={1}>\n            Trigger 1\n          </Popover.Trigger>\n          <Popover.Trigger handle={popover} id=\"trigger2\" payload={2}>\n            Trigger 2\n          </Popover.Trigger>\n          <Popover.Root handle={popover}>\n            {({ payload }: { payload: number | undefined }) => (\n              <Popover.Portal>\n                <Popover.Positioner>\n                  <Popover.Popup data-testid=\"content\">{payload}</Popover.Popup>\n                </Popover.Positioner>\n              </Popover.Portal>\n            )}\n          </Popover.Root>\n        </div>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n      expect(screen.queryByRole('dialog')).toBe(null);\n\n      await act(() => popover.open('trigger2'));\n      await waitFor(() => {\n        expect(screen.queryByRole('dialog')).not.toBe(null);\n      });\n\n      expect(screen.getByTestId('content').textContent).toBe('2');\n      expect(trigger2).toHaveAttribute('aria-expanded', 'true');\n      expect(trigger1).not.toHaveAttribute('aria-expanded', 'true');\n\n      await act(() => popover.close());\n      await waitFor(() => {\n        expect(screen.queryByRole('dialog')).toBe(null);\n      });\n\n      expect(trigger2).toHaveAttribute('aria-expanded', 'false');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/popover/root/PopoverRoot.spec.tsx",
    "content": "import { expectType } from '#test-utils';\nimport { Popover } from '@base-ui/react/popover';\n\nconst numberPayloadHandle = Popover.createHandle<number>();\n\nconst rootWithDirectChildren = (\n  <Popover.Root handle={numberPayloadHandle}>\n    <Popover.Portal />\n  </Popover.Root>\n);\n\nconst rootWithFunctionChildren = (\n  <Popover.Root handle={numberPayloadHandle}>\n    {({ payload }) => {\n      expectType<number | undefined, typeof payload>(payload);\n      return null;\n    }}\n  </Popover.Root>\n);\n\nconst triggerWithPayload = <Popover.Trigger handle={numberPayloadHandle} payload={42} />;\nconst triggerWithoutPayload = <Popover.Trigger handle={numberPayloadHandle} />;\n\nconst triggerWithInvalidPayload = (\n  // @ts-expect-error\n  <Popover.Trigger handle={numberPayloadHandle} payload={'invalid'} />\n);\n"
  },
  {
    "path": "packages/react/src/popover/root/PopoverRoot.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { Popover } from '@base-ui/react/popover';\nimport { Combobox } from '@base-ui/react/combobox';\nimport { Menu } from '@base-ui/react/menu';\nimport { useRefWithInit } from '@base-ui/utils/useRefWithInit';\nimport { act, fireEvent, flushMicrotasks, screen, waitFor } from '@mui/internal-test-utils';\nimport { createRenderer, isJSDOM, popupConformanceTests, wait } from '#test-utils';\nimport { OPEN_DELAY } from '../utils/constants';\nimport { PATIENT_CLICK_THRESHOLD } from '../../utils/constants';\nimport { REASONS } from '../../utils/reasons';\n\ndescribe('<Popover.Root />', () => {\n  beforeEach(() => {\n    globalThis.BASE_UI_ANIMATIONS_DISABLED = true;\n  });\n\n  const { render, clock } = createRenderer();\n\n  popupConformanceTests({\n    createComponent: (props) => (\n      <Popover.Root {...props.root}>\n        <Popover.Trigger {...props.trigger}>Open menu</Popover.Trigger>\n        <Popover.Portal {...props.portal}>\n          <Popover.Positioner>\n            <Popover.Popup {...props.popup}>Content</Popover.Popup>\n          </Popover.Positioner>\n        </Popover.Portal>\n      </Popover.Root>\n    ),\n    render,\n    triggerMouseAction: 'click',\n    expectedPopupRole: 'dialog',\n  });\n\n  describe.for([\n    { name: 'contained triggers', Component: ContainedTriggerPopover },\n    { name: 'detached triggers', Component: DetachedTriggerPopover },\n    { name: 'multiple detached triggers', Component: MultipleDetachedTriggersPopover },\n  ])('when using $name', ({ Component: TestPopover }) => {\n    it('should render the children', async () => {\n      await render(<TestPopover />);\n\n      expect(screen.getByText('Toggle')).not.toBe(null);\n    });\n\n    describe('uncontrolled open', () => {\n      it('should close when the anchor is clicked twice', async () => {\n        await render(<TestPopover />);\n\n        const anchor = screen.getByRole('button', { name: 'Toggle' });\n\n        fireEvent.click(anchor);\n\n        await flushMicrotasks();\n\n        expect(screen.getByText('Content')).not.toBe(null);\n\n        fireEvent.click(anchor);\n\n        expect(screen.queryByText('Content')).toBe(null);\n      });\n    });\n\n    describe('controlled open', () => {\n      it('should call onChange when the open state changes', async () => {\n        const handleChange = vi.fn();\n\n        function App() {\n          const [open, setOpen] = React.useState(false);\n\n          return (\n            <TestPopover\n              rootProps={{\n                open,\n                onOpenChange: (nextOpen) => {\n                  handleChange(open);\n                  setOpen(nextOpen);\n                },\n              }}\n            />\n          );\n        }\n\n        await render(<App />);\n\n        expect(screen.queryByText('Content')).toBe(null);\n\n        const anchor = screen.getByRole('button', { name: 'Toggle' });\n\n        fireEvent.click(anchor);\n\n        await flushMicrotasks();\n\n        expect(screen.getByText('Content')).not.toBe(null);\n\n        fireEvent.click(anchor);\n\n        expect(screen.queryByText('Content')).toBe(null);\n        expect(handleChange.mock.calls.length).toBe(2);\n        expect(handleChange.mock.calls[0][0]).toBe(false);\n        expect(handleChange.mock.calls[1][0]).toBe(true);\n      });\n    });\n\n    describe('nested menu interactions', () => {\n      it('keeps the popover open when a nested menu opens via Enter using a shared container', async () => {\n        vi.spyOn(console, 'error').mockImplementation((...args) => {\n          if (args[0] === 'null') {\n            // a bug in vitest prints specific browser errors as \"null\"\n            // See https://github.com/vitest-dev/vitest/issues/9285\n            // TODO(@mui/base): debug why this test triggers \"ResizeObserver loop completed with undelivered notifications\"\n            // It seems related to @testing-library/user-event. Native vitest `userEvent` does not trigger it.\n            return;\n          }\n          console.error(...args);\n        });\n\n        function Test() {\n          const [dialogNode, setDialogNode] = React.useState<HTMLDialogElement | null>(null);\n          const handleDialogRef = React.useCallback((node: HTMLDialogElement | null) => {\n            if (node) {\n              setDialogNode(node);\n            }\n          }, []);\n\n          return (\n            <dialog open ref={handleDialogRef}>\n              <TestPopover\n                portalProps={{ container: dialogNode ?? undefined }}\n                popupProps={{\n                  children: (\n                    <Menu.Root>\n                      <Menu.Trigger>Open nested</Menu.Trigger>\n                      <Menu.Portal container={dialogNode ?? undefined}>\n                        <Menu.Positioner>\n                          <Menu.Popup data-testid=\"menu-popup\">Nested Menu</Menu.Popup>\n                        </Menu.Positioner>\n                      </Menu.Portal>\n                    </Menu.Root>\n                  ),\n                }}\n              />\n            </dialog>\n          );\n        }\n\n        const { user } = await render(<Test />);\n\n        const popoverTrigger = screen.getByRole('button', { name: 'Toggle' });\n\n        await act(async () => {\n          popoverTrigger.focus();\n        });\n\n        await user.keyboard('{Enter}');\n        await screen.findByTestId('popover-popup');\n\n        const nestedTrigger = await screen.findByRole('button', { name: 'Open nested' });\n\n        await act(async () => {\n          nestedTrigger.focus();\n        });\n\n        await user.keyboard('{Enter}');\n        await screen.findByTestId('menu-popup');\n\n        expect(screen.getByTestId('popover-popup')).not.toBe(null);\n      });\n\n      it('keeps the popover open when a nested menu opens via pointer using a shared container', async () => {\n        vi.spyOn(console, 'error').mockImplementation((...args) => {\n          if (args[0] === 'null') {\n            // a bug in vitest prints specific browser errors as \"null\"\n            // See https://github.com/vitest-dev/vitest/issues/9285\n            // TODO(@mui/base): debug why this test triggers \"ResizeObserver loop completed with undelivered notifications\"\n            // It seems related to @testing-library/user-event. Native vitest `userEvent` does not trigger it.\n            return;\n          }\n          console.error(...args);\n        });\n\n        function Test() {\n          const [dialogNode, setDialogNode] = React.useState<HTMLDialogElement | null>(null);\n          const handleDialogRef = React.useCallback((node: HTMLDialogElement | null) => {\n            if (node) {\n              setDialogNode(node);\n            }\n          }, []);\n\n          return (\n            <dialog open ref={handleDialogRef}>\n              <TestPopover\n                portalProps={{ container: dialogNode ?? undefined }}\n                popupProps={{\n                  children: (\n                    <Menu.Root>\n                      <Menu.Trigger>Open nested</Menu.Trigger>\n                      <Menu.Portal container={dialogNode ?? undefined}>\n                        <Menu.Positioner>\n                          <Menu.Popup data-testid=\"menu-popup\">\n                            <Menu.Item closeOnClick={false}>Item</Menu.Item>\n                          </Menu.Popup>\n                        </Menu.Positioner>\n                      </Menu.Portal>\n                    </Menu.Root>\n                  ),\n                }}\n              />\n            </dialog>\n          );\n        }\n\n        const { user } = await render(<Test />);\n\n        const popoverTrigger = screen.getByRole('button', { name: 'Toggle' });\n        await user.click(popoverTrigger);\n        await screen.findByTestId('popover-popup');\n\n        const nestedTrigger = await screen.findByRole('button', { name: 'Open nested' });\n        await user.click(nestedTrigger);\n        await screen.findByTestId('menu-popup');\n\n        const item = await screen.findByText('Item');\n        await user.click(item);\n\n        await waitFor(() => {\n          expect(screen.getByTestId('popover-popup')).not.toBe(null);\n        });\n      });\n    });\n\n    describe('prop: defaultOpen', () => {\n      it('should open when the component is rendered', async () => {\n        await render(<TestPopover rootProps={{ defaultOpen: true }} />);\n\n        expect(screen.getByText('Content')).not.toBe(null);\n      });\n\n      it('should not open when the component is rendered and open is controlled', async () => {\n        await render(<TestPopover rootProps={{ defaultOpen: true, open: false }} />);\n\n        expect(screen.queryByText('Content')).toBe(null);\n      });\n\n      it('should not close when the component is rendered and open is controlled', async () => {\n        await render(<TestPopover rootProps={{ defaultOpen: true, open: true }} />);\n\n        expect(screen.getByText('Content')).not.toBe(null);\n      });\n\n      it('should remain uncontrolled', async () => {\n        await render(<TestPopover rootProps={{ defaultOpen: true }} />);\n\n        expect(screen.getByText('Content')).not.toBe(null);\n\n        const anchor = screen.getByTestId('trigger');\n\n        fireEvent.click(anchor);\n\n        expect(screen.queryByText('Content')).toBe(null);\n      });\n    });\n\n    describe('prop: delay', () => {\n      clock.withFakeTimers();\n\n      it('should open after delay with rest type by default', async () => {\n        await render(<TestPopover triggerProps={{ openOnHover: true, delay: 100 }} />);\n\n        const anchor = screen.getByRole('button', { name: 'Toggle' });\n\n        fireEvent.mouseEnter(anchor);\n        fireEvent.mouseMove(anchor);\n\n        await flushMicrotasks();\n\n        expect(screen.queryByText('Content')).toBe(null);\n\n        clock.tick(100);\n\n        await flushMicrotasks();\n\n        expect(screen.getByText('Content')).not.toBe(null);\n      });\n    });\n\n    describe('prop: closeDelay', () => {\n      clock.withFakeTimers();\n\n      it('should close after delay', async () => {\n        await render(<TestPopover triggerProps={{ openOnHover: true, closeDelay: 100 }} />);\n\n        const anchor = screen.getByRole('button', { name: 'Toggle' });\n\n        fireEvent.mouseEnter(anchor);\n        fireEvent.mouseMove(anchor);\n\n        clock.tick(OPEN_DELAY);\n\n        await flushMicrotasks();\n\n        expect(screen.getByText('Content')).not.toBe(null);\n\n        fireEvent.mouseLeave(anchor);\n\n        clock.tick(50);\n\n        expect(screen.getByText('Content')).not.toBe(null);\n\n        clock.tick(50);\n\n        expect(screen.queryByText('Content')).toBe(null);\n      });\n    });\n\n    describe('BaseUIChangeEventDetails', () => {\n      it('onOpenChange cancel() prevents opening while uncontrolled', async () => {\n        await render(\n          <TestPopover\n            rootProps={{\n              onOpenChange: (nextOpen, eventDetails) => {\n                if (nextOpen) {\n                  eventDetails.cancel();\n                }\n              },\n            }}\n          />,\n        );\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n        fireEvent.click(trigger);\n        await flushMicrotasks();\n\n        expect(screen.queryByText('Content')).toBe(null);\n      });\n    });\n\n    describe('focus management', () => {\n      it('focuses the trigger after the popover is closed but not unmounted', async () => {\n        const { user } = await render(\n          <div>\n            <input type=\"text\" />\n            <TestPopover\n              portalProps={{ keepMounted: true }}\n              popupProps={{ children: <Popover.Close>Close</Popover.Close> }}\n            />\n            <input type=\"text\" />\n          </div>,\n        );\n\n        const toggle = screen.getByRole('button', { name: 'Toggle' });\n\n        await user.click(toggle);\n        await flushMicrotasks();\n\n        const close = screen.getByRole('button', { name: 'Close' });\n\n        await user.click(close);\n\n        await waitFor(\n          () => {\n            expect(toggle).toHaveFocus();\n          },\n          { timeout: 1500 },\n        );\n      });\n\n      it('does not move focus to the popover when opened with hover', async () => {\n        const { user } = await render(\n          <TestPopover\n            triggerProps={{ openOnHover: true, delay: 0 }}\n            popupProps={{ children: <Popover.Close>Close</Popover.Close> }}\n          />,\n        );\n\n        const toggle = screen.getByRole('button', { name: 'Toggle' });\n\n        act(() => toggle.focus());\n\n        await user.hover(toggle);\n        await flushMicrotasks();\n\n        const close = screen.getByRole('button', { name: 'Close' });\n\n        expect(close).not.toBe(null);\n        expect(close).not.to.toHaveFocus();\n      });\n\n      it('does not change focus when opened with hover and closed', async () => {\n        const style = `\n        .popup {\n          width: 100px;\n          height: 100px;\n          background-color: red;\n          opacity: 1;\n          transition: opacity 1ms;\n        }\n\n        .popup[data-exiting] {\n          opacity: 0;\n        }\n      `;\n\n        const { user } = await render(\n          <div>\n            {/* eslint-disable-next-line react/no-danger */}\n            <style dangerouslySetInnerHTML={{ __html: style }} />\n            <input type=\"text\" data-testid=\"first-input\" />\n            <TestPopover\n              triggerProps={{ openOnHover: true, delay: 0, closeDelay: 0 }}\n              popupProps={{ className: 'popup', children: null }}\n            />\n            <input type=\"text\" data-testid=\"last-input\" />\n          </div>,\n        );\n\n        const toggle = screen.getByRole('button', { name: 'Toggle' });\n        const firstInput = screen.getByTestId('first-input');\n        const lastInput = screen.getByTestId('last-input');\n\n        await act(async () => lastInput.focus());\n\n        await user.hover(toggle);\n        await flushMicrotasks();\n\n        await user.hover(firstInput);\n        await flushMicrotasks();\n\n        await waitFor(() => {\n          expect(screen.queryByRole('dialog')).toBe(null);\n        });\n\n        expect(lastInput).toHaveFocus();\n      });\n\n      describe('with the popup following immediately the only trigger', () => {\n        it('moves focus to the element following the trigger, excluding the popup, when tabbing forward from the open popup', async () => {\n          const { user } = await render(\n            <div>\n              <input />\n              <TestPopover\n                rootProps={{ defaultOpen: true }}\n                popupProps={{ children: <input data-testid=\"input-inside\" /> }}\n                afterTrigger={<input data-testid=\"focus-target\" />}\n              />\n              <input />\n            </div>,\n          );\n\n          const inputInside = screen.getByTestId('input-inside');\n          await act(async () => inputInside.focus());\n\n          await user.tab();\n\n          expect(screen.getByTestId('focus-target')).toHaveFocus();\n\n          await waitFor(() => {\n            expect(screen.queryByTestId('popover-popup')).toBe(null);\n          });\n        });\n\n        it('closes a nested combobox popup when tabbing out of the popover', async () => {\n          const { user } = await render(\n            <div>\n              <TestPopover\n                rootProps={{ defaultOpen: true }}\n                portalProps={{ keepMounted: true }}\n                popupProps={{\n                  children: (\n                    <Combobox.Root items={['a', 'b']}>\n                      <Combobox.Input data-testid=\"combobox-input\" />\n                      <Combobox.Portal>\n                        <Combobox.Positioner>\n                          <Combobox.Popup>\n                            <Combobox.List>\n                              <Combobox.Item value=\"a\">a</Combobox.Item>\n                              <Combobox.Item value=\"b\">b</Combobox.Item>\n                            </Combobox.List>\n                          </Combobox.Popup>\n                        </Combobox.Positioner>\n                      </Combobox.Portal>\n                    </Combobox.Root>\n                  ),\n                }}\n                afterTrigger={<input data-testid=\"focus-target\" />}\n              />\n            </div>,\n          );\n\n          const comboboxInput = screen.getByTestId('combobox-input');\n          await user.click(comboboxInput);\n          await flushMicrotasks();\n\n          expect(screen.getByRole('listbox')).toBeVisible();\n\n          await user.tab();\n\n          expect(screen.getByTestId('focus-target')).toHaveFocus();\n\n          await waitFor(() => {\n            expect(screen.getByTestId('popover-popup')).toHaveAttribute('data-closed');\n          });\n\n          await waitFor(() => {\n            expect(screen.queryByRole('listbox')).toBe(null);\n          });\n        });\n\n        it('closes a nested combobox popup when tabbing backward to the trigger', async () => {\n          const { user } = await render(\n            <div>\n              <TestPopover\n                rootProps={{ defaultOpen: true }}\n                portalProps={{ keepMounted: true }}\n                popupProps={{\n                  children: (\n                    <Combobox.Root items={['a', 'b']}>\n                      <Combobox.Input data-testid=\"combobox-input\" />\n                      <Combobox.Portal>\n                        <Combobox.Positioner>\n                          <Combobox.Popup>\n                            <Combobox.List>\n                              <Combobox.Item value=\"a\">a</Combobox.Item>\n                              <Combobox.Item value=\"b\">b</Combobox.Item>\n                            </Combobox.List>\n                          </Combobox.Popup>\n                        </Combobox.Positioner>\n                      </Combobox.Portal>\n                    </Combobox.Root>\n                  ),\n                }}\n              />\n            </div>,\n          );\n\n          const comboboxInput = screen.getByTestId('combobox-input');\n          await user.click(comboboxInput);\n          await flushMicrotasks();\n\n          expect(screen.getByRole('listbox')).toBeVisible();\n\n          const trigger = screen.getByTestId('trigger');\n          expect(trigger).not.toHaveAttribute('aria-hidden', 'true');\n\n          await user.tab({ shift: true });\n\n          expect(trigger).toHaveFocus();\n\n          await waitFor(() => {\n            expect(screen.queryByRole('listbox')).toBe(null);\n          });\n        });\n\n        it.skipIf(isJSDOM)(\n          'moves focus to the trigger when tabbing backward from the open popup then to the popup when tabbing forward',\n          async () => {\n            const { user } = await render(\n              <div>\n                <input />\n                <TestPopover\n                  rootProps={{ defaultOpen: true }}\n                  popupProps={{ children: <input data-testid=\"input-inside\" /> }}\n                />\n                <input />\n              </div>,\n            );\n\n            const inputInside = screen.getByTestId('input-inside');\n            await act(async () => inputInside.focus());\n\n            await wait(50);\n            await user.tab({ shift: true });\n\n            await waitFor(() => {\n              expect(screen.getByRole('button', { name: 'Toggle' })).toHaveFocus();\n            });\n\n            await waitFor(() => {\n              expect(screen.queryByTestId('popover-popup')).toBeVisible();\n            });\n\n            await wait(50);\n            await user.keyboard('{Tab}');\n            await waitFor(() => {\n              expect(screen.getByTestId('input-inside')).toHaveFocus();\n            });\n          },\n        );\n      });\n\n      describe('with focusable elements between the trigger and the popup', () => {\n        it('moves focus to the element following the trigger when tabbing forward from the open popup', async () => {\n          const { user } = await render(\n            <div>\n              <input />\n              <TestPopover\n                rootProps={{ defaultOpen: true }}\n                afterTrigger={<input data-testid=\"focus-target\" />}\n                popupProps={{ children: <input data-testid=\"input-inside\" /> }}\n              />\n              <input />\n            </div>,\n          );\n\n          const inputInside = screen.getByTestId('input-inside');\n          await act(async () => inputInside.focus());\n\n          await user.tab();\n\n          await waitFor(() => {\n            expect(screen.getByTestId('focus-target')).toHaveFocus();\n          });\n\n          await waitFor(() => {\n            expect(screen.queryByTestId('popover-popup')).toBe(null);\n          });\n        });\n\n        it.skipIf(isJSDOM)(\n          'moves focus to the trigger when tabbing backward from the open popup then to the popup when tabbing forward',\n          async () => {\n            const { user } = await render(\n              <div>\n                <input />\n                <TestPopover\n                  rootProps={{ defaultOpen: true }}\n                  afterTrigger={<input />}\n                  popupProps={{ children: <input data-testid=\"input-inside\" /> }}\n                />\n                <input />\n              </div>,\n            );\n\n            await waitFor(() => {\n              expect(screen.getByTestId('input-inside')).toHaveFocus();\n            });\n\n            await user.tab({ shift: true });\n\n            await waitFor(() => {\n              expect(screen.getByRole('button', { name: 'Toggle' })).toHaveFocus();\n            });\n\n            await waitFor(() => {\n              expect(screen.queryByTestId('popover-popup')).toBeVisible();\n            });\n\n            await wait(50);\n            await user.tab();\n            await wait(50);\n            await waitFor(() => {\n              expect(screen.getByTestId('input-inside')).toHaveFocus();\n            });\n          },\n        );\n      });\n\n      describe('with the popup preceding immediately the only trigger', () => {\n        it('moves focus to the element following the trigger, excluding the popup, when tabbing forward from the open popup', async () => {\n          const { user } = await render(\n            <div>\n              <input />\n              <TestPopover\n                rootProps={{ defaultOpen: true }}\n                triggerPlacement=\"after-content\"\n                popupProps={{ children: <input data-testid=\"input-inside\" /> }}\n                afterTrigger={<input data-testid=\"focus-target\" />}\n              />\n              <input />\n            </div>,\n          );\n\n          const inputInside = screen.getByTestId('input-inside');\n          await act(async () => inputInside.focus());\n\n          await user.tab();\n\n          expect(screen.getByTestId('focus-target')).toHaveFocus();\n\n          await waitFor(() => {\n            expect(screen.queryByTestId('popover-popup')).toBe(null);\n          });\n        });\n\n        it.skipIf(isJSDOM)(\n          'moves focus to the trigger when tabbing backward from the open popup then to the popup when tabbing forward',\n          async () => {\n            const { user } = await render(\n              <div>\n                <input />\n                <TestPopover\n                  rootProps={{ defaultOpen: true }}\n                  triggerPlacement=\"after-content\"\n                  popupProps={{ children: <input data-testid=\"input-inside\" /> }}\n                />\n                <input />\n              </div>,\n            );\n\n            const inputInside = screen.getByTestId('input-inside');\n            await act(async () => inputInside.focus());\n\n            await wait(50);\n            await user.tab({ shift: true });\n\n            await waitFor(() => {\n              expect(screen.getByRole('button', { name: 'Toggle' })).toHaveFocus();\n            });\n\n            await waitFor(() => {\n              expect(screen.queryByTestId('popover-popup')).toBeVisible();\n            });\n\n            await wait(50);\n            await user.keyboard('{Tab}');\n\n            await waitFor(() => {\n              expect(screen.getByTestId('input-inside')).toHaveFocus();\n            });\n          },\n        );\n      });\n    });\n\n    describe('outside press event with backdrops', () => {\n      it('uses intentional outside press with user backdrop (mouse): closes on click, not on mousedown', async () => {\n        const handleOpenChange = vi.fn();\n\n        await render(\n          <TestPopover\n            rootProps={{ defaultOpen: true, onOpenChange: handleOpenChange }}\n            portalProps={{ children: <Popover.Backdrop data-testid=\"backdrop\" /> }}\n          />,\n        );\n\n        const backdrop = screen.getByTestId('backdrop');\n\n        fireEvent.mouseDown(backdrop);\n        expect(screen.queryByRole('dialog')).not.toBe(null);\n        expect(handleOpenChange.mock.calls.length).toBe(0);\n\n        fireEvent.click(backdrop);\n        await waitFor(() => {\n          expect(screen.queryByRole('dialog')).toBe(null);\n        });\n        expect(handleOpenChange.mock.calls.length).toBe(1);\n      });\n\n      it('uses intentional outside press with internal backdrop (modal=true): closes on click, not on mousedown', async () => {\n        const handleOpenChange = vi.fn();\n\n        await render(\n          <TestPopover\n            rootProps={{ defaultOpen: true, onOpenChange: handleOpenChange, modal: true }}\n          />,\n        );\n\n        const internalBackdrop = document.querySelector('[role=\"presentation\"]') as HTMLElement;\n\n        fireEvent.mouseDown(internalBackdrop);\n        expect(screen.queryByRole('dialog')).not.toBe(null);\n        expect(handleOpenChange.mock.calls.length).toBe(0);\n\n        fireEvent.click(internalBackdrop);\n        await waitFor(() => {\n          expect(screen.queryByRole('dialog')).toBe(null);\n        });\n        expect(handleOpenChange.mock.calls.length).toBe(1);\n      });\n\n      it('closing via outside press: works when clicking another element inside the same shadow root', async () => {\n        const handleOpenChange = vi.fn();\n\n        const host = document.body.appendChild(document.createElement('div'));\n        const shadowRoot = host.attachShadow({ mode: 'open' });\n        const container = document.createElement('div');\n        shadowRoot.appendChild(container);\n\n        try {\n          await render(\n            <React.Fragment>\n              <button data-testid=\"outside\">Outside</button>\n              <TestPopover\n                rootProps={{ defaultOpen: true, onOpenChange: handleOpenChange }}\n                portalProps={{ container: shadowRoot }}\n              />\n            </React.Fragment>,\n            { container },\n          );\n\n          const outsideButton = shadowRoot.querySelector('[data-testid=\"outside\"]') as HTMLElement;\n\n          fireEvent.click(outsideButton);\n\n          await waitFor(() => {\n            expect(shadowRoot.querySelector('[role=\"dialog\"]')).toBe(null);\n          });\n\n          expect(handleOpenChange.mock.calls.length).toBe(1);\n          expect(handleOpenChange.mock.calls[0][1].reason).toBe(REASONS.outsidePress);\n        } finally {\n          await act(async () => {\n            host.remove();\n          });\n        }\n      });\n\n      it('closing via outside press: works when clicking outside the shadow root', async () => {\n        const handleOpenChange = vi.fn();\n\n        const host = document.body.appendChild(document.createElement('div'));\n        const shadowRoot = host.attachShadow({ mode: 'open' });\n        const container = document.createElement('div');\n        shadowRoot.appendChild(container);\n\n        try {\n          await render(\n            <TestPopover\n              rootProps={{ defaultOpen: true, onOpenChange: handleOpenChange }}\n              portalProps={{ container: shadowRoot }}\n            />,\n            { container },\n          );\n\n          fireEvent.click(document.body);\n\n          await waitFor(() => {\n            expect(shadowRoot.querySelector('[role=\"dialog\"]')).toBe(null);\n          });\n\n          expect(handleOpenChange.mock.calls.length).toBe(1);\n          expect(handleOpenChange.mock.calls[0][1].reason).toBe(REASONS.outsidePress);\n        } finally {\n          await act(async () => {\n            host.remove();\n          });\n        }\n      });\n    });\n\n    describe('non-modal focus transitions', () => {\n      it('closes as soon as focus leaves the popup on pointer down outside', async () => {\n        function TestCase() {\n          return (\n            <React.Fragment>\n              <TestPopover\n                rootProps={{ defaultOpen: true }}\n                popupProps={{ children: <button data-testid=\"inside\">Inside</button> }}\n              />\n              <button data-testid=\"outside\">Outside</button>\n            </React.Fragment>\n          );\n        }\n\n        await render(<TestCase />);\n\n        const inside = screen.getByTestId('inside');\n        await act(async () => {\n          inside.focus();\n        });\n\n        const outside = screen.getByTestId('outside');\n\n        fireEvent.pointerDown(outside);\n        await act(async () => {\n          outside.focus();\n        });\n        fireEvent.focusOut(inside, { relatedTarget: outside });\n\n        await flushMicrotasks();\n\n        expect(screen.queryByRole('dialog')).toBe(null);\n      });\n\n      it.skipIf(isJSDOM)(\n        'moves focus to the next element when tabbing out of a nested menu inside the popover',\n        async () => {\n          const { user } = await render(\n            <div>\n              <TestPopover\n                rootProps={{ defaultOpen: true }}\n                portalProps={{ keepMounted: true }}\n                popupProps={{\n                  children: (\n                    <React.Fragment>\n                      <button type=\"button\" data-testid=\"before\">\n                        Before\n                      </button>\n                      <Menu.Root>\n                        <Menu.Trigger>Menu</Menu.Trigger>\n                        <Menu.Portal>\n                          <Menu.Positioner>\n                            <Menu.Popup>\n                              <Menu.Item>Item</Menu.Item>\n                            </Menu.Popup>\n                          </Menu.Positioner>\n                        </Menu.Portal>\n                      </Menu.Root>\n                      <button type=\"button\" data-testid=\"after\">\n                        After\n                      </button>\n                    </React.Fragment>\n                  ),\n                }}\n              />\n            </div>,\n          );\n\n          await user.click(screen.getByRole('button', { name: 'Menu' }));\n\n          const menu = await screen.findByRole('menu');\n          await waitFor(() => {\n            expect(menu).toHaveFocus();\n          });\n\n          await user.tab();\n\n          expect(screen.getByTestId('after')).toHaveFocus();\n          expect(screen.queryByRole('menu')).toBe(null);\n          expect(screen.getByTestId('popover-popup')).toBeVisible();\n        },\n      );\n    });\n\n    describe.skipIf(isJSDOM)('pointerdown removal', () => {\n      it('moves focus to the popup when a focused child is removed on pointerdown and outside press still dismisses', async () => {\n        function Test() {\n          const [showButton, setShowButton] = React.useState(true);\n          return (\n            <TestPopover\n              rootProps={{ defaultOpen: true, modal: 'trap-focus' }}\n              popupProps={{\n                children: showButton && (\n                  <button data-testid=\"remove\" onPointerDown={() => setShowButton(false)}>\n                    Remove on pointer down\n                  </button>\n                ),\n              }}\n            />\n          );\n        }\n\n        const { user } = await render(<Test />);\n\n        const removeButton = screen.getByTestId('remove');\n        await waitFor(() => {\n          expect(removeButton).toHaveFocus();\n        });\n        fireEvent.pointerDown(removeButton);\n\n        const popup = screen.getByTestId('popover-popup');\n        await waitFor(() => {\n          expect(popup).toHaveFocus();\n        });\n\n        await user.click(document.body);\n\n        await waitFor(() => {\n          expect(screen.queryByRole('dialog')).toBe(null);\n        });\n      });\n    });\n\n    describe('prop: actionsRef', () => {\n      it('unmounts the popover when the `unmount` method is called', async () => {\n        const actionsRef = {\n          current: {\n            unmount: vi.fn(),\n            close: vi.fn(),\n          },\n        };\n\n        const { user } = await render(\n          <TestPopover\n            rootProps={{\n              actionsRef,\n              onOpenChange: (open, details) => {\n                details.preventUnmountOnClose();\n              },\n            }}\n          />,\n        );\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n        await user.click(trigger);\n\n        await waitFor(() => {\n          expect(screen.queryByRole('dialog')).not.toBe(null);\n        });\n\n        await user.click(trigger);\n\n        await waitFor(() => {\n          expect(screen.queryByRole('dialog')).not.toBe(null);\n        });\n\n        await act(async () => actionsRef.current.unmount());\n\n        await waitFor(() => {\n          expect(screen.queryByRole('dialog')).toBe(null);\n        });\n      });\n\n      it('closes the popover when the `close` method is called', async () => {\n        const actionsRef = React.createRef<Popover.Root.Actions>();\n        await render(<TestPopover rootProps={{ defaultOpen: true, actionsRef }} />);\n\n        await act(async () => {\n          actionsRef.current!.close();\n        });\n\n        await waitFor(() => {\n          expect(screen.queryByText('Content')).toBe(null);\n        });\n      });\n    });\n\n    describe('prop: modal', () => {\n      it('should render an internal backdrop when `true`', async () => {\n        const { user } = await render(\n          <div>\n            <TestPopover rootProps={{ modal: true }} />\n            <button>Outside</button>\n          </div>,\n        );\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n\n        await user.click(trigger);\n\n        await waitFor(() => {\n          expect(screen.queryByRole('dialog')).not.toBe(null);\n        });\n\n        const positioner = screen.getByTestId('positioner');\n\n        expect(positioner.previousElementSibling).toHaveAttribute('role', 'presentation');\n      });\n\n      it('should only render focus guards inside the popup when `true`', async () => {\n        const { user } = await render(\n          <div>\n            <TestPopover\n              rootProps={{ modal: true }}\n              popupProps={{ children: <Popover.Close>Close</Popover.Close> }}\n            />\n          </div>,\n        );\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n\n        await user.click(trigger);\n\n        await waitFor(() => {\n          expect(screen.queryByRole('dialog')).not.to.equal(null);\n        });\n\n        expect(\n          trigger.previousElementSibling?.hasAttribute('data-base-ui-focus-guard'),\n        ).not.to.equal(true);\n        expect(trigger.nextElementSibling?.hasAttribute('data-base-ui-focus-guard')).not.to.equal(\n          true,\n        );\n        expect(\n          document.querySelectorAll('[data-base-ui-focus-guard][data-type=\"inside\"]'),\n        ).to.have.length(2);\n      });\n\n      it('should keep trigger focus guards when `true` without a close part', async () => {\n        const { user } = await render(\n          <div>\n            <TestPopover\n              rootProps={{ defaultOpen: true, modal: true }}\n              popupProps={{ children: <input data-testid=\"input-inside\" /> }}\n              afterTrigger={<input data-testid=\"focus-target\" />}\n            />\n          </div>,\n        );\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n        expect(trigger.previousElementSibling).to.have.attribute('data-base-ui-focus-guard');\n        expect(trigger.nextElementSibling).to.have.attribute('data-base-ui-focus-guard');\n\n        await act(async () => {\n          screen.getByTestId('input-inside').focus();\n        });\n\n        await user.tab();\n\n        expect(screen.getByTestId('focus-target')).toHaveFocus();\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('popover-popup')).to.equal(null);\n        });\n      });\n\n      it('should not render an internal backdrop when `false`', async () => {\n        const { user } = await render(\n          <div>\n            <TestPopover rootProps={{ modal: false }} />\n            <button>Outside</button>\n          </div>,\n        );\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n\n        await user.click(trigger);\n\n        await waitFor(() => {\n          expect(screen.queryByRole('dialog')).not.toBe(null);\n        });\n\n        const positioner = screen.getByTestId('positioner');\n\n        expect(positioner.previousElementSibling).toBe(null);\n      });\n\n      describe('with openOnHover', () => {\n        clock.withFakeTimers();\n\n        it('enables modal behavior after a hover-open is clicked', async () => {\n          await render(\n            <TestPopover\n              rootProps={{ modal: true }}\n              triggerProps={{ openOnHover: true, delay: 0 }}\n            />,\n          );\n\n          const trigger = screen.getByRole('button', { name: 'Toggle' });\n\n          fireEvent.mouseEnter(trigger);\n          fireEvent.mouseMove(trigger);\n\n          await flushMicrotasks();\n          expect(screen.queryByRole('dialog')).not.toBe(null);\n\n          const positioner = screen.getByTestId('positioner');\n          expect(positioner.previousElementSibling).toBe(null);\n\n          clock.tick(PATIENT_CLICK_THRESHOLD - 1);\n          fireEvent.click(trigger);\n\n          await flushMicrotasks();\n\n          expect(positioner.previousElementSibling).toHaveAttribute('role', 'presentation');\n        });\n      });\n    });\n\n    describe.skipIf(isJSDOM)('prop: onOpenChangeComplete', () => {\n      it('is called on close when there is no exit animation defined', async () => {\n        const onOpenChangeComplete = vi.fn();\n\n        function Test() {\n          const [open, setOpen] = React.useState(true);\n          return (\n            <div>\n              <button onClick={() => setOpen(false)}>Close</button>\n              <TestPopover\n                rootProps={{ open, onOpenChangeComplete }}\n                popupProps={{ children: null }}\n              />\n            </div>\n          );\n        }\n\n        const { user } = await render(<Test />);\n\n        const closeButton = screen.getByText('Close');\n        await user.click(closeButton);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('popover-popup')).toBe(null);\n        });\n\n        expect(onOpenChangeComplete.mock.calls[0][0]).toBe(true);\n        expect(onOpenChangeComplete.mock.lastCall?.[0]).toBe(false);\n      });\n\n      it('is called on close when the exit animation finishes', async () => {\n        globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n        const onOpenChangeComplete = vi.fn();\n\n        function Test() {\n          const style = `\n          @keyframes test-anim {\n            to {\n              opacity: 0;\n            }\n          }\n\n          .animation-test-indicator[data-ending-style] {\n            animation: test-anim 1ms;\n          }\n        `;\n\n          const [open, setOpen] = React.useState(true);\n\n          return (\n            <div>\n              {/* eslint-disable-next-line react/no-danger */}\n              <style dangerouslySetInnerHTML={{ __html: style }} />\n              <button onClick={() => setOpen(false)}>Close</button>\n              <TestPopover\n                rootProps={{ open, onOpenChangeComplete }}\n                popupProps={{ className: 'animation-test-indicator', children: null }}\n              />\n            </div>\n          );\n        }\n\n        const { user } = await render(<Test />);\n\n        expect(screen.getByTestId('popover-popup')).not.toBe(null);\n\n        // Wait for open animation to finish\n        await waitFor(() => {\n          expect(onOpenChangeComplete.mock.calls[0][0]).toBe(true);\n        });\n\n        const closeButton = screen.getByText('Close');\n        await user.click(closeButton);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('popover-popup')).toBe(null);\n        });\n\n        expect(onOpenChangeComplete.mock.lastCall?.[0]).toBe(false);\n      });\n\n      it('is called on open when there is no enter animation defined', async () => {\n        const onOpenChangeComplete = vi.fn();\n\n        function Test() {\n          const [open, setOpen] = React.useState(false);\n          return (\n            <div>\n              <button onClick={() => setOpen(true)}>Open</button>\n              <TestPopover\n                rootProps={{ open, onOpenChangeComplete }}\n                popupProps={{ children: null }}\n              />\n            </div>\n          );\n        }\n\n        const { user } = await render(<Test />);\n\n        const openButton = screen.getByText('Open');\n        await user.click(openButton);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('popover-popup')).not.toBe(null);\n        });\n\n        expect(onOpenChangeComplete.mock.calls.length).toBe(2);\n        expect(onOpenChangeComplete.mock.calls[0][0]).toBe(true);\n      });\n\n      it('is called on open when the enter animation finishes', async () => {\n        globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n        const onOpenChangeComplete = vi.fn();\n\n        function Test() {\n          const style = `\n          @keyframes test-anim {\n            from {\n              opacity: 0;\n            }\n          }\n\n          .animation-test-indicator[data-starting-style] {\n            animation: test-anim 1ms;\n          }\n        `;\n\n          const [open, setOpen] = React.useState(false);\n\n          return (\n            <div>\n              {/* eslint-disable-next-line react/no-danger */}\n              <style dangerouslySetInnerHTML={{ __html: style }} />\n              <button onClick={() => setOpen(true)}>Open</button>\n              <TestPopover\n                rootProps={{\n                  open,\n                  onOpenChange: (nextOpen) => setOpen(nextOpen),\n                  onOpenChangeComplete,\n                }}\n                popupProps={{ className: 'animation-test-indicator', children: null }}\n              />\n            </div>\n          );\n        }\n\n        const { user } = await render(<Test />);\n\n        const openButton = screen.getByText('Open');\n        await user.click(openButton);\n\n        // Wait for open animation to finish\n        await waitFor(() => {\n          expect(onOpenChangeComplete.mock.calls[0][0]).toBe(true);\n        });\n\n        expect(screen.queryByTestId('popover-popup')).not.toBe(null);\n      });\n\n      it('does not get called on mount when not open', async () => {\n        const onOpenChangeComplete = vi.fn();\n\n        await render(\n          <TestPopover rootProps={{ onOpenChangeComplete }} popupProps={{ children: null }} />,\n        );\n\n        expect(onOpenChangeComplete.mock.calls.length).toBe(0);\n      });\n    });\n\n    describe('nested popup interactions', () => {\n      it('keeps the parent popover open when press starts in nested popover and ends outside', async () => {\n        function Test() {\n          return (\n            <div>\n              <button type=\"button\" data-testid=\"outside\">\n                Outside\n              </button>\n\n              <Popover.Root defaultOpen>\n                <Popover.Trigger>Parent</Popover.Trigger>\n                <Popover.Portal>\n                  <Popover.Positioner>\n                    <Popover.Popup data-testid=\"parent-popup\">\n                      <Popover.Root>\n                        <Popover.Trigger>Child</Popover.Trigger>\n                        <Popover.Portal>\n                          <Popover.Positioner>\n                            <Popover.Popup data-testid=\"child-popup\">Child content</Popover.Popup>\n                          </Popover.Positioner>\n                        </Popover.Portal>\n                      </Popover.Root>\n                    </Popover.Popup>\n                  </Popover.Positioner>\n                </Popover.Portal>\n              </Popover.Root>\n            </div>\n          );\n        }\n\n        await render(<Test />);\n\n        expect(screen.queryByTestId('parent-popup')).not.toBe(null);\n\n        const childTrigger = screen.getByRole('button', { name: 'Child' });\n\n        fireEvent.click(childTrigger);\n\n        const childPopup = await screen.findByTestId('child-popup');\n        const outside = screen.getByTestId('outside');\n\n        fireEvent.pointerDown(childPopup, { pointerType: 'mouse', button: 0 });\n        fireEvent.click(outside);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('parent-popup')).not.toBe(null);\n        });\n        expect(screen.queryByTestId('child-popup')).not.toBe(null);\n      });\n\n      it.skipIf(isJSDOM)(\n        'should not close popover when scrolling nested popup on touch',\n        async () => {\n          const fruits = Array.from({ length: 50 }, (_, i) => i);\n          await render(\n            <TestPopover\n              rootProps={{ defaultOpen: true }}\n              popupProps={{\n                children: (\n                  <Combobox.Root items={fruits} defaultOpen>\n                    <Combobox.Input placeholder=\"Choose a fruit\" />\n                    <Combobox.Portal>\n                      <Combobox.Positioner>\n                        <Combobox.Popup\n                          data-testid=\"combobox-popup\"\n                          style={{ maxHeight: 200, overflow: 'auto' }}\n                        >\n                          <Combobox.List>\n                            {(item: string) => (\n                              <Combobox.Item key={item} value={item} style={{ height: 100 }}>\n                                {item}\n                              </Combobox.Item>\n                            )}\n                          </Combobox.List>\n                        </Combobox.Popup>\n                      </Combobox.Positioner>\n                    </Combobox.Portal>\n                  </Combobox.Root>\n                ),\n              }}\n            />,\n          );\n\n          const popoverPopup = screen.getByTestId('popover-popup');\n          expect(popoverPopup).not.toBe(null);\n\n          await flushMicrotasks();\n\n          const comboboxPopup = screen.getByTestId('combobox-popup');\n          expect(comboboxPopup).not.toBe(null);\n\n          // Simulate touch scroll: touchstart + touchmove on the scrollable list\n          const touch1 = new Touch({\n            identifier: 1,\n            target: comboboxPopup,\n            clientX: 100,\n            clientY: 100,\n          });\n\n          fireEvent.touchStart(comboboxPopup, {\n            touches: [touch1],\n          });\n\n          // Wait for the markInsideReactTree timeout to finish\n          await new Promise((resolve) => {\n            setTimeout(resolve);\n          });\n\n          const touch2 = new Touch({\n            identifier: 1,\n            target: comboboxPopup,\n            clientX: 100,\n            clientY: 50,\n          });\n\n          fireEvent.touchMove(comboboxPopup, {\n            touches: [touch2],\n          });\n\n          fireEvent.touchEnd(comboboxPopup, {\n            changedTouches: [touch2],\n          });\n\n          await flushMicrotasks();\n\n          expect(screen.queryByTestId('popover-popup')).not.toBe(null);\n          expect(screen.queryByTestId('combobox-popup')).not.toBe(null);\n        },\n      );\n\n      it('should close child popover when clicking parent popover', async () => {\n        const { user } = await render(\n          <TestPopover\n            triggerProps={{ 'data-testid': 'parent-trigger' } as Popover.Trigger.Props}\n            popupProps={\n              {\n                'data-testid': 'parent-popup',\n                children: (\n                  <ContainedTriggerPopover\n                    triggerProps={{ 'data-testid': 'child-trigger' } as Popover.Trigger.Props}\n                    popupProps={\n                      { 'data-testid': 'child-popup', children: null } as Popover.Popup.Props\n                    }\n                  />\n                ),\n              } as Popover.Popup.Props\n            }\n          />,\n        );\n\n        expect(screen.queryByTestId('parent-popup')).toBe(null);\n        expect(screen.queryByTestId('child-popup')).toBe(null);\n\n        const parentTrigger = screen.getByTestId('parent-trigger');\n        await user.click(parentTrigger);\n        await flushMicrotasks();\n\n        const parentPopup = screen.getByTestId('parent-popup');\n\n        expect(parentPopup).not.toBe(null);\n        expect(screen.queryByTestId('child-popup')).toBe(null);\n\n        const childTrigger = screen.getByTestId('child-trigger');\n        await user.click(childTrigger);\n        await flushMicrotasks();\n\n        expect(parentPopup).not.toBe(null);\n        expect(screen.getByTestId('child-popup')).not.toBe(null);\n\n        await user.click(parentPopup);\n        await flushMicrotasks();\n\n        expect(screen.queryByTestId('parent-popup')).not.toBe(null);\n        expect(screen.queryByTestId('child-popup')).toBe(null);\n      });\n    });\n  });\n});\n\ntype TestPopoverProps = {\n  rootProps?: Popover.Root.Props;\n  triggerProps?: Popover.Trigger.Props;\n  portalProps?: Popover.Portal.Props;\n  positionerProps?: Popover.Positioner.Props;\n  popupProps?: Popover.Popup.Props;\n  triggerPlacement?: 'before-content' | 'after-content';\n  beforeTrigger?: React.ReactNode;\n  afterTrigger?: React.ReactNode;\n  includeTrigger?: boolean;\n};\n\nfunction ContainedTriggerPopover(props: TestPopoverProps) {\n  const {\n    rootProps,\n    triggerProps,\n    portalProps,\n    positionerProps,\n    popupProps,\n    triggerPlacement = 'before-content',\n    afterTrigger,\n    includeTrigger = true,\n  } = props;\n\n  const { children: triggerChildren, ...restTriggerProps } = triggerProps ?? {};\n  const { children: popupChildren, ...restPopupProps } = popupProps ?? {};\n  const { children: portalChildren, ...restPortalProps } = portalProps ?? {};\n\n  const renderPortal = () => (\n    <Popover.Portal {...restPortalProps}>\n      {portalChildren}\n      <Popover.Positioner data-testid=\"positioner\" {...positionerProps}>\n        <Popover.Popup data-testid=\"popover-popup\" {...restPopupProps}>\n          {popupChildren ?? 'Content'}\n        </Popover.Popup>\n      </Popover.Positioner>\n    </Popover.Portal>\n  );\n\n  const triggerElement = includeTrigger ? (\n    <Popover.Trigger data-testid=\"trigger\" {...restTriggerProps}>\n      {triggerChildren ?? 'Toggle'}\n    </Popover.Trigger>\n  ) : null;\n\n  return (\n    <Popover.Root {...rootProps}>\n      {triggerPlacement === 'before-content' ? (\n        <React.Fragment>\n          {triggerElement}\n          {afterTrigger}\n          {renderPortal()}\n        </React.Fragment>\n      ) : (\n        <React.Fragment>\n          {renderPortal()}\n          {triggerElement}\n          {afterTrigger}\n        </React.Fragment>\n      )}\n    </Popover.Root>\n  );\n}\n\nfunction DetachedTriggerPopover(props: TestPopoverProps) {\n  const {\n    rootProps,\n    triggerProps,\n    portalProps,\n    positionerProps,\n    popupProps,\n    triggerPlacement = 'before-content',\n    afterTrigger,\n  } = props;\n\n  const { children: triggerChildren, ...restTriggerProps } = triggerProps ?? {};\n  const popoverHandle = useRefWithInit(() => Popover.createHandle()).current;\n\n  return (\n    <React.Fragment>\n      {triggerPlacement === 'before-content' && (\n        <React.Fragment>\n          <Popover.Trigger data-testid=\"trigger\" handle={popoverHandle} {...restTriggerProps}>\n            {triggerChildren ?? 'Toggle'}\n          </Popover.Trigger>\n          {afterTrigger}\n        </React.Fragment>\n      )}\n      <ContainedTriggerPopover\n        rootProps={{ ...rootProps, handle: popoverHandle }}\n        portalProps={portalProps}\n        positionerProps={positionerProps}\n        popupProps={popupProps}\n        includeTrigger={false}\n      />\n      {triggerPlacement === 'after-content' && (\n        <React.Fragment>\n          <Popover.Trigger data-testid=\"trigger\" handle={popoverHandle} {...restTriggerProps}>\n            {triggerChildren ?? 'Toggle'}\n          </Popover.Trigger>\n          {afterTrigger}\n        </React.Fragment>\n      )}\n    </React.Fragment>\n  );\n}\n\nfunction MultipleDetachedTriggersPopover(props: TestPopoverProps) {\n  const {\n    rootProps,\n    triggerProps,\n    portalProps,\n    positionerProps,\n    popupProps,\n    afterTrigger,\n    triggerPlacement = 'before-content',\n  } = props;\n\n  const { children: triggerChildren, ...restTriggerProps } = triggerProps ?? {};\n  const popoverHandle = useRefWithInit(() => Popover.createHandle()).current;\n\n  const renderTriggers = () => (\n    <React.Fragment>\n      <Popover.Trigger data-testid=\"trigger\" handle={popoverHandle} {...restTriggerProps}>\n        {triggerChildren ?? 'Toggle'}\n      </Popover.Trigger>\n      {afterTrigger}\n      <Popover.Trigger data-testid=\"trigger-2\" handle={popoverHandle}>\n        Toggle another\n      </Popover.Trigger>\n    </React.Fragment>\n  );\n\n  return (\n    <React.Fragment>\n      {triggerPlacement === 'before-content' && <React.Fragment>{renderTriggers()}</React.Fragment>}\n      <ContainedTriggerPopover\n        rootProps={{ ...rootProps, handle: popoverHandle }}\n        portalProps={portalProps}\n        positionerProps={positionerProps}\n        popupProps={popupProps}\n        includeTrigger={false}\n      />\n      {triggerPlacement === 'after-content' && <React.Fragment>{renderTriggers()}</React.Fragment>}\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "packages/react/src/popover/root/PopoverRoot.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useScrollLock } from '@base-ui/utils/useScrollLock';\nimport { useOnFirstRender } from '@base-ui/utils/useOnFirstRender';\nimport {\n  useDismiss,\n  useInteractions,\n  useRole,\n  FloatingTree,\n  useFloatingParentNodeId,\n  useSyncedFloatingRootContext,\n} from '../../floating-ui-react';\nimport { PopoverRootContext, usePopoverRootContext } from './PopoverRootContext';\nimport { PopoverStore } from '../store/PopoverStore';\nimport { PopoverHandle } from '../store/PopoverHandle';\nimport {\n  createChangeEventDetails,\n  type BaseUIChangeEventDetails,\n} from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport {\n  useImplicitActiveTrigger,\n  useOpenStateTransitions,\n  type PayloadChildRenderFunction,\n} from '../../utils/popups';\nimport { useOpenInteractionType } from '../../utils/useOpenInteractionType';\n\nfunction PopoverRootComponent<Payload>({ props }: { props: PopoverRoot.Props<Payload> }) {\n  const {\n    children,\n    open: openProp,\n    defaultOpen = false,\n    onOpenChange,\n    onOpenChangeComplete,\n    modal = false,\n    handle,\n    triggerId: triggerIdProp,\n    defaultTriggerId: defaultTriggerIdProp = null,\n  } = props;\n\n  const store = PopoverStore.useStore(handle?.store, {\n    modal,\n    open: defaultOpen,\n    openProp,\n    activeTriggerId: defaultTriggerIdProp,\n    triggerIdProp,\n  });\n\n  // Support initially open state when uncontrolled\n  useOnFirstRender(() => {\n    if (openProp === undefined && store.state.open === false && defaultOpen === true) {\n      store.update({\n        open: true,\n        activeTriggerId: defaultTriggerIdProp,\n      });\n    }\n  });\n\n  store.useControlledProp('openProp', openProp);\n  store.useControlledProp('triggerIdProp', triggerIdProp);\n\n  const open = store.useState('open');\n  const positionerElement = store.useState('positionerElement');\n  const payload = store.useState('payload') as Payload | undefined;\n  const openReason = store.useState('openChangeReason');\n\n  store.useContextCallback('onOpenChange', onOpenChange);\n  store.useContextCallback('onOpenChangeComplete', onOpenChangeComplete);\n\n  const { openMethod, triggerProps: interactionTypeTriggerProps } = useOpenInteractionType(open);\n\n  useImplicitActiveTrigger(store);\n  const { forceUnmount } = useOpenStateTransitions(open, store, () => {\n    store.update({ stickIfOpen: true, openChangeReason: null });\n  });\n\n  useScrollLock(\n    open && modal === true && openReason !== REASONS.triggerHover && openMethod !== 'touch',\n    positionerElement,\n  );\n\n  React.useEffect(() => {\n    if (!open) {\n      store.context.stickIfOpenTimeout.clear();\n    }\n  }, [store, open]);\n\n  const createPopoverEventDetails = React.useCallback(\n    (reason: PopoverRoot.ChangeEventReason) => {\n      const details: PopoverRoot.ChangeEventDetails =\n        createChangeEventDetails<PopoverRoot.ChangeEventReason>(\n          reason,\n        ) as PopoverRoot.ChangeEventDetails;\n      details.preventUnmountOnClose = () => {\n        store.set('preventUnmountingOnClose', true);\n      };\n\n      return details;\n    },\n    [store],\n  );\n\n  const handleImperativeClose = React.useCallback(() => {\n    store.setOpen(false, createPopoverEventDetails(REASONS.imperativeAction));\n  }, [store, createPopoverEventDetails]);\n\n  React.useImperativeHandle(\n    props.actionsRef,\n    () => ({ unmount: forceUnmount, close: handleImperativeClose }),\n    [forceUnmount, handleImperativeClose],\n  );\n\n  const floatingRootContext = useSyncedFloatingRootContext({\n    popupStore: store,\n    onOpenChange: store.setOpen,\n  });\n\n  const dismiss = useDismiss(floatingRootContext, {\n    outsidePressEvent: {\n      // Ensure `aria-hidden` on outside elements is removed immediately\n      // on outside press when trapping focus.\n      mouse: modal === 'trap-focus' ? 'sloppy' : 'intentional',\n      touch: 'sloppy',\n    },\n  });\n\n  const role = useRole(floatingRootContext);\n\n  const { getReferenceProps, getFloatingProps, getTriggerProps } = useInteractions([dismiss, role]);\n\n  const activeTriggerProps = React.useMemo(() => {\n    return getReferenceProps(interactionTypeTriggerProps);\n  }, [getReferenceProps, interactionTypeTriggerProps]);\n\n  const inactiveTriggerProps = React.useMemo(() => {\n    return getTriggerProps(interactionTypeTriggerProps);\n  }, [getTriggerProps, interactionTypeTriggerProps]);\n\n  const popupProps = React.useMemo(() => {\n    return getFloatingProps();\n  }, [getFloatingProps]);\n\n  store.useSyncedValues({\n    modal,\n    openMethod,\n    activeTriggerProps,\n    inactiveTriggerProps,\n    popupProps,\n    floatingRootContext,\n    nested: useFloatingParentNodeId() != null,\n  });\n\n  const popoverContext: PopoverRootContext<Payload> = React.useMemo(\n    () => ({\n      store,\n    }),\n    [store],\n  );\n\n  return (\n    <PopoverRootContext.Provider value={popoverContext as PopoverRootContext<unknown>}>\n      {typeof children === 'function' ? children({ payload }) : children}\n    </PopoverRootContext.Provider>\n  );\n}\n\n/**\n * Groups all parts of the popover.\n * Doesn’t render its own HTML element.\n *\n * Documentation: [Base UI Popover](https://base-ui.com/react/components/popover)\n */\nexport function PopoverRoot<Payload = unknown>(props: PopoverRoot.Props<Payload>) {\n  if (usePopoverRootContext(true)) {\n    return <PopoverRootComponent props={props} />;\n  }\n\n  return (\n    <FloatingTree>\n      <PopoverRootComponent props={props} />\n    </FloatingTree>\n  );\n}\n\nexport interface PopoverRootState {}\n\nexport interface PopoverRootProps<Payload = unknown> {\n  /**\n   * Whether the popover is initially open.\n   *\n   * To render a controlled popover, use the `open` prop instead.\n   * @default false\n   */\n  defaultOpen?: boolean | undefined;\n  /**\n   * Whether the popover is currently open.\n   */\n  open?: boolean | undefined;\n  /**\n   * Event handler called when the popover is opened or closed.\n   */\n  onOpenChange?:\n    | ((open: boolean, eventDetails: PopoverRoot.ChangeEventDetails) => void)\n    | undefined;\n  /**\n   * Event handler called after any animations complete when the popover is opened or closed.\n   */\n  onOpenChangeComplete?: ((open: boolean) => void) | undefined;\n  /**\n   * A ref to imperative actions.\n   * - `unmount`: When specified, the popover will not be unmounted when closed.\n   * Instead, the `unmount` function must be called to unmount the popover manually.\n   * Useful when the popover's animation is controlled by an external library.\n   * - `close`: Closes the dialog imperatively when called.\n   */\n  actionsRef?: React.RefObject<PopoverRoot.Actions | null> | undefined;\n  /**\n   * Determines if the popover enters a modal state when open.\n   * - `true`: user interaction is limited to the popover: document page scroll is locked, and pointer interactions on outside elements are disabled.\n   * - `false`: user interaction with the rest of the document is allowed.\n   * - `'trap-focus'`: focus is trapped inside the popover, but document page scroll is not locked and pointer interactions outside of it remain enabled.\n   *\n   * When `modal` is `true`, focus trapping is enabled only if `<Popover.Close>` is rendered\n   * inside `<Popover.Popup>`. It can be visually hidden with your own CSS if needed, such as\n   * Tailwind's `sr-only` utility.\n   *\n   * When `modal` is `'trap-focus'`, render `<Popover.Close>` inside `<Popover.Popup>` so touch\n   * screen readers can escape the popup.\n   * @default false\n   */\n  modal?: boolean | 'trap-focus' | undefined;\n  /**\n   * ID of the trigger that the popover is associated with.\n   * This is useful in conjunction with the `open` prop to create a controlled popover.\n   * There's no need to specify this prop when the popover is uncontrolled (i.e. when the `open` prop is not set).\n   */\n  triggerId?: string | null | undefined;\n  /**\n   * ID of the trigger that the popover is associated with.\n   * This is useful in conjunction with the `defaultOpen` prop to create an initially open popover.\n   */\n  defaultTriggerId?: string | null | undefined;\n  /**\n   * A handle to associate the popover with a trigger.\n   * If specified, allows external triggers to control the popover's open state.\n   */\n  handle?: PopoverHandle<Payload> | undefined;\n  /**\n   * The content of the popover.\n   * This can be a regular React node or a render function that receives the `payload` of the active trigger.\n   */\n  children?: React.ReactNode | PayloadChildRenderFunction<Payload>;\n}\n\nexport interface PopoverRootActions {\n  unmount: () => void;\n  close: () => void;\n}\n\nexport type PopoverRootChangeEventReason =\n  | typeof REASONS.triggerHover\n  | typeof REASONS.triggerFocus\n  | typeof REASONS.triggerPress\n  | typeof REASONS.outsidePress\n  | typeof REASONS.escapeKey\n  | typeof REASONS.closePress\n  | typeof REASONS.focusOut\n  | typeof REASONS.imperativeAction\n  | typeof REASONS.none;\nexport type PopoverRootChangeEventDetails =\n  BaseUIChangeEventDetails<PopoverRoot.ChangeEventReason> & {\n    preventUnmountOnClose(): void;\n  };\n\nexport namespace PopoverRoot {\n  export type State = PopoverRootState;\n  export type Props<Payload = unknown> = PopoverRootProps<Payload>;\n  export type Actions = PopoverRootActions;\n  export type ChangeEventReason = PopoverRootChangeEventReason;\n  export type ChangeEventDetails = PopoverRootChangeEventDetails;\n}\n"
  },
  {
    "path": "packages/react/src/popover/root/PopoverRootContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport type { PopoverStore } from '../store/PopoverStore';\n\nexport interface PopoverRootContext<Payload = unknown> {\n  store: PopoverStore<Payload>;\n}\n\nexport const PopoverRootContext = React.createContext<PopoverRootContext | undefined>(undefined);\n\nexport function usePopoverRootContext(optional?: false): PopoverRootContext;\nexport function usePopoverRootContext(optional: true): PopoverRootContext | undefined;\nexport function usePopoverRootContext(optional?: boolean) {\n  const context = React.useContext(PopoverRootContext);\n  if (context === undefined && !optional) {\n    throw new Error(\n      'Base UI: PopoverRootContext is missing. Popover parts must be placed within <Popover.Root>.',\n    );\n  }\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/popover/store/PopoverHandle.ts",
    "content": "import { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport { PopoverStore } from './PopoverStore';\n\nexport class PopoverHandle<Payload> {\n  /**\n   * Internal store holding the popover's state.\n   * @internal\n   */\n  public readonly store: PopoverStore<Payload>;\n\n  constructor() {\n    this.store = new PopoverStore<Payload>();\n  }\n\n  /**\n   * Opens the popover and associates it with the trigger with the given id.\n   * The trigger must be a Popover.Trigger component with this handle passed as a prop.\n   *\n   * @param triggerId ID of the trigger to associate with the popover.\n   */\n  open(triggerId: string) {\n    const triggerElement = triggerId\n      ? (this.store.context.triggerElements.getById(triggerId) ?? undefined)\n      : undefined;\n\n    if (triggerId && !triggerElement) {\n      throw new Error(`Base UI: PopoverHandle.open: No trigger found with id \"${triggerId}\".`);\n    }\n\n    this.store.setOpen(\n      true,\n      createChangeEventDetails(\n        REASONS.imperativeAction,\n        undefined,\n        triggerElement as HTMLElement | undefined,\n      ),\n    );\n  }\n\n  /**\n   * Closes the popover.\n   */\n  close() {\n    this.store.setOpen(\n      false,\n      createChangeEventDetails(REASONS.imperativeAction, undefined, undefined),\n    );\n  }\n\n  /**\n   * Indicates whether the popover is currently open.\n   */\n  get isOpen() {\n    return this.store.state.open;\n  }\n}\n\n/**\n * Creates a new handle to connect a Popover.Root with detached Popover.Trigger components.\n */\nexport function createPopoverHandle<Payload>(): PopoverHandle<Payload> {\n  return new PopoverHandle<Payload>();\n}\n"
  },
  {
    "path": "packages/react/src/popover/store/PopoverStore.ts",
    "content": "/* eslint-disable react-hooks/rules-of-hooks */\nimport * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport { ReactStore, createSelector } from '@base-ui/utils/store';\nimport { Timeout } from '@base-ui/utils/useTimeout';\nimport { useRefWithInit } from '@base-ui/utils/useRefWithInit';\nimport { useOnMount } from '@base-ui/utils/useOnMount';\nimport { type InteractionType } from '@base-ui/utils/useEnhancedClickHandler';\nimport { FloatingUIOpenChangeDetails } from '../../utils/types';\nimport { PopoverRoot } from './../root/PopoverRoot';\nimport { REASONS } from '../../utils/reasons';\nimport {\n  createInitialPopupStoreState,\n  PopupStoreContext,\n  popupStoreSelectors,\n  PopupStoreState,\n  PopupTriggerMap,\n} from '../../utils/popups';\nimport { PATIENT_CLICK_THRESHOLD } from '../../utils/constants';\n\nexport type State<Payload> = PopupStoreState<Payload> & {\n  disabled: boolean;\n  instantType: 'dismiss' | 'click' | undefined;\n  modal: boolean | 'trap-focus';\n  focusManagerModal: boolean;\n  openMethod: InteractionType | null;\n  openChangeReason: PopoverRoot.ChangeEventReason | null;\n  stickIfOpen: boolean;\n  nested: boolean;\n  titleElementId: string | undefined;\n  descriptionElementId: string | undefined;\n  openOnHover: boolean;\n  closeDelay: number;\n  hasViewport: boolean;\n};\n\ntype Context = PopupStoreContext<PopoverRoot.ChangeEventDetails> & {\n  readonly popupRef: React.RefObject<HTMLElement | null>;\n  readonly backdropRef: React.RefObject<HTMLDivElement | null>;\n  readonly internalBackdropRef: React.RefObject<HTMLDivElement | null>;\n  readonly triggerFocusTargetRef: React.RefObject<HTMLElement | null>;\n  readonly beforeContentFocusGuardRef: React.RefObject<HTMLElement | null>;\n  readonly stickIfOpenTimeout: Timeout;\n};\n\nfunction createInitialState<Payload>(): State<Payload> {\n  return {\n    ...createInitialPopupStoreState(),\n    disabled: false,\n    modal: false,\n    focusManagerModal: false,\n    instantType: undefined,\n    openMethod: null,\n    openChangeReason: null,\n    titleElementId: undefined,\n    descriptionElementId: undefined,\n    stickIfOpen: true,\n    nested: false,\n    openOnHover: false,\n    closeDelay: 0,\n    hasViewport: false,\n  };\n}\n\nconst selectors = {\n  ...popupStoreSelectors,\n  disabled: createSelector((state: State<unknown>) => state.disabled),\n  instantType: createSelector((state: State<unknown>) => state.instantType),\n  openMethod: createSelector((state: State<unknown>) => state.openMethod),\n  openChangeReason: createSelector((state: State<unknown>) => state.openChangeReason),\n  modal: createSelector((state: State<unknown>) => state.modal),\n  focusManagerModal: createSelector((state: State<unknown>) => state.focusManagerModal),\n  stickIfOpen: createSelector((state: State<unknown>) => state.stickIfOpen),\n  titleElementId: createSelector((state: State<unknown>) => state.titleElementId),\n  descriptionElementId: createSelector((state: State<unknown>) => state.descriptionElementId),\n  openOnHover: createSelector((state: State<unknown>) => state.openOnHover),\n  closeDelay: createSelector((state: State<unknown>) => state.closeDelay),\n  hasViewport: createSelector((state: State<unknown>) => state.hasViewport),\n};\n\nexport class PopoverStore<Payload> extends ReactStore<\n  Readonly<State<Payload>>,\n  Context,\n  Selectors\n> {\n  constructor(initialState?: Partial<State<Payload>>) {\n    const initial = { ...createInitialState<Payload>(), ...initialState };\n\n    if (initial.open && initialState?.mounted === undefined) {\n      initial.mounted = true;\n    }\n\n    super(\n      initial,\n      {\n        popupRef: React.createRef<HTMLElement>(),\n        backdropRef: React.createRef<HTMLDivElement>(),\n        internalBackdropRef: React.createRef<HTMLDivElement>(),\n        onOpenChange: undefined,\n        onOpenChangeComplete: undefined,\n        triggerFocusTargetRef: React.createRef<HTMLElement>(),\n        beforeContentFocusGuardRef: React.createRef<HTMLElement>(),\n        stickIfOpenTimeout: new Timeout(),\n        triggerElements: new PopupTriggerMap(),\n      },\n      selectors,\n    );\n  }\n\n  setOpen = (\n    nextOpen: boolean,\n    eventDetails: Omit<PopoverRoot.ChangeEventDetails, 'preventUnmountOnClose'>,\n  ) => {\n    const isHover = eventDetails.reason === REASONS.triggerHover;\n    const isKeyboardClick =\n      eventDetails.reason === REASONS.triggerPress &&\n      (eventDetails.event as MouseEvent).detail === 0;\n    const isDismissClose =\n      !nextOpen && (eventDetails.reason === REASONS.escapeKey || eventDetails.reason == null);\n\n    (eventDetails as PopoverRoot.ChangeEventDetails).preventUnmountOnClose = () => {\n      this.set('preventUnmountingOnClose', true);\n    };\n\n    this.context.onOpenChange?.(nextOpen, eventDetails as PopoverRoot.ChangeEventDetails);\n\n    if (eventDetails.isCanceled) {\n      return;\n    }\n\n    const details: FloatingUIOpenChangeDetails = {\n      open: nextOpen,\n      nativeEvent: eventDetails.event,\n      reason: eventDetails.reason,\n      nested: this.state.nested,\n      triggerElement: eventDetails.trigger,\n    };\n\n    const floatingEvents = this.state.floatingRootContext.context.events;\n    floatingEvents?.emit('openchange', details);\n\n    const changeState = () => {\n      const updatedState: Partial<State<Payload>> = {\n        open: nextOpen,\n        openChangeReason: eventDetails.reason,\n      };\n\n      // If a popup is closing, the `trigger` may be null.\n      // We want to keep the previous value so that exit animations are played and focus is returned correctly.\n      const newTriggerId = eventDetails.trigger?.id ?? null;\n      if (newTriggerId || nextOpen) {\n        updatedState.activeTriggerId = newTriggerId;\n        updatedState.activeTriggerElement = eventDetails.trigger ?? null;\n      }\n\n      this.update(updatedState);\n    };\n\n    if (isHover) {\n      // Only allow \"patient\" clicks to close the popover if it's open.\n      // If they clicked within 500ms of the popover opening, keep it open.\n      this.set('stickIfOpen', true);\n      this.context.stickIfOpenTimeout.start(PATIENT_CLICK_THRESHOLD, () => {\n        this.set('stickIfOpen', false);\n      });\n\n      ReactDOM.flushSync(changeState);\n    } else {\n      changeState();\n    }\n\n    if (isKeyboardClick || isDismissClose) {\n      this.set('instantType', isKeyboardClick ? 'click' : 'dismiss');\n    } else if (eventDetails.reason === REASONS.focusOut) {\n      this.set('instantType', 'focus');\n    } else {\n      this.set('instantType', undefined);\n    }\n  };\n\n  public static useStore<Payload>(\n    externalStore: PopoverStore<Payload> | undefined,\n    initialState: Partial<State<Payload>>,\n  ) {\n    const internalStore = useRefWithInit(() => {\n      return new PopoverStore<Payload>(initialState);\n    }).current;\n\n    const store = externalStore ?? internalStore;\n\n    useOnMount(internalStore.disposeEffect);\n    return store;\n  }\n\n  private disposeEffect = () => {\n    return this.context.stickIfOpenTimeout.disposeEffect();\n  };\n}\n\ntype Selectors = typeof selectors;\n"
  },
  {
    "path": "packages/react/src/popover/title/PopoverTitle.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { Popover } from '@base-ui/react/popover';\nimport { screen } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Popover.Title />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Popover.Title />, () => ({\n    refInstanceof: window.HTMLHeadingElement,\n    render(node) {\n      return render(\n        <Popover.Root open>\n          <Popover.Trigger>Trigger</Popover.Trigger>\n          <Popover.Portal>\n            <Popover.Positioner>\n              <Popover.Popup>{node}</Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        </Popover.Root>,\n      );\n    },\n  }));\n\n  it('labels the popup element with its id', async () => {\n    await render(\n      <Popover.Root open>\n        <Popover.Trigger>Trigger</Popover.Trigger>\n        <Popover.Portal>\n          <Popover.Positioner>\n            <Popover.Popup>\n              <Popover.Title>Title</Popover.Title>\n            </Popover.Popup>\n          </Popover.Positioner>\n        </Popover.Portal>\n      </Popover.Root>,\n    );\n\n    const id = document.querySelector('h2')?.id;\n    expect(screen.getByRole('dialog')).toHaveAttribute('aria-labelledby', id);\n  });\n});\n"
  },
  {
    "path": "packages/react/src/popover/title/PopoverTitle.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { usePopoverRootContext } from '../root/PopoverRootContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useBaseUiId } from '../../utils/useBaseUiId';\n\n/**\n * A heading that labels the popover.\n * Renders an `<h2>` element.\n *\n * Documentation: [Base UI Popover](https://base-ui.com/react/components/popover)\n */\nexport const PopoverTitle = React.forwardRef(function PopoverTitle(\n  componentProps: PopoverTitle.Props,\n  forwardedRef: React.ForwardedRef<HTMLHeadingElement>,\n) {\n  const { render, className, ...elementProps } = componentProps;\n\n  const { store } = usePopoverRootContext();\n\n  const id = useBaseUiId(elementProps.id);\n\n  useIsoLayoutEffect(() => {\n    store.set('titleElementId', id);\n    return () => {\n      store.set('titleElementId', undefined);\n    };\n  }, [store, id]);\n\n  const element = useRenderElement('h2', componentProps, {\n    ref: forwardedRef,\n    props: [{ id }, elementProps],\n  });\n\n  return element;\n});\n\nexport interface PopoverTitleState {}\n\nexport interface PopoverTitleProps extends BaseUIComponentProps<\n  'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6',\n  PopoverTitleState\n> {}\n\nexport namespace PopoverTitle {\n  export type State = PopoverTitleState;\n  export type Props = PopoverTitleProps;\n}\n"
  },
  {
    "path": "packages/react/src/popover/trigger/PopoverTrigger.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { Popover } from '@base-ui/react/popover';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\nimport {\n  act,\n  fireEvent,\n  flushMicrotasks,\n  ignoreActWarnings,\n  screen,\n  waitFor,\n} from '@mui/internal-test-utils';\nimport { PATIENT_CLICK_THRESHOLD } from '../../utils/constants';\n\ndescribe('<Popover.Trigger />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Popover.Trigger />, () => ({\n    refInstanceof: window.HTMLButtonElement,\n    testComponentPropWith: 'button',\n    button: true,\n    render(node) {\n      return render(<Popover.Root open>{node}</Popover.Root>);\n    },\n  }));\n\n  describe('prop: disabled', () => {\n    it('disables the popover', async () => {\n      const { user } = await render(\n        <Popover.Root>\n          <Popover.Trigger disabled />\n          <Popover.Portal>\n            <Popover.Positioner>\n              <Popover.Popup>Content</Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        </Popover.Root>,\n      );\n\n      const trigger = screen.getByRole('button');\n      expect(trigger).toHaveAttribute('disabled');\n      expect(trigger).toHaveAttribute('data-disabled');\n\n      await user.click(trigger);\n      expect(screen.queryByText('Content')).toBe(null);\n\n      await user.keyboard('[Tab]');\n      expect(document.activeElement).not.toBe(trigger);\n    });\n\n    it('custom element', async () => {\n      const { user } = await render(\n        <Popover.Root>\n          <Popover.Trigger disabled render={<span />} nativeButton={false} />\n          <Popover.Portal>\n            <Popover.Positioner>\n              <Popover.Popup>Content</Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        </Popover.Root>,\n      );\n\n      const trigger = screen.getByRole('button');\n      expect(trigger).not.toHaveAttribute('disabled');\n      expect(trigger).toHaveAttribute('data-disabled');\n      expect(trigger).toHaveAttribute('aria-disabled', 'true');\n\n      await user.click(trigger);\n      expect(screen.queryByText('Content')).toBe(null);\n\n      await user.keyboard('[Tab]');\n      expect(document.activeElement).not.toBe(trigger);\n    });\n  });\n\n  describe('style hooks', () => {\n    it('should have the data-popup-open and data-pressed attributes when open by clicking', async () => {\n      await render(\n        <Popover.Root>\n          <Popover.Trigger />\n        </Popover.Root>,\n      );\n\n      const trigger = screen.getByRole('button');\n\n      await act(async () => {\n        trigger.click();\n      });\n\n      expect(trigger).toHaveAttribute('data-popup-open');\n      expect(trigger).toHaveAttribute('data-pressed');\n    });\n\n    it('should have the data-popup-open but not the data-pressed attribute when open by hover', async () => {\n      const { user } = await render(\n        <Popover.Root>\n          <Popover.Trigger openOnHover delay={0} />\n          <Popover.Portal>\n            <Popover.Positioner>\n              <Popover.Popup />\n            </Popover.Positioner>\n          </Popover.Portal>\n        </Popover.Root>,\n      );\n\n      const trigger = screen.getByRole('button');\n\n      await user.hover(trigger);\n\n      expect(trigger).toHaveAttribute('data-popup-open');\n      expect(trigger).not.toHaveAttribute('data-pressed');\n    });\n\n    it('should not have the data-popup-open and data-pressed attributes when open by click when `openOnHover=true` and `delay=0`', async () => {\n      const { user } = await render(\n        <Popover.Root>\n          <Popover.Trigger delay={0} openOnHover />\n          <Popover.Portal>\n            <Popover.Positioner>\n              <Popover.Popup />\n            </Popover.Positioner>\n          </Popover.Portal>\n        </Popover.Root>,\n      );\n\n      const trigger = screen.getByRole('button');\n\n      await user.hover(trigger);\n\n      await act(async () => {\n        trigger.click();\n      });\n\n      expect(trigger).toHaveAttribute('data-popup-open');\n    });\n\n    it('should have the data-popup-open and data-pressed attributes when open by click when `openOnHover=true`', async () => {\n      const { user } = await render(\n        <Popover.Root>\n          <Popover.Trigger openOnHover />\n          <Popover.Portal>\n            <Popover.Positioner>\n              <Popover.Popup />\n            </Popover.Positioner>\n          </Popover.Portal>\n        </Popover.Root>,\n      );\n\n      const trigger = screen.getByRole('button');\n\n      await user.hover(trigger);\n      await act(async () => {\n        trigger.click();\n      });\n\n      expect(trigger).toHaveAttribute('data-popup-open');\n      expect(trigger).toHaveAttribute('data-pressed');\n    });\n  });\n\n  describe('impatient clicks with `openOnHover=true`', () => {\n    const { clock, render: renderFakeTimers } = createRenderer();\n\n    clock.withFakeTimers();\n\n    it('does not close the popover if the user clicks too quickly', async () => {\n      await renderFakeTimers(\n        <Popover.Root>\n          <Popover.Trigger delay={0} openOnHover />\n          <Popover.Portal>\n            <Popover.Positioner>\n              <Popover.Popup />\n            </Popover.Positioner>\n          </Popover.Portal>\n        </Popover.Root>,\n      );\n\n      const trigger = screen.getByRole('button');\n\n      fireEvent.mouseMove(trigger);\n\n      clock.tick(PATIENT_CLICK_THRESHOLD - 1);\n\n      fireEvent.click(trigger);\n\n      expect(trigger).toHaveAttribute('data-popup-open');\n    });\n\n    it('closes the popover if the user clicks patiently', async () => {\n      await renderFakeTimers(\n        <Popover.Root>\n          <Popover.Trigger delay={0} openOnHover />\n          <Popover.Portal>\n            <Popover.Positioner>\n              <Popover.Popup />\n            </Popover.Positioner>\n          </Popover.Portal>\n        </Popover.Root>,\n      );\n\n      const trigger = screen.getByRole('button');\n\n      fireEvent.mouseEnter(trigger);\n\n      clock.tick(PATIENT_CLICK_THRESHOLD);\n\n      fireEvent.click(trigger);\n\n      expect(trigger).not.toHaveAttribute('data-popup-open');\n    });\n\n    it('sticks if the user clicks impatiently', async () => {\n      await renderFakeTimers(\n        <Popover.Root>\n          <Popover.Trigger delay={0} openOnHover />\n          <Popover.Portal>\n            <Popover.Positioner>\n              <Popover.Popup />\n            </Popover.Positioner>\n          </Popover.Portal>\n        </Popover.Root>,\n      );\n\n      const trigger = screen.getByRole('button');\n\n      fireEvent.mouseEnter(trigger);\n\n      clock.tick(PATIENT_CLICK_THRESHOLD - 1);\n\n      fireEvent.click(trigger);\n      fireEvent.mouseLeave(trigger);\n\n      expect(trigger).toHaveAttribute('data-popup-open');\n\n      clock.tick(1);\n\n      expect(trigger).toHaveAttribute('data-popup-open');\n    });\n\n    it('does not stick if the user clicks patiently', async () => {\n      await renderFakeTimers(\n        <Popover.Root>\n          <Popover.Trigger delay={0} openOnHover />\n          <Popover.Portal>\n            <Popover.Positioner>\n              <Popover.Popup />\n            </Popover.Positioner>\n          </Popover.Portal>\n        </Popover.Root>,\n      );\n\n      const trigger = screen.getByRole('button');\n\n      fireEvent.mouseEnter(trigger);\n\n      clock.tick(PATIENT_CLICK_THRESHOLD);\n\n      fireEvent.click(trigger);\n      fireEvent.mouseLeave(trigger);\n\n      expect(trigger).not.toHaveAttribute('data-popup-open');\n    });\n\n    it('sticks when clicked before the hover delay completes', async () => {\n      await renderFakeTimers(\n        <Popover.Root>\n          <Popover.Trigger openOnHover delay={300}>\n            Open\n          </Popover.Trigger>\n          <Popover.Portal>\n            <Popover.Positioner>\n              <Popover.Popup>Content</Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        </Popover.Root>,\n      );\n\n      const trigger = screen.getByRole('button');\n\n      fireEvent.mouseEnter(trigger);\n      fireEvent.mouseMove(trigger);\n\n      clock.tick(100);\n\n      // User clicks impatiently to open\n      fireEvent.click(trigger);\n\n      expect(trigger).toHaveAttribute('data-popup-open');\n\n      fireEvent.mouseLeave(trigger);\n\n      expect(trigger).toHaveAttribute('data-popup-open');\n    });\n\n    it('should keep the popover open when re-hovered and clicked within the patient threshold', async () => {\n      await render(\n        <Popover.Root>\n          <Popover.Trigger openOnHover delay={100}>\n            Open\n          </Popover.Trigger>\n          <Popover.Portal>\n            <Popover.Positioner>\n              <Popover.Popup>Content</Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        </Popover.Root>,\n      );\n\n      const trigger = screen.getByRole('button');\n\n      fireEvent.mouseEnter(trigger);\n      fireEvent.mouseMove(trigger);\n\n      clock.tick(100);\n      await flushMicrotasks();\n\n      expect(screen.getByText('Content')).not.toBe(null);\n\n      clock.tick(PATIENT_CLICK_THRESHOLD);\n\n      fireEvent.mouseLeave(trigger);\n      fireEvent.mouseEnter(trigger);\n      fireEvent.mouseMove(trigger);\n\n      fireEvent.click(trigger);\n      expect(screen.getByText('Content')).not.toBe(null);\n    });\n  });\n\n  it.skipIf(isJSDOM)(\n    'should toggle closed with Enter or Space when rendering a <div>',\n    async () => {\n      ignoreActWarnings();\n      const { userEvent: user } = await import('vitest/browser');\n      const { render: vbrRender, cleanup } = await import('vitest-browser-react');\n\n      try {\n        await vbrRender(\n          <div>\n            <Popover.Root>\n              <Popover.Trigger render={<div />} nativeButton={false} data-testid=\"div-trigger\">\n                Toggle\n              </Popover.Trigger>\n              <Popover.Portal>\n                <Popover.Positioner>\n                  <Popover.Popup>Content</Popover.Popup>\n                </Popover.Positioner>\n              </Popover.Portal>\n            </Popover.Root>\n            <button data-testid=\"other-button\">Other button</button>\n          </div>,\n        );\n\n        const trigger = screen.getByTestId('div-trigger');\n\n        await act(async () => trigger.focus());\n        await user.keyboard('[Enter]');\n        expect(screen.queryByText('Content')).not.toBe(null);\n\n        await user.tab({ shift: true });\n        expect(document.activeElement).toBe(trigger);\n\n        await user.keyboard('[Enter]');\n        await waitFor(() => {\n          expect(screen.queryByText('Content')).toBe(null);\n        });\n\n        await user.keyboard('[Enter]');\n        expect(screen.queryByText('Content')).not.toBe(null);\n\n        await user.tab({ shift: true });\n        expect(document.activeElement).toBe(trigger);\n\n        await user.keyboard('[Space]');\n        expect(screen.queryByText('Content')).toBe(null);\n\n        await user.keyboard('[Space]');\n        expect(screen.queryByText('Content')).not.toBe(null);\n\n        await user.tab({ shift: true });\n        expect(document.activeElement).toBe(trigger);\n\n        await user.keyboard('[Space]');\n        expect(screen.queryByText('Content')).toBe(null);\n      } finally {\n        await cleanup();\n      }\n    },\n  );\n});\n"
  },
  {
    "path": "packages/react/src/popover/trigger/PopoverTrigger.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport { type FocusableElement } from 'tabbable';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { usePopoverRootContext } from '../root/PopoverRootContext';\nimport { useButton } from '../../use-button/useButton';\nimport type { BaseUIComponentProps, NativeButtonProps } from '../../utils/types';\nimport {\n  triggerOpenStateMapping,\n  pressableTriggerOpenStateMapping,\n} from '../../utils/popupStateMapping';\nimport { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { CLICK_TRIGGER_IDENTIFIER } from '../../utils/constants';\nimport {\n  safePolygon,\n  useClick,\n  useHoverReferenceInteraction,\n  useInteractions,\n} from '../../floating-ui-react';\nimport { OPEN_DELAY } from '../utils/constants';\nimport { PopoverHandle } from '../store/PopoverHandle';\nimport { useBaseUiId } from '../../utils/useBaseUiId';\nimport { FocusGuard } from '../../utils/FocusGuard';\nimport {\n  contains,\n  getNextTabbable,\n  getTabbableAfterElement,\n  getTabbableBeforeElement,\n  isOutsideEvent,\n} from '../../floating-ui-react/utils';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport { useTriggerDataForwarding } from '../../utils/popups';\n\n/**\n * A button that opens the popover.\n * Renders a `<button>` element.\n *\n * Documentation: [Base UI Popover](https://base-ui.com/react/components/popover)\n */\nexport const PopoverTrigger = React.forwardRef(function PopoverTrigger(\n  componentProps: PopoverTrigger.Props,\n  forwardedRef: React.ForwardedRef<HTMLElement>,\n) {\n  const {\n    render,\n    className,\n    disabled = false,\n    nativeButton = true,\n    handle,\n    payload,\n    openOnHover = false,\n    delay = OPEN_DELAY,\n    closeDelay = 0,\n    id: idProp,\n    ...elementProps\n  } = componentProps;\n\n  const rootContext = usePopoverRootContext(true);\n  const store = handle?.store ?? rootContext?.store;\n  if (!store) {\n    throw new Error(\n      'Base UI: <Popover.Trigger> must be either used within a <Popover.Root> component or provided with a handle.',\n    );\n  }\n\n  const thisTriggerId = useBaseUiId(idProp);\n  const isTriggerActive = store.useState('isTriggerActive', thisTriggerId);\n  const floatingContext = store.useState('floatingRootContext');\n  const isOpenedByThisTrigger = store.useState('isOpenedByTrigger', thisTriggerId);\n\n  const triggerElementRef = React.useRef<HTMLElement | null>(null);\n\n  const { registerTrigger, isMountedByThisTrigger } = useTriggerDataForwarding(\n    thisTriggerId,\n    triggerElementRef,\n    store,\n    {\n      payload,\n      disabled,\n      openOnHover,\n      closeDelay,\n    },\n  );\n\n  const openReason = store.useState('openChangeReason');\n  const stickIfOpen = store.useState('stickIfOpen');\n  const openMethod = store.useState('openMethod');\n  const focusManagerModal = store.useState('focusManagerModal');\n\n  const hoverProps = useHoverReferenceInteraction(floatingContext, {\n    enabled:\n      floatingContext != null &&\n      openOnHover &&\n      (openMethod !== 'touch' || openReason !== REASONS.triggerPress),\n    mouseOnly: true,\n    move: false,\n    handleClose: safePolygon(),\n    restMs: delay,\n    delay: {\n      close: closeDelay,\n    },\n    triggerElementRef,\n    isActiveTrigger: isTriggerActive,\n  });\n\n  const click = useClick(floatingContext, { enabled: floatingContext != null, stickIfOpen });\n\n  const localProps = useInteractions([click]);\n\n  const rootTriggerProps = store.useState('triggerProps', isMountedByThisTrigger);\n\n  const state: PopoverTriggerState = {\n    disabled,\n    open: isOpenedByThisTrigger,\n  };\n\n  const { getButtonProps, buttonRef } = useButton({\n    disabled,\n    native: nativeButton,\n  });\n\n  const stateAttributesMapping: StateAttributesMapping<{ open: boolean }> = React.useMemo(\n    () => ({\n      open(value) {\n        if (value && openReason === REASONS.triggerPress) {\n          return pressableTriggerOpenStateMapping.open(value);\n        }\n\n        return triggerOpenStateMapping.open(value);\n      },\n    }),\n    [openReason],\n  );\n\n  const element = useRenderElement('button', componentProps, {\n    state,\n    ref: [buttonRef, forwardedRef, registerTrigger, triggerElementRef],\n    props: [\n      localProps.getReferenceProps(),\n      hoverProps,\n      rootTriggerProps,\n      { [CLICK_TRIGGER_IDENTIFIER as string]: '', id: thisTriggerId },\n      elementProps,\n      getButtonProps,\n    ],\n    stateAttributesMapping,\n  });\n\n  const preFocusGuardRef = React.useRef<HTMLElement>(null);\n\n  const handlePreFocusGuardFocus = useStableCallback((event: React.FocusEvent) => {\n    ReactDOM.flushSync(() => {\n      store.setOpen(\n        false,\n        createChangeEventDetails(\n          REASONS.focusOut,\n          event.nativeEvent,\n          event.currentTarget as HTMLElement,\n        ),\n      );\n    });\n\n    const previousTabbable: FocusableElement | null = getTabbableBeforeElement(\n      preFocusGuardRef.current,\n    );\n    previousTabbable?.focus();\n  });\n\n  const handleFocusTargetFocus = useStableCallback((event: React.FocusEvent) => {\n    const positionerElement = store.select('positionerElement');\n    if (positionerElement && isOutsideEvent(event, positionerElement)) {\n      store.context.beforeContentFocusGuardRef.current?.focus();\n    } else {\n      ReactDOM.flushSync(() => {\n        store.setOpen(\n          false,\n          createChangeEventDetails(\n            REASONS.focusOut,\n            event.nativeEvent,\n            event.currentTarget as HTMLElement,\n          ),\n        );\n      });\n\n      let nextTabbable = getTabbableAfterElement(\n        store.context.triggerFocusTargetRef.current || triggerElementRef.current,\n      );\n\n      while (nextTabbable !== null && contains(positionerElement, nextTabbable)) {\n        const prevTabbable = nextTabbable;\n        nextTabbable = getNextTabbable(nextTabbable);\n        if (nextTabbable === prevTabbable) {\n          break;\n        }\n      }\n\n      nextTabbable?.focus();\n    }\n  });\n\n  // A fragment with key is required to ensure that the `element` is mounted to the same DOM node\n  // regardless of whether the focus guards are rendered or not.\n\n  if (isTriggerActive && !focusManagerModal) {\n    return (\n      <React.Fragment>\n        <FocusGuard ref={preFocusGuardRef} onFocus={handlePreFocusGuardFocus} />\n        <React.Fragment key={thisTriggerId}>{element}</React.Fragment>\n        <FocusGuard ref={store.context.triggerFocusTargetRef} onFocus={handleFocusTargetFocus} />\n      </React.Fragment>\n    );\n  }\n\n  return <React.Fragment key={thisTriggerId}>{element}</React.Fragment>;\n}) as PopoverTrigger;\n\nexport interface PopoverTrigger {\n  <Payload>(\n    componentProps: PopoverTriggerProps<Payload> & React.RefAttributes<HTMLElement>,\n  ): React.JSX.Element;\n}\n\nexport interface PopoverTriggerState {\n  /**\n   * Whether the popover is currently disabled.\n   */\n  disabled: boolean;\n  /**\n   * Whether the popover is currently open.\n   */\n  open: boolean;\n}\n\nexport type PopoverTriggerProps<Payload = unknown> = NativeButtonProps &\n  BaseUIComponentProps<'button', PopoverTriggerState> & {\n    /**\n     * Whether the component renders a native `<button>` element when replacing it\n     * via the `render` prop.\n     * Set to `false` if the rendered element is not a button (e.g. `<div>`).\n     * @default true\n     */\n    nativeButton?: boolean | undefined;\n    /**\n     * A handle to associate the trigger with a popover.\n     */\n    handle?: PopoverHandle<Payload> | undefined;\n    /**\n     * A payload to pass to the popover when it is opened.\n     */\n    payload?: Payload | undefined;\n    /**\n     * ID of the trigger. In addition to being forwarded to the rendered element,\n     * it is also used to specify the active trigger for the popover in controlled mode (with the PopoverRoot `triggerId` prop).\n     */\n    id?: string | undefined;\n    /**\n     * Whether the popover should also open when the trigger is hovered.\n     * @default false\n     */\n    openOnHover?: boolean | undefined;\n    /**\n     * How long to wait before the popover may be opened on hover. Specified in milliseconds.\n     *\n     * Requires the `openOnHover` prop.\n     * @default 300\n     */\n    delay?: number | undefined;\n    /**\n     * How long to wait before closing the popover that was opened on hover.\n     * Specified in milliseconds.\n     *\n     * Requires the `openOnHover` prop.\n     * @default 0\n     */\n    closeDelay?: number | undefined;\n  };\n\nexport namespace PopoverTrigger {\n  export type State = PopoverTriggerState;\n  export type Props<Payload = unknown> = PopoverTriggerProps<Payload>;\n}\n"
  },
  {
    "path": "packages/react/src/popover/trigger/PopoverTriggerDataAttributes.ts",
    "content": "import { CommonTriggerDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum PopoverTriggerDataAttributes {\n  /**\n   * Present when the corresponding popover is open.\n   */\n  popupOpen = CommonTriggerDataAttributes.popupOpen,\n  /**\n   * Present when the trigger is pressed.\n   */\n  pressed = CommonTriggerDataAttributes.pressed,\n}\n"
  },
  {
    "path": "packages/react/src/popover/utils/constants.ts",
    "content": "export const OPEN_DELAY = 300;\n"
  },
  {
    "path": "packages/react/src/popover/viewport/PopoverViewport.test.tsx",
    "content": "import { expect } from 'vitest';\nimport * as React from 'react';\nimport { Popover } from '@base-ui/react/popover';\nimport { screen, waitFor } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\n\ndescribe('<Popover.Viewport />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Popover.Viewport />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <Popover.Root open>\n          <Popover.Trigger>Trigger</Popover.Trigger>\n          <Popover.Portal>\n            <Popover.Positioner>\n              <Popover.Popup>{node}</Popover.Popup>\n            </Popover.Positioner>\n          </Popover.Portal>\n        </Popover.Root>,\n      );\n    },\n  }));\n\n  it('should render children in the `current` container by default', async () => {\n    await render(\n      <Popover.Root open>\n        <Popover.Trigger>Trigger</Popover.Trigger>\n        <Popover.Portal>\n          <Popover.Positioner>\n            <Popover.Popup>\n              <Popover.Viewport>\n                <div data-testid=\"content\">Content</div>\n              </Popover.Viewport>\n            </Popover.Popup>\n          </Popover.Positioner>\n        </Popover.Portal>\n      </Popover.Root>,\n    );\n\n    const currentContainer = screen.getByTestId('content').closest('[data-current]');\n    expect(currentContainer).not.toBe(null);\n    expect(currentContainer!.textContent).toBe('Content');\n  });\n\n  it('should remount the `current` container when the active trigger changes', async () => {\n    const { user } = await render(\n      <Popover.Root>\n        {({ payload }) => (\n          <React.Fragment>\n            <Popover.Trigger payload=\"first\" data-testid=\"trigger1\">\n              Trigger 1\n            </Popover.Trigger>\n            <Popover.Trigger payload=\"second\" data-testid=\"trigger2\">\n              Trigger 2\n            </Popover.Trigger>\n            <Popover.Portal>\n              <Popover.Positioner>\n                <Popover.Popup>\n                  <Popover.Viewport>\n                    {payload === 'first' ? (\n                      <img data-testid=\"payload-image-1\" src=\"about:blank\" alt=\"Preview 1\" />\n                    ) : null}\n                    {payload === 'second' ? (\n                      <img data-testid=\"payload-image-2\" src=\"about:blank\" alt=\"Preview 2\" />\n                    ) : null}\n                  </Popover.Viewport>\n                </Popover.Popup>\n              </Popover.Positioner>\n            </Popover.Portal>\n          </React.Fragment>\n        )}\n      </Popover.Root>,\n    );\n\n    const trigger1 = screen.getByTestId('trigger1');\n    const trigger2 = screen.getByTestId('trigger2');\n\n    await user.click(trigger1);\n\n    const firstImage = await screen.findByTestId('payload-image-1');\n    const firstContainer = firstImage.closest('[data-current]');\n    expect(firstContainer).not.toBe(null);\n\n    await user.click(trigger2);\n\n    await waitFor(() => {\n      const secondImage = screen.getByTestId('payload-image-2');\n      const secondContainer = secondImage.closest('[data-current]');\n      expect(secondContainer).not.toBe(null);\n      expect(secondContainer).not.toBe(firstContainer);\n    });\n  });\n\n  describe.skipIf(isJSDOM)('morphing containers with multiple triggers and payloads', () => {\n    beforeEach(() => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n    });\n\n    afterEach(() => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = true;\n    });\n\n    it('should create morphing containers during transitions', async () => {\n      const { user } = await render(\n        <div>\n          <style>\n            {`\n              [data-transitioning] [data-previous] {\n                animation: slide-out 0.3s ease-out forwards;\n              }\n              [data-transitioning] [data-current] {\n                animation: slide-in 0.3s ease-out forwards;\n              }\n              @keyframes slide-out {\n                from { transform: translateX(0); opacity: 1; }\n                to { transform: translateX(-30%); opacity: 0; }\n              }\n              @keyframes slide-in {\n                from { transform: translateX(30%); opacity: 0; }\n                to { transform: translateX(0); opacity: 1; }\n              }\n            `}\n          </style>\n          <Popover.Root>\n            {({ payload }) => (\n              <React.Fragment>\n                <Popover.Trigger\n                  payload={0}\n                  data-testid=\"trigger1\"\n                  style={{\n                    position: 'absolute',\n                    top: '10px',\n                    left: '10px',\n                    width: '100px',\n                    height: '50px',\n                  }}\n                >\n                  Trigger 1\n                </Popover.Trigger>\n                <Popover.Trigger\n                  payload={1}\n                  data-testid=\"trigger2\"\n                  style={{\n                    position: 'absolute',\n                    top: '100px',\n                    left: '200px',\n                    width: '100px',\n                    height: '50px',\n                  }}\n                >\n                  Trigger 2\n                </Popover.Trigger>\n                <Popover.Portal>\n                  <Popover.Positioner>\n                    <Popover.Popup>\n                      <Popover.Viewport>\n                        <div data-testid=\"content\">Content {payload as number}</div>\n                      </Popover.Viewport>\n                    </Popover.Popup>\n                  </Popover.Positioner>\n                </Popover.Portal>\n              </React.Fragment>\n            )}\n          </Popover.Root>\n        </div>,\n      );\n\n      const trigger1 = screen.getByTestId('trigger1');\n      const trigger2 = screen.getByTestId('trigger2');\n\n      await user.click(trigger1);\n      await waitFor(() => {\n        expect(screen.getByText('Content 0')).toBeVisible();\n      });\n\n      // Click second trigger to trigger morphing\n      await user.click(trigger2);\n\n      // Check for morphing containers during transition\n      let previousContainer: HTMLElement | null = null;\n      await waitFor(() => {\n        previousContainer = document.querySelector('[data-previous]');\n        expect(previousContainer).not.toBe(null);\n      });\n\n      expect(previousContainer).toHaveAttribute('inert');\n      expect(previousContainer!.textContent).toBe('Content 0');\n\n      const nextContainer = document.querySelector('[data-current]');\n      expect(nextContainer).not.toBe(null);\n      expect(nextContainer!.textContent).toBe('Content 1');\n\n      // Verify they are cleaned up after animation\n      await waitFor(() => {\n        expect(document.querySelector('[data-previous]')).toBe(null);\n      });\n\n      expect(document.querySelector('[data-current]')).toBeVisible();\n      expect(screen.getByText('Content 1')).toBeVisible();\n    });\n\n    it('should handle rapid trigger changes', async () => {\n      function TestComponent() {\n        return (\n          <div>\n            <style>\n              {`\n              [data-transitioning] [data-previous] {\n                animation: slide-out 0.2s ease-out forwards;\n              }\n              [data-transitioning] [data-current] {\n                animation: slide-in 0.2s ease-out forwards;\n              }\n              @keyframes slide-out {\n                from { transform: translateX(0); opacity: 1; }\n                to { transform: translateX(-30%); opacity: 0; }\n              }\n              @keyframes slide-in {\n                from { transform: translateX(30%); opacity: 0; }\n                to { transform: translateX(0); opacity: 1; }\n              }\n            `}\n            </style>\n            <Popover.Root>\n              {({ payload }) => (\n                <React.Fragment>\n                  <Popover.Trigger payload={1} data-testid=\"trigger1\">\n                    Trigger 1\n                  </Popover.Trigger>\n                  <Popover.Trigger payload={2} data-testid=\"trigger2\">\n                    Trigger 2\n                  </Popover.Trigger>\n                  <Popover.Trigger payload={3} data-testid=\"trigger3\">\n                    Trigger 3\n                  </Popover.Trigger>\n                  <Popover.Portal>\n                    <Popover.Positioner>\n                      <Popover.Popup>\n                        <Popover.Viewport>Content {payload as number}</Popover.Viewport>\n                      </Popover.Popup>\n                    </Popover.Positioner>\n                  </Popover.Portal>\n                </React.Fragment>\n              )}\n            </Popover.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<TestComponent />);\n\n      const trigger1 = screen.getByTestId('trigger1');\n      const trigger2 = screen.getByTestId('trigger2');\n      const trigger3 = screen.getByTestId('trigger3');\n\n      await user.click(trigger1);\n      await user.click(trigger2);\n      await user.click(trigger3);\n      await user.click(trigger1);\n\n      const content = await screen.findByText('Content 1');\n      await waitFor(() => {\n        expect(content).toBeVisible();\n      });\n    });\n\n    it.each([\n      {\n        name: 'should calculate \"right down\" direction',\n        trigger1: { top: 10, left: 10 },\n        trigger2: { top: 100, left: 200 },\n        expectedDirection: ['right', 'down'],\n      },\n      {\n        name: 'should calculate \"left up\" direction',\n        trigger1: { top: 100, left: 200 },\n        trigger2: { top: 10, left: 10 },\n        expectedDirection: ['left', 'up'],\n      },\n      {\n        name: 'should calculate \"right\" direction (horizontal only)',\n        trigger1: { top: 50, left: 10 },\n        trigger2: { top: 52, left: 200 }, // 2px vertical difference within tolerance\n        expectedDirection: ['right'],\n      },\n      {\n        name: 'should calculate \"down\" direction (vertical only)',\n        trigger1: { top: 10, left: 50 },\n        trigger2: { top: 100, left: 52 }, // 2px horizontal difference within tolerance\n        expectedDirection: ['down'],\n      },\n      {\n        name: 'should handle tolerance for small differences',\n        trigger1: { top: 50, left: 50 },\n        trigger2: { top: 52, left: 52 }, // Both differences within 5px tolerance\n        expectedDirection: [],\n      },\n      {\n        name: 'should calculate \"left down\" direction',\n        trigger1: { top: 10, left: 200 },\n        trigger2: { top: 100, left: 10 },\n        expectedDirection: ['left', 'down'],\n      },\n      {\n        name: 'should calculate \"right up\" direction',\n        trigger1: { top: 100, left: 10 },\n        trigger2: { top: 10, left: 200 },\n        expectedDirection: ['right', 'up'],\n      },\n    ])('$name', async ({ trigger1, trigger2, expectedDirection }) => {\n      const { user } = await render(\n        <div>\n          <style>\n            {`\n              [data-transitioning] [data-previous] {\n                animation: slide-out 0.2s ease-out forwards;\n              }\n              [data-transitioning] [data-current] {\n                animation: slide-in 0.2s ease-out forwards;\n              }\n              @keyframes slide-out {\n                from { transform: translateX(0); opacity: 1; }\n                to { transform: translateX(-30%); opacity: 0; }\n              }\n              @keyframes slide-in {\n                from { transform: translateX(30%); opacity: 0; }\n                to { transform: translateX(0); opacity: 1; }\n              }\n            `}\n          </style>\n          <Popover.Root>\n            {({ payload }) => (\n              <React.Fragment>\n                <Popover.Trigger\n                  payload={0}\n                  data-testid=\"trigger1\"\n                  style={{\n                    position: 'absolute',\n                    top: `${trigger1.top}px`,\n                    left: `${trigger1.left}px`,\n                    width: '100px',\n                    height: '50px',\n                  }}\n                >\n                  Trigger 1\n                </Popover.Trigger>\n                <Popover.Trigger\n                  payload={1}\n                  data-testid=\"trigger2\"\n                  style={{\n                    position: 'absolute',\n                    top: `${trigger2.top}px`,\n                    left: `${trigger2.left}px`,\n                    width: '100px',\n                    height: '50px',\n                  }}\n                >\n                  Trigger 2\n                </Popover.Trigger>\n                <Popover.Portal>\n                  <Popover.Positioner>\n                    <Popover.Popup>\n                      <Popover.Viewport data-testid=\"viewport\">\n                        <div data-testid=\"content\">Content {payload as number}</div>\n                      </Popover.Viewport>\n                    </Popover.Popup>\n                  </Popover.Positioner>\n                </Popover.Portal>\n              </React.Fragment>\n            )}\n          </Popover.Root>\n        </div>,\n      );\n\n      const triggerElement1 = screen.getByTestId('trigger1');\n      const triggerElement2 = screen.getByTestId('trigger2');\n\n      await user.click(triggerElement1);\n\n      await waitFor(() => {\n        expect(screen.getByText('Content 0')).toBeVisible();\n      });\n\n      await user.click(triggerElement2);\n\n      const viewport = screen.getByTestId('viewport');\n      await waitFor(() => {\n        expect(viewport).toHaveAttribute('data-activation-direction');\n      });\n\n      const direction = viewport.getAttribute('data-activation-direction');\n\n      if (expectedDirection.length === 0) {\n        expect(direction?.trim()).toBe('');\n      } else {\n        expectedDirection.forEach((dir) => {\n          expect(direction).toContain(dir);\n        });\n      }\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/popover/viewport/PopoverViewport.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { usePopoverRootContext } from '../root/PopoverRootContext';\nimport { usePopoverPositionerContext } from '../positioner/PopoverPositionerContext';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport { PopoverViewportCssVars } from './PopoverViewportCssVars';\nimport { usePopupViewport } from '../../utils/usePopupViewport';\n\nconst stateAttributesMapping: StateAttributesMapping<PopoverViewportState> = {\n  activationDirection: (value) =>\n    value\n      ? {\n          'data-activation-direction': value,\n        }\n      : null,\n};\n\n/**\n * A viewport for displaying content transitions.\n * This component is only required if one popup can be opened by multiple triggers, its content change based on the trigger\n * and switching between them is animated.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Popover](https://base-ui.com/react/components/popover)\n */\nexport const PopoverViewport = React.forwardRef(function PopoverViewport(\n  componentProps: PopoverViewport.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, children, ...elementProps } = componentProps;\n  const { store } = usePopoverRootContext();\n  const { side } = usePopoverPositionerContext();\n\n  const instantType = store.useState('instantType');\n\n  const { children: childrenToRender, state: viewportState } = usePopupViewport({\n    store,\n    side,\n    cssVars: PopoverViewportCssVars,\n    children,\n  });\n\n  const state: PopoverViewportState = {\n    activationDirection: viewportState.activationDirection,\n    transitioning: viewportState.transitioning,\n    instant: instantType,\n  };\n\n  return useRenderElement('div', componentProps, {\n    state,\n    ref: forwardedRef,\n    props: [elementProps, { children: childrenToRender }],\n    stateAttributesMapping,\n  });\n});\n\nexport interface PopoverViewportState {\n  /**\n   * The activation direction of the transitioned content.\n   */\n  activationDirection: string | undefined;\n  /**\n   * Whether the viewport is currently transitioning between contents.\n   */\n  transitioning: boolean;\n  /**\n   * Present if animations should be instant.\n   */\n  instant: 'dismiss' | 'click' | undefined;\n}\n\nexport namespace PopoverViewport {\n  export interface Props extends BaseUIComponentProps<'div', PopoverViewportState> {\n    /**\n     * The content to render inside the transition container.\n     */\n    children?: React.ReactNode;\n  }\n\n  export type State = PopoverViewportState;\n}\n"
  },
  {
    "path": "packages/react/src/popover/viewport/PopoverViewportCssVars.ts",
    "content": "export enum PopoverViewportCssVars {\n  /**\n   * The width of the parent popup.\n   * This variable is placed on the 'previous' container and stores the width of the popup when the previous content was rendered.\n   * It can be used to freeze the dimensions of the popup when animating between different content.\n   */\n  popupWidth = '--popup-width',\n  /**\n   * The height of the parent popup.\n   * This variable is placed on the 'previous' container and stores the height of the popup when the previous content was rendered.\n   * It can be used to freeze the dimensions of the popup when animating between different content.\n   */\n  popupHeight = '--popup-height',\n}\n"
  },
  {
    "path": "packages/react/src/popover/viewport/PopoverViewportDataAttributes.ts",
    "content": "export enum PopoverViewportDataAttributes {\n  /**\n   * Applied to the direct child of the viewport when no transitions are present or the new content when it's entering.\n   */\n  current = 'data-current',\n  /**\n   * Applied to the direct child of the viewport that contains the exiting content when transitions are present.\n   */\n  previous = 'data-previous',\n  /**\n   * Indicates the direction from which the popup was activated.\n   * This can be used to create directional animations based on how the popup was triggered.\n   * Contains space-separated values for both horizontal and vertical axes.\n   * @type {`${'left' | 'right'} {'top' | 'bottom'}`}\n   */\n  activationDirection = 'data-activation-direction',\n  /**\n   * Indicates that the viewport is currently transitioning between old and new content.\n   */\n  transitioning = 'data-transitioning',\n  /**\n   * Present if animations should be instant.\n   * @type {'dismiss' | 'click'}\n   */\n  instant = 'data-instant',\n}\n"
  },
  {
    "path": "packages/react/src/preview-card/arrow/PreviewCardArrow.test.tsx",
    "content": "import { PreviewCard } from '@base-ui/react/preview-card';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<PreviewCard.Arrow />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<PreviewCard.Arrow />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <PreviewCard.Root open>\n          <PreviewCard.Portal>\n            <PreviewCard.Positioner>\n              <PreviewCard.Popup>{node}</PreviewCard.Popup>\n            </PreviewCard.Positioner>\n          </PreviewCard.Portal>\n        </PreviewCard.Root>,\n      );\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/preview-card/arrow/PreviewCardArrow.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { usePreviewCardPositionerContext } from '../positioner/PreviewCardPositionerContext';\nimport { usePreviewCardRootContext } from '../root/PreviewCardContext';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport type { Align, Side } from '../../utils/useAnchorPositioning';\nimport { popupStateMapping } from '../../utils/popupStateMapping';\nimport { useRenderElement } from '../../utils/useRenderElement';\n\n/**\n * Displays an element positioned against the preview card anchor.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Preview Card](https://base-ui.com/react/components/preview-card)\n */\nexport const PreviewCardArrow = React.forwardRef(function PreviewCardArrow(\n  componentProps: PreviewCardArrow.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, ...elementProps } = componentProps;\n\n  const store = usePreviewCardRootContext();\n  const { arrowRef, side, align, arrowUncentered, arrowStyles } = usePreviewCardPositionerContext();\n  const open = store.useState('open');\n\n  const state: PreviewCardArrowState = {\n    open,\n    side,\n    align,\n    uncentered: arrowUncentered,\n  };\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: [arrowRef, forwardedRef],\n    props: [{ style: arrowStyles, 'aria-hidden': true }, elementProps],\n    stateAttributesMapping: popupStateMapping,\n  });\n\n  return element;\n});\n\nexport interface PreviewCardArrowState {\n  /**\n   * Whether the preview card is currently open.\n   */\n  open: boolean;\n  /**\n   * The side of the anchor the component is placed on.\n   */\n  side: Side;\n  /**\n   * The alignment of the component relative to the anchor.\n   */\n  align: Align;\n  /**\n   * Whether the arrow cannot be centered on the anchor.\n   */\n  uncentered: boolean;\n}\n\nexport interface PreviewCardArrowProps extends BaseUIComponentProps<'div', PreviewCardArrowState> {}\n\nexport namespace PreviewCardArrow {\n  export type State = PreviewCardArrowState;\n  export type Props = PreviewCardArrowProps;\n}\n"
  },
  {
    "path": "packages/react/src/preview-card/arrow/PreviewCardArrowDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum PreviewCardArrowDataAttributes {\n  /**\n   * Present when the preview card is open.\n   */\n  open = CommonPopupDataAttributes.open,\n  /**\n   * Present when the preview card is closed.\n   */\n  closed = CommonPopupDataAttributes.closed,\n  /**\n   * Indicates which side the popup is positioned relative to the trigger.\n   * @type {'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'}\n   */\n  side = CommonPopupDataAttributes.side,\n  /**\n   * Indicates how the popup is aligned relative to specified side.\n   * @type {'start' | 'center' | 'end'}\n   */\n  align = CommonPopupDataAttributes.align,\n  /**\n   * Present when the preview card arrow is uncentered.\n   */\n  uncentered = 'data-uncentered',\n}\n"
  },
  {
    "path": "packages/react/src/preview-card/backdrop/PreviewCardBackdrop.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { PreviewCard } from '@base-ui/react/preview-card';\nimport { screen, waitFor } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<PreviewCard.Backdrop />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<PreviewCard.Backdrop />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(<PreviewCard.Root open>{node}</PreviewCard.Root>);\n    },\n  }));\n\n  it('sets `pointer-events: none` style', async () => {\n    const { user } = await render(\n      <PreviewCard.Root>\n        <PreviewCard.Trigger delay={0} closeDelay={0}>\n          Open\n        </PreviewCard.Trigger>\n        <PreviewCard.Portal>\n          <PreviewCard.Backdrop data-testid=\"backdrop\" />\n          <PreviewCard.Positioner>\n            <PreviewCard.Popup />\n          </PreviewCard.Positioner>\n        </PreviewCard.Portal>\n      </PreviewCard.Root>,\n    );\n\n    await user.hover(screen.getByText('Open'));\n\n    await waitFor(() => {\n      expect(screen.getByTestId('backdrop').style.pointerEvents).toBe('none');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/preview-card/backdrop/PreviewCardBackdrop.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { usePreviewCardRootContext } from '../root/PreviewCardContext';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { type StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport { popupStateMapping as baseMapping } from '../../utils/popupStateMapping';\nimport type { TransitionStatus } from '../../utils/useTransitionStatus';\nimport { transitionStatusMapping } from '../../utils/stateAttributesMapping';\nimport { useRenderElement } from '../../utils/useRenderElement';\n\nconst stateAttributesMapping: StateAttributesMapping<PreviewCardBackdropState> = {\n  ...baseMapping,\n  ...transitionStatusMapping,\n};\n\n/**\n * An overlay displayed beneath the popup.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Preview Card](https://base-ui.com/react/components/preview-card)\n */\nexport const PreviewCardBackdrop = React.forwardRef(function PreviewCardBackdrop(\n  componentProps: PreviewCardBackdrop.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, ...elementProps } = componentProps;\n\n  const store = usePreviewCardRootContext();\n  const open = store.useState('open');\n  const mounted = store.useState('mounted');\n  const transitionStatus = store.useState('transitionStatus');\n\n  const state: PreviewCardBackdropState = {\n    open,\n    transitionStatus,\n  };\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: [forwardedRef],\n    props: [\n      {\n        role: 'presentation',\n        hidden: !mounted,\n        style: {\n          pointerEvents: 'none',\n          userSelect: 'none',\n          WebkitUserSelect: 'none',\n        },\n      },\n      elementProps,\n    ],\n    stateAttributesMapping,\n  });\n\n  return element;\n});\n\nexport interface PreviewCardBackdropState {\n  /**\n   * Whether the preview card is currently open.\n   */\n  open: boolean;\n  /**\n   * The transition status of the component.\n   */\n  transitionStatus: TransitionStatus;\n}\n\nexport interface PreviewCardBackdropProps extends BaseUIComponentProps<\n  'div',\n  PreviewCardBackdropState\n> {}\n\nexport namespace PreviewCardBackdrop {\n  export type State = PreviewCardBackdropState;\n  export type Props = PreviewCardBackdropProps;\n}\n"
  },
  {
    "path": "packages/react/src/preview-card/backdrop/PreviewCardBackdropDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum PreviewCardBackdropDataAttributes {\n  /**\n   * Present when the preview card is open.\n   */\n  open = CommonPopupDataAttributes.open,\n  /**\n   * Present when the preview card is closed.\n   */\n  closed = CommonPopupDataAttributes.closed,\n  /**\n   * Present when the preview card is animating in.\n   */\n  startingStyle = CommonPopupDataAttributes.startingStyle,\n  /**\n   * Present when the preview card is animating out.\n   */\n  endingStyle = CommonPopupDataAttributes.endingStyle,\n}\n"
  },
  {
    "path": "packages/react/src/preview-card/index.parts.ts",
    "content": "export { PreviewCardRoot as Root } from './root/PreviewCardRoot';\nexport { PreviewCardPortal as Portal } from './portal/PreviewCardPortal';\nexport { PreviewCardTrigger as Trigger } from './trigger/PreviewCardTrigger';\nexport { PreviewCardPositioner as Positioner } from './positioner/PreviewCardPositioner';\nexport { PreviewCardPopup as Popup } from './popup/PreviewCardPopup';\nexport { PreviewCardArrow as Arrow } from './arrow/PreviewCardArrow';\nexport { PreviewCardBackdrop as Backdrop } from './backdrop/PreviewCardBackdrop';\nexport { PreviewCardViewport as Viewport } from './viewport/PreviewCardViewport';\nexport {\n  createPreviewCardHandle as createHandle,\n  PreviewCardHandle as Handle,\n} from './store/PreviewCardHandle';\n"
  },
  {
    "path": "packages/react/src/preview-card/index.ts",
    "content": "export * as PreviewCard from './index.parts';\n\nexport type * from './root/PreviewCardRoot';\nexport type * from './trigger/PreviewCardTrigger';\nexport type * from './portal/PreviewCardPortal';\nexport type * from './positioner/PreviewCardPositioner';\nexport type * from './popup/PreviewCardPopup';\nexport type * from './arrow/PreviewCardArrow';\nexport type * from './backdrop/PreviewCardBackdrop';\n"
  },
  {
    "path": "packages/react/src/preview-card/popup/PreviewCardPopup.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { PreviewCard } from '@base-ui/react/preview-card';\nimport { screen } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Popover.Popup />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<PreviewCard.Popup />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <PreviewCard.Root open>\n          <PreviewCard.Portal>\n            <PreviewCard.Positioner>{node}</PreviewCard.Positioner>\n          </PreviewCard.Portal>\n        </PreviewCard.Root>,\n      );\n    },\n  }));\n\n  it('should render the children', async () => {\n    await render(\n      <PreviewCard.Root open>\n        <PreviewCard.Portal>\n          <PreviewCard.Positioner>\n            <PreviewCard.Popup>Content</PreviewCard.Popup>\n          </PreviewCard.Positioner>\n        </PreviewCard.Portal>\n      </PreviewCard.Root>,\n    );\n\n    expect(screen.getByText('Content')).not.toBe(null);\n  });\n});\n"
  },
  {
    "path": "packages/react/src/preview-card/popup/PreviewCardPopup.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { usePreviewCardRootContext } from '../root/PreviewCardContext';\nimport { usePreviewCardPositionerContext } from '../positioner/PreviewCardPositionerContext';\nimport type { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport type { Align, Side } from '../../utils/useAnchorPositioning';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { popupStateMapping as baseMapping } from '../../utils/popupStateMapping';\nimport type { TransitionStatus } from '../../utils/useTransitionStatus';\nimport { transitionStatusMapping } from '../../utils/stateAttributesMapping';\nimport { useOpenChangeComplete } from '../../utils/useOpenChangeComplete';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { getDisabledMountTransitionStyles } from '../../utils/getDisabledMountTransitionStyles';\nimport { useHoverFloatingInteraction } from '../../floating-ui-react';\n\nconst stateAttributesMapping: StateAttributesMapping<PreviewCardPopupState> = {\n  ...baseMapping,\n  ...transitionStatusMapping,\n};\n\n/**\n * A container for the preview card contents.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Preview Card](https://base-ui.com/react/components/preview-card)\n */\nexport const PreviewCardPopup = React.forwardRef(function PreviewCardPopup(\n  componentProps: PreviewCardPopup.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { className, render, ...elementProps } = componentProps;\n\n  const store = usePreviewCardRootContext();\n  const { side, align } = usePreviewCardPositionerContext();\n\n  const open = store.useState('open');\n  const instantType = store.useState('instantType');\n  const transitionStatus = store.useState('transitionStatus');\n  const popupProps = store.useState('popupProps');\n  const floatingContext = store.useState('floatingRootContext');\n\n  useOpenChangeComplete({\n    open,\n    ref: store.context.popupRef,\n    onComplete() {\n      if (open) {\n        store.context.onOpenChangeComplete?.(true);\n      }\n    },\n  });\n\n  const getCloseDelay = useStableCallback(() => store.context.closeDelayRef.current);\n\n  useHoverFloatingInteraction(floatingContext, {\n    closeDelay: getCloseDelay,\n  });\n\n  const state: PreviewCardPopupState = {\n    open,\n    side,\n    align,\n    instant: instantType,\n    transitionStatus,\n  };\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: [forwardedRef, store.context.popupRef, store.useStateSetter('popupElement')],\n    props: [popupProps, getDisabledMountTransitionStyles(transitionStatus), elementProps],\n    stateAttributesMapping,\n  });\n\n  return element;\n});\n\nexport interface PreviewCardPopupState {\n  /**\n   * Whether the preview card is currently open.\n   */\n  open: boolean;\n  /**\n   * The side of the anchor the component is placed on.\n   */\n  side: Side;\n  /**\n   * The alignment of the component relative to the anchor.\n   */\n  align: Align;\n  /**\n   * Whether transitions should be skipped.\n   */\n  instant: 'dismiss' | 'focus' | undefined;\n  /**\n   * The transition status of the component.\n   */\n  transitionStatus: TransitionStatus;\n}\n\nexport interface PreviewCardPopupProps extends BaseUIComponentProps<'div', PreviewCardPopupState> {}\n\nexport namespace PreviewCardPopup {\n  export type State = PreviewCardPopupState;\n  export type Props = PreviewCardPopupProps;\n}\n"
  },
  {
    "path": "packages/react/src/preview-card/popup/PreviewCardPopupDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum PreviewCardPopupDataAttributes {\n  /**\n   * Present when the preview card is open.\n   */\n  open = CommonPopupDataAttributes.open,\n  /**\n   * Present when the preview card is closed.\n   */\n  closed = CommonPopupDataAttributes.closed,\n  /**\n   * Present when the preview card is animating in.\n   */\n  startingStyle = CommonPopupDataAttributes.startingStyle,\n  /**\n   * Present when the preview card is animating out.\n   */\n  endingStyle = CommonPopupDataAttributes.endingStyle,\n  /**\n   * Indicates which side the popup is positioned relative to the trigger.\n   * @type {'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'}\n   */\n  side = CommonPopupDataAttributes.side,\n  /**\n   * Indicates how the popup is aligned relative to specified side.\n   * @type {'start' | 'center' | 'end'}\n   */\n  align = CommonPopupDataAttributes.align,\n}\n"
  },
  {
    "path": "packages/react/src/preview-card/portal/PreviewCardPortal.test.tsx",
    "content": "import * as React from 'react';\nimport { PreviewCard } from '@base-ui/react/preview-card';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<PreviewCard.Portal />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<PreviewCard.Portal keepMounted />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(<PreviewCard.Root open>{node}</PreviewCard.Root>);\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/preview-card/portal/PreviewCardPortal.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { usePreviewCardRootContext } from '../root/PreviewCardContext';\nimport { PreviewCardPortalContext } from './PreviewCardPortalContext';\nimport { FloatingPortalLite } from '../../utils/FloatingPortalLite';\n\n/**\n * A portal element that moves the popup to a different part of the DOM.\n * By default, the portal element is appended to `<body>`.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Preview Card](https://base-ui.com/react/components/preview-card)\n */\nexport const PreviewCardPortal = React.forwardRef(function PreviewCardPortal(\n  props: PreviewCardPortal.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { keepMounted = false, ...portalProps } = props;\n\n  const store = usePreviewCardRootContext();\n  const mounted = store.useState('mounted');\n\n  const shouldRender = mounted || keepMounted;\n  if (!shouldRender) {\n    return null;\n  }\n\n  return (\n    <PreviewCardPortalContext.Provider value={keepMounted}>\n      <FloatingPortalLite ref={forwardedRef} {...portalProps} />\n    </PreviewCardPortalContext.Provider>\n  );\n});\n\nexport interface PreviewCardPortalState {}\n\nexport interface PreviewCardPortalProps extends FloatingPortalLite.Props<PreviewCardPortalState> {\n  /**\n   * Whether to keep the portal mounted in the DOM while the popup is hidden.\n   * @default false\n   */\n  keepMounted?: boolean | undefined;\n}\n\nexport namespace PreviewCardPortal {\n  export type State = PreviewCardPortalState;\n  export type Props = PreviewCardPortalProps;\n}\n"
  },
  {
    "path": "packages/react/src/preview-card/portal/PreviewCardPortalContext.ts",
    "content": "'use client';\nimport * as React from 'react';\n\nexport const PreviewCardPortalContext = React.createContext<boolean | undefined>(undefined);\n\nexport function usePreviewCardPortalContext() {\n  const value = React.useContext(PreviewCardPortalContext);\n  if (value === undefined) {\n    throw new Error('Base UI: <PreviewCard.Portal> is missing.');\n  }\n  return value;\n}\n"
  },
  {
    "path": "packages/react/src/preview-card/positioner/PreviewCardPositioner.spec.tsx",
    "content": "import { PreviewCard } from '@base-ui/react';\n\n// @ts-expect-error - `keepMounted` should not be available\n<PreviewCard.Positioner keepMounted />;\n"
  },
  {
    "path": "packages/react/src/preview-card/positioner/PreviewCardPositioner.test.tsx",
    "content": "import { expect } from 'vitest';\nimport * as React from 'react';\nimport { PreviewCard } from '@base-ui/react/preview-card';\nimport { screen, waitFor } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\n\nconst Trigger = React.forwardRef(function Trigger(\n  props: PreviewCard.Trigger.Props,\n  ref: React.ForwardedRef<HTMLAnchorElement>,\n) {\n  return <PreviewCard.Trigger {...props} ref={ref} render={<div />} />;\n});\n\ndescribe('<PreviewCard.Positioner />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<PreviewCard.Positioner />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <PreviewCard.Root open>\n          <PreviewCard.Portal>{node}</PreviewCard.Portal>\n        </PreviewCard.Root>,\n      );\n    },\n  }));\n\n  const baselineX = 10;\n  const baselineY = 36;\n  const popupWidth = 52;\n  const popupHeight = 24;\n  const anchorWidth = 72;\n  const anchorHeight = 36;\n  const triggerStyle = { width: anchorWidth, height: anchorHeight };\n  const popupStyle = { width: popupWidth, height: popupHeight };\n\n  describe.skipIf(isJSDOM)('prop: sideOffset', () => {\n    it('offsets the side when a number is specified', async () => {\n      const sideOffset = 7;\n      await render(\n        <PreviewCard.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <PreviewCard.Portal>\n            <PreviewCard.Positioner data-testid=\"positioner\" sideOffset={sideOffset}>\n              <PreviewCard.Popup style={popupStyle}>Popup</PreviewCard.Popup>\n            </PreviewCard.Positioner>\n          </PreviewCard.Portal>\n        </PreviewCard.Root>,\n      );\n\n      expect(screen.getByTestId('positioner').getBoundingClientRect()).toMatchObject({\n        x: baselineX,\n        y: baselineY + sideOffset,\n      });\n    });\n\n    it('offsets the side when a function is specified', async () => {\n      await render(\n        <PreviewCard.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <PreviewCard.Portal>\n            <PreviewCard.Positioner\n              data-testid=\"positioner\"\n              sideOffset={(data) => data.positioner.width + data.anchor.width}\n            >\n              <PreviewCard.Popup style={popupStyle}>Popup</PreviewCard.Popup>\n            </PreviewCard.Positioner>\n          </PreviewCard.Portal>\n        </PreviewCard.Root>,\n      );\n\n      expect(screen.getByTestId('positioner').getBoundingClientRect()).toMatchObject({\n        x: baselineX,\n        y: baselineY + popupWidth + anchorWidth,\n      });\n    });\n\n    it('can read the latest side inside sideOffset', async () => {\n      let side = 'none';\n      await render(\n        <PreviewCard.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <PreviewCard.Portal>\n            <PreviewCard.Positioner\n              side=\"left\"\n              data-testid=\"positioner\"\n              sideOffset={(data) => {\n                side = data.side;\n                return 0;\n              }}\n            >\n              <PreviewCard.Popup style={popupStyle}>Popup</PreviewCard.Popup>\n            </PreviewCard.Positioner>\n          </PreviewCard.Portal>\n        </PreviewCard.Root>,\n      );\n\n      // correctly flips the side in the browser\n      expect(side).toBe('right');\n    });\n\n    it('can read the latest align inside sideOffset', async () => {\n      let align = 'none';\n      await render(\n        <PreviewCard.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <PreviewCard.Portal>\n            <PreviewCard.Positioner\n              side=\"right\"\n              align=\"start\"\n              data-testid=\"positioner\"\n              sideOffset={(data) => {\n                align = data.align;\n                return 0;\n              }}\n            >\n              <PreviewCard.Popup style={popupStyle}>Popup</PreviewCard.Popup>\n            </PreviewCard.Positioner>\n          </PreviewCard.Portal>\n        </PreviewCard.Root>,\n      );\n\n      // correctly flips the align in the browser\n      expect(align).toBe('end');\n    });\n\n    it('reads logical side inside sideOffset', async () => {\n      let side = 'none';\n      await render(\n        <PreviewCard.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <PreviewCard.Portal>\n            <PreviewCard.Positioner\n              side=\"inline-start\"\n              data-testid=\"positioner\"\n              sideOffset={(data) => {\n                side = data.side;\n                return 0;\n              }}\n            >\n              <PreviewCard.Popup style={popupStyle}>Popup</PreviewCard.Popup>\n            </PreviewCard.Positioner>\n          </PreviewCard.Portal>\n        </PreviewCard.Root>,\n      );\n\n      // correctly flips the side in the browser\n      expect(side).toBe('inline-end');\n    });\n  });\n\n  describe.skipIf(isJSDOM)('prop: alignOffset', () => {\n    it('offsets the align when a number is specified', async () => {\n      const alignOffset = 7;\n      await render(\n        <PreviewCard.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <PreviewCard.Portal>\n            <PreviewCard.Positioner data-testid=\"positioner\" alignOffset={alignOffset}>\n              <PreviewCard.Popup style={popupStyle}>Popup</PreviewCard.Popup>\n            </PreviewCard.Positioner>\n          </PreviewCard.Portal>\n        </PreviewCard.Root>,\n      );\n\n      expect(screen.getByTestId('positioner').getBoundingClientRect()).toMatchObject({\n        x: baselineX + alignOffset,\n        y: baselineY,\n      });\n    });\n\n    it('offsets the align when a function is specified', async () => {\n      await render(\n        <PreviewCard.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <PreviewCard.Portal>\n            <PreviewCard.Positioner\n              data-testid=\"positioner\"\n              alignOffset={(data) => data.positioner.width}\n            >\n              <PreviewCard.Popup style={popupStyle}>Popup</PreviewCard.Popup>\n            </PreviewCard.Positioner>\n          </PreviewCard.Portal>\n        </PreviewCard.Root>,\n      );\n\n      expect(screen.getByTestId('positioner').getBoundingClientRect()).toMatchObject({\n        x: baselineX + popupWidth,\n        y: baselineY,\n      });\n    });\n\n    it('can read the latest side inside alignOffset', async () => {\n      let side = 'none';\n      await render(\n        <PreviewCard.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <PreviewCard.Portal>\n            <PreviewCard.Positioner\n              side=\"left\"\n              data-testid=\"positioner\"\n              alignOffset={(data) => {\n                side = data.side;\n                return 0;\n              }}\n            >\n              <PreviewCard.Popup style={popupStyle}>Popup</PreviewCard.Popup>\n            </PreviewCard.Positioner>\n          </PreviewCard.Portal>\n        </PreviewCard.Root>,\n      );\n\n      // correctly flips the side in the browser\n      expect(side).toBe('right');\n    });\n\n    it('can read the latest align inside alignOffset', async () => {\n      let align = 'none';\n      await render(\n        <PreviewCard.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <PreviewCard.Portal>\n            <PreviewCard.Positioner\n              side=\"right\"\n              align=\"start\"\n              data-testid=\"positioner\"\n              alignOffset={(data) => {\n                align = data.align;\n                return 0;\n              }}\n            >\n              <PreviewCard.Popup style={popupStyle}>Popup</PreviewCard.Popup>\n            </PreviewCard.Positioner>\n          </PreviewCard.Portal>\n        </PreviewCard.Root>,\n      );\n\n      // correctly flips the align in the browser\n      expect(align).toBe('end');\n    });\n\n    it('reads logical side inside alignOffset', async () => {\n      let side = 'none';\n      await render(\n        <PreviewCard.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <PreviewCard.Portal>\n            <PreviewCard.Positioner\n              side=\"inline-start\"\n              data-testid=\"positioner\"\n              alignOffset={(data) => {\n                side = data.side;\n                return 0;\n              }}\n            >\n              <PreviewCard.Popup style={popupStyle}>Popup</PreviewCard.Popup>\n            </PreviewCard.Positioner>\n          </PreviewCard.Portal>\n        </PreviewCard.Root>,\n      );\n\n      // correctly flips the side in the browser\n      expect(side).toBe('inline-end');\n    });\n  });\n\n  it.skipIf(isJSDOM)('uses transform positioning without Viewport', async () => {\n    await render(\n      <PreviewCard.Root open>\n        <Trigger style={triggerStyle}>Trigger</Trigger>\n        <PreviewCard.Portal>\n          <PreviewCard.Positioner data-testid=\"positioner\">\n            <PreviewCard.Popup style={popupStyle}>Popup</PreviewCard.Popup>\n          </PreviewCard.Positioner>\n        </PreviewCard.Portal>\n      </PreviewCard.Root>,\n    );\n\n    const positioner = screen.getByTestId('positioner');\n    expect(positioner.style.transform).not.toBe('');\n  });\n\n  it.skipIf(isJSDOM)('uses top/left positioning with Viewport', async () => {\n    await render(\n      <PreviewCard.Root open>\n        <Trigger style={triggerStyle}>Trigger</Trigger>\n        <PreviewCard.Portal>\n          <PreviewCard.Positioner data-testid=\"positioner\">\n            <PreviewCard.Popup style={popupStyle}>\n              <PreviewCard.Viewport>Popup</PreviewCard.Viewport>\n            </PreviewCard.Popup>\n          </PreviewCard.Positioner>\n        </PreviewCard.Portal>\n      </PreviewCard.Root>,\n    );\n\n    const positioner = screen.getByTestId('positioner');\n    await waitFor(() => {\n      expect(positioner.style.transform).toBe('');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/preview-card/positioner/PreviewCardPositioner.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { usePreviewCardRootContext } from '../root/PreviewCardContext';\nimport { PreviewCardPositionerContext } from './PreviewCardPositionerContext';\nimport { FloatingNode, useFloatingNodeId } from '../../floating-ui-react';\nimport {\n  type Side,\n  type Align,\n  useAnchorPositioning,\n  type UseAnchorPositioningSharedParameters,\n} from '../../utils/useAnchorPositioning';\nimport type { BaseUIComponentProps, HTMLProps } from '../../utils/types';\nimport { popupStateMapping } from '../../utils/popupStateMapping';\nimport { usePreviewCardPortalContext } from '../portal/PreviewCardPortalContext';\nimport { POPUP_COLLISION_AVOIDANCE } from '../../utils/constants';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { adaptiveOrigin } from '../../utils/adaptiveOriginMiddleware';\nimport { getDisabledMountTransitionStyles } from '../../utils/getDisabledMountTransitionStyles';\n\n/**\n * Positions the popup against the trigger.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Preview Card](https://base-ui.com/react/components/preview-card)\n */\nexport const PreviewCardPositioner = React.forwardRef(function PreviewCardPositioner(\n  componentProps: PreviewCardPositioner.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const {\n    render,\n    className,\n    anchor,\n    positionMethod = 'absolute',\n    side = 'bottom',\n    align = 'center',\n    sideOffset = 0,\n    alignOffset = 0,\n    collisionBoundary = 'clipping-ancestors',\n    collisionPadding = 5,\n    arrowPadding = 5,\n    sticky = false,\n    disableAnchorTracking = false,\n    collisionAvoidance = POPUP_COLLISION_AVOIDANCE,\n    ...elementProps\n  } = componentProps;\n\n  const store = usePreviewCardRootContext();\n  const keepMounted = usePreviewCardPortalContext();\n  const nodeId = useFloatingNodeId();\n\n  const open = store.useState('open');\n  const mounted = store.useState('mounted');\n  const floatingRootContext = store.useState('floatingRootContext');\n  const instantType = store.useState('instantType');\n  const transitionStatus = store.useState('transitionStatus');\n  const hasViewport = store.useState('hasViewport');\n\n  const positioning = useAnchorPositioning({\n    anchor,\n    floatingRootContext,\n    positionMethod,\n    mounted,\n    side,\n    sideOffset,\n    align,\n    alignOffset,\n    arrowPadding,\n    collisionBoundary,\n    collisionPadding,\n    sticky,\n    disableAnchorTracking,\n    keepMounted,\n    nodeId,\n    collisionAvoidance,\n    adaptiveOrigin: hasViewport ? adaptiveOrigin : undefined,\n  });\n\n  const defaultProps: HTMLProps = React.useMemo(() => {\n    const hiddenStyles: React.CSSProperties = {};\n\n    if (!open) {\n      hiddenStyles.pointerEvents = 'none';\n    }\n\n    return {\n      role: 'presentation',\n      hidden: !mounted,\n      style: {\n        ...positioning.positionerStyles,\n        ...hiddenStyles,\n      },\n    };\n  }, [open, mounted, positioning.positionerStyles]);\n\n  const state: PreviewCardPositionerState = {\n    open,\n    side: positioning.side,\n    align: positioning.align,\n    anchorHidden: positioning.anchorHidden,\n    instant: instantType,\n  };\n\n  const contextValue: PreviewCardPositionerContext = React.useMemo(\n    () => ({\n      side: positioning.side,\n      align: positioning.align,\n      arrowRef: positioning.arrowRef,\n      arrowUncentered: positioning.arrowUncentered,\n      arrowStyles: positioning.arrowStyles,\n    }),\n    [\n      positioning.side,\n      positioning.align,\n      positioning.arrowRef,\n      positioning.arrowUncentered,\n      positioning.arrowStyles,\n    ],\n  );\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    props: [defaultProps, getDisabledMountTransitionStyles(transitionStatus), elementProps],\n    ref: [forwardedRef, store.useStateSetter('positionerElement')],\n    stateAttributesMapping: popupStateMapping,\n  });\n\n  return (\n    <PreviewCardPositionerContext.Provider value={contextValue}>\n      <FloatingNode id={nodeId}>{element}</FloatingNode>\n    </PreviewCardPositionerContext.Provider>\n  );\n});\n\nexport interface PreviewCardPositionerState {\n  /**\n   * Whether the preview card is currently open.\n   */\n  open: boolean;\n  /**\n   * The side of the anchor the component is placed on.\n   */\n  side: Side;\n  /**\n   * The alignment of the component relative to the anchor.\n   */\n  align: Align;\n  /**\n   * Whether the anchor element is hidden.\n   */\n  anchorHidden: boolean;\n  /**\n   * Whether transitions should be skipped.\n   */\n  instant: 'dismiss' | 'focus' | undefined;\n}\n\nexport interface PreviewCardPositionerProps\n  extends\n    UseAnchorPositioningSharedParameters,\n    BaseUIComponentProps<'div', PreviewCardPositionerState> {}\n\nexport namespace PreviewCardPositioner {\n  export type State = PreviewCardPositionerState;\n  export type Props = PreviewCardPositionerProps;\n}\n"
  },
  {
    "path": "packages/react/src/preview-card/positioner/PreviewCardPositionerContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport type { Side, Align } from '../../utils/useAnchorPositioning';\n\nexport interface PreviewCardPositionerContext {\n  side: Side;\n  align: Align;\n  arrowRef: React.RefObject<Element | null>;\n  arrowUncentered: boolean;\n  arrowStyles: React.CSSProperties;\n}\n\nexport const PreviewCardPositionerContext = React.createContext<\n  PreviewCardPositionerContext | undefined\n>(undefined);\n\nexport function usePreviewCardPositionerContext() {\n  const context = React.useContext(PreviewCardPositionerContext);\n  if (context === undefined) {\n    throw new Error(\n      'Base UI: <PreviewCard.Popup> and <PreviewCard.Arrow> must be used within the <PreviewCard.Positioner> component',\n    );\n  }\n\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/preview-card/positioner/PreviewCardPositionerCssVars.ts",
    "content": "export enum PreviewCardPositionerCssVars {\n  /**\n   * The available width between the trigger and the edge of the viewport.\n   * @type {number}\n   */\n  availableWidth = '--available-width',\n  /**\n   * The available height between the trigger and the edge of the viewport.\n   * @type {number}\n   */\n  availableHeight = '--available-height',\n  /**\n   * The anchor's width.\n   * @type {number}\n   */\n  anchorWidth = '--anchor-width',\n  /**\n   * The anchor's height.\n   * @type {number}\n   */\n  anchorHeight = '--anchor-height',\n  /**\n   * The coordinates that this element is anchored to. Used for animations and transitions.\n   * @type {string}\n   */\n  transformOrigin = '--transform-origin',\n}\n"
  },
  {
    "path": "packages/react/src/preview-card/positioner/PreviewCardPositionerDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum PreviewCardPositionerDataAttributes {\n  /**\n   * Present when the preview card is open.\n   */\n  open = CommonPopupDataAttributes.open,\n  /**\n   * Present when the preview card is closed.\n   */\n  closed = CommonPopupDataAttributes.closed,\n  /**\n   * Present when the anchor is hidden.\n   */\n  anchorHidden = CommonPopupDataAttributes.anchorHidden,\n  /**\n   * Indicates which side the popup is positioned relative to the trigger.\n   * @type {'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'}\n   */\n  side = CommonPopupDataAttributes.side,\n  /**\n   * Indicates how the popup is aligned relative to specified side.\n   * @type {'start' | 'center' | 'end'}\n   */\n  align = CommonPopupDataAttributes.align,\n}\n"
  },
  {
    "path": "packages/react/src/preview-card/root/PreviewCardContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { PreviewCardStore } from '../store/PreviewCardStore';\n\nexport type PreviewCardRootContext<Payload = unknown> = PreviewCardStore<Payload>;\n\nexport const PreviewCardRootContext = React.createContext<PreviewCardRootContext | undefined>(\n  undefined,\n);\n\nexport function usePreviewCardRootContext(optional?: false): PreviewCardRootContext;\nexport function usePreviewCardRootContext(optional: true): PreviewCardRootContext | undefined;\nexport function usePreviewCardRootContext(optional?: boolean) {\n  const context = React.useContext(PreviewCardRootContext);\n  if (context === undefined && !optional) {\n    throw new Error(\n      'Base UI: PreviewCardRootContext is missing. PreviewCard parts must be placed within <PreviewCard.Root>.',\n    );\n  }\n\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/preview-card/root/PreviewCardRoot.detached-triggers.test.tsx",
    "content": "import { expect } from 'vitest';\nimport * as React from 'react';\nimport { createRenderer, isJSDOM } from '#test-utils';\nimport { PreviewCard } from '@base-ui/react/preview-card';\nimport { screen, waitFor, randomStringValue, act, flushMicrotasks } from '@mui/internal-test-utils';\n\ndescribe('<PreviewCard.Root />', () => {\n  beforeEach(async () => {\n    globalThis.BASE_UI_ANIMATIONS_DISABLED = true;\n  });\n\n  const { render } = createRenderer();\n\n  describe.skipIf(isJSDOM)('multiple triggers within Root', () => {\n    type NumberPayload = { payload: number | undefined };\n\n    it('should open the preview card with any trigger on hover', async () => {\n      const popupId = randomStringValue();\n      const { user } = await render(\n        <PreviewCard.Root>\n          <button type=\"button\" aria-label=\"Initial focus\" autoFocus />\n          <PreviewCard.Trigger href=\"#\" delay={0}>\n            Trigger 1\n          </PreviewCard.Trigger>\n          <PreviewCard.Trigger href=\"#\" delay={0}>\n            Trigger 2\n          </PreviewCard.Trigger>\n          <PreviewCard.Trigger href=\"#\" delay={0}>\n            Trigger 3\n          </PreviewCard.Trigger>\n\n          <PreviewCard.Portal>\n            <PreviewCard.Positioner>\n              <PreviewCard.Popup data-testid={popupId}>Content</PreviewCard.Popup>\n            </PreviewCard.Positioner>\n          </PreviewCard.Portal>\n        </PreviewCard.Root>,\n      );\n\n      const trigger1 = screen.getByRole('link', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('link', { name: 'Trigger 2' });\n      const trigger3 = screen.getByRole('link', { name: 'Trigger 3' });\n\n      await waitFor(() => {\n        expect(screen.queryByTestId(popupId)).toBe(null);\n      });\n\n      await user.hover(trigger1);\n      expect(screen.queryByTestId(popupId)).toBeVisible();\n      await user.hover(document.body);\n      await waitFor(() => {\n        expect(screen.queryByTestId(popupId)).toBe(null);\n      });\n\n      await user.hover(trigger2);\n      expect(screen.queryByTestId(popupId)).toBeVisible();\n      await user.hover(document.body);\n      await waitFor(() => {\n        expect(screen.queryByTestId(popupId)).toBe(null);\n      });\n\n      await user.hover(trigger3);\n      expect(screen.queryByTestId(popupId)).toBeVisible();\n      await user.hover(document.body);\n      await waitFor(() => {\n        expect(screen.queryByTestId(popupId)).toBe(null);\n      });\n    });\n\n    it('should open the preview card immediately when hovering another trigger', async () => {\n      const popupId = randomStringValue();\n      const { user } = await render(\n        <PreviewCard.Root>\n          {({ payload }: NumberPayload) => (\n            <React.Fragment>\n              <button type=\"button\" aria-label=\"Initial focus\" autoFocus />\n              <PreviewCard.Trigger href=\"#\" delay={0} payload={1}>\n                Trigger 1\n              </PreviewCard.Trigger>\n\n              {/* delay should be ignored when moving from already active trigger */}\n              <PreviewCard.Trigger href=\"#\" delay={2000} payload={2}>\n                Trigger 2\n              </PreviewCard.Trigger>\n\n              <PreviewCard.Portal>\n                <PreviewCard.Positioner>\n                  <PreviewCard.Popup data-testid={popupId}>Content: {payload}</PreviewCard.Popup>\n                </PreviewCard.Positioner>\n              </PreviewCard.Portal>\n            </React.Fragment>\n          )}\n        </PreviewCard.Root>,\n      );\n\n      const trigger1 = screen.getByRole('link', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('link', { name: 'Trigger 2' });\n\n      await user.hover(trigger1);\n      expect(screen.queryByTestId(popupId)).toBeVisible();\n      expect(screen.getByTestId(popupId).textContent).toBe('Content: 1');\n\n      await user.hover(trigger2);\n      expect(screen.queryByTestId(popupId)).toBeVisible();\n      expect(screen.getByTestId(popupId).textContent).toBe('Content: 2');\n    });\n\n    it('should open the preview card with any trigger on focus', async () => {\n      await render(\n        <PreviewCard.Root>\n          <button type=\"button\" aria-label=\"Initial focus\" autoFocus />\n          <PreviewCard.Trigger href=\"#\" delay={0}>\n            Trigger 1\n          </PreviewCard.Trigger>\n          <PreviewCard.Trigger href=\"#\" delay={0}>\n            Trigger 2\n          </PreviewCard.Trigger>\n          <PreviewCard.Trigger href=\"#\" delay={0}>\n            Trigger 3\n          </PreviewCard.Trigger>\n\n          <PreviewCard.Portal>\n            <PreviewCard.Positioner>\n              <PreviewCard.Popup>Content</PreviewCard.Popup>\n            </PreviewCard.Positioner>\n          </PreviewCard.Portal>\n        </PreviewCard.Root>,\n      );\n\n      const trigger1 = screen.getByRole('link', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('link', { name: 'Trigger 2' });\n      const trigger3 = screen.getByRole('link', { name: 'Trigger 3' });\n\n      expect(screen.queryByText('Content')).toBe(null);\n\n      await act(async () => trigger1.focus());\n      await flushMicrotasks();\n      expect(screen.getByText('Content')).toBeVisible();\n      await act(async () => trigger1.blur());\n      expect(screen.queryByText('Content')).toBe(null);\n\n      await act(async () => trigger2.focus());\n      await flushMicrotasks();\n      expect(screen.getByText('Content')).toBeVisible();\n      await act(async () => trigger2.blur());\n      expect(screen.queryByText('Content')).toBe(null);\n\n      await act(async () => trigger3.focus());\n      await flushMicrotasks();\n      expect(screen.getByText('Content')).toBeVisible();\n      await act(async () => trigger3.blur());\n      expect(screen.queryByText('Content')).toBe(null);\n    });\n\n    it('should open again after escape when focusing another trigger', async () => {\n      const popupId = randomStringValue();\n      const { user } = await render(\n        <PreviewCard.Root>\n          <button type=\"button\" aria-label=\"Initial focus\" autoFocus />\n          <PreviewCard.Trigger href=\"#\" delay={0}>\n            Trigger 1\n          </PreviewCard.Trigger>\n          <PreviewCard.Trigger href=\"#\" delay={0}>\n            Trigger 2\n          </PreviewCard.Trigger>\n\n          <PreviewCard.Portal>\n            <PreviewCard.Positioner>\n              <PreviewCard.Popup data-testid={popupId}>Content</PreviewCard.Popup>\n            </PreviewCard.Positioner>\n          </PreviewCard.Portal>\n        </PreviewCard.Root>,\n      );\n\n      const trigger1 = screen.getByRole('link', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('link', { name: 'Trigger 2' });\n\n      await act(async () => trigger1.focus());\n      await waitFor(() => {\n        expect(screen.queryByTestId(popupId)).toBeVisible();\n      });\n\n      await user.keyboard('{Escape}');\n      await waitFor(() => {\n        expect(screen.queryByTestId(popupId)).toBe(null);\n      });\n\n      await act(async () => trigger2.focus());\n      await waitFor(() => {\n        expect(screen.queryByTestId(popupId)).toBeVisible();\n      });\n    });\n\n    it('should switch immediately when focusing another trigger while open', async () => {\n      await render(\n        <PreviewCard.Root>\n          {({ payload }: NumberPayload) => (\n            <React.Fragment>\n              <button type=\"button\" aria-label=\"Initial focus\" autoFocus />\n              <PreviewCard.Trigger href=\"#\" payload={1} delay={0}>\n                Trigger 1\n              </PreviewCard.Trigger>\n              <PreviewCard.Trigger href=\"#\" payload={2} delay={2000}>\n                Trigger 2\n              </PreviewCard.Trigger>\n\n              <PreviewCard.Portal>\n                <PreviewCard.Positioner>\n                  <PreviewCard.Popup>\n                    <span data-testid=\"content\">{payload}</span>\n                  </PreviewCard.Popup>\n                </PreviewCard.Positioner>\n              </PreviewCard.Portal>\n            </React.Fragment>\n          )}\n        </PreviewCard.Root>,\n      );\n\n      const trigger1 = screen.getByRole('link', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('link', { name: 'Trigger 2' });\n\n      await act(async () => trigger1.focus());\n      await flushMicrotasks();\n      await waitFor(() => {\n        expect(screen.getByTestId('content').textContent).toBe('1');\n      });\n\n      await act(async () => trigger2.focus());\n      await flushMicrotasks();\n      expect(screen.getByTestId('content').textContent).toBe('2');\n    });\n\n    it('should set the payload and render content based on its value', async () => {\n      const { user } = await render(\n        <PreviewCard.Root>\n          {({ payload }: NumberPayload) => (\n            <React.Fragment>\n              <button type=\"button\" aria-label=\"Initial focus\" autoFocus />\n              <PreviewCard.Trigger href=\"#\" payload={1} delay={0}>\n                Trigger 1\n              </PreviewCard.Trigger>\n              <PreviewCard.Trigger href=\"#\" payload={2} delay={0}>\n                Trigger 2\n              </PreviewCard.Trigger>\n\n              <PreviewCard.Portal>\n                <PreviewCard.Positioner>\n                  <PreviewCard.Popup>\n                    <span data-testid=\"content\">{payload}</span>\n                  </PreviewCard.Popup>\n                </PreviewCard.Positioner>\n              </PreviewCard.Portal>\n            </React.Fragment>\n          )}\n        </PreviewCard.Root>,\n      );\n\n      const trigger1 = screen.getByRole('link', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('link', { name: 'Trigger 2' });\n\n      await user.hover(trigger1);\n      expect(screen.getByTestId('content').textContent).toBe('1');\n\n      await user.unhover(trigger1);\n      await user.hover(trigger2);\n      expect(screen.getByTestId('content').textContent).toBe('2');\n    });\n\n    it('should reuse the popup and positioner DOM nodes when switching triggers', async () => {\n      await render(\n        <PreviewCard.Root>\n          {({ payload }: NumberPayload) => (\n            <React.Fragment>\n              <button type=\"button\" aria-label=\"Initial focus\" autoFocus />\n              <PreviewCard.Trigger href=\"#\" payload={1} delay={0}>\n                Trigger 1\n              </PreviewCard.Trigger>\n              <PreviewCard.Trigger href=\"#\" payload={2} delay={0}>\n                Trigger 2\n              </PreviewCard.Trigger>\n\n              <PreviewCard.Portal>\n                <PreviewCard.Positioner data-testid=\"positioner\" key=\"pos\">\n                  <PreviewCard.Popup data-testid=\"popup\" key=\"pop\">\n                    <span>{payload}</span>\n                  </PreviewCard.Popup>\n                </PreviewCard.Positioner>\n              </PreviewCard.Portal>\n            </React.Fragment>\n          )}\n        </PreviewCard.Root>,\n      );\n\n      const trigger1 = screen.getByRole('link', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('link', { name: 'Trigger 2' });\n\n      await act(async () => trigger1.focus());\n      const popupElement = screen.getByTestId('popup');\n      const positionerElement = screen.getByTestId('positioner');\n\n      await act(async () => trigger2.focus());\n      expect(screen.getByTestId('positioner')).toBe(positionerElement);\n      expect(screen.getByTestId('popup')).toBe(popupElement);\n    });\n\n    it('should allow controlling the preview card state programmatically', async () => {\n      function Test() {\n        const [open, setOpen] = React.useState(false);\n        const [activeTrigger, setActiveTrigger] = React.useState<string | null>(null);\n\n        return (\n          <div>\n            <button type=\"button\" aria-label=\"Initial focus\" autoFocus />\n            <PreviewCard.Root\n              open={open}\n              triggerId={activeTrigger}\n              onOpenChange={(nextOpen, details) => {\n                setActiveTrigger(details.trigger?.id ?? null);\n                setOpen(nextOpen);\n              }}\n            >\n              {({ payload }: NumberPayload) => (\n                <React.Fragment>\n                  <PreviewCard.Trigger href=\"#\" payload={1} id=\"trigger-1\" delay={0}>\n                    Trigger 1\n                  </PreviewCard.Trigger>\n                  <PreviewCard.Trigger href=\"#\" payload={2} id=\"trigger-2\" delay={0}>\n                    Trigger 2\n                  </PreviewCard.Trigger>\n\n                  <PreviewCard.Portal>\n                    <PreviewCard.Positioner>\n                      <PreviewCard.Popup>\n                        <span data-testid=\"content\">{payload as number}</span>\n                      </PreviewCard.Popup>\n                    </PreviewCard.Positioner>\n                  </PreviewCard.Portal>\n                </React.Fragment>\n              )}\n            </PreviewCard.Root>\n            <button\n              onClick={() => {\n                setOpen(true);\n                setActiveTrigger('trigger-1');\n              }}\n            >\n              Open Trigger 1\n            </button>\n            <button\n              onClick={() => {\n                setOpen(true);\n                setActiveTrigger('trigger-2');\n              }}\n            >\n              Open Trigger 2\n            </button>\n            <button onClick={() => setOpen(false)}>Close</button>\n          </div>\n        );\n      }\n\n      const { user } = await render(<Test />);\n      await user.click(screen.getByRole('button', { name: 'Open Trigger 1' }));\n      expect(screen.getByTestId('content').textContent).toBe('1');\n      await user.click(screen.getByRole('button', { name: 'Open Trigger 2' }));\n      expect(screen.getByTestId('content').textContent).toBe('2');\n      await user.click(screen.getByRole('button', { name: 'Close' }));\n      expect(screen.queryByTestId('content')).toBe(null);\n    });\n\n    it('allows setting an initially open preview card', async () => {\n      const testPreviewCard = PreviewCard.createHandle<number>();\n      const triggerId = randomStringValue();\n      await render(\n        <PreviewCard.Root handle={testPreviewCard} defaultOpen defaultTriggerId={triggerId}>\n          {({ payload }: NumberPayload) => (\n            <React.Fragment>\n              <button type=\"button\" aria-label=\"Initial focus\" autoFocus />\n              <PreviewCard.Trigger href=\"#\" handle={testPreviewCard} payload={1}>\n                Trigger 1\n              </PreviewCard.Trigger>\n              <PreviewCard.Trigger href=\"#\" handle={testPreviewCard} payload={2} id={triggerId}>\n                Trigger 2\n              </PreviewCard.Trigger>\n              <PreviewCard.Portal>\n                <PreviewCard.Positioner>\n                  <PreviewCard.Popup data-testid=\"popup\">\n                    <span>{payload}</span>\n                  </PreviewCard.Popup>\n                </PreviewCard.Positioner>\n              </PreviewCard.Portal>\n            </React.Fragment>\n          )}\n        </PreviewCard.Root>,\n      );\n\n      await waitFor(() => {\n        expect(screen.getByTestId('popup').textContent).toBe('2');\n      });\n    });\n  });\n\n  describe.skipIf(isJSDOM)('multiple detached triggers', () => {\n    type NumberPayload = { payload: number | undefined };\n\n    it('should open the preview card with any trigger on hover', async () => {\n      const testPreviewCard = PreviewCard.createHandle();\n      const popupId = randomStringValue();\n      const { user } = await render(\n        <div>\n          <button type=\"button\" aria-label=\"Initial focus\" autoFocus />\n          <PreviewCard.Trigger href=\"#\" handle={testPreviewCard} delay={0}>\n            Trigger 1\n          </PreviewCard.Trigger>\n          <PreviewCard.Trigger href=\"#\" handle={testPreviewCard} delay={0}>\n            Trigger 2\n          </PreviewCard.Trigger>\n          <PreviewCard.Trigger href=\"#\" handle={testPreviewCard} delay={0}>\n            Trigger 3\n          </PreviewCard.Trigger>\n\n          <PreviewCard.Root handle={testPreviewCard}>\n            <PreviewCard.Portal>\n              <PreviewCard.Positioner>\n                <PreviewCard.Popup data-testid={popupId}>Content</PreviewCard.Popup>\n              </PreviewCard.Positioner>\n            </PreviewCard.Portal>\n          </PreviewCard.Root>\n        </div>,\n      );\n\n      const trigger1 = screen.getByRole('link', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('link', { name: 'Trigger 2' });\n      const trigger3 = screen.getByRole('link', { name: 'Trigger 3' });\n\n      await waitFor(() => {\n        expect(screen.queryByTestId(popupId)).toBe(null);\n      });\n\n      await user.hover(trigger1);\n      await waitFor(() => {\n        expect(screen.queryByTestId(popupId)).toBeVisible();\n      });\n      await user.unhover(trigger1);\n      await waitFor(() => {\n        expect(screen.queryByTestId(popupId)).toBe(null);\n      });\n\n      await user.hover(trigger2);\n      await waitFor(() => {\n        expect(screen.queryByTestId(popupId)).toBeVisible();\n      });\n      await user.unhover(trigger2);\n      await waitFor(() => {\n        expect(screen.queryByTestId(popupId)).toBe(null);\n      });\n\n      await user.hover(trigger3);\n      await waitFor(() => {\n        expect(screen.queryByTestId(popupId)).toBeVisible();\n      });\n      await user.unhover(trigger3);\n      await waitFor(() => {\n        expect(screen.queryByTestId(popupId)).toBe(null);\n      });\n    });\n\n    it('should open the preview card with any trigger on focus', async () => {\n      const testPreviewCard = PreviewCard.createHandle();\n      await render(\n        <div>\n          <button type=\"button\" aria-label=\"Initial focus\" autoFocus />\n          <PreviewCard.Trigger href=\"#\" handle={testPreviewCard} delay={0}>\n            Trigger 1\n          </PreviewCard.Trigger>\n          <PreviewCard.Trigger href=\"#\" handle={testPreviewCard} delay={0}>\n            Trigger 2\n          </PreviewCard.Trigger>\n          <PreviewCard.Trigger href=\"#\" handle={testPreviewCard} delay={0}>\n            Trigger 3\n          </PreviewCard.Trigger>\n\n          <PreviewCard.Root handle={testPreviewCard}>\n            <PreviewCard.Portal>\n              <PreviewCard.Positioner>\n                <PreviewCard.Popup>Content</PreviewCard.Popup>\n              </PreviewCard.Positioner>\n            </PreviewCard.Portal>\n          </PreviewCard.Root>\n        </div>,\n      );\n\n      const trigger1 = screen.getByRole('link', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('link', { name: 'Trigger 2' });\n      const trigger3 = screen.getByRole('link', { name: 'Trigger 3' });\n\n      expect(screen.queryByText('Content')).toBe(null);\n\n      await act(async () => trigger1.focus());\n      await flushMicrotasks();\n      expect(screen.getByText('Content')).toBeVisible();\n      await act(async () => trigger1.blur());\n      expect(screen.queryByText('Content')).toBe(null);\n\n      await act(async () => trigger2.focus());\n      await flushMicrotasks();\n      expect(screen.getByText('Content')).toBeVisible();\n      await act(async () => trigger2.blur());\n      expect(screen.queryByText('Content')).toBe(null);\n\n      await act(async () => trigger3.focus());\n      await flushMicrotasks();\n      expect(screen.getByText('Content')).toBeVisible();\n      await act(async () => trigger3.blur());\n      expect(screen.queryByText('Content')).toBe(null);\n    });\n\n    it('should set the payload and render content based on its value', async () => {\n      const testPreviewCard = PreviewCard.createHandle<number>();\n      const { user } = await render(\n        <div>\n          <button type=\"button\" aria-label=\"Initial focus\" autoFocus />\n          <PreviewCard.Trigger href=\"#\" handle={testPreviewCard} payload={1} delay={0}>\n            Trigger 1\n          </PreviewCard.Trigger>\n          <PreviewCard.Trigger href=\"#\" handle={testPreviewCard} payload={2} delay={0}>\n            Trigger 2\n          </PreviewCard.Trigger>\n\n          <PreviewCard.Root handle={testPreviewCard}>\n            {({ payload }: NumberPayload) => (\n              <PreviewCard.Portal>\n                <PreviewCard.Positioner>\n                  <PreviewCard.Popup>\n                    <span data-testid=\"content\">{payload}</span>\n                  </PreviewCard.Popup>\n                </PreviewCard.Positioner>\n              </PreviewCard.Portal>\n            )}\n          </PreviewCard.Root>\n        </div>,\n      );\n\n      const trigger1 = screen.getByRole('link', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('link', { name: 'Trigger 2' });\n\n      await user.hover(trigger1);\n      expect(screen.getByTestId('content').textContent).toBe('1');\n\n      await user.unhover(trigger1);\n      await user.hover(trigger2);\n      expect(screen.getByTestId('content').textContent).toBe('2');\n    });\n\n    it('should reuse the popup and positioner DOM nodes when switching triggers', async () => {\n      const testPreviewCard = PreviewCard.createHandle<number>();\n      await render(\n        <React.Fragment>\n          <button type=\"button\" aria-label=\"Initial focus\" autoFocus />\n          <PreviewCard.Trigger href=\"#\" handle={testPreviewCard} payload={1} delay={0}>\n            Trigger 1\n          </PreviewCard.Trigger>\n          <PreviewCard.Trigger href=\"#\" handle={testPreviewCard} payload={2} delay={0}>\n            Trigger 2\n          </PreviewCard.Trigger>\n\n          <PreviewCard.Root handle={testPreviewCard}>\n            {({ payload }: NumberPayload) => (\n              <PreviewCard.Portal>\n                <PreviewCard.Positioner data-testid=\"positioner\">\n                  <PreviewCard.Popup data-testid=\"popup\">\n                    <span>{payload}</span>\n                  </PreviewCard.Popup>\n                </PreviewCard.Positioner>\n              </PreviewCard.Portal>\n            )}\n          </PreviewCard.Root>\n        </React.Fragment>,\n      );\n\n      const trigger1 = screen.getByRole('link', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('link', { name: 'Trigger 2' });\n\n      await act(async () => trigger1.focus());\n      const popupElement = screen.getByTestId('popup');\n      const positionerElement = screen.getByTestId('positioner');\n\n      await act(async () => trigger2.focus());\n      expect(screen.getByTestId('popup')).toBe(popupElement);\n      expect(screen.getByTestId('positioner')).toBe(positionerElement);\n    });\n\n    it('should allow controlling the preview card state programmatically', async () => {\n      const testPreviewCard = PreviewCard.createHandle<number>();\n      function Test() {\n        const [open, setOpen] = React.useState(false);\n        const [activeTrigger, setActiveTrigger] = React.useState<string | null>(null);\n\n        return (\n          <div style={{ margin: 50 }}>\n            <button type=\"button\" aria-label=\"Initial focus\" autoFocus />\n            <PreviewCard.Trigger\n              href=\"#\"\n              handle={testPreviewCard}\n              payload={1}\n              id=\"trigger-1\"\n              delay={0}\n            >\n              Trigger 1\n            </PreviewCard.Trigger>\n            <PreviewCard.Trigger\n              href=\"#\"\n              handle={testPreviewCard}\n              payload={2}\n              id=\"trigger-2\"\n              delay={0}\n            >\n              Trigger 2\n            </PreviewCard.Trigger>\n\n            <PreviewCard.Root\n              open={open}\n              onOpenChange={(nextOpen, details) => {\n                setActiveTrigger(details.trigger?.id ?? null);\n                setOpen(nextOpen);\n              }}\n              triggerId={activeTrigger}\n              handle={testPreviewCard}\n            >\n              {({ payload }: NumberPayload) => (\n                <PreviewCard.Portal>\n                  <PreviewCard.Positioner data-testid=\"positioner\" side=\"bottom\" align=\"start\">\n                    <PreviewCard.Popup>\n                      <span data-testid=\"content\">{payload}</span>\n                    </PreviewCard.Popup>\n                  </PreviewCard.Positioner>\n                </PreviewCard.Portal>\n              )}\n            </PreviewCard.Root>\n\n            <button\n              onClick={() => {\n                setOpen(true);\n                setActiveTrigger('trigger-1');\n              }}\n            >\n              Open Trigger 1\n            </button>\n            <button\n              onClick={() => {\n                setOpen(true);\n                setActiveTrigger('trigger-2');\n              }}\n            >\n              Open Trigger 2\n            </button>\n            <button onClick={() => setOpen(false)}>Close</button>\n          </div>\n        );\n      }\n\n      const { user } = await render(<Test />);\n\n      const trigger1 = screen.getByRole('link', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('link', { name: 'Trigger 2' });\n\n      await user.click(screen.getByRole('button', { name: 'Open Trigger 1' }));\n      expect(screen.getByTestId('content').textContent).toBe('1');\n\n      await waitFor(() => {\n        expect(\n          Math.abs(\n            screen.getByTestId('positioner').getBoundingClientRect().left -\n              trigger1.getBoundingClientRect().left,\n          ),\n        ).toBeLessThanOrEqual(1);\n      });\n\n      await user.click(screen.getByRole('button', { name: 'Open Trigger 2' }));\n      expect(screen.getByTestId('content').textContent).toBe('2');\n      await waitFor(() => {\n        expect(\n          Math.abs(\n            screen.getByTestId('positioner').getBoundingClientRect().left -\n              trigger2.getBoundingClientRect().left,\n          ),\n        ).toBeLessThanOrEqual(1);\n      });\n\n      await user.click(screen.getByRole('button', { name: 'Close' }));\n      expect(screen.queryByTestId('content')).toBe(null);\n    });\n\n    it('allows setting an initially open preview card', async () => {\n      const testPreviewCard = PreviewCard.createHandle<number>();\n      const triggerId = randomStringValue();\n      await render(\n        <React.Fragment>\n          <button type=\"button\" aria-label=\"Initial focus\" autoFocus />\n          <PreviewCard.Trigger href=\"#\" handle={testPreviewCard} payload={1}>\n            Trigger 1\n          </PreviewCard.Trigger>\n          <PreviewCard.Trigger href=\"#\" handle={testPreviewCard} payload={2} id={triggerId}>\n            Trigger 2\n          </PreviewCard.Trigger>\n\n          <PreviewCard.Root handle={testPreviewCard} defaultOpen defaultTriggerId={triggerId}>\n            {({ payload }: NumberPayload) => (\n              <PreviewCard.Portal>\n                <PreviewCard.Positioner>\n                  <PreviewCard.Popup data-testid=\"popup\">\n                    <span>{payload}</span>\n                  </PreviewCard.Popup>\n                </PreviewCard.Positioner>\n              </PreviewCard.Portal>\n            )}\n          </PreviewCard.Root>\n        </React.Fragment>,\n      );\n\n      await waitFor(() => {\n        expect(screen.getByTestId('popup').textContent).toBe('2');\n      });\n    });\n\n    it('should not have inline scale style after switching triggers', async () => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n      const testPreviewCard = PreviewCard.createHandle<number>();\n\n      function Test() {\n        return (\n          <React.Fragment>\n            <button type=\"button\" aria-label=\"Initial focus\" autoFocus />\n            <PreviewCard.Trigger href=\"#\" handle={testPreviewCard} payload={1} delay={0}>\n              Trigger 1\n            </PreviewCard.Trigger>\n            <PreviewCard.Trigger href=\"#\" handle={testPreviewCard} payload={2} delay={0}>\n              Trigger 2\n            </PreviewCard.Trigger>\n\n            <PreviewCard.Root handle={testPreviewCard}>\n              {({ payload }: NumberPayload) => (\n                <PreviewCard.Portal>\n                  <PreviewCard.Positioner>\n                    <PreviewCard.Popup data-testid=\"popup\">\n                      <PreviewCard.Viewport>\n                        <span data-testid=\"content\">{payload}</span>\n                      </PreviewCard.Viewport>\n                    </PreviewCard.Popup>\n                  </PreviewCard.Positioner>\n                </PreviewCard.Portal>\n              )}\n            </PreviewCard.Root>\n          </React.Fragment>\n        );\n      }\n\n      const { user } = await render(<Test />);\n\n      const trigger1 = screen.getByRole('link', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('link', { name: 'Trigger 2' });\n\n      // Open with Trigger 1\n      await user.hover(trigger1);\n      await waitFor(() => {\n        expect(screen.getByTestId('content').textContent).toBe('1');\n      });\n\n      // Switch to Trigger 2\n      await user.unhover(trigger1);\n      await user.hover(trigger2);\n      await waitFor(() => {\n        expect(screen.getByTestId('content').textContent).toBe('2');\n      });\n\n      // The popup should not have an inline scale style that would override CSS transitions\n      const popup = screen.getByTestId('popup');\n      expect(popup.style.scale).toBe('');\n    });\n  });\n\n  describe.skipIf(isJSDOM)('imperative actions on the handle', () => {\n    it('opens and closes the preview card', async () => {\n      const handle = PreviewCard.createHandle();\n      await render(\n        <div>\n          <button type=\"button\" aria-label=\"Initial focus\" autoFocus />\n          <PreviewCard.Trigger href=\"#\" handle={handle} id=\"trigger\">\n            Trigger\n          </PreviewCard.Trigger>\n          <PreviewCard.Root handle={handle}>\n            <PreviewCard.Portal>\n              <PreviewCard.Positioner>\n                <PreviewCard.Popup data-testid=\"content\">Content</PreviewCard.Popup>\n              </PreviewCard.Positioner>\n            </PreviewCard.Portal>\n          </PreviewCard.Root>\n        </div>,\n      );\n\n      const trigger = screen.getByRole('link', { name: 'Trigger' });\n      expect(screen.queryByTestId('content')).toBe(null);\n\n      await act(() => handle.open('trigger'));\n      await waitFor(() => {\n        expect(screen.queryByTestId('content')).not.toBe(null);\n      });\n\n      expect(screen.getByTestId('content').textContent).toBe('Content');\n      expect(trigger).toHaveAttribute('data-popup-open');\n\n      await act(() => handle.close());\n      await waitFor(() => {\n        expect(screen.queryByTestId('content')).toBe(null);\n      });\n\n      expect(trigger).not.toHaveAttribute('data-popup-open');\n    });\n\n    it('sets the payload associated with the trigger', async () => {\n      const handle = PreviewCard.createHandle<number>();\n      await render(\n        <div>\n          <button type=\"button\" aria-label=\"Initial focus\" autoFocus />\n          <PreviewCard.Trigger href=\"#\" handle={handle} id=\"trigger1\" payload={1}>\n            Trigger 1\n          </PreviewCard.Trigger>\n          <PreviewCard.Trigger href=\"#\" handle={handle} id=\"trigger2\" payload={2}>\n            Trigger 2\n          </PreviewCard.Trigger>\n          <PreviewCard.Root handle={handle}>\n            {({ payload }: { payload: number | undefined }) => (\n              <PreviewCard.Portal>\n                <PreviewCard.Positioner>\n                  <PreviewCard.Popup data-testid=\"content\">{payload}</PreviewCard.Popup>\n                </PreviewCard.Positioner>\n              </PreviewCard.Portal>\n            )}\n          </PreviewCard.Root>\n        </div>,\n      );\n\n      const trigger1 = screen.getByRole('link', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('link', { name: 'Trigger 2' });\n      expect(screen.queryByTestId('content')).toBe(null);\n\n      await act(() => handle.open('trigger2'));\n      await waitFor(() => {\n        expect(screen.queryByTestId('content')).not.toBe(null);\n      });\n\n      expect(screen.getByTestId('content').textContent).toBe('2');\n      expect(trigger2).toHaveAttribute('data-popup-open');\n      expect(trigger1).not.toHaveAttribute('data-popup-open');\n\n      await act(() => handle.close());\n      await waitFor(() => {\n        expect(screen.queryByTestId('content')).toBe(null);\n      });\n\n      expect(trigger2).not.toHaveAttribute('data-popup-open');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/preview-card/root/PreviewCardRoot.spec.tsx",
    "content": "import { expectType } from '#test-utils';\nimport { PreviewCard } from '@base-ui/react/preview-card';\n\nconst numberPayloadHandle = PreviewCard.createHandle<number>();\n\nconst rootWithDirectChildren = (\n  <PreviewCard.Root handle={numberPayloadHandle}>\n    <PreviewCard.Portal />\n  </PreviewCard.Root>\n);\n\nconst rootWithFunctionChildren = (\n  <PreviewCard.Root handle={numberPayloadHandle}>\n    {({ payload }) => {\n      expectType<number | undefined, typeof payload>(payload);\n      return null;\n    }}\n  </PreviewCard.Root>\n);\n\nconst triggerWithPayload = <PreviewCard.Trigger handle={numberPayloadHandle} payload={42} />;\nconst triggerWithoutPayload = <PreviewCard.Trigger handle={numberPayloadHandle} />;\n\nconst triggerWithInvalidPayload = (\n  // @ts-expect-error\n  <PreviewCard.Trigger handle={numberPayloadHandle} payload={'invalid'} />\n);\n"
  },
  {
    "path": "packages/react/src/preview-card/root/PreviewCardRoot.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { PreviewCard } from '@base-ui/react/preview-card';\nimport { act, fireEvent, screen, flushMicrotasks, waitFor } from '@mui/internal-test-utils';\nimport { useRefWithInit } from '@base-ui/utils/useRefWithInit';\nimport { createRenderer, isJSDOM, popupConformanceTests } from '#test-utils';\nimport { CLOSE_DELAY, OPEN_DELAY } from '../utils/constants';\n\ndescribe('<PreviewCard.Root />', () => {\n  beforeEach(() => {\n    globalThis.BASE_UI_ANIMATIONS_DISABLED = true;\n  });\n\n  const { render, clock } = createRenderer();\n\n  popupConformanceTests({\n    createComponent: (props) => (\n      <PreviewCard.Root {...props.root}>\n        <PreviewCard.Trigger href=\"#\" {...props.trigger}>\n          Link\n        </PreviewCard.Trigger>\n        <PreviewCard.Portal {...props.portal}>\n          <PreviewCard.Positioner>\n            <PreviewCard.Popup {...props.popup}>Content</PreviewCard.Popup>\n          </PreviewCard.Positioner>\n        </PreviewCard.Portal>\n      </PreviewCard.Root>\n    ),\n    render,\n    triggerMouseAction: 'hover',\n  });\n\n  describe.for([\n    { name: 'contained triggers', Component: ContainedTriggerPreviewCard },\n    { name: 'detached triggers', Component: DetachedTriggerPreviewCard },\n    { name: 'multiple detached triggers', Component: MultipleDetachedTriggersPreviewCard },\n  ])('when using $name', ({ Component: TestPreviewCard }) => {\n    describe('uncontrolled open', () => {\n      clock.withFakeTimers();\n\n      it('should open when the trigger is hovered', async () => {\n        await render(<TestPreviewCard />);\n\n        const trigger = screen.getByRole('link', { name: 'Link' });\n\n        fireEvent.pointerDown(trigger, { pointerType: 'mouse' });\n        fireEvent.mouseEnter(trigger);\n        fireEvent.mouseMove(trigger);\n\n        clock.tick(OPEN_DELAY);\n\n        await flushMicrotasks();\n\n        expect(screen.getByText('Content')).not.toBe(null);\n      });\n\n      it('should close when the trigger is unhovered', async () => {\n        await render(<TestPreviewCard />);\n\n        const trigger = screen.getByRole('link', { name: 'Link' });\n\n        fireEvent.pointerDown(trigger, { pointerType: 'mouse' });\n        fireEvent.mouseEnter(trigger);\n        fireEvent.mouseMove(trigger);\n\n        clock.tick(OPEN_DELAY);\n\n        await flushMicrotasks();\n\n        fireEvent.mouseLeave(trigger);\n\n        clock.tick(CLOSE_DELAY);\n\n        expect(screen.queryByText('Content')).toBe(null);\n      });\n\n      it('should open when the trigger is focused', async () => {\n        if (!isJSDOM) {\n          // Ignore due to `:focus-visible` being required in the browser.\n          return;\n        }\n\n        await render(<TestPreviewCard />);\n\n        const trigger = screen.getByRole('link', { name: 'Link' });\n\n        await act(async () => trigger.focus());\n\n        clock.tick(OPEN_DELAY);\n\n        await flushMicrotasks();\n\n        expect(screen.getByText('Content')).not.toBe(null);\n      });\n\n      it('should close when the trigger is blurred', async () => {\n        await render(<TestPreviewCard />);\n\n        const trigger = screen.getByRole('link', { name: 'Link' });\n\n        await act(async () => trigger.focus());\n        clock.tick(OPEN_DELAY);\n        await flushMicrotasks();\n\n        await act(async () => trigger.blur());\n        clock.tick(CLOSE_DELAY);\n\n        expect(screen.queryByText('Content')).toBe(null);\n      });\n    });\n\n    describe('prop: onOpenChange', () => {\n      clock.withFakeTimers();\n\n      it('should call onOpenChange when the open state changes', async () => {\n        const handleChange = vi.fn();\n\n        function App() {\n          const [open, setOpen] = React.useState(false);\n\n          return (\n            <TestPreviewCard\n              rootProps={{\n                open,\n                onOpenChange: (nextOpen) => {\n                  handleChange(open);\n                  setOpen(nextOpen);\n                },\n              }}\n            />\n          );\n        }\n\n        await render(<App />);\n\n        expect(screen.queryByText('Content')).toBe(null);\n\n        const trigger = screen.getByRole('link', { name: 'Link' });\n\n        fireEvent.mouseEnter(trigger);\n        fireEvent.mouseMove(trigger);\n\n        clock.tick(OPEN_DELAY);\n\n        await flushMicrotasks();\n\n        expect(screen.getByText('Content')).not.toBe(null);\n\n        fireEvent.mouseLeave(trigger);\n\n        clock.tick(CLOSE_DELAY);\n\n        expect(screen.queryByText('Content')).toBe(null);\n        expect(handleChange.mock.calls.length).toBe(2);\n        expect(handleChange.mock.calls[0][0]).toBe(false);\n        expect(handleChange.mock.calls[1][0]).toBe(true);\n      });\n\n      it('should not call onChange when the open state does not change', async () => {\n        const handleChange = vi.fn();\n\n        function App() {\n          const [open, setOpen] = React.useState(false);\n\n          return (\n            <TestPreviewCard\n              rootProps={{\n                open,\n                onOpenChange: (nextOpen) => {\n                  handleChange(open);\n                  setOpen(nextOpen);\n                },\n              }}\n            />\n          );\n        }\n\n        await render(<App />);\n\n        expect(screen.queryByText('Content')).toBe(null);\n\n        const trigger = screen.getByRole('link', { name: 'Link' });\n\n        fireEvent.mouseEnter(trigger);\n        fireEvent.mouseMove(trigger);\n\n        clock.tick(OPEN_DELAY);\n\n        await flushMicrotasks();\n\n        expect(screen.getByText('Content')).not.toBe(null);\n        expect(handleChange.mock.calls.length).toBe(1);\n        expect(handleChange.mock.calls[0][0]).toBe(false);\n      });\n    });\n\n    describe('prop: defaultOpen', () => {\n      clock.withFakeTimers();\n\n      it('should open when the component is rendered', async () => {\n        await render(\n          <TestPreviewCard\n            rootProps={{\n              defaultOpen: true,\n            }}\n          />,\n        );\n\n        expect(screen.getByText('Content')).not.toBe(null);\n      });\n\n      it('should not open when the component is rendered and open is controlled', async () => {\n        await render(\n          <TestPreviewCard\n            rootProps={{\n              defaultOpen: true,\n              open: false,\n            }}\n          />,\n        );\n\n        expect(screen.queryByText('Content')).toBe(null);\n      });\n\n      it('should not close when the component is rendered and open is controlled', async () => {\n        await render(\n          <TestPreviewCard\n            rootProps={{\n              defaultOpen: true,\n              open: true,\n            }}\n          />,\n        );\n\n        expect(screen.getByText('Content')).not.toBe(null);\n      });\n\n      it('should remain uncontrolled', async () => {\n        await render(\n          <TestPreviewCard\n            rootProps={{\n              defaultOpen: true,\n            }}\n          />,\n        );\n\n        expect(screen.getByText('Content')).not.toBe(null);\n\n        const trigger = screen.getByRole('link', { name: 'Link' });\n\n        fireEvent.mouseLeave(trigger);\n\n        clock.tick(CLOSE_DELAY);\n\n        expect(screen.queryByText('Content')).toBe(null);\n      });\n    });\n\n    describe('prop: delay', () => {\n      clock.withFakeTimers();\n\n      it('should open after delay with rest type by default', async () => {\n        await render(<TestPreviewCard triggerProps={{ delay: 100 }} />);\n\n        const trigger = screen.getByRole('link', { name: 'Link' });\n\n        fireEvent.mouseEnter(trigger);\n        fireEvent.mouseMove(trigger);\n\n        await flushMicrotasks();\n\n        expect(screen.queryByText('Content')).toBe(null);\n\n        clock.tick(100);\n\n        await flushMicrotasks();\n\n        expect(screen.getByText('Content')).not.toBe(null);\n      });\n    });\n\n    describe('prop: closeDelay', () => {\n      clock.withFakeTimers();\n\n      it('should close after delay', async () => {\n        await render(<TestPreviewCard triggerProps={{ closeDelay: 100 }} />);\n\n        const trigger = screen.getByRole('link', { name: 'Link' });\n\n        fireEvent.mouseEnter(trigger);\n        fireEvent.mouseMove(trigger);\n\n        clock.tick(OPEN_DELAY);\n\n        await flushMicrotasks();\n\n        expect(screen.getByText('Content')).not.toBe(null);\n\n        fireEvent.mouseLeave(trigger);\n\n        expect(screen.getByText('Content')).not.toBe(null);\n\n        clock.tick(100);\n\n        expect(screen.queryByText('Content')).toBe(null);\n      });\n    });\n\n    describe('BaseUIChangeEventDetails', () => {\n      it('onOpenChange cancel() prevents opening while uncontrolled', async () => {\n        await render(\n          <TestPreviewCard\n            rootProps={{\n              onOpenChange: (nextOpen, eventDetails) => {\n                if (nextOpen) {\n                  eventDetails.cancel();\n                }\n              },\n            }}\n          />,\n        );\n\n        const trigger = screen.getByRole('link', { name: 'Link' });\n        fireEvent.pointerDown(trigger, { pointerType: 'mouse' });\n        fireEvent.mouseEnter(trigger);\n        fireEvent.mouseMove(trigger);\n        await flushMicrotasks();\n\n        expect(screen.queryByText('Content')).toBe(null);\n      });\n    });\n\n    describe.skipIf(!isJSDOM)('prop: actionsRef', () => {\n      it('unmounts the preview card when the `unmount` method is called', async () => {\n        const actionsRef = {\n          current: {\n            unmount: vi.fn(),\n            close: vi.fn(),\n          },\n        };\n\n        const { user } = await render(\n          <TestPreviewCard\n            rootProps={{\n              actionsRef,\n              onOpenChange: (open, details) => {\n                details.preventUnmountOnClose();\n              },\n            }}\n            triggerProps={{\n              delay: 0,\n              closeDelay: 0,\n            }}\n          />,\n        );\n\n        const trigger = screen.getByRole('link', { name: 'Link' });\n        await user.hover(trigger);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('positioner')).not.toBe(null);\n        });\n\n        await user.unhover(trigger);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('positioner')).not.toBe(null);\n        });\n\n        await act(async () => actionsRef.current.unmount());\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('positioner')).toBe(null);\n        });\n      });\n    });\n\n    describe.skipIf(isJSDOM)('prop: onOpenChangeComplete', () => {\n      it('is called on close when there is no exit animation defined', async () => {\n        const onOpenChangeComplete = vi.fn();\n\n        function Test() {\n          const [open, setOpen] = React.useState(true);\n          return (\n            <div>\n              <button onClick={() => setOpen(false)}>Close</button>\n              <TestPreviewCard\n                rootProps={{\n                  open,\n                  onOpenChangeComplete,\n                }}\n              />\n            </div>\n          );\n        }\n\n        const { user } = await render(<Test />);\n\n        const closeButton = screen.getByText('Close');\n        await user.click(closeButton);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('popup')).toBe(null);\n        });\n\n        expect(onOpenChangeComplete.mock.calls[0][0]).toBe(true);\n        expect(onOpenChangeComplete.mock.lastCall?.[0]).toBe(false);\n      });\n\n      it('is called on close when the exit animation finishes', async () => {\n        globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n        const onOpenChangeComplete = vi.fn();\n\n        function Test() {\n          const style = `\n          @keyframes test-anim {\n            to {\n              opacity: 0;\n            }\n          }\n\n          .animation-test-indicator[data-ending-style] {\n            animation: test-anim 1ms;\n          }\n        `;\n\n          const [open, setOpen] = React.useState(true);\n\n          return (\n            <div>\n              {/* eslint-disable-next-line react/no-danger */}\n              <style dangerouslySetInnerHTML={{ __html: style }} />\n              <button onClick={() => setOpen(false)}>Close</button>\n              <TestPreviewCard\n                rootProps={{\n                  open,\n                  onOpenChangeComplete,\n                }}\n                popupProps={{\n                  className: 'animation-test-indicator',\n                }}\n              />\n            </div>\n          );\n        }\n\n        const { user } = await render(<Test />);\n\n        expect(screen.getByTestId('popup')).not.toBe(null);\n\n        const closeButton = screen.getByText('Close');\n        await user.click(closeButton);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('popup')).toBe(null);\n        });\n\n        expect(onOpenChangeComplete.mock.lastCall?.[0]).toBe(false);\n      });\n    });\n\n    describe.skipIf(isJSDOM)('prop: onOpenChangeComplete', () => {\n      it('is called on close when there is no exit animation defined', async () => {\n        const onOpenChangeComplete = vi.fn();\n\n        function Test() {\n          const [open, setOpen] = React.useState(true);\n          return (\n            <div>\n              <button onClick={() => setOpen(false)}>Close</button>\n              <TestPreviewCard\n                rootProps={{\n                  open,\n                  onOpenChangeComplete,\n                }}\n              />\n            </div>\n          );\n        }\n\n        const { user } = await render(<Test />);\n\n        const closeButton = screen.getByText('Close');\n        await user.click(closeButton);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('popup')).toBe(null);\n        });\n\n        expect(onOpenChangeComplete.mock.calls[0][0]).toBe(true);\n        expect(onOpenChangeComplete.mock.lastCall?.[0]).toBe(false);\n      });\n\n      it('is called on close when the exit animation finishes', async () => {\n        globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n        const onOpenChangeComplete = vi.fn();\n\n        function Test() {\n          const style = `\n          @keyframes test-anim {\n            to {\n              opacity: 0;\n            }\n          }\n\n          .animation-test-indicator[data-ending-style] {\n            animation: test-anim 1ms;\n          }\n        `;\n\n          const [open, setOpen] = React.useState(true);\n\n          return (\n            <div>\n              {/* eslint-disable-next-line react/no-danger */}\n              <style dangerouslySetInnerHTML={{ __html: style }} />\n              <button onClick={() => setOpen(false)}>Close</button>\n              <TestPreviewCard\n                rootProps={{\n                  open,\n                  onOpenChangeComplete,\n                }}\n                popupProps={{\n                  className: 'animation-test-indicator',\n                }}\n              />\n            </div>\n          );\n        }\n\n        const { user } = await render(<Test />);\n\n        expect(screen.getByTestId('popup')).not.toBe(null);\n\n        // Wait for open animation to finish\n        await waitFor(() => {\n          expect(onOpenChangeComplete.mock.calls[0][0]).toBe(true);\n        });\n\n        const closeButton = screen.getByText('Close');\n        await user.click(closeButton);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('popup')).toBe(null);\n        });\n\n        expect(onOpenChangeComplete.mock.lastCall?.[0]).toBe(false);\n      });\n\n      it('is called on open when there is no enter animation defined', async () => {\n        const onOpenChangeComplete = vi.fn();\n\n        function Test() {\n          const [open, setOpen] = React.useState(false);\n          return (\n            <div>\n              <button onClick={() => setOpen(true)}>Open</button>\n              <TestPreviewCard\n                rootProps={{\n                  open,\n                  onOpenChangeComplete,\n                }}\n              />\n            </div>\n          );\n        }\n\n        const { user } = await render(<Test />);\n\n        const openButton = screen.getByText('Open');\n        await user.click(openButton);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('popup')).not.toBe(null);\n        });\n\n        expect(onOpenChangeComplete.mock.calls.length).toBe(2);\n        expect(onOpenChangeComplete.mock.calls[0][0]).toBe(true);\n      });\n\n      it('is called on open when the enter animation finishes', async () => {\n        globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n        const onOpenChangeComplete = vi.fn();\n\n        function Test() {\n          const style = `\n          @keyframes test-anim {\n            from {\n              opacity: 0;\n            }\n          }\n\n          .animation-test-indicator[data-starting-style] {\n            animation: test-anim 1ms;\n          }\n        `;\n\n          const [open, setOpen] = React.useState(false);\n\n          return (\n            <div>\n              {/* eslint-disable-next-line react/no-danger */}\n              <style dangerouslySetInnerHTML={{ __html: style }} />\n              <button onClick={() => setOpen(true)}>Open</button>\n              <TestPreviewCard\n                rootProps={{\n                  open,\n                  onOpenChangeComplete,\n                }}\n                popupProps={{\n                  className: 'animation-test-indicator',\n                }}\n              />\n            </div>\n          );\n        }\n\n        const { user } = await render(<Test />);\n\n        const openButton = screen.getByText('Open');\n        await user.click(openButton);\n\n        // Wait for open animation to finish\n        await waitFor(() => {\n          expect(onOpenChangeComplete.mock.calls[0][0]).toBe(true);\n        });\n\n        expect(screen.queryByTestId('popup')).not.toBe(null);\n      });\n\n      it('does not get called on mount when not open', async () => {\n        const onOpenChangeComplete = vi.fn();\n\n        await render(\n          <TestPreviewCard\n            rootProps={{\n              onOpenChangeComplete,\n            }}\n          />,\n        );\n\n        expect(onOpenChangeComplete.mock.calls.length).toBe(0);\n      });\n    });\n  });\n\n  describe('nested preview card interactions', () => {\n    it('keeps the parent preview card open when clicking nested trigger', async () => {\n      function Test() {\n        return (\n          <PreviewCard.Root defaultOpen>\n            <PreviewCard.Trigger href=\"#\">Parent</PreviewCard.Trigger>\n            <PreviewCard.Portal>\n              <PreviewCard.Positioner>\n                <PreviewCard.Popup data-testid=\"parent-popup\">\n                  <PreviewCard.Root>\n                    <PreviewCard.Trigger href=\"#\">Child</PreviewCard.Trigger>\n                    <PreviewCard.Portal>\n                      <PreviewCard.Positioner>\n                        <PreviewCard.Popup data-testid=\"child-popup\">\n                          Child content\n                        </PreviewCard.Popup>\n                      </PreviewCard.Positioner>\n                    </PreviewCard.Portal>\n                  </PreviewCard.Root>\n                </PreviewCard.Popup>\n              </PreviewCard.Positioner>\n            </PreviewCard.Portal>\n          </PreviewCard.Root>\n        );\n      }\n\n      await render(<Test />);\n\n      expect(screen.queryByTestId('parent-popup')).not.toBe(null);\n\n      const childTrigger = screen.getByRole('link', { name: 'Child' });\n\n      fireEvent.click(childTrigger);\n\n      await flushMicrotasks();\n\n      // Parent popup should still be open after clicking the child trigger\n      expect(screen.queryByTestId('parent-popup')).not.toBe(null);\n    });\n\n    it('keeps the parent preview card open when press starts in nested popup and ends outside', async () => {\n      function Test() {\n        return (\n          <div>\n            <button type=\"button\" data-testid=\"outside\">\n              Outside\n            </button>\n\n            <PreviewCard.Root defaultOpen>\n              <PreviewCard.Trigger href=\"#\">Parent</PreviewCard.Trigger>\n              <PreviewCard.Portal>\n                <PreviewCard.Positioner>\n                  <PreviewCard.Popup data-testid=\"parent-popup\">\n                    <PreviewCard.Root defaultOpen>\n                      <PreviewCard.Trigger href=\"#\">Child</PreviewCard.Trigger>\n                      <PreviewCard.Portal>\n                        <PreviewCard.Positioner>\n                          <PreviewCard.Popup data-testid=\"child-popup\">\n                            Child content\n                          </PreviewCard.Popup>\n                        </PreviewCard.Positioner>\n                      </PreviewCard.Portal>\n                    </PreviewCard.Root>\n                  </PreviewCard.Popup>\n                </PreviewCard.Positioner>\n              </PreviewCard.Portal>\n            </PreviewCard.Root>\n          </div>\n        );\n      }\n\n      await render(<Test />);\n\n      expect(screen.queryByTestId('parent-popup')).not.toBe(null);\n      expect(screen.queryByTestId('child-popup')).not.toBe(null);\n\n      const childPopup = screen.getByTestId('child-popup');\n      const outside = screen.getByTestId('outside');\n\n      fireEvent.pointerDown(childPopup, { pointerType: 'mouse', button: 0 });\n      fireEvent.click(outside);\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('parent-popup')).not.toBe(null);\n      });\n      expect(screen.queryByTestId('child-popup')).not.toBe(null);\n    });\n\n    it('keeps the parent preview card open when hovering nested trigger', async () => {\n      function Test() {\n        return (\n          <PreviewCard.Root defaultOpen>\n            <PreviewCard.Trigger href=\"#\">Parent</PreviewCard.Trigger>\n            <PreviewCard.Portal>\n              <PreviewCard.Positioner data-testid=\"parent-positioner\">\n                <PreviewCard.Popup data-testid=\"parent-popup\">\n                  <div>Parent content</div>\n                  <PreviewCard.Root>\n                    <PreviewCard.Trigger href=\"#\" data-testid=\"child-trigger\">\n                      Child\n                    </PreviewCard.Trigger>\n                    <PreviewCard.Portal>\n                      <PreviewCard.Positioner>\n                        <PreviewCard.Popup data-testid=\"child-popup\">\n                          Child content\n                        </PreviewCard.Popup>\n                      </PreviewCard.Positioner>\n                    </PreviewCard.Portal>\n                  </PreviewCard.Root>\n                </PreviewCard.Popup>\n              </PreviewCard.Positioner>\n            </PreviewCard.Portal>\n          </PreviewCard.Root>\n        );\n      }\n\n      await render(<Test />);\n\n      expect(screen.queryByTestId('parent-popup')).not.toBe(null);\n\n      const childTrigger = screen.getByTestId('child-trigger');\n\n      // Simulate hovering from parent content to child trigger\n      fireEvent.pointerDown(childTrigger, { pointerType: 'mouse' });\n      fireEvent.mouseEnter(childTrigger);\n      fireEvent.mouseMove(childTrigger);\n\n      await flushMicrotasks();\n\n      // Parent popup should still be open after hovering the child trigger\n      expect(screen.queryByTestId('parent-popup')).not.toBe(null);\n    });\n\n    describe('race condition between close timers and hover-open logic', () => {\n      clock.withFakeTimers();\n\n      it('keeps the parent open and re-opens the child when re-entering after partial close', async () => {\n        function Test() {\n          return (\n            <PreviewCard.Root defaultOpen>\n              <PreviewCard.Trigger href=\"#\" data-testid=\"parent-trigger\">\n                Parent\n              </PreviewCard.Trigger>\n              <PreviewCard.Portal>\n                <PreviewCard.Positioner>\n                  <PreviewCard.Popup data-testid=\"parent-popup\">\n                    <div>Parent content</div>\n                    <PreviewCard.Root defaultOpen>\n                      <PreviewCard.Trigger href=\"#\" data-testid=\"child-trigger\">\n                        Child\n                      </PreviewCard.Trigger>\n                      <PreviewCard.Portal>\n                        <PreviewCard.Positioner>\n                          <PreviewCard.Popup data-testid=\"child-popup\">\n                            Child content\n                          </PreviewCard.Popup>\n                        </PreviewCard.Positioner>\n                      </PreviewCard.Portal>\n                    </PreviewCard.Root>\n                  </PreviewCard.Popup>\n                </PreviewCard.Positioner>\n              </PreviewCard.Portal>\n            </PreviewCard.Root>\n          );\n        }\n\n        await render(<Test />);\n\n        // Events must be triggered on positioner elements (parent of popup)\n        const parentPopup = screen.getByTestId('parent-popup').parentElement!;\n        let childPopup = screen.getByTestId('child-popup').parentElement!;\n\n        // Step 3: Move mouse outside all previews\n        fireEvent.mouseLeave(childPopup);\n        fireEvent.mouseLeave(parentPopup);\n        fireEvent.mouseMove(document.body);\n\n        // Advance partway through close delay but not all the way\n        clock.tick(CLOSE_DELAY / 2);\n        await flushMicrotasks();\n\n        // Step 4: Re-enter parent popup before it closes\n        fireEvent.mouseEnter(parentPopup);\n\n        // Let the child's close delay finish — child closes\n        clock.tick(CLOSE_DELAY);\n        await flushMicrotasks();\n\n        // Parent should still be open\n        expect(screen.queryByTestId('parent-popup')).not.toBe(null);\n        expect(screen.queryByTestId('child-popup')).toBe(null);\n\n        // Step 5: Hover child trigger again to re-open child\n        const childTrigger = screen.getByTestId('child-trigger');\n\n        fireEvent.mouseEnter(childTrigger);\n        fireEvent.mouseMove(childTrigger);\n\n        clock.tick(OPEN_DELAY);\n        await flushMicrotasks();\n\n        // Parent and child should be open\n        expect(screen.queryByTestId('parent-popup')).not.toBe(null);\n        childPopup = screen.getByTestId('child-popup').parentElement!;\n\n        fireEvent.mouseLeave(childTrigger, { relatedTarget: childPopup });\n        fireEvent.mouseLeave(parentPopup, { relatedTarget: childPopup });\n        fireEvent.mouseEnter(childPopup);\n\n        clock.tick(CLOSE_DELAY);\n\n        expect(screen.queryByTestId('parent-popup')).not.toBe(null);\n        expect(screen.queryByTestId('child-popup')).not.toBe(null);\n      });\n    });\n\n    describe('synchronized closing', () => {\n      clock.withFakeTimers();\n\n      it('parent popup closes as soon as the child popup closes', async () => {\n        function Test() {\n          return (\n            <PreviewCard.Root>\n              <PreviewCard.Trigger href=\"#\" data-testid=\"parent-trigger\">\n                Parent\n              </PreviewCard.Trigger>\n              <PreviewCard.Portal>\n                <PreviewCard.Positioner>\n                  <PreviewCard.Popup data-testid=\"parent-popup\">\n                    <div>Parent content</div>\n                    <PreviewCard.Root>\n                      <PreviewCard.Trigger href=\"#\" data-testid=\"child-trigger\">\n                        Child\n                      </PreviewCard.Trigger>\n                      <PreviewCard.Portal>\n                        <PreviewCard.Positioner>\n                          <PreviewCard.Popup data-testid=\"child-popup\">\n                            Child content\n                          </PreviewCard.Popup>\n                        </PreviewCard.Positioner>\n                      </PreviewCard.Portal>\n                    </PreviewCard.Root>\n                  </PreviewCard.Popup>\n                </PreviewCard.Positioner>\n              </PreviewCard.Portal>\n            </PreviewCard.Root>\n          );\n        }\n\n        await render(<Test />);\n\n        const parentTrigger = screen.getByTestId('parent-trigger');\n        fireEvent.mouseEnter(parentTrigger);\n        clock.tick(OPEN_DELAY);\n\n        // Events must be triggered on positioner elements (parent of popup)\n        const parentPopup = screen.getByTestId('parent-popup').parentElement!;\n        const childTrigger = screen.getByTestId('child-trigger');\n\n        fireEvent.mouseLeave(parentTrigger, { relatedTarget: parentPopup });\n        fireEvent.mouseEnter(parentPopup);\n        fireEvent.mouseEnter(childTrigger);\n        clock.tick(OPEN_DELAY);\n\n        const childPopup = screen.getByTestId('child-popup').parentElement!;\n\n        fireEvent.mouseLeave(childTrigger, { relatedTarget: childPopup });\n        fireEvent.mouseLeave(parentPopup, { relatedTarget: childPopup });\n        fireEvent.mouseEnter(childPopup);\n        fireEvent.mouseLeave(childPopup);\n        fireEvent.mouseMove(document.body);\n\n        clock.tick(CLOSE_DELAY + 10);\n        await flushMicrotasks();\n\n        expect(screen.queryByTestId('child-popup')).toBe(null);\n        expect(screen.queryByTestId('parent-popup')).toBe(null);\n      });\n    });\n  });\n});\n\ntype TestPreviewCardProps = {\n  rootProps?: PreviewCard.Root.Props;\n  triggerProps?: PreviewCard.Trigger.Props;\n  portalProps?: PreviewCard.Portal.Props;\n  positionerProps?: PreviewCard.Positioner.Props;\n  popupProps?: PreviewCard.Popup.Props;\n};\n\nfunction ContainedTriggerPreviewCard(props: TestPreviewCardProps) {\n  const { rootProps, triggerProps, portalProps, positionerProps, popupProps } = props;\n\n  const { children: triggerChildren, ...restTriggerProps } = triggerProps ?? {};\n  const { children: popupChildren, ...restPopupProps } = popupProps ?? {};\n  const { children: portalChildren, ...restPortalProps } = portalProps ?? {};\n\n  const triggerContent = triggerChildren ?? 'Link';\n  const popupContent = popupChildren ?? 'Content';\n\n  return (\n    <PreviewCard.Root {...rootProps}>\n      <PreviewCard.Trigger href=\"#\" data-testid=\"trigger\" {...restTriggerProps}>\n        {triggerContent}\n      </PreviewCard.Trigger>\n      <PreviewCard.Portal {...restPortalProps}>\n        {portalChildren}\n        <PreviewCard.Positioner data-testid=\"positioner\" {...positionerProps}>\n          <PreviewCard.Popup data-testid=\"popup\" {...restPopupProps}>\n            {popupContent}\n          </PreviewCard.Popup>\n        </PreviewCard.Positioner>\n      </PreviewCard.Portal>\n    </PreviewCard.Root>\n  );\n}\n\nfunction DetachedTriggerPreviewCard(props: TestPreviewCardProps) {\n  const { rootProps, triggerProps, portalProps, positionerProps, popupProps } = props;\n\n  const { children: triggerChildren, ...restTriggerProps } = triggerProps ?? {};\n  const { children: popupChildren, ...restPopupProps } = popupProps ?? {};\n  const { children: portalChildren, ...restPortalProps } = portalProps ?? {};\n\n  const triggerContent = triggerChildren ?? 'Link';\n  const popupContent = popupChildren ?? 'Content';\n\n  const previewCardHandle = useRefWithInit(() => PreviewCard.createHandle()).current;\n\n  return (\n    <React.Fragment>\n      <PreviewCard.Trigger\n        href=\"#\"\n        data-testid=\"trigger\"\n        {...restTriggerProps}\n        handle={previewCardHandle}\n      >\n        {triggerContent}\n      </PreviewCard.Trigger>\n      <PreviewCard.Root {...rootProps} handle={previewCardHandle}>\n        <PreviewCard.Portal {...restPortalProps}>\n          {portalChildren}\n          <PreviewCard.Positioner data-testid=\"positioner\" {...positionerProps}>\n            <PreviewCard.Popup data-testid=\"popup\" {...restPopupProps}>\n              {popupContent}\n            </PreviewCard.Popup>\n          </PreviewCard.Positioner>\n        </PreviewCard.Portal>\n      </PreviewCard.Root>\n    </React.Fragment>\n  );\n}\n\nfunction MultipleDetachedTriggersPreviewCard(props: TestPreviewCardProps) {\n  const { rootProps, triggerProps, portalProps, positionerProps, popupProps } = props;\n\n  const { children: triggerChildren, ...restTriggerProps } = triggerProps ?? {};\n  const { children: popupChildren, ...restPopupProps } = popupProps ?? {};\n  const { children: portalChildren, ...restPortalProps } = portalProps ?? {};\n\n  const triggerContent = triggerChildren ?? 'Link';\n  const popupContent = popupChildren ?? 'Content';\n\n  const previewCardHandle = useRefWithInit(() => PreviewCard.createHandle()).current;\n\n  return (\n    <React.Fragment>\n      <PreviewCard.Trigger\n        href=\"#\"\n        data-testid=\"trigger\"\n        {...restTriggerProps}\n        handle={previewCardHandle}\n      >\n        {triggerContent}\n      </PreviewCard.Trigger>\n      <PreviewCard.Trigger href=\"#\" data-testid=\"trigger-2\" handle={previewCardHandle}>\n        Another link\n      </PreviewCard.Trigger>\n\n      <PreviewCard.Root {...rootProps} handle={previewCardHandle}>\n        <PreviewCard.Portal {...restPortalProps}>\n          {portalChildren}\n          <PreviewCard.Positioner data-testid=\"positioner\" {...positionerProps}>\n            <PreviewCard.Popup data-testid=\"popup\" {...restPopupProps}>\n              {popupContent}\n            </PreviewCard.Popup>\n          </PreviewCard.Positioner>\n        </PreviewCard.Portal>\n      </PreviewCard.Root>\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "packages/react/src/preview-card/root/PreviewCardRoot.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useOnFirstRender } from '@base-ui/utils/useOnFirstRender';\nimport { useDismiss, useInteractions, FloatingTree } from '../../floating-ui-react';\nimport { PreviewCardRootContext, usePreviewCardRootContext } from './PreviewCardContext';\nimport {\n  createChangeEventDetails,\n  type BaseUIChangeEventDetails,\n} from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport { PreviewCardStore } from '../store/PreviewCardStore';\nimport {\n  PayloadChildRenderFunction,\n  useImplicitActiveTrigger,\n  useOpenStateTransitions,\n} from '../../utils/popups';\nimport { PreviewCardHandle } from '../store/PreviewCardHandle';\n\nfunction PreviewCardRootComponent<Payload>(props: PreviewCardRoot.Props<Payload>) {\n  const {\n    open: openProp,\n    defaultOpen = false,\n    onOpenChange,\n    onOpenChangeComplete,\n    actionsRef,\n    handle,\n    triggerId: triggerIdProp,\n    defaultTriggerId: defaultTriggerIdProp = null,\n    children,\n  } = props;\n\n  const store = PreviewCardStore.useStore<Payload>(handle?.store, {\n    open: defaultOpen,\n    openProp,\n    activeTriggerId: defaultTriggerIdProp,\n    triggerIdProp,\n  });\n\n  // Support initially open state when uncontrolled\n  useOnFirstRender(() => {\n    if (openProp === undefined && store.state.open === false && defaultOpen === true) {\n      store.update({\n        open: true,\n        activeTriggerId: defaultTriggerIdProp,\n      });\n    }\n  });\n\n  store.useControlledProp('openProp', openProp);\n  store.useControlledProp('triggerIdProp', triggerIdProp);\n\n  store.useContextCallback('onOpenChange', onOpenChange);\n  store.useContextCallback('onOpenChangeComplete', onOpenChangeComplete);\n\n  const open = store.useState('open');\n\n  const activeTriggerId = store.useState('activeTriggerId');\n  const payload = store.useState('payload') as Payload | undefined;\n\n  useImplicitActiveTrigger(store);\n  const { forceUnmount } = useOpenStateTransitions(open, store);\n\n  useIsoLayoutEffect(() => {\n    if (open) {\n      if (activeTriggerId == null) {\n        store.set('payload', undefined);\n      }\n    }\n  }, [store, activeTriggerId, open]);\n\n  const handleImperativeClose = React.useCallback(() => {\n    store.setOpen(false, createPreviewCardEventDetails(store, REASONS.imperativeAction));\n  }, [store]);\n\n  React.useImperativeHandle(\n    actionsRef,\n    () => ({ unmount: forceUnmount, close: handleImperativeClose }),\n    [forceUnmount, handleImperativeClose],\n  );\n\n  const floatingRootContext = store.useState('floatingRootContext');\n\n  const dismiss = useDismiss(floatingRootContext);\n\n  const { getReferenceProps, getTriggerProps, getFloatingProps } = useInteractions([dismiss]);\n\n  const activeTriggerProps = React.useMemo(() => getReferenceProps(), [getReferenceProps]);\n  const inactiveTriggerProps = React.useMemo(() => getTriggerProps(), [getTriggerProps]);\n  const popupProps = React.useMemo(() => getFloatingProps(), [getFloatingProps]);\n\n  store.useSyncedValues({\n    activeTriggerProps,\n    inactiveTriggerProps,\n    popupProps,\n  });\n\n  return (\n    <PreviewCardRootContext.Provider value={store as PreviewCardRootContext}>\n      {typeof children === 'function' ? children({ payload }) : children}\n    </PreviewCardRootContext.Provider>\n  );\n}\n\n/**\n * Groups all parts of the preview card.\n * Doesn’t render its own HTML element.\n *\n * Documentation: [Base UI Preview Card](https://base-ui.com/react/components/preview-card)\n */\nexport function PreviewCardRoot<Payload>(props: PreviewCardRoot.Props<Payload>) {\n  if (usePreviewCardRootContext(true)) {\n    return <PreviewCardRootComponent {...props} />;\n  }\n\n  return (\n    <FloatingTree>\n      <PreviewCardRootComponent {...props} />\n    </FloatingTree>\n  );\n}\n\nfunction createPreviewCardEventDetails<Payload>(\n  store: PreviewCardStore<Payload>,\n  reason: PreviewCardRoot.ChangeEventReason,\n) {\n  const details: PreviewCardRoot.ChangeEventDetails =\n    createChangeEventDetails<PreviewCardRoot.ChangeEventReason>(\n      reason,\n    ) as PreviewCardRoot.ChangeEventDetails;\n  details.preventUnmountOnClose = () => {\n    store.set('preventUnmountingOnClose', true);\n  };\n  return details;\n}\n\nexport interface PreviewCardRootState {}\n\nexport interface PreviewCardRootProps<Payload = unknown> {\n  /**\n   * Whether the preview card is initially open.\n   *\n   * To render a controlled preview card, use the `open` prop instead.\n   * @default false\n   */\n  defaultOpen?: boolean | undefined;\n  /**\n   * Whether the preview card is currently open.\n   */\n  open?: boolean | undefined;\n  /**\n   * Event handler called when the preview card is opened or closed.\n   */\n  onOpenChange?:\n    | ((open: boolean, eventDetails: PreviewCardRoot.ChangeEventDetails) => void)\n    | undefined;\n  /**\n   * Event handler called after any animations complete when the preview card is opened or closed.\n   */\n  onOpenChangeComplete?: ((open: boolean) => void) | undefined;\n  /**\n   * A ref to imperative actions.\n   * - `unmount`: Unmounts the preview card popup.\n   * - `close`: Closes the preview card imperatively when called.\n   */\n  actionsRef?: React.RefObject<PreviewCardRoot.Actions | null> | undefined;\n  /**\n   * A handle to associate the preview card with a trigger.\n   * If specified, allows external triggers to control the card's open state.\n   * Can be created with the PreviewCard.createHandle() method.\n   */\n  handle?: PreviewCardHandle<Payload> | undefined;\n  /**\n   * The content of the preview card.\n   * This can be a regular React node or a render function that receives the `payload` of the active trigger.\n   */\n  children?: React.ReactNode | PayloadChildRenderFunction<Payload>;\n  /**\n   * ID of the trigger that the preview card is associated with.\n   * This is useful in conjunction with the `open` prop to create a controlled preview card.\n   * There's no need to specify this prop when the preview card is uncontrolled (i.e. when the `open` prop is not set).\n   */\n  triggerId?: string | null | undefined;\n  /**\n   * ID of the trigger that the preview card is associated with.\n   * This is useful in conjunction with the `defaultOpen` prop to create an initially open preview card.\n   */\n  defaultTriggerId?: string | null | undefined;\n}\n\nexport interface PreviewCardRootActions {\n  unmount: () => void;\n  close: () => void;\n}\n\nexport type PreviewCardRootChangeEventReason =\n  | typeof REASONS.triggerHover\n  | typeof REASONS.triggerFocus\n  | typeof REASONS.triggerPress\n  | typeof REASONS.outsidePress\n  | typeof REASONS.escapeKey\n  | typeof REASONS.imperativeAction\n  | typeof REASONS.none;\n\nexport type PreviewCardRootChangeEventDetails =\n  BaseUIChangeEventDetails<PreviewCardRoot.ChangeEventReason> & {\n    preventUnmountOnClose(): void;\n  };\n\nexport namespace PreviewCardRoot {\n  export type State = PreviewCardRootState;\n  export type Props<Payload = unknown> = PreviewCardRootProps<Payload>;\n  export type Actions = PreviewCardRootActions;\n  export type ChangeEventReason = PreviewCardRootChangeEventReason;\n  export type ChangeEventDetails = PreviewCardRootChangeEventDetails;\n}\n"
  },
  {
    "path": "packages/react/src/preview-card/store/PreviewCardHandle.ts",
    "content": "import { PreviewCardStore } from './PreviewCardStore';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\n\n/**\n * A handle to control a preview card imperatively and to associate detached triggers with it.\n */\nexport class PreviewCardHandle<Payload> {\n  /**\n   * Internal store holding the preview card state.\n   * @internal\n   */\n  public readonly store: PreviewCardStore<Payload>;\n\n  constructor() {\n    this.store = new PreviewCardStore<Payload>();\n  }\n\n  /**\n   * Opens the preview card and associates it with the trigger with the given ID.\n   * The trigger must be a PreviewCard.Trigger component with this handle passed as a prop.\n   *\n   * This method should only be called in an event handler or an effect (not during rendering).\n   *\n   * @param triggerId ID of the trigger to associate with the preview card.\n   */\n  open(triggerId: string) {\n    const triggerElement = triggerId\n      ? (this.store.context.triggerElements.getById(triggerId) as HTMLElement | undefined)\n      : undefined;\n\n    if (triggerId && !triggerElement) {\n      throw new Error(`Base UI: PreviewCardHandle.open: No trigger found with id \"${triggerId}\".`);\n    }\n\n    this.store.setOpen(\n      true,\n      createChangeEventDetails(REASONS.imperativeAction, undefined, triggerElement),\n    );\n  }\n\n  /**\n   * Closes the preview card.\n   */\n  close() {\n    this.store.setOpen(\n      false,\n      createChangeEventDetails(REASONS.imperativeAction, undefined, undefined),\n    );\n  }\n\n  /**\n   * Indicates whether the preview card is currently open.\n   */\n  get isOpen() {\n    return this.store.state.open;\n  }\n}\n\n/**\n * Creates a new handle to connect a PreviewCard.Root with detached PreviewCard.Trigger components.\n */\nexport function createPreviewCardHandle<Payload>(): PreviewCardHandle<Payload> {\n  return new PreviewCardHandle<Payload>();\n}\n"
  },
  {
    "path": "packages/react/src/preview-card/store/PreviewCardStore.ts",
    "content": "import * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport { createSelector, ReactStore } from '@base-ui/utils/store';\nimport { useRefWithInit } from '@base-ui/utils/useRefWithInit';\nimport {\n  createInitialPopupStoreState,\n  PopupStoreContext,\n  popupStoreSelectors,\n  PopupStoreState,\n  PopupTriggerMap,\n} from '../../utils/popups';\nimport { useSyncedFloatingRootContext } from '../../floating-ui-react';\nimport { type PreviewCardRoot } from '../root/PreviewCardRoot';\nimport { REASONS } from '../../utils/reasons';\nimport { CLOSE_DELAY } from '../utils/constants';\n\nexport type State<Payload> = PopupStoreState<Payload> & {\n  instantType: 'dismiss' | 'focus' | undefined;\n  hasViewport: boolean;\n};\n\nexport type Context = PopupStoreContext<PreviewCardRoot.ChangeEventDetails> & {\n  closeDelayRef: React.RefObject<number>;\n};\n\nconst selectors = {\n  ...popupStoreSelectors,\n  instantType: createSelector((state: State<unknown>) => state.instantType),\n  hasViewport: createSelector((state: State<unknown>) => state.hasViewport),\n};\n\nexport class PreviewCardStore<Payload> extends ReactStore<\n  Readonly<State<Payload>>,\n  Context,\n  typeof selectors\n> {\n  constructor(initialState?: Partial<State<Payload>>) {\n    super(\n      { ...createInitialState(), ...initialState },\n      {\n        popupRef: React.createRef<HTMLElement | null>(),\n        onOpenChange: undefined,\n        onOpenChangeComplete: undefined,\n        triggerElements: new PopupTriggerMap(),\n        closeDelayRef: { current: CLOSE_DELAY },\n      },\n      selectors,\n    );\n  }\n\n  public setOpen = (\n    nextOpen: boolean,\n    eventDetails: Omit<PreviewCardRoot.ChangeEventDetails, 'preventUnmountOnClose'>,\n  ) => {\n    const reason = eventDetails.reason;\n\n    const isHover = reason === REASONS.triggerHover;\n    const isFocusOpen = nextOpen && reason === REASONS.triggerFocus;\n    const isDismissClose =\n      !nextOpen && (reason === REASONS.triggerPress || reason === REASONS.escapeKey);\n\n    (eventDetails as PreviewCardRoot.ChangeEventDetails).preventUnmountOnClose = () => {\n      this.set('preventUnmountingOnClose', true);\n    };\n\n    this.context.onOpenChange?.(nextOpen, eventDetails as PreviewCardRoot.ChangeEventDetails);\n\n    if (eventDetails.isCanceled) {\n      return;\n    }\n\n    const changeState = () => {\n      const updatedState: Partial<State<Payload>> = { open: nextOpen };\n\n      if (isFocusOpen) {\n        updatedState.instantType = 'focus';\n      } else if (isDismissClose) {\n        updatedState.instantType = 'dismiss';\n      } else if (reason === REASONS.triggerHover) {\n        updatedState.instantType = undefined;\n      }\n\n      // If a popup is closing, the `trigger` may be null.\n      // We want to keep the previous value so that exit animations are played and focus is returned correctly.\n      const newTriggerId = eventDetails.trigger?.id ?? null;\n      if (newTriggerId || nextOpen) {\n        updatedState.activeTriggerId = newTriggerId;\n        updatedState.activeTriggerElement = eventDetails.trigger ?? null;\n      }\n\n      this.update(updatedState);\n    };\n\n    if (isHover) {\n      // If a hover reason is provided, we need to flush the state synchronously. This ensures\n      // `node.getAnimations()` knows about the new state.\n      ReactDOM.flushSync(changeState);\n    } else {\n      changeState();\n    }\n  };\n\n  public static useStore<Payload>(\n    externalStore: PreviewCardStore<Payload> | undefined,\n    initialState?: Partial<State<Payload>>,\n  ) {\n    // eslint-disable-next-line react-hooks/rules-of-hooks\n    const internalStore = useRefWithInit(() => {\n      return new PreviewCardStore<Payload>(initialState);\n    }).current;\n\n    const store = externalStore ?? internalStore;\n\n    // eslint-disable-next-line react-hooks/rules-of-hooks\n    const floatingRootContext = useSyncedFloatingRootContext({\n      popupStore: store,\n      onOpenChange: store.setOpen,\n    });\n\n    // It's safe to set this here because when this code runs for the first time,\n    // nothing has had a chance to subscribe to the `store` yet.\n    // For subsequent renders, the `floatingRootContext` reference remains the same,\n    // so it's basically a no-op.\n    (store.state as State<Payload>).floatingRootContext = floatingRootContext;\n    return store;\n  }\n}\n\nfunction createInitialState<Payload>(): State<Payload> {\n  return {\n    ...createInitialPopupStoreState(),\n    instantType: undefined,\n    hasViewport: false,\n  };\n}\n"
  },
  {
    "path": "packages/react/src/preview-card/trigger/PreviewCardTrigger.test.tsx",
    "content": "import { PreviewCard } from '@base-ui/react/preview-card';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<PreviewCard.Trigger />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<PreviewCard.Trigger />, () => ({\n    refInstanceof: window.HTMLAnchorElement,\n    render(node) {\n      return render(<PreviewCard.Root open>{node}</PreviewCard.Root>);\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/preview-card/trigger/PreviewCardTrigger.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { usePreviewCardRootContext } from '../root/PreviewCardContext';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { triggerOpenStateMapping } from '../../utils/popupStateMapping';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useBaseUiId } from '../../utils/useBaseUiId';\nimport { PreviewCardHandle } from '../store/PreviewCardHandle';\nimport { useTriggerDataForwarding } from '../../utils/popups';\nimport { CLOSE_DELAY, OPEN_DELAY } from '../utils/constants';\nimport { safePolygon, useFocus, useHoverReferenceInteraction } from '../../floating-ui-react';\n\n/**\n * A link that opens the preview card.\n * Renders an `<a>` element.\n *\n * Documentation: [Base UI Preview Card](https://base-ui.com/react/components/preview-card)\n */\nexport const PreviewCardTrigger = React.forwardRef(function PreviewCardTrigger(\n  componentProps: PreviewCardTrigger.Props,\n  forwardedRef: React.ForwardedRef<HTMLAnchorElement>,\n) {\n  const {\n    render,\n    className,\n    delay,\n    closeDelay,\n    id: idProp,\n    payload,\n    handle,\n    ...elementProps\n  } = componentProps;\n\n  const rootContext = usePreviewCardRootContext(true);\n  const store = handle?.store ?? rootContext;\n  if (!store) {\n    throw new Error(\n      'Base UI: <PreviewCard.Trigger> must be either used within a <PreviewCard.Root> component or provided with a handle.',\n    );\n  }\n\n  const thisTriggerId = useBaseUiId(idProp);\n  const isTriggerActive = store.useState('isTriggerActive', thisTriggerId);\n  const isOpenedByThisTrigger = store.useState('isOpenedByTrigger', thisTriggerId);\n  const floatingRootContext = store.useState('floatingRootContext');\n\n  const triggerElementRef = React.useRef<Element | null>(null);\n\n  const delayWithDefault = delay ?? OPEN_DELAY;\n  const closeDelayWithDefault = closeDelay ?? CLOSE_DELAY;\n\n  const { registerTrigger, isMountedByThisTrigger } = useTriggerDataForwarding(\n    thisTriggerId,\n    triggerElementRef,\n    store,\n    {\n      payload,\n    },\n  );\n\n  useIsoLayoutEffect(() => {\n    if (isMountedByThisTrigger) {\n      store.context.closeDelayRef.current = closeDelayWithDefault;\n    }\n  }, [store, isMountedByThisTrigger, closeDelayWithDefault]);\n\n  const hoverProps = useHoverReferenceInteraction(floatingRootContext, {\n    mouseOnly: true,\n    move: false,\n    handleClose: safePolygon(),\n    delay: () => ({ open: delayWithDefault, close: closeDelayWithDefault }),\n    triggerElementRef,\n    isActiveTrigger: isTriggerActive,\n  });\n\n  const focusProps = useFocus(floatingRootContext, { delay: delayWithDefault });\n\n  const state: PreviewCardTriggerState = { open: isOpenedByThisTrigger };\n\n  const rootTriggerProps = store.useState('triggerProps', isMountedByThisTrigger);\n\n  const element = useRenderElement('a', componentProps, {\n    state,\n    ref: [forwardedRef, registerTrigger, triggerElementRef],\n    props: [\n      hoverProps,\n      focusProps.reference,\n      rootTriggerProps,\n      { id: thisTriggerId },\n      elementProps,\n    ],\n    stateAttributesMapping: triggerOpenStateMapping,\n  });\n\n  return element;\n}) as PreviewCardTrigger;\n\nexport interface PreviewCardTrigger {\n  <Payload>(\n    componentProps: PreviewCardTrigger.Props<Payload> & React.RefAttributes<HTMLElement>,\n  ): React.JSX.Element;\n}\n\nexport interface PreviewCardTriggerState {\n  /**\n   * Whether the preview card is currently open.\n   */\n  open: boolean;\n}\n\nexport interface PreviewCardTriggerProps<Payload = unknown> extends BaseUIComponentProps<\n  'a',\n  PreviewCardTriggerState\n> {\n  /**\n   * A handle to associate the trigger with a preview card.\n   */\n  handle?: PreviewCardHandle<Payload> | undefined;\n  /**\n   * A payload to pass to the preview card when it is opened.\n   */\n  payload?: Payload | undefined;\n  /**\n   * How long to wait before the preview card opens. Specified in milliseconds.\n   * @default 600\n   */\n  delay?: number | undefined;\n  /**\n   * How long to wait before closing the preview card. Specified in milliseconds.\n   * @default 300\n   */\n  closeDelay?: number | undefined;\n}\n\nexport namespace PreviewCardTrigger {\n  export type State = PreviewCardTriggerState;\n  export type Props<Payload = unknown> = PreviewCardTriggerProps<Payload>;\n}\n"
  },
  {
    "path": "packages/react/src/preview-card/trigger/PreviewCardTriggerDataAttributes.ts",
    "content": "import { CommonTriggerDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum PreviewCardTriggerDataAttributes {\n  /**\n   * Present when the corresponding preview card is open.\n   */\n  popupOpen = CommonTriggerDataAttributes.popupOpen,\n}\n"
  },
  {
    "path": "packages/react/src/preview-card/utils/constants.ts",
    "content": "export const OPEN_DELAY = 600;\nexport const CLOSE_DELAY = 300;\n"
  },
  {
    "path": "packages/react/src/preview-card/viewport/PreviewCardViewport.test.tsx",
    "content": "import { expect } from 'vitest';\nimport * as React from 'react';\nimport { PreviewCard } from '@base-ui/react/preview-card';\nimport { act, ignoreActWarnings, screen, waitFor } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance, isJSDOM, waitSingleFrame } from '#test-utils';\n\ndescribe('<PreviewCard.Viewport />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<PreviewCard.Viewport />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <PreviewCard.Root open>\n          <PreviewCard.Trigger>Trigger</PreviewCard.Trigger>\n          <PreviewCard.Portal>\n            <PreviewCard.Positioner>\n              <PreviewCard.Popup>{node}</PreviewCard.Popup>\n            </PreviewCard.Positioner>\n          </PreviewCard.Portal>\n        </PreviewCard.Root>,\n      );\n    },\n  }));\n\n  it('should render children in the `current` container by default', async () => {\n    await render(\n      <PreviewCard.Root open>\n        <PreviewCard.Trigger>Trigger</PreviewCard.Trigger>\n        <PreviewCard.Portal>\n          <PreviewCard.Positioner>\n            <PreviewCard.Popup>\n              <PreviewCard.Viewport>\n                <div data-testid=\"content\">Content</div>\n              </PreviewCard.Viewport>\n            </PreviewCard.Popup>\n          </PreviewCard.Positioner>\n        </PreviewCard.Portal>\n      </PreviewCard.Root>,\n    );\n\n    const currentContainer = screen.getByTestId('content').closest('[data-current]');\n    expect(currentContainer).not.toBe(null);\n    expect(currentContainer!.textContent).toBe('Content');\n  });\n\n  it('should remount the `current` container when the active trigger changes', async () => {\n    ignoreActWarnings();\n    await render(\n      <PreviewCard.Root>\n        {({ payload }) => (\n          <React.Fragment>\n            <PreviewCard.Trigger href=\"#\" payload=\"first\" delay={0} data-testid=\"trigger1\">\n              Trigger 1\n            </PreviewCard.Trigger>\n            <PreviewCard.Trigger href=\"#\" payload=\"second\" delay={0} data-testid=\"trigger2\">\n              Trigger 2\n            </PreviewCard.Trigger>\n            <PreviewCard.Portal>\n              <PreviewCard.Positioner>\n                <PreviewCard.Popup>\n                  <PreviewCard.Viewport>\n                    {payload === 'first' ? (\n                      <img data-testid=\"payload-image-1\" src=\"about:blank\" alt=\"Preview 1\" />\n                    ) : null}\n                    {payload === 'second' ? (\n                      <img data-testid=\"payload-image-2\" src=\"about:blank\" alt=\"Preview 2\" />\n                    ) : null}\n                  </PreviewCard.Viewport>\n                </PreviewCard.Popup>\n              </PreviewCard.Positioner>\n            </PreviewCard.Portal>\n          </React.Fragment>\n        )}\n      </PreviewCard.Root>,\n    );\n\n    const trigger1 = screen.getByTestId('trigger1');\n    const trigger2 = screen.getByTestId('trigger2');\n\n    await waitSingleFrame();\n    await act(async () => trigger1.focus());\n\n    const firstImage = await screen.findByTestId('payload-image-1');\n    const firstContainer = firstImage.closest('[data-current]');\n    expect(firstContainer).not.toBe(null);\n\n    await waitSingleFrame();\n    await act(async () => trigger2.focus());\n\n    const secondImage = await screen.findByTestId('payload-image-2');\n    const secondContainer = secondImage.closest('[data-current]');\n    expect(secondContainer).not.toBe(null);\n    expect(secondContainer).not.toBe(firstContainer);\n  });\n\n  describe.skipIf(isJSDOM)('morphing containers with multiple triggers and payloads', () => {\n    beforeEach(() => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n    });\n\n    afterEach(() => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = true;\n    });\n\n    it('should create morphing containers during transitions', async () => {\n      ignoreActWarnings();\n      await render(\n        <div>\n          <style>\n            {`\n              [data-transitioning] [data-previous] {\n                animation: slide-out 0.3s ease-out forwards;\n              }\n              [data-transitioning] [data-current] {\n                animation: slide-in 0.3s ease-out forwards;\n              }\n              @keyframes slide-out {\n                from { transform: translateX(0); opacity: 1; }\n                to { transform: translateX(-30%); opacity: 0; }\n              }\n              @keyframes slide-in {\n                from { transform: translateX(30%); opacity: 0; }\n                to { transform: translateX(0); opacity: 1; }\n              }\n            `}\n          </style>\n          <PreviewCard.Root>\n            {({ payload }) => (\n              <React.Fragment>\n                <PreviewCard.Trigger\n                  href=\"#\"\n                  payload={0}\n                  delay={0}\n                  data-testid=\"trigger1\"\n                  style={{\n                    position: 'absolute',\n                    top: '10px',\n                    left: '10px',\n                    width: '100px',\n                    height: '50px',\n                  }}\n                >\n                  Trigger 1\n                </PreviewCard.Trigger>\n                <PreviewCard.Trigger\n                  href=\"#\"\n                  payload={1}\n                  delay={0}\n                  data-testid=\"trigger2\"\n                  style={{\n                    position: 'absolute',\n                    top: '100px',\n                    left: '200px',\n                    width: '100px',\n                    height: '50px',\n                  }}\n                >\n                  Trigger 2\n                </PreviewCard.Trigger>\n                <PreviewCard.Portal>\n                  <PreviewCard.Positioner>\n                    <PreviewCard.Popup>\n                      <PreviewCard.Viewport>\n                        <div data-testid=\"content\">Content {payload as number}</div>\n                      </PreviewCard.Viewport>\n                    </PreviewCard.Popup>\n                  </PreviewCard.Positioner>\n                </PreviewCard.Portal>\n              </React.Fragment>\n            )}\n          </PreviewCard.Root>\n        </div>,\n      );\n\n      const trigger1 = screen.getByTestId('trigger1');\n      const trigger2 = screen.getByTestId('trigger2');\n\n      await waitSingleFrame();\n      await act(async () => trigger1.focus());\n\n      await waitFor(() => {\n        expect(screen.getByText('Content 0')).toBeVisible();\n      });\n\n      await waitSingleFrame();\n      await act(async () => trigger2.focus());\n\n      // Check for morphing containers during transition\n      let previousContainer: HTMLElement | null = null;\n      await waitFor(() => {\n        previousContainer = document.querySelector('[data-previous]');\n        expect(previousContainer).not.toBe(null);\n      });\n\n      expect(previousContainer).toHaveAttribute('inert');\n      expect(previousContainer!.textContent).toBe('Content 0');\n\n      const nextContainer = document.querySelector('[data-current]');\n      expect(nextContainer).not.toBe(null);\n      expect(nextContainer!.textContent).toBe('Content 1');\n\n      // Verify they are cleaned up after animation\n      await waitFor(() => {\n        expect(document.querySelector('[data-previous]')).toBe(null);\n      });\n\n      expect(document.querySelector('[data-current]')).toBeVisible();\n      expect(screen.getByText('Content 1')).toBeVisible();\n    });\n\n    it('should handle rapid trigger changes', async () => {\n      ignoreActWarnings();\n      function TestComponent() {\n        return (\n          <div>\n            <style>\n              {`\n              [data-transitioning] [data-previous] {\n                animation: slide-out 0.2s ease-out forwards;\n              }\n              [data-transitioning] [data-current] {\n                animation: slide-in 0.2s ease-out forwards;\n              }\n              @keyframes slide-out {\n                from { transform: translateX(0); opacity: 1; }\n                to { transform: translateX(-30%); opacity: 0; }\n              }\n              @keyframes slide-in {\n                from { transform: translateX(30%); opacity: 0; }\n                to { transform: translateX(0); opacity: 1; }\n              }\n            `}\n            </style>\n            <PreviewCard.Root>\n              {({ payload }) => (\n                <React.Fragment>\n                  <PreviewCard.Trigger href=\"#\" payload={1} delay={0} data-testid=\"trigger1\">\n                    Trigger 1\n                  </PreviewCard.Trigger>\n                  <PreviewCard.Trigger href=\"#\" payload={2} delay={0} data-testid=\"trigger2\">\n                    Trigger 2\n                  </PreviewCard.Trigger>\n                  <PreviewCard.Trigger href=\"#\" payload={3} delay={0} data-testid=\"trigger3\">\n                    Trigger 3\n                  </PreviewCard.Trigger>\n                  <PreviewCard.Portal>\n                    <PreviewCard.Positioner>\n                      <PreviewCard.Popup>\n                        <PreviewCard.Viewport>Content {payload as number}</PreviewCard.Viewport>\n                      </PreviewCard.Popup>\n                    </PreviewCard.Positioner>\n                  </PreviewCard.Portal>\n                </React.Fragment>\n              )}\n            </PreviewCard.Root>\n          </div>\n        );\n      }\n\n      await render(<TestComponent />);\n\n      const trigger1 = screen.getByTestId('trigger1');\n      const trigger2 = screen.getByTestId('trigger2');\n      const trigger3 = screen.getByTestId('trigger3');\n\n      await waitSingleFrame();\n      await act(async () => trigger1.focus());\n      await waitSingleFrame();\n      await act(async () => trigger2.focus());\n      await waitSingleFrame();\n      await act(async () => trigger3.focus());\n      await waitSingleFrame();\n      await act(async () => trigger1.focus());\n\n      await waitFor(async () => {\n        expect(await screen.findByText('Content 1')).toBeVisible();\n      });\n    });\n\n    it.each([\n      {\n        name: 'should calculate \"right down\" direction',\n        trigger1: { top: 10, left: 10 },\n        trigger2: { top: 100, left: 200 },\n        expectedDirection: ['right', 'down'],\n      },\n      {\n        name: 'should calculate \"left up\" direction',\n        trigger1: { top: 100, left: 200 },\n        trigger2: { top: 10, left: 10 },\n        expectedDirection: ['left', 'up'],\n      },\n      {\n        name: 'should calculate \"right\" direction (horizontal only)',\n        trigger1: { top: 50, left: 10 },\n        trigger2: { top: 52, left: 200 }, // 2px vertical difference within tolerance\n        expectedDirection: ['right'],\n      },\n      {\n        name: 'should calculate \"down\" direction (vertical only)',\n        trigger1: { top: 10, left: 50 },\n        trigger2: { top: 100, left: 52 }, // 2px horizontal difference within tolerance\n        expectedDirection: ['down'],\n      },\n      {\n        name: 'should handle tolerance for small differences',\n        trigger1: { top: 50, left: 50 },\n        trigger2: { top: 52, left: 52 }, // Both differences within 5px tolerance\n        expectedDirection: [],\n      },\n      {\n        name: 'should calculate \"left down\" direction',\n        trigger1: { top: 10, left: 200 },\n        trigger2: { top: 100, left: 10 },\n        expectedDirection: ['left', 'down'],\n      },\n      {\n        name: 'should calculate \"right up\" direction',\n        trigger1: { top: 100, left: 10 },\n        trigger2: { top: 10, left: 200 },\n        expectedDirection: ['right', 'up'],\n      },\n    ])('$name', async ({ trigger1, trigger2, expectedDirection }) => {\n      ignoreActWarnings();\n\n      await render(\n        <div>\n          <style>\n            {`\n              [data-transitioning] [data-previous] {\n                animation: slide-out 0.2s ease-out forwards;\n              }\n              [data-transitioning] [data-current] {\n                animation: slide-in 0.2s ease-out forwards;\n              }\n              @keyframes slide-out {\n                from { transform: translateX(0); opacity: 1; }\n                to { transform: translateX(-30%); opacity: 0; }\n              }\n              @keyframes slide-in {\n                from { transform: translateX(30%); opacity: 0; }\n                to { transform: translateX(0); opacity: 1; }\n              }\n            `}\n          </style>\n          <PreviewCard.Root>\n            {({ payload }) => (\n              <React.Fragment>\n                <PreviewCard.Trigger\n                  href=\"#\"\n                  payload={0}\n                  delay={0}\n                  data-testid=\"trigger1\"\n                  style={{\n                    position: 'absolute',\n                    top: `${trigger1.top}px`,\n                    left: `${trigger1.left}px`,\n                    width: '100px',\n                    height: '50px',\n                  }}\n                >\n                  Trigger 1\n                </PreviewCard.Trigger>\n                <PreviewCard.Trigger\n                  href=\"#\"\n                  payload={1}\n                  delay={0}\n                  data-testid=\"trigger2\"\n                  style={{\n                    position: 'absolute',\n                    top: `${trigger2.top}px`,\n                    left: `${trigger2.left}px`,\n                    width: '100px',\n                    height: '50px',\n                  }}\n                >\n                  Trigger 2\n                </PreviewCard.Trigger>\n                <PreviewCard.Portal>\n                  <PreviewCard.Positioner>\n                    <PreviewCard.Popup>\n                      <PreviewCard.Viewport data-testid=\"viewport\">\n                        <div data-testid=\"content\">Content {payload as number}</div>\n                      </PreviewCard.Viewport>\n                    </PreviewCard.Popup>\n                  </PreviewCard.Positioner>\n                </PreviewCard.Portal>\n              </React.Fragment>\n            )}\n          </PreviewCard.Root>\n        </div>,\n      );\n\n      const triggerElement1 = screen.getByTestId('trigger1');\n      const triggerElement2 = screen.getByTestId('trigger2');\n\n      await waitSingleFrame();\n      await act(async () => triggerElement1.focus());\n\n      await waitFor(() => {\n        expect(screen.getByText('Content 0')).toBeVisible();\n      });\n\n      await waitSingleFrame();\n      await act(async () => triggerElement2.focus());\n\n      const viewport = screen.getByTestId('viewport');\n      await waitFor(() => {\n        expect(viewport).toHaveAttribute('data-activation-direction');\n      });\n\n      const direction = viewport.getAttribute('data-activation-direction');\n\n      if (expectedDirection.length === 0) {\n        expect(direction?.trim()).toBe('');\n      } else {\n        expectedDirection.forEach((dir) => {\n          expect(direction).toContain(dir);\n        });\n      }\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/preview-card/viewport/PreviewCardViewport.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { usePreviewCardRootContext } from '../root/PreviewCardContext';\nimport { usePreviewCardPositionerContext } from '../positioner/PreviewCardPositionerContext';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport { PreviewCardViewportCssVars } from './PreviewCardViewportCssVars';\nimport { usePopupViewport } from '../../utils/usePopupViewport';\n\nconst stateAttributesMapping: StateAttributesMapping<PreviewCardViewportState> = {\n  activationDirection: (value) =>\n    value\n      ? {\n          'data-activation-direction': value,\n        }\n      : null,\n};\n\n/**\n * A viewport for displaying content transitions.\n * This component is only required if one popup can be opened by multiple triggers, its content change based on the trigger\n * and switching between them is animated.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Preview Card](https://base-ui.com/react/components/preview-card)\n */\nexport const PreviewCardViewport = React.forwardRef(function PreviewCardViewport(\n  componentProps: PreviewCardViewport.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, children, ...elementProps } = componentProps;\n  const store = usePreviewCardRootContext();\n  const positioner = usePreviewCardPositionerContext();\n\n  const instantType = store.useState('instantType');\n\n  const { children: childrenToRender, state: viewportState } = usePopupViewport({\n    store,\n    side: positioner.side,\n    cssVars: PreviewCardViewportCssVars,\n    children,\n  });\n\n  const state: PreviewCardViewportState = {\n    activationDirection: viewportState.activationDirection,\n    transitioning: viewportState.transitioning,\n    instant: instantType,\n  };\n\n  return useRenderElement('div', componentProps, {\n    state,\n    ref: forwardedRef,\n    props: [elementProps, { children: childrenToRender }],\n    stateAttributesMapping,\n  });\n});\n\nexport interface PreviewCardViewportState {\n  /**\n   * The activation direction of the transitioned content.\n   */\n  activationDirection: string | undefined;\n  /**\n   * Whether the viewport is currently transitioning between contents.\n   */\n  transitioning: boolean;\n  /**\n   * Present if animations should be instant.\n   */\n  instant: 'dismiss' | 'focus' | undefined;\n}\n\nexport namespace PreviewCardViewport {\n  export interface Props extends BaseUIComponentProps<'div', PreviewCardViewportState> {\n    /**\n     * The content to render inside the transition container.\n     */\n    children?: React.ReactNode;\n  }\n\n  export type State = PreviewCardViewportState;\n}\n"
  },
  {
    "path": "packages/react/src/preview-card/viewport/PreviewCardViewportCssVars.ts",
    "content": "export enum PreviewCardViewportCssVars {\n  /**\n   * The width of the parent popup.\n   * This variable is placed on the 'previous' container and stores the width of the popup when the previous content was rendered.\n   * It can be used to freeze the dimensions of the popup when animating between different content.\n   */\n  popupWidth = '--popup-width',\n  /**\n   * The height of the parent popup.\n   * This variable is placed on the 'previous' container and stores the height of the popup when the previous content was rendered.\n   * It can be used to freeze the dimensions of the popup when animating between different content.\n   */\n  popupHeight = '--popup-height',\n}\n"
  },
  {
    "path": "packages/react/src/preview-card/viewport/PreviewCardViewportDataAttributes.ts",
    "content": "export enum PreviewCardViewportDataAttributes {\n  /**\n   * Applied to the direct child of the viewport when no transitions are present or the new content when it's entering.\n   */\n  current = 'data-current',\n  /**\n   * Applied to the direct child of the viewport that contains the exiting content when transitions are present.\n   */\n  previous = 'data-previous',\n  /**\n   * Indicates the direction from which the popup was activated.\n   * This can be used to create directional animations based on how the popup was triggered.\n   * Contains space-separated values for both horizontal and vertical axes.\n   * @type {`${'left' | 'right'} {'top' | 'bottom'}`}\n   */\n  activationDirection = 'data-activation-direction',\n  /**\n   * Indicates that the viewport is currently transitioning between old and new content.\n   */\n  transitioning = 'data-transitioning',\n  /**\n   * Present if animations should be instant.\n   * @type {'delay' | 'dismiss' | 'focus'}\n   */\n  instant = 'data-instant',\n}\n"
  },
  {
    "path": "packages/react/src/progress/index.parts.ts",
    "content": "export { ProgressRoot as Root } from './root/ProgressRoot';\nexport { ProgressTrack as Track } from './track/ProgressTrack';\nexport { ProgressIndicator as Indicator } from './indicator/ProgressIndicator';\nexport { ProgressValue as Value } from './value/ProgressValue';\nexport { ProgressLabel as Label } from './label/ProgressLabel';\n\nexport type { ProgressStatus as Status } from './root/ProgressRoot';\n"
  },
  {
    "path": "packages/react/src/progress/index.ts",
    "content": "export * as Progress from './index.parts';\n\nexport type * from './root/ProgressRoot';\nexport type * from './indicator/ProgressIndicator';\nexport type * from './label/ProgressLabel';\nexport type * from './track/ProgressTrack';\nexport type * from './value/ProgressValue';\n"
  },
  {
    "path": "packages/react/src/progress/indicator/ProgressIndicator.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { screen } from '@mui/internal-test-utils';\nimport { Progress } from '@base-ui/react/progress';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\n\ndescribe('<Progress.Indicator />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Progress.Indicator />, () => ({\n    render: (node) => {\n      return render(<Progress.Root value={40}>{node}</Progress.Root>);\n    },\n    refInstanceof: window.HTMLDivElement,\n  }));\n\n  describe.skipIf(isJSDOM)('internal styles', () => {\n    it('determinate', async () => {\n      await render(\n        <Progress.Root value={33}>\n          <Progress.Track>\n            <Progress.Indicator data-testid=\"indicator\" render={<span />} />\n          </Progress.Track>\n        </Progress.Root>,\n      );\n\n      const indicator = screen.getByTestId('indicator');\n\n      expect(indicator).toHaveComputedStyle({\n        insetInlineStart: '0px',\n        width: '33%',\n      });\n    });\n\n    it('sets zero width when value is 0', async () => {\n      await render(\n        <Progress.Root value={0}>\n          <Progress.Track>\n            <Progress.Indicator data-testid=\"indicator\" />\n          </Progress.Track>\n        </Progress.Root>,\n      );\n\n      const indicator = screen.getByTestId('indicator');\n\n      expect(indicator).toHaveComputedStyle({\n        insetInlineStart: '0px',\n        width: '0px',\n      });\n    });\n\n    it('indeterminate', async () => {\n      await render(\n        <Progress.Root value={null}>\n          <Progress.Track>\n            <Progress.Indicator data-testid=\"indicator\" />\n          </Progress.Track>\n        </Progress.Root>,\n      );\n\n      const indicator = screen.getByTestId('indicator');\n\n      expect(indicator).toHaveComputedStyle({});\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/progress/indicator/ProgressIndicator.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { valueToPercent } from '../../utils/valueToPercent';\nimport type { ProgressRootState } from '../root/ProgressRoot';\nimport { useProgressRootContext } from '../root/ProgressRootContext';\nimport { progressStateAttributesMapping } from '../root/stateAttributesMapping';\nimport type { BaseUIComponentProps } from '../../utils/types';\n\n/**\n * Visualizes the completion status of the task.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Progress](https://base-ui.com/react/components/progress)\n */\nexport const ProgressIndicator = React.forwardRef(function ProgressIndicator(\n  componentProps: ProgressIndicator.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, ...elementProps } = componentProps;\n\n  const { max, min, value, state } = useProgressRootContext();\n\n  const percentageValue =\n    Number.isFinite(value) && value !== null ? valueToPercent(value, min, max) : null;\n\n  const getStyles = React.useCallback(() => {\n    if (percentageValue == null) {\n      return {};\n    }\n\n    return {\n      insetInlineStart: 0,\n      height: 'inherit',\n      width: `${percentageValue}%`,\n    };\n  }, [percentageValue]);\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: forwardedRef,\n    props: [\n      {\n        style: getStyles(),\n      },\n      elementProps,\n    ],\n    stateAttributesMapping: progressStateAttributesMapping,\n  });\n\n  return element;\n});\n\nexport interface ProgressIndicatorState extends ProgressRootState {}\n\nexport interface ProgressIndicatorProps extends BaseUIComponentProps<\n  'div',\n  ProgressIndicatorState\n> {}\n\nexport namespace ProgressIndicator {\n  export type State = ProgressIndicatorState;\n  export type Props = ProgressIndicatorProps;\n}\n"
  },
  {
    "path": "packages/react/src/progress/indicator/ProgressIndicatorDataAttributes.ts",
    "content": "export enum ProgressIndicatorDataAttributes {\n  /**\n   * Present when the progress has completed.\n   */\n  complete = 'data-complete',\n  /**\n   * Present when the progress is in indeterminate state.\n   */\n  indeterminate = 'data-indeterminate',\n  /**\n   * Present while the progress is progressing.\n   */\n  progressing = 'data-progressing',\n}\n"
  },
  {
    "path": "packages/react/src/progress/label/ProgressLabel.test.tsx",
    "content": "import { Progress } from '@base-ui/react/progress';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Progress.Label />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Progress.Label />, () => ({\n    render: (node) => {\n      return render(<Progress.Root value={40}>{node}</Progress.Root>);\n    },\n    refInstanceof: window.HTMLSpanElement,\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/progress/label/ProgressLabel.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useRegisteredLabelId } from '../../utils/useRegisteredLabelId';\nimport { useProgressRootContext } from '../root/ProgressRootContext';\nimport { progressStateAttributesMapping } from '../root/stateAttributesMapping';\nimport type { ProgressRootState } from '../root/ProgressRoot';\nimport type { BaseUIComponentProps } from '../../utils/types';\n\n/**\n * An accessible label for the progress bar.\n * Renders a `<span>` element.\n *\n * Documentation: [Base UI Progress](https://base-ui.com/react/components/progress)\n */\nexport const ProgressLabel = React.forwardRef(function ProgressLabel(\n  componentProps: ProgressLabel.Props,\n  forwardedRef: React.ForwardedRef<HTMLSpanElement>,\n) {\n  const { render, className, id: idProp, ...elementProps } = componentProps;\n\n  const { setLabelId, state } = useProgressRootContext();\n\n  const id = useRegisteredLabelId(idProp, setLabelId);\n\n  const element = useRenderElement('span', componentProps, {\n    state,\n    ref: forwardedRef,\n    props: [\n      {\n        id,\n        role: 'presentation',\n      },\n      elementProps,\n    ],\n    stateAttributesMapping: progressStateAttributesMapping,\n  });\n\n  return element;\n});\n\nexport interface ProgressLabelState extends ProgressRootState {}\n\nexport interface ProgressLabelProps extends BaseUIComponentProps<'span', ProgressLabelState> {}\n\nexport namespace ProgressLabel {\n  export type State = ProgressLabelState;\n  export type Props = ProgressLabelProps;\n}\n"
  },
  {
    "path": "packages/react/src/progress/label/ProgressLabelDataAttributes.ts",
    "content": "export enum ProgressLabelDataAttributes {\n  /**\n   * Present when the progress has completed.\n   */\n  complete = 'data-complete',\n  /**\n   * Present when the progress is in indeterminate state.\n   */\n  indeterminate = 'data-indeterminate',\n  /**\n   * Present while the progress is progressing.\n   */\n  progressing = 'data-progressing',\n}\n"
  },
  {
    "path": "packages/react/src/progress/root/ProgressRoot.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { screen } from '@mui/internal-test-utils';\nimport { Progress } from '@base-ui/react/progress';\nimport { createRenderer, describeConformance } from '#test-utils';\nimport type { ProgressRoot } from './ProgressRoot';\n\nfunction TestProgress(props: ProgressRoot.Props) {\n  return (\n    <Progress.Root {...props}>\n      <Progress.Track>\n        <Progress.Indicator />\n      </Progress.Track>\n    </Progress.Root>\n  );\n}\n\ndescribe('<Progress.Root />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Progress.Root value={50} />, () => ({\n    render,\n    refInstanceof: window.HTMLDivElement,\n  }));\n\n  describe('ARIA attributes', () => {\n    it('sets the correct aria attributes', async () => {\n      await render(\n        <Progress.Root value={30}>\n          <Progress.Label>Downloading</Progress.Label>\n          <Progress.Value />\n          <Progress.Track>\n            <Progress.Indicator />\n          </Progress.Track>\n        </Progress.Root>,\n      );\n\n      const progressbar = screen.getByRole('progressbar');\n      const label = screen.getByText('Downloading');\n\n      expect(progressbar).toHaveAttribute('aria-valuenow', '30');\n      expect(progressbar).toHaveAttribute('aria-valuemin', '0');\n      expect(progressbar).toHaveAttribute('aria-valuemax', '100');\n      expect(progressbar).toHaveAttribute(\n        'aria-valuetext',\n        (0.3).toLocaleString(undefined, { style: 'percent' }),\n      );\n      expect(progressbar.getAttribute('aria-labelledby')).toBe(label.getAttribute('id'));\n    });\n\n    it('should update aria-valuenow when value changes', async () => {\n      const { setProps } = await render(<TestProgress value={50} />);\n      const progressbar = screen.getByRole('progressbar');\n      await setProps({ value: 77 });\n      expect(progressbar).toHaveAttribute('aria-valuenow', '77');\n    });\n  });\n\n  describe('prop: format', () => {\n    it('formats the value', async () => {\n      const format: Intl.NumberFormatOptions = {\n        style: 'currency',\n        currency: 'USD',\n      };\n      function formatValue(v: number) {\n        return new Intl.NumberFormat(undefined, format).format(v);\n      }\n\n      await render(\n        <Progress.Root value={30} format={format}>\n          <Progress.Value data-testid=\"value\" />\n          <Progress.Track>\n            <Progress.Indicator />\n          </Progress.Track>\n        </Progress.Root>,\n      );\n\n      const value = screen.getByTestId('value');\n      const progressbar = screen.getByRole('progressbar');\n      expect(value).toHaveTextContent(formatValue(30));\n      expect(progressbar).toHaveAttribute('aria-valuetext', formatValue(30));\n    });\n  });\n\n  describe('prop: locale', () => {\n    it('sets the locale when formatting the value', async () => {\n      // In German locale, numbers use dot as thousands separator and comma as decimal separator\n      const expectedValue = new Intl.NumberFormat('de-DE').format(70.51);\n\n      await render(\n        <Progress.Root\n          value={70.51}\n          format={{\n            style: 'decimal',\n            minimumFractionDigits: 2,\n            maximumFractionDigits: 2,\n          }}\n          locale=\"de-DE\"\n        >\n          <Progress.Value data-testid=\"value\" />\n        </Progress.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent(expectedValue);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/progress/root/ProgressRoot.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useValueAsRef } from '@base-ui/utils/useValueAsRef';\nimport { visuallyHidden } from '@base-ui/utils/visuallyHidden';\nimport { formatNumberValue } from '../../utils/formatNumber';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { ProgressRootContext } from './ProgressRootContext';\nimport { progressStateAttributesMapping } from './stateAttributesMapping';\nimport { BaseUIComponentProps, HTMLProps } from '../../utils/types';\n\nfunction getDefaultAriaValueText(formattedValue: string | null, value: number | null) {\n  if (value == null) {\n    return 'indeterminate progress';\n  }\n\n  return formattedValue || `${value}%`;\n}\n\n/**\n * Groups all parts of the progress bar and provides the task completion status to screen readers.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Progress](https://base-ui.com/react/components/progress)\n */\nexport const ProgressRoot = React.forwardRef(function ProgressRoot(\n  componentProps: ProgressRoot.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const {\n    format,\n    getAriaValueText = getDefaultAriaValueText,\n    locale,\n    max = 100,\n    min = 0,\n    value,\n    render,\n    className,\n    children,\n    ...elementProps\n  } = componentProps;\n\n  const [labelId, setLabelId] = React.useState<string | undefined>();\n\n  const formatOptionsRef = useValueAsRef(format);\n\n  let status: ProgressStatus = 'indeterminate';\n  if (Number.isFinite(value)) {\n    status = value === max ? 'complete' : 'progressing';\n  }\n  const formattedValue = formatNumberValue(value, locale, formatOptionsRef.current);\n\n  const state: ProgressRootState = React.useMemo(() => ({ status }), [status]);\n\n  const defaultProps: HTMLProps = {\n    'aria-labelledby': labelId,\n    'aria-valuemax': max,\n    'aria-valuemin': min,\n    'aria-valuenow': value ?? undefined,\n    'aria-valuetext': getAriaValueText(formattedValue, value),\n    role: 'progressbar',\n    children: (\n      <React.Fragment>\n        {children}\n        <span role=\"presentation\" style={visuallyHidden}>\n          {/* force NVDA to read the label https://github.com/mui/base-ui/issues/4184 */}x\n        </span>\n      </React.Fragment>\n    ),\n  };\n\n  const contextValue: ProgressRootContext = React.useMemo(\n    () => ({\n      formattedValue,\n      max,\n      min,\n      setLabelId,\n      state,\n      status,\n      value,\n    }),\n    [formattedValue, max, min, setLabelId, state, status, value],\n  );\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: forwardedRef,\n    props: [defaultProps, elementProps],\n    stateAttributesMapping: progressStateAttributesMapping,\n  });\n\n  return (\n    <ProgressRootContext.Provider value={contextValue}>{element}</ProgressRootContext.Provider>\n  );\n});\n\nexport type ProgressStatus = 'indeterminate' | 'progressing' | 'complete';\n\nexport interface ProgressRootState {\n  /**\n   * The current status.\n   */\n  status: ProgressStatus;\n}\n\nexport interface ProgressRootProps extends BaseUIComponentProps<'div', ProgressRootState> {\n  /**\n   * A string value that provides a user-friendly name for `aria-valuenow`, the current value of the meter.\n   */\n  'aria-valuetext'?: React.AriaAttributes['aria-valuetext'] | undefined;\n  /**\n   * Options to format the value.\n   */\n  format?: Intl.NumberFormatOptions | undefined;\n  /**\n   * Accepts a function which returns a string value that provides a human-readable text alternative for the current value of the progress bar.\n   */\n  getAriaValueText?: ((formattedValue: string | null, value: number | null) => string) | undefined;\n  /**\n   * The locale used by `Intl.NumberFormat` when formatting the value.\n   * Defaults to the user's runtime locale.\n   */\n  locale?: Intl.LocalesArgument | undefined;\n  /**\n   * The maximum value.\n   * @default 100\n   */\n  max?: number | undefined;\n  /**\n   * The minimum value.\n   * @default 0\n   */\n  min?: number | undefined;\n  /**\n   * The current value. The component is indeterminate when value is `null`.\n   * @default null\n   */\n  value: number | null;\n}\n\nexport namespace ProgressRoot {\n  export type State = ProgressRootState;\n  export type Props = ProgressRootProps;\n}\n"
  },
  {
    "path": "packages/react/src/progress/root/ProgressRootContext.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport type { ProgressStatus, ProgressRootState } from './ProgressRoot';\n\nexport type ProgressRootContext = {\n  /**\n   * Formatted value of the component.\n   */\n  formattedValue: string;\n  /**\n   * The maximum value.\n   */\n  max: number;\n  /**\n   * The minimum value.\n   */\n  min: number;\n  /**\n   * Value of the component.\n   */\n  value: number | null;\n  setLabelId: React.Dispatch<React.SetStateAction<string | undefined>>;\n  state: ProgressRootState;\n  status: ProgressStatus;\n};\n\n/**\n * @internal\n */\nexport const ProgressRootContext = React.createContext<ProgressRootContext | undefined>(undefined);\n\nexport function useProgressRootContext() {\n  const context = React.useContext(ProgressRootContext);\n  if (context === undefined) {\n    throw new Error(\n      'Base UI: ProgressRootContext is missing. Progress parts must be placed within <Progress.Root>.',\n    );\n  }\n\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/progress/root/ProgressRootDataAttributes.ts",
    "content": "export enum ProgressRootDataAttributes {\n  /**\n   * Present when the progress has completed.\n   */\n  complete = 'data-complete',\n  /**\n   * Present when the progress is in indeterminate state.\n   */\n  indeterminate = 'data-indeterminate',\n  /**\n   * Present while the progress is progressing.\n   */\n  progressing = 'data-progressing',\n}\n"
  },
  {
    "path": "packages/react/src/progress/root/stateAttributesMapping.ts",
    "content": "import type { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport type { ProgressRootState } from './ProgressRoot';\nimport { ProgressRootDataAttributes } from './ProgressRootDataAttributes';\n\nexport const progressStateAttributesMapping: StateAttributesMapping<ProgressRootState> = {\n  status(value): Record<string, string> | null {\n    if (value === 'progressing') {\n      return { [ProgressRootDataAttributes.progressing]: '' };\n    }\n    if (value === 'complete') {\n      return { [ProgressRootDataAttributes.complete]: '' };\n    }\n    if (value === 'indeterminate') {\n      return { [ProgressRootDataAttributes.indeterminate]: '' };\n    }\n    return null;\n  },\n};\n"
  },
  {
    "path": "packages/react/src/progress/track/ProgressTrack.test.tsx",
    "content": "import { Progress } from '@base-ui/react/progress';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Progress.Track />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Progress.Track />, () => ({\n    render: (node) => {\n      return render(<Progress.Root value={40}>{node}</Progress.Root>);\n    },\n    refInstanceof: window.HTMLDivElement,\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/progress/track/ProgressTrack.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useProgressRootContext } from '../root/ProgressRootContext';\nimport { progressStateAttributesMapping } from '../root/stateAttributesMapping';\nimport type { ProgressRootState } from '../root/ProgressRoot';\nimport type { BaseUIComponentProps } from '../../utils/types';\n\n/**\n * Contains the progress bar indicator.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Progress](https://base-ui.com/react/components/progress)\n */\nexport const ProgressTrack = React.forwardRef(function ProgressTrack(\n  componentProps: ProgressTrack.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, ...elementProps } = componentProps;\n\n  const { state } = useProgressRootContext();\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: forwardedRef,\n    props: elementProps,\n    stateAttributesMapping: progressStateAttributesMapping,\n  });\n\n  return element;\n});\n\nexport interface ProgressTrackState extends ProgressRootState {}\n\nexport interface ProgressTrackProps extends BaseUIComponentProps<'div', ProgressTrackState> {}\n\nexport namespace ProgressTrack {\n  export type State = ProgressTrackState;\n  export type Props = ProgressTrackProps;\n}\n"
  },
  {
    "path": "packages/react/src/progress/track/ProgressTrackDataAttributes.ts",
    "content": "export enum ProgressTrackDataAttributes {\n  /**\n   * Present when the progress has completed.\n   */\n  complete = 'data-complete',\n  /**\n   * Present when the progress is in indeterminate state.\n   */\n  indeterminate = 'data-indeterminate',\n  /**\n   * Present while the progress is progressing.\n   */\n  progressing = 'data-progressing',\n}\n"
  },
  {
    "path": "packages/react/src/progress/value/ProgressValue.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport { screen } from '@mui/internal-test-utils';\nimport { Progress } from '@base-ui/react/progress';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Progress.Value />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Progress.Value />, () => ({\n    render: (node) => {\n      return render(<Progress.Root value={40}>{node}</Progress.Root>);\n    },\n    refInstanceof: window.HTMLSpanElement,\n  }));\n\n  describe('prop: children', () => {\n    it('renders the value when children is not provided', async () => {\n      await render(\n        <Progress.Root value={30}>\n          <Progress.Value data-testid=\"value\" />\n        </Progress.Root>,\n      );\n\n      const value = screen.getByTestId('value');\n      expect(value).toHaveTextContent((0.3).toLocaleString(undefined, { style: 'percent' }));\n    });\n\n    it('renders a formatted value when a format is provided', async () => {\n      const format: Intl.NumberFormatOptions = {\n        style: 'currency',\n        currency: 'USD',\n      };\n      function formatValue(v: number) {\n        return new Intl.NumberFormat(undefined, format).format(v);\n      }\n\n      await render(\n        <Progress.Root value={30} format={format}>\n          <Progress.Value data-testid=\"value\" />\n        </Progress.Root>,\n      );\n\n      const value = screen.getByTestId('value');\n      expect(value).toHaveTextContent(formatValue(30));\n    });\n\n    describe('it accepts a render function', () => {\n      it('numerical value', async () => {\n        const renderSpy = vi.fn();\n        const format: Intl.NumberFormatOptions = {\n          style: 'currency',\n          currency: 'USD',\n        };\n        function formatValue(v: number) {\n          return new Intl.NumberFormat(undefined, format).format(v);\n        }\n        await render(\n          <Progress.Root value={30} format={format}>\n            <Progress.Value data-testid=\"value\">{renderSpy}</Progress.Value>\n          </Progress.Root>,\n        );\n        expect(renderSpy.mock.lastCall?.[0]).toEqual(formatValue(30));\n        expect(renderSpy.mock.lastCall?.[1]).toEqual(30);\n      });\n\n      it('indeterminate value', async () => {\n        const renderSpy = vi.fn();\n        const format: Intl.NumberFormatOptions = {\n          style: 'currency',\n          currency: 'USD',\n        };\n        await render(\n          <Progress.Root value={null} format={format}>\n            <Progress.Value data-testid=\"value\">{renderSpy}</Progress.Value>\n          </Progress.Root>,\n        );\n        expect(renderSpy.mock.lastCall?.[0]).toEqual('indeterminate');\n        expect(renderSpy.mock.lastCall?.[1]).toEqual(null);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/progress/value/ProgressValue.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useProgressRootContext } from '../root/ProgressRootContext';\nimport type { ProgressRootState } from '../root/ProgressRoot';\nimport { progressStateAttributesMapping } from '../root/stateAttributesMapping';\n/**\n * A text label displaying the current value.\n * Renders a `<span>` element.\n *\n * Documentation: [Base UI Progress](https://base-ui.com/react/components/progress)\n */\nexport const ProgressValue = React.forwardRef(function ProgressValue(\n  componentProps: ProgressValue.Props,\n  forwardedRef: React.ForwardedRef<HTMLSpanElement>,\n) {\n  const { className, render, children, ...elementProps } = componentProps;\n\n  const { value, formattedValue, state } = useProgressRootContext();\n\n  const formattedValueArg = value == null ? 'indeterminate' : formattedValue;\n  const formattedValueDisplay = value == null ? null : formattedValue;\n\n  const element = useRenderElement('span', componentProps, {\n    state,\n    ref: forwardedRef,\n    props: [\n      {\n        'aria-hidden': true,\n        children:\n          typeof children === 'function'\n            ? children(formattedValueArg, value)\n            : formattedValueDisplay,\n      },\n      elementProps,\n    ],\n    stateAttributesMapping: progressStateAttributesMapping,\n  });\n\n  return element;\n});\n\nexport interface ProgressValueState extends ProgressRootState {}\n\nexport interface ProgressValueProps extends Omit<\n  BaseUIComponentProps<'span', ProgressValueState>,\n  'children'\n> {\n  children?:\n    | null\n    | ((formattedValue: string | null, value: number | null) => React.ReactNode)\n    | undefined;\n}\n\nexport namespace ProgressValue {\n  export type State = ProgressValueState;\n  export type Props = ProgressValueProps;\n}\n"
  },
  {
    "path": "packages/react/src/progress/value/ProgressValueDataAttributes.ts",
    "content": "export enum ProgressValueDataAttributes {\n  /**\n   * Present when the progress has completed.\n   */\n  complete = 'data-complete',\n  /**\n   * Present when the progress is in indeterminate state.\n   */\n  indeterminate = 'data-indeterminate',\n  /**\n   * Present while the progress is progressing.\n   */\n  progressing = 'data-progressing',\n}\n"
  },
  {
    "path": "packages/react/src/radio/index.parts.ts",
    "content": "export { RadioRoot as Root } from './root/RadioRoot';\nexport { RadioIndicator as Indicator } from './indicator/RadioIndicator';\n"
  },
  {
    "path": "packages/react/src/radio/index.ts",
    "content": "export * as Radio from './index.parts';\n\nexport type * from './root/RadioRoot';\nexport type * from './indicator/RadioIndicator';\n"
  },
  {
    "path": "packages/react/src/radio/indicator/RadioIndicator.test.tsx",
    "content": "import { expect } from 'vitest';\nimport * as React from 'react';\nimport { screen, waitFor } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\nimport { Radio } from '@base-ui/react/radio';\nimport { RadioGroup } from '@base-ui/react/radio-group';\n\ndescribe('<Radio.Indicator />', () => {\n  beforeEach(() => {\n    globalThis.BASE_UI_ANIMATIONS_DISABLED = true;\n  });\n\n  const { render } = createRenderer();\n\n  describeConformance(<Radio.Indicator />, () => ({\n    refInstanceof: window.HTMLSpanElement,\n    render(node) {\n      return render(<Radio.Root value=\"\">{node}</Radio.Root>);\n    },\n  }));\n\n  it('should remove the indicator when there is no exit animation defined', async ({ skip }) => {\n    if (isJSDOM) {\n      skip();\n    }\n\n    function Test() {\n      const [value, setValue] = React.useState('a');\n      return (\n        <div>\n          <button onClick={() => setValue('b')}>Close</button>\n          <RadioGroup value={value}>\n            <Radio.Root value=\"a\">\n              <Radio.Indicator className=\"animation-test-indicator\" data-testid=\"indicator-a\" />\n            </Radio.Root>\n            <Radio.Root value=\"a\">\n              <Radio.Indicator className=\"animation-test-indicator\" />\n            </Radio.Root>\n          </RadioGroup>\n        </div>\n      );\n    }\n\n    const { user } = await render(<Test />);\n\n    expect(screen.getByTestId('indicator-a')).not.toBe(null);\n\n    const closeButton = screen.getByText('Close');\n\n    await user.click(closeButton);\n\n    await waitFor(() => {\n      expect(screen.queryByTestId('indicator-a')).toBe(null);\n    });\n  });\n\n  it('should remove the indicator when the animation finishes', async ({ skip }) => {\n    if (isJSDOM) {\n      skip();\n    }\n\n    globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n    let animationFinished = false;\n    const notifyAnimationFinished = () => {\n      animationFinished = true;\n    };\n\n    function Test() {\n      const style = `\n        @keyframes test-anim {\n          to {\n            opacity: 0;\n          }\n        }\n\n        .animation-test-indicator[data-ending-style] {\n          animation: test-anim 1ms;\n        }\n      `;\n\n      const [value, setValue] = React.useState('a');\n\n      return (\n        <div>\n          {/* eslint-disable-next-line react/no-danger */}\n          <style dangerouslySetInnerHTML={{ __html: style }} />\n          <button onClick={() => setValue('b')}>Close</button>\n          <RadioGroup value={value}>\n            <Radio.Root value=\"a\">\n              <Radio.Indicator\n                className=\"animation-test-indicator\"\n                keepMounted\n                onAnimationEnd={notifyAnimationFinished}\n                data-testid=\"indicator-a\"\n              />\n            </Radio.Root>\n            <Radio.Root value=\"a\">\n              <Radio.Indicator className=\"animation-test-indicator\" keepMounted />\n            </Radio.Root>\n          </RadioGroup>\n        </div>\n      );\n    }\n\n    const { user } = await render(<Test />);\n\n    expect(screen.getByTestId('indicator-a')).not.toBe(null);\n\n    const closeButton = screen.getByText('Close');\n    await user.click(closeButton);\n\n    await waitFor(() => {\n      expect(animationFinished).toBe(true);\n    });\n  });\n\n  describe.skipIf(isJSDOM)('animations', () => {\n    afterEach(() => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = true;\n    });\n\n    it('triggers enter animation via data-starting-style when mounting', async () => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n      let transitionFinished = false;\n      function notifyTransitionFinished() {\n        transitionFinished = true;\n      }\n\n      const style = `\n        .animation-test-indicator {\n          transition: opacity 1ms;\n        }\n\n        .animation-test-indicator[data-starting-style],\n        .animation-test-indicator[data-ending-style] {\n          opacity: 0;\n        }\n      `;\n\n      function Test() {\n        const [value, setValue] = React.useState('b');\n\n        function handleSelectA() {\n          setValue('a');\n        }\n\n        return (\n          <div>\n            {/* eslint-disable-next-line react/no-danger */}\n            <style dangerouslySetInnerHTML={{ __html: style }} />\n            <button onClick={handleSelectA}>Select a</button>\n            <RadioGroup value={value}>\n              <Radio.Root value=\"a\">\n                <Radio.Indicator\n                  className=\"animation-test-indicator\"\n                  data-testid=\"indicator-a\"\n                  onTransitionEnd={notifyTransitionFinished}\n                />\n              </Radio.Root>\n              <Radio.Root value=\"b\">\n                <Radio.Indicator className=\"animation-test-indicator\" data-testid=\"indicator-b\" />\n              </Radio.Root>\n            </RadioGroup>\n          </div>\n        );\n      }\n\n      const { user } = await render(<Test />);\n      expect(screen.queryByTestId('indicator-a')).toBe(null);\n\n      await user.click(screen.getByText('Select a'));\n\n      await waitFor(() => {\n        expect(transitionFinished).toBe(true);\n      });\n\n      expect(screen.getByTestId('indicator-a')).not.toBe(null);\n    });\n\n    it('applies data-ending-style before unmount', async () => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n      const style = `\n        @keyframes test-anim {\n          to {\n            opacity: 0;\n          }\n        }\n\n        .animation-test-indicator[data-ending-style] {\n          animation: test-anim 1ms;\n        }\n      `;\n\n      function Test() {\n        const [value, setValue] = React.useState('a');\n\n        function handleSelectB() {\n          setValue('b');\n        }\n\n        return (\n          <div>\n            {/* eslint-disable-next-line react/no-danger */}\n            <style dangerouslySetInnerHTML={{ __html: style }} />\n            <button onClick={handleSelectB}>Select b</button>\n            <RadioGroup value={value}>\n              <Radio.Root value=\"a\">\n                <Radio.Indicator className=\"animation-test-indicator\" data-testid=\"indicator-a\" />\n              </Radio.Root>\n              <Radio.Root value=\"b\">\n                <Radio.Indicator className=\"animation-test-indicator\" data-testid=\"indicator-b\" />\n              </Radio.Root>\n            </RadioGroup>\n          </div>\n        );\n      }\n\n      const { user } = await render(<Test />);\n      expect(screen.getByTestId('indicator-a')).not.toBe(null);\n\n      await user.click(screen.getByText('Select b'));\n\n      await waitFor(() => {\n        const indicator = screen.queryByTestId('indicator-a');\n        expect(indicator).not.toBe(null);\n        expect(indicator).toHaveAttribute('data-ending-style');\n      });\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('indicator-a')).toBe(null);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/radio/indicator/RadioIndicator.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useRadioRootContext } from '../root/RadioRootContext';\nimport { stateAttributesMapping } from '../utils/stateAttributesMapping';\nimport { useOpenChangeComplete } from '../../utils/useOpenChangeComplete';\nimport { type TransitionStatus, useTransitionStatus } from '../../utils/useTransitionStatus';\n\n/**\n * Indicates whether the radio button is selected.\n * Renders a `<span>` element.\n *\n * Documentation: [Base UI Radio](https://base-ui.com/react/components/radio)\n */\nexport const RadioIndicator = React.forwardRef(function RadioIndicator(\n  componentProps: RadioIndicator.Props,\n  forwardedRef: React.ForwardedRef<HTMLSpanElement>,\n) {\n  const { render, className, keepMounted = false, ...elementProps } = componentProps;\n\n  const rootState = useRadioRootContext();\n\n  const rendered = rootState.checked;\n\n  const { mounted, transitionStatus, setMounted } = useTransitionStatus(rendered);\n\n  const state: RadioIndicatorState = {\n    ...rootState,\n    transitionStatus,\n  };\n\n  const indicatorRef = React.useRef<HTMLSpanElement | null>(null);\n\n  const shouldRender = keepMounted || mounted;\n\n  const element = useRenderElement('span', componentProps, {\n    ref: [forwardedRef, indicatorRef],\n    state,\n    props: elementProps,\n    stateAttributesMapping,\n  });\n\n  useOpenChangeComplete({\n    open: rendered,\n    ref: indicatorRef,\n    onComplete() {\n      if (!rendered) {\n        setMounted(false);\n      }\n    },\n  });\n\n  if (!shouldRender) {\n    return null;\n  }\n\n  return element;\n});\n\nexport interface RadioIndicatorProps extends BaseUIComponentProps<'span', RadioIndicatorState> {\n  /**\n   * Whether to keep the HTML element in the DOM when the radio button is inactive.\n   * @default false\n   */\n  keepMounted?: boolean | undefined;\n}\n\nexport interface RadioIndicatorState {\n  /**\n   * Whether the radio button is currently selected.\n   */\n  checked: boolean;\n  /**\n   * The transition status of the component.\n   */\n  transitionStatus: TransitionStatus;\n}\n\nexport namespace RadioIndicator {\n  export type Props = RadioIndicatorProps;\n  export type State = RadioIndicatorState;\n}\n"
  },
  {
    "path": "packages/react/src/radio/indicator/RadioIndicatorDataAttributes.ts",
    "content": "import { TransitionStatusDataAttributes } from '../../utils/stateAttributesMapping';\n\nexport enum RadioIndicatorDataAttributes {\n  /**\n   * Present when the radio is checked.\n   */\n  checked = 'data-checked',\n  /**\n   * Present when the radio is not checked.\n   */\n  unchecked = 'data-unchecked',\n  /**\n   * Present when the radio is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the radio is readonly.\n   */\n  readonly = 'data-readonly',\n  /**\n   * Present when the radio is required.\n   */\n  required = 'data-required',\n  /**\n   * Present when the radio indicator is animating in.\n   */\n  startingStyle = TransitionStatusDataAttributes.startingStyle,\n  /**\n   * Present when the radio indicator is animating out.\n   */\n  endingStyle = TransitionStatusDataAttributes.endingStyle,\n  /**\n   * Present when the radio is in valid state (when wrapped in Field.Root).\n   */\n  valid = 'data-valid',\n  /**\n   * Present when the radio is in invalid state (when wrapped in Field.Root).\n   */\n  invalid = 'data-invalid',\n  /**\n   * Present when the radio has been touched (when wrapped in Field.Root).\n   */\n  touched = 'data-touched',\n  /**\n   * Present when the radio's value has changed (when wrapped in Field.Root).\n   */\n  dirty = 'data-dirty',\n  /**\n   * Present when the radio is checked (when wrapped in Field.Root).\n   */\n  filled = 'data-filled',\n  /**\n   * Present when the radio is focused (when wrapped in Field.Root).\n   */\n  focused = 'data-focused',\n}\n"
  },
  {
    "path": "packages/react/src/radio/root/RadioRoot.spec.tsx",
    "content": "import { Radio } from '@base-ui/react/radio';\n\nconst value = 'a';\n\n<Radio.Root value={value} />;\n<Radio.Root value={1} />;\n<Radio.Root value={null} />;\n\n<Radio.Root<string | null> value={null} />;\n\n// @ts-expect-error value must match explicit generic type\n<Radio.Root<'a' | 'b'> value=\"c\" />;\n"
  },
  {
    "path": "packages/react/src/radio/root/RadioRoot.test.tsx",
    "content": "import { expect } from 'vitest';\nimport * as React from 'react';\nimport { Radio } from '@base-ui/react/radio';\nimport { RadioGroup } from '@base-ui/react/radio-group';\nimport { fireEvent, screen, waitFor } from '@mui/internal-test-utils';\nimport { describeConformance, createRenderer } from '#test-utils';\n\ndescribe('<Radio.Root />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Radio.Root value=\"\" />, () => ({\n    refInstanceof: window.HTMLSpanElement,\n    testComponentPropWith: 'span',\n    button: true,\n    render,\n  }));\n\n  it('does not forward `value` prop', async () => {\n    await render(\n      <RadioGroup>\n        <Radio.Root value=\"test\" data-testid=\"radio-root\" />\n      </RadioGroup>,\n    );\n\n    expect(screen.getByTestId('radio-root')).not.toHaveAttribute('value');\n  });\n\n  it('allows `null` value', async () => {\n    await render(\n      <RadioGroup>\n        <Radio.Root value={null} data-testid=\"radio-null\" />\n        <Radio.Root value=\"a\" data-testid=\"radio-a\" />\n      </RadioGroup>,\n    );\n\n    const radioNull = screen.getByTestId('radio-null');\n    const radioA = screen.getByTestId('radio-a');\n    fireEvent.click(radioNull);\n    expect(radioNull).toHaveAttribute('aria-checked', 'true');\n    fireEvent.click(radioA);\n    expect(radioNull).toHaveAttribute('aria-checked', 'false');\n  });\n\n  it('associates `id` with the native button when `nativeButton=true`', async () => {\n    await render(\n      <div>\n        <label data-testid=\"label\" htmlFor=\"myRadio\">\n          A\n        </label>\n\n        <RadioGroup defaultValue=\"b\">\n          <Radio.Root value=\"a\" id=\"myRadio\" nativeButton render={<button />} data-testid=\"a\" />\n          <Radio.Root value=\"b\" data-testid=\"b\" />\n        </RadioGroup>\n      </div>,\n    );\n\n    const radioA = screen.getByTestId('a');\n    expect(radioA).toHaveAttribute('id', 'myRadio');\n\n    const hiddenInput = radioA.nextElementSibling as HTMLInputElement | null;\n    expect(hiddenInput?.tagName).toBe('INPUT');\n    expect(hiddenInput).not.toHaveAttribute('id', 'myRadio');\n\n    expect(radioA).toHaveAttribute('aria-checked', 'false');\n    fireEvent.click(screen.getByTestId('label'));\n    expect(radioA).toHaveAttribute('aria-checked', 'true');\n  });\n\n  it('sets `aria-labelledby` from a sibling label associated with the hidden input', async () => {\n    await render(\n      <div>\n        <label htmlFor=\"radio-input\">Label</label>\n        <RadioGroup>\n          <Radio.Root value=\"a\" id=\"radio-input\" />\n        </RadioGroup>\n      </div>,\n    );\n\n    const label = screen.getByText('Label');\n    expect(label.id).not.toBe('');\n    expect(screen.getByRole('radio')).toHaveAttribute('aria-labelledby', label.id);\n  });\n\n  it('updates fallback `aria-labelledby` when the hidden input id changes', async () => {\n    function TestCase() {\n      const [id, setId] = React.useState('radio-input-a');\n\n      return (\n        <React.Fragment>\n          <label htmlFor=\"radio-input-a\">Label A</label>\n          <label htmlFor=\"radio-input-b\">Label B</label>\n          <RadioGroup>\n            <Radio.Root value=\"a\" id={id} />\n          </RadioGroup>\n          <button type=\"button\" onClick={() => setId('radio-input-b')}>\n            Toggle\n          </button>\n        </React.Fragment>\n      );\n    }\n\n    await render(<TestCase />);\n\n    const radio = screen.getByRole('radio');\n    const labelA = screen.getByText('Label A');\n\n    expect(labelA.id).not.toBe('');\n    expect(radio).toHaveAttribute('aria-labelledby', labelA.id);\n\n    fireEvent.click(screen.getByRole('button', { name: 'Toggle' }));\n\n    await waitFor(() => {\n      const labelB = screen.getByText('Label B');\n\n      expect(labelB.id).not.toBe('');\n      expect(labelA.id).not.toBe(labelB.id);\n      expect(radio).toHaveAttribute('aria-labelledby', labelB.id);\n    });\n  });\n\n  describe('prop: disabled', () => {\n    it('uses aria-disabled instead of HTML disabled', async () => {\n      await render(\n        <RadioGroup>\n          <Radio.Root value=\"a\" disabled data-testid=\"radio\" />\n        </RadioGroup>,\n      );\n\n      const radio = screen.getByTestId('radio');\n      expect(radio).not.toHaveAttribute('disabled');\n      expect(radio).toHaveAttribute('aria-disabled', 'true');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/radio/root/RadioRoot.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useMergedRefs } from '@base-ui/utils/useMergedRefs';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { visuallyHidden, visuallyHiddenInput } from '@base-ui/utils/visuallyHidden';\nimport type { BaseUIComponentProps, NonNativeButtonProps } from '../../utils/types';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport { EMPTY_OBJECT } from '../../utils/constants';\nimport { NOOP } from '../../utils/noop';\nimport { stateAttributesMapping } from '../utils/stateAttributesMapping';\nimport { useBaseUiId } from '../../utils/useBaseUiId';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useButton } from '../../use-button';\nimport { ACTIVE_COMPOSITE_ITEM } from '../../composite/constants';\nimport { CompositeItem } from '../../composite/item/CompositeItem';\nimport type { FieldRootState } from '../../field/root/FieldRoot';\nimport { useFieldRootContext } from '../../field/root/FieldRootContext';\nimport { useFieldItemContext } from '../../field/item/FieldItemContext';\nimport { useLabelableContext } from '../../labelable-provider/LabelableContext';\nimport { useAriaLabelledBy } from '../../labelable-provider/useAriaLabelledBy';\nimport { useLabelableId } from '../../labelable-provider/useLabelableId';\nimport { useRadioGroupContext } from '../../radio-group/RadioGroupContext';\nimport { serializeValue } from '../../utils/serializeValue';\nimport { RadioRootContext } from './RadioRootContext';\n\n/**\n * Represents the radio button itself.\n * Renders a `<span>` element and a hidden `<input>` beside.\n *\n * Documentation: [Base UI Radio](https://base-ui.com/react/components/radio)\n */\nexport const RadioRoot = React.forwardRef(function RadioRoot<Value>(\n  componentProps: RadioRoot.Props<Value>,\n  forwardedRef: React.ForwardedRef<HTMLElement>,\n) {\n  const {\n    render,\n    className,\n    disabled: disabledProp = false,\n    readOnly: readOnlyProp = false,\n    required: requiredProp = false,\n    'aria-labelledby': ariaLabelledByProp,\n    value,\n    inputRef: inputRefProp,\n    nativeButton = false,\n    id: idProp,\n    ...elementProps\n  } = componentProps;\n\n  const groupContext = useRadioGroupContext();\n\n  const {\n    disabled: disabledGroup,\n    readOnly: readOnlyGroup,\n    required: requiredGroup,\n    checkedValue,\n    touched = false,\n    validation,\n    name,\n  } = groupContext ?? {};\n  const setCheckedValue = groupContext?.setCheckedValue ?? NOOP;\n  const setTouched = groupContext?.setTouched ?? NOOP;\n  const registerControlRef = groupContext?.registerControlRef ?? NOOP;\n  const registerInputRef = groupContext?.registerInputRef ?? NOOP;\n\n  const {\n    setDirty,\n    validityData,\n    setTouched: setFieldTouched,\n    setFilled,\n    state: fieldState,\n    disabled: fieldDisabled,\n  } = useFieldRootContext();\n  const fieldItemContext = useFieldItemContext();\n  const { labelId, getDescriptionProps } = useLabelableContext();\n\n  const disabled = fieldDisabled || fieldItemContext.disabled || disabledGroup || disabledProp;\n  const readOnly = readOnlyGroup || readOnlyProp;\n  const required = requiredGroup || requiredProp;\n\n  const checked = groupContext ? checkedValue === value : value === '';\n  const serializedValue = React.useMemo(() => serializeValue(value), [value]);\n\n  const radioRef = React.useRef<HTMLElement>(null);\n  const inputRef = React.useRef<HTMLInputElement>(null);\n\n  const handleControlRef = useStableCallback((element: HTMLElement | null) => {\n    if (!element) {\n      return;\n    }\n\n    registerControlRef(element, disabled);\n  });\n\n  const mergedInputRef = useMergedRefs(inputRefProp, inputRef, registerInputRef);\n\n  useIsoLayoutEffect(() => {\n    if (inputRef.current?.checked) {\n      setFilled(true);\n    }\n  }, [setFilled]);\n\n  useIsoLayoutEffect(() => {\n    if (!inputRef.current) {\n      return;\n    }\n\n    if (disabled && checked) {\n      registerInputRef(null);\n      return;\n    }\n\n    if (radioRef.current) {\n      registerControlRef(radioRef.current, disabled);\n    }\n\n    registerInputRef(inputRef.current);\n  }, [checked, disabled, registerControlRef, registerInputRef]);\n\n  const id = useBaseUiId();\n  const inputId = useLabelableId({\n    id: idProp,\n    implicit: false,\n    controlRef: radioRef,\n  });\n  const hiddenInputId = nativeButton ? undefined : inputId;\n  const ariaLabelledBy = useAriaLabelledBy(\n    ariaLabelledByProp,\n    labelId,\n    inputRef,\n    !nativeButton,\n    hiddenInputId,\n  );\n\n  const rootProps: React.ComponentPropsWithRef<'span'> = {\n    role: 'radio',\n    'aria-checked': checked,\n    'aria-required': required || undefined,\n    'aria-readonly': readOnly || undefined,\n    'aria-labelledby': ariaLabelledBy,\n    [ACTIVE_COMPOSITE_ITEM as string]: checked ? '' : undefined,\n    id: nativeButton ? inputId : id,\n    onKeyDown(event) {\n      if (event.key === 'Enter') {\n        event.preventDefault();\n      }\n    },\n    onClick(event) {\n      if (event.defaultPrevented || disabled || readOnly) {\n        return;\n      }\n\n      event.preventDefault();\n\n      inputRef.current?.dispatchEvent(\n        new PointerEvent('click', {\n          bubbles: true,\n          shiftKey: event.shiftKey,\n          ctrlKey: event.ctrlKey,\n          altKey: event.altKey,\n          metaKey: event.metaKey,\n        }),\n      );\n    },\n    onFocus(event) {\n      if (event.defaultPrevented || disabled || readOnly || !touched) {\n        return;\n      }\n\n      inputRef.current?.click();\n\n      setTouched(false);\n    },\n  };\n\n  const { getButtonProps, buttonRef } = useButton({\n    disabled,\n    native: nativeButton,\n  });\n\n  const inputProps: React.ComponentPropsWithRef<'input'> = {\n    type: 'radio',\n    ref: mergedInputRef,\n    id: hiddenInputId,\n    name,\n    tabIndex: -1,\n    style: name ? visuallyHiddenInput : visuallyHidden,\n    'aria-hidden': true,\n    ...(value !== undefined ? { value: serializedValue } : EMPTY_OBJECT),\n    disabled,\n    checked,\n    required,\n    readOnly,\n    onChange(event) {\n      // Workaround for https://github.com/facebook/react/issues/9023\n      if (event.nativeEvent.defaultPrevented) {\n        return;\n      }\n\n      if (disabled || readOnly || value === undefined) {\n        return;\n      }\n\n      const details = createChangeEventDetails(REASONS.none, event.nativeEvent);\n\n      if (details.isCanceled) {\n        return;\n      }\n\n      setFieldTouched(true);\n      setDirty(value !== validityData.initialValue);\n      setFilled(true);\n      setCheckedValue(value, details);\n    },\n    onFocus() {\n      radioRef.current?.focus();\n    },\n  };\n\n  const state: RadioRootState = React.useMemo(\n    () => ({\n      ...fieldState,\n      required,\n      disabled,\n      readOnly,\n      checked,\n    }),\n    [fieldState, disabled, readOnly, checked, required],\n  );\n\n  const contextValue: RadioRootContext = state;\n\n  const isRadioGroup = groupContext !== undefined;\n\n  const refs = [forwardedRef, radioRef, buttonRef, handleControlRef];\n  const props = [\n    rootProps,\n    getDescriptionProps,\n    validation?.getValidationProps ?? EMPTY_OBJECT,\n    elementProps,\n    getButtonProps,\n  ];\n\n  const element = useRenderElement('span', componentProps, {\n    enabled: !isRadioGroup,\n    state,\n    ref: refs,\n    props,\n    stateAttributesMapping,\n  });\n\n  return (\n    <RadioRootContext.Provider value={contextValue}>\n      {isRadioGroup ? (\n        <CompositeItem\n          tag=\"span\"\n          render={render}\n          className={className}\n          state={state}\n          refs={refs}\n          props={props}\n          stateAttributesMapping={stateAttributesMapping}\n        />\n      ) : (\n        element\n      )}\n      <input {...inputProps} />\n    </RadioRootContext.Provider>\n  );\n}) as {\n  <Value>(props: RadioRoot.Props<Value>): React.JSX.Element;\n};\n\nexport interface RadioRootState extends FieldRootState {\n  /**\n   * Whether the radio button is currently selected.\n   */\n  checked: boolean;\n  /**\n   * Whether the component should ignore user interaction.\n   */\n  disabled: boolean;\n  /**\n   * Whether the user should be unable to select the radio button.\n   */\n  readOnly: boolean;\n  /**\n   * Whether the user must choose a value before submitting a form.\n   */\n  required: boolean;\n}\n\nexport interface RadioRootProps<Value = any>\n  extends NonNativeButtonProps, Omit<BaseUIComponentProps<'span', RadioRootState>, 'value'> {\n  /**\n   * The unique identifying value of the radio in a group.\n   */\n  value: Value;\n  /**\n   * Whether the component should ignore user interaction.\n   */\n  disabled?: boolean | undefined;\n  /**\n   * Whether the user must choose a value before submitting a form.\n   */\n  required?: boolean | undefined;\n  /**\n   * Whether the user should be unable to select the radio button.\n   */\n  readOnly?: boolean | undefined;\n  /**\n   * A ref to access the hidden input element.\n   */\n  inputRef?: React.Ref<HTMLInputElement> | undefined;\n}\n\nexport namespace RadioRoot {\n  export type State = RadioRootState;\n  export type Props<TValue = any> = RadioRootProps<TValue>;\n}\n"
  },
  {
    "path": "packages/react/src/radio/root/RadioRootContext.ts",
    "content": "'use client';\nimport * as React from 'react';\n\nexport interface RadioRootContext {\n  disabled: boolean;\n  readOnly: boolean;\n  checked: boolean;\n  required: boolean;\n}\n\nexport const RadioRootContext = React.createContext<RadioRootContext | undefined>(undefined);\n\nexport function useRadioRootContext() {\n  const value = React.useContext(RadioRootContext);\n  if (value === undefined) {\n    throw new Error(\n      'Base UI: RadioRootContext is missing. Radio parts must be placed within <Radio.Root>.',\n    );\n  }\n\n  return value;\n}\n"
  },
  {
    "path": "packages/react/src/radio/root/RadioRootDataAttributes.ts",
    "content": "export enum RadioRootDataAttributes {\n  /**\n   * Present when the radio is checked.\n   */\n  checked = 'data-checked',\n  /**\n   * Present when the radio is not checked.\n   */\n  unchecked = 'data-unchecked',\n  /**\n   * Present when the radio is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the radio is readonly.\n   */\n  readonly = 'data-readonly',\n  /**\n   * Present when the radio is required.\n   */\n  required = 'data-required',\n  /**\n   * Present when the radio is in valid state (when wrapped in Field.Root).\n   */\n  valid = 'data-valid',\n  /**\n   * Present when the radio is in invalid state (when wrapped in Field.Root).\n   */\n  invalid = 'data-invalid',\n  /**\n   * Present when the radio has been touched (when wrapped in Field.Root).\n   */\n  touched = 'data-touched',\n  /**\n   * Present when the radio's value has changed (when wrapped in Field.Root).\n   */\n  dirty = 'data-dirty',\n  /**\n   * Present when the radio is checked (when wrapped in Field.Root).\n   */\n  filled = 'data-filled',\n  /**\n   * Present when the radio is focused (when wrapped in Field.Root).\n   */\n  focused = 'data-focused',\n}\n"
  },
  {
    "path": "packages/react/src/radio/utils/stateAttributesMapping.ts",
    "content": "import type { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport type { TransitionStatus } from '../../utils/useTransitionStatus';\nimport { transitionStatusMapping } from '../../utils/stateAttributesMapping';\nimport { fieldValidityMapping } from '../../field/utils/constants';\nimport { RadioRootDataAttributes } from '../root/RadioRootDataAttributes';\n\nexport const stateAttributesMapping = {\n  checked(value): Record<string, string> {\n    if (value) {\n      return { [RadioRootDataAttributes.checked]: '' };\n    }\n    return { [RadioRootDataAttributes.unchecked]: '' };\n  },\n  ...transitionStatusMapping,\n  ...fieldValidityMapping,\n} satisfies StateAttributesMapping<{\n  checked: boolean;\n  transitionStatus: TransitionStatus;\n  valid: boolean | null;\n}>;\n"
  },
  {
    "path": "packages/react/src/radio-group/RadioGroup.spec.tsx",
    "content": "import { expectType } from '#test-utils';\nimport { RadioGroup } from '@base-ui/react/radio-group';\n\nconst values = ['a', 'b', 'c'];\n\n<RadioGroup\n  value={values[0]}\n  onValueChange={(value) => {\n    expectType<string, typeof value>(value);\n  }}\n/>;\n\nconst narrowedValues = ['a', 'b', 'c'] as const;\ntype NarrowedValue = (typeof narrowedValues)[number];\nconst narrowedIndex: number = 0;\nconst narrowedValue = narrowedValues[narrowedIndex];\n\n<RadioGroup\n  value={narrowedValue}\n  onValueChange={(value) => {\n    expectType<NarrowedValue, typeof value>(value);\n  }}\n/>;\n\n<RadioGroup\n  defaultValue={narrowedValue}\n  onValueChange={(value) => {\n    expectType<NarrowedValue, typeof value>(value);\n  }}\n/>;\n\n<RadioGroup<NarrowedValue>\n  value={narrowedValue}\n  onValueChange={(value) => {\n    expectType<NarrowedValue, typeof value>(value);\n  }}\n/>;\n\n<RadioGroup<string | null>\n  onValueChange={(value) => {\n    expectType<string | null, typeof value>(value);\n  }}\n/>;\n\n<RadioGroup\n  value={null}\n  onValueChange={(value) => {\n    expectType<null, typeof value>(value);\n  }}\n/>;\n\n<RadioGroup\n  defaultValue={null}\n  onValueChange={(value) => {\n    expectType<null, typeof value>(value);\n  }}\n/>;\n"
  },
  {
    "path": "packages/react/src/radio-group/RadioGroup.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { RadioGroup } from '@base-ui/react/radio-group';\nimport { Radio } from '@base-ui/react/radio';\nimport { Field } from '@base-ui/react/field';\nimport { Fieldset } from '@base-ui/react/fieldset';\nimport { Form } from '@base-ui/react/form';\nimport { DirectionProvider, type TextDirection } from '@base-ui/react/direction-provider';\nimport { isJSDOM, createRenderer } from '#test-utils';\nimport { act, screen, fireEvent } from '@mui/internal-test-utils';\nimport { describeConformance } from '../../test/describeConformance';\n\ndescribe('<RadioGroup />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<RadioGroup />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render,\n  }));\n\n  describe('extra props', () => {\n    it('can override the built-in attributes', async () => {\n      const { container } = await render(<RadioGroup role=\"switch\" />);\n      expect(container.firstElementChild as HTMLElement).toHaveAttribute('role', 'switch');\n    });\n  });\n\n  describe('prop: onValueChange', () => {\n    it('should call onValueChange when an item is clicked', async () => {\n      const handleChange = vi.fn();\n      await render(\n        <RadioGroup onValueChange={handleChange}>\n          <Radio.Root value=\"a\" data-testid=\"item\" />\n        </RadioGroup>,\n      );\n\n      const item = screen.getByTestId('item');\n\n      fireEvent.click(item);\n\n      expect(handleChange.mock.calls.length).toBe(1);\n      expect(handleChange.mock.calls[0][0]).toBe('a');\n    });\n\n    it('should report keyboard modifier event properties when calling onCheckedChange', async () => {\n      const handleChange = vi.fn((value, eventDetails) => eventDetails);\n\n      const { user } = await render(\n        <RadioGroup onValueChange={handleChange}>\n          <Radio.Root value=\"a\" data-testid=\"item\" />\n        </RadioGroup>,\n      );\n\n      const item = screen.getByTestId('item');\n\n      await user.keyboard('{Shift>}');\n      await user.click(item);\n      await user.keyboard('{/Shift}');\n\n      expect(handleChange.mock.calls.length).toBe(1);\n      expect(handleChange.mock.results[0]?.value.event.shiftKey).toBe(true);\n    });\n  });\n\n  describe('prop: disabled', () => {\n    it('should have the `aria-disabled` attribute', async () => {\n      await render(\n        <RadioGroup disabled>\n          <Radio.Root value=\"a\" />\n        </RadioGroup>,\n      );\n      expect(screen.getByRole('radiogroup')).toHaveAttribute('aria-disabled', 'true');\n      expect(screen.getByRole('radio')).toHaveAttribute('aria-disabled', 'true');\n      expect(screen.getByRole('radio')).toHaveAttribute('data-disabled');\n      const input = document.querySelector('input[type=\"radio\"]');\n      expect(input).toHaveAttribute('disabled');\n    });\n\n    it('should not have the aria attribute when `disabled` is not set', async () => {\n      await render(<RadioGroup />);\n      expect(screen.getByRole('radiogroup')).not.toHaveAttribute('aria-disabled');\n    });\n\n    it('should not change its state when clicked', async () => {\n      await render(\n        <RadioGroup disabled>\n          <Radio.Root value=\"\" data-testid=\"item\" />\n        </RadioGroup>,\n      );\n\n      const item = screen.getByTestId('item');\n\n      expect(item).toHaveAttribute('aria-checked', 'false');\n\n      fireEvent.click(item);\n\n      expect(item).toHaveAttribute('aria-checked', 'false');\n    });\n  });\n\n  describe('prop: readOnly', () => {\n    it('should have the `aria-readonly` attribute', async () => {\n      await render(<RadioGroup readOnly />);\n      const group = screen.getByRole('radiogroup');\n      expect(group).toHaveAttribute('aria-readonly', 'true');\n    });\n\n    it('should not have the aria attribute when `readOnly` is not set', async () => {\n      await render(<RadioGroup />);\n      const group = screen.getByRole('radiogroup');\n      expect(group).not.toHaveAttribute('aria-readonly');\n    });\n\n    it('should not change its state when clicked', async () => {\n      await render(\n        <RadioGroup readOnly>\n          <Radio.Root value=\"\" data-testid=\"item\" />\n        </RadioGroup>,\n      );\n\n      const item = screen.getByTestId('item');\n\n      expect(item).toHaveAttribute('aria-checked', 'false');\n\n      fireEvent.click(item);\n\n      expect(item).toHaveAttribute('aria-checked', 'false');\n    });\n  });\n\n  it('should update its state if the underlying input is toggled', async () => {\n    await render(\n      <RadioGroup data-testid=\"root\">\n        <Radio.Root value=\"\" data-testid=\"item\" />\n      </RadioGroup>,\n    );\n\n    const group = screen.getByTestId('root');\n    const item = screen.getByTestId('item');\n\n    const input = group.querySelector<HTMLInputElement>('input')!;\n\n    fireEvent.click(input);\n\n    expect(item).toHaveAttribute('aria-checked', 'true');\n  });\n\n  it('should place the style hooks on the root and subcomponents', async () => {\n    await render(\n      <RadioGroup defaultValue=\"1\" disabled readOnly required>\n        <Radio.Root value=\"1\" data-testid=\"item\">\n          <Radio.Indicator data-testid=\"indicator\" />\n        </Radio.Root>\n      </RadioGroup>,\n    );\n\n    const root = screen.getByRole('radiogroup');\n    const item = screen.getByTestId('item');\n    const indicator = screen.getByTestId('indicator');\n\n    expect(root).toHaveAttribute('data-disabled', '');\n    expect(root).toHaveAttribute('data-readonly', '');\n    expect(root).toHaveAttribute('data-required', '');\n\n    expect(item).toHaveAttribute('data-checked', '');\n    expect(item).toHaveAttribute('data-disabled', '');\n    expect(item).toHaveAttribute('data-readonly', '');\n    expect(item).toHaveAttribute('data-required', '');\n\n    expect(indicator).toHaveAttribute('data-checked', '');\n    expect(indicator).toHaveAttribute('data-disabled', '');\n    expect(indicator).toHaveAttribute('data-readonly', '');\n    expect(indicator).toHaveAttribute('data-required', '');\n  });\n\n  it('should set the name attribute on each radio input', async () => {\n    await render(\n      <RadioGroup name=\"radio-group\">\n        <Radio.Root value=\"a\" data-testid=\"radio\" />\n      </RadioGroup>,\n    );\n    const radio = screen.getByTestId('radio');\n    const input = radio.nextElementSibling as HTMLInputElement;\n\n    expect(input).toHaveAttribute('name', 'radio-group');\n    expect(input).toHaveAttribute('value', 'a');\n  });\n\n  it('points inputRef to the checked radio input when present', async () => {\n    const groupInputRef = React.createRef<HTMLInputElement>();\n\n    await render(\n      <RadioGroup defaultValue=\"a\" inputRef={groupInputRef}>\n        <Radio.Root value=\"a\" data-testid=\"radio-a\" />\n        <Radio.Root value=\"b\" data-testid=\"radio-b\" />\n      </RadioGroup>,\n    );\n\n    const radioA = screen.getByTestId('radio-a');\n    const radioB = screen.getByTestId('radio-b');\n    const inputA = radioA.nextElementSibling as HTMLInputElement;\n    const inputB = radioB.nextElementSibling as HTMLInputElement;\n\n    expect(groupInputRef.current).toBe(inputA);\n\n    fireEvent.click(radioB);\n\n    expect(groupInputRef.current).toBe(inputB);\n  });\n\n  it('allows reading inputRef.current in an effect', async () => {\n    let observedValue: string | null = null;\n\n    function App() {\n      const inputRef = React.useRef<HTMLInputElement>(null);\n\n      React.useLayoutEffect(() => {\n        observedValue = inputRef.current?.value ?? null;\n      });\n\n      return (\n        <RadioGroup defaultValue=\"a\" inputRef={inputRef}>\n          <Radio.Root value=\"a\" />\n          <Radio.Root value=\"b\" />\n        </RadioGroup>\n      );\n    }\n\n    await render(<App />);\n\n    expect(observedValue).toBe('a');\n  });\n\n  it('supports inputRef as a function', async () => {\n    const inputRefSpy = vi.fn();\n\n    await render(\n      <RadioGroup defaultValue=\"a\" inputRef={inputRefSpy}>\n        <Radio.Root value=\"a\" data-testid=\"radio-a\" />\n        <Radio.Root value=\"b\" data-testid=\"radio-b\" />\n      </RadioGroup>,\n    );\n\n    const radioA = screen.getByTestId('radio-a');\n    const radioB = screen.getByTestId('radio-b');\n    const inputA = radioA.nextElementSibling as HTMLInputElement;\n    const inputB = radioB.nextElementSibling as HTMLInputElement;\n\n    fireEvent.click(radioB);\n\n    expect(inputRefSpy.mock.calls.some((args) => args[0] === inputA)).toBe(true);\n    expect(inputRefSpy.mock.calls.some((args) => args[0] === inputB)).toBe(true);\n    expect(inputRefSpy.mock.lastCall?.[0]).toBe(inputB);\n  });\n\n  it('skips disabled radios when assigning inputRef', async () => {\n    const groupInputRef = React.createRef<HTMLInputElement>();\n\n    await render(\n      <RadioGroup inputRef={groupInputRef}>\n        <Radio.Root value=\"a\" disabled data-testid=\"radio-a\" />\n        <Radio.Root value=\"b\" data-testid=\"radio-b\" />\n      </RadioGroup>,\n    );\n\n    const inputB = (screen.getByTestId('radio-b').nextElementSibling ??\n      null) as HTMLInputElement | null;\n\n    expect(groupInputRef.current).toBe(inputB);\n  });\n\n  it('points inputRef to the first radio input when nativeButton wraps a button', async () => {\n    const groupInputRef = React.createRef<HTMLInputElement>();\n\n    await render(\n      <RadioGroup inputRef={groupInputRef}>\n        <Radio.Root\n          nativeButton\n          value=\"a\"\n          render={(props) => (\n            <label>\n              <button {...props} data-testid=\"radio-a\" />\n              <span>Label A</span>\n            </label>\n          )}\n        />\n        <Radio.Root\n          nativeButton\n          value=\"b\"\n          render={(props) => (\n            <label>\n              <button {...props} data-testid=\"radio-b\" />\n              <span>Label B</span>\n            </label>\n          )}\n        />\n      </RadioGroup>,\n    );\n\n    const inputs = document.querySelectorAll<HTMLInputElement>('input[type=\"radio\"]');\n    expect(inputs.length).toBe(2);\n    expect(groupInputRef.current).toBe(inputs[0]);\n  });\n\n  it('keeps inputRef pointing to the first radio when the value is cleared', async () => {\n    const groupInputRef = React.createRef<HTMLInputElement>();\n\n    function App() {\n      const [value, setValue] = React.useState<null | string>('a');\n\n      return (\n        <React.Fragment>\n          <RadioGroup value={value} inputRef={groupInputRef}>\n            <Radio.Root value=\"a\" data-testid=\"radio-a\" />\n            <Radio.Root value=\"b\" data-testid=\"radio-b\" />\n          </RadioGroup>\n          <button type=\"button\" onClick={() => setValue(null)}>\n            Clear\n          </button>\n        </React.Fragment>\n      );\n    }\n\n    await render(<App />);\n\n    const radioA = screen.getByTestId('radio-a');\n    const inputA = radioA.nextElementSibling as HTMLInputElement;\n\n    expect(groupInputRef.current).toBe(inputA);\n\n    fireEvent.click(screen.getByText('Clear'));\n\n    expect(groupInputRef.current).toBe(inputA);\n  });\n\n  it.skipIf(isJSDOM)(\n    'should return null when no radio is selected (matching native behavior)',\n    async () => {\n      await render(\n        <form\n          onSubmit={(event) => {\n            event.preventDefault();\n            const formData = new FormData(event.currentTarget);\n            expect(formData.get('test-group')).toBe(null);\n          }}\n        >\n          <RadioGroup name=\"test-group\">\n            <Radio.Root value=\"option-a\" />\n            <Radio.Root value=\"option-b\" />\n          </RadioGroup>\n          <button type=\"submit\">Submit</button>\n        </form>,\n      );\n\n      const submitButton = screen.getByRole('button');\n      submitButton.click();\n    },\n  );\n\n  it.skipIf(isJSDOM)('should return null in form data when no radio is selected', async () => {\n    await render(\n      <form data-testid=\"form\">\n        <RadioGroup name=\"group\">\n          <Radio.Root value=\"a\" />\n          <Radio.Root value=\"b\" />\n          <Radio.Root value=\"c\" />\n        </RadioGroup>\n      </form>,\n    );\n\n    const form = screen.getByTestId('form') as HTMLFormElement;\n    const formData = new FormData(form);\n    expect(formData.get('group')).toBe(null);\n  });\n\n  it.skipIf(isJSDOM)('should include selected radio value in form data', async () => {\n    await render(\n      <form data-testid=\"form\">\n        <RadioGroup name=\"group\">\n          <Radio.Root value=\"a\" data-testid=\"radio-a\" />\n          <Radio.Root value=\"b\" />\n          <Radio.Root value=\"c\" />\n        </RadioGroup>\n      </form>,\n    );\n\n    const radio = screen.getByTestId('radio-a');\n    const form = screen.getByTestId('form') as HTMLFormElement;\n\n    await act(async () => {\n      radio.click();\n    });\n\n    const formData = new FormData(form);\n    expect(formData.get('group')).toBe('a');\n  });\n\n  it('should automatically select radio upon navigation', async () => {\n    const { user } = await render(\n      <RadioGroup>\n        <Radio.Root value=\"a\" data-testid=\"a\" />\n        <Radio.Root value=\"b\" data-testid=\"b\" />\n      </RadioGroup>,\n    );\n\n    const a = screen.getByTestId('a');\n    const b = screen.getByTestId('b');\n\n    act(() => {\n      a.focus();\n    });\n\n    expect(a).toHaveAttribute('aria-checked', 'false');\n\n    await user.keyboard('{ArrowDown}');\n\n    expect(a).toHaveAttribute('aria-checked', 'false');\n\n    expect(b).toHaveFocus();\n    expect(b).toHaveAttribute('aria-checked', 'true');\n  });\n\n  describe('should manage arrow key navigation', () => {\n    [\n      ['ltr', 'ArrowRight', 'ArrowLeft'],\n      ['rtl', 'ArrowLeft', 'ArrowRight'],\n    ].forEach((entry) => {\n      const [direction, horizontalNextKey, horizontalPrevKey] = entry;\n\n      describe.skipIf(isJSDOM && direction === 'rtl')(direction, () => {\n        it(direction, async () => {\n          const { user } = await render(\n            <DirectionProvider direction={direction as TextDirection}>\n              <button data-testid=\"before\" />\n              <RadioGroup>\n                <Radio.Root value=\"a\" data-testid=\"a\" />\n                <Radio.Root value=\"b\" data-testid=\"b\" />\n                <Radio.Root value=\"c\" data-testid=\"c\" />\n              </RadioGroup>\n              <button data-testid=\"after\" />\n            </DirectionProvider>,\n          );\n\n          const a = screen.getByTestId('a');\n          const b = screen.getByTestId('b');\n          const c = screen.getByTestId('c');\n          const after = screen.getByTestId('after');\n\n          act(() => {\n            a.focus();\n          });\n\n          expect(a).toHaveFocus();\n\n          await user.keyboard('{ArrowDown}');\n\n          expect(b).toHaveFocus();\n\n          await user.keyboard('{ArrowDown}');\n\n          expect(c).toHaveFocus();\n\n          await user.keyboard('{ArrowDown}');\n\n          expect(a).toHaveFocus();\n\n          await user.keyboard('{ArrowUp}');\n\n          expect(c).toHaveFocus();\n\n          await user.keyboard('{ArrowUp}');\n\n          expect(b).toHaveFocus();\n\n          await user.keyboard('{ArrowUp}');\n\n          expect(a).toHaveFocus();\n\n          await user.keyboard(`{${horizontalPrevKey}}`);\n\n          expect(c).toHaveFocus();\n\n          await user.keyboard(`{${horizontalNextKey}}`);\n\n          expect(a).toHaveFocus();\n\n          await user.tab();\n\n          expect(after).toHaveFocus();\n\n          await user.tab({ shift: true });\n\n          expect(a).toHaveFocus();\n\n          await user.keyboard(`{${horizontalPrevKey}}`);\n\n          expect(c).toHaveFocus();\n\n          await user.tab({ shift: true });\n          await user.tab();\n\n          expect(c).toHaveFocus();\n        });\n\n        describe('modifier keys', () => {\n          it('when Shift is pressed arrow keys move focus normally', async () => {\n            const { user } = await render(\n              <DirectionProvider direction={direction as TextDirection}>\n                <RadioGroup>\n                  <Radio.Root value=\"a\" data-testid=\"a\" />\n                  <Radio.Root value=\"b\" data-testid=\"b\" />\n                  <Radio.Root value=\"c\" data-testid=\"c\" />\n                </RadioGroup>\n              </DirectionProvider>,\n            );\n\n            const a = screen.getByTestId('a');\n            const b = screen.getByTestId('b');\n            const c = screen.getByTestId('c');\n\n            await user.keyboard('{Tab}');\n            expect(a).toHaveFocus();\n\n            await user.keyboard(`{Shift>}{${horizontalNextKey}}`);\n            expect(b).toHaveFocus();\n\n            await user.keyboard('{Shift>}{ArrowDown}');\n            expect(c).toHaveFocus();\n          });\n        });\n      });\n    });\n  });\n\n  describe('style hooks', () => {\n    it('should apply data-checked and data-unchecked to radio root and indicator', async () => {\n      await render(\n        <RadioGroup>\n          <Radio.Root value=\"a\" data-testid=\"a\">\n            <Radio.Indicator keepMounted data-testid=\"indicator-a\" />\n          </Radio.Root>\n          <Radio.Root value=\"b\" data-testid=\"b\">\n            <Radio.Indicator keepMounted data-testid=\"indicator-b\" />\n          </Radio.Root>\n        </RadioGroup>,\n      );\n\n      const a = screen.getByTestId('a');\n      const b = screen.getByTestId('b');\n      const indicatorA = screen.getByTestId('indicator-a');\n      const indicatorB = screen.getByTestId('indicator-b');\n\n      expect(a).toHaveAttribute('data-unchecked', '');\n      expect(indicatorA).toHaveAttribute('data-unchecked', '');\n\n      expect(b).toHaveAttribute('data-unchecked', '');\n      expect(indicatorB).toHaveAttribute('data-unchecked', '');\n\n      fireEvent.click(a);\n\n      expect(a).toHaveAttribute('data-checked', '');\n      expect(indicatorA).toHaveAttribute('data-checked', '');\n\n      expect(b).toHaveAttribute('data-unchecked', '');\n      expect(indicatorB).toHaveAttribute('data-unchecked', '');\n\n      fireEvent.click(b);\n\n      expect(a).toHaveAttribute('data-unchecked', '');\n      expect(indicatorA).toHaveAttribute('data-unchecked', '');\n\n      expect(b).toHaveAttribute('data-checked', '');\n      expect(indicatorB).toHaveAttribute('data-checked', '');\n\n      fireEvent.click(a);\n\n      expect(a).toHaveAttribute('data-checked', '');\n      expect(indicatorA).toHaveAttribute('data-checked', '');\n\n      expect(b).toHaveAttribute('data-unchecked', '');\n      expect(indicatorB).toHaveAttribute('data-unchecked', '');\n    });\n  });\n\n  it('does not forward `value` prop', async () => {\n    await render(\n      <RadioGroup value=\"test\" data-testid=\"radio-group\">\n        <Radio.Root value=\"\" />\n      </RadioGroup>,\n    );\n\n    expect(screen.getByTestId('radio-group')).not.toHaveAttribute('value');\n  });\n\n  it('sets tabIndex=0 to the correct element initially', async () => {\n    await render(\n      <RadioGroup defaultValue=\"b\">\n        <Radio.Root value=\"a\" data-testid=\"radio-a\" />\n        <Radio.Root value=\"b\" data-testid=\"radio-b\" />\n      </RadioGroup>,\n    );\n\n    const radioA = screen.getByTestId('radio-a');\n    const radioB = screen.getByTestId('radio-b');\n\n    expect(radioA).not.toHaveAttribute('tabindex', '0');\n    expect(radioB).toHaveAttribute('tabindex', '0');\n  });\n\n  describe('with native <label>', () => {\n    it('associates implicitly', async () => {\n      const changeSpy = vi.fn((newValue) => newValue);\n      await render(\n        <RadioGroup onValueChange={changeSpy}>\n          <label data-testid=\"label\">\n            <Radio.Root value=\"apple\" />\n            Apple\n          </label>\n\n          <label data-testid=\"label\">\n            <Radio.Root value=\"banana\" />\n            Banana\n          </label>\n        </RadioGroup>,\n      );\n\n      const [label1, label2] = screen.getAllByTestId('label');\n\n      fireEvent.click(label1);\n      expect(changeSpy.mock.calls.length).toBe(1);\n      expect(changeSpy.mock.results.at(-1)?.value).toBe('apple');\n\n      fireEvent.click(label2);\n      expect(changeSpy.mock.calls.length).toBe(2);\n      expect(changeSpy.mock.results.at(-1)?.value).toBe('banana');\n    });\n\n    it('associates explicitly', async () => {\n      const changeSpy = vi.fn((newValue) => newValue);\n      await render(\n        <RadioGroup onValueChange={changeSpy}>\n          <div>\n            <label data-testid=\"label\" htmlFor=\"RadioA\">\n              Apple\n            </label>\n            <Radio.Root value=\"apple\" id=\"RadioA\" />\n          </div>\n\n          <div>\n            <label data-testid=\"label\" htmlFor=\"RadioB\">\n              Banana\n            </label>\n            <Radio.Root value=\"banana\" id=\"RadioB\" />\n          </div>\n        </RadioGroup>,\n      );\n\n      const [label1, label2] = screen.getAllByTestId('label');\n\n      fireEvent.click(label1);\n      expect(changeSpy.mock.calls.length).toBe(1);\n      expect(changeSpy.mock.results.at(-1)?.value).toBe('apple');\n\n      fireEvent.click(label2);\n      expect(changeSpy.mock.calls.length).toBe(2);\n      expect(changeSpy.mock.results.at(-1)?.value).toBe('banana');\n    });\n  });\n\n  describe('Field', () => {\n    it('passes the `name` prop to the radio input', async () => {\n      await render(\n        <Field.Root name=\"test\" data-testid=\"field\">\n          <RadioGroup name=\"group\">\n            <Field.Item>\n              <Radio.Root value=\"a\" data-testid=\"item\" />\n            </Field.Item>\n          </RadioGroup>\n        </Field.Root>,\n      );\n\n      const radio = screen.getByTestId('item');\n      const input = radio.nextElementSibling as HTMLInputElement;\n\n      expect(input).toHaveAttribute('name', 'test');\n    });\n\n    describe('Field.Root', () => {\n      it('should receive disabled prop from Field.Root', async () => {\n        await render(\n          <Field.Root disabled>\n            <RadioGroup>\n              <Field.Item>\n                <Radio.Root value=\"a\" data-testid=\"radio\" />\n              </Field.Item>\n            </RadioGroup>\n          </Field.Root>,\n        );\n\n        const radioGroup = screen.getByRole('radiogroup');\n        const radio = screen.getByTestId('radio');\n\n        expect(radioGroup).toHaveAttribute('aria-disabled', 'true');\n        expect(radioGroup).toHaveAttribute('data-disabled');\n        expect(radio).toHaveAttribute('aria-disabled', 'true');\n        expect(radio).toHaveAttribute('data-disabled');\n      });\n\n      it('should receive name prop from Field.Root', async () => {\n        await render(\n          <Field.Root name=\"field-radio\">\n            <RadioGroup value=\"a\">\n              <Field.Item>\n                <Radio.Root value=\"a\" data-testid=\"radio\" />\n              </Field.Item>\n            </RadioGroup>\n          </Field.Root>,\n        );\n\n        const radio = screen.getByTestId('radio');\n        const input = radio.nextElementSibling as HTMLInputElement;\n\n        expect(input).toHaveAttribute('name', 'field-radio');\n      });\n\n      it('revalidates when the controlled value changes externally', async () => {\n        const validateSpy = vi.fn((value: unknown) => ((value as string) === 'b' ? 'error' : null));\n\n        function App() {\n          const [value, setValue] = React.useState('a');\n\n          return (\n            <React.Fragment>\n              <Field.Root validationMode=\"onChange\" validate={validateSpy} name=\"choices\">\n                <RadioGroup\n                  value={value}\n                  onValueChange={(nextValue) => setValue(nextValue as string)}\n                >\n                  <Field.Item>\n                    <Radio.Root value=\"a\" data-testid=\"radio\" />\n                  </Field.Item>\n                  <Field.Item>\n                    <Radio.Root value=\"b\" data-testid=\"radio\" />\n                  </Field.Item>\n                </RadioGroup>\n              </Field.Root>\n              <button type=\"button\" onClick={() => setValue('b')}>\n                Select externally\n              </button>\n            </React.Fragment>\n          );\n        }\n\n        await render(<App />);\n\n        const radioGroup = screen.getByRole('radiogroup');\n        const toggle = screen.getByText('Select externally');\n\n        expect(radioGroup).not.toHaveAttribute('aria-invalid');\n        const initialCallCount = validateSpy.mock.calls.length;\n\n        fireEvent.click(toggle);\n\n        expect(validateSpy.mock.calls.length).toBe(initialCallCount + 1);\n        expect(validateSpy.mock.lastCall?.[0]).toBe('b');\n        expect(radioGroup).toHaveAttribute('aria-invalid', 'true');\n      });\n    });\n\n    describe('Field.Label', () => {\n      it('associates implicitly', async () => {\n        const changeSpy = vi.fn((newValue) => newValue);\n        await render(\n          <Field.Root name=\"options\">\n            <RadioGroup onValueChange={changeSpy}>\n              <Field.Item>\n                <Field.Label data-testid=\"label\">\n                  <Radio.Root value=\"apple\" />\n                  Apple\n                </Field.Label>\n              </Field.Item>\n              <Field.Item>\n                <Field.Label data-testid=\"label\">\n                  <Radio.Root value=\"banana\" />\n                  Banana\n                </Field.Label>\n              </Field.Item>\n            </RadioGroup>\n          </Field.Root>,\n        );\n\n        const labels = screen.getAllByTestId('label');\n        expect(labels.length).toBe(2);\n        labels.forEach((label) => {\n          expect(label).toHaveAttribute('for');\n        });\n\n        fireEvent.click(screen.getByText('Apple'));\n        expect(changeSpy.mock.calls.length).toBe(1);\n        expect(changeSpy.mock.results.at(-1)?.value).toBe('apple');\n      });\n\n      it('associates explicitly', async () => {\n        const changeSpy = vi.fn((newValue) => newValue);\n        await render(\n          <Field.Root name=\"options\">\n            <RadioGroup onValueChange={changeSpy}>\n              <Field.Item>\n                <Radio.Root value=\"apple\" />\n                <Field.Label data-testid=\"label\">Apple</Field.Label>\n                <Field.Description data-testid=\"description\">\n                  An apple is the round, edible fruit of an apple tree\n                </Field.Description>\n              </Field.Item>\n              <Field.Item>\n                <Radio.Root value=\"banana\" />\n                <Field.Label data-testid=\"label\">Banana</Field.Label>\n                <Field.Description data-testid=\"description\">\n                  A banana is an elongated, edible fruit\n                </Field.Description>\n              </Field.Item>\n            </RadioGroup>\n          </Field.Root>,\n        );\n\n        const radios = screen.getAllByRole('radio');\n        const labels = screen.getAllByTestId('label');\n        const descriptions = screen.getAllByTestId('description');\n        const inputs = document.querySelectorAll('input[type=\"radio\"]');\n\n        radios.forEach((radio, index) => {\n          const label = labels[index];\n          const description = descriptions[index];\n          const input = inputs[index];\n\n          expect(label.getAttribute('for')).not.toBe(null);\n          expect(label.getAttribute('for')).toBe(input?.getAttribute('id'));\n          expect(description.getAttribute('id')).not.toBe(null);\n          expect(description.getAttribute('id')).toBe(radio.getAttribute('aria-describedby'));\n        });\n\n        fireEvent.click(screen.getByText('Banana'));\n        expect(changeSpy.mock.results.at(-1)?.value).toBe('banana');\n      });\n    });\n\n    describe('Field.Description', () => {\n      it('links the group and individual radios', async () => {\n        await render(\n          <Field.Root name=\"apple\">\n            <RadioGroup defaultValue={[]}>\n              <Field.Description data-testid=\"group-description\">\n                Group description\n              </Field.Description>\n              <Field.Item>\n                <Field.Label>\n                  <Radio.Root value=\"fuji-apple\" />\n                  Fuji\n                </Field.Label>\n              </Field.Item>\n            </RadioGroup>\n          </Field.Root>,\n        );\n\n        const groupDescription = screen.getByTestId('group-description');\n        const groupDescriptionId = groupDescription.getAttribute('id');\n        expect(groupDescriptionId).not.toBe(null);\n        expect(screen.getByRole('radiogroup').getAttribute('aria-describedby')).toContain(\n          groupDescriptionId,\n        );\n        expect(screen.getByRole('radio').getAttribute('aria-describedby')).toContain(\n          groupDescriptionId,\n        );\n      });\n    });\n\n    describe('prop: validationMode', () => {\n      it('onSubmit', async () => {\n        const { user } = await render(\n          <Form>\n            <Field.Root\n              validate={(val) => {\n                if (val === 'a') {\n                  return 'custom error a';\n                }\n\n                if (val === 'c') {\n                  return 'custom error c';\n                }\n                return null;\n              }}\n            >\n              <RadioGroup>\n                <Radio.Root value=\"a\" data-testid=\"item\" />\n                <Radio.Root value=\"b\" data-testid=\"item\" />\n                <Radio.Root value=\"c\" data-testid=\"item\" />\n              </RadioGroup>\n            </Field.Root>\n            <button type=\"submit\">submit</button>\n          </Form>,\n        );\n\n        const radioGroup = screen.getByRole('radiogroup');\n        const [radioA, radioB, radioC] = screen.getAllByTestId('item');\n        expect(radioGroup).not.toHaveAttribute('aria-invalid');\n\n        await user.click(radioA);\n        expect(radioA).toHaveAttribute('data-checked', '');\n        expect(radioGroup).not.toHaveAttribute('aria-invalid');\n\n        await user.click(radioC);\n        expect(radioC).toHaveAttribute('data-checked', '');\n        expect(radioGroup).not.toHaveAttribute('aria-invalid');\n\n        await user.click(screen.getByText('submit'));\n        expect(radioGroup).toHaveAttribute('aria-invalid');\n\n        await user.click(radioB);\n        expect(radioB).toHaveAttribute('data-checked', '');\n        expect(radioGroup).not.toHaveAttribute('aria-invalid');\n      });\n    });\n  });\n\n  describe('Fieldset', () => {\n    it('labels the radio group from the fieldset legend', async () => {\n      await render(\n        <Field.Root name=\"test\">\n          <Fieldset.Root render={<RadioGroup />}>\n            <Fieldset.Legend>Legend</Fieldset.Legend>\n            <Field.Item>\n              <Radio.Root value=\"a\" />\n            </Field.Item>\n          </Fieldset.Root>\n        </Field.Root>,\n      );\n\n      const legend = screen.getByText('Legend');\n      const radioGroup = screen.getByRole('radiogroup');\n\n      expect(radioGroup.getAttribute('aria-labelledby')).toBe(legend.getAttribute('id'));\n    });\n  });\n\n  describe('Form', () => {\n    const { render: renderFakeTimers, clock } = createRenderer({\n      clockOptions: {\n        shouldAdvanceTime: true,\n      },\n    });\n\n    clock.withFakeTimers();\n\n    it('triggers native HTML validation on submit', async () => {\n      const { user } = await renderFakeTimers(\n        <Form>\n          <Field.Root name=\"test\" data-testid=\"field\">\n            <RadioGroup name=\"group\" required>\n              <Field.Item>\n                <Radio.Root value=\"a\" data-testid=\"item\" />\n              </Field.Item>\n            </RadioGroup>\n            <Field.Error match=\"valueMissing\" data-testid=\"error\">\n              required\n            </Field.Error>\n          </Field.Root>\n          <button type=\"submit\">Submit</button>\n        </Form>,\n      );\n\n      const submit = screen.getByText('Submit');\n\n      expect(screen.queryByTestId('error')).toBe(null);\n\n      await user.click(submit);\n\n      const error = screen.getByTestId('error');\n      expect(error).toHaveTextContent('required');\n    });\n\n    it('clears required validation when a value is selected', async () => {\n      const { user } = await renderFakeTimers(\n        <Form>\n          <Field.Root name=\"test\" data-testid=\"field\">\n            <RadioGroup name=\"group\" required data-testid=\"group\">\n              <Radio.Root value=\"a\" data-testid=\"item-a\" />\n              <Radio.Root value=\"b\" data-testid=\"item-b\" />\n            </RadioGroup>\n            <Field.Error match=\"valueMissing\" data-testid=\"error\">\n              required\n            </Field.Error>\n          </Field.Root>\n          <button type=\"submit\">Submit</button>\n        </Form>,\n      );\n\n      expect(screen.queryByTestId('error')).toBe(null);\n\n      const group = screen.getByTestId('group');\n      const radioA = screen.getByTestId('item-a');\n      const radioB = screen.getByTestId('item-b');\n\n      await user.click(screen.getByText('Submit'));\n\n      expect(screen.getByTestId('error')).toHaveTextContent('required');\n      expect(group).toHaveAttribute('aria-invalid', 'true');\n      expect(radioA).toHaveAttribute('aria-invalid', 'true');\n      expect(radioB).toHaveAttribute('aria-invalid', 'true');\n\n      await user.click(radioB);\n\n      expect(screen.queryByTestId('error')).toBe(null);\n      expect(group).not.toHaveAttribute('aria-invalid', 'true');\n      expect(radioA).not.toHaveAttribute('aria-invalid', 'true');\n      expect(radioB).not.toHaveAttribute('aria-invalid', 'true');\n    });\n\n    it('validates when inputRef is a function', async () => {\n      const inputRefSpy = vi.fn(() => () => {});\n      const { user } = await renderFakeTimers(\n        <Form>\n          <Field.Root name=\"test\">\n            <RadioGroup name=\"group\" required inputRef={inputRefSpy}>\n              <Radio.Root value=\"a\" data-testid=\"item-a\" />\n              <Radio.Root value=\"b\" data-testid=\"item-b\" />\n            </RadioGroup>\n            <Field.Error match=\"valueMissing\" data-testid=\"error\">\n              required\n            </Field.Error>\n          </Field.Root>\n          <button type=\"submit\">Submit</button>\n        </Form>,\n      );\n\n      expect(screen.queryByTestId('error')).toBe(null);\n\n      await user.click(screen.getByText('Submit'));\n\n      expect(inputRefSpy.mock.calls.length > 0).toBe(true);\n      expect(screen.getByTestId('error')).toHaveTextContent('required');\n    });\n\n    it('focuses the first enabled radio when all radios start disabled', async () => {\n      function App() {\n        const [disabled, setDisabled] = React.useState(true);\n\n        return (\n          <Form>\n            <Field.Root name=\"test\">\n              <RadioGroup name=\"group\" required>\n                <Radio.Root value=\"a\" disabled={disabled} data-testid=\"item-a\" />\n                <Radio.Root value=\"b\" disabled={disabled} data-testid=\"item-b\" />\n              </RadioGroup>\n            </Field.Root>\n            <button type=\"button\" onClick={() => setDisabled(false)}>\n              Enable\n            </button>\n            <button type=\"submit\">Submit</button>\n          </Form>\n        );\n      }\n\n      const { user } = await renderFakeTimers(<App />);\n\n      await user.click(screen.getByText('Enable'));\n\n      const radioA = screen.getByTestId('item-a');\n\n      await user.click(screen.getByText('Submit'));\n\n      expect(document.activeElement).toBe(radioA);\n    });\n\n    it('clears external errors on change', async () => {\n      await renderFakeTimers(\n        <Form\n          errors={{\n            test: 'test',\n          }}\n        >\n          <Field.Root name=\"test\" data-testid=\"field\">\n            <RadioGroup data-testid=\"radio-group\">\n              <Field.Item>\n                <Radio.Root value=\"a\" data-testid=\"item-a\" />\n              </Field.Item>\n              <Field.Item>\n                <Radio.Root value=\"b\" data-testid=\"item-b\" />\n              </Field.Item>\n            </RadioGroup>\n            <Field.Error data-testid=\"error\" />\n          </Field.Root>\n        </Form>,\n      );\n\n      const itemA = screen.getByTestId('item-a');\n      const radioGroup = screen.getByTestId('radio-group');\n\n      expect(screen.queryByTestId('error')).toHaveTextContent('test');\n\n      fireEvent.click(itemA);\n\n      expect(screen.queryByTestId('error')).toBe(null);\n      expect(radioGroup).not.toHaveAttribute('aria-invalid', 'true');\n    });\n\n    it('appends the id attribute of the error to aria-describedby of individual radios', async () => {\n      const { user } = await renderFakeTimers(\n        <Form>\n          <Field.Root name=\"test\" data-testid=\"field\">\n            <RadioGroup name=\"group\" required>\n              <Field.Item>\n                <Radio.Root value=\"a\" />\n                <Field.Description>description</Field.Description>\n              </Field.Item>\n            </RadioGroup>\n            <Field.Error match=\"valueMissing\" data-testid=\"error\" />\n          </Field.Root>\n          <button type=\"submit\">Submit</button>\n        </Form>,\n      );\n\n      expect(screen.queryByTestId('error')).toBe(null);\n\n      await user.click(screen.getByText('Submit'));\n\n      const error = screen.getByTestId('error');\n      const radio = screen.getByRole('radio');\n      const description = screen.getByText('description');\n      expect(radio.getAttribute('aria-describedby')).toContain(error.getAttribute('id'));\n      expect(radio.getAttribute('aria-describedby')).toContain(description.getAttribute('id'));\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/radio-group/RadioGroup.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useControlled } from '@base-ui/utils/useControlled';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport type { BaseUIComponentProps, HTMLProps } from '../utils/types';\nimport { useBaseUiId } from '../utils/useBaseUiId';\nimport { contains } from '../floating-ui-react/utils';\nimport { SHIFT } from '../composite/composite';\nimport { CompositeRoot } from '../composite/root/CompositeRoot';\nimport { useField } from '../field/useField';\nimport { useFieldRootContext } from '../field/root/FieldRootContext';\nimport { fieldValidityMapping } from '../field/utils/constants';\nimport type { FieldRootState } from '../field/root/FieldRoot';\nimport { useFieldsetRootContext } from '../fieldset/root/FieldsetRootContext';\nimport { useFormContext } from '../form/FormContext';\nimport { useLabelableContext } from '../labelable-provider/LabelableContext';\nimport { useValueChanged } from '../utils/useValueChanged';\nimport { RadioGroupContext } from './RadioGroupContext';\nimport type { BaseUIChangeEventDetails } from '../utils/createBaseUIEventDetails';\nimport { REASONS } from '../utils/reasons';\n\nconst MODIFIER_KEYS = [SHIFT];\n\n/**\n * Provides a shared state to a series of radio buttons.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Radio Group](https://base-ui.com/react/components/radio)\n */\nexport const RadioGroup = React.forwardRef(function RadioGroup<Value>(\n  componentProps: RadioGroup.Props<Value>,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const {\n    render,\n    className,\n    disabled: disabledProp,\n    readOnly,\n    required,\n    onValueChange: onValueChangeProp,\n    value: externalValue,\n    defaultValue,\n    name: nameProp,\n    inputRef: inputRefProp,\n    id: idProp,\n    ...elementProps\n  } = componentProps;\n\n  const {\n    setTouched: setFieldTouched,\n    setFocused,\n    shouldValidateOnChange,\n    validationMode,\n    name: fieldName,\n    disabled: fieldDisabled,\n    state: fieldState,\n    validation,\n    setDirty,\n    setFilled,\n    validityData,\n  } = useFieldRootContext();\n  const { labelId } = useLabelableContext();\n  const { clearErrors } = useFormContext();\n  const fieldsetContext = useFieldsetRootContext(true);\n\n  const disabled = fieldDisabled || disabledProp;\n  const name = fieldName ?? nameProp;\n  const id = useBaseUiId(idProp);\n\n  const [checkedValue, setCheckedValueUnwrapped] = useControlled({\n    controlled: externalValue,\n    default: defaultValue,\n    name: 'RadioGroup',\n    state: 'value',\n  });\n\n  const onValueChange = useStableCallback(onValueChangeProp);\n\n  const setCheckedValue = useStableCallback(\n    (value: Value, eventDetails: RadioGroup.ChangeEventDetails) => {\n      onValueChange(value, eventDetails);\n\n      if (eventDetails.isCanceled) {\n        return;\n      }\n\n      setCheckedValueUnwrapped(value);\n    },\n  );\n\n  const controlRef = React.useRef<HTMLElement>(null);\n  const groupInputRef = React.useRef<HTMLInputElement | null>(null);\n  const firstEnabledInputRef = React.useRef<HTMLInputElement | null>(null);\n\n  function setInputRef(hiddenInput: HTMLInputElement | null) {\n    let cleanup: void | (() => void) | undefined = undefined;\n\n    if (inputRefProp) {\n      if (typeof inputRefProp === 'function') {\n        cleanup = inputRefProp(hiddenInput);\n      } else {\n        inputRefProp.current = hiddenInput;\n      }\n    }\n\n    groupInputRef.current = hiddenInput;\n    validation.inputRef.current = hiddenInput;\n\n    return cleanup;\n  }\n\n  const registerControlRef = useStableCallback(\n    (element: HTMLElement | null, isDisabled = false) => {\n      if (!element) {\n        return;\n      }\n\n      if (isDisabled) {\n        if (controlRef.current === element) {\n          controlRef.current = null;\n        }\n        return;\n      }\n\n      if (controlRef.current == null) {\n        controlRef.current = element;\n      }\n    },\n  );\n\n  const registerInputRef = useStableCallback((input: HTMLInputElement | null) => {\n    if (!input || input.disabled) {\n      return undefined;\n    }\n\n    if (!firstEnabledInputRef.current) {\n      firstEnabledInputRef.current = input;\n    }\n\n    const currentInput = groupInputRef.current;\n    if (input.checked || currentInput == null || currentInput.disabled) {\n      return setInputRef(input);\n    }\n\n    return undefined;\n  });\n\n  useField({\n    id,\n    commit: validation.commit,\n    value: checkedValue,\n    controlRef,\n    name,\n    getValue: () => checkedValue ?? null,\n  });\n\n  useValueChanged(checkedValue, () => {\n    clearErrors(name);\n\n    setDirty(checkedValue !== validityData.initialValue);\n    setFilled(checkedValue != null);\n\n    if (shouldValidateOnChange()) {\n      validation.commit(checkedValue);\n    } else {\n      validation.commit(checkedValue, true);\n    }\n\n    const fallbackInput = firstEnabledInputRef.current;\n    if (checkedValue == null && fallbackInput && !fallbackInput.disabled) {\n      setInputRef(fallbackInput);\n    }\n  });\n\n  const [touched, setTouched] = React.useState(false);\n\n  const ariaLabelledby = elementProps['aria-labelledby'] ?? labelId ?? fieldsetContext?.legendId;\n\n  const state: RadioGroupState = {\n    ...fieldState,\n    disabled: disabled ?? false,\n    required: required ?? false,\n    readOnly: readOnly ?? false,\n  };\n\n  const contextValue: RadioGroupContext<Value> = React.useMemo(\n    () => ({\n      ...fieldState,\n      checkedValue,\n      disabled,\n      validation,\n      name,\n      onValueChange,\n      readOnly,\n      registerControlRef,\n      registerInputRef,\n      required,\n      setCheckedValue,\n      setTouched,\n      touched,\n    }),\n    [\n      checkedValue,\n      disabled,\n      validation,\n      fieldState,\n      name,\n      onValueChange,\n      readOnly,\n      registerControlRef,\n      registerInputRef,\n      required,\n      setCheckedValue,\n      setTouched,\n      touched,\n    ],\n  );\n\n  const defaultProps: HTMLProps = {\n    role: 'radiogroup',\n    'aria-required': required || undefined,\n    'aria-disabled': disabled || undefined,\n    'aria-readonly': readOnly || undefined,\n    'aria-labelledby': ariaLabelledby,\n    onFocus() {\n      setFocused(true);\n    },\n    onBlur(event) {\n      if (!contains(event.currentTarget, event.relatedTarget)) {\n        setFieldTouched(true);\n        setFocused(false);\n\n        if (validationMode === 'onBlur') {\n          validation.commit(checkedValue);\n        }\n      }\n    },\n    onKeyDownCapture(event) {\n      if (event.key.startsWith('Arrow')) {\n        setFieldTouched(true);\n        setTouched(true);\n        setFocused(true);\n      }\n    },\n  };\n\n  return (\n    <RadioGroupContext.Provider value={contextValue}>\n      <CompositeRoot\n        render={render}\n        className={className}\n        state={state}\n        props={[defaultProps, validation.getValidationProps, elementProps]}\n        refs={[forwardedRef]}\n        stateAttributesMapping={fieldValidityMapping}\n        enableHomeAndEndKeys={false}\n        modifierKeys={MODIFIER_KEYS}\n      />\n    </RadioGroupContext.Provider>\n  );\n}) as {\n  <Value>(props: RadioGroup.Props<Value>): React.JSX.Element;\n};\n\nexport interface RadioGroupState extends FieldRootState {\n  /**\n   * Whether the user should be unable to select a different radio button in the group.\n   */\n  readOnly: boolean;\n  /**\n   * Whether the user must tick a radio button within the group before submitting a form.\n   */\n  required: boolean;\n}\n\nexport interface RadioGroupProps<Value = any> extends Omit<\n  BaseUIComponentProps<'div', RadioGroupState>,\n  'value'\n> {\n  /**\n   * Whether the component should ignore user interaction.\n   * @default false\n   */\n  disabled?: boolean | undefined;\n  /**\n   * Whether the user should be unable to select a different radio button in the group.\n   * @default false\n   */\n  readOnly?: boolean | undefined;\n  /**\n   * Whether the user must choose a value before submitting a form.\n   * @default false\n   */\n  required?: boolean | undefined;\n  /**\n   * Identifies the field when a form is submitted.\n   */\n  name?: string | undefined;\n  /**\n   * The controlled value of the radio item that should be currently selected.\n   *\n   * To render an uncontrolled radio group, use the `defaultValue` prop instead.\n   */\n  value?: Value | undefined;\n  /**\n   * The uncontrolled value of the radio button that should be initially selected.\n   *\n   * To render a controlled radio group, use the `value` prop instead.\n   */\n  defaultValue?: Value | undefined;\n  /**\n   * Callback fired when the value changes.\n   */\n  onValueChange?: ((value: Value, eventDetails: RadioGroup.ChangeEventDetails) => void) | undefined;\n  /**\n   * A ref to access the hidden input element.\n   */\n  inputRef?: React.Ref<HTMLInputElement> | undefined;\n}\n\nexport type RadioGroupChangeEventReason = typeof REASONS.none;\n\nexport type RadioGroupChangeEventDetails = BaseUIChangeEventDetails<RadioGroup.ChangeEventReason>;\n\nexport namespace RadioGroup {\n  export type State = RadioGroupState;\n  export type Props<TValue = any> = RadioGroupProps<TValue>;\n  export type ChangeEventReason = RadioGroupChangeEventReason;\n  export type ChangeEventDetails = RadioGroupChangeEventDetails;\n}\n"
  },
  {
    "path": "packages/react/src/radio-group/RadioGroupContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport type { UseFieldValidationReturnValue } from '../field/root/useFieldValidation';\nimport type { BaseUIChangeEventDetails } from '../utils/createBaseUIEventDetails';\nimport type { BaseUIEventReasons } from '../utils/reasons';\n\nexport interface RadioGroupContext<Value> {\n  disabled: boolean | undefined;\n  readOnly: boolean | undefined;\n  required: boolean | undefined;\n  name: string | undefined;\n  checkedValue: Value | undefined;\n  setCheckedValue: (\n    value: Value,\n    eventDetails: BaseUIChangeEventDetails<BaseUIEventReasons['none']>,\n  ) => void;\n  onValueChange: (\n    value: Value,\n    eventDetails: BaseUIChangeEventDetails<BaseUIEventReasons['none']>,\n  ) => void;\n  touched: boolean;\n  setTouched: React.Dispatch<React.SetStateAction<boolean>>;\n  validation?: UseFieldValidationReturnValue | undefined;\n  registerControlRef: (element: HTMLElement | null, disabled?: boolean) => void;\n  registerInputRef: (element: HTMLInputElement | null) => void;\n}\n\nexport const RadioGroupContext = React.createContext<RadioGroupContext<any> | undefined>(undefined);\n\nexport function useRadioGroupContext() {\n  return React.useContext(RadioGroupContext);\n}\n"
  },
  {
    "path": "packages/react/src/radio-group/RadioGroupDataAttributes.ts",
    "content": "export enum RadioGroupDataAttributes {\n  /**\n   * Present when the radio group is disabled.\n   */\n  disabled = 'data-disabled',\n}\n"
  },
  {
    "path": "packages/react/src/radio-group/index.ts",
    "content": "export { RadioGroup } from './RadioGroup';\n\nexport type * from './RadioGroup';\n"
  },
  {
    "path": "packages/react/src/scroll-area/constants.ts",
    "content": "export const SCROLL_TIMEOUT = 500;\nexport const MIN_THUMB_SIZE = 16;\n"
  },
  {
    "path": "packages/react/src/scroll-area/content/ScrollAreaContent.test.tsx",
    "content": "import { ScrollArea } from '@base-ui/react/scroll-area';\nimport { createRenderer } from '#test-utils';\nimport { describeConformance } from '../../../test/describeConformance';\n\ndescribe('<ScrollArea.Content />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<ScrollArea.Content />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <ScrollArea.Root>\n          <ScrollArea.Viewport>{node}</ScrollArea.Viewport>\n        </ScrollArea.Root>,\n      );\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/scroll-area/content/ScrollAreaContent.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useScrollAreaViewportContext } from '../viewport/ScrollAreaViewportContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useScrollAreaRootContext } from '../root/ScrollAreaRootContext';\nimport { scrollAreaStateAttributesMapping } from '../root/stateAttributes';\nimport type { ScrollAreaRootState } from '../root/ScrollAreaRoot';\n\n/**\n * A container for the content of the scroll area.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Scroll Area](https://base-ui.com/react/components/scroll-area)\n */\nexport const ScrollAreaContent = React.forwardRef(function ScrollAreaContent(\n  componentProps: ScrollAreaContent.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, ...elementProps } = componentProps;\n\n  const contentWrapperRef = React.useRef<HTMLDivElement | null>(null);\n\n  const { computeThumbPosition } = useScrollAreaViewportContext();\n  const { viewportState } = useScrollAreaRootContext();\n\n  useIsoLayoutEffect(() => {\n    if (typeof ResizeObserver === 'undefined') {\n      return undefined;\n    }\n\n    let hasInitialized = false;\n    const ro = new ResizeObserver(() => {\n      // ResizeObserver fires once upon observing, so we skip the initial call\n      // to avoid double-calculating the thumb position on mount.\n      if (!hasInitialized) {\n        hasInitialized = true;\n        return;\n      }\n      computeThumbPosition();\n    });\n\n    if (contentWrapperRef.current) {\n      ro.observe(contentWrapperRef.current);\n    }\n\n    return () => {\n      ro.disconnect();\n    };\n  }, [computeThumbPosition]);\n\n  const element = useRenderElement('div', componentProps, {\n    ref: [forwardedRef, contentWrapperRef],\n    state: viewportState,\n    stateAttributesMapping: scrollAreaStateAttributesMapping,\n    props: [\n      {\n        role: 'presentation',\n        style: {\n          minWidth: 'fit-content',\n        },\n      },\n      elementProps,\n    ],\n  });\n\n  return element;\n});\n\nexport interface ScrollAreaContentState extends ScrollAreaRootState {}\n\nexport interface ScrollAreaContentProps extends BaseUIComponentProps<\n  'div',\n  ScrollAreaContentState\n> {}\n\nexport namespace ScrollAreaContent {\n  export type State = ScrollAreaContentState;\n  export type Props = ScrollAreaContentProps;\n}\n"
  },
  {
    "path": "packages/react/src/scroll-area/corner/ScrollAreaCorner.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { ScrollArea } from '@base-ui/react/scroll-area';\nimport { screen, waitFor } from '@mui/internal-test-utils';\nimport { createRenderer, isJSDOM } from '#test-utils';\nimport { describeConformance } from '../../../test/describeConformance';\n\nfunction mockViewportMetrics(viewport: HTMLDivElement | null) {\n  if (!viewport) {\n    return;\n  }\n\n  const metrics = {\n    clientHeight: 100,\n    scrollHeight: 1000,\n    clientWidth: 100,\n    scrollWidth: 1000,\n  };\n\n  for (const [key, value] of Object.entries(metrics)) {\n    const descriptor = Object.getOwnPropertyDescriptor(viewport, key);\n    if (!descriptor || descriptor.configurable) {\n      Object.defineProperty(viewport, key, { value, configurable: true });\n    }\n  }\n}\n\ndescribe('<ScrollArea.Corner />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<ScrollArea.Corner />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <ScrollArea.Root>\n          <ScrollArea.Viewport ref={mockViewportMetrics} style={{ width: 100, height: 100 }}>\n            <div style={{ width: 1000, height: 1000 }} />\n          </ScrollArea.Viewport>\n          <ScrollArea.Scrollbar orientation=\"vertical\" keepMounted style={{ width: 10 }}>\n            <ScrollArea.Thumb />\n          </ScrollArea.Scrollbar>\n          <ScrollArea.Scrollbar orientation=\"horizontal\" keepMounted style={{ height: 10 }}>\n            <ScrollArea.Thumb />\n          </ScrollArea.Scrollbar>\n          {node}\n        </ScrollArea.Root>,\n      );\n    },\n  }));\n\n  describe.skipIf(isJSDOM)('interactions', () => {\n    it('should apply correct corner size when both scrollbars are present', async () => {\n      await render(\n        <ScrollArea.Root style={{ width: 200, height: 200 }}>\n          <ScrollArea.Viewport data-testid=\"viewport\" style={{ width: '100%', height: '100%' }}>\n            <div style={{ width: 1000, height: 1000 }} />\n          </ScrollArea.Viewport>\n          <ScrollArea.Scrollbar orientation=\"vertical\" style={{ width: 10 }} />\n          <ScrollArea.Scrollbar orientation=\"horizontal\" style={{ height: 10 }} />\n          <ScrollArea.Corner data-testid=\"corner\" />\n        </ScrollArea.Root>,\n      );\n\n      const corner = screen.getByTestId('corner');\n\n      await waitFor(() => {\n        const style = getComputedStyle(corner);\n        expect(style.getPropertyValue('--scroll-area-corner-width')).toBe('10px');\n        expect(style.getPropertyValue('--scroll-area-corner-height')).toBe('10px');\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/scroll-area/corner/ScrollAreaCorner.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useScrollAreaRootContext } from '../root/ScrollAreaRootContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\n\n/**\n * A small rectangular area that appears at the intersection of horizontal and vertical scrollbars.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Scroll Area](https://base-ui.com/react/components/scroll-area)\n */\nexport const ScrollAreaCorner = React.forwardRef(function ScrollAreaCorner(\n  componentProps: ScrollAreaCorner.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, ...elementProps } = componentProps;\n\n  const { cornerRef, cornerSize, hiddenState } = useScrollAreaRootContext();\n\n  const element = useRenderElement('div', componentProps, {\n    ref: [forwardedRef, cornerRef],\n    props: [\n      {\n        style: {\n          position: 'absolute',\n          bottom: 0,\n          insetInlineEnd: 0,\n          width: cornerSize.width,\n          height: cornerSize.height,\n        },\n      },\n      elementProps,\n    ],\n  });\n\n  if (hiddenState.corner) {\n    return null;\n  }\n\n  return element;\n});\n\nexport interface ScrollAreaCornerState {}\n\nexport interface ScrollAreaCornerProps extends BaseUIComponentProps<'div', ScrollAreaCornerState> {}\n\nexport namespace ScrollAreaCorner {\n  export type State = ScrollAreaCornerState;\n  export type Props = ScrollAreaCornerProps;\n}\n"
  },
  {
    "path": "packages/react/src/scroll-area/index.parts.ts",
    "content": "export { ScrollAreaRoot as Root } from './root/ScrollAreaRoot';\nexport { ScrollAreaViewport as Viewport } from './viewport/ScrollAreaViewport';\nexport { ScrollAreaScrollbar as Scrollbar } from './scrollbar/ScrollAreaScrollbar';\nexport { ScrollAreaContent as Content } from './content/ScrollAreaContent';\nexport { ScrollAreaThumb as Thumb } from './thumb/ScrollAreaThumb';\nexport { ScrollAreaCorner as Corner } from './corner/ScrollAreaCorner';\n"
  },
  {
    "path": "packages/react/src/scroll-area/index.ts",
    "content": "export * as ScrollArea from './index.parts';\n\nexport type * from './root/ScrollAreaRoot';\nexport type * from './viewport/ScrollAreaViewport';\nexport type * from './scrollbar/ScrollAreaScrollbar';\nexport type * from './content/ScrollAreaContent';\nexport type * from './thumb/ScrollAreaThumb';\nexport type * from './corner/ScrollAreaCorner';\n"
  },
  {
    "path": "packages/react/src/scroll-area/root/ScrollAreaRoot.test.tsx",
    "content": "import { expect } from 'vitest';\nimport * as React from 'react';\nimport { ScrollArea } from '@base-ui/react/scroll-area';\nimport { act, fireEvent, flushMicrotasks, screen, waitFor } from '@mui/internal-test-utils';\nimport { createRenderer, isJSDOM } from '#test-utils';\nimport { describeConformance } from '../../../test/describeConformance';\nimport { DirectionProvider } from '../../direction-provider/DirectionProvider';\nimport { SCROLL_TIMEOUT } from '../constants';\n\nconst VIEWPORT_SIZE = 200;\nconst SCROLLABLE_CONTENT_SIZE = 1000;\nconst SCROLLBAR_WIDTH = 10;\nconst SCROLLBAR_HEIGHT = 10;\n\nasync function withMockResizeObserver(test: (notifyResizeObserver: () => void) => Promise<void>) {\n  const originalResizeObserver = window.ResizeObserver;\n  let notifyResizeObserver: (() => void) | null = null;\n\n  class ResizeObserverMock implements ResizeObserver {\n    callback: ResizeObserverCallback;\n\n    constructor(callback: ResizeObserverCallback) {\n      this.callback = callback;\n    }\n\n    observe() {\n      notifyResizeObserver = () => {\n        this.callback([], this);\n      };\n    }\n\n    unobserve() {}\n\n    disconnect() {}\n\n    takeRecords() {\n      return [];\n    }\n  }\n\n  window.ResizeObserver = ResizeObserverMock;\n\n  try {\n    await test(() => {\n      expect(notifyResizeObserver).not.toBe(null);\n      notifyResizeObserver?.();\n    });\n  } finally {\n    window.ResizeObserver = originalResizeObserver;\n  }\n}\n\ndescribe('<ScrollArea.Root />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<ScrollArea.Root />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render,\n  }));\n\n  describe('data-scrolling attribute', () => {\n    const { render: renderWithClock, clock } = createRenderer();\n\n    clock.withFakeTimers();\n\n    it('adds [data-scrolling] attribute when viewport is scrolled', async () => {\n      await renderWithClock(\n        <ScrollArea.Root data-testid=\"root\" style={{ width: 200, height: 200 }}>\n          <ScrollArea.Viewport data-testid=\"viewport\" style={{ width: '100%', height: '100%' }}>\n            <div style={{ width: 1000, height: 1000 }} />\n          </ScrollArea.Viewport>\n        </ScrollArea.Root>,\n      );\n\n      const root = screen.getByTestId('root');\n      const viewport = screen.getByTestId('viewport');\n\n      expect(root).not.toHaveAttribute('data-scrolling');\n\n      fireEvent.pointerEnter(viewport);\n      fireEvent.scroll(viewport, { target: { scrollTop: 1 } });\n\n      expect(root).toHaveAttribute('data-scrolling', '');\n\n      await clock.tickAsync(SCROLL_TIMEOUT);\n\n      expect(root).not.toHaveAttribute('data-scrolling');\n\n      // Test horizontal scrolling\n      fireEvent.pointerEnter(viewport);\n      fireEvent.scroll(viewport, { target: { scrollLeft: 1 } });\n\n      expect(root).toHaveAttribute('data-scrolling', '');\n\n      await clock.tickAsync(SCROLL_TIMEOUT);\n\n      expect(root).not.toHaveAttribute('data-scrolling');\n    });\n  });\n\n  describe.skipIf(isJSDOM)('sizing', () => {\n    it('recomputes thumb size when becoming visible without requiring scroll', async () => {\n      function App() {\n        const [visible, setVisible] = React.useState(false);\n\n        return (\n          <React.Fragment>\n            <button type=\"button\" onClick={() => setVisible(true)}>\n              show\n            </button>\n            <div style={{ display: visible ? 'block' : 'none' }}>\n              <ScrollArea.Root style={{ width: VIEWPORT_SIZE, height: VIEWPORT_SIZE }}>\n                <ScrollArea.Viewport\n                  data-testid=\"viewport\"\n                  style={{ width: '100%', height: '100%' }}\n                >\n                  <div\n                    style={{ width: SCROLLABLE_CONTENT_SIZE, height: SCROLLABLE_CONTENT_SIZE }}\n                  />\n                </ScrollArea.Viewport>\n                <ScrollArea.Scrollbar orientation=\"vertical\" style={{ display: 'flex' }}>\n                  <ScrollArea.Thumb data-testid=\"vertical-thumb\" style={{ paddingBlock: 8 }} />\n                </ScrollArea.Scrollbar>\n              </ScrollArea.Root>\n            </div>\n          </React.Fragment>\n        );\n      }\n\n      await render(<App />);\n\n      fireEvent.click(screen.getByRole('button', { name: 'show' }));\n\n      const verticalThumb = await screen.findByTestId('vertical-thumb');\n\n      await waitFor(() => {\n        expect(\n          getComputedStyle(verticalThumb).getPropertyValue('--scroll-area-thumb-height'),\n        ).not.toBe('0px');\n      });\n    });\n\n    it('shows scrollbars after mount compute before the first ResizeObserver measurement', async () => {\n      await withMockResizeObserver(async (notifyResizeObserver) => {\n        await render(\n          <ScrollArea.Root style={{ width: VIEWPORT_SIZE, height: VIEWPORT_SIZE }}>\n            <ScrollArea.Viewport data-testid=\"viewport\" style={{ width: '100%', height: '100%' }}>\n              <div style={{ width: SCROLLABLE_CONTENT_SIZE, height: SCROLLABLE_CONTENT_SIZE }} />\n            </ScrollArea.Viewport>\n            <ScrollArea.Scrollbar orientation=\"vertical\" data-testid=\"vertical-scrollbar\">\n              <ScrollArea.Thumb data-testid=\"vertical-thumb\" />\n            </ScrollArea.Scrollbar>\n          </ScrollArea.Root>,\n        );\n\n        const verticalScrollbar = await screen.findByTestId('vertical-scrollbar');\n\n        await waitFor(() => {\n          expect(getComputedStyle(verticalScrollbar).visibility).toBe('visible');\n        });\n\n        await act(async () => {\n          notifyResizeObserver();\n        });\n\n        await waitFor(() => {\n          expect(getComputedStyle(verticalScrollbar).visibility).toBe('visible');\n        });\n      });\n    });\n\n    it('shows keepMounted scrollbar track and thumb after mount compute', async () => {\n      await withMockResizeObserver(async (notifyResizeObserver) => {\n        await render(\n          <ScrollArea.Root style={{ width: VIEWPORT_SIZE, height: VIEWPORT_SIZE }}>\n            <ScrollArea.Viewport data-testid=\"viewport\" style={{ width: '100%', height: '100%' }}>\n              <div style={{ width: SCROLLABLE_CONTENT_SIZE, height: SCROLLABLE_CONTENT_SIZE }} />\n            </ScrollArea.Viewport>\n            <ScrollArea.Scrollbar\n              orientation=\"vertical\"\n              data-testid=\"vertical-scrollbar\"\n              keepMounted\n            >\n              <ScrollArea.Thumb data-testid=\"vertical-thumb\" />\n            </ScrollArea.Scrollbar>\n          </ScrollArea.Root>,\n        );\n\n        const verticalScrollbar = await screen.findByTestId('vertical-scrollbar');\n        const verticalThumb = await screen.findByTestId('vertical-thumb');\n\n        await waitFor(() => {\n          expect(getComputedStyle(verticalScrollbar).visibility).toBe('visible');\n          expect(getComputedStyle(verticalThumb).visibility).toBe('visible');\n        });\n\n        await act(async () => {\n          notifyResizeObserver();\n        });\n\n        await waitFor(() => {\n          expect(getComputedStyle(verticalScrollbar).visibility).toBe('visible');\n          expect(getComputedStyle(verticalThumb).visibility).toBe('visible');\n        });\n      });\n    });\n\n    it('recomputes corner size when content starts overflowing', async () => {\n      await withMockResizeObserver(async (notifyResizeObserver) => {\n        const renderArea = (contentSize: number) => (\n          <ScrollArea.Root style={{ width: VIEWPORT_SIZE, height: VIEWPORT_SIZE }}>\n            <ScrollArea.Viewport data-testid=\"viewport\" style={{ width: '100%', height: '100%' }}>\n              <div style={{ width: contentSize, height: contentSize }} />\n            </ScrollArea.Viewport>\n            <ScrollArea.Scrollbar\n              orientation=\"vertical\"\n              data-testid=\"scrollbar-vertical\"\n              style={{ width: 11 }}\n            >\n              <ScrollArea.Thumb />\n            </ScrollArea.Scrollbar>\n            <ScrollArea.Scrollbar\n              orientation=\"horizontal\"\n              data-testid=\"scrollbar-horizontal\"\n              style={{ height: 13 }}\n            >\n              <ScrollArea.Thumb />\n            </ScrollArea.Scrollbar>\n            <ScrollArea.Corner data-testid=\"corner\" />\n          </ScrollArea.Root>\n        );\n\n        const { rerender } = await render(renderArea(VIEWPORT_SIZE / 2));\n\n        await act(async () => {\n          notifyResizeObserver();\n        });\n\n        expect(screen.queryByTestId('corner')).toBe(null);\n\n        await rerender(renderArea(SCROLLABLE_CONTENT_SIZE));\n\n        await act(async () => {\n          notifyResizeObserver();\n        });\n\n        await waitFor(() => {\n          const corner = screen.getByTestId('corner');\n          expect(corner.style.width).toBe('11px');\n          expect(corner.style.height).toBe('13px');\n        });\n      });\n    });\n\n    it('should correctly set thumb height and width based on scrollable content', async () => {\n      await render(\n        <ScrollArea.Root style={{ width: VIEWPORT_SIZE, height: VIEWPORT_SIZE }}>\n          <ScrollArea.Viewport data-testid=\"viewport\" style={{ width: '100%', height: '100%' }}>\n            <div style={{ width: SCROLLABLE_CONTENT_SIZE, height: SCROLLABLE_CONTENT_SIZE }} />\n          </ScrollArea.Viewport>\n          <ScrollArea.Scrollbar orientation=\"vertical\" data-testid=\"vertical-scrollbar\">\n            <ScrollArea.Thumb data-testid=\"vertical-thumb\" />\n          </ScrollArea.Scrollbar>\n          <ScrollArea.Scrollbar orientation=\"horizontal\" data-testid=\"horizontal-scrollbar\">\n            <ScrollArea.Thumb data-testid=\"horizontal-thumb\" />\n          </ScrollArea.Scrollbar>\n        </ScrollArea.Root>,\n      );\n\n      const verticalThumb = screen.getByTestId('vertical-thumb');\n      const horizontalThumb = screen.getByTestId('horizontal-thumb');\n\n      await waitFor(() => {\n        expect(getComputedStyle(verticalThumb).getPropertyValue('--scroll-area-thumb-height')).toBe(\n          `${(VIEWPORT_SIZE / SCROLLABLE_CONTENT_SIZE) * VIEWPORT_SIZE}px`,\n        );\n        expect(\n          getComputedStyle(horizontalThumb).getPropertyValue('--scroll-area-thumb-width'),\n        ).toBe(`${(VIEWPORT_SIZE / SCROLLABLE_CONTENT_SIZE) * VIEWPORT_SIZE}px`);\n      });\n    });\n\n    it('should not add padding for overlay scrollbars', async () => {\n      await render(\n        <ScrollArea.Root style={{ width: VIEWPORT_SIZE, height: VIEWPORT_SIZE }}>\n          <ScrollArea.Viewport data-testid=\"viewport\" style={{ width: '100%', height: '100%' }}>\n            <div style={{ width: SCROLLABLE_CONTENT_SIZE, height: SCROLLABLE_CONTENT_SIZE }} />\n          </ScrollArea.Viewport>\n          <ScrollArea.Scrollbar\n            orientation=\"vertical\"\n            style={{ width: SCROLLBAR_WIDTH, height: '100%' }}\n          />\n          <ScrollArea.Scrollbar\n            orientation=\"horizontal\"\n            style={{ height: SCROLLBAR_HEIGHT, width: '100%' }}\n          />\n        </ScrollArea.Root>,\n      );\n\n      const contentWrapper = screen.getByTestId('viewport').firstElementChild!;\n      const style = getComputedStyle(contentWrapper);\n\n      expect(style.paddingLeft).toBe('0px');\n      expect(style.paddingRight).toBe('0px');\n      expect(style.paddingBottom).toBe('0px');\n    });\n\n    it('accounts for scrollbar padding', async () => {\n      const PADDING = 8;\n\n      await render(\n        <ScrollArea.Root style={{ width: VIEWPORT_SIZE, height: VIEWPORT_SIZE }}>\n          <ScrollArea.Viewport data-testid=\"viewport\" style={{ width: '100%', height: '100%' }}>\n            <div style={{ width: SCROLLABLE_CONTENT_SIZE, height: SCROLLABLE_CONTENT_SIZE }} />\n          </ScrollArea.Viewport>\n          <ScrollArea.Scrollbar\n            orientation=\"vertical\"\n            data-testid=\"vertical-scrollbar\"\n            style={{ paddingBlock: PADDING }}\n          >\n            <ScrollArea.Thumb data-testid=\"vertical-thumb\" />\n          </ScrollArea.Scrollbar>\n          <ScrollArea.Scrollbar\n            orientation=\"horizontal\"\n            data-testid=\"horizontal-scrollbar\"\n            style={{ paddingInline: PADDING }}\n          >\n            <ScrollArea.Thumb data-testid=\"horizontal-thumb\" />\n          </ScrollArea.Scrollbar>\n        </ScrollArea.Root>,\n      );\n\n      const verticalThumb = screen.getByTestId('vertical-thumb');\n      const horizontalThumb = screen.getByTestId('horizontal-thumb');\n\n      await waitFor(() => {\n        expect(getComputedStyle(verticalThumb).getPropertyValue('--scroll-area-thumb-height')).toBe(\n          `${(VIEWPORT_SIZE - PADDING * 2) * (VIEWPORT_SIZE / SCROLLABLE_CONTENT_SIZE)}px`,\n        );\n        expect(\n          getComputedStyle(horizontalThumb).getPropertyValue('--scroll-area-thumb-width'),\n        ).toBe(`${(VIEWPORT_SIZE - PADDING * 2) * (VIEWPORT_SIZE / SCROLLABLE_CONTENT_SIZE)}px`);\n      });\n    });\n\n    it('accounts for scrollbar margin', async () => {\n      const margin = 11;\n      const viewportSize = 390;\n\n      await render(\n        <ScrollArea.Root style={{ width: viewportSize, height: viewportSize }}>\n          <ScrollArea.Viewport data-testid=\"viewport\" style={{ width: '100%', height: '100%' }}>\n            <div style={{ width: SCROLLABLE_CONTENT_SIZE, height: SCROLLABLE_CONTENT_SIZE }} />\n          </ScrollArea.Viewport>\n          <ScrollArea.Scrollbar\n            orientation=\"vertical\"\n            data-testid=\"vertical-scrollbar\"\n            style={{ marginInline: margin }}\n          >\n            <ScrollArea.Thumb data-testid=\"vertical-thumb\" />\n          </ScrollArea.Scrollbar>\n          <ScrollArea.Scrollbar\n            orientation=\"horizontal\"\n            data-testid=\"horizontal-scrollbar\"\n            style={{ marginBlock: margin }}\n          >\n            <ScrollArea.Thumb data-testid=\"horizontal-thumb\" />\n          </ScrollArea.Scrollbar>\n        </ScrollArea.Root>,\n      );\n\n      const verticalThumb = screen.getByTestId('vertical-thumb');\n      const horizontalThumb = screen.getByTestId('horizontal-thumb');\n\n      await waitFor(() => {\n        expect(getComputedStyle(verticalThumb).getPropertyValue('--scroll-area-thumb-height')).toBe(\n          `${viewportSize * (viewportSize / SCROLLABLE_CONTENT_SIZE)}px`,\n        );\n        expect(\n          getComputedStyle(horizontalThumb).getPropertyValue('--scroll-area-thumb-width'),\n        ).toBe(`${viewportSize * (viewportSize / SCROLLABLE_CONTENT_SIZE)}px`);\n      });\n    });\n\n    it('accounts for thumb margin', async () => {\n      const MARGIN = 8;\n\n      await render(\n        <ScrollArea.Root style={{ width: VIEWPORT_SIZE, height: VIEWPORT_SIZE }}>\n          <ScrollArea.Viewport data-testid=\"viewport\" style={{ width: '100%', height: '100%' }}>\n            <div style={{ width: SCROLLABLE_CONTENT_SIZE, height: SCROLLABLE_CONTENT_SIZE }} />\n          </ScrollArea.Viewport>\n          <ScrollArea.Scrollbar orientation=\"vertical\" data-testid=\"vertical-scrollbar\">\n            <ScrollArea.Thumb data-testid=\"vertical-thumb\" style={{ marginBlock: MARGIN }} />\n          </ScrollArea.Scrollbar>\n          <ScrollArea.Scrollbar orientation=\"horizontal\" data-testid=\"horizontal-scrollbar\">\n            <ScrollArea.Thumb data-testid=\"horizontal-thumb\" style={{ marginInline: MARGIN }} />\n          </ScrollArea.Scrollbar>\n        </ScrollArea.Root>,\n      );\n\n      const verticalThumb = screen.getByTestId('vertical-thumb');\n      const horizontalThumb = screen.getByTestId('horizontal-thumb');\n\n      await waitFor(() => {\n        expect(getComputedStyle(verticalThumb).getPropertyValue('--scroll-area-thumb-height')).toBe(\n          `${(VIEWPORT_SIZE - MARGIN * 2) * (VIEWPORT_SIZE / SCROLLABLE_CONTENT_SIZE)}px`,\n        );\n        expect(\n          getComputedStyle(horizontalThumb).getPropertyValue('--scroll-area-thumb-width'),\n        ).toBe(`${(VIEWPORT_SIZE - MARGIN * 2) * (VIEWPORT_SIZE / SCROLLABLE_CONTENT_SIZE)}px`);\n      });\n    });\n  });\n\n  describe.skipIf(isJSDOM)('overflow data attributes', () => {\n    it('recomputes horizontal overflow edges when direction changes', async () => {\n      const renderArea = (direction: 'ltr' | 'rtl') => (\n        <DirectionProvider direction={direction}>\n          <ScrollArea.Root\n            data-testid=\"root\"\n            style={{ width: VIEWPORT_SIZE, height: VIEWPORT_SIZE, direction }}\n          >\n            <ScrollArea.Viewport data-testid=\"viewport\" style={{ width: '100%', height: '100%' }}>\n              <div style={{ width: SCROLLABLE_CONTENT_SIZE, height: VIEWPORT_SIZE }} />\n            </ScrollArea.Viewport>\n            <ScrollArea.Scrollbar orientation=\"horizontal\">\n              <ScrollArea.Thumb />\n            </ScrollArea.Scrollbar>\n          </ScrollArea.Root>\n        </DirectionProvider>\n      );\n\n      const { rerender } = await render(renderArea('ltr'));\n\n      const root = screen.getByTestId('root');\n      const viewport = screen.getByTestId('viewport');\n\n      await waitFor(() => expect(root).toHaveAttribute('data-has-overflow-x'));\n\n      const maxScrollLeft = viewport.scrollWidth - viewport.clientWidth;\n\n      fireEvent.scroll(viewport, {\n        target: {\n          scrollLeft: maxScrollLeft / 2,\n        },\n      });\n\n      /* eslint-disable testing-library/no-wait-for-multiple-assertions */\n      await waitFor(() => {\n        expect(root).toHaveAttribute('data-overflow-x-start');\n        expect(root).toHaveAttribute('data-overflow-x-end');\n      });\n      /* eslint-enable testing-library/no-wait-for-multiple-assertions */\n\n      await rerender(renderArea('rtl'));\n\n      await act(async () => {\n        viewport.scrollLeft = -maxScrollLeft;\n      });\n\n      await waitFor(() => {\n        expect(root).toHaveAttribute('data-overflow-x-start');\n        expect(root).not.toHaveAttribute('data-overflow-x-end');\n      });\n    });\n\n    it('applies data attributes on root, viewport and scrollbars based on overflow and edges', async () => {\n      await render(\n        <ScrollArea.Root data-testid=\"root\" style={{ width: VIEWPORT_SIZE, height: VIEWPORT_SIZE }}>\n          <ScrollArea.Viewport data-testid=\"viewport\" style={{ width: '100%', height: '100%' }}>\n            <ScrollArea.Content data-testid=\"content\">\n              <div style={{ width: SCROLLABLE_CONTENT_SIZE, height: SCROLLABLE_CONTENT_SIZE }} />\n            </ScrollArea.Content>\n          </ScrollArea.Viewport>\n          <ScrollArea.Scrollbar orientation=\"vertical\" data-testid=\"scrollbar-vertical\">\n            <ScrollArea.Thumb />\n          </ScrollArea.Scrollbar>\n          <ScrollArea.Scrollbar orientation=\"horizontal\" data-testid=\"scrollbar-horizontal\">\n            <ScrollArea.Thumb />\n          </ScrollArea.Scrollbar>\n        </ScrollArea.Root>,\n      );\n\n      const root = screen.getByTestId('root');\n      const viewport = screen.getByTestId('viewport');\n      const content = screen.getByTestId('content');\n      const vScrollbar = screen.getByTestId('scrollbar-vertical');\n      const hScrollbar = screen.getByTestId('scrollbar-horizontal');\n\n      // Initial: at start (top/left)\n      /* eslint-disable testing-library/no-wait-for-multiple-assertions */\n      await waitFor(() => {\n        expect(root).toHaveAttribute('data-has-overflow-x');\n        expect(root).toHaveAttribute('data-has-overflow-y');\n        expect(root).not.toHaveAttribute('data-overflow-x-start');\n        expect(root).toHaveAttribute('data-overflow-x-end');\n        expect(root).not.toHaveAttribute('data-overflow-y-start');\n        expect(root).toHaveAttribute('data-overflow-y-end');\n\n        expect(viewport).toHaveAttribute('data-has-overflow-x');\n        expect(viewport).toHaveAttribute('data-has-overflow-y');\n        expect(viewport).not.toHaveAttribute('data-overflow-x-start');\n        expect(viewport).toHaveAttribute('data-overflow-x-end');\n        expect(viewport).not.toHaveAttribute('data-overflow-y-start');\n        expect(viewport).toHaveAttribute('data-overflow-y-end');\n        expect(content).toHaveAttribute('data-has-overflow-x');\n        expect(content).toHaveAttribute('data-has-overflow-y');\n        expect(content).not.toHaveAttribute('data-overflow-x-start');\n        expect(content).toHaveAttribute('data-overflow-x-end');\n        expect(content).not.toHaveAttribute('data-overflow-y-start');\n        expect(content).toHaveAttribute('data-overflow-y-end');\n\n        expect(vScrollbar).toHaveAttribute('data-has-overflow-y');\n        expect(vScrollbar).not.toHaveAttribute('data-overflow-y-start');\n        expect(vScrollbar).toHaveAttribute('data-overflow-y-end');\n        expect(hScrollbar).toHaveAttribute('data-has-overflow-x');\n        expect(hScrollbar).not.toHaveAttribute('data-overflow-x-start');\n        expect(hScrollbar).toHaveAttribute('data-overflow-x-end');\n      });\n      /* eslint-enable testing-library/no-wait-for-multiple-assertions */\n\n      // Scroll to middle\n      const halfY = (viewport.scrollHeight - viewport.clientHeight) / 2;\n      const halfX = (viewport.scrollWidth - viewport.clientWidth) / 2;\n      fireEvent.scroll(viewport, {\n        target: { scrollTop: halfY, scrollLeft: halfX },\n      });\n      await flushMicrotasks();\n\n      expect(root).toHaveAttribute('data-overflow-y-start');\n      expect(root).toHaveAttribute('data-overflow-y-end');\n      expect(root).toHaveAttribute('data-overflow-x-start');\n      expect(root).toHaveAttribute('data-overflow-x-end');\n\n      expect(viewport).toHaveAttribute('data-overflow-y-start');\n      expect(viewport).toHaveAttribute('data-overflow-y-end');\n      expect(viewport).toHaveAttribute('data-overflow-x-start');\n      expect(viewport).toHaveAttribute('data-overflow-x-end');\n      expect(content).toHaveAttribute('data-overflow-y-start');\n      expect(content).toHaveAttribute('data-overflow-y-end');\n      expect(content).toHaveAttribute('data-overflow-x-start');\n      expect(content).toHaveAttribute('data-overflow-x-end');\n\n      expect(vScrollbar).toHaveAttribute('data-overflow-y-start');\n      expect(vScrollbar).toHaveAttribute('data-overflow-y-end');\n      expect(hScrollbar).toHaveAttribute('data-overflow-x-start');\n      expect(hScrollbar).toHaveAttribute('data-overflow-x-end');\n\n      // Scroll to end\n      fireEvent.scroll(viewport, {\n        target: {\n          scrollTop: viewport.scrollHeight - viewport.clientHeight,\n          scrollLeft: viewport.scrollWidth - viewport.clientWidth,\n        },\n      });\n      await flushMicrotasks();\n\n      expect(root).toHaveAttribute('data-overflow-y-start');\n      expect(root).not.toHaveAttribute('data-overflow-y-end');\n      expect(root).toHaveAttribute('data-overflow-x-start');\n      expect(root).not.toHaveAttribute('data-overflow-x-end');\n\n      expect(viewport).toHaveAttribute('data-overflow-y-start');\n      expect(viewport).not.toHaveAttribute('data-overflow-y-end');\n      expect(viewport).toHaveAttribute('data-overflow-x-start');\n      expect(viewport).not.toHaveAttribute('data-overflow-x-end');\n      expect(content).toHaveAttribute('data-overflow-y-start');\n      expect(content).not.toHaveAttribute('data-overflow-y-end');\n      expect(content).toHaveAttribute('data-overflow-x-start');\n      expect(content).not.toHaveAttribute('data-overflow-x-end');\n\n      expect(vScrollbar).toHaveAttribute('data-overflow-y-start');\n      expect(vScrollbar).not.toHaveAttribute('data-overflow-y-end');\n      expect(hScrollbar).toHaveAttribute('data-overflow-x-start');\n      expect(hScrollbar).not.toHaveAttribute('data-overflow-x-end');\n    });\n\n    it('treats near-edge scroll offsets as fully scrolled', async () => {\n      await render(\n        <ScrollArea.Root data-testid=\"root\" style={{ width: VIEWPORT_SIZE, height: VIEWPORT_SIZE }}>\n          <ScrollArea.Viewport data-testid=\"viewport\" style={{ width: '100%', height: '100%' }}>\n            <ScrollArea.Content data-testid=\"content\">\n              <div style={{ width: SCROLLABLE_CONTENT_SIZE, height: SCROLLABLE_CONTENT_SIZE }} />\n            </ScrollArea.Content>\n          </ScrollArea.Viewport>\n          <ScrollArea.Scrollbar orientation=\"vertical\" data-testid=\"scrollbar-vertical\">\n            <ScrollArea.Thumb />\n          </ScrollArea.Scrollbar>\n          <ScrollArea.Scrollbar orientation=\"horizontal\" data-testid=\"scrollbar-horizontal\">\n            <ScrollArea.Thumb />\n          </ScrollArea.Scrollbar>\n        </ScrollArea.Root>,\n      );\n\n      const root = screen.getByTestId('root');\n      const viewport = screen.getByTestId('viewport');\n\n      const maxScrollTop = viewport.scrollHeight - viewport.clientHeight;\n      const maxScrollLeft = viewport.scrollWidth - viewport.clientWidth;\n\n      fireEvent.scroll(viewport, {\n        target: {\n          scrollTop: maxScrollTop - 0.5,\n          scrollLeft: maxScrollLeft - 0.5,\n        },\n      });\n      await flushMicrotasks();\n\n      expect(root).toHaveAttribute('data-overflow-y-start');\n      expect(root).not.toHaveAttribute('data-overflow-y-end');\n      expect(root).toHaveAttribute('data-overflow-x-start');\n      expect(root).not.toHaveAttribute('data-overflow-x-end');\n    });\n\n    it('respects overflowEdgeThreshold and exposes scroll metrics', async () => {\n      await render(\n        <ScrollArea.Root\n          data-testid=\"root\"\n          overflowEdgeThreshold={{ xStart: 20, yStart: 5 }}\n          style={{ width: VIEWPORT_SIZE, height: VIEWPORT_SIZE }}\n        >\n          <ScrollArea.Viewport data-testid=\"viewport\" style={{ width: '100%', height: '100%' }}>\n            <ScrollArea.Content data-testid=\"content\">\n              <div style={{ width: SCROLLABLE_CONTENT_SIZE, height: SCROLLABLE_CONTENT_SIZE }} />\n            </ScrollArea.Content>\n          </ScrollArea.Viewport>\n          <ScrollArea.Scrollbar orientation=\"vertical\" data-testid=\"scrollbar-vertical\">\n            <ScrollArea.Thumb />\n          </ScrollArea.Scrollbar>\n          <ScrollArea.Scrollbar orientation=\"horizontal\" data-testid=\"scrollbar-horizontal\">\n            <ScrollArea.Thumb />\n          </ScrollArea.Scrollbar>\n        </ScrollArea.Root>,\n      );\n\n      const viewport = screen.getByTestId('viewport');\n\n      fireEvent.scroll(viewport, {\n        target: { scrollLeft: 15, scrollTop: 7 },\n      });\n\n      await waitFor(() => expect(viewport).not.toHaveAttribute('data-overflow-x-start'));\n      expect(viewport).toHaveAttribute('data-overflow-y-start');\n\n      fireEvent.scroll(viewport, {\n        target: { scrollLeft: 35, scrollTop: 7 },\n      });\n\n      await waitFor(() => expect(viewport).toHaveAttribute('data-overflow-x-start'));\n\n      const viewportStyle = viewport.style;\n      const startPx = viewportStyle.getPropertyValue('--scroll-area-overflow-x-start');\n      expect(startPx).toBe('35px');\n\n      const horizontalEndPx = viewportStyle.getPropertyValue('--scroll-area-overflow-x-end');\n      expect(horizontalEndPx).not.toBe('');\n      expect(horizontalEndPx).not.toBe('0px');\n    });\n\n    it('does not add state attributes when content does not overflow', async () => {\n      await render(\n        <ScrollArea.Root data-testid=\"root\" style={{ width: VIEWPORT_SIZE, height: VIEWPORT_SIZE }}>\n          <ScrollArea.Viewport data-testid=\"viewport\" style={{ width: '100%', height: '100%' }}>\n            <ScrollArea.Content data-testid=\"content\">\n              <div style={{ width: VIEWPORT_SIZE / 2, height: VIEWPORT_SIZE / 2 }} />\n            </ScrollArea.Content>\n          </ScrollArea.Viewport>\n          <ScrollArea.Scrollbar orientation=\"vertical\" keepMounted data-testid=\"scrollbar-vertical\">\n            <ScrollArea.Thumb />\n          </ScrollArea.Scrollbar>\n          <ScrollArea.Scrollbar\n            orientation=\"horizontal\"\n            keepMounted\n            data-testid=\"scrollbar-horizontal\"\n          >\n            <ScrollArea.Thumb />\n          </ScrollArea.Scrollbar>\n        </ScrollArea.Root>,\n      );\n\n      const root = screen.getByTestId('root');\n      const viewport = screen.getByTestId('viewport');\n      const content = screen.getByTestId('content');\n      const vScrollbar = screen.getByTestId('scrollbar-vertical');\n      const hScrollbar = screen.getByTestId('scrollbar-horizontal');\n\n      expect(root).not.toHaveAttribute('data-has-overflow-x');\n      expect(root).not.toHaveAttribute('data-has-overflow-y');\n      expect(root).not.toHaveAttribute('data-overflow-x-start');\n      expect(root).not.toHaveAttribute('data-overflow-x-end');\n      expect(root).not.toHaveAttribute('data-overflow-y-start');\n      expect(root).not.toHaveAttribute('data-overflow-y-end');\n\n      expect(viewport).not.toHaveAttribute('data-overflow-x-start');\n      expect(viewport).not.toHaveAttribute('data-overflow-x-end');\n      expect(viewport).not.toHaveAttribute('data-overflow-y-start');\n      expect(viewport).not.toHaveAttribute('data-overflow-y-end');\n      expect(content).not.toHaveAttribute('data-overflow-x-start');\n      expect(content).not.toHaveAttribute('data-overflow-x-end');\n      expect(content).not.toHaveAttribute('data-overflow-y-start');\n      expect(content).not.toHaveAttribute('data-overflow-y-end');\n\n      expect(vScrollbar).not.toHaveAttribute('data-overflow-y-start');\n      expect(vScrollbar).not.toHaveAttribute('data-overflow-y-end');\n      expect(hScrollbar).not.toHaveAttribute('data-overflow-x-start');\n      expect(hScrollbar).not.toHaveAttribute('data-overflow-x-end');\n    });\n\n    it('correctly handles RTL', async () => {\n      await render(\n        <DirectionProvider direction=\"rtl\">\n          <ScrollArea.Root\n            data-testid=\"root\"\n            style={{ width: VIEWPORT_SIZE, height: VIEWPORT_SIZE, direction: 'rtl' }}\n          >\n            <ScrollArea.Viewport data-testid=\"viewport\" style={{ width: '100%', height: '100%' }}>\n              <div style={{ width: SCROLLABLE_CONTENT_SIZE, height: 200 }} />\n            </ScrollArea.Viewport>\n            <ScrollArea.Scrollbar orientation=\"horizontal\" data-testid=\"scrollbar-horizontal\">\n              <ScrollArea.Thumb />\n            </ScrollArea.Scrollbar>\n          </ScrollArea.Root>\n        </DirectionProvider>,\n      );\n\n      const root = screen.getByTestId('root');\n      const viewport = screen.getByTestId('viewport');\n\n      const maxScrollLeft = viewport.scrollWidth - viewport.clientWidth;\n      fireEvent.scroll(viewport, {\n        target: {\n          scrollLeft: 0,\n        },\n      });\n\n      await waitFor(() => expect(root).toHaveAttribute('data-has-overflow-x'));\n      expect(root).not.toHaveAttribute('data-overflow-x-start');\n      expect(root).toHaveAttribute('data-overflow-x-end');\n\n      fireEvent.scroll(viewport, {\n        target: {\n          scrollLeft: -maxScrollLeft / 2,\n        },\n      });\n\n      await waitFor(() => expect(root).toHaveAttribute('data-overflow-x-start'));\n      expect(root).toHaveAttribute('data-overflow-x-end');\n\n      fireEvent.scroll(viewport, {\n        target: {\n          scrollLeft: -maxScrollLeft,\n        },\n      });\n\n      await waitFor(() => expect(root).toHaveAttribute('data-overflow-x-start'));\n      expect(root).not.toHaveAttribute('data-overflow-x-end');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/scroll-area/root/ScrollAreaRoot.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useTimeout } from '@base-ui/utils/useTimeout';\nimport type { BaseUIComponentProps, HTMLProps } from '../../utils/types';\nimport { ScrollAreaRootContext } from './ScrollAreaRootContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { ScrollAreaRootCssVars } from './ScrollAreaRootCssVars';\nimport { SCROLL_TIMEOUT } from '../constants';\nimport { getOffset } from '../utils/getOffset';\nimport { ScrollAreaScrollbarDataAttributes } from '../scrollbar/ScrollAreaScrollbarDataAttributes';\nimport { styleDisableScrollbar } from '../../utils/styles';\nimport { useBaseUiId } from '../../utils/useBaseUiId';\nimport { scrollAreaStateAttributesMapping } from './stateAttributes';\nimport { contains } from '../../floating-ui-react/utils';\nimport { useCSPContext } from '../../csp-provider/CSPContext';\n\nconst DEFAULT_COORDS = { x: 0, y: 0 };\nconst DEFAULT_SIZE = { width: 0, height: 0 };\nconst DEFAULT_OVERFLOW_EDGES = { xStart: false, xEnd: false, yStart: false, yEnd: false };\nconst DEFAULT_HIDDEN_STATE = { x: true, y: true, corner: true };\n\nexport type HiddenState = typeof DEFAULT_HIDDEN_STATE;\nexport type OverflowEdges = typeof DEFAULT_OVERFLOW_EDGES;\nexport type Size = typeof DEFAULT_SIZE;\nexport type Coords = typeof DEFAULT_COORDS;\n\n/**\n * Groups all parts of the scroll area.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Scroll Area](https://base-ui.com/react/components/scroll-area)\n */\nexport const ScrollAreaRoot = React.forwardRef(function ScrollAreaRoot(\n  componentProps: ScrollAreaRoot.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const {\n    render,\n    className,\n    overflowEdgeThreshold: overflowEdgeThresholdProp,\n    ...elementProps\n  } = componentProps;\n\n  const overflowEdgeThreshold = normalizeOverflowEdgeThreshold(overflowEdgeThresholdProp);\n\n  const rootId = useBaseUiId();\n\n  const scrollYTimeout = useTimeout();\n  const scrollXTimeout = useTimeout();\n  const { nonce, disableStyleElements } = useCSPContext();\n\n  const [hovering, setHovering] = React.useState(false);\n  const [scrollingX, setScrollingX] = React.useState(false);\n  const [scrollingY, setScrollingY] = React.useState(false);\n  const [touchModality, setTouchModality] = React.useState(false);\n  const [hasMeasuredScrollbar, setHasMeasuredScrollbar] = React.useState(false);\n  const [cornerSize, setCornerSize] = React.useState<Size>(DEFAULT_SIZE);\n  const [thumbSize, setThumbSize] = React.useState<Size>(DEFAULT_SIZE);\n  const [overflowEdges, setOverflowEdges] = React.useState(DEFAULT_OVERFLOW_EDGES);\n  const [hiddenState, setHiddenState] = React.useState(DEFAULT_HIDDEN_STATE);\n\n  const rootRef = React.useRef<HTMLDivElement | null>(null);\n  const viewportRef = React.useRef<HTMLDivElement | null>(null);\n  const scrollbarYRef = React.useRef<HTMLDivElement | null>(null);\n  const scrollbarXRef = React.useRef<HTMLDivElement | null>(null);\n  const thumbYRef = React.useRef<HTMLDivElement | null>(null);\n  const thumbXRef = React.useRef<HTMLDivElement | null>(null);\n  const cornerRef = React.useRef<HTMLDivElement | null>(null);\n\n  const thumbDraggingRef = React.useRef(false);\n  const startYRef = React.useRef(0);\n  const startXRef = React.useRef(0);\n  const startScrollTopRef = React.useRef(0);\n  const startScrollLeftRef = React.useRef(0);\n  const currentOrientationRef = React.useRef<'vertical' | 'horizontal'>('vertical');\n  const scrollPositionRef = React.useRef(DEFAULT_COORDS);\n\n  const handleScroll = useStableCallback((scrollPosition: Coords) => {\n    const offsetX = scrollPosition.x - scrollPositionRef.current.x;\n    const offsetY = scrollPosition.y - scrollPositionRef.current.y;\n    scrollPositionRef.current = scrollPosition;\n\n    if (offsetY !== 0) {\n      setScrollingY(true);\n\n      scrollYTimeout.start(SCROLL_TIMEOUT, () => {\n        setScrollingY(false);\n      });\n    }\n\n    if (offsetX !== 0) {\n      setScrollingX(true);\n\n      scrollXTimeout.start(SCROLL_TIMEOUT, () => {\n        setScrollingX(false);\n      });\n    }\n  });\n\n  const handlePointerDown = useStableCallback((event: React.PointerEvent) => {\n    if (event.button !== 0) {\n      return;\n    }\n\n    thumbDraggingRef.current = true;\n    startYRef.current = event.clientY;\n    startXRef.current = event.clientX;\n    currentOrientationRef.current = event.currentTarget.getAttribute(\n      ScrollAreaScrollbarDataAttributes.orientation,\n    ) as 'vertical' | 'horizontal';\n\n    if (viewportRef.current) {\n      startScrollTopRef.current = viewportRef.current.scrollTop;\n      startScrollLeftRef.current = viewportRef.current.scrollLeft;\n    }\n    if (thumbYRef.current && currentOrientationRef.current === 'vertical') {\n      thumbYRef.current.setPointerCapture(event.pointerId);\n    }\n    if (thumbXRef.current && currentOrientationRef.current === 'horizontal') {\n      thumbXRef.current.setPointerCapture(event.pointerId);\n    }\n  });\n\n  const handlePointerMove = useStableCallback((event: React.PointerEvent) => {\n    if (!thumbDraggingRef.current) {\n      return;\n    }\n\n    const deltaY = event.clientY - startYRef.current;\n    const deltaX = event.clientX - startXRef.current;\n\n    if (viewportRef.current) {\n      const scrollableContentHeight = viewportRef.current.scrollHeight;\n      const viewportHeight = viewportRef.current.clientHeight;\n      const scrollableContentWidth = viewportRef.current.scrollWidth;\n      const viewportWidth = viewportRef.current.clientWidth;\n\n      if (\n        thumbYRef.current &&\n        scrollbarYRef.current &&\n        currentOrientationRef.current === 'vertical'\n      ) {\n        const scrollbarYOffset = getOffset(scrollbarYRef.current, 'padding', 'y');\n        const thumbYOffset = getOffset(thumbYRef.current, 'margin', 'y');\n        const thumbHeight = thumbYRef.current.offsetHeight;\n        const maxThumbOffsetY =\n          scrollbarYRef.current.offsetHeight - thumbHeight - scrollbarYOffset - thumbYOffset;\n        const scrollRatioY = deltaY / maxThumbOffsetY;\n        viewportRef.current.scrollTop =\n          startScrollTopRef.current + scrollRatioY * (scrollableContentHeight - viewportHeight);\n        event.preventDefault();\n\n        setScrollingY(true);\n\n        scrollYTimeout.start(SCROLL_TIMEOUT, () => {\n          setScrollingY(false);\n        });\n      }\n\n      if (\n        thumbXRef.current &&\n        scrollbarXRef.current &&\n        currentOrientationRef.current === 'horizontal'\n      ) {\n        const scrollbarXOffset = getOffset(scrollbarXRef.current, 'padding', 'x');\n        const thumbXOffset = getOffset(thumbXRef.current, 'margin', 'x');\n        const thumbWidth = thumbXRef.current.offsetWidth;\n        const maxThumbOffsetX =\n          scrollbarXRef.current.offsetWidth - thumbWidth - scrollbarXOffset - thumbXOffset;\n        const scrollRatioX = deltaX / maxThumbOffsetX;\n        viewportRef.current.scrollLeft =\n          startScrollLeftRef.current + scrollRatioX * (scrollableContentWidth - viewportWidth);\n        event.preventDefault();\n\n        setScrollingX(true);\n\n        scrollXTimeout.start(SCROLL_TIMEOUT, () => {\n          setScrollingX(false);\n        });\n      }\n    }\n  });\n\n  const handlePointerUp = useStableCallback((event: React.PointerEvent) => {\n    thumbDraggingRef.current = false;\n\n    if (thumbYRef.current && currentOrientationRef.current === 'vertical') {\n      thumbYRef.current.releasePointerCapture(event.pointerId);\n    }\n    if (thumbXRef.current && currentOrientationRef.current === 'horizontal') {\n      thumbXRef.current.releasePointerCapture(event.pointerId);\n    }\n  });\n\n  function handleTouchModalityChange(event: React.PointerEvent) {\n    setTouchModality(event.pointerType === 'touch');\n  }\n\n  function handlePointerEnterOrMove(event: React.PointerEvent) {\n    handleTouchModalityChange(event);\n\n    if (event.pointerType !== 'touch') {\n      const isTargetRootChild = contains(rootRef.current, event.target as Element);\n      setHovering(isTargetRootChild);\n    }\n  }\n\n  const state: ScrollAreaRootState = React.useMemo(\n    () => ({\n      scrolling: scrollingX || scrollingY,\n      hasOverflowX: !hiddenState.x,\n      hasOverflowY: !hiddenState.y,\n      overflowXStart: overflowEdges.xStart,\n      overflowXEnd: overflowEdges.xEnd,\n      overflowYStart: overflowEdges.yStart,\n      overflowYEnd: overflowEdges.yEnd,\n      cornerHidden: hiddenState.corner,\n    }),\n    [scrollingX, scrollingY, hiddenState.x, hiddenState.y, hiddenState.corner, overflowEdges],\n  );\n\n  const props: HTMLProps = {\n    role: 'presentation',\n    onPointerEnter: handlePointerEnterOrMove,\n    onPointerMove: handlePointerEnterOrMove,\n    onPointerDown: handleTouchModalityChange,\n    onPointerLeave() {\n      setHovering(false);\n    },\n    style: {\n      position: 'relative',\n      [ScrollAreaRootCssVars.scrollAreaCornerHeight as string]: `${cornerSize.height}px`,\n      [ScrollAreaRootCssVars.scrollAreaCornerWidth as string]: `${cornerSize.width}px`,\n    },\n  };\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: [forwardedRef, rootRef],\n    props: [props, elementProps],\n    stateAttributesMapping: scrollAreaStateAttributesMapping,\n  });\n\n  const contextValue = React.useMemo(\n    () => ({\n      handlePointerDown,\n      handlePointerMove,\n      handlePointerUp,\n      handleScroll,\n      cornerSize,\n      setCornerSize,\n      thumbSize,\n      setThumbSize,\n      hasMeasuredScrollbar,\n      setHasMeasuredScrollbar,\n      touchModality,\n      cornerRef,\n      scrollingX,\n      setScrollingX,\n      scrollingY,\n      setScrollingY,\n      hovering,\n      setHovering,\n      viewportRef,\n      rootRef,\n      scrollbarYRef,\n      scrollbarXRef,\n      thumbYRef,\n      thumbXRef,\n      rootId,\n      hiddenState,\n      setHiddenState,\n      overflowEdges,\n      setOverflowEdges,\n      viewportState: state,\n      overflowEdgeThreshold,\n    }),\n    [\n      handlePointerDown,\n      handlePointerMove,\n      handlePointerUp,\n      handleScroll,\n      cornerSize,\n      thumbSize,\n      hasMeasuredScrollbar,\n      touchModality,\n      scrollingX,\n      setScrollingX,\n      scrollingY,\n      setScrollingY,\n      hovering,\n      setHovering,\n      rootId,\n      hiddenState,\n      overflowEdges,\n      state,\n      overflowEdgeThreshold,\n    ],\n  );\n\n  return (\n    <ScrollAreaRootContext.Provider value={contextValue}>\n      {!disableStyleElements && styleDisableScrollbar.getElement(nonce)}\n      {element}\n    </ScrollAreaRootContext.Provider>\n  );\n});\n\nexport interface ScrollAreaRootState {\n  /**\n   * Whether the scroll area is being scrolled.\n   */\n  scrolling: boolean;\n  /**\n   * Whether horizontal overflow is present.\n   */\n  hasOverflowX: boolean;\n  /**\n   * Whether vertical overflow is present.\n   */\n  hasOverflowY: boolean;\n  /**\n   * Whether there is overflow on the inline start side for the horizontal axis.\n   */\n  overflowXStart: boolean;\n  /**\n   * Whether there is overflow on the inline end side for the horizontal axis.\n   */\n  overflowXEnd: boolean;\n  /**\n   * Whether there is overflow on the block start side.\n   */\n  overflowYStart: boolean;\n  /**\n   * Whether there is overflow on the block end side.\n   */\n  overflowYEnd: boolean;\n  /**\n   * Whether the scrollbar corner is hidden.\n   */\n  cornerHidden: boolean;\n}\n\nexport interface ScrollAreaRootProps extends BaseUIComponentProps<'div', ScrollAreaRootState> {\n  /**\n   * The threshold in pixels that must be passed before the overflow edge attributes are applied.\n   * Accepts a single number for all edges or an object to configure them individually.\n   * @default 0\n   */\n  overflowEdgeThreshold?:\n    | number\n    | Partial<{\n        xStart: number;\n        xEnd: number;\n        yStart: number;\n        yEnd: number;\n      }>\n    | undefined;\n}\n\nexport namespace ScrollAreaRoot {\n  export type State = ScrollAreaRootState;\n  export type Props = ScrollAreaRootProps;\n}\n\nfunction normalizeOverflowEdgeThreshold(\n  threshold: ScrollAreaRoot.Props['overflowEdgeThreshold'] | undefined,\n) {\n  if (typeof threshold === 'number') {\n    const value = Math.max(0, threshold);\n    return {\n      xStart: value,\n      xEnd: value,\n      yStart: value,\n      yEnd: value,\n    };\n  }\n\n  return {\n    xStart: Math.max(0, threshold?.xStart || 0),\n    xEnd: Math.max(0, threshold?.xEnd || 0),\n    yStart: Math.max(0, threshold?.yStart || 0),\n    yEnd: Math.max(0, threshold?.yEnd || 0),\n  };\n}\n"
  },
  {
    "path": "packages/react/src/scroll-area/root/ScrollAreaRootContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport type {\n  Coords,\n  HiddenState,\n  OverflowEdges,\n  Size,\n  ScrollAreaRootState,\n} from './ScrollAreaRoot';\n\nexport interface ScrollAreaRootContext {\n  cornerSize: Size;\n  setCornerSize: React.Dispatch<React.SetStateAction<Size>>;\n  thumbSize: Size;\n  setThumbSize: React.Dispatch<React.SetStateAction<Size>>;\n  hasMeasuredScrollbar: boolean;\n  setHasMeasuredScrollbar: React.Dispatch<React.SetStateAction<boolean>>;\n  touchModality: boolean;\n  hovering: boolean;\n  setHovering: React.Dispatch<React.SetStateAction<boolean>>;\n  scrollingX: boolean;\n  setScrollingX: React.Dispatch<React.SetStateAction<boolean>>;\n  scrollingY: boolean;\n  setScrollingY: React.Dispatch<React.SetStateAction<boolean>>;\n  viewportRef: React.RefObject<HTMLDivElement | null>;\n  rootRef: React.RefObject<HTMLDivElement | null>;\n  scrollbarYRef: React.RefObject<HTMLDivElement | null>;\n  thumbYRef: React.RefObject<HTMLDivElement | null>;\n  scrollbarXRef: React.RefObject<HTMLDivElement | null>;\n  thumbXRef: React.RefObject<HTMLDivElement | null>;\n  cornerRef: React.RefObject<HTMLDivElement | null>;\n  handlePointerDown: (event: React.PointerEvent) => void;\n  handlePointerMove: (event: React.PointerEvent) => void;\n  handlePointerUp: (event: React.PointerEvent) => void;\n  handleScroll: (scrollPosition: Coords) => void;\n  rootId: string | undefined;\n  hiddenState: HiddenState;\n  setHiddenState: React.Dispatch<React.SetStateAction<HiddenState>>;\n  overflowEdges: OverflowEdges;\n  setOverflowEdges: React.Dispatch<React.SetStateAction<OverflowEdges>>;\n  viewportState: ScrollAreaRootState;\n  overflowEdgeThreshold: {\n    xStart: number;\n    xEnd: number;\n    yStart: number;\n    yEnd: number;\n  };\n}\n\nexport const ScrollAreaRootContext = React.createContext<ScrollAreaRootContext | undefined>(\n  undefined,\n);\n\nexport function useScrollAreaRootContext() {\n  const context = React.useContext(ScrollAreaRootContext);\n  if (context === undefined) {\n    throw new Error(\n      'Base UI: ScrollAreaRootContext is missing. ScrollArea parts must be placed within <ScrollArea.Root>.',\n    );\n  }\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/scroll-area/root/ScrollAreaRootCssVars.ts",
    "content": "export enum ScrollAreaRootCssVars {\n  /**\n   * The scroll area's corner height.\n   * @type {number}\n   */\n  scrollAreaCornerHeight = '--scroll-area-corner-height',\n  /**\n   * The scroll area's corner width.\n   * @type {number}\n   */\n  scrollAreaCornerWidth = '--scroll-area-corner-width',\n}\n"
  },
  {
    "path": "packages/react/src/scroll-area/root/ScrollAreaRootDataAttributes.ts",
    "content": "export enum ScrollAreaRootDataAttributes {\n  /**\n   * Present when the user scrolls inside the scroll area.\n   */\n  scrolling = 'data-scrolling',\n  /**\n   * Present when the scroll area content is wider than the viewport.\n   */\n  hasOverflowX = 'data-has-overflow-x',\n  /**\n   * Present when the scroll area content is taller than the viewport.\n   */\n  hasOverflowY = 'data-has-overflow-y',\n  /**\n   * Present when there is overflow on the horizontal start side.\n   */\n  overflowXStart = 'data-overflow-x-start',\n  /**\n   * Present when there is overflow on the horizontal end side.\n   */\n  overflowXEnd = 'data-overflow-x-end',\n  /**\n   * Present when there is overflow on the vertical start side.\n   */\n  overflowYStart = 'data-overflow-y-start',\n  /**\n   * Present when there is overflow on the vertical end side.\n   */\n  overflowYEnd = 'data-overflow-y-end',\n}\n"
  },
  {
    "path": "packages/react/src/scroll-area/root/stateAttributes.ts",
    "content": "import type { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport type { ScrollAreaRootState } from './ScrollAreaRoot';\nimport { ScrollAreaRootDataAttributes } from './ScrollAreaRootDataAttributes';\n\nexport const scrollAreaStateAttributesMapping: StateAttributesMapping<ScrollAreaRootState> = {\n  hasOverflowX: (value) => (value ? { [ScrollAreaRootDataAttributes.hasOverflowX]: '' } : null),\n  hasOverflowY: (value) => (value ? { [ScrollAreaRootDataAttributes.hasOverflowY]: '' } : null),\n  overflowXStart: (value) => (value ? { [ScrollAreaRootDataAttributes.overflowXStart]: '' } : null),\n  overflowXEnd: (value) => (value ? { [ScrollAreaRootDataAttributes.overflowXEnd]: '' } : null),\n  overflowYStart: (value) => (value ? { [ScrollAreaRootDataAttributes.overflowYStart]: '' } : null),\n  overflowYEnd: (value) => (value ? { [ScrollAreaRootDataAttributes.overflowYEnd]: '' } : null),\n  cornerHidden: () => null,\n};\n"
  },
  {
    "path": "packages/react/src/scroll-area/scrollbar/ScrollAreaScrollbar.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { ScrollArea } from '@base-ui/react/scroll-area';\nimport { screen, fireEvent, flushMicrotasks, waitFor } from '@mui/internal-test-utils';\nimport { createRenderer, isJSDOM, describeConformance } from '#test-utils';\nimport { SCROLL_TIMEOUT } from '../constants';\n\ndescribe('<ScrollArea.Scrollbar />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<ScrollArea.Scrollbar keepMounted />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(<ScrollArea.Root>{node}</ScrollArea.Root>);\n    },\n  }));\n\n  describe('data-scrolling attribute', () => {\n    const { render: renderWithClock, clock } = createRenderer();\n\n    clock.withFakeTimers();\n\n    it('adds [data-scrolling] attribute when viewport is scrolled in the correct direction', async () => {\n      await renderWithClock(\n        <ScrollArea.Root style={{ width: 200, height: 200 }}>\n          <ScrollArea.Viewport data-testid=\"viewport\" style={{ width: '100%', height: '100%' }}>\n            <div style={{ width: 1000, height: 1000 }} />\n          </ScrollArea.Viewport>\n          <ScrollArea.Scrollbar orientation=\"vertical\" data-testid=\"vertical\" keepMounted />\n          <ScrollArea.Scrollbar orientation=\"horizontal\" data-testid=\"horizontal\" keepMounted />\n          <ScrollArea.Corner />\n        </ScrollArea.Root>,\n      );\n\n      const verticalScrollbar = screen.getByTestId('vertical');\n      const horizontalScrollbar = screen.getByTestId('horizontal');\n      const viewport = screen.getByTestId('viewport');\n\n      expect(verticalScrollbar).not.toHaveAttribute('data-scrolling');\n      expect(horizontalScrollbar).not.toHaveAttribute('data-scrolling');\n\n      fireEvent.pointerEnter(viewport);\n      fireEvent.scroll(viewport, {\n        target: {\n          scrollTop: 1,\n        },\n      });\n\n      expect(verticalScrollbar).toHaveAttribute('data-scrolling', '');\n      expect(horizontalScrollbar).not.toHaveAttribute('data-scrolling', '');\n\n      await clock.tickAsync(SCROLL_TIMEOUT - 1);\n\n      expect(verticalScrollbar).toHaveAttribute('data-scrolling', '');\n      expect(horizontalScrollbar).not.toHaveAttribute('data-scrolling', '');\n\n      fireEvent.pointerEnter(viewport);\n      fireEvent.scroll(viewport, {\n        target: {\n          scrollLeft: 1,\n        },\n      });\n\n      await clock.tickAsync(1); // vertical just finished\n\n      expect(verticalScrollbar).not.toHaveAttribute('data-scrolling');\n      expect(horizontalScrollbar).toHaveAttribute('data-scrolling');\n\n      await clock.tickAsync(SCROLL_TIMEOUT - 2); // already ticked 1ms above\n\n      expect(verticalScrollbar).not.toHaveAttribute('data-scrolling');\n      expect(horizontalScrollbar).toHaveAttribute('data-scrolling');\n\n      await clock.tickAsync(1);\n\n      expect(verticalScrollbar).not.toHaveAttribute('data-scrolling');\n      expect(horizontalScrollbar).not.toHaveAttribute('data-scrolling');\n    });\n  });\n\n  describe.skipIf(isJSDOM)('data overflow attributes (scrollbars)', () => {\n    const VIEWPORT_SIZE = 200;\n    const SCROLLABLE_CONTENT_SIZE = 1000;\n\n    it('applies data attributes on vertical and horizontal scrollbars', async () => {\n      await render(\n        <ScrollArea.Root style={{ width: VIEWPORT_SIZE, height: VIEWPORT_SIZE }}>\n          <ScrollArea.Viewport data-testid=\"viewport\" style={{ width: '100%', height: '100%' }}>\n            <div style={{ width: SCROLLABLE_CONTENT_SIZE, height: SCROLLABLE_CONTENT_SIZE }} />\n          </ScrollArea.Viewport>\n          <ScrollArea.Scrollbar orientation=\"vertical\" data-testid=\"scrollbar-vertical\">\n            <ScrollArea.Thumb />\n          </ScrollArea.Scrollbar>\n          <ScrollArea.Scrollbar orientation=\"horizontal\" data-testid=\"scrollbar-horizontal\">\n            <ScrollArea.Thumb />\n          </ScrollArea.Scrollbar>\n        </ScrollArea.Root>,\n      );\n\n      const viewport = screen.getByTestId('viewport');\n      const vScrollbar = screen.getByTestId('scrollbar-vertical');\n      const hScrollbar = screen.getByTestId('scrollbar-horizontal');\n\n      /* eslint-disable testing-library/no-wait-for-multiple-assertions */\n      await waitFor(() => {\n        expect(vScrollbar).toHaveAttribute('data-has-overflow-y');\n        expect(vScrollbar).not.toHaveAttribute('data-overflow-y-start');\n        expect(vScrollbar).toHaveAttribute('data-overflow-y-end');\n\n        expect(hScrollbar).toHaveAttribute('data-has-overflow-x');\n        expect(hScrollbar).not.toHaveAttribute('data-overflow-x-start');\n        expect(hScrollbar).toHaveAttribute('data-overflow-x-end');\n      });\n      /* eslint-enable testing-library/no-wait-for-multiple-assertions */\n\n      // Scroll to middle\n      const halfY = (viewport.scrollHeight - viewport.clientHeight) / 2;\n      const halfX = (viewport.scrollWidth - viewport.clientWidth) / 2;\n      fireEvent.scroll(viewport, {\n        target: { scrollTop: halfY, scrollLeft: halfX },\n      });\n      await flushMicrotasks();\n\n      expect(vScrollbar).toHaveAttribute('data-overflow-y-start');\n      expect(vScrollbar).toHaveAttribute('data-overflow-y-end');\n      expect(hScrollbar).toHaveAttribute('data-overflow-x-start');\n      expect(hScrollbar).toHaveAttribute('data-overflow-x-end');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/scroll-area/scrollbar/ScrollAreaScrollbar.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport type { BaseUIComponentProps, HTMLProps } from '../../utils/types';\nimport { useScrollAreaRootContext } from '../root/ScrollAreaRootContext';\nimport { ScrollAreaScrollbarContext } from './ScrollAreaScrollbarContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { getOffset } from '../utils/getOffset';\nimport { ScrollAreaRootCssVars } from '../root/ScrollAreaRootCssVars';\nimport { ScrollAreaScrollbarCssVars } from './ScrollAreaScrollbarCssVars';\nimport { useDirection } from '../../direction-provider/DirectionContext';\nimport { scrollAreaStateAttributesMapping } from '../root/stateAttributes';\nimport type { ScrollAreaRootState } from '../root/ScrollAreaRoot';\n\n/**\n * A vertical or horizontal scrollbar for the scroll area.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Scroll Area](https://base-ui.com/react/components/scroll-area)\n */\nexport const ScrollAreaScrollbar = React.forwardRef(function ScrollAreaScrollbar(\n  componentProps: ScrollAreaScrollbar.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const {\n    render,\n    className,\n    orientation = 'vertical',\n    keepMounted = false,\n    ...elementProps\n  } = componentProps;\n\n  const {\n    hovering,\n    scrollingX,\n    scrollingY,\n    hiddenState,\n    overflowEdges,\n    scrollbarYRef,\n    scrollbarXRef,\n    viewportRef,\n    thumbYRef,\n    thumbXRef,\n    handlePointerDown,\n    handlePointerUp,\n    rootId,\n    thumbSize,\n    hasMeasuredScrollbar,\n  } = useScrollAreaRootContext();\n\n  const state: ScrollAreaScrollbarState = {\n    hovering,\n    scrolling: {\n      horizontal: scrollingX,\n      vertical: scrollingY,\n    }[orientation],\n    orientation,\n    hasOverflowX: !hiddenState.x,\n    hasOverflowY: !hiddenState.y,\n    overflowXStart: overflowEdges.xStart,\n    overflowXEnd: overflowEdges.xEnd,\n    overflowYStart: overflowEdges.yStart,\n    overflowYEnd: overflowEdges.yEnd,\n    cornerHidden: hiddenState.corner,\n  };\n\n  const direction = useDirection();\n  const hideTrackUntilMeasured = !hasMeasuredScrollbar && !keepMounted;\n\n  React.useEffect(() => {\n    const viewportEl = viewportRef.current;\n    const scrollbarEl = orientation === 'vertical' ? scrollbarYRef.current : scrollbarXRef.current;\n\n    if (!scrollbarEl) {\n      return undefined;\n    }\n\n    function handleWheel(event: WheelEvent) {\n      if (!viewportEl || !scrollbarEl || event.ctrlKey) {\n        return;\n      }\n\n      event.preventDefault();\n\n      if (orientation === 'vertical') {\n        if (viewportEl.scrollTop === 0 && event.deltaY < 0) {\n          return;\n        }\n      } else if (viewportEl.scrollLeft === 0 && event.deltaX < 0) {\n        return;\n      }\n\n      if (orientation === 'vertical') {\n        if (\n          viewportEl.scrollTop === viewportEl.scrollHeight - viewportEl.clientHeight &&\n          event.deltaY > 0\n        ) {\n          return;\n        }\n      } else if (\n        viewportEl.scrollLeft === viewportEl.scrollWidth - viewportEl.clientWidth &&\n        event.deltaX > 0\n      ) {\n        return;\n      }\n\n      if (orientation === 'vertical') {\n        viewportEl.scrollTop += event.deltaY;\n      } else {\n        viewportEl.scrollLeft += event.deltaX;\n      }\n    }\n\n    scrollbarEl.addEventListener('wheel', handleWheel, { passive: false });\n\n    return () => {\n      scrollbarEl.removeEventListener('wheel', handleWheel);\n    };\n  }, [orientation, scrollbarXRef, scrollbarYRef, viewportRef]);\n\n  const props: HTMLProps = {\n    ...(rootId && { 'data-id': `${rootId}-scrollbar` }),\n    onPointerDown(event) {\n      if (event.button !== 0) {\n        return;\n      }\n\n      // Ignore clicks on thumb\n      if (event.currentTarget !== event.target) {\n        return;\n      }\n\n      if (!viewportRef.current) {\n        return;\n      }\n\n      // Handle Y-axis (vertical) scroll\n      if (thumbYRef.current && scrollbarYRef.current && orientation === 'vertical') {\n        const thumbYOffset = getOffset(thumbYRef.current, 'margin', 'y');\n        const scrollbarYOffset = getOffset(scrollbarYRef.current, 'padding', 'y');\n        const thumbHeight = thumbYRef.current.offsetHeight;\n        const trackRectY = scrollbarYRef.current.getBoundingClientRect();\n        const clickY =\n          event.clientY - trackRectY.top - thumbHeight / 2 - scrollbarYOffset + thumbYOffset / 2;\n\n        const scrollableContentHeight = viewportRef.current.scrollHeight;\n        const viewportHeight = viewportRef.current.clientHeight;\n\n        const maxThumbOffsetY =\n          scrollbarYRef.current.offsetHeight - thumbHeight - scrollbarYOffset - thumbYOffset;\n        const scrollRatioY = clickY / maxThumbOffsetY;\n        const newScrollTop = scrollRatioY * (scrollableContentHeight - viewportHeight);\n\n        viewportRef.current.scrollTop = newScrollTop;\n      }\n\n      if (thumbXRef.current && scrollbarXRef.current && orientation === 'horizontal') {\n        const thumbXOffset = getOffset(thumbXRef.current, 'margin', 'x');\n        const scrollbarXOffset = getOffset(scrollbarXRef.current, 'padding', 'x');\n        const thumbWidth = thumbXRef.current.offsetWidth;\n        const trackRectX = scrollbarXRef.current.getBoundingClientRect();\n        const clickX =\n          event.clientX - trackRectX.left - thumbWidth / 2 - scrollbarXOffset + thumbXOffset / 2;\n\n        const scrollableContentWidth = viewportRef.current.scrollWidth;\n        const viewportWidth = viewportRef.current.clientWidth;\n\n        const maxThumbOffsetX =\n          scrollbarXRef.current.offsetWidth - thumbWidth - scrollbarXOffset - thumbXOffset;\n        const scrollRatioX = clickX / maxThumbOffsetX;\n\n        let newScrollLeft: number;\n        if (direction === 'rtl') {\n          // In RTL, invert the scroll direction\n          newScrollLeft = (1 - scrollRatioX) * (scrollableContentWidth - viewportWidth);\n\n          // Adjust for browsers that use negative scrollLeft in RTL\n          if (viewportRef.current.scrollLeft <= 0) {\n            newScrollLeft = -newScrollLeft;\n          }\n        } else {\n          newScrollLeft = scrollRatioX * (scrollableContentWidth - viewportWidth);\n        }\n\n        viewportRef.current.scrollLeft = newScrollLeft;\n      }\n\n      handlePointerDown(event);\n    },\n    onPointerUp: handlePointerUp,\n    style: {\n      position: 'absolute',\n      touchAction: 'none',\n      WebkitUserSelect: 'none',\n      userSelect: 'none',\n      visibility: hideTrackUntilMeasured ? 'hidden' : undefined,\n      ...(orientation === 'vertical' && {\n        top: 0,\n        bottom: `var(${ScrollAreaRootCssVars.scrollAreaCornerHeight})`,\n        insetInlineEnd: 0,\n        [ScrollAreaScrollbarCssVars.scrollAreaThumbHeight as string]: `${thumbSize.height}px`,\n      }),\n      ...(orientation === 'horizontal' && {\n        insetInlineStart: 0,\n        insetInlineEnd: `var(${ScrollAreaRootCssVars.scrollAreaCornerWidth})`,\n        bottom: 0,\n        [ScrollAreaScrollbarCssVars.scrollAreaThumbWidth as string]: `${thumbSize.width}px`,\n      }),\n    },\n  };\n\n  const element = useRenderElement('div', componentProps, {\n    ref: [forwardedRef, orientation === 'vertical' ? scrollbarYRef : scrollbarXRef],\n    state,\n    props: [props, elementProps],\n    stateAttributesMapping: scrollAreaStateAttributesMapping,\n  });\n\n  const contextValue = React.useMemo(() => ({ orientation }), [orientation]);\n\n  const isHidden = orientation === 'vertical' ? hiddenState.y : hiddenState.x;\n\n  const shouldRender = keepMounted || !isHidden;\n  if (!shouldRender) {\n    return null;\n  }\n\n  return (\n    <ScrollAreaScrollbarContext.Provider value={contextValue}>\n      {element}\n    </ScrollAreaScrollbarContext.Provider>\n  );\n});\n\nexport interface ScrollAreaScrollbarState extends ScrollAreaRootState {\n  /**\n   * Whether the scroll area is being hovered.\n   */\n  hovering: boolean;\n  /**\n   * Whether the scroll area is being scrolled.\n   */\n  scrolling: boolean;\n  /**\n   * The orientation of the scrollbar.\n   */\n  orientation: 'vertical' | 'horizontal';\n}\n\nexport interface ScrollAreaScrollbarProps extends BaseUIComponentProps<\n  'div',\n  ScrollAreaScrollbarState\n> {\n  /**\n   * Whether the scrollbar controls vertical or horizontal scroll.\n   * @default 'vertical'\n   */\n  orientation?: 'vertical' | 'horizontal' | undefined;\n  /**\n   * Whether to keep the HTML element in the DOM when the viewport isn’t scrollable.\n   * @default false\n   */\n  keepMounted?: boolean | undefined;\n}\n\nexport namespace ScrollAreaScrollbar {\n  export type State = ScrollAreaScrollbarState;\n  export type Props = ScrollAreaScrollbarProps;\n}\n"
  },
  {
    "path": "packages/react/src/scroll-area/scrollbar/ScrollAreaScrollbarContext.ts",
    "content": "'use client';\nimport * as React from 'react';\n\nexport interface ScrollAreaScrollbarContext {\n  orientation: 'horizontal' | 'vertical';\n}\n\nexport const ScrollAreaScrollbarContext = React.createContext<\n  ScrollAreaScrollbarContext | undefined\n>(undefined);\n\nexport function useScrollAreaScrollbarContext() {\n  const context = React.useContext(ScrollAreaScrollbarContext);\n  if (context === undefined) {\n    throw new Error(\n      'Base UI: ScrollAreaScrollbarContext is missing. ScrollAreaScrollbar parts must be placed within <ScrollArea.Scrollbar>.',\n    );\n  }\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/scroll-area/scrollbar/ScrollAreaScrollbarCssVars.ts",
    "content": "export enum ScrollAreaScrollbarCssVars {\n  /**\n   * The scroll area thumb's height.\n   * @type {number}\n   */\n  scrollAreaThumbHeight = '--scroll-area-thumb-height',\n  /**\n   * The scroll area thumb's width.\n   * @type {number}\n   */\n  scrollAreaThumbWidth = '--scroll-area-thumb-width',\n}\n"
  },
  {
    "path": "packages/react/src/scroll-area/scrollbar/ScrollAreaScrollbarDataAttributes.ts",
    "content": "export enum ScrollAreaScrollbarDataAttributes {\n  /**\n   * Indicates the orientation of the scrollbar.\n   * @type {'horizontal' | 'vertical'}\n   */\n  orientation = 'data-orientation',\n  /**\n   * Present when the pointer is over the scroll area.\n   */\n  hovering = 'data-hovering',\n  /**\n   * Present when the user scrolls inside the scroll area.\n   */\n  scrolling = 'data-scrolling',\n  /**\n   * Present when the scroll area content is wider than the viewport.\n   */\n  hasOverflowX = 'data-has-overflow-x',\n  /**\n   * Present when the scroll area content is taller than the viewport.\n   */\n  hasOverflowY = 'data-has-overflow-y',\n  /**\n   * Present when there is overflow on the horizontal start side.\n   */\n  overflowXStart = 'data-overflow-x-start',\n  /**\n   * Present when there is overflow on the horizontal end side.\n   */\n  overflowXEnd = 'data-overflow-x-end',\n  /**\n   * Present when there is overflow on the vertical start side.\n   */\n  overflowYStart = 'data-overflow-y-start',\n  /**\n   * Present when there is overflow on the vertical end side.\n   */\n  overflowYEnd = 'data-overflow-y-end',\n}\n"
  },
  {
    "path": "packages/react/src/scroll-area/thumb/ScrollAreaThumb.test.tsx",
    "content": "import { createRenderer } from '#test-utils';\nimport { ScrollArea } from '@base-ui/react/scroll-area';\nimport { describeConformance } from '../../../test/describeConformance';\n\ndescribe('<ScrollArea.Thumb />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<ScrollArea.Thumb />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <ScrollArea.Root>\n          <ScrollArea.Scrollbar keepMounted>{node}</ScrollArea.Scrollbar>\n        </ScrollArea.Root>,\n      );\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/scroll-area/thumb/ScrollAreaThumb.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useScrollAreaRootContext } from '../root/ScrollAreaRootContext';\nimport { useScrollAreaScrollbarContext } from '../scrollbar/ScrollAreaScrollbarContext';\nimport { ScrollAreaScrollbarCssVars } from '../scrollbar/ScrollAreaScrollbarCssVars';\nimport { useRenderElement } from '../../utils/useRenderElement';\n\n/**\n * The draggable part of the scrollbar that indicates the current scroll position.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Scroll Area](https://base-ui.com/react/components/scroll-area)\n */\nexport const ScrollAreaThumb = React.forwardRef(function ScrollAreaThumb(\n  componentProps: ScrollAreaThumb.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, ...elementProps } = componentProps;\n\n  const {\n    thumbYRef,\n    thumbXRef,\n    handlePointerDown,\n    handlePointerMove,\n    handlePointerUp,\n    setScrollingX,\n    setScrollingY,\n    hasMeasuredScrollbar,\n  } = useScrollAreaRootContext();\n\n  const { orientation } = useScrollAreaScrollbarContext();\n\n  const state: ScrollAreaThumbState = { orientation };\n\n  const element = useRenderElement('div', componentProps, {\n    ref: [forwardedRef, orientation === 'vertical' ? thumbYRef : thumbXRef],\n    state,\n    props: [\n      {\n        onPointerDown: handlePointerDown,\n        onPointerMove: handlePointerMove,\n        onPointerUp(event) {\n          if (orientation === 'vertical') {\n            setScrollingY(false);\n          }\n          if (orientation === 'horizontal') {\n            setScrollingX(false);\n          }\n          handlePointerUp(event);\n        },\n        style: {\n          visibility: hasMeasuredScrollbar ? undefined : 'hidden',\n          ...(orientation === 'vertical' && {\n            height: `var(${ScrollAreaScrollbarCssVars.scrollAreaThumbHeight})`,\n          }),\n          ...(orientation === 'horizontal' && {\n            width: `var(${ScrollAreaScrollbarCssVars.scrollAreaThumbWidth})`,\n          }),\n        },\n      },\n      elementProps,\n    ],\n  });\n\n  return element;\n});\n\nexport interface ScrollAreaThumbState {\n  /**\n   * The component orientation.\n   */\n  orientation?: 'horizontal' | 'vertical' | undefined;\n}\n\nexport interface ScrollAreaThumbProps extends BaseUIComponentProps<'div', ScrollAreaThumbState> {}\n\nexport namespace ScrollAreaThumb {\n  export type State = ScrollAreaThumbState;\n  export type Props = ScrollAreaThumbProps;\n}\n"
  },
  {
    "path": "packages/react/src/scroll-area/thumb/ScrollAreaThumbDataAttributes.ts",
    "content": "export enum ScrollAreaThumbDataAttributes {\n  /**\n   * Indicates the orientation of the scrollbar.\n   * @type {'horizontal' | 'vertical'}\n   */\n  orientation = 'data-orientation',\n}\n"
  },
  {
    "path": "packages/react/src/scroll-area/utils/getOffset.ts",
    "content": "export function getOffset(\n  element: Element | null,\n  prop: 'margin' | 'padding',\n  axis: 'x' | 'y',\n): number {\n  if (!element) {\n    return 0;\n  }\n\n  const styles = getComputedStyle(element);\n  const propAxis = axis === 'x' ? 'Inline' : 'Block';\n\n  // Safari misreports `marginInlineEnd` in RTL.\n  // We have to assume the start/end values are symmetrical, which is likely.\n  if (axis === 'x' && prop === 'margin') {\n    return parseFloat(styles[`${prop}InlineStart`]) * 2;\n  }\n\n  return (\n    parseFloat(styles[`${prop}${propAxis}Start`]) + parseFloat(styles[`${prop}${propAxis}End`])\n  );\n}\n"
  },
  {
    "path": "packages/react/src/scroll-area/utils/scrollEdges.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\nimport { normalizeScrollOffset, SCROLL_EDGE_TOLERANCE_PX } from './scrollEdges';\n\ndescribe('normalizeScrollOffset', () => {\n  it('returns 0 when max is non-positive', () => {\n    expect(normalizeScrollOffset(10, 0)).toBe(0);\n    expect(normalizeScrollOffset(10, -5)).toBe(0);\n  });\n\n  it('snaps to the start edge within the tolerance', () => {\n    expect(normalizeScrollOffset(0.5, 10)).toBe(0);\n  });\n\n  it('snaps to the end edge within the tolerance', () => {\n    expect(normalizeScrollOffset(9.5, 10)).toBe(10);\n  });\n\n  it('keeps values away from edges unchanged', () => {\n    expect(normalizeScrollOffset(5, 10)).toBe(5);\n  });\n\n  it('chooses the closest edge when tolerances overlap', () => {\n    const max = SCROLL_EDGE_TOLERANCE_PX;\n\n    expect(normalizeScrollOffset(0, max)).toBe(0);\n    expect(normalizeScrollOffset(max, max)).toBe(max);\n    expect(normalizeScrollOffset(max * 0.4, max)).toBe(0);\n    expect(normalizeScrollOffset(max * 0.6, max)).toBe(max);\n  });\n});\n"
  },
  {
    "path": "packages/react/src/scroll-area/utils/scrollEdges.ts",
    "content": "import { clamp } from '../../utils/clamp';\n\nexport const SCROLL_EDGE_TOLERANCE_PX = 1;\n\nexport function normalizeScrollOffset(value: number, max: number) {\n  if (max <= 0) {\n    return 0;\n  }\n\n  const clamped = clamp(value, 0, max);\n  const startDistance = clamped;\n  const endDistance = max - clamped;\n  const withinStartTolerance = startDistance <= SCROLL_EDGE_TOLERANCE_PX;\n  const withinEndTolerance = endDistance <= SCROLL_EDGE_TOLERANCE_PX;\n\n  if (withinStartTolerance && withinEndTolerance) {\n    return startDistance <= endDistance ? 0 : max;\n  }\n\n  if (withinStartTolerance) {\n    return 0;\n  }\n\n  if (withinEndTolerance) {\n    return max;\n  }\n\n  return clamped;\n}\n"
  },
  {
    "path": "packages/react/src/scroll-area/viewport/ScrollAreaViewport.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { ScrollArea } from '@base-ui/react/scroll-area';\nimport { createRenderer, isJSDOM, describeConformance } from '#test-utils';\nimport { fireEvent, screen, waitFor } from '@mui/internal-test-utils';\nimport { SCROLL_TIMEOUT } from '../constants';\n\ndescribe('<ScrollArea.Viewport />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<ScrollArea.Viewport />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(<ScrollArea.Root>{node}</ScrollArea.Root>);\n    },\n  }));\n\n  describe('data-scrolling attribute', () => {\n    const { render: renderWithClock, clock } = createRenderer();\n\n    clock.withFakeTimers();\n\n    it('adds [data-scrolling] attribute when viewport is scrolled', async () => {\n      await renderWithClock(\n        <ScrollArea.Root style={{ width: 200, height: 200 }}>\n          <ScrollArea.Viewport data-testid=\"viewport\" style={{ width: '100%', height: '100%' }}>\n            <div style={{ width: 1000, height: 1000 }} />\n          </ScrollArea.Viewport>\n        </ScrollArea.Root>,\n      );\n\n      const viewport = screen.getByTestId('viewport');\n\n      expect(viewport).not.toHaveAttribute('data-scrolling');\n\n      fireEvent.pointerEnter(viewport);\n      fireEvent.scroll(viewport, { target: { scrollTop: 1 } });\n\n      expect(viewport).toHaveAttribute('data-scrolling', '');\n\n      await clock.tickAsync(SCROLL_TIMEOUT);\n\n      expect(viewport).not.toHaveAttribute('data-scrolling');\n\n      // Test horizontal scrolling\n      fireEvent.pointerEnter(viewport);\n      fireEvent.scroll(viewport, { target: { scrollLeft: 1 } });\n\n      expect(viewport).toHaveAttribute('data-scrolling', '');\n\n      await clock.tickAsync(SCROLL_TIMEOUT);\n\n      expect(viewport).not.toHaveAttribute('data-scrolling');\n    });\n\n    it('removes [data-scrolling] after timeout', async () => {\n      await renderWithClock(\n        <ScrollArea.Root style={{ width: 200, height: 200 }}>\n          <ScrollArea.Viewport data-testid=\"viewport\" style={{ width: '100%', height: '100%' }}>\n            <div style={{ width: 1000, height: 1000 }} />\n          </ScrollArea.Viewport>\n        </ScrollArea.Root>,\n      );\n\n      const viewport = screen.getByTestId('viewport');\n\n      // Start scrolling\n      fireEvent.pointerEnter(viewport);\n      fireEvent.scroll(viewport, { target: { scrollTop: 1 } });\n\n      expect(viewport).toHaveAttribute('data-scrolling', '');\n\n      // Wait less than timeout - should still be scrolling\n      await clock.tickAsync(SCROLL_TIMEOUT - 1);\n\n      expect(viewport).toHaveAttribute('data-scrolling', '');\n\n      // Wait for remaining timeout\n      await clock.tickAsync(1);\n\n      expect(viewport).not.toHaveAttribute('data-scrolling');\n    });\n  });\n\n  describe.skipIf(isJSDOM)('overflow data attributes (viewport)', () => {\n    const VIEWPORT_SIZE = 200;\n    const SCROLLABLE_CONTENT_SIZE = 1000;\n\n    it('applies data attributes on viewport', async () => {\n      await render(\n        <ScrollArea.Root style={{ width: VIEWPORT_SIZE, height: VIEWPORT_SIZE }}>\n          <ScrollArea.Viewport data-testid=\"viewport\" style={{ width: '100%', height: '100%' }}>\n            <div style={{ width: SCROLLABLE_CONTENT_SIZE, height: SCROLLABLE_CONTENT_SIZE }} />\n          </ScrollArea.Viewport>\n        </ScrollArea.Root>,\n      );\n\n      const viewport = screen.getByTestId('viewport');\n\n      /* eslint-disable testing-library/no-wait-for-multiple-assertions */\n      await waitFor(() => {\n        expect(viewport).toHaveAttribute('data-has-overflow-x');\n        expect(viewport).toHaveAttribute('data-has-overflow-y');\n        expect(viewport).not.toHaveAttribute('data-overflow-x-start');\n        expect(viewport).toHaveAttribute('data-overflow-x-end');\n        expect(viewport).not.toHaveAttribute('data-overflow-y-start');\n        expect(viewport).toHaveAttribute('data-overflow-y-end');\n      });\n      /* eslint-enable testing-library/no-wait-for-multiple-assertions */\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/scroll-area/viewport/ScrollAreaViewport.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { isWebKit } from '@base-ui/utils/detectBrowser';\nimport { useTimeout } from '@base-ui/utils/useTimeout';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useScrollAreaRootContext } from '../root/ScrollAreaRootContext';\nimport { ScrollAreaViewportContext } from './ScrollAreaViewportContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useDirection } from '../../direction-provider/DirectionContext';\nimport { getOffset } from '../utils/getOffset';\nimport { MIN_THUMB_SIZE } from '../constants';\nimport { clamp } from '../../utils/clamp';\nimport { styleDisableScrollbar } from '../../utils/styles';\nimport { scrollAreaStateAttributesMapping } from '../root/stateAttributes';\nimport type { HiddenState, ScrollAreaRootState } from '../root/ScrollAreaRoot';\nimport { ScrollAreaViewportCssVars } from './ScrollAreaViewportCssVars';\nimport { normalizeScrollOffset } from '../utils/scrollEdges';\n\n// Module-level flag to ensure we only register the CSS properties once,\n// regardless of how many Scroll Area components are mounted.\nlet scrollAreaOverflowVarsRegistered = false;\n/**\n * Removes inheritance of the scroll area overflow CSS variables, which\n * improves rendering performance in complex scroll areas with deep subtrees.\n * Instead, each child must manually opt-in to using these properties by\n * specifying `inherit`.\n * See https://motion.dev/blog/web-animation-performance-tier-list\n * under the \"Improving CSS variable performance\" section.\n */\nfunction removeCSSVariableInheritance() {\n  if (\n    scrollAreaOverflowVarsRegistered ||\n    // When `inherits: false`, specifying `inherit` on child elements doesn't work\n    // in Safari. To let CSS features work correctly, this optimization must be skipped.\n    isWebKit\n  ) {\n    return;\n  }\n\n  if (typeof CSS !== 'undefined' && 'registerProperty' in CSS) {\n    [\n      ScrollAreaViewportCssVars.scrollAreaOverflowXStart,\n      ScrollAreaViewportCssVars.scrollAreaOverflowXEnd,\n      ScrollAreaViewportCssVars.scrollAreaOverflowYStart,\n      ScrollAreaViewportCssVars.scrollAreaOverflowYEnd,\n    ].forEach((name) => {\n      try {\n        CSS.registerProperty({\n          name,\n          syntax: '<length>',\n          inherits: false,\n          initialValue: '0px',\n        });\n      } catch {\n        /* ignore already-registered */\n      }\n    });\n  }\n\n  scrollAreaOverflowVarsRegistered = true;\n}\n\n/**\n * The actual scrollable container of the scroll area.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Scroll Area](https://base-ui.com/react/components/scroll-area)\n */\nexport const ScrollAreaViewport = React.forwardRef(function ScrollAreaViewport(\n  componentProps: ScrollAreaViewport.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, ...elementProps } = componentProps;\n\n  const {\n    viewportRef,\n    scrollbarYRef,\n    scrollbarXRef,\n    thumbYRef,\n    thumbXRef,\n    cornerRef,\n    cornerSize,\n    setCornerSize,\n    setThumbSize,\n    rootId,\n    setHiddenState,\n    hiddenState,\n    setHasMeasuredScrollbar,\n    handleScroll,\n    setHovering,\n    setOverflowEdges,\n    overflowEdges,\n    overflowEdgeThreshold,\n    scrollingX,\n    scrollingY,\n  } = useScrollAreaRootContext();\n\n  const direction = useDirection();\n\n  const programmaticScrollRef = React.useRef(true);\n  const lastMeasuredViewportMetricsRef = React.useRef<[number, number, number, number]>([\n    NaN,\n    NaN,\n    NaN,\n    NaN,\n  ]);\n\n  const scrollEndTimeout = useTimeout();\n  const waitForAnimationsTimeout = useTimeout();\n\n  const computeThumbPosition = useStableCallback(() => {\n    const viewportEl = viewportRef.current;\n    const scrollbarYEl = scrollbarYRef.current;\n    const scrollbarXEl = scrollbarXRef.current;\n    const thumbYEl = thumbYRef.current;\n    const thumbXEl = thumbXRef.current;\n    const cornerEl = cornerRef.current;\n\n    if (!viewportEl) {\n      return;\n    }\n\n    const scrollableContentHeight = viewportEl.scrollHeight;\n    const scrollableContentWidth = viewportEl.scrollWidth;\n    const viewportHeight = viewportEl.clientHeight;\n    const viewportWidth = viewportEl.clientWidth;\n    const scrollTop = viewportEl.scrollTop;\n    const scrollLeft = viewportEl.scrollLeft;\n    const lastMeasuredViewportMetrics = lastMeasuredViewportMetricsRef.current;\n    const isFirstMeasurement = Number.isNaN(lastMeasuredViewportMetrics[0]);\n    lastMeasuredViewportMetrics[0] = viewportHeight;\n    lastMeasuredViewportMetrics[1] = scrollableContentHeight;\n    lastMeasuredViewportMetrics[2] = viewportWidth;\n    lastMeasuredViewportMetrics[3] = scrollableContentWidth;\n\n    if (isFirstMeasurement) {\n      setHasMeasuredScrollbar(true);\n    }\n\n    if (scrollableContentHeight === 0 || scrollableContentWidth === 0) {\n      return;\n    }\n\n    const nextHiddenState = getHiddenState(viewportEl);\n    const scrollbarYHidden = nextHiddenState.y;\n    const scrollbarXHidden = nextHiddenState.x;\n    const ratioX = viewportWidth / scrollableContentWidth;\n    const ratioY = viewportHeight / scrollableContentHeight;\n    const maxScrollLeft = Math.max(0, scrollableContentWidth - viewportWidth);\n    const maxScrollTop = Math.max(0, scrollableContentHeight - viewportHeight);\n\n    let scrollLeftFromStart = 0;\n    let scrollLeftFromEnd = 0;\n    if (!scrollbarXHidden) {\n      let rawScrollLeftFromStart = 0;\n      if (direction === 'rtl') {\n        rawScrollLeftFromStart = clamp(-scrollLeft, 0, maxScrollLeft);\n      } else {\n        rawScrollLeftFromStart = clamp(scrollLeft, 0, maxScrollLeft);\n      }\n      scrollLeftFromStart = normalizeScrollOffset(rawScrollLeftFromStart, maxScrollLeft);\n      scrollLeftFromEnd = maxScrollLeft - scrollLeftFromStart;\n    }\n\n    const rawScrollTopFromStart = !scrollbarYHidden ? clamp(scrollTop, 0, maxScrollTop) : 0;\n    const scrollTopFromStart = !scrollbarYHidden\n      ? normalizeScrollOffset(rawScrollTopFromStart, maxScrollTop)\n      : 0;\n    const scrollTopFromEnd = !scrollbarYHidden ? maxScrollTop - scrollTopFromStart : 0;\n    const nextWidth = scrollbarXHidden ? 0 : viewportWidth;\n    const nextHeight = scrollbarYHidden ? 0 : viewportHeight;\n\n    let nextCornerWidth = 0;\n    let nextCornerHeight = 0;\n    if (!scrollbarXHidden && !scrollbarYHidden) {\n      nextCornerWidth = scrollbarYEl?.offsetWidth || 0;\n      nextCornerHeight = scrollbarXEl?.offsetHeight || 0;\n    }\n\n    // Only subtract corner size from scrollbar dimensions if the corner hasn't been sized yet.\n    // Once sized, the layout will already account for it.\n    const cornerNotYetSized = cornerSize.width === 0 && cornerSize.height === 0;\n    const cornerWidthOffset = cornerNotYetSized ? nextCornerWidth : 0;\n    const cornerHeightOffset = cornerNotYetSized ? nextCornerHeight : 0;\n\n    const scrollbarXOffset = getOffset(scrollbarXEl, 'padding', 'x');\n    const scrollbarYOffset = getOffset(scrollbarYEl, 'padding', 'y');\n    const thumbXOffset = getOffset(thumbXEl, 'margin', 'x');\n    const thumbYOffset = getOffset(thumbYEl, 'margin', 'y');\n\n    const idealNextWidth = nextWidth - scrollbarXOffset - thumbXOffset;\n    const idealNextHeight = nextHeight - scrollbarYOffset - thumbYOffset;\n\n    const maxNextWidth = scrollbarXEl\n      ? Math.min(scrollbarXEl.offsetWidth - cornerWidthOffset, idealNextWidth)\n      : idealNextWidth;\n    const maxNextHeight = scrollbarYEl\n      ? Math.min(scrollbarYEl.offsetHeight - cornerHeightOffset, idealNextHeight)\n      : idealNextHeight;\n\n    const clampedNextWidth = Math.max(MIN_THUMB_SIZE, maxNextWidth * ratioX);\n    const clampedNextHeight = Math.max(MIN_THUMB_SIZE, maxNextHeight * ratioY);\n\n    setThumbSize((prevSize) => {\n      if (prevSize.height === clampedNextHeight && prevSize.width === clampedNextWidth) {\n        return prevSize;\n      }\n\n      return {\n        width: clampedNextWidth,\n        height: clampedNextHeight,\n      };\n    });\n\n    // Handle Y (vertical) scroll\n    if (scrollbarYEl && thumbYEl) {\n      const maxThumbOffsetY =\n        scrollbarYEl.offsetHeight - clampedNextHeight - scrollbarYOffset - thumbYOffset;\n      const scrollRangeY = scrollableContentHeight - viewportHeight;\n      const scrollRatioY = scrollRangeY === 0 ? 0 : scrollTop / scrollRangeY;\n\n      // In Safari, don't allow it to go negative or too far as `scrollTop` considers the rubber\n      // band effect.\n      const thumbOffsetY = Math.min(maxThumbOffsetY, Math.max(0, scrollRatioY * maxThumbOffsetY));\n\n      thumbYEl.style.transform = `translate3d(0,${thumbOffsetY}px,0)`;\n    }\n\n    // Handle X (horizontal) scroll\n    if (scrollbarXEl && thumbXEl) {\n      const maxThumbOffsetX =\n        scrollbarXEl.offsetWidth - clampedNextWidth - scrollbarXOffset - thumbXOffset;\n      const scrollRangeX = scrollableContentWidth - viewportWidth;\n      const scrollRatioX = scrollRangeX === 0 ? 0 : scrollLeft / scrollRangeX;\n\n      // In Safari, don't allow it to go negative or too far as `scrollLeft` considers the rubber\n      // band effect.\n      const thumbOffsetX =\n        direction === 'rtl'\n          ? clamp(scrollRatioX * maxThumbOffsetX, -maxThumbOffsetX, 0)\n          : clamp(scrollRatioX * maxThumbOffsetX, 0, maxThumbOffsetX);\n\n      thumbXEl.style.transform = `translate3d(${thumbOffsetX}px,0,0)`;\n    }\n\n    const overflowMetricsPx: Array<[ScrollAreaViewportCssVars, number]> = [\n      [ScrollAreaViewportCssVars.scrollAreaOverflowXStart, scrollLeftFromStart],\n      [ScrollAreaViewportCssVars.scrollAreaOverflowXEnd, scrollLeftFromEnd],\n      [ScrollAreaViewportCssVars.scrollAreaOverflowYStart, scrollTopFromStart],\n      [ScrollAreaViewportCssVars.scrollAreaOverflowYEnd, scrollTopFromEnd],\n    ];\n\n    for (const [cssVar, value] of overflowMetricsPx) {\n      viewportEl.style.setProperty(cssVar, `${value}px`);\n    }\n\n    if (cornerEl) {\n      if (scrollbarXHidden || scrollbarYHidden) {\n        setCornerSize({ width: 0, height: 0 });\n      } else if (!scrollbarXHidden && !scrollbarYHidden) {\n        setCornerSize({ width: nextCornerWidth, height: nextCornerHeight });\n      }\n    }\n\n    setHiddenState((prevState) => mergeHiddenState(prevState, nextHiddenState));\n\n    const nextOverflowEdges = {\n      xStart: !scrollbarXHidden && scrollLeftFromStart > overflowEdgeThreshold.xStart,\n      xEnd: !scrollbarXHidden && scrollLeftFromEnd > overflowEdgeThreshold.xEnd,\n      yStart: !scrollbarYHidden && scrollTopFromStart > overflowEdgeThreshold.yStart,\n      yEnd: !scrollbarYHidden && scrollTopFromEnd > overflowEdgeThreshold.yEnd,\n    };\n\n    setOverflowEdges((prev) => {\n      if (\n        prev.xStart === nextOverflowEdges.xStart &&\n        prev.xEnd === nextOverflowEdges.xEnd &&\n        prev.yStart === nextOverflowEdges.yStart &&\n        prev.yEnd === nextOverflowEdges.yEnd\n      ) {\n        return prev;\n      }\n      return nextOverflowEdges;\n    });\n  });\n\n  useIsoLayoutEffect(() => {\n    if (!viewportRef.current) {\n      return;\n    }\n\n    removeCSSVariableInheritance();\n  }, [viewportRef]);\n\n  useIsoLayoutEffect(() => {\n    // Wait for scrollbar and thumb refs after hidden-state toggles, and refresh math on direction flips.\n    queueMicrotask(computeThumbPosition);\n  }, [computeThumbPosition, hiddenState, direction]);\n\n  useIsoLayoutEffect(() => {\n    // `onMouseEnter` doesn't fire upon load, so we need to check if the viewport is already\n    // being hovered.\n    if (viewportRef.current?.matches(':hover')) {\n      setHovering(true);\n    }\n  }, [viewportRef, setHovering]);\n\n  React.useEffect(() => {\n    const viewport = viewportRef.current;\n    if (typeof ResizeObserver === 'undefined' || !viewport) {\n      return undefined;\n    }\n\n    let hasInitialized = false;\n    const ro = new ResizeObserver(() => {\n      // Avoid duplicate mount-time recompute when observer data matches what the mount\n      // scheduling pass already measured. If dimensions changed before the first observer\n      // delivery, keep the recompute so overflow transitions stay in sync.\n      if (!hasInitialized) {\n        hasInitialized = true;\n        const lastMeasuredViewportMetrics = lastMeasuredViewportMetricsRef.current;\n        if (\n          lastMeasuredViewportMetrics[0] === viewport.clientHeight &&\n          lastMeasuredViewportMetrics[1] === viewport.scrollHeight &&\n          lastMeasuredViewportMetrics[2] === viewport.clientWidth &&\n          lastMeasuredViewportMetrics[3] === viewport.scrollWidth\n        ) {\n          return;\n        }\n      }\n\n      computeThumbPosition();\n    });\n\n    ro.observe(viewport);\n\n    // If there are animations in the viewport, wait for them to finish and then recompute the thumb position.\n    // This is necessary when the viewport contains a Dialog that is animating its popup on open\n    // and the popup is using a transform for the animation, which affects the size of the viewport.\n    // Without this, the thumb position will be incorrect until scrolling (i.e. if the scrollbar shows\n    // on hover, the thumb has an incorrect size).\n    // We assume the user is using `onOpenChangeComplete` to hide the scrollbar\n    // until animations complete because otherwise the scrollbar would show the thumb resizing mid-animation.\n    waitForAnimationsTimeout.start(0, () => {\n      const animations = viewport.getAnimations({ subtree: true });\n      if (animations.length === 0) {\n        return;\n      }\n\n      Promise.allSettled(animations.map((animation) => animation.finished))\n        .then(computeThumbPosition)\n        .catch(() => {});\n    });\n\n    return () => {\n      ro.disconnect();\n      waitForAnimationsTimeout.clear();\n    };\n  }, [computeThumbPosition, viewportRef, waitForAnimationsTimeout]);\n\n  function handleUserInteraction() {\n    programmaticScrollRef.current = false;\n  }\n\n  const props: React.ComponentProps<'div'> = {\n    role: 'presentation',\n    ...(rootId && { 'data-id': `${rootId}-viewport` }),\n    // https://accessibilityinsights.io/info-examples/web/scrollable-region-focusable/\n    // Keep non-scrollable viewports out of tab order.\n    tabIndex: hiddenState.x && hiddenState.y ? -1 : 0,\n    className: styleDisableScrollbar.className,\n    style: {\n      overflow: 'scroll',\n    },\n    onScroll() {\n      if (!viewportRef.current) {\n        return;\n      }\n\n      computeThumbPosition();\n\n      if (!programmaticScrollRef.current) {\n        handleScroll({\n          x: viewportRef.current.scrollLeft,\n          y: viewportRef.current.scrollTop,\n        });\n      }\n\n      // Debounce the restoration of the programmatic flag so that it only\n      // flips back to `true` once scrolling has come to a rest. This ensures\n      // that momentum scrolling (where no further user-interaction events fire)\n      // is still treated as user-driven.\n      // 100 ms without scroll events ≈ scroll end\n      // https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollend_event\n      scrollEndTimeout.start(100, () => {\n        programmaticScrollRef.current = true;\n      });\n    },\n    onWheel: handleUserInteraction,\n    onTouchMove: handleUserInteraction,\n    onPointerMove: handleUserInteraction,\n    onPointerEnter: handleUserInteraction,\n    onKeyDown: handleUserInteraction,\n  };\n\n  const viewportState: ScrollAreaViewportState = React.useMemo(\n    () => ({\n      scrolling: scrollingX || scrollingY,\n      hasOverflowX: !hiddenState.x,\n      hasOverflowY: !hiddenState.y,\n      overflowXStart: overflowEdges.xStart,\n      overflowXEnd: overflowEdges.xEnd,\n      overflowYStart: overflowEdges.yStart,\n      overflowYEnd: overflowEdges.yEnd,\n      cornerHidden: hiddenState.corner,\n    }),\n    [scrollingX, scrollingY, hiddenState.x, hiddenState.y, hiddenState.corner, overflowEdges],\n  );\n\n  const element = useRenderElement('div', componentProps, {\n    ref: [forwardedRef, viewportRef],\n    state: viewportState,\n    props: [props, elementProps],\n    stateAttributesMapping: scrollAreaStateAttributesMapping,\n  });\n\n  const contextValue: ScrollAreaViewportContext = React.useMemo(\n    () => ({\n      computeThumbPosition,\n    }),\n    [computeThumbPosition],\n  );\n\n  return (\n    <ScrollAreaViewportContext.Provider value={contextValue}>\n      {element}\n    </ScrollAreaViewportContext.Provider>\n  );\n});\n\nexport interface ScrollAreaViewportProps extends BaseUIComponentProps<\n  'div',\n  ScrollAreaViewportState\n> {}\n\nexport interface ScrollAreaViewportState extends ScrollAreaRootState {}\n\nexport namespace ScrollAreaViewport {\n  export type Props = ScrollAreaViewportProps;\n  export type State = ScrollAreaViewportState;\n}\n\nfunction getHiddenState(viewport: HTMLElement): HiddenState {\n  const y = viewport.clientHeight >= viewport.scrollHeight;\n  const x = viewport.clientWidth >= viewport.scrollWidth;\n\n  return {\n    y,\n    x,\n    corner: y || x,\n  };\n}\n\nfunction mergeHiddenState(prevState: HiddenState, nextState: HiddenState) {\n  if (\n    prevState.y === nextState.y &&\n    prevState.x === nextState.x &&\n    prevState.corner === nextState.corner\n  ) {\n    return prevState;\n  }\n\n  return nextState;\n}\n"
  },
  {
    "path": "packages/react/src/scroll-area/viewport/ScrollAreaViewportContext.ts",
    "content": "'use client';\nimport * as React from 'react';\n\nexport interface ScrollAreaViewportContext {\n  computeThumbPosition: () => void;\n}\n\nexport const ScrollAreaViewportContext = React.createContext<ScrollAreaViewportContext | undefined>(\n  undefined,\n);\n\nexport function useScrollAreaViewportContext() {\n  const context = React.useContext(ScrollAreaViewportContext);\n  if (context === undefined) {\n    throw new Error(\n      'Base UI: ScrollAreaViewportContext missing. ScrollAreaViewport parts must be placed within <ScrollArea.Viewport>.',\n    );\n  }\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/scroll-area/viewport/ScrollAreaViewportCssVars.ts",
    "content": "export enum ScrollAreaViewportCssVars {\n  /**\n   * The distance from the horizontal start edge in pixels.\n   * @type {number}\n   */\n  scrollAreaOverflowXStart = '--scroll-area-overflow-x-start',\n  /**\n   * The distance from the horizontal end edge in pixels.\n   * @type {number}\n   */\n  scrollAreaOverflowXEnd = '--scroll-area-overflow-x-end',\n  /**\n   * The distance from the vertical start edge in pixels.\n   * @type {number}\n   */\n  scrollAreaOverflowYStart = '--scroll-area-overflow-y-start',\n  /**\n   * The distance from the vertical end edge in pixels.\n   * @type {number}\n   */\n  scrollAreaOverflowYEnd = '--scroll-area-overflow-y-end',\n}\n"
  },
  {
    "path": "packages/react/src/scroll-area/viewport/ScrollAreaViewportDataAttributes.ts",
    "content": "export enum ScrollAreaViewportDataAttributes {\n  /**\n   * Present when the user scrolls inside the scroll area.\n   */\n  scrolling = 'data-scrolling',\n  /**\n   * Present when the scroll area content is wider than the viewport.\n   */\n  hasOverflowX = 'data-has-overflow-x',\n  /**\n   * Present when the scroll area content is taller than the viewport.\n   */\n  hasOverflowY = 'data-has-overflow-y',\n  /**\n   * Present when there is overflow on the horizontal start side.\n   */\n  overflowXStart = 'data-overflow-x-start',\n  /**\n   * Present when there is overflow on the horizontal end side.\n   */\n  overflowXEnd = 'data-overflow-x-end',\n  /**\n   * Present when there is overflow on the vertical start side.\n   */\n  overflowYStart = 'data-overflow-y-start',\n  /**\n   * Present when there is overflow on the vertical end side.\n   */\n  overflowYEnd = 'data-overflow-y-end',\n}\n"
  },
  {
    "path": "packages/react/src/select/arrow/SelectArrow.test.tsx",
    "content": "import { Select } from '@base-ui/react/select';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Select.Arrow />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Select.Arrow />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <Select.Root open>\n          <Select.Positioner alignItemWithTrigger={false}>{node}</Select.Positioner>\n        </Select.Root>,\n      );\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/select/arrow/SelectArrow.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStore } from '@base-ui/utils/store';\nimport { useSelectPositionerContext } from '../positioner/SelectPositionerContext';\nimport { useSelectRootContext } from '../root/SelectRootContext';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport type { Align, Side } from '../../utils/useAnchorPositioning';\nimport type { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport { popupStateMapping as baseMapping } from '../../utils/popupStateMapping';\nimport { transitionStatusMapping } from '../../utils/stateAttributesMapping';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { selectors } from '../store';\n\nconst stateAttributesMapping: StateAttributesMapping<SelectArrowState> = {\n  ...baseMapping,\n  ...transitionStatusMapping,\n};\n\n/**\n * Displays an element positioned against the select popup anchor.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Select](https://base-ui.com/react/components/select)\n */\nexport const SelectArrow = React.forwardRef(function SelectArrow(\n  componentProps: SelectArrow.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { className, render, ...elementProps } = componentProps;\n\n  const { store } = useSelectRootContext();\n  const { side, align, arrowRef, arrowStyles, arrowUncentered, alignItemWithTriggerActive } =\n    useSelectPositionerContext();\n\n  const open = useStore(store, selectors.open, true);\n\n  const state: SelectArrowState = {\n    open,\n    side,\n    align,\n    uncentered: arrowUncentered,\n  };\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: [arrowRef, forwardedRef],\n    props: [{ style: arrowStyles, 'aria-hidden': true }, elementProps],\n    stateAttributesMapping,\n  });\n\n  if (alignItemWithTriggerActive) {\n    return null;\n  }\n\n  return element;\n});\n\nexport interface SelectArrowState {\n  /**\n   * Whether the select popup is currently open.\n   */\n  open: boolean;\n  /**\n   * The side of the anchor the component is placed on.\n   */\n  side: Side | 'none';\n  /**\n   * The alignment of the component relative to the anchor.\n   */\n  align: Align;\n  /**\n   * Whether the arrow cannot be centered on the anchor.\n   */\n  uncentered: boolean;\n}\n\nexport interface SelectArrowProps extends BaseUIComponentProps<'div', SelectArrowState> {}\n\nexport namespace SelectArrow {\n  export type State = SelectArrowState;\n  export type Props = SelectArrowProps;\n}\n"
  },
  {
    "path": "packages/react/src/select/arrow/SelectArrowDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum SelectArrowDataAttributes {\n  /**\n   * Present when the select popup is open.\n   */\n  open = CommonPopupDataAttributes.open,\n  /**\n   * Present when the select popup is closed.\n   */\n  closed = CommonPopupDataAttributes.closed,\n  /**\n   * Indicates which side the popup is positioned relative to the trigger.\n   * @type {'none' | 'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'}\n   */\n  side = CommonPopupDataAttributes.side,\n  /**\n   * Indicates how the popup is aligned relative to specified side.\n   * @type {'start' | 'center' | 'end'}\n   */\n  align = CommonPopupDataAttributes.align,\n  /**\n   * Present when the select arrow is uncentered.\n   */\n  uncentered = 'data-uncentered',\n}\n"
  },
  {
    "path": "packages/react/src/select/backdrop/SelectBackdrop.test.tsx",
    "content": "import { Select } from '@base-ui/react/select';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Select.Backdrop />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Select.Backdrop />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(<Select.Root open>{node}</Select.Root>);\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/select/backdrop/SelectBackdrop.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStore } from '@base-ui/utils/store';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useSelectRootContext } from '../root/SelectRootContext';\nimport { popupStateMapping } from '../../utils/popupStateMapping';\nimport type { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport type { TransitionStatus } from '../../utils/useTransitionStatus';\nimport { transitionStatusMapping } from '../../utils/stateAttributesMapping';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { selectors } from '../store';\n\nconst stateAttributesMapping: StateAttributesMapping<SelectBackdropState> = {\n  ...popupStateMapping,\n  ...transitionStatusMapping,\n};\n\n/**\n * An overlay displayed beneath the menu popup.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Select](https://base-ui.com/react/components/select)\n */\nexport const SelectBackdrop = React.forwardRef(function SelectBackdrop(\n  componentProps: SelectBackdrop.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { className, render, ...elementProps } = componentProps;\n\n  const { store } = useSelectRootContext();\n\n  const open = useStore(store, selectors.open);\n  const mounted = useStore(store, selectors.mounted);\n  const transitionStatus = useStore(store, selectors.transitionStatus);\n\n  const state: SelectBackdropState = {\n    open,\n    transitionStatus,\n  };\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: forwardedRef,\n    props: [\n      {\n        role: 'presentation',\n        hidden: !mounted,\n        style: {\n          userSelect: 'none',\n          WebkitUserSelect: 'none',\n        },\n      },\n      elementProps,\n    ],\n    stateAttributesMapping,\n  });\n\n  return element;\n});\n\nexport interface SelectBackdropState {\n  /**\n   * Whether the component is open.\n   */\n  open: boolean;\n  /**\n   * The transition status of the component.\n   */\n  transitionStatus: TransitionStatus;\n}\n\nexport interface SelectBackdropProps extends BaseUIComponentProps<'div', SelectBackdropState> {}\n\nexport namespace SelectBackdrop {\n  export type State = SelectBackdropState;\n  export type Props = SelectBackdropProps;\n}\n"
  },
  {
    "path": "packages/react/src/select/backdrop/SelectBackdropDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum SelectBackdropDataAttributes {\n  /**\n   * Present when the select is open.\n   */\n  open = CommonPopupDataAttributes.open,\n  /**\n   * Present when the select is closed.\n   */\n  closed = CommonPopupDataAttributes.closed,\n  /**\n   * Present when the select is animating in.\n   */\n  startingStyle = CommonPopupDataAttributes.startingStyle,\n  /**\n   * Present when the select is animating out.\n   */\n  endingStyle = CommonPopupDataAttributes.endingStyle,\n}\n"
  },
  {
    "path": "packages/react/src/select/group/SelectGroup.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { Select } from '@base-ui/react/select';\nimport { createRenderer, describeConformance } from '#test-utils';\nimport { screen } from '@mui/internal-test-utils';\n\ndescribe('<Select.Group />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Select.Group />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(<Select.Root open>{node}</Select.Root>);\n    },\n  }));\n\n  it('should render group with label', async () => {\n    await render(\n      <Select.Root open>\n        <Select.Positioner>\n          <Select.Group>\n            <Select.GroupLabel>Fruits</Select.GroupLabel>\n            <Select.Item value=\"apple\">Apple</Select.Item>\n            <Select.Item value=\"banana\">Banana</Select.Item>\n          </Select.Group>\n        </Select.Positioner>\n      </Select.Root>,\n    );\n\n    expect(screen.getByRole('group')).toHaveAttribute('aria-labelledby');\n    expect(screen.getByText('Fruits')).toBeVisible();\n  });\n\n  it('should associate label with group', async () => {\n    await render(\n      <Select.Root open>\n        <Select.Positioner>\n          <Select.Group>\n            <Select.GroupLabel>Vegetables</Select.GroupLabel>\n            <Select.Item value=\"carrot\">Carrot</Select.Item>\n            <Select.Item value=\"lettuce\">Lettuce</Select.Item>\n          </Select.Group>\n        </Select.Positioner>\n      </Select.Root>,\n    );\n\n    const Group = screen.getByRole('group');\n    const label = screen.getByText('Vegetables');\n    expect(Group).toHaveAttribute('aria-labelledby', label.id);\n  });\n});\n"
  },
  {
    "path": "packages/react/src/select/group/SelectGroup.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { SelectGroupContext } from './SelectGroupContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\n\n/**\n * Groups related select items with the corresponding label.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Select](https://base-ui.com/react/components/select)\n */\nexport const SelectGroup = React.forwardRef(function SelectGroup(\n  componentProps: SelectGroup.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { className, render, ...elementProps } = componentProps;\n\n  const [labelId, setLabelId] = React.useState<string | undefined>();\n\n  const contextValue: SelectGroupContext = React.useMemo(\n    () => ({\n      labelId,\n      setLabelId,\n    }),\n    [labelId, setLabelId],\n  );\n\n  const element = useRenderElement('div', componentProps, {\n    ref: forwardedRef,\n    props: [\n      {\n        role: 'group',\n        'aria-labelledby': labelId,\n      },\n      elementProps,\n    ],\n  });\n\n  return <SelectGroupContext.Provider value={contextValue}>{element}</SelectGroupContext.Provider>;\n});\n\nexport interface SelectGroupState {}\nexport interface SelectGroupProps extends BaseUIComponentProps<'div', SelectGroupState> {}\n\nexport namespace SelectGroup {\n  export type State = SelectGroupState;\n  export type Props = SelectGroupProps;\n}\n"
  },
  {
    "path": "packages/react/src/select/group/SelectGroupContext.ts",
    "content": "'use client';\nimport * as React from 'react';\n\nexport interface SelectGroupContext {\n  labelId: string | undefined;\n  setLabelId: React.Dispatch<React.SetStateAction<string | undefined>>;\n}\n\nexport const SelectGroupContext = React.createContext<SelectGroupContext | undefined>(undefined);\n\nexport function useSelectGroupContext() {\n  const context = React.useContext(SelectGroupContext);\n  if (context === undefined) {\n    throw new Error(\n      'Base UI: SelectGroupContext is missing. SelectGroup parts must be placed within <Select.Group>.',\n    );\n  }\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/select/group-label/SelectGroupLabel.test.tsx",
    "content": "import { Select } from '@base-ui/react/select';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Select.GroupLabel />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Select.GroupLabel />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <Select.Root open>\n          <Select.Group>{node}</Select.Group>\n        </Select.Root>,\n      );\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/select/group-label/SelectGroupLabel.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useBaseUiId } from '../../utils/useBaseUiId';\nimport { useSelectGroupContext } from '../group/SelectGroupContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\n\n/**\n * An accessible label that is automatically associated with its parent group.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Select](https://base-ui.com/react/components/select)\n */\nexport const SelectGroupLabel = React.forwardRef(function SelectGroupLabel(\n  componentProps: SelectGroupLabel.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { className, render, id: idProp, ...elementProps } = componentProps;\n\n  const { setLabelId } = useSelectGroupContext();\n\n  const id = useBaseUiId(idProp);\n\n  useIsoLayoutEffect(() => {\n    setLabelId(id);\n  }, [id, setLabelId]);\n\n  const element = useRenderElement('div', componentProps, {\n    ref: forwardedRef,\n    props: [{ id }, elementProps],\n  });\n\n  return element;\n});\n\nexport interface SelectGroupLabelState {}\n\nexport interface SelectGroupLabelProps extends BaseUIComponentProps<'div', SelectGroupLabelState> {}\n\nexport namespace SelectGroupLabel {\n  export type State = SelectGroupLabelState;\n  export type Props = SelectGroupLabelProps;\n}\n"
  },
  {
    "path": "packages/react/src/select/icon/SelectIcon.test.tsx",
    "content": "import { Select } from '@base-ui/react/select';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Select.Icon />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Select.Icon />, () => ({\n    refInstanceof: window.HTMLSpanElement,\n    render(node) {\n      return render(<Select.Root open>{node}</Select.Root>);\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/select/icon/SelectIcon.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStore } from '@base-ui/utils/store';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useSelectRootContext } from '../root/SelectRootContext';\nimport { triggerOpenStateMapping } from '../../utils/popupStateMapping';\nimport { selectors } from '../store';\n\n/**\n * An icon that indicates that the trigger button opens a select popup.\n * Renders a `<span>` element.\n *\n * Documentation: [Base UI Select](https://base-ui.com/react/components/select)\n */\nexport const SelectIcon = React.forwardRef(function SelectIcon(\n  componentProps: SelectIcon.Props,\n  forwardedRef: React.ForwardedRef<HTMLSpanElement>,\n) {\n  const { className, render, ...elementProps } = componentProps;\n\n  const { store } = useSelectRootContext();\n  const open = useStore(store, selectors.open);\n\n  const state: SelectIconState = {\n    open,\n  };\n\n  const element = useRenderElement('span', componentProps, {\n    state,\n    ref: forwardedRef,\n    props: [{ 'aria-hidden': true, children: '▼' }, elementProps],\n    stateAttributesMapping: triggerOpenStateMapping,\n  });\n\n  return element;\n});\n\nexport interface SelectIconState {\n  /**\n   * Whether the select popup is currently open.\n   */\n  open: boolean;\n}\n\nexport interface SelectIconProps extends BaseUIComponentProps<'span', SelectIconState> {}\n\nexport namespace SelectIcon {\n  export type State = SelectIconState;\n  export type Props = SelectIconProps;\n}\n"
  },
  {
    "path": "packages/react/src/select/icon/SelectIconDataAttributes.ts",
    "content": "import { CommonTriggerDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum SelectIconDataAttributes {\n  /**\n   * Present when the corresponding popup is open.\n   */\n  popupOpen = CommonTriggerDataAttributes.popupOpen,\n}\n"
  },
  {
    "path": "packages/react/src/select/index.parts.ts",
    "content": "export { SelectRoot as Root } from './root/SelectRoot';\nexport { SelectLabel as Label } from './label/SelectLabel';\nexport { SelectTrigger as Trigger } from './trigger/SelectTrigger';\nexport { SelectValue as Value } from './value/SelectValue';\nexport { SelectIcon as Icon } from './icon/SelectIcon';\nexport { SelectPortal as Portal } from './portal/SelectPortal';\nexport { SelectBackdrop as Backdrop } from './backdrop/SelectBackdrop';\nexport { SelectPositioner as Positioner } from './positioner/SelectPositioner';\nexport { SelectPopup as Popup } from './popup/SelectPopup';\nexport { SelectList as List } from './list/SelectList';\nexport { SelectItem as Item } from './item/SelectItem';\nexport { SelectItemIndicator as ItemIndicator } from './item-indicator/SelectItemIndicator';\nexport { SelectItemText as ItemText } from './item-text/SelectItemText';\nexport { SelectArrow as Arrow } from './arrow/SelectArrow';\nexport { SelectScrollDownArrow as ScrollDownArrow } from './scroll-down-arrow/SelectScrollDownArrow';\nexport { SelectScrollUpArrow as ScrollUpArrow } from './scroll-up-arrow/SelectScrollUpArrow';\nexport { SelectGroup as Group } from './group/SelectGroup';\nexport { SelectGroupLabel as GroupLabel } from './group-label/SelectGroupLabel';\nexport { Separator } from '../separator/Separator';\n"
  },
  {
    "path": "packages/react/src/select/index.ts",
    "content": "export * as Select from './index.parts';\n\nexport type * from './root/SelectRoot';\nexport type * from './label/SelectLabel';\nexport type * from './trigger/SelectTrigger';\nexport type * from './value/SelectValue';\nexport type * from './icon/SelectIcon';\nexport type * from './portal/SelectPortal';\nexport type * from './backdrop/SelectBackdrop';\nexport type * from './positioner/SelectPositioner';\nexport type * from './popup/SelectPopup';\nexport type * from './list/SelectList';\nexport type * from './item/SelectItem';\nexport type * from './item-indicator/SelectItemIndicator';\nexport type * from './item-text/SelectItemText';\nexport type * from './arrow/SelectArrow';\nexport type * from './scroll-down-arrow/SelectScrollDownArrow';\nexport type * from './scroll-up-arrow/SelectScrollUpArrow';\nexport type * from './group/SelectGroup';\nexport type * from './group-label/SelectGroupLabel';\n"
  },
  {
    "path": "packages/react/src/select/item/SelectItem.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport { Select } from '@base-ui/react/select';\nimport {\n  act,\n  fireEvent,\n  flushMicrotasks,\n  ignoreActWarnings,\n  screen,\n  waitFor,\n} from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\n\ndescribe('<Select.Item />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Select.Item value=\"\" />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    button: true,\n    render(node) {\n      return render(<Select.Root open>{node}</Select.Root>);\n    },\n  }));\n\n  it('should select the item and close popup when clicked', async () => {\n    ignoreActWarnings();\n    await render(\n      <Select.Root>\n        <Select.Trigger data-testid=\"trigger\">\n          <Select.Value data-testid=\"value\" />\n        </Select.Trigger>\n        <Select.Positioner data-testid=\"positioner\">\n          <Select.Item value=\"one\">one</Select.Item>\n        </Select.Positioner>\n      </Select.Root>,\n    );\n\n    const value = screen.getByTestId('value');\n    const trigger = screen.getByTestId('trigger');\n    const positioner = screen.getByTestId('positioner');\n\n    expect(value.textContent).toBe('');\n\n    fireEvent.click(trigger);\n\n    await flushMicrotasks();\n\n    fireEvent.click(screen.getByText('one'));\n\n    await flushMicrotasks();\n\n    expect(value.textContent).toBe('one');\n\n    expect(positioner).not.toBeVisible();\n  });\n\n  it.skipIf(!isJSDOM)('navigating with keyboard should focus item', async () => {\n    const { user } = await render(\n      <Select.Root>\n        <Select.Trigger data-testid=\"trigger\">\n          <Select.Value />\n        </Select.Trigger>\n        <Select.Portal>\n          <Select.Positioner>\n            <Select.Popup>\n              <Select.Item value=\"one\">one</Select.Item>\n              <Select.Item value=\"two\">two</Select.Item>\n              <Select.Item value=\"three\">three</Select.Item>\n            </Select.Popup>\n          </Select.Positioner>\n        </Select.Portal>\n      </Select.Root>,\n    );\n\n    fireEvent.click(screen.getByTestId('trigger'));\n    await flushMicrotasks();\n\n    await waitFor(() => {\n      expect(screen.getByRole('listbox')).not.toBe(null);\n    });\n    await waitFor(() => {\n      expect(screen.getByText('one')).toHaveFocus();\n    });\n\n    await user.keyboard('{ArrowDown}');\n    await waitFor(() => {\n      expect(screen.getByText('two')).toHaveFocus();\n    });\n\n    await user.keyboard('{ArrowDown}');\n    await waitFor(() => {\n      expect(screen.getByText('three')).toHaveFocus();\n    });\n  });\n\n  it.skipIf(!isJSDOM)('should select item when Enter key is pressed', async () => {\n    const { user } = await render(\n      <Select.Root>\n        <Select.Trigger data-testid=\"trigger\">\n          <Select.Value data-testid=\"value\" />\n        </Select.Trigger>\n        <Select.Portal>\n          <Select.Positioner>\n            <Select.Popup>\n              <Select.Item value=\"one\">one</Select.Item>\n              <Select.Item value=\"two\">two</Select.Item>\n            </Select.Popup>\n          </Select.Positioner>\n        </Select.Portal>\n      </Select.Root>,\n    );\n\n    fireEvent.click(screen.getByTestId('trigger'));\n    await flushMicrotasks();\n\n    await user.keyboard('{ArrowDown}');\n    await user.keyboard('{ArrowDown}');\n    await user.keyboard('{Enter}');\n\n    await waitFor(() => {\n      expect(screen.getByTestId('value').textContent).toBe('two');\n    });\n  });\n\n  it('should focus disabled items', async () => {\n    await render(\n      <Select.Root open>\n        <Select.Trigger data-testid=\"trigger\">\n          <Select.Value data-testid=\"value\" />\n        </Select.Trigger>\n        <Select.Portal>\n          <Select.Positioner>\n            <Select.Popup>\n              <Select.Item value=\"two\" disabled>\n                two\n              </Select.Item>\n            </Select.Popup>\n          </Select.Positioner>\n        </Select.Portal>\n      </Select.Root>,\n    );\n\n    const item = screen.getByText('two');\n    await act(() => item.focus());\n    await waitFor(() => {\n      expect(item).toHaveFocus();\n    });\n  });\n\n  it('should not select disabled item', async () => {\n    await render(\n      <Select.Root>\n        <Select.Trigger data-testid=\"trigger\">\n          <Select.Value data-testid=\"value\" />\n        </Select.Trigger>\n        <Select.Portal>\n          <Select.Positioner>\n            <Select.Popup>\n              <Select.Item value=\"one\">one</Select.Item>\n              <Select.Item value=\"two\" disabled>\n                two\n              </Select.Item>\n            </Select.Popup>\n          </Select.Positioner>\n        </Select.Portal>\n      </Select.Root>,\n    );\n\n    fireEvent.click(screen.getByTestId('trigger'));\n    await flushMicrotasks();\n\n    fireEvent.click(screen.getByText('two'));\n    expect(screen.getByTestId('value').textContent).toBe('');\n  });\n\n  it('should call onClick exactly once for a regular click', async () => {\n    const handleClick = vi.fn();\n\n    await render(\n      <Select.Root>\n        <Select.Trigger data-testid=\"trigger\">\n          <Select.Value data-testid=\"value\" />\n        </Select.Trigger>\n        <Select.Portal>\n          <Select.Positioner>\n            <Select.Popup>\n              <Select.Item value=\"one\" onClick={handleClick}>\n                one\n              </Select.Item>\n              <Select.Item value=\"two\">two</Select.Item>\n            </Select.Popup>\n          </Select.Positioner>\n        </Select.Portal>\n      </Select.Root>,\n    );\n\n    fireEvent.click(screen.getByTestId('trigger'));\n    await flushMicrotasks();\n\n    fireEvent.click(screen.getByRole('option', { name: 'one' }));\n    await flushMicrotasks();\n\n    expect(screen.getByTestId('value').textContent).toBe('one');\n    expect(handleClick).toHaveBeenCalledOnce();\n  });\n\n  it('should focus the selected item upon opening the popup', async () => {\n    const { user } = await render(\n      <Select.Root>\n        <Select.Trigger data-testid=\"trigger\">\n          <Select.Value data-testid=\"value\" />\n        </Select.Trigger>\n        <Select.Portal>\n          <Select.Positioner>\n            <Select.Popup>\n              <Select.Item value=\"one\">one</Select.Item>\n              <Select.Item value=\"two\">two</Select.Item>\n              <Select.Item value=\"three\">three</Select.Item>\n            </Select.Popup>\n          </Select.Positioner>\n        </Select.Portal>\n      </Select.Root>,\n    );\n\n    const trigger = screen.getByTestId('trigger');\n\n    fireEvent.click(trigger);\n    await user.click(screen.getByRole('option', { name: 'three' }));\n    fireEvent.click(trigger);\n\n    await waitFor(() => {\n      expect(screen.getByRole('option', { name: 'three' })).toHaveFocus();\n    });\n  });\n\n  it.skipIf(isJSDOM)(\n    'should allow pointer click after a typeahead sequence that ends with Space',\n    async () => {\n      const { user } = await render(\n        <Select.Root defaultOpen>\n          <Select.Trigger data-testid=\"trigger\">\n            <Select.Value data-testid=\"value\" />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"one\">Item One</Select.Item>\n                <Select.Item value=\"two\">Item Two</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      const [firstItem, secondItem] = screen.getAllByRole('option');\n\n      await act(async () => {\n        firstItem.focus();\n      });\n\n      await user.keyboard('item t ');\n\n      await waitFor(() => {\n        expect(secondItem).toHaveFocus();\n      });\n\n      await user.click(secondItem);\n\n      await waitFor(() => {\n        expect(screen.getByTestId('value').textContent).toBe('two');\n      });\n    },\n  );\n\n  describe.skipIf(!isJSDOM)('quick selection', () => {\n    const { render: renderFakeTimers, clock } = createRenderer({\n      clockOptions: {\n        shouldAdvanceTime: true,\n      },\n    });\n\n    clock.withFakeTimers();\n\n    it('should not select an item on quick mouseup when showing a placeholder (no null item)', async () => {\n      ignoreActWarnings();\n      const fonts = [\n        { label: 'Sans-serif', value: 'sans' },\n        { label: 'Serif', value: 'serif' },\n        { label: 'Monospace', value: 'mono' },\n        { label: 'Cursive', value: 'cursive' },\n      ];\n\n      await renderFakeTimers(\n        <Select.Root items={fonts}>\n          <Select.Trigger data-testid=\"trigger\">\n            <Select.Value data-testid=\"value\" placeholder=\"Select font\" />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                {fonts.map(({ label, value }) => (\n                  <Select.Item key={value} value={value}>\n                    {label}\n                  </Select.Item>\n                ))}\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      const value = screen.getByTestId('value');\n\n      expect(value.textContent).toBe('Select font');\n\n      // Open on mousedown and keep the mouse button \"held\" (no mouseup yet).\n      fireEvent.mouseDown(trigger);\n\n      await waitFor(() => {\n        expect(screen.queryByRole('listbox')).not.toBe(null);\n      });\n\n      const option = screen.getByRole('option', { name: 'Sans-serif' });\n      fireEvent.mouseMove(option);\n\n      // Release quickly over an unselected option.\n      await clock.tickAsync(250);\n      fireEvent.mouseUp(option);\n\n      await waitFor(() => {\n        expect(value.textContent).toBe('Select font');\n      });\n    });\n\n    it('should call onClick when selecting via drag-to-select (mousedown on trigger, mouseup on item)', async () => {\n      ignoreActWarnings();\n      const handleClick = vi.fn();\n\n      await renderFakeTimers(\n        <Select.Root>\n          <Select.Trigger data-testid=\"trigger\">\n            <Select.Value data-testid=\"value\" placeholder=\"Select font\" />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"one\" onClick={handleClick}>\n                  one\n                </Select.Item>\n                <Select.Item value=\"two\">two</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      fireEvent.mouseDown(trigger);\n      await waitFor(() => expect(screen.queryByRole('listbox')).not.toBe(null));\n\n      const option = screen.getByRole('option', { name: 'one' });\n      fireEvent.pointerEnter(option, { pointerType: 'mouse' });\n      fireEvent.pointerMove(option, { pointerType: 'mouse' });\n\n      // Wait past the delay gates and release the mouse over the option\n      await act(async () => {\n        await clock.tickAsync(500);\n      });\n      fireEvent.mouseUp(option);\n\n      await waitFor(() => expect(screen.getByTestId('value').textContent).toBe('one'));\n      expect(handleClick).toHaveBeenCalledOnce();\n    });\n\n    it('should not select item when onClick calls preventBaseUIHandler during drag-to-select', async () => {\n      ignoreActWarnings();\n      const handleClick = vi.fn((event) => event.preventBaseUIHandler());\n\n      await renderFakeTimers(\n        <Select.Root>\n          <Select.Trigger data-testid=\"trigger\">\n            <Select.Value data-testid=\"value\" placeholder=\"Select font\" />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"one\" onClick={handleClick}>\n                  one\n                </Select.Item>\n                <Select.Item value=\"two\">two</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      fireEvent.mouseDown(trigger);\n      await waitFor(() => expect(screen.queryByRole('listbox')).not.toBe(null));\n\n      const option = screen.getByRole('option', { name: 'one' });\n      fireEvent.pointerEnter(option, { pointerType: 'mouse' });\n      fireEvent.pointerMove(option, { pointerType: 'mouse' });\n\n      // Wait past the delay gates and release the mouse over the option\n      await act(async () => {\n        await clock.tickAsync(500);\n      });\n      fireEvent.mouseUp(option);\n\n      expect(handleClick).toHaveBeenCalledOnce();\n      expect(screen.getByTestId('value').textContent).toBe('Select font');\n      expect(screen.queryByRole('listbox')).not.toBe(null);\n    });\n  });\n\n  describe.skipIf(!isJSDOM)('style hooks', () => {\n    it('should apply data-highlighted attribute when item is highlighted', async () => {\n      const { user } = await render(\n        <Select.Root defaultValue=\"a\">\n          <Select.Trigger data-testid=\"trigger\" />\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"a\">a</Select.Item>\n                <Select.Item value=\"b\">b</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      fireEvent.click(screen.getByTestId('trigger'));\n      await flushMicrotasks();\n\n      expect(screen.getByRole('option', { name: 'a' })).toHaveAttribute('data-highlighted', '');\n      expect(screen.getByRole('option', { name: 'b' })).not.toHaveAttribute('data-highlighted');\n\n      await user.keyboard('{ArrowDown}');\n      await flushMicrotasks();\n\n      expect(screen.getByRole('option', { name: 'a' })).not.toHaveAttribute('data-highlighted');\n      expect(screen.getByRole('option', { name: 'b' })).toHaveAttribute('data-highlighted', '');\n    });\n\n    it('should apply data-selected attribute when item is selected', async () => {\n      await render(\n        <Select.Root>\n          <Select.Trigger data-testid=\"trigger\" />\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"a\">a</Select.Item>\n                <Select.Item value=\"b\">b</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      fireEvent.click(screen.getByTestId('trigger'));\n      await flushMicrotasks();\n\n      fireEvent.click(screen.getByRole('option', { name: 'a' }));\n      await flushMicrotasks();\n\n      fireEvent.click(screen.getByTestId('trigger'));\n      await waitFor(() => {\n        expect(screen.getByRole('option', { name: 'a' })).toHaveAttribute('data-selected', '');\n      });\n      expect(screen.getByRole('option', { name: 'b' })).not.toHaveAttribute('data-selected');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/select/item/SelectItem.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useValueAsRef } from '@base-ui/utils/useValueAsRef';\nimport { isMouseWithinBounds } from '@base-ui/utils/isMouseWithinBounds';\nimport { useTimeout } from '@base-ui/utils/useTimeout';\nimport { useStore } from '@base-ui/utils/store';\nimport { useSelectRootContext } from '../root/SelectRootContext';\nimport {\n  useCompositeListItem,\n  IndexGuessBehavior,\n} from '../../composite/list/useCompositeListItem';\nimport type {\n  BaseUIComponentProps,\n  BaseUIEvent,\n  HTMLProps,\n  NonNativeButtonProps,\n} from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { SelectItemContext } from './SelectItemContext';\nimport { selectors } from '../store';\nimport { useButton } from '../../use-button';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport { compareItemEquality, removeItem } from '../../utils/itemEquality';\n\n/**\n * An individual option in the select popup.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Select](https://base-ui.com/react/components/select)\n */\nexport const SelectItem = React.memo(\n  React.forwardRef(function SelectItem(\n    componentProps: SelectItem.Props,\n    forwardedRef: React.ForwardedRef<HTMLElement>,\n  ) {\n    const {\n      render,\n      className,\n      value: itemValue = null,\n      label,\n      disabled = false,\n      nativeButton = false,\n      ...elementProps\n    } = componentProps;\n\n    const textRef = React.useRef<HTMLElement | null>(null);\n    const listItem = useCompositeListItem({\n      label,\n      textRef,\n      indexGuessBehavior: IndexGuessBehavior.GuessFromOrder,\n    });\n\n    const {\n      store,\n      getItemProps,\n      setOpen,\n      setValue,\n      selectionRef,\n      typingRef,\n      valuesRef,\n      keyboardActiveRef,\n      multiple,\n      highlightItemOnHover,\n    } = useSelectRootContext();\n\n    const highlightTimeout = useTimeout();\n\n    const highlighted = useStore(store, selectors.isActive, listItem.index);\n    const selected = useStore(store, selectors.isSelected, listItem.index, itemValue);\n    const selectedByFocus = useStore(store, selectors.isSelectedByFocus, listItem.index);\n    const isItemEqualToValue = useStore(store, selectors.isItemEqualToValue);\n\n    const index = listItem.index;\n    const hasRegistered = index !== -1;\n\n    const itemRef = React.useRef<HTMLDivElement | null>(null);\n    const indexRef = useValueAsRef(index);\n\n    useIsoLayoutEffect(() => {\n      if (!hasRegistered) {\n        return undefined;\n      }\n\n      const values = valuesRef.current;\n      values[index] = itemValue;\n\n      return () => {\n        delete values[index];\n      };\n    }, [hasRegistered, index, itemValue, valuesRef]);\n\n    useIsoLayoutEffect(() => {\n      if (!hasRegistered) {\n        return undefined;\n      }\n\n      const selectedValue = store.state.value;\n\n      let selectedCandidate = selectedValue;\n      if (multiple && Array.isArray(selectedValue) && selectedValue.length > 0) {\n        selectedCandidate = selectedValue[selectedValue.length - 1];\n      }\n\n      if (\n        selectedCandidate !== undefined &&\n        compareItemEquality(itemValue, selectedCandidate, isItemEqualToValue)\n      ) {\n        store.set('selectedIndex', index);\n      }\n      return undefined;\n    }, [hasRegistered, index, multiple, isItemEqualToValue, store, itemValue]);\n\n    const state: SelectItemState = {\n      disabled,\n      selected,\n      highlighted,\n    };\n\n    const rootProps = getItemProps({ active: highlighted, selected });\n    // With our custom `focusItemOnHover` implementation, this interferes with the logic and can\n    // cause the index state to be stuck when leaving the select popup.\n    rootProps.onFocus = undefined;\n    rootProps.id = undefined;\n\n    const lastKeyRef = React.useRef<string | null>(null);\n    const pointerTypeRef = React.useRef<'mouse' | 'touch' | 'pen'>('mouse');\n    const didPointerDownRef = React.useRef(false);\n\n    const { getButtonProps, buttonRef } = useButton({\n      disabled,\n      focusableWhenDisabled: true,\n      native: nativeButton,\n      composite: true,\n    });\n\n    function commitSelection(event: MouseEvent) {\n      const selectedValue = store.state.value;\n      if (multiple) {\n        const currentValue = Array.isArray(selectedValue) ? selectedValue : [];\n        const nextValue = selected\n          ? removeItem(currentValue, itemValue, isItemEqualToValue)\n          : [...currentValue, itemValue];\n        setValue(nextValue, createChangeEventDetails(REASONS.itemPress, event));\n      } else {\n        setValue(itemValue, createChangeEventDetails(REASONS.itemPress, event));\n        setOpen(false, createChangeEventDetails(REASONS.itemPress, event));\n      }\n    }\n\n    const defaultProps: HTMLProps = {\n      role: 'option',\n      'aria-selected': selected,\n      tabIndex: highlighted ? 0 : -1,\n      onFocus() {\n        store.set('activeIndex', index);\n      },\n      onMouseEnter() {\n        if (\n          !keyboardActiveRef.current &&\n          store.state.selectedIndex === null &&\n          highlightItemOnHover\n        ) {\n          store.set('activeIndex', index);\n        }\n      },\n      onMouseMove() {\n        if (highlightItemOnHover) {\n          store.set('activeIndex', index);\n        }\n      },\n      onMouseLeave(event) {\n        if (\n          !highlightItemOnHover ||\n          keyboardActiveRef.current ||\n          pointerTypeRef.current === 'touch' ||\n          isMouseWithinBounds(event)\n        ) {\n          return;\n        }\n\n        highlightTimeout.start(0, () => {\n          if (pointerTypeRef.current === 'touch') {\n            return;\n          }\n\n          if (store.state.activeIndex === index) {\n            store.set('activeIndex', null);\n          }\n        });\n      },\n      onTouchStart() {\n        selectionRef.current = {\n          allowSelectedMouseUp: false,\n          allowUnselectedMouseUp: false,\n        };\n      },\n      onKeyDown(event: BaseUIEvent<React.KeyboardEvent>) {\n        lastKeyRef.current = event.key;\n        store.set('activeIndex', index);\n\n        if (event.key === ' ' && typingRef.current) {\n          event.preventDefault();\n        }\n      },\n      onClick(event) {\n        didPointerDownRef.current = false;\n\n        // Prevent double commit on {Enter}\n        if (event.type === 'keydown' && lastKeyRef.current === null) {\n          return;\n        }\n\n        if (\n          disabled ||\n          (event.type === 'keydown' && lastKeyRef.current === ' ' && typingRef.current) ||\n          (pointerTypeRef.current !== 'touch' && !highlighted)\n        ) {\n          return;\n        }\n\n        lastKeyRef.current = null;\n        commitSelection(event.nativeEvent);\n      },\n      onPointerEnter(event) {\n        pointerTypeRef.current = event.pointerType;\n      },\n      onPointerDown(event) {\n        pointerTypeRef.current = event.pointerType;\n        didPointerDownRef.current = true;\n      },\n      onMouseUp() {\n        if (disabled) {\n          return;\n        }\n\n        // Regular click (pointerdown on this element) if didPointerDownRef is set, otherwise drag-to-select\n        if (didPointerDownRef.current) {\n          didPointerDownRef.current = false;\n          return;\n        }\n\n        const disallowSelectedMouseUp = !selectionRef.current.allowSelectedMouseUp && selected;\n        const disallowUnselectedMouseUp = !selectionRef.current.allowUnselectedMouseUp && !selected;\n\n        if (\n          disallowSelectedMouseUp ||\n          disallowUnselectedMouseUp ||\n          (pointerTypeRef.current !== 'touch' && !highlighted)\n        ) {\n          return;\n        }\n\n        itemRef.current?.click();\n      },\n    };\n\n    const element = useRenderElement('div', componentProps, {\n      ref: [buttonRef, forwardedRef, listItem.ref, itemRef],\n      state,\n      props: [rootProps, defaultProps, elementProps, getButtonProps],\n    });\n\n    const contextValue: SelectItemContext = React.useMemo(\n      () => ({\n        selected,\n        indexRef,\n        textRef,\n        selectedByFocus,\n        hasRegistered,\n      }),\n      [selected, indexRef, textRef, selectedByFocus, hasRegistered],\n    );\n\n    return <SelectItemContext.Provider value={contextValue}>{element}</SelectItemContext.Provider>;\n  }),\n);\n\nexport interface SelectItemState {\n  /**\n   * Whether the item should ignore user interaction.\n   */\n  disabled: boolean;\n  /**\n   * Whether the item is selected.\n   */\n  selected: boolean;\n  /**\n   * Whether the item is highlighted.\n   */\n  highlighted: boolean;\n}\n\nexport interface SelectItemProps\n  extends NonNativeButtonProps, Omit<BaseUIComponentProps<'div', SelectItemState>, 'id'> {\n  children?: React.ReactNode;\n  /**\n   * A unique value that identifies this select item.\n   * @default null\n   */\n  value?: any;\n  /**\n   * Whether the component should ignore user interaction.\n   * @default false\n   */\n  disabled?: boolean | undefined;\n  /**\n   * Specifies the text label to use when the item is matched during keyboard text navigation.\n   *\n   * Defaults to the item text content if not provided.\n   */\n  label?: string | undefined;\n}\n\nexport namespace SelectItem {\n  export type State = SelectItemState;\n  export type Props = SelectItemProps;\n}\n"
  },
  {
    "path": "packages/react/src/select/item/SelectItemContext.ts",
    "content": "'use client';\nimport * as React from 'react';\n\nexport interface SelectItemContext {\n  selected: boolean;\n  indexRef: React.RefObject<number>;\n  textRef: React.RefObject<HTMLElement | null>;\n  selectedByFocus: boolean;\n  hasRegistered: boolean;\n}\n\nexport const SelectItemContext = React.createContext<SelectItemContext | undefined>(undefined);\n\nexport function useSelectItemContext() {\n  const context = React.useContext(SelectItemContext);\n  if (!context) {\n    throw new Error(\n      'Base UI: SelectItemContext is missing. SelectItem parts must be placed within <Select.Item>.',\n    );\n  }\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/select/item/SelectItemDataAttributes.ts",
    "content": "export enum SelectItemDataAttributes {\n  /**\n   * Present when the select item is selected.\n   */\n  selected = 'data-selected',\n  /**\n   * Present when the select item is highlighted.\n   */\n  highlighted = 'data-highlighted',\n  /**\n   * Present when the select item is disabled.\n   */\n  disabled = 'data-disabled',\n}\n"
  },
  {
    "path": "packages/react/src/select/item-indicator/SelectItemIndicator.test.tsx",
    "content": "import { Select } from '@base-ui/react/select';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Select.ItemIndicator />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Select.ItemIndicator />, () => ({\n    refInstanceof: window.HTMLSpanElement,\n    render(node) {\n      return render(\n        <Select.Root open>\n          <Select.Trigger>\n            <Select.Value />\n          </Select.Trigger>\n          <Select.Positioner>\n            <Select.Item>{node}</Select.Item>\n          </Select.Positioner>\n        </Select.Root>,\n      );\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/select/item-indicator/SelectItemIndicator.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useSelectItemContext } from '../item/SelectItemContext';\nimport { type TransitionStatus, useTransitionStatus } from '../../utils/useTransitionStatus';\nimport { useOpenChangeComplete } from '../../utils/useOpenChangeComplete';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { transitionStatusMapping } from '../../utils/stateAttributesMapping';\n\n/**\n * Indicates whether the select item is selected.\n * Renders a `<span>` element.\n *\n * Documentation: [Base UI Select](https://base-ui.com/react/components/select)\n */\nexport const SelectItemIndicator = React.forwardRef(function SelectItemIndicator(\n  componentProps: SelectItemIndicator.Props,\n  forwardedRef: React.ForwardedRef<HTMLSpanElement>,\n) {\n  const keepMounted = componentProps.keepMounted ?? false;\n\n  const { selected } = useSelectItemContext();\n\n  const shouldRender = keepMounted || selected;\n  if (!shouldRender) {\n    return null;\n  }\n\n  // eslint-disable-next-line @typescript-eslint/no-use-before-define\n  return <Inner {...componentProps} ref={forwardedRef} />;\n});\n\n/** The core implementation of the indicator is split here to avoid paying the hooks\n * costs unless the element needs to be mounted. */\nconst Inner = React.memo(\n  React.forwardRef(\n    (\n      componentProps: SelectItemIndicator.Props,\n      forwardedRef: React.ForwardedRef<HTMLSpanElement>,\n    ) => {\n      const { render, className, keepMounted, ...elementProps } = componentProps;\n\n      const { selected } = useSelectItemContext();\n\n      const indicatorRef = React.useRef<HTMLSpanElement | null>(null);\n\n      const { transitionStatus, setMounted } = useTransitionStatus(selected);\n\n      const state: SelectItemIndicatorState = {\n        selected,\n        transitionStatus,\n      };\n\n      const element = useRenderElement('span', componentProps, {\n        ref: [forwardedRef, indicatorRef],\n        state,\n        props: [\n          {\n            'aria-hidden': true,\n            children: '✔️',\n          },\n          elementProps,\n        ],\n        stateAttributesMapping: transitionStatusMapping,\n      });\n\n      useOpenChangeComplete({\n        open: selected,\n        ref: indicatorRef,\n        onComplete() {\n          if (!selected) {\n            setMounted(false);\n          }\n        },\n      });\n\n      return element;\n    },\n  ),\n);\n\nexport interface SelectItemIndicatorState {\n  /**\n   * Whether the item is selected.\n   */\n  selected: boolean;\n  /**\n   * The transition status of the component.\n   */\n  transitionStatus: TransitionStatus;\n}\n\nexport interface SelectItemIndicatorProps extends BaseUIComponentProps<\n  'span',\n  SelectItemIndicatorState\n> {\n  children?: React.ReactNode;\n  /**\n   * Whether to keep the HTML element in the DOM when the item is not selected.\n   */\n  keepMounted?: boolean | undefined;\n}\n\nexport namespace SelectItemIndicator {\n  export type State = SelectItemIndicatorState;\n  export type Props = SelectItemIndicatorProps;\n}\n"
  },
  {
    "path": "packages/react/src/select/item-indicator/SelectItemIndicatorDataAttributes.ts",
    "content": "import { TransitionStatusDataAttributes } from '../../utils/stateAttributesMapping';\n\nexport enum SelectItemIndicatorDataAttributes {\n  /**\n   * Present when the indicator is animating in.\n   */\n  startingStyle = TransitionStatusDataAttributes.startingStyle,\n  /**\n   * Present when the indicator is animating out.\n   */\n  endingStyle = TransitionStatusDataAttributes.endingStyle,\n}\n"
  },
  {
    "path": "packages/react/src/select/item-text/SelectItemText.test.tsx",
    "content": "import { Select } from '@base-ui/react/select';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Select.ItemText />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Select.ItemText />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <Select.Root open>\n          <Select.Positioner>\n            <Select.Item value=\"\">{node}</Select.Item>\n          </Select.Positioner>\n        </Select.Root>,\n      );\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/select/item-text/SelectItemText.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useSelectRootContext } from '../root/SelectRootContext';\nimport { useSelectItemContext } from '../item/SelectItemContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\n\n/**\n * A text label of the select item.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Select](https://base-ui.com/react/components/select)\n */\nexport const SelectItemText = React.memo(\n  React.forwardRef(function SelectItemText(\n    componentProps: SelectItemText.Props,\n    forwardedRef: React.ForwardedRef<HTMLDivElement>,\n  ) {\n    const { indexRef, textRef, selectedByFocus, hasRegistered } = useSelectItemContext();\n    const { selectedItemTextRef } = useSelectRootContext();\n\n    const { className, render, ...elementProps } = componentProps;\n\n    const localRef = React.useCallback(\n      (node: HTMLElement | null) => {\n        if (!node || !hasRegistered) {\n          return;\n        }\n        const hasNoSelectedItemText =\n          selectedItemTextRef.current === null || !selectedItemTextRef.current.isConnected;\n        if (selectedByFocus || (hasNoSelectedItemText && indexRef.current === 0)) {\n          selectedItemTextRef.current = node;\n        }\n      },\n      [selectedItemTextRef, indexRef, selectedByFocus, hasRegistered],\n    );\n\n    const element = useRenderElement('div', componentProps, {\n      ref: [localRef, forwardedRef, textRef],\n      props: elementProps,\n    });\n\n    return element;\n  }),\n);\n\nexport interface SelectItemTextState {}\n\nexport interface SelectItemTextProps extends BaseUIComponentProps<'div', SelectItemTextState> {}\n\nexport namespace SelectItemText {\n  export type State = SelectItemTextState;\n  export type Props = SelectItemTextProps;\n}\n"
  },
  {
    "path": "packages/react/src/select/label/SelectLabel.test.tsx",
    "content": "import { Select } from '@base-ui/react/select';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Select.Label />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Select.Label />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <Select.Root>\n          {node}\n          <Select.Trigger />\n          <Select.Portal>\n            <Select.Positioner />\n          </Select.Portal>\n        </Select.Root>,\n      );\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/select/label/SelectLabel.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStore } from '@base-ui/utils/store';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport type { FieldRoot } from '../../field/root/FieldRoot';\nimport { useFieldRootContext } from '../../field/root/FieldRootContext';\nimport { fieldValidityMapping } from '../../field/utils/constants';\nimport { useLabel } from '../../labelable-provider/useLabel';\nimport { getDefaultLabelId } from '../../utils/resolveAriaLabelledBy';\nimport { useSelectRootContext } from '../root/SelectRootContext';\nimport { selectors } from '../store';\n\n/**\n * An accessible label that is automatically associated with the select trigger.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Select](https://base-ui.com/react/components/select)\n */\nexport const SelectLabel = React.forwardRef(function SelectLabel(\n  componentProps: SelectLabel.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, ...elementProps } = componentProps;\n  // Keep label id derived from the root and ignore runtime `id` overrides from untyped consumers.\n  const elementPropsWithoutId = elementProps as typeof elementProps & { id?: string | undefined };\n  delete elementPropsWithoutId.id;\n\n  const fieldRootContext = useFieldRootContext();\n  const { store } = useSelectRootContext();\n\n  const triggerElement = useStore(store, selectors.triggerElement);\n  const rootId = useStore(store, selectors.id);\n  const defaultLabelId = getDefaultLabelId(rootId);\n\n  const labelProps = useLabel({\n    id: defaultLabelId,\n    fallbackControlId: triggerElement?.id ?? rootId,\n    setLabelId(nextLabelId) {\n      store.set('labelId', nextLabelId);\n    },\n  });\n\n  return useRenderElement('div', componentProps, {\n    ref: forwardedRef,\n    state: fieldRootContext.state,\n    props: [labelProps, elementProps],\n    stateAttributesMapping: fieldValidityMapping,\n  });\n});\n\nexport type SelectLabelState = FieldRoot.State;\n\nexport interface SelectLabelProps extends Omit<\n  BaseUIComponentProps<'div', SelectLabel.State>,\n  'id'\n> {}\n\nexport namespace SelectLabel {\n  export type State = SelectLabelState;\n  export type Props = SelectLabelProps;\n}\n"
  },
  {
    "path": "packages/react/src/select/list/SelectList.test.tsx",
    "content": "import { Select } from '@base-ui/react/select';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Select.List />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Select.List />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <Select.Root open>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>{node}</Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/select/list/SelectList.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useStore } from '@base-ui/utils/store';\nimport type { BaseUIComponentProps, HTMLProps } from '../../utils/types';\nimport { useSelectRootContext } from '../root/SelectRootContext';\nimport { useSelectPositionerContext } from '../positioner/SelectPositionerContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { styleDisableScrollbar } from '../../utils/styles';\nimport { LIST_FUNCTIONAL_STYLES } from '../popup/utils';\nimport { selectors } from '../store';\n\n/**\n * A container for the select items.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Select](https://base-ui.com/react/components/select)\n */\nexport const SelectList = React.forwardRef(function SelectList(\n  componentProps: SelectList.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { className, render, ...elementProps } = componentProps;\n\n  const { store, scrollHandlerRef } = useSelectRootContext();\n  const { alignItemWithTriggerActive } = useSelectPositionerContext();\n\n  const hasScrollArrows = useStore(store, selectors.hasScrollArrows);\n  const openMethod = useStore(store, selectors.openMethod);\n  const multiple = useStore(store, selectors.multiple);\n  const id = useStore(store, selectors.id);\n\n  const defaultProps: HTMLProps = {\n    id: `${id}-list`,\n    role: 'listbox',\n    'aria-multiselectable': multiple || undefined,\n    onScroll(event) {\n      scrollHandlerRef.current?.(event.currentTarget);\n    },\n    ...(alignItemWithTriggerActive && {\n      style: LIST_FUNCTIONAL_STYLES,\n    }),\n    className:\n      hasScrollArrows && openMethod !== 'touch' ? styleDisableScrollbar.className : undefined,\n  };\n\n  const setListElement = useStableCallback((element: HTMLElement | null) => {\n    store.set('listElement', element);\n  });\n\n  return useRenderElement('div', componentProps, {\n    ref: [forwardedRef, setListElement],\n    props: [defaultProps, elementProps],\n  });\n});\n\nexport interface SelectListProps extends BaseUIComponentProps<'div', SelectListState> {}\n\nexport interface SelectListState {}\n\nexport namespace SelectList {\n  export type Props = SelectListProps;\n  export type State = SelectListState;\n}\n"
  },
  {
    "path": "packages/react/src/select/popup/SelectPopup.test.tsx",
    "content": "import { expect } from 'vitest';\nimport * as React from 'react';\nimport { Select } from '@base-ui/react/select';\nimport { act, screen, waitFor } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\n\ndescribe('<Select.Popup />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Select.Popup />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <Select.Root open>\n          <Select.Portal>\n            <Select.Positioner>{node}</Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n    },\n  }));\n\n  it('has aria attributes when no Select.List is present', async () => {\n    const { user } = await render(\n      <Select.Root multiple>\n        <Select.Trigger>Trigger</Select.Trigger>\n        <Select.Portal>\n          <Select.Positioner>\n            <Select.Popup data-testid=\"popup\">Popup</Select.Popup>\n          </Select.Positioner>\n        </Select.Portal>\n      </Select.Root>,\n    );\n\n    const trigger = screen.getByRole('combobox');\n\n    expect(trigger).not.toHaveAttribute('aria-controls');\n    expect(trigger).toHaveAttribute('aria-expanded', 'false');\n\n    await user.click(trigger);\n\n    const popup = await screen.findByTestId('popup');\n    const listbox = await screen.findByRole('listbox');\n\n    expect(popup).toBe(listbox);\n    expect(popup.id).not.toBe('');\n    expect(popup).toHaveAttribute('aria-multiselectable', 'true');\n    expect(trigger).toHaveAttribute('aria-controls', popup.id);\n    expect(trigger).toHaveAttribute('aria-expanded', 'true');\n    expect(trigger).toHaveAttribute('aria-haspopup', 'listbox');\n  });\n\n  it('places aria attributes on Select.List instead if it is present', async () => {\n    const { user } = await render(\n      <Select.Root multiple>\n        <Select.Trigger>Trigger</Select.Trigger>\n        <Select.Portal>\n          <Select.Positioner>\n            <Select.Popup data-testid=\"popup\">\n              <Select.List data-testid=\"list\">\n                <Select.Item value=\"1\">Item 1</Select.Item>\n                <Select.Item value=\"2\">Item 2</Select.Item>\n              </Select.List>\n            </Select.Popup>\n          </Select.Positioner>\n        </Select.Portal>\n      </Select.Root>,\n    );\n\n    const trigger = screen.getByRole('combobox');\n\n    expect(trigger).not.toHaveAttribute('aria-controls');\n    expect(trigger).toHaveAttribute('aria-expanded', 'false');\n    expect(trigger).toHaveAttribute('aria-haspopup', 'listbox');\n\n    await user.click(trigger);\n\n    const popup = await screen.findByTestId('popup');\n    const list = await screen.findByTestId('list');\n    const listbox = await screen.findByRole('listbox');\n\n    expect(list).toBe(listbox);\n    expect(list).toHaveAttribute('aria-multiselectable');\n    expect(popup).toHaveAttribute('role', 'presentation');\n    expect(popup).not.toHaveAttribute('aria-multiselectable');\n    expect(list.id).not.toBe('');\n    expect(trigger).toHaveAttribute('aria-controls', list.id);\n    expect(trigger).not.toHaveAttribute('aria-controls', popup.id);\n    expect(trigger).toHaveAttribute('aria-expanded', 'true');\n    expect(trigger).toHaveAttribute('aria-haspopup', 'listbox');\n  });\n\n  it('restores transform-related inline styles after measurement', async () => {\n    let popupElement: HTMLElement | null = null;\n\n    await render(\n      <Select.Root open>\n        <Select.Trigger>Trigger</Select.Trigger>\n        <Select.Portal>\n          <Select.Positioner>\n            <Select.Popup\n              ref={(node) => {\n                if (node) {\n                  node.style.setProperty('transform', 'translateX(10px)');\n                  node.style.setProperty('scale', '0.8');\n                  node.style.setProperty('translate', '1px 2px');\n                }\n                popupElement = node;\n              }}\n            >\n              <Select.Item value=\"1\">\n                <Select.ItemText>Item 1</Select.ItemText>\n              </Select.Item>\n            </Select.Popup>\n          </Select.Positioner>\n        </Select.Portal>\n      </Select.Root>,\n    );\n\n    await new Promise<void>(queueMicrotask);\n\n    expect(popupElement).not.toBe(null);\n    expect(popupElement!.style.getPropertyValue('transform')).toBe('translateX(10px)');\n    expect(popupElement!.style.getPropertyValue('scale')).toBe('0.8');\n    expect(popupElement!.style.getPropertyValue('translate')).toBe('1px 2px');\n  });\n\n  it.skipIf(isJSDOM)('keeps alignItemWithTrigger active at browser zoom', async () => {\n    const docEl = document.documentElement;\n    const previousZoom = docEl.style.zoom;\n\n    try {\n      docEl.style.zoom = '0.9';\n\n      await render(\n        <div style={{ paddingTop: 100, paddingLeft: 10 }}>\n          <Select.Root open defaultValue=\"css-modules\">\n            <Select.Trigger data-testid=\"trigger\" style={{ width: 108, height: 30 }}>\n              <Select.Value />\n            </Select.Trigger>\n            <Select.Portal>\n              <Select.Positioner data-testid=\"positioner\">\n                <Select.Popup data-testid=\"popup\" style={{ maxHeight: 'none' }}>\n                  <Select.Item value=\"css-modules\">\n                    <Select.ItemText>CSS Modules</Select.ItemText>\n                  </Select.Item>\n                  <Select.Item value=\"tailwind-v4\">\n                    <Select.ItemText>Tailwind v4</Select.ItemText>\n                  </Select.Item>\n                </Select.Popup>\n              </Select.Positioner>\n            </Select.Portal>\n          </Select.Root>\n        </div>,\n      );\n\n      await waitFor(() => {\n        expect(screen.getByTestId('positioner')).toHaveAttribute('data-side', 'none');\n      });\n    } finally {\n      docEl.style.zoom = previousZoom;\n    }\n  });\n\n  it.skipIf(isJSDOM)(\n    'treats short popups as top-positioned when maxScrollTop is off by 1px',\n    async () => {\n      const docEl = document.documentElement;\n      const clientHeightDescriptor = Object.getOwnPropertyDescriptor(docEl, 'clientHeight');\n      const clientWidthDescriptor = Object.getOwnPropertyDescriptor(docEl, 'clientWidth');\n\n      const restoreDescriptor = (\n        target: object,\n        property: 'clientHeight' | 'clientWidth',\n        descriptor: PropertyDescriptor | undefined,\n      ) => {\n        if (descriptor) {\n          Object.defineProperty(target, property, descriptor);\n        } else {\n          delete (target as Record<string, unknown>)[property];\n        }\n      };\n\n      const createRect = (left: number, top: number, width: number, height: number) => ({\n        x: left,\n        y: top,\n        left,\n        top,\n        width,\n        height,\n        right: left + width,\n        bottom: top + height,\n        toJSON: () => ({}),\n      });\n\n      try {\n        Object.defineProperty(docEl, 'clientHeight', { value: 646, configurable: true });\n        Object.defineProperty(docEl, 'clientWidth', { value: 300, configurable: true });\n\n        await render(\n          <Select.Root open defaultValue=\"1\">\n            <Select.Trigger\n              data-testid=\"trigger\"\n              ref={(node) => {\n                if (!node) {\n                  return;\n                }\n\n                Object.defineProperty(node, 'offsetWidth', { value: 80, configurable: true });\n                Object.defineProperty(node, 'offsetHeight', { value: 30, configurable: true });\n                Object.defineProperty(node, 'getBoundingClientRect', {\n                  value: () => createRect(10, 170.3793487548828, 80, 30),\n                  configurable: true,\n                });\n              }}\n            >\n              Trigger\n            </Select.Trigger>\n            <Select.Portal>\n              <Select.Positioner\n                data-testid=\"positioner\"\n                ref={(node) => {\n                  if (!node) {\n                    return;\n                  }\n\n                  Object.defineProperty(node, 'getBoundingClientRect', {\n                    value: () => createRect(10, 0, 108, 63.96739196777344),\n                    configurable: true,\n                  });\n                }}\n              >\n                <Select.Popup\n                  data-testid=\"popup\"\n                  ref={(node) => {\n                    if (!node) {\n                      return;\n                    }\n\n                    Object.defineProperty(node, 'scrollHeight', {\n                      value: 64,\n                      configurable: true,\n                    });\n                    Object.defineProperty(node, 'clientHeight', {\n                      value: 63,\n                      configurable: true,\n                    });\n                  }}\n                >\n                  <Select.Item value=\"1\">Item 1</Select.Item>\n                  <Select.Item value=\"2\">Item 2</Select.Item>\n                </Select.Popup>\n              </Select.Positioner>\n            </Select.Portal>\n          </Select.Root>,\n        );\n\n        const positioner = screen.getByTestId('positioner');\n\n        await waitFor(() => {\n          expect(positioner).toHaveAttribute('data-side', 'none');\n          expect(positioner.style.top).not.toBe('');\n          expect(positioner.style.bottom).toBe('');\n        });\n      } finally {\n        restoreDescriptor(docEl, 'clientHeight', clientHeightDescriptor);\n        restoreDescriptor(docEl, 'clientWidth', clientWidthDescriptor);\n      }\n    },\n  );\n\n  it.skipIf(isJSDOM)(\n    'keeps alignItemWithTrigger active when the aligned height is fractionally below minHeight',\n    async () => {\n      const docEl = document.documentElement;\n      const clientHeightDescriptor = Object.getOwnPropertyDescriptor(docEl, 'clientHeight');\n      const clientWidthDescriptor = Object.getOwnPropertyDescriptor(docEl, 'clientWidth');\n\n      const restoreDescriptor = (\n        target: object,\n        property: 'clientHeight' | 'clientWidth',\n        descriptor: PropertyDescriptor | undefined,\n      ) => {\n        if (descriptor) {\n          Object.defineProperty(target, property, descriptor);\n        } else {\n          delete (target as Record<string, unknown>)[property];\n        }\n      };\n\n      const createRect = (left: number, top: number, width: number, height: number) => ({\n        x: left,\n        y: top,\n        left,\n        top,\n        width,\n        height,\n        right: left + width,\n        bottom: top + height,\n        toJSON: () => ({}),\n      });\n\n      try {\n        Object.defineProperty(docEl, 'clientHeight', { value: 646, configurable: true });\n        Object.defineProperty(docEl, 'clientWidth', { value: 300, configurable: true });\n\n        await render(\n          <Select.Root open defaultValue=\"1\">\n            <Select.Trigger\n              data-testid=\"trigger\"\n              ref={(node) => {\n                if (!node) {\n                  return;\n                }\n\n                Object.defineProperty(node, 'offsetWidth', { value: 80, configurable: true });\n                Object.defineProperty(node, 'offsetHeight', { value: 30, configurable: true });\n                Object.defineProperty(node, 'getBoundingClientRect', {\n                  value: () => createRect(10, 170.3793487548828, 80, 30),\n                  configurable: true,\n                });\n              }}\n            >\n              Trigger\n            </Select.Trigger>\n            <Select.Portal>\n              <Select.Positioner\n                data-testid=\"positioner\"\n                ref={(node) => {\n                  if (!node) {\n                    return;\n                  }\n\n                  Object.defineProperty(node, 'getBoundingClientRect', {\n                    value: () => createRect(10, 0, 108, 63.96739196777344),\n                    configurable: true,\n                  });\n                }}\n              >\n                <Select.Popup\n                  data-testid=\"popup\"\n                  style={{ minHeight: 65 }}\n                  ref={(node) => {\n                    if (!node) {\n                      return;\n                    }\n\n                    Object.defineProperty(node, 'scrollHeight', {\n                      value: 64,\n                      configurable: true,\n                    });\n                    Object.defineProperty(node, 'clientHeight', {\n                      value: 63,\n                      configurable: true,\n                    });\n                  }}\n                >\n                  <Select.Item value=\"1\">Item 1</Select.Item>\n                  <Select.Item value=\"2\">Item 2</Select.Item>\n                </Select.Popup>\n              </Select.Positioner>\n            </Select.Portal>\n          </Select.Root>,\n        );\n\n        await waitFor(() => {\n          expect(screen.getByTestId('positioner')).toHaveAttribute('data-side', 'none');\n        });\n      } finally {\n        restoreDescriptor(docEl, 'clientHeight', clientHeightDescriptor);\n        restoreDescriptor(docEl, 'clientWidth', clientWidthDescriptor);\n      }\n    },\n  );\n\n  describe('prop: finalFocus', () => {\n    it('should focus the trigger by default when closed', async () => {\n      await render(\n        <div>\n          <input />\n          <Select.Root>\n            <Select.Trigger data-testid=\"trigger\">Open</Select.Trigger>\n            <Select.Portal>\n              <Select.Positioner>\n                <Select.Popup>\n                  <Select.Item value=\"1\">Item 1</Select.Item>\n                </Select.Popup>\n              </Select.Positioner>\n            </Select.Portal>\n          </Select.Root>\n          <input />\n        </div>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      await act(async () => {\n        trigger.click();\n      });\n\n      const item = screen.getByText('Item 1');\n      await act(async () => {\n        item.click();\n      });\n\n      await waitFor(() => {\n        expect(trigger).toHaveFocus();\n      });\n    });\n\n    it('should focus the element provided to the prop when closed', async () => {\n      function TestComponent() {\n        const inputRef = React.useRef<HTMLInputElement | null>(null);\n        return (\n          <div>\n            <input />\n            <Select.Root>\n              <Select.Trigger data-testid=\"trigger\">Open</Select.Trigger>\n              <Select.Portal>\n                <Select.Positioner>\n                  <Select.Popup finalFocus={inputRef}>\n                    <Select.Item value=\"1\">Item 1</Select.Item>\n                  </Select.Popup>\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n            <input />\n            <input data-testid=\"input-to-focus\" ref={inputRef} />\n            <input />\n          </div>\n        );\n      }\n\n      await render(<TestComponent />);\n\n      const trigger = screen.getByTestId('trigger');\n      await act(async () => {\n        trigger.click();\n      });\n\n      const item = screen.getByText('Item 1');\n      await act(async () => {\n        item.click();\n      });\n\n      const inputToFocus = screen.getByTestId('input-to-focus');\n\n      await waitFor(() => {\n        expect(inputToFocus).toHaveFocus();\n      });\n    });\n\n    it('should focus the element provided to `finalFocus` as a function when closed', async () => {\n      function TestComponent() {\n        const ref = React.useRef<HTMLInputElement>(null);\n        const getRef = React.useCallback(() => ref.current, []);\n        return (\n          <div>\n            <Select.Root>\n              <Select.Trigger data-testid=\"trigger\">Open</Select.Trigger>\n              <Select.Portal>\n                <Select.Positioner>\n                  <Select.Popup finalFocus={getRef}>\n                    <Select.Item value=\"1\">Item 1</Select.Item>\n                  </Select.Popup>\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n            <input data-testid=\"input-to-focus\" ref={ref} />\n          </div>\n        );\n      }\n\n      await render(<TestComponent />);\n\n      const trigger = screen.getByTestId('trigger');\n      await act(async () => {\n        trigger.click();\n      });\n\n      const item = screen.getByText('Item 1');\n      await act(async () => {\n        item.click();\n      });\n\n      await waitFor(() => {\n        expect(screen.getByTestId('input-to-focus')).toHaveFocus();\n      });\n    });\n\n    it('should not move focus when finalFocus is false', async () => {\n      function TestComponent() {\n        return (\n          <div>\n            <Select.Root>\n              <Select.Trigger data-testid=\"trigger\">Open</Select.Trigger>\n              <Select.Portal>\n                <Select.Positioner>\n                  <Select.Popup finalFocus={false}>\n                    <Select.Item value=\"1\">Item 1</Select.Item>\n                  </Select.Popup>\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n          </div>\n        );\n      }\n\n      await render(<TestComponent />);\n      const trigger = screen.getByTestId('trigger');\n\n      await act(async () => {\n        trigger.click();\n      });\n\n      const item = screen.getByText('Item 1');\n      await act(async () => {\n        item.click();\n      });\n\n      await waitFor(() => {\n        expect(trigger).not.toHaveFocus();\n      });\n    });\n\n    it('should move focus to trigger when finalFocus returns true', async () => {\n      function TestComponent() {\n        return (\n          <div>\n            <Select.Root>\n              <Select.Trigger data-testid=\"trigger\">Open</Select.Trigger>\n              <Select.Portal>\n                <Select.Positioner>\n                  <Select.Popup finalFocus={() => true}>\n                    <Select.Item value=\"1\">Item 1</Select.Item>\n                  </Select.Popup>\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n          </div>\n        );\n      }\n\n      await render(<TestComponent />);\n      const trigger = screen.getByTestId('trigger');\n\n      await act(async () => {\n        trigger.click();\n      });\n\n      const item = screen.getByText('Item 1');\n      await act(async () => {\n        item.click();\n      });\n\n      await waitFor(() => {\n        expect(trigger).toHaveFocus();\n      });\n    });\n\n    it('uses default behavior when finalFocus returns null', async () => {\n      function TestComponent() {\n        return (\n          <div>\n            <Select.Root>\n              <Select.Trigger data-testid=\"trigger\">Open</Select.Trigger>\n              <Select.Portal>\n                <Select.Positioner>\n                  <Select.Popup finalFocus={() => null}>\n                    <Select.Item value=\"1\">Item 1</Select.Item>\n                  </Select.Popup>\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n          </div>\n        );\n      }\n\n      await render(<TestComponent />);\n      const trigger = screen.getByTestId('trigger');\n\n      await act(async () => {\n        trigger.click();\n      });\n\n      const item = screen.getByText('Item 1');\n      await act(async () => {\n        item.click();\n      });\n\n      await waitFor(() => {\n        expect(trigger).toHaveFocus();\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/select/popup/SelectPopup.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport { rectToClientRect } from '@floating-ui/utils';\nimport { useTimeout } from '@base-ui/utils/useTimeout';\nimport { isWebKit } from '@base-ui/utils/detectBrowser';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { ownerDocument, ownerWindow } from '@base-ui/utils/owner';\nimport { isMouseWithinBounds } from '@base-ui/utils/isMouseWithinBounds';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useStore } from '@base-ui/utils/store';\nimport { useAnimationFrame } from '@base-ui/utils/useAnimationFrame';\nimport type { InteractionType } from '@base-ui/utils/useEnhancedClickHandler';\nimport { FloatingFocusManager, platform as floatingPlatform } from '../../floating-ui-react';\nimport type { ClientRectObject } from '../../floating-ui-react';\nimport type { BaseUIComponentProps, HTMLProps } from '../../utils/types';\nimport { useSelectFloatingContext, useSelectRootContext } from '../root/SelectRootContext';\nimport { popupStateMapping } from '../../utils/popupStateMapping';\nimport type { Side, Align } from '../../utils/useAnchorPositioning';\nimport type { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport type { TransitionStatus } from '../../utils/useTransitionStatus';\nimport { useSelectPositionerContext } from '../positioner/SelectPositionerContext';\nimport { styleDisableScrollbar } from '../../utils/styles';\nimport { transitionStatusMapping } from '../../utils/stateAttributesMapping';\nimport { useOpenChangeComplete } from '../../utils/useOpenChangeComplete';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { selectors } from '../store';\nimport { clearStyles, LIST_FUNCTIONAL_STYLES } from './utils';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport { useToolbarRootContext } from '../../toolbar/root/ToolbarRootContext';\nimport { COMPOSITE_KEYS } from '../../composite/composite';\nimport { getDisabledMountTransitionStyles } from '../../utils/getDisabledMountTransitionStyles';\nimport { clamp } from '../../utils/clamp';\nimport { useCSPContext } from '../../csp-provider/CSPContext';\n\nconst SCROLL_EPS_PX = 1;\n\nconst stateAttributesMapping: StateAttributesMapping<SelectPopupState> = {\n  ...popupStateMapping,\n  ...transitionStatusMapping,\n};\n\n/**\n * A container for the select list.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Select](https://base-ui.com/react/components/select)\n */\nexport const SelectPopup = React.forwardRef(function SelectPopup(\n  componentProps: SelectPopup.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, finalFocus, ...elementProps } = componentProps;\n\n  const {\n    store,\n    popupRef,\n    onOpenChangeComplete,\n    setOpen,\n    valueRef,\n    selectedItemTextRef,\n    keyboardActiveRef,\n    multiple,\n    handleScrollArrowVisibility,\n    scrollHandlerRef,\n    highlightItemOnHover,\n  } = useSelectRootContext();\n  const {\n    side,\n    align,\n    alignItemWithTriggerActive,\n    setControlledAlignItemWithTrigger,\n    scrollDownArrowRef,\n    scrollUpArrowRef,\n  } = useSelectPositionerContext();\n  const insideToolbar = useToolbarRootContext(true) != null;\n  const floatingRootContext = useSelectFloatingContext();\n\n  const { nonce, disableStyleElements } = useCSPContext();\n\n  const highlightTimeout = useTimeout();\n\n  const id = useStore(store, selectors.id);\n  const open = useStore(store, selectors.open);\n  const mounted = useStore(store, selectors.mounted);\n  const popupProps = useStore(store, selectors.popupProps);\n  const transitionStatus = useStore(store, selectors.transitionStatus);\n  const triggerElement = useStore(store, selectors.triggerElement);\n  const positionerElement = useStore(store, selectors.positionerElement);\n  const listElement = useStore(store, selectors.listElement);\n\n  const initialHeightRef = React.useRef(0);\n  const reachedMaxHeightRef = React.useRef(false);\n  const maxHeightRef = React.useRef(0);\n  const initialPlacedRef = React.useRef(false);\n  const originalPositionerStylesRef = React.useRef<React.CSSProperties>({});\n\n  const scrollArrowFrame = useAnimationFrame();\n\n  const handleScroll = useStableCallback((scroller: HTMLDivElement) => {\n    if (!positionerElement || !popupRef.current || !initialPlacedRef.current) {\n      return;\n    }\n\n    if (reachedMaxHeightRef.current || !alignItemWithTriggerActive) {\n      handleScrollArrowVisibility();\n      return;\n    }\n\n    const isTopPositioned = positionerElement.style.top === '0px';\n    const isBottomPositioned = positionerElement.style.bottom === '0px';\n\n    const currentHeight = positionerElement.getBoundingClientRect().height;\n    const doc = ownerDocument(positionerElement);\n    const positionerStyles = getComputedStyle(positionerElement);\n    const marginTop = parseFloat(positionerStyles.marginTop);\n    const marginBottom = parseFloat(positionerStyles.marginBottom);\n    const maxPopupHeight = getMaxPopupHeight(getComputedStyle(popupRef.current));\n    const maxAvailableHeight = Math.min(\n      doc.documentElement.clientHeight - marginTop - marginBottom,\n      maxPopupHeight,\n    );\n\n    const scrollTop = scroller.scrollTop;\n    const maxScrollTop = getMaxScrollTop(scroller);\n\n    let nextPositionerHeight = 0;\n    let nextScrollTop: number | null = null;\n    let setReachedMax = false;\n    let scrollToMax = false;\n\n    const setHeight = (height: number) => {\n      positionerElement.style.height = `${height}px`;\n    };\n\n    const handleSmallDiff = (diff: number, targetScrollTop: number) => {\n      const heightDelta = clamp(diff, 0, maxAvailableHeight - currentHeight);\n      if (heightDelta > 0) {\n        // Consume the remaining scroll in height.\n        setHeight(currentHeight + heightDelta);\n      }\n      scroller.scrollTop = targetScrollTop;\n      if (maxAvailableHeight - (currentHeight + heightDelta) <= SCROLL_EPS_PX) {\n        reachedMaxHeightRef.current = true;\n      }\n      handleScrollArrowVisibility();\n    };\n\n    if (isTopPositioned) {\n      const diff = maxScrollTop - scrollTop;\n      const idealHeight = currentHeight + diff;\n      const nextHeight = Math.min(idealHeight, maxAvailableHeight);\n\n      nextPositionerHeight = nextHeight;\n\n      if (diff <= SCROLL_EPS_PX) {\n        handleSmallDiff(diff, maxScrollTop);\n        return;\n      }\n\n      if (maxAvailableHeight - nextHeight > SCROLL_EPS_PX) {\n        scrollToMax = true;\n      } else {\n        setReachedMax = true;\n      }\n    } else if (isBottomPositioned) {\n      const diff = scrollTop;\n      const idealHeight = currentHeight + diff;\n      const nextHeight = Math.min(idealHeight, maxAvailableHeight);\n      const overshoot = idealHeight - maxAvailableHeight;\n\n      nextPositionerHeight = nextHeight;\n\n      if (diff <= SCROLL_EPS_PX) {\n        handleSmallDiff(diff, 0);\n        return;\n      }\n\n      if (maxAvailableHeight - nextHeight > SCROLL_EPS_PX) {\n        nextScrollTop = 0;\n      } else {\n        setReachedMax = true;\n\n        if (scrollTop < maxScrollTop) {\n          nextScrollTop = scrollTop - (diff - overshoot);\n        }\n      }\n    }\n\n    nextPositionerHeight = Math.ceil(nextPositionerHeight);\n\n    if (nextPositionerHeight !== 0) {\n      setHeight(nextPositionerHeight);\n    }\n\n    if (scrollToMax || nextScrollTop != null) {\n      // Recompute bounds after resizing (clientHeight likely changed).\n      const nextMaxScrollTop = getMaxScrollTop(scroller);\n\n      const target = scrollToMax ? nextMaxScrollTop : clamp(nextScrollTop!, 0, nextMaxScrollTop);\n\n      // Avoid adjustments that re-trigger scroll events forever.\n      if (Math.abs(scroller.scrollTop - target) > SCROLL_EPS_PX) {\n        scroller.scrollTop = target;\n      }\n    }\n\n    if (setReachedMax || nextPositionerHeight >= maxAvailableHeight - SCROLL_EPS_PX) {\n      reachedMaxHeightRef.current = true;\n    }\n\n    handleScrollArrowVisibility();\n  });\n\n  React.useImperativeHandle(scrollHandlerRef, () => handleScroll, [handleScroll]);\n\n  useOpenChangeComplete({\n    open,\n    ref: popupRef,\n    onComplete() {\n      if (open) {\n        onOpenChangeComplete?.(true);\n      }\n    },\n  });\n\n  const state: SelectPopupState = {\n    open,\n    transitionStatus,\n    side,\n    align,\n  };\n\n  useIsoLayoutEffect(() => {\n    if (\n      !positionerElement ||\n      !popupRef.current ||\n      Object.keys(originalPositionerStylesRef.current).length\n    ) {\n      return;\n    }\n\n    originalPositionerStylesRef.current = {\n      top: positionerElement.style.top || '0',\n      left: positionerElement.style.left || '0',\n      right: positionerElement.style.right,\n      height: positionerElement.style.height,\n      bottom: positionerElement.style.bottom,\n      minHeight: positionerElement.style.minHeight,\n      maxHeight: positionerElement.style.maxHeight,\n      marginTop: positionerElement.style.marginTop,\n      marginBottom: positionerElement.style.marginBottom,\n    };\n  }, [popupRef, positionerElement]);\n\n  useIsoLayoutEffect(() => {\n    if (open || alignItemWithTriggerActive) {\n      return;\n    }\n\n    initialPlacedRef.current = false;\n    reachedMaxHeightRef.current = false;\n    initialHeightRef.current = 0;\n    maxHeightRef.current = 0;\n\n    clearStyles(positionerElement, originalPositionerStylesRef.current);\n  }, [open, alignItemWithTriggerActive, positionerElement, popupRef]);\n\n  useIsoLayoutEffect(() => {\n    const popupElement = popupRef.current;\n\n    if (\n      !open ||\n      !triggerElement ||\n      !positionerElement ||\n      !popupElement ||\n      store.state.transitionStatus === 'ending'\n    ) {\n      return;\n    }\n\n    if (!alignItemWithTriggerActive) {\n      initialPlacedRef.current = true;\n      scrollArrowFrame.request(handleScrollArrowVisibility);\n      popupElement.style.removeProperty('--transform-origin');\n      return;\n    }\n\n    // Wait for `selectedItemTextRef.current` to be set.\n    queueMicrotask(() => {\n      // Ensure we remove any transforms that can affect the location of the popup\n      // and therefore the calculations.\n      const restoreTransformStyles = unsetTransformStyles(popupElement);\n      popupElement.style.removeProperty('--transform-origin');\n\n      try {\n        const positionerStyles = getComputedStyle(positionerElement);\n        const popupStyles = getComputedStyle(popupElement);\n\n        const doc = ownerDocument(triggerElement);\n        const win = ownerWindow(positionerElement);\n        const scale = getScale(triggerElement);\n        const triggerRect = normalizeRect(triggerElement.getBoundingClientRect(), scale);\n        const positionerRect = normalizeRect(positionerElement.getBoundingClientRect(), scale);\n        const triggerX = triggerRect.left;\n        const triggerHeight = triggerRect.height;\n        const scroller = listElement || popupElement;\n        const scrollHeight = scroller.scrollHeight;\n\n        const borderBottom = parseFloat(popupStyles.borderBottomWidth);\n        const marginTop = parseFloat(positionerStyles.marginTop) || 10;\n        const marginBottom = parseFloat(positionerStyles.marginBottom) || 10;\n        const minHeight = parseFloat(positionerStyles.minHeight) || 100;\n        const maxPopupHeight = getMaxPopupHeight(popupStyles);\n\n        const paddingLeft = 5;\n        const paddingRight = 5;\n        const triggerCollisionThreshold = 20;\n\n        const viewportHeight = doc.documentElement.clientHeight - marginTop - marginBottom;\n        const viewportWidth = doc.documentElement.clientWidth;\n        const availableSpaceBeneathTrigger = viewportHeight - triggerRect.bottom + triggerHeight;\n\n        const textElement = selectedItemTextRef.current;\n        const valueElement = valueRef.current;\n\n        let textRect: ClientRectObject | undefined;\n        let offsetX = 0;\n        let offsetY = 0;\n\n        if (textElement && valueElement) {\n          const valueRect = normalizeRect(valueElement.getBoundingClientRect(), scale);\n          textRect = normalizeRect(textElement.getBoundingClientRect(), scale);\n\n          const valueLeftFromTriggerLeft = valueRect.left - triggerX;\n          const textLeftFromPositionerLeft = textRect.left - positionerRect.left;\n          const valueCenterFromPositionerTop =\n            valueRect.top - triggerRect.top + valueRect.height / 2;\n          const textCenterFromTriggerTop = textRect.top - positionerRect.top + textRect.height / 2;\n\n          offsetX = valueLeftFromTriggerLeft - textLeftFromPositionerLeft;\n          offsetY = textCenterFromTriggerTop - valueCenterFromPositionerTop;\n        }\n\n        const idealHeight = availableSpaceBeneathTrigger + offsetY + marginBottom + borderBottom;\n        let height = Math.min(viewportHeight, idealHeight);\n        const maxHeight = viewportHeight - marginTop - marginBottom;\n        const scrollTop = idealHeight - height;\n\n        const left = Math.max(paddingLeft, triggerX + offsetX);\n        const maxRight = viewportWidth - paddingRight;\n        const rightOverflow = Math.max(0, left + positionerRect.width - maxRight);\n\n        positionerElement.style.left = `${left - rightOverflow}px`;\n        positionerElement.style.height = `${height}px`;\n        positionerElement.style.maxHeight = 'auto';\n        positionerElement.style.marginTop = `${marginTop}px`;\n        positionerElement.style.marginBottom = `${marginBottom}px`;\n        popupElement.style.height = '100%';\n\n        const maxScrollTop = getMaxScrollTop(scroller);\n        const isTopPositioned = scrollTop >= maxScrollTop - SCROLL_EPS_PX;\n\n        if (isTopPositioned) {\n          height = Math.min(viewportHeight, positionerRect.height) - (scrollTop - maxScrollTop);\n        }\n\n        // When the trigger is too close to the top or bottom of the viewport, or the minHeight is\n        // reached, we fallback to aligning the popup to the trigger as the UX is poor otherwise.\n        const fallbackToAlignPopupToTrigger =\n          triggerRect.top < triggerCollisionThreshold ||\n          triggerRect.bottom > viewportHeight - triggerCollisionThreshold ||\n          Math.ceil(height) + SCROLL_EPS_PX < Math.min(scrollHeight, minHeight);\n\n        // Safari doesn't position the popup correctly when pinch-zoomed.\n        const isPinchZoomed = (win.visualViewport?.scale ?? 1) !== 1 && isWebKit;\n\n        if (fallbackToAlignPopupToTrigger || isPinchZoomed) {\n          initialPlacedRef.current = true;\n          clearStyles(positionerElement, originalPositionerStylesRef.current);\n          ReactDOM.flushSync(() => setControlledAlignItemWithTrigger(false));\n          return;\n        }\n\n        if (isTopPositioned) {\n          const topOffset = Math.max(0, viewportHeight - idealHeight);\n          positionerElement.style.top = positionerRect.height >= maxHeight ? '0' : `${topOffset}px`;\n          positionerElement.style.height = `${height}px`;\n          scroller.scrollTop = getMaxScrollTop(scroller);\n          initialHeightRef.current = Math.max(minHeight, height);\n        } else {\n          positionerElement.style.bottom = '0';\n          initialHeightRef.current = Math.max(minHeight, height);\n          scroller.scrollTop = scrollTop;\n        }\n\n        if (textRect) {\n          const popupTop = positionerRect.top;\n          const popupHeight = positionerRect.height;\n          const textCenterY = textRect.top + textRect.height / 2;\n\n          const transformOriginY =\n            popupHeight > 0 ? ((textCenterY - popupTop) / popupHeight) * 100 : 50;\n\n          const clampedY = clamp(transformOriginY, 0, 100);\n\n          popupElement.style.setProperty('--transform-origin', `50% ${clampedY}%`);\n        }\n\n        if (initialHeightRef.current === viewportHeight || height >= maxPopupHeight) {\n          reachedMaxHeightRef.current = true;\n        }\n\n        handleScrollArrowVisibility();\n\n        // Avoid the `onScroll` event logic from triggering before the popup is placed.\n        setTimeout(() => {\n          initialPlacedRef.current = true;\n        });\n      } finally {\n        restoreTransformStyles();\n      }\n    });\n  }, [\n    store,\n    open,\n    positionerElement,\n    triggerElement,\n    valueRef,\n    selectedItemTextRef,\n    popupRef,\n    handleScrollArrowVisibility,\n    alignItemWithTriggerActive,\n    setControlledAlignItemWithTrigger,\n    scrollArrowFrame,\n    scrollDownArrowRef,\n    scrollUpArrowRef,\n    listElement,\n  ]);\n\n  React.useEffect(() => {\n    if (!alignItemWithTriggerActive || !positionerElement || !open) {\n      return undefined;\n    }\n\n    const win = ownerWindow(positionerElement);\n\n    function handleResize(event: UIEvent) {\n      setOpen(false, createChangeEventDetails(REASONS.windowResize, event));\n    }\n\n    win.addEventListener('resize', handleResize);\n\n    return () => {\n      win.removeEventListener('resize', handleResize);\n    };\n  }, [setOpen, alignItemWithTriggerActive, positionerElement, open]);\n\n  const defaultProps: HTMLProps = {\n    ...(listElement\n      ? {\n          role: 'presentation',\n          'aria-orientation': undefined,\n        }\n      : {\n          role: 'listbox',\n          'aria-multiselectable': multiple || undefined,\n          id: `${id}-list`,\n        }),\n    onKeyDown(event) {\n      keyboardActiveRef.current = true;\n      if (insideToolbar && COMPOSITE_KEYS.has(event.key)) {\n        event.stopPropagation();\n      }\n    },\n    onMouseMove() {\n      keyboardActiveRef.current = false;\n    },\n    onPointerLeave(event) {\n      if (!highlightItemOnHover || isMouseWithinBounds(event) || event.pointerType === 'touch') {\n        return;\n      }\n\n      const popup = event.currentTarget;\n\n      highlightTimeout.start(0, () => {\n        store.set('activeIndex', null);\n        popup.focus({ preventScroll: true });\n      });\n    },\n    onScroll(event) {\n      if (listElement) {\n        return;\n      }\n      handleScroll(event.currentTarget);\n    },\n    ...(alignItemWithTriggerActive && {\n      style: listElement ? { height: '100%' } : LIST_FUNCTIONAL_STYLES,\n    }),\n  };\n\n  const element = useRenderElement('div', componentProps, {\n    ref: [forwardedRef, popupRef],\n    state,\n    stateAttributesMapping,\n    props: [\n      popupProps,\n      defaultProps,\n      getDisabledMountTransitionStyles(transitionStatus),\n      {\n        className:\n          !listElement && alignItemWithTriggerActive ? styleDisableScrollbar.className : undefined,\n      },\n      elementProps,\n    ],\n  });\n\n  return (\n    <React.Fragment>\n      {!disableStyleElements && styleDisableScrollbar.getElement(nonce)}\n      <FloatingFocusManager\n        context={floatingRootContext}\n        modal={false}\n        disabled={!mounted}\n        returnFocus={finalFocus}\n        restoreFocus\n      >\n        {element}\n      </FloatingFocusManager>\n    </React.Fragment>\n  );\n});\n\nexport interface SelectPopupProps extends BaseUIComponentProps<'div', SelectPopupState> {\n  children?: React.ReactNode;\n  /**\n   * Determines the element to focus when the select popup is closed.\n   *\n   * - `false`: Do not move focus.\n   * - `true`: Move focus based on the default behavior (trigger or previously focused element).\n   * - `RefObject`: Move focus to the ref element.\n   * - `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`).\n   *   Return an element to focus, `true` to use the default behavior, or `false`/`undefined` to do nothing.\n   */\n  finalFocus?:\n    | boolean\n    | React.RefObject<HTMLElement | null>\n    | ((closeType: InteractionType) => boolean | HTMLElement | null | void)\n    | undefined;\n}\n\nexport interface SelectPopupState {\n  /**\n   * The side of the anchor the component is placed on.\n   */\n  side: Side | 'none';\n  /**\n   * The alignment of the component relative to the anchor.\n   */\n  align: Align;\n  /**\n   * Whether the component is open.\n   */\n  open: boolean;\n  /**\n   * The transition status of the component.\n   */\n  transitionStatus: TransitionStatus;\n}\n\nexport namespace SelectPopup {\n  export type Props = SelectPopupProps;\n  export type State = SelectPopupState;\n}\n\nfunction getMaxPopupHeight(popupStyles: CSSStyleDeclaration) {\n  const maxHeightStyle = popupStyles.maxHeight || '';\n  return maxHeightStyle.endsWith('px') ? parseFloat(maxHeightStyle) || Infinity : Infinity;\n}\n\nfunction getMaxScrollTop(scroller: HTMLElement) {\n  return Math.max(0, scroller.scrollHeight - scroller.clientHeight);\n}\n\nfunction getScale(element: HTMLElement) {\n  // The platform API is async-capable, but the DOM platform returns a plain scale object.\n  return floatingPlatform.getScale(element) as { x: number; y: number };\n}\n\nfunction normalizeRect(\n  rect: DOMRect | DOMRectReadOnly,\n  scale: { x: number; y: number },\n): ClientRectObject {\n  return rectToClientRect({\n    x: rect.x / scale.x,\n    y: rect.y / scale.y,\n    width: rect.width / scale.x,\n    height: rect.height / scale.y,\n  });\n}\n\nconst TRANSFORM_STYLE_RESETS = [\n  ['transform', 'none'],\n  ['scale', '1'],\n  ['translate', '0 0'],\n] as const;\n\ntype TransformStyleProperty = (typeof TRANSFORM_STYLE_RESETS)[number][0];\n\nfunction unsetTransformStyles(popupElement: HTMLElement) {\n  const { style } = popupElement;\n  const originalStyles = {} as Record<TransformStyleProperty, string>;\n\n  for (const [property, value] of TRANSFORM_STYLE_RESETS) {\n    originalStyles[property] = style.getPropertyValue(property);\n    style.setProperty(property, value, 'important');\n  }\n\n  return () => {\n    for (const [property] of TRANSFORM_STYLE_RESETS) {\n      const originalValue = originalStyles[property];\n      if (originalValue) {\n        style.setProperty(property, originalValue);\n      } else {\n        style.removeProperty(property);\n      }\n    }\n  };\n}\n"
  },
  {
    "path": "packages/react/src/select/popup/SelectPopupDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum SelectPopupDataAttributes {\n  /**\n   * Present when the select is open.\n   */\n  open = CommonPopupDataAttributes.open,\n  /**\n   * Present when the select is closed.\n   */\n  closed = CommonPopupDataAttributes.closed,\n  /**\n   * Present when the select is animating in.\n   */\n  startingStyle = CommonPopupDataAttributes.startingStyle,\n  /**\n   * Present when the select is animating out.\n   */\n  endingStyle = CommonPopupDataAttributes.endingStyle,\n  /**\n   * Indicates which side the popup is positioned relative to the trigger.\n   * @type {'none' | 'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'}\n   */\n  side = CommonPopupDataAttributes.side,\n  /**\n   * Indicates how the popup is aligned relative to specified side.\n   * @type {'start' | 'center' | 'end'}\n   */\n  align = CommonPopupDataAttributes.align,\n}\n"
  },
  {
    "path": "packages/react/src/select/popup/utils.ts",
    "content": "export function clearStyles(element: HTMLElement | null, originalStyles: React.CSSProperties) {\n  if (element) {\n    Object.assign(element.style, originalStyles);\n  }\n}\n\nexport const LIST_FUNCTIONAL_STYLES = {\n  position: 'relative',\n  maxHeight: '100%',\n  overflowX: 'hidden',\n  overflowY: 'auto',\n} as const;\n"
  },
  {
    "path": "packages/react/src/select/portal/SelectPortal.test.tsx",
    "content": "import * as React from 'react';\nimport { Select } from '@base-ui/react/select';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Select.Portal />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Select.Portal />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(<Select.Root open>{node}</Select.Root>);\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/select/portal/SelectPortal.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStore } from '@base-ui/utils/store';\nimport { FloatingPortal } from '../../floating-ui-react';\nimport { SelectPortalContext } from './SelectPortalContext';\nimport { useSelectRootContext } from '../root/SelectRootContext';\nimport { selectors } from '../store';\n\n/**\n * A portal element that moves the popup to a different part of the DOM.\n * By default, the portal element is appended to `<body>`.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Select](https://base-ui.com/react/components/select)\n */\nexport const SelectPortal = React.forwardRef(function SelectPortal(\n  portalProps: SelectPortal.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { store } = useSelectRootContext();\n  const mounted = useStore(store, selectors.mounted);\n  const forceMount = useStore(store, selectors.forceMount);\n\n  const shouldRender = mounted || forceMount;\n  if (!shouldRender) {\n    return null;\n  }\n\n  return (\n    <SelectPortalContext.Provider value>\n      <FloatingPortal ref={forwardedRef} {...portalProps} />\n    </SelectPortalContext.Provider>\n  );\n});\n\nexport interface SelectPortalState {}\n\nexport interface SelectPortalProps extends FloatingPortal.Props<SelectPortalState> {}\n\nexport namespace SelectPortal {\n  export type State = SelectPortalState;\n  export type Props = SelectPortalProps;\n}\n"
  },
  {
    "path": "packages/react/src/select/portal/SelectPortalContext.ts",
    "content": "'use client';\nimport * as React from 'react';\n\nexport const SelectPortalContext = React.createContext<boolean | undefined>(undefined);\n\nexport function useSelectPortalContext() {\n  const value = React.useContext(SelectPortalContext);\n  if (value === undefined) {\n    throw new Error('Base UI: <Select.Portal> is missing.');\n  }\n  return value;\n}\n"
  },
  {
    "path": "packages/react/src/select/positioner/SelectPositioner.spec.tsx",
    "content": "import { Select } from '@base-ui/react';\n\n// @ts-expect-error - `keepMounted` should not be available\n<Select.Positioner keepMounted />;\n"
  },
  {
    "path": "packages/react/src/select/positioner/SelectPositioner.test.tsx",
    "content": "import { expect } from 'vitest';\nimport * as React from 'react';\nimport { Select } from '@base-ui/react/select';\nimport { screen } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\n\nconst Trigger = React.forwardRef(function Trigger(\n  props: Select.Trigger.Props,\n  ref: React.ForwardedRef<HTMLButtonElement>,\n) {\n  return <Select.Trigger {...props} ref={ref} />;\n});\n\ndescribe('<Select.Positioner />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Select.Positioner />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <Select.Root open>\n          <Select.Portal>{node}</Select.Portal>\n        </Select.Root>,\n      );\n    },\n  }));\n\n  const baselineX = 10;\n  const baselineY = 36;\n  const popupWidth = 52;\n  const popupHeight = 24;\n  const anchorWidth = 72;\n  const anchorHeight = 36;\n  const triggerStyle = { width: anchorWidth, height: anchorHeight };\n  const popupStyle = { width: popupWidth, height: popupHeight };\n\n  describe.skipIf(isJSDOM)('prop: sideOffset', () => {\n    it('offsets the side when a number is specified', async () => {\n      const sideOffset = 7;\n      await render(\n        <Select.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Select.Portal>\n            <Select.Positioner\n              data-testid=\"positioner\"\n              align=\"center\"\n              sideOffset={sideOffset}\n              alignItemWithTrigger={false}\n            >\n              <Select.Popup style={popupStyle}>Popup</Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      expect(screen.getByTestId('positioner').style.transform).toBe(\n        `translate(${baselineX}px, ${baselineY + sideOffset}px)`,\n      );\n    });\n\n    it('offsets the side when a function is specified', async () => {\n      await render(\n        <Select.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Select.Portal>\n            <Select.Positioner\n              data-testid=\"positioner\"\n              align=\"center\"\n              sideOffset={(data) => data.positioner.width + data.anchor.width}\n              alignItemWithTrigger={false}\n            >\n              <Select.Popup style={popupStyle}>Popup</Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      expect(screen.getByTestId('positioner').style.transform).toBe(\n        `translate(${baselineX}px, ${baselineY + popupWidth + anchorWidth}px)`,\n      );\n    });\n\n    it('can read the latest side inside sideOffset', async () => {\n      let side = 'none';\n      await render(\n        <Select.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Select.Portal>\n            <Select.Positioner\n              side=\"left\"\n              align=\"center\"\n              data-testid=\"positioner\"\n              alignItemWithTrigger={false}\n              sideOffset={(data) => {\n                side = data.side;\n                return 0;\n              }}\n            >\n              <Select.Popup style={popupStyle}>Popup</Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      // correctly flips the side in the browser\n      expect(side).toBe('right');\n    });\n\n    it('can read the latest align inside sideOffset', async () => {\n      let align = 'none';\n      await render(\n        <Select.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Select.Portal>\n            <Select.Positioner\n              side=\"right\"\n              align=\"start\"\n              data-testid=\"positioner\"\n              alignItemWithTrigger={false}\n              sideOffset={(data) => {\n                align = data.align;\n                return 0;\n              }}\n            >\n              <Select.Popup style={popupStyle}>Popup</Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      // correctly flips the align in the browser\n      expect(align).toBe('end');\n    });\n\n    it('reads logical side inside sideOffset', async () => {\n      let side = 'none';\n      await render(\n        <Select.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Select.Portal>\n            <Select.Positioner\n              side=\"inline-start\"\n              data-testid=\"positioner\"\n              alignItemWithTrigger={false}\n              sideOffset={(data) => {\n                side = data.side;\n                return 0;\n              }}\n            >\n              <Select.Popup style={popupStyle}>Popup</Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      // correctly flips the side in the browser\n      expect(side).toBe('inline-end');\n    });\n  });\n\n  describe.skipIf(isJSDOM)('prop: alignOffset', () => {\n    it('offsets the align when a number is specified', async () => {\n      const alignOffset = 7;\n      await render(\n        <Select.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Select.Portal>\n            <Select.Positioner\n              data-testid=\"positioner\"\n              align=\"center\"\n              alignOffset={alignOffset}\n              alignItemWithTrigger={false}\n            >\n              <Select.Popup style={popupStyle}>Popup</Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      expect(screen.getByTestId('positioner').style.transform).toBe(\n        `translate(${baselineX + alignOffset}px, ${baselineY}px)`,\n      );\n    });\n\n    it('offsets the align when a function is specified', async () => {\n      await render(\n        <Select.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Select.Portal>\n            <Select.Positioner\n              data-testid=\"positioner\"\n              align=\"center\"\n              alignItemWithTrigger={false}\n              alignOffset={(data) => data.positioner.width}\n            >\n              <Select.Popup style={popupStyle}>Popup</Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      expect(screen.getByTestId('positioner').style.transform).toBe(\n        `translate(${baselineX + popupWidth}px, ${baselineY}px)`,\n      );\n    });\n\n    it('can read the latest side inside alignOffset', async () => {\n      let side = 'none';\n      await render(\n        <Select.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Select.Portal>\n            <Select.Positioner\n              side=\"left\"\n              align=\"center\"\n              data-testid=\"positioner\"\n              alignItemWithTrigger={false}\n              alignOffset={(data) => {\n                side = data.side;\n                return 0;\n              }}\n            >\n              <Select.Popup style={popupStyle}>Popup</Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      // correctly flips the side in the browser\n      expect(side).toBe('right');\n    });\n\n    it('can read the latest align inside alignOffset', async () => {\n      let align = 'none';\n      await render(\n        <Select.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Select.Portal>\n            <Select.Positioner\n              side=\"right\"\n              align=\"start\"\n              data-testid=\"positioner\"\n              alignItemWithTrigger={false}\n              alignOffset={(data) => {\n                align = data.align;\n                return 0;\n              }}\n            >\n              <Select.Popup style={popupStyle}>Popup</Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      // correctly flips the align in the browser\n      expect(align).toBe('end');\n    });\n\n    it('reads logical side inside alignOffset', async () => {\n      let side = 'none';\n      await render(\n        <Select.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Select.Portal>\n            <Select.Positioner\n              side=\"inline-start\"\n              data-testid=\"positioner\"\n              alignItemWithTrigger={false}\n              alignOffset={(data) => {\n                side = data.side;\n                return 0;\n              }}\n            >\n              <Select.Popup style={popupStyle}>Popup</Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      // correctly flips the side in the browser\n      expect(side).toBe('inline-end');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/select/positioner/SelectPositioner.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { inertValue } from '@base-ui/utils/inertValue';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useScrollLock } from '@base-ui/utils/useScrollLock';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useStore } from '@base-ui/utils/store';\nimport { useSelectRootContext, useSelectFloatingContext } from '../root/SelectRootContext';\nimport { CompositeList } from '../../composite/list/CompositeList';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { popupStateMapping } from '../../utils/popupStateMapping';\nimport {\n  useAnchorPositioning,\n  type Align,\n  type Side,\n  type UseAnchorPositioningSharedParameters,\n} from '../../utils/useAnchorPositioning';\nimport { SelectPositionerContext } from './SelectPositionerContext';\nimport { InternalBackdrop } from '../../utils/InternalBackdrop';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { DROPDOWN_COLLISION_AVOIDANCE } from '../../utils/constants';\nimport { getDisabledMountTransitionStyles } from '../../utils/getDisabledMountTransitionStyles';\nimport { clearStyles } from '../popup/utils';\nimport { selectors } from '../store';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport { findItemIndex, selectedValueIncludes } from '../../utils/itemEquality';\n\nconst FIXED: React.CSSProperties = { position: 'fixed' };\n\n/**\n * Positions the select popup.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Select](https://base-ui.com/react/components/select)\n */\nexport const SelectPositioner = React.forwardRef(function SelectPositioner(\n  componentProps: SelectPositioner.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const {\n    anchor,\n    positionMethod = 'absolute',\n    className,\n    render,\n    side = 'bottom',\n    align = 'center',\n    sideOffset = 0,\n    alignOffset = 0,\n    collisionBoundary = 'clipping-ancestors',\n    collisionPadding,\n    arrowPadding = 5,\n    sticky = false,\n    disableAnchorTracking,\n    alignItemWithTrigger = true,\n    collisionAvoidance = DROPDOWN_COLLISION_AVOIDANCE,\n    ...elementProps\n  } = componentProps;\n\n  const {\n    store,\n    listRef,\n    labelsRef,\n    alignItemWithTriggerActiveRef,\n    selectedItemTextRef,\n    valuesRef,\n    initialValueRef,\n    popupRef,\n    setValue,\n  } = useSelectRootContext();\n  const floatingRootContext = useSelectFloatingContext();\n\n  const open = useStore(store, selectors.open);\n  const mounted = useStore(store, selectors.mounted);\n  const modal = useStore(store, selectors.modal);\n  const value = useStore(store, selectors.value);\n  const openMethod = useStore(store, selectors.openMethod);\n  const positionerElement = useStore(store, selectors.positionerElement);\n  const triggerElement = useStore(store, selectors.triggerElement);\n  const isItemEqualToValue = useStore(store, selectors.isItemEqualToValue);\n  const transitionStatus = useStore(store, selectors.transitionStatus);\n\n  const scrollUpArrowRef = React.useRef<HTMLDivElement | null>(null);\n  const scrollDownArrowRef = React.useRef<HTMLDivElement | null>(null);\n\n  const [controlledAlignItemWithTrigger, setControlledAlignItemWithTrigger] =\n    React.useState(alignItemWithTrigger);\n  const alignItemWithTriggerActive =\n    mounted && controlledAlignItemWithTrigger && openMethod !== 'touch';\n\n  if (!mounted && controlledAlignItemWithTrigger !== alignItemWithTrigger) {\n    setControlledAlignItemWithTrigger(alignItemWithTrigger);\n  }\n\n  useIsoLayoutEffect(() => {\n    if (!mounted) {\n      if (selectors.scrollUpArrowVisible(store.state)) {\n        store.set('scrollUpArrowVisible', false);\n      }\n      if (selectors.scrollDownArrowVisible(store.state)) {\n        store.set('scrollDownArrowVisible', false);\n      }\n    }\n  }, [store, mounted]);\n\n  React.useImperativeHandle(alignItemWithTriggerActiveRef, () => alignItemWithTriggerActive);\n\n  useScrollLock(\n    (alignItemWithTriggerActive || modal) && open && openMethod !== 'touch',\n    triggerElement,\n  );\n\n  const positioning = useAnchorPositioning({\n    anchor,\n    floatingRootContext,\n    positionMethod,\n    mounted,\n    side,\n    sideOffset,\n    align,\n    alignOffset,\n    arrowPadding,\n    collisionBoundary,\n    collisionPadding,\n    sticky,\n    disableAnchorTracking: disableAnchorTracking ?? alignItemWithTriggerActive,\n    collisionAvoidance,\n    keepMounted: true,\n  });\n\n  const renderedSide = alignItemWithTriggerActive ? 'none' : positioning.side;\n  const positionerStyles = alignItemWithTriggerActive ? FIXED : positioning.positionerStyles;\n\n  const defaultProps: React.ComponentProps<'div'> = React.useMemo(() => {\n    const hiddenStyles: React.CSSProperties = {};\n\n    if (!open) {\n      hiddenStyles.pointerEvents = 'none';\n    }\n\n    return {\n      role: 'presentation',\n      hidden: !mounted,\n      style: {\n        ...positionerStyles,\n        ...hiddenStyles,\n      },\n    };\n  }, [open, mounted, positionerStyles]);\n\n  const state: SelectPositionerState = {\n    open,\n    side: renderedSide,\n    align: positioning.align,\n    anchorHidden: positioning.anchorHidden,\n  };\n\n  const setPositionerElement = useStableCallback((element) => {\n    store.set('positionerElement', element);\n  });\n\n  const element = useRenderElement('div', componentProps, {\n    ref: [forwardedRef, setPositionerElement],\n    state,\n    stateAttributesMapping: popupStateMapping,\n    props: [defaultProps, getDisabledMountTransitionStyles(transitionStatus), elementProps],\n  });\n\n  const prevMapSizeRef = React.useRef(0);\n\n  const onMapChange = useStableCallback(\n    (map: Map<Element, { index?: number | null | undefined } | null>) => {\n      if (map.size === 0 && prevMapSizeRef.current === 0) {\n        return;\n      }\n\n      if (valuesRef.current.length === 0) {\n        return;\n      }\n\n      const prevSize = prevMapSizeRef.current;\n      prevMapSizeRef.current = map.size;\n\n      if (map.size === prevSize) {\n        return;\n      }\n\n      const eventDetails = createChangeEventDetails(REASONS.none);\n\n      if (prevSize !== 0 && !store.state.multiple && value !== null) {\n        const selectedValueIndex = findItemIndex(valuesRef.current, value, isItemEqualToValue);\n        if (selectedValueIndex === -1) {\n          const initialSelectedValue = initialValueRef.current;\n          const hasInitial =\n            initialSelectedValue != null &&\n            findItemIndex(valuesRef.current, initialSelectedValue, isItemEqualToValue) !== -1;\n          const nextValue = hasInitial ? initialSelectedValue : null;\n          setValue(nextValue, eventDetails);\n\n          if (nextValue === null) {\n            store.set('selectedIndex', null);\n            selectedItemTextRef.current = null;\n          }\n        }\n      }\n\n      if (prevSize !== 0 && store.state.multiple && Array.isArray(value)) {\n        const hasVisibleItem = (selectedItemValue: unknown) =>\n          findItemIndex(valuesRef.current, selectedItemValue, isItemEqualToValue) !== -1;\n        const nextValue = value.filter((selectedItemValue) => hasVisibleItem(selectedItemValue));\n        if (\n          nextValue.length !== value.length ||\n          nextValue.some(\n            (selectedItemValue) =>\n              !selectedValueIncludes(value, selectedItemValue, isItemEqualToValue),\n          )\n        ) {\n          setValue(nextValue, eventDetails);\n\n          if (nextValue.length === 0) {\n            store.set('selectedIndex', null);\n            selectedItemTextRef.current = null;\n          }\n        }\n      }\n\n      if (open && alignItemWithTriggerActive) {\n        store.update({\n          scrollUpArrowVisible: false,\n          scrollDownArrowVisible: false,\n        });\n\n        const stylesToClear: React.CSSProperties = { height: '' };\n        clearStyles(positionerElement, stylesToClear);\n        clearStyles(popupRef.current, stylesToClear);\n      }\n    },\n  );\n\n  const contextValue: SelectPositionerContext = React.useMemo(\n    () => ({\n      ...positioning,\n      side: renderedSide,\n      alignItemWithTriggerActive,\n      setControlledAlignItemWithTrigger,\n      scrollUpArrowRef,\n      scrollDownArrowRef,\n    }),\n    [positioning, renderedSide, alignItemWithTriggerActive, setControlledAlignItemWithTrigger],\n  );\n\n  return (\n    <CompositeList elementsRef={listRef} labelsRef={labelsRef} onMapChange={onMapChange}>\n      <SelectPositionerContext.Provider value={contextValue}>\n        {mounted && modal && <InternalBackdrop inert={inertValue(!open)} cutout={triggerElement} />}\n        {element}\n      </SelectPositionerContext.Provider>\n    </CompositeList>\n  );\n});\n\nexport interface SelectPositionerState {\n  /**\n   * Whether the component is open.\n   */\n  open: boolean;\n  /**\n   * The side of the anchor the component is placed on.\n   */\n  side: Side | 'none';\n  /**\n   * The alignment of the component relative to the anchor.\n   */\n  align: Align;\n  /**\n   * Whether the anchor element is hidden.\n   */\n  anchorHidden: boolean;\n}\n\nexport interface SelectPositionerProps\n  extends UseAnchorPositioningSharedParameters, BaseUIComponentProps<'div', SelectPositionerState> {\n  /**\n   * Whether the positioner overlaps the trigger so the selected item's text is aligned with the trigger's value text. This only applies to mouse input and is automatically disabled if there is not enough space.\n   * @default true\n   */\n  alignItemWithTrigger?: boolean | undefined;\n}\n\nexport namespace SelectPositioner {\n  export type State = SelectPositionerState;\n  export type Props = SelectPositionerProps;\n}\n"
  },
  {
    "path": "packages/react/src/select/positioner/SelectPositionerContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport type { Side, UseAnchorPositioningReturnValue } from '../../utils/useAnchorPositioning';\n\nexport interface SelectPositionerContext extends Omit<UseAnchorPositioningReturnValue, 'side'> {\n  side: 'none' | Side;\n  alignItemWithTriggerActive: boolean;\n  setControlledAlignItemWithTrigger: React.Dispatch<React.SetStateAction<boolean>>;\n  scrollUpArrowRef: React.RefObject<HTMLDivElement | null>;\n  scrollDownArrowRef: React.RefObject<HTMLDivElement | null>;\n}\n\nexport const SelectPositionerContext = React.createContext<SelectPositionerContext | undefined>(\n  undefined,\n);\n\nexport function useSelectPositionerContext() {\n  const context = React.useContext(SelectPositionerContext);\n  if (!context) {\n    throw new Error(\n      'Base UI: SelectPositionerContext is missing. SelectPositioner parts must be placed within <Select.Positioner>.',\n    );\n  }\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/select/positioner/SelectPositionerCssVars.ts",
    "content": "export enum SelectPositionerCssVars {\n  /**\n   * The available width between the trigger and the edge of the viewport.\n   * @type {number}\n   */\n  availableWidth = '--available-width',\n  /**\n   * The available height between the trigger and the edge of the viewport.\n   * @type {number}\n   */\n  availableHeight = '--available-height',\n  /**\n   * The anchor's width.\n   * @type {number}\n   */\n  anchorWidth = '--anchor-width',\n  /**\n   * The anchor's height.\n   * @type {number}\n   */\n  anchorHeight = '--anchor-height',\n  /**\n   * The coordinates that this element is anchored to. Used for animations and transitions.\n   * @type {string}\n   */\n  transformOrigin = '--transform-origin',\n}\n"
  },
  {
    "path": "packages/react/src/select/positioner/SelectPositionerDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum SelectPositionerDataAttributes {\n  /**\n   * Present when the select popup is open.\n   */\n  open = CommonPopupDataAttributes.open,\n  /**\n   * Present when the select popup is closed.\n   */\n  closed = CommonPopupDataAttributes.closed,\n  /**\n   * Present when the anchor is hidden.\n   */\n  anchorHidden = CommonPopupDataAttributes.anchorHidden,\n  /**\n   * Indicates which side the popup is positioned relative to the trigger.\n   * @type {'none' | 'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'}\n   */\n  side = CommonPopupDataAttributes.side,\n  /**\n   * Indicates how the popup is aligned relative to specified side.\n   * @type {'start' | 'center' | 'end'}\n   */\n  align = CommonPopupDataAttributes.align,\n}\n"
  },
  {
    "path": "packages/react/src/select/root/SelectRoot.spec.tsx",
    "content": "import * as React from 'react';\nimport { expectType } from '#test-utils';\nimport { Select } from '@base-ui/react/select';\nimport { mergeProps } from '../../merge-props';\n\nconst objectItems = [\n  { value: 'a', label: 'apple' },\n  { value: 'b', label: 'banana' },\n  { value: 'c', label: 'cherry' },\n];\n\nconst objectItemsReadonly = [\n  { value: 'a', label: 'apple' },\n  { value: 'b', label: 'banana' },\n  { value: 'c', label: 'cherry' },\n] as const;\n\nconst groupedItemsReadonly = [\n  {\n    heading: 'Fruits',\n    items: [\n      { value: 'a', label: 'apple' },\n      { value: 'b', label: 'banana' },\n    ],\n  },\n] as const;\n\n<Select.Root items={groupedItemsReadonly} />;\n\n<Select.Root\n  items={objectItemsReadonly}\n  defaultValue=\"a\"\n  itemToStringLabel={(item) => {\n    item.startsWith('a');\n    return item;\n  }}\n  itemToStringValue={(item) => {\n    item.startsWith('a');\n    return item;\n  }}\n  onValueChange={(value) => {\n    // @ts-expect-error\n    value.startsWith('a');\n  }}\n/>;\n\n<Select.Root\n  items={objectItems}\n  defaultValue=\"a\"\n  itemToStringLabel={(item) => {\n    item.startsWith('a');\n    return item;\n  }}\n  itemToStringValue={(item) => {\n    item.startsWith('a');\n    return item;\n  }}\n  onValueChange={(value) => {\n    // @ts-expect-error\n    value.startsWith('a');\n  }}\n/>;\n\n<Select.Root\n  items={objectItems}\n  value=\"a\"\n  itemToStringLabel={(item) => {\n    item.startsWith('a');\n    return item;\n  }}\n  itemToStringValue={(item) => {\n    item.startsWith('a');\n    return item;\n  }}\n  onValueChange={(value) => {\n    // @ts-expect-error - possibly null\n    value.startsWith('a');\n  }}\n/>;\n\ntype Obj = { code: string };\nconst objectValueItems: Array<{ value: Obj; label: string }> = [\n  { value: { code: 'a' }, label: 'apple' },\n  { value: { code: 'b' }, label: 'banana' },\n];\n\n<Select.Root\n  items={objectValueItems}\n  defaultValue={objectValueItems[0].value}\n  itemToStringLabel={(item) => item.code}\n  itemToStringValue={(item) => item.code}\n  onValueChange={(value) => {\n    // @ts-expect-error\n    value.code;\n  }}\n/>;\n\n<Select.Root\n  items={objectValueItems}\n  value={objectValueItems[0].value}\n  itemToStringLabel={(item) => item.code}\n  itemToStringValue={(item) => item.code}\n  onValueChange={(value) => {\n    // @ts-expect-error - possibly null\n    value.code;\n  }}\n/>;\n\n<Select.Root\n  value=\"a\"\n  isItemEqualToValue={(item, value) => {\n    expectType<string, typeof item>(item);\n    expectType<string, typeof value>(value);\n    item.toUpperCase();\n    value?.toUpperCase();\n    return item === value;\n  }}\n/>;\n\n<Select.Root\n  value={objectValueItems[0]}\n  isItemEqualToValue={(item, value) => {\n    expectType<(typeof objectValueItems)[number], typeof item>(item);\n    expectType<(typeof objectValueItems)[number], typeof value>(value);\n    item.value.code;\n    value.value.code;\n    return value != null && item.value.code === value.value.code;\n  }}\n/>;\n\n<Select.Root\n  multiple\n  // @ts-expect-error\n  defaultValue=\"javascript\"\n  onValueChange={(value) => {\n    value.pop();\n  }}\n/>;\n\n<Select.Root\n  multiple\n  defaultValue={['javascript', 'typescript']}\n  onValueChange={(value) => {\n    value.pop();\n  }}\n/>;\n\n<Select.Root\n  multiple={false}\n  // @ts-expect-error\n  defaultValue={['javascript', 'typescript']}\n  onValueChange={(value) => {\n    // @ts-expect-error\n    value.pop();\n  }}\n/>;\n\n<Select.Root\n  defaultValue=\"javascript\"\n  onValueChange={(value) => {\n    // @ts-expect-error\n    value.pop();\n  }}\n/>;\n\n<Select.Root\n  multiple\n  onValueChange={(value) => {\n    value.pop();\n  }}\n/>;\n\nfunction App() {\n  const [multiple, setMultiple] = React.useState(false);\n  return (\n    <Select.Root\n      multiple={multiple}\n      onValueChange={(value) => {\n        // @ts-expect-error\n        value.pop();\n      }}\n    />\n  );\n}\n\n<Select.Root\n  defaultValue=\"test\"\n  onValueChange={(value) => {\n    // @ts-expect-error\n    value.length;\n  }}\n/>;\n\n<Select.Root\n  defaultValue={[{ id: 2, name: 'Bob' }]}\n  itemToStringLabel={(item) => item.name}\n  itemToStringValue={(item) => String(item.id)}\n  isItemEqualToValue={(item, value) => item.id === value.id}\n  defaultOpen\n  multiple\n/>;\n\n// Should accept null value\n<Select.Root items={objectItemsReadonly} value={null} />;\n\nfunction App2() {\n  const [value, setValue] = React.useState('a');\n  return (\n    <Select.Root\n      value={value}\n      onValueChange={(newValue) => {\n        // @ts-expect-error\n        newValue.length;\n        // @ts-expect-error - user is forced to type useState with null\n        // even if they don't want to allow null\n        setValue(newValue);\n      }}\n    />\n  );\n}\n\nfunction App3() {\n  const [value, setValue] = React.useState<string | null>('a');\n  return (\n    <Select.Root\n      value={value}\n      onValueChange={(newValue) => {\n        // @ts-expect-error\n        newValue.length;\n        setValue(newValue);\n      }}\n    />\n  );\n}\n\nmergeProps<typeof Select.Root<any>>(\n  {\n    value: '',\n  },\n  {},\n);\n\nexport function Wrapper<Value, Multiple extends boolean | undefined = false>(\n  props: Select.Root.Props<Value, Multiple>,\n) {\n  return <Select.Root {...props} />;\n}\n"
  },
  {
    "path": "packages/react/src/select/root/SelectRoot.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { Select } from '@base-ui/react/select';\nimport { Popover } from '@base-ui/react/popover';\nimport {\n  act,\n  fireEvent,\n  flushMicrotasks,\n  screen,\n  waitFor,\n  ignoreActWarnings,\n  reactMajor,\n} from '@mui/internal-test-utils';\nimport { createRenderer, isJSDOM, popupConformanceTests, wait } from '#test-utils';\nimport { Field } from '@base-ui/react/field';\nimport { Form } from '@base-ui/react/form';\n\ndescribe('<Select.Root />', () => {\n  beforeEach(() => {\n    globalThis.BASE_UI_ANIMATIONS_DISABLED = true;\n  });\n\n  const { render, renderToString } = createRenderer();\n\n  describe('conformance', () => {\n    beforeEach(() => {\n      ignoreActWarnings();\n    });\n\n    popupConformanceTests({\n      createComponent: (props) => (\n        <Select.Root {...props.root}>\n          <Select.Trigger {...props.trigger}>\n            <Select.Value />\n          </Select.Trigger>\n          <Select.Portal {...props.portal}>\n            <Select.Positioner>\n              <Select.Popup {...props.popup}>\n                <Select.Item>Item</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>\n      ),\n      render,\n      triggerMouseAction: 'click',\n      expectedPopupRole: 'listbox',\n      alwaysMounted: 'only-after-open',\n    });\n  });\n\n  describe('server-side rendering', () => {\n    it('does not link Select.Label before hydration', () => {\n      renderToString(\n        <Select.Root>\n          <Select.Label data-testid=\"label\">Font</Select.Label>\n          <Select.Trigger data-testid=\"trigger\">\n            <Select.Value />\n          </Select.Trigger>\n        </Select.Root>,\n      );\n\n      const label = screen.getByTestId('label');\n      const trigger = screen.getByTestId('trigger');\n\n      expect(label.id).not.toBe('');\n      expect(trigger.id).not.toBe('');\n      expect(trigger).not.toHaveAttribute('aria-labelledby');\n    });\n  });\n\n  describe('prop: defaultValue', () => {\n    it('should select the item by default', async () => {\n      await render(\n        <Select.Root defaultValue=\"b\">\n          <Select.Trigger data-testid=\"trigger\">\n            <Select.Value />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"a\">a</Select.Item>\n                <Select.Item value=\"b\">b</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n\n      fireEvent.click(trigger);\n\n      await flushMicrotasks();\n\n      expect(screen.getByRole('option', { name: 'b', hidden: false })).toHaveAttribute(\n        'data-selected',\n        '',\n      );\n    });\n  });\n\n  describe('prop: value', () => {\n    it('should select the item specified by the value prop', async () => {\n      await render(\n        <Select.Root value=\"b\">\n          <Select.Trigger data-testid=\"trigger\">\n            <Select.Value />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"a\">a</Select.Item>\n                <Select.Item value=\"b\">b</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n\n      fireEvent.click(trigger);\n\n      await flushMicrotasks();\n\n      expect(screen.getByRole('option', { name: 'b', hidden: false })).toHaveAttribute(\n        'data-selected',\n        '',\n      );\n    });\n\n    it('should update the selected item when the value prop changes', async () => {\n      const { setProps } = await render(\n        <Select.Root value=\"a\">\n          <Select.Trigger data-testid=\"trigger\">\n            <Select.Value />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"a\">a</Select.Item>\n                <Select.Item value=\"b\">b</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n\n      fireEvent.click(trigger);\n\n      await flushMicrotasks();\n\n      expect(screen.getByRole('option', { name: 'a', hidden: false })).toHaveAttribute(\n        'data-selected',\n        '',\n      );\n\n      await setProps({ value: 'b' });\n\n      expect(screen.getByRole('option', { name: 'b', hidden: false })).toHaveAttribute(\n        'data-selected',\n        '',\n      );\n    });\n\n    it('should not update the internal value if the controlled value prop does not change', async () => {\n      const onValueChange = vi.fn();\n      await render(\n        <Select.Root value=\"a\" onValueChange={onValueChange}>\n          <Select.Trigger data-testid=\"trigger\">\n            <Select.Value />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"a\">a</Select.Item>\n                <Select.Item value=\"b\">b</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      expect(trigger).toHaveTextContent('a');\n\n      fireEvent.click(trigger);\n      await flushMicrotasks();\n\n      const optionB = screen.getByRole('option', { name: 'b' });\n      fireEvent.click(optionB);\n      await flushMicrotasks();\n\n      expect(onValueChange.mock.calls.length).toBe(0);\n      expect(trigger).toHaveTextContent('a');\n    });\n\n    it('updates <Select.Value /> label when the value prop changes before the popup opens', async () => {\n      const { setProps } = await render(\n        <Select.Root value=\"b\">\n          <Select.Trigger data-testid=\"trigger\">\n            <Select.Value />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"a\">a</Select.Item>\n                <Select.Item value=\"b\">b</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n\n      expect(trigger).toHaveTextContent('b');\n\n      await setProps({ value: 'a' });\n      await flushMicrotasks();\n\n      expect(trigger).toHaveTextContent('a');\n    });\n  });\n\n  describe('prop: itemToStringValue', () => {\n    it('uses itemToStringValue for form submission', async () => {\n      const items = [\n        { country: 'United States', code: 'US' },\n        { country: 'Canada', code: 'CA' },\n        { country: 'Australia', code: 'AU' },\n      ];\n\n      await render(\n        <Select.Root\n          name=\"country\"\n          defaultValue={items[0]}\n          itemToStringLabel={(item) => item.country}\n          itemToStringValue={(item) => item.code}\n        >\n          <Select.Trigger>\n            <Select.Value />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                {items.map((it) => (\n                  <Select.Item key={it.code} value={it}>\n                    {it.country}\n                  </Select.Item>\n                ))}\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      const hiddenInput = screen.getByRole('textbox', {\n        hidden: true,\n      });\n      expect(hiddenInput).toHaveValue('US');\n    });\n\n    it('uses itemToStringValue for multiple selection form submission', async () => {\n      const items = [\n        { country: 'United States', code: 'US' },\n        { country: 'Canada', code: 'CA' },\n        { country: 'Australia', code: 'AU' },\n      ];\n\n      const { container } = await render(\n        <Select.Root\n          name=\"countries\"\n          multiple\n          defaultValue={[items[0], items[1]]}\n          itemToStringLabel={(item) => item.country}\n          itemToStringValue={(item) => item.code}\n        >\n          <Select.Trigger>\n            <Select.Value />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                {items.map((it) => (\n                  <Select.Item key={it.code} value={it}>\n                    {it.country}\n                  </Select.Item>\n                ))}\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      // eslint-disable-next-line testing-library/no-container -- No appropriate method on screen since it's a type=hidden input\n      const hiddenInputs = container.querySelectorAll('input[name=\"countries\"]');\n      expect(hiddenInputs).toHaveLength(2);\n      expect(hiddenInputs[0]).toHaveValue('US');\n      expect(hiddenInputs[1]).toHaveValue('CA');\n    });\n  });\n\n  describe('prop: itemToStringLabel', () => {\n    const items = [\n      { country: 'United States', code: 'US' },\n      { country: 'Canada', code: 'CA' },\n      { country: 'Australia', code: 'AU' },\n    ];\n\n    it('uses itemToStringLabel for trigger text when value is object', async () => {\n      await render(\n        <Select.Root\n          defaultValue={items[1]}\n          itemToStringLabel={(item) => item.country}\n          itemToStringValue={(item) => item.code}\n        >\n          <Select.Trigger data-testid=\"trigger\">\n            <Select.Value />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                {items.map((it) => (\n                  <Select.Item key={it.code} value={it}>\n                    {it.country}\n                  </Select.Item>\n                ))}\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      expect(trigger).toHaveTextContent('Canada');\n    });\n\n    it('updates trigger text with itemToStringLabel after selecting object item', async () => {\n      const { user } = await render(\n        <Select.Root\n          defaultOpen\n          itemToStringLabel={(item: any) => item.country}\n          itemToStringValue={(item: any) => item.code}\n        >\n          <Select.Trigger data-testid=\"trigger\">\n            <Select.Value />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                {items.map((it) => (\n                  <Select.Item key={it.code} value={it}>\n                    {it.country}\n                  </Select.Item>\n                ))}\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      await user.click(screen.getByRole('option', { name: 'Canada' }));\n      expect(screen.getByTestId('trigger')).toHaveTextContent('Canada');\n    });\n  });\n\n  describe('prop: onValueChange', () => {\n    const { render: renderFakeTimers, clock } = createRenderer({\n      clockOptions: {\n        shouldAdvanceTime: true,\n      },\n    });\n\n    clock.withFakeTimers();\n\n    it('should call onValueChange when an item is selected', async () => {\n      if (reactMajor <= 18) {\n        ignoreActWarnings();\n      }\n\n      const handleValueChange = vi.fn();\n\n      function App() {\n        const [value, setValue] = React.useState<string | null>('');\n\n        return (\n          <Select.Root\n            value={value}\n            onValueChange={(newValue) => {\n              setValue(newValue);\n              handleValueChange(newValue);\n            }}\n          >\n            <Select.Trigger data-testid=\"trigger\">\n              <Select.Value />\n            </Select.Trigger>\n            <Select.Portal>\n              <Select.Positioner>\n                <Select.Popup>\n                  <Select.Item value=\"a\">a</Select.Item>\n                  <Select.Item value=\"b\">b</Select.Item>\n                </Select.Popup>\n              </Select.Positioner>\n            </Select.Portal>\n          </Select.Root>\n        );\n      }\n\n      const { user } = await renderFakeTimers(<App />);\n\n      const trigger = screen.getByTestId('trigger');\n\n      await user.click(trigger);\n      await flushMicrotasks();\n\n      const option = screen.getByRole('option', { name: 'b' });\n      await clock.tickAsync(200);\n      await user.click(option);\n\n      expect(handleValueChange.mock.calls[0][0]).toBe('b');\n    });\n\n    it('is not called twice on select', async () => {\n      if (reactMajor <= 18) {\n        ignoreActWarnings();\n      }\n\n      const handleValueChange = vi.fn();\n\n      const { user } = await renderFakeTimers(\n        <Select.Root onValueChange={handleValueChange}>\n          <Select.Trigger data-testid=\"trigger\">\n            <Select.Value />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"a\">a</Select.Item>\n                <Select.Item value=\"b\">b</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n\n      await user.click(trigger);\n      await flushMicrotasks();\n\n      const option = screen.getByRole('option', { name: 'b' });\n      await clock.tickAsync(200);\n      await user.click(option);\n\n      expect(handleValueChange.mock.calls.length).toBe(1);\n    });\n  });\n\n  describe('prop: defaultOpen', () => {\n    it('should open the select by default', async () => {\n      await render(\n        <Select.Root defaultOpen>\n          <Select.Trigger data-testid=\"trigger\">\n            <Select.Value />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"a\">a</Select.Item>\n                <Select.Item value=\"b\">b</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      expect(screen.getByRole('listbox', { hidden: false })).toBeVisible();\n    });\n\n    it('should select an item and close when clicked while opened by default', async () => {\n      const handleValueChange = vi.fn();\n\n      const { user } = await render(\n        <Select.Root defaultOpen onValueChange={handleValueChange}>\n          <Select.Trigger data-testid=\"trigger\">\n            <Select.Value data-testid=\"value\" />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"a\">a</Select.Item>\n                <Select.Item value=\"b\">b</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      expect(screen.queryByRole('listbox')).toBeVisible();\n\n      const optionB = screen.getByRole('option', { name: 'b' });\n\n      fireEvent.mouseMove(optionB);\n      await user.click(optionB);\n      await flushMicrotasks();\n\n      expect(handleValueChange.mock.calls.length).toBe(1);\n      expect(handleValueChange.mock.calls[0][0]).toBe('b');\n\n      await waitFor(() => {\n        expect(screen.queryByRole('listbox')).toBe(null);\n      });\n    });\n  });\n\n  describe('prop: onOpenChange', () => {\n    it('should call onOpenChange when the select is opened or closed', async () => {\n      const handleOpenChange = vi.fn();\n\n      const { user } = await render(\n        <Select.Root onOpenChange={handleOpenChange}>\n          <Select.Trigger data-testid=\"trigger\">\n            <Select.Value />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"a\">a</Select.Item>\n                <Select.Item value=\"b\">b</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n\n      await user.click(trigger);\n      await waitFor(() => {\n        expect(handleOpenChange.mock.calls.length).toBe(1);\n      });\n      expect(handleOpenChange.mock.calls[0][0]).toBe(true);\n    });\n  });\n\n  describe('BaseUIChangeEventDetails', () => {\n    it('onOpenChange cancel() prevents opening while uncontrolled', async () => {\n      await render(\n        <Select.Root\n          onOpenChange={(nextOpen, eventDetails) => {\n            if (nextOpen) {\n              eventDetails.cancel();\n            }\n          }}\n        >\n          <Select.Trigger data-testid=\"trigger\">\n            <Select.Value />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"a\">a</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      fireEvent.click(trigger);\n      await flushMicrotasks();\n\n      expect(screen.queryByRole('listbox')).toBe(null);\n    });\n  });\n\n  it('should handle browser autofill', async () => {\n    const { user } = await render(\n      <Select.Root name=\"select\">\n        <Select.Trigger data-testid=\"trigger\">\n          <Select.Value />\n        </Select.Trigger>\n        <Select.Portal>\n          <Select.Positioner>\n            <Select.Popup>\n              <Select.Item value=\"a\">a</Select.Item>\n              <Select.Item value=\"b\">b</Select.Item>\n            </Select.Popup>\n          </Select.Positioner>\n        </Select.Portal>\n      </Select.Root>,\n    );\n\n    const trigger = screen.getByTestId('trigger');\n\n    const selectInput = screen.getByRole('textbox', {\n      hidden: true,\n    });\n    expect(selectInput).toHaveAttribute('name', 'select');\n    fireEvent.change(selectInput, { target: { value: 'b' } });\n    await flushMicrotasks();\n\n    await user.click(trigger);\n\n    await waitFor(() => {\n      expect(screen.getByRole('option', { name: 'b' })).toHaveAttribute('data-selected', '');\n    });\n  });\n\n  it('should pass autoComplete to the hidden input', async () => {\n    await render(\n      <Select.Root name=\"country\" autoComplete=\"country\">\n        <Select.Trigger data-testid=\"trigger\">\n          <Select.Value />\n        </Select.Trigger>\n        <Select.Portal>\n          <Select.Positioner>\n            <Select.Popup>\n              <Select.Item value=\"US\">United States</Select.Item>\n              <Select.Item value=\"CA\">Canada</Select.Item>\n            </Select.Popup>\n          </Select.Positioner>\n        </Select.Portal>\n      </Select.Root>,\n    );\n\n    const hiddenInput = screen.getByRole('textbox', { hidden: true });\n    expect(hiddenInput).toHaveAttribute('name', 'country');\n    expect(hiddenInput).toHaveAttribute('autocomplete', 'country');\n  });\n\n  it('should handle browser autofill with object values', async () => {\n    const items = [\n      { country: 'United States', code: 'US' },\n      { country: 'Canada', code: 'CA' },\n    ];\n\n    const { user } = await render(\n      <Select.Root\n        name=\"country\"\n        itemToStringLabel={(item: any) => item.country}\n        itemToStringValue={(item: any) => item.code}\n      >\n        <Select.Trigger data-testid=\"trigger\">\n          <Select.Value />\n        </Select.Trigger>\n        <Select.Portal>\n          <Select.Positioner>\n            <Select.Popup>\n              {items.map((it) => (\n                <Select.Item key={it.code} value={it}>\n                  {it.country}\n                </Select.Item>\n              ))}\n            </Select.Popup>\n          </Select.Positioner>\n        </Select.Portal>\n      </Select.Root>,\n    );\n\n    const trigger = screen.getByTestId('trigger');\n\n    const selectInput = screen.getByRole('textbox', {\n      hidden: true,\n    });\n    expect(selectInput).toHaveAttribute('name', 'country');\n    fireEvent.change(selectInput, { target: { value: 'CA' } });\n    await flushMicrotasks();\n\n    await user.click(trigger);\n\n    await waitFor(() => {\n      expect(screen.getByRole('option', { name: 'Canada', hidden: false })).toHaveAttribute(\n        'data-selected',\n        '',\n      );\n    });\n  });\n\n  describe('prop: modal', () => {\n    it('should render an internal backdrop when `true`', async () => {\n      const { user } = await render(\n        <div>\n          <Select.Root modal>\n            <Select.Trigger data-testid=\"trigger\">Open</Select.Trigger>\n            <Select.Portal>\n              <Select.Positioner data-testid=\"positioner\">\n                <Select.Popup>\n                  <Select.Item>1</Select.Item>\n                </Select.Popup>\n              </Select.Positioner>\n            </Select.Portal>\n          </Select.Root>\n          <button>Outside</button>\n        </div>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n\n      await user.click(trigger);\n\n      await waitFor(() => {\n        expect(screen.queryByRole('listbox')).not.toBe(null);\n      });\n\n      const positioner = screen.getByTestId('positioner');\n\n      expect(positioner.previousElementSibling).toHaveAttribute('role', 'presentation');\n    });\n\n    it('should not render an internal backdrop when `false`', async () => {\n      const { user } = await render(\n        <div>\n          <Select.Root modal={false}>\n            <Select.Trigger data-testid=\"trigger\">Open</Select.Trigger>\n            <Select.Portal>\n              <Select.Positioner data-testid=\"positioner\">\n                <Select.Popup>\n                  <Select.Item>1</Select.Item>\n                </Select.Popup>\n              </Select.Positioner>\n            </Select.Portal>\n          </Select.Root>\n          <button>Outside</button>\n        </div>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n\n      await user.click(trigger);\n\n      await waitFor(() => {\n        expect(screen.queryByRole('listbox')).not.toBe(null);\n      });\n\n      const positioner = screen.getByTestId('positioner');\n\n      expect(positioner.previousElementSibling).toBe(null);\n    });\n  });\n\n  describe.skipIf(isJSDOM)('interaction type tracking (openMethod)', () => {\n    it('keeps touch interaction type when reopening quickly after close', async ({\n      onTestFinished,\n    }) => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n      let nextFrameId = 0;\n      const frameCallbacks = new Map<number, FrameRequestCallback>();\n\n      const requestAnimationFrameSpy = vi\n        .spyOn(window, 'requestAnimationFrame')\n        .mockImplementation((callback: FrameRequestCallback) => {\n          nextFrameId += 1;\n          frameCallbacks.set(nextFrameId, callback);\n          return nextFrameId;\n        });\n      const cancelAnimationFrameSpy = vi\n        .spyOn(window, 'cancelAnimationFrame')\n        .mockImplementation((id: number) => {\n          frameCallbacks.delete(id);\n        });\n\n      onTestFinished(() => {\n        requestAnimationFrameSpy.mockRestore();\n        cancelAnimationFrameSpy.mockRestore();\n        globalThis.BASE_UI_ANIMATIONS_DISABLED = true;\n      });\n\n      const style = `\n        @keyframes select-close-test {\n          to {\n            opacity: 0;\n          }\n        }\n\n        .animation-test-indicator[data-ending-style] {\n          animation: select-close-test 20ms linear;\n        }\n      `;\n\n      await render(\n        <div>\n          {/* eslint-disable-next-line react/no-danger */}\n          <style dangerouslySetInnerHTML={{ __html: style }} />\n          <Select.Root modal>\n            <Select.Trigger>Open</Select.Trigger>\n            <Select.Portal>\n              <Select.Positioner>\n                <Select.Popup className=\"animation-test-indicator\">\n                  <Select.Item>Item</Select.Item>\n                </Select.Popup>\n              </Select.Positioner>\n            </Select.Portal>\n          </Select.Root>\n        </div>,\n      );\n\n      const trigger = screen.getByRole('combobox');\n\n      const isScrollLocked = () =>\n        trigger.ownerDocument.documentElement.style.overflow === 'hidden' ||\n        trigger.ownerDocument.documentElement.hasAttribute('data-base-ui-scroll-locked') ||\n        trigger.ownerDocument.body.style.overflow === 'hidden';\n\n      function fireTouchPress() {\n        fireEvent.pointerDown(trigger, { pointerType: 'touch' });\n        fireEvent.mouseDown(trigger);\n      }\n\n      function flushAnimationFrames() {\n        let iterations = 0;\n        while (frameCallbacks.size > 0) {\n          if (iterations > 20) {\n            throw new Error('Exceeded maximum animation frame flush iterations.');\n          }\n\n          const pending = Array.from(frameCallbacks.values());\n          frameCallbacks.clear();\n          pending.forEach((callback) => {\n            callback(0);\n          });\n          iterations += 1;\n        }\n      }\n\n      fireTouchPress();\n      await act(async () => {\n        flushAnimationFrames();\n      });\n\n      await waitFor(() => {\n        expect(screen.queryByRole('listbox')).not.toBe(null);\n      });\n\n      fireTouchPress();\n\n      await act(async () => {\n        flushAnimationFrames();\n      });\n\n      await waitFor(() => {\n        expect(trigger).toHaveAttribute('aria-expanded', 'false');\n      });\n\n      // Re-open while the previous close animation is still pending.\n      fireTouchPress();\n\n      await act(async () => {\n        flushAnimationFrames();\n      });\n\n      await waitFor(() => {\n        expect(screen.queryByRole('listbox')).not.toBe(null);\n      });\n\n      await wait(30);\n\n      expect(isScrollLocked()).toBe(false);\n    });\n\n    it('keeps touch positioning during the close transition', async ({ onTestFinished }) => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n      onTestFinished(() => {\n        globalThis.BASE_UI_ANIMATIONS_DISABLED = true;\n      });\n\n      const style = `\n        @keyframes select-close-test {\n          to {\n            opacity: 0;\n          }\n        }\n\n        .animation-test-popup[data-ending-style] {\n          animation: select-close-test 100ms linear;\n        }\n      `;\n\n      await render(\n        <div style={{ paddingTop: 80 }}>\n          {/* eslint-disable-next-line react/no-danger */}\n          <style dangerouslySetInnerHTML={{ __html: style }} />\n          <Select.Root>\n            <Select.Trigger>Open</Select.Trigger>\n            <Select.Portal>\n              <Select.Positioner>\n                <Select.Popup className=\"animation-test-popup\">\n                  <Select.Item>Item</Select.Item>\n                </Select.Popup>\n              </Select.Positioner>\n            </Select.Portal>\n          </Select.Root>\n        </div>,\n      );\n\n      const trigger = screen.getByRole('combobox');\n\n      function fireTouchPress() {\n        fireEvent.pointerDown(trigger, { pointerType: 'touch' });\n        fireEvent.mouseDown(trigger);\n      }\n\n      fireTouchPress();\n\n      const popup = await screen.findByRole('listbox');\n      const positioner = popup.parentElement as HTMLElement;\n\n      expect(getComputedStyle(positioner).position).toBe('absolute');\n\n      fireTouchPress();\n\n      await waitFor(() => {\n        expect(popup).toHaveAttribute('data-ending-style');\n      });\n\n      expect(getComputedStyle(positioner).position).toBe('absolute');\n    });\n\n    it('keeps the selected item highlighted when reopening after a touch-driven mouseleave', async () => {\n      await render(\n        <Select.Root>\n          <Select.Trigger>\n            <Select.Value />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"a\">a</Select.Item>\n                <Select.Item value=\"b\">b</Select.Item>\n                <Select.Item value=\"c\">c</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      const trigger = screen.getByRole('combobox');\n\n      function fireTouchPress(element: HTMLElement) {\n        fireEvent.pointerDown(element, { pointerType: 'touch' });\n        fireEvent.mouseDown(element);\n      }\n\n      fireTouchPress(trigger);\n\n      await waitFor(() => {\n        expect(screen.getByRole('listbox')).toBeInTheDocument();\n      });\n\n      const optionB = screen.getByRole('option', { name: 'b' });\n      fireEvent.pointerDown(optionB, { pointerType: 'touch' });\n      fireEvent.click(optionB);\n      fireEvent.mouseLeave(optionB, { clientX: -1, clientY: -1 });\n\n      fireTouchPress(trigger);\n\n      await waitFor(() => {\n        expect(screen.getByRole('option', { name: 'b' })).toHaveAttribute('data-highlighted');\n      });\n    });\n\n    it('recomputes positioning before the popup becomes visible again after touch dismiss', async ({\n      onTestFinished,\n    }) => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n      onTestFinished(() => {\n        globalThis.BASE_UI_ANIMATIONS_DISABLED = true;\n      });\n\n      const onOpenChangeComplete = vi.fn();\n      const items = Array.from({ length: 80 }, (_, index) => `Item ${index + 1}`);\n      const style = `\n        @keyframes select-reopen-test {\n          to {\n            opacity: 0;\n            transform: scale(0.9);\n          }\n        }\n\n        .reopen-test-popup {\n          width: 120px;\n          transition:\n            transform 150ms,\n            opacity 150ms;\n        }\n\n        .reopen-test-popup[data-starting-style],\n        .reopen-test-popup[data-ending-style] {\n          animation: select-reopen-test 20ms linear;\n        }\n\n        .reopen-test-list {\n          max-height: var(--available-height);\n          overflow-y: auto;\n        }\n      `;\n\n      function Test() {\n        const [open, setOpen] = React.useState(false);\n        const [paddingTop, setPaddingTop] = React.useState(0);\n        const triggerRef = React.useRef<HTMLButtonElement | null>(null);\n\n        React.useLayoutEffect(() => {\n          const trigger = triggerRef.current;\n          if (!trigger) {\n            return;\n          }\n\n          const gap =\n            document.documentElement.clientHeight - trigger.getBoundingClientRect().bottom;\n          if (Math.abs(gap - 100) <= 1) {\n            return;\n          }\n\n          setPaddingTop((prev) => prev + gap - 100);\n        }, [paddingTop]);\n\n        return (\n          <div style={{ paddingTop }}>\n            {/* eslint-disable-next-line react/no-danger */}\n            <style dangerouslySetInnerHTML={{ __html: style }} />\n            <button data-testid=\"outside\">Outside</button>\n            <Select.Root\n              open={open}\n              onOpenChange={setOpen}\n              onOpenChangeComplete={onOpenChangeComplete}\n            >\n              <Select.Trigger ref={triggerRef}>Open</Select.Trigger>\n              <Select.Portal>\n                <Select.Positioner data-testid=\"positioner\" sideOffset={8}>\n                  <Select.Popup className=\"reopen-test-popup\">\n                    <Select.ScrollUpArrow />\n                    <Select.Arrow />\n                    <Select.List className=\"reopen-test-list\">\n                      <div aria-hidden style={{ height: 75 }}>\n                        Start\n                      </div>\n                      {items.map((item) => (\n                        <Select.Item key={item} value={item}>\n                          {item}\n                        </Select.Item>\n                      ))}\n                      <div aria-hidden style={{ height: 75 }}>\n                        End\n                      </div>\n                    </Select.List>\n                    <Select.ScrollDownArrow />\n                  </Select.Popup>\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<Test />);\n\n      const trigger = screen.getByRole('combobox');\n      const outside = screen.getByTestId('outside');\n\n      await waitFor(() => {\n        const gap = document.documentElement.clientHeight - trigger.getBoundingClientRect().bottom;\n        expect(Math.abs(gap - 100)).toBeLessThanOrEqual(1);\n      });\n\n      function fireTouchPress() {\n        fireEvent.pointerDown(trigger, { pointerType: 'touch' });\n        fireEvent.mouseDown(trigger);\n      }\n\n      fireTouchPress();\n\n      await waitFor(() => {\n        expect(screen.queryByRole('listbox')).not.toBe(null);\n      });\n\n      const initialPositioner = screen.getByTestId('positioner');\n\n      expect(initialPositioner).toHaveAttribute('data-side', 'top');\n\n      fireEvent.pointerDown(outside, { pointerType: 'touch' });\n      fireEvent.mouseDown(outside);\n\n      await waitFor(() => {\n        expect(trigger).toHaveAttribute('aria-expanded', 'false');\n        expect(onOpenChangeComplete.mock.calls.some(([value]) => value === false)).toBe(true);\n        expect(screen.getByTestId('positioner').style.opacity).toBe('0');\n      });\n\n      fireTouchPress();\n\n      await waitFor(() => {\n        expect(screen.getByTestId('positioner').style.opacity).not.toBe('0');\n      });\n\n      const reopenedPositioner = screen.getByTestId('positioner');\n      const reopenedList = screen.getByRole('listbox');\n      expect(reopenedPositioner).toHaveAttribute('data-side', 'top');\n      expect(reopenedList.getBoundingClientRect().height).toBeGreaterThan(200);\n\n      await user.click(outside);\n    });\n  });\n\n  describe('prop: actionsRef', () => {\n    it('unmounts the select when the `unmount` method is called', async () => {\n      const actionsRef = {\n        current: {\n          unmount: vi.fn(),\n        },\n      };\n\n      const { user } = await render(\n        <Select.Root actionsRef={actionsRef}>\n          <Select.Trigger data-testid=\"trigger\">Open</Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item>1</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      await user.click(trigger);\n\n      await waitFor(() => {\n        expect(screen.queryByRole('listbox')).not.toBe(null);\n      });\n\n      await user.click(trigger);\n\n      await waitFor(() => {\n        expect(screen.queryByRole('listbox')).not.toBe(null);\n      });\n\n      await act(async () => {\n        await new Promise((resolve) => {\n          requestAnimationFrame(resolve);\n        });\n        actionsRef.current.unmount();\n      });\n\n      await waitFor(() => {\n        expect(screen.queryByRole('listbox')).toBe(null);\n      });\n    });\n  });\n\n  describe.skipIf(isJSDOM)('select inside popover', () => {\n    it('keeps the popover open when selecting via drag-to-select', async () => {\n      ignoreActWarnings();\n\n      function Test() {\n        const [value, setValue] = React.useState<string | null>('one');\n        return (\n          <div>\n            <span data-testid=\"selected-value\">{value}</span>\n            <Popover.Root defaultOpen>\n              <Popover.Trigger>Open popover</Popover.Trigger>\n              <Popover.Portal>\n                <Popover.Positioner>\n                  <Popover.Popup data-testid=\"popover-popup\">\n                    <Select.Root value={value} onValueChange={setValue}>\n                      <Select.Trigger data-testid=\"select-trigger\">\n                        <Select.Value placeholder=\"Pick one\" />\n                      </Select.Trigger>\n                      <Select.Portal>\n                        <Select.Positioner>\n                          <Select.Popup>\n                            <Select.Item value=\"one\">One</Select.Item>\n                            <Select.Item value=\"two\">Two</Select.Item>\n                          </Select.Popup>\n                        </Select.Positioner>\n                      </Select.Portal>\n                    </Select.Root>\n                  </Popover.Popup>\n                </Popover.Positioner>\n              </Popover.Portal>\n            </Popover.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<Test />);\n\n      const selectTrigger = screen.getByTestId('select-trigger');\n\n      await user.pointer({ keys: '[MouseLeft>]', target: selectTrigger });\n\n      const option = await screen.findByRole('option', { name: 'Two' });\n      await user.pointer({ target: option });\n      await wait(500);\n      await user.pointer({ keys: '[/MouseLeft]', target: option });\n\n      await waitFor(() => expect(screen.getByTestId('selected-value')).toHaveTextContent('two'));\n      await waitFor(() => expect(screen.queryByTestId('popover-popup')).not.toBe(null));\n    });\n  });\n\n  describe.skipIf(isJSDOM)('prop: onOpenChangeComplete', () => {\n    it('is called on close when there is no exit animation defined', async () => {\n      const onOpenChangeComplete = vi.fn();\n\n      function Test() {\n        const [open, setOpen] = React.useState(true);\n        return (\n          <div>\n            <button onClick={() => setOpen(false)}>Close</button>\n            <Select.Root open={open} onOpenChangeComplete={onOpenChangeComplete}>\n              <Select.Portal>\n                <Select.Positioner>\n                  <Select.Popup data-testid=\"popup\" />\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<Test />);\n\n      const closeButton = screen.getByText('Close');\n      await user.click(closeButton);\n\n      await waitFor(() => {\n        expect(screen.queryByRole('listbox')).toBe(null);\n      });\n\n      expect(onOpenChangeComplete.mock.calls[0][0]).toBe(true);\n      expect(onOpenChangeComplete.mock.lastCall?.[0]).toBe(false);\n    });\n\n    it('is called on close when the exit animation finishes', async () => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n      const onOpenChangeComplete = vi.fn();\n\n      function Test() {\n        const style = `\n          @keyframes test-anim {\n            to {\n              opacity: 0;\n            }\n          }\n\n          .animation-test-indicator[data-ending-style] {\n            animation: test-anim 1ms;\n          }\n        `;\n\n        const [open, setOpen] = React.useState(true);\n\n        return (\n          <div>\n            {/* eslint-disable-next-line react/no-danger */}\n            <style dangerouslySetInnerHTML={{ __html: style }} />\n            <button onClick={() => setOpen(false)}>Close</button>\n            <Select.Root open={open} onOpenChangeComplete={onOpenChangeComplete}>\n              <Select.Portal>\n                <Select.Positioner>\n                  <Select.Popup className=\"animation-test-indicator\" data-testid=\"popup\" />\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<Test />);\n\n      expect(screen.queryByRole('listbox')).not.toBe(null);\n\n      // Wait for open animation to finish\n      await waitFor(() => {\n        expect(onOpenChangeComplete.mock.calls[0][0]).toBe(true);\n      });\n\n      const closeButton = screen.getByText('Close');\n      await user.click(closeButton);\n\n      await waitFor(() => {\n        expect(screen.queryByRole('listbox')).toBe(null);\n      });\n\n      expect(onOpenChangeComplete.mock.lastCall?.[0]).toBe(false);\n    });\n\n    it('is called on open when there is no enter animation defined', async () => {\n      const onOpenChangeComplete = vi.fn();\n\n      function Test() {\n        const [open, setOpen] = React.useState(false);\n        return (\n          <div>\n            <button onClick={() => setOpen(true)}>Open</button>\n            <Select.Root open={open} onOpenChangeComplete={onOpenChangeComplete}>\n              <Select.Portal>\n                <Select.Positioner>\n                  <Select.Popup data-testid=\"popup\" />\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<Test />);\n\n      const openButton = screen.getByText('Open');\n      await user.click(openButton);\n\n      await waitFor(() => {\n        expect(screen.queryByRole('listbox')).not.toBe(null);\n      });\n\n      expect(onOpenChangeComplete.mock.calls.length).toBe(2); // 1 in browser\n      expect(onOpenChangeComplete.mock.calls[0][0]).toBe(true);\n    });\n\n    it('is called on open when the enter animation finishes', async () => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n      const onOpenChangeComplete = vi.fn();\n\n      function Test() {\n        const style = `\n          @keyframes test-anim {\n            from {\n              opacity: 0;\n            }\n          }\n\n          .animation-test-indicator[data-starting-style] {\n            animation: test-anim 1ms;\n          }\n        `;\n\n        const [open, setOpen] = React.useState(false);\n\n        return (\n          <div>\n            {/* eslint-disable-next-line react/no-danger */}\n            <style dangerouslySetInnerHTML={{ __html: style }} />\n            <button onClick={() => setOpen(true)}>Open</button>\n            <Select.Root\n              open={open}\n              onOpenChange={setOpen}\n              onOpenChangeComplete={onOpenChangeComplete}\n            >\n              <Select.Portal>\n                <Select.Positioner>\n                  <Select.Popup className=\"animation-test-indicator\" data-testid=\"popup\" />\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<Test />);\n\n      const openButton = screen.getByText('Open');\n      await user.click(openButton);\n\n      // Wait for open animation to finish\n      await waitFor(() => {\n        expect(onOpenChangeComplete.mock.calls[0][0]).toBe(true);\n      });\n\n      expect(screen.queryByRole('listbox')).not.toBe(null);\n    });\n\n    it('does not get called on mount when not open', async () => {\n      const onOpenChangeComplete = vi.fn();\n\n      await render(\n        <Select.Root onOpenChangeComplete={onOpenChangeComplete}>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup data-testid=\"popup\" />\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      expect(onOpenChangeComplete.mock.calls.length).toBe(0);\n    });\n  });\n\n  describe('prop: disabled', () => {\n    it('sets the disabled state', async () => {\n      const handleOpenChange = vi.fn();\n      const { user } = await render(\n        <Select.Root defaultValue=\"b\" onOpenChange={handleOpenChange} disabled>\n          <Select.Trigger>\n            <Select.Value />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"a\">a</Select.Item>\n                <Select.Item value=\"b\">b</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      const trigger = screen.getByRole('combobox');\n      expect(trigger).toHaveAttribute('disabled');\n      expect(trigger).toHaveAttribute('data-disabled');\n\n      await user.keyboard('[Tab]');\n\n      expect(expect(document.activeElement)).not.toBe(trigger);\n\n      await user.click(trigger);\n      expect(handleOpenChange.mock.calls.length).toBe(0);\n    });\n\n    it('updates the disabled state when the disabled prop changes', async () => {\n      const handleOpenChange = vi.fn();\n      function App() {\n        const [disabled, setDisabled] = React.useState(true);\n        return (\n          <React.Fragment>\n            <button onClick={() => setDisabled(!disabled)}>toggle</button>\n            <Select.Root defaultValue=\"b\" onOpenChange={handleOpenChange} disabled={disabled}>\n              <Select.Trigger>\n                <Select.Value />\n              </Select.Trigger>\n              <Select.Portal>\n                <Select.Positioner>\n                  <Select.Popup>\n                    <Select.Item value=\"a\">a</Select.Item>\n                    <Select.Item value=\"b\">b</Select.Item>\n                  </Select.Popup>\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n          </React.Fragment>\n        );\n      }\n      const { user } = await render(<App />);\n\n      const trigger = screen.getByRole('combobox');\n      expect(trigger).toHaveAttribute('disabled');\n      expect(trigger).toHaveAttribute('data-disabled');\n\n      await user.keyboard('[Tab]');\n\n      expect(expect(document.activeElement)).not.toBe(trigger);\n\n      await user.click(trigger);\n      expect(handleOpenChange.mock.calls.length).toBe(0);\n\n      await user.click(screen.getByRole('button', { name: 'toggle' }));\n\n      expect(trigger).not.toHaveAttribute('disabled');\n      expect(trigger).not.toHaveAttribute('data-disabled');\n\n      await user.keyboard('[Tab]');\n      expect(trigger).toHaveFocus();\n\n      await user.click(trigger);\n      await waitFor(() => {\n        expect(handleOpenChange.mock.calls.length).toBe(1);\n      });\n    });\n  });\n\n  describe('prop: readOnly', () => {\n    it('sets the readOnly state', async () => {\n      const handleOpenChange = vi.fn();\n      const { user } = await render(\n        <Select.Root defaultValue=\"b\" onOpenChange={handleOpenChange} readOnly>\n          <Select.Trigger>\n            <Select.Value />\n          </Select.Trigger>\n        </Select.Root>,\n      );\n\n      const trigger = screen.getByRole('combobox');\n      expect(trigger).toHaveAttribute('aria-readonly', 'true');\n      expect(trigger).toHaveAttribute('data-readonly');\n\n      await user.keyboard('[Tab]');\n      expect(trigger).toHaveFocus();\n\n      await user.click(trigger);\n      expect(handleOpenChange.mock.calls.length).toBe(0);\n    });\n\n    it('should not open the select when clicked', async () => {\n      const handleOpenChange = vi.fn();\n      const { user } = await render(\n        <Select.Root onOpenChange={handleOpenChange} readOnly>\n          <Select.Trigger>\n            <Select.Value />\n          </Select.Trigger>\n        </Select.Root>,\n      );\n\n      const trigger = screen.getByRole('combobox');\n\n      await user.click(trigger);\n      expect(screen.queryByRole('listbox')).toBe(null);\n      expect(handleOpenChange.mock.calls.length).toBe(0);\n    });\n\n    it('should not open the select when using keyboard', async () => {\n      const handleOpenChange = vi.fn();\n      const { user } = await render(\n        <Select.Root onOpenChange={handleOpenChange} readOnly>\n          <Select.Trigger>\n            <Select.Value />\n          </Select.Trigger>\n        </Select.Root>,\n      );\n\n      const trigger = screen.getByRole('combobox');\n\n      await act(async () => {\n        trigger.focus();\n      });\n\n      expect(screen.queryByRole('listbox')).toBe(null);\n      expect(document.activeElement).toBe(trigger);\n\n      await user.keyboard('[ArrowDown]');\n      expect(screen.queryByRole('listbox')).toBe(null);\n      expect(handleOpenChange.mock.calls.length).toBe(0);\n\n      await user.keyboard('[Enter]');\n      expect(screen.queryByRole('listbox')).toBe(null);\n      expect(handleOpenChange.mock.calls.length).toBe(0);\n\n      await user.keyboard('[Space]');\n      expect(screen.queryByRole('listbox')).toBe(null);\n      expect(handleOpenChange.mock.calls.length).toBe(0);\n    });\n  });\n\n  describe('prop: id', () => {\n    it('sets the id on the trigger', async () => {\n      await render(\n        <Select.Root id=\"test-id\">\n          <Select.Trigger>\n            <Select.Value />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"a\">a</Select.Item>\n                <Select.Item value=\"b\">b</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      const trigger = screen.getByRole('combobox');\n      expect(trigger).toHaveAttribute('id', 'test-id');\n    });\n\n    it('sets a hidden input id when name is not provided', async () => {\n      await render(\n        <Select.Root id=\"test-id\">\n          <Select.Trigger>\n            <Select.Value />\n          </Select.Trigger>\n        </Select.Root>,\n      );\n\n      const hiddenInput = screen.getByRole('textbox', { hidden: true });\n      expect(hiddenInput).toHaveAttribute('id', 'test-id-hidden-input');\n      expect(hiddenInput).not.toHaveAttribute('name');\n    });\n\n    it('does not set a hidden input id when name is provided', async () => {\n      await render(\n        <Select.Root id=\"test-id\" name=\"country\">\n          <Select.Trigger>\n            <Select.Value />\n          </Select.Trigger>\n        </Select.Root>,\n      );\n\n      const hiddenInput = screen.getByRole('textbox', { hidden: true });\n      expect(hiddenInput).toHaveAttribute('name', 'country');\n      expect(hiddenInput).not.toHaveAttribute('id');\n    });\n  });\n\n  describe('with Field.Root parent', () => {\n    it('should receive disabled prop from Field.Root', async () => {\n      await render(\n        <Field.Root disabled>\n          <Select.Root>\n            <Select.Trigger data-testid=\"trigger\">\n              <Select.Value />\n            </Select.Trigger>\n            <Select.Portal>\n              <Select.Positioner>\n                <Select.Popup>\n                  <Select.Item value=\"a\">a</Select.Item>\n                  <Select.Item value=\"b\">b</Select.Item>\n                </Select.Popup>\n              </Select.Positioner>\n            </Select.Portal>\n          </Select.Root>\n        </Field.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      expect(trigger).toHaveAttribute('disabled');\n    });\n\n    it('should receive name prop from Field.Root', async () => {\n      await render(\n        <Field.Root name=\"field-select\">\n          <Select.Root>\n            <Select.Trigger data-testid=\"trigger\">\n              <Select.Value />\n            </Select.Trigger>\n            <Select.Portal>\n              <Select.Positioner>\n                <Select.Popup>\n                  <Select.Item value=\"a\">a</Select.Item>\n                  <Select.Item value=\"b\">b</Select.Item>\n                </Select.Popup>\n              </Select.Positioner>\n            </Select.Portal>\n          </Select.Root>\n        </Field.Root>,\n      );\n\n      const hiddenInput = screen.getByRole('textbox', { hidden: true });\n      expect(hiddenInput).toHaveAttribute('name', 'field-select');\n    });\n  });\n\n  it('resets selected index when value is set to null without a null item', async () => {\n    function App() {\n      const [value, setValue] = React.useState<string | null>(null);\n      return (\n        <div>\n          <button onClick={() => setValue('1')}>1</button>\n          <button onClick={() => setValue('2')}>2</button>\n          <button onClick={() => setValue(null)}>null</button>\n          <Select.Root value={value} onValueChange={setValue}>\n            <Select.Trigger data-testid=\"trigger\">\n              <Select.Value data-testid=\"value\">{(val) => val ?? 'initial'}</Select.Value>\n            </Select.Trigger>\n            <Select.Portal>\n              <Select.Positioner>\n                <Select.Popup>\n                  <Select.Item value=\"1\">1</Select.Item>\n                  <Select.Item value=\"2\">2</Select.Item>\n                </Select.Popup>\n              </Select.Positioner>\n            </Select.Portal>\n          </Select.Root>\n        </div>\n      );\n    }\n\n    const { user } = await render(<App />);\n\n    await user.click(screen.getByText('initial'));\n\n    await user.click(screen.getByRole('button', { name: '1' }));\n    expect(screen.getByTestId('value')).toHaveTextContent('1');\n\n    await user.click(screen.getByRole('button', { name: '2' }));\n    expect(screen.getByTestId('value')).toHaveTextContent('2');\n\n    await user.click(screen.getByRole('button', { name: 'null' }));\n    expect(screen.getByTestId('value')).toHaveTextContent('initial');\n\n    await user.click(screen.getByTestId('trigger'));\n    await waitFor(() => {\n      expect(screen.queryByRole('option', { name: '2' })).not.toHaveAttribute('data-selected', '');\n    });\n  });\n\n  describe('Form', () => {\n    const { render: renderFakeTimers, clock } = createRenderer({\n      clockOptions: {\n        shouldAdvanceTime: true,\n      },\n    });\n\n    clock.withFakeTimers();\n\n    it('submits stringified value to onFormSubmit when itemToStringValue is provided', async () => {\n      const items = [\n        { code: 'US', label: 'United States' },\n        { code: 'CA', label: 'Canada' },\n      ];\n      const handleFormSubmit = vi.fn();\n\n      const { user } = await renderFakeTimers(\n        <Form onFormSubmit={handleFormSubmit}>\n          <Field.Root name=\"country\">\n            <Select.Root\n              defaultValue={items[0]}\n              itemToStringLabel={(item) => item.label}\n              itemToStringValue={(item) => item.code}\n            >\n              <Select.Trigger>\n                <Select.Value />\n              </Select.Trigger>\n              <Select.Portal>\n                <Select.Positioner>\n                  <Select.Popup>\n                    <Select.Item value={items[0]}>United States</Select.Item>\n                    <Select.Item value={items[1]}>Canada</Select.Item>\n                  </Select.Popup>\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n          </Field.Root>\n          <button type=\"submit\">Submit</button>\n        </Form>,\n      );\n\n      await user.click(screen.getByText('Submit'));\n\n      expect(handleFormSubmit.mock.calls.length).toBe(1);\n      expect(handleFormSubmit.mock.calls[0][0]).toEqual({ country: 'US' });\n    });\n\n    it('triggers native HTML validation on submit', async () => {\n      const { user } = await render(\n        <Form>\n          <Field.Root name=\"test\" data-testid=\"field\">\n            <Select.Root required>\n              <Select.Trigger data-testid=\"trigger\">\n                <Select.Value />\n              </Select.Trigger>\n              <Select.Portal>\n                <Select.Positioner />\n              </Select.Portal>\n            </Select.Root>\n            <Field.Error match=\"valueMissing\" data-testid=\"error\">\n              required\n            </Field.Error>\n          </Field.Root>\n          <button type=\"submit\">Submit</button>\n        </Form>,\n      );\n\n      const submit = screen.getByText('Submit');\n\n      expect(screen.queryByTestId('error')).toBe(null);\n\n      await user.click(submit);\n\n      const error = screen.getByTestId('error');\n      expect(error).toHaveTextContent('required');\n    });\n\n    it('clears external errors on change', async () => {\n      ignoreActWarnings();\n\n      const { user } = await renderFakeTimers(\n        <Form\n          errors={{\n            select: 'test',\n          }}\n        >\n          <Field.Root name=\"select\">\n            <Select.Root>\n              <Select.Trigger data-testid=\"trigger\">\n                <Select.Value />\n              </Select.Trigger>\n              <Select.Portal>\n                <Select.Positioner>\n                  <Select.Popup>\n                    <Select.Item value=\"a\">a</Select.Item>\n                    <Select.Item value=\"b\">b</Select.Item>\n                  </Select.Popup>\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n            <Field.Error data-testid=\"error\" />\n          </Field.Root>\n        </Form>,\n      );\n\n      expect(screen.getByTestId('error')).toHaveTextContent('test');\n\n      const trigger = screen.getByTestId('trigger');\n      expect(trigger).toHaveAttribute('aria-invalid', 'true');\n\n      await user.click(trigger);\n      await flushMicrotasks();\n\n      const option = screen.getByRole('option', { name: 'b' });\n      await clock.tickAsync(200);\n      await user.click(option);\n\n      expect(screen.queryByTestId('error')).toBe(null);\n      expect(trigger).not.toHaveAttribute('aria-invalid');\n    });\n\n    it('revalidates immediately after form submission errors', async () => {\n      ignoreActWarnings();\n\n      const { user } = await renderFakeTimers(\n        <Form>\n          <Field.Root name=\"select\">\n            <Select.Root required>\n              <Select.Trigger data-testid=\"trigger\">\n                <Select.Value />\n              </Select.Trigger>\n              <Select.Portal>\n                <Select.Positioner>\n                  <Select.Popup>\n                    <Select.Item value=\"a\">a</Select.Item>\n                    <Select.Item value=\"b\">b</Select.Item>\n                  </Select.Popup>\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n            <Field.Error match=\"valueMissing\" data-testid=\"error\">\n              required\n            </Field.Error>\n          </Field.Root>\n          <button type=\"submit\" data-testid=\"submit\">\n            Submit\n          </button>\n        </Form>,\n      );\n\n      const submit = screen.getByTestId('submit');\n      await user.click(submit);\n\n      expect(screen.getByTestId('error')).toHaveTextContent('required');\n      const trigger = screen.getByTestId('trigger');\n      expect(trigger).toHaveAttribute('aria-invalid', 'true');\n\n      await user.click(trigger);\n      await flushMicrotasks();\n      await clock.tickAsync(200);\n      await user.click(screen.getByRole('option', { name: 'b' }));\n\n      expect(screen.queryByTestId('error')).toBe(null);\n      expect(trigger).not.toHaveAttribute('aria-invalid');\n    });\n  });\n\n  describe('Field', () => {\n    const { render: renderFakeTimers, clock } = createRenderer({\n      clockOptions: {\n        shouldAdvanceTime: true,\n      },\n    });\n\n    clock.withFakeTimers();\n\n    it('[data-touched]', async () => {\n      await render(\n        <Field.Root>\n          <Select.Root>\n            <Select.Trigger data-testid=\"trigger\" />\n            <Select.Portal>\n              <Select.Positioner>\n                <Select.Popup>\n                  <Select.Item value=\"\">Select</Select.Item>\n                  <Select.Item value=\"1\">Option 1</Select.Item>\n                </Select.Popup>\n              </Select.Positioner>\n            </Select.Portal>\n          </Select.Root>\n        </Field.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n\n      expect(trigger).not.toHaveAttribute('data-dirty');\n\n      fireEvent.focus(trigger);\n      fireEvent.blur(trigger);\n\n      await flushMicrotasks();\n\n      expect(trigger).toHaveAttribute('data-touched', '');\n    });\n\n    it('[data-dirty]', async () => {\n      ignoreActWarnings();\n\n      const { user } = await renderFakeTimers(\n        <Field.Root>\n          <Select.Root>\n            <Select.Trigger data-testid=\"trigger\" />\n            <Select.Portal>\n              <Select.Positioner>\n                <Select.Popup>\n                  <Select.Item value=\"\">Select</Select.Item>\n                  <Select.Item value=\"1\">Option 1</Select.Item>\n                </Select.Popup>\n              </Select.Positioner>\n            </Select.Portal>\n          </Select.Root>\n        </Field.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n\n      expect(trigger).not.toHaveAttribute('data-dirty');\n\n      await user.click(trigger);\n      await flushMicrotasks();\n      await clock.tickAsync(200);\n\n      const option = screen.getByRole('option', { name: 'Option 1' });\n\n      // Arrow Down to focus the Option 1\n      await user.keyboard('{ArrowDown}');\n      await user.click(option);\n      await flushMicrotasks();\n\n      expect(trigger).toHaveAttribute('data-dirty', '');\n    });\n\n    describe('[data-filled]', () => {\n      it('adds [data-filled] attribute when filled', async () => {\n        ignoreActWarnings();\n\n        const { user } = await renderFakeTimers(\n          <Field.Root>\n            <Select.Root>\n              <Select.Trigger data-testid=\"trigger\" />\n              <Select.Portal>\n                <Select.Positioner>\n                  <Select.Popup>\n                    <Select.Item value=\"\">Select</Select.Item>\n                    <Select.Item value=\"1\">Option 1</Select.Item>\n                  </Select.Popup>\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n          </Field.Root>,\n        );\n\n        const trigger = screen.getByTestId('trigger');\n\n        expect(trigger).not.toHaveAttribute('data-filled');\n\n        await user.click(trigger);\n        await flushMicrotasks();\n        await clock.tickAsync(200);\n\n        const option = screen.getByRole('option', { name: 'Option 1' });\n\n        // Arrow Down to focus the Option 1\n        await user.keyboard('{ArrowDown}');\n        await user.click(option);\n        await flushMicrotasks();\n\n        expect(trigger).toHaveAttribute('data-filled', '');\n\n        await user.click(trigger);\n\n        await flushMicrotasks();\n\n        const select = screen.getByRole('listbox');\n\n        expect(select).not.toHaveAttribute('data-filled');\n      });\n\n      it('adds [data-filled] attribute when already filled', async () => {\n        await render(\n          <Field.Root>\n            <Select.Root defaultValue=\"1\">\n              <Select.Trigger data-testid=\"trigger\" />\n              <Select.Portal>\n                <Select.Positioner>\n                  <Select.Popup>\n                    <Select.Item value=\"1\">Option 1</Select.Item>\n                  </Select.Popup>\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n          </Field.Root>,\n        );\n\n        const trigger = screen.getByTestId('trigger');\n\n        expect(trigger).toHaveAttribute('data-filled');\n      });\n\n      it('does not add [data-filled] attribute when multiple value is empty', async () => {\n        ignoreActWarnings();\n        const { user } = await renderFakeTimers(\n          <Field.Root>\n            <Select.Root multiple>\n              <Select.Trigger data-testid=\"trigger\" />\n              <Select.Portal>\n                <Select.Positioner>\n                  <Select.Popup>\n                    <Select.Item value=\"\">Select</Select.Item>\n                    <Select.Item value=\"1\">Option 1</Select.Item>\n                  </Select.Popup>\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n          </Field.Root>,\n        );\n\n        const trigger = screen.getByTestId('trigger');\n\n        expect(trigger).not.toHaveAttribute('data-filled');\n\n        await user.click(trigger);\n        await flushMicrotasks();\n        await clock.tickAsync(200);\n\n        const option = screen.getByRole('option', { name: 'Option 1' });\n\n        await user.click(option);\n        await flushMicrotasks();\n\n        expect(trigger).toHaveAttribute('data-filled', '');\n\n        await user.click(option);\n        await flushMicrotasks();\n\n        expect(trigger).not.toHaveAttribute('data-filled');\n      });\n\n      it('does not add [data-filled] attribute when multiple defaultValue is empty array', async () => {\n        ignoreActWarnings();\n\n        const { user } = await renderFakeTimers(\n          <Field.Root>\n            <Select.Root multiple defaultValue={[]}>\n              <Select.Trigger data-testid=\"trigger\" />\n              <Select.Portal>\n                <Select.Positioner>\n                  <Select.Popup>\n                    <Select.Item value=\"\">Select</Select.Item>\n                    <Select.Item value=\"1\">Option 1</Select.Item>\n                  </Select.Popup>\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n          </Field.Root>,\n        );\n\n        const trigger = screen.getByTestId('trigger');\n\n        expect(trigger).not.toHaveAttribute('data-filled');\n\n        await user.click(trigger);\n        await flushMicrotasks();\n        await clock.tickAsync(200);\n\n        const option = screen.getByRole('option', { name: 'Option 1' });\n\n        await user.click(option);\n        await flushMicrotasks();\n\n        expect(trigger).toHaveAttribute('data-filled', '');\n\n        await user.click(option);\n        await flushMicrotasks();\n\n        expect(trigger).not.toHaveAttribute('data-filled');\n      });\n    });\n\n    it('[data-focused]', async () => {\n      await render(\n        <Field.Root>\n          <Select.Root>\n            <Select.Trigger data-testid=\"trigger\" />\n            <Select.Portal>\n              <Select.Positioner>\n                <Select.Popup>\n                  <Select.Item value=\"\">Select</Select.Item>\n                  <Select.Item value=\"1\">Option 1</Select.Item>\n                </Select.Popup>\n              </Select.Positioner>\n            </Select.Portal>\n          </Select.Root>\n        </Field.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n\n      expect(trigger).not.toHaveAttribute('data-focused');\n\n      fireEvent.focus(trigger);\n\n      expect(trigger).toHaveAttribute('data-focused', '');\n\n      fireEvent.blur(trigger);\n\n      expect(trigger).not.toHaveAttribute('data-focused');\n    });\n\n    it('does not mark as touched when focus moves into the popup', async () => {\n      const validateSpy = vi.fn(() => 'error');\n\n      await render(\n        <React.Fragment>\n          <Field.Root validationMode=\"onBlur\" validate={validateSpy}>\n            <Select.Root>\n              <Select.Trigger data-testid=\"trigger\" />\n              <Select.Portal>\n                <Select.Positioner>\n                  <Select.Popup>\n                    <Select.Item value=\"1\">Option 1</Select.Item>\n                  </Select.Popup>\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n          </Field.Root>\n          <button data-testid=\"outside\">Outside</button>\n        </React.Fragment>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n\n      fireEvent.focus(trigger);\n      fireEvent.click(trigger);\n\n      await flushMicrotasks();\n\n      const listbox = screen.getByRole('listbox');\n\n      fireEvent.blur(trigger, { relatedTarget: listbox });\n      fireEvent.focus(listbox);\n\n      await flushMicrotasks();\n\n      expect(validateSpy.mock.calls.length).toBe(0);\n      expect(trigger).toHaveAttribute('data-focused', '');\n      expect(trigger).not.toHaveAttribute('data-touched');\n      expect(trigger).not.toHaveAttribute('aria-invalid');\n    });\n\n    it('validates when the popup is blurred', async () => {\n      const validateSpy = vi.fn(() => 'error');\n\n      await render(\n        <React.Fragment>\n          <Field.Root validationMode=\"onBlur\" validate={validateSpy}>\n            <Select.Root>\n              <Select.Trigger data-testid=\"trigger\" />\n              <Select.Portal>\n                <Select.Positioner>\n                  <Select.Popup>\n                    <Select.Item value=\"1\">Option 1</Select.Item>\n                  </Select.Popup>\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n          </Field.Root>\n          <button data-testid=\"outside\">Outside</button>\n        </React.Fragment>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      const outside = screen.getByTestId('outside');\n\n      fireEvent.focus(trigger);\n      fireEvent.click(trigger);\n\n      await flushMicrotasks();\n\n      const listbox = screen.getByRole('listbox');\n\n      fireEvent.blur(trigger, { relatedTarget: listbox });\n      fireEvent.focus(listbox);\n\n      fireEvent.blur(listbox, { relatedTarget: outside });\n      fireEvent.focus(outside);\n\n      await waitFor(() => {\n        expect(validateSpy.mock.calls.length).toBe(1);\n      });\n\n      // The above `waitFor` might not ensure re-render has finished\n      await waitFor(() => {\n        expect(trigger).toHaveAttribute('data-touched', '');\n      });\n      expect(trigger).not.toHaveAttribute('data-focused');\n      expect(trigger).toHaveAttribute('aria-invalid', 'true');\n    });\n\n    it('prop: validate', async () => {\n      await render(\n        <Field.Root validationMode=\"onBlur\" validate={() => 'error'}>\n          <Select.Root>\n            <Select.Trigger data-testid=\"trigger\" />\n            <Select.Portal>\n              <Select.Positioner />\n            </Select.Portal>\n          </Select.Root>\n        </Field.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n\n      expect(trigger).not.toHaveAttribute('aria-invalid');\n\n      fireEvent.focus(trigger);\n      fireEvent.blur(trigger);\n\n      await flushMicrotasks();\n\n      expect(trigger).toHaveAttribute('aria-invalid', 'true');\n    });\n\n    it('passes raw value to validate when itemToStringValue is provided', async () => {\n      const items = [\n        { code: 'US', label: 'United States' },\n        { code: 'CA', label: 'Canada' },\n      ];\n      const validateSpy = vi.fn((value: unknown) => {\n        expect(value).toBe(items[0]);\n        return 'error';\n      });\n\n      await render(\n        <Field.Root validationMode=\"onBlur\" validate={validateSpy}>\n          <Select.Root\n            defaultValue={items[0]}\n            itemToStringLabel={(item) => item.label}\n            itemToStringValue={(item) => item.code}\n          >\n            <Select.Trigger data-testid=\"trigger\">\n              <Select.Value />\n            </Select.Trigger>\n            <Select.Portal>\n              <Select.Positioner />\n            </Select.Portal>\n          </Select.Root>\n        </Field.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n\n      fireEvent.focus(trigger);\n      fireEvent.blur(trigger);\n\n      await waitFor(() => {\n        expect(validateSpy.mock.calls.length).toBe(1);\n      });\n      expect(trigger).toHaveAttribute('aria-invalid', 'true');\n    });\n\n    it('prop: validateMode=onSubmit', async () => {\n      ignoreActWarnings();\n\n      const { user } = await render(\n        <Form>\n          <Field.Root validate={(val) => (val === '2' ? 'error' : null)}>\n            <Select.Root required>\n              <Select.Trigger />\n              <Select.Portal>\n                <Select.Positioner>\n                  <Select.Popup>\n                    <Select.Item value=\"1\">Option 1</Select.Item>\n                    <Select.Item value=\"2\">Option 2</Select.Item>\n                  </Select.Popup>\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n          </Field.Root>\n          <button type=\"submit\">submit</button>\n        </Form>,\n      );\n\n      const trigger = screen.getByRole('combobox');\n      expect(trigger).not.toHaveAttribute('aria-invalid');\n\n      await user.click(screen.getByText('submit'));\n      expect(trigger).toHaveAttribute('aria-invalid', 'true');\n\n      // Arrow Down to focus Option 1 (valid)\n      await user.keyboard('{ArrowDown}');\n      await user.keyboard('{Enter}');\n      expect(trigger).not.toHaveAttribute('aria-invalid');\n\n      await user.click(trigger);\n      // Arrow Down to focus Option 2 (invalid)\n      await user.keyboard('{ArrowDown}');\n      await user.keyboard('{Enter}');\n      expect(trigger).toHaveAttribute('aria-invalid', 'true');\n\n      await user.click(trigger);\n      // Arrow Down to focus Option 1 (valid)\n      await user.keyboard('{ArrowUp}');\n      await user.keyboard('{Enter}');\n      await flushMicrotasks();\n      expect(trigger).not.toHaveAttribute('aria-invalid');\n    });\n\n    // flaky in real browser\n    it.skipIf(!isJSDOM)('prop: validationMode=onChange', async () => {\n      ignoreActWarnings();\n      const { user } = await render(\n        <Field.Root\n          validationMode=\"onChange\"\n          validate={(value) => {\n            return value === '1' ? 'error' : null;\n          }}\n        >\n          <Select.Root>\n            <Select.Trigger data-testid=\"trigger\">\n              <Select.Value />\n            </Select.Trigger>\n            <Select.Portal>\n              <Select.Positioner>\n                <Select.Popup>\n                  <Select.Item value=\"1\">Option 1</Select.Item>\n                </Select.Popup>\n              </Select.Positioner>\n            </Select.Portal>\n          </Select.Root>\n        </Field.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n\n      expect(trigger).not.toHaveAttribute('aria-invalid');\n\n      await user.click(trigger);\n\n      await flushMicrotasks();\n\n      // Arrow Down to focus the Option 1\n      await user.keyboard('{ArrowDown}');\n      await user.keyboard('{Enter}');\n\n      expect(trigger).toHaveAttribute('aria-invalid', 'true');\n    });\n\n    it('revalidates when the controlled value changes externally', async () => {\n      const validateSpy = vi.fn((value: unknown) => ((value as string) === 'b' ? 'error' : null));\n\n      function App() {\n        const [value, setValue] = React.useState('a');\n\n        return (\n          <React.Fragment>\n            <Field.Root validationMode=\"onChange\" validate={validateSpy} name=\"flavor\">\n              <Select.Root value={value} onValueChange={(next) => setValue(next as string)}>\n                <Select.Trigger data-testid=\"trigger\">\n                  <Select.Value />\n                </Select.Trigger>\n                <Select.Portal>\n                  <Select.Positioner>\n                    <Select.Popup>\n                      <Select.Item value=\"a\">Option A</Select.Item>\n                      <Select.Item value=\"b\">Option B</Select.Item>\n                    </Select.Popup>\n                  </Select.Positioner>\n                </Select.Portal>\n              </Select.Root>\n            </Field.Root>\n            <button type=\"button\" onClick={() => setValue('b')}>\n              Select externally\n            </button>\n          </React.Fragment>\n        );\n      }\n\n      await render(<App />);\n\n      const trigger = screen.getByTestId('trigger');\n      const toggle = screen.getByText('Select externally');\n\n      expect(trigger).not.toHaveAttribute('aria-invalid');\n      const initialCallCount = validateSpy.mock.calls.length;\n\n      fireEvent.click(toggle);\n      await flushMicrotasks();\n\n      expect(validateSpy.mock.calls.length).toBe(initialCallCount + 1);\n      expect(validateSpy.mock.lastCall?.[0]).toBe('b');\n      expect(trigger).toHaveAttribute('aria-invalid', 'true');\n    });\n\n    // flaky in real browser\n    it.skipIf(!isJSDOM)('prop: validationMode=onBlur', async () => {\n      ignoreActWarnings();\n      const { user } = await render(\n        <Field.Root\n          validationMode=\"onBlur\"\n          validate={(value) => {\n            return value === '1' ? 'error' : null;\n          }}\n        >\n          <Select.Root>\n            <Select.Trigger data-testid=\"trigger\">\n              <Select.Value />\n            </Select.Trigger>\n            <Select.Portal>\n              <Select.Positioner>\n                <Select.Popup>\n                  <Select.Item value=\"1\">Option 1</Select.Item>\n                </Select.Popup>\n              </Select.Positioner>\n            </Select.Portal>\n          </Select.Root>\n          <Field.Error data-testid=\"error\" />\n        </Field.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n\n      expect(trigger).not.toHaveAttribute('aria-invalid');\n\n      await user.click(trigger);\n\n      await flushMicrotasks();\n\n      // Arrow Down to focus the Option 1\n      await user.keyboard('{ArrowDown}');\n      await user.keyboard('{Enter}');\n\n      fireEvent.blur(trigger);\n\n      await flushMicrotasks();\n\n      await waitFor(() => {\n        expect(trigger).toHaveAttribute('aria-invalid', 'true');\n      });\n    });\n\n    it('Field.Label', async () => {\n      await render(\n        <Field.Root>\n          <Select.Root>\n            <Select.Trigger data-testid=\"trigger\" />\n            <Select.Portal>\n              <Select.Positioner />\n            </Select.Portal>\n          </Select.Root>\n          <Field.Label data-testid=\"label\" nativeLabel={false} render={<span />} />\n        </Field.Root>,\n      );\n\n      expect(screen.getByTestId('trigger')).toHaveAttribute(\n        'aria-labelledby',\n        screen.getByTestId('label').id,\n      );\n    });\n\n    it('Select.Label', async () => {\n      await render(\n        <Select.Root>\n          <Select.Label data-testid=\"label\" />\n          <Select.Trigger data-testid=\"trigger\" />\n          <Select.Portal>\n            <Select.Positioner />\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      expect(screen.getByTestId('trigger')).toHaveAttribute(\n        'aria-labelledby',\n        screen.getByTestId('label').id,\n      );\n    });\n\n    it('does not set fallback aria-labelledby when no label is rendered', async () => {\n      await render(\n        <Select.Root>\n          <Select.Trigger data-testid=\"trigger\" aria-label=\"Font\" />\n          <Select.Portal>\n            <Select.Positioner />\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      await waitFor(() => {\n        expect(screen.getByTestId('trigger')).not.toHaveAttribute('aria-labelledby');\n      });\n    });\n\n    it('updates Select.Label linkage when root id changes', async () => {\n      const { setProps } = await render(\n        <Select.Root id=\"first\">\n          <Select.Label data-testid=\"label\">Theme</Select.Label>\n          <Select.Trigger data-testid=\"trigger\" />\n          <Select.Portal>\n            <Select.Positioner />\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      await setProps({ id: 'second' });\n\n      /* eslint-disable testing-library/no-wait-for-multiple-assertions */\n      await waitFor(() => {\n        const label = screen.getByTestId('label');\n        const trigger = screen.getByTestId('trigger');\n        expect(trigger).toHaveAttribute('id', 'second');\n        expect(label.id).toBe('second-label');\n        expect(trigger).toHaveAttribute('aria-labelledby', label.id);\n      });\n      /* eslint-enable testing-library/no-wait-for-multiple-assertions */\n    });\n\n    it('Select.Label focuses trigger without opening', async () => {\n      const { user } = await render(\n        <Select.Root>\n          <Select.Label data-testid=\"label\">Font</Select.Label>\n          <Select.Trigger data-testid=\"trigger\">\n            <Select.Value />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"sans\">Sans-serif</Select.Item>\n                <Select.Item value=\"serif\">Serif</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      await user.click(screen.getByTestId('label'));\n\n      expect(screen.getByTestId('trigger')).toHaveFocus();\n      expect(screen.queryByRole('listbox')).toBe(null);\n    });\n\n    it('Field.Label links to trigger and focuses it', async () => {\n      const { user } = await render(\n        <Field.Root>\n          <Field.Label data-testid=\"label\">Font</Field.Label>\n          <Select.Root>\n            <Select.Trigger data-testid=\"trigger\">\n              <Select.Value />\n            </Select.Trigger>\n            <Select.Portal>\n              <Select.Positioner>\n                <Select.Popup>\n                  <Select.Item value=\"sans\">Sans-serif</Select.Item>\n                  <Select.Item value=\"serif\">Serif</Select.Item>\n                </Select.Popup>\n              </Select.Positioner>\n            </Select.Portal>\n          </Select.Root>\n        </Field.Root>,\n      );\n\n      const label = screen.getByTestId<HTMLLabelElement>('label');\n      const trigger = screen.getByTestId('trigger');\n\n      expect(label).toHaveAttribute('for', trigger.id);\n      expect(trigger).toHaveAttribute('id', label?.htmlFor);\n\n      await user.click(label);\n\n      expect(screen.getByRole('listbox')).toHaveFocus();\n    });\n\n    it('Field.Label links to trigger when trigger has an explicit id', async () => {\n      const { user } = await render(\n        <Field.Root>\n          <Field.Label data-testid=\"label\">Font</Field.Label>\n          <Select.Root>\n            <Select.Trigger data-testid=\"trigger\" id=\"x-id\">\n              <Select.Value />\n            </Select.Trigger>\n            <Select.Portal>\n              <Select.Positioner>\n                <Select.Popup>\n                  <Select.Item value=\"sans\">Sans-serif</Select.Item>\n                  <Select.Item value=\"serif\">Serif</Select.Item>\n                </Select.Popup>\n              </Select.Positioner>\n            </Select.Portal>\n          </Select.Root>\n        </Field.Root>,\n      );\n\n      const label = screen.getByTestId<HTMLLabelElement>('label');\n      const trigger = screen.getByTestId('trigger');\n\n      expect(trigger).toHaveAttribute('id', 'x-id');\n      expect(label).toHaveAttribute('for', 'x-id');\n      expect(trigger).toHaveAttribute('id', label?.htmlFor);\n\n      await user.click(label);\n\n      expect(screen.getByRole('listbox')).toHaveFocus();\n    });\n\n    it('Field.Description', async () => {\n      await render(\n        <Field.Root>\n          <Select.Root>\n            <Select.Trigger data-testid=\"trigger\" />\n            <Select.Portal>\n              <Select.Positioner />\n            </Select.Portal>\n          </Select.Root>\n          <Field.Description data-testid=\"description\" />\n        </Field.Root>,\n      );\n\n      expect(screen.getByTestId('trigger')).toHaveAttribute(\n        'aria-describedby',\n        screen.getByTestId('description').id,\n      );\n    });\n  });\n\n  describe('dynamic items', () => {\n    const { render: renderFakeTimers, clock } = createRenderer({\n      clockOptions: {\n        shouldAdvanceTime: true,\n      },\n    });\n\n    clock.withFakeTimers();\n\n    it('skips null items when navigating', async () => {\n      function DynamicMenu() {\n        const [itemsFiltered, setItemsFiltered] = React.useState(false);\n\n        return (\n          <Select.Root\n            onOpenChange={(newOpen) => {\n              if (newOpen) {\n                setTimeout(() => {\n                  setItemsFiltered(true);\n                }, 0);\n              }\n            }}\n            onOpenChangeComplete={(newOpen) => {\n              if (!newOpen) {\n                setItemsFiltered(false);\n              }\n            }}\n          >\n            <Select.Trigger>Toggle</Select.Trigger>\n            <Select.Portal>\n              <Select.Positioner>\n                <Select.Popup>\n                  <Select.Item>Add to Library</Select.Item>\n                  {!itemsFiltered && (\n                    <React.Fragment>\n                      <Select.Item>Add to Playlist</Select.Item>\n                      <Select.Item>Play Next</Select.Item>\n                      <Select.Item>Play Last</Select.Item>\n                    </React.Fragment>\n                  )}\n                  <Select.Item>Favorite</Select.Item>\n                  <Select.Item>Share</Select.Item>\n                </Select.Popup>\n              </Select.Positioner>\n            </Select.Portal>\n          </Select.Root>\n        );\n      }\n\n      const { user } = await renderFakeTimers(<DynamicMenu />);\n\n      const trigger = screen.getByText('Toggle');\n\n      await act(async () => {\n        trigger.focus();\n      });\n\n      await user.keyboard('{ArrowDown}');\n\n      await waitFor(() => {\n        expect(screen.queryByRole('listbox')).not.toBe(null);\n      });\n\n      await user.keyboard('{ArrowDown}');\n      await user.keyboard('{ArrowDown}'); // Share\n      await user.keyboard('{ArrowDown}'); // Share still\n\n      expect(screen.queryByRole('option', { name: 'Share' })).toHaveFocus();\n    });\n\n    it('unselects the selected item if removed', async () => {\n      function DynamicMenu() {\n        const [items, setItems] = React.useState(['a', 'b', 'c']);\n        const [selectedItem, setSelectedItem] = React.useState<string | null>('a');\n\n        return (\n          <div>\n            <button\n              onClick={() => {\n                setItems((prev) => prev.filter((item) => item !== 'a'));\n              }}\n            >\n              Remove\n            </button>\n\n            <button\n              onClick={() => {\n                setItems(['a', 'b', 'c']);\n              }}\n            >\n              Add\n            </button>\n            <div data-testid=\"value\">{selectedItem}</div>\n\n            <Select.Root value={selectedItem} onValueChange={setSelectedItem}>\n              <Select.Trigger>Toggle</Select.Trigger>\n              <Select.Portal>\n                <Select.Positioner>\n                  <Select.Popup>\n                    {items.map((item) => (\n                      <Select.Item key={item} value={item}>\n                        {item}\n                      </Select.Item>\n                    ))}\n                  </Select.Popup>\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n          </div>\n        );\n      }\n\n      const { user } = await renderFakeTimers(<DynamicMenu />);\n\n      const trigger = screen.getByText('Toggle');\n\n      await act(async () => {\n        trigger.focus();\n      });\n      await user.keyboard('{ArrowDown}');\n\n      expect(screen.queryByRole('option', { name: 'a' })).toHaveAttribute('data-selected');\n      expect(screen.getByTestId('value')).toHaveTextContent('a');\n\n      fireEvent.click(screen.getByText('Remove'));\n\n      expect(screen.queryByRole('option', { name: 'b' })).not.toHaveAttribute('data-selected');\n\n      fireEvent.click(screen.getByText('Add'));\n\n      expect(screen.queryByRole('option', { name: 'a' })).not.toHaveAttribute('data-selected');\n    });\n\n    it('resets to default when the selected item is removed from the list', async () => {\n      if (reactMajor <= 18) {\n        ignoreActWarnings();\n      }\n\n      function Test() {\n        const [items, setItems] = React.useState(['a', 'b', 'c']);\n        return (\n          <div>\n            <Select.Root defaultValue=\"b\">\n              <Select.Trigger data-testid=\"trigger\">\n                <Select.Value />\n              </Select.Trigger>\n              <Select.Portal>\n                <Select.Positioner>\n                  <Select.Popup>\n                    {items.map((it) => (\n                      <Select.Item key={it} value={it}>\n                        {it}\n                      </Select.Item>\n                    ))}\n                  </Select.Popup>\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n            <button\n              data-testid=\"remove-c\"\n              onClick={() => setItems((prev) => prev.filter((i) => i !== 'c'))}\n            >\n              Remove C\n            </button>\n          </div>\n        );\n      }\n\n      const { user } = await render(<Test />);\n\n      const trigger = screen.getByTestId('trigger');\n\n      await user.click(trigger);\n      await user.click(screen.getByRole('option', { name: 'c' }));\n\n      await user.click(screen.getByTestId('remove-c'));\n\n      await waitFor(() => {\n        expect(trigger).toHaveTextContent('b');\n      });\n\n      await user.click(trigger);\n      await waitFor(() => {\n        expect(screen.getByRole('option', { name: 'b' })).toHaveAttribute('data-selected', '');\n      });\n    });\n\n    it('resets via onValueChange and does not break in controlled mode when the selected item is removed', async () => {\n      if (reactMajor <= 18) {\n        ignoreActWarnings();\n      }\n\n      function TestControlled() {\n        const [items, setItems] = React.useState(['a', 'b', 'c']);\n        const [value, setValue] = React.useState<string | null>('c');\n        return (\n          <div>\n            <Select.Root value={value} onValueChange={setValue}>\n              <Select.Trigger data-testid=\"trigger\">\n                <Select.Value />\n              </Select.Trigger>\n              <Select.Portal>\n                <Select.Positioner>\n                  <Select.Popup>\n                    {items.map((it) => (\n                      <Select.Item key={it} value={it}>\n                        {it}\n                      </Select.Item>\n                    ))}\n                  </Select.Popup>\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n            <button\n              data-testid=\"remove-c\"\n              onClick={() => setItems((prev) => prev.filter((i) => i !== 'c'))}\n            >\n              Remove C\n            </button>\n          </div>\n        );\n      }\n\n      const { user } = await render(<TestControlled />);\n\n      const trigger = screen.getByTestId('trigger');\n      expect(trigger).toHaveTextContent('c');\n\n      await user.click(screen.getByTestId('remove-c'));\n\n      // Opening should not break; and no option is selected since the value is missing from list\n      await user.click(trigger);\n\n      await waitFor(() => {\n        expect(screen.getByRole('listbox')).toBeVisible();\n      });\n\n      const options = await screen.findAllByRole('option');\n      options.forEach((opt) => {\n        expect(opt).not.toHaveAttribute('data-selected');\n      });\n    });\n\n    it('falls back to null when both selected and initial default are removed (uncontrolled)', async () => {\n      if (reactMajor <= 18) {\n        ignoreActWarnings();\n      }\n\n      function Test() {\n        const [items, setItems] = React.useState(['a', 'b', 'c']);\n        return (\n          <div>\n            <Select.Root defaultValue=\"b\">\n              <Select.Trigger data-testid=\"trigger\">\n                <Select.Value />\n              </Select.Trigger>\n              <Select.Portal>\n                <Select.Positioner>\n                  <Select.Popup>\n                    {items.map((it) => (\n                      <Select.Item key={it} value={it}>\n                        {it}\n                      </Select.Item>\n                    ))}\n                  </Select.Popup>\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n            <button\n              data-testid=\"remove-b\"\n              onClick={() => setItems((prev) => prev.filter((i) => i !== 'b'))}\n            >\n              Remove B\n            </button>\n            <button\n              data-testid=\"remove-c\"\n              onClick={() => setItems((prev) => prev.filter((i) => i !== 'c'))}\n            >\n              Remove C\n            </button>\n          </div>\n        );\n      }\n\n      const { user } = await render(<Test />);\n\n      const trigger = screen.getByTestId('trigger');\n\n      await user.click(trigger);\n      await user.click(screen.getByRole('option', { name: 'c' }));\n\n      await user.click(screen.getByTestId('remove-b'));\n      await user.click(screen.getByTestId('remove-c'));\n\n      // Now no fallback remains; value should reset to null\n      await waitFor(() => {\n        expect(trigger).toHaveTextContent('');\n      });\n\n      await user.click(trigger);\n\n      const options = await screen.findAllByRole('option');\n      options.forEach((opt) => {\n        expect(opt).not.toHaveAttribute('data-selected');\n      });\n    });\n\n    it('falls back to null when both selected and initial default are removed (controlled)', async () => {\n      if (reactMajor <= 18) {\n        ignoreActWarnings();\n      }\n\n      function TestControlled() {\n        const [items, setItems] = React.useState(['a', 'b', 'c']);\n        const [value, setValue] = React.useState<string | null>('c');\n        return (\n          <div>\n            <Select.Root value={value} onValueChange={setValue}>\n              <Select.Trigger data-testid=\"trigger\">\n                <Select.Value />\n              </Select.Trigger>\n              <Select.Portal>\n                <Select.Positioner>\n                  <Select.Popup>\n                    {items.map((it) => (\n                      <Select.Item key={it} value={it}>\n                        {it}\n                      </Select.Item>\n                    ))}\n                  </Select.Popup>\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n            <button\n              data-testid=\"remove-b\"\n              onClick={() => setItems((prev) => prev.filter((i) => i !== 'b'))}\n            >\n              Remove B\n            </button>\n            <button\n              data-testid=\"remove-c\"\n              onClick={() => setItems((prev) => prev.filter((i) => i !== 'c'))}\n            >\n              Remove C\n            </button>\n          </div>\n        );\n      }\n\n      const { user } = await render(<TestControlled />);\n      const trigger = screen.getByTestId('trigger');\n\n      await user.click(screen.getByTestId('remove-b'));\n      await user.click(screen.getByTestId('remove-c'));\n\n      await user.click(trigger);\n\n      const options = await screen.findAllByRole('option');\n      options.forEach((opt) => {\n        expect(opt).not.toHaveAttribute('data-selected');\n      });\n    });\n  });\n\n  describe('typeahead', () => {\n    it.skipIf(isJSDOM)(\n      'does not trigger selection when Space is pressed during text navigation',\n      async () => {\n        const handleItemClick = vi.fn();\n        const handleValueChange = vi.fn();\n\n        const { user } = await render(\n          <Select.Root defaultOpen onValueChange={handleValueChange}>\n            <Select.Trigger data-testid=\"trigger\">\n              <Select.Value data-testid=\"value\" />\n            </Select.Trigger>\n            <Select.Portal>\n              <Select.Positioner>\n                <Select.Popup>\n                  <Select.Item value=\"one\" onClick={() => handleItemClick()}>\n                    Item One\n                  </Select.Item>\n                  <Select.Item value=\"two\" onClick={() => handleItemClick()}>\n                    Item Two\n                  </Select.Item>\n                </Select.Popup>\n              </Select.Positioner>\n            </Select.Portal>\n          </Select.Root>,\n        );\n\n        const options = screen.getAllByRole('option');\n\n        await act(async () => {\n          options[0].focus();\n        });\n\n        await user.keyboard('Item T');\n\n        expect(handleItemClick.mock.calls.length > 0).toBe(false);\n        expect(handleValueChange.mock.calls.length > 0).toBe(false);\n\n        await waitFor(() => {\n          expect(options[1]).toHaveFocus();\n        });\n      },\n    );\n\n    it('starts from the first match after value reset (closed)', async () => {\n      function App() {\n        const [value, setValue] = React.useState<string | null>(null);\n        return (\n          <React.Fragment>\n            <button data-testid=\"reset\" onClick={() => setValue(null)}>\n              Reset\n            </button>\n            <Select.Root value={value} onValueChange={setValue}>\n              <Select.Trigger data-testid=\"trigger\">\n                <Select.Value data-testid=\"value\" />\n              </Select.Trigger>\n              <Select.Portal>\n                <Select.Positioner>\n                  <Select.Popup>\n                    <Select.Item value=\"a1\">A1</Select.Item>\n                    <Select.Item value=\"a2\">A2</Select.Item>\n                  </Select.Popup>\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n          </React.Fragment>\n        );\n      }\n\n      const { user } = await render(<App />);\n\n      const trigger = screen.getByTestId('trigger');\n      const valueEl = screen.getByTestId('value');\n      const resetBtn = screen.getByTestId('reset');\n\n      await act(async () => trigger.focus());\n      await user.keyboard('a');\n      expect(valueEl.textContent).toBe('a1');\n\n      await user.click(resetBtn);\n\n      await act(async () => trigger.focus());\n      await user.keyboard('a');\n      expect(valueEl.textContent).toBe('a1');\n    });\n\n    it('does not jump matches after a closed-state value reset', async () => {\n      function App() {\n        const [value, setValue] = React.useState<string | null>('dog');\n        return (\n          <React.Fragment>\n            <button data-testid=\"set-car\" onClick={() => setValue('car')}>\n              Set car\n            </button>\n            <Select.Root value={value} onValueChange={setValue}>\n              <Select.Trigger data-testid=\"trigger\">\n                <Select.Value data-testid=\"value\" />\n              </Select.Trigger>\n              <Select.Portal>\n                <Select.Positioner>\n                  <Select.Popup>\n                    <Select.Item value=\"car\">car</Select.Item>\n                    <Select.Item value=\"cat\">cat</Select.Item>\n                    <Select.Item value=\"dog\">dog</Select.Item>\n                  </Select.Popup>\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n          </React.Fragment>\n        );\n      }\n\n      const { user } = await render(<App />);\n\n      const trigger = screen.getByTestId('trigger');\n      const valueEl = screen.getByTestId('value');\n      const setCarButton = screen.getByTestId('set-car');\n\n      expect(valueEl.textContent).toBe('dog');\n\n      await user.click(setCarButton);\n      expect(valueEl.textContent).toBe('car');\n\n      await act(async () => trigger.focus());\n      await user.keyboard('c');\n      expect(valueEl.textContent).toBe('cat');\n\n      await user.keyboard('a');\n      expect(valueEl.textContent).toBe('cat');\n    });\n  });\n\n  describe('prop: multiple', () => {\n    it('removes selections that no longer exist', async () => {\n      function Test() {\n        const [items, setItems] = React.useState(['a', 'b', 'c']);\n        return (\n          <div>\n            <Select.Root multiple defaultValue={['a', 'c']}>\n              <Select.Trigger data-testid=\"trigger\">\n                <Select.Value />\n              </Select.Trigger>\n              <Select.Portal>\n                <Select.Positioner>\n                  <Select.Popup>\n                    {items.map((it) => (\n                      <Select.Item key={it} value={it}>\n                        {it}\n                      </Select.Item>\n                    ))}\n                  </Select.Popup>\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n            <button\n              data-testid=\"remove-a\"\n              onClick={() => setItems((prev) => prev.filter((i) => i !== 'a'))}\n            >\n              Remove A\n            </button>\n            <button\n              data-testid=\"remove-c\"\n              onClick={() => setItems((prev) => prev.filter((i) => i !== 'c'))}\n            >\n              Remove C\n            </button>\n          </div>\n        );\n      }\n\n      const { user } = await render(<Test />);\n\n      const trigger = screen.getByTestId('trigger');\n      await user.click(trigger);\n\n      await waitFor(() => {\n        expect(screen.getByRole('option', { name: 'a' })).toHaveAttribute('data-selected', '');\n      });\n      expect(screen.getByRole('option', { name: 'c' })).toHaveAttribute('data-selected', '');\n\n      // Remove one of the selected items; remaining selection should persist\n      await user.click(screen.getByTestId('remove-c'));\n\n      await user.click(trigger);\n\n      await waitFor(() => {\n        expect(screen.getByRole('option', { name: 'a' })).toHaveAttribute('data-selected', '');\n      });\n      expect(screen.queryByRole('option', { name: 'c' })).toBe(null);\n\n      // Remove the last selected item; selection should become empty\n      await user.click(screen.getByTestId('remove-a'));\n\n      await user.click(trigger);\n\n      const options = await screen.findAllByRole('option');\n      options.forEach((opt) => {\n        expect(opt).not.toHaveAttribute('data-selected');\n      });\n    });\n\n    it('should allow multiple selections when multiple is true', async () => {\n      const handleValueChange = vi.fn();\n\n      function App() {\n        const [value, setValue] = React.useState<any[]>([]);\n\n        return (\n          <Select.Root\n            multiple\n            value={value}\n            onValueChange={(newValue) => {\n              setValue(newValue);\n              handleValueChange(newValue);\n            }}\n          >\n            <Select.Trigger data-testid=\"trigger\">\n              <Select.Value />\n            </Select.Trigger>\n            <Select.Portal>\n              <Select.Positioner>\n                <Select.Popup>\n                  <Select.Item value=\"a\">a</Select.Item>\n                  <Select.Item value=\"b\">b</Select.Item>\n                  <Select.Item value=\"c\">c</Select.Item>\n                </Select.Popup>\n              </Select.Positioner>\n            </Select.Portal>\n          </Select.Root>\n        );\n      }\n\n      const { user } = await render(<App />);\n\n      const trigger = screen.getByTestId('trigger');\n\n      await user.click(trigger);\n      await flushMicrotasks();\n\n      const optionA = await screen.findByRole('option', { name: 'a' });\n      await user.click(optionA);\n      await flushMicrotasks();\n\n      expect(handleValueChange.mock.calls[0][0]).toEqual(['a']);\n      expect(optionA).toHaveAttribute('data-selected', '');\n\n      const optionB = screen.getByRole('option', { name: 'b' });\n      await user.click(optionB);\n      await flushMicrotasks();\n\n      expect(handleValueChange.mock.calls[1][0]).toEqual(['a', 'b']);\n      expect(optionA).toHaveAttribute('data-selected', '');\n      expect(optionB).toHaveAttribute('data-selected', '');\n\n      expect(screen.getByRole('listbox')).not.toBe(null);\n    });\n\n    it('should deselect items when clicked again in multiple mode', async () => {\n      const handleValueChange = vi.fn();\n\n      function App() {\n        const [value, setValue] = React.useState(['a', 'b']);\n\n        return (\n          <Select.Root\n            multiple\n            value={value}\n            onValueChange={(newValue) => {\n              setValue(newValue);\n              handleValueChange(newValue);\n            }}\n          >\n            <Select.Trigger data-testid=\"trigger\">\n              <Select.Value />\n            </Select.Trigger>\n            <Select.Portal>\n              <Select.Positioner>\n                <Select.Popup>\n                  <Select.Item value=\"a\">a</Select.Item>\n                  <Select.Item value=\"b\">b</Select.Item>\n                  <Select.Item value=\"c\">c</Select.Item>\n                </Select.Popup>\n              </Select.Positioner>\n            </Select.Portal>\n          </Select.Root>\n        );\n      }\n\n      const { user } = await render(<App />);\n\n      const trigger = screen.getByTestId('trigger');\n\n      await user.click(trigger);\n      await flushMicrotasks();\n\n      const optionA = await screen.findByRole('option', { name: 'a' });\n      const optionB = screen.getByRole('option', { name: 'b' });\n\n      expect(optionA).toHaveAttribute('data-selected', '');\n      expect(optionB).toHaveAttribute('data-selected', '');\n\n      await user.click(optionA);\n      await flushMicrotasks();\n\n      expect(handleValueChange.mock.calls[0][0]).toEqual(['b']);\n      expect(optionA).not.toHaveAttribute('data-selected');\n      expect(optionB).toHaveAttribute('data-selected', '');\n    });\n\n    it('keeps the active index on a deselected item in multiple mode', async () => {\n      const { user } = await render(\n        <Select.Root multiple value={['a']}>\n          <Select.Trigger data-testid=\"trigger\">\n            <Select.Value />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"a\">a</Select.Item>\n                <Select.Item value=\"b\">b</Select.Item>\n                <Select.Item value=\"c\">c</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n\n      await user.click(trigger);\n\n      const optionB = await screen.findByRole('option', { name: 'b' });\n\n      await user.click(optionB);\n\n      await waitFor(() => {\n        expect(optionB).toHaveAttribute('data-highlighted');\n      });\n\n      await user.click(optionB);\n\n      await waitFor(() => {\n        expect(optionB).toHaveAttribute('data-highlighted');\n      });\n    });\n\n    it('should handle defaultValue as array in multiple mode', async () => {\n      await render(\n        <Select.Root multiple defaultValue={['a', 'c']}>\n          <Select.Trigger data-testid=\"trigger\">\n            <Select.Value />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"a\">a</Select.Item>\n                <Select.Item value=\"b\">b</Select.Item>\n                <Select.Item value=\"c\">c</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n\n      fireEvent.click(trigger);\n      await flushMicrotasks();\n\n      expect(screen.getByRole('option', { name: 'a' })).toHaveAttribute('data-selected', '');\n      expect(screen.getByRole('option', { name: 'b' })).not.toHaveAttribute('data-selected');\n      expect(screen.getByRole('option', { name: 'c' })).toHaveAttribute('data-selected', '');\n    });\n\n    it('should serialize multiple values correctly for form submission', async () => {\n      const { container } = await render(\n        <Select.Root multiple name=\"select\" value={['a', 'c']}>\n          <Select.Trigger data-testid=\"trigger\">\n            <Select.Value />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"a\">a</Select.Item>\n                <Select.Item value=\"b\">b</Select.Item>\n                <Select.Item value=\"c\">c</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      // eslint-disable-next-line testing-library/no-container -- No appropriate method on screen since it's a hidden input without any type\n      const hiddenInputs = container.querySelectorAll(\n        '[name=\"select\"]',\n      ) as NodeListOf<HTMLInputElement>;\n      expect(hiddenInputs).toHaveLength(2);\n      const values = Array.from(hiddenInputs).map((input) => input.value);\n      expect(values).toEqual(['a', 'c']);\n    });\n\n    it('should serialize empty array as empty string in multiple mode', async () => {\n      const { container } = await render(\n        <Select.Root multiple name=\"select\">\n          <Select.Trigger data-testid=\"trigger\">\n            <Select.Value />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"a\">a</Select.Item>\n                <Select.Item value=\"b\">b</Select.Item>\n                <Select.Item value=\"c\">c</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      // In multiple mode with empty array, no hidden inputs with name should exist\n      // eslint-disable-next-line testing-library/no-container -- No appropriate method on screen since it's a hidden input without any type\n      const namedHiddenInputs = container.querySelectorAll('[name=\"select\"]');\n      expect(namedHiddenInputs).toHaveLength(0);\n\n      // But the main input should have the serialized empty value for Field validation purposes\n      // eslint-disable-next-line testing-library/no-container -- No appropriate method on screen since it's a hidden input without any type\n      const mainInput = container.querySelector<HTMLInputElement>('input[aria-hidden=\"true\"]');\n      expect(mainInput).not.toBe(null);\n      expect(mainInput?.value).toBe('');\n    });\n\n    it('does not mark the hidden input as required when selection exists', async () => {\n      await render(\n        <Select.Root multiple required name=\"select\" value={['a']}>\n          <Select.Trigger>\n            <Select.Value />\n          </Select.Trigger>\n        </Select.Root>,\n      );\n\n      const hiddenInput = screen.getByRole('textbox', { hidden: true });\n      expect(hiddenInput).not.toBe(null);\n      expect(hiddenInput).not.toHaveAttribute('required');\n    });\n\n    it('keeps the hidden input required when no selection exists', async () => {\n      await render(\n        <Select.Root multiple required name=\"select\" value={[]}>\n          <Select.Trigger>\n            <Select.Value />\n          </Select.Trigger>\n        </Select.Root>,\n      );\n\n      const hiddenInput = screen.getByRole('textbox', { hidden: true });\n      expect(hiddenInput).not.toBe(null);\n      expect(hiddenInput).toHaveAttribute('required');\n    });\n\n    it('should not close popup when selecting items in multiple mode', async () => {\n      const { user } = await render(\n        <Select.Root multiple>\n          <Select.Trigger data-testid=\"trigger\">\n            <Select.Value />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"a\">a</Select.Item>\n                <Select.Item value=\"b\">b</Select.Item>\n                <Select.Item value=\"c\">c</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n\n      await user.click(trigger);\n      await flushMicrotasks();\n\n      await waitFor(() => {\n        expect(screen.getByRole('listbox')).not.toBe(null);\n      });\n\n      const optionA = await screen.findByRole('option', { name: 'a' });\n      await user.click(optionA);\n      await flushMicrotasks();\n\n      expect(screen.getByRole('listbox')).not.toBe(null);\n\n      const optionB = screen.getByRole('option', { name: 'b' });\n      await user.click(optionB);\n      await flushMicrotasks();\n\n      expect(screen.getByRole('listbox')).not.toBe(null);\n    });\n\n    it('should close popup in single select mode', async () => {\n      const { user } = await render(\n        <Select.Root>\n          <Select.Trigger data-testid=\"trigger\">\n            <Select.Value />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"a\">a</Select.Item>\n                <Select.Item value=\"b\">b</Select.Item>\n                <Select.Item value=\"c\">c</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n\n      await user.click(trigger);\n      await flushMicrotasks();\n\n      await waitFor(() => {\n        expect(screen.getByRole('listbox')).not.toBe(null);\n      });\n\n      const optionA = await screen.findByRole('option', { name: 'a' });\n      await user.click(optionA);\n      await flushMicrotasks();\n\n      await waitFor(() => {\n        expect(screen.queryByRole('listbox')).toBe(null);\n      });\n    });\n\n    it('should update selected items when value prop changes', async () => {\n      const { setProps } = await render(\n        <Select.Root multiple value={['a']}>\n          <Select.Trigger data-testid=\"trigger\">\n            <Select.Value />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"a\">a</Select.Item>\n                <Select.Item value=\"b\">b</Select.Item>\n                <Select.Item value=\"c\">c</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n\n      fireEvent.click(trigger);\n      await flushMicrotasks();\n\n      expect(await screen.findByRole('option', { name: 'a' })).toHaveAttribute('data-selected', '');\n      expect(screen.getByRole('option', { name: 'b' })).not.toHaveAttribute('data-selected');\n\n      await setProps({ value: ['a', 'b', 'c'] });\n\n      expect(screen.getByRole('option', { name: 'a' })).toHaveAttribute('data-selected', '');\n      expect(screen.getByRole('option', { name: 'b' })).toHaveAttribute('data-selected', '');\n      expect(screen.getByRole('option', { name: 'c' })).toHaveAttribute('data-selected', '');\n    });\n  });\n\n  describe('prop: isItemEqualToValue', () => {\n    it('matches object values using the provided comparator', async () => {\n      const users = [\n        { id: 1, name: 'Alice' },\n        { id: 2, name: 'Bob' },\n      ];\n\n      await render(\n        <Select.Root\n          value={{ id: 2, name: 'Bob' }}\n          itemToStringLabel={(item) => item.name}\n          itemToStringValue={(item) => String(item.id)}\n          isItemEqualToValue={(item, value) => item.id === value.id}\n        >\n          <Select.Trigger data-testid=\"trigger\">\n            <Select.Value />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                {users.map((user) => (\n                  <Select.Item key={user.id} value={user}>\n                    {user.name}\n                  </Select.Item>\n                ))}\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      expect(trigger).toHaveTextContent('Bob');\n\n      fireEvent.click(trigger);\n\n      await waitFor(() => {\n        expect(screen.getByRole('option', { name: 'Bob' })).toHaveAttribute('data-selected', '');\n      });\n    });\n\n    it('passes item as the first comparator argument in multiple mode', async () => {\n      const users = [\n        { id: 1, name: 'Alice', source: 'item' },\n        { id: 2, name: 'Bob', source: 'item' },\n      ];\n\n      await render(\n        <Select.Root\n          multiple\n          defaultOpen\n          defaultValue={[{ id: 2, name: 'Bob', source: 'selected' }]}\n          itemToStringLabel={(item) => item.name}\n          itemToStringValue={(item) => String(item.id)}\n          isItemEqualToValue={(item, value) =>\n            item.id === value.id && item.source === 'item' && value.source === 'selected'\n          }\n        >\n          <Select.Trigger data-testid=\"trigger\">\n            <Select.Value />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                {users.map((user) => (\n                  <Select.Item key={user.id} value={user}>\n                    {user.name}\n                  </Select.Item>\n                ))}\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      const option = screen.getByRole('option', { name: 'Bob' });\n      expect(option).toHaveAttribute('data-selected', '');\n\n      fireEvent.click(option);\n\n      await waitFor(() => {\n        expect(screen.getByRole('option', { name: 'Bob' })).not.toHaveAttribute('data-selected');\n      });\n    });\n  });\n\n  describe('prop: highlightItemOnHover', () => {\n    it('highlights an item on mouse move by default', async () => {\n      const { user } = await render(\n        <Select.Root defaultOpen>\n          <Select.Trigger data-testid=\"trigger\">\n            <Select.Value />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"a\">a</Select.Item>\n                <Select.Item value=\"b\">b</Select.Item>\n                <Select.Item value=\"c\">c</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      const optionB = screen.getByRole('option', { name: 'b' });\n      await user.hover(optionB);\n\n      await waitFor(() => {\n        expect(optionB).toHaveAttribute('data-highlighted');\n      });\n    });\n\n    it('does not highlight items from mouse movement when disabled', async () => {\n      const { user } = await render(\n        <Select.Root defaultOpen highlightItemOnHover={false}>\n          <Select.Trigger data-testid=\"trigger\">\n            <Select.Value />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"a\">a</Select.Item>\n                <Select.Item value=\"b\">b</Select.Item>\n                <Select.Item value=\"c\">c</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      const optionB = screen.getByRole('option', { name: 'b' });\n      await user.hover(optionB);\n\n      await flushMicrotasks();\n\n      expect(optionB).not.toHaveAttribute('data-highlighted');\n    });\n\n    it('does not remove highlight when mousing out of popup when disabled', async () => {\n      const { user } = await render(\n        <Select.Root defaultOpen highlightItemOnHover={false}>\n          <Select.Trigger data-testid=\"trigger\">\n            <Select.Value />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"a\">a</Select.Item>\n                <Select.Item value=\"b\">b</Select.Item>\n                <Select.Item value=\"c\">c</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      const optionA = screen.getByRole('option', { name: 'a' });\n      await user.hover(optionA);\n\n      await user.keyboard('{ArrowDown}');\n\n      await waitFor(() => {\n        expect(optionA).toHaveAttribute('data-highlighted');\n      });\n\n      const popup = screen.getByRole('listbox');\n      await user.unhover(popup);\n\n      await waitFor(() => {\n        expect(optionA).toHaveAttribute('data-highlighted');\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/select/root/SelectRoot.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { visuallyHidden, visuallyHiddenInput } from '@base-ui/utils/visuallyHidden';\nimport { useMergedRefs } from '@base-ui/utils/useMergedRefs';\nimport { useRefWithInit } from '@base-ui/utils/useRefWithInit';\nimport { useOnFirstRender } from '@base-ui/utils/useOnFirstRender';\nimport { usePreviousValue } from '@base-ui/utils/usePreviousValue';\nimport { useControlled } from '@base-ui/utils/useControlled';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useValueAsRef } from '@base-ui/utils/useValueAsRef';\nimport { useStore, Store } from '@base-ui/utils/store';\nimport {\n  useClick,\n  useDismiss,\n  useFloatingRootContext,\n  useInteractions,\n  useListNavigation,\n  useTypeahead,\n} from '../../floating-ui-react';\nimport { SelectRootContext, SelectFloatingContext } from './SelectRootContext';\nimport { useFieldRootContext } from '../../field/root/FieldRootContext';\nimport { useLabelableId } from '../../labelable-provider/useLabelableId';\nimport { useTransitionStatus } from '../../utils/useTransitionStatus';\nimport { selectors, type State as StoreState } from '../store';\nimport {\n  type BaseUIChangeEventDetails,\n  createChangeEventDetails,\n} from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport { useOpenChangeComplete } from '../../utils/useOpenChangeComplete';\nimport { useFormContext } from '../../form/FormContext';\nimport { useField } from '../../field/useField';\nimport { type Group, stringifyAsValue } from '../../utils/resolveValueLabel';\nimport { EMPTY_ARRAY, EMPTY_OBJECT } from '../../utils/constants';\nimport { defaultItemEquality, findItemIndex } from '../../utils/itemEquality';\nimport { useValueChanged } from '../../utils/useValueChanged';\nimport { useOpenInteractionType } from '../../utils/useOpenInteractionType';\nimport { mergeProps } from '../../merge-props';\n\n/**\n * Groups all parts of the select.\n * Doesn’t render its own HTML element.\n *\n * Documentation: [Base UI Select](https://base-ui.com/react/components/select)\n */\nexport function SelectRoot<Value, Multiple extends boolean | undefined = false>(\n  props: SelectRoot.Props<Value, Multiple>,\n): React.JSX.Element {\n  const {\n    id,\n    value: valueProp,\n    defaultValue = null,\n    onValueChange,\n    open: openProp,\n    defaultOpen = false,\n    onOpenChange,\n    name: nameProp,\n    autoComplete,\n    disabled: disabledProp = false,\n    readOnly = false,\n    required = false,\n    modal = true,\n    actionsRef,\n    inputRef,\n    onOpenChangeComplete,\n    items,\n    multiple = false,\n    itemToStringLabel,\n    itemToStringValue,\n    isItemEqualToValue = defaultItemEquality,\n    highlightItemOnHover = true,\n    children,\n  } = props;\n\n  const { clearErrors } = useFormContext();\n  const {\n    setDirty,\n    setTouched,\n    setFocused,\n    shouldValidateOnChange,\n    validityData,\n    setFilled,\n    name: fieldName,\n    disabled: fieldDisabled,\n    validation,\n    validationMode,\n  } = useFieldRootContext();\n\n  const generatedId = useLabelableId({ id });\n\n  const disabled = fieldDisabled || disabledProp;\n  const name = fieldName ?? nameProp;\n\n  const [value, setValueUnwrapped] = useControlled({\n    controlled: valueProp,\n    default: multiple ? (defaultValue ?? EMPTY_ARRAY) : defaultValue,\n    name: 'Select',\n    state: 'value',\n  });\n\n  const [open, setOpenUnwrapped] = useControlled({\n    controlled: openProp,\n    default: defaultOpen,\n    name: 'Select',\n    state: 'open',\n  });\n\n  const listRef = React.useRef<Array<HTMLElement | null>>([]);\n  const labelsRef = React.useRef<Array<string | null>>([]);\n  const popupRef = React.useRef<HTMLDivElement | null>(null);\n  const scrollHandlerRef = React.useRef<((el: HTMLDivElement) => void) | null>(null);\n  const scrollArrowsMountedCountRef = React.useRef(0);\n  const valueRef = React.useRef<HTMLSpanElement | null>(null);\n  const valuesRef = React.useRef<Array<any>>([]);\n  const typingRef = React.useRef(false);\n  const keyboardActiveRef = React.useRef(false);\n  const selectedItemTextRef = React.useRef<HTMLSpanElement | null>(null);\n  const selectionRef = React.useRef({\n    allowSelectedMouseUp: false,\n    allowUnselectedMouseUp: false,\n  });\n  const alignItemWithTriggerActiveRef = React.useRef(false);\n\n  const { mounted, setMounted, transitionStatus } = useTransitionStatus(open);\n  const { openMethod, triggerProps: interactionTypeProps } = useOpenInteractionType(open);\n\n  const store = useRefWithInit(\n    () =>\n      new Store<StoreState>({\n        id: generatedId,\n        labelId: undefined,\n        modal,\n        multiple,\n        itemToStringLabel,\n        itemToStringValue,\n        isItemEqualToValue,\n        value,\n        open,\n        mounted,\n        transitionStatus,\n        items,\n        forceMount: false,\n        openMethod: null,\n        activeIndex: null,\n        selectedIndex: null,\n        popupProps: {},\n        triggerProps: {},\n        triggerElement: null,\n        positionerElement: null,\n        listElement: null,\n        scrollUpArrowVisible: false,\n        scrollDownArrowVisible: false,\n        hasScrollArrows: false,\n      }),\n  ).current;\n\n  const activeIndex = useStore(store, selectors.activeIndex);\n  const selectedIndex = useStore(store, selectors.selectedIndex);\n  const triggerElement = useStore(store, selectors.triggerElement);\n  const positionerElement = useStore(store, selectors.positionerElement);\n\n  const previousOpenMethod = usePreviousValue(openMethod);\n  const renderedOpenMethod = openMethod ?? previousOpenMethod;\n\n  const serializedValue = React.useMemo(() => {\n    if (multiple && Array.isArray(value) && value.length === 0) {\n      return '';\n    }\n    return stringifyAsValue(value, itemToStringValue);\n  }, [multiple, value, itemToStringValue]);\n\n  const fieldStringValue = React.useMemo(() => {\n    if (multiple && Array.isArray(value)) {\n      return value.map((currentValue) => stringifyAsValue(currentValue, itemToStringValue));\n    }\n    return stringifyAsValue(value, itemToStringValue);\n  }, [multiple, value, itemToStringValue]);\n\n  const controlRef = useValueAsRef(store.state.triggerElement);\n\n  useField({\n    id: generatedId,\n    commit: validation.commit,\n    value,\n    controlRef,\n    name,\n    getValue: () => fieldStringValue,\n  });\n\n  const initialValueRef = React.useRef(value);\n  useIsoLayoutEffect(() => {\n    // Ensure the values and labels are registered for programmatic value changes.\n    if (value !== initialValueRef.current) {\n      store.set('forceMount', true);\n    }\n  }, [store, value]);\n\n  useIsoLayoutEffect(() => {\n    setFilled(multiple ? Array.isArray(value) && value.length > 0 : value != null);\n  }, [multiple, value, setFilled]);\n\n  useIsoLayoutEffect(\n    function syncSelectedIndex() {\n      if (open) {\n        return;\n      }\n\n      const registry = valuesRef.current;\n\n      if (multiple) {\n        const currentValue = Array.isArray(value) ? value : [];\n        if (currentValue.length === 0) {\n          store.set('selectedIndex', null);\n          return;\n        }\n\n        const lastValue = currentValue[currentValue.length - 1];\n        const lastIndex = findItemIndex(registry, lastValue, isItemEqualToValue);\n        store.set('selectedIndex', lastIndex === -1 ? null : lastIndex);\n        return;\n      }\n\n      const index = findItemIndex(registry, value as Value, isItemEqualToValue);\n      store.set('selectedIndex', index === -1 ? null : index);\n    },\n    [multiple, open, value, valuesRef, isItemEqualToValue, store],\n  );\n\n  useValueChanged(value, () => {\n    clearErrors(name);\n    setDirty(value !== validityData.initialValue);\n\n    if (shouldValidateOnChange()) {\n      validation.commit(value);\n    } else {\n      validation.commit(value, true);\n    }\n  });\n\n  const setOpen = useStableCallback(\n    (nextOpen: boolean, eventDetails: SelectRoot.ChangeEventDetails) => {\n      onOpenChange?.(nextOpen, eventDetails);\n\n      if (eventDetails.isCanceled) {\n        return;\n      }\n\n      setOpenUnwrapped(nextOpen);\n\n      if (\n        !nextOpen &&\n        (eventDetails.reason === REASONS.focusOut || eventDetails.reason === REASONS.outsidePress)\n      ) {\n        setTouched(true);\n        setFocused(false);\n\n        if (validationMode === 'onBlur') {\n          validation.commit(value);\n        }\n      }\n\n      // The active index will sync to the last selected index on the next open.\n      // Workaround `enableFocusInside` in Floating UI setting `tabindex=0` of a non-highlighted\n      // option upon close when tabbing out due to `keepMounted=true`:\n      // https://github.com/floating-ui/floating-ui/pull/3004/files#diff-962a7439cdeb09ea98d4b622a45d517bce07ad8c3f866e089bda05f4b0bbd875R194-R199\n      // This otherwise causes options to retain `tabindex=0` incorrectly when the popup is closed\n      // when tabbing outside.\n      if (!nextOpen && store.state.activeIndex !== null) {\n        const activeOption = listRef.current[store.state.activeIndex];\n        // Wait for Floating UI's focus effect to have fired\n        queueMicrotask(() => {\n          activeOption?.setAttribute('tabindex', '-1');\n        });\n      }\n    },\n  );\n\n  const handleUnmount = useStableCallback(() => {\n    setMounted(false);\n    store.update({ activeIndex: null, openMethod: null });\n    onOpenChangeComplete?.(false);\n  });\n\n  useOpenChangeComplete({\n    enabled: !actionsRef,\n    open,\n    ref: popupRef,\n    onComplete() {\n      if (!open) {\n        handleUnmount();\n      }\n    },\n  });\n\n  React.useImperativeHandle(actionsRef, () => ({ unmount: handleUnmount }), [handleUnmount]);\n\n  const setValue = useStableCallback(\n    (nextValue: any, eventDetails: SelectRoot.ChangeEventDetails) => {\n      onValueChange?.(nextValue, eventDetails);\n\n      if (eventDetails.isCanceled) {\n        return;\n      }\n\n      setValueUnwrapped(nextValue);\n    },\n  );\n\n  const handleScrollArrowVisibility = useStableCallback(() => {\n    const scroller = store.state.listElement || popupRef.current;\n    if (!scroller) {\n      return;\n    }\n\n    const viewportTop = scroller.scrollTop;\n    const viewportBottom = scroller.scrollTop + scroller.clientHeight;\n    const shouldShowUp = viewportTop > 1;\n    const shouldShowDown = viewportBottom < scroller.scrollHeight - 1;\n\n    if (store.state.scrollUpArrowVisible !== shouldShowUp) {\n      store.set('scrollUpArrowVisible', shouldShowUp);\n    }\n    if (store.state.scrollDownArrowVisible !== shouldShowDown) {\n      store.set('scrollDownArrowVisible', shouldShowDown);\n    }\n  });\n\n  const floatingContext = useFloatingRootContext({\n    open,\n    onOpenChange: setOpen,\n    elements: {\n      reference: triggerElement,\n      floating: positionerElement,\n    },\n  });\n\n  const click = useClick(floatingContext, {\n    enabled: !readOnly && !disabled,\n    event: 'mousedown',\n  });\n\n  const dismiss = useDismiss(floatingContext, {\n    bubbles: false,\n  });\n\n  const listNavigation = useListNavigation(floatingContext, {\n    enabled: !readOnly && !disabled,\n    listRef,\n    activeIndex,\n    selectedIndex,\n    disabledIndices: EMPTY_ARRAY as number[],\n    onNavigate(nextActiveIndex) {\n      // Retain the highlight while transitioning out.\n      if (nextActiveIndex === null && !open) {\n        return;\n      }\n\n      store.set('activeIndex', nextActiveIndex);\n    },\n    // Implement our own listeners since `onPointerLeave` on each option fires while scrolling with\n    // the `alignItemWithTrigger=true`, causing a performance issue on Chrome.\n    focusItemOnHover: false,\n  });\n\n  const typeahead = useTypeahead(floatingContext, {\n    enabled: !readOnly && !disabled && (open || !multiple),\n    listRef: labelsRef,\n    activeIndex,\n    selectedIndex,\n    onMatch(index) {\n      if (open) {\n        store.set('activeIndex', index);\n      } else {\n        setValue(valuesRef.current[index], createChangeEventDetails('none'));\n      }\n    },\n    onTypingChange(typing) {\n      typingRef.current = typing;\n    },\n  });\n\n  const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions([\n    click,\n    dismiss,\n    listNavigation,\n    typeahead,\n  ]);\n\n  const mergedTriggerProps = React.useMemo(() => {\n    return mergeProps(\n      getReferenceProps(),\n      interactionTypeProps,\n      generatedId ? { id: generatedId } : EMPTY_OBJECT,\n    );\n  }, [getReferenceProps, interactionTypeProps, generatedId]);\n\n  useOnFirstRender(() => {\n    store.update({\n      popupProps: getFloatingProps(),\n      triggerProps: mergedTriggerProps,\n    });\n  });\n\n  useIsoLayoutEffect(() => {\n    store.update({\n      id: generatedId,\n      modal,\n      multiple,\n      value,\n      open,\n      mounted,\n      transitionStatus,\n      popupProps: getFloatingProps(),\n      triggerProps: mergedTriggerProps,\n      items,\n      itemToStringLabel,\n      itemToStringValue,\n      isItemEqualToValue,\n      openMethod: renderedOpenMethod,\n    });\n  }, [\n    store,\n    generatedId,\n    modal,\n    multiple,\n    value,\n    open,\n    mounted,\n    transitionStatus,\n    getFloatingProps,\n    mergedTriggerProps,\n    items,\n    itemToStringLabel,\n    itemToStringValue,\n    isItemEqualToValue,\n    renderedOpenMethod,\n  ]);\n\n  const contextValue: SelectRootContext = React.useMemo(\n    () => ({\n      store,\n      name,\n      required,\n      disabled,\n      readOnly,\n      multiple,\n      itemToStringLabel,\n      itemToStringValue,\n      highlightItemOnHover,\n      setValue,\n      setOpen,\n      listRef,\n      popupRef,\n      scrollHandlerRef,\n      handleScrollArrowVisibility,\n      scrollArrowsMountedCountRef,\n      getItemProps,\n      events: floatingContext.context.events,\n      valueRef,\n      valuesRef,\n      labelsRef,\n      typingRef,\n      selectionRef,\n      selectedItemTextRef,\n      validation,\n      onOpenChangeComplete,\n      keyboardActiveRef,\n      alignItemWithTriggerActiveRef,\n      initialValueRef,\n    }),\n    [\n      store,\n      name,\n      required,\n      disabled,\n      readOnly,\n      multiple,\n      itemToStringLabel,\n      itemToStringValue,\n      highlightItemOnHover,\n      setValue,\n      setOpen,\n      getItemProps,\n      floatingContext.context.events,\n      validation,\n      onOpenChangeComplete,\n      handleScrollArrowVisibility,\n    ],\n  );\n\n  const ref = useMergedRefs(inputRef, validation.inputRef);\n\n  const hasMultipleSelection = multiple && Array.isArray(value) && value.length > 0;\n  const hiddenInputName = multiple ? undefined : name;\n\n  const hiddenInputs = React.useMemo(() => {\n    if (!multiple || !Array.isArray(value) || !name) {\n      return null;\n    }\n\n    return value.map((v) => {\n      const currentSerializedValue = stringifyAsValue(v, itemToStringValue);\n      return (\n        <input\n          key={currentSerializedValue}\n          type=\"hidden\"\n          name={name}\n          value={currentSerializedValue}\n        />\n      );\n    });\n  }, [multiple, value, name, itemToStringValue]);\n\n  return (\n    <SelectRootContext.Provider value={contextValue}>\n      <SelectFloatingContext.Provider value={floatingContext}>\n        {children}\n        <input\n          {...validation.getInputValidationProps({\n            onFocus() {\n              // Move focus to the trigger element when the hidden input is focused.\n              store.state.triggerElement?.focus({\n                // Supported in Chrome from 144 (January 2026)\n                // @ts-expect-error - focusVisible is not yet in the lib.dom.d.ts\n                focusVisible: true,\n              });\n            },\n            // Handle browser autofill.\n            onChange(event: React.ChangeEvent<HTMLInputElement>) {\n              // Workaround for https://github.com/facebook/react/issues/9023\n              if (event.nativeEvent.defaultPrevented) {\n                return;\n              }\n\n              const nextValue = event.target.value;\n              const details = createChangeEventDetails(REASONS.none, event.nativeEvent);\n\n              function handleChange() {\n                if (multiple) {\n                  // Browser autofill only writes a single scalar value.\n                  return;\n                }\n\n                // Handle single selection: match against registered values using serialization\n                const matchingValue = valuesRef.current.find((v) => {\n                  const candidate = stringifyAsValue(v, itemToStringValue);\n                  if (candidate.toLowerCase() === nextValue.toLowerCase()) {\n                    return true;\n                  }\n                  return false;\n                });\n\n                if (matchingValue != null) {\n                  setDirty(matchingValue !== validityData.initialValue);\n                  setValue(matchingValue, details);\n\n                  if (shouldValidateOnChange()) {\n                    validation.commit(matchingValue);\n                  }\n                }\n              }\n\n              store.set('forceMount', true);\n              queueMicrotask(handleChange);\n            },\n          })}\n          id={generatedId && hiddenInputName == null ? `${generatedId}-hidden-input` : undefined}\n          name={hiddenInputName}\n          autoComplete={autoComplete}\n          value={serializedValue}\n          disabled={disabled}\n          required={required && !hasMultipleSelection}\n          readOnly={readOnly}\n          ref={ref}\n          style={name ? visuallyHiddenInput : visuallyHidden}\n          tabIndex={-1}\n          aria-hidden\n        />\n        {hiddenInputs}\n      </SelectFloatingContext.Provider>\n    </SelectRootContext.Provider>\n  );\n}\n\ntype SelectValueType<Value, Multiple extends boolean | undefined> = Multiple extends true\n  ? Value[]\n  : Value;\n\nexport interface SelectRootProps<Value, Multiple extends boolean | undefined = false> {\n  children?: React.ReactNode;\n  /**\n   * A ref to access the hidden input element.\n   */\n  inputRef?: React.Ref<HTMLInputElement> | undefined;\n  /**\n   * Identifies the field when a form is submitted.\n   */\n  name?: string | undefined;\n  /**\n   * Provides a hint to the browser for autofill.\n   * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/autocomplete\n   */\n  autoComplete?: string | undefined;\n  /**\n   * The id of the Select.\n   */\n  id?: string | undefined;\n  /**\n   * Whether the user must choose a value before submitting a form.\n   * @default false\n   */\n  required?: boolean | undefined;\n  /**\n   * Whether the user should be unable to choose a different option from the select popup.\n   * @default false\n   */\n  readOnly?: boolean | undefined;\n  /**\n   * Whether the component should ignore user interaction.\n   * @default false\n   */\n  disabled?: boolean | undefined;\n  /**\n   * Whether multiple items can be selected.\n   * @default false\n   */\n  multiple?: Multiple | undefined;\n  /**\n   * Whether moving the pointer over items should highlight them.\n   * Disabling this prop allows CSS `:hover` to be differentiated from the `:focus` (`data-highlighted`) state.\n   * @default true\n   */\n  highlightItemOnHover?: boolean | undefined;\n  /**\n   * Whether the select popup is initially open.\n   *\n   * To render a controlled select popup, use the `open` prop instead.\n   * @default false\n   */\n  defaultOpen?: boolean | undefined;\n  /**\n   * Event handler called when the select popup is opened or closed.\n   */\n  onOpenChange?: ((open: boolean, eventDetails: SelectRootChangeEventDetails) => void) | undefined;\n  /**\n   * Event handler called after any animations complete when the select popup is opened or closed.\n   */\n  onOpenChangeComplete?: ((open: boolean) => void) | undefined;\n  /**\n   * Whether the select popup is currently open.\n   */\n  open?: boolean | undefined;\n  /**\n   * Determines if the select enters a modal state when open.\n   * - `true`: user interaction is limited to the select: document page scroll is locked and pointer interactions on outside elements are disabled.\n   * - `false`: user interaction with the rest of the document is allowed.\n   * @default true\n   */\n  modal?: boolean | undefined;\n  /**\n   * A ref to imperative actions.\n   * - `unmount`: When specified, the select will not be unmounted when closed.\n   * Instead, the `unmount` function must be called to unmount the select manually.\n   * Useful when the select's animation is controlled by an external library.\n   */\n  actionsRef?: React.RefObject<SelectRootActions | null> | undefined;\n  /**\n   * Data structure of the items rendered in the select popup.\n   * When specified, `<Select.Value>` renders the label of the selected item instead of the raw value.\n   * @example\n   * ```tsx\n   * const items = {\n   *   sans: 'Sans-serif',\n   *   serif: 'Serif',\n   *   mono: 'Monospace',\n   *   cursive: 'Cursive',\n   * };\n   * <Select.Root items={items} />\n   * ```\n   */\n  items?:\n    | Record<string, React.ReactNode>\n    | ReadonlyArray<{ label: React.ReactNode; value: any }>\n    | ReadonlyArray<Group<any>>\n    | undefined;\n  /**\n   * When the item values are objects (`<Select.Item value={object}>`), this function converts the object value to a string representation for display in the trigger.\n   * If the shape of the object is `{ value, label }`, the label will be used automatically without needing to specify this prop.\n   */\n  itemToStringLabel?: ((itemValue: Value) => string) | undefined;\n  /**\n   * When the item values are objects (`<Select.Item value={object}>`), this function converts the object value to a string representation for form submission.\n   * If the shape of the object is `{ value, label }`, the value will be used automatically without needing to specify this prop.\n   */\n  itemToStringValue?: ((itemValue: Value) => string) | undefined;\n  /**\n   * Custom comparison logic used to determine if a select item value matches the current selected value. Useful when item values are objects without matching referentially.\n   * Defaults to `Object.is` comparison.\n   */\n  isItemEqualToValue?: ((itemValue: Value, value: Value) => boolean) | undefined;\n  /**\n   * The uncontrolled value of the select when it’s initially rendered.\n   *\n   * To render a controlled select, use the `value` prop instead.\n   */\n  defaultValue?: SelectValueType<Value, Multiple> | null | undefined;\n  /**\n   * The value of the select. Use when controlled.\n   */\n  value?: SelectValueType<Value, Multiple> | null | undefined;\n  /**\n   * Event handler called when the value of the select changes.\n   */\n  onValueChange?:\n    | ((\n        value: SelectValueType<Value, Multiple> | (Multiple extends true ? never : null),\n        eventDetails: SelectRootChangeEventDetails,\n      ) => void)\n    | undefined;\n}\n\nexport interface SelectRootState {}\n\nexport interface SelectRootActions {\n  unmount: () => void;\n}\n\nexport type SelectRootChangeEventReason =\n  | typeof REASONS.triggerPress\n  | typeof REASONS.outsidePress\n  | typeof REASONS.escapeKey\n  | typeof REASONS.windowResize\n  | typeof REASONS.itemPress\n  | typeof REASONS.focusOut\n  | typeof REASONS.listNavigation\n  | typeof REASONS.cancelOpen\n  | typeof REASONS.none;\n\nexport type SelectRootChangeEventDetails = BaseUIChangeEventDetails<SelectRootChangeEventReason>;\n\nexport namespace SelectRoot {\n  export type Props<Value, Multiple extends boolean | undefined = false> = SelectRootProps<\n    Value,\n    Multiple\n  >;\n  export type State = SelectRootState;\n  export type Actions = SelectRootActions;\n  export type ChangeEventReason = SelectRootChangeEventReason;\n  export type ChangeEventDetails = SelectRootChangeEventDetails;\n}\n"
  },
  {
    "path": "packages/react/src/select/root/SelectRootContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { type FloatingEvents, type FloatingRootContext } from '../../floating-ui-react';\nimport type { SelectStore } from '../store';\nimport type { UseFieldValidationReturnValue } from '../../field/root/useFieldValidation';\nimport type { HTMLProps } from '../../utils/types';\nimport type { SelectRoot } from './SelectRoot';\n\nexport interface SelectRootContext {\n  store: SelectStore;\n  name: string | undefined;\n  disabled: boolean;\n  readOnly: boolean;\n  required: boolean;\n  multiple: boolean;\n  highlightItemOnHover: boolean;\n  setValue: (nextValue: any, eventDetails: SelectRoot.ChangeEventDetails) => void;\n  setOpen: (open: boolean, eventDetails: SelectRoot.ChangeEventDetails) => void;\n  listRef: React.RefObject<Array<HTMLElement | null>>;\n  popupRef: React.RefObject<HTMLDivElement | null>;\n  scrollHandlerRef: React.RefObject<((el: HTMLDivElement) => void) | null>;\n  handleScrollArrowVisibility: () => void;\n  scrollArrowsMountedCountRef: React.RefObject<number>;\n  getItemProps: (\n    props?: HTMLProps & { active?: boolean | undefined; selected?: boolean | undefined },\n  ) => Record<string, unknown>; // PREVENT_COMMIT\n  events: FloatingEvents;\n  valueRef: React.RefObject<HTMLSpanElement | null>;\n  valuesRef: React.RefObject<Array<any>>;\n  labelsRef: React.RefObject<Array<string | null>>;\n  typingRef: React.RefObject<boolean>;\n  selectionRef: React.RefObject<{\n    allowUnselectedMouseUp: boolean;\n    allowSelectedMouseUp: boolean;\n  }>;\n  selectedItemTextRef: React.RefObject<HTMLSpanElement | null>;\n  validation: UseFieldValidationReturnValue;\n  onOpenChangeComplete?: ((open: boolean) => void) | undefined;\n  keyboardActiveRef: React.RefObject<boolean>;\n  alignItemWithTriggerActiveRef: React.RefObject<boolean>;\n  initialValueRef: React.RefObject<any>;\n}\n\nexport const SelectRootContext = React.createContext<SelectRootContext | null>(null);\nexport const SelectFloatingContext = React.createContext<FloatingRootContext | null>(null);\n\nexport function useSelectRootContext() {\n  const context = React.useContext(SelectRootContext);\n  if (context === null) {\n    throw new Error(\n      'Base UI: SelectRootContext is missing. Select parts must be placed within <Select.Root>.',\n    );\n  }\n  return context;\n}\n\nexport function useSelectFloatingContext() {\n  const context = React.useContext(SelectFloatingContext);\n  if (context === null) {\n    throw new Error(\n      'Base UI: SelectFloatingContext is missing. Select parts must be placed within <Select.Root>.',\n    );\n  }\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/select/scroll-arrow/SelectScrollArrow.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useTimeout } from '@base-ui/utils/useTimeout';\nimport { useStore } from '@base-ui/utils/store';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useSelectRootContext } from '../root/SelectRootContext';\nimport { useSelectPositionerContext } from '../positioner/SelectPositionerContext';\nimport { Side } from '../../utils/useAnchorPositioning';\nimport { type TransitionStatus, useTransitionStatus } from '../../utils/useTransitionStatus';\nimport { useOpenChangeComplete } from '../../utils/useOpenChangeComplete';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { selectors } from '../store';\n\n/**\n * @internal\n */\nexport const SelectScrollArrow = React.forwardRef(function SelectScrollArrow(\n  componentProps: SelectScrollArrow.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, direction, keepMounted = false, ...elementProps } = componentProps;\n\n  const { store, popupRef, listRef, handleScrollArrowVisibility, scrollArrowsMountedCountRef } =\n    useSelectRootContext();\n  const { side, scrollDownArrowRef, scrollUpArrowRef } = useSelectPositionerContext();\n\n  const visibleSelector =\n    direction === 'up' ? selectors.scrollUpArrowVisible : selectors.scrollDownArrowVisible;\n\n  const stateVisible = useStore(store, visibleSelector);\n  const openMethod = useStore(store, selectors.openMethod);\n\n  // Scroll arrows are disabled for touch modality as they are a hover-only element.\n  const visible = stateVisible && openMethod !== 'touch';\n\n  const timeout = useTimeout();\n\n  const scrollArrowRef = direction === 'up' ? scrollUpArrowRef : scrollDownArrowRef;\n\n  const { transitionStatus, setMounted } = useTransitionStatus(visible);\n\n  useIsoLayoutEffect(() => {\n    scrollArrowsMountedCountRef.current += 1;\n    if (!store.state.hasScrollArrows) {\n      store.set('hasScrollArrows', true);\n    }\n\n    return () => {\n      scrollArrowsMountedCountRef.current = Math.max(0, scrollArrowsMountedCountRef.current - 1);\n      if (scrollArrowsMountedCountRef.current === 0 && store.state.hasScrollArrows) {\n        store.set('hasScrollArrows', false);\n      }\n    };\n  }, [store, scrollArrowsMountedCountRef]);\n\n  useOpenChangeComplete({\n    open: visible,\n    ref: scrollArrowRef,\n    onComplete() {\n      if (!visible) {\n        setMounted(false);\n      }\n    },\n  });\n\n  const state: SelectScrollArrowState = {\n    direction,\n    visible,\n    side,\n    transitionStatus,\n  };\n\n  const defaultProps: React.ComponentProps<'div'> = {\n    'aria-hidden': true,\n    children: direction === 'up' ? '▲' : '▼',\n    style: {\n      position: 'absolute',\n    },\n    onMouseMove(event) {\n      if ((event.movementX === 0 && event.movementY === 0) || timeout.isStarted()) {\n        return;\n      }\n\n      store.set('activeIndex', null);\n\n      function scrollNextItem() {\n        const scroller = store.state.listElement ?? popupRef.current;\n        if (!scroller) {\n          return;\n        }\n\n        store.set('activeIndex', null);\n        handleScrollArrowVisibility();\n\n        const isScrolledToTop = scroller.scrollTop === 0;\n        const isScrolledToBottom =\n          Math.round(scroller.scrollTop + scroller.clientHeight) >= scroller.scrollHeight;\n\n        const list = listRef.current;\n\n        // Fallback when there are no items registered yet.\n        if (list.length === 0) {\n          if (direction === 'up') {\n            store.set('scrollUpArrowVisible', !isScrolledToTop);\n          } else {\n            store.set('scrollDownArrowVisible', !isScrolledToBottom);\n          }\n        }\n\n        if (\n          (direction === 'up' && isScrolledToTop) ||\n          (direction === 'down' && isScrolledToBottom)\n        ) {\n          timeout.clear();\n          return;\n        }\n\n        if (\n          (store.state.listElement || popupRef.current) &&\n          listRef.current &&\n          listRef.current.length > 0\n        ) {\n          const items = listRef.current;\n          const scrollArrowHeight = scrollArrowRef.current?.offsetHeight || 0;\n\n          if (direction === 'up') {\n            let firstVisibleIndex = 0;\n            const scrollTop = scroller.scrollTop + scrollArrowHeight;\n\n            for (let i = 0; i < items.length; i += 1) {\n              const item = items[i];\n              if (item) {\n                const itemTop = item.offsetTop;\n                if (itemTop >= scrollTop) {\n                  firstVisibleIndex = i;\n                  break;\n                }\n              }\n            }\n\n            const targetIndex = Math.max(0, firstVisibleIndex - 1);\n            if (targetIndex < firstVisibleIndex) {\n              const targetItem = items[targetIndex];\n              if (targetItem) {\n                scroller.scrollTop = Math.max(0, targetItem.offsetTop - scrollArrowHeight);\n              }\n            } else {\n              // Already at the first item; ensure we reach the absolute top to account for group labels.\n              scroller.scrollTop = 0;\n            }\n          } else {\n            let lastVisibleIndex = items.length - 1;\n            const scrollBottom = scroller.scrollTop + scroller.clientHeight - scrollArrowHeight;\n\n            for (let i = 0; i < items.length; i += 1) {\n              const item = items[i];\n              if (item) {\n                const itemBottom = item.offsetTop + item.offsetHeight;\n                if (itemBottom > scrollBottom) {\n                  lastVisibleIndex = Math.max(0, i - 1);\n                  break;\n                }\n              }\n            }\n\n            const targetIndex = Math.min(items.length - 1, lastVisibleIndex + 1);\n            if (targetIndex > lastVisibleIndex) {\n              const targetItem = items[targetIndex];\n              if (targetItem) {\n                scroller.scrollTop =\n                  targetItem.offsetTop +\n                  targetItem.offsetHeight -\n                  scroller.clientHeight +\n                  scrollArrowHeight;\n              }\n            } else {\n              // Already at the last item; ensure we reach the true bottom.\n              scroller.scrollTop = scroller.scrollHeight - scroller.clientHeight;\n            }\n          }\n        }\n\n        timeout.start(40, scrollNextItem);\n      }\n\n      timeout.start(40, scrollNextItem);\n    },\n    onMouseLeave() {\n      timeout.clear();\n    },\n  };\n\n  const element = useRenderElement('div', componentProps, {\n    ref: [forwardedRef, scrollArrowRef],\n    state,\n    props: [defaultProps, elementProps],\n  });\n\n  const shouldRender = visible || keepMounted;\n  if (!shouldRender) {\n    return null;\n  }\n\n  return element;\n});\n\nexport interface SelectScrollArrowState {\n  /**\n   * The direction of the element.\n   */\n  direction: 'up' | 'down';\n  /**\n   * Whether the element is visible.\n   */\n  visible: boolean;\n  /**\n   * The side of the anchor the component is placed on.\n   */\n  side: Side | 'none';\n  /**\n   * The transition status of the component.\n   */\n  transitionStatus: TransitionStatus;\n}\n\nexport interface SelectScrollArrowProps extends BaseUIComponentProps<\n  'div',\n  SelectScrollArrowState\n> {\n  direction: 'up' | 'down';\n  /**\n   * Whether to keep the HTML element in the DOM while the select popup is not scrollable.\n   * @default false\n   */\n  keepMounted?: boolean | undefined;\n}\n\nexport namespace SelectScrollArrow {\n  export type State = SelectScrollArrowState;\n  export type Props = SelectScrollArrowProps;\n}\n"
  },
  {
    "path": "packages/react/src/select/scroll-down-arrow/SelectScrollDownArrow.test.tsx",
    "content": "import { Select } from '@base-ui/react/select';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Select.ScrollUpArrow />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Select.ScrollUpArrow keepMounted />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <Select.Root open>\n          <Select.Positioner>{node}</Select.Positioner>\n        </Select.Root>,\n      );\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/select/scroll-down-arrow/SelectScrollDownArrow.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { SelectScrollArrow } from '../scroll-arrow/SelectScrollArrow';\nimport type { BaseUIComponentProps } from '../../utils/types';\n\n/**\n * An element that scrolls the select popup down when hovered. Does not render when using touch input.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Select](https://base-ui.com/react/components/select)\n */\nexport const SelectScrollDownArrow = React.forwardRef(function SelectScrollDownArrow(\n  props: SelectScrollDownArrow.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  return <SelectScrollArrow {...props} ref={forwardedRef} direction=\"down\" />;\n});\n\nexport interface SelectScrollDownArrowState {}\n\nexport interface SelectScrollDownArrowProps extends BaseUIComponentProps<\n  'div',\n  SelectScrollDownArrowState\n> {\n  /**\n   * Whether to keep the HTML element in the DOM while the select popup is not scrollable.\n   * @default false\n   */\n  keepMounted?: boolean | undefined;\n}\n\nexport namespace SelectScrollDownArrow {\n  export type State = SelectScrollDownArrowState;\n  export type Props = SelectScrollDownArrowProps;\n}\n"
  },
  {
    "path": "packages/react/src/select/scroll-down-arrow/SelectScrollDownArrowDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum SelectScrollDownArrowDataAttributes {\n  /**\n   * Present when the scroll arrow is animating in.\n   */\n  startingStyle = CommonPopupDataAttributes.startingStyle,\n  /**\n   * Present when the scroll arrow is animating out.\n   */\n  endingStyle = CommonPopupDataAttributes.endingStyle,\n  /**\n   * Indicates the direction of the scroll arrow.\n   * @type {'down'}\n   */\n  direction = 'data-direction',\n  /**\n   * Present when the scroll arrow is visible.\n   */\n  visible = 'data-visible',\n  /**\n   * Indicates which side the popup is positioned relative to the trigger.\n   * @type {'none' | 'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'}\n   */\n  side = CommonPopupDataAttributes.side,\n}\n"
  },
  {
    "path": "packages/react/src/select/scroll-up-arrow/SelectScrollUpArrow.test.tsx",
    "content": "import { Select } from '@base-ui/react/select';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Select.ScrollUpArrow />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Select.ScrollUpArrow keepMounted />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <Select.Root open>\n          <Select.Positioner>{node}</Select.Positioner>\n        </Select.Root>,\n      );\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/select/scroll-up-arrow/SelectScrollUpArrow.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { SelectScrollArrow } from '../scroll-arrow/SelectScrollArrow';\nimport type { BaseUIComponentProps } from '../../utils/types';\n\n/**\n * An element that scrolls the select popup up when hovered. Does not render when using touch input.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Select](https://base-ui.com/react/components/select)\n */\nexport const SelectScrollUpArrow = React.forwardRef(function SelectScrollUpArrow(\n  props: SelectScrollUpArrow.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  return <SelectScrollArrow {...props} ref={forwardedRef} direction=\"up\" />;\n});\n\nexport interface SelectScrollUpArrowState {}\n\nexport interface SelectScrollUpArrowProps extends BaseUIComponentProps<\n  'div',\n  SelectScrollUpArrowState\n> {\n  /**\n   * Whether to keep the HTML element in the DOM while the select popup is not scrollable.\n   * @default false\n   */\n  keepMounted?: boolean | undefined;\n}\n\nexport namespace SelectScrollUpArrow {\n  export type State = SelectScrollUpArrowState;\n  export type Props = SelectScrollUpArrowProps;\n}\n"
  },
  {
    "path": "packages/react/src/select/scroll-up-arrow/SelectScrollUpArrowDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum SelectScrollUpArrowDataAttributes {\n  /**\n   * Present when the scroll arrow is animating in.\n   */\n  startingStyle = CommonPopupDataAttributes.startingStyle,\n  /**\n   * Present when the scroll arrow is animating out.\n   */\n  endingStyle = CommonPopupDataAttributes.endingStyle,\n  /**\n   * Indicates the direction of the scroll arrow.\n   * @type {'up'}\n   */\n  direction = 'data-direction',\n  /**\n   * Present when the scroll arrow is visible.\n   */\n  visible = 'data-visible',\n  /**\n   * Indicates which side the popup is positioned relative to the trigger.\n   * @type {'none' | 'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'}\n   */\n  side = CommonPopupDataAttributes.side,\n}\n"
  },
  {
    "path": "packages/react/src/select/store.ts",
    "content": "import { Store, createSelector } from '@base-ui/utils/store';\nimport { type InteractionType } from '@base-ui/utils/useEnhancedClickHandler';\nimport type { TransitionStatus } from '../utils/useTransitionStatus';\nimport type { HTMLProps } from '../utils/types';\nimport { compareItemEquality } from '../utils/itemEquality';\nimport { type Group, hasNullItemLabel, stringifyAsValue } from '../utils/resolveValueLabel';\n\nexport type State = {\n  id: string | undefined;\n  labelId: string | undefined;\n  modal: boolean;\n  multiple: boolean;\n\n  items:\n    | Record<string, React.ReactNode>\n    | ReadonlyArray<{ label: React.ReactNode; value: any }>\n    | ReadonlyArray<Group<any>>\n    | undefined;\n  itemToStringLabel: ((item: any) => string) | undefined;\n  itemToStringValue: ((item: any) => string) | undefined;\n  isItemEqualToValue: (itemValue: any, selectedValue: any) => boolean;\n\n  value: any;\n\n  open: boolean;\n  mounted: boolean;\n  forceMount: boolean;\n  transitionStatus: TransitionStatus;\n  openMethod: InteractionType | null;\n\n  activeIndex: number | null;\n  selectedIndex: number | null;\n\n  popupProps: HTMLProps;\n  triggerProps: HTMLProps;\n  triggerElement: HTMLElement | null;\n  positionerElement: HTMLElement | null;\n  listElement: HTMLDivElement | null;\n\n  scrollUpArrowVisible: boolean;\n  scrollDownArrowVisible: boolean;\n\n  hasScrollArrows: boolean;\n};\n\nexport type SelectStore = Store<State>;\n\nexport const selectors = {\n  id: createSelector((state: State) => state.id),\n  labelId: createSelector((state: State) => state.labelId),\n  modal: createSelector((state: State) => state.modal),\n  multiple: createSelector((state: State) => state.multiple),\n\n  items: createSelector((state: State) => state.items),\n  itemToStringLabel: createSelector((state: State) => state.itemToStringLabel),\n  itemToStringValue: createSelector((state: State) => state.itemToStringValue),\n  isItemEqualToValue: createSelector((state: State) => state.isItemEqualToValue),\n\n  value: createSelector((state: State) => state.value),\n\n  hasSelectedValue: createSelector((state: State) => {\n    const { value, multiple, itemToStringValue } = state;\n    if (value == null) {\n      return false;\n    }\n    if (multiple && Array.isArray(value)) {\n      return value.length > 0;\n    }\n\n    return stringifyAsValue(value, itemToStringValue) !== '';\n  }),\n\n  hasNullItemLabel: createSelector((state: State, enabled: boolean) => {\n    return enabled ? hasNullItemLabel(state.items) : false;\n  }),\n\n  open: createSelector((state: State) => state.open),\n  mounted: createSelector((state: State) => state.mounted),\n  forceMount: createSelector((state: State) => state.forceMount),\n  transitionStatus: createSelector((state: State) => state.transitionStatus),\n  openMethod: createSelector((state: State) => state.openMethod),\n\n  activeIndex: createSelector((state: State) => state.activeIndex),\n  selectedIndex: createSelector((state: State) => state.selectedIndex),\n  isActive: createSelector((state: State, index: number) => state.activeIndex === index),\n\n  isSelected: createSelector((state: State, index: number, itemValue: any) => {\n    const comparer = state.isItemEqualToValue;\n    const storeValue = state.value;\n\n    if (state.multiple) {\n      return (\n        Array.isArray(storeValue) &&\n        storeValue.some((selectedItem) => compareItemEquality(itemValue, selectedItem, comparer))\n      );\n    }\n\n    // `selectedIndex` is only updated after the items mount for the first time,\n    // the value check avoids a re-render for the initially selected item.\n    if (state.selectedIndex === index && state.selectedIndex !== null) {\n      return true;\n    }\n\n    return compareItemEquality(itemValue, storeValue, comparer);\n  }),\n  isSelectedByFocus: createSelector((state: State, index: number) => {\n    return state.selectedIndex === index;\n  }),\n\n  popupProps: createSelector((state: State) => state.popupProps),\n  triggerProps: createSelector((state: State) => state.triggerProps),\n  triggerElement: createSelector((state: State) => state.triggerElement),\n  positionerElement: createSelector((state: State) => state.positionerElement),\n  listElement: createSelector((state: State) => state.listElement),\n\n  scrollUpArrowVisible: createSelector((state: State) => state.scrollUpArrowVisible),\n  scrollDownArrowVisible: createSelector((state: State) => state.scrollDownArrowVisible),\n\n  hasScrollArrows: createSelector((state: State) => state.hasScrollArrows),\n};\n"
  },
  {
    "path": "packages/react/src/select/trigger/SelectTrigger.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport { Select } from '@base-ui/react/select';\nimport { createRenderer, describeConformance } from '#test-utils';\nimport { fireEvent, screen, waitFor } from '@mui/internal-test-utils';\n\ndescribe('<Select.Trigger />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Select.Trigger />, () => ({\n    refInstanceof: window.HTMLButtonElement,\n    button: true,\n    render(node) {\n      return render(<Select.Root open>{node}</Select.Root>);\n    },\n  }));\n\n  describe('disabled state', () => {\n    it('cannot be focused when disabled', async () => {\n      const { user } = await render(\n        <Select.Root defaultValue=\"b\">\n          <Select.Trigger data-testid=\"trigger\" disabled>\n            <Select.Value />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"a\">a</Select.Item>\n                <Select.Item value=\"b\">b</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      expect(trigger).toHaveAttribute('data-disabled');\n\n      await user.keyboard('[Tab]');\n\n      expect(expect(document.activeElement)).not.toBe(trigger);\n    });\n\n    it('does not toggle the popup when disabled', async () => {\n      const handleOpenChange = vi.fn();\n      await render(\n        <Select.Root defaultValue=\"b\" onOpenChange={handleOpenChange}>\n          <Select.Trigger data-testid=\"trigger\" disabled>\n            <Select.Value />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"a\">a</Select.Item>\n                <Select.Item value=\"b\">b</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n\n      fireEvent.click(trigger);\n\n      await waitFor(() => {\n        expect(screen.queryByRole('listbox')).toBe(null);\n      });\n      expect(handleOpenChange.mock.calls.length).toBe(0);\n    });\n  });\n\n  describe('placeholder state', () => {\n    it('should have the data-placeholder attribute when provided `null` value', async () => {\n      await render(\n        <Select.Root value={null}>\n          <Select.Trigger data-testid=\"trigger\">\n            <Select.Value data-testid=\"value\" />\n          </Select.Trigger>\n\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"a\">a</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      const value = screen.getByTestId('value');\n\n      expect(trigger).toHaveAttribute('data-placeholder');\n      expect(value).toHaveAttribute('data-placeholder');\n    });\n\n    it('should have the data-placeholder attribute when provided custom property with `itemToStringValue`', async () => {\n      const shippingMethods = [\n        { id: '', name: 'Default' },\n        { id: 'standard', name: 'Standard' },\n        { id: 'express', name: 'Express' },\n        { id: 'overnight', name: 'Overnight' },\n      ];\n\n      await render(\n        <Select.Root\n          defaultValue={shippingMethods[0]}\n          itemToStringValue={(item) => item.id}\n          itemToStringLabel={(item) => item.name}\n        >\n          <Select.Trigger data-testid=\"trigger\">\n            <Select.Value data-testid=\"value\" />\n          </Select.Trigger>\n        </Select.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      const value = screen.getByTestId('value');\n\n      expect(trigger).toHaveAttribute('data-placeholder');\n      expect(value).toHaveAttribute('data-placeholder');\n      expect(value.textContent).toBe('Default');\n    });\n\n    it('should have the data-placeholder attribute when provided { value: null }', async () => {\n      const fonts = [{ label: 'Select font', value: null }];\n\n      await render(\n        <Select.Root items={fonts}>\n          <Select.Trigger data-testid=\"trigger\">\n            <Select.Value data-testid=\"value\" />\n          </Select.Trigger>\n        </Select.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      const value = screen.getByTestId('value');\n\n      expect(trigger).toHaveAttribute('data-placeholder');\n      expect(value).toHaveAttribute('data-placeholder');\n      expect(value.textContent).toBe('Select font');\n    });\n\n    it('should not have the data-placeholder attribute when provided a value', async () => {\n      await render(\n        <Select.Root defaultValue=\"a\">\n          <Select.Trigger data-testid=\"trigger\">\n            <Select.Value data-testid=\"value\" />\n          </Select.Trigger>\n        </Select.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      const value = screen.getByTestId('value');\n\n      expect(trigger).not.toHaveAttribute('data-placeholder');\n      expect(value).not.toHaveAttribute('data-placeholder');\n    });\n\n    it('should not have the data-placeholder attribute when multiple mode has a default value', async () => {\n      await render(\n        <Select.Root multiple defaultValue={['a']}>\n          <Select.Trigger data-testid=\"trigger\">\n            <Select.Value data-testid=\"value\" />\n          </Select.Trigger>\n        </Select.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      const value = screen.getByTestId('value');\n\n      expect(trigger).not.toHaveAttribute('data-placeholder');\n      expect(value).not.toHaveAttribute('data-placeholder');\n    });\n  });\n\n  describe('style hooks', () => {\n    it('should have the data-popup-open and data-pressed attributes when open', async () => {\n      const { user } = await render(\n        <Select.Root>\n          <Select.Trigger />\n        </Select.Root>,\n      );\n\n      const trigger = screen.getByRole('combobox');\n\n      await user.click(trigger);\n\n      await waitFor(() => {\n        expect(trigger).toHaveAttribute('data-popup-open');\n      });\n      expect(trigger).toHaveAttribute('data-pressed');\n    });\n  });\n\n  describe('prop: required', () => {\n    it('sets aria-required attribute when required', async () => {\n      await render(\n        <Select.Root required>\n          <Select.Trigger data-testid=\"trigger\" />\n        </Select.Root>,\n      );\n\n      const trigger = screen.getByTestId('trigger');\n      expect(trigger).toHaveAttribute('aria-required', 'true');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/select/trigger/SelectTrigger.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { ownerDocument } from '@base-ui/utils/owner';\nimport { useTimeout } from '@base-ui/utils/useTimeout';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useMergedRefs } from '@base-ui/utils/useMergedRefs';\nimport { useValueAsRef } from '@base-ui/utils/useValueAsRef';\nimport { useStore } from '@base-ui/utils/store';\nimport { useSelectRootContext } from '../root/SelectRootContext';\nimport { BaseUIComponentProps, HTMLProps, NativeButtonProps } from '../../utils/types';\nimport { useFieldRootContext } from '../../field/root/FieldRootContext';\nimport { useLabelableContext } from '../../labelable-provider/LabelableContext';\nimport { pressableTriggerOpenStateMapping } from '../../utils/popupStateMapping';\nimport { fieldValidityMapping } from '../../field/utils/constants';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport { selectors } from '../store';\nimport { getPseudoElementBounds } from '../../utils/getPseudoElementBounds';\nimport { contains, getFloatingFocusElement } from '../../floating-ui-react/utils';\nimport { mergeProps } from '../../merge-props';\nimport { useButton } from '../../use-button';\nimport type { FieldRootState } from '../../field/root/FieldRoot';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport { useLabelableId } from '../../labelable-provider/useLabelableId';\nimport { resolveAriaLabelledBy } from '../../utils/resolveAriaLabelledBy';\n\nconst BOUNDARY_OFFSET = 2;\nconst SELECTED_DELAY = 400;\nconst UNSELECTED_DELAY = 200;\n\nconst stateAttributesMapping: StateAttributesMapping<SelectTriggerState> = {\n  ...pressableTriggerOpenStateMapping,\n  ...fieldValidityMapping,\n  value: () => null,\n};\n\n/**\n * A button that opens the select popup.\n * Renders a `<button>` element.\n *\n * Documentation: [Base UI Select](https://base-ui.com/react/components/select)\n */\nexport const SelectTrigger = React.forwardRef(function SelectTrigger(\n  componentProps: SelectTrigger.Props,\n  forwardedRef: React.ForwardedRef<HTMLButtonElement>,\n) {\n  const {\n    render,\n    className,\n    id: idProp,\n    disabled: disabledProp = false,\n    nativeButton = true,\n    ...elementProps\n  } = componentProps;\n\n  const {\n    setTouched,\n    setFocused,\n    validationMode,\n    state: fieldState,\n    disabled: fieldDisabled,\n  } = useFieldRootContext();\n  const { labelId: fieldLabelId } = useLabelableContext();\n  const {\n    store,\n    setOpen,\n    selectionRef,\n    validation,\n    readOnly,\n    required,\n    alignItemWithTriggerActiveRef,\n    disabled: selectDisabled,\n    keyboardActiveRef,\n  } = useSelectRootContext();\n\n  const disabled = fieldDisabled || selectDisabled || disabledProp;\n\n  const open = useStore(store, selectors.open);\n  const value = useStore(store, selectors.value);\n  const triggerProps = useStore(store, selectors.triggerProps);\n  const positionerElement = useStore(store, selectors.positionerElement);\n  const listElement = useStore(store, selectors.listElement);\n  const rootId = useStore(store, selectors.id);\n  const selectLabelId = useStore(store, selectors.labelId);\n  const hasSelectedValue = useStore(store, selectors.hasSelectedValue);\n  const shouldCheckNullItemLabel = !hasSelectedValue && open;\n  const hasNullItemLabel = useStore(store, selectors.hasNullItemLabel, shouldCheckNullItemLabel);\n\n  const id = idProp ?? rootId;\n  const ariaLabelledBy = resolveAriaLabelledBy(fieldLabelId, selectLabelId);\n\n  useLabelableId({ id });\n\n  const positionerRef = useValueAsRef(positionerElement);\n\n  const triggerRef = React.useRef<HTMLElement | null>(null);\n\n  const { getButtonProps, buttonRef } = useButton({\n    disabled,\n    native: nativeButton,\n  });\n\n  const setTriggerElement = useStableCallback((element) => {\n    store.set('triggerElement', element);\n  });\n\n  const mergedRef = useMergedRefs<HTMLElement>(\n    forwardedRef,\n    triggerRef,\n    buttonRef,\n    setTriggerElement,\n  );\n\n  const timeoutFocus = useTimeout();\n  const timeoutMouseDown = useTimeout();\n  const selectedDelayTimeout = useTimeout();\n  const unselectedDelayTimeout = useTimeout();\n\n  React.useEffect(() => {\n    if (open) {\n      const hasSelectedItemInList = hasSelectedValue || hasNullItemLabel;\n      const shouldDelayUnselectedMouseUpLonger = !hasSelectedItemInList;\n\n      // When there is no selected item in the list (placeholder-only selects), a mousedown\n      // on the trigger followed by a quick mouseup over the first option can accidentally select\n      // within 200ms. Delay unselected mouseup to match the safer 400ms window.\n      if (shouldDelayUnselectedMouseUpLonger) {\n        selectedDelayTimeout.start(SELECTED_DELAY, () => {\n          selectionRef.current.allowUnselectedMouseUp = true;\n          selectionRef.current.allowSelectedMouseUp = true;\n        });\n      } else {\n        // mousedown -> move to unselected item -> mouseup should not select within 200ms.\n        unselectedDelayTimeout.start(UNSELECTED_DELAY, () => {\n          selectionRef.current.allowUnselectedMouseUp = true;\n\n          // mousedown -> mouseup on selected item should not select within 400ms.\n          selectedDelayTimeout.start(UNSELECTED_DELAY, () => {\n            selectionRef.current.allowSelectedMouseUp = true;\n          });\n        });\n      }\n\n      return () => {\n        selectedDelayTimeout.clear();\n        unselectedDelayTimeout.clear();\n      };\n    }\n\n    selectionRef.current = {\n      allowSelectedMouseUp: false,\n      allowUnselectedMouseUp: false,\n    };\n\n    timeoutMouseDown.clear();\n\n    return undefined;\n  }, [\n    open,\n    hasSelectedValue,\n    hasNullItemLabel,\n    selectionRef,\n    timeoutMouseDown,\n    selectedDelayTimeout,\n    unselectedDelayTimeout,\n  ]);\n\n  const ariaControlsId = React.useMemo(() => {\n    return listElement?.id ?? getFloatingFocusElement(positionerElement)?.id;\n  }, [listElement, positionerElement]);\n\n  const props: HTMLProps = mergeProps<'button'>(\n    triggerProps,\n    {\n      id,\n      role: 'combobox',\n      'aria-expanded': open ? 'true' : 'false',\n      'aria-haspopup': 'listbox',\n      'aria-controls': open ? ariaControlsId : undefined,\n      'aria-labelledby': ariaLabelledBy,\n      'aria-readonly': readOnly || undefined,\n      'aria-required': required || undefined,\n      tabIndex: disabled ? -1 : 0,\n      ref: mergedRef,\n      onFocus(event) {\n        setFocused(true);\n\n        // The popup element shouldn't obscure the focused trigger.\n        if (open && alignItemWithTriggerActiveRef.current) {\n          setOpen(false, createChangeEventDetails(REASONS.none, event.nativeEvent));\n        }\n\n        // Saves a re-render on initial click: `forceMount === true` mounts\n        // the items before `open === true`. We could sync those cycles better\n        // without a timeout, but this is enough for now.\n        //\n        // XXX: might be causing `act()` warnings.\n        timeoutFocus.start(0, () => {\n          store.set('forceMount', true);\n        });\n      },\n      onBlur(event) {\n        // If focus is moving into the popup, don't count it as a blur.\n        if (contains(positionerElement, event.relatedTarget)) {\n          return;\n        }\n\n        setTouched(true);\n        setFocused(false);\n\n        if (validationMode === 'onBlur') {\n          validation.commit(value);\n        }\n      },\n      onPointerMove() {\n        keyboardActiveRef.current = false;\n      },\n      onKeyDown() {\n        keyboardActiveRef.current = true;\n      },\n      onMouseDown(event) {\n        if (open) {\n          return;\n        }\n\n        const doc = ownerDocument(event.currentTarget);\n\n        function handleMouseUp(mouseEvent: MouseEvent) {\n          if (!triggerRef.current) {\n            return;\n          }\n\n          const mouseUpTarget = mouseEvent.target as Element | null;\n\n          // Early return if clicked on trigger element or its children\n          if (\n            contains(triggerRef.current, mouseUpTarget) ||\n            contains(positionerRef.current, mouseUpTarget) ||\n            mouseUpTarget === triggerRef.current\n          ) {\n            return;\n          }\n\n          const bounds = getPseudoElementBounds(triggerRef.current);\n\n          if (\n            mouseEvent.clientX >= bounds.left - BOUNDARY_OFFSET &&\n            mouseEvent.clientX <= bounds.right + BOUNDARY_OFFSET &&\n            mouseEvent.clientY >= bounds.top - BOUNDARY_OFFSET &&\n            mouseEvent.clientY <= bounds.bottom + BOUNDARY_OFFSET\n          ) {\n            return;\n          }\n\n          setOpen(false, createChangeEventDetails(REASONS.cancelOpen, mouseEvent));\n        }\n\n        // Firefox can fire this upon mousedown\n        timeoutMouseDown.start(0, () => {\n          doc.addEventListener('mouseup', handleMouseUp, { once: true });\n        });\n      },\n    },\n    validation.getValidationProps,\n    elementProps,\n    getButtonProps,\n  );\n\n  // ensure nested useButton does not overwrite the combobox role:\n  // <Toolbar.Button render={<Select.Trigger />} />\n  props.role = 'combobox';\n\n  const state: SelectTriggerState = {\n    ...fieldState,\n    open,\n    disabled,\n    value,\n    readOnly,\n    placeholder: !hasSelectedValue,\n  };\n\n  return useRenderElement('button', componentProps, {\n    ref: [forwardedRef, triggerRef],\n    state,\n    stateAttributesMapping,\n    props,\n  });\n});\n\nexport interface SelectTriggerState extends FieldRootState {\n  /**\n   * Whether the select popup is currently open.\n   */\n  open: boolean;\n  /**\n   * Whether the select popup is readonly.\n   */\n  readOnly: boolean;\n  /**\n   * The value of the currently selected item.\n   */\n  value: any;\n  /**\n   * Whether the select doesn't have a value.\n   */\n  placeholder: boolean;\n}\n\nexport interface SelectTriggerProps\n  extends NativeButtonProps, BaseUIComponentProps<'button', SelectTriggerState> {\n  children?: React.ReactNode;\n  /**\n   * Whether the component should ignore user interaction.\n   */\n  disabled?: boolean | undefined;\n}\n\nexport namespace SelectTrigger {\n  export type State = SelectTriggerState;\n  export type Props = SelectTriggerProps;\n}\n"
  },
  {
    "path": "packages/react/src/select/trigger/SelectTriggerDataAttributes.ts",
    "content": "import { CommonTriggerDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum SelectTriggerDataAttributes {\n  /**\n   * Present when the corresponding select is open.\n   */\n  popupOpen = CommonTriggerDataAttributes.popupOpen,\n  /**\n   * Present when the trigger is pressed.\n   */\n  pressed = CommonTriggerDataAttributes.pressed,\n  /**\n   * Present when the select is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the select is readonly.\n   */\n  readonly = 'data-readonly',\n  /**\n   * Present when the select is required.\n   */\n  required = 'data-required',\n  /**\n   * Present when the select is in valid state (when wrapped in Field.Root).\n   */\n  valid = 'data-valid',\n  /**\n   * Present when the select is in invalid state (when wrapped in Field.Root).\n   */\n  invalid = 'data-invalid',\n  /**\n   * Present when the select has been touched (when wrapped in Field.Root).\n   */\n  touched = 'data-touched',\n  /**\n   * Present when the select's value has changed (when wrapped in Field.Root).\n   */\n  dirty = 'data-dirty',\n  /**\n   * Present when the select has a value (when wrapped in Field.Root).\n   */\n  filled = 'data-filled',\n  /**\n   * Present when the select trigger is focused (when wrapped in Field.Root).\n   */\n  focused = 'data-focused',\n  /**\n   * Present when the select doesn't have a value.\n   */\n  placeholder = 'data-placeholder',\n}\n"
  },
  {
    "path": "packages/react/src/select/value/SelectValue.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { Select } from '@base-ui/react/select';\nimport { fireEvent, flushMicrotasks, screen } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Select.Value />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Select.Value />, () => ({\n    refInstanceof: window.HTMLSpanElement,\n    render(node) {\n      return render(<Select.Root open>{node}</Select.Root>);\n    },\n  }));\n\n  describe('prop: children', () => {\n    it('accepts a function with a value parameter', async () => {\n      const children = vi.fn();\n      await render(\n        <Select.Root value=\"1\">\n          <Select.Trigger>\n            <Select.Value>\n              {(value) => {\n                children(value);\n                return value;\n              }}\n            </Select.Value>\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"1\">one</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      fireEvent.click(screen.getByText('1'));\n      await flushMicrotasks();\n\n      expect(children.mock.calls[0]?.[0]).toBe('1');\n      expect(children.mock.calls[0]?.at(-1)).toBe('1');\n    });\n\n    it('overrides the text when children is a string', async () => {\n      await render(\n        <Select.Root value=\"1\">\n          <Select.Value>one</Select.Value>\n        </Select.Root>,\n      );\n\n      expect(screen.getByText('one')).not.toBe(null);\n    });\n  });\n\n  describe('prop: items (object format)', () => {\n    it('displays the label from items object when no children are provided', async () => {\n      const items = {\n        sans: 'Sans-serif',\n        serif: 'Serif',\n        mono: 'Monospace',\n      };\n\n      await render(\n        <Select.Root value=\"sans\" items={items}>\n          <Select.Trigger>\n            <Select.Value data-testid=\"value\" />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"sans\">Sans-serif</Select.Item>\n                <Select.Item value=\"serif\">Serif</Select.Item>\n                <Select.Item value=\"mono\">Monospace</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('Sans-serif');\n    });\n\n    it('updates the label when value changes with items object', async () => {\n      const items = {\n        sans: 'Sans-serif',\n        serif: 'Serif',\n        mono: 'Monospace',\n      };\n\n      function App() {\n        const [value, setValue] = React.useState<string | null>('sans');\n        return (\n          <div>\n            <button onClick={() => setValue('serif')}>serif</button>\n            <button onClick={() => setValue('mono')}>mono</button>\n            <Select.Root value={value} onValueChange={setValue} items={items}>\n              <Select.Trigger>\n                <Select.Value data-testid=\"value\" />\n              </Select.Trigger>\n              <Select.Portal>\n                <Select.Positioner>\n                  <Select.Popup>\n                    <Select.Item value=\"sans\">Sans-serif</Select.Item>\n                    <Select.Item value=\"serif\">Serif</Select.Item>\n                    <Select.Item value=\"mono\">Monospace</Select.Item>\n                  </Select.Popup>\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<App />);\n\n      expect(screen.getByTestId('value')).toHaveTextContent('Sans-serif');\n\n      await user.click(screen.getByRole('button', { name: 'serif' }));\n      expect(screen.getByTestId('value')).toHaveTextContent('Serif');\n\n      await user.click(screen.getByRole('button', { name: 'mono' }));\n      expect(screen.getByTestId('value')).toHaveTextContent('Monospace');\n    });\n\n    it('falls back to raw value when value is not in items object', async () => {\n      const items = {\n        sans: 'Sans-serif',\n        serif: 'Serif',\n      };\n\n      await render(\n        <Select.Root value=\"unknown\" items={items}>\n          <Select.Value data-testid=\"value\" />\n        </Select.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('unknown');\n    });\n\n    it('supports ReactNode labels in items object', async () => {\n      const items = {\n        sans: <span>Sans-serif</span>,\n        serif: <span>Serif</span>,\n      };\n\n      await render(\n        <Select.Root value=\"sans\" items={items}>\n          <Select.Value data-testid=\"value\" />\n        </Select.Root>,\n      );\n\n      expect(screen.getByTestId('value').querySelector('span')).toHaveTextContent('Sans-serif');\n    });\n\n    it('can lookup null value', async () => {\n      const items = {\n        sans: 'Sans-serif',\n        serif: 'Serif',\n        null: 'Null',\n      };\n\n      await render(\n        <Select.Root value={null} items={items}>\n          <Select.Value data-testid=\"value\" />\n        </Select.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('Null');\n    });\n  });\n\n  describe('prop: items (array format)', () => {\n    it('displays the label from items array when no children are provided', async () => {\n      const items = [\n        { value: 'sans', label: 'Sans-serif' },\n        { value: 'serif', label: 'Serif' },\n        { value: 'mono', label: 'Monospace' },\n      ];\n\n      await render(\n        <Select.Root value=\"serif\" items={items}>\n          <Select.Trigger>\n            <Select.Value data-testid=\"value\" />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"sans\">Sans-serif</Select.Item>\n                <Select.Item value=\"serif\">Serif</Select.Item>\n                <Select.Item value=\"mono\">Monospace</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('Serif');\n    });\n\n    it('updates the label when value changes with items array', async () => {\n      const items = [\n        { value: 'sans', label: 'Sans-serif' },\n        { value: 'serif', label: 'Serif' },\n        { value: 'mono', label: 'Monospace' },\n      ];\n\n      function App() {\n        const [value, setValue] = React.useState<string | null>('sans');\n        return (\n          <div>\n            <button onClick={() => setValue('serif')}>serif</button>\n            <button onClick={() => setValue('mono')}>mono</button>\n            <Select.Root value={value} onValueChange={setValue} items={items}>\n              <Select.Trigger>\n                <Select.Value data-testid=\"value\" />\n              </Select.Trigger>\n              <Select.Portal>\n                <Select.Positioner>\n                  <Select.Popup>\n                    <Select.Item value=\"sans\">Sans-serif</Select.Item>\n                    <Select.Item value=\"serif\">Serif</Select.Item>\n                    <Select.Item value=\"mono\">Monospace</Select.Item>\n                  </Select.Popup>\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<App />);\n\n      expect(screen.getByTestId('value')).toHaveTextContent('Sans-serif');\n\n      await user.click(screen.getByRole('button', { name: 'serif' }));\n      expect(screen.getByTestId('value')).toHaveTextContent('Serif');\n\n      await user.click(screen.getByRole('button', { name: 'mono' }));\n      expect(screen.getByTestId('value')).toHaveTextContent('Monospace');\n    });\n\n    it('falls back to raw value when value is not in items array', async () => {\n      const items = [\n        { value: 'sans', label: 'Sans-serif' },\n        { value: 'serif', label: 'Serif' },\n      ];\n\n      await render(\n        <Select.Root value=\"unknown\" items={items}>\n          <Select.Value data-testid=\"value\" />\n        </Select.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('unknown');\n    });\n\n    it('supports ReactNode labels in items array', async () => {\n      const items = [\n        { value: 'bold', label: <strong>Bold Text</strong> },\n        { value: 'italic', label: <em>Italic Text</em> },\n      ];\n\n      await render(\n        <Select.Root value=\"bold\" items={items}>\n          <Select.Value data-testid=\"value\" />\n        </Select.Root>,\n      );\n\n      expect(screen.getByTestId('value').querySelector('strong')).toHaveTextContent('Bold Text');\n    });\n\n    it('is not stale after being updated', async () => {\n      function App() {\n        const [value, setValue] = React.useState<string | null>('a');\n        const [items, setItems] = React.useState([\n          { value: 'a', label: 'a' },\n          { value: 'b', label: 'b' },\n        ]);\n\n        function updateItems() {\n          setItems([\n            { value: 'a', label: 'a new' },\n            { value: 'b', label: 'b new' },\n            { value: 'c', label: 'c' },\n          ]);\n        }\n\n        return (\n          <div>\n            <button onClick={updateItems}>update</button>\n            <button onClick={() => setValue('c')}>select c</button>\n            <Select.Root value={value} onValueChange={setValue} items={items}>\n              <Select.Trigger>\n                <Select.Value data-testid=\"value\" />\n              </Select.Trigger>\n              <Select.Portal>\n                <Select.Positioner>\n                  <Select.Popup>\n                    {items.map((item) => (\n                      <Select.Item key={item.value} value={item.value}>\n                        {item.label}\n                      </Select.Item>\n                    ))}\n                  </Select.Popup>\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n          </div>\n        );\n      }\n\n      const { user } = await render(<App />);\n\n      expect(screen.getByTestId('value')).toHaveTextContent('a');\n\n      await user.click(screen.getByRole('button', { name: 'update' }));\n\n      expect(screen.getByTestId('value')).toHaveTextContent('a new');\n\n      await user.click(screen.getByRole('button', { name: 'select c' }));\n\n      expect(screen.getByTestId('value')).toHaveTextContent('c');\n    });\n  });\n\n  describe('prop: itemToStringLabel', () => {\n    it('uses custom itemToStringLabel function', async () => {\n      const items = [\n        { country: 'United States', code: 'US' },\n        { country: 'Canada', code: 'CA' },\n      ];\n\n      await render(\n        <Select.Root\n          value={items[1]}\n          itemToStringLabel={(i: any) => i.country}\n          itemToStringValue={(i: any) => i.code}\n        >\n          <Select.Trigger>\n            <Select.Value data-testid=\"value\" />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                {items.map((it) => (\n                  <Select.Item key={it.code} value={it}>\n                    {it.country}\n                  </Select.Item>\n                ))}\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('Canada');\n    });\n\n    it('falls back to label/value properties when functions are not provided', async () => {\n      const items = [\n        { label: 'United States', value: 'US' },\n        { label: 'Canada', value: 'CA' },\n      ];\n\n      await render(\n        <Select.Root name=\"country\" value={items[1]}>\n          <Select.Trigger>\n            <Select.Value data-testid=\"value\" />\n          </Select.Trigger>\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                {items.map((it) => (\n                  <Select.Item key={it.value} value={it}>\n                    {it.label}\n                  </Select.Item>\n                ))}\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      const hiddenInput = screen.getByRole('textbox', {\n        hidden: true,\n      });\n      expect(hiddenInput).toHaveValue('CA');\n      expect(hiddenInput).toHaveAttribute('name', 'country');\n    });\n  });\n\n  describe('prop: children (takes precedence over items)', () => {\n    it('uses children string over items object', async () => {\n      const items = {\n        sans: 'Sans-serif',\n        serif: 'Serif',\n      };\n\n      await render(\n        <Select.Root value=\"sans\" items={items}>\n          <Select.Value data-testid=\"value\">Custom Text</Select.Value>\n        </Select.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('Custom Text');\n    });\n\n    it('uses children function over items array', async () => {\n      const items = [\n        { value: 'sans', label: 'Sans-serif' },\n        { value: 'serif', label: 'Serif' },\n      ];\n\n      await render(\n        <Select.Root value=\"sans\" items={items}>\n          <Select.Value data-testid=\"value\">{(value) => `Custom: ${value}`}</Select.Value>\n        </Select.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('Custom: sans');\n    });\n  });\n\n  it('changes text when the value changes', async () => {\n    function App() {\n      const [value, setValue] = React.useState<string | null>(null);\n      return (\n        <div>\n          <button onClick={() => setValue('1')}>1</button>\n          <button onClick={() => setValue('2')}>2</button>\n          <button onClick={() => setValue(null)}>null</button>\n          <Select.Root value={value} onValueChange={setValue}>\n            <Select.Trigger>\n              <Select.Value data-testid=\"value\">{(val) => val ?? 'initial'}</Select.Value>\n            </Select.Trigger>\n            <Select.Portal>\n              <Select.Positioner>\n                <Select.Popup>\n                  <Select.Item value=\"1\">1</Select.Item>\n                  <Select.Item value=\"2\">2</Select.Item>\n                </Select.Popup>\n              </Select.Positioner>\n            </Select.Portal>\n          </Select.Root>\n        </div>\n      );\n    }\n\n    const { user } = await render(<App />);\n\n    await user.click(screen.getByText('initial'));\n    await flushMicrotasks();\n\n    await user.click(screen.getByRole('button', { name: '1' }));\n    expect(screen.getByTestId('value')).toHaveTextContent('1');\n\n    await user.click(screen.getByRole('button', { name: '2' }));\n    expect(screen.getByTestId('value')).toHaveTextContent('2');\n\n    await user.click(screen.getByRole('button', { name: 'null' }));\n    expect(screen.getByTestId('value')).toHaveTextContent('initial');\n  });\n\n  describe('prop: multiple', () => {\n    it('displays comma-separated labels for multiple values with items object', async () => {\n      const items = {\n        sans: 'Sans-serif',\n        serif: 'Serif',\n        mono: 'Monospace',\n      };\n\n      await render(\n        <Select.Root value={['sans', 'serif']} items={items} multiple>\n          <Select.Trigger>\n            <span data-testid=\"value\">\n              <Select.Value />\n            </span>\n          </Select.Trigger>\n        </Select.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('Sans-serif, Serif');\n    });\n\n    it('displays comma-separated labels for multiple values with items array', async () => {\n      const items = [\n        { value: 'serif', label: 'Serif' },\n        { value: 'mono', label: 'Monospace' },\n      ];\n\n      await render(\n        <Select.Root value={['serif', 'mono']} items={items} multiple>\n          <Select.Trigger>\n            <span data-testid=\"value\">\n              <Select.Value />\n            </span>\n          </Select.Trigger>\n        </Select.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('Serif, Monospace');\n    });\n\n    it('supports ReactNode labels for multiple selections', async () => {\n      const items = [\n        { value: 'bold', label: <strong>Bold Text</strong> },\n        { value: 'italic', label: <em>Italic Text</em> },\n      ];\n\n      await render(\n        <Select.Root value={['bold', 'italic']} items={items} multiple>\n          <Select.Trigger>\n            <span data-testid=\"value\">\n              <Select.Value />\n            </span>\n          </Select.Trigger>\n        </Select.Root>,\n      );\n\n      const value = screen.getByTestId('value');\n      expect(value.querySelector('strong')).toHaveTextContent('Bold Text');\n      expect(value.querySelector('em')).toHaveTextContent('Italic Text');\n      expect(value).toHaveTextContent('Bold Text, Italic Text');\n    });\n\n    it('falls back to raw values when no items are provided', async () => {\n      await render(\n        <Select.Root value={['serif', 'mono']} multiple>\n          <Select.Trigger>\n            <span data-testid=\"value\">\n              <Select.Value />\n            </span>\n          </Select.Trigger>\n        </Select.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('serif, mono');\n    });\n\n    it('displays single value when only one value is selected in multiple mode', async () => {\n      await render(\n        <Select.Root value={['sans']} multiple>\n          <Select.Value data-testid=\"value\" />\n        </Select.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('sans');\n    });\n\n    it('displays empty when no values are selected in multiple mode', async () => {\n      await render(\n        <Select.Root value={[]} multiple>\n          <Select.Value data-testid=\"value\" />\n        </Select.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('');\n    });\n\n    it('children function receives array of values in multiple mode', async () => {\n      const children = vi.fn();\n      const items = {\n        sans: 'Sans-serif',\n        serif: 'Serif',\n      };\n\n      await render(\n        <Select.Root value={['sans', 'serif']} items={items} multiple>\n          <Select.Value>\n            {(values) => {\n              children(values);\n              return `Selected: ${Array.isArray(values) ? values.join(' + ') : values}`;\n            }}\n          </Select.Value>\n        </Select.Root>,\n      );\n\n      expect(children.mock.calls[0]?.[0]).toEqual(['sans', 'serif']);\n      expect(screen.getByText('Selected: sans + serif')).not.toBe(null);\n    });\n\n    it('children prop takes precedence over items in multiple mode', async () => {\n      const items = {\n        sans: 'Sans-serif',\n        serif: 'Serif',\n      };\n\n      await render(\n        <Select.Root value={['sans', 'serif']} items={items} multiple>\n          <Select.Value data-testid=\"value\">Custom Multiple Text</Select.Value>\n        </Select.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('Custom Multiple Text');\n    });\n\n    it('defaults to empty array when no value is provided', async () => {\n      const renderValue = vi.fn();\n\n      await render(\n        <Select.Root multiple>\n          <Select.Value>{renderValue}</Select.Value>\n        </Select.Root>,\n      );\n\n      expect(renderValue.mock.calls[0]?.[0]).toEqual([]);\n    });\n  });\n\n  describe('prop: placeholder', () => {\n    it('displays placeholder when no value is selected', async () => {\n      await render(\n        <Select.Root>\n          <Select.Value data-testid=\"value\" placeholder=\"Select an option\" />\n        </Select.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('Select an option');\n    });\n\n    it('displays placeholder when value is null', async () => {\n      await render(\n        <Select.Root value={null}>\n          <Select.Value data-testid=\"value\" placeholder=\"Select an option\" />\n        </Select.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('Select an option');\n    });\n\n    it('does not display placeholder when value is selected', async () => {\n      await render(\n        <Select.Root value=\"option1\">\n          <Select.Value data-testid=\"value\" placeholder=\"Select an option\" />\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value=\"option1\">Option 1</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('option1');\n    });\n\n    it('children prop takes precedence over placeholder', async () => {\n      await render(\n        <Select.Root>\n          <Select.Value data-testid=\"value\" placeholder=\"Select an option\">\n            Custom Text\n          </Select.Value>\n        </Select.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('Custom Text');\n    });\n\n    it('children function takes precedence over placeholder', async () => {\n      await render(\n        <Select.Root>\n          <Select.Value data-testid=\"value\" placeholder=\"Select an option\">\n            {(value) => value || 'Function fallback'}\n          </Select.Value>\n        </Select.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('Function fallback');\n    });\n\n    it('null item label in items takes precedence over placeholder', async () => {\n      const items = [\n        { value: null, label: 'None' },\n        { value: 'option1', label: 'Option 1' },\n      ];\n\n      await render(\n        <Select.Root items={items}>\n          <Select.Value data-testid=\"value\" placeholder=\"Select an option\" />\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value={null}>None</Select.Item>\n                <Select.Item value=\"option1\">Option 1</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('None');\n    });\n\n    it('uses placeholder when items have null value without label', async () => {\n      const items = [\n        { value: null, label: null },\n        { value: 'option1', label: 'Option 1' },\n      ];\n\n      await render(\n        <Select.Root items={items}>\n          <Select.Value data-testid=\"value\" placeholder=\"Select an option\" />\n          <Select.Portal>\n            <Select.Positioner>\n              <Select.Popup>\n                <Select.Item value={null}>None</Select.Item>\n                <Select.Item value=\"option1\">Option 1</Select.Item>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </Select.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('Select an option');\n    });\n\n    it('displays placeholder when object items do not have a null key', async () => {\n      const items = {\n        option1: 'Option 1',\n        option2: 'Option 2',\n      };\n\n      await render(\n        <Select.Root items={items}>\n          <Select.Value data-testid=\"value\" placeholder=\"Select an option\" />\n        </Select.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('Select an option');\n    });\n\n    it('null key label in object items takes precedence over placeholder', async () => {\n      const items = {\n        null: 'None',\n        option1: 'Option 1',\n      };\n\n      await render(\n        <Select.Root items={items}>\n          <Select.Value data-testid=\"value\" placeholder=\"Select an option\" />\n        </Select.Root>,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent('None');\n    });\n\n    it('supports ReactNode as placeholder', async () => {\n      await render(\n        <Select.Root>\n          <Select.Value\n            data-testid=\"value\"\n            placeholder={<span data-testid=\"placeholder\">Select an option</span>}\n          />\n        </Select.Root>,\n      );\n\n      expect(screen.getByTestId('placeholder')).toHaveTextContent('Select an option');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/select/value/SelectValue.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStore } from '@base-ui/utils/store';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useSelectRootContext } from '../root/SelectRootContext';\nimport { resolveMultipleLabels, resolveSelectedLabel } from '../../utils/resolveValueLabel';\nimport { selectors } from '../store';\nimport { StateAttributesMapping } from '../../utils/getStateAttributesProps';\n\nconst stateAttributesMapping: StateAttributesMapping<SelectValueState> = {\n  value: () => null,\n};\n\n/**\n * A text label of the currently selected item.\n * Renders a `<span>` element.\n *\n * Documentation: [Base UI Select](https://base-ui.com/react/components/select)\n */\nexport const SelectValue = React.forwardRef(function SelectValue(\n  componentProps: SelectValue.Props,\n  forwardedRef: React.ForwardedRef<HTMLSpanElement>,\n) {\n  const {\n    className,\n    render,\n    children: childrenProp,\n    placeholder,\n    ...elementProps\n  } = componentProps;\n\n  const { store, valueRef } = useSelectRootContext();\n\n  const value = useStore(store, selectors.value);\n  const items = useStore(store, selectors.items);\n  const itemToStringLabel = useStore(store, selectors.itemToStringLabel);\n  const hasSelectedValue = useStore(store, selectors.hasSelectedValue);\n\n  const shouldCheckNullItemLabel = !hasSelectedValue && placeholder != null && childrenProp == null;\n  const hasNullLabel = useStore(store, selectors.hasNullItemLabel, shouldCheckNullItemLabel);\n\n  const state: SelectValueState = {\n    value,\n    placeholder: !hasSelectedValue,\n  };\n\n  let children = null;\n  if (typeof childrenProp === 'function') {\n    children = childrenProp(value);\n  } else if (childrenProp != null) {\n    children = childrenProp;\n  } else if (!hasSelectedValue && placeholder != null && !hasNullLabel) {\n    children = placeholder;\n  } else if (Array.isArray(value)) {\n    children = resolveMultipleLabels(value, items, itemToStringLabel);\n  } else {\n    children = resolveSelectedLabel(value, items, itemToStringLabel);\n  }\n\n  const element = useRenderElement('span', componentProps, {\n    state,\n    ref: [forwardedRef, valueRef],\n    props: [{ children }, elementProps],\n    stateAttributesMapping,\n  });\n\n  return element;\n});\n\nexport interface SelectValueState {\n  /**\n   * The value of the currently selected item.\n   */\n  value: any;\n  /**\n   * Whether the placeholder is being displayed.\n   */\n  placeholder: boolean;\n}\n\nexport interface SelectValueProps extends Omit<\n  BaseUIComponentProps<'span', SelectValueState>,\n  'children'\n> {\n  /**\n   * Accepts a function that returns a `ReactNode` to format the selected value.\n   * @example\n   * ```tsx\n   * <Select.Value>\n   *   {(value: string | null) => value ? labels[value] : 'No value'}\n   * </Select.Value>\n   * ```\n   */\n  children?: React.ReactNode | ((value: any) => React.ReactNode);\n  /**\n   * The placeholder value to display when no value is selected.\n   * This is overridden by `children` if specified, or by a null item's label in `items`.\n   */\n  placeholder?: React.ReactNode;\n}\n\nexport namespace SelectValue {\n  export type State = SelectValueState;\n  export type Props = SelectValueProps;\n}\n"
  },
  {
    "path": "packages/react/src/select/value/SelectValueDataAttributes.ts",
    "content": "export enum SelectValueDataAttributes {\n  /**\n   * Present when the select doesn't have a value.\n   */\n  placeholder = 'data-placeholder',\n}\n"
  },
  {
    "path": "packages/react/src/separator/Separator.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { screen } from '@mui/internal-test-utils';\nimport { Separator } from '@base-ui/react/separator';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Separator />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Separator />, () => ({\n    render,\n    refInstanceof: window.HTMLDivElement,\n  }));\n\n  it('renders a div with the `separator` role', async () => {\n    await render(<Separator />);\n    expect(screen.getByRole('separator')).toBeVisible();\n  });\n\n  describe('prop: orientation', () => {\n    ['horizontal', 'vertical'].forEach((orientation) => {\n      it(orientation, async () => {\n        await render(<Separator orientation={orientation as Separator.Props['orientation']} />);\n\n        expect(screen.getByRole('separator')).toHaveAttribute('aria-orientation', orientation);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/separator/Separator.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport type { BaseUIComponentProps, Orientation } from '../utils/types';\nimport { useRenderElement } from '../utils/useRenderElement';\n\n/**\n * A separator element accessible to screen readers.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Separator](https://base-ui.com/react/components/separator)\n */\nexport const Separator = React.forwardRef(function SeparatorComponent(\n  componentProps: Separator.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { className, render, orientation = 'horizontal', ...elementProps } = componentProps;\n\n  const state: SeparatorState = { orientation };\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: forwardedRef,\n    props: [{ role: 'separator', 'aria-orientation': orientation }, elementProps],\n  });\n\n  return element;\n});\n\nexport interface SeparatorProps extends BaseUIComponentProps<'div', SeparatorState> {\n  /**\n   * The orientation of the separator.\n   * @default 'horizontal'\n   */\n  orientation?: Orientation | undefined;\n}\n\nexport interface SeparatorState {\n  /**\n   * The orientation of the separator.\n   */\n  orientation: Orientation;\n}\n\nexport namespace Separator {\n  export type Props = SeparatorProps;\n  export type State = SeparatorState;\n}\n"
  },
  {
    "path": "packages/react/src/separator/index.ts",
    "content": "export { Separator } from './Separator';\n\nexport type * from './Separator';\n"
  },
  {
    "path": "packages/react/src/slider/control/SliderControl.test.tsx",
    "content": "import { Slider } from '@base-ui/react/slider';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Slider.Control />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Slider.Control />, () => ({\n    render: (node) => {\n      return render(<Slider.Root>{node}</Slider.Root>);\n    },\n    refInstanceof: window.HTMLDivElement,\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/slider/control/SliderControl.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { isElement } from '@floating-ui/utils/dom';\nimport { ownerDocument } from '@base-ui/utils/owner';\nimport { useAnimationFrame } from '@base-ui/utils/useAnimationFrame';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useValueAsRef } from '@base-ui/utils/useValueAsRef';\nimport { activeElement, contains } from '../../floating-ui-react/utils';\nimport type { Coords } from '../../floating-ui-react/types';\nimport { clamp } from '../../utils/clamp';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport {\n  createChangeEventDetails,\n  createGenericEventDetails,\n} from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useDirection } from '../../direction-provider/DirectionContext';\nimport { useSliderRootContext } from '../root/SliderRootContext';\nimport { sliderStateAttributesMapping } from '../root/stateAttributesMapping';\nimport type { SliderRootState } from '../root/SliderRoot';\nimport { getMidpoint } from '../utils/getMidpoint';\nimport { roundValueToStep } from '../utils/roundValueToStep';\nimport { validateMinimumDistance } from '../utils/validateMinimumDistance';\nimport { resolveThumbCollision } from '../utils/resolveThumbCollision';\n\nconst INTENTIONAL_DRAG_COUNT_THRESHOLD = 2;\n\nfunction getControlOffset(styles: CSSStyleDeclaration | null, vertical: boolean) {\n  if (!styles) {\n    return {\n      start: 0,\n      end: 0,\n    };\n  }\n\n  function parseSize(value: string | null | undefined) {\n    const parsed = value != null ? parseFloat(value) : 0;\n    return Number.isNaN(parsed) ? 0 : parsed;\n  }\n\n  const start = !vertical ? 'InlineStart' : 'Top';\n  const end = !vertical ? 'InlineEnd' : 'Bottom';\n\n  return {\n    start: parseSize(styles[`border${start}Width`]) + parseSize(styles[`padding${start}`]),\n    end: parseSize(styles[`border${end}Width`]) + parseSize(styles[`padding${end}`]),\n  };\n}\n\nfunction getFingerCoords(\n  event: TouchEvent | PointerEvent | React.PointerEvent,\n  touchIdRef: React.RefObject<number | null>,\n): Coords | null {\n  // The event is TouchEvent\n  if (touchIdRef.current != null && (event as TouchEvent).changedTouches) {\n    const touchEvent = event as TouchEvent;\n    for (let i = 0; i < touchEvent.changedTouches.length; i += 1) {\n      const touch = touchEvent.changedTouches[i];\n      if (touch.identifier === touchIdRef.current) {\n        return {\n          x: touch.clientX,\n          y: touch.clientY,\n        };\n      }\n    }\n\n    return null;\n  }\n\n  // The event is PointerEvent\n  return {\n    x: (event as PointerEvent).clientX,\n    y: (event as PointerEvent).clientY,\n  };\n}\n\n/**\n * The clickable, interactive part of the slider.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Slider](https://base-ui.com/react/components/slider)\n */\nexport const SliderControl = React.forwardRef(function SliderControl(\n  componentProps: SliderControl.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render: renderProp, className, ...elementProps } = componentProps;\n\n  const {\n    disabled,\n    dragging,\n    inset,\n    lastChangedValueRef,\n    lastChangeReasonRef,\n    max,\n    min,\n    minStepsBetweenValues,\n    onValueCommitted,\n    orientation,\n    pressedInputRef,\n    pressedThumbCenterOffsetRef,\n    pressedThumbIndexRef,\n    pressedValuesRef,\n    registerFieldControlRef,\n    renderBeforeHydration,\n    setActive,\n    setDragging,\n    setValue,\n    state,\n    step,\n    thumbCollisionBehavior,\n    thumbRefs,\n    values,\n  } = useSliderRootContext();\n\n  const direction = useDirection();\n  const range = values.length > 1;\n  const vertical = orientation === 'vertical';\n\n  const controlRef = React.useRef<HTMLElement>(null);\n  const stylesRef = React.useRef<CSSStyleDeclaration>(null);\n  const setStylesRef = useStableCallback((element: HTMLElement | null) => {\n    if (element && stylesRef.current == null) {\n      if (stylesRef.current == null) {\n        stylesRef.current = getComputedStyle(element);\n      }\n    }\n  });\n\n  // A number that uniquely identifies the current finger in the touch session.\n  const touchIdRef = React.useRef<number>(null);\n  // The number of touch/pointermove events that have fired.\n  const moveCountRef = React.useRef(0);\n  // The offset amount to each side of the control for inset sliders.\n  // This value should be equal to the radius or half the width/height of the thumb.\n  const insetThumbOffsetRef = React.useRef(0);\n  const latestValuesRef = useValueAsRef(values);\n\n  const updatePressedThumb = useStableCallback((nextIndex: number) => {\n    if (pressedThumbIndexRef.current !== nextIndex) {\n      pressedThumbIndexRef.current = nextIndex;\n    }\n\n    const thumbElement = thumbRefs.current[nextIndex];\n\n    if (!thumbElement) {\n      pressedThumbCenterOffsetRef.current = null;\n      pressedInputRef.current = null;\n      return;\n    }\n\n    pressedInputRef.current = thumbElement.querySelector<HTMLInputElement>('input[type=\"range\"]');\n  });\n\n  const getFingerState = useStableCallback((fingerCoords: Coords): FingerState | null => {\n    const control = controlRef.current;\n\n    if (!control) {\n      return null;\n    }\n\n    const { width, height, bottom, left, right } = control.getBoundingClientRect();\n\n    const controlOffset = getControlOffset(stylesRef.current, vertical);\n    const insetThumbOffset = insetThumbOffsetRef.current;\n    const controlSize =\n      (vertical ? height : width) - controlOffset.start - controlOffset.end - insetThumbOffset * 2;\n    const thumbCenterOffset = pressedThumbCenterOffsetRef.current ?? 0;\n    const fingerX = fingerCoords.x - thumbCenterOffset;\n    const fingerY = fingerCoords.y - thumbCenterOffset;\n\n    const valueSize = vertical\n      ? bottom - fingerY - controlOffset.end\n      : (direction === 'rtl' ? right - fingerX : fingerX - left) - controlOffset.start;\n    // the value at the finger origin scaled down to fit the range [0, 1]\n    const valueRescaled = clamp((valueSize - insetThumbOffset) / controlSize, 0, 1);\n\n    let newValue = (max - min) * valueRescaled + min;\n    newValue = roundValueToStep(newValue, step, min);\n    newValue = clamp(newValue, min, max);\n\n    if (!range) {\n      return {\n        value: newValue,\n        thumbIndex: 0,\n        didSwap: false,\n      };\n    }\n\n    const thumbIndex = pressedThumbIndexRef.current;\n\n    if (thumbIndex < 0) {\n      return null;\n    }\n\n    const collisionResult = resolveThumbCollision({\n      behavior: thumbCollisionBehavior,\n      values,\n      currentValues: latestValuesRef.current ?? values,\n      initialValues: pressedValuesRef.current,\n      pressedIndex: thumbIndex,\n      nextValue: newValue,\n      min,\n      max,\n      step,\n      minStepsBetweenValues,\n    });\n\n    if (thumbCollisionBehavior === 'swap' && collisionResult.didSwap) {\n      updatePressedThumb(collisionResult.thumbIndex);\n    } else {\n      pressedThumbIndexRef.current = collisionResult.thumbIndex;\n    }\n\n    return collisionResult;\n  });\n\n  const startPressing = useStableCallback((fingerCoords: Coords) => {\n    pressedValuesRef.current = range ? values.slice() : null;\n    latestValuesRef.current = values;\n\n    const pressedThumbIndex = pressedThumbIndexRef.current;\n    let closestThumbIndex = pressedThumbIndex;\n\n    if (pressedThumbIndex > -1 && pressedThumbIndex < values.length) {\n      if (values[pressedThumbIndex] === max) {\n        let candidateIndex = pressedThumbIndex;\n\n        while (candidateIndex > 0 && values[candidateIndex - 1] === max) {\n          candidateIndex -= 1;\n        }\n\n        closestThumbIndex = candidateIndex;\n      }\n    } else {\n      // pressed on control\n      const axis = !vertical ? 'x' : 'y';\n      let minDistance: number | undefined;\n\n      closestThumbIndex = -1;\n\n      for (let i = 0; i < thumbRefs.current.length; i += 1) {\n        const thumbEl = thumbRefs.current[i];\n        if (isElement(thumbEl)) {\n          const midpoint = getMidpoint(thumbEl);\n          const distance = Math.abs(fingerCoords[axis] - midpoint[axis]);\n\n          if (minDistance === undefined || distance <= minDistance) {\n            closestThumbIndex = i;\n            minDistance = distance;\n          }\n        }\n      }\n    }\n\n    if (closestThumbIndex > -1 && closestThumbIndex !== pressedThumbIndex) {\n      updatePressedThumb(closestThumbIndex);\n    }\n\n    if (inset) {\n      const thumbEl = thumbRefs.current[closestThumbIndex];\n      if (isElement(thumbEl)) {\n        const thumbRect = thumbEl.getBoundingClientRect();\n        const side = !vertical ? 'width' : 'height';\n        insetThumbOffsetRef.current = thumbRect[side] / 2;\n      }\n    }\n  });\n\n  const focusThumb = useStableCallback((thumbIndex: number) => {\n    thumbRefs.current?.[thumbIndex]\n      ?.querySelector<HTMLInputElement>('input[type=\"range\"]')\n      ?.focus({ preventScroll: true });\n  });\n\n  const handleTouchMove = useStableCallback((nativeEvent: TouchEvent | PointerEvent) => {\n    const fingerCoords = getFingerCoords(nativeEvent, touchIdRef);\n\n    if (fingerCoords == null) {\n      return;\n    }\n\n    moveCountRef.current += 1;\n\n    // Cancel move in case some other element consumed a pointerup event and it was not fired.\n    if (nativeEvent.type === 'pointermove' && (nativeEvent as PointerEvent).buttons === 0) {\n      handleTouchEnd(nativeEvent);\n      return;\n    }\n\n    const finger = getFingerState(fingerCoords);\n\n    if (finger == null) {\n      return;\n    }\n\n    if (validateMinimumDistance(finger.value, step, minStepsBetweenValues)) {\n      if (!dragging && moveCountRef.current > INTENTIONAL_DRAG_COUNT_THRESHOLD) {\n        setDragging(true);\n      }\n\n      setValue(\n        finger.value,\n        createChangeEventDetails(REASONS.drag, nativeEvent, undefined, {\n          activeThumbIndex: finger.thumbIndex,\n        }),\n      );\n\n      latestValuesRef.current = Array.isArray(finger.value) ? finger.value : [finger.value];\n\n      if (finger.didSwap) {\n        focusThumb(finger.thumbIndex);\n      }\n    }\n  });\n\n  function handleTouchEnd(nativeEvent: TouchEvent | PointerEvent) {\n    setActive(-1);\n    setDragging(false);\n\n    pressedInputRef.current = null;\n    pressedThumbCenterOffsetRef.current = null;\n\n    const fingerCoords = getFingerCoords(nativeEvent, touchIdRef);\n    const finger = fingerCoords != null ? getFingerState(fingerCoords) : null;\n\n    if (finger != null) {\n      const commitReason = lastChangeReasonRef.current;\n      onValueCommitted(\n        lastChangedValueRef.current ?? finger.value,\n        createGenericEventDetails(commitReason, nativeEvent),\n      );\n    }\n\n    if (\n      'pointerType' in nativeEvent &&\n      controlRef.current?.hasPointerCapture(nativeEvent.pointerId)\n    ) {\n      controlRef.current?.releasePointerCapture(nativeEvent.pointerId);\n    }\n\n    pressedThumbIndexRef.current = -1;\n    touchIdRef.current = null;\n    pressedValuesRef.current = null;\n    // eslint-disable-next-line @typescript-eslint/no-use-before-define\n    stopListening();\n  }\n\n  const handleTouchStart = useStableCallback((nativeEvent: TouchEvent) => {\n    if (disabled) {\n      return;\n    }\n\n    const touch = nativeEvent.changedTouches[0];\n\n    if (touch != null) {\n      touchIdRef.current = touch.identifier;\n    }\n\n    const fingerCoords = getFingerCoords(nativeEvent, touchIdRef);\n\n    if (fingerCoords != null) {\n      startPressing(fingerCoords);\n\n      const finger = getFingerState(fingerCoords);\n\n      if (finger == null) {\n        return;\n      }\n\n      focusThumb(finger.thumbIndex);\n      setValue(\n        finger.value,\n        createChangeEventDetails(REASONS.trackPress, nativeEvent, undefined, {\n          activeThumbIndex: finger.thumbIndex,\n        }),\n      );\n\n      latestValuesRef.current = Array.isArray(finger.value) ? finger.value : [finger.value];\n\n      if (finger.didSwap) {\n        focusThumb(finger.thumbIndex);\n      }\n    }\n\n    moveCountRef.current = 0;\n    const doc = ownerDocument(controlRef.current);\n    doc.addEventListener('touchmove', handleTouchMove, { passive: true });\n    doc.addEventListener('touchend', handleTouchEnd, { passive: true });\n  });\n\n  const stopListening = useStableCallback(() => {\n    const doc = ownerDocument(controlRef.current);\n    doc.removeEventListener('pointermove', handleTouchMove);\n    doc.removeEventListener('pointerup', handleTouchEnd);\n    doc.removeEventListener('touchmove', handleTouchMove);\n    doc.removeEventListener('touchend', handleTouchEnd);\n    pressedValuesRef.current = null;\n  });\n\n  const focusFrame = useAnimationFrame();\n\n  React.useEffect(() => {\n    const control = controlRef.current;\n    if (!control) {\n      return () => stopListening();\n    }\n\n    control.addEventListener('touchstart', handleTouchStart, {\n      passive: true,\n    });\n\n    return () => {\n      control.removeEventListener('touchstart', handleTouchStart);\n      focusFrame.cancel();\n\n      stopListening();\n    };\n  }, [stopListening, handleTouchStart, controlRef, focusFrame]);\n\n  React.useEffect(() => {\n    if (disabled) {\n      stopListening();\n    }\n  }, [disabled, stopListening]);\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: [forwardedRef, registerFieldControlRef, controlRef, setStylesRef],\n    props: [\n      {\n        ['data-base-ui-slider-control' as string]: renderBeforeHydration ? '' : undefined,\n        onPointerDown(event) {\n          const control = controlRef.current;\n\n          if (\n            !control ||\n            disabled ||\n            event.defaultPrevented ||\n            !isElement(event.target) ||\n            // Only handle left clicks\n            event.button !== 0\n          ) {\n            return;\n          }\n\n          const fingerCoords = getFingerCoords(event, touchIdRef);\n\n          if (fingerCoords != null) {\n            startPressing(fingerCoords);\n\n            const finger = getFingerState(fingerCoords);\n\n            if (finger == null) {\n              return;\n            }\n\n            const pressedOnFocusedThumb = contains(\n              thumbRefs.current[finger.thumbIndex],\n              activeElement(ownerDocument(control)),\n            );\n\n            if (pressedOnFocusedThumb) {\n              event.preventDefault();\n            } else {\n              focusFrame.request(() => {\n                focusThumb(finger.thumbIndex);\n              });\n            }\n\n            setDragging(true);\n\n            const pressedOnAnyThumb = pressedThumbCenterOffsetRef.current != null;\n            if (!pressedOnAnyThumb) {\n              setValue(\n                finger.value,\n                createChangeEventDetails(REASONS.trackPress, event.nativeEvent, undefined, {\n                  activeThumbIndex: finger.thumbIndex,\n                }),\n              );\n\n              latestValuesRef.current = Array.isArray(finger.value) ? finger.value : [finger.value];\n\n              if (finger.didSwap) {\n                focusThumb(finger.thumbIndex);\n              }\n            }\n          }\n\n          if (event.nativeEvent.pointerId) {\n            control.setPointerCapture(event.nativeEvent.pointerId);\n          }\n\n          moveCountRef.current = 0;\n          const doc = ownerDocument(controlRef.current);\n          doc.addEventListener('pointermove', handleTouchMove, { passive: true });\n          doc.addEventListener('pointerup', handleTouchEnd, { once: true });\n        },\n        tabIndex: -1,\n      },\n      elementProps,\n    ],\n    stateAttributesMapping: sliderStateAttributesMapping,\n  });\n\n  return element;\n});\n\ninterface FingerState {\n  value: number | number[];\n  thumbIndex: number;\n  didSwap: boolean;\n}\n\nexport interface SliderControlState extends SliderRootState {}\n\nexport interface SliderControlProps extends BaseUIComponentProps<'div', SliderControlState> {}\n\nexport namespace SliderControl {\n  export type State = SliderControlState;\n  export type Props = SliderControlProps;\n}\n"
  },
  {
    "path": "packages/react/src/slider/control/SliderControlDataAttributes.ts",
    "content": "export enum SliderControlDataAttributes {\n  /**\n   * Present while the user is dragging.\n   */\n  dragging = 'data-dragging',\n  /**\n   * Indicates the orientation of the slider.\n   * @type {'horizontal' | 'vertical'}\n   */\n  orientation = 'data-orientation',\n  /**\n   * Present when the slider is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the slider is in valid state (when wrapped in Field.Root).\n   */\n  valid = 'data-valid',\n  /**\n   * Present when the slider is in invalid state (when wrapped in Field.Root).\n   */\n  invalid = 'data-invalid',\n  /**\n   * Present when the slider has been touched (when wrapped in Field.Root).\n   */\n  touched = 'data-touched',\n  /**\n   * Present when the slider's value has changed (when wrapped in Field.Root).\n   */\n  dirty = 'data-dirty',\n  /**\n   * Present when the slider is focused (when wrapped in Field.Root).\n   */\n  focused = 'data-focused',\n}\n"
  },
  {
    "path": "packages/react/src/slider/index.parts.ts",
    "content": "export { SliderRoot as Root } from './root/SliderRoot';\nexport { SliderLabel as Label } from './label/SliderLabel';\nexport { SliderValue as Value } from './value/SliderValue';\nexport { SliderControl as Control } from './control/SliderControl';\nexport { SliderTrack as Track } from './track/SliderTrack';\nexport { SliderThumb as Thumb } from './thumb/SliderThumb';\nexport { SliderIndicator as Indicator } from './indicator/SliderIndicator';\n"
  },
  {
    "path": "packages/react/src/slider/index.ts",
    "content": "export * as Slider from './index.parts';\n\nexport type * from './root/SliderRoot';\nexport type * from './label/SliderLabel';\nexport type * from './value/SliderValue';\nexport type * from './control/SliderControl';\nexport type * from './track/SliderTrack';\nexport type * from './thumb/SliderThumb';\nexport type * from './indicator/SliderIndicator';\n"
  },
  {
    "path": "packages/react/src/slider/indicator/SliderIndicator.test.tsx",
    "content": "import { Slider } from '@base-ui/react/slider';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Slider.Indicator />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Slider.Indicator />, () => ({\n    render: (node) => {\n      return render(<Slider.Root>{node}</Slider.Root>);\n    },\n    refInstanceof: window.HTMLDivElement,\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/slider/indicator/SliderIndicator.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useOnMount } from '@base-ui/utils/useOnMount';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { valueToPercent } from '../../utils/valueToPercent';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useSliderRootContext } from '../root/SliderRootContext';\nimport { sliderStateAttributesMapping } from '../root/stateAttributesMapping';\nimport type { SliderRootState } from '../root/SliderRoot';\n\nfunction getInsetStyles(\n  vertical: boolean,\n  range: boolean,\n  start: number | undefined,\n  end: number | undefined,\n  renderBeforeHydration: boolean,\n  mounted: boolean,\n): React.CSSProperties & Record<string, unknown> {\n  const visibility =\n    start === undefined || (range && end === undefined) ? ('hidden' as const) : undefined;\n\n  const startEdge = vertical ? 'bottom' : 'insetInlineStart';\n  const mainSide = vertical ? 'height' : 'width';\n  const crossSide = vertical ? 'width' : 'height';\n\n  const styles: React.CSSProperties & Record<string, unknown> = {\n    visibility: renderBeforeHydration && !mounted ? 'hidden' : visibility,\n    position: vertical ? 'absolute' : 'relative',\n    [crossSide]: 'inherit',\n  };\n\n  styles['--start-position'] = `${start ?? 0}%`;\n\n  if (!range) {\n    styles[startEdge] = 0;\n    styles[mainSide] = 'var(--start-position)';\n\n    return styles;\n  }\n\n  styles['--relative-size'] = `${(end ?? 0) - (start ?? 0)}%`;\n\n  styles[startEdge] = 'var(--start-position)';\n  styles[mainSide] = 'var(--relative-size)';\n\n  return styles;\n}\n\nfunction getCenteredStyles(\n  vertical: boolean,\n  range: boolean,\n  start: number,\n  end: number,\n): React.CSSProperties {\n  const startEdge = vertical ? 'bottom' : 'insetInlineStart';\n  const mainSide = vertical ? 'height' : 'width';\n  const crossSide = vertical ? 'width' : 'height';\n\n  const styles: React.CSSProperties = {\n    position: vertical ? 'absolute' : 'relative',\n    [crossSide]: 'inherit',\n  };\n\n  if (!range) {\n    styles[startEdge] = 0;\n    styles[mainSide] = `${start}%`;\n\n    return styles;\n  }\n\n  const size = end - start;\n\n  styles[startEdge] = `${start}%`;\n  styles[mainSide] = `${size}%`;\n\n  return styles;\n}\n\n/**\n * Visualizes the current value of the slider.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Slider](https://base-ui.com/react/components/slider)\n */\nexport const SliderIndicator = React.forwardRef(function SliderIndicator(\n  componentProps: SliderIndicator.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, ...elementProps } = componentProps;\n\n  const { indicatorPosition, inset, max, min, orientation, renderBeforeHydration, state, values } =\n    useSliderRootContext();\n\n  const [isMounted, setIsMounted] = React.useState(false);\n  useOnMount(() => setIsMounted(true));\n\n  const vertical = orientation === 'vertical';\n  const range = values.length > 1;\n\n  const style = inset\n    ? getInsetStyles(\n        vertical,\n        range,\n        indicatorPosition[0],\n        indicatorPosition[1],\n        renderBeforeHydration,\n        isMounted,\n      )\n    : getCenteredStyles(\n        vertical,\n        range,\n        valueToPercent(values[0], min, max),\n        valueToPercent(values[values.length - 1], min, max),\n      );\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: forwardedRef,\n    props: [\n      {\n        ['data-base-ui-slider-indicator' as string]: renderBeforeHydration ? '' : undefined,\n        style,\n        suppressHydrationWarning: renderBeforeHydration || undefined,\n      },\n      elementProps,\n    ],\n    stateAttributesMapping: sliderStateAttributesMapping,\n  });\n\n  return element;\n});\n\nexport interface SliderIndicatorState extends SliderRootState {}\n\nexport interface SliderIndicatorProps extends BaseUIComponentProps<'div', SliderIndicatorState> {}\n\nexport namespace SliderIndicator {\n  export type State = SliderIndicatorState;\n  export type Props = SliderIndicatorProps;\n}\n"
  },
  {
    "path": "packages/react/src/slider/indicator/SliderIndicatorDataAttributes.ts",
    "content": "export enum SliderIndicatorDataAttributes {\n  /**\n   * Present while the user is dragging.\n   */\n  dragging = 'data-dragging',\n  /**\n   * Indicates the orientation of the slider.\n   * @type {'horizontal' | 'vertical'}\n   */\n  orientation = 'data-orientation',\n  /**\n   * Present when the slider is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the slider is in valid state (when wrapped in Field.Root).\n   */\n  valid = 'data-valid',\n  /**\n   * Present when the slider is in invalid state (when wrapped in Field.Root).\n   */\n  invalid = 'data-invalid',\n  /**\n   * Present when the slider has been touched (when wrapped in Field.Root).\n   */\n  touched = 'data-touched',\n  /**\n   * Present when the slider's value has changed (when wrapped in Field.Root).\n   */\n  dirty = 'data-dirty',\n  /**\n   * Present when the slider is focused (when wrapped in Field.Root).\n   */\n  focused = 'data-focused',\n}\n"
  },
  {
    "path": "packages/react/src/slider/label/SliderLabel.test.tsx",
    "content": "import { Slider } from '@base-ui/react/slider';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Slider.Label />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Slider.Label />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <Slider.Root defaultValue={50}>\n          {node}\n          <Slider.Control>\n            <Slider.Thumb />\n          </Slider.Control>\n        </Slider.Root>,\n      );\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/slider/label/SliderLabel.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { isHTMLElement } from '@floating-ui/utils/dom';\nimport { ownerDocument } from '@base-ui/utils/owner';\nimport { focusElementWithVisible, useLabel } from '../../labelable-provider/useLabel';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport type { SliderRoot } from '../root/SliderRoot';\nimport { useSliderRootContext } from '../root/SliderRootContext';\nimport { sliderStateAttributesMapping } from '../root/stateAttributesMapping';\n\n/**\n * An accessible label that is automatically associated with the slider thumbs.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Slider](https://base-ui.com/react/components/slider)\n */\nexport const SliderLabel = React.forwardRef(function SliderLabel(\n  componentProps: SliderLabel.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, ...elementProps } = componentProps;\n  // Keep label id derived from the root and ignore runtime `id` overrides from untyped consumers.\n  const elementPropsWithoutId = elementProps as typeof elementProps & { id?: string | undefined };\n  delete elementPropsWithoutId.id;\n\n  const { state, setLabelId, controlRef, rootLabelId } = useSliderRootContext();\n\n  function focusControl(event: React.MouseEvent, controlId: string | null | undefined) {\n    if (controlId) {\n      const controlElement = ownerDocument(event.currentTarget).getElementById(controlId);\n      if (isHTMLElement(controlElement)) {\n        focusElementWithVisible(controlElement);\n        return;\n      }\n    }\n\n    const fallbackInputs = controlRef.current?.querySelectorAll('input[type=\"range\"]');\n    const fallbackInput = fallbackInputs?.length === 1 ? fallbackInputs[0] : null;\n    if (isHTMLElement(fallbackInput)) {\n      focusElementWithVisible(fallbackInput);\n    }\n  }\n\n  const labelProps = useLabel({\n    id: rootLabelId,\n    setLabelId,\n    focusControl,\n  });\n\n  return useRenderElement('div', componentProps, {\n    ref: forwardedRef,\n    state,\n    props: [labelProps, elementProps],\n    stateAttributesMapping: sliderStateAttributesMapping,\n  });\n});\n\nexport type SliderLabelState = SliderRoot.State;\n\nexport interface SliderLabelProps extends Omit<\n  BaseUIComponentProps<'div', SliderLabel.State>,\n  'id'\n> {}\n\nexport namespace SliderLabel {\n  export type State = SliderLabelState;\n  export type Props = SliderLabelProps;\n}\n"
  },
  {
    "path": "packages/react/src/slider/root/SliderRoot.spec.tsx",
    "content": "import { expectType } from '#test-utils';\nimport { Slider } from '@base-ui/react/slider';\nimport { REASONS } from '../../utils/reasons';\n\nconst value: number = 25;\nconst array = [25];\n\nconst singleValueWithOnValueChange = (\n  <Slider.Root value={value} onValueChange={(v) => expectType<number, typeof v>(v)} />\n);\nconst singleDefaultValueWithOnValueChange = (\n  <Slider.Root defaultValue={25} onValueChange={(v) => expectType<number, typeof v>(v)} />\n);\n\nconst arrayValueWithOnValueChange = (\n  <Slider.Root value={array} onValueChange={(v) => expectType<number[], typeof v>(v)} />\n);\nconst arrayDefaultValueWithOnValueChange = (\n  <Slider.Root defaultValue={[25]} onValueChange={(v) => expectType<number[], typeof v>(v)} />\n);\n\nconst singleValueWithOnValueCommitted = (\n  <Slider.Root value={value} onValueCommitted={(v) => expectType<number, typeof v>(v)} />\n);\nconst singleDefaultValueWithOnValueCommitted = (\n  <Slider.Root defaultValue={25} onValueCommitted={(v) => expectType<number, typeof v>(v)} />\n);\n\nconst arrayValueWithOnValueCommitted = (\n  <Slider.Root value={array} onValueCommitted={(v) => expectType<number[], typeof v>(v)} />\n);\nconst arrayDefaultValueWithOnValueCommitted = (\n  <Slider.Root defaultValue={[25]} onValueCommitted={(v) => expectType<number[], typeof v>(v)} />\n);\n\nconst singleValueExplicitTypeAnnotation = (\n  <Slider.Root<number> onValueChange={(v) => expectType<number, typeof v>(v)} />\n);\nconst arrayValueExplicitTypeAnnotation = (\n  <Slider.Root<number[]> onValueChange={(v) => expectType<number[], typeof v>(v)} />\n);\n\ntype SliderChangeHandler = NonNullable<Slider.Root.Props['onValueChange']>;\ntype SliderCommitHandler = NonNullable<Slider.Root.Props['onValueCommitted']>;\n\ntype SliderChangeDetails = Parameters<SliderChangeHandler>[1];\n\nfunction assertSliderChange(details: SliderChangeDetails) {\n  if (details.reason === REASONS.drag) {\n    const event: PointerEvent | TouchEvent = details.event;\n    void event;\n    // @ts-expect-error pointer drag does not emit wheel events\n    const wheelEvent: WheelEvent = details.event;\n    void wheelEvent;\n  }\n\n  if (details.reason === REASONS.keyboard) {\n    const event: KeyboardEvent = details.event;\n    void event;\n  }\n\n  if (details.reason === REASONS.trackPress) {\n    const event: PointerEvent | MouseEvent | TouchEvent = details.event;\n    void event;\n  }\n}\n\ntype SliderCommitDetails = Parameters<SliderCommitHandler>[1];\n\nfunction assertSliderCommit(details: SliderCommitDetails) {\n  if (details.reason === REASONS.drag) {\n    const event: PointerEvent | TouchEvent = details.event;\n    void event;\n  }\n\n  if (details.reason === REASONS.inputChange) {\n    const event: InputEvent | Event = details.event;\n    void event;\n  }\n}\n\nconst handleSliderChange: SliderChangeHandler = (v, details) => {\n  expectType<number | readonly number[], typeof v>(v);\n  assertSliderChange(details);\n};\n\nconst handleSliderCommit: SliderCommitHandler = (v, details) => {\n  expectType<number | readonly number[], typeof v>(v);\n  assertSliderCommit(details);\n};\n\nconst sliderReasonNarrowing = (\n  <Slider.Root\n    defaultValue={25}\n    onValueChange={handleSliderChange}\n    onValueCommitted={handleSliderCommit}\n  />\n);\n"
  },
  {
    "path": "packages/react/src/slider/root/SliderRoot.test.tsx",
    "content": "import { expect, expect as expectVitest, vi } from 'vitest';\nimport * as React from 'react';\nimport { act, flushMicrotasks, fireEvent, screen, waitFor } from '@mui/internal-test-utils';\nimport { DirectionProvider, type TextDirection } from '@base-ui/react/direction-provider';\nimport { Field } from '@base-ui/react/field';\nimport { Slider } from '@base-ui/react/slider';\nimport { Form } from '@base-ui/react/form';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\nimport { isWebKit } from '@base-ui/utils/detectBrowser';\nimport { REASONS } from '../../utils/reasons';\nimport {\n  ARROW_RIGHT,\n  ARROW_LEFT,\n  ARROW_UP,\n  ARROW_DOWN,\n  HOME,\n  END,\n} from '../../composite/composite';\nimport type { Orientation } from '../../utils/types';\nimport type { SliderRoot } from './SliderRoot';\nimport { createTouches, getHorizontalSliderRect } from '../utils/test-utils';\n\nconst USD_NUMBER_FORMAT: Intl.NumberFormatOptions = {\n  style: 'currency',\n  currency: 'USD',\n};\n\nfunction TestSlider(props: SliderRoot.Props) {\n  return (\n    <Slider.Root data-testid=\"root\" {...props}>\n      <Slider.Value data-testid=\"value\" />\n      <Slider.Control data-testid=\"control\">\n        <Slider.Track>\n          <Slider.Indicator />\n          <Slider.Thumb data-testid=\"thumb\" />\n        </Slider.Track>\n      </Slider.Control>\n    </Slider.Root>\n  );\n}\n\nfunction TestRangeSlider(props: SliderRoot.Props) {\n  return (\n    <Slider.Root data-testid=\"root\" {...props}>\n      <Slider.Value data-testid=\"value\" />\n      <Slider.Control data-testid=\"control\">\n        <Slider.Track>\n          <Slider.Indicator />\n          <Slider.Thumb index={0} data-testid=\"thumb\" />\n          <Slider.Thumb index={1} data-testid=\"thumb\" />\n        </Slider.Track>\n      </Slider.Control>\n    </Slider.Root>\n  );\n}\n\nfunction TestMultiThumbSlider(props: SliderRoot.Props) {\n  return (\n    <Slider.Root data-testid=\"root\" {...props}>\n      <Slider.Value data-testid=\"value\" />\n      <Slider.Control data-testid=\"control\">\n        <Slider.Track>\n          <Slider.Indicator />\n          <Slider.Thumb index={0} data-testid=\"thumb\" />\n          <Slider.Thumb index={1} data-testid=\"thumb\" />\n          <Slider.Thumb index={2} data-testid=\"thumb\" />\n        </Slider.Track>\n      </Slider.Control>\n    </Slider.Root>\n  );\n}\n\ndescribe.skipIf(typeof Touch === 'undefined')('<Slider.Root />', () => {\n  beforeAll(function beforeHook() {\n    // PointerEvent not fully implemented in jsdom, causing\n    // fireEvent.pointer* to ignore options\n    // https://github.com/jsdom/jsdom/issues/2527\n    (window as any).PointerEvent = window.MouseEvent;\n  });\n\n  const { render, renderToString } = createRenderer();\n\n  describeConformance(<Slider.Root defaultValue={50} />, () => ({\n    render,\n    refInstanceof: window.HTMLDivElement,\n  }));\n\n  describe('server-side rendering', () => {\n    it('does not link Slider.Label before hydration', () => {\n      renderToString(\n        <Slider.Root defaultValue={30} data-testid=\"root\">\n          <Slider.Label data-testid=\"label\">Volume</Slider.Label>\n          <Slider.Control>\n            <Slider.Track>\n              <Slider.Thumb />\n            </Slider.Track>\n          </Slider.Control>\n        </Slider.Root>,\n      );\n\n      const root = screen.getByTestId('root');\n      const label = screen.getByTestId('label');\n      const slider = screen.getByRole('slider');\n\n      expect(label.id).not.toBe('');\n      expect(root.id).not.toBe('');\n      expect(root).not.toHaveAttribute('aria-labelledby');\n      expect(slider).not.toHaveAttribute('aria-labelledby');\n    });\n  });\n\n  it.skipIf(isWebKit)('should not break when initial value is out of range', async () => {\n    await render(<TestRangeSlider value={[19, 41]} min={20} max={40} />);\n\n    const sliderControl = screen.getByTestId('control');\n\n    vi.spyOn(sliderControl, 'getBoundingClientRect').mockImplementation(getHorizontalSliderRect);\n\n    fireEvent.touchStart(\n      sliderControl,\n      createTouches([{ identifier: 1, clientX: 100, clientY: 0 }]),\n    );\n\n    fireEvent.touchMove(document.body, createTouches([{ identifier: 1, clientX: 20, clientY: 0 }]));\n  });\n\n  describe('ARIA attributes', () => {\n    it('it has the correct aria attributes', async () => {\n      await render(\n        <Slider.Root defaultValue={30} aria-labelledby=\"labelId\" data-testid=\"root\">\n          <Slider.Value />\n          <Slider.Control>\n            <Slider.Track>\n              <Slider.Indicator />\n              <Slider.Thumb />\n            </Slider.Track>\n          </Slider.Control>\n        </Slider.Root>,\n      );\n\n      const root = screen.getByTestId('root');\n      const slider = screen.getByRole('slider');\n\n      expect(slider.tagName).toBe('INPUT');\n\n      expect(root).toHaveAttribute('aria-labelledby', 'labelId');\n\n      expect(slider).toHaveAttribute('aria-valuenow', '30');\n      expect(slider).toHaveAttribute('aria-orientation', 'horizontal');\n      expect(slider).toHaveAttribute('aria-labelledby', 'labelId');\n      expect(slider).toHaveAttribute('step', '1');\n    });\n\n    it('should update aria-valuenow', async () => {\n      await render(<TestSlider defaultValue={50} />);\n      const slider = screen.getByRole('slider');\n\n      await act(async () => {\n        slider.focus();\n      });\n\n      fireEvent.change(slider, { target: { value: '51' } });\n      expect(slider).toHaveAttribute('aria-valuenow', '51');\n\n      fireEvent.keyDown(slider, { key: ARROW_RIGHT });\n      expect(slider).toHaveAttribute('aria-valuenow', '52');\n    });\n\n    it('should set default aria-valuetext on range slider thumbs', async () => {\n      await render(<TestRangeSlider defaultValue={[44, 50]} />);\n\n      const [thumb1, thumb2] = screen.getAllByTestId('thumb');\n\n      expect(thumb1.querySelector('input')).toHaveAttribute('aria-valuetext', '44 start range');\n      expect(thumb2.querySelector('input')).toHaveAttribute('aria-valuetext', '50 end range');\n    });\n  });\n\n  describe.skipIf(isJSDOM || isWebKit)('rtl', () => {\n    it('should handle RTL', async () => {\n      const handleValueChange = vi.fn((newValue) => newValue);\n\n      await render(\n        <div dir=\"rtl\">\n          <DirectionProvider direction=\"rtl\">\n            <TestSlider value={30} onValueChange={handleValueChange} />\n          </DirectionProvider>\n        </div>,\n      );\n\n      const sliderControl = screen.getByTestId('control');\n      const sliderThumb = screen.getByTestId('thumb');\n      expect(sliderThumb.style.insetInlineStart).toBe('30%');\n\n      vi.spyOn(sliderControl, 'getBoundingClientRect').mockImplementation(getHorizontalSliderRect);\n\n      fireEvent.touchStart(\n        sliderControl,\n        createTouches([{ identifier: 1, clientX: 20, clientY: 0 }]),\n      );\n\n      fireEvent.touchMove(\n        document.body,\n        createTouches([{ identifier: 1, clientX: 22, clientY: 0 }]),\n      );\n\n      expect(handleValueChange.mock.calls.length).toBe(2);\n      expect(handleValueChange.mock.results[0]?.value).toBe(80);\n      expect(handleValueChange.mock.results.at(-1)?.value).toBe(78);\n    });\n  });\n\n  describe('prop: disabled', () => {\n    it('should render data-disabled on all subcomponents', async () => {\n      await render(\n        <Slider.Root defaultValue={30} disabled data-testid=\"root\">\n          <Slider.Value data-testid=\"value\" />\n          <Slider.Control data-testid=\"control\">\n            <Slider.Track data-testid=\"track\">\n              <Slider.Indicator data-testid=\"indicator\" />\n              <Slider.Thumb data-testid=\"thumb\" />\n            </Slider.Track>\n          </Slider.Control>\n        </Slider.Root>,\n      );\n\n      const root = screen.getByTestId('root');\n      const value = screen.getByTestId('value');\n      const control = screen.getByTestId('control');\n      const track = screen.getByTestId('track');\n      const indicator = screen.getByTestId('indicator');\n      const thumb = screen.getByTestId('thumb');\n\n      [root, value, control, track, indicator, thumb].forEach((subcomponent) => {\n        expect(subcomponent).toHaveAttribute('data-disabled', '');\n      });\n    });\n\n    // TODO: Don't skip once a fix for https://github.com/jsdom/jsdom/issues/3029 is released.\n    it.skipIf(isJSDOM || isWebKit)(\n      'should not respond to drag events after becoming disabled',\n      async () => {\n        const { setProps } = await render(\n          <TestSlider defaultValue={0} data-testid=\"slider-root\" />,\n        );\n\n        const sliderControl = screen.getByTestId('control');\n\n        vi.spyOn(sliderControl, 'getBoundingClientRect').mockImplementation(\n          getHorizontalSliderRect,\n        );\n        fireEvent.touchStart(\n          sliderControl,\n          createTouches([{ identifier: 1, clientX: 21, clientY: 0 }]),\n        );\n\n        const thumb = screen.getByRole('slider');\n\n        expect(thumb).toHaveAttribute('aria-valuenow', '21');\n        expect(thumb).toHaveFocus();\n\n        await setProps({ disabled: true });\n        expect(thumb).not.toHaveFocus();\n        // expect(thumb).not.toHaveClass(classes.active);\n\n        fireEvent.touchMove(\n          sliderControl,\n          createTouches([{ identifier: 1, clientX: 30, clientY: 0 }]),\n        );\n\n        expect(thumb).toHaveAttribute('aria-valuenow', '21');\n      },\n    );\n\n    // TODO: Don't skip once a fix for https://github.com/jsdom/jsdom/issues/3029 is released.\n    it.skipIf(isJSDOM || isWebKit)('should not respond to drag events if disabled', async () => {\n      await render(<TestSlider defaultValue={21} data-testid=\"slider-root\" disabled />);\n\n      const thumb = screen.getByRole('slider');\n      const sliderControl = screen.getByTestId('control');\n\n      vi.spyOn(sliderControl, 'getBoundingClientRect').mockImplementation(getHorizontalSliderRect);\n\n      fireEvent.touchStart(\n        sliderControl,\n        createTouches([{ identifier: 1, clientX: 21, clientY: 0 }]),\n      );\n\n      fireEvent.touchMove(\n        document.body,\n        createTouches([{ identifier: 1, clientX: 30, clientY: 0 }]),\n      );\n\n      fireEvent.touchEnd(\n        document.body,\n        createTouches([{ identifier: 1, clientX: 30, clientY: 0 }]),\n      );\n\n      expect(thumb).toHaveAttribute('aria-valuenow', '21');\n    });\n  });\n\n  describe('prop: orientation', () => {\n    it('sets the `aria-orientation` attribute', async () => {\n      await render(<TestSlider orientation=\"vertical\" />);\n\n      const sliderRoot = screen.getByRole('slider');\n      expect(sliderRoot).toHaveAttribute('aria-orientation', 'vertical');\n    });\n\n    it('sets the data-orientation attribute', async () => {\n      await render(<TestSlider />);\n\n      const sliderRoot = screen.getByRole('group');\n      expect(sliderRoot).toHaveAttribute('data-orientation', 'horizontal');\n      const sliderControl = screen.getByTestId('control');\n      expect(sliderControl).toHaveAttribute('data-orientation', 'horizontal');\n      const sliderOutput = screen.getByTestId('value');\n      expect(sliderOutput).toHaveAttribute('data-orientation', 'horizontal');\n    });\n\n    it.skipIf(isJSDOM || !/WebKit/.test(window.navigator.userAgent))(\n      'does not set the orientation via appearance for WebKit browsers',\n      async () => {\n        await render(<TestSlider orientation=\"vertical\" />);\n\n        const slider = screen.getByRole('slider');\n\n        expect(slider).toHaveProperty('tagName', 'INPUT');\n        expect(slider).toHaveProperty('type', 'range');\n        // Only relevant to implementations using `input[type=\"range\"]` with implicit `[role=\"slider\"]`\n        // We're not setting this by default because it changes horizontal keyboard navigation in WebKit: https://issues.chromium.org/issues/40739626\n        expect(slider).not.toHaveComputedStyle({ webkitAppearance: 'slider-vertical' });\n      },\n    );\n\n    it.skipIf(isJSDOM || isWebKit)('should report the right position', async () => {\n      const handleValueChange = vi.fn();\n\n      await render(\n        <TestSlider orientation=\"vertical\" defaultValue={20} onValueChange={handleValueChange} />,\n      );\n\n      const sliderControl = screen.getByTestId('control');\n      vi.spyOn(sliderControl, 'getBoundingClientRect').mockImplementation(() => ({\n        width: 10,\n        height: 100,\n        bottom: 100,\n        left: 0,\n        x: 0,\n        y: 0,\n        top: 0,\n        right: 10,\n        toJSON() {},\n      }));\n\n      fireEvent.touchStart(\n        sliderControl,\n        createTouches([{ identifier: 1, clientX: 0, clientY: 20 }]),\n      );\n      fireEvent.touchMove(\n        document.body,\n        createTouches([{ identifier: 1, clientX: 0, clientY: 22 }]),\n      );\n\n      expect(handleValueChange.mock.calls.length).toBe(2);\n      expect(handleValueChange.mock.calls[0][0]).toBe(80);\n      expect(handleValueChange.mock.calls[1][0]).toBe(78);\n    });\n  });\n\n  describe('prop: step', () => {\n    it('supports non-integer values', async () => {\n      await render(\n        <React.Fragment>\n          <TestSlider value={51.1} min={-100} max={100} step={0.00000001} />\n          <TestSlider value={0.00000005} min={-100} max={100} step={0.00000001} />\n          <TestSlider value={1e-7} min={-100} max={100} step={0.00000001} />\n        </React.Fragment>,\n      );\n      const [slider1, slider2, slider3] = screen.getAllByRole('slider');\n\n      expect(slider1).toHaveAttribute('aria-valuenow', '51.1');\n      expect(slider2).toHaveAttribute('aria-valuenow', '5e-8');\n      expect(slider3).toHaveAttribute('aria-valuenow', '1e-7');\n    });\n\n    it.skipIf(isJSDOM || isWebKit)('should round value to step precision', async () => {\n      await render(<TestSlider defaultValue={0.2} min={0} max={1} step={0.1} />);\n\n      const slider = screen.getByRole('slider');\n\n      await act(async () => {\n        slider.focus();\n      });\n\n      const sliderControl = screen.getByTestId('control');\n      vi.spyOn(sliderControl, 'getBoundingClientRect').mockImplementation(getHorizontalSliderRect);\n\n      await act(async () => {\n        slider.focus();\n      });\n\n      expect(slider).toHaveAttribute('aria-valuenow', '0.2');\n\n      fireEvent.touchStart(\n        sliderControl,\n        createTouches([{ identifier: 1, clientX: 20, clientY: 0 }]),\n      );\n\n      fireEvent.touchMove(\n        document.body,\n        createTouches([{ identifier: 1, clientX: 80, clientY: 0 }]),\n      );\n      expect(slider).toHaveAttribute('aria-valuenow', '0.8');\n\n      fireEvent.touchMove(\n        document.body,\n        createTouches([{ identifier: 1, clientX: 40, clientY: 0 }]),\n      );\n      expect(slider).toHaveAttribute('aria-valuenow', '0.4');\n    });\n\n    it.skipIf(isJSDOM || isWebKit)(\n      'should not fail to round value to step precision when step is very small',\n      async () => {\n        await render(\n          <TestSlider defaultValue={0.00000002} min={0} max={0.0000001} step={0.00000001} />,\n        );\n\n        const slider = screen.getByRole('slider');\n\n        await act(async () => {\n          slider.focus();\n        });\n\n        const sliderControl = screen.getByTestId('control');\n        vi.spyOn(sliderControl, 'getBoundingClientRect').mockImplementation(\n          getHorizontalSliderRect,\n        );\n\n        await act(async () => {\n          slider.focus();\n        });\n\n        expect(slider).toHaveAttribute('aria-valuenow', '2e-8');\n\n        fireEvent.touchStart(\n          sliderControl,\n          createTouches([{ identifier: 1, clientX: 20, clientY: 0 }]),\n        );\n\n        fireEvent.touchMove(\n          document.body,\n          createTouches([{ identifier: 1, clientX: 80, clientY: 0 }]),\n        );\n        expect(slider).toHaveAttribute('aria-valuenow', '8e-8');\n      },\n    );\n\n    it.skipIf(isJSDOM || isWebKit)(\n      'should not fail to round value to step precision when step is very small and negative',\n      async () => {\n        await render(\n          <TestSlider defaultValue={-0.00000002} min={-0.0000001} max={0} step={0.00000001} />,\n        );\n\n        const slider = screen.getByRole('slider');\n\n        await act(async () => {\n          slider.focus();\n        });\n\n        const sliderControl = screen.getByTestId('control');\n        vi.spyOn(sliderControl, 'getBoundingClientRect').mockImplementation(\n          getHorizontalSliderRect,\n        );\n\n        await act(async () => {\n          slider.focus();\n        });\n\n        expect(slider).toHaveAttribute('aria-valuenow', '-2e-8');\n\n        fireEvent.touchStart(\n          sliderControl,\n          createTouches([{ identifier: 1, clientX: 80, clientY: 0 }]),\n        );\n\n        fireEvent.touchMove(\n          document.body,\n          createTouches([{ identifier: 1, clientX: 20, clientY: 0 }]),\n        );\n        expect(slider).toHaveAttribute('aria-valuenow', '-8e-8');\n      },\n    );\n  });\n\n  describe('prop: max', () => {\n    it('sets the max attribute on the input', async () => {\n      await render(<TestSlider defaultValue={150} step={100} max={750} />);\n      expect(screen.getByRole('slider')).toHaveAttribute('max', '750');\n    });\n\n    it('should not go more than the max', async () => {\n      const { user } = await render(<TestSlider defaultValue={100} step={100} max={200} />);\n\n      const slider = screen.getByRole('slider');\n\n      await user.keyboard('[Tab]');\n\n      await user.keyboard(`[${ARROW_RIGHT}]`);\n      expect(slider).toHaveAttribute('aria-valuenow', '200');\n      await user.keyboard(`[${ARROW_RIGHT}]`);\n      expect(slider).toHaveAttribute('aria-valuenow', '200');\n    });\n\n    it.skipIf(isJSDOM || isWebKit)('should reach right edge value', async () => {\n      await render(<TestSlider defaultValue={90} min={6} max={108} step={10} />);\n\n      const sliderControl = screen.getByTestId('control');\n\n      vi.spyOn(sliderControl, 'getBoundingClientRect').mockImplementation(getHorizontalSliderRect);\n\n      const slider = screen.getByRole('slider');\n      await act(async () => {\n        slider.focus();\n      });\n\n      expect(slider).toHaveAttribute('aria-valuenow', '90');\n\n      fireEvent.touchStart(\n        sliderControl,\n        createTouches([{ identifier: 1, clientX: 20, clientY: 0 }]),\n      );\n\n      fireEvent.touchMove(\n        document.body,\n        createTouches([{ identifier: 1, clientX: 100, clientY: 0 }]),\n      );\n      expect(slider).toHaveAttribute('aria-valuenow', '106');\n\n      fireEvent.touchMove(\n        document.body,\n        createTouches([{ identifier: 1, clientX: 200, clientY: 0 }]),\n      );\n      expect(slider).toHaveAttribute('aria-valuenow', '106');\n\n      fireEvent.touchMove(\n        document.body,\n        createTouches([{ identifier: 1, clientX: 50, clientY: 0 }]),\n      );\n      expect(slider).toHaveAttribute('aria-valuenow', '56');\n\n      fireEvent.touchMove(\n        document.body,\n        createTouches([{ identifier: 1, clientX: -100, clientY: 0 }]),\n      );\n      expect(slider).toHaveAttribute('aria-valuenow', '6');\n    });\n  });\n\n  describe('prop: min', () => {\n    it('sets the min attribute on the input', async () => {\n      await render(<TestSlider defaultValue={150} step={100} min={150} max={200} />);\n      expect(screen.getByRole('slider')).toHaveAttribute('min', '150');\n    });\n\n    it('should use min as the step origin', async () => {\n      await render(<TestSlider defaultValue={150} step={100} max={750} min={150} />);\n\n      const slider = screen.getByRole('slider');\n      await act(async () => {\n        slider.focus();\n      });\n\n      expect(slider).toHaveAttribute('aria-valuenow', '150');\n    });\n\n    it('should not go less than the min', async () => {\n      const { user } = await render(<TestSlider defaultValue={1} step={1} min={0} />);\n      const slider = screen.getByRole('slider');\n\n      await user.keyboard('[Tab]');\n\n      await user.keyboard(`[${ARROW_LEFT}]`);\n      expect(slider).toHaveAttribute('aria-valuenow', '0');\n      await user.keyboard(`[${ARROW_LEFT}]`);\n      expect(slider).toHaveAttribute('aria-valuenow', '0');\n    });\n  });\n\n  describe('prop: minStepsBetweenValues', () => {\n    it('should enforce a minimum difference between range slider values', async () => {\n      const handleValueChange = vi.fn();\n\n      const { user } = await render(\n        <TestRangeSlider\n          onValueChange={handleValueChange}\n          defaultValue={[44, 50]}\n          step={2}\n          minStepsBetweenValues={2}\n        />,\n      );\n\n      await user.keyboard('[Tab]');\n\n      await user.keyboard(`[${ARROW_UP}]`);\n      expect(handleValueChange.mock.calls.length).toBe(1);\n      expect(handleValueChange.mock.calls[0][0]).toEqual([46, 50]);\n      await user.keyboard(`[${ARROW_UP}]`);\n      expect(handleValueChange.mock.calls.length).toBe(1);\n\n      await user.keyboard('[Tab]');\n\n      await user.keyboard(`[${ARROW_UP}]`);\n      expect(handleValueChange.mock.calls.length).toBe(2);\n      expect(handleValueChange.mock.calls[1][0]).toEqual([46, 52]);\n      await user.keyboard(`[${ARROW_DOWN}]`);\n      await user.keyboard(`[${ARROW_DOWN}]`);\n      expect(handleValueChange.mock.calls.length).toBe(3);\n      expect(handleValueChange.mock.calls[2][0]).toEqual([46, 50]);\n    });\n  });\n\n  describe('prop: onValueCommitted', () => {\n    it('single value', async () => {\n      const handleValueCommitted = vi.fn((newValue: number, eventDetails) => ({\n        newValue,\n        reason: eventDetails.reason,\n      }));\n\n      await render(\n        <Slider.Root onValueCommitted={handleValueCommitted} defaultValue={0}>\n          <Slider.Control data-testid=\"control\">\n            <Slider.Thumb />\n          </Slider.Control>\n        </Slider.Root>,\n      );\n\n      const sliderControl = screen.getByTestId('control');\n\n      vi.spyOn(sliderControl, 'getBoundingClientRect').mockImplementation(getHorizontalSliderRect);\n\n      const slider = screen.getByRole('slider');\n\n      fireEvent.pointerDown(sliderControl, {\n        buttons: 1,\n        clientX: 10,\n      });\n      fireEvent.pointerUp(sliderControl, {\n        buttons: 1,\n        clientX: 10,\n      });\n\n      expect(handleValueCommitted.mock.calls.length).toBe(1);\n      expect(handleValueCommitted.mock.results.at(-1)?.value.newValue).toBe(10);\n      expect(handleValueCommitted.mock.results.at(-1)?.value.reason).toBe(REASONS.trackPress);\n\n      await act(async () => {\n        slider.focus();\n      });\n\n      fireEvent.change(slider, { target: { value: 23 } });\n      expect(handleValueCommitted.mock.calls.length).toBe(2);\n      expect(handleValueCommitted.mock.results.at(-1)?.value.reason).toBe(REASONS.inputChange);\n    });\n\n    it('array value', async () => {\n      const handleValueCommitted = vi.fn((newValue: number[], eventDetails) => ({\n        newValue,\n        reason: eventDetails.reason,\n      }));\n\n      await render(\n        <Slider.Root onValueCommitted={handleValueCommitted} defaultValue={[10, 20]}>\n          <Slider.Control data-testid=\"control\">\n            <Slider.Thumb index={0} />\n            <Slider.Thumb index={1} />\n          </Slider.Control>\n        </Slider.Root>,\n      );\n\n      const sliderControl = screen.getByTestId('control');\n\n      vi.spyOn(sliderControl, 'getBoundingClientRect').mockImplementation(getHorizontalSliderRect);\n\n      const [thumb1, thumb2] = screen.getAllByRole('slider');\n\n      fireEvent.pointerDown(thumb2, {\n        buttons: 1,\n        clientX: 20,\n      });\n\n      fireEvent.pointerMove(thumb2, {\n        buttons: 1,\n        clientX: 30,\n      });\n\n      expect(handleValueCommitted.mock.calls.length).toBe(0);\n\n      fireEvent.pointerUp(thumb2, {\n        buttons: 1,\n        clientX: 30,\n      });\n\n      expect(handleValueCommitted.mock.calls.length).toBe(1);\n      expect(handleValueCommitted.mock.results.at(-1)?.value.reason).toBe(REASONS.drag);\n\n      await act(async () => {\n        thumb1.focus();\n      });\n\n      fireEvent.change(thumb1, { target: { value: 23 } });\n      expect(handleValueCommitted.mock.calls.length).toBe(2);\n      expect(handleValueCommitted.mock.results.at(-1)?.value.reason).toBe(REASONS.inputChange);\n    });\n  });\n\n  describe('events', () => {\n    it.skipIf(isJSDOM)('should call handlers', async () => {\n      const handleValueChange = vi.fn();\n      const handleValueCommitted = vi.fn();\n\n      await render(\n        <TestSlider\n          onValueChange={handleValueChange}\n          onValueCommitted={handleValueCommitted}\n          value={0}\n        />,\n      );\n\n      const sliderControl = screen.getByTestId('control');\n\n      vi.spyOn(sliderControl, 'getBoundingClientRect').mockImplementation(getHorizontalSliderRect);\n\n      const slider = screen.getByRole('slider');\n\n      fireEvent.pointerDown(sliderControl, {\n        buttons: 1,\n        clientX: 10,\n      });\n      fireEvent.pointerUp(sliderControl, {\n        buttons: 1,\n        clientX: 10,\n      });\n\n      expect(handleValueChange.mock.calls.length).toBe(1);\n      expect(handleValueChange.mock.calls[0][0]).toBe(10);\n      expect(handleValueChange.mock.calls[0][1].activeThumbIndex).toBe(0);\n      expect(handleValueCommitted.mock.calls.length).toBe(1);\n      expect(handleValueCommitted.mock.calls[0][0]).toBe(10);\n      expect(handleValueCommitted.mock.calls[0][1].reason).toBe(REASONS.trackPress);\n\n      await act(async () => {\n        slider.focus();\n      });\n\n      fireEvent.change(slider, { target: { value: 23 } });\n      expect(handleValueChange.mock.calls.length).toBe(2);\n      expect(handleValueCommitted.mock.calls.length).toBe(2);\n      expect(handleValueCommitted.mock.calls[1][1].reason).toBe(REASONS.inputChange);\n    });\n\n    it.skipIf(isJSDOM || isWebKit)('should support touch events', async () => {\n      const handleValueChange = vi.fn();\n\n      await render(\n        <TestRangeSlider\n          defaultValue={[20, 30]}\n          style={{ width: '100px' }}\n          onValueChange={handleValueChange}\n        />,\n      );\n\n      const sliderControl = screen.getByTestId('control');\n      vi.spyOn(sliderControl, 'getBoundingClientRect').mockImplementation(getHorizontalSliderRect);\n\n      fireEvent.touchStart(\n        sliderControl,\n        createTouches([{ identifier: 1, clientX: 20, clientY: 0 }]),\n      );\n\n      fireEvent.touchMove(\n        document.body,\n        createTouches([{ identifier: 1, clientX: 21, clientY: 0 }]),\n      );\n\n      fireEvent.touchEnd(\n        document.body,\n        createTouches([{ identifier: 1, clientX: 21, clientY: 0 }]),\n      );\n\n      fireEvent.touchStart(\n        sliderControl,\n        createTouches([{ identifier: 1, clientX: 21, clientY: 0 }]),\n      );\n\n      fireEvent.touchMove(\n        document.body,\n        createTouches([{ identifier: 1, clientX: 22, clientY: 0 }]),\n      );\n\n      fireEvent.touchEnd(\n        document.body,\n        createTouches([{ identifier: 1, clientX: 22, clientY: 0 }]),\n      );\n\n      fireEvent.touchStart(\n        sliderControl,\n        createTouches([{ identifier: 1, clientX: 22, clientY: 0 }]),\n      );\n\n      fireEvent.touchMove(\n        document.body,\n        createTouches([{ identifier: 1, clientX: 22.1, clientY: 0 }]),\n      );\n\n      fireEvent.touchEnd(\n        document.body,\n        createTouches([{ identifier: 1, clientX: 22.1, clientY: 0 }]),\n      );\n\n      expect(handleValueChange.mock.calls.length).toBe(2);\n      expect(handleValueChange.mock.calls[0][0]).toEqual([21, 30]);\n      expect(handleValueChange.mock.calls[1][0]).toEqual([22, 30]);\n    });\n\n    it.skipIf(isJSDOM || isWebKit)(\n      'should only listen to changes from the same touchpoint',\n      async () => {\n        const handleValueChange = vi.fn();\n        const handleValueCommitted = vi.fn();\n\n        await render(\n          <TestSlider\n            onValueChange={handleValueChange}\n            onValueCommitted={handleValueCommitted}\n            value={0}\n          />,\n        );\n\n        const sliderControl = screen.getByTestId('control');\n\n        vi.spyOn(sliderControl, 'getBoundingClientRect').mockImplementation(\n          getHorizontalSliderRect,\n        );\n\n        fireEvent.touchStart(\n          sliderControl,\n          createTouches([{ identifier: 1, clientX: 0, clientY: 0 }]),\n        );\n        expect(handleValueChange.mock.calls.length).toBe(0);\n        expect(handleValueCommitted.mock.calls.length).toBe(0);\n\n        fireEvent.touchStart(\n          document.body,\n          createTouches([{ identifier: 2, clientX: 40, clientY: 0 }]),\n        );\n        expect(handleValueChange.mock.calls.length).toBe(0);\n        expect(handleValueCommitted.mock.calls.length).toBe(0);\n\n        fireEvent.touchMove(\n          document.body,\n          createTouches([{ identifier: 1, clientX: 1, clientY: 0 }]),\n        );\n        expect(handleValueChange.mock.calls.length).toBe(1);\n        expect(handleValueCommitted.mock.calls.length).toBe(0);\n\n        fireEvent.touchMove(\n          document.body,\n          createTouches([{ identifier: 2, clientX: 41, clientY: 0 }]),\n        );\n        expect(handleValueChange.mock.calls.length).toBe(1);\n        expect(handleValueCommitted.mock.calls.length).toBe(0);\n\n        fireEvent.touchEnd(\n          document.body,\n          createTouches([{ identifier: 1, clientX: 2, clientY: 0 }]),\n        );\n        expect(handleValueChange.mock.calls.length).toBe(1);\n        expect(handleValueCommitted.mock.calls.length).toBe(1);\n        expect(handleValueCommitted.mock.calls[0][1].reason).toBe('drag');\n      },\n    );\n\n    it.skipIf(isJSDOM)('should hedge against a dropped mouseup event', async () => {\n      const handleValueChange = vi.fn();\n\n      await render(<TestSlider onValueChange={handleValueChange} value={0} />);\n\n      const sliderControl = screen.getByTestId('control');\n\n      vi.spyOn(sliderControl, 'getBoundingClientRect').mockImplementation(getHorizontalSliderRect);\n\n      fireEvent.pointerDown(sliderControl, {\n        buttons: 1,\n        clientX: 1,\n      });\n      expect(handleValueChange.mock.calls.length).toBe(1);\n      expect(handleValueChange.mock.calls[0][0]).toBe(1);\n\n      fireEvent.pointerMove(document.body, {\n        buttons: 1,\n        clientX: 10,\n      });\n      expect(handleValueChange.mock.calls.length).toBe(2);\n      expect(handleValueChange.mock.calls[1][0]).toBe(10);\n\n      fireEvent.pointerMove(document.body, {\n        buttons: 0,\n        clientX: 11,\n      });\n      // The mouse's button was released, stop the dragging session.\n      expect(handleValueChange.mock.calls.length).toBe(2);\n    });\n\n    it.skipIf(isWebKit)('should focus the slider when touching', async () => {\n      await render(<TestSlider defaultValue={30} />);\n      const slider = screen.getByRole('slider');\n      const sliderControl = screen.getByTestId('control');\n\n      vi.spyOn(sliderControl, 'getBoundingClientRect').mockImplementation(getHorizontalSliderRect);\n\n      fireEvent.touchStart(\n        sliderControl,\n        createTouches([{ identifier: 1, clientX: 0, clientY: 0 }]),\n      );\n\n      expect(slider).toHaveFocus();\n    });\n\n    it('should focus the slider when dragging', async () => {\n      await render(<TestSlider defaultValue={30} step={10} />);\n      const slider = screen.getByRole('slider');\n      const sliderThumb = screen.getByTestId('thumb');\n      const sliderControl = screen.getByTestId('control');\n\n      vi.spyOn(sliderControl, 'getBoundingClientRect').mockImplementation(getHorizontalSliderRect);\n\n      fireEvent.pointerDown(sliderThumb, {\n        buttons: 1,\n        clientX: 1,\n      });\n\n      await waitFor(() => {\n        expect(slider).toHaveFocus();\n      });\n    });\n\n    it.skipIf(isWebKit)('should not override the event.target on touch events', async () => {\n      const handleValueChange = vi.fn();\n      const handleNativeEvent = vi.fn();\n      const handleEvent = vi.fn();\n      function Test() {\n        React.useEffect(() => {\n          document.addEventListener('touchstart', handleNativeEvent);\n          return () => {\n            document.removeEventListener('touchstart', handleNativeEvent);\n          };\n        });\n\n        return (\n          <div onTouchStart={handleEvent}>\n            <TestSlider value={0} onValueChange={handleValueChange} />\n          </div>\n        );\n      }\n\n      await render(<Test />);\n      const sliderControl = screen.getByTestId('control');\n\n      vi.spyOn(sliderControl, 'getBoundingClientRect').mockImplementation(getHorizontalSliderRect);\n\n      fireEvent.touchStart(\n        sliderControl,\n        createTouches([{ identifier: 1, clientX: 0, clientY: 0 }]),\n      );\n\n      expect(handleValueChange.mock.calls.length).toBe(0);\n      expect(handleNativeEvent.mock.calls.length).toBe(1);\n      expect(handleNativeEvent.mock.calls[0][0]).toHaveProperty('target', sliderControl);\n      expect(handleEvent.mock.calls.length).toBe(1);\n      expect(handleEvent.mock.calls[0][0]).toHaveProperty('target', sliderControl);\n    });\n\n    it('should not override the event.target on mouse events', async () => {\n      const handleValueChange = vi.fn();\n      const handleNativeEvent = vi.fn();\n      const handleEvent = vi.fn();\n      function Test() {\n        React.useEffect(() => {\n          document.addEventListener('mousedown', handleNativeEvent);\n          return () => {\n            document.removeEventListener('mousedown', handleNativeEvent);\n          };\n        });\n\n        return (\n          <div onMouseDown={handleEvent}>\n            <TestSlider value={0} onValueChange={handleValueChange} />\n          </div>\n        );\n      }\n      await render(<Test />);\n      const sliderControl = screen.getByTestId('control');\n\n      vi.spyOn(sliderControl, 'getBoundingClientRect').mockImplementation(getHorizontalSliderRect);\n\n      fireEvent.mouseDown(sliderControl);\n\n      expect(handleValueChange.mock.calls.length).toBe(0);\n      expect(handleNativeEvent.mock.calls.length).toBe(1);\n      expect(handleNativeEvent.mock.calls[0][0]).toHaveProperty('target', sliderControl);\n      expect(handleEvent.mock.calls.length).toBe(1);\n      expect(handleEvent.mock.calls[0][0]).toHaveProperty('target', sliderControl);\n    });\n  });\n\n  describe.skipIf(isWebKit)('dragging state', () => {\n    it('should not apply data-dragging for click modality', async () => {\n      await render(<TestSlider defaultValue={90} />);\n\n      const sliderControl = screen.getByTestId('control');\n\n      vi.spyOn(sliderControl, 'getBoundingClientRect').mockImplementation(getHorizontalSliderRect);\n\n      fireEvent.touchStart(\n        sliderControl,\n        createTouches([{ identifier: 1, clientX: 20, clientY: 0 }]),\n      );\n      fireEvent.touchMove(\n        document.body,\n        createTouches([{ identifier: 1, clientX: 21, clientY: 0 }]),\n      );\n      expect(sliderControl).not.toHaveAttribute('data-dragging');\n      fireEvent.touchEnd(document.body, createTouches([{ identifier: 1, clientX: 0, clientY: 0 }]));\n    });\n\n    it('should apply data-dragging for dragging modality', async () => {\n      await render(<TestSlider defaultValue={90} />);\n\n      const sliderControl = screen.getByTestId('control');\n\n      vi.spyOn(sliderControl, 'getBoundingClientRect').mockImplementation(getHorizontalSliderRect);\n\n      fireEvent.touchStart(\n        sliderControl,\n        createTouches([{ identifier: 1, clientX: 20, clientY: 0 }]),\n      );\n      fireEvent.touchMove(\n        document.body,\n        createTouches([{ identifier: 1, clientX: 200, clientY: 0 }]),\n      );\n      fireEvent.touchMove(\n        document.body,\n        createTouches([{ identifier: 1, clientX: 200, clientY: 0 }]),\n      );\n\n      expect(sliderControl).not.toHaveAttribute('data-dragging');\n\n      fireEvent.touchMove(\n        document.body,\n        createTouches([{ identifier: 1, clientX: 200, clientY: 0 }]),\n      );\n\n      expect(sliderControl).toHaveAttribute('data-dragging', '');\n      fireEvent.touchEnd(document.body, createTouches([{ identifier: 1, clientX: 0, clientY: 0 }]));\n      expect(sliderControl).not.toHaveAttribute('data-dragging');\n    });\n  });\n\n  describe('prop: onValueChange', () => {\n    it.skipIf(isJSDOM)('is called when clicking on the control', async () => {\n      const handleValueChange = vi.fn();\n      await render(<TestSlider defaultValue={50} onValueChange={handleValueChange} />);\n\n      const sliderControl = screen.getByTestId('control');\n\n      vi.spyOn(sliderControl, 'getBoundingClientRect').mockImplementation(getHorizontalSliderRect);\n\n      fireEvent.pointerDown(sliderControl, {\n        buttons: 1,\n        clientX: 41,\n      });\n\n      expect(handleValueChange.mock.calls.length).toBe(1);\n    });\n\n    it('is not called when clicking on the thumb', async () => {\n      const handleValueChange = vi.fn();\n      await render(<TestSlider defaultValue={50} onValueChange={handleValueChange} />);\n\n      const sliderControl = screen.getByTestId('control');\n      const sliderThumb = screen.getByTestId('thumb');\n\n      vi.spyOn(sliderControl, 'getBoundingClientRect').mockImplementation(getHorizontalSliderRect);\n\n      fireEvent.pointerDown(sliderThumb, {\n        buttons: 1,\n        clientX: 51,\n      });\n\n      expect(handleValueChange.mock.calls.length).toBe(0);\n    });\n\n    it('should not react to right clicks', async () => {\n      const handleValueChange = vi.fn();\n      await render(<TestSlider defaultValue={50} onValueChange={handleValueChange} />);\n\n      const sliderControl = screen.getByTestId('control');\n\n      vi.spyOn(sliderControl, 'getBoundingClientRect').mockImplementation(getHorizontalSliderRect);\n\n      fireEvent.pointerDown(sliderControl, {\n        button: 2,\n        clientX: 41,\n      });\n\n      expect(handleValueChange.mock.calls.length).toBe(0);\n    });\n\n    it('provides the change reason for input events', async () => {\n      const handleValueChange = vi.fn();\n      await render(<TestSlider defaultValue={30} onValueChange={handleValueChange} />);\n\n      const slider = screen.getByRole('slider');\n      fireEvent.change(slider, { target: { value: '35' } });\n\n      expect(handleValueChange).toHaveBeenCalledTimes(1);\n      const [, details] = handleValueChange.mock.calls[0] as [\n        number,\n        SliderRoot.ChangeEventDetails,\n      ];\n      expect(details.reason).toBe(REASONS.inputChange);\n      expect(details.activeThumbIndex).toBe(0);\n    });\n\n    it('provides the change reason for keyboard interactions', async () => {\n      const handleValueChange = vi.fn();\n      await render(<TestSlider defaultValue={40} onValueChange={handleValueChange} />);\n\n      const slider = screen.getByRole('slider');\n      await act(async () => {\n        slider.focus();\n      });\n      fireEvent.keyDown(slider, { key: ARROW_RIGHT });\n\n      expect(handleValueChange).toHaveBeenCalledTimes(1);\n      const [, details] = handleValueChange.mock.calls[0] as [\n        number,\n        SliderRoot.ChangeEventDetails,\n      ];\n      expect(details.reason).toBe('keyboard');\n    });\n\n    it('provides the change reason for track presses', async () => {\n      const handleValueChange = vi.fn();\n      await render(<TestSlider defaultValue={0} onValueChange={handleValueChange} />);\n\n      const sliderControl = screen.getByTestId('control');\n      vi.spyOn(sliderControl, 'getBoundingClientRect').mockImplementation(getHorizontalSliderRect);\n\n      fireEvent.pointerDown(sliderControl, {\n        pointerId: 1,\n        pointerType: 'mouse',\n        button: 0,\n        buttons: 1,\n        clientX: 80,\n        clientY: 0,\n      });\n\n      await waitFor(() => {\n        expect(handleValueChange.mock.calls.length).toBe(1);\n      });\n      const [, details] = handleValueChange.mock.calls[0] as [\n        number | number[],\n        SliderRoot.ChangeEventDetails,\n      ];\n      expect(details.reason).toBe(REASONS.trackPress);\n    });\n\n    it.skipIf(isJSDOM)('drags the intended thumb when 3 thumbs are present', async () => {\n      const handleValueChange = vi.fn();\n\n      await render(\n        <TestMultiThumbSlider\n          defaultValue={[10, 40, 60]}\n          min={0}\n          max={100}\n          onValueChange={handleValueChange}\n        />,\n      );\n\n      const sliderControl = screen.getByTestId('control');\n      const thirdThumb = screen.getAllByTestId('thumb')[2];\n\n      vi.spyOn(sliderControl, 'getBoundingClientRect').mockImplementation(getHorizontalSliderRect);\n      vi.spyOn(thirdThumb, 'getBoundingClientRect').mockImplementation(() => ({\n        width: 0,\n        height: 0,\n        bottom: 0,\n        left: 60,\n        right: 60,\n        top: 0,\n        x: 60,\n        y: 0,\n        toJSON() {},\n      }));\n\n      fireEvent.pointerDown(thirdThumb, {\n        pointerId: 1,\n        buttons: 1,\n        clientX: 60,\n      });\n\n      fireEvent.pointerMove(document, {\n        pointerId: 1,\n        buttons: 1,\n        clientX: 80,\n      });\n\n      expect(handleValueChange.mock.calls.length).toBeGreaterThan(0);\n\n      const [newValue] = handleValueChange.mock.lastCall ?? [];\n      expect(handleValueChange.mock.lastCall?.[1].activeThumbIndex).toBe(2);\n      expect(newValue[0]).toBe(10);\n      expect(newValue[1]).toBe(40);\n      expect(newValue[2]).not.toBe(60);\n    });\n\n    it.skipIf(isJSDOM)('should fire only when the value changes', async () => {\n      const handleValueChange = vi.fn();\n      await render(<TestSlider defaultValue={20} onValueChange={handleValueChange} />);\n\n      const sliderControl = screen.getByTestId('control');\n\n      vi.spyOn(sliderControl, 'getBoundingClientRect').mockImplementation(getHorizontalSliderRect);\n\n      fireEvent.pointerDown(sliderControl, {\n        buttons: 1,\n        clientX: 21,\n      });\n\n      fireEvent.pointerMove(document.body, {\n        buttons: 1,\n        clientX: 22,\n      });\n      // Sometimes another event with the same position is fired by the browser.\n      fireEvent.pointerMove(document.body, {\n        buttons: 1,\n        clientX: 22,\n      });\n\n      expect(handleValueChange.mock.calls.length).toBe(2);\n      expect(handleValueChange.mock.calls[0][0]).toEqual(21);\n      expect(handleValueChange.mock.calls[1][0]).toEqual(22);\n    });\n\n    type Values = Array<[string, number[]]>;\n\n    const values = [\n      ['readonly range', Object.freeze([2, 1])],\n      ['range', [2, 1]],\n    ] as Values;\n    values.forEach(([valueLabel, value]) => {\n      it.skipIf(isJSDOM)(`is called even if the ${valueLabel} did not change`, async () => {\n        const handleValueChange = vi.fn();\n\n        await render(\n          <TestRangeSlider\n            min={0}\n            max={5}\n            onValueChange={handleValueChange}\n            value={value}\n            style={{ width: '100px' }}\n          />,\n        );\n\n        const sliderControl = screen.getByTestId('control');\n\n        vi.spyOn(sliderControl, 'getBoundingClientRect').mockImplementation(\n          getHorizontalSliderRect,\n        );\n\n        // pixel:  0   20  40  60  80  100\n        // slider: |---|---|---|---|---|\n        // values: 0   1   2   3   4   5\n        // value:      ��   ��\n        // mouse:           ��\n\n        fireEvent.pointerDown(sliderControl, {\n          buttons: 1,\n          clientX: 41,\n        });\n\n        expect(handleValueChange.mock.calls.length).toBe(1);\n        expect(handleValueChange.mock.calls[0][0]).not.toBe(value);\n        expect(handleValueChange.mock.calls[0][0]).toEqual(value.slice().sort((a, b) => a - b));\n      });\n    });\n\n    it('should pass \"name\" and \"value\" as part of the event.target for onValueChange', async () => {\n      const handleValueChange = vi\n        .fn()\n        .mockImplementation((newValue, data) => (data as any).event.target);\n\n      await render(\n        <TestSlider onValueChange={handleValueChange} name=\"change-testing\" value={3} />,\n      );\n\n      const slider = screen.getByRole('slider');\n\n      await act(async () => {\n        slider.focus();\n      });\n      fireEvent.change(slider, {\n        target: {\n          value: 4,\n        },\n      });\n\n      expect(handleValueChange.mock.calls.length).toBe(1);\n      const target = handleValueChange.mock.results[0]?.value;\n      expect(target).toEqual({\n        name: 'change-testing',\n        value: 4,\n      });\n    });\n\n    it.skipIf(!isJSDOM)(\n      'should not rely on the global event when cloning change events',\n      async () => {\n        const hadGlobalEvent = Object.prototype.hasOwnProperty.call(globalThis, 'event');\n        const previousDescriptor = Object.getOwnPropertyDescriptor(globalThis, 'event');\n        const globalEventConstructor = class {\n          constructor() {\n            throw new Error('Should not construct global event');\n          }\n        };\n        const fakeGlobalEvent = {\n          type: 'click',\n          constructor: globalEventConstructor,\n        };\n\n        Object.defineProperty(globalThis, 'event', {\n          configurable: true,\n          get() {\n            return fakeGlobalEvent;\n          },\n          set() {\n            // Ignore assignments from the event system to ensure we never use it.\n          },\n        });\n\n        try {\n          const handleValueChange = vi.fn();\n\n          await render(\n            <TestSlider onValueChange={handleValueChange} name=\"change-testing\" value={3} />,\n          );\n\n          const slider = screen.getByRole('slider');\n\n          await act(async () => {\n            slider.focus();\n          });\n\n          expectVitest(() => {\n            fireEvent.change(slider, {\n              target: {\n                value: 4,\n              },\n            });\n          }).not.toThrow();\n\n          expectVitest(handleValueChange).toHaveBeenCalledTimes(1);\n        } finally {\n          if (hadGlobalEvent && previousDescriptor) {\n            Object.defineProperty(globalThis, 'event', previousDescriptor);\n          } else {\n            delete (globalThis as any).event;\n          }\n        }\n      },\n    );\n\n    it.skipIf(isJSDOM)('should handle keyboard changes inside a shadow root', async () => {\n      const host = document.createElement('div');\n      document.body.appendChild(host);\n      const shadowRoot = host.attachShadow({ mode: 'open' });\n      const container = document.createElement('div');\n      shadowRoot.appendChild(container);\n\n      try {\n        const handleValueChange = vi.fn();\n\n        await render(<TestSlider onValueChange={handleValueChange} name=\"shadow\" value={3} />, {\n          container,\n        });\n\n        const slider = shadowRoot.querySelector('input[type=\"range\"]');\n        expectVitest(slider).toBeTruthy();\n\n        if (!slider) {\n          return;\n        }\n\n        await act(async () => {\n          (slider as HTMLInputElement).focus();\n        });\n\n        await act(async () => {\n          slider.dispatchEvent(new KeyboardEvent('keydown', { key: ARROW_RIGHT, bubbles: true }));\n        });\n\n        expectVitest(handleValueChange).toHaveBeenCalledTimes(1);\n      } finally {\n        await act(async () => {\n          host.remove();\n        });\n      }\n    });\n\n    it.skipIf(isJSDOM)(\n      'onValueCommitted is called with the same value as the latest onValueChange when pointerUp occurs at a different location than onValueChange',\n      async () => {\n        const handleValueChange = vi.fn();\n        const handleValueCommitted = vi.fn();\n\n        await render(\n          <TestSlider\n            onValueChange={handleValueChange}\n            onValueCommitted={handleValueCommitted}\n            defaultValue={0}\n          />,\n        );\n\n        const sliderControl = screen.getByTestId('control');\n\n        vi.spyOn(sliderControl, 'getBoundingClientRect').mockImplementation(\n          getHorizontalSliderRect,\n        );\n\n        fireEvent.pointerDown(sliderControl, {\n          buttons: 1,\n          clientX: 10,\n        });\n        fireEvent.pointerMove(sliderControl, {\n          buttons: 1,\n          clientX: 15,\n        });\n        fireEvent.pointerUp(sliderControl, {\n          buttons: 1,\n          clientX: 20,\n        });\n\n        expect(handleValueChange.mock.calls.length).toBe(2);\n        expect(handleValueChange.mock.calls[0][0]).toBe(10);\n        expect(handleValueChange.mock.calls[1][0]).toBe(15);\n        expect(handleValueCommitted.mock.calls.length).toBe(1);\n        expect(handleValueCommitted.mock.calls[0][0]).toBe(15);\n        expect(handleValueCommitted.mock.calls[0][1].reason).toBe('drag');\n      },\n    );\n  });\n\n  describe('keyboard interactions', () => {\n    [\n      ['ltr', 'horizontal', [ARROW_LEFT, ARROW_DOWN], [ARROW_RIGHT, ARROW_UP]],\n      ['ltr', 'vertical', [ARROW_LEFT, ARROW_DOWN], [ARROW_RIGHT, ARROW_UP]],\n      ['rtl', 'horizontal', [ARROW_RIGHT, ARROW_DOWN], [ARROW_LEFT, ARROW_UP]],\n      ['rtl', 'vertical', [ARROW_RIGHT, ARROW_DOWN], [ARROW_LEFT, ARROW_UP]],\n    ].forEach((entry) => {\n      const [direction, orientation, decrementKeys, incrementKeys] = entry as [\n        direction: TextDirection,\n        orientation: Orientation,\n        decrementKeys: string[],\n        incrementKeys: string[],\n      ];\n\n      describe(String(direction), () => {\n        describe(`orientation: ${orientation}`, () => {\n          decrementKeys.forEach((key) => {\n            it(`key: ${key} decrements the value`, async () => {\n              const handleValueChange = vi.fn();\n              const { user } = await render(\n                <div dir={direction}>\n                  <DirectionProvider direction={direction}>\n                    <Slider.Root\n                      orientation={orientation}\n                      defaultValue={20}\n                      onValueChange={handleValueChange}\n                    >\n                      <Slider.Control>\n                        <Slider.Track>\n                          <Slider.Indicator />\n                          <Slider.Thumb data-testid=\"thumb\" />\n                        </Slider.Track>\n                      </Slider.Control>\n                    </Slider.Root>\n                  </DirectionProvider>\n                </div>,\n              );\n\n              const input = screen.getByRole('slider');\n\n              await user.keyboard('[Tab]');\n              expect(input).toHaveFocus();\n\n              await user.keyboard(`[${key}]`);\n              expect(handleValueChange.mock.calls.length).toBe(1);\n              expect(handleValueChange.mock.calls[0][0]).toEqual(19);\n              expect(input).toHaveAttribute('aria-valuenow', '19');\n            });\n\n            it(`key: ${key} decrements the value by largeStep when Shift is pressed`, async () => {\n              const handleValueChange = vi.fn();\n              const { user } = await render(\n                <div dir={direction}>\n                  <DirectionProvider direction={direction}>\n                    <Slider.Root\n                      orientation={orientation}\n                      defaultValue={20}\n                      largeStep={10}\n                      onValueChange={handleValueChange}\n                    >\n                      <Slider.Control>\n                        <Slider.Track>\n                          <Slider.Indicator />\n                          <Slider.Thumb data-testid=\"thumb\" />\n                        </Slider.Track>\n                      </Slider.Control>\n                    </Slider.Root>\n                  </DirectionProvider>\n                </div>,\n              );\n\n              const input = screen.getByRole('slider');\n\n              await user.keyboard('[Tab]');\n              expect(input).toHaveFocus();\n\n              await user.keyboard(`{Shift>}{${key}}`);\n              expect(handleValueChange.mock.calls.length).toBe(1);\n              expect(handleValueChange.mock.calls[0][0]).toEqual(10);\n              expect(input).toHaveAttribute('aria-valuenow', '10');\n            });\n\n            it(`key: ${key} stops at min when decrementing while Shift is pressed`, async () => {\n              const handleValueChange = vi.fn();\n              const { user } = await render(\n                <div dir={direction}>\n                  <DirectionProvider direction={direction}>\n                    <Slider.Root\n                      orientation={orientation}\n                      defaultValue={20}\n                      largeStep={10}\n                      min={15}\n                      onValueChange={handleValueChange}\n                    >\n                      <Slider.Control>\n                        <Slider.Track>\n                          <Slider.Indicator />\n                          <Slider.Thumb data-testid=\"thumb\" />\n                        </Slider.Track>\n                      </Slider.Control>\n                    </Slider.Root>\n                  </DirectionProvider>\n                </div>,\n              );\n\n              const input = screen.getByRole('slider');\n\n              await user.keyboard('[Tab]');\n              expect(input).toHaveFocus();\n\n              await user.keyboard(`{Shift>}{${key}}`);\n              expect(handleValueChange.mock.calls.length).toBe(1);\n              expect(handleValueChange.mock.calls[0][0]).toEqual(15);\n              expect(input).toHaveAttribute('aria-valuenow', '15');\n            });\n          });\n\n          incrementKeys.forEach((key) => {\n            it(`key: ${key} increments the value`, async () => {\n              const handleValueChange = vi.fn();\n              const { user } = await render(\n                <div dir={direction}>\n                  <DirectionProvider direction={direction}>\n                    <Slider.Root\n                      orientation={orientation}\n                      defaultValue={20}\n                      onValueChange={handleValueChange}\n                    >\n                      <Slider.Control>\n                        <Slider.Track>\n                          <Slider.Indicator />\n                          <Slider.Thumb data-testid=\"thumb\" />\n                        </Slider.Track>\n                      </Slider.Control>\n                    </Slider.Root>\n                  </DirectionProvider>\n                </div>,\n              );\n\n              const input = screen.getByRole('slider');\n\n              await user.keyboard('[Tab]');\n              expect(input).toHaveFocus();\n\n              await user.keyboard(`[${key}]`);\n              expect(handleValueChange.mock.calls.length).toBe(1);\n              expect(handleValueChange.mock.calls[0][0]).toEqual(21);\n              expect(input).toHaveAttribute('aria-valuenow', '21');\n            });\n\n            it(`key: ${key} increments the value by largeStep when Shift is pressed`, async () => {\n              const handleValueChange = vi.fn();\n              const { user } = await render(\n                <div dir={direction}>\n                  <DirectionProvider direction={direction}>\n                    <Slider.Root\n                      orientation={orientation}\n                      defaultValue={20}\n                      largeStep={10}\n                      onValueChange={handleValueChange}\n                    >\n                      <Slider.Control>\n                        <Slider.Track>\n                          <Slider.Indicator />\n                          <Slider.Thumb data-testid=\"thumb\" />\n                        </Slider.Track>\n                      </Slider.Control>\n                    </Slider.Root>\n                  </DirectionProvider>\n                </div>,\n              );\n\n              const input = screen.getByRole('slider');\n\n              await user.keyboard('[Tab]');\n              expect(input).toHaveFocus();\n\n              await user.keyboard(`{Shift>}{${key}}`);\n              expect(handleValueChange.mock.calls.length).toBe(1);\n              expect(handleValueChange.mock.calls[0][0]).toEqual(30);\n              expect(input).toHaveAttribute('aria-valuenow', '30');\n            });\n\n            it(`key: ${key} stops at max when incrementing while Shift is pressed`, async () => {\n              const handleValueChange = vi.fn();\n              const { user } = await render(\n                <div dir={direction}>\n                  <DirectionProvider direction={direction}>\n                    <Slider.Root\n                      orientation={orientation}\n                      defaultValue={20}\n                      largeStep={10}\n                      max={21}\n                      onValueChange={handleValueChange}\n                    >\n                      <Slider.Control>\n                        <Slider.Track>\n                          <Slider.Indicator />\n                          <Slider.Thumb data-testid=\"thumb\" />\n                        </Slider.Track>\n                      </Slider.Control>\n                    </Slider.Root>\n                  </DirectionProvider>\n                </div>,\n              );\n\n              const input = screen.getByRole('slider');\n\n              await user.keyboard('[Tab]');\n              expect(input).toHaveFocus();\n\n              await user.keyboard(`{Shift>}{${key}}`);\n              expect(handleValueChange.mock.calls.length).toBe(1);\n              expect(handleValueChange.mock.calls[0][0]).toEqual(21);\n              expect(input).toHaveAttribute('aria-valuenow', '21');\n            });\n          });\n\n          describe('key: End', () => {\n            it('sets value to max in a single value slider', async () => {\n              const handleValueChange = vi.fn();\n              const { user } = await render(\n                <div dir={direction}>\n                  <DirectionProvider direction={direction}>\n                    <Slider.Root\n                      orientation={orientation}\n                      defaultValue={20}\n                      max={77}\n                      onValueChange={handleValueChange}\n                    >\n                      <Slider.Control>\n                        <Slider.Track>\n                          <Slider.Indicator />\n                          <Slider.Thumb data-testid=\"thumb\" />\n                        </Slider.Track>\n                      </Slider.Control>\n                    </Slider.Root>\n                  </DirectionProvider>\n                </div>,\n              );\n\n              const input = screen.getByRole('slider');\n\n              await user.keyboard('[Tab]');\n              expect(input).toHaveFocus();\n\n              await user.keyboard(`[${END}]`);\n              expect(handleValueChange.mock.calls.length).toBe(1);\n              expect(handleValueChange.mock.calls[0][0]).toEqual(77);\n              expect(input).toHaveAttribute('aria-valuenow', '77');\n            });\n\n            it('sets value to the maximum possible value in a range slider', async () => {\n              const handleValueChange = vi.fn();\n              const { user } = await render(\n                <div dir={direction}>\n                  <DirectionProvider direction={direction}>\n                    <Slider.Root defaultValue={[20, 50]} max={77} onValueChange={handleValueChange}>\n                      <Slider.Control>\n                        <Slider.Track>\n                          <Slider.Indicator />\n                          <Slider.Thumb index={0} />\n                          <Slider.Thumb index={1} />\n                        </Slider.Track>\n                      </Slider.Control>\n                    </Slider.Root>\n                  </DirectionProvider>\n                </div>,\n              );\n\n              const [input1, input2] = screen.getAllByRole('slider');\n\n              await user.keyboard('[Tab]');\n              expect(input1).toHaveFocus();\n\n              await user.keyboard(`[${END}]`);\n              expect(handleValueChange.mock.calls.length).toBe(1);\n              expect(handleValueChange.mock.calls[0][0]).toEqual([50, 50]);\n              await user.keyboard(`[${END}]`);\n              expect(handleValueChange.mock.calls.length).toBe(1);\n\n              await user.keyboard('[Tab]');\n              expect(input2).toHaveFocus();\n\n              await user.keyboard(`[${END}]`);\n              expect(handleValueChange.mock.calls.length).toBe(2);\n              expect(handleValueChange.mock.calls[1][0]).toEqual([50, 77]);\n            });\n          });\n\n          describe('key: Home', () => {\n            it('sets value to min in a single value slider', async () => {\n              const handleValueChange = vi.fn();\n              const { user } = await render(\n                <div dir={direction}>\n                  <DirectionProvider direction={direction}>\n                    <Slider.Root\n                      orientation={orientation}\n                      defaultValue={20}\n                      min={17}\n                      onValueChange={handleValueChange}\n                    >\n                      <Slider.Control>\n                        <Slider.Track>\n                          <Slider.Indicator />\n                          <Slider.Thumb data-testid=\"thumb\" />\n                        </Slider.Track>\n                      </Slider.Control>\n                    </Slider.Root>\n                  </DirectionProvider>\n                </div>,\n              );\n\n              const input = screen.getByRole('slider');\n\n              await user.keyboard('[Tab]');\n              expect(input).toHaveFocus();\n\n              await user.keyboard(`[${HOME}]`);\n              expect(handleValueChange.mock.calls.length).toBe(1);\n              expect(handleValueChange.mock.calls[0][0]).toEqual(17);\n              expect(input).toHaveAttribute('aria-valuenow', '17');\n            });\n\n            it('sets value to the minimum possible value in a range slider', async () => {\n              const handleValueChange = vi.fn();\n              const { user } = await render(\n                <div dir={direction}>\n                  <DirectionProvider direction={direction}>\n                    <Slider.Root defaultValue={[20, 50]} min={7} onValueChange={handleValueChange}>\n                      <Slider.Control>\n                        <Slider.Track>\n                          <Slider.Indicator />\n                          <Slider.Thumb index={0} />\n                          <Slider.Thumb index={1} />\n                        </Slider.Track>\n                      </Slider.Control>\n                    </Slider.Root>\n                  </DirectionProvider>\n                </div>,\n              );\n\n              const [input1, input2] = screen.getAllByRole('slider');\n\n              await user.keyboard('[Tab]');\n              await user.keyboard('[Tab]');\n              expect(input2).toHaveFocus();\n\n              await user.keyboard(`[${HOME}]`);\n              expect(handleValueChange.mock.calls.length).toBe(1);\n              expect(handleValueChange.mock.calls[0][0]).toEqual([20, 20]);\n              await user.keyboard(`[${HOME}]`);\n              expect(handleValueChange.mock.calls.length).toBe(1);\n\n              await user.keyboard('{Shift>}{Tab}');\n              expect(input1).toHaveFocus();\n\n              await user.keyboard(`[${HOME}]`);\n              expect(handleValueChange.mock.calls.length).toBe(2);\n              expect(handleValueChange.mock.calls[1][0]).toEqual([7, 20]);\n            });\n          });\n\n          describe('key: PageUp', () => {\n            it('increments the value by largeStep', async () => {\n              const handleValueChange = vi.fn();\n              const { user } = await render(\n                <div dir={direction}>\n                  <DirectionProvider direction={direction}>\n                    <Slider.Root\n                      orientation={orientation}\n                      defaultValue={20}\n                      largeStep={5}\n                      onValueChange={handleValueChange}\n                    >\n                      <Slider.Control>\n                        <Slider.Track>\n                          <Slider.Indicator />\n                          <Slider.Thumb data-testid=\"thumb\" />\n                        </Slider.Track>\n                      </Slider.Control>\n                    </Slider.Root>\n                  </DirectionProvider>\n                </div>,\n              );\n\n              const input = screen.getByRole('slider');\n\n              await user.keyboard('[Tab]');\n              expect(input).toHaveFocus();\n\n              await user.keyboard('[PageUp]');\n              expect(handleValueChange.mock.calls.length).toBe(1);\n              expect(handleValueChange.mock.calls[0][0]).toEqual(25);\n              expect(input).toHaveAttribute('aria-valuenow', '25');\n            });\n\n            it('does not exceed max', async () => {\n              const handleValueChange = vi.fn();\n              const { user } = await render(\n                <div dir={direction}>\n                  <DirectionProvider direction={direction}>\n                    <Slider.Root\n                      orientation={orientation}\n                      defaultValue={20}\n                      largeStep={5}\n                      max={21}\n                      onValueChange={handleValueChange}\n                    >\n                      <Slider.Control>\n                        <Slider.Track>\n                          <Slider.Indicator />\n                          <Slider.Thumb data-testid=\"thumb\" />\n                        </Slider.Track>\n                      </Slider.Control>\n                    </Slider.Root>\n                  </DirectionProvider>\n                </div>,\n              );\n\n              const input = screen.getByRole('slider');\n\n              await user.keyboard('[Tab]');\n              expect(input).toHaveFocus();\n\n              await user.keyboard('[PageUp]');\n              expect(handleValueChange.mock.calls.length).toBe(1);\n              expect(handleValueChange.mock.calls[0][0]).toEqual(21);\n              expect(input).toHaveAttribute('aria-valuenow', '21');\n            });\n          });\n\n          describe('key: PageDown', () => {\n            it('decrements the value by largeStep', async () => {\n              const handleValueChange = vi.fn();\n              const { user } = await render(\n                <div dir={direction}>\n                  <DirectionProvider direction={direction}>\n                    <Slider.Root\n                      orientation={orientation}\n                      defaultValue={20}\n                      largeStep={5}\n                      onValueChange={handleValueChange}\n                    >\n                      <Slider.Control>\n                        <Slider.Track>\n                          <Slider.Indicator />\n                          <Slider.Thumb data-testid=\"thumb\" />\n                        </Slider.Track>\n                      </Slider.Control>\n                    </Slider.Root>\n                  </DirectionProvider>\n                </div>,\n              );\n\n              const input = screen.getByRole('slider');\n\n              await user.keyboard('[Tab]');\n              expect(input).toHaveFocus();\n\n              await user.keyboard('[PageDown]');\n              expect(handleValueChange.mock.calls.length).toBe(1);\n              expect(handleValueChange.mock.calls[0][0]).toEqual(15);\n              expect(input).toHaveAttribute('aria-valuenow', '15');\n            });\n\n            it('does not go below min', async () => {\n              const handleValueChange = vi.fn();\n              const { user } = await render(\n                <div dir={direction}>\n                  <DirectionProvider direction={direction}>\n                    <Slider.Root\n                      orientation={orientation}\n                      defaultValue={20}\n                      largeStep={5}\n                      min={17}\n                      onValueChange={handleValueChange}\n                    >\n                      <Slider.Control>\n                        <Slider.Track>\n                          <Slider.Indicator />\n                          <Slider.Thumb data-testid=\"thumb\" />\n                        </Slider.Track>\n                      </Slider.Control>\n                    </Slider.Root>\n                  </DirectionProvider>\n                </div>,\n              );\n\n              const input = screen.getByRole('slider');\n\n              await user.keyboard('[Tab]');\n              expect(input).toHaveFocus();\n\n              await user.keyboard('[PageDown]');\n              expect(handleValueChange.mock.calls.length).toBe(1);\n              expect(handleValueChange.mock.calls[0][0]).toEqual(17);\n              expect(input).toHaveAttribute('aria-valuenow', '17');\n            });\n          });\n        });\n      });\n\n      it('keypresses should correct invalid values', async () => {\n        function App() {\n          const [val, setVal] = React.useState(5.4698);\n          return (\n            <Slider.Root value={val} onValueChange={setVal} min={0} max={10} step={1}>\n              <Slider.Control>\n                <Slider.Track>\n                  <Slider.Indicator />\n                  <Slider.Thumb data-testid=\"thumb\" />\n                </Slider.Track>\n              </Slider.Control>\n            </Slider.Root>\n          );\n        }\n        const { user } = await render(<App />);\n\n        const input = screen.getByRole('slider');\n\n        expect(input).toHaveAttribute('aria-valuenow', '5.4698');\n        await user.keyboard('[Tab]');\n        expect(input).toHaveFocus();\n        await user.keyboard(`[${ARROW_RIGHT}]`);\n        expect(input).toHaveAttribute('aria-valuenow', '6');\n      });\n    });\n  });\n\n  describe('prop: format', () => {\n    it('formats the value', async () => {\n      function formatValue(v: number) {\n        return new Intl.NumberFormat(undefined, USD_NUMBER_FORMAT).format(v);\n      }\n\n      await render(<TestSlider defaultValue={50} format={USD_NUMBER_FORMAT} />);\n\n      const value = screen.getByTestId('value');\n      const slider = screen.getByRole('slider');\n      expect(value).toHaveTextContent(formatValue(50));\n      expect(slider).toHaveAttribute('aria-valuetext', formatValue(50));\n    });\n\n    it('formats range values', async () => {\n      function formatValue(v: number) {\n        return new Intl.NumberFormat(undefined, USD_NUMBER_FORMAT).format(v);\n      }\n\n      await render(<TestRangeSlider defaultValue={[50, 75]} format={USD_NUMBER_FORMAT} />);\n\n      const value = screen.getByTestId('value');\n      expect(value).toHaveTextContent(`${formatValue(50)} – ${formatValue(75)}`);\n      const [slider1, slider2] = screen.getAllByRole('slider');\n      expect(slider1).toHaveAttribute('aria-valuetext', `${formatValue(50)} start range`);\n      expect(slider2).toHaveAttribute('aria-valuetext', `${formatValue(75)} end range`);\n    });\n  });\n\n  describe('prop: locale', () => {\n    it('sets the locale when formatting a single value', async () => {\n      const format: Intl.NumberFormatOptions = {\n        style: 'decimal',\n        minimumFractionDigits: 2,\n        maximumFractionDigits: 2,\n      };\n      const expectedValue = new Intl.NumberFormat('de-DE', format).format(70.51);\n\n      await render(<TestSlider value={70.51} format={format} step={0.01} locale=\"de-DE\" />);\n\n      expect(screen.getByTestId('value')).toHaveTextContent(expectedValue);\n    });\n\n    it('sets the locale when formatting a range value', async () => {\n      const format: Intl.NumberFormatOptions = {\n        style: 'decimal',\n        minimumFractionDigits: 2,\n        maximumFractionDigits: 2,\n      };\n      const expectedValue = `${new Intl.NumberFormat('de-DE', format).format(24.8)} – ${new Intl.NumberFormat('de-DE', format).format(70.51)}`;\n\n      await render(\n        <TestRangeSlider value={[24.8, 70.51]} format={format} step={0.01} locale=\"de-DE\" />,\n      );\n\n      expect(screen.getByTestId('value')).toHaveTextContent(expectedValue);\n    });\n  });\n\n  describe('Form', () => {\n    it('clears external errors on change', async () => {\n      const { user } = await render(\n        <Form\n          errors={{\n            test: 'test',\n          }}\n        >\n          <Field.Root name=\"test\" data-testid=\"field\">\n            <TestSlider data-testid=\"slider\" defaultValue={50} />\n            <Field.Error data-testid=\"error\" />\n          </Field.Root>\n        </Form>,\n      );\n\n      const slider = screen.getByRole('slider');\n\n      expect(slider).toHaveAttribute('aria-invalid', 'true');\n      expect(screen.getByTestId('error')).toHaveTextContent('test');\n\n      await user.keyboard('[Tab]');\n      expect(slider).toHaveFocus();\n\n      await user.keyboard(`{Shift>}{ArrowRight}`);\n\n      expect(slider).not.toHaveAttribute('aria-invalid');\n      expect(screen.queryByTestId('error')).toBe(null);\n    });\n\n    describe.skipIf(isJSDOM)('form submission', () => {\n      it('should include the slider value', async () => {\n        await render(\n          <Form\n            onSubmit={(event) => {\n              event.preventDefault();\n              const formData = new FormData(event.currentTarget);\n              expect(formData.get('slider')).toBe('25');\n            }}\n          >\n            <Field.Root name=\"slider\">\n              <Slider.Root defaultValue={25} format={USD_NUMBER_FORMAT}>\n                <Slider.Control>\n                  <Slider.Thumb />\n                </Slider.Control>\n              </Slider.Root>\n            </Field.Root>\n            <button type=\"submit\">Submit</button>\n          </Form>,\n        );\n\n        const submit = screen.getByRole('button');\n        fireEvent.click(submit);\n      });\n\n      it('should include range slider value', async () => {\n        await render(\n          <Form\n            onSubmit={(event) => {\n              event.preventDefault();\n              const formData = new FormData(event.currentTarget);\n              expect(formData.getAll('slider')).toEqual(['25', '50']);\n            }}\n          >\n            <Field.Root name=\"slider\">\n              <Slider.Root defaultValue={[25, 50]} format={USD_NUMBER_FORMAT}>\n                <Slider.Control>\n                  <Slider.Thumb />\n                  <Slider.Thumb />\n                </Slider.Control>\n              </Slider.Root>\n            </Field.Root>\n            <button type=\"submit\">Submit</button>\n          </Form>,\n        );\n\n        const submit = screen.getByRole('button');\n        fireEvent.click(submit);\n      });\n    });\n  });\n\n  describe('Field', () => {\n    it('should receive disabled prop from Field.Root', async () => {\n      await render(\n        <Field.Root disabled>\n          <Slider.Root data-testid=\"root\">\n            <Slider.Control>\n              <Slider.Thumb />\n            </Slider.Control>\n          </Slider.Root>\n        </Field.Root>,\n      );\n\n      const root = screen.getByTestId('root');\n      expect(root).toHaveAttribute('data-disabled', '');\n    });\n\n    it('should receive name prop from Field.Root', async () => {\n      await render(\n        <Field.Root name=\"field-slider\">\n          <Slider.Root>\n            <Slider.Control>\n              <Slider.Thumb />\n            </Slider.Control>\n          </Slider.Root>\n        </Field.Root>,\n      );\n\n      expect(screen.getByRole('slider')).toHaveAttribute('name', 'field-slider');\n    });\n\n    it('[data-touched]', async () => {\n      await render(\n        <Field.Root>\n          <Slider.Root data-testid=\"root\">\n            <Slider.Control>\n              <Slider.Thumb />\n            </Slider.Control>\n          </Slider.Root>\n        </Field.Root>,\n      );\n\n      const root = screen.getByTestId('root');\n      const input = screen.getByRole('slider');\n\n      fireEvent.focus(input);\n      fireEvent.blur(input);\n\n      expect(root).toHaveAttribute('data-touched', '');\n    });\n\n    it('[data-dirty]', async () => {\n      await render(\n        <Field.Root>\n          <Slider.Root data-testid=\"root\">\n            <Slider.Control>\n              <Slider.Thumb />\n            </Slider.Control>\n          </Slider.Root>\n        </Field.Root>,\n      );\n\n      const root = screen.getByTestId('root');\n      const input = screen.getByRole('slider');\n\n      expect(root).not.toHaveAttribute('data-dirty');\n\n      fireEvent.change(input, { target: { value: 'value' } });\n\n      expect(root).toHaveAttribute('data-dirty', '');\n    });\n\n    it('[data-focused]', async () => {\n      await render(\n        <Field.Root>\n          <Slider.Root data-testid=\"root\">\n            <Slider.Control>\n              <Slider.Thumb />\n            </Slider.Control>\n          </Slider.Root>\n        </Field.Root>,\n      );\n\n      const root = screen.getByTestId('root');\n      const input = screen.getByRole('slider');\n\n      expect(root).not.toHaveAttribute('data-focused');\n\n      fireEvent.focus(input);\n\n      expect(root).toHaveAttribute('data-focused', '');\n\n      fireEvent.blur(input);\n\n      expect(root).not.toHaveAttribute('data-focused');\n    });\n\n    describe('prop: validate', () => {\n      it('validationMode=onSubmit', async () => {\n        await render(\n          <Form>\n            <Field.Root validate={(val) => ((val as number) > 90 ? 'error' : null)}>\n              <Slider.Root defaultValue={99} data-testid=\"root\">\n                <Slider.Control>\n                  <Slider.Thumb data-testid=\"thumb\" />\n                </Slider.Control>\n              </Slider.Root>\n              <Field.Error data-testid=\"error\" />\n            </Field.Root>\n            <button type=\"submit\">submit</button>\n          </Form>,\n        );\n\n        const root = screen.getByTestId('root');\n        const thumb = screen.getByTestId('thumb');\n        const input = screen.getByRole('slider');\n        expect(input).not.toHaveAttribute('aria-invalid');\n        expect(screen.queryByTestId('error')).toBe(null);\n\n        fireEvent.change(input, { target: { value: '98' } });\n        expect(input).not.toHaveAttribute('aria-invalid');\n        expect(screen.queryByTestId('error')).toBe(null);\n\n        fireEvent.click(screen.getByText('submit'));\n        expect(input).toHaveAttribute('aria-invalid', 'true');\n        expect(screen.queryByTestId('error')).not.toBe(null);\n        expect(root).toHaveAttribute('data-invalid');\n        expect(thumb).toHaveAttribute('data-invalid');\n\n        fireEvent.change(input, { target: { value: '10' } });\n        expect(input).not.toHaveAttribute('aria-invalid');\n        expect(screen.queryByTestId('error')).toBe(null);\n        expect(root).not.toHaveAttribute('data-invalid');\n        expect(input).not.toHaveAttribute('data-invalid');\n        expect(root).toHaveAttribute('data-valid');\n        expect(thumb).toHaveAttribute('data-valid');\n\n        fireEvent.change(input, { target: { value: '94' } });\n        expect(input).toHaveAttribute('aria-invalid', 'true');\n        expect(screen.queryByTestId('error')).not.toBe(null);\n\n        fireEvent.change(input, { target: { value: '12' } });\n        expect(input).not.toHaveAttribute('aria-invalid');\n        expect(screen.queryByTestId('error')).toBe(null);\n      });\n\n      it('validationMode=onBlur', async () => {\n        await render(\n          <Field.Root\n            validationMode=\"onBlur\"\n            validate={(value) => ((value as number) > 1 ? 'error' : null)}\n          >\n            <Slider.Root>\n              <Slider.Control>\n                <Slider.Thumb />\n              </Slider.Control>\n            </Slider.Root>\n            <Field.Error data-testid=\"error\" />\n          </Field.Root>,\n        );\n\n        const input = screen.getByRole('slider');\n        expect(input).not.toHaveAttribute('aria-invalid');\n\n        fireEvent.change(input, { target: { value: '2' } });\n        expect(input).not.toHaveAttribute('aria-invalid');\n        fireEvent.blur(input);\n        await flushMicrotasks();\n        expect(input).toHaveAttribute('aria-invalid', 'true');\n      });\n\n      it('validationMode=onChange', async () => {\n        await render(\n          <Field.Root\n            validationMode=\"onChange\"\n            validate={(value) => (Number(value) === 1 ? 'error' : null)}\n          >\n            <Slider.Root defaultValue={0}>\n              <Slider.Control>\n                <Slider.Thumb />\n              </Slider.Control>\n            </Slider.Root>\n          </Field.Root>,\n        );\n\n        const input = screen.getByRole('slider');\n        expect(input).not.toHaveAttribute('aria-invalid');\n\n        fireEvent.change(input, { target: { value: '1' } });\n        await flushMicrotasks();\n        expect(input).toHaveAttribute('aria-invalid', 'true');\n      });\n\n      it('revalidates when the controlled value changes externally', async () => {\n        const validateSpy = vi.fn((value: unknown) => (Number(value) === 5 ? 'error' : null));\n\n        function App() {\n          const [value, setValue] = React.useState(0);\n\n          return (\n            <React.Fragment>\n              <Field.Root validationMode=\"onChange\" validate={validateSpy} name=\"volume\">\n                <Slider.Root value={value} onValueChange={(next) => setValue(next as number)}>\n                  <Slider.Control>\n                    <Slider.Thumb />\n                  </Slider.Control>\n                </Slider.Root>\n              </Field.Root>\n              <button type=\"button\" onClick={() => setValue(5)}>\n                Set externally\n              </button>\n            </React.Fragment>\n          );\n        }\n\n        await render(<App />);\n\n        const slider = screen.getByRole('slider');\n        const toggle = screen.getByText('Set externally');\n\n        expect(slider).not.toHaveAttribute('aria-invalid');\n        const initialCallCount = validateSpy.mock.calls.length;\n\n        fireEvent.click(toggle);\n        await flushMicrotasks();\n\n        expect(validateSpy.mock.calls.length).toBe(initialCallCount + 1);\n        expect(validateSpy.mock.lastCall?.[0]).toBe(5);\n        expect(slider).toHaveAttribute('aria-invalid', 'true');\n      });\n\n      it('receives an array value for range sliders', async () => {\n        const validateSpy = vi.fn();\n        await render(\n          <Form>\n            <Field.Root validate={validateSpy}>\n              <Slider.Root defaultValue={[5, 12]}>\n                <Slider.Control>\n                  <Slider.Thumb index={0} />\n                  <Slider.Thumb index={1} />\n                </Slider.Control>\n              </Slider.Root>\n              <Field.Error data-testid=\"error\" />\n            </Field.Root>\n            <button type=\"submit\">submit</button>\n          </Form>,\n        );\n\n        fireEvent.click(screen.getByText('submit'));\n        expect(validateSpy.mock.calls.length).toBe(1);\n        expect(validateSpy.mock.calls[0][0]).toEqual([5, 12]);\n      });\n\n      it('does not call validate on change when validationMode is omitted', async () => {\n        const validateSpy = vi.fn();\n        await render(\n          <Form>\n            <Field.Root validate={validateSpy}>\n              <Slider.Root defaultValue={50}>\n                <Slider.Control data-testid=\"control\">\n                  <Slider.Track>\n                    <Slider.Thumb aria-label=\"Value\" />\n                  </Slider.Track>\n                </Slider.Control>\n              </Slider.Root>\n            </Field.Root>\n            <button type=\"submit\">submit</button>\n          </Form>,\n        );\n\n        expect(validateSpy.mock.calls.length).toBe(0);\n\n        const sliderControl = screen.getByTestId('control');\n        fireEvent.pointerDown(sliderControl, { buttons: 1, clientX: 10 });\n        fireEvent.pointerUp(sliderControl, { buttons: 1, clientX: 30 });\n\n        expect(validateSpy.mock.calls.length).toBe(0);\n      });\n    });\n\n    it('Field.Label', async () => {\n      await render(\n        <Field.Root>\n          <Slider.Root>\n            <Slider.Control>\n              <Slider.Thumb />\n            </Slider.Control>\n          </Slider.Root>\n          <Field.Label data-testid=\"label\" />\n        </Field.Root>,\n      );\n\n      expect(screen.getByRole('slider')).toHaveAttribute(\n        'aria-labelledby',\n        screen.getByTestId('label').id,\n      );\n    });\n\n    it('Slider.Label', async () => {\n      await render(\n        <Slider.Root>\n          <Slider.Label data-testid=\"label\" />\n          <Slider.Control>\n            <Slider.Thumb />\n          </Slider.Control>\n        </Slider.Root>,\n      );\n\n      expect(screen.getByRole('slider')).toHaveAttribute(\n        'aria-labelledby',\n        screen.getByTestId('label').id,\n      );\n    });\n\n    it('Slider.Label focuses slider on click', async () => {\n      const { user } = await render(\n        <Slider.Root>\n          <Slider.Label data-testid=\"label\">Volume</Slider.Label>\n          <Slider.Control>\n            <Slider.Thumb />\n          </Slider.Control>\n        </Slider.Root>,\n      );\n\n      await user.click(screen.getByTestId('label'));\n\n      expect(screen.getByRole('slider')).toHaveFocus();\n    });\n\n    it('Slider.Label does not focus a thumb on click for range sliders', async () => {\n      const { user } = await render(\n        <Slider.Root defaultValue={[20, 80]}>\n          <Slider.Label data-testid=\"label\">Price range</Slider.Label>\n          <Slider.Control>\n            <Slider.Track>\n              <Slider.Thumb aria-label=\"Minimum price\" />\n              <Slider.Thumb aria-label=\"Maximum price\" />\n            </Slider.Track>\n          </Slider.Control>\n        </Slider.Root>,\n      );\n\n      await user.click(screen.getByTestId('label'));\n\n      const [minimumSlider, maximumSlider] = screen.getAllByRole('slider');\n      expect(minimumSlider).not.toHaveFocus();\n      expect(maximumSlider).not.toHaveFocus();\n    });\n\n    it('does not set aria-labelledby when getAriaLabel is provided', async () => {\n      await render(\n        <Slider.Root defaultValue={[20, 80]}>\n          <Slider.Label>Price range</Slider.Label>\n          <Slider.Control>\n            <Slider.Track>\n              <Slider.Thumb getAriaLabel={() => 'Minimum price'} />\n              <Slider.Thumb getAriaLabel={() => 'Maximum price'} />\n            </Slider.Track>\n          </Slider.Control>\n        </Slider.Root>,\n      );\n\n      const [minimumSlider, maximumSlider] = screen.getAllByRole('slider');\n      expect(minimumSlider).toHaveAttribute('aria-label', 'Minimum price');\n      expect(maximumSlider).toHaveAttribute('aria-label', 'Maximum price');\n      expect(minimumSlider).not.toHaveAttribute('aria-labelledby');\n      expect(maximumSlider).not.toHaveAttribute('aria-labelledby');\n    });\n\n    it('does not set fallback aria-labelledby when no label is rendered', async () => {\n      await render(\n        <Slider.Root>\n          <Slider.Control>\n            <Slider.Thumb aria-label=\"Volume\" />\n          </Slider.Control>\n        </Slider.Root>,\n      );\n\n      await waitFor(() => {\n        expect(screen.getByRole('slider')).not.toHaveAttribute('aria-labelledby');\n      });\n    });\n\n    it('updates Slider.Label linkage when root id changes', async () => {\n      const { setProps } = await render(\n        <Slider.Root id=\"first\" defaultValue={30} data-testid=\"root\">\n          <Slider.Label data-testid=\"label\">Volume</Slider.Label>\n          <Slider.Control>\n            <Slider.Track>\n              <Slider.Thumb />\n            </Slider.Track>\n          </Slider.Control>\n        </Slider.Root>,\n      );\n\n      await setProps({ id: 'second' });\n\n      /* eslint-disable testing-library/no-wait-for-multiple-assertions */\n      await waitFor(() => {\n        const root = screen.getByTestId('root');\n        const label = screen.getByTestId('label');\n        const slider = screen.getByRole('slider');\n        expect(root).toHaveAttribute('id', 'second');\n        expect(label.id).toBe('second-label');\n        expect(root).toHaveAttribute('aria-labelledby', label.id);\n        expect(slider).toHaveAttribute('aria-labelledby', label.id);\n      });\n      /* eslint-enable testing-library/no-wait-for-multiple-assertions */\n    });\n\n    it('Field.Description', async () => {\n      await render(\n        <Field.Root>\n          <Slider.Root data-testid=\"slider\">\n            <Slider.Control />\n          </Slider.Root>\n          <Field.Description data-testid=\"description\" />\n        </Field.Root>,\n      );\n\n      expect(screen.getByTestId('slider')).toHaveAttribute(\n        'aria-describedby',\n        screen.getByTestId('description').id,\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/slider/root/SliderRoot.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { ownerDocument } from '@base-ui/utils/owner';\nimport { useControlled } from '@base-ui/utils/useControlled';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useValueAsRef } from '@base-ui/utils/useValueAsRef';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { warn } from '@base-ui/utils/warn';\nimport type { BaseUIComponentProps, Orientation } from '../../utils/types';\nimport {\n  createChangeEventDetails,\n  createGenericEventDetails,\n  type BaseUIChangeEventDetails,\n  type BaseUIGenericEventDetails,\n} from '../../utils/createBaseUIEventDetails';\nimport { useValueChanged } from '../../utils/useValueChanged';\nimport { useBaseUiId } from '../../utils/useBaseUiId';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { clamp } from '../../utils/clamp';\nimport { areArraysEqual } from '../../utils/areArraysEqual';\nimport { activeElement } from '../../floating-ui-react/utils';\nimport { CompositeList, type CompositeMetadata } from '../../composite/list/CompositeList';\nimport type { FieldRootState } from '../../field/root/FieldRoot';\nimport { useField } from '../../field/useField';\nimport { useFieldRootContext } from '../../field/root/FieldRootContext';\nimport { useFormContext } from '../../form/FormContext';\nimport { useLabelableContext } from '../../labelable-provider/LabelableContext';\nimport { resolveAriaLabelledBy, getDefaultLabelId } from '../../utils/resolveAriaLabelledBy';\nimport { asc } from '../utils/asc';\nimport { getSliderValue } from '../utils/getSliderValue';\nimport { validateMinimumDistance } from '../utils/validateMinimumDistance';\nimport type { ThumbMetadata } from '../thumb/SliderThumb';\nimport { sliderStateAttributesMapping } from './stateAttributesMapping';\nimport { SliderRootContext } from './SliderRootContext';\nimport { REASONS } from '../../utils/reasons';\n\nfunction getSliderChangeEventReason(\n  event: React.KeyboardEvent | React.ChangeEvent,\n): SliderRootChangeEventReason {\n  return 'key' in event ? REASONS.keyboard : REASONS.inputChange;\n}\n\nfunction areValuesEqual(\n  newValue: number | readonly number[],\n  oldValue: number | readonly number[],\n) {\n  if (typeof newValue === 'number' && typeof oldValue === 'number') {\n    return newValue === oldValue;\n  }\n  if (Array.isArray(newValue) && Array.isArray(oldValue)) {\n    return areArraysEqual(newValue, oldValue);\n  }\n  return false;\n}\n\n/**\n * Groups all parts of the slider.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Slider](https://base-ui.com/react/components/slider)\n */\nexport const SliderRoot = React.forwardRef(function SliderRoot<\n  Value extends number | readonly number[],\n>(componentProps: SliderRoot.Props<Value>, forwardedRef: React.ForwardedRef<HTMLDivElement>) {\n  const {\n    'aria-labelledby': ariaLabelledByProp,\n    className,\n    defaultValue,\n    disabled: disabledProp = false,\n    id: idProp,\n    format,\n    largeStep = 10,\n    locale,\n    render,\n    max = 100,\n    min = 0,\n    minStepsBetweenValues = 0,\n    name: nameProp,\n    onValueChange: onValueChangeProp,\n    onValueCommitted: onValueCommittedProp,\n    orientation = 'horizontal',\n    step = 1,\n    thumbCollisionBehavior = 'push',\n    thumbAlignment = 'center',\n    value: valueProp,\n    ...elementProps\n  } = componentProps;\n\n  const id = useBaseUiId(idProp);\n  const defaultLabelId = getDefaultLabelId(id);\n  const onValueChange = useStableCallback(\n    onValueChangeProp as (\n      value: number | number[],\n      eventDetails: SliderRoot.ChangeEventDetails,\n    ) => void,\n  );\n  const onValueCommitted = useStableCallback(\n    onValueCommittedProp as (\n      value: number | readonly number[],\n      eventDetails: SliderRoot.CommitEventDetails,\n    ) => void,\n  );\n\n  const { clearErrors } = useFormContext();\n  const {\n    state: fieldState,\n    disabled: fieldDisabled,\n    name: fieldName,\n    setTouched,\n    setDirty,\n    validityData,\n    shouldValidateOnChange,\n    validation,\n  } = useFieldRootContext();\n  const { labelId: fieldLabelId } = useLabelableContext();\n  const [labelId, setLabelId] = React.useState<string | undefined>();\n\n  const ariaLabelledby = ariaLabelledByProp ?? resolveAriaLabelledBy(fieldLabelId, labelId);\n  const disabled = fieldDisabled || disabledProp;\n  const name = fieldName ?? nameProp;\n\n  // The internal value is potentially unsorted, e.g. to support frozen arrays\n  // https://github.com/mui/material-ui/pull/28472\n  const [valueUnwrapped, setValueUnwrapped] = useControlled({\n    controlled: valueProp,\n    default: defaultValue ?? min,\n    name: 'Slider',\n  });\n\n  const sliderRef = React.useRef<HTMLElement>(null);\n  const controlRef = React.useRef<HTMLElement>(null);\n  const thumbRefs = React.useRef<(HTMLElement | null)[]>([]);\n  // The input element nested in the pressed thumb.\n  const pressedInputRef = React.useRef<HTMLInputElement>(null);\n  // The px distance between the pointer and the center of a pressed thumb.\n  const pressedThumbCenterOffsetRef = React.useRef<number | null>(null);\n  // The index of the pressed thumb, or the closest thumb if the `Control` was pressed.\n  // This is updated on pointerdown, which is sooner than the `active/activeIndex`\n  // state which is updated later when the nested `input` receives focus.\n  const pressedThumbIndexRef = React.useRef(-1);\n  // The values when the current drag interaction started.\n  const pressedValuesRef = React.useRef<readonly number[] | null>(null);\n  const lastChangedValueRef = React.useRef<number | readonly number[] | null>(null);\n  const lastChangeReasonRef = React.useRef<SliderRoot.ChangeEventReason>('none');\n\n  const formatOptionsRef = useValueAsRef(format);\n\n  // We can't use the :active browser pseudo-classes.\n  // - The active state isn't triggered when clicking on the rail.\n  // - The active state isn't transferred when inversing a range slider.\n  const [active, setActiveState] = React.useState(-1);\n  const [lastUsedThumbIndex, setLastUsedThumbIndex] = React.useState(-1);\n  const [dragging, setDragging] = React.useState(false);\n  const [thumbMap, setThumbMap] = React.useState(\n    () => new Map<Node, CompositeMetadata<ThumbMetadata> | null>(),\n  );\n  const [indicatorPosition, setIndicatorPosition] = React.useState<(number | undefined)[]>([\n    undefined,\n    undefined,\n  ]);\n\n  const setActive = useStableCallback((value: number) => {\n    setActiveState(value);\n\n    if (value !== -1) {\n      setLastUsedThumbIndex(value);\n    }\n  });\n\n  useField({\n    id,\n    commit: validation.commit,\n    value: valueUnwrapped,\n    controlRef,\n    name,\n    getValue: () => valueUnwrapped,\n  });\n\n  useValueChanged(valueUnwrapped, () => {\n    clearErrors(name);\n\n    if (shouldValidateOnChange()) {\n      validation.commit(valueUnwrapped);\n    } else {\n      validation.commit(valueUnwrapped, true);\n    }\n\n    const initialValue = validityData.initialValue as Value | undefined;\n    let isDirty: boolean;\n    if (Array.isArray(valueUnwrapped) && Array.isArray(initialValue)) {\n      isDirty = !areArraysEqual(valueUnwrapped, initialValue);\n    } else {\n      isDirty = valueUnwrapped !== initialValue;\n    }\n    setDirty(isDirty);\n  });\n\n  const registerFieldControlRef = useStableCallback((element: HTMLElement | null) => {\n    if (element) {\n      controlRef.current = element;\n    }\n  });\n\n  const range = Array.isArray(valueUnwrapped);\n\n  const values = React.useMemo(() => {\n    if (!range) {\n      return [clamp(valueUnwrapped as number, min, max)];\n    }\n    return valueUnwrapped.slice().sort(asc);\n  }, [max, min, range, valueUnwrapped]);\n\n  const setValue = useStableCallback(\n    (newValue: number | number[], details?: SliderRoot.ChangeEventDetails) => {\n      if (Number.isNaN(newValue) || areValuesEqual(newValue, valueUnwrapped)) {\n        return;\n      }\n\n      const changeDetails =\n        details ??\n        createChangeEventDetails(REASONS.none, undefined, undefined, { activeThumbIndex: -1 });\n\n      lastChangeReasonRef.current = changeDetails.reason;\n\n      // Redefine target to allow name and value to be read.\n      // This allows seamless integration with the most popular form libraries.\n      // https://github.com/mui/material-ui/issues/13485#issuecomment-676048492\n      // Clone the event to not override `target` of the original event.\n      const nativeEvent = changeDetails.event;\n      const EventConstructor = (nativeEvent.constructor as typeof Event | undefined) ?? Event;\n      const clonedEvent = new EventConstructor(nativeEvent.type, nativeEvent);\n\n      Object.defineProperty(clonedEvent, 'target', {\n        writable: true,\n        value: { value: newValue, name },\n      });\n\n      changeDetails.event = clonedEvent;\n\n      lastChangedValueRef.current = newValue;\n\n      onValueChange(newValue, changeDetails);\n\n      if (changeDetails.isCanceled) {\n        return;\n      }\n\n      setValueUnwrapped(newValue as Value);\n    },\n  );\n\n  const handleInputChange = useStableCallback(\n    (valueInput: number, index: number, event: React.KeyboardEvent | React.ChangeEvent) => {\n      const newValue = getSliderValue(valueInput, index, min, max, range, values);\n\n      if (validateMinimumDistance(newValue, step, minStepsBetweenValues)) {\n        const reason = getSliderChangeEventReason(event);\n        setValue(\n          newValue,\n          createChangeEventDetails(reason, event.nativeEvent, undefined, {\n            activeThumbIndex: index,\n          }),\n        );\n        setTouched(true);\n\n        const nextValue = lastChangedValueRef.current ?? newValue;\n        onValueCommitted(nextValue, createGenericEventDetails(reason, event.nativeEvent));\n      }\n    },\n  );\n\n  if (process.env.NODE_ENV !== 'production') {\n    if (min >= max) {\n      warn('Slider `max` must be greater than `min`.');\n    }\n  }\n\n  useIsoLayoutEffect(() => {\n    const activeEl = activeElement(ownerDocument(sliderRef.current));\n    if (disabled && activeEl && sliderRef.current?.contains(activeEl)) {\n      // This is necessary because Firefox and Safari will keep focus\n      // on a disabled element:\n      // https://codesandbox.io/p/sandbox/mui-pr-22247-forked-h151h?file=/src/App.js\n      (activeEl as HTMLElement).blur();\n    }\n  }, [disabled]);\n\n  if (disabled && active !== -1) {\n    setActive(-1);\n  }\n\n  const state: SliderRootState = React.useMemo(\n    () => ({\n      ...fieldState,\n      activeThumbIndex: active,\n      disabled,\n      dragging,\n      orientation,\n      max,\n      min,\n      minStepsBetweenValues,\n      step,\n      values,\n    }),\n    [\n      fieldState,\n      active,\n      disabled,\n      dragging,\n      max,\n      min,\n      minStepsBetweenValues,\n      orientation,\n      step,\n      values,\n    ],\n  );\n\n  const contextValue: SliderRootContext = React.useMemo(\n    () => ({\n      active,\n      controlRef,\n      disabled,\n      dragging,\n      validation,\n      formatOptionsRef,\n      handleInputChange,\n      indicatorPosition,\n      inset: thumbAlignment !== 'center',\n      labelId: ariaLabelledby,\n      rootLabelId: defaultLabelId,\n      largeStep,\n      lastUsedThumbIndex,\n      lastChangedValueRef,\n      lastChangeReasonRef,\n      locale,\n      max,\n      min,\n      minStepsBetweenValues,\n      name,\n      onValueCommitted,\n      orientation,\n      pressedInputRef,\n      pressedThumbCenterOffsetRef,\n      pressedThumbIndexRef,\n      pressedValuesRef,\n      registerFieldControlRef,\n      renderBeforeHydration: thumbAlignment === 'edge',\n      setActive,\n      setDragging,\n      setIndicatorPosition,\n      setLabelId,\n      setValue,\n      state,\n      step,\n      thumbCollisionBehavior,\n      thumbMap,\n      thumbRefs,\n      values,\n    }),\n    [\n      active,\n      controlRef,\n      ariaLabelledby,\n      defaultLabelId,\n      disabled,\n      dragging,\n      validation,\n      formatOptionsRef,\n      handleInputChange,\n      indicatorPosition,\n      largeStep,\n      lastUsedThumbIndex,\n      lastChangedValueRef,\n      lastChangeReasonRef,\n      locale,\n      max,\n      min,\n      minStepsBetweenValues,\n      name,\n      onValueCommitted,\n      orientation,\n      pressedInputRef,\n      pressedThumbCenterOffsetRef,\n      pressedThumbIndexRef,\n      pressedValuesRef,\n      registerFieldControlRef,\n      setActive,\n      setDragging,\n      setIndicatorPosition,\n      setLabelId,\n      setValue,\n      state,\n      step,\n      thumbCollisionBehavior,\n      thumbAlignment,\n      thumbMap,\n      thumbRefs,\n      values,\n    ],\n  );\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: [forwardedRef, sliderRef],\n    props: [\n      {\n        'aria-labelledby': ariaLabelledby,\n        id,\n        role: 'group',\n      },\n      validation.getValidationProps,\n      elementProps,\n    ],\n    stateAttributesMapping: sliderStateAttributesMapping,\n  });\n\n  return (\n    <SliderRootContext.Provider value={contextValue}>\n      <CompositeList elementsRef={thumbRefs} onMapChange={setThumbMap}>\n        {element}\n      </CompositeList>\n    </SliderRootContext.Provider>\n  );\n}) as {\n  <Value extends number | readonly number[]>(\n    props: SliderRoot.Props<Value> & {\n      ref?: React.Ref<HTMLDivElement> | undefined;\n    },\n  ): React.JSX.Element;\n};\n\nexport interface SliderRootState extends FieldRootState {\n  /**\n   * The index of the active thumb.\n   */\n  activeThumbIndex: number;\n  /**\n   * Whether the component should ignore user interaction.\n   */\n  disabled: boolean;\n  /**\n   * Whether the thumb is currently being dragged.\n   */\n  dragging: boolean;\n  /**\n   * The maximum value.\n   */\n  max: number;\n  /**\n   * The minimum value.\n   */\n  min: number;\n  /**\n   * The minimum steps between values in a range slider.\n   * @default 0\n   */\n  minStepsBetweenValues: number;\n  /**\n   * The component orientation.\n   */\n  orientation: Orientation;\n  /**\n   * The step increment of the slider when incrementing or decrementing. It will snap\n   * to multiples of this value. Decimal values are supported.\n   * @default 1\n   */\n  step: number;\n  /**\n   * The raw number value of the slider.\n   */\n  values: readonly number[];\n}\n\nexport interface SliderRootProps<\n  Value extends number | readonly number[] = number | readonly number[],\n> extends BaseUIComponentProps<'div', SliderRootState> {\n  /**\n   * The uncontrolled value of the slider when it’s initially rendered.\n   *\n   * To render a controlled slider, use the `value` prop instead.\n   */\n  defaultValue?: Value | undefined;\n  /**\n   * Whether the slider should ignore user interaction.\n   * @default false\n   */\n  disabled?: boolean | undefined;\n  /**\n   * Options to format the input value.\n   */\n  format?: Intl.NumberFormatOptions | undefined;\n  /**\n   * The locale used by `Intl.NumberFormat` when formatting the value.\n   * Defaults to the user's runtime locale.\n   */\n  locale?: Intl.LocalesArgument | undefined;\n  /**\n   * The maximum allowed value of the slider.\n   * Should not be equal to min.\n   * @default 100\n   */\n  max?: number | undefined;\n  /**\n   * The minimum allowed value of the slider.\n   * Should not be equal to max.\n   * @default 0\n   */\n  min?: number | undefined;\n  /**\n   * The minimum steps between values in a range slider.\n   * @default 0\n   */\n  minStepsBetweenValues?: number | undefined;\n  /**\n   * Identifies the field when a form is submitted.\n   */\n  name?: string | undefined;\n  /**\n   * The component orientation.\n   * @default 'horizontal'\n   */\n  orientation?: Orientation | undefined;\n  /**\n   * The granularity with which the slider can step through values. (A \"discrete\" slider.)\n   * The `min` prop serves as the origin for the valid values.\n   * We recommend (max - min) to be evenly divisible by the step.\n   * @default 1\n   */\n  step?: number | undefined;\n  /**\n   * The granularity with which the slider can step through values when using Page Up/Page Down or Shift + Arrow Up/Arrow Down.\n   * @default 10\n   */\n  largeStep?: number | undefined;\n  /**\n   * How the thumb(s) are aligned relative to `Slider.Control` when the value is at `min` or `max`:\n   * - `center`: The center of the thumb is aligned with the control edge\n   * - `edge`: The thumb is inset within the control such that its edge is aligned with the control edge\n   * - `edge-client-only`: Same as `edge` but renders after React hydration on the client, reducing bundle size in return\n   * @default 'center'\n   */\n  thumbAlignment?: 'center' | 'edge' | 'edge-client-only' | undefined;\n  /**\n   * Controls how thumbs behave when they collide during pointer interactions.\n   *\n   * - `'push'` (default): Thumbs push each other without restoring their previous positions when dragged back.\n   * - `'swap'`: Thumbs swap places when dragged past each other.\n   * - `'none'`: Thumbs cannot move past each other; excess movement is ignored.\n   *\n   * @default 'push'\n   */\n  thumbCollisionBehavior?: 'push' | 'swap' | 'none' | undefined;\n  /**\n   * The value of the slider.\n   * For ranged sliders, provide an array with two values.\n   */\n  value?: Value | undefined;\n  /**\n   * Callback function that is fired when the slider's value changed.\n   * You can pull out the new value by accessing `event.target.value` (any).\n   *\n   * The `eventDetails.reason` indicates what triggered the change:\n   *\n   * - `'input-change'` when the hidden range input emits a change event (for example, via form integration)\n   * - `'track-press'` when the control track is pressed\n   * - `'drag'` while dragging a thumb\n   * - `'keyboard'` for keyboard input\n   * - `'none'` when the change is triggered without a specific interaction\n   */\n  onValueChange?:\n    | ((\n        value: Value extends number ? number : Value,\n        eventDetails: SliderRoot.ChangeEventDetails,\n      ) => void)\n    | undefined;\n  /**\n   * Callback function that is fired when the `pointerup` is triggered.\n   * **Warning**: This is a generic event not a change event.\n   *\n   * The `eventDetails.reason` indicates what triggered the commit:\n   *\n   * - `'drag'` while dragging a thumb\n   * - `'track-press'` when the control track is pressed\n   * - `'keyboard'` for keyboard input\n   * - `'input-change'` when the hidden range input emits a change event (for example, via form integration)\n   * - `'none'` when the commit occurs without a specific interaction\n   */\n  onValueCommitted?:\n    | ((\n        value: Value extends number ? number : Value,\n        eventDetails: SliderRoot.CommitEventDetails,\n      ) => void)\n    | undefined;\n}\n\nexport interface SliderRootChangeEventCustomProperties {\n  /**\n   * The index of the active thumb at the time of the change.\n   */\n  activeThumbIndex: number;\n}\n\nexport type SliderRootChangeEventReason =\n  | typeof REASONS.inputChange\n  | typeof REASONS.trackPress\n  | typeof REASONS.drag\n  | typeof REASONS.keyboard\n  | typeof REASONS.none;\nexport type SliderRootChangeEventDetails = BaseUIChangeEventDetails<\n  SliderRoot.ChangeEventReason,\n  SliderRootChangeEventCustomProperties\n>;\n\nexport type SliderRootCommitEventReason =\n  | typeof REASONS.inputChange\n  | typeof REASONS.trackPress\n  | typeof REASONS.drag\n  | typeof REASONS.keyboard\n  | typeof REASONS.none;\nexport type SliderRootCommitEventDetails = BaseUIGenericEventDetails<SliderRoot.CommitEventReason>;\n\nexport namespace SliderRoot {\n  export type State = SliderRootState;\n  export type Props<Value extends number | readonly number[] = number | readonly number[]> =\n    SliderRootProps<Value>;\n  export type ChangeEventReason = SliderRootChangeEventReason;\n  export type ChangeEventDetails = SliderRootChangeEventDetails;\n  export type CommitEventReason = SliderRootCommitEventReason;\n  export type CommitEventDetails = SliderRootCommitEventDetails;\n}\n"
  },
  {
    "path": "packages/react/src/slider/root/SliderRootContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport type { Orientation } from '../../utils/types';\nimport type { CompositeMetadata } from '../../composite/list/CompositeList';\nimport type { UseFieldValidationReturnValue } from '../../field/root/useFieldValidation';\nimport type { ThumbMetadata } from '../thumb/SliderThumb';\nimport type { SliderRoot, SliderRootState } from './SliderRoot';\n\nexport interface SliderRootContext {\n  /**\n   * The index of the active thumb.\n   */\n  active: number;\n  /**\n   * The index of the most recently interacted thumb.\n   */\n  lastUsedThumbIndex: number;\n  controlRef: React.RefObject<HTMLElement | null>;\n  dragging: boolean;\n  disabled: boolean;\n  validation: UseFieldValidationReturnValue;\n  formatOptionsRef: React.RefObject<Intl.NumberFormatOptions | undefined>;\n  handleInputChange: (\n    valueInput: number,\n    index: number,\n    event: React.KeyboardEvent | React.ChangeEvent,\n  ) => void;\n  indicatorPosition: (number | undefined)[];\n  inset: boolean;\n  labelId?: string | undefined;\n  rootLabelId?: string | undefined;\n  /**\n   * The large step value of the slider when incrementing or decrementing while the shift key is held,\n   * or when using Page-Up or Page-Down keys. Snaps to multiples of this value.\n   * @default 10\n   */\n  largeStep: number;\n  lastChangedValueRef: React.RefObject<number | readonly number[] | null>;\n  lastChangeReasonRef: React.RefObject<SliderRoot.ChangeEventReason>;\n  /**\n   * The locale used by `Intl.NumberFormat` when formatting the value.\n   * Defaults to the user's runtime locale.\n   */\n  locale?: Intl.LocalesArgument | undefined;\n  /**\n   * The maximum allowed value of the slider.\n   */\n  max: number;\n  /**\n   * The minimum allowed value of the slider.\n   */\n  min: number;\n  /**\n   * The minimum steps between values in a range slider.\n   */\n  minStepsBetweenValues: number;\n  name: string | undefined;\n  /**\n   * Function to be called when drag ends and the pointer is released.\n   */\n  onValueCommitted: (\n    newValue: number | readonly number[],\n    data: SliderRoot.CommitEventDetails,\n  ) => void;\n  /**\n   * The component orientation.\n   * @default 'horizontal'\n   */\n  orientation: Orientation;\n  pressedInputRef: React.RefObject<HTMLInputElement | null>;\n  pressedThumbCenterOffsetRef: React.RefObject<number | null>;\n  pressedThumbIndexRef: React.RefObject<number>;\n  pressedValuesRef: React.RefObject<readonly number[] | null>;\n  renderBeforeHydration: boolean;\n  registerFieldControlRef: React.RefCallback<Element> | null;\n  setActive: (index: number) => void;\n  setDragging: React.Dispatch<React.SetStateAction<boolean>>;\n  setIndicatorPosition: React.Dispatch<React.SetStateAction<(number | undefined)[]>>;\n  setLabelId: React.Dispatch<React.SetStateAction<string | undefined>>;\n  /**\n   * Callback fired when dragging and invokes onValueChange.\n   */\n  setValue: (newValue: number | number[], details?: SliderRoot.ChangeEventDetails) => void;\n  state: SliderRootState;\n  /**\n   * The step increment of the slider when incrementing or decrementing. It will snap\n   * to multiples of this value. Decimal values are supported.\n   * @default 1\n   */\n  step: number;\n  thumbCollisionBehavior: 'push' | 'swap' | 'none';\n  thumbMap: Map<Node, CompositeMetadata<ThumbMetadata> | null>;\n  thumbRefs: React.RefObject<(HTMLElement | null)[]>;\n  /**\n   * The value(s) of the slider\n   */\n  values: readonly number[];\n}\n\nexport const SliderRootContext = React.createContext<SliderRootContext | undefined>(undefined);\n\nexport function useSliderRootContext() {\n  const context = React.useContext(SliderRootContext);\n  if (context === undefined) {\n    throw new Error(\n      'Base UI: SliderRootContext is missing. Slider parts must be placed within <Slider.Root>.',\n    );\n  }\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/slider/root/SliderRootDataAttributes.ts",
    "content": "export enum SliderRootDataAttributes {\n  /**\n   * Present while the user is dragging.\n   */\n  dragging = 'data-dragging',\n  /**\n   * Indicates the orientation of the slider.\n   * @type {'horizontal' | 'vertical'}\n   */\n  orientation = 'data-orientation',\n  /**\n   * Present when the slider is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the slider is in valid state (when wrapped in Field.Root).\n   */\n  valid = 'data-valid',\n  /**\n   * Present when the slider is in invalid state (when wrapped in Field.Root).\n   */\n  invalid = 'data-invalid',\n  /**\n   * Present when the slider has been touched (when wrapped in Field.Root).\n   */\n  touched = 'data-touched',\n  /**\n   * Present when the slider's value has changed (when wrapped in Field.Root).\n   */\n  dirty = 'data-dirty',\n  /**\n   * Present when the slider is focused (when wrapped in Field.Root).\n   */\n  focused = 'data-focused',\n}\n"
  },
  {
    "path": "packages/react/src/slider/root/stateAttributesMapping.ts",
    "content": "import type { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport type { SliderRootState } from './SliderRoot';\nimport { fieldValidityMapping } from '../../field/utils/constants';\n\nexport const sliderStateAttributesMapping: StateAttributesMapping<SliderRootState> = {\n  activeThumbIndex: () => null,\n  max: () => null,\n  min: () => null,\n  minStepsBetweenValues: () => null,\n  step: () => null,\n  values: () => null,\n  ...fieldValidityMapping,\n};\n"
  },
  {
    "path": "packages/react/src/slider/thumb/SliderThumb.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { fireEvent, screen, waitFor } from '@mui/internal-test-utils';\nimport { Slider } from '@base-ui/react/slider';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\nimport { isWebKit } from '@base-ui/utils/detectBrowser';\nimport { createTouches, getHorizontalSliderRect } from '../utils/test-utils';\n\ndescribe('<Slider.Thumb />', () => {\n  const { render, renderToString } = createRenderer();\n\n  describeConformance(<Slider.Thumb />, () => ({\n    render: (node) => {\n      return render(<Slider.Root>{node}</Slider.Root>);\n    },\n    refInstanceof: window.HTMLDivElement,\n  }));\n\n  describe('ARIA attributes', () => {\n    ['aria-label', 'aria-labelledby', 'aria-describedby'].forEach((attr) => {\n      it(`forwards ${attr} to the input`, async () => {\n        await render(\n          <Slider.Root defaultValue={50}>\n            <Slider.Control>\n              <Slider.Thumb\n                {...{\n                  [attr]: 'test',\n                }}\n              />\n            </Slider.Control>\n          </Slider.Root>,\n        );\n        expect(screen.getByRole('slider')).toHaveAttribute(attr, 'test');\n      });\n    });\n  });\n\n  // AT (e.g. Android Talkback) may use increase/decrease actions to interact\n  // with the slider which works on `input type=\"range\"` via change events, but\n  // not pure ARIA implementations using `div role=\"slider\"`. The `input`\n  // element(s) must be the only focusable element(s).\n  // See:\n  // - https://issues.chromium.org/issues/40816094\n  // - https://github.com/mui/material-ui/issues/23506\n  describe('events', () => {\n    describe.skipIf(isJSDOM)('focus and blur', () => {\n      it('single thumb', async () => {\n        const focusAndBlurSpy = vi.fn((event) => event.target);\n        const { user } = await render(\n          <Slider.Root defaultValue={50}>\n            <Slider.Control>\n              <Slider.Thumb onFocus={focusAndBlurSpy} onBlur={focusAndBlurSpy} />\n            </Slider.Control>\n          </Slider.Root>,\n        );\n        expect(document.body).toHaveFocus();\n        const input = screen.getByRole('slider');\n        expect(input.tagName).toBe('INPUT');\n        expect(input).toHaveAttribute('type', 'range');\n\n        await user.keyboard('[Tab]');\n        // We assert above that the tabbable elements of the slider are\n        // input[type=\"range\"] because TalkBack doesn't simulate keyboard events for increments\n        // or decrements (proof: https://issues.chromium.org/issues/40816094). Instead, it triggers change events on those native slider inputs.\n        expect(input).toHaveFocus();\n        expect(focusAndBlurSpy.mock.calls.length).toBe(1);\n        expect(focusAndBlurSpy.mock.results[0]?.value).toBe(input);\n\n        await user.keyboard('[Tab]');\n        expect(document.body).toHaveFocus();\n        expect(focusAndBlurSpy.mock.calls.length).toBe(2);\n        expect(focusAndBlurSpy.mock.results.at(-1)?.value).toBe(input);\n      });\n\n      it('multiple thumbs', async () => {\n        const focusSpy = vi.fn((event) => event.target);\n        const blurSpy = vi.fn((event) => event.target);\n        const { user } = await render(\n          <Slider.Root defaultValue={[50, 70]}>\n            <Slider.Control>\n              <Slider.Thumb onFocus={focusSpy} onBlur={blurSpy} />\n              <Slider.Thumb onFocus={focusSpy} onBlur={blurSpy} />\n            </Slider.Control>\n          </Slider.Root>,\n        );\n        expect(document.body).toHaveFocus();\n        const [slider1, slider2] = screen.getAllByRole('slider');\n        expect(slider1).toHaveProperty('tagName', 'INPUT');\n        expect(slider1).toHaveAttribute('type', 'range');\n        expect(slider2).toHaveProperty('tagName', 'INPUT');\n        expect(slider2).toHaveAttribute('type', 'range');\n\n        await user.keyboard('[Tab]');\n        expect(slider1).toHaveFocus();\n        expect(focusSpy.mock.calls.length).toBe(1);\n        expect(focusSpy.mock.results.at(-1)?.value).toBe(slider1);\n\n        await user.keyboard('[Tab]');\n        expect(blurSpy.mock.calls.length).toBe(1);\n        expect(blurSpy.mock.results.at(-1)?.value).toBe(slider1);\n        expect(slider2).toHaveFocus();\n        expect(focusSpy.mock.calls.length).toBe(2);\n        expect(focusSpy.mock.results.at(-1)?.value).toBe(slider2);\n\n        await user.keyboard('[Tab]');\n        expect(blurSpy.mock.calls.length).toBe(2);\n        expect(blurSpy.mock.results.at(-1)?.value).toBe(slider2);\n        expect(document.body).toHaveFocus();\n      });\n    });\n\n    describe('change', () => {\n      it('handles change events', async () => {\n        const handleValueChange = vi.fn();\n        await render(\n          <Slider.Root defaultValue={50} onValueChange={handleValueChange}>\n            <Slider.Control>\n              <Slider.Thumb />\n            </Slider.Control>\n          </Slider.Root>,\n        );\n\n        const slider = screen.getByRole('slider');\n        expect(slider).toHaveAttribute('aria-valuenow', '50');\n        fireEvent.change(slider, { target: { value: '51' } });\n        expect(handleValueChange.mock.calls.length).toBe(1);\n        expect(slider).toHaveAttribute('aria-valuenow', '51');\n      });\n\n      it('does not change the value beyond min and max', async () => {\n        const handleValueChange = vi.fn();\n        await render(\n          <Slider.Root defaultValue={50} min={40} max={60} onValueChange={handleValueChange}>\n            <Slider.Control>\n              <Slider.Thumb />\n            </Slider.Control>\n          </Slider.Root>,\n        );\n\n        const slider = screen.getByRole('slider');\n        expect(slider).toHaveAttribute('aria-valuenow', '50');\n\n        fireEvent.change(slider, { target: { value: '30' } });\n        expect(slider).toHaveAttribute('aria-valuenow', '40');\n        expect(handleValueChange.mock.calls.length).toBe(1);\n        fireEvent.change(slider, { target: { value: '30' } });\n        expect(handleValueChange.mock.calls.length).toBe(1);\n\n        fireEvent.change(slider, { target: { value: '70' } });\n        expect(slider).toHaveAttribute('aria-valuenow', '60');\n        expect(handleValueChange.mock.calls.length).toBe(2);\n        fireEvent.change(slider, { target: { value: '70' } });\n        expect(handleValueChange.mock.calls.length).toBe(2);\n      });\n\n      it('handles non-integer values', async () => {\n        const handleValueChange = vi.fn();\n        await render(\n          <Slider.Root\n            defaultValue={50}\n            min={-100}\n            max={100}\n            step={0.00000001}\n            onValueChange={handleValueChange}\n          >\n            <Slider.Control>\n              <Slider.Thumb />\n            </Slider.Control>\n          </Slider.Root>,\n        );\n\n        const slider = screen.getByRole('slider');\n        expect(slider).toHaveAttribute('aria-valuenow', '50');\n        expect(slider).toHaveAttribute('step', '1e-8');\n\n        fireEvent.change(slider, { target: { value: '51.1' } });\n        expect(slider).toHaveAttribute('aria-valuenow', '51.1');\n\n        fireEvent.change(slider, { target: { value: '0.00000005' } });\n        expect(slider).toHaveAttribute('aria-valuenow', '5e-8');\n\n        fireEvent.change(slider, { target: { value: '1e-7' } });\n        expect(slider).toHaveAttribute('aria-valuenow', '1e-7');\n      });\n    });\n  });\n\n  describe('prop: tabIndex', () => {\n    it('can be removed from the tab sequence', async () => {\n      const { user } = await render(\n        <Slider.Root defaultValue={50}>\n          <Slider.Control>\n            <Slider.Thumb tabIndex={-1} />\n          </Slider.Control>\n        </Slider.Root>,\n      );\n\n      expect(screen.getByRole('slider')).toHaveProperty('tabIndex', -1);\n      expect(document.body).toHaveFocus();\n      await user.keyboard('[Tab]');\n      expect(document.body).toHaveFocus();\n    });\n  });\n\n  describe('prop: children', () => {\n    it('renders the nested input as a sibling to children', async () => {\n      await render(\n        <Slider.Root defaultValue={50}>\n          <Slider.Control>\n            <Slider.Thumb data-testid=\"thumb\">\n              <span data-testid=\"child\" />\n            </Slider.Thumb>\n          </Slider.Control>\n        </Slider.Root>,\n      );\n\n      const thumb = screen.getByTestId('thumb');\n      expect(thumb.querySelector('input[type=\"range\"]')).toBe(screen.getByRole('slider'));\n      expect(thumb.querySelector('[data-testid=\"child\"]')).toBe(screen.getByTestId('child'));\n    });\n\n    it('renders the nested input when using the short form render prop', async () => {\n      await render(\n        <Slider.Root defaultValue={50}>\n          <Slider.Control>\n            <Slider.Thumb render={<div data-testid=\"thumb\" />}>\n              <span data-testid=\"child\" />\n            </Slider.Thumb>\n          </Slider.Control>\n        </Slider.Root>,\n      );\n\n      const thumb = screen.getByTestId('thumb');\n      expect(thumb.querySelector('input[type=\"range\"]')).toBe(screen.getByRole('slider'));\n      expect(thumb.querySelector('[data-testid=\"child\"]')).toBe(screen.getByTestId('child'));\n    });\n\n    it('renders the nested input when using the long form render prop', async () => {\n      await render(\n        <Slider.Root defaultValue={50}>\n          <Slider.Control>\n            <Slider.Thumb render={(props) => <div data-testid=\"thumb\" {...props} />}>\n              <span data-testid=\"child\" />\n            </Slider.Thumb>\n          </Slider.Control>\n        </Slider.Root>,\n      );\n\n      const thumb = screen.getByTestId('thumb');\n      expect(thumb.querySelector('input[type=\"range\"]')).toBe(screen.getByRole('slider'));\n      expect(thumb.querySelector('[data-testid=\"child\"]')).toBe(screen.getByTestId('child'));\n    });\n  });\n\n  describe('prop: inputRef', () => {\n    it('can focus the input element', async () => {\n      function App() {\n        const inputRef = React.useRef<HTMLInputElement>(null);\n        return (\n          <React.Fragment>\n            <Slider.Root defaultValue={50}>\n              <Slider.Control>\n                <Slider.Thumb inputRef={inputRef} />\n              </Slider.Control>\n            </Slider.Root>\n            <button\n              onClick={() => {\n                if (inputRef.current) {\n                  inputRef.current.focus();\n                }\n              }}\n            >\n              Button\n            </button>\n          </React.Fragment>\n        );\n      }\n      const { user } = await render(<App />);\n\n      expect(document.body).toHaveFocus();\n      await user.click(screen.getByText('Button'));\n      expect(screen.getByRole('slider')).toHaveFocus();\n    });\n  });\n\n  describe('stacking order', () => {\n    it('relies on DOM order before any thumb is used', async () => {\n      await render(\n        <Slider.Root defaultValue={[20, 20]}>\n          <Slider.Control>\n            <Slider.Thumb data-testid=\"thumb-0\" />\n            <Slider.Thumb data-testid=\"thumb-1\" />\n          </Slider.Control>\n        </Slider.Root>,\n      );\n\n      expect(screen.getByTestId('thumb-0').style.zIndex).toBe('');\n      expect(screen.getByTestId('thumb-1').style.zIndex).toBe('');\n    });\n\n    it('keeps the most recently active thumb on top after focus moves away', async () => {\n      const { user } = await render(\n        <Slider.Root defaultValue={[20, 20]}>\n          <Slider.Control>\n            <Slider.Thumb data-testid=\"thumb-0\" />\n            <Slider.Thumb data-testid=\"thumb-1\" />\n          </Slider.Control>\n        </Slider.Root>,\n      );\n\n      const [thumb0, thumb1] = [screen.getByTestId('thumb-0'), screen.getByTestId('thumb-1')];\n\n      await user.keyboard('[Tab]');\n      expect(screen.getAllByRole('slider')[0]).toHaveFocus();\n      expect(thumb0.style.zIndex).toBe('2');\n\n      await user.keyboard('[Tab]');\n      expect(screen.getAllByRole('slider')[1]).toHaveFocus();\n      expect(thumb1.style.zIndex).toBe('2');\n\n      await user.keyboard('[Tab]');\n      expect(document.body).toHaveFocus();\n      expect(thumb1.style.zIndex).toBe('1');\n      expect(thumb0.style.zIndex).toBe('');\n    });\n  });\n\n  describe('prop: thumbAlignment', () => {\n    it.skipIf(isJSDOM)('recomputes inset positions when the slider becomes visible', async () => {\n      function App() {\n        const [visible, setVisible] = React.useState(false);\n\n        return (\n          <React.Fragment>\n            <button type=\"button\" onClick={() => setVisible(true)}>\n              show\n            </button>\n            <div style={{ display: visible ? 'block' : 'none' }}>\n              <Slider.Root defaultValue={30} thumbAlignment=\"edge\" style={{ width: '100px' }}>\n                <Slider.Control\n                  data-testid=\"control\"\n                  style={{ position: 'relative', width: '100%', height: '10px' }}\n                >\n                  <Slider.Track style={{ position: 'relative', width: '100%', height: '10px' }}>\n                    <Slider.Indicator data-testid=\"indicator\" />\n                    <Slider.Thumb data-testid=\"thumb\" style={{ width: '10px', height: '10px' }} />\n                  </Slider.Track>\n                </Slider.Control>\n              </Slider.Root>\n            </div>\n          </React.Fragment>\n        );\n      }\n\n      const { user } = await render(<App />);\n\n      const thumb = screen.getByTestId('thumb');\n      const indicator = screen.getByTestId('indicator');\n\n      await waitFor(() => {\n        expect(thumb.style.visibility).toBe('hidden');\n        expect(thumb.style.getPropertyValue('--position')).toBe('0%');\n        expect(indicator.style.visibility).toBe('hidden');\n        expect(indicator.style.getPropertyValue('--start-position')).toBe('0%');\n      });\n\n      await user.click(screen.getByRole('button', { name: 'show' }));\n\n      await waitFor(() => {\n        expect(thumb.style.visibility).toBe('');\n        expect(thumb.style.getPropertyValue('--position')).toBe('32%');\n        expect(indicator.style.visibility).toBe('');\n        expect(indicator.style.getPropertyValue('--start-position')).toBe('32%');\n      });\n    });\n\n    it.skipIf(isJSDOM)(\n      'recomputes range inset positions when the slider becomes visible',\n      async () => {\n        function App() {\n          const [visible, setVisible] = React.useState(false);\n\n          return (\n            <React.Fragment>\n              <button type=\"button\" onClick={() => setVisible(true)}>\n                show\n              </button>\n              <div style={{ display: visible ? 'block' : 'none' }}>\n                <Slider.Root\n                  defaultValue={[30, 70]}\n                  thumbAlignment=\"edge\"\n                  style={{ width: '100px' }}\n                >\n                  <Slider.Control\n                    data-testid=\"control\"\n                    style={{ position: 'relative', width: '100%', height: '10px' }}\n                  >\n                    <Slider.Track style={{ position: 'relative', width: '100%', height: '10px' }}>\n                      <Slider.Indicator data-testid=\"indicator\" />\n                      <Slider.Thumb\n                        data-testid=\"start-thumb\"\n                        style={{ width: '10px', height: '10px' }}\n                      />\n                      <Slider.Thumb\n                        data-testid=\"end-thumb\"\n                        style={{ width: '10px', height: '10px' }}\n                      />\n                    </Slider.Track>\n                  </Slider.Control>\n                </Slider.Root>\n              </div>\n            </React.Fragment>\n          );\n        }\n\n        const { user } = await render(<App />);\n\n        const startThumb = screen.getByTestId('start-thumb');\n        const endThumb = screen.getByTestId('end-thumb');\n        const indicator = screen.getByTestId('indicator');\n\n        await waitFor(() => {\n          expect(startThumb.style.visibility).toBe('hidden');\n          expect(startThumb.style.getPropertyValue('--position')).toBe('0%');\n          expect(endThumb.style.visibility).toBe('hidden');\n          expect(endThumb.style.getPropertyValue('--position')).toBe('0%');\n          expect(indicator.style.visibility).toBe('hidden');\n          expect(indicator.style.getPropertyValue('--start-position')).toBe('0%');\n          expect(indicator.style.getPropertyValue('--relative-size')).toBe('0%');\n        });\n\n        await user.click(screen.getByRole('button', { name: 'show' }));\n\n        await waitFor(() => {\n          expect(startThumb.style.visibility).toBe('');\n          expect(startThumb.style.getPropertyValue('--position')).toBe('32%');\n          expect(endThumb.style.visibility).toBe('');\n          expect(endThumb.style.getPropertyValue('--position')).toBe('68%');\n          expect(indicator.style.visibility).toBe('');\n          expect(indicator.style.getPropertyValue('--start-position')).toBe('32%');\n          expect(indicator.style.getPropertyValue('--relative-size')).toBe('36%');\n        });\n      },\n    );\n  });\n\n  /**\n   * Browser tests render with 1024px width by default, so most tests here set\n   * the component to `width: 100px` to make the asserted values more readable.\n   */\n  describe.skipIf(isJSDOM || isWebKit || typeof Touch === 'undefined')('positioning styles', () => {\n    describe('positions the thumb when dragged', () => {\n      it('single thumb', async () => {\n        await render(\n          <Slider.Root\n            style={{\n              width: '1000px',\n            }}\n          >\n            <Slider.Control data-testid=\"control\">\n              <Slider.Track>\n                <Slider.Indicator />\n                <Slider.Thumb data-testid=\"thumb\" />\n              </Slider.Track>\n            </Slider.Control>\n          </Slider.Root>,\n        );\n\n        const sliderControl = screen.getByTestId('control');\n\n        const thumbStyles = getComputedStyle(screen.getByTestId('thumb'));\n\n        vi.spyOn(sliderControl, 'getBoundingClientRect').mockImplementation(() =>\n          getHorizontalSliderRect(1000),\n        );\n\n        fireEvent.touchStart(\n          sliderControl,\n          createTouches([{ identifier: 1, clientX: 20, clientY: 0 }]),\n        );\n        fireEvent.touchMove(\n          document.body,\n          createTouches([{ identifier: 1, clientX: 199, clientY: 0 }]),\n        );\n        fireEvent.touchMove(\n          document.body,\n          createTouches([{ identifier: 1, clientX: 199, clientY: 0 }]),\n        );\n\n        fireEvent.touchMove(\n          document.body,\n          createTouches([{ identifier: 1, clientX: 199, clientY: 0 }]),\n        );\n\n        expect(thumbStyles.getPropertyValue('left')).toBe('200px');\n        fireEvent.touchEnd(\n          document.body,\n          createTouches([{ identifier: 1, clientX: 0, clientY: 0 }]),\n        );\n        expect(thumbStyles.getPropertyValue('left')).toBe('200px');\n      });\n\n      it('multiple thumbs', async () => {\n        await render(\n          <Slider.Root\n            defaultValue={[20, 40]}\n            style={{\n              width: '1000px',\n            }}\n          >\n            <Slider.Control data-testid=\"control\">\n              <Slider.Track>\n                <Slider.Indicator />\n                <Slider.Thumb data-testid=\"thumb\" />\n                <Slider.Thumb data-testid=\"thumb\" />\n              </Slider.Track>\n            </Slider.Control>\n          </Slider.Root>,\n        );\n\n        const sliderControl = screen.getByTestId('control');\n\n        const computedStyles = {\n          thumb1: getComputedStyle(screen.getAllByTestId('thumb')[0]),\n          thumb2: getComputedStyle(screen.getAllByTestId('thumb')[1]),\n        };\n\n        vi.spyOn(sliderControl, 'getBoundingClientRect').mockImplementation(() =>\n          getHorizontalSliderRect(1000),\n        );\n\n        fireEvent.touchStart(\n          sliderControl,\n          createTouches([{ identifier: 1, clientX: 400, clientY: 0 }]),\n        );\n        fireEvent.touchMove(\n          document.body,\n          createTouches([{ identifier: 1, clientX: 699, clientY: 0 }]),\n        );\n        fireEvent.touchMove(\n          document.body,\n          createTouches([{ identifier: 1, clientX: 699, clientY: 0 }]),\n        );\n\n        fireEvent.touchMove(\n          document.body,\n          createTouches([{ identifier: 1, clientX: 699, clientY: 0 }]),\n        );\n\n        expect(computedStyles.thumb2.getPropertyValue('left')).toBe('700px');\n        fireEvent.touchEnd(\n          document.body,\n          createTouches([{ identifier: 1, clientX: 0, clientY: 0 }]),\n        );\n        expect(computedStyles.thumb1.getPropertyValue('left')).toBe('200px');\n        expect(computedStyles.thumb2.getPropertyValue('left')).toBe('700px');\n      });\n\n      describe('prop: thumbCollisionBehavior', () => {\n        function getSliderValues() {\n          return screen\n            .getAllByRole('slider')\n            .map((input) => Number(input.getAttribute('aria-valuenow')));\n        }\n\n        it('prevents thumbs from passing each other when set to \"none\"', async () => {\n          await render(\n            <Slider.Root\n              defaultValue={[20, 40]}\n              thumbCollisionBehavior=\"none\"\n              style={{\n                width: '1000px',\n              }}\n            >\n              <Slider.Control data-testid=\"control\">\n                <Slider.Track>\n                  <Slider.Indicator />\n                  <Slider.Thumb index={0} data-testid=\"thumb1\" />\n                  <Slider.Thumb index={1} />\n                </Slider.Track>\n              </Slider.Control>\n            </Slider.Root>,\n          );\n\n          const sliderControl = screen.getByTestId('control');\n\n          vi.spyOn(sliderControl, 'getBoundingClientRect').mockImplementation(() =>\n            getHorizontalSliderRect(1000),\n          );\n\n          fireEvent.touchStart(\n            sliderControl,\n            createTouches([{ identifier: 1, clientX: 200, clientY: 0 }]),\n          );\n          fireEvent.touchMove(\n            document.body,\n            createTouches([{ identifier: 1, clientX: 600, clientY: 0 }]),\n          );\n          fireEvent.touchEnd(\n            document.body,\n            createTouches([{ identifier: 1, clientX: 600, clientY: 0 }]),\n          );\n\n          expect(getSliderValues()).toEqual([40, 40]);\n        });\n\n        it('pushes adjacent thumbs forward when set to \"push\"', async () => {\n          await render(\n            <Slider.Root\n              defaultValue={[20, 40]}\n              thumbCollisionBehavior=\"push\"\n              style={{\n                width: '1000px',\n              }}\n            >\n              <Slider.Control data-testid=\"control\">\n                <Slider.Track>\n                  <Slider.Indicator />\n                  <Slider.Thumb index={0} data-testid=\"thumb1\" />\n                  <Slider.Thumb index={1} />\n                </Slider.Track>\n              </Slider.Control>\n            </Slider.Root>,\n          );\n\n          const sliderControl = screen.getByTestId('control');\n\n          vi.spyOn(sliderControl, 'getBoundingClientRect').mockImplementation(() =>\n            getHorizontalSliderRect(1000),\n          );\n\n          fireEvent.touchStart(\n            sliderControl,\n            createTouches([{ identifier: 1, clientX: 200, clientY: 0 }]),\n          );\n          fireEvent.touchMove(\n            document.body,\n            createTouches([{ identifier: 1, clientX: 650, clientY: 0 }]),\n          );\n          fireEvent.touchEnd(\n            document.body,\n            createTouches([{ identifier: 1, clientX: 650, clientY: 0 }]),\n          );\n\n          expect(getSliderValues()).toEqual([65, 65]);\n        });\n\n        it('allows thumbs to swap when set to \"swap\"', async () => {\n          await render(\n            <Slider.Root\n              defaultValue={[20, 40]}\n              thumbCollisionBehavior=\"swap\"\n              style={{\n                width: '1000px',\n              }}\n            >\n              <Slider.Control data-testid=\"control\">\n                <Slider.Track>\n                  <Slider.Indicator />\n                  <Slider.Thumb index={0} data-testid=\"thumb1\" />\n                  <Slider.Thumb index={1} />\n                </Slider.Track>\n              </Slider.Control>\n            </Slider.Root>,\n          );\n\n          const sliderControl = screen.getByTestId('control');\n\n          vi.spyOn(sliderControl, 'getBoundingClientRect').mockImplementation(() =>\n            getHorizontalSliderRect(1000),\n          );\n\n          fireEvent.touchStart(\n            sliderControl,\n            createTouches([{ identifier: 1, clientX: 200, clientY: 0 }]),\n          );\n          fireEvent.touchMove(\n            document.body,\n            createTouches([{ identifier: 1, clientX: 700, clientY: 0 }]),\n          );\n          fireEvent.touchEnd(\n            document.body,\n            createTouches([{ identifier: 1, clientX: 700, clientY: 0 }]),\n          );\n\n          expect(getSliderValues()).toEqual([40, 70]);\n        });\n\n        it('maintains minimum steps between values when swapping', async () => {\n          await render(\n            <Slider.Root\n              defaultValue={[20, 40, 60]}\n              minStepsBetweenValues={10}\n              thumbCollisionBehavior=\"swap\"\n              style={{\n                width: '1000px',\n              }}\n            >\n              <Slider.Control data-testid=\"control\">\n                <Slider.Track>\n                  <Slider.Indicator />\n                  <Slider.Thumb index={0} data-testid=\"thumb1\" />\n                  <Slider.Thumb index={1} />\n                  <Slider.Thumb index={2} />\n                </Slider.Track>\n              </Slider.Control>\n            </Slider.Root>,\n          );\n\n          const sliderControl = screen.getByTestId('control');\n\n          vi.spyOn(sliderControl, 'getBoundingClientRect').mockImplementation(() =>\n            getHorizontalSliderRect(1000),\n          );\n\n          fireEvent.touchStart(\n            sliderControl,\n            createTouches([{ identifier: 1, clientX: 200, clientY: 0 }]),\n          );\n          fireEvent.touchMove(\n            sliderControl,\n            createTouches([{ identifier: 1, clientX: 500, clientY: 0 }]),\n          );\n          fireEvent.touchMove(\n            sliderControl,\n            createTouches([{ identifier: 1, clientX: 550, clientY: 0 }]),\n          );\n          fireEvent.touchMove(\n            sliderControl,\n            createTouches([{ identifier: 1, clientX: 800, clientY: 0 }]),\n          );\n          fireEvent.touchEnd(\n            sliderControl,\n            createTouches([{ identifier: 1, clientX: 800, clientY: 0 }]),\n          );\n\n          expect(getSliderValues()).toEqual([30, 50, 80]);\n        });\n      });\n    });\n\n    describe('positions the thumb when the controlled value changes externally', () => {\n      it('single thumb', async () => {\n        function App() {\n          const [val, setVal] = React.useState(20);\n          return (\n            <React.Fragment>\n              <button onClick={() => setVal(55)} />\n              <Slider.Root\n                value={val}\n                onValueChange={(newVal) => setVal(newVal as number)}\n                style={{\n                  width: '100px',\n                }}\n              >\n                <Slider.Control data-testid=\"control\">\n                  <Slider.Track>\n                    <Slider.Indicator />\n                    <Slider.Thumb data-testid=\"thumb\" />\n                  </Slider.Track>\n                </Slider.Control>\n              </Slider.Root>\n            </React.Fragment>\n          );\n        }\n        await render(<App />);\n\n        const thumbStyles = getComputedStyle(screen.getByTestId('thumb'));\n        expect(thumbStyles.getPropertyValue('left')).toBe('20px');\n\n        fireEvent.click(screen.getByRole('button'));\n        expect(thumbStyles.getPropertyValue('left')).toBe('55px');\n      });\n\n      it('multiple thumbs', async () => {\n        function App() {\n          const [val, setVal] = React.useState([20, 50]);\n          return (\n            <React.Fragment>\n              <button onClick={() => setVal([33, 72])} />\n              <Slider.Root\n                value={val}\n                onValueChange={(newVal) => setVal(newVal as number[])}\n                style={{\n                  width: '100px',\n                }}\n              >\n                <Slider.Control data-testid=\"control\">\n                  <Slider.Track>\n                    <Slider.Indicator />\n                    <Slider.Thumb data-testid=\"thumb\" />\n                    <Slider.Thumb data-testid=\"thumb\" />\n                  </Slider.Track>\n                </Slider.Control>\n              </Slider.Root>\n            </React.Fragment>\n          );\n        }\n        await render(<App />);\n\n        const computedStyles = {\n          thumb1: getComputedStyle(screen.getAllByTestId('thumb')[0]),\n          thumb2: getComputedStyle(screen.getAllByTestId('thumb')[1]),\n        };\n\n        expect(computedStyles.thumb1.getPropertyValue('left')).toBe('20px');\n        expect(computedStyles.thumb2.getPropertyValue('left')).toBe('50px');\n\n        fireEvent.click(screen.getByRole('button'));\n        expect(computedStyles.thumb1.getPropertyValue('left')).toBe('33px');\n        expect(computedStyles.thumb2.getPropertyValue('left')).toBe('72px');\n      });\n    });\n\n    it('thumb should not go out of bounds when the controlled value goes out of bounds', async () => {\n      function App() {\n        const [val, setVal] = React.useState(50);\n        return (\n          <React.Fragment>\n            <button onClick={() => setVal(119.9)}>max</button>\n            <button onClick={() => setVal(-7.31)}>min</button>\n            <Slider.Root\n              value={val}\n              onValueChange={setVal}\n              min={0}\n              max={100}\n              style={{ width: '100px' }}\n            >\n              <Slider.Control data-testid=\"control\">\n                <Slider.Track>\n                  <Slider.Indicator />\n                  <Slider.Thumb data-testid=\"thumb\" />\n                </Slider.Track>\n              </Slider.Control>\n            </Slider.Root>\n          </React.Fragment>\n        );\n      }\n      const { user } = await render(<App />);\n\n      const thumbStyles = getComputedStyle(screen.getByTestId('thumb'));\n      expect(thumbStyles.getPropertyValue('left')).toBe('50px');\n\n      await user.click(screen.getByRole('button', { name: 'max' }));\n      expect(thumbStyles.getPropertyValue('left')).toBe('100px');\n\n      await user.click(screen.getByRole('button', { name: 'min' }));\n      expect(thumbStyles.getPropertyValue('left')).toBe('0px');\n    });\n  });\n\n  describe.skipIf(isJSDOM)('server-side rendering', () => {\n    it('single thumb', async () => {\n      await renderToString(\n        <Slider.Root\n          defaultValue={30}\n          style={{\n            width: '100px',\n          }}\n        >\n          <Slider.Value />\n          <Slider.Control>\n            <Slider.Track>\n              <Slider.Indicator />\n              <Slider.Thumb data-testid=\"thumb\" />\n            </Slider.Track>\n          </Slider.Control>\n        </Slider.Root>,\n      );\n\n      expect(getComputedStyle(screen.getByTestId('thumb')).getPropertyValue('left')).toBe('30px');\n    });\n\n    it('multiple thumbs', async () => {\n      renderToString(\n        <Slider.Root\n          defaultValue={[30, 40]}\n          style={{\n            width: '100px',\n          }}\n        >\n          <Slider.Value />\n          <Slider.Control>\n            <Slider.Track>\n              <Slider.Thumb index={0} data-testid=\"thumb\" />\n              <Slider.Thumb index={1} data-testid=\"thumb\" />\n            </Slider.Track>\n          </Slider.Control>\n        </Slider.Root>,\n      );\n\n      const [thumb0, thumb1] = Array.from(await screen.findAllByTestId('thumb'));\n\n      expect(getComputedStyle(thumb0).getPropertyValue('left')).toBe('30px');\n      expect(getComputedStyle(thumb1).getPropertyValue('left')).toBe('40px');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/slider/thumb/SliderThumb.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useOnMount } from '@base-ui/utils/useOnMount';\nimport { useMergedRefs } from '@base-ui/utils/useMergedRefs';\nimport { visuallyHidden } from '@base-ui/utils/visuallyHidden';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { formatNumber } from '../../utils/formatNumber';\nimport { mergeProps } from '../../merge-props';\nimport { useBaseUiId } from '../../utils/useBaseUiId';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { valueToPercent } from '../../utils/valueToPercent';\nimport {\n  ARROW_DOWN,\n  ARROW_UP,\n  ARROW_RIGHT,\n  ARROW_LEFT,\n  HOME,\n  END,\n  COMPOSITE_KEYS,\n  PAGE_UP,\n  PAGE_DOWN,\n} from '../../composite/composite';\nimport { useCompositeListItem } from '../../composite/list/useCompositeListItem';\nimport { useDirection } from '../../direction-provider/DirectionContext';\nimport { useCSPContext } from '../../csp-provider/CSPContext';\nimport { useFieldRootContext } from '../../field/root/FieldRootContext';\nimport { type LabelableContext } from '../../labelable-provider/LabelableContext';\nimport { useLabelableId } from '../../labelable-provider/useLabelableId';\nimport { getMidpoint } from '../utils/getMidpoint';\nimport { getSliderValue } from '../utils/getSliderValue';\nimport { roundValueToStep } from '../utils/roundValueToStep';\nimport type { SliderRootState } from '../root/SliderRoot';\nimport { useSliderRootContext } from '../root/SliderRootContext';\nimport { sliderStateAttributesMapping } from '../root/stateAttributesMapping';\nimport { SliderThumbDataAttributes } from './SliderThumbDataAttributes';\nimport { script as prehydrationScript } from './prehydrationScript.min';\n\nconst ALL_KEYS = new Set([\n  ARROW_UP,\n  ARROW_DOWN,\n  ARROW_LEFT,\n  ARROW_RIGHT,\n  HOME,\n  END,\n  PAGE_UP,\n  PAGE_DOWN,\n]);\n\nfunction getDefaultAriaValueText(\n  values: readonly number[],\n  index: number,\n  format: Intl.NumberFormatOptions | undefined,\n  locale: Intl.LocalesArgument | undefined,\n): string | undefined {\n  if (index < 0) {\n    return undefined;\n  }\n\n  if (values.length === 2) {\n    if (index === 0) {\n      return `${formatNumber(values[index], locale, format)} start range`;\n    }\n\n    return `${formatNumber(values[index], locale, format)} end range`;\n  }\n\n  return format ? formatNumber(values[index], locale, format) : undefined;\n}\n\nfunction getNewValue(\n  thumbValue: number,\n  step: number,\n  direction: 1 | -1,\n  min: number,\n  max: number,\n): number {\n  return direction === 1 ? Math.min(thumbValue + step, max) : Math.max(thumbValue - step, min);\n}\n\n/**\n * The draggable part of the slider at the tip of the indicator.\n * Renders a `<div>` element and a nested `<input type=\"range\">`.\n *\n * Documentation: [Base UI Slider](https://base-ui.com/react/components/slider)\n */\nexport const SliderThumb = React.forwardRef(function SliderThumb(\n  componentProps: SliderThumb.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const {\n    render,\n    children: childrenProp,\n    className,\n    'aria-describedby': ariaDescribedByProp,\n    'aria-label': ariaLabelProp,\n    'aria-labelledby': ariaLabelledByProp,\n    disabled: disabledProp = false,\n    getAriaLabel: getAriaLabelProp,\n    getAriaValueText: getAriaValueTextProp,\n    id: idProp,\n    index: indexProp,\n    inputRef: inputRefProp,\n    onBlur: onBlurProp,\n    onFocus: onFocusProp,\n    onKeyDown: onKeyDownProp,\n    tabIndex: tabIndexProp,\n    ...elementProps\n  } = componentProps;\n\n  const { nonce } = useCSPContext();\n  const id = useBaseUiId(idProp);\n\n  const {\n    active: activeIndex,\n    lastUsedThumbIndex,\n    controlRef,\n    disabled: contextDisabled,\n    validation,\n    formatOptionsRef,\n    handleInputChange,\n    inset,\n    labelId,\n    largeStep,\n    locale,\n    max,\n    min,\n    minStepsBetweenValues,\n    name,\n    orientation,\n    pressedInputRef,\n    pressedThumbCenterOffsetRef,\n    pressedThumbIndexRef,\n    renderBeforeHydration,\n    setActive,\n    setIndicatorPosition,\n    state,\n    step,\n    values: sliderValues,\n  } = useSliderRootContext();\n\n  const direction = useDirection();\n\n  const disabled = disabledProp || contextDisabled;\n  const range = sliderValues.length > 1;\n  const vertical = orientation === 'vertical';\n  const rtl = direction === 'rtl';\n\n  const { setTouched, setFocused, validationMode } = useFieldRootContext();\n\n  const thumbRef = React.useRef<HTMLElement>(null);\n  const inputRef = React.useRef<HTMLInputElement>(null);\n\n  const defaultInputId = useBaseUiId();\n  const labelableId = useLabelableId();\n  const inputId = range ? defaultInputId : labelableId;\n\n  const thumbMetadata = React.useMemo(\n    () => ({\n      inputId,\n    }),\n    [inputId],\n  );\n\n  const { ref: listItemRef, index: compositeIndex } = useCompositeListItem<ThumbMetadata>({\n    metadata: thumbMetadata,\n  });\n\n  const index = !range ? 0 : (indexProp ?? compositeIndex);\n  const last = index === sliderValues.length - 1;\n  const thumbValue = sliderValues[index];\n  const thumbValuePercent = valueToPercent(thumbValue, min, max);\n\n  const [isMounted, setIsMounted] = React.useState(false);\n  const [positionPercent, setPositionPercent] = React.useState<number | undefined>();\n\n  useOnMount(() => setIsMounted(true));\n\n  const safeLastUsedThumbIndex =\n    lastUsedThumbIndex >= 0 && lastUsedThumbIndex < sliderValues.length ? lastUsedThumbIndex : -1;\n\n  const getInsetPosition = useStableCallback(() => {\n    const control = controlRef.current;\n    const thumb = thumbRef.current;\n    if (!control || !thumb) {\n      return;\n    }\n    const thumbRect = thumb.getBoundingClientRect();\n    const controlRect = control.getBoundingClientRect();\n\n    const side = vertical ? 'height' : 'width';\n    // the total travel distance adjusted to account for the thumb size\n    const controlSize = controlRect[side] - thumbRect[side];\n    // px distance from the starting edge (inline-start or bottom) to the thumb center\n    const thumbOffsetFromControlEdge =\n      thumbRect[side] / 2 + (controlSize * thumbValuePercent) / 100;\n    const nextPositionPercent = (thumbOffsetFromControlEdge / controlRect[side]) * 100;\n    const nextInsetPosition = Number.isFinite(nextPositionPercent)\n      ? nextPositionPercent\n      : undefined;\n\n    setPositionPercent(nextInsetPosition);\n\n    if (index === 0) {\n      setIndicatorPosition((prevPosition) => [nextInsetPosition, prevPosition[1]]);\n    } else if (last) {\n      setIndicatorPosition((prevPosition) => [prevPosition[0], nextInsetPosition]);\n    }\n  });\n\n  useIsoLayoutEffect(() => {\n    if (inset) {\n      queueMicrotask(getInsetPosition);\n    }\n  }, [getInsetPosition, inset]);\n\n  useIsoLayoutEffect(() => {\n    if (inset) {\n      getInsetPosition();\n    }\n  }, [getInsetPosition, inset, thumbValuePercent]);\n\n  useIsoLayoutEffect(() => {\n    if (!inset || typeof ResizeObserver !== 'function') {\n      return undefined;\n    }\n\n    const control = controlRef.current;\n    const thumb = thumbRef.current;\n\n    if (!control || !thumb) {\n      return undefined;\n    }\n\n    const resizeObserver = new ResizeObserver(getInsetPosition);\n\n    resizeObserver.observe(control);\n    resizeObserver.observe(thumb);\n\n    return () => {\n      resizeObserver.disconnect();\n    };\n  }, [controlRef, getInsetPosition, inset]);\n\n  const getThumbStyle = React.useCallback(() => {\n    const startEdge = vertical ? 'bottom' : 'insetInlineStart';\n    const crossOffsetProperty = vertical ? 'left' : 'top';\n\n    let zIndex: number | undefined;\n    if (range) {\n      if (activeIndex === index) {\n        zIndex = 2;\n      } else if (safeLastUsedThumbIndex === index) {\n        zIndex = 1;\n      }\n    } else if (activeIndex === index) {\n      zIndex = 1;\n    }\n\n    if (!inset) {\n      if (!Number.isFinite(thumbValuePercent)) {\n        return visuallyHidden;\n      }\n\n      return {\n        position: 'absolute',\n        [startEdge]: `${thumbValuePercent}%`,\n        [crossOffsetProperty]: '50%',\n        translate: `${(vertical || !rtl ? -1 : 1) * 50}% ${(vertical ? 1 : -1) * 50}%`,\n        zIndex,\n      } satisfies React.CSSProperties;\n    }\n\n    return {\n      ['--position' as string]: `${positionPercent ?? 0}%`,\n      visibility:\n        (renderBeforeHydration && !isMounted) || positionPercent === undefined\n          ? 'hidden'\n          : undefined,\n      position: 'absolute',\n      [startEdge]: 'var(--position)',\n      [crossOffsetProperty]: '50%',\n      translate: `${(vertical || !rtl ? -1 : 1) * 50}% ${(vertical ? 1 : -1) * 50}%`,\n      zIndex,\n    } satisfies React.CSSProperties;\n  }, [\n    activeIndex,\n    index,\n    inset,\n    isMounted,\n    positionPercent,\n    range,\n    renderBeforeHydration,\n    rtl,\n    safeLastUsedThumbIndex,\n    thumbValuePercent,\n    vertical,\n  ]);\n\n  let cssWritingMode: React.CSSProperties['writingMode'];\n  if (orientation === 'vertical') {\n    cssWritingMode = rtl ? 'vertical-rl' : 'vertical-lr';\n  }\n\n  const ariaLabel =\n    typeof getAriaLabelProp === 'function' ? getAriaLabelProp(index) : ariaLabelProp;\n\n  const inputProps = mergeProps<'input'>(\n    {\n      'aria-label': ariaLabel,\n      'aria-labelledby': ariaLabelledByProp ?? (ariaLabel == null ? labelId : undefined),\n      'aria-describedby': ariaDescribedByProp,\n      'aria-orientation': orientation,\n      'aria-valuenow': thumbValue,\n      'aria-valuetext':\n        typeof getAriaValueTextProp === 'function'\n          ? getAriaValueTextProp(\n              formatNumber(thumbValue, locale, formatOptionsRef.current ?? undefined),\n              thumbValue,\n              index,\n            )\n          : getDefaultAriaValueText(\n              sliderValues,\n              index,\n              formatOptionsRef.current ?? undefined,\n              locale,\n            ),\n      disabled,\n      id: inputId,\n      max,\n      min,\n      name,\n      onChange(event: React.ChangeEvent<HTMLInputElement>) {\n        handleInputChange(event.target.valueAsNumber, index, event);\n      },\n      onFocus() {\n        setActive(index);\n        setFocused(true);\n      },\n      onBlur() {\n        if (!thumbRef.current) {\n          return;\n        }\n\n        setActive(-1);\n        setTouched(true);\n        setFocused(false);\n\n        if (validationMode === 'onBlur') {\n          validation.commit(getSliderValue(thumbValue, index, min, max, range, sliderValues));\n        }\n      },\n      onKeyDown(event: React.KeyboardEvent) {\n        if (!ALL_KEYS.has(event.key)) {\n          return;\n        }\n        if (COMPOSITE_KEYS.has(event.key)) {\n          event.stopPropagation();\n        }\n\n        let newValue = null;\n        const roundedValue = roundValueToStep(thumbValue, step, min);\n        switch (event.key) {\n          case ARROW_UP:\n            newValue = getNewValue(roundedValue, event.shiftKey ? largeStep : step, 1, min, max);\n            break;\n          case ARROW_RIGHT:\n            newValue = getNewValue(\n              roundedValue,\n              event.shiftKey ? largeStep : step,\n              rtl ? -1 : 1,\n              min,\n              max,\n            );\n            break;\n          case ARROW_DOWN:\n            newValue = getNewValue(roundedValue, event.shiftKey ? largeStep : step, -1, min, max);\n            break;\n          case ARROW_LEFT:\n            newValue = getNewValue(\n              roundedValue,\n              event.shiftKey ? largeStep : step,\n              rtl ? 1 : -1,\n              min,\n              max,\n            );\n            break;\n          case PAGE_UP:\n            newValue = getNewValue(roundedValue, largeStep, 1, min, max);\n            break;\n          case PAGE_DOWN:\n            newValue = getNewValue(roundedValue, largeStep, -1, min, max);\n            break;\n          case END:\n            newValue = max;\n\n            if (range) {\n              newValue = Number.isFinite(sliderValues[index + 1])\n                ? sliderValues[index + 1] - step * minStepsBetweenValues\n                : max;\n            }\n            break;\n          case HOME:\n            newValue = min;\n\n            if (range) {\n              newValue = Number.isFinite(sliderValues[index - 1])\n                ? sliderValues[index - 1] + step * minStepsBetweenValues\n                : min;\n            }\n            break;\n          default:\n            break;\n        }\n\n        if (newValue !== null) {\n          handleInputChange(newValue, index, event);\n          event.preventDefault();\n        }\n      },\n      step,\n      style: {\n        ...visuallyHidden,\n        // So that VoiceOver's focus indicator matches the thumb's dimensions\n        width: '100%',\n        height: '100%',\n        writingMode: cssWritingMode,\n      },\n      tabIndex: tabIndexProp ?? undefined,\n      type: 'range',\n      value: thumbValue ?? '',\n    },\n    validation.getInputValidationProps,\n  );\n\n  const mergedInputRef = useMergedRefs(inputRef, validation.inputRef, inputRefProp);\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: [forwardedRef, listItemRef, thumbRef],\n    props: [\n      {\n        [SliderThumbDataAttributes.index as string]: index,\n        children: (\n          <React.Fragment>\n            {childrenProp}\n            <input ref={mergedInputRef} {...inputProps} />\n            {inset &&\n              !isMounted &&\n              renderBeforeHydration &&\n              // this must be rendered with the last thumb to ensure all\n              // preceding thumbs are already rendered in the DOM\n              last && (\n                <script\n                  nonce={nonce}\n                  // eslint-disable-next-line react/no-danger\n                  dangerouslySetInnerHTML={{ __html: prehydrationScript }}\n                  suppressHydrationWarning\n                />\n              )}\n          </React.Fragment>\n        ),\n        id,\n        onBlur: onBlurProp,\n        onFocus: onFocusProp,\n        onPointerDown(event) {\n          pressedThumbIndexRef.current = index;\n\n          if (thumbRef.current != null) {\n            const axis = orientation === 'horizontal' ? 'x' : 'y';\n            const midpoint = getMidpoint(thumbRef.current);\n            const offset =\n              (orientation === 'horizontal' ? event.clientX : event.clientY) - midpoint[axis];\n            pressedThumbCenterOffsetRef.current = offset;\n          }\n\n          if (inputRef.current != null && pressedInputRef.current !== inputRef.current) {\n            pressedInputRef.current = inputRef.current;\n          }\n        },\n        style: getThumbStyle(),\n        suppressHydrationWarning: renderBeforeHydration || undefined,\n        tabIndex: -1,\n      },\n      elementProps,\n    ],\n    stateAttributesMapping: sliderStateAttributesMapping,\n  });\n\n  return element;\n});\n\nexport interface ThumbMetadata {\n  inputId: LabelableContext['controlId'];\n}\n\nexport interface SliderThumbState extends SliderRootState {}\n\nexport interface SliderThumbProps extends Omit<\n  BaseUIComponentProps<'div', SliderThumbState>,\n  'onBlur' | 'onFocus'\n> {\n  /**\n   * Whether the thumb should ignore user interaction.\n   * @default false\n   */\n  disabled?: boolean | undefined;\n  /**\n   * A function which returns a string value for the [`aria-label`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-label) attribute of the `input`.\n   */\n  getAriaLabel?: ((index: number) => string) | null | undefined;\n  /**\n   * A function which returns a string value for the [`aria-valuetext`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-valuetext) attribute of the `input`.\n   * This is important for screen reader users.\n   */\n  getAriaValueText?:\n    | ((formattedValue: string, value: number, index: number) => string)\n    | null\n    | undefined;\n  /**\n   * The index of the thumb which corresponds to the index of its value in the\n   * `value` or `defaultValue` array.\n   * This prop is required to support server-side rendering for range sliders\n   * with multiple thumbs.\n   * @example\n   * ```tsx\n   * <Slider.Root value={[10, 20]}>\n   *   <Slider.Thumb index={0} />\n   *   <Slider.Thumb index={1} />\n   * </Slider.Root>\n   * ```\n   */\n  index?: number | undefined;\n  /**\n   * A ref to access the nested input element.\n   */\n  inputRef?: React.Ref<HTMLInputElement> | undefined;\n  /**\n   * A blur handler forwarded to the `input`.\n   */\n  onBlur?: React.FocusEventHandler<HTMLInputElement> | undefined;\n  /**\n   * A focus handler forwarded to the `input`.\n   */\n  onFocus?: React.FocusEventHandler<HTMLInputElement> | undefined;\n  /**\n   * Optional tab index attribute forwarded to the `input`.\n   */\n  tabIndex?: number | undefined;\n}\n\nexport namespace SliderThumb {\n  export type State = SliderThumbState;\n  export type Props = SliderThumbProps;\n}\n"
  },
  {
    "path": "packages/react/src/slider/thumb/SliderThumbDataAttributes.ts",
    "content": "export enum SliderThumbDataAttributes {\n  /**\n   * Indicates the index of the thumb in range sliders.\n   */\n  index = 'data-index',\n  /**\n   * Present while the user is dragging.\n   */\n  dragging = 'data-dragging',\n  /**\n   * Indicates the orientation of the slider.\n   * @type {'horizontal' | 'vertical'}\n   */\n  orientation = 'data-orientation',\n  /**\n   * Present when the slider is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the slider is in valid state (when wrapped in Field.Root).\n   */\n  valid = 'data-valid',\n  /**\n   * Present when the slider is in invalid state (when wrapped in Field.Root).\n   */\n  invalid = 'data-invalid',\n  /**\n   * Present when the slider has been touched (when wrapped in Field.Root).\n   */\n  touched = 'data-touched',\n  /**\n   * Present when the slider's value has changed (when wrapped in Field.Root).\n   */\n  dirty = 'data-dirty',\n  /**\n   * Present when the slider is focused (when wrapped in Field.Root).\n   */\n  focused = 'data-focused',\n}\n"
  },
  {
    "path": "packages/react/src/slider/thumb/prehydrationScript.min.ts",
    "content": "// This file is autogenerated. Do not edit it directly.\n// To update it, modify the corresponding source file and run `pnpm inline-scripts`.\n\n// prettier-ignore\nexport const script = '!function(){const t=document.currentScript?.parentElement;if(!t)return;const e=t.closest(\"[data-base-ui-slider-control]\");if(!e)return;const r=e.querySelector(\"[data-base-ui-slider-indicator]\"),i=e.getBoundingClientRect(),n=\"vertical\"===e.getAttribute(\"data-orientation\")?\"height\":\"width\",o=e.querySelectorAll(\\'input[type=\"range\"]\\'),l=o.length>1,s=o.length-1;let a=null,u=null;for(let t=0;t<o.length;t+=1){const e=o[t],y=parseFloat(e.getAttribute(\"value\")??\"\");if(Number.isNaN(y))return;const c=e.parentElement;if(!c)return;const p=parseFloat(e.getAttribute(\"max\")??\"100\"),g=parseFloat(e.getAttribute(\"min\")??\"0\"),b=c?.getBoundingClientRect(),d=i[n]-b[n],m=100*(y-g)/(p-g),v=(b[n]/2+d*m/100)/i[n]*100;c.style.setProperty(\"--position\",`${v}%`),Number.isFinite(v)&&(c.style.removeProperty(\"visibility\"),r&&(0===t?(a=v,r.style.setProperty(\"--start-position\",`${v}%`),l||r.style.removeProperty(\"visibility\")):t===s&&(u=v-(a??0),r.style.setProperty(\"--end-position\",`${v}%`),r.style.setProperty(\"--relative-size\",`${u}%`),r.style.removeProperty(\"visibility\"))))}}();';\n"
  },
  {
    "path": "packages/react/src/slider/thumb/prehydrationScript.template.js",
    "content": "(function prehydration() {\n  const firstThumb = document.currentScript?.parentElement;\n  if (!firstThumb) {\n    return;\n  }\n\n  const control = firstThumb.closest('[data-base-ui-slider-control]');\n  if (!control) {\n    return;\n  }\n\n  const indicator = control.querySelector('[data-base-ui-slider-indicator]');\n  const controlRect = control.getBoundingClientRect();\n  const vertical = control.getAttribute('data-orientation') === 'vertical';\n  const side = vertical ? 'height' : 'width';\n  const inputElems = control.querySelectorAll('input[type=\"range\"]');\n  const range = inputElems.length > 1;\n  const lastIndex = inputElems.length - 1;\n\n  let startPosition = null;\n  let relativeSize = null;\n\n  for (let i = 0; i < inputElems.length; i += 1) {\n    const input = inputElems[i];\n\n    const value = parseFloat(input.getAttribute('value') ?? '');\n\n    if (Number.isNaN(value)) {\n      return;\n    }\n\n    const thumb = input.parentElement;\n    if (!thumb) {\n      return;\n    }\n\n    const max = parseFloat(input.getAttribute('max') ?? '100');\n    const min = parseFloat(input.getAttribute('min') ?? '0');\n\n    const thumbRect = thumb?.getBoundingClientRect();\n\n    const controlSize = controlRect[side] - thumbRect[side];\n    const thumbValuePercent = ((value - min) * 100) / (max - min);\n    const thumbOffsetFromControlEdge =\n      thumbRect[side] / 2 + (controlSize * thumbValuePercent) / 100;\n    const percent = (thumbOffsetFromControlEdge / controlRect[side]) * 100;\n\n    thumb.style.setProperty(`--position`, `${percent}%`);\n\n    if (Number.isFinite(percent)) {\n      thumb.style.removeProperty('visibility');\n\n      if (indicator) {\n        if (i === 0) {\n          startPosition = percent;\n          indicator.style.setProperty('--start-position', `${percent}%`);\n          if (!range) {\n            indicator.style.removeProperty('visibility');\n          }\n        } else if (i === lastIndex) {\n          relativeSize = percent - (startPosition ?? 0);\n          indicator.style.setProperty('--end-position', `${percent}%`);\n          indicator.style.setProperty('--relative-size', `${relativeSize}%`);\n          indicator.style.removeProperty('visibility');\n        }\n      }\n    }\n  }\n})();\n"
  },
  {
    "path": "packages/react/src/slider/track/SliderTrack.test.tsx",
    "content": "import { Slider } from '@base-ui/react/slider';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Slider.Track />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Slider.Track />, () => ({\n    render: (node) => {\n      return render(<Slider.Root>{node}</Slider.Root>);\n    },\n    refInstanceof: window.HTMLDivElement,\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/slider/track/SliderTrack.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useSliderRootContext } from '../root/SliderRootContext';\nimport type { SliderRootState } from '../root/SliderRoot';\nimport { sliderStateAttributesMapping } from '../root/stateAttributesMapping';\n\n/**\n * Contains the slider indicator and represents the entire range of the slider.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Slider](https://base-ui.com/react/components/slider)\n */\nexport const SliderTrack = React.forwardRef(function SliderTrack(\n  componentProps: SliderTrack.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, ...elementProps } = componentProps;\n\n  const { state } = useSliderRootContext();\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: forwardedRef,\n    props: [\n      {\n        style: {\n          position: 'relative',\n        },\n      },\n      elementProps,\n    ],\n    stateAttributesMapping: sliderStateAttributesMapping,\n  });\n\n  return element;\n});\n\nexport interface SliderTrackState extends SliderRootState {}\n\nexport interface SliderTrackProps extends BaseUIComponentProps<'div', SliderTrackState> {}\n\nexport namespace SliderTrack {\n  export type State = SliderTrackState;\n  export type Props = SliderTrackProps;\n}\n"
  },
  {
    "path": "packages/react/src/slider/track/SliderTrackDataAttributes.ts",
    "content": "export enum SliderTrackDataAttributes {\n  /**\n   * Present while the user is dragging.\n   */\n  dragging = 'data-dragging',\n  /**\n   * Indicates the orientation of the slider.\n   * @type {'horizontal' | 'vertical'}\n   */\n  orientation = 'data-orientation',\n  /**\n   * Present when the slider is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the slider is in valid state (when wrapped in Field.Root).\n   */\n  valid = 'data-valid',\n  /**\n   * Present when the slider is in invalid state (when wrapped in Field.Root).\n   */\n  invalid = 'data-invalid',\n  /**\n   * Present when the slider has been touched (when wrapped in Field.Root).\n   */\n  touched = 'data-touched',\n  /**\n   * Present when the slider's value has changed (when wrapped in Field.Root).\n   */\n  dirty = 'data-dirty',\n  /**\n   * Present when the slider is focused (when wrapped in Field.Root).\n   */\n  focused = 'data-focused',\n}\n"
  },
  {
    "path": "packages/react/src/slider/utils/asc.ts",
    "content": "export function asc(a: number, b: number) {\n  return a - b;\n}\n"
  },
  {
    "path": "packages/react/src/slider/utils/getMidpoint.ts",
    "content": "import type { Coords } from '../../floating-ui-react/types';\n\nexport function getMidpoint(element: HTMLElement): Coords {\n  const rect = element.getBoundingClientRect();\n  return {\n    x: (rect.left + rect.right) / 2,\n    y: (rect.top + rect.bottom) / 2,\n  };\n}\n"
  },
  {
    "path": "packages/react/src/slider/utils/getPushedThumbValues.test.ts",
    "content": "import { expect } from 'vitest';\nimport { getPushedThumbValues } from './getPushedThumbValues';\n\ndescribe('getPushedThumbValues', () => {\n  it('pushes the next thumb forward when moving past it', () => {\n    const result = getPushedThumbValues({\n      values: [20, 40],\n      index: 0,\n      nextValue: 70,\n      min: 0,\n      max: 100,\n      step: 1,\n      minStepsBetweenValues: 0,\n    });\n\n    expect(result).toEqual([70, 70]);\n  });\n\n  it('ensures minimum distance between thumbs while pushing forward', () => {\n    const result = getPushedThumbValues({\n      values: [20, 40],\n      index: 0,\n      nextValue: 60,\n      min: 0,\n      max: 100,\n      step: 1,\n      minStepsBetweenValues: 5,\n    });\n\n    expect(result).toEqual([60, 65]);\n  });\n\n  it('pushes previous thumbs backward when moving before them', () => {\n    const result = getPushedThumbValues({\n      values: [20, 40],\n      index: 1,\n      nextValue: -10,\n      min: 0,\n      max: 100,\n      step: 1,\n      minStepsBetweenValues: 0,\n    });\n\n    expect(result).toEqual([0, 0]);\n  });\n\n  it('pushes multiple thumbs in sequence', () => {\n    const result = getPushedThumbValues({\n      values: [10, 50, 90],\n      index: 1,\n      nextValue: 95,\n      min: 0,\n      max: 100,\n      step: 1,\n      minStepsBetweenValues: 5,\n    });\n\n    expect(result).toEqual([10, 95, 100]);\n  });\n\n  it('allows fractional minimum distances', () => {\n    const result = getPushedThumbValues({\n      values: [0, 1],\n      index: 0,\n      nextValue: 1.4,\n      min: 0,\n      max: 10,\n      step: 1,\n      minStepsBetweenValues: 0.4,\n    });\n\n    expect(result[0]).toBe(1.4);\n    expect(result[1]).toBe(1.8);\n  });\n\n  it('restores pushed thumbs towards their initial value when space allows', () => {\n    const initialValues = [30, 50];\n\n    const pushed = getPushedThumbValues({\n      values: initialValues,\n      initialValues,\n      index: 1,\n      nextValue: 20,\n      min: 0,\n      max: 100,\n      step: 1,\n      minStepsBetweenValues: 0,\n    });\n\n    expect(pushed).toEqual([20, 20]);\n\n    const restored = getPushedThumbValues({\n      values: pushed,\n      initialValues,\n      index: 1,\n      nextValue: 35,\n      min: 0,\n      max: 100,\n      step: 1,\n      minStepsBetweenValues: 0,\n    });\n\n    expect(restored).toEqual([30, 35]);\n  });\n});\n"
  },
  {
    "path": "packages/react/src/slider/utils/getPushedThumbValues.ts",
    "content": "import { clamp } from '../../utils/clamp';\n\ninterface GetPushedThumbValuesParams {\n  values: readonly number[];\n  index: number;\n  nextValue: number;\n  min: number;\n  max: number;\n  step: number;\n  minStepsBetweenValues: number;\n  initialValues?: readonly number[] | undefined;\n}\n\n/**\n * Returns a new array of slider values where attempting to move the thumb at `index`\n * beyond its neighbours \"pushes\" them while respecting `minStepsBetweenValues`.\n */\nexport function getPushedThumbValues({\n  values,\n  index,\n  nextValue,\n  min,\n  max,\n  step,\n  minStepsBetweenValues,\n  initialValues,\n}: GetPushedThumbValuesParams): number[] {\n  if (values.length === 0) {\n    return [];\n  }\n\n  const nextValues = values.slice();\n  const minValueDifference = step * minStepsBetweenValues;\n  const lastIndex = nextValues.length - 1;\n  const baseInitialValues = initialValues ?? values;\n\n  const indexMin = min + index * minValueDifference;\n  const indexMax = max - (lastIndex - index) * minValueDifference;\n  nextValues[index] = clamp(nextValue, indexMin, indexMax);\n\n  for (let i = index + 1; i <= lastIndex; i += 1) {\n    const minAllowed = nextValues[i - 1] + minValueDifference;\n    const maxAllowed = max - (lastIndex - i) * minValueDifference;\n    const initialValue = baseInitialValues[i] ?? nextValues[i];\n    let candidate = Math.max(nextValues[i], minAllowed);\n\n    if (initialValue < candidate) {\n      candidate = Math.max(initialValue, minAllowed);\n    }\n\n    nextValues[i] = clamp(candidate, minAllowed, maxAllowed);\n  }\n\n  for (let i = index - 1; i >= 0; i -= 1) {\n    const maxAllowed = nextValues[i + 1] - minValueDifference;\n    const minAllowed = min + i * minValueDifference;\n    const initialValue = baseInitialValues[i] ?? nextValues[i];\n    let candidate = Math.min(nextValues[i], maxAllowed);\n\n    if (initialValue > candidate) {\n      candidate = Math.min(initialValue, maxAllowed);\n    }\n\n    nextValues[i] = clamp(candidate, minAllowed, maxAllowed);\n  }\n\n  for (let i = 0; i <= lastIndex; i += 1) {\n    nextValues[i] = Number(nextValues[i].toFixed(12));\n  }\n\n  return nextValues;\n}\n"
  },
  {
    "path": "packages/react/src/slider/utils/getSliderValue.ts",
    "content": "import { clamp } from '../../utils/clamp';\nimport { replaceArrayItemAtIndex } from './replaceArrayItemAtIndex';\n\nexport function getSliderValue(\n  valueInput: number,\n  index: number,\n  min: number,\n  max: number,\n  range: boolean,\n  values: readonly number[],\n) {\n  let newValue: number | number[] = valueInput;\n\n  newValue = clamp(newValue, min, max);\n\n  if (range) {\n    newValue = replaceArrayItemAtIndex(\n      values,\n      index,\n      // Bound the new value to the thumb's neighbours.\n      clamp(newValue, values[index - 1] || -Infinity, values[index + 1] || Infinity),\n    );\n  }\n\n  return newValue;\n}\n"
  },
  {
    "path": "packages/react/src/slider/utils/replaceArrayItemAtIndex.ts",
    "content": "import { asc } from './asc';\n\nexport function replaceArrayItemAtIndex(array: readonly number[], index: number, newValue: number) {\n  const output = array.slice();\n  output[index] = newValue;\n  return output.sort(asc);\n}\n"
  },
  {
    "path": "packages/react/src/slider/utils/resolveThumbCollision.test.ts",
    "content": "import { expect } from 'vitest';\nimport { resolveThumbCollision } from './resolveThumbCollision';\n\ndescribe('resolveThumbCollision', () => {\n  it('prevents thumbs from passing each other when behavior is \"none\"', () => {\n    const result = resolveThumbCollision({\n      behavior: 'none',\n      values: [20, 40],\n      pressedIndex: 0,\n      nextValue: 70,\n      min: 0,\n      max: 100,\n      step: 1,\n      minStepsBetweenValues: 0,\n    });\n\n    expect(result.value).toEqual([40, 40]);\n    expect(result.thumbIndex).toBe(0);\n    expect(result.didSwap).toBe(false);\n  });\n\n  it('pushes thumbs forward without cling when behavior is \"push\"', () => {\n    const result = resolveThumbCollision({\n      behavior: 'push',\n      values: [20, 40],\n      pressedIndex: 0,\n      nextValue: 70,\n      min: 0,\n      max: 100,\n      step: 1,\n      minStepsBetweenValues: 0,\n    });\n\n    expect(result.value).toEqual([70, 70]);\n    expect(result.thumbIndex).toBe(0);\n    expect(result.didSwap).toBe(false);\n  });\n\n  it('keeps pushed thumbs in place when moving backward in push mode', () => {\n    const startValues = [20, 40];\n\n    const pushed = resolveThumbCollision({\n      behavior: 'push',\n      values: startValues,\n      currentValues: startValues,\n      initialValues: startValues,\n      pressedIndex: 0,\n      nextValue: 70,\n      min: 0,\n      max: 100,\n      step: 1,\n      minStepsBetweenValues: 0,\n    });\n\n    const nextValues = pushed.value as number[];\n    expect(nextValues).toEqual([70, 70]);\n\n    const movedBack = resolveThumbCollision({\n      behavior: 'push',\n      values: nextValues,\n      currentValues: nextValues,\n      initialValues: startValues,\n      pressedIndex: 0,\n      nextValue: 30,\n      min: 0,\n      max: 100,\n      step: 1,\n      minStepsBetweenValues: 0,\n    });\n\n    expect(movedBack.value).toEqual([30, 70]);\n    expect(movedBack.thumbIndex).toBe(0);\n    expect(movedBack.didSwap).toBe(false);\n  });\n\n  it('swaps thumbs when behavior is \"swap\"', () => {\n    const result = resolveThumbCollision({\n      behavior: 'swap',\n      values: [20, 40],\n      pressedIndex: 0,\n      nextValue: 65,\n      min: 0,\n      max: 100,\n      step: 1,\n      minStepsBetweenValues: 0,\n    });\n\n    expect(result.value).toEqual([40, 65]);\n    expect(result.thumbIndex).toBe(1);\n    expect(result.didSwap).toBe(true);\n  });\n\n  it('maintains swap continuity with minimum steps when provided current and initial values', () => {\n    const startValues = [20, 80];\n\n    const first = resolveThumbCollision({\n      behavior: 'swap',\n      values: startValues,\n      currentValues: startValues,\n      initialValues: startValues,\n      pressedIndex: 0,\n      nextValue: 85,\n      min: 0,\n      max: 100,\n      step: 1,\n      minStepsBetweenValues: 10,\n    });\n\n    const firstValues = first.value as number[];\n    expect(firstValues).toEqual([70, 85]);\n    expect(first.thumbIndex).toBe(1);\n    expect(first.didSwap).toBe(true);\n\n    const continued = resolveThumbCollision({\n      behavior: 'swap',\n      values: startValues,\n      currentValues: firstValues,\n      initialValues: startValues,\n      pressedIndex: first.thumbIndex,\n      nextValue: 95,\n      min: 0,\n      max: 100,\n      step: 1,\n      minStepsBetweenValues: 10,\n    });\n\n    const continuedValues = continued.value as number[];\n    expect(continuedValues).toEqual([70, 95]);\n    expect(continued.thumbIndex).toBe(1);\n    expect(continued.didSwap).toBe(false);\n  });\n\n  it('does not swap before reaching neighbour value with minimum steps', () => {\n    const result = resolveThumbCollision({\n      behavior: 'swap',\n      values: [25, 45],\n      currentValues: [40, 45],\n      initialValues: [25, 45],\n      pressedIndex: 0,\n      nextValue: 44,\n      min: 0,\n      max: 100,\n      step: 1,\n      minStepsBetweenValues: 5,\n    });\n\n    const resultValues = result.value as number[];\n    expect(resultValues).toEqual([40, 45]);\n    expect(result.thumbIndex).toBe(0);\n    expect(result.didSwap).toBe(false);\n  });\n\n  it('swaps once reaching the neighbour value with minimum steps', () => {\n    const result = resolveThumbCollision({\n      behavior: 'swap',\n      values: [25, 45],\n      currentValues: [40, 45],\n      initialValues: [25, 45],\n      pressedIndex: 0,\n      nextValue: 45,\n      min: 0,\n      max: 100,\n      step: 1,\n      minStepsBetweenValues: 5,\n    });\n\n    const resultValues = result.value as number[];\n    expect(resultValues).toEqual([40, 45]);\n    expect(result.thumbIndex).toBe(1);\n    expect(result.didSwap).toBe(true);\n  });\n\n  it('does not swap backward before reaching neighbour value with minimum steps', () => {\n    const result = resolveThumbCollision({\n      behavior: 'swap',\n      values: [25, 45],\n      currentValues: [25, 40],\n      initialValues: [25, 45],\n      pressedIndex: 1,\n      nextValue: 29,\n      min: 0,\n      max: 100,\n      step: 1,\n      minStepsBetweenValues: 5,\n    });\n\n    const resultValues = result.value as number[];\n    expect(resultValues).toEqual([25, 30]);\n    expect(result.thumbIndex).toBe(1);\n    expect(result.didSwap).toBe(false);\n  });\n\n  it('swaps backward once reaching the neighbour value with minimum steps', () => {\n    const result = resolveThumbCollision({\n      behavior: 'swap',\n      values: [25, 45],\n      currentValues: [25, 40],\n      initialValues: [25, 45],\n      pressedIndex: 1,\n      nextValue: 25,\n      min: 0,\n      max: 100,\n      step: 1,\n      minStepsBetweenValues: 5,\n    });\n\n    const resultValues = result.value as number[];\n    expect(resultValues).toEqual([25, 30]);\n    expect(result.thumbIndex).toBe(0);\n    expect(result.didSwap).toBe(true);\n  });\n\n  it('does not move the clamped neighbour when swapping across with minimum steps', () => {\n    const startValues = [25, 45];\n    const currentValues = [40, 45];\n\n    const result = resolveThumbCollision({\n      behavior: 'swap',\n      values: currentValues,\n      currentValues,\n      initialValues: startValues,\n      pressedIndex: 0,\n      nextValue: 46,\n      min: 0,\n      max: 100,\n      step: 1,\n      minStepsBetweenValues: 5,\n    });\n\n    const resultValues = result.value as number[];\n    expect(resultValues).toEqual([40, 46]);\n    expect(result.thumbIndex).toBe(1);\n    expect(result.didSwap).toBe(true);\n  });\n});\n"
  },
  {
    "path": "packages/react/src/slider/utils/resolveThumbCollision.ts",
    "content": "import { clamp } from '../../utils/clamp';\nimport { getPushedThumbValues } from './getPushedThumbValues';\nimport { SliderRootContext } from '../root/SliderRootContext';\n\nexport interface ResolveThumbCollisionParams {\n  behavior: SliderRootContext['thumbCollisionBehavior'];\n  values: readonly number[];\n  currentValues?: readonly number[] | null | undefined;\n  initialValues?: readonly number[] | null | undefined;\n  pressedIndex: number;\n  nextValue: number;\n  min: number;\n  max: number;\n  step: number;\n  minStepsBetweenValues: number;\n}\n\nexport interface ResolveThumbCollisionResult {\n  value: number | number[];\n  thumbIndex: number;\n  didSwap: boolean;\n}\n\nexport function resolveThumbCollision({\n  behavior,\n  values,\n  currentValues,\n  initialValues,\n  pressedIndex,\n  nextValue,\n  min,\n  max,\n  step,\n  minStepsBetweenValues,\n}: ResolveThumbCollisionParams): ResolveThumbCollisionResult {\n  const activeValues = currentValues ?? values;\n  const baselineValues = initialValues ?? values;\n  const range = activeValues.length > 1;\n\n  if (!range) {\n    return {\n      value: nextValue,\n      thumbIndex: 0,\n      didSwap: false,\n    };\n  }\n\n  const minValueDifference = step * minStepsBetweenValues;\n\n  switch (behavior) {\n    case 'swap': {\n      const pressedInitialValue = activeValues[pressedIndex];\n      const epsilon = 1e-7;\n      const candidateValues = activeValues.slice();\n      const previousNeighbor = candidateValues[pressedIndex - 1];\n      const nextNeighbor = candidateValues[pressedIndex + 1];\n\n      const lowerBound = previousNeighbor != null ? previousNeighbor + minValueDifference : min;\n      const upperBound = nextNeighbor != null ? nextNeighbor - minValueDifference : max;\n\n      const constrainedValue = clamp(nextValue, lowerBound, upperBound);\n      const pressedValueAfterClamp = Number(constrainedValue.toFixed(12));\n      candidateValues[pressedIndex] = pressedValueAfterClamp;\n\n      const movingForward = nextValue > pressedInitialValue;\n      const movingBackward = nextValue < pressedInitialValue;\n\n      const shouldSwapForward =\n        movingForward && nextNeighbor != null && nextValue >= nextNeighbor - epsilon;\n      const shouldSwapBackward =\n        movingBackward && previousNeighbor != null && nextValue <= previousNeighbor + epsilon;\n\n      if (!shouldSwapForward && !shouldSwapBackward) {\n        return {\n          value: candidateValues,\n          thumbIndex: pressedIndex,\n          didSwap: false,\n        };\n      }\n\n      const targetIndex = shouldSwapForward ? pressedIndex + 1 : pressedIndex - 1;\n\n      const initialValuesForPush = candidateValues.map((_, index) => {\n        if (index === pressedIndex) {\n          return pressedValueAfterClamp;\n        }\n\n        const baseline = baselineValues[index];\n        if (baseline != null) {\n          return baseline;\n        }\n\n        return activeValues[index];\n      });\n\n      let nextValueForTarget = nextValue;\n      if (shouldSwapForward) {\n        nextValueForTarget = Math.max(nextValue, candidateValues[targetIndex]);\n      } else {\n        nextValueForTarget = Math.min(nextValue, candidateValues[targetIndex]);\n      }\n\n      const adjustedValues = getPushedThumbValues({\n        values: candidateValues,\n        index: targetIndex,\n        nextValue: nextValueForTarget,\n        min,\n        max,\n        step,\n        minStepsBetweenValues,\n        initialValues: initialValuesForPush,\n      });\n\n      const neighborIndex = shouldSwapForward ? targetIndex - 1 : targetIndex + 1;\n\n      if (neighborIndex >= 0 && neighborIndex < adjustedValues.length) {\n        const previousValue = adjustedValues[neighborIndex - 1];\n        const nextValueAfter = adjustedValues[neighborIndex + 1];\n\n        let neighborLowerBound = previousValue != null ? previousValue + minValueDifference : min;\n        neighborLowerBound = Math.max(neighborLowerBound, min + neighborIndex * minValueDifference);\n\n        let neighborUpperBound = nextValueAfter != null ? nextValueAfter - minValueDifference : max;\n        neighborUpperBound = Math.min(\n          neighborUpperBound,\n          max - (adjustedValues.length - 1 - neighborIndex) * minValueDifference,\n        );\n\n        const restoredValue = clamp(pressedValueAfterClamp, neighborLowerBound, neighborUpperBound);\n        adjustedValues[neighborIndex] = Number(restoredValue.toFixed(12));\n      }\n\n      return {\n        value: adjustedValues,\n        thumbIndex: targetIndex,\n        didSwap: true,\n      };\n    }\n    case 'push': {\n      const nextValues = getPushedThumbValues({\n        values: activeValues,\n        index: pressedIndex,\n        nextValue,\n        min,\n        max,\n        step,\n        minStepsBetweenValues,\n      });\n\n      return {\n        value: nextValues,\n        thumbIndex: pressedIndex,\n        didSwap: false,\n      };\n    }\n    case 'none':\n    default: {\n      const candidateValues = activeValues.slice();\n      const previousNeighbor = candidateValues[pressedIndex - 1];\n      const nextNeighbor = candidateValues[pressedIndex + 1];\n\n      const lowerBound = previousNeighbor != null ? previousNeighbor + minValueDifference : min;\n      const upperBound = nextNeighbor != null ? nextNeighbor - minValueDifference : max;\n\n      const constrainedValue = clamp(nextValue, lowerBound, upperBound);\n      candidateValues[pressedIndex] = Number(constrainedValue.toFixed(12));\n\n      return {\n        value: candidateValues,\n        thumbIndex: pressedIndex,\n        didSwap: false,\n      };\n    }\n  }\n}\n"
  },
  {
    "path": "packages/react/src/slider/utils/roundValueToStep.ts",
    "content": "function getDecimalPrecision(num: number) {\n  // This handles the case when num is very small (0.00000001), js will turn this into 1e-8.\n  // When num is bigger than 1 or less than -1 it won't get converted to this notation so it's fine.\n  if (Math.abs(num) < 1) {\n    const parts = num.toExponential().split('e-');\n    const matissaDecimalPart = parts[0].split('.')[1];\n    return (matissaDecimalPart ? matissaDecimalPart.length : 0) + parseInt(parts[1], 10);\n  }\n\n  const decimalPart = num.toString().split('.')[1];\n  return decimalPart ? decimalPart.length : 0;\n}\n\nexport function roundValueToStep(value: number, step: number, min: number) {\n  const nearest = Math.round((value - min) / step) * step + min;\n  return Number(nearest.toFixed(getDecimalPrecision(step)));\n}\n"
  },
  {
    "path": "packages/react/src/slider/utils/test-utils.ts",
    "content": "type Touches = Array<Pick<Touch, 'identifier' | 'clientX' | 'clientY'>>;\n\nexport function createTouches(touches: Touches) {\n  return {\n    changedTouches: touches.map(\n      (touch) =>\n        // eslint-disable-next-line compat/compat -- used in test environment only\n        new Touch({\n          target: document.body,\n          ...touch,\n        }),\n    ),\n  };\n}\n\nexport function getHorizontalSliderRect(width = 100) {\n  return {\n    width,\n    height: 10,\n    bottom: 10,\n    left: 0,\n    x: 0,\n    y: 0,\n    top: 0,\n    right: width,\n    toJSON() {},\n  };\n}\n"
  },
  {
    "path": "packages/react/src/slider/utils/validateMinimumDistance.ts",
    "content": "export function validateMinimumDistance(\n  values: number | readonly number[],\n  step: number,\n  minStepsBetweenValues: number,\n) {\n  if (!Array.isArray(values)) {\n    return true;\n  }\n\n  const distances = values.reduce((acc: number[], val, index, vals) => {\n    if (index === vals.length - 1) {\n      return acc;\n    }\n\n    acc.push(Math.abs(val - vals[index + 1]));\n\n    return acc;\n  }, []);\n\n  return Math.min(...distances) >= step * minStepsBetweenValues;\n}\n"
  },
  {
    "path": "packages/react/src/slider/utils/valueArrayToPercentages.ts",
    "content": "import { clamp } from '../../utils/clamp';\nimport { valueToPercent } from '../../utils/valueToPercent';\n\nexport function valueArrayToPercentages(values: number[], min: number, max: number) {\n  const output = [];\n  for (let i = 0; i < values.length; i += 1) {\n    output.push(clamp(valueToPercent(values[i], min, max), 0, 100));\n  }\n  return output;\n}\n"
  },
  {
    "path": "packages/react/src/slider/value/SliderValue.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport { screen } from '@mui/internal-test-utils';\nimport { Slider } from '@base-ui/react/slider';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Slider.Value />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Slider.Value />, () => ({\n    render: (node) => {\n      return render(<Slider.Root>{node}</Slider.Root>);\n    },\n    refInstanceof: window.HTMLOutputElement,\n  }));\n\n  it('renders a single value', async () => {\n    await render(\n      <Slider.Root defaultValue={40}>\n        <Slider.Value data-testid=\"output\" />\n      </Slider.Root>,\n    );\n\n    const sliderValue = screen.getByTestId('output');\n\n    expect(sliderValue).toHaveTextContent('40');\n  });\n\n  it('renders a range', async () => {\n    await render(\n      <Slider.Root defaultValue={[40, 65]}>\n        <Slider.Value data-testid=\"output\" />\n      </Slider.Root>,\n    );\n\n    const sliderValue = screen.getByTestId('output');\n\n    expect(sliderValue).toHaveTextContent('40 – 65');\n  });\n\n  it('renders all thumb values', async () => {\n    await render(\n      <Slider.Root defaultValue={[40, 60, 80, 95]}>\n        <Slider.Value data-testid=\"output\" />\n      </Slider.Root>,\n    );\n\n    const sliderValue = screen.getByTestId('output');\n\n    expect(sliderValue).toHaveTextContent('40 – 60 – 80 – 95');\n  });\n\n  describe('prop: children', () => {\n    it('accepts a render function', async () => {\n      const format: Intl.NumberFormatOptions = {\n        style: 'currency',\n        currency: 'USD',\n      };\n      function formatValue(v: number) {\n        return new Intl.NumberFormat(undefined, format).format(v);\n      }\n      const renderSpy = vi.fn();\n      await render(\n        <Slider.Root defaultValue={[40, 60]} format={format}>\n          <Slider.Value data-testid=\"output\">{renderSpy}</Slider.Value>\n        </Slider.Root>,\n      );\n\n      expect(renderSpy.mock.lastCall?.[0]).toEqual([formatValue(40), formatValue(60)]);\n      expect(renderSpy.mock.lastCall?.[1]).toEqual([40, 60]);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/slider/value/SliderValue.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { formatNumber } from '../../utils/formatNumber';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useSliderRootContext } from '../root/SliderRootContext';\nimport { sliderStateAttributesMapping } from '../root/stateAttributesMapping';\nimport type { SliderRootState } from '../root/SliderRoot';\n\n/**\n * Displays the current value of the slider as text.\n * Renders an `<output>` element.\n *\n * Documentation: [Base UI Slider](https://base-ui.com/react/components/slider)\n */\nexport const SliderValue = React.forwardRef(function SliderValue(\n  componentProps: SliderValue.Props,\n  forwardedRef: React.ForwardedRef<HTMLOutputElement>,\n) {\n  const {\n    'aria-live': ariaLive = 'off',\n    render,\n    className,\n    children,\n    ...elementProps\n  } = componentProps;\n\n  const { thumbMap, state, values, formatOptionsRef, locale } = useSliderRootContext();\n\n  const outputFor = React.useMemo(() => {\n    let htmlFor = '';\n    for (const thumbMetadata of thumbMap.values()) {\n      if (thumbMetadata?.inputId) {\n        htmlFor += `${thumbMetadata.inputId} `;\n      }\n    }\n    return htmlFor.trim() === '' ? undefined : htmlFor.trim();\n  }, [thumbMap]);\n\n  const formattedValues = React.useMemo(() => {\n    const arr = [];\n    for (let i = 0; i < values.length; i += 1) {\n      arr.push(formatNumber(values[i], locale, formatOptionsRef.current ?? undefined));\n    }\n    return arr;\n  }, [formatOptionsRef, locale, values]);\n\n  const defaultDisplayValue = React.useMemo(() => {\n    const arr = [];\n    for (let i = 0; i < values.length; i += 1) {\n      arr.push(formattedValues[i] || values[i]);\n    }\n    return arr.join(' – ');\n  }, [values, formattedValues]);\n\n  const element = useRenderElement('output', componentProps, {\n    state,\n    ref: forwardedRef,\n    props: [\n      {\n        // off by default because it will keep announcing when the slider is being dragged\n        // and also when the value is changing (but not yet committed)\n        'aria-live': ariaLive,\n        children:\n          typeof children === 'function' ? children(formattedValues, values) : defaultDisplayValue,\n        htmlFor: outputFor,\n      },\n      elementProps,\n    ],\n    stateAttributesMapping: sliderStateAttributesMapping,\n  });\n\n  return element;\n});\n\nexport interface SliderValueState extends SliderRootState {}\n\nexport interface SliderValueProps extends Omit<\n  BaseUIComponentProps<'output', SliderValueState>,\n  'children'\n> {\n  children?:\n    | null\n    | ((formattedValues: readonly string[], values: readonly number[]) => React.ReactNode)\n    | undefined;\n}\n\nexport namespace SliderValue {\n  export type State = SliderValueState;\n  export type Props = SliderValueProps;\n}\n"
  },
  {
    "path": "packages/react/src/slider/value/SliderValueDataAttributes.ts",
    "content": "export enum SliderValueDataAttributes {\n  /**\n   * Present while the user is dragging.\n   */\n  dragging = 'data-dragging',\n  /**\n   * Indicates the orientation of the slider.\n   * @type {'horizontal' | 'vertical'}\n   */\n  orientation = 'data-orientation',\n  /**\n   * Present when the slider is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the slider is in valid state (when wrapped in Field.Root).\n   */\n  valid = 'data-valid',\n  /**\n   * Present when the slider is in invalid state (when wrapped in Field.Root).\n   */\n  invalid = 'data-invalid',\n  /**\n   * Present when the slider has been touched (when wrapped in Field.Root).\n   */\n  touched = 'data-touched',\n  /**\n   * Present when the slider's value has changed (when wrapped in Field.Root).\n   */\n  dirty = 'data-dirty',\n  /**\n   * Present when the slider is focused (when wrapped in Field.Root).\n   */\n  focused = 'data-focused',\n}\n"
  },
  {
    "path": "packages/react/src/switch/index.parts.ts",
    "content": "export { SwitchRoot as Root } from './root/SwitchRoot';\nexport { SwitchThumb as Thumb } from './thumb/SwitchThumb';\n"
  },
  {
    "path": "packages/react/src/switch/index.ts",
    "content": "export * as Switch from './index.parts';\n\nexport type * from './root/SwitchRoot';\nexport type * from './thumb/SwitchThumb';\n"
  },
  {
    "path": "packages/react/src/switch/root/SwitchRoot.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { act, fireEvent, screen, waitFor } from '@mui/internal-test-utils';\nimport { Switch } from '@base-ui/react/switch';\nimport { describeConformance, createRenderer, isJSDOM } from '#test-utils';\nimport { Field } from '@base-ui/react/field';\nimport { Form } from '@base-ui/react/form';\n\ndescribe('<Switch.Root />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Switch.Root />, () => ({\n    refInstanceof: window.HTMLSpanElement,\n    testComponentPropWith: 'span',\n    button: true,\n    render,\n  }));\n\n  describe('interactions', () => {\n    it('should change its state when clicked', async () => {\n      await render(<Switch.Root />);\n      const switchElement = screen.getByRole('switch');\n\n      expect(switchElement).toHaveAttribute('aria-checked', 'false');\n\n      await act(async () => {\n        switchElement.click();\n      });\n\n      expect(switchElement).toHaveAttribute('aria-checked', 'true');\n    });\n\n    it('should update its state when changed from outside', async () => {\n      function Test() {\n        const [checked, setChecked] = React.useState(false);\n        return (\n          <div>\n            <button onClick={() => setChecked((c) => !c)}>Toggle</button>\n            <Switch.Root checked={checked} />;\n          </div>\n        );\n      }\n\n      await render(<Test />);\n      const switchElement = screen.getByRole('switch');\n      const button = screen.getByText('Toggle');\n\n      expect(switchElement).toHaveAttribute('aria-checked', 'false');\n      await act(async () => {\n        button.click();\n      });\n\n      expect(switchElement).toHaveAttribute('aria-checked', 'true');\n\n      await act(async () => {\n        button.click();\n      });\n\n      expect(switchElement).toHaveAttribute('aria-checked', 'false');\n    });\n\n    it('should update its state if the underlying input is toggled', async () => {\n      await render(<Switch.Root />);\n      const switchElement = screen.getByRole('switch');\n      const internalInput = screen.getByRole('checkbox', { hidden: true });\n\n      await act(async () => {\n        internalInput.click();\n      });\n\n      expect(switchElement).toHaveAttribute('aria-checked', 'true');\n    });\n\n    ['Enter', 'Space'].forEach((key) => {\n      it(`can be activated with ${key} key`, async () => {\n        const { user } = await render(<Switch.Root />);\n\n        const switchEl = screen.getByRole('switch');\n        expect(switchEl).toHaveAttribute('aria-checked', 'false');\n\n        await user.keyboard('[Tab]');\n        expect(switchEl).toHaveFocus();\n\n        await user.keyboard(`[${key}]`);\n        expect(switchEl).toHaveAttribute('aria-checked', 'true');\n      });\n    });\n  });\n\n  describe('extra props', () => {\n    it('should override the built-in attributes', async () => {\n      await render(<Switch.Root role=\"checkbox\" data-testid=\"switch\" />);\n      expect(screen.getByTestId('switch')).toHaveAttribute('role', 'checkbox');\n    });\n\n    it('sets `aria-labelledby` from a sibling label associated with the hidden input', async () => {\n      await render(\n        <div>\n          <label htmlFor=\"switch-input\">Label</label>\n          <Switch.Root id=\"switch-input\" />\n        </div>,\n      );\n\n      const label = screen.getByText('Label');\n      expect(label.id).not.toBe('');\n      expect(screen.getByRole('switch')).toHaveAttribute('aria-labelledby', label.id);\n    });\n\n    it('updates fallback `aria-labelledby` when the hidden input id changes', async () => {\n      function TestCase() {\n        const [id, setId] = React.useState('switch-input-a');\n\n        return (\n          <React.Fragment>\n            <label htmlFor=\"switch-input-a\">Label A</label>\n            <label htmlFor=\"switch-input-b\">Label B</label>\n            <Switch.Root id={id} />\n            <button type=\"button\" onClick={() => setId('switch-input-b')}>\n              Toggle\n            </button>\n          </React.Fragment>\n        );\n      }\n\n      await render(<TestCase />);\n\n      const switchEl = screen.getByRole('switch');\n      const labelA = screen.getByText('Label A');\n\n      expect(labelA.id).not.toBe('');\n      expect(switchEl).toHaveAttribute('aria-labelledby', labelA.id);\n\n      fireEvent.click(screen.getByRole('button', { name: 'Toggle' }));\n\n      await waitFor(() => {\n        const labelB = screen.getByText('Label B');\n\n        expect(labelB.id).not.toBe('');\n        expect(labelA.id).not.toBe(labelB.id);\n        expect(switchEl).toHaveAttribute('aria-labelledby', labelB.id);\n      });\n    });\n  });\n\n  describe('prop: onCheckedChange', () => {\n    it('should call onCheckedChange when clicked', async () => {\n      const handleChange = vi.fn();\n      await render(<Switch.Root onCheckedChange={handleChange} />);\n      const switchElement = screen.getByRole('switch');\n\n      await act(async () => {\n        switchElement.click();\n      });\n\n      expect(handleChange.mock.calls.length).toBe(1);\n      expect(handleChange.mock.calls[0][0]).toBe(true);\n    });\n\n    it('should report keyboard modifier event properties when calling onCheckedChange', async () => {\n      const handleChange = vi.fn((checked, eventDetails) => eventDetails);\n      const { user } = await render(<Switch.Root onCheckedChange={handleChange} />);\n      const switchElement = screen.getByRole('switch');\n\n      await user.keyboard('{Shift>}');\n      await user.click(switchElement);\n      await user.keyboard('{/Shift}');\n\n      expect(handleChange.mock.calls.length).toBe(1);\n      expect(handleChange.mock.results[0]?.value.event.shiftKey).toBe(true);\n    });\n  });\n\n  describe('prop: onClick', () => {\n    it('should call onClick when clicked', async () => {\n      const handleClick = vi.fn();\n      await render(<Switch.Root onClick={handleClick} />);\n      const switchElement = screen.getByRole('switch');\n\n      await act(async () => {\n        switchElement.click();\n      });\n\n      expect(handleClick.mock.calls.length).toBe(1);\n    });\n  });\n\n  describe('prop: disabled', () => {\n    it('uses aria-disabled instead of HTML disabled', async () => {\n      await render(<Switch.Root disabled />);\n      expect(screen.getByRole('switch')).not.toHaveAttribute('disabled');\n      expect(screen.getByRole('switch')).toHaveAttribute('aria-disabled', 'true');\n    });\n\n    it('should not have the `disabled` attribute when `disabled` is not set', async () => {\n      await render(<Switch.Root />);\n      expect(screen.getByRole('switch')).not.toHaveAttribute('disabled');\n    });\n\n    it('should not change its state when clicked', async () => {\n      await render(<Switch.Root disabled />);\n      const switchElement = screen.getByRole('switch');\n\n      expect(switchElement).toHaveAttribute('aria-checked', 'false');\n\n      await act(async () => {\n        switchElement.click();\n      });\n\n      expect(switchElement).toHaveAttribute('aria-checked', 'false');\n    });\n  });\n\n  describe('prop: readOnly', () => {\n    it('should have the `aria-readonly` attribute', async () => {\n      await render(<Switch.Root readOnly />);\n      expect(screen.getByRole('switch')).toHaveAttribute('aria-readonly', 'true');\n    });\n\n    it('should not have the aria attribute when `readOnly` is not set', async () => {\n      await render(<Switch.Root />);\n      expect(screen.getByRole('switch')).not.toHaveAttribute('aria-readonly');\n    });\n\n    it('should not change its state when clicked', async () => {\n      await render(<Switch.Root readOnly />);\n      const switchElement = screen.getByRole('switch');\n\n      expect(switchElement).toHaveAttribute('aria-checked', 'false');\n\n      await act(async () => {\n        switchElement.click();\n      });\n\n      expect(switchElement).toHaveAttribute('aria-checked', 'false');\n    });\n  });\n\n  describe('prop: required', () => {\n    it('should have the `aria-required` attribute', async () => {\n      await render(<Switch.Root required />);\n      expect(screen.getByRole('switch')).toHaveAttribute('aria-required', 'true');\n    });\n\n    it('should not have the aria attribute when `required` is not set', async () => {\n      await render(<Switch.Root />);\n      expect(screen.getByRole('switch')).not.toHaveAttribute('aria-required');\n    });\n  });\n\n  describe('prop: inputRef', () => {\n    it('should be able to access the native input', async () => {\n      const inputRef = React.createRef<HTMLInputElement>();\n      await render(<Switch.Root inputRef={inputRef} />);\n      const internalInput = screen.getByRole('checkbox', { hidden: true });\n\n      expect(inputRef.current).toBe(internalInput);\n    });\n  });\n\n  it('should place the style hooks on the root and the thumb', async () => {\n    const { setProps } = await render(\n      <Switch.Root defaultChecked disabled readOnly required>\n        <Switch.Thumb data-testid=\"thumb\" />\n      </Switch.Root>,\n    );\n\n    const switchElement = screen.getByRole('switch');\n    const thumb = screen.getByTestId('thumb');\n\n    expect(switchElement).toHaveAttribute('data-checked', '');\n    expect(switchElement).toHaveAttribute('data-disabled', '');\n    expect(switchElement).toHaveAttribute('data-readonly', '');\n    expect(switchElement).toHaveAttribute('data-required', '');\n\n    expect(thumb).toHaveAttribute('data-checked', '');\n    expect(thumb).toHaveAttribute('data-disabled', '');\n    expect(thumb).toHaveAttribute('data-readonly', '');\n    expect(thumb).toHaveAttribute('data-required', '');\n\n    await setProps({ disabled: false, readOnly: false });\n    fireEvent.click(switchElement);\n\n    expect(switchElement).toHaveAttribute('data-unchecked', '');\n    expect(switchElement).not.toHaveAttribute('data-checked');\n\n    expect(thumb).toHaveAttribute('data-unchecked', '');\n    expect(thumb).not.toHaveAttribute('data-checked');\n  });\n\n  it('should set the name attribute only on the input', async () => {\n    await render(<Switch.Root name=\"switch-name\" />);\n\n    const switchElement = screen.getByRole('switch');\n    const input = screen.getByRole('checkbox', { hidden: true });\n\n    expect(input).toHaveAttribute('name', 'switch-name');\n    expect(switchElement).not.toHaveAttribute('name');\n  });\n\n  it('should not set the value attribute by default', async () => {\n    await render(<Switch.Root />);\n\n    const input = screen.getByRole('checkbox', { hidden: true });\n\n    expect(input).not.toHaveAttribute('value');\n  });\n\n  it('should set the value attribute only on the input', async () => {\n    await render(<Switch.Root value=\"1\" />);\n\n    const switchElement = screen.getByRole('switch');\n    const input = screen.getByRole('checkbox', { hidden: true });\n\n    expect(input).toHaveAttribute('value', '1');\n    expect(switchElement).not.toHaveAttribute('value');\n  });\n\n  describe('with native <label>', () => {\n    it('should toggle the switch when a wrapping <label> is clicked', async () => {\n      const { user } = await render(\n        <label data-testid=\"label\">\n          <Switch.Root />\n          Toggle\n        </label>,\n      );\n\n      const switchElement = screen.getByRole('switch');\n      expect(switchElement).toHaveAttribute('aria-checked', 'false');\n\n      await user.click(screen.getByTestId('label'));\n      expect(switchElement).toHaveAttribute('aria-checked', 'true');\n    });\n\n    it('should toggle the switch when a explicitly linked <label> is clicked', async () => {\n      const { user } = await render(\n        <div>\n          <label data-testid=\"label\" htmlFor=\"mySwitch\">\n            Toggle\n          </label>\n\n          <Switch.Root id=\"mySwitch\" />\n        </div>,\n      );\n\n      const switchElement = screen.getByRole('switch');\n      expect(switchElement).toHaveAttribute('aria-checked', 'false');\n\n      await user.click(screen.getByTestId('label'));\n      expect(switchElement).toHaveAttribute('aria-checked', 'true');\n    });\n\n    it('should associate `id` with the native button when `nativeButton=true`', async () => {\n      const { user } = await render(\n        <div>\n          <label data-testid=\"label\" htmlFor=\"mySwitch\">\n            Toggle\n          </label>\n\n          <Switch.Root id=\"mySwitch\" nativeButton render={<button />} />\n        </div>,\n      );\n\n      const switchElement = screen.getByRole('switch');\n      expect(switchElement).toHaveAttribute('id', 'mySwitch');\n\n      const hiddenInput = screen.getByRole('checkbox', { hidden: true });\n      expect(hiddenInput).not.toHaveAttribute('id', 'mySwitch');\n\n      expect(switchElement).toHaveAttribute('aria-checked', 'false');\n      await user.click(screen.getByTestId('label'));\n      expect(switchElement).toHaveAttribute('aria-checked', 'true');\n    });\n  });\n\n  describe('Form', () => {\n    // FormData is not available in JSDOM\n    it.skipIf(isJSDOM)(\n      'should include the switch value in form submission, matching native checkbox behavior',\n      async () => {\n        const submitSpy = vi.fn((event) => {\n          event.preventDefault();\n          const formData = new FormData(event.currentTarget);\n          return formData.get('test-switch');\n        });\n\n        const { user } = await render(\n          <Form onSubmit={submitSpy}>\n            <Field.Root name=\"test-switch\">\n              <Switch.Root />\n            </Field.Root>\n            <button type=\"submit\">Submit</button>\n          </Form>,\n        );\n\n        const switchElement = screen.getByRole('switch');\n        const submitButton = screen.getByRole('button')!;\n\n        await user.click(submitButton);\n\n        expect(submitSpy.mock.calls.length).toBe(1);\n        expect(submitSpy.mock.results.at(-1)?.value).toBe(null);\n\n        await user.click(switchElement);\n        await user.click(submitButton);\n\n        expect(submitSpy.mock.calls.length).toBe(2);\n        expect(submitSpy.mock.results.at(-1)?.value).toBe('on');\n      },\n    );\n\n    it.skipIf(isJSDOM)('matches native checkbox form submission behavior', async () => {\n      const nativeSubmitSpy = vi.fn((event) => {\n        event.preventDefault();\n        const formData = new FormData(event.currentTarget);\n        return {\n          get: formData.get('native'),\n          getAll: formData.getAll('native'),\n        };\n      });\n\n      const customSubmitSpy = vi.fn((event) => {\n        event.preventDefault();\n        const formData = new FormData(event.currentTarget);\n        return {\n          get: formData.get('custom'),\n          getAll: formData.getAll('custom'),\n        };\n      });\n\n      const { user: nativeUser } = await render(\n        <form onSubmit={nativeSubmitSpy}>\n          <input type=\"checkbox\" name=\"native\" />\n          <button type=\"submit\">Submit</button>\n        </form>,\n      );\n\n      const nativeCheckbox = screen.getByRole('checkbox');\n      const nativeSubmitButton = screen.getByRole('button')!;\n\n      await nativeUser.click(nativeSubmitButton);\n      expect(nativeSubmitSpy.mock.results.at(-1)?.value.get).toBe(null);\n      expect(nativeSubmitSpy.mock.results.at(-1)?.value.getAll).toEqual([]);\n\n      await nativeUser.click(nativeCheckbox);\n      await nativeUser.click(nativeSubmitButton);\n      expect(nativeSubmitSpy.mock.results.at(-1)?.value.get).toBe('on');\n\n      const { user: customUser } = await render(\n        <Form onSubmit={customSubmitSpy}>\n          <Field.Root name=\"custom\">\n            <Switch.Root />\n          </Field.Root>\n          <button type=\"submit\">Submit</button>\n        </Form>,\n      );\n\n      const customSwitch = screen.getByRole('switch');\n      const customSubmitButton = screen.getAllByRole('button')[1]!;\n\n      await customUser.click(customSubmitButton);\n      expect(customSubmitSpy.mock.results.at(-1)?.value.get).toBe(null);\n      expect(customSubmitSpy.mock.results.at(-1)?.value.getAll).toEqual([]);\n\n      await customUser.click(customSwitch);\n      await customUser.click(customSubmitButton);\n      expect(customSubmitSpy.mock.results.at(-1)?.value.get).toBe('on');\n    });\n\n    it.skipIf(isJSDOM)(\n      'should submit uncheckedValue when switch is off and uncheckedValue is specified',\n      async () => {\n        const submitSpy = vi.fn((event) => {\n          event.preventDefault();\n          const formData = new FormData(event.currentTarget);\n          return formData.get('test-switch');\n        });\n\n        const { user } = await render(\n          <Form onSubmit={submitSpy}>\n            <Field.Root name=\"test-switch\">\n              <Switch.Root uncheckedValue=\"off\" />\n            </Field.Root>\n            <button type=\"submit\">Submit</button>\n          </Form>,\n        );\n\n        const switchElement = screen.getByRole('switch');\n        const submitButton = screen.getByRole('button')!;\n\n        await user.click(submitButton);\n\n        expect(submitSpy.mock.calls.length).toBe(1);\n        expect(submitSpy.mock.results.at(-1)?.value).toBe('off');\n\n        await user.click(switchElement);\n        await user.click(submitButton);\n\n        expect(submitSpy.mock.calls.length).toBe(2);\n        expect(submitSpy.mock.results.at(-1)?.value).toBe('on');\n\n        await user.click(switchElement);\n        await user.click(submitButton);\n\n        expect(submitSpy.mock.calls.length).toBe(3);\n        expect(submitSpy.mock.results.at(-1)?.value).toBe('off');\n      },\n    );\n\n    it.skipIf(isJSDOM)('should submit custom uncheckedValue when switch is off', async () => {\n      const submitSpy = vi.fn((event) => {\n        event.preventDefault();\n        const formData = new FormData(event.currentTarget);\n        return formData.get('test-switch');\n      });\n\n      const { user } = await render(\n        <Form onSubmit={submitSpy}>\n          <Field.Root name=\"test-switch\">\n            <Switch.Root uncheckedValue=\"false\" />\n          </Field.Root>\n          <button type=\"submit\">Submit</button>\n        </Form>,\n      );\n\n      const switchElement = screen.getByRole('switch');\n      const submitButton = screen.getByRole('button')!;\n\n      await user.click(submitButton);\n\n      expect(submitSpy.mock.calls.length).toBe(1);\n      expect(submitSpy.mock.results.at(-1)?.value).toBe('false');\n\n      await user.click(switchElement);\n      await user.click(submitButton);\n\n      expect(submitSpy.mock.calls.length).toBe(2);\n      expect(submitSpy.mock.results.at(-1)?.value).toBe('on');\n    });\n\n    it('triggers native HTML validation on submit', async () => {\n      const { user } = await render(\n        <Form>\n          <Field.Root name=\"test\">\n            <Switch.Root name=\"switch\" required />\n            <Field.Error match=\"valueMissing\" data-testid=\"error\">\n              required\n            </Field.Error>\n          </Field.Root>\n          <button type=\"submit\">Submit</button>\n        </Form>,\n      );\n\n      const submit = screen.getByText('Submit');\n\n      expect(screen.queryByTestId('error')).toBe(null);\n\n      await user.click(submit);\n\n      const error = screen.getByTestId('error');\n      expect(error).toHaveTextContent('required');\n    });\n\n    it('clears external errors on change', async () => {\n      await render(\n        <Form\n          errors={{\n            test: 'test',\n          }}\n        >\n          <Field.Root name=\"test\" data-testid=\"field\">\n            <Switch.Root data-testid=\"switch\" />\n            <Field.Error data-testid=\"error\" />\n          </Field.Root>\n        </Form>,\n      );\n\n      const switchElement = screen.getByTestId('switch');\n\n      expect(switchElement).toHaveAttribute('aria-invalid', 'true');\n      expect(screen.queryByTestId('error')).toHaveTextContent('test');\n\n      fireEvent.click(switchElement);\n\n      expect(switchElement).not.toHaveAttribute('aria-invalid');\n      expect(screen.queryByTestId('error')).toBe(null);\n    });\n  });\n\n  describe('Field', () => {\n    it('should receive disabled prop from Field.Root', async () => {\n      await render(\n        <Field.Root disabled>\n          <Switch.Root />\n        </Field.Root>,\n      );\n\n      const switchElement = screen.getByRole('switch');\n      expect(switchElement).toHaveAttribute('data-disabled');\n    });\n\n    it('should receive name prop from Field.Root', async () => {\n      await render(\n        <Field.Root name=\"field-switch\">\n          <Switch.Root />\n        </Field.Root>,\n      );\n\n      const input = screen.getByRole('checkbox', { hidden: true });\n      expect(input).toHaveAttribute('name', 'field-switch');\n    });\n\n    it('[data-touched]', async () => {\n      await render(\n        <Field.Root>\n          <Switch.Root data-testid=\"button\" />\n        </Field.Root>,\n      );\n\n      const button = screen.getByTestId('button');\n\n      fireEvent.focus(button);\n      fireEvent.blur(button);\n\n      expect(button).toHaveAttribute('data-touched', '');\n    });\n\n    it('[data-dirty]', async () => {\n      await render(\n        <Field.Root>\n          <Switch.Root data-testid=\"button\" />\n        </Field.Root>,\n      );\n\n      const button = screen.getByTestId('button');\n\n      expect(button).not.toHaveAttribute('data-dirty');\n\n      fireEvent.click(button);\n\n      expect(button).toHaveAttribute('data-dirty', '');\n    });\n\n    describe('[data-filled]', () => {\n      it('adds [data-filled] attribute when checked after being initially unchecked', async () => {\n        await render(\n          <Field.Root>\n            <Switch.Root data-testid=\"button\" />\n          </Field.Root>,\n        );\n\n        const button = screen.getByTestId('button');\n\n        expect(button).not.toHaveAttribute('data-filled');\n\n        fireEvent.click(button);\n\n        expect(button).toHaveAttribute('data-filled', '');\n\n        fireEvent.click(button);\n\n        expect(button).not.toHaveAttribute('data-filled');\n      });\n\n      it('removes [data-filled] attribute when unchecked after being initially checked', async () => {\n        await render(\n          <Field.Root>\n            <Switch.Root data-testid=\"button\" defaultChecked />\n          </Field.Root>,\n        );\n\n        const button = screen.getByTestId('button');\n\n        expect(button).toHaveAttribute('data-filled');\n\n        fireEvent.click(button);\n\n        expect(button).not.toHaveAttribute('data-filled', '');\n      });\n    });\n\n    it('[data-focused]', async () => {\n      await render(\n        <Field.Root>\n          <Switch.Root data-testid=\"button\" />\n        </Field.Root>,\n      );\n\n      const button = screen.getByTestId('button');\n\n      expect(button).not.toHaveAttribute('data-focused');\n\n      fireEvent.focus(button);\n\n      expect(button).toHaveAttribute('data-focused', '');\n\n      fireEvent.blur(button);\n\n      expect(button).not.toHaveAttribute('data-focused');\n    });\n\n    it('prop: validationMode=onSubmit', async () => {\n      await render(\n        <Form>\n          <Field.Root>\n            <Switch.Root required />\n            <Field.Error data-testid=\"error\" />\n          </Field.Root>\n          <button type=\"submit\">submit</button>\n        </Form>,\n      );\n\n      const button = screen.getByRole('switch');\n      expect(button).not.toHaveAttribute('aria-invalid');\n\n      fireEvent.click(screen.getByText('submit'));\n      expect(button).toHaveAttribute('aria-invalid', 'true');\n      expect(screen.queryByTestId('error')).not.toBe(null);\n\n      fireEvent.click(button);\n      expect(button).not.toHaveAttribute('aria-invalid');\n      expect(screen.queryByTestId('error')).toBe(null);\n\n      fireEvent.click(button);\n      expect(button).toHaveAttribute('aria-invalid', 'true');\n      expect(screen.queryByTestId('error')).not.toBe(null);\n    });\n\n    it('prop: validationMode=onChange', async () => {\n      await render(\n        <Field.Root\n          validationMode=\"onChange\"\n          validate={(value) => {\n            const checked = value as boolean;\n            return checked ? 'error' : null;\n          }}\n        >\n          <Switch.Root data-testid=\"button\" />\n        </Field.Root>,\n      );\n\n      const button = screen.getByTestId('button');\n\n      expect(button).not.toHaveAttribute('aria-invalid');\n\n      fireEvent.click(button);\n\n      expect(button).toHaveAttribute('aria-invalid', 'true');\n    });\n\n    it('revalidates when a controlled value changes externally', async () => {\n      const validateSpy = vi.fn((value: unknown) => ((value as boolean) ? 'error' : null));\n\n      function App() {\n        const [checked, setChecked] = React.useState(false);\n\n        return (\n          <React.Fragment>\n            <Field.Root validationMode=\"onChange\" validate={validateSpy} name=\"newsletters\">\n              <Switch.Root data-testid=\"button\" checked={checked} onCheckedChange={setChecked} />\n            </Field.Root>\n            <button type=\"button\" onClick={() => setChecked((prev) => !prev)}>\n              Toggle externally\n            </button>\n          </React.Fragment>\n        );\n      }\n\n      await render(<App />);\n\n      const button = screen.getByTestId('button');\n      const toggle = screen.getByText('Toggle externally');\n\n      expect(button).not.toHaveAttribute('aria-invalid');\n      const initialCallCount = validateSpy.mock.calls.length;\n\n      fireEvent.click(toggle);\n\n      expect(validateSpy.mock.calls.length).toBe(initialCallCount + 1);\n      expect(validateSpy.mock.lastCall?.[0]).toBe(true);\n      expect(button).toHaveAttribute('aria-invalid', 'true');\n    });\n\n    it('prop: validationMode=onBlur', async () => {\n      await render(\n        <Field.Root\n          validationMode=\"onBlur\"\n          validate={(value) => {\n            const checked = value as boolean;\n            return checked ? 'error' : null;\n          }}\n        >\n          <Switch.Root data-testid=\"button\" />\n          <Field.Error data-testid=\"error\" />\n        </Field.Root>,\n      );\n\n      const button = screen.getByTestId('button');\n\n      expect(button).not.toHaveAttribute('aria-invalid');\n\n      fireEvent.click(button);\n      fireEvent.blur(button);\n\n      expect(button).toHaveAttribute('aria-invalid', 'true');\n    });\n\n    describe('Field.Label', () => {\n      describe('implicit', () => {\n        it('sets `for` on the label', async () => {\n          await render(\n            <Field.Root>\n              <Field.Label data-testid=\"label\">\n                <Switch.Root />\n                OK\n              </Field.Label>\n            </Field.Root>,\n          );\n\n          const label = screen.getByTestId('label');\n          expect(label.getAttribute('for')).not.toBe(null);\n\n          const input = document.querySelector('input[type=\"checkbox\"]');\n          expect(label.getAttribute('for')).toBe(input?.getAttribute('id'));\n\n          const switchEl = screen.getByRole('switch');\n          expect(switchEl.getAttribute('aria-labelledby')).toBe(label.getAttribute('id'));\n          expect(switchEl).toHaveAttribute('aria-checked', 'false');\n\n          fireEvent.click(label);\n          expect(switchEl).toHaveAttribute('aria-checked', 'true');\n        });\n      });\n\n      describe('explicit association', () => {\n        it('when the label is sibling to the switch', async () => {\n          await render(\n            <Field.Root>\n              <Field.Label data-testid=\"label\">Label</Field.Label>\n              <Switch.Root />\n            </Field.Root>,\n          );\n\n          const label = screen.getByTestId('label');\n          const switchEl = screen.getByRole('switch');\n          const input = document.querySelector('input[type=\"checkbox\"]');\n\n          expect(label.getAttribute('for')).not.toBe(null);\n\n          expect(label.getAttribute('for')).toBe(input?.getAttribute('id'));\n          expect(switchEl.getAttribute('aria-labelledby')).toBe(label.getAttribute('id'));\n\n          expect(switchEl).toHaveAttribute('aria-checked', 'false');\n\n          fireEvent.click(label);\n          expect(switchEl).toHaveAttribute('aria-checked', 'true');\n        });\n\n        it('when rendering a non-native button', async () => {\n          await render(\n            <Field.Root>\n              <Field.Label data-testid=\"label\">OK</Field.Label>\n              <Switch.Root render={<span />} nativeButton={false} />\n            </Field.Root>,\n          );\n\n          const label = screen.getByTestId('label');\n          expect(label.getAttribute('for')).not.toBe(null);\n          const input = document.querySelector('input[type=\"checkbox\"]');\n          expect(input?.getAttribute('id')).toBe(label.getAttribute('for'));\n          const switchEl = screen.getByRole('switch');\n          expect(switchEl.getAttribute('aria-labelledby')).toBe(label.getAttribute('id'));\n        });\n\n        it('when rendering a non-native label', async () => {\n          await render(\n            <Field.Root>\n              <Field.Label data-testid=\"label\" render={<span />} nativeLabel={false}>\n                <Switch.Root data-testid=\"button\" />\n              </Field.Label>\n            </Field.Root>,\n          );\n\n          const label = screen.getByTestId('label');\n          const switchEl = screen.getByRole('switch');\n\n          expect(label.getAttribute('for')).toBe(null);\n          expect(label.getAttribute('id')).not.toBe(null);\n\n          expect(switchEl.getAttribute('aria-labelledby')).toBe(label.getAttribute('id'));\n          expect(switchEl).toHaveAttribute('aria-checked', 'false');\n\n          // non-native labels cannot toggle a non-native-button switch\n          fireEvent.click(label);\n          expect(switchEl).not.toHaveAttribute('aria-checked', 'true');\n        });\n      });\n    });\n\n    it('Field.Description', async () => {\n      await render(\n        <Field.Root>\n          <Switch.Root data-testid=\"button\" />\n          <Field.Description data-testid=\"description\" />\n        </Field.Root>,\n      );\n\n      const internalInput = screen.queryByRole<HTMLInputElement>('checkbox', { hidden: true });\n\n      expect(internalInput).toHaveAttribute(\n        'aria-describedby',\n        screen.getByTestId('description').id,\n      );\n    });\n  });\n\n  it('can render a native button', async () => {\n    const { container, user } = await render(<Switch.Root render={<button />} nativeButton />);\n\n    const switchEl = screen.getByRole('switch');\n    expect(switchEl).toHaveAttribute('aria-checked', 'false');\n    // eslint-disable-next-line testing-library/no-container\n    expect(container.querySelector('button')).toBe(switchEl);\n\n    await user.keyboard('[Tab]');\n    expect(switchEl).toHaveFocus();\n\n    await user.keyboard('[Enter]');\n    expect(switchEl).toHaveAttribute('aria-checked', 'true');\n\n    await user.keyboard('[Space]');\n    expect(switchEl).toHaveAttribute('aria-checked', 'false');\n\n    await user.click(switchEl);\n    expect(switchEl).toHaveAttribute('aria-checked', 'true');\n  });\n});\n"
  },
  {
    "path": "packages/react/src/switch/root/SwitchRoot.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useControlled } from '@base-ui/utils/useControlled';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useMergedRefs } from '@base-ui/utils/useMergedRefs';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { visuallyHidden, visuallyHiddenInput } from '@base-ui/utils/visuallyHidden';\nimport { EMPTY_OBJECT } from '@base-ui/utils/empty';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport type { BaseUIComponentProps, NonNativeButtonProps } from '../../utils/types';\nimport { mergeProps } from '../../merge-props';\nimport { useBaseUiId } from '../../utils/useBaseUiId';\nimport { useButton } from '../../use-button';\nimport { SwitchRootContext } from './SwitchRootContext';\nimport { stateAttributesMapping } from '../stateAttributesMapping';\nimport { useField } from '../../field/useField';\nimport type { FieldRootState } from '../../field/root/FieldRoot';\nimport { useFieldRootContext } from '../../field/root/FieldRootContext';\nimport { useFormContext } from '../../form/FormContext';\nimport { useLabelableContext } from '../../labelable-provider/LabelableContext';\nimport { useAriaLabelledBy } from '../../labelable-provider/useAriaLabelledBy';\nimport { useLabelableId } from '../../labelable-provider/useLabelableId';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport type { BaseUIChangeEventDetails } from '../../types';\nimport { useValueChanged } from '../../utils/useValueChanged';\n\n/**\n * Represents the switch itself.\n * Renders a `<span>` element and a hidden `<input>` beside.\n *\n * Documentation: [Base UI Switch](https://base-ui.com/react/components/switch)\n */\nexport const SwitchRoot = React.forwardRef(function SwitchRoot(\n  componentProps: SwitchRoot.Props,\n  forwardedRef: React.ForwardedRef<HTMLElement>,\n) {\n  const {\n    checked: checkedProp,\n    className,\n    defaultChecked,\n    'aria-labelledby': ariaLabelledByProp,\n    id: idProp,\n    inputRef: externalInputRef,\n    name: nameProp,\n    nativeButton = false,\n    onCheckedChange: onCheckedChangeProp,\n    readOnly = false,\n    required = false,\n    disabled: disabledProp = false,\n    render,\n    uncheckedValue,\n    value,\n    ...elementProps\n  } = componentProps;\n\n  const { clearErrors } = useFormContext();\n  const {\n    state: fieldState,\n    setTouched,\n    setDirty,\n    validityData,\n    setFilled,\n    setFocused,\n    shouldValidateOnChange,\n    validationMode,\n    disabled: fieldDisabled,\n    name: fieldName,\n    validation,\n  } = useFieldRootContext();\n  const { labelId } = useLabelableContext();\n\n  const disabled = fieldDisabled || disabledProp;\n  const name = fieldName ?? nameProp;\n\n  const onCheckedChange = useStableCallback(onCheckedChangeProp);\n\n  const inputRef = React.useRef<HTMLInputElement>(null);\n  const handleInputRef = useMergedRefs(inputRef, externalInputRef, validation.inputRef);\n\n  const switchRef = React.useRef<HTMLButtonElement | null>(null);\n\n  const id = useBaseUiId();\n\n  const controlId = useLabelableId({\n    id: idProp,\n    implicit: false,\n    controlRef: switchRef,\n  });\n  const hiddenInputId = nativeButton ? undefined : controlId;\n\n  const [checked, setCheckedState] = useControlled({\n    controlled: checkedProp,\n    default: Boolean(defaultChecked),\n    name: 'Switch',\n    state: 'checked',\n  });\n\n  useField({\n    id,\n    commit: validation.commit,\n    value: checked,\n    controlRef: switchRef,\n    name,\n    getValue: () => checked,\n  });\n\n  useIsoLayoutEffect(() => {\n    if (inputRef.current) {\n      setFilled(inputRef.current.checked);\n    }\n  }, [inputRef, setFilled]);\n\n  useValueChanged(checked, () => {\n    clearErrors(name);\n    setDirty(checked !== validityData.initialValue);\n    setFilled(checked);\n\n    if (shouldValidateOnChange()) {\n      validation.commit(checked);\n    } else {\n      validation.commit(checked, true);\n    }\n  });\n\n  const { getButtonProps, buttonRef } = useButton({\n    disabled,\n    native: nativeButton,\n  });\n  const ariaLabelledBy = useAriaLabelledBy(\n    ariaLabelledByProp,\n    labelId,\n    inputRef,\n    !nativeButton,\n    hiddenInputId,\n  );\n\n  const rootProps: React.ComponentPropsWithRef<'span'> = {\n    id: nativeButton ? controlId : id,\n    role: 'switch',\n    'aria-checked': checked,\n    'aria-readonly': readOnly || undefined,\n    'aria-required': required || undefined,\n    'aria-labelledby': ariaLabelledBy,\n    onFocus() {\n      if (!disabled) {\n        setFocused(true);\n      }\n    },\n    onBlur() {\n      const element = inputRef.current;\n      if (!element || disabled) {\n        return;\n      }\n\n      setTouched(true);\n      setFocused(false);\n\n      if (validationMode === 'onBlur') {\n        validation.commit(element.checked);\n      }\n    },\n    onClick(event) {\n      if (readOnly || disabled) {\n        return;\n      }\n\n      event.preventDefault();\n\n      inputRef.current?.dispatchEvent(\n        new PointerEvent('click', {\n          bubbles: true,\n          shiftKey: event.shiftKey,\n          ctrlKey: event.ctrlKey,\n          altKey: event.altKey,\n          metaKey: event.metaKey,\n        }),\n      );\n    },\n  };\n\n  const inputProps: React.ComponentPropsWithRef<'input'> = React.useMemo(\n    () =>\n      mergeProps<'input'>(\n        {\n          checked,\n          disabled,\n          id: hiddenInputId,\n          name,\n          required,\n          style: name ? visuallyHiddenInput : visuallyHidden,\n          tabIndex: -1,\n          type: 'checkbox',\n          'aria-hidden': true,\n          ref: handleInputRef,\n          onChange(event) {\n            // Workaround for https://github.com/facebook/react/issues/9023\n            if (event.nativeEvent.defaultPrevented) {\n              return;\n            }\n\n            const nextChecked = event.target.checked;\n            const eventDetails = createChangeEventDetails(REASONS.none, event.nativeEvent);\n\n            onCheckedChange?.(nextChecked, eventDetails);\n\n            if (eventDetails.isCanceled) {\n              return;\n            }\n\n            setCheckedState(nextChecked);\n          },\n          onFocus() {\n            switchRef.current?.focus();\n          },\n        },\n        validation.getInputValidationProps,\n        // React <19 sets an empty value if `undefined` is passed explicitly\n        // To avoid this, we only set the value if it's defined\n        value !== undefined ? { value } : EMPTY_OBJECT,\n      ),\n    [\n      checked,\n      disabled,\n      handleInputRef,\n      hiddenInputId,\n      name,\n      onCheckedChange,\n      required,\n      setCheckedState,\n      validation,\n      value,\n    ],\n  );\n\n  const state: SwitchRootState = React.useMemo(\n    () => ({\n      ...fieldState,\n      checked,\n      disabled,\n      readOnly,\n      required,\n    }),\n    [fieldState, checked, disabled, readOnly, required],\n  );\n\n  const element = useRenderElement('span', componentProps, {\n    state,\n    ref: [forwardedRef, switchRef, buttonRef],\n    props: [rootProps, validation.getValidationProps, elementProps, getButtonProps],\n    stateAttributesMapping,\n  });\n\n  return (\n    <SwitchRootContext.Provider value={state}>\n      {element}\n      {!checked && name && uncheckedValue !== undefined && (\n        <input type=\"hidden\" name={name} value={uncheckedValue} />\n      )}\n      <input {...inputProps} />\n    </SwitchRootContext.Provider>\n  );\n});\n\nexport interface SwitchRootState extends FieldRootState {\n  /**\n   * Whether the switch is currently active.\n   */\n  checked: boolean;\n  /**\n   * Whether the component should ignore user interaction.\n   */\n  disabled: boolean;\n  /**\n   * Whether the user should be unable to activate or deactivate the switch.\n   */\n  readOnly: boolean;\n  /**\n   * Whether the user must activate the switch before submitting a form.\n   */\n  required: boolean;\n}\n\nexport interface SwitchRootProps\n  extends NonNativeButtonProps, Omit<BaseUIComponentProps<'span', SwitchRootState>, 'onChange'> {\n  /**\n   * The id of the switch element.\n   */\n  id?: string | undefined;\n  /**\n   * Whether the switch is currently active.\n   *\n   * To render an uncontrolled switch, use the `defaultChecked` prop instead.\n   */\n  checked?: boolean | undefined;\n  /**\n   * Whether the switch is initially active.\n   *\n   * To render a controlled switch, use the `checked` prop instead.\n   * @default false\n   */\n  defaultChecked?: boolean | undefined;\n  /**\n   * Whether the component should ignore user interaction.\n   * @default false\n   */\n  disabled?: boolean | undefined;\n  /**\n   * A ref to access the hidden `<input>` element.\n   */\n  inputRef?: React.Ref<HTMLInputElement> | undefined;\n  /**\n   * Identifies the field when a form is submitted.\n   */\n  name?: string | undefined;\n  /**\n   * Event handler called when the switch is activated or deactivated.\n   */\n  onCheckedChange?:\n    | ((checked: boolean, eventDetails: SwitchRoot.ChangeEventDetails) => void)\n    | undefined;\n  /**\n   * Whether the user should be unable to activate or deactivate the switch.\n   * @default false\n   */\n  readOnly?: boolean | undefined;\n  /**\n   * Whether the user must activate the switch before submitting a form.\n   * @default false\n   */\n  required?: boolean | undefined;\n  /**\n   * The value submitted with the form when the switch is on.\n   * By default, switch submits the \"on\" value, matching native checkbox behavior.\n   */\n  value?: string | undefined;\n  /**\n   * The value submitted with the form when the switch is off.\n   * By default, unchecked switches do not submit any value, matching native checkbox behavior.\n   */\n  uncheckedValue?: string | undefined;\n}\n\nexport type SwitchRootChangeEventReason = typeof REASONS.none;\nexport type SwitchRootChangeEventDetails = BaseUIChangeEventDetails<SwitchRoot.ChangeEventReason>;\n\nexport namespace SwitchRoot {\n  export type State = SwitchRootState;\n  export type Props = SwitchRootProps;\n  export type ChangeEventReason = SwitchRootChangeEventReason;\n  export type ChangeEventDetails = SwitchRootChangeEventDetails;\n}\n"
  },
  {
    "path": "packages/react/src/switch/root/SwitchRootContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport type { SwitchRootState } from './SwitchRoot';\n\nexport type SwitchRootContext = SwitchRootState;\n\nexport const SwitchRootContext = React.createContext<SwitchRootContext | undefined>(undefined);\n\nexport function useSwitchRootContext() {\n  const context = React.useContext(SwitchRootContext);\n  if (context === undefined) {\n    throw new Error(\n      'Base UI: SwitchRootContext is missing. Switch parts must be placed within <Switch.Root>.',\n    );\n  }\n\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/switch/root/SwitchRootDataAttributes.ts",
    "content": "export enum SwitchRootDataAttributes {\n  /**\n   * Present when the switch is checked.\n   */\n  checked = 'data-checked',\n  /**\n   * Present when the switch is not checked.\n   */\n  unchecked = 'data-unchecked',\n  /**\n   * Present when the switch is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the switch is readonly.\n   */\n  readonly = 'data-readonly',\n  /**\n   * Present when the switch is required.\n   */\n  required = 'data-required',\n  /**\n   * Present when the switch is in valid state (when wrapped in Field.Root).\n   */\n  valid = 'data-valid',\n  /**\n   * Present when the switch is in invalid state (when wrapped in Field.Root).\n   */\n  invalid = 'data-invalid',\n  /**\n   * Present when the switch has been touched (when wrapped in Field.Root).\n   */\n  touched = 'data-touched',\n  /**\n   * Present when the switch's value has changed (when wrapped in Field.Root).\n   */\n  dirty = 'data-dirty',\n  /**\n   * Present when the switch is active (when wrapped in Field.Root).\n   */\n  filled = 'data-filled',\n  /**\n   * Present when the switch is focused (when wrapped in Field.Root).\n   */\n  focused = 'data-focused',\n}\n"
  },
  {
    "path": "packages/react/src/switch/stateAttributesMapping.ts",
    "content": "import type { SwitchRootState } from './root/SwitchRoot';\nimport type { StateAttributesMapping } from '../utils/getStateAttributesProps';\nimport { fieldValidityMapping } from '../field/utils/constants';\nimport { SwitchRootDataAttributes } from './root/SwitchRootDataAttributes';\n\nexport const stateAttributesMapping: StateAttributesMapping<SwitchRootState> = {\n  ...fieldValidityMapping,\n  checked(value): Record<string, string> {\n    if (value) {\n      return {\n        [SwitchRootDataAttributes.checked]: '',\n      };\n    }\n\n    return {\n      [SwitchRootDataAttributes.unchecked]: '',\n    };\n  },\n};\n"
  },
  {
    "path": "packages/react/src/switch/thumb/SwitchThumb.test.tsx",
    "content": "import { Switch } from '@base-ui/react/switch';\nimport { createRenderer, describeConformance } from '#test-utils';\nimport { SwitchRootContext } from '../root/SwitchRootContext';\n\nconst testContext: SwitchRootContext = {\n  checked: false,\n  disabled: false,\n  readOnly: false,\n  required: false,\n  dirty: false,\n  touched: false,\n  filled: false,\n  focused: false,\n  valid: null,\n};\n\ndescribe('<Switch.Thumb />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Switch.Thumb />, () => ({\n    refInstanceof: window.HTMLSpanElement,\n    render: (node) => {\n      return render(\n        <SwitchRootContext.Provider value={testContext}>{node}</SwitchRootContext.Provider>,\n      );\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/switch/thumb/SwitchThumb.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport type { SwitchRootState } from '../root/SwitchRoot';\nimport { useSwitchRootContext } from '../root/SwitchRootContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useFieldRootContext } from '../../field/root/FieldRootContext';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { stateAttributesMapping } from '../stateAttributesMapping';\n\n/**\n * The movable part of the switch that indicates whether the switch is on or off.\n * Renders a `<span>`.\n *\n * Documentation: [Base UI Switch](https://base-ui.com/react/components/switch)\n */\nexport const SwitchThumb = React.forwardRef(function SwitchThumb(\n  componentProps: SwitchThumb.Props,\n  forwardedRef: React.ForwardedRef<HTMLSpanElement>,\n) {\n  const { render, className, ...elementProps } = componentProps;\n\n  const { state: fieldState } = useFieldRootContext();\n\n  const state = useSwitchRootContext();\n  const extendedState = { ...fieldState, ...state };\n\n  return useRenderElement('span', componentProps, {\n    state: extendedState,\n    ref: forwardedRef,\n    stateAttributesMapping,\n    props: elementProps,\n  });\n});\n\nexport interface SwitchThumbProps extends BaseUIComponentProps<'span', SwitchThumbState> {}\n\nexport interface SwitchThumbState extends SwitchRootState {}\n\nexport namespace SwitchThumb {\n  export type Props = SwitchThumbProps;\n  export type State = SwitchThumbState;\n}\n"
  },
  {
    "path": "packages/react/src/switch/thumb/SwitchThumbDataAttributes.ts",
    "content": "export enum SwitchThumbDataAttributes {\n  /**\n   * Present when the switch is checked.\n   */\n  checked = 'data-checked',\n  /**\n   * Present when the switch is not checked.\n   */\n  unchecked = 'data-unchecked',\n  /**\n   * Present when the switch is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the switch is readonly.\n   */\n  readonly = 'data-readonly',\n  /**\n   * Present when the switch is required.\n   */\n  required = 'data-required',\n  /**\n   * Present when the switch is in valid state (when wrapped in Field.Root).\n   */\n  valid = 'data-valid',\n  /**\n   * Present when the switch is in invalid state (when wrapped in Field.Root).\n   */\n  invalid = 'data-invalid',\n  /**\n   * Present when the switch has been touched (when wrapped in Field.Root).\n   */\n  touched = 'data-touched',\n  /**\n   * Present when the switch's value has changed (when wrapped in Field.Root).\n   */\n  dirty = 'data-dirty',\n  /**\n   * Present when the switch is active (when wrapped in Field.Root).\n   */\n  filled = 'data-filled',\n  /**\n   * Present when the switch is focused (when wrapped in Field.Root).\n   */\n  focused = 'data-focused',\n}\n"
  },
  {
    "path": "packages/react/src/tabs/index.parts.ts",
    "content": "export { TabsRoot as Root } from './root/TabsRoot';\nexport { TabsTab as Tab } from './tab/TabsTab';\nexport { TabsIndicator as Indicator } from './indicator/TabsIndicator';\nexport { TabsPanel as Panel } from './panel/TabsPanel';\nexport { TabsList as List } from './list/TabsList';\n"
  },
  {
    "path": "packages/react/src/tabs/index.ts",
    "content": "export * as Tabs from './index.parts';\n\nexport type * from './root/TabsRoot';\nexport type * from './indicator/TabsIndicator';\nexport type * from './tab/TabsTab';\nexport type * from './panel/TabsPanel';\nexport type * from './list/TabsList';\n"
  },
  {
    "path": "packages/react/src/tabs/indicator/TabsIndicator.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { Tabs } from '@base-ui/react/tabs';\nimport { waitFor, screen } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\nimport { getCssDimensions } from '../../utils/getCssDimensions';\n\ndescribe('<Tabs.Indicator />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Tabs.Indicator />, () => ({\n    render: (node) => {\n      return render(\n        <Tabs.Root defaultValue={1}>\n          <Tabs.List>\n            <Tabs.Tab value={1} />\n            {node}\n          </Tabs.List>\n        </Tabs.Root>,\n      );\n    },\n    refInstanceof: window.HTMLSpanElement,\n    testRenderPropWith: 'div',\n  }));\n\n  describe.skipIf(isJSDOM)('rendering', () => {\n    it('should not render when no tab is active', async () => {\n      await render(\n        <Tabs.Root value={null}>\n          <Tabs.List>\n            <Tabs.Indicator data-testid=\"bubble\" />\n          </Tabs.List>\n        </Tabs.Root>,\n      );\n\n      expect(screen.queryByTestId('bubble')).toBe(null);\n    });\n\n    function assertSize(actual: string, expected: number) {\n      const actualNumber = parseFloat(actual);\n      expect(Math.abs(actualNumber - expected)).toBeLessThanOrEqual(0.01);\n    }\n\n    function assertBubblePositionVariables(\n      bubble: HTMLElement,\n      tabList: HTMLElement,\n      activeTab: HTMLElement,\n    ) {\n      const tabRect = activeTab.getBoundingClientRect();\n      const tabListRect = tabList.getBoundingClientRect();\n      const { width: tabWidth, height: tabHeight } = getCssDimensions(activeTab);\n      const { width: tabListWidth, height: tabListHeight } = getCssDimensions(tabList);\n      const scaleX = tabListWidth > 0 ? tabListRect.width / tabListWidth : 1;\n      const scaleY = tabListHeight > 0 ? tabListRect.height / tabListHeight : 1;\n\n      const relativeLeft =\n        (tabRect.left - tabListRect.left) / scaleX + tabList.scrollLeft - tabList.clientLeft;\n      const relativeTop =\n        (tabRect.top - tabListRect.top) / scaleY + tabList.scrollTop - tabList.clientTop;\n      const relativeRight = tabList.scrollWidth - relativeLeft - tabWidth;\n      const relativeBottom = tabList.scrollHeight - relativeTop - tabHeight;\n\n      const bubbleComputedStyle = window.getComputedStyle(bubble);\n      const actualLeft = bubbleComputedStyle.getPropertyValue('--active-tab-left');\n      const actualRight = bubbleComputedStyle.getPropertyValue('--active-tab-right');\n      const actualTop = bubbleComputedStyle.getPropertyValue('--active-tab-top');\n      const actualBottom = bubbleComputedStyle.getPropertyValue('--active-tab-bottom');\n      const actualWidth = bubbleComputedStyle.getPropertyValue('--active-tab-width');\n      const actualHeight = bubbleComputedStyle.getPropertyValue('--active-tab-height');\n\n      assertSize(actualLeft, relativeLeft);\n      assertSize(actualRight, relativeRight);\n      assertSize(actualTop, relativeTop);\n      assertSize(actualBottom, relativeBottom);\n      assertSize(actualWidth, tabWidth);\n      assertSize(actualHeight, tabHeight);\n    }\n\n    it('should set CSS variables corresponding to the active tab', async () => {\n      await render(\n        <Tabs.Root value={2}>\n          <Tabs.List>\n            <Tabs.Tab value={1}>One</Tabs.Tab>\n            <Tabs.Tab value={2}>Two</Tabs.Tab>\n            <Tabs.Tab value={3}>Three</Tabs.Tab>\n            <Tabs.Indicator data-testid=\"bubble\" />\n          </Tabs.List>\n        </Tabs.Root>,\n      );\n\n      const bubble = screen.getByTestId('bubble');\n      const tabs = screen.getAllByRole('tab');\n      const activeTab = tabs[1];\n      const tabList = screen.getByRole('tablist');\n\n      await waitFor(() => {\n        assertBubblePositionVariables(bubble, tabList, activeTab);\n      });\n    });\n\n    it('should update the position and movement variables when the active tab changes', async () => {\n      const { setProps } = await render(\n        <Tabs.Root value={2}>\n          <Tabs.List>\n            <Tabs.Tab value={1}>One</Tabs.Tab>\n            <Tabs.Tab value={2}>Two</Tabs.Tab>\n            <Tabs.Tab value={3}>Three</Tabs.Tab>\n            <Tabs.Indicator data-testid=\"bubble\" />\n          </Tabs.List>\n        </Tabs.Root>,\n      );\n\n      await setProps({ value: 3 });\n\n      const bubble = screen.getByTestId('bubble');\n      const tabs = screen.getAllByRole('tab');\n      let activeTab = tabs[2];\n      const tabList = screen.getByRole('tablist');\n\n      assertBubblePositionVariables(bubble, tabList, activeTab);\n\n      await setProps({ value: 1 });\n      activeTab = tabs[0];\n      await waitFor(() => {\n        assertBubblePositionVariables(bubble, tabList, activeTab);\n      });\n    });\n\n    it('should update the position variables when the tab list is resized', async () => {\n      const { setProps } = await render(\n        <Tabs.Root value={1} style={{ width: '400px' }}>\n          <Tabs.List style={{ display: 'flex' }}>\n            <Tabs.Tab value={1} style={{ flex: '1 1 auto' }}>\n              One\n            </Tabs.Tab>\n            <Tabs.Tab value={2} style={{ flex: '1 1 auto' }}>\n              Two\n            </Tabs.Tab>\n            <Tabs.Indicator data-testid=\"bubble\" />\n          </Tabs.List>\n        </Tabs.Root>,\n      );\n\n      const bubble = screen.getByTestId('bubble');\n      const tabs = screen.getAllByRole('tab');\n      const activeTab = tabs[0];\n      const tabList = screen.getByRole('tablist');\n\n      assertBubblePositionVariables(bubble, tabList, activeTab);\n\n      await setProps({\n        style: { width: '800px' },\n      });\n\n      await waitFor(() => {\n        assertBubblePositionVariables(bubble, tabList, activeTab);\n      });\n    });\n\n    it('should account for scroll and border when the tab list is transformed', async () => {\n      await render(\n        <div style={{ transform: 'scale(1.5)' }}>\n          <Tabs.Root value={3}>\n            <Tabs.List\n              data-testid=\"tab-list\"\n              style={{\n                width: '240px',\n                display: 'flex',\n                gap: '8px',\n                overflowX: 'auto',\n                border: '6px solid black',\n                padding: '4px',\n              }}\n            >\n              <Tabs.Tab value={1} style={{ flex: '0 0 120px' }}>\n                One\n              </Tabs.Tab>\n              <Tabs.Tab value={2} style={{ flex: '0 0 120px' }}>\n                Two\n              </Tabs.Tab>\n              <Tabs.Tab value={3} style={{ flex: '0 0 120px' }}>\n                Three\n              </Tabs.Tab>\n              <Tabs.Tab value={4} style={{ flex: '0 0 120px' }}>\n                Four\n              </Tabs.Tab>\n              <Tabs.Tab value={5} style={{ flex: '0 0 120px' }}>\n                Five\n              </Tabs.Tab>\n              <Tabs.Indicator data-testid=\"bubble\" />\n            </Tabs.List>\n          </Tabs.Root>\n        </div>,\n      );\n\n      const bubble = screen.getByTestId('bubble');\n      const tabList = screen.getByTestId('tab-list');\n      const activeTab = screen.getAllByRole('tab')[2];\n\n      tabList.scrollLeft = 80;\n\n      await waitFor(() => {\n        assertBubblePositionVariables(bubble, tabList, activeTab);\n      });\n    });\n\n    it('updates position when a different tab resizes', async () => {\n      await render(\n        <Tabs.Root value={2}>\n          <Tabs.List\n            data-testid=\"tab-list\"\n            style={{ width: '300px', display: 'flex', overflow: 'hidden' }}\n          >\n            <Tabs.Tab data-testid=\"first-tab\" value={1} style={{ width: '100px', flexShrink: 0 }}>\n              One\n            </Tabs.Tab>\n            <Tabs.Tab value={2} style={{ width: '100px', flexShrink: 0 }}>\n              Two\n            </Tabs.Tab>\n            <Tabs.Tab value={3} style={{ width: '100px', flexShrink: 0 }}>\n              Three\n            </Tabs.Tab>\n            <Tabs.Indicator data-testid=\"bubble\" />\n          </Tabs.List>\n        </Tabs.Root>,\n      );\n\n      const bubble = screen.getByTestId('bubble');\n      const tabList = screen.getByTestId('tab-list');\n      const firstTab = screen.getByTestId('first-tab');\n      const activeTab = screen.getAllByRole('tab')[1];\n\n      await waitFor(() => {\n        assertBubblePositionVariables(bubble, tabList, activeTab);\n      });\n\n      firstTab.setAttribute('style', 'width: 140px; flex-shrink: 0;');\n\n      await waitFor(() => {\n        assertBubblePositionVariables(bubble, tabList, activeTab);\n      });\n    });\n\n    it('updates position when a new tab is inserted and then resized', async () => {\n      function TestTabs({ insertedTabWidth }: { insertedTabWidth: number | null }) {\n        return (\n          <Tabs.Root value={2}>\n            <Tabs.List\n              data-testid=\"tab-list\"\n              style={{ width: '320px', display: 'flex', overflow: 'hidden' }}\n            >\n              {insertedTabWidth != null && (\n                <Tabs.Tab\n                  data-testid=\"inserted-tab\"\n                  value={0}\n                  style={{ width: `${insertedTabWidth}px`, flexShrink: 0 }}\n                >\n                  Inserted\n                </Tabs.Tab>\n              )}\n              <Tabs.Tab value={1} style={{ width: '100px', flexShrink: 0 }}>\n                One\n              </Tabs.Tab>\n              <Tabs.Tab value={2} style={{ width: '100px', flexShrink: 0 }}>\n                Two\n              </Tabs.Tab>\n              <Tabs.Tab value={3} style={{ width: '100px', flexShrink: 0 }}>\n                Three\n              </Tabs.Tab>\n              <Tabs.Indicator data-testid=\"bubble\" />\n            </Tabs.List>\n          </Tabs.Root>\n        );\n      }\n\n      const { setProps } = await render(<TestTabs insertedTabWidth={null} />);\n\n      const bubble = screen.getByTestId('bubble');\n      const tabList = screen.getByTestId('tab-list');\n\n      await waitFor(() => {\n        assertBubblePositionVariables(bubble, tabList, screen.getByRole('tab', { selected: true }));\n      });\n\n      await setProps({ insertedTabWidth: 60 });\n\n      const insertedTab = screen.getByTestId('inserted-tab');\n\n      await waitFor(() => {\n        assertBubblePositionVariables(bubble, tabList, screen.getByRole('tab', { selected: true }));\n      });\n\n      insertedTab.setAttribute('style', 'width: 120px; flex-shrink: 0;');\n\n      await waitFor(() => {\n        assertBubblePositionVariables(bubble, tabList, screen.getByRole('tab', { selected: true }));\n      });\n    });\n\n    it('updates all indicators when a different tab resizes', async () => {\n      await render(\n        <Tabs.Root value={2}>\n          <Tabs.List\n            data-testid=\"tab-list\"\n            style={{ width: '300px', display: 'flex', overflow: 'hidden' }}\n          >\n            <Tabs.Tab data-testid=\"first-tab\" value={1} style={{ width: '100px', flexShrink: 0 }}>\n              One\n            </Tabs.Tab>\n            <Tabs.Tab value={2} style={{ width: '100px', flexShrink: 0 }}>\n              Two\n            </Tabs.Tab>\n            <Tabs.Tab value={3} style={{ width: '100px', flexShrink: 0 }}>\n              Three\n            </Tabs.Tab>\n            <Tabs.Indicator data-testid=\"bubble-1\" />\n            <Tabs.Indicator data-testid=\"bubble-2\" />\n          </Tabs.List>\n        </Tabs.Root>,\n      );\n\n      const bubble1 = screen.getByTestId('bubble-1');\n      const bubble2 = screen.getByTestId('bubble-2');\n      const tabList = screen.getByTestId('tab-list');\n      const firstTab = screen.getByTestId('first-tab');\n      const activeTab = screen.getAllByRole('tab')[1];\n\n      await waitFor(() => {\n        assertBubblePositionVariables(bubble1, tabList, activeTab);\n        assertBubblePositionVariables(bubble2, tabList, activeTab);\n      });\n\n      firstTab.setAttribute('style', 'width: 140px; flex-shrink: 0;');\n\n      await waitFor(() => {\n        assertBubblePositionVariables(bubble1, tabList, activeTab);\n        assertBubblePositionVariables(bubble2, tabList, activeTab);\n      });\n    });\n\n    it('perf: single tab resize does not fan out excessive indicator rerenders', async () => {\n      const renderIndicatorSpy = vi.fn();\n\n      const LoggingIndicator = React.forwardRef(function LoggingIndicator(\n        props: any & { renderSpy: () => void },\n        ref: React.ForwardedRef<HTMLSpanElement>,\n      ) {\n        const { renderSpy, state, ...other } = props;\n        renderSpy();\n        return <span {...other} ref={ref} />;\n      });\n\n      await render(\n        <Tabs.Root value={50}>\n          <Tabs.List\n            data-testid=\"tab-list\"\n            style={{ width: '1200px', display: 'flex', overflow: 'hidden' }}\n          >\n            {Array.from({ length: 100 }, (_, i) => (\n              <Tabs.Tab\n                data-testid={`tab-${i + 1}`}\n                key={i}\n                value={i + 1}\n                style={{ width: '120px', flexShrink: 0 }}\n              >\n                {i + 1}\n              </Tabs.Tab>\n            ))}\n            <Tabs.Indicator\n              data-testid=\"bubble\"\n              render={<LoggingIndicator renderSpy={renderIndicatorSpy} />}\n            />\n          </Tabs.List>\n        </Tabs.Root>,\n      );\n\n      const bubble = screen.getByTestId('bubble');\n      const tabList = screen.getByTestId('tab-list');\n\n      await waitFor(() => {\n        assertBubblePositionVariables(bubble, tabList, screen.getByRole('tab', { selected: true }));\n      });\n\n      const firstTab = screen.getByTestId('tab-1');\n      const initialRenderCount = renderIndicatorSpy.mock.calls.length;\n      firstTab.setAttribute('style', 'width: 180px; flex-shrink: 0;');\n\n      await waitFor(() => {\n        assertBubblePositionVariables(bubble, tabList, screen.getByRole('tab', { selected: true }));\n      });\n\n      // React strict mode doubles render calls in tests.\n      expect(renderIndicatorSpy.mock.calls.length - initialRenderCount).toBeLessThan(5);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/tabs/indicator/TabsIndicator.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useForcedRerendering } from '@base-ui/utils/useForcedRerendering';\nimport { useOnMount } from '@base-ui/utils/useOnMount';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { getCssDimensions } from '../../utils/getCssDimensions';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport type { TabsRoot, TabsRootState } from '../root/TabsRoot';\nimport { useTabsRootContext } from '../root/TabsRootContext';\nimport { tabsStateAttributesMapping } from '../root/stateAttributesMapping';\nimport { useTabsListContext } from '../list/TabsListContext';\nimport type { TabsTab } from '../tab/TabsTab';\nimport { script as prehydrationScript } from './prehydrationScript.min';\nimport { TabsIndicatorCssVars } from './TabsIndicatorCssVars';\nimport { useCSPContext } from '../../csp-provider/CSPContext';\n\nconst stateAttributesMapping = {\n  ...tabsStateAttributesMapping,\n  activeTabPosition: () => null,\n  activeTabSize: () => null,\n};\n\n/**\n * A visual indicator that can be styled to match the position of the currently active tab.\n * Renders a `<span>` element.\n *\n * Documentation: [Base UI Tabs](https://base-ui.com/react/components/tabs)\n */\nexport const TabsIndicator = React.forwardRef(function TabIndicator(\n  componentProps: TabsIndicator.Props,\n  forwardedRef: React.ForwardedRef<HTMLSpanElement>,\n) {\n  const { className, render, renderBeforeHydration = false, ...elementProps } = componentProps;\n\n  const { nonce } = useCSPContext();\n\n  const { getTabElementBySelectedValue, orientation, tabActivationDirection, value } =\n    useTabsRootContext();\n\n  const { tabsListElement, registerIndicatorUpdateListener } = useTabsListContext();\n\n  const [isMounted, setIsMounted] = React.useState(false);\n\n  useOnMount(() => setIsMounted(true));\n\n  const rerender = useForcedRerendering();\n\n  React.useEffect(() => {\n    return registerIndicatorUpdateListener(rerender);\n  }, [registerIndicatorUpdateListener, rerender]);\n\n  let left = 0;\n  let right = 0;\n  let top = 0;\n  let bottom = 0;\n  let width = 0;\n  let height = 0;\n\n  let isTabSelected = false;\n\n  if (value != null && tabsListElement != null) {\n    const activeTab = getTabElementBySelectedValue(value);\n    isTabSelected = true;\n\n    if (activeTab != null) {\n      const { width: computedWidth, height: computedHeight } = getCssDimensions(activeTab);\n      const { width: tabListWidth, height: tabListHeight } = getCssDimensions(tabsListElement);\n      const tabRect = activeTab.getBoundingClientRect();\n      const tabsListRect = tabsListElement.getBoundingClientRect();\n      const scaleX = tabListWidth > 0 ? tabsListRect.width / tabListWidth : 1;\n      const scaleY = tabListHeight > 0 ? tabsListRect.height / tabListHeight : 1;\n      const hasNonZeroScale =\n        Math.abs(scaleX) > Number.EPSILON && Math.abs(scaleY) > Number.EPSILON;\n\n      if (hasNonZeroScale) {\n        const tabLeftDelta = tabRect.left - tabsListRect.left;\n        const tabTopDelta = tabRect.top - tabsListRect.top;\n\n        left = tabLeftDelta / scaleX + tabsListElement.scrollLeft - tabsListElement.clientLeft;\n        top = tabTopDelta / scaleY + tabsListElement.scrollTop - tabsListElement.clientTop;\n      } else {\n        left = activeTab.offsetLeft;\n        top = activeTab.offsetTop;\n      }\n\n      width = computedWidth;\n      height = computedHeight;\n      right = tabsListElement.scrollWidth - left - width;\n      bottom = tabsListElement.scrollHeight - top - height;\n    }\n  }\n\n  const activeTabPosition = React.useMemo(\n    () =>\n      isTabSelected\n        ? {\n            left,\n            right,\n            top,\n            bottom,\n          }\n        : null,\n    [left, right, top, bottom, isTabSelected],\n  );\n\n  const activeTabSize = React.useMemo(\n    () =>\n      isTabSelected\n        ? {\n            width,\n            height,\n          }\n        : null,\n    [width, height, isTabSelected],\n  );\n\n  const style = React.useMemo(() => {\n    if (!isTabSelected) {\n      return undefined;\n    }\n\n    return {\n      [TabsIndicatorCssVars.activeTabLeft]: `${left}px`,\n      [TabsIndicatorCssVars.activeTabRight]: `${right}px`,\n      [TabsIndicatorCssVars.activeTabTop]: `${top}px`,\n      [TabsIndicatorCssVars.activeTabBottom]: `${bottom}px`,\n      [TabsIndicatorCssVars.activeTabWidth]: `${width}px`,\n      [TabsIndicatorCssVars.activeTabHeight]: `${height}px`,\n    } as React.CSSProperties;\n  }, [left, right, top, bottom, width, height, isTabSelected]);\n\n  const displayIndicator = isTabSelected && width > 0 && height > 0;\n\n  const state: TabsIndicatorState = {\n    orientation,\n    activeTabPosition,\n    activeTabSize,\n    tabActivationDirection,\n  };\n\n  const element = useRenderElement('span', componentProps, {\n    state,\n    ref: forwardedRef,\n    props: [\n      {\n        role: 'presentation',\n        style,\n        hidden: !displayIndicator, // do not display the indicator before the layout is settled\n      },\n      elementProps,\n      {\n        suppressHydrationWarning: true,\n      },\n    ],\n    stateAttributesMapping,\n  });\n\n  if (value == null) {\n    return null;\n  }\n\n  return (\n    <React.Fragment>\n      {element}\n      {!isMounted && renderBeforeHydration && (\n        <script\n          nonce={nonce}\n          // eslint-disable-next-line react/no-danger\n          dangerouslySetInnerHTML={{ __html: prehydrationScript }}\n          suppressHydrationWarning\n        />\n      )}\n    </React.Fragment>\n  );\n});\n\nexport interface TabsIndicatorState extends TabsRootState {\n  /**\n   * The active tab position.\n   */\n  activeTabPosition: TabsTab.Position | null;\n  /**\n   * The active tab size.\n   */\n  activeTabSize: TabsTab.Size | null;\n  /**\n   * The component orientation.\n   */\n  orientation: TabsRoot.Orientation;\n}\n\nexport interface TabsIndicatorProps extends BaseUIComponentProps<'span', TabsIndicatorState> {\n  /**\n   * Whether to render itself before React hydrates.\n   * This minimizes the time that the indicator isn’t visible after server-side rendering.\n   * @default false\n   */\n  renderBeforeHydration?: boolean | undefined;\n}\n\nexport namespace TabsIndicator {\n  export type State = TabsIndicatorState;\n  export type Props = TabsIndicatorProps;\n}\n"
  },
  {
    "path": "packages/react/src/tabs/indicator/TabsIndicatorCssVars.ts",
    "content": "export enum TabsIndicatorCssVars {\n  /**\n   * Indicates the distance on the left side from the parent's container if the tab is active.\n   * @type {number}\n   */\n  activeTabLeft = '--active-tab-left',\n  /**\n   * Indicates the distance on the right side from the parent's container if the tab is active.\n   * @type {number}\n   */\n  activeTabRight = '--active-tab-right',\n  /**\n   * Indicates the distance on the top side from the parent's container if the tab is active.\n   * @type {number}\n   */\n  activeTabTop = '--active-tab-top',\n  /**\n   * Indicates the distance on the bottom side from the parent's container if the tab is active.\n   * @type {number}\n   */\n  activeTabBottom = '--active-tab-bottom',\n  /**\n   * Indicates the width of the tab if it is active.\n   * @type {number}\n   */\n  activeTabWidth = '--active-tab-width',\n  /**\n   * Indicates the height of the tab if it is active.\n   * @type {number}\n   */\n  activeTabHeight = '--active-tab-height',\n}\n"
  },
  {
    "path": "packages/react/src/tabs/indicator/TabsIndicatorDataAttributes.ts",
    "content": "export enum TabsIndicatorDataAttributes {\n  /**\n   * Indicates the direction of the activation (based on the previous active tab).\n   * @type {'left' | 'right' | 'up' | 'down' | 'none'}\n   */\n  activationDirection = 'data-activation-direction',\n  /**\n   * Indicates the orientation of the tabs.\n   * @type {'horizontal' | 'vertical'}\n   */\n  orientation = 'data-orientation',\n}\n"
  },
  {
    "path": "packages/react/src/tabs/indicator/prehydrationScript.min.ts",
    "content": "// This file is autogenerated. Do not edit it directly.\n// To update it, modify the corresponding source file and run `pnpm inline-scripts`.\n\n// prettier-ignore\nexport const script = '!function(){const t=document.currentScript.previousElementSibling;if(!t)return;const e=t.closest(\\'[role=\"tablist\"]\\');if(!e)return;const i=e.querySelector(\"[data-active]\");if(!i)return;if(0===i.offsetWidth||0===e.offsetWidth)return;let o=0,n=0,h=0,l=0,r=0,f=0;function s(t){const e=getComputedStyle(t);let i=parseFloat(e.width)||0,o=parseFloat(e.height)||0;return(Math.round(i)!==t.offsetWidth||Math.round(o)!==t.offsetHeight)&&(i=t.offsetWidth,o=t.offsetHeight),{width:i,height:o}}if(null!=i&&null!=e){const{width:t,height:c}=s(i),{width:u,height:d}=s(e),a=i.getBoundingClientRect(),g=e.getBoundingClientRect(),p=u>0?g.width/u:1,b=d>0?g.height/d:1;if(Math.abs(p)>Number.EPSILON&&Math.abs(b)>Number.EPSILON){const t=a.left-g.left,i=a.top-g.top;o=t/p+e.scrollLeft-e.clientLeft,h=i/b+e.scrollTop-e.clientTop}else o=i.offsetLeft,h=i.offsetTop;r=t,f=c,n=e.scrollWidth-o-r,l=e.scrollHeight-h-f}function c(e,i){t.style.setProperty(`--active-tab-${e}`,`${i}px`)}c(\"left\",o),c(\"right\",n),c(\"top\",h),c(\"bottom\",l),c(\"width\",r),c(\"height\",f),r>0&&f>0&&t.removeAttribute(\"hidden\")}();';\n"
  },
  {
    "path": "packages/react/src/tabs/indicator/prehydrationScript.template.js",
    "content": "(function prehydration() {\n  const indicator = document.currentScript.previousElementSibling;\n  if (!indicator) {\n    return;\n  }\n\n  const tabsList = indicator.closest('[role=\"tablist\"]');\n  if (!tabsList) {\n    return;\n  }\n\n  const activeTab = tabsList.querySelector('[data-active]');\n  if (!activeTab) {\n    return;\n  }\n\n  if (activeTab.offsetWidth === 0 || tabsList.offsetWidth === 0) {\n    return;\n  }\n\n  let left = 0;\n  let right = 0;\n  let top = 0;\n  let bottom = 0;\n  let width = 0;\n  let height = 0;\n\n  function getCssDimensions(element) {\n    const css = getComputedStyle(element);\n    let cssWidth = parseFloat(css.width) || 0;\n    let cssHeight = parseFloat(css.height) || 0;\n    const shouldFallback =\n      Math.round(cssWidth) !== element.offsetWidth ||\n      Math.round(cssHeight) !== element.offsetHeight;\n\n    if (shouldFallback) {\n      cssWidth = element.offsetWidth;\n      cssHeight = element.offsetHeight;\n    }\n\n    return {\n      width: cssWidth,\n      height: cssHeight,\n    };\n  }\n\n  if (activeTab != null && tabsList != null) {\n    const { width: computedWidth, height: computedHeight } = getCssDimensions(activeTab);\n    const { width: tabsListWidth, height: tabsListHeight } = getCssDimensions(tabsList);\n    const tabRect = activeTab.getBoundingClientRect();\n    const tabsListRect = tabsList.getBoundingClientRect();\n    const scaleX = tabsListWidth > 0 ? tabsListRect.width / tabsListWidth : 1;\n    const scaleY = tabsListHeight > 0 ? tabsListRect.height / tabsListHeight : 1;\n    const hasNonZeroScale = Math.abs(scaleX) > Number.EPSILON && Math.abs(scaleY) > Number.EPSILON;\n\n    if (hasNonZeroScale) {\n      const tabLeftDelta = tabRect.left - tabsListRect.left;\n      const tabTopDelta = tabRect.top - tabsListRect.top;\n\n      left = tabLeftDelta / scaleX + tabsList.scrollLeft - tabsList.clientLeft;\n      top = tabTopDelta / scaleY + tabsList.scrollTop - tabsList.clientTop;\n    } else {\n      left = activeTab.offsetLeft;\n      top = activeTab.offsetTop;\n    }\n\n    width = computedWidth;\n    height = computedHeight;\n    right = tabsList.scrollWidth - left - width;\n    bottom = tabsList.scrollHeight - top - height;\n  }\n\n  function setProp(name, value) {\n    indicator.style.setProperty(`--active-tab-${name}`, `${value}px`);\n  }\n\n  setProp('left', left);\n  setProp('right', right);\n  setProp('top', top);\n  setProp('bottom', bottom);\n  setProp('width', width);\n  setProp('height', height);\n\n  if (width > 0 && height > 0) {\n    indicator.removeAttribute('hidden');\n  }\n})();\n"
  },
  {
    "path": "packages/react/src/tabs/list/TabsList.test.tsx",
    "content": "import { expect } from 'vitest';\nimport * as React from 'react';\nimport { act, screen } from '@mui/internal-test-utils';\nimport { Tabs } from '@base-ui/react/tabs';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Tabs.List />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Tabs.List />, () => ({\n    render: (node) => render(<Tabs.Root>{node}</Tabs.Root>),\n    refInstanceof: window.HTMLDivElement,\n  }));\n\n  describe('accessibility attributes', () => {\n    it('sets the aria-selected attribute on the active tab', async () => {\n      await render(\n        <Tabs.Root defaultValue={1}>\n          <Tabs.List>\n            <Tabs.Tab value={1}>Tab 1</Tabs.Tab>\n            <Tabs.Tab value={2}>Tab 2</Tabs.Tab>\n            <Tabs.Tab value={3}>Tab 3</Tabs.Tab>\n          </Tabs.List>\n        </Tabs.Root>,\n      );\n\n      const tab1 = screen.getByText('Tab 1');\n      const tab2 = screen.getByText('Tab 2');\n      const tab3 = screen.getByText('Tab 3');\n\n      expect(tab1).toHaveAttribute('aria-selected', 'true');\n      expect(tab2).toHaveAttribute('aria-selected', 'false');\n      expect(tab3).toHaveAttribute('aria-selected', 'false');\n\n      await act(async () => {\n        tab2.click();\n      });\n\n      expect(tab1).toHaveAttribute('aria-selected', 'false');\n      expect(tab2).toHaveAttribute('aria-selected', 'true');\n      expect(tab3).toHaveAttribute('aria-selected', 'false');\n\n      await act(async () => {\n        tab3.click();\n      });\n\n      expect(tab1).toHaveAttribute('aria-selected', 'false');\n      expect(tab2).toHaveAttribute('aria-selected', 'false');\n      expect(tab3).toHaveAttribute('aria-selected', 'true');\n\n      await act(async () => {\n        tab1.click();\n      });\n\n      expect(tab1).toHaveAttribute('aria-selected', 'true');\n      expect(tab2).toHaveAttribute('aria-selected', 'false');\n      expect(tab3).toHaveAttribute('aria-selected', 'false');\n    });\n  });\n\n  it('can be named via `aria-label`', async () => {\n    await render(\n      <Tabs.Root defaultValue={0}>\n        <Tabs.List aria-label=\"string label\">\n          <Tabs.Tab value={0} />\n        </Tabs.List>\n      </Tabs.Root>,\n    );\n\n    expect(screen.getByRole('tablist')).toHaveAccessibleName('string label');\n  });\n\n  it('can be named via `aria-labelledby`', async () => {\n    await render(\n      <React.Fragment>\n        <h3 id=\"label-id\">complex name</h3>\n        <Tabs.Root defaultValue={0}>\n          <Tabs.List aria-labelledby=\"label-id\">\n            <Tabs.Tab value={0} />\n          </Tabs.List>\n        </Tabs.Root>\n      </React.Fragment>,\n    );\n\n    expect(screen.getByRole('tablist')).toHaveAccessibleName('complex name');\n  });\n});\n"
  },
  {
    "path": "packages/react/src/tabs/list/TabsList.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { BaseUIComponentProps, HTMLProps } from '../../utils/types';\nimport type { TabsRoot, TabsRootState } from '../root/TabsRoot';\nimport { CompositeRoot } from '../../composite/root/CompositeRoot';\nimport { tabsStateAttributesMapping } from '../root/stateAttributesMapping';\nimport { useTabsRootContext } from '../root/TabsRootContext';\nimport type { TabsTab } from '../tab/TabsTab';\nimport { TabsListContext } from './TabsListContext';\nimport { EMPTY_ARRAY } from '../../utils/constants';\n\n/**\n * Groups the individual tab buttons.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Tabs](https://base-ui.com/react/components/tabs)\n */\nexport const TabsList = React.forwardRef(function TabsList(\n  componentProps: TabsList.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const {\n    activateOnFocus = false,\n    className,\n    loopFocus = true,\n    render,\n    ...elementProps\n  } = componentProps;\n\n  const {\n    getTabElementBySelectedValue,\n    onValueChange,\n    orientation,\n    value,\n    setTabMap,\n    tabActivationDirection,\n  } = useTabsRootContext();\n\n  const [highlightedTabIndex, setHighlightedTabIndex] = React.useState(0);\n  const [tabsListElement, setTabsListElement] = React.useState<HTMLElement | null>(null);\n\n  const indicatorUpdateListenersRef = React.useRef(new Set<() => void>());\n  const tabResizeObserverElementsRef = React.useRef(new Set<HTMLElement>());\n  const resizeObserverRef = React.useRef<ResizeObserver | null>(null);\n\n  const notifyIndicatorUpdateListeners = useStableCallback(() => {\n    indicatorUpdateListenersRef.current.forEach((listener) => {\n      listener();\n    });\n  });\n\n  React.useEffect(() => {\n    if (typeof ResizeObserver === 'undefined') {\n      return undefined;\n    }\n\n    const resizeObserver = new ResizeObserver(() => {\n      if (!indicatorUpdateListenersRef.current.size) {\n        return;\n      }\n      notifyIndicatorUpdateListeners();\n    });\n\n    resizeObserverRef.current = resizeObserver;\n\n    if (tabsListElement) {\n      resizeObserver.observe(tabsListElement);\n    }\n\n    tabResizeObserverElementsRef.current.forEach((element) => {\n      resizeObserver.observe(element);\n    });\n\n    return () => {\n      resizeObserver.disconnect();\n      resizeObserverRef.current = null;\n    };\n  }, [tabsListElement, notifyIndicatorUpdateListeners]);\n\n  const registerIndicatorUpdateListener = useStableCallback((listener: () => void) => {\n    indicatorUpdateListenersRef.current.add(listener);\n    return () => {\n      indicatorUpdateListenersRef.current.delete(listener);\n    };\n  });\n\n  const registerTabResizeObserverElement = useStableCallback((element: HTMLElement) => {\n    tabResizeObserverElementsRef.current.add(element);\n    resizeObserverRef.current?.observe(element);\n    return () => {\n      tabResizeObserverElementsRef.current.delete(element);\n      resizeObserverRef.current?.unobserve(element);\n    };\n  });\n\n  const detectActivationDirection = useActivationDirectionDetector(\n    value, // the old value\n    orientation,\n    tabsListElement,\n    getTabElementBySelectedValue,\n  );\n\n  const onTabActivation = useStableCallback(\n    (newValue: TabsTab.Value, eventDetails: TabsRoot.ChangeEventDetails) => {\n      if (newValue !== value) {\n        const activationDirection = detectActivationDirection(newValue);\n        eventDetails.activationDirection = activationDirection;\n        onValueChange(newValue, eventDetails);\n      }\n    },\n  );\n\n  const state: TabsListState = {\n    orientation,\n    tabActivationDirection,\n  };\n\n  const defaultProps: HTMLProps = {\n    'aria-orientation': orientation === 'vertical' ? 'vertical' : undefined,\n    role: 'tablist',\n  };\n\n  const tabsListContextValue: TabsListContext = React.useMemo(\n    () => ({\n      activateOnFocus,\n      highlightedTabIndex,\n      registerIndicatorUpdateListener,\n      registerTabResizeObserverElement,\n      onTabActivation,\n      setHighlightedTabIndex,\n      tabsListElement,\n    }),\n    [\n      activateOnFocus,\n      highlightedTabIndex,\n      registerIndicatorUpdateListener,\n      registerTabResizeObserverElement,\n      onTabActivation,\n      setHighlightedTabIndex,\n      tabsListElement,\n    ],\n  );\n\n  return (\n    <TabsListContext.Provider value={tabsListContextValue}>\n      <CompositeRoot\n        render={render}\n        className={className}\n        state={state}\n        refs={[forwardedRef, setTabsListElement]}\n        props={[defaultProps, elementProps]}\n        stateAttributesMapping={tabsStateAttributesMapping}\n        highlightedIndex={highlightedTabIndex}\n        enableHomeAndEndKeys\n        loopFocus={loopFocus}\n        orientation={orientation}\n        onHighlightedIndexChange={setHighlightedTabIndex}\n        onMapChange={setTabMap}\n        disabledIndices={EMPTY_ARRAY as number[]}\n      />\n    </TabsListContext.Provider>\n  );\n});\n\nfunction getInset(tab: HTMLElement, tabsList: HTMLElement) {\n  const { left: tabLeft, top: tabTop } = tab.getBoundingClientRect();\n  const { left: listLeft, top: listTop } = tabsList.getBoundingClientRect();\n\n  const left = tabLeft - listLeft;\n  const top = tabTop - listTop;\n\n  return { left, top };\n}\n\nfunction useActivationDirectionDetector(\n  // the old value\n  activeTabValue: any,\n  orientation: TabsRoot.Orientation,\n  tabsListElement: HTMLElement | null,\n  getTabElement: (selectedValue: any) => HTMLElement | null,\n): (newValue: any) => TabsTab.ActivationDirection {\n  const [previousTabEdge, setPreviousTabEdge] = React.useState<number | null>(null);\n\n  useIsoLayoutEffect(() => {\n    // Whenever orientation changes, reset the state.\n    if (activeTabValue == null || tabsListElement == null) {\n      setPreviousTabEdge(null);\n      return;\n    }\n\n    const activeTab = getTabElement(activeTabValue);\n    if (activeTab == null) {\n      setPreviousTabEdge(null);\n      return;\n    }\n\n    const { left, top } = getInset(activeTab, tabsListElement);\n    setPreviousTabEdge(orientation === 'horizontal' ? left : top);\n  }, [orientation, getTabElement, tabsListElement, activeTabValue]);\n\n  return React.useCallback(\n    (newValue: any) => {\n      if (newValue === activeTabValue) {\n        return 'none';\n      }\n\n      if (newValue == null) {\n        setPreviousTabEdge(null);\n        return 'none';\n      }\n\n      if (newValue != null && tabsListElement != null) {\n        const activeTabElement = getTabElement(newValue);\n\n        if (activeTabElement != null) {\n          const { left, top } = getInset(activeTabElement, tabsListElement);\n\n          if (previousTabEdge == null) {\n            setPreviousTabEdge(orientation === 'horizontal' ? left : top);\n            return 'none';\n          }\n\n          if (orientation === 'horizontal') {\n            if (left < previousTabEdge) {\n              setPreviousTabEdge(left);\n              return 'left';\n            }\n            if (left > previousTabEdge) {\n              setPreviousTabEdge(left);\n              return 'right';\n            }\n          } else if (top < previousTabEdge) {\n            setPreviousTabEdge(top);\n            return 'up';\n          } else if (top > previousTabEdge) {\n            setPreviousTabEdge(top);\n            return 'down';\n          }\n        }\n      }\n\n      return 'none';\n    },\n    [getTabElement, orientation, previousTabEdge, tabsListElement, activeTabValue],\n  );\n}\n\nexport interface TabsListState extends TabsRootState {}\n\nexport interface TabsListProps extends BaseUIComponentProps<'div', TabsListState> {\n  /**\n   * Whether to automatically change the active tab on arrow key focus.\n   * Otherwise, tabs will be activated using <kbd>Enter</kbd> or <kbd>Space</kbd> key press.\n   * @default false\n   */\n  activateOnFocus?: boolean | undefined;\n  /**\n   * Whether to loop keyboard focus back to the first item\n   * when the end of the list is reached while using the arrow keys.\n   * @default true\n   */\n  loopFocus?: boolean | undefined;\n}\n\nexport namespace TabsList {\n  export type State = TabsListState;\n  export type Props = TabsListProps;\n}\n"
  },
  {
    "path": "packages/react/src/tabs/list/TabsListContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport type { TabsRoot } from '../root/TabsRoot';\n\nexport interface TabsListContext {\n  activateOnFocus: boolean;\n  highlightedTabIndex: number;\n  registerIndicatorUpdateListener: (listener: () => void) => () => void;\n  registerTabResizeObserverElement: (element: HTMLElement) => () => void;\n  onTabActivation: (newValue: any, eventDetails: TabsRoot.ChangeEventDetails) => void;\n  setHighlightedTabIndex: (index: number) => void;\n  tabsListElement: HTMLElement | null;\n}\n\nexport const TabsListContext = React.createContext<TabsListContext | undefined>(undefined);\n\nexport function useTabsListContext() {\n  const context = React.useContext(TabsListContext);\n  if (context === undefined) {\n    throw new Error(\n      'Base UI: TabsListContext is missing. TabsList parts must be placed within <Tabs.List>.',\n    );\n  }\n\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/tabs/list/TabsListDataAttributes.ts",
    "content": "export enum TabsListDataAttributes {\n  /**\n   * Indicates the direction of the activation (based on the previous active tab).\n   * @type {'left' | 'right' | 'up' | 'down' | 'none'}\n   */\n  activationDirection = 'data-activation-direction',\n  /**\n   * Indicates the orientation of the tabs.\n   * @type {'horizontal' | 'vertical'}\n   */\n  orientation = 'data-orientation',\n}\n"
  },
  {
    "path": "packages/react/src/tabs/panel/TabsPanel.test.tsx",
    "content": "import { afterEach, expect } from 'vitest';\nimport { Tabs } from '@base-ui/react/tabs';\nimport { screen, waitFor } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\n\ndescribe('<Tabs.Panel />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Tabs.Panel value=\"1\" keepMounted />, () => ({\n    render: (node) => render(<Tabs.Root>{node}</Tabs.Root>),\n    refInstanceof: window.HTMLDivElement,\n  }));\n\n  describe.skipIf(isJSDOM)('animations', () => {\n    afterEach(() => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = true;\n    });\n\n    it('triggers enter animation via data-starting-style when mounting', async () => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n      let transitionFinished = false;\n      const notifyTransitionFinished = () => {\n        transitionFinished = true;\n      };\n\n      const style = `\n        .animation-test-panel {\n          transition: opacity 1ms;\n        }\n\n        .animation-test-panel[data-starting-style],\n        .animation-test-panel[data-ending-style] {\n          opacity: 0;\n        }\n      `;\n\n      const { user } = await render(\n        <div>\n          {/* eslint-disable-next-line react/no-danger */}\n          <style dangerouslySetInnerHTML={{ __html: style }} />\n          <Tabs.Root defaultValue=\"one\">\n            <Tabs.List>\n              <Tabs.Tab value=\"one\">One</Tabs.Tab>\n              <Tabs.Tab value=\"two\">Two</Tabs.Tab>\n            </Tabs.List>\n            <Tabs.Panel value=\"one\">Panel one</Tabs.Panel>\n            <Tabs.Panel\n              className=\"animation-test-panel\"\n              data-testid=\"panel-two\"\n              onTransitionEnd={notifyTransitionFinished}\n              value=\"two\"\n            >\n              Panel two\n            </Tabs.Panel>\n          </Tabs.Root>\n        </div>,\n      );\n\n      expect(screen.queryByTestId('panel-two')).toBeNull();\n\n      await user.click(screen.getByRole('tab', { name: 'Two' }));\n\n      await waitFor(() => {\n        expect(transitionFinished).toBe(true);\n      });\n\n      expect(screen.getByTestId('panel-two')).not.toBeNull();\n    });\n\n    it('applies data-ending-style before unmount', async () => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n      const style = `\n        @keyframes test-anim {\n          to {\n            opacity: 0;\n          }\n        }\n\n        .animation-test-panel[data-ending-style] {\n          animation: test-anim 100ms;\n        }\n      `;\n\n      const { user } = await render(\n        <div>\n          {/* eslint-disable-next-line react/no-danger */}\n          <style dangerouslySetInnerHTML={{ __html: style }} />\n          <Tabs.Root defaultValue=\"one\">\n            <Tabs.List>\n              <Tabs.Tab value=\"one\">One</Tabs.Tab>\n              <Tabs.Tab value=\"two\">Two</Tabs.Tab>\n            </Tabs.List>\n            <Tabs.Panel className=\"animation-test-panel\" data-testid=\"panel-one\" value=\"one\">\n              Panel one\n            </Tabs.Panel>\n            <Tabs.Panel value=\"two\">Panel two</Tabs.Panel>\n          </Tabs.Root>\n        </div>,\n      );\n\n      expect(screen.getByTestId('panel-one')).not.toBeNull();\n\n      await user.click(screen.getByRole('tab', { name: 'Two' }));\n\n      await waitFor(() => {\n        const panel = screen.queryByTestId('panel-one');\n        expect(panel).not.toBeNull();\n        expect(panel).toHaveAttribute('data-ending-style');\n      });\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('panel-one')).toBeNull();\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/tabs/panel/TabsPanel.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { inertValue } from '@base-ui/utils/inertValue';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useBaseUiId } from '../../utils/useBaseUiId';\nimport type { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport { transitionStatusMapping } from '../../utils/stateAttributesMapping';\nimport { useOpenChangeComplete } from '../../utils/useOpenChangeComplete';\nimport { type TransitionStatus, useTransitionStatus } from '../../utils/useTransitionStatus';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useCompositeListItem } from '../../composite/list/useCompositeListItem';\nimport { tabsStateAttributesMapping } from '../root/stateAttributesMapping';\nimport { useTabsRootContext } from '../root/TabsRootContext';\nimport type { TabsRootState } from '../root/TabsRoot';\nimport type { TabsTab } from '../tab/TabsTab';\nimport { TabsPanelDataAttributes } from './TabsPanelDataAttributes';\n\nconst stateAttributesMapping: StateAttributesMapping<TabsPanelState> = {\n  ...tabsStateAttributesMapping,\n  ...transitionStatusMapping,\n};\n\n/**\n * A panel displayed when the corresponding tab is active.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Tabs](https://base-ui.com/react/components/tabs)\n */\nexport const TabsPanel = React.forwardRef(function TabPanel(\n  componentProps: TabsPanel.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { className, value, render, keepMounted = false, ...elementProps } = componentProps;\n\n  const {\n    value: selectedValue,\n    getTabIdByPanelValue,\n    orientation,\n    tabActivationDirection,\n    registerMountedTabPanel,\n    unregisterMountedTabPanel,\n  } = useTabsRootContext();\n\n  const id = useBaseUiId();\n\n  const metadata = React.useMemo(\n    () => ({\n      id,\n      value,\n    }),\n    [id, value],\n  );\n\n  const { ref: listItemRef, index } = useCompositeListItem<TabsPanel.Metadata>({\n    metadata,\n  });\n\n  const open = value === selectedValue;\n  const { mounted, transitionStatus, setMounted } = useTransitionStatus(open);\n  const hidden = !mounted;\n\n  const correspondingTabId = getTabIdByPanelValue(value);\n\n  const state: TabsPanelState = {\n    hidden,\n    orientation,\n    tabActivationDirection,\n    transitionStatus,\n  };\n\n  const panelRef = React.useRef<HTMLDivElement | null>(null);\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: [forwardedRef, listItemRef, panelRef],\n    props: [\n      {\n        'aria-labelledby': correspondingTabId,\n        hidden,\n        id,\n        role: 'tabpanel',\n        tabIndex: open ? 0 : -1,\n        inert: inertValue(!open),\n        [TabsPanelDataAttributes.index as string]: index,\n      },\n      elementProps,\n    ],\n    stateAttributesMapping,\n  });\n\n  useOpenChangeComplete({\n    open,\n    ref: panelRef,\n    onComplete() {\n      if (!open) {\n        setMounted(false);\n      }\n    },\n  });\n\n  useIsoLayoutEffect(() => {\n    if (hidden && !keepMounted) {\n      return undefined;\n    }\n\n    if (id == null) {\n      return undefined;\n    }\n\n    registerMountedTabPanel(value, id);\n    return () => {\n      unregisterMountedTabPanel(value, id);\n    };\n  }, [hidden, keepMounted, value, id, registerMountedTabPanel, unregisterMountedTabPanel]);\n\n  const shouldRender = keepMounted || mounted;\n  if (!shouldRender) {\n    return null;\n  }\n\n  return element;\n});\n\nexport interface TabsPanelMetadata {\n  id?: string | undefined;\n  value: TabsTab.Value;\n}\n\nexport interface TabsPanelState extends TabsRootState {\n  /**\n   * Whether the component is hidden.\n   */\n  hidden: boolean;\n  /**\n   * The transition status of the component.\n   */\n  transitionStatus: TransitionStatus;\n}\n\nexport interface TabsPanelProps extends BaseUIComponentProps<'div', TabsPanelState> {\n  /**\n   * The value of the TabPanel. It will be shown when the Tab with the corresponding value is active.\n   */\n  value: TabsTab.Value;\n  /**\n   * Whether to keep the HTML element in the DOM while the panel is hidden.\n   * @default false\n   */\n  keepMounted?: boolean | undefined;\n}\n\nexport namespace TabsPanel {\n  export type Metadata = TabsPanelMetadata;\n  export type State = TabsPanelState;\n  export type Props = TabsPanelProps;\n}\n"
  },
  {
    "path": "packages/react/src/tabs/panel/TabsPanelDataAttributes.ts",
    "content": "import { TransitionStatusDataAttributes } from '../../utils/stateAttributesMapping';\n\nexport enum TabsPanelDataAttributes {\n  /**\n   * Indicates the index of the tab panel.\n   */\n  index = 'data-index',\n  /**\n   * Indicates the direction of the activation (based on the previous active tab).\n   * @type {'left' | 'right' | 'up' | 'down' | 'none'}\n   */\n  activationDirection = 'data-activation-direction',\n  /**\n   * Indicates the orientation of the tabs.\n   * @type {'horizontal' | 'vertical'}\n   */\n  orientation = 'data-orientation',\n  /**\n   * Present when the panel is hidden.\n   */\n  hidden = 'data-hidden',\n  /**\n   * Present when the panel is animating in.\n   */\n  startingStyle = TransitionStatusDataAttributes.startingStyle,\n  /**\n   * Present when the panel is animating out.\n   */\n  endingStyle = TransitionStatusDataAttributes.endingStyle,\n}\n"
  },
  {
    "path": "packages/react/src/tabs/root/TabsRoot.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport { act, flushMicrotasks, fireEvent, screen, waitFor } from '@mui/internal-test-utils';\nimport { DirectionProvider, type TextDirection } from '@base-ui/react/direction-provider';\nimport { Popover } from '@base-ui/react/popover';\nimport { Dialog } from '@base-ui/react/dialog';\nimport { Tabs } from '@base-ui/react/tabs';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\n\ndescribe('<Tabs.Root />', () => {\n  const { render } = createRenderer();\n\n  beforeEach(function beforeHook({ skip }) {\n    const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);\n\n    // The test fails on Safari with just:\n    //\n    // container.scrollLeft = 200;\n    // expect(container.scrollLeft).toBe(200); 💥\n    if (isSafari) {\n      skip();\n    }\n  });\n\n  describeConformance(<Tabs.Root value={0} />, () => ({\n    render,\n    refInstanceof: window.HTMLDivElement,\n  }));\n\n  describe('prop: children', () => {\n    it('should accept a null child', async () => {\n      await render(\n        <Tabs.Root value={0}>\n          {null}\n          <Tabs.List>\n            <Tabs.Tab value={1} />\n          </Tabs.List>\n        </Tabs.Root>,\n      );\n\n      expect(screen.getAllByRole('tab')).toHaveLength(1);\n    });\n\n    it('should support empty children', async () => {\n      await render(<Tabs.Root value={1} />);\n    });\n\n    it('puts the selected child in tab order', async () => {\n      const { setProps } = await render(\n        <Tabs.Root value={1}>\n          <Tabs.List>\n            <Tabs.Tab value={0} />\n            <Tabs.Tab value={1} />\n          </Tabs.List>\n        </Tabs.Root>,\n      );\n\n      expect(screen.getAllByRole('tab').map((tab) => tab.tabIndex)).toEqual([-1, 0]);\n\n      await setProps({ value: 0 });\n\n      expect(screen.getAllByRole('tab').map((tab) => tab.tabIndex)).toEqual([0, -1]);\n    });\n\n    it('sets the aria-labelledby attribute on tab panels to the corresponding tab id', async () => {\n      await render(\n        <Tabs.Root defaultValue=\"tab-0\">\n          <Tabs.List>\n            <Tabs.Tab value=\"tab-0\" />\n            <Tabs.Tab value=\"tab-1\" id=\"explicit-tab-id-1\" />\n            <Tabs.Tab value=\"tab-2\" />\n            <Tabs.Tab value=\"tab-3\" id=\"explicit-tab-id-3\" />\n          </Tabs.List>\n          <Tabs.Panel value=\"tab-1\" keepMounted />\n          <Tabs.Panel value=\"tab-0\" keepMounted />\n          <Tabs.Panel value=\"tab-2\" keepMounted />\n          <Tabs.Panel value=\"tab-3\" keepMounted />\n        </Tabs.Root>,\n      );\n\n      const tabs = screen.getAllByRole('tab');\n      const tabPanels = screen.getAllByRole('tabpanel', { hidden: true });\n\n      expect(tabPanels[0]).toHaveAttribute('aria-labelledby', tabs[1].id);\n      expect(tabPanels[1]).toHaveAttribute('aria-labelledby', tabs[0].id);\n      expect(tabPanels[2]).toHaveAttribute('aria-labelledby', tabs[2].id);\n      expect(tabPanels[3]).toHaveAttribute('aria-labelledby', tabs[3].id);\n    });\n\n    it('sets the aria-controls attribute on tabs to the corresponding tab panel id', async () => {\n      await render(\n        <Tabs.Root defaultValue=\"tab-0\">\n          <Tabs.List>\n            <Tabs.Tab value=\"tab-0\" />\n            <Tabs.Tab value=\"tab-1\" id=\"explicit-tab-id-1\" />\n            <Tabs.Tab value=\"tab-2\" />\n            <Tabs.Tab value=\"tab-3\" id=\"explicit-tab-id-3\" />\n          </Tabs.List>\n          <Tabs.Panel value=\"tab-1\" keepMounted />\n          <Tabs.Panel value=\"tab-0\" keepMounted />\n          <Tabs.Panel value=\"tab-2\" keepMounted />\n          <Tabs.Panel value=\"tab-3\" keepMounted />\n        </Tabs.Root>,\n      );\n\n      const tabs = screen.getAllByRole('tab');\n      const tabPanels = screen.getAllByRole('tabpanel', { hidden: true });\n\n      expect(tabs[0]).toHaveAttribute('aria-controls', tabPanels[1].id);\n      expect(tabs[1]).toHaveAttribute('aria-controls', tabPanels[0].id);\n      expect(tabs[2]).toHaveAttribute('aria-controls', tabPanels[2].id);\n      expect(tabs[3]).toHaveAttribute('aria-controls', tabPanels[3].id);\n    });\n\n    it('sets aria-controls on the first tab when no value is provided', async () => {\n      await render(\n        <Tabs.Root>\n          <Tabs.List>\n            <Tabs.Tab value={0} />\n            <Tabs.Tab value={1} />\n          </Tabs.List>\n          <Tabs.Panel value={0} keepMounted />\n          <Tabs.Panel value={1} keepMounted />\n        </Tabs.Root>,\n      );\n\n      const tabs = screen.getAllByRole('tab');\n      const tabPanels = screen.getAllByRole('tabpanel', { hidden: true });\n\n      expect(tabs[0]).toHaveAttribute('aria-controls', tabPanels[0].id);\n      expect(tabs[1]).toHaveAttribute('aria-controls', tabPanels[1].id);\n      expect(tabPanels[0]).toHaveAttribute('aria-labelledby', tabs[0].id);\n      expect(tabPanels[1]).toHaveAttribute('aria-labelledby', tabs[1].id);\n    });\n\n    it('syncs aria-controls to the mounted tab panel when keepMounted is false', async () => {\n      const { user } = await render(\n        <Tabs.Root defaultValue=\"tab-0\">\n          <Tabs.List>\n            <Tabs.Tab value=\"tab-0\">Tab 0</Tabs.Tab>\n            <Tabs.Tab value=\"tab-1\">Tab 1</Tabs.Tab>\n          </Tabs.List>\n          <Tabs.Panel value=\"tab-0\">Panel 0</Tabs.Panel>\n          <Tabs.Panel value=\"tab-1\">Panel 1</Tabs.Panel>\n        </Tabs.Root>,\n      );\n\n      const tabs = screen.getAllByRole('tab');\n      const [firstTabPanel] = screen.getAllByRole('tabpanel');\n\n      expect(tabs[0]).toHaveAttribute('aria-controls', firstTabPanel.id);\n      expect(tabs[1]).not.toHaveAttribute('aria-controls');\n\n      await user.click(tabs[1]);\n\n      await waitFor(() => {\n        const [secondTabPanel] = screen.getAllByRole('tabpanel');\n\n        expect(secondTabPanel).toHaveTextContent('Panel 1');\n        expect(tabs[0]).not.toHaveAttribute('aria-controls');\n        expect(tabs[1]).toHaveAttribute('aria-controls', secondTabPanel.id);\n      });\n    });\n  });\n\n  describe('prop: value', () => {\n    it('should pass selected prop to children', async () => {\n      const tabs = (\n        <Tabs.Root value={1}>\n          <Tabs.List>\n            <Tabs.Tab value={0} />\n            <Tabs.Tab value={1} />\n          </Tabs.List>\n        </Tabs.Root>\n      );\n\n      await render(tabs);\n      const tabElements = screen.getAllByRole('tab');\n      expect(tabElements[0]).toHaveAttribute('aria-selected', 'false');\n      expect(tabElements[1]).toHaveAttribute('aria-selected', 'true');\n    });\n\n    it('should support values of different types', async () => {\n      const tabValues = [0, '1', { value: 2 }, () => 3, Symbol('4'), /5/];\n\n      await render(\n        <Tabs.Root>\n          <Tabs.List>\n            {tabValues.map((value, index) => (\n              <Tabs.Tab key={index} value={value} />\n            ))}\n          </Tabs.List>\n          {tabValues.map((value, index) => (\n            <Tabs.Panel key={index} value={value} keepMounted />\n          ))}\n        </Tabs.Root>,\n      );\n\n      const tabElements = screen.getAllByRole('tab');\n      const tabPanelElements = screen.getAllByRole('tabpanel', { hidden: true });\n\n      await Promise.allSettled(\n        tabValues.map(async (value, index) => {\n          expect(tabPanelElements[index]).toHaveAttribute('aria-labelledby', tabElements[index].id);\n\n          await act(() => {\n            tabElements[index].click();\n          });\n\n          expect(tabPanelElements[index]).not.toHaveAttribute('hidden');\n        }),\n      );\n    });\n  });\n\n  describe('disabled tabs', () => {\n    it('should select the second tab when the first one is disabled', async () => {\n      await render(\n        <Tabs.Root>\n          <Tabs.List>\n            <Tabs.Tab value={0} disabled>\n              Disabled tab\n            </Tabs.Tab>\n            <Tabs.Tab value={1}>Enabled tab</Tabs.Tab>\n          </Tabs.List>\n          <Tabs.Panel value={0} keepMounted>\n            Disabled panel\n          </Tabs.Panel>\n          <Tabs.Panel value={1} keepMounted>\n            Enabled panel\n          </Tabs.Panel>\n        </Tabs.Root>,\n      );\n\n      const [disabledTab, enabledTab] = screen.getAllByRole('tab');\n      const [disabledPanel, enabledPanel] = screen.getAllByRole('tabpanel', { hidden: true });\n\n      expect(disabledTab).toHaveAttribute('aria-selected', 'false');\n      expect(enabledTab).toHaveAttribute('aria-selected', 'true');\n      expect(disabledPanel).toHaveAttribute('hidden');\n      expect(enabledPanel).not.toHaveAttribute('hidden');\n      expect(enabledPanel).toHaveTextContent('Enabled panel');\n    });\n\n    it('should select the third tab when first two tabs are disabled', async () => {\n      await render(\n        <Tabs.Root>\n          <Tabs.List>\n            <Tabs.Tab value={0} disabled data-testid=\"tab-0\">\n              Tab 0\n            </Tabs.Tab>\n            <Tabs.Tab value={1} disabled data-testid=\"tab-1\">\n              Tab 1\n            </Tabs.Tab>\n            <Tabs.Tab value={2} data-testid=\"tab-2\">\n              Tab 2\n            </Tabs.Tab>\n            <Tabs.Tab value={3} data-testid=\"tab-3\">\n              Tab 3\n            </Tabs.Tab>\n          </Tabs.List>\n          <Tabs.Panel value={0}>Panel 0</Tabs.Panel>\n          <Tabs.Panel value={1}>Panel 1</Tabs.Panel>\n          <Tabs.Panel value={2}>Panel 2</Tabs.Panel>\n          <Tabs.Panel value={3}>Panel 3</Tabs.Panel>\n        </Tabs.Root>,\n      );\n\n      const tabs = screen.getAllByRole('tab');\n\n      // The first non-disabled tab (tab 2) should be selected\n      expect(tabs[2]).toHaveAttribute('aria-selected', 'true');\n      expect(tabs[0]).toHaveAttribute('aria-selected', 'false');\n      expect(tabs[1]).toHaveAttribute('aria-selected', 'false');\n      expect(tabs[3]).toHaveAttribute('aria-selected', 'false');\n    });\n\n    it('should still honor explicit defaultValue even if it points to a disabled tab', async () => {\n      await render(\n        <Tabs.Root defaultValue={0}>\n          <Tabs.List>\n            <Tabs.Tab value={0} disabled data-testid=\"tab-0\">\n              Tab 0\n            </Tabs.Tab>\n            <Tabs.Tab value={1} data-testid=\"tab-1\">\n              Tab 1\n            </Tabs.Tab>\n            <Tabs.Tab value={2} data-testid=\"tab-2\">\n              Tab 2\n            </Tabs.Tab>\n          </Tabs.List>\n          <Tabs.Panel value={0}>Panel 0</Tabs.Panel>\n          <Tabs.Panel value={1}>Panel 1</Tabs.Panel>\n          <Tabs.Panel value={2}>Panel 2</Tabs.Panel>\n        </Tabs.Root>,\n      );\n\n      const tabs = screen.getAllByRole('tab');\n\n      // The explicitly set disabled tab should be selected\n      expect(tabs[0]).toHaveAttribute('aria-selected', 'true');\n      expect(tabs[1]).toHaveAttribute('aria-selected', 'false');\n      expect(tabs[2]).toHaveAttribute('aria-selected', 'false');\n    });\n\n    it('should still honor explicit value prop even if it points to a disabled tab', async () => {\n      await render(\n        <Tabs.Root value={0}>\n          <Tabs.List>\n            <Tabs.Tab value={0} disabled data-testid=\"tab-0\">\n              Tab 0\n            </Tabs.Tab>\n            <Tabs.Tab value={1} data-testid=\"tab-1\">\n              Tab 1\n            </Tabs.Tab>\n            <Tabs.Tab value={2} data-testid=\"tab-2\">\n              Tab 2\n            </Tabs.Tab>\n          </Tabs.List>\n          <Tabs.Panel value={0}>Panel 0</Tabs.Panel>\n          <Tabs.Panel value={1}>Panel 1</Tabs.Panel>\n          <Tabs.Panel value={2}>Panel 2</Tabs.Panel>\n        </Tabs.Root>,\n      );\n\n      const tabs = screen.getAllByRole('tab');\n\n      // The explicitly set disabled tab should be selected\n      expect(tabs[0]).toHaveAttribute('aria-selected', 'true');\n      expect(tabs[1]).toHaveAttribute('aria-selected', 'false');\n      expect(tabs[2]).toHaveAttribute('aria-selected', 'false');\n    });\n\n    it('does not set tabIndex=0 on disabled tabs when they are programmatically selected', async () => {\n      const { setProps } = await render(\n        <Tabs.Root value={1}>\n          <Tabs.List>\n            <Tabs.Tab value={0} disabled>\n              Tab 0\n            </Tabs.Tab>\n            <Tabs.Tab value={1}>Tab 1</Tabs.Tab>\n            <Tabs.Tab value={2}>Tab 2</Tabs.Tab>\n          </Tabs.List>\n          <Tabs.Panel value={0}>Panel 0</Tabs.Panel>\n          <Tabs.Panel value={1}>Panel 1</Tabs.Panel>\n          <Tabs.Panel value={2}>Panel 2</Tabs.Panel>\n        </Tabs.Root>,\n      );\n\n      const tabs = screen.getAllByRole('tab');\n\n      // Initially, tab 1 is selected and should be highlighted (tabIndex=0)\n      expect(tabs[1]).toHaveAttribute('tabindex', '0');\n      expect(tabs[0]).toHaveAttribute('tabindex', '-1');\n      expect(tabs[2]).toHaveAttribute('tabindex', '-1');\n\n      // Programmatically select the disabled tab 0\n      await setProps({ value: 0 });\n      await flushMicrotasks();\n\n      // The disabled tab should be selected but NOT highlighted (tabIndex should remain -1)\n      expect(tabs[0]).toHaveAttribute('aria-selected', 'true');\n      expect(tabs[0]).toHaveAttribute('tabindex', '-1');\n\n      // The previously highlighted tab should retain the highlight\n      expect(tabs[1]).toHaveAttribute('tabindex', '0');\n    });\n\n    it('does not select any tab when all tabs are disabled', async () => {\n      await render(\n        <Tabs.Root>\n          <Tabs.List>\n            <Tabs.Tab value={0} disabled>\n              Tab 0\n            </Tabs.Tab>\n            <Tabs.Tab value={1} disabled>\n              Tab 1\n            </Tabs.Tab>\n            <Tabs.Tab value={2} disabled>\n              Tab 2\n            </Tabs.Tab>\n          </Tabs.List>\n          <Tabs.Panel value={0} keepMounted>\n            Panel 0\n          </Tabs.Panel>\n          <Tabs.Panel value={1} keepMounted>\n            Panel 1\n          </Tabs.Panel>\n          <Tabs.Panel value={2} keepMounted>\n            Panel 2\n          </Tabs.Panel>\n        </Tabs.Root>,\n      );\n\n      const tabs = screen.getAllByRole('tab');\n      const panels = screen.getAllByRole('tabpanel', { hidden: true });\n\n      // No tab should be selected\n      expect(tabs[0]).toHaveAttribute('aria-selected', 'false');\n      expect(tabs[1]).toHaveAttribute('aria-selected', 'false');\n      expect(tabs[2]).toHaveAttribute('aria-selected', 'false');\n\n      // All panels should be hidden\n      expect(panels[0]).toHaveAttribute('hidden');\n      expect(panels[1]).toHaveAttribute('hidden');\n      expect(panels[2]).toHaveAttribute('hidden');\n    });\n  });\n\n  describe('prop: onValueChange', () => {\n    it('when `activateOnFocus = true` should call onValueChange on pointerdown', async () => {\n      const handleChange = vi.fn();\n      const handlePointerDown = vi.fn();\n      const { user } = await render(\n        <Tabs.Root value={0} onValueChange={handleChange}>\n          <Tabs.List activateOnFocus>\n            <Tabs.Tab value={0} />\n            <Tabs.Tab value={1} onPointerDown={handlePointerDown} />\n          </Tabs.List>\n        </Tabs.Root>,\n      );\n\n      await user.pointer({ keys: '[MouseLeft>]', target: screen.getAllByRole('tab')[1] });\n      expect(handleChange.mock.calls.length).toBe(1);\n      expect(handlePointerDown.mock.calls.length).toBe(1);\n    });\n\n    it.skipIf(isJSDOM)('should call onValueChange when clicking', async () => {\n      const handleChange = vi.fn();\n\n      await render(\n        <Tabs.Root value={0} onValueChange={handleChange}>\n          <Tabs.List>\n            <Tabs.Tab value={0} />\n            <Tabs.Tab value={1} />\n          </Tabs.List>\n        </Tabs.Root>,\n      );\n\n      fireEvent.click(screen.getAllByRole('tab')[1]);\n      expect(handleChange.mock.calls.length).toBe(1);\n      expect(handleChange.mock.calls[0][0]).toBe(1);\n      expect(handleChange.mock.calls[0][1].activationDirection).toBe('right');\n    });\n\n    it('should not call onValueChange on non-main button clicks', async () => {\n      const handleChange = vi.fn();\n\n      await render(\n        <Tabs.Root value={0} onValueChange={handleChange}>\n          <Tabs.List>\n            <Tabs.Tab value={0} />\n            <Tabs.Tab value={1} />\n          </Tabs.List>\n        </Tabs.Root>,\n      );\n\n      fireEvent.click(screen.getAllByRole('tab')[1], { button: 2 });\n      expect(handleChange.mock.calls.length).toBe(0);\n    });\n\n    it('should not call onValueChange when already active', async () => {\n      const handleChange = vi.fn();\n\n      await render(\n        <Tabs.Root value={0} onValueChange={handleChange}>\n          <Tabs.List>\n            <Tabs.Tab value={0} />\n            <Tabs.Tab value={1} />\n          </Tabs.List>\n        </Tabs.Root>,\n      );\n\n      fireEvent.click(screen.getAllByRole('tab')[0]);\n      expect(handleChange.mock.calls.length).toBe(0);\n    });\n\n    it('when `activateOnFocus = true` should call onValueChange if an unactive tab gets focused', async () => {\n      const handleChange = vi.fn();\n\n      await render(\n        <Tabs.Root value={0} onValueChange={handleChange}>\n          <Tabs.List activateOnFocus>\n            <Tabs.Tab value={0} />\n            <Tabs.Tab value={1} />\n          </Tabs.List>\n        </Tabs.Root>,\n      );\n\n      const [firstTab] = screen.getAllByRole('tab');\n\n      await act(async () => {\n        firstTab.focus();\n      });\n\n      fireEvent.keyDown(firstTab, { key: 'ArrowRight' });\n      await flushMicrotasks();\n\n      expect(handleChange.mock.calls.length).toBe(1);\n      expect(handleChange.mock.calls[0][0]).toBe(1);\n    });\n\n    it('when `activateOnFocus = false` should not call onValueChange if an unactive tab gets focused', async () => {\n      const handleChange = vi.fn();\n\n      await render(\n        <Tabs.Root value={1} onValueChange={handleChange}>\n          <Tabs.List activateOnFocus={false}>\n            <Tabs.Tab value={0} />\n            <Tabs.Tab value={1} />\n          </Tabs.List>\n        </Tabs.Root>,\n      );\n\n      const [firstTab] = screen.getAllByRole('tab');\n\n      await act(async () => {\n        firstTab.focus();\n      });\n\n      expect(handleChange.mock.calls.length).toBe(0);\n    });\n  });\n\n  describe('prop: orientation', () => {\n    it('does not add aria-orientation by default', async () => {\n      await render(\n        <Tabs.Root value={0}>\n          <Tabs.List>\n            <Tabs.Root />\n          </Tabs.List>\n        </Tabs.Root>,\n      );\n\n      expect(screen.getByRole('tablist')).not.toHaveAttribute('aria-orientation');\n    });\n\n    it('adds the proper aria-orientation when vertical', async () => {\n      await render(\n        <Tabs.Root value={0} orientation=\"vertical\">\n          <Tabs.List>\n            <Tabs.Root />\n          </Tabs.List>\n        </Tabs.Root>,\n      );\n\n      expect(screen.getByRole('tablist')).toHaveAttribute('aria-orientation', 'vertical');\n    });\n  });\n\n  describe('pointer navigation', () => {\n    it('selects the clicked tab', async () => {\n      const { user } = await render(\n        <Tabs.Root defaultValue={0}>\n          <Tabs.List activateOnFocus={false}>\n            <Tabs.Tab value={0}>Tab 1</Tabs.Tab>\n            <Tabs.Tab value={1}>Tab 2</Tabs.Tab>\n            <Tabs.Tab value={2}>Tab 3</Tabs.Tab>\n          </Tabs.List>\n          <Tabs.Panel value={0} keepMounted>\n            Panel 1\n          </Tabs.Panel>\n          <Tabs.Panel value={1} keepMounted>\n            Panel 2\n          </Tabs.Panel>\n          <Tabs.Panel value={2} keepMounted>\n            Panel 3\n          </Tabs.Panel>\n        </Tabs.Root>,\n      );\n\n      const tab2 = screen.getByRole('tab', { name: 'Tab 2' });\n      await user.click(tab2);\n\n      const panels = screen.getAllByRole('tabpanel', { hidden: true });\n\n      expect(panels[0]).toHaveAttribute('hidden');\n      expect(panels[1]).not.toHaveAttribute('hidden');\n      expect(panels[2]).toHaveAttribute('hidden');\n    });\n\n    it('does not select the clicked disabled tab', async () => {\n      const { user } = await render(\n        <Tabs.Root defaultValue={0}>\n          <Tabs.List activateOnFocus={false}>\n            <Tabs.Tab value={0}>Tab 1</Tabs.Tab>\n            <Tabs.Tab disabled value={1}>\n              Tab 2\n            </Tabs.Tab>\n            <Tabs.Tab value={2}>Tab 3</Tabs.Tab>\n          </Tabs.List>\n          <Tabs.Panel value={0} keepMounted>\n            Panel 1\n          </Tabs.Panel>\n          <Tabs.Panel value={1} keepMounted>\n            Panel 2\n          </Tabs.Panel>\n          <Tabs.Panel value={2} keepMounted>\n            Panel 3\n          </Tabs.Panel>\n        </Tabs.Root>,\n      );\n\n      const tab2 = screen.getByRole('tab', { name: 'Tab 2' });\n      await user.click(tab2);\n\n      const panels = screen.getAllByRole('tabpanel', { hidden: true });\n\n      expect(panels[0]).not.toHaveAttribute('hidden');\n      expect(panels[1]).toHaveAttribute('hidden');\n      expect(panels[2]).toHaveAttribute('hidden');\n    });\n  });\n\n  describe('keyboard navigation when focus is on a tab', () => {\n    [\n      ['horizontal', 'ltr', 'ArrowLeft', 'ArrowRight'],\n      ['horizontal', 'rtl', 'ArrowRight', 'ArrowLeft'],\n      ['vertical', undefined, 'ArrowUp', 'ArrowDown'],\n    ].forEach((entry) => {\n      const [orientation, direction, previousItemKey, nextItemKey] = entry;\n\n      describe.skipIf(isJSDOM && direction === 'rtl')(\n        `when focus is on a tab element in a ${orientation} ${direction ?? ''} tablist`,\n        () => {\n          describe(previousItemKey ?? '', () => {\n            describe('with `activateOnFocus = false`', () => {\n              it('moves focus to the last tab without activating it if focus is on the first tab', async () => {\n                const handleChange = vi.fn();\n                const handleKeyDown = vi.fn();\n\n                await render(\n                  <DirectionProvider direction={direction as TextDirection}>\n                    <Tabs.Root\n                      onValueChange={handleChange}\n                      orientation={orientation as Tabs.Root.Props['orientation']}\n                      value={0}\n                    >\n                      <Tabs.List activateOnFocus={false} onKeyDown={handleKeyDown}>\n                        <Tabs.Tab value={0} />\n                        <Tabs.Tab value={1} />\n                        <Tabs.Tab value={2} />\n                      </Tabs.List>\n                    </Tabs.Root>\n                  </DirectionProvider>,\n                );\n\n                const [firstTab, , lastTab] = screen.getAllByRole('tab');\n                await act(async () => {\n                  firstTab.focus();\n                });\n\n                fireEvent.keyDown(firstTab, { key: previousItemKey });\n                await flushMicrotasks();\n\n                expect(lastTab).toHaveFocus();\n                expect(handleChange.mock.calls.length).toBe(0);\n                expect(handleKeyDown.mock.calls.length).toBe(1);\n                expect(handleKeyDown.mock.calls[0][0]).toHaveProperty('defaultPrevented', true);\n              });\n\n              it('moves focus to the previous tab without activating it', async () => {\n                const handleChange = vi.fn();\n                const handleKeyDown = vi.fn();\n\n                await render(\n                  <DirectionProvider direction={direction as TextDirection}>\n                    <Tabs.Root\n                      onValueChange={handleChange}\n                      orientation={orientation as Tabs.Root.Props['orientation']}\n                      value={1}\n                    >\n                      <Tabs.List activateOnFocus={false} onKeyDown={handleKeyDown}>\n                        <Tabs.Tab value={0} />\n                        <Tabs.Tab value={1} />\n                        <Tabs.Tab value={2} />\n                      </Tabs.List>\n                    </Tabs.Root>\n                  </DirectionProvider>,\n                );\n\n                const [firstTab, secondTab] = screen.getAllByRole('tab');\n                await act(async () => {\n                  secondTab.focus();\n                });\n\n                fireEvent.keyDown(secondTab, { key: previousItemKey });\n                await flushMicrotasks();\n\n                expect(firstTab).toHaveFocus();\n                expect(handleChange.mock.calls.length).toBe(0);\n                expect(handleKeyDown.mock.calls.length).toBe(1);\n                expect(handleKeyDown.mock.calls[0][0]).toHaveProperty('defaultPrevented', true);\n              });\n\n              it('moves focus to a disabled tab without activating it', async () => {\n                const handleKeyDown = vi.fn();\n\n                await render(\n                  <DirectionProvider direction={direction as TextDirection}>\n                    <Tabs.Root\n                      orientation={orientation as Tabs.Root.Props['orientation']}\n                      value={2}\n                    >\n                      <Tabs.List activateOnFocus={false} onKeyDown={handleKeyDown}>\n                        <Tabs.Tab value={0} />\n                        <Tabs.Tab value={1} disabled />\n                        <Tabs.Tab value={2} />\n                      </Tabs.List>\n                    </Tabs.Root>\n                  </DirectionProvider>,\n                );\n\n                const [, disabledTab, lastTab] = screen.getAllByRole('tab');\n                await act(async () => {\n                  lastTab.focus();\n                });\n\n                fireEvent.keyDown(lastTab, { key: previousItemKey });\n                await flushMicrotasks();\n\n                expect(disabledTab).toHaveFocus();\n                expect(handleKeyDown.mock.calls.length).toBe(1);\n                expect(handleKeyDown.mock.calls[0][0]).toHaveProperty('defaultPrevented', true);\n              });\n            });\n\n            describe('with `activateOnFocus = true`', () => {\n              it('moves focus to the last tab while activating it if focus is on the first tab', async () => {\n                const handleChange = vi.fn();\n                const handleKeyDown = vi.fn();\n\n                await render(\n                  <DirectionProvider direction={direction as TextDirection}>\n                    <Tabs.Root\n                      onValueChange={handleChange}\n                      orientation={orientation as Tabs.Root.Props['orientation']}\n                      value={0}\n                    >\n                      <Tabs.List onKeyDown={handleKeyDown} activateOnFocus>\n                        <Tabs.Tab value={0} />\n                        <Tabs.Tab value={1} />\n                        <Tabs.Tab value={2} />\n                      </Tabs.List>\n                    </Tabs.Root>\n                  </DirectionProvider>,\n                );\n\n                const [firstTab, , lastTab] = screen.getAllByRole('tab');\n                await act(async () => {\n                  firstTab.focus();\n                });\n\n                fireEvent.keyDown(firstTab, { key: previousItemKey });\n                await flushMicrotasks();\n\n                expect(lastTab).toHaveFocus();\n                expect(handleChange.mock.calls.length).toBe(1);\n                expect(handleChange.mock.calls[0][0]).toBe(2);\n                expect(handleKeyDown.mock.calls.length).toBe(1);\n                expect(handleKeyDown.mock.calls[0][0]).toHaveProperty('defaultPrevented', true);\n              });\n\n              it('moves focus to the previous tab while activating it', async () => {\n                const handleChange = vi.fn();\n                const handleKeyDown = vi.fn();\n\n                await render(\n                  <DirectionProvider direction={direction as TextDirection}>\n                    <Tabs.Root\n                      onValueChange={handleChange}\n                      orientation={orientation as Tabs.Root.Props['orientation']}\n                      value={1}\n                    >\n                      <Tabs.List onKeyDown={handleKeyDown} activateOnFocus>\n                        <Tabs.Tab value={0} />\n                        <Tabs.Tab value={1} />\n                        <Tabs.Tab value={2} />\n                      </Tabs.List>\n                    </Tabs.Root>\n                  </DirectionProvider>,\n                );\n\n                const [firstTab, secondTab] = screen.getAllByRole('tab');\n                await act(async () => {\n                  secondTab.focus();\n                });\n\n                fireEvent.keyDown(secondTab, { key: previousItemKey });\n                await flushMicrotasks();\n\n                expect(firstTab).toHaveFocus();\n                expect(handleChange.mock.calls.length).toBe(1);\n                expect(handleChange.mock.calls[0][0]).toBe(0);\n                expect(handleKeyDown.mock.calls.length).toBe(1);\n                expect(handleKeyDown.mock.calls[0][0]).toHaveProperty('defaultPrevented', true);\n              });\n            });\n\n            it('moves focus to a disabled tab without activating it', async () => {\n              const handleKeyDown = vi.fn();\n\n              await render(\n                <DirectionProvider direction={direction as TextDirection}>\n                  <Tabs.Root orientation={orientation as Tabs.Root.Props['orientation']} value={2}>\n                    <Tabs.List onKeyDown={handleKeyDown}>\n                      <Tabs.Tab value={0} />\n                      <Tabs.Tab value={1} disabled />\n                      <Tabs.Tab value={2} />\n                    </Tabs.List>\n                  </Tabs.Root>\n                </DirectionProvider>,\n              );\n\n              const [, disabledTab, lastTab] = screen.getAllByRole('tab');\n              await act(async () => {\n                lastTab.focus();\n              });\n\n              fireEvent.keyDown(lastTab, { key: previousItemKey });\n              await flushMicrotasks();\n\n              expect(disabledTab).toHaveFocus();\n              expect(handleKeyDown.mock.calls.length).toBe(1);\n              expect(handleKeyDown.mock.calls[0][0]).toHaveProperty('defaultPrevented', true);\n            });\n          });\n\n          describe(nextItemKey ?? '', () => {\n            describe('with `activateOnFocus = false`', () => {\n              it('moves focus to the first tab without activating it if focus is on the last tab', async () => {\n                const handleChange = vi.fn();\n                const handleKeyDown = vi.fn();\n\n                await render(\n                  <DirectionProvider direction={direction as TextDirection}>\n                    <Tabs.Root\n                      onValueChange={handleChange}\n                      orientation={orientation as Tabs.Root.Props['orientation']}\n                      value={2}\n                    >\n                      <Tabs.List activateOnFocus={false} onKeyDown={handleKeyDown}>\n                        <Tabs.Tab value={0} />\n                        <Tabs.Tab value={1} />\n                        <Tabs.Tab value={2} />\n                      </Tabs.List>\n                    </Tabs.Root>\n                  </DirectionProvider>,\n                );\n\n                const [firstTab, , lastTab] = screen.getAllByRole('tab');\n                await act(async () => {\n                  lastTab.focus();\n                });\n\n                fireEvent.keyDown(lastTab, { key: nextItemKey });\n                await flushMicrotasks();\n\n                expect(firstTab).toHaveFocus();\n                expect(handleChange.mock.calls.length).toBe(0);\n                expect(handleKeyDown.mock.calls.length).toBe(1);\n                expect(handleKeyDown.mock.calls[0][0]).toHaveProperty('defaultPrevented', true);\n              });\n\n              it('moves focus to the next tab without activating it', async () => {\n                const handleChange = vi.fn();\n                const handleKeyDown = vi.fn();\n\n                await render(\n                  <DirectionProvider direction={direction as TextDirection}>\n                    <Tabs.Root\n                      onValueChange={handleChange}\n                      orientation={orientation as Tabs.Root.Props['orientation']}\n                      value={1}\n                    >\n                      <Tabs.List activateOnFocus={false} onKeyDown={handleKeyDown}>\n                        <Tabs.Tab value={0} />\n                        <Tabs.Tab value={1} />\n                        <Tabs.Tab value={2} />\n                      </Tabs.List>\n                    </Tabs.Root>\n                  </DirectionProvider>,\n                );\n\n                const [, secondTab, lastTab] = screen.getAllByRole('tab');\n                await act(async () => {\n                  secondTab.focus();\n                });\n\n                fireEvent.keyDown(secondTab, { key: nextItemKey });\n                await flushMicrotasks();\n\n                expect(lastTab).toHaveFocus();\n                expect(handleChange.mock.calls.length).toBe(0);\n                expect(handleKeyDown.mock.calls.length).toBe(1);\n                expect(handleKeyDown.mock.calls[0][0]).toHaveProperty('defaultPrevented', true);\n              });\n\n              it('moves focus to a disabled tab without activating it', async () => {\n                const handleChange = vi.fn();\n                const handleKeyDown = vi.fn();\n\n                await render(\n                  <DirectionProvider direction={direction as TextDirection}>\n                    <Tabs.Root\n                      onValueChange={handleChange}\n                      orientation={orientation as Tabs.Root.Props['orientation']}\n                      value={0}\n                    >\n                      <Tabs.List activateOnFocus={false} onKeyDown={handleKeyDown}>\n                        <Tabs.Tab value={0} />\n                        <Tabs.Tab value={1} disabled />\n                        <Tabs.Tab value={2} />\n                      </Tabs.List>\n                    </Tabs.Root>\n                  </DirectionProvider>,\n                );\n\n                const [firstTab, disabledTab, thirdTab] = screen.getAllByRole('tab');\n                await act(async () => {\n                  firstTab.focus();\n                });\n\n                fireEvent.keyDown(firstTab, { key: nextItemKey });\n                await flushMicrotasks();\n\n                expect(disabledTab).toHaveFocus();\n                expect(handleChange.mock.calls.length).toBe(0);\n                expect(handleKeyDown.mock.calls.length).toBe(1);\n                expect(handleKeyDown.mock.calls[0][0]).toHaveProperty('defaultPrevented', true);\n\n                fireEvent.keyDown(disabledTab, { key: nextItemKey });\n                await flushMicrotasks();\n                expect(thirdTab).toHaveFocus();\n              });\n            });\n\n            describe('with `activateOnFocus = true`', () => {\n              it('moves focus to the first tab while activating it if focus is on the last tab', async () => {\n                const handleChange = vi.fn();\n                const handleKeyDown = vi.fn();\n\n                await render(\n                  <DirectionProvider direction={direction as TextDirection}>\n                    <Tabs.Root\n                      onValueChange={handleChange}\n                      orientation={orientation as Tabs.Root.Props['orientation']}\n                      value={2}\n                    >\n                      <Tabs.List onKeyDown={handleKeyDown} activateOnFocus>\n                        <Tabs.Tab value={0} />\n                        <Tabs.Tab value={1} />\n                        <Tabs.Tab value={2} />\n                      </Tabs.List>\n                    </Tabs.Root>\n                  </DirectionProvider>,\n                );\n\n                const [firstTab, , lastTab] = screen.getAllByRole('tab');\n                await act(async () => {\n                  lastTab.focus();\n                });\n\n                fireEvent.keyDown(lastTab, { key: nextItemKey });\n                await flushMicrotasks();\n\n                expect(firstTab).toHaveFocus();\n                expect(handleChange.mock.calls.length).toBe(1);\n                expect(handleChange.mock.calls[0][0]).toBe(0);\n                expect(handleKeyDown.mock.calls.length).toBe(1);\n                expect(handleKeyDown.mock.calls[0][0]).toHaveProperty('defaultPrevented', true);\n              });\n\n              it('moves focus to the next tab while activating it', async () => {\n                const handleChange = vi.fn();\n                const handleKeyDown = vi.fn();\n\n                await render(\n                  <DirectionProvider direction={direction as TextDirection}>\n                    <Tabs.Root\n                      onValueChange={handleChange}\n                      orientation={orientation as Tabs.Root.Props['orientation']}\n                      value={1}\n                    >\n                      <Tabs.List onKeyDown={handleKeyDown} activateOnFocus>\n                        <Tabs.Tab value={0} />\n                        <Tabs.Tab value={1} />\n                        <Tabs.Tab value={2} />\n                      </Tabs.List>\n                    </Tabs.Root>\n                  </DirectionProvider>,\n                );\n\n                const [, secondTab, lastTab] = screen.getAllByRole('tab');\n                await act(async () => {\n                  secondTab.focus();\n                });\n\n                fireEvent.keyDown(secondTab, { key: nextItemKey });\n                await flushMicrotasks();\n\n                expect(lastTab).toHaveFocus();\n                expect(handleChange.mock.calls.length).toBe(1);\n                expect(handleChange.mock.calls[0][0]).toBe(2);\n                expect(handleKeyDown.mock.calls.length).toBe(1);\n                expect(handleKeyDown.mock.calls[0][0]).toHaveProperty('defaultPrevented', true);\n              });\n            });\n\n            it('moves focus to a disabled tab without activating it', async () => {\n              const handleChange = vi.fn();\n              const handleKeyDown = vi.fn();\n\n              await render(\n                <DirectionProvider direction={direction as TextDirection}>\n                  <Tabs.Root\n                    onValueChange={handleChange}\n                    orientation={orientation as Tabs.Root.Props['orientation']}\n                    value={0}\n                  >\n                    <Tabs.List onKeyDown={handleKeyDown}>\n                      <Tabs.Tab value={0} />\n                      <Tabs.Tab value={1} disabled />\n                      <Tabs.Tab value={2} />\n                    </Tabs.List>\n                  </Tabs.Root>\n                </DirectionProvider>,\n              );\n\n              const [firstTab, disabledTab, thirdTab] = screen.getAllByRole('tab');\n              await act(async () => {\n                firstTab.focus();\n              });\n\n              fireEvent.keyDown(firstTab, { key: nextItemKey });\n              await flushMicrotasks();\n\n              expect(disabledTab).toHaveFocus();\n              expect(handleChange.mock.calls.length).toBe(0);\n              expect(handleKeyDown.mock.calls.length).toBe(1);\n              expect(handleKeyDown.mock.calls[0][0]).toHaveProperty('defaultPrevented', true);\n\n              fireEvent.keyDown(disabledTab, { key: nextItemKey });\n              await flushMicrotasks();\n              expect(thirdTab).toHaveFocus();\n            });\n          });\n\n          describe('modifier keys', () => {\n            ['Shift', 'Control', 'Alt', 'Meta'].forEach((modifierKey) => {\n              it(`does not move focus when modifier key: ${modifierKey} is pressed`, async () => {\n                const handleChange = vi.fn();\n                const handleKeyDown = vi.fn();\n                const { user } = await render(\n                  <DirectionProvider direction={direction as TextDirection}>\n                    <Tabs.Root\n                      onValueChange={handleChange}\n                      orientation={orientation as Tabs.Root.Props['orientation']}\n                      value={0}\n                    >\n                      <Tabs.List onKeyDown={handleKeyDown}>\n                        <Tabs.Tab value={0} />\n                        <Tabs.Tab value={1} />\n                        <Tabs.Tab value={2} />\n                      </Tabs.List>\n                    </Tabs.Root>\n                  </DirectionProvider>,\n                );\n\n                const [firstTab] = screen.getAllByRole('tab');\n\n                await user.keyboard('[Tab]');\n                expect(firstTab).toHaveFocus();\n\n                await user.keyboard(`{${modifierKey}>}{${nextItemKey}}`);\n                expect(firstTab).toHaveFocus();\n                expect(handleChange.mock.calls.length).toBe(0);\n                expect(handleKeyDown.mock.calls.length).toBe(2);\n\n                await user.keyboard(`{${modifierKey}>}{${previousItemKey}}`);\n                expect(firstTab).toHaveFocus();\n                expect(handleChange.mock.calls.length).toBe(0);\n                expect(handleKeyDown.mock.calls.length).toBe(4);\n              });\n            });\n          });\n        },\n      );\n    });\n\n    describe('when focus is on a tab regardless of orientation', () => {\n      describe('Home', () => {\n        it('when `activateOnFocus = false`, moves focus to the first tab without activating it', async () => {\n          const handleChange = vi.fn();\n          const handleKeyDown = vi.fn();\n\n          await render(\n            <Tabs.Root onValueChange={handleChange} value={2}>\n              <Tabs.List activateOnFocus={false} onKeyDown={handleKeyDown}>\n                <Tabs.Tab value={0} />\n                <Tabs.Tab value={1} />\n                <Tabs.Tab value={2} />\n              </Tabs.List>\n            </Tabs.Root>,\n          );\n\n          const [firstTab, , lastTab] = screen.getAllByRole('tab');\n          await act(async () => {\n            lastTab.focus();\n          });\n\n          fireEvent.keyDown(lastTab, { key: 'Home' });\n          await flushMicrotasks();\n\n          expect(firstTab).toHaveFocus();\n          expect(handleChange.mock.calls.length).toBe(0);\n          expect(handleKeyDown.mock.calls.length).toBe(1);\n          expect(handleKeyDown.mock.calls[0][0]).toHaveProperty('defaultPrevented', true);\n        });\n\n        it('when `activateOnFocus = true`, moves focus to the first tab while activating it', async () => {\n          const handleChange = vi.fn();\n          const handleKeyDown = vi.fn();\n\n          await render(\n            <Tabs.Root onValueChange={handleChange} value={2}>\n              <Tabs.List onKeyDown={handleKeyDown} activateOnFocus>\n                <Tabs.Tab value={0} />\n                <Tabs.Tab value={1} />\n                <Tabs.Tab value={2} />\n              </Tabs.List>\n            </Tabs.Root>,\n          );\n\n          const [firstTab, , lastTab] = screen.getAllByRole('tab');\n          await act(async () => {\n            lastTab.focus();\n          });\n\n          fireEvent.keyDown(lastTab, { key: 'Home' });\n          await flushMicrotasks();\n\n          expect(firstTab).toHaveFocus();\n          expect(handleChange.mock.calls.length).toBe(1);\n          expect(handleChange.mock.calls[0][0]).toBe(0);\n          expect(handleKeyDown.mock.calls.length).toBe(1);\n          expect(handleKeyDown.mock.calls[0][0]).toHaveProperty('defaultPrevented', true);\n        });\n\n        [false, true].forEach((activateOnFocusProp) => {\n          it(`when \\`activateOnFocus = ${activateOnFocusProp}\\`, moves focus to a disabled tab without activating it`, async () => {\n            const handleChange = vi.fn();\n            const handleKeyDown = vi.fn();\n\n            await render(\n              <Tabs.Root onValueChange={handleChange} value={2}>\n                <Tabs.List activateOnFocus={activateOnFocusProp} onKeyDown={handleKeyDown}>\n                  <Tabs.Tab value={0} disabled />\n                  <Tabs.Tab value={1} />\n                  <Tabs.Tab value={2} />\n                </Tabs.List>\n              </Tabs.Root>,\n            );\n\n            const [disabledTab, , lastTab] = screen.getAllByRole('tab');\n            await act(async () => {\n              lastTab.focus();\n            });\n\n            fireEvent.keyDown(lastTab, { key: 'Home' });\n            await flushMicrotasks();\n\n            expect(disabledTab).toHaveFocus();\n            expect(handleChange.mock.calls.length).toBe(0);\n            expect(handleKeyDown.mock.calls.length).toBe(1);\n            expect(handleKeyDown.mock.calls[0][0]).toHaveProperty('defaultPrevented', true);\n          });\n        });\n      });\n\n      describe('End', () => {\n        it('when `activateOnFocus = false`, moves focus to the last tab without activating it', async () => {\n          const handleChange = vi.fn();\n          const handleKeyDown = vi.fn();\n\n          await render(\n            <Tabs.Root onValueChange={handleChange} value={0}>\n              <Tabs.List activateOnFocus={false} onKeyDown={handleKeyDown}>\n                <Tabs.Tab value={0} />\n                <Tabs.Tab value={1} />\n                <Tabs.Tab value={2} />\n              </Tabs.List>\n            </Tabs.Root>,\n          );\n\n          const [firstTab, , lastTab] = screen.getAllByRole('tab');\n          await act(async () => {\n            firstTab.focus();\n          });\n\n          fireEvent.keyDown(firstTab, { key: 'End' });\n          await flushMicrotasks();\n\n          expect(lastTab).toHaveFocus();\n          expect(handleChange.mock.calls.length).toBe(0);\n          expect(handleKeyDown.mock.calls.length).toBe(1);\n          expect(handleKeyDown.mock.calls[0][0]).toHaveProperty('defaultPrevented', true);\n        });\n\n        it('when `activateOnFocus = true`, moves focus to the last tab while activating it', async () => {\n          const handleChange = vi.fn();\n          const handleKeyDown = vi.fn();\n\n          await render(\n            <Tabs.Root onValueChange={handleChange} value={0}>\n              <Tabs.List onKeyDown={handleKeyDown} activateOnFocus>\n                <Tabs.Tab value={0} />\n                <Tabs.Tab value={1} />\n                <Tabs.Tab value={2} />\n              </Tabs.List>\n            </Tabs.Root>,\n          );\n\n          const [firstTab, , lastTab] = screen.getAllByRole('tab');\n          await act(async () => {\n            firstTab.focus();\n          });\n\n          fireEvent.keyDown(firstTab, { key: 'End' });\n          await flushMicrotasks();\n\n          expect(lastTab).toHaveFocus();\n          expect(handleChange.mock.calls.length).toBe(1);\n          expect(handleChange.mock.calls[0][0]).toBe(2);\n          expect(handleKeyDown.mock.calls.length).toBe(1);\n          expect(handleKeyDown.mock.calls[0][0]).toHaveProperty('defaultPrevented', true);\n        });\n\n        [false, true].forEach((activateOnFocusProp) => {\n          it(`when \\`activateOnFocus = ${activateOnFocusProp}\\`, moves focus to a disabled tab without activating it`, async () => {\n            const handleChange = vi.fn();\n            const handleKeyDown = vi.fn();\n\n            await render(\n              <Tabs.Root onValueChange={handleChange} value={0}>\n                <Tabs.List activateOnFocus={activateOnFocusProp} onKeyDown={handleKeyDown}>\n                  <Tabs.Tab value={0} />\n                  <Tabs.Tab value={1} />\n                  <Tabs.Tab value={2} disabled />\n                </Tabs.List>\n              </Tabs.Root>,\n            );\n\n            const [firstTab, , disabledTab] = screen.getAllByRole('tab');\n            await act(async () => {\n              firstTab.focus();\n            });\n\n            fireEvent.keyDown(firstTab, { key: 'End' });\n            await flushMicrotasks();\n\n            expect(disabledTab).toHaveFocus();\n            expect(handleChange.mock.calls.length).toBe(0);\n            expect(handleKeyDown.mock.calls.length).toBe(1);\n            expect(handleKeyDown.mock.calls[0][0]).toHaveProperty('defaultPrevented', true);\n          });\n        });\n      });\n    });\n\n    it('should allow to focus first tab when there are no active tabs', async () => {\n      await render(\n        <Tabs.Root defaultValue={0}>\n          <Tabs.List>\n            <Tabs.Tab value={0} />\n            <Tabs.Tab value={1} />\n          </Tabs.List>\n        </Tabs.Root>,\n      );\n\n      expect(screen.getAllByRole('tab').map((tab) => tab.getAttribute('tabIndex'))).toEqual([\n        '0',\n        '-1',\n      ]);\n    });\n  });\n\n  describe.skipIf(isJSDOM)('activation direction', () => {\n    it('should set the `data-activation-direction` attribute on the tabs root with orientation=horizontal', async () => {\n      await render(\n        <Tabs.Root data-testid=\"root\">\n          <Tabs.List>\n            <Tabs.Tab value={0} />\n            <Tabs.Tab value={1} />\n          </Tabs.List>\n        </Tabs.Root>,\n      );\n\n      const root = screen.getByTestId('root');\n      const [tab1, tab2] = screen.getAllByRole('tab');\n\n      expect(root).toHaveAttribute('data-activation-direction', 'none');\n      await act(async () => {\n        tab2.click();\n      });\n\n      expect(root).toHaveAttribute('data-activation-direction', 'right');\n\n      await act(async () => {\n        tab1.click();\n      });\n\n      expect(root).toHaveAttribute('data-activation-direction', 'left');\n    });\n\n    it('should set the `data-activation-direction` attribute on the tabs root with orientation=vertical', async () => {\n      await render(\n        <Tabs.Root data-testid=\"root\" orientation=\"vertical\">\n          <Tabs.List>\n            <Tabs.Tab value={0} style={{ display: 'block' }} />\n            <Tabs.Tab value={1} style={{ display: 'block' }} />\n          </Tabs.List>\n        </Tabs.Root>,\n      );\n\n      const root = screen.getByTestId('root');\n      const [tab1, tab2] = screen.getAllByRole('tab');\n\n      expect(root).toHaveAttribute('data-activation-direction', 'none');\n      await act(async () => {\n        tab2.click();\n      });\n\n      expect(root).toHaveAttribute('data-activation-direction', 'down');\n\n      await act(async () => {\n        tab1.click();\n      });\n\n      expect(root).toHaveAttribute('data-activation-direction', 'up');\n    });\n  });\n\n  describe('popups', () => {\n    it('works inside Popover', async () => {\n      function ExamplePopover() {\n        return (\n          <Popover.Root>\n            <Popover.Trigger>Open</Popover.Trigger>\n            <Popover.Portal>\n              <Popover.Positioner sideOffset={8}>\n                <Popover.Popup>\n                  <Tabs.Root defaultValue=\"overview\">\n                    <Tabs.List>\n                      <Tabs.Tab value=\"overview\">Overview</Tabs.Tab>\n                      <Tabs.Tab value=\"projects\">Projects</Tabs.Tab>\n                      <Tabs.Tab value=\"account\">Account</Tabs.Tab>\n                    </Tabs.List>\n                    <Tabs.Panel value=\"overview\" />\n                    <Tabs.Panel value=\"projects\" />\n                    <Tabs.Panel value=\"account\" />\n                  </Tabs.Root>\n                </Popover.Popup>\n              </Popover.Positioner>\n            </Popover.Portal>\n          </Popover.Root>\n        );\n      }\n\n      const { user } = await render(<ExamplePopover />);\n\n      const trigger = screen.getByRole('button', { name: 'Open' });\n\n      await user.click(trigger);\n\n      const tab1 = screen.getByRole('tab', { name: 'Overview' });\n      await waitFor(() => {\n        expect(tab1).toHaveFocus();\n      });\n\n      await user.keyboard('{ArrowRight}');\n\n      const tab2 = screen.getByRole('tab', { name: 'Projects' });\n      await waitFor(() => {\n        expect(tab2).toHaveFocus();\n      });\n    });\n\n    it('works inside Dialog', async () => {\n      function ExampleDialog() {\n        return (\n          <Dialog.Root>\n            <Dialog.Trigger>Open</Dialog.Trigger>\n            <Dialog.Portal>\n              <Dialog.Popup>\n                <Tabs.Root defaultValue=\"overview\">\n                  <Tabs.List>\n                    <Tabs.Tab value=\"overview\">Overview</Tabs.Tab>\n                    <Tabs.Tab value=\"projects\">Projects</Tabs.Tab>\n                    <Tabs.Tab value=\"account\">Account</Tabs.Tab>\n                  </Tabs.List>\n                  <Tabs.Panel value=\"overview\" />\n                  <Tabs.Panel value=\"projects\" />\n                  <Tabs.Panel value=\"account\" />\n                </Tabs.Root>\n              </Dialog.Popup>\n            </Dialog.Portal>\n          </Dialog.Root>\n        );\n      }\n\n      const { user } = await render(<ExampleDialog />);\n\n      const trigger = screen.getByRole('button', { name: 'Open' });\n\n      await user.click(trigger);\n\n      const tab1 = screen.getByRole('tab', { name: 'Overview' });\n      await waitFor(() => {\n        expect(tab1).toHaveFocus();\n      });\n      await user.keyboard('{ArrowRight}');\n\n      const tab2 = screen.getByRole('tab', { name: 'Projects' });\n      await waitFor(() => {\n        expect(tab2).toHaveFocus();\n      });\n    });\n  });\n\n  describe('highlight synchronization on external value change relative to focus', () => {\n    it('when focus is outside the tablist, highlight follows the new active tab (tabIndex=0 moves)', async () => {\n      const { setProps } = await render(\n        <Tabs.Root value={0}>\n          <Tabs.List activateOnFocus={false}>\n            <Tabs.Tab value={0} />\n            <Tabs.Tab value={1} />\n            <Tabs.Tab value={2} />\n          </Tabs.List>\n        </Tabs.Root>,\n      );\n\n      const [firstTab, secondTab, thirdTab] = screen.getAllByRole('tab');\n\n      expect(firstTab.tabIndex).toBe(0);\n\n      await setProps({ value: 2 });\n      await flushMicrotasks();\n\n      expect(firstTab.tabIndex).toBe(-1);\n      expect(secondTab.tabIndex).toBe(-1);\n      expect(thirdTab.tabIndex).toBe(0);\n\n      await setProps({ value: 1 });\n      await flushMicrotasks();\n\n      expect(firstTab.tabIndex).toBe(-1);\n      expect(secondTab.tabIndex).toBe(0);\n      expect(thirdTab.tabIndex).toBe(-1);\n    });\n\n    it('when focus is inside the tablist, highlight stays put on external change and arrow keys continue from the focused tab', async () => {\n      const { setProps } = await render(\n        <Tabs.Root value={0}>\n          <Tabs.List activateOnFocus={false}>\n            <Tabs.Tab value={0}>Tab 1</Tabs.Tab>\n            <Tabs.Tab value={1}>Tab 2</Tabs.Tab>\n            <Tabs.Tab value={2}>Tab 3</Tabs.Tab>\n          </Tabs.List>\n        </Tabs.Root>,\n      );\n\n      const [firstTab, secondTab, thirdTab] = screen.getAllByRole('tab');\n\n      await act(async () => {\n        firstTab.focus();\n      });\n      expect(firstTab).toHaveProperty('tabIndex', 0);\n\n      await setProps({ value: 2 });\n      await flushMicrotasks();\n\n      // Highlight should not change (still on first tab), but selection did\n      expect(firstTab.tabIndex).toBe(0);\n      expect(secondTab.tabIndex).toBe(-1);\n      expect(thirdTab.tabIndex).toBe(-1);\n      expect(firstTab).toHaveAttribute('aria-selected', 'false');\n      expect(thirdTab).toHaveAttribute('aria-selected', 'true');\n\n      // Arrow navigation should continue from the highlighted tab\n      fireEvent.keyDown(firstTab, { key: 'ArrowRight' });\n      await flushMicrotasks();\n\n      expect(secondTab).toHaveFocus();\n      // Selection remains the externally-set tab since activateOnFocus=false\n      expect(thirdTab).toHaveAttribute('aria-selected', 'true');\n      expect(secondTab).toHaveAttribute('aria-selected', 'false');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/tabs/root/TabsRoot.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useControlled } from '@base-ui/utils/useControlled';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport type { BaseUIComponentProps, Orientation as BaseOrientation } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { CompositeList } from '../../composite/list/CompositeList';\nimport type { CompositeMetadata } from '../../composite/list/CompositeList';\nimport { useDirection } from '../../direction-provider/DirectionContext';\nimport { TabsRootContext } from './TabsRootContext';\nimport { tabsStateAttributesMapping } from './stateAttributesMapping';\nimport type { TabsTab } from '../tab/TabsTab';\nimport type { TabsPanel } from '../panel/TabsPanel';\nimport { type BaseUIChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\n\n/**\n * Groups the tabs and the corresponding panels.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Tabs](https://base-ui.com/react/components/tabs)\n */\nexport const TabsRoot = React.forwardRef(function TabsRoot(\n  componentProps: TabsRoot.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const {\n    className,\n    defaultValue: defaultValueProp = 0,\n    onValueChange: onValueChangeProp,\n    orientation = 'horizontal',\n    render,\n    value: valueProp,\n    ...elementProps\n  } = componentProps;\n\n  const direction = useDirection();\n\n  // Track whether the user explicitly provided a `defaultValue` prop.\n  // Used to determine if we should honor a disabled tab selection.\n  const hasExplicitDefaultValueProp = Object.hasOwn(componentProps, 'defaultValue');\n\n  const tabPanelRefs = React.useRef<(HTMLElement | null)[]>([]);\n  const [mountedTabPanels, setMountedTabPanels] = React.useState(\n    () => new Map<TabsTab.Value | number, string>(),\n  );\n\n  const [value, setValue] = useControlled({\n    controlled: valueProp,\n    default: defaultValueProp,\n    name: 'Tabs',\n    state: 'value',\n  });\n\n  const isControlled = valueProp !== undefined;\n\n  const [tabMap, setTabMap] = React.useState(\n    () => new Map<Node, CompositeMetadata<TabsTab.Metadata> | null>(),\n  );\n\n  const [tabActivationDirection, setTabActivationDirection] =\n    React.useState<TabsTab.ActivationDirection>('none');\n\n  const onValueChange = useStableCallback(\n    (newValue: TabsTab.Value, eventDetails: TabsRoot.ChangeEventDetails) => {\n      onValueChangeProp?.(newValue, eventDetails);\n\n      if (eventDetails.isCanceled) {\n        return;\n      }\n\n      setValue(newValue);\n      setTabActivationDirection(eventDetails.activationDirection);\n    },\n  );\n\n  const registerMountedTabPanel = useStableCallback(\n    (panelValue: TabsTab.Value | number, panelId: string) => {\n      setMountedTabPanels((prev) => {\n        if (prev.get(panelValue) === panelId) {\n          return prev;\n        }\n\n        const next = new Map(prev);\n        next.set(panelValue, panelId);\n        return next;\n      });\n    },\n  );\n\n  const unregisterMountedTabPanel = useStableCallback(\n    (panelValue: TabsTab.Value | number, panelId: string) => {\n      setMountedTabPanels((prev) => {\n        if (!prev.has(panelValue) || prev.get(panelValue) !== panelId) {\n          return prev;\n        }\n\n        const next = new Map(prev);\n        next.delete(panelValue);\n        return next;\n      });\n    },\n  );\n\n  // get the `id` attribute of <Tabs.Panel> to set as the value of `aria-controls` on <Tabs.Tab>\n  const getTabPanelIdByValue = React.useCallback(\n    (tabValue: TabsTab.Value) => {\n      return mountedTabPanels.get(tabValue);\n    },\n    [mountedTabPanels],\n  );\n\n  // get the `id` attribute of <Tabs.Tab> to set as the value of `aria-labelledby` on <Tabs.Panel>\n  const getTabIdByPanelValue = React.useCallback(\n    (tabPanelValue: TabsTab.Value) => {\n      for (const tabMetadata of tabMap.values()) {\n        if (tabPanelValue === tabMetadata?.value) {\n          return tabMetadata?.id;\n        }\n      }\n      return undefined;\n    },\n    [tabMap],\n  );\n\n  // used in `useActivationDirectionDetector` for setting data-activation-direction\n  const getTabElementBySelectedValue = React.useCallback(\n    (selectedValue: TabsTab.Value | undefined): HTMLElement | null => {\n      if (selectedValue === undefined) {\n        return null;\n      }\n\n      for (const [tabElement, tabMetadata] of tabMap.entries()) {\n        if (tabMetadata != null && selectedValue === (tabMetadata.value ?? tabMetadata.index)) {\n          return tabElement as HTMLElement;\n        }\n      }\n\n      return null;\n    },\n    [tabMap],\n  );\n\n  const tabsContextValue: TabsRootContext = React.useMemo(\n    () => ({\n      direction,\n      getTabElementBySelectedValue,\n      getTabIdByPanelValue,\n      getTabPanelIdByValue,\n      onValueChange,\n      orientation,\n      registerMountedTabPanel,\n      setTabMap,\n      unregisterMountedTabPanel,\n      tabActivationDirection,\n      value,\n    }),\n    [\n      direction,\n      getTabElementBySelectedValue,\n      getTabIdByPanelValue,\n      getTabPanelIdByValue,\n      onValueChange,\n      orientation,\n      registerMountedTabPanel,\n      setTabMap,\n      unregisterMountedTabPanel,\n      tabActivationDirection,\n      value,\n    ],\n  );\n\n  const selectedTabMetadata = React.useMemo(() => {\n    for (const tabMetadata of tabMap.values()) {\n      if (tabMetadata != null && tabMetadata.value === value) {\n        return tabMetadata;\n      }\n    }\n    return undefined;\n  }, [tabMap, value]);\n\n  // Find the first non-disabled tab value.\n  // Used as a fallback when the current selection is disabled or missing.\n  const firstEnabledTabValue = React.useMemo(() => {\n    for (const tabMetadata of tabMap.values()) {\n      if (tabMetadata != null && !tabMetadata.disabled) {\n        return tabMetadata.value;\n      }\n    }\n    return undefined;\n  }, [tabMap]);\n\n  // Automatically switch to the first enabled tab when:\n  // - The current selection is disabled (and wasn't explicitly set via defaultValue)\n  // - The current selection is missing (tab was removed from DOM)\n  // Falls back to null if all tabs are disabled.\n  useIsoLayoutEffect(() => {\n    if (isControlled || tabMap.size === 0) {\n      return;\n    }\n\n    const selectionIsDisabled = selectedTabMetadata?.disabled;\n    const selectionIsMissing = selectedTabMetadata == null && value !== null;\n\n    const shouldHonorExplicitDefaultSelection =\n      hasExplicitDefaultValueProp && selectionIsDisabled && value === defaultValueProp;\n\n    if (shouldHonorExplicitDefaultSelection) {\n      return;\n    }\n\n    if (!selectionIsDisabled && !selectionIsMissing) {\n      return;\n    }\n\n    const fallbackValue = firstEnabledTabValue ?? null;\n\n    if (value === fallbackValue) {\n      return;\n    }\n\n    setValue(fallbackValue);\n    setTabActivationDirection('none');\n  }, [\n    defaultValueProp,\n    firstEnabledTabValue,\n    hasExplicitDefaultValueProp,\n    isControlled,\n    selectedTabMetadata,\n    setTabActivationDirection,\n    setValue,\n    tabMap,\n    value,\n  ]);\n\n  const state: TabsRootState = {\n    orientation,\n    tabActivationDirection,\n  };\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: forwardedRef,\n    props: elementProps,\n    stateAttributesMapping: tabsStateAttributesMapping,\n  });\n\n  return (\n    <TabsRootContext.Provider value={tabsContextValue}>\n      <CompositeList<TabsPanel.Metadata> elementsRef={tabPanelRefs}>{element}</CompositeList>\n    </TabsRootContext.Provider>\n  );\n});\n\nexport type TabsRootOrientation = BaseOrientation;\n\nexport interface TabsRootState {\n  /**\n   * The component orientation.\n   */\n  orientation: TabsRoot.Orientation;\n  /**\n   * The direction used for tab activation.\n   */\n  tabActivationDirection: TabsTab.ActivationDirection;\n}\n\nexport interface TabsRootProps extends BaseUIComponentProps<'div', TabsRootState> {\n  /**\n   * The value of the currently active `Tab`. Use when the component is controlled.\n   * When the value is `null`, no Tab will be active.\n   */\n  value?: TabsTab.Value | undefined;\n  /**\n   * The default value. Use when the component is not controlled.\n   * When the value is `null`, no Tab will be active.\n   * @default 0\n   */\n  defaultValue?: TabsTab.Value | undefined;\n  /**\n   * The component orientation (layout flow direction).\n   * @default 'horizontal'\n   */\n  orientation?: TabsRoot.Orientation | undefined;\n  /**\n   * Callback invoked when new value is being set.\n   */\n  onValueChange?:\n    | ((value: TabsTab.Value, eventDetails: TabsRoot.ChangeEventDetails) => void)\n    | undefined;\n}\n\nexport type TabsRootChangeEventReason = typeof REASONS.none;\nexport type TabsRootChangeEventDetails = BaseUIChangeEventDetails<\n  TabsRoot.ChangeEventReason,\n  { activationDirection: TabsTab.ActivationDirection }\n>;\n\nexport namespace TabsRoot {\n  export type State = TabsRootState;\n  export type Props = TabsRootProps;\n  export type Orientation = TabsRootOrientation;\n  export type ChangeEventReason = TabsRootChangeEventReason;\n  export type ChangeEventDetails = TabsRootChangeEventDetails;\n}\n"
  },
  {
    "path": "packages/react/src/tabs/root/TabsRootContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport type { TabsTab } from '../tab/TabsTab';\nimport type { TabsRoot } from './TabsRoot';\n\nexport interface TabsRootContext {\n  /**\n   * The currently active tab's value.\n   */\n  value: TabsTab.Value;\n  /**\n   * Callback for setting new value.\n   */\n  onValueChange: (value: TabsTab.Value, eventDetails: TabsRoot.ChangeEventDetails) => void;\n  /**\n   * The component orientation (layout flow direction).\n   */\n  orientation: 'horizontal' | 'vertical';\n  /**\n   * Gets the element of the Tab with the given value.\n   */\n  getTabElementBySelectedValue: (selectedValue: TabsTab.Value | undefined) => HTMLElement | null;\n  /**\n   * Gets the `id` attribute of the Tab that corresponds to the given TabPanel value.\n   * @param (any) panelValue Value to find the Tab for.\n   */\n  getTabIdByPanelValue: (panelValue: TabsTab.Value) => string | undefined;\n  /**\n   * Gets the `id` attribute of the TabPanel that corresponds to the given Tab value.\n   * @param (any) tabValue Value to find the TabPanel for.\n   */\n  getTabPanelIdByValue: (tabValue: TabsTab.Value) => string | undefined;\n  registerMountedTabPanel: (panelValue: TabsTab.Value | number, panelId: string) => void;\n  setTabMap: (\n    map: Map<Node, (TabsTab.Metadata & { index?: number | null | undefined }) | null>,\n  ) => void;\n  unregisterMountedTabPanel: (panelValue: TabsTab.Value | number, panelId: string) => void;\n  /**\n   * The position of the active tab relative to the previously active tab.\n   */\n  tabActivationDirection: TabsTab.ActivationDirection;\n}\n\n/**\n * @internal\n */\nexport const TabsRootContext = React.createContext<TabsRootContext | undefined>(undefined);\n\nexport function useTabsRootContext() {\n  const context = React.useContext(TabsRootContext);\n  if (context === undefined) {\n    throw new Error(\n      'Base UI: TabsRootContext is missing. Tabs parts must be placed within <Tabs.Root>.',\n    );\n  }\n\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/tabs/root/TabsRootDataAttributes.ts",
    "content": "export enum TabsRootDataAttributes {\n  /**\n   * Indicates the direction of the activation (based on the previous active tab).\n   * @type {'left' | 'right' | 'up' | 'down' | 'none'}\n   */\n  activationDirection = 'data-activation-direction',\n  /**\n   * Indicates the orientation of the tabs.\n   * @type {'horizontal' | 'vertical'}\n   */\n  orientation = 'data-orientation',\n}\n"
  },
  {
    "path": "packages/react/src/tabs/root/stateAttributesMapping.ts",
    "content": "import type { TabsRootState } from './TabsRoot';\nimport type { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport { TabsRootDataAttributes } from './TabsRootDataAttributes';\n\nexport const tabsStateAttributesMapping: StateAttributesMapping<TabsRootState> = {\n  tabActivationDirection: (dir) => ({\n    [TabsRootDataAttributes.activationDirection]: dir,\n  }),\n};\n"
  },
  {
    "path": "packages/react/src/tabs/tab/TabsTab.test.tsx",
    "content": "import { Tabs } from '@base-ui/react/tabs';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Tabs.Tab />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Tabs.Tab value=\"1\" />, () => ({\n    refInstanceof: window.HTMLButtonElement,\n    testComponentPropWith: 'button',\n    button: true,\n    render: (node) =>\n      render(\n        <Tabs.Root>\n          <Tabs.List>{node}</Tabs.List>\n        </Tabs.Root>,\n      ),\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/tabs/tab/TabsTab.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { ownerDocument } from '@base-ui/utils/owner';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useBaseUiId } from '../../utils/useBaseUiId';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport type { BaseUIComponentProps, NativeButtonProps } from '../../utils/types';\nimport { useButton } from '../../use-button';\nimport { ACTIVE_COMPOSITE_ITEM } from '../../composite/constants';\nimport { useCompositeItem } from '../../composite/item/useCompositeItem';\nimport type { TabsRoot } from '../root/TabsRoot';\nimport { useTabsRootContext } from '../root/TabsRootContext';\nimport { useTabsListContext } from '../list/TabsListContext';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\nimport { activeElement, contains } from '../../floating-ui-react/utils';\n\n/**\n * An individual interactive tab button that toggles the corresponding panel.\n * Renders a `<button>` element.\n *\n * Documentation: [Base UI Tabs](https://base-ui.com/react/components/tabs)\n */\nexport const TabsTab = React.forwardRef(function TabsTab(\n  componentProps: TabsTab.Props,\n  forwardedRef: React.ForwardedRef<HTMLElement>,\n) {\n  const {\n    className,\n    disabled = false,\n    render,\n    value,\n    id: idProp,\n    nativeButton = true,\n    ...elementProps\n  } = componentProps;\n\n  const { value: activeTabValue, getTabPanelIdByValue, orientation } = useTabsRootContext();\n\n  const {\n    activateOnFocus,\n    highlightedTabIndex,\n    onTabActivation,\n    registerTabResizeObserverElement,\n    setHighlightedTabIndex,\n    tabsListElement,\n  } = useTabsListContext();\n\n  const id = useBaseUiId(idProp);\n\n  const tabMetadata = React.useMemo(() => ({ disabled, id, value }), [disabled, id, value]);\n\n  const {\n    compositeProps,\n    compositeRef,\n    index,\n    // hook is used instead of the CompositeItem component\n    // because the index is needed for Tab internals\n  } = useCompositeItem<TabsTab.Metadata>({\n    metadata: tabMetadata,\n  });\n\n  const active = value === activeTabValue;\n\n  const isNavigatingRef = React.useRef(false);\n  const tabElementRef = React.useRef<HTMLElement | null>(null);\n\n  React.useEffect(() => {\n    const tabElement = tabElementRef.current;\n    if (!tabElement) {\n      return undefined;\n    }\n\n    return registerTabResizeObserverElement(tabElement);\n  }, [registerTabResizeObserverElement]);\n\n  // Keep the highlighted item in sync with the currently active tab\n  // when the value prop changes externally (controlled mode)\n  useIsoLayoutEffect(() => {\n    if (isNavigatingRef.current) {\n      isNavigatingRef.current = false;\n      return;\n    }\n\n    if (!(active && index > -1 && highlightedTabIndex !== index)) {\n      return;\n    }\n\n    // If focus is currently within the tabs list, don't override the roving\n    // focus highlight. This keeps keyboard navigation relative to the focused\n    // item after an external/asynchronous selection change.\n    const listElement = tabsListElement;\n    if (listElement != null) {\n      const activeEl = activeElement(ownerDocument(listElement));\n      if (activeEl && contains(listElement, activeEl)) {\n        return;\n      }\n    }\n\n    // Don't highlight disabled tabs to prevent them from interfering with keyboard navigation.\n    // Keyboard focus (tabIndex) should remain on an enabled tab even when a disabled tab is selected.\n    if (!disabled) {\n      setHighlightedTabIndex(index);\n    }\n  }, [active, index, highlightedTabIndex, setHighlightedTabIndex, disabled, tabsListElement]);\n\n  const { getButtonProps, buttonRef } = useButton({\n    disabled,\n    native: nativeButton,\n    focusableWhenDisabled: true,\n  });\n\n  const tabPanelId = getTabPanelIdByValue(value);\n\n  const isPressingRef = React.useRef(false);\n  const isMainButtonRef = React.useRef(false);\n\n  function onClick(event: React.MouseEvent<HTMLButtonElement>) {\n    if (active || disabled) {\n      return;\n    }\n\n    onTabActivation(\n      value,\n      createChangeEventDetails(REASONS.none, event.nativeEvent, undefined, {\n        activationDirection: 'none',\n      }),\n    );\n  }\n\n  function onFocus(event: React.FocusEvent<HTMLButtonElement>) {\n    if (active) {\n      return;\n    }\n\n    // Only highlight enabled tabs when focused (disabled tabs remain focusable via focusableWhenDisabled).\n    if (index > -1 && !disabled) {\n      setHighlightedTabIndex(index);\n    }\n\n    if (disabled) {\n      return;\n    }\n\n    if (\n      activateOnFocus &&\n      (!isPressingRef.current || // keyboard or touch focus\n        (isPressingRef.current && isMainButtonRef.current)) // mouse focus\n    ) {\n      onTabActivation(\n        value,\n        createChangeEventDetails(REASONS.none, event.nativeEvent, undefined, {\n          activationDirection: 'none',\n        }),\n      );\n    }\n  }\n\n  function onPointerDown(event: React.PointerEvent<HTMLButtonElement>) {\n    if (active || disabled) {\n      return;\n    }\n\n    isPressingRef.current = true;\n\n    function handlePointerUp() {\n      isPressingRef.current = false;\n      isMainButtonRef.current = false;\n    }\n\n    if (!event.button || event.button === 0) {\n      isMainButtonRef.current = true;\n\n      const doc = ownerDocument(event.currentTarget);\n      doc.addEventListener('pointerup', handlePointerUp, { once: true });\n    }\n  }\n\n  const state: TabsTabState = {\n    disabled,\n    active,\n    orientation,\n  };\n\n  const element = useRenderElement('button', componentProps, {\n    state,\n    ref: [forwardedRef, buttonRef, compositeRef, tabElementRef],\n    props: [\n      compositeProps,\n      {\n        role: 'tab',\n        'aria-controls': tabPanelId,\n        'aria-selected': active,\n        id,\n        onClick,\n        onFocus,\n        onPointerDown,\n        [ACTIVE_COMPOSITE_ITEM as string]: active ? '' : undefined,\n        onKeyDownCapture() {\n          isNavigatingRef.current = true;\n        },\n      },\n      elementProps,\n      getButtonProps,\n    ],\n  });\n\n  return element;\n});\n\nexport type TabsTabValue = any | null;\n\nexport type TabsTabActivationDirection = 'left' | 'right' | 'up' | 'down' | 'none';\n\nexport interface TabsTabPosition {\n  left: number;\n  right: number;\n  top: number;\n  bottom: number;\n}\n\nexport interface TabsTabSize {\n  width: number;\n  height: number;\n}\n\nexport interface TabsTabMetadata {\n  disabled: boolean;\n  id: string | undefined;\n  value: TabsTab.Value | undefined;\n}\n\nexport interface TabsTabState {\n  /**\n   * Whether the component should ignore user interaction.\n   */\n  disabled: boolean;\n  /**\n   * Whether the component is active.\n   */\n  active: boolean;\n  /**\n   * The component orientation.\n   */\n  orientation: TabsRoot.Orientation;\n}\n\nexport interface TabsTabProps\n  extends NativeButtonProps, BaseUIComponentProps<'button', TabsTabState> {\n  /**\n   * The value of the Tab.\n   */\n  value: TabsTab.Value;\n  /**\n   * Whether the Tab is disabled.\n   *\n   * If a first Tab on a `<Tabs.List>` is disabled, it won't initially be selected.\n   * Instead, the next enabled Tab will be selected.\n   * However, it does not work like this during server-side rendering, as it is not known\n   * during pre-rendering which Tabs are disabled.\n   * To work around it, ensure that `defaultValue` or `value` on `<Tabs.Root>` is set to an enabled Tab's value.\n   */\n  disabled?: boolean | undefined;\n}\n\nexport namespace TabsTab {\n  export type Value = TabsTabValue;\n  export type ActivationDirection = TabsTabActivationDirection;\n  export type Position = TabsTabPosition;\n  export type Size = TabsTabSize;\n  export type Metadata = TabsTabMetadata;\n  export type State = TabsTabState;\n  export type Props = TabsTabProps;\n}\n"
  },
  {
    "path": "packages/react/src/tabs/tab/TabsTabDataAttributes.ts",
    "content": "export enum TabsTabDataAttributes {\n  /**\n   * Indicates the direction of the activation (based on the previous active tab).\n   * @type {'left' | 'right' | 'up' | 'down' | 'none'}\n   */\n  activationDirection = 'data-activation-direction',\n  /**\n   * Indicates the orientation of the tabs.\n   * @type {'horizontal' | 'vertical'}\n   */\n  orientation = 'data-orientation',\n  /**\n   * Present when the tab is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Present when the tab is active.\n   */\n  active = 'data-active',\n}\n"
  },
  {
    "path": "packages/react/src/temporal-adapter-date-fns/TemporalAdapterDateFns.test.ts",
    "content": "import { fr } from 'date-fns/locale/fr';\nimport { describeGregorianAdapter } from '#test-utils';\nimport { TemporalAdapterDateFns } from './TemporalAdapterDateFns';\n\ndescribe('TemporalAdapterDateFns', () => {\n  describeGregorianAdapter({\n    adapter: new TemporalAdapterDateFns(),\n    adapterFr: new TemporalAdapterDateFns({ locale: fr }),\n    setDefaultTimezone: null,\n    // The Date object doesn't contain a locale\n    createDateInFrenchLocale: (dateStr) => new Date(dateStr),\n  });\n});\n"
  },
  {
    "path": "packages/react/src/temporal-adapter-date-fns/TemporalAdapterDateFns.ts",
    "content": "'use client';\nimport { addDays } from 'date-fns/addDays';\nimport { addHours } from 'date-fns/addHours';\nimport { addMinutes } from 'date-fns/addMinutes';\nimport { addMonths } from 'date-fns/addMonths';\nimport { addSeconds } from 'date-fns/addSeconds';\nimport { addMilliseconds } from 'date-fns/addMilliseconds';\nimport { addWeeks } from 'date-fns/addWeeks';\nimport { addYears } from 'date-fns/addYears';\nimport { differenceInDays } from 'date-fns/differenceInDays';\nimport { differenceInHours } from 'date-fns/differenceInHours';\nimport { differenceInMinutes } from 'date-fns/differenceInMinutes';\nimport { differenceInMonths } from 'date-fns/differenceInMonths';\nimport { differenceInWeeks } from 'date-fns/differenceInWeeks';\nimport { differenceInYears } from 'date-fns/differenceInYears';\nimport { endOfDay } from 'date-fns/endOfDay';\nimport { endOfHour } from 'date-fns/endOfHour';\nimport { endOfMinute } from 'date-fns/endOfMinute';\nimport { endOfMonth } from 'date-fns/endOfMonth';\nimport { endOfSecond } from 'date-fns/endOfSecond';\nimport { endOfWeek } from 'date-fns/endOfWeek';\nimport { endOfYear } from 'date-fns/endOfYear';\nimport { format as dateFnsFormat } from 'date-fns/format';\nimport { getDate } from 'date-fns/getDate';\nimport { getDay } from 'date-fns/getDay';\nimport { getDaysInMonth } from 'date-fns/getDaysInMonth';\nimport { getHours } from 'date-fns/getHours';\nimport { getMilliseconds } from 'date-fns/getMilliseconds';\nimport { getMinutes } from 'date-fns/getMinutes';\nimport { getMonth } from 'date-fns/getMonth';\nimport { getSeconds } from 'date-fns/getSeconds';\nimport { getWeek } from 'date-fns/getWeek';\nimport { getYear } from 'date-fns/getYear';\nimport { isAfter } from 'date-fns/isAfter';\nimport { isBefore } from 'date-fns/isBefore';\nimport { isEqual } from 'date-fns/isEqual';\nimport { isSameDay } from 'date-fns/isSameDay';\nimport { isSameHour } from 'date-fns/isSameHour';\nimport { isSameYear } from 'date-fns/isSameYear';\nimport { isSameMonth } from 'date-fns/isSameMonth';\nimport { isValid } from 'date-fns/isValid';\nimport { isWithinInterval } from 'date-fns/isWithinInterval';\nimport { Locale as DateFnsLocale } from 'date-fns/locale';\nimport { enUS } from 'date-fns/locale/en-US';\nimport { parse } from 'date-fns/parse';\nimport { setDate } from 'date-fns/setDate';\nimport { setHours } from 'date-fns/setHours';\nimport { setMilliseconds } from 'date-fns/setMilliseconds';\nimport { setMinutes } from 'date-fns/setMinutes';\nimport { setMonth } from 'date-fns/setMonth';\nimport { setSeconds } from 'date-fns/setSeconds';\nimport { setYear } from 'date-fns/setYear';\nimport { startOfDay } from 'date-fns/startOfDay';\nimport { startOfHour } from 'date-fns/startOfHour';\nimport { startOfMinute } from 'date-fns/startOfMinute';\nimport { startOfMonth } from 'date-fns/startOfMonth';\nimport { startOfSecond } from 'date-fns/startOfSecond';\nimport { startOfYear } from 'date-fns/startOfYear';\nimport { startOfWeek } from 'date-fns/startOfWeek';\nimport { TZDate } from '@date-fns/tz';\nimport {\n  TemporalAdapterFormats,\n  DateBuilderReturnType,\n  TemporalTimezone,\n  TemporalAdapter,\n} from '../types/temporal';\n\nconst FORMATS: TemporalAdapterFormats = {\n  // Digit formats with leading zeroes\n  yearPadded: 'yyyy',\n  monthPadded: 'MM',\n  dayOfMonthPadded: 'dd',\n  hours24hPadded: 'HH',\n  hours12hPadded: 'hh',\n  minutesPadded: 'mm',\n  secondsPadded: 'ss',\n\n  // Digit formats without leading zeroes\n  dayOfMonth: 'd',\n  hours24h: 'H',\n  hours12h: 'h',\n\n  // Letter formats\n  month3Letters: 'MMM',\n  monthFullLetter: 'MMMM',\n  weekday: 'EEEE',\n  weekday3Letters: 'EEE',\n  weekday1Letter: 'EEEEE',\n  meridiem: 'a',\n\n  // Full formats\n  localizedDateWithFullMonthAndWeekDay: 'PPPP',\n  localizedNumericDate: 'P', // Note: Day and month are padded on enUS unlike Luxon\n};\n\n// TODO Temporal: Replace with `@base-ui/react/types` path when Temporal components will become public.\ndeclare module '@base-ui/react/types/temporal' {\n  interface TemporalSupportedObjectLookup {\n    'date-fns': Date;\n  }\n}\n\nexport class TemporalAdapterDateFns implements TemporalAdapter {\n  public isTimezoneCompatible = true;\n\n  public lib = 'date-fns';\n\n  private locale: DateFnsLocale;\n\n  public formats = FORMATS;\n\n  public escapedCharacters = { start: \"'\", end: \"'\" };\n\n  constructor({ locale }: TemporalAdapterDateFns.ConstructorParameters = {}) {\n    this.locale = locale ?? enUS;\n  }\n\n  public now = (timezone: TemporalTimezone) => {\n    if (timezone === 'system' || timezone === 'default') {\n      return new Date();\n    }\n\n    return TZDate.tz(timezone);\n  };\n\n  public date = <T extends string | null>(\n    value: T,\n    timezone: TemporalTimezone,\n  ): DateBuilderReturnType<T> => {\n    type R = DateBuilderReturnType<T>;\n    if (value === null) {\n      return null as unknown as R;\n    }\n\n    const date = new Date(value);\n    if (timezone === 'system' || timezone === 'default') {\n      return date as unknown as R;\n    }\n\n    // `new TZDate(value, timezone)` returns a date with the same timestamp `new Date(value)` would return,\n    // whereas we want to create that represents the string in the given timezone.\n    return new TZDate(\n      date.getFullYear(),\n      date.getMonth(),\n      date.getDate(),\n      date.getHours(),\n      date.getMinutes(),\n      date.getSeconds(),\n      date.getMilliseconds(),\n      timezone,\n    ) as unknown as R;\n  };\n\n  public parse = (value: string, format: string, timezone: TemporalTimezone): Date => {\n    const date = parse(value, format, new Date(), {\n      locale: this.locale,\n    });\n\n    if (timezone === 'system' || timezone === 'default') {\n      return date;\n    }\n\n    // `new TZDate(value, timezone)` returns a date with the same timestamp `new Date(value)` would return,\n    // whereas we want to create that represents the string in the given timezone.\n    return new TZDate(\n      date.getFullYear(),\n      date.getMonth(),\n      date.getDate(),\n      date.getHours(),\n      date.getMinutes(),\n      date.getSeconds(),\n      date.getMilliseconds(),\n      timezone,\n    );\n  };\n\n  public getTimezone = (value: Date): string => {\n    if (value instanceof TZDate) {\n      return value.timeZone ?? 'system';\n    }\n\n    return 'system';\n  };\n\n  public setTimezone = (value: Date, timezone: TemporalTimezone): Date => {\n    const isSystemTimezone = timezone === 'system' || timezone === 'default';\n\n    if (value instanceof TZDate) {\n      if (isSystemTimezone) {\n        return this.toJsDate(value);\n      }\n      return value.withTimeZone(timezone);\n    }\n\n    if (isSystemTimezone) {\n      return value;\n    }\n    return new TZDate(value, timezone);\n  };\n\n  public toJsDate = (value: Date) => {\n    if (value instanceof TZDate) {\n      return new Date(value.getTime());\n    }\n    return value;\n  };\n\n  public getCurrentLocaleCode = () => {\n    return this.locale.code;\n  };\n\n  public isValid = (value: Date | null): value is Date => {\n    if (value == null) {\n      return false;\n    }\n\n    return isValid(value);\n  };\n\n  public format = (value: Date, formatKey: keyof TemporalAdapterFormats) => {\n    return this.formatByString(value, this.formats[formatKey]);\n  };\n\n  public formatByString = (value: Date, format: string) => {\n    return dateFnsFormat(value, format, { locale: this.locale });\n  };\n\n  public isEqual = (value: Date | null, comparing: Date | null) => {\n    if (value === null && comparing === null) {\n      return true;\n    }\n\n    if (value === null || comparing === null) {\n      return false;\n    }\n\n    return isEqual(value, comparing);\n  };\n\n  public isSameYear = (value: Date, comparing: Date) => {\n    return isSameYear(value, comparing);\n  };\n\n  public isSameMonth = (value: Date, comparing: Date) => {\n    return isSameMonth(value, comparing);\n  };\n\n  public isSameDay = (value: Date, comparing: Date) => {\n    return isSameDay(value, comparing);\n  };\n\n  public isSameHour = (value: Date, comparing: Date) => {\n    return isSameHour(value, comparing);\n  };\n\n  public isAfter = (value: Date, comparing: Date) => {\n    return isAfter(value, comparing);\n  };\n\n  public isBefore = (value: Date, comparing: Date) => {\n    return isBefore(value, comparing);\n  };\n\n  public isWithinRange = (value: Date, [start, end]: [Date, Date]) => {\n    return isWithinInterval(value, { start, end });\n  };\n\n  public startOfYear = (value: Date) => {\n    return startOfYear(value);\n  };\n\n  public startOfMonth = (value: Date) => {\n    return startOfMonth(value);\n  };\n\n  public startOfWeek = (value: Date) => {\n    return startOfWeek(value, { locale: this.locale });\n  };\n\n  public startOfDay = (value: Date) => {\n    return startOfDay(value);\n  };\n\n  public startOfHour = (value: Date) => {\n    return startOfHour(value);\n  };\n\n  public startOfMinute = (value: Date) => {\n    return startOfMinute(value);\n  };\n\n  public startOfSecond = (value: Date) => {\n    return startOfSecond(value);\n  };\n\n  public endOfYear = (value: Date): Date => {\n    return endOfYear(value);\n  };\n\n  public endOfMonth = (value: Date): Date => {\n    return endOfMonth(value);\n  };\n\n  public endOfWeek = (value: Date): Date => {\n    return endOfWeek(value, { locale: this.locale });\n  };\n\n  public endOfDay = (value: Date): Date => {\n    return endOfDay(value);\n  };\n\n  public endOfHour = (value: Date) => {\n    return endOfHour(value);\n  };\n\n  public endOfMinute = (value: Date) => {\n    return endOfMinute(value);\n  };\n\n  public endOfSecond = (value: Date) => {\n    return endOfSecond(value);\n  };\n\n  public addYears = (value: Date, amount: number): Date => {\n    return addYears(value, amount);\n  };\n\n  public addMonths = (value: Date, amount: number): Date => {\n    return addMonths(value, amount);\n  };\n\n  public addWeeks = (value: Date, amount: number): Date => {\n    return addWeeks(value, amount);\n  };\n\n  public addDays = (value: Date, amount: number): Date => {\n    return addDays(value, amount);\n  };\n\n  public addHours = (value: Date, amount: number): Date => {\n    return addHours(value, amount);\n  };\n\n  public addMinutes = (value: Date, amount: number): Date => {\n    return addMinutes(value, amount);\n  };\n\n  public addSeconds = (value: Date, amount: number): Date => {\n    return addSeconds(value, amount);\n  };\n\n  public addMilliseconds = (value: Date, amount: number) => {\n    return addMilliseconds(value, amount);\n  };\n\n  public getYear = (value: Date): number => {\n    return getYear(value);\n  };\n\n  public getMonth = (value: Date): number => {\n    return getMonth(value);\n  };\n\n  public getDate = (value: Date): number => {\n    return getDate(value);\n  };\n\n  public getHours = (value: Date): number => {\n    return getHours(value);\n  };\n\n  public getMinutes = (value: Date): number => {\n    return getMinutes(value);\n  };\n\n  public getSeconds = (value: Date): number => {\n    return getSeconds(value);\n  };\n\n  public getMilliseconds = (value: Date): number => {\n    return getMilliseconds(value);\n  };\n\n  public getTime = (value: Date): number => {\n    return value.getTime();\n  };\n\n  public setYear = (value: Date, year: number): Date => {\n    return setYear(value, year);\n  };\n\n  public setMonth = (value: Date, month: number): Date => {\n    return setMonth(value, month);\n  };\n\n  public setDate = (value: Date, date: number): Date => {\n    return setDate(value, date);\n  };\n\n  public setHours = (value: Date, hours: number): Date => {\n    return setHours(value, hours);\n  };\n\n  public setMinutes = (value: Date, minutes: number): Date => {\n    return setMinutes(value, minutes);\n  };\n\n  public setSeconds = (value: Date, seconds: number): Date => {\n    return setSeconds(value, seconds);\n  };\n\n  public setMilliseconds = (value: Date, milliseconds: number): Date => {\n    return setMilliseconds(value, milliseconds);\n  };\n\n  public differenceInYears = (value: Date, comparing: Date): number => {\n    return differenceInYears(value, comparing);\n  };\n\n  public differenceInMonths = (value: Date, comparing: Date): number => {\n    return differenceInMonths(value, comparing);\n  };\n\n  public differenceInWeeks = (value: Date, comparing: Date): number => {\n    return differenceInWeeks(value, comparing);\n  };\n\n  public differenceInDays = (value: Date, comparing: Date): number => {\n    return differenceInDays(value, comparing);\n  };\n\n  public differenceInHours = (value: Date, comparing: Date): number => {\n    return differenceInHours(value, comparing);\n  };\n\n  public differenceInMinutes = (value: Date, comparing: Date): number => {\n    return differenceInMinutes(value, comparing);\n  };\n\n  public getDaysInMonth = (value: Date): number => {\n    return getDaysInMonth(value);\n  };\n\n  public getWeekNumber = (value: Date) => {\n    return getWeek(value, { locale: this.locale });\n  };\n\n  public getDayOfWeek = (value: Date) => {\n    const weekStartsOn = this.locale.options?.weekStartsOn ?? 0;\n    return ((getDay(value) + 7 - weekStartsOn) % 7) + 1;\n  };\n}\n\nexport namespace TemporalAdapterDateFns {\n  export interface ConstructorParameters {\n    /**\n     * The locale to use for formatting and parsing dates.\n     * @default enUS\n     */\n    locale?: DateFnsLocale | undefined;\n  }\n}\n"
  },
  {
    "path": "packages/react/src/temporal-adapter-date-fns/index.ts",
    "content": "export { TemporalAdapterDateFns } from './TemporalAdapterDateFns';\n"
  },
  {
    "path": "packages/react/src/temporal-adapter-luxon/TemporalAdapterLuxon.test.ts",
    "content": "// TODO: Remove if temporal adapters are supported\n// @ts-nocheck No types available\nimport { DateTime, Settings } from 'luxon';\nimport { describeGregorianAdapter } from '#test-utils';\nimport { TemporalAdapterLuxon } from './TemporalAdapterLuxon';\n\ndescribe('TemporalAdapterLuxon', () => {\n  describeGregorianAdapter({\n    adapter: new TemporalAdapterLuxon(),\n    adapterFr: new TemporalAdapterLuxon({ locale: 'fr' }),\n    setDefaultTimezone: (timezone) => {\n      Settings.defaultZone = timezone ?? 'system';\n    },\n    createDateInFrenchLocale: (dateStr) => DateTime.fromISO(dateStr, { locale: 'fr' }),\n  });\n});\n"
  },
  {
    "path": "packages/react/src/temporal-adapter-luxon/TemporalAdapterLuxon.ts",
    "content": "// Intentionally ignore TS issues in this file to avoid docs being built with `| DateTime`\n// TODO: Remove if temporal adapters are supported\n// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\n'use client';\nimport { DateTime, Info } from 'luxon';\nimport {\n  TemporalAdapterFormats,\n  DateBuilderReturnType,\n  TemporalTimezone,\n  TemporalAdapter,\n} from '../types';\n\nconst FORMATS: TemporalAdapterFormats = {\n  // Digit formats with leading zeroes\n  yearPadded: 'yyyy',\n  monthPadded: 'MM',\n  dayOfMonthPadded: 'dd',\n  hours24hPadded: 'HH',\n  hours12hPadded: 'hh',\n  minutesPadded: 'mm',\n  secondsPadded: 'ss',\n\n  // Digit formats without leading zeroes\n  dayOfMonth: 'd',\n  hours24h: 'H',\n  hours12h: 'h',\n\n  // Letter formats\n  month3Letters: 'MMM',\n  monthFullLetter: 'MMMM',\n  weekday: 'cccc',\n  weekday3Letters: 'ccc',\n  weekday1Letter: 'ccccc',\n  meridiem: 'a',\n\n  // Full formats\n  localizedDateWithFullMonthAndWeekDay: 'DDDD',\n  localizedNumericDate: 'D',\n  fullMonthAndYear: 'MMMM yyyy',\n};\n\n// Temporarily disabled to avoid docs being built with `| DateTime`\n// declare module '@base-ui/react/types' {\n//   interface TemporalSupportedObjectLookup {\n//     luxon: DateTime;\n//   }\n// }\n\nexport class TemporalAdapterLuxon implements TemporalAdapter {\n  public isTimezoneCompatible = true;\n\n  public lib = 'luxon';\n\n  private locale: string;\n\n  public formats: TemporalAdapterFormats = FORMATS;\n\n  public escapedCharacters = { start: \"'\", end: \"'\" };\n\n  constructor({ locale }: TemporalAdapterLuxon.ConstructorParameters = {}) {\n    this.locale = locale ?? 'en-US';\n  }\n\n  private setLocaleToValue = (value: DateTime) => {\n    const expectedLocale = this.getCurrentLocaleCode();\n    if (expectedLocale === value.locale) {\n      return value;\n    }\n\n    return value.setLocale(expectedLocale);\n  };\n\n  public now = (timezone: TemporalTimezone) => {\n    // @ts-expect-error locale is not identified as a field\n    return DateTime.fromJSDate(new Date(), { locale: this.locale, zone: timezone });\n  };\n\n  public date = <T extends string | null>(\n    value: T,\n    timezone: TemporalTimezone,\n  ): DateBuilderReturnType<T> => {\n    if (value === null) {\n      return null;\n    }\n\n    return DateTime.fromISO(value, { locale: this.locale, zone: timezone });\n  };\n\n  public parse = (value: string, format: string, timezone: TemporalTimezone): DateTime => {\n    return DateTime.fromFormat(value, format, { locale: this.locale, zone: timezone });\n  };\n\n  public getTimezone = (value: DateTime): string => {\n    // When using the system zone, we want to return \"system\", not something like \"Europe/Paris\"\n    if (value.zone.type === 'system') {\n      return 'system';\n    }\n\n    return value.zoneName!;\n  };\n\n  public setTimezone = (value: DateTime, timezone: TemporalTimezone): DateTime => {\n    if (!value.zone.equals(Info.normalizeZone(timezone))) {\n      return value.setZone(timezone);\n    }\n\n    return value;\n  };\n\n  public toJsDate = (value: DateTime) => {\n    return value.toJSDate();\n  };\n\n  public getCurrentLocaleCode = () => {\n    return this.locale;\n  };\n\n  public isValid = (value: DateTime | null): value is DateTime => {\n    if (value == null) {\n      return false;\n    }\n\n    return value.isValid;\n  };\n\n  public format = (value: DateTime, formatKey: keyof TemporalAdapterFormats) => {\n    return this.formatByString(value, this.formats[formatKey]);\n  };\n\n  public formatByString = (value: DateTime, format: string) => {\n    return value.setLocale(this.locale).toFormat(format);\n  };\n\n  public isEqual = (value: DateTime | null, comparing: DateTime | null) => {\n    if (value === null && comparing === null) {\n      return true;\n    }\n\n    if (value === null || comparing === null) {\n      return false;\n    }\n\n    return +value === +comparing;\n  };\n\n  public isSameYear = (value: DateTime, comparing: DateTime) => {\n    const comparingInValueTimezone = this.setTimezone(comparing, this.getTimezone(value));\n    return value.hasSame(comparingInValueTimezone, 'year');\n  };\n\n  public isSameMonth = (value: DateTime, comparing: DateTime) => {\n    const comparingInValueTimezone = this.setTimezone(comparing, this.getTimezone(value));\n    return value.hasSame(comparingInValueTimezone, 'month');\n  };\n\n  public isSameDay = (value: DateTime, comparing: DateTime) => {\n    const comparingInValueTimezone = this.setTimezone(comparing, this.getTimezone(value));\n    return value.hasSame(comparingInValueTimezone, 'day');\n  };\n\n  public isSameHour = (value: DateTime, comparing: DateTime) => {\n    const comparingInValueTimezone = this.setTimezone(comparing, this.getTimezone(value));\n    return value.hasSame(comparingInValueTimezone, 'hour');\n  };\n\n  public isAfter = (value: DateTime, comparing: DateTime) => {\n    return value > comparing;\n  };\n\n  public isBefore = (value: DateTime, comparing: DateTime) => {\n    return value < comparing;\n  };\n\n  public isWithinRange = (value: DateTime, [start, end]: [DateTime, DateTime]) => {\n    if (this.isAfter(value, start) && this.isBefore(value, end)) {\n      return true;\n    }\n\n    return this.isEqual(value, start) || this.isEqual(value, end);\n  };\n\n  public startOfYear = (value: DateTime) => {\n    return value.startOf('year');\n  };\n\n  public startOfMonth = (value: DateTime) => {\n    return value.startOf('month');\n  };\n\n  public startOfWeek = (value: DateTime) => {\n    return this.setLocaleToValue(value).startOf('week', { useLocaleWeeks: true });\n  };\n\n  public startOfDay = (value: DateTime) => {\n    return value.startOf('day');\n  };\n\n  public startOfHour = (value: DateTime) => {\n    return value.startOf('hour');\n  };\n\n  public startOfMinute = (value: DateTime) => {\n    return value.startOf('minute');\n  };\n\n  public startOfSecond = (value: DateTime) => {\n    return value.startOf('second');\n  };\n\n  public endOfYear = (value: DateTime) => {\n    return value.endOf('year');\n  };\n\n  public endOfMonth = (value: DateTime) => {\n    return value.endOf('month');\n  };\n\n  public endOfWeek = (value: DateTime) => {\n    return this.setLocaleToValue(value).endOf('week', { useLocaleWeeks: true });\n  };\n\n  public endOfDay = (value: DateTime) => {\n    return value.endOf('day');\n  };\n\n  public endOfHour = (value: DateTime) => {\n    return value.endOf('hour');\n  };\n\n  public endOfMinute = (value: DateTime) => {\n    return value.endOf('minute');\n  };\n\n  public endOfSecond = (value: DateTime) => {\n    return value.endOf('second');\n  };\n\n  public addYears = (value: DateTime, amount: number) => {\n    return value.plus({ years: amount });\n  };\n\n  public addMonths = (value: DateTime, amount: number) => {\n    return value.plus({ months: amount });\n  };\n\n  public addWeeks = (value: DateTime, amount: number) => {\n    return value.plus({ weeks: amount });\n  };\n\n  public addDays = (value: DateTime, amount: number) => {\n    return value.plus({ days: amount });\n  };\n\n  public addHours = (value: DateTime, amount: number) => {\n    return value.plus({ hours: amount });\n  };\n\n  public addMinutes = (value: DateTime, amount: number) => {\n    return value.plus({ minutes: amount });\n  };\n\n  public addSeconds = (value: DateTime, amount: number) => {\n    return value.plus({ seconds: amount });\n  };\n\n  public addMilliseconds = (value: DateTime, amount: number) => {\n    return value.plus({ milliseconds: amount });\n  };\n\n  public getYear = (value: DateTime) => {\n    return value.get('year');\n  };\n\n  public getMonth = (value: DateTime) => {\n    // See https://github.com/moment/luxon/blob/master/docs/moment.md#major-functional-differences\n    return value.get('month') - 1;\n  };\n\n  public getDate = (value: DateTime) => {\n    return value.get('day');\n  };\n\n  public getHours = (value: DateTime) => {\n    return value.get('hour');\n  };\n\n  public getMinutes = (value: DateTime) => {\n    return value.get('minute');\n  };\n\n  public getSeconds = (value: DateTime) => {\n    return value.get('second');\n  };\n\n  public getMilliseconds = (value: DateTime) => {\n    return value.get('millisecond');\n  };\n\n  public getTime = (value: DateTime): number => {\n    return value.toMillis();\n  };\n\n  public setYear = (value: DateTime, year: number) => {\n    return value.set({ year });\n  };\n\n  public setMonth = (value: DateTime, month: number) => {\n    // See https://github.com/moment/luxon/blob/master/docs/moment.md#major-functional-differences\n    return value.set({ month: month + 1 });\n  };\n\n  public setDate = (value: DateTime, date: number) => {\n    return value.set({ day: date });\n  };\n\n  public setHours = (value: DateTime, hours: number) => {\n    return value.set({ hour: hours });\n  };\n\n  public setMinutes = (value: DateTime, minutes: number) => {\n    return value.set({ minute: minutes });\n  };\n\n  public setSeconds = (value: DateTime, seconds: number) => {\n    return value.set({ second: seconds });\n  };\n\n  public setMilliseconds = (value: DateTime, milliseconds: number) => {\n    return value.set({ millisecond: milliseconds });\n  };\n\n  public differenceInYears = (value: DateTime, comparing: DateTime): number => {\n    return Math.floor(value.diff(comparing, 'years').as('years'));\n  };\n\n  public differenceInMonths = (value: DateTime, comparing: DateTime): number => {\n    return Math.floor(value.diff(comparing, 'months').as('months'));\n  };\n\n  public differenceInWeeks = (value: DateTime, comparing: DateTime): number => {\n    return Math.floor(value.diff(comparing, 'weeks').as('weeks'));\n  };\n\n  public differenceInDays = (value: DateTime, comparing: DateTime): number => {\n    return Math.floor(value.diff(comparing, 'days').as('days'));\n  };\n\n  public differenceInHours = (value: DateTime, comparing: DateTime): number => {\n    return Math.floor(value.diff(comparing, 'hours').as('hours'));\n  };\n\n  public differenceInMinutes = (value: DateTime, comparing: DateTime): number => {\n    return Math.floor(value.diff(comparing, 'minutes').as('minutes'));\n  };\n\n  public getDaysInMonth = (value: DateTime) => {\n    return value.daysInMonth!;\n  };\n\n  public getWeekNumber = (value: DateTime) => {\n    /* istanbul ignore next */\n    return value.localWeekNumber ?? value.weekNumber;\n  };\n\n  public getDayOfWeek = (value: DateTime) => {\n    /* istanbul ignore next */\n    return value.localWeekday ?? value.weekday;\n  };\n}\n\nexport namespace TemporalAdapterLuxon {\n  export interface ConstructorParameters {\n    /**\n     * The locale to use for formatting and parsing dates.\n     * @default 'en-US'\n     */\n    locale?: string | undefined;\n  }\n}\n"
  },
  {
    "path": "packages/react/src/temporal-adapter-luxon/index.ts",
    "content": "export { TemporalAdapterLuxon } from './TemporalAdapterLuxon';\n"
  },
  {
    "path": "packages/react/src/temporal-adapter-provider/TemporalAdapterContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { TemporalAdapter } from '../types/temporal';\nimport { TemporalAdapterDateFns } from '../temporal-adapter-date-fns/TemporalAdapterDateFns';\n\nexport type TemporalAdapterContext = {\n  adapter: TemporalAdapter;\n};\n\n/**\n * @internal\n */\nexport const TemporalAdapterContext = React.createContext<TemporalAdapterContext | undefined>({\n  adapter: new TemporalAdapterDateFns(),\n});\n\nexport function useTemporalAdapter() {\n  const context = React.useContext(TemporalAdapterContext);\n  if (context === undefined) {\n    throw new Error(\n      'Base UI: TemporalAdapterContext is missing. Temporal components must be place within <TemporalAdapterProvider />',\n    );\n  }\n\n  return context.adapter;\n}\n"
  },
  {
    "path": "packages/react/src/temporal-adapter-provider/TemporalAdapterProvider.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { TemporalAdapter } from '../types/temporal';\nimport { TemporalAdapterContext } from './TemporalAdapterContext';\n\n/**\n * Defines the date library adapter for Base UI temporal components.\n */\nexport const TemporalAdapterProvider: React.FC<TemporalAdapterProvider.Props> =\n  function DateAdapterProvider(props: TemporalAdapterProvider.Props) {\n    const { children, adapter } = props;\n\n    const contextValue = React.useMemo(() => ({ adapter }), [adapter]);\n\n    return (\n      <TemporalAdapterContext.Provider value={contextValue}>\n        {children}\n      </TemporalAdapterContext.Provider>\n    );\n  };\n\nexport namespace TemporalAdapterProvider {\n  export interface Props {\n    children?: React.ReactNode;\n    /**\n     * The date library adapter.\n     */\n    adapter: TemporalAdapter;\n  }\n}\n"
  },
  {
    "path": "packages/react/src/temporal-adapter-provider/index.ts",
    "content": "export { TemporalAdapterProvider } from './TemporalAdapterProvider';\n"
  },
  {
    "path": "packages/react/src/toast/action/ToastAction.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { Toast } from '@base-ui/react/toast';\nimport { screen } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance } from '#test-utils';\nimport { List, Button } from '../utils/test-utils';\n\ndescribe('<Toast.Action />', () => {\n  const { render } = createRenderer();\n\n  const toast: Toast.Root.ToastObject = {\n    id: 'test',\n    title: 'title',\n  };\n\n  describeConformance(<Toast.Action>action</Toast.Action>, () => ({\n    refInstanceof: window.HTMLButtonElement,\n    testComponentPropWith: 'button',\n    button: true,\n    render(node) {\n      return render(\n        <Toast.Provider>\n          <Toast.Viewport>\n            <Toast.Root toast={toast}>{node}</Toast.Root>\n          </Toast.Viewport>\n        </Toast.Provider>,\n      );\n    },\n  }));\n\n  it('performs an action when clicked', async () => {\n    const { user } = await render(\n      <Toast.Provider>\n        <Toast.Viewport>\n          <List />\n        </Toast.Viewport>\n        <Button />\n      </Toast.Provider>,\n    );\n\n    const button = screen.getByRole('button', { name: 'add' });\n\n    await user.click(button);\n\n    expect(screen.getByTestId('action').id).toBe('action');\n  });\n\n  it('does not render if it has no children', async () => {\n    function AddButton() {\n      const { add } = Toast.useToastManager();\n      return (\n        <button\n          type=\"button\"\n          onClick={() =>\n            add({\n              actionProps: {\n                children: undefined,\n              },\n            })\n          }\n        >\n          add\n        </button>\n      );\n    }\n\n    const { user } = await render(\n      <Toast.Provider>\n        <Toast.Viewport>\n          <List />\n        </Toast.Viewport>\n        <AddButton />\n      </Toast.Provider>,\n    );\n\n    const button = screen.getByRole('button', { name: 'add' });\n    await user.click(button);\n\n    const actionElement = screen.queryByTestId('action');\n    expect(actionElement).toBe(null);\n  });\n});\n"
  },
  {
    "path": "packages/react/src/toast/action/ToastAction.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport type { BaseUIComponentProps, NativeButtonProps } from '../../utils/types';\nimport { useToastRootContext } from '../root/ToastRootContext';\nimport { useButton } from '../../use-button/useButton';\nimport { useRenderElement } from '../../utils/useRenderElement';\n\n/**\n * Performs an action when clicked.\n * Renders a `<button>` element.\n *\n * Documentation: [Base UI Toast](https://base-ui.com/react/components/toast)\n */\nexport const ToastAction = React.forwardRef(function ToastAction(\n  componentProps: ToastAction.Props,\n  forwardedRef: React.ForwardedRef<HTMLButtonElement>,\n) {\n  const { render, className, disabled, nativeButton = true, ...elementProps } = componentProps;\n\n  const { toast } = useToastRootContext();\n\n  const computedChildren = toast.actionProps?.children ?? elementProps.children;\n  const shouldRender = Boolean(computedChildren);\n\n  const { getButtonProps, buttonRef } = useButton({\n    disabled,\n    native: nativeButton,\n  });\n\n  const state: ToastActionState = {\n    type: toast.type,\n  };\n\n  const element = useRenderElement('button', componentProps, {\n    ref: [forwardedRef, buttonRef],\n    state,\n    props: [\n      elementProps,\n      toast.actionProps,\n      getButtonProps,\n      {\n        children: computedChildren,\n      },\n    ],\n  });\n\n  if (!shouldRender) {\n    return null;\n  }\n\n  return element;\n});\n\nexport interface ToastActionState {\n  /**\n   * The type of the toast.\n   */\n  type: string | undefined;\n}\n\nexport interface ToastActionProps\n  extends NativeButtonProps, BaseUIComponentProps<'button', ToastActionState> {}\n\nexport namespace ToastAction {\n  export type State = ToastActionState;\n  export type Props = ToastActionProps;\n}\n"
  },
  {
    "path": "packages/react/src/toast/action/ToastActionDataAttributes.ts",
    "content": "export enum ToastActionDataAttributes {\n  /**\n   * The type of the toast.\n   * @type {string}\n   */\n  type = 'data-type',\n}\n"
  },
  {
    "path": "packages/react/src/toast/arrow/ToastArrow.test.tsx",
    "content": "import { Toast } from '@base-ui/react/toast';\nimport { createRenderer, describeConformance } from '#test-utils';\n\nconst toast: Toast.Root.ToastObject = {\n  id: 'test',\n  title: 'Toast title',\n};\n\ndescribe('<Toast.Arrow />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Toast.Arrow />, () => ({\n    refInstanceof: window.Element,\n    render(node) {\n      return render(\n        <Toast.Provider>\n          <Toast.Positioner toast={toast}>{node}</Toast.Positioner>\n        </Toast.Provider>,\n      );\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/toast/arrow/ToastArrow.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useToastPositionerContext } from '../positioner/ToastPositionerContext';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport type { Side, Align } from '../../utils/useAnchorPositioning';\nimport { useRenderElement } from '../../utils/useRenderElement';\n\n/**\n * Displays an element positioned against the toast anchor.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Toast](https://base-ui.com/react/components/toast)\n */\nexport const ToastArrow = React.forwardRef(function ToastArrow(\n  componentProps: ToastArrow.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { className, render, ...elementProps } = componentProps;\n\n  const { arrowRef, side, align, arrowUncentered, arrowStyles } = useToastPositionerContext();\n\n  const state: ToastArrowState = {\n    side,\n    align,\n    uncentered: arrowUncentered,\n  };\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: [forwardedRef, arrowRef],\n    props: [{ style: arrowStyles, 'aria-hidden': true }, elementProps],\n  });\n\n  return element;\n});\n\nexport interface ToastArrowState {\n  /**\n   * The side of the anchor the component is placed on.\n   */\n  side: Side;\n  /**\n   * The alignment of the component relative to the anchor.\n   */\n  align: Align;\n  /**\n   * Whether the arrow cannot be centered on the anchor.\n   */\n  uncentered: boolean;\n}\n\nexport interface ToastArrowProps extends BaseUIComponentProps<'div', ToastArrowState> {}\n\nexport namespace ToastArrow {\n  export type State = ToastArrowState;\n  export type Props = ToastArrowProps;\n}\n"
  },
  {
    "path": "packages/react/src/toast/arrow/ToastArrowDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum ToastArrowDataAttributes {\n  /**\n   * Indicates which side the toast is positioned relative to the anchor.\n   * @type {'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'}\n   */\n  side = CommonPopupDataAttributes.side,\n  /**\n   * Indicates how the toast is aligned relative to specified side.\n   * @type {'start' | 'center' | 'end'}\n   */\n  align = CommonPopupDataAttributes.align,\n  /**\n   * Present when the toast arrow is uncentered.\n   */\n  uncentered = 'data-uncentered',\n}\n"
  },
  {
    "path": "packages/react/src/toast/close/ToastClose.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { Toast } from '@base-ui/react/toast';\nimport { act, screen } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance } from '#test-utils';\nimport { List, Button } from '../utils/test-utils';\n\ndescribe('<Toast.Close />', () => {\n  const { render } = createRenderer();\n\n  const toast: Toast.Root.ToastObject = {\n    id: 'test',\n    title: 'title',\n  };\n\n  describeConformance(<Toast.Close />, () => ({\n    refInstanceof: window.HTMLButtonElement,\n    testComponentPropWith: 'button',\n    button: true,\n    render(node) {\n      return render(\n        <Toast.Provider>\n          <Toast.Viewport>\n            <Toast.Root toast={toast}>{node}</Toast.Root>\n          </Toast.Viewport>\n        </Toast.Provider>,\n      );\n    },\n  }));\n\n  it('closes the toast when clicked', async () => {\n    const { user } = await render(\n      <Toast.Provider>\n        <Toast.Viewport data-testid=\"viewport\">\n          <List />\n        </Toast.Viewport>\n        <Button />\n      </Toast.Provider>,\n    );\n\n    const button = screen.getByRole('button', { name: 'add' });\n    const viewport = screen.getByTestId('viewport');\n\n    await user.click(button);\n\n    expect(screen.getByTestId('title')).not.toBe(null);\n\n    await act(async () => {\n      viewport.focus();\n    });\n\n    const closeButton = screen.getByRole('button', { name: 'close-press' });\n\n    await user.click(closeButton);\n\n    expect(screen.queryByTestId('title')).toBe(null);\n  });\n});\n"
  },
  {
    "path": "packages/react/src/toast/close/ToastClose.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport type { BaseUIComponentProps, NativeButtonProps } from '../../utils/types';\nimport { useToastRootContext } from '../root/ToastRootContext';\nimport { useToastProviderContext } from '../provider/ToastProviderContext';\nimport { useButton } from '../../use-button/useButton';\nimport { useRenderElement } from '../../utils/useRenderElement';\n\n/**\n * Closes the toast when clicked.\n * Renders a `<button>` element.\n *\n * Documentation: [Base UI Toast](https://base-ui.com/react/components/toast)\n */\nexport const ToastClose = React.forwardRef(function ToastClose(\n  componentProps: ToastClose.Props,\n  forwardedRef: React.ForwardedRef<HTMLButtonElement>,\n) {\n  const { render, className, disabled, nativeButton = true, ...elementProps } = componentProps;\n\n  const store = useToastProviderContext();\n  const { toast } = useToastRootContext();\n  const expanded = store.useState('expanded');\n\n  const [hasFocus, setHasFocus] = React.useState(false);\n\n  const { getButtonProps, buttonRef } = useButton({\n    disabled,\n    native: nativeButton,\n  });\n\n  const state: ToastCloseState = {\n    type: toast.type,\n  };\n\n  const element = useRenderElement('button', componentProps, {\n    ref: [forwardedRef, buttonRef],\n    state,\n    props: [\n      {\n        'aria-hidden': !expanded && !hasFocus,\n        onClick() {\n          store.closeToast(toast.id);\n        },\n        onFocus() {\n          setHasFocus(true);\n        },\n        onBlur() {\n          setHasFocus(false);\n        },\n      },\n      elementProps,\n      getButtonProps,\n    ],\n  });\n\n  return element;\n});\n\nexport interface ToastCloseState {\n  /**\n   * The type of the toast.\n   */\n  type: string | undefined;\n}\n\nexport interface ToastCloseProps\n  extends NativeButtonProps, BaseUIComponentProps<'button', ToastCloseState> {}\n\nexport namespace ToastClose {\n  export type State = ToastCloseState;\n  export type Props = ToastCloseProps;\n}\n"
  },
  {
    "path": "packages/react/src/toast/close/ToastCloseDataAttributes.ts",
    "content": "export enum ToastCloseDataAttributes {\n  /**\n   * The type of the toast.\n   * @type {string}\n   */\n  type = 'data-type',\n}\n"
  },
  {
    "path": "packages/react/src/toast/content/ToastContent.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useToastRootContext } from '../root/ToastRootContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\n\n/**\n * A container for the contents of a toast.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Toast](https://base-ui.com/react/components/toast)\n */\nexport const ToastContent = React.forwardRef(function ToastContent(\n  componentProps: ToastContent.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, ...elementProps } = componentProps;\n\n  const { visibleIndex, expanded, recalculateHeight } = useToastRootContext();\n\n  const contentRef = React.useRef<HTMLDivElement | null>(null);\n\n  useIsoLayoutEffect(() => {\n    const node = contentRef.current;\n    if (!node) {\n      return undefined;\n    }\n\n    recalculateHeight();\n\n    if (typeof ResizeObserver !== 'function' || typeof MutationObserver !== 'function') {\n      return undefined;\n    }\n\n    const resizeObserver = new ResizeObserver(() => recalculateHeight(true));\n    const mutationObserver = new MutationObserver(() => recalculateHeight(true));\n\n    resizeObserver.observe(node);\n    mutationObserver.observe(node, { childList: true, subtree: true, characterData: true });\n\n    return () => {\n      resizeObserver.disconnect();\n      mutationObserver.disconnect();\n    };\n  }, [recalculateHeight]);\n\n  const behind = visibleIndex > 0;\n\n  const state: ToastContentState = {\n    expanded,\n    behind,\n  };\n\n  const element = useRenderElement('div', componentProps, {\n    ref: [forwardedRef, contentRef],\n    state,\n    props: elementProps,\n  });\n\n  return element;\n});\n\nexport interface ToastContentState {\n  /**\n   * Whether the toast viewport is expanded.\n   */\n  expanded: boolean;\n  /**\n   * Whether the toast is behind the frontmost toast in the stack.\n   */\n  behind: boolean;\n}\n\nexport interface ToastContentProps extends BaseUIComponentProps<'div', ToastContentState> {}\n\nexport namespace ToastContent {\n  export type State = ToastContentState;\n  export type Props = ToastContentProps;\n}\n"
  },
  {
    "path": "packages/react/src/toast/content/ToastContentDataAttributes.ts",
    "content": "export enum ToastContentDataAttributes {\n  /**\n   * Present when the toast viewport is expanded.\n   * @type {boolean}\n   */\n  expanded = 'data-expanded',\n  /**\n   * Present when the toast is behind the frontmost toast in the stack.\n   * @type {boolean}\n   */\n  behind = 'data-behind',\n}\n"
  },
  {
    "path": "packages/react/src/toast/createToastManager.spec.tsx",
    "content": "import { expectType } from '#test-utils';\nimport { createToastManager } from './createToastManager';\n\ntype ToastPayload = {\n  id: string;\n  count: number;\n};\n\nconst typedManager = createToastManager<ToastPayload>();\n\nconst typedAddId = typedManager.add({\n  title: 'typed',\n  data: {\n    id: 'typed',\n    count: 1,\n  },\n});\nexpectType<string, typeof typedAddId>(typedAddId);\n\ntypedManager.add({\n  title: 'wrong-shape',\n  data: {\n    id: 'test',\n    // @ts-expect-error - message is not a valid property\n    message: 'not a number',\n  },\n});\n\ntypedManager.add({\n  title: 'wrong-shape',\n  // @ts-expect-error - count is a missing property\n  data: {\n    id: 'test',\n  },\n});\n\ntypedManager.update('typed', {\n  data: {\n    id: 'typed-update',\n    count: 2,\n  },\n});\n\ntypedManager.promise(Promise.resolve(2), {\n  loading: 'loading',\n  success: (value) => ({\n    title: `${value}`,\n    data: {\n      id: 'typed-success',\n      count: value,\n    },\n  }),\n  error: 'error',\n});\n\nconst legacyManager = createToastManager();\n\nconst legacyAddId = legacyManager.add<ToastPayload>({\n  title: 'legacy',\n  data: {\n    id: 'legacy',\n    count: 3,\n  },\n});\nexpectType<string, typeof legacyAddId>(legacyAddId);\n\nlegacyManager.update<ToastPayload>('legacy', {\n  data: {\n    id: 'legacy-update',\n    count: 4,\n  },\n});\n\nlegacyManager.promise<number, ToastPayload>(Promise.resolve(5), {\n  loading: 'loading',\n  success: (value) => ({\n    title: `${value}`,\n    data: {\n      id: 'legacy-success',\n      count: value,\n    },\n  }),\n  error: 'error',\n});\n"
  },
  {
    "path": "packages/react/src/toast/createToastManager.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { Toast } from '@base-ui/react/toast';\nimport { fireEvent, flushMicrotasks, screen } from '@mui/internal-test-utils';\nimport { createRenderer, isJSDOM } from '#test-utils';\nimport { List } from './utils/test-utils';\n\ndescribe.skipIf(!isJSDOM)('createToastManager', () => {\n  const { render, clock } = createRenderer();\n\n  clock.withFakeTimers();\n\n  describe('add', () => {\n    it('adds a toast', async () => {\n      const toastManager = Toast.createToastManager();\n\n      function add() {\n        toastManager.add({\n          title: 'title',\n        });\n      }\n\n      function AddButton() {\n        return (\n          <button type=\"button\" onClick={add}>\n            add\n          </button>\n        );\n      }\n\n      await render(\n        <Toast.Provider toastManager={toastManager}>\n          <Toast.Viewport>\n            <List />\n          </Toast.Viewport>\n          <AddButton />\n        </Toast.Provider>,\n      );\n\n      expect(screen.queryByTestId('title')).toBe(null);\n\n      const button = screen.getByRole('button', { name: 'add' });\n      fireEvent.click(button);\n\n      expect(screen.queryByTestId('title')).not.toBe(null);\n\n      await clock.tickAsync(5000);\n\n      expect(screen.queryByTestId('title')).toBe(null);\n    });\n\n    it('returns a toast id', async () => {\n      const toastManager = Toast.createToastManager();\n\n      const toastId = toastManager.add({\n        title: 'title',\n      });\n\n      expect(toastId).toBeTypeOf('string');\n    });\n  });\n\n  describe('promise', () => {\n    it('adds a toast with the loading state that is updated with the success state', async () => {\n      const toastManager = Toast.createToastManager();\n\n      function add() {\n        toastManager.promise(\n          new Promise((resolve) => {\n            setTimeout(() => {\n              resolve('success');\n            }, 1000);\n          }),\n          {\n            loading: 'loading',\n            success: 'success',\n            error: 'error',\n          },\n        );\n      }\n\n      function AddButton() {\n        return (\n          <button type=\"button\" onClick={add}>\n            add\n          </button>\n        );\n      }\n\n      await render(\n        <Toast.Provider toastManager={toastManager}>\n          <Toast.Viewport>\n            <List />\n          </Toast.Viewport>\n          <AddButton />\n        </Toast.Provider>,\n      );\n\n      const button = screen.getByRole('button', { name: 'add' });\n      fireEvent.click(button);\n      await flushMicrotasks();\n\n      expect(screen.queryByTestId('description')).toHaveTextContent('loading');\n\n      await clock.tickAsync(1000);\n\n      expect(screen.queryByTestId('description')).toHaveTextContent('success');\n    });\n\n    it('does not inherit a loading timeout when success does not specify one', async () => {\n      const toastManager = Toast.createToastManager();\n\n      function add() {\n        toastManager.promise(\n          new Promise((resolve) => {\n            setTimeout(() => {\n              resolve('success');\n            }, 1000);\n          }),\n          {\n            loading: {\n              description: 'loading',\n              timeout: 0,\n            },\n            success: {\n              description: 'success',\n            },\n            error: 'error',\n          },\n        );\n      }\n\n      function AddButton() {\n        return (\n          <button type=\"button\" onClick={add}>\n            add\n          </button>\n        );\n      }\n\n      await render(\n        <Toast.Provider toastManager={toastManager}>\n          <Toast.Viewport>\n            <List />\n          </Toast.Viewport>\n          <AddButton />\n        </Toast.Provider>,\n      );\n\n      fireEvent.click(screen.getByRole('button', { name: 'add' }));\n      await flushMicrotasks();\n\n      expect(screen.queryByTestId('description')).toHaveTextContent('loading');\n\n      await clock.tickAsync(1000);\n\n      expect(screen.queryByTestId('description')).toHaveTextContent('success');\n\n      await clock.tickAsync(5000);\n\n      expect(screen.queryByTestId('description')).toBe(null);\n    });\n\n    it('adds a toast with the loading state that is updated with the error state', async () => {\n      const toastManager = Toast.createToastManager();\n\n      function promise() {\n        toastManager\n          .promise(\n            new Promise((res, rej) => {\n              rej(new Error('error'));\n            }),\n            {\n              loading: 'loading',\n              success: 'success',\n              error: 'error',\n            },\n          )\n          .catch(() => {\n            // Swallow the error\n          });\n      }\n\n      function AddButton() {\n        return (\n          <button type=\"button\" onClick={promise}>\n            add\n          </button>\n        );\n      }\n\n      await render(\n        <Toast.Provider toastManager={toastManager}>\n          <Toast.Viewport>\n            <List />\n          </Toast.Viewport>\n          <AddButton />\n        </Toast.Provider>,\n      );\n\n      const button = screen.getByRole('button', { name: 'add' });\n      fireEvent.click(button);\n      await flushMicrotasks();\n\n      expect(screen.getByTestId('description')).toHaveTextContent('error');\n    });\n\n    it('does not reopen a dismissed promise toast when it resolves', async () => {\n      const toastManager = Toast.createToastManager();\n      let resolvePromise: (value: string) => void = () => {\n        throw new Error('Promise resolver should be assigned before resolving.');\n      };\n\n      function add() {\n        const pendingPromise = new Promise<string>((resolve) => {\n          resolvePromise = resolve;\n        });\n\n        toastManager.promise(pendingPromise, {\n          loading: 'loading',\n          success: 'success',\n          error: 'error',\n        });\n      }\n\n      function Button() {\n        return (\n          <button type=\"button\" onClick={add}>\n            add\n          </button>\n        );\n      }\n\n      await render(\n        <Toast.Provider toastManager={toastManager}>\n          <Toast.Viewport>\n            <List />\n          </Toast.Viewport>\n          <Button />\n        </Toast.Provider>,\n      );\n\n      fireEvent.click(screen.getByRole('button', { name: 'add' }));\n\n      expect(screen.getByTestId('description')).toHaveTextContent('loading');\n\n      fireEvent.click(screen.getByLabelText('close-press'));\n      resolvePromise('success');\n\n      await flushMicrotasks();\n\n      expect(screen.queryByTestId('title')).toBe(null);\n    });\n  });\n\n  describe('update', () => {\n    it('updates a toast', async () => {\n      const toastManager = Toast.createToastManager();\n\n      let toastId: string;\n\n      function add() {\n        toastId = toastManager.add({\n          title: 'title',\n        });\n      }\n\n      function update() {\n        toastManager.update(toastId, {\n          title: 'updated',\n        });\n      }\n\n      function Buttons() {\n        return (\n          <React.Fragment>\n            <button type=\"button\" onClick={add}>\n              add\n            </button>\n            <button type=\"button\" onClick={update}>\n              update method\n            </button>\n          </React.Fragment>\n        );\n      }\n\n      await render(\n        <Toast.Provider toastManager={toastManager}>\n          <Toast.Viewport>\n            <List />\n          </Toast.Viewport>\n          <Buttons />\n        </Toast.Provider>,\n      );\n\n      const button = screen.getByRole('button', { name: 'add' });\n      fireEvent.click(button);\n\n      const updateButton = screen.getByRole('button', { name: 'update method' });\n      fireEvent.click(updateButton);\n\n      expect(screen.getByTestId('title')).toHaveTextContent('updated');\n    });\n\n    it('resets the auto-dismiss timer when updating with the same timeout value', async () => {\n      const toastManager = Toast.createToastManager();\n\n      let toastId: string;\n\n      function add() {\n        toastId = toastManager.add({\n          title: 'title',\n          timeout: 1000,\n        });\n      }\n\n      function resetTimeout() {\n        toastManager.update(toastId, {\n          timeout: 1000,\n        });\n      }\n\n      function Buttons() {\n        return (\n          <React.Fragment>\n            <button type=\"button\" onClick={add}>\n              add\n            </button>\n            <button type=\"button\" onClick={resetTimeout}>\n              reset timeout\n            </button>\n          </React.Fragment>\n        );\n      }\n\n      await render(\n        <Toast.Provider toastManager={toastManager}>\n          <Toast.Viewport>\n            <List />\n          </Toast.Viewport>\n          <Buttons />\n        </Toast.Provider>,\n      );\n\n      fireEvent.click(screen.getByRole('button', { name: 'add' }));\n      expect(screen.queryByTestId('title')).not.toBe(null);\n\n      await clock.tickAsync(900);\n      expect(screen.queryByTestId('title')).not.toBe(null);\n\n      fireEvent.click(screen.getByRole('button', { name: 'reset timeout' }));\n\n      await clock.tickAsync(200);\n      expect(screen.queryByTestId('title')).not.toBe(null);\n\n      await clock.tickAsync(800);\n      expect(screen.queryByTestId('title')).toBe(null);\n    });\n\n    it('resets the auto-dismiss timer when updating from 0 to a timeout, then updating with the same timeout again', async () => {\n      const toastManager = Toast.createToastManager();\n\n      let toastId: string;\n\n      function add() {\n        toastId = toastManager.add({\n          title: 'title',\n          timeout: 0,\n        });\n      }\n\n      function setTimeoutTo1000() {\n        toastManager.update(toastId, {\n          timeout: 1000,\n        });\n      }\n\n      function resetTimeoutTo1000() {\n        toastManager.update(toastId, {\n          timeout: 1000,\n        });\n      }\n\n      function Buttons() {\n        return (\n          <React.Fragment>\n            <button type=\"button\" onClick={add}>\n              add\n            </button>\n            <button type=\"button\" onClick={setTimeoutTo1000}>\n              set timeout\n            </button>\n            <button type=\"button\" onClick={resetTimeoutTo1000}>\n              reset timeout\n            </button>\n          </React.Fragment>\n        );\n      }\n\n      await render(\n        <Toast.Provider toastManager={toastManager}>\n          <Toast.Viewport>\n            <List />\n          </Toast.Viewport>\n          <Buttons />\n        </Toast.Provider>,\n      );\n\n      fireEvent.click(screen.getByRole('button', { name: 'add' }));\n      expect(screen.queryByTestId('title')).not.toBe(null);\n\n      fireEvent.click(screen.getByRole('button', { name: 'set timeout' }));\n\n      await clock.tickAsync(900);\n      expect(screen.queryByTestId('title')).not.toBe(null);\n\n      fireEvent.click(screen.getByRole('button', { name: 'reset timeout' }));\n\n      await clock.tickAsync(200);\n      expect(screen.queryByTestId('title')).not.toBe(null);\n\n      await clock.tickAsync(800);\n      expect(screen.queryByTestId('title')).toBe(null);\n    });\n\n    it('auto-dismisses when updating timeout from 0 to a positive value', async () => {\n      const toastManager = Toast.createToastManager();\n\n      let toastId: string;\n\n      function add() {\n        toastId = toastManager.add({\n          title: 'title',\n          timeout: 0,\n        });\n      }\n\n      function update() {\n        toastManager.update(toastId, {\n          timeout: 1000,\n        });\n      }\n\n      function Buttons() {\n        return (\n          <React.Fragment>\n            <button type=\"button\" onClick={add}>\n              add\n            </button>\n            <button type=\"button\" onClick={update}>\n              update method\n            </button>\n          </React.Fragment>\n        );\n      }\n\n      await render(\n        <Toast.Provider toastManager={toastManager}>\n          <Toast.Viewport>\n            <List />\n          </Toast.Viewport>\n          <Buttons />\n        </Toast.Provider>,\n      );\n\n      fireEvent.click(screen.getByRole('button', { name: 'add' }));\n      expect(screen.queryByTestId('title')).not.toBe(null);\n\n      fireEvent.click(screen.getByRole('button', { name: 'update method' }));\n      await clock.tickAsync(1000);\n\n      expect(screen.queryByTestId('title')).toBe(null);\n    });\n\n    it('schedules a timer when updating a loading toast to a non-loading type', async () => {\n      const toastManager = Toast.createToastManager();\n\n      let toastId: string;\n\n      function add() {\n        toastId = toastManager.add({\n          title: 'loading',\n          type: 'loading',\n        });\n      }\n\n      function update() {\n        toastManager.update(toastId, {\n          title: 'success',\n          type: 'success',\n          timeout: 1000,\n        });\n      }\n\n      function Buttons() {\n        return (\n          <React.Fragment>\n            <button type=\"button\" onClick={add}>\n              add\n            </button>\n            <button type=\"button\" onClick={update}>\n              update method\n            </button>\n          </React.Fragment>\n        );\n      }\n\n      await render(\n        <Toast.Provider toastManager={toastManager}>\n          <Toast.Viewport>\n            <List />\n          </Toast.Viewport>\n          <Buttons />\n        </Toast.Provider>,\n      );\n\n      fireEvent.click(screen.getByRole('button', { name: 'add' }));\n      expect(screen.getByTestId('title')).toHaveTextContent('loading');\n\n      fireEvent.click(screen.getByRole('button', { name: 'update method' }));\n      expect(screen.getByTestId('title')).toHaveTextContent('success');\n\n      await clock.tickAsync(1000);\n\n      expect(screen.queryByTestId('title')).toBe(null);\n    });\n\n    it('does not clear the auto-dismiss timer when updated twice before a re-render', async () => {\n      const toastManager = Toast.createToastManager();\n\n      let toastId: string;\n\n      function add() {\n        toastId = toastManager.add({\n          title: 'loading',\n          type: 'loading',\n        });\n      }\n\n      function doubleUpdate() {\n        toastManager.update(toastId, {\n          type: 'success',\n          timeout: 1000,\n        });\n\n        toastManager.update(toastId, {\n          title: 'new',\n        });\n      }\n\n      function Buttons() {\n        return (\n          <React.Fragment>\n            <button type=\"button\" onClick={add}>\n              add\n            </button>\n            <button type=\"button\" onClick={doubleUpdate}>\n              double update\n            </button>\n          </React.Fragment>\n        );\n      }\n\n      await render(\n        <Toast.Provider toastManager={toastManager}>\n          <Toast.Viewport>\n            <List />\n          </Toast.Viewport>\n          <Buttons />\n        </Toast.Provider>,\n      );\n\n      fireEvent.click(screen.getByRole('button', { name: 'add' }));\n      expect(screen.getByTestId('title')).toHaveTextContent('loading');\n\n      fireEvent.click(screen.getByRole('button', { name: 'double update' }));\n      expect(screen.getByTestId('title')).toHaveTextContent('new');\n\n      await clock.tickAsync(1000);\n\n      expect(screen.queryByTestId('title')).toBe(null);\n    });\n  });\n\n  describe('close', () => {\n    it('closes a toast', async () => {\n      const toastManager = Toast.createToastManager();\n\n      let toastId: string;\n\n      function add() {\n        toastId = toastManager.add({\n          title: 'title',\n        });\n      }\n\n      function close() {\n        toastManager.close(toastId);\n      }\n\n      function Buttons() {\n        return (\n          <React.Fragment>\n            <button type=\"button\" onClick={add}>\n              add\n            </button>\n            <button type=\"button\" onClick={close}>\n              close\n            </button>\n          </React.Fragment>\n        );\n      }\n\n      await render(\n        <Toast.Provider toastManager={toastManager}>\n          <Toast.Viewport>\n            <List />\n          </Toast.Viewport>\n          <Buttons />\n        </Toast.Provider>,\n      );\n\n      const button = screen.getByRole('button', { name: 'add' });\n      fireEvent.click(button);\n\n      const closeButton = screen.getByRole('button', { name: 'close' });\n      fireEvent.click(closeButton);\n\n      expect(screen.queryByTestId('title')).toBe(null);\n    });\n\n    it('closes all toasts', async () => {\n      const toastManager = Toast.createToastManager();\n\n      function add() {\n        toastManager.add({ title: 'title' });\n      }\n\n      function close() {\n        toastManager.close();\n      }\n\n      function Buttons() {\n        return (\n          <React.Fragment>\n            <button type=\"button\" onClick={add}>\n              add\n            </button>\n            <button type=\"button\" onClick={close}>\n              close\n            </button>\n          </React.Fragment>\n        );\n      }\n\n      await render(\n        <Toast.Provider toastManager={toastManager}>\n          <Toast.Viewport>\n            <List />\n          </Toast.Viewport>\n          <Buttons />\n        </Toast.Provider>,\n      );\n\n      const button = screen.getByRole('button', { name: 'add' });\n      Array.from({ length: 5 }).forEach(() => {\n        fireEvent.click(button);\n      });\n\n      const closeButton = screen.getByRole('button', { name: 'close' });\n      fireEvent.click(closeButton);\n\n      expect(screen.queryByTestId('title')).toBe(null);\n    });\n\n    it('does not call onClose when closing toasts that are already ending', async () => {\n      const toastManager = Toast.createToastManager();\n      const onCloseSpy1 = vi.fn(() => {\n        toastManager.close();\n      });\n      const onCloseSpy2 = vi.fn();\n      let toastId1: string;\n\n      function add() {\n        toastId1 = toastManager.add({\n          title: 'toast 1',\n          onClose: onCloseSpy1,\n        });\n\n        toastManager.add({\n          title: 'toast 2',\n          onClose: onCloseSpy2,\n        });\n      }\n\n      function close() {\n        toastManager.close(toastId1);\n      }\n\n      function Buttons() {\n        return (\n          <React.Fragment>\n            <button type=\"button\" onClick={add}>\n              add\n            </button>\n            <button type=\"button\" onClick={close}>\n              close\n            </button>\n          </React.Fragment>\n        );\n      }\n\n      await render(\n        <Toast.Provider toastManager={toastManager}>\n          <Toast.Viewport>\n            <List />\n          </Toast.Viewport>\n          <Buttons />\n        </Toast.Provider>,\n      );\n\n      fireEvent.click(screen.getByRole('button', { name: 'add' }));\n      fireEvent.click(screen.getByRole('button', { name: 'close' }));\n\n      expect(onCloseSpy1.mock.calls.length).toBe(1);\n      expect(onCloseSpy2.mock.calls.length).toBe(1);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/toast/createToastManager.ts",
    "content": "import { generateId } from '@base-ui/utils/generateId';\nimport type {\n  ToastObject,\n  ToastManagerAddOptions,\n  ToastManagerPromiseOptions,\n  ToastManagerUpdateOptions,\n} from './useToastManager';\n\n/**\n * Creates a new toast manager.\n */\nexport function createToastManager<Data extends object = any>(): ToastManager<Data> {\n  const listeners = new Set<(data: ToastManagerEvent) => void>();\n\n  function emit(data: ToastManagerEvent) {\n    listeners.forEach((listener) => listener(data));\n  }\n\n  return {\n    // This should be private aside from ToastProvider needing to access it.\n    // https://x.com/drosenwasser/status/1816947740032872664\n    ' subscribe': function subscribe(listener: (data: ToastManagerEvent) => void) {\n      listeners.add(listener);\n      return () => {\n        listeners.delete(listener);\n      };\n    },\n\n    add<T extends Data = Data>(options: ToastManagerAddOptions<T>): string {\n      const id = options.id || generateId('toast');\n      const toastToAdd: ToastObject<T> = {\n        ...options,\n        id,\n        transitionStatus: 'starting',\n      };\n\n      emit({\n        action: 'add',\n        options: toastToAdd,\n      });\n\n      return id;\n    },\n\n    close(id?: string): void {\n      emit({\n        action: 'close',\n        options: { id },\n      });\n    },\n\n    update<T extends Data = Data>(id: string, updates: ToastManagerUpdateOptions<T>): void {\n      emit({\n        action: 'update',\n        options: {\n          ...updates,\n          id,\n        },\n      });\n    },\n\n    promise<Value, T extends Data = Data>(\n      promiseValue: Promise<Value>,\n      options: ToastManagerPromiseOptions<Value, T>,\n    ): Promise<Value> {\n      let handledPromise = promiseValue;\n\n      emit({\n        action: 'promise',\n        options: {\n          ...options,\n          promise: promiseValue,\n          setPromise(promise: Promise<Value>) {\n            handledPromise = promise;\n          },\n        },\n      });\n\n      return handledPromise;\n    },\n  };\n}\n\nexport interface ToastManager<Data extends object = any> {\n  ' subscribe': (listener: (data: ToastManagerEvent) => void) => () => void;\n  add: <T extends Data = Data>(options: ToastManagerAddOptions<T>) => string;\n  close: (id?: string) => void;\n  update: <T extends Data = Data>(id: string, updates: ToastManagerUpdateOptions<T>) => void;\n  promise: <Value, T extends Data = Data>(\n    promiseValue: Promise<Value>,\n    options: ToastManagerPromiseOptions<Value, T>,\n  ) => Promise<Value>;\n}\n\nexport interface ToastManagerEvent {\n  action: 'add' | 'close' | 'update' | 'promise';\n  options: any;\n}\n"
  },
  {
    "path": "packages/react/src/toast/description/ToastDescription.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { Toast } from '@base-ui/react/toast';\nimport { createRenderer, describeConformance } from '#test-utils';\nimport { screen } from '@mui/internal-test-utils';\nimport { List, Button } from '../utils/test-utils';\n\nconst toast: Toast.Root.ToastObject = {\n  id: 'test',\n  title: 'Toast title',\n};\n\ndescribe('<Toast.Description />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Toast.Description>description</Toast.Description>, () => ({\n    refInstanceof: window.HTMLParagraphElement,\n    render(node) {\n      return render(\n        <Toast.Provider>\n          <Toast.Viewport>\n            <Toast.Root toast={toast}>{node}</Toast.Root>\n          </Toast.Viewport>\n        </Toast.Provider>,\n      );\n    },\n  }));\n\n  it('adds aria-describedby to the root element', async () => {\n    const { user } = await render(\n      <Toast.Provider>\n        <Toast.Viewport>\n          <List />\n        </Toast.Viewport>\n        <Button />\n      </Toast.Provider>,\n    );\n\n    const button = screen.getByRole('button', { name: 'add' });\n    await user.click(button);\n\n    const descriptionElement = screen.getByTestId('description');\n    const descriptionId = descriptionElement.id;\n\n    const rootElement = screen.getByTestId('root');\n    expect(rootElement).not.toBe(null);\n    expect(rootElement.getAttribute('aria-describedby')).toBe(descriptionId);\n  });\n\n  it('does not render if it has no children', async () => {\n    function AddButton() {\n      const { add } = Toast.useToastManager();\n      return (\n        <button type=\"button\" onClick={() => add({ description: undefined })}>\n          add\n        </button>\n      );\n    }\n\n    const { user } = await render(\n      <Toast.Provider>\n        <Toast.Viewport>\n          <List />\n        </Toast.Viewport>\n        <AddButton />\n      </Toast.Provider>,\n    );\n\n    const button = screen.getByRole('button', { name: 'add' });\n    await user.click(button);\n\n    const descriptionElement = screen.queryByTestId('description');\n    expect(descriptionElement).toBe(null);\n  });\n\n  it('renders the description by default', async () => {\n    const { user } = await render(\n      <Toast.Provider>\n        <Toast.Viewport>\n          <List />\n        </Toast.Viewport>\n        <Button />\n      </Toast.Provider>,\n    );\n\n    const button = screen.getByRole('button', { name: 'add' });\n    await user.click(button);\n\n    const titleElement = screen.getByTestId('description');\n    expect(titleElement).not.toBe(null);\n    expect(titleElement.textContent).toBe('description');\n  });\n});\n"
  },
  {
    "path": "packages/react/src/toast/description/ToastDescription.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useId } from '@base-ui/utils/useId';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useToastRootContext } from '../root/ToastRootContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\n\n/**\n * A description that describes the toast.\n * Can be used as the default message for the toast when no title is provided.\n * Renders a `<p>` element.\n *\n * Documentation: [Base UI Toast](https://base-ui.com/react/components/toast)\n */\nexport const ToastDescription = React.forwardRef(function ToastDescription(\n  componentProps: ToastDescription.Props,\n  forwardedRef: React.ForwardedRef<HTMLParagraphElement>,\n) {\n  const { render, className, id: idProp, children: childrenProp, ...elementProps } = componentProps;\n\n  const { toast, setDescriptionId } = useToastRootContext();\n\n  const children = childrenProp ?? toast.description;\n\n  const shouldRender = Boolean(children);\n\n  const id = useId(idProp);\n\n  useIsoLayoutEffect(() => {\n    if (!shouldRender) {\n      return undefined;\n    }\n\n    setDescriptionId(id);\n\n    return () => {\n      setDescriptionId(undefined);\n    };\n  }, [shouldRender, id, setDescriptionId]);\n\n  const state: ToastDescriptionState = {\n    type: toast.type,\n  };\n\n  const element = useRenderElement('p', componentProps, {\n    ref: forwardedRef,\n    state,\n    props: {\n      ...elementProps,\n      id,\n      children,\n    },\n  });\n\n  if (!shouldRender) {\n    return null;\n  }\n\n  return element;\n});\n\nexport interface ToastDescriptionState {\n  /**\n   * The type of the toast.\n   */\n  type: string | undefined;\n}\n\nexport interface ToastDescriptionProps extends BaseUIComponentProps<'p', ToastDescriptionState> {}\n\nexport namespace ToastDescription {\n  export type State = ToastDescriptionState;\n  export type Props = ToastDescriptionProps;\n}\n"
  },
  {
    "path": "packages/react/src/toast/description/ToastDescriptionDataAttributes.ts",
    "content": "export enum ToastDescriptionDataAttributes {\n  /**\n   * The type of the toast.\n   * @type {string}\n   */\n  type = 'data-type',\n}\n"
  },
  {
    "path": "packages/react/src/toast/index.parts.ts",
    "content": "export { ToastProvider as Provider } from './provider/ToastProvider';\nexport { ToastViewport as Viewport } from './viewport/ToastViewport';\nexport { ToastRoot as Root } from './root/ToastRoot';\nexport { ToastContent as Content } from './content/ToastContent';\nexport { ToastDescription as Description } from './description/ToastDescription';\nexport { ToastTitle as Title } from './title/ToastTitle';\nexport { ToastClose as Close } from './close/ToastClose';\nexport { ToastAction as Action } from './action/ToastAction';\nexport { ToastPortal as Portal } from './portal/ToastPortal';\nexport { ToastPositioner as Positioner } from './positioner/ToastPositioner';\nexport { ToastArrow as Arrow } from './arrow/ToastArrow';\n\nexport { useToastManager } from './useToastManager';\nexport { createToastManager } from './createToastManager';\n"
  },
  {
    "path": "packages/react/src/toast/index.ts",
    "content": "export * as Toast from './index.parts';\n\nexport type * from './root/ToastRoot';\nexport type * from './provider/ToastProvider';\nexport type * from './viewport/ToastViewport';\nexport type * from './content/ToastContent';\nexport type * from './description/ToastDescription';\nexport type * from './title/ToastTitle';\nexport type * from './close/ToastClose';\nexport type * from './action/ToastAction';\nexport type * from './portal/ToastPortal';\nexport type * from './positioner/ToastPositioner';\nexport type * from './arrow/ToastArrow';\nexport type * from './useToastManager';\nexport type * from './createToastManager';\n"
  },
  {
    "path": "packages/react/src/toast/portal/ToastPortal.test.tsx",
    "content": "import * as React from 'react';\nimport { Toast } from '@base-ui/react/toast';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Toast.Portal />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Toast.Portal />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(node);\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/toast/portal/ToastPortal.tsx",
    "content": "'use client';\nimport { FloatingPortalLite } from '../../utils/FloatingPortalLite';\n\n/**\n * A portal element that moves the viewport to a different part of the DOM.\n * By default, the portal element is appended to `<body>`.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Toast](https://base-ui.com/react/components/toast)\n */\nexport const ToastPortal = FloatingPortalLite;\n\nexport interface ToastPortalState {}\n\nexport interface ToastPortalProps extends FloatingPortalLite.Props<ToastPortalState> {}\n\nexport namespace ToastPortal {\n  export type State = ToastPortalState;\n  export type Props = ToastPortalProps;\n}\n"
  },
  {
    "path": "packages/react/src/toast/positioner/ToastPositioner.test.tsx",
    "content": "import { Toast } from '@base-ui/react/toast';\nimport { createRenderer, describeConformance } from '#test-utils';\n\nconst toast: Toast.Root.ToastObject = {\n  id: 'test',\n  title: 'Toast title',\n};\n\ndescribe('<Toast.Positioner />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Toast.Positioner toast={toast} />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(<Toast.Provider>{node}</Toast.Provider>);\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/toast/positioner/ToastPositioner.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { isElement } from '@floating-ui/utils/dom';\nimport {\n  useAnchorPositioning,\n  type Side,\n  type Align,\n  type UseAnchorPositioningSharedParameters,\n} from '../../utils/useAnchorPositioning';\nimport type { BaseUIComponentProps, HTMLProps } from '../../utils/types';\nimport { popupStateMapping } from '../../utils/popupStateMapping';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { EMPTY_OBJECT, POPUP_COLLISION_AVOIDANCE } from '../../utils/constants';\nimport { getDisabledMountTransitionStyles } from '../../utils/getDisabledMountTransitionStyles';\nimport { ToastPositionerContext } from './ToastPositionerContext';\nimport { useFloatingRootContext } from '../../floating-ui-react';\nimport { NOOP } from '../../utils/noop';\nimport type { ToastObject } from '../useToastManager';\nimport { ToastRootCssVars } from '../root/ToastRootCssVars';\nimport { useToastProviderContext } from '../provider/ToastProviderContext';\n\n/**\n * Positions the toast against the anchor.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Toast](https://base-ui.com/react/components/toast)\n */\nexport const ToastPositioner = React.forwardRef(function ToastPositioner(\n  componentProps: ToastPositioner.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { toast, ...props } = componentProps;\n\n  const store = useToastProviderContext();\n\n  const positionerProps = (toast.positionerProps ?? EMPTY_OBJECT) as NonNullable<\n    typeof toast.positionerProps\n  >;\n\n  const {\n    render,\n    className,\n    anchor: anchorProp = positionerProps.anchor,\n    positionMethod = positionerProps.positionMethod ?? 'absolute',\n    side = positionerProps.side ?? 'top',\n    align = positionerProps.align ?? 'center',\n    sideOffset = positionerProps.sideOffset ?? 0,\n    alignOffset = positionerProps.alignOffset ?? 0,\n    collisionBoundary = positionerProps.collisionBoundary ?? 'clipping-ancestors',\n    collisionPadding = positionerProps.collisionPadding ?? 5,\n    arrowPadding = positionerProps.arrowPadding ?? 5,\n    sticky = positionerProps.sticky ?? false,\n    disableAnchorTracking = positionerProps.disableAnchorTracking ?? false,\n    collisionAvoidance = positionerProps.collisionAvoidance ?? POPUP_COLLISION_AVOIDANCE,\n    ...elementProps\n  } = props;\n\n  const [positionerElement, setPositionerElement] = React.useState<HTMLDivElement | null>(null);\n\n  const domIndex = store.useState('toastIndex', toast.id);\n  const visibleIndex = store.useState('toastVisibleIndex', toast.id);\n\n  const anchor = isElement(anchorProp) ? anchorProp : null;\n\n  const floatingRootContext = useFloatingRootContext({\n    open: true,\n    onOpenChange: NOOP,\n    elements: {\n      floating: positionerElement,\n      reference: anchor,\n    },\n  });\n\n  const positioning = useAnchorPositioning({\n    anchor,\n    positionMethod,\n    floatingRootContext,\n    mounted: true,\n    side,\n    sideOffset,\n    align,\n    alignOffset,\n    collisionBoundary,\n    collisionPadding,\n    sticky,\n    arrowPadding,\n    disableAnchorTracking,\n    keepMounted: true,\n    collisionAvoidance,\n  });\n\n  const defaultProps: HTMLProps = React.useMemo(() => {\n    const hiddenStyles: React.CSSProperties = {};\n\n    return {\n      role: 'presentation',\n      style: {\n        ...positioning.positionerStyles,\n        ...hiddenStyles,\n        [ToastRootCssVars.index as string]:\n          toast.transitionStatus === 'ending' ? domIndex : visibleIndex,\n      },\n    };\n  }, [positioning.positionerStyles, toast.transitionStatus, domIndex, visibleIndex]);\n\n  const state: ToastPositionerState = React.useMemo(\n    () => ({\n      side: positioning.side,\n      align: positioning.align,\n      anchorHidden: positioning.anchorHidden,\n    }),\n    [positioning.side, positioning.align, positioning.anchorHidden],\n  );\n\n  const contextValue: ToastPositionerContext = React.useMemo(\n    () => ({\n      ...state,\n      arrowRef: positioning.arrowRef,\n      arrowStyles: positioning.arrowStyles,\n      arrowUncentered: positioning.arrowUncentered,\n    }),\n    [state, positioning.arrowRef, positioning.arrowStyles, positioning.arrowUncentered],\n  );\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    props: [defaultProps, getDisabledMountTransitionStyles(toast.transitionStatus), elementProps],\n    ref: [forwardedRef, setPositionerElement],\n    stateAttributesMapping: popupStateMapping,\n  });\n\n  return (\n    <ToastPositionerContext.Provider value={contextValue}>\n      {element}\n    </ToastPositionerContext.Provider>\n  );\n});\n\nexport interface ToastPositionerState {\n  /**\n   * The side of the anchor the component is placed on.\n   */\n  side: Side;\n  /**\n   * The alignment of the component relative to the anchor.\n   */\n  align: Align;\n  /**\n   * Whether the anchor element is hidden.\n   */\n  anchorHidden: boolean;\n}\n\nexport interface ToastPositionerProps\n  extends\n    BaseUIComponentProps<'div', ToastPositionerState>,\n    Omit<UseAnchorPositioningSharedParameters, 'side' | 'anchor'> {\n  /**\n   * An element to position the toast against.\n   */\n  anchor?: Element | null | undefined;\n  /**\n   * Which side of the anchor element to align the toast against.\n   * May automatically change to avoid collisions.\n   * @default 'top'\n   */\n  side?: Side | undefined;\n  /**\n   * The toast object associated with the positioner.\n   */\n  toast: ToastObject<any>;\n}\n\nexport namespace ToastPositioner {\n  export type State = ToastPositionerState;\n  export type Props = ToastPositionerProps;\n}\n"
  },
  {
    "path": "packages/react/src/toast/positioner/ToastPositionerContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport type { Side, Align } from '../../utils/useAnchorPositioning';\n\nexport interface ToastPositionerContext {\n  side: Side;\n  align: Align;\n  arrowRef: React.RefObject<Element | null>;\n  arrowUncentered: boolean;\n  arrowStyles: React.CSSProperties;\n}\n\nexport const ToastPositionerContext = React.createContext<ToastPositionerContext | undefined>(\n  undefined,\n);\n\nexport function useToastPositionerContext() {\n  const context = React.useContext(ToastPositionerContext);\n  if (context === undefined) {\n    throw new Error(\n      'Base UI: ToastPositionerContext is missing. ToastPositioner parts must be placed within <Toast.Positioner>.',\n    );\n  }\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/toast/positioner/ToastPositionerCssVars.ts",
    "content": "export enum ToastPositionerCssVars {\n  /**\n   * The available width between the anchor and the edge of the viewport.\n   * @type {number}\n   */\n  availableWidth = '--available-width',\n  /**\n   * The available height between the anchor and the edge of the viewport.\n   * @type {number}\n   */\n  availableHeight = '--available-height',\n  /**\n   * The anchor's width.\n   * @type {number}\n   */\n  anchorWidth = '--anchor-width',\n  /**\n   * The anchor's height.\n   * @type {number}\n   */\n  anchorHeight = '--anchor-height',\n  /**\n   * The coordinates that this element is anchored to. Used for animations and transitions.\n   * @type {string}\n   */\n  transformOrigin = '--transform-origin',\n}\n"
  },
  {
    "path": "packages/react/src/toast/positioner/ToastPositionerDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum ToastPositionerDataAttributes {\n  /**\n   * Present when the anchor is hidden.\n   */\n  anchorHidden = CommonPopupDataAttributes.anchorHidden,\n  /**\n   * Indicates which side the toast is positioned relative to the trigger.\n   * @type {'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'}\n   */\n  side = CommonPopupDataAttributes.side,\n  /**\n   * Indicates how the toast is aligned relative to specified side.\n   * @type {'start' | 'center' | 'end'}\n   */\n  align = CommonPopupDataAttributes.align,\n}\n"
  },
  {
    "path": "packages/react/src/toast/provider/ToastProvider.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useOnMount } from '@base-ui/utils/useOnMount';\nimport { useRefWithInit } from '@base-ui/utils/useRefWithInit';\nimport { ToastContext } from './ToastProviderContext';\nimport type { ToastManager } from '../createToastManager';\nimport { ToastStore } from '../store';\n\n/**\n * Provides a context for creating and managing toasts.\n *\n * Documentation: [Base UI Toast](https://base-ui.com/react/components/toast)\n */\nexport const ToastProvider: React.FC<ToastProvider.Props> = function ToastProvider(props) {\n  const { children, timeout = 5000, limit = 3, toastManager } = props;\n\n  const store = useRefWithInit(\n    () =>\n      new ToastStore({\n        timeout,\n        limit,\n        viewport: null,\n        toasts: [],\n        hovering: false,\n        focused: false,\n        isWindowFocused: true,\n        prevFocusElement: null,\n      }),\n  ).current;\n\n  useOnMount(store.disposeEffect);\n\n  React.useEffect(\n    function subscribeToToastManager() {\n      if (!toastManager) {\n        return undefined;\n      }\n\n      const unsubscribe = toastManager[' subscribe'](({ action, options }) => {\n        const id = options.id;\n\n        if (action === 'promise' && options.promise) {\n          store.promiseToast(options.promise, options);\n        } else if (action === 'update' && id) {\n          store.updateToast(id, options);\n        } else if (action === 'close') {\n          store.closeToast(id);\n        } else {\n          store.addToast(options);\n        }\n      });\n\n      return unsubscribe;\n    },\n    [store, timeout, toastManager],\n  );\n\n  store.useSyncedValues({ timeout, limit });\n\n  return <ToastContext.Provider value={store}>{children}</ToastContext.Provider>;\n};\n\nexport interface ToastProviderState {}\n\nexport interface ToastProviderProps {\n  children?: React.ReactNode;\n  /**\n   * The default amount of time (in ms) before a toast is auto dismissed.\n   * A value of `0` will prevent the toast from being dismissed automatically.\n   * @default 5000\n   */\n  timeout?: number | undefined;\n  /**\n   * The maximum number of toasts that can be displayed at once.\n   * When the limit is reached, the oldest toast will be removed to make room for the new one.\n   * @default 3\n   */\n  limit?: number | undefined;\n  /**\n   * A global manager for toasts to use outside of a React component.\n   */\n  toastManager?: ToastManager | undefined;\n}\n\nexport namespace ToastProvider {\n  export type State = ToastProviderState;\n  export type Props = ToastProviderProps;\n}\n"
  },
  {
    "path": "packages/react/src/toast/provider/ToastProviderContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { ToastStore } from '../store';\n\nexport type ToastContext = ToastStore;\n\nexport const ToastContext = React.createContext<ToastContext | undefined>(undefined);\n\nexport function useToastProviderContext() {\n  const context = React.useContext(ToastContext);\n  if (!context) {\n    throw new Error('Base UI: useToastManager must be used within <Toast.Provider>.');\n  }\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/toast/root/ToastRoot.test.tsx",
    "content": "import { expect } from 'vitest';\nimport * as React from 'react';\nimport { Toast } from '@base-ui/react/toast';\nimport { act, screen, fireEvent, waitFor } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\nimport type { ToastManagerAddOptions } from '../useToastManager';\nimport { List, Button } from '../utils/test-utils';\n\nconst toast: Toast.Root.ToastObject = {\n  id: 'test',\n  title: 'Toast title',\n};\n\ndescribe('<Toast.Root />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Toast.Root toast={toast} />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <Toast.Provider>\n          <Toast.Viewport>{node}</Toast.Viewport>\n        </Toast.Provider>,\n      );\n    },\n  }));\n\n  it.skipIf(isJSDOM)('recalculates height when content mutates', async () => {\n    function ToastList() {\n      return Toast.useToastManager().toasts.map((toastItem) => (\n        <Toast.Root\n          key={toastItem.id}\n          toast={toastItem}\n          data-testid=\"toast-root\"\n          style={{ width: 30 }}\n        >\n          <Toast.Content>\n            <Toast.Title>{toastItem.title}</Toast.Title>\n            <Toast.Description>{toastItem.description}</Toast.Description>\n          </Toast.Content>\n        </Toast.Root>\n      ));\n    }\n\n    function App() {\n      const { add, update } = Toast.useToastManager();\n      const [toastId, setToastId] = React.useState<string | null>(null);\n      const addedRef = React.useRef(false);\n\n      React.useEffect(() => {\n        if (addedRef.current) {\n          return;\n        }\n        addedRef.current = true;\n        const id = add({\n          id: 'resizable-toast',\n          title: 'Loading',\n          description: 'Short',\n        });\n        setToastId(id);\n      }, [add]);\n\n      return (\n        <div>\n          <button\n            type=\"button\"\n            onClick={() => {\n              if (!toastId) {\n                return;\n              }\n              update(toastId, {\n                title: 'Success',\n                description:\n                  'This content is longer than before and should cause the height to increase',\n              });\n            }}\n          >\n            update\n          </button>\n          <Toast.Viewport>\n            <ToastList />\n          </Toast.Viewport>\n        </div>\n      );\n    }\n\n    await render(\n      <Toast.Provider>\n        <App />\n      </Toast.Provider>,\n    );\n\n    const toastRoot = await screen.findByTestId('toast-root');\n\n    await waitFor(() => {\n      const height = toastRoot.style.getPropertyValue('--toast-height');\n      expect(height).not.toBe('');\n    });\n\n    const initialHeight = parseInt(toastRoot.style.getPropertyValue('--toast-height'), 10);\n\n    fireEvent.click(screen.getByRole('button', { name: 'update' }));\n\n    await waitFor(() => {\n      const newHeight = parseInt(toastRoot.style.getPropertyValue('--toast-height'), 10);\n      expect(newHeight).toBeGreaterThan(initialHeight);\n    });\n  });\n\n  // requires :focus-visible check\n  it.skipIf(isJSDOM)('closes when pressing escape', async () => {\n    const { user } = await render(\n      <Toast.Provider>\n        <Toast.Viewport>\n          <List />\n        </Toast.Viewport>\n        <Button />\n      </Toast.Provider>,\n    );\n\n    const button = screen.getByRole('button', { name: 'add' });\n\n    await act(async () => button.focus());\n    await user.click(button);\n\n    await user.keyboard('{F6}');\n    await user.keyboard('{Tab}');\n    await user.keyboard('{Escape}');\n\n    expect(screen.queryByTestId('root')).toBe(null);\n  });\n\n  describe.skipIf(isJSDOM)('swipe behavior', () => {\n    function SwipeTestButton({\n      toastOptions,\n    }: {\n      toastOptions?: Partial<ToastManagerAddOptions<any>>;\n    } = {}) {\n      const { add } = Toast.useToastManager();\n      return (\n        <button\n          type=\"button\"\n          onClick={() => {\n            add({\n              id: 'swipe-test-toast',\n              title: 'Swipe Me',\n              description: 'Swipe to dismiss',\n              ...(toastOptions ?? {}),\n            });\n          }}\n        >\n          add toast\n        </button>\n      );\n    }\n\n    function SwipeTestToast({\n      swipeDirection,\n    }: {\n      swipeDirection: Toast.Root.Props['swipeDirection'];\n    }) {\n      return Toast.useToastManager().toasts.map((toastItem) => (\n        <Toast.Root\n          key={toastItem.id}\n          toast={toastItem}\n          data-testid=\"toast-root\"\n          swipeDirection={swipeDirection}\n        >\n          <Toast.Title>{toastItem.title}</Toast.Title>\n          <Toast.Description>{toastItem.description}</Toast.Description>\n        </Toast.Root>\n      ));\n    }\n\n    function simulateSwipe(\n      element: HTMLElement,\n      startX: number,\n      startY: number,\n      endX: number,\n      endY: number,\n    ) {\n      fireEvent.pointerDown(element, {\n        clientX: startX,\n        clientY: startY,\n        button: 0,\n        bubbles: true,\n        pointerId: 1,\n      });\n      // Fire an initial move event close to the start to trigger the isFirstPointerMoveRef logic correctly.\n      // This simulates the finger moving slightly before the main swipe movement is registered.\n      let deltaX = 0;\n      if (endX > startX) {\n        deltaX = 1;\n      } else if (endX < startX) {\n        deltaX = -1;\n      }\n\n      let deltaY = 0;\n      if (endY > startY) {\n        deltaY = 1;\n      } else if (endY < startY) {\n        deltaY = -1;\n      }\n\n      fireEvent.pointerMove(element, {\n        clientX: startX + deltaX,\n        clientY: startY + deltaY,\n        bubbles: true,\n        pointerId: 1,\n      });\n      // Fire the main move event to the end position.\n      fireEvent.pointerMove(element, {\n        clientX: endX,\n        clientY: endY,\n        bubbles: true,\n        pointerId: 1,\n      });\n      fireEvent.pointerUp(element, { clientX: endX, clientY: endY, bubbles: true, pointerId: 1 });\n    }\n\n    it('closes toast when swiping in the specified direction beyond threshold', async () => {\n      await render(\n        <Toast.Provider>\n          <Toast.Viewport>\n            <SwipeTestToast swipeDirection=\"up\" />\n          </Toast.Viewport>\n          <SwipeTestButton />\n        </Toast.Provider>,\n      );\n\n      fireEvent.click(screen.getByRole('button', { name: 'add toast' }));\n\n      const toastElement = screen.getByTestId('toast-root');\n\n      // Swipe up (starting at y=100, ending at y=55, which is > 40px threshold)\n      simulateSwipe(toastElement, 100, 100, 100, 55);\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('toast-root')).toBe(null);\n      });\n    });\n\n    it('does not close toast when swiping in the specified direction below threshold', async () => {\n      await render(\n        <Toast.Provider>\n          <Toast.Viewport>\n            <SwipeTestToast swipeDirection=\"up\" />\n          </Toast.Viewport>\n          <SwipeTestButton />\n        </Toast.Provider>,\n      );\n\n      fireEvent.click(screen.getByRole('button', { name: 'add toast' }));\n\n      const toastElement = screen.getByTestId('toast-root');\n\n      // Swipe up but only by 10px (below 40px threshold)\n      simulateSwipe(toastElement, 100, 100, 100, 90);\n\n      expect(screen.queryByTestId('toast-root')).not.toBe(null);\n    });\n\n    it('does not close toast when swiping in a non-specified direction', async () => {\n      await render(\n        <Toast.Provider>\n          <Toast.Viewport>\n            <SwipeTestToast swipeDirection=\"up\" />\n          </Toast.Viewport>\n          <SwipeTestButton />\n        </Toast.Provider>,\n      );\n\n      fireEvent.click(screen.getByRole('button', { name: 'add toast' }));\n\n      const toastElement = screen.getByTestId('toast-root');\n\n      // Swipe down (opposite of allowed direction)\n      simulateSwipe(toastElement, 100, 100, 100, 150);\n\n      expect(screen.queryByTestId('toast-root')).not.toBe(null);\n    });\n\n    describe('supports multiple swipe directions', () => {\n      it('up + right', async () => {\n        await render(\n          <Toast.Provider>\n            <Toast.Viewport>\n              <SwipeTestToast swipeDirection={['up', 'right']} />\n            </Toast.Viewport>\n            <SwipeTestButton />\n          </Toast.Provider>,\n        );\n\n        const addToast = screen.getByRole('button', { name: 'add toast' });\n\n        fireEvent.click(addToast);\n\n        // Swipe right\n        simulateSwipe(screen.getByTestId('toast-root'), 100, 100, 150, 100);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('toast-root')).toBe(null);\n        });\n\n        fireEvent.click(addToast);\n\n        // Swipe up\n        simulateSwipe(screen.getByTestId('toast-root'), 100, 100, 100, 50);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('toast-root')).toBe(null);\n        });\n      });\n\n      it('right + left', async () => {\n        await render(\n          <Toast.Provider>\n            <Toast.Viewport>\n              <SwipeTestToast swipeDirection={['right', 'left']} />\n            </Toast.Viewport>\n            <SwipeTestButton />\n          </Toast.Provider>,\n        );\n\n        const addToast = screen.getByRole('button', { name: 'add toast' });\n\n        fireEvent.click(addToast);\n\n        // Swipe right\n        simulateSwipe(screen.getByTestId('toast-root'), 100, 100, 150, 100);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('toast-root')).toBe(null);\n        });\n\n        fireEvent.click(addToast);\n\n        // Swipe left\n        simulateSwipe(screen.getByTestId('toast-root'), 100, 100, 50, 100);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('toast-root')).toBe(null);\n        });\n      });\n\n      it('up + down', async () => {\n        await render(\n          <Toast.Provider>\n            <Toast.Viewport>\n              <SwipeTestToast swipeDirection={['up', 'down']} />\n            </Toast.Viewport>\n            <SwipeTestButton />\n          </Toast.Provider>,\n        );\n\n        const addToast = screen.getByRole('button', { name: 'add toast' });\n\n        fireEvent.click(addToast);\n\n        // Swipe up\n        simulateSwipe(screen.getByTestId('toast-root'), 100, 100, 100, 50);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('toast-root')).toBe(null);\n        });\n\n        fireEvent.click(addToast);\n\n        // Swipe down\n        simulateSwipe(screen.getByTestId('toast-root'), 100, 100, 100, 150);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('toast-root')).toBe(null);\n        });\n      });\n    });\n\n    it('cancels swipe when direction is reversed beyond threshold', async () => {\n      await render(\n        <Toast.Provider>\n          <Toast.Viewport>\n            <SwipeTestToast swipeDirection=\"up\" />\n          </Toast.Viewport>\n          <SwipeTestButton />\n        </Toast.Provider>,\n      );\n\n      fireEvent.click(screen.getByRole('button', { name: 'add toast' }));\n\n      const toastElement = screen.getByTestId('toast-root');\n\n      // Start swiping up\n      fireEvent.pointerDown(toastElement, { clientX: 100, clientY: 100, button: 0, pointerId: 1 });\n      fireEvent.pointerMove(toastElement, { clientX: 100, clientY: 80, pointerId: 1 });\n\n      // Then reverse direction\n      fireEvent.pointerMove(toastElement, { clientX: 100, clientY: 90, pointerId: 1 });\n      fireEvent.pointerUp(toastElement, { clientX: 100, clientY: 90, pointerId: 1 });\n\n      expect(screen.queryByTestId('toast-root')).not.toBe(null);\n    });\n\n    it('applies [data-swiping] attribute when swiping', async () => {\n      await render(\n        <Toast.Provider>\n          <Toast.Viewport>\n            <SwipeTestToast swipeDirection=\"up\" />\n          </Toast.Viewport>\n          <SwipeTestButton />\n        </Toast.Provider>,\n      );\n\n      fireEvent.click(screen.getByRole('button', { name: 'add toast' }));\n\n      const toastElement = screen.getByTestId('toast-root');\n\n      fireEvent.pointerDown(toastElement, { clientX: 100, clientY: 100, button: 0, pointerId: 1 });\n\n      expect(toastElement.getAttribute('data-swiping')).toBe('');\n    });\n\n    it('dismisses toast when swiped down with downward swipe direction', async () => {\n      await render(\n        <Toast.Provider>\n          <Toast.Viewport>\n            <SwipeTestToast swipeDirection=\"down\" />\n          </Toast.Viewport>\n          <SwipeTestButton />\n        </Toast.Provider>,\n      );\n\n      fireEvent.click(screen.getByRole('button', { name: 'add toast' }));\n\n      const toastElement = screen.getByTestId('toast-root');\n      simulateSwipe(toastElement, 100, 100, 100, 150);\n\n      expect(screen.queryByTestId('toast-root')).toBe(null);\n    });\n\n    it('dismisses toast when swiped left with leftward swipe direction', async () => {\n      await render(\n        <Toast.Provider>\n          <Toast.Viewport>\n            <SwipeTestToast swipeDirection=\"left\" />\n          </Toast.Viewport>\n          <SwipeTestButton />\n        </Toast.Provider>,\n      );\n\n      fireEvent.click(screen.getByRole('button', { name: 'add toast' }));\n\n      const toastElement = screen.getByTestId('toast-root');\n      simulateSwipe(toastElement, 100, 100, 50, 100);\n\n      expect(screen.queryByTestId('toast-root')).toBe(null);\n    });\n\n    it('dismisses toast when swiped right with rightward swipe direction', async () => {\n      await render(\n        <Toast.Provider>\n          <Toast.Viewport>\n            <SwipeTestToast swipeDirection=\"right\" />\n          </Toast.Viewport>\n          <SwipeTestButton />\n        </Toast.Provider>,\n      );\n\n      fireEvent.click(screen.getByRole('button', { name: 'add toast' }));\n\n      const toastElement = screen.getByTestId('toast-root');\n      simulateSwipe(toastElement, 100, 100, 150, 100);\n\n      expect(screen.queryByTestId('toast-root')).toBe(null);\n    });\n\n    it('allows swiping in multiple directions when specified', async () => {\n      await render(\n        <Toast.Provider>\n          <Toast.Viewport>\n            <SwipeTestToast swipeDirection={['up', 'right']} />\n          </Toast.Viewport>\n          <SwipeTestButton />\n        </Toast.Provider>,\n      );\n\n      fireEvent.click(screen.getByRole('button', { name: 'add toast' }));\n\n      const toastElement = screen.getByTestId('toast-root');\n\n      // First test upward swipe\n      simulateSwipe(toastElement, 100, 100, 100, 50);\n\n      expect(screen.queryByTestId('toast-root')).toBe(null);\n\n      fireEvent.click(screen.getByRole('button', { name: 'add toast' }));\n      const secondToastElement = screen.getByTestId('toast-root');\n\n      simulateSwipe(secondToastElement, 100, 100, 150, 100);\n\n      expect(screen.queryByTestId('toast-root')).toBe(null);\n    });\n\n    it('does not dismiss when swiped in non-specified direction', async () => {\n      await render(\n        <Toast.Provider>\n          <Toast.Viewport>\n            <SwipeTestToast swipeDirection=\"up\" />\n          </Toast.Viewport>\n          <SwipeTestButton />\n        </Toast.Provider>,\n      );\n\n      fireEvent.click(screen.getByRole('button', { name: 'add toast' }));\n\n      const toastElement = screen.getByTestId('toast-root');\n\n      // Swipe right (not allowed)\n      simulateSwipe(toastElement, 100, 100, 150, 100);\n\n      expect(screen.queryByTestId('toast-root')).not.toBe(null);\n\n      // Swipe down (not allowed)\n      simulateSwipe(toastElement, 100, 100, 100, 150);\n\n      expect(screen.queryByTestId('toast-root')).not.toBe(null);\n    });\n\n    it('does not dismiss when swipe distance is below threshold', async () => {\n      await render(\n        <Toast.Provider>\n          <Toast.Viewport>\n            <SwipeTestToast swipeDirection=\"up\" />\n          </Toast.Viewport>\n          <SwipeTestButton />\n        </Toast.Provider>,\n      );\n\n      fireEvent.click(screen.getByRole('button', { name: 'add toast' }));\n\n      const toastElement = screen.getByTestId('toast-root');\n\n      // Small upward swipe (below threshold)\n      simulateSwipe(toastElement, 100, 100, 100, 95);\n\n      expect(screen.queryByTestId('toast-root')).not.toBe(null);\n    });\n\n    it('does not start swiping from elements with the data-base-ui-swipe-ignore attribute', async () => {\n      await render(\n        <Toast.Provider>\n          <Toast.Viewport>\n            <Toast.Root toast={toast} data-testid=\"toast-root\" swipeDirection=\"up\">\n              <div data-base-ui-swipe-ignore data-testid=\"ignore-target\">\n                Ignore swipe\n              </div>\n            </Toast.Root>\n          </Toast.Viewport>\n        </Toast.Provider>,\n      );\n\n      const toastElement = screen.getByTestId('toast-root');\n      const ignoreTarget = screen.getByTestId('ignore-target');\n\n      fireEvent.pointerDown(ignoreTarget, { clientX: 100, clientY: 100, button: 0, pointerId: 1 });\n\n      expect(toastElement).not.toHaveAttribute('data-swiping');\n    });\n\n    it('does not start swiping from elements with the legacy data-swipe-ignore attribute', async () => {\n      await render(\n        <Toast.Provider>\n          <Toast.Viewport>\n            <Toast.Root toast={toast} data-testid=\"toast-root\" swipeDirection=\"up\">\n              <div data-swipe-ignore data-testid=\"ignore-target\">\n                Ignore swipe\n              </div>\n            </Toast.Root>\n          </Toast.Viewport>\n        </Toast.Provider>,\n      );\n\n      const toastElement = screen.getByTestId('toast-root');\n      const ignoreTarget = screen.getByTestId('ignore-target');\n\n      fireEvent.pointerDown(ignoreTarget, { clientX: 100, clientY: 100, button: 0, pointerId: 1 });\n\n      expect(toastElement).not.toHaveAttribute('data-swiping');\n    });\n\n    it('ignores swipe gestures when toast is anchored', async () => {\n      const anchor = document.createElement('div');\n      document.body.appendChild(anchor);\n\n      try {\n        await render(\n          <Toast.Provider>\n            <Toast.Viewport>\n              <SwipeTestToast swipeDirection=\"up\" />\n            </Toast.Viewport>\n            <SwipeTestButton\n              toastOptions={{\n                positionerProps: {\n                  anchor,\n                },\n              }}\n            />\n          </Toast.Provider>,\n        );\n\n        fireEvent.click(screen.getByRole('button', { name: 'add toast' }));\n\n        const toastElement = screen.getByTestId('toast-root');\n\n        simulateSwipe(toastElement, 100, 100, 100, 55);\n\n        expect(screen.queryByTestId('toast-root')).not.toBe(null);\n      } finally {\n        document.body.removeChild(anchor);\n      }\n    });\n  });\n\n  describe('object identity', () => {\n    // Regression test for https://github.com/mui/base-ui/issues/3922\n    // Toast calculations should use ID-based lookups, not referential equality\n    it('works correctly when toast objects are recreated (not referentially equal)', async () => {\n      // This component wraps useToastManager and creates NEW toast objects\n      // by spreading them. This is a common pattern when users want to\n      // add type-safety to the data field or transform toast properties.\n      function ToastListWithNewObjects() {\n        const { toasts } = Toast.useToastManager();\n\n        // Create new objects - this breaks referential equality\n        const transformedToasts = toasts.map((t) => ({\n          ...t,\n          // Users might transform data for type-safety, like:\n          // data: parseToastData(t.data)\n        }));\n\n        return transformedToasts.map((toastItem) => (\n          <Toast.Root key={toastItem.id} toast={toastItem} data-testid=\"toast-root\">\n            <Toast.Title>{toastItem.title}</Toast.Title>\n            <Toast.Description>{toastItem.description}</Toast.Description>\n            <Toast.Close data-testid=\"toast-close\">Close</Toast.Close>\n          </Toast.Root>\n        ));\n      }\n\n      function AddButton() {\n        const { add } = Toast.useToastManager();\n        return (\n          <button\n            type=\"button\"\n            onClick={() => {\n              add({\n                id: 'test-toast',\n                title: 'Test Title',\n                description: 'Test Description',\n              });\n            }}\n          >\n            add toast\n          </button>\n        );\n      }\n\n      await render(\n        <Toast.Provider>\n          <Toast.Viewport>\n            <ToastListWithNewObjects />\n          </Toast.Viewport>\n          <AddButton />\n        </Toast.Provider>,\n      );\n\n      fireEvent.click(screen.getByRole('button', { name: 'add toast' }));\n\n      const toastElement = await screen.findByTestId('toast-root');\n\n      // Verify the toast index is correctly calculated (should be 0, not -1)\n      // The --toast-index CSS variable is set based on the domIndex calculation\n      await waitFor(() => {\n        const toastIndex = toastElement.style.getPropertyValue('--toast-index');\n        expect(toastIndex).toBe('0');\n      });\n\n      // Verify the close button works (which also relies on ID-based lookup)\n      fireEvent.click(screen.getByTestId('toast-close'));\n\n      await waitFor(() => {\n        expect(screen.queryByTestId('toast-root')).toBe(null);\n      });\n    });\n\n    it('correctly calculates indices for multiple toasts with recreated objects', async () => {\n      function ToastListWithNewObjects() {\n        const { toasts } = Toast.useToastManager();\n\n        // Create new objects - this breaks referential equality\n        const transformedToasts = toasts.map((t) => ({ ...t }));\n\n        return transformedToasts.map((toastItem) => (\n          <Toast.Root key={toastItem.id} toast={toastItem} data-testid={`toast-${toastItem.id}`}>\n            <Toast.Title>{toastItem.title}</Toast.Title>\n          </Toast.Root>\n        ));\n      }\n\n      function AddButton() {\n        const { add } = Toast.useToastManager();\n        return (\n          <button\n            type=\"button\"\n            onClick={() => {\n              add({ title: 'Toast 1' });\n              add({ title: 'Toast 2' });\n              add({ title: 'Toast 3' });\n            }}\n          >\n            add toasts\n          </button>\n        );\n      }\n\n      await render(\n        <Toast.Provider>\n          <Toast.Viewport>\n            <ToastListWithNewObjects />\n          </Toast.Viewport>\n          <AddButton />\n        </Toast.Provider>,\n      );\n\n      fireEvent.click(screen.getByRole('button', { name: 'add toasts' }));\n\n      // Wait for all toasts to appear\n      const toasts = await screen.findAllByTestId(/^toast-/);\n      expect(toasts).toHaveLength(3);\n\n      // Verify each toast has a valid (non-negative) index\n      await waitFor(() => {\n        toasts.forEach((toastEl) => {\n          const toastIndex = parseInt(toastEl.style.getPropertyValue('--toast-index'), 10);\n          expect(toastIndex).toBeGreaterThanOrEqual(0);\n        });\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/toast/root/ToastRoot.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport { ownerDocument } from '@base-ui/utils/owner';\nimport { inertValue } from '@base-ui/utils/inertValue';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { activeElement, contains, getTarget } from '../../floating-ui-react/utils';\nimport type { BaseUIComponentProps, HTMLProps } from '../../utils/types';\nimport type { ToastObject as ToastObjectType } from '../useToastManager';\nimport { ToastRootContext } from './ToastRootContext';\nimport { transitionStatusMapping } from '../../utils/stateAttributesMapping';\nimport type { TransitionStatus } from '../../utils/useTransitionStatus';\nimport { useToastProviderContext } from '../provider/ToastProviderContext';\nimport { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useOpenChangeComplete } from '../../utils/useOpenChangeComplete';\nimport { ToastRootCssVars } from './ToastRootCssVars';\nimport { BASE_UI_SWIPE_IGNORE_SELECTOR, LEGACY_SWIPE_IGNORE_SELECTOR } from '../../utils/constants';\n\nconst stateAttributesMapping: StateAttributesMapping<ToastRootState> = {\n  ...transitionStatusMapping,\n  swipeDirection(value) {\n    return value ? { 'data-swipe-direction': value } : null;\n  },\n};\n\nconst SWIPE_THRESHOLD = 40;\nconst REVERSE_CANCEL_THRESHOLD = 10;\nconst OPPOSITE_DIRECTION_DAMPING_FACTOR = 0.5;\nconst MIN_DRAG_THRESHOLD = 1;\nconst TOAST_SWIPE_IGNORE_SELECTOR = `${BASE_UI_SWIPE_IGNORE_SELECTOR},${LEGACY_SWIPE_IGNORE_SELECTOR}`;\n\nfunction getDisplacement(\n  direction: 'up' | 'down' | 'left' | 'right',\n  deltaX: number,\n  deltaY: number,\n) {\n  switch (direction) {\n    case 'up':\n      return -deltaY;\n    case 'down':\n      return deltaY;\n    case 'left':\n      return -deltaX;\n    case 'right':\n      return deltaX;\n    default:\n      return 0;\n  }\n}\n\nfunction getElementTransform(element: HTMLElement) {\n  const computedStyle = window.getComputedStyle(element);\n  const transform = computedStyle.transform;\n  let translateX = 0;\n  let translateY = 0;\n  let scale = 1;\n  if (transform && transform !== 'none') {\n    const matrix = transform.match(/matrix(?:3d)?\\(([^)]+)\\)/);\n    if (matrix) {\n      const values = matrix[1].split(', ').map(parseFloat);\n      if (values.length === 6) {\n        translateX = values[4];\n        translateY = values[5];\n        scale = Math.sqrt(values[0] * values[0] + values[1] * values[1]);\n      } else if (values.length === 16) {\n        translateX = values[12];\n        translateY = values[13];\n        scale = values[0];\n      }\n    }\n  }\n  return { x: translateX, y: translateY, scale };\n}\n\n/**\n * Groups all parts of an individual toast.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Toast](https://base-ui.com/react/components/toast)\n */\nexport const ToastRoot = React.forwardRef(function ToastRoot(\n  componentProps: ToastRoot.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const {\n    toast,\n    render,\n    className,\n    swipeDirection = ['down', 'right'],\n    ...elementProps\n  } = componentProps;\n\n  const isAnchored = toast.positionerProps?.anchor !== undefined;\n\n  let swipeDirections: ('up' | 'down' | 'left' | 'right')[] = [];\n  if (!isAnchored) {\n    swipeDirections = Array.isArray(swipeDirection) ? swipeDirection : [swipeDirection];\n  }\n\n  const swipeEnabled = swipeDirections.length > 0;\n\n  const store = useToastProviderContext();\n\n  const [currentSwipeDirection, setCurrentSwipeDirection] = React.useState<\n    'up' | 'down' | 'left' | 'right' | undefined\n  >(undefined);\n  const [isSwiping, setIsSwiping] = React.useState(false);\n  const [isRealSwipe, setIsRealSwipe] = React.useState(false);\n  const [dragDismissed, setDragDismissed] = React.useState(false);\n  const [dragOffset, setDragOffset] = React.useState({ x: 0, y: 0 });\n  const [initialTransform, setInitialTransform] = React.useState({ x: 0, y: 0, scale: 1 });\n  const [titleId, setTitleId] = React.useState<string | undefined>();\n  const [descriptionId, setDescriptionId] = React.useState<string | undefined>();\n  const [lockedDirection, setLockedDirection] = React.useState<'horizontal' | 'vertical' | null>(\n    null,\n  );\n\n  const rootRef = React.useRef<HTMLDivElement | null>(null);\n  const dragStartPosRef = React.useRef({ x: 0, y: 0 });\n  const initialTransformRef = React.useRef({ x: 0, y: 0, scale: 1 });\n  const intendedSwipeDirectionRef = React.useRef<'up' | 'down' | 'left' | 'right' | undefined>(\n    undefined,\n  );\n  const maxSwipeDisplacementRef = React.useRef(0);\n  const cancelledSwipeRef = React.useRef(false);\n  const swipeCancelBaselineRef = React.useRef({ x: 0, y: 0 });\n  const isFirstPointerMoveRef = React.useRef(false);\n\n  const domIndex = store.useState('toastIndex', toast.id);\n  const visibleIndex = store.useState('toastVisibleIndex', toast.id);\n  const offsetY = store.useState('toastOffsetY', toast.id);\n  const focused = store.useState('focused');\n  const expanded = store.useState('expanded');\n\n  useOpenChangeComplete({\n    open: toast.transitionStatus !== 'ending',\n    ref: rootRef,\n    onComplete() {\n      if (toast.transitionStatus === 'ending') {\n        store.removeToast(toast.id);\n      }\n    },\n  });\n\n  /**\n   * Recalculates the natural height of the toast and updates it in the toast manager.\n   * @param flushSync Whether to flush the update synchronously. Use in observer\n   * callbacks to avoid visual flickers.\n   */\n  const recalculateHeight = useStableCallback((flushSync: boolean = false) => {\n    const element = rootRef.current;\n    if (!element) {\n      return;\n    }\n\n    const previousHeight = element.style.height;\n    element.style.height = 'auto';\n    const height = element.offsetHeight;\n    element.style.height = previousHeight;\n\n    function update() {\n      store.updateToastInternal(toast.id, {\n        ref: rootRef,\n        height,\n        ...(toast.transitionStatus === 'starting' ? { transitionStatus: undefined } : {}),\n      });\n    }\n\n    if (flushSync) {\n      ReactDOM.flushSync(update);\n    } else {\n      update();\n    }\n  });\n\n  useIsoLayoutEffect(recalculateHeight, [recalculateHeight]);\n\n  function applyDirectionalDamping(deltaX: number, deltaY: number) {\n    let newDeltaX = deltaX;\n    let newDeltaY = deltaY;\n\n    if (!swipeDirections.includes('left') && !swipeDirections.includes('right')) {\n      newDeltaX =\n        deltaX > 0\n          ? deltaX ** OPPOSITE_DIRECTION_DAMPING_FACTOR\n          : -(Math.abs(deltaX) ** OPPOSITE_DIRECTION_DAMPING_FACTOR);\n    } else {\n      if (!swipeDirections.includes('right') && deltaX > 0) {\n        newDeltaX = deltaX ** OPPOSITE_DIRECTION_DAMPING_FACTOR;\n      }\n      if (!swipeDirections.includes('left') && deltaX < 0) {\n        newDeltaX = -(Math.abs(deltaX) ** OPPOSITE_DIRECTION_DAMPING_FACTOR);\n      }\n    }\n\n    if (!swipeDirections.includes('up') && !swipeDirections.includes('down')) {\n      newDeltaY =\n        deltaY > 0\n          ? deltaY ** OPPOSITE_DIRECTION_DAMPING_FACTOR\n          : -(Math.abs(deltaY) ** OPPOSITE_DIRECTION_DAMPING_FACTOR);\n    } else {\n      if (!swipeDirections.includes('down') && deltaY > 0) {\n        newDeltaY = deltaY ** OPPOSITE_DIRECTION_DAMPING_FACTOR;\n      }\n      if (!swipeDirections.includes('up') && deltaY < 0) {\n        newDeltaY = -(Math.abs(deltaY) ** OPPOSITE_DIRECTION_DAMPING_FACTOR);\n      }\n    }\n\n    return { x: newDeltaX, y: newDeltaY };\n  }\n\n  function handlePointerDown(event: React.PointerEvent) {\n    if (event.button !== 0) {\n      return;\n    }\n\n    if (event.pointerType === 'touch') {\n      store.pauseTimers();\n    }\n\n    const target = getTarget(event.nativeEvent) as HTMLElement | null;\n\n    const isInteractiveElement = target\n      ? target.closest(`button,a,input,textarea,[role=\"button\"],${TOAST_SWIPE_IGNORE_SELECTOR}`)\n      : false;\n\n    if (isInteractiveElement) {\n      return;\n    }\n\n    cancelledSwipeRef.current = false;\n    intendedSwipeDirectionRef.current = undefined;\n    maxSwipeDisplacementRef.current = 0;\n    dragStartPosRef.current = { x: event.clientX, y: event.clientY };\n    swipeCancelBaselineRef.current = dragStartPosRef.current;\n\n    if (rootRef.current) {\n      const transform = getElementTransform(rootRef.current);\n      initialTransformRef.current = transform;\n      setInitialTransform(transform);\n      setDragOffset({\n        x: transform.x,\n        y: transform.y,\n      });\n    }\n\n    store.setHovering(true);\n    setIsSwiping(true);\n    setIsRealSwipe(false);\n    setLockedDirection(null);\n    isFirstPointerMoveRef.current = true;\n\n    rootRef.current?.setPointerCapture(event.pointerId);\n  }\n\n  function handlePointerMove(event: React.PointerEvent) {\n    if (!isSwiping) {\n      return;\n    }\n\n    // Prevent text selection on Safari\n    event.preventDefault();\n\n    if (isFirstPointerMoveRef.current) {\n      // Adjust the starting position to the current position on the first move\n      // to account for the delay between pointerdown and the first pointermove on iOS.\n      dragStartPosRef.current = { x: event.clientX, y: event.clientY };\n      isFirstPointerMoveRef.current = false;\n    }\n\n    const { clientY, clientX, movementX, movementY } = event;\n\n    if (\n      (movementY < 0 && clientY > swipeCancelBaselineRef.current.y) ||\n      (movementY > 0 && clientY < swipeCancelBaselineRef.current.y)\n    ) {\n      swipeCancelBaselineRef.current = { x: swipeCancelBaselineRef.current.x, y: clientY };\n    }\n\n    if (\n      (movementX < 0 && clientX > swipeCancelBaselineRef.current.x) ||\n      (movementX > 0 && clientX < swipeCancelBaselineRef.current.x)\n    ) {\n      swipeCancelBaselineRef.current = { x: clientX, y: swipeCancelBaselineRef.current.y };\n    }\n\n    const deltaX = clientX - dragStartPosRef.current.x;\n    const deltaY = clientY - dragStartPosRef.current.y;\n    const cancelDeltaY = clientY - swipeCancelBaselineRef.current.y;\n    const cancelDeltaX = clientX - swipeCancelBaselineRef.current.x;\n\n    if (!isRealSwipe) {\n      const movementDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);\n      if (movementDistance >= MIN_DRAG_THRESHOLD) {\n        setIsRealSwipe(true);\n        if (lockedDirection === null) {\n          const hasHorizontal =\n            swipeDirections.includes('left') || swipeDirections.includes('right');\n          const hasVertical = swipeDirections.includes('up') || swipeDirections.includes('down');\n          if (hasHorizontal && hasVertical) {\n            const absX = Math.abs(deltaX);\n            const absY = Math.abs(deltaY);\n            setLockedDirection(absX > absY ? 'horizontal' : 'vertical');\n          }\n        }\n      }\n    }\n\n    let candidate: 'up' | 'down' | 'left' | 'right' | undefined;\n    if (!intendedSwipeDirectionRef.current) {\n      if (lockedDirection === 'vertical') {\n        if (deltaY > 0) {\n          candidate = 'down';\n        } else if (deltaY < 0) {\n          candidate = 'up';\n        }\n      } else if (lockedDirection === 'horizontal') {\n        if (deltaX > 0) {\n          candidate = 'right';\n        } else if (deltaX < 0) {\n          candidate = 'left';\n        }\n      } else if (Math.abs(deltaX) >= Math.abs(deltaY)) {\n        candidate = deltaX > 0 ? 'right' : 'left';\n      } else {\n        candidate = deltaY > 0 ? 'down' : 'up';\n      }\n\n      if (candidate && swipeDirections.includes(candidate)) {\n        intendedSwipeDirectionRef.current = candidate;\n        maxSwipeDisplacementRef.current = getDisplacement(candidate, deltaX, deltaY);\n        setCurrentSwipeDirection(candidate);\n      }\n    } else {\n      const direction = intendedSwipeDirectionRef.current;\n      const currentDisplacement = getDisplacement(direction, cancelDeltaX, cancelDeltaY);\n      if (currentDisplacement > SWIPE_THRESHOLD) {\n        cancelledSwipeRef.current = false;\n        setCurrentSwipeDirection(direction);\n      } else if (\n        !(swipeDirections.includes('left') && swipeDirections.includes('right')) &&\n        !(swipeDirections.includes('up') && swipeDirections.includes('down')) &&\n        maxSwipeDisplacementRef.current - currentDisplacement >= REVERSE_CANCEL_THRESHOLD\n      ) {\n        // Mark that a change-of-mind has occurred\n        cancelledSwipeRef.current = true;\n      }\n    }\n\n    const dampedDelta = applyDirectionalDamping(deltaX, deltaY);\n    let newOffsetX = initialTransformRef.current.x;\n    let newOffsetY = initialTransformRef.current.y;\n\n    if (lockedDirection === 'horizontal') {\n      if (swipeDirections.includes('left') || swipeDirections.includes('right')) {\n        newOffsetX += dampedDelta.x;\n      }\n    } else if (lockedDirection === 'vertical') {\n      if (swipeDirections.includes('up') || swipeDirections.includes('down')) {\n        newOffsetY += dampedDelta.y;\n      }\n    } else {\n      if (swipeDirections.includes('left') || swipeDirections.includes('right')) {\n        newOffsetX += dampedDelta.x;\n      }\n      if (swipeDirections.includes('up') || swipeDirections.includes('down')) {\n        newOffsetY += dampedDelta.y;\n      }\n    }\n\n    setDragOffset({ x: newOffsetX, y: newOffsetY });\n  }\n\n  function handlePointerUp(event: React.PointerEvent) {\n    if (!isSwiping) {\n      return;\n    }\n\n    setIsSwiping(false);\n    setIsRealSwipe(false);\n    setLockedDirection(null);\n\n    rootRef.current?.releasePointerCapture(event.pointerId);\n\n    if (cancelledSwipeRef.current) {\n      setDragOffset({ x: initialTransform.x, y: initialTransform.y });\n      setCurrentSwipeDirection(undefined);\n      return;\n    }\n\n    let shouldClose = false;\n    const deltaX = dragOffset.x - initialTransform.x;\n    const deltaY = dragOffset.y - initialTransform.y;\n    let dismissDirection: 'up' | 'down' | 'left' | 'right' | undefined;\n\n    for (const direction of swipeDirections) {\n      switch (direction) {\n        case 'right':\n          if (deltaX > SWIPE_THRESHOLD) {\n            shouldClose = true;\n            dismissDirection = 'right';\n          }\n          break;\n        case 'left':\n          if (deltaX < -SWIPE_THRESHOLD) {\n            shouldClose = true;\n            dismissDirection = 'left';\n          }\n          break;\n        case 'down':\n          if (deltaY > SWIPE_THRESHOLD) {\n            shouldClose = true;\n            dismissDirection = 'down';\n          }\n          break;\n        case 'up':\n          if (deltaY < -SWIPE_THRESHOLD) {\n            shouldClose = true;\n            dismissDirection = 'up';\n          }\n          break;\n        default:\n          break;\n      }\n      if (shouldClose) {\n        break;\n      }\n    }\n\n    if (shouldClose) {\n      setCurrentSwipeDirection(dismissDirection);\n      setDragDismissed(true);\n      store.closeToast(toast.id);\n    } else {\n      setDragOffset({ x: initialTransform.x, y: initialTransform.y });\n      setCurrentSwipeDirection(undefined);\n    }\n  }\n\n  function handleKeyDown(event: React.KeyboardEvent) {\n    if (event.key === 'Escape') {\n      if (\n        !rootRef.current ||\n        !contains(rootRef.current, activeElement(ownerDocument(rootRef.current)))\n      ) {\n        return;\n      }\n      store.closeToast(toast.id);\n    }\n  }\n\n  React.useEffect(() => {\n    if (!swipeEnabled) {\n      return undefined;\n    }\n\n    const element = rootRef.current;\n    if (!element) {\n      return undefined;\n    }\n\n    function preventDefaultTouchStart(event: TouchEvent) {\n      if (contains(element, event.target as HTMLElement | null)) {\n        event.preventDefault();\n      }\n    }\n\n    element.addEventListener('touchmove', preventDefaultTouchStart, { passive: false });\n    return () => {\n      element.removeEventListener('touchmove', preventDefaultTouchStart);\n    };\n  }, [swipeEnabled]);\n\n  function getDragStyles() {\n    if (\n      !isSwiping &&\n      dragOffset.x === initialTransform.x &&\n      dragOffset.y === initialTransform.y &&\n      !dragDismissed\n    ) {\n      return {\n        [ToastRootCssVars.swipeMovementX]: '0px',\n        [ToastRootCssVars.swipeMovementY]: '0px',\n      };\n    }\n\n    const deltaX = dragOffset.x - initialTransform.x;\n    const deltaY = dragOffset.y - initialTransform.y;\n\n    return {\n      transition: isSwiping ? 'none' : undefined,\n      // While swiping, freeze the element at its current visual transform so it doesn't snap to the\n      // end position.\n      transform: isSwiping\n        ? `translateX(${dragOffset.x}px) translateY(${dragOffset.y}px) scale(${initialTransform.scale})`\n        : undefined,\n      [ToastRootCssVars.swipeMovementX]: `${deltaX}px`,\n      [ToastRootCssVars.swipeMovementY]: `${deltaY}px`,\n    };\n  }\n\n  const isHighPriority = toast.priority === 'high';\n\n  const defaultProps: HTMLProps = {\n    role: isHighPriority ? 'alertdialog' : 'dialog',\n    tabIndex: 0,\n    'aria-modal': false,\n    'aria-labelledby': titleId,\n    'aria-describedby': descriptionId,\n    'aria-hidden': isHighPriority && !focused ? true : undefined,\n    onPointerDown: swipeEnabled ? handlePointerDown : undefined,\n    onPointerMove: swipeEnabled ? handlePointerMove : undefined,\n    onPointerUp: swipeEnabled ? handlePointerUp : undefined,\n    onKeyDown: handleKeyDown,\n    inert: inertValue(toast.limited),\n    style: {\n      ...getDragStyles(),\n      [ToastRootCssVars.index as string]:\n        toast.transitionStatus === 'ending' ? domIndex : visibleIndex,\n      [ToastRootCssVars.offsetY as string]: `${offsetY}px`,\n      [ToastRootCssVars.height as string]: toast.height ? `${toast.height}px` : undefined,\n    },\n  };\n\n  const toastRoot: ToastRootContext = React.useMemo(\n    () => ({\n      rootRef,\n      toast,\n      titleId,\n      setTitleId,\n      descriptionId,\n      setDescriptionId,\n      swiping: isSwiping,\n      swipeDirection: currentSwipeDirection,\n      recalculateHeight,\n      index: domIndex,\n      visibleIndex,\n      expanded,\n    }),\n    [\n      toast,\n      titleId,\n      descriptionId,\n      isSwiping,\n      currentSwipeDirection,\n      recalculateHeight,\n      domIndex,\n      visibleIndex,\n      expanded,\n    ],\n  );\n\n  const state: ToastRootState = {\n    transitionStatus: toast.transitionStatus,\n    expanded,\n    limited: toast.limited || false,\n    type: toast.type,\n    swiping: toastRoot.swiping,\n    swipeDirection: toastRoot.swipeDirection,\n  };\n\n  const element = useRenderElement('div', componentProps, {\n    ref: [forwardedRef, toastRoot.rootRef],\n    state,\n    stateAttributesMapping,\n    props: [defaultProps, elementProps],\n  });\n\n  return <ToastRootContext.Provider value={toastRoot}>{element}</ToastRootContext.Provider>;\n});\n\nexport type ToastRootToastObject<Data extends object = any> = ToastObjectType<Data>;\nexport interface ToastRootState {\n  /**\n   * The transition status of the component.\n   */\n  transitionStatus: TransitionStatus;\n  /**\n   * Whether the toasts in the viewport are expanded.\n   */\n  expanded: boolean;\n  /**\n   * Whether the toast was removed due to exceeding the limit.\n   */\n  limited: boolean;\n  /**\n   * The type of the toast.\n   */\n  type: string | undefined;\n  /**\n   * Whether the toast is being swiped.\n   */\n  swiping: boolean;\n  /**\n   * The direction the toast is being swiped.\n   */\n  swipeDirection: 'up' | 'down' | 'left' | 'right' | undefined;\n}\nexport interface ToastRootProps extends BaseUIComponentProps<'div', ToastRootState> {\n  /**\n   * The toast to render.\n   */\n  toast: ToastRootToastObject<any>;\n  /**\n   * Direction(s) in which the toast can be swiped to dismiss.\n   * @default ['down', 'right']\n   */\n  swipeDirection?:\n    | 'up'\n    | 'down'\n    | 'left'\n    | 'right'\n    | ('up' | 'down' | 'left' | 'right')[]\n    | undefined;\n}\n\nexport namespace ToastRoot {\n  export type ToastObject<Data extends object = any> = ToastRootToastObject<Data>;\n  export type State = ToastRootState;\n  export type Props = ToastRootProps;\n}\n"
  },
  {
    "path": "packages/react/src/toast/root/ToastRootContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport type { ToastObject } from '../useToastManager';\n\nexport interface ToastRootContext {\n  toast: ToastObject<any>;\n  rootRef: React.RefObject<HTMLElement | null>;\n  titleId: string | undefined;\n  setTitleId: React.Dispatch<React.SetStateAction<string | undefined>>;\n  descriptionId: string | undefined;\n  setDescriptionId: React.Dispatch<React.SetStateAction<string | undefined>>;\n  swiping: boolean;\n  swipeDirection: 'up' | 'down' | 'left' | 'right' | undefined;\n  index: number;\n  visibleIndex: number;\n  expanded: boolean;\n  recalculateHeight: (flushSync?: boolean) => void;\n}\n\nexport const ToastRootContext = React.createContext<ToastRootContext | undefined>(undefined);\n\nexport function useToastRootContext(): ToastRootContext {\n  const context = React.useContext(ToastRootContext);\n  if (!context) {\n    throw new Error(\n      'Base UI: ToastRootContext is missing. Toast parts must be used within <Toast.Root>.',\n    );\n  }\n  return context as ToastRootContext;\n}\n"
  },
  {
    "path": "packages/react/src/toast/root/ToastRootCssVars.ts",
    "content": "export enum ToastRootCssVars {\n  /**\n   * Indicates the index of the toast in the list.\n   * @type {number}\n   */\n  index = '--toast-index',\n  /**\n   * Indicates the vertical pixels offset of the toast in the list when expanded.\n   * @type {number}\n   */\n  offsetY = '--toast-offset-y',\n  /**\n   * Indicates the measured natural height of the toast in pixels.\n   * @type {number}\n   */\n  height = '--toast-height',\n  /**\n   * Indicates the horizontal swipe movement of the toast.\n   * @type {number}\n   */\n  swipeMovementX = '--toast-swipe-movement-x',\n  /**\n   * Indicates the vertical swipe movement of the toast.\n   * @type {number}\n   */\n  swipeMovementY = '--toast-swipe-movement-y',\n}\n"
  },
  {
    "path": "packages/react/src/toast/root/ToastRootDataAttributes.ts",
    "content": "import { TransitionStatusDataAttributes } from '../../utils/stateAttributesMapping';\n\nexport enum ToastRootDataAttributes {\n  /**\n   * Present when the toast is expanded in the viewport.\n   * @type {boolean}\n   */\n  expanded = 'data-expanded',\n  /**\n   * Present when the toast was removed due to exceeding the limit.\n   * @type {boolean}\n   */\n  limited = 'data-limited',\n  /**\n   * The type of the toast.\n   * @type {string}\n   */\n  type = 'data-type',\n  /**\n   * Present when the toast is being swiped.\n   * @type {boolean}\n   */\n  swiping = 'data-swiping',\n  /**\n   * The direction the toast was swiped.\n   * @type {'up' | 'down' | 'left' | 'right'}\n   */\n  swipeDirection = 'data-swipe-direction',\n  /**\n   * Present when the toast is animating in.\n   */\n  startingStyle = TransitionStatusDataAttributes.startingStyle,\n  /**\n   * Present when the toast is animating out.\n   */\n  endingStyle = TransitionStatusDataAttributes.endingStyle,\n}\n"
  },
  {
    "path": "packages/react/src/toast/store.ts",
    "content": "import { ReactStore, createSelector, createSelectorMemoized } from '@base-ui/utils/store';\nimport { generateId } from '@base-ui/utils/generateId';\nimport { ownerDocument } from '@base-ui/utils/owner';\nimport { Timeout } from '@base-ui/utils/useTimeout';\nimport {\n  ToastManagerAddOptions,\n  ToastManagerPromiseOptions,\n  ToastManagerUpdateOptions,\n  ToastObject,\n} from './useToastManager';\nimport { resolvePromiseOptions } from './utils/resolvePromiseOptions';\nimport { activeElement, contains, getTarget } from '../floating-ui-react/utils';\nimport { isFocusVisible } from './utils/focusVisible';\n\ntype ToastInternalUpdateOptions<Data extends object> = Partial<Omit<ToastObject<Data>, 'id'>>;\n\nexport type State = {\n  toasts: ToastObject<any>[];\n  hovering: boolean;\n  focused: boolean;\n  timeout: number;\n  limit: number;\n  isWindowFocused: boolean;\n  viewport: HTMLElement | null;\n  prevFocusElement: HTMLElement | null;\n};\n\nconst toastMapSelector = createSelectorMemoized(\n  (state: State) => state.toasts,\n  (toasts) => {\n    const map = new Map<\n      string,\n      { value: ToastObject<any>; domIndex: number; visibleIndex: number; offsetY: number }\n    >();\n    let visibleIndex = 0;\n    let offsetY = 0;\n    toasts.forEach((toast, toastIndex) => {\n      const isEnding = toast.transitionStatus === 'ending';\n      map.set(toast.id, {\n        value: toast,\n        domIndex: toastIndex,\n        visibleIndex: isEnding ? -1 : visibleIndex,\n        offsetY,\n      });\n\n      offsetY += toast.height || 0;\n\n      if (!isEnding) {\n        visibleIndex += 1;\n      }\n    });\n    return map;\n  },\n);\n\nexport const selectors = {\n  toasts: createSelector((state: State) => state.toasts),\n  isEmpty: createSelector((state: State) => state.toasts.length === 0),\n  toast: createSelector(toastMapSelector, (toastMap, id: string) => toastMap.get(id)?.value),\n  toastIndex: createSelector(\n    toastMapSelector,\n    (toastMap, id: string) => toastMap.get(id)?.domIndex ?? -1,\n  ),\n  toastOffsetY: createSelector(\n    toastMapSelector,\n    (toastMap, id: string) => toastMap.get(id)?.offsetY ?? 0,\n  ),\n  toastVisibleIndex: createSelector(\n    toastMapSelector,\n    (toastMap, id: string) => toastMap.get(id)?.visibleIndex ?? -1,\n  ),\n  hovering: createSelector((state: State) => state.hovering),\n  focused: createSelector((state: State) => state.focused),\n  expanded: createSelector((state: State) => state.hovering || state.focused),\n  expandedOrOutOfFocus: createSelector(\n    (state: State) => state.hovering || state.focused || !state.isWindowFocused,\n  ),\n  prevFocusElement: createSelector((state: State) => state.prevFocusElement),\n};\n\nexport class ToastStore extends ReactStore<State, {}, typeof selectors> {\n  private timers = new Map<string, TimerInfo>();\n\n  private areTimersPaused = false;\n\n  constructor(initialState: State) {\n    super(initialState, {}, selectors);\n  }\n\n  setFocused(focused: boolean) {\n    this.set('focused', focused);\n  }\n\n  setHovering(hovering: boolean) {\n    this.set('hovering', hovering);\n  }\n\n  setIsWindowFocused(isWindowFocused: boolean) {\n    this.set('isWindowFocused', isWindowFocused);\n  }\n\n  setPrevFocusElement(prevFocusElement: HTMLElement | null) {\n    this.set('prevFocusElement', prevFocusElement);\n  }\n\n  setViewport = (viewport: HTMLElement | null) => {\n    this.set('viewport', viewport);\n  };\n\n  disposeEffect = () => {\n    return () => {\n      this.timers.forEach((timer) => {\n        timer.timeout?.clear();\n      });\n      this.timers.clear();\n    };\n  };\n\n  removeToast(toastId: string) {\n    const index = selectors.toastIndex(this.state, toastId);\n    if (index === -1) {\n      return;\n    }\n\n    const toast = this.state.toasts[index];\n    toast?.onRemove?.();\n\n    const newToasts = [...this.state.toasts];\n    newToasts.splice(index, 1);\n    this.setToasts(newToasts);\n  }\n\n  addToast = <Data extends object>(toast: ToastManagerAddOptions<Data>): string => {\n    const { toasts, timeout, limit } = this.state;\n    const id = toast.id || generateId('toast');\n    const toastToAdd: ToastObject<Data> = {\n      ...toast,\n      id,\n      transitionStatus: 'starting',\n    };\n\n    const updatedToasts = [toastToAdd, ...toasts];\n    const activeToasts = updatedToasts.filter((t) => t.transitionStatus !== 'ending');\n\n    // Mark oldest toasts for removal when over limit\n    if (activeToasts.length > limit) {\n      const excessCount = activeToasts.length - limit;\n      const oldestActiveToasts = activeToasts.slice(-excessCount);\n      const limitedIds = new Set(oldestActiveToasts.map((t) => t.id));\n\n      this.setToasts(\n        updatedToasts.map((t) => {\n          const limited = limitedIds.has(t.id);\n          if (t.limited !== limited) {\n            return { ...t, limited };\n          }\n          return t;\n        }),\n      );\n    } else {\n      this.setToasts(updatedToasts.map((t) => (t.limited ? { ...t, limited: false } : t)));\n    }\n\n    const duration = toastToAdd.timeout ?? timeout;\n    if (toastToAdd.type !== 'loading' && duration > 0) {\n      this.scheduleTimer(id, duration, () => this.closeToast(id));\n    }\n\n    if (selectors.expandedOrOutOfFocus(this.state)) {\n      this.pauseTimers();\n    }\n\n    return id;\n  };\n\n  updateToast = <Data extends object>(id: string, updates: ToastManagerUpdateOptions<Data>) => {\n    this.updateToastInternal(id, updates);\n  };\n\n  updateToastInternal = <Data extends object>(\n    id: string,\n    updates: ToastInternalUpdateOptions<Data>,\n  ) => {\n    const { timeout, toasts } = this.state;\n    const prevToast = selectors.toast(this.state, id) ?? null;\n    if (!prevToast) {\n      return;\n    }\n\n    // Ignore updates for toasts that are already closing.\n    // This prevents races where async updates (e.g. promise success/error)\n    // can block a dismissal from completing.\n    if (prevToast.transitionStatus === 'ending') {\n      return;\n    }\n\n    const nextToast = { ...prevToast, ...updates };\n\n    this.setToasts(toasts.map((toast) => (toast.id === id ? { ...toast, ...updates } : toast)));\n\n    const nextTimeout = nextToast.timeout ?? timeout;\n    const prevTimeout = prevToast?.timeout ?? timeout;\n\n    const timeoutUpdated = Object.hasOwn(updates, 'timeout');\n\n    const shouldHaveTimer =\n      nextToast.transitionStatus !== 'ending' && nextToast.type !== 'loading' && nextTimeout > 0;\n\n    const hasTimer = this.timers.has(id);\n    const timeoutChanged = prevTimeout !== nextTimeout;\n    const wasLoading = prevToast?.type === 'loading';\n\n    if (!shouldHaveTimer && hasTimer) {\n      const timer = this.timers.get(id);\n      timer?.timeout?.clear();\n      this.timers.delete(id);\n      return;\n    }\n\n    // Schedule or reschedule timer if needed\n    if (shouldHaveTimer && (!hasTimer || timeoutChanged || timeoutUpdated || wasLoading)) {\n      const timer = this.timers.get(id);\n      if (timer) {\n        timer.timeout?.clear();\n        this.timers.delete(id);\n      }\n\n      this.scheduleTimer(id, nextTimeout, () => this.closeToast(id));\n\n      if (selectors.expandedOrOutOfFocus(this.state)) {\n        this.pauseTimers();\n      }\n    }\n  };\n\n  closeToast = (toastId?: string) => {\n    const closeAll = toastId === undefined;\n    const { limit, toasts } = this.state;\n    let toastsToClose: ToastObject<any>[];\n\n    if (closeAll) {\n      toastsToClose = toasts;\n      this.timers.forEach((timer) => {\n        timer.timeout?.clear();\n      });\n      this.timers.clear();\n    } else {\n      const toast = selectors.toast(this.state, toastId);\n      if (!toast) {\n        return;\n      }\n      toastsToClose = [toast];\n      const timer = this.timers.get(toastId);\n      if (timer?.timeout) {\n        timer.timeout.clear();\n        this.timers.delete(toastId);\n      }\n    }\n\n    let activeIndex = 0;\n    const newToasts = toasts.map((item) => {\n      if (closeAll || item.id === toastId) {\n        return { ...item, transitionStatus: 'ending' as const, height: 0 };\n      }\n      if (item.transitionStatus === 'ending') {\n        return item;\n      }\n      const isLimited = activeIndex >= limit;\n      activeIndex += 1;\n      return item.limited !== isLimited ? { ...item, limited: isLimited } : item;\n    });\n\n    const updates: Partial<State> = { toasts: newToasts };\n    if (closeAll || toasts.length === 1) {\n      updates.hovering = false;\n      updates.focused = false;\n    }\n    this.update(updates);\n\n    toastsToClose.forEach((toast) => {\n      if (toast.transitionStatus !== 'ending') {\n        toast.onClose?.();\n      }\n    });\n\n    this.handleFocusManagement(toastId);\n  };\n\n  promiseToast = <Value, Data extends object>(\n    promiseValue: Promise<Value>,\n    options: ToastManagerPromiseOptions<Value, Data>,\n  ): Promise<Value> => {\n    // Create a loading toast (which does not auto-dismiss).\n    const loadingOptions = resolvePromiseOptions(options.loading);\n    const id = this.addToast({\n      ...loadingOptions,\n      type: 'loading',\n    });\n\n    const handledPromise = promiseValue\n      .then((result: Value) => {\n        const successOptions = resolvePromiseOptions(options.success, result);\n        this.updateToast(id, {\n          ...successOptions,\n          type: 'success',\n          timeout: successOptions.timeout,\n        });\n\n        return result;\n      })\n      .catch((error) => {\n        const errorOptions = resolvePromiseOptions(options.error, error);\n        this.updateToast(id, {\n          ...errorOptions,\n          type: 'error',\n          timeout: errorOptions.timeout,\n        });\n\n        return Promise.reject(error);\n      });\n\n    // Private API used exclusively by `Manager` to handoff the promise\n    // back to the manager after it's handled here.\n    if ({}.hasOwnProperty.call(options, 'setPromise')) {\n      (options as any).setPromise(handledPromise);\n    }\n\n    return handledPromise;\n  };\n\n  pauseTimers() {\n    if (this.areTimersPaused) {\n      return;\n    }\n    this.areTimersPaused = true;\n    this.timers.forEach((timer) => {\n      if (timer.timeout) {\n        timer.timeout.clear();\n        const elapsed = Date.now() - timer.start;\n        const remaining = timer.delay - elapsed;\n        timer.remaining = remaining > 0 ? remaining : 0;\n      }\n    });\n  }\n\n  resumeTimers() {\n    if (!this.areTimersPaused) {\n      return;\n    }\n    this.areTimersPaused = false;\n    this.timers.forEach((timer, id) => {\n      timer.remaining = timer.remaining > 0 ? timer.remaining : timer.delay;\n      timer.timeout ??= Timeout.create();\n      timer.timeout.start(timer.remaining, () => {\n        this.timers.delete(id);\n        timer.callback();\n      });\n      timer.start = Date.now();\n    });\n  }\n\n  restoreFocusToPrevElement() {\n    this.state.prevFocusElement?.focus({ preventScroll: true });\n  }\n\n  handleDocumentPointerDown = (event: PointerEvent) => {\n    if (event.pointerType !== 'touch') {\n      return;\n    }\n\n    const target = getTarget(event) as Element | null;\n    if (contains(this.state.viewport, target)) {\n      return;\n    }\n\n    this.resumeTimers();\n    this.update({ hovering: false, focused: false });\n  };\n\n  private scheduleTimer(id: string, delay: number, callback: () => void) {\n    const start = Date.now();\n    const shouldStartActive = !selectors.expandedOrOutOfFocus(this.state);\n    const currentTimeout = shouldStartActive ? Timeout.create() : undefined;\n\n    currentTimeout?.start(delay, () => {\n      this.timers.delete(id);\n      callback();\n    });\n\n    this.timers.set(id, {\n      timeout: currentTimeout,\n      start: shouldStartActive ? start : 0,\n      delay,\n      remaining: delay,\n      callback,\n    });\n  }\n\n  private setToasts(newToasts: ToastObject<any>[]) {\n    const updates: Partial<State> = { toasts: newToasts };\n    if (newToasts.length === 0) {\n      updates.hovering = false;\n      updates.focused = false;\n    }\n    this.update(updates);\n  }\n\n  private handleFocusManagement(toastId: string | undefined) {\n    const activeEl = activeElement(ownerDocument(this.state.viewport));\n    if (\n      !this.state.viewport ||\n      !contains(this.state.viewport, activeEl) ||\n      !isFocusVisible(activeEl)\n    ) {\n      return;\n    }\n\n    if (toastId === undefined) {\n      this.restoreFocusToPrevElement();\n      return;\n    }\n\n    const toasts = selectors.toasts(this.state);\n    const currentIndex = selectors.toastIndex(this.state, toastId);\n    let nextToast: ToastObject<any> | null = null;\n\n    // Try to find the next toast that isn't animating out\n    let index = currentIndex + 1;\n    while (index < toasts.length) {\n      if (toasts[index].transitionStatus !== 'ending') {\n        nextToast = toasts[index];\n        break;\n      }\n      index += 1;\n    }\n\n    // Go backwards if no next toast is found\n    if (!nextToast) {\n      index = currentIndex - 1;\n      while (index >= 0) {\n        if (toasts[index].transitionStatus !== 'ending') {\n          nextToast = toasts[index];\n          break;\n        }\n        index -= 1;\n      }\n    }\n\n    if (nextToast) {\n      nextToast.ref?.current?.focus();\n    } else {\n      this.restoreFocusToPrevElement();\n    }\n  }\n}\n\ninterface TimerInfo {\n  timeout?: Timeout | undefined;\n  start: number;\n  delay: number;\n  remaining: number;\n  callback: () => void;\n}\n"
  },
  {
    "path": "packages/react/src/toast/title/ToastTitle.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { Toast } from '@base-ui/react/toast';\nimport { createRenderer, describeConformance } from '#test-utils';\nimport { screen } from '@mui/internal-test-utils';\nimport { List, Button } from '../utils/test-utils';\n\nconst toast = {\n  id: 'test',\n  title: 'Toast title',\n};\n\ndescribe('<Toast.Title />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Toast.Title>title</Toast.Title>, () => ({\n    refInstanceof: window.HTMLHeadingElement,\n    render(node) {\n      return render(\n        <Toast.Provider>\n          <Toast.Viewport>\n            <Toast.Root toast={toast}>{node}</Toast.Root>\n          </Toast.Viewport>\n        </Toast.Provider>,\n      );\n    },\n  }));\n\n  it('adds aria-labelledby to the root element', async () => {\n    const { user } = await render(\n      <Toast.Provider>\n        <Toast.Viewport>\n          <List />\n        </Toast.Viewport>\n        <Button />\n      </Toast.Provider>,\n    );\n\n    const button = screen.getByRole('button', { name: 'add' });\n    await user.click(button);\n\n    const titleElement = screen.getByTestId('title');\n    const titleId = titleElement.id;\n\n    const rootElement = screen.getByTestId('root');\n    expect(rootElement).not.toBe(null);\n    expect(rootElement.getAttribute('aria-labelledby')).toBe(titleId);\n  });\n\n  it('does not render if it has no children', async () => {\n    function AddButton() {\n      const { add } = Toast.useToastManager();\n      return (\n        <button type=\"button\" onClick={() => add({ title: undefined })}>\n          add\n        </button>\n      );\n    }\n\n    const { user } = await render(\n      <Toast.Provider>\n        <Toast.Viewport>\n          <List />\n        </Toast.Viewport>\n        <AddButton />\n      </Toast.Provider>,\n    );\n\n    const button = screen.getByRole('button', { name: 'add' });\n    await user.click(button);\n\n    const titleElement = screen.queryByTestId('title');\n    expect(titleElement).toBe(null);\n  });\n\n  it('renders the title by default', async () => {\n    const { user } = await render(\n      <Toast.Provider>\n        <Toast.Viewport>\n          <List />\n        </Toast.Viewport>\n        <Button />\n      </Toast.Provider>,\n    );\n\n    const button = screen.getByRole('button', { name: 'add' });\n    await user.click(button);\n\n    const titleElement = screen.getByTestId('title');\n    expect(titleElement).not.toBe(null);\n    expect(titleElement.textContent).toBe('title');\n  });\n});\n"
  },
  {
    "path": "packages/react/src/toast/title/ToastTitle.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useId } from '@base-ui/utils/useId';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { useToastRootContext } from '../root/ToastRootContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\n\n/**\n * A title that labels the toast.\n * Renders an `<h2>` element.\n *\n * Documentation: [Base UI Toast](https://base-ui.com/react/components/toast)\n */\nexport const ToastTitle = React.forwardRef(function ToastTitle(\n  componentProps: ToastTitle.Props,\n  forwardedRef: React.ForwardedRef<HTMLHeadingElement>,\n) {\n  const { render, className, id: idProp, children: childrenProp, ...elementProps } = componentProps;\n\n  const { toast, setTitleId } = useToastRootContext();\n\n  const children = childrenProp ?? toast.title;\n\n  const shouldRender = Boolean(children);\n\n  const id = useId(idProp);\n\n  useIsoLayoutEffect(() => {\n    if (!shouldRender) {\n      return undefined;\n    }\n\n    setTitleId(id);\n\n    return () => {\n      setTitleId(undefined);\n    };\n  }, [shouldRender, id, setTitleId]);\n\n  const state: ToastTitleState = {\n    type: toast.type,\n  };\n\n  const element = useRenderElement('h2', componentProps, {\n    ref: forwardedRef,\n    state,\n    props: {\n      ...elementProps,\n      id,\n      children,\n    },\n  });\n\n  if (!shouldRender) {\n    return null;\n  }\n\n  return element;\n});\n\nexport interface ToastTitleState {\n  /**\n   * The type of the toast.\n   */\n  type: string | undefined;\n}\n\nexport interface ToastTitleProps extends BaseUIComponentProps<'h2', ToastTitleState> {}\n\nexport namespace ToastTitle {\n  export type State = ToastTitleState;\n  export type Props = ToastTitleProps;\n}\n"
  },
  {
    "path": "packages/react/src/toast/title/ToastTitleDataAttributes.ts",
    "content": "export enum ToastTitleDataAttributes {\n  /**\n   * The type of the toast.\n   * @type {string}\n   */\n  type = 'data-type',\n}\n"
  },
  {
    "path": "packages/react/src/toast/useToastManager.spec.tsx",
    "content": "/* eslint-disable react-hooks/rules-of-hooks */\nimport { expectType } from '#test-utils';\nimport { useToastManager } from './useToastManager';\n\ntype ToastPayload = {\n  id: string;\n  count: number;\n};\n\nconst typedManager = useToastManager<ToastPayload>();\n\nconst typedToastData = typedManager.toasts[0]?.data;\nexpectType<ToastPayload | undefined, typeof typedToastData>(typedToastData);\n\nconst typedAddId = typedManager.add({\n  title: 'typed',\n  data: {\n    id: 'typed',\n    count: 1,\n  },\n});\nexpectType<string, typeof typedAddId>(typedAddId);\n\ntypedManager.add({\n  title: 'wrong-shape',\n  data: {\n    id: 'test',\n    // @ts-expect-error - message is not a valid property\n    message: 'not a number',\n  },\n});\n\ntypedManager.add({\n  title: 'wrong-shape',\n  // @ts-expect-error - count is a missing property\n  data: {\n    id: 'test',\n  },\n});\n\ntypedManager.update('typed', {\n  data: {\n    id: 'typed-update',\n    count: 2,\n  },\n});\n\ntypedManager.promise(Promise.resolve(2), {\n  loading: 'loading',\n  success: (value) => ({\n    title: `${value}`,\n    data: {\n      id: 'typed-success',\n      count: value,\n    },\n  }),\n  error: 'error',\n});\n\nconst legacyManager = useToastManager();\n\nconst legacyAddId = legacyManager.add<ToastPayload>({\n  title: 'legacy',\n  data: {\n    id: 'legacy',\n    count: 3,\n  },\n});\nexpectType<string, typeof legacyAddId>(legacyAddId);\n\nlegacyManager.update<ToastPayload>('legacy', {\n  data: {\n    id: 'legacy-update',\n    count: 4,\n  },\n});\n\nlegacyManager.promise<number, ToastPayload>(Promise.resolve(5), {\n  loading: 'loading',\n  success: (value) => ({\n    title: `${value}`,\n    data: {\n      id: 'legacy-success',\n      count: value,\n    },\n  }),\n  error: 'error',\n});\n"
  },
  {
    "path": "packages/react/src/toast/useToastManager.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { Toast } from '@base-ui/react/toast';\nimport { Dialog } from '@base-ui/react/dialog';\nimport { fireEvent, flushMicrotasks, screen } from '@mui/internal-test-utils';\nimport { createRenderer, isJSDOM } from '#test-utils';\nimport { useToastManager } from './useToastManager';\nimport { List } from './utils/test-utils';\n\nasync function tick(clock: ReturnType<typeof createRenderer>['clock'], ms: number) {\n  clock.tick(ms);\n  await flushMicrotasks();\n}\n\ndescribe.skipIf(!isJSDOM)('useToast', () => {\n  describe('add', () => {\n    const { clock, render } = createRenderer();\n\n    clock.withFakeTimers();\n\n    it('adds a toast to the viewport that auto-dismisses after 5s by default', async () => {\n      function AddButton() {\n        const { add } = useToastManager();\n        return (\n          <button\n            onClick={() => {\n              add({\n                title: 'test',\n              });\n            }}\n          >\n            add\n          </button>\n        );\n      }\n\n      await render(\n        <Toast.Provider>\n          <Toast.Viewport>\n            <List />\n          </Toast.Viewport>\n          <AddButton />\n        </Toast.Provider>,\n      );\n\n      const button = screen.getByRole('button', { name: 'add' });\n      fireEvent.click(button);\n\n      expect(screen.queryByTestId('root')).not.toBe(null);\n\n      await tick(clock, 5000);\n\n      expect(screen.queryByTestId('root')).toBe(null);\n    });\n\n    describe('option: timeout', () => {\n      it('dismisses the toast after the specified timeout', async () => {\n        function AddButton() {\n          const { add } = useToastManager();\n          return <button onClick={() => add({ title: 'test', timeout: 1000 })}>add</button>;\n        }\n\n        await render(\n          <Toast.Provider>\n            <Toast.Viewport>\n              <List />\n            </Toast.Viewport>\n            <AddButton />\n          </Toast.Provider>,\n        );\n\n        const button = screen.getByRole('button', { name: 'add' });\n        fireEvent.click(button);\n\n        expect(screen.queryByTestId('root')).not.toBe(null);\n\n        await tick(clock, 1000);\n\n        expect(screen.queryByTestId('root')).toBe(null);\n      });\n    });\n\n    describe('option: title', () => {\n      it('renders the title', async () => {\n        function AddButton() {\n          const { add } = useToastManager();\n          return (\n            <button\n              onClick={() =>\n                add({\n                  title: 'title',\n                  description: 'description',\n                })\n              }\n            >\n              add\n            </button>\n          );\n        }\n\n        function CustomList() {\n          const { toasts } = useToastManager();\n          return toasts.map((t) => (\n            <Toast.Root key={t.id} toast={t} data-testid=\"root\">\n              <Toast.Title data-testid=\"title\">{t.title}</Toast.Title>\n            </Toast.Root>\n          ));\n        }\n\n        await render(\n          <Toast.Provider>\n            <Toast.Viewport>\n              <CustomList />\n            </Toast.Viewport>\n            <AddButton />\n          </Toast.Provider>,\n        );\n\n        const button = screen.getByRole('button', { name: 'add' });\n        fireEvent.click(button);\n\n        expect(screen.queryByTestId('title')).toHaveTextContent('title');\n      });\n    });\n\n    describe('option: description', () => {\n      it('renders the description', async () => {\n        function AddButton() {\n          const { add } = useToastManager();\n          return (\n            <button\n              onClick={() =>\n                add({\n                  title: 'title',\n                  description: 'description',\n                })\n              }\n            >\n              add\n            </button>\n          );\n        }\n\n        function CustomList() {\n          const { toasts } = useToastManager();\n          return toasts.map((t) => (\n            <Toast.Root key={t.id} toast={t} data-testid=\"root\">\n              <Toast.Description data-testid=\"description\">{t.description}</Toast.Description>\n            </Toast.Root>\n          ));\n        }\n\n        await render(\n          <Toast.Provider>\n            <Toast.Viewport>\n              <CustomList />\n            </Toast.Viewport>\n            <AddButton />\n          </Toast.Provider>,\n        );\n\n        const button = screen.getByRole('button', { name: 'add' });\n        fireEvent.click(button);\n\n        expect(screen.queryByTestId('description')).toHaveTextContent('description');\n      });\n    });\n\n    describe('option: type', () => {\n      it('renders the type', async () => {\n        function AddButton() {\n          const { add } = useToastManager();\n          return <button onClick={() => add({ title: 'test', type: 'success' })}>add</button>;\n        }\n\n        function CustomList() {\n          const { toasts } = useToastManager();\n          return toasts.map((t) => (\n            <Toast.Root key={t.id} toast={t} data-testid=\"root\">\n              <Toast.Title data-testid=\"title\">{t.title}</Toast.Title>\n              <span>{t.type}</span>\n            </Toast.Root>\n          ));\n        }\n\n        await render(\n          <Toast.Provider>\n            <Toast.Viewport>\n              <CustomList />\n            </Toast.Viewport>\n            <AddButton />\n          </Toast.Provider>,\n        );\n\n        const button = screen.getByRole('button', { name: 'add' });\n        fireEvent.click(button);\n\n        expect(screen.queryByTestId('title')).toHaveTextContent('test');\n        expect(screen.queryByText('success')).not.toBe(null);\n      });\n    });\n\n    describe('option: onClose', () => {\n      it('calls onClose when the toast is closed', async () => {\n        const onCloseSpy = vi.fn();\n\n        function AddButton() {\n          const { add, close } = useToastManager();\n          const idRef = React.useRef<string | null>(null);\n          return (\n            <React.Fragment>\n              <button\n                onClick={() => {\n                  idRef.current = add({\n                    title: 'test',\n                    onClose: onCloseSpy,\n                  });\n                }}\n              >\n                add\n              </button>\n              <button\n                onClick={() => {\n                  if (idRef.current) {\n                    close(idRef.current);\n                  }\n                }}\n              >\n                close\n              </button>\n            </React.Fragment>\n          );\n        }\n\n        await render(\n          <Toast.Provider>\n            <Toast.Viewport>\n              <List />\n            </Toast.Viewport>\n            <AddButton />\n          </Toast.Provider>,\n        );\n\n        const addButton = screen.getByRole('button', { name: 'add' });\n        fireEvent.click(addButton);\n\n        expect(onCloseSpy.mock.calls.length).toBe(0);\n\n        const closeButton = screen.getByRole('button', { name: 'close' });\n        fireEvent.click(closeButton);\n\n        expect(onCloseSpy.mock.calls.length).toBe(1);\n      });\n\n      it('calls onClose when the toast auto-dismisses', async () => {\n        const onCloseSpy = vi.fn();\n\n        function AddButton() {\n          const { add } = useToastManager();\n          return (\n            <button\n              onClick={() => {\n                add({\n                  title: 'test',\n                  timeout: 1000,\n                  onClose: onCloseSpy,\n                });\n              }}\n            >\n              add\n            </button>\n          );\n        }\n\n        await render(\n          <Toast.Provider>\n            <Toast.Viewport>\n              <List />\n            </Toast.Viewport>\n            <AddButton />\n          </Toast.Provider>,\n        );\n\n        const button = screen.getByRole('button', { name: 'add' });\n        fireEvent.click(button);\n\n        expect(onCloseSpy.mock.calls.length).toBe(0);\n\n        await tick(clock, 1000);\n\n        expect(onCloseSpy.mock.calls.length).toBe(1);\n      });\n    });\n\n    describe('option: onRemove', () => {\n      it('calls onRemove when the toast is removed', async () => {\n        const onRemoveSpy = vi.fn();\n\n        function AddButton() {\n          const { add, close } = useToastManager();\n          const idRef = React.useRef<string | null>(null);\n          return (\n            <React.Fragment>\n              <button\n                onClick={() => {\n                  idRef.current = add({\n                    title: 'test',\n                    onRemove: onRemoveSpy,\n                  });\n                }}\n              >\n                add\n              </button>\n              <button\n                onClick={() => {\n                  if (idRef.current) {\n                    close(idRef.current);\n                  }\n                }}\n              >\n                close\n              </button>\n            </React.Fragment>\n          );\n        }\n\n        await render(\n          <Toast.Provider>\n            <Toast.Viewport>\n              <List />\n            </Toast.Viewport>\n            <AddButton />\n          </Toast.Provider>,\n        );\n\n        const addButton = screen.getByRole('button', { name: 'add' });\n        fireEvent.click(addButton);\n\n        expect(onRemoveSpy.mock.calls.length).toBe(0);\n\n        const closeButton = screen.getByRole('button', { name: 'close' });\n        fireEvent.click(closeButton);\n\n        expect(onRemoveSpy.mock.calls.length).toBe(1);\n      });\n    });\n\n    describe('option: priority', () => {\n      it('applies correct ARIA attributes for high priority toasts', async () => {\n        function AddButton() {\n          const { add } = useToastManager();\n          return (\n            <button onClick={() => add({ title: 'high priority', priority: 'high' })}>\n              add high\n            </button>\n          );\n        }\n\n        await render(\n          <Toast.Provider>\n            <Toast.Viewport>\n              <List />\n            </Toast.Viewport>\n            <AddButton />\n          </Toast.Provider>,\n        );\n\n        const highPriorityButton = screen.getByRole('button', { name: 'add high' });\n        fireEvent.click(highPriorityButton);\n\n        const highRoot = screen.getByTestId('root');\n\n        expect(highRoot.getAttribute('role')).toBe('alertdialog');\n        expect(highRoot.getAttribute('aria-modal')).toBe('false');\n        expect(screen.getByRole('alert')).not.toBe(null);\n        expect(screen.getByRole('alert').getAttribute('aria-atomic')).toBe('true');\n\n        const closeHighButton = screen.getByLabelText('close-press');\n        fireEvent.click(closeHighButton);\n\n        expect(screen.queryByRole('alert')).toBe(null);\n      });\n    });\n  });\n\n  describe('promise', () => {\n    const { clock, render } = createRenderer();\n\n    clock.withFakeTimers();\n\n    function CustomList() {\n      const { toasts } = useToastManager();\n      return toasts.map((t) => (\n        <Toast.Root key={t.id} toast={t} data-testid=\"root\">\n          <Toast.Title data-testid=\"title\">{t.title}</Toast.Title>\n          <Toast.Description data-testid=\"description\">{t.description}</Toast.Description>\n          <Toast.Close aria-label=\"close-press\" />\n          <span>{t.type}</span>\n        </Toast.Root>\n      ));\n    }\n\n    it('displays success state as description after promise resolves', async () => {\n      function AddButton() {\n        const { promise } = useToastManager();\n        return (\n          <button\n            onClick={() => {\n              promise(\n                new Promise((res) => {\n                  setTimeout(() => {\n                    res('success');\n                  }, 1000);\n                }),\n                {\n                  loading: 'loading',\n                  success: 'success',\n                  error: 'error',\n                },\n              );\n            }}\n          >\n            add\n          </button>\n        );\n      }\n\n      await render(\n        <Toast.Provider>\n          <Toast.Viewport>\n            <CustomList />\n          </Toast.Viewport>\n          <AddButton />\n        </Toast.Provider>,\n      );\n\n      const button = screen.getByRole('button', { name: 'add' });\n      fireEvent.click(button);\n\n      expect(screen.getByTestId('description')).toHaveTextContent('loading');\n\n      await tick(clock, 1000);\n\n      expect(screen.getByTestId('description')).toHaveTextContent('success');\n    });\n\n    it('displays error state as description after promise rejects', async () => {\n      function AddButton() {\n        const { promise } = useToastManager();\n        return (\n          <button\n            onClick={() => {\n              promise(\n                new Promise((res, rej) => {\n                  setTimeout(() => {\n                    rej(new Error('error'));\n                  }, 1000);\n                }),\n                {\n                  loading: 'loading',\n                  success: 'success',\n                  error: 'error',\n                },\n              ).catch(() => {\n                // Explicitly catch rejection to prevent test failure\n              });\n            }}\n          >\n            add\n          </button>\n        );\n      }\n\n      await render(\n        <Toast.Provider>\n          <Toast.Viewport>\n            <CustomList />\n          </Toast.Viewport>\n          <AddButton />\n        </Toast.Provider>,\n      );\n\n      const button = screen.getByRole('button', { name: 'add' });\n      fireEvent.click(button);\n\n      expect(screen.getByTestId('description')).toHaveTextContent('loading');\n\n      await tick(clock, 1000);\n\n      expect(screen.getByTestId('description')).toHaveTextContent('error');\n    });\n\n    it('passes data when success is a function', async () => {\n      function AddButton() {\n        const { promise } = useToastManager();\n        return (\n          <button\n            onClick={() =>\n              promise(\n                new Promise((res) => {\n                  res('test success');\n                }),\n                {\n                  loading: 'loading',\n                  success: (data) => `${data}`,\n                  error: 'error',\n                },\n              )\n            }\n          >\n            add\n          </button>\n        );\n      }\n\n      await render(\n        <Toast.Provider>\n          <Toast.Viewport>\n            <CustomList />\n          </Toast.Viewport>\n          <AddButton />\n        </Toast.Provider>,\n      );\n\n      const button = screen.getByRole('button', { name: 'add' });\n      fireEvent.click(button);\n\n      expect(screen.getByTestId('description')).toHaveTextContent('loading');\n\n      await tick(clock, 1000);\n\n      expect(screen.getByTestId('description')).toHaveTextContent('test success');\n    });\n\n    it('passes data when error is a function', async () => {\n      function AddButton() {\n        const { promise } = useToastManager();\n        return (\n          <button\n            onClick={() =>\n              promise(\n                new Promise((res, rej) => {\n                  rej(new Error('test error'));\n                }),\n                {\n                  loading: 'loading',\n                  success: 'success',\n                  error: (error: Error) => `${error.message}`,\n                },\n              ).catch(() => {\n                // Explicitly catch rejection to prevent test failure\n              })\n            }\n          >\n            add\n          </button>\n        );\n      }\n\n      await render(\n        <Toast.Provider>\n          <Toast.Viewport>\n            <CustomList />\n          </Toast.Viewport>\n          <AddButton />\n        </Toast.Provider>,\n      );\n\n      const button = screen.getByRole('button', { name: 'add' });\n      fireEvent.click(button);\n\n      expect(screen.getByTestId('description')).toHaveTextContent('loading');\n\n      await tick(clock, 1000);\n\n      expect(screen.getByTestId('description')).toHaveTextContent('test error');\n    });\n\n    it('supports custom options', async () => {\n      function AddButton() {\n        const { promise } = useToastManager();\n        return (\n          <button\n            onClick={() =>\n              promise(\n                new Promise((res) => {\n                  res('success');\n                }),\n                {\n                  loading: {\n                    title: 'loading title',\n                    description: 'loading description',\n                  },\n                  success: 'success',\n                  error: 'error',\n                },\n              )\n            }\n          >\n            add\n          </button>\n        );\n      }\n\n      await render(\n        <Toast.Provider>\n          <Toast.Viewport>\n            <CustomList />\n          </Toast.Viewport>\n          <AddButton />\n        </Toast.Provider>,\n      );\n\n      const button = screen.getByRole('button', { name: 'add' });\n      fireEvent.click(button);\n\n      expect(screen.getByTestId('title')).toHaveTextContent('loading title');\n      expect(screen.getByTestId('description')).toHaveTextContent('loading description');\n\n      await flushMicrotasks();\n    });\n\n    it('does not reopen a dismissed promise toast when it resolves', async () => {\n      let resolvePromise: (value: string) => void = () => {\n        throw new Error('Promise resolver should be assigned before resolving.');\n      };\n\n      function AddButton() {\n        const { promise } = useToastManager();\n        return (\n          <button\n            onClick={() => {\n              const pendingPromise = new Promise<string>((resolve) => {\n                resolvePromise = resolve;\n              });\n\n              promise(pendingPromise, {\n                loading: 'loading',\n                success: 'success',\n                error: 'error',\n              });\n            }}\n          >\n            add\n          </button>\n        );\n      }\n\n      await render(\n        <Toast.Provider>\n          <Toast.Viewport>\n            <CustomList />\n          </Toast.Viewport>\n          <AddButton />\n        </Toast.Provider>,\n      );\n\n      fireEvent.click(screen.getByRole('button', { name: 'add' }));\n\n      expect(screen.getByTestId('description')).toHaveTextContent('loading');\n\n      fireEvent.click(screen.getByLabelText('close-press'));\n      resolvePromise('success');\n\n      await flushMicrotasks();\n\n      expect(screen.queryByTestId('root')).toBe(null);\n    });\n\n    describe('timeout handling', () => {\n      it('auto-dismisses success toast after default timeout when promise resolves', async () => {\n        function AddButton() {\n          const { promise } = useToastManager();\n          return (\n            <button\n              onClick={() => {\n                promise(\n                  new Promise((res) => {\n                    setTimeout(() => {\n                      res('success');\n                    }, 1000);\n                  }),\n                  {\n                    loading: 'loading',\n                    success: 'success',\n                    error: 'error',\n                  },\n                );\n              }}\n            >\n              add\n            </button>\n          );\n        }\n\n        await render(\n          <Toast.Provider>\n            <Toast.Viewport>\n              <CustomList />\n            </Toast.Viewport>\n            <AddButton />\n          </Toast.Provider>,\n        );\n\n        const button = screen.getByRole('button', { name: 'add' });\n        fireEvent.click(button);\n\n        expect(screen.getByTestId('description')).toHaveTextContent('loading');\n\n        await tick(clock, 1000);\n\n        expect(screen.getByTestId('description')).toHaveTextContent('success');\n\n        await tick(clock, 5000);\n\n        expect(screen.queryByTestId('root')).toBe(null);\n      });\n\n      it('auto-dismisses error toast after default timeout when promise rejects', async () => {\n        function AddButton() {\n          const { promise } = useToastManager();\n          return (\n            <button\n              onClick={() => {\n                promise(\n                  new Promise((res, rej) => {\n                    setTimeout(() => {\n                      rej(new Error('error'));\n                    }, 1000);\n                  }),\n                  {\n                    loading: 'loading',\n                    success: 'success',\n                    error: 'error',\n                  },\n                ).catch(() => {\n                  // Explicitly catch rejection to prevent test failure\n                });\n              }}\n            >\n              add\n            </button>\n          );\n        }\n\n        await render(\n          <Toast.Provider>\n            <Toast.Viewport>\n              <CustomList />\n            </Toast.Viewport>\n            <AddButton />\n          </Toast.Provider>,\n        );\n\n        const button = screen.getByRole('button', { name: 'add' });\n        fireEvent.click(button);\n\n        expect(screen.getByTestId('description')).toHaveTextContent('loading');\n\n        await tick(clock, 1000);\n\n        expect(screen.getByTestId('description')).toHaveTextContent('error');\n\n        await tick(clock, 5000);\n        expect(screen.queryByTestId('root')).toBe(null);\n      });\n\n      it('uses custom timeout from success options when promise resolves', async () => {\n        function AddButton() {\n          const { promise } = useToastManager();\n          return (\n            <button\n              onClick={() => {\n                promise(\n                  new Promise((res) => {\n                    setTimeout(() => {\n                      res('success');\n                    }, 1000);\n                  }),\n                  {\n                    loading: 'loading',\n                    success: {\n                      description: 'success',\n                      timeout: 2000,\n                    },\n                    error: 'error',\n                  },\n                );\n              }}\n            >\n              add\n            </button>\n          );\n        }\n\n        await render(\n          <Toast.Provider>\n            <Toast.Viewport>\n              <CustomList />\n            </Toast.Viewport>\n            <AddButton />\n          </Toast.Provider>,\n        );\n\n        const button = screen.getByRole('button', { name: 'add' });\n        fireEvent.click(button);\n\n        await tick(clock, 1000);\n\n        expect(screen.getByTestId('description')).toHaveTextContent('success');\n\n        await tick(clock, 1000);\n        expect(screen.getByTestId('root')).not.toBe(null);\n\n        await tick(clock, 1000);\n        expect(screen.queryByTestId('root')).toBe(null);\n      });\n\n      it('uses custom timeout from error options when promise rejects', async () => {\n        function AddButton() {\n          const { promise } = useToastManager();\n          return (\n            <button\n              onClick={() => {\n                promise(\n                  new Promise((res, rej) => {\n                    setTimeout(() => {\n                      rej(new Error('error'));\n                    }, 1000);\n                  }),\n                  {\n                    loading: 'loading',\n                    success: 'success',\n                    error: {\n                      description: 'error',\n                      timeout: 3000,\n                    },\n                  },\n                ).catch(() => {\n                  // Explicitly catch rejection to prevent test failure\n                });\n              }}\n            >\n              add\n            </button>\n          );\n        }\n\n        await render(\n          <Toast.Provider>\n            <Toast.Viewport>\n              <CustomList />\n            </Toast.Viewport>\n            <AddButton />\n          </Toast.Provider>,\n        );\n\n        const button = screen.getByRole('button', { name: 'add' });\n        fireEvent.click(button);\n\n        await tick(clock, 1000);\n\n        expect(screen.getByTestId('description')).toHaveTextContent('error');\n\n        await tick(clock, 2000);\n        expect(screen.getByTestId('root')).not.toBe(null);\n\n        await tick(clock, 1000);\n        expect(screen.queryByTestId('root')).toBe(null);\n      });\n\n      it('uses provider timeout when no custom timeout is specified', async () => {\n        function AddButton() {\n          const { promise } = useToastManager();\n          return (\n            <button\n              onClick={() => {\n                promise(\n                  new Promise((res) => {\n                    setTimeout(() => {\n                      res('success');\n                    }, 1000);\n                  }),\n                  {\n                    loading: 'loading',\n                    success: 'success',\n                    error: 'error',\n                  },\n                );\n              }}\n            >\n              add\n            </button>\n          );\n        }\n\n        await render(\n          <Toast.Provider timeout={1000}>\n            <Toast.Viewport>\n              <CustomList />\n            </Toast.Viewport>\n            <AddButton />\n          </Toast.Provider>,\n        );\n\n        const button = screen.getByRole('button', { name: 'add' });\n        fireEvent.click(button);\n\n        await tick(clock, 1000);\n\n        expect(screen.getByTestId('description')).toHaveTextContent('success');\n\n        await tick(clock, 1000);\n        expect(screen.queryByTestId('root')).toBe(null);\n      });\n\n      it('does not inherit a loading timeout when success does not specify one', async () => {\n        function AddButton() {\n          const { promise } = useToastManager();\n          return (\n            <button\n              onClick={() => {\n                promise(\n                  new Promise((res) => {\n                    setTimeout(() => {\n                      res('success');\n                    }, 1000);\n                  }),\n                  {\n                    loading: {\n                      description: 'loading',\n                      timeout: 0,\n                    },\n                    success: 'success',\n                    error: 'error',\n                  },\n                );\n              }}\n            >\n              add\n            </button>\n          );\n        }\n\n        await render(\n          <Toast.Provider>\n            <Toast.Viewport>\n              <CustomList />\n            </Toast.Viewport>\n            <AddButton />\n          </Toast.Provider>,\n        );\n\n        fireEvent.click(screen.getByRole('button', { name: 'add' }));\n        expect(screen.getByTestId('description')).toHaveTextContent('loading');\n\n        await tick(clock, 1000);\n        expect(screen.getByTestId('description')).toHaveTextContent('success');\n\n        await tick(clock, 5000);\n        expect(screen.queryByTestId('root')).toBe(null);\n      });\n\n      it('does not auto-dismiss when timeout is set to 0', async () => {\n        function AddButton() {\n          const { promise } = useToastManager();\n          return (\n            <button\n              onClick={() => {\n                promise(\n                  new Promise((res) => {\n                    setTimeout(() => {\n                      res('success');\n                    }, 1000);\n                  }),\n                  {\n                    loading: 'loading',\n                    success: {\n                      description: 'success',\n                      timeout: 0,\n                    },\n                    error: 'error',\n                  },\n                );\n              }}\n            >\n              add\n            </button>\n          );\n        }\n\n        await render(\n          <Toast.Provider>\n            <Toast.Viewport>\n              <CustomList />\n            </Toast.Viewport>\n            <AddButton />\n          </Toast.Provider>,\n        );\n\n        const button = screen.getByRole('button', { name: 'add' });\n        fireEvent.click(button);\n\n        await tick(clock, 1000);\n\n        expect(screen.getByTestId('description')).toHaveTextContent('success');\n\n        await tick(clock, 10000);\n        expect(screen.getByTestId('root')).not.toBe(null);\n      });\n\n      it('pauses timers when hovering over toast', async () => {\n        function AddButton() {\n          const { promise } = useToastManager();\n          return (\n            <button\n              onClick={() => {\n                promise(\n                  new Promise((res) => {\n                    setTimeout(() => {\n                      res('success');\n                    }, 1000);\n                  }),\n                  {\n                    loading: 'loading',\n                    success: {\n                      description: 'success',\n                      timeout: 3000,\n                    },\n                    error: 'error',\n                  },\n                );\n              }}\n            >\n              add\n            </button>\n          );\n        }\n\n        await render(\n          <Toast.Provider>\n            <Toast.Viewport>\n              <CustomList />\n            </Toast.Viewport>\n            <AddButton />\n          </Toast.Provider>,\n        );\n\n        const button = screen.getByRole('button', { name: 'add' });\n        fireEvent.click(button);\n\n        await tick(clock, 1000);\n\n        expect(screen.getByTestId('description')).toHaveTextContent('success');\n\n        await tick(clock, 1000);\n\n        const toast = screen.getByTestId('root');\n        fireEvent.mouseEnter(toast);\n\n        await tick(clock, 5000);\n        expect(screen.getByTestId('root')).not.toBe(null);\n\n        fireEvent.mouseLeave(toast);\n        await tick(clock, 2000);\n        expect(screen.queryByTestId('root')).toBe(null);\n      });\n    });\n  });\n\n  describe('update', () => {\n    const { clock, render } = createRenderer();\n\n    clock.withFakeTimers();\n\n    function CustomList() {\n      const { toasts } = useToastManager();\n      return toasts.map((t) => (\n        <Toast.Root key={t.id} toast={t} data-testid=\"root\">\n          <Toast.Title data-testid=\"title\">{t.title}</Toast.Title>\n        </Toast.Root>\n      ));\n    }\n\n    it('updates the toast', async () => {\n      function AddButton() {\n        const { add, update } = useToastManager();\n        const idRef = React.useRef<string | null>(null);\n        return (\n          <React.Fragment>\n            <button\n              type=\"button\"\n              onClick={() => {\n                idRef.current = add({ title: 'test' });\n              }}\n            >\n              add\n            </button>\n            <button\n              type=\"button\"\n              onClick={() => {\n                if (idRef.current) {\n                  update(idRef.current, { title: 'updated' });\n                }\n              }}\n            >\n              update\n            </button>\n          </React.Fragment>\n        );\n      }\n\n      await render(\n        <Toast.Provider>\n          <Toast.Viewport>\n            <CustomList />\n          </Toast.Viewport>\n          <AddButton />\n        </Toast.Provider>,\n      );\n\n      const button = screen.getByRole('button', { name: 'add' });\n      fireEvent.click(button);\n\n      expect(screen.getByTestId('title')).toHaveTextContent('test');\n\n      const updateButton = screen.getByRole('button', { name: 'update' });\n      fireEvent.click(updateButton);\n\n      expect(screen.getByTestId('title')).toHaveTextContent('updated');\n    });\n\n    it('auto-dismisses when timeout changes from 0 to a positive value', async () => {\n      function AddButton() {\n        const { add, update } = useToastManager();\n        const idRef = React.useRef<string | null>(null);\n        return (\n          <React.Fragment>\n            <button\n              type=\"button\"\n              onClick={() => {\n                idRef.current = add({ title: 'test', timeout: 0 });\n              }}\n            >\n              add\n            </button>\n            <button\n              type=\"button\"\n              onClick={() => {\n                if (idRef.current) {\n                  update(idRef.current, { timeout: 1000 });\n                }\n              }}\n            >\n              update\n            </button>\n          </React.Fragment>\n        );\n      }\n\n      await render(\n        <Toast.Provider>\n          <Toast.Viewport>\n            <CustomList />\n          </Toast.Viewport>\n          <AddButton />\n        </Toast.Provider>,\n      );\n\n      fireEvent.click(screen.getByRole('button', { name: 'add' }));\n      expect(screen.queryByTestId('root')).not.toBe(null);\n\n      fireEvent.click(screen.getByRole('button', { name: 'update' }));\n      await tick(clock, 1000);\n\n      expect(screen.queryByTestId('root')).toBe(null);\n    });\n\n    it('schedules a timer when updating a loading toast to a non-loading type', async () => {\n      function AddButton() {\n        const { add, update } = useToastManager();\n        const idRef = React.useRef<string | null>(null);\n        return (\n          <React.Fragment>\n            <button\n              type=\"button\"\n              onClick={() => {\n                idRef.current = add({ title: 'loading', type: 'loading' });\n              }}\n            >\n              add\n            </button>\n            <button\n              type=\"button\"\n              onClick={() => {\n                if (idRef.current) {\n                  update(idRef.current, { title: 'success', type: 'success', timeout: 1000 });\n                }\n              }}\n            >\n              update\n            </button>\n          </React.Fragment>\n        );\n      }\n\n      await render(\n        <Toast.Provider>\n          <Toast.Viewport>\n            <CustomList />\n          </Toast.Viewport>\n          <AddButton />\n        </Toast.Provider>,\n      );\n\n      fireEvent.click(screen.getByRole('button', { name: 'add' }));\n      expect(screen.getByTestId('title')).toHaveTextContent('loading');\n\n      fireEvent.click(screen.getByRole('button', { name: 'update' }));\n      expect(screen.getByTestId('title')).toHaveTextContent('success');\n\n      await tick(clock, 1000);\n      expect(screen.queryByTestId('root')).toBe(null);\n    });\n  });\n\n  describe('close', () => {\n    const { clock, render } = createRenderer();\n\n    clock.withFakeTimers();\n\n    function CustomList() {\n      const { toasts } = useToastManager();\n      return toasts.map((t) => (\n        <Toast.Root key={t.id} toast={t} data-testid=\"root\">\n          <Toast.Title data-testid=\"title\">{t.title}</Toast.Title>\n        </Toast.Root>\n      ));\n    }\n\n    it('closes a toast', async () => {\n      function AddButton() {\n        const { add, close } = useToastManager();\n        const idRef = React.useRef<string | null>(null);\n        return (\n          <React.Fragment>\n            <button\n              onClick={() => {\n                idRef.current = add({ title: 'test' });\n              }}\n            >\n              add\n            </button>\n            <button\n              onClick={() => {\n                if (idRef.current) {\n                  close(idRef.current);\n                }\n              }}\n            >\n              close\n            </button>\n          </React.Fragment>\n        );\n      }\n\n      await render(\n        <Toast.Provider>\n          <Toast.Viewport>\n            <CustomList />\n          </Toast.Viewport>\n          <AddButton />\n        </Toast.Provider>,\n      );\n\n      const addButton = screen.getByRole('button', { name: 'add' });\n      fireEvent.click(addButton);\n\n      expect(screen.getByTestId('root')).not.toBe(null);\n\n      const closeButton = screen.getByRole('button', { name: 'close' });\n      fireEvent.click(closeButton);\n\n      expect(screen.queryByTestId('root')).toBe(null);\n    });\n\n    it('closes all toasts', async () => {\n      function AddButton() {\n        const { add, close } = useToastManager();\n        return (\n          <React.Fragment>\n            <button\n              onClick={() => {\n                add({ title: 'test' });\n              }}\n            >\n              add\n            </button>\n            <button\n              onClick={() => {\n                close();\n              }}\n            >\n              close\n            </button>\n          </React.Fragment>\n        );\n      }\n\n      await render(\n        <Toast.Provider>\n          <Toast.Viewport>\n            <CustomList />\n          </Toast.Viewport>\n          <AddButton />\n        </Toast.Provider>,\n      );\n\n      const addButton = screen.getByRole('button', { name: 'add' });\n      Array.from({ length: 5 }).forEach(() => {\n        fireEvent.click(addButton);\n      });\n\n      expect(screen.getAllByTestId('root')).toHaveLength(5);\n\n      const closeButton = screen.getByRole('button', { name: 'close' });\n      fireEvent.click(closeButton);\n\n      expect(screen.queryByTestId('root')).toBe(null);\n    });\n  });\n\n  describe('prop: limit', () => {\n    const { clock, render } = createRenderer();\n\n    clock.withFakeTimers();\n\n    function TestList() {\n      const [count, setCount] = React.useState(0);\n      const { toasts, add } = useToastManager();\n      return (\n        <React.Fragment>\n          {toasts.map((t) => (\n            <Toast.Root key={t.id} toast={t} data-testid={t.title}>\n              <Toast.Close data-testid={`close-${t.title}`} />\n            </Toast.Root>\n          ))}\n          <button\n            onClick={() => {\n              const nextCount = count + 1;\n              setCount(nextCount);\n              add({ title: `toast-${nextCount}` });\n            }}\n          >\n            add\n          </button>\n        </React.Fragment>\n      );\n    }\n\n    it('marks toasts as limited when the limit is exceeded', async () => {\n      await render(\n        <Toast.Provider limit={2}>\n          <Toast.Viewport>\n            <TestList />\n          </Toast.Viewport>\n        </Toast.Provider>,\n      );\n\n      const addButton = screen.getByRole('button', { name: 'add' });\n\n      fireEvent.click(addButton);\n      const toast1 = screen.getByTestId('toast-1');\n      expect(toast1).not.toHaveAttribute('data-limited');\n\n      fireEvent.click(addButton);\n      const toast2 = screen.getByTestId('toast-2');\n      expect(toast2).not.toHaveAttribute('data-limited');\n\n      fireEvent.click(addButton);\n      const toast3 = screen.getByTestId('toast-3');\n      expect(toast3).not.toHaveAttribute('data-limited');\n      expect(toast1).toHaveAttribute('data-limited');\n    });\n\n    it('unmarks toasts as limited when the limit is not exceeded', async () => {\n      await render(\n        <Toast.Provider limit={2}>\n          <Toast.Viewport>\n            <TestList />\n          </Toast.Viewport>\n        </Toast.Provider>,\n      );\n\n      const addButton = screen.getByRole('button', { name: 'add' });\n\n      fireEvent.click(addButton);\n      const toast1 = screen.getByTestId('toast-1');\n      expect(toast1).not.toHaveAttribute('data-limited');\n\n      fireEvent.click(addButton);\n      const toast2 = screen.getByTestId('toast-2');\n      expect(toast2).not.toHaveAttribute('data-limited');\n\n      fireEvent.click(addButton);\n      const toast3 = screen.getByTestId('toast-3');\n      expect(toast3).not.toHaveAttribute('data-limited');\n\n      const closeToast3 = screen.getByTestId('close-toast-3');\n      fireEvent.click(closeToast3);\n\n      expect(toast1).not.toHaveAttribute('data-limited');\n    });\n  });\n\n  describe('in dialog', () => {\n    const { clock, render } = createRenderer();\n\n    clock.withFakeTimers();\n\n    function DialogToastExample() {\n      const { add } = useToastManager();\n      const [isOpen, setIsOpen] = React.useState(false);\n\n      return (\n        <React.Fragment>\n          <button onClick={() => setIsOpen(true)}>open dialog</button>\n          <Dialog.Root open={isOpen} onOpenChange={setIsOpen}>\n            <Dialog.Portal>\n              <Dialog.Backdrop />\n              <Dialog.Popup>\n                <button\n                  onClick={() =>\n                    add({\n                      title: 'Toast in dialog',\n                      description: 'This toast is in a dialog',\n                    })\n                  }\n                >\n                  add\n                </button>\n                <Dialog.Close />\n              </Dialog.Popup>\n            </Dialog.Portal>\n          </Dialog.Root>\n        </React.Fragment>\n      );\n    }\n\n    function ToastInDialogList() {\n      const { toasts } = useToastManager();\n      return toasts.map((toast) => (\n        <Toast.Root key={toast.id} toast={toast} data-testid=\"toast-root\">\n          <Toast.Title data-testid=\"toast-title\">{toast.title}</Toast.Title>\n          <Toast.Description data-testid=\"toast-description\">{toast.description}</Toast.Description>\n          <Toast.Close data-testid=\"toast-close\" aria-label=\"close\" />\n        </Toast.Root>\n      ));\n    }\n\n    it('toasts in dialogs are accessible and not aria-hidden', async () => {\n      await render(\n        <Toast.Provider>\n          <Toast.Viewport>\n            <ToastInDialogList />\n          </Toast.Viewport>\n          <DialogToastExample />\n        </Toast.Provider>,\n      );\n\n      const openDialogButton = screen.getByRole('button', { name: 'open dialog' });\n      fireEvent.click(openDialogButton);\n\n      expect(screen.getByRole('dialog')).not.toBe(null);\n\n      const addToastButton = screen.getByRole('button', { name: 'add' });\n      fireEvent.click(addToastButton);\n\n      const toastRoot = screen.getByTestId('toast-root');\n      expect(toastRoot).not.toBe(null);\n      expect(screen.getByTestId('toast-title')).toHaveTextContent('Toast in dialog');\n      expect(screen.getByTestId('toast-description')).toHaveTextContent(\n        'This toast is in a dialog',\n      );\n    });\n\n    it('high priority toasts in dialogs have correct accessibility structure', async () => {\n      function HighPriorityToastInDialog() {\n        const { add } = useToastManager();\n        return (\n          <Dialog.Root open>\n            <Dialog.Portal>\n              <Dialog.Backdrop />\n              <Dialog.Popup>\n                <button\n                  onClick={() => {\n                    add({\n                      title: 'High priority toast',\n                      description: 'This is urgent',\n                      priority: 'high',\n                    });\n                  }}\n                >\n                  add\n                </button>\n              </Dialog.Popup>\n            </Dialog.Portal>\n          </Dialog.Root>\n        );\n      }\n\n      await render(\n        <Toast.Provider>\n          <Toast.Viewport>\n            <ToastInDialogList />\n          </Toast.Viewport>\n          <HighPriorityToastInDialog />\n        </Toast.Provider>,\n      );\n\n      const addToastButton = screen.getByRole('button', { name: 'add' });\n      fireEvent.click(addToastButton);\n\n      const toastRoot = screen.getByTestId('toast-root');\n      expect(toastRoot).toHaveAttribute('aria-hidden', 'true');\n      expect(screen.queryByRole('alert')).not.toBe(null);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/toast/useToastManager.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { ToastContext } from './provider/ToastProviderContext';\nimport type { ToastPositionerProps } from './positioner/ToastPositioner';\n\n/**\n * Returns the array of toasts and methods to manage them.\n */\nexport function useToastManager<Data extends object = any>(): UseToastManagerReturnValue<Data> {\n  const store = React.useContext(ToastContext);\n\n  if (!store) {\n    throw new Error('Base UI: useToastManager must be used within <Toast.Provider>.');\n  }\n\n  const toasts = store.useState('toasts');\n\n  return React.useMemo(\n    () => ({\n      toasts,\n      add: store.addToast,\n      close: store.closeToast,\n      update: store.updateToast,\n      promise: store.promiseToast,\n    }),\n    [toasts, store],\n  );\n}\n\nexport interface ToastObject<Data extends object> {\n  /**\n   * The unique identifier for the toast.\n   */\n  id: string;\n  /**\n   * The ref for the toast.\n   */\n  ref?: React.RefObject<HTMLElement | null> | undefined;\n  /**\n   * The title of the toast.\n   */\n  title?: React.ReactNode;\n  /**\n   * The type of the toast. Used to conditionally style the toast,\n   * including conditionally rendering elements based on the type.\n   */\n  type?: string | undefined;\n  /**\n   * The description of the toast.\n   */\n  description?: React.ReactNode;\n  /**\n   * The amount of time (in ms) before the toast is auto dismissed.\n   * A value of `0` will prevent the toast from being dismissed automatically.\n   * @default 5000\n   */\n  timeout?: number | undefined;\n  /**\n   * The priority of the toast.\n   * - `low` - The toast will be announced politely.\n   * - `high` - The toast will be announced urgently.\n   * @default 'low'\n   */\n  priority?: 'low' | 'high' | undefined;\n  /**\n   * The transition status of the toast.\n   */\n  transitionStatus?: 'starting' | 'ending' | undefined;\n  /**\n   * Determines if the toast was closed due to the limit being reached.\n   */\n  limited?: boolean | undefined;\n  /**\n   * The height of the toast.\n   */\n  height?: number | undefined;\n  /**\n   * Callback function to be called when the toast is closed.\n   */\n  onClose?: (() => void) | undefined;\n  /**\n   * Callback function to be called when the toast is removed from the list after any animations are complete when closed.\n   */\n  onRemove?: (() => void) | undefined;\n  /**\n   * The props for the action button.\n   */\n  actionProps?: React.ComponentPropsWithoutRef<'button'> | undefined;\n  /**\n   * The props forwarded to the toast positioner element when rendering anchored toasts.\n   */\n  positionerProps?: ToastManagerPositionerProps | undefined;\n  /**\n   * Custom data for the toast.\n   */\n  data?: Data | undefined;\n}\n\nexport interface ToastManagerPositionerProps extends Omit<\n  ToastPositionerProps,\n  'anchor' | 'toast'\n> {\n  /**\n   * An element to position the toast against.\n   */\n  anchor?: Element | null | undefined;\n}\n\nexport interface UseToastManagerReturnValue<Data extends object = any> {\n  toasts: ToastObject<Data>[];\n  add: <T extends Data = Data>(options: ToastManagerAddOptions<T>) => string;\n  close: (toastId?: string) => void;\n  update: <T extends Data = Data>(toastId: string, options: ToastManagerUpdateOptions<T>) => void;\n  promise: <Value, T extends Data = Data>(\n    promise: Promise<Value>,\n    options: ToastManagerPromiseOptions<Value, T>,\n  ) => Promise<Value>;\n}\n\nexport interface ToastManagerAddOptions<Data extends object> extends Omit<\n  ToastObject<Data>,\n  'id' | 'animation' | 'height' | 'ref' | 'limited'\n> {\n  id?: string | undefined;\n}\n\nexport interface ToastManagerUpdateOptions<Data extends object> extends Partial<\n  Omit<ToastObject<Data>, 'id' | 'ref' | 'height' | 'transitionStatus' | 'limited'>\n> {}\n\nexport interface ToastManagerPromiseOptions<Value, Data extends object> {\n  loading: string | ToastManagerUpdateOptions<Data>;\n  success:\n    | string\n    | ToastManagerUpdateOptions<Data>\n    | ((result: Value) => string | ToastManagerUpdateOptions<Data>);\n  error:\n    | string\n    | ToastManagerUpdateOptions<Data>\n    | ((error: any) => string | ToastManagerUpdateOptions<Data>);\n}\n"
  },
  {
    "path": "packages/react/src/toast/utils/focusVisible.ts",
    "content": "export { matchesFocusVisible as isFocusVisible } from '../../floating-ui-react/utils';\n"
  },
  {
    "path": "packages/react/src/toast/utils/resolvePromiseOptions.ts",
    "content": "import type { ToastManagerUpdateOptions } from '../useToastManager';\n\nexport function resolvePromiseOptions<T, Data extends object>(\n  options:\n    | string\n    | ToastManagerUpdateOptions<Data>\n    | ((result: T) => string | ToastManagerUpdateOptions<Data>),\n  result?: T,\n): ToastManagerUpdateOptions<Data> {\n  if (typeof options === 'string') {\n    return {\n      description: options,\n    };\n  }\n\n  if (typeof options === 'function') {\n    const resolvedOptions = options(result as T);\n    return typeof resolvedOptions === 'string' ? { description: resolvedOptions } : resolvedOptions;\n  }\n\n  return options;\n}\n"
  },
  {
    "path": "packages/react/src/toast/utils/test-utils.tsx",
    "content": "import { Toast } from '@base-ui/react/toast';\n\n/**\n * @internal\n */\nexport function Button() {\n  const { add } = Toast.useToastManager();\n  return (\n    <button\n      type=\"button\"\n      onClick={() => {\n        add({\n          title: 'title',\n          description: 'description',\n          actionProps: {\n            id: 'action',\n            children: 'action',\n          },\n        });\n      }}\n    >\n      add\n    </button>\n  );\n}\n\n/**\n * @internal\n */\nexport function List() {\n  return Toast.useToastManager().toasts.map((toastItem) => (\n    <Toast.Root key={toastItem.id} toast={toastItem} data-testid=\"root\">\n      <Toast.Title data-testid=\"title\" />\n      <Toast.Description data-testid=\"description\" />\n      <Toast.Close aria-label=\"close-press\" />\n      <Toast.Action data-testid=\"action\" />\n    </Toast.Root>\n  ));\n}\n"
  },
  {
    "path": "packages/react/src/toast/viewport/ToastViewport.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { Toast } from '@base-ui/react/toast';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\nimport { act, fireEvent, screen } from '@mui/internal-test-utils';\nimport { List, Button } from '../utils/test-utils';\n\ndescribe('<Toast.Viewport />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Toast.Viewport />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(<Toast.Provider>{node}</Toast.Provider>);\n    },\n  }));\n\n  it('gets focused when F6 is pressed', async () => {\n    const { user } = await render(\n      <Toast.Provider>\n        <Toast.Viewport data-testid=\"viewport\">\n          <List />\n        </Toast.Viewport>\n        <Button />\n      </Toast.Provider>,\n    );\n\n    const button = screen.getByRole('button', { name: 'add' });\n\n    await user.click(button);\n    await user.keyboard('{F6}');\n\n    expect(screen.getByTestId('viewport')).toHaveFocus();\n  });\n\n  it('focuses first toast upon tab after viewport is focused', async () => {\n    const { user } = await render(\n      <Toast.Provider>\n        <Toast.Viewport>\n          <List />\n        </Toast.Viewport>\n        <Button />\n      </Toast.Provider>,\n    );\n\n    const button = screen.getByRole('button', { name: 'add' });\n\n    await user.click(button);\n    await user.keyboard('{F6}');\n    await user.keyboard('{Tab}');\n\n    expect(screen.getByTestId('root')).toHaveFocus();\n  });\n\n  it('returns focus to previous element when pressing shift+Tab on first toast', async () => {\n    const { user } = await render(\n      <Toast.Provider>\n        <Toast.Viewport>\n          <List />\n        </Toast.Viewport>\n        <Button />\n      </Toast.Provider>,\n    );\n\n    const button = screen.getByRole('button', { name: 'add' });\n\n    await user.click(button);\n    await user.keyboard('{F6}');\n    await user.tab();\n    await user.tab({ shift: true });\n\n    expect(button).toHaveFocus();\n  });\n\n  it('returns focus to previous element when pressing shift+Tab on last toast', async () => {\n    const { user } = await render(\n      <Toast.Provider>\n        <Toast.Viewport>\n          <List />\n        </Toast.Viewport>\n        <Button />\n      </Toast.Provider>,\n    );\n\n    const button = screen.getByRole('button', { name: 'add' });\n\n    await user.click(button);\n    await user.click(button);\n\n    await user.keyboard('{F6}');\n    await user.tab(); // first toast\n    await user.tab(); // first toast close button\n    await user.tab(); // first toast action button\n    await user.tab(); // last toast\n    await user.tab(); // last toast close button\n    await user.tab(); // last toast action button\n    await user.tab();\n\n    expect(button).toHaveFocus();\n  });\n\n  it('removes expanded on mouseleave when focus-visible not inside', async () => {\n    const { user } = await render(\n      <Toast.Provider>\n        <Toast.Viewport data-testid=\"viewport\">\n          <List />\n        </Toast.Viewport>\n        <Button />\n      </Toast.Provider>,\n    );\n\n    const button = screen.getByRole('button', { name: 'add' });\n\n    await user.click(button);\n    const root = await screen.findByTestId('root');\n    const viewport = screen.getByTestId('viewport');\n\n    fireEvent.mouseEnter(root);\n    expect(viewport).toHaveAttribute('data-expanded');\n\n    fireEvent.mouseLeave(root);\n    expect(viewport).not.toHaveAttribute('data-expanded');\n  });\n\n  it('keeps expanded on mouseleave when focus-visible is inside', async () => {\n    const { user } = await render(\n      <Toast.Provider>\n        <Toast.Viewport data-testid=\"viewport\">\n          <List />\n        </Toast.Viewport>\n        <Button />\n      </Toast.Provider>,\n    );\n\n    const button = screen.getByRole('button', { name: 'add' });\n    await user.click(button);\n    const root = await screen.findByTestId('root');\n    const viewport = screen.getByTestId('viewport');\n\n    await user.keyboard('{F6}');\n    await user.tab();\n\n    fireEvent.mouseEnter(root);\n    expect(viewport).toHaveAttribute('data-expanded');\n    fireEvent.mouseLeave(root);\n    expect(viewport).toHaveAttribute('data-expanded');\n  });\n\n  describe('timers', () => {\n    const { render: renderFakeTimers, clock } = createRenderer();\n\n    clock.withFakeTimers();\n\n    it('pauses timers when hovering', async () => {\n      await renderFakeTimers(\n        <Toast.Provider>\n          <Toast.Viewport>\n            <List />\n          </Toast.Viewport>\n          <Button />\n        </Toast.Provider>,\n      );\n\n      const button = screen.getByRole('button', { name: 'add' });\n\n      fireEvent.click(button);\n      fireEvent.mouseEnter(screen.getByTestId('root'));\n\n      clock.tick(5001);\n\n      expect(screen.queryByTestId('root')).not.toBe(null);\n    });\n\n    it('resumes timers when not hovering', async () => {\n      await renderFakeTimers(\n        <Toast.Provider>\n          <Toast.Viewport>\n            <List />\n          </Toast.Viewport>\n          <Button />\n        </Toast.Provider>,\n      );\n\n      const button = screen.getByRole('button', { name: 'add' });\n\n      fireEvent.click(button);\n      fireEvent.mouseEnter(screen.getByTestId('root'));\n\n      clock.tick(5000);\n\n      fireEvent.mouseLeave(screen.getByTestId('root'));\n\n      clock.tick(4999);\n\n      expect(screen.queryByTestId('root')).not.toBe(null);\n\n      clock.tick(2);\n\n      expect(screen.queryByTestId('root')).toBe(null);\n    });\n\n    it('pauses timers when the viewport is focused', async () => {\n      await renderFakeTimers(\n        <Toast.Provider>\n          <Toast.Viewport data-testid=\"viewport\">\n            <List />\n          </Toast.Viewport>\n          <Button />\n        </Toast.Provider>,\n      );\n\n      const button = screen.getByRole('button', { name: 'add' });\n\n      fireEvent.click(button);\n      fireEvent.keyDown(document.activeElement as HTMLElement, { key: 'F6' });\n\n      clock.tick(5001);\n\n      expect(screen.queryByTestId('root')).not.toBe(null);\n    });\n\n    it.skipIf(!isJSDOM)('resumes timers when the viewport is blurred', async () => {\n      await renderFakeTimers(\n        <Toast.Provider>\n          <Toast.Viewport data-testid=\"viewport\">\n            <List />\n          </Toast.Viewport>\n          <Button />\n        </Toast.Provider>,\n      );\n\n      const button = screen.getByRole('button', { name: 'add' });\n\n      fireEvent.click(button);\n      fireEvent.keyDown(document.activeElement as HTMLElement, { key: 'F6' });\n\n      clock.tick(5001);\n\n      await act(async () => button.focus());\n\n      clock.tick(5001);\n\n      expect(screen.queryByTestId('root')).toBe(null);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/toast/viewport/ToastViewport.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { ownerDocument, ownerWindow } from '@base-ui/utils/owner';\nimport { visuallyHidden } from '@base-ui/utils/visuallyHidden';\nimport { useTimeout } from '@base-ui/utils/useTimeout';\nimport { activeElement, contains, getTarget } from '../../floating-ui-react/utils';\nimport { FocusGuard } from '../../utils/FocusGuard';\nimport type { BaseUIComponentProps, HTMLProps } from '../../utils/types';\nimport { useToastProviderContext } from '../provider/ToastProviderContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { isFocusVisible } from '../utils/focusVisible';\nimport { ToastViewportCssVars } from './ToastViewportCssVars';\n\n/**\n * A container viewport for toasts.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Toast](https://base-ui.com/react/components/toast)\n */\nexport const ToastViewport = React.forwardRef(function ToastViewport(\n  componentProps: ToastViewport.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, children, ...elementProps } = componentProps;\n\n  const store = useToastProviderContext();\n  const windowFocusTimeout = useTimeout();\n\n  const handlingFocusGuardRef = React.useRef(false);\n  const markedReadyForMouseLeaveRef = React.useRef(false);\n\n  const isEmpty = store.useState('isEmpty');\n  const toasts = store.useState('toasts');\n  const focused = store.useState('focused');\n  const expanded = store.useState('expanded');\n  const prevFocusElement = store.useState('prevFocusElement');\n  const frontmostHeight = toasts[0]?.height ?? 0;\n\n  const hasTransitioningToasts = React.useMemo(\n    () => toasts.some((toast) => toast.transitionStatus === 'ending'),\n    [toasts],\n  );\n\n  // Listen globally for F6 so we can force-focus the viewport.\n  React.useEffect(() => {\n    const viewport = store.state.viewport;\n    if (!viewport) {\n      return undefined;\n    }\n\n    function handleGlobalKeyDown(event: KeyboardEvent) {\n      if (isEmpty) {\n        return;\n      }\n\n      if (event.key === 'F6' && event.target !== viewport) {\n        event.preventDefault();\n        store.setPrevFocusElement(activeElement(ownerDocument(viewport)) as HTMLElement | null);\n        viewport?.focus({ preventScroll: true });\n        store.pauseTimers();\n        store.setFocused(true);\n      }\n    }\n\n    const win = ownerWindow(viewport);\n\n    win.addEventListener('keydown', handleGlobalKeyDown);\n\n    return () => {\n      win.removeEventListener('keydown', handleGlobalKeyDown);\n    };\n  }, [store, isEmpty]);\n\n  React.useEffect(() => {\n    const viewport = store.state.viewport;\n    if (!viewport || isEmpty) {\n      return undefined;\n    }\n\n    const win = ownerWindow(viewport);\n\n    function handleWindowBlur(event: FocusEvent) {\n      if (event.target !== win) {\n        return;\n      }\n\n      store.setIsWindowFocused(false);\n      store.pauseTimers();\n    }\n\n    function handleWindowFocus(event: FocusEvent) {\n      if (event.relatedTarget || event.target === win) {\n        return;\n      }\n\n      const target = getTarget(event);\n      const activeEl = activeElement(ownerDocument(viewport));\n      if (!contains(viewport, target as HTMLElement | null) || !isFocusVisible(activeEl)) {\n        store.resumeTimers();\n      }\n\n      // Wait for the `handleFocus` event to fire.\n      windowFocusTimeout.start(0, () => store.setIsWindowFocused(true));\n    }\n\n    win.addEventListener('blur', handleWindowBlur, true);\n    win.addEventListener('focus', handleWindowFocus, true);\n\n    return () => {\n      win.removeEventListener('blur', handleWindowBlur, true);\n      win.removeEventListener('focus', handleWindowFocus, true);\n    };\n  }, [\n    store,\n    windowFocusTimeout,\n    // `store.state.viewport` isn't available on the first render,\n    // since the portal node hasn't yet been created.\n    // By adding this dependency, we ensure the window listeners\n    // are added when toasts have been created, once the ref is available.\n    isEmpty,\n  ]);\n\n  React.useEffect(() => {\n    const viewport = store.state.viewport;\n    if (!viewport || isEmpty) {\n      return undefined;\n    }\n\n    const doc = ownerDocument(viewport);\n    doc.addEventListener('pointerdown', store.handleDocumentPointerDown, true);\n\n    return () => {\n      doc.removeEventListener('pointerdown', store.handleDocumentPointerDown, true);\n    };\n  }, [isEmpty, store]);\n\n  function handleFocusGuard(event: React.FocusEvent) {\n    const viewport = store.state.viewport;\n    if (!viewport) {\n      return;\n    }\n\n    handlingFocusGuardRef.current = true;\n\n    // If we're coming off the container, move to the first toast\n    if (event.relatedTarget === viewport) {\n      toasts[0]?.ref?.current?.focus();\n    } else {\n      store.restoreFocusToPrevElement();\n    }\n  }\n\n  function handleKeyDown(event: React.KeyboardEvent) {\n    if (event.key === 'Tab' && event.shiftKey && event.target === store.state.viewport) {\n      event.preventDefault();\n      store.restoreFocusToPrevElement();\n      store.resumeTimers();\n    }\n  }\n\n  React.useEffect(() => {\n    if (\n      !store.state.isWindowFocused ||\n      hasTransitioningToasts ||\n      !markedReadyForMouseLeaveRef.current\n    ) {\n      return;\n    }\n\n    // Once transitions have finished, see if a mouseleave was already triggered\n    // but blocked from taking effect. If so, we can now safely resume timers and\n    // collapse the viewport.\n    store.resumeTimers();\n    store.setHovering(false);\n    markedReadyForMouseLeaveRef.current = false;\n  }, [hasTransitioningToasts, store]);\n\n  function handleMouseEnter() {\n    store.pauseTimers();\n    store.setHovering(true);\n    markedReadyForMouseLeaveRef.current = false;\n  }\n\n  function handleMouseLeave() {\n    if (hasTransitioningToasts) {\n      // When swiping to dismiss, wait until the transitions have settled\n      // to avoid the viewport collapsing while the user is interacting.\n      markedReadyForMouseLeaveRef.current = true;\n    } else {\n      store.resumeTimers();\n      store.setHovering(false);\n    }\n  }\n\n  function handleFocus() {\n    if (handlingFocusGuardRef.current) {\n      handlingFocusGuardRef.current = false;\n      return;\n    }\n\n    if (focused) {\n      return;\n    }\n\n    // Only set focused when the active element is focus-visible.\n    // This prevents the viewport from staying expanded when clicking inside without\n    // keyboard navigation.\n    if (isFocusVisible(ownerDocument(store.state.viewport).activeElement)) {\n      store.setFocused(true);\n      store.pauseTimers();\n    }\n  }\n\n  function handleBlur(event: React.FocusEvent) {\n    if (!focused || contains(store.state.viewport, event.relatedTarget as HTMLElement | null)) {\n      return;\n    }\n\n    store.setFocused(false);\n    store.resumeTimers();\n  }\n\n  const defaultProps: HTMLProps = {\n    tabIndex: -1,\n    role: 'region',\n    'aria-live': 'polite',\n    'aria-atomic': false,\n    'aria-relevant': 'additions text',\n    'aria-label': 'Notifications',\n    onMouseEnter: handleMouseEnter,\n    onMouseMove: handleMouseEnter,\n    onMouseLeave: handleMouseLeave,\n    onFocus: handleFocus,\n    onBlur: handleBlur,\n    onKeyDown: handleKeyDown,\n    onClick: handleFocus,\n  };\n\n  const state: ToastViewportState = {\n    expanded,\n  };\n\n  const element = useRenderElement('div', componentProps, {\n    ref: [forwardedRef, store.setViewport],\n    state,\n    props: [\n      defaultProps,\n      {\n        style: {\n          [ToastViewportCssVars.frontmostHeight as string]: frontmostHeight\n            ? `${frontmostHeight}px`\n            : undefined,\n        },\n      },\n      elementProps,\n      {\n        children: (\n          <React.Fragment>\n            {!isEmpty && prevFocusElement && <FocusGuard onFocus={handleFocusGuard} />}\n            {children}\n            {!isEmpty && prevFocusElement && <FocusGuard onFocus={handleFocusGuard} />}\n          </React.Fragment>\n        ),\n      },\n    ],\n  });\n\n  const highPriorityToasts = React.useMemo(\n    () => toasts.filter((toast) => toast.priority === 'high'),\n    [toasts],\n  );\n\n  return (\n    <React.Fragment>\n      {!isEmpty && prevFocusElement && <FocusGuard onFocus={handleFocusGuard} />}\n      {element}\n      {!focused && highPriorityToasts.length > 0 && (\n        <div style={visuallyHidden}>\n          {highPriorityToasts.map((toast) => (\n            <div key={toast.id} role=\"alert\" aria-atomic>\n              <div>{toast.title}</div>\n              <div>{toast.description}</div>\n            </div>\n          ))}\n        </div>\n      )}\n    </React.Fragment>\n  );\n});\n\nexport interface ToastViewportState {\n  /**\n   * Whether toasts are expanded in the viewport.\n   */\n  expanded: boolean;\n}\n\nexport interface ToastViewportProps extends BaseUIComponentProps<'div', ToastViewportState> {}\n\nexport namespace ToastViewport {\n  export type State = ToastViewportState;\n  export type Props = ToastViewportProps;\n}\n"
  },
  {
    "path": "packages/react/src/toast/viewport/ToastViewportCssVars.ts",
    "content": "export enum ToastViewportCssVars {\n  /**\n   * Indicates the height of the frontmost toast.\n   * @type {number}\n   */\n  frontmostHeight = '--toast-frontmost-height',\n}\n"
  },
  {
    "path": "packages/react/src/toast/viewport/ToastViewportDataAttributes.ts",
    "content": "export enum ToastViewportDataAttributes {\n  /**\n   * Indicates toasts are expanded in the viewport.\n   * @type {boolean}\n   */\n  expanded = 'data-expanded',\n}\n"
  },
  {
    "path": "packages/react/src/toggle/Toggle.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { act, screen } from '@mui/internal-test-utils';\nimport { Toggle } from '@base-ui/react/toggle';\nimport { createRenderer, describeConformance } from '#test-utils';\nimport { ToggleGroup } from '../toggle-group/ToggleGroup';\n\ndescribe('<Toggle />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Toggle />, () => ({\n    refInstanceof: window.HTMLButtonElement,\n    testComponentPropWith: 'button',\n    button: true,\n    render,\n  }));\n\n  describe('pressed state', () => {\n    it('controlled', async () => {\n      function App() {\n        const [pressed, setPressed] = React.useState(false);\n        return (\n          <div>\n            <input type=\"checkbox\" checked={pressed} onChange={() => setPressed(!pressed)} />\n            <Toggle pressed={pressed} />;\n          </div>\n        );\n      }\n\n      await render(<App />);\n      const checkbox = screen.getByRole('checkbox');\n      const button = screen.getByRole('button');\n\n      expect(button).toHaveAttribute('aria-pressed', 'false');\n      await act(async () => {\n        checkbox.click();\n      });\n\n      expect(button).toHaveAttribute('aria-pressed', 'true');\n\n      await act(async () => {\n        checkbox.click();\n      });\n\n      expect(button).toHaveAttribute('aria-pressed', 'false');\n    });\n\n    it('uncontrolled', async () => {\n      await render(<Toggle defaultPressed={false} />);\n\n      const button = screen.getByRole('button');\n\n      expect(button).toHaveAttribute('aria-pressed', 'false');\n      await act(async () => {\n        button.click();\n      });\n\n      expect(button).toHaveAttribute('aria-pressed', 'true');\n\n      await act(async () => {\n        button.click();\n      });\n\n      expect(button).toHaveAttribute('aria-pressed', 'false');\n    });\n  });\n\n  describe('prop: onPressedChange', () => {\n    it('is called when the pressed state changes', async () => {\n      const handlePressed = vi.fn();\n\n      await render(<Toggle defaultPressed={false} onPressedChange={handlePressed} />);\n\n      const button = screen.getByRole('button');\n\n      await act(async () => {\n        button.click();\n      });\n\n      expect(handlePressed.mock.calls.length).toBe(1);\n      expect(handlePressed.mock.calls[0][0]).toBe(true);\n    });\n  });\n\n  describe('prop: disabled', () => {\n    it('disables the component', async () => {\n      const handlePressed = vi.fn();\n      await render(<Toggle disabled onPressedChange={handlePressed} />);\n\n      const button = screen.getByRole('button');\n\n      expect(button).toHaveAttribute('disabled');\n      expect(button).toHaveAttribute('data-disabled');\n      expect(button).toHaveAttribute('aria-pressed', 'false');\n\n      await act(async () => {\n        button.click();\n      });\n\n      expect(handlePressed.mock.calls.length).toBe(0);\n      expect(button).toHaveAttribute('aria-pressed', 'false');\n    });\n  });\n\n  describe('prop: render', () => {\n    it('should pass composite props', async () => {\n      const renderSpy = vi.fn();\n\n      function ToggleRenderComponent({\n        renderProps,\n      }: {\n        renderProps: React.ComponentProps<'button'>;\n      }) {\n        renderSpy(renderProps);\n        return <button type=\"button\" {...renderProps} />;\n      }\n\n      await render(\n        <ToggleGroup defaultValue={['left']}>\n          <Toggle value=\"left\" render={(props) => <ToggleRenderComponent renderProps={props} />} />\n        </ToggleGroup>,\n      );\n\n      expect(renderSpy.mock.lastCall?.[0]).toHaveProperty('tabIndex', 0);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/toggle/Toggle.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useControlled } from '@base-ui/utils/useControlled';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { error } from '@base-ui/utils/error';\nimport { useBaseUiId } from '../utils/useBaseUiId';\nimport { useRenderElement } from '../utils/useRenderElement';\nimport type { BaseUIComponentProps, NativeButtonProps } from '../utils/types';\nimport { useToggleGroupContext } from '../toggle-group/ToggleGroupContext';\nimport { useButton } from '../use-button/useButton';\nimport { CompositeItem } from '../composite/item/CompositeItem';\nimport {\n  type BaseUIChangeEventDetails,\n  createChangeEventDetails,\n} from '../utils/createBaseUIEventDetails';\nimport { REASONS } from '../utils/reasons';\n\n/**\n * A two-state button that can be on or off.\n * Renders a `<button>` element.\n *\n * Documentation: [Base UI Toggle](https://base-ui.com/react/components/toggle)\n */\nexport const Toggle = React.forwardRef(function Toggle<Value extends string>(\n  componentProps: Toggle.Props<Value>,\n  forwardedRef: React.ForwardedRef<HTMLButtonElement>,\n) {\n  const {\n    className,\n    defaultPressed: defaultPressedProp = false,\n    disabled: disabledProp = false,\n    form, // never participates in form validation\n    onPressedChange: onPressedChangeProp,\n    pressed: pressedProp,\n    render,\n    type, // cannot change button type\n    value: valueProp,\n    nativeButton = true,\n    ...elementProps\n  } = componentProps;\n\n  // `|| undefined` handles cases, where value is falsy (i.e. \"\")\n  const value = useBaseUiId(valueProp || undefined);\n  const groupContext = useToggleGroupContext();\n  const groupValue = groupContext?.value ?? [];\n\n  const defaultPressed = groupContext ? undefined : defaultPressedProp;\n\n  const disabled = (disabledProp || groupContext?.disabled) ?? false;\n\n  if (process.env.NODE_ENV !== 'production') {\n    // eslint-disable-next-line react-hooks/rules-of-hooks\n    useIsoLayoutEffect(() => {\n      if (groupContext && valueProp === undefined && groupContext.isValueInitialized) {\n        error(\n          'A `<Toggle>` component rendered in a `<ToggleGroup>` has no explicit `value` prop.',\n          'This will cause issues between the Toggle Group and Toggle values.',\n          'Provide the `<Toggle>` with a `value` prop matching the `<ToggleGroup>` values prop type.',\n        );\n      }\n    }, [groupContext, valueProp, groupContext?.isValueInitialized]);\n  }\n\n  const [pressed, setPressedState] = useControlled({\n    controlled: groupContext ? value !== undefined && groupValue.indexOf(value) > -1 : pressedProp,\n    default: defaultPressed,\n    name: 'Toggle',\n    state: 'pressed',\n  });\n\n  const onPressedChange = useStableCallback(\n    (nextPressed: boolean, eventDetails: Toggle.ChangeEventDetails) => {\n      if (value) {\n        groupContext?.setGroupValue?.(value, nextPressed, eventDetails);\n      }\n      onPressedChangeProp?.(nextPressed, eventDetails);\n    },\n  );\n\n  const { getButtonProps, buttonRef } = useButton({\n    disabled,\n    native: nativeButton,\n  });\n\n  const state: ToggleState = {\n    disabled,\n    pressed,\n  };\n\n  const refs = [buttonRef, forwardedRef];\n  const props = [\n    {\n      'aria-pressed': pressed,\n      onClick(event: React.MouseEvent) {\n        const nextPressed = !pressed;\n        const details = createChangeEventDetails(REASONS.none, event.nativeEvent);\n\n        onPressedChange(nextPressed, details);\n\n        if (details.isCanceled) {\n          return;\n        }\n\n        setPressedState(nextPressed);\n      },\n    },\n    elementProps,\n    getButtonProps,\n  ];\n\n  const element = useRenderElement('button', componentProps, {\n    enabled: !groupContext,\n    state,\n    ref: refs,\n    props,\n  });\n\n  if (groupContext) {\n    return (\n      <CompositeItem\n        tag=\"button\"\n        render={render}\n        className={className}\n        state={state}\n        refs={refs}\n        props={props}\n      />\n    );\n  }\n\n  return element;\n}) as {\n  <Value extends string>(\n    props: Toggle.Props<Value> & React.RefAttributes<HTMLButtonElement>,\n  ): React.JSX.Element;\n};\n\nexport interface ToggleState {\n  /**\n   * Whether the toggle is currently pressed.\n   */\n  pressed: boolean;\n  /**\n   * Whether the toggle should ignore user interaction.\n   */\n  disabled: boolean;\n}\n\nexport interface ToggleProps<Value extends string>\n  extends NativeButtonProps, BaseUIComponentProps<'button', ToggleState> {\n  /**\n   * Whether the toggle button is currently pressed.\n   * This is the controlled counterpart of `defaultPressed`.\n   */\n  pressed?: boolean | undefined;\n  /**\n   * Whether the toggle button is currently pressed.\n   * This is the uncontrolled counterpart of `pressed`.\n   * @default false\n   */\n  defaultPressed?: boolean | undefined;\n  /**\n   * Whether the component should ignore user interaction.\n   * @default false\n   */\n  disabled?: boolean | undefined;\n  /**\n   * Callback fired when the pressed state is changed.\n   */\n  onPressedChange?:\n    | ((pressed: boolean, eventDetails: Toggle.ChangeEventDetails) => void)\n    | undefined;\n  /**\n   * A unique string that identifies the toggle when used\n   * inside a toggle group.\n   */\n  value?: Value | undefined;\n}\n\nexport type ToggleChangeEventReason = typeof REASONS.none;\n\nexport type ToggleChangeEventDetails = BaseUIChangeEventDetails<Toggle.ChangeEventReason>;\n\nexport namespace Toggle {\n  export type State = ToggleState;\n  export type Props<TValue extends string = string> = ToggleProps<TValue>;\n  export type ChangeEventReason = ToggleChangeEventReason;\n  export type ChangeEventDetails = ToggleChangeEventDetails;\n}\n"
  },
  {
    "path": "packages/react/src/toggle/ToggleDataAttributes.ts",
    "content": "export enum ToggleDataAttributes {\n  /**\n   * Present when the toggle button is pressed.\n   */\n  pressed = 'data-pressed',\n}\n"
  },
  {
    "path": "packages/react/src/toggle/index.ts",
    "content": "export { Toggle } from './Toggle';\n\nexport type * from './Toggle';\n"
  },
  {
    "path": "packages/react/src/toggle-group/ToggleGroup.spec.tsx",
    "content": "import { expectType } from '#test-utils';\nimport { ToggleGroup } from '@base-ui/react/toggle-group';\n\nconst toggleDefaultValueReadonly = (value: NonNullable<ToggleGroup.Props['defaultValue']>) =>\n  expectType<readonly string[], typeof value>(value);\n\nconst toggleValueReadonly = (value: NonNullable<ToggleGroup.Props['value']>) =>\n  expectType<readonly string[], typeof value>(value);\n\nconst values = ['a', 'b', 'c'];\n\n<ToggleGroup\n  value={values}\n  onValueChange={(value) => {\n    expectType<string[], typeof value>(value);\n  }}\n/>;\n\nconst narrowedValues = ['a', 'b', 'c'] as const;\ntype ReadonlyValue = (typeof narrowedValues)[number];\n\n<ToggleGroup\n  value={narrowedValues}\n  onValueChange={(value) => {\n    expectType<ReadonlyValue[], typeof value>(value);\n  }}\n/>;\n"
  },
  {
    "path": "packages/react/src/toggle-group/ToggleGroup.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport { act, screen } from '@mui/internal-test-utils';\nimport { DirectionProvider, type TextDirection } from '@base-ui/react/direction-provider';\nimport { ToggleGroup } from '@base-ui/react/toggle-group';\nimport { Toggle } from '@base-ui/react/toggle';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\n\ndescribe('<ToggleGroup />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<ToggleGroup />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render,\n  }));\n\n  it('renders a `group`', async () => {\n    await render(<ToggleGroup aria-label=\"My Toggle Group\" />);\n\n    expect(screen.queryByRole('group', { name: 'My Toggle Group' })).not.toBe(null);\n  });\n\n  describe('uncontrolled', () => {\n    it('pressed state', async ({ skip }) => {\n      if (isJSDOM) {\n        skip();\n      }\n\n      const { user } = await render(\n        <ToggleGroup>\n          <Toggle value=\"one\" />\n          <Toggle value=\"two\" />\n        </ToggleGroup>,\n      );\n\n      const [button1, button2] = screen.getAllByRole('button');\n\n      expect(button1).toHaveAttribute('aria-pressed', 'false');\n      expect(button2).toHaveAttribute('aria-pressed', 'false');\n\n      await user.pointer({ keys: '[MouseLeft]', target: button1 });\n\n      expect(button1).toHaveAttribute('aria-pressed', 'true');\n      expect(button1).toHaveAttribute('data-pressed');\n      expect(button2).toHaveAttribute('aria-pressed', 'false');\n\n      await user.pointer({ keys: '[MouseLeft]', target: button2 });\n\n      expect(button2).toHaveAttribute('aria-pressed', 'true');\n      expect(button2).toHaveAttribute('data-pressed');\n      expect(button1).toHaveAttribute('aria-pressed', 'false');\n    });\n\n    it('prop: defaultValue', async () => {\n      const { user } = await render(\n        <ToggleGroup defaultValue={['two']}>\n          <Toggle value=\"one\" />\n          <Toggle value=\"two\" />\n        </ToggleGroup>,\n      );\n\n      const [button1, button2] = screen.getAllByRole('button');\n\n      expect(button2).toHaveAttribute('aria-pressed', 'true');\n      expect(button2).toHaveAttribute('data-pressed');\n      expect(button1).toHaveAttribute('aria-pressed', 'false');\n\n      await user.pointer({ keys: '[MouseLeft]', target: button1 });\n\n      expect(button1).toHaveAttribute('aria-pressed', 'true');\n      expect(button1).toHaveAttribute('data-pressed');\n      expect(button2).toHaveAttribute('aria-pressed', 'false');\n    });\n\n    it('when Toggles omit value', async () => {\n      const { user } = await render(\n        <ToggleGroup>\n          <Toggle />\n          <Toggle value=\"\" />\n        </ToggleGroup>,\n      );\n\n      const [button1, button2] = screen.getAllByRole('button');\n\n      expect(button2).toHaveAttribute('aria-pressed', 'false');\n      expect(button1).toHaveAttribute('aria-pressed', 'false');\n\n      await user.click(button1);\n      expect(button1).toHaveAttribute('aria-pressed', 'true');\n      expect(button2).toHaveAttribute('aria-pressed', 'false');\n\n      await user.click(button2);\n      expect(button1).toHaveAttribute('aria-pressed', 'false');\n      expect(button2).toHaveAttribute('aria-pressed', 'true');\n    });\n\n    it('should warn if Toggle value is not set and ToggleGroup value is defined', async () => {\n      vi.spyOn(console, 'error')\n        .mockName('console.error')\n        .mockImplementation(() => {});\n\n      await render(\n        <ToggleGroup defaultValue={['one']}>\n          <Toggle />\n          <Toggle />\n        </ToggleGroup>,\n      );\n\n      expect(console.error).toHaveBeenCalledExactlyOnceWith(\n        'Base UI: A `<Toggle>` component rendered in a `<ToggleGroup>` has no explicit `value` prop. This will cause issues between the Toggle Group and Toggle values. Provide the `<Toggle>` with a `value` prop matching the `<ToggleGroup>` values prop type.',\n      );\n    });\n  });\n\n  describe('controlled', () => {\n    it('pressed state', async () => {\n      const { setProps } = await render(\n        <ToggleGroup value={['two']}>\n          <Toggle value=\"one\" />\n          <Toggle value=\"two\" />\n        </ToggleGroup>,\n      );\n\n      const [button1, button2] = screen.getAllByRole('button');\n\n      expect(button1).toHaveAttribute('aria-pressed', 'false');\n      expect(button2).toHaveAttribute('aria-pressed', 'true');\n      expect(button2).toHaveAttribute('data-pressed');\n\n      await setProps({ value: ['one'] });\n\n      expect(button1).toHaveAttribute('aria-pressed', 'true');\n      expect(button1).toHaveAttribute('data-pressed');\n      expect(button2).toHaveAttribute('aria-pressed', 'false');\n\n      await setProps({ value: ['two'] });\n\n      expect(button2).toHaveAttribute('aria-pressed', 'true');\n      expect(button2).toHaveAttribute('data-pressed');\n      expect(button1).toHaveAttribute('aria-pressed', 'false');\n    });\n\n    it('prop: value', async () => {\n      const { setProps } = await render(\n        <ToggleGroup value={['two']}>\n          <Toggle value=\"one\" />\n          <Toggle value=\"two\" />\n        </ToggleGroup>,\n      );\n\n      const [button1, button2] = screen.getAllByRole('button');\n\n      expect(button2).toHaveAttribute('aria-pressed', 'true');\n      expect(button2).toHaveAttribute('data-pressed');\n      expect(button1).toHaveAttribute('aria-pressed', 'false');\n\n      await setProps({ value: ['one'] });\n\n      expect(button1).toHaveAttribute('aria-pressed', 'true');\n      expect(button1).toHaveAttribute('data-pressed');\n      expect(button2).toHaveAttribute('aria-pressed', 'false');\n    });\n  });\n\n  describe('prop: disabled', () => {\n    it('can disable the whole group', async () => {\n      await render(\n        <ToggleGroup disabled>\n          <Toggle value=\"one\" />\n          <Toggle value=\"two\" />\n        </ToggleGroup>,\n      );\n\n      const [button1, button2] = screen.getAllByRole('button');\n\n      expect(button1).toHaveAttribute('aria-disabled', 'true');\n      expect(button1).toHaveAttribute('data-disabled');\n      expect(button2).toHaveAttribute('aria-disabled', 'true');\n      expect(button2).toHaveAttribute('data-disabled');\n    });\n\n    it('can disable individual items', async () => {\n      await render(\n        <ToggleGroup>\n          <Toggle value=\"one\" />\n          <Toggle value=\"two\" disabled />\n        </ToggleGroup>,\n      );\n\n      const [button1, button2] = screen.getAllByRole('button');\n\n      expect(button1).toHaveAttribute('aria-disabled', 'false');\n      expect(button1).not.toHaveAttribute('data-disabled');\n      expect(button2).toHaveAttribute('aria-disabled', 'true');\n      expect(button2).toHaveAttribute('data-disabled');\n    });\n  });\n\n  describe('prop: orientation', () => {\n    it('vertical', async () => {\n      await render(\n        <ToggleGroup orientation=\"vertical\">\n          <Toggle value=\"one\" />\n          <Toggle value=\"two\" />\n        </ToggleGroup>,\n      );\n\n      const group = screen.queryByRole('group');\n      expect(group).toHaveAttribute('data-orientation', 'vertical');\n    });\n  });\n\n  describe('prop: multiple', () => {\n    it('multiple items can be pressed when true', async () => {\n      const { user } = await render(\n        <ToggleGroup multiple defaultValue={['one']}>\n          <Toggle value=\"one\" />\n          <Toggle value=\"two\" />\n        </ToggleGroup>,\n      );\n\n      const [button1, button2] = screen.getAllByRole('button');\n\n      expect(button1).toHaveAttribute('aria-pressed', 'true');\n      expect(button2).toHaveAttribute('aria-pressed', 'false');\n\n      await user.pointer({ keys: '[MouseLeft]', target: button2 });\n\n      expect(button1).toHaveAttribute('aria-pressed', 'true');\n      expect(button2).toHaveAttribute('aria-pressed', 'true');\n    });\n\n    it('only one item can be pressed when false', async () => {\n      const { user } = await render(\n        <ToggleGroup defaultValue={['one']}>\n          <Toggle value=\"one\" />\n          <Toggle value=\"two\" />\n        </ToggleGroup>,\n      );\n\n      const [button1, button2] = screen.getAllByRole('button');\n\n      expect(button1).toHaveAttribute('aria-pressed', 'true');\n      expect(button2).toHaveAttribute('aria-pressed', 'false');\n\n      await user.pointer({ keys: '[MouseLeft]', target: button2 });\n\n      expect(button1).toHaveAttribute('aria-pressed', 'false');\n      expect(button2).toHaveAttribute('aria-pressed', 'true');\n    });\n\n    it('when Toggles omit value', async () => {\n      const { user } = await render(\n        <ToggleGroup multiple>\n          <Toggle value=\"\" />\n          <Toggle />\n        </ToggleGroup>,\n      );\n\n      const [button1, button2] = screen.getAllByRole('button');\n\n      expect(button2).toHaveAttribute('aria-pressed', 'false');\n      expect(button1).toHaveAttribute('aria-pressed', 'false');\n\n      await user.click(button1);\n      expect(button1).toHaveAttribute('aria-pressed', 'true');\n      expect(button2).toHaveAttribute('aria-pressed', 'false');\n\n      await user.click(button2);\n      expect(button1).toHaveAttribute('aria-pressed', 'true');\n      expect(button2).toHaveAttribute('aria-pressed', 'true');\n\n      await user.click(button1);\n      expect(button1).toHaveAttribute('aria-pressed', 'false');\n      expect(button2).toHaveAttribute('aria-pressed', 'true');\n    });\n  });\n\n  describe.skipIf(isJSDOM)('keyboard interactions', () => {\n    [\n      ['ltr', 'ArrowRight', 'ArrowDown', 'ArrowLeft', 'ArrowUp'],\n      ['rtl', 'ArrowLeft', 'ArrowDown', 'ArrowRight', 'ArrowUp'],\n    ].forEach((entry) => {\n      const [direction, horizontalNextKey, verticalNextKey, horizontalPrevKey, verticalPrevKey] =\n        entry;\n\n      it(direction, async () => {\n        const { user } = await render(\n          <DirectionProvider direction={direction as TextDirection}>\n            <ToggleGroup>\n              <Toggle value=\"one\" />\n              <Toggle value=\"two\" />\n              <Toggle value=\"three\" />\n            </ToggleGroup>\n          </DirectionProvider>,\n        );\n\n        const [button1, button2, button3] = screen.getAllByRole('button');\n\n        await user.keyboard('[Tab]');\n\n        expect(button1).toHaveAttribute('tabindex', '0');\n        expect(button1).toHaveFocus();\n\n        await user.keyboard(`[${horizontalNextKey}]`);\n\n        expect(button2).toHaveAttribute('tabindex', '0');\n        expect(button2).toHaveFocus();\n\n        await user.keyboard(`[${horizontalNextKey}]`);\n\n        expect(button3).toHaveAttribute('tabindex', '0');\n        expect(button3).toHaveFocus();\n\n        await user.keyboard(`[${verticalNextKey}]`);\n\n        expect(button1).toHaveAttribute('tabindex', '0');\n        expect(button1).toHaveFocus();\n\n        await user.keyboard(`[${verticalNextKey}]`);\n\n        expect(button2).toHaveAttribute('tabindex', '0');\n        expect(button2).toHaveFocus();\n\n        await user.keyboard(`[${horizontalPrevKey}]`);\n\n        expect(button1).toHaveAttribute('tabindex', '0');\n        expect(button1).toHaveFocus();\n\n        await user.keyboard(`[${verticalPrevKey}]`);\n\n        expect(button3).toHaveAttribute('tabindex', '0');\n        expect(button3).toHaveFocus();\n      });\n    });\n\n    it('Home key moves focus to the first item', async () => {\n      const { user } = await render(\n        <ToggleGroup>\n          <Toggle value=\"one\" />\n          <Toggle value=\"two\" />\n          <Toggle value=\"three\" />\n        </ToggleGroup>,\n      );\n\n      const [button1, button2, button3] = screen.getAllByRole('button');\n\n      await user.keyboard('[Tab]');\n      expect(button1).toHaveFocus();\n\n      await user.keyboard('[ArrowRight][ArrowRight]');\n      expect(button3).toHaveFocus();\n\n      await user.keyboard('[Home]');\n      expect(button1).toHaveAttribute('tabindex', '0');\n      expect(button1).toHaveFocus();\n\n      await user.keyboard('[ArrowRight]');\n      expect(button2).toHaveFocus();\n\n      await user.keyboard('[Home]');\n      expect(button1).toHaveAttribute('tabindex', '0');\n      expect(button1).toHaveFocus();\n    });\n\n    it('End key moves focus to the last item', async () => {\n      const { user } = await render(\n        <ToggleGroup>\n          <Toggle value=\"one\" />\n          <Toggle value=\"two\" />\n          <Toggle value=\"three\" />\n        </ToggleGroup>,\n      );\n\n      const [button1, button2, button3] = screen.getAllByRole('button');\n\n      await user.keyboard('[Tab]');\n      expect(button1).toHaveFocus();\n\n      await user.keyboard('[End]');\n      expect(button3).toHaveAttribute('tabindex', '0');\n      expect(button3).toHaveFocus();\n\n      await user.keyboard('[ArrowLeft]');\n      expect(button2).toHaveFocus();\n\n      await user.keyboard('[End]');\n      expect(button3).toHaveAttribute('tabindex', '0');\n      expect(button3).toHaveFocus();\n    });\n\n    ['Enter', 'Space'].forEach((key) => {\n      it(`key: ${key} toggles the pressed state`, async () => {\n        const { user } = await render(\n          <ToggleGroup>\n            <Toggle value=\"one\" />\n            <Toggle value=\"two\" />\n          </ToggleGroup>,\n        );\n\n        const [button1] = screen.getAllByRole('button');\n\n        expect(button1).toHaveAttribute('aria-pressed', 'false');\n\n        await act(async () => {\n          button1.focus();\n        });\n\n        await user.keyboard(`[${key}]`);\n\n        expect(button1).toHaveAttribute('aria-pressed', 'true');\n\n        await user.keyboard(`[${key}]`);\n\n        expect(button1).toHaveAttribute('aria-pressed', 'false');\n      });\n    });\n  });\n\n  describe('prop: onValueChange', () => {\n    it('fires when an Item is clicked', async () => {\n      const onValueChange = vi.fn();\n\n      const { user } = await render(\n        <ToggleGroup onValueChange={onValueChange}>\n          <Toggle value=\"one\" />\n          <Toggle value=\"two\" />\n        </ToggleGroup>,\n      );\n\n      const [button1, button2] = screen.getAllByRole('button');\n\n      expect(onValueChange.mock.calls.length).toBe(0);\n\n      await user.pointer({ keys: '[MouseLeft]', target: button1 });\n\n      expect(onValueChange.mock.calls.length).toBe(1);\n      expect(onValueChange.mock.calls[0][0]).toEqual(['one']);\n\n      await user.pointer({ keys: '[MouseLeft]', target: button2 });\n\n      expect(onValueChange.mock.calls.length).toBe(2);\n      expect(onValueChange.mock.calls[1][0]).toEqual(['two']);\n    });\n\n    ['Enter', 'Space'].forEach((key) => {\n      it(`fires when when the ${key} is pressed`, async ({ skip }) => {\n        if (isJSDOM) {\n          skip();\n        }\n\n        const onValueChange = vi.fn();\n\n        const { user } = await render(\n          <ToggleGroup onValueChange={onValueChange}>\n            <Toggle value=\"one\" />\n            <Toggle value=\"two\" />\n          </ToggleGroup>,\n        );\n\n        const [button1, button2] = screen.getAllByRole('button');\n\n        expect(onValueChange.mock.calls.length).toBe(0);\n\n        await act(async () => {\n          button1.focus();\n        });\n\n        await user.keyboard(`[${key}]`);\n\n        expect(onValueChange.mock.calls.length).toBe(1);\n        expect(onValueChange.mock.calls[0][0]).toEqual(['one']);\n\n        await act(async () => {\n          button2.focus();\n        });\n\n        await user.keyboard(`[${key}]`);\n\n        expect(onValueChange.mock.calls.length).toBe(2);\n        expect(onValueChange.mock.calls[1][0]).toEqual(['two']);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/toggle-group/ToggleGroup.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useControlled } from '@base-ui/utils/useControlled';\nimport { useRenderElement } from '../utils/useRenderElement';\nimport type { BaseUIComponentProps, HTMLProps, Orientation } from '../utils/types';\nimport { CompositeRoot } from '../composite/root/CompositeRoot';\nimport { useToolbarRootContext } from '../toolbar/root/ToolbarRootContext';\nimport { ToggleGroupContext } from './ToggleGroupContext';\nimport { ToggleGroupDataAttributes } from './ToggleGroupDataAttributes';\nimport type { BaseUIChangeEventDetails } from '../utils/createBaseUIEventDetails';\nimport { REASONS } from '../utils/reasons';\n\nconst stateAttributesMapping = {\n  multiple(value: boolean) {\n    if (value) {\n      return { [ToggleGroupDataAttributes.multiple]: '' } as Record<string, string>;\n    }\n    return null;\n  },\n};\n\n/**\n * Provides a shared state to a series of toggle buttons.\n *\n * Documentation: [Base UI Toggle Group](https://base-ui.com/react/components/toggle-group)\n */\nexport const ToggleGroup = React.forwardRef(function ToggleGroup<Value extends string>(\n  componentProps: ToggleGroup.Props<Value>,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const {\n    defaultValue: defaultValueProp,\n    disabled: disabledProp = false,\n    loopFocus = true,\n    onValueChange,\n    orientation = 'horizontal',\n    multiple = false,\n    value: valueProp,\n    className,\n    render,\n    ...elementProps\n  } = componentProps;\n\n  const toolbarContext = useToolbarRootContext(true);\n\n  const defaultValue = React.useMemo(() => {\n    if (valueProp === undefined) {\n      return defaultValueProp ?? [];\n    }\n\n    return undefined;\n  }, [valueProp, defaultValueProp]);\n\n  const isValueInitialized = React.useMemo(\n    () => valueProp !== undefined || defaultValueProp !== undefined,\n    [valueProp, defaultValueProp],\n  );\n\n  const disabled = (toolbarContext?.disabled ?? false) || disabledProp;\n\n  const [groupValue, setValueState] = useControlled({\n    controlled: valueProp,\n    default: defaultValue,\n    name: 'ToggleGroup',\n    state: 'value',\n  });\n\n  const setGroupValue = useStableCallback(\n    (\n      newValue: Value,\n      nextPressed: boolean,\n      eventDetails: BaseUIChangeEventDetails<typeof REASONS.none>,\n    ) => {\n      let newGroupValue: Value[];\n      if (multiple) {\n        newGroupValue = groupValue.slice();\n        if (nextPressed) {\n          newGroupValue.push(newValue);\n        } else {\n          newGroupValue.splice(groupValue.indexOf(newValue), 1);\n        }\n      } else {\n        newGroupValue = nextPressed ? [newValue] : [];\n      }\n      if (Array.isArray(newGroupValue)) {\n        onValueChange?.(newGroupValue, eventDetails);\n\n        if (eventDetails.isCanceled) {\n          return;\n        }\n\n        setValueState(newGroupValue);\n      }\n    },\n  );\n\n  const state: ToggleGroupState = { disabled, multiple, orientation };\n\n  const contextValue: ToggleGroupContext<Value> = React.useMemo(\n    () => ({\n      disabled,\n      orientation,\n      setGroupValue,\n      value: groupValue,\n      isValueInitialized,\n    }),\n    [disabled, orientation, setGroupValue, groupValue, isValueInitialized],\n  );\n\n  const defaultProps: HTMLProps = {\n    role: 'group',\n  };\n\n  const element = useRenderElement('div', componentProps, {\n    enabled: Boolean(toolbarContext),\n    state,\n    ref: forwardedRef,\n    props: [defaultProps, elementProps],\n    stateAttributesMapping,\n  });\n\n  return (\n    <ToggleGroupContext.Provider value={contextValue}>\n      {toolbarContext ? (\n        element\n      ) : (\n        <CompositeRoot\n          render={render}\n          className={className}\n          state={state}\n          refs={[forwardedRef]}\n          props={[defaultProps, elementProps]}\n          stateAttributesMapping={stateAttributesMapping}\n          loopFocus={loopFocus}\n          enableHomeAndEndKeys\n        />\n      )}\n    </ToggleGroupContext.Provider>\n  );\n}) as {\n  <Value extends string>(\n    props: ToggleGroup.Props<Value> & React.RefAttributes<HTMLDivElement>,\n  ): React.JSX.Element;\n};\n\nexport interface ToggleGroupState {\n  /**\n   * Whether the component should ignore user interaction.\n   */\n  disabled: boolean;\n  /**\n   * When `false` only one item in the group can be pressed. If any item in\n   * the group becomes pressed, the others will become unpressed.\n   * When `true` multiple items can be pressed.\n   * @default false\n   */\n  multiple: boolean;\n  /**\n   * The orientation of the toggle group.\n   */\n  orientation: Orientation;\n}\n\nexport interface ToggleGroupProps<Value extends string> extends BaseUIComponentProps<\n  'div',\n  ToggleGroupState\n> {\n  /**\n   * The open state of the toggle group represented by an array of\n   * the values of all pressed toggle buttons.\n   * This is the controlled counterpart of `defaultValue`.\n   */\n  value?: readonly Value[] | undefined;\n  /**\n   * The open state of the toggle group represented by an array of\n   * the values of all pressed toggle buttons.\n   * This is the uncontrolled counterpart of `value`.\n   */\n  defaultValue?: readonly Value[] | undefined;\n  /**\n   * Callback fired when the pressed states of the toggle group changes.\n   */\n  onValueChange?:\n    | ((groupValue: Value[], eventDetails: ToggleGroup.ChangeEventDetails) => void)\n    | undefined;\n  /**\n   * Whether the toggle group should ignore user interaction.\n   * @default false\n   */\n  disabled?: boolean | undefined;\n  /**\n   * @default 'horizontal'\n   */\n  orientation?: Orientation | undefined;\n  /**\n   * Whether to loop keyboard focus back to the first item\n   * when the end of the list is reached while using the arrow keys.\n   * @default true\n   */\n  loopFocus?: boolean | undefined;\n  /**\n   * When `false` only one item in the group can be pressed. If any item in\n   * the group becomes pressed, the others will become unpressed.\n   * When `true` multiple items can be pressed.\n   * @default false\n   */\n  multiple?: boolean | undefined;\n}\n\nexport type ToggleGroupChangeEventReason = typeof REASONS.none;\n\nexport type ToggleGroupChangeEventDetails = BaseUIChangeEventDetails<ToggleGroup.ChangeEventReason>;\n\nexport namespace ToggleGroup {\n  export type State = ToggleGroupState;\n  export type Props<Value extends string = string> = ToggleGroupProps<Value>;\n  export type ChangeEventReason = ToggleGroupChangeEventReason;\n  export type ChangeEventDetails = ToggleGroupChangeEventDetails;\n}\n"
  },
  {
    "path": "packages/react/src/toggle-group/ToggleGroupContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport type { Orientation } from '../utils/types';\nimport type { BaseUIChangeEventDetails } from '../utils/createBaseUIEventDetails';\nimport type { BaseUIEventReasons } from '../utils/reasons';\n\nexport interface ToggleGroupContext<Value> {\n  value: readonly Value[];\n  setGroupValue: (\n    newValue: Value,\n    nextPressed: boolean,\n    eventDetails: BaseUIChangeEventDetails<BaseUIEventReasons['none']>,\n  ) => void;\n  disabled: boolean;\n  orientation: Orientation;\n  /**\n   * Indicates whether the value has been initialized via `value` or `defaultValue` props.\n   * Used to determine if Toggle should warn users about data inconsistency problems.\n   */\n  isValueInitialized: boolean;\n}\n\nexport const ToggleGroupContext = React.createContext<ToggleGroupContext<any> | undefined>(\n  undefined,\n);\n\nexport function useToggleGroupContext<Value>(optional = true) {\n  const context = React.useContext<ToggleGroupContext<Value> | undefined>(ToggleGroupContext);\n  if (context === undefined && !optional) {\n    throw new Error(\n      'Base UI: ToggleGroupContext is missing. ToggleGroup parts must be placed within <ToggleGroup>.',\n    );\n  }\n\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/toggle-group/ToggleGroupDataAttributes.ts",
    "content": "export enum ToggleGroupDataAttributes {\n  /**\n   * Present when the toggle group is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Indicates the orientation of the toggle group.\n   * @type {'horizontal' | 'vertical'}\n   */\n  orientation = 'data-orientation',\n  /**\n   * Present when the toggle group allows multiple buttons to be in the pressed state at the same time.\n   */\n  multiple = 'data-multiple',\n}\n"
  },
  {
    "path": "packages/react/src/toggle-group/index.ts",
    "content": "export { ToggleGroup } from './ToggleGroup';\n\nexport type * from './ToggleGroup';\n"
  },
  {
    "path": "packages/react/src/toolbar/button/ToolbarButton.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport { Toolbar } from '@base-ui/react/toolbar';\nimport { Switch } from '@base-ui/react/switch';\nimport { Menu } from '@base-ui/react/menu';\nimport { Select } from '@base-ui/react/select';\nimport { Dialog } from '@base-ui/react/dialog';\nimport { AlertDialog } from '@base-ui/react/alert-dialog';\nimport { Popover } from '@base-ui/react/popover';\nimport { Toggle } from '@base-ui/react/toggle';\nimport { ToggleGroup } from '@base-ui/react/toggle-group';\nimport { screen, waitFor } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\nimport { NOOP } from '../../utils/noop';\nimport { ToolbarRootContext } from '../root/ToolbarRootContext';\nimport { CompositeRootContext } from '../../composite/root/CompositeRootContext';\n\nconst testCompositeContext: CompositeRootContext = {\n  highlightedIndex: 0,\n  onHighlightedIndexChange: NOOP,\n  highlightItemOnHover: false,\n  relayKeyboardEvent: NOOP,\n};\n\nconst testToolbarContext: ToolbarRootContext = {\n  disabled: false,\n  orientation: 'horizontal',\n  setItemMap: NOOP,\n};\n\ndescribe('<Toolbar.Button />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Toolbar.Button />, () => ({\n    refInstanceof: window.HTMLButtonElement,\n    testComponentPropWith: 'button',\n    button: true,\n    render: (node) => {\n      return render(\n        <ToolbarRootContext.Provider value={testToolbarContext}>\n          <CompositeRootContext.Provider value={testCompositeContext}>\n            {node}\n          </CompositeRootContext.Provider>\n        </ToolbarRootContext.Provider>,\n      );\n    },\n  }));\n\n  describe('ARIA attributes', () => {\n    it('renders a button', async () => {\n      await render(\n        <Toolbar.Root>\n          <Toolbar.Button data-testid=\"button\" />\n        </Toolbar.Root>,\n      );\n\n      expect(screen.getByTestId('button')).toBe(screen.getByRole('button'));\n    });\n  });\n\n  describe('prop: disabled', () => {\n    it('disables the button', async () => {\n      const handleClick = vi.fn();\n      const handleMouseDown = vi.fn().mockName('handleMouseDown');\n      const handlePointerDown = vi.fn();\n      const handleKeyDown = vi.fn();\n\n      const { user } = await render(\n        <Toolbar.Root>\n          <Toolbar.Button\n            disabled\n            onClick={handleClick}\n            onMouseDown={handleMouseDown}\n            onPointerDown={handlePointerDown}\n            onKeyDown={handleKeyDown}\n          />\n        </Toolbar.Root>,\n      );\n\n      const button = screen.getByRole('button');\n\n      expect(button).not.toHaveAttribute('disabled');\n      expect(button).toHaveAttribute('data-disabled');\n      expect(button).toHaveAttribute('aria-disabled', 'true');\n\n      await user.click(button);\n      await user.keyboard(`[Space]`);\n      await user.keyboard(`[Enter]`);\n      expect(handleClick).toHaveBeenCalledTimes(0);\n      expect(handleMouseDown).toHaveBeenCalledTimes(0);\n      expect(handlePointerDown).toHaveBeenCalledTimes(0);\n      expect(handleKeyDown).toHaveBeenCalledTimes(0);\n    });\n  });\n\n  describe('rendering other Base UI components', () => {\n    describe('Switch', () => {\n      it('renders a switch', async () => {\n        vi.spyOn(console, 'error')\n          .mockName('console.error')\n          .mockImplementation(() => {});\n\n        await render(\n          <Toolbar.Root>\n            <Toolbar.Button data-testid=\"button\" render={<Switch.Root />} />\n          </Toolbar.Root>,\n        );\n\n        expect(console.error).toHaveBeenCalledTimes(1);\n        expect(console.error).toHaveBeenCalledWith(\n          expect.stringContaining(\n            'Base UI: A component that acts as a button expected a native <button> because ' +\n              'the `nativeButton` prop is true. Rendering a non-<button> removes native button semantics, ' +\n              'which can impact forms and accessibility. Use a real <button> in the `render` prop, or ' +\n              'set `nativeButton` to `false`.',\n          ),\n        );\n\n        expect(screen.getByTestId('button')).toBe(screen.getByRole('switch'));\n      });\n\n      it('handles interactions', async () => {\n        vi.spyOn(console, 'error')\n          .mockName('console.error')\n          .mockImplementation(() => {});\n\n        const handleCheckedChange = vi.fn();\n        const handleClick = vi.fn();\n        const { user } = await render(\n          <Toolbar.Root>\n            <Toolbar.Button\n              onClick={handleClick}\n              render={<Switch.Root defaultChecked={false} onCheckedChange={handleCheckedChange} />}\n            />\n          </Toolbar.Root>,\n        );\n\n        expect(console.error).toHaveBeenCalledTimes(1);\n        expect(console.error).toHaveBeenCalledWith(\n          expect.stringContaining(\n            'Base UI: A component that acts as a button expected a native <button> because ' +\n              'the `nativeButton` prop is true. Rendering a non-<button> removes native button semantics, ' +\n              'which can impact forms and accessibility. Use a real <button> in the `render` prop, or ' +\n              'set `nativeButton` to `false`.',\n          ),\n        );\n\n        const switchElement = screen.getByRole('switch');\n        expect(switchElement).toHaveAttribute('data-unchecked');\n\n        await user.keyboard('[Tab]');\n        expect(switchElement).toHaveAttribute('tabindex', '0');\n\n        await user.click(switchElement);\n        expect(handleCheckedChange).toHaveBeenCalledTimes(1);\n        expect(handleClick).toHaveBeenCalledTimes(1);\n        expect(switchElement).toHaveAttribute('data-checked');\n\n        await user.keyboard('[Enter]');\n        expect(handleCheckedChange).toHaveBeenCalledTimes(2);\n        expect(handleClick).toHaveBeenCalledTimes(2);\n        expect(switchElement).toHaveAttribute('data-unchecked');\n\n        await user.keyboard('[Space]');\n        expect(handleCheckedChange).toHaveBeenCalledTimes(3);\n        expect(handleClick).toHaveBeenCalledTimes(3);\n        expect(switchElement).toHaveAttribute('data-checked');\n      });\n\n      it('disabled state', async () => {\n        vi.spyOn(console, 'error')\n          .mockName('console.error')\n          .mockImplementation(() => {});\n\n        const handleCheckedChange = vi.fn();\n        const handleClick = vi.fn();\n        const { user } = await render(\n          <Toolbar.Root>\n            <Toolbar.Button\n              disabled\n              onClick={handleClick}\n              render={<Switch.Root onCheckedChange={handleCheckedChange} />}\n            />\n          </Toolbar.Root>,\n        );\n\n        expect(console.error).toHaveBeenCalledTimes(1);\n        expect(console.error).toHaveBeenCalledWith(\n          expect.stringContaining(\n            'Base UI: A component that acts as a button expected a native <button> because ' +\n              'the `nativeButton` prop is true. Rendering a non-<button> removes native button semantics, ' +\n              'which can impact forms and accessibility. Use a real <button> in the `render` prop, or ' +\n              'set `nativeButton` to `false`.',\n          ),\n        );\n\n        const switchElement = screen.getByRole('switch');\n\n        expect(switchElement).not.toHaveAttribute('disabled');\n        expect(switchElement).toHaveAttribute('data-disabled');\n        expect(switchElement).toHaveAttribute('aria-disabled', 'true');\n\n        await user.keyboard('[Tab]');\n        expect(switchElement).toHaveAttribute('tabindex', '0');\n\n        await user.keyboard('[Enter]');\n        expect(handleCheckedChange).toHaveBeenCalledTimes(0);\n        expect(handleClick).toHaveBeenCalledTimes(0);\n\n        await user.keyboard('[Space]');\n        expect(handleCheckedChange).toHaveBeenCalledTimes(0);\n        expect(handleClick).toHaveBeenCalledTimes(0);\n\n        await user.click(switchElement);\n        expect(handleCheckedChange).toHaveBeenCalledTimes(0);\n        expect(handleClick).toHaveBeenCalledTimes(0);\n      });\n    });\n\n    describe('Menu', () => {\n      it('renders a menu trigger', async () => {\n        await render(\n          <Toolbar.Root>\n            <Menu.Root>\n              <Toolbar.Button data-testid=\"button\" render={<Menu.Trigger>Toggle</Menu.Trigger>} />\n              <Menu.Portal>\n                <Menu.Positioner>\n                  <Menu.Popup>\n                    <Menu.Item data-testid=\"item-1\">1</Menu.Item>\n                    <Menu.Item data-testid=\"item-2\">2</Menu.Item>\n                    <Menu.Item data-testid=\"item-3\">3</Menu.Item>\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            </Menu.Root>\n          </Toolbar.Root>,\n        );\n\n        expect(screen.getByTestId('button')).toHaveAttribute('aria-haspopup', 'menu');\n      });\n\n      it('handles interactions', async () => {\n        const handleOpenChange = vi.fn();\n        const handleClick = vi.fn();\n        const { user } = await render(\n          <Toolbar.Root>\n            <Menu.Root onOpenChange={handleOpenChange}>\n              <Toolbar.Button\n                data-testid=\"button\"\n                onClick={handleClick}\n                render={<Menu.Trigger>Toggle</Menu.Trigger>}\n              />\n              <Menu.Portal>\n                <Menu.Positioner>\n                  <Menu.Popup>\n                    <Menu.Item data-testid=\"item-1\">1</Menu.Item>\n                    <Menu.Item data-testid=\"item-2\">2</Menu.Item>\n                    <Menu.Item data-testid=\"item-3\">3</Menu.Item>\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            </Menu.Root>\n          </Toolbar.Root>,\n        );\n\n        expect(screen.queryByRole('menu')).toBe(null);\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n\n        await user.keyboard('[Tab]');\n        expect(trigger).toHaveFocus();\n\n        await user.keyboard('[Enter]');\n        expect(handleClick).toHaveBeenCalledTimes(1);\n        expect(handleOpenChange).toHaveBeenCalledTimes(1);\n        expect(screen.queryByRole('menu')).not.toBe(null);\n\n        await waitFor(() => {\n          expect(screen.getByTestId('item-1')).toHaveFocus();\n        });\n\n        await user.keyboard('[ArrowDown]');\n        await waitFor(() => {\n          expect(screen.getByTestId('item-2')).toHaveFocus();\n        });\n\n        await user.keyboard('[ArrowDown]');\n        await waitFor(() => {\n          expect(screen.getByTestId('item-3')).toHaveFocus();\n        });\n\n        await user.keyboard('[ArrowUp]');\n        await waitFor(() => {\n          expect(screen.getByTestId('item-2')).toHaveFocus();\n        });\n\n        await user.keyboard('[Escape]');\n        await waitFor(() => {\n          expect(screen.queryByRole('menu')).toBe(null);\n        });\n\n        expect(handleOpenChange).toHaveBeenCalledTimes(2);\n\n        await waitFor(() => {\n          expect(trigger).toHaveFocus();\n        });\n      });\n\n      it('disabled state', async () => {\n        const handleOpenChange = vi.fn();\n        const handleClick = vi.fn();\n        const { user } = await render(\n          <Toolbar.Root>\n            <Menu.Root onOpenChange={handleOpenChange}>\n              <Toolbar.Button\n                data-testid=\"button\"\n                disabled\n                onClick={handleClick}\n                render={<Menu.Trigger>Toggle</Menu.Trigger>}\n              />\n              <Menu.Portal>\n                <Menu.Positioner>\n                  <Menu.Popup>\n                    <Menu.Item data-testid=\"item-1\">1</Menu.Item>\n                    <Menu.Item data-testid=\"item-2\">2</Menu.Item>\n                    <Menu.Item data-testid=\"item-3\">3</Menu.Item>\n                  </Menu.Popup>\n                </Menu.Positioner>\n              </Menu.Portal>\n            </Menu.Root>\n          </Toolbar.Root>,\n        );\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n        expect(trigger).not.toHaveAttribute('disabled');\n        expect(trigger).toHaveAttribute('data-disabled');\n        expect(trigger).toHaveAttribute('aria-disabled', 'true');\n\n        expect(screen.queryByRole('menu')).toBe(null);\n\n        await user.keyboard('[Tab]');\n        expect(trigger).toHaveFocus();\n\n        await user.keyboard('[Enter]');\n        await user.keyboard('[Space]');\n        await user.keyboard('[ArrowUp]');\n        await user.keyboard('[ArrowDown]');\n\n        expect(handleClick).toHaveBeenCalledTimes(0);\n        expect(handleOpenChange).toHaveBeenCalledTimes(0);\n        expect(screen.queryByRole('menu')).toBe(null);\n      });\n    });\n\n    describe('Select', () => {\n      it('renders a select trigger', async () => {\n        await render(\n          <Toolbar.Root>\n            <Select.Root defaultValue=\"a\">\n              <Toolbar.Button data-testid=\"button\" render={<Select.Trigger />} />\n              <Select.Portal>\n                <Select.Positioner>\n                  <Select.Popup>\n                    <Select.Item value=\"a\">a</Select.Item>\n                    <Select.Item value=\"b\">b</Select.Item>\n                  </Select.Popup>\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n          </Toolbar.Root>,\n        );\n\n        const trigger = screen.getByTestId('button');\n        expect(trigger).toBe(screen.getByRole('combobox'));\n        expect(trigger).toHaveAttribute('aria-haspopup', 'listbox');\n      });\n\n      it.skipIf(!isJSDOM)('handles interactions', async () => {\n        const handleValueChange = vi.fn();\n        const { user } = await render(\n          <Toolbar.Root>\n            <Select.Root defaultValue=\"a\" onValueChange={handleValueChange}>\n              <Toolbar.Button data-testid=\"button\" render={<Select.Trigger />} />\n              <Select.Portal>\n                <Select.Positioner>\n                  <Select.Popup data-testid=\"popup\">\n                    <Select.Item value=\"a\" data-testid=\"item-a\">\n                      a\n                    </Select.Item>\n                    <Select.Item value=\"b\" data-testid=\"item-b\">\n                      b\n                    </Select.Item>\n                  </Select.Popup>\n                </Select.Positioner>\n              </Select.Portal>\n            </Select.Root>\n          </Toolbar.Root>,\n        );\n\n        expect(screen.queryByRole('listbox')).toBe(null);\n\n        const trigger = screen.getByTestId('button');\n        await user.keyboard('[Tab]');\n        expect(trigger).toHaveFocus();\n\n        await user.keyboard('[ArrowDown]');\n        expect(screen.queryByRole('listbox')).toBe(screen.getByTestId('popup'));\n        await waitFor(() => {\n          expect(screen.getByRole('option', { name: 'a' })).toHaveFocus();\n        });\n\n        await user.keyboard('[ArrowDown]');\n        await waitFor(() => {\n          expect(screen.getByRole('option', { name: 'b' })).toHaveFocus();\n        });\n\n        await user.keyboard('[Enter]');\n        await waitFor(() => {\n          expect(screen.queryByRole('listbox')).toBe(null);\n        });\n\n        await waitFor(() => {\n          expect(trigger).toHaveFocus();\n        });\n\n        expect(handleValueChange).toHaveBeenCalledTimes(1);\n        expect(handleValueChange).toHaveBeenCalledWith('b', expect.anything());\n      });\n\n      it('disabled state', async () => {\n        await expect(async () => {\n          const onValueChange = vi.fn();\n          const onOpenChange = vi.fn();\n          const { user } = await render(\n            <Toolbar.Root>\n              <Select.Root\n                defaultValue=\"a\"\n                onValueChange={onValueChange}\n                onOpenChange={onOpenChange}\n              >\n                <Toolbar.Button disabled render={<Select.Trigger nativeButton={false} />} />\n                <Select.Portal>\n                  <Select.Positioner>\n                    <Select.Popup>\n                      <Select.Item value=\"a\" />\n                      <Select.Item value=\"b\" />\n                    </Select.Popup>\n                  </Select.Positioner>\n                </Select.Portal>\n              </Select.Root>\n            </Toolbar.Root>,\n          );\n\n          expect(screen.queryByRole('listbox')).toBe(null);\n\n          const trigger = screen.getByRole('combobox');\n          expect(trigger).not.toHaveAttribute('disabled');\n          expect(trigger).toHaveAttribute('data-disabled');\n          expect(trigger).toHaveAttribute('aria-disabled', 'true');\n\n          await user.keyboard('[Tab]');\n          expect(trigger).toHaveFocus();\n\n          expect(onOpenChange).toHaveBeenCalledTimes(0);\n          expect(onValueChange).toHaveBeenCalledTimes(0);\n\n          await user.keyboard('[ArrowUp]');\n          await user.keyboard('[ArrowDown]');\n          await user.keyboard('[Enter]');\n          await user.keyboard('[Space]');\n\n          expect(onOpenChange).toHaveBeenCalledTimes(0);\n          expect(onValueChange).toHaveBeenCalledTimes(0);\n        }).toErrorDev([\n          'Base UI: A component that acts as a button expected a non-<button> because ' +\n            'the `nativeButton` prop is false. Rendering a <button> keeps native behavior while Base UI ' +\n            'applies non-native attributes and handlers, which can add unintended extra attributes ' +\n            '(such as `role` or `aria-disabled`). Use a non-<button> in the `render` prop, or set ' +\n            '`nativeButton` to `true`.',\n        ]);\n      });\n    });\n\n    describe('Dialog', () => {\n      it('renders a dialog trigger', async () => {\n        await render(\n          <Toolbar.Root>\n            <Dialog.Root modal={false}>\n              <Toolbar.Button render={<Dialog.Trigger data-testid=\"trigger\" />} />\n              <Dialog.Portal>\n                <Dialog.Backdrop />\n                <Dialog.Popup>\n                  <Dialog.Title>title text</Dialog.Title>\n                </Dialog.Popup>\n              </Dialog.Portal>\n            </Dialog.Root>\n          </Toolbar.Root>,\n        );\n\n        expect(screen.getByTestId('trigger')).toBe(screen.getByRole('button'));\n      });\n\n      it('handles interactions', async () => {\n        const onOpenChange = vi.fn();\n        const { user } = await render(\n          <Toolbar.Root>\n            <Dialog.Root modal={false} onOpenChange={onOpenChange}>\n              <Toolbar.Button render={<Dialog.Trigger />} />\n              <Dialog.Portal>\n                <Dialog.Backdrop />\n                <Dialog.Popup>\n                  <Dialog.Title>title text</Dialog.Title>\n                </Dialog.Popup>\n              </Dialog.Portal>\n            </Dialog.Root>\n          </Toolbar.Root>,\n        );\n\n        expect(screen.queryByText('title text')).toBe(null);\n\n        const trigger = screen.getByRole('button');\n        await user.keyboard('[Tab]');\n        expect(trigger).toHaveFocus();\n        expect(onOpenChange).toHaveBeenCalledTimes(0);\n\n        await user.keyboard('[Enter]');\n        expect(screen.queryByText('title text')).not.toBe(null);\n        expect(onOpenChange).toHaveBeenCalledTimes(1);\n        expect(onOpenChange).toHaveBeenNthCalledWith(1, true, expect.anything());\n\n        await user.keyboard('[Escape]');\n        expect(screen.queryByText('title text')).toBe(null);\n        expect(onOpenChange).toHaveBeenCalledTimes(2);\n        expect(onOpenChange).toHaveBeenNthCalledWith(2, false, expect.anything());\n\n        await waitFor(() => {\n          expect(trigger).toHaveFocus();\n        });\n      });\n\n      it('disabled state', async () => {\n        const onOpenChange = vi.fn();\n        const { user } = await render(\n          <Toolbar.Root>\n            <Dialog.Root modal={false} onOpenChange={onOpenChange}>\n              <Toolbar.Button disabled render={<Dialog.Trigger />} />\n              <Dialog.Portal>\n                <Dialog.Backdrop />\n                <Dialog.Popup>\n                  <Dialog.Title>title text</Dialog.Title>\n                </Dialog.Popup>\n              </Dialog.Portal>\n            </Dialog.Root>\n          </Toolbar.Root>,\n        );\n\n        expect(screen.queryByText('title text')).toBe(null);\n\n        const trigger = screen.getByRole('button');\n        expect(trigger).not.toHaveAttribute('disabled');\n        expect(trigger).toHaveAttribute('data-disabled');\n        expect(trigger).toHaveAttribute('aria-disabled', 'true');\n\n        await user.keyboard('[Tab]');\n        expect(trigger).toHaveFocus();\n        expect(onOpenChange).toHaveBeenCalledTimes(0);\n\n        await user.keyboard('[Enter]');\n        await user.keyboard('[Space]');\n        await user.keyboard('[ArrowUp]');\n        await user.keyboard('[ArrowDown]');\n        expect(onOpenChange).toHaveBeenCalledTimes(0);\n      });\n\n      it('prevents composite keydowns from escaping', async () => {\n        const onOpenChange = vi.fn();\n        const { user } = await render(\n          <Toolbar.Root>\n            <Dialog.Root modal={false} onOpenChange={onOpenChange}>\n              <Toolbar.Button render={<Dialog.Trigger />}>dialog</Toolbar.Button>\n              <Dialog.Portal>\n                <Dialog.Popup />\n              </Dialog.Portal>\n            </Dialog.Root>\n\n            <Toolbar.Button>empty</Toolbar.Button>\n          </Toolbar.Root>,\n        );\n\n        expect(screen.queryByRole('dialog')).toBe(null);\n\n        const trigger = screen.getByRole('button', { name: 'dialog' });\n        await user.click(trigger);\n\n        await waitFor(() => {\n          expect(screen.queryByRole('dialog')).toHaveFocus();\n        });\n\n        await user.keyboard('{ArrowRight}');\n\n        expect(onOpenChange).toHaveBeenLastCalledWith(true, expect.anything());\n      });\n    });\n\n    describe('AlertDialog', () => {\n      it('renders an alert dialog trigger', async () => {\n        await render(\n          <Toolbar.Root>\n            <AlertDialog.Root>\n              <Toolbar.Button render={<AlertDialog.Trigger data-testid=\"trigger\" />} />\n              <AlertDialog.Portal>\n                <AlertDialog.Backdrop />\n                <AlertDialog.Popup>\n                  <AlertDialog.Title>title text</AlertDialog.Title>\n                </AlertDialog.Popup>\n              </AlertDialog.Portal>\n            </AlertDialog.Root>\n          </Toolbar.Root>,\n        );\n\n        expect(screen.getByTestId('trigger')).toBe(screen.getByRole('button'));\n      });\n\n      it('handles interactions', async () => {\n        const onOpenChange = vi.fn();\n        const { user } = await render(\n          <Toolbar.Root>\n            <AlertDialog.Root onOpenChange={onOpenChange}>\n              <Toolbar.Button render={<AlertDialog.Trigger />} />\n              <AlertDialog.Portal>\n                <AlertDialog.Backdrop />\n                <AlertDialog.Popup>\n                  <AlertDialog.Title>title text</AlertDialog.Title>\n                </AlertDialog.Popup>\n              </AlertDialog.Portal>\n            </AlertDialog.Root>\n          </Toolbar.Root>,\n        );\n\n        expect(screen.queryByText('title text')).toBe(null);\n\n        const trigger = screen.getByRole('button');\n        await user.keyboard('[Tab]');\n        expect(trigger).toHaveFocus();\n        expect(onOpenChange).toHaveBeenCalledTimes(0);\n\n        await user.keyboard('[Enter]');\n        expect(screen.queryByText('title text')).not.toBe(null);\n        expect(onOpenChange).toHaveBeenCalledTimes(1);\n        expect(onOpenChange).toHaveBeenNthCalledWith(1, true, expect.anything());\n\n        await user.keyboard('[Escape]');\n        expect(screen.queryByText('title text')).toBe(null);\n        expect(onOpenChange).toHaveBeenCalledTimes(2);\n        expect(onOpenChange).toHaveBeenNthCalledWith(2, false, expect.anything());\n\n        await waitFor(() => {\n          expect(trigger).toHaveFocus();\n        });\n      });\n\n      it('disabled state', async () => {\n        const onOpenChange = vi.fn();\n        const { user } = await render(\n          <Toolbar.Root>\n            <AlertDialog.Root onOpenChange={onOpenChange}>\n              <Toolbar.Button disabled render={<AlertDialog.Trigger />} />\n              <AlertDialog.Portal>\n                <AlertDialog.Backdrop />\n                <AlertDialog.Popup>\n                  <AlertDialog.Title>title text</AlertDialog.Title>\n                </AlertDialog.Popup>\n              </AlertDialog.Portal>\n            </AlertDialog.Root>\n          </Toolbar.Root>,\n        );\n\n        expect(screen.queryByText('title text')).toBe(null);\n\n        const trigger = screen.getByRole('button');\n        expect(trigger).not.toHaveAttribute('disabled');\n        expect(trigger).toHaveAttribute('data-disabled');\n        expect(trigger).toHaveAttribute('aria-disabled', 'true');\n\n        await user.keyboard('[Tab]');\n        expect(trigger).toHaveFocus();\n        expect(onOpenChange).toHaveBeenCalledTimes(0);\n\n        await user.keyboard('[Enter]');\n        await user.keyboard('[Space]');\n        await user.keyboard('[ArrowUp]');\n        await user.keyboard('[ArrowDown]');\n        expect(onOpenChange).toHaveBeenCalledTimes(0);\n      });\n\n      it('prevents composite keydowns from escaping', async () => {\n        const onOpenChange = vi.fn();\n        const { user } = await render(\n          <Toolbar.Root>\n            <AlertDialog.Root onOpenChange={onOpenChange}>\n              <Toolbar.Button render={<AlertDialog.Trigger />}>dialog</Toolbar.Button>\n              <AlertDialog.Portal>\n                <AlertDialog.Popup />\n              </AlertDialog.Portal>\n            </AlertDialog.Root>\n\n            <Toolbar.Button>empty</Toolbar.Button>\n          </Toolbar.Root>,\n        );\n\n        expect(screen.queryByRole('dialog')).toBe(null);\n\n        const trigger = screen.getByRole('button', { name: 'dialog' });\n        await user.click(trigger);\n\n        await waitFor(() => {\n          expect(screen.queryByRole('alertdialog')).toHaveFocus();\n        });\n\n        await user.keyboard('{ArrowRight}');\n\n        expect(onOpenChange).toHaveBeenLastCalledWith(true, expect.anything());\n      });\n    });\n\n    describe('Popover', () => {\n      it('renders a popover trigger', async () => {\n        await render(\n          <Toolbar.Root>\n            <Popover.Root>\n              <Toolbar.Button render={<Popover.Trigger data-testid=\"trigger\" />} />\n              <Popover.Portal>\n                <Popover.Positioner>\n                  <Popover.Popup>Content</Popover.Popup>\n                </Popover.Positioner>\n              </Popover.Portal>\n            </Popover.Root>\n          </Toolbar.Root>,\n        );\n\n        expect(screen.getByTestId('trigger')).toBe(screen.getByRole('button'));\n        expect(screen.getByRole('button')).toHaveAttribute('aria-haspopup', 'dialog');\n      });\n\n      it('handles interactions', async () => {\n        const onOpenChange = vi.fn();\n        const { user } = await render(\n          <Toolbar.Root>\n            <Popover.Root onOpenChange={onOpenChange}>\n              <Toolbar.Button render={<Popover.Trigger />} />\n              <Popover.Portal>\n                <Popover.Positioner>\n                  <Popover.Popup>Content</Popover.Popup>\n                </Popover.Positioner>\n              </Popover.Portal>\n            </Popover.Root>\n          </Toolbar.Root>,\n        );\n\n        expect(screen.queryByText('Content')).toBe(null);\n\n        const trigger = screen.getByRole('button');\n        await user.keyboard('[Tab]');\n        expect(trigger).toHaveFocus();\n        expect(onOpenChange).toHaveBeenCalledTimes(0);\n\n        await user.keyboard('[Enter]');\n        expect(screen.queryByText('Content')).not.toBe(null);\n        expect(onOpenChange).toHaveBeenCalledTimes(1);\n        expect(onOpenChange).toHaveBeenNthCalledWith(1, true, expect.anything());\n\n        await user.keyboard('[Escape]');\n        expect(onOpenChange).toHaveBeenCalledTimes(2);\n        expect(onOpenChange).toHaveBeenNthCalledWith(2, false, expect.anything());\n        await waitFor(() => {\n          expect(trigger).toHaveFocus();\n        });\n      });\n\n      it('disabled state', async () => {\n        const onOpenChange = vi.fn();\n        const { user } = await render(\n          <Toolbar.Root>\n            <Popover.Root onOpenChange={onOpenChange}>\n              <Toolbar.Button disabled render={<Popover.Trigger />} />\n              <Popover.Portal>\n                <Popover.Positioner>\n                  <Popover.Popup>Content</Popover.Popup>\n                </Popover.Positioner>\n              </Popover.Portal>\n            </Popover.Root>\n          </Toolbar.Root>,\n        );\n\n        expect(screen.queryByText('Content')).toBe(null);\n\n        const trigger = screen.getByRole('button');\n        expect(trigger).not.toHaveAttribute('disabled');\n        expect(trigger).toHaveAttribute('data-disabled');\n        expect(trigger).toHaveAttribute('aria-disabled', 'true');\n\n        await user.keyboard('[Tab]');\n        expect(trigger).toHaveFocus();\n        expect(onOpenChange).toHaveBeenCalledTimes(0);\n\n        await user.keyboard('[Enter]');\n        await user.keyboard('[Space]');\n        await user.keyboard('[ArrowUp]');\n        await user.keyboard('[ArrowDown]');\n        expect(onOpenChange).toHaveBeenCalledTimes(0);\n      });\n    });\n\n    describe('Toggle and ToggleGroup', () => {\n      it('renders toggle and toggle group', async () => {\n        await render(\n          <Toolbar.Root>\n            <Toolbar.Button render={<Toggle />} value=\"apple\" />\n            <ToggleGroup>\n              <Toolbar.Button render={<Toggle />} value=\"one\" />\n              <Toolbar.Button render={<Toggle />} value=\"two\" />\n            </ToggleGroup>\n          </Toolbar.Root>,\n        );\n\n        expect(screen.getAllByRole('button').length).toBe(3);\n        screen.getAllByRole('button').forEach((button) => {\n          expect(button).toHaveAttribute('aria-pressed');\n        });\n      });\n\n      it('handles interactions', async () => {\n        const onPressedChange = vi.fn();\n        const { user } = await render(\n          <Toolbar.Root>\n            <Toolbar.Button render={<Toggle onPressedChange={onPressedChange} />} value=\"apple\" />\n            <ToggleGroup>\n              <Toolbar.Button render={<Toggle onPressedChange={onPressedChange} />} value=\"one\" />\n              <Toolbar.Button render={<Toggle onPressedChange={onPressedChange} />} value=\"two\" />\n            </ToggleGroup>\n          </Toolbar.Root>,\n        );\n\n        const [button1, button2, button3] = screen.getAllByRole('button');\n\n        [button1, button2, button3].forEach((button) => {\n          expect(button).toHaveAttribute('aria-pressed', 'false');\n        });\n        expect(onPressedChange).toHaveBeenCalledTimes(0);\n\n        await user.keyboard('[Tab]');\n        await waitFor(() => {\n          expect(button1).toHaveFocus();\n        });\n\n        await user.keyboard('[Enter]');\n        expect(onPressedChange).toHaveBeenCalledTimes(1);\n        expect(button1).toHaveAttribute('aria-pressed', 'true');\n\n        await user.keyboard('[ArrowRight]');\n        await waitFor(() => {\n          expect(button2).toHaveFocus();\n        });\n\n        await user.keyboard('[Space]');\n        expect(onPressedChange).toHaveBeenCalledTimes(2);\n        expect(button2).toHaveAttribute('aria-pressed', 'true');\n\n        await user.keyboard('[ArrowRight]');\n        await waitFor(() => {\n          expect(button3).toHaveFocus();\n        });\n\n        await user.keyboard('[Enter]');\n        expect(onPressedChange).toHaveBeenCalledTimes(3);\n        expect(button3).toHaveAttribute('aria-pressed', 'true');\n      });\n\n      it('disabled state', async () => {\n        const onPressedChange = vi.fn();\n        const { user } = await render(\n          <Toolbar.Root>\n            <Toolbar.Button\n              disabled\n              render={<Toggle onPressedChange={onPressedChange} />}\n              value=\"apple\"\n            />\n            <ToggleGroup>\n              <Toolbar.Button\n                disabled\n                render={<Toggle onPressedChange={onPressedChange} />}\n                value=\"one\"\n              />\n              <Toolbar.Button\n                disabled\n                render={<Toggle onPressedChange={onPressedChange} />}\n                value=\"two\"\n              />\n            </ToggleGroup>\n          </Toolbar.Root>,\n        );\n        const [button1, button2, button3] = screen.getAllByRole('button');\n\n        [button1, button2, button3].forEach((button) => {\n          expect(button).toHaveAttribute('aria-pressed', 'false');\n          expect(button).not.toHaveAttribute('disabled');\n          expect(button).toHaveAttribute('data-disabled');\n          expect(button).toHaveAttribute('aria-disabled', 'true');\n        });\n        expect(onPressedChange).toHaveBeenCalledTimes(0);\n\n        await user.keyboard('[Tab]');\n        await waitFor(() => {\n          expect(button1).toHaveFocus();\n        });\n        await user.keyboard('[Enter]');\n        await user.keyboard('[Space]');\n        expect(onPressedChange).toHaveBeenCalledTimes(0);\n\n        await user.keyboard('[ArrowRight]');\n        await waitFor(() => {\n          expect(button2).toHaveFocus();\n        });\n        await user.keyboard('[Enter]');\n        await user.keyboard('[Space]');\n        expect(onPressedChange).toHaveBeenCalledTimes(0);\n\n        await user.keyboard('[ArrowRight]');\n        await waitFor(() => {\n          expect(button3).toHaveFocus();\n        });\n        await user.keyboard('[Enter]');\n        await user.keyboard('[Space]');\n        expect(onPressedChange).toHaveBeenCalledTimes(0);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/toolbar/button/ToolbarButton.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { BaseUIComponentProps, NativeButtonProps } from '../../utils/types';\nimport { useButton } from '../../use-button';\nimport type { ToolbarRootState } from '../root/ToolbarRoot';\nimport { useToolbarRootContext } from '../root/ToolbarRootContext';\nimport { useToolbarGroupContext } from '../group/ToolbarGroupContext';\nimport { CompositeItem } from '../../composite/item/CompositeItem';\n\n/**\n * A button that can be used as-is or as a trigger for other components.\n * Renders a `<button>` element.\n *\n * Documentation: [Base UI Toolbar](https://base-ui.com/react/components/toolbar)\n */\nexport const ToolbarButton = React.forwardRef(function ToolbarButton(\n  componentProps: ToolbarButton.Props,\n  forwardedRef: React.ForwardedRef<HTMLButtonElement>,\n) {\n  const {\n    className,\n    disabled: disabledProp = false,\n    focusableWhenDisabled = true,\n    render,\n    nativeButton = true,\n    ...elementProps\n  } = componentProps;\n\n  const itemMetadata = React.useMemo(() => ({ focusableWhenDisabled }), [focusableWhenDisabled]);\n\n  const { disabled: toolbarDisabled, orientation } = useToolbarRootContext();\n\n  const groupContext = useToolbarGroupContext(true);\n\n  const disabled = toolbarDisabled || (groupContext?.disabled ?? false) || disabledProp;\n\n  const { getButtonProps, buttonRef } = useButton({\n    disabled,\n    focusableWhenDisabled,\n    native: nativeButton,\n  });\n\n  const state: ToolbarButtonState = {\n    disabled,\n    orientation,\n    focusable: focusableWhenDisabled,\n  };\n\n  return (\n    <CompositeItem\n      tag=\"button\"\n      render={render}\n      className={className}\n      metadata={itemMetadata}\n      state={state}\n      refs={[forwardedRef, buttonRef]}\n      props={[\n        elementProps,\n        // for integrating with Menu and Select disabled states, `disabled` is\n        // intentionally duplicated even though getButtonProps includes it already\n        // TODO: follow up after https://github.com/mui/base-ui/issues/1976#issuecomment-2916905663\n        { disabled },\n        getButtonProps,\n      ]}\n    />\n  );\n});\n\nexport interface ToolbarButtonState extends ToolbarRootState {\n  /**\n   * Whether the component is disabled.\n   */\n  disabled: boolean;\n  /**\n   * Whether the component remains focusable when disabled.\n   */\n  focusable: boolean;\n}\n\nexport interface ToolbarButtonProps\n  extends NativeButtonProps, BaseUIComponentProps<'button', ToolbarButtonState> {\n  /**\n   * When `true` the item is disabled.\n   * @default false\n   */\n  disabled?: boolean | undefined;\n  /**\n   * When `true` the item remains focusable when disabled.\n   * @default true\n   */\n  focusableWhenDisabled?: boolean | undefined;\n}\n\nexport namespace ToolbarButton {\n  export type State = ToolbarButtonState;\n  export type Props = ToolbarButtonProps;\n}\n"
  },
  {
    "path": "packages/react/src/toolbar/button/ToolbarButtonDataAttributes.ts",
    "content": "export enum ToolbarButtonDataAttributes {\n  /**\n   * Present when the button is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Indicates the orientation of the toolbar.\n   * @type {'horizontal' | 'vertical'}\n   */\n  orientation = 'data-orientation',\n  /**\n   * Present when the button remains focusable when disabled.\n   */\n  focusable = 'data-focusable',\n}\n"
  },
  {
    "path": "packages/react/src/toolbar/group/ToolbarGroup.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { Toolbar } from '@base-ui/react/toolbar';\nimport { screen } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance } from '#test-utils';\nimport { NOOP } from '../../utils/noop';\nimport { ToolbarRootContext } from '../root/ToolbarRootContext';\nimport { CompositeRootContext } from '../../composite/root/CompositeRootContext';\n\nconst testCompositeContext: CompositeRootContext = {\n  highlightedIndex: 0,\n  onHighlightedIndexChange: NOOP,\n  highlightItemOnHover: false,\n  relayKeyboardEvent: NOOP,\n};\n\nconst testToolbarContext: ToolbarRootContext = {\n  disabled: false,\n  orientation: 'horizontal',\n  setItemMap: NOOP,\n};\n\ndescribe('<Toolbar.Group />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Toolbar.Group />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render: (node) => {\n      return render(\n        <ToolbarRootContext.Provider value={testToolbarContext}>\n          <CompositeRootContext.Provider value={testCompositeContext}>\n            {node}\n          </CompositeRootContext.Provider>\n        </ToolbarRootContext.Provider>,\n      );\n    },\n  }));\n\n  describe('ARIA attributes', () => {\n    it('renders a group', async () => {\n      await render(\n        <Toolbar.Root>\n          <Toolbar.Group data-testid=\"group\" />\n        </Toolbar.Root>,\n      );\n\n      expect(screen.getByTestId('group')).toBe(screen.getByRole('group'));\n    });\n  });\n\n  describe('prop: disabled', () => {\n    it('disables all toolbar items except links in the group', async () => {\n      await render(\n        <Toolbar.Root>\n          <Toolbar.Group disabled>\n            <Toolbar.Button />\n            <Toolbar.Link href=\"https://base-ui.com\">Link</Toolbar.Link>\n            <Toolbar.Input defaultValue=\"\" />\n          </Toolbar.Group>\n        </Toolbar.Root>,\n      );\n\n      [screen.getByRole('button'), screen.getByRole('textbox')].forEach((toolbarItem) => {\n        expect(toolbarItem).toHaveAttribute('aria-disabled', 'true');\n        expect(toolbarItem).toHaveAttribute('data-disabled');\n      });\n\n      expect(screen.getByText('Link')).not.toHaveAttribute('data-disabled');\n      expect(screen.getByText('Link')).not.toHaveAttribute('aria-disabled');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/toolbar/group/ToolbarGroup.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { useToolbarRootContext } from '../root/ToolbarRootContext';\nimport type { ToolbarRootState } from '../root/ToolbarRoot';\nimport { ToolbarGroupContext } from './ToolbarGroupContext';\n\n/**\n * Groups several toolbar items or toggles.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Toolbar](https://base-ui.com/react/components/toolbar)\n */\nexport const ToolbarGroup = React.forwardRef(function ToolbarGroup(\n  componentProps: ToolbarGroup.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { className, disabled: disabledProp = false, render, ...elementProps } = componentProps;\n\n  const { orientation, disabled: toolbarDisabled } = useToolbarRootContext();\n\n  const disabled = toolbarDisabled || disabledProp;\n\n  const contextValue: ToolbarGroupContext = React.useMemo(\n    () => ({\n      disabled,\n    }),\n    [disabled],\n  );\n\n  const state: ToolbarRootState = {\n    disabled,\n    orientation,\n  };\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: forwardedRef,\n    props: [{ role: 'group' }, elementProps],\n  });\n\n  return (\n    <ToolbarGroupContext.Provider value={contextValue}>{element}</ToolbarGroupContext.Provider>\n  );\n});\n\nexport interface ToolbarGroupState extends ToolbarRootState {}\n\nexport interface ToolbarGroupProps extends BaseUIComponentProps<'div', ToolbarGroupState> {\n  /**\n   * When `true` all toolbar items in the group are disabled.\n   * @default false\n   */\n  disabled?: boolean | undefined;\n}\n\nexport namespace ToolbarGroup {\n  export type State = ToolbarGroupState;\n  export type Props = ToolbarGroupProps;\n}\n"
  },
  {
    "path": "packages/react/src/toolbar/group/ToolbarGroupContext.ts",
    "content": "'use client';\nimport * as React from 'react';\n\nexport interface ToolbarGroupContext {\n  disabled: boolean;\n}\n\nexport const ToolbarGroupContext = React.createContext<ToolbarGroupContext | undefined>(undefined);\n\nexport function useToolbarGroupContext(optional?: false): ToolbarGroupContext;\nexport function useToolbarGroupContext(optional: true): ToolbarGroupContext | undefined;\nexport function useToolbarGroupContext(optional?: boolean) {\n  const context = React.useContext(ToolbarGroupContext);\n  if (context === undefined && !optional) {\n    throw new Error(\n      'Base UI: ToolbarGroupContext is missing. ToolbarGroup parts must be placed within <Toolbar.Group>.',\n    );\n  }\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/toolbar/group/ToolbarGroupDataAttributes.ts",
    "content": "export enum ToolbarGroupDataAttributes {\n  /**\n   * Present when the group is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Indicates the orientation of the toolbar.\n   * @type {'horizontal' | 'vertical'}\n   */\n  orientation = 'data-orientation',\n}\n"
  },
  {
    "path": "packages/react/src/toolbar/index.parts.ts",
    "content": "export { ToolbarSeparator as Separator } from './separator/ToolbarSeparator';\nexport { ToolbarRoot as Root } from './root/ToolbarRoot';\nexport { ToolbarGroup as Group } from './group/ToolbarGroup';\nexport { ToolbarButton as Button } from './button/ToolbarButton';\nexport { ToolbarLink as Link } from './link/ToolbarLink';\nexport { ToolbarInput as Input } from './input/ToolbarInput';\n\nexport { type Orientation } from '../utils/types';\n"
  },
  {
    "path": "packages/react/src/toolbar/index.ts",
    "content": "export * as Toolbar from './index.parts';\n\nexport type { Orientation } from './index.parts';\nexport type * from './root/ToolbarRoot';\nexport type * from './group/ToolbarGroup';\nexport type * from './button/ToolbarButton';\nexport type * from './link/ToolbarLink';\nexport type * from './input/ToolbarInput';\nexport type * from './separator/ToolbarSeparator';\n"
  },
  {
    "path": "packages/react/src/toolbar/input/ToolbarInput.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport { Toolbar } from '@base-ui/react/toolbar';\nimport { NumberField } from '@base-ui/react/number-field';\nimport { screen } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\nimport { NOOP } from '../../utils/noop';\nimport { ToolbarRootContext } from '../root/ToolbarRootContext';\nimport { type Orientation } from '../../utils/types';\nimport { CompositeRootContext } from '../../composite/root/CompositeRootContext';\nimport { ARROW_UP, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT } from '../../composite/composite';\n\nconst testCompositeContext: CompositeRootContext = {\n  highlightedIndex: 0,\n  onHighlightedIndexChange: NOOP,\n  highlightItemOnHover: false,\n  relayKeyboardEvent: NOOP,\n};\n\nconst testToolbarContext: ToolbarRootContext = {\n  disabled: false,\n  orientation: 'horizontal',\n  setItemMap: NOOP,\n};\n\ndescribe('<Toolbar.Input />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Toolbar.Input />, () => ({\n    refInstanceof: window.HTMLInputElement,\n    testRenderPropWith: 'input',\n    render: (node) => {\n      return render(\n        <ToolbarRootContext.Provider value={testToolbarContext}>\n          <CompositeRootContext.Provider value={testCompositeContext}>\n            {node}\n          </CompositeRootContext.Provider>\n        </ToolbarRootContext.Provider>,\n      );\n    },\n  }));\n\n  describe('ARIA attributes', () => {\n    it('renders a textbox', async () => {\n      await render(\n        <Toolbar.Root>\n          <Toolbar.Input data-testid=\"input\" />\n        </Toolbar.Root>,\n      );\n\n      expect(screen.getByTestId('input')).toBe(screen.getByRole('textbox'));\n    });\n  });\n\n  describe.skipIf(isJSDOM)('keyboard navigation', () => {\n    // when navigating through RTL text in real browsers the arrow keys for\n    // moving the text insertion cursor is also reversed from LTR but this doesn't\n    // work with testing library\n    [\n      ['horizontal', ARROW_RIGHT, ARROW_LEFT],\n      ['vertical', ARROW_DOWN, ARROW_UP],\n    ].forEach((entry) => {\n      const [orientation, nextKey, prevKey] = entry;\n\n      it(`orientation: ${orientation}`, async () => {\n        const { user } = await render(\n          <Toolbar.Root orientation={orientation as Orientation}>\n            <Toolbar.Button />\n            <Toolbar.Input defaultValue=\"abcd\" />\n            <Toolbar.Button />\n          </Toolbar.Root>,\n        );\n        const input = screen.getByRole('textbox') as HTMLInputElement;\n        const [button1, button2] = screen.getAllByRole('button');\n\n        await user.keyboard('[Tab]');\n        expect(button1).toHaveFocus();\n\n        await user.keyboard(`[${nextKey}]`);\n        expect(input).toHaveFocus();\n\n        // Firefox doesn't support document.getSelection() in inputs\n        expect(input.selectionStart).toBe(0);\n        expect(input.selectionEnd).toBe(4);\n\n        await user.keyboard(`[${ARROW_RIGHT}]`);\n        await user.keyboard(`[${nextKey}]`);\n\n        expect(button2).toHaveFocus();\n\n        await user.keyboard(`[${prevKey}]`);\n        expect(input).toHaveFocus();\n\n        await user.keyboard(`[${ARROW_LEFT}]`);\n        await user.keyboard(`[${prevKey}]`);\n\n        expect(button1).toHaveFocus();\n      });\n    });\n  });\n\n  describe('rendering NumberField', () => {\n    it('renders NumberField.Input', async () => {\n      await render(\n        <Toolbar.Root>\n          <NumberField.Root>\n            <NumberField.Group>\n              <Toolbar.Input render={<NumberField.Input />} />\n            </NumberField.Group>\n          </NumberField.Root>\n        </Toolbar.Root>,\n      );\n\n      expect(screen.getByRole('textbox')).toHaveAttribute('aria-roledescription', 'Number field');\n    });\n\n    it('handles interactions', async () => {\n      const onValueChange = vi.fn();\n      const { user } = await render(\n        <Toolbar.Root>\n          <NumberField.Root min={1} max={10} defaultValue={5} onValueChange={onValueChange}>\n            <NumberField.Group>\n              <NumberField.Decrement />\n              <Toolbar.Input render={<NumberField.Input />} />\n              <NumberField.Increment />\n            </NumberField.Group>\n          </NumberField.Root>\n        </Toolbar.Root>,\n      );\n\n      const input = screen.getByRole('textbox');\n\n      await user.keyboard('[Tab]');\n      expect(input).toHaveAttribute('tabindex', '0');\n      expect(input).toHaveFocus();\n\n      await user.keyboard(`[${ARROW_UP}]`);\n      expect(onValueChange.mock.calls.length).toBe(1);\n      expect(onValueChange.mock.calls[0][0]).toBe(6);\n\n      await user.keyboard(`[${ARROW_DOWN}]`);\n      expect(onValueChange.mock.calls.length).toBe(2);\n      expect(onValueChange.mock.calls[1][0]).toBe(5);\n    });\n\n    it('disabled state', async () => {\n      const onValueChange = vi.fn();\n      const { user } = await render(\n        <Toolbar.Root>\n          <NumberField.Root min={1} max={10} defaultValue={5} onValueChange={onValueChange}>\n            <NumberField.Group>\n              <NumberField.Decrement />\n              <Toolbar.Input disabled render={<NumberField.Input />} />\n              <NumberField.Increment />\n            </NumberField.Group>\n          </NumberField.Root>\n        </Toolbar.Root>,\n      );\n\n      const input = screen.getByRole('textbox');\n\n      expect(input).not.toHaveAttribute('disabled');\n      expect(input).toHaveAttribute('data-disabled');\n      expect(input).toHaveAttribute('aria-disabled', 'true');\n\n      await user.keyboard('[Tab]');\n      expect(input).toHaveAttribute('tabindex', '0');\n      expect(input).toHaveFocus();\n\n      await user.keyboard(`[${ARROW_UP}]`);\n      await user.keyboard(`[${ARROW_DOWN}]`);\n      expect(onValueChange.mock.calls.length).toBe(0);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/toolbar/input/ToolbarInput.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { BaseUIComponentProps, HTMLProps } from '../../utils/types';\nimport { useFocusableWhenDisabled } from '../../utils/useFocusableWhenDisabled';\nimport { ARROW_LEFT, ARROW_RIGHT, stopEvent } from '../../composite/composite';\nimport type { ToolbarRootState } from '../root/ToolbarRoot';\nimport { useToolbarRootContext } from '../root/ToolbarRootContext';\nimport { useToolbarGroupContext } from '../group/ToolbarGroupContext';\nimport { CompositeItem } from '../../composite/item/CompositeItem';\n\n/**\n * A native input element that integrates with Toolbar keyboard navigation.\n * Renders an `<input>` element.\n *\n * Documentation: [Base UI Toolbar](https://base-ui.com/react/components/toolbar)\n */\nexport const ToolbarInput = React.forwardRef(function ToolbarInput(\n  componentProps: ToolbarInput.Props,\n  forwardedRef: React.ForwardedRef<HTMLInputElement>,\n) {\n  const {\n    className,\n    focusableWhenDisabled = true,\n    render,\n    disabled: disabledProp = false,\n    ...elementProps\n  } = componentProps;\n\n  const itemMetadata = React.useMemo(() => ({ focusableWhenDisabled }), [focusableWhenDisabled]);\n\n  const { disabled: toolbarDisabled, orientation } = useToolbarRootContext();\n\n  const groupContext = useToolbarGroupContext(true);\n\n  const disabled = toolbarDisabled || (groupContext?.disabled ?? false) || disabledProp;\n\n  const { props: focusableWhenDisabledProps } = useFocusableWhenDisabled({\n    composite: true,\n    disabled,\n    focusableWhenDisabled,\n    isNativeButton: false,\n  });\n\n  const state: ToolbarInputState = {\n    disabled,\n    orientation,\n    focusable: focusableWhenDisabled,\n  };\n\n  const defaultProps: HTMLProps = {\n    onClick(event) {\n      if (disabled) {\n        event.preventDefault();\n      }\n    },\n    onKeyDown(event) {\n      if (event.key !== ARROW_LEFT && event.key !== ARROW_RIGHT && disabled) {\n        stopEvent(event);\n      }\n    },\n    onPointerDown(event) {\n      if (disabled) {\n        event.preventDefault();\n      }\n    },\n  };\n\n  return (\n    <CompositeItem\n      tag=\"input\"\n      render={render}\n      className={className}\n      metadata={itemMetadata}\n      state={state}\n      refs={[forwardedRef]}\n      props={[defaultProps, elementProps, focusableWhenDisabledProps]}\n    />\n  );\n});\n\nexport interface ToolbarInputState extends ToolbarRootState {\n  /**\n   * Whether the component is disabled.\n   */\n  disabled: boolean;\n  /**\n   * Whether the component remains focusable when disabled.\n   */\n  focusable: boolean;\n}\n\nexport interface ToolbarInputProps extends BaseUIComponentProps<'input', ToolbarInputState> {\n  /**\n   * When `true` the item is disabled.\n   * @default false\n   */\n  disabled?: boolean | undefined;\n  /**\n   * When `true` the item remains focusable when disabled.\n   * @default true\n   */\n  focusableWhenDisabled?: boolean | undefined;\n  defaultValue?: React.ComponentProps<'input'>['defaultValue'] | undefined;\n}\n\nexport namespace ToolbarInput {\n  export type State = ToolbarInputState;\n  export type Props = ToolbarInputProps;\n}\n"
  },
  {
    "path": "packages/react/src/toolbar/input/ToolbarInputDataAttributes.ts",
    "content": "export enum ToolbarInputDataAttributes {\n  /**\n   * Present when the input is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Indicates the orientation of the toolbar.\n   * @type {'horizontal' | 'vertical'}\n   */\n  orientation = 'data-orientation',\n  /**\n   * Present when the input remains focusable when disabled.\n   */\n  focusable = 'data-focusable',\n}\n"
  },
  {
    "path": "packages/react/src/toolbar/link/ToolbarLink.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { Toolbar } from '@base-ui/react/toolbar';\nimport { screen } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance } from '#test-utils';\nimport { NOOP } from '../../utils/noop';\nimport { ToolbarRootContext } from '../root/ToolbarRootContext';\nimport { CompositeRootContext } from '../../composite/root/CompositeRootContext';\n\nconst testCompositeContext: CompositeRootContext = {\n  highlightedIndex: 0,\n  onHighlightedIndexChange: NOOP,\n  highlightItemOnHover: false,\n  relayKeyboardEvent: NOOP,\n};\n\nconst testToolbarContext: ToolbarRootContext = {\n  disabled: false,\n  orientation: 'horizontal',\n  setItemMap: NOOP,\n};\n\ndescribe('<Toolbar.Link />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Toolbar.Link />, () => ({\n    refInstanceof: window.HTMLAnchorElement,\n    testRenderPropWith: 'a',\n    render: (node) => {\n      return render(\n        <ToolbarRootContext.Provider value={testToolbarContext}>\n          <CompositeRootContext.Provider value={testCompositeContext}>\n            {node}\n          </CompositeRootContext.Provider>\n        </ToolbarRootContext.Provider>,\n      );\n    },\n  }));\n\n  describe('ARIA attributes', () => {\n    it('renders an anchor', async () => {\n      await render(\n        <Toolbar.Root>\n          <Toolbar.Link data-testid=\"link\" href=\"https://base-ui.com\" />\n        </Toolbar.Root>,\n      );\n\n      expect(screen.getByTestId('link')).toBe(screen.getByRole('link'));\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/toolbar/link/ToolbarLink.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport type { ToolbarRoot } from '../root/ToolbarRoot';\nimport { useToolbarRootContext } from '../root/ToolbarRootContext';\nimport { CompositeItem } from '../../composite/item/CompositeItem';\n\nconst TOOLBAR_LINK_METADATA = {\n  // links cannot be disabled, this metadata is only used for deriving `disabledIndices``\n  // TODO: better name\n  focusableWhenDisabled: true,\n};\n\n/**\n * A link component.\n * Renders an `<a>` element.\n *\n * Documentation: [Base UI Toolbar](https://base-ui.com/react/components/toolbar)\n */\nexport const ToolbarLink = React.forwardRef(function ToolbarLink(\n  componentProps: ToolbarLink.Props,\n  forwardedRef: React.ForwardedRef<HTMLAnchorElement>,\n) {\n  const { className, render, ...elementProps } = componentProps;\n\n  const { orientation } = useToolbarRootContext();\n\n  const state: ToolbarLinkState = {\n    orientation,\n  };\n\n  return (\n    <CompositeItem\n      tag=\"a\"\n      render={render}\n      className={className}\n      metadata={TOOLBAR_LINK_METADATA}\n      state={state}\n      refs={[forwardedRef]}\n      props={[elementProps]}\n    />\n  );\n});\n\nexport interface ToolbarLinkState {\n  /**\n   * The component orientation.\n   */\n  orientation: ToolbarRoot.Orientation;\n}\n\nexport interface ToolbarLinkProps extends BaseUIComponentProps<'a', ToolbarLinkState> {}\n\nexport namespace ToolbarLink {\n  export type State = ToolbarLinkState;\n  export type Props = ToolbarLinkProps;\n}\n"
  },
  {
    "path": "packages/react/src/toolbar/link/ToolbarLinkDataAttributes.ts",
    "content": "export enum ToolbarLinkDataAttributes {\n  /**\n   * Indicates the orientation of the toolbar.\n   * @type {'horizontal' | 'vertical'}\n   */\n  orientation = 'data-orientation',\n}\n"
  },
  {
    "path": "packages/react/src/toolbar/root/ToolbarRoot.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { Toolbar } from '@base-ui/react/toolbar';\nimport { DirectionProvider, type TextDirection } from '@base-ui/react/direction-provider';\nimport { screen } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\nimport { type Orientation } from '../../utils/types';\n\ndescribe('<Toolbar.Root />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Toolbar.Root />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render,\n  }));\n\n  describe('ARIA attributes', () => {\n    it('has role=\"toolbar\"', async () => {\n      const { container } = await render(<Toolbar.Root />);\n\n      expect(container.firstElementChild as HTMLElement).toHaveAttribute('role', 'toolbar');\n    });\n  });\n\n  describe.skipIf(isJSDOM)('keyboard navigation', () => {\n    [\n      ['ltr', 'horizontal', 'ArrowRight', 'ArrowLeft'],\n      ['ltr', 'vertical', 'ArrowDown', 'ArrowUp'],\n      ['rtl', 'horizontal', 'ArrowLeft', 'ArrowRight'],\n      ['rtl', 'vertical', 'ArrowDown', 'ArrowUp'],\n    ].forEach((entry) => {\n      const [direction, orientation, nextKey, prevKey] = entry;\n\n      describe(direction, () => {\n        it(`orientation: ${orientation}`, async () => {\n          const { user } = await render(\n            <DirectionProvider direction={direction as TextDirection}>\n              <Toolbar.Root dir={direction} orientation={orientation as Orientation}>\n                <Toolbar.Button />\n                <Toolbar.Link href=\"https://base-ui.com\">Link</Toolbar.Link>\n                <Toolbar.Group>\n                  <Toolbar.Button />\n                  <Toolbar.Button />\n                </Toolbar.Group>\n                <Toolbar.Input defaultValue=\"\" />\n              </Toolbar.Root>\n            </DirectionProvider>,\n          );\n          const [button1, groupedButton1, groupedButton2] = screen.getAllByRole('button');\n          const link = screen.getByText('Link');\n          const input = screen.getByRole('textbox');\n\n          await user.keyboard('[Tab]');\n          expect(button1).toHaveFocus();\n\n          await user.keyboard(`[${nextKey}]`);\n          expect(link).toHaveFocus();\n\n          await user.keyboard(`[${nextKey}]`);\n          expect(groupedButton1).toHaveFocus();\n\n          await user.keyboard(`[${nextKey}]`);\n          expect(groupedButton2).toHaveFocus();\n\n          await user.keyboard(`[${nextKey}]`);\n          expect(input).toHaveFocus();\n\n          // loop to the beginning\n          await user.keyboard(`[${nextKey}]`);\n          expect(button1).toHaveFocus();\n\n          await user.keyboard(`[${prevKey}]`);\n          expect(input).toHaveFocus();\n\n          await user.keyboard(`[${prevKey}]`);\n          expect(groupedButton2).toHaveFocus();\n        });\n      });\n    });\n  });\n\n  describe('prop: disabled', () => {\n    it('disables all toolbar items except links', async () => {\n      await render(\n        <Toolbar.Root disabled>\n          <Toolbar.Button />\n          <Toolbar.Link href=\"https://base-ui.com\">Link</Toolbar.Link>\n          <Toolbar.Input defaultValue=\"\" />\n          <Toolbar.Group>\n            <Toolbar.Button />\n            <Toolbar.Link href=\"https://base-ui.com\">Link</Toolbar.Link>\n            <Toolbar.Input defaultValue=\"\" />\n          </Toolbar.Group>\n        </Toolbar.Root>,\n      );\n\n      [...screen.getAllByRole('button'), ...screen.getAllByRole('textbox')].forEach(\n        (toolbarItem) => {\n          expect(toolbarItem).toHaveAttribute('aria-disabled', 'true');\n          expect(toolbarItem).toHaveAttribute('data-disabled');\n        },\n      );\n\n      expect(screen.getByRole('group')).toHaveAttribute('data-disabled');\n\n      screen.getAllByText('Link').forEach((link) => {\n        expect(link).not.toHaveAttribute('data-disabled');\n        expect(link).not.toHaveAttribute('aria-disabled');\n      });\n    });\n  });\n\n  describe.skipIf(isJSDOM)('prop: focusableWhenDisabled', () => {\n    function expectFocusedWhenDisabled(element: Element) {\n      expect(element).toHaveAttribute('data-disabled');\n      expect(element).toHaveAttribute('aria-disabled', 'true');\n      expect(element).toHaveFocus();\n    }\n\n    it('toolbar items can be focused when disabled by default', async () => {\n      const { user } = await render(\n        <Toolbar.Root>\n          <Toolbar.Button disabled />\n          <Toolbar.Group>\n            <Toolbar.Button disabled />\n            <Toolbar.Button disabled />\n          </Toolbar.Group>\n          <Toolbar.Input defaultValue=\"\" disabled />\n        </Toolbar.Root>,\n      );\n\n      const input = screen.getByRole('textbox');\n      const buttons = screen.getAllByRole('button');\n      [input, ...buttons].forEach((item) => {\n        expect(item).not.toHaveAttribute('disabled');\n      });\n\n      const [button1, groupedButton1, groupedButton2] = buttons;\n\n      await user.keyboard('[Tab]');\n      expect(button1).toHaveFocus();\n\n      await user.keyboard('[ArrowRight]');\n      expectFocusedWhenDisabled(groupedButton1);\n\n      await user.keyboard('[ArrowRight]');\n      expectFocusedWhenDisabled(groupedButton2);\n\n      await user.keyboard('[ArrowRight]');\n      expectFocusedWhenDisabled(input);\n\n      // loop to the beginning\n      await user.keyboard('[ArrowRight]');\n      expect(button1).toHaveAttribute('tabindex', '0');\n\n      await user.keyboard('[ArrowLeft]');\n      expectFocusedWhenDisabled(input);\n\n      await user.keyboard('[ArrowLeft]');\n      expectFocusedWhenDisabled(groupedButton2);\n    });\n\n    it('toolbar items can individually disable focusableWhenDisabled', async () => {\n      const { user } = await render(\n        <Toolbar.Root>\n          <Toolbar.Button disabled />\n          <Toolbar.Group>\n            <Toolbar.Button disabled />\n            <Toolbar.Button disabled focusableWhenDisabled={false} />\n          </Toolbar.Group>\n          <Toolbar.Input defaultValue=\"\" disabled />\n        </Toolbar.Root>,\n      );\n\n      const input = screen.getByRole('textbox');\n      const buttons = screen.getAllByRole('button');\n      const focusableWhenDisabledButtons = buttons.filter(\n        (button) => button.getAttribute('data-focusable') != null,\n      );\n      [input, ...focusableWhenDisabledButtons].forEach((item) => {\n        expect(item).not.toHaveAttribute('disabled');\n      });\n\n      const [button1, groupedButton1, groupedButton2] = buttons;\n      expect(groupedButton2).toHaveAttribute('disabled');\n\n      await user.keyboard('[Tab]');\n      expect(button1).toHaveFocus();\n\n      await user.keyboard('[ArrowRight]');\n      expectFocusedWhenDisabled(groupedButton1);\n\n      await user.keyboard('[ArrowRight]');\n      expectFocusedWhenDisabled(input);\n\n      // loop to the beginning\n      await user.keyboard('[ArrowRight]');\n      expect(button1).toHaveAttribute('tabindex', '0');\n\n      await user.keyboard('[ArrowLeft]');\n      expectFocusedWhenDisabled(input);\n\n      await user.keyboard('[ArrowLeft]');\n      expectFocusedWhenDisabled(groupedButton1);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/toolbar/root/ToolbarRoot.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { BaseUIComponentProps, Orientation as BaseOrientation, HTMLProps } from '../../utils/types';\nimport { CompositeRoot } from '../../composite/root/CompositeRoot';\nimport type { CompositeMetadata } from '../../composite/list/CompositeList';\nimport { ToolbarRootContext } from './ToolbarRootContext';\n\n/**\n * A container for grouping a set of controls, such as buttons, toggle groups, or menus.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Toolbar](https://base-ui.com/react/components/toolbar)\n */\nexport const ToolbarRoot = React.forwardRef(function ToolbarRoot(\n  componentProps: ToolbarRoot.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const {\n    disabled = false,\n    loopFocus = true,\n    orientation = 'horizontal',\n    className,\n    render,\n    ...elementProps\n  } = componentProps;\n\n  const [itemMap, setItemMap] = React.useState(\n    () => new Map<Node, CompositeMetadata<ToolbarRoot.ItemMetadata> | null>(),\n  );\n\n  const disabledIndices = React.useMemo(() => {\n    const output: number[] = [];\n    for (const itemMetadata of itemMap.values()) {\n      if (itemMetadata?.index && !itemMetadata.focusableWhenDisabled) {\n        output.push(itemMetadata.index);\n      }\n    }\n    return output;\n  }, [itemMap]);\n\n  const toolbarRootContext: ToolbarRootContext = React.useMemo(\n    () => ({\n      disabled,\n      orientation,\n      setItemMap,\n    }),\n    [disabled, orientation, setItemMap],\n  );\n\n  const state: ToolbarRootState = { disabled, orientation };\n\n  const defaultProps: HTMLProps = {\n    'aria-orientation': orientation,\n    role: 'toolbar',\n  };\n\n  return (\n    <ToolbarRootContext.Provider value={toolbarRootContext}>\n      <CompositeRoot\n        render={render}\n        className={className}\n        state={state}\n        refs={[forwardedRef]}\n        props={[defaultProps, elementProps]}\n        disabledIndices={disabledIndices}\n        loopFocus={loopFocus}\n        onMapChange={setItemMap}\n        orientation={orientation}\n      />\n    </ToolbarRootContext.Provider>\n  );\n});\n\nexport interface ToolbarRootItemMetadata {\n  focusableWhenDisabled: boolean;\n}\n\nexport type ToolbarRootOrientation = BaseOrientation;\n\nexport interface ToolbarRootState {\n  /**\n   * Whether the component is disabled.\n   */\n  disabled: boolean;\n  /**\n   * The component orientation.\n   */\n  orientation: ToolbarRoot.Orientation;\n}\n\nexport interface ToolbarRootProps extends BaseUIComponentProps<'div', ToolbarRootState> {\n  disabled?: boolean | undefined;\n  /**\n   * The orientation of the toolbar.\n   * @default 'horizontal'\n   */\n  orientation?: ToolbarRoot.Orientation | undefined;\n  /**\n   * If `true`, using keyboard navigation will wrap focus to the other end of the toolbar once the end is reached.\n   *\n   * @default true\n   */\n  loopFocus?: boolean | undefined;\n}\n\nexport namespace ToolbarRoot {\n  export type ItemMetadata = ToolbarRootItemMetadata;\n  export type Orientation = ToolbarRootOrientation;\n  export type State = ToolbarRootState;\n  export type Props = ToolbarRootProps;\n}\n"
  },
  {
    "path": "packages/react/src/toolbar/root/ToolbarRootContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport type { Orientation } from '../../utils/types';\nimport type { CompositeMetadata } from '../../composite/list/CompositeList';\nimport type { ToolbarRoot } from './ToolbarRoot';\n\nexport interface ToolbarRootContext {\n  disabled: boolean;\n  orientation: Orientation;\n  setItemMap: React.Dispatch<\n    React.SetStateAction<Map<Node, CompositeMetadata<ToolbarRoot.ItemMetadata> | null>>\n  >;\n}\n\nexport const ToolbarRootContext = React.createContext<ToolbarRootContext | undefined>(undefined);\n\nexport function useToolbarRootContext(optional?: false): ToolbarRootContext;\nexport function useToolbarRootContext(optional: true): ToolbarRootContext | undefined;\nexport function useToolbarRootContext(optional?: boolean) {\n  const context = React.useContext(ToolbarRootContext);\n  if (context === undefined && !optional) {\n    throw new Error(\n      'Base UI: ToolbarRootContext is missing. Toolbar parts must be placed within <Toolbar.Root>.',\n    );\n  }\n\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/toolbar/root/ToolbarRootDataAttributes.ts",
    "content": "export enum ToolbarRootDataAttributes {\n  /**\n   * Present when the toolbar is disabled.\n   */\n  disabled = 'data-disabled',\n  /**\n   * Indicates the orientation of the toolbar.\n   * @type {'horizontal' | 'vertical'}\n   */\n  orientation = 'data-orientation',\n}\n"
  },
  {
    "path": "packages/react/src/toolbar/separator/ToolbarSeparator.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { Separator, type SeparatorState } from '../../separator';\nimport { useToolbarRootContext } from '../root/ToolbarRootContext';\nimport type { ToolbarRoot } from '../root/ToolbarRoot';\n/**\n * A separator element accessible to screen readers.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Toolbar](https://base-ui.com/react/components/toolbar)\n */\nexport const ToolbarSeparator = React.forwardRef(function ToolbarSeparator(\n  props: ToolbarSeparator.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const context = useToolbarRootContext();\n\n  const orientation = (\n    {\n      vertical: 'horizontal',\n      horizontal: 'vertical',\n    } as Record<ToolbarRoot.Orientation, ToolbarRoot.Orientation>\n  )[context.orientation];\n\n  return <Separator orientation={orientation} {...props} ref={forwardedRef} />;\n});\n\nexport interface ToolbarSeparatorState extends SeparatorState {}\n\nexport interface ToolbarSeparatorProps\n  extends BaseUIComponentProps<'div', ToolbarSeparatorState>, Separator.Props {}\n\nexport namespace ToolbarSeparator {\n  export type State = ToolbarSeparatorState;\n  export type Props = ToolbarSeparatorProps;\n}\n"
  },
  {
    "path": "packages/react/src/toolbar/separator/ToolbarSeparatorDataAttributes.ts",
    "content": "export enum ToolbarSeparatorDataAttributes {\n  /**\n   * Indicates the orientation of the toolbar.\n   * @type {'horizontal' | 'vertical'}\n   */\n  orientation = 'data-orientation',\n}\n"
  },
  {
    "path": "packages/react/src/tooltip/arrow/TooltipArrow.test.tsx",
    "content": "import { Tooltip } from '@base-ui/react/tooltip';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Tooltip.Arrow />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Tooltip.Arrow />, () => ({\n    refInstanceof: window.Element,\n    render(node) {\n      return render(\n        <Tooltip.Root open>\n          <Tooltip.Portal>\n            <Tooltip.Positioner>\n              <Tooltip.Popup>{node}</Tooltip.Popup>\n            </Tooltip.Positioner>\n          </Tooltip.Portal>\n        </Tooltip.Root>,\n      );\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/tooltip/arrow/TooltipArrow.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useTooltipPositionerContext } from '../positioner/TooltipPositionerContext';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport type { Side, Align } from '../../utils/useAnchorPositioning';\nimport { popupStateMapping } from '../../utils/popupStateMapping';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useTooltipRootContext } from '../root/TooltipRootContext';\n\n/**\n * Displays an element positioned against the tooltip anchor.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Tooltip](https://base-ui.com/react/components/tooltip)\n */\nexport const TooltipArrow = React.forwardRef(function TooltipArrow(\n  componentProps: TooltipArrow.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { className, render, ...elementProps } = componentProps;\n  const store = useTooltipRootContext();\n\n  const instantType = store.useState('instantType');\n\n  const { open, arrowRef, side, align, arrowUncentered, arrowStyles } =\n    useTooltipPositionerContext();\n\n  const state: TooltipArrowState = {\n    open,\n    side,\n    align,\n    uncentered: arrowUncentered,\n    instant: instantType,\n  };\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: [forwardedRef, arrowRef],\n    props: [{ style: arrowStyles, 'aria-hidden': true }, elementProps],\n    stateAttributesMapping: popupStateMapping,\n  });\n\n  return element;\n});\n\nexport interface TooltipArrowState {\n  /**\n   * Whether the tooltip is currently open.\n   */\n  open: boolean;\n  /**\n   * The side of the anchor the component is placed on.\n   */\n  side: Side;\n  /**\n   * The alignment of the component relative to the anchor.\n   */\n  align: Align;\n  /**\n   * Whether the arrow cannot be centered on the anchor.\n   */\n  uncentered: boolean;\n  /**\n   * Whether transitions should be skipped.\n   */\n  instant: 'delay' | 'dismiss' | 'focus' | undefined;\n}\n\nexport interface TooltipArrowProps extends BaseUIComponentProps<'div', TooltipArrowState> {}\n\nexport namespace TooltipArrow {\n  export type State = TooltipArrowState;\n  export type Props = TooltipArrowProps;\n}\n"
  },
  {
    "path": "packages/react/src/tooltip/arrow/TooltipArrowDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum TooltipArrowDataAttributes {\n  /**\n   * Present when the tooltip is open.\n   */\n  open = CommonPopupDataAttributes.open,\n  /**\n   * Present when the tooltip is closed.\n   */\n  closed = CommonPopupDataAttributes.closed,\n  /**\n   * Indicates which side the popup is positioned relative to the trigger.\n   * @type {'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'}\n   */\n  side = CommonPopupDataAttributes.side,\n  /**\n   * Indicates how the popup is aligned relative to specified side.\n   * @type {'start' | 'center' | 'end'}\n   */\n  align = CommonPopupDataAttributes.align,\n  /**\n   * Present when the tooltip arrow is uncentered.\n   */\n  uncentered = 'data-uncentered',\n  /**\n   * Present if animations should be instant.\n   * @type {'delay' | 'dismiss' | 'focus'}\n   */\n  instant = 'data-instant',\n}\n"
  },
  {
    "path": "packages/react/src/tooltip/index.parts.ts",
    "content": "export { TooltipRoot as Root } from './root/TooltipRoot';\nexport { TooltipTrigger as Trigger } from './trigger/TooltipTrigger';\nexport { TooltipPortal as Portal } from './portal/TooltipPortal';\nexport { TooltipPositioner as Positioner } from './positioner/TooltipPositioner';\nexport { TooltipPopup as Popup } from './popup/TooltipPopup';\nexport { TooltipArrow as Arrow } from './arrow/TooltipArrow';\nexport { TooltipProvider as Provider } from './provider/TooltipProvider';\nexport { TooltipViewport as Viewport } from './viewport/TooltipViewport';\nexport {\n  createTooltipHandle as createHandle,\n  TooltipHandle as Handle,\n} from './store/TooltipHandle';\n"
  },
  {
    "path": "packages/react/src/tooltip/index.ts",
    "content": "export * as Tooltip from './index.parts';\n\nexport type * from './provider/TooltipProvider';\nexport type * from './root/TooltipRoot';\nexport type * from './trigger/TooltipTrigger';\nexport type * from './portal/TooltipPortal';\nexport type * from './positioner/TooltipPositioner';\nexport type * from './popup/TooltipPopup';\nexport type * from './arrow/TooltipArrow';\n"
  },
  {
    "path": "packages/react/src/tooltip/popup/TooltipPopup.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { Tooltip } from '@base-ui/react/tooltip';\nimport { screen } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Tooltip.Popup />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Tooltip.Popup />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <Tooltip.Root open>\n          <Tooltip.Portal>\n            <Tooltip.Positioner>{node}</Tooltip.Positioner>\n          </Tooltip.Portal>\n        </Tooltip.Root>,\n      );\n    },\n  }));\n\n  it('should render the children', async () => {\n    await render(\n      <Tooltip.Root open>\n        <Tooltip.Portal>\n          <Tooltip.Positioner>\n            <Tooltip.Popup>Content</Tooltip.Popup>\n          </Tooltip.Positioner>\n        </Tooltip.Portal>\n      </Tooltip.Root>,\n    );\n\n    expect(screen.getByText('Content')).not.toBe(null);\n  });\n});\n"
  },
  {
    "path": "packages/react/src/tooltip/popup/TooltipPopup.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useTooltipRootContext } from '../root/TooltipRootContext';\nimport { useTooltipPositionerContext } from '../positioner/TooltipPositionerContext';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport type { Align, Side } from '../../utils/useAnchorPositioning';\nimport type { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport { popupStateMapping as baseMapping } from '../../utils/popupStateMapping';\nimport type { TransitionStatus } from '../../utils/useTransitionStatus';\nimport { transitionStatusMapping } from '../../utils/stateAttributesMapping';\nimport { useOpenChangeComplete } from '../../utils/useOpenChangeComplete';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { getDisabledMountTransitionStyles } from '../../utils/getDisabledMountTransitionStyles';\nimport { useHoverFloatingInteraction } from '../../floating-ui-react';\n\nconst stateAttributesMapping: StateAttributesMapping<TooltipPopupState> = {\n  ...baseMapping,\n  ...transitionStatusMapping,\n};\n\n/**\n * A container for the tooltip contents.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Tooltip](https://base-ui.com/react/components/tooltip)\n */\nexport const TooltipPopup = React.forwardRef(function TooltipPopup(\n  componentProps: TooltipPopup.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { className, render, ...elementProps } = componentProps;\n\n  const store = useTooltipRootContext();\n  const { side, align } = useTooltipPositionerContext();\n\n  const open = store.useState('open');\n  const instantType = store.useState('instantType');\n  const transitionStatus = store.useState('transitionStatus');\n  const popupProps = store.useState('popupProps');\n  const floatingContext = store.useState('floatingRootContext');\n\n  useOpenChangeComplete({\n    open,\n    ref: store.context.popupRef,\n    onComplete() {\n      if (open) {\n        store.context.onOpenChangeComplete?.(true);\n      }\n    },\n  });\n\n  const disabled = store.useState('disabled');\n  const closeDelay = store.useState('closeDelay');\n\n  useHoverFloatingInteraction(floatingContext, {\n    enabled: !disabled,\n    closeDelay,\n  });\n\n  const state: TooltipPopupState = {\n    open,\n    side,\n    align,\n    instant: instantType,\n    transitionStatus,\n  };\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    ref: [forwardedRef, store.context.popupRef, store.useStateSetter('popupElement')],\n    props: [popupProps, getDisabledMountTransitionStyles(transitionStatus), elementProps],\n    stateAttributesMapping,\n  });\n\n  return element;\n});\n\nexport interface TooltipPopupState {\n  /**\n   * Whether the tooltip is currently open.\n   */\n  open: boolean;\n  /**\n   * The side of the anchor the component is placed on.\n   */\n  side: Side;\n  /**\n   * The alignment of the component relative to the anchor.\n   */\n  align: Align;\n  /**\n   * Whether transitions should be skipped.\n   */\n  instant: 'delay' | 'focus' | 'dismiss' | undefined;\n  /**\n   * The transition status of the component.\n   */\n  transitionStatus: TransitionStatus;\n}\n\nexport interface TooltipPopupProps extends BaseUIComponentProps<'div', TooltipPopupState> {}\n\nexport namespace TooltipPopup {\n  export type State = TooltipPopupState;\n  export type Props = TooltipPopupProps;\n}\n"
  },
  {
    "path": "packages/react/src/tooltip/popup/TooltipPopupDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum TooltipPopupDataAttributes {\n  /**\n   * Present when the tooltip is open.\n   */\n  open = CommonPopupDataAttributes.open,\n  /**\n   * Present when the tooltip is closed.\n   */\n  closed = CommonPopupDataAttributes.closed,\n  /**\n   * Present when the tooltip is animating in.\n   */\n  startingStyle = CommonPopupDataAttributes.startingStyle,\n  /**\n   * Present when the tooltip is animating out.\n   */\n  endingStyle = CommonPopupDataAttributes.endingStyle,\n  /**\n   * Indicates which side the popup is positioned relative to the trigger.\n   * @type {'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'}\n   */\n  side = CommonPopupDataAttributes.side,\n  /**\n   * Indicates how the popup is aligned relative to specified side.\n   * @type {'start' | 'center' | 'end'}\n   */\n  align = CommonPopupDataAttributes.align,\n  /**\n   * Present if animations should be instant.\n   * @type {'delay' | 'dismiss' | 'focus'}\n   */\n  instant = 'data-instant',\n}\n"
  },
  {
    "path": "packages/react/src/tooltip/portal/TooltipPortal.test.tsx",
    "content": "import * as React from 'react';\nimport { Tooltip } from '@base-ui/react/tooltip';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Tooltip.Portal />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Tooltip.Portal />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(<Tooltip.Root open>{node}</Tooltip.Root>);\n    },\n  }));\n});\n"
  },
  {
    "path": "packages/react/src/tooltip/portal/TooltipPortal.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useTooltipRootContext } from '../root/TooltipRootContext';\nimport { TooltipPortalContext } from './TooltipPortalContext';\nimport { FloatingPortalLite } from '../../utils/FloatingPortalLite';\n\n/**\n * A portal element that moves the popup to a different part of the DOM.\n * By default, the portal element is appended to `<body>`.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Tooltip](https://base-ui.com/react/components/tooltip)\n */\nexport const TooltipPortal = React.forwardRef(function TooltipPortal(\n  props: TooltipPortal.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { keepMounted = false, ...portalProps } = props;\n\n  const store = useTooltipRootContext();\n  const mounted = store.useState('mounted');\n\n  const shouldRender = mounted || keepMounted;\n  if (!shouldRender) {\n    return null;\n  }\n\n  return (\n    <TooltipPortalContext.Provider value={keepMounted}>\n      <FloatingPortalLite ref={forwardedRef} {...portalProps} />\n    </TooltipPortalContext.Provider>\n  );\n});\n\nexport interface TooltipPortalState {}\n\nexport interface TooltipPortalProps extends FloatingPortalLite.Props<TooltipPortalState> {\n  /**\n   * Whether to keep the portal mounted in the DOM while the popup is hidden.\n   * @default false\n   */\n  keepMounted?: boolean | undefined;\n}\n\nexport namespace TooltipPortal {\n  export type State = TooltipPortalState;\n  export type Props = TooltipPortalProps;\n}\n"
  },
  {
    "path": "packages/react/src/tooltip/portal/TooltipPortalContext.ts",
    "content": "'use client';\nimport * as React from 'react';\n\nexport const TooltipPortalContext = React.createContext<boolean | undefined>(undefined);\n\nexport function useTooltipPortalContext() {\n  const value = React.useContext(TooltipPortalContext);\n  if (value === undefined) {\n    throw new Error('Base UI: <Tooltip.Portal> is missing.');\n  }\n  return value;\n}\n"
  },
  {
    "path": "packages/react/src/tooltip/positioner/TooltipPositioner.spec.tsx",
    "content": "import { Tooltip } from '@base-ui/react';\n\n// @ts-expect-error - `keepMounted` should not be available\n<Tooltip.Positioner keepMounted />;\n"
  },
  {
    "path": "packages/react/src/tooltip/positioner/TooltipPositioner.test.tsx",
    "content": "import { expect } from 'vitest';\nimport * as React from 'react';\nimport { Tooltip } from '@base-ui/react/tooltip';\nimport { screen, waitFor } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance, isJSDOM } from '#test-utils';\n\nconst Trigger = React.forwardRef(function Trigger(\n  props: Tooltip.Trigger.Props,\n  ref: React.ForwardedRef<any>,\n) {\n  return <Tooltip.Trigger {...props} ref={ref} render={<div />} />;\n});\n\ndescribe('<Tooltip.Positioner />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Tooltip.Positioner />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <Tooltip.Root open>\n          <Tooltip.Portal>{node}</Tooltip.Portal>\n        </Tooltip.Root>,\n      );\n    },\n  }));\n\n  const baselineX = 10;\n  const baselineY = 36;\n  const popupWidth = 52;\n  const popupHeight = 24;\n  const anchorWidth = 72;\n  const anchorHeight = 36;\n  const triggerStyle = { width: anchorWidth, height: anchorHeight };\n  const popupStyle = { width: popupWidth, height: popupHeight };\n\n  describe.skipIf(isJSDOM)('prop: sideOffset', () => {\n    it('offsets the side when a number is specified', async () => {\n      const sideOffset = 7;\n      await render(\n        <Tooltip.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Tooltip.Portal>\n            <Tooltip.Positioner data-testid=\"positioner\" sideOffset={sideOffset}>\n              <Tooltip.Popup style={popupStyle}>Popup</Tooltip.Popup>\n            </Tooltip.Positioner>\n          </Tooltip.Portal>\n        </Tooltip.Root>,\n      );\n\n      expect(screen.getByTestId('positioner').getBoundingClientRect()).toMatchObject({\n        x: baselineX,\n        y: baselineY + sideOffset,\n      });\n    });\n\n    it('offsets the side when a function is specified', async () => {\n      await render(\n        <Tooltip.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Tooltip.Portal>\n            <Tooltip.Positioner\n              data-testid=\"positioner\"\n              sideOffset={(data) => data.positioner.width + data.anchor.width}\n            >\n              <Tooltip.Popup style={popupStyle}>Popup</Tooltip.Popup>\n            </Tooltip.Positioner>\n          </Tooltip.Portal>\n        </Tooltip.Root>,\n      );\n\n      expect(screen.getByTestId('positioner').getBoundingClientRect()).toMatchObject({\n        x: baselineX,\n        y: baselineY + popupWidth + anchorWidth,\n      });\n    });\n\n    it('can read the latest side inside sideOffset', async () => {\n      let side = 'none';\n      await render(\n        <Tooltip.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Tooltip.Portal>\n            <Tooltip.Positioner\n              side=\"left\"\n              data-testid=\"positioner\"\n              sideOffset={(data) => {\n                side = data.side;\n                return 0;\n              }}\n            >\n              <Tooltip.Popup style={popupStyle}>Popup</Tooltip.Popup>\n            </Tooltip.Positioner>\n          </Tooltip.Portal>\n        </Tooltip.Root>,\n      );\n\n      // correctly flips the side in the browser\n      expect(side).toBe('right');\n    });\n\n    it('can read the latest align inside sideOffset', async () => {\n      let align = 'none';\n      await render(\n        <Tooltip.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Tooltip.Portal>\n            <Tooltip.Positioner\n              side=\"right\"\n              align=\"start\"\n              data-testid=\"positioner\"\n              sideOffset={(data) => {\n                align = data.align;\n                return 0;\n              }}\n            >\n              <Tooltip.Popup style={popupStyle}>Popup</Tooltip.Popup>\n            </Tooltip.Positioner>\n          </Tooltip.Portal>\n        </Tooltip.Root>,\n      );\n\n      // correctly flips the align in the browser\n      expect(align).toBe('end');\n    });\n\n    it('reads logical side inside sideOffset', async () => {\n      let side = 'none';\n      await render(\n        <Tooltip.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Tooltip.Portal>\n            <Tooltip.Positioner\n              side=\"inline-start\"\n              data-testid=\"positioner\"\n              sideOffset={(data) => {\n                side = data.side;\n                return 0;\n              }}\n            >\n              <Tooltip.Popup style={popupStyle}>Popup</Tooltip.Popup>\n            </Tooltip.Positioner>\n          </Tooltip.Portal>\n        </Tooltip.Root>,\n      );\n\n      // correctly flips the side in the browser\n      expect(side).toBe('inline-end');\n    });\n  });\n\n  describe.skipIf(isJSDOM)('prop: alignOffset', () => {\n    it('offsets the align when a number is specified', async () => {\n      const alignOffset = 7;\n      await render(\n        <Tooltip.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Tooltip.Portal>\n            <Tooltip.Positioner data-testid=\"positioner\" alignOffset={alignOffset}>\n              <Tooltip.Popup style={popupStyle}>Popup</Tooltip.Popup>\n            </Tooltip.Positioner>\n          </Tooltip.Portal>\n        </Tooltip.Root>,\n      );\n\n      expect(screen.getByTestId('positioner').getBoundingClientRect()).toMatchObject({\n        x: baselineX + alignOffset,\n        y: baselineY,\n      });\n    });\n\n    it('offsets the align when a function is specified', async () => {\n      await render(\n        <Tooltip.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Tooltip.Portal>\n            <Tooltip.Positioner\n              data-testid=\"positioner\"\n              alignOffset={(data) => data.positioner.width}\n            >\n              <Tooltip.Popup style={popupStyle}>Popup</Tooltip.Popup>\n            </Tooltip.Positioner>\n          </Tooltip.Portal>\n        </Tooltip.Root>,\n      );\n\n      expect(screen.getByTestId('positioner').getBoundingClientRect()).toMatchObject({\n        x: baselineX + popupWidth,\n        y: baselineY,\n      });\n    });\n\n    it('can read the latest side inside alignOffset', async () => {\n      let side = 'none';\n      await render(\n        <Tooltip.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Tooltip.Portal>\n            <Tooltip.Positioner\n              side=\"left\"\n              data-testid=\"positioner\"\n              alignOffset={(data) => {\n                side = data.side;\n                return 0;\n              }}\n            >\n              <Tooltip.Popup style={popupStyle}>Popup</Tooltip.Popup>\n            </Tooltip.Positioner>\n          </Tooltip.Portal>\n        </Tooltip.Root>,\n      );\n\n      // correctly flips the side in the browser\n      expect(side).toBe('right');\n    });\n\n    it('can read the latest align inside alignOffset', async () => {\n      let align = 'none';\n      await render(\n        <Tooltip.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Tooltip.Portal>\n            <Tooltip.Positioner\n              side=\"right\"\n              align=\"start\"\n              data-testid=\"positioner\"\n              alignOffset={(data) => {\n                align = data.align;\n                return 0;\n              }}\n            >\n              <Tooltip.Popup style={popupStyle}>Popup</Tooltip.Popup>\n            </Tooltip.Positioner>\n          </Tooltip.Portal>\n        </Tooltip.Root>,\n      );\n\n      // correctly flips the align in the browser\n      expect(align).toBe('end');\n    });\n\n    it('reads logical side inside alignOffset', async () => {\n      let side = 'none';\n      await render(\n        <Tooltip.Root open>\n          <Trigger style={triggerStyle}>Trigger</Trigger>\n          <Tooltip.Portal>\n            <Tooltip.Positioner\n              side=\"inline-start\"\n              data-testid=\"positioner\"\n              alignOffset={(data) => {\n                side = data.side;\n                return 0;\n              }}\n            >\n              <Tooltip.Popup style={popupStyle}>Popup</Tooltip.Popup>\n            </Tooltip.Positioner>\n          </Tooltip.Portal>\n        </Tooltip.Root>,\n      );\n\n      // correctly flips the side in the browser\n      expect(side).toBe('inline-end');\n    });\n  });\n\n  it.skipIf(isJSDOM)('uses transform positioning without Viewport', async () => {\n    await render(\n      <Tooltip.Root open>\n        <Trigger style={triggerStyle}>Trigger</Trigger>\n        <Tooltip.Portal>\n          <Tooltip.Positioner data-testid=\"positioner\">\n            <Tooltip.Popup style={popupStyle}>Popup</Tooltip.Popup>\n          </Tooltip.Positioner>\n        </Tooltip.Portal>\n      </Tooltip.Root>,\n    );\n\n    const positioner = screen.getByTestId('positioner');\n    expect(positioner.style.transform).not.toBe('');\n  });\n\n  it.skipIf(isJSDOM)('uses top/left positioning with Viewport', async () => {\n    await render(\n      <Tooltip.Root open>\n        <Trigger style={triggerStyle}>Trigger</Trigger>\n        <Tooltip.Portal>\n          <Tooltip.Positioner data-testid=\"positioner\">\n            <Tooltip.Popup style={popupStyle}>\n              <Tooltip.Viewport>Popup</Tooltip.Viewport>\n            </Tooltip.Popup>\n          </Tooltip.Positioner>\n        </Tooltip.Portal>\n      </Tooltip.Root>,\n    );\n\n    const positioner = screen.getByTestId('positioner');\n    await waitFor(() => {\n      expect(positioner.style.transform).toBe('');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/tooltip/positioner/TooltipPositioner.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useTooltipRootContext } from '../root/TooltipRootContext';\nimport { TooltipPositionerContext } from './TooltipPositionerContext';\nimport {\n  useAnchorPositioning,\n  type Side,\n  type Align,\n  type UseAnchorPositioningSharedParameters,\n} from '../../utils/useAnchorPositioning';\nimport type { BaseUIComponentProps, HTMLProps } from '../../utils/types';\nimport { popupStateMapping } from '../../utils/popupStateMapping';\nimport { useTooltipPortalContext } from '../portal/TooltipPortalContext';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { POPUP_COLLISION_AVOIDANCE } from '../../utils/constants';\nimport { adaptiveOrigin } from '../../utils/adaptiveOriginMiddleware';\nimport { getDisabledMountTransitionStyles } from '../../utils/getDisabledMountTransitionStyles';\n\n/**\n * Positions the tooltip against the trigger.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Tooltip](https://base-ui.com/react/components/tooltip)\n */\nexport const TooltipPositioner = React.forwardRef(function TooltipPositioner(\n  componentProps: TooltipPositioner.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const {\n    render,\n    className,\n    anchor,\n    positionMethod = 'absolute',\n    side = 'top',\n    align = 'center',\n    sideOffset = 0,\n    alignOffset = 0,\n    collisionBoundary = 'clipping-ancestors',\n    collisionPadding = 5,\n    arrowPadding = 5,\n    sticky = false,\n    disableAnchorTracking = false,\n    collisionAvoidance = POPUP_COLLISION_AVOIDANCE,\n    ...elementProps\n  } = componentProps;\n\n  const store = useTooltipRootContext();\n  const keepMounted = useTooltipPortalContext();\n\n  const open = store.useState('open');\n  const mounted = store.useState('mounted');\n  const trackCursorAxis = store.useState('trackCursorAxis');\n  const disableHoverablePopup = store.useState('disableHoverablePopup');\n  const floatingRootContext = store.useState('floatingRootContext');\n  const instantType = store.useState('instantType');\n  const transitionStatus = store.useState('transitionStatus');\n  const hasViewport = store.useState('hasViewport');\n\n  const positioning = useAnchorPositioning({\n    anchor,\n    positionMethod,\n    floatingRootContext,\n    mounted,\n    side,\n    sideOffset,\n    align,\n    alignOffset,\n    collisionBoundary,\n    collisionPadding,\n    sticky,\n    arrowPadding,\n    disableAnchorTracking,\n    keepMounted,\n    collisionAvoidance,\n    adaptiveOrigin: hasViewport ? adaptiveOrigin : undefined,\n  });\n\n  const defaultProps: HTMLProps = React.useMemo(() => {\n    const hiddenStyles: React.CSSProperties = {};\n\n    if (!open || trackCursorAxis === 'both' || disableHoverablePopup) {\n      hiddenStyles.pointerEvents = 'none';\n    }\n\n    return {\n      role: 'presentation',\n      hidden: !mounted,\n      style: {\n        ...positioning.positionerStyles,\n        ...hiddenStyles,\n      },\n    };\n  }, [open, trackCursorAxis, disableHoverablePopup, mounted, positioning.positionerStyles]);\n\n  const state: TooltipPositionerState = React.useMemo(\n    () => ({\n      open,\n      side: positioning.side,\n      align: positioning.align,\n      anchorHidden: positioning.anchorHidden,\n      instant: trackCursorAxis !== 'none' ? 'tracking-cursor' : instantType,\n    }),\n    [\n      open,\n      positioning.side,\n      positioning.align,\n      positioning.anchorHidden,\n      trackCursorAxis,\n      instantType,\n    ],\n  );\n\n  const contextValue: TooltipPositionerContext = React.useMemo(\n    () => ({\n      ...state,\n      arrowRef: positioning.arrowRef,\n      arrowStyles: positioning.arrowStyles,\n      arrowUncentered: positioning.arrowUncentered,\n    }),\n    [state, positioning.arrowRef, positioning.arrowStyles, positioning.arrowUncentered],\n  );\n\n  const element = useRenderElement('div', componentProps, {\n    state,\n    props: [defaultProps, getDisabledMountTransitionStyles(transitionStatus), elementProps],\n    ref: [forwardedRef, store.useStateSetter('positionerElement')],\n    stateAttributesMapping: popupStateMapping,\n  });\n\n  return (\n    <TooltipPositionerContext.Provider value={contextValue}>\n      {element}\n    </TooltipPositionerContext.Provider>\n  );\n});\n\nexport interface TooltipPositionerState {\n  /**\n   * Whether the tooltip is currently open.\n   */\n  open: boolean;\n  /**\n   * The side of the anchor the component is placed on.\n   */\n  side: Side;\n  /**\n   * The alignment of the component relative to the anchor.\n   */\n  align: Align;\n  /**\n   * Whether the anchor element is hidden.\n   */\n  anchorHidden: boolean;\n  /**\n   * Whether CSS transitions should be disabled.\n   */\n  instant: string | undefined;\n}\n\nexport interface TooltipPositionerProps\n  extends\n    BaseUIComponentProps<'div', TooltipPositionerState>,\n    Omit<UseAnchorPositioningSharedParameters, 'side'> {\n  /**\n   * Which side of the anchor element to align the popup against.\n   * May automatically change to avoid collisions.\n   * @default 'top'\n   */\n  side?: Side | undefined;\n}\n\nexport namespace TooltipPositioner {\n  export type State = TooltipPositionerState;\n  export type Props = TooltipPositionerProps;\n}\n"
  },
  {
    "path": "packages/react/src/tooltip/positioner/TooltipPositionerContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport type { Side, Align } from '../../utils/useAnchorPositioning';\n\nexport interface TooltipPositionerContext {\n  open: boolean;\n  side: Side;\n  align: Align;\n  arrowRef: React.RefObject<Element | null>;\n  arrowUncentered: boolean;\n  arrowStyles: React.CSSProperties;\n}\n\nexport const TooltipPositionerContext = React.createContext<TooltipPositionerContext | undefined>(\n  undefined,\n);\n\nexport function useTooltipPositionerContext() {\n  const context = React.useContext(TooltipPositionerContext);\n  if (context === undefined) {\n    throw new Error(\n      'Base UI: TooltipPositionerContext is missing. TooltipPositioner parts must be placed within <Tooltip.Positioner>.',\n    );\n  }\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/tooltip/positioner/TooltipPositionerCssVars.ts",
    "content": "export enum TooltipPositionerCssVars {\n  /**\n   * The available width between the trigger and the edge of the viewport.\n   * @type {number}\n   */\n  availableWidth = '--available-width',\n  /**\n   * The available height between the trigger and the edge of the viewport.\n   * @type {number}\n   */\n  availableHeight = '--available-height',\n  /**\n   * The anchor's width.\n   * @type {number}\n   */\n  anchorWidth = '--anchor-width',\n  /**\n   * The anchor's height.\n   * @type {number}\n   */\n  anchorHeight = '--anchor-height',\n  /**\n   * The coordinates that this element is anchored to. Used for animations and transitions.\n   * @type {string}\n   */\n  transformOrigin = '--transform-origin',\n}\n"
  },
  {
    "path": "packages/react/src/tooltip/positioner/TooltipPositionerDataAttributes.ts",
    "content": "import { CommonPopupDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum TooltipPositionerDataAttributes {\n  /**\n   * Present when the tooltip is open.\n   */\n  open = CommonPopupDataAttributes.open,\n  /**\n   * Present when the tooltip is closed.\n   */\n  closed = CommonPopupDataAttributes.closed,\n  /**\n   * Present when the anchor is hidden.\n   */\n  anchorHidden = CommonPopupDataAttributes.anchorHidden,\n  /**\n   * Indicates which side the popup is positioned relative to the trigger.\n   * @type {'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'}\n   */\n  side = CommonPopupDataAttributes.side,\n  /**\n   * Indicates how the popup is aligned relative to specified side.\n   * @type {'start' | 'center' | 'end'}\n   */\n  align = CommonPopupDataAttributes.align,\n}\n"
  },
  {
    "path": "packages/react/src/tooltip/provider/TooltipProvider.test.tsx",
    "content": "import { expect } from 'vitest';\nimport { Tooltip } from '@base-ui/react/tooltip';\nimport { screen, fireEvent, flushMicrotasks } from '@mui/internal-test-utils';\nimport { createRenderer } from '#test-utils';\nimport { OPEN_DELAY } from '../utils/constants';\n\ndescribe('<Tooltip.Provider />', () => {\n  const { render, clock } = createRenderer();\n\n  describe('prop: delay', () => {\n    clock.withFakeTimers();\n\n    it('waits for the delay before showing the tooltip', async () => {\n      await render(\n        <Tooltip.Provider delay={10_000}>\n          <Tooltip.Root>\n            <Tooltip.Trigger />\n            <Tooltip.Portal>\n              <Tooltip.Positioner>\n                <Tooltip.Popup>Content</Tooltip.Popup>\n              </Tooltip.Positioner>\n            </Tooltip.Portal>\n          </Tooltip.Root>\n        </Tooltip.Provider>,\n      );\n\n      const trigger = screen.getByRole('button');\n\n      fireEvent.mouseEnter(trigger);\n      fireEvent.mouseMove(trigger);\n\n      expect(screen.queryByText('Content')).toBe(null);\n\n      clock.tick(1_000);\n\n      expect(screen.queryByText('Content')).toBe(null);\n\n      clock.tick(9_000);\n\n      await flushMicrotasks();\n\n      expect(screen.queryByText('Content')).not.toBe(null);\n    });\n\n    it('respects delay=0', async () => {\n      await render(\n        <Tooltip.Provider delay={0}>\n          <Tooltip.Root>\n            <Tooltip.Trigger />\n            <Tooltip.Portal>\n              <Tooltip.Positioner>\n                <Tooltip.Popup>Content</Tooltip.Popup>\n              </Tooltip.Positioner>\n            </Tooltip.Portal>\n          </Tooltip.Root>\n        </Tooltip.Provider>,\n      );\n\n      const trigger = screen.getByRole('button');\n\n      fireEvent.mouseEnter(trigger);\n      fireEvent.mouseMove(trigger);\n\n      clock.tick(0);\n\n      expect(screen.queryByText('Content')).not.toBe(null);\n    });\n\n    it('respects trigger delay prop over provider delay prop', async () => {\n      await render(\n        <Tooltip.Provider delay={10}>\n          <Tooltip.Root>\n            <Tooltip.Trigger delay={100} />\n            <Tooltip.Portal>\n              <Tooltip.Positioner>\n                <Tooltip.Popup>Content</Tooltip.Popup>\n              </Tooltip.Positioner>\n            </Tooltip.Portal>\n          </Tooltip.Root>\n        </Tooltip.Provider>,\n      );\n\n      const trigger = screen.getByRole('button');\n\n      fireEvent.mouseEnter(trigger);\n      fireEvent.mouseMove(trigger);\n\n      expect(screen.queryByText('Content')).toBe(null);\n\n      clock.tick(99);\n\n      expect(screen.queryByText('Content')).toBe(null);\n\n      clock.tick(1);\n\n      await flushMicrotasks();\n\n      expect(screen.queryByText('Content')).not.toBe(null);\n    });\n  });\n\n  describe('prop: closeDelay', () => {\n    clock.withFakeTimers();\n\n    it('waits for the closeDelay before hiding the tooltip', async () => {\n      await render(\n        <Tooltip.Provider closeDelay={400}>\n          <Tooltip.Root>\n            <Tooltip.Trigger />\n            <Tooltip.Portal>\n              <Tooltip.Positioner>\n                <Tooltip.Popup>Content</Tooltip.Popup>\n              </Tooltip.Positioner>\n            </Tooltip.Portal>\n          </Tooltip.Root>\n        </Tooltip.Provider>,\n      );\n\n      const trigger = screen.getByRole('button');\n\n      fireEvent.mouseEnter(trigger);\n      fireEvent.mouseMove(trigger);\n\n      clock.tick(OPEN_DELAY);\n\n      await flushMicrotasks();\n\n      expect(screen.queryByText('Content')).not.toBe(null);\n\n      fireEvent.mouseLeave(trigger);\n\n      clock.tick(300);\n\n      expect(screen.queryByText('Content')).not.toBe(null);\n\n      clock.tick(300);\n\n      expect(screen.queryByText('Content')).toBe(null);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/tooltip/provider/TooltipProvider.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { FloatingDelayGroup } from '../../floating-ui-react';\nimport { TooltipProviderContext } from './TooltipProviderContext';\n\n/**\n * Provides a shared delay for multiple tooltips. The grouping logic ensures that\n * once a tooltip becomes visible, the adjacent tooltips will be shown instantly.\n *\n * Documentation: [Base UI Tooltip](https://base-ui.com/react/components/tooltip)\n */\nexport const TooltipProvider: React.FC<TooltipProvider.Props> = function TooltipProvider(props) {\n  const { delay, closeDelay, timeout = 400 } = props;\n\n  const contextValue: TooltipProviderContext = React.useMemo(\n    () => ({\n      delay,\n      closeDelay,\n    }),\n    [delay, closeDelay],\n  );\n\n  const delayValue = React.useMemo(() => ({ open: delay, close: closeDelay }), [delay, closeDelay]);\n\n  return (\n    <TooltipProviderContext.Provider value={contextValue}>\n      <FloatingDelayGroup delay={delayValue} timeoutMs={timeout}>\n        {props.children}\n      </FloatingDelayGroup>\n    </TooltipProviderContext.Provider>\n  );\n};\n\nexport interface TooltipProviderState {}\n\nexport interface TooltipProviderProps {\n  children?: React.ReactNode;\n  /**\n   * How long to wait before opening a tooltip. Specified in milliseconds.\n   */\n  delay?: number | undefined;\n  /**\n   * How long to wait before closing a tooltip. Specified in milliseconds.\n   */\n  closeDelay?: number | undefined;\n  /**\n   * Another tooltip will open instantly if the previous tooltip\n   * is closed within this timeout. Specified in milliseconds.\n   * @default 400\n   */\n  timeout?: number | undefined;\n}\n\nexport namespace TooltipProvider {\n  export type State = TooltipProviderState;\n  export type Props = TooltipProviderProps;\n}\n"
  },
  {
    "path": "packages/react/src/tooltip/provider/TooltipProviderContext.ts",
    "content": "'use client';\nimport * as React from 'react';\n\nexport interface TooltipProviderContext {\n  delay: number | undefined;\n  closeDelay: number | undefined;\n}\n\nexport const TooltipProviderContext = React.createContext<TooltipProviderContext | undefined>(\n  undefined,\n);\n\nexport function useTooltipProviderContext(): TooltipProviderContext | undefined {\n  return React.useContext(TooltipProviderContext);\n}\n"
  },
  {
    "path": "packages/react/src/tooltip/root/TooltipRoot.detached-triggers.test.tsx",
    "content": "import { vi, expect } from 'vitest';\nimport * as React from 'react';\nimport { createRenderer, isJSDOM } from '#test-utils';\nimport { Tooltip } from '@base-ui/react/tooltip';\nimport { screen, waitFor, randomStringValue, act, flushMicrotasks } from '@mui/internal-test-utils';\n\ndescribe('<Tooltip.Root />', () => {\n  beforeEach(async () => {\n    globalThis.BASE_UI_ANIMATIONS_DISABLED = true;\n\n    await act(async () => {\n      document.body.click();\n    });\n\n    // Wait for all tooltips to unmount\n    await waitFor(() => {\n      const tooltips = document.querySelectorAll('[data-open]');\n      expect(tooltips.length).toBe(0);\n    });\n  });\n\n  const { render } = createRenderer();\n\n  describe.skipIf(isJSDOM)('multiple triggers within Root', () => {\n    type NumberPayload = { payload: number | undefined };\n\n    it('should open the tooltip with any trigger on hover', async () => {\n      vi.spyOn(console, 'error').mockImplementation((...args) => {\n        if (args[0] === 'null') {\n          // a bug in vitest prints specific browser errors as \"null\"\n          // See https://github.com/vitest-dev/vitest/issues/9285\n          // TODO(@mui/base): debug why this test triggers \"ResizeObserver loop completed with undelivered notifications\"\n          // It seems related to @testing-library/user-event. Native vitest `userEvent` does not trigger it.\n          return;\n        }\n        console.error(...args);\n      });\n\n      const popupId = randomStringValue();\n      const { user } = await render(\n        <Tooltip.Root>\n          <input type=\"text\" aria-label=\"Initial focus\" autoFocus />\n          <Tooltip.Trigger delay={0}>Trigger 1</Tooltip.Trigger>\n          <Tooltip.Trigger delay={0}>Trigger 2</Tooltip.Trigger>\n          <Tooltip.Trigger delay={0}>Trigger 3</Tooltip.Trigger>\n\n          <Tooltip.Portal>\n            <Tooltip.Positioner>\n              <Tooltip.Popup data-testid={popupId}>Tooltip Content</Tooltip.Popup>\n            </Tooltip.Positioner>\n          </Tooltip.Portal>\n        </Tooltip.Root>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n      const trigger3 = screen.getByRole('button', { name: 'Trigger 3' });\n\n      await waitFor(() => {\n        expect(screen.queryByTestId(popupId)).toBe(null);\n      });\n\n      await user.hover(trigger1);\n      expect(screen.queryByTestId(popupId)).toBeVisible();\n      await user.hover(document.body);\n      await waitFor(() => {\n        expect(screen.queryByTestId(popupId)).toBe(null);\n      });\n\n      await user.hover(trigger2);\n      expect(screen.queryByTestId(popupId)).toBeVisible();\n      await user.hover(document.body);\n      await waitFor(() => {\n        expect(screen.queryByTestId(popupId)).toBe(null);\n      });\n\n      await user.hover(trigger3);\n      expect(screen.queryByTestId(popupId)).toBeVisible();\n      await user.hover(document.body);\n      await waitFor(() => {\n        expect(screen.queryByTestId(popupId)).toBe(null);\n      });\n    });\n\n    it('should open the tooltip with any trigger on focus', async () => {\n      await render(\n        <Tooltip.Root>\n          <Tooltip.Trigger>Trigger 1</Tooltip.Trigger>\n          <Tooltip.Trigger>Trigger 2</Tooltip.Trigger>\n          <Tooltip.Trigger>Trigger 3</Tooltip.Trigger>\n\n          <Tooltip.Portal>\n            <Tooltip.Positioner>\n              <Tooltip.Popup>Tooltip Content</Tooltip.Popup>\n            </Tooltip.Positioner>\n          </Tooltip.Portal>\n        </Tooltip.Root>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n      const trigger3 = screen.getByRole('button', { name: 'Trigger 3' });\n\n      expect(screen.queryByText('Tooltip Content')).toBe(null);\n\n      await act(async () => trigger1.focus());\n      await flushMicrotasks();\n      expect(screen.getByText('Tooltip Content')).toBeVisible();\n      await act(async () => trigger1.blur());\n      expect(screen.queryByText('Tooltip Content')).toBe(null);\n\n      await act(async () => trigger2.focus());\n      await flushMicrotasks();\n      expect(screen.getByText('Tooltip Content')).toBeVisible();\n      await act(async () => trigger2.blur());\n      expect(screen.queryByText('Tooltip Content')).toBe(null);\n\n      await act(async () => trigger3.focus());\n      await flushMicrotasks();\n      expect(screen.getByText('Tooltip Content')).toBeVisible();\n      await act(async () => trigger3.blur());\n      expect(screen.queryByText('Tooltip Content')).toBe(null);\n    });\n\n    it('should set the payload and render content based on its value', async () => {\n      const { user } = await render(\n        <Tooltip.Root>\n          {({ payload }: NumberPayload) => (\n            <React.Fragment>\n              <Tooltip.Trigger payload={1} delay={0}>\n                Trigger 1\n              </Tooltip.Trigger>\n              <Tooltip.Trigger payload={2} delay={0}>\n                Trigger 2\n              </Tooltip.Trigger>\n\n              <Tooltip.Portal>\n                <Tooltip.Positioner>\n                  <Tooltip.Popup>\n                    <span data-testid=\"content\">{payload}</span>\n                  </Tooltip.Popup>\n                </Tooltip.Positioner>\n              </Tooltip.Portal>\n            </React.Fragment>\n          )}\n        </Tooltip.Root>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n\n      await user.hover(trigger1);\n      expect(screen.getByTestId('content').textContent).toBe('1');\n\n      await user.unhover(trigger1);\n      await user.hover(trigger2);\n      expect(screen.getByTestId('content').textContent).toBe('2');\n    });\n\n    it('should reuse the popup and positioner DOM nodes when switching triggers', async () => {\n      await render(\n        <Tooltip.Root>\n          {({ payload }: NumberPayload) => (\n            <React.Fragment>\n              <Tooltip.Trigger payload={1} delay={0}>\n                Trigger 1\n              </Tooltip.Trigger>\n              <Tooltip.Trigger payload={2} delay={0}>\n                Trigger 2\n              </Tooltip.Trigger>\n\n              <Tooltip.Portal>\n                <Tooltip.Positioner data-testid=\"positioner\" key=\"pos\">\n                  <Tooltip.Popup data-testid=\"popup\" key=\"pop\">\n                    <span>{payload}</span>\n                  </Tooltip.Popup>\n                </Tooltip.Positioner>\n              </Tooltip.Portal>\n            </React.Fragment>\n          )}\n        </Tooltip.Root>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n\n      await act(async () => trigger1.focus());\n      const popupElement = screen.getByTestId('popup');\n      const positionerElement = screen.getByTestId('positioner');\n\n      await act(async () => trigger2.focus());\n      expect(screen.getByTestId('positioner')).toBe(positionerElement);\n      expect(screen.getByTestId('popup')).toBe(popupElement);\n    });\n\n    it('should allow controlling the tooltip state programmatically', async () => {\n      function Test() {\n        const [open, setOpen] = React.useState(false);\n        const [activeTrigger, setActiveTrigger] = React.useState<string | null>(null);\n\n        return (\n          <div>\n            <Tooltip.Root\n              open={open}\n              triggerId={activeTrigger}\n              onOpenChange={(nextOpen, details) => {\n                setActiveTrigger(details.trigger?.id ?? null);\n                setOpen(nextOpen);\n              }}\n            >\n              {({ payload }: NumberPayload) => (\n                <React.Fragment>\n                  <Tooltip.Trigger payload={1} id=\"trigger-1\" delay={0}>\n                    Trigger 1\n                  </Tooltip.Trigger>\n                  <Tooltip.Trigger payload={2} id=\"trigger-2\" delay={0}>\n                    Trigger 2\n                  </Tooltip.Trigger>\n\n                  <Tooltip.Portal>\n                    <Tooltip.Positioner>\n                      <Tooltip.Popup>\n                        <span data-testid=\"content\">{payload as number}</span>\n                      </Tooltip.Popup>\n                    </Tooltip.Positioner>\n                  </Tooltip.Portal>\n                </React.Fragment>\n              )}\n            </Tooltip.Root>\n            <button\n              onClick={() => {\n                setOpen(true);\n                setActiveTrigger('trigger-1');\n              }}\n            >\n              Open Trigger 1\n            </button>\n            <button\n              onClick={() => {\n                setOpen(true);\n                setActiveTrigger('trigger-2');\n              }}\n            >\n              Open Trigger 2\n            </button>\n            <button onClick={() => setOpen(false)}>Close</button>\n          </div>\n        );\n      }\n\n      const { user } = await render(<Test />);\n      await user.click(screen.getByRole('button', { name: 'Open Trigger 1' }));\n      expect(screen.getByTestId('content').textContent).toBe('1');\n      await user.click(screen.getByRole('button', { name: 'Open Trigger 2' }));\n      expect(screen.getByTestId('content').textContent).toBe('2');\n      await user.click(screen.getByRole('button', { name: 'Close' }));\n      expect(screen.queryByTestId('content')).toBe(null);\n    });\n\n    it('allows setting an initially open tooltip', async () => {\n      const testTooltip = Tooltip.createHandle<number>();\n      const triggerId = randomStringValue();\n      await render(\n        <Tooltip.Root handle={testTooltip} defaultOpen defaultTriggerId={triggerId}>\n          {({ payload }: NumberPayload) => (\n            <React.Fragment>\n              <button type=\"button\" aria-label=\"Initial focus\" autoFocus />\n              <Tooltip.Trigger handle={testTooltip} payload={1}>\n                Trigger 1\n              </Tooltip.Trigger>\n              <Tooltip.Trigger handle={testTooltip} payload={2} id={triggerId}>\n                Trigger 2\n              </Tooltip.Trigger>\n              <Tooltip.Portal>\n                <Tooltip.Positioner>\n                  <Tooltip.Popup data-testid=\"popup\">\n                    <span>{payload}</span>\n                  </Tooltip.Popup>\n                </Tooltip.Positioner>\n              </Tooltip.Portal>\n            </React.Fragment>\n          )}\n        </Tooltip.Root>,\n      );\n\n      await waitFor(() => {\n        expect(screen.getByTestId('popup').textContent).toBe('2');\n      });\n    });\n  });\n\n  describe.skipIf(isJSDOM)('multiple detached triggers', () => {\n    type NumberPayload = { payload: number | undefined };\n\n    it('should open the tooltip with any trigger on hover', async () => {\n      const testTooltip = Tooltip.createHandle();\n      const popupId = randomStringValue();\n      const { user } = await render(\n        <div>\n          <button type=\"button\" aria-label=\"Initial focus\" autoFocus />\n          <Tooltip.Trigger handle={testTooltip} delay={0}>\n            Trigger 1\n          </Tooltip.Trigger>\n          <Tooltip.Trigger handle={testTooltip} delay={0}>\n            Trigger 2\n          </Tooltip.Trigger>\n          <Tooltip.Trigger handle={testTooltip} delay={0}>\n            Trigger 3\n          </Tooltip.Trigger>\n\n          <Tooltip.Root handle={testTooltip}>\n            <Tooltip.Portal>\n              <Tooltip.Positioner>\n                <Tooltip.Popup data-testid={popupId}>Tooltip Content</Tooltip.Popup>\n              </Tooltip.Positioner>\n            </Tooltip.Portal>\n          </Tooltip.Root>\n        </div>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n      const trigger3 = screen.getByRole('button', { name: 'Trigger 3' });\n\n      await waitFor(() => {\n        expect(screen.queryByTestId(popupId)).toBe(null);\n      });\n\n      await user.hover(trigger1);\n      await waitFor(() => {\n        expect(screen.queryByTestId(popupId)).toBeVisible();\n      });\n      await user.unhover(trigger1);\n      await waitFor(() => {\n        expect(screen.queryByTestId(popupId)).toBe(null);\n      });\n\n      await user.hover(trigger2);\n      await waitFor(() => {\n        expect(screen.queryByTestId(popupId)).toBeVisible();\n      });\n      await user.unhover(trigger2);\n      await waitFor(() => {\n        expect(screen.queryByTestId(popupId)).toBe(null);\n      });\n\n      await user.hover(trigger3);\n      await waitFor(() => {\n        expect(screen.queryByTestId(popupId)).toBeVisible();\n      });\n      await user.unhover(trigger3);\n      await waitFor(() => {\n        expect(screen.queryByTestId(popupId)).toBe(null);\n      });\n    });\n\n    it('should open the tooltip with any trigger on focus', async () => {\n      const testTooltip = Tooltip.createHandle();\n      await render(\n        <div>\n          <Tooltip.Trigger handle={testTooltip}>Trigger 1</Tooltip.Trigger>\n          <Tooltip.Trigger handle={testTooltip}>Trigger 2</Tooltip.Trigger>\n          <Tooltip.Trigger handle={testTooltip}>Trigger 3</Tooltip.Trigger>\n\n          <Tooltip.Root handle={testTooltip}>\n            <Tooltip.Portal>\n              <Tooltip.Positioner>\n                <Tooltip.Popup>Tooltip Content</Tooltip.Popup>\n              </Tooltip.Positioner>\n            </Tooltip.Portal>\n          </Tooltip.Root>\n        </div>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n      const trigger3 = screen.getByRole('button', { name: 'Trigger 3' });\n\n      expect(screen.queryByText('Tooltip Content')).toBe(null);\n\n      await act(async () => trigger1.focus());\n      await flushMicrotasks();\n      expect(screen.getByText('Tooltip Content')).toBeVisible();\n      await act(async () => trigger1.blur());\n      expect(screen.queryByText('Tooltip Content')).toBe(null);\n\n      await act(async () => trigger2.focus());\n      await flushMicrotasks();\n      expect(screen.getByText('Tooltip Content')).toBeVisible();\n      await act(async () => trigger2.blur());\n      expect(screen.queryByText('Tooltip Content')).toBe(null);\n\n      await act(async () => trigger3.focus());\n      await flushMicrotasks();\n      expect(screen.getByText('Tooltip Content')).toBeVisible();\n      await act(async () => trigger3.blur());\n      expect(screen.queryByText('Tooltip Content')).toBe(null);\n    });\n\n    it('should close when focusing a disabled trigger while another trigger is open', async () => {\n      const testTooltip = Tooltip.createHandle<number>();\n      await render(\n        <div>\n          <Tooltip.Trigger handle={testTooltip} payload={1}>\n            Trigger 1\n          </Tooltip.Trigger>\n          <Tooltip.Trigger handle={testTooltip} payload={2} disabled>\n            Trigger 2\n          </Tooltip.Trigger>\n\n          <Tooltip.Root handle={testTooltip}>\n            {({ payload }: NumberPayload) => (\n              <Tooltip.Portal>\n                <Tooltip.Positioner>\n                  <Tooltip.Popup>\n                    <span data-testid=\"content\">{payload}</span>\n                  </Tooltip.Popup>\n                </Tooltip.Positioner>\n              </Tooltip.Portal>\n            )}\n          </Tooltip.Root>\n        </div>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n\n      await act(async () => trigger1.focus());\n      await flushMicrotasks();\n      expect(screen.getByTestId('content').textContent).toBe('1');\n\n      await act(async () => trigger2.focus());\n      await flushMicrotasks();\n      await waitFor(() => {\n        expect(screen.queryByTestId('content')).toBe(null);\n      });\n      expect(trigger2).not.toHaveAttribute('data-popup-open');\n    });\n\n    it('should set the payload and render content based on its value', async () => {\n      const testTooltip = Tooltip.createHandle<number>();\n      const { user } = await render(\n        <div>\n          <Tooltip.Trigger handle={testTooltip} payload={1} delay={0}>\n            Trigger 1\n          </Tooltip.Trigger>\n          <Tooltip.Trigger handle={testTooltip} payload={2} delay={0}>\n            Trigger 2\n          </Tooltip.Trigger>\n\n          <Tooltip.Root handle={testTooltip}>\n            {({ payload }: NumberPayload) => (\n              <Tooltip.Portal>\n                <Tooltip.Positioner>\n                  <Tooltip.Popup>\n                    <span data-testid=\"content\">{payload}</span>\n                  </Tooltip.Popup>\n                </Tooltip.Positioner>\n              </Tooltip.Portal>\n            )}\n          </Tooltip.Root>\n        </div>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n\n      await user.hover(trigger1);\n      expect(screen.getByTestId('content').textContent).toBe('1');\n\n      await user.unhover(trigger1);\n      await user.hover(trigger2);\n      expect(screen.getByTestId('content').textContent).toBe('2');\n    });\n\n    it('should close when hovering a disabled trigger while another trigger is open', async () => {\n      const testTooltip = Tooltip.createHandle<number>();\n      const { user } = await render(\n        <div>\n          <Tooltip.Trigger handle={testTooltip} payload={1} delay={0}>\n            Trigger 1\n          </Tooltip.Trigger>\n          <Tooltip.Trigger handle={testTooltip} payload={2} disabled>\n            Trigger 2\n          </Tooltip.Trigger>\n\n          <Tooltip.Root handle={testTooltip}>\n            {({ payload }: NumberPayload) => (\n              <Tooltip.Portal>\n                <Tooltip.Positioner>\n                  <Tooltip.Popup>\n                    <span data-testid=\"content\">{payload}</span>\n                  </Tooltip.Popup>\n                </Tooltip.Positioner>\n              </Tooltip.Portal>\n            )}\n          </Tooltip.Root>\n        </div>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n\n      await user.hover(trigger1);\n      await waitFor(() => {\n        expect(screen.getByTestId('content').textContent).toBe('1');\n      });\n\n      await user.hover(trigger2);\n      await waitFor(() => {\n        expect(screen.queryByTestId('content')).toBe(null);\n      });\n      expect(trigger2).not.toHaveAttribute('data-popup-open');\n    });\n\n    it('should switch to a rendered disabled button trigger when trigger hover is enabled', async () => {\n      const testTooltip = Tooltip.createHandle<number>();\n      const { user } = await render(\n        <div>\n          <Tooltip.Trigger handle={testTooltip} payload={1} delay={0}>\n            Trigger 1\n          </Tooltip.Trigger>\n          <Tooltip.Trigger\n            handle={testTooltip}\n            payload={2}\n            delay={0}\n            render={\n              <button type=\"button\" disabled>\n                Trigger 2\n              </button>\n            }\n          />\n\n          <Tooltip.Root handle={testTooltip}>\n            {({ payload }: NumberPayload) => (\n              <Tooltip.Portal>\n                <Tooltip.Positioner>\n                  <Tooltip.Popup>\n                    <span data-testid=\"content\">{payload}</span>\n                  </Tooltip.Popup>\n                </Tooltip.Positioner>\n              </Tooltip.Portal>\n            )}\n          </Tooltip.Root>\n        </div>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n\n      await user.hover(trigger1);\n      await waitFor(() => {\n        expect(screen.getByTestId('content').textContent).toBe('1');\n      });\n\n      await user.hover(trigger2);\n      await waitFor(() => {\n        expect(screen.getByTestId('content').textContent).toBe('2');\n      });\n      expect(trigger2).toHaveAttribute('data-popup-open');\n    });\n\n    it('should reuse the popup and positioner DOM nodes when switching triggers', async () => {\n      const testTooltip = Tooltip.createHandle<number>();\n      await render(\n        <React.Fragment>\n          <Tooltip.Trigger handle={testTooltip} payload={1} delay={0}>\n            Trigger 1\n          </Tooltip.Trigger>\n          <Tooltip.Trigger handle={testTooltip} payload={2} delay={0}>\n            Trigger 2\n          </Tooltip.Trigger>\n\n          <Tooltip.Root handle={testTooltip}>\n            {({ payload }: NumberPayload) => (\n              <Tooltip.Portal>\n                <Tooltip.Positioner data-testid=\"positioner\">\n                  <Tooltip.Popup data-testid=\"popup\">\n                    <span>{payload}</span>\n                  </Tooltip.Popup>\n                </Tooltip.Positioner>\n              </Tooltip.Portal>\n            )}\n          </Tooltip.Root>\n        </React.Fragment>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n\n      await act(async () => trigger1.focus());\n      const popupElement = screen.getByTestId('popup');\n      const positionerElement = screen.getByTestId('positioner');\n\n      await act(async () => trigger2.focus());\n      expect(screen.getByTestId('popup')).toBe(popupElement);\n      expect(screen.getByTestId('positioner')).toBe(positionerElement);\n    });\n\n    it('should allow controlling the tooltip state programmatically', async () => {\n      const testTooltip = Tooltip.createHandle<number>();\n      function Test() {\n        const [open, setOpen] = React.useState(false);\n        const [activeTrigger, setActiveTrigger] = React.useState<string | null>(null);\n\n        return (\n          <div style={{ margin: 50 }}>\n            <Tooltip.Trigger handle={testTooltip} payload={1} id=\"trigger-1\" delay={0}>\n              Trigger 1\n            </Tooltip.Trigger>\n            <Tooltip.Trigger handle={testTooltip} payload={2} id=\"trigger-2\" delay={0}>\n              Trigger 2\n            </Tooltip.Trigger>\n\n            <Tooltip.Root\n              open={open}\n              onOpenChange={(nextOpen, details) => {\n                setActiveTrigger(details.trigger?.id ?? null);\n                setOpen(nextOpen);\n              }}\n              triggerId={activeTrigger}\n              handle={testTooltip}\n            >\n              {({ payload }: NumberPayload) => (\n                <Tooltip.Portal>\n                  <Tooltip.Positioner data-testid=\"positioner\" side=\"bottom\" align=\"start\">\n                    <Tooltip.Popup>\n                      <span data-testid=\"content\">{payload}</span>\n                    </Tooltip.Popup>\n                  </Tooltip.Positioner>\n                </Tooltip.Portal>\n              )}\n            </Tooltip.Root>\n\n            <button\n              onClick={() => {\n                setOpen(true);\n                setActiveTrigger('trigger-1');\n              }}\n            >\n              Open Trigger 1\n            </button>\n            <button\n              onClick={() => {\n                setOpen(true);\n                setActiveTrigger('trigger-2');\n              }}\n            >\n              Open Trigger 2\n            </button>\n            <button onClick={() => setOpen(false)}>Close</button>\n          </div>\n        );\n      }\n\n      const { user } = await render(<Test />);\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n\n      await user.click(screen.getByRole('button', { name: 'Open Trigger 1' }));\n      expect(screen.getByTestId('content').textContent).toBe('1');\n\n      await waitFor(() => {\n        expect(\n          Math.abs(\n            screen.getByTestId('positioner').getBoundingClientRect().left -\n              trigger1.getBoundingClientRect().left,\n          ),\n        ).toBeLessThanOrEqual(1);\n      });\n\n      await user.click(screen.getByRole('button', { name: 'Open Trigger 2' }));\n      expect(screen.getByTestId('content').textContent).toBe('2');\n      await waitFor(() => {\n        expect(\n          Math.abs(\n            screen.getByTestId('positioner').getBoundingClientRect().left -\n              trigger2.getBoundingClientRect().left,\n          ),\n        ).toBeLessThanOrEqual(1);\n      });\n\n      await user.click(screen.getByRole('button', { name: 'Close' }));\n      expect(screen.queryByTestId('content')).toBe(null);\n    });\n\n    it('allows setting an initially open tooltip', async () => {\n      const testTooltip = Tooltip.createHandle<number>();\n      const triggerId = randomStringValue();\n      await render(\n        <React.Fragment>\n          <button type=\"button\" aria-label=\"Initial focus\" autoFocus />\n          <Tooltip.Trigger handle={testTooltip} payload={1}>\n            Trigger 1\n          </Tooltip.Trigger>\n          <Tooltip.Trigger handle={testTooltip} payload={2} id={triggerId}>\n            Trigger 2\n          </Tooltip.Trigger>\n\n          <Tooltip.Root handle={testTooltip} defaultOpen defaultTriggerId={triggerId}>\n            {({ payload }: NumberPayload) => (\n              <Tooltip.Portal>\n                <Tooltip.Positioner>\n                  <Tooltip.Popup data-testid=\"popup\">\n                    <span>{payload}</span>\n                  </Tooltip.Popup>\n                </Tooltip.Positioner>\n              </Tooltip.Portal>\n            )}\n          </Tooltip.Root>\n        </React.Fragment>,\n      );\n\n      await waitFor(() => {\n        expect(screen.getByTestId('popup').textContent).toBe('2');\n      });\n    });\n\n    it('should not have inline scale style after switching triggers', async () => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n      const testTooltip = Tooltip.createHandle<number>();\n\n      function Test() {\n        return (\n          <React.Fragment>\n            <button type=\"button\" aria-label=\"Initial focus\" autoFocus />\n            <Tooltip.Trigger handle={testTooltip} payload={1} delay={0}>\n              Trigger 1\n            </Tooltip.Trigger>\n            <Tooltip.Trigger handle={testTooltip} payload={2} delay={0}>\n              Trigger 2\n            </Tooltip.Trigger>\n\n            <Tooltip.Root handle={testTooltip}>\n              {({ payload }: NumberPayload) => (\n                <Tooltip.Portal>\n                  <Tooltip.Positioner>\n                    <Tooltip.Popup data-testid=\"popup\">\n                      <Tooltip.Viewport>\n                        <span data-testid=\"content\">{payload}</span>\n                      </Tooltip.Viewport>\n                    </Tooltip.Popup>\n                  </Tooltip.Positioner>\n                </Tooltip.Portal>\n              )}\n            </Tooltip.Root>\n          </React.Fragment>\n        );\n      }\n\n      const { user } = await render(<Test />);\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n\n      // Open with Trigger 1\n      await user.hover(trigger1);\n      await waitFor(() => {\n        expect(screen.getByTestId('content').textContent).toBe('1');\n      });\n\n      // Switch to Trigger 2\n      await user.unhover(trigger1);\n      await user.hover(trigger2);\n      await waitFor(() => {\n        expect(screen.getByTestId('content').textContent).toBe('2');\n      });\n\n      // The popup should not have an inline scale style that would override CSS transitions\n      const popup = screen.getByTestId('popup');\n      expect(popup.style.scale).toBe('');\n    });\n  });\n\n  describe.skipIf(isJSDOM)('imperative actions on the handle', () => {\n    it('opens and closes the tooltip', async () => {\n      const tooltip = Tooltip.createHandle();\n      await render(\n        <div>\n          <Tooltip.Trigger handle={tooltip} id=\"trigger\">\n            Trigger\n          </Tooltip.Trigger>\n          <Tooltip.Root handle={tooltip}>\n            <Tooltip.Portal>\n              <Tooltip.Positioner>\n                <Tooltip.Popup data-testid=\"content\">Content</Tooltip.Popup>\n              </Tooltip.Positioner>\n            </Tooltip.Portal>\n          </Tooltip.Root>\n        </div>,\n      );\n\n      const trigger = screen.getByRole('button', { name: 'Trigger' });\n      expect(screen.queryByTestId('content')).toBe(null);\n\n      await act(() => tooltip.open('trigger'));\n      await waitFor(() => {\n        expect(screen.queryByTestId('content')).not.toBe(null);\n      });\n\n      expect(screen.getByTestId('content').textContent).toBe('Content');\n      expect(trigger).toHaveAttribute('data-popup-open');\n\n      await act(() => tooltip.close());\n      await waitFor(() => {\n        expect(screen.queryByTestId('content')).toBe(null);\n      });\n\n      expect(trigger).not.toHaveAttribute('data-popup-open');\n    });\n\n    it('sets the payload associated with the trigger', async () => {\n      const tooltip = Tooltip.createHandle<number>();\n      await render(\n        <div>\n          <Tooltip.Trigger handle={tooltip} id=\"trigger1\" payload={1}>\n            Trigger 1\n          </Tooltip.Trigger>\n          <Tooltip.Trigger handle={tooltip} id=\"trigger2\" payload={2}>\n            Trigger 2\n          </Tooltip.Trigger>\n          <Tooltip.Root handle={tooltip}>\n            {({ payload }: { payload: number | undefined }) => (\n              <Tooltip.Portal>\n                <Tooltip.Positioner>\n                  <Tooltip.Popup data-testid=\"content\">{payload}</Tooltip.Popup>\n                </Tooltip.Positioner>\n              </Tooltip.Portal>\n            )}\n          </Tooltip.Root>\n        </div>,\n      );\n\n      const trigger1 = screen.getByRole('button', { name: 'Trigger 1' });\n      const trigger2 = screen.getByRole('button', { name: 'Trigger 2' });\n      expect(screen.queryByTestId('content')).toBe(null);\n\n      await act(() => tooltip.open('trigger2'));\n      await waitFor(() => {\n        expect(screen.queryByTestId('content')).not.toBe(null);\n      });\n\n      expect(screen.getByTestId('content').textContent).toBe('2');\n      expect(trigger2).toHaveAttribute('data-popup-open');\n      expect(trigger1).not.toHaveAttribute('data-popup-open');\n\n      await act(() => tooltip.close());\n      await waitFor(() => {\n        expect(screen.queryByTestId('content')).toBe(null);\n      });\n\n      expect(trigger2).not.toHaveAttribute('data-popup-open');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/tooltip/root/TooltipRoot.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { Tooltip } from '@base-ui/react/tooltip';\nimport { act, fireEvent, flushMicrotasks, screen, waitFor } from '@mui/internal-test-utils';\nimport { createRenderer, isJSDOM, popupConformanceTests } from '#test-utils';\nimport { useRefWithInit } from '@base-ui/utils/useRefWithInit';\nimport { OPEN_DELAY } from '../utils/constants';\nimport { REASONS } from '../../utils/reasons';\n\ndescribe('<Tooltip.Root />', () => {\n  beforeEach(async () => {\n    globalThis.BASE_UI_ANIMATIONS_DISABLED = true;\n  });\n\n  afterEach(async () => {\n    await act(async () => {\n      document.body.click();\n    });\n  });\n\n  const { render, clock } = createRenderer();\n\n  popupConformanceTests({\n    createComponent: (props) => (\n      <Tooltip.Root {...props.root}>\n        <Tooltip.Trigger {...props.trigger}>Open menu</Tooltip.Trigger>\n        <Tooltip.Portal {...props.portal}>\n          <Tooltip.Positioner>\n            <Tooltip.Popup {...props.popup}>Content</Tooltip.Popup>\n          </Tooltip.Positioner>\n        </Tooltip.Portal>\n      </Tooltip.Root>\n    ),\n    render,\n    triggerMouseAction: 'hover',\n  });\n\n  describe.for([\n    { name: 'contained triggers', Component: ContainedTriggerTooltip },\n    { name: 'detached triggers', Component: DetachedTriggerTooltip },\n    { name: 'multiple detached triggers', Component: MultipleDetachedTriggersTooltip },\n  ])('when using $name', ({ Component: TestTooltip }) => {\n    describe('uncontrolled open', () => {\n      clock.withFakeTimers();\n\n      it('should open when the trigger is hovered', async () => {\n        await render(<TestTooltip />);\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n\n        fireEvent.pointerDown(trigger, { pointerType: 'mouse' });\n        fireEvent.mouseEnter(trigger);\n        fireEvent.mouseMove(trigger);\n\n        clock.tick(OPEN_DELAY);\n\n        await flushMicrotasks();\n\n        expect(screen.getByText('Content')).not.toBe(null);\n      });\n\n      it('should close when the trigger is unhovered', async () => {\n        await render(<TestTooltip />);\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n\n        fireEvent.pointerDown(trigger, { pointerType: 'mouse' });\n        fireEvent.mouseEnter(trigger);\n        fireEvent.mouseMove(trigger);\n\n        clock.tick(OPEN_DELAY);\n\n        await flushMicrotasks();\n\n        fireEvent.mouseLeave(trigger);\n\n        await flushMicrotasks();\n        expect(screen.queryByText('Content')).toBe(null);\n      });\n\n      it('should open when the trigger is focused', async ({ skip }) => {\n        if (isJSDOM) {\n          skip();\n        }\n\n        await render(<TestTooltip />);\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n\n        await act(async () => trigger.focus());\n\n        await flushMicrotasks();\n\n        expect(screen.getByText('Content')).not.toBe(null);\n      });\n\n      it('should close when the trigger is blurred', async () => {\n        await render(<TestTooltip />);\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n\n        await act(async () => {\n          trigger.focus();\n        });\n\n        clock.tick(OPEN_DELAY);\n        await flushMicrotasks();\n\n        await act(async () => {\n          trigger.blur();\n        });\n\n        clock.tick(OPEN_DELAY);\n\n        expect(screen.queryByText('Content')).toBe(null);\n      });\n    });\n\n    describe('controlled open', () => {\n      clock.withFakeTimers();\n      it('should call onOpenChange when the open state changes', async () => {\n        const handleChange = vi.fn();\n\n        function App() {\n          const [open, setOpen] = React.useState(false);\n\n          return (\n            <TestTooltip\n              rootProps={{\n                open,\n                onOpenChange: (nextOpen) => {\n                  handleChange(open);\n                  setOpen(nextOpen);\n                },\n              }}\n            />\n          );\n        }\n\n        await render(<App />);\n\n        expect(screen.queryByText('Content')).toBe(null);\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n\n        fireEvent.mouseEnter(trigger);\n        fireEvent.mouseMove(trigger);\n\n        clock.tick(OPEN_DELAY);\n\n        await flushMicrotasks();\n\n        expect(screen.getByText('Content')).not.toBe(null);\n\n        fireEvent.mouseLeave(trigger);\n\n        expect(screen.queryByText('Content')).toBe(null);\n        expect(handleChange.mock.calls.length).toBe(2);\n        expect(handleChange.mock.calls[0][0]).toBe(false);\n        expect(handleChange.mock.calls[1][0]).toBe(true);\n      });\n\n      it('should not call onChange when the open state does not change', async () => {\n        const handleChange = vi.fn();\n\n        function App() {\n          const [open, setOpen] = React.useState(false);\n\n          return (\n            <TestTooltip\n              rootProps={{\n                open,\n                onOpenChange: (nextOpen) => {\n                  handleChange(open);\n                  setOpen(nextOpen);\n                },\n              }}\n            />\n          );\n        }\n\n        await render(<App />);\n\n        expect(screen.queryByText('Content')).toBe(null);\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n\n        fireEvent.mouseEnter(trigger);\n        fireEvent.mouseMove(trigger);\n\n        clock.tick(OPEN_DELAY);\n\n        await flushMicrotasks();\n\n        expect(screen.getByText('Content')).not.toBe(null);\n        expect(handleChange.mock.calls.length).toBe(1);\n        expect(handleChange.mock.calls[0][0]).toBe(false);\n      });\n    });\n\n    describe('prop: defaultOpen', () => {\n      it('should open when the component is rendered', async () => {\n        await render(<TestTooltip rootProps={{ defaultOpen: true }} />);\n\n        await flushMicrotasks();\n\n        expect(screen.getByText('Content')).not.toBe(null);\n      });\n\n      it('should not open when the component is rendered and open is controlled', async () => {\n        await render(<TestTooltip rootProps={{ defaultOpen: true, open: false }} />);\n\n        await flushMicrotasks();\n\n        expect(screen.queryByText('Content')).toBe(null);\n      });\n\n      it('should not close when the component is rendered and open is controlled', async () => {\n        await render(<TestTooltip rootProps={{ defaultOpen: true, open: true }} />);\n\n        await flushMicrotasks();\n\n        expect(screen.getByText('Content')).not.toBe(null);\n      });\n\n      it('should remain uncontrolled', async () => {\n        await render(<TestTooltip rootProps={{ defaultOpen: true }} />);\n\n        await flushMicrotasks();\n\n        expect(screen.getByText('Content')).not.toBe(null);\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n\n        fireEvent.mouseLeave(trigger);\n\n        await flushMicrotasks();\n\n        expect(screen.queryByText('Content')).toBe(null);\n      });\n    });\n\n    describe('prop: delay', () => {\n      clock.withFakeTimers();\n\n      it('should open after rest delay', async () => {\n        await render(<TestTooltip triggerProps={{ delay: 100 }} />);\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n\n        fireEvent.mouseEnter(trigger);\n        fireEvent.mouseMove(trigger);\n\n        await flushMicrotasks();\n\n        expect(screen.queryByText('Content')).toBe(null);\n\n        clock.tick(100);\n\n        await flushMicrotasks();\n\n        expect(screen.getByText('Content')).not.toBe(null);\n      });\n    });\n\n    describe('prop: closeDelay', () => {\n      clock.withFakeTimers();\n\n      it('should close after delay', async () => {\n        await render(<TestTooltip triggerProps={{ closeDelay: 100 }} />);\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n\n        fireEvent.mouseEnter(trigger);\n        fireEvent.mouseMove(trigger);\n\n        clock.tick(OPEN_DELAY);\n\n        await flushMicrotasks();\n\n        expect(screen.getByText('Content')).not.toBe(null);\n\n        fireEvent.mouseLeave(trigger);\n\n        expect(screen.getByText('Content')).not.toBe(null);\n\n        clock.tick(100);\n\n        expect(screen.queryByText('Content')).toBe(null);\n      });\n    });\n\n    describe('prop: actionsRef', () => {\n      it('unmounts the tooltip when the `unmount` method is called', async () => {\n        const actionsRef = {\n          current: {\n            unmount: vi.fn(),\n            close: vi.fn(),\n          },\n        };\n\n        const { user } = await render(\n          <TestTooltip\n            rootProps={{\n              actionsRef,\n              onOpenChange: (open, details) => {\n                details.preventUnmountOnClose();\n              },\n            }}\n          />,\n        );\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n        await user.hover(trigger);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('positioner')).not.toBe(null);\n        });\n\n        await user.unhover(trigger);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('positioner')).not.toBe(null);\n        });\n\n        await act(async () => actionsRef.current.unmount());\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('positioner')).toBe(null);\n        });\n      });\n    });\n\n    describe.skipIf(isJSDOM)('prop: onOpenChangeComplete', () => {\n      it('is called on close when there is no exit animation defined', async () => {\n        const onOpenChangeComplete = vi.fn();\n\n        function Test() {\n          const [open, setOpen] = React.useState(true);\n          return (\n            <div>\n              <button onClick={() => setOpen(false)}>Close</button>\n              <Tooltip.Root open={open} onOpenChangeComplete={onOpenChangeComplete}>\n                <Tooltip.Portal>\n                  <Tooltip.Positioner>\n                    <Tooltip.Popup data-testid=\"popup\" />\n                  </Tooltip.Positioner>\n                </Tooltip.Portal>\n              </Tooltip.Root>\n            </div>\n          );\n        }\n\n        const { user } = await render(<Test />);\n\n        const closeButton = screen.getByText('Close');\n        await user.click(closeButton);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('popup')).toBe(null);\n        });\n\n        expect(onOpenChangeComplete.mock.calls[0][0]).toBe(true);\n        expect(onOpenChangeComplete.mock.lastCall?.[0]).toBe(false);\n      });\n\n      it('is called on close when the exit animation finishes', async () => {\n        globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n        const onOpenChangeComplete = vi.fn();\n\n        function Test() {\n          const style = `\n          @keyframes test-anim {\n            to {\n              opacity: 0;\n            }\n          }\n\n          .animation-test-indicator[data-ending-style] {\n            animation: test-anim 1ms;\n          }\n        `;\n\n          const [open, setOpen] = React.useState(true);\n\n          return (\n            <div>\n              {/* eslint-disable-next-line react/no-danger */}\n              <style dangerouslySetInnerHTML={{ __html: style }} />\n              <button onClick={() => setOpen(false)}>Close</button>\n              <Tooltip.Root open={open} onOpenChangeComplete={onOpenChangeComplete}>\n                <Tooltip.Portal>\n                  <Tooltip.Positioner>\n                    <Tooltip.Popup className=\"animation-test-indicator\" data-testid=\"popup\" />\n                  </Tooltip.Positioner>\n                </Tooltip.Portal>\n              </Tooltip.Root>\n            </div>\n          );\n        }\n\n        const { user } = await render(<Test />);\n\n        expect(screen.getByTestId('popup')).not.toBe(null);\n\n        // Wait for open animation to finish\n        await waitFor(() => {\n          expect(onOpenChangeComplete.mock.calls[0][0]).toBe(true);\n        });\n\n        const closeButton = screen.getByText('Close');\n        await user.click(closeButton);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('popup')).toBe(null);\n        });\n\n        expect(onOpenChangeComplete.mock.lastCall?.[0]).toBe(false);\n      });\n\n      it('is called on open when there is no enter animation defined', async () => {\n        const onOpenChangeComplete = vi.fn();\n\n        function Test() {\n          const [open, setOpen] = React.useState(false);\n          return (\n            <div>\n              <button onClick={() => setOpen(true)}>Open</button>\n              <Tooltip.Root open={open} onOpenChangeComplete={onOpenChangeComplete}>\n                <Tooltip.Portal>\n                  <Tooltip.Positioner>\n                    <Tooltip.Popup data-testid=\"popup\" />\n                  </Tooltip.Positioner>\n                </Tooltip.Portal>\n              </Tooltip.Root>\n            </div>\n          );\n        }\n\n        const { user } = await render(<Test />);\n\n        const openButton = screen.getByText('Open');\n        await user.click(openButton);\n\n        await waitFor(() => {\n          expect(screen.queryByTestId('popup')).not.toBe(null);\n        });\n\n        expect(onOpenChangeComplete.mock.calls.length).toBe(2);\n        expect(onOpenChangeComplete.mock.calls[0][0]).toBe(true);\n      });\n\n      it('is called on open when the enter animation finishes', async () => {\n        globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n        const onOpenChangeComplete = vi.fn();\n\n        function Test() {\n          const style = `\n          @keyframes test-anim {\n            from {\n              opacity: 0;\n            }\n          }\n\n          .animation-test-indicator[data-starting-style] {\n            animation: test-anim 1ms;\n          }\n        `;\n\n          const [open, setOpen] = React.useState(false);\n\n          return (\n            <div>\n              {/* eslint-disable-next-line react/no-danger */}\n              <style dangerouslySetInnerHTML={{ __html: style }} />\n              <button onClick={() => setOpen(true)}>Open</button>\n              <Tooltip.Root\n                open={open}\n                onOpenChange={setOpen}\n                onOpenChangeComplete={onOpenChangeComplete}\n              >\n                <Tooltip.Portal>\n                  <Tooltip.Positioner>\n                    <Tooltip.Popup className=\"animation-test-indicator\" data-testid=\"popup\" />\n                  </Tooltip.Positioner>\n                </Tooltip.Portal>\n              </Tooltip.Root>\n            </div>\n          );\n        }\n\n        const { user } = await render(<Test />);\n\n        const openButton = screen.getByText('Open');\n        await user.click(openButton);\n\n        // Wait for open animation to finish\n        await waitFor(() => {\n          expect(onOpenChangeComplete.mock.calls[0][0]).toBe(true);\n        });\n\n        expect(screen.queryByTestId('popup')).not.toBe(null);\n      });\n\n      it('does not get called on mount when not open', async () => {\n        const onOpenChangeComplete = vi.fn();\n\n        await render(\n          <Tooltip.Root onOpenChangeComplete={onOpenChangeComplete}>\n            <Tooltip.Portal>\n              <Tooltip.Positioner>\n                <Tooltip.Popup data-testid=\"popup\" />\n              </Tooltip.Positioner>\n            </Tooltip.Portal>\n          </Tooltip.Root>,\n        );\n\n        expect(onOpenChangeComplete.mock.calls.length).toBe(0);\n      });\n    });\n\n    describe.skipIf(isJSDOM)('animations', () => {\n      it('toggles instant animations for adjacent tooltips only while opening', async () => {\n        globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n        const style = `\n          .tooltip {\n            transition: opacity 20ms;\n          }\n          .tooltip[data-starting-style],\n          .tooltip[data-ending-style] {\n            opacity: 0;\n          }\n\n          .tooltip[data-instant] {\n            transition: none;\n          }\n        `;\n\n        const { user } = await render(\n          <Tooltip.Provider>\n            {/* eslint-disable-next-line react/no-danger */}\n            <style dangerouslySetInnerHTML={{ __html: style }} />\n            <Tooltip.Root>\n              <Tooltip.Trigger data-testid=\"trigger-1\" delay={0}>\n                First\n              </Tooltip.Trigger>\n              <Tooltip.Portal>\n                <Tooltip.Positioner>\n                  <Tooltip.Popup className=\"tooltip\" data-testid=\"popup-1\">\n                    First tooltip\n                  </Tooltip.Popup>\n                </Tooltip.Positioner>\n              </Tooltip.Portal>\n            </Tooltip.Root>\n            <Tooltip.Root>\n              <Tooltip.Trigger data-testid=\"trigger-2\" delay={0}>\n                Second\n              </Tooltip.Trigger>\n              <Tooltip.Portal>\n                <Tooltip.Positioner>\n                  <Tooltip.Popup className=\"tooltip\" data-testid=\"popup-2\">\n                    Second tooltip\n                  </Tooltip.Popup>\n                </Tooltip.Positioner>\n              </Tooltip.Portal>\n            </Tooltip.Root>\n          </Tooltip.Provider>,\n        );\n\n        const firstTrigger = screen.getByTestId('trigger-1');\n        const secondTrigger = screen.getByTestId('trigger-2');\n\n        await user.hover(firstTrigger);\n\n        const firstPopup = await screen.findByTestId('popup-1');\n        expect(firstPopup.dataset.instant).toBe(undefined);\n\n        await user.unhover(firstTrigger);\n        await user.hover(secondTrigger);\n\n        const secondPopup = await screen.findByTestId('popup-2');\n\n        await waitFor(() => {\n          expect(secondPopup.dataset.instant).toBe('delay');\n          expect(secondPopup.getAnimations().length).toBe(0);\n        });\n\n        await user.unhover(secondTrigger);\n\n        await waitFor(() => {\n          expect(secondPopup.dataset.endingStyle).toBe('');\n          expect(secondPopup.dataset.instant).toBe(undefined);\n          expect(secondPopup.getAnimations().length).toBe(1);\n        });\n      });\n\n      it('inline opacity: 0 is removed before user CSS transitions run', async () => {\n        globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n\n        // The inline opacity: 0 applied before positioning must be removed\n        // before CSS transitions start, so it does not trigger an unwanted\n        // opacity transition.\n        const style = `\n          .tooltip {\n            transition: opacity 200ms;\n            opacity: 1;\n          }\n        `;\n\n        const { user } = await render(\n          <Tooltip.Root>\n            {/* eslint-disable-next-line react/no-danger */}\n            <style dangerouslySetInnerHTML={{ __html: style }} />\n            <Tooltip.Trigger data-testid=\"trigger\" delay={0}>\n              Trigger\n            </Tooltip.Trigger>\n            <Tooltip.Portal>\n              <Tooltip.Positioner>\n                <Tooltip.Popup className=\"tooltip\" data-testid=\"popup\">\n                  Tooltip\n                </Tooltip.Popup>\n              </Tooltip.Positioner>\n            </Tooltip.Portal>\n          </Tooltip.Root>,\n        );\n\n        await user.hover(screen.getByTestId('trigger'));\n\n        const popup = await screen.findByTestId('popup');\n\n        // Opacity should be 1 immediately — no unwanted fade from 0 to 1.\n        // No opacity transition should be running.\n        await waitFor(() => {\n          expect(Number(getComputedStyle(popup).opacity)).toBe(1);\n          const opacityAnimations = popup\n            .getAnimations()\n            .filter((a) => (a as CSSTransition).transitionProperty === 'opacity');\n          expect(opacityAnimations.length).toBe(0);\n        });\n      });\n    });\n\n    describe('prop: disabled', () => {\n      it('should not open when disabled', async () => {\n        await render(<TestTooltip rootProps={{ disabled: true }} triggerProps={{ delay: 0 }} />);\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n\n        fireEvent.pointerDown(trigger, { pointerType: 'mouse' });\n        fireEvent.mouseEnter(trigger);\n        fireEvent.mouseMove(trigger);\n\n        await flushMicrotasks();\n\n        expect(screen.queryByText('Content')).toBe(null);\n\n        await act(async () => trigger.focus());\n\n        expect(screen.queryByText('Content')).toBe(null);\n      });\n\n      it('should not open on focus when the trigger is disabled', async () => {\n        await render(<TestTooltip triggerProps={{ disabled: true, delay: 0 }} />);\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n\n        await act(async () => trigger.focus());\n        await flushMicrotasks();\n\n        expect(screen.queryByText('Content')).toBe(null);\n      });\n\n      it('should close if open when becoming disabled', async () => {\n        function App() {\n          const [disabled, setDisabled] = React.useState(false);\n          return (\n            <div>\n              <TestTooltip\n                rootProps={{ defaultOpen: true, disabled }}\n                triggerProps={{ delay: 0 }}\n              />\n              <button\n                data-testid=\"disabled\"\n                onClick={() => {\n                  setDisabled(true);\n                }}\n              />\n            </div>\n          );\n        }\n\n        await render(<App />);\n\n        expect(screen.queryByText('Content')).not.toBe(null);\n\n        const disabledButton = screen.getByTestId('disabled');\n        fireEvent.click(disabledButton);\n\n        expect(screen.queryByText('Content')).toBe(null);\n      });\n\n      it('does not throw error when combined with defaultOpen', async () => {\n        await render(<TestTooltip rootProps={{ defaultOpen: true, disabled: true }} />);\n\n        expect(screen.queryByText('Content')).toBe(null);\n      });\n    });\n\n    describe('prop: disableHoverablePopup', () => {\n      it('applies pointer-events: none to the positioner when `disableHoverablePopup = true`', async () => {\n        await render(\n          <TestTooltip rootProps={{ disableHoverablePopup: true }} triggerProps={{ delay: 0 }} />,\n        );\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n\n        fireEvent.pointerDown(trigger, { pointerType: 'mouse' });\n        fireEvent.mouseEnter(trigger);\n        fireEvent.mouseMove(trigger);\n\n        await flushMicrotasks();\n\n        expect(screen.getByTestId('positioner').style.pointerEvents).toBe('none');\n      });\n\n      it('does not apply pointer-events: none to the positioner when `disableHoverablePopup = false`', async () => {\n        await render(\n          <TestTooltip rootProps={{ disableHoverablePopup: false }} triggerProps={{ delay: 0 }} />,\n        );\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n\n        fireEvent.pointerDown(trigger, { pointerType: 'mouse' });\n        fireEvent.mouseEnter(trigger);\n        fireEvent.mouseMove(trigger);\n\n        await flushMicrotasks();\n\n        expect(screen.getByTestId('positioner').style.pointerEvents).toBe('');\n      });\n    });\n\n    describe('BaseUIChangeEventDetails', () => {\n      it('onOpenChange cancel() prevents opening while uncontrolled', async () => {\n        await render(\n          <TestTooltip\n            rootProps={{\n              onOpenChange: (nextOpen, eventDetails) => {\n                if (nextOpen) {\n                  eventDetails.cancel();\n                }\n              },\n            }}\n            triggerProps={{ delay: 0 }}\n          />,\n        );\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n        fireEvent.pointerDown(trigger, { pointerType: 'mouse' });\n        fireEvent.mouseEnter(trigger);\n        fireEvent.mouseMove(trigger);\n\n        await flushMicrotasks();\n\n        expect(screen.queryByText('Content')).toBe(null);\n      });\n\n      it('allowPropagation() prevents stopPropagation on Escape while still closing', async () => {\n        const stopPropagationSpy = vi.spyOn(Event.prototype as any, 'stopPropagation');\n\n        await render(\n          <TestTooltip\n            rootProps={{\n              defaultOpen: true,\n              onOpenChange: (nextOpen, eventDetails) => {\n                if (!nextOpen && eventDetails.reason === REASONS.escapeKey) {\n                  eventDetails.allowPropagation();\n                }\n              },\n            }}\n            triggerProps={{ delay: 0 }}\n          />,\n        );\n\n        expect(screen.getByText('Content')).not.toBe(null);\n\n        fireEvent.keyDown(document.body, { key: 'Escape' });\n\n        await waitFor(() => {\n          expect(screen.queryByText('Content')).toBe(null);\n        });\n\n        expect(stopPropagationSpy).toHaveBeenCalledTimes(0);\n        stopPropagationSpy.mockRestore();\n      });\n    });\n\n    describe('dismissal', () => {\n      clock.withFakeTimers();\n\n      it('should not open when the trigger was clicked before delay duration', async () => {\n        await render(<TestTooltip />);\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n\n        fireEvent.pointerDown(trigger, { pointerType: 'mouse' });\n        fireEvent.mouseEnter(trigger);\n        fireEvent.mouseMove(trigger);\n\n        clock.tick(OPEN_DELAY / 2);\n\n        fireEvent.click(trigger);\n\n        clock.tick(OPEN_DELAY / 2);\n\n        await flushMicrotasks();\n\n        expect(screen.queryByText('Content')).toBe(null);\n      });\n\n      it('should open when the trigger was clicked before delay duration and closeOnClick is false', async () => {\n        await render(<TestTooltip triggerProps={{ closeOnClick: false }} />);\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n\n        fireEvent.pointerDown(trigger, { pointerType: 'mouse' });\n        fireEvent.mouseEnter(trigger);\n        fireEvent.mouseMove(trigger);\n\n        clock.tick(OPEN_DELAY / 2);\n\n        fireEvent.click(trigger);\n\n        clock.tick(OPEN_DELAY / 2);\n\n        await flushMicrotasks();\n\n        expect(screen.getByText('Content')).not.toBe(null);\n      });\n\n      it('should close when the trigger is clicked after delay duration', async () => {\n        await render(<TestTooltip />);\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n\n        fireEvent.pointerDown(trigger, { pointerType: 'mouse' });\n        fireEvent.mouseEnter(trigger);\n        fireEvent.mouseMove(trigger);\n\n        clock.tick(OPEN_DELAY);\n\n        await flushMicrotasks();\n\n        expect(screen.getByText('Content')).not.toBe(null);\n\n        fireEvent.click(trigger);\n\n        expect(screen.queryByText('Content')).toBe(null);\n      });\n\n      it('should not close when the trigger is clicked after delay duration and closeOnClick is false', async () => {\n        await render(<TestTooltip triggerProps={{ closeOnClick: false }} />);\n\n        const trigger = screen.getByRole('button', { name: 'Toggle' });\n\n        fireEvent.pointerDown(trigger, { pointerType: 'mouse' });\n        fireEvent.mouseEnter(trigger);\n        fireEvent.mouseMove(trigger);\n\n        clock.tick(OPEN_DELAY);\n\n        await flushMicrotasks();\n\n        expect(screen.getByText('Content')).not.toBe(null);\n\n        fireEvent.click(trigger);\n\n        expect(screen.getByText('Content')).not.toBe(null);\n      });\n    });\n  });\n\n  it('keeps the tooltip open when moving across spaced triggers without a closeDelay', async () => {\n    const testTooltip = Tooltip.createHandle();\n    const { user } = await render(\n      <Tooltip.Provider timeout={400}>\n        <div style={{ display: 'flex', gap: 32 }}>\n          <Tooltip.Trigger handle={testTooltip} delay={0}>\n            Trigger 1\n          </Tooltip.Trigger>\n          <Tooltip.Trigger handle={testTooltip} delay={0}>\n            Trigger 2\n          </Tooltip.Trigger>\n          <Tooltip.Trigger handle={testTooltip} delay={0}>\n            Trigger 3\n          </Tooltip.Trigger>\n        </div>\n\n        <Tooltip.Root handle={testTooltip}>\n          <Tooltip.Portal>\n            <Tooltip.Positioner>\n              <Tooltip.Popup data-testid=\"popup\">Tooltip Content</Tooltip.Popup>\n            </Tooltip.Positioner>\n          </Tooltip.Portal>\n        </Tooltip.Root>\n      </Tooltip.Provider>,\n    );\n\n    const [trigger1, trigger2, trigger3] = screen.getAllByRole('button');\n\n    await user.hover(trigger1);\n    await waitFor(() => {\n      expect(screen.getByTestId('popup')).toBeVisible();\n    });\n\n    await user.unhover(trigger1);\n    await user.hover(trigger2);\n    await waitFor(() => {\n      expect(screen.getByTestId('popup')).toBeVisible();\n    });\n\n    fireEvent.mouseLeave(trigger2, { relatedTarget: document.body, clientX: 120, clientY: 0 });\n    await user.hover(trigger3);\n\n    await waitFor(() => {\n      expect(screen.getByTestId('popup')).toBeVisible();\n    });\n\n    fireEvent.mouseMove(document.body, { clientX: 300, clientY: 0 });\n\n    await waitFor(() => {\n      expect(screen.getByTestId('popup')).toBeVisible();\n    });\n  });\n});\n\ntype TestTooltipProps = {\n  rootProps?: Tooltip.Root.Props;\n  triggerProps?: Tooltip.Trigger.Props;\n  portalProps?: Tooltip.Portal.Props;\n  positionerProps?: Tooltip.Positioner.Props;\n  popupProps?: Tooltip.Popup.Props;\n  portalChildren?: React.ReactNode;\n  beforeTrigger?: React.ReactNode;\n  betweenTriggerAndPortal?: React.ReactNode;\n  afterPortal?: React.ReactNode;\n};\n\nfunction ContainedTriggerTooltip(props: TestTooltipProps) {\n  const {\n    rootProps,\n    triggerProps,\n    portalProps,\n    positionerProps,\n    popupProps,\n    beforeTrigger,\n    betweenTriggerAndPortal,\n    afterPortal,\n  } = props;\n\n  const { children: triggerChildren, ...restTriggerProps } = triggerProps ?? {};\n  const { children: popupChildren, ...restPopupProps } = popupProps ?? {};\n  const { children: portalChildren, ...restPortalProps } = portalProps ?? {};\n\n  const triggerContent = triggerChildren ?? 'Toggle';\n  const popupContent = popupChildren ?? 'Content';\n\n  return (\n    <Tooltip.Root {...rootProps}>\n      {beforeTrigger}\n      <Tooltip.Trigger data-testid=\"trigger\" {...restTriggerProps}>\n        {triggerContent}\n      </Tooltip.Trigger>\n      {betweenTriggerAndPortal}\n      <Tooltip.Portal {...restPortalProps}>\n        {portalChildren}\n        <Tooltip.Positioner data-testid=\"positioner\" {...positionerProps}>\n          <Tooltip.Popup data-testid=\"popup\" {...restPopupProps}>\n            {popupContent}\n          </Tooltip.Popup>\n        </Tooltip.Positioner>\n      </Tooltip.Portal>\n      {afterPortal}\n    </Tooltip.Root>\n  );\n}\n\nfunction DetachedTriggerTooltip(props: TestTooltipProps) {\n  const {\n    rootProps,\n    triggerProps,\n    portalProps,\n    positionerProps,\n    popupProps,\n    beforeTrigger,\n    betweenTriggerAndPortal,\n    afterPortal,\n  } = props;\n\n  const { children: triggerChildren, ...restTriggerProps } = triggerProps ?? {};\n  const { children: popupChildren, ...restPopupProps } = popupProps ?? {};\n  const { children: portalChildren, ...restPortalProps } = portalProps ?? {};\n\n  const triggerContent = triggerChildren ?? 'Toggle';\n  const popupContent = popupChildren ?? 'Content';\n\n  const tooltipHandle = useRefWithInit(() => Tooltip.createHandle()).current;\n\n  return (\n    <React.Fragment>\n      {beforeTrigger}\n      <Tooltip.Trigger data-testid=\"trigger\" handle={tooltipHandle} {...restTriggerProps}>\n        {triggerContent}\n      </Tooltip.Trigger>\n      {betweenTriggerAndPortal}\n      <Tooltip.Root handle={tooltipHandle} {...rootProps}>\n        <Tooltip.Portal {...restPortalProps}>\n          {portalChildren}\n          <Tooltip.Positioner data-testid=\"positioner\" {...positionerProps}>\n            <Tooltip.Popup data-testid=\"popup\" {...restPopupProps}>\n              {popupContent}\n            </Tooltip.Popup>\n          </Tooltip.Positioner>\n        </Tooltip.Portal>\n        {afterPortal}\n      </Tooltip.Root>\n    </React.Fragment>\n  );\n}\n\nfunction MultipleDetachedTriggersTooltip(props: TestTooltipProps) {\n  const {\n    rootProps,\n    triggerProps,\n    portalProps,\n    positionerProps,\n    popupProps,\n    beforeTrigger,\n    betweenTriggerAndPortal,\n    afterPortal,\n  } = props;\n\n  const { children: triggerChildren, ...restTriggerProps } = triggerProps ?? {};\n  const { children: popupChildren, ...restPopupProps } = popupProps ?? {};\n  const { children: portalChildren, ...restPortalProps } = portalProps ?? {};\n\n  const triggerContent = triggerChildren ?? 'Toggle';\n  const popupContent = popupChildren ?? 'Content';\n\n  const tooltipHandle = useRefWithInit(() => Tooltip.createHandle()).current;\n\n  return (\n    <React.Fragment>\n      {beforeTrigger}\n      <Tooltip.Trigger data-testid=\"trigger\" handle={tooltipHandle} {...restTriggerProps}>\n        {triggerContent}\n      </Tooltip.Trigger>\n      <Tooltip.Trigger data-testid=\"trigger-2\" handle={tooltipHandle}>\n        Toggle another\n      </Tooltip.Trigger>\n      {betweenTriggerAndPortal}\n      <Tooltip.Root handle={tooltipHandle} {...rootProps}>\n        <Tooltip.Portal {...restPortalProps}>\n          {portalChildren}\n          <Tooltip.Positioner data-testid=\"positioner\" {...positionerProps}>\n            <Tooltip.Popup data-testid=\"popup\" {...restPopupProps}>\n              {popupContent}\n            </Tooltip.Popup>\n          </Tooltip.Positioner>\n        </Tooltip.Portal>\n        {afterPortal}\n      </Tooltip.Root>\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "packages/react/src/tooltip/root/TooltipRoot.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { fastComponent } from '@base-ui/utils/fastHooks';\nimport { useOnFirstRender } from '@base-ui/utils/useOnFirstRender';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { TooltipRootContext } from './TooltipRootContext';\nimport { useClientPoint, useDismiss, useInteractions } from '../../floating-ui-react';\nimport {\n  type BaseUIChangeEventDetails,\n  createChangeEventDetails,\n} from '../../utils/createBaseUIEventDetails';\nimport {\n  useImplicitActiveTrigger,\n  useOpenStateTransitions,\n  type PayloadChildRenderFunction,\n} from '../../utils/popups';\nimport { TooltipStore } from '../store/TooltipStore';\nimport { type TooltipHandle } from '../store/TooltipHandle';\nimport { REASONS } from '../../utils/reasons';\n\n/**\n * Groups all parts of the tooltip.\n * Doesn’t render its own HTML element.\n *\n * Documentation: [Base UI Tooltip](https://base-ui.com/react/components/tooltip)\n */\nexport const TooltipRoot = fastComponent(function TooltipRoot<Payload>(\n  props: TooltipRoot.Props<Payload>,\n) {\n  const {\n    disabled = false,\n    defaultOpen = false,\n    open: openProp,\n    disableHoverablePopup = false,\n    trackCursorAxis = 'none',\n    actionsRef,\n    onOpenChange,\n    onOpenChangeComplete,\n    handle,\n    triggerId: triggerIdProp,\n    defaultTriggerId: defaultTriggerIdProp = null,\n    children,\n  } = props;\n\n  const store = TooltipStore.useStore<Payload>(handle?.store, {\n    open: defaultOpen,\n    openProp,\n    activeTriggerId: defaultTriggerIdProp,\n    triggerIdProp,\n  });\n\n  // Support initially open state when uncontrolled\n  useOnFirstRender(() => {\n    if (openProp === undefined && store.state.open === false && defaultOpen === true) {\n      store.update({\n        open: true,\n        activeTriggerId: defaultTriggerIdProp,\n      });\n    }\n  });\n\n  store.useControlledProp('openProp', openProp);\n  store.useControlledProp('triggerIdProp', triggerIdProp);\n\n  store.useContextCallback('onOpenChange', onOpenChange);\n  store.useContextCallback('onOpenChangeComplete', onOpenChangeComplete);\n\n  const openState = store.useState('open');\n  const open = !disabled && openState;\n\n  const activeTriggerId = store.useState('activeTriggerId');\n  const payload = store.useState('payload') as Payload | undefined;\n\n  store.useSyncedValues({\n    trackCursorAxis,\n    disableHoverablePopup,\n  });\n\n  useIsoLayoutEffect(() => {\n    if (openState && disabled) {\n      store.setOpen(false, createChangeEventDetails(REASONS.disabled));\n    }\n  }, [openState, disabled, store]);\n\n  store.useSyncedValue('disabled', disabled);\n\n  useImplicitActiveTrigger(store);\n  const { forceUnmount, transitionStatus } = useOpenStateTransitions(open, store);\n  const isInstantPhase = store.useState('isInstantPhase');\n  const instantType = store.useState('instantType');\n  const lastOpenChangeReason = store.useState('lastOpenChangeReason');\n\n  // Animations should be instant in two cases:\n  // 1) Opening during the provider's instant phase (adjacent tooltip opens instantly)\n  // 2) Closing because another tooltip opened (reason === 'none')\n  // Otherwise, allow the animation to play. In particular, do not disable animations\n  // during the 'ending' phase unless it's due to a sibling opening.\n  const previousInstantTypeRef = React.useRef<string | undefined | null>(null);\n  useIsoLayoutEffect(() => {\n    if (\n      (transitionStatus === 'ending' && lastOpenChangeReason === REASONS.none) ||\n      (transitionStatus !== 'ending' && isInstantPhase)\n    ) {\n      // Capture the current instant type so we can restore it later\n      // and set to 'delay' to disable animations while moving from one trigger to another\n      // within a delay group.\n      if (instantType !== 'delay') {\n        previousInstantTypeRef.current = instantType;\n      }\n      store.set('instantType', 'delay');\n    } else if (previousInstantTypeRef.current !== null) {\n      store.set('instantType', previousInstantTypeRef.current);\n      previousInstantTypeRef.current = null;\n    }\n  }, [transitionStatus, isInstantPhase, lastOpenChangeReason, instantType, store]);\n\n  useIsoLayoutEffect(() => {\n    if (open) {\n      if (activeTriggerId == null) {\n        store.set('payload', undefined);\n      }\n    }\n  }, [store, activeTriggerId, open]);\n\n  const handleImperativeClose = React.useCallback(() => {\n    store.setOpen(false, createTooltipEventDetails(store, REASONS.imperativeAction));\n  }, [store]);\n\n  React.useImperativeHandle(\n    actionsRef,\n    () => ({ unmount: forceUnmount, close: handleImperativeClose }),\n    [forceUnmount, handleImperativeClose],\n  );\n\n  const floatingRootContext = store.useState('floatingRootContext');\n\n  const dismiss = useDismiss(floatingRootContext, {\n    enabled: !disabled,\n    referencePress: () => store.select('closeOnClick'),\n  });\n  const clientPoint = useClientPoint(floatingRootContext, {\n    enabled: !disabled && trackCursorAxis !== 'none',\n    axis: trackCursorAxis === 'none' ? undefined : trackCursorAxis,\n  });\n\n  const { getReferenceProps, getFloatingProps, getTriggerProps } = useInteractions([\n    dismiss,\n    clientPoint,\n  ]);\n\n  const activeTriggerProps = React.useMemo(() => getReferenceProps(), [getReferenceProps]);\n  const inactiveTriggerProps = React.useMemo(() => getTriggerProps(), [getTriggerProps]);\n  const popupProps = React.useMemo(() => getFloatingProps(), [getFloatingProps]);\n\n  store.useSyncedValues({\n    activeTriggerProps,\n    inactiveTriggerProps,\n    popupProps,\n  });\n\n  return (\n    <TooltipRootContext.Provider value={store as TooltipRootContext}>\n      {typeof children === 'function' ? children({ payload }) : children}\n    </TooltipRootContext.Provider>\n  );\n});\n\nfunction createTooltipEventDetails<Payload>(\n  store: TooltipStore<Payload>,\n  reason: TooltipRoot.ChangeEventReason,\n) {\n  const details: TooltipRoot.ChangeEventDetails =\n    createChangeEventDetails<TooltipRoot.ChangeEventReason>(\n      reason,\n    ) as TooltipRoot.ChangeEventDetails;\n  details.preventUnmountOnClose = () => {\n    store.set('preventUnmountingOnClose', true);\n  };\n  return details;\n}\n\nexport interface TooltipRootState {}\n\nexport interface TooltipRootProps<Payload = unknown> {\n  /**\n   * Whether the tooltip is initially open.\n   *\n   * To render a controlled tooltip, use the `open` prop instead.\n   * @default false\n   */\n  defaultOpen?: boolean | undefined;\n  /**\n   * Whether the tooltip is currently open.\n   */\n  open?: boolean | undefined;\n  /**\n   * Event handler called when the tooltip is opened or closed.\n   */\n  onOpenChange?:\n    | ((open: boolean, eventDetails: TooltipRoot.ChangeEventDetails) => void)\n    | undefined;\n  /**\n   * Event handler called after any animations complete when the tooltip is opened or closed.\n   */\n  onOpenChangeComplete?: ((open: boolean) => void) | undefined;\n  /**\n   * Whether the tooltip contents can be hovered without closing the tooltip.\n   * @default false\n   */\n  disableHoverablePopup?: boolean | undefined;\n  /**\n   * Determines which axis the tooltip should track the cursor on.\n   * @default 'none'\n   */\n  trackCursorAxis?: 'none' | 'x' | 'y' | 'both' | undefined;\n  /**\n   * A ref to imperative actions.\n   * - `unmount`: Unmounts the tooltip popup.\n   * - `close`: Closes the tooltip imperatively when called.\n   */\n  actionsRef?: React.RefObject<TooltipRoot.Actions | null> | undefined;\n  /**\n   * Whether the tooltip is disabled.\n   * @default false\n   */\n  disabled?: boolean | undefined;\n  /**\n   * A handle to associate the tooltip with a trigger.\n   * If specified, allows external triggers to control the tooltip's open state.\n   * Can be created with the Tooltip.createHandle() method.\n   */\n  handle?: TooltipHandle<Payload> | undefined;\n  /**\n   * The content of the tooltip.\n   * This can be a regular React node or a render function that receives the `payload` of the active trigger.\n   */\n  children?: React.ReactNode | PayloadChildRenderFunction<Payload>;\n  /**\n   * ID of the trigger that the tooltip is associated with.\n   * This is useful in conjunction with the `open` prop to create a controlled tooltip.\n   * There's no need to specify this prop when the tooltip is uncontrolled (i.e. when the `open` prop is not set).\n   */\n  triggerId?: string | null | undefined;\n  /**\n   * ID of the trigger that the tooltip is associated with.\n   * This is useful in conjunction with the `defaultOpen` prop to create an initially open tooltip.\n   */\n  defaultTriggerId?: string | null | undefined;\n}\n\nexport interface TooltipRootActions {\n  unmount: () => void;\n  close: () => void;\n}\n\nexport type TooltipRootChangeEventReason =\n  | typeof REASONS.triggerHover\n  | typeof REASONS.triggerFocus\n  | typeof REASONS.triggerPress\n  | typeof REASONS.outsidePress\n  | typeof REASONS.escapeKey\n  | typeof REASONS.disabled\n  | typeof REASONS.imperativeAction\n  | typeof REASONS.none;\n\nexport type TooltipRootChangeEventDetails =\n  BaseUIChangeEventDetails<TooltipRoot.ChangeEventReason> & {\n    preventUnmountOnClose(): void;\n  };\n\nexport namespace TooltipRoot {\n  export type State = TooltipRootState;\n  export type Props<Payload = unknown> = TooltipRootProps<Payload>;\n  export type Actions = TooltipRootActions;\n  export type ChangeEventReason = TooltipRootChangeEventReason;\n  export type ChangeEventDetails = TooltipRootChangeEventDetails;\n}\n"
  },
  {
    "path": "packages/react/src/tooltip/root/TooltipRootContext.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { TooltipStore } from '../store/TooltipStore';\n\nexport type TooltipRootContext<Payload = unknown> = TooltipStore<Payload>;\n\nexport const TooltipRootContext = React.createContext<TooltipRootContext | undefined>(undefined);\n\nexport function useTooltipRootContext(optional?: false): TooltipRootContext;\nexport function useTooltipRootContext(optional: true): TooltipRootContext | undefined;\nexport function useTooltipRootContext(optional?: boolean) {\n  const context = React.useContext(TooltipRootContext);\n  if (context === undefined && !optional) {\n    throw new Error(\n      'Base UI: TooltipRootContext is missing. Tooltip parts must be placed within <Tooltip.Root>.',\n    );\n  }\n\n  return context;\n}\n"
  },
  {
    "path": "packages/react/src/tooltip/store/TooltipHandle.ts",
    "content": "import { TooltipStore } from './TooltipStore';\nimport { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';\nimport { REASONS } from '../../utils/reasons';\n\n/**\n * A handle to control a tooltip imperatively and to associate detached triggers with it.\n */\nexport class TooltipHandle<Payload> {\n  /**\n   * Internal store holding the tooltip state.\n   * @internal\n   */\n  public readonly store: TooltipStore<Payload>;\n\n  constructor() {\n    this.store = new TooltipStore<Payload>();\n  }\n\n  /**\n   * Opens the tooltip and associates it with the trigger with the given ID.\n   * The trigger must be a Tooltip.Trigger component with this handle passed as a prop.\n   *\n   * This method should only be called in an event handler or an effect (not during rendering).\n   *\n   * @param triggerId ID of the trigger to associate with the tooltip.\n   */\n  open(triggerId: string) {\n    const triggerElement = triggerId\n      ? (this.store.context.triggerElements.getById(triggerId) as HTMLElement | undefined)\n      : undefined;\n\n    if (triggerId && !triggerElement) {\n      throw new Error(`Base UI: TooltipHandle.open: No trigger found with id \"${triggerId}\".`);\n    }\n\n    this.store.setOpen(\n      true,\n      createChangeEventDetails(REASONS.imperativeAction, undefined, triggerElement),\n    );\n  }\n\n  /**\n   * Closes the tooltip.\n   */\n  close() {\n    this.store.setOpen(\n      false,\n      createChangeEventDetails(REASONS.imperativeAction, undefined, undefined),\n    );\n  }\n\n  /**\n   * Indicates whether the tooltip is currently open.\n   */\n  get isOpen() {\n    return this.store.state.open;\n  }\n}\n\n/**\n * Creates a new handle to connect a Tooltip.Root with detached Tooltip.Trigger components.\n */\nexport function createTooltipHandle<Payload>(): TooltipHandle<Payload> {\n  return new TooltipHandle<Payload>();\n}\n"
  },
  {
    "path": "packages/react/src/tooltip/store/TooltipStore.ts",
    "content": "import * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport { createSelector, ReactStore } from '@base-ui/utils/store';\nimport { useRefWithInit } from '@base-ui/utils/useRefWithInit';\nimport { type TooltipRoot } from '../root/TooltipRoot';\nimport { useSyncedFloatingRootContext } from '../../floating-ui-react';\nimport { REASONS } from '../../utils/reasons';\nimport {\n  createInitialPopupStoreState,\n  PopupStoreContext,\n  popupStoreSelectors,\n  PopupStoreState,\n  PopupTriggerMap,\n} from '../../utils/popups';\n\nexport type State<Payload> = PopupStoreState<Payload> & {\n  disabled: boolean;\n  instantType: 'delay' | 'dismiss' | 'focus' | undefined;\n  isInstantPhase: boolean;\n  trackCursorAxis: 'none' | 'x' | 'y' | 'both';\n  disableHoverablePopup: boolean;\n  openChangeReason: TooltipRoot.ChangeEventReason | null;\n  closeOnClick: boolean;\n  closeDelay: number;\n  hasViewport: boolean;\n};\n\nexport type Context = PopupStoreContext<TooltipRoot.ChangeEventDetails> & {\n  readonly popupRef: React.RefObject<HTMLElement | null>;\n};\n\nconst selectors = {\n  ...popupStoreSelectors,\n  disabled: createSelector((state: State<unknown>) => state.disabled),\n  instantType: createSelector((state: State<unknown>) => state.instantType),\n  isInstantPhase: createSelector((state: State<unknown>) => state.isInstantPhase),\n  trackCursorAxis: createSelector((state: State<unknown>) => state.trackCursorAxis),\n  disableHoverablePopup: createSelector((state: State<unknown>) => state.disableHoverablePopup),\n  lastOpenChangeReason: createSelector((state: State<unknown>) => state.openChangeReason),\n  closeOnClick: createSelector((state: State<unknown>) => state.closeOnClick),\n  closeDelay: createSelector((state: State<unknown>) => state.closeDelay),\n  hasViewport: createSelector((state: State<unknown>) => state.hasViewport),\n};\n\nexport class TooltipStore<Payload> extends ReactStore<\n  Readonly<State<Payload>>,\n  Context,\n  typeof selectors\n> {\n  constructor(initialState?: Partial<State<Payload>>) {\n    super(\n      { ...createInitialState(), ...initialState },\n      {\n        popupRef: React.createRef<HTMLElement | null>(),\n        onOpenChange: undefined,\n        onOpenChangeComplete: undefined,\n        triggerElements: new PopupTriggerMap(),\n      },\n      selectors,\n    );\n  }\n\n  setOpen = (\n    nextOpen: boolean,\n    eventDetails: Omit<TooltipRoot.ChangeEventDetails, 'preventUnmountOnClose'>,\n  ) => {\n    const reason = eventDetails.reason;\n\n    const isHover = reason === REASONS.triggerHover;\n    const isFocusOpen = nextOpen && reason === REASONS.triggerFocus;\n    const isDismissClose =\n      !nextOpen && (reason === REASONS.triggerPress || reason === REASONS.escapeKey);\n\n    (eventDetails as TooltipRoot.ChangeEventDetails).preventUnmountOnClose = () => {\n      this.set('preventUnmountingOnClose', true);\n    };\n\n    this.context.onOpenChange?.(nextOpen, eventDetails as TooltipRoot.ChangeEventDetails);\n\n    if (eventDetails.isCanceled) {\n      return;\n    }\n\n    const changeState = () => {\n      const updatedState: Partial<State<Payload>> = { open: nextOpen, openChangeReason: reason };\n\n      if (isFocusOpen) {\n        updatedState.instantType = 'focus';\n      } else if (isDismissClose) {\n        updatedState.instantType = 'dismiss';\n      } else if (reason === REASONS.triggerHover) {\n        updatedState.instantType = undefined;\n      }\n\n      // If a popup is closing, the `trigger` may be null.\n      // We want to keep the previous value so that exit animations are played and focus is returned correctly.\n      const newTriggerId = eventDetails.trigger?.id ?? null;\n      if (newTriggerId || nextOpen) {\n        updatedState.activeTriggerId = newTriggerId;\n        updatedState.activeTriggerElement = eventDetails.trigger ?? null;\n      }\n\n      this.update(updatedState);\n    };\n\n    if (isHover) {\n      // If a hover reason is provided, we need to flush the state synchronously. This ensures\n      // `node.getAnimations()` knows about the new state.\n      ReactDOM.flushSync(changeState);\n    } else {\n      changeState();\n    }\n  };\n\n  static useStore<Payload>(\n    externalStore: TooltipStore<Payload> | undefined,\n    initialState?: Partial<State<Payload>>,\n  ) {\n    // eslint-disable-next-line react-hooks/rules-of-hooks\n    const internalStore = useRefWithInit(() => {\n      return new TooltipStore<Payload>(initialState);\n    }).current;\n\n    const store = externalStore ?? internalStore;\n\n    // eslint-disable-next-line react-hooks/rules-of-hooks\n    const floatingRootContext = useSyncedFloatingRootContext({\n      popupStore: store,\n      onOpenChange: store.setOpen,\n    });\n\n    // It's safe to set this here because when this code runs for the first time,\n    // nothing has had a chance to subscribe to the `store` yet.\n    // For subsequent renders, the `floatingRootContext` reference remains the same,\n    // so it's basically a no-op.\n    (store.state as State<any>).floatingRootContext = floatingRootContext;\n    return store;\n  }\n}\n\nfunction createInitialState<Payload>(): State<Payload> {\n  return {\n    ...createInitialPopupStoreState(),\n    disabled: false,\n    instantType: undefined,\n    isInstantPhase: false,\n    trackCursorAxis: 'none',\n    disableHoverablePopup: false,\n    openChangeReason: null,\n    closeOnClick: true,\n    closeDelay: 0,\n    hasViewport: false,\n  };\n}\n"
  },
  {
    "path": "packages/react/src/tooltip/trigger/TooltipTrigger.spec.tsx",
    "content": "import type * as React from 'react';\nimport { Tooltip } from '@base-ui/react/tooltip';\n\n// `props: any` will error\n<Tooltip.Trigger render={(props) => <button type=\"button\" {...props} />} />;\n<Tooltip.Trigger render={(props) => <input {...props} />} />;\n"
  },
  {
    "path": "packages/react/src/tooltip/trigger/TooltipTrigger.test.tsx",
    "content": "import { expect } from 'vitest';\nimport * as React from 'react';\nimport { Tooltip } from '@base-ui/react/tooltip';\nimport { screen, waitFor } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance } from '#test-utils';\n\ndescribe('<Tooltip.Trigger />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Tooltip.Trigger />, () => ({\n    refInstanceof: window.HTMLButtonElement,\n    render(node) {\n      return render(<Tooltip.Root>{node}</Tooltip.Root>);\n    },\n  }));\n\n  it('removes `data-popup-open` as soon as `open` becomes false', async () => {\n    function TooltipWithPreventedUnmount() {\n      const [open, setOpen] = React.useState(false);\n\n      return (\n        <Tooltip.Root\n          open={open}\n          onOpenChange={(nextOpen, eventDetails) => {\n            if (!nextOpen) {\n              eventDetails.preventUnmountOnClose();\n            }\n            setOpen(nextOpen);\n          }}\n        >\n          <Tooltip.Trigger data-testid=\"trigger\" delay={0} closeDelay={0}>\n            Trigger\n          </Tooltip.Trigger>\n          <Tooltip.Portal>\n            <Tooltip.Positioner>\n              <Tooltip.Popup>Content</Tooltip.Popup>\n            </Tooltip.Positioner>\n          </Tooltip.Portal>\n        </Tooltip.Root>\n      );\n    }\n\n    const { user } = await render(<TooltipWithPreventedUnmount />);\n    const trigger = screen.getByTestId('trigger');\n\n    await user.hover(trigger);\n    await waitFor(() => {\n      expect(trigger).toHaveAttribute('data-popup-open');\n    });\n\n    await user.unhover(trigger);\n    await waitFor(() => {\n      expect(trigger).not.toHaveAttribute('data-popup-open');\n    });\n\n    expect(screen.getByText('Content')).not.toBe(null);\n  });\n});\n"
  },
  {
    "path": "packages/react/src/tooltip/trigger/TooltipTrigger.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { fastComponentRef } from '@base-ui/utils/fastHooks';\nimport { useTooltipRootContext } from '../root/TooltipRootContext';\nimport type { BaseUIComponentProps } from '../../utils/types';\nimport { triggerOpenStateMapping } from '../../utils/popupStateMapping';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { useTriggerDataForwarding } from '../../utils/popups';\nimport { useBaseUiId } from '../../utils/useBaseUiId';\nimport { TooltipHandle } from '../store/TooltipHandle';\nimport { useTooltipProviderContext } from '../provider/TooltipProviderContext';\nimport {\n  safePolygon,\n  useDelayGroup,\n  useFocus,\n  useHoverReferenceInteraction,\n} from '../../floating-ui-react';\nimport { TooltipTriggerDataAttributes } from './TooltipTriggerDataAttributes';\n\nimport { OPEN_DELAY } from '../utils/constants';\n\n/**\n * An element to attach the tooltip to.\n * Renders a `<button>` element.\n *\n * Documentation: [Base UI Tooltip](https://base-ui.com/react/components/tooltip)\n */\nexport const TooltipTrigger = fastComponentRef(function TooltipTrigger(\n  componentProps: TooltipTrigger.Props,\n  forwardedRef: React.ForwardedRef<Element>,\n) {\n  const {\n    className,\n    render,\n    handle,\n    payload,\n    disabled: disabledProp,\n    delay,\n    closeOnClick = true,\n    closeDelay,\n    id: idProp,\n    ...elementProps\n  } = componentProps;\n\n  const rootContext = useTooltipRootContext(true);\n  const store = handle?.store ?? rootContext;\n  if (!store) {\n    throw new Error(\n      'Base UI: <Tooltip.Trigger> must be either used within a <Tooltip.Root> component or provided with a handle.',\n    );\n  }\n\n  const thisTriggerId = useBaseUiId(idProp);\n  const isTriggerActive = store.useState('isTriggerActive', thisTriggerId);\n  const isOpenedByThisTrigger = store.useState('isOpenedByTrigger', thisTriggerId);\n  const floatingRootContext = store.useState('floatingRootContext');\n\n  const triggerElementRef = React.useRef<Element | null>(null);\n\n  const delayWithDefault = delay ?? OPEN_DELAY;\n  const closeDelayWithDefault = closeDelay ?? 0;\n\n  const { registerTrigger, isMountedByThisTrigger } = useTriggerDataForwarding(\n    thisTriggerId,\n    triggerElementRef,\n    store,\n    {\n      payload,\n      closeOnClick,\n      closeDelay: closeDelayWithDefault,\n    },\n  );\n\n  const providerContext = useTooltipProviderContext();\n  const { delayRef, isInstantPhase, hasProvider } = useDelayGroup(floatingRootContext, {\n    open: isOpenedByThisTrigger,\n  });\n\n  store.useSyncedValue('isInstantPhase', isInstantPhase);\n\n  const rootDisabled = store.useState('disabled');\n  const disabled = disabledProp ?? rootDisabled;\n  const trackCursorAxis = store.useState('trackCursorAxis');\n  const disableHoverablePopup = store.useState('disableHoverablePopup');\n\n  const hoverProps = useHoverReferenceInteraction(floatingRootContext, {\n    enabled: !disabled,\n    mouseOnly: true,\n    move: false,\n    handleClose: !disableHoverablePopup && trackCursorAxis !== 'both' ? safePolygon() : null,\n    restMs() {\n      const providerDelay = providerContext?.delay;\n      const groupOpenValue =\n        typeof delayRef.current === 'object' ? delayRef.current.open : undefined;\n\n      let computedRestMs = delayWithDefault;\n      if (hasProvider) {\n        if (groupOpenValue !== 0) {\n          computedRestMs = delay ?? providerDelay ?? delayWithDefault;\n        } else {\n          computedRestMs = 0;\n        }\n      }\n\n      return computedRestMs;\n    },\n    delay() {\n      const closeValue = typeof delayRef.current === 'object' ? delayRef.current.close : undefined;\n\n      let computedCloseDelay: number | undefined = closeDelayWithDefault;\n      if (closeDelay == null && hasProvider) {\n        computedCloseDelay = closeValue;\n      }\n\n      return {\n        close: computedCloseDelay,\n      };\n    },\n    triggerElementRef,\n    isActiveTrigger: isTriggerActive,\n  });\n\n  const focusProps = useFocus(floatingRootContext, { enabled: !disabled }).reference;\n\n  const state: TooltipTriggerState = { open: isOpenedByThisTrigger };\n\n  const rootTriggerProps = store.useState('triggerProps', isMountedByThisTrigger);\n\n  const element = useRenderElement('button', componentProps, {\n    state,\n    ref: [forwardedRef, registerTrigger, triggerElementRef],\n    props: [\n      hoverProps,\n      focusProps,\n      rootTriggerProps,\n      {\n        onPointerDown() {\n          store.set('closeOnClick', closeOnClick);\n        },\n        id: thisTriggerId,\n        [TooltipTriggerDataAttributes.triggerDisabled]: disabled ? '' : undefined,\n      } as React.HTMLAttributes<Element>,\n      elementProps,\n    ],\n    stateAttributesMapping: triggerOpenStateMapping,\n  });\n\n  return element;\n}) as TooltipTrigger;\n\nexport interface TooltipTrigger {\n  <Payload>(\n    componentProps: TooltipTrigger.Props<Payload> & React.RefAttributes<HTMLElement>,\n  ): React.JSX.Element;\n}\n\nexport interface TooltipTriggerState {\n  /**\n   * Whether the tooltip is currently open.\n   */\n  open: boolean;\n}\n\nexport interface TooltipTriggerProps<Payload = unknown> extends BaseUIComponentProps<\n  'button',\n  TooltipTriggerState\n> {\n  /**\n   * A handle to associate the trigger with a tooltip.\n   */\n  handle?: TooltipHandle<Payload> | undefined;\n  /**\n   * A payload to pass to the tooltip when it is opened.\n   */\n  payload?: Payload | undefined;\n  /**\n   * How long to wait before opening the tooltip. Specified in milliseconds.\n   * @default 600\n   */\n  delay?: number | undefined;\n  /**\n   * Whether the tooltip should close when this trigger is clicked.\n   * @default true\n   */\n  closeOnClick?: boolean | undefined;\n  /**\n   * How long to wait before closing the tooltip. Specified in milliseconds.\n   * @default 0\n   */\n  closeDelay?: number | undefined;\n  /**\n   * If `true`, the tooltip will not open when interacting with this trigger.\n   * Note that this doesn't apply the `disabled` attribute to the trigger element.\n   * If you want to disable the trigger element itself, you can pass the `disabled` prop to the trigger element via the `render` prop.\n   * @default false\n   */\n  disabled?: boolean | undefined;\n}\n\nexport namespace TooltipTrigger {\n  export type State = TooltipTriggerState;\n  export type Props<Payload = unknown> = TooltipTriggerProps<Payload>;\n}\n"
  },
  {
    "path": "packages/react/src/tooltip/trigger/TooltipTriggerDataAttributes.ts",
    "content": "import { CommonTriggerDataAttributes } from '../../utils/popupStateMapping';\n\nexport enum TooltipTriggerDataAttributes {\n  /**\n   * Present when the corresponding tooltip is open.\n   */\n  popupOpen = CommonTriggerDataAttributes.popupOpen,\n  /**\n   * Present when the trigger is disabled, either by the `disabled` prop or by a parent `<Tooltip.Root>` component.\n   */\n  triggerDisabled = 'data-trigger-disabled',\n}\n"
  },
  {
    "path": "packages/react/src/tooltip/utils/constants.ts",
    "content": "export const OPEN_DELAY = 600;\n"
  },
  {
    "path": "packages/react/src/tooltip/viewport/TooltipViewport.test.tsx",
    "content": "import { expect } from 'vitest';\nimport * as React from 'react';\nimport { Tooltip } from '@base-ui/react/tooltip';\nimport { act, ignoreActWarnings, screen, waitFor } from '@mui/internal-test-utils';\nimport { createRenderer, describeConformance, isJSDOM, waitSingleFrame } from '#test-utils';\n\ndescribe('<Tooltip.Viewport />', () => {\n  const { render } = createRenderer();\n\n  describeConformance(<Tooltip.Viewport />, () => ({\n    refInstanceof: window.HTMLDivElement,\n    render(node) {\n      return render(\n        <Tooltip.Root open>\n          <Tooltip.Trigger>Trigger</Tooltip.Trigger>\n          <Tooltip.Portal>\n            <Tooltip.Positioner>\n              <Tooltip.Popup>{node}</Tooltip.Popup>\n            </Tooltip.Positioner>\n          </Tooltip.Portal>\n        </Tooltip.Root>,\n      );\n    },\n  }));\n\n  it('should render children in the `current` container by default', async () => {\n    await render(\n      <Tooltip.Root open>\n        <Tooltip.Trigger>Trigger</Tooltip.Trigger>\n        <Tooltip.Portal>\n          <Tooltip.Positioner>\n            <Tooltip.Popup>\n              <Tooltip.Viewport>\n                <div data-testid=\"content\">Content</div>\n              </Tooltip.Viewport>\n            </Tooltip.Popup>\n          </Tooltip.Positioner>\n        </Tooltip.Portal>\n      </Tooltip.Root>,\n    );\n\n    const currentContainer = screen.getByTestId('content').closest('[data-current]');\n    expect(currentContainer).not.toBe(null);\n    expect(currentContainer!.textContent).toBe('Content');\n  });\n\n  it('should remount the `current` container when the active trigger changes', async () => {\n    ignoreActWarnings();\n    await render(\n      <Tooltip.Root>\n        {({ payload }) => (\n          <React.Fragment>\n            <Tooltip.Trigger payload=\"first\" delay={0} data-testid=\"trigger1\">\n              Trigger 1\n            </Tooltip.Trigger>\n            <Tooltip.Trigger payload=\"second\" delay={0} data-testid=\"trigger2\">\n              Trigger 2\n            </Tooltip.Trigger>\n            <Tooltip.Portal>\n              <Tooltip.Positioner>\n                <Tooltip.Popup>\n                  <Tooltip.Viewport>\n                    {payload === 'first' ? (\n                      <img data-testid=\"payload-image-1\" src=\"about:blank\" alt=\"Preview 1\" />\n                    ) : null}\n                    {payload === 'second' ? (\n                      <img data-testid=\"payload-image-2\" src=\"about:blank\" alt=\"Preview 2\" />\n                    ) : null}\n                  </Tooltip.Viewport>\n                </Tooltip.Popup>\n              </Tooltip.Positioner>\n            </Tooltip.Portal>\n          </React.Fragment>\n        )}\n      </Tooltip.Root>,\n    );\n\n    const trigger1 = screen.getByTestId('trigger1');\n    const trigger2 = screen.getByTestId('trigger2');\n\n    await waitSingleFrame();\n    await act(async () => trigger1.focus());\n\n    const firstImage = await screen.findByTestId('payload-image-1');\n    const firstContainer = firstImage.closest('[data-current]');\n    expect(firstContainer).not.toBe(null);\n\n    await waitSingleFrame();\n    await act(async () => trigger2.focus());\n\n    await waitFor(() => {\n      const secondImage = screen.getByTestId('payload-image-2');\n      const secondContainer = secondImage.closest('[data-current]');\n      expect(secondContainer).not.toBe(null);\n      expect(secondContainer).not.toBe(firstContainer);\n    });\n  });\n\n  describe.skipIf(isJSDOM)('morphing containers with multiple triggers and payloads', () => {\n    beforeEach(() => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n    });\n\n    afterEach(() => {\n      globalThis.BASE_UI_ANIMATIONS_DISABLED = true;\n    });\n\n    it('should create morphing containers during transitions', async () => {\n      ignoreActWarnings();\n      await render(\n        <div>\n          <style>\n            {`\n              [data-transitioning] [data-previous] {\n                animation: slide-out 0.3s ease-out forwards;\n              }\n              [data-transitioning] [data-current] {\n                animation: slide-in 0.3s ease-out forwards;\n              }\n              @keyframes slide-out {\n                from { transform: translateX(0); opacity: 1; }\n                to { transform: translateX(-30%); opacity: 0; }\n              }\n              @keyframes slide-in {\n                from { transform: translateX(30%); opacity: 0; }\n                to { transform: translateX(0); opacity: 1; }\n              }\n            `}\n          </style>\n          <Tooltip.Root>\n            {({ payload }) => (\n              <React.Fragment>\n                <Tooltip.Trigger\n                  payload={0}\n                  delay={0}\n                  data-testid=\"trigger1\"\n                  style={{\n                    position: 'absolute',\n                    top: '10px',\n                    left: '10px',\n                    width: '100px',\n                    height: '50px',\n                  }}\n                >\n                  Trigger 1\n                </Tooltip.Trigger>\n                <Tooltip.Trigger\n                  payload={1}\n                  delay={0}\n                  data-testid=\"trigger2\"\n                  style={{\n                    position: 'absolute',\n                    top: '100px',\n                    left: '200px',\n                    width: '100px',\n                    height: '50px',\n                  }}\n                >\n                  Trigger 2\n                </Tooltip.Trigger>\n                <Tooltip.Portal>\n                  <Tooltip.Positioner>\n                    <Tooltip.Popup>\n                      <Tooltip.Viewport>\n                        <div data-testid=\"content\">Content {payload as number}</div>\n                      </Tooltip.Viewport>\n                    </Tooltip.Popup>\n                  </Tooltip.Positioner>\n                </Tooltip.Portal>\n              </React.Fragment>\n            )}\n          </Tooltip.Root>\n        </div>,\n      );\n\n      const trigger1 = screen.getByTestId('trigger1');\n      const trigger2 = screen.getByTestId('trigger2');\n\n      await waitSingleFrame();\n      await act(async () => trigger1.focus());\n\n      await waitFor(() => {\n        expect(screen.getByText('Content 0')).toBeVisible();\n      });\n\n      await waitSingleFrame();\n      await act(async () => trigger2.focus());\n\n      // Check for morphing containers during transition\n      let previousContainer: HTMLElement | null = null;\n      await waitFor(() => {\n        previousContainer = document.querySelector('[data-previous]');\n        expect(previousContainer).not.toBe(null);\n      });\n\n      expect(previousContainer).toHaveAttribute('inert');\n      expect(previousContainer!.textContent).toBe('Content 0');\n\n      const nextContainer = document.querySelector('[data-current]');\n      expect(nextContainer).not.toBe(null);\n      expect(nextContainer!.textContent).toBe('Content 1');\n\n      // Verify they are cleaned up after animation\n      await waitFor(() => {\n        expect(document.querySelector('[data-previous]')).toBe(null);\n      });\n\n      expect(document.querySelector('[data-current]')).toBeVisible();\n      expect(await screen.findByText('Content 1')).toBeVisible();\n    });\n\n    it('should handle rapid trigger changes', async () => {\n      ignoreActWarnings();\n      function TestComponent() {\n        return (\n          <div>\n            <style>\n              {`\n              [data-transitioning] [data-previous] {\n                animation: slide-out 0.2s ease-out forwards;\n              }\n              [data-transitioning] [data-current] {\n                animation: slide-in 0.2s ease-out forwards;\n              }\n              @keyframes slide-out {\n                from { transform: translateX(0); opacity: 1; }\n                to { transform: translateX(-30%); opacity: 0; }\n              }\n              @keyframes slide-in {\n                from { transform: translateX(30%); opacity: 0; }\n                to { transform: translateX(0); opacity: 1; }\n              }\n            `}\n            </style>\n            <Tooltip.Root>\n              {({ payload }) => (\n                <React.Fragment>\n                  <Tooltip.Trigger payload={1} delay={0} data-testid=\"trigger1\">\n                    Trigger 1\n                  </Tooltip.Trigger>\n                  <Tooltip.Trigger payload={2} delay={0} data-testid=\"trigger2\">\n                    Trigger 2\n                  </Tooltip.Trigger>\n                  <Tooltip.Trigger payload={3} delay={0} data-testid=\"trigger3\">\n                    Trigger 3\n                  </Tooltip.Trigger>\n                  <Tooltip.Portal>\n                    <Tooltip.Positioner>\n                      <Tooltip.Popup>\n                        <Tooltip.Viewport>Content {payload as number}</Tooltip.Viewport>\n                      </Tooltip.Popup>\n                    </Tooltip.Positioner>\n                  </Tooltip.Portal>\n                </React.Fragment>\n              )}\n            </Tooltip.Root>\n          </div>\n        );\n      }\n\n      await render(<TestComponent />);\n\n      const trigger1 = screen.getByTestId('trigger1');\n      const trigger2 = screen.getByTestId('trigger2');\n      const trigger3 = screen.getByTestId('trigger3');\n\n      await waitSingleFrame();\n      await act(async () => trigger1.focus());\n      await waitSingleFrame();\n      await act(async () => trigger2.focus());\n      await waitSingleFrame();\n      await act(async () => trigger3.focus());\n      await waitSingleFrame();\n      await act(async () => trigger1.focus());\n\n      await waitFor(async () => {\n        expect(await screen.findByText('Content 1')).toBeVisible();\n      });\n    });\n\n    it.each([\n      {\n        name: 'should calculate \"right down\" direction',\n        trigger1: { top: 10, left: 10 },\n        trigger2: { top: 100, left: 200 },\n        expectedDirection: ['right', 'down'],\n      },\n      {\n        name: 'should calculate \"left up\" direction',\n        trigger1: { top: 100, left: 200 },\n        trigger2: { top: 10, left: 10 },\n        expectedDirection: ['left', 'up'],\n      },\n      {\n        name: 'should calculate \"right\" direction (horizontal only)',\n        trigger1: { top: 50, left: 10 },\n        trigger2: { top: 52, left: 200 }, // 2px vertical difference within tolerance\n        expectedDirection: ['right'],\n      },\n      {\n        name: 'should calculate \"down\" direction (vertical only)',\n        trigger1: { top: 10, left: 50 },\n        trigger2: { top: 100, left: 52 }, // 2px horizontal difference within tolerance\n        expectedDirection: ['down'],\n      },\n      {\n        name: 'should handle tolerance for small differences',\n        trigger1: { top: 50, left: 50 },\n        trigger2: { top: 52, left: 52 }, // Both differences within 5px tolerance\n        expectedDirection: [],\n      },\n      {\n        name: 'should calculate \"left down\" direction',\n        trigger1: { top: 10, left: 200 },\n        trigger2: { top: 100, left: 10 },\n        expectedDirection: ['left', 'down'],\n      },\n      {\n        name: 'should calculate \"right up\" direction',\n        trigger1: { top: 100, left: 10 },\n        trigger2: { top: 10, left: 200 },\n        expectedDirection: ['right', 'up'],\n      },\n    ])('$name', async ({ trigger1, trigger2, expectedDirection }) => {\n      ignoreActWarnings();\n      await render(\n        <div>\n          <style>\n            {`\n              [data-transitioning] [data-previous] {\n                animation: slide-out 0.2s ease-out forwards;\n              }\n              [data-transitioning] [data-current] {\n                animation: slide-in 0.2s ease-out forwards;\n              }\n              @keyframes slide-out {\n                from { transform: translateX(0); opacity: 1; }\n                to { transform: translateX(-30%); opacity: 0; }\n              }\n              @keyframes slide-in {\n                from { transform: translateX(30%); opacity: 0; }\n                to { transform: translateX(0); opacity: 1; }\n              }\n            `}\n          </style>\n          <Tooltip.Root>\n            {({ payload }) => (\n              <React.Fragment>\n                <Tooltip.Trigger\n                  payload={0}\n                  delay={0}\n                  data-testid=\"trigger1\"\n                  style={{\n                    position: 'absolute',\n                    top: `${trigger1.top}px`,\n                    left: `${trigger1.left}px`,\n                    width: '100px',\n                    height: '50px',\n                  }}\n                >\n                  Trigger 1\n                </Tooltip.Trigger>\n                <Tooltip.Trigger\n                  payload={1}\n                  delay={0}\n                  data-testid=\"trigger2\"\n                  style={{\n                    position: 'absolute',\n                    top: `${trigger2.top}px`,\n                    left: `${trigger2.left}px`,\n                    width: '100px',\n                    height: '50px',\n                  }}\n                >\n                  Trigger 2\n                </Tooltip.Trigger>\n                <Tooltip.Portal>\n                  <Tooltip.Positioner>\n                    <Tooltip.Popup>\n                      <Tooltip.Viewport data-testid=\"viewport\">\n                        <div data-testid=\"content\">Content {payload as number}</div>\n                      </Tooltip.Viewport>\n                    </Tooltip.Popup>\n                  </Tooltip.Positioner>\n                </Tooltip.Portal>\n              </React.Fragment>\n            )}\n          </Tooltip.Root>\n        </div>,\n      );\n\n      const triggerElement1 = screen.getByTestId('trigger1');\n      const triggerElement2 = screen.getByTestId('trigger2');\n\n      await waitSingleFrame();\n      await act(async () => triggerElement1.focus());\n\n      await waitFor(() => {\n        expect(screen.getByText('Content 0')).toBeVisible();\n      });\n\n      await waitSingleFrame();\n      await act(async () => triggerElement2.focus());\n\n      const viewport = screen.getByTestId('viewport');\n      await waitFor(() => {\n        expect(viewport).toHaveAttribute('data-activation-direction');\n      });\n\n      const direction = viewport.getAttribute('data-activation-direction');\n\n      if (expectedDirection.length === 0) {\n        expect(direction?.trim()).toBe('');\n      } else {\n        expectedDirection.forEach((dir) => {\n          expect(direction).toContain(dir);\n        });\n      }\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/tooltip/viewport/TooltipViewport.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useTooltipRootContext } from '../root/TooltipRootContext';\nimport { useTooltipPositionerContext } from '../positioner/TooltipPositionerContext';\nimport { BaseUIComponentProps } from '../../utils/types';\nimport { useRenderElement } from '../../utils/useRenderElement';\nimport { StateAttributesMapping } from '../../utils/getStateAttributesProps';\nimport { TooltipViewportCssVars } from './TooltipViewportCssVars';\nimport { usePopupViewport } from '../../utils/usePopupViewport';\n\nconst stateAttributesMapping: StateAttributesMapping<TooltipViewportState> = {\n  activationDirection: (value) =>\n    value\n      ? {\n          'data-activation-direction': value,\n        }\n      : null,\n};\n\n/**\n * A viewport for displaying content transitions.\n * This component is only required if one popup can be opened by multiple triggers, its content change based on the trigger\n * and switching between them is animated.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Tooltip](https://base-ui.com/react/components/tooltip)\n */\nexport const TooltipViewport = React.forwardRef(function TooltipViewport(\n  componentProps: TooltipViewport.Props,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { render, className, children, ...elementProps } = componentProps;\n  const store = useTooltipRootContext();\n  const positioner = useTooltipPositionerContext();\n\n  const instantType = store.useState('instantType');\n\n  const { children: childrenToRender, state: viewportState } = usePopupViewport({\n    store,\n    side: positioner.side,\n    cssVars: TooltipViewportCssVars,\n    children,\n  });\n\n  const state: TooltipViewportState = {\n    activationDirection: viewportState.activationDirection,\n    transitioning: viewportState.transitioning,\n    instant: instantType,\n  };\n\n  return useRenderElement('div', componentProps, {\n    state,\n    ref: forwardedRef,\n    props: [elementProps, { children: childrenToRender }],\n    stateAttributesMapping,\n  });\n});\n\nexport interface TooltipViewportState {\n  /**\n   * The activation direction of the transitioned content.\n   */\n  activationDirection: string | undefined;\n  /**\n   * Whether the viewport is currently transitioning between contents.\n   */\n  transitioning: boolean;\n  /**\n   * Present if animations should be instant.\n   */\n  instant: 'delay' | 'dismiss' | 'focus' | undefined;\n}\n\nexport namespace TooltipViewport {\n  export interface Props extends BaseUIComponentProps<'div', TooltipViewportState> {\n    /**\n     * The content to render inside the transition container.\n     */\n    children?: React.ReactNode;\n  }\n\n  export type State = TooltipViewportState;\n}\n"
  },
  {
    "path": "packages/react/src/tooltip/viewport/TooltipViewportCssVars.ts",
    "content": "export enum TooltipViewportCssVars {\n  /**\n   * The width of the parent popup.\n   * This variable is placed on the 'previous' container and stores the width of the popup when the previous content was rendered.\n   * It can be used to freeze the dimensions of the popup when animating between different content.\n   */\n  popupWidth = '--popup-width',\n  /**\n   * The height of the parent popup.\n   * This variable is placed on the 'previous' container and stores the height of the popup when the previous content was rendered.\n   * It can be used to freeze the dimensions of the popup when animating between different content.\n   */\n  popupHeight = '--popup-height',\n}\n"
  },
  {
    "path": "packages/react/src/tooltip/viewport/TooltipViewportDataAttributes.ts",
    "content": "export enum TooltipViewportDataAttributes {\n  /**\n   * Applied to the direct child of the viewport when no transitions are present or the new content when it's entering.\n   */\n  current = 'data-current',\n  /**\n   * Applied to the direct child of the viewport that contains the exiting content when transitions are present.\n   */\n  previous = 'data-previous',\n  /**\n   * Indicates the direction from which the popup was activated.\n   * This can be used to create directional animations based on how the popup was triggered.\n   * Contains space-separated values for both horizontal and vertical axes.\n   * @type {`${'left' | 'right'} {'top' | 'bottom'}`}\n   */\n  activationDirection = 'data-activation-direction',\n  /**\n   * Indicates that the viewport is currently transitioning between old and new content.\n   */\n  transitioning = 'data-transitioning',\n  /**\n   * Present if animations should be instant.\n   * @type {'delay' | 'dismiss' | 'focus'}\n   */\n  instant = 'data-instant',\n}\n"
  },
  {
    "path": "packages/react/src/types/index.ts",
    "content": "import type * as React from 'react';\n\nexport type {\n  BaseUIChangeEventDetails,\n  BaseUIGenericEventDetails,\n} from '../utils/createBaseUIEventDetails';\n\nexport type HTMLProps<T = any> = React.HTMLAttributes<T> & {\n  ref?: React.Ref<T> | undefined;\n};\n\n/**\n * Shape of the render prop: a function that takes props to be spread on the element and component's state and returns a React element.\n *\n * @template Props Props to be spread on the rendered element.\n * @template State Component's internal state.\n */\nexport type ComponentRenderFn<Props, State> = (\n  props: Props,\n  state: State,\n) => React.ReactElement<unknown>;\n\nexport type BaseUIEvent<E extends React.SyntheticEvent<Element, Event>> = E & {\n  preventBaseUIHandler: () => void;\n  readonly baseUIHandlerPrevented?: boolean | undefined;\n};\n"
  },
  {
    "path": "packages/react/src/types/temporal/index.ts",
    "content": "export * from './temporal-adapter';\nexport * from './temporal';\n"
  },
  {
    "path": "packages/react/src/types/temporal/temporal-adapter.ts",
    "content": "import { TemporalTimezone, TemporalSupportedObject, TemporalSupportedValue } from './temporal';\n\nexport interface TemporalAdapterFormats {\n  /**\n   * The 4-digit year.\n   * @example \"2019\"\n   */\n  yearPadded: string;\n  /**\n   * The month with leading zeros.\n   * @example \"08\"\n   */\n  monthPadded: string;\n  /**\n   * The day of the month with leading zeros.\n   * @example \"01\"\n   */\n  dayOfMonthPadded: string;\n  /**\n   * The hours with leading zeros, 24-hour clock.\n   * @example \"01\", \"23\"\n   */\n  hours24hPadded: string;\n  /**\n   * The hours with leading zeros, 12-hour clock.\n   * @example \"01\", \"11\"\n   */\n  hours12hPadded: string;\n  /**\n   * The minutes with leading zeros.\n   * @example \"01\", \"59\"\n   */\n  minutesPadded: string;\n  /**\n   * The seconds with leading zeros.\n   * @example \"01\", \"59\"\n   */\n  secondsPadded: string;\n  /**\n   * The day of the month without leading zeros.\n   * @example \"1\"\n   */\n  dayOfMonth: string;\n  /**\n   * The hours without leading zeros, 24-hour clock.\n   * @example \"1\", \"23\"\n   */\n  hours24h: string;\n  /**\n   * The hours without leading zeros, 12-hour clock.\n   * @example \"1\", \"11\"\n   */\n  hours12h: string;\n  /**\n   * The abbreviated month name.\n   * @example \"Aug\"\n   */\n  month3Letters: string;\n  /**\n   * The full month name.\n   * @example \"August\"\n   */\n  monthFullLetter: string;\n  /**\n   * The week day name.\n   * @example \"Wednesday\"\n   */\n  weekday: string;\n  /**\n   * The abbreviated week day name.\n   * @example \"Wed\"\n   * */\n  weekday3Letters: string;\n  /**\n   * The initial of the week day name.\n   * @example \"W\"\n   * */\n  weekday1Letter: string;\n  /**\n   * The meridiem.\n   * @example \"AM\"\n   */\n  meridiem: string;\n  /**\n   * The localized date format including year, month, day and weekday.\n   * @example \"Wednesday, August 6, 2014\"\n   */\n  localizedDateWithFullMonthAndWeekDay: string;\n  /**\n   * The localized numeric date format including year, month and day.\n   * @example \"8/6/2014\"\n   */\n  localizedNumericDate: string;\n}\n\nexport type DateBuilderReturnType<T extends string | null> = [T] extends [null]\n  ? null\n  : TemporalSupportedObject;\n\nexport interface TemporalAdapter {\n  isTimezoneCompatible: boolean;\n  formats: TemporalAdapterFormats;\n  /**\n   * Name of the library that is used right now.\n   */\n  lib: string;\n  /**\n   * Characters used to escape a string inside a format.\n   */\n  escapedCharacters: { start: string; end: string };\n  /**\n   * Creates a date in the date library format.\n   */\n  date<T extends string | null>(value: T, timezone: TemporalTimezone): DateBuilderReturnType<T>;\n  /**\n   * Parses a date from a string in the given format.\n   */\n  parse(value: string, format: string, timezone: TemporalTimezone): TemporalSupportedObject;\n  /**\n   * Creates a date in the date library format for the current time.\n   */\n  now(timezone: TemporalTimezone): TemporalSupportedObject;\n  /**\n   * Extracts the timezone from a date.\n   */\n  getTimezone(value: TemporalSupportedValue | null): TemporalTimezone;\n  /**\n   * Converts a date to another timezone.\n   */\n  setTimezone(value: TemporalSupportedObject, timezone: TemporalTimezone): TemporalSupportedObject;\n  /**\n   * Converts a date in the library format into a JavaScript `Date` object.\n   */\n  toJsDate(value: TemporalSupportedObject): Date;\n  /**\n   * Gets the code of the locale currently used by the adapter.\n   */\n  getCurrentLocaleCode(): string;\n  /**\n   * Checks if the date is valid.\n   */\n  isValid(value: TemporalSupportedValue): value is TemporalSupportedObject;\n  /**\n   * Formats a date using an adapter format string (see the `AdapterFormats` interface).\n   */\n  format(value: TemporalSupportedObject, formatKey: keyof TemporalAdapterFormats): string;\n  /**\n   * Formats a date using a format of the date library.\n   */\n  formatByString(value: TemporalSupportedObject, formatString: string): string;\n  /**\n   * Checks if the two dates are equal (which means they represent the same timestamp).\n   */\n  isEqual(value: TemporalSupportedValue, comparing: TemporalSupportedValue): boolean;\n  /**\n   * Checks if the two dates are in the same year.\n   * Uses the timezone of the `value`.\n   */\n  isSameYear(value: TemporalSupportedObject, comparing: TemporalSupportedObject): boolean;\n  /**\n   * Checks if the two dates are in the same month.\n   * Uses the timezone of the `value`.\n   */\n  isSameMonth(value: TemporalSupportedObject, comparing: TemporalSupportedObject): boolean;\n  /**\n   * Checks if the two dates are in the same day.\n   * Uses the timezone of the `value`.\n   */\n  isSameDay(value: TemporalSupportedObject, comparing: TemporalSupportedObject): boolean;\n  /**\n   * Checks if the two dates are at the same hour.\n   * Uses the timezone of the `value`.\n   */\n  isSameHour(value: TemporalSupportedObject, comparing: TemporalSupportedObject): boolean;\n  /**\n   * Checks if the `value` date is after the `comparing` date.\n   */\n  isAfter(value: TemporalSupportedObject, comparing: TemporalSupportedObject): boolean;\n  /**\n   * Checks if the `value` date is before the `comparing` date.\n   */\n  isBefore(value: TemporalSupportedObject, comparing: TemporalSupportedObject): boolean;\n  /**\n   * Checks if the value is within the provided range.\n   */\n  isWithinRange(\n    value: TemporalSupportedObject,\n    range: [TemporalSupportedObject, TemporalSupportedObject],\n  ): boolean;\n  /**\n   * Returns the start of the year for the given date.\n   */\n  startOfYear(value: TemporalSupportedObject): TemporalSupportedObject;\n  /**\n   * Returns the start of the month for the given date.\n   */\n  startOfMonth(value: TemporalSupportedObject): TemporalSupportedObject;\n  /**\n   * Returns the start of the week for the given date.\n   */\n  startOfWeek(value: TemporalSupportedObject): TemporalSupportedObject;\n  /**\n   * Returns the start of the day for the given date.\n   */\n  startOfDay(value: TemporalSupportedObject): TemporalSupportedObject;\n  /**\n   * Returns the start of the hour for the given date.\n   */\n  startOfHour(value: TemporalSupportedObject): TemporalSupportedObject;\n  /**\n   * Returns the start of the minute for the given date.\n   */\n  startOfMinute(value: TemporalSupportedObject): TemporalSupportedObject;\n  /**\n   * Returns the start of the second for the given date.\n   */\n  startOfSecond(value: TemporalSupportedObject): TemporalSupportedObject;\n  /**\n   * Returns the end of the year for the given date.\n   */\n  endOfYear(value: TemporalSupportedObject): TemporalSupportedObject;\n  /**\n   * Returns the end of the month for the given date.\n   */\n  endOfMonth(value: TemporalSupportedObject): TemporalSupportedObject;\n  /**\n   * Returns the end of the week for the given date.\n   */\n  endOfWeek(value: TemporalSupportedObject): TemporalSupportedObject;\n  /**\n   * Returns the end of the day for the given date.\n   */\n  endOfDay(value: TemporalSupportedObject): TemporalSupportedObject;\n  /**\n   * Returns the end of the hour for the given date.\n   */\n  endOfHour(value: TemporalSupportedObject): TemporalSupportedObject;\n  /**\n   * Returns the end of the minute for the given date.\n   */\n  endOfMinute(value: TemporalSupportedObject): TemporalSupportedObject;\n  /**\n   * Returns the end of the second for the given date.\n   */\n  endOfSecond(value: TemporalSupportedObject): TemporalSupportedObject;\n  /**\n   * Adds the specified number of years to the given date.\n   */\n  addYears(value: TemporalSupportedObject, amount: number): TemporalSupportedObject;\n  /**\n   * Adds the specified number of months to the given date.\n   */\n  addMonths(value: TemporalSupportedObject, amount: number): TemporalSupportedObject;\n  /**\n   * Adds the specified number of weeks to the given date.\n   */\n  addWeeks(value: TemporalSupportedObject, amount: number): TemporalSupportedObject;\n  /**\n   * Adds the specified number of days to the given date.\n   */\n  addDays(value: TemporalSupportedObject, amount: number): TemporalSupportedObject;\n  /**\n   * Adds the specified number of hours to the given date.\n   */\n  addHours(value: TemporalSupportedObject, amount: number): TemporalSupportedObject;\n  /**\n   * Adds the specified number of minutes to the given date.\n   */\n  addMinutes(value: TemporalSupportedObject, amount: number): TemporalSupportedObject;\n  /**\n   * Adds the specified number of seconds to the given date.\n   */\n  addSeconds(value: TemporalSupportedObject, amount: number): TemporalSupportedObject;\n  /**\n   * Adds the specified number of milliseconds to the given date.\n   */\n  addMilliseconds(value: TemporalSupportedObject, amount: number): TemporalSupportedObject;\n  /**\n   * Gets the year of the given date.\n   */\n  getYear(value: TemporalSupportedObject): number;\n  /**\n   * Gets the month of the given date.\n   * The value is 0-based, in the Gregorian calendar January = 0, February = 1, ...\n   */\n  getMonth(value: TemporalSupportedObject): number;\n  /**\n   * Gets the date (day in the month) of the given date.\n   */\n  getDate(value: TemporalSupportedObject): number;\n  /**\n   * Gets the hours of the given date.\n   */\n  getHours(value: TemporalSupportedObject): number;\n  /**\n   * Gets the minutes of the given date.\n   */\n  getMinutes(value: TemporalSupportedObject): number;\n  /**\n   * Gets the seconds of the given date.\n   */\n  getSeconds(value: TemporalSupportedObject): number;\n  /**\n   * Gets the milliseconds of the given date.\n   */\n  getMilliseconds(value: TemporalSupportedObject): number;\n  /**\n   * Gets the time since epoch of the given date.\n   */\n  getTime(value: TemporalSupportedObject): number;\n  /**\n   * Sets the year to the given date.\n   */\n  setYear(value: TemporalSupportedObject, year: number): TemporalSupportedObject;\n  /**\n   * Sets the month to the given date.\n   */\n  setMonth(value: TemporalSupportedObject, month: number): TemporalSupportedObject;\n  /**\n   * Sets the date (day in the month) to the given date.\n   */\n  setDate(value: TemporalSupportedObject, date: number): TemporalSupportedObject;\n  /**\n   * Sets the hours to the given date.\n   */\n  setHours(value: TemporalSupportedObject, hours: number): TemporalSupportedObject;\n  /**\n   * Sets the minutes to the given date.\n   */\n  setMinutes(value: TemporalSupportedObject, minutes: number): TemporalSupportedObject;\n  /**\n   * Sets the seconds to the given date.\n   */\n  setSeconds(value: TemporalSupportedObject, seconds: number): TemporalSupportedObject;\n  /**\n   * Sets the milliseconds to the given date.\n   */\n  setMilliseconds(value: TemporalSupportedObject, milliseconds: number): TemporalSupportedObject;\n  /**\n   * Gets the number of full years between the given dates.\n   */\n  differenceInYears: (value: TemporalSupportedObject, comparing: TemporalSupportedObject) => number;\n  /**\n   * Gets the number of full months between the given dates.\n   */\n  differenceInMonths: (\n    value: TemporalSupportedObject,\n    comparing: TemporalSupportedObject,\n  ) => number;\n  /**\n   * Gets the number of full weeks between the given dates.\n   */\n  differenceInWeeks: (value: TemporalSupportedObject, comparing: TemporalSupportedObject) => number;\n  /**\n   * Gets the number of full days between the given dates.\n   */\n  differenceInDays: (value: TemporalSupportedObject, comparing: TemporalSupportedObject) => number;\n  /**\n   * Gets the number of full hours between the given dates.\n   */\n  differenceInHours: (value: TemporalSupportedObject, comparing: TemporalSupportedObject) => number;\n  /**\n   * Gets the number of full minutes between the given dates.\n   */\n  differenceInMinutes: (\n    value: TemporalSupportedObject,\n    comparing: TemporalSupportedObject,\n  ) => number;\n  /**\n   * Gets the number of days in a month of the given date.\n   */\n  getDaysInMonth(value: TemporalSupportedObject): number;\n  /**\n   * Gets the number of the week of the given date.\n   */\n  getWeekNumber(value: TemporalSupportedObject): number;\n  /**\n   * Gets the number of the day of the week of the given date.\n   * The value is 1-based, 1 - first day of the week, 7 - last day of the week.\n   */\n  getDayOfWeek(value: TemporalSupportedObject): number;\n}\n"
  },
  {
    "path": "packages/react/src/types/temporal/temporal.ts",
    "content": "/**\n * Lookup in which each date library can register its supported date object type.\n *\n * ```\n * // Example taken from the TemporalAdapterDateFns.ts file.\n * declare module '@base-ui/react/types' {\n *   interface TemporalSupportedObjectLookup {\n *     'date-fns': Date;\n *   }\n * }\n * ```\n */\nexport interface TemporalSupportedObjectLookup {}\n\n/**\n * The valid shape date objects can take in the components and utilities that deal with dates and times.\n */\nexport type TemporalSupportedObject = keyof TemporalSupportedObjectLookup extends never\n  ? any\n  : TemporalSupportedObjectLookup[keyof TemporalSupportedObjectLookup];\n\n/**\n * The valid value for the timezone argument in components and utilities that deal with dates and times.\n */\nexport type TemporalTimezone = 'default' | 'system' | 'UTC' | string;\n\n/**\n * The type that the `value` and `defaultValue` props can receive on non-range date and time components (date, time and date-time).\n */\nexport type TemporalValue = TemporalSupportedObject | null;\n\n/**\n * The type that the `value` and `defaultValue` props can receive on range components (date-range, time-range and date-time-range).\n */\nexport type TemporalRangeValue = [TemporalValue, TemporalValue];\n\n/**\n * The type that the `value` and `defaultValue` props can receive on all temporal components.\n */\nexport type TemporalSupportedValue = TemporalValue | TemporalRangeValue;\n\nexport type TemporalNonNullableRangeValue = [TemporalSupportedObject, TemporalSupportedObject];\n\nexport type TemporalNonNullableValue<TValue extends TemporalSupportedValue> =\n  TValue extends TemporalRangeValue\n    ? TValue extends TemporalValue\n      ? TemporalSupportedObject | TemporalNonNullableRangeValue\n      : TemporalNonNullableRangeValue\n    : TemporalSupportedObject;\n"
  },
  {
    "path": "packages/react/src/unstable-use-media-query/index.ts",
    "content": "import * as React from 'react';\nimport { useSyncExternalStore } from 'use-sync-external-store/shim';\n\nexport function useMediaQuery(query: string, options: useMediaQuery.Options): boolean {\n  // Wait for jsdom to support the match media feature.\n  // All the browsers Base UI support have this built-in.\n  // This defensive check is here for simplicity.\n  // Most of the time, the match media logic isn't central to people tests.\n  const supportMatchMedia =\n    typeof window !== 'undefined' && typeof window.matchMedia !== 'undefined';\n\n  query = query.replace(/^@media( ?)/m, '');\n\n  const {\n    defaultMatches = false,\n    matchMedia = supportMatchMedia ? window.matchMedia : null,\n    ssrMatchMedia = null,\n    noSsr = false,\n  } = options;\n\n  const getDefaultSnapshot = React.useCallback(() => defaultMatches, [defaultMatches]);\n\n  const getServerSnapshot = React.useMemo(() => {\n    if (noSsr && matchMedia) {\n      return () => matchMedia(query).matches;\n    }\n\n    if (ssrMatchMedia !== null) {\n      const { matches } = ssrMatchMedia(query);\n      return () => matches;\n    }\n    return getDefaultSnapshot;\n  }, [getDefaultSnapshot, query, ssrMatchMedia, noSsr, matchMedia]);\n\n  const [getSnapshot, subscribe] = React.useMemo(() => {\n    if (matchMedia === null) {\n      return [getDefaultSnapshot, () => () => {}];\n    }\n\n    const mediaQueryList = matchMedia(query);\n\n    return [\n      () => mediaQueryList.matches,\n      (notify: () => void) => {\n        mediaQueryList.addEventListener('change', notify);\n        return () => {\n          mediaQueryList.removeEventListener('change', notify);\n        };\n      },\n    ];\n  }, [getDefaultSnapshot, matchMedia, query]);\n\n  const match = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n\n  if (process.env.NODE_ENV !== 'production') {\n    // eslint-disable-next-line react-hooks/rules-of-hooks\n    React.useDebugValue({ query, match });\n  }\n\n  return match;\n}\n\nexport interface UseMediaQueryOptions {\n  /**\n   * As `window.matchMedia()` is unavailable on the server,\n   * it returns a default matches during the first mount.\n   * @default false\n   */\n  defaultMatches?: boolean | undefined;\n  /**\n   * You can provide your own implementation of matchMedia.\n   * This can be used for handling an iframe content window.\n   */\n  matchMedia?: typeof window.matchMedia | undefined;\n  /**\n   * To perform the server-side hydration, the hook needs to render twice.\n   * A first time with `defaultMatches`, the value of the server, and a second time with the resolved value.\n   * This double pass rendering cycle comes with a drawback: it's slower.\n   * You can set this option to `true` if you use the returned value **only** client-side.\n   * @default false\n   */\n  noSsr?: boolean | undefined;\n  /**\n   * You can provide your own implementation of `matchMedia`, it's used when rendering server-side.\n   */\n  ssrMatchMedia?: ((query: string) => { matches: boolean }) | undefined;\n}\n\nexport interface UseMediaQueryState {}\n\nexport namespace useMediaQuery {\n  export type State = UseMediaQueryState;\n  export type Options = UseMediaQueryOptions;\n}\n"
  },
  {
    "path": "packages/react/src/use-button/index.ts",
    "content": "export { useButton } from './useButton';\n"
  },
  {
    "path": "packages/react/src/use-button/useButton.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { act, fireEvent, screen } from '@mui/internal-test-utils';\nimport { useMergedRefs } from '@base-ui/utils/useMergedRefs';\nimport { createRenderer, isJSDOM } from '#test-utils';\nimport { useButton } from './useButton';\nimport { CompositeRoot } from '../composite/root/CompositeRoot';\nimport { mergeProps } from '../merge-props';\n\ndescribe('useButton', () => {\n  const { render, renderToString } = createRenderer();\n  const focusElement = async (element: HTMLElement) => {\n    await act(async () => {\n      element.focus();\n    });\n  };\n\n  describe('non-native button', () => {\n    describe('keyboard interactions', () => {\n      ['Enter', 'Space'].forEach((key) => {\n        it(`can be activated with ${key} key`, async () => {\n          const clickSpy = vi.fn();\n\n          function Button(props: React.ButtonHTMLAttributes<HTMLButtonElement>) {\n            const { getButtonProps } = useButton({\n              native: false,\n            });\n\n            return <span {...getButtonProps(props)} />;\n          }\n\n          const { user } = await render(<Button onClick={clickSpy} />);\n\n          const button = screen.getByRole('button');\n\n          await user.keyboard('[Tab]');\n          expect(button).toHaveFocus();\n\n          await user.keyboard(`[${key}]`);\n          expect(clickSpy).toHaveBeenCalledTimes(1);\n        });\n      });\n    });\n  });\n\n  describe('param: focusableWhenDisabled', () => {\n    it('allows disabled buttons to be focused', async () => {\n      function TestButton(props: React.ButtonHTMLAttributes<HTMLButtonElement>) {\n        const { disabled, ...otherProps } = props;\n        const { getButtonProps } = useButton({\n          disabled,\n          focusableWhenDisabled: true,\n        });\n\n        return <button {...getButtonProps(otherProps)} />;\n      }\n      await render(<TestButton disabled />);\n      const button = screen.getByRole('button');\n      await focusElement(button);\n      expect(button).toHaveFocus();\n    });\n\n    it('force overrides disabled attribute when put in a composite', async () => {\n      function TestButton(props: { buttonKey?: React.Key }) {\n        const { getButtonProps, buttonRef } = useButton({\n          disabled: true,\n          focusableWhenDisabled: true,\n        });\n        return (\n          <button ref={buttonRef} key={props.buttonKey} {...getButtonProps({ disabled: true })} />\n        );\n      }\n\n      const { rerender } = await render(\n        <CompositeRoot>\n          <TestButton />\n        </CompositeRoot>,\n      );\n\n      async function verify() {\n        const button = screen.getByRole('button');\n        await focusElement(button);\n        expect(button).toHaveFocus();\n      }\n\n      await verify();\n\n      // Ensure it works after ref change\n      await rerender(\n        <CompositeRoot>\n          <TestButton buttonKey=\"rerender\" />\n        </CompositeRoot>,\n      );\n      await verify();\n    });\n\n    it('prevents interactions except focus and blur', async () => {\n      const handleClick = vi.fn();\n      const handleKeyDown = vi.fn();\n      const handleKeyUp = vi.fn();\n      const handleFocus = vi.fn();\n      const handleBlur = vi.fn();\n\n      function TestButton(props: React.ButtonHTMLAttributes<HTMLButtonElement>) {\n        const { disabled, ...otherProps } = props;\n        const { getButtonProps } = useButton({\n          disabled,\n          focusableWhenDisabled: true,\n          native: false,\n        });\n\n        return <span {...getButtonProps(otherProps)} />;\n      }\n\n      const { user } = await render(\n        <TestButton\n          disabled\n          onClick={handleClick}\n          onKeyDown={handleKeyDown}\n          onKeyUp={handleKeyUp}\n          onFocus={handleFocus}\n          onBlur={handleBlur}\n        />,\n      );\n\n      const button = screen.getByRole('button');\n      expect(document.activeElement).not.toBe(button);\n\n      expect(handleFocus).toHaveBeenCalledTimes(0);\n      await user.keyboard('[Tab]');\n      expect(button).toHaveFocus();\n      expect(handleFocus).toHaveBeenCalledTimes(1);\n\n      await user.keyboard('[Enter]');\n      expect(handleKeyDown).toHaveBeenCalledTimes(0);\n      expect(handleClick).toHaveBeenCalledTimes(0);\n\n      await user.keyboard('[Space]');\n      expect(handleKeyUp).toHaveBeenCalledTimes(0);\n      expect(handleClick).toHaveBeenCalledTimes(0);\n\n      await user.click(button);\n      expect(handleKeyDown).toHaveBeenCalledTimes(0);\n      expect(handleKeyUp).toHaveBeenCalledTimes(0);\n      expect(handleClick).toHaveBeenCalledTimes(0);\n\n      expect(handleBlur).toHaveBeenCalledTimes(0);\n      await user.keyboard('[Tab]');\n      expect(handleBlur).toHaveBeenCalledTimes(1);\n      expect(document.activeElement).not.toBe(button);\n    });\n  });\n\n  describe('param: tabIndex', () => {\n    it('returns tabIndex in getButtonProps when host component is BUTTON', async () => {\n      function TestButton() {\n        const { getButtonProps } = useButton();\n\n        expect(getButtonProps().tabIndex).toBe(0);\n\n        return <button {...getButtonProps()} />;\n      }\n\n      await render(<TestButton />);\n      expect(screen.getByRole('button')).toHaveProperty('tabIndex', 0);\n    });\n\n    it('returns tabIndex in getButtonProps when host component is not BUTTON', async () => {\n      function TestButton() {\n        const ref = React.useRef(null);\n        const { getButtonProps, buttonRef } = useButton({ native: false });\n        useMergedRefs(ref, buttonRef);\n\n        expect(getButtonProps().tabIndex).toBe(0);\n\n        return <span {...getButtonProps()} />;\n      }\n\n      await render(<TestButton />);\n      expect(screen.getByRole('button')).toHaveProperty('tabIndex', 0);\n    });\n\n    it('returns tabIndex in getButtonProps if it is explicitly provided', async () => {\n      const customTabIndex = 3;\n      function TestButton() {\n        const { getButtonProps } = useButton({ tabIndex: customTabIndex });\n        return <button {...getButtonProps()} />;\n      }\n\n      await render(<TestButton />);\n      expect(screen.getByRole('button')).toHaveProperty('tabIndex', customTabIndex);\n    });\n  });\n\n  describe('arbitrary props', () => {\n    it('are passed to the host component', async () => {\n      const buttonTestId = 'button-test-id';\n      function TestButton() {\n        const { getButtonProps } = useButton();\n        return <button {...getButtonProps({ 'data-testid': buttonTestId })} />;\n      }\n\n      await render(<TestButton />);\n      expect(screen.getByRole('button')).toHaveAttribute('data-testid', buttonTestId);\n    });\n  });\n\n  describe('event handlers', () => {\n    it('key: Space fires keyup then click on non-composite buttons', async () => {\n      const handleKeyDown = vi.fn();\n      const handleKeyUp = vi.fn();\n      const handleClick = vi.fn();\n\n      function TestButton(props: React.ButtonHTMLAttributes<HTMLButtonElement>) {\n        const { getButtonProps } = useButton({ native: false });\n\n        return <span {...getButtonProps(props)} />;\n      }\n\n      await render(\n        <TestButton onKeyDown={handleKeyDown} onKeyUp={handleKeyUp} onClick={handleClick} />,\n      );\n\n      const button = screen.getByRole('button');\n\n      await focusElement(button);\n      expect(button).toHaveFocus();\n\n      fireEvent.keyDown(button, { key: ' ' });\n      expect(handleKeyDown).toHaveBeenCalledTimes(1);\n      expect(handleClick).toHaveBeenCalledTimes(0);\n\n      fireEvent.keyUp(button, { key: ' ' });\n      expect(handleKeyUp).toHaveBeenCalledTimes(1);\n      expect(handleClick).toHaveBeenCalledTimes(1);\n    });\n\n    it('key: Space fires keydown then click on composite buttons', async () => {\n      const handleKeyDown = vi.fn();\n      const handleKeyUp = vi.fn();\n      const handleClick = vi.fn();\n\n      function TestButton(props: React.ButtonHTMLAttributes<HTMLButtonElement>) {\n        const { getButtonProps } = useButton({ native: false, composite: true });\n\n        return <span {...getButtonProps(props)} />;\n      }\n\n      await render(\n        <TestButton\n          tabIndex={0}\n          onKeyDown={handleKeyDown}\n          onKeyUp={handleKeyUp}\n          onClick={handleClick}\n        />,\n      );\n\n      const button = screen.getByRole('button');\n\n      await focusElement(button);\n      expect(button).toHaveFocus();\n\n      fireEvent.keyDown(button, { key: ' ' });\n      expect(handleKeyDown).toHaveBeenCalledTimes(1);\n      expect(handleClick).toHaveBeenCalledTimes(1);\n\n      fireEvent.keyUp(button, { key: ' ' });\n      expect(handleKeyUp).toHaveBeenCalledTimes(1);\n      expect(handleClick).toHaveBeenCalledTimes(1);\n    });\n\n    it('key: Space fires keydown then click on composite links', async () => {\n      const handleClick = vi.fn();\n\n      function TestButton(props: React.AnchorHTMLAttributes<HTMLAnchorElement>) {\n        const { getButtonProps } = useButton({ native: false, composite: true });\n\n        return <a href=\"#test\" {...getButtonProps(props)} />;\n      }\n\n      await render(<TestButton onClick={handleClick} />);\n\n      const link = screen.getByRole('button');\n\n      await focusElement(link);\n      expect(link).toHaveFocus();\n\n      fireEvent.keyDown(link, { key: ' ' });\n      expect(handleClick).toHaveBeenCalledTimes(1);\n\n      fireEvent.keyUp(link, { key: ' ' });\n      expect(handleClick).toHaveBeenCalledTimes(1);\n    });\n\n    it('does not click composite links when Space is prevented for text navigation', async () => {\n      const handleClick = vi.fn();\n\n      function TestButton(props: React.AnchorHTMLAttributes<HTMLAnchorElement>) {\n        const { getButtonProps } = useButton({ native: false, composite: true });\n\n        return <a href=\"#test\" {...getButtonProps({ role: 'menuitem', ...props })} />;\n      }\n\n      await render(\n        <TestButton onKeyDown={(event) => event.preventDefault()} onClick={handleClick} />,\n      );\n\n      const link = screen.getByRole('menuitem');\n\n      await focusElement(link);\n      expect(link).toHaveFocus();\n\n      fireEvent.keyDown(link, { key: ' ' });\n      expect(handleClick).toHaveBeenCalledTimes(0);\n    });\n\n    it('does not click composite gridcells when Space is prevented', async () => {\n      const handleClick = vi.fn();\n\n      function TestButton(props: React.HTMLAttributes<HTMLDivElement>) {\n        const { getButtonProps } = useButton({ native: false, composite: true });\n\n        return <div {...getButtonProps({ role: 'gridcell', tabIndex: 0, ...props })} />;\n      }\n\n      await render(\n        <TestButton onKeyDown={(event) => event.preventDefault()} onClick={handleClick} />,\n      );\n\n      const gridcell = screen.getByRole('gridcell');\n\n      await focusElement(gridcell);\n      expect(gridcell).toHaveFocus();\n\n      fireEvent.keyDown(gridcell, { key: ' ' });\n      expect(handleClick).toHaveBeenCalledTimes(0);\n    });\n\n    it('clicks composite switches when Space is prevented', async () => {\n      const handleClick = vi.fn();\n\n      function TestButton(props: React.HTMLAttributes<HTMLDivElement>) {\n        const { getButtonProps } = useButton({ native: false, composite: true });\n\n        return <div {...getButtonProps({ role: 'switch', tabIndex: 0, ...props })} />;\n      }\n\n      await render(\n        <TestButton onKeyDown={(event) => event.preventDefault()} onClick={handleClick} />,\n      );\n\n      const switchElement = screen.getByRole('switch');\n\n      await focusElement(switchElement);\n      expect(switchElement).toHaveFocus();\n\n      fireEvent.keyDown(switchElement, { key: ' ' });\n      expect(handleClick).toHaveBeenCalledTimes(1);\n    });\n\n    it('key: Space fires keydown then click on native composite buttons', async () => {\n      const handleKeyDown = vi.fn();\n      const handleKeyUp = vi.fn();\n      const handleClick = vi.fn();\n\n      function TestButton(props: React.ButtonHTMLAttributes<HTMLButtonElement>) {\n        const { getButtonProps } = useButton({ composite: true });\n\n        return <button {...getButtonProps(props)} />;\n      }\n\n      await render(\n        <TestButton onKeyDown={handleKeyDown} onKeyUp={handleKeyUp} onClick={handleClick} />,\n      );\n\n      const button = screen.getByRole('button');\n\n      await focusElement(button);\n      expect(button).toHaveFocus();\n\n      fireEvent.keyDown(button, { key: ' ' });\n      expect(handleKeyDown).toHaveBeenCalledTimes(1);\n      expect(handleClick).toHaveBeenCalledTimes(1);\n\n      fireEvent.keyUp(button, { key: ' ' });\n      expect(handleKeyUp).toHaveBeenCalledTimes(1);\n      expect(handleClick).toHaveBeenCalledTimes(1);\n    });\n\n    it('does not fire duplicate clicks for Space on native composite buttons', async () => {\n      const handleClick = vi.fn();\n\n      function TestButton(props: React.ButtonHTMLAttributes<HTMLButtonElement>) {\n        const { getButtonProps } = useButton({ composite: true });\n\n        return <button {...getButtonProps(props)} />;\n      }\n\n      const { user } = await render(<TestButton onClick={handleClick} />);\n\n      const button = screen.getByRole('button');\n\n      await focusElement(button);\n      expect(button).toHaveFocus();\n\n      await user.keyboard('[Space]');\n      expect(handleClick).toHaveBeenCalledTimes(1);\n    });\n\n    it('does not fire duplicate clicks for Space when composed with another useButton', async () => {\n      const handleClick = vi.fn();\n\n      function TestButton() {\n        const outer = useButton({ native: false, composite: true });\n        const inner = useButton({ native: false, composite: true });\n\n        return (\n          <span\n            {...mergeProps(\n              outer.getButtonProps({ onClick: handleClick, tabIndex: 0 }),\n              inner.getButtonProps({ onClick: handleClick }),\n            )}\n          />\n        );\n      }\n\n      const { user } = await render(<TestButton />);\n\n      const button = screen.getByRole('button');\n\n      await focusElement(button);\n      expect(button).toHaveFocus();\n\n      await user.keyboard('[Space]');\n      expect(handleClick).toHaveBeenCalledTimes(1);\n    });\n\n    it('key: Space preserves native submit semantics on composite buttons', async () => {\n      const handleSubmit = vi.fn((event: React.FormEvent<HTMLFormElement>) => {\n        event.preventDefault();\n      });\n\n      function TestButton() {\n        const { getButtonProps } = useButton({ composite: true });\n\n        return (\n          <form onSubmit={handleSubmit}>\n            <button {...getButtonProps({ type: 'submit' })}>Submit</button>\n          </form>\n        );\n      }\n\n      await render(<TestButton />);\n\n      const button = screen.getByRole('button', { name: 'Submit' });\n\n      await focusElement(button);\n      expect(button).toHaveFocus();\n\n      fireEvent.keyDown(button, { key: ' ' });\n      expect(handleSubmit).toHaveBeenCalledTimes(1);\n\n      fireEvent.keyUp(button, { key: ' ' });\n      expect(handleSubmit).toHaveBeenCalledTimes(1);\n    });\n\n    it('key: Space preserves native reset semantics on composite buttons', async () => {\n      const handleReset = vi.fn((event: React.FormEvent<HTMLFormElement>) => {\n        event.preventDefault();\n      });\n\n      function TestButton() {\n        const { getButtonProps } = useButton({ composite: true });\n\n        return (\n          <form onReset={handleReset}>\n            <button {...getButtonProps({ type: 'reset' })}>Reset</button>\n          </form>\n        );\n      }\n\n      await render(<TestButton />);\n\n      const button = screen.getByRole('button', { name: 'Reset' });\n\n      await focusElement(button);\n      expect(button).toHaveFocus();\n\n      fireEvent.keyDown(button, { key: ' ' });\n      expect(handleReset).toHaveBeenCalledTimes(1);\n\n      fireEvent.keyUp(button, { key: ' ' });\n      expect(handleReset).toHaveBeenCalledTimes(1);\n    });\n\n    it('does not click composite buttons when keydown calls preventBaseUIHandler', async () => {\n      const handleClick = vi.fn();\n\n      function TestButton(props: React.ButtonHTMLAttributes<HTMLButtonElement>) {\n        const { getButtonProps } = useButton({ native: false, composite: true });\n\n        return <span {...getButtonProps(props)} />;\n      }\n\n      await render(\n        <TestButton\n          tabIndex={0}\n          onKeyDown={(event) =>\n            (\n              event as React.KeyboardEvent<HTMLButtonElement> & {\n                preventBaseUIHandler: () => void;\n              }\n            ).preventBaseUIHandler()\n          }\n          onClick={handleClick}\n        />,\n      );\n\n      const button = screen.getByRole('button');\n\n      await focusElement(button);\n      expect(button).toHaveFocus();\n\n      fireEvent.keyDown(button, { key: ' ' });\n      expect(handleClick).toHaveBeenCalledTimes(0);\n    });\n\n    it('key: Space fires keydown then click when in composite root context', async () => {\n      const handleKeyDown = vi.fn();\n      const handleKeyUp = vi.fn();\n      const handleClick = vi.fn();\n\n      function TestButton(props: React.ButtonHTMLAttributes<HTMLButtonElement>) {\n        const { getButtonProps } = useButton({ native: false });\n\n        return <span {...getButtonProps(props)} />;\n      }\n\n      await render(\n        <CompositeRoot>\n          <TestButton\n            tabIndex={0}\n            onKeyDown={handleKeyDown}\n            onKeyUp={handleKeyUp}\n            onClick={handleClick}\n          />\n        </CompositeRoot>,\n      );\n\n      const button = screen.getByRole('button');\n\n      await focusElement(button);\n      expect(button).toHaveFocus();\n\n      fireEvent.keyDown(button, { key: ' ' });\n      expect(handleKeyDown).toHaveBeenCalledTimes(1);\n      expect(handleClick).toHaveBeenCalledTimes(1);\n\n      fireEvent.keyUp(button, { key: ' ' });\n      expect(handleKeyUp).toHaveBeenCalledTimes(1);\n      expect(handleClick).toHaveBeenCalledTimes(1);\n    });\n\n    it('key: Space fires keydown then click on native buttons in composite root context', async () => {\n      const handleKeyDown = vi.fn();\n      const handleKeyUp = vi.fn();\n      const handleClick = vi.fn();\n\n      function TestButton(props: React.ButtonHTMLAttributes<HTMLButtonElement>) {\n        const { getButtonProps } = useButton();\n\n        return <button {...getButtonProps(props)} />;\n      }\n\n      await render(\n        <CompositeRoot>\n          <TestButton onKeyDown={handleKeyDown} onKeyUp={handleKeyUp} onClick={handleClick} />\n        </CompositeRoot>,\n      );\n\n      const button = screen.getByRole('button');\n\n      await focusElement(button);\n      expect(button).toHaveFocus();\n\n      fireEvent.keyDown(button, { key: ' ' });\n      expect(handleKeyDown).toHaveBeenCalledTimes(1);\n      expect(handleClick).toHaveBeenCalledTimes(1);\n\n      fireEvent.keyUp(button, { key: ' ' });\n      expect(handleKeyUp).toHaveBeenCalledTimes(1);\n      expect(handleClick).toHaveBeenCalledTimes(1);\n    });\n\n    it('`composite=false` keeps keyup activation inside composite root context', async () => {\n      const handleKeyDown = vi.fn();\n      const handleKeyUp = vi.fn();\n      const handleClick = vi.fn();\n\n      function TestButton(props: React.ButtonHTMLAttributes<HTMLButtonElement>) {\n        const { getButtonProps } = useButton({ native: false, composite: false });\n\n        return <span {...getButtonProps(props)} />;\n      }\n\n      await render(\n        <CompositeRoot>\n          <TestButton onKeyDown={handleKeyDown} onKeyUp={handleKeyUp} onClick={handleClick} />\n        </CompositeRoot>,\n      );\n\n      const button = screen.getByRole('button');\n\n      await focusElement(button);\n      expect(button).toHaveFocus();\n\n      fireEvent.keyDown(button, { key: ' ' });\n      expect(handleKeyDown).toHaveBeenCalledTimes(1);\n      expect(handleClick).toHaveBeenCalledTimes(0);\n\n      fireEvent.keyUp(button, { key: ' ' });\n      expect(handleKeyUp).toHaveBeenCalledTimes(1);\n      expect(handleClick).toHaveBeenCalledTimes(1);\n    });\n\n    // calling preventDefault in keyUp on a <button> will not dispatch a click event if Space is pressed\n    // https://codesandbox.io/p/sandbox/button-keyup-preventdefault-dn7f0\n    it('key: Space fires a click event even if preventDefault was called on keyUp', async () => {\n      const handleClick = vi.fn();\n\n      function TestButton(props: React.ButtonHTMLAttributes<HTMLButtonElement>) {\n        const { getButtonProps } = useButton({ native: false });\n\n        return <span {...getButtonProps(props)} />;\n      }\n\n      const { user } = await render(\n        <TestButton\n          onKeyUp={(event: React.KeyboardEvent<HTMLButtonElement>) => event.preventDefault()}\n          onClick={handleClick}\n        />,\n      );\n\n      const button = screen.getByRole('button');\n\n      await user.keyboard('[Tab]');\n      expect(button).toHaveFocus();\n\n      await user.keyboard('[Space]');\n      expect(handleClick).toHaveBeenCalledTimes(1);\n    });\n\n    it('key: Enter fires keydown then click on non-native buttons', async () => {\n      const handleKeyDown = vi.fn();\n      const handleClick = vi.fn();\n\n      function TestButton(props: React.ButtonHTMLAttributes<HTMLButtonElement>) {\n        const { getButtonProps } = useButton({ native: false });\n\n        return <span {...getButtonProps(props)} />;\n      }\n\n      await render(<TestButton onKeyDown={handleKeyDown} onClick={handleClick} />);\n\n      const button = screen.getByRole('button');\n\n      await focusElement(button);\n      expect(button).toHaveFocus();\n\n      expect(handleKeyDown).toHaveBeenCalledTimes(0);\n      fireEvent.keyDown(button, { key: 'Enter' });\n      expect(handleKeyDown).toHaveBeenCalledTimes(1);\n      expect(handleClick).toHaveBeenCalledTimes(1);\n    });\n  });\n\n  describe.skipIf(isJSDOM)('server-side rendering', () => {\n    it('should server-side render', async () => {\n      function TestButton(props: React.ButtonHTMLAttributes<HTMLButtonElement>) {\n        const { disabled, ...otherProps } = props;\n        const { getButtonProps } = useButton({ disabled, native: false });\n\n        return <span {...getButtonProps(otherProps)} />;\n      }\n\n      const { container } = await renderToString(<TestButton disabled />);\n\n      expect(container.firstChild).toHaveProperty('role', 'button');\n    });\n\n    it('adds disabled attribute', async () => {\n      function TestButton(props: React.ButtonHTMLAttributes<HTMLButtonElement>) {\n        const { disabled, ...otherProps } = props;\n        const { getButtonProps } = useButton({ disabled });\n        return <button {...getButtonProps(otherProps)} />;\n      }\n\n      renderToString(<TestButton disabled>Submit</TestButton>);\n      expect(screen.getByRole('button')).toHaveProperty('disabled');\n    });\n  });\n\n  describe('dev warnings', () => {\n    it('errors if nativeButton=true but ref is not a button', async () => {\n      const errorSpy = vi\n        .spyOn(console, 'error')\n        .mockName('console.error')\n        .mockImplementation(() => {});\n      function TestButton() {\n        const { getButtonProps, buttonRef } = useButton({ native: true });\n        return <span {...getButtonProps()} ref={buttonRef} />;\n      }\n      await render(<TestButton />);\n      expect(errorSpy).toHaveBeenCalledTimes(1);\n      expect(errorSpy).toHaveBeenCalledWith(\n        expect.stringContaining(\n          'Base UI: A component that acts as a button expected a native <button> because ' +\n            'the `nativeButton` prop is true. Rendering a non-<button> removes native button semantics, ' +\n            'which can impact forms and accessibility. Use a real <button> in the `render` prop, or set ' +\n            '`nativeButton` to `false`.',\n        ),\n      );\n    });\n\n    it('errors if nativeButton=false but ref is a button', async () => {\n      const errorSpy = vi\n        .spyOn(console, 'error')\n        .mockName('console.error')\n        .mockImplementation(() => {});\n      function TestButton() {\n        const { getButtonProps, buttonRef } = useButton({ native: false });\n        return <button {...getButtonProps()} ref={buttonRef} />;\n      }\n      await render(<TestButton />);\n      expect(errorSpy).toHaveBeenCalledTimes(1);\n      expect(errorSpy).toHaveBeenCalledWith(\n        expect.stringContaining(\n          'Base UI: A component that acts as a button expected a non-<button> because ' +\n            'the `nativeButton` prop is false. Rendering a <button> keeps native behavior while Base UI ' +\n            'applies non-native attributes and handlers, which can add unintended extra attributes ' +\n            '(such as `role` or `aria-disabled`). Use a non-<button> in the `render` prop, or set ' +\n            '`nativeButton` to `true`.',\n        ),\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/use-button/useButton.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { isHTMLElement } from '@floating-ui/utils/dom';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { error } from '@base-ui/utils/error';\nimport { SafeReact } from '@base-ui/utils/safeReact';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { makeEventPreventable, mergeProps } from '../merge-props';\nimport { useCompositeRootContext } from '../composite/root/CompositeRootContext';\nimport { BaseUIEvent, HTMLProps } from '../utils/types';\nimport { useFocusableWhenDisabled } from '../utils/useFocusableWhenDisabled';\n\nexport function useButton(parameters: UseButtonParameters = {}): UseButtonReturnValue {\n  const {\n    disabled = false,\n    focusableWhenDisabled,\n    tabIndex = 0,\n    native: isNativeButton = true,\n    composite: compositeProp,\n  } = parameters;\n\n  const elementRef = React.useRef<HTMLElement | null>(null);\n\n  const compositeRootContext = useCompositeRootContext(true);\n  const isCompositeItem = compositeProp ?? compositeRootContext !== undefined;\n\n  const { props: focusableWhenDisabledProps } = useFocusableWhenDisabled({\n    focusableWhenDisabled,\n    disabled,\n    composite: isCompositeItem,\n    tabIndex,\n    isNativeButton,\n  });\n\n  if (process.env.NODE_ENV !== 'production') {\n    // eslint-disable-next-line react-hooks/rules-of-hooks\n    React.useEffect(() => {\n      if (!elementRef.current) {\n        return;\n      }\n\n      const isButtonTag = isButtonElement(elementRef.current);\n\n      if (isNativeButton) {\n        if (!isButtonTag) {\n          const ownerStackMessage = SafeReact.captureOwnerStack?.() || '';\n          const message =\n            'A component that acts as a button expected a native <button> because the ' +\n            '`nativeButton` prop is true. Rendering a non-<button> removes native button ' +\n            'semantics, which can impact forms and accessibility. Use a real <button> in the ' +\n            '`render` prop, or set `nativeButton` to `false`.';\n          error(`${message}${ownerStackMessage}`);\n        }\n      } else if (isButtonTag) {\n        const ownerStackMessage = SafeReact.captureOwnerStack?.() || '';\n        const message =\n          'A component that acts as a button expected a non-<button> because the `nativeButton` ' +\n          'prop is false. Rendering a <button> keeps native behavior while Base UI applies ' +\n          'non-native attributes and handlers, which can add unintended extra attributes (such ' +\n          'as `role` or `aria-disabled`). Use a non-<button> in the `render` prop, or set ' +\n          '`nativeButton` to `true`.';\n        error(`${message}${ownerStackMessage}`);\n      }\n    }, [isNativeButton]);\n  }\n\n  // handles a disabled composite button rendering another button, e.g.\n  // <Toolbar.Button disabled render={<Menu.Trigger />} />\n  // the `disabled` prop needs to pass through 2 `useButton`s then finally\n  // delete the `disabled` attribute from DOM\n  const updateDisabled = React.useCallback(() => {\n    const element = elementRef.current;\n\n    if (!isButtonElement(element)) {\n      return;\n    }\n\n    if (\n      isCompositeItem &&\n      disabled &&\n      focusableWhenDisabledProps.disabled === undefined &&\n      element.disabled\n    ) {\n      element.disabled = false;\n    }\n  }, [disabled, focusableWhenDisabledProps.disabled, isCompositeItem]);\n\n  useIsoLayoutEffect(updateDisabled, [updateDisabled]);\n\n  const getButtonProps = React.useCallback(\n    (externalProps: GenericButtonProps = {}) => {\n      const {\n        onClick: externalOnClick,\n        onMouseDown: externalOnMouseDown,\n        onKeyUp: externalOnKeyUp,\n        onKeyDown: externalOnKeyDown,\n        onPointerDown: externalOnPointerDown,\n        ...otherExternalProps\n      } = externalProps;\n\n      const type = isNativeButton ? 'button' : undefined;\n\n      return mergeProps<'button'>(\n        {\n          type,\n          onClick(event: React.MouseEvent) {\n            if (disabled) {\n              event.preventDefault();\n              return;\n            }\n            externalOnClick?.(event);\n          },\n          onMouseDown(event: React.MouseEvent) {\n            if (!disabled) {\n              externalOnMouseDown?.(event);\n            }\n          },\n          onKeyDown(event: BaseUIEvent<React.KeyboardEvent>) {\n            if (disabled) {\n              return;\n            }\n\n            makeEventPreventable(event);\n            externalOnKeyDown?.(event);\n            if (event.baseUIHandlerPrevented) {\n              return;\n            }\n\n            const isCurrentTarget = event.target === event.currentTarget;\n            const currentTarget = event.currentTarget as HTMLElement;\n            const isButton = isButtonElement(currentTarget);\n            const isLink = !isNativeButton && isValidLinkElement(currentTarget);\n            const shouldClick = isCurrentTarget && (isNativeButton ? isButton : !isLink);\n            const isEnterKey = event.key === 'Enter';\n            const isSpaceKey = event.key === ' ';\n            const role = currentTarget.getAttribute('role');\n            const isTextNavigationRole =\n              role?.startsWith('menuitem') || role === 'option' || role === 'gridcell';\n\n            if (isCurrentTarget && isCompositeItem && isSpaceKey) {\n              if (event.defaultPrevented && isTextNavigationRole) {\n                return;\n              }\n\n              event.preventDefault();\n\n              if (isLink || (isNativeButton && isButton)) {\n                currentTarget.click();\n                event.preventBaseUIHandler();\n              } else if (shouldClick) {\n                externalOnClick?.(event);\n                event.preventBaseUIHandler();\n              }\n\n              return;\n            }\n\n            // Keyboard accessibility for native and non-native elements.\n            if (shouldClick) {\n              if (!isNativeButton && (isSpaceKey || isEnterKey)) {\n                event.preventDefault();\n              }\n\n              if (!isNativeButton && isEnterKey) {\n                externalOnClick?.(event);\n              }\n            }\n          },\n          onKeyUp(event: BaseUIEvent<React.KeyboardEvent>) {\n            if (disabled) {\n              return;\n            }\n\n            // calling preventDefault in keyUp on a <button> will not dispatch a click event if Space is pressed\n            // https://codesandbox.io/p/sandbox/button-keyup-preventdefault-dn7f0\n            makeEventPreventable(event);\n            externalOnKeyUp?.(event);\n\n            if (\n              event.target === event.currentTarget &&\n              isNativeButton &&\n              isCompositeItem &&\n              isButtonElement(event.currentTarget as HTMLElement) &&\n              event.key === ' '\n            ) {\n              event.preventDefault();\n              return;\n            }\n\n            if (event.baseUIHandlerPrevented) {\n              return;\n            }\n\n            // Keyboard accessibility for non interactive elements\n            if (\n              event.target === event.currentTarget &&\n              !isNativeButton &&\n              !isCompositeItem &&\n              event.key === ' '\n            ) {\n              externalOnClick?.(event);\n            }\n          },\n          onPointerDown(event: React.PointerEvent) {\n            if (disabled) {\n              event.preventDefault();\n              return;\n            }\n            externalOnPointerDown?.(event);\n          },\n        },\n        !isNativeButton ? { role: 'button' } : undefined,\n        focusableWhenDisabledProps,\n        otherExternalProps,\n      );\n    },\n    [disabled, focusableWhenDisabledProps, isCompositeItem, isNativeButton],\n  );\n\n  const buttonRef = useStableCallback((element: HTMLElement | null) => {\n    elementRef.current = element;\n    updateDisabled();\n  });\n\n  return {\n    getButtonProps,\n    buttonRef,\n  };\n}\n\nfunction isButtonElement(\n  elem: HTMLButtonElement | HTMLAnchorElement | HTMLElement | null,\n): elem is HTMLButtonElement {\n  return isHTMLElement(elem) && elem.tagName === 'BUTTON';\n}\n\nfunction isValidLinkElement(elem: HTMLElement | null): elem is HTMLAnchorElement {\n  return Boolean(elem?.tagName === 'A' && (elem as HTMLAnchorElement)?.href);\n}\n\ninterface GenericButtonProps extends Omit<HTMLProps, 'onClick'>, AdditionalButtonProps {\n  onClick?: ((event: React.SyntheticEvent) => void) | undefined;\n}\n\ninterface AdditionalButtonProps extends Partial<{\n  'aria-disabled': React.AriaAttributes['aria-disabled'];\n  disabled: boolean;\n  role: React.AriaRole;\n  tabIndex?: number | undefined;\n}> {}\n\nexport interface UseButtonParameters {\n  /**\n   * Whether the component should ignore user interaction.\n   * @default false\n   */\n  disabled?: boolean | undefined;\n  /**\n   * Whether the button may receive focus even if it is disabled.\n   * @default false\n   */\n  focusableWhenDisabled?: boolean | undefined;\n  tabIndex?: NonNullable<React.HTMLAttributes<any>['tabIndex']> | undefined;\n  /**\n   * Whether the component is being rendered as a native button.\n   * @default true\n   */\n  native?: boolean | undefined;\n  /**\n   * Whether the button is part of a composite widget.\n   * When `true`, keyboard activation for Space occurs on keydown rather than keyup.\n   * @default inferred from CompositeRoot context\n   */\n  composite?: boolean | undefined;\n}\n\nexport interface UseButtonReturnValue {\n  /**\n   * Resolver for the button props.\n   * @param externalProps additional props for the button\n   * @returns props that should be spread on the button\n   */\n  getButtonProps: (\n    externalProps?: React.ComponentPropsWithRef<any>,\n  ) => React.ComponentPropsWithRef<any>;\n  /**\n   * A ref to the button DOM element. This ref should be passed to the rendered element.\n   * It is not a part of the props returned by `getButtonProps`.\n   */\n  buttonRef: React.Ref<HTMLElement>;\n}\n\nexport interface UseButtonState {}\n"
  },
  {
    "path": "packages/react/src/use-render/index.ts",
    "content": "export * from './useRender';\nexport type { HTMLProps, ComponentRenderFn } from '../utils/types';\n"
  },
  {
    "path": "packages/react/src/use-render/useRender.spec.tsx",
    "content": "/* eslint-disable react-hooks/rules-of-hooks */\nimport * as React from 'react';\nimport { expectType } from '#test-utils';\nimport { useRender } from './useRender';\nimport { Button } from '../button';\n\nconst element1 = useRender({\n  render: () => <div>Test</div>,\n});\n\nexpectType<React.ReactElement, typeof element1>(element1);\n\nconst element2 = useRender({\n  render: () => <div>Test</div>,\n  enabled: true,\n});\n\nexpectType<React.ReactElement, typeof element2>(element2);\n\nconst element3 = useRender({\n  render: () => <div>Test</div>,\n  enabled: false,\n});\n\nexpectType<null, typeof element3>(element3);\n\nconst element4 = useRender({\n  render: () => <div>Test</div>,\n  enabled: Math.random() > 0.5,\n});\n\nexpectType<React.ReactElement | null, typeof element4>(element4);\n\nconst element5 = useRender({\n  render: () => <button type=\"button\">Click</button>,\n});\n\nexpectType<React.ReactElement, typeof element5>(element5);\n\nconst element6 = useRender({\n  render: <div />,\n});\n\nexpectType<React.ReactElement, typeof element6>(element6);\n\nconst element7 = useRender({\n  render: <button type=\"button\" aria-label=\"Submit\" />,\n  props: {\n    className: 'btn-primary',\n    onClick: () => console.log('clicked'),\n  },\n});\n\nexpectType<React.ReactElement, typeof element7>(element7);\n\nfunction App() {\n  const element = useRender({ defaultTagName: 'div' });\n  return <Button render={element} />;\n}\n"
  },
  {
    "path": "packages/react/src/use-render/useRender.test.tsx",
    "content": "import { expect } from 'vitest';\n/* eslint-disable testing-library/render-result-naming-convention */\nimport * as React from 'react';\nimport { useRender } from '@base-ui/react/use-render';\nimport { createRenderer } from '#test-utils';\n\ndescribe('useRender', () => {\n  const { render } = createRenderer();\n\n  it('render props does not overwrite className in a render function when unspecified', async () => {\n    function TestComponent(props: {\n      render: useRender.Parameters<{}, Element, undefined>['render'];\n      className?: string;\n    }) {\n      const { render: renderProp, className } = props;\n      const element = useRender({\n        render: renderProp,\n        props: {\n          className,\n        },\n      });\n      return element;\n    }\n\n    const { container } = await render(\n      <TestComponent\n        render={(props: any, state: any) => (\n          <span {...props} className={`my-span ${props.className ?? ''}`} {...state} />\n        )}\n      />,\n    );\n\n    const element = container.firstElementChild;\n\n    expect(element).toHaveAttribute('class', 'my-span ');\n  });\n\n  it('refs are handled as expected', async () => {\n    const refs: React.Ref<HTMLElement | undefined>[] = [];\n\n    function TestComponent(\n      props: {\n        render: useRender.Parameters<{}, Element, undefined>['render'];\n        className?: string;\n      } & React.ComponentPropsWithRef<'span'>,\n    ) {\n      const { render: renderProp, ...otherProps } = props;\n      const ref1 = React.useRef<HTMLElement>(null);\n      const ref2 = React.useRef<HTMLElement>(null);\n\n      React.useEffect(() => {\n        refs[0] = ref1;\n        refs[1] = ref2;\n      }, []);\n\n      const element = useRender({\n        render: renderProp,\n        ref: [ref1, ref2],\n        props: otherProps,\n      });\n      return element;\n    }\n\n    const { container } = await render(\n      <TestComponent render={(props: any, state: any) => <span {...props} {...state} />} />,\n    );\n    expect(refs.length).toBe(2);\n\n    refs.forEach((ref) => {\n      expect(ref).toEqual({ current: container.firstElementChild });\n    });\n  });\n\n  describe('param: defaultTagName', () => {\n    it('renders div by default if no defaultTagName and no render params are provided', async () => {\n      function TestComponent() {\n        return useRender({});\n      }\n\n      const { container } = await render(<TestComponent />);\n      expect(container.firstElementChild).toHaveProperty('tagName', 'DIV');\n    });\n\n    it('renders the element with the default tag with no render prop', async () => {\n      function TestComponent({\n        defaultTagName,\n      }: {\n        defaultTagName: keyof React.JSX.IntrinsicElements;\n      }) {\n        return useRender({ defaultTagName });\n      }\n\n      const { container, setProps } = await render(<TestComponent defaultTagName=\"div\" />);\n      expect(container.firstElementChild).toHaveProperty('tagName', 'DIV');\n\n      await setProps({ defaultTagName: 'span' });\n      expect(container.firstElementChild).toHaveProperty('tagName', 'SPAN');\n    });\n\n    it('is overwritten by the render prop', async () => {\n      function TestComponent({\n        render: renderProp,\n        defaultTagName,\n      }: {\n        render: useRender.Parameters<{}, Element, undefined>['render'];\n        defaultTagName: keyof React.JSX.IntrinsicElements;\n      }) {\n        return useRender({ render: renderProp, defaultTagName });\n      }\n\n      const { container, setProps } = await render(\n        <TestComponent defaultTagName=\"div\" render={<span />} />,\n      );\n      expect(container.firstElementChild).toHaveProperty('tagName', 'SPAN');\n\n      await setProps({ defaultTagName: 'a' });\n      expect(container.firstElementChild).toHaveProperty('tagName', 'SPAN');\n    });\n  });\n\n  describe('state to data attributes', () => {\n    it('converts state to data attributes automatically', async () => {\n      function TestComponent() {\n        const element = useRender({\n          render: <button type=\"button\" />,\n          state: {\n            active: true,\n            index: 42,\n          },\n        });\n        return element;\n      }\n\n      const { container } = await render(<TestComponent />);\n      const button = container.firstElementChild;\n\n      expect(button).toHaveAttribute('data-active', '');\n      expect(button).toHaveAttribute('data-index', '42');\n    });\n\n    it('handles undefined values in state', async () => {\n      function TestComponent() {\n        const element = useRender({\n          render: <div />,\n          state: {\n            defined: 'value',\n            notDefined: undefined,\n          },\n        });\n        return element;\n      }\n\n      const { container } = await render(<TestComponent />);\n      const div = container.firstElementChild;\n\n      expect(div).toHaveAttribute('data-defined', 'value');\n      expect(div).not.toHaveAttribute('data-notdefined');\n    });\n\n    it('merges state-based data attributes with existing props', async () => {\n      function TestComponent() {\n        const element = useRender({\n          render: <button type=\"button\" />,\n          state: {\n            form: 'login',\n          },\n          props: {\n            className: 'btn-primary',\n            id: 'submit-btn',\n            'data-existing': 'prop',\n          },\n        });\n        return element;\n      }\n\n      const { container } = await render(<TestComponent />);\n      const button = container.firstElementChild;\n\n      expect(button).toHaveAttribute('data-form', 'login');\n\n      expect(button).toHaveAttribute('class', 'btn-primary');\n      expect(button).toHaveAttribute('id', 'submit-btn');\n\n      expect(button).toHaveAttribute('data-existing', 'prop');\n    });\n\n    it('props override state-based data attributes', async () => {\n      function TestComponent() {\n        const element = useRender({\n          render: <button type=\"button\" />,\n          state: {\n            active: true,\n          },\n          props: {\n            'data-active': 'false',\n          },\n        });\n        return element;\n      }\n\n      const { container } = await render(<TestComponent />);\n      const button = container.firstElementChild;\n\n      expect(button).toHaveAttribute('data-active', 'false');\n    });\n\n    it('handles empty state', async () => {\n      function TestComponent() {\n        const element = useRender({\n          render: <span />,\n          state: {},\n          props: {\n            className: 'test-class',\n          },\n        });\n        return element;\n      }\n\n      const { container } = await render(<TestComponent />);\n      const span = container.firstElementChild;\n\n      expect(span).toHaveAttribute('class', 'test-class');\n\n      const attributes = span?.attributes;\n      if (attributes) {\n        for (let i = 0; i < attributes.length; i += 1) {\n          expect(attributes[i].name).not.toMatch(/^data-/);\n        }\n      }\n    });\n\n    it('handles undefined state', async () => {\n      function TestComponent() {\n        const element = useRender({\n          render: <div />,\n          state: undefined,\n          props: {\n            className: 'test-class',\n            'data-from-props': 'value',\n          },\n        });\n        return element;\n      }\n\n      const { container } = await render(<TestComponent />);\n      const div = container.firstElementChild;\n\n      expect(div).toHaveAttribute('class', 'test-class');\n      expect(div).toHaveAttribute('data-from-props', 'value');\n    });\n\n    it('converts boolean values in state to data attributes', async () => {\n      function TestComponent() {\n        const element = useRender({\n          render: <button type=\"button\" />,\n          state: {\n            active: true,\n            disabled: false,\n          },\n        });\n        return element;\n      }\n\n      const { container } = await render(<TestComponent />);\n      const button = container.firstElementChild;\n\n      expect(button).toHaveAttribute('data-active', '');\n      expect(button).not.toHaveAttribute('data-disabled');\n    });\n\n    it('converts number values in state to data attributes', async () => {\n      function TestComponent() {\n        const element = useRender({\n          render: <div />,\n          state: {\n            count: 0,\n            index: 42,\n            percentage: 99.9,\n          },\n        });\n        return element;\n      }\n\n      const { container } = await render(<TestComponent />);\n      const div = container.firstElementChild;\n\n      expect(div).not.toHaveAttribute('data-count');\n      expect(div).toHaveAttribute('data-index', '42');\n      expect(div).toHaveAttribute('data-percentage', '99.9');\n    });\n\n    it('supports custom stateAttributesMapping for kebab-case conversion', async () => {\n      function TestComponent() {\n        const element = useRender({\n          render: <button type=\"button\" />,\n          state: {\n            isActive: true,\n            itemCount: 5,\n            userName: 'John',\n          },\n          stateAttributesMapping: {\n            isActive: (value) => (value ? { 'data-is-active': '' } : null),\n            itemCount: (value) => ({ 'data-item-count': value.toString() }),\n            userName: (value) => ({ 'data-user-name': value }),\n          },\n        });\n        return element;\n      }\n\n      const { container } = await render(<TestComponent />);\n      const button = container.firstElementChild;\n\n      expect(button).toHaveAttribute('data-is-active', '');\n      expect(button).toHaveAttribute('data-item-count', '5');\n      expect(button).toHaveAttribute('data-user-name', 'John');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/use-render/useRender.ts",
    "content": "import * as React from 'react';\nimport type { ComponentRenderFn } from '../utils/types';\nimport { HTMLProps } from '../utils/types';\nimport { useRenderElement } from '../utils/useRenderElement';\nimport { StateAttributesMapping } from '../utils/getStateAttributesProps';\n\n/**\n * Renders a Base UI element.\n *\n * @public\n */\nexport function useRender<\n  State extends Record<string, unknown>,\n  RenderedElementType extends Element,\n  Enabled extends boolean | undefined = undefined,\n>(\n  params: useRender.Parameters<State, RenderedElementType, Enabled>,\n): useRender.ReturnValue<Enabled> {\n  return useRenderElement(params.defaultTagName ?? 'div', params, params);\n}\n\nexport type UseRenderRenderProp<State = Record<string, unknown>> =\n  | React.ReactElement\n  | ComponentRenderFn<React.HTMLAttributes<any>, State>;\n\nexport type UseRenderElementProps<ElementType extends React.ElementType> =\n  React.ComponentPropsWithRef<ElementType>;\n\nexport type UseRenderComponentProps<\n  ElementType extends React.ElementType,\n  State = {},\n  RenderFunctionProps = HTMLProps,\n> = React.ComponentPropsWithRef<ElementType> & {\n  /**\n   * Allows you to replace the component’s HTML element\n   * with a different tag, or compose it with another component.\n   *\n   * Accepts a `ReactElement` or a function that returns the element to render.\n   */\n  render?: React.ReactElement | ComponentRenderFn<RenderFunctionProps, State> | undefined;\n};\n\nexport interface UseRenderParameters<\n  State,\n  RenderedElementType extends Element,\n  Enabled extends boolean | undefined,\n> {\n  /**\n   * The React element or a function that returns one to override the default element.\n   */\n  render?: UseRenderRenderProp<State> | undefined;\n  /**\n   * The ref to apply to the rendered element.\n   */\n  ref?: React.Ref<RenderedElementType> | React.Ref<RenderedElementType>[] | undefined;\n  /**\n   * The state of the component, passed as the second argument to the `render` callback.\n   * State properties are automatically converted to data-* attributes.\n   */\n  state?: State | undefined;\n  /**\n   * Custom mapping for converting state properties to data-* attributes.\n   * @example\n   * { isActive: (value) => (value ? { 'data-is-active': '' } : null) }\n   */\n  stateAttributesMapping?: StateAttributesMapping<State> | undefined;\n  /**\n   * Props to be spread on the rendered element.\n   * They are merged with the internal props of the component, so that event handlers\n   * are merged, `className` strings and `style` properties are joined, while other external props overwrite the\n   * internal ones.\n   */\n  props?: Record<string, unknown> | undefined;\n  /**\n   * If `false`, the hook will skip most of its internal logic and return `null`.\n   * This is useful for rendering a component conditionally.\n   * @default true\n   */\n  enabled?: Enabled | undefined;\n  /**\n   * The default tag name to use for the rendered element when `render` is not provided.\n   * @default 'div'\n   */\n  defaultTagName?: keyof React.JSX.IntrinsicElements | undefined;\n}\n\nexport type UseRenderReturnValue<Enabled extends boolean | undefined> = Enabled extends false\n  ? null\n  : React.ReactElement;\n\nexport interface UseRenderState {}\n\nexport namespace useRender {\n  export type State = UseRenderState;\n  export type RenderProp<TState = Record<string, unknown>> = UseRenderRenderProp<TState>;\n\n  export type ElementProps<ElementType extends React.ElementType> =\n    UseRenderElementProps<ElementType>;\n\n  export type ComponentProps<\n    ElementType extends React.ElementType,\n    TState = {},\n    RenderFunctionProps = HTMLProps,\n  > = UseRenderComponentProps<ElementType, TState, RenderFunctionProps>;\n\n  export type Parameters<\n    TState,\n    RenderedElementType extends Element,\n    Enabled extends boolean | undefined,\n  > = UseRenderParameters<TState, RenderedElementType, Enabled>;\n\n  export type ReturnValue<Enabled extends boolean | undefined> = UseRenderReturnValue<Enabled>;\n}\n"
  },
  {
    "path": "packages/react/src/utils/FloatingPortalLite.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport { useFloatingPortalNode, type FloatingPortal } from '../floating-ui-react';\n\n/**\n * `FloatingPortal` includes tabbable logic handling for focus management.\n * For components that don't need tabbable logic, use `FloatingPortalLite`.\n * @internal\n */\nexport const FloatingPortalLite = React.forwardRef(function FloatingPortalLite(\n  componentProps: FloatingPortalLite.Props<any>,\n  forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { children, container, className, render, ...elementProps } = componentProps;\n\n  const { portalNode, portalSubtree } = useFloatingPortalNode({\n    container,\n    ref: forwardedRef,\n    componentProps,\n    elementProps,\n  });\n\n  if (!portalSubtree && !portalNode) {\n    return null;\n  }\n\n  return (\n    <React.Fragment>\n      {portalSubtree}\n      {portalNode && ReactDOM.createPortal(children, portalNode)}\n    </React.Fragment>\n  );\n});\n\nexport interface FloatingPortalLiteState {}\n\nexport interface FloatingPortalLiteProps<TState> extends FloatingPortal.Props<TState> {}\n\nexport namespace FloatingPortalLite {\n  export type State = FloatingPortalLiteState;\n  export type Props<TState> = FloatingPortalLiteProps<TState>;\n}\n"
  },
  {
    "path": "packages/react/src/utils/FocusGuard.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { isSafari } from '@base-ui/utils/detectBrowser';\nimport { visuallyHidden } from '@base-ui/utils/visuallyHidden';\n\n/**\n * @internal\n */\nexport const FocusGuard = React.forwardRef(function FocusGuard(\n  props: React.ComponentPropsWithoutRef<'span'>,\n  ref: React.ForwardedRef<HTMLSpanElement>,\n) {\n  const [role, setRole] = React.useState<'button' | undefined>();\n\n  useIsoLayoutEffect(() => {\n    if (isSafari) {\n      // Unlike other screen readers such as NVDA and JAWS, the virtual cursor\n      // on VoiceOver does trigger the onFocus event, so we can use the focus\n      // trap element. On Safari, only buttons trigger the onFocus event.\n      setRole('button');\n    }\n  }, []);\n\n  const restProps = {\n    tabIndex: 0,\n    // Role is only for VoiceOver\n    role,\n  };\n\n  return (\n    <span\n      {...props}\n      ref={ref}\n      style={visuallyHidden}\n      aria-hidden={role ? undefined : true}\n      {...restProps}\n      data-base-ui-focus-guard=\"\"\n    />\n  );\n});\n"
  },
  {
    "path": "packages/react/src/utils/InternalBackdrop.tsx",
    "content": "import * as React from 'react';\n\n/**\n * @internal\n */\nexport const InternalBackdrop = React.forwardRef(function InternalBackdrop(\n  props: InternalBackdrop.Props,\n  ref: React.ForwardedRef<HTMLDivElement>,\n) {\n  const { cutout, ...otherProps } = props;\n\n  let clipPath: string | undefined;\n  if (cutout) {\n    const rect = cutout?.getBoundingClientRect();\n    clipPath = `polygon(\n      0% 0%,\n      100% 0%,\n      100% 100%,\n      0% 100%,\n      0% 0%,\n      ${rect.left}px ${rect.top}px,\n      ${rect.left}px ${rect.bottom}px,\n      ${rect.right}px ${rect.bottom}px,\n      ${rect.right}px ${rect.top}px,\n      ${rect.left}px ${rect.top}px\n    )`;\n  }\n\n  return (\n    <div\n      ref={ref}\n      role=\"presentation\"\n      // Ensures Floating UI's outside press detection runs, as it considers\n      // it an element that existed when the popup rendered.\n      data-base-ui-inert=\"\"\n      {...otherProps}\n      style={{\n        position: 'fixed',\n        inset: 0,\n        userSelect: 'none',\n        WebkitUserSelect: 'none',\n        clipPath,\n      }}\n    />\n  );\n});\n\nexport interface InternalBackdropState {}\n\nexport interface InternalBackdropProps extends React.HTMLAttributes<HTMLDivElement> {\n  /**\n   * The element to cut out of the backdrop.\n   * This is useful for allowing certain elements to be interactive while the backdrop is present.\n   */\n  cutout?: Element | null | undefined;\n}\n\nexport namespace InternalBackdrop {\n  export type State = InternalBackdropState;\n  export type Props = InternalBackdropProps;\n}\n"
  },
  {
    "path": "packages/react/src/utils/adaptiveOriginMiddleware.ts",
    "content": "import { ownerDocument, ownerWindow } from '@base-ui/utils/owner';\nimport { getSide } from '@floating-ui/utils';\nimport { Middleware } from '../floating-ui-react';\n\nexport const DEFAULT_SIDES = {\n  sideX: 'left',\n  sideY: 'top',\n};\n\nexport const adaptiveOrigin: Middleware = {\n  name: 'adaptiveOrigin',\n  async fn(state) {\n    const {\n      x: rawX,\n      y: rawY,\n      rects: { floating: floatRect },\n      elements: { floating },\n      platform,\n      strategy,\n      placement,\n    } = state;\n\n    const win = ownerWindow(floating);\n    const styles = win.getComputedStyle(floating);\n    const hasTransition = styles.transitionDuration !== '0s' && styles.transitionDuration !== '';\n\n    if (!hasTransition) {\n      return {\n        x: rawX,\n        y: rawY,\n        data: DEFAULT_SIDES,\n      };\n    }\n\n    const offsetParent = await platform.getOffsetParent?.(floating);\n\n    let offsetDimensions = { width: 0, height: 0 };\n\n    // For fixed strategy, prefer visualViewport if available\n    if (strategy === 'fixed' && win?.visualViewport) {\n      offsetDimensions = {\n        width: win.visualViewport.width,\n        height: win.visualViewport.height,\n      };\n    } else if (offsetParent === win) {\n      const doc = ownerDocument(floating);\n      offsetDimensions = {\n        width: doc.documentElement.clientWidth,\n        height: doc.documentElement.clientHeight,\n      };\n    } else if (await platform.isElement?.(offsetParent)) {\n      offsetDimensions = await platform.getDimensions(offsetParent);\n    }\n\n    const currentSide = getSide(placement);\n    let x = rawX;\n    let y = rawY;\n\n    if (currentSide === 'left') {\n      x = offsetDimensions.width - (rawX + floatRect.width);\n    }\n    if (currentSide === 'top') {\n      y = offsetDimensions.height - (rawY + floatRect.height);\n    }\n\n    const sideX = currentSide === 'left' ? 'right' : DEFAULT_SIDES.sideX;\n    const sideY = currentSide === 'top' ? 'bottom' : DEFAULT_SIDES.sideY;\n    return {\n      x,\n      y,\n      data: {\n        sideX,\n        sideY,\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "packages/react/src/utils/areArraysEqual.ts",
    "content": "type ItemComparer<Item> = (a: Item, b: Item) => boolean;\n\nexport function areArraysEqual<Item>(\n  array1: ReadonlyArray<Item>,\n  array2: ReadonlyArray<Item>,\n  itemComparer: ItemComparer<Item> = (a, b) => a === b,\n) {\n  return (\n    array1.length === array2.length &&\n    array1.every((value, index) => itemComparer(value, array2[index]))\n  );\n}\n"
  },
  {
    "path": "packages/react/src/utils/clamp.test.ts",
    "content": "import { expect } from 'vitest';\nimport { clamp } from './clamp';\n\ndescribe('clamp', () => {\n  it('clamps a value based on min and max', () => {\n    expect(clamp(1, 2, 4)).toBe(2);\n    expect(clamp(5, 2, 4)).toBe(4);\n    expect(clamp(-5, -1, 5)).toBe(-1);\n  });\n});\n"
  },
  {
    "path": "packages/react/src/utils/clamp.ts",
    "content": "export function clamp(\n  val: number,\n  min: number = Number.MIN_SAFE_INTEGER,\n  max: number = Number.MAX_SAFE_INTEGER,\n): number {\n  return Math.max(min, Math.min(val, max));\n}\n"
  },
  {
    "path": "packages/react/src/utils/closePart.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\n\ninterface ClosePartContextValue {\n  register: () => () => void;\n}\n\nconst ClosePartContext = React.createContext<ClosePartContextValue | undefined>(undefined);\n\nexport function useClosePartCount() {\n  const [closePartCount, setClosePartCount] = React.useState(0);\n\n  const register = useStableCallback(() => {\n    setClosePartCount((count) => count + 1);\n\n    return () => {\n      setClosePartCount((count) => Math.max(0, count - 1));\n    };\n  });\n\n  const context = React.useMemo(() => ({ register }), [register]);\n\n  return {\n    context,\n    hasClosePart: closePartCount > 0,\n  };\n}\n\nexport function ClosePartProvider(props: {\n  value: ClosePartContextValue;\n  children: React.ReactNode;\n}) {\n  const { value, children } = props;\n  return <ClosePartContext.Provider value={value}>{children}</ClosePartContext.Provider>;\n}\n\nexport function useClosePartRegistration() {\n  const context = React.useContext(ClosePartContext);\n\n  useIsoLayoutEffect(() => {\n    return context?.register();\n  }, [context]);\n}\n"
  },
  {
    "path": "packages/react/src/utils/collapsibleOpenStateMapping.ts",
    "content": "import type { StateAttributesMapping } from './getStateAttributesProps';\nimport { CollapsiblePanelDataAttributes } from '../collapsible/panel/CollapsiblePanelDataAttributes';\nimport { CollapsibleTriggerDataAttributes } from '../collapsible/trigger/CollapsibleTriggerDataAttributes';\n\nconst PANEL_OPEN_HOOK = {\n  [CollapsiblePanelDataAttributes.open]: '',\n};\n\nconst PANEL_CLOSED_HOOK = {\n  [CollapsiblePanelDataAttributes.closed]: '',\n};\n\nexport const triggerOpenStateMapping: StateAttributesMapping<{\n  open: boolean;\n}> = {\n  open(value) {\n    if (value) {\n      return {\n        [CollapsibleTriggerDataAttributes.panelOpen]: '',\n      };\n    }\n    return null;\n  },\n};\n\nexport const collapsibleOpenStateMapping = {\n  open(value) {\n    if (value) {\n      return PANEL_OPEN_HOOK;\n    }\n    return PANEL_CLOSED_HOOK;\n  },\n} satisfies StateAttributesMapping<{\n  open: boolean;\n}>;\n"
  },
  {
    "path": "packages/react/src/utils/constants.ts",
    "content": "export const TYPEAHEAD_RESET_MS = 500;\nexport const PATIENT_CLICK_THRESHOLD = 500;\nexport const DISABLED_TRANSITIONS_STYLE = { style: { transition: 'none' } };\n\nexport { EMPTY_OBJECT, EMPTY_ARRAY } from '@base-ui/utils/empty';\nexport const CLICK_TRIGGER_IDENTIFIER = 'data-base-ui-click-trigger';\nexport const BASE_UI_SWIPE_IGNORE_ATTRIBUTE = 'data-base-ui-swipe-ignore';\nexport const LEGACY_SWIPE_IGNORE_ATTRIBUTE = 'data-swipe-ignore';\n\nexport const BASE_UI_SWIPE_IGNORE_SELECTOR = `[${BASE_UI_SWIPE_IGNORE_ATTRIBUTE}]`;\nexport const LEGACY_SWIPE_IGNORE_SELECTOR = `[${LEGACY_SWIPE_IGNORE_ATTRIBUTE}]`;\n\n/**\n * Used for dropdowns that usually strictly prefer top/bottom placements and\n * use `var(--available-height)` to limit their height.\n */\nexport const DROPDOWN_COLLISION_AVOIDANCE = {\n  fallbackAxisSide: 'none',\n} as const;\n\n/**\n * Used by regular popups that usually aren't scrollable and are allowed to\n * freely flip to any axis of placement.\n */\nexport const POPUP_COLLISION_AVOIDANCE = {\n  fallbackAxisSide: 'end',\n} as const;\n\n/**\n * Special visually hidden styles for the aria-owns owner element to ensure owned element\n * accessibility in iOS/Safari/VoiceControl.\n * The owner element is an empty span, so most of the common visually hidden styles are not needed.\n * @see https://github.com/floating-ui/floating-ui/issues/3403\n */\nexport const ownerVisuallyHidden: React.CSSProperties = {\n  clipPath: 'inset(50%)',\n  position: 'fixed',\n  top: 0,\n  left: 0,\n};\n"
  },
  {
    "path": "packages/react/src/utils/createBaseUIEventDetails.spec.ts",
    "content": "import { expectType } from '#test-utils';\nimport { createGenericEventDetails } from './createBaseUIEventDetails';\nimport { REASONS } from './reasons';\n\nconst incrementDetails = createGenericEventDetails(REASONS.incrementPress);\nexpectType<typeof REASONS.incrementPress, typeof incrementDetails.reason>(incrementDetails.reason);\n\nconst keyboardDetails = createGenericEventDetails(REASONS.keyboard);\nexpectType<typeof REASONS.keyboard, typeof keyboardDetails.reason>(keyboardDetails.reason);\n\n// @ts-expect-error reason must exist in REASONS\ncreateGenericEventDetails('invalid-reason');\n"
  },
  {
    "path": "packages/react/src/utils/createBaseUIEventDetails.ts",
    "content": "import { EMPTY_OBJECT } from './constants';\nimport { REASONS } from './reasons';\n\ninterface ReasonToEventMap {\n  [REASONS.none]: Event;\n\n  [REASONS.triggerPress]: MouseEvent | PointerEvent | TouchEvent | KeyboardEvent;\n  [REASONS.triggerHover]: MouseEvent;\n  [REASONS.triggerFocus]: FocusEvent;\n\n  [REASONS.outsidePress]: MouseEvent | PointerEvent | TouchEvent;\n  [REASONS.itemPress]: MouseEvent | KeyboardEvent | PointerEvent;\n  [REASONS.closePress]: MouseEvent | KeyboardEvent | PointerEvent;\n  [REASONS.linkPress]: MouseEvent | PointerEvent;\n  [REASONS.clearPress]: PointerEvent | MouseEvent | KeyboardEvent;\n  [REASONS.chipRemovePress]: PointerEvent | MouseEvent | KeyboardEvent;\n  [REASONS.trackPress]: PointerEvent | MouseEvent | TouchEvent;\n  [REASONS.incrementPress]: PointerEvent | MouseEvent | TouchEvent;\n  [REASONS.decrementPress]: PointerEvent | MouseEvent | TouchEvent;\n\n  [REASONS.inputChange]: InputEvent | Event;\n  [REASONS.inputClear]: InputEvent | FocusEvent | Event;\n  [REASONS.inputBlur]: FocusEvent;\n  [REASONS.inputPaste]: ClipboardEvent;\n  [REASONS.inputPress]: MouseEvent | PointerEvent | TouchEvent | KeyboardEvent;\n\n  [REASONS.focusOut]: FocusEvent | KeyboardEvent;\n  [REASONS.escapeKey]: KeyboardEvent;\n  [REASONS.closeWatcher]: Event;\n  [REASONS.listNavigation]: KeyboardEvent;\n  [REASONS.keyboard]: KeyboardEvent;\n\n  [REASONS.pointer]: PointerEvent;\n  [REASONS.drag]: PointerEvent | TouchEvent;\n  [REASONS.swipe]: PointerEvent | TouchEvent;\n  [REASONS.wheel]: WheelEvent;\n  [REASONS.scrub]: PointerEvent;\n\n  [REASONS.cancelOpen]: MouseEvent;\n  [REASONS.siblingOpen]: Event;\n  [REASONS.disabled]: Event;\n  [REASONS.imperativeAction]: Event;\n\n  [REASONS.windowResize]: UIEvent;\n}\n\n/**\n * Maps a change `reason` string to the corresponding native event type.\n */\nexport type ReasonToEvent<Reason extends string> = Reason extends keyof ReasonToEventMap\n  ? ReasonToEventMap[Reason]\n  : Event;\n\ntype BaseUIChangeEventDetail<Reason extends string, CustomProperties extends object> = {\n  /**\n   * The reason for the event.\n   */\n  reason: Reason;\n  /**\n   * The native event associated with the custom event.\n   */\n  event: ReasonToEvent<Reason>;\n  /**\n   * Cancels Base UI from handling the event.\n   */\n  cancel: () => void;\n  /**\n   * Allows the event to propagate in cases where Base UI will stop the propagation.\n   */\n  allowPropagation: () => void;\n  /**\n   * Indicates whether the event has been canceled.\n   */\n  isCanceled: boolean;\n  /**\n   * Indicates whether the event is allowed to propagate.\n   */\n  isPropagationAllowed: boolean;\n  /**\n   * The element that triggered the event, if applicable.\n   */\n  trigger: Element | undefined;\n} & CustomProperties;\n\n/**\n * Details of custom change events emitted by Base UI components.\n */\nexport type BaseUIChangeEventDetails<\n  Reason extends string,\n  CustomProperties extends object = {},\n> = Reason extends string ? BaseUIChangeEventDetail<Reason, CustomProperties> : never;\n\n/**\n * Details of custom generic events emitted by Base UI components.\n */\ntype BaseUIGenericEventDetail<Reason extends string, CustomProperties extends object> = {\n  /**\n   * The reason for the event.\n   */\n  reason: Reason;\n  /**\n   * The native event associated with the custom event.\n   */\n  event: ReasonToEvent<Reason>;\n} & CustomProperties;\n\nexport type BaseUIGenericEventDetails<\n  Reason extends string,\n  CustomProperties extends object = {},\n> = Reason extends string ? BaseUIGenericEventDetail<Reason, CustomProperties> : never;\n\n/**\n * Creates a Base UI event details object with the given reason and utilities\n * for preventing Base UI's internal event handling.\n */\nexport function createChangeEventDetails<\n  Reason extends string,\n  CustomProperties extends object = {},\n>(\n  reason: Reason,\n  event?: ReasonToEvent<Reason>,\n  trigger?: HTMLElement,\n  customProperties?: CustomProperties,\n): BaseUIChangeEventDetails<Reason, CustomProperties> {\n  let canceled = false;\n  let allowPropagation = false;\n  const custom = customProperties ?? (EMPTY_OBJECT as CustomProperties);\n  const details: BaseUIChangeEventDetail<Reason, CustomProperties> = {\n    reason,\n    event: (event ?? new Event('base-ui')) as ReasonToEvent<Reason>,\n    cancel() {\n      canceled = true;\n    },\n    allowPropagation() {\n      allowPropagation = true;\n    },\n    get isCanceled() {\n      return canceled;\n    },\n    get isPropagationAllowed() {\n      return allowPropagation;\n    },\n    trigger,\n    ...custom,\n  };\n  return details as BaseUIChangeEventDetails<Reason, CustomProperties>;\n}\n\nexport function createGenericEventDetails<\n  Reason extends keyof ReasonToEventMap,\n  CustomProperties extends object = {},\n>(\n  reason: Reason,\n  event?: ReasonToEvent<Reason>,\n  customProperties?: CustomProperties,\n): BaseUIGenericEventDetails<Reason, CustomProperties> {\n  const custom = customProperties ?? (EMPTY_OBJECT as CustomProperties);\n  const details: BaseUIGenericEventDetail<Reason, CustomProperties> = {\n    reason,\n    event: (event ?? new Event('base-ui')) as ReasonToEvent<Reason>,\n    ...custom,\n  };\n  return details as BaseUIGenericEventDetails<Reason, CustomProperties>;\n}\n"
  },
  {
    "path": "packages/react/src/utils/formatNumber.test.ts",
    "content": "import { expect } from 'vitest';\nimport { getFormatter } from './formatNumber';\n\nconst getOptions = (): Intl.NumberFormatOptions => ({\n  currency: 'USD',\n  style: 'currency',\n  minimumFractionDigits: 2,\n  maximumFractionDigits: 2,\n});\n\ndescribe('NumberField format', () => {\n  describe('getFormatter', () => {\n    it('caches the formatter based on options', () => {\n      const formatter1 = getFormatter(undefined, getOptions());\n      const formatter2 = getFormatter(undefined, getOptions());\n      expect(formatter1).toBe(formatter2);\n    });\n  });\n\n  describe('formatNumber', () => {\n    it('formats a number', () => {\n      const expected = new Intl.NumberFormat(undefined, {\n        style: 'currency',\n        currency: 'USD',\n      }).format(1234.56);\n      expect(getFormatter(undefined, getOptions()).format(1234.56)).toBe(expected);\n    });\n\n    it('formats a number with different options', () => {\n      expect(getFormatter('en-US', { style: 'percent' }).format(0.1234)).toBe('12%');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/utils/formatNumber.ts",
    "content": "export const cache = new Map<string, Intl.NumberFormat>();\n\nexport function getFormatter(locale?: Intl.LocalesArgument, options?: Intl.NumberFormatOptions) {\n  const optionsString = JSON.stringify({ locale, options });\n  const cachedFormatter = cache.get(optionsString);\n\n  if (cachedFormatter) {\n    return cachedFormatter;\n  }\n\n  const formatter = new Intl.NumberFormat(locale, options);\n  cache.set(optionsString, formatter);\n\n  return formatter;\n}\n\nexport function formatNumber(\n  value: number | null,\n  locale?: Intl.LocalesArgument,\n  options?: Intl.NumberFormatOptions,\n) {\n  if (value == null) {\n    return '';\n  }\n  return getFormatter(locale, options).format(value);\n}\n\nexport function formatNumberMaxPrecision(\n  value: number | null,\n  locale?: Intl.LocalesArgument,\n  options?: Intl.NumberFormatOptions,\n) {\n  return formatNumber(value, locale, {\n    ...options,\n    maximumFractionDigits: 20,\n  });\n}\n\nexport function formatNumberValue(\n  value: number | null,\n  locale?: Intl.LocalesArgument,\n  format?: Intl.NumberFormatOptions,\n): string {\n  if (value == null) {\n    return '';\n  }\n  if (!format) {\n    return formatNumber(value / 100, locale, { style: 'percent' });\n  }\n\n  return formatNumber(value, locale, format);\n}\n"
  },
  {
    "path": "packages/react/src/utils/getCssDimensions.ts",
    "content": "import { type Dimensions, round } from '@floating-ui/utils';\nimport { getComputedStyle, isHTMLElement } from '@floating-ui/utils/dom';\n\nexport function getCssDimensions(element: Element): Dimensions {\n  const css = getComputedStyle(element);\n  // In testing environments, the `width` and `height` properties are empty\n  // strings for SVG elements, returning NaN. Fallback to `0` in this case.\n  let width = parseFloat(css.width) || 0;\n  let height = parseFloat(css.height) || 0;\n  const hasOffset = isHTMLElement(element);\n  const offsetWidth = hasOffset ? element.offsetWidth : width;\n  const offsetHeight = hasOffset ? element.offsetHeight : height;\n  const shouldFallback = round(width) !== offsetWidth || round(height) !== offsetHeight;\n\n  if (shouldFallback) {\n    width = offsetWidth;\n    height = offsetHeight;\n  }\n\n  return {\n    width,\n    height,\n  };\n}\n"
  },
  {
    "path": "packages/react/src/utils/getDisabledMountTransitionStyles.ts",
    "content": "import { DISABLED_TRANSITIONS_STYLE, EMPTY_OBJECT } from './constants';\nimport type { TransitionStatus } from './useTransitionStatus';\n\nexport function getDisabledMountTransitionStyles(transitionStatus: TransitionStatus): {\n  style?: React.CSSProperties | undefined;\n} {\n  return transitionStatus === 'starting' ? DISABLED_TRANSITIONS_STYLE : EMPTY_OBJECT;\n}\n"
  },
  {
    "path": "packages/react/src/utils/getElementAtPoint.ts",
    "content": "export function getElementAtPoint(doc: Document | null | undefined, x: number, y: number) {\n  return typeof doc?.elementFromPoint === 'function' ? doc.elementFromPoint(x, y) : null;\n}\n"
  },
  {
    "path": "packages/react/src/utils/getPseudoElementBounds.ts",
    "content": "interface ElementBounds {\n  left: number;\n  right: number;\n  top: number;\n  bottom: number;\n}\n\nexport function getPseudoElementBounds(element: HTMLElement): ElementBounds {\n  const elementRect = element.getBoundingClientRect();\n\n  // Avoid \"Not implemented: window.getComputedStyle(elt, pseudoElt)\"\n  if (process.env.NODE_ENV !== 'production') {\n    return elementRect;\n  }\n\n  const beforeStyles = window.getComputedStyle(element, '::before');\n  const afterStyles = window.getComputedStyle(element, '::after');\n\n  const hasPseudoElements = beforeStyles.content !== 'none' || afterStyles.content !== 'none';\n\n  if (!hasPseudoElements) {\n    return elementRect;\n  }\n\n  // Get dimensions of pseudo-elements\n  const beforeWidth = parseFloat(beforeStyles.width) || 0;\n  const beforeHeight = parseFloat(beforeStyles.height) || 0;\n  const afterWidth = parseFloat(afterStyles.width) || 0;\n  const afterHeight = parseFloat(afterStyles.height) || 0;\n\n  // Calculate max dimensions including pseudo-elements\n  const totalWidth = Math.max(elementRect.width, beforeWidth, afterWidth);\n  const totalHeight = Math.max(elementRect.height, beforeHeight, afterHeight);\n\n  // Calculate the differences to extend the bounds\n  const widthDiff = totalWidth - elementRect.width;\n  const heightDiff = totalHeight - elementRect.height;\n\n  return {\n    left: elementRect.left - widthDiff / 2,\n    right: elementRect.right + widthDiff / 2,\n    top: elementRect.top - heightDiff / 2,\n    bottom: elementRect.bottom + heightDiff / 2,\n  };\n}\n"
  },
  {
    "path": "packages/react/src/utils/getStateAttributesProps.test.ts",
    "content": "import { expect } from 'vitest';\nimport { getStateAttributesProps } from './getStateAttributesProps';\n\ndescribe('getStateAttributesProps', () => {\n  it('converts the state fields to data attributes', () => {\n    const state = {\n      checked: true,\n      orientation: 'vertical',\n      count: 42,\n    };\n\n    const result = getStateAttributesProps(state);\n    expect(result).toEqual({\n      'data-checked': '',\n      'data-orientation': 'vertical',\n      'data-count': '42',\n    });\n  });\n\n  it('changes the fields names to lowercase', () => {\n    const state = {\n      readOnly: true,\n    };\n\n    const result = getStateAttributesProps(state);\n    expect(result).toEqual({\n      'data-readonly': '',\n    });\n  });\n\n  it('changes true values to a data-attribute without a value', () => {\n    const state = {\n      required: true,\n      disabled: false,\n    };\n\n    const result = getStateAttributesProps(state);\n    expect(result).toEqual({ 'data-required': '' });\n  });\n\n  it('does not include false values', () => {\n    const state = {\n      required: true,\n      disabled: false,\n    };\n\n    const result = getStateAttributesProps(state);\n    expect(result).not.toHaveProperty('data-disabled');\n  });\n\n  it('supports custom mapping', () => {\n    const state = {\n      checked: true,\n      orientation: 'vertical',\n      count: 42,\n    };\n\n    const result = getStateAttributesProps(state, {\n      checked: (value) => ({ 'data-state': value ? 'checked' : 'unchecked' }),\n    });\n\n    expect(result).toEqual({\n      'data-state': 'checked',\n      'data-orientation': 'vertical',\n      'data-count': '42',\n    });\n  });\n\n  it('supports nulls returned from custom mapping', () => {\n    const state = {\n      checked: false,\n      orientation: 'vertical',\n    };\n\n    const result = getStateAttributesProps(state, {\n      checked: (value) => (value === true ? { 'data-state': 'checked' } : null),\n    });\n\n    expect(result).toEqual({\n      'data-orientation': 'vertical',\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/utils/getStateAttributesProps.ts",
    "content": "export type StateAttributesMapping<State> = {\n  [Property in keyof State]?: (state: State[Property]) => Record<string, string> | null;\n};\n\nexport function getStateAttributesProps<State extends Record<string, any>>(\n  state: State,\n  customMapping?: StateAttributesMapping<State>,\n) {\n  const props: Record<string, string> = {};\n\n  /* eslint-disable-next-line guard-for-in */\n  for (const key in state) {\n    const value = state[key];\n\n    if (customMapping?.hasOwnProperty(key)) {\n      const customProps = customMapping[key]!(value);\n      if (customProps != null) {\n        Object.assign(props, customProps);\n      }\n\n      continue;\n    }\n\n    if (value === true) {\n      props[`data-${key.toLowerCase()}`] = '';\n    } else if (value) {\n      props[`data-${key.toLowerCase()}`] = value.toString();\n    }\n  }\n\n  return props;\n}\n"
  },
  {
    "path": "packages/react/src/utils/hideMiddleware.ts",
    "content": "import { hide as nativeHide, type Middleware } from '@floating-ui/react-dom';\n\nexport const hide: Middleware = {\n  name: 'hide',\n  async fn(state) {\n    const { width, height, x, y } = state.rects.reference;\n    const anchorHidden = width === 0 && height === 0 && x === 0 && y === 0;\n    const nativeHideResult = await nativeHide().fn(state);\n    return {\n      data: {\n        referenceHidden: nativeHideResult.data?.referenceHidden || anchorHidden,\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "packages/react/src/utils/itemEquality.ts",
    "content": "export type ItemEqualityComparer<Item = any, Value = Item> = (\n  itemValue: Item,\n  selectedValue: Value,\n) => boolean;\n\nexport const defaultItemEquality: ItemEqualityComparer = (itemValue, selectedValue) =>\n  Object.is(itemValue, selectedValue);\n\nexport function compareItemEquality<Item, Value>(\n  itemValue: Item,\n  selectedValue: Value,\n  comparer: ItemEqualityComparer<Item, Value>,\n): boolean {\n  if (itemValue == null || selectedValue == null) {\n    return Object.is(itemValue, selectedValue);\n  }\n  return comparer(itemValue, selectedValue);\n}\n\nexport function selectedValueIncludes<Item, Value>(\n  selectedValues: readonly Item[] | undefined | null,\n  itemValue: Value,\n  comparer: ItemEqualityComparer<Value, Item>,\n): boolean {\n  if (!selectedValues || selectedValues.length === 0) {\n    return false;\n  }\n  return selectedValues.some((selectedValue) => {\n    if (selectedValue === undefined) {\n      return false;\n    }\n    return compareItemEquality(itemValue, selectedValue, comparer);\n  });\n}\n\nexport function findItemIndex<Item, Value>(\n  itemValues: readonly Item[] | undefined | null,\n  selectedValue: Value,\n  comparer: ItemEqualityComparer<Item, Value>,\n): number {\n  if (!itemValues || itemValues.length === 0) {\n    return -1;\n  }\n  return itemValues.findIndex((itemValue) => {\n    if (itemValue === undefined) {\n      return false;\n    }\n    return compareItemEquality(itemValue, selectedValue, comparer);\n  });\n}\n\nexport function removeItem<Item, Value>(\n  selectedValues: readonly Item[],\n  itemValue: Value,\n  comparer: ItemEqualityComparer<Value, Item>,\n): Item[] {\n  return selectedValues.filter(\n    (selectedValue) => !compareItemEquality(itemValue, selectedValue, comparer),\n  );\n}\n"
  },
  {
    "path": "packages/react/src/utils/noop.ts",
    "content": "export { NOOP } from '@base-ui/utils/empty';\n"
  },
  {
    "path": "packages/react/src/utils/popupStateMapping.ts",
    "content": "import type { StateAttributesMapping } from './getStateAttributesProps';\nimport { TransitionStatusDataAttributes } from './stateAttributesMapping';\n\nexport enum CommonPopupDataAttributes {\n  /**\n   * Present when the popup is open.\n   */\n  open = 'data-open',\n  /**\n   * Present when the popup is closed.\n   */\n  closed = 'data-closed',\n  /**\n   * Present when the popup is animating in.\n   */\n  startingStyle = TransitionStatusDataAttributes.startingStyle,\n  /**\n   * Present when the popup is animating out.\n   */\n  endingStyle = TransitionStatusDataAttributes.endingStyle,\n  /**\n   * Present when the anchor is hidden.\n   */\n  anchorHidden = 'data-anchor-hidden',\n  /**\n   * Indicates which side the popup is positioned relative to the trigger.\n   * @type { 'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'}\n   */\n  side = 'data-side',\n  /**\n   * Indicates how the popup is aligned relative to specified side.\n   * @type {'start' | 'center' | 'end'}\n   */\n  align = 'data-align',\n}\n\nexport enum CommonTriggerDataAttributes {\n  /**\n   * Present when the popup is open.\n   */\n  popupOpen = 'data-popup-open',\n  /**\n   * Present when a pressable trigger is pressed.\n   */\n  pressed = 'data-pressed',\n}\n\nconst TRIGGER_HOOK = {\n  [CommonTriggerDataAttributes.popupOpen]: '',\n};\n\nconst PRESSABLE_TRIGGER_HOOK = {\n  [CommonTriggerDataAttributes.popupOpen]: '',\n  [CommonTriggerDataAttributes.pressed]: '',\n};\n\nconst POPUP_OPEN_HOOK = {\n  [CommonPopupDataAttributes.open]: '',\n};\n\nconst POPUP_CLOSED_HOOK = {\n  [CommonPopupDataAttributes.closed]: '',\n};\n\nconst ANCHOR_HIDDEN_HOOK = {\n  [CommonPopupDataAttributes.anchorHidden]: '',\n};\n\nexport const triggerOpenStateMapping = {\n  open(value) {\n    if (value) {\n      return TRIGGER_HOOK;\n    }\n    return null;\n  },\n} satisfies StateAttributesMapping<{ open: boolean }>;\n\nexport const pressableTriggerOpenStateMapping = {\n  open(value) {\n    if (value) {\n      return PRESSABLE_TRIGGER_HOOK;\n    }\n    return null;\n  },\n} satisfies StateAttributesMapping<{ open: boolean }>;\n\nexport const popupStateMapping = {\n  open(value) {\n    if (value) {\n      return POPUP_OPEN_HOOK;\n    }\n    return POPUP_CLOSED_HOOK;\n  },\n  anchorHidden(value) {\n    if (value) {\n      return ANCHOR_HIDDEN_HOOK;\n    }\n    return null;\n  },\n} satisfies StateAttributesMapping<{ open: boolean; anchorHidden: boolean }>;\n"
  },
  {
    "path": "packages/react/src/utils/popups/index.ts",
    "content": "export * from './popupStoreUtils';\nexport * from './popupTriggerMap';\nexport * from './store';\n"
  },
  {
    "path": "packages/react/src/utils/popups/popupStoreUtils.test.tsx",
    "content": "import { describe, expect, it, vi } from 'vitest';\nimport * as React from 'react';\nimport { render } from '@testing-library/react';\nimport { ReactStore } from '@base-ui/utils/store';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport {\n  createInitialPopupStoreState,\n  PopupStoreContext,\n  PopupStoreState,\n  PopupStoreSelectors,\n  PopupTriggerMap,\n  useTriggerRegistration,\n} from './';\n\nfunction createStore() {\n  return new ReactStore<PopupStoreState<unknown>, PopupStoreContext<unknown>, PopupStoreSelectors>(\n    createInitialPopupStoreState(),\n    {\n      triggerElements: new PopupTriggerMap(),\n      popupRef: React.createRef<HTMLElement | null>(),\n      onOpenChangeComplete: undefined,\n    },\n  );\n}\n\nfunction TestTrigger({\n  id,\n  store,\n  element,\n  repeat = 1,\n}: {\n  id: string;\n  store: ReactStore<PopupStoreState<unknown>, PopupStoreContext<unknown>, PopupStoreSelectors>;\n  element: HTMLElement;\n  repeat?: number;\n}) {\n  const register = useTriggerRegistration(id, store);\n\n  useIsoLayoutEffect(() => {\n    for (let i = 0; i < repeat; i += 1) {\n      register(element);\n    }\n    return () => {\n      register(null);\n    };\n  }, [register, repeat, element]);\n\n  return null;\n}\n\ndescribe('PopupTriggerMap', () => {\n  it('stores and retrieves elements by id', () => {\n    const map = new PopupTriggerMap();\n    const button = document.createElement('button');\n\n    map.add('trigger', button);\n\n    expect(map.getById('trigger')).toBe(button);\n    expect(map.hasElement(button)).toBe(true);\n    expect(map.hasMatchingElement((element) => element === button)).toBe(true);\n  });\n\n  it('replaces a registered element when the id is reused', () => {\n    const map = new PopupTriggerMap();\n    const first = document.createElement('button');\n    const second = document.createElement('button');\n\n    map.add('trigger', first);\n    map.add('trigger', second);\n\n    expect(map.getById('trigger')).toBe(second);\n    expect(map.hasElement(first)).toBe(false);\n    expect(map.hasElement(second)).toBe(true);\n  });\n\n  it('deletes an element and no longer matches it', () => {\n    const map = new PopupTriggerMap();\n    const button = document.createElement('button');\n\n    map.add('trigger', button);\n    map.delete('trigger');\n\n    expect(map.getById('trigger')).toBeUndefined();\n    expect(map.hasElement(button)).toBe(false);\n    expect(map.hasMatchingElement((element) => element === button)).toBe(false);\n  });\n});\n\ndescribe('useTriggerRegistration', () => {\n  it('registers and unregisters triggers through the context map', () => {\n    const store = createStore();\n    const spy = vi.spyOn(store, 'set');\n    const element = document.createElement('button');\n\n    const { unmount } = render(\n      <TestTrigger id=\"trigger\" store={store} element={element} repeat={3} />,\n    );\n\n    expect(store.context.triggerElements.getById('trigger')).toBe(element);\n    expect(store.context.triggerElements.hasElement(element)).toBe(true);\n    expect(spy).not.toHaveBeenCalled();\n\n    unmount();\n    expect(store.context.triggerElements.getById('trigger')).toBeUndefined();\n    expect(store.context.triggerElements.hasElement(element)).toBe(false);\n  });\n\n  it('re-registers when the trigger id changes without notifying the store', () => {\n    const store = createStore();\n    const spy = vi.spyOn(store, 'set');\n    const element = document.createElement('button');\n\n    const { rerender, unmount } = render(\n      <TestTrigger id=\"first\" store={store} element={element} />,\n    );\n\n    expect(store.context.triggerElements.getById('first')).toBe(element);\n    expect(spy).not.toHaveBeenCalled();\n\n    rerender(<TestTrigger id=\"second\" store={store} element={element} />);\n\n    expect(store.context.triggerElements.getById('first')).toBeUndefined();\n    expect(store.context.triggerElements.getById('second')).toBe(element);\n    expect(spy).not.toHaveBeenCalled();\n\n    unmount();\n    expect(store.context.triggerElements.getById('second')).toBeUndefined();\n    expect(store.context.triggerElements.hasElement(element)).toBe(false);\n  });\n});\n"
  },
  {
    "path": "packages/react/src/utils/popups/popupStoreUtils.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { ReactStore } from '@base-ui/utils/store';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useTransitionStatus } from '../useTransitionStatus';\nimport { useOpenChangeComplete } from '../useOpenChangeComplete';\nimport {\n  PopupStoreState,\n  PopupStoreContext,\n  popupStoreSelectors,\n  PopupStoreSelectors,\n} from './store';\n\n/**\n * Returns a callback ref that registers/unregisters the trigger element in the store.\n *\n * @param store The Store instance where the trigger should be registered.\n */\nexport function useTriggerRegistration<State extends PopupStoreState<any>>(\n  id: string | undefined,\n  store: ReactStore<State, PopupStoreContext<any>, PopupStoreSelectors>,\n) {\n  // Keep track of the currently registered element to unregister it on unmount or id change.\n  const registeredElementIdRef = React.useRef<string | null>(null);\n  const registeredElementRef = React.useRef<Element | null>(null);\n\n  return React.useCallback(\n    (element: Element | null) => {\n      if (id === undefined) {\n        return;\n      }\n\n      if (registeredElementIdRef.current !== null) {\n        const registeredId = registeredElementIdRef.current;\n        const registeredElement = registeredElementRef.current;\n        const currentElement = store.context.triggerElements.getById(registeredId);\n\n        if (registeredElement && currentElement === registeredElement) {\n          store.context.triggerElements.delete(registeredId);\n        }\n\n        registeredElementIdRef.current = null;\n        registeredElementRef.current = null;\n      }\n\n      if (element !== null) {\n        registeredElementIdRef.current = id;\n        registeredElementRef.current = element;\n        store.context.triggerElements.add(id, element);\n      }\n    },\n    [store, id],\n  );\n}\n\n/**\n * Sets up trigger data forwarding to the store.\n *\n * @param triggerId Id of the trigger.\n * @param triggerElement The trigger DOM element.\n * @param store The Store instance managing the popup state.\n * @param stateUpdates An object with state updates to apply when the trigger is active.\n */\nexport function useTriggerDataForwarding<State extends PopupStoreState<any>>(\n  triggerId: string | undefined,\n  triggerElementRef: React.RefObject<Element | null>,\n  store: ReactStore<State, PopupStoreContext<any>, typeof popupStoreSelectors>,\n  stateUpdates: Omit<Partial<State>, 'activeTriggerId' | 'activeTriggerElement'>,\n) {\n  const isMountedByThisTrigger = store.useState('isMountedByTrigger', triggerId);\n\n  const baseRegisterTrigger = useTriggerRegistration(triggerId, store);\n\n  const registerTrigger = useStableCallback((element: Element | null) => {\n    baseRegisterTrigger(element);\n\n    if (!element || !store.select('open')) {\n      return;\n    }\n\n    const activeTriggerId = store.select('activeTriggerId');\n\n    if (activeTriggerId === triggerId) {\n      store.update({\n        activeTriggerElement: element,\n        ...stateUpdates,\n      } as Partial<State>);\n      return;\n    }\n\n    if (activeTriggerId == null) {\n      // This runs when popup is open, but no active trigger is set.\n      // It can happen when using controlled mode and the trigger is mounted after opening or if `triggerId` prop is not set explicitly.\n      // In such cases the first trigger to run this code becomes the active trigger (store.select('activeTriggerId') should not return null after that).\n      // This is mostly for compatibility with contained triggers where no explicit `triggerId` was required in controlled mode.\n      store.update({\n        activeTriggerId: triggerId,\n        activeTriggerElement: element,\n        ...stateUpdates,\n      } as Partial<State>);\n    }\n  });\n\n  useIsoLayoutEffect(() => {\n    if (isMountedByThisTrigger) {\n      store.update({\n        activeTriggerElement: triggerElementRef.current,\n        ...stateUpdates,\n      } as Partial<State>);\n    }\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [isMountedByThisTrigger, store, triggerElementRef, ...Object.values(stateUpdates)]);\n\n  return { registerTrigger, isMountedByThisTrigger };\n}\n\nexport type PayloadChildRenderFunction<Payload> = (arg: {\n  payload: Payload | undefined;\n}) => React.ReactNode;\n\n/**\n * Ensures that when there's only one trigger element registered, it is set as the active trigger.\n * This allows controlled popups to work correctly without an explicit triggerId, maintaining compatibility\n * with the contained triggers.\n *\n * This should be called on the Root part.\n *\n * @param open Whether the popup is open.\n * @param store The Store instance managing the popup state.\n */\nexport function useImplicitActiveTrigger<State extends PopupStoreState<any>>(\n  store: ReactStore<State, PopupStoreContext<any>, typeof popupStoreSelectors>,\n) {\n  const open = store.useState('open');\n  useIsoLayoutEffect(() => {\n    if (open && !store.select('activeTriggerId') && store.context.triggerElements.size === 1) {\n      const iteratorResult = store.context.triggerElements.entries().next();\n      if (!iteratorResult.done) {\n        const [implicitTriggerId, implicitTriggerElement] = iteratorResult.value;\n        store.update({\n          activeTriggerId: implicitTriggerId,\n          activeTriggerElement: implicitTriggerElement,\n        } as Partial<State>);\n      }\n    }\n  }, [open, store]);\n}\n\n/**\n * Mangages the mounted state of the popup.\n * Sets up the transition status listeners and handles unmounting when needed.\n * Updates the `mounted` and `transitionStatus` states in the store.\n *\n * @param open Whether the popup is open.\n * @param store The Store instance managing the popup state.\n * @param onUnmount Optional callback to be called when the popup is unmounted.\n *\n * @returns A function to forcibly unmount the popup.\n */\nexport function useOpenStateTransitions<State extends PopupStoreState<any>>(\n  open: boolean,\n  store: ReactStore<State, PopupStoreContext<any>, typeof popupStoreSelectors>,\n  onUnmount?: () => void,\n) {\n  const { mounted, setMounted, transitionStatus } = useTransitionStatus(open);\n\n  store.useSyncedValues({ mounted, transitionStatus } as Partial<State>);\n\n  const forceUnmount = useStableCallback(() => {\n    setMounted(false);\n    store.update({\n      activeTriggerId: null,\n      activeTriggerElement: null,\n      mounted: false,\n    } as Partial<State>);\n    onUnmount?.();\n    store.context.onOpenChangeComplete?.(false);\n  });\n\n  const preventUnmountingOnClose = store.useState('preventUnmountingOnClose');\n\n  useOpenChangeComplete({\n    enabled: !preventUnmountingOnClose,\n    open,\n    ref: store.context.popupRef,\n    onComplete() {\n      if (!open) {\n        forceUnmount();\n      }\n    },\n  });\n\n  return { forceUnmount, transitionStatus };\n}\n"
  },
  {
    "path": "packages/react/src/utils/popups/popupTriggerMap.test.ts",
    "content": "import { describe, expect, it } from 'vitest';\nimport { isJSDOM } from '#test-utils';\nimport { PopupTriggerMap } from './popupTriggerMap';\n\ndescribe('PopupTriggerMap', () => {\n  it('adds and retrieves elements by id', () => {\n    const map = new PopupTriggerMap();\n    const button = document.createElement('button');\n\n    map.add('trigger', button);\n\n    expect(map.getById('trigger')).toBe(button);\n    expect(map.hasElement(button)).toBe(true);\n    expect(map.hasMatchingElement((el) => el === button)).toBe(true);\n    expect(map.size).toBe(1);\n  });\n\n  it('replaces an existing element when the id is reused', () => {\n    const map = new PopupTriggerMap();\n    const first = document.createElement('button');\n    const second = document.createElement('button');\n\n    map.add('trigger', first);\n    map.add('trigger', second);\n\n    expect(map.getById('trigger')).toBe(second);\n    expect(map.hasElement(first)).toBe(false);\n    expect(map.hasElement(second)).toBe(true);\n    expect(map.size).toBe(1);\n  });\n\n  it('deletes elements by id', () => {\n    const map = new PopupTriggerMap();\n    const button = document.createElement('button');\n\n    map.add('trigger', button);\n    map.delete('trigger');\n\n    expect(map.getById('trigger')).toBeUndefined();\n    expect(map.hasElement(button)).toBe(false);\n    expect(map.hasMatchingElement((el) => el === button)).toBe(false);\n    expect(map.size).toBe(0);\n  });\n\n  it('does not duplicate when the same element is added twice with the same id', () => {\n    const map = new PopupTriggerMap();\n    const button = document.createElement('button');\n\n    map.add('trigger', button);\n    map.add('trigger', button);\n\n    expect(map.getById('trigger')).toBe(button);\n    expect(map.size).toBe(1);\n  });\n\n  it('throws in non-production when the same element is registered under multiple ids', () => {\n    const originalEnv = process.env.NODE_ENV;\n    process.env.NODE_ENV = 'development';\n\n    const map = new PopupTriggerMap();\n    const button = document.createElement('button');\n\n    try {\n      map.add('first', button);\n      expect(() => map.add('second', button)).toThrow(\n        'Base UI: A trigger element cannot be registered under multiple IDs in PopupTriggerMap.',\n      );\n    } finally {\n      process.env.NODE_ENV = originalEnv;\n    }\n  });\n\n  it.skipIf(!isJSDOM)(\n    'does not throw in production when the same element is registered under multiple ids',\n    () => {\n      const originalEnv = process.env.NODE_ENV;\n      process.env.NODE_ENV = 'production';\n\n      const map = new PopupTriggerMap();\n      const button = document.createElement('button');\n\n      try {\n        map.add('first', button);\n        expect(() => map.add('second', button)).not.toThrow();\n        expect(map.getById('first')).toBe(button);\n        expect(map.getById('second')).toBe(button);\n        expect(map.hasElement(button)).toBe(true);\n        expect(map.size).toBe(2);\n      } finally {\n        process.env.NODE_ENV = originalEnv;\n      }\n    },\n  );\n});\n"
  },
  {
    "path": "packages/react/src/utils/popups/popupTriggerMap.ts",
    "content": "/**\n * Data structure to keep track of popup trigger elements by their IDs.\n * Uses both a set of Elements and a map of IDs to Elements for efficient lookups.\n */\nexport class PopupTriggerMap {\n  private elementsSet: Set<Element>;\n\n  private idMap: Map<string, Element>;\n\n  constructor() {\n    this.elementsSet = new Set();\n    this.idMap = new Map();\n  }\n\n  /**\n   * Adds a trigger element with the given ID.\n   *\n   * Note: The provided element is assumed to not be registered under multiple IDs.\n   */\n  public add(id: string, element: Element) {\n    const existingElement = this.idMap.get(id);\n    if (existingElement === element) {\n      return;\n    }\n\n    if (existingElement !== undefined) {\n      // We assume that the same element won't be registered under multiple ids.\n      // This is safe considering how useTriggerRegistration is implemented.\n      this.elementsSet.delete(existingElement);\n    }\n\n    this.elementsSet.add(element);\n    this.idMap.set(id, element);\n\n    if (process.env.NODE_ENV !== 'production') {\n      if (this.elementsSet.size !== this.idMap.size) {\n        throw new Error(\n          'Base UI: A trigger element cannot be registered under multiple IDs in PopupTriggerMap.',\n        );\n      }\n    }\n  }\n\n  /**\n   * Removes the trigger element with the given ID.\n   */\n  public delete(id: string) {\n    const element = this.idMap.get(id);\n    if (element) {\n      this.elementsSet.delete(element);\n      this.idMap.delete(id);\n    }\n  }\n\n  /**\n   * Whether the given element is registered as a trigger.\n   */\n  public hasElement(element: Element): boolean {\n    return this.elementsSet.has(element);\n  }\n\n  /**\n   * Whether there is a registered trigger element matching the given predicate.\n   */\n  public hasMatchingElement(predicate: (el: Element) => boolean): boolean {\n    for (const element of this.elementsSet) {\n      if (predicate(element)) {\n        return true;\n      }\n    }\n\n    return false;\n  }\n\n  /**\n   * Returns the trigger element associated with the given ID, or undefined if no such element exists.\n   */\n  public getById(id: string): Element | undefined {\n    return this.idMap.get(id);\n  }\n\n  /**\n   * Returns an iterable of all registered trigger entries, where each entry is a tuple of [id, element].\n   */\n  public entries(): IterableIterator<[string, Element]> {\n    return this.idMap.entries();\n  }\n\n  /**\n   * Returns an iterable of all registered trigger elements.\n   */\n  public elements(): IterableIterator<Element> {\n    return this.elementsSet.values();\n  }\n\n  /**\n   * Returns the number of registered trigger elements.\n   */\n  public get size(): number {\n    return this.idMap.size;\n  }\n}\n"
  },
  {
    "path": "packages/react/src/utils/popups/store.ts",
    "content": "import { createSelector } from '@base-ui/utils/store';\nimport { FloatingRootContext } from '../../floating-ui-react';\nimport { getEmptyRootContext } from '../../floating-ui-react/utils/getEmptyRootContext';\nimport { EMPTY_OBJECT } from '../constants';\nimport { TransitionStatus } from '../useTransitionStatus';\nimport { PopupTriggerMap } from './popupTriggerMap';\nimport { HTMLProps } from '../types';\n\n/**\n * State common to all popup stores.\n */\nexport type PopupStoreState<Payload> = {\n  /**\n   * Whether the popup is open (internal state).\n   */\n  open: boolean;\n  /**\n   * Whether the popup is open (external prop).\n   */\n  readonly openProp: boolean | undefined;\n  /**\n   * Whether the popup should be mounted in the DOM.\n   * This usually follows `open` but can be different during exit transitions.\n   */\n  mounted: boolean;\n  /**\n   * The current enter/exit transition status of the popup.\n   */\n  transitionStatus: TransitionStatus;\n\n  floatingRootContext: FloatingRootContext;\n  /**\n   * Whether to prevent unmounting the popup when closed.\n   * Useful for interactling with JS animation libraries that control unmounting themselves.\n   */\n  preventUnmountingOnClose: boolean;\n\n  /**\n   * Optional payload set by the trigger.\n   */\n  payload: Payload | undefined;\n\n  /**\n   * ID of the currently active trigger.\n   */\n  activeTriggerId: string | null;\n  /**\n   * The currently active trigger DOM element.\n   */\n  activeTriggerElement: Element | null;\n  /**\n   * ID of the trigger (external prop).\n   */\n  readonly triggerIdProp: string | null | undefined;\n  /**\n   * The popup DOM element.\n   */\n  popupElement: HTMLElement | null;\n  /**\n   * The positioner DOM element.\n   */\n  positionerElement: HTMLElement | null;\n\n  /**\n   * Props to spread onto the active trigger element.\n   */\n  activeTriggerProps: HTMLProps;\n  /**\n   * Props to spread onto inactive trigger elements.\n   */\n  inactiveTriggerProps: HTMLProps;\n  /**\n   * Props to spread onto the popup element.\n   */\n  popupProps: HTMLProps;\n};\n\nexport function createInitialPopupStoreState<Payload>(): PopupStoreState<Payload> {\n  return {\n    open: false,\n    openProp: undefined,\n    mounted: false,\n    transitionStatus: 'idle',\n    floatingRootContext: getEmptyRootContext(),\n    preventUnmountingOnClose: false,\n    payload: undefined,\n    activeTriggerId: null,\n    activeTriggerElement: null,\n    triggerIdProp: undefined,\n    popupElement: null,\n    positionerElement: null,\n    activeTriggerProps: EMPTY_OBJECT as HTMLProps,\n    inactiveTriggerProps: EMPTY_OBJECT as HTMLProps,\n    popupProps: EMPTY_OBJECT as HTMLProps,\n  };\n}\n\nexport type PopupStoreContext<ChangeEventDetails> = {\n  /**\n   * Map of registered trigger elements.\n   */\n  readonly triggerElements: PopupTriggerMap;\n  /**\n   * Reference to the popup element.\n   */\n  readonly popupRef: React.RefObject<HTMLElement | null>;\n  /**\n   * Callback fired when the open state changes.\n   */\n  onOpenChange?: ((open: boolean, eventDetails: ChangeEventDetails) => void) | undefined;\n  /**\n   * Callback fired when the open state change animation completes.\n   */\n  onOpenChangeComplete: ((open: boolean) => void) | undefined;\n};\n\ntype S = PopupStoreState<unknown>;\n\nconst activeTriggerIdSelector = createSelector(\n  (state: S) => state.triggerIdProp ?? state.activeTriggerId,\n);\n\nexport const popupStoreSelectors = {\n  open: createSelector((state: S) => state.openProp ?? state.open),\n  mounted: createSelector((state: S) => state.mounted),\n  transitionStatus: createSelector((state: S) => state.transitionStatus),\n  floatingRootContext: createSelector((state: S) => state.floatingRootContext),\n  preventUnmountingOnClose: createSelector((state: S) => state.preventUnmountingOnClose),\n  payload: createSelector((state: S) => state.payload),\n\n  activeTriggerId: activeTriggerIdSelector,\n  activeTriggerElement: createSelector((state: S) =>\n    state.mounted ? state.activeTriggerElement : null,\n  ),\n  /**\n   * Whether the trigger with the given ID was used to open the popup.\n   */\n  isTriggerActive: createSelector(\n    (state: S, triggerId: string | undefined) =>\n      triggerId !== undefined && activeTriggerIdSelector(state) === triggerId,\n  ),\n  /**\n   * Whether the popup is open and was activated by a trigger with the given ID.\n   */\n  isOpenedByTrigger: createSelector(\n    (state: S, triggerId: string | undefined) =>\n      triggerId !== undefined && activeTriggerIdSelector(state) === triggerId && state.open,\n  ),\n  /**\n   * Whether the popup is mounted and was activated by a trigger with the given ID.\n   */\n  isMountedByTrigger: createSelector(\n    (state: S, triggerId: string | undefined) =>\n      triggerId !== undefined && activeTriggerIdSelector(state) === triggerId && state.mounted,\n  ),\n\n  triggerProps: createSelector((state: S, isActive: boolean) =>\n    isActive ? state.activeTriggerProps : state.inactiveTriggerProps,\n  ),\n  popupProps: createSelector((state: S) => state.popupProps),\n\n  popupElement: createSelector((state: S) => state.popupElement),\n  positionerElement: createSelector((state: S) => state.positionerElement),\n};\n\nexport type PopupStoreSelectors = typeof popupStoreSelectors;\n"
  },
  {
    "path": "packages/react/src/utils/reason-parts.ts",
    "content": "export const none = 'none' as const;\n\nexport const triggerPress = 'trigger-press' as const;\nexport const triggerHover = 'trigger-hover' as const;\nexport const triggerFocus = 'trigger-focus' as const;\n\nexport const outsidePress = 'outside-press' as const;\nexport const itemPress = 'item-press' as const;\nexport const closePress = 'close-press' as const;\nexport const linkPress = 'link-press' as const;\nexport const clearPress = 'clear-press' as const;\nexport const chipRemovePress = 'chip-remove-press' as const;\nexport const trackPress = 'track-press' as const;\nexport const incrementPress = 'increment-press' as const;\nexport const decrementPress = 'decrement-press' as const;\n\nexport const inputChange = 'input-change' as const;\nexport const inputClear = 'input-clear' as const;\nexport const inputBlur = 'input-blur' as const;\nexport const inputPaste = 'input-paste' as const;\nexport const inputPress = 'input-press' as const;\n\nexport const focusOut = 'focus-out' as const;\nexport const escapeKey = 'escape-key' as const;\nexport const closeWatcher = 'close-watcher' as const;\nexport const listNavigation = 'list-navigation' as const;\nexport const keyboard = 'keyboard' as const;\n\nexport const pointer = 'pointer' as const;\nexport const drag = 'drag' as const;\nexport const wheel = 'wheel' as const;\nexport const scrub = 'scrub' as const;\n\nexport const cancelOpen = 'cancel-open' as const;\nexport const siblingOpen = 'sibling-open' as const;\nexport const disabled = 'disabled' as const;\nexport const imperativeAction = 'imperative-action' as const;\nexport const swipe = 'swipe' as const;\n\nexport const windowResize = 'window-resize' as const;\n\nexport const dayPress = 'day-press' as const;\nexport const monthChange = 'month-change' as const;\nexport const valuePropChange = 'value-prop-change' as const;\n"
  },
  {
    "path": "packages/react/src/utils/reasons.ts",
    "content": "import * as REASONS from './reason-parts';\n\nexport { REASONS };\nexport type BaseUIEventReasons = typeof REASONS;\nexport type BaseUIEventReason = BaseUIEventReasons[keyof BaseUIEventReasons];\n"
  },
  {
    "path": "packages/react/src/utils/resolveAriaLabelledBy.ts",
    "content": "'use client';\n\nexport function getDefaultLabelId(id: string | null | undefined) {\n  return id == null ? undefined : `${id}-label`;\n}\n\nexport function resolveAriaLabelledBy(\n  fieldLabelId: string | undefined,\n  localLabelId: string | undefined,\n) {\n  return fieldLabelId ?? localLabelId;\n}\n"
  },
  {
    "path": "packages/react/src/utils/resolveClassName.ts",
    "content": "/**\n * If the provided className is a string, it will be returned as is.\n * Otherwise, the function will call the className function with the state as the first argument.\n *\n * @param className\n * @param state\n */\nexport function resolveClassName<State>(\n  className: string | ((state: State) => string | undefined) | undefined,\n  state: State,\n) {\n  return typeof className === 'function' ? className(state) : className;\n}\n"
  },
  {
    "path": "packages/react/src/utils/resolveRef.ts",
    "content": "/**\n * If the provided argument is a ref object, returns its `current` value.\n * Otherwise, returns the argument itself.\n */\nexport function resolveRef<T extends HTMLElement | null | undefined>(\n  maybeRef: T | React.RefObject<T>,\n): T {\n  if (maybeRef == null) {\n    return maybeRef;\n  }\n\n  return 'current' in maybeRef ? maybeRef.current : maybeRef;\n}\n"
  },
  {
    "path": "packages/react/src/utils/resolveStyle.ts",
    "content": "/**\n * If the provided style is an object, it will be returned as is.\n * Otherwise, the function will call the style function with the state as the first argument.\n *\n * @param style\n * @param state\n */\nexport function resolveStyle<State>(\n  style: React.CSSProperties | ((state: State) => React.CSSProperties | undefined) | undefined,\n  state: State,\n) {\n  return typeof style === 'function' ? style(state) : style;\n}\n"
  },
  {
    "path": "packages/react/src/utils/resolveValueLabel.test.ts",
    "content": "import { expect } from 'vitest';\nimport { hasNullItemLabel } from './resolveValueLabel';\n\ndescribe('resolveValueLabel', () => {\n  describe('hasNullItemLabel', () => {\n    it('returns true when grouped items contain a null-valued item with a label', () => {\n      const items = [\n        {\n          value: 'group-1',\n          items: [\n            { value: 'a', label: 'A' },\n            { value: null, label: 'Select' },\n          ],\n        },\n      ];\n\n      expect(hasNullItemLabel(items)).toBe(true);\n    });\n\n    it('returns false when grouped items contain a null-valued item without a label', () => {\n      const items = [\n        {\n          value: 'group-1',\n          items: [\n            { value: null, label: null },\n            { value: 'a', label: 'A' },\n          ],\n        },\n      ];\n\n      expect(hasNullItemLabel(items)).toBe(false);\n    });\n\n    it('returns false when grouped items do not contain a null-valued item', () => {\n      const items = [\n        {\n          value: 'group-1',\n          items: [{ value: 'a', label: 'A' }],\n        },\n      ];\n\n      expect(hasNullItemLabel(items)).toBe(false);\n    });\n\n    it('supports grouped items with custom heading keys', () => {\n      const items = [\n        {\n          heading: 'group-1',\n          items: [\n            { value: 'a', label: 'A' },\n            { value: null, label: 'Select' },\n          ],\n        },\n      ];\n\n      expect(hasNullItemLabel(items)).toBe(true);\n    });\n\n    it('returns true when flat items contain a null-valued item with a label', () => {\n      const items = [\n        { value: 'a', label: 'A' },\n        { value: null, label: 'None' },\n      ];\n\n      expect(hasNullItemLabel(items)).toBe(true);\n    });\n\n    it('returns false when flat items do not contain a null-valued item', () => {\n      const items = [\n        { value: 'a', label: 'A' },\n        { value: 'b', label: 'B' },\n      ];\n\n      expect(hasNullItemLabel(items)).toBe(false);\n    });\n\n    it('returns false when items is a Record without a \"null\" key', () => {\n      const items = {\n        sans: 'Sans-serif',\n        serif: 'Serif',\n        mono: 'Monospace',\n      };\n\n      expect(hasNullItemLabel(items)).toBe(false);\n    });\n\n    it('returns true when items is a Record with a \"null\" key', () => {\n      const items = {\n        null: 'None',\n        sans: 'Sans-serif',\n        serif: 'Serif',\n      };\n\n      expect(hasNullItemLabel(items)).toBe(true);\n    });\n\n    it('returns false when items is undefined', () => {\n      expect(hasNullItemLabel(undefined)).toBe(false);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/utils/resolveValueLabel.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { serializeValue } from './serializeValue';\n\ntype ItemRecord = Record<string, React.ReactNode>;\ntype ItemsInput = ItemRecord | ReadonlyArray<LabeledItem> | ReadonlyArray<Group<any>> | undefined;\n\ninterface LabeledItem {\n  value: any;\n  label: React.ReactNode;\n}\n\nexport interface Group<Item = any> {\n  [key: string]: unknown;\n  items: ReadonlyArray<Item>;\n}\n\nexport function isGroupedItems(\n  items: ReadonlyArray<any | Group<any>> | undefined,\n): items is ReadonlyArray<Group<any>> {\n  return (\n    items != null &&\n    items.length > 0 &&\n    typeof items[0] === 'object' &&\n    items[0] != null &&\n    'items' in items[0]\n  );\n}\n\n/**\n * Checks if the items array contains an item with a null value that has a non-null label.\n */\nexport function hasNullItemLabel(items: ItemsInput): boolean {\n  if (!Array.isArray(items)) {\n    return items != null && 'null' in items;\n  }\n\n  const arrayItems = items as ReadonlyArray<LabeledItem> | ReadonlyArray<Group<any>>;\n\n  if (isGroupedItems(arrayItems)) {\n    for (const group of arrayItems) {\n      for (const item of group.items) {\n        if (item && item.value == null && item.label != null) {\n          return true;\n        }\n      }\n    }\n    return false;\n  }\n\n  for (const item of arrayItems) {\n    if (item && item.value == null && item.label != null) {\n      return true;\n    }\n  }\n\n  return false;\n}\n\nexport function stringifyAsLabel(item: any, itemToStringLabel?: (item: any) => string) {\n  if (itemToStringLabel && item != null) {\n    return itemToStringLabel(item) ?? '';\n  }\n  if (item && typeof item === 'object') {\n    if ('label' in item && item.label != null) {\n      return String(item.label);\n    }\n    if ('value' in item) {\n      return String(item.value);\n    }\n  }\n  return serializeValue(item);\n}\n\nexport function stringifyAsValue(item: any, itemToStringValue?: (item: any) => string) {\n  if (itemToStringValue && item != null) {\n    return itemToStringValue(item) ?? '';\n  }\n  if (item && typeof item === 'object' && 'value' in item && 'label' in item) {\n    return serializeValue(item.value);\n  }\n  return serializeValue(item);\n}\n\nexport function resolveSelectedLabel(\n  value: any,\n  items: ItemsInput,\n  itemToStringLabel?: (item: any) => string,\n): React.ReactNode {\n  function fallback() {\n    return stringifyAsLabel(value, itemToStringLabel);\n  }\n\n  if (itemToStringLabel && value != null) {\n    return itemToStringLabel(value);\n  }\n\n  // Custom object with explicit label takes precedence\n  if (value && typeof value === 'object' && 'label' in value && value.label != null) {\n    return value.label;\n  }\n\n  // Items provided as plain record map\n  if (items && !Array.isArray(items)) {\n    return (items as any)[value] ?? fallback();\n  }\n\n  // Items provided as array (flat or grouped)\n  if (Array.isArray(items)) {\n    const arrayItems = items as ReadonlyArray<LabeledItem> | ReadonlyArray<Group<any>>;\n    const flatItems: ReadonlyArray<LabeledItem> = isGroupedItems(arrayItems)\n      ? arrayItems.flatMap((group) => group.items)\n      : arrayItems;\n\n    if (value == null || typeof value !== 'object') {\n      const match = flatItems.find((item) => item.value === value);\n      if (match && match.label != null) {\n        return match.label;\n      }\n      return fallback();\n    }\n\n    // Object without explicit label: try matching by its `value` property\n    if ('value' in value) {\n      const match = flatItems.find((item) => item && item.value === value.value);\n      if (match && match.label != null) {\n        return match.label;\n      }\n    }\n  }\n\n  return fallback();\n}\n\nexport function resolveMultipleLabels(\n  values: any[],\n  items: ItemsInput,\n  itemToStringLabel?: (item: any) => string,\n): React.ReactNode {\n  return values.reduce((acc, value, index) => {\n    if (index > 0) {\n      acc.push(', ');\n    }\n    acc.push(\n      <React.Fragment key={index}>\n        {resolveSelectedLabel(value, items, itemToStringLabel)}\n      </React.Fragment>,\n    );\n    return acc;\n  }, []);\n}\n"
  },
  {
    "path": "packages/react/src/utils/scrollable.ts",
    "content": "import { getComputedStyle, isHTMLElement } from '@floating-ui/utils/dom';\n\nexport type ScrollAxis = 'horizontal' | 'vertical';\n\nexport function isScrollable(element: HTMLElement, axis: ScrollAxis): boolean {\n  const style = getComputedStyle(element);\n\n  if (axis === 'vertical') {\n    const overflowY = style.overflowY;\n    return (\n      (overflowY === 'auto' || overflowY === 'scroll') &&\n      element.scrollHeight > element.clientHeight\n    );\n  }\n\n  const overflowX = style.overflowX;\n  return (\n    (overflowX === 'auto' || overflowX === 'scroll') && element.scrollWidth > element.clientWidth\n  );\n}\n\nexport function hasScrollableAncestor(\n  target: HTMLElement,\n  root: HTMLElement,\n  axes: ScrollAxis[],\n): boolean {\n  let node: HTMLElement | null = target;\n  while (node && node !== root) {\n    for (const axis of axes) {\n      if (isScrollable(node, axis)) {\n        return true;\n      }\n    }\n    node = node.parentElement;\n  }\n  return false;\n}\n\nexport function findScrollableTouchTarget(\n  target: EventTarget | null,\n  root: HTMLElement,\n  axis: ScrollAxis = 'vertical',\n): HTMLElement | null {\n  let node = isHTMLElement(target) ? target : null;\n  while (node && node !== root) {\n    if (isScrollable(node, axis)) {\n      return node;\n    }\n    node = node.parentElement;\n  }\n\n  return isScrollable(root, axis) ? root : null;\n}\n"
  },
  {
    "path": "packages/react/src/utils/serializeValue.ts",
    "content": "export function serializeValue(value: unknown): string {\n  if (value == null) {\n    return '';\n  }\n  if (typeof value === 'string') {\n    return value;\n  }\n  try {\n    return JSON.stringify(value);\n  } catch {\n    return String(value);\n  }\n}\n"
  },
  {
    "path": "packages/react/src/utils/stateAttributesMapping.ts",
    "content": "import type { TransitionStatus } from './useTransitionStatus';\nimport type { StateAttributesMapping } from './getStateAttributesProps';\n\nexport enum TransitionStatusDataAttributes {\n  /**\n   * Present when the component is animating in.\n   */\n  startingStyle = 'data-starting-style',\n  /**\n   * Present when the component is animating out.\n   */\n  endingStyle = 'data-ending-style',\n}\n\nconst STARTING_HOOK = { [TransitionStatusDataAttributes.startingStyle]: '' };\nconst ENDING_HOOK = { [TransitionStatusDataAttributes.endingStyle]: '' };\n\nexport const transitionStatusMapping = {\n  transitionStatus(value): Record<string, string> | null {\n    if (value === 'starting') {\n      return STARTING_HOOK;\n    }\n    if (value === 'ending') {\n      return ENDING_HOOK;\n    }\n    return null;\n  },\n} satisfies StateAttributesMapping<{ transitionStatus: TransitionStatus }>;\n"
  },
  {
    "path": "packages/react/src/utils/styles.tsx",
    "content": "const DISABLE_SCROLLBAR_CLASS_NAME = 'base-ui-disable-scrollbar';\n\nexport const styleDisableScrollbar = {\n  className: DISABLE_SCROLLBAR_CLASS_NAME,\n  getElement(nonce?: string) {\n    return (\n      <style nonce={nonce} href={DISABLE_SCROLLBAR_CLASS_NAME} precedence=\"base-ui:low\">\n        {`.${DISABLE_SCROLLBAR_CLASS_NAME}{scrollbar-width:none}.${DISABLE_SCROLLBAR_CLASS_NAME}::-webkit-scrollbar{display:none}`}\n      </style>\n    );\n  },\n};\n"
  },
  {
    "path": "packages/react/src/utils/temporal/date-helpers.ts",
    "content": "import { TemporalAdapter, TemporalSupportedObject } from '../../types/temporal';\n\nexport function mergeDateAndTime(\n  adapter: TemporalAdapter,\n  dateParam: TemporalSupportedObject,\n  timeParam: TemporalSupportedObject,\n): TemporalSupportedObject {\n  let mergedDate = dateParam;\n  mergedDate = adapter.setHours(mergedDate, adapter.getHours(timeParam));\n  mergedDate = adapter.setMinutes(mergedDate, adapter.getMinutes(timeParam));\n  mergedDate = adapter.setSeconds(mergedDate, adapter.getSeconds(timeParam));\n  mergedDate = adapter.setMilliseconds(mergedDate, adapter.getMilliseconds(timeParam));\n\n  return mergedDate;\n}\n\nexport function areDatesEqual(\n  adapter: TemporalAdapter,\n  a: TemporalSupportedObject | null,\n  b: TemporalSupportedObject | null,\n) {\n  if (!adapter.isValid(a) && a != null && !adapter.isValid(b) && b != null) {\n    return true;\n  }\n\n  return adapter.isEqual(a, b);\n}\n\nexport function replaceInvalidDateByNull(\n  adapter: TemporalAdapter,\n  value: TemporalSupportedObject | null,\n): TemporalSupportedObject | null {\n  if (adapter.isValid(value)) {\n    return value;\n  }\n  return null;\n}\n\n/**\n * Check if the day of the date A is after the day of the date B.\n * Uses timezone of the date A.\n */\nexport function isAfterDay(\n  adapter: TemporalAdapter,\n  dateA: TemporalSupportedObject,\n  dateB: TemporalSupportedObject,\n): boolean {\n  const dateBWithCorrectTimezone = adapter.setTimezone(dateB, adapter.getTimezone(dateA));\n  return adapter.isAfter(dateA, adapter.endOfDay(dateBWithCorrectTimezone));\n}\n\n/**\n * Check if the day of the date A is before the day of the date B.\n * Uses timezone of the date A.\n */\nexport function isBeforeDay(\n  adapter: TemporalAdapter,\n  dateA: TemporalSupportedObject,\n  dateB: TemporalSupportedObject,\n): boolean {\n  const dateBWithCorrectTimezone = adapter.setTimezone(dateB, adapter.getTimezone(dateA));\n  return adapter.isBefore(dateA, adapter.startOfDay(dateBWithCorrectTimezone));\n}\n\nexport function formatMonthFullLetterAndYear(\n  adapter: TemporalAdapter,\n  date: TemporalSupportedObject,\n) {\n  const f = adapter.formats;\n  const dateFormat = `${f.monthFullLetter} ${f.yearPadded}`;\n\n  return adapter.formatByString(date, dateFormat);\n}\n"
  },
  {
    "path": "packages/react/src/utils/temporal/getDateManager.ts",
    "content": "import { areDatesEqual } from './date-helpers';\nimport { validateDate, ValidateDateReturnValue, ValidateDateValidationProps } from './validateDate';\nimport { TemporalManager } from './types';\nimport { TemporalValue, TemporalAdapter } from '../../types/temporal';\n\nexport function getDateManager(adapter: TemporalAdapter): GetDateManagerReturnValue {\n  return {\n    emptyValue: null,\n    emptyValidationError: null,\n    areValuesEqual: (valueA, valueB) => areDatesEqual(adapter, valueA, valueB),\n    getValidationError: (value, validationProps) =>\n      validateDate({ adapter, value, validationProps }),\n    areValidationErrorEquals: (errorA, errorB) => errorA === errorB,\n    isValidationErrorEmpty: (error) => error == null,\n    getTimezone: (value) => (adapter.isValid(value) ? adapter.getTimezone(value) : null),\n    setTimezone: (value, timezone) => (value == null ? null : adapter.setTimezone(value, timezone)),\n    getDatesFromValue: (value) => (value == null ? [] : [value]),\n  };\n}\n\nexport type GetDateManagerReturnValue = TemporalManager<\n  TemporalValue,\n  ValidateDateReturnValue,\n  ValidateDateValidationProps\n>;\n"
  },
  {
    "path": "packages/react/src/utils/temporal/getInitialReferenceDate.test.ts",
    "content": "import { expect } from 'vitest';\nimport { TemporalAdapterDateFns } from '../../temporal-adapter-date-fns/TemporalAdapterDateFns';\nimport { getInitialReferenceDate } from './getInitialReferenceDate';\n\ndescribe('getInitialReferenceDate', () => {\n  const adapter = new TemporalAdapterDateFns();\n\n  describe('when externalDate is provided and valid', () => {\n    it('should return externalDate', () => {\n      const result = getInitialReferenceDate({\n        adapter,\n        timezone: 'default',\n        externalDate: adapter.date('2025-03-15', 'default'),\n        externalReferenceDate: null,\n        validationProps: {},\n      });\n\n      expect(result).toEqualDateTime('2025-03-15');\n    });\n\n    it('should return externalDate even if externalReferenceDate is also provided', () => {\n      const result = getInitialReferenceDate({\n        adapter,\n        timezone: 'default',\n        externalDate: adapter.date('2025-03-15', 'default'),\n        externalReferenceDate: adapter.date('2025-04-20', 'default'),\n        validationProps: {},\n      });\n\n      expect(result).toEqualDateTime('2025-03-15');\n    });\n\n    it('should return externalDate even if it is outside minDate/maxDate bounds', () => {\n      const result = getInitialReferenceDate({\n        adapter,\n        timezone: 'default',\n        externalDate: adapter.date('2025-01-15', 'default'),\n        externalReferenceDate: null,\n        validationProps: {\n          minDate: adapter.date('2025-02-01', 'default'),\n          maxDate: adapter.date('2025-12-31', 'default'),\n        },\n      });\n\n      // externalDate is returned as-is, validation bounds don't apply\n      expect(result).toEqualDateTime('2025-01-15');\n    });\n  });\n\n  describe('when externalReferenceDate is provided and valid (no externalDate)', () => {\n    it('should return externalReferenceDate', () => {\n      const result = getInitialReferenceDate({\n        adapter,\n        timezone: 'default',\n        externalDate: null,\n        externalReferenceDate: adapter.date('2025-04-20', 'default'),\n        validationProps: {},\n      });\n\n      expect(result).toEqualDateTime('2025-04-20');\n    });\n\n    it('should return externalReferenceDate even if it is outside minDate/maxDate bounds', () => {\n      const result = getInitialReferenceDate({\n        adapter,\n        timezone: 'default',\n        externalDate: null,\n        externalReferenceDate: adapter.date('2025-01-15', 'default'),\n        validationProps: {\n          minDate: adapter.date('2025-02-01', 'default'),\n          maxDate: adapter.date('2025-12-31', 'default'),\n        },\n      });\n\n      // externalReferenceDate is returned as-is, validation bounds don't apply\n      expect(result).toEqualDateTime('2025-01-15');\n    });\n  });\n\n  describe('when neither externalDate nor externalReferenceDate is provided', () => {\n    it('should return the current date (start of day)', () => {\n      // Set a fixed time for this test\n      vi.setSystemTime(new Date('2025-06-15T14:30:00.000Z'));\n\n      const result = getInitialReferenceDate({\n        adapter,\n        timezone: 'default',\n        externalDate: null,\n        externalReferenceDate: null,\n        validationProps: {},\n      });\n\n      expect(result).toEqualDateTime('2025-06-15');\n      vi.useRealTimers();\n    });\n\n    it('should clamp to minDate if current date is before minDate', () => {\n      vi.setSystemTime(new Date('2025-01-15T14:30:00.000Z'));\n\n      const result = getInitialReferenceDate({\n        adapter,\n        timezone: 'default',\n        externalDate: null,\n        externalReferenceDate: null,\n        validationProps: { minDate: adapter.date('2025-02-01', 'default') },\n      });\n\n      expect(result).toEqualDateTime('2025-02-01');\n      vi.useRealTimers();\n    });\n\n    it('should clamp to maxDate if current date is after maxDate', () => {\n      vi.setSystemTime(new Date('2025-12-15T14:30:00.000Z'));\n\n      const result = getInitialReferenceDate({\n        adapter,\n        timezone: 'default',\n        externalDate: null,\n        externalReferenceDate: null,\n        validationProps: { maxDate: adapter.date('2025-06-30', 'default') },\n      });\n\n      expect(result).toEqualDateTime('2025-06-30');\n      vi.useRealTimers();\n    });\n\n    it('should return current date if it is within minDate/maxDate bounds', () => {\n      vi.setSystemTime(new Date('2025-06-15T14:30:00.000Z'));\n\n      const result = getInitialReferenceDate({\n        adapter,\n        timezone: 'default',\n        externalDate: null,\n        externalReferenceDate: null,\n        validationProps: {\n          minDate: adapter.date('2025-01-01', 'default'),\n          maxDate: adapter.date('2025-12-31', 'default'),\n        },\n      });\n\n      expect(result).toEqualDateTime('2025-06-15');\n      vi.useRealTimers();\n    });\n  });\n\n  describe('timezone handling', () => {\n    it('should set the timezone on the returned date', () => {\n      const result = getInitialReferenceDate({\n        adapter,\n        timezone: 'UTC',\n        externalDate: adapter.date('2025-03-15', 'default'),\n        externalReferenceDate: null,\n        validationProps: {},\n      });\n\n      expect(adapter.getTimezone(result)).toBe('UTC');\n    });\n\n    it('should set the timezone when using externalReferenceDate', () => {\n      const result = getInitialReferenceDate({\n        adapter,\n        timezone: 'UTC',\n        externalDate: null,\n        externalReferenceDate: adapter.date('2025-04-20', 'default'),\n        validationProps: {},\n      });\n\n      expect(adapter.getTimezone(result)).toBe('UTC');\n    });\n\n    it('should set the timezone when falling back to current date', () => {\n      vi.setSystemTime(new Date('2025-06-15T14:30:00.000Z'));\n\n      const result = getInitialReferenceDate({\n        adapter,\n        timezone: 'UTC',\n        externalDate: null,\n        externalReferenceDate: null,\n        validationProps: {},\n      });\n\n      expect(adapter.getTimezone(result)).toBe('UTC');\n      vi.useRealTimers();\n    });\n  });\n\n  describe('invalid dates handling', () => {\n    it('should fall back to externalReferenceDate if externalDate is invalid', () => {\n      const result = getInitialReferenceDate({\n        adapter,\n        timezone: 'default',\n        externalDate: new Date('invalid'),\n        externalReferenceDate: adapter.date('2025-04-20', 'default'),\n        validationProps: {},\n      });\n\n      expect(result).toEqualDateTime('2025-04-20');\n    });\n\n    it('should fall back to current date if both externalDate and externalReferenceDate are invalid', () => {\n      vi.setSystemTime(new Date('2025-06-15T14:30:00.000Z'));\n\n      const result = getInitialReferenceDate({\n        adapter,\n        timezone: 'default',\n        externalDate: new Date('invalid'),\n        externalReferenceDate: new Date('also-invalid'),\n        validationProps: {},\n      });\n\n      expect(result).toEqualDateTime('2025-06-15');\n      vi.useRealTimers();\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/utils/temporal/getInitialReferenceDate.ts",
    "content": "import { TemporalAdapter, TemporalTimezone, TemporalSupportedObject } from '../../types/temporal';\nimport { isAfterDay, isBeforeDay } from './date-helpers';\nimport { ValidateDateValidationProps } from './validateDate';\n\nexport function getInitialReferenceDate(\n  parameters: GetInitialReferenceDateParameters,\n): TemporalSupportedObject {\n  const {\n    adapter,\n    timezone,\n    externalDate,\n    externalReferenceDate,\n    validationProps: { minDate, maxDate },\n  } = parameters;\n  let referenceDate: TemporalSupportedObject | null = null;\n\n  if (adapter.isValid(externalDate)) {\n    referenceDate = externalDate;\n  } else if (adapter.isValid(externalReferenceDate)) {\n    referenceDate = externalReferenceDate;\n  } else {\n    referenceDate = adapter.startOfDay(adapter.now(timezone));\n    if (minDate != null && isBeforeDay(adapter, referenceDate, minDate)) {\n      referenceDate = minDate;\n    }\n    if (maxDate != null && isAfterDay(adapter, referenceDate, maxDate)) {\n      referenceDate = maxDate;\n    }\n  }\n\n  return adapter.setTimezone(referenceDate, timezone);\n}\n\nexport interface GetInitialReferenceDateParameters {\n  /**\n   * The adapter used to manipulate the date.\n   */\n  adapter: TemporalAdapter;\n  /**\n   * The date provided by the user, if any.\n   * If the component is a range component, this will be the start date if defined or the end date otherwise.\n   */\n  externalDate: TemporalSupportedObject | null;\n  /**\n   * The reference date provided by the user, if any.\n   */\n  externalReferenceDate: TemporalSupportedObject | null;\n  /**\n   * The timezone the reference date should be in.\n   */\n  timezone: TemporalTimezone;\n  /**\n   * The props used to validate the date, time or date-time object.\n   */\n  validationProps: GetInitialReferenceDateValidationProps;\n}\n\nexport interface GetInitialReferenceDateValidationProps extends ValidateDateValidationProps {}\n"
  },
  {
    "path": "packages/react/src/utils/temporal/types.ts",
    "content": "import {\n  TemporalTimezone,\n  TemporalSupportedValue,\n  TemporalSupportedObject,\n} from '../../types/temporal';\n\nexport interface TemporalTimezoneProps {\n  /**\n   * Choose which timezone to use for the value.\n   * Example: \"default\", \"system\", \"UTC\", \"America/New_York\".\n   * If you pass values from other timezones to some props, they will be converted to this timezone before being used.\n   * @default 'The timezone of the \"value\" or \"defaultValue\" prop if defined, \"default\" otherwise.'\n   */\n  timezone?: TemporalTimezone | undefined;\n}\n\nexport interface TemporalOnErrorProps<TValue extends TemporalSupportedValue, TError> {\n  /**\n   * Callback fired when the error associated with the current value changes.\n   * When a validation error is detected, the `error` parameter contains a non-null value.\n   * This can be used to render an appropriate form error.\n   * @template TError The validation error type. It will be either `string` or a `null`. It can be in `[start, end]` format in case of range value.\n   * @template TValue The value type. It will be the same type as `value` or `null`. It can be in `[start, end]` format in case of range value.\n   * @param {TError} error The reason why the current value is not valid.\n   * @param {TValue} value The value associated with the error.\n   */\n  onError?: ((error: TError, value: TValue) => void) | undefined;\n}\n\n/**\n * Object that contains all the necessary methods and properties to adapt a temporal component or utilities for a given value type.\n */\nexport interface TemporalManager<\n  TValue extends TemporalSupportedValue,\n  TError,\n  TValidationProps extends {},\n> {\n  /**\n   * Value to set when emptying the component.\n   */\n  emptyValue: TValue;\n  /**\n   * Error when the value is valid.\n   * It is used to initialize the error state.\n   */\n  emptyValidationError: TError;\n  /**\n   * Checks whether two values are equal.\n   */\n  areValuesEqual: (valueA: TValue, valueB: TValue) => boolean;\n  /**\n   * Returns the error associated with a value for the current set of validation props.\n   */\n  getValidationError: (value: TValue, validationProps: TValidationProps) => TError;\n  /**\n   * Checks whether two validation errors are equal.\n   */\n  areValidationErrorEquals: (errorA: TError, errorB: TError | null) => boolean;\n  /**\n   * Checks whether the current validation error is empty.\n   */\n  isValidationErrorEmpty: (error: TError) => boolean;\n  /**\n   * Returns the timezone of the date inside a value.\n   * When used on a range component, throw an error if both values don't have the same timezone.\n   */\n  getTimezone: (value: TValue) => string | null;\n  /**\n   * Changes the timezone of the dates inside a value.\n   */\n  setTimezone: (value: TValue, timezone: TemporalTimezone) => TValue;\n  /**\n   * Returns the list of dates contained in the value.\n   */\n  getDatesFromValue: (value: TValue) => TemporalSupportedObject[];\n}\n"
  },
  {
    "path": "packages/react/src/utils/temporal/validateDate.test.tsx",
    "content": "import { expect } from 'vitest';\n// TODO Temporal: Replace with `@base-ui/react/types` import when Temporal components will become public.\nimport { TemporalAdapter } from '../../types/temporal';\nimport { TemporalAdapterDateFns } from '../../temporal-adapter-date-fns';\nimport { TemporalAdapterLuxon } from '../../temporal-adapter-luxon';\nimport { validateDate } from './validateDate';\n\n// @ts-expect-error\nconst adapters: TemporalAdapter[] = [new TemporalAdapterDateFns(), new TemporalAdapterLuxon()];\n\nadapters.forEach((adapter) => {\n  describe(`${adapter.constructor.name}: validateDate`, () => {\n    it('should return null when no min date and not max date are provided', () => {\n      expect(\n        validateDate({\n          adapter,\n          value: adapter.date('2025-06-03', 'default'),\n          validationProps: {},\n        }),\n      ).toBe(null);\n    });\n\n    it('should return null when the provided value is between the min date and the max date', () => {\n      expect(\n        validateDate({\n          adapter,\n          value: adapter.date('2025-06-03', 'default'),\n          validationProps: {\n            minDate: adapter.date('2025-06-01', 'default'),\n            maxDate: adapter.date('2025-06-05', 'default'),\n          },\n        }),\n      ).toBe(null);\n    });\n\n    it('should return null when the provided value is on the same day as the min date', () => {\n      expect(\n        validateDate({\n          adapter,\n          value: adapter.date('2025-06-03T15:30', 'default'),\n          validationProps: {\n            minDate: adapter.date('2025-06-03T20:30', 'default'),\n          },\n        }),\n      ).toBe(null);\n    });\n\n    it('should return null when the provided value is on the same day as the max date', () => {\n      expect(\n        validateDate({\n          adapter,\n          value: adapter.date('2025-06-03T20:30', 'default'),\n          validationProps: {\n            maxDate: adapter.date('2025-06-03T15:30', 'default'),\n          },\n        }),\n      ).toBe(null);\n    });\n\n    it('should return \"invalid\" when receiving an invalid date', () => {\n      expect(\n        validateDate({\n          adapter,\n          value: adapter.date('Invalid date', 'default'),\n          validationProps: {},\n        }),\n      ).toBe('invalid');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/utils/temporal/validateDate.ts",
    "content": "import { TemporalAdapter, TemporalValue, TemporalSupportedObject } from '../../types/temporal';\nimport { isAfterDay, isBeforeDay } from './date-helpers';\n\nexport function validateDate(parameters: ValidateDateParameters): ValidateDateReturnValue {\n  const { adapter, value, validationProps } = parameters;\n  if (value === null) {\n    return null;\n  }\n\n  const { minDate, maxDate } = validationProps;\n\n  if (!adapter.isValid(value)) {\n    return 'invalid';\n  }\n  if (minDate != null && isBeforeDay(adapter, value, minDate)) {\n    return 'before-min-date';\n  }\n  if (maxDate != null && isAfterDay(adapter, value, maxDate)) {\n    return 'after-max-date';\n  }\n  return null;\n}\n\nexport interface ValidateDateParameters {\n  /**\n   * The adapter used to manipulate the date.\n   */\n  adapter: TemporalAdapter;\n  /**\n   * The value to validate.\n   */\n  value: TemporalValue;\n  /**\n   * The props used to validate a date.\n   */\n  validationProps: ValidateDateValidationProps;\n}\n\nexport interface ValidateDateValidationProps {\n  /**\n   * Minimal selectable date.\n   */\n  minDate?: TemporalSupportedObject | undefined;\n  /**\n   * Maximal selectable date.\n   */\n  maxDate?: TemporalSupportedObject | undefined;\n}\n\nexport type ValidateDateReturnValue = 'invalid' | 'before-min-date' | 'after-max-date' | null;\n"
  },
  {
    "path": "packages/react/src/utils/types.ts",
    "content": "import type * as React from 'react';\nimport type { BaseUIEvent, ComponentRenderFn, HTMLProps } from '../types';\n\nexport type { HTMLProps, BaseUIEvent, ComponentRenderFn };\n\nexport interface FloatingUIOpenChangeDetails {\n  open: boolean;\n  reason: string;\n  nativeEvent: Event;\n  nested: boolean;\n  triggerElement?: Element | undefined;\n}\n\ntype WithPreventBaseUIHandler<T> = T extends (event: infer E) => any\n  ? E extends React.SyntheticEvent<Element, Event>\n    ? (event: BaseUIEvent<E>) => ReturnType<T>\n    : T\n  : T extends undefined\n    ? undefined\n    : T;\n\n/**\n * Adds a `preventBaseUIHandler` method to all event handlers.\n */\nexport type WithBaseUIEvent<T> = {\n  [K in keyof T]: WithPreventBaseUIHandler<T[K]>;\n};\n\n/**\n * Props shared by all Base UI components.\n * Contains `className` (string or callback taking the component's state as an argument) and `render` (function to customize rendering).\n */\nexport type BaseUIComponentProps<\n  ElementType extends React.ElementType,\n  State,\n  RenderFunctionProps = HTMLProps,\n> = Omit<\n  WithBaseUIEvent<React.ComponentPropsWithRef<ElementType>>,\n  'className' | 'color' | 'defaultValue' | 'defaultChecked'\n> & {\n  /**\n   * CSS class applied to the element, or a function that\n   * returns a class based on the component’s state.\n   */\n  className?: string | ((state: State) => string | undefined) | undefined;\n  /**\n   * Allows you to replace the component’s HTML element\n   * with a different tag, or compose it with another component.\n   *\n   * Accepts a `ReactElement` or a function that returns the element to render.\n   */\n  render?: React.ReactElement | ComponentRenderFn<RenderFunctionProps, State> | undefined;\n  /**\n   * Style applied to the element, or a function that\n   * returns a style object based on the component’s state.\n   */\n  style?: React.CSSProperties | ((state: State) => React.CSSProperties | undefined) | undefined;\n};\n\nexport interface NativeButtonProps {\n  /**\n   * Whether the component renders a native `<button>` element when replacing it\n   * via the `render` prop.\n   * Set to `false` if the rendered element is not a button (e.g. `<div>`).\n   * @default true\n   */\n  nativeButton?: boolean | undefined;\n}\n\nexport interface NonNativeButtonProps {\n  /**\n   * Whether the component renders a native `<button>` element when replacing it\n   * via the `render` prop.\n   * Set to `true` if the rendered element is a native button.\n   * @default false\n   */\n  nativeButton?: boolean | undefined;\n}\n\n/**\n * Simplifies the display of a type (without modifying it).\n * Taken from https://effectivetypescript.com/2022/02/25/gentips-4-display/\n */\nexport type Simplify<T> = T extends Function ? T : { [K in keyof T]: T[K] };\n\n/**\n * Makes specified keys in a type required.\n *\n * @template T - The original type.\n * @template K - The keys to make required.\n */\nexport type RequiredExcept<T, K extends keyof T> = Required<Omit<T, K>> & Pick<T, K>;\n\nexport type Orientation = 'horizontal' | 'vertical';\n"
  },
  {
    "path": "packages/react/src/utils/useAnchorPositioning.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { getSide, getAlignment, type Rect, getSideAxis } from '@floating-ui/utils';\nimport { ownerDocument } from '@base-ui/utils/owner';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useValueAsRef } from '@base-ui/utils/useValueAsRef';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport {\n  autoUpdate,\n  flip,\n  limitShift,\n  offset,\n  shift,\n  useFloating,\n  size,\n  type UseFloatingOptions,\n  type Placement,\n  type FloatingRootContext,\n  type VirtualElement,\n  type Padding,\n  type FloatingContext,\n  type Side as PhysicalSide,\n  type MiddlewareState,\n  type AutoUpdateOptions,\n  type Middleware,\n  type FloatingTreeStore,\n} from '../floating-ui-react';\nimport { useDirection } from '../direction-provider/DirectionContext';\nimport { arrow } from '../floating-ui-react/middleware/arrow';\nimport { hide } from './hideMiddleware';\nimport { DEFAULT_SIDES } from './adaptiveOriginMiddleware';\n\nfunction getLogicalSide(sideParam: Side, renderedSide: PhysicalSide, isRtl: boolean): Side {\n  const isLogicalSideParam = sideParam === 'inline-start' || sideParam === 'inline-end';\n  const logicalRight = isRtl ? 'inline-start' : 'inline-end';\n  const logicalLeft = isRtl ? 'inline-end' : 'inline-start';\n  return (\n    {\n      top: 'top',\n      right: isLogicalSideParam ? logicalRight : 'right',\n      bottom: 'bottom',\n      left: isLogicalSideParam ? logicalLeft : 'left',\n    } satisfies Record<PhysicalSide, Side>\n  )[renderedSide];\n}\n\nfunction getOffsetData(state: MiddlewareState, sideParam: Side, isRtl: boolean) {\n  const { rects, placement } = state;\n  const data = {\n    side: getLogicalSide(sideParam, getSide(placement), isRtl),\n    align: getAlignment(placement) || 'center',\n    anchor: { width: rects.reference.width, height: rects.reference.height },\n    positioner: { width: rects.floating.width, height: rects.floating.height },\n  } as const;\n  return data;\n}\n\nexport type Side = 'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start';\nexport type Align = 'start' | 'center' | 'end';\nexport type Boundary = 'clipping-ancestors' | Element | Element[] | Rect;\nexport type OffsetFunction = (data: {\n  side: Side;\n  align: Align;\n  anchor: { width: number; height: number };\n  positioner: { width: number; height: number };\n}) => number;\n\ninterface SideFlipMode {\n  /**\n   * How to avoid collisions on the side axis.\n   * - `'flip'`: If there is not enough space, place the popup on the opposite side.\n   * - `'none'`: Keep the preferred side even if it overflows.\n   */\n  side?: 'flip' | 'none' | undefined;\n  /**\n   * How to avoid collisions on the align axis.\n   * - `'flip'`: If there is not enough space, swap `'start'` and `'end'` alignment.\n   * - `'shift'`: Keep the alignment and shift the popup to fit within the boundary.\n   * - `'none'`: Keep the preferred alignment even if it overflows.\n   */\n  align?: 'flip' | 'shift' | 'none' | undefined;\n  /**\n   * If both sides on the preferred axis do not fit, determines whether to fallback\n   * to a side on the perpendicular axis and which logical side to prefer.\n   * - `'start'`: Prefer the logical start side on the perpendicular axis.\n   * - `'end'`: Prefer the logical end side on the perpendicular axis.\n   * - `'none'`: Do not fallback to the perpendicular axis.\n   */\n  fallbackAxisSide?: 'start' | 'end' | 'none' | undefined;\n}\n\ninterface SideShiftMode {\n  /**\n   * How to avoid collisions on the side axis.\n   * - `'shift'`: Keep the preferred side and shift the popup to fit within the boundary.\n   * - `'none'`: Keep the preferred side even if it overflows.\n   */\n  side?: 'shift' | 'none' | undefined;\n  /**\n   * How to avoid collisions on the align axis.\n   * - `'shift'`: Keep the alignment and shift the popup to fit within the boundary.\n   * - `'none'`: Keep the preferred alignment even if it overflows.\n   */\n  align?: 'shift' | 'none' | undefined;\n  /**\n   * If both sides on the preferred axis do not fit, determines whether to fallback\n   * to a side on the perpendicular axis and which logical side to prefer.\n   * - `'start'`: Prefer the logical start side on the perpendicular axis.\n   * - `'end'`: Prefer the logical end side on the perpendicular axis.\n   * - `'none'`: Do not fallback to the perpendicular axis.\n   */\n  fallbackAxisSide?: 'start' | 'end' | 'none' | undefined;\n}\n\nexport type CollisionAvoidance = SideFlipMode | SideShiftMode;\n\n/**\n * Provides standardized anchor positioning behavior for floating elements. Wraps Floating UI's\n * `useFloating` hook.\n */\nexport function useAnchorPositioning(\n  params: UseAnchorPositioningParameters,\n): UseAnchorPositioningReturnValue {\n  const {\n    // Public parameters\n    anchor,\n    positionMethod = 'absolute',\n    side: sideParam = 'bottom',\n    sideOffset = 0,\n    align = 'center',\n    alignOffset = 0,\n    collisionBoundary,\n    collisionPadding: collisionPaddingParam = 5,\n    sticky = false,\n    arrowPadding = 5,\n    disableAnchorTracking = false,\n    // Private parameters\n    keepMounted = false,\n    floatingRootContext,\n    mounted,\n    collisionAvoidance,\n    shiftCrossAxis = false,\n    nodeId,\n    adaptiveOrigin,\n    lazyFlip = false,\n    externalTree,\n  } = params;\n\n  const [mountSide, setMountSide] = React.useState<PhysicalSide | null>(null);\n\n  if (!mounted && mountSide !== null) {\n    setMountSide(null);\n  }\n\n  const collisionAvoidanceSide = collisionAvoidance.side || 'flip';\n  const collisionAvoidanceAlign = collisionAvoidance.align || 'flip';\n  const collisionAvoidanceFallbackAxisSide = collisionAvoidance.fallbackAxisSide || 'end';\n\n  const anchorFn = typeof anchor === 'function' ? anchor : undefined;\n  const anchorFnCallback = useStableCallback(anchorFn);\n  const anchorDep = anchorFn ? anchorFnCallback : anchor;\n  const anchorValueRef = useValueAsRef(anchor);\n  const mountedRef = useValueAsRef(mounted);\n\n  const direction = useDirection();\n  const isRtl = direction === 'rtl';\n\n  const side =\n    mountSide ||\n    (\n      {\n        top: 'top',\n        right: 'right',\n        bottom: 'bottom',\n        left: 'left',\n        'inline-end': isRtl ? 'left' : 'right',\n        'inline-start': isRtl ? 'right' : 'left',\n      } satisfies Record<Side, PhysicalSide>\n    )[sideParam];\n\n  const placement = align === 'center' ? side : (`${side}-${align}` as Placement);\n\n  let collisionPadding = collisionPaddingParam as {\n    top: number;\n    right: number;\n    bottom: number;\n    left: number;\n  };\n\n  // Create a bias to the preferred side.\n  // On iOS, when the mobile software keyboard opens, the input is exactly centered\n  // in the viewport, but this can cause it to flip to the top undesirably.\n  const bias = 1;\n  const biasTop = sideParam === 'bottom' ? bias : 0;\n  const biasBottom = sideParam === 'top' ? bias : 0;\n  const biasLeft = sideParam === 'right' ? bias : 0;\n  const biasRight = sideParam === 'left' ? bias : 0;\n\n  if (typeof collisionPadding === 'number') {\n    collisionPadding = {\n      top: collisionPadding + biasTop,\n      right: collisionPadding + biasRight,\n      bottom: collisionPadding + biasBottom,\n      left: collisionPadding + biasLeft,\n    };\n  } else if (collisionPadding) {\n    collisionPadding = {\n      top: (collisionPadding.top || 0) + biasTop,\n      right: (collisionPadding.right || 0) + biasRight,\n      bottom: (collisionPadding.bottom || 0) + biasBottom,\n      left: (collisionPadding.left || 0) + biasLeft,\n    };\n  }\n\n  const commonCollisionProps = {\n    boundary: collisionBoundary === 'clipping-ancestors' ? 'clippingAncestors' : collisionBoundary,\n    padding: collisionPadding,\n  } as const;\n\n  // Using a ref assumes that the arrow element is always present in the DOM for the lifetime of the\n  // popup. If this assumption ends up being false, we can switch to state to manage the arrow's\n  // presence.\n  const arrowRef = React.useRef<Element | null>(null);\n\n  // Keep these reactive if they're not functions\n  const sideOffsetRef = useValueAsRef(sideOffset);\n  const alignOffsetRef = useValueAsRef(alignOffset);\n  const sideOffsetDep = typeof sideOffset !== 'function' ? sideOffset : 0;\n  const alignOffsetDep = typeof alignOffset !== 'function' ? alignOffset : 0;\n\n  const middleware: UseFloatingOptions['middleware'] = [\n    offset(\n      (state) => {\n        const data = getOffsetData(state, sideParam, isRtl);\n\n        const sideAxis =\n          typeof sideOffsetRef.current === 'function'\n            ? sideOffsetRef.current(data)\n            : sideOffsetRef.current;\n        const alignAxis =\n          typeof alignOffsetRef.current === 'function'\n            ? alignOffsetRef.current(data)\n            : alignOffsetRef.current;\n\n        return {\n          mainAxis: sideAxis,\n          crossAxis: alignAxis,\n          alignmentAxis: alignAxis,\n        };\n      },\n      [sideOffsetDep, alignOffsetDep, isRtl, sideParam],\n    ),\n  ];\n\n  const shiftDisabled = collisionAvoidanceAlign === 'none' && collisionAvoidanceSide !== 'shift';\n  const crossAxisShiftEnabled =\n    !shiftDisabled && (sticky || shiftCrossAxis || collisionAvoidanceSide === 'shift');\n\n  const flipMiddleware =\n    collisionAvoidanceSide === 'none'\n      ? null\n      : flip({\n          ...commonCollisionProps,\n          // Ensure the popup flips if it's been limited by its --available-height and it resizes.\n          // Since the size() padding is smaller than the flip() padding, flip() will take precedence.\n          padding: {\n            top: collisionPadding.top + bias,\n            right: collisionPadding.right + bias,\n            bottom: collisionPadding.bottom + bias,\n            left: collisionPadding.left + bias,\n          },\n          mainAxis: !shiftCrossAxis && collisionAvoidanceSide === 'flip',\n          crossAxis: collisionAvoidanceAlign === 'flip' ? 'alignment' : false,\n          fallbackAxisSideDirection: collisionAvoidanceFallbackAxisSide,\n        });\n  const shiftMiddleware = shiftDisabled\n    ? null\n    : shift(\n        (data) => {\n          const html = ownerDocument(data.elements.floating).documentElement;\n          return {\n            ...commonCollisionProps,\n            // Use the Layout Viewport to avoid shifting around when pinch-zooming\n            // for context menus.\n            rootBoundary: shiftCrossAxis\n              ? { x: 0, y: 0, width: html.clientWidth, height: html.clientHeight }\n              : undefined,\n            mainAxis: collisionAvoidanceAlign !== 'none',\n            crossAxis: crossAxisShiftEnabled,\n            limiter:\n              sticky || shiftCrossAxis\n                ? undefined\n                : limitShift((limitData) => {\n                    if (!arrowRef.current) {\n                      return {};\n                    }\n                    const { width, height } = arrowRef.current.getBoundingClientRect();\n                    const sideAxis = getSideAxis(getSide(limitData.placement));\n                    const arrowSize = sideAxis === 'y' ? width : height;\n                    const offsetAmount =\n                      sideAxis === 'y'\n                        ? collisionPadding.left + collisionPadding.right\n                        : collisionPadding.top + collisionPadding.bottom;\n                    return {\n                      offset: arrowSize / 2 + offsetAmount / 2,\n                    };\n                  }),\n          };\n        },\n        [commonCollisionProps, sticky, shiftCrossAxis, collisionPadding, collisionAvoidanceAlign],\n      );\n\n  // https://floating-ui.com/docs/flip#combining-with-shift\n  if (\n    collisionAvoidanceSide === 'shift' ||\n    collisionAvoidanceAlign === 'shift' ||\n    align === 'center'\n  ) {\n    middleware.push(shiftMiddleware, flipMiddleware);\n  } else {\n    middleware.push(flipMiddleware, shiftMiddleware);\n  }\n\n  middleware.push(\n    size({\n      ...commonCollisionProps,\n      apply({ elements: { floating }, availableWidth, availableHeight, rects }) {\n        if (!mountedRef.current) {\n          return;\n        }\n        const floatingStyle = floating.style;\n        floatingStyle.setProperty('--available-width', `${availableWidth}px`);\n        floatingStyle.setProperty('--available-height', `${availableHeight}px`);\n\n        // Snap anchor dimensions to device pixels to ensure the popup's visual width matches the anchor's one.\n        const dpr = window.devicePixelRatio || 1;\n        const { x, y, width, height } = rects.reference;\n        const anchorWidth = (Math.round((x + width) * dpr) - Math.round(x * dpr)) / dpr;\n        const anchorHeight = (Math.round((y + height) * dpr) - Math.round(y * dpr)) / dpr;\n\n        floatingStyle.setProperty('--anchor-width', `${anchorWidth}px`);\n        floatingStyle.setProperty('--anchor-height', `${anchorHeight}px`);\n      },\n    }),\n    arrow(\n      () => ({\n        // `transform-origin` calculations rely on an element existing. If the arrow hasn't been set,\n        // we'll create a fake element.\n        element: arrowRef.current || document.createElement('div'),\n        padding: arrowPadding,\n        offsetParent: 'floating',\n      }),\n      [arrowPadding],\n    ),\n    {\n      name: 'transformOrigin',\n      fn(state) {\n        const { elements, middlewareData, placement: renderedPlacement, rects, y } = state;\n\n        const currentRenderedSide = getSide(renderedPlacement);\n        const currentRenderedAxis = getSideAxis(currentRenderedSide);\n        const arrowEl = arrowRef.current;\n        const arrowX = middlewareData.arrow?.x || 0;\n        const arrowY = middlewareData.arrow?.y || 0;\n        const arrowWidth = arrowEl?.clientWidth || 0;\n        const arrowHeight = arrowEl?.clientHeight || 0;\n        const transformX = arrowX + arrowWidth / 2;\n        const transformY = arrowY + arrowHeight / 2;\n        const shiftY = Math.abs(middlewareData.shift?.y || 0);\n        const halfAnchorHeight = rects.reference.height / 2;\n        const sideOffsetValue =\n          typeof sideOffset === 'function'\n            ? sideOffset(getOffsetData(state, sideParam, isRtl))\n            : sideOffset;\n        const isOverlappingAnchor = shiftY > sideOffsetValue;\n\n        const adjacentTransformOrigin = {\n          top: `${transformX}px calc(100% + ${sideOffsetValue}px)`,\n          bottom: `${transformX}px ${-sideOffsetValue}px`,\n          left: `calc(100% + ${sideOffsetValue}px) ${transformY}px`,\n          right: `${-sideOffsetValue}px ${transformY}px`,\n        }[currentRenderedSide];\n        const overlapTransformOrigin = `${transformX}px ${rects.reference.y + halfAnchorHeight - y}px`;\n\n        elements.floating.style.setProperty(\n          '--transform-origin',\n          crossAxisShiftEnabled && currentRenderedAxis === 'y' && isOverlappingAnchor\n            ? overlapTransformOrigin\n            : adjacentTransformOrigin,\n        );\n\n        return {};\n      },\n    },\n    hide,\n    adaptiveOrigin,\n  );\n\n  useIsoLayoutEffect(() => {\n    // Ensure positioning doesn't run initially for `keepMounted` elements that\n    // aren't initially open.\n    if (!mounted && floatingRootContext) {\n      floatingRootContext.update({\n        referenceElement: null,\n        floatingElement: null,\n        domReferenceElement: null,\n      });\n    }\n  }, [mounted, floatingRootContext]);\n\n  const autoUpdateOptions: AutoUpdateOptions = React.useMemo(\n    () => ({\n      elementResize: !disableAnchorTracking && typeof ResizeObserver !== 'undefined',\n      layoutShift: !disableAnchorTracking && typeof IntersectionObserver !== 'undefined',\n    }),\n    [disableAnchorTracking],\n  );\n\n  const {\n    refs,\n    elements,\n    x,\n    y,\n    middlewareData,\n    update,\n    placement: renderedPlacement,\n    context,\n    isPositioned,\n    floatingStyles: originalFloatingStyles,\n  } = useFloating({\n    rootContext: floatingRootContext,\n    open: keepMounted ? mounted : undefined,\n    placement,\n    middleware,\n    strategy: positionMethod,\n    whileElementsMounted: keepMounted\n      ? undefined\n      : (...args) => autoUpdate(...args, autoUpdateOptions),\n    nodeId,\n    externalTree,\n  });\n\n  const { sideX, sideY } = middlewareData.adaptiveOrigin || DEFAULT_SIDES;\n\n  // Default to `fixed` when not positioned to prevent `autoFocus` scroll jumps.\n  // This ensures the popup is inside the viewport initially before it gets positioned.\n  const resolvedPosition: 'absolute' | 'fixed' = isPositioned ? positionMethod : 'fixed';\n\n  const floatingStyles = React.useMemo<React.CSSProperties>(() => {\n    const base = adaptiveOrigin\n      ? { position: resolvedPosition, [sideX]: x, [sideY]: y }\n      : { position: resolvedPosition, ...originalFloatingStyles };\n    if (!isPositioned) {\n      base.opacity = 0;\n    }\n    return base;\n  }, [adaptiveOrigin, resolvedPosition, sideX, x, sideY, y, originalFloatingStyles, isPositioned]);\n\n  const registeredPositionReferenceRef = React.useRef<Element | VirtualElement | null>(null);\n\n  useIsoLayoutEffect(() => {\n    if (!mounted) {\n      return;\n    }\n\n    const anchorValue = anchorValueRef.current;\n    const resolvedAnchor = typeof anchorValue === 'function' ? anchorValue() : anchorValue;\n    const unwrappedElement =\n      (isRef(resolvedAnchor) ? resolvedAnchor.current : resolvedAnchor) || null;\n    const finalAnchor = unwrappedElement || null;\n\n    if (finalAnchor !== registeredPositionReferenceRef.current) {\n      refs.setPositionReference(finalAnchor);\n      registeredPositionReferenceRef.current = finalAnchor;\n    }\n  }, [mounted, refs, anchorDep, anchorValueRef]);\n\n  React.useEffect(() => {\n    if (!mounted) {\n      return;\n    }\n\n    const anchorValue = anchorValueRef.current;\n\n    // Refs from parent components are set after useLayoutEffect runs and are available in useEffect.\n    // Therefore, if the anchor is a ref, we need to update the position reference in useEffect.\n    if (typeof anchorValue === 'function') {\n      return;\n    }\n\n    if (isRef(anchorValue) && anchorValue.current !== registeredPositionReferenceRef.current) {\n      refs.setPositionReference(anchorValue.current);\n      registeredPositionReferenceRef.current = anchorValue.current;\n    }\n  }, [mounted, refs, anchorDep, anchorValueRef]);\n\n  React.useEffect(() => {\n    if (keepMounted && mounted && elements.domReference && elements.floating) {\n      return autoUpdate(elements.domReference, elements.floating, update, autoUpdateOptions);\n    }\n    return undefined;\n  }, [keepMounted, mounted, elements, update, autoUpdateOptions]);\n\n  const renderedSide = getSide(renderedPlacement);\n  const logicalRenderedSide = getLogicalSide(sideParam, renderedSide, isRtl);\n  const renderedAlign = getAlignment(renderedPlacement) || 'center';\n  const anchorHidden = Boolean(middlewareData.hide?.referenceHidden);\n\n  /**\n   * Locks the flip (makes it \"sticky\") so it doesn't prefer a given placement\n   * and flips back lazily, not eagerly. Ideal for filtered lists that change\n   * the size of the popup dynamically to avoid unwanted flipping when typing.\n   */\n  useIsoLayoutEffect(() => {\n    if (lazyFlip && mounted && isPositioned) {\n      setMountSide(renderedSide);\n    }\n  }, [lazyFlip, mounted, isPositioned, renderedSide]);\n\n  const arrowStyles = React.useMemo(\n    () => ({\n      position: 'absolute' as const,\n      top: middlewareData.arrow?.y,\n      left: middlewareData.arrow?.x,\n    }),\n    [middlewareData.arrow],\n  );\n\n  const arrowUncentered = middlewareData.arrow?.centerOffset !== 0;\n\n  return React.useMemo(\n    () => ({\n      positionerStyles: floatingStyles,\n      arrowStyles,\n      arrowRef,\n      arrowUncentered,\n      side: logicalRenderedSide,\n      align: renderedAlign,\n      physicalSide: renderedSide,\n      anchorHidden,\n      refs,\n      context,\n      isPositioned,\n      update,\n    }),\n    [\n      floatingStyles,\n      arrowStyles,\n      arrowRef,\n      arrowUncentered,\n      logicalRenderedSide,\n      renderedAlign,\n      renderedSide,\n      anchorHidden,\n      refs,\n      context,\n      isPositioned,\n      update,\n    ],\n  );\n}\n\nfunction isRef(\n  param: Element | VirtualElement | React.RefObject<any> | null | undefined,\n): param is React.RefObject<any> {\n  return param != null && 'current' in param;\n}\n\nexport interface UseAnchorPositioningSharedParameters {\n  /**\n   * An element to position the popup against.\n   * By default, the popup will be positioned against the trigger.\n   */\n  anchor?:\n    | Element\n    | null\n    | VirtualElement\n    | React.RefObject<Element | null>\n    | (() => Element | VirtualElement | null)\n    | undefined;\n  /**\n   * Determines which CSS `position` property to use.\n   * @default 'absolute'\n   */\n  positionMethod?: 'absolute' | 'fixed' | undefined;\n  /**\n   * Which side of the anchor element to align the popup against.\n   * May automatically change to avoid collisions.\n   * @default 'bottom'\n   */\n  side?: Side | undefined;\n  /**\n   * Distance between the anchor and the popup in pixels.\n   * Also accepts a function that returns the distance to read the dimensions of the anchor\n   * and positioner elements, along with its side and alignment.\n   *\n   * The function takes a `data` object parameter with the following properties:\n   * - `data.anchor`: the dimensions of the anchor element with properties `width` and `height`.\n   * - `data.positioner`: the dimensions of the positioner element with properties `width` and `height`.\n   * - `data.side`: which side of the anchor element the positioner is aligned against.\n   * - `data.align`: how the positioner is aligned relative to the specified side.\n   *\n   * @example\n   * ```jsx\n   * <Positioner\n   *   sideOffset={({ side, align, anchor, positioner }) => {\n   *     return side === 'top' || side === 'bottom'\n   *       ? anchor.height\n   *       : anchor.width;\n   *   }}\n   * />\n   * ```\n   *\n   * @default 0\n   */\n  sideOffset?: number | OffsetFunction | undefined;\n  /**\n   * How to align the popup relative to the specified side.\n   * @default 'center'\n   */\n  align?: Align | undefined;\n  /**\n   * Additional offset along the alignment axis in pixels.\n   * Also accepts a function that returns the offset to read the dimensions of the anchor\n   * and positioner elements, along with its side and alignment.\n   *\n   * The function takes a `data` object parameter with the following properties:\n   * - `data.anchor`: the dimensions of the anchor element with properties `width` and `height`.\n   * - `data.positioner`: the dimensions of the positioner element with properties `width` and `height`.\n   * - `data.side`: which side of the anchor element the positioner is aligned against.\n   * - `data.align`: how the positioner is aligned relative to the specified side.\n   *\n   * @example\n   * ```jsx\n   * <Positioner\n   *   alignOffset={({ side, align, anchor, positioner }) => {\n   *     return side === 'top' || side === 'bottom'\n   *       ? anchor.width\n   *       : anchor.height;\n   *   }}\n   * />\n   * ```\n   *\n   * @default 0\n   */\n  alignOffset?: number | OffsetFunction | undefined;\n  /**\n   * An element or a rectangle that delimits the area that the popup is confined to.\n   * @default 'clipping-ancestors'\n   */\n  collisionBoundary?: Boundary | undefined;\n  /**\n   * Additional space to maintain from the edge of the collision boundary.\n   * @default 5\n   */\n  collisionPadding?: Padding | undefined;\n  /**\n   * Whether to maintain the popup in the viewport after\n   * the anchor element was scrolled out of view.\n   * @default false\n   */\n  sticky?: boolean | undefined;\n  /**\n   * Minimum distance to maintain between the arrow and the edges of the popup.\n   *\n   * Use it to prevent the arrow element from hanging out of the rounded corners of a popup.\n   * @default 5\n   */\n  arrowPadding?: number | undefined;\n  /**\n   * Whether to disable the popup from tracking any layout shift of its positioning anchor.\n   * @default false\n   */\n  disableAnchorTracking?: boolean | undefined;\n  /**\n   * Determines how to handle collisions when positioning the popup.\n   *\n   * `side` controls overflow on the preferred placement axis (`top`/`bottom` or `left`/`right`):\n   * - `'flip'`: keep the requested side when it fits; otherwise try the opposite side\n   *   (`top` and `bottom`, or `left` and `right`).\n   * - `'shift'`: never change side; keep the requested side and move the popup within\n   *   the clipping boundary so it stays visible.\n   * - `'none'`: do not correct side-axis overflow.\n   *\n   * `align` controls overflow on the alignment axis (`start`/`center`/`end`):\n   * - `'flip'`: keep side, but swap `start` and `end` when the requested alignment overflows.\n   * - `'shift'`: keep side and requested alignment, then nudge the popup along the\n   *   alignment axis to fit.\n   * - `'none'`: do not correct alignment-axis overflow.\n   *\n   * `fallbackAxisSide` controls fallback behavior on the perpendicular axis when the\n   * preferred axis cannot fit:\n   * - `'start'`: allow perpendicular fallback and try the logical start side first\n   *   (`top` before `bottom`, or `left` before `right` in LTR).\n   * - `'end'`: allow perpendicular fallback and try the logical end side first\n   *   (`bottom` before `top`, or `right` before `left` in LTR).\n   * - `'none'`: do not fallback to the perpendicular axis.\n   *\n   * When `side` is `'shift'`, explicitly setting `align` only supports `'shift'` or `'none'`.\n   * If `align` is omitted, it defaults to `'flip'`.\n   *\n   * @example\n   * ```jsx\n   * <Positioner\n   *   collisionAvoidance={{\n   *     side: 'shift',\n   *     align: 'shift',\n   *     fallbackAxisSide: 'none',\n   *   }}\n   * />\n   * ```\n   *\n   */\n  collisionAvoidance?: CollisionAvoidance | undefined;\n}\n\nexport interface UseAnchorPositioningParameters extends UseAnchorPositioningSharedParameters {\n  keepMounted?: boolean | undefined;\n  trackCursorAxis?: 'none' | 'x' | 'y' | 'both' | undefined;\n  floatingRootContext?: FloatingRootContext | undefined;\n  mounted: boolean;\n  disableAnchorTracking: boolean;\n  nodeId?: string | undefined;\n  adaptiveOrigin?: Middleware | undefined;\n  collisionAvoidance: CollisionAvoidance;\n  shiftCrossAxis?: boolean | undefined;\n  lazyFlip?: boolean | undefined;\n  externalTree?: FloatingTreeStore | undefined;\n}\n\nexport interface UseAnchorPositioningReturnValue {\n  positionerStyles: React.CSSProperties;\n  arrowStyles: React.CSSProperties;\n  arrowRef: React.RefObject<Element | null>;\n  arrowUncentered: boolean;\n  side: Side;\n  align: Align;\n  physicalSide: PhysicalSide;\n  anchorHidden: boolean;\n  refs: ReturnType<typeof useFloating>['refs'];\n  context: FloatingContext;\n  isPositioned: boolean;\n  update: () => void;\n}\n\nexport interface UseAnchorPositioningState {}\n"
  },
  {
    "path": "packages/react/src/utils/useAnimationsFinished.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport { useAnimationFrame } from '@base-ui/utils/useAnimationFrame';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { resolveRef } from './resolveRef';\nimport { TransitionStatusDataAttributes } from './stateAttributesMapping';\n\n/**\n * Executes a function once all animations have finished on the provided element.\n * @param elementOrRef - The element to watch for animations.\n * @param waitForStartingStyleRemoved - Whether to wait for [data-starting-style] to be removed before checking for animations.\n * @param treatAbortedAsFinished - Whether to treat aborted animations as finished. If `false`, and there are aborted animations,\n *   the function will check again if any new animations have started and wait for them to finish.\n * @returns A function that takes a callback to execute once all animations have finished, and an optional AbortSignal to abort the callback\n */\nexport function useAnimationsFinished(\n  elementOrRef: React.RefObject<HTMLElement | null> | HTMLElement | null,\n  waitForStartingStyleRemoved = false,\n  treatAbortedAsFinished = true,\n) {\n  const frame = useAnimationFrame();\n\n  return useStableCallback(\n    (\n      /**\n       * A function to execute once all animations have finished.\n       */\n      fnToExecute: () => void,\n      /**\n       * An optional [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) that\n       * can be used to abort `fnToExecute` before all the animations have finished.\n       * @default null\n       */\n      signal: AbortSignal | null = null,\n    ) => {\n      frame.cancel();\n\n      function done() {\n        // Synchronously flush the unmounting of the component so that the browser doesn't\n        // paint: https://github.com/mui/base-ui/issues/979\n        ReactDOM.flushSync(fnToExecute);\n      }\n\n      const element = resolveRef(elementOrRef);\n      if (element == null) {\n        return;\n      }\n      const resolvedElement = element;\n\n      if (\n        typeof resolvedElement.getAnimations !== 'function' ||\n        globalThis.BASE_UI_ANIMATIONS_DISABLED\n      ) {\n        fnToExecute();\n      } else {\n        function execWaitForStartingStyleRemoved() {\n          const startingStyleAttribute = TransitionStatusDataAttributes.startingStyle;\n\n          // If `[data-starting-style]` isn't present, fall back to waiting one more frame\n          // to give \"open\" animations a chance to be registered.\n          if (!resolvedElement.hasAttribute(startingStyleAttribute)) {\n            frame.request(exec);\n            return;\n          }\n\n          // Wait for `[data-starting-style]` to have been removed.\n          const attributeObserver = new MutationObserver(() => {\n            if (!resolvedElement.hasAttribute(startingStyleAttribute)) {\n              attributeObserver.disconnect();\n              exec();\n            }\n          });\n\n          attributeObserver.observe(resolvedElement, {\n            attributes: true,\n            attributeFilter: [startingStyleAttribute],\n          });\n\n          signal?.addEventListener('abort', () => attributeObserver.disconnect(), { once: true });\n        }\n\n        function exec() {\n          Promise.all(resolvedElement.getAnimations().map((anim) => anim.finished))\n            .then(() => {\n              if (signal?.aborted) {\n                return;\n              }\n\n              done();\n            })\n            .catch(() => {\n              const currentAnimations = resolvedElement.getAnimations();\n\n              if (treatAbortedAsFinished) {\n                if (signal?.aborted) {\n                  return;\n                }\n\n                done();\n              } else if (\n                !signal?.aborted &&\n                currentAnimations.length > 0 &&\n                currentAnimations.some((anim) => anim.pending || anim.playState !== 'finished')\n              ) {\n                // Sometimes animations can be aborted because a property they depend on changes while the animation plays.\n                // In such cases, we need to re-check if any new animations have started.\n                exec();\n              }\n            });\n        }\n\n        if (waitForStartingStyleRemoved) {\n          execWaitForStartingStyleRemoved();\n          return;\n        }\n\n        frame.request(exec);\n      }\n    },\n  );\n}\n"
  },
  {
    "path": "packages/react/src/utils/useBaseUiId.ts",
    "content": "'use client';\nimport { useId } from '@base-ui/utils/useId';\n\n/**\n * Wraps `useId` and prefixes generated `id`s with `base-ui-`\n * @param {string | undefined} idOverride overrides the generated id when provided\n * @returns {string | undefined}\n */\nexport function useBaseUiId(idOverride?: string): string | undefined {\n  return useId(idOverride, 'base-ui');\n}\n"
  },
  {
    "path": "packages/react/src/utils/useFocusableWhenDisabled.ts",
    "content": "'use client';\nimport * as React from 'react';\n\nexport function useFocusableWhenDisabled(\n  parameters: UseFocusableWhenDisabledParameters,\n): UseFocusableWhenDisabledReturnValue {\n  const {\n    focusableWhenDisabled,\n    disabled,\n    composite = false,\n    tabIndex: tabIndexProp = 0,\n    isNativeButton,\n  } = parameters;\n\n  const isFocusableComposite = composite && focusableWhenDisabled !== false;\n  const isNonFocusableComposite = composite && focusableWhenDisabled === false;\n\n  // we can't explicitly assign `undefined` to any of these props because it\n  // would otherwise prevent subsequently merged props from setting them\n  const props = React.useMemo(() => {\n    const additionalProps = {\n      // allow Tabbing away from focusableWhenDisabled elements\n      onKeyDown(event: React.KeyboardEvent) {\n        if (disabled && focusableWhenDisabled && event.key !== 'Tab') {\n          event.preventDefault();\n        }\n      },\n    } as FocusableWhenDisabledProps;\n\n    if (!composite) {\n      additionalProps.tabIndex = tabIndexProp;\n\n      if (!isNativeButton && disabled) {\n        additionalProps.tabIndex = focusableWhenDisabled ? tabIndexProp : -1;\n      }\n    }\n\n    if (\n      (isNativeButton && (focusableWhenDisabled || isFocusableComposite)) ||\n      (!isNativeButton && disabled)\n    ) {\n      additionalProps['aria-disabled'] = disabled;\n    }\n\n    if (isNativeButton && (!focusableWhenDisabled || isNonFocusableComposite)) {\n      additionalProps.disabled = disabled;\n    }\n\n    return additionalProps;\n  }, [\n    composite,\n    disabled,\n    focusableWhenDisabled,\n    isFocusableComposite,\n    isNonFocusableComposite,\n    isNativeButton,\n    tabIndexProp,\n  ]);\n\n  return { props };\n}\n\ninterface FocusableWhenDisabledProps {\n  'aria-disabled'?: boolean | undefined;\n  disabled?: boolean | undefined;\n  onKeyDown: (event: React.KeyboardEvent) => void;\n  tabIndex: number;\n}\n\nexport interface UseFocusableWhenDisabledParameters {\n  /**\n   * Whether the component should be focusable when disabled.\n   * When `undefined`, composite items are focusable when disabled by default.\n   */\n  focusableWhenDisabled?: boolean | undefined;\n  /**\n   * The disabled state of the component.\n   */\n  disabled: boolean;\n  /**\n   * Whether this is a composite item or not.\n   * @default false\n   */\n  composite?: boolean | undefined;\n  /**\n   * @default 0\n   */\n  tabIndex?: number | undefined;\n  /**\n   * @default true\n   */\n  isNativeButton: boolean;\n}\n\nexport interface UseFocusableWhenDisabledReturnValue {\n  props: FocusableWhenDisabledProps;\n}\n\nexport interface UseFocusableWhenDisabledState {}\n"
  },
  {
    "path": "packages/react/src/utils/useMixedToggleClickHandler.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { ownerDocument } from '@base-ui/utils/owner';\nimport { BaseUIEvent } from './types';\nimport { EMPTY_OBJECT } from './constants';\n\n/**\n * Returns `click` and `mousedown` handlers that fix the behavior of triggers of popups that are toggled by different events.\n * For example, a button that opens a popup on mousedown and closes it on click.\n * This hook prevents the popup from closing immediately after the mouse button is released.\n */\nexport function useMixedToggleClickHandler(params: UseMixedToggleClickHandlerParameters) {\n  const { enabled = true, mouseDownAction, open } = params;\n  const ignoreClickRef = React.useRef(false);\n\n  return React.useMemo(() => {\n    if (!enabled) {\n      return EMPTY_OBJECT;\n    }\n\n    return {\n      onMouseDown: (event: React.MouseEvent) => {\n        if ((mouseDownAction === 'open' && !open) || (mouseDownAction === 'close' && open)) {\n          ignoreClickRef.current = true;\n\n          ownerDocument(event.currentTarget as Element).addEventListener(\n            'click',\n            () => {\n              ignoreClickRef.current = false;\n            },\n            { once: true },\n          );\n        }\n      },\n      onClick: (event: BaseUIEvent<React.MouseEvent>) => {\n        if (ignoreClickRef.current) {\n          ignoreClickRef.current = false;\n          event.preventBaseUIHandler();\n        }\n      },\n    };\n  }, [enabled, mouseDownAction, open]);\n}\n\nexport interface UseMixedToggleClickHandlerParameters {\n  /**\n   * Whether the mixed toggle click handler is enabled.\n   * @default true\n   */\n  enabled?: boolean | undefined;\n  /**\n   * Determines what action is performed on mousedown.\n   */\n  mouseDownAction: 'open' | 'close';\n  /**\n   * The current open state of the popup.\n   */\n  open: boolean;\n}\n\nexport interface UseMixedToggleClickHandlerState {}\n"
  },
  {
    "path": "packages/react/src/utils/useOpenChangeComplete.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useAnimationsFinished } from './useAnimationsFinished';\n\n/**\n * Calls the provided function when the CSS open/close animation or transition completes.\n */\nexport function useOpenChangeComplete(parameters: UseOpenChangeCompleteParameters) {\n  const { enabled = true, open, ref, onComplete: onCompleteParam } = parameters;\n\n  const onComplete = useStableCallback(onCompleteParam);\n  const runOnceAnimationsFinish = useAnimationsFinished(ref, open, false);\n\n  React.useEffect(() => {\n    if (!enabled) {\n      return undefined;\n    }\n\n    const abortController = new AbortController();\n\n    runOnceAnimationsFinish(onComplete, abortController.signal);\n\n    return () => {\n      abortController.abort();\n    };\n  }, [enabled, open, onComplete, runOnceAnimationsFinish]);\n}\n\nexport interface UseOpenChangeCompleteParameters {\n  /**\n   * Whether the hook is enabled.\n   * @default true\n   */\n  enabled?: boolean | undefined;\n  /**\n   * Whether the element is open.\n   */\n  open?: boolean | undefined;\n  /**\n   * Ref to the element being closed.\n   */\n  ref: React.RefObject<HTMLElement | null>;\n  /**\n   * Function to call when the animation completes (or there is no animation).\n   */\n  onComplete: () => void;\n}\n\nexport interface UseOpenChangeCompleteState {}\n"
  },
  {
    "path": "packages/react/src/utils/useOpenInteractionType.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { InteractionType, useEnhancedClickHandler } from '@base-ui/utils/useEnhancedClickHandler';\nimport { isIOS } from '@base-ui/utils/detectBrowser';\nimport { useValueChanged } from './useValueChanged';\n\n/**\n * Determines the interaction type (keyboard, mouse, touch, etc.) that opened the component.\n *\n * @param open The open state of the component.\n */\nexport function useOpenInteractionType(open: boolean) {\n  const [openMethod, setOpenMethod] = React.useState<InteractionType | null>(null);\n\n  const handleTriggerClick = useStableCallback(\n    (_: React.MouseEvent, interactionType: InteractionType) => {\n      if (!open) {\n        setOpenMethod(\n          interactionType ||\n            // On iOS Safari, the hitslop around touch targets means tapping outside an element's\n            // bounds does not fire `pointerdown` but does fire `mousedown`. The `interactionType`\n            // will be \"\" in that case.\n            (isIOS ? 'touch' : ''),\n        );\n      }\n    },\n  );\n\n  useValueChanged(open, (previousOpen) => {\n    if (previousOpen && !open) {\n      setOpenMethod(null);\n    }\n  });\n\n  const { onClick, onPointerDown } = useEnhancedClickHandler(handleTriggerClick);\n\n  return React.useMemo(\n    () => ({\n      openMethod,\n      triggerProps: {\n        onClick,\n        onPointerDown,\n      },\n    }),\n    [openMethod, onClick, onPointerDown],\n  );\n}\n"
  },
  {
    "path": "packages/react/src/utils/usePopupAutoResize.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { useAnimationFrame } from '@base-ui/utils/useAnimationFrame';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { NOOP } from '@base-ui/utils/empty';\nimport { useAnimationsFinished } from './useAnimationsFinished';\nimport { getCssDimensions } from './getCssDimensions';\nimport { Dimensions } from '../floating-ui-react/types';\nimport { Side } from './useAnchorPositioning';\nimport { EMPTY_OBJECT } from './constants';\n\nconst DEFAULT_ENABLED = () => true;\n\n/**\n * Allows the element to automatically resize based on its content while supporting animations.\n */\nexport function usePopupAutoResize(parameters: UsePopupAutoResizeParameters) {\n  const {\n    popupElement,\n    positionerElement,\n    content,\n    mounted,\n    enabled = DEFAULT_ENABLED,\n    onMeasureLayout: onMeasureLayoutParam,\n    onMeasureLayoutComplete: onMeasureLayoutCompleteParam,\n    side,\n    direction,\n  } = parameters;\n\n  const runOnceAnimationsFinish = useAnimationsFinished(popupElement, true, false);\n\n  const animationFrame = useAnimationFrame();\n\n  const committedDimensionsRef = React.useRef<Dimensions | null>(null);\n  const liveDimensionsRef = React.useRef<Dimensions | null>(null);\n  const isInitialRenderRef = React.useRef(true);\n\n  const restoreAnchoringStylesRef = React.useRef(NOOP);\n\n  const onMeasureLayout = useStableCallback(onMeasureLayoutParam);\n  const onMeasureLayoutComplete = useStableCallback(onMeasureLayoutCompleteParam);\n\n  const anchoringStyles: React.CSSProperties = React.useMemo(() => {\n    // Ensure popup size transitions correctly when anchored to `bottom` (side=top) or `right` (side=left).\n    let isOriginSide = side === 'top';\n    let isPhysicalLeft = side === 'left';\n    if (direction === 'rtl') {\n      isOriginSide = isOriginSide || side === 'inline-end';\n      isPhysicalLeft = isPhysicalLeft || side === 'inline-end';\n    } else {\n      isOriginSide = isOriginSide || side === 'inline-start';\n      isPhysicalLeft = isPhysicalLeft || side === 'inline-start';\n    }\n\n    return isOriginSide\n      ? {\n          position: 'absolute',\n          [side === 'top' ? 'bottom' : 'top']: '0',\n          [isPhysicalLeft ? 'right' : 'left']: '0',\n        }\n      : EMPTY_OBJECT;\n  }, [side, direction]);\n\n  useIsoLayoutEffect(() => {\n    // Reset the state when the popup is closed.\n    if (!mounted || !enabled() || typeof ResizeObserver !== 'function') {\n      restoreAnchoringStylesRef.current = NOOP;\n      isInitialRenderRef.current = true;\n      committedDimensionsRef.current = null;\n      liveDimensionsRef.current = null;\n      return undefined;\n    }\n\n    if (!popupElement || !positionerElement) {\n      return undefined;\n    }\n\n    restoreAnchoringStylesRef.current = applyElementStyles(\n      popupElement,\n      anchoringStyles as Record<string, string>,\n    );\n\n    const observer = new ResizeObserver((entries) => {\n      const entry = entries[0];\n      if (entry) {\n        liveDimensionsRef.current = {\n          width: Math.ceil(entry.borderBoxSize[0].inlineSize),\n          height: Math.ceil(entry.borderBoxSize[0].blockSize),\n        };\n      }\n    });\n\n    observer.observe(popupElement);\n\n    // Measure the rendered size to enable transitions:\n    setPopupCssSize(popupElement, 'auto');\n\n    const restorePopupPosition = overrideElementStyle(popupElement, 'position', 'static');\n    const restorePopupTransform = overrideElementStyle(popupElement, 'transform', 'none');\n    const restorePopupScale = overrideElementStyle(popupElement, 'scale', '1');\n    const restorePositionerAvailableSize = applyElementStyles(positionerElement, {\n      '--available-width': 'max-content',\n      '--available-height': 'max-content',\n    });\n\n    function restoreMeasurementOverrides() {\n      restorePopupPosition();\n      restorePopupTransform();\n      restorePositionerAvailableSize();\n    }\n\n    function restoreMeasurementOverridesIncludingScale() {\n      restoreMeasurementOverrides();\n      restorePopupScale();\n    }\n\n    onMeasureLayout?.();\n\n    // Initial render (for each time the popup opens).\n    if (isInitialRenderRef.current || committedDimensionsRef.current === null) {\n      setPositionerCssSize(positionerElement, 'max-content');\n\n      const dimensions = getCssDimensions(popupElement);\n\n      committedDimensionsRef.current = dimensions;\n\n      setPositionerCssSize(positionerElement, dimensions);\n      restoreMeasurementOverridesIncludingScale();\n      onMeasureLayoutComplete?.(null, dimensions);\n\n      isInitialRenderRef.current = false;\n\n      return () => {\n        observer.disconnect();\n        restoreAnchoringStylesRef.current();\n        restoreAnchoringStylesRef.current = NOOP;\n      };\n    }\n\n    // Subsequent renders while open (when `content` changes).\n    setPopupCssSize(popupElement, 'auto');\n    setPositionerCssSize(positionerElement, 'max-content');\n\n    const previousDimensions = committedDimensionsRef.current ?? liveDimensionsRef.current;\n    const newDimensions = getCssDimensions(popupElement);\n\n    // Commit immediately so future content changes have a stable previous size, even if\n    // ResizeObserver runs after this point.\n    committedDimensionsRef.current = newDimensions;\n\n    if (!previousDimensions) {\n      setPositionerCssSize(positionerElement, newDimensions);\n      restoreMeasurementOverridesIncludingScale();\n      onMeasureLayoutComplete?.(null, newDimensions);\n\n      return () => {\n        observer.disconnect();\n        animationFrame.cancel();\n        restoreAnchoringStylesRef.current();\n        restoreAnchoringStylesRef.current = NOOP;\n      };\n    }\n\n    setPopupCssSize(popupElement, previousDimensions);\n    restoreMeasurementOverridesIncludingScale();\n    onMeasureLayoutComplete?.(previousDimensions, newDimensions);\n\n    setPositionerCssSize(positionerElement, newDimensions);\n\n    const abortController = new AbortController();\n\n    animationFrame.request(() => {\n      setPopupCssSize(popupElement, newDimensions);\n\n      runOnceAnimationsFinish(() => {\n        popupElement.style.setProperty('--popup-width', 'auto');\n        popupElement.style.setProperty('--popup-height', 'auto');\n      }, abortController.signal);\n    });\n\n    return () => {\n      observer.disconnect();\n      abortController.abort();\n      animationFrame.cancel();\n      restoreAnchoringStylesRef.current();\n      restoreAnchoringStylesRef.current = NOOP;\n    };\n  }, [\n    content,\n    popupElement,\n    positionerElement,\n    runOnceAnimationsFinish,\n    animationFrame,\n    enabled,\n    mounted,\n    onMeasureLayout,\n    onMeasureLayoutComplete,\n    anchoringStyles,\n  ]);\n}\n\ninterface UsePopupAutoResizeParameters {\n  /**\n   * Element to resize.\n   */\n  popupElement: HTMLElement | null;\n  /*\n   * Positioner element (parent of the popup)\n   */\n  positionerElement: HTMLElement | null;\n  /**\n   * Whether the popup is mounted.\n   */\n  mounted: boolean;\n  /*\n   * Content that may change and trigger a resize.\n   * This doesn't have to be the actual content of the popup, but a value that triggers a resize.\n   */\n  content: unknown;\n  /**\n   * Whether the auto-resize is enabled. This function runs in an effect and can safely access refs.\n   */\n  enabled?: (() => boolean) | undefined;\n  /**\n   * Callback fired immediately before measuring the dimensions of the new content.\n   */\n  onMeasureLayout?: (() => void) | undefined;\n  /**\n   * Callback fired after the new dimensions have been measured.\n   *\n   * @param previousDimensions Dimensions before the change, or `null` if this is the first measurement.\n   * @param newDimensions Newly measured dimensions.\n   */\n  onMeasureLayoutComplete?:\n    | ((previousDimensions: Dimensions | null, newDimensions: Dimensions) => void)\n    | undefined;\n\n  side: Side;\n  direction: 'ltr' | 'rtl';\n}\n\nfunction overrideElementStyle(element: HTMLElement, property: string, value: string) {\n  const originalValue = element.style.getPropertyValue(property);\n  element.style.setProperty(property, value);\n\n  return () => {\n    element.style.setProperty(property, originalValue);\n  };\n}\n\nfunction applyElementStyles(element: HTMLElement, styles: Record<string, string>) {\n  const restorers: Array<() => void> = [];\n\n  for (const [key, value] of Object.entries(styles)) {\n    restorers.push(overrideElementStyle(element, key, value));\n  }\n\n  return restorers.length\n    ? () => {\n        restorers.forEach((restore) => restore());\n      }\n    : NOOP;\n}\n\nfunction setPopupCssSize(popupElement: HTMLElement, size: Dimensions | 'auto') {\n  const width = size === 'auto' ? 'auto' : `${size.width}px`;\n  const height = size === 'auto' ? 'auto' : `${size.height}px`;\n  popupElement.style.setProperty('--popup-width', width);\n  popupElement.style.setProperty('--popup-height', height);\n}\n\nfunction setPositionerCssSize(positionerElement: HTMLElement, size: Dimensions | 'max-content') {\n  const width = size === 'max-content' ? 'max-content' : `${size.width}px`;\n  const height = size === 'max-content' ? 'max-content' : `${size.height}px`;\n  positionerElement.style.setProperty('--positioner-width', width);\n  positionerElement.style.setProperty('--positioner-height', height);\n}\n"
  },
  {
    "path": "packages/react/src/utils/usePopupViewport.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport { inertValue } from '@base-ui/utils/inertValue';\nimport { useAnimationFrame } from '@base-ui/utils/useAnimationFrame';\nimport { usePreviousValue } from '@base-ui/utils/usePreviousValue';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport type { ReactStore } from '@base-ui/utils/store';\nimport { useAnimationsFinished } from './useAnimationsFinished';\nimport { usePopupAutoResize } from './usePopupAutoResize';\nimport { Dimensions } from '../floating-ui-react/types';\nimport { Side } from './useAnchorPositioning';\nimport { useDirection } from '../direction-provider';\n\nexport type PopupViewportCssVars = {\n  /**\n   * CSS variable name storing the popup width for the previous content snapshot.\n   */\n  popupWidth: string;\n  /**\n   * CSS variable name storing the popup height for the previous content snapshot.\n   */\n  popupHeight: string;\n};\n\nexport interface PopupViewportState {\n  /**\n   * Direction from which the popup was activated, used for directional animations.\n   */\n  activationDirection: string | undefined;\n  /**\n   * Whether the viewport is currently transitioning between contents.\n   */\n  transitioning: boolean;\n}\n\ntype PopupViewportStore = Pick<ReactStore<any, any, any>, 'useState' | 'set'>;\n\nexport interface UsePopupViewportParameters {\n  /**\n   * Popup store instance for accessing shared popup state.\n   */\n  store: PopupViewportStore;\n  /**\n   * Side of the positioner relative to the trigger.\n   */\n  side: Side;\n  /**\n   * CSS variable names used for sizing the previous content snapshot.\n   */\n  cssVars: PopupViewportCssVars;\n  /**\n   * Viewport children to render in the current container.\n   */\n  children?: React.ReactNode;\n}\n\nexport interface UsePopupViewportResult {\n  /**\n   * The viewport children wrapped in current/previous containers as needed.\n   */\n  children: React.ReactNode;\n  /**\n   * Viewport state used for data attributes and render prop styling.\n   */\n  state: PopupViewportState;\n}\n\n/**\n * Builds morphing viewport containers for popups that animate between trigger-based content.\n * Handles previous-content snapshots, auto-resize, and state attributes for transitions.\n */\nexport function usePopupViewport(parameters: UsePopupViewportParameters): UsePopupViewportResult {\n  const { store, side, cssVars, children } = parameters;\n\n  const direction = useDirection();\n\n  const activeTrigger = store.useState('activeTriggerElement');\n  const activeTriggerId = store.useState('activeTriggerId');\n  const open = store.useState('open');\n  const payload = store.useState('payload');\n  const mounted = store.useState('mounted');\n  const popupElement = store.useState('popupElement');\n  const positionerElement = store.useState('positionerElement');\n\n  const previousActiveTrigger = usePreviousValue(open ? activeTrigger : null);\n  // Remount current content on trigger changes (and once more when payload lags) to avoid DOM reuse flashes.\n  // The key bumps immediately on trigger switches, then again if the payload arrives on a later render.\n  const currentContentKey = usePopupContentKey(activeTriggerId, payload);\n\n  const capturedNodeRef = React.useRef<HTMLElement | null>(null);\n  const [previousContentNode, setPreviousContentNode] = React.useState<HTMLElement | null>(null);\n\n  const [newTriggerOffset, setNewTriggerOffset] = React.useState<Offset | null>(null);\n\n  const currentContainerRef = React.useRef<HTMLDivElement>(null);\n  const previousContainerRef = React.useRef<HTMLDivElement>(null);\n\n  const onAnimationsFinished = useAnimationsFinished(currentContainerRef, true, false);\n  const cleanupFrame = useAnimationFrame();\n\n  const [previousContentDimensions, setPreviousContentDimensions] = React.useState<{\n    width: number;\n    height: number;\n  } | null>(null);\n\n  const [showStartingStyleAttribute, setShowStartingStyleAttribute] = React.useState(false);\n\n  useIsoLayoutEffect(() => {\n    store.set('hasViewport', true);\n    return () => {\n      store.set('hasViewport', false);\n    };\n  }, [store]);\n\n  const handleMeasureLayout = useStableCallback(() => {\n    currentContainerRef.current?.style.setProperty('animation', 'none');\n    currentContainerRef.current?.style.setProperty('transition', 'none');\n\n    previousContainerRef.current?.style.setProperty('display', 'none');\n  });\n\n  const handleMeasureLayoutComplete = useStableCallback((previousDimensions: Dimensions | null) => {\n    currentContainerRef.current?.style.removeProperty('animation');\n    currentContainerRef.current?.style.removeProperty('transition');\n\n    previousContainerRef.current?.style.removeProperty('display');\n\n    if (previousDimensions) {\n      setPreviousContentDimensions(previousDimensions);\n    }\n  });\n\n  const lastHandledTriggerRef = React.useRef<Element | null>(null);\n\n  useIsoLayoutEffect(() => {\n    // When a trigger changes, set the captured children HTML to state,\n    // so we can render both new and old content.\n    if (\n      activeTrigger &&\n      previousActiveTrigger &&\n      activeTrigger !== previousActiveTrigger &&\n      lastHandledTriggerRef.current !== activeTrigger &&\n      capturedNodeRef.current\n    ) {\n      setPreviousContentNode(capturedNodeRef.current);\n      setShowStartingStyleAttribute(true);\n\n      // Calculate the relative position between the previous and new trigger,\n      // so we can pass it to the style hook for animation purposes.\n      const offset = calculateRelativePosition(previousActiveTrigger, activeTrigger);\n      setNewTriggerOffset(offset);\n\n      cleanupFrame.request(() => {\n        ReactDOM.flushSync(() => {\n          setShowStartingStyleAttribute(false);\n        });\n        onAnimationsFinished(() => {\n          setPreviousContentNode(null);\n          setPreviousContentDimensions(null);\n          capturedNodeRef.current = null;\n        });\n      });\n\n      lastHandledTriggerRef.current = activeTrigger;\n    }\n  }, [\n    activeTrigger,\n    previousActiveTrigger,\n    previousContentNode,\n    onAnimationsFinished,\n    cleanupFrame,\n  ]);\n\n  // Capture a clone of the current content DOM subtree when not transitioning.\n  // We can't store previous React nodes as they may be stateful; instead we capture DOM clones for visual continuity.\n  useIsoLayoutEffect(() => {\n    // When a transition is in progress, we store the next content in capturedNodeRef.\n    // This handles the case where the trigger changes multiple times before the transition finishes.\n    // We want to always capture the latest content for the previous snapshot.\n    // So clicking quickly on T1, T2, T3 will result in the following sequence:\n    // 1. T1 -> T2: previousContent = T1, currentContent = T2\n    // 2. T2 -> T3: previousContent = T2, currentContent = T3\n    const source = currentContainerRef.current;\n    if (!source) {\n      return;\n    }\n\n    const wrapper = document.createElement('div');\n    for (const child of Array.from(source.childNodes)) {\n      wrapper.appendChild(child.cloneNode(true));\n    }\n\n    capturedNodeRef.current = wrapper;\n  });\n\n  const isTransitioning = previousContentNode != null;\n  let childrenToRender: React.ReactNode;\n  if (!isTransitioning) {\n    childrenToRender = (\n      <div data-current ref={currentContainerRef} key={currentContentKey}>\n        {children}\n      </div>\n    );\n  } else {\n    childrenToRender = (\n      <React.Fragment>\n        <div\n          data-previous\n          inert={inertValue(true)}\n          ref={previousContainerRef}\n          style={\n            {\n              ...(previousContentDimensions\n                ? {\n                    [cssVars.popupWidth]: `${previousContentDimensions.width}px`,\n                    [cssVars.popupHeight]: `${previousContentDimensions.height}px`,\n                  }\n                : null),\n              position: 'absolute',\n            } as React.CSSProperties\n          }\n          key=\"previous\"\n          data-ending-style={showStartingStyleAttribute ? undefined : ''}\n        />\n        <div\n          data-current\n          ref={currentContainerRef}\n          key={currentContentKey}\n          data-starting-style={showStartingStyleAttribute ? '' : undefined}\n        >\n          {children}\n        </div>\n      </React.Fragment>\n    );\n  }\n\n  // When previousContentNode is present, imperatively populate the previous container with the cloned children.\n  useIsoLayoutEffect(() => {\n    const container = previousContainerRef.current;\n    if (!container || !previousContentNode) {\n      return;\n    }\n\n    container.replaceChildren(...Array.from(previousContentNode.childNodes));\n  }, [previousContentNode]);\n\n  usePopupAutoResize({\n    popupElement,\n    positionerElement,\n    mounted,\n    content: payload,\n    onMeasureLayout: handleMeasureLayout,\n    onMeasureLayoutComplete: handleMeasureLayoutComplete,\n    side,\n    direction,\n  });\n\n  const state: PopupViewportState = {\n    activationDirection: getActivationDirection(newTriggerOffset),\n    transitioning: isTransitioning,\n  };\n\n  return { children: childrenToRender, state };\n}\n\ntype Offset = {\n  horizontal: number;\n  vertical: number;\n};\n\n/**\n * Returns a string describing the provided offset.\n * It describes both the horizontal and vertical offset, separated by a space.\n *\n * @param offset\n */\nfunction getActivationDirection(offset: Offset | null): string | undefined {\n  if (!offset) {\n    return undefined;\n  }\n\n  return `${getValueWithTolerance(offset.horizontal, 5, 'right', 'left')} ${getValueWithTolerance(offset.vertical, 5, 'down', 'up')}`;\n}\n\n/**\n * Returns a label describing the value (positive/negative) treating values\n * within tolerance as zero.\n *\n * @param value Value to check\n * @param tolerance Tolerance to treat the value as zero.\n * @param positiveLabel\n * @param negativeLabel\n * @returns If 0 < abs(value) < tolerance, returns an empty string. Otherwise returns positiveLabel or negativeLabel.\n */\nfunction getValueWithTolerance(\n  value: number,\n  tolerance: number,\n  positiveLabel: string,\n  negativeLabel: string,\n) {\n  if (value > tolerance) {\n    return positiveLabel;\n  }\n\n  if (value < -tolerance) {\n    return negativeLabel;\n  }\n\n  return '';\n}\n\n/**\n * Calculates the relative position between centers of two elements.\n */\nfunction calculateRelativePosition(from: Element, to: Element): Offset {\n  const fromRect = from.getBoundingClientRect();\n  const toRect = to.getBoundingClientRect();\n\n  const fromCenter = {\n    x: fromRect.left + fromRect.width / 2,\n    y: fromRect.top + fromRect.height / 2,\n  };\n  const toCenter = {\n    x: toRect.left + toRect.width / 2,\n    y: toRect.top + toRect.height / 2,\n  };\n\n  return {\n    horizontal: toCenter.x - fromCenter.x,\n    vertical: toCenter.y - fromCenter.y,\n  };\n}\n\n/**\n * Returns a key that forces remounting content when triggers change or a payload is updated.\n */\nfunction usePopupContentKey(activeTriggerId: string | null, payload: unknown): string {\n  const [contentKey, setContentKey] = React.useState(0);\n  const previousActiveTriggerIdRef = React.useRef(activeTriggerId);\n  const previousPayloadRef = React.useRef(payload);\n  const pendingPayloadUpdateRef = React.useRef(false);\n\n  useIsoLayoutEffect(() => {\n    // Compare against the last committed values to decide whether we need a new DOM subtree.\n    const previousActiveTriggerId = previousActiveTriggerIdRef.current;\n    const previousPayload = previousPayloadRef.current;\n    const triggerIdChanged = activeTriggerId !== previousActiveTriggerId;\n    const payloadChanged = payload !== previousPayload;\n\n    if (triggerIdChanged) {\n      // Remount immediately on trigger change; remember if payload hasn't caught up yet.\n      setContentKey((value) => value + 1);\n      pendingPayloadUpdateRef.current = !payloadChanged;\n    } else if (pendingPayloadUpdateRef.current && payloadChanged) {\n      // Payload arrived a render later, so remount once more to avoid reusing the old <img>.\n      setContentKey((value) => value + 1);\n      pendingPayloadUpdateRef.current = false;\n    }\n\n    // Persist current values for the next render's comparison.\n    previousActiveTriggerIdRef.current = activeTriggerId;\n    previousPayloadRef.current = payload;\n  }, [activeTriggerId, payload]);\n\n  return `${activeTriggerId ?? 'current'}-${contentKey}`;\n}\n"
  },
  {
    "path": "packages/react/src/utils/usePressAndHold.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { useTimeout } from '@base-ui/utils/useTimeout';\nimport { useInterval } from '@base-ui/utils/useInterval';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { ownerWindow } from '@base-ui/utils/owner';\n\nconst DEFAULT_TICK_DELAY = 60;\nconst DEFAULT_START_DELAY = 400;\nconst DEFAULT_SCROLL_DISTANCE = 8;\nconst TOUCH_TIMEOUT = 50;\nconst MAX_POINTER_MOVES_AFTER_TOUCH = 3;\n\n// Treat pen as touch-like to avoid forcing the software keyboard on stylus taps.\n// Linux Chrome may emit \"pen\" historically for mouse usage due to a bug, but the touch path\n// still works with minor behavioral differences.\nfunction isTouchLikePointerType(pointerType: string) {\n  return pointerType === 'touch' || pointerType === 'pen';\n}\n\nexport interface UsePressAndHoldParameters {\n  disabled: boolean;\n  readOnly?: boolean | undefined;\n  /**\n   * Called on each tick during a hold. Return `false` to stop the auto-change sequence.\n   */\n  tick: (triggerEvent?: Event) => boolean;\n  /**\n   * Called when the hold ends via the global `pointerup` event.\n   */\n  onStop?: ((nativeEvent: PointerEvent) => void) | undefined;\n  /**\n   * Interval between ticks once the hold is active.\n   * @default 60\n   */\n  tickDelay?: number | undefined;\n  /**\n   * Delay before the repeating ticks start after the initial hold.\n   * @default 400\n   */\n  startDelay?: number | undefined;\n  /**\n   * Pointer movement distance (px) that cancels the hold and is treated as scrolling.\n   * @default 8\n   */\n  scrollDistance?: number | undefined;\n  /**\n   * Ref to the anchor element used to resolve `ownerWindow`.\n   */\n  elementRef: React.RefObject<HTMLElement | null>;\n}\n\nexport interface UsePressAndHoldReturnValue {\n  pointerHandlers: {\n    onTouchStart: React.TouchEventHandler<HTMLElement>;\n    onTouchEnd: React.TouchEventHandler<HTMLElement>;\n    onPointerDown: React.PointerEventHandler<HTMLElement>;\n    onPointerUp: React.PointerEventHandler<HTMLElement>;\n    onPointerMove: React.PointerEventHandler<HTMLElement>;\n    onMouseEnter: React.MouseEventHandler<HTMLElement>;\n    onMouseLeave: React.MouseEventHandler<HTMLElement>;\n    onMouseUp: React.MouseEventHandler<HTMLElement>;\n  };\n  /**\n   * Returns `true` if the `onClick` handler should be skipped.\n   * Use this in the element's `onClick` to prevent double-firing on mouse clicks\n   * (already handled by `onPointerDown`) and to suppress the synthesized click\n   * that browsers fire after a touch hold.\n   */\n  shouldSkipClick: (event: React.MouseEvent) => boolean;\n}\n\n/**\n * Adds press-and-hold behavior to a button element.\n * On pointer down, performs one action immediately, then after a delay starts\n * continuous repeated actions at a fixed interval. Handles mouse, touch, and pen\n * inputs correctly, including Android-specific quirks.\n */\nexport function usePressAndHold(params: UsePressAndHoldParameters): UsePressAndHoldReturnValue {\n  const {\n    disabled,\n    readOnly = false,\n    tick,\n    onStop,\n    tickDelay = DEFAULT_TICK_DELAY,\n    startDelay = DEFAULT_START_DELAY,\n    scrollDistance = DEFAULT_SCROLL_DISTANCE,\n    elementRef,\n  } = params;\n\n  const startTickTimeout = useTimeout();\n  const tickInterval = useInterval();\n  const intentionalTouchCheckTimeout = useTimeout();\n\n  const isPressedRef = React.useRef(false);\n  const movesAfterTouchRef = React.useRef(0);\n  const downCoordsRef = React.useRef({ x: 0, y: 0 });\n  const isTouchingButtonRef = React.useRef(false);\n  const ignoreClickRef = React.useRef(false);\n  const pointerTypeRef = React.useRef('');\n  const unsubscribeFromGlobalContextMenuRef = React.useRef<() => void>(() => {});\n\n  const stopAutoChange = useStableCallback(() => {\n    intentionalTouchCheckTimeout.clear();\n    startTickTimeout.clear();\n    tickInterval.clear();\n    unsubscribeFromGlobalContextMenuRef.current();\n    movesAfterTouchRef.current = 0;\n  });\n\n  const startAutoChange = useStableCallback((triggerNativeEvent?: Event) => {\n    stopAutoChange();\n\n    const element = elementRef.current;\n    if (!element) {\n      return;\n    }\n\n    const win = ownerWindow(element);\n\n    function handleContextMenu(event: Event) {\n      event.preventDefault();\n    }\n\n    // A global context menu listener is necessary to prevent the context menu from\n    // appearing when the touch is slightly outside of the element's hit area.\n    win.addEventListener('contextmenu', handleContextMenu);\n    unsubscribeFromGlobalContextMenuRef.current = () => {\n      win.removeEventListener('contextmenu', handleContextMenu);\n    };\n\n    win.addEventListener(\n      'pointerup',\n      (event: PointerEvent) => {\n        isPressedRef.current = false;\n        stopAutoChange();\n        onStop?.(event);\n      },\n      { once: true },\n    );\n\n    if (!tick(triggerNativeEvent)) {\n      stopAutoChange();\n      return;\n    }\n\n    startTickTimeout.start(startDelay, () => {\n      tickInterval.start(tickDelay, () => {\n        if (!tick(triggerNativeEvent)) {\n          stopAutoChange();\n        }\n      });\n    });\n  });\n\n  React.useEffect(() => () => stopAutoChange(), [stopAutoChange]);\n\n  const pointerHandlers: UsePressAndHoldReturnValue['pointerHandlers'] = {\n    onTouchStart() {\n      isTouchingButtonRef.current = true;\n    },\n    onTouchEnd() {\n      isTouchingButtonRef.current = false;\n    },\n    onPointerDown(event) {\n      const isMainButton = !event.button || event.button === 0;\n      if (event.defaultPrevented || !isMainButton || disabled || readOnly) {\n        return;\n      }\n\n      pointerTypeRef.current = event.pointerType;\n      ignoreClickRef.current = false;\n      isPressedRef.current = true;\n      downCoordsRef.current = { x: event.clientX, y: event.clientY };\n\n      const isTouchPointer = isTouchLikePointerType(event.pointerType);\n\n      if (!isTouchPointer) {\n        event.preventDefault();\n        startAutoChange(event.nativeEvent);\n      } else {\n        // Check if the pointerdown was intentional and not the result of a scroll or\n        // pinch-zoom. In that case, we don't want to start the auto-change sequence.\n        intentionalTouchCheckTimeout.start(TOUCH_TIMEOUT, () => {\n          const moves = movesAfterTouchRef.current;\n          movesAfterTouchRef.current = 0;\n          // Only start auto-change if the touch is still pressed (prevents races\n          // with pointerup occurring before the timeout fires on quick taps).\n          const stillPressed = isPressedRef.current;\n          if (stillPressed && moves < MAX_POINTER_MOVES_AFTER_TOUCH) {\n            startAutoChange(event.nativeEvent);\n            ignoreClickRef.current = true; // synthesized click after hold should be ignored\n          } else {\n            // No auto-change (simple tap or scroll gesture), allow the click handler\n            // to perform a single action.\n            ignoreClickRef.current = false;\n            stopAutoChange();\n          }\n        });\n      }\n    },\n    onPointerUp(event) {\n      // Ensure we mark the press as released for touch flows even if auto-change never\n      // started, so the delayed auto-change check won't start after a quick tap.\n      if (isTouchLikePointerType(event.pointerType)) {\n        isPressedRef.current = false;\n      }\n    },\n    onPointerMove(event) {\n      if (\n        disabled ||\n        readOnly ||\n        !isTouchLikePointerType(event.pointerType) ||\n        !isPressedRef.current\n      ) {\n        return;\n      }\n\n      if (movesAfterTouchRef.current != null) {\n        movesAfterTouchRef.current += 1;\n      }\n\n      const { x, y } = downCoordsRef.current;\n      const dx = x - event.clientX;\n      const dy = y - event.clientY;\n\n      if (dx ** 2 + dy ** 2 > scrollDistance ** 2) {\n        stopAutoChange();\n      }\n    },\n    onMouseEnter(event) {\n      if (\n        event.defaultPrevented ||\n        disabled ||\n        readOnly ||\n        !isPressedRef.current ||\n        isTouchingButtonRef.current ||\n        isTouchLikePointerType(pointerTypeRef.current)\n      ) {\n        return;\n      }\n\n      startAutoChange(event.nativeEvent);\n    },\n    onMouseLeave() {\n      if (isTouchingButtonRef.current) {\n        return;\n      }\n\n      stopAutoChange();\n    },\n    onMouseUp() {\n      if (isTouchingButtonRef.current) {\n        return;\n      }\n\n      stopAutoChange();\n    },\n  };\n\n  const shouldSkipClick = useStableCallback((event: React.MouseEvent): boolean => {\n    if (event.defaultPrevented) {\n      return true;\n    }\n    if (isTouchLikePointerType(pointerTypeRef.current)) {\n      return ignoreClickRef.current;\n    }\n    return event.detail !== 0;\n  });\n\n  return { pointerHandlers, shouldSkipClick };\n}\n"
  },
  {
    "path": "packages/react/src/utils/useRegisteredLabelId.ts",
    "content": "'use client';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useBaseUiId } from './useBaseUiId';\n\nexport function useRegisteredLabelId(\n  idProp: string | undefined,\n  setLabelId: (id: string | undefined) => void,\n): string | undefined {\n  const id = useBaseUiId(idProp);\n\n  useIsoLayoutEffect(() => {\n    setLabelId(id);\n\n    return () => {\n      setLabelId(undefined);\n    };\n  }, [id, setLabelId]);\n\n  return id;\n}\n"
  },
  {
    "path": "packages/react/src/utils/useRenderElement.spec.tsx",
    "content": "/* eslint-disable react-hooks/rules-of-hooks */\nimport * as React from 'react';\nimport { expectType } from '#test-utils';\nimport { useRenderElement } from './useRenderElement';\n\nconst element1 = useRenderElement('div', {}, {});\n\nexpectType<React.ReactElement, typeof element1>(element1);\n\nconst element2 = useRenderElement(\n  'div',\n  {},\n  {\n    enabled: true,\n  },\n);\n\nexpectType<React.ReactElement, typeof element2>(element2);\n\nconst element3 = useRenderElement(\n  'div',\n  {},\n  {\n    enabled: false,\n  },\n);\n\nexpectType<null, typeof element3>(element3);\n\nconst element4 = useRenderElement(\n  'div',\n  {},\n  {\n    enabled: Math.random() > 0.5,\n  },\n);\n\nexpectType<React.ReactElement | null, typeof element4>(element4);\n"
  },
  {
    "path": "packages/react/src/utils/useRenderElement.test.tsx",
    "content": "import { vi, expect } from 'vitest';\n/* eslint-disable testing-library/render-result-naming-convention */\nimport * as React from 'react';\nimport { createRenderer } from '#test-utils';\nimport { reactMajor } from '@mui/internal-test-utils';\nimport type { BaseUIComponentProps, ComponentRenderFn, HTMLProps } from '../utils/types';\nimport { useRenderElement } from './useRenderElement';\nimport { EMPTY_OBJECT } from './constants';\n\ndescribe('useRenderElement', () => {\n  const { render } = createRenderer();\n\n  const TestComponent = React.forwardRef(function TestComponent(\n    componentProps: BaseUIComponentProps<'div', { active?: boolean }> & { active?: boolean },\n    forwardedRef: React.ForwardedRef<HTMLDivElement>,\n  ) {\n    const { className, render: renderProp, active, ...elementProps } = componentProps;\n\n    const state = { active };\n\n    const element = useRenderElement('div', componentProps, {\n      state,\n      ref: forwardedRef,\n      props: [{ ...elementProps, className: 'test-component', style: { padding: '10px' } }],\n    });\n\n    return element;\n  });\n\n  const DirectPropsTestComponent = React.forwardRef(function DirectPropsTestComponent(\n    componentProps: BaseUIComponentProps<'div', { active?: boolean }> & { active?: boolean },\n    forwardedRef: React.ForwardedRef<HTMLDivElement>,\n  ) {\n    const { className, render: renderProp, active, ...elementProps } = componentProps;\n\n    return useRenderElement('div', componentProps, {\n      state: { active },\n      ref: forwardedRef,\n      props: elementProps,\n    });\n  });\n\n  const ArrayPropsTestComponent = React.forwardRef(function ArrayPropsTestComponent(\n    componentProps: BaseUIComponentProps<'div', { active?: boolean }> & { active?: boolean },\n    forwardedRef: React.ForwardedRef<HTMLDivElement>,\n  ) {\n    const { className, render: renderProp, active, ...elementProps } = componentProps;\n\n    return useRenderElement('div', componentProps, {\n      state: { active },\n      ref: forwardedRef,\n      props: [elementProps, { className: 'test-component' }],\n    });\n  });\n\n  function DisabledPropsTestComponent(props: {\n    propsGetter: () => React.ComponentPropsWithRef<'div'>;\n  }) {\n    return useRenderElement(\n      'div',\n      {},\n      {\n        enabled: false,\n        props: [props.propsGetter],\n      },\n    );\n  }\n\n  it('accepts className as function', async () => {\n    const { container } = await render(\n      <TestComponent\n        active\n        className={(state) => (state.active ? 'active-class' : 'inactive-class')}\n      />,\n    );\n\n    const element = container.firstElementChild;\n\n    expect(element).toHaveAttribute('class', 'active-class test-component');\n  });\n\n  it('accepts className as function that returns undefined', async () => {\n    const { container } = await render(\n      <TestComponent className={(state) => (state.active ? 'active-class' : undefined)} />,\n    );\n\n    const element = container.firstElementChild;\n\n    expect(element).toHaveAttribute('class', 'test-component');\n  });\n\n  it('accepts style as function', async () => {\n    const { container } = await render(\n      <TestComponent\n        active\n        style={(state) => ({ color: state.active ? 'rgb(255,0,0)' : 'rgb(0,255,0)' })}\n      />,\n    );\n\n    const element = container.firstElementChild;\n\n    expect(element?.getAttribute('style')).toBe('padding: 10px; color: rgb(255, 0, 0);');\n  });\n\n  it('accepts style as function that returns undefined', async () => {\n    const { container } = await render(\n      <TestComponent style={(state) => (state.active ? { color: 'rgb(255,0,0)' } : undefined)} />,\n    );\n\n    const element = container.firstElementChild;\n\n    expect(element?.getAttribute('style')).toBe('padding: 10px;');\n  });\n\n  it('makes single prop objects preventable', async () => {\n    const handleMouseDown = vi.fn((event) => {\n      event.preventBaseUIHandler();\n    });\n\n    const { container } = await render(<DirectPropsTestComponent onMouseDown={handleMouseDown} />);\n\n    const element = container.firstElementChild as HTMLDivElement;\n\n    expect(() =>\n      element.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })),\n    ).not.toThrow();\n    expect(handleMouseDown).toHaveBeenCalledTimes(1);\n  });\n\n  it('makes multi-prop arrays preventable when the event handler is first', async () => {\n    const handleMouseDown = vi.fn((event) => {\n      event.preventBaseUIHandler();\n    });\n\n    const { container } = await render(<ArrayPropsTestComponent onMouseDown={handleMouseDown} />);\n\n    const element = container.firstElementChild as HTMLDivElement;\n\n    expect(() =>\n      element.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })),\n    ).not.toThrow();\n    expect(handleMouseDown).toHaveBeenCalledTimes(1);\n  });\n\n  it('makes obscure single-prop events preventable', async () => {\n    const handleContextMenu = vi.fn((event) => {\n      event.preventBaseUIHandler();\n    });\n\n    const { container } = await render(\n      <DirectPropsTestComponent onContextMenu={handleContextMenu} />,\n    );\n\n    const element = container.firstElementChild as HTMLDivElement;\n\n    expect(() =>\n      element.dispatchEvent(new MouseEvent('contextmenu', { bubbles: true })),\n    ).not.toThrow();\n    expect(handleContextMenu).toHaveBeenCalledTimes(1);\n  });\n\n  it('makes obscure multi-prop array events preventable when the event handler is first', async () => {\n    const handleContextMenu = vi.fn((event) => {\n      event.preventBaseUIHandler();\n    });\n\n    const { container } = await render(\n      <ArrayPropsTestComponent onContextMenu={handleContextMenu} />,\n    );\n\n    const element = container.firstElementChild as HTMLDivElement;\n\n    expect(() =>\n      element.dispatchEvent(new MouseEvent('contextmenu', { bubbles: true })),\n    ).not.toThrow();\n    expect(handleContextMenu).toHaveBeenCalledTimes(1);\n  });\n\n  it('does not resolve props when disabled', async () => {\n    const propsGetter = vi.fn(() => ({\n      onMouseDown() {},\n    }));\n\n    const { container } = await render(<DisabledPropsTestComponent propsGetter={propsGetter} />);\n\n    expect(container.firstElementChild).toBeNull();\n    expect(propsGetter).not.toHaveBeenCalled();\n  });\n\n  describe('prop: render', () => {\n    it('accepts render as a function that receives props and state', async () => {\n      const renderCalls: Array<[HTMLProps, { active?: boolean }]> = [];\n      const renderFn: ComponentRenderFn<HTMLProps, { active?: boolean }> = (props, state) => {\n        renderCalls.push([props, state]);\n        return <span {...props} data-active={String(state.active)} />;\n      };\n\n      const { container } = await render(\n        <TestComponent active render={renderFn} data-testid=\"custom\" />,\n      );\n\n      const element = container.firstElementChild;\n\n      expect(renderCalls.length).toBeGreaterThan(0);\n      const [firstCallProps, firstCallState] = renderCalls[0];\n      expect(firstCallProps).toMatchObject({\n        className: 'test-component',\n        'data-testid': 'custom',\n      });\n      expect(firstCallProps.style).toEqual({ padding: '10px' });\n      expect(firstCallState).toEqual({ active: true });\n      expect(element?.tagName).toBe('SPAN');\n      expect(element).toHaveAttribute('data-testid', 'custom');\n      expect(element).toHaveAttribute('data-active', 'true');\n    });\n\n    it('warns when render is passed a function with an uppercase name', async () => {\n      const warnSpy = vi\n        .spyOn(console, 'warn')\n        .mockName('console.warn')\n        .mockImplementation(() => {});\n\n      function UppercaseRenderPropWarningTestComponent(props: React.ComponentPropsWithRef<'span'>) {\n        return <span {...props} />;\n      }\n\n      await render(<TestComponent render={UppercaseRenderPropWarningTestComponent} />);\n\n      expect(warnSpy.mock.calls.length).toBe(1);\n      expect(warnSpy.mock.calls[0][0]).toContain(\n        'Base UI: The `render` prop received a function named `UppercaseRenderPropWarningTestComponent` that starts with an uppercase letter.',\n      );\n      expect(warnSpy.mock.calls[0][0]).toContain(\n        'Use `render={<Component />}` or `render={(props) => <Component {...props} />}` instead.',\n      );\n      warnSpy.mockRestore();\n    });\n\n    it('warns when render is passed a function with an uppercase acronym prefix', async () => {\n      const warnSpy = vi\n        .spyOn(console, 'warn')\n        .mockName('console.warn')\n        .mockImplementation(() => {});\n\n      function UIInput(props: React.ComponentPropsWithRef<'span'>) {\n        return <span {...props} />;\n      }\n\n      await render(<TestComponent render={UIInput} />);\n\n      expect(warnSpy.mock.calls.length).toBe(1);\n      warnSpy.mockRestore();\n    });\n\n    it('does not warn when render is passed a lowercase callback', async () => {\n      const warnSpy = vi\n        .spyOn(console, 'warn')\n        .mockName('console.warn')\n        .mockImplementation(() => {});\n\n      const renderFn = (props: React.ComponentPropsWithRef<'span'>) => <span {...props} />;\n\n      await render(<TestComponent render={renderFn} />);\n\n      expect(warnSpy.mock.calls.length).toBe(0);\n      warnSpy.mockRestore();\n    });\n\n    it('does not warn when render is passed a screaming snake case callback', async () => {\n      const warnSpy = vi\n        .spyOn(console, 'warn')\n        .mockName('console.warn')\n        .mockImplementation(() => {});\n\n      const renderFn = (props: React.ComponentPropsWithRef<'span'>) => <span {...props} />;\n      Object.defineProperty(renderFn, 'name', {\n        value: 'DEFAULT_RENDER',\n      });\n\n      await render(<TestComponent render={renderFn} />);\n\n      expect(warnSpy.mock.calls.length).toBe(0);\n      warnSpy.mockRestore();\n    });\n\n    it('does not warn when render is passed a callback with an inferred useCallback name', async () => {\n      const warnSpy = vi\n        .spyOn(console, 'warn')\n        .mockName('console.warn')\n        .mockImplementation(() => {});\n\n      const renderFn = (props: React.ComponentPropsWithRef<'span'>) => <span {...props} />;\n      Object.defineProperty(renderFn, 'name', {\n        value: 'DropdownMenuExample.useCallback[renderSearchInput]',\n      });\n\n      await render(<TestComponent render={renderFn} />);\n\n      expect(warnSpy.mock.calls.length).toBe(0);\n      warnSpy.mockRestore();\n    });\n\n    it('does not warn when render is passed as a React element', async () => {\n      const warnSpy = vi\n        .spyOn(console, 'warn')\n        .mockName('console.warn')\n        .mockImplementation(() => {});\n\n      function UppercaseRenderElement(props: React.ComponentPropsWithRef<'span'>) {\n        return <span {...props} />;\n      }\n\n      await render(<TestComponent render={<UppercaseRenderElement />} />);\n\n      expect(warnSpy.mock.calls.length).toBe(0);\n      warnSpy.mockRestore();\n    });\n\n    it('accepts render as a React element and clones it with merged props', async () => {\n      const CustomElement = React.forwardRef<HTMLSpanElement, React.ComponentPropsWithRef<'span'>>(\n        function CustomElement(props, ref) {\n          return <span ref={ref} {...props} />;\n        },\n      );\n\n      const { container } = await render(\n        <TestComponent active render={<CustomElement data-active=\"true\" />} data-testid=\"custom\" />,\n      );\n\n      const element = container.firstElementChild;\n\n      expect(element?.tagName).toBe('SPAN');\n      expect(element).toHaveAttribute('data-testid', 'custom');\n      expect(element).toHaveAttribute('data-active', 'true');\n    });\n\n    it('forwards ref to render element', async () => {\n      const CustomElement = React.forwardRef<HTMLDivElement, React.ComponentPropsWithRef<'div'>>(\n        function CustomElement(props, ref) {\n          return <div ref={ref} {...props} />;\n        },\n      );\n\n      const ref = React.createRef<HTMLDivElement>();\n      const { container } = await render(<TestComponent ref={ref} render={<CustomElement />} />);\n      const element = container.firstElementChild;\n      expect(ref.current).toBe(element);\n    });\n\n    it('merges className from render element and component props', async () => {\n      const { container } = await render(\n        <TestComponent\n          active\n          className=\"component-class\"\n          render={<div className=\"render-class\" />}\n        />,\n      );\n\n      const element = container.firstElementChild;\n\n      expect(element?.className).toContain('component-class');\n      expect(element?.className).toContain('render-class');\n      expect(element?.className).toContain('test-component');\n    });\n\n    it('merges className function with render element', async () => {\n      const { container } = await render(\n        <TestComponent\n          active\n          className={(state) => (state.active ? 'active-class' : '')}\n          render={<div className=\"render-class\" />}\n        />,\n      );\n\n      const element = container.firstElementChild;\n\n      expect(element?.className).toContain('active-class');\n      expect(element?.className).toContain('render-class');\n      expect(element?.className).toContain('test-component');\n    });\n\n    it('merges style from render element and component props', async () => {\n      const { container } = await render(\n        <TestComponent\n          active\n          style={{ color: 'rgb(255, 0, 0)' }}\n          render={<div style={{ fontSize: '16px' }} />}\n        />,\n      );\n\n      const element = container.firstElementChild as HTMLElement;\n      expect(element.style.padding).toBe('10px');\n      expect(element.style.color).toBe('rgb(255, 0, 0)');\n      expect(element.style.fontSize).toBe('16px');\n    });\n\n    it('merges style function with render element', async () => {\n      const { container } = await render(\n        <TestComponent\n          active\n          style={(state) => ({ color: state.active ? 'rgb(255, 0, 0)' : 'rgb(0, 0, 0)' })}\n          render={<div style={{ fontSize: '16px' }} />}\n        />,\n      );\n\n      const element = container.firstElementChild as HTMLElement;\n      expect(element.style.padding).toBe('10px');\n      expect(element.style.color).toBe('rgb(255, 0, 0)');\n      expect(element.style.fontSize).toBe('16px');\n    });\n\n    it('handles lazy elements', async () => {\n      const LazyComponent = React.lazy(() =>\n        Promise.resolve({\n          default: React.forwardRef<HTMLDivElement, React.ComponentPropsWithRef<'div'>>(\n            function LazyDiv(props, ref) {\n              return <div ref={ref} data-lazy=\"true\" {...props} />;\n            },\n          ),\n        }),\n      );\n\n      const { container } = await render(\n        <React.Suspense fallback={<div>Loading…</div>}>\n          <TestComponent active render={<LazyComponent data-testid=\"lazy\" />} />\n        </React.Suspense>,\n      );\n\n      const element = container.firstElementChild;\n      expect(element).not.toBe(null);\n      expect(element?.getAttribute('data-testid')).toBe('lazy');\n      expect(element?.getAttribute('data-lazy')).toBe('true');\n      expect(element?.className).toContain('test-component');\n    });\n\n    // React 18 also log console error, React 19 fixed that. Ignoring this test for React 18.\n    it.skipIf(reactMajor < 19)(\n      'throws error for invalid render element in development',\n      async () => {\n        const originalEnv = process.env.NODE_ENV;\n\n        let error: Error | null = null;\n        try {\n          process.env.NODE_ENV = 'development';\n          await render(<TestComponent render={'not a valid element' as any} />);\n        } catch (err) {\n          error = err as Error;\n        } finally {\n          process.env.NODE_ENV = originalEnv;\n        }\n\n        expect(error).not.toBe(null);\n        expect(error?.message).toMatch(\n          /Base UI: The `render` prop was provided an invalid React element/,\n        );\n      },\n    );\n\n    it('handles render element with existing ref', async () => {\n      const CustomElement = React.forwardRef<HTMLDivElement, React.ComponentPropsWithRef<'div'>>(\n        function CustomElement(props, ref) {\n          return <div ref={ref} {...props} />;\n        },\n      );\n\n      const renderRef = React.createRef<HTMLDivElement>();\n      const componentRef = React.createRef<HTMLDivElement>();\n\n      await render(<TestComponent ref={componentRef} render={<CustomElement ref={renderRef} />} />);\n\n      expect(renderRef.current).toBeInstanceOf(HTMLDivElement);\n      expect(componentRef.current).toBeInstanceOf(HTMLDivElement);\n      expect(renderRef.current).toBe(componentRef.current);\n    });\n  });\n\n  describe('EMPTY_OBJECT mutation safety', () => {\n    // This test verifies that the hook doesn't attempt to mutate EMPTY_OBJECT\n    // which would throw a TypeError in strict mode since it's frozen.\n    const MinimalComponent = React.forwardRef(function MinimalComponent(\n      componentProps: BaseUIComponentProps<'div', Record<string, never>>,\n      forwardedRef: React.ForwardedRef<HTMLDivElement>,\n    ) {\n      // Using EMPTY_OBJECT as state and no additional props simulates the edge case\n      // where mergeObjects might return undefined and fall back to EMPTY_OBJECT\n      const element = useRenderElement('div', componentProps, {\n        state: EMPTY_OBJECT,\n        ref: forwardedRef,\n        // No props passed - relies on stateProps which will be {}\n      });\n\n      return element;\n    });\n\n    it('does not throw when className is provided with minimal props', async () => {\n      const { container } = await render(<MinimalComponent className=\"test-class\" />);\n      expect(container.firstElementChild).not.toBeNull();\n      expect(container.firstElementChild).toHaveAttribute('class', 'test-class');\n    });\n\n    it('does not throw when style is provided with minimal props', async () => {\n      const { container } = await render(<MinimalComponent style={{ color: 'red' }} />);\n      expect(container.firstElementChild).not.toBeNull();\n      const element = container.firstElementChild as HTMLElement;\n      expect(element.style.color).toBe('red');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react/src/utils/useRenderElement.tsx",
    "content": "import * as React from 'react';\nimport { useMergedRefs, useMergedRefsN } from '@base-ui/utils/useMergedRefs';\nimport { getReactElementRef } from '@base-ui/utils/getReactElementRef';\nimport { mergeObjects } from '@base-ui/utils/mergeObjects';\nimport { warn } from '@base-ui/utils/warn';\nimport type { BaseUIComponentProps, ComponentRenderFn, HTMLProps } from './types';\nimport { getStateAttributesProps, StateAttributesMapping } from './getStateAttributesProps';\nimport { resolveClassName } from './resolveClassName';\nimport { resolveStyle } from './resolveStyle';\nimport { mergeProps, mergePropsN, mergeClassNames } from '../merge-props';\nimport { EMPTY_OBJECT } from './constants';\n\ntype IntrinsicTagName = keyof React.JSX.IntrinsicElements;\n\n/**\n * Renders a Base UI element.\n *\n * @param element The default HTML element to render. Can be overridden by the `render` prop.\n * @param componentProps An object containing the `render` and `className` props to be used for element customization. Other props are ignored.\n * @param params Additional parameters for rendering the element.\n */\nexport function useRenderElement<\n  State extends Record<string, any>,\n  RenderedElementType extends Element,\n  TagName extends IntrinsicTagName | undefined,\n  Enabled extends boolean | undefined = undefined,\n>(\n  element: TagName,\n  componentProps: UseRenderElementComponentProps<State>,\n  params: UseRenderElementParameters<State, RenderedElementType, TagName, Enabled> = {},\n): Enabled extends false ? null : React.ReactElement {\n  const renderProp = componentProps.render;\n  const outProps = useRenderElementProps(componentProps, params);\n  if (params.enabled === false) {\n    return null as Enabled extends false ? null : React.ReactElement;\n  }\n\n  const state = params.state ?? (EMPTY_OBJECT as State);\n  return evaluateRenderProp(element, renderProp, outProps, state) as Enabled extends false\n    ? null\n    : React.ReactElement;\n}\n\n/**\n * Computes render element final props.\n */\nfunction useRenderElementProps<\n  State extends Record<string, any>,\n  RenderedElementType extends Element,\n  TagName extends IntrinsicTagName | undefined,\n  Enabled extends boolean | undefined,\n>(\n  componentProps: UseRenderElementComponentProps<State>,\n  params: UseRenderElementParameters<State, RenderedElementType, TagName, Enabled> = {},\n): React.HTMLAttributes<any> & React.RefAttributes<any> {\n  const { className: classNameProp, style: styleProp, render: renderProp } = componentProps;\n\n  const {\n    state = EMPTY_OBJECT as State,\n    ref,\n    props,\n    stateAttributesMapping,\n    enabled = true,\n  } = params;\n\n  const className = enabled ? resolveClassName(classNameProp, state) : undefined;\n  const style = enabled ? resolveStyle(styleProp, state) : undefined;\n\n  const stateProps = enabled\n    ? getStateAttributesProps(state, stateAttributesMapping)\n    : EMPTY_OBJECT;\n\n  const resolvedProps = enabled && props ? resolveRenderFunctionProps<TagName>(props) : undefined;\n\n  // Ensure outProps is always a new mutable object when enabled, never EMPTY_OBJECT.\n  // This prevents potential TypeError when setting ref, className, or style properties,\n  // since EMPTY_OBJECT is frozen and mutations would fail in strict mode.\n  const outProps: React.HTMLAttributes<any> & React.RefAttributes<any> = enabled\n    ? (mergeObjects(stateProps, resolvedProps) ?? {})\n    : EMPTY_OBJECT;\n\n  // SAFETY: The `useMergedRefs` functions use a single hook to store the same value,\n  // switching between them at runtime is safe. If this assertion fails, React will\n  // throw at runtime anyway.\n  // This also skips the `useMergedRefs` call on the server, which is fine because\n  // refs are not used on the server side.\n  /* eslint-disable react-hooks/rules-of-hooks */\n  if (typeof document !== 'undefined') {\n    if (!enabled) {\n      useMergedRefs(null, null);\n    } else if (Array.isArray(ref)) {\n      outProps.ref = useMergedRefsN([outProps.ref, getReactElementRef(renderProp), ...ref]);\n    } else {\n      outProps.ref = useMergedRefs(outProps.ref, getReactElementRef(renderProp), ref);\n    }\n  }\n\n  if (!enabled) {\n    return EMPTY_OBJECT;\n  }\n\n  if (className !== undefined) {\n    outProps.className = mergeClassNames(outProps.className, className);\n  }\n\n  if (style !== undefined) {\n    outProps.style = mergeObjects(outProps.style, style);\n  }\n\n  return outProps;\n}\n\nfunction resolveRenderFunctionProps<TagName extends IntrinsicTagName | undefined>(\n  props: NonNullable<UseRenderElementParameters<any, any, TagName, any>['props']>,\n): RenderFunctionProps<TagName> {\n  if (Array.isArray(props)) {\n    return mergePropsN(props) as RenderFunctionProps<TagName>;\n  }\n\n  return mergeProps(undefined, props) as RenderFunctionProps<TagName>;\n}\n\n// The symbol React uses internally for lazy components\n// https://github.com/facebook/react/blob/a0566250b210499b4c5677f5ac2eedbd71d51a1b/packages/shared/ReactSymbols.js#L31\n//\n// TODO delete once https://github.com/facebook/react/issues/32392 is fixed\nconst REACT_LAZY_TYPE = Symbol.for('react.lazy');\nconst COMPONENT_IDENTIFIER_PATTERN = /^[A-Z][A-Za-z0-9$]*$/;\nconst LOWERCASE_CHARACTER_PATTERN = /[a-z]/;\n\nfunction evaluateRenderProp<T extends React.ElementType, S>(\n  element: IntrinsicTagName | undefined,\n  render: BaseUIComponentProps<T, S>['render'],\n  props: React.HTMLAttributes<any> & React.RefAttributes<any>,\n  state: S,\n): React.ReactElement {\n  if (render) {\n    if (typeof render === 'function') {\n      if (process.env.NODE_ENV !== 'production') {\n        warnIfRenderPropLooksLikeComponent(render);\n      }\n      return render(props, state);\n    }\n    const mergedProps = mergeProps(props, render.props);\n    mergedProps.ref = props.ref;\n\n    let newElement = render;\n\n    // Workaround for https://github.com/facebook/react/issues/32392\n    // This works because the toArray() logic unwrap lazy element type in\n    // https://github.com/facebook/react/blob/a0566250b210499b4c5677f5ac2eedbd71d51a1b/packages/react/src/ReactChildren.js#L186\n    if (newElement?.$$typeof === REACT_LAZY_TYPE) {\n      const children = React.Children.toArray(render);\n      newElement = children[0] as BaseUIComponentProps<T, S>['render'];\n    }\n\n    // There is a high number of indirections, the error message thrown by React.cloneElement() is\n    // hard to use for developers, this logic provides a better context.\n    //\n    // Our general guideline is to never change the control flow depending on the environment.\n    // However, React.cloneElement() throws if React.isValidElement() is false,\n    // so we can throw before with custom message.\n    if (process.env.NODE_ENV !== 'production') {\n      if (!React.isValidElement(newElement)) {\n        throw new Error(\n          [\n            'Base UI: The `render` prop was provided an invalid React element as `React.isValidElement(render)` is `false`.',\n            'A valid React element must be provided to the `render` prop because it is cloned with props to replace the default element.',\n            'https://base-ui.com/r/invalid-render-prop',\n          ].join('\\n'),\n        );\n      }\n    }\n\n    return React.cloneElement(newElement, mergedProps);\n  }\n  if (element) {\n    if (typeof element === 'string') {\n      return renderTag(element, props);\n    }\n  }\n  // Unreachable, but the typings on `useRenderElement` need to be reworked\n  // to annotate it correctly.\n  throw new Error('Base UI: Render element or function are not defined.');\n}\n\nfunction warnIfRenderPropLooksLikeComponent(renderFn: { name: string }) {\n  const functionName = renderFn.name;\n  if (functionName.length === 0) {\n    return;\n  }\n\n  if (!COMPONENT_IDENTIFIER_PATTERN.test(functionName)) {\n    return;\n  }\n\n  if (!LOWERCASE_CHARACTER_PATTERN.test(functionName)) {\n    return;\n  }\n\n  warn(\n    `The \\`render\\` prop received a function named \\`${functionName}\\` that starts with an uppercase letter.`,\n    'This usually means a React component was passed directly as `render={Component}`.',\n    'Base UI calls `render` as a plain function, which can break the Rules of Hooks during reconciliation.',\n    'If this is an intentional render callback, rename it to start with a lowercase letter.',\n    'Use `render={<Component />}` or `render={(props) => <Component {...props} />}` instead.',\n    'https://base-ui.com/r/invalid-render-prop',\n  );\n}\n\nfunction renderTag(Tag: string, props: Record<string, any>) {\n  if (Tag === 'button') {\n    return <button type=\"button\" {...props} key={props.key} />;\n  }\n  if (Tag === 'img') {\n    return <img alt=\"\" {...props} key={props.key} />;\n  }\n  return React.createElement(Tag, props);\n}\n\ntype RenderFunctionProps<TagName> = TagName extends keyof React.JSX.IntrinsicElements\n  ? React.JSX.IntrinsicElements[TagName]\n  : React.HTMLAttributes<any>;\n\nexport type UseRenderElementParameters<\n  State,\n  RenderedElementType extends Element,\n  TagName,\n  Enabled extends boolean | undefined,\n> = {\n  /**\n   * If `false`, the hook will skip most of its internal logic and return `null`.\n   * This is useful for rendering a component conditionally.\n   * @default true\n   */\n  enabled?: Enabled | undefined;\n  /**\n   * @deprecated\n   */\n  propGetter?: ((externalProps: HTMLProps) => HTMLProps) | undefined;\n  /**\n   * The ref to apply to the rendered element.\n   */\n  ref?: React.Ref<RenderedElementType> | (React.Ref<RenderedElementType> | undefined)[] | undefined;\n  /**\n   * The state of the component.\n   */\n  state?: State | undefined;\n  /**\n   * Intrinsic props to be spread on the rendered element.\n   */\n  props?:\n    | RenderFunctionProps<TagName>\n    | Array<\n        | RenderFunctionProps<TagName>\n        | undefined\n        | ((props: RenderFunctionProps<TagName>) => RenderFunctionProps<TagName>)\n      >\n    | undefined;\n  /**\n   * A mapping of state to `data-*` attributes.\n   */\n  stateAttributesMapping?: StateAttributesMapping<State> | undefined;\n};\n\nexport interface UseRenderElementComponentProps<State> {\n  /**\n   * The class name to apply to the rendered element.\n   * Can be a string or a function that accepts the state and returns a string.\n   */\n  className?: string | ((state: State) => string | undefined) | undefined;\n  /**\n   * The render prop or React element to override the default element.\n   */\n  render?: undefined | React.ReactElement | ComponentRenderFn<React.HTMLAttributes<any>, State>;\n  /**\n   * The style to apply to the rendered element.\n   * Can be a style object or a function that accepts the state and returns a style object.\n   */\n  style?: React.CSSProperties | ((state: State) => React.CSSProperties | undefined) | undefined;\n}\n\nexport interface UseRenderElementState {}\n"
  },
  {
    "path": "packages/react/src/utils/useSwipeDismiss.test.tsx",
    "content": "import { beforeAll, describe, expect, it, vi } from 'vitest';\nimport * as React from 'react';\nimport { fireEvent, flushMicrotasks, screen } from '@mui/internal-test-utils';\nimport { createRenderer, isJSDOM } from '#test-utils';\nimport { useSwipeDismiss } from './useSwipeDismiss';\n\nfunction SwipeBox() {\n  const ref = React.useRef<HTMLDivElement>(null);\n  const swipe = useSwipeDismiss({\n    enabled: true,\n    directions: ['down'],\n    elementRef: ref,\n    movementCssVars: { x: '--x', y: '--y' },\n  });\n\n  return (\n    <div data-testid=\"el\" ref={ref} style={swipe.getDragStyles()} {...swipe.getPointerProps()} />\n  );\n}\n\nfunction SwipeProgressBox({ onProgress }: { onProgress: (progress: number) => void }) {\n  const ref = React.useRef<HTMLDivElement>(null);\n  const swipe = useSwipeDismiss({\n    enabled: true,\n    directions: ['right'],\n    elementRef: ref,\n    movementCssVars: { x: '--x', y: '--y' },\n    onProgress,\n  });\n\n  return (\n    <div\n      data-testid=\"progress\"\n      ref={ref}\n      style={swipe.getDragStyles()}\n      {...swipe.getPointerProps()}\n    />\n  );\n}\n\nfunction createTouch(target: EventTarget, point: { clientX: number; clientY: number }) {\n  if (typeof Touch === 'function') {\n    return new Touch({\n      identifier: 1,\n      target,\n      ...point,\n    });\n  }\n\n  return point;\n}\n\ndescribe('useSwipeDismiss', () => {\n  beforeAll(function beforeHook() {\n    // PointerEvent not fully implemented in jsdom, causing fireEvent.pointer* to ignore options.\n    // https://github.com/jsdom/jsdom/issues/2527\n    (window as any).PointerEvent = window.MouseEvent;\n  });\n\n  const { render } = createRenderer();\n\n  it('does not start swiping within a scrollable element when ignoreScrollableAncestors is true', async () => {\n    const onSwipeStart = vi.fn();\n\n    function SwipeBoxScrollable() {\n      const ref = React.useRef<HTMLDivElement>(null);\n      const swipeDismiss = useSwipeDismiss({\n        enabled: true,\n        directions: ['down'],\n        elementRef: ref,\n        movementCssVars: { x: '--x', y: '--y' },\n        ignoreScrollableAncestors: true,\n        onSwipeStart,\n      });\n\n      return (\n        <div\n          data-testid=\"root\"\n          ref={ref}\n          style={swipeDismiss.getDragStyles()}\n          {...swipeDismiss.getPointerProps()}\n        >\n          <div data-testid=\"scroll\" style={{ overflowY: 'auto', height: 100 }}>\n            <div style={{ height: 200 }} />\n          </div>\n        </div>\n      );\n    }\n\n    await render(<SwipeBoxScrollable />);\n\n    const root = screen.getByTestId('root');\n    const scroll = screen.getByTestId('scroll') as HTMLDivElement;\n\n    const originalElementFromPoint = document.elementFromPoint;\n    document.elementFromPoint = () => scroll;\n\n    if (scroll.scrollHeight <= scroll.clientHeight) {\n      const scrollHeightDescriptor = Object.getOwnPropertyDescriptor(scroll, 'scrollHeight');\n      if (!scrollHeightDescriptor || scrollHeightDescriptor.configurable) {\n        Object.defineProperty(scroll, 'scrollHeight', { value: 200, configurable: true });\n      }\n\n      const clientHeightDescriptor = Object.getOwnPropertyDescriptor(scroll, 'clientHeight');\n      if (!clientHeightDescriptor || clientHeightDescriptor.configurable) {\n        Object.defineProperty(scroll, 'clientHeight', { value: 100, configurable: true });\n      }\n    }\n\n    try {\n      fireEvent.pointerDown(scroll, {\n        button: 0,\n        buttons: 1,\n        pointerId: 1,\n        clientX: 0,\n        clientY: 100,\n        bubbles: true,\n        pointerType: 'mouse',\n        movementX: 0,\n        movementY: 0,\n      });\n\n      await flushMicrotasks();\n\n      fireEvent.pointerMove(scroll, {\n        pointerId: 1,\n        clientX: 0,\n        clientY: 150,\n        bubbles: true,\n        movementX: 0,\n        movementY: 50,\n      });\n\n      await flushMicrotasks();\n\n      expect(onSwipeStart).not.toHaveBeenCalled();\n      expect(root.style.getPropertyValue('--y')).toBe('0px');\n    } finally {\n      document.elementFromPoint = originalElementFromPoint;\n    }\n  });\n\n  it('does not prevent touch scrolling during swipe interactions', async () => {\n    await render(<SwipeBox />);\n    const element = screen.getByTestId('el');\n\n    fireEvent.pointerDown(element, {\n      button: 0,\n      buttons: 1,\n      pointerId: 1,\n      clientX: 0,\n      clientY: 100,\n      bubbles: true,\n      pointerType: 'mouse',\n      movementX: 0,\n      movementY: 0,\n    });\n\n    await flushMicrotasks();\n\n    // First move establishes the baseline (iOS pointermove delay handling).\n    fireEvent.pointerMove(element, {\n      pointerId: 1,\n      clientX: 0,\n      clientY: 100,\n      bubbles: true,\n      movementX: 0,\n      movementY: 0,\n    });\n\n    await flushMicrotasks();\n\n    // Move up (unsupported) should not block the default touch scroll behavior.\n    fireEvent.pointerMove(element, {\n      pointerId: 1,\n      clientX: 0,\n      clientY: 50,\n      bubbles: true,\n      movementX: 0,\n      movementY: -50,\n    });\n\n    await flushMicrotasks();\n\n    const touchMoveBefore = new Event('touchmove', { bubbles: true, cancelable: true });\n    element.dispatchEvent(touchMoveBefore);\n    expect(touchMoveBefore.defaultPrevented).toBe(false);\n\n    // Once a supported direction is detected, touchmove should still not be prevented.\n    fireEvent.pointerMove(element, {\n      pointerId: 1,\n      clientX: 0,\n      clientY: 150,\n      bubbles: true,\n      movementX: 0,\n      movementY: 100,\n    });\n\n    await flushMicrotasks();\n\n    const touchMoveAfter = new Event('touchmove', { bubbles: true, cancelable: true });\n    element.dispatchEvent(touchMoveAfter);\n    expect(touchMoveAfter.defaultPrevented).toBe(false);\n  });\n\n  it('fires onProgress relative to the element size', async () => {\n    const onProgress = vi.fn();\n    await render(<SwipeProgressBox onProgress={onProgress} />);\n    const element = screen.getByTestId('progress');\n\n    const widthDescriptor = Object.getOwnPropertyDescriptor(element, 'offsetWidth');\n    if (!widthDescriptor || widthDescriptor.configurable) {\n      Object.defineProperty(element, 'offsetWidth', { value: 200, configurable: true });\n    }\n\n    fireEvent.pointerDown(element, {\n      button: 0,\n      buttons: 1,\n      pointerId: 1,\n      clientX: 0,\n      clientY: 0,\n      bubbles: true,\n      pointerType: 'mouse',\n      movementX: 0,\n      movementY: 0,\n    });\n\n    await flushMicrotasks();\n\n    fireEvent.pointerMove(element, {\n      pointerId: 1,\n      clientX: 0,\n      clientY: 0,\n      bubbles: true,\n      movementX: 0,\n      movementY: 0,\n    });\n\n    await flushMicrotasks();\n\n    fireEvent.pointerMove(element, {\n      pointerId: 1,\n      clientX: 50,\n      clientY: 0,\n      bubbles: true,\n      movementX: 50,\n      movementY: 0,\n    });\n\n    await flushMicrotasks();\n\n    const progress = onProgress.mock.calls.at(-1)?.[0];\n    expect(progress).toBeCloseTo(0.25, 2);\n  });\n\n  it('continues firing onProgress when swipe progress is clamped', async () => {\n    const onProgress = vi.fn();\n    await render(<SwipeProgressBox onProgress={onProgress} />);\n    const element = screen.getByTestId('progress');\n\n    const widthDescriptor = Object.getOwnPropertyDescriptor(element, 'offsetWidth');\n    if (!widthDescriptor || widthDescriptor.configurable) {\n      Object.defineProperty(element, 'offsetWidth', { value: 200, configurable: true });\n    }\n\n    fireEvent.pointerDown(element, {\n      button: 0,\n      buttons: 1,\n      pointerId: 1,\n      clientX: 0,\n      clientY: 0,\n      bubbles: true,\n      pointerType: 'mouse',\n      movementX: 0,\n      movementY: 0,\n    });\n\n    await flushMicrotasks();\n\n    // Baseline move.\n    fireEvent.pointerMove(element, {\n      pointerId: 1,\n      clientX: 0,\n      clientY: 0,\n      bubbles: true,\n      movementX: 0,\n      movementY: 0,\n    });\n\n    await flushMicrotasks();\n\n    fireEvent.pointerMove(element, {\n      pointerId: 1,\n      clientX: 50,\n      clientY: 0,\n      bubbles: true,\n      movementX: 50,\n      movementY: 0,\n    });\n\n    await flushMicrotasks();\n\n    const callsAfterForward = onProgress.mock.calls.length;\n\n    // Move past the starting point in the opposite direction; progress is clamped to 0.\n    fireEvent.pointerMove(element, {\n      pointerId: 1,\n      clientX: -10,\n      clientY: 0,\n      bubbles: true,\n      movementX: -60,\n      movementY: 0,\n    });\n\n    await flushMicrotasks();\n\n    const callsAfterReverse = onProgress.mock.calls.length;\n    expect(callsAfterReverse).toBeGreaterThan(callsAfterForward);\n\n    fireEvent.pointerMove(element, {\n      pointerId: 1,\n      clientX: -20,\n      clientY: 0,\n      bubbles: true,\n      movementX: -10,\n      movementY: 0,\n    });\n\n    await flushMicrotasks();\n\n    expect(onProgress.mock.calls.length).toBeGreaterThan(callsAfterReverse);\n    expect(onProgress.mock.calls.at(-1)?.[0]).toBe(0);\n  });\n\n  it('applies exponential damping for opposite-direction movement', async () => {\n    await render(<SwipeBox />);\n    const element = screen.getByTestId('el');\n\n    fireEvent.pointerDown(element, {\n      button: 0,\n      buttons: 1,\n      pointerId: 1,\n      clientX: 0,\n      clientY: 100,\n      bubbles: true,\n      pointerType: 'mouse',\n      movementX: 0,\n      movementY: 0,\n    });\n\n    await flushMicrotasks();\n    expect(element.style.transition).toBe('none');\n\n    fireEvent.pointerMove(element, {\n      pointerId: 1,\n      clientX: 0,\n      clientY: 100,\n      bubbles: true,\n      movementX: 0,\n      movementY: 0,\n    });\n\n    await flushMicrotasks();\n\n    fireEvent.pointerMove(element, {\n      pointerId: 1,\n      clientX: 0,\n      clientY: 50,\n      bubbles: true,\n      movementX: 0,\n      movementY: -50,\n    });\n\n    await flushMicrotasks();\n\n    expect(element.style.getPropertyValue('--y')).not.toBe('0px');\n  });\n\n  it('respects custom swipeThreshold', async () => {\n    const onDismiss = vi.fn();\n\n    function SwipeBoxThreshold() {\n      const ref = React.useRef<HTMLDivElement>(null);\n      const swipe = useSwipeDismiss({\n        enabled: true,\n        directions: ['down'],\n        elementRef: ref,\n        movementCssVars: { x: '--x', y: '--y' },\n        swipeThreshold: 10,\n        onDismiss,\n      });\n\n      return (\n        <div\n          data-testid=\"el\"\n          ref={ref}\n          style={swipe.getDragStyles()}\n          {...swipe.getPointerProps()}\n        />\n      );\n    }\n\n    await render(<SwipeBoxThreshold />);\n    const element = screen.getByTestId('el');\n\n    fireEvent.pointerDown(element, {\n      button: 0,\n      buttons: 1,\n      pointerId: 1,\n      clientX: 0,\n      clientY: 0,\n      bubbles: true,\n      pointerType: 'mouse',\n      movementX: 0,\n      movementY: 0,\n    });\n\n    await flushMicrotasks();\n\n    // Baseline move.\n    fireEvent.pointerMove(element, {\n      pointerId: 1,\n      clientX: 0,\n      clientY: 0,\n      bubbles: true,\n      movementX: 0,\n      movementY: 0,\n    });\n\n    await flushMicrotasks();\n\n    // Move beyond the custom 10px threshold.\n    fireEvent.pointerMove(element, {\n      pointerId: 1,\n      clientX: 0,\n      clientY: 20,\n      bubbles: true,\n      movementX: 0,\n      movementY: 20,\n    });\n\n    await flushMicrotasks();\n\n    fireEvent.pointerUp(element, {\n      pointerId: 1,\n      clientX: 0,\n      clientY: 20,\n      bubbles: true,\n    });\n\n    await flushMicrotasks();\n\n    expect(onDismiss).toHaveBeenCalled();\n  });\n\n  it('fires onSwipingChange on start and end', async () => {\n    const onSwipingChange = vi.fn();\n\n    function SwipeBoxSwipingChange() {\n      const ref = React.useRef<HTMLDivElement>(null);\n      const swipe = useSwipeDismiss({\n        enabled: true,\n        directions: ['down'],\n        elementRef: ref,\n        movementCssVars: { x: '--x', y: '--y' },\n        onSwipingChange,\n      });\n\n      return (\n        <div\n          data-testid=\"swiping\"\n          ref={ref}\n          style={swipe.getDragStyles()}\n          {...swipe.getPointerProps()}\n        />\n      );\n    }\n\n    await render(<SwipeBoxSwipingChange />);\n    const element = screen.getByTestId('swiping');\n\n    fireEvent.pointerDown(element, {\n      button: 0,\n      buttons: 1,\n      pointerId: 1,\n      clientX: 0,\n      clientY: 0,\n      bubbles: true,\n      pointerType: 'mouse',\n      movementX: 0,\n      movementY: 0,\n    });\n\n    await flushMicrotasks();\n\n    fireEvent.pointerUp(element, {\n      pointerId: 1,\n      clientX: 0,\n      clientY: 0,\n      bubbles: true,\n    });\n\n    await flushMicrotasks();\n\n    expect(onSwipingChange).toHaveBeenCalledTimes(2);\n    expect(onSwipingChange).toHaveBeenNthCalledWith(1, true);\n    expect(onSwipingChange).toHaveBeenLastCalledWith(false);\n  });\n\n  it('cancels pointer swipe when the primary mouse button is released without pointerup', async () => {\n    const onDismiss = vi.fn();\n    const onSwipingChange = vi.fn();\n\n    function SwipeBoxPointerCancel() {\n      const ref = React.useRef<HTMLDivElement>(null);\n      const swipe = useSwipeDismiss({\n        enabled: true,\n        directions: ['down'],\n        elementRef: ref,\n        movementCssVars: { x: '--x', y: '--y' },\n        onDismiss,\n        onSwipingChange,\n      });\n\n      return (\n        <div\n          data-testid=\"pointer-cancel\"\n          ref={ref}\n          style={swipe.getDragStyles()}\n          {...swipe.getPointerProps()}\n        />\n      );\n    }\n\n    await render(<SwipeBoxPointerCancel />);\n    const element = screen.getByTestId('pointer-cancel');\n\n    fireEvent.pointerDown(element, {\n      button: 0,\n      buttons: 1,\n      pointerId: 1,\n      clientX: 0,\n      clientY: 0,\n      bubbles: true,\n      pointerType: 'mouse',\n      movementX: 0,\n      movementY: 0,\n    });\n\n    await flushMicrotasks();\n\n    fireEvent.pointerMove(element, {\n      pointerId: 1,\n      buttons: 1,\n      clientX: 0,\n      clientY: 0,\n      bubbles: true,\n      pointerType: 'mouse',\n      movementX: 0,\n      movementY: 0,\n    });\n\n    await flushMicrotasks();\n\n    fireEvent.pointerMove(element, {\n      pointerId: 1,\n      buttons: 1,\n      clientX: 0,\n      clientY: 12,\n      bubbles: true,\n      pointerType: 'mouse',\n      movementX: 0,\n      movementY: 12,\n    });\n\n    await flushMicrotasks();\n\n    expect(onSwipingChange).toHaveBeenCalledWith(true);\n    expect(element.style.getPropertyValue('--y')).not.toBe('0px');\n\n    fireEvent.pointerMove(element, {\n      pointerId: 1,\n      buttons: 0,\n      clientX: 0,\n      clientY: 16,\n      bubbles: true,\n      pointerType: 'mouse',\n      movementX: 0,\n      movementY: 4,\n    });\n\n    await flushMicrotasks();\n\n    expect(onSwipingChange).toHaveBeenLastCalledWith(false);\n    expect(element.style.getPropertyValue('--y')).toBe('0px');\n    expect(onDismiss).not.toHaveBeenCalled();\n\n    fireEvent.pointerMove(element, {\n      pointerId: 1,\n      buttons: 0,\n      clientX: 0,\n      clientY: 40,\n      bubbles: true,\n      pointerType: 'mouse',\n      movementX: 0,\n      movementY: 24,\n    });\n\n    await flushMicrotasks();\n\n    expect(element.style.getPropertyValue('--y')).toBe('0px');\n    expect(onDismiss).not.toHaveBeenCalled();\n  });\n\n  it('resets swiping when touch ends over a scrollable descendant', async () => {\n    const onSwipingChange = vi.fn();\n\n    function SwipeBoxTouchScrollableEnd() {\n      const ref = React.useRef<HTMLDivElement>(null);\n      const swipe = useSwipeDismiss({\n        enabled: true,\n        directions: ['down'],\n        elementRef: ref,\n        movementCssVars: { x: '--x', y: '--y' },\n        onSwipingChange,\n      });\n\n      return (\n        <div\n          data-testid=\"touch-root\"\n          ref={ref}\n          style={swipe.getDragStyles()}\n          {...swipe.getTouchProps()}\n        >\n          <div data-testid=\"touch-scroll\" style={{ overflowY: 'auto', maxHeight: 40 }}>\n            <div style={{ height: 120 }} />\n          </div>\n        </div>\n      );\n    }\n\n    await render(<SwipeBoxTouchScrollableEnd />);\n\n    const root = screen.getByTestId('touch-root');\n    const scroll = screen.getByTestId('touch-scroll');\n\n    const scrollHeightDescriptor = Object.getOwnPropertyDescriptor(scroll, 'scrollHeight');\n    if (!scrollHeightDescriptor || scrollHeightDescriptor.configurable) {\n      Object.defineProperty(scroll, 'scrollHeight', { value: 120, configurable: true });\n    }\n\n    const clientHeightDescriptor = Object.getOwnPropertyDescriptor(scroll, 'clientHeight');\n    if (!clientHeightDescriptor || clientHeightDescriptor.configurable) {\n      Object.defineProperty(scroll, 'clientHeight', { value: 40, configurable: true });\n    }\n\n    fireEvent.touchStart(root, {\n      touches: [\n        createTouch(root, {\n          clientX: 0,\n          clientY: 0,\n        }),\n      ],\n    });\n\n    await flushMicrotasks();\n\n    fireEvent.touchMove(root, {\n      touches: [\n        createTouch(root, {\n          clientX: 0,\n          clientY: 20,\n        }),\n      ],\n    });\n\n    await flushMicrotasks();\n\n    fireEvent.touchEnd(scroll, {\n      changedTouches: [\n        createTouch(scroll, {\n          clientX: 0,\n          clientY: 20,\n        }),\n      ],\n    });\n\n    await flushMicrotasks();\n\n    expect(onSwipingChange).toHaveBeenCalledTimes(2);\n    expect(onSwipingChange).toHaveBeenNthCalledWith(1, true);\n    expect(onSwipingChange).toHaveBeenLastCalledWith(false);\n    expect(root.style.getPropertyValue('--y')).toBe('0px');\n  });\n\n  it('allows onRelease to override dismissal', async () => {\n    const onDismiss = vi.fn();\n    const onRelease = vi.fn(() => false);\n\n    function SwipeBoxReleaseOverride() {\n      const ref = React.useRef<HTMLDivElement>(null);\n      const swipe = useSwipeDismiss({\n        enabled: true,\n        directions: ['down'],\n        elementRef: ref,\n        movementCssVars: { x: '--x', y: '--y' },\n        swipeThreshold: 10,\n        onDismiss,\n        onRelease,\n      });\n\n      return (\n        <div\n          data-testid=\"release\"\n          ref={ref}\n          style={swipe.getDragStyles()}\n          {...swipe.getPointerProps()}\n        />\n      );\n    }\n\n    await render(<SwipeBoxReleaseOverride />);\n    const element = screen.getByTestId('release');\n\n    fireEvent.pointerDown(element, {\n      button: 0,\n      buttons: 1,\n      pointerId: 1,\n      clientX: 0,\n      clientY: 0,\n      bubbles: true,\n      pointerType: 'mouse',\n      movementX: 0,\n      movementY: 0,\n    });\n\n    await flushMicrotasks();\n\n    fireEvent.pointerMove(element, {\n      pointerId: 1,\n      clientX: 0,\n      clientY: 0,\n      bubbles: true,\n      movementX: 0,\n      movementY: 0,\n    });\n\n    await flushMicrotasks();\n\n    fireEvent.pointerMove(element, {\n      pointerId: 1,\n      clientX: 0,\n      clientY: 20,\n      bubbles: true,\n      movementX: 0,\n      movementY: 20,\n    });\n\n    await flushMicrotasks();\n\n    fireEvent.pointerUp(element, {\n      pointerId: 1,\n      clientX: 0,\n      clientY: 20,\n      bubbles: true,\n    });\n\n    await flushMicrotasks();\n\n    expect(onRelease).toHaveBeenCalled();\n    expect(onDismiss).not.toHaveBeenCalled();\n  });\n\n  it.skipIf(!isJSDOM)('provides swipe velocity on release', async () => {\n    const onRelease = vi.fn();\n\n    function SwipeBoxReleaseVelocity() {\n      const ref = React.useRef<HTMLDivElement>(null);\n      const swipe = useSwipeDismiss({\n        enabled: true,\n        directions: ['right'],\n        elementRef: ref,\n        movementCssVars: { x: '--x', y: '--y' },\n        onRelease,\n      });\n\n      return (\n        <div\n          data-testid=\"release-velocity\"\n          ref={ref}\n          style={swipe.getDragStyles()}\n          {...swipe.getPointerProps()}\n        />\n      );\n    }\n\n    vi.useFakeTimers();\n    try {\n      vi.setSystemTime(new Date(1000));\n      await render(<SwipeBoxReleaseVelocity />);\n      const element = screen.getByTestId('release-velocity');\n\n      fireEvent.pointerDown(element, {\n        button: 0,\n        buttons: 1,\n        pointerId: 1,\n        clientX: 0,\n        clientY: 0,\n        bubbles: true,\n        pointerType: 'mouse',\n        movementX: 0,\n        movementY: 0,\n        timeStamp: 1000,\n      });\n\n      await flushMicrotasks();\n\n      vi.setSystemTime(new Date(1100));\n      fireEvent.pointerMove(element, {\n        pointerId: 1,\n        clientX: 0,\n        clientY: 0,\n        bubbles: true,\n        movementX: 0,\n        movementY: 0,\n        timeStamp: 1100,\n      });\n\n      await flushMicrotasks();\n\n      vi.setSystemTime(new Date(1200));\n      fireEvent.pointerMove(element, {\n        pointerId: 1,\n        clientX: 50,\n        clientY: 0,\n        bubbles: true,\n        movementX: 50,\n        movementY: 0,\n        timeStamp: 1200,\n      });\n\n      await flushMicrotasks();\n\n      vi.setSystemTime(new Date(1300));\n      fireEvent.pointerUp(element, {\n        pointerId: 1,\n        clientX: 50,\n        clientY: 0,\n        bubbles: true,\n        timeStamp: 1300,\n      });\n\n      await flushMicrotasks();\n\n      const details = onRelease.mock.calls[0]?.[0];\n      expect(details?.velocityX).toBeCloseTo(0.25, 2);\n      expect(details?.velocityY).toBeCloseTo(0, 2);\n    } finally {\n      vi.useRealTimers();\n    }\n  });\n\n  it.skipIf(!isJSDOM)('provides release velocity from the latest swipe movement', async () => {\n    const onRelease = vi.fn();\n\n    function SwipeBoxReleaseVelocity() {\n      const ref = React.useRef<HTMLDivElement>(null);\n      const swipe = useSwipeDismiss({\n        enabled: true,\n        directions: ['right'],\n        elementRef: ref,\n        movementCssVars: { x: '--x', y: '--y' },\n        onRelease,\n      });\n\n      return (\n        <div\n          data-testid=\"release-velocity-latest\"\n          ref={ref}\n          style={swipe.getDragStyles()}\n          {...swipe.getPointerProps()}\n        />\n      );\n    }\n\n    vi.useFakeTimers();\n    try {\n      vi.setSystemTime(new Date(1000));\n      await render(<SwipeBoxReleaseVelocity />);\n      const element = screen.getByTestId('release-velocity-latest');\n\n      fireEvent.pointerDown(element, {\n        button: 0,\n        buttons: 1,\n        pointerId: 1,\n        clientX: 0,\n        clientY: 0,\n        bubbles: true,\n        pointerType: 'mouse',\n        movementX: 0,\n        movementY: 0,\n        timeStamp: 1000,\n      });\n\n      await flushMicrotasks();\n\n      vi.setSystemTime(new Date(1100));\n      fireEvent.pointerMove(element, {\n        pointerId: 1,\n        clientX: 0,\n        clientY: 0,\n        bubbles: true,\n        movementX: 0,\n        movementY: 0,\n        timeStamp: 1100,\n      });\n\n      await flushMicrotasks();\n\n      vi.setSystemTime(new Date(1200));\n      fireEvent.pointerMove(element, {\n        pointerId: 1,\n        clientX: 50,\n        clientY: 0,\n        bubbles: true,\n        movementX: 50,\n        movementY: 0,\n        timeStamp: 1200,\n      });\n\n      await flushMicrotasks();\n\n      vi.setSystemTime(new Date(1216));\n      fireEvent.pointerMove(element, {\n        pointerId: 1,\n        clientX: 70,\n        clientY: 0,\n        bubbles: true,\n        movementX: 20,\n        movementY: 0,\n        timeStamp: 1216,\n      });\n\n      await flushMicrotasks();\n\n      vi.setSystemTime(new Date(1224));\n      fireEvent.pointerUp(element, {\n        pointerId: 1,\n        clientX: 70,\n        clientY: 0,\n        bubbles: true,\n        timeStamp: 1224,\n      });\n\n      await flushMicrotasks();\n\n      const details = onRelease.mock.calls[0]?.[0];\n      expect(details?.releaseVelocityX).toBeCloseTo(1.25, 2);\n      expect(details?.releaseVelocityY).toBeCloseTo(0, 2);\n    } finally {\n      vi.useRealTimers();\n    }\n  });\n\n  it.skipIf(!isJSDOM)('clamps short swipe durations when computing velocity', async () => {\n    const onRelease = vi.fn();\n\n    function SwipeBoxReleaseVelocity() {\n      const ref = React.useRef<HTMLDivElement>(null);\n      const swipe = useSwipeDismiss({\n        enabled: true,\n        directions: ['right'],\n        elementRef: ref,\n        movementCssVars: { x: '--x', y: '--y' },\n        onRelease,\n      });\n\n      return (\n        <div\n          data-testid=\"release-velocity-short\"\n          ref={ref}\n          style={swipe.getDragStyles()}\n          {...swipe.getPointerProps()}\n        />\n      );\n    }\n\n    vi.useFakeTimers();\n    try {\n      vi.setSystemTime(new Date(1000));\n      await render(<SwipeBoxReleaseVelocity />);\n      const element = screen.getByTestId('release-velocity-short');\n\n      fireEvent.pointerDown(element, {\n        button: 0,\n        buttons: 1,\n        pointerId: 1,\n        clientX: 0,\n        clientY: 0,\n        bubbles: true,\n        pointerType: 'mouse',\n        movementX: 0,\n        movementY: 0,\n        timeStamp: 1000,\n      });\n\n      await flushMicrotasks();\n\n      vi.setSystemTime(new Date(1005));\n      fireEvent.pointerMove(element, {\n        pointerId: 1,\n        clientX: 0,\n        clientY: 0,\n        bubbles: true,\n        movementX: 0,\n        movementY: 0,\n        timeStamp: 1005,\n      });\n\n      await flushMicrotasks();\n\n      vi.setSystemTime(new Date(1010));\n      fireEvent.pointerMove(element, {\n        pointerId: 1,\n        clientX: 30,\n        clientY: 0,\n        bubbles: true,\n        movementX: 30,\n        movementY: 0,\n        timeStamp: 1010,\n      });\n\n      await flushMicrotasks();\n\n      vi.setSystemTime(new Date(1015));\n      fireEvent.pointerUp(element, {\n        pointerId: 1,\n        clientX: 30,\n        clientY: 0,\n        bubbles: true,\n        timeStamp: 1015,\n      });\n\n      await flushMicrotasks();\n\n      const details = onRelease.mock.calls[0]?.[0];\n      expect(details?.velocityX).toBeCloseTo(0.6, 2);\n      expect(details?.velocityY).toBeCloseTo(0, 2);\n    } finally {\n      vi.useRealTimers();\n    }\n  });\n\n  it('ignores pointer interactions that were default prevented', async () => {\n    const onSwipeStart = vi.fn();\n\n    function SwipeBoxWithPreventedChild() {\n      const ref = React.useRef<HTMLDivElement>(null);\n      const swipeDismiss = useSwipeDismiss({\n        enabled: true,\n        directions: ['down'],\n        elementRef: ref,\n        movementCssVars: { x: '--x', y: '--y' },\n        onSwipeStart,\n      });\n\n      return (\n        <div\n          data-testid=\"root\"\n          ref={ref}\n          style={swipeDismiss.getDragStyles()}\n          {...swipeDismiss.getPointerProps()}\n        >\n          <div data-testid=\"child\" onPointerDown={(event) => event.preventDefault()} />\n        </div>\n      );\n    }\n\n    await render(<SwipeBoxWithPreventedChild />);\n\n    const root = screen.getByTestId('root');\n    const child = screen.getByTestId('child');\n\n    fireEvent.pointerDown(child, {\n      button: 0,\n      buttons: 1,\n      pointerId: 1,\n      clientX: 0,\n      clientY: 100,\n      bubbles: true,\n      pointerType: 'mouse',\n      movementX: 0,\n      movementY: 0,\n    });\n\n    await flushMicrotasks();\n\n    fireEvent.pointerMove(child, {\n      pointerId: 1,\n      clientX: 0,\n      clientY: 150,\n      bubbles: true,\n      movementX: 0,\n      movementY: 50,\n    });\n\n    await flushMicrotasks();\n\n    expect(onSwipeStart).not.toHaveBeenCalled();\n    expect(root.style.getPropertyValue('--y')).toBe('0px');\n  });\n});\n"
  },
  {
    "path": "packages/react/src/utils/useSwipeDismiss.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { ownerDocument } from '@base-ui/utils/owner';\nimport { contains, getTarget } from '../floating-ui-react/utils';\nimport { findScrollableTouchTarget, hasScrollableAncestor, type ScrollAxis } from './scrollable';\nimport { clamp } from './clamp';\nimport { getElementAtPoint } from './getElementAtPoint';\n\nexport type SwipeDirection = 'up' | 'down' | 'left' | 'right';\n\ntype SwipeDismissNativeEvent = PointerEvent | TouchEvent;\ntype SwipeDismissStartEvent = React.PointerEvent | React.TouchEvent;\ntype SwipeDismissMoveEvent = React.PointerEvent | React.TouchEvent;\ntype SwipeDismissEndEvent = React.PointerEvent | React.TouchEvent;\ntype SwipeProgressDetailsInternal = {\n  deltaX: number;\n  deltaY: number;\n  direction: SwipeDirection | undefined;\n};\n\nconst DEFAULT_SWIPE_THRESHOLD = 40;\nconst REVERSE_CANCEL_THRESHOLD = 10;\nconst MIN_DRAG_THRESHOLD = 1;\nconst MIN_VELOCITY_DURATION_MS = 50;\nconst MIN_RELEASE_VELOCITY_DURATION_MS = 16;\nconst MAX_RELEASE_VELOCITY_AGE_MS = 80;\nconst DEFAULT_IGNORE_SELECTOR = 'button,a,input,select,textarea,label,[role=\"button\"]';\n\nexport function getDisplacement(direction: SwipeDirection, deltaX: number, deltaY: number) {\n  switch (direction) {\n    case 'up':\n      return -deltaY;\n    case 'down':\n      return deltaY;\n    case 'left':\n      return -deltaX;\n    case 'right':\n      return deltaX;\n    default:\n      return 0;\n  }\n}\n\nexport function getElementTransform(element: HTMLElement) {\n  const computedStyle = window.getComputedStyle(element);\n  const transform = computedStyle.transform;\n  let translateX = 0;\n  let translateY = 0;\n  let scale = 1;\n\n  if (transform && transform !== 'none') {\n    const matrix = transform.match(/matrix(?:3d)?\\(([^)]+)\\)/);\n    if (matrix) {\n      const values = matrix[1].split(', ').map(parseFloat);\n      if (values.length === 6) {\n        translateX = values[4];\n        translateY = values[5];\n        scale = Math.sqrt(values[0] * values[0] + values[1] * values[1]);\n      } else if (values.length === 16) {\n        translateX = values[12];\n        translateY = values[13];\n        scale = values[0];\n      }\n    }\n  }\n\n  return { x: translateX, y: translateY, scale };\n}\n\nfunction getValidTimeStamp(timeStamp: number): number | null {\n  return Number.isFinite(timeStamp) && timeStamp > 0 ? timeStamp : null;\n}\n\nfunction hasPrimaryMouseButton(buttons: number): boolean {\n  return buttons % 2 === 1;\n}\n\nfunction safelyChangePointerCapture(\n  element: HTMLElement,\n  pointerId: number,\n  method: 'setPointerCapture' | 'releasePointerCapture',\n) {\n  const pointerCaptureMethod = element[method];\n  if (typeof pointerCaptureMethod !== 'function') {\n    return;\n  }\n\n  try {\n    pointerCaptureMethod.call(element, pointerId);\n  } catch (error) {\n    if (error && typeof error === 'object' && 'name' in error && error.name === 'NotFoundError') {\n      return;\n    }\n    throw error;\n  }\n}\n\nexport function useSwipeDismiss(options: UseSwipeDismissOptions): UseSwipeDismissReturnValue {\n  const {\n    enabled,\n    directions,\n    elementRef,\n    movementCssVars,\n    canStart,\n    ignoreSelectorWhenTouch = true,\n    ignoreScrollableAncestors = false,\n    swipeThreshold: swipeThresholdProp,\n    onDismiss,\n    onProgress,\n    onSwipeStart,\n    onRelease,\n    onSwipingChange,\n    trackDrag = true,\n  } = options;\n\n  const ignoreSelector = DEFAULT_IGNORE_SELECTOR;\n  const primaryDirection = directions.length === 1 ? directions[0] : undefined;\n\n  const swipeThresholdDefault = Math.max(\n    0,\n    typeof swipeThresholdProp === 'number' ? swipeThresholdProp : DEFAULT_SWIPE_THRESHOLD,\n  );\n\n  const allowLeft = directions.includes('left');\n  const allowRight = directions.includes('right');\n  const allowUp = directions.includes('up');\n  const allowDown = directions.includes('down');\n  const hasHorizontal = allowLeft || allowRight;\n  const hasVertical = allowUp || allowDown;\n\n  const scrollAxes = React.useMemo((): ScrollAxis[] => {\n    const axes: ScrollAxis[] = [];\n    if (hasVertical) {\n      axes.push('vertical');\n    }\n    if (hasHorizontal) {\n      axes.push('horizontal');\n    }\n    return axes;\n  }, [hasHorizontal, hasVertical]);\n\n  const [currentSwipeDirection, setCurrentSwipeDirection] = React.useState<\n    SwipeDirection | undefined\n  >(undefined);\n  const [isSwiping, setIsSwiping] = React.useState(false);\n  const [isRealSwipe, setIsRealSwipe] = React.useState(false);\n  const [dragDismissed, setDragDismissed] = React.useState(false);\n  const [dragOffset, setDragOffset] = React.useState({ x: 0, y: 0 });\n  const [initialTransform, setInitialTransform] = React.useState({ x: 0, y: 0, scale: 1 });\n  const [lockedDirection, setLockedDirection] = React.useState<'horizontal' | 'vertical' | null>(\n    null,\n  );\n\n  const dragStartPosRef = React.useRef({ x: 0, y: 0 });\n  const dragOffsetRef = React.useRef({ x: 0, y: 0 });\n  const lastMovePosRef = React.useRef<{ x: number; y: number } | null>(null);\n  const initialTransformRef = React.useRef({ x: 0, y: 0, scale: 1 });\n  const intendedSwipeDirectionRef = React.useRef<SwipeDirection | undefined>(undefined);\n  const maxSwipeDisplacementRef = React.useRef(0);\n  const cancelledSwipeRef = React.useRef(false);\n  const swipeCancelBaselineRef = React.useRef({ x: 0, y: 0 });\n  const isFirstPointerMoveRef = React.useRef(false);\n  const pendingSwipeRef = React.useRef(false);\n  const pendingSwipeStartPosRef = React.useRef<{ x: number; y: number } | null>(null);\n  const swipeFromScrollableRef = React.useRef(false);\n  const sawPrimaryButtonsOnMoveRef = React.useRef(false);\n  const elementSizeRef = React.useRef({ width: 0, height: 0 });\n  const swipeProgressRef = React.useRef(0);\n  const swipeThresholdRef = React.useRef(swipeThresholdDefault);\n  const swipeStartTimeRef = React.useRef<number | null>(null);\n  const lastDragSampleRef = React.useRef<{ x: number; y: number; time: number } | null>(null);\n  const lastDragVelocityRef = React.useRef({ x: 0, y: 0 });\n  const lastProgressDetailsRef = React.useRef<SwipeProgressDetailsInternal | null>(null);\n  const isSwipingRef = React.useRef(false);\n\n  const setSwiping = useStableCallback((nextSwiping: boolean) => {\n    if (isSwipingRef.current === nextSwiping) {\n      return;\n    }\n\n    isSwipingRef.current = nextSwiping;\n    setIsSwiping(nextSwiping);\n    onSwipingChange?.(nextSwiping);\n  });\n\n  function resolveSwipeThreshold(direction: SwipeDirection | undefined) {\n    if (!direction) {\n      return;\n    }\n\n    if (typeof swipeThresholdProp !== 'function') {\n      swipeThresholdRef.current = swipeThresholdDefault;\n      return;\n    }\n\n    const element = elementRef.current;\n    if (!element) {\n      return;\n    }\n\n    const value = swipeThresholdProp({ element, direction });\n    swipeThresholdRef.current = Math.max(0, value);\n  }\n\n  const updateSwipeProgress = useStableCallback(\n    (progress: number, details?: SwipeProgressDetailsInternal) => {\n      const nextProgress = Number.isFinite(progress) ? clamp(progress, 0, 1) : 0;\n      const progressChanged = nextProgress !== swipeProgressRef.current;\n      let detailsChanged = false;\n\n      if (details) {\n        const lastDetails = lastProgressDetailsRef.current;\n        detailsChanged =\n          !lastDetails ||\n          lastDetails.deltaX !== details.deltaX ||\n          lastDetails.deltaY !== details.deltaY ||\n          lastDetails.direction !== details.direction;\n      }\n\n      if (!progressChanged && !detailsChanged) {\n        return;\n      }\n\n      swipeProgressRef.current = nextProgress;\n      if (details) {\n        lastProgressDetailsRef.current = details;\n      } else if (progressChanged) {\n        lastProgressDetailsRef.current = null;\n      }\n      onProgress?.(nextProgress, details);\n    },\n  );\n\n  function recordDragSample(offset: { x: number; y: number }, timeStamp: number | null) {\n    if (timeStamp === null) {\n      return;\n    }\n\n    const lastSample = lastDragSampleRef.current;\n    if (lastSample && timeStamp > lastSample.time) {\n      const durationMs = Math.max(timeStamp - lastSample.time, MIN_RELEASE_VELOCITY_DURATION_MS);\n      lastDragVelocityRef.current = {\n        x: (offset.x - lastSample.x) / durationMs,\n        y: (offset.y - lastSample.y) / durationMs,\n      };\n    }\n\n    lastDragSampleRef.current = { x: offset.x, y: offset.y, time: timeStamp };\n  }\n\n  const reset = React.useCallback(() => {\n    setCurrentSwipeDirection(undefined);\n    setSwiping(false);\n    setIsRealSwipe(false);\n    setDragDismissed(false);\n    setDragOffset({ x: 0, y: 0 });\n    setInitialTransform({ x: 0, y: 0, scale: 1 });\n    setLockedDirection(null);\n    updateSwipeProgress(0);\n\n    swipeThresholdRef.current = swipeThresholdDefault;\n    dragStartPosRef.current = { x: 0, y: 0 };\n    dragOffsetRef.current = { x: 0, y: 0 };\n    initialTransformRef.current = { x: 0, y: 0, scale: 1 };\n    intendedSwipeDirectionRef.current = undefined;\n    maxSwipeDisplacementRef.current = 0;\n    cancelledSwipeRef.current = false;\n    swipeCancelBaselineRef.current = { x: 0, y: 0 };\n    isFirstPointerMoveRef.current = false;\n    lastMovePosRef.current = null;\n    pendingSwipeRef.current = false;\n    pendingSwipeStartPosRef.current = null;\n    swipeFromScrollableRef.current = false;\n    sawPrimaryButtonsOnMoveRef.current = false;\n    elementSizeRef.current = { width: 0, height: 0 };\n    swipeStartTimeRef.current = null;\n    lastDragSampleRef.current = null;\n    lastDragVelocityRef.current = { x: 0, y: 0 };\n    lastProgressDetailsRef.current = null;\n  }, [setSwiping, swipeThresholdDefault, updateSwipeProgress]);\n\n  React.useEffect(() => {\n    if (typeof swipeThresholdProp !== 'function') {\n      swipeThresholdRef.current = swipeThresholdDefault;\n    }\n  }, [swipeThresholdDefault, swipeThresholdProp]);\n\n  function getPrimaryPointerPosition(\n    event: SwipeDismissStartEvent | SwipeDismissMoveEvent | SwipeDismissEndEvent,\n  ) {\n    if ('touches' in event) {\n      const touch = event.touches[0];\n      return touch ? { x: touch.clientX, y: touch.clientY } : null;\n    }\n\n    return { x: event.clientX, y: event.clientY };\n  }\n\n  function isTouchLikeEvent(\n    event: SwipeDismissStartEvent | SwipeDismissMoveEvent | SwipeDismissEndEvent,\n  ) {\n    if ('touches' in event) {\n      return true;\n    }\n    return event.pointerType === 'touch';\n  }\n\n  function getTargetAtPoint(position: { x: number; y: number }, nativeEvent: Event) {\n    const doc = ownerDocument(elementRef.current);\n    const elementAtPoint = getElementAtPoint(doc, position.x, position.y);\n    const target = elementAtPoint ?? getTarget(nativeEvent);\n    return target as HTMLElement | null;\n  }\n\n  function findGestureScrollableTouchTarget(\n    target: EventTarget | null,\n    root: HTMLElement,\n  ): HTMLElement | null {\n    if (hasHorizontal && !hasVertical) {\n      return findScrollableTouchTarget(target, root, 'horizontal');\n    }\n\n    if (hasVertical && !hasHorizontal) {\n      return findScrollableTouchTarget(target, root, 'vertical');\n    }\n\n    return (\n      findScrollableTouchTarget(target, root, 'vertical') ??\n      findScrollableTouchTarget(target, root, 'horizontal')\n    );\n  }\n\n  function startSwipeAtPosition(\n    event: SwipeDismissStartEvent | SwipeDismissMoveEvent,\n    position: { x: number; y: number },\n    startOptions?: {\n      ignoreScrollableTarget?: boolean | undefined;\n      ignoreScrollableAncestors?: boolean | undefined;\n    },\n  ) {\n    swipeFromScrollableRef.current = false;\n    const touchLike = isTouchLikeEvent(event);\n    const target = getTargetAtPoint(position, event.nativeEvent);\n\n    const doc = ownerDocument(elementRef.current);\n    const body = doc.body;\n\n    const scrollableTarget =\n      touchLike && body ? findGestureScrollableTouchTarget(target, body) : null;\n    const ignoreScrollableTarget = startOptions?.ignoreScrollableTarget ?? false;\n    if (scrollableTarget && !ignoreScrollableTarget) {\n      return false;\n    }\n    swipeFromScrollableRef.current = Boolean(scrollableTarget && ignoreScrollableTarget);\n\n    const isInteractiveElement = target ? target.closest(ignoreSelector) : false;\n    if (isInteractiveElement && (!touchLike || ignoreSelectorWhenTouch)) {\n      return false;\n    }\n\n    const element = elementRef.current;\n    if (ignoreScrollableAncestors && element && target && scrollAxes.length > 0) {\n      const ignoreAncestors = startOptions?.ignoreScrollableAncestors ?? false;\n      if (!ignoreAncestors && hasScrollableAncestor(target, element, scrollAxes)) {\n        return false;\n      }\n    }\n\n    cancelledSwipeRef.current = false;\n    intendedSwipeDirectionRef.current = undefined;\n    maxSwipeDisplacementRef.current = 0;\n\n    dragStartPosRef.current = position;\n    swipeStartTimeRef.current = getValidTimeStamp(event.timeStamp);\n    swipeCancelBaselineRef.current = position;\n    lastMovePosRef.current = position;\n\n    if (element) {\n      elementSizeRef.current = { width: element.offsetWidth, height: element.offsetHeight };\n      resolveSwipeThreshold(primaryDirection);\n      const transform = getElementTransform(element);\n      initialTransformRef.current = transform;\n      dragOffsetRef.current = { x: transform.x, y: transform.y };\n      setInitialTransform(transform);\n      setDragOffset({ x: transform.x, y: transform.y });\n      recordDragSample({ x: transform.x, y: transform.y }, swipeStartTimeRef.current);\n\n      if (!('touches' in event)) {\n        safelyChangePointerCapture(element, event.pointerId, 'setPointerCapture');\n      }\n    }\n\n    onSwipeStart?.(event.nativeEvent as SwipeDismissNativeEvent);\n\n    setSwiping(true);\n    setIsRealSwipe(false);\n    setLockedDirection(null);\n    isFirstPointerMoveRef.current = true;\n    updateSwipeProgress(0);\n\n    return true;\n  }\n\n  function resetPendingSwipeState() {\n    clearPendingSwipeStartState();\n    swipeFromScrollableRef.current = false;\n    lastMovePosRef.current = null;\n  }\n\n  function clearPendingSwipeStartState() {\n    pendingSwipeRef.current = false;\n    pendingSwipeStartPosRef.current = null;\n  }\n\n  function cancelSwipeInteraction(event: React.PointerEvent) {\n    resetPendingSwipeState();\n\n    if (!isSwipingRef.current) {\n      return;\n    }\n\n    setSwiping(false);\n    setIsRealSwipe(false);\n    setLockedDirection(null);\n\n    const resolvedInitialTransform = trackDrag ? initialTransform : initialTransformRef.current;\n    dragOffsetRef.current = { x: resolvedInitialTransform.x, y: resolvedInitialTransform.y };\n    setDragOffset({ x: resolvedInitialTransform.x, y: resolvedInitialTransform.y });\n    setCurrentSwipeDirection(undefined);\n    sawPrimaryButtonsOnMoveRef.current = false;\n\n    const element = elementRef.current;\n    if (element) {\n      safelyChangePointerCapture(element, event.pointerId, 'releasePointerCapture');\n    }\n\n    updateSwipeProgress(0, {\n      deltaX: 0,\n      deltaY: 0,\n      direction: undefined,\n    });\n  }\n\n  function applyDirectionalDamping(deltaX: number, deltaY: number) {\n    const exponent = (value: number) => (value >= 0 ? value ** 0.5 : -(Math.abs(value) ** 0.5));\n    const dampAxis = (delta: number, allowNegative: boolean, allowPositive: boolean) => {\n      if (!allowNegative && delta < 0) {\n        return exponent(delta);\n      }\n      if (!allowPositive && delta > 0) {\n        return exponent(delta);\n      }\n      return delta;\n    };\n\n    const newDeltaX = hasHorizontal ? dampAxis(deltaX, allowLeft, allowRight) : exponent(deltaX);\n    const newDeltaY = hasVertical ? dampAxis(deltaY, allowUp, allowDown) : exponent(deltaY);\n\n    return { x: newDeltaX, y: newDeltaY };\n  }\n\n  function canSwipeFromScrollEdgeOnPendingMove(\n    scrollTarget: HTMLElement,\n    deltaX: number,\n    deltaY: number,\n  ): boolean | null {\n    const absDeltaX = Math.abs(deltaX);\n    const absDeltaY = Math.abs(deltaY);\n    const useVerticalAxis =\n      hasVertical && deltaY !== 0 && (!hasHorizontal || absDeltaY >= absDeltaX);\n\n    if (useVerticalAxis) {\n      const maxScrollTop = Math.max(0, scrollTarget.scrollHeight - scrollTarget.clientHeight);\n      const atTop = scrollTarget.scrollTop <= 0;\n      const atBottom = scrollTarget.scrollTop >= maxScrollTop;\n      const movingDown = deltaY > 0;\n      const movingUp = deltaY < 0;\n      const canSwipeDown = movingDown && atTop && allowDown;\n      const canSwipeUp = movingUp && atBottom && allowUp;\n      return canSwipeDown || canSwipeUp;\n    }\n\n    const useHorizontalAxis =\n      hasHorizontal && deltaX !== 0 && (!hasVertical || absDeltaX > absDeltaY);\n    if (useHorizontalAxis) {\n      const maxScrollLeft = Math.max(0, scrollTarget.scrollWidth - scrollTarget.clientWidth);\n      const atLeft = scrollTarget.scrollLeft <= 0;\n      const atRight = scrollTarget.scrollLeft >= maxScrollLeft;\n      const movingRight = deltaX > 0;\n      const movingLeft = deltaX < 0;\n      const canSwipeRight = movingRight && atLeft && allowRight;\n      const canSwipeLeft = movingLeft && atRight && allowLeft;\n      return canSwipeRight || canSwipeLeft;\n    }\n\n    return null;\n  }\n\n  const handleStart = useStableCallback((event: SwipeDismissStartEvent) => {\n    if (!enabled) {\n      return;\n    }\n\n    if (event.defaultPrevented || event.nativeEvent.defaultPrevented) {\n      return;\n    }\n\n    if (!('touches' in event) && event.button !== 0) {\n      return;\n    }\n\n    const startPos = getPrimaryPointerPosition(event);\n    if (!startPos) {\n      return;\n    }\n\n    pendingSwipeRef.current = true;\n    pendingSwipeStartPosRef.current = startPos;\n    swipeFromScrollableRef.current = false;\n    sawPrimaryButtonsOnMoveRef.current = false;\n\n    const allowedToStart = canStart\n      ? canStart(startPos, {\n          nativeEvent: event.nativeEvent as SwipeDismissNativeEvent,\n          direction: primaryDirection,\n        })\n      : true;\n    if (!allowedToStart) {\n      return;\n    }\n\n    if (startSwipeAtPosition(event, startPos)) {\n      clearPendingSwipeStartState();\n    }\n  });\n\n  function handleMoveCore(\n    event: SwipeDismissMoveEvent,\n    position: { x: number; y: number },\n    movement: { x: number; y: number },\n  ) {\n    if (!enabled || !isSwipingRef.current) {\n      return;\n    }\n\n    const target = getTarget(event.nativeEvent) as HTMLElement | null;\n    if (isTouchLikeEvent(event) && !swipeFromScrollableRef.current) {\n      const boundaryElement = event.currentTarget as HTMLElement;\n      if (findGestureScrollableTouchTarget(target, boundaryElement)) {\n        return;\n      }\n    }\n\n    if (!('touches' in event)) {\n      // Prevent text selection on Safari\n      event.preventDefault();\n    }\n\n    if (isFirstPointerMoveRef.current) {\n      // Adjust the starting position to the current position on the first move\n      // to account for the delay between pointerdown and the first pointermove on iOS.\n      dragStartPosRef.current = position;\n      const moveTime = getValidTimeStamp(event.timeStamp);\n      if (moveTime !== null) {\n        swipeStartTimeRef.current = moveTime;\n      }\n      isFirstPointerMoveRef.current = false;\n    }\n\n    const clientX = position.x;\n    const clientY = position.y;\n    const movementX = movement.x;\n    const movementY = movement.y;\n\n    if (\n      (movementY < 0 && clientY > swipeCancelBaselineRef.current.y) ||\n      (movementY > 0 && clientY < swipeCancelBaselineRef.current.y)\n    ) {\n      swipeCancelBaselineRef.current = { x: swipeCancelBaselineRef.current.x, y: clientY };\n    }\n\n    if (\n      (movementX < 0 && clientX > swipeCancelBaselineRef.current.x) ||\n      (movementX > 0 && clientX < swipeCancelBaselineRef.current.x)\n    ) {\n      swipeCancelBaselineRef.current = { x: clientX, y: swipeCancelBaselineRef.current.y };\n    }\n\n    const deltaX = clientX - dragStartPosRef.current.x;\n    const deltaY = clientY - dragStartPosRef.current.y;\n    const cancelDeltaY = clientY - swipeCancelBaselineRef.current.y;\n    const cancelDeltaX = clientX - swipeCancelBaselineRef.current.x;\n\n    if (!isRealSwipe) {\n      const movementDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);\n      if (movementDistance >= MIN_DRAG_THRESHOLD) {\n        setIsRealSwipe(true);\n        if (lockedDirection === null) {\n          if (hasHorizontal && hasVertical) {\n            const absX = Math.abs(deltaX);\n            const absY = Math.abs(deltaY);\n            setLockedDirection(absX > absY ? 'horizontal' : 'vertical');\n          }\n        }\n      }\n    }\n\n    let candidate: SwipeDirection | undefined;\n    if (!intendedSwipeDirectionRef.current) {\n      if (lockedDirection === 'vertical') {\n        if (deltaY > 0) {\n          candidate = 'down';\n        } else if (deltaY < 0) {\n          candidate = 'up';\n        }\n      } else if (lockedDirection === 'horizontal') {\n        if (deltaX > 0) {\n          candidate = 'right';\n        } else if (deltaX < 0) {\n          candidate = 'left';\n        }\n      } else if (Math.abs(deltaX) >= Math.abs(deltaY)) {\n        candidate = deltaX > 0 ? 'right' : 'left';\n      } else {\n        candidate = deltaY > 0 ? 'down' : 'up';\n      }\n\n      if (candidate) {\n        const isAllowed =\n          (candidate === 'left' && allowLeft) ||\n          (candidate === 'right' && allowRight) ||\n          (candidate === 'up' && allowUp) ||\n          (candidate === 'down' && allowDown);\n        if (isAllowed) {\n          intendedSwipeDirectionRef.current = candidate;\n          maxSwipeDisplacementRef.current = getDisplacement(candidate, deltaX, deltaY);\n          setCurrentSwipeDirection(candidate);\n          resolveSwipeThreshold(candidate);\n        }\n      }\n    } else {\n      const direction = intendedSwipeDirectionRef.current;\n      const currentDisplacement = getDisplacement(direction, cancelDeltaX, cancelDeltaY);\n      if (currentDisplacement > swipeThresholdRef.current) {\n        cancelledSwipeRef.current = false;\n        setCurrentSwipeDirection(direction);\n      } else if (\n        !(allowLeft && allowRight) &&\n        !(allowUp && allowDown) &&\n        maxSwipeDisplacementRef.current - currentDisplacement >= REVERSE_CANCEL_THRESHOLD\n      ) {\n        // Mark that a change-of-mind has occurred\n        cancelledSwipeRef.current = true;\n      }\n    }\n\n    const dampedDelta = applyDirectionalDamping(deltaX, deltaY);\n    let newOffsetX = initialTransformRef.current.x;\n    let newOffsetY = initialTransformRef.current.y;\n\n    if (lockedDirection === 'horizontal') {\n      if (hasHorizontal) {\n        newOffsetX += dampedDelta.x;\n      }\n    } else if (lockedDirection === 'vertical') {\n      if (hasVertical) {\n        newOffsetY += dampedDelta.y;\n      }\n    } else {\n      if (hasHorizontal) {\n        newOffsetX += dampedDelta.x;\n      }\n      if (hasVertical) {\n        newOffsetY += dampedDelta.y;\n      }\n    }\n\n    dragOffsetRef.current = { x: newOffsetX, y: newOffsetY };\n    if (trackDrag) {\n      setDragOffset({ x: newOffsetX, y: newOffsetY });\n    }\n    recordDragSample({ x: newOffsetX, y: newOffsetY }, getValidTimeStamp(event.timeStamp));\n    const dragDeltaX = newOffsetX - initialTransformRef.current.x;\n    const dragDeltaY = newOffsetY - initialTransformRef.current.y;\n    const swipeDirectionDetails = intendedSwipeDirectionRef.current;\n\n    const progressDirection = primaryDirection ?? intendedSwipeDirectionRef.current;\n    if (!progressDirection) {\n      updateSwipeProgress(0, {\n        deltaX: dragDeltaX,\n        deltaY: dragDeltaY,\n        direction: swipeDirectionDetails,\n      });\n      return;\n    }\n\n    const size =\n      progressDirection === 'left' || progressDirection === 'right'\n        ? elementSizeRef.current.width\n        : elementSizeRef.current.height;\n    const scale = initialTransformRef.current.scale || 1;\n    if (size <= 0 || scale <= 0) {\n      updateSwipeProgress(0, {\n        deltaX: dragDeltaX,\n        deltaY: dragDeltaY,\n        direction: swipeDirectionDetails,\n      });\n      return;\n    }\n\n    const progressDisplacement = getDisplacement(\n      progressDirection,\n      newOffsetX - initialTransformRef.current.x,\n      newOffsetY - initialTransformRef.current.y,\n    );\n    if (progressDisplacement <= 0) {\n      updateSwipeProgress(0, {\n        deltaX: dragDeltaX,\n        deltaY: dragDeltaY,\n        direction: swipeDirectionDetails,\n      });\n      return;\n    }\n\n    updateSwipeProgress(progressDisplacement / (size * scale), {\n      deltaX: dragDeltaX,\n      deltaY: dragDeltaY,\n      direction: swipeDirectionDetails,\n    });\n  }\n\n  const handleMove = useStableCallback((event: SwipeDismissMoveEvent) => {\n    const currentPos = getPrimaryPointerPosition(event);\n    if (!currentPos) {\n      return;\n    }\n\n    if (!('touches' in event)) {\n      const hasPrimaryButton = hasPrimaryMouseButton(event.buttons);\n      if (hasPrimaryButton) {\n        sawPrimaryButtonsOnMoveRef.current = true;\n      }\n\n      // Cancel the swipe if a non-primary button takes over the interaction.\n      // This handles cases where a right-click interrupts dragging.\n      const lostPrimaryButtonDuringSwipe =\n        event.buttons === 0 && sawPrimaryButtonsOnMoveRef.current;\n      if ((event.buttons !== 0 && !hasPrimaryButton) || lostPrimaryButtonDuringSwipe) {\n        cancelSwipeInteraction(event);\n        return;\n      }\n    }\n\n    if (!isSwiping && pendingSwipeRef.current) {\n      if (\n        !isTouchLikeEvent(event) &&\n        (event.defaultPrevented || event.nativeEvent.defaultPrevented)\n      ) {\n        resetPendingSwipeState();\n        return;\n      }\n\n      const allowedToStart = canStart\n        ? canStart(currentPos, {\n            nativeEvent: event.nativeEvent as SwipeDismissNativeEvent,\n            direction: primaryDirection,\n          })\n        : true;\n\n      if (allowedToStart) {\n        const pendingStartPos = pendingSwipeStartPosRef.current;\n        let ignoreScrollableOnStart = false;\n        if (isTouchLikeEvent(event)) {\n          const element = elementRef.current;\n          if (pendingStartPos && element) {\n            const target = getTargetAtPoint(currentPos, event.nativeEvent);\n            const doc = ownerDocument(element);\n            const body = doc.body;\n            const scrollTarget = body ? findGestureScrollableTouchTarget(target, body) : null;\n\n            if (\n              scrollTarget &&\n              (contains(element, scrollTarget) || contains(scrollTarget, element))\n            ) {\n              const deltaX = currentPos.x - pendingStartPos.x;\n              const deltaY = currentPos.y - pendingStartPos.y;\n              const canSwipeFromEdge = canSwipeFromScrollEdgeOnPendingMove(\n                scrollTarget,\n                deltaX,\n                deltaY,\n              );\n\n              if (canSwipeFromEdge === false) {\n                return;\n              }\n\n              if (canSwipeFromEdge === true) {\n                ignoreScrollableOnStart = true;\n              }\n            }\n          }\n        }\n\n        const started = startSwipeAtPosition(event, currentPos, {\n          ignoreScrollableTarget: ignoreScrollableOnStart,\n          ignoreScrollableAncestors: ignoreScrollableOnStart,\n        });\n        if (started) {\n          if (pendingStartPos && ignoreScrollableOnStart) {\n            // Preserve displacement between touchstart and the move that activates swipe from\n            // a scroll-edge so quick flicks can dismiss.\n            clearPendingSwipeStartState();\n            dragStartPosRef.current = pendingStartPos;\n            swipeCancelBaselineRef.current = pendingStartPos;\n            lastMovePosRef.current = pendingStartPos;\n            isFirstPointerMoveRef.current = false;\n          } else {\n            // Start from the current in-bounds position without dropping follow-up move\n            // displacement; this avoids jumps when entering from outside the element while\n            // keeping swipe tracking responsive on the next move.\n            clearPendingSwipeStartState();\n            swipeFromScrollableRef.current = false;\n          }\n        }\n      }\n    }\n\n    const previousPos = lastMovePosRef.current;\n    const movement =\n      previousPos === null\n        ? { x: 0, y: 0 }\n        : { x: currentPos.x - previousPos.x, y: currentPos.y - previousPos.y };\n\n    lastMovePosRef.current = currentPos;\n    handleMoveCore(event, currentPos, movement);\n  });\n\n  const handleEnd = useStableCallback((event: SwipeDismissEndEvent) => {\n    if (!enabled) {\n      return;\n    }\n\n    const resolvedDragOffset = dragOffsetRef.current;\n    const resolvedInitialTransform = initialTransformRef.current;\n    const releaseDeltaX = resolvedDragOffset.x - resolvedInitialTransform.x;\n    const releaseDeltaY = resolvedDragOffset.y - resolvedInitialTransform.y;\n    const progressDetails: SwipeProgressDetailsInternal = {\n      deltaX: releaseDeltaX,\n      deltaY: releaseDeltaY,\n      direction: currentSwipeDirection ?? intendedSwipeDirectionRef.current,\n    };\n\n    if (!isSwipingRef.current) {\n      resetPendingSwipeState();\n      updateSwipeProgress(0, progressDetails);\n      return;\n    }\n\n    setSwiping(false);\n    setIsRealSwipe(false);\n    setLockedDirection(null);\n    resetPendingSwipeState();\n    sawPrimaryButtonsOnMoveRef.current = false;\n\n    const element = elementRef.current;\n    if (element) {\n      if (!('touches' in event)) {\n        safelyChangePointerCapture(element, event.pointerId, 'releasePointerCapture');\n      }\n    }\n\n    const deltaX = releaseDeltaX;\n    const deltaY = releaseDeltaY;\n    const startTime = swipeStartTimeRef.current;\n    const endTime = getValidTimeStamp(event.timeStamp);\n    const durationMs =\n      startTime !== null && endTime !== null && endTime > startTime ? endTime - startTime : 0;\n    const velocityDurationMs = durationMs > 0 ? Math.max(durationMs, MIN_VELOCITY_DURATION_MS) : 0;\n    const velocityX = velocityDurationMs > 0 ? deltaX / velocityDurationMs : 0;\n    const velocityY = velocityDurationMs > 0 ? deltaY / velocityDurationMs : 0;\n    let releaseVelocityX = lastDragVelocityRef.current.x;\n    let releaseVelocityY = lastDragVelocityRef.current.y;\n    const lastSample = lastDragSampleRef.current;\n    if (lastSample && endTime !== null && endTime >= lastSample.time) {\n      const ageMs = endTime - lastSample.time;\n      if (ageMs <= MAX_RELEASE_VELOCITY_AGE_MS) {\n        const sampleDurationMs = Math.max(ageMs, MIN_RELEASE_VELOCITY_DURATION_MS);\n        const deltaFromLastSampleX = resolvedDragOffset.x - lastSample.x;\n        const deltaFromLastSampleY = resolvedDragOffset.y - lastSample.y;\n        const sampleVelocityX = deltaFromLastSampleX / sampleDurationMs;\n        const sampleVelocityY = deltaFromLastSampleY / sampleDurationMs;\n        if (sampleVelocityX !== 0) {\n          releaseVelocityX = sampleVelocityX;\n        }\n        if (sampleVelocityY !== 0) {\n          releaseVelocityY = sampleVelocityY;\n        }\n      } else {\n        releaseVelocityX = 0;\n        releaseVelocityY = 0;\n      }\n    }\n\n    const releaseDecision = onRelease?.({\n      event: event.nativeEvent as SwipeDismissNativeEvent,\n      direction: currentSwipeDirection ?? intendedSwipeDirectionRef.current,\n      deltaX,\n      deltaY,\n      velocityX,\n      velocityY,\n      releaseVelocityX,\n      releaseVelocityY,\n    });\n    const hasReleaseDecision = typeof releaseDecision === 'boolean';\n\n    if (cancelledSwipeRef.current && !hasReleaseDecision) {\n      dragOffsetRef.current = { x: resolvedInitialTransform.x, y: resolvedInitialTransform.y };\n      setDragOffset({ x: resolvedInitialTransform.x, y: resolvedInitialTransform.y });\n      setCurrentSwipeDirection(undefined);\n      updateSwipeProgress(0, progressDetails);\n      return;\n    }\n\n    let shouldClose = false;\n    let dismissDirection: SwipeDirection | undefined;\n\n    if (hasReleaseDecision) {\n      shouldClose = releaseDecision;\n      dismissDirection =\n        currentSwipeDirection ?? intendedSwipeDirectionRef.current ?? primaryDirection;\n    } else {\n      for (const direction of directions) {\n        switch (direction) {\n          case 'right':\n            if (deltaX > swipeThresholdRef.current) {\n              shouldClose = true;\n              dismissDirection = 'right';\n            }\n            break;\n          case 'left':\n            if (deltaX < -swipeThresholdRef.current) {\n              shouldClose = true;\n              dismissDirection = 'left';\n            }\n            break;\n          case 'down':\n            if (deltaY > swipeThresholdRef.current) {\n              shouldClose = true;\n              dismissDirection = 'down';\n            }\n            break;\n          case 'up':\n            if (deltaY < -swipeThresholdRef.current) {\n              shouldClose = true;\n              dismissDirection = 'up';\n            }\n            break;\n          default:\n            break;\n        }\n        if (shouldClose) {\n          break;\n        }\n      }\n    }\n\n    if (shouldClose && dismissDirection) {\n      setCurrentSwipeDirection(dismissDirection);\n      setDragDismissed(true);\n      onDismiss?.(event.nativeEvent as SwipeDismissNativeEvent, { direction: dismissDirection });\n    } else {\n      dragOffsetRef.current = { x: resolvedInitialTransform.x, y: resolvedInitialTransform.y };\n      setDragOffset({ x: resolvedInitialTransform.x, y: resolvedInitialTransform.y });\n      setCurrentSwipeDirection(undefined);\n      updateSwipeProgress(0, progressDetails);\n    }\n  });\n\n  const getDragStyles = React.useCallback((): React.CSSProperties => {\n    const resolvedDragOffset = trackDrag ? dragOffset : dragOffsetRef.current;\n    const resolvedInitialTransform = trackDrag ? initialTransform : initialTransformRef.current;\n\n    if (\n      !isSwiping &&\n      resolvedDragOffset.x === resolvedInitialTransform.x &&\n      resolvedDragOffset.y === resolvedInitialTransform.y &&\n      !dragDismissed\n    ) {\n      return {\n        [movementCssVars.x]: '0px',\n        [movementCssVars.y]: '0px',\n      } as React.CSSProperties;\n    }\n\n    const deltaX = resolvedDragOffset.x - resolvedInitialTransform.x;\n    const deltaY = resolvedDragOffset.y - resolvedInitialTransform.y;\n\n    return {\n      transition: isSwiping ? 'none' : undefined,\n      // While swiping, freeze the element at its current visual transform so it doesn't snap to the\n      // end position.\n      transform: isSwiping\n        ? `translateX(${resolvedDragOffset.x}px) translateY(${resolvedDragOffset.y}px) scale(${resolvedInitialTransform.scale})`\n        : undefined,\n      [movementCssVars.x]: `${deltaX}px`,\n      [movementCssVars.y]: `${deltaY}px`,\n    } as React.CSSProperties;\n  }, [dragDismissed, dragOffset, initialTransform, isSwiping, movementCssVars, trackDrag]);\n\n  const getPointerProps = React.useCallback(() => {\n    if (!enabled) {\n      return {};\n    }\n\n    return {\n      onPointerDown: handleStart,\n      onPointerMove: handleMove,\n      onPointerUp: handleEnd,\n      onPointerCancel: handleEnd,\n    } as const;\n  }, [enabled, handleEnd, handleMove, handleStart]);\n\n  const getTouchProps = React.useCallback(() => {\n    if (!enabled) {\n      return {};\n    }\n\n    return {\n      onTouchStart: handleStart,\n      onTouchMove: handleMove,\n      onTouchEnd: handleEnd,\n      onTouchCancel: handleEnd,\n    } as const;\n  }, [enabled, handleEnd, handleMove, handleStart]);\n\n  return {\n    swiping: isSwiping,\n    swipeDirection: currentSwipeDirection,\n    dragDismissed,\n    getPointerProps,\n    getTouchProps,\n    getDragStyles,\n    reset,\n  };\n}\n\nexport interface UseSwipeDismissState {}\n\nexport interface UseSwipeDismissDetails {\n  nativeEvent: PointerEvent | TouchEvent;\n  direction: SwipeDirection | undefined;\n}\n\nexport type UseSwipeDismissProgressDetails = SwipeProgressDetailsInternal;\n\nexport interface UseSwipeDismissOptions {\n  enabled: boolean;\n  directions: SwipeDirection[];\n  elementRef: React.RefObject<HTMLElement | null>;\n  movementCssVars: { x: string; y: string };\n  /**\n   * The minimum distance (in pixels) the pointer must travel from the initial swipe point\n   * before the gesture is considered a dismiss.\n   * @default 40\n   */\n  swipeThreshold?:\n    | number\n    | ((details: { element: HTMLElement; direction: SwipeDirection }) => number)\n    | undefined;\n  /**\n   * If provided, swiping will only begin once this returns true.\n   * The predicate is evaluated on start and on subsequent move events while the pointer is down.\n   */\n  canStart?:\n    | ((position: { x: number; y: number }, details: UseSwipeDismissDetails) => boolean)\n    | undefined;\n  /**\n   * If true, swiping won't start when the gesture begins within a scrollable element.\n   * This helps avoid conflicts between scrolling content and swipe-to-dismiss.\n   * @default false\n   */\n  ignoreScrollableAncestors?: boolean | undefined;\n  /**\n   * If false, touch interactions can start swiping on interactive elements\n   * that are ignored during pointer swipes.\n   * @default true\n   */\n  ignoreSelectorWhenTouch?: boolean | undefined;\n  /**\n   * Whether to update drag offsets in React state on every move.\n   * Disable for event-only usage to avoid re-renders.\n   * @default true\n   */\n  trackDrag?: boolean | undefined;\n  onSwipeStart?: ((event: PointerEvent | TouchEvent) => void) | undefined;\n  onProgress?: ((progress: number, details?: UseSwipeDismissProgressDetails) => void) | undefined;\n  /**\n   * Called when the swipe interaction starts or ends.\n   */\n  onSwipingChange?: ((swiping: boolean) => void) | undefined;\n  /**\n   * Called when the swipe interaction ends. Returning `true` or `false`\n   * overrides the default dismissal behavior.\n   */\n  onRelease?:\n    | ((details: {\n        event: PointerEvent | TouchEvent;\n        direction: SwipeDirection | undefined;\n        deltaX: number;\n        deltaY: number;\n        velocityX: number;\n        velocityY: number;\n        releaseVelocityX: number;\n        releaseVelocityY: number;\n      }) => boolean | void)\n    | undefined;\n  onDismiss?:\n    | ((event: PointerEvent | TouchEvent, details: { direction: SwipeDirection }) => void)\n    | undefined;\n}\n\nexport interface UseSwipeDismissReturnValue {\n  swiping: boolean;\n  swipeDirection: SwipeDirection | undefined;\n  dragDismissed: boolean;\n  getPointerProps: () => {\n    onPointerDown?: ((event: React.PointerEvent) => void) | undefined;\n    onPointerMove?: ((event: React.PointerEvent) => void) | undefined;\n    onPointerUp?: ((event: React.PointerEvent) => void) | undefined;\n    onPointerCancel?: ((event: React.PointerEvent) => void) | undefined;\n  };\n  getTouchProps: () => {\n    onTouchStart?: ((event: React.TouchEvent) => void) | undefined;\n    onTouchMove?: ((event: React.TouchEvent) => void) | undefined;\n    onTouchEnd?: ((event: React.TouchEvent) => void) | undefined;\n    onTouchCancel?: ((event: React.TouchEvent) => void) | undefined;\n  };\n  getDragStyles: () => React.CSSProperties;\n  reset: () => void;\n}\n"
  },
  {
    "path": "packages/react/src/utils/useTransitionStatus.d.ts",
    "content": "import * as React from 'react';\n\nexport type TransitionStatus = 'starting' | 'ending' | 'idle' | undefined;\n/**\n * Provides a status string for CSS animations.\n * @param open - a boolean that determines if the element is open.\n * @param enableIdleState - a boolean that enables the `'idle'` state between `'starting'` and `'ending'`\n */\nexport declare function useTransitionStatus(\n  open: boolean,\n  enableIdleState?: boolean,\n  deferEndingState?: boolean,\n): {\n  mounted: boolean;\n  setMounted: React.Dispatch<React.SetStateAction<boolean>>;\n  transitionStatus: TransitionStatus;\n};\n"
  },
  {
    "path": "packages/react/src/utils/useTransitionStatus.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { AnimationFrame } from '@base-ui/utils/useAnimationFrame';\n\nexport type TransitionStatus = 'starting' | 'ending' | 'idle' | undefined;\n\n/**\n * Provides a status string for CSS animations.\n * @param open - a boolean that determines if the element is open.\n * @param enableIdleState - a boolean that enables the `'idle'` state between `'starting'` and `'ending'`\n */\nexport function useTransitionStatus(\n  open: boolean,\n  enableIdleState: boolean = false,\n  deferEndingState: boolean = false,\n) {\n  const [transitionStatus, setTransitionStatus] = React.useState<TransitionStatus>(\n    open && enableIdleState ? 'idle' : undefined,\n  );\n  const [mounted, setMounted] = React.useState(open);\n\n  if (open && !mounted) {\n    setMounted(true);\n    setTransitionStatus('starting');\n  }\n\n  if (!open && mounted && transitionStatus !== 'ending' && !deferEndingState) {\n    setTransitionStatus('ending');\n  }\n\n  if (!open && !mounted && transitionStatus === 'ending') {\n    setTransitionStatus(undefined);\n  }\n\n  useIsoLayoutEffect(() => {\n    if (!open && mounted && transitionStatus !== 'ending' && deferEndingState) {\n      const frame = AnimationFrame.request(() => {\n        setTransitionStatus('ending');\n      });\n\n      return () => {\n        AnimationFrame.cancel(frame);\n      };\n    }\n\n    return undefined;\n  }, [open, mounted, transitionStatus, deferEndingState]);\n\n  useIsoLayoutEffect(() => {\n    if (!open || enableIdleState) {\n      return undefined;\n    }\n\n    const frame = AnimationFrame.request(() => {\n      // Avoid `flushSync` here due to Firefox.\n      // See https://github.com/mui/base-ui/pull/3424\n      setTransitionStatus(undefined);\n    });\n\n    return () => {\n      AnimationFrame.cancel(frame);\n    };\n  }, [enableIdleState, open]);\n\n  useIsoLayoutEffect(() => {\n    if (!open || !enableIdleState) {\n      return undefined;\n    }\n\n    if (open && mounted && transitionStatus !== 'idle') {\n      setTransitionStatus('starting');\n    }\n\n    const frame = AnimationFrame.request(() => {\n      setTransitionStatus('idle');\n    });\n\n    return () => {\n      AnimationFrame.cancel(frame);\n    };\n  }, [enableIdleState, open, mounted, setTransitionStatus, transitionStatus]);\n\n  return React.useMemo(\n    () => ({\n      mounted,\n      setMounted,\n      transitionStatus,\n    }),\n    [mounted, transitionStatus],\n  );\n}\n"
  },
  {
    "path": "packages/react/src/utils/useValueChanged.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\n\nexport function useValueChanged<T>(value: T, onChange: (previousValue: T) => void) {\n  const valueRef = React.useRef(value);\n  const onChangeCallback = useStableCallback(onChange);\n\n  useIsoLayoutEffect(() => {\n    if (valueRef.current === value) {\n      return;\n    }\n\n    onChangeCallback(valueRef.current);\n  }, [value, onChangeCallback]);\n\n  useIsoLayoutEffect(() => {\n    valueRef.current = value;\n  }, [value]);\n}\n"
  },
  {
    "path": "packages/react/src/utils/valueToPercent.ts",
    "content": "export function valueToPercent(value: number, min: number, max: number) {\n  return ((value - min) * 100) / (max - min);\n}\n"
  },
  {
    "path": "packages/react/test/addVitestMatchers.ts",
    "content": "import { expect } from 'vitest';\n\ndeclare module 'vitest' {\n  interface Assertion<T = any> {\n    toEqualDateTime(expected: any): T;\n  }\n\n  interface AsymmetricMatchersContaining {\n    toEqualDateTime(expected: any): void;\n  }\n}\n\nfunction cleanDate(value: any): Date {\n  if (typeof value?.toJSDate === 'function') {\n    return value.toJSDate();\n  }\n\n  if (typeof value === 'string') {\n    return new Date(value);\n  }\n\n  return value;\n}\n\nfunction formatDate(value: Date): string {\n  return `${value.getFullYear()}-${value.getMonth() + 1}-${value.getDate()}T${value.getHours()}:${value.getMinutes()}:${value.getSeconds()}.${value.getMilliseconds()}`;\n}\n\nexpect.extend({\n  toEqualDateTime(received, expected) {\n    const actual = formatDate(cleanDate(received));\n    const expectedFormatted = formatDate(cleanDate(expected));\n    const pass = actual === expectedFormatted;\n\n    return {\n      pass,\n      message: () =>\n        pass\n          ? `expected ${actual} not to equal ${expectedFormatted}`\n          : `expected ${actual} to equal ${expectedFormatted}`,\n    };\n  },\n});\n\nexport type {};\n"
  },
  {
    "path": "packages/react/test/conformanceTests/className.tsx",
    "content": "import * as React from 'react';\nimport { expect } from 'vitest';\nimport type {\n  ConformantComponentProps,\n  BaseUiConformanceTestsOptions,\n} from '../describeConformance';\nimport { throwMissingPropError } from './utils';\n\nexport function testClassName(\n  element: React.ReactElement<ConformantComponentProps>,\n  getOptions: () => BaseUiConformanceTestsOptions,\n) {\n  describe('prop: className', () => {\n    const { render } = getOptions();\n\n    if (!render) {\n      throwMissingPropError('render');\n    }\n\n    it('should apply the className when passed as a string', async () => {\n      await render(React.cloneElement(element, { className: 'test-class' }));\n      expect(document.querySelector('.test-class')).not.toBe(null);\n    });\n  });\n}\n"
  },
  {
    "path": "packages/react/test/conformanceTests/propForwarding.tsx",
    "content": "import * as React from 'react';\nimport { expect } from 'vitest';\nimport { flushMicrotasks, randomStringValue, screen } from '@mui/internal-test-utils';\nimport { throwMissingPropError } from './utils';\nimport type {\n  ConformantComponentProps,\n  BaseUiConformanceTestsOptions,\n} from '../describeConformance';\n\nexport function testPropForwarding(\n  element: React.ReactElement<ConformantComponentProps>,\n  getOptions: () => BaseUiConformanceTestsOptions,\n) {\n  const { render, testRenderPropWith: Element = 'div', button = false } = getOptions();\n\n  if (!render) {\n    throwMissingPropError('render');\n  }\n\n  const nativeButton = Element === 'button';\n\n  describe('prop forwarding', () => {\n    it('forwards custom props to the default element', async () => {\n      const otherProps = {\n        lang: 'fr',\n        'data-foobar': randomStringValue(),\n      };\n\n      await render(React.cloneElement(element, { 'data-testid': 'root', ...otherProps }));\n\n      await flushMicrotasks();\n\n      const customRoot = screen.getByTestId('root');\n      expect(customRoot).toHaveAttribute('lang', otherProps.lang);\n      expect(customRoot).toHaveAttribute('data-foobar', otherProps['data-foobar']);\n    });\n\n    it('forwards custom props to the customized element defined with a function', async () => {\n      const otherProps = {\n        lang: 'fr',\n        'data-foobar': randomStringValue(),\n        ...(button && { nativeButton }),\n      };\n\n      await render(\n        React.cloneElement(element, {\n          render: (props: any) => {\n            return <Element {...props} data-testid=\"custom-root\" />;\n          },\n          ...otherProps,\n        }),\n      );\n\n      await flushMicrotasks();\n\n      const customRoot = screen.getByTestId('custom-root');\n      expect(customRoot).toHaveAttribute('lang', otherProps.lang);\n      expect(customRoot).toHaveAttribute('data-foobar', otherProps['data-foobar']);\n    });\n\n    it('forwards custom props to the customized element defined using JSX', async () => {\n      const otherProps = {\n        lang: 'fr',\n        'data-foobar': randomStringValue(),\n        ...(button && { nativeButton }),\n      };\n\n      await render(\n        React.cloneElement(element, {\n          render: <Element data-testid=\"custom-root\" />,\n          ...otherProps,\n        }),\n      );\n\n      await flushMicrotasks();\n\n      const customRoot = screen.getByTestId('custom-root');\n      expect(customRoot).toHaveAttribute('lang', otherProps.lang);\n      expect(customRoot).toHaveAttribute('data-foobar', otherProps['data-foobar']);\n    });\n\n    it('forwards the custom `style` attribute defined on the component', async () => {\n      await render(\n        React.cloneElement(element, {\n          style: { color: 'green' },\n          'data-testid': 'custom-root',\n        }),\n      );\n\n      await flushMicrotasks();\n\n      const customRoot = screen.getByTestId('custom-root');\n      expect(customRoot).toHaveAttribute('style');\n      expect(customRoot.getAttribute('style')).toContain('color: green');\n    });\n\n    it('forwards the custom `style` attribute defined on the render function', async () => {\n      await render(\n        React.cloneElement(element, {\n          render: (props: any) => {\n            return <Element {...props} style={{ color: 'green' }} data-testid=\"custom-root\" />;\n          },\n          ...(button && { nativeButton }),\n        }),\n      );\n\n      await flushMicrotasks();\n\n      const customRoot = screen.getByTestId('custom-root');\n      expect(customRoot).toHaveAttribute('style');\n      expect(customRoot.getAttribute('style')).toContain('color: green');\n    });\n\n    it('forwards the custom `style` attribute defined on the render function', async () => {\n      await render(\n        React.cloneElement(element, {\n          render: <Element style={{ color: 'green' }} data-testid=\"custom-root\" />,\n          ...(button && { nativeButton }),\n        }),\n      );\n\n      await flushMicrotasks();\n\n      const customRoot = screen.getByTestId('custom-root');\n      expect(customRoot).toHaveAttribute('style');\n      expect(customRoot.getAttribute('style')).toContain('color: green');\n    });\n  });\n}\n"
  },
  {
    "path": "packages/react/test/conformanceTests/refForwarding.tsx",
    "content": "import * as React from 'react';\nimport { expect } from 'vitest';\nimport type {\n  ConformantComponentProps,\n  BaseUiConformanceTestsOptions,\n} from '../describeConformance';\nimport { throwMissingPropError } from './utils';\n\nasync function verifyRef(\n  element: React.ReactElement<ConformantComponentProps>,\n  render: BaseUiConformanceTestsOptions['render'],\n  onRef: (instance: unknown, element: HTMLElement | null) => void,\n) {\n  if (!render) {\n    throwMissingPropError('render');\n  }\n\n  const ref = React.createRef();\n\n  const { container } = await render(\n    <React.Fragment>{React.cloneElement(element, { ref })}</React.Fragment>,\n  );\n\n  onRef(ref.current, container);\n}\n\nexport function testRefForwarding(\n  element: React.ReactElement<ConformantComponentProps>,\n  getOptions: () => BaseUiConformanceTestsOptions,\n) {\n  describe('ref', () => {\n    it(`attaches the ref`, async () => {\n      const { render, refInstanceof } = getOptions();\n\n      await verifyRef(element, render, (instance) => {\n        expect(instance).toBeInstanceOf(refInstanceof);\n      });\n    });\n  });\n}\n"
  },
  {
    "path": "packages/react/test/conformanceTests/renderProp.tsx",
    "content": "import * as React from 'react';\nimport { expect } from 'vitest';\nimport { randomStringValue, screen } from '@mui/internal-test-utils';\nimport type {\n  ConformantComponentProps,\n  BaseUiConformanceTestsOptions,\n} from '../describeConformance';\nimport { throwMissingPropError } from './utils';\n\nexport function testRenderProp(\n  element: React.ReactElement<ConformantComponentProps>,\n  getOptions: () => BaseUiConformanceTestsOptions,\n) {\n  const {\n    render,\n    testRenderPropWith: Element = 'div',\n    button = false,\n    wrappingAllowed = true,\n  } = getOptions();\n\n  if (!render) {\n    throwMissingPropError('render');\n  }\n\n  const nativeButton = Element === 'button';\n\n  const Wrapper = React.forwardRef<any, { children?: React.ReactNode }>(\n    function Wrapper(props, forwardedRef) {\n      return wrappingAllowed ? (\n        <div data-testid=\"base-ui-wrapper\">\n          <Element ref={forwardedRef} {...props} data-testid=\"wrapped\" />\n        </div>\n      ) : (\n        /* @ts-expect-error complex type */\n        <Element ref={forwardedRef} {...props} data-testid=\"wrapped\" />\n      );\n    },\n  );\n\n  describe('prop: render', () => {\n    it('renders a customized root element with a function', async () => {\n      const testValue = randomStringValue();\n\n      await render(\n        React.cloneElement(element, {\n          render: (props: any) => {\n            const { key, ...propsWithoutKey } = props;\n            return <Wrapper key={key} {...propsWithoutKey} data-test-value={testValue} />;\n          },\n          ...(button && { nativeButton }),\n        }),\n      );\n\n      if (wrappingAllowed) {\n        expect(screen.queryByTestId('base-ui-wrapper')).not.toBe(null);\n      }\n      expect(screen.queryByTestId('wrapped')).not.toBe(null);\n      expect(screen.queryByTestId('wrapped')).toHaveAttribute('data-test-value', testValue);\n    });\n\n    it('renders a customized root element with an element', async () => {\n      const testValue = randomStringValue();\n\n      await render(\n        React.cloneElement(element, {\n          render: <Wrapper data-test-value={testValue} />,\n          ...(button && { nativeButton }),\n        }),\n      );\n\n      if (wrappingAllowed) {\n        expect(screen.queryByTestId('base-ui-wrapper')).not.toBe(null);\n      }\n      expect(screen.queryByTestId('wrapped')).not.toBe(null);\n      expect(screen.queryByTestId('wrapped')).toHaveAttribute('data-test-value', testValue);\n    });\n\n    it('renders a customized root element with an element', async () => {\n      await render(\n        React.cloneElement(element, {\n          render: <Wrapper />,\n          ...(button && { nativeButton: Element === 'button' }),\n        }),\n      );\n\n      if (wrappingAllowed) {\n        expect(screen.queryByTestId('base-ui-wrapper')).not.toBe(null);\n      } else {\n        expect(screen.queryByTestId('wrapped')).not.toBe(null);\n      }\n    });\n\n    it('should pass the ref to the custom component', async () => {\n      let instanceFromRef = null;\n\n      function Test() {\n        return React.cloneElement(element, {\n          ref: (el: HTMLElement | null) => {\n            instanceFromRef = el;\n          },\n          render: (props: any) => {\n            const { key, ...propsWithoutKey } = props;\n            return <Wrapper key={key} {...propsWithoutKey} />;\n          },\n          'data-testid': 'wrapped',\n          ...(button && { nativeButton }),\n        });\n      }\n\n      await render(<Test />);\n      expect(instanceFromRef!.tagName).toBe(Element.toUpperCase());\n      expect(instanceFromRef!).toHaveAttribute('data-testid', 'wrapped');\n    });\n\n    it('should merge the rendering element ref with the custom component ref', async () => {\n      let refA = null;\n      let refB = null;\n\n      function Test() {\n        return React.cloneElement(element, {\n          ref: (el: HTMLElement | null) => {\n            refA = el;\n          },\n          render: (\n            <Wrapper\n              ref={(el: HTMLElement | null) => {\n                refB = el;\n              }}\n            />\n          ),\n          'data-testid': 'wrapped',\n          ...(button && { nativeButton }),\n        });\n      }\n\n      await render(<Test />);\n\n      expect(refA).not.toBe(null);\n      expect(refA!.tagName).toBe(Element.toUpperCase());\n      expect(refA!).toHaveAttribute('data-testid', 'wrapped');\n      expect(refB).not.toBe(null);\n      expect(refB!.tagName).toBe(Element.toUpperCase());\n      expect(refB!).toHaveAttribute('data-testid', 'wrapped');\n    });\n\n    it('should merge the rendering element className with the custom component className', async () => {\n      function Test() {\n        return React.cloneElement(element, {\n          className: 'component-classname',\n          render: <Element className=\"render-prop-classname\" />,\n          'data-testid': 'test-component',\n          ...(button && { nativeButton }),\n        });\n      }\n\n      await render(<Test />);\n\n      const component = screen.getByTestId('test-component');\n      expect(component.classList.contains('component-classname')).toBe(true);\n      expect(component.classList.contains('render-prop-classname')).toBe(true);\n    });\n\n    it('should merge the rendering element resolved className with the custom component className', async () => {\n      function Test() {\n        return React.cloneElement(element, {\n          className: () => 'conditional-component-classname',\n          render: <Element className=\"render-prop-classname\" />,\n          'data-testid': 'test-component',\n          ...(button && { nativeButton }),\n        });\n      }\n\n      await render(<Test />);\n\n      const component = screen.getByTestId('test-component');\n      expect(component.classList.contains('conditional-component-classname')).toBe(true);\n      expect(component.classList.contains('render-prop-classname')).toBe(true);\n    });\n  });\n}\n"
  },
  {
    "path": "packages/react/test/conformanceTests/utils.ts",
    "content": "export function throwMissingPropError(field: string): never {\n  throw new Error(`missing \"${field}\" in options\n\n  > describeConformance(element, () => options)\n`);\n}\n"
  },
  {
    "path": "packages/react/test/createRenderer.ts",
    "content": "import * as React from 'react';\nimport {\n  CreateRendererOptions,\n  RenderOptions,\n  createRenderer as sharedCreateRenderer,\n  Renderer,\n  MuiRenderResult,\n  act,\n} from '@mui/internal-test-utils';\n\nexport type BaseUIRenderResult = Omit<MuiRenderResult, 'rerender' | 'setProps'> & {\n  rerender: (newElement: React.ReactElement<DataAttributes>) => Promise<void>;\n  setProps: (newProps: object) => Promise<void>;\n};\n\ntype BaseUITestRenderer = Omit<Renderer, 'render'> & {\n  render: (\n    element: React.ReactElement<DataAttributes>,\n    options?: RenderOptions,\n  ) => Promise<BaseUIRenderResult>;\n};\n\ninterface DataAttributes {\n  [key: `data-${string}`]: string;\n}\n\nexport function createRenderer(globalOptions?: CreateRendererOptions): BaseUITestRenderer {\n  const createRendererResult = sharedCreateRenderer(globalOptions);\n  const { render: originalRender } = createRendererResult;\n\n  const render = async (element: React.ReactElement<DataAttributes>, options?: RenderOptions) => {\n    const result = await act(async () => originalRender(element, options));\n\n    async function rerender(newElement: React.ReactElement<DataAttributes>) {\n      await act(async () => result.rerender(newElement));\n    }\n\n    async function setProps(newProps: object) {\n      await rerender(React.cloneElement(element, newProps));\n    }\n\n    return { ...result, rerender, setProps };\n  };\n\n  return {\n    ...createRendererResult,\n    render,\n  };\n}\n"
  },
  {
    "path": "packages/react/test/describeConformance.tsx",
    "content": "import * as React from 'react';\nimport {\n  ConformanceOptions,\n  MuiRenderResult,\n  RenderOptions,\n  createDescribe,\n} from '@mui/internal-test-utils';\nimport { testPropForwarding } from './conformanceTests/propForwarding';\nimport { testRefForwarding } from './conformanceTests/refForwarding';\nimport { testRenderProp } from './conformanceTests/renderProp';\nimport { testClassName } from './conformanceTests/className';\nimport { BaseUIRenderResult } from './createRenderer';\n\nexport type ConformantComponentProps = {\n  render?: React.ReactElement<unknown> | ((props: Record<string, unknown>) => React.ReactNode);\n  ref?: React.Ref<unknown>;\n  'data-testid'?: string;\n  className?: string | ((state: unknown) => string);\n  style?: React.CSSProperties;\n  nativeButton?: boolean;\n};\n\nexport interface BaseUiConformanceTestsOptions extends Omit<\n  Partial<ConformanceOptions>,\n  'render' | 'mount' | 'skip' | 'classes'\n> {\n  render: (\n    element: React.ReactElement<\n      ConformantComponentProps,\n      string | React.JSXElementConstructor<any>\n    >,\n    options?: RenderOptions | undefined,\n  ) => Promise<BaseUIRenderResult> | MuiRenderResult;\n  skip?: (keyof typeof fullSuite)[];\n  testRenderPropWith?: keyof React.JSX.IntrinsicElements;\n  button?: boolean;\n  /**\n   * Whether the component is allowed to be wrapped by an extra element for testing.\n   * @default true\n   */\n  wrappingAllowed?: boolean;\n}\n\nconst fullSuite = {\n  propsSpread: testPropForwarding,\n  refForwarding: testRefForwarding,\n  renderProp: testRenderProp,\n  className: testClassName,\n};\n\nfunction describeConformanceFn(\n  minimalElement: React.ReactElement<ConformantComponentProps>,\n  getOptions: () => BaseUiConformanceTestsOptions,\n) {\n  const { after: runAfterHook = () => {}, only = Object.keys(fullSuite), skip = [] } = getOptions();\n\n  const filteredTests = Object.keys(fullSuite).filter(\n    (testKey) =>\n      only.indexOf(testKey) !== -1 && skip.indexOf(testKey as keyof typeof fullSuite) === -1,\n  ) as (keyof typeof fullSuite)[];\n\n  afterAll(runAfterHook);\n\n  filteredTests.forEach((testKey) => {\n    const test = fullSuite[testKey];\n    test(minimalElement, getOptions as any);\n  });\n}\n\nexport const describeConformance = createDescribe('Base UI component API', describeConformanceFn);\n"
  },
  {
    "path": "packages/react/test/describeGregorianAdapter/describeGregorianAdapter.ts",
    "content": "import createDescribe from '@mui/internal-test-utils/createDescribe';\nimport { testComputations } from './testComputations';\nimport { testLocalization } from './testLocalization';\nimport { testFormats } from './testFormats';\nimport { DescribeGregorianAdapterParameters } from './describeGregorianAdapter.types';\n\nfunction innerGregorianDescribeAdapter(parameters: DescribeGregorianAdapterParameters) {\n  describe(parameters.adapter.lib, () => {\n    testComputations(parameters);\n    testLocalization(parameters);\n    testFormats(parameters);\n  });\n}\n\ntype DescribeGregorianAdapter = {\n  (parameters: DescribeGregorianAdapterParameters): void;\n  skip: (parameters: DescribeGregorianAdapterParameters) => void;\n  only: (parameters: DescribeGregorianAdapterParameters) => void;\n};\n\nexport const describeGregorianAdapter = createDescribe(\n  'Gregorian adapter methods',\n  innerGregorianDescribeAdapter,\n) as DescribeGregorianAdapter;\n"
  },
  {
    "path": "packages/react/test/describeGregorianAdapter/describeGregorianAdapter.types.ts",
    "content": "// TODO Temporal: Replace with `@base-ui/react/types` import when Temporal components will become public.\nimport {\n  TemporalAdapter,\n  TemporalSupportedObject,\n  TemporalTimezone,\n} from '../../src/types/temporal';\n\nexport interface DescribeGregorianAdapterParameters {\n  /**\n   * Default adapter.\n   */\n  adapter: TemporalAdapter;\n  /**\n   * Adapter with French locale.\n   */\n  adapterFr: TemporalAdapter;\n  /**\n   * Adapter with timezone support.\n   * If not provided, will use the same adapter as `adapter`.\n   */\n  adapterTZ?: TemporalAdapter;\n  /**\n   * Sets the default timezone of the date library.\n   * This is used to ensure that the adapter works correctly when the timezone is set to \"default\".\n   * If the adapter does not support setting a default timezone, set this property to `null`.\n   */\n  setDefaultTimezone: ((timezone: TemporalTimezone | undefined) => void) | null;\n  /**\n   * Creates a date in French locale.\n   */\n  createDateInFrenchLocale: (dateStr: string) => TemporalSupportedObject;\n}\n\nexport type DescribeGregorianAdapterTestSuite = (\n  params: DescribeGregorianAdapterParameters,\n) => void;\n"
  },
  {
    "path": "packages/react/test/describeGregorianAdapter/describeGregorianAdapter.utils.ts",
    "content": "export const TEST_DATE_ISO_STRING = '2018-10-30T11:44:25.750Z';\n\nexport const TEST_DATE_LOCALE_STRING = '2018-10-30';\n"
  },
  {
    "path": "packages/react/test/describeGregorianAdapter/index.ts",
    "content": "export { describeGregorianAdapter } from './describeGregorianAdapter';\nexport { TEST_DATE_ISO_STRING } from './describeGregorianAdapter.utils';\n"
  },
  {
    "path": "packages/react/test/describeGregorianAdapter/testComputations.ts",
    "content": "import { expect } from 'vitest';\n// TODO Temporal: Replace with `@base-ui/react/types` import when Temporal components will become public.\nimport {\n  TemporalAdapter,\n  TemporalSupportedObject,\n  TemporalTimezone,\n} from '../../src/types/temporal';\nimport { DescribeGregorianAdapterTestSuite } from './describeGregorianAdapter.types';\nimport { TEST_DATE_ISO_STRING, TEST_DATE_LOCALE_STRING } from './describeGregorianAdapter.utils';\n\n/**\n * To check if the date has the right offset even after changing its date parts,\n * we convert it to a different timezone that always has the same offset,\n * then we check that both dates have the same hour value.\n */\n// We change to\nconst expectSameTimeInMonacoTZ = (adapter: TemporalAdapter, value: TemporalSupportedObject) => {\n  const valueInMonacoTz = adapter.setTimezone(value, 'Europe/Monaco');\n  expect(adapter.getHours(value)).toBe(adapter.getHours(valueInMonacoTz));\n};\n\nexport const testComputations: DescribeGregorianAdapterTestSuite = ({\n  adapter,\n  adapterTZ = adapter,\n  adapterFr,\n  setDefaultTimezone,\n  createDateInFrenchLocale,\n}) => {\n  const testDateIso = adapter.date(TEST_DATE_ISO_STRING, 'default');\n  const testDateIsoInSystemTz = adapter.date(TEST_DATE_ISO_STRING, 'system');\n  const testDateIsoFr = createDateInFrenchLocale(TEST_DATE_ISO_STRING);\n  const testDateLastNonDSTDay = adapterTZ.isTimezoneCompatible\n    ? adapterTZ.date('2022-03-27', 'Europe/Paris')\n    : adapterTZ.date('2022-03-27', 'default');\n  const testDateLocale = adapter.date(TEST_DATE_LOCALE_STRING, 'default');\n\n  describe('Method: date', () => {\n    it('should parse ISO strings', () => {\n      if (adapter.isTimezoneCompatible) {\n        const test = (timezone: TemporalTimezone, expectedTimezones: string = timezone) => {\n          [adapterTZ, adapterFr].forEach((instance) => {\n            const dateWithZone = instance.date(TEST_DATE_ISO_STRING, timezone);\n            expect(instance.getTimezone(dateWithZone)).toBe(expectedTimezones);\n\n            // Should keep the time of the value in the UTC timezone\n            expect(dateWithZone).toEqualDateTime(TEST_DATE_ISO_STRING);\n          });\n        };\n\n        test('UTC');\n        test('system');\n        test('America/New_York');\n        test('Europe/Paris');\n\n        if (setDefaultTimezone != null) {\n          setDefaultTimezone('America/New_York');\n          test('default', 'America/New_York');\n\n          setDefaultTimezone('Europe/Paris');\n          test('default', 'Europe/Paris');\n\n          // Reset to the default timezone\n          setDefaultTimezone(undefined);\n        }\n      } else {\n        expect(adapter.date(TEST_DATE_ISO_STRING, 'system')).toEqualDateTime(TEST_DATE_ISO_STRING);\n        expect(adapter.date(TEST_DATE_ISO_STRING, 'default')).toEqualDateTime(TEST_DATE_ISO_STRING);\n      }\n    });\n\n    it('should parse locale strings', () => {\n      if (adapter.isTimezoneCompatible) {\n        const test = (timezone: TemporalTimezone) => {\n          [adapterTZ, adapterFr].forEach((instance) => {\n            const dateWithZone = instance.date(TEST_DATE_LOCALE_STRING, timezone);\n            expect(instance.getTimezone(dateWithZone)).toBe(timezone);\n\n            // Should keep the time of the date in the target timezone\n            expect(instance.format(dateWithZone, 'hours24hPadded')).toBe('00');\n          });\n        };\n\n        test('UTC');\n        test('system');\n        test('America/New_York');\n        test('Europe/Paris');\n      } else {\n        expect(adapter.date(TEST_DATE_LOCALE_STRING, 'system')).toEqualDateTime(\n          TEST_DATE_LOCALE_STRING,\n        );\n      }\n    });\n\n    it('should parse null', () => {\n      expect(adapter.date(null, 'system')).toBe(null);\n\n      if (adapter.isTimezoneCompatible) {\n        expect(adapter.date(null, 'UTC')).toBe(null);\n        expect(adapter.date(null, 'America/New_York')).toBe(null);\n      }\n    });\n  });\n\n  describe('Method: date', () => {\n    it('should parse custom strings', () => {\n      // RFC5545 format: YYYYMMDDTHHmmssZ\n      const f = adapter.formats;\n      const dateFormat = `${f.yearPadded}${f.monthPadded}${f.dayOfMonthPadded}`;\n      const dateTimeSeparator = `${adapter.escapedCharacters.start}T${adapter.escapedCharacters.end}`;\n      const timeFormat = `${f.hours24hPadded}${f.minutesPadded}${f.secondsPadded}`;\n      const timezoneSuffix = `${adapter.escapedCharacters.start}Z${adapter.escapedCharacters.end}`;\n      const format = `${dateFormat}${dateTimeSeparator}${timeFormat}${timezoneSuffix}`;\n\n      expect(adapter.parse('20181030T114400Z', format, 'default')).toEqualDateTime(\n        '2018-10-30T11:44:00.000Z',\n      );\n    });\n  });\n\n  describe('Method: now', () => {\n    test.skipIf(adapterTZ.lib !== 'dayjs')('should support system timezone', () => {\n      if (adapter.isTimezoneCompatible) {\n        const testTodayZone = (timezone: TemporalTimezone) => {\n          const dateWithZone = adapterTZ.now(timezone);\n          expect(adapterTZ.getTimezone(dateWithZone)).toBe(timezone);\n          expect(Math.abs(adapterTZ.toJsDate(dateWithZone).getTime() - Date.now())).toBeLessThan(5);\n        };\n\n        testTodayZone('system');\n        testTodayZone('UTC');\n        testTodayZone('America/New_York');\n      } else {\n        expect(\n          Math.abs(adapterTZ.toJsDate(adapter.now('system')).getTime() - Date.now()),\n        ).toBeLessThan(5);\n      }\n    });\n  });\n\n  test.skipIf(!adapter.isTimezoneCompatible)('Method: getTimezone', () => {\n    const testTimezone = (timezone: string, expectedTimezone = timezone) => {\n      expect(adapter.getTimezone(adapter.now(timezone))).toBe(expectedTimezone);\n    };\n\n    testTimezone('system');\n    testTimezone('Europe/Paris');\n    testTimezone('America/New_York');\n    testTimezone('UTC');\n\n    if (setDefaultTimezone != null) {\n      setDefaultTimezone('America/Chicago');\n      testTimezone('default', 'America/Chicago');\n      setDefaultTimezone(undefined);\n    }\n  });\n\n  test.skipIf(!adapter.isTimezoneCompatible)(\n    'should not mix Europe/London and UTC in winter',\n    () => {\n      const dateWithZone = adapter.date('2023-10-30T11:44:00.000Z', 'Europe/London');\n      expect(adapter.getTimezone(dateWithZone)).toBe('Europe/London');\n    },\n  );\n\n  describe('Method: setTimezone', () => {\n    it.skipIf(!adapter.isTimezoneCompatible)(\n      'should convert the date to the target timezone without impacting its timestamp',\n      () => {\n        const dateWithLocaleTimezone = adapter.date(TEST_DATE_ISO_STRING, 'system');\n\n        const testTimezone = (timezone: TemporalTimezone) => {\n          const dateInTargetTimezone = adapter.setTimezone(dateWithLocaleTimezone, timezone);\n          expect(adapter.getTimezone(dateInTargetTimezone)).toBe(timezone);\n          expect(adapter.toJsDate(dateInTargetTimezone).getTime()).toBe(\n            adapter.toJsDate(dateWithLocaleTimezone).getTime(),\n          );\n        };\n\n        testTimezone('America/New_York');\n        testTimezone('Europe/Paris');\n        testTimezone('Australia/Sydney');\n        testTimezone('UTC');\n      },\n    );\n\n    test.skipIf(!adapter.isTimezoneCompatible || setDefaultTimezone == null)(\n      'should convert the date to the default timezone',\n      () => {\n        setDefaultTimezone!('America/New_York');\n        expect(adapter.getTimezone(adapter.setTimezone(testDateIsoInSystemTz, 'default'))).toBe(\n          'America/New_York',\n        );\n\n        setDefaultTimezone!('Europe/Paris');\n        expect(adapter.getTimezone(adapter.setTimezone(testDateIsoInSystemTz, 'default'))).toBe(\n          'Europe/Paris',\n        );\n\n        setDefaultTimezone!(undefined);\n      },\n    );\n\n    test.skipIf(!adapter.isTimezoneCompatible)(\n      'should do nothing if the adapter does not support timezones',\n      () => {\n        const systemDate = adapter.date(TEST_DATE_ISO_STRING, 'system');\n        expect(adapter.setTimezone(systemDate, 'default')).toEqualDateTime(systemDate);\n        expect(adapter.setTimezone(systemDate, 'system')).toEqualDateTime(systemDate);\n\n        const defaultDate = adapter.date(TEST_DATE_ISO_STRING, 'default');\n        expect(adapter.setTimezone(systemDate, 'default')).toEqualDateTime(defaultDate);\n        expect(adapter.setTimezone(systemDate, 'system')).toEqualDateTime(defaultDate);\n      },\n    );\n  });\n\n  it('Method: toJsDate', () => {\n    expect(adapter.toJsDate(testDateIso)).toBeInstanceOf(Date);\n    expect(adapter.toJsDate(testDateLocale)).toBeInstanceOf(Date);\n  });\n\n  it('Method: isValid', () => {\n    const invalidDate = adapter.date('2018-42-30T11:60:00.000Z', 'default');\n\n    expect(adapter.isValid(testDateIso)).toBe(true);\n    expect(adapter.isValid(testDateLocale)).toBe(true);\n    expect(adapter.isValid(invalidDate)).toBe(false);\n    expect(adapter.isValid(null)).toBe(false);\n  });\n\n  describe('Method: isEqual', () => {\n    it('should work in the same timezone', () => {\n      expect(adapter.isEqual(adapter.date(null, 'default'), null)).toBe(true);\n      expect(adapter.isEqual(testDateIso, adapter.date(TEST_DATE_ISO_STRING, 'default'))).toBe(\n        true,\n      );\n      expect(adapter.isEqual(null, testDateIso)).toBe(false);\n      expect(\n        adapter.isEqual(testDateLocale, adapter.date(TEST_DATE_LOCALE_STRING, 'default')),\n      ).toBe(true);\n      expect(adapter.isEqual(null, testDateLocale)).toBe(false);\n    });\n\n    test.skipIf(!adapter.isTimezoneCompatible)('should work with different timezones', () => {\n      const dateInLondonTZ = adapterTZ.setTimezone(testDateIso, 'Europe/London');\n      const dateInParisTZ = adapterTZ.setTimezone(testDateIso, 'Europe/Paris');\n\n      expect(adapterTZ.isEqual(dateInLondonTZ, dateInParisTZ)).toBe(true);\n    });\n  });\n\n  describe('Method: isSameYear', () => {\n    it('should work in the same timezone', () => {\n      expect(\n        adapter.isSameYear(testDateIso, adapter.date('2018-10-01T00:00:00.000Z', 'default')),\n      ).toBe(true);\n      expect(\n        adapter.isSameYear(testDateIso, adapter.date('2019-10-01T00:00:00.000Z', 'default')),\n      ).toBe(false);\n      expect(\n        adapter.isSameYear(testDateLocale, adapter.date('2018-10-01T00:00:00.000Z', 'default')),\n      ).toBe(true);\n      expect(\n        adapter.isSameYear(testDateLocale, adapter.date('2019-10-01T00:00:00.000Z', 'default')),\n      ).toBe(false);\n    });\n\n    test.skipIf(!adapter.isTimezoneCompatible)('should work with different timezones', () => {\n      // Both dates below have the same timestamp, but they are not in the same year when represented in their respective timezone.\n      // The adapter should still consider that they are in the same year.\n      const dateInLondonTZ = adapterTZ.endOfYear(\n        adapterTZ.setTimezone(testDateIso, 'Europe/London'),\n      );\n      const dateInParisTZ = adapterTZ.setTimezone(dateInLondonTZ, 'Europe/Paris');\n\n      expect(adapterTZ.isSameYear(dateInLondonTZ, dateInParisTZ)).toBe(true);\n      expect(adapterTZ.isSameYear(dateInParisTZ, dateInLondonTZ)).toBe(true);\n    });\n  });\n\n  describe('Method: isSameMonth', () => {\n    it('should work in the same timezone', () => {\n      expect(\n        adapter.isSameMonth(testDateIso, adapter.date('2018-10-01T00:00:00.000Z', 'default')),\n      ).toBe(true);\n      expect(\n        adapter.isSameMonth(testDateIso, adapter.date('2019-10-01T00:00:00.000Z', 'default')),\n      ).toBe(false);\n      expect(\n        adapter.isSameMonth(testDateLocale, adapter.date('2018-10-01T00:00:00.000Z', 'default')),\n      ).toBe(true);\n      expect(\n        adapter.isSameMonth(testDateLocale, adapter.date('2019-10-01T00:00:00.000Z', 'default')),\n      ).toBe(false);\n    });\n\n    test.skipIf(!adapter.isTimezoneCompatible)('should work with different timezones', () => {\n      // Both dates below have the same timestamp, but they are not in the same month when represented in their respective timezone.\n      // The adapter should still consider that they are in the same month.\n      const dateInLondonTZ = adapterTZ.endOfMonth(\n        adapterTZ.setTimezone(testDateIso, 'Europe/London'),\n      );\n      const dateInParisTZ = adapterTZ.setTimezone(dateInLondonTZ, 'Europe/Paris');\n\n      expect(adapterTZ.isSameMonth(dateInLondonTZ, dateInParisTZ)).toBe(true);\n      expect(adapterTZ.isSameMonth(dateInParisTZ, dateInLondonTZ)).toBe(true);\n    });\n  });\n\n  describe('Method: isSameDay', () => {\n    it('should work in the same timezone', () => {\n      expect(\n        adapter.isSameDay(testDateIso, adapter.date('2018-10-30T00:00:00.000Z', 'default')),\n      ).toBe(true);\n      expect(\n        adapter.isSameDay(testDateIso, adapter.date('2019-10-30T00:00:00.000Z', 'default')),\n      ).toBe(false);\n      expect(\n        adapter.isSameDay(testDateLocale, adapter.date('2018-10-30T00:00:00.000Z', 'default')),\n      ).toBe(true);\n      expect(\n        adapter.isSameDay(testDateLocale, adapter.date('2019-10-30T00:00:00.000Z', 'default')),\n      ).toBe(false);\n    });\n\n    test.skipIf(!adapter.isTimezoneCompatible)('should work with different timezones', () => {\n      // Both dates below have the same timestamp, but they are not in the same day when represented in their respective timezone.\n      // The adapter should still consider that they are in the same day.\n      const dateInLondonTZ = adapterTZ.endOfDay(\n        adapterTZ.setTimezone(testDateIso, 'Europe/London'),\n      );\n      const dateInParisTZ = adapterTZ.setTimezone(dateInLondonTZ, 'Europe/Paris');\n\n      expect(adapterTZ.isSameDay(dateInLondonTZ, dateInParisTZ)).toBe(true);\n      expect(adapterTZ.isSameDay(dateInParisTZ, dateInLondonTZ)).toBe(true);\n    });\n  });\n\n  describe('Method: isSameHour', () => {\n    it('should work in the same timezone', () => {\n      expect(\n        adapter.isSameHour(testDateIso, adapter.date('2018-10-30T11:00:00.000Z', 'default')),\n      ).toBe(true);\n      expect(\n        adapter.isSameHour(testDateIso, adapter.date('2018-10-30T12:00:00.000Z', 'default')),\n      ).toBe(false);\n    });\n\n    test.skipIf(!adapter.isTimezoneCompatible)('should work with different timezones', () => {\n      // Both dates below have the same timestamp, but they are not in the same day when represented in their respective timezone.\n      // The adapter should still consider that they are in the same day.\n      const dateInLondonTZ = adapterTZ.setTimezone(testDateIso, 'Europe/London');\n      const dateInParisTZ = adapterTZ.setTimezone(dateInLondonTZ, 'Europe/Paris');\n\n      expect(adapterTZ.isSameHour(dateInLondonTZ, dateInParisTZ)).toBe(true);\n      expect(adapterTZ.isSameHour(dateInParisTZ, dateInLondonTZ)).toBe(true);\n    });\n  });\n\n  describe('Method: isAfter', () => {\n    it('should work with the same timezone', () => {\n      expect(adapter.isAfter(adapter.now('default'), testDateIso)).toBe(true);\n      expect(adapter.isAfter(testDateIso, adapter.now('default'))).toBe(false);\n\n      expect(adapter.isAfter(adapter.now('default'), testDateLocale)).toBe(true);\n      expect(adapter.isAfter(testDateLocale, adapter.now('default'))).toBe(false);\n    });\n\n    test.skipIf(!adapter.isTimezoneCompatible)('should work with different timezones', () => {\n      const dateInLondonTZ = adapterTZ.endOfDay(\n        adapterTZ.setTimezone(testDateIso, 'Europe/London'),\n      );\n      const dateInParisTZ = adapterTZ.addMinutes(\n        adapterTZ.endOfDay(adapterTZ.setTimezone(testDateIso, 'Europe/Paris')),\n        30,\n      );\n\n      expect(adapter.isAfter(dateInLondonTZ, dateInParisTZ)).toBe(true);\n      expect(adapter.isAfter(dateInParisTZ, dateInLondonTZ)).toBe(false);\n    });\n  });\n\n  describe('Method: isBefore', () => {\n    it('should work with the same timezone', () => {\n      expect(adapter.isBefore(testDateIso, adapter.now('default'))).toBe(true);\n      expect(adapter.isBefore(adapter.now('default'), testDateIso)).toBe(false);\n\n      expect(adapter.isBefore(testDateLocale, adapter.now('default'))).toBe(true);\n      expect(adapter.isBefore(adapter.now('default'), testDateLocale)).toBe(false);\n    });\n\n    test.skipIf(!adapter.isTimezoneCompatible)('should work with different timezones', () => {\n      const dateInLondonTZ = adapterTZ.endOfDay(\n        adapterTZ.setTimezone(testDateIso, 'Europe/London'),\n      );\n      const dateInParisTZ = adapterTZ.addMinutes(\n        adapterTZ.endOfDay(adapterTZ.setTimezone(testDateIso, 'Europe/Paris')),\n        30,\n      );\n\n      expect(adapter.isBefore(dateInLondonTZ, dateInParisTZ)).toBe(false);\n      expect(adapter.isBefore(dateInParisTZ, dateInLondonTZ)).toBe(true);\n    });\n  });\n\n  describe('Method: isWithinRange', () => {\n    it('should work on simple examples', () => {\n      expect(\n        adapter.isWithinRange(adapter.date('2019-10-01T00:00:00.000Z', 'default'), [\n          adapter.date('2019-09-01T00:00:00.000Z', 'default'),\n          adapter.date('2019-11-01T00:00:00.000Z', 'default'),\n        ]),\n      ).toBe(true);\n\n      expect(\n        adapter.isWithinRange(adapter.date('2019-12-01T00:00:00.000Z', 'default'), [\n          adapter.date('2019-09-01T00:00:00.000Z', 'default'),\n          adapter.date('2019-11-01T00:00:00.000Z', 'default'),\n        ]),\n      ).toBe(false);\n\n      expect(\n        adapter.isWithinRange(adapter.date('2019-10-01', 'default'), [\n          adapter.date('2019-09-01', 'default'),\n          adapter.date('2019-11-01', 'default'),\n        ]),\n      ).toBe(true);\n\n      expect(\n        adapter.isWithinRange(adapter.date('2019-12-01', 'default'), [\n          adapter.date('2019-09-01', 'default'),\n          adapter.date('2019-11-01', 'default'),\n        ]),\n      ).toBe(false);\n    });\n\n    it('should use inclusiveness of range', () => {\n      expect(\n        adapter.isWithinRange(adapter.date('2019-09-01T00:00:00.000Z', 'default'), [\n          adapter.date('2019-09-01T00:00:00.000Z', 'default'),\n          adapter.date('2019-12-01T00:00:00.000Z', 'default'),\n        ]),\n      ).toBe(true);\n\n      expect(\n        adapter.isWithinRange(adapter.date('2019-12-01T00:00:00.000Z', 'default'), [\n          adapter.date('2019-09-01T00:00:00.000Z', 'default'),\n          adapter.date('2019-12-01T00:00:00.000Z', 'default'),\n        ]),\n      ).toBe(true);\n\n      expect(\n        adapter.isWithinRange(adapter.date('2019-09-01', 'default'), [\n          adapter.date('2019-09-01', 'default'),\n          adapter.date('2019-12-01', 'default'),\n        ]),\n      ).toBe(true);\n\n      expect(\n        adapter.isWithinRange(adapter.date('2019-12-01', 'default'), [\n          adapter.date('2019-09-01', 'default'),\n          adapter.date('2019-12-01', 'default'),\n        ]),\n      ).toBe(true);\n    });\n\n    it('should be equal with values in different locales', () => {\n      expect(\n        adapter.isWithinRange(adapter.date('2022-04-17', 'default'), [\n          adapterFr.date('2022-04-17', 'default'),\n          adapterFr.date('2022-04-19', 'default'),\n        ]),\n      ).toBe(true);\n    });\n  });\n\n  it('Method: startOfYear', () => {\n    const expected = '2018-01-01T00:00:00.000Z';\n    expect(adapter.startOfYear(testDateIso)).toEqualDateTime(expected);\n    expect(adapter.startOfYear(testDateLocale)).toEqualDateTime(expected);\n  });\n\n  it('Method: startOfMonth', () => {\n    const expected = '2018-10-01T00:00:00.000Z';\n    expect(adapter.startOfMonth(testDateIso)).toEqualDateTime(expected);\n    expect(adapter.startOfMonth(testDateLocale)).toEqualDateTime(expected);\n  });\n\n  describe('Method: startOfWeek', () => {\n    it('should handle basic use-cases', () => {\n      expect(adapter.startOfWeek(testDateIso)).toEqualDateTime('2018-10-28T00:00:00.000Z');\n      expect(adapter.startOfWeek(testDateLocale)).toEqualDateTime('2018-10-28T00:00:00.000Z');\n    });\n\n    it('should use the adapter locale when the date has another locale', () => {\n      expect(adapter.startOfWeek(testDateIsoFr)).toEqualDateTime('2018-10-28T00:00:00.000Z');\n    });\n  });\n\n  it('Method: startOfDay', () => {\n    const expected = '2018-10-30T00:00:00.000Z';\n    expect(adapter.startOfDay(testDateIso)).toEqualDateTime(expected);\n    expect(adapter.startOfDay(testDateLocale)).toEqualDateTime(expected);\n  });\n\n  it('Method: startOfHour', () => {\n    const expected = '2018-10-30T11:00:00.000Z';\n    expect(adapter.startOfHour(testDateIso)).toEqualDateTime(expected);\n  });\n\n  it('Method: startOfMinute', () => {\n    const expected = '2018-10-30T11:44:00.000Z';\n    expect(adapter.startOfMinute(testDateIso)).toEqualDateTime(expected);\n  });\n\n  it('Method: startOfSecond', () => {\n    const expected = '2018-10-30T11:44:25.000Z';\n    expect(adapter.startOfSecond(testDateIso)).toEqualDateTime(expected);\n  });\n\n  it('Method: endOfYear', () => {\n    const expected = '2018-12-31T23:59:59.999Z';\n    expect(adapter.endOfYear(testDateIso)).toEqualDateTime(expected);\n    expect(adapter.endOfYear(testDateLocale)).toEqualDateTime(expected);\n  });\n\n  describe('Method: endOfMonth', () => {\n    it('should handle basic use-cases', () => {\n      const expected = '2018-10-31T23:59:59.999Z';\n      expect(adapter.endOfMonth(testDateIso)).toEqualDateTime(expected);\n      expect(adapter.endOfMonth(testDateLocale)).toEqualDateTime(expected);\n    });\n\n    test.skipIf(!adapter.isTimezoneCompatible)('should update the offset when entering DST', () => {\n      expectSameTimeInMonacoTZ(adapterTZ, testDateLastNonDSTDay);\n      expectSameTimeInMonacoTZ(adapterTZ, adapterTZ.endOfMonth(testDateLastNonDSTDay));\n    });\n  });\n\n  it('Method: endOfWeek', () => {\n    expect(adapter.endOfWeek(testDateIso)).toEqualDateTime('2018-11-03T23:59:59.999Z');\n    expect(adapter.endOfWeek(testDateLocale)).toEqualDateTime('2018-11-03T23:59:59.999Z');\n  });\n\n  it('Method: endOfDay', () => {\n    const expected = '2018-10-30T23:59:59.999Z';\n    expect(adapter.endOfDay(testDateIso)).toEqualDateTime(expected);\n    expect(adapter.endOfDay(testDateLocale)).toEqualDateTime(expected);\n  });\n\n  it('Method: endOfHour', () => {\n    const expected = '2018-10-30T11:59:59.999Z';\n    expect(adapter.endOfHour(testDateIso)).toEqualDateTime(expected);\n  });\n\n  it('Method: endOfMinute', () => {\n    const expected = '2018-10-30T11:44:59.999Z';\n    expect(adapter.endOfMinute(testDateIso)).toEqualDateTime(expected);\n  });\n\n  it('Method: endOfSecond', () => {\n    const expected = '2018-10-30T11:44:25.999Z';\n    expect(adapter.endOfSecond(testDateIso)).toEqualDateTime(expected);\n  });\n\n  it('Method: addYears', () => {\n    expect(adapter.addYears(testDateIso, 2)).toEqualDateTime('2020-10-30T11:44:25.750Z');\n    expect(adapter.addYears(testDateIso, -2)).toEqualDateTime('2016-10-30T11:44:25.750Z');\n  });\n\n  describe('Method: addMonths', () => {\n    it('should handle basic use-cases', () => {\n      expect(adapter.addMonths(testDateIso, 2)).toEqualDateTime('2018-12-30T11:44:25.750Z');\n      expect(adapter.addMonths(testDateIso, -2)).toEqualDateTime('2018-08-30T11:44:25.750Z');\n      expect(adapter.addMonths(testDateIso, 3)).toEqualDateTime('2019-01-30T11:44:25.750Z');\n    });\n\n    test.skipIf(!adapter.isTimezoneCompatible)('should update the offset when entering DST', () => {\n      expectSameTimeInMonacoTZ(adapterTZ, testDateLastNonDSTDay);\n      expectSameTimeInMonacoTZ(adapterTZ, adapterTZ.addMonths(testDateLastNonDSTDay, 1));\n    });\n  });\n\n  describe('Method: addWeeks', () => {\n    it('should handle basic use-cases', () => {\n      expect(adapter.addWeeks(testDateIso, 2)).toEqualDateTime('2018-11-13T11:44:25.750Z');\n      expect(adapter.addWeeks(testDateIso, -2)).toEqualDateTime('2018-10-16T11:44:25.750Z');\n    });\n\n    test.skipIf(!adapter.isTimezoneCompatible)('should update the offset when entering DST', () => {\n      expectSameTimeInMonacoTZ(adapterTZ, testDateLastNonDSTDay);\n      expectSameTimeInMonacoTZ(adapterTZ, adapterTZ.addWeeks(testDateLastNonDSTDay, 1));\n    });\n  });\n\n  describe('Method: addWeeks', () => {\n    it('should handle basic use-cases', () => {\n      expect(adapter.addDays(testDateIso, 2)).toEqualDateTime('2018-11-01T11:44:25.750Z');\n      expect(adapter.addDays(testDateIso, -2)).toEqualDateTime('2018-10-28T11:44:25.750Z');\n    });\n\n    test.skipIf(!adapter.isTimezoneCompatible)('should update the offset when entering DST', () => {\n      expectSameTimeInMonacoTZ(adapterTZ, testDateLastNonDSTDay);\n      expectSameTimeInMonacoTZ(adapterTZ, adapterTZ.addDays(testDateLastNonDSTDay, 1));\n    });\n  });\n\n  it('Method: addHours', () => {\n    expect(adapter.addHours(testDateIso, 2)).toEqualDateTime('2018-10-30T13:44:25.750Z');\n    expect(adapter.addHours(testDateIso, -2)).toEqualDateTime('2018-10-30T09:44:25.750Z');\n    expect(adapter.addHours(testDateIso, 15)).toEqualDateTime('2018-10-31T02:44:25.750Z');\n  });\n\n  it('Method: addMinutes', () => {\n    expect(adapter.addMinutes(testDateIso, 2)).toEqualDateTime('2018-10-30T11:46:25.750Z');\n    expect(adapter.addMinutes(testDateIso, -2)).toEqualDateTime('2018-10-30T11:42:25.750Z');\n    expect(adapter.addMinutes(testDateIso, 20)).toEqualDateTime('2018-10-30T12:04:25.750Z');\n  });\n\n  it('Method: addSeconds', () => {\n    expect(adapter.addSeconds(testDateIso, 2)).toEqualDateTime('2018-10-30T11:44:27.750Z');\n    expect(adapter.addSeconds(testDateIso, -2)).toEqualDateTime('2018-10-30T11:44:23.750Z');\n    expect(adapter.addSeconds(testDateIso, 70)).toEqualDateTime('2018-10-30T11:45:35.750Z');\n  });\n\n  it('Method: addMilliseconds', () => {\n    expect(adapter.addMilliseconds(testDateIso, 2)).toEqualDateTime('2018-10-30T11:44:25.752Z');\n    expect(adapter.addMilliseconds(testDateIso, -2)).toEqualDateTime('2018-10-30T11:44:25.748Z');\n    expect(adapter.addMilliseconds(testDateIso, 500)).toEqualDateTime('2018-10-30T11:44:26.250Z');\n  });\n\n  it('Method: getYear', () => {\n    expect(adapter.getYear(testDateIso)).toBe(2018);\n  });\n\n  it('Method: getMonth', () => {\n    expect(adapter.getMonth(testDateIso)).toBe(9);\n  });\n\n  it('Method: getDate', () => {\n    expect(adapter.getDate(testDateIso)).toBe(30);\n  });\n\n  it('Method: getHours', () => {\n    expect(adapter.getHours(testDateIso)).toBe(11);\n  });\n\n  it('Method: getMinutes', () => {\n    expect(adapter.getMinutes(testDateIso)).toBe(44);\n  });\n\n  it('Method: getSeconds', () => {\n    expect(adapter.getSeconds(testDateIso)).toBe(25);\n  });\n\n  it('Method: getMilliseconds', () => {\n    expect(adapter.getMilliseconds(testDateIso)).toBe(750);\n  });\n\n  it('Method: getTime', () => {\n    expect(adapter.getTime(testDateIso)).toBe(1540899865750);\n  });\n\n  it('Method: setYear', () => {\n    expect(adapter.setYear(testDateIso, 2011)).toEqualDateTime('2011-10-30T11:44:25.750');\n  });\n\n  it('Method: setMonth', () => {\n    expect(adapter.setMonth(testDateIso, 4)).toEqualDateTime('2018-05-30T11:44:25.750');\n  });\n\n  it('Method: setDate', () => {\n    expect(adapter.setDate(testDateIso, 15)).toEqualDateTime('2018-10-15T11:44:25.750');\n  });\n\n  it('Method: setHours', () => {\n    expect(adapter.setHours(testDateIso, 0)).toEqualDateTime('2018-10-30T00:44:25.750');\n  });\n\n  it('Method: setMinutes', () => {\n    expect(adapter.setMinutes(testDateIso, 12)).toEqualDateTime('2018-10-30T11:12:25.750');\n  });\n\n  it('Method: setSeconds', () => {\n    expect(adapter.setSeconds(testDateIso, 11)).toEqualDateTime('2018-10-30T11:44:11.750');\n  });\n\n  it('Method: setMilliseconds', () => {\n    expect(adapter.setMilliseconds(testDateIso, 11)).toEqualDateTime('2018-10-30T11:44:25.011Z');\n  });\n\n  describe('Method: differenceInYears', () => {\n    it('should handle basic use-cases', () => {\n      expect(\n        adapter.differenceInYears(\n          adapter.date('2020-04-01', 'default'),\n          adapter.date('2018-10-30', 'default'),\n        ),\n      ).toBe(1);\n\n      expect(\n        adapter.differenceInYears(\n          adapter.date('2020-04-01', 'default'),\n          adapter.date('2018-04-01', 'default'),\n        ),\n      ).toBe(2);\n    });\n\n    test.skipIf(!adapter.isTimezoneCompatible)('should work with timezones', () => {\n      expect(\n        adapter.differenceInYears(\n          adapter.date('2020-04-01T12:00', 'Europe/Paris'),\n          adapter.date('2018-04-01T12:00', 'America/New_York'),\n        ),\n      ).toBe(1);\n    });\n  });\n\n  describe('Method: differenceInMonths', () => {\n    it('should handle basic use-cases', () => {\n      expect(\n        adapter.differenceInMonths(\n          adapter.date('2019-01-30', 'default'),\n          adapter.date('2018-10-30', 'default'),\n        ),\n      ).toBe(3);\n\n      expect(\n        adapter.differenceInMonths(\n          adapter.date('2019-01-15', 'default'),\n          adapter.date('2018-10-30', 'default'),\n        ),\n      ).toBe(2);\n    });\n\n    test.skipIf(!adapter.isTimezoneCompatible)('should work with timezones', () => {\n      expect(\n        adapter.differenceInMonths(\n          adapter.date('2018-06-30T12:00', 'Europe/Paris'),\n          adapter.date('2018-04-30T12:00', 'America/New_York'),\n        ),\n      ).toBe(1);\n    });\n  });\n\n  describe('Method: differenceInDays', () => {\n    it('should handle basic use-cases', () => {\n      expect(\n        adapter.differenceInDays(\n          adapter.date('2018-11-05', 'default'),\n          adapter.date('2018-10-30', 'default'),\n        ),\n      ).toBe(6);\n\n      expect(\n        adapter.differenceInDays(\n          adapter.date('2018-11-05', 'default'),\n          adapter.date('2018-11-01', 'default'),\n        ),\n      ).toBe(4);\n    });\n\n    test.skipIf(!adapter.isTimezoneCompatible)('should work with timezones', () => {\n      expect(\n        adapter.differenceInDays(\n          adapter.date('2018-10-07T12:00', 'Europe/Paris'),\n          adapter.date('2018-10-05T12:00', 'America/New_York'),\n        ),\n      ).toBe(1);\n    });\n  });\n\n  describe('Method: differenceInHours', () => {\n    it('should handle basic use-cases', () => {\n      expect(\n        adapter.differenceInHours(\n          adapter.date('2018-10-31T15:00', 'default'),\n          adapter.date('2018-10-30T11:00', 'default'),\n        ),\n      ).toBe(28);\n\n      expect(\n        adapter.differenceInHours(\n          adapter.date('2018-10-31T15:00', 'default'),\n          adapter.date('2018-10-31T11:00', 'default'),\n        ),\n      ).toBe(4);\n    });\n\n    test.skipIf(!adapter.isTimezoneCompatible)('should work with timezones', () => {\n      expect(\n        adapter.differenceInHours(\n          adapter.date('2018-10-30T12:00', 'Europe/Paris'),\n          adapter.date('2018-10-30T12:00', 'America/New_York'),\n        ),\n      ).toBe(-5);\n    });\n\n    test.skipIf(!adapter.isTimezoneCompatible)('should work accross DST', () => {\n      expect(\n        adapter.differenceInHours(\n          adapter.date('2022-03-28', 'Europe/Paris'),\n          adapter.date('2022-03-27', 'Europe/Paris'),\n        ),\n      ).toBe(23);\n    });\n  });\n\n  describe('Method: differenceInMinutes', () => {\n    it('should handle basic use-cases', () => {\n      expect(\n        adapter.differenceInMinutes(\n          adapter.date('2018-10-30T12:30', 'default'),\n          adapter.date('2018-10-30T11:00', 'default'),\n        ),\n      ).toBe(90);\n\n      expect(\n        adapter.differenceInMinutes(\n          adapter.date('2018-10-30T11:30', 'default'),\n          adapter.date('2018-10-30T11:00', 'default'),\n        ),\n      ).toBe(30);\n    });\n\n    test.skipIf(!adapter.isTimezoneCompatible)('should work with timezones', () => {\n      expect(\n        adapter.differenceInMinutes(\n          adapter.date('2018-10-30T12:00', 'Europe/Paris'),\n          adapter.date('2018-10-30T12:00', 'America/New_York'),\n        ),\n      ).toBe(-300);\n    });\n\n    test.skipIf(!adapter.isTimezoneCompatible)('should work accross DST', () => {\n      expect(\n        adapter.differenceInMinutes(\n          adapter.date('2022-03-28', 'Europe/Paris'),\n          adapter.date('2022-03-27', 'Europe/Paris'),\n        ),\n      ).toBe(23 * 60);\n    });\n  });\n\n  it('Method: getDaysInMonth', () => {\n    expect(adapter.getDaysInMonth(testDateIso)).toBe(31);\n    expect(adapter.getDaysInMonth(testDateLocale)).toBe(31);\n    expect(adapter.getDaysInMonth(adapter.addMonths(testDateIso, 1))).toBe(30);\n  });\n\n  it('Method: getDayOfWeek', () => {\n    expect(adapter.getDayOfWeek(testDateIso)).toBe(3);\n  });\n\n  it('Method: getWeekNumber', () => {\n    expect(adapter.getWeekNumber(testDateIso)).toBe(44);\n  });\n};\n"
  },
  {
    "path": "packages/react/test/describeGregorianAdapter/testFormats.ts",
    "content": "import { expect } from 'vitest';\n// TODO Temporal: Replace with `@base-ui/react/types` import when Temporal components will become public.\nimport { TemporalAdapterFormats } from '../../src/types/temporal';\nimport { DescribeGregorianAdapterTestSuite } from './describeGregorianAdapter.types';\n\nexport const testFormats: DescribeGregorianAdapterTestSuite = ({ adapter }) => {\n  const expectFormattedDate = (format: keyof TemporalAdapterFormats, expected: string) => {\n    const date = adapter.date('2020-01-01T15:08:09.000Z', 'utc');\n    const result = adapter.format(date, format);\n\n    expect(result).toBe(expected);\n  };\n\n  it('should correctly format standalone hardcoded formats', () => {\n    // Digit formats with leading zeroes\n    expectFormattedDate('yearPadded', '2020');\n    expectFormattedDate('monthPadded', '01');\n    expectFormattedDate('dayOfMonthPadded', '01');\n    expectFormattedDate('hours24hPadded', '15');\n    expectFormattedDate('hours12hPadded', '03');\n    expectFormattedDate('minutesPadded', '08');\n    expectFormattedDate('secondsPadded', '09');\n\n    // Digit formats without leading zeroes\n    expectFormattedDate('dayOfMonth', '1');\n\n    // Letter formats\n    expectFormattedDate('weekday', 'Wednesday');\n    expectFormattedDate('weekday3Letters', 'Wed');\n    expectFormattedDate('meridiem', 'PM');\n  });\n};\n"
  },
  {
    "path": "packages/react/test/describeGregorianAdapter/testLocalization.ts",
    "content": "import { expect } from 'vitest';\nimport { DescribeGregorianAdapterTestSuite } from './describeGregorianAdapter.types';\n\nexport const testLocalization: DescribeGregorianAdapterTestSuite = ({ adapter }) => {\n  it('Method: getCurrentLocaleCode', () => {\n    // TODO: When adding the moment adapter\n    // if (adapter.lib === 'moment') {\n    //   moment.locale('en');\n    // }\n\n    // Returns the default locale\n    expect(adapter.getCurrentLocaleCode()).toMatch(/en/);\n  });\n};\n"
  },
  {
    "path": "packages/react/test/floating-ui-tests/Button.module.css",
    "content": ".Button {\n  background: rgb(226 232 240 / 0.9);\n  border-radius: 0.25rem;\n  padding: 8px 12px;\n  transition-property:\n    color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow,\n    transform, filter, backdrop-filter;\n  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n  transition-duration: 150ms;\n}\n\n.Button:hover {\n  background: rgb(226 232 240 / 0.5);\n}\n\n.Button[data-open] {\n  background: rgb(226 232 240 / 0.5);\n}\n"
  },
  {
    "path": "packages/react/test/floating-ui-tests/Button.tsx",
    "content": "import c from 'clsx';\nimport * as React from 'react';\nimport styles from './Button.module.css';\n\n/** @internal */\nexport const Button = React.forwardRef<\n  HTMLButtonElement,\n  React.ButtonHTMLAttributes<HTMLButtonElement>\n>(function Button(props, ref) {\n  return (\n    // eslint-disable-next-line react/button-has-type\n    <button {...props} ref={ref} className={c(props.className, styles.Button)} />\n  );\n});\n"
  },
  {
    "path": "packages/react/test/floating-ui-tests/ComplexGrid.module.css",
    "content": ".Grid {\n  display: grid;\n  gap: 8px;\n}\n\n.Item {\n  border: 1px solid #000;\n}\n\n.Item:disabled {\n  opacity: 0.2;\n}\n"
  },
  {
    "path": "packages/react/test/floating-ui-tests/ComplexGrid.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport {\n  FloatingFocusManager,\n  useClick,\n  useDismiss,\n  useFloating,\n  useInteractions,\n  useListNavigation,\n} from '../../src/floating-ui-react';\nimport styles from './ComplexGrid.module.css';\n\ninterface Props {\n  orientation?: 'horizontal' | 'both';\n  loopFocus?: boolean;\n  rtl?: boolean;\n}\n\n/*\n * Grid diagram for reference:\n * Disabled indices marked with ()\n */\n\n/** @internal */\nexport function Main({ orientation = 'horizontal', loopFocus = false, rtl = false }: Props) {\n  const [open, setOpen] = React.useState(false);\n  const [activeIndex, setActiveIndex] = React.useState<number | null>(null);\n\n  const listRef = React.useRef<Array<HTMLElement | null>>([]);\n\n  const { floatingStyles, refs, context } = useFloating({\n    open,\n    onOpenChange: setOpen,\n    placement: 'bottom-start',\n  });\n\n  const disabledIndices = [0, 1, 2, 3, 4, 5, 6, 9, 14, 23, 35];\n\n  const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions([\n    useClick(context),\n    useListNavigation(context, {\n      listRef,\n      activeIndex,\n      onNavigate: setActiveIndex,\n      cols: 7,\n      orientation,\n      loopFocus,\n      rtl,\n      openOnArrowKeyDown: false,\n      disabledIndices,\n    }),\n    useDismiss(context),\n  ]);\n\n  return (\n    <React.Fragment>\n      <h1>Complex Grid</h1>\n      <div className={styles.Container}>\n        <button ref={refs.setReference} type=\"button\" {...getReferenceProps()}>\n          Reference\n        </button>\n        {open && (\n          <FloatingFocusManager context={context}>\n            <div\n              ref={refs.setFloating}\n              data-testid=\"floating\"\n              className={styles.Grid}\n              style={{\n                ...floatingStyles,\n                display: 'grid',\n                gridTemplateColumns: '100px 100px 100px 100px 100px 100px 100px',\n                zIndex: 999,\n              }}\n              {...getFloatingProps()}\n            >\n              {[...Array(37)].map((_, index) => (\n                <button\n                  type=\"button\"\n                  role=\"option\"\n                  key={index}\n                  aria-selected={activeIndex === index}\n                  tabIndex={activeIndex === index ? 0 : -1}\n                  disabled={disabledIndices.includes(index)}\n                  ref={(node) => {\n                    listRef.current[index] = node;\n                  }}\n                  className={styles.Item}\n                  {...getItemProps()}\n                >\n                  Item {index}\n                </button>\n              ))}\n            </div>\n          </FloatingFocusManager>\n        )}\n      </div>\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "packages/react/test/floating-ui-tests/EmojiPicker.module.css",
    "content": ".Heading {\n  margin-bottom: 2rem;\n  font-size: 3rem;\n  font-weight: 700;\n}\n\n.Container {\n  margin-bottom: 1rem;\n  display: grid;\n  height: 20rem;\n  place-items: center;\n  border: 1px solid #94a3b8;\n  border-radius: 0.25rem;\n}\n\n@media (min-width: 1024px) {\n  .Container {\n    width: 40rem;\n  }\n}\n\n.Trigger {\n  font-size: 1.5rem;\n}\n\n.Floating {\n  border: 1px solid rgb(15 23 42 / 0.1);\n  border-radius: 0.5rem;\n  background: rgb(255 255 255 / 0.7);\n  background-clip: padding-box;\n  padding: 16px;\n  box-shadow:\n    0 4px 6px -1px rgb(0 0 0 / 0.1),\n    0 2px 4px -2px rgb(0 0 0 / 0.1);\n  backdrop-filter: blur(4px);\n}\n\n.Label {\n  font-size: 0.875rem;\n  text-transform: uppercase;\n  opacity: 0.4;\n}\n\n.Input {\n  border: 1px solid #cbd5e1;\n  margin-top: 8px;\n  margin-bottom: 8px;\n  display: block;\n  width: 9rem;\n  border-radius: 0.25rem;\n  padding: 4px;\n  outline: none;\n}\n\n.Input:focus {\n  border-color: #2563eb;\n}\n\n.Option {\n  aspect-ratio: 1;\n  cursor: default;\n  border-radius: 0.25rem;\n  text-align: center;\n  font-size: 1.875rem;\n  user-select: none;\n}\n\n.OptionSelected {\n  background: #cffafe;\n}\n\n.OptionActive {\n  background: #a5f3fc;\n}\n\n.OptionDisabled {\n  opacity: 0.4;\n}\n\n.Listbox {\n  display: grid;\n  grid-template-columns: repeat(3, minmax(0, 1fr));\n}\n"
  },
  {
    "path": "packages/react/test/floating-ui-tests/EmojiPicker.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport c from 'clsx';\nimport { useId } from '@base-ui/utils/useId';\nimport type { Placement } from '../../src/floating-ui-react/types';\nimport {\n  arrow,\n  autoUpdate,\n  flip,\n  FloatingFocusManager,\n  FloatingPortal,\n  offset,\n  useClick,\n  useDismiss,\n  useFloating,\n  useInteractions,\n  useListNavigation,\n  useRole,\n} from '../../src/floating-ui-react';\nimport { Button } from './Button';\nimport styles from './EmojiPicker.module.css';\n\nconst emojis = [\n  {\n    name: 'apple',\n    emoji: '🍎',\n  },\n  {\n    name: 'orange',\n    emoji: '🍊',\n  },\n  {\n    name: 'watermelon',\n    emoji: '🍉',\n  },\n  {\n    name: 'strawberry',\n    emoji: '🍓',\n  },\n  {\n    name: 'pear',\n    emoji: '🍐',\n  },\n  {\n    name: 'banana',\n    emoji: '🍌',\n  },\n  {\n    name: 'pineapple',\n    emoji: '🍍',\n  },\n  {\n    name: 'cherry',\n    emoji: '🍒',\n  },\n  {\n    name: 'peach',\n    emoji: '🍑',\n  },\n];\n\ntype OptionProps = React.HTMLAttributes<HTMLButtonElement> & {\n  name: string;\n  active: boolean;\n  selected: boolean;\n  children: React.ReactNode;\n};\n\n/** @internal */\nconst Option = React.forwardRef<HTMLButtonElement, OptionProps>(function Option(\n  { name, active, selected, children, ...props },\n  ref,\n) {\n  const id = useId();\n  return (\n    <button\n      {...props}\n      ref={ref}\n      id={id}\n      role=\"option\"\n      className={c(styles.Option, {\n        [styles.OptionSelected]: selected && !active,\n        [styles.OptionActive]: active,\n        [styles.OptionDisabled]: name === 'orange',\n      })}\n      aria-selected={selected}\n      disabled={name === 'orange'}\n      aria-label={name}\n      tabIndex={-1}\n      data-active={active ? '' : undefined}\n      type=\"button\"\n    >\n      {children}\n    </button>\n  );\n});\n\n/** @internal */\nexport function Main() {\n  const [open, setOpen] = React.useState(false);\n  const [search, setSearch] = React.useState('');\n  const [selectedEmoji, setSelectedEmoji] = React.useState<string | null>(null);\n  const [activeIndex, setActiveIndex] = React.useState<number | null>(null);\n  const [placement, setPlacement] = React.useState<Placement | null>(null);\n\n  const arrowRef = React.useRef(null);\n\n  const listRef = React.useRef<Array<HTMLElement | null>>([]);\n\n  const noResultsId = useId();\n\n  const {\n    floatingStyles,\n    refs,\n    context,\n    placement: resultantPlacement,\n  } = useFloating({\n    placement: placement ?? 'bottom-start',\n    open,\n    onOpenChange: setOpen,\n    // We don't want flipping to occur while searching, as the floating element\n    // will resize and cause disorientation.\n    middleware: [\n      offset(8),\n      ...(placement ? [] : [flip()]),\n      arrow({\n        element: arrowRef,\n        padding: 20,\n      }),\n    ],\n    whileElementsMounted: autoUpdate,\n  });\n\n  // Handles opening the floating element via the Choose Emoji button.\n  const { getReferenceProps, getFloatingProps } = useInteractions([\n    useClick(context),\n    useDismiss(context),\n    useRole(context, { role: 'menu' }),\n  ]);\n\n  // Handles the list navigation where the reference is the inner input, not\n  // the button that opens the floating element.\n  const {\n    getReferenceProps: getInputProps,\n    getFloatingProps: getListFloatingProps,\n    getItemProps,\n  } = useInteractions([\n    useListNavigation(context, {\n      listRef,\n      onNavigate: open ? setActiveIndex : undefined,\n      activeIndex,\n      cols: 3,\n      orientation: 'horizontal',\n      loopFocus: true,\n      focusItemOnOpen: false,\n      virtual: true,\n      allowEscape: true,\n    }),\n  ]);\n\n  React.useEffect(() => {\n    if (open) {\n      setPlacement(resultantPlacement);\n    } else {\n      setSearch('');\n      setActiveIndex(null);\n      setPlacement(null);\n    }\n  }, [open, resultantPlacement]);\n\n  const handleEmojiClick = () => {\n    if (activeIndex !== null) {\n      // eslint-disable-next-line\n      setSelectedEmoji(filteredEmojis[activeIndex].emoji);\n      setOpen(false);\n    }\n  };\n\n  const handleKeyDown = (event: React.KeyboardEvent) => {\n    if (event.key === 'Enter') {\n      handleEmojiClick();\n    }\n  };\n\n  const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n    setActiveIndex(null);\n    setSearch(event.target.value);\n  };\n\n  const filteredEmojis = emojis.filter(({ name }) =>\n    name.toLocaleLowerCase().includes(search.toLocaleLowerCase()),\n  );\n\n  return (\n    <React.Fragment>\n      <h1 className={styles.Heading}>Emoji Picker</h1>\n      <div className={styles.Container}>\n        <div className=\"text-center\">\n          <Button\n            ref={refs.setReference}\n            className={styles.Trigger}\n            aria-label=\"Choose emoji\"\n            aria-describedby=\"emoji-label\"\n            data-open={open ? '' : undefined}\n            {...getReferenceProps()}\n          >\n            ☻\n          </Button>\n          <br />\n          {selectedEmoji && (\n            <span id=\"emoji-label\">\n              <span\n                style={{ fontSize: 30 }}\n                aria-label={emojis.find(({ emoji }) => emoji === selectedEmoji)?.name}\n              >\n                {selectedEmoji}\n              </span>{' '}\n              selected\n            </span>\n          )}\n          <FloatingPortal>\n            {open && (\n              <FloatingFocusManager context={context} modal={false}>\n                <div\n                  ref={refs.setFloating}\n                  className={styles.Floating}\n                  style={floatingStyles}\n                  {...getFloatingProps(getListFloatingProps())}\n                >\n                  <span className={styles.Label}>Emoji Picker</span>\n                  <input\n                    className={styles.Input}\n                    placeholder=\"Search emoji\"\n                    value={search}\n                    aria-controls={filteredEmojis.length === 0 ? noResultsId : undefined}\n                    {...getInputProps({\n                      onChange: handleInputChange,\n                      onKeyDown: handleKeyDown,\n                    })}\n                  />\n                  {filteredEmojis.length === 0 && (\n                    <p\n                      key={search}\n                      id={noResultsId}\n                      role=\"region\"\n                      aria-atomic=\"true\"\n                      aria-live=\"assertive\"\n                    >\n                      No results.\n                    </p>\n                  )}\n                  {filteredEmojis.length > 0 && (\n                    <div className={styles.Listbox} role=\"listbox\">\n                      {filteredEmojis.map(({ name, emoji }, index) => (\n                        <Option\n                          key={name}\n                          name={name}\n                          ref={(node) => {\n                            listRef.current[index] = node;\n                          }}\n                          selected={selectedEmoji === emoji}\n                          active={activeIndex === index}\n                          {...getItemProps({\n                            onClick: handleEmojiClick,\n                          })}\n                        >\n                          {emoji}\n                        </Option>\n                      ))}\n                    </div>\n                  )}\n                  <span\n                    data-testid=\"emoji-picker-active-index\"\n                    data-active-index={activeIndex ?? ''}\n                    style={{ display: 'none' }}\n                  />\n                </div>\n              </FloatingFocusManager>\n            )}\n          </FloatingPortal>\n        </div>\n      </div>\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "packages/react/test/floating-ui-tests/Grid.module.css",
    "content": ".Grid {\n  display: grid;\n  gap: 8px;\n}\n\n.Item {\n  border: 1px solid #000;\n}\n\n.Item:disabled {\n  opacity: 0.2;\n}\n"
  },
  {
    "path": "packages/react/test/floating-ui-tests/Grid.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport {\n  FloatingFocusManager,\n  useClick,\n  useDismiss,\n  useFloating,\n  useInteractions,\n  useListNavigation,\n} from '../../src/floating-ui-react';\nimport styles from './Grid.module.css';\n\ninterface Props {\n  orientation?: 'horizontal' | 'both';\n  loopFocus?: boolean;\n}\n\n/** @internal */\nexport function Main({ orientation = 'horizontal', loopFocus = false }: Props) {\n  const [open, setOpen] = React.useState(false);\n  const [activeIndex, setActiveIndex] = React.useState<number | null>(null);\n\n  const listRef = React.useRef<Array<HTMLElement | null>>([]);\n\n  const { floatingStyles, refs, context } = useFloating({\n    open,\n    onOpenChange: setOpen,\n    placement: 'bottom-start',\n  });\n\n  const disabledIndices = [0, 1, 2, 3, 4, 5, 6, 7, 10, 15, 45, 48];\n\n  const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions([\n    useClick(context),\n    useListNavigation(context, {\n      listRef,\n      activeIndex,\n      onNavigate: setActiveIndex,\n      cols: 5,\n      orientation,\n      loopFocus,\n      openOnArrowKeyDown: false,\n      disabledIndices,\n    }),\n    useDismiss(context),\n  ]);\n\n  return (\n    <React.Fragment>\n      <h1>Grid</h1>\n      <div className={styles.Container}>\n        <button ref={refs.setReference} type=\"button\" {...getReferenceProps()}>\n          Reference\n        </button>\n        {open && (\n          <FloatingFocusManager context={context}>\n            <div\n              role=\"menu\"\n              ref={refs.setFloating}\n              data-testid=\"floating\"\n              className={styles.Grid}\n              style={{\n                ...floatingStyles,\n                gridTemplateColumns: '100px 100px 100px 100px 100px',\n                zIndex: 999,\n              }}\n              {...getFloatingProps()}\n            >\n              {[...Array(49)].map((_, index) => (\n                <button\n                  type=\"button\"\n                  role=\"option\"\n                  key={index}\n                  aria-selected={activeIndex === index}\n                  tabIndex={activeIndex === index ? 0 : -1}\n                  disabled={disabledIndices.includes(index)}\n                  ref={(node) => {\n                    listRef.current[index] = node;\n                  }}\n                  className={styles.Item}\n                  {...getItemProps()}\n                >\n                  Item {index}\n                </button>\n              ))}\n            </div>\n          </FloatingFocusManager>\n        )}\n      </div>\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "packages/react/test/floating-ui-tests/ListboxFocus.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { CompositeList } from '../../src/composite/list/CompositeList';\nimport { useCompositeListItem } from '../../src/composite/list/useCompositeListItem';\nimport {\n  useFloating,\n  useInteractions,\n  useListNavigation,\n  useTypeahead,\n  useRole,\n} from '../../src/floating-ui-react';\n\ninterface SelectContextValue {\n  activeIndex: number | null;\n  selectedIndex: number | null;\n  getItemProps: ReturnType<typeof useInteractions>['getItemProps'];\n  handleSelect: (index: number | null) => void;\n}\n\nconst SelectContext = React.createContext<SelectContextValue>({} as SelectContextValue);\n\n/** @internal */\nfunction Listbox({ children }: { children: React.ReactNode }) {\n  const [activeIndex, setActiveIndex] = React.useState<number | null>(1);\n  const [selectedIndex, setSelectedIndex] = React.useState<number | null>(null);\n\n  const { refs, context } = useFloating({\n    open: true,\n  });\n\n  const elementsRef = React.useRef<Array<HTMLElement | null>>([]);\n  const labelsRef = React.useRef<Array<string | null>>([]);\n\n  const handleSelect = React.useCallback((index: number | null) => {\n    setSelectedIndex(index);\n  }, []);\n\n  function handleTypeaheadMatch(index: number | null) {\n    setActiveIndex(index);\n  }\n\n  const listNav = useListNavigation(context, {\n    listRef: elementsRef,\n    activeIndex,\n    selectedIndex,\n    onNavigate: setActiveIndex,\n    focusItemOnHover: false,\n  });\n  const typeahead = useTypeahead(context, {\n    listRef: labelsRef,\n    activeIndex,\n    selectedIndex,\n    onMatch: handleTypeaheadMatch,\n  });\n  const role = useRole(context, { role: 'listbox' });\n\n  const { getFloatingProps, getItemProps } = useInteractions([listNav, typeahead, role]);\n\n  const selectContext = React.useMemo(\n    () => ({\n      activeIndex,\n      selectedIndex,\n      getItemProps,\n      handleSelect,\n    }),\n    [activeIndex, selectedIndex, getItemProps, handleSelect],\n  );\n\n  return (\n    <SelectContext.Provider value={selectContext}>\n      <button onClick={() => setSelectedIndex(1)} data-testid=\"reference\" type=\"button\">\n        Select\n      </button>\n      <div ref={refs.setFloating} {...getFloatingProps()}>\n        <CompositeList elementsRef={elementsRef} labelsRef={labelsRef}>\n          {children}\n        </CompositeList>\n      </div>\n    </SelectContext.Provider>\n  );\n}\n\n/** @internal */\nfunction Option({ label }: { label: string }) {\n  const { activeIndex, selectedIndex, getItemProps, handleSelect } =\n    React.useContext(SelectContext);\n\n  const { ref, index } = useCompositeListItem({ label });\n\n  const isActive = activeIndex === index;\n  const isSelected = selectedIndex === index;\n\n  const isFocusable =\n    // eslint-disable-next-line no-nested-ternary\n    activeIndex !== null ? isActive : selectedIndex !== null ? isSelected : index === 0;\n\n  return (\n    <button\n      ref={ref}\n      type=\"button\"\n      role=\"option\"\n      aria-selected={isActive && isSelected}\n      tabIndex={isFocusable ? 0 : -1}\n      style={{\n        background: isActive ? 'cyan' : '',\n        fontWeight: isSelected ? 'bold' : '',\n      }}\n      {...getItemProps({\n        onClick: () => handleSelect(index),\n      })}\n    >\n      {label}\n    </button>\n  );\n}\n\n/** @internal */\nexport function Main() {\n  return (\n    <Listbox>\n      <Option label=\"Apple\" />\n      <Option label=\"Blueberry\" />\n      <Option label=\"Watermelon\" />\n      <Option label=\"Banana\" />\n    </Listbox>\n  );\n}\n"
  },
  {
    "path": "packages/react/test/floating-ui-tests/Menu.module.css",
    "content": ".Trigger {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 1rem;\n  border-radius: 0.25rem;\n  padding: 4px 8px;\n  text-align: left;\n}\n\n.TriggerNested:focus {\n  background: #3b82f6;\n  outline: none;\n  color: #fff;\n}\n\n.TriggerNestedOpenNoFocus {\n  background: #3b82f6;\n  color: #fff;\n}\n\n.TriggerNestedOpenHasFocus {\n  background: #e2e8f0;\n  border-radius: 0.25rem;\n  padding: 4px 8px;\n}\n\n.TriggerRootOpen {\n  background: #e2e8f0;\n}\n\n.Icon {\n  margin-left: 1rem;\n}\n\n.Panel {\n  border: 1px solid rgb(15 23 42 / 0.1);\n  border-radius: 0.25rem;\n  background: #fff;\n  background-clip: padding-box;\n  padding: 4px;\n  box-shadow:\n    0 10px 15px -3px rgb(0 0 0 / 0.1),\n    0 4px 6px -4px rgb(0 0 0 / 0.1);\n  outline: none;\n}\n\n.PanelFlex {\n  display: flex;\n  flex-direction: column;\n}\n\n.PanelGrid {\n  display: grid;\n  grid-template-columns: repeat(var(--cols), minmax(0, 1fr));\n  gap: 0.75rem;\n}\n\n.Item {\n  display: flex;\n  border-radius: 0.25rem;\n  padding: 4px 8px;\n  text-align: left;\n  outline: none;\n}\n\n.Item:focus {\n  background: #3b82f6;\n  color: #fff;\n}\n\n.ItemDisabled {\n  opacity: 0.4;\n}\n\n.Heading {\n  margin-bottom: 2rem;\n  font-size: 3rem;\n  font-weight: 700;\n}\n\n.Container {\n  margin-bottom: 1rem;\n  display: grid;\n  height: 20rem;\n  place-items: center;\n  border: 1px solid #94a3b8;\n  border-radius: 0.25rem;\n}\n\n@media (min-width: 1024px) {\n  .Container {\n    width: 40rem;\n  }\n}\n"
  },
  {
    "path": "packages/react/test/floating-ui-tests/Menu.tsx",
    "content": "'use client';\nimport c from 'clsx';\nimport * as React from 'react';\nimport { useMergedRefsN } from '@base-ui/utils/useMergedRefs';\nimport { CompositeList } from '../../src/composite/list/CompositeList';\nimport { useCompositeListItem } from '../../src/composite/list/useCompositeListItem';\nimport { getEmptyRootContext } from '../../src/floating-ui-react/utils/getEmptyRootContext';\nimport {\n  autoUpdate,\n  flip,\n  FloatingFocusManager,\n  FloatingNode,\n  FloatingPortal,\n  FloatingTree,\n  offset,\n  safePolygon,\n  shift,\n  useClick,\n  useDismiss,\n  useFloating,\n  useFloatingNodeId,\n  useFloatingParentNodeId,\n  useFloatingTree,\n  useHover,\n  useInteractions,\n  useListNavigation,\n  useRole,\n  useTypeahead,\n  useFocus,\n} from '../../src/floating-ui-react';\nimport styles from './Menu.module.css';\n\ntype MenuContextType = {\n  getItemProps: ReturnType<typeof useInteractions>['getItemProps'];\n  activeIndex: number | null;\n  setActiveIndex: React.Dispatch<React.SetStateAction<number | null>>;\n  setHasFocusInside: React.Dispatch<React.SetStateAction<boolean>>;\n  allowHover: boolean;\n  isOpen: boolean;\n  setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;\n  parent: MenuContextType | null;\n};\n\nconst MenuContext = React.createContext<MenuContextType>({\n  getItemProps: () => ({}),\n  activeIndex: null,\n  setActiveIndex: () => {},\n  setHasFocusInside: () => {},\n  allowHover: true,\n  isOpen: false,\n  setIsOpen: () => {},\n  parent: null,\n});\n\ninterface MenuProps {\n  label: string;\n  nested?: boolean;\n  children?: React.ReactNode;\n  keepMounted?: boolean;\n  orientation?: 'vertical' | 'horizontal' | 'both';\n  cols?: number;\n  openOnFocus?: boolean;\n}\n\n/** @internal */\nexport const MenuComponent = React.forwardRef<\n  HTMLButtonElement,\n  MenuProps & React.HTMLProps<HTMLButtonElement>\n>(function Menu(\n  {\n    children,\n    label,\n    keepMounted = false,\n    cols,\n    orientation: orientationOption,\n    openOnFocus = false,\n    ...props\n  },\n  forwardedRef,\n) {\n  const [isOpen, setIsOpen] = React.useState(false);\n  const [activeIndex, setActiveIndex] = React.useState<number | null>(null);\n  const [allowHover, setAllowHover] = React.useState(false);\n  const [hasFocusInside, setHasFocusInside] = React.useState(false);\n\n  const elementsRef = React.useRef<Array<HTMLButtonElement | null>>([]);\n  const labelsRef = React.useRef<Array<string | null>>([]);\n\n  const tree = useFloatingTree();\n  const nodeId = useFloatingNodeId();\n  const parentId = useFloatingParentNodeId();\n  const isNested = parentId != null;\n  const orientation = orientationOption ?? (cols ? 'both' : 'vertical');\n\n  const parent = React.useContext(MenuContext);\n  const item = useCompositeListItem();\n\n  const { floatingStyles, refs, context } = useFloating({\n    nodeId,\n    open: isOpen,\n    onOpenChange: setIsOpen,\n    placement: isNested ? 'right-start' : 'bottom-start',\n    middleware: [\n      offset({ mainAxis: isNested ? 0 : 4, alignmentAxis: isNested ? -4 : 0 }),\n      flip(),\n      shift(),\n    ],\n    whileElementsMounted: autoUpdate,\n  });\n  const fallbackContext = React.useMemo(() => getEmptyRootContext(), []);\n  const hoverContext = isNested && allowHover ? context : fallbackContext;\n\n  const hover = useHover(hoverContext, {\n    delay: { open: 75 },\n    handleClose: safePolygon({ blockPointerEvents: true }),\n  });\n  const click = useClick(context, {\n    event: 'mousedown',\n    toggle: !isNested || !allowHover,\n    ignoreMouse: isNested,\n  });\n  const focus = useFocus(context, { enabled: openOnFocus });\n  const role = useRole(context, { role: 'menu' });\n  const dismiss = useDismiss(context, { bubbles: true });\n  const listNavigation = useListNavigation(context, {\n    listRef: elementsRef,\n    activeIndex,\n    nested: isNested,\n    onNavigate: setActiveIndex,\n    orientation,\n    cols,\n  });\n  const typeahead = useTypeahead(context, {\n    listRef: labelsRef,\n    onMatch: isOpen ? setActiveIndex : undefined,\n    activeIndex,\n  });\n\n  const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions([\n    hover,\n    click,\n    role,\n    dismiss,\n    focus,\n    listNavigation,\n    typeahead,\n  ]);\n\n  // Event emitter allows you to communicate across tree components.\n  // This effect closes all menus when an item gets clicked anywhere\n  // in the tree.\n  React.useEffect(() => {\n    if (!tree) {\n      return undefined;\n    }\n\n    function handleTreeClick() {\n      setIsOpen(false);\n    }\n\n    function onSubMenuOpen(event: { nodeId: string; parentId: string }) {\n      if (event.nodeId !== nodeId && event.parentId === parentId) {\n        setIsOpen(false);\n      }\n    }\n\n    tree.events.on('click', handleTreeClick);\n    tree.events.on('menuopen', onSubMenuOpen);\n\n    return () => {\n      tree.events.off('click', handleTreeClick);\n      tree.events.off('menuopen', onSubMenuOpen);\n    };\n  }, [tree, nodeId, parentId]);\n\n  React.useEffect(() => {\n    if (isOpen && tree) {\n      tree.events.emit('menuopen', { parentId, nodeId });\n    }\n  }, [tree, isOpen, nodeId, parentId]);\n\n  // Determine if \"hover\" logic can run based on the modality of input. This\n  // prevents unwanted focus synchronization as menus open and close with\n  // keyboard navigation and the cursor is resting on the menu.\n  React.useEffect(() => {\n    function onPointerMove({ pointerType }: PointerEvent) {\n      if (pointerType !== 'touch') {\n        setAllowHover(true);\n      }\n    }\n\n    function onKeyDown() {\n      setAllowHover(false);\n    }\n\n    window.addEventListener('pointermove', onPointerMove, {\n      once: true,\n      capture: true,\n    });\n    window.addEventListener('keydown', onKeyDown, true);\n    return () => {\n      window.removeEventListener('pointermove', onPointerMove, {\n        capture: true,\n      });\n      window.removeEventListener('keydown', onKeyDown, true);\n    };\n  }, [allowHover]);\n\n  return (\n    <FloatingNode id={nodeId}>\n      <button\n        type=\"button\"\n        ref={useMergedRefsN([refs.setReference, item.ref, forwardedRef])}\n        data-open={isOpen ? '' : undefined}\n        // eslint-disable-next-line no-nested-ternary\n        tabIndex={!isNested ? props.tabIndex : parent.activeIndex === item.index ? 0 : -1}\n        className={c(props.className || styles.Trigger, {\n          [styles.TriggerNested]: isNested,\n          [styles.TriggerNestedOpenNoFocus]: isOpen && isNested && !hasFocusInside,\n          [styles.TriggerNestedOpenHasFocus]: isNested && isOpen && hasFocusInside,\n          [styles.TriggerRootOpen]: !isNested && isOpen,\n        })}\n        {...getReferenceProps(\n          parent.getItemProps({\n            ...props,\n            onFocus(event: React.FocusEvent<HTMLButtonElement>) {\n              props.onFocus?.(event);\n              setHasFocusInside(false);\n              parent.setHasFocusInside(true);\n            },\n            onMouseEnter(event: React.MouseEvent<HTMLButtonElement>) {\n              props.onMouseEnter?.(event);\n              if (parent.allowHover && parent.isOpen) {\n                parent.setActiveIndex(item.index);\n              }\n            },\n          }),\n        )}\n      >\n        {label}\n        {isNested && (\n          <span aria-hidden className={styles.Icon}>\n            Icon\n          </span>\n        )}\n      </button>\n      <MenuContext.Provider\n        // eslint-disable-next-line react/jsx-no-constructed-context-values\n        value={{\n          activeIndex,\n          setActiveIndex,\n          getItemProps,\n          setHasFocusInside,\n          allowHover,\n          isOpen,\n          setIsOpen,\n          parent,\n        }}\n      >\n        <CompositeList elementsRef={elementsRef} labelsRef={labelsRef}>\n          {(keepMounted || isOpen) && (\n            <FloatingPortal>\n              <FloatingFocusManager\n                context={context}\n                modal={false}\n                initialFocus={!isNested}\n                returnFocus={!isNested}\n              >\n                <div\n                  ref={refs.setFloating}\n                  className={c(\n                    styles.Panel,\n                    {\n                      [styles.PanelFlex]: !cols,\n                    },\n                    {\n                      [styles.PanelGrid]: cols,\n                    },\n                  )}\n                  style={{\n                    ...floatingStyles,\n                    // @ts-expect-error css var\n                    '--cols': cols,\n                    // eslint-disable-next-line no-nested-ternary\n                    visibility: !keepMounted ? undefined : isOpen ? 'visible' : 'hidden',\n                  }}\n                  aria-hidden={!isOpen}\n                  {...getFloatingProps()}\n                >\n                  {children}\n                </div>\n              </FloatingFocusManager>\n            </FloatingPortal>\n          )}\n        </CompositeList>\n      </MenuContext.Provider>\n    </FloatingNode>\n  );\n});\n\ninterface MenuItemProps {\n  label: string;\n  disabled?: boolean;\n}\n\n/** @internal */\nexport const MenuItem = React.forwardRef<\n  HTMLButtonElement,\n  MenuItemProps & React.ButtonHTMLAttributes<HTMLButtonElement>\n>(function MenuItem({ label, disabled, ...props }, forwardedRef) {\n  const menu = React.useContext(MenuContext);\n  const item = useCompositeListItem({ label: disabled ? null : label });\n  const tree = useFloatingTree();\n  const isActive = item.index === menu.activeIndex;\n\n  return (\n    <button\n      {...props}\n      ref={useMergedRefsN([item.ref, forwardedRef])}\n      type=\"button\"\n      role=\"menuitem\"\n      disabled={disabled}\n      tabIndex={isActive ? 0 : -1}\n      className={c(styles.Item, { [styles.ItemDisabled]: disabled })}\n      {...menu.getItemProps({\n        active: isActive,\n        onClick(event: React.MouseEvent<HTMLButtonElement>) {\n          props.onClick?.(event);\n          tree?.events.emit('click');\n        },\n        onFocus(event: React.FocusEvent<HTMLButtonElement>) {\n          props.onFocus?.(event);\n          menu.setHasFocusInside(true);\n        },\n        onMouseEnter(event: React.MouseEvent<HTMLButtonElement>) {\n          props.onMouseEnter?.(event);\n          if (menu.allowHover && menu.isOpen) {\n            menu.setActiveIndex(item.index);\n          }\n        },\n        onKeyDown(event) {\n          function closeParents(parent: MenuContextType | null) {\n            parent?.setIsOpen(false);\n            if (parent?.parent) {\n              closeParents(parent.parent);\n            }\n          }\n\n          if (\n            event.key === 'ArrowRight' &&\n            // If the root reference is in a menubar, close parents\n            tree?.nodesRef.current[0].context?.elements.domReference?.closest('[role=\"menubar\"]')\n          ) {\n            closeParents(menu.parent);\n          }\n        },\n      })}\n    >\n      {label}\n    </button>\n  );\n});\n\n/** @internal */\nexport const Menu = React.forwardRef<\n  HTMLButtonElement,\n  MenuProps & React.HTMLProps<HTMLButtonElement>\n>(function MenuWrapper(props, ref) {\n  const parentId = useFloatingParentNodeId();\n\n  if (parentId === null) {\n    return (\n      <FloatingTree>\n        <MenuComponent {...props} ref={ref} />\n      </FloatingTree>\n    );\n  }\n\n  return <MenuComponent {...props} ref={ref} />;\n});\n\n/** @internal */\nexport function Main() {\n  /* eslint-disable no-console */\n  return (\n    <React.Fragment>\n      <h1 className={styles.Heading}>Menu</h1>\n      <div className={styles.Container}>\n        <Menu label=\"Edit\">\n          <MenuItem label=\"Undo\" onClick={() => console.log('Undo')} />\n          <MenuItem label=\"Redo\" />\n          <MenuItem label=\"Cut\" disabled />\n          <Menu label=\"Copy as\" keepMounted>\n            <MenuItem label=\"Text\" />\n            <MenuItem label=\"Video\" />\n            <Menu label=\"Image\" keepMounted cols={2} orientation=\"horizontal\">\n              <MenuItem label=\".png\" />\n              <MenuItem label=\".jpg\" />\n              <MenuItem label=\".svg\" />\n              <MenuItem label=\".gif\" />\n            </Menu>\n            <MenuItem label=\"Audio\" />\n          </Menu>\n          <Menu label=\"Share\">\n            <MenuItem label=\"Mail\" />\n            <MenuItem label=\"Instagram\" />\n          </Menu>\n        </Menu>\n      </div>\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "packages/react/test/floating-ui-tests/MenuOrientation.module.css",
    "content": ".Trigger {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 1rem;\n  border-radius: 0.25rem;\n  padding: 4px 8px;\n  text-align: left;\n}\n\n.TriggerNested:focus {\n  background: #3b82f6;\n  outline: none;\n  color: #fff;\n}\n\n.TriggerNestedOpenNoFocus {\n  background: #3b82f6;\n  color: #fff;\n}\n\n.TriggerNestedOpenHasFocus {\n  background: #e2e8f0;\n  border-radius: 0.25rem;\n  padding: 4px 8px;\n}\n\n.TriggerRootOpen {\n  background: #e2e8f0;\n}\n\n.Icon {\n  margin-left: 1rem;\n}\n\n.Panel {\n  border: 1px solid rgb(15 23 42 / 0.1);\n  border-radius: 0.25rem;\n  background: #fff;\n  background-clip: padding-box;\n  padding: 4px;\n  box-shadow:\n    0 10px 15px -3px rgb(0 0 0 / 0.1),\n    0 4px 6px -4px rgb(0 0 0 / 0.1);\n  outline: none;\n}\n\n.PanelFlexCol {\n  display: flex;\n  flex-direction: column;\n}\n\n.PanelFlexRow {\n  display: flex;\n  flex-direction: row;\n}\n\n.PanelGrid {\n  display: grid;\n  grid-template-columns: repeat(var(--cols), minmax(0, 1fr));\n  gap: 0.75rem;\n}\n\n.Item {\n  display: flex;\n  border-radius: 0.25rem;\n  padding: 4px 8px;\n  text-align: left;\n  outline: none;\n}\n\n.Item:focus {\n  background: #3b82f6;\n  color: #fff;\n}\n\n.ItemDisabled {\n  opacity: 0.4;\n}\n\n.Heading {\n  margin-bottom: 2rem;\n  font-size: 3rem;\n  font-weight: 700;\n}\n\n.Container {\n  margin-bottom: 1rem;\n  display: grid;\n  height: 20rem;\n  place-items: center;\n  border: 1px solid #94a3b8;\n  border-radius: 0.25rem;\n}\n\n@media (min-width: 1024px) {\n  .Container {\n    width: 40rem;\n  }\n}\n"
  },
  {
    "path": "packages/react/test/floating-ui-tests/MenuOrientation.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport c from 'clsx';\nimport { useMergedRefsN } from '@base-ui/utils/useMergedRefs';\nimport { CompositeList } from '../../src/composite/list/CompositeList';\nimport { useCompositeListItem } from '../../src/composite/list/useCompositeListItem';\nimport { getEmptyRootContext } from '../../src/floating-ui-react/utils/getEmptyRootContext';\nimport {\n  autoUpdate,\n  flip,\n  FloatingFocusManager,\n  FloatingNode,\n  FloatingPortal,\n  FloatingTree,\n  offset,\n  safePolygon,\n  shift,\n  useClick,\n  useDismiss,\n  useFloating,\n  useFloatingNodeId,\n  useFloatingParentNodeId,\n  useFloatingTree,\n  useHover,\n  useInteractions,\n  useListNavigation,\n  useRole,\n  useTypeahead,\n} from '../../src/floating-ui-react';\nimport styles from './MenuOrientation.module.css';\n\ntype MenuContextType = {\n  getItemProps: ReturnType<typeof useInteractions>['getItemProps'];\n  activeIndex: number | null;\n  setActiveIndex: React.Dispatch<React.SetStateAction<number | null>>;\n  setHasFocusInside: React.Dispatch<React.SetStateAction<boolean>>;\n  allowHover: boolean;\n  isOpen: boolean;\n  setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;\n  parent: MenuContextType | null;\n  orientation: 'vertical' | 'horizontal' | 'both';\n};\n\nconst MenuContext = React.createContext<MenuContextType>({\n  getItemProps: () => ({}),\n  activeIndex: null,\n  setActiveIndex: () => {},\n  setHasFocusInside: () => {},\n  allowHover: true,\n  isOpen: false,\n  setIsOpen: () => {},\n  parent: null,\n  orientation: 'vertical',\n});\n\ninterface MenuProps {\n  label: string;\n  nested?: boolean;\n  children?: React.ReactNode;\n  keepMounted?: boolean;\n  orientation?: 'vertical' | 'horizontal' | 'both';\n  cols?: number;\n}\n\n/** @internal */\nexport const MenuComponent = React.forwardRef<\n  HTMLButtonElement,\n  MenuProps & React.HTMLProps<HTMLButtonElement>\n>(function Menu(\n  { children, label, keepMounted = false, cols, orientation: orientationOption, ...props },\n  forwardedRef,\n) {\n  const [isOpen, setIsOpen] = React.useState(false);\n  const [activeIndex, setActiveIndex] = React.useState<number | null>(null);\n  const [allowHover, setAllowHover] = React.useState(false);\n  const [hasFocusInside, setHasFocusInside] = React.useState(false);\n\n  const elementsRef = React.useRef<Array<HTMLButtonElement | null>>([]);\n  const labelsRef = React.useRef<Array<string | null>>([]);\n\n  const tree = useFloatingTree();\n  const nodeId = useFloatingNodeId();\n  const parentId = useFloatingParentNodeId();\n  const isNested = parentId != null;\n  const orientation = orientationOption ?? (cols ? 'both' : 'vertical');\n\n  const parent = React.useContext(MenuContext);\n  const item = useCompositeListItem();\n\n  const { floatingStyles, refs, context } = useFloating({\n    nodeId,\n    open: isOpen,\n    onOpenChange: setIsOpen,\n    placement: isNested ? 'right-start' : 'bottom-start',\n    middleware: [\n      offset({ mainAxis: isNested ? 0 : 4, alignmentAxis: isNested ? -4 : 0 }),\n      flip(),\n      shift(),\n    ],\n    whileElementsMounted: autoUpdate,\n  });\n  const fallbackContext = React.useMemo(() => getEmptyRootContext(), []);\n  const hoverContext = isNested && allowHover ? context : fallbackContext;\n\n  const hover = useHover(hoverContext, {\n    delay: { open: 75 },\n    handleClose: safePolygon({ blockPointerEvents: true }),\n  });\n  const click = useClick(context, {\n    event: 'mousedown',\n    toggle: !isNested || !allowHover,\n    ignoreMouse: isNested,\n  });\n  const role = useRole(context, { role: 'menu' });\n  const dismiss = useDismiss(context, { bubbles: true });\n  const listNavigation = useListNavigation(context, {\n    listRef: elementsRef,\n    activeIndex,\n    nested: isNested,\n    onNavigate: setActiveIndex,\n    orientation,\n    cols,\n  });\n  const typeahead = useTypeahead(context, {\n    listRef: labelsRef,\n    onMatch: isOpen ? setActiveIndex : undefined,\n    activeIndex,\n  });\n\n  const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions([\n    hover,\n    click,\n    role,\n    dismiss,\n    listNavigation,\n    typeahead,\n  ]);\n\n  // Event emitter allows you to communicate across tree components.\n  // This effect closes all menus when an item gets clicked anywhere\n  // in the tree.\n  React.useEffect(() => {\n    if (!tree) {\n      return;\n    }\n\n    function handleTreeClick() {\n      setIsOpen(false);\n    }\n\n    function onSubMenuOpen(event: { nodeId: string; parentId: string }) {\n      if (event.nodeId !== nodeId && event.parentId === parentId) {\n        setIsOpen(false);\n      }\n    }\n\n    tree.events.on('click', handleTreeClick);\n    tree.events.on('menuopen', onSubMenuOpen);\n\n    // eslint-disable-next-line consistent-return\n    return () => {\n      tree.events.off('click', handleTreeClick);\n      tree.events.off('menuopen', onSubMenuOpen);\n    };\n  }, [tree, nodeId, parentId]);\n\n  React.useEffect(() => {\n    if (isOpen && tree) {\n      tree.events.emit('menuopen', { parentId, nodeId });\n    }\n  }, [tree, isOpen, nodeId, parentId]);\n\n  // Determine if \"hover\" logic can run based on the modality of input. This\n  // prevents unwanted focus synchronization as menus open and close with\n  // keyboard navigation and the cursor is resting on the menu.\n  React.useEffect(() => {\n    function onPointerMove({ pointerType }: PointerEvent) {\n      if (pointerType !== 'touch') {\n        setAllowHover(true);\n      }\n    }\n\n    function onKeyDown() {\n      setAllowHover(false);\n    }\n\n    window.addEventListener('pointermove', onPointerMove, {\n      once: true,\n      capture: true,\n    });\n    window.addEventListener('keydown', onKeyDown, true);\n    return () => {\n      window.removeEventListener('pointermove', onPointerMove, {\n        capture: true,\n      });\n      window.removeEventListener('keydown', onKeyDown, true);\n    };\n  }, [allowHover]);\n\n  return (\n    <FloatingNode id={nodeId}>\n      <button\n        type=\"button\"\n        ref={useMergedRefsN([refs.setReference, item.ref, forwardedRef])}\n        data-open={isOpen ? '' : undefined}\n        // eslint-disable-next-line no-nested-ternary\n        tabIndex={!isNested ? props.tabIndex : parent.activeIndex === item.index ? 0 : -1}\n        className={c(props.className || styles.Trigger, {\n          [styles.TriggerNested]: isNested,\n          [styles.TriggerNestedOpenNoFocus]: isOpen && isNested && !hasFocusInside,\n          [styles.TriggerNestedOpenHasFocus]: isNested && isOpen && hasFocusInside,\n          [styles.TriggerRootOpen]: !isNested && isOpen,\n        })}\n        {...getReferenceProps(\n          parent.getItemProps({\n            ...props,\n            onFocus(event: React.FocusEvent<HTMLButtonElement>) {\n              props.onFocus?.(event);\n              setHasFocusInside(false);\n              parent.setHasFocusInside(true);\n            },\n            onMouseEnter(event: React.MouseEvent<HTMLButtonElement>) {\n              props.onMouseEnter?.(event);\n              if (parent.allowHover && parent.isOpen) {\n                parent.setActiveIndex(item.index);\n              }\n            },\n          }),\n        )}\n      >\n        {label}\n        {isNested && (\n          <span aria-hidden className={styles.Icon}>\n            Icon\n          </span>\n        )}\n      </button>\n      <MenuContext.Provider\n        // eslint-disable-next-line react/jsx-no-constructed-context-values\n        value={{\n          activeIndex,\n          setActiveIndex,\n          getItemProps,\n          setHasFocusInside,\n          allowHover,\n          isOpen,\n          setIsOpen,\n          parent,\n          orientation,\n        }}\n      >\n        <CompositeList elementsRef={elementsRef} labelsRef={labelsRef}>\n          {(keepMounted || isOpen) && (\n            <FloatingPortal>\n              <FloatingFocusManager\n                context={context}\n                modal={false}\n                initialFocus={!isNested}\n                returnFocus={!isNested}\n              >\n                <div\n                  ref={refs.setFloating}\n                  className={c(\n                    styles.Panel,\n                    {\n                      [styles.PanelFlexCol]: !cols && orientation !== 'horizontal',\n                    },\n                    {\n                      [styles.PanelFlexRow]: orientation === 'horizontal',\n                    },\n                    {\n                      [styles.PanelGrid]: cols,\n                    },\n                  )}\n                  style={{\n                    ...floatingStyles,\n                    // @ts-expect-error css var\n                    '--cols': cols,\n                    // eslint-disable-next-line no-nested-ternary\n                    visibility: !keepMounted ? undefined : isOpen ? 'visible' : 'hidden',\n                  }}\n                  aria-hidden={!isOpen}\n                  {...getFloatingProps()}\n                >\n                  {children}\n                </div>\n              </FloatingFocusManager>\n            </FloatingPortal>\n          )}\n        </CompositeList>\n      </MenuContext.Provider>\n    </FloatingNode>\n  );\n});\n\ninterface MenuItemProps {\n  label: string;\n  disabled?: boolean;\n}\n\n/** @internal */\nexport const MenuItem = React.forwardRef<\n  HTMLButtonElement,\n  MenuItemProps & React.ButtonHTMLAttributes<HTMLButtonElement>\n>(function MenuItem({ label, disabled, ...props }, forwardedRef) {\n  const menu = React.useContext(MenuContext);\n  const item = useCompositeListItem({ label: disabled ? null : label });\n  const tree = useFloatingTree();\n  const isActive = item.index === menu.activeIndex;\n\n  return (\n    <button\n      {...props}\n      ref={useMergedRefsN([item.ref, forwardedRef])}\n      type=\"button\"\n      role=\"menuitem\"\n      disabled={disabled}\n      tabIndex={isActive ? 0 : -1}\n      className={c(styles.Item, { [styles.ItemDisabled]: disabled })}\n      {...menu.getItemProps({\n        active: isActive,\n        onClick(event: React.MouseEvent<HTMLButtonElement>) {\n          props.onClick?.(event);\n          tree?.events.emit('click');\n        },\n        onFocus(event: React.FocusEvent<HTMLButtonElement>) {\n          props.onFocus?.(event);\n          menu.setHasFocusInside(true);\n        },\n        onMouseEnter(event: React.MouseEvent<HTMLButtonElement>) {\n          props.onMouseEnter?.(event);\n          if (menu.allowHover && menu.isOpen) {\n            menu.setActiveIndex(item.index);\n          }\n        },\n        onKeyDown(event) {\n          function closeParents(parent: MenuContextType | null) {\n            parent?.setIsOpen(false);\n            if (parent?.parent) {\n              closeParents(parent.parent);\n            }\n          }\n\n          if (\n            event.key === 'ArrowRight' &&\n            // If the root reference is in a menubar, close parents\n            tree?.nodesRef.current[0].context?.elements.domReference?.closest('[role=\"menubar\"]')\n          ) {\n            closeParents(menu.parent);\n          }\n        },\n      })}\n    >\n      {label}\n    </button>\n  );\n});\n\n/** @internal */\nexport const Menu = React.forwardRef<\n  HTMLButtonElement,\n  MenuProps & React.HTMLProps<HTMLButtonElement>\n>(function MenuWrapper(props, ref) {\n  const parentId = useFloatingParentNodeId();\n\n  if (parentId === null) {\n    return (\n      <FloatingTree>\n        <MenuComponent {...props} ref={ref} />\n      </FloatingTree>\n    );\n  }\n\n  return <MenuComponent {...props} ref={ref} />;\n});\n\n/** @internal */\nexport function HorizontalMenu() {\n  return (\n    <React.Fragment>\n      <h1 className={styles.Heading}>Horizontal menu</h1>\n      <div className={styles.Container}>\n        <Menu label=\"Edit\" orientation=\"horizontal\">\n          <MenuItem\n            label=\"Undo\"\n            onClick={() => {\n              // eslint-disable-next-line no-console\n              return console.log('Undo');\n            }}\n          />\n          <MenuItem label=\"Redo\" />\n          <MenuItem label=\"Cut\" disabled />\n          <Menu label=\"Copy as\" keepMounted>\n            <MenuItem label=\"Text\" />\n            <MenuItem label=\"Video\" />\n            <Menu label=\"Image\" keepMounted cols={2}>\n              <MenuItem label=\".png\" />\n              <MenuItem label=\".jpg\" />\n              <MenuItem label=\".svg\" />\n              <MenuItem label=\".gif\" />\n            </Menu>\n            <MenuItem label=\"Audio\" />\n          </Menu>\n          <Menu label=\"Share\">\n            <MenuItem label=\"Mail\" />\n            <MenuItem label=\"Instagram\" />\n          </Menu>\n        </Menu>\n      </div>\n    </React.Fragment>\n  );\n}\n\n/** @internal */\nexport function VerticalMenu() {\n  return (\n    <React.Fragment>\n      <h1 className={styles.Heading}>Vertical menu</h1>\n      <div className={styles.Container}>\n        <Menu label=\"Edit\">\n          <MenuItem\n            label=\"Undo\"\n            onClick={() => {\n              // eslint-disable-next-line no-console\n              return console.log('Undo');\n            }}\n          />\n          <MenuItem label=\"Redo\" />\n          <MenuItem label=\"Cut\" disabled />\n          <Menu label=\"Copy as\" keepMounted orientation=\"horizontal\">\n            <MenuItem label=\"Text\" />\n            <MenuItem label=\"Video\" />\n            <Menu label=\"Image\" keepMounted cols={2}>\n              <MenuItem label=\".png\" />\n              <MenuItem label=\".jpg\" />\n              <MenuItem label=\".svg\" />\n              <MenuItem label=\".gif\" />\n            </Menu>\n            <MenuItem label=\"Audio\" />\n          </Menu>\n          <Menu label=\"Share\">\n            <MenuItem label=\"Mail\" />\n            <MenuItem label=\"Instagram\" />\n          </Menu>\n        </Menu>\n      </div>\n    </React.Fragment>\n  );\n}\n\n/** @internal */\nexport function HorizontalMenuWithHorizontalSubmenus() {\n  return (\n    <React.Fragment>\n      <h1 className={styles.Heading}>Horizontal menu with horizontal submenus</h1>\n      <div className={styles.Container}>\n        <Menu label=\"Edit\" orientation=\"horizontal\">\n          <MenuItem\n            label=\"Undo\"\n            onClick={() => {\n              // eslint-disable-next-line no-console\n              return console.log('Undo');\n            }}\n          />\n          <MenuItem label=\"Redo\" />\n          <MenuItem label=\"Cut\" disabled />\n          <Menu label=\"Copy as\" keepMounted orientation=\"horizontal\">\n            <MenuItem label=\"Text\" />\n            <MenuItem label=\"Video\" />\n            <Menu label=\"Image\" keepMounted cols={2}>\n              <MenuItem label=\".png\" />\n              <MenuItem label=\".jpg\" />\n              <MenuItem label=\".svg\" />\n              <MenuItem label=\".gif\" />\n            </Menu>\n            <MenuItem label=\"Audio\" />\n          </Menu>\n          <Menu label=\"Share\">\n            <MenuItem label=\"Mail\" />\n            <MenuItem label=\"Instagram\" />\n          </Menu>\n        </Menu>\n      </div>\n    </React.Fragment>\n  );\n}\n\n/** @internal */\nexport function Main() {\n  return (\n    <React.Fragment>\n      <HorizontalMenu />\n      <VerticalMenu />\n      <HorizontalMenuWithHorizontalSubmenus />\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "packages/react/test/floating-ui-tests/Navigation.module.css",
    "content": ".Heading {\n  margin-bottom: 2rem;\n  font-size: 3rem;\n  font-weight: 700;\n}\n\n.Container {\n  margin-bottom: 1rem;\n  display: grid;\n  height: 20rem;\n  place-items: center;\n  border: 1px solid #94a3b8;\n  border-radius: 0.25rem;\n}\n\n@media (min-width: 1024px) {\n  .Container {\n    width: 40rem;\n  }\n}\n\n.Item {\n  background: #f1f5f9;\n  margin-top: 4px;\n  margin-bottom: 4px;\n  display: flex;\n  width: 12rem;\n  align-items: center;\n  justify-content: space-between;\n  border-radius: 0.25rem;\n  padding: 8px;\n}\n\n.SubItem {\n  display: block;\n  padding: 0.5rem 0.75rem;\n  color: #111827;\n  text-decoration: none;\n  border-radius: 0.25rem;\n}\n\n.Subnav {\n  background: #f1f5f9;\n  display: flex;\n  flex-direction: column;\n  overflow-y: auto;\n  border-radius: 0.25rem;\n  padding: 8px 16px;\n  backdrop-filter: blur(4px);\n  outline: none;\n}\n\n.SubnavList {\n  display: flex;\n  flex-direction: column;\n}\n"
  },
  {
    "path": "packages/react/test/floating-ui-tests/Navigation.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport { useMergedRefs } from '@base-ui/utils/useMergedRefs';\nimport { getEmptyRootContext } from '../../src/floating-ui-react/utils/getEmptyRootContext';\nimport {\n  flip,\n  FloatingFocusManager,\n  FloatingNode,\n  FloatingPortal,\n  offset,\n  safePolygon,\n  shift,\n  useDismiss,\n  useFloating,\n  useFloatingNodeId,\n  useFocus,\n  useHover,\n  useInteractions,\n} from '../../src/floating-ui-react';\nimport styles from './Navigation.module.css';\n\ninterface SubItemProps {\n  label: string;\n  href: string;\n}\n\n/** @internal */\nexport const NavigationSubItem = React.forwardRef<\n  HTMLAnchorElement,\n  SubItemProps & React.HTMLProps<HTMLAnchorElement>\n>(function NavigationSubItem({ label, href, ...props }, ref) {\n  return (\n    <a {...props} ref={ref} href={href} className={styles.SubItem}>\n      {label}\n    </a>\n  );\n});\n\ninterface ItemProps {\n  label: string;\n  href: string;\n  children?: React.ReactNode;\n}\n\n/** @internal */\nexport const NavigationItem = React.forwardRef<\n  HTMLAnchorElement,\n  ItemProps & React.HTMLProps<HTMLAnchorElement>\n>(function NavigationItem({ children, label, href, ...props }, ref) {\n  const [open, setOpen] = React.useState(false);\n  const hasChildren = !!children;\n  const fallbackContext = React.useMemo(() => getEmptyRootContext(), []);\n\n  const nodeId = useFloatingNodeId();\n\n  const { floatingStyles, refs, context } = useFloating({\n    open,\n    nodeId,\n    onOpenChange: setOpen,\n    middleware: [offset(8), flip(), shift()],\n    placement: 'right-start',\n  });\n\n  const { getReferenceProps, getFloatingProps } = useInteractions([\n    useHover(hasChildren ? context : fallbackContext, {\n      handleClose: safePolygon(),\n    }),\n    useFocus(context, {\n      enabled: hasChildren,\n    }),\n    useDismiss(context, {\n      enabled: hasChildren,\n    }),\n  ]);\n\n  const mergedReferenceRef = useMergedRefs(ref, refs.setReference);\n\n  return (\n    <FloatingNode id={nodeId}>\n      <li>\n        <a\n          href={href}\n          ref={mergedReferenceRef}\n          className={styles.Item}\n          {...getReferenceProps(props)}\n        >\n          {label}\n        </a>\n      </li>\n      <FloatingPortal>\n        {open && (\n          <FloatingFocusManager context={context} modal={false} initialFocus={false}>\n            <div\n              data-testid=\"subnavigation\"\n              ref={refs.setFloating}\n              className={styles.Subnav}\n              style={floatingStyles}\n              {...getFloatingProps()}\n            >\n              <button type=\"button\" onClick={() => setOpen(false)}>\n                Close\n              </button>\n              <ul className={styles.SubnavList}>{children}</ul>\n            </div>\n          </FloatingFocusManager>\n        )}\n      </FloatingPortal>\n    </FloatingNode>\n  );\n});\n\ninterface NavigationProps {\n  children?: React.ReactNode;\n}\n\n/** @internal */\nexport function Navigation(props: NavigationProps) {\n  return (\n    <nav>\n      <ul>{props.children}</ul>\n    </nav>\n  );\n}\n\n/** @internal */\nexport function Main() {\n  return (\n    <React.Fragment>\n      <h1 className={styles.Heading}>Navigation</h1>\n      <div className={styles.Container}>\n        <Navigation>\n          <NavigationItem label=\"Home\" href=\"#\" />\n          <NavigationItem label=\"Product\" href=\"#\">\n            <NavigationSubItem label=\"Link 1\" href=\"#\" />\n            <NavigationSubItem label=\"Link 2\" href=\"#\" />\n            <NavigationSubItem label=\"Link 3\" href=\"#\" />\n          </NavigationItem>\n          <NavigationItem label=\"About\" href=\"#\" />\n        </Navigation>\n      </div>\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "packages/react/test/floating-ui-tests/Popover.module.css",
    "content": ".Heading {\n  margin-bottom: 2rem;\n  font-size: 3rem;\n  font-weight: 700;\n}\n\n.Container {\n  margin-bottom: 1rem;\n  display: grid;\n  height: 20rem;\n  place-items: center;\n  border: 1px solid #94a3b8;\n  border-radius: 0.25rem;\n}\n\n@media (min-width: 1024px) {\n  .Container {\n    width: 40rem;\n  }\n}\n\n.Title {\n  margin-bottom: 8px;\n  font-size: 1.5rem;\n  font-weight: 700;\n}\n\n.Description {\n  margin-bottom: 8px;\n}\n\n.CloseButton {\n  font-weight: 700;\n}\n\n.Floating {\n  border: 1px solid rgb(15 23 42 / 0.1);\n  border-radius: 0.25rem;\n  background: #fff;\n  background-clip: padding-box;\n  padding: 24px 16px;\n  box-shadow:\n    0 4px 6px -1px rgb(0 0 0 / 0.1),\n    0 2px 4px -2px rgb(0 0 0 / 0.1);\n}\n"
  },
  {
    "path": "packages/react/test/floating-ui-tests/Popover.tsx",
    "content": "/* eslint-disable @typescript-eslint/no-shadow */\n'use client';\nimport * as React from 'react';\nimport { getEmptyRootContext } from '../../src/floating-ui-react/utils/getEmptyRootContext';\nimport type { Placement } from '../../src/floating-ui-react/types';\nimport {\n  autoUpdate,\n  flip,\n  FloatingFocusManager,\n  FloatingNode,\n  FloatingPortal,\n  FloatingTree,\n  offset,\n  safePolygon,\n  shift,\n  useClick,\n  useDismiss,\n  useFloating,\n  useFloatingNodeId,\n  useFloatingParentNodeId,\n  useHover,\n  useInteractions,\n  useRole,\n} from '../../src/floating-ui-react';\nimport styles from './Popover.module.css';\n\n/** @internal */\nexport function Main() {\n  return (\n    <React.Fragment>\n      <h1 className={styles.Heading}>Popover</h1>\n      <div className={styles.Container}>\n        <Popover\n          modal\n          bubbles\n          render={({ labelId, descriptionId, close }) => (\n            <React.Fragment>\n              <h2 id={labelId} className={styles.Title}>\n                Title\n              </h2>\n              <p id={descriptionId} className={styles.Description}>\n                Description\n              </p>\n              <Popover\n                modal\n                bubbles\n                render={({ labelId, descriptionId, close }) => (\n                  <React.Fragment>\n                    <h2 id={labelId} className={styles.Title}>\n                      Title\n                    </h2>\n                    <p id={descriptionId} className={styles.Description}>\n                      Description\n                    </p>\n                    <Popover\n                      modal\n                      bubbles={false}\n                      render={({ labelId, descriptionId, close }) => (\n                        <React.Fragment>\n                          <h2 id={labelId} className={styles.Title}>\n                            Title\n                          </h2>\n                          <p id={descriptionId} className={styles.Description}>\n                            Description\n                          </p>\n                          <button type=\"button\" onClick={close} className={styles.CloseButton}>\n                            Close\n                          </button>\n                        </React.Fragment>\n                      )}\n                    >\n                      <button type=\"button\">My button</button>\n                    </Popover>\n                    <button type=\"button\" onClick={close} className={styles.CloseButton}>\n                      Close\n                    </button>\n                  </React.Fragment>\n                )}\n              >\n                <button type=\"button\">My button</button>\n              </Popover>\n              <button type=\"button\" onClick={close} className={styles.CloseButton}>\n                Close\n              </button>\n            </React.Fragment>\n          )}\n        >\n          <button type=\"button\">My button</button>\n        </Popover>\n      </div>\n    </React.Fragment>\n  );\n}\ninterface Props {\n  render: (data: { close: () => void; labelId: string; descriptionId: string }) => React.ReactNode;\n  placement?: Placement;\n  modal?: boolean;\n  children?: React.ReactElement<HTMLElement>;\n  bubbles?: boolean;\n  hover?: boolean;\n}\n\n/** @internal */\nfunction PopoverComponent({\n  children,\n  render,\n  placement,\n  modal = true,\n  bubbles = true,\n  hover = false,\n}: Props) {\n  const [open, setOpen] = React.useState(false);\n\n  const nodeId = useFloatingNodeId();\n  const { floatingStyles, refs, context } = useFloating({\n    nodeId,\n    open,\n    placement,\n    onOpenChange: setOpen,\n    middleware: [offset(10), flip(), shift()],\n    whileElementsMounted: autoUpdate,\n  });\n\n  const id = React.useId();\n  const labelId = `${id}-label`;\n  const descriptionId = `${id}-description`;\n  const fallbackContext = React.useMemo(() => getEmptyRootContext(), []);\n\n  const { getReferenceProps, getFloatingProps } = useInteractions([\n    useHover(hover ? context : fallbackContext, {\n      handleClose: safePolygon({ blockPointerEvents: true }),\n    }),\n    useClick(context),\n    useRole(context),\n    useDismiss(context, {\n      bubbles,\n    }),\n  ]);\n\n  return (\n    <FloatingNode id={nodeId}>\n      {React.isValidElement(children) &&\n        React.cloneElement(\n          children,\n          getReferenceProps({\n            ref: refs.setReference,\n            'data-open': open ? '' : undefined,\n          } as React.HTMLProps<Element>),\n        )}\n      <FloatingPortal>\n        {open && (\n          <FloatingFocusManager context={context} modal={modal}>\n            <div\n              className={styles.Floating}\n              ref={refs.setFloating}\n              style={floatingStyles}\n              aria-labelledby={labelId}\n              aria-describedby={descriptionId}\n              {...getFloatingProps()}\n            >\n              {render({\n                labelId,\n                descriptionId,\n                close: () => setOpen(false),\n              })}\n            </div>\n          </FloatingFocusManager>\n        )}\n      </FloatingPortal>\n    </FloatingNode>\n  );\n}\n\n/** @internal */\nexport function Popover(props: Props) {\n  const parentId = useFloatingParentNodeId();\n\n  // This is a root, so we wrap it with the tree\n  if (parentId === null) {\n    return (\n      <FloatingTree>\n        <PopoverComponent {...props} />\n      </FloatingTree>\n    );\n  }\n\n  return <PopoverComponent {...props} />;\n}\n"
  },
  {
    "path": "packages/react/test/index.ts",
    "content": "export * from '@base-ui/utils/testUtils';\nexport { createRenderer } from './createRenderer';\nexport { describeConformance } from './describeConformance';\nexport { popupConformanceTests } from './popupConformanceTests';\nexport * from './wait';\n\n// Temporal\nexport { createTemporalRenderer } from './temporal';\nexport { describeGregorianAdapter } from './describeGregorianAdapter';\n"
  },
  {
    "path": "packages/react/test/popupConformanceTests.tsx",
    "content": "import * as React from 'react';\nimport { expect, vi } from 'vitest';\nimport { randomStringValue, screen, waitFor } from '@mui/internal-test-utils';\nimport { createRenderer, isJSDOM } from '#test-utils';\n\nexport function popupConformanceTests(config: PopupTestConfig) {\n  const {\n    createComponent,\n    triggerMouseAction,\n    render,\n    expectedPopupRole,\n    expectedAriaHasPopupValue = expectedPopupRole,\n    alwaysMounted: alwaysMountedParam = false,\n    combobox = false,\n  } = config;\n\n  const alwaysMounted = alwaysMountedParam === 'only-after-open' ? false : alwaysMountedParam;\n\n  const prepareComponent = (props: TestedComponentProps) => {\n    return createComponent({\n      ...props,\n      trigger: {\n        'data-testid': 'trigger',\n        ...props.trigger,\n      },\n      popup: {\n        'data-testid': 'popup',\n        ...props.popup,\n      },\n    });\n  };\n\n  describe('Popup conformance', () => {\n    describe('controlled mode', () => {\n      it('opens the popup with the `open` prop', async () => {\n        const { rerender } = await render(prepareComponent({ root: { open: false } }));\n        if (!alwaysMounted) {\n          expect(getPopup()).toBe(null);\n        } else {\n          expect(getPopup()).toBeInaccessible();\n        }\n\n        await rerender(prepareComponent({ root: { open: true } }));\n        expect(getPopup()).not.toBe(null);\n      });\n    });\n\n    if (triggerMouseAction === 'click') {\n      describe('uncontrolled mode', () => {\n        it('opens the popup when clicking on the trigger', async () => {\n          const { user } = await render(prepareComponent({}));\n\n          const trigger = getTrigger();\n          if (!alwaysMounted) {\n            expect(getPopup()).toBe(null);\n          } else {\n            expect(getPopup()).toBeInaccessible();\n          }\n\n          await user.click(trigger);\n          await waitFor(() => {\n            expect(getPopup()).not.toBe(null);\n          });\n        });\n      });\n    }\n\n    if (expectedPopupRole || triggerMouseAction === 'click') {\n      describe('ARIA attributes', () => {\n        if (expectedPopupRole) {\n          it(`has the ${expectedPopupRole} role on the popup`, async () => {\n            await render(prepareComponent({ root: { open: true } }));\n            const popup = getPopup();\n            expect(popup).not.toBe(null);\n            expect(popup).toHaveAttribute('role', expectedPopupRole);\n          });\n        }\n\n        if (triggerMouseAction === 'click') {\n          it('has the `aria-controls` attribute on the trigger', async () => {\n            await render(prepareComponent({ root: { open: true } }));\n            const trigger = getTrigger();\n            const popup = getPopup();\n            expect(trigger).toHaveAttribute('aria-controls', popup?.id);\n          });\n\n          it('has the `aria-expanded` attribute on the trigger when open', async () => {\n            const { user } = await render(prepareComponent({}));\n            const trigger = getTrigger();\n            if (!alwaysMounted) {\n              expect(getPopup()).toBe(null);\n            } else {\n              expect(getPopup()).toBeInaccessible();\n            }\n            expect(trigger).toHaveAttribute('aria-expanded', 'false');\n            await user.click(trigger);\n            await waitFor(() => {\n              if (combobox) {\n                expect(getPopup()).toHaveAttribute('role', 'listbox');\n              } else {\n                expect(getPopup()).toHaveAttribute('data-open');\n              }\n            });\n            expect(trigger).toHaveAttribute('aria-expanded', 'true');\n          });\n\n          if (expectedAriaHasPopupValue) {\n            it('has the `aria-haspopup` attribute on the trigger', async () => {\n              await render(prepareComponent({ root: { open: true } }));\n              const trigger = getTrigger();\n              expect(trigger).toHaveAttribute('aria-haspopup', expectedAriaHasPopupValue);\n            });\n          }\n\n          it('allows a custom `id` prop', async () => {\n            await render(prepareComponent({ root: { open: true }, popup: { id: 'TestId' } }));\n            const trigger = getTrigger();\n            const popup = getPopup();\n            expect(trigger.getAttribute('aria-controls')).toBe(popup?.getAttribute('id'));\n          });\n        }\n      });\n    }\n\n    describe('animations', () => {\n      beforeEach(() => {\n        globalThis.BASE_UI_ANIMATIONS_DISABLED = false;\n      });\n\n      afterEach(() => {\n        globalThis.BASE_UI_ANIMATIONS_DISABLED = true;\n      });\n\n      it('removes the popup when there is no exit animation defined', async ({ skip }) => {\n        if (isJSDOM) {\n          skip();\n        }\n\n        const { rerender } = await render(prepareComponent({ root: { open: true } }));\n\n        await waitFor(() => {\n          expect(getPopup()).not.toBe(null);\n        });\n\n        await rerender(prepareComponent({ root: { open: false } }));\n        await waitFor(() => {\n          if (!alwaysMounted && alwaysMountedParam !== 'only-after-open') {\n            expect(getPopup()).toBe(null);\n          } else {\n            expect(getPopup()).toBeInaccessible();\n          }\n        });\n      });\n\n      it('removes the popup when the animation finishes', async ({ skip }) => {\n        // XXX: revisit after feedback from the team\n        skip();\n\n        if (isJSDOM) {\n          skip();\n        }\n\n        const handleAnimationEnd = vi.fn();\n        const animationName = `anim-${randomStringValue()}`;\n\n        function Test(props: { open: boolean }) {\n          const style = `\n            @keyframes ${animationName} {\n              to {\n                opacity: 0;\n              }\n            }\n\n            .animation-test-popup-${animationName}[data-open] {\n              opacity: 1;\n            }\n\n            .animation-test-popup-${animationName}[data-ending-style] {\n              animation: ${animationName} 150ms;\n            }\n          `;\n\n          return (\n            <div>\n              {/* eslint-disable-next-line react/no-danger */}\n              <style dangerouslySetInnerHTML={{ __html: style }} />\n              {prepareComponent({\n                root: { open: props.open },\n                portal: { keepMounted: true },\n                popup: {\n                  className: `animation-test-popup-${animationName}`,\n                  onAnimationEnd: handleAnimationEnd,\n                },\n              })}\n            </div>\n          );\n        }\n\n        const { setProps } = await render(<Test open />);\n        await setProps({ open: false });\n\n        await waitFor(() => {\n          const popup = getPopup();\n          expect(popup).not.toBe(null);\n          expect(popup).toBeInaccessible();\n        });\n\n        await waitFor(() => {\n          expect(handleAnimationEnd).toHaveBeenCalledTimes(1);\n        });\n      });\n    });\n  });\n}\n\nfunction getTrigger() {\n  return screen.getByTestId('trigger');\n}\n\nfunction getPopup() {\n  return screen.queryByTestId('popup');\n}\n\nexport interface PopupTestConfig {\n  /**\n   * A function that returns a JSX tree with a component to test.\n   * Its parameters contain props to be spread on the component's parts.\n   */\n  createComponent: (props: TestedComponentProps) => React.JSX.Element;\n  /**\n   * How the popup is triggered.\n   */\n  triggerMouseAction: 'click' | 'hover';\n  /**\n   * Render function returned from `createRenderer`.\n   */\n  render: ReturnType<typeof createRenderer>['render'];\n  /**\n   * Expected `role` attribute of the popup element.\n   */\n  expectedPopupRole?: string;\n  /**\n   * Expected `aria-haspopup` attribute of the trigger element.\n   */\n  expectedAriaHasPopupValue?: string;\n  /**\n   * Whether the popup contents are always present in the DOM.\n   */\n  alwaysMounted?: boolean | 'only-after-open';\n  /**\n   * Whether the popup is a combobox.\n   */\n  combobox?: boolean;\n}\n\ninterface RootProps {\n  open?: boolean;\n  onOpenChange?: (open: boolean | null) => void;\n}\n\ninterface TriggerProps {\n  'data-testid'?: string;\n}\n\ninterface PopupProps {\n  className?: string;\n  id?: string;\n  'data-testid'?: string;\n  onAnimationEnd?: () => void;\n}\n\ninterface PortalProps {\n  keepMounted?: boolean;\n}\n\ninterface TestedComponentProps {\n  root?: RootProps;\n  popup?: PopupProps;\n  trigger?: TriggerProps;\n  portal?: PortalProps;\n}\n"
  },
  {
    "path": "packages/react/test/temporal.tsx",
    "content": "import * as React from 'react';\nimport { Locale } from 'date-fns/locale';\nimport { LocalizationProvider } from '@base-ui/react/localization-provider';\nimport {\n  createRenderer,\n  CreateRendererOptions,\n  MuiRenderResult,\n  RenderOptions,\n} from '@mui/internal-test-utils/createRenderer';\n// TODO Temporal: Replace with `@base-ui/react/types` import when Temporal components will become public.\nimport { TemporalAdapter } from '../src/types/temporal';\nimport { TemporalAdapterDateFns } from '../../react/src/temporal-adapter-date-fns/TemporalAdapterDateFns';\n\n/**\n * Returns a function to render a temporal component, wrapped with LocalizationProvider.\n */\nexport function createTemporalRenderer(\n  parameters: createTemporalRenderer.Parameters = {},\n): createTemporalRenderer.ReturnValue {\n  const { locale, clockConfig, ...createRendererOptions } = parameters;\n\n  const { render: clientRender } = createRenderer({\n    ...createRendererOptions,\n  });\n  beforeEach(() => {\n    if (clockConfig) {\n      vi.setSystemTime(clockConfig);\n    }\n  });\n  afterEach(() => {\n    if (clockConfig) {\n      vi.useRealTimers();\n    }\n  });\n\n  function Wrapper({ children }: { children?: React.ReactNode }) {\n    return <LocalizationProvider temporalLocale={locale}>{children}</LocalizationProvider>;\n  }\n\n  return {\n    render(element, options) {\n      return clientRender(element, { ...options, wrapper: Wrapper });\n    },\n    adapter: new TemporalAdapterDateFns({ locale }),\n  };\n}\n\nexport namespace createTemporalRenderer {\n  export interface Parameters extends Omit<CreateRendererOptions, 'clock' | 'clockOptions'> {\n    /**\n     * The locale to use for the tests.\n     * @default en-US\n     */\n    locale?: Locale;\n  }\n\n  export interface ReturnValue {\n    render(\n      node: React.ReactElement<DataAttributes>,\n      options?: Omit<RenderOptions, 'wrapper'>,\n    ): MuiRenderResult;\n    adapter: TemporalAdapter;\n  }\n}\n\ninterface DataAttributes {\n  [key: `data-${string}`]: string;\n}\n"
  },
  {
    "path": "packages/react/test/types.d.ts",
    "content": "declare module '*.module.css';\n"
  },
  {
    "path": "packages/react/test/wait.ts",
    "content": "/**\n * Waits for the specified number of milliseconds.\n */\nexport async function wait(ms: number) {\n  await new Promise((resolve) => {\n    setTimeout(resolve, ms);\n  });\n}\n\n/**\n * Waits for a single animation frame.\n */\nexport async function waitSingleFrame(): Promise<void> {\n  return new Promise((resolve) => {\n    requestAnimationFrame(() => {\n      resolve();\n    });\n  });\n}\n"
  },
  {
    "path": "packages/react/tsconfig.build.json",
    "content": "{\n  \"extends\": \"../../tsconfig.base.json\",\n  // This config is for emitting declarations (.d.ts) only.\n  // TS source files are transpiled to JS by Babel.\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"declaration\": true,\n    \"emitDeclarationOnly\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"bundler\",\n    \"noEmit\": false,\n    \"rootDir\": \"./src\",\n    \"outDir\": \"build/esm\",\n    \"types\": [\"@mui/internal-code-infra/build-env\"]\n  },\n  \"include\": [\"src/**/*.ts*\"],\n  \"exclude\": [\"src/**/*.spec.ts*\", \"src/**/*.test.ts*\"],\n  \"references\": [{ \"path\": \"../utils/tsconfig.build.json\" }]\n}\n"
  },
  {
    "path": "packages/react/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.base.json\",\n  \"references\": [\n    {\n      \"path\": \"./tsconfig.build.json\"\n    },\n    {\n      \"path\": \"./tsconfig.test.json\"\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/react/tsconfig.test.json",
    "content": "{\n  \"extends\": \"../../tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"declaration\": true,\n    \"emitDeclarationOnly\": true,\n    \"noEmit\": false,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"bundler\",\n    \"rootDir\": \".\",\n    \"outDir\": \"build-tests\",\n    \"types\": [\"vitest/globals\", \"@testing-library/jest-dom\"]\n  },\n  \"include\": [\n    \"vitest-env.d.ts\",\n    \"src/**/*.spec.ts*\",\n    \"src/**/*.test.ts*\",\n    \"src/index.test.ts\",\n    \"test\",\n    \"test/addVitestMatchers.ts\",\n    \"src/global.d.ts\",\n    \"package.json\"\n  ],\n  \"references\": [{ \"path\": \"./tsconfig.build.json\" }]\n}\n"
  },
  {
    "path": "packages/react/vitest-env.d.ts",
    "content": "declare const process: {\n  env: {\n    NODE_ENV?: string;\n    [key: string]: string | undefined;\n  };\n};\n"
  },
  {
    "path": "packages/react/vitest.config.mts",
    "content": "import { mergeConfig, defineProject } from 'vitest/config';\n// eslint-disable-next-line import/no-relative-packages\nimport sharedConfig from '../../vitest.shared.mts';\n\nexport default mergeConfig(\n  sharedConfig,\n  defineProject({\n    define: {\n      'process.env.NODE_ENV': JSON.stringify('test'),\n    },\n  }),\n);\n"
  },
  {
    "path": "packages/utils/.npmignore",
    "content": "*.tsbuildinfo\n"
  },
  {
    "path": "packages/utils/CHANGELOG.md",
    "content": "# Versions\n\n## 0.2.0\n\n_Nov 17, 2025_\n\n- Add infrastructure to support nested stores (#3037) by @michaldudak\n- Export `useScrollLock` (#3233) by @atomiks\n\n## 0.1.0\n\n_Jul 30, 2025_\n\nInitial release\n"
  },
  {
    "path": "packages/utils/MAINTAINERS.md",
    "content": "# Package rules\n\nThe utils package is shared across multiple codebases, which may cause issues with reference equality\nif different versions of the package are used in the same project. To avoid this, we should follow\nthese rules:\n\n- Avoid global variables as much as possible, in particular `React.Context` instances.\n- Avoid the `instanceof` operator for types defined in the utils package.\n"
  },
  {
    "path": "packages/utils/README.md",
    "content": "# @base-ui/utils\n\nA collection of React utility functions for Base UI.\n"
  },
  {
    "path": "packages/utils/package.json",
    "content": "{\n  \"name\": \"@base-ui/utils\",\n  \"version\": \"0.2.6\",\n  \"description\": \"A collection of React utility functions for Base UI.\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mui/base-ui.git\",\n    \"directory\": \"packages/utils\"\n  },\n  \"license\": \"MIT\",\n  \"exports\": {\n    \"./store\": \"./src/store/index.ts\",\n    \"./*\": \"./src/*.ts\"\n  },\n  \"imports\": {\n    \"#formatErrorMessage\": \"./src/formatErrorMessage.ts\"\n  },\n  \"type\": \"commonjs\",\n  \"scripts\": {\n    \"prebuild\": \"rimraf --glob build build-tests \\\"*.tsbuildinfo\\\"\",\n    \"build\": \"code-infra build --copy .npmignore\",\n    \"test:package\": \"publint --pack pnpm run ./build && attw --pack ./build --exclude-entrypoints package.json --exclude-entrypoints esm --exclude-entrypoints cjs\",\n    \"release\": \"pnpm build && pnpm publish\",\n    \"test\": \"cross-env VITEST_ENV=jsdom vitest\",\n    \"typescript\": \"tsc -b tsconfig.json\"\n  },\n  \"dependencies\": {\n    \"@babel/runtime\": \"^7.28.6\",\n    \"@floating-ui/utils\": \"^0.2.11\",\n    \"reselect\": \"^5.1.1\",\n    \"use-sync-external-store\": \"^1.6.0\"\n  },\n  \"devDependencies\": {\n    \"@testing-library/react\": \"16.3.2\",\n    \"@testing-library/user-event\": \"14.6.1\",\n    \"@types/react\": \"19.2.14\",\n    \"@types/react-dom\": \"19.2.3\",\n    \"@types/use-sync-external-store\": \"1.5.0\",\n    \"react\": \"19.2.4\",\n    \"react-dom\": \"19.2.4\",\n    \"typescript\": \"5.9.3\"\n  },\n  \"peerDependencies\": {\n    \"@types/react\": \"^17 || ^18 || ^19\",\n    \"react\": \"^17 || ^18 || ^19\",\n    \"react-dom\": \"^17 || ^18 || ^19\"\n  },\n  \"peerDependenciesMeta\": {\n    \"@types/react\": {\n      \"optional\": true\n    }\n  },\n  \"sideEffects\": false,\n  \"publishConfig\": {\n    \"access\": \"public\",\n    \"directory\": \"build\"\n  }\n}\n"
  },
  {
    "path": "packages/utils/src/detectBrowser.ts",
    "content": "interface NavigatorUAData {\n  brands: Array<{ brand: string; version: string }>;\n  mobile: boolean;\n  platform: string;\n}\n\nconst hasNavigator = typeof navigator !== 'undefined';\n\nconst nav = getNavigatorData();\nconst platform = getPlatform();\nconst userAgent = getUserAgent();\n\nexport const isWebKit =\n  typeof CSS === 'undefined' || !CSS.supports\n    ? false\n    : CSS.supports('-webkit-backdrop-filter:none');\n\nexport const isIOS =\n  // iPads can claim to be MacIntel\n  nav.platform === 'MacIntel' && nav.maxTouchPoints > 1\n    ? true\n    : /iP(hone|ad|od)|iOS/.test(nav.platform);\n\nexport const isFirefox = hasNavigator && /firefox/i.test(userAgent);\nexport const isSafari = hasNavigator && /apple/i.test(navigator.vendor);\nexport const isEdge = hasNavigator && /Edg/i.test(userAgent);\nexport const isAndroid = (hasNavigator && /android/i.test(platform)) || /android/i.test(userAgent);\nexport const isMac =\n  hasNavigator && platform.toLowerCase().startsWith('mac') && !navigator.maxTouchPoints;\nexport const isJSDOM = userAgent.includes('jsdom/');\n\n// Avoid Chrome DevTools blue warning.\nfunction getNavigatorData(): { platform: string; maxTouchPoints: number } {\n  if (!hasNavigator) {\n    return { platform: '', maxTouchPoints: -1 };\n  }\n\n  const uaData = (navigator as any).userAgentData as NavigatorUAData | undefined;\n\n  if (uaData?.platform) {\n    return {\n      platform: uaData.platform,\n      maxTouchPoints: navigator.maxTouchPoints,\n    };\n  }\n\n  return {\n    platform: navigator.platform ?? '',\n    maxTouchPoints: navigator.maxTouchPoints ?? -1,\n  };\n}\n\nfunction getUserAgent(): string {\n  if (!hasNavigator) {\n    return '';\n  }\n\n  const uaData = (navigator as any).userAgentData as NavigatorUAData | undefined;\n\n  if (uaData && Array.isArray(uaData.brands)) {\n    return uaData.brands.map(({ brand, version }) => `${brand}/${version}`).join(' ');\n  }\n\n  return navigator.userAgent;\n}\n\nfunction getPlatform(): string {\n  if (!hasNavigator) {\n    return '';\n  }\n\n  const uaData = (navigator as any).userAgentData as NavigatorUAData | undefined;\n\n  if (uaData?.platform) {\n    return uaData.platform;\n  }\n\n  return navigator.platform ?? '';\n}\n"
  },
  {
    "path": "packages/utils/src/empty.ts",
    "content": "export function NOOP() {}\n\nexport const EMPTY_ARRAY: readonly any[] = Object.freeze([]);\n\nexport const EMPTY_OBJECT = Object.freeze({});\n"
  },
  {
    "path": "packages/utils/src/error.ts",
    "content": "let set: Set<string>;\nif (process.env.NODE_ENV !== 'production') {\n  set = new Set<string>();\n}\n\nexport function error(...messages: string[]) {\n  if (process.env.NODE_ENV !== 'production') {\n    const messageKey = messages.join(' ');\n    if (!set.has(messageKey)) {\n      set.add(messageKey);\n      console.error(`Base UI: ${messageKey}`);\n    }\n  }\n}\n\nexport function reset() {\n  set?.clear();\n}\n"
  },
  {
    "path": "packages/utils/src/fastHooks.ts",
    "content": "import * as React from 'react';\nimport { useRefWithInit } from './useRefWithInit';\n\nexport type Instance = {\n  didInitialize: boolean;\n};\n\ntype HookType = {\n  before: (instance: any) => void;\n  after: (instance: any) => void;\n};\n\nconst hooks: HookType[] = [];\n\nlet currentInstance: Instance | undefined = undefined;\n\nexport function getInstance() {\n  return currentInstance;\n}\n\nexport function setInstance(instance: Instance | undefined): void {\n  currentInstance = instance;\n}\n\nexport function register(hook: HookType): void {\n  hooks.push(hook);\n}\n\nexport function fastComponent<P extends object, E extends HTMLElement, R extends React.ReactNode>(\n  fn: (props: P) => R,\n): typeof fn {\n  const FastComponent = (props: P, forwardedRef: React.Ref<E>): R => {\n    const instance = useRefWithInit(createInstance).current;\n\n    let result;\n    try {\n      currentInstance = instance;\n\n      for (const hook of hooks) {\n        hook.before(instance);\n      }\n\n      result = (fn as any)(props, forwardedRef);\n\n      for (const hook of hooks) {\n        hook.after(instance);\n      }\n\n      instance.didInitialize = true;\n    } finally {\n      currentInstance = undefined;\n    }\n\n    return result;\n  };\n  FastComponent.displayName = (fn as any).displayName || fn.name;\n  return FastComponent as unknown as typeof fn;\n}\n\nexport function fastComponentRef<P extends object, E extends Element, R extends React.ReactNode>(\n  fn: (props: React.PropsWithoutRef<P>, forwardedRef: React.Ref<E>) => R,\n) {\n  return React.forwardRef<E, P>(fastComponent(fn as any) as unknown as typeof fn);\n}\n\nfunction createInstance(): Instance {\n  return {\n    didInitialize: false,\n  };\n}\n"
  },
  {
    "path": "packages/utils/src/fastObjectShallowCompare.ts",
    "content": "// https://github.com/mui/mui-x/blob/master/packages/x-internals/src/fastObjectShallowCompare/fastObjectShallowCompare.ts\nconst is = Object.is;\n\nexport function fastObjectShallowCompare<T extends Record<string, any> | null>(a: T, b: T) {\n  if (a === b) {\n    return true;\n  }\n  if (!(a instanceof Object) || !(b instanceof Object)) {\n    return false;\n  }\n\n  let aLength = 0;\n  let bLength = 0;\n\n  /* eslint-disable guard-for-in */\n  for (const key in a) {\n    aLength += 1;\n\n    if (!is(a[key], b[key])) {\n      return false;\n    }\n    if (!(key in b)) {\n      return false;\n    }\n  }\n\n  /* eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-unused-vars */\n  for (const _ in b) {\n    bLength += 1;\n  }\n  return aLength === bLength;\n}\n"
  },
  {
    "path": "packages/utils/src/formatErrorMessage.test.ts",
    "content": "import { expect } from 'vitest';\nimport formatErrorMessage, { createFormatErrorMessage } from './formatErrorMessage';\n\ndescribe('formatErrorMessage', () => {\n  describe('default export', () => {\n    it('formats error message with code only', () => {\n      const result = formatErrorMessage(123);\n      expect(result).toBe(\n        'Base UI error #123; visit https://base-ui.com/production-error?code=123 for the full message.',\n      );\n    });\n\n    it('formats error message with code and args', () => {\n      const result = formatErrorMessage(456, 'arg1', 'arg2');\n      expect(result).toBe(\n        'Base UI error #456; visit https://base-ui.com/production-error?code=456&args%5B%5D=arg1&args%5B%5D=arg2 for the full message.',\n      );\n    });\n  });\n\n  describe('createFormatErrorMessage', () => {\n    it('creates a formatter with custom URL and prefix', () => {\n      const customFormatter = createFormatErrorMessage('https://example.com/errors', 'My Library');\n      const result = customFormatter(789);\n      expect(result).toBe(\n        'My Library error #789; visit https://example.com/errors?code=789 for the full message.',\n      );\n    });\n\n    it('creates a formatter that handles args correctly', () => {\n      const customFormatter = createFormatErrorMessage(\n        'https://custom.dev/error-page',\n        'Custom UI',\n      );\n      const result = customFormatter(100, 'foo', 'bar');\n      expect(result).toBe(\n        'Custom UI error #100; visit https://custom.dev/error-page?code=100&args%5B%5D=foo&args%5B%5D=bar for the full message.',\n      );\n    });\n\n    it('handles special characters in args', () => {\n      const customFormatter = createFormatErrorMessage('https://example.com/errors', 'Test');\n      const result = customFormatter(1, 'hello world', 'foo&bar');\n      expect(result).toBe(\n        'Test error #1; visit https://example.com/errors?code=1&args%5B%5D=hello+world&args%5B%5D=foo%26bar for the full message.',\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "packages/utils/src/formatErrorMessage.ts",
    "content": "/**\n * Creates a formatErrorMessage function with a custom URL and prefix.\n * @param baseUrl - The base URL for the error page (e.g., 'https://base-ui.com/production-error')\n * @param prefix - The prefix for the error message (e.g., 'Base UI')\n * @returns A function that formats error messages with the given URL and prefix\n */\nexport function createFormatErrorMessage(\n  baseUrl: string,\n  prefix: string,\n): (code: number, ...args: string[]) => string {\n  return function formatErrorMessage(code: number, ...args: string[]): string {\n    const url = new URL(baseUrl);\n    url.searchParams.set('code', code.toString());\n    args.forEach((arg) => url.searchParams.append('args[]', arg));\n    return `${prefix} error #${code}; visit ${url} for the full message.`;\n  };\n}\n\n/**\n * WARNING: Don't import this directly. It's imported by the code generated by\n * `@mui/internal-babel-plugin-minify-errors`. Make sure to always use string literals in `Error`\n * constructors to ensure the plugin works as expected. Supported patterns include:\n *   throw new Error('My message');\n *   throw new Error(`My message: ${foo}`);\n *   throw new Error(`My message: ${foo}` + 'another string');\n *   ...\n */\nconst formatErrorMessage = createFormatErrorMessage(\n  'https://base-ui.com/production-error',\n  'Base UI',\n);\n\nexport default formatErrorMessage;\n"
  },
  {
    "path": "packages/utils/src/generateId.ts",
    "content": "let counter = 0;\nexport function generateId(prefix: string) {\n  counter += 1;\n  return `${prefix}-${Math.random().toString(36).slice(2, 6)}-${counter}`;\n}\n"
  },
  {
    "path": "packages/utils/src/getReactElementRef.test.tsx",
    "content": "import { expect } from 'vitest';\nimport * as React from 'react';\nimport { getReactElementRef } from './getReactElementRef';\n\ndescribe('getReactElementRef', () => {\n  it('returns null when not provided a React element', () => {\n    expect(getReactElementRef(false)).toBe(null);\n    expect(getReactElementRef(undefined)).toBe(null);\n    expect(getReactElementRef(1)).toBe(null);\n\n    const children = [<div key=\"1\" />, <div key=\"2\" />];\n    expect(getReactElementRef(children)).toBe(null);\n  });\n\n  it('returns the ref of a React element', () => {\n    const ref = React.createRef<HTMLDivElement>();\n    const element = <div ref={ref} />;\n\n    expect(getReactElementRef(element)).toBe(ref);\n  });\n\n  it('returns null for a fragment', () => {\n    const element = (\n      <React.Fragment>\n        <p>Hello</p>\n        <p>Hello</p>\n      </React.Fragment>\n    );\n\n    expect(getReactElementRef(element)).toBe(null);\n  });\n\n  it('returns null for an element without a ref', () => {\n    const element = <div />;\n\n    expect(getReactElementRef(element)).toBe(null);\n  });\n});\n"
  },
  {
    "path": "packages/utils/src/getReactElementRef.ts",
    "content": "import * as React from 'react';\nimport { isReactVersionAtLeast } from './reactVersion';\n\n/**\n * Extracts the `ref` from a React element, handling different React versions.\n */\nexport function getReactElementRef(element: unknown): React.Ref<unknown> | null {\n  if (!React.isValidElement(element)) {\n    return null;\n  }\n\n  const reactElement = element as React.ReactElement & { ref?: React.Ref<unknown> | undefined };\n  const propsWithRef = reactElement.props as { ref?: React.Ref<unknown> | undefined } | undefined;\n\n  return (isReactVersionAtLeast(19) ? propsWithRef?.ref : reactElement.ref) ?? null;\n}\n"
  },
  {
    "path": "packages/utils/src/inertValue.ts",
    "content": "import { isReactVersionAtLeast } from './reactVersion';\n\nexport function inertValue(value?: boolean): boolean | undefined {\n  if (isReactVersionAtLeast(19)) {\n    return value;\n  }\n  // compatibility with React < 19\n  return (value ? 'true' : undefined) as boolean | undefined;\n}\n"
  },
  {
    "path": "packages/utils/src/isElementDisabled.ts",
    "content": "export function isElementDisabled(element: HTMLElement | null) {\n  return (\n    element == null ||\n    element.hasAttribute('disabled') ||\n    element.getAttribute('aria-disabled') === 'true'\n  );\n}\n"
  },
  {
    "path": "packages/utils/src/isMouseWithinBounds.ts",
    "content": "export function isMouseWithinBounds(event: React.MouseEvent) {\n  const targetRect = event.currentTarget.getBoundingClientRect();\n\n  // Safari randomly fires `mouseleave` incorrectly when the item is\n  // aligned to the trigger. This is a workaround to prevent the highlight\n  // from being removed while the cursor is still within the bounds of the item.\n  // https://github.com/mui/base-ui/issues/869\n  const isWithinBounds =\n    targetRect.top + 1 <= event.clientY &&\n    event.clientY <= targetRect.bottom - 1 &&\n    targetRect.left + 1 <= event.clientX &&\n    event.clientX <= targetRect.right - 1;\n\n  return isWithinBounds;\n}\n"
  },
  {
    "path": "packages/utils/src/mergeObjects.ts",
    "content": "export function mergeObjects<A extends object | undefined, B extends object | undefined>(\n  a: A,\n  b: B,\n) {\n  if (a && !b) {\n    return a;\n  }\n  if (!a && b) {\n    return b;\n  }\n  if (a || b) {\n    return { ...a, ...b };\n  }\n  return undefined;\n}\n"
  },
  {
    "path": "packages/utils/src/owner.ts",
    "content": "export { getWindow as ownerWindow } from '@floating-ui/utils/dom';\n\nexport function ownerDocument(node: Element | null) {\n  return node?.ownerDocument || document;\n}\n"
  },
  {
    "path": "packages/utils/src/reactVersion.ts",
    "content": "import * as React from 'react';\n\nconst majorVersion = parseInt(React.version, 10);\n\ntype SupportedVersions = 17 | 18 | 19;\n\nexport function isReactVersionAtLeast(reactVersionToCheck: SupportedVersions): boolean {\n  return majorVersion >= reactVersionToCheck;\n}\n"
  },
  {
    "path": "packages/utils/src/safeReact.ts",
    "content": "import * as React from 'react';\n\n// https://github.com/mui/material-ui/issues/41190#issuecomment-2040873379\nexport const SafeReact = { ...React } as typeof React;\n"
  },
  {
    "path": "packages/utils/src/store/ReactStore.spec.ts",
    "content": "import { expectType } from '../testUtils';\nimport { createSelector } from './createSelector';\nimport { ReactStore } from './ReactStore';\n\ninterface TestState {\n  count: number | undefined;\n  text: string;\n}\n\nconst selectors = {\n  count: createSelector((state: TestState) => state.count),\n  text: createSelector((state: TestState) => state.text),\n  textLongerThan(state: TestState, length: number) {\n    return state.text.length > length;\n  },\n  textLengthBetween(state: TestState, minLength: number, maxLength: number) {\n    return state.text.length >= minLength && state.text.length <= maxLength;\n  },\n};\n\nconst store = new ReactStore<TestState, Record<string, never>, typeof selectors>(\n  { count: 0, text: '' },\n  undefined,\n  selectors,\n);\n\nconst count = store.select('count');\nexpectType<number | undefined, typeof count>(count);\n\nconst text = store.select('text');\nexpectType<string, typeof text>(text);\n\nconst isTextLongerThan5 = store.select('textLongerThan', 5);\nexpectType<boolean, typeof isTextLongerThan5>(isTextLongerThan5);\n\nconst isTextLengthBetween3And10 = store.select('textLengthBetween', 3, 10);\nexpectType<boolean, typeof isTextLengthBetween3And10>(isTextLengthBetween3And10);\n\nconst countReactive = store.useState('count');\nexpectType<number | undefined, typeof countReactive>(countReactive);\n\nconst textReactive = store.useState('text');\nexpectType<string, typeof textReactive>(textReactive);\n\nconst isTextLongerThan7Reactive = store.useState('textLongerThan', 7);\nexpectType<boolean, typeof isTextLongerThan7Reactive>(isTextLongerThan7Reactive);\n\nconst isTextLengthBetween2And8Reactive = store.useState('textLengthBetween', 2, 8);\nexpectType<boolean, typeof isTextLengthBetween2And8Reactive>(isTextLengthBetween2And8Reactive);\n\n// incorrect calls:\n\n// @ts-expect-error\nstore.select();\n// @ts-expect-error\nstore.select('count', 1);\n// @ts-expect-error\nstore.select('textLongerThan');\n// @ts-expect-error\nstore.select('textLengthBetween', 1);\n// @ts-expect-error\nstore.select('textLongerThan', 2, 3);\n\n// @ts-expect-error\nstore.useState();\n// @ts-expect-error\nstore.useState('count', 1);\n// @ts-expect-error\nstore.useState('textLongerThan');\n// @ts-expect-error\nstore.useState('textLengthBetween', 1);\n// @ts-expect-error\nstore.useState('textLongerThan', 2, 3);\n\nconst unsubscribeFromCount = store.observe('count', (newValue, oldValue) => {\n  expectType<number | undefined, typeof newValue>(newValue);\n  expectType<number | undefined, typeof oldValue>(oldValue);\n});\nexpectType<() => void, typeof unsubscribeFromCount>(unsubscribeFromCount);\n\nconst unsubscribeFromSelector = store.observe(\n  (state) => state.text.length,\n  (newValue, oldValue) => {\n    expectType<number, typeof newValue>(newValue);\n    expectType<number, typeof oldValue>(oldValue);\n  },\n);\nexpectType<() => void, typeof unsubscribeFromSelector>(unsubscribeFromSelector);\n\n// @ts-expect-error listener must match selector return type\nstore.observe(\n  (state) => state.text.length,\n  (newValue: string) => {\n    expectType<string, typeof newValue>(newValue);\n  },\n);\n"
  },
  {
    "path": "packages/utils/src/store/ReactStore.test.tsx",
    "content": "import { expect, vi, type MockInstance } from 'vitest';\nimport * as React from 'react';\nimport { act, createRenderer, screen } from '@mui/internal-test-utils';\nimport { ReactStore } from './ReactStore';\nimport { useRefWithInit } from '../useRefWithInit';\nimport { createSelector } from './createSelector';\n\ntype TestState = { value: number; label: string };\n\nfunction useStableStore<State extends object>(initial: State) {\n  return useRefWithInit(() => new ReactStore<State>(initial)).current;\n}\n\ndescribe('ReactStore', () => {\n  const { render } = createRenderer();\n\n  it('syncs internal state from controlled prop', () => {\n    let store!: ReactStore<TestState>;\n\n    function Test({ controlled }: { controlled: number | undefined }) {\n      store = useStableStore<TestState>({ value: 0, label: '' });\n      store.useControlledProp('value', controlled);\n      return null;\n    }\n\n    const { setProps } = render(<Test controlled={1} />);\n    expect(store.state.value).toBe(1);\n\n    act(() => {\n      store.update({ label: 'y' });\n    });\n    // Non-controlled keys still update\n    expect(store.state.label).toBe('y');\n\n    // Changing the controlled prop updates internal state\n    act(() => {\n      setProps({ controlled: 7 });\n    });\n    expect(store.state.value).toBe(7);\n  });\n\n  it('warns on switching from uncontrolled to controlled', () => {\n    function Test({ controlled }: { controlled?: number }) {\n      const store = useStableStore<TestState>({ value: 0, label: '' });\n      store.useControlledProp('value', controlled);\n      return null;\n    }\n\n    const { setProps } = render(<Test />);\n\n    expect(() => {\n      act(() => setProps({ controlled: 1 }));\n    }).toErrorDev([\n      'A component is changing the controlled state of value to be uncontrolled. Elements should not switch from uncontrolled to controlled (or vice versa).',\n      'A component is changing the controlled state of value to be uncontrolled. Elements should not switch from uncontrolled to controlled (or vice versa).',\n    ]);\n  });\n\n  it('warns on switching from controlled to uncontrolled', () => {\n    function Test({ controlled }: { controlled?: number }) {\n      const store = useStableStore<TestState>({ value: 0, label: '' });\n      store.useControlledProp('value', controlled);\n      return null;\n    }\n\n    const { setProps } = render(<Test controlled={1} />);\n\n    expect(() => {\n      act(() => setProps({ controlled: undefined }));\n    }).toErrorDev([\n      'A component is changing the uncontrolled state of value to be controlled. Elements should not switch from uncontrolled to controlled (or vice versa).',\n      'A component is changing the uncontrolled state of value to be controlled. Elements should not switch from uncontrolled to controlled (or vice versa).',\n    ]);\n  });\n\n  it('useProp updates a single key when the passed value changes', () => {\n    let store!: ReactStore<TestState>;\n\n    function Test({ value }: { value: number }) {\n      store = useStableStore<TestState>({ value: 0, label: '' });\n      store.useSyncedValue('value', value);\n      return null;\n    }\n\n    const { setProps } = render(<Test value={1} />);\n    expect(store.state.value).toBe(1);\n\n    act(() => setProps({ value: 2 }));\n    expect(store.state.value).toBe(2);\n  });\n\n  it('useProps applies multiple keys from a props object', () => {\n    let store!: ReactStore<TestState>;\n\n    function Test({ props }: { props: Partial<TestState> }) {\n      store = useStableStore<TestState>({ value: 0, label: '' });\n      store.useSyncedValues(props);\n      return null;\n    }\n\n    const { setProps } = render(<Test props={{ value: 5, label: 'a' }} />);\n    expect(store.state.value).toBe(5);\n    expect(store.state.label).toBe('a');\n\n    act(() => setProps({ props: { value: 6, label: 'b' } }));\n    expect(store.state.value).toBe(6);\n    expect(store.state.label).toBe('b');\n  });\n\n  it('useSyncedValues depends on entries instead of object identity', () => {\n    let store!: ReactStore<TestState>;\n    let updateSpy!: MockInstance;\n\n    function Test({ props }: { props: Partial<TestState> }) {\n      store = useStableStore<TestState>({ value: 0, label: '' });\n\n      if (!updateSpy) {\n        updateSpy = vi.spyOn(store, 'update');\n      }\n\n      store.useSyncedValues(props);\n      return null;\n    }\n\n    const { setProps } = render(<Test props={{ value: 5, label: 'a' }} />, { strict: false });\n\n    expect(updateSpy.mock.calls.length).toBe(1);\n\n    act(() => {\n      setProps({ props: { value: 5, label: 'a' } });\n    });\n\n    expect(updateSpy.mock.calls.length).toBe(1);\n\n    act(() => {\n      setProps({ props: { value: 6, label: 'a' } });\n    });\n\n    expect(updateSpy.mock.calls.length).toBe(2);\n    expect(store.state.value).toBe(6);\n  });\n\n  it('warns if useSyncedValues keys change between renders', () => {\n    function Test({ props }: { props: Partial<TestState> }) {\n      const store = useStableStore<TestState>({ value: 0, label: '' });\n      store.useSyncedValues(props);\n      return null;\n    }\n\n    const { setProps } = render(<Test props={{ value: 1 }} />);\n\n    expect(() => {\n      act(() => {\n        setProps({ props: { label: 'x' } });\n      });\n    }).toErrorDev([\n      'ReactStore.useSyncedValues expects the same prop keys on every render. Keys should be stable.',\n      'ReactStore.useSyncedValues expects the same prop keys on every render. Keys should be stable.',\n    ]);\n  });\n\n  it('useSyncedValueWithCleanup synchronizes value and resets on cleanup', () => {\n    type CleanupState = { node: HTMLDivElement | undefined };\n    let store!: ReactStore<CleanupState>;\n\n    const firstNode = document.createElement('div');\n    const secondNode = document.createElement('div');\n\n    function Test({ node }: { node: HTMLDivElement | undefined }) {\n      store = useStableStore<CleanupState>({ node: undefined });\n      store.useSyncedValueWithCleanup('node', node);\n      return null;\n    }\n\n    const { setProps, unmount } = render(<Test node={firstNode} />);\n    expect(store.state.node).toBe(firstNode);\n\n    act(() => {\n      setProps({ node: secondNode });\n    });\n    expect(store.state.node).toBe(secondNode);\n\n    act(() => {\n      unmount();\n    });\n    expect(store.state.node).toBe(undefined);\n  });\n\n  it('useStateSetter returns a stable callback that updates the store state', () => {\n    type ElementState = { element: HTMLDivElement | null };\n    let store!: ReactStore<ElementState>;\n    let forceUpdate!: React.Dispatch<React.SetStateAction<number>>;\n    let lastSetter!: (element: HTMLDivElement | null) => void;\n\n    const element = document.createElement('div');\n\n    function Test() {\n      store = useStableStore<ElementState>({ element: null });\n      const setter = store.useStateSetter('element');\n      lastSetter = setter;\n      const [, setTick] = React.useState(0);\n      forceUpdate = setTick;\n\n      React.useEffect(() => {\n        setter(element);\n      }, [setter]);\n\n      return null;\n    }\n\n    render(<Test />);\n    const firstSetter = lastSetter;\n    expect(store.state.element).toBe(element);\n\n    act(() => {\n      forceUpdate((value) => value + 1);\n    });\n\n    expect(lastSetter).toBe(firstSetter);\n\n    act(() => {\n      lastSetter(null);\n    });\n\n    expect(store.state.element).toBe(null);\n  });\n\n  it('supports nested stores as state values', async () => {\n    type ParentState = { count: number };\n    type ChildState = { count: number; parent?: ReactStore<ParentState> };\n\n    const parentSelectors = { count: (state: ParentState) => state.count };\n    const childSelectors = {\n      count: (state: ChildState) => state.parent?.state.count ?? state.count,\n      parent: (state: ChildState) => state.parent,\n    };\n\n    const localCountSelector = createSelector((state: ChildState) => state.count);\n\n    const parentStore = new ReactStore<ParentState, Record<string, never>, typeof parentSelectors>(\n      { count: 0 },\n      undefined,\n      parentSelectors,\n    );\n\n    const childStore = new ReactStore<ChildState, Record<string, never>, typeof childSelectors>(\n      { count: 10 },\n      undefined,\n      childSelectors,\n    );\n\n    let unsubscribeParentHandler: () => void;\n    const onParentUpdated = (\n      newParent: ReactStore<ParentState> | undefined,\n      _: ReactStore<ParentState> | undefined,\n      store: ReactStore<ChildState, any, any>,\n    ) => {\n      if (!newParent) {\n        unsubscribeParentHandler?.();\n        return;\n      }\n\n      unsubscribeParentHandler = newParent.subscribe(() => {\n        store.notifyAll();\n      });\n    };\n\n    const onCountUpdated = (\n      newCount: number,\n      _: number,\n      store: ReactStore<ChildState, any, any>,\n    ) => {\n      store.state.parent?.set('count', newCount);\n    };\n\n    childStore.observe('parent', onParentUpdated);\n    childStore.observe(localCountSelector, onCountUpdated);\n\n    function Test() {\n      const count = childStore.useState('count');\n      return <output data-testid=\"output\">{count}</output>;\n    }\n\n    render(<Test />);\n    const output = screen.getByTestId('output');\n\n    await act(async () => {\n      childStore.set('count', 5);\n    });\n    expect(childStore.state.count).toBe(5);\n    expect(output.textContent).toBe('5');\n\n    await act(async () => {\n      childStore.set('parent', parentStore);\n    });\n    expect(childStore.state.count).toBe(5);\n    expect(childStore.select('count')).toBe(0);\n    expect(output.textContent).toBe('0');\n\n    await act(async () => {\n      childStore.set('count', 20);\n    });\n    expect(childStore.state.count).toBe(20);\n    expect(parentStore.state.count).toBe(20);\n    expect(childStore.select('count')).toBe(20);\n    expect(output.textContent).toBe('20');\n\n    await act(async () => {\n      parentStore.set('count', 15);\n    });\n    expect(parentStore.state.count).toBe(15);\n    expect(childStore.state.count).toBe(20);\n    expect(childStore.select('count')).toBe(15);\n    expect(output.textContent).toBe('15');\n  });\n  describe('observeSelector', () => {\n    type CounterState = { count: number; multiplier: number };\n    const selectors = {\n      count: (state: CounterState) => state.count,\n      doubled: (state: CounterState) => state.count * 2,\n      multiplied: (state: CounterState) => state.count * state.multiplier,\n    };\n\n    it('accepts selector functions', () => {\n      const store = new ReactStore<CounterState>({ count: 0, multiplier: 1 });\n      const calls: Array<{ newValue: boolean; oldValue: boolean }> = [];\n\n      const unsubscribe = store.observe(\n        (state) => state.count > 1,\n        (newValue, oldValue) => {\n          calls.push({ newValue, oldValue });\n        },\n      );\n\n      expect(calls).toHaveLength(1);\n      expect(calls[0]).toEqual({ newValue: false, oldValue: false });\n\n      store.set('count', 2);\n      expect(calls).toHaveLength(2);\n      expect(calls[1]).toEqual({ newValue: true, oldValue: false });\n\n      store.set('count', 1);\n      expect(calls).toHaveLength(3);\n      expect(calls[2]).toEqual({ newValue: false, oldValue: true });\n\n      unsubscribe();\n\n      store.set('count', 3);\n      expect(calls).toHaveLength(3);\n    });\n\n    it('calls listener immediately with current selector result on subscription', () => {\n      const store = new ReactStore<CounterState, Record<string, never>, typeof selectors>(\n        { count: 5, multiplier: 3 },\n        undefined,\n        selectors,\n      );\n      const calls: Array<{ newValue: number; oldValue: number }> = [];\n\n      store.observe('doubled', (newValue: number, oldValue: number) => {\n        calls.push({ newValue, oldValue });\n      });\n\n      expect(calls).toHaveLength(1);\n      expect(calls[0]).toEqual({ newValue: 10, oldValue: 10 });\n    });\n\n    it('calls listener when selector result changes', () => {\n      const store = new ReactStore<CounterState, Record<string, never>, typeof selectors>(\n        { count: 5, multiplier: 3 },\n        undefined,\n        selectors,\n      );\n      const calls: Array<{ newValue: number; oldValue: number }> = [];\n\n      store.observe('doubled', (newValue: number, oldValue: number) => {\n        calls.push({ newValue, oldValue });\n      });\n\n      store.set('count', 10);\n      store.set('count', 7);\n\n      expect(calls).toHaveLength(3);\n      expect(calls[1]).toEqual({ newValue: 20, oldValue: 10 });\n      expect(calls[2]).toEqual({ newValue: 14, oldValue: 20 });\n    });\n\n    it('does not call listener when selector result is unchanged', () => {\n      const store = new ReactStore<CounterState, Record<string, never>, typeof selectors>(\n        { count: 5, multiplier: 3 },\n        undefined,\n        selectors,\n      );\n      const calls: Array<{ newValue: number; oldValue: number }> = [];\n\n      store.observe('doubled', (newValue: number, oldValue: number) => {\n        calls.push({ newValue, oldValue });\n      });\n\n      store.set('multiplier', 5);\n\n      expect(calls).toHaveLength(1); // Only initial call\n    });\n\n    it('calls listener when any dependency of the selector changes', () => {\n      const store = new ReactStore<CounterState, Record<string, never>, typeof selectors>(\n        { count: 5, multiplier: 3 },\n        undefined,\n        selectors,\n      );\n      const calls: Array<{ newValue: number; oldValue: number }> = [];\n\n      store.observe('multiplied', (newValue: number, oldValue: number) => {\n        calls.push({ newValue, oldValue });\n      });\n\n      store.set('count', 10);\n      store.set('multiplier', 2);\n\n      expect(calls).toHaveLength(3);\n      expect(calls[0]).toEqual({ newValue: 15, oldValue: 15 });\n      expect(calls[1]).toEqual({ newValue: 30, oldValue: 15 });\n      expect(calls[2]).toEqual({ newValue: 20, oldValue: 30 });\n    });\n\n    it('provides the store instance to the listener', () => {\n      const store = new ReactStore<CounterState, Record<string, never>, typeof selectors>(\n        { count: 5, multiplier: 3 },\n        undefined,\n        selectors,\n      );\n      let receivedStore!: ReactStore<CounterState, Record<string, never>, typeof selectors>;\n\n      store.observe('doubled', (_: number, __: number, storeArg) => {\n        receivedStore = storeArg;\n      });\n\n      expect(receivedStore).toBe(store);\n    });\n\n    it('returns an unsubscribe function that stops observing', () => {\n      const store = new ReactStore<CounterState, Record<string, never>, typeof selectors>(\n        { count: 5, multiplier: 3 },\n        undefined,\n        selectors,\n      );\n      const calls: Array<{ newValue: number; oldValue: number }> = [];\n\n      const unsubscribe = store.observe('doubled', (newValue: number, oldValue: number) => {\n        calls.push({ newValue, oldValue });\n      });\n\n      store.set('count', 10);\n      expect(calls).toHaveLength(2);\n\n      unsubscribe();\n\n      store.set('count', 15);\n      expect(calls).toHaveLength(2); // No new calls after unsubscribe\n    });\n\n    it('supports multiple observers on the same selector', () => {\n      const store = new ReactStore<CounterState, Record<string, never>, typeof selectors>(\n        { count: 5, multiplier: 3 },\n        undefined,\n        selectors,\n      );\n      const calls1: number[] = [];\n      const calls2: number[] = [];\n\n      store.observe('doubled', (newValue: number) => {\n        calls1.push(newValue);\n      });\n\n      store.observe('doubled', (newValue: number) => {\n        calls2.push(newValue);\n      });\n\n      store.set('count', 10);\n\n      expect(calls1).toEqual([10, 20]);\n      expect(calls2).toEqual([10, 20]);\n    });\n\n    it('supports observers on different selectors', () => {\n      const store = new ReactStore<CounterState, Record<string, never>, typeof selectors>(\n        { count: 5, multiplier: 3 },\n        undefined,\n        selectors,\n      );\n      const doubledCalls: number[] = [];\n      const multipliedCalls: number[] = [];\n\n      store.observe('doubled', (newValue: number) => {\n        doubledCalls.push(newValue);\n      });\n\n      store.observe('multiplied', (newValue: number) => {\n        multipliedCalls.push(newValue);\n      });\n\n      store.set('count', 10);\n      store.set('multiplier', 2);\n\n      expect(doubledCalls).toEqual([10, 20]);\n      expect(multipliedCalls).toEqual([15, 30, 20]);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/utils/src/store/ReactStore.ts",
    "content": "/* False positives - ESLint thinks we're calling a hook from a class component. */\n/* eslint-disable react-hooks/rules-of-hooks */\n'use client';\nimport * as React from 'react';\nimport { Store } from './Store';\nimport { useStore } from './useStore';\nimport { useStableCallback } from '../useStableCallback';\nimport { useIsoLayoutEffect } from '../useIsoLayoutEffect';\nimport { NOOP } from '../empty';\n\n/**\n * A Store that supports controlled state keys, non-reactive values and provides utility methods for React.\n */\nexport class ReactStore<\n  State extends object,\n  Context = Record<string, never>,\n  Selectors extends Record<string, SelectorFunction<State>> = Record<string, never>,\n> extends Store<State> {\n  /**\n   * Creates a new ReactStore instance.\n   *\n   * @param state Initial state of the store.\n   * @param context Non-reactive context values.\n   * @param selectors Optional selectors for use with `useState`.\n   */\n  constructor(state: State, context: Context = {} as Context, selectors?: Selectors) {\n    super(state);\n    this.context = context;\n    this.selectors = selectors;\n  }\n\n  /**\n   * Non-reactive values such as refs, callbacks, etc.\n   */\n  readonly context: Context;\n\n  private selectors: Selectors | undefined;\n\n  /**\n   * Synchronizes a single external value into the store.\n   *\n   * Note that the while the value in `state` is updated immediately, the value returned\n   * by `useState` is updated before the next render (similarly to React's `useState`).\n   */\n  useSyncedValue<Key extends keyof State, Value extends State[Key]>(\n    key: keyof State,\n    value: Value,\n  ) {\n    React.useDebugValue(key);\n    useIsoLayoutEffect(() => {\n      if (this.state[key] !== value) {\n        this.set(key, value);\n      }\n    }, [key, value]);\n  }\n\n  /**\n   * Synchronizes a single external value into the store and\n   * cleans it up (sets to `undefined`) on unmount.\n   *\n   * Note that the while the value in `state` is updated immediately, the value returned\n   * by `useState` is updated before the next render (similarly to React's `useState`).\n   */\n  public useSyncedValueWithCleanup<Key extends KeysAllowingUndefined<State>>(\n    key: Key,\n    value: State[Key],\n  ) {\n    // eslint-disable-next-line consistent-this\n    const store = this;\n    useIsoLayoutEffect(() => {\n      if (store.state[key] !== value) {\n        store.set(key, value);\n      }\n\n      return () => {\n        store.set(key, undefined as State[Key]);\n      };\n    }, [store, key, value]);\n  }\n\n  /**\n   * Synchronizes multiple external values into the store.\n   *\n   * Note that the while the values in `state` are updated immediately, the values returned\n   * by `useState` are updated before the next render (similarly to React's `useState`).\n   */\n  public useSyncedValues(statePart: Partial<State>) {\n    // eslint-disable-next-line consistent-this\n    const store = this;\n    if (process.env.NODE_ENV !== 'production') {\n      // Check that an object with the same shape is passed on every render\n      React.useDebugValue(statePart, (p) => Object.keys(p));\n      const keys = React.useRef<Array<keyof State>>(\n        Object.keys(statePart) as Array<keyof State>,\n      ).current;\n\n      const nextKeys = Object.keys(statePart);\n      if (keys.length !== nextKeys.length || keys.some((key, index) => key !== nextKeys[index])) {\n        console.error(\n          'ReactStore.useSyncedValues expects the same prop keys on every render. Keys should be stable.',\n        );\n      }\n    }\n\n    const dependencies = Object.values(statePart);\n\n    useIsoLayoutEffect(() => {\n      store.update(statePart);\n      // eslint-disable-next-line react-hooks/exhaustive-deps\n    }, [store, ...dependencies]);\n  }\n\n  /**\n   * Registers a controllable prop pair (`controlled`, `defaultValue`) for a specific key. If `controlled`\n   * is non-undefined, the store's state at `key` is updated to match `controlled`.\n   */\n  useControlledProp<Key extends keyof State, Value extends State[Key]>(\n    key: keyof State,\n    controlled: Value | undefined,\n  ): void {\n    React.useDebugValue(key);\n    const isControlled = controlled !== undefined;\n\n    useIsoLayoutEffect(() => {\n      if (isControlled && !Object.is(this.state[key], controlled)) {\n        // Set the internal state to match the controlled value.\n        super.setState({ ...this.state, [key]: controlled });\n      }\n    }, [key, controlled, isControlled]);\n\n    if (process.env.NODE_ENV !== 'production') {\n      // eslint-disable-next-line\n      const cache = ((this as any).controlledValues ??= new Map<keyof State, boolean>());\n      if (!cache.has(key)) {\n        cache.set(key, isControlled);\n      }\n      const previouslyControlled = cache.get(key);\n      if (previouslyControlled !== undefined && previouslyControlled !== isControlled) {\n        console.error(\n          `A component is changing the ${\n            isControlled ? '' : 'un'\n          }controlled state of ${key.toString()} to be ${isControlled ? 'un' : ''}controlled. Elements should not switch from uncontrolled to controlled (or vice versa).`,\n        );\n      }\n    }\n  }\n\n  /** Gets the current value from the store using a selector with the provided key.\n   *\n   * @param key Key of the selector to use.\n   */\n  select<Key extends keyof Selectors>(\n    key: Key,\n    ...args: SelectorArgs<Selectors[Key]>\n  ): ReturnType<Selectors[Key]>;\n\n  select(key: keyof Selectors, a1?: unknown, a2?: unknown, a3?: unknown) {\n    const selector = this.selectors![key];\n    return selector(this.state, a1, a2, a3);\n  }\n\n  /**\n   * Returns a value from the store's state using a selector function.\n   * Used to subscribe to specific parts of the state.\n   * This methods causes a rerender whenever the selected state changes.\n   *\n   * @param key Key of the selector to use.\n   */\n  useState<Key extends keyof Selectors>(\n    key: Key,\n    ...args: SelectorArgs<Selectors[Key]>\n  ): ReturnType<Selectors[Key]>;\n\n  useState(key: keyof Selectors, a1?: unknown, a2?: unknown, a3?: unknown) {\n    React.useDebugValue(key);\n    return useStore(this, this.selectors![key], a1, a2, a3);\n  }\n\n  /**\n   * Wraps a function with `useStableCallback` to ensure it has a stable reference\n   * and assigns it to the context.\n   *\n   * @param key Key of the event callback. Must be a function in the context.\n   * @param fn Function to assign.\n   */\n  useContextCallback<Key extends ContextFunctionKeys<Context>>(\n    key: Key,\n    fn: ContextFunction<Context, Key> | undefined,\n  ) {\n    React.useDebugValue(key);\n    const stableFunction = useStableCallback(fn ?? (NOOP as ContextFunction<Context, Key>));\n    (this.context as Record<Key, ContextFunction<Context, Key>>)[key] = stableFunction;\n  }\n\n  /**\n   * Returns a stable setter function for a specific key in the store's state.\n   * It's commonly used to pass as a ref callback to React elements.\n   *\n   * @param key Key of the state to set.\n   */\n  useStateSetter<const Key extends keyof State, Value extends State[Key]>(key: keyof State) {\n    const ref = React.useRef<(v: Value) => void>(undefined as any);\n    if (ref.current === undefined) {\n      ref.current = (value: Value) => {\n        this.set(key, value);\n      };\n    }\n    return ref.current;\n  }\n\n  /**\n   * Observes changes derived from the store's selectors and calls the listener when the selected value changes.\n   *\n   * @param key Key of the selector to observe.\n   * @param listener Listener function called when the selector result changes.\n   */\n  observe<Key extends keyof Selectors>(\n    selector: Key,\n    listener: (\n      newValue: ReturnType<Selectors[Key]>,\n      oldValue: ReturnType<Selectors[Key]>,\n      store: this,\n    ) => void,\n  ): () => void;\n\n  observe<Selector extends ObserveSelector<State>>(\n    selector: Selector,\n    listener: (newValue: ReturnType<Selector>, oldValue: ReturnType<Selector>, store: this) => void,\n  ): () => void;\n\n  observe(\n    selector: keyof Selectors | ObserveSelector<State>,\n    listener: (newValue: any, oldValue: any, store: this) => void,\n  ) {\n    let selectFn: ObserveSelector<State>;\n\n    if (typeof selector === 'function') {\n      selectFn = selector;\n    } else {\n      selectFn = this.selectors![selector] as ObserveSelector<State>;\n    }\n\n    let prevValue = selectFn(this.state);\n\n    listener(prevValue, prevValue, this);\n\n    return this.subscribe((nextState) => {\n      const nextValue = selectFn(nextState);\n      if (!Object.is(prevValue, nextValue)) {\n        const oldValue = prevValue;\n        prevValue = nextValue;\n        listener(nextValue, oldValue, this);\n      }\n    });\n  }\n}\n\ntype MaybeCallable = (...args: any[]) => any;\n\ntype ContextFunctionKeys<Context> = {\n  [Key in keyof Context]-?: Extract<Context[Key], MaybeCallable> extends never ? never : Key;\n}[keyof Context];\n\ntype ContextFunction<Context, Key extends keyof Context> = Extract<Context[Key], MaybeCallable>;\n\ntype KeysAllowingUndefined<State> = {\n  [Key in keyof State]-?: undefined extends State[Key] ? Key : never;\n}[keyof State];\n\ntype ObserveSelector<State> = (state: State) => any;\n\ntype SelectorFunction<State> = (state: State, ...args: any[]) => any;\n\ntype Tail<T extends readonly any[]> = T extends readonly [any, ...infer Rest] ? Rest : [];\n\ntype SelectorArgs<Selector> = Selector extends (...params: infer Params) => any\n  ? Tail<Params>\n  : never;\n"
  },
  {
    "path": "packages/utils/src/store/Store.ts",
    "content": "import { useStore } from './useStore';\n\ntype Listener<T> = (state: T) => void;\n\n/**\n * A data store implementation that allows subscribing to state changes and updating the state.\n * It uses an observer pattern to notify subscribers when the state changes.\n */\nexport class Store<State> {\n  /**\n   * The current state of the store.\n   * This property is updated immediately when the state changes as a result of calling {@link setState}, {@link update}, or {@link set}.\n   * To subscribe to state changes, use the {@link useState} method. The value returned by {@link useState} is updated after the component renders (similarly to React's useState).\n   * The values can be used directly (to avoid subscribing to the store) in effects or event handlers.\n   *\n   * Do not modify properties in state directly. Instead, use the provided methods to ensure proper state management and listener notification.\n   */\n  state: State;\n\n  private listeners: Set<Listener<State>>;\n\n  // Internal state to handle recursive `setState()` calls\n  private updateTick: number;\n\n  constructor(state: State) {\n    this.state = state;\n    this.listeners = new Set();\n    this.updateTick = 0;\n  }\n\n  /**\n   * Registers a listener that will be called whenever the store's state changes.\n   *\n   * @param fn The listener function to be called on state changes.\n   * @returns A function to unsubscribe the listener.\n   */\n  subscribe = (fn: Listener<State>) => {\n    this.listeners.add(fn);\n    return () => {\n      this.listeners.delete(fn);\n    };\n  };\n\n  /**\n   * Returns the current state of the store.\n   */\n  getSnapshot = () => {\n    return this.state;\n  };\n\n  /**\n   * Updates the entire store's state and notifies all registered listeners.\n   *\n   * @param newState The new state to set for the store.\n   */\n  setState(newState: State) {\n    if (this.state === newState) {\n      return;\n    }\n\n    this.state = newState;\n    this.updateTick += 1;\n\n    const currentTick = this.updateTick;\n    for (const listener of this.listeners) {\n      if (currentTick !== this.updateTick) {\n        // If the tick has changed, a recursive `setState` call has been made,\n        // and it has already notified all listeners.\n        return;\n      }\n      listener(newState);\n    }\n  }\n\n  /**\n   * Merges the provided changes into the current state and notifies listeners if there are changes.\n   *\n   * @param changes An object containing the changes to apply to the current state.\n   */\n  update(changes: Partial<State>) {\n    for (const key in changes) {\n      if (!Object.is(this.state[key], changes[key])) {\n        this.setState({ ...this.state, ...changes });\n        return;\n      }\n    }\n  }\n\n  /**\n   * Sets a specific key in the store's state to a new value and notifies listeners if the value has changed.\n   *\n   * @param key The key in the store's state to update.\n   * @param value The new value to set for the specified key.\n   */\n  set<T>(key: keyof State, value: T) {\n    if (!Object.is(this.state[key], value)) {\n      this.setState({ ...this.state, [key]: value });\n    }\n  }\n\n  /**\n   * Gives the state a new reference and updates all registered listeners.\n   */\n  notifyAll() {\n    const newState = { ...this.state };\n    this.setState(newState);\n  }\n\n  use<F extends (...args: any) => any>(selector: F, ...args: SelectorArgs<F>): ReturnType<F>;\n\n  use(selector: any, a1?: unknown, a2?: unknown, a3?: unknown) {\n    // eslint-disable-next-line react-hooks/rules-of-hooks\n    return useStore(this, selector, a1, a2, a3);\n  }\n}\n\nexport type ReadonlyStore<State> = Pick<Store<State>, 'getSnapshot' | 'subscribe' | 'state'>;\n\ntype SelectorArgs<Selector> = Selector extends (...params: infer Params) => any\n  ? Tail<Params>\n  : never;\n\ntype Tail<T extends readonly any[]> = T extends readonly [any, ...infer Rest] ? Rest : [];\n"
  },
  {
    "path": "packages/utils/src/store/StoreInspector.tsx",
    "content": "'use client';\nimport * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport { Store } from './Store';\nimport { useForcedRerendering } from '../useForcedRerendering';\nimport { useStableCallback } from '../useStableCallback';\nimport { useAnimationFrame } from '../useAnimationFrame';\nimport { useIsoLayoutEffect } from '../useIsoLayoutEffect';\nimport { useTimeout } from '../useTimeout';\n\nconst STYLES = `\n.baseui-store-inspector-trigger {\n  all: unset;\n  width: 32px;\n  height: 32px;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  color: oklch(0.651 0.078 264);\n}\n\n.baseui-store-inspector-trigger:hover,\n.baseui-store-inspector-trigger:focus-visible {\n opacity: 0.8;\n}\n\n.baseui-store-inspector-content {\n  background: #101010;\n  flex: 1 1 auto;\n  min-height: 0;\n  overflow: auto;\n  padding-bottom: 12px;\n  scrollbar-width: thin;\n  padding: 8px;\n  border-radius: 4px;\n  border: 1px solid rgba(255, 255, 255, 0.1);\n}\n\n.baseui-store-inspector-content h3 {\n  text-transform: uppercase;\n  font-weight: bold;\n}\n\n.baseui-store-inspector-content pre {\n  margin: 0 0 16px 0;\n}\n\n.baseui-store-inspector-content pre:last-child {\n  margin-bottom: 0;\n}\n\n.baseui-store-inspector-root {\n  position: fixed;\n  background: oklch(0.34 0.036 264);\n  color: #fff;\n  z-index: 1000;\n  font-size: 12px;\n  padding: 8px;\n  border-radius: 8px;\n  display: flex;\n  flex-direction: column;\n  box-sizing: border-box;\n  max-width: 50vw;\n  color-scheme: dark;\n  overflow: clip;\n  box-shadow:\n    0 10px 15px -3px oklch(12% 9% 264deg / 8%),\n    0 4px 6px -4px oklch(12% 9% 264deg / 8%);\n}\n\n.baseui-store-inspector-header {\n  display: flex;\n  align-items: center;\n  cursor: move;\n  user-select: none;\n  -webkit-user-select: none;\n  touch-action: none;\n  padding: 4px 8px 8px 8px;\n  gap: 8px;\n\n  h2 {\n    font-size: 16px;\n    flex-grow: 1;\n  }\n}\n\n.baseui-store-inspector-header button {\n  all: unset;\n  width: 32px;\n  height: 32px;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  cursor: default;\n}\n\n.baseui-store-inspector-header button:hover,\n.baseui-store-inspector-header button:focus-visible {\n  opacity: 0.8;\n}\n\n.baseui-store-inspector-resize-handle {\n  position: absolute;\n  width: 8px;\n  height: 8px;\n  right: -4px;\n  bottom: -4px;\n  cursor: se-resize;\n  background: linear-gradient(135deg, transparent 50%, rgba(255, 255, 255, 0.25) 50%);\n  border-radius: 2px;\n}\n`;\n\nexport interface StoreInspectorProps {\n  /**\n   * Instance of the store to inspect.\n   */\n  store: Store<any>;\n  /**\n   * Additional data to display in the inspector.\n   */\n  additionalData?: any;\n  /**\n   * Title to display in the panel header.\n   */\n  title?: string | undefined;\n  /**\n   * Whether the inspector panel should be open by default.\n   * @default false\n   */\n  defaultOpen?: boolean | undefined;\n}\n\n/**\n * A tool to inspect the state of a Store in a floating panel.\n * This is intended for development and debugging purposes.\n */\nexport function StoreInspector(props: StoreInspectorProps) {\n  const { store, title, additionalData, defaultOpen = false } = props;\n  const [open, setOpen] = React.useState(defaultOpen);\n\n  return (\n    <React.Fragment>\n      <style href=\"baseui-store-inspector\" precedence=\"default\">\n        {STYLES}\n      </style>\n      <button\n        className=\"baseui-store-inspector-trigger\"\n        type=\"button\"\n        onClick={(event) => {\n          event.preventDefault();\n          event.stopPropagation();\n          setOpen((o) => !o);\n        }}\n        title=\"Toggle store inspector\"\n        aria-hidden\n      >\n        <FileJson />\n      </button>\n      <StoreInspectorPanel\n        open={open}\n        store={store}\n        title={title}\n        additionalData={additionalData}\n        onClose={() => setOpen(false)}\n      />\n    </React.Fragment>\n  );\n}\n\ninterface PanelProps {\n  store: Store<any>;\n  title?: string | undefined;\n  additionalData?: any;\n  open: boolean;\n  onClose?: (() => void) | undefined;\n}\n\nexport function StoreInspectorPanel({ store, title, additionalData, open, onClose }: PanelProps) {\n  const rerender = useForcedRerendering();\n  const rerenderTimeout = useTimeout();\n\n  // Update when state changes\n  useIsoLayoutEffect(() => {\n    const unsubscribe = store.subscribe(() => {\n      rerenderTimeout.start(1, () => rerender());\n    });\n\n    return unsubscribe;\n  }, [store, rerender, rerenderTimeout]);\n\n  const logToConsole = useStableCallback(() => {\n    const data: any = {\n      state: store.state,\n    };\n\n    if (Object.keys((store as any).context ?? {}).length > 0) {\n      data.context = (store as any).context;\n    }\n\n    if (additionalData !== undefined) {\n      data.additionalData = additionalData;\n    }\n\n    // eslint-disable-next-line no-console\n    console.log(data);\n  });\n\n  if (typeof document === 'undefined') {\n    return null;\n  }\n\n  const content = (\n    <Window\n      title={title ?? 'Store Inspector'}\n      onClose={onClose}\n      headerActions={\n        <button type=\"button\" onClick={logToConsole} title=\"Log to console\">\n          <SquareTerminal />\n        </button>\n      }\n    >\n      <h3>State</h3>\n      <pre>{JSON.stringify(store.state, getStringifyReplacer(), 2)}</pre>\n      {Object.keys((store as any).context ?? {}).length > 0 && (\n        <React.Fragment>\n          <h3>Context</h3>\n          <pre>{JSON.stringify((store as any).context, getStringifyReplacer(), 2)}</pre>\n        </React.Fragment>\n      )}\n      {additionalData !== undefined && (\n        <React.Fragment>\n          <h3>Additional data</h3>\n          <pre>{JSON.stringify(additionalData, getStringifyReplacer(), 2)}</pre>\n        </React.Fragment>\n      )}\n    </Window>\n  );\n\n  return open ? ReactDOM.createPortal(content, document.body) : null;\n}\n\nfunction getStringifyReplacer() {\n  const ancestors: any[] = [];\n\n  return function replacer(this: unknown, _: string, value: unknown) {\n    if (value instanceof Element) {\n      return `Element(${value.tagName.toLowerCase()}${value.id ? `#${value.id}` : ''})`;\n    }\n\n    if (value === undefined) {\n      return '[undefined]';\n    }\n\n    if (value instanceof Map) {\n      return Array.from(value.entries());\n    }\n\n    if (value instanceof Set) {\n      return Array.from(value);\n    }\n\n    if (typeof value !== 'object' || value === null) {\n      return value;\n    }\n    // `this` is the object that value is contained in,\n    // i.e., its direct parent.\n    while (ancestors.length > 0 && ancestors.at(-1) !== this) {\n      ancestors.pop();\n    }\n\n    if (ancestors.includes(value)) {\n      return '[circular reference]';\n    }\n\n    ancestors.push(value);\n    return value;\n  };\n}\n\ninterface WindowProps {\n  title?: string | undefined;\n  onClose?: (() => void) | undefined;\n  children: React.ReactNode;\n  headerActions?: React.ReactNode;\n}\n\n/**\n * A reusable draggable and resizable window component.\n * Handles all the pointer events for dragging and resizing internally.\n */\nfunction Window({ title, onClose, children, headerActions }: WindowProps) {\n  const rootRef = React.useRef<HTMLDivElement | null>(null);\n  const headerRef = React.useRef<HTMLDivElement | null>(null);\n  const resizeHandleRef = React.useRef<HTMLDivElement | null>(null);\n  const raf = useAnimationFrame();\n  const minWidth = 160;\n  const minHeight = 52;\n\n  // Track position when user drags the window\n  const [position, setPosition] = React.useState<{ left: number; top: number } | null>(null);\n  const dragStateRef = React.useRef<{\n    dragging: boolean;\n    startX: number;\n    startY: number;\n    startLeft: number;\n    startTop: number;\n  } | null>(null);\n\n  // Track size when user resizes the window\n  const [size, setSize] = React.useState<{ width: number; height: number } | null>(null);\n  const resizeStateRef = React.useRef<{\n    resizing: boolean;\n    startX: number;\n    startY: number;\n    startWidth: number;\n    startHeight: number;\n    minWidth: number;\n    minHeight: number;\n    maxWidth: number;\n    maxHeight: number;\n  } | null>(null);\n\n  useIsoLayoutEffect(() => {\n    if (position != null) {\n      return;\n    }\n    const el = rootRef.current;\n    if (!el) {\n      return;\n    }\n\n    setPosition({ left: 8, top: 8 });\n  }, [position]);\n\n  const onPointerDown = useStableCallback((event: React.PointerEvent) => {\n    if (!headerRef.current || !rootRef.current) {\n      return;\n    }\n    const target = event.target as Element | null;\n    if (target && target.closest('button')) {\n      return;\n    }\n    const currentPos = position ?? { left: 8, top: 8 };\n    dragStateRef.current = {\n      dragging: true,\n      startX: event.clientX,\n      startY: event.clientY,\n      startLeft: currentPos.left,\n      startTop: currentPos.top,\n    };\n    try {\n      headerRef.current.setPointerCapture(event.pointerId);\n    } catch {\n      void 0;\n    }\n    event.preventDefault();\n  });\n\n  const endDrag = useStableCallback((event?: PointerEvent) => {\n    if (headerRef.current && event) {\n      try {\n        headerRef.current.releasePointerCapture(event.pointerId);\n      } catch {\n        void 0;\n      }\n    }\n    if (dragStateRef.current) {\n      dragStateRef.current.dragging = false;\n    }\n  });\n\n  const onPointerMove = useStableCallback((event: PointerEvent) => {\n    const state = dragStateRef.current;\n    if (!state || !state.dragging) {\n      return;\n    }\n    const nextLeft = state.startLeft + (event.clientX - state.startX);\n    const nextTop = Math.max(0, state.startTop + (event.clientY - state.startY));\n\n    raf.request(() => {\n      setPosition({ left: nextLeft, top: nextTop });\n    });\n  });\n\n  const onResizePointerDown = useStableCallback((event: React.PointerEvent) => {\n    if (!rootRef.current) {\n      return;\n    }\n    const rect = rootRef.current.getBoundingClientRect();\n    const currentSize = size ?? { width: rect.width, height: rect.height };\n    const currentLeft = position?.left ?? rect.left;\n    const currentTop = position?.top ?? rect.top;\n    const maxWidth = Math.max(100, window.innerWidth - currentLeft);\n    const maxHeight = Math.max(80, window.innerHeight - currentTop);\n    resizeStateRef.current = {\n      resizing: true,\n      startX: event.clientX,\n      startY: event.clientY,\n      startWidth: currentSize.width,\n      startHeight: currentSize.height,\n      minWidth,\n      minHeight,\n      maxWidth,\n      maxHeight,\n    };\n    try {\n      (event.currentTarget as any)?.setPointerCapture?.((event as any).pointerId);\n    } catch {\n      void 0;\n    }\n    event.preventDefault();\n  });\n\n  const onResizePointerMove = useStableCallback((event: PointerEvent) => {\n    const state = resizeStateRef.current;\n    if (!state || !state.resizing) {\n      return;\n    }\n    const dx = event.clientX - state.startX;\n    const dy = event.clientY - state.startY;\n    const nextWidth = Math.min(state.maxWidth, Math.max(state.minWidth, state.startWidth + dx));\n    const nextHeight = Math.min(state.maxHeight, Math.max(state.minHeight, state.startHeight + dy));\n    raf.request(() => {\n      setSize({ width: nextWidth, height: nextHeight });\n    });\n  });\n\n  const endResize = useStableCallback((event?: PointerEvent) => {\n    if (event) {\n      try {\n        (event.target as any)?.releasePointerCapture?.((event as any).pointerId);\n      } catch {\n        void 0;\n      }\n    }\n    if (resizeStateRef.current) {\n      resizeStateRef.current.resizing = false;\n    }\n  });\n\n  // Bind/unbind global listeners for dragging and resizing\n  useIsoLayoutEffect(() => {\n    const move = (event: PointerEvent) => {\n      onPointerMove(event);\n      onResizePointerMove(event);\n    };\n    const up = (event: PointerEvent) => {\n      endDrag(event);\n      endResize(event);\n    };\n    window.addEventListener('pointermove', move);\n    window.addEventListener('pointerup', up);\n    window.addEventListener('pointercancel', up);\n    return () => {\n      window.removeEventListener('pointermove', move);\n      window.removeEventListener('pointerup', up);\n      window.removeEventListener('pointercancel', up);\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, []);\n\n  // Compute window style once per render\n  const style: React.CSSProperties = {};\n  const viewportMax =\n    typeof window !== 'undefined' ? Math.max(0, window.innerHeight - 16) : undefined;\n  if (position) {\n    style.top = position.top;\n    style.left = position.left;\n    style.right = 'auto';\n    style.position = 'fixed';\n    if (size?.width != null) {\n      style.width = size.width;\n    }\n    if (size?.height != null) {\n      style.height = size.height;\n    }\n    style.maxHeight =\n      typeof window !== 'undefined'\n        ? Math.max(0, window.innerHeight - position.top - 8)\n        : undefined;\n  } else {\n    if (size?.width != null) {\n      style.width = size.width;\n    }\n    if (size?.height != null) {\n      style.height = size.height;\n    }\n    style.maxHeight = viewportMax;\n  }\n\n  return (\n    <div ref={rootRef} className=\"baseui-store-inspector-root\" style={style}>\n      <div ref={headerRef} className=\"baseui-store-inspector-header\" onPointerDown={onPointerDown}>\n        <h2>{title}</h2>\n        {headerActions}\n        <button type=\"button\" onClick={onClose} title=\"Close window\">\n          <CloseIcon />\n        </button>\n      </div>\n      <div className=\"baseui-store-inspector-content\">{children}</div>\n      <div\n        ref={resizeHandleRef}\n        onPointerDown={onResizePointerDown}\n        style={{ position: 'relative' }}\n      >\n        <div className=\"baseui-store-inspector-resize-handle\" />\n      </div>\n    </div>\n  );\n}\n\nfunction CloseIcon() {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    >\n      <circle cx=\"12\" cy=\"12\" r=\"10\" />\n      <path d=\"m15 9-6 6\" />\n      <path d=\"m9 9 6 6\" />\n    </svg>\n  );\n}\n\nfunction FileJson() {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    >\n      <path d=\"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z\" />\n      <path d=\"M14 2v4a2 2 0 0 0 2 2h4\" />\n      <path d=\"M10 12a1 1 0 0 0-1 1v1a1 1 0 0 1-1 1 1 1 0 0 1 1 1v1a1 1 0 0 0 1 1\" />\n      <path d=\"M14 18a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1 1 1 0 0 1-1-1v-1a1 1 0 0 0-1-1\" />\n    </svg>\n  );\n}\n\nfunction SquareTerminal() {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    >\n      <path d=\"m7 11 2-2-2-2\" />\n      <path d=\"M11 13h4\" />\n      <rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" ry=\"2\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "packages/utils/src/store/createSelector.ts",
    "content": "import { lruMemoize, createSelectorCreator, Selector } from 'reselect';\n\n/* eslint-disable no-underscore-dangle */ // __cacheKey__\n\nconst reselectCreateSelector = createSelectorCreator({\n  memoize: lruMemoize,\n  memoizeOptions: {\n    maxSize: 1,\n    equalityCheck: Object.is,\n  },\n});\n\ntype Fn = (...args: any[]) => any;\ntype SelectorWithArgs = ReturnType<typeof reselectCreateSelector> & { selectorArgs: any[3] };\n\ntype CreateSelectorFunction = <\n  const Args extends any[],\n  const Selectors extends ReadonlyArray<Selector<any>>,\n  const Combiner extends (...args: readonly [...ReturnTypes<Selectors>, ...Args]) => any,\n>(\n  ...items: [...Selectors, Combiner]\n) => (\n  ...args: Selectors['length'] extends 0\n    ? MergeParams<ReturnTypes<Selectors>, Parameters<Combiner>>\n    : [\n        StateFromSelectorList<Selectors>,\n        ...MergeParams<ReturnTypes<Selectors>, Parameters<Combiner>>,\n      ]\n) => ReturnType<Combiner>;\n\ntype StateFromSelectorList<Selectors extends readonly any[]> = Selectors extends [\n  f: infer F,\n  ...other: infer R,\n]\n  ? StateFromSelector<F> extends StateFromSelectorList<R>\n    ? StateFromSelector<F>\n    : StateFromSelectorList<R>\n  : {};\n\ntype StateFromSelector<T> = T extends (first: infer F, ...args: any[]) => any ? F : never;\n\ntype DropFirst<T> = T extends [any, ...infer Xs] ? Xs : [];\n\ntype ReturnTypes<FunctionsArray extends readonly Fn[]> = {\n  [Index in keyof FunctionsArray]: FunctionsArray[Index] extends FunctionsArray[number]\n    ? ReturnType<FunctionsArray[Index]>\n    : never;\n};\n\ntype MergeParams<\n  STypes extends readonly unknown[],\n  CTypes extends readonly unknown[],\n> = STypes['length'] extends 0 ? CTypes : MergeParams<DropFirst<STypes>, DropFirst<CTypes>>;\n\n/**\n * Creates a selector function that can be used to derive values from the store's state.\n * The selector can take up to three additional arguments that can be used in the selector logic.\n * This function accepts up to six functions and combines them into a single selector function.\n * The last parameter is the combiner function that combines the results of the previous selectors.\n *\n * @example\n * const selector = createSelector(\n *  (state) => state.disabled\n * );\n *\n * @example\n * const selector = createSelector(\n *   (state) => state.disabled,\n *   (state) => state.open,\n *   (disabled, open) => ({ disabled, open })\n * );\n *\n */\n/* eslint-disable id-denylist */\nexport const createSelector = ((\n  a: Function,\n  b?: Function,\n  c?: Function,\n  d?: Function,\n  e?: Function,\n  f?: Function,\n  ...other: any[]\n) => {\n  if (other.length > 0) {\n    throw new Error('Unsupported number of selectors');\n  }\n\n  let selector: any;\n\n  if (a && b && c && d && e && f) {\n    selector = (state: any, a1: any, a2: any, a3: any) => {\n      const va = a(state, a1, a2, a3);\n      const vb = b(state, a1, a2, a3);\n      const vc = c(state, a1, a2, a3);\n      const vd = d(state, a1, a2, a3);\n      const ve = e(state, a1, a2, a3);\n      return f(va, vb, vc, vd, ve, a1, a2, a3);\n    };\n  } else if (a && b && c && d && e) {\n    selector = (state: any, a1: any, a2: any, a3: any) => {\n      const va = a(state, a1, a2, a3);\n      const vb = b(state, a1, a2, a3);\n      const vc = c(state, a1, a2, a3);\n      const vd = d(state, a1, a2, a3);\n      return e(va, vb, vc, vd, a1, a2, a3);\n    };\n  } else if (a && b && c && d) {\n    selector = (state: any, a1: any, a2: any, a3: any) => {\n      const va = a(state, a1, a2, a3);\n      const vb = b(state, a1, a2, a3);\n      const vc = c(state, a1, a2, a3);\n      return d(va, vb, vc, a1, a2, a3);\n    };\n  } else if (a && b && c) {\n    selector = (state: any, a1: any, a2: any, a3: any) => {\n      const va = a(state, a1, a2, a3);\n      const vb = b(state, a1, a2, a3);\n      return c(va, vb, a1, a2, a3);\n    };\n  } else if (a && b) {\n    selector = (state: any, a1: any, a2: any, a3: any) => {\n      const va = a(state, a1, a2, a3);\n      return b(va, a1, a2, a3);\n    };\n  } else if (a) {\n    selector = a;\n  } else {\n    throw /* minify-error-disabled */ new Error('Missing arguments');\n  }\n\n  return selector;\n}) as unknown as CreateSelectorFunction;\n/* eslint-enable id-denylist */\n\nexport const createSelectorMemoized: CreateSelectorFunction = (...selectors: any[]) => {\n  type CacheKey = { id: number };\n\n  const cache = new WeakMap<CacheKey, SelectorWithArgs>();\n  let nextCacheId = 1;\n\n  const combiner = selectors[selectors.length - 1];\n  const nSelectors = selectors.length - 1 || 1;\n  // (s1, s2, ..., sN, a1, a2, a3) => { ... }\n  const argsLength = combiner.length - nSelectors;\n\n  if (argsLength > 3) {\n    throw new Error('Unsupported number of arguments');\n  }\n\n  const selector = (state: any, a1: any, a2: any, a3: any) => {\n    let cacheKey = state.__cacheKey__;\n    if (!cacheKey) {\n      cacheKey = { id: nextCacheId };\n      state.__cacheKey__ = cacheKey;\n      nextCacheId += 1;\n    }\n\n    let fn = cache.get(cacheKey);\n    if (!fn) {\n      let reselectArgs = selectors;\n      const selectorArgs = [undefined, undefined, undefined];\n      switch (argsLength) {\n        case 0:\n          break;\n        case 1: {\n          reselectArgs = [...selectors.slice(0, -1), () => selectorArgs[0], combiner];\n          break;\n        }\n        case 2: {\n          reselectArgs = [\n            ...selectors.slice(0, -1),\n            () => selectorArgs[0],\n            () => selectorArgs[1],\n            combiner,\n          ];\n          break;\n        }\n        case 3: {\n          reselectArgs = [\n            ...selectors.slice(0, -1),\n            () => selectorArgs[0],\n            () => selectorArgs[1],\n            () => selectorArgs[2],\n            combiner,\n          ];\n          break;\n        }\n        default:\n          throw new Error('Unsupported number of arguments');\n      }\n\n      fn = reselectCreateSelector(...(reselectArgs as any)) as unknown as SelectorWithArgs;\n      fn.selectorArgs = selectorArgs;\n\n      cache.set(cacheKey, fn);\n    }\n\n    fn.selectorArgs[0] = a1;\n    fn.selectorArgs[1] = a2;\n    fn.selectorArgs[2] = a3;\n\n    // prettier-ignore\n    switch (argsLength) {\n      case 0: return fn(state);\n      case 1: return fn(state, a1);\n      case 2: return fn(state, a1, a2);\n      case 3: return fn(state, a1, a2, a3);\n      default:\n        throw /* minify-error-disabled */ new Error('unreachable');\n    }\n  };\n\n  return selector as any;\n};\n"
  },
  {
    "path": "packages/utils/src/store/index.ts",
    "content": "export * from './createSelector';\nexport * from './useStore';\nexport * from './Store';\nexport * from './ReactStore';\nexport * from './StoreInspector';\n"
  },
  {
    "path": "packages/utils/src/store/useStore.ts",
    "content": "import * as React from 'react';\n/* We need to import the shim because React 17 does not support the `useSyncExternalStore` API.\n * More info: https://github.com/mui/mui-x/issues/18303#issuecomment-2958392341 */\nimport { useSyncExternalStore } from 'use-sync-external-store/shim';\nimport { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector';\nimport { isReactVersionAtLeast } from '../reactVersion';\nimport { register, getInstance, Instance } from '../fastHooks';\nimport type { ReadonlyStore } from './Store';\n\n/* Some tests fail in R18 with the raw useSyncExternalStore. It may be possible to make it work\n * but for now we only enable it for R19+. */\nconst canUseRawUseSyncExternalStore = isReactVersionAtLeast(19);\nconst useStoreImplementation = canUseRawUseSyncExternalStore ? useStoreFast : useStoreLegacy;\n\nexport function useStore<State, Value>(\n  store: ReadonlyStore<State>,\n  selector: (state: State) => Value,\n): Value;\nexport function useStore<State, Value, A1>(\n  store: ReadonlyStore<State>,\n  selector: (state: State, a1: A1) => Value,\n  a1: A1,\n): Value;\nexport function useStore<State, Value, A1, A2>(\n  store: ReadonlyStore<State>,\n  selector: (state: State, a1: A1, a2: A2) => Value,\n  a1: A1,\n  a2: A2,\n): Value;\nexport function useStore<State, Value, A1, A2, A3>(\n  store: ReadonlyStore<State>,\n  selector: (state: State, a1: A1, a2: A2, a3: A3) => Value,\n  a1: A1,\n  a2: A2,\n  a3: A3,\n): Value;\nexport function useStore(\n  store: ReadonlyStore<unknown>,\n  selector: Function,\n  a1?: unknown,\n  a2?: unknown,\n  a3?: unknown,\n): unknown {\n  return useStoreImplementation(store, selector, a1, a2, a3);\n}\n\nfunction useStoreR19(\n  store: ReadonlyStore<unknown>,\n  selector: Function,\n  a1?: unknown,\n  a2?: unknown,\n  a3?: unknown,\n): unknown {\n  const getSelection = React.useCallback(\n    () => selector(store.getSnapshot(), a1, a2, a3),\n    [store, selector, a1, a2, a3],\n  );\n\n  return useSyncExternalStore(store.subscribe, getSelection, getSelection);\n}\n\nexport type StoreInstance = Instance & {\n  syncIndex: number;\n  syncTick: number;\n  syncHooks: {\n    store: any;\n    selector: Function;\n    a1: unknown;\n    a2: unknown;\n    a3: unknown;\n    value: unknown;\n    didChange: boolean;\n  }[];\n  didChangeStore: boolean;\n  subscribe: (onStoreChange: any) => () => void;\n  getSnapshot: () => unknown;\n};\n\nregister({\n  before(instance: StoreInstance) {\n    instance.syncIndex = 0;\n\n    if (!instance.didInitialize) {\n      instance.syncTick = 1;\n      instance.syncHooks = [];\n      instance.didChangeStore = true;\n      instance.getSnapshot = () => {\n        let didChange = false;\n        for (let i = 0; i < instance.syncHooks.length; i += 1) {\n          const hook = instance.syncHooks[i];\n          const value = hook.selector(hook.store.state, hook.a1, hook.a2, hook.a3);\n          if (hook.didChange || !Object.is(hook.value, value)) {\n            didChange = true;\n            hook.value = value;\n            hook.didChange = false;\n          }\n        }\n        if (didChange) {\n          instance.syncTick += 1;\n        }\n        return instance.syncTick;\n      };\n    }\n  },\n  after(instance: StoreInstance) {\n    if (instance.syncHooks.length > 0) {\n      if (instance.didChangeStore) {\n        instance.didChangeStore = false;\n        instance.subscribe = (onStoreChange) => {\n          const stores = new Set<ReadonlyStore<unknown>>();\n          for (const hook of instance.syncHooks) {\n            stores.add(hook.store);\n          }\n          const unsubscribes: Array<() => void> = [];\n          for (const store of stores) {\n            unsubscribes.push(store.subscribe(onStoreChange));\n          }\n          return () => {\n            for (const unsubscribe of unsubscribes) {\n              unsubscribe();\n            }\n          };\n        };\n      }\n      // eslint-disable-next-line react-hooks/rules-of-hooks\n      useSyncExternalStore(instance.subscribe, instance.getSnapshot, instance.getSnapshot);\n    }\n  },\n});\n\nfunction useStoreFast(\n  store: ReadonlyStore<unknown>,\n  selector: Function,\n  a1?: unknown,\n  a2?: unknown,\n  a3?: unknown,\n): unknown {\n  const instance = getInstance() as StoreInstance | undefined;\n  if (!instance) {\n    // eslint-disable-next-line react-hooks/rules-of-hooks\n    return useStoreR19(store, selector, a1, a2, a3);\n  }\n\n  const index = instance.syncIndex;\n  instance.syncIndex += 1;\n\n  let hook;\n  if (!instance.didInitialize) {\n    hook = {\n      store,\n      selector,\n      a1,\n      a2,\n      a3,\n      value: selector(store.getSnapshot(), a1, a2, a3),\n      didChange: false,\n    };\n    instance.syncHooks.push(hook);\n  } else {\n    hook = instance.syncHooks[index];\n    if (\n      hook.store !== store ||\n      hook.selector !== selector ||\n      !Object.is(hook.a1, a1) ||\n      !Object.is(hook.a2, a2) ||\n      !Object.is(hook.a3, a3)\n    ) {\n      if (hook.store !== store) {\n        instance.didChangeStore = true;\n      }\n      hook.store = store;\n      hook.selector = selector;\n      hook.a1 = a1;\n      hook.a2 = a2;\n      hook.a3 = a3;\n      hook.didChange = true;\n    }\n  }\n\n  return hook.value;\n}\n\nfunction useStoreLegacy(\n  store: ReadonlyStore<unknown>,\n  selector: Function,\n  a1?: unknown,\n  a2?: unknown,\n  a3?: unknown,\n): unknown {\n  return useSyncExternalStoreWithSelector(\n    store.subscribe,\n    store.getSnapshot,\n    store.getSnapshot,\n    (state) => selector(state, a1, a2, a3),\n  );\n}\n"
  },
  {
    "path": "packages/utils/src/testUtils.ts",
    "content": "/**\n * Whether the test runs in JSDOM environment\n */\nexport const isJSDOM = /jsdom/.test(window.navigator.userAgent);\n\n// https://stackoverflow.com/questions/53807517/how-to-test-if-two-types-are-exactly-the-same\nexport type IfEquals<T, U, Y = unknown, N = never> =\n  (<G>() => G extends T ? 1 : 2) extends <G>() => G extends U ? 1 : 2 ? Y : N;\n\n/**\n * Issues a type error if `Expected` is not identical to `Actual`.\n *\n * `Expected` should be declared when invoking `expectType`.\n * `Actual` should almost always we be a `typeof value` statement.\n *\n * @example `expectType<number | string, typeof value>(value)`\n * TypeScript issues a type error since `value is not assignable to never`.\n * This means `typeof value` is not identical to `number | string`\n * @param _actual\n */\nexport function expectType<Expected, Actual>(_actual: IfEquals<Actual, Expected, Actual>): void {}\n"
  },
  {
    "path": "packages/utils/src/useAnimationFrame.ts",
    "content": "'use client';\nimport { useRefWithInit } from './useRefWithInit';\nimport { useOnMount } from './useOnMount';\n\ntype AnimationFrameId = number;\n\n/** Unlike `setTimeout`, rAF doesn't guarantee a positive integer return value, so we can't have\n * a monomorphic `uint` type with `0` meaning empty.\n * See warning note at:\n * https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame#return_value */\nconst EMPTY = null;\n\nlet LAST_RAF = globalThis.requestAnimationFrame;\n\nclass Scheduler {\n  /* This implementation uses an array as a backing data-structure for frame callbacks.\n   * It allows `O(1)` callback cancelling by inserting a `null` in the array, though it\n   * never calls the native `cancelAnimationFrame` if there are no frames left. This can\n   * be much more efficient if there is a call pattern that alterns as\n   * \"request-cancel-request-cancel-…\".\n   * But in the case of \"request-request-…-cancel-cancel-…\", it leaves the final animation\n   * frame to run anyway. We turn that frame into a `O(1)` no-op via `callbacksCount`. */\n\n  callbacks = [] as (FrameRequestCallback | null)[];\n\n  callbacksCount = 0;\n\n  nextId = 1;\n\n  startId = 1;\n\n  isScheduled = false;\n\n  tick = (timestamp: number) => {\n    this.isScheduled = false;\n\n    const currentCallbacks = this.callbacks;\n    const currentCallbacksCount = this.callbacksCount;\n\n    // Update these before iterating, callbacks could call `requestAnimationFrame` again.\n    this.callbacks = [];\n    this.callbacksCount = 0;\n    this.startId = this.nextId;\n\n    if (currentCallbacksCount > 0) {\n      for (let i = 0; i < currentCallbacks.length; i += 1) {\n        currentCallbacks[i]?.(timestamp);\n      }\n    }\n  };\n\n  request(fn: FrameRequestCallback) {\n    const id = this.nextId;\n    this.nextId += 1;\n    this.callbacks.push(fn);\n    this.callbacksCount += 1;\n\n    /* In a test environment with fake timers, a fake `requestAnimationFrame` can be called\n     * but there's no guarantee that the animation frame will actually run before the fake\n     * timers are teared, which leaves `isScheduled` set, but won't run our `tick()`. */\n    const didRAFChange =\n      process.env.NODE_ENV !== 'production' &&\n      LAST_RAF !== requestAnimationFrame &&\n      ((LAST_RAF = requestAnimationFrame), true);\n\n    if (!this.isScheduled || didRAFChange) {\n      requestAnimationFrame(this.tick);\n      this.isScheduled = true;\n    }\n    return id;\n  }\n\n  cancel(id: AnimationFrameId) {\n    const index = id - this.startId;\n    if (index < 0 || index >= this.callbacks.length) {\n      return;\n    }\n    this.callbacks[index] = null;\n    this.callbacksCount -= 1;\n  }\n}\n\nconst scheduler = new Scheduler();\n\nexport class AnimationFrame {\n  static create() {\n    return new AnimationFrame();\n  }\n\n  static request(fn: FrameRequestCallback) {\n    return scheduler.request(fn);\n  }\n\n  static cancel(id: AnimationFrameId) {\n    return scheduler.cancel(id);\n  }\n\n  currentId: AnimationFrameId | null = EMPTY;\n\n  /**\n   * Executes `fn` after `delay`, clearing any previously scheduled call.\n   */\n  request(fn: Function) {\n    this.cancel();\n    this.currentId = scheduler.request(() => {\n      this.currentId = EMPTY;\n      fn();\n    });\n  }\n\n  cancel = () => {\n    if (this.currentId !== EMPTY) {\n      scheduler.cancel(this.currentId);\n      this.currentId = EMPTY;\n    }\n  };\n\n  disposeEffect = () => {\n    return this.cancel;\n  };\n}\n\n/**\n * A `requestAnimationFrame` with automatic cleanup and guard.\n */\nexport function useAnimationFrame() {\n  const timeout = useRefWithInit(AnimationFrame.create).current;\n\n  useOnMount(timeout.disposeEffect);\n\n  return timeout;\n}\n"
  },
  {
    "path": "packages/utils/src/useControlled.test.tsx",
    "content": "import { expect } from 'vitest';\nimport * as React from 'react';\nimport { act, createRenderer } from '@mui/internal-test-utils';\nimport { useControlled } from './useControlled';\n\ninterface TestComponentChildrenArgument {\n  value: number | string;\n  setValue: React.Dispatch<React.SetStateAction<number | string>>;\n}\n\ninterface TestComponentProps {\n  value?: number | string;\n  defaultValue?: number | string;\n  children: (parames: TestComponentChildrenArgument) => React.ReactNode;\n}\n\nfunction TestComponent({ value: valueProp, defaultValue, children }: TestComponentProps) {\n  const [value, setValue] = useControlled({\n    controlled: valueProp,\n    default: defaultValue,\n    name: 'TestComponent',\n  });\n  return children({ value, setValue });\n}\n\ndescribe('useControlled', () => {\n  const { render } = createRenderer();\n\n  it('works correctly when is not controlled', () => {\n    let valueState;\n    let setValueState: React.Dispatch<React.SetStateAction<number | string>>;\n    render(\n      <TestComponent defaultValue={1}>\n        {({ value, setValue }) => {\n          valueState = value;\n          setValueState = setValue;\n          return null;\n        }}\n      </TestComponent>,\n    );\n    expect(valueState).toBe(1);\n\n    act(() => {\n      setValueState(2);\n    });\n\n    expect(valueState).toBe(2);\n  });\n\n  it('works correctly when is controlled', () => {\n    let valueState;\n    render(\n      <TestComponent value={1}>\n        {({ value }) => {\n          valueState = value;\n          return null;\n        }}\n      </TestComponent>,\n    );\n    expect(valueState).toBe(1);\n  });\n\n  it('warns when switching from uncontrolled to controlled', () => {\n    let setProps: (newProps: any) => void;\n    expect(() => {\n      ({ setProps } = render(<TestComponent>{() => null}</TestComponent>));\n    }).not.toErrorDev();\n\n    expect(() => {\n      setProps({ value: 'foobar' });\n    }).toErrorDev(\n      'Base UI: A component is changing the uncontrolled value state of TestComponent to be controlled.',\n    );\n  });\n\n  it('should warn when switching from controlled to uncontrolled', () => {\n    let setProps: (newProps: any) => void;\n\n    expect(() => {\n      ({ setProps } = render(<TestComponent value=\"foobar\">{() => null}</TestComponent>));\n    }).not.toErrorDev();\n\n    expect(() => {\n      setProps({ value: undefined });\n    }).toErrorDev(\n      'Base UI: A component is changing the controlled value state of TestComponent to be uncontrolled.',\n    );\n  });\n\n  describe('prop: defaultValue', () => {\n    it('warns when changed after initial rendering', () => {\n      let setProps: (newProps: any) => void;\n\n      expect(() => {\n        ({ setProps } = render(<TestComponent>{() => null}</TestComponent>));\n      }).not.toErrorDev();\n\n      expect(() => {\n        setProps({ defaultValue: 1 });\n      }).toErrorDev(\n        'Base UI: A component is changing the default value state of an uncontrolled TestComponent after being initialized.',\n      );\n    });\n\n    it('does not warn when controlled', () => {\n      let setProps: (newProps: any) => void;\n\n      expect(() => {\n        ({ setProps } = render(\n          <TestComponent value={1} defaultValue={0}>\n            {() => null}\n          </TestComponent>,\n        ));\n      }).not.toErrorDev();\n\n      expect(() => {\n        setProps({ defaultValue: 1 });\n      }).not.toErrorDev();\n    });\n\n    it('does not warn when NaN', () => {\n      expect(() => {\n        render(<TestComponent defaultValue={NaN}>{() => null}</TestComponent>);\n      }).not.toErrorDev();\n    });\n\n    it('does not warn when an array', () => {\n      function TestComponentArray() {\n        useControlled({\n          controlled: undefined,\n          default: [],\n          name: 'TestComponent',\n        });\n        return null;\n      }\n\n      expect(() => {\n        render(<TestComponentArray />);\n      }).not.toErrorDev();\n    });\n  });\n});\n"
  },
  {
    "path": "packages/utils/src/useControlled.ts",
    "content": "'use client';\n// TODO: uncomment once we enable eslint-plugin-react-compiler // eslint-disable-next-line react-compiler/react-compiler -- process.env never changes, dependency arrays are intentionally ignored\n/* eslint-disable react-hooks/rules-of-hooks, react-hooks/exhaustive-deps */\nimport * as React from 'react';\n\nexport interface UseControlledProps<T = unknown> {\n  /**\n   * Holds the component value when it's controlled.\n   */\n  controlled: T | undefined;\n  /**\n   * The default value when uncontrolled.\n   */\n  default: T | undefined;\n  /**\n   * The component name displayed in warnings.\n   */\n  name: string;\n  /**\n   * The name of the state variable displayed in warnings.\n   */\n  state?: string | undefined;\n}\n\nexport function useControlled<T = unknown>({\n  controlled,\n  default: defaultProp,\n  name,\n  state = 'value',\n}: UseControlledProps<T>): [T, (newValue: T | ((prevValue: T) => T)) => void] {\n  // isControlled is ignored in the hook dependency lists as it should never change.\n  const { current: isControlled } = React.useRef(controlled !== undefined);\n  const [valueState, setValue] = React.useState(defaultProp);\n  const value = isControlled ? controlled : valueState;\n\n  if (process.env.NODE_ENV !== 'production') {\n    React.useEffect(() => {\n      if (isControlled !== (controlled !== undefined)) {\n        console.error(\n          [\n            `Base UI: A component is changing the ${\n              isControlled ? '' : 'un'\n            }controlled ${state} state of ${name} to be ${isControlled ? 'un' : ''}controlled.`,\n            'Elements should not switch from uncontrolled to controlled (or vice versa).',\n            `Decide between using a controlled or uncontrolled ${name} ` +\n              'element for the lifetime of the component.',\n            \"The nature of the state is determined during the first render. It's considered controlled if the value is not `undefined`.\",\n            'More info: https://fb.me/react-controlled-components',\n          ].join('\\n'),\n        );\n      }\n    }, [state, name, controlled]);\n\n    const { current: defaultValue } = React.useRef(defaultProp);\n\n    React.useEffect(() => {\n      // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is for more details.\n      if (!isControlled && JSON.stringify(defaultValue) !== JSON.stringify(defaultProp)) {\n        console.error(\n          [\n            `Base UI: A component is changing the default ${state} state of an uncontrolled ${name} after being initialized. ` +\n              `To suppress this warning opt to use a controlled ${name}.`,\n          ].join('\\n'),\n        );\n      }\n    }, [JSON.stringify(defaultProp)]);\n  }\n\n  const setValueIfUncontrolled = React.useCallback((newValue: React.SetStateAction<T>) => {\n    if (!isControlled) {\n      setValue(newValue as T);\n    }\n  }, []);\n\n  return [value as T, setValueIfUncontrolled];\n}\n"
  },
  {
    "path": "packages/utils/src/useEnhancedClickHandler.ts",
    "content": "'use client';\nimport * as React from 'react';\n\nexport type InteractionType = 'mouse' | 'touch' | 'pen' | 'keyboard' | '';\n\n/**\n * Provides a cross-browser way to determine the type of the pointer used to click.\n * Safari and Firefox do not provide the PointerEvent to the click handler (they use MouseEvent) yet.\n * Additionally, this implementation detects if the click was triggered by the keyboard.\n *\n * @param handler The function to be called when the button is clicked. The first parameter is the original event and the second parameter is the pointer type.\n */\nexport function useEnhancedClickHandler(\n  handler: (event: React.MouseEvent | React.PointerEvent, interactionType: InteractionType) => void,\n) {\n  const lastClickInteractionTypeRef = React.useRef<InteractionType>('');\n\n  const handlePointerDown = React.useCallback(\n    (event: React.PointerEvent) => {\n      if (event.defaultPrevented) {\n        return;\n      }\n\n      lastClickInteractionTypeRef.current = event.pointerType as InteractionType;\n      handler(event, event.pointerType as InteractionType);\n    },\n    [handler],\n  );\n\n  const handleClick = React.useCallback(\n    (event: React.MouseEvent | React.PointerEvent) => {\n      // event.detail has the number of clicks performed on the element. 0 means it was triggered by the keyboard.\n      if (event.detail === 0) {\n        handler(event, 'keyboard');\n        return;\n      }\n\n      if ('pointerType' in event) {\n        // Chrome and Edge correctly use PointerEvent\n        handler(event, event.pointerType);\n      } else {\n        handler(event, lastClickInteractionTypeRef.current);\n      }\n      lastClickInteractionTypeRef.current = '';\n    },\n    [handler],\n  );\n\n  return { onClick: handleClick, onPointerDown: handlePointerDown };\n}\n"
  },
  {
    "path": "packages/utils/src/useForcedRerendering.ts",
    "content": "'use client';\nimport * as React from 'react';\n\n/**\n * Returns a function that forces a rerender.\n */\nexport function useForcedRerendering() {\n  const [, setState] = React.useState({});\n\n  return React.useCallback(() => {\n    setState({});\n  }, []);\n}\n"
  },
  {
    "path": "packages/utils/src/useId.test.tsx",
    "content": "import { expect } from 'vitest';\nimport * as React from 'react';\nimport { createRenderer, screen } from '@mui/internal-test-utils';\nimport { useId } from '@base-ui/utils/useId';\n\ninterface TestComponentProps {\n  id?: string;\n}\n\ndescribe('useId', () => {\n  const { render, renderToString } = createRenderer();\n\n  it('returns the provided ID', () => {\n    function TestComponent({ id: idProp }: TestComponentProps) {\n      const id = useId(idProp);\n      return <span data-testid=\"target\" id={id} />;\n    }\n    const { hydrate } = renderToString(<TestComponent id=\"some-id\" />);\n    const { setProps } = hydrate();\n\n    expect(screen.getByTestId('target')).toHaveProperty('id', 'some-id');\n\n    setProps({ id: 'another-id' });\n\n    expect(screen.getByTestId('target')).toHaveProperty('id', 'another-id');\n  });\n\n  it(\"generates an ID if one isn't provided\", () => {\n    function TestComponent({ id: idProp }: TestComponentProps) {\n      const id = useId(idProp);\n      return <span data-testid=\"target\" id={id} />;\n    }\n    const { hydrate } = renderToString(<TestComponent />);\n    const { setProps } = hydrate();\n\n    expect(screen.getByTestId('target').id).not.toBe('');\n\n    setProps({ id: 'another-id' });\n    expect(screen.getByTestId('target')).toHaveProperty('id', 'another-id');\n  });\n\n  it('can be suffixed', () => {\n    function Widget() {\n      const id = useId();\n      const labelId = `${id}-label`;\n\n      return (\n        <React.Fragment>\n          <span data-testid=\"labelable\" aria-labelledby={labelId} />\n          <span data-testid=\"label\" id={labelId}>\n            Label\n          </span>\n        </React.Fragment>\n      );\n    }\n    render(<Widget />);\n\n    expect(screen.getByTestId('labelable')).toHaveAttribute(\n      'aria-labelledby',\n      screen.getByTestId('label').id,\n    );\n  });\n\n  it('can be used in in IDREF attributes', () => {\n    function Widget() {\n      const labelPartA = useId();\n      const labelPartB = useId();\n\n      return (\n        <React.Fragment>\n          <span data-testid=\"labelable\" aria-labelledby={`${labelPartA} ${labelPartB}`} />\n          <span data-testid=\"labelA\" id={labelPartA}>\n            A\n          </span>\n          <span data-testid=\"labelB\" id={labelPartB}>\n            B\n          </span>\n        </React.Fragment>\n      );\n    }\n    render(<Widget />);\n\n    expect(screen.getByTestId('labelable')).toHaveAttribute(\n      'aria-labelledby',\n      `${screen.getByTestId('labelA').id} ${screen.getByTestId('labelB').id}`,\n    );\n  });\n\n  it('provides an ID on server in React 18', ({ skip }) => {\n    if (React.useId === undefined) {\n      skip();\n    }\n    function TestComponent() {\n      const id = useId();\n      return <span data-testid=\"target\" id={id} />;\n    }\n    renderToString(<TestComponent />);\n\n    expect(screen.getByTestId('target').id).not.toBe('');\n  });\n\n  it('can be prefixed', () => {\n    const PREFIX = 'base-ui';\n    function Widget() {\n      const id = useId(undefined, PREFIX);\n\n      return (\n        <React.Fragment>\n          <span data-testid=\"labelable\" aria-labelledby={id} />\n          <span data-testid=\"label\" id={id}>\n            Label\n          </span>\n        </React.Fragment>\n      );\n    }\n    render(<Widget />);\n\n    expect(screen.getByTestId('label').id.slice(0, 8)).toBe(`${PREFIX}-`);\n    expect(screen.getByTestId('labelable')).toHaveAttribute(\n      'aria-labelledby',\n      screen.getByTestId('label').id,\n    );\n  });\n});\n"
  },
  {
    "path": "packages/utils/src/useId.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { SafeReact } from './safeReact';\n\nlet globalId = 0;\n\n// TODO React 17: Remove `useGlobalId` once React 17 support is removed\nfunction useGlobalId(idOverride?: string, prefix: string = 'mui'): string | undefined {\n  const [defaultId, setDefaultId] = React.useState(idOverride);\n  const id = idOverride || defaultId;\n  React.useEffect(() => {\n    if (defaultId == null) {\n      // Fallback to this default id when possible.\n      // Use the incrementing value for client-side rendering only.\n      // We can't use it server-side.\n      // If you want to use random values please consider the Birthday Problem: https://en.wikipedia.org/wiki/Birthday_problem\n      globalId += 1;\n      setDefaultId(`${prefix}-${globalId}`);\n    }\n  }, [defaultId, prefix]);\n  return id;\n}\n\nconst maybeReactUseId: undefined | (() => string) = SafeReact.useId;\n\n/**\n *\n * @example <div id={useId()} />\n * @param idOverride\n * @returns {string}\n */\nexport function useId(idOverride?: string, prefix?: string): string | undefined {\n  // React.useId() is only available from React 17.0.0.\n  if (maybeReactUseId !== undefined) {\n    const reactId = maybeReactUseId();\n    return idOverride ?? (prefix ? `${prefix}-${reactId}` : reactId);\n  }\n\n  // TODO: uncomment once we enable eslint-plugin-react-compiler // eslint-disable-next-line react-compiler/react-compiler\n  // eslint-disable-next-line react-hooks/rules-of-hooks -- `React.useId` is invariant at runtime.\n  return useGlobalId(idOverride, prefix);\n}\n"
  },
  {
    "path": "packages/utils/src/useInterval.ts",
    "content": "'use client';\nimport { useRefWithInit } from './useRefWithInit';\nimport { useOnMount } from './useOnMount';\nimport { Timeout } from './useTimeout';\n\ntype IntervalId = number;\n\nconst EMPTY = 0 as IntervalId;\n\nexport class Interval extends Timeout {\n  static create() {\n    return new Interval();\n  }\n\n  /**\n   * Executes `fn` at `delay` interval, clearing any previously scheduled call.\n   */\n  start(delay: number, fn: Function) {\n    this.clear();\n    this.currentId = setInterval(() => {\n      fn();\n    }, delay) as unknown as number;\n  }\n\n  clear = () => {\n    if (this.currentId !== EMPTY) {\n      clearInterval(this.currentId as IntervalId);\n      this.currentId = EMPTY;\n    }\n  };\n}\n\n/**\n * A `setInterval` with automatic cleanup and guard.\n */\nexport function useInterval() {\n  const timeout = useRefWithInit(Interval.create).current;\n\n  useOnMount(timeout.disposeEffect);\n\n  return timeout;\n}\n"
  },
  {
    "path": "packages/utils/src/useIsoLayoutEffect.ts",
    "content": "'use client';\nimport * as React from 'react';\n\nconst noop = () => {};\n\nexport const useIsoLayoutEffect = typeof document !== 'undefined' ? React.useLayoutEffect : noop;\n"
  },
  {
    "path": "packages/utils/src/useMergedRefs.test.tsx",
    "content": "import { expect, vi } from 'vitest';\nimport * as React from 'react';\nimport { createRenderer, MuiRenderResult, screen } from '@mui/internal-test-utils';\nimport { getReactElementRef } from './getReactElementRef';\nimport { useMergedRefs } from './useMergedRefs';\n\ndescribe('useMergedRefs', () => {\n  const { render } = createRenderer();\n\n  it('returns a single ref-setter function that forks the ref to its inputs', () => {\n    interface TestComponentProps {\n      innerRef: React.Ref<HTMLDivElement | null>;\n    }\n\n    function Component(props: TestComponentProps) {\n      const { innerRef } = props;\n      const [ownRefCurrent, ownRef] = React.useState<HTMLDivElement | null>(null);\n\n      const handleRef = useMergedRefs(innerRef, ownRef);\n\n      return <div ref={handleRef}>{ownRefCurrent ? 'has a ref' : 'has no ref'}</div>;\n    }\n\n    const outerRef = React.createRef<HTMLDivElement>();\n\n    expect(() => {\n      render(<Component innerRef={outerRef} />);\n    }).not.toErrorDev();\n    expect(outerRef.current!.textContent).toBe('has a ref');\n  });\n\n  it('forks if only one of the branches requires a ref', () => {\n    const Component = React.forwardRef(function Component(props, ref) {\n      const [hasRef, setHasRef] = React.useState(false);\n      const handleOwnRef = React.useCallback(() => setHasRef(true), []);\n      const handleRef = useMergedRefs(handleOwnRef, ref);\n\n      return (\n        <div ref={handleRef} data-testid=\"hasRef\">\n          {String(hasRef)}\n        </div>\n      );\n    });\n\n    expect(() => {\n      render(<Component />);\n    }).not.toErrorDev();\n\n    expect(screen.getByTestId('hasRef')).toHaveTextContent('true');\n  });\n\n  it('does nothing if none of the forked branches requires a ref', () => {\n    interface TestComponentProps {\n      children: React.ReactElement<any>;\n    }\n\n    const Outer = React.forwardRef<HTMLDivElement, TestComponentProps>(function Outer(props, ref) {\n      const { children } = props;\n      const handleRef = useMergedRefs(getReactElementRef(children), ref);\n\n      return React.cloneElement(children, { ref: handleRef });\n    });\n\n    function Inner() {\n      return <div />;\n    }\n\n    expect(() => {\n      render(\n        <Outer>\n          <Inner />\n        </Outer>,\n      );\n    }).not.toErrorDev();\n  });\n\n  describe('changing refs', () => {\n    interface TestComponentProps {\n      leftRef?: React.Ref<HTMLDivElement | null>;\n      rightRef?: React.Ref<HTMLDivElement | null>;\n      id?: string;\n    }\n\n    function Div(props: TestComponentProps) {\n      const { leftRef, rightRef, ...other } = props;\n      const handleRef = useMergedRefs(leftRef, rightRef);\n\n      return <div {...other} ref={handleRef} />;\n    }\n\n    it('handles changing from no ref to some ref', () => {\n      let view: MuiRenderResult;\n\n      expect(() => {\n        view = render(<Div id=\"test\" />);\n      }).not.toErrorDev();\n\n      const ref = React.createRef<HTMLDivElement>();\n      expect(() => {\n        view.setProps({ leftRef: ref });\n      }).not.toErrorDev();\n      expect(ref.current!.id).toBe('test');\n    });\n\n    it('cleans up detached refs', () => {\n      const firstLeftRef = React.createRef<HTMLDivElement>();\n      const firstRightRef = React.createRef<HTMLDivElement>();\n      const secondRightRef = React.createRef<HTMLDivElement>();\n      let view: MuiRenderResult;\n\n      expect(() => {\n        view = render(<Div leftRef={firstLeftRef} rightRef={firstRightRef} id=\"test\" />);\n      }).not.toErrorDev();\n\n      expect(firstLeftRef.current!.id).toBe('test');\n      expect(firstRightRef.current!.id).toBe('test');\n      expect(secondRightRef.current).toBe(null);\n\n      view!.setProps({ rightRef: secondRightRef });\n\n      expect(firstLeftRef.current!.id).toBe('test');\n      expect(firstRightRef.current).toBe(null);\n      expect(secondRightRef.current!.id).toBe('test');\n    });\n  });\n\n  test('calls clean up function if it exists', () => {\n    const cleanUp = vi.fn();\n    const setup = vi.fn();\n    const setup2 = vi.fn();\n    const nullHandler = vi.fn();\n\n    function onRefChangeWithCleanup(ref: HTMLDivElement | null) {\n      if (ref) {\n        setup(ref.id);\n      } else {\n        nullHandler();\n      }\n      return cleanUp;\n    }\n\n    function onRefChangeWithoutCleanup(ref: HTMLDivElement | null) {\n      if (ref) {\n        setup2(ref.id);\n      } else {\n        nullHandler();\n      }\n    }\n\n    function App() {\n      const ref = useMergedRefs(onRefChangeWithCleanup, onRefChangeWithoutCleanup);\n      return <div id=\"test\" ref={ref} />;\n    }\n\n    const { unmount } = render(<App />, { strict: false });\n\n    expect(setup.mock.calls[0][0]).toBe('test');\n    expect(setup.mock.calls.length).toBe(1);\n    expect(cleanUp.mock.calls.length).toBe(0);\n\n    expect(setup2.mock.calls[0][0]).toBe('test');\n    expect(setup2.mock.calls.length).toBe(1);\n\n    unmount();\n\n    expect(setup.mock.calls.length).toBe(1);\n    expect(cleanUp.mock.calls.length).toBe(1);\n\n    // Setup was not called again\n    expect(setup2.mock.calls.length).toBe(1);\n    // Null handler hit because no cleanup is returned\n    expect(nullHandler.mock.calls.length).toBe(1);\n  });\n});\n"
  },
  {
    "path": "packages/utils/src/useMergedRefs.ts",
    "content": "import * as React from 'react';\nimport { useRefWithInit } from './useRefWithInit';\n\ntype Empty = null | undefined;\ntype InputRef<I> = React.Ref<I> | Empty;\ntype Result<I> = React.RefCallback<I> | null;\ntype Cleanup = () => void;\n\ntype ForkRef<I> = {\n  callback: React.RefCallback<I> | null;\n  cleanup: Cleanup | null;\n  refs: InputRef<I>[];\n};\n\n/**\n * Merges refs into a single memoized callback ref or `null`.\n * This makes sure multiple refs are updated together and have the same value.\n *\n * This function accepts up to four refs. If you need to merge more, or have an unspecified number of refs to merge,\n * use `useMergedRefsN` instead.\n */\nexport function useMergedRefs<I>(a: InputRef<I>, b: InputRef<I>): Result<I>;\nexport function useMergedRefs<I>(a: InputRef<I>, b: InputRef<I>, c: InputRef<I>): Result<I>;\nexport function useMergedRefs<I>(\n  a: InputRef<I>,\n  b: InputRef<I>,\n  c: InputRef<I>,\n  d: InputRef<I>,\n): Result<I>;\nexport function useMergedRefs<I>(\n  a: InputRef<I>,\n  b: InputRef<I>,\n  c?: InputRef<I>,\n  d?: InputRef<I>,\n): Result<I> {\n  const forkRef = useRefWithInit(createForkRef<I>).current;\n  if (didChange(forkRef, a, b, c, d)) {\n    update(forkRef, [a, b, c, d]);\n  }\n  return forkRef.callback;\n}\n\n/**\n * Merges an array of refs into a single memoized callback ref or `null`.\n *\n * If you need to merge a fixed number (up to four) of refs, use `useMergedRefs` instead for better performance.\n */\nexport function useMergedRefsN<I>(refs: InputRef<I>[]): Result<I> {\n  const forkRef = useRefWithInit(createForkRef<I>).current;\n  if (didChangeN(forkRef, refs)) {\n    update(forkRef, refs);\n  }\n  return forkRef.callback;\n}\n\nfunction createForkRef<I>(): ForkRef<I> {\n  return {\n    callback: null,\n    cleanup: null as Cleanup | null,\n    refs: [],\n  };\n}\n\nfunction didChange<I>(\n  forkRef: ForkRef<I>,\n  a: InputRef<I>,\n  b: InputRef<I>,\n  c: InputRef<I>,\n  d: InputRef<I>,\n) {\n  // prettier-ignore\n  return (\n    forkRef.refs[0] !== a ||\n    forkRef.refs[1] !== b ||\n    forkRef.refs[2] !== c ||\n    forkRef.refs[3] !== d\n  )\n}\n\nfunction didChangeN<I>(forkRef: ForkRef<I>, newRefs: InputRef<I>[]) {\n  return (\n    forkRef.refs.length !== newRefs.length ||\n    forkRef.refs.some((ref, index) => ref !== newRefs[index])\n  );\n}\n\nfunction update<I>(forkRef: ForkRef<I>, refs: InputRef<I>[]) {\n  forkRef.refs = refs;\n\n  if (refs.every((ref) => ref == null)) {\n    forkRef.callback = null;\n    return;\n  }\n\n  forkRef.callback = (instance: I) => {\n    if (forkRef.cleanup) {\n      forkRef.cleanup();\n      forkRef.cleanup = null;\n    }\n\n    if (instance != null) {\n      const cleanupCallbacks = Array(refs.length).fill(null) as Array<Cleanup | null>;\n\n      for (let i = 0; i < refs.length; i += 1) {\n        const ref = refs[i];\n        if (ref == null) {\n          continue;\n        }\n        switch (typeof ref) {\n          case 'function': {\n            const refCleanup = ref(instance);\n            if (typeof refCleanup === 'function') {\n              cleanupCallbacks[i] = refCleanup;\n            }\n            break;\n          }\n          case 'object': {\n            ref.current = instance;\n            break;\n          }\n          default:\n        }\n      }\n\n      forkRef.cleanup = () => {\n        for (let i = 0; i < refs.length; i += 1) {\n          const ref = refs[i];\n          if (ref == null) {\n            continue;\n          }\n          switch (typeof ref) {\n            case 'function': {\n              const cleanupCallback = cleanupCallbacks[i];\n              if (typeof cleanupCallback === 'function') {\n                cleanupCallback();\n              } else {\n                ref(null);\n              }\n              break;\n            }\n            case 'object': {\n              ref.current = null;\n              break;\n            }\n            default:\n          }\n        }\n      };\n    }\n  };\n}\n"
  },
  {
    "path": "packages/utils/src/useOnFirstRender.ts",
    "content": "'use client';\nimport * as React from 'react';\n\nexport function useOnFirstRender(fn: Function) {\n  const ref = React.useRef(true);\n  if (ref.current) {\n    ref.current = false;\n    fn();\n  }\n}\n"
  },
  {
    "path": "packages/utils/src/useOnMount.ts",
    "content": "'use client';\nimport * as React from 'react';\n\nconst EMPTY = [] as unknown[];\n\n/**\n * A React.useEffect equivalent that runs once, when the component is mounted.\n */\nexport function useOnMount(fn: React.EffectCallback) {\n  // TODO: uncomment once we enable eslint-plugin-react-compiler // eslint-disable-next-line react-compiler/react-compiler -- no need to put `fn` in the dependency array\n  /* eslint-disable react-hooks/exhaustive-deps */\n  React.useEffect(fn, EMPTY);\n  /* eslint-enable react-hooks/exhaustive-deps */\n}\n"
  },
  {
    "path": "packages/utils/src/usePreviousValue.test.tsx",
    "content": "import { expect } from 'vitest';\nimport * as React from 'react';\nimport { act, createRenderer } from '@mui/internal-test-utils';\nimport { usePreviousValue } from './usePreviousValue';\n\ninterface TestComponentProps {\n  value: any;\n  unrelatedProp?: any;\n  children: (previous: any) => React.ReactNode;\n}\n\nfunction TestComponent({ value, children }: TestComponentProps) {\n  const previous = usePreviousValue(value);\n  return children(previous);\n}\n\ndescribe('usePrevious', () => {\n  const { render } = createRenderer();\n\n  it('should return null on the first render', () => {\n    let previousValue: any;\n    render(\n      <TestComponent value=\"first\">\n        {(previous) => {\n          previousValue = previous;\n          return null;\n        }}\n      </TestComponent>,\n    );\n\n    expect(previousValue).toBe(null);\n  });\n\n  it('should return the previous value on subsequent renders', () => {\n    let previousValue: any;\n    const { setProps } = render(\n      <TestComponent value=\"first\">\n        {(previous) => {\n          previousValue = previous;\n          return null;\n        }}\n      </TestComponent>,\n    );\n\n    expect(previousValue).toBe(null);\n\n    setProps({ value: 'second' });\n    expect(previousValue).toBe('first');\n\n    setProps({ value: 'third' });\n    expect(previousValue).toBe('second');\n  });\n\n  it('should work with primitive values', () => {\n    let previousValue: any;\n    const { setProps } = render(\n      <TestComponent value={42}>\n        {(previous) => {\n          previousValue = previous;\n          return null;\n        }}\n      </TestComponent>,\n    );\n\n    expect(previousValue).toBe(null);\n\n    setProps({ value: 100 });\n    expect(previousValue).toBe(42);\n\n    setProps({ value: true });\n    expect(previousValue).toBe(100);\n\n    setProps({ value: false });\n    expect(previousValue).toBe(true);\n  });\n\n  it('should ignore renders where the value does not change', () => {\n    let previousValue: any;\n    const { setProps } = render(\n      <TestComponent value=\"stable\">\n        {(previous) => {\n          previousValue = previous;\n          return null;\n        }}\n      </TestComponent>,\n    );\n\n    expect(previousValue).toBe(null);\n\n    setProps({ unrelatedProp: 1 });\n    expect(previousValue).toBe(null);\n\n    setProps({ unrelatedProp: 2 });\n    expect(previousValue).toBe(null);\n  });\n\n  it('should work with object values', () => {\n    let previousValue: any;\n    const obj1 = { a: 1 };\n    const obj2 = { b: 2 };\n    const obj3 = { c: 3 };\n\n    const { setProps } = render(\n      <TestComponent value={obj1}>\n        {(previous) => {\n          previousValue = previous;\n          return null;\n        }}\n      </TestComponent>,\n    );\n\n    expect(previousValue).toBe(null);\n\n    setProps({ value: obj2 });\n    expect(previousValue).toBe(obj1);\n\n    setProps({ value: obj3 });\n    expect(previousValue).toBe(obj2);\n  });\n\n  it('should handle undefined and null values', () => {\n    let previousValue: any;\n    const { setProps } = render(\n      <TestComponent value={undefined}>\n        {(previous) => {\n          previousValue = previous;\n          return null;\n        }}\n      </TestComponent>,\n    );\n\n    expect(previousValue).toBe(null);\n\n    setProps({ value: null });\n    expect(previousValue).toBe(undefined);\n\n    setProps({ value: 'defined' });\n    expect(previousValue).toBe(null);\n\n    setProps({ value: undefined });\n    expect(previousValue).toBe('defined');\n  });\n\n  it('should handle rapid value changes', () => {\n    let previousValue: any;\n    const { setProps } = render(\n      <TestComponent value=\"initial\">\n        {(previous) => {\n          previousValue = previous;\n          return null;\n        }}\n      </TestComponent>,\n    );\n\n    expect(previousValue).toBe(null);\n\n    act(() => {\n      setProps({ value: 'first' });\n      setProps({ value: 'second' });\n      setProps({ value: 'third' });\n    });\n\n    // With React batching, only the final value 'third' causes a render,\n    // so the previous value should be 'initial' (from the first render)\n    expect(previousValue).toBe('initial');\n  });\n\n  it('should maintain type safety', () => {\n    let previousValue: string | null = null;\n    const { setProps } = render(\n      <TestComponent value=\"hello\">\n        {(previous: string | null) => {\n          previousValue = previous;\n          return null;\n        }}\n      </TestComponent>,\n    );\n\n    expect(previousValue).toBe(null);\n\n    setProps({ value: 'world' });\n    expect(previousValue).toBe('hello');\n    expect(typeof previousValue).toBe('string');\n  });\n});\n"
  },
  {
    "path": "packages/utils/src/usePreviousValue.ts",
    "content": "'use client';\nimport * as React from 'react';\n\n/**\n * Returns a previous value of its argument.\n * @param value Current value.\n * @returns Previous value, or null if there is no previous value.\n */\nexport function usePreviousValue<T>(value: T): T | null {\n  const [state, setState] = React.useState<{ current: T; previous: T | null }>({\n    current: value,\n    previous: null,\n  });\n\n  if (value !== state.current) {\n    setState({ current: value, previous: state.current });\n  }\n\n  return state.previous;\n}\n"
  },
  {
    "path": "packages/utils/src/useRefWithInit.ts",
    "content": "'use client';\nimport * as React from 'react';\n\nconst UNINITIALIZED = {};\n\n/**\n * A React.useRef() that is initialized with a function. Note that it accepts an optional\n * initialization argument, so the initialization function doesn't need to be an inline closure.\n *\n * @usage\n *   const ref = useRefWithInit(sortColumns, columns)\n */\nexport function useRefWithInit<T>(init: () => T): React.RefObject<T>;\nexport function useRefWithInit<T, U>(init: (arg: U) => T, initArg: U): React.RefObject<T>;\nexport function useRefWithInit(init: (arg?: unknown) => unknown, initArg?: unknown) {\n  const ref = React.useRef(UNINITIALIZED as any);\n\n  if (ref.current === UNINITIALIZED) {\n    ref.current = init(initArg);\n  }\n\n  return ref;\n}\n"
  },
  {
    "path": "packages/utils/src/useScrollLock.ts",
    "content": "'use client';\nimport { isOverflowElement } from '@floating-ui/utils/dom';\nimport { isIOS, isWebKit } from './detectBrowser';\nimport { ownerDocument, ownerWindow } from './owner';\nimport { useIsoLayoutEffect } from './useIsoLayoutEffect';\nimport { Timeout } from './useTimeout';\nimport { AnimationFrame } from './useAnimationFrame';\nimport { NOOP } from './empty';\n\nlet originalHtmlStyles: Partial<CSSStyleDeclaration> = {};\nlet originalBodyStyles: Partial<CSSStyleDeclaration> = {};\nlet originalHtmlScrollBehavior = '';\n\nfunction hasInsetScrollbars(referenceElement: Element | null) {\n  if (typeof document === 'undefined') {\n    return false;\n  }\n  const doc = ownerDocument(referenceElement);\n  const win = ownerWindow(doc);\n  return win.innerWidth - doc.documentElement.clientWidth > 0;\n}\n\nfunction supportsStableScrollbarGutter(referenceElement: Element | null) {\n  const supported =\n    typeof CSS !== 'undefined' && CSS.supports && CSS.supports('scrollbar-gutter', 'stable');\n\n  if (!supported || typeof document === 'undefined') {\n    return false;\n  }\n\n  const doc = ownerDocument(referenceElement);\n  const html = doc.documentElement;\n  const body = doc.body;\n\n  const scrollContainer = isOverflowElement(html) ? html : body;\n\n  const originalScrollContainerOverflowY = scrollContainer.style.overflowY;\n  const originalHtmlStyleGutter = html.style.scrollbarGutter;\n\n  html.style.scrollbarGutter = 'stable';\n\n  scrollContainer.style.overflowY = 'scroll';\n  const before = scrollContainer.offsetWidth;\n\n  scrollContainer.style.overflowY = 'hidden';\n  const after = scrollContainer.offsetWidth;\n\n  scrollContainer.style.overflowY = originalScrollContainerOverflowY;\n  html.style.scrollbarGutter = originalHtmlStyleGutter;\n\n  return before === after;\n}\n\nfunction preventScrollOverlayScrollbars(referenceElement: Element | null) {\n  const doc = ownerDocument(referenceElement);\n  const html = doc.documentElement;\n  const body = doc.body;\n\n  // If an `overflow` style is present on <html>, we need to lock it, because a lock on <body>\n  // won't have any effect.\n  // But if <body> has an `overflow` style (like `overflow-x: hidden`), we need to lock it\n  // instead, as sticky elements shift otherwise.\n  const elementToLock = isOverflowElement(html) ? html : body;\n  const originalElementToLockStyles = {\n    overflowY: elementToLock.style.overflowY,\n    overflowX: elementToLock.style.overflowX,\n  };\n\n  Object.assign(elementToLock.style, {\n    overflowY: 'hidden',\n    overflowX: 'hidden',\n  });\n\n  return () => {\n    Object.assign(elementToLock.style, originalElementToLockStyles);\n  };\n}\n\nfunction preventScrollInsetScrollbars(referenceElement: Element | null) {\n  const doc = ownerDocument(referenceElement);\n  const html = doc.documentElement;\n  const body = doc.body;\n  const win = ownerWindow(html);\n\n  let scrollTop = 0;\n  let scrollLeft = 0;\n  let updateGutterOnly = false;\n  const resizeFrame = AnimationFrame.create();\n\n  // Pinch-zoom in Safari causes a shift. Just don't lock scroll if there's any pinch-zoom.\n  if (isWebKit && (win.visualViewport?.scale ?? 1) !== 1) {\n    return () => {};\n  }\n\n  function lockScroll() {\n    /* DOM reads: */\n\n    const htmlStyles = win.getComputedStyle(html);\n    const bodyStyles = win.getComputedStyle(body);\n    const htmlScrollbarGutterValue = htmlStyles.scrollbarGutter || '';\n    const hasBothEdges = htmlScrollbarGutterValue.includes('both-edges');\n    const scrollbarGutterValue = hasBothEdges ? 'stable both-edges' : 'stable';\n\n    scrollTop = html.scrollTop;\n    scrollLeft = html.scrollLeft;\n\n    originalHtmlStyles = {\n      scrollbarGutter: html.style.scrollbarGutter,\n      overflowY: html.style.overflowY,\n      overflowX: html.style.overflowX,\n    };\n    originalHtmlScrollBehavior = html.style.scrollBehavior;\n\n    originalBodyStyles = {\n      position: body.style.position,\n      height: body.style.height,\n      width: body.style.width,\n      boxSizing: body.style.boxSizing,\n      overflowY: body.style.overflowY,\n      overflowX: body.style.overflowX,\n      scrollBehavior: body.style.scrollBehavior,\n    };\n\n    const isScrollableY = html.scrollHeight > html.clientHeight;\n    const isScrollableX = html.scrollWidth > html.clientWidth;\n    const hasConstantOverflowY =\n      htmlStyles.overflowY === 'scroll' || bodyStyles.overflowY === 'scroll';\n    const hasConstantOverflowX =\n      htmlStyles.overflowX === 'scroll' || bodyStyles.overflowX === 'scroll';\n\n    // Values can be negative in Firefox\n    const scrollbarWidth = Math.max(0, win.innerWidth - body.clientWidth);\n    const scrollbarHeight = Math.max(0, win.innerHeight - body.clientHeight);\n\n    // Avoid shift due to the default <body> margin. This does cause elements to be clipped\n    // with whitespace. Warn if <body> has margins?\n    const marginY = parseFloat(bodyStyles.marginTop) + parseFloat(bodyStyles.marginBottom);\n    const marginX = parseFloat(bodyStyles.marginLeft) + parseFloat(bodyStyles.marginRight);\n    const elementToLock = isOverflowElement(html) ? html : body;\n\n    updateGutterOnly = supportsStableScrollbarGutter(referenceElement);\n\n    /*\n     * DOM writes:\n     * Do not read the DOM past this point!\n     */\n\n    if (updateGutterOnly) {\n      html.style.scrollbarGutter = scrollbarGutterValue;\n      elementToLock.style.overflowY = 'hidden';\n      elementToLock.style.overflowX = 'hidden';\n      return;\n    }\n\n    Object.assign(html.style, {\n      scrollbarGutter: scrollbarGutterValue,\n      overflowY: 'hidden',\n      overflowX: 'hidden',\n    });\n\n    if (isScrollableY || hasConstantOverflowY) {\n      html.style.overflowY = 'scroll';\n    }\n    if (isScrollableX || hasConstantOverflowX) {\n      html.style.overflowX = 'scroll';\n    }\n\n    Object.assign(body.style, {\n      position: 'relative',\n      height:\n        marginY || scrollbarHeight ? `calc(100dvh - ${marginY + scrollbarHeight}px)` : '100dvh',\n      width: marginX || scrollbarWidth ? `calc(100vw - ${marginX + scrollbarWidth}px)` : '100vw',\n      boxSizing: 'border-box',\n      overflow: 'hidden',\n      scrollBehavior: 'unset',\n    });\n\n    body.scrollTop = scrollTop;\n    body.scrollLeft = scrollLeft;\n    html.setAttribute('data-base-ui-scroll-locked', '');\n    html.style.scrollBehavior = 'unset';\n  }\n\n  function cleanup() {\n    Object.assign(html.style, originalHtmlStyles);\n    Object.assign(body.style, originalBodyStyles);\n\n    if (!updateGutterOnly) {\n      html.scrollTop = scrollTop;\n      html.scrollLeft = scrollLeft;\n      html.removeAttribute('data-base-ui-scroll-locked');\n      html.style.scrollBehavior = originalHtmlScrollBehavior;\n    }\n  }\n\n  function handleResize() {\n    cleanup();\n    resizeFrame.request(lockScroll);\n  }\n\n  lockScroll();\n  win.addEventListener('resize', handleResize);\n\n  return () => {\n    resizeFrame.cancel();\n    cleanup();\n    // Sometimes this cleanup can be run after test teardown\n    // because it is called in a `setTimeout(fn, 0)`,\n    // in which case `removeEventListener` wouldn't be available,\n    // so we check for it to avoid test failures.\n    if (typeof win.removeEventListener === 'function') {\n      win.removeEventListener('resize', handleResize);\n    }\n  };\n}\n\nclass ScrollLocker {\n  lockCount = 0;\n  restore = null as (() => void) | null;\n  timeoutLock = Timeout.create();\n  timeoutUnlock = Timeout.create();\n\n  acquire(referenceElement: Element | null) {\n    this.lockCount += 1;\n    if (this.lockCount === 1 && this.restore === null) {\n      this.timeoutLock.start(0, () => this.lock(referenceElement));\n    }\n    return this.release;\n  }\n\n  release = () => {\n    this.lockCount -= 1;\n    if (this.lockCount === 0 && this.restore) {\n      this.timeoutUnlock.start(0, this.unlock);\n    }\n  };\n\n  private unlock = () => {\n    if (this.lockCount === 0 && this.restore) {\n      this.restore?.();\n      this.restore = null;\n    }\n  };\n\n  private lock(referenceElement: Element | null) {\n    if (this.lockCount === 0 || this.restore !== null) {\n      return;\n    }\n\n    const doc = ownerDocument(referenceElement);\n    const html = doc.documentElement;\n    const htmlOverflowY = ownerWindow(html).getComputedStyle(html).overflowY;\n\n    // If the site author already hid overflow on <html>, respect it and bail out.\n    if (htmlOverflowY === 'hidden' || htmlOverflowY === 'clip') {\n      this.restore = NOOP;\n      return;\n    }\n\n    const hasOverlayScrollbars = isIOS || !hasInsetScrollbars(referenceElement);\n\n    // On iOS, scroll locking does not work if the navbar is collapsed. Due to numerous\n    // side effects and bugs that arise on iOS, it must be researched extensively before\n    // being enabled to ensure it doesn't cause the following issues:\n    // - Textboxes must scroll into view when focused, nor cause a glitchy scroll animation.\n    // - The navbar must not force itself into view and cause layout shift.\n    // - Scroll containers must not flicker upon closing a popup when it has an exit animation.\n    this.restore = hasOverlayScrollbars\n      ? preventScrollOverlayScrollbars(referenceElement)\n      : preventScrollInsetScrollbars(referenceElement);\n  }\n}\n\nconst SCROLL_LOCKER = new ScrollLocker();\n\n/**\n * Locks the scroll of the document when enabled.\n *\n * @param enabled - Whether to enable the scroll lock.\n * @param referenceElement - Element to use as a reference for lock calculations.\n */\nexport function useScrollLock(enabled: boolean = true, referenceElement: Element | null = null) {\n  useIsoLayoutEffect(() => {\n    if (!enabled) {\n      return undefined;\n    }\n    return SCROLL_LOCKER.acquire(referenceElement);\n  }, [enabled, referenceElement]);\n}\n"
  },
  {
    "path": "packages/utils/src/useStableCallback.ts",
    "content": "'use client';\nimport * as React from 'react';\nimport { useRefWithInit } from './useRefWithInit';\n\n// https://github.com/mui/material-ui/issues/41190#issuecomment-2040873379\nconst useInsertionEffect = (React as any)[\n  `useInsertionEffect${Math.random().toFixed(1)}`.slice(0, -3)\n];\nconst useSafeInsertionEffect =\n  // React 17 doesn't have useInsertionEffect.\n  useInsertionEffect &&\n  // Preact replaces useInsertionEffect with useLayoutEffect and fires too late.\n  useInsertionEffect !== React.useLayoutEffect\n    ? useInsertionEffect\n    : (fn: any) => fn();\n\ntype Callback = (...args: any[]) => any;\n\ntype Stable<T extends Callback> = {\n  /** The next value for callback */\n  next: T | undefined;\n  /** The function to be called by trampoline. This must fail during the initial render phase. */\n  callback: T | undefined;\n  trampoline: T;\n  effect: () => void;\n};\n\n/**\n * Stabilizes the function passed so it's always the same between renders.\n *\n * The function becomes non-reactive to any values it captures.\n * It can safely be passed as a dependency of `React.useMemo` and `React.useEffect` without re-triggering them if its captured values change.\n *\n * The function must only be called inside effects and event handlers, never during render (which throws an error).\n *\n * This hook is a more permissive version of React 19.2's `React.useEffectEvent` in that it can be passed through contexts and called in event handler props, not just effects.\n */\nexport function useStableCallback<T extends Callback>(callback: T | undefined): T {\n  const stable = useRefWithInit(createStableCallback).current;\n  stable.next = callback;\n  useSafeInsertionEffect(stable.effect);\n  return stable.trampoline;\n}\n\nfunction createStableCallback() {\n  const stable: Stable<any> = {\n    next: undefined,\n    callback: assertNotCalled,\n    trampoline: (...args: []) => stable.callback?.(...args),\n    effect: () => {\n      stable.callback = stable.next;\n    },\n  };\n  return stable;\n}\n\nfunction assertNotCalled() {\n  if (process.env.NODE_ENV !== 'production') {\n    throw /* minify-error-disabled */ new Error(\n      'Base UI: Cannot call an event handler while rendering.',\n    );\n  }\n}\n"
  },
  {
    "path": "packages/utils/src/useTimeout.ts",
    "content": "'use client';\nimport { useRefWithInit } from './useRefWithInit';\nimport { useOnMount } from './useOnMount';\n\ntype TimeoutId = number;\n\nconst EMPTY = 0 as TimeoutId;\n\nexport class Timeout {\n  static create() {\n    return new Timeout();\n  }\n\n  currentId: TimeoutId = EMPTY;\n\n  /**\n   * Executes `fn` after `delay`, clearing any previously scheduled call.\n   */\n  start(delay: number, fn: Function) {\n    this.clear();\n    this.currentId = setTimeout(() => {\n      this.currentId = EMPTY;\n      fn();\n    }, delay) as unknown as number; /* Node.js types are enabled in development */\n  }\n\n  isStarted() {\n    return this.currentId !== EMPTY;\n  }\n\n  clear = () => {\n    if (this.currentId !== EMPTY) {\n      clearTimeout(this.currentId as TimeoutId);\n      this.currentId = EMPTY;\n    }\n  };\n\n  disposeEffect = () => {\n    return this.clear;\n  };\n}\n\n/**\n * A `setTimeout` with automatic cleanup and guard.\n */\nexport function useTimeout() {\n  const timeout = useRefWithInit(Timeout.create).current;\n\n  useOnMount(timeout.disposeEffect);\n\n  return timeout;\n}\n"
  },
  {
    "path": "packages/utils/src/useValueAsRef.ts",
    "content": "'use client';\nimport { useIsoLayoutEffect } from './useIsoLayoutEffect';\nimport { useRefWithInit } from './useRefWithInit';\n\n/**\n * Untracks the provided value by turning it into a ref to remove its reactivity.\n *\n * Used to access the passed value inside `React.useEffect` without causing the effect to re-run when the value changes.\n */\nexport function useValueAsRef<T>(value: T) {\n  const latest = useRefWithInit(createLatestRef, value).current;\n\n  latest.next = value;\n\n  // eslint-disable-next-line react-hooks/exhaustive-deps\n  useIsoLayoutEffect(latest.effect);\n\n  return latest;\n}\n\nfunction createLatestRef<T>(value: T) {\n  const latest = {\n    current: value,\n    next: value,\n    effect: () => {\n      latest.current = latest.next;\n    },\n  };\n  return latest;\n}\n"
  },
  {
    "path": "packages/utils/src/visuallyHidden.ts",
    "content": "import * as React from 'react';\n\nconst visuallyHiddenBase: React.CSSProperties = {\n  clipPath: 'inset(50%)',\n  overflow: 'hidden',\n  whiteSpace: 'nowrap',\n  border: 0,\n  padding: 0,\n  width: 1,\n  height: 1,\n  margin: -1,\n};\n\nexport const visuallyHidden: React.CSSProperties = {\n  ...visuallyHiddenBase,\n  position: 'fixed',\n  top: 0,\n  left: 0,\n};\n\nexport const visuallyHiddenInput: React.CSSProperties = {\n  ...visuallyHiddenBase,\n  position: 'absolute',\n};\n"
  },
  {
    "path": "packages/utils/src/warn.ts",
    "content": "let set: Set<string>;\nif (process.env.NODE_ENV !== 'production') {\n  set = new Set<string>();\n}\n\nexport function warn(...messages: string[]) {\n  if (process.env.NODE_ENV !== 'production') {\n    const messageKey = messages.join(' ');\n    if (!set.has(messageKey)) {\n      set.add(messageKey);\n      console.warn(`Base UI: ${messageKey}`);\n    }\n  }\n}\n"
  },
  {
    "path": "packages/utils/tsconfig.build.json",
    "content": "{\n  \"extends\": \"../../tsconfig.base.json\",\n  // This config is for emitting declarations (.d.ts) only.\n  // TS source files are transpiled to JS by Babel.\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"declaration\": true,\n    \"emitDeclarationOnly\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"bundler\",\n    \"noEmit\": false,\n    \"rootDir\": \"./src\",\n    \"outDir\": \"build/esm\",\n    \"lib\": [\"ES2022\", \"DOM\"],\n    \"types\": [\"@mui/internal-code-infra/build-env\"]\n  },\n  \"include\": [\"src/**/*.ts*\", \"src/**/*.tsx\"],\n  \"exclude\": [\"src/**/*.spec.ts*\", \"src/**/*.test.ts*\"]\n}\n"
  },
  {
    "path": "packages/utils/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.base.json\",\n  \"references\": [\n    {\n      \"path\": \"./tsconfig.build.json\"\n    },\n    {\n      \"path\": \"./tsconfig.test.json\"\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/utils/tsconfig.test.json",
    "content": "{\n  \"extends\": \"../../tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"declaration\": true,\n    \"emitDeclarationOnly\": true,\n    \"noEmit\": false,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"bundler\",\n    \"rootDir\": \".\",\n    \"outDir\": \"build-tests\",\n    \"types\": [\"vitest/globals\", \"@testing-library/jest-dom\"]\n  },\n  \"include\": [\"src/**/*.spec.ts*\", \"src/**/*.test.ts*\", \"src/index.test.ts\", \"package.json\"],\n  \"references\": [{ \"path\": \"./tsconfig.build.json\" }]\n}\n"
  },
  {
    "path": "packages/utils/vitest.config.mts",
    "content": "import { mergeConfig, defineProject } from 'vitest/config';\n// eslint-disable-next-line import/no-relative-packages\nimport sharedConfig from '../../vitest.shared.mts';\n\nexport default mergeConfig(\n  sharedConfig,\n  defineProject({\n    define: {\n      'process.env.NODE_ENV': JSON.stringify('test'),\n    },\n  }),\n);\n"
  },
  {
    "path": "playground/vite-app/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "playground/vite-app/README.md",
    "content": "# Base UI Vite playground\n\nA small Vite + React app used for local experimentation and performance benchmarks against the Base UI source in this repo.\n\n## Usage\n\n### Dev mode\n\n```bash\npnpm dev\n```\n\n### Production mode\n\n```bash\npnpm build\npnpm serve\n```\n\n### Profiling build\n\nThis app supports a profiling build that aliases `react-dom/client` to `react-dom/profiling`.\nThis enables React Performance Tracks in Chrome Developer Tools profiler.\n\n```bash\npnpm build:profile\npnpm serve\n```\n\n## React Performance Tracks\n\nReact can annotate the browser Performance panel with scheduling, commit, and component timing tracks in development and profiling builds. To use it:\n\n1. Install the React DevTools browser extension.\n2. Use the profiling build above (or `pnpm -C playground/vite-app dev`) and open the app in a Chromium-based browser.\n3. Open DevTools > Performance, start a recording, then interact with the app.\n4. Look for the React tracks in the timeline.\n\nReference: https://react.dev/reference/dev-tools/react-performance-tracks\n\n## Deployment\n\nThe playground is deployed alongside the main docs.\n`pnpm docs:build` in the repo root builds the playground and copies the output to the Next.js export directory.\nThe playground is then available under `/vite-playground`.\n"
  },
  {
    "path": "playground/vite-app/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/vite.svg\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Base UI Vite Playground</title>\n  </head>\n  <body>\n    <div id=\"root\"></div>\n    <script type=\"module\" src=\"/src/index.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "playground/vite-app/package.json",
    "content": "{\n  \"name\": \"base-ui-playground-vite-app\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc -b && vite build\",\n    \"build:profile\": \"cross-env REACT_PROFILING=1 pnpm run build\",\n    \"serve\": \"vite preview\"\n  },\n  \"dependencies\": {\n    \"radix-ui\": \"^1.4.3\",\n    \"react\": \"19.2.4\",\n    \"react-dom\": \"19.2.4\",\n    \"react-router\": \"7.13.1\"\n  },\n  \"devDependencies\": {\n    \"@eslint/js\": \"10.0.1\",\n    \"@tailwindcss/vite\": \"4.2.1\",\n    \"@types/node\": \"22.18.13\",\n    \"@types/react\": \"19.2.14\",\n    \"@types/react-dom\": \"19.2.3\",\n    \"@vitejs/plugin-react\": \"5.1.4\",\n    \"cross-env\": \"10.1.0\",\n    \"eslint\": \"10.0.3\",\n    \"eslint-plugin-react-hooks\": \"7.0.1\",\n    \"eslint-plugin-react-refresh\": \"0.5.2\",\n    \"globals\": \"17.4.0\",\n    \"tailwindcss\": \"4.2.1\",\n    \"typescript\": \"5.9.3\",\n    \"typescript-eslint\": \"8.57.0\",\n    \"vite\": \"7.3.1\"\n  }\n}\n"
  },
  {
    "path": "playground/vite-app/src/Home.tsx",
    "content": "import { Link } from 'react-router';\nimport { routes } from './routes';\n\nexport function Home() {\n  return (\n    <div className=\"space-y-3\">\n      <Nav />\n    </div>\n  );\n}\n\nfunction Nav() {\n  return (\n    <ul className=\"space-y-2 text-sm\">\n      {routes.map((entry) => {\n        if (entry.type === 'header') {\n          return (\n            <li\n              key={entry.label}\n              className=\"pt-2 text-xs font-semibold uppercase tracking-wide text-gray-500\"\n            >\n              {entry.label}\n            </li>\n          );\n        }\n\n        if (entry.type === 'route' && entry.showInNav) {\n          return (\n            <li key={entry.path}>\n              <Link className=\"text-gray-800 hover:underline\" to={entry.path}>\n                {entry.label}\n              </Link>\n            </li>\n          );\n        }\n\n        return null;\n      })}\n    </ul>\n  );\n}\n"
  },
  {
    "path": "playground/vite-app/src/experiments/perf/SettingsPanel.tsx",
    "content": "import * as React from 'react';\nimport styles from './perf.module.css';\n\nexport interface Settings {\n  renderDialog: boolean;\n  renderMenu: boolean;\n  renderPopover: boolean;\n  renderPreviewCard: boolean;\n  renderTooltip: boolean;\n}\n\nexport const defaultSettings: Settings = {\n  renderDialog: true,\n  renderMenu: true,\n  renderPopover: true,\n  renderPreviewCard: true,\n  renderTooltip: true,\n};\n\nconst settingsStorageKey = 'base-ui:perf-settings';\n\nconst settingRows: Array<{ key: keyof Settings; label: string }> = [\n  { key: 'renderDialog', label: 'Dialog' },\n  { key: 'renderMenu', label: 'Menu' },\n  { key: 'renderPopover', label: 'Popover' },\n  { key: 'renderPreviewCard', label: 'Preview Card' },\n  { key: 'renderTooltip', label: 'Tooltip' },\n];\n\ninterface SettingsPanelProps {\n  settings: Settings;\n  onChange: React.Dispatch<React.SetStateAction<Settings>>;\n}\n\nfunction normalizeSettings(value: unknown): Settings {\n  if (!value || typeof value !== 'object') {\n    return defaultSettings;\n  }\n\n  const record = value as Partial<Settings>;\n  return {\n    renderDialog:\n      typeof record.renderDialog === 'boolean' ? record.renderDialog : defaultSettings.renderDialog,\n    renderMenu:\n      typeof record.renderMenu === 'boolean' ? record.renderMenu : defaultSettings.renderMenu,\n    renderPopover:\n      typeof record.renderPopover === 'boolean'\n        ? record.renderPopover\n        : defaultSettings.renderPopover,\n    renderPreviewCard:\n      typeof record.renderPreviewCard === 'boolean'\n        ? record.renderPreviewCard\n        : defaultSettings.renderPreviewCard,\n    renderTooltip:\n      typeof record.renderTooltip === 'boolean'\n        ? record.renderTooltip\n        : defaultSettings.renderTooltip,\n  };\n}\n\nfunction readStoredSettings(): Settings {\n  if (typeof window === 'undefined') {\n    return defaultSettings;\n  }\n\n  try {\n    const raw = window.localStorage.getItem(settingsStorageKey);\n    return raw ? normalizeSettings(JSON.parse(raw)) : defaultSettings;\n  } catch {\n    return defaultSettings;\n  }\n}\n\nexport function usePersistedSettings(): [Settings, React.Dispatch<React.SetStateAction<Settings>>] {\n  const [settings, setSettings] = React.useState<Settings>(readStoredSettings);\n\n  React.useEffect(() => {\n    try {\n      window.localStorage.setItem(settingsStorageKey, JSON.stringify(settings));\n    } catch {\n      // Ignore write failures (storage might be unavailable).\n    }\n  }, [settings]);\n\n  return [settings, setSettings];\n}\n\nexport function SettingsPanel(props: SettingsPanelProps) {\n  const { settings, onChange } = props;\n\n  return (\n    <fieldset className={styles.settingsPanel}>\n      <legend className={styles.settingsLegend}>Render components</legend>\n      <div className={styles.settingsGrid}>\n        {settingRows.map((row) => (\n          <label key={row.key} className={styles.settingsRow}>\n            <input\n              type=\"checkbox\"\n              className={styles.settingsCheckbox}\n              checked={settings[row.key]}\n              onChange={(event) => {\n                const nextChecked = event.currentTarget.checked;\n                onChange((prev) => ({\n                  ...prev,\n                  [row.key]: nextChecked,\n                }));\n              }}\n            />\n            <span>{row.label}</span>\n          </label>\n        ))}\n      </div>\n    </fieldset>\n  );\n}\n"
  },
  {
    "path": "playground/vite-app/src/experiments/perf/contained-triggers.tsx",
    "content": "import * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\nimport { Tooltip } from '@base-ui/react/tooltip';\nimport { Popover } from '@base-ui/react/popover';\nimport { Dialog } from '@base-ui/react/dialog';\nimport { PreviewCard } from '@base-ui/react/preview-card';\nimport menuDemoStyles from 'docs/src/app/(docs)/react/components/menu/demos/submenu/css-modules/index.module.css';\nimport tooltipDemoStyles from 'docs/src/app/(docs)/react/components/tooltip/demos/hero/css-modules/index.module.css';\nimport popoverDemoStyles from 'docs/src/app/(docs)/react/components/popover/demos/_index.module.css';\nimport dialogDemoStyles from 'docs/src/app/(docs)/react/components/dialog/demos/_index.module.css';\nimport previewCardDemoStyles from 'docs/src/app/(docs)/react/components/preview-card/demos/index.module.css';\nimport PerformanceBenchmark from './utils/benchmark';\nimport { type Settings, SettingsPanel, usePersistedSettings } from './SettingsPanel';\nimport styles from './perf.module.css';\n\ninterface RowData {\n  label: string;\n  index: number;\n}\n\ninterface RowProps {\n  rowData: RowData;\n}\n\nconst rowCount = 500;\nconst menuItemCount = 50;\n\nconst rows = Array.from({ length: rowCount }).map((_, i) => ({\n  label: `Row ${i + 1}`,\n  index: i + 1,\n}));\n\nconst menuItems = Array.from({ length: menuItemCount }).map((_, i) => ({\n  label: `Menu Item ${i + 1}`,\n  index: i + 1,\n}));\n\nexport default function PerfExperiment() {\n  const [settings, setSettings] = usePersistedSettings();\n\n  return (\n    <div className={styles.container}>\n      <h1>Initial render performance - contained triggers</h1>\n      <SettingsPanel settings={settings} onChange={setSettings} />\n      <PerformanceBenchmark>\n        <TestComponent settings={settings} />\n      </PerformanceBenchmark>\n    </div>\n  );\n}\n\nfunction TestComponent(props: { settings: Settings }) {\n  const { renderMenu, renderTooltip, renderPopover, renderDialog, renderPreviewCard } =\n    props.settings;\n  return (\n    <div className={styles.rows}>\n      {rows.map((row) => (\n        <div key={row.index} className={styles.row}>\n          <span className={styles.label}>{row.label}</span>\n          <span className={styles.actions}>\n            {renderDialog && <StyledDialog rowData={row} />}\n            {renderMenu && <StyledMenu rowData={row} />}\n            {renderPopover && <StyledPopover rowData={row} />}\n            {renderPreviewCard && <StyledPreviewCard rowData={row} />}\n            {renderTooltip && <StyledTooltip rowData={row} />}\n          </span>\n        </div>\n      ))}\n    </div>\n  );\n}\n\nfunction StyledMenu({ rowData }: RowProps) {\n  return (\n    <Menu.Root>\n      <Menu.Trigger className={styles.button} data-id={rowData.index}>\n        Menu\n      </Menu.Trigger>\n      <Menu.Portal>\n        <Menu.Positioner sideOffset={8} className={menuDemoStyles.Positioner}>\n          <Menu.Popup className={menuDemoStyles.Popup}>\n            <Menu.Arrow className={menuDemoStyles.Arrow}>\n              <ArrowSvg />\n            </Menu.Arrow>\n            {menuItems.map((item) => (\n              <Menu.Item\n                key={item.index}\n                onClick={() => console.log(`Clicked ${item.label} for ${rowData.label}`)}\n                className={menuDemoStyles.Item}\n              >\n                {item.label} for {rowData.label}\n              </Menu.Item>\n            ))}\n          </Menu.Popup>\n        </Menu.Positioner>\n      </Menu.Portal>\n    </Menu.Root>\n  );\n}\n\nfunction StyledPopover({ rowData }: RowProps) {\n  return (\n    <Popover.Root>\n      <Popover.Trigger className={styles.button} data-id={rowData.index}>\n        Popover\n      </Popover.Trigger>\n      <Popover.Portal>\n        <Popover.Positioner sideOffset={8} className={popoverDemoStyles.Positioner}>\n          <Popover.Popup className={popoverDemoStyles.Popup}>\n            <Popover.Arrow className={popoverDemoStyles.Arrow}>\n              <ArrowSvg />\n            </Popover.Arrow>\n            {rowData && <div>Popover for {rowData.label}</div>}\n          </Popover.Popup>\n        </Popover.Positioner>\n      </Popover.Portal>\n    </Popover.Root>\n  );\n}\n\nfunction StyledTooltip({ rowData }: RowProps) {\n  return (\n    <Tooltip.Root>\n      <Tooltip.Trigger className={styles.button} data-id={rowData.index}>\n        Tooltip\n      </Tooltip.Trigger>\n      <Tooltip.Portal>\n        <Tooltip.Positioner sideOffset={10}>\n          <Tooltip.Popup className={tooltipDemoStyles.Popup}>\n            <Tooltip.Arrow className={tooltipDemoStyles.Arrow}>\n              <ArrowSvg />\n            </Tooltip.Arrow>\n            Tooltip for {rowData.label}\n          </Tooltip.Popup>\n        </Tooltip.Positioner>\n      </Tooltip.Portal>\n    </Tooltip.Root>\n  );\n}\n\nfunction StyledDialog({ rowData }: RowProps) {\n  return (\n    <Dialog.Root>\n      <Dialog.Trigger className={styles.button} data-id={rowData.index}>\n        Dialog\n      </Dialog.Trigger>\n      <Dialog.Portal>\n        <Dialog.Backdrop className={dialogDemoStyles.Backdrop} />\n        <Dialog.Popup className={dialogDemoStyles.Popup}>\n          <Dialog.Title className={dialogDemoStyles.Title}>Dialog</Dialog.Title>\n          <Dialog.Description className={dialogDemoStyles.Description}>\n            Dialog content for {rowData.label}\n          </Dialog.Description>\n          <div className={dialogDemoStyles.Actions}>\n            <Dialog.Close className={dialogDemoStyles.Button}>Close</Dialog.Close>\n          </div>\n        </Dialog.Popup>\n      </Dialog.Portal>\n    </Dialog.Root>\n  );\n}\n\nfunction StyledPreviewCard({ rowData }: RowProps) {\n  return (\n    <PreviewCard.Root>\n      <PreviewCard.Trigger\n        className={styles.button}\n        href=\"#\"\n        data-id={rowData.index}\n        onClick={(event) => event.preventDefault()}\n      >\n        Preview Card\n      </PreviewCard.Trigger>\n      <PreviewCard.Portal>\n        <PreviewCard.Positioner sideOffset={8} className={previewCardDemoStyles.Positioner}>\n          <PreviewCard.Popup className={previewCardDemoStyles.Popup}>\n            <PreviewCard.Arrow className={previewCardDemoStyles.Arrow}>\n              <PreviewCardArrowSvg />\n            </PreviewCard.Arrow>\n            <div className={previewCardDemoStyles.PopupContent} style={{ width: 'max-content' }}>\n              <p className={previewCardDemoStyles.Summary}>Preview for {rowData.label}</p>\n            </div>\n          </PreviewCard.Popup>\n        </PreviewCard.Positioner>\n      </PreviewCard.Portal>\n    </PreviewCard.Root>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={menuDemoStyles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={menuDemoStyles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={menuDemoStyles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nfunction PreviewCardArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={previewCardDemoStyles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={previewCardDemoStyles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={previewCardDemoStyles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "playground/vite-app/src/experiments/perf/detached-triggers.tsx",
    "content": "import * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\nimport { Tooltip } from '@base-ui/react/tooltip';\nimport { Popover } from '@base-ui/react/popover';\nimport { Dialog } from '@base-ui/react/dialog';\nimport { PreviewCard } from '@base-ui/react/preview-card';\nimport menuDemoStyles from 'docs/src/app/(docs)/react/components/menu/demos/submenu/css-modules/index.module.css';\nimport tooltipDemoStyles from 'docs/src/app/(docs)/react/components/tooltip/demos/hero/css-modules/index.module.css';\nimport popoverDemoStyles from 'docs/src/app/(docs)/react/components/popover/demos/_index.module.css';\nimport dialogDemoStyles from 'docs/src/app/(docs)/react/components/dialog/demos/_index.module.css';\nimport previewCardDemoStyles from 'docs/src/app/(docs)/react/components/preview-card/demos/index.module.css';\nimport PerformanceBenchmark from './utils/benchmark';\nimport { type Settings, SettingsPanel, usePersistedSettings } from './SettingsPanel';\nimport styles from './perf.module.css';\n\ninterface RowData {\n  label: string;\n  index: number;\n}\n\nconst rowCount = 500;\nconst menuItemCount = 50;\n\nconst rows = Array.from({ length: rowCount }).map((_, i) => ({\n  label: `Row ${i + 1}`,\n  index: i + 1,\n}));\n\nconst menuItems = Array.from({ length: menuItemCount }).map((_, i) => ({\n  label: `Menu Item ${i + 1}`,\n  index: i + 1,\n}));\n\nconst rowMenuHandle = Menu.createHandle<RowData>();\nconst genericTooltipHandle = Tooltip.createHandle<RowData>();\nconst rowPopoverHandle = Popover.createHandle<RowData>();\nconst rowDialogHandle = Dialog.createHandle<RowData>();\nconst rowPreviewCardHandle = PreviewCard.createHandle<RowData>();\n\nexport default function PerfExperiment() {\n  const [settings, setSettings] = usePersistedSettings();\n\n  return (\n    <div className={styles.container}>\n      <h1>Initial render performance - detached triggers</h1>\n      <SettingsPanel settings={settings} onChange={setSettings} />\n      <PerformanceBenchmark>\n        <TestComponent settings={settings} />\n      </PerformanceBenchmark>\n    </div>\n  );\n}\n\nfunction TestComponent(props: { settings: Settings }) {\n  const { renderMenu, renderTooltip, renderPopover, renderDialog, renderPreviewCard } =\n    props.settings;\n  return (\n    <div>\n      <div className={styles.rows}>\n        {rows.map((row) => (\n          <div key={row.index} className={styles.row}>\n            <span className={styles.label}>{row.label}</span>\n            <span className={styles.actions}>\n              {renderDialog && (\n                <Dialog.Trigger\n                  handle={rowDialogHandle}\n                  payload={row}\n                  className={styles.button}\n                  data-id={row.index}\n                >\n                  Dialog\n                </Dialog.Trigger>\n              )}\n              {renderMenu && (\n                <Menu.Trigger\n                  handle={rowMenuHandle}\n                  payload={row}\n                  className={styles.button}\n                  data-id={row.index}\n                >\n                  Menu\n                </Menu.Trigger>\n              )}\n              {renderPopover && (\n                <Popover.Trigger\n                  handle={rowPopoverHandle}\n                  payload={row}\n                  className={styles.button}\n                  data-id={row.index}\n                >\n                  Popover\n                </Popover.Trigger>\n              )}\n\n              {renderPreviewCard && (\n                <PreviewCard.Trigger\n                  handle={rowPreviewCardHandle}\n                  payload={row}\n                  className={styles.button}\n                  href=\"#\"\n                  data-id={row.index}\n                  onClick={(event) => event.preventDefault()}\n                >\n                  Preview Card\n                </PreviewCard.Trigger>\n              )}\n              {renderTooltip && (\n                <Tooltip.Trigger\n                  handle={genericTooltipHandle}\n                  payload={row}\n                  className={styles.button}\n                  data-id={row.index}\n                >\n                  Tooltip\n                </Tooltip.Trigger>\n              )}\n            </span>\n          </div>\n        ))}\n      </div>\n      {renderDialog && <StyledDialog />}\n      {renderMenu && <StyledMenu />}\n      {renderPopover && <StyledPopover />}\n      {renderPreviewCard && <StyledPreviewCard />}\n      {renderTooltip && <StyledTooltip />}\n    </div>\n  );\n}\n\nfunction StyledMenu() {\n  return (\n    <Menu.Root handle={rowMenuHandle}>\n      {({ payload: rowData }) => (\n        <Menu.Portal>\n          <Menu.Positioner sideOffset={8} className={menuDemoStyles.Positioner}>\n            <Menu.Popup className={menuDemoStyles.Popup}>\n              <Menu.Arrow className={menuDemoStyles.Arrow}>\n                <ArrowSvg />\n              </Menu.Arrow>\n              {rowData && (\n                <React.Fragment>\n                  {menuItems.map((item) => (\n                    <Menu.Item\n                      key={item.index}\n                      onClick={() => console.log(`Clicked ${item.label} for ${rowData.label}`)}\n                      className={menuDemoStyles.Item}\n                    >\n                      {item.label} for {rowData.label}\n                    </Menu.Item>\n                  ))}\n                </React.Fragment>\n              )}\n            </Menu.Popup>\n          </Menu.Positioner>\n        </Menu.Portal>\n      )}\n    </Menu.Root>\n  );\n}\n\nfunction StyledPopover() {\n  return (\n    <Popover.Root handle={rowPopoverHandle}>\n      {({ payload: rowData }) => (\n        <Popover.Portal>\n          <Popover.Positioner sideOffset={8} className={popoverDemoStyles.Positioner}>\n            <Popover.Popup className={popoverDemoStyles.Popup}>\n              <Popover.Arrow className={popoverDemoStyles.Arrow}>\n                <ArrowSvg />\n              </Popover.Arrow>\n              {rowData && <div>Popover for {rowData.label}</div>}\n            </Popover.Popup>\n          </Popover.Positioner>\n        </Popover.Portal>\n      )}\n    </Popover.Root>\n  );\n}\n\nfunction StyledTooltip() {\n  return (\n    <Tooltip.Root handle={genericTooltipHandle}>\n      {({ payload: rowData }) =>\n        rowData ? (\n          <Tooltip.Portal>\n            <Tooltip.Positioner sideOffset={10}>\n              <Tooltip.Popup className={tooltipDemoStyles.Popup}>\n                <Tooltip.Arrow className={tooltipDemoStyles.Arrow}>\n                  <ArrowSvg />\n                </Tooltip.Arrow>\n                Tooltip for {rowData.label}\n              </Tooltip.Popup>\n            </Tooltip.Positioner>\n          </Tooltip.Portal>\n        ) : null\n      }\n    </Tooltip.Root>\n  );\n}\n\nfunction StyledDialog() {\n  return (\n    <Dialog.Root handle={rowDialogHandle}>\n      {({ payload: rowData }) =>\n        rowData ? (\n          <Dialog.Portal>\n            <Dialog.Backdrop className={dialogDemoStyles.Backdrop} />\n            <Dialog.Popup className={dialogDemoStyles.Popup}>\n              <Dialog.Title className={dialogDemoStyles.Title}>Dialog</Dialog.Title>\n              <Dialog.Description className={dialogDemoStyles.Description}>\n                Dialog content for {rowData.label}\n              </Dialog.Description>\n              <div className={dialogDemoStyles.Actions}>\n                <Dialog.Close className={dialogDemoStyles.Button}>Close</Dialog.Close>\n              </div>\n            </Dialog.Popup>\n          </Dialog.Portal>\n        ) : null\n      }\n    </Dialog.Root>\n  );\n}\n\nfunction StyledPreviewCard() {\n  return (\n    <PreviewCard.Root handle={rowPreviewCardHandle}>\n      {({ payload: rowData }) =>\n        rowData ? (\n          <PreviewCard.Portal>\n            <PreviewCard.Positioner sideOffset={8} className={previewCardDemoStyles.Positioner}>\n              <PreviewCard.Popup className={previewCardDemoStyles.Popup}>\n                <PreviewCard.Arrow className={previewCardDemoStyles.Arrow}>\n                  <PreviewCardArrowSvg />\n                </PreviewCard.Arrow>\n                <div\n                  className={previewCardDemoStyles.PopupContent}\n                  style={{ width: 'max-content' }}\n                >\n                  <p className={previewCardDemoStyles.Summary}>Preview for {rowData.label}</p>\n                </div>\n              </PreviewCard.Popup>\n            </PreviewCard.Positioner>\n          </PreviewCard.Portal>\n        ) : null\n      }\n    </PreviewCard.Root>\n  );\n}\n\nfunction ArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={menuDemoStyles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={menuDemoStyles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={menuDemoStyles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n\nfunction PreviewCardArrowSvg(props: React.ComponentProps<'svg'>) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={previewCardDemoStyles.ArrowFill}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={previewCardDemoStyles.ArrowOuterStroke}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={previewCardDemoStyles.ArrowInnerStroke}\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "playground/vite-app/src/experiments/perf/perf.module.css",
    "content": ".container {\n  h1 {\n    margin-bottom: 8px;\n    font-size: 1.5rem;\n    font-weight: 600;\n    color: var(--color-gray-900);\n  }\n}\n\n.settingsPanel {\n  margin-block: 12px;\n  padding: 12px 16px 16px;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.5rem;\n  background-color: var(--color-gray-50);\n}\n\n.settingsLegend {\n  padding: 0 6px;\n  font-weight: 600;\n  color: var(--color-gray-900);\n}\n\n.settingsGrid {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 10px 16px;\n  margin-top: 8px;\n}\n\n.settingsRow {\n  display: inline-flex;\n  align-items: center;\n  gap: 8px;\n  color: var(--color-gray-900);\n}\n\n.rows {\n  display: flex;\n  flex-direction: column;\n  margin-top: 16px;\n}\n\n.row {\n  padding: 8px 0;\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  border-bottom: 1px solid var(--color-gray-200);\n}\n\n.actions {\n  display: flex;\n  gap: 8px;\n}\n\n.button {\n  box-sizing: border-box;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 1.75rem;\n  padding: 0 0.375rem;\n  margin: 0;\n  outline: 0;\n  border: 1px solid var(--color-gray-200);\n  border-radius: 0.375rem;\n  background-color: var(--color-gray-50);\n  font-family: inherit;\n  font-size: 1rem;\n  font-weight: 400;\n  line-height: 1.5rem;\n  color: var(--color-gray-900);\n  user-select: none;\n\n  @media (hover: hover) {\n    &:hover {\n      background-color: var(--color-gray-100);\n    }\n  }\n\n  &:active {\n    background-color: var(--color-gray-100);\n  }\n\n  &[data-popup-open] {\n    background-color: var(--color-gray-100);\n  }\n\n  &:focus-visible {\n    outline: 2px solid var(--color-blue);\n    outline-offset: -1px;\n  }\n}\n\n.radixArrow {\n  transform: rotate(180deg);\n  transform-origin: center;\n}\n"
  },
  {
    "path": "playground/vite-app/src/experiments/perf/radix-triggers.tsx",
    "content": "import * as React from 'react';\nimport { DropdownMenu, Tooltip, Popover, Dialog, HoverCard } from 'radix-ui';\nimport menuDemoStyles from 'docs/src/app/(docs)/react/components/menu/demos/submenu/css-modules/index.module.css';\nimport tooltipDemoStyles from 'docs/src/app/(docs)/react/components/tooltip/demos/hero/css-modules/index.module.css';\nimport popoverDemoStyles from 'docs/src/app/(docs)/react/components/popover/demos/_index.module.css';\nimport dialogDemoStyles from 'docs/src/app/(docs)/react/components/dialog/demos/_index.module.css';\nimport previewCardDemoStyles from 'docs/src/app/(docs)/react/components/preview-card/demos/index.module.css';\nimport PerformanceBenchmark from './utils/benchmark';\nimport { type Settings, SettingsPanel, usePersistedSettings } from './SettingsPanel';\nimport styles from './perf.module.css';\n\ninterface RowData {\n  label: string;\n  index: number;\n}\n\ninterface RowProps {\n  rowData: RowData;\n}\n\nconst rowCount = 500;\nconst menuItemCount = 50;\n\nconst rows = Array.from({ length: rowCount }).map((_, i) => ({\n  label: `Row ${i + 1}`,\n  index: i + 1,\n}));\n\nconst menuItems = Array.from({ length: menuItemCount }).map((_, i) => ({\n  label: `Menu Item ${i + 1}`,\n  index: i + 1,\n}));\n\nexport default function PerfExperiment() {\n  const [settings, setSettings] = usePersistedSettings();\n\n  return (\n    <div className={styles.container}>\n      <h1>Initial render performance - Radix</h1>\n      <SettingsPanel settings={settings} onChange={setSettings} />\n      <PerformanceBenchmark>\n        <Tooltip.Provider>\n          <TestComponent settings={settings} />\n        </Tooltip.Provider>\n      </PerformanceBenchmark>\n    </div>\n  );\n}\n\nfunction TestComponent(props: { settings: Settings }) {\n  const { renderMenu, renderTooltip, renderPopover, renderDialog, renderPreviewCard } =\n    props.settings;\n  return (\n    <div className={styles.rows}>\n      {rows.map((row) => (\n        <div key={row.index} className={styles.row}>\n          <span className={styles.label}>{row.label}</span>\n          <span className={styles.actions}>\n            {renderDialog && <StyledDialog rowData={row} />}\n            {renderMenu && <StyledMenu rowData={row} />}\n            {renderPopover && <StyledPopover rowData={row} />}\n            {renderPreviewCard && <StyledHoverCard rowData={row} />}\n            {renderTooltip && <StyledTooltip rowData={row} />}\n          </span>\n        </div>\n      ))}\n    </div>\n  );\n}\n\nfunction StyledMenu({ rowData }: RowProps) {\n  return (\n    <DropdownMenu.Root>\n      <DropdownMenu.Trigger className={styles.button} data-id={rowData.index}>\n        Menu\n      </DropdownMenu.Trigger>\n      <DropdownMenu.Portal>\n        <DropdownMenu.Content sideOffset={8} className={menuDemoStyles.Popup}>\n          <DropdownMenu.Arrow asChild className={styles.radixArrow}>\n            <ArrowSvg\n              fillClassName={menuDemoStyles.ArrowFill}\n              outerStrokeClassName={menuDemoStyles.ArrowOuterStroke}\n              innerStrokeClassName={menuDemoStyles.ArrowInnerStroke}\n            />\n          </DropdownMenu.Arrow>\n          {menuItems.map((item) => (\n            <DropdownMenu.Item\n              key={item.index}\n              onSelect={() => console.log(`Clicked ${item.label} for ${rowData.label}`)}\n              className={menuDemoStyles.Item}\n            >\n              {item.label} for {rowData.label}\n            </DropdownMenu.Item>\n          ))}\n        </DropdownMenu.Content>\n      </DropdownMenu.Portal>\n    </DropdownMenu.Root>\n  );\n}\n\nfunction StyledPopover({ rowData }: RowProps) {\n  return (\n    <Popover.Root>\n      <Popover.Trigger className={styles.button} data-id={rowData.index}>\n        Popover\n      </Popover.Trigger>\n      <Popover.Portal>\n        <Popover.Content sideOffset={8} className={popoverDemoStyles.Popup}>\n          <Popover.Arrow asChild className={styles.radixArrow}>\n            <ArrowSvg\n              fillClassName={popoverDemoStyles.ArrowFill}\n              outerStrokeClassName={popoverDemoStyles.ArrowOuterStroke}\n              innerStrokeClassName={popoverDemoStyles.ArrowInnerStroke}\n            />\n          </Popover.Arrow>\n          {rowData && <div>Details for {rowData.label}</div>}\n        </Popover.Content>\n      </Popover.Portal>\n    </Popover.Root>\n  );\n}\n\nfunction StyledTooltip({ rowData }: RowProps) {\n  return (\n    <Tooltip.Root>\n      <Tooltip.Trigger className={styles.button} data-id={rowData.index}>\n        Tooltip\n      </Tooltip.Trigger>\n      <Tooltip.Portal>\n        <Tooltip.Content sideOffset={10} className={tooltipDemoStyles.Popup}>\n          <Tooltip.Arrow asChild className={styles.radixArrow}>\n            <ArrowSvg\n              fillClassName={tooltipDemoStyles.ArrowFill}\n              outerStrokeClassName={tooltipDemoStyles.ArrowOuterStroke}\n              innerStrokeClassName={tooltipDemoStyles.ArrowInnerStroke}\n            />\n          </Tooltip.Arrow>\n          Tooltip for {rowData.label}\n        </Tooltip.Content>\n      </Tooltip.Portal>\n    </Tooltip.Root>\n  );\n}\n\nfunction StyledDialog({ rowData }: RowProps) {\n  return (\n    <Dialog.Root>\n      <Dialog.Trigger className={styles.button} data-id={rowData.index}>\n        Dialog\n      </Dialog.Trigger>\n      <Dialog.Portal>\n        <Dialog.Overlay className={dialogDemoStyles.Backdrop} />\n        <Dialog.Content className={dialogDemoStyles.Popup}>\n          <Dialog.Title className={dialogDemoStyles.Title}>Dialog</Dialog.Title>\n          <Dialog.Description className={dialogDemoStyles.Description}>\n            Dialog content for {rowData.label}\n          </Dialog.Description>\n          <div className={dialogDemoStyles.Actions}>\n            <Dialog.Close className={dialogDemoStyles.Button}>Close</Dialog.Close>\n          </div>\n        </Dialog.Content>\n      </Dialog.Portal>\n    </Dialog.Root>\n  );\n}\n\nfunction StyledHoverCard({ rowData }: RowProps) {\n  return (\n    <HoverCard.Root>\n      <HoverCard.Trigger\n        className={styles.button}\n        href=\"#\"\n        data-id={rowData.index}\n        onClick={(event) => event.preventDefault()}\n      >\n        Preview Card\n      </HoverCard.Trigger>\n      <HoverCard.Portal>\n        <HoverCard.Content sideOffset={8} className={previewCardDemoStyles.Popup}>\n          <HoverCard.Arrow asChild className={styles.radixArrow}>\n            <ArrowSvg\n              fillClassName={previewCardDemoStyles.ArrowFill}\n              outerStrokeClassName={previewCardDemoStyles.ArrowOuterStroke}\n              innerStrokeClassName={previewCardDemoStyles.ArrowInnerStroke}\n            />\n          </HoverCard.Arrow>\n          <div className={previewCardDemoStyles.PopupContent} style={{ width: 'max-content' }}>\n            <p className={previewCardDemoStyles.Summary}>Preview for {rowData.label}</p>\n          </div>\n        </HoverCard.Content>\n      </HoverCard.Portal>\n    </HoverCard.Root>\n  );\n}\n\ninterface ArrowSvgProps extends React.ComponentProps<'svg'> {\n  fillClassName: string;\n  outerStrokeClassName: string;\n  innerStrokeClassName: string;\n}\n\nfunction ArrowSvg({\n  fillClassName,\n  outerStrokeClassName,\n  innerStrokeClassName,\n  ...props\n}: ArrowSvgProps) {\n  return (\n    <svg width=\"20\" height=\"10\" viewBox=\"0 0 20 10\" fill=\"none\" {...props}>\n      <path\n        d=\"M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z\"\n        className={fillClassName}\n      />\n      <path\n        d=\"M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z\"\n        className={outerStrokeClassName}\n      />\n      <path\n        d=\"M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z\"\n        className={innerStrokeClassName}\n      />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "playground/vite-app/src/experiments/perf/utils/benchmark.tsx",
    "content": "import * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport { useStableCallback } from '@base-ui/utils/useStableCallback';\nimport { useTimeout } from '@base-ui/utils/useTimeout';\nimport styles from '../perf.module.css';\n\nconst DOM_SETTLE_QUIET_WINDOW_MS = 32;\n\nconst Controls = React.memo(function Controls(props: {\n  setShowBenchmark: React.Dispatch<React.SetStateAction<boolean>>;\n  benchmarkRootRef: React.RefObject<HTMLDivElement | null>;\n}) {\n  const { setShowBenchmark, benchmarkRootRef } = props;\n  const settleTimeout = useTimeout();\n  const [isRunning, setIsRunning] = React.useState(false);\n  const [shouldRemoveOutliers, setShouldRemoveOutliers] = React.useState(true);\n\n  const measureDomSettled = useStableCallback(() => {\n    const start = performance.now();\n    let lastMutationAt = start;\n    let observer: MutationObserver | null = null;\n    let resolved = false;\n\n    return new Promise<number>((resolve) => {\n      const finish = () => {\n        if (resolved) {\n          return;\n        }\n        resolved = true;\n        observer?.disconnect();\n        settleTimeout.clear();\n        resolve(Math.max(0, lastMutationAt - start));\n      };\n\n      const root = benchmarkRootRef.current;\n\n      if (root) {\n        observer = new MutationObserver(() => {\n          lastMutationAt = performance.now();\n          settleTimeout.start(DOM_SETTLE_QUIET_WINDOW_MS, finish);\n        });\n\n        observer.observe(root, {\n          attributes: true,\n          childList: true,\n          characterData: true,\n          subtree: true,\n        });\n\n        // Resolve once the DOM stays quiet for a small window after the last mutation.\n        settleTimeout.start(DOM_SETTLE_QUIET_WINDOW_MS, finish);\n      } else {\n        finish();\n      }\n\n      ReactDOM.flushSync(() => {\n        setShowBenchmark(true);\n      });\n    });\n  });\n\n  const runBenchmark = useStableCallback(async (iterations: number, warmupIterations: number) => {\n    if (isRunning) {\n      console.warn('Benchmark is already running.');\n      return;\n    }\n\n    setIsRunning(true);\n    console.log(`Running benchmark: ${iterations} iterations (+${warmupIterations} warmup)...`);\n    try {\n      const results = [] as number[];\n\n      for (let i = 0; i < warmupIterations + iterations; i += 1) {\n        ReactDOM.flushSync(() => {\n          setShowBenchmark(false);\n        });\n\n        // eslint-disable-next-line no-await-in-loop\n        const domSettleDuration = await measureDomSettled();\n\n        if (i < warmupIterations) {\n          continue;\n        }\n\n        results.push(Math.round(domSettleDuration * 10) / 10);\n      }\n\n      logResults(shouldRemoveOutliers ? removeOutliers(results) : results);\n    } finally {\n      setIsRunning(false);\n    }\n  });\n\n  return (\n    <div className=\"flex gap-2 items-baseline\">\n      <button\n        type=\"button\"\n        onClick={() => setShowBenchmark((prev: boolean) => !prev)}\n        className={styles.button}\n        disabled={isRunning}\n      >\n        Toggle\n      </button>\n      <button\n        type=\"button\"\n        onClick={() => runBenchmark(10, 5)}\n        className={styles.button}\n        disabled={isRunning}\n      >\n        Run 10\n      </button>\n      <button\n        type=\"button\"\n        onClick={() => runBenchmark(20, 5)}\n        className={styles.button}\n        disabled={isRunning}\n      >\n        Run 20\n      </button>\n      <button\n        type=\"button\"\n        onClick={() => runBenchmark(50, 5)}\n        className={styles.button}\n        disabled={isRunning}\n      >\n        Run 50\n      </button>\n      <label style={{ marginLeft: 8 }}>\n        <input\n          type=\"checkbox\"\n          style={{ marginRight: 4 }}\n          checked={shouldRemoveOutliers}\n          onChange={(ev) => setShouldRemoveOutliers(ev.target.checked)}\n        />\n        Remove outliers\n      </label>\n    </div>\n  );\n});\n\nexport default function PerformanceBenchmark(props: React.PropsWithChildren<{}>) {\n  const [showBenchmark, setShowBenchmark] = React.useState(false);\n  const benchmarkRootRef = React.useRef<HTMLDivElement>(null);\n\n  return (\n    <div>\n      <Controls setShowBenchmark={setShowBenchmark} benchmarkRootRef={benchmarkRootRef} />\n      <div ref={benchmarkRootRef}>{showBenchmark && props.children}</div>\n    </div>\n  );\n}\n\nfunction logResults(results: number[]) {\n  console.log(results);\n  console.log(\n    'Average:',\n    Math.round((results.reduce((a, b) => a + b, 0) / results.length) * 10) / 10,\n  );\n  console.log(\n    'Std Dev:',\n    (() => {\n      const avg = results.reduce((a, b) => a + b, 0) / results.length;\n      const squareDiffs = results.map((value) => {\n        const diff = value - avg;\n        return diff * diff;\n      });\n      const avgSquareDiff = squareDiffs.reduce((a, b) => a + b, 0) / squareDiffs.length;\n      return +Math.sqrt(avgSquareDiff).toFixed(2);\n    })(),\n  );\n}\n\nfunction removeOutliers(data: number[]) {\n  const sortedData = data.slice().sort((a, b) => a - b);\n  const q1Index = Math.floor(sortedData.length / 4);\n  const q3Index = Math.floor((sortedData.length * 3) / 4);\n  const q1 = sortedData[q1Index];\n  const q3 = sortedData[q3Index];\n  const iqr = q3 - q1;\n  const lowerBound = q1 - 1.5 * iqr;\n  const upperBound = q3 + 1.5 * iqr;\n\n  return data.filter((value) => value >= lowerBound && value <= upperBound);\n}\n"
  },
  {
    "path": "playground/vite-app/src/index.css",
    "content": "@import 'tailwindcss';\n\n:root {\n  --color-blue: oklch(45% 50% 264deg);\n  --color-red: oklch(50% 55% 31deg);\n\n  --color-gray-50: oklch(98.42% 0.0034 247.86deg);\n  --color-gray-100: oklch(12% 9.5% 264deg / 5%);\n  --color-gray-200: oklch(12% 9% 264deg / 8%);\n  --color-gray-300: oklch(12% 8.5% 264deg / 17%);\n  --color-gray-400: oklch(12% 8% 264deg / 38%);\n  --color-gray-500: oklch(12% 7.5% 264deg / 50%);\n  --color-gray-600: oklch(12% 7% 264deg / 67%);\n  --color-gray-700: oklch(12% 6% 264deg / 77%);\n  --color-gray-800: oklch(12% 5% 264deg / 85%);\n  --color-gray-900: oklch(12% 5% 264deg / 90%);\n  --color-gray-950: oklch(12% 5% 264deg / 95%);\n\n  @media (prefers-color-scheme: dark) {\n    --color-blue: oklch(69% 50% 264deg);\n    --color-red: oklch(80% 55% 31deg);\n\n    --color-gray-50: oklch(17% 1% 264deg);\n    --color-gray-100: oklch(28% 3% 264deg / 65%);\n    --color-gray-200: oklch(31% 3% 264deg / 80%);\n    --color-gray-300: oklch(35% 3% 264deg / 80%);\n    --color-gray-400: oklch(47% 3.5% 264deg / 80%);\n    --color-gray-500: oklch(64% 4% 264deg / 80%);\n    --color-gray-600: oklch(82% 4% 264deg / 80%);\n    --color-gray-700: oklch(92% 4.5% 264deg / 80%);\n    --color-gray-800: oklch(93% 3.5% 264deg / 85%);\n    --color-gray-900: oklch(95% 2% 264deg / 90%);\n    --color-gray-950: oklch(94% 1.5% 264deg / 95%);\n  }\n}\n\nhtml {\n  margin: 0;\n  color-scheme: light dark;\n}\n\nbody {\n  margin: 0;\n  min-height: 100vh;\n  background-color: var(--color-gray-50);\n  color: var(--color-gray-900);\n  font-family:\n    -apple-system, BlinkMacSystemFont, 'Segoe UI Variable Display', 'Helvetica Neue', Arial,\n    sans-serif;\n}\n"
  },
  {
    "path": "playground/vite-app/src/index.tsx",
    "content": "import * as React from 'react';\nimport * as ReactDOM from 'react-dom/client';\nimport { BrowserRouter, Link, Navigate, Route, Routes } from 'react-router';\nimport { Home } from './Home';\nimport { routes } from './routes';\nimport './index.css';\n\nconst baseUrl = import.meta.env.BASE_URL;\nconst routerBase = baseUrl === '/' ? '' : baseUrl.replace(/\\/$/, '');\n\nReactDOM.createRoot(document.getElementById('root')!).render(\n  <React.StrictMode>\n    <BrowserRouter basename={routerBase}>\n      <App />\n    </BrowserRouter>\n  </React.StrictMode>,\n);\n\nexport function App() {\n  return (\n    <div className=\"min-h-screen bg-gray-50 text-gray-900\">\n      <header className=\"border-b border-gray-200\">\n        <div className=\"mx-auto flex max-w-5xl flex-wrap items-center justify-between gap-4 px-6 py-4\">\n          <Link className=\"flex items-center gap-3 text-lg font-semibold\" to=\"/\">\n            <img\n              src={`${baseUrl}base-ui-logo.svg`}\n              alt=\"Base UI logo\"\n              className=\"h-6 w-6 relative -top-0.5\"\n            />\n            Base UI playground\n          </Link>\n        </div>\n      </header>\n      <main className=\"mx-auto max-w-5xl px-6 py-8\">\n        <Routes>\n          <Route path=\"/\" element={<Home />} />\n          {routes.map((entry) => {\n            if (entry.type === 'route') {\n              return <Route key={entry.path} path={entry.path} element={entry.element} />;\n            }\n\n            if (entry.type === 'redirect') {\n              return (\n                <Route\n                  key={entry.path}\n                  path={entry.path}\n                  element={<Navigate replace to={entry.to} />}\n                />\n              );\n            }\n\n            return null;\n          })}\n          <Route path=\"*\" element={<NotFound />} />\n        </Routes>\n      </main>\n    </div>\n  );\n}\n\nfunction NotFound() {\n  return (\n    <div className=\"space-y-3\">\n      <h1 className=\"text-2xl font-semibold\">Not found</h1>\n      <p className=\"text-sm text-gray-700\">\n        This page doesn&apos;t exist.{' '}\n        <Link to=\"/\" className=\"hover:underline\">\n          Go home\n        </Link>\n        .\n      </p>\n    </div>\n  );\n}\n"
  },
  {
    "path": "playground/vite-app/src/routes.tsx",
    "content": "import type * as React from 'react';\nimport ContainedTriggers from './experiments/perf/contained-triggers';\nimport DetachedTriggers from './experiments/perf/detached-triggers';\nimport RadixTriggers from './experiments/perf/radix-triggers';\n\nexport type RouteEntry =\n  | {\n      type: 'route';\n      path: string;\n      label: string;\n      element: React.ReactElement;\n      showInNav?: boolean;\n    }\n  | {\n      type: 'redirect';\n      path: string;\n      to: string;\n    }\n  | {\n      type: 'header';\n      label: string;\n    };\n\nexport const defaultRoute = '/perf/contained-triggers';\n\nexport const routes: RouteEntry[] = [\n  { type: 'header', label: 'Performance benchmarks' },\n  { type: 'redirect', path: '/perf', to: defaultRoute },\n  {\n    type: 'route',\n    path: '/perf/contained-triggers',\n    label: 'Base UI contained triggers',\n    element: <ContainedTriggers />,\n    showInNav: true,\n  },\n  {\n    type: 'route',\n    path: '/perf/detached-triggers',\n    label: 'Base UI detached triggers',\n    element: <DetachedTriggers />,\n    showInNav: true,\n  },\n  {\n    type: 'route',\n    path: '/perf/radix-triggers',\n    label: 'Radix triggers',\n    element: <RadixTriggers />,\n    showInNav: true,\n  },\n];\n"
  },
  {
    "path": "playground/vite-app/tsconfig.app.json",
    "content": "{\n  \"compilerOptions\": {\n    \"tsBuildInfoFile\": \"./node_modules/.tmp/tsconfig.app.tsbuildinfo\",\n    \"composite\": true,\n    \"target\": \"ES2022\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2022\", \"DOM\", \"DOM.Iterable\"],\n    \"module\": \"ESNext\",\n    \"types\": [\"vite/client\", \"node\"],\n    \"skipLibCheck\": true,\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"@base-ui/react\": [\"../../packages/react/src/index.ts\"],\n      \"@base-ui/react/*\": [\"../../packages/react/src/*\"],\n      \"@base-ui/utils\": [\"../../packages/utils/src/index.ts\"],\n      \"@base-ui/utils/*\": [\"../../packages/utils/src/*\"],\n      \"docs/*\": [\"../../docs/*\"]\n    },\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"moduleDetection\": \"force\",\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\",\n\n    /* Linting */\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"noUncheckedSideEffectImports\": true\n  },\n  \"references\": [\n    { \"path\": \"../../packages/utils/tsconfig.build.json\" },\n    { \"path\": \"../../packages/react/tsconfig.build.json\" }\n  ],\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "playground/vite-app/tsconfig.json",
    "content": "{\n  \"files\": [],\n  \"references\": [{ \"path\": \"./tsconfig.app.json\" }, { \"path\": \"./tsconfig.node.json\" }]\n}\n"
  },
  {
    "path": "playground/vite-app/tsconfig.node.json",
    "content": "{\n  \"compilerOptions\": {\n    \"tsBuildInfoFile\": \"./node_modules/.tmp/tsconfig.node.tsbuildinfo\",\n    \"target\": \"ES2023\",\n    \"lib\": [\"ES2023\"],\n    \"module\": \"ESNext\",\n    \"types\": [\"node\"],\n    \"skipLibCheck\": true,\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"verbatimModuleSyntax\": true,\n    \"moduleDetection\": \"force\",\n    \"noEmit\": true,\n\n    /* Linting */\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"erasableSyntaxOnly\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"noUncheckedSideEffectImports\": true\n  },\n  \"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "playground/vite-app/vite.config.ts",
    "content": "import path from 'node:path';\nimport { defineConfig } from 'vite';\nimport tailwindcss from '@tailwindcss/vite';\nimport react from '@vitejs/plugin-react';\n\n// https://vite.dev/config/\nexport default defineConfig(() => {\n  const isProfiling = process.env.REACT_PROFILING === '1' || process.env.REACT_PROFILING === 'true';\n  const baseUrl = process.env.PLAYGROUND_BASE ?? '/';\n  const outDir = process.env.PLAYGROUND_OUT_DIR ?? 'dist';\n  const resolvedOutDir = path.isAbsolute(outDir) ? outDir : path.resolve(__dirname, outDir);\n\n  return {\n    base: baseUrl,\n    plugins: [tailwindcss(), react()],\n    build: {\n      sourcemap: true,\n      outDir: resolvedOutDir,\n      emptyOutDir: true,\n    },\n    resolve: {\n      alias: {\n        '@base-ui/react': path.resolve(__dirname, '..', '..', 'packages', 'react', 'src'),\n        '@base-ui/utils': path.resolve(__dirname, '..', '..', 'packages', 'utils', 'src'),\n        ...(isProfiling ? { 'react-dom/client': 'react-dom/profiling' } : {}),\n        docs: path.resolve(__dirname, '..', '..', 'docs'),\n      },\n    },\n    server: {\n      fs: {\n        // Allow serving Base UI source from the monorepo root.\n        allow: [path.resolve(__dirname, '..', '..')],\n      },\n    },\n  };\n});\n"
  },
  {
    "path": "pnpm-workspace.yaml",
    "content": "packages:\n  - packages/*\n  - docs\n  - test\n  - test/*\n  - scripts/*\n  - examples/*\n  - playground/*\n\nonlyBuiltDependencies:\n  - '@mui/internal-babel-plugin-display-name'\n  - '@vvago/vale'\n  - core-js\n  - esbuild\n  - nx\n  - sharp\n  - unrs-resolver\n\nengineStrict: true\n"
  },
  {
    "path": "prettier.config.mjs",
    "content": "import { createBaseConfig } from '@mui/internal-code-infra/prettier';\n\nexport default createBaseConfig();\n"
  },
  {
    "path": "renovate.json",
    "content": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"extends\": [\"github>mui/mui-public//renovate/default\"],\n  \"schedule\": \"* 2-6 * * 5\",\n  \"lockFileMaintenance\": {\n    \"schedule\": \"* 0-4 4 * *\"\n  },\n  \"packageRules\": [\n    {\n      \"groupName\": \"Floating UI\",\n      \"description\": \"Update Floating UI as soon as the new version is released\",\n      \"matchPackageNames\": [\"@floating-ui/*\"],\n      \"schedule\": null\n    },\n    {\n      \"groupName\": \"Tailwind CSS\",\n      \"matchPackageNames\": [\"tailwindcss\", \"@tailwindcss/*\", \"prettier-plugin-tailwindcss\"]\n    },\n    {\n      \"groupName\": \"MUI public packages\",\n      \"matchPackageNames\": [\"@mui/*\", \"!@mui/internal-*\", \"!@mui/docs\"],\n      \"allowedVersions\": \"!/-dev/\"\n    },\n    {\n      \"groupName\": \"JSDOM\",\n      \"description\": \"v28 is causing further performance issues and timeouts in our tests, so we will stay on v27 until the issues are resolved. https://github.com/mui/base-ui/pull/4372\",\n      \"matchPackageNames\": [\"jsdom\"],\n      \"allowedVersions\": \"<28\"\n    }\n  ]\n}\n"
  },
  {
    "path": "scripts/README.md",
    "content": "# Scripts\n\n## Release\n\nA typical release goes like this:\n\n### Prerequisites\n\n1. You must be a member of the `@base-ui` org in npm to publish the release.\n2. Set up your npm authToken by logging into npm (`npm login`) . This will save a token to `~/.npmrc` as a line that looks like this:\n   ```text\n   //registry.npmjs.org/:_authToken=npm_000000000000000000000000000000000000\n   ```\n3. Generate a GitHub Token at https://github.com/settings/personal-access-tokens/new and add it to your shell rc script (either `.bashrc` or `.zshrc`) as `GITHUB_TOKEN`.\n   - When creating the token, choose **mui** as the Resource owner.\n   - Set expiration to 366 days or less.\n   - Set **Public Repositories (read-only)** in Repository access.\n   - Organization permissions are not required.\n\n### Prepare the release of the packages\n\n1. Update the root `/package.json`'s version.\n2. Generate the changelog with `pnpm release:changelog`\n   The output must be prepended to the top level `CHANGELOG.md`.\n   Run `pnpm release:changelog --help` for more information. If your GitHub token is not in your env, pass it as `--githubToken <my-token>` to the above command.\n3. Update the changelog as necessary. In particular, describe all the breaking changes.\n4. Generate the changelog in a format suitable for the docs with `pnpm release:changelog:docs`.\n5. Create a new release page at `docs/src/app/(docs)/react/overview/releases/<version-slug>/page.mdx` (where `<version-slug>` uses hyphens, for example, `v1-1-0` for v1.1.0). Paste the generated changelog there, following the format of existing release pages (title, `<Subtitle>` with the date, `<Meta>` tag).\n6. Copy the changes made in point 3 to the new release page.\n7. Add a new entry to `docs/src/data/releases.ts` with the version, versionSlug, date, and highlights. Move the `latest: true` flag to the new release if appropriate.\n8. Run `pnpm release:version`. Keep the package versions of stable public packages the same as the root `package.json` version.\n9. Open a PR with changes and wait for review and green CI.\n10. Merge the PR once the CI is green and it has been approved.\n\n### Release the packages\n\n1. Run `pnpm release:publish`. You may be asked to authenticate with GitHub when running the command for the first time or after a very long time.\n2. It'll automatically fetch the latest merged release PR and ask for confirmation before publishing.\n3. If you already know the sha of the commit, you can pass it directly like `pnpm release:publish --sha <your-sha>`.\n4. Other flags for the command:\n\n   > - **--dry-run** Used for debugging. Or directly run `pnpm release:publish:dry-run`.\n   > - **--dist-tag** Use to publish legacy or canary versions.\n\n5. This command invokes the [Publish](https://github.com/mui/base-ui/actions/workflows/publish.yml) GitHub action. It'll log the url which can be opened to see the latest workflow run.\n6. The next screen shows \"@username requested your review to deploy to npm-publish\", click \"Review deployments\" and authorize your workflow run. **Never approve workflow runs you didn't initiaite.**\n\n### Publish the documentation\n\nThe documentation must be updated on the `docs-vX` branch (`docs-v1` for `v1.X` releases, `docs-v2` for `v2.X` releases, etc.)\n\nPush the working branch to the documentation release branch to deploy the documentation with the latest changes:\n\n```bash\npnpm docs:deploy\n```\n\nYou can follow the deployment process [on the Netlify Dashboard](https://app.netlify.com/sites/base-ui/deploys?filter=docs-v1)\nOnce deployed, it will be accessible at https://base-ui.netlify.app/ for the `docs-v1` deployment.\n\n### GitHub release\n\nAfter the documentation deployment is done, review, and then publish the release that was created in draft mode during the release step [GitHub releases page](https://github.com/mui/base-ui/releases)\nMake sure to check the **Set as a pre-release** checkbox if publishing an unstable version.\n"
  },
  {
    "path": "scripts/api-docs-builder/package.json",
    "content": "{\n  \"name\": \"api-docs-builder\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"dependencies\": {\n    \"@mui/internal-docs-infra\": \"0.6.1-canary.3\",\n    \"@types/yargs\": \"^17.0.35\",\n    \"es-toolkit\": \"^1.45.1\",\n    \"rimraf\": \"^6.1.3\",\n    \"tsx\": \"^4.21.0\",\n    \"typescript-api-extractor\": \"1.0.0-alpha.13\",\n    \"yargs\": \"^18.0.0\"\n  },\n  \"scripts\": {\n    \"start\": \"tsx src/index.ts -c ../../packages/react/tsconfig.build.json -o ../../docs/reference/generated\"\n  }\n}\n"
  },
  {
    "path": "scripts/api-docs-builder/src/componentHandler.ts",
    "content": "import fs from 'fs';\nimport path from 'path';\nimport * as tae from 'typescript-api-extractor';\nimport { formatProperties, formatEnum } from './formatter';\nimport memberOrder from './order.json';\n\nconst componentsDir = path.resolve(process.cwd(), '../../packages/react/src');\n\nfunction kebabToPascal(str: string): string {\n  return str\n    .split('-')\n    .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n    .join('');\n}\n\nconst componentGroupNames = fs\n  .readdirSync(componentsDir, { withFileTypes: true })\n  .filter((dirent) => dirent.isDirectory())\n  .map((dirent) => kebabToPascal(dirent.name))\n  .sort((a, b) => b.length - a.length);\n\nconst namespaceAliasMap = buildNamespaceAliasMap();\n\nexport async function formatComponentData(component: tae.ExportNode, allExports: tae.ExportNode[]) {\n  const description = component.documentation?.description?.replace(/\\n\\nDocumentation: .*$/ms, '');\n  const dataAttributes = allExports.find((node) => node.name === `${component.name}DataAttributes`);\n  const cssVariables = allExports.find((node) => node.name === `${component.name}CssVars`);\n\n  const raw = {\n    name: component.name,\n    description,\n    props: sortObjectByKeys(\n      await formatProperties((component.type as tae.ComponentNode).props, allExports),\n      memberOrder.props,\n    ),\n    dataAttributes: dataAttributes\n      ? sortObjectByKeys(\n          formatEnum(dataAttributes.type as tae.EnumNode),\n          memberOrder.dataAttributes,\n        )\n      : {},\n    cssVariables: cssVariables\n      ? sortObjectByKeys(formatEnum(cssVariables.type as tae.EnumNode), memberOrder.cssVariables)\n      : {},\n  } as Record<string, any>;\n\n  // Post-process type strings to align naming across re-exports and hide internal suffixes.\n  const componentGroup = extractComponentGroup(component.name);\n  const formatted = rewriteTypeStringsDeep(raw, componentGroup) as typeof raw;\n  formatted.name = component.name;\n  return formatted;\n}\n\nexport function isPublicComponent(exportNode: tae.ExportNode) {\n  // Must be a ComponentNode and not marked as ignored\n  if (\n    !(exportNode.type instanceof tae.ComponentNode) ||\n    exportNode.documentation?.hasTag('ignore') ||\n    !exportNode.isPublic()\n  ) {\n    return false;\n  }\n\n  // Must start with a known component group name (e.g., \"Tooltip\", \"Dialog\", etc.)\n  // This filters out type aliases like \"ComponentRenderFn\" that happen to be\n  // callable types returning React elements\n  return componentGroupNames.some((group) => exportNode.name.startsWith(group));\n}\n\nfunction sortObjectByKeys<T>(obj: Record<string, T>, order: string[]): Record<string, T> {\n  if (order.length === 0) {\n    return obj;\n  }\n\n  const sortedObj: Record<string, T> = {};\n  const everythingElse: Record<string, T> = {};\n\n  // Gather keys that are not in the order array\n  Object.keys(obj).forEach((key) => {\n    if (!order.includes(key)) {\n      everythingElse[key] = obj[key];\n    }\n  });\n\n  // Sort the keys of everythingElse\n  const sortedEverythingElseKeys = Object.keys(everythingElse).sort();\n\n  // Populate the sorted object according to the order array\n  order.forEach((key) => {\n    if (key === '__EVERYTHING_ELSE__') {\n      // Insert all \"everything else\" keys at this position, sorted\n      sortedEverythingElseKeys.forEach((sortedKey) => {\n        sortedObj[sortedKey] = everythingElse[sortedKey];\n      });\n    } else if (obj.hasOwnProperty(key)) {\n      sortedObj[key] = obj[key];\n    }\n  });\n\n  return sortedObj;\n}\n\nexport function extractComponentGroup(componentExportName: string): string {\n  const directMatch = componentGroupNames.find((name) => componentExportName.startsWith(name));\n\n  if (directMatch) {\n    return directMatch;\n  }\n\n  const match = componentExportName.match(/^[A-Z][a-z0-9]*/);\n  return match ? match[0] : componentExportName;\n}\n\nfunction rewriteTypeValue(value: string, componentGroup: string): string {\n  let next = value.replaceAll('.RootInternal', '.Root');\n\n  // When documenting Autocomplete (which re-exports Combobox),\n  // display Autocomplete.* instead of Combobox.*\n  if (componentGroup === 'Autocomplete') {\n    next = next.replaceAll(/\\bCombobox\\./g, 'Autocomplete.');\n  }\n\n  next = applyNamespaceAliases(next, componentGroup);\n  next = applyComponentNamespaceShorthand(next, componentGroup);\n\n  return dedupeUnionMembers(next);\n}\n\nfunction applyNamespaceAliases(value: string, componentGroup: string): string {\n  const orderedGroups = new Set<string>([componentGroup]);\n  for (const group of namespaceAliasMap.keys()) {\n    orderedGroups.add(group);\n  }\n\n  let result = value;\n  for (const group of orderedGroups) {\n    const aliases = namespaceAliasMap.get(group);\n    if (!aliases) {\n      continue;\n    }\n\n    const locals = Array.from(aliases.keys()).sort((a, b) => b.length - a.length);\n    for (const local of locals) {\n      const publicName = aliases.get(local)!;\n      const escapedLocal = escapeForRegex(local);\n\n      result = result.replace(\n        new RegExp(`\\\\b${escapedLocal}\\\\.(?=[A-Za-z])`, 'g'),\n        `${publicName}.`,\n      );\n\n      result = result.replace(\n        new RegExp(`\\\\b${escapedLocal}([A-Z][A-Za-z0-9]*)\\\\b`, 'g'),\n        `${publicName}.$1`,\n      );\n\n      result = result.replace(new RegExp(`\\\\b${escapedLocal}\\\\b`, 'g'), publicName);\n    }\n  }\n\n  return result;\n}\n\nfunction applyComponentNamespaceShorthand(value: string, componentGroup: string): string {\n  if (!componentGroup) {\n    return value;\n  }\n\n  const escapedGroup = escapeForRegex(componentGroup);\n  return value.replace(\n    new RegExp(`\\\\b${escapedGroup}(Props|State)\\\\b`, 'g'),\n    `${componentGroup}.$1`,\n  );\n}\n\nfunction escapeForRegex(value: string): string {\n  return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nfunction buildNamespaceAliasMap() {\n  const files = collectIndexPartsFiles(componentsDir);\n  const map = new Map<string, Map<string, string>>();\n\n  for (const file of files) {\n    const groupDir = path.basename(path.dirname(file));\n    const groupName = kebabToPascal(groupDir);\n    const aliases = map.get(groupName) ?? new Map<string, string>();\n\n    const content = fs.readFileSync(file, 'utf8');\n    const exportRegex = /export\\s*\\{([^}]+)\\}\\s*from\\s*['\"][^'\"\\n]+['\"];?/g;\n\n    for (const match of content.matchAll(exportRegex)) {\n      const entries = match[1]\n        .split(',')\n        .map((entry) => entry.trim())\n        .filter(Boolean);\n\n      for (const entry of entries) {\n        const [local, alias] = entry.split(/\\s+as\\s+/).map((part) => part.trim());\n        if (!local || !alias) {\n          continue;\n        }\n\n        aliases.set(local, `${groupName}.${alias}`);\n      }\n    }\n\n    if (aliases.size > 0) {\n      map.set(groupName, aliases);\n    }\n  }\n\n  return map;\n}\n\nfunction collectIndexPartsFiles(dir: string): string[] {\n  const result: string[] = [];\n  const entries = fs.readdirSync(dir, { withFileTypes: true });\n\n  for (const entry of entries) {\n    const fullPath = path.join(dir, entry.name);\n    if (entry.isDirectory()) {\n      result.push(...collectIndexPartsFiles(fullPath));\n    } else if (entry.isFile() && entry.name === 'index.parts.ts') {\n      result.push(fullPath);\n    }\n  }\n\n  return result;\n}\n\nfunction dedupeUnionMembers(value: string): string {\n  if (!value.includes('|')) {\n    return value;\n  }\n\n  const unionLinePattern = /^\\s*\\|/m;\n  if (unionLinePattern.test(value)) {\n    const seen = new Set<string>();\n    const lines = value.split('\\n');\n    const resultLines: string[] = [];\n    let currentEntry: string[] | null = null;\n\n    const flushEntry = () => {\n      if (!currentEntry) {\n        return;\n      }\n\n      const entryText = currentEntry.join('\\n');\n      const key = entryText.replace(/^\\s*\\|\\s*/, '').trim();\n      if (!seen.has(key)) {\n        seen.add(key);\n        resultLines.push(entryText);\n      }\n\n      currentEntry = null;\n    };\n\n    lines.forEach((line) => {\n      if (line.trim().startsWith('|')) {\n        flushEntry();\n        currentEntry = [line];\n      } else if (currentEntry) {\n        currentEntry.push(line);\n      } else {\n        resultLines.push(line);\n      }\n    });\n\n    flushEntry();\n\n    return resultLines.join('\\n');\n  }\n\n  const parts = splitTopLevelUnion(value);\n  const seen = new Set<string>();\n  const dedupedParts = parts.filter((part) => {\n    const key = part.trim();\n    if (seen.has(key)) {\n      return false;\n    }\n    seen.add(key);\n    return true;\n  });\n\n  return dedupedParts.join(' | ');\n}\n\nfunction splitTopLevelUnion(value: string): string[] {\n  const parts: string[] = [];\n  let current = '';\n  let depthAngle = 0;\n  let depthParen = 0;\n  let depthCurly = 0;\n  let depthSquare = 0;\n\n  for (let i = 0; i < value.length; i += 1) {\n    const char = value[i];\n\n    switch (char) {\n      case '<':\n        depthAngle += 1;\n        break;\n      case '>':\n        depthAngle = Math.max(0, depthAngle - 1);\n        break;\n      case '(': {\n        depthParen += 1;\n        break;\n      }\n      case ')':\n        depthParen = Math.max(0, depthParen - 1);\n        break;\n      case '{':\n        depthCurly += 1;\n        break;\n      case '}':\n        depthCurly = Math.max(0, depthCurly - 1);\n        break;\n      case '[':\n        depthSquare += 1;\n        break;\n      case ']':\n        depthSquare = Math.max(0, depthSquare - 1);\n        break;\n      default:\n        break;\n    }\n\n    if (\n      char === '|' &&\n      depthAngle === 0 &&\n      depthParen === 0 &&\n      depthCurly === 0 &&\n      depthSquare === 0\n    ) {\n      parts.push(current.trim());\n      current = '';\n    } else {\n      current += char;\n    }\n  }\n\n  if (current.trim() !== '') {\n    parts.push(current.trim());\n  }\n\n  return parts.length > 0 ? parts : [value];\n}\n\nfunction rewriteTypeStringsDeep(node: any, componentGroup: string): any {\n  if (node == null) {\n    return node;\n  }\n\n  if (typeof node === 'string') {\n    return rewriteTypeValue(node, componentGroup);\n  }\n\n  if (Array.isArray(node)) {\n    return node.map((item) => rewriteTypeStringsDeep(item, componentGroup));\n  }\n\n  if (typeof node === 'object') {\n    const result: Record<string, any> = {};\n    for (const [key, value] of Object.entries(node)) {\n      result[key] = rewriteTypeStringsDeep(value, componentGroup);\n    }\n    return result;\n  }\n\n  return node;\n}\n"
  },
  {
    "path": "scripts/api-docs-builder/src/formatter.ts",
    "content": "/* eslint-disable no-await-in-loop */\nimport * as tae from 'typescript-api-extractor';\nimport fs from 'fs';\nimport path from 'path';\nimport { uniq, sortBy } from 'es-toolkit/array';\nimport * as prettier from 'prettier';\n\nexport async function formatProperties(\n  props: tae.PropertyNode[],\n  allExports: tae.ExportNode[] | undefined = undefined,\n) {\n  const result: Record<string, any> = {};\n\n  for (const prop of props) {\n    // skip `ref` for components\n    if (prop.name === 'ref' && (allExports?.length ?? -1) > 0) {\n      continue;\n    }\n\n    // skip props marked with @ignore\n    if (prop.documentation?.hasTag('ignore')) {\n      continue;\n    }\n\n    const exampleTag = prop.documentation?.tags\n      ?.filter((tag) => tag.name === 'example')\n      .map((tag) => tag.value)\n      .join('\\n');\n\n    let detailedType = formatType(prop.type, prop.optional, prop.documentation?.tags);\n    if (prop.name !== 'className' && prop.name !== 'render' && allExports) {\n      detailedType = formatDetailedType(prop.type, allExports);\n    }\n\n    const formattedDetailedType = await prettier.format(`type _ = ${detailedType}`, {\n      parser: 'typescript',\n      singleQuote: true,\n      semi: false,\n      printWidth: 60,\n    });\n\n    // Improve readability by formatting complex types with Prettier.\n    // Prettier either formats the type on a single line or multiple lines.\n    // If it's on a single line, we remove the `type _ = ` prefix.\n    // If it's on multiple lines, we remove the first line (`type _ =`) and de-indent the rest.\n    const lines = formattedDetailedType.trimEnd().split('\\n');\n    if (lines.length === 1) {\n      detailedType = lines[0].replace(/^type _ = /, '');\n    } else {\n      const codeLines = lines.slice(1);\n      const nonEmptyLines = codeLines.filter((l) => l.trim() !== '');\n      if (nonEmptyLines.length > 0) {\n        const minIndent = Math.min(...nonEmptyLines.map((l) => l.match(/^\\s*/)?.[0].length ?? 0));\n\n        if (Number.isFinite(minIndent) && minIndent > 0) {\n          detailedType = codeLines.map((l) => l.substring(minIndent)).join('\\n');\n        } else {\n          detailedType = codeLines.join('\\n');\n        }\n      } else {\n        detailedType = codeLines.join('\\n');\n      }\n    }\n\n    const formattedType = formatType(prop.type, prop.optional, prop.documentation?.tags);\n\n    const resultObject: Record<string, any> = {\n      type: formattedType,\n      default: prop.documentation?.defaultValue,\n      required: !prop.optional || undefined,\n      description: prop.documentation?.description,\n      example: exampleTag || undefined,\n      detailedType,\n    };\n\n    if (detailedType === formattedType) {\n      delete resultObject.detailedType;\n    }\n\n    result[prop.name] = resultObject;\n  }\n\n  return result;\n}\n\nexport type DocumentationOverride = {\n  description?: string;\n  tags?: tae.DocumentationTag[];\n};\n\nexport function formatParameters(\n  params: tae.Parameter[],\n  optionalOverrides?: boolean[],\n  documentationOverrides?: Array<DocumentationOverride | undefined>,\n) {\n  const result: Record<string, any> = {};\n\n  for (const [index, param] of params.entries()) {\n    const isOptional = optionalOverrides?.[index] ?? param.optional;\n    const documentation = documentationOverrides?.[index] ?? param.documentation;\n    const exampleTag = documentation?.tags\n      ?.filter((tag) => tag.name === 'example')\n      .map((tag) => tag.value)\n      .join('\\n');\n\n    result[param.name] = {\n      type: formatType(param.type, isOptional, documentation?.tags, true),\n      default: param.defaultValue,\n      optional: isOptional || undefined,\n      description: documentation?.description,\n      example: exampleTag || undefined,\n    };\n  }\n\n  return result;\n}\n\nexport function formatDetailedType(\n  type: tae.AnyType,\n  allExports: tae.ExportNode[],\n  visited = new Set<string>(),\n): string {\n  // Prevent infinite recursion\n  if (type instanceof tae.ExternalTypeNode) {\n    const qualifiedName = getFullyQualifiedName(type.typeName);\n    if (visited.has(qualifiedName)) {\n      return qualifiedName;\n    }\n    visited.add(qualifiedName);\n\n    const exportNode = allExports.find((node) => node.name === type.typeName.name);\n    // Only recurse if the export actually provides more type information\n    // (not just re-exporting the same external type)\n    if (\n      exportNode &&\n      !(\n        exportNode.type instanceof tae.ExternalTypeNode &&\n        exportNode.type.typeName.name === type.typeName.name\n      )\n    ) {\n      return formatDetailedType(\n        (exportNode.type as unknown as tae.AnyType) ?? type,\n        allExports,\n        visited,\n      );\n    }\n\n    // Manually expand known external aliases when declaration is not in local exports\n    switch (true) {\n      case qualifiedName.includes('Padding'):\n        return '{ top?: number; right?: number; bottom?: number; left?: number } | number';\n      default:\n        return qualifiedName;\n    }\n  }\n\n  if (type instanceof tae.UnionNode) {\n    const memberTypes = type.types.map((t) => formatDetailedType(t, allExports, visited));\n    return uniq(memberTypes).join(' | ');\n  }\n\n  if (type instanceof tae.IntersectionNode) {\n    const memberTypes = type.types.map((t) => formatDetailedType(t, allExports, visited));\n    return uniq(memberTypes).join(' & ');\n  }\n\n  // For objects and everything else, reuse existing formatter with object expansion enabled\n  return formatType(type, false, undefined, true);\n}\n\nexport function formatEnum(enumNode: tae.EnumNode) {\n  const result: Record<string, any> = {};\n  for (const member of sortBy(enumNode.members, ['value'])) {\n    result[member.value] = {\n      description: member.documentation?.description,\n      type: member.documentation?.tags?.find((tag) => tag.name === 'type')?.value,\n    };\n  }\n\n  return result;\n}\n\nexport function formatType(\n  type: tae.AnyType,\n  removeUndefined: boolean,\n  jsdocTags: tae.DocumentationTag[] | undefined = undefined,\n  expandObjects: boolean = false,\n): string {\n  const typeTag = jsdocTags?.find?.((tag) => tag.name === 'type');\n  const typeValue = typeTag?.value;\n\n  if (typeValue) {\n    return typeValue;\n  }\n\n  if (type instanceof tae.ExternalTypeNode) {\n    if (/^ReactElement(<.*>)?/.test(type.typeName.name || '')) {\n      return 'ReactElement';\n    }\n\n    if (type.typeName.namespaces?.length === 1 && type.typeName.namespaces[0] === 'React') {\n      return createNameWithTypeArguments(type.typeName);\n    }\n\n    return getFullyQualifiedName(type.typeName);\n  }\n\n  if (type instanceof tae.IntrinsicNode) {\n    return type.typeName ? getFullyQualifiedName(type.typeName) : type.intrinsic;\n  }\n\n  if (type instanceof tae.UnionNode) {\n    if (type.typeName) {\n      return getFullyQualifiedName(type.typeName);\n    }\n\n    let memberTypes = type.types;\n\n    if (removeUndefined) {\n      memberTypes = memberTypes.filter(\n        (t) => !(t instanceof tae.IntrinsicNode && t.intrinsic === 'undefined'),\n      );\n    }\n\n    // Deduplicates types in unions.\n    // Plain unions are handled by TypeScript API Extractor, but we also display unions in type parameters constraints,\n    // so we need to merge those here.\n    const flattenedMemberTypes = memberTypes.flatMap((t) => {\n      if (t instanceof tae.UnionNode) {\n        return t.typeName ? t : t.types;\n      }\n\n      if (t instanceof tae.TypeParameterNode && t.constraint instanceof tae.UnionNode) {\n        return t.constraint.types;\n      }\n\n      return t;\n    });\n\n    const formattedMemberTypes = uniq(\n      orderMembers(flattenedMemberTypes).map((t) => formatType(t, removeUndefined)),\n    );\n\n    return formattedMemberTypes.join(' | ');\n  }\n\n  if (type instanceof tae.IntersectionNode) {\n    if (type.typeName) {\n      return getFullyQualifiedName(type.typeName);\n    }\n\n    return orderMembers(type.types)\n      .map((t) => formatType(t, false))\n      .join(' & ');\n  }\n\n  if (type instanceof tae.ObjectNode) {\n    if (type.typeName && !expandObjects) {\n      return getFullyQualifiedName(type.typeName);\n    }\n\n    if (isObjectEmpty(type.properties)) {\n      return '{}';\n    }\n\n    return `{ ${type.properties\n      .map((m) => `${m.name}${m.optional ? '?' : ''}: ${formatType(m.type, m.optional)}`)\n      .join(', ')} }`;\n  }\n\n  if (type instanceof tae.LiteralNode) {\n    return normalizeQuotes(type.value as string);\n  }\n\n  if (type instanceof tae.ArrayNode) {\n    const formattedMemberType = formatType(type.elementType, false);\n\n    if (formattedMemberType.includes(' ')) {\n      return `(${formattedMemberType})[]`;\n    }\n\n    return `${formattedMemberType}[]`;\n  }\n\n  if (type instanceof tae.FunctionNode) {\n    // If object expansion is requested, we want to fully expand the function signature instead\n    // of returning the aliased type name (e.g., OffsetFunction).\n    if (!expandObjects && type.typeName && !type.typeName.name?.startsWith('ComponentRenderFn')) {\n      return getFullyQualifiedName(type.typeName);\n    }\n\n    const functionSignature = type.callSignatures\n      .map((s) => {\n        const params = s.parameters\n          .map((p) => `${p.name}: ${formatType(p.type, false)}`)\n          .join(', ');\n        const returnType = formatType(s.returnValueType, false);\n        return `(${params}) => ${returnType}`;\n      })\n      .join(' | ');\n    return `(${functionSignature})`;\n  }\n\n  if (type instanceof tae.TupleNode) {\n    if (type.typeName) {\n      return getFullyQualifiedName(type.typeName);\n    }\n\n    return `[${type.types.map((member: tae.AnyType) => formatType(member, false)).join(', ')}]`;\n  }\n\n  if (type instanceof tae.TypeParameterNode) {\n    return type.constraint !== undefined ? formatType(type.constraint, removeUndefined) : type.name;\n  }\n\n  return 'unknown';\n}\n\nfunction kebabToPascal(str: string): string {\n  return str\n    .split('-')\n    .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n    .join('');\n}\n\n// TODO make this less dependent on the structure of the repo\nconst componentsDir = path.resolve(process.cwd(), '../../packages/react/src');\nconst componentNames: string[] = fs\n  .readdirSync(componentsDir, { withFileTypes: true })\n  .filter((dirent) => dirent.isDirectory())\n  .map((dirent) => kebabToPascal(dirent.name));\n\nfunction getFullyQualifiedName(typeName: tae.TypeName): string {\n  const nameWithTypeArgs = createNameWithTypeArguments(typeName);\n\n  // Special case for TemporalSupportedObject to improve readability\n  // This would be removed if we supported multiple adapters or left only `AdapterDateFns` and refactored types accordingly.\n  // TODO: Remove if temporal adapters are supported\n  if (nameWithTypeArgs === 'TemporalSupportedObject') {\n    return 'Date';\n  }\n\n  if (!typeName.namespaces || typeName.namespaces.length === 0) {\n    return nameWithTypeArgs;\n  }\n\n  // Our components are defined in the source as [ComponentName][Part], but exported as [ComponentName].[Part].\n  // The following code adjusts the namespaces to match the exported names.\n  const joinedNamespaces = typeName.namespaces.map((namespace) => {\n    const componentNameInNamespace = componentNames.find((componentName) =>\n      new RegExp(`^${componentName}[A-Z]`).test(namespace),\n    );\n\n    if (componentNameInNamespace) {\n      const dotPosition = componentNameInNamespace.length;\n      return `${namespace.substring(0, dotPosition)}.${namespace.substring(dotPosition)}`;\n    }\n\n    return namespace;\n  });\n\n  return `${joinedNamespaces.join('.')}.${nameWithTypeArgs}`;\n}\n\nfunction createNameWithTypeArguments(typeName: tae.TypeName) {\n  if (\n    typeName.typeArguments &&\n    typeName.typeArguments.length > 0 &&\n    typeName.typeArguments.some((ta) => ta.equalToDefault === false)\n  ) {\n    return `${typeName.name}<${typeName.typeArguments.map((ta) => formatType(ta.type, false)).join(', ')}>`;\n  }\n\n  return typeName.name;\n}\n\n/**\n * Looks for 'any', 'null' and 'undefined' types and moves them to the end of the array of types.\n */\nfunction orderMembers(members: readonly tae.AnyType[]): readonly tae.AnyType[] {\n  let orderedMembers = pushToEnd(members, 'any');\n  orderedMembers = pushToEnd(orderedMembers, 'null');\n  orderedMembers = pushToEnd(orderedMembers, 'undefined');\n  return orderedMembers;\n}\n\nfunction pushToEnd(members: readonly tae.AnyType[], name: string): readonly tae.AnyType[] {\n  const index = members.findIndex((member: tae.AnyType) => {\n    return member instanceof tae.IntrinsicNode && member.intrinsic === name;\n  });\n\n  if (index !== -1) {\n    const member = members[index];\n    return [...members.slice(0, index), ...members.slice(index + 1), member];\n  }\n\n  return members;\n}\n\nfunction isObjectEmpty(object: Record<any, any>) {\n  // eslint-disable-next-line\n  for (const _ in object) {\n    return false;\n  }\n  return true;\n}\n\nfunction normalizeQuotes(str: string) {\n  if (str.startsWith('\"') && str.endsWith('\"')) {\n    return str\n      .replaceAll(\"'\", \"\\\\'\")\n      .replaceAll('\\\\\"', '\"')\n      .replace(/^\"(.*)\"$/, \"'$1'\");\n  }\n\n  return str;\n}\n"
  },
  {
    "path": "scripts/api-docs-builder/src/hookHandler.ts",
    "content": "import * as tae from 'typescript-api-extractor';\nimport { formatProperties, formatParameters, formatType } from './formatter';\n\nexport async function formatHookData(hook: tae.ExportNode) {\n  const description = hook.documentation?.description?.replace(/\\n\\nDocumentation: .*$/ms, '');\n\n  // We don't support hooks with multiple signatures yet\n  const signature = (hook.type as tae.FunctionNode).callSignatures[0];\n  const parameters = signature.parameters;\n  let formattedParameters: Record<string, any>;\n  if (\n    parameters.length === 1 &&\n    parameters[0].type instanceof tae.ObjectNode &&\n    parameters[0].name === 'params'\n  ) {\n    formattedParameters = await formatProperties(parameters[0].type.properties, []);\n  } else {\n    formattedParameters = formatParameters(parameters);\n  }\n\n  let formattedReturnValue: Record<string, any> | string;\n  if (signature.returnValueType instanceof tae.ObjectNode) {\n    formattedReturnValue = await formatProperties(signature.returnValueType.properties, []);\n  } else {\n    formattedReturnValue = formatType(signature.returnValueType, false, undefined, true);\n  }\n\n  return {\n    name: hook.name,\n    description,\n    parameters: formattedParameters,\n    returnValue: formattedReturnValue,\n  };\n}\n\nexport function isPublicHook(exportNode: tae.ExportNode) {\n  return (\n    exportNode.type instanceof tae.FunctionNode &&\n    exportNode.name.startsWith('use') &&\n    exportNode.isPublic(true)\n  );\n}\n"
  },
  {
    "path": "scripts/api-docs-builder/src/index.ts",
    "content": "/* eslint-disable no-await-in-loop */\n/* eslint-disable prefer-template */\n/* eslint-disable no-console */\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport * as inspector from 'node:inspector';\nimport yargs from 'yargs';\nimport { hideBin } from 'yargs/helpers';\nimport * as tae from 'typescript-api-extractor';\nimport { kebabCase } from 'es-toolkit/string';\nimport ts from 'typescript';\nimport { globby } from 'globby';\nimport { syncPageIndex } from '@mui/internal-docs-infra/pipeline/syncPageIndex';\nimport { isPublicComponent, formatComponentData, extractComponentGroup } from './componentHandler';\nimport { isPublicHook, formatHookData } from './hookHandler';\nimport { isPublicUtility, formatUtilityData } from './utilityHandler';\n\nconst isDebug = inspector.url() !== undefined;\n\n/**\n * Maps component parts that are re-exported from other components.\n * Key: ComponentName-PartName, Value: OriginalComponentName-OriginalPartName\n */\nconst REEXPORTED_PARTS: Record<string, string> = {\n  'AlertDialog-Backdrop': 'Dialog-Backdrop',\n  'AlertDialog-Close': 'Dialog-Close',\n  'AlertDialog-Description': 'Dialog-Description',\n  'AlertDialog-Popup': 'Dialog-Popup',\n  'AlertDialog-Portal': 'Dialog-Portal',\n  'AlertDialog-Title': 'Dialog-Title',\n  'AlertDialog-Trigger': 'Dialog-Trigger',\n  'AlertDialog-Viewport': 'Dialog-Viewport',\n};\n\n/**\n * Converts PascalCase to Title Case\n * @example pascalToTitleCase('AlertDialog') -> 'Alert Dialog'\n * @example pascalToTitleCase('NumberField') -> 'Number Field'\n */\nfunction pascalToTitleCase(str: string): string {\n  return str.replace(/([A-Z])/g, ' $1').trim();\n}\n\ninterface RunOptions {\n  files?: string[];\n  configPath: string;\n  out: string;\n}\n\ninterface TsConfig {\n  options: ts.CompilerOptions;\n  fileNames: string[];\n}\n\nfunction kebabToPascal(str: string): string {\n  return str\n    .split('-')\n    .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n    .join('');\n}\n\nfunction isNotReExport(exp: tae.ExportNode & { sourceFilePath?: string }) {\n  // Filter out:\n  // 1. Namespaced exports like \"Accordion.Root\" (from `export * as Accordion`)\n  // 2. Re-exports/aliases where the export name differs from the type name\n  //    e.g., `export { AccordionRoot as Root }` has name=\"Root\" but typeName=\"AccordionRoot\"\n  if (exp.name.includes('.')) {\n    return false;\n  }\n\n  if (exp.sourceFilePath?.endsWith('.parts.ts')) {\n    return false;\n  }\n\n  return true;\n}\n\nasync function run(options: RunOptions) {\n  const config = tae.loadConfig(options.configPath);\n  const files = await getFilesToProcess(options, config);\n  const program = ts.createProgram(files, config.options);\n\n  const { exports, errorCount } = findAllExports(program, files);\n\n  // Track component groups and their parts with API metadata\n  const componentsMap = new Map<\n    string,\n    Map<\n      string,\n      {\n        props: string[];\n        dataAttributes: string[];\n        cssVariables: string[];\n      }\n    >\n  >();\n\n  for (const exportNode of exports.filter(isNotReExport).filter(isPublicComponent)) {\n    const componentApiReference = await formatComponentData(exportNode, exports);\n    const componentName = exportNode.name; // e.g., \"AlertDialogPortal\" or \"DialogBackdrop\"\n\n    // Determine component group from the export's source file, not the component name\n    // This ensures re-exports are grouped correctly (e.g., Dialog parts re-exported by AlertDialog)\n    const sourceFile = exportNode.sourceFilePath || '';\n    const match = sourceFile.match(/\\/src\\/([^/]+)\\//);\n    const componentGroup = match ? kebabToPascal(match[1]) : extractComponentGroup(componentName);\n\n    // Extract props, data attributes, and CSS variables\n    const props = componentApiReference.props\n      ? Object.keys(componentApiReference.props).sort()\n      : [];\n    const dataAttributes = componentApiReference.dataAttributes\n      ? Object.keys(componentApiReference.dataAttributes).sort()\n      : [];\n    const cssVariables = componentApiReference.cssVariables\n      ? Object.keys(componentApiReference.cssVariables).sort()\n      : [];\n\n    // Extract the part name by removing the component group prefix\n    if (componentGroup && componentName.startsWith(componentGroup)) {\n      const partName = componentName.slice(componentGroup.length); // e.g., \"Portal\"\n\n      if (!componentsMap.has(componentGroup)) {\n        componentsMap.set(componentGroup, new Map());\n      }\n\n      const partsMap = componentsMap.get(componentGroup)!;\n\n      // If partName is empty, this is the root component (e.g., \"Checkbox\" from \"Checkbox\")\n      // Store it as \"Root\" to distinguish it from the component group itself\n      const partKey = partName || componentName;\n\n      partsMap.set(partKey, {\n        props,\n        dataAttributes,\n        cssVariables,\n      });\n    }\n\n    const json = JSON.stringify(componentApiReference, null, 2) + '\\n';\n    fs.writeFileSync(path.join(options.out, `${kebabCase(exportNode.name)}.json`), json);\n  }\n\n  for (const exportNode of exports.filter(isNotReExport).filter(isPublicHook)) {\n    const json = JSON.stringify(await formatHookData(exportNode), null, 2) + '\\n';\n    fs.writeFileSync(path.join(options.out, `${kebabCase(exportNode.name)}.json`), json);\n  }\n\n  for (const exportNode of exports.filter(isPublicUtility)) {\n    const json = JSON.stringify(await formatUtilityData(exportNode), null, 2) + '\\n';\n    fs.writeFileSync(path.join(options.out, `${kebabCase(exportNode.name)}.json`), json);\n  }\n\n  // Build the final components metadata\n  const componentsMetadata = new Map<\n    string,\n    {\n      parts?: Record<\n        string,\n        {\n          props: string[];\n          dataAttributes: string[];\n          cssVariables: string[];\n        }\n      >;\n      exports?: Record<\n        string,\n        {\n          props: string[];\n          dataAttributes: string[];\n          cssVariables: string[];\n        }\n      >;\n    }\n  >();\n\n  for (const [componentName, partsMap] of componentsMap) {\n    // Determine if this is a multi-part component or single component\n    // If there's more than one part, or if the single part name doesn't match the component name,\n    // it's a multi-part component\n    const isSingleComponent = partsMap.size === 1 && partsMap.has(componentName);\n\n    if (isSingleComponent) {\n      // Single component: use \"exports\" with the component name\n      const metadata = partsMap.get(componentName)!;\n      componentsMetadata.set(componentName, {\n        exports: {\n          [componentName]: metadata,\n        },\n      });\n    } else {\n      // Multi-part component: use \"parts\" with part names\n      const parts: Record<\n        string,\n        {\n          props: string[];\n          dataAttributes: string[];\n          cssVariables: string[];\n        }\n      > = {};\n\n      for (const [partName, metadata] of partsMap) {\n        parts[partName] = metadata;\n      }\n\n      componentsMetadata.set(componentName, {\n        parts,\n      });\n    }\n  }\n\n  // Add re-exported parts based on REEXPORTED_PARTS mapping\n  for (const [reexportKey, sourceKey] of Object.entries(REEXPORTED_PARTS)) {\n    const [targetComponent, targetPart] = reexportKey.split('-');\n    const [sourceComponent, sourcePart] = sourceKey.split('-');\n\n    const sourceMetadata = componentsMap.get(sourceComponent);\n    if (!sourceMetadata) {\n      continue;\n    }\n\n    const partMetadata = sourceMetadata.get(sourcePart);\n    if (!partMetadata) {\n      continue;\n    }\n\n    // Ensure target component exists in the metadata\n    if (!componentsMetadata.has(targetComponent)) {\n      componentsMetadata.set(targetComponent, { parts: {} });\n    }\n\n    const targetMetadata = componentsMetadata.get(targetComponent)!;\n    if (!targetMetadata.parts) {\n      targetMetadata.parts = {};\n    }\n\n    targetMetadata.parts[targetPart] = partMetadata;\n  }\n\n  // Update the components page index with metadata all at once\n  if (componentsMetadata.size > 0) {\n    const componentsPagePath = path.resolve(\n      path.dirname(path.dirname(path.dirname(options.configPath))),\n      'docs/src/app/(docs)/react/components/page.mdx',\n    );\n\n    // Base directory for docs (matches baseDir in next.config.mjs transformMarkdownMetadata)\n    const docsPath = path.resolve(\n      path.dirname(path.dirname(path.dirname(options.configPath))),\n      'docs',\n    );\n\n    const docsBasePath = path.dirname(componentsPagePath);\n\n    // Build metadata for all components, but only include those with existing pages\n    const skippedComponents: string[] = [];\n    const allComponentsMetadata = (\n      await Promise.all(\n        Array.from(componentsMetadata.entries()).map(async ([componentName, metadata]) => {\n          const slug = kebabCase(componentName);\n          const componentPagePath = path.join(docsBasePath, slug, 'page.mdx');\n\n          // Check if the page exists\n          if (!fs.existsSync(componentPagePath)) {\n            skippedComponents.push(componentName);\n            return null;\n          }\n\n          return {\n            slug,\n            path: `./${slug}/page.mdx`,\n            title: pascalToTitleCase(componentName),\n            ...metadata,\n          };\n        }),\n      )\n    ).filter((metadata): metadata is NonNullable<typeof metadata> => metadata !== null);\n\n    // Update the index once with all components in a single operation\n    await syncPageIndex({\n      pagePath: componentsPagePath,\n      metadataList: allComponentsMetadata,\n      baseDir: docsPath,\n    });\n\n    console.log(\n      `\\nUpdated components index with metadata for ${allComponentsMetadata.length} component(s).`,\n    );\n    if (skippedComponents.length > 0) {\n      console.log(\n        `Skipped ${skippedComponents.length} component(s) with no page: ${skippedComponents.join(', ')}`,\n      );\n    }\n  }\n\n  console.log(`\\nProcessed ${files.length} files.`);\n  if (errorCount > 0) {\n    console.log(`❌ Found ${errorCount} errors.`);\n    process.exit(1);\n  }\n}\n\nasync function getFilesToProcess(options: RunOptions, config: TsConfig): Promise<string[]> {\n  if (options.files && options.files.length > 0) {\n    const files = await globby(options.files, {\n      cwd: path.dirname(options.configPath),\n      absolute: true,\n      onlyFiles: true,\n    });\n\n    if (files.length === 0) {\n      console.error('No files found matching the provided patterns.');\n      process.exit(1);\n    } else {\n      console.log(`Found ${files.length} files to process based on the provided patterns:`);\n      files.forEach((file) => console.log(`- ${file}`));\n      console.log('');\n    }\n\n    return files;\n  }\n\n  return config.fileNames;\n}\n\nyargs(hideBin(process.argv))\n  .command<RunOptions>(\n    '$0',\n    'Extracts the API descriptions from a set of files',\n    (command) => {\n      return command\n        .option('configPath', {\n          alias: 'c',\n          type: 'string',\n          demandOption: true,\n          description: 'The path to the tsconfig.json file',\n        })\n        .option('out', {\n          alias: 'o',\n          demandOption: true,\n          type: 'string',\n          description: 'The output directory.',\n        })\n        .option('files', {\n          alias: 'f',\n          type: 'array',\n          demandOption: false,\n          description:\n            'The files to extract the API descriptions from. If not provided, all files in the tsconfig.json are used. You can use globs like `src/**/*.{ts,tsx}` and `!**/*.test.*`. Paths are relative to the tsconfig.json file.',\n        })\n        .option('includeExternal', {\n          alias: 'e',\n          type: 'boolean',\n          default: false,\n          description: 'Include props defined outside of the project',\n        });\n    },\n    run,\n  )\n  .help()\n  .strict()\n  .version(false)\n  .parse();\n\nfunction findAllExports(program: ts.Program, sourceFiles: string[]) {\n  const allExports: Array<tae.ExportNode & { sourceFilePath?: string }> = [];\n  let errorCounter = 0;\n\n  for (const file of sourceFiles) {\n    if (!isDebug) {\n      console.log(`Processing ${file}`);\n      console.group();\n    }\n\n    try {\n      const ast = tae.parseFromProgram(file, program);\n      // Tag each export with its source file path\n      for (const exp of ast.exports) {\n        (exp as any).sourceFilePath = file;\n        allExports.push(exp);\n      }\n    } catch (error) {\n      console.error(`⛔ Error processing ${file}: ${(error as Error).message}`);\n      errorCounter += 1;\n    } finally {\n      if (!isDebug) {\n        console.groupEnd();\n      }\n    }\n  }\n\n  return { exports: allExports, errorCount: errorCounter };\n}\n"
  },
  {
    "path": "scripts/api-docs-builder/src/order.json",
    "content": "{\n  \"cssVariables\": [],\n  \"dataAttributes\": [\n    \"data-checked\",\n    \"data-unchecked\",\n    \"data-open\",\n    \"data-closed\",\n    \"data-panel-open\",\n    \"data-popup-open\",\n    \"data-popup-side\",\n    \"data-list-empty\",\n    \"data-pressed\",\n    \"data-selected\",\n    \"data-highlighted\",\n    \"data-dragging\",\n    \"data-orientation\",\n    \"data-disabled\",\n    \"data-readonly\",\n    \"data-required\",\n    \"data-valid\",\n    \"data-invalid\",\n    \"data-dirty\",\n    \"data-touched\",\n    \"data-uncentered\",\n    \"data-anchor-hidden\",\n    \"__EVERYTHING_ELSE__\",\n    \"data-starting-style\",\n    \"data-ending-style\"\n  ],\n  \"props\": [\n    \"aria-label\",\n    \"aria-labelledby\",\n    \"name\",\n    \"label\",\n    \"defaultChecked\",\n    \"checked\",\n    \"onCheckedChange\",\n    \"indeterminate\",\n    \"defaultValue\",\n    \"value\",\n    \"onValueChange\",\n    \"onValueCommitted\",\n    \"allValues\",\n    \"defaultInputValue\",\n    \"inputValue\",\n    \"onInputValueChange\",\n    \"defaultOpen\",\n    \"open\",\n    \"onOpenChange\",\n    \"defaultPressed\",\n    \"pressed\",\n    \"onPressedChange\",\n    \"onPressedCommitted\",\n    \"snapPoints\",\n    \"defaultSnapPoint\",\n    \"snapPoint\",\n    \"onSnapPointChange\",\n    \"onClick\",\n    \"closeOnClick\",\n    \"errors\",\n    \"hidden\",\n    \"hiddenUntilFound\",\n    \"autoHighlight\",\n    \"keepHighlight\",\n    \"highlightItemOnHover\",\n    \"defaultVisibleDate\",\n    \"visibleDate\",\n    \"onVisibleDateChange\",\n    \"__EVERYTHING_ELSE__\",\n    \"step\",\n    \"smallStep\",\n    \"largeStep\",\n    \"minStepsBetweenValues\",\n    \"min\",\n    \"max\",\n    \"allowWheelScrub\",\n    \"format\",\n    \"autoFocus\",\n    \"container\",\n    \"align\",\n    \"alignOffset\",\n    \"side\",\n    \"sideOffset\",\n    \"arrowPadding\",\n    \"anchor\",\n    \"collisionAvoidance\",\n    \"collisionBoundary\",\n    \"collisionPadding\",\n    \"sticky\",\n    \"positionMethod\",\n    \"trackAnchor\",\n    \"trackCursorAxis\",\n    \"initialFocus\",\n    \"finalFocus\",\n    \"match\",\n    \"forceShow\",\n    \"disabled\",\n    \"readOnly\",\n    \"required\",\n    \"valid\",\n    \"invalid\",\n    \"openOnHover\",\n    \"delay\",\n    \"closeDelay\",\n    \"timeout\",\n    \"disableHoverablePopup\",\n    \"loop\",\n    \"orientation\",\n    \"inputRef\",\n    \"validate\",\n    \"validationMode\",\n    \"validationDebounceTime\",\n    \"id\",\n    \"children\",\n    \"className\",\n    \"style\",\n    \"keepMounted\",\n    \"render\"\n  ]\n}\n"
  },
  {
    "path": "scripts/api-docs-builder/src/utilityHandler.ts",
    "content": "import * as tae from 'typescript-api-extractor';\nimport { formatParameters, formatType, type DocumentationOverride } from './formatter';\n\nfunction extractDescription(documentation: tae.Documentation | undefined) {\n  if (!documentation?.description) {\n    return '';\n  }\n\n  if (typeof documentation.description === 'string') {\n    return documentation.description.replace(/\\n\\nDocumentation: .*$/ms, '');\n  }\n\n  if (Array.isArray(documentation.description)) {\n    const descArray = documentation.description as any[];\n    return descArray\n      .map((node: any) => {\n        if (typeof node === 'string') {\n          return node;\n        }\n        if (node && typeof node === 'object') {\n          if (node.kind && typeof node.getText === 'function') {\n            let text = node.getText();\n            text = text\n              .replace(/^\\/\\*\\*\\s*/, '')\n              .replace(/\\s*\\*\\/\\s*$/, '')\n              .replace(/^\\s*\\*\\s?/gm, '');\n            return text;\n          }\n          if (node.text) {\n            return node.text;\n          }\n          if (node.target) {\n            return node.target;\n          }\n        }\n        return '';\n      })\n      .join('')\n      .trim()\n      .replace(/\\n\\nDocumentation: .*$/ms, '');\n  }\n\n  if (typeof documentation.description === 'object') {\n    const desc = documentation.description as any;\n    if (typeof desc.getText === 'function') {\n      return desc.getText();\n    }\n    return desc.text || desc.target || '';\n  }\n\n  return '';\n}\n\nfunction extractReturnDescription(documentation: tae.Documentation | undefined) {\n  const returnTag = documentation?.tags?.find(\n    (tag) => tag.name === 'returns' || tag.name === 'return',\n  );\n  if (!returnTag?.value) {\n    return undefined;\n  }\n\n  const description = returnTag.value.replace(/^\\s*-\\s*/, '').trim();\n  return description || undefined;\n}\n\nfunction applyParamTags(\n  tags: tae.DocumentationTag[] | undefined,\n  parameters: tae.Parameter[],\n  documentationOverrides: Array<DocumentationOverride | undefined>,\n) {\n  if (!tags?.length) {\n    return;\n  }\n\n  for (const tag of tags) {\n    if (tag.name !== 'param' || typeof tag.value !== 'string') {\n      continue;\n    }\n\n    const trimmed = tag.value.trim();\n    if (!trimmed) {\n      continue;\n    }\n\n    const [paramName, ...rest] = trimmed.split(/\\s+/);\n    if (!paramName) {\n      continue;\n    }\n\n    const description = rest.join(' ').replace(/^-\\s*/, '');\n    if (!description) {\n      continue;\n    }\n\n    const index = parameters.findIndex((param) => param.name === paramName);\n    if (index === -1 || documentationOverrides[index]) {\n      continue;\n    }\n\n    documentationOverrides[index] = { description };\n  }\n}\n\nexport async function formatUtilityData(utility: tae.ExportNode) {\n  const functionNode = utility.type as tae.FunctionNode;\n\n  // Find the signature with the most parameters (likely the implementation signature)\n  let bestSignature = functionNode.callSignatures[0];\n  let maxParams = bestSignature.parameters.length;\n\n  for (const sig of functionNode.callSignatures) {\n    if (sig.parameters.length > maxParams) {\n      bestSignature = sig;\n      maxParams = sig.parameters.length;\n    }\n  }\n\n  // Try to get description from the utility node's documentation\n  let description = extractDescription(utility.documentation);\n\n  // Append @important tag content to description if present\n  const importantTag = utility.documentation?.tags?.find((tag) => tag.name === 'important');\n  if (importantTag?.value) {\n    description += `\\n\\n${importantTag.value}`;\n  }\n\n  const returnDescription = extractReturnDescription(utility.documentation);\n\n  const parameters = bestSignature.parameters;\n  const optionalOverrides = parameters.map((param, index) => {\n    if (param.optional) {\n      return true;\n    }\n\n    for (const signature of functionNode.callSignatures) {\n      if (signature.parameters.length <= index) {\n        return true;\n      }\n\n      if (signature.parameters[index]?.optional) {\n        return true;\n      }\n    }\n\n    return false;\n  });\n\n  const documentationOverrides = Array<DocumentationOverride | undefined>(parameters.length).fill(\n    undefined,\n  );\n\n  for (const signature of functionNode.callSignatures) {\n    signature.parameters.forEach((param, index) => {\n      if (documentationOverrides[index]) {\n        return;\n      }\n\n      const documentation = param.documentation;\n      if (documentation?.description || documentation?.tags?.length) {\n        documentationOverrides[index] = documentation;\n      }\n    });\n  }\n\n  applyParamTags(utility.documentation?.tags, parameters, documentationOverrides);\n\n  const formattedParameters = formatParameters(\n    parameters,\n    optionalOverrides,\n    documentationOverrides,\n  );\n\n  const formattedReturnValue = formatType(bestSignature.returnValueType, false, undefined, true);\n\n  return {\n    name: utility.name,\n    description,\n    parameters: formattedParameters,\n    returnValue: returnDescription\n      ? {\n          type: formattedReturnValue,\n          description: returnDescription,\n        }\n      : formattedReturnValue,\n  };\n}\n\nexport function isPublicUtility(exportNode: tae.ExportNode) {\n  return (\n    exportNode.type instanceof tae.FunctionNode &&\n    !exportNode.name.startsWith('use') && // Exclude hooks\n    exportNode.isPublic(true)\n  );\n}\n"
  },
  {
    "path": "scripts/changelog.config.mjs",
    "content": "/**\n * @type {import('@mui/internal-code-infra/changelog').ChangelogConfig}\n */\nexport default {\n  format: {\n    version: 'v{{version}}',\n    dateFormat: process.env.FORMAT === 'docs' ? '**MMM DD, YYYY**' : '_MMM DD, YYYY_',\n    changelogMessage:\n      process.env.FORMAT === 'docs'\n        ? '{{flagPrefix}} {{message}} ([#{{prNumber}}]({{prUrl}}))'\n        : '{{flagPrefix}} {{message}} (#{{prNumber}}) by @{{author}}',\n  },\n\n  filter: {\n    // Exclude bot commits\n    excludeCommitByAuthors: [/\\[bot\\]$/],\n\n    // Exclude commits with certain labels\n    excludeCommitWithLabels: [\n      'internal',\n      'dependencies',\n      'scope: code-infra',\n      'scope: docs-infra',\n      'scope: support-infra',\n      'test',\n      'release',\n      'docs',\n      'website',\n    ],\n    excludeAuthorsFromContributors: ['Copilot'],\n  },\n\n  categorization: {\n    strategy: 'component',\n\n    labels: {\n      component: {\n        prefix: ['component:', 'hook:'],\n      },\n      // Category overrides - labels that force a specific section\n      categoryOverrides: {\n        'all components': 'general changes',\n        'scope: all components': 'general changes',\n      },\n      // Explicit flag labels\n      flags: {\n        'breaking change': {\n          prefix: '🚨 **Breaking change:**',\n        },\n      },\n    },\n\n    sections: {\n      order: {\n        'general changes': -1,\n      },\n      fallbackSection: 'general changes',\n    },\n  },\n};\n"
  },
  {
    "path": "scripts/inlineScripts.mts",
    "content": "import * as path from 'node:path';\nimport { watch } from 'node:fs';\nimport { readFile, writeFile } from 'node:fs/promises';\nimport { fileURLToPath } from 'node:url';\nimport { globby } from 'globby';\nimport { minify } from 'terser';\n\nconst currentDirectory = fileURLToPath(new URL('.', import.meta.url));\n\nconst preamble = [\n  '// This file is autogenerated. Do not edit it directly.',\n  '// To update it, modify the corresponding source file and run `pnpm inline-scripts`.',\n].join('\\n');\n\nasync function buildFile(sourceFile: string) {\n  const sourceContent = await readFile(sourceFile, 'utf8');\n  const { code: minifiedCode } = await minify(sourceContent, { ecma: 2020 });\n  const escapedCode = minifiedCode!.replace(/'/g, \"\\\\'\");\n  const output = [\n    preamble,\n    '',\n    '// prettier-ignore',\n    `export const script = '${escapedCode}';\\n`,\n  ].join('\\n');\n  const outputFilename = sourceFile.replace('.template.js', '.min.ts');\n  await writeFile(outputFilename, output);\n}\n\n/**\n * Finds files with the `.template.js` extension in the `react` package, minifies them, and writes\n * the code to a new file with the `.min.ts` extension.\n * The minified code is then exported as a string literal.\n */\nasync function run() {\n  const files = await globby('**/*.template.js', {\n    absolute: true,\n    cwd: path.resolve(currentDirectory, '../packages/react/src'),\n  });\n  await Promise.all(files.map(buildFile));\n  return files;\n}\n\nconst files = await run();\n\nif (process.argv.includes('--watch') || process.argv.includes('-w')) {\n  // eslint-disable-next-line no-console\n  console.log('Processing *.template.js files in watch mode...');\n\n  files.forEach((file) => {\n    watch(file, (eventType) => {\n      if (eventType === 'change') {\n        buildFile(file);\n      }\n    });\n  });\n}\n"
  },
  {
    "path": "scripts/tsconfig.json",
    "content": "{\n  \"extends\": \"../tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"lib\": [\"ES2021\"],\n    \"module\": \"preserve\",\n    \"moduleResolution\": \"bundler\"\n  },\n  \"include\": [\"**/*.ts\", \"**/*.mts\", \"**/*.mjs\", \"../package.json\", \"../*.mjs\", \"../*.mts\"],\n  \"exclude\": [\"**/node_modules\"]\n}\n"
  },
  {
    "path": "stylelint.config.mjs",
    "content": "// Note: To debug stylelint config resolution for a specific file, use\n//         pnpm exec stylelint --print-config <path-to-file>\n\n/** @type {import('stylelint').Config} */\nexport default {\n  extends: '@mui/internal-code-infra/stylelint',\n  rules: {\n    // empty lines help with readability\n    'declaration-empty-line-before': null,\n    'custom-property-empty-line-before': null,\n    'comment-empty-line-before': null,\n    'at-rule-empty-line-before': null,\n    'function-no-unknown': true,\n    'number-max-precision': 5,\n\n    'no-descending-specificity': null, // Some styles depend on order\n  },\n  overrides: [\n    {\n      files: ['docs/**/*'],\n      extends: ['stylelint-config-tailwindcss'],\n      rules: {\n        // https://github.com/zhilidali/stylelint-config-tailwindcss/issues/12\n        'declaration-property-value-no-unknown': null,\n        // Remove when ready\n        'keyframes-name-pattern': null,\n      },\n    },\n    {\n      // Not fixing experiments for now\n      files: ['docs/src/app/[(]private[)]/experiments/**/*'],\n      rules: {\n        'block-no-redundant-nested-style-rules': null,\n        'no-descending-specificity': null,\n        'no-duplicate-selectors': null,\n      },\n    },\n  ],\n};\n"
  },
  {
    "path": "test/README.md",
    "content": "# Testing\n\nThanks for writing tests! Here's a quick run-down on our current setup.\n\n## Getting started\n\n1. Add a unit test to `packages/*/src/TheUnitInQuestion/TheUnitInQuestion.test.js` or an integration test `packages/*/test/`.\n2. Run `pnpm test:jsdom TheUnitInQuestion`.\n3. Implement the tested behavior\n4. Open a PR once the test passes or if you want somebody to review your work\n\n## Tools we use\n\n- [@testing-library/react](https://testing-library.com/docs/react-testing-library/intro/)\n- [Vitest](https://vitest.dev/)\n- [@testing-library/jest-dom](https://testing-library.com/docs/ecosystem-jest-dom/)\n- [Playwright](https://playwright.dev/)\n- [jsdom](https://github.com/jsdom/jsdom)\n\n## Writing tests\n\nFor all unit tests, please use the return value of `createRenderer` from `#test-utils`.\nIt prepares the test suite and returns a function with the same interface as\n[`render` from `@testing-library/react`](https://testing-library.com/docs/react-testing-library/api#render).\n\n```js\ndescribe('test suite', () => {\n  const { render } = createRenderer();\n\n  test('first', async () => {\n    await render(<input />);\n  });\n});\n```\n\nFor new tests please use Vitest's native `expect()` and `vi.fn()` APIs. Prefer expressive built-in matchers along with\n[`@testing-library/jest-dom`](https://testing-library.com/docs/ecosystem-jest-dom/) DOM assertions where applicable.\n\nDeciding where to put a test is (like naming things) a hard problem:\n\n- When in doubt, put the new test case directly in the unit test file for that component, for example `packages/react/src/accordion/root/AccordionRoot.test.tsx`.\n- If your test requires multiple components from the library create a new integration test.\n- If you find yourself using a lot of `data-testid` attributes or you're accessing\n  a lot of styles consider adding a component (that doesn't require any interaction)\n  to `test/regressions/tests/`, for example `test/regressions/tests/List/ListWithSomeStyleProp`\n- If you have to dispatch and compose many different DOM events prefer end-to-end tests (Checkout the [end-to-end testing readme](./e2e/README.md) for more information.)\n\n### Unexpected calls to `console.error` or `console.warn`\n\nBy default, our test suite fails if any test recorded `console.error` or `console.warn` calls that are unexpected.\n\nThe failure message includes the full test name (suite names + test name).\nThis should help locating the test in case the top of the stack can't be read due to excessive error messages.\nThe error includes the logged message as well as the stacktrace of that message.\n\nYou can explicitly [expect no console calls](#writing-a-test-for-consoleerror-or-consolewarn) for when you're adding a regression test.\nThis makes the test more readable and properly fails the test in watchmode if the test had unexpected `console` calls.\n\n### Writing a test for `console.error` or `console.warn`\n\nIf you add a new warning via `console.error` or `console.warn` you should add tests that expect this message.\nFor tests that expect a call you can use our custom `toWarnDev` or `toErrorDev` matchers.\nThe expected messages must be a subset of the actual messages and match the casing.\nThe order of these messages must match as well.\n\nExample:\n\n```jsx\nfunction SomeComponent({ variant }) {\n  if (process.env.NODE_ENV !== 'production') {\n    if (variant === 'unexpected') {\n      console.error(\"That variant doesn't make sense.\");\n    }\n    if (variant !== undefined) {\n      console.error('`variant` is deprecated.');\n    }\n  }\n\n  return <div />;\n}\nexpect(() => {\n  render(<SomeComponent variant=\"unexpected\" />);\n}).toErrorDev([\"That variant doesn't make sense.\", '`variant` is deprecated.']);\n```\n\n```js\nfunction SomeComponent({ variant }) {\n  if (process.env.NODE_ENV !== 'production') {\n    if (variant === 'unexpected') {\n      console.error(\"That variant doesn't make sense.\");\n    }\n    if (variant !== undefined) {\n      console.error('`variant` is deprecated.');\n    }\n  }\n\n  return <div />;\n}\nexpect(() => {\n  render(<SomeComponent />);\n}).not.toErrorDev();\n```\n\n## Commands\n\nWe uses a wide range of tests approach as each of them comes with a different\ntrade-off, mainly completeness vs. speed.\n\n### React API level\n\n#### Debugging tests\n\nIf you want to debug tests with the, for example Chrome inspector (chrome://inspect) you can run `pnpm test:jsdom <testFilePattern> --debug`.\nNote that the test will not get executed until you start code execution in the inspector.\n\nRunning a browser test (`pnpm test:chromium`) locally opens a browser window that lets you set breakpoints.\n\n#### Run the core unit test suite\n\nTo run all of the unit tests run `pnpm test:jsdom`.\nIt runs a Vitest CLI that lets you filter tests and watches for changes.\n\nIf you want to run only tests from a particular file, append its name to the commandline: `pnpm test:jsdom TheUnitInQuestion`\n\n### DOM API level\n\n#### Run the test suite in the browser\n\n`pnpm test:chromium`\n`pnpm test:firefox`\n\nTesting the components with JSDOM sometimes isn't enough, as it doesn't support all the APIs.\nWe need to make sure they will behave as expected with a **real DOM**.\nTo solve that problem we use Vitest in [browser mode](https://vitest.dev/guide/browser/).\n\nOur tests run on different browsers to increase the coverage:\n\n- [Headless Chromium](https://chromium.googlesource.com/chromium/src/+/lkgr/headless/README.md)\n- Headless Firefox\n- Chrome, Safari, and Edge thanks to [BrowserStack](https://www.browserstack.com)\n\nIn development mode, if `pnpm test:chromium` or `pnpm test:firefox` fails with this error \"Cannot start ChromeHeadless. Can not find the binary\", you can solve it by installing the missing headless browsers: `pnpm playwright install --with-deps`.\n\n##### BrowserStack\n\nWe only use BrowserStack for non-PR commits to save resources.\nBrowserStack rarely reports actual issues so we only use it as a stop-gap for releases not merges.\n\nTo force a run of BrowserStack on a PR you have to run the pipeline with `browserstack-force` set to `true`.\nFor example, you've opened a PR with the number 64209 and now after everything is green you want to make sure the change passes all browsers:\n\n```bash\ncurl --request POST \\\n  --url https://circleci.com/api/v2/project/gh/mui/base-ui/pipeline \\\n  --header 'content-type: application/json' \\\n  --header 'Circle-Token: $CIRCLE_TOKEN' \\\n  --data-raw '{\"branch\":\"pull/64209/head\",\"parameters\":{\"browserstack-force\":true}}'\n```\n\n### Browser API level\n\nIn the end, components are going to be used in a real browser.\nThe DOM is just one dimension of that environment,\nso we also need to take into account the rendering engine.\n\n#### Visual regression tests\n\nCheck out the [visual regression testing readme](./regressions/README.md) for more information.\n\n#### end-to-end tests\n\nCheckout the [end-to-end testing readme](./e2e/README.md) for more information.\n\n##### Development\n\nWhen working on the visual regression tests you can run `pnpm test:regressions:dev` in the background to constantly rebuild the views used for visual regression testing.\nTo actually take the screenshots you can then run `pnpm test:regressions:run`.\nYou can view the screenshots in `test/regressions/screenshots/chrome`.\n\nAlternatively, you might want to open `http://localhost:5173` (while `pnpm test:regressions:dev` is running) to view individual views separately.\n\n### Testing multiple versions of React\n\nYou can check integration of different versions of React (for example different [release channels](https://react.dev/community/versioning-policy) or PRs to React) by running `pnpm dlx @mui/internal-code-infra@canary set-version-overrides --pkg react@<version>`.\n\nPossible values for `version`:\n\n- default: `stable` (minimum supported React version)\n- a tag on npm, for example `next`, `experimental` or `latest`\n- an older version, for example `^17.0.0`\n\n#### CI\n\nYou can pass the same `version` to our CircleCI pipeline as well:\n\nWith the following API request we're triggering a run of the default workflow in\nPR #24289 for `react@next`\n\n```bash\ncurl --request POST \\\n  --url https://circleci.com/api/v2/project/gh/mui/base-ui/pipeline \\\n  --header 'content-type: application/json' \\\n  --header 'Circle-Token: $CIRCLE_TOKEN' \\\n  --data-raw '{\"branch\":\"pull/24289/head\",\"parameters\":{\"react-version\":\"next\"}}'\n```\n"
  },
  {
    "path": "test/bundle-size/bundle-size-checker.config.mjs",
    "content": "/**\n * @file Configuration file for bundle-size-checker\n *\n * This file determines which packages and components will have their bundle sizes measured.\n */\nimport path from 'path';\nimport fs from 'fs/promises';\nimport { defineConfig } from '@mui/internal-bundle-size-checker';\n\nconst rootDir = path.resolve(import.meta.dirname, '../..');\n\nasync function getBaseUiExports() {\n  // Read the package.json to get exports\n  const packageJsonPath = path.join(rootDir, 'packages/react/package.json');\n  const packageJsonContent = await fs.readFile(packageJsonPath, 'utf8');\n  const packageJson = JSON.parse(packageJsonContent);\n\n  // Get all export paths from @base-ui/react package.json\n  const exports = packageJson.exports;\n  const entrypoints = Object.keys(exports).map((exportKey) => {\n    // Convert from \"./accordion\" to \"@base-ui/react/accordion\"\n    const entrypoint = exportKey === '.' ? '@base-ui/react' : `@base-ui/react${exportKey.slice(1)}`;\n    return entrypoint;\n  });\n\n  return entrypoints;\n}\n\nasync function getUtilsExports() {\n  // Read top-level files to get utils exports\n  const utilsDir = path.join(rootDir, 'packages/utils/src');\n  const files = await fs.readdir(utilsDir);\n\n  // Get file stats concurrently\n  const fileStats = await Promise.all(\n    files.map(async (file) => {\n      const filePath = path.join(utilsDir, file);\n      const stat = await fs.stat(filePath);\n      return { file, stat };\n    }),\n  );\n\n  const entrypoints = fileStats\n    .filter(({ file, stat }) => {\n      // For files, only include .ts and .tsx files\n      if (stat.isFile() && !(file.endsWith('.ts') || file.endsWith('.tsx'))) {\n        return false;\n      }\n      // Exclude test files\n      if (file.includes('.test.')) {\n        return false;\n      }\n      return true;\n    })\n    .map(({ file }) => `@base-ui/utils/${file.replace(/\\.(js|ts|tsx)$/, '')}`);\n\n  return entrypoints;\n}\n\n/**\n * Generates the entrypoints configuration by scanning the exports field in package.json.\n */\nexport default defineConfig(async () => {\n  return {\n    entrypoints: [...(await getBaseUiExports()), ...(await getUtilsExports())],\n    upload: !!process.env.CI,\n    comment: true,\n  };\n});\n"
  },
  {
    "path": "test/bundle-size/package.json",
    "content": "{\n  \"private\": true,\n  \"description\": \"Bundle size measurement workspace for MUI packages\",\n  \"scripts\": {\n    \"check\": \"cross-env NODE_OPTIONS=\\\"--max-old-space-size=4096\\\" bundle-size-checker --output ../size-snapshot.json\"\n  },\n  \"dependencies\": {\n    \"@base-ui/react\": \"workspace:*\",\n    \"@base-ui/utils\": \"workspace:*\",\n    \"@mui/internal-bundle-size-checker\": \"1.0.9-canary.65\",\n    \"radix-ui\": \"^1.4.3\"\n  },\n  \"devDependencies\": {\n    \"cross-env\": \"10.1.0\"\n  }\n}\n"
  },
  {
    "path": "test/e2e/README.md",
    "content": "# end-to-end testing\n\nEnd-to-end tests (short <abbr title=\"end-to-end\">e2e</abbr>) are split into two parts:\n\n1. The rendered UI, or test fixtures\n2. Instrumentation of fixtures with a simple Vite app\n\n## Rendered UI\n\nThe composition of all tests happens in [`./main.tsx`](./main.tsx).\nThe rendered UI is located inside a separate file in `./fixtures` and written as a React component.\nIf you're adding a new test prefer a new component instead of editing existing files since that might unknowingly alter existing tests.\n\n## Instrumentation\n\nWe're using [`playwright`](https://playwright.dev) to replay user actions.\nEach test tests only a single fixture.\nA fixture can be loaded with `await renderFixture(fixturePath)`, for example `renderFixture('FocusTrap/OpenFocusTrap')`.\n\n## Commands\n\nFor development `pnpm test:e2e:dev` and `pnpm test:e2e:run --watch` in separate terminals is recommended.\n\n| command                | description                                                                                   |\n| :--------------------- | :-------------------------------------------------------------------------------------------- |\n| `pnpm test:e2e`        | Full run                                                                                      |\n| `pnpm test:e2e:dev`    | Prepares the fixtures and runs a Vite dev server                                              |\n| `pnpm test:e2e:run`    | Runs the tests (requires `pnpm test:e2e:dev` or `pnpm test:e2e:build`+`pnpm test:e2e:server`) |\n| `pnpm test:e2e:build`  | Builds the Vite bundle for viewing fixtures                                                   |\n| `pnpm test:e2e:server` | Serves the fixture bundle.                                                                    |\n"
  },
  {
    "path": "test/e2e/TestViewer.tsx",
    "content": "import * as React from 'react';\n\nfunction TestViewer(props: { children: React.ReactNode }) {\n  const { children } = props;\n\n  // We're simulating `act(() => ReactDOM.render(children))`\n  // In the end children passive effects should've been flushed.\n  // React doesn't have any such guarantee outside of `act()` so we're approximating it.\n  const [ready, setReady] = React.useState(false);\n  React.useEffect(() => {\n    setReady(true);\n  }, []);\n\n  return (\n    <div aria-busy={!ready} data-testid=\"testcase\">\n      {children}\n    </div>\n  );\n}\n\nexport default TestViewer;\n"
  },
  {
    "path": "test/e2e/fixtures/.gitkeep",
    "content": ""
  },
  {
    "path": "test/e2e/fixtures/Radio.module.css",
    "content": ".Root {\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n  gap: 0.25rem;\n  color: black;\n}\n\n.Caption {\n  font-weight: 500;\n}\n\n.Label {\n  display: flex;\n  align-items: center;\n  gap: 0.5rem;\n}\n\n.RadioRoot {\n  display: flex;\n  width: 1.25rem;\n  height: 1.25rem;\n  align-items: center;\n  justify-content: center;\n  border-radius: 9999px;\n  outline: 0;\n}\n\n.RadioRoot[data-unchecked] {\n  border: 1px solid lightgray;\n}\n\n.RadioRoot[data-checked] {\n  background: black;\n}\n\n.RadioRoot:focus-visible {\n  outline: 2px solid darkblue;\n  outline-offset: 2px;\n}\n\n.Indicator {\n  display: flex;\n}\n\n.Indicator::before {\n  content: '';\n  width: 0.5rem;\n  height: 0.5rem;\n  border-radius: 9999px;\n  background: #f9fafb;\n}\n\n.RadioRoot[data-unchecked] .Indicator {\n  display: none;\n}\n"
  },
  {
    "path": "test/e2e/fixtures/Radio.tsx",
    "content": "import * as React from 'react';\nimport { Radio } from '@base-ui/react/radio';\nimport { RadioGroup } from '@base-ui/react/radio-group';\nimport styles from './Radio.module.css';\n\nexport default function ExampleRadioGroup() {\n  return (\n    <RadioGroup aria-labelledby=\"apples-caption\" defaultValue=\"fuji-apple\" className={styles.Root}>\n      <div className={styles.Caption} id=\"apples-caption\">\n        Best apple\n      </div>\n\n      <label className={styles.Label}>\n        <Radio.Root data-testid=\"one\" value=\"fuji-apple\" className={styles.RadioRoot}>\n          <Radio.Indicator className={styles.Indicator} />\n        </Radio.Root>\n        Fuji\n      </label>\n\n      <label className={styles.Label}>\n        <Radio.Root data-testid=\"two\" value=\"gala-apple\" className={styles.RadioRoot}>\n          <Radio.Indicator className={styles.Indicator} />\n        </Radio.Root>\n        Gala\n      </label>\n\n      <label className={styles.Label}>\n        <Radio.Root data-testid=\"three\" value=\"granny-smith-apple\" className={styles.RadioRoot}>\n          <Radio.Indicator className={styles.Indicator} />\n        </Radio.Root>\n        Granny Smith\n      </label>\n    </RadioGroup>\n  );\n}\n"
  },
  {
    "path": "test/e2e/fixtures/field/validate-on-change/Input.module.css",
    "content": ".Root {\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n  gap: 0.25rem;\n}\n\n.Control {\n  width: 100%;\n  height: 2.5rem;\n  padding-left: 0.875rem;\n  border: 1px solid #e5e7eb;\n  border-radius: 0.375rem;\n  color: #111827;\n  font-size: 1rem;\n}\n\n.Control:focus {\n  outline: 2px solid #1e40af;\n  outline-offset: -1px;\n}\n\n.Error {\n  color: #991b1b;\n  font-size: 0.875rem;\n}\n"
  },
  {
    "path": "test/e2e/fixtures/field/validate-on-change/Input.tsx",
    "content": "import * as React from 'react';\nimport { Field } from '@base-ui/react/field';\nimport styles from './Input.module.css';\n\nexport default function InputValidateOnChange() {\n  return (\n    <Field.Root\n      validationMode=\"onChange\"\n      validate={(val) => (val === 'abcd' ? 'custom error' : null)}\n      className={styles.Root}\n    >\n      <Field.Control required minLength={3} defaultValue=\"\" className={styles.Control} />\n      <Field.Error data-testid=\"error\" className={styles.Error} match=\"valueMissing\">\n        valueMissing error\n      </Field.Error>\n      <Field.Error data-testid=\"error\" className={styles.Error} match=\"tooShort\">\n        tooShort error\n      </Field.Error>\n      <Field.Error data-testid=\"error\" className={styles.Error} match=\"customError\" />\n    </Field.Root>\n  );\n}\n"
  },
  {
    "path": "test/e2e/fixtures/field/validate-on-change/Select.module.css",
    "content": ".Root {\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n  gap: 0.25rem;\n}\n\n.Trigger {\n  display: flex;\n  height: 2.5rem;\n  min-width: 9rem;\n  align-items: center;\n  justify-content: space-between;\n  gap: 0.75rem;\n  padding-right: 0.75rem;\n  padding-left: 0.875rem;\n  border: 1px solid #e5e7eb;\n  border-radius: 0.375rem;\n  color: #111827;\n  font-size: 1rem;\n  user-select: none;\n  cursor: default;\n}\n\n.Trigger:hover,\n.Trigger[data-popup-open] {\n  background: #f3f4f6;\n}\n\n.Trigger:focus-visible {\n  outline: 2px solid #1e40af;\n  outline-offset: -1px;\n}\n\n.Positioner {\n  z-index: 10;\n  outline: none;\n  user-select: none;\n}\n\n.Popup {\n  transform-origin: var(--transform-origin);\n  border: 1px solid #e5e7eb;\n  border-radius: 0.375rem;\n  background: canvas;\n  color: #111827;\n  background-clip: padding-box;\n  box-shadow: 0 10px 15px -3px rgb(229 231 235 / 70%);\n  transition:\n    transform 120ms ease,\n    opacity 120ms ease;\n}\n\n.Popup[data-starting-style],\n.Popup[data-ending-style] {\n  transform: scale(0.9);\n  opacity: 0;\n}\n\n.Popup[data-side='none'][data-starting-style],\n.Popup[data-side='none'][data-ending-style] {\n  transform: scale(1);\n  opacity: 1;\n  transition: none;\n}\n\n.ScrollArrow {\n  position: relative;\n  z-index: 1;\n  display: flex;\n  width: 100%;\n  height: 1rem;\n  align-items: center;\n  justify-content: center;\n  border-radius: 0.375rem;\n  background: canvas;\n  font-size: 0.75rem;\n  text-align: center;\n  cursor: default;\n}\n\n.ScrollArrow::before {\n  content: '';\n  position: absolute;\n  left: 0;\n  width: 100%;\n  height: 100%;\n}\n\n.ScrollUp {\n  top: 0;\n}\n\n.ScrollUp[data-side='none']::before {\n  top: -100%;\n}\n\n.ScrollDown {\n  bottom: 0;\n}\n\n.ScrollDown[data-side='none']::before {\n  bottom: -100%;\n}\n\n.List {\n  position: relative;\n  overflow-y: auto;\n  max-height: var(--available-height);\n  padding-top: 0.25rem;\n  padding-bottom: 0.25rem;\n  scroll-padding-top: 1.5rem;\n  scroll-padding-bottom: 1.5rem;\n}\n\n.Item {\n  display: flex;\n  min-width: var(--anchor-width);\n  align-items: center;\n  gap: 0.5rem;\n  padding: 0.5rem 1rem 0.5rem 0.625rem;\n  font-size: 0.875rem;\n  line-height: 1rem;\n  outline: none;\n  user-select: none;\n  cursor: default;\n}\n\n.Popup[data-side='none'] .Item {\n  min-width: calc(var(--anchor-width) + 1rem);\n  padding-right: 3rem;\n  font-size: 1rem;\n  line-height: 1rem;\n}\n\n.Item[data-highlighted] {\n  position: relative;\n  z-index: 0;\n  color: #f9fafb;\n}\n\n.Item[data-highlighted]::before {\n  content: '';\n  position: absolute;\n  inset: 0 0.25rem;\n  z-index: -1;\n  border-radius: 0.125rem;\n  background: #111827;\n}\n\n@media (pointer: coarse) {\n  .Item {\n    padding-top: 0.625rem;\n    padding-bottom: 0.625rem;\n    font-size: 0.925rem;\n  }\n}\n\n.Error {\n  color: #991b1b;\n  font-size: 0.875rem;\n}\n"
  },
  {
    "path": "test/e2e/fixtures/field/validate-on-change/Select.tsx",
    "content": "import * as React from 'react';\nimport { Field } from '@base-ui/react/field';\nimport { Select } from '@base-ui/react/select';\nimport styles from './Select.module.css';\n\nconst items = [\n  { label: 'select', value: null },\n  { label: 'one', value: 'one' },\n  { label: 'two', value: 'two' },\n  { label: 'three', value: 'three' },\n  { label: 'four', value: 'four' },\n];\n\nexport default function SelectValidateOnChange() {\n  return (\n    <Field.Root\n      validationMode=\"onChange\"\n      validate={(val) => {\n        if (val === 'one') {\n          return 'error one';\n        }\n\n        if (val === 'three') {\n          return 'error three';\n        }\n        return null;\n      }}\n      className={styles.Root}\n    >\n      <Select.Root items={items} required>\n        <Select.Trigger className={styles.Trigger}>\n          <Select.Value />\n        </Select.Trigger>\n        <Select.Portal>\n          <Select.Positioner className={styles.Positioner} sideOffset={8}>\n            <Select.Popup className={styles.Popup}>\n              <Select.ScrollUpArrow className={`${styles.ScrollArrow} ${styles.ScrollUp}`} />\n              <Select.List className={styles.List}>\n                {items.map(({ label, value }) => (\n                  <Select.Item key={label} value={value} className={styles.Item}>\n                    <Select.ItemText>{label}</Select.ItemText>\n                  </Select.Item>\n                ))}\n              </Select.List>\n              <Select.ScrollDownArrow className={`${styles.ScrollArrow} ${styles.ScrollDown}`} />\n            </Select.Popup>\n          </Select.Positioner>\n        </Select.Portal>\n      </Select.Root>\n      <Field.Error data-testid=\"error\" className={styles.Error} match=\"valueMissing\">\n        valueMissing error\n      </Field.Error>\n      <Field.Error data-testid=\"error\" className={styles.Error} match=\"customError\" />\n    </Field.Root>\n  );\n}\n"
  },
  {
    "path": "test/e2e/fixtures/menu/LinkItemNavigation.module.css",
    "content": ".Page {\n  padding: 1rem;\n}\n\n.Heading {\n  margin-bottom: 1rem;\n  font-size: 1.5rem;\n  font-weight: 700;\n}\n\n.Trigger {\n  border: none;\n  border-radius: 0.25rem;\n  padding: 0.5rem 1rem;\n  background: #f9fafb;\n  color: #111827;\n}\n\n.Trigger:hover {\n  background: #f3f4f6;\n}\n\n.Popup {\n  width: 12rem;\n  padding: 0.25rem;\n  border-radius: 0.25rem;\n  background: canvas;\n  box-shadow: 0 10px 15px -3px rgb(229 231 235 / 70%);\n}\n\n.LinkItem {\n  display: block;\n  border-radius: 0.25rem;\n  padding: 0.5rem 0.75rem;\n  color: #111827;\n  text-decoration: none;\n}\n\n.LinkItem:hover {\n  background: #f3f4f6;\n}\n\n.LinkItem:focus-visible {\n  outline: 2px solid #1e40af;\n}\n\n.LinkItem[data-highlighted] {\n  background: #111827;\n  color: #f9fafb;\n}\n"
  },
  {
    "path": "test/e2e/fixtures/menu/LinkItemNavigation.tsx",
    "content": "import { Menu } from '@base-ui/react/menu';\nimport styles from './LinkItemNavigation.module.css';\n\nexport default function MenuLinkItemNavigation() {\n  return (\n    <div className={styles.Page}>\n      <h1 data-testid=\"page-heading\" className={styles.Heading}>\n        Menu with Link Items\n      </h1>\n\n      <Menu.Root>\n        <Menu.Trigger data-testid=\"menu-trigger\" className={styles.Trigger}>\n          Open Menu\n        </Menu.Trigger>\n\n        <Menu.Portal>\n          <Menu.Positioner>\n            <Menu.Popup className={styles.Popup}>\n              <Menu.LinkItem\n                data-testid=\"link-one\"\n                href=\"/e2e-fixtures/menu/PageOne\"\n                className={styles.LinkItem}\n              >\n                Page one\n              </Menu.LinkItem>\n\n              <Menu.LinkItem\n                data-testid=\"link-two\"\n                href=\"/e2e-fixtures/menu/PageTwo\"\n                className={styles.LinkItem}\n              >\n                Page two\n              </Menu.LinkItem>\n            </Menu.Popup>\n          </Menu.Positioner>\n        </Menu.Portal>\n      </Menu.Root>\n    </div>\n  );\n}\n"
  },
  {
    "path": "test/e2e/fixtures/menu/PageOne.tsx",
    "content": "export default function PageOne() {\n  return <div data-testid=\"test-page\">Page one</div>;\n}\n"
  },
  {
    "path": "test/e2e/fixtures/menu/PageTwo.tsx",
    "content": "export default function PageTwo() {\n  return <div data-testid=\"test-page\">Page two</div>;\n}\n"
  },
  {
    "path": "test/e2e/fixtures/menu/ReactRouterLinkItemNavigation.tsx",
    "content": "import { Menu } from '@base-ui/react/menu';\nimport { Link } from 'react-router';\nimport styles from './LinkItemNavigation.module.css';\n\nexport default function ReactRouterLinkItemNavigation() {\n  return (\n    <div className={styles.Page}>\n      <h1 data-testid=\"page-heading\" className={styles.Heading}>\n        Menu with React Router Link Items\n      </h1>\n\n      <Menu.Root>\n        <Menu.Trigger data-testid=\"menu-trigger\" className={styles.Trigger}>\n          Open Menu\n        </Menu.Trigger>\n\n        <Menu.Portal>\n          <Menu.Positioner>\n            <Menu.Popup className={styles.Popup}>\n              <Menu.LinkItem\n                data-testid=\"link-one\"\n                href=\"/e2e-fixtures/menu/PageOne\"\n                render={<Link to=\"/e2e-fixtures/menu/PageOne\" />}\n                className={styles.LinkItem}\n              >\n                Page one\n              </Menu.LinkItem>\n\n              <Menu.LinkItem\n                data-testid=\"link-two\"\n                href=\"/e2e-fixtures/menu/PageTwo\"\n                render={<Link to=\"/e2e-fixtures/menu/PageTwo\" />}\n                className={styles.LinkItem}\n              >\n                Page two\n              </Menu.LinkItem>\n            </Menu.Popup>\n          </Menu.Positioner>\n        </Menu.Portal>\n      </Menu.Root>\n    </div>\n  );\n}\n"
  },
  {
    "path": "test/e2e/fixtures/slider/Inset.module.css",
    "content": ".Control {\n  position: relative;\n  display: flex;\n  width: 120px;\n  height: 20px;\n  align-items: center;\n  background: #e5e7eb;\n  touch-action: none;\n  user-select: none;\n}\n\n.Thumb {\n  width: 20px;\n  height: 20px;\n  border-radius: 9999px;\n  outline: 1px solid #d1d5db;\n  background: red;\n  user-select: none;\n}\n\n.Thumb:has(:focus-visible) {\n  outline: 2px solid #1e40af;\n}\n"
  },
  {
    "path": "test/e2e/fixtures/slider/Inset.tsx",
    "content": "import { Slider } from '@base-ui/react/slider';\nimport styles from './Inset.module.css';\n\nexport default function InsetSlider() {\n  return (\n    <Slider.Root thumbAlignment=\"edge\" defaultValue={30}>\n      <Slider.Control className={styles.Control}>\n        <Slider.Thumb data-testid=\"thumb\" className={styles.Thumb} />\n      </Slider.Control>\n      <Slider.Value data-testid=\"output\" />\n    </Slider.Root>\n  );\n}\n"
  },
  {
    "path": "test/e2e/fixtures/slider/Range.module.css",
    "content": ".Control {\n  position: relative;\n  display: flex;\n  width: 100px;\n  height: 20px;\n  align-items: center;\n  background: #e5e7eb;\n  touch-action: none;\n  user-select: none;\n}\n\n.ThumbRed {\n  width: 20px;\n  height: 20px;\n  border-radius: 9999px;\n  outline: 1px solid #d1d5db;\n  background: red;\n  user-select: none;\n}\n\n.ThumbBlue {\n  width: 20px;\n  height: 20px;\n  border-radius: 9999px;\n  outline: 1px solid #d1d5db;\n  background: blue;\n  user-select: none;\n}\n\n.ThumbRed:has(:focus-visible),\n.ThumbBlue:has(:focus-visible) {\n  outline: 2px solid #1e40af;\n}\n"
  },
  {
    "path": "test/e2e/fixtures/slider/Range.tsx",
    "content": "import * as React from 'react';\nimport { Slider } from '@base-ui/react/slider';\nimport styles from './Range.module.css';\n\nexport default function RangeSlider() {\n  return (\n    <Slider.Root defaultValue={[25, 30]}>\n      <Slider.Control className={styles.Control}>\n        <Slider.Thumb index={0} className={styles.ThumbRed} />\n        <Slider.Thumb index={1} className={styles.ThumbBlue} />\n      </Slider.Control>\n      <Slider.Value data-testid=\"output\" />\n    </Slider.Root>\n  );\n}\n"
  },
  {
    "path": "test/e2e/fixtures/slider/RangeSliderMax.module.css",
    "content": ".Control {\n  position: relative;\n  display: flex;\n  width: 100px;\n  height: 20px;\n  align-items: center;\n  background: #e5e7eb;\n  touch-action: none;\n  user-select: none;\n}\n\n.ThumbRed {\n  width: 20px;\n  height: 20px;\n  border-radius: 9999px;\n  outline: 1px solid #d1d5db;\n  background: red;\n  user-select: none;\n}\n\n.ThumbBlue {\n  width: 20px;\n  height: 20px;\n  border-radius: 9999px;\n  outline: 1px solid #d1d5db;\n  background: blue;\n  user-select: none;\n}\n\n.ThumbRed:has(:focus-visible),\n.ThumbBlue:has(:focus-visible) {\n  outline: 2px solid #1e40af;\n}\n"
  },
  {
    "path": "test/e2e/fixtures/slider/RangeSliderMax.tsx",
    "content": "import * as React from 'react';\nimport { Slider } from '@base-ui/react/slider';\nimport styles from './RangeSliderMax.module.css';\n\nexport default function RangeSliderMax() {\n  return (\n    <Slider.Root defaultValue={[100, 100]}>\n      <Slider.Control className={styles.Control}>\n        <Slider.Thumb index={0} className={styles.ThumbRed} />\n        <Slider.Thumb index={1} className={styles.ThumbBlue} />\n      </Slider.Control>\n      <Slider.Value data-testid=\"output\" />\n    </Slider.Root>\n  );\n}\n"
  },
  {
    "path": "test/e2e/index.html",
    "content": "<!doctype html>\n<html>\n  <head>\n    <title>Playwright end-to-end test</title>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"initial-scale=1, width=device-width\" />\n    <style>\n      html {\n        color-scheme: light dark;\n      }\n      body {\n        background-color: var(--color-background, white);\n        color: var(--color-foreground, black);\n      }\n    </style>\n  </head>\n  <body>\n    <div id=\"react-root\"></div>\n    <script type=\"module\" src=\"main.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "test/e2e/index.test.ts",
    "content": "import { describe, it, beforeAll, afterAll, expect } from 'vitest';\nimport { chromium, expect, Page, Browser } from '@playwright/test';\nimport '@mui/internal-test-utils/initPlaywrightMatchers';\n\nconst BASE_URL = 'http://localhost:5173';\n\nfunction delay(duration: number): Promise<void> {\n  return new Promise<void>((resolve) => {\n    setTimeout(() => {\n      resolve();\n    }, duration);\n  });\n}\n\n/**\n * Attempts page.goto with retries\n *\n * @remarks The server and runner can be started up simultaneously\n * @param page\n * @param url\n */\nasync function attemptGoto(page: Page, url: string): Promise<boolean> {\n  const maxAttempts = 10;\n  const retryTimeoutMS = 250;\n\n  let didNavigate = false;\n  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {\n    try {\n      // eslint-disable-next-line no-await-in-loop\n      await page.goto(url);\n      didNavigate = true;\n    } catch (error) {\n      // eslint-disable-next-line no-await-in-loop\n      await delay(retryTimeoutMS);\n    }\n  }\n\n  return didNavigate;\n}\n\ndescribe('e2e', () => {\n  let browser: Browser;\n  let page: Page;\n\n  async function renderFixture(fixturePath: string) {\n    await page.goto(`${BASE_URL}/e2e-fixtures/${fixturePath}#no-dev`);\n    await page.waitForSelector('[data-testid=\"testcase\"]:not([aria-busy=\"true\"])');\n  }\n\n  beforeAll(async function beforeHook() {\n    browser = await chromium.launch({\n      headless: true,\n    });\n    page = await browser.newPage();\n    const isServerRunning = await attemptGoto(page, `${BASE_URL}#no-dev`);\n    if (!isServerRunning) {\n      throw new Error(\n        `Unable to navigate to ${BASE_URL} after multiple attempts. Did you forget to run \\`pnpm test:e2e:server\\` and \\`pnpm test:e2e:build\\`?`,\n      );\n    }\n  }, 20000);\n\n  afterAll(async () => {\n    await browser.close();\n  });\n\n  describe('<Field />', () => {\n    describe('validationMode=onChange', () => {\n      it('<Field.Control />', async () => {\n        await renderFixture('field/validate-on-change/Input');\n\n        const valueMissingError = page.getByText('valueMissing error');\n        const tooShortError = page.getByText('tooShort error');\n        const customError = page.getByText('custom error');\n\n        await expect(valueMissingError).toBeHidden();\n        await expect(tooShortError).toBeHidden();\n        await expect(customError).toBeHidden();\n\n        const input = page.getByRole('textbox');\n\n        await input.press('a');\n        await expect(tooShortError).toBeVisible();\n\n        // clear the input\n        await input.press('Backspace');\n        await expect(valueMissingError).toBeVisible();\n\n        await input.pressSequentially('abc');\n        await expect(input).toHaveValue('abc');\n        await expect(valueMissingError).toBeHidden();\n        await expect(tooShortError).toBeHidden();\n        await expect(customError).toBeHidden();\n\n        await input.press('d');\n        await expect(input).toHaveValue('abcd');\n        await expect(customError).toBeVisible();\n\n        await input.press('Backspace');\n        await expect(input).toHaveValue('abc');\n        await expect(valueMissingError).toBeHidden();\n        await expect(tooShortError).toBeHidden();\n        await expect(customError).toBeHidden();\n\n        await input.press('Backspace');\n        await expect(input).toHaveValue('ab');\n        await expect(tooShortError).toBeVisible();\n\n        await input.press('Backspace');\n        await input.press('Backspace');\n        await expect(input).toHaveValue('');\n        await expect(valueMissingError).toBeVisible();\n      });\n\n      it('<Select />', async () => {\n        // options one & three returns errors\n        // options two and four are valid\n        // the field is required\n        await renderFixture('field/validate-on-change/Select');\n\n        const valueMissingError = page.getByText('valueMissing error');\n        const errorOne = page.getByText('error one');\n        const errorThree = page.getByText('error three');\n\n        await expect(valueMissingError).toBeHidden();\n        await expect(errorOne).toBeHidden();\n        await expect(errorThree).toBeHidden();\n\n        const trigger = await page.getByRole('combobox');\n        await expect(trigger).toHaveText('select');\n\n        const options = page.getByRole('option');\n\n        await trigger.click();\n        await options.filter({ hasText: 'one' }).click();\n        await expect(trigger).toHaveText('one');\n        await expect(errorOne).toBeVisible();\n\n        await trigger.click();\n        await options.filter({ hasText: 'two' }).click();\n        await expect(trigger).toHaveText('two');\n        await expect(valueMissingError).toBeHidden();\n        await expect(errorOne).toBeHidden();\n        await expect(errorThree).toBeHidden();\n\n        await trigger.click();\n        // clear the value\n        await options.filter({ hasText: 'select' }).click();\n        await expect(trigger).toHaveText('select');\n        await expect(valueMissingError).toBeVisible();\n\n        await trigger.click();\n        await options.filter({ hasText: 'three' }).click();\n        await expect(trigger).toHaveText('three');\n        await expect(errorThree).toBeVisible();\n\n        await trigger.click();\n        await options.filter({ hasText: 'four' }).click();\n        await expect(trigger).toHaveText('four');\n        await expect(valueMissingError).toBeHidden();\n        await expect(errorOne).toBeHidden();\n        await expect(errorThree).toBeHidden();\n      });\n    });\n  });\n\n  describe('<Radio />', () => {\n    it('loops focus by default', async () => {\n      await renderFixture('Radio');\n\n      await page.keyboard.press('Tab');\n      await expect(page.getByTestId('one')).toBeFocused();\n\n      await page.keyboard.press('ArrowRight');\n      await expect(page.getByTestId('two')).toBeFocused();\n\n      await page.keyboard.press('ArrowLeft');\n      await expect(page.getByTestId('one')).toBeFocused();\n\n      await page.keyboard.press('ArrowLeft');\n      await expect(page.getByTestId('three')).toBeFocused();\n    });\n  });\n\n  describe('<Slider />', () => {\n    it('overlapping thumbs', async () => {\n      await renderFixture('slider/Range');\n\n      // mouse down at the center of the lower thumb but the upper thumb\n      // is moved due to overlap\n      await page.mouse.move(25, 10);\n      await page.mouse.down();\n      await page.mouse.move(100, 10);\n      await page.mouse.up();\n\n      await expect(page.getByRole('status')).toHaveText('25 – 100');\n    });\n\n    it('overlapping thumbs at max', async () => {\n      await renderFixture('slider/RangeSliderMax');\n\n      // both thumbs are at max with the upper thumb completely covering the\n      // lower one; the lower one will be moved by the pointer instead so the\n      // slider doesn't get stuck\n      await page.mouse.move(100, 10);\n      await page.mouse.down();\n      await page.mouse.move(50, 10);\n      await page.mouse.up();\n\n      await expect(page.getByRole('status')).toHaveText('50 – 100');\n    });\n\n    it('inset thumbs', async () => {\n      await renderFixture('slider/Inset');\n      await expect(page.getByRole('status')).toHaveText('30');\n\n      // click the left inset offset region\n      await page.mouse.click(10, 10);\n      await expect(page.getByRole('status')).toHaveText('0');\n      // click the right inset offset region\n      await page.mouse.click(110, 10);\n      await expect(page.getByRole('status')).toHaveText('100');\n      // drag from the center of the thumb\n      await page.mouse.move(110, 10);\n      await page.mouse.down();\n      await page.mouse.move(90, 10);\n      await page.mouse.up();\n      await expect(page.getByRole('status')).toHaveText('80');\n    });\n  });\n\n  describe('<Menu />', () => {\n    describe('<Menu.LinkItem />', () => {\n      it('navigates on click', async () => {\n        await renderFixture('menu/LinkItemNavigation');\n\n        const trigger = page.getByTestId('menu-trigger');\n        await trigger.click();\n\n        const linkOne = page.getByTestId('link-one');\n        await linkOne.click();\n\n        await expect(page).toHaveURL(/\\/e2e-fixtures\\/menu\\/PageOne/);\n        await expect(page.getByTestId('test-page')).toHaveText('Page one');\n\n        await page.goBack();\n        await expect(page.getByTestId('page-heading')).toHaveText('Menu with Link Items');\n\n        await trigger.click();\n        const linkTwo = page.getByTestId('link-two');\n        await linkTwo.click();\n\n        await expect(page).toHaveURL(/\\/e2e-fixtures\\/menu\\/PageTwo/);\n        await expect(page.getByTestId('test-page')).toHaveText('Page two');\n      });\n\n      it('navigates on Enter key press', async () => {\n        await renderFixture('menu/LinkItemNavigation');\n\n        await page.keyboard.press('Tab');\n        await page.keyboard.press('Enter');\n        // first item (page one) is initially highlighted\n        await page.keyboard.press('ArrowDown');\n        await page.keyboard.press('Enter');\n\n        await expect(page).toHaveURL(/\\/e2e-fixtures\\/menu\\/PageTwo/);\n        await expect(page.getByTestId('test-page')).toHaveText('Page two');\n      });\n\n      it('navigates when rendering React Router Link component', async () => {\n        await renderFixture('menu/ReactRouterLinkItemNavigation');\n\n        const trigger = page.getByTestId('menu-trigger');\n        await trigger.click();\n\n        const linkOne = page.getByTestId('link-one');\n        await linkOne.click();\n\n        await expect(page).toHaveURL(/\\/e2e-fixtures\\/menu\\/PageOne/);\n        await expect(page.getByTestId('test-page')).toHaveText('Page one');\n\n        await page.goBack();\n        await expect(page.getByTestId('page-heading')).toHaveText(\n          'Menu with React Router Link Items',\n        );\n\n        await trigger.click();\n        const linkTwo = page.getByTestId('link-two');\n        await linkTwo.click();\n\n        await expect(page).toHaveURL(/\\/e2e-fixtures\\/menu\\/PageTwo/);\n        await expect(page.getByTestId('test-page')).toHaveText('Page two');\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "test/e2e/main.tsx",
    "content": "import * as React from 'react';\nimport * as ReactDOMClient from 'react-dom/client';\nimport { BrowserRouter as Router, Routes, Route, Link } from 'react-router';\nimport * as DomTestingLibrary from '@testing-library/dom';\nimport TestViewer from './TestViewer';\nimport 'docs/src/css/index.css';\n\ninterface Fixture {\n  Component: React.ComponentType<any>;\n  name: string;\n  path: string;\n  suite: string;\n}\n\nconst globbedFixtures = import.meta.glob<{ default: React.ComponentType<unknown> }>(\n  './fixtures/**/*.{js,jsx,ts,tsx}',\n  {\n    eager: true,\n  },\n);\n\nconst fixtures: Fixture[] = [];\n\nfor (const path in globbedFixtures) {\n  const [suite, name] = path\n    .replace('./', '')\n    .replace(/\\.\\w+$/, '')\n    .split('/');\n\n  fixtures.push({\n    path,\n    suite: `e2e-${suite}`,\n    name,\n    Component: globbedFixtures[path].default,\n  });\n}\n\nfunction App() {\n  function computeIsDev() {\n    if (window.location.hash === '#dev') {\n      return true;\n    }\n    if (window.location.hash === '#no-dev') {\n      return false;\n    }\n    return process.env.NODE_ENV === 'development';\n  }\n  const [isDev, setDev] = React.useState(computeIsDev);\n  React.useEffect(() => {\n    function handleHashChange() {\n      setDev(computeIsDev());\n    }\n    window.addEventListener('hashchange', handleHashChange);\n\n    return () => {\n      window.removeEventListener('hashchange', handleHashChange);\n    };\n  }, []);\n\n  function computePath(fixture: Fixture) {\n    return `/${fixture.suite}/${fixture.path.slice(11, -4)}`;\n  }\n\n  return (\n    <Router>\n      <Routes>\n        {fixtures.map((fixture) => {\n          const path = computePath(fixture);\n          const FixtureComponent = fixture.Component;\n          if (FixtureComponent === undefined) {\n            console.warn('Missing `Component` ', fixture);\n            return null;\n          }\n\n          return (\n            <Route\n              key={path}\n              path={path}\n              element={\n                <TestViewer>\n                  <FixtureComponent />\n                </TestViewer>\n              }\n            />\n          );\n        })}\n      </Routes>\n      <div hidden={!isDev}>\n        <p>\n          Devtools can be enabled by appending <code>#dev</code> in the addressbar or disabled by\n          appending <code>#no-dev</code>.\n        </p>\n        <a href=\"#no-dev\">Hide devtools</a>\n        <details>\n          <summary id=\"my-test-summary\">nav for all tests</summary>\n          <nav id=\"tests\">\n            <ol>\n              {fixtures.map((test) => {\n                const path = computePath(test);\n                return (\n                  <li key={path}>\n                    <Link to={path}>{path}</Link>\n                  </li>\n                );\n              })}\n            </ol>\n          </nav>\n        </details>\n      </div>\n    </Router>\n  );\n}\n\nconst container = document.getElementById('react-root');\nconst children = <App />;\n\nif (container != null) {\n  const reactRoot = ReactDOMClient.createRoot(container);\n  reactRoot.render(children);\n}\n\nwindow.DomTestingLibrary = DomTestingLibrary;\nwindow.elementToString = function elementToString(element) {\n  if (\n    element != null &&\n    (element.nodeType === element.ELEMENT_NODE || element.nodeType === element.DOCUMENT_NODE)\n  ) {\n    return window.DomTestingLibrary.prettyDOM(element as Element, undefined, {\n      highlight: true,\n      maxDepth: 1,\n    });\n  }\n  return String(element);\n};\n"
  },
  {
    "path": "test/e2e/postcss.config.js",
    "content": "module.exports = {\n  plugins: {\n    'postcss-import': {},\n    '@tailwindcss/postcss': {},\n    'postcss-custom-media': {},\n  },\n};\n"
  },
  {
    "path": "test/e2e/serve.json",
    "content": "{\n  \"public\": \"build\",\n  \"rewrites\": [{ \"source\": \"**\", \"destination\": \"/index.html\" }]\n}\n"
  },
  {
    "path": "test/e2e/vite.config.mjs",
    "content": "import * as path from 'path';\nimport { defineConfig, mergeConfig } from 'vite';\nimport sharedConfig from '../vite.shared.config.mjs';\n\nexport default mergeConfig(\n  sharedConfig,\n  defineConfig({\n    root: path.join(process.cwd(), 'test/e2e'),\n  }),\n);\n"
  },
  {
    "path": "test/e2e/vitest.config.mts",
    "content": "import { mergeConfig, defineProject } from 'vitest/config';\nimport { playwright } from '@vitest/browser-playwright';\n// eslint-disable-next-line import/no-relative-packages\nimport sharedConfig from '../../vitest.shared.mts';\n\nexport default mergeConfig(\n  sharedConfig,\n  defineProject({\n    test: {\n      environment: 'node',\n      testTimeout: (process.env.CIRCLECI === 'true' ? 4 : 2) * 1000, // Circle CI has low-performance CPUs.\n      browser: {\n        provider: playwright(),\n        enabled: false,\n      },\n      env: {\n        VITEST_ENV: 'node',\n      },\n    },\n  }),\n);\n"
  },
  {
    "path": "test/node-resolution/index.mjs",
    "content": "/* eslint-disable no-console */\nimport * as base from '@base-ui/react';\nimport { Accordion } from '@base-ui/react/accordion';\n\nconsole.assert(base, 'base');\nconsole.assert(Accordion, 'Accordion');\n"
  },
  {
    "path": "test/node-resolution/package.json",
    "content": "{\n  \"name\": \"@base-ui/test-node-resolution\",\n  \"private\": true,\n  \"scripts\": {\n    \"test\": \"node ./index.mjs\"\n  },\n  \"dependencies\": {\n    \"@base-ui/react\": \"workspace:*\",\n    \"@base-ui/utils\": \"workspace:*\"\n  }\n}\n"
  },
  {
    "path": "test/package.json",
    "content": "{\n  \"name\": \"@base-ui/monorepo-tests\",\n  \"private\": true,\n  \"scripts\": {\n    \"typescript\": \"tsc -p tsconfig.json\"\n  },\n  \"devDependencies\": {\n    \"@base-ui/react\": \"workspace:*\",\n    \"@base-ui/utils\": \"workspace:*\",\n    \"@playwright/test\": \"1.58.2\",\n    \"@testing-library/dom\": \"10.4.1\",\n    \"@testing-library/jest-dom\": \"6.9.1\",\n    \"@types/react\": \"19.2.14\",\n    \"@types/react-dom\": \"19.2.3\",\n    \"docs\": \"workspace:^\",\n    \"es-toolkit\": \"1.45.1\",\n    \"is-wsl\": \"3.1.1\",\n    \"react\": \"19.2.4\",\n    \"react-dom\": \"19.2.4\",\n    \"react-router\": \"7.13.1\",\n    \"util\": \"0.12.5\",\n    \"webpack\": \"5.105.4\",\n    \"yargs\": \"18.0.0\"\n  }\n}\n"
  },
  {
    "path": "test/public-types/autocomplete.tsx",
    "content": "import * as React from 'react';\nimport { Autocomplete } from '@base-ui/react/autocomplete';\n\nexport type AutocompleteProps<Value = string> = Autocomplete.Root.Props<Value>;\nexport type AutocompleteActions = Autocomplete.Root.Actions;\nexport type AutocompleteChangeEventDetails = Autocomplete.Root.ChangeEventDetails;\nexport type AutocompleteChangeEventReason = Autocomplete.Root.ChangeEventReason;\nexport type AutocompleteHighlightEventDetails = Autocomplete.Root.HighlightEventDetails;\nexport type AutocompleteHighlightEventReason = Autocomplete.Root.HighlightEventReason;\n\nexport const { useFilter } = Autocomplete;\n\nexport interface SimpleAutocompleteProps extends Omit<Autocomplete.Root.Props<string>, 'children'> {\n  items: readonly string[];\n}\n\nfunction useAutocompleteFilter(\n  options?: Parameters<typeof Autocomplete.useFilter>[0],\n): ReturnType<typeof Autocomplete.useFilter> {\n  const filter = Autocomplete.useFilter(options);\n\n  React.useEffect(() => {\n    filter.contains('value', 'val');\n    filter.startsWith('value', 'v');\n    filter.endsWith('value', 'ue');\n  }, [filter]);\n\n  return filter;\n}\n\nexport const AutocompleteHarness = React.forwardRef<HTMLInputElement, SimpleAutocompleteProps>(\n  function AutocompleteHarness({ items, ...props }, ref) {\n    const actionsRef = React.useRef<AutocompleteActions>({ unmount() {} });\n    const filter = useAutocompleteFilter();\n\n    React.useMemo(() => filter.contains(items[0] ?? '', ''), [filter, items]);\n\n    const handleValueChange = React.useCallback(\n      (value: string, details: AutocompleteChangeEventDetails) => {\n        const reason: AutocompleteChangeEventReason = details.reason;\n        if (reason === 'item-press') {\n          details.cancel();\n        }\n        return value;\n      },\n      [],\n    );\n\n    const handleItemHighlighted = React.useCallback(\n      (_value: string | undefined, details: AutocompleteHighlightEventDetails) => {\n        const reason = details.reason;\n        if (reason === 'keyboard') {\n          void 0;\n        }\n      },\n      [],\n    );\n\n    return (\n      <Autocomplete.Root\n        {...props}\n        actionsRef={actionsRef}\n        onValueChange={handleValueChange}\n        onItemHighlighted={handleItemHighlighted}\n      >\n        <Autocomplete.Input ref={ref} />\n        <Autocomplete.Trigger />\n        <Autocomplete.Positioner>\n          <Autocomplete.Popup>\n            {items.map((item) => (\n              <Autocomplete.Item key={item} value={item}>\n                {item}\n              </Autocomplete.Item>\n            ))}\n          </Autocomplete.Popup>\n        </Autocomplete.Positioner>\n      </Autocomplete.Root>\n    );\n  },\n);\n"
  },
  {
    "path": "test/public-types/checkbox.tsx",
    "content": "import { Checkbox } from '@base-ui/react/checkbox';\n\nexport type CheckboxProps = Omit<Checkbox.Root.Props, 'children'> & {\n  // Add an optional className to mirror original example's augmentation pattern.\n  className?: string;\n};\n"
  },
  {
    "path": "test/public-types/combobox.tsx",
    "content": "import * as React from 'react';\nimport { Combobox } from '@base-ui/react/combobox';\n\nexport type ComboboxProps<\n  Value = string,\n  Multiple extends boolean | undefined = false,\n> = Combobox.Root.Props<Value, Multiple>;\nexport type ComboboxActions = Combobox.Root.Actions;\nexport type ComboboxChangeEventDetails = Combobox.Root.ChangeEventDetails;\nexport type ComboboxChangeEventReason = Combobox.Root.ChangeEventReason;\nexport type ComboboxHighlightEventDetails = Combobox.Root.HighlightEventDetails;\nexport type ComboboxHighlightEventReason = Combobox.Root.HighlightEventReason;\n\nexport const { useFilter } = Combobox;\n\nexport interface SimpleComboboxProps extends Omit<Combobox.Root.Props<string, false>, 'children'> {\n  items: readonly string[];\n}\n\nfunction useComboboxFilter(\n  options?: Parameters<typeof Combobox.useFilter>[0],\n): ReturnType<typeof Combobox.useFilter> {\n  return Combobox.useFilter(options);\n}\n\nexport const ComboboxHarness = React.forwardRef<HTMLInputElement, SimpleComboboxProps>(\n  function ComboboxHarness(props, ref) {\n    const actionsRef = React.useRef<ComboboxActions>({ unmount() {} });\n    const filter = useComboboxFilter({ value: props.value, multiple: false });\n\n    function handleValueChange(value: string | null, details: ComboboxChangeEventDetails) {\n      const reason: ComboboxChangeEventReason = details.reason;\n      if (reason === 'item-press') {\n        details.cancel();\n      }\n      void value;\n    }\n\n    function handleItemHighlighted(\n      value: string | undefined,\n      details: ComboboxHighlightEventDetails,\n    ) {\n      void value;\n      const reason: ComboboxHighlightEventReason = details.reason;\n      if (reason === 'keyboard') {\n        void 0;\n      }\n    }\n\n    return (\n      <Combobox.Root\n        actionsRef={actionsRef}\n        onValueChange={handleValueChange}\n        onItemHighlighted={handleItemHighlighted}\n        filter={filter.contains}\n      >\n        <Combobox.Input ref={ref} />\n        <Combobox.Trigger />\n        <Combobox.Positioner>\n          <Combobox.Popup>\n            {props.items.map((item) => (\n              <Combobox.Item key={item} value={item}>\n                {item}\n              </Combobox.Item>\n            ))}\n          </Combobox.Popup>\n        </Combobox.Positioner>\n      </Combobox.Root>\n    );\n  },\n);\n"
  },
  {
    "path": "test/public-types/index.tsx",
    "content": "import * as React from 'react';\nimport { Menu } from '@base-ui/react/menu';\nimport { Toast } from '@base-ui/react/toast';\nimport type { CheckboxProps } from './checkbox';\nimport type { SeparatorProps } from './separator';\nimport type { SimpleAutocompleteProps } from './autocomplete';\nimport { AutocompleteHarness } from './autocomplete';\nimport type { SimpleComboboxProps } from './combobox';\nimport { ComboboxHarness } from './combobox';\nimport type {\n  MenuRootActions,\n  MenuRootChangeEventDetails,\n  MenuRootChangeEventReason,\n  SimpleMenuProps,\n} from './menu';\nimport type {\n  ToastCreateManagerReturn,\n  ToastManagerReturnValue,\n  ToastManagerAddOptions,\n  ToastManagerPromiseOptions,\n  ToastManagerUpdateOptions,\n  ToastProviderProps,\n} from './toast';\n\nexport const MyCheckbox = React.forwardRef<HTMLDivElement, CheckboxProps>(() => {\n  return <div />;\n});\n\nexport const MySeparator = React.forwardRef<HTMLHRElement, SeparatorProps>(() => {\n  return <hr />;\n});\n\nexport const SimpleCombobox = React.forwardRef<HTMLInputElement, SimpleComboboxProps>(\n  function SimpleCombobox(props, ref) {\n    return <ComboboxHarness ref={ref} {...props} />;\n  },\n);\n\nexport const SimpleAutocomplete = React.forwardRef<HTMLInputElement, SimpleAutocompleteProps>(\n  function SimpleAutocomplete(props, ref) {\n    return <AutocompleteHarness ref={ref} {...props} />;\n  },\n);\n\nexport const SimpleMenu = React.forwardRef<HTMLButtonElement, SimpleMenuProps>(function SimpleMenu(\n  { label = 'Menu', ...rest },\n  ref,\n) {\n  const actionsRef = React.useRef<MenuRootActions>(null);\n\n  function handleMenuOpenChange(\n    open: boolean,\n    details: MenuRootChangeEventDetails,\n  ): MenuRootChangeEventReason | undefined {\n    if (details.reason === 'trigger-hover') {\n      return details.reason;\n    }\n    return undefined;\n  }\n\n  return (\n    <Menu.Root {...rest} actionsRef={actionsRef} onOpenChange={handleMenuOpenChange}>\n      <Menu.Trigger ref={ref}>{label}</Menu.Trigger>\n      <Menu.Positioner>\n        <Menu.Popup>\n          <Menu.Item>Item 1</Menu.Item>\n          <Menu.Item>Item 2</Menu.Item>\n        </Menu.Popup>\n      </Menu.Positioner>\n    </Menu.Root>\n  );\n});\n\nfunction exerciseToastManager(manager: ToastManagerReturnValue) {\n  const id = manager.add({ title: 'Toast', description: 'Created' });\n  manager.update(id, { description: 'Updated description', timeout: 4000 });\n  const promiseOptions: ToastManagerPromiseOptions<string, { info: string }> = {\n    loading: { description: 'Loading…', data: { info: 'loading' } },\n    success: (value) => ({ description: value, data: { info: value } }),\n    error: (error) => ({ description: String(error), data: { info: 'failed' } }),\n  };\n\n  void manager.promise(Promise.resolve('done'), promiseOptions);\n  manager.close(id);\n}\n\nexport function ToastHarness(props: ToastProviderProps) {\n  const externalManagerRef = React.useRef<ToastCreateManagerReturn>(Toast.createToastManager());\n\n  React.useEffect(() => {\n    const manager = externalManagerRef.current;\n    const addOptions: ToastManagerAddOptions<{ details: string }> = {\n      title: 'External toast',\n      description: 'Created via external manager',\n      data: { details: 'payload' },\n    };\n    const toastId = manager.add(addOptions);\n\n    const updateOptions: ToastManagerUpdateOptions<{ details: string }> = {\n      description: 'Updated externally',\n      data: { details: 'updated' },\n    };\n    manager.update(toastId, updateOptions);\n\n    const externalPromiseOptions: ToastManagerPromiseOptions<string, { details: string }> = {\n      loading: { description: 'Loading external…', data: { details: 'loading' } },\n      success: (value) => ({ description: value, data: { details: value } }),\n      error: (error) => ({ description: String(error), data: { details: 'error' } }),\n    };\n    void manager.promise(Promise.resolve('value'), externalPromiseOptions);\n  }, []);\n\n  return (\n    <Toast.Provider {...props} toastManager={externalManagerRef.current}>\n      <Toast.Viewport />\n      <ToastUsage />\n    </Toast.Provider>\n  );\n}\n\nfunction ToastUsage() {\n  const manager = Toast.useToastManager();\n\n  React.useEffect(() => {\n    exerciseToastManager(manager);\n  }, [manager]);\n\n  return null;\n}\n"
  },
  {
    "path": "test/public-types/menu.tsx",
    "content": "import { Menu } from '@base-ui/react/menu';\n\nexport type MenuRootProps = Menu.Root.Props;\nexport type MenuRootActions = Menu.Root.Actions;\nexport type MenuRootChangeEventReason = Menu.Root.ChangeEventReason;\nexport type MenuRootChangeEventDetails = Menu.Root.ChangeEventDetails;\nexport type MenuRootOrientation = Menu.Root.Orientation;\n\nexport interface SimpleMenuProps extends Omit<MenuRootProps, 'children'> {\n  label?: string;\n}\n"
  },
  {
    "path": "test/public-types/package.json",
    "content": "{\n  \"private\": true,\n  \"dependencies\": {\n    \"@base-ui/react\": \"workspace:*\",\n    \"react\": \"19.2.4\"\n  },\n  \"scripts\": {\n    \"release:test\": \"tsc --noEmit\"\n  }\n}\n"
  },
  {
    "path": "test/public-types/separator.tsx",
    "content": "import { Separator } from '@base-ui/react/separator';\n\nexport type SeparatorProps = Omit<Separator.Props, 'children'> & {\n  // Add an optional className to mirror original example's augmentation pattern.\n  className?: string;\n};\n"
  },
  {
    "path": "test/public-types/toast.tsx",
    "content": "import {\n  Toast,\n  type UseToastManagerReturnValue,\n  type ToastManagerAddOptions as BaseToastManagerAddOptions,\n  type ToastManagerPromiseOptions as BaseToastManagerPromiseOptions,\n  type ToastManagerUpdateOptions as BaseToastManagerUpdateOptions,\n} from '@base-ui/react/toast';\n\nexport type ToastProviderProps = Toast.Provider.Props;\nexport type ToastViewportProps = Toast.Viewport.Props;\nexport type ToastRootProps = Toast.Root.Props;\n\nexport type ToastManagerReturnValue = UseToastManagerReturnValue;\nexport type ToastManagerAdd = UseToastManagerReturnValue['add'];\nexport type ToastManagerUpdate = UseToastManagerReturnValue['update'];\nexport type ToastManagerPromise = UseToastManagerReturnValue['promise'];\n\nexport type ToastManagerAddOptions<Data extends object> = BaseToastManagerAddOptions<Data>;\nexport type ToastManagerUpdateOptions<Data extends object> = BaseToastManagerUpdateOptions<Data>;\nexport type ToastManagerPromiseOptions<Value, Data extends object> = BaseToastManagerPromiseOptions<\n  Value,\n  Data\n>;\n\nexport type ToastCreateManagerReturn = ReturnType<typeof Toast.createToastManager>;\n\nexport const { useToastManager, createToastManager } = Toast;\n"
  },
  {
    "path": "test/public-types/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"nodenext\",\n    \"target\": \"es2020\",\n    \"lib\": [\"es2020\", \"dom\"],\n    \"jsx\": \"preserve\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noEmit\": true,\n    \"experimentalDecorators\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"noErrorTruncation\": false,\n    \"allowJs\": true,\n    \"emitDeclarationOnly\": true,\n    \"allowImportingTsExtensions\": true,\n    \"rootDir\": \".\",\n    \"outDir\": \"./build\",\n\n    // Don't touch these settings\n    \"skipLibCheck\": false,\n    \"declaration\": true\n  },\n  \"include\": [\"**/*.ts\", \"**/*.tsx\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "test/public-types/use-render.tsx",
    "content": "import * as React from 'react';\nimport { useRender } from '@base-ui/react/use-render';\n\nexport type UseRenderRenderProp<State = Record<string, unknown>> = useRender.RenderProp<State>;\nexport type UseRenderElementProps<ElementType extends React.ElementType> =\n  useRender.ElementProps<ElementType>;\nexport type UseRenderComponentProps<\n  ElementType extends React.ElementType,\n  State = {},\n  RenderFunctionProps = Record<string, unknown>,\n> = useRender.ComponentProps<ElementType, State, RenderFunctionProps>;\nexport type UseRenderParameters<\n  State,\n  RenderedElementType extends Element,\n  Enabled extends boolean | undefined,\n> = useRender.Parameters<State, RenderedElementType, Enabled>;\nexport type UseRenderReturnValue<Enabled extends boolean | undefined> =\n  useRender.ReturnValue<Enabled>;\n\nexport interface UseRenderHarnessProps {\n  state: { expanded: boolean };\n  enabled?: boolean;\n  render?: useRender.RenderProp<{ expanded: boolean }>;\n}\n\nexport function UseRenderHarness({ state, enabled = true, render }: UseRenderHarnessProps) {\n  const buttonRef = React.useRef<HTMLButtonElement>(null);\n\n  const element = useRender({\n    defaultTagName: 'button',\n    state,\n    enabled,\n    render:\n      render ??\n      ((props, currentState) => (\n        <button type=\"button\" {...props} data-expanded={currentState?.expanded ? '' : undefined} />\n      )),\n    props: {\n      type: 'button',\n      'aria-expanded': state.expanded,\n    },\n    ref: buttonRef,\n  });\n\n  return element;\n}\n\nexport function DisabledUseRenderExample() {\n  const hiddenElement = useRender<{ expanded: boolean }, HTMLDivElement, false>({\n    enabled: false,\n    defaultTagName: 'div',\n  });\n\n  React.useEffect(() => {\n    void hiddenElement;\n  }, [hiddenElement]);\n\n  return null;\n}\n"
  },
  {
    "path": "test/regressions/README.md",
    "content": "# Visual regression testing\n\nVisual regression tests are split into two parts:\n\n1. The rendered UI, or test fixtures\n2. Instrumentation of fixtures with a simple Vite app\n\n## Rendered UI\n\nThe composition of all tests happens in [`./main.tsx`](./main.tsx).\nThe rendered UI is either:\n\n1. located inside a separate file in `./fixtures` and written as a React component.\n\n   Here is an [example](https://github.com/mui/material-ui/blob/814fb60bbd8e500517b2307b6a297a638838ca89/test/regressions/tests/Menu/SimpleMenuList.js#L6-L16) with the `Menu` component.\n\n2. a demo from `docs/src/app/(docs)/react`\n\n   By default all demos are included.\n   We exclude demos if they are redundant or flaky etc.\n   The logic for this exclusion is handled (like the composition) in [`./main.tsx`](./main.tsx)\n\nIf you introduce new behavior, prefer adding a demo to the documentation to solve documentation and testing with one file.\nIf you're adding a new test prefer a new component instead of editing existing files since that might unknowingly alter existing tests.\n\n## Instrumentation\n\n### Manual\n\n`pnpm test:regressions:dev` will build all fixtures and render an overview page that lists all fixtures.\nThis can be used to debug individual fixtures.\nBy default, a devtools-like view is shown that can be disabled by appending `#no-dev` to the URL, for example `http://localhost:5173/docs-components-checkbox-group-demos-hero-tailwind/index.tsx#no-dev` or forced by appending `#dev` to the URL, for example `http://localhost:5173/docs-components-checkbox-group-demos-hero-tailwind/index.tsx#dev`.\n\n### Automatic\n\nWe're using [`playwright`](https://playwright.dev) to iterate over each fixture and take a screenshot.\nIt allows catching regressions like this one:\n\n![before](/test/docs-regressions-before.png)\n![diff](/test/docs-regressions-diff.png)\n\nScreenshots are saved in `./screenshots/$BROWSER_NAME/`.\nEach test tests only a single fixture.\nA fixture can be loaded with `await renderFixture(fixturePath)`, for example `renderFixture('FocusTrap/OpenFocusTrap')`.\n\n## Commands\n\nFor development run `pnpm test:regressions:dev`.\n\n| command                        | description                                                                                                           |\n| :----------------------------- | :-------------------------------------------------------------------------------------------------------------------- |\n| `pnpm test:regressions`        | Full run                                                                                                              |\n| `pnpm test:regressions:dev`    | Prepares the fixtures and runs a Vite dev server                                                                      |\n| `pnpm test:regressions:run`    | Runs the tests (requires `pnpm test:regressions:dev` or `pnpm test:regressions:build`+`pnpm test:regressions:server`) |\n| `pnpm test:regressions:build`  | Builds the Vite bundle for viewing fixtures                                                                           |\n| `pnpm test:regressions:server` | Serves the fixture bundle.                                                                                            |\n"
  },
  {
    "path": "test/regressions/TestViewer.tsx",
    "content": "import * as React from 'react';\n\nfunction TestViewer(props: { children: React.ReactNode }) {\n  const { children } = props;\n\n  // We're simulating `act(() => ReactDOM.render(children))`\n  // In the end children passive effects should've been flushed.\n  // React doesn't have any such guarantee outside of `act()` so we're approximating it.\n  const [ready, setReady] = React.useState(false);\n  React.useEffect(() => {\n    function handleFontsEvent(event: Event) {\n      if (event.type === 'loading') {\n        setReady(false);\n      } else if (event.type === 'loadingdone') {\n        // Don't know if there could be multiple loaded events after we started loading multiple times.\n        // So make sure we're only ready if fonts are actually ready.\n        if (document.fonts.status === 'loaded') {\n          setReady(true);\n        }\n      }\n    }\n\n    document.fonts.addEventListener('loading', handleFontsEvent);\n    document.fonts.addEventListener('loadingdone', handleFontsEvent);\n\n    // In case the child triggered font fetching we're not ready yet.\n    // The fonts event handler will mark the test as ready on `loadingdone`\n    if (document.fonts.status === 'loaded') {\n      setReady(true);\n    }\n\n    return () => {\n      document.fonts.removeEventListener('loading', handleFontsEvent);\n      document.fonts.removeEventListener('loadingdone', handleFontsEvent);\n    };\n  }, []);\n\n  const globalStyles = `\n    html {\n      --webkit-font-smoothing: antialiased;\n      --moz-osx-font-smoothing: grayscale;\n      /* Do the opposite of the docs in order to help catching issues. */\n      box-sizing: content-box;\n    }\n\n    *, *::before, *::after {\n      box-sizing: inherit;\n      /* Disable transitions to avoid flaky screenshots */\n      transition: none !important;\n      animation: none !important;\n    }\n\n    body {\n      margin: 0;\n      overflow-x: hidden;\n    }\n  `;\n\n  return (\n    <React.Fragment>\n      {/* eslint-disable-next-line react/no-danger */}\n      <style dangerouslySetInnerHTML={{ __html: globalStyles }} />\n      <div aria-busy={!ready} data-testid=\"testcase\" style={{ display: 'block', padding: '8px' }}>\n        {children}\n      </div>\n    </React.Fragment>\n  );\n}\n\nexport default TestViewer;\n"
  },
  {
    "path": "test/regressions/fakeDateSetup.ts",
    "content": "// Override Date so that `new Date()` and `Date.now()` return a stable timestamp for Argos.\n// This must be imported before any other module that uses Date.\n//\n// We can't use a `class extends Date` to patch it, because @date-fns/tz's TZDateMini relies on\n// `Object.getOwnPropertyNames(Date.prototype)` returning all native methods (get*, set*) to set up\n// its timezone-aware overrides. A subclass's prototype only has 'constructor' as an own property.\n// Using Reflect.construct preserves the native Date.prototype while correctly forwarding new.target\n// for proper subclass construction (TZDateMini extends Date).\n\n// Calendar PR merge day\nconst fakeNow = new Date('2026-03-13T17:44:00Z').valueOf();\n\nconst OriginalDate = Date;\nconst offset = fakeNow - OriginalDate.now();\n\nfunction FakeDate(...args: any[]): Date | string {\n  if (new.target) {\n    return args.length === 0\n      ? Reflect.construct(OriginalDate, [OriginalDate.now() + offset], new.target)\n      : Reflect.construct(OriginalDate, args, new.target);\n  }\n  return new OriginalDate(OriginalDate.now() + offset).toString();\n}\n\nFakeDate.prototype = OriginalDate.prototype;\nFakeDate.parse = OriginalDate.parse;\nFakeDate.UTC = OriginalDate.UTC;\nFakeDate.now = () => OriginalDate.now() + offset;\n\n(globalThis as any).Date = FakeDate;\n"
  },
  {
    "path": "test/regressions/fixtures/.gitkeep",
    "content": ""
  },
  {
    "path": "test/regressions/fixtures.ts",
    "content": "import * as React from 'react';\n\nexport interface Fixture {\n  Component: React.ComponentType<unknown>;\n  name: string;\n  path: string;\n  suite: string;\n}\n\n// Get all the fixtures specifically written for preventing visual regressions.\nconst globbedRegressionFixtures = import.meta.glob<{ default: React.ComponentType<unknown> }>(\n  './fixtures/**/*.tsx',\n  { eager: true },\n);\n\nconst regressionFixtures: Fixture[] = [];\n\nfor (const path in globbedRegressionFixtures) {\n  const [suite, name] = path\n    .replace(/\\\\/g, '/')\n    .replace('./', '')\n    .replace(/\\.\\w+$/, '')\n    .split('/');\n\n  regressionFixtures.push({\n    path,\n    suite: `regression-${suite}`,\n    name,\n    Component: globbedRegressionFixtures[path].default,\n  });\n}\n\nconst blacklist: (string | RegExp)[] = [];\n\nconst unusedBlacklistPatterns = new Set(blacklist);\n\nfunction excludeDemoFixture(suite: string, name: string, path: string) {\n  const blacklisted = blacklist.some((pattern) => {\n    if (typeof pattern === 'string') {\n      if (pattern === suite) {\n        unusedBlacklistPatterns.delete(pattern);\n\n        return true;\n      }\n      if (pattern === `${suite}/${name}.png`) {\n        unusedBlacklistPatterns.delete(pattern);\n\n        return true;\n      }\n\n      return false;\n    }\n\n    if (pattern.test(suite)) {\n      unusedBlacklistPatterns.delete(pattern);\n      return true;\n    }\n    return false;\n  });\n\n  if (blacklisted) {\n    return true;\n  }\n\n  const pathSegments = path.split('/');\n  if (pathSegments[1] === 'components' && pathSegments.length === 6) {\n    // For demos inside subdirectories under components, include just the entry point - index.js.\n    return pathSegments[5] !== 'index.js';\n  }\n\n  return false;\n}\n\n// Also use all public demos to avoid code duplication.\nconst globbedDemos = import.meta.glob<{ default: React.ComponentType<unknown> }>(\n  // technically it should be 'docs/src/app/\\\\(public\\\\)/\\\\(content\\\\)/react/**/*.tsx' but tinyglobby doesn't resolve this on Windows\n  'docs/src/app/?docs?/react/**/*.tsx',\n  { eager: true },\n);\n\nconst demoFixtures: Fixture[] = [];\n\nfor (const path in globbedDemos) {\n  const [name, ...suiteArray] = path.split('react')[1].split('/').reverse();\n  const suite = `docs-${suiteArray\n    .filter((v) => v)\n    .reverse()\n    .join('-')}`;\n\n  if (!excludeDemoFixture(suite, name, path) && globbedDemos[path].default) {\n    demoFixtures.push({\n      path,\n      suite,\n      name,\n      Component: globbedDemos[path].default,\n    });\n  }\n}\n\nif (unusedBlacklistPatterns.size > 0) {\n  console.warn(\n    `The following patterns are unused:\\n\\n${Array.from(unusedBlacklistPatterns)\n      .map((pattern) => `- ${pattern}`)\n      .join('\\n')}`,\n  );\n}\n\nexport const fixtures: Fixture[] = regressionFixtures.concat(demoFixtures);\n"
  },
  {
    "path": "test/regressions/index.html",
    "content": "<!doctype html>\n<html>\n  <head>\n    <title>Visual regression tests</title>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"initial-scale=1, width=device-width\" />\n    <style>\n      body {\n        background-color: white;\n        font-family: 'Die Grotesk A', system-ui, sans-serif;\n      }\n    </style>\n  </head>\n\n  <body>\n    <div id=\"test-viewer\"></div>\n    <div id=\"react-root\"></div>\n    <script type=\"module\" src=\"main.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "test/regressions/index.test.ts",
    "content": "import { describe, it } from 'vitest';\nimport * as path from 'node:path';\nimport * as fs from 'node:fs/promises';\nimport { chromium, Locator } from '@playwright/test';\n\nconst baseUrl = 'http://localhost:5173';\nconst screenshotDir = path.resolve(__dirname, './screenshots/chrome');\n\nconst browser = await chromium.launch({\n  args: ['--font-render-hinting=none'],\n  // otherwise the loaded google Roboto font isn't applied\n  headless: false,\n});\n// reuse viewport from `vrtest`\n// https://github.com/nathanmarks/vrtest/blob/1185b852a6c1813cedf5d81f6d6843d9a241c1ce/src/server/runner.js#L44\nconst page = await browser.newPage({ viewport: { width: 1000, height: 700 }, timezoneId: 'UTC' });\n\n// Block images since they slow down tests (need download).\n// They're also most likely decorative for documentation demos\nawait page.route(/./, async (route, request) => {\n  const type = await request.resourceType();\n  if (type === 'image') {\n    route.abort();\n  } else {\n    route.continue();\n  }\n});\n\n// Wait for all requests to finish.\n// This should load shared resources such as fonts.\nawait page.goto(`${baseUrl}#no-dev`, { waitUntil: 'networkidle' });\n\n// Simulate portrait mode for date pickers.\n// See `useIsLandscape`.\nawait page.evaluate(() => {\n  Object.defineProperty(window.screen.orientation, 'angle', {\n    get() {\n      return 0;\n    },\n  });\n});\n\nlet routes = await page.$$eval('#tests a', (links: HTMLAnchorElement[]) => {\n  return links.map((link) => link.href);\n});\nroutes = routes.map((route: string) => route.replace(baseUrl, ''));\n\nasync function renderFixture(index: number) {\n  // Use client-side routing which is much faster than full page navigation via page.goto().\n  // Could become an issue with test isolation.\n  // If tests are flaky due to global pollution switch to page.goto(route);\n  // puppeteers built-in click() times out\n  await page.$eval(`#tests li:nth-of-type(${index + 1}) a`, (link: HTMLAnchorElement) => {\n    link.click();\n  });\n  // Move cursor offscreen to not trigger unwanted hover effects.\n  page.mouse.move(0, 0);\n\n  const testcase = page.locator('[data-testid=\"testcase\"]:not([aria-busy=\"true\"])');\n  await testcase.waitFor();\n  return testcase;\n}\n\nasync function takeScreenshot({ testcase, route }: { testcase: Locator; route: string }) {\n  const screenshotPath = path.resolve(screenshotDir, `.${route}.png`);\n  await fs.mkdir(path.dirname(screenshotPath), { recursive: true });\n\n  const explicitScreenshotTarget = await page.$('[data-testid=\"screenshot-target\"]');\n  const screenshotTarget = explicitScreenshotTarget || testcase;\n\n  await screenshotTarget?.screenshot({ path: screenshotPath, type: 'png' });\n}\n\n// prepare screenshots\nawait fs.rm(screenshotDir, { force: true, recursive: true });\n\ndescribe('visual regressions', () => {\n  beforeEach(async () => {\n    await page.evaluate(() => {\n      localStorage.clear();\n    });\n  });\n\n  afterAll(async () => {\n    await browser.close();\n  });\n\n  routes.forEach((route: string, index: number) => {\n    it(\n      `creates screenshots of ${route}`,\n      // With the playwright inspector we might want to call `page.pause` which would lead to a timeout.\n      { timeout: process.env.PWDEBUG ? 0 : 5000 },\n      async () => {\n        const testcase = await renderFixture(index);\n\n        await takeScreenshot({ testcase, route });\n      },\n    );\n  });\n});\n"
  },
  {
    "path": "test/regressions/main.tsx",
    "content": "import './fakeDateSetup';\nimport * as React from 'react';\nimport * as ReactDOMClient from 'react-dom/client';\nimport { BrowserRouter as Router, Routes, Route, Link } from 'react-router';\nimport TestViewer from './TestViewer';\nimport { fixtures, type Fixture } from './fixtures';\nimport 'docs/src/css/index.css';\n\nconst viewerRoot = document.getElementById('test-viewer');\n\nfunction FixtureRenderer({ component: FixtureComponent }: { component: React.ElementType }) {\n  const viewerReactRoot = React.useRef<ReactDOMClient.Root | null>(null);\n\n  React.useLayoutEffect(() => {\n    const renderTimeout = setTimeout(() => {\n      const children = (\n        <TestViewer>\n          <FixtureComponent />\n        </TestViewer>\n      );\n\n      if (viewerReactRoot.current == null && viewerRoot != null) {\n        viewerReactRoot.current = ReactDOMClient.createRoot(viewerRoot);\n      }\n\n      viewerReactRoot.current?.render(children);\n    });\n\n    return () => {\n      clearTimeout(renderTimeout);\n      setTimeout(() => {\n        viewerReactRoot.current?.unmount();\n        viewerReactRoot.current = null;\n      });\n    };\n  }, [FixtureComponent]);\n\n  return null;\n}\n\nfunction App() {\n  function computeIsDev() {\n    if (window.location.hash === '#dev') {\n      return true;\n    }\n    if (window.location.hash === '#no-dev') {\n      return false;\n    }\n    return process.env.NODE_ENV === 'development';\n  }\n  const [isDev, setDev] = React.useState(computeIsDev);\n  React.useEffect(() => {\n    function handleHashChange() {\n      setDev(computeIsDev());\n    }\n    window.addEventListener('hashchange', handleHashChange);\n\n    return () => {\n      window.removeEventListener('hashchange', handleHashChange);\n    };\n  }, []);\n\n  function computePath(fixture: Fixture) {\n    return `/${fixture.suite}/${fixture.name}`;\n  }\n\n  return (\n    <Router>\n      <Routes>\n        {fixtures.map((fixture) => {\n          const path = computePath(fixture);\n          const FixtureComponent = fixture.Component;\n          if (FixtureComponent === undefined) {\n            console.warn('Missing `Component` for ', fixture);\n            return null;\n          }\n\n          return (\n            <Route\n              key={path}\n              path={path}\n              element={<FixtureRenderer component={FixtureComponent} />}\n            />\n          );\n        })}\n      </Routes>\n\n      <div hidden={!isDev}>\n        <p>\n          Devtools can be enabled by appending <code>#dev</code> in the addressbar or disabled by\n          appending <code>#no-dev</code>.\n        </p>\n        <a href=\"#no-dev\">Hide devtools</a>\n        <details>\n          <summary id=\"my-test-summary\">nav for all tests</summary>\n          <nav id=\"tests\">\n            <ol>\n              {fixtures.map((fixture) => {\n                const path = computePath(fixture);\n                return (\n                  <li key={path}>\n                    <Link to={path}>{path}</Link>\n                  </li>\n                );\n              })}\n            </ol>\n          </nav>\n        </details>\n      </div>\n    </Router>\n  );\n}\n\nconst container = document.getElementById('react-root');\nconst children = <App />;\n\nif (container != null) {\n  const reactRoot = ReactDOMClient.createRoot(container);\n  reactRoot.render(children);\n}\n"
  },
  {
    "path": "test/regressions/manual/README.md",
    "content": "# manual visual regression tests\n\nThese are expensive tests that should only be consulted if you suspect that something changed.\nMove the test you want to check inside a temporary folder in `../test` and run the visual regression test suite to get a screenshot.\n"
  },
  {
    "path": "test/regressions/postcss.config.js",
    "content": "module.exports = {\n  plugins: {\n    'postcss-import': {},\n    '@tailwindcss/postcss': {},\n    'postcss-custom-media': {},\n  },\n};\n"
  },
  {
    "path": "test/regressions/serve.json",
    "content": "{\n  \"public\": \"build\",\n  \"rewrites\": [{ \"source\": \"**\", \"destination\": \"/index.html\" }]\n}\n"
  },
  {
    "path": "test/regressions/vite.config.mjs",
    "content": "import * as path from 'path';\nimport { defineConfig, mergeConfig } from 'vite';\nimport sharedConfig from '../vite.shared.config.mjs';\n\nexport default mergeConfig(\n  sharedConfig,\n  defineConfig({\n    root: path.join(process.cwd(), 'test/regressions'),\n  }),\n);\n"
  },
  {
    "path": "test/regressions/vitest.config.mts",
    "content": "import { mergeConfig, defineProject } from 'vitest/config';\nimport { playwright } from '@vitest/browser-playwright';\n// eslint-disable-next-line import/no-relative-packages\nimport sharedConfig from '../../vitest.shared.mts';\n\nexport default mergeConfig(\n  sharedConfig,\n  defineProject({\n    test: {\n      environment: 'node',\n      testTimeout: (process.env.CIRCLECI === 'true' ? 4 : 2) * 1000, // Circle CI has low-performance CPUs.\n      browser: {\n        provider: playwright(),\n        enabled: false,\n        instances: [{ browser: 'chromium' }],\n      },\n      env: {\n        VITEST_ENV: 'node',\n      },\n    },\n  }),\n);\n"
  },
  {
    "path": "test/setupVitest.ts",
    "content": "import { vi } from 'vitest';\nimport setupVitest from '@mui/internal-test-utils/setupVitest';\n// eslint-disable-next-line import/no-relative-packages\nimport '../packages/react/test/addVitestMatchers';\nimport '@testing-library/jest-dom/vitest';\nimport { reset } from '@base-ui/utils/error';\n\ndeclare global {\n  // eslint-disable-next-line vars-on-top\n  var BASE_UI_ANIMATIONS_DISABLED: boolean;\n}\n\nsetupVitest();\n\nafterEach(() => {\n  vi.resetAllMocks();\n  reset();\n});\n\nglobalThis.BASE_UI_ANIMATIONS_DISABLED = true;\n\nif (typeof window !== 'undefined' && window?.navigator?.userAgent?.includes('jsdom')) {\n  globalThis.requestAnimationFrame = (cb) => {\n    setTimeout(() => cb(0), 0);\n    return 0;\n  };\n}\n"
  },
  {
    "path": "test/tsconfig.json",
    "content": "{\n  \"extends\": \"../tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"module\": \"es2022\",\n    \"moduleResolution\": \"bundler\",\n    \"types\": [\"vite/client\", \"vitest/globals\", \"@testing-library/jest-dom\"]\n  },\n  \"include\": [\"e2e/**/*\", \"regressions/**/*\", \"./*.ts\"],\n  \"exclude\": [\"node_modules\", \"build\"]\n}\n"
  },
  {
    "path": "test/vite.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "test/vite.shared.config.mjs",
    "content": "import * as path from 'path';\nimport { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\n\nconst shouldDisableWorkspaceAliases = Boolean(process.env.MUI_DISABLE_WORKSPACE_ALIASES);\n\nexport default defineConfig({\n  mode: process.env.NODE_ENV || 'development',\n  plugins: [react()],\n  resolve: {\n    alias: {\n      ...(shouldDisableWorkspaceAliases\n        ? // TODO Temporal: Remove and revert to `undefined` when calendar is publicly exported\n          {\n            '@base-ui/react/calendar': path.join(process.cwd(), 'packages/react/src/calendar'),\n            '@base-ui/react/localization-provider': path.join(\n              process.cwd(),\n              'packages/react/src/localization-provider',\n            ),\n          }\n        : {\n            '@base-ui/react': path.join(process.cwd(), 'packages/react/src'),\n            '@base-ui/utils': path.join(process.cwd(), 'packages/utils/src'),\n          }),\n      './fonts': path.join(process.cwd(), '/docs/src/css/fonts'),\n      docs: path.join(process.cwd(), '/docs'),\n      stream: null,\n      zlib: null,\n    },\n  },\n  build: { outDir: 'build', chunkSizeWarningLimit: 9999 },\n});\n"
  },
  {
    "path": "tsconfig.base.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"nodenext\",\n    \"target\": \"es2022\",\n    \"lib\": [\"es2022\", \"dom\"],\n    \"jsx\": \"react-jsx\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noEmit\": true,\n    \"experimentalDecorators\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"noErrorTruncation\": false,\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"allowImportingTsExtensions\": true,\n    \"noUncheckedSideEffectImports\": true,\n    \"paths\": {\n      \"@base-ui/react\": [\"./packages/react/src\"],\n      \"@base-ui/react/*\": [\"./packages/react/src/*\"],\n      \"@base-ui/utils/*\": [\"./packages/utils/src/*\"],\n      \"docs/*\": [\"./docs/*\"]\n    }\n  },\n  \"exclude\": [\"**/.*/\", \"**/build\", \"**/build-tests\", \"**/node_modules\", \"docs/export\"]\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"extends\": \"./tsconfig.base.json\",\n  \"files\": [],\n  \"include\": [],\n  \"references\": [\n    {\n      \"path\": \"docs/tsconfig.json\"\n    },\n    {\n      \"path\": \"packages/react/tsconfig.json\"\n    },\n    {\n      \"path\": \"packages/utils/tsconfig.json\"\n    },\n    {\n      \"path\": \"scripts/tsconfig.json\"\n    },\n    {\n      \"path\": \"playground/vite-app/tsconfig.json\"\n    }\n  ]\n}\n"
  },
  {
    "path": "vitest.config.mts",
    "content": "import { resolve, dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { defineConfig } from 'vitest/config';\n\nconst CURRENT_DIR = dirname(fileURLToPath(import.meta.url));\nconst WORKSPACE_ROOT = resolve(CURRENT_DIR, './');\n\nexport default defineConfig({\n  test: {\n    sequence: {\n      hooks: 'list',\n    },\n    projects: [\n      'packages/*/vitest.config.mts',\n      'docs/vitest.config.mts',\n      'test/e2e/vitest.config.mts',\n      'test/regressions/vitest.config.mts',\n    ],\n    reporters: process.env.CI\n      ? ['default', ['junit', { outputFile: './test-results/junit.xml' }]]\n      : ['default'],\n    coverage: {\n      provider: 'istanbul',\n      reporter: [['text', { maxCols: 200 }], 'lcov'],\n      reportsDirectory: resolve(WORKSPACE_ROOT, 'coverage'),\n      include: ['packages/*/src/**/*.ts', 'packages/*/src/**/*.tsx'],\n      exclude: ['**/*.test.{js,ts,tsx}', '**/*.test/*'],\n    },\n  },\n});\n"
  },
  {
    "path": "vitest.shared.mts",
    "content": "import { resolve, dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { type UserWorkspaceConfig } from 'vitest/config';\n// eslint-disable-next-line import/extensions\nimport viteConfig from '@base-ui/monorepo-tests/vite.shared.config.mjs';\nimport { playwright } from '@vitest/browser-playwright';\n\nconst CURRENT_DIR = dirname(fileURLToPath(import.meta.url));\nconst WORKSPACE_ROOT = resolve(CURRENT_DIR, './');\nconst environment = process.env.VITEST_ENV;\n\ntype BrowserModeConfig = (UserWorkspaceConfig['test'] & {})['browser'];\n\nconst supportedBrowsers = new Set(['chromium', 'webkit', 'firefox'] as const);\ntype SupportedBrowser = typeof supportedBrowsers extends Set<infer U> ? U : never;\n\nfunction isSupportedBrowser(env: string | undefined): env is SupportedBrowser {\n  return !!env && (supportedBrowsers as Set<string>).has(env);\n}\n\nfunction getBrowserConfig(): BrowserModeConfig {\n  if (!environment) {\n    return undefined;\n  }\n\n  let instances;\n\n  if (environment === 'all-browsers') {\n    instances = Array.from(supportedBrowsers, (browser) => ({ browser }));\n  } else if (isSupportedBrowser(environment)) {\n    instances = [{ browser: environment }];\n  } else {\n    return undefined;\n  }\n\n  return {\n    enabled: true,\n    provider: playwright(),\n    screenshotFailures: false,\n    headless: true,\n    instances,\n  };\n}\n\nconst config: UserWorkspaceConfig = {\n  test: {\n    exclude: ['node_modules', 'build', '**/*.spec.*'],\n    globals: true,\n    setupFiles: [resolve(WORKSPACE_ROOT, './test/setupVitest.ts')],\n    environment: 'jsdom',\n    environmentOptions: {\n      jsdom: {\n        pretendToBeVisual: true,\n        url: 'http://localhost',\n      },\n    },\n    browser: getBrowserConfig(),\n    env: {\n      VITEST: 'true',\n    },\n    // Avoid committing tests that influence their own retry\n    retry: process.env.CI ? 1 : 0,\n  },\n  resolve: viteConfig.resolve,\n};\n\nexport default config;\n"
  }
]